Skip to content

Commit c103720

Browse files
committed
Display settings in footer
1 parent 02f8146 commit c103720

File tree

4 files changed

+146
-86
lines changed

4 files changed

+146
-86
lines changed

src/Display.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ArtworkDarkSvg from "./dsfr/artwork/dark.svg";
1010
import ArtworkSystemSvg from "./dsfr/artwork/system.svg";
1111
import { getAssetUrl } from "./lib/tools/getAssetUrl";
1212
import type { HeaderProps } from "./Header";
13+
import type { FooterProps } from "./Footer";
1314

1415
export type DisplayProps = {
1516
className?: string;
@@ -18,7 +19,8 @@ export type DisplayProps = {
1819
const dialogId = "fr-theme-modal";
1920
const dialogTitleId = "fr-theme-modal-title";
2021

21-
export const headerQuickAccessDisplay: HeaderProps.QuickAccessItem.Button = {
22+
export const headerFooterDisplayItem: HeaderProps.QuickAccessItem.Button &
23+
FooterProps.BottomItem.Button = {
2224
"buttonProps": {
2325
"aria-controls": dialogId,
2426
...({ "data-fr-opened": false } as {})

src/Footer.tsx

Lines changed: 107 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { cx } from "./lib/tools/cx";
88
import { assert } from "tsafe/assert";
99
import type { Equals } from "tsafe";
1010
import { createComponentI18nApi } from "./lib/i18n";
11+
import type { FrIconClassName, RiIconClassName } from "./lib/generatedFromCss/classNames";
12+
import { id } from "tsafe/id";
1113

1214
export type FooterProps = {
1315
className?: string;
@@ -20,6 +22,7 @@ export type FooterProps = {
2022
personalDataLinkProps?: RegisteredLinkProps;
2123
cookiesManagementLinkProps?: RegisteredLinkProps;
2224
homeLinkProps: RegisteredLinkProps & { title: string };
25+
bottomItems?: FooterProps.BottomItem[];
2326
classes?: Partial<
2427
Record<
2528
| "root"
@@ -40,6 +43,30 @@ export type FooterProps = {
4043
>;
4144
};
4245

46+
export namespace FooterProps {
47+
export type BottomItem = BottomItem.Link | BottomItem.Button;
48+
49+
export namespace BottomItem {
50+
export type Common = {
51+
iconId?: FrIconClassName | RiIconClassName;
52+
text: ReactNode;
53+
};
54+
55+
export type Link = Common & {
56+
linkProps: RegisteredLinkProps;
57+
buttonProps?: never;
58+
};
59+
60+
export type Button = Common & {
61+
linkProps?: undefined;
62+
buttonProps: React.DetailedHTMLProps<
63+
React.ButtonHTMLAttributes<HTMLButtonElement>,
64+
HTMLButtonElement
65+
>;
66+
};
67+
}
68+
}
69+
4370
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-footer> */
4471
export const Footer = memo(
4572
forwardRef<HTMLDivElement, FooterProps>((props, ref) => {
@@ -55,6 +82,7 @@ export const Footer = memo(
5582
termsLinkProps,
5683
personalDataLinkProps,
5784
cookiesManagementLinkProps,
85+
bottomItems = [],
5886
...rest
5987
} = props;
6088

@@ -131,71 +159,85 @@ export const Footer = memo(
131159
</div>
132160
<div className={cx(fr.cx("fr-footer__bottom"), classes.bottom)}>
133161
<ul className={cx(fr.cx("fr-footer__bottom-list"), classes.bottomList)}>
134-
{(
135-
[
136-
["website map", websiteMapLinkProps],
137-
["accessibility", accessibilityLinkProps],
138-
["terms", termsLinkProps],
139-
["personal data", personalDataLinkProps],
140-
["cookies managment", cookiesManagementLinkProps]
141-
] as const
142-
)
143-
.filter(
144-
([section, linkProps]) =>
145-
section === "accessibility" || linkProps !== undefined
146-
)
147-
.map(([section, linkProps], i) => (
148-
<li
149-
key={i}
150-
className={cx(
151-
fr.cx("fr-footer__bottom-item"),
152-
classes.bottomItem
153-
)}
154-
>
155-
{section === "accessibility"
156-
? (() => {
157-
const text = `${t("accessibility")}: ${t(
158-
accessibility
159-
)}`;
162+
{[
163+
...(websiteMapLinkProps === undefined
164+
? []
165+
: [
166+
id<FooterProps.BottomItem>({
167+
"text": t("website map"),
168+
"linkProps": websiteMapLinkProps
169+
})
170+
]),
171+
id<FooterProps.BottomItem>({
172+
"text": `${t("accessibility")}: ${t(accessibility)}`,
173+
"linkProps": accessibilityLinkProps ?? {}
174+
}),
175+
...(termsLinkProps === undefined
176+
? []
177+
: [
178+
id<FooterProps.BottomItem>({
179+
"text": t("terms"),
180+
"linkProps": termsLinkProps
181+
})
182+
]),
183+
...(personalDataLinkProps === undefined
184+
? []
185+
: [
186+
id<FooterProps.BottomItem>({
187+
"text": t("personal data"),
188+
"linkProps": personalDataLinkProps
189+
})
190+
]),
191+
...(cookiesManagementLinkProps === undefined
192+
? []
193+
: [
194+
id<FooterProps.BottomItem>({
195+
"text": t("cookies management"),
196+
"linkProps": cookiesManagementLinkProps
197+
})
198+
]),
199+
...bottomItems
200+
].map(({ iconId, text, buttonProps, linkProps }, i) => (
201+
<li
202+
key={i}
203+
className={cx(
204+
fr.cx("fr-footer__bottom-item"),
205+
classes.bottomItem
206+
)}
207+
>
208+
{(() => {
209+
const className = cx(
210+
fr.cx(
211+
"fr-footer__bottom-link",
212+
...(iconId !== undefined
213+
? ([iconId, "fr-link--icon-left"] as const)
214+
: [])
215+
),
216+
classes.bottomLink
217+
);
160218

161-
return linkProps === undefined ? (
162-
<a
163-
className={cx(
164-
fr.cx("fr-footer__bottom-link"),
165-
classes.bottomLink
166-
)}
167-
href="#"
168-
>
169-
{text}
170-
</a>
171-
) : (
172-
<Link
173-
{...linkProps}
174-
className={cx(
175-
fr.cx("fr-footer__bottom-link"),
176-
classes.bottomLink,
177-
linkProps.className
178-
)}
179-
>
180-
{text}
181-
</Link>
182-
);
183-
})()
184-
: (assert(linkProps !== undefined),
185-
(
186-
<Link
187-
{...linkProps}
188-
className={cx(
189-
fr.cx("fr-footer__bottom-link"),
190-
classes.bottomLink,
191-
linkProps.className
192-
)}
193-
>
194-
{t(section)}
195-
</Link>
196-
))}
197-
</li>
198-
))}
219+
return linkProps !== undefined ? (
220+
Object.keys(linkProps).length === 0 ? (
221+
<span className={className}>{text}</span>
222+
) : (
223+
<Link
224+
{...linkProps}
225+
className={cx(className, linkProps.className)}
226+
>
227+
{text}
228+
</Link>
229+
)
230+
) : (
231+
<button
232+
{...buttonProps}
233+
className={cx(className, buttonProps.className)}
234+
>
235+
{text}
236+
</button>
237+
);
238+
})()}
239+
</li>
240+
))}
199241
</ul>
200242
<div className={cx(fr.cx("fr-footer__bottom-copy"), classes.bottomCopy)}>
201243
<p>
@@ -231,7 +273,7 @@ const { useTranslation, addFooterTranslations } = createComponentI18nApi({
231273
"fully compliant": "totalement conforme",
232274
"terms": "Mentions légales",
233275
"personal data": "Données personnelles",
234-
"cookies managment": "Gestion des cookies",
276+
"cookies management": "Gestion des cookies",
235277
"license mention": "Sauf mention contraire, tous les contenus de ce site sont sous",
236278
"etalab license": "licence etalab-2.0"
237279
/* spell-checker: enable */

stories/Display.stories.tsx

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as React from "react";
2+
import { fr } from "../dist";
23
import { Header } from "../dist/Header";
3-
import { Display, headerQuickAccessDisplay } from "../dist/Display";
4+
import { Footer } from "../dist/Footer";
5+
import { Display, headerFooterDisplayItem } from "../dist/Display";
46
import { sectionName } from "./sectionName";
57
import { getStoryFactory } from "./getStory";
68
import { symToStr } from "tsafe/symToStr";
@@ -21,20 +23,27 @@ the theme state.
2123
2224
\`\`\`tsx
2325
import { Header } from "@codegouvfr/react-dsfr/Header";
24-
import { Display, headerQuickAccessDisplay } from "@codegouvfr/react-dsfr/Display";
26+
import { Display, headerFooterDisplayItem } from "@codegouvfr/react-dsfr/Display";
2527
2628
function App(){
2729
2830
return (
2931
<>
3032
<Header
31-
// other header props...
33+
// other Header props...
3234
quickAccessItems={[
3335
// other quick access items...
34-
headerQuickAccessDisplay
36+
headerFooterDisplayItem
3537
]}
3638
>
3739
{/* ... your app ...*/}
40+
<Footer
41+
// other Footer props...
42+
bottomItems={[
43+
// other other bottom items...
44+
headerFooterDisplayItem
45+
]}
46+
/>
3847
<Display />
3948
<>
4049
);
@@ -47,24 +56,34 @@ function App(){
4756

4857
export default meta;
4958

59+
const brandTop = (
60+
<>
61+
INTITULE
62+
<br />
63+
OFFICIEL
64+
</>
65+
);
66+
67+
const homeLinkProps = {
68+
"href": "#",
69+
"title": "Accueil - Nom de l’entité (ministère, secrétariat d‘état, gouvernement)"
70+
};
71+
5072
function Story() {
5173
return (
5274
<>
5375
<Header
54-
brandTop={
55-
<>
56-
INTITULE
57-
<br />
58-
OFFICIEL
59-
</>
60-
}
76+
brandTop={brandTop}
6177
serviceTitle="Nom du site / service"
62-
homeLinkProps={{
63-
"href": "#",
64-
"title":
65-
"Accueil - Nom de l’entité (ministère, secrétariat d‘état, gouvernement)"
66-
}}
67-
quickAccessItems={[headerQuickAccessDisplay]}
78+
homeLinkProps={homeLinkProps}
79+
quickAccessItems={[headerFooterDisplayItem]}
80+
/>
81+
<Footer
82+
className={fr.cx("fr-mt-5v")}
83+
brandTop={brandTop}
84+
homeLinkProps={homeLinkProps}
85+
accessibility="fully compliant"
86+
bottomItems={[headerFooterDisplayItem]}
6887
/>
6988
<Display />
7089
</>

stories/Footer.stories.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,6 @@ export const Default = getStory({
7272
"websiteMapLinkProps": {
7373
"href": "#"
7474
},
75-
"accessibilityLinkProps": {
76-
"href": "#"
77-
},
7875
"termsLinkProps": {
7976
"href": "#"
8077
},

0 commit comments

Comments
 (0)