@@ -8,6 +8,8 @@ import { cx } from "./lib/tools/cx";
88import { assert } from "tsafe/assert" ;
99import type { Equals } from "tsafe" ;
1010import { createComponentI18nApi } from "./lib/i18n" ;
11+ import type { FrIconClassName , RiIconClassName } from "./lib/generatedFromCss/classNames" ;
12+ import { id } from "tsafe/id" ;
1113
1214export 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> */
4471export 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 */
0 commit comments