Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions apps/site/components/withFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand All @@ -18,7 +19,10 @@ const WithFooter: FC = () => {

const navigation = {
socialLinks,
footerLinks: footerLinks.map(link => ({ ...link, text: t(link.text) })),
footerLinks: footerLinks.map(link => ({
...link,
translation: t(link.text),
})),
};

const primary = (
Expand Down Expand Up @@ -50,12 +54,14 @@ const WithFooter: FC = () => {
</div>
);

const legal = <WithLegal footerLinks={navigation.footerLinks} />;

return (
<Footer
navigation={navigation}
as={Link}
pathname={pathname}
slots={{ primary }}
slots={{ primary, legal }}
/>
);
};
Expand Down
77 changes: 77 additions & 0 deletions apps/site/components/withLegal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useTranslations } from 'next-intl';

import Link from '#site/components/Link';

import type { FC } from 'react';

type LegalProps = {
footerLinks: Array<{
text: string;
link: string;
translation: 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 = [
'foundationName',
'trademarkPolicy',
'trademarkList',
'termsOfUse',
'privacyPolicy',
'bylaws',
'codeOfConduct',
'cookiePolicy',
];

const WithLegal: FC<LegalProps> = ({ 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.foundationName"
},
*
*
* @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) => (
<Link href={getLinkFromTranslationKey(key)}>{chunks}</Link>
);
return acc;
},
{} as Record<string, (text: React.ReactNode) => React.ReactNode>
);

return (
<>
<p>{t.rich('components.containers.footer.legal', richComponents)}</p>

<p>
{footerLinks.map((link, index) => (
<span key={link.link}>
<Link href={link.link}>{link.translation}</Link>
{index < footerLinks.length - 1 && ' | '}
</span>
))}
</p>
</>
);
};

export default WithLegal;
28 changes: 22 additions & 6 deletions apps/site/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.foundationName"
},
{
"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": [
Expand Down
9 changes: 7 additions & 2 deletions packages/i18n/src/locales/en.json
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is the only place the new writing is placed. It's slightly duplicative between legal paragraph and the links which are used later, but I think it's acceptable considering alternatives

Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
"components": {
"containers": {
"footer": {
"legal": "Copyright <foundationName>OpenJS Foundation</foundationName> and Node.js contributors. All rights reserved. The <foundationName>OpenJS Foundation</foundationName> has registered trademarks and uses trademarks. For a list of trademarks of the <foundationName>OpenJS Foundation</foundationName>, please see our <trademarkPolicy>Trademark Policy</trademarkPolicy> and <trademarkList>Trademark List</trademarkList>. Trademarks and logos not indicated on the <trademarkList>list of OpenJS Foundation trademarks</trademarkList> are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.",
"links": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need these individual keys?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The links? The diff is hard to see if you are point at line 5 or line 6.

The openjs guidance is a paragraph with links and a list of links afterwards, which makes for duplication...somewhere. I've chosen to put the duplication in the translation strings rather than the code

"openJSFoundation": "OpenJS Foundation",
"trademarkPolicy": "Trademark Policy",
"foundationName": "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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retention of the security policy here means it is ADDED to the OpenJS footer. I think this is acceptable, as we purposely put it there at one point to expand visibility.

},
"releasePills": {
Expand Down
43 changes: 42 additions & 1 deletion packages/ui-components/src/Containers/Footer/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -43,4 +54,34 @@
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 max-xs:font-semibold
text-green-600
dark:text-green-400;

&:hover {
@apply cursor-pointer
text-green-900
dark:text-green-200;
}
}
}
}
67 changes: 24 additions & 43 deletions packages/ui-components/src/Containers/Footer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import classNames from 'classnames';

import NavItem from '#ui/Containers/NavBar/NavItem';
import {
Bluesky,
Expand Down Expand Up @@ -38,6 +40,7 @@ type Navigation = {
type ExtraNavigationSlots = {
primary?: ReactNode;
secondary?: ReactNode;
legal?: ReactNode;
};

type FooterProps = {
Expand All @@ -53,56 +56,34 @@ const Footer: FC<FooterProps> = ({
navigation,
slots,
}) => {
const openJSlink = navigation.footerLinks.at(-1)!;

return (
<footer className={styles.footer}>
<div className={styles.sectionPrimary}>
{slots?.primary}

{navigation.footerLinks.slice(0, -1).map(item => (
<NavItem
key={item.link}
type="footer"
href={item.link}
as={as}
pathname={pathname}
>
{item.text}
</NavItem>
))}
</div>

<div className={styles.sectionSecondary}>
{slots?.secondary}
<div className={styles.row}>
<div className={styles.sectionPrimary}>{slots?.primary}</div>

<NavItem
type="footer"
href={openJSlink.link}
as={as}
pathname={pathname}
>
&copy; {openJSlink.text}
</NavItem>
<div className={styles.sectionSecondary}>
{slots?.secondary}

<div className={styles.social}>
{navigation.socialLinks.map(link => {
const SocialIcon = footerSocialIcons[link.icon];
<div className={styles.social}>
{navigation.socialLinks.map(link => {
const SocialIcon = footerSocialIcons[link.icon];

return (
<NavItem
key={link.icon}
href={link.link}
type="footer"
as={as}
pathname={pathname}
>
<SocialIcon width={20} height={20} aria-label={link.link} />
</NavItem>
);
})}
return (
<NavItem
key={link.icon}
href={link.link}
type="footer"
as={as}
pathname={pathname}
>
<SocialIcon width={20} height={20} aria-label={link.link} />
</NavItem>
);
})}
</div>
</div>
</div>
<div className={classNames(styles.row, styles.legal)}>{slots?.legal}</div>
</footer>
);
};
Expand Down
Loading