From 56ebca6de872b66bca9014f37864fd6d6b579818 Mon Sep 17 00:00:00 2001 From: bmuenzenmeyer Date: Sat, 31 Jan 2026 10:35:49 -0600 Subject: [PATCH 01/10] introduce third slot to footer --- .../src/Containers/Footer/index.module.css | 36 +++++++++- .../src/Containers/Footer/index.tsx | 67 +++++++------------ 2 files changed, 59 insertions(+), 44 deletions(-) diff --git a/packages/ui-components/src/Containers/Footer/index.module.css b/packages/ui-components/src/Containers/Footer/index.module.css index d52a1027da39a..0e7e950d09490 100644 --- a/packages/ui-components/src/Containers/Footer/index.module.css +++ b/packages/ui-components/src/Containers/Footer/index.module.css @@ -9,13 +9,24 @@ border-neutral-200 bg-white py-4 + text-neutral-500 sm:px-8 - md:flex-row md:justify-between md:py-5 dark:border-neutral-900 dark:bg-neutral-950; + .row { + @apply flex + flex-col + items-center + gap-6 + md:flex-row + md:justify-between + md:gap-0 + md:self-stretch; + } + .sectionPrimary { @apply flex flex-wrap @@ -43,4 +54,27 @@ gap-1; } } + + .legal { + @apply flex + flex-col + gap-2 + px-4 + text-center + text-xs + text-balance + md:px-14; + + p { + @apply text-center + text-sm + text-neutral-800 + dark:text-neutral-500; + } + + a { + @apply text-green-600 + dark:text-green-400; + } + } } diff --git a/packages/ui-components/src/Containers/Footer/index.tsx b/packages/ui-components/src/Containers/Footer/index.tsx index cb006285d8410..419bf770bbb7c 100644 --- a/packages/ui-components/src/Containers/Footer/index.tsx +++ b/packages/ui-components/src/Containers/Footer/index.tsx @@ -1,3 +1,5 @@ +import classNames from 'classnames'; + import NavItem from '#ui/Containers/NavBar/NavItem'; import { Bluesky, @@ -38,6 +40,7 @@ type Navigation = { type ExtraNavigationSlots = { primary?: ReactNode; secondary?: ReactNode; + legal?: ReactNode; }; type FooterProps = { @@ -53,56 +56,34 @@ const Footer: FC = ({ navigation, slots, }) => { - const openJSlink = navigation.footerLinks.at(-1)!; - return (
-
- {slots?.primary} - - {navigation.footerLinks.slice(0, -1).map(item => ( - - {item.text} - - ))} -
- -
- {slots?.secondary} +
+
{slots?.primary}
- - © {openJSlink.text} - +
+ {slots?.secondary} -
- {navigation.socialLinks.map(link => { - const SocialIcon = footerSocialIcons[link.icon]; +
+ {navigation.socialLinks.map(link => { + const SocialIcon = footerSocialIcons[link.icon]; - return ( - - - - ); - })} + return ( + + + + ); + })} +
+
{slots?.legal}
); }; From c38564239320f19505a0c4c04607780ff9518879 Mon Sep 17 00:00:00 2001 From: bmuenzenmeyer Date: Sat, 31 Jan 2026 10:37:13 -0600 Subject: [PATCH 02/10] add and order new keys and links for footer --- apps/site/navigation.json | 28 ++++++++++++++++++++++------ packages/i18n/src/locales/en.json | 9 +++++++-- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/apps/site/navigation.json b/apps/site/navigation.json index a767fafa79e8c..1baeb2e2fc7c4 100644 --- a/apps/site/navigation.json +++ b/apps/site/navigation.json @@ -33,24 +33,40 @@ }, "footerLinks": [ { - "link": "https://trademark-policy.openjsf.org/", - "text": "components.containers.footer.links.trademarkPolicy" + "link": "https://openjsf.org/", + "text": "components.containers.footer.links.openjsf" + }, + { + "link": "https://terms-of-use.openjsf.org/", + "text": "components.containers.footer.links.termsOfUse" }, { "link": "https://privacy-policy.openjsf.org/", "text": "components.containers.footer.links.privacyPolicy" }, + { + "link": "https://bylaws.openjsf.org/", + "text": "components.containers.footer.links.bylaws" + }, { "link": "https://github.com/openjs-foundation/cross-project-council/blob/main/CODE_OF_CONDUCT.md", "text": "components.containers.footer.links.codeOfConduct" }, { - "link": "https://github.com/nodejs/node/security/policy", - "text": "components.containers.footer.links.security" + "link": "https://trademark-policy.openjsf.org/", + "text": "components.containers.footer.links.trademarkPolicy" }, { - "link": "https://openjsf.org/", - "text": "components.containers.footer.links.openJSFoundation" + "link": "https://trademark-list.openjsf.org/", + "text": "components.containers.footer.links.trademarkList" + }, + { + "link": "https://www.linuxfoundation.org/cookies/", + "text": "components.containers.footer.links.cookiePolicy" + }, + { + "link": "https://github.com/nodejs/node/security/policy", + "text": "components.containers.footer.links.security" } ], "socialLinks": [ diff --git a/packages/i18n/src/locales/en.json b/packages/i18n/src/locales/en.json index ecf327b1018ec..9a74ad38e517b 100644 --- a/packages/i18n/src/locales/en.json +++ b/packages/i18n/src/locales/en.json @@ -2,11 +2,16 @@ "components": { "containers": { "footer": { + "legal": "Copyright OpenJS Foundation and Node.js contributors. All rights reserved. The OpenJS Foundation has registered trademarks and uses trademarks. For a list of trademarks of the OpenJS Foundation, please see our Trademark Policy and Trademark List. Trademarks and logos not indicated on the list of OpenJS Foundation trademarks are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.", "links": { - "openJSFoundation": "OpenJS Foundation", - "trademarkPolicy": "Trademark Policy", + "openjsf": "OpenJS Foundation", + "termsOfUse": "Terms of Use", "privacyPolicy": "Privacy Policy", + "bylaws": "Bylaws", "codeOfConduct": "Code of Conduct", + "trademarkPolicy": "Trademark Policy", + "trademarkList": "Trademark List", + "cookiePolicy": "Cookie Policy", "security": "Security Policy" }, "releasePills": { From 40010e9544f1fd0622faafabec6ba13796091016 Mon Sep 17 00:00:00 2001 From: bmuenzenmeyer Date: Sat, 31 Jan 2026 10:37:59 -0600 Subject: [PATCH 03/10] add withLegal with full translation support --- apps/site/components/withLegal.tsx | 76 ++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 apps/site/components/withLegal.tsx diff --git a/apps/site/components/withLegal.tsx b/apps/site/components/withLegal.tsx new file mode 100644 index 0000000000000..a18d9fc41ea4b --- /dev/null +++ b/apps/site/components/withLegal.tsx @@ -0,0 +1,76 @@ +import { useTranslations } from 'next-intl'; + +import Link from '#site/components/Link'; + +import type { FC } from 'react'; + +type LegalProps = { + footerLinks: Array<{ + text: string; + link: string; + }>; +}; + +/** + * These keys match the following locations, and are kept in sync to lessen duplication: + * - translation keys within [locale].json components.containers.footer.links + * - keys within the large [locale].json components.containers.footer.legal paragraph + * - used directly to find the passed links from navigation.footerLinks + */ +const RICH_TRANSLATION_KEYS = [ + 'openjsf', + 'trademarkPolicy', + 'trademarkList', + 'termsOfUse', + 'privacyPolicy', + 'bylaws', + 'codeOfConduct', + 'cookiePolicy', +]; + +const WithLegal: FC = ({ footerLinks }) => { + const t = useTranslations(); + + /** + * Takes the footerLinks from navigation constants and returns the link based on the final part of the translation key. + * + * Example: { + "link": "https://openjsf.org/", + "text": "components.containers.footer.links.openjsf" + }, + * + * + * @param key the final part of a translation string + * @returns the link URL matching the translation key + */ + const getLinkFromTranslationKey = (key: string) => { + return footerLinks.find(link => link.text.split('.').pop() === key)?.link; + }; + + const richComponents = RICH_TRANSLATION_KEYS.reduce( + (acc, key) => { + acc[key] = (chunks: React.ReactNode) => ( + {chunks} + ); + return acc; + }, + {} as Record JSX.Element> + ); + + return ( + <> +

{t.rich('components.containers.footer.legal', richComponents)}

+ +

+ {footerLinks.map((link, index) => ( + + {link.text} + {index < footerLinks.length - 1 && ' | '} + + ))} +

+ + ); +}; + +export default WithLegal; From 556a98e964c349f7725d27a505cfda04682cad80 Mon Sep 17 00:00:00 2001 From: bmuenzenmeyer Date: Sat, 31 Jan 2026 10:38:54 -0600 Subject: [PATCH 04/10] add withLegal to the new legal slot --- apps/site/components/withFooter.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/site/components/withFooter.tsx b/apps/site/components/withFooter.tsx index b6fb622df253f..f7b5792511de7 100644 --- a/apps/site/components/withFooter.tsx +++ b/apps/site/components/withFooter.tsx @@ -8,6 +8,7 @@ import { siteNavigation } from '#site/next.json.mjs'; import type { FC } from 'react'; +import WithLegal from './withLegal'; import WithNodeRelease from './withNodeRelease'; const WithFooter: FC = () => { @@ -50,12 +51,14 @@ const WithFooter: FC = () => { ); + const legal = ; + return (