Skip to content

Commit dcbf8dc

Browse files
committed
First draft of the actual Header
1 parent 9869530 commit dcbf8dc

File tree

15 files changed

+680
-222
lines changed

15 files changed

+680
-222
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,10 @@
123123
".": "./dist/lib/index.js",
124124
"./next": "./dist/next.js",
125125
"./mui": "./dist/mui.js",
126+
"./lib": "./dist/lib/index.js",
126127
"./Tabs": "./dist/Tabs.js",
127128
"./Notice": "./dist/Notice.js",
128-
"./Header": "./dist/Header.js",
129+
"./Header": "./dist/Header/index.js",
129130
"./DarkModeSwitch": "./dist/DarkModeSwitch.js",
130131
"./Alert": "./dist/Alert.js",
131132
"./Accordion": "./dist/Accordion.js"

src/Header.tsx

Lines changed: 0 additions & 135 deletions
This file was deleted.

src/Header/Header.tsx

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import React, { memo, forwardRef, useId } from "react";
2+
import type { ReactNode } from "react";
3+
import { fr } from "../lib";
4+
import { createComponentI18nApi } from "../lib/i18n";
5+
import { symToStr } from "tsafe/symToStr";
6+
import { cx } from "../lib/tools/cx";
7+
import type { LinkProps } from "../lib/routing";
8+
import { useLink } from "../lib/routing";
9+
import type { MainNavigationProps } from "./MainNavigation";
10+
import { MainNavigation } from "./MainNavigation";
11+
import { assert } from "tsafe/assert";
12+
import type { Equals } from "tsafe";
13+
14+
//NOTE: WIP
15+
16+
export type HeaderProps = {
17+
className?: string;
18+
brandTop: ReactNode;
19+
serviceTitle?: ReactNode;
20+
serviceTagline?: ReactNode;
21+
/** Don't forget the title on the link for accessibility*/
22+
homeLinkProps: LinkProps;
23+
mainNavigationProps?: MainNavigationProps;
24+
classes?: Partial<
25+
Record<
26+
| "root"
27+
| "body"
28+
| "bodyRow"
29+
| "brand"
30+
| "brandTop"
31+
| "logo"
32+
| "navbar"
33+
| "service"
34+
| "serviceTitle"
35+
| "serviceTagline"
36+
| "menu"
37+
| "menuLinks",
38+
string
39+
>
40+
>;
41+
};
42+
43+
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-header> */
44+
export const Header = memo(
45+
forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
46+
const {
47+
className,
48+
brandTop,
49+
serviceTitle,
50+
serviceTagline,
51+
homeLinkProps,
52+
mainNavigationProps,
53+
classes = {},
54+
...rest
55+
} = props;
56+
57+
assert<Equals<keyof typeof rest, never>>();
58+
59+
const menuButtonId = `button-${useId()}`;
60+
const modalId = `modal-${useId()}`;
61+
62+
const { t } = useTranslation();
63+
64+
const { Link } = useLink();
65+
66+
return (
67+
<header
68+
role="banner"
69+
className={cx(fr.cx("fr-header"), classes.root, className)}
70+
ref={ref}
71+
{...rest}
72+
>
73+
<div className={cx(fr.cx("fr-header__body" as any), classes.body)}>
74+
<div className={fr.cx("fr-container")}>
75+
<div className={cx(fr.cx("fr-header__body-row"), classes.bodyRow)}>
76+
<div
77+
className={cx(
78+
fr.cx("fr-header__brand", "fr-enlarge-link"),
79+
classes.brand
80+
)}
81+
>
82+
<div
83+
className={cx(fr.cx("fr-header__brand-top"), classes.brandTop)}
84+
>
85+
<div className={cx(fr.cx("fr-header__logo"), classes.logo)}>
86+
{(() => {
87+
const children = (
88+
<p className={fr.cx("fr-logo")}>{brandTop}</p>
89+
);
90+
91+
return serviceTitle !== undefined ? (
92+
children
93+
) : (
94+
<Link {...homeLinkProps}>{children}</Link>
95+
);
96+
})()}
97+
</div>
98+
{mainNavigationProps !== undefined && (
99+
<div
100+
className={cx(
101+
fr.cx("fr-header__navbar"),
102+
classes.navbar
103+
)}
104+
>
105+
<button
106+
className={fr.cx("fr-btn--menu", "fr-btn")}
107+
data-fr-opened="false"
108+
aria-controls={modalId}
109+
aria-haspopup="menu"
110+
id={menuButtonId}
111+
title="Menu"
112+
>
113+
{t("menu")}
114+
</button>
115+
</div>
116+
)}
117+
</div>
118+
{serviceTitle !== undefined && (
119+
<div
120+
className={cx(fr.cx("fr-header__service"), classes.service)}
121+
>
122+
<Link {...homeLinkProps}>
123+
<p
124+
className={cx(
125+
fr.cx("fr-header__service-title"),
126+
classes.serviceTitle
127+
)}
128+
>
129+
{serviceTitle}
130+
</p>
131+
</Link>
132+
{serviceTagline !== undefined && (
133+
<p
134+
className={cx(
135+
fr.cx("fr-header__service-tagline" as any),
136+
classes.serviceTagline
137+
)}
138+
>
139+
{serviceTagline}
140+
</p>
141+
)}
142+
</div>
143+
)}
144+
</div>
145+
</div>
146+
</div>
147+
</div>
148+
{mainNavigationProps !== undefined && (
149+
<div
150+
className={cx(fr.cx("fr-header__menu", "fr-modal"), classes.menu)}
151+
id={modalId}
152+
aria-labelledby={menuButtonId}
153+
>
154+
<div className={fr.cx("fr-container")}>
155+
<button
156+
className={fr.cx("fr-btn--close", "fr-btn")}
157+
aria-controls={modalId}
158+
title={t("close")}
159+
>
160+
{t("close")}
161+
</button>
162+
<div
163+
className={cx(fr.cx("fr-header__menu-links"), classes.menuLinks)}
164+
/>
165+
<MainNavigation {...mainNavigationProps} />
166+
</div>
167+
</div>
168+
)}
169+
</header>
170+
);
171+
})
172+
);
173+
174+
Header.displayName = symToStr({ Header });
175+
176+
const { useTranslation, addHeaderTranslations } = createComponentI18nApi({
177+
"componentName": symToStr({ Header }),
178+
"frMessages": {
179+
/* spell-checker: disable */
180+
"menu": "Menu",
181+
"close": "Fermer"
182+
/* spell-checker: enable */
183+
}
184+
});
185+
186+
addHeaderTranslations({
187+
"lang": "en",
188+
"messages": {
189+
"close": "Close"
190+
}
191+
});
192+
193+
export { addHeaderTranslations };

0 commit comments

Comments
 (0)