Skip to content

Commit 4b606f8

Browse files
committed
Feature mega menu
1 parent 30450ed commit 4b606f8

File tree

6 files changed

+626
-18
lines changed

6 files changed

+626
-18
lines changed

src/Header/MainNavigation/MainNavigation.tsx

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,20 @@ export type MainNavigationProps = {
1717
className?: string;
1818
items: MainNavigationProps.Item[];
1919
classes?: Partial<
20-
Record<"root" | "list" | "item" | "link" | "btn" | "menu" | "menuList", string>
20+
Record<
21+
| "root"
22+
| "list"
23+
| "item"
24+
| "link"
25+
| "btn"
26+
| "menu"
27+
| "menuList"
28+
| "megaMenu"
29+
| "megaMenuLeader"
30+
| "megaMenuCategory"
31+
| "megaMenuList",
32+
string
33+
>
2134
>;
2235
};
2336

@@ -34,19 +47,22 @@ export namespace MainNavigationProps {
3447
export type Link = Common & {
3548
linkProps: RegisteredLinkProps;
3649
menuLinks?: undefined;
37-
megaMenuProps?: undefined;
50+
megaMenu?: undefined;
3851
};
3952

4053
export type Menu = Common & {
4154
linkProps?: undefined;
4255
menuLinks: MenuProps.Link[];
43-
megaMenuProps?: undefined;
56+
megaMenu?: undefined;
4457
};
4558

4659
export type MegaMenu = Common & {
4760
linkProps?: undefined;
4861
menuLinks?: undefined;
49-
megaMenuProps: MegaMenuProps;
62+
megaMenu: {
63+
leader?: MegaMenuProps.Leader;
64+
categories: MegaMenuProps.Category[];
65+
};
5066
};
5167
}
5268
}
@@ -86,7 +102,7 @@ export const MainNavigation = memo(
86102
isActive = false,
87103
linkProps,
88104
menuLinks = [],
89-
megaMenuProps
105+
megaMenu
90106
},
91107
i
92108
) => (
@@ -126,14 +142,20 @@ export const MainNavigation = memo(
126142
id={getMenuId(i)}
127143
/>
128144
)}
129-
{megaMenuProps !== undefined && (
145+
{megaMenu !== undefined && (
130146
<MegaMenu
131-
{...megaMenuProps}
132-
className={cx(
133-
fr.cx("fr-collapse"),
134-
megaMenuProps.className
135-
)}
147+
classes={{
148+
"root": cx(
149+
fr.cx("fr-collapse"),
150+
classes.megaMenu
151+
),
152+
"leader": classes.megaMenuLeader,
153+
"category": classes.megaMenuCategory,
154+
"list": classes.menuList
155+
}}
136156
id={getMenuId(i)}
157+
leader={megaMenu.leader}
158+
categories={megaMenu.categories}
137159
/>
138160
)}
139161
</>
Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,160 @@
11
import React, { memo, forwardRef } from "react";
2+
import type { ReactNode } from "react";
23
import { symToStr } from "tsafe/symToStr";
4+
import { createComponentI18nApi } from "../../lib/i18n";
35
import { fr } from "../../lib";
46
import { cx } from "../../lib/tools/cx";
57
import { assert } from "tsafe/assert";
68
import type { Equals } from "tsafe";
9+
import { useLink } from "../../lib/routing";
10+
import type { RegisteredLinkProps } from "../../lib/routing";
711

812
export type MegaMenuProps = {
9-
className?: string;
10-
classes?: Partial<Record<"root", string>>;
13+
classes?: Partial<Record<"root" | "leader" | "category" | "list", string>>;
14+
leader?: MegaMenuProps.Leader;
15+
categories: MegaMenuProps.Category[];
1116
};
17+
export namespace MegaMenuProps {
18+
export type Leader = {
19+
title: ReactNode;
20+
paragraph: ReactNode;
21+
link?: {
22+
linkProps: RegisteredLinkProps;
23+
text: ReactNode;
24+
};
25+
};
26+
27+
export type Category = {
28+
categoryMainLink: {
29+
text: ReactNode;
30+
linkProps: RegisteredLinkProps;
31+
};
32+
links: {
33+
text: ReactNode;
34+
linkProps: RegisteredLinkProps;
35+
isActive?: boolean;
36+
}[];
37+
};
38+
}
1239

1340
export const MegaMenu = memo(
1441
forwardRef<HTMLDivElement, MegaMenuProps & { id: string }>((props, ref) => {
15-
const { className, id, classes = {}, ...rest } = props;
42+
const { id, classes = {}, leader, categories, ...rest } = props;
1643

1744
assert<Equals<keyof typeof rest, never>>();
1845

46+
const { t } = useTranslation();
47+
48+
const { Link } = useLink();
49+
1950
return (
2051
<div
21-
className={cx(fr.cx("fr-mega-menu"), classes.root, className)}
52+
className={cx(fr.cx("fr-mega-menu"), classes.root)}
2253
tabIndex={-1}
2354
id={id}
2455
ref={ref}
2556
{...rest}
2657
>
27-
TODO
58+
<div className={fr.cx("fr-container", "fr-container--fluid", "fr-container-lg")}>
59+
<button
60+
className={fr.cx("fr-link--close", "fr-link")}
61+
aria-controls="mega-menu-775"
62+
>
63+
{t("close")}
64+
</button>
65+
<div className={fr.cx("fr-grid-row", "fr-grid-row-lg--gutters")}>
66+
{leader !== undefined && (
67+
<div
68+
className={fr.cx(
69+
"fr-col-12",
70+
"fr-col-lg-8",
71+
"fr-col-offset-lg-4--right",
72+
"fr-mb-4v"
73+
)}
74+
>
75+
<div className={cx(fr.cx("fr-mega-menu__leader"), classes.leader)}>
76+
<h4 className={fr.cx("fr-h4", "fr-mb-2v")}>{leader.title}</h4>
77+
<p className={fr.cx("fr-hidden", "fr-displayed-lg")}>
78+
{leader.paragraph}
79+
</p>
80+
{leader.link !== undefined && (
81+
<Link
82+
{...leader.link.linkProps}
83+
className={cx(
84+
fr.cx(
85+
"fr-link",
86+
"fr-icon-arrow-right-line",
87+
"fr-link--icon-right",
88+
"fr-link--align-on-content" as any
89+
),
90+
leader.link.linkProps.className
91+
)}
92+
>
93+
{leader.link.text}
94+
</Link>
95+
)}
96+
</div>
97+
</div>
98+
)}
99+
{categories.map(({ categoryMainLink, links }, i) => (
100+
<div className={fr.cx("fr-col-12", "fr-col-lg-3")} key={i}>
101+
<h5
102+
className={cx(
103+
fr.cx("fr-mega-menu__category"),
104+
classes.category
105+
)}
106+
>
107+
<Link
108+
{...categoryMainLink.linkProps}
109+
className={cx(
110+
fr.cx("fr-nav__link"),
111+
categoryMainLink.linkProps.className
112+
)}
113+
>
114+
{categoryMainLink.text}
115+
</Link>
116+
</h5>
117+
<ul className={cx(fr.cx("fr-mega-menu__list"), classes.list)}>
118+
{links.map(({ linkProps, text, isActive }, i) => (
119+
<li key={i}>
120+
<Link
121+
{...linkProps}
122+
className={cx(
123+
fr.cx("fr-nav__link"),
124+
linkProps.className
125+
)}
126+
{...(!isActive ? {} : { "aria-current": "page" })}
127+
>
128+
{text}
129+
</Link>
130+
</li>
131+
))}
132+
</ul>
133+
</div>
134+
))}
135+
</div>
136+
</div>
28137
</div>
29138
);
30139
})
31140
);
32141

33142
MegaMenu.displayName = symToStr({ MegaMenu });
34143

144+
const { useTranslation, addMegaMenuTranslations } = createComponentI18nApi({
145+
"componentName": symToStr({ MegaMenu }),
146+
"frMessages": {
147+
/* spell-checker: disable */
148+
"close": "Fermer"
149+
/* spell-checker: enable */
150+
}
151+
});
152+
153+
addMegaMenuTranslations({
154+
"lang": "en",
155+
"messages": {
156+
"close": "Close"
157+
}
158+
});
159+
35160
export default MegaMenu;

stories/Display.stories.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ function Story() {
5959
</>
6060
}
6161
serviceTitle="Nom du site / service"
62-
homeLinkProps={{ "href": "#" }}
62+
homeLinkProps={{
63+
"href": "#",
64+
"title":
65+
"Accueil - Nom de l’entité (ministère, secrétariat d‘état, gouvernement)"
66+
}}
6367
quickAccessItems={[headerQuickAccessDisplay]}
6468
/>
6569
<Display />

stories/Header.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ const { meta, getStory } = getStoryFactory({
1313
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Header/Header.tsx)
1414
1515
See also [\\<MainNavigation \\/\\>](https://react-dsfr-components.etalab.studio/?path=/docs/components-mainnavigation)`,
16+
"argTypes": {
17+
"brandTop": {
18+
"control": { "type": null }
19+
},
20+
"homeLinkProps": {
21+
"control": { "type": null }
22+
}
23+
},
1624
"disabledProps": ["lang"]
1725
});
1826

0 commit comments

Comments
 (0)