diff --git a/src/Footer.tsx b/src/Footer.tsx index 0f0b69868..aeca0c505 100644 --- a/src/Footer.tsx +++ b/src/Footer.tsx @@ -171,13 +171,13 @@ export const Footer = memo( assert>(); const { brandTop, homeLinkProps } = (() => { - const wrap = getBrandTopAndHomeLinkProps(); const brandTop = brandTop_prop ?? wrap?.brandTop; const homeLinkProps = homeLinkProps_prop ?? wrap?.homeLinkProps; - const exceptionMessage = " hasn't been provided to the Footer and we cannot retrieve it from the Header (it's probably client side)"; + const exceptionMessage = + " hasn't been provided to the Footer and we cannot retrieve it from the Header (it's probably client side)"; if (brandTop === undefined) { throw new Error(symToStr({ brandTop }) + exceptionMessage); @@ -187,11 +187,9 @@ export const Footer = memo( throw new Error(symToStr({ homeLinkProps }) + exceptionMessage); } - return { brandTop, homeLinkProps}; - + return { brandTop, homeLinkProps }; })(); - const { Link } = getLink(); const { t } = useTranslation(); @@ -428,11 +426,11 @@ export const Footer = memo( ...(websiteMapLinkProps === undefined ? [] : [ - id({ - "text": t("website map"), - "linkProps": websiteMapLinkProps - }) - ]), + id({ + "text": t("website map"), + "linkProps": websiteMapLinkProps + }) + ]), id({ "text": `${t("accessibility")}: ${t(accessibility)}`, "linkProps": accessibilityLinkProps ?? {} @@ -440,64 +438,69 @@ export const Footer = memo( ...(termsLinkProps === undefined ? [] : [ - id({ - "text": t("terms"), - "linkProps": termsLinkProps - }) - ]), + id({ + "text": t("terms"), + "linkProps": termsLinkProps + }) + ]), ...(personalDataLinkProps === undefined ? [] : [ - id({ - "text": t("personal data"), - "linkProps": personalDataLinkProps - }) - ]), + id({ + "text": t("personal data"), + "linkProps": personalDataLinkProps + }) + ]), ...(cookiesManagementButtonProps === undefined ? // one or the other, but not both. Priority to button for consent modal control. - cookiesManagementLinkProps === undefined + cookiesManagementLinkProps === undefined ? [] : [ - id({ - "text": t("cookies management"), - "linkProps": cookiesManagementLinkProps - }) - ] + id({ + "text": t("cookies management"), + "linkProps": cookiesManagementLinkProps + }) + ] : [ - id({ - "text": t("cookies management"), - "buttonProps": - cookiesManagementButtonProps.nativeButtonProps - }) - ]), + id({ + "text": t("cookies management"), + "buttonProps": + cookiesManagementButtonProps.nativeButtonProps + }) + ]), ...bottomItems - ].map((bottomItem, i) => -
  • - { - !typeGuard( - bottomItem, - bottomItem instanceof Object && "text" in bottomItem - ) ? ( - bottomItem - ) : ( - - ) - } + ].map((bottomItem, i) => ( +
  • + {!typeGuard( + bottomItem, + bottomItem instanceof Object && "text" in bottomItem + ) ? ( + bottomItem + ) : ( + + )}
  • - )} + ))}

    {license === undefined ? t("license mention", { - "licenseUrl": - "https://github.com/etalab/licence-ouverte/blob/master/LO.md" - }) + "licenseUrl": + "https://github.com/etalab/licence-ouverte/blob/master/LO.md" + }) : license}

    diff --git a/src/Header.tsx b/src/Header.tsx index 05fd72bcf..a4f54f2ae 100644 --- a/src/Header.tsx +++ b/src/Header.tsx @@ -12,6 +12,7 @@ import type { MainNavigationProps } from "./MainNavigation"; import { MainNavigation } from "./MainNavigation"; import { Display } from "./Display/Display"; import { setBrandTopAndHomeLinkProps } from "./zz_internal/brandTopAndHomeLinkProps"; +import { typeGuard } from "tsafe/typeGuard"; export type HeaderProps = { className?: string; @@ -21,7 +22,7 @@ export type HeaderProps = { serviceTagline?: ReactNode; navigation?: MainNavigationProps.Item[] | ReactNode; /** There should be at most three of them */ - quickAccessItems?: HeaderProps.QuickAccessItem[]; + quickAccessItems?: (HeaderProps.QuickAccessItem | ReactNode)[]; operatorLogo?: { orientation: "horizontal" | "vertical"; /** @@ -125,22 +126,15 @@ export const Header = memo( const quickAccessNode = (
      - {quickAccessItems.map(({ iconId, text, buttonProps, linkProps }, i) => ( + {quickAccessItems.map((quickAccessItem, i) => (
    • - {linkProps !== undefined ? ( - - {text} - + {!typeGuard( + quickAccessItem, + quickAccessItem instanceof Object && "text" in quickAccessItem + ) ? ( + quickAccessItem ) : ( - + )}
    • ))} @@ -406,3 +400,38 @@ addHeaderTranslations({ }); export { addHeaderTranslations }; + +export type HeaderQuickAccessItemProps = { + className?: string; + quickAccessItem: HeaderProps.QuickAccessItem; +}; + +export function HeaderQuickAccessItem(props: HeaderQuickAccessItemProps): JSX.Element { + const { className, quickAccessItem } = props; + + const { Link } = getLink(); + + return quickAccessItem.linkProps !== undefined ? ( + + {quickAccessItem.text} + + ) : ( + + ); +} diff --git a/stories/Header.stories.tsx b/stories/Header.stories.tsx index d3692daa2..c89a3f03e 100644 --- a/stories/Header.stories.tsx +++ b/stories/Header.stories.tsx @@ -13,7 +13,14 @@ const { meta, getStory } = getStoryFactory({ - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/en-tete) - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Header.tsx) -See also [\\](https://react-dsfr-components.etalab.studio/?path=/docs/components-mainnavigation)`, +See also [\\](https://react-dsfr-components.etalab.studio/?path=/docs/components-mainnavigation) + +> Note for Next App Router: If you want to have \`quickAccessItems\` client side without having to wrap the whole \`
      \` +> component within a \`"use client";\` directive you can use the \`\` component as demonstrated +[here](https://github.com/codegouvfr/react-dsfr/blob/703961e480eb5f8d39e571fdd64de725aa1d4ff9/test/integration/next-appdir/app/layout.tsx#L91) and +[here](https://github.com/codegouvfr/react-dsfr/blob/703961e480eb5f8d39e571fdd64de725aa1d4ff9/test/integration/next-appdir/ui/ClientHeaderQuickAccessItem.tsx#L1-L18). + +`, "argTypes": { "brandTop": { "control": { "type": null }, diff --git a/test/integration/next-appdir/app/layout.tsx b/test/integration/next-appdir/app/layout.tsx index 838d69931..21d01d184 100644 --- a/test/integration/next-appdir/app/layout.tsx +++ b/test/integration/next-appdir/app/layout.tsx @@ -13,6 +13,7 @@ import { fr } from "@codegouvfr/react-dsfr"; import { Navigation } from "./Navigation"; import Link from "next/link"; import { ClientFooterItem } from "../ui/ClientFooterItem"; +import { ClientHeaderQuickAccessItem } from "../ui/ClientHeaderQuickAccessItem"; declare module "@codegouvfr/react-dsfr/gdpr" { interface RegisterGdprServices { @@ -86,7 +87,8 @@ export default function RootLayout({ children }: { children: JSX.Element; }) { href: `mailto:${"joseph.garrone@code.gouv.fr"}`, }, text: "Nous contacter", - } + }, + ]} navigation={} /> diff --git a/test/integration/next-appdir/ui/ClientHeaderQuickAccessItem.tsx b/test/integration/next-appdir/ui/ClientHeaderQuickAccessItem.tsx new file mode 100644 index 000000000..09e3d1f0d --- /dev/null +++ b/test/integration/next-appdir/ui/ClientHeaderQuickAccessItem.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { HeaderQuickAccessItem } from "@codegouvfr/react-dsfr/Header"; + +export function ClientHeaderQuickAccessItem() { + return ( + + ); +} \ No newline at end of file