aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-10-20 15:23:47 +0200
committerArmand Philippot <git@armandphilippot.com>2023-11-11 18:14:41 +0100
commit98044be08600daf6bd7c7e1a4adada319dbcbbaf (patch)
tree73b5509d2061a984a8f1e22ff776fdcdb764ecce /src
parent9492414d4ae94045eff4e06f636529bc0e71cb06 (diff)
feat(components): add a Colophon component
Diffstat (limited to 'src')
-rw-r--r--src/components/molecules/colophon/colophon.module.scss20
-rw-r--r--src/components/molecules/colophon/colophon.stories.tsx83
-rw-r--r--src/components/molecules/colophon/colophon.test.tsx37
-rw-r--r--src/components/molecules/colophon/colophon.tsx59
-rw-r--r--src/components/molecules/colophon/index.ts1
-rw-r--r--src/components/molecules/index.ts1
-rw-r--r--src/components/organisms/layout/site-footer.module.scss11
-rw-r--r--src/components/organisms/layout/site-footer.test.tsx4
-rw-r--r--src/components/organisms/layout/site-footer.tsx34
-rw-r--r--src/i18n/en.json4
-rw-r--r--src/i18n/fr.json4
11 files changed, 211 insertions, 47 deletions
diff --git a/src/components/molecules/colophon/colophon.module.scss b/src/components/molecules/colophon/colophon.module.scss
new file mode 100644
index 0000000..725ed58
--- /dev/null
+++ b/src/components/molecules/colophon/colophon.module.scss
@@ -0,0 +1,20 @@
+@use "../../../styles/abstracts/mixins" as mix;
+
+.list {
+ ::marker {
+ color: var(--color-fg);
+ }
+
+ @include mix.media("screen") {
+ @include mix.dimensions(null, "sm") {
+ flex-flow: column;
+ }
+ }
+}
+
+.legal {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ gap: var(--spacing-2xs);
+}
diff --git a/src/components/molecules/colophon/colophon.stories.tsx b/src/components/molecules/colophon/colophon.stories.tsx
new file mode 100644
index 0000000..7baecad
--- /dev/null
+++ b/src/components/molecules/colophon/colophon.stories.tsx
@@ -0,0 +1,83 @@
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Icon } from '../../atoms';
+import { Copyright } from '../copyright';
+import { Colophon } from './colophon';
+
+/**
+ * Colophon - Storybook Meta
+ */
+export default {
+ title: 'Molecules/Colophon',
+ component: Colophon,
+ argTypes: {
+ copyright: {
+ description: 'The website copyright.',
+ type: {
+ name: 'object',
+ required: true,
+ value: {},
+ },
+ },
+ links: {
+ control: {
+ type: 'object',
+ },
+ description:
+ 'Adds links to the colophon (a Legal Notice link for example).',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'object',
+ required: false,
+ value: {},
+ },
+ },
+ },
+} as ComponentMeta<typeof Colophon>;
+
+const Template: ComponentStory<typeof Colophon> = (args) => (
+ <Colophon {...args} />
+);
+
+/**
+ * Colophon Stories - Default
+ */
+export const Default = Template.bind({});
+Default.args = {
+ copyright: <Copyright from="2021" owner="Brand" to="2023" />,
+};
+
+/**
+ * Colophon Stories - WithLicense
+ */
+export const WithLicense = Template.bind({});
+WithLicense.args = {
+ copyright: <Copyright from="2021" owner="Brand" to="2023" />,
+ license: <Icon heading="CC BY SA" shape="cc-by-sa" />,
+};
+
+/**
+ * Colophon Stories - WithLinks
+ */
+export const WithLinks = Template.bind({});
+WithLinks.args = {
+ copyright: <Copyright from="2021" owner="Brand" to="2023" />,
+ links: [
+ { href: '#legal', id: 'item-1', label: 'Legal notice' },
+ { href: '#credits', id: 'item-2', label: 'Credits' },
+ ],
+};
+
+/**
+ * Colophon Stories - WithLicenseAndLinks
+ */
+export const WithLicenseAndLinks = Template.bind({});
+WithLicenseAndLinks.args = {
+ copyright: <Copyright from="2021" owner="Brand" to="2023" />,
+ license: <Icon heading="CC BY SA" shape="cc-by-sa" />,
+ links: [
+ { href: '#legal', id: 'item-1', label: 'Legal notice' },
+ { href: '#credits', id: 'item-2', label: 'Credits' },
+ ],
+};
diff --git a/src/components/molecules/colophon/colophon.test.tsx b/src/components/molecules/colophon/colophon.test.tsx
new file mode 100644
index 0000000..37b2ef4
--- /dev/null
+++ b/src/components/molecules/colophon/colophon.test.tsx
@@ -0,0 +1,37 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import { Colophon, type ColophonLink } from './colophon';
+
+describe('Colophon', () => {
+ it('renders a copyright', () => {
+ const copyright = 'ea aliquam porro';
+
+ render(<Colophon copyright={copyright} />);
+
+ expect(rtlScreen.getByRole('listitem')).toHaveTextContent(copyright);
+ });
+
+ it('can render a license', () => {
+ const copyright = 'ea aliquam porro';
+ const license = 'ea facere non';
+
+ render(<Colophon copyright={copyright} license={license} />);
+
+ const items = rtlScreen.getAllByRole('listitem');
+
+ expect(items[items.length - 1]).toHaveTextContent(license);
+ });
+
+ it('can render some links', () => {
+ const copyright = 'ea aliquam porro';
+ const links: ColophonLink[] = [
+ { href: '#blanditiis', id: 'perferendis', label: 'quis' },
+ { href: '#mollitia', id: 'sint', label: 'ducimus' },
+ ];
+
+ render(<Colophon copyright={copyright} links={links} />);
+
+ // The links + the copyright
+ expect(rtlScreen.getAllByRole('listitem')).toHaveLength(links.length + 1);
+ });
+});
diff --git a/src/components/molecules/colophon/colophon.tsx b/src/components/molecules/colophon/colophon.tsx
new file mode 100644
index 0000000..9676eb2
--- /dev/null
+++ b/src/components/molecules/colophon/colophon.tsx
@@ -0,0 +1,59 @@
+import {
+ type ForwardRefRenderFunction,
+ forwardRef,
+ type ReactNode,
+} from 'react';
+import { List, ListItem, type ListProps } from '../../atoms';
+import { NavLink } from '../nav';
+import styles from './colophon.module.scss';
+
+export type ColophonLink = {
+ id: string;
+ href: string;
+ label: ReactNode;
+};
+
+export type ColophonProps = Omit<ListProps<false, false>, 'children'> & {
+ /**
+ * The website copyright.
+ */
+ copyright: ReactNode;
+ /**
+ * The website license.
+ */
+ license?: ReactNode;
+ /**
+ * The colophon links (ie. legal notice)
+ */
+ links?: ColophonLink[];
+};
+
+const ColophonWithRef: ForwardRefRenderFunction<
+ HTMLUListElement,
+ ColophonProps
+> = ({ className = '', copyright, license, links, ...props }, ref) => {
+ const colophonClass = `${styles.list} ${className}`;
+
+ return (
+ <List
+ {...props}
+ className={colophonClass}
+ isInline
+ ref={ref}
+ // eslint-disable-next-line react/jsx-no-literals -- Spacing allowed
+ spacing="2xs"
+ >
+ <ListItem className={styles.legal} hideMarker>
+ {copyright}
+ {license}
+ </ListItem>
+ {links?.map(({ id, ...link }) => (
+ <ListItem key={id}>
+ <NavLink {...link} />
+ </ListItem>
+ ))}
+ </List>
+ );
+};
+
+export const Colophon = forwardRef(ColophonWithRef);
diff --git a/src/components/molecules/colophon/index.ts b/src/components/molecules/colophon/index.ts
new file mode 100644
index 0000000..6e6f9cd
--- /dev/null
+++ b/src/components/molecules/colophon/index.ts
@@ -0,0 +1 @@
+export * from './colophon';
diff --git a/src/components/molecules/index.ts b/src/components/molecules/index.ts
index d53d999..c2c94d0 100644
--- a/src/components/molecules/index.ts
+++ b/src/components/molecules/index.ts
@@ -3,6 +3,7 @@ export * from './buttons';
export * from './card';
export * from './code';
export * from './collapsible';
+export * from './colophon';
export * from './copyright';
export * from './forms';
export * from './grid';
diff --git a/src/components/organisms/layout/site-footer.module.scss b/src/components/organisms/layout/site-footer.module.scss
index a502763..158c419 100644
--- a/src/components/organisms/layout/site-footer.module.scss
+++ b/src/components/organisms/layout/site-footer.module.scss
@@ -18,17 +18,6 @@
}
}
-.nav {
- @include mix.media("screen") {
- @include mix.dimensions("sm") {
- &::before {
- content: "\2022";
- margin-right: var(--spacing-2xs);
- }
- }
- }
-}
-
.back-to-top {
position: fixed;
bottom: calc(var(--toolbar-size, 0px) + var(--spacing-md));
diff --git a/src/components/organisms/layout/site-footer.test.tsx b/src/components/organisms/layout/site-footer.test.tsx
index 11ddd7f..3ad4022 100644
--- a/src/components/organisms/layout/site-footer.test.tsx
+++ b/src/components/organisms/layout/site-footer.test.tsx
@@ -7,9 +7,9 @@ const copyright: SiteFooterProps['copyright'] = {
owner: 'Lorem ipsum',
};
-const navItems: SiteFooterProps['navItems'] = [
+const navItems = [
{ id: 'legal-notice', href: '#', label: 'Legal notice' },
-];
+] satisfies SiteFooterProps['navItems'];
describe('SiteFooter', () => {
it('renders the website copyright', () => {
diff --git a/src/components/organisms/layout/site-footer.tsx b/src/components/organisms/layout/site-footer.tsx
index 9ed5ce6..ccab051 100644
--- a/src/components/organisms/layout/site-footer.tsx
+++ b/src/components/organisms/layout/site-footer.tsx
@@ -4,20 +4,13 @@ import { Footer } from '../../atoms';
import {
BackToTop,
type BackToTopProps,
- NavList,
- NavItem,
- NavLink,
+ Colophon,
+ type ColophonLink,
type CopyrightProps,
Copyright,
} from '../../molecules';
import styles from './site-footer.module.scss';
-export type FooterLinks = {
- id: string;
- href: string;
- label: string;
-};
-
export type SiteFooterProps = {
/**
* Set additional classnames to the back to top button.
@@ -38,7 +31,7 @@ export type SiteFooterProps = {
/**
* The footer nav items.
*/
- navItems?: FooterLinks[];
+ navItems?: ColophonLink[];
/**
* An element id (without hashtag) used as anchor for back to top button.
*/
@@ -59,11 +52,6 @@ export const SiteFooter: FC<SiteFooterProps> = ({
topId,
}) => {
const intl = useIntl();
- const ariaLabel = intl.formatMessage({
- defaultMessage: 'Footer',
- description: 'SiteFooter: an accessible name for the footer nav',
- id: 'pRzkFR',
- });
const backToTop = intl.formatMessage({
defaultMessage: 'Back to top',
description: 'SiteFooter: an accessible name for the back to top button',
@@ -75,17 +63,11 @@ export const SiteFooter: FC<SiteFooterProps> = ({
return (
<Footer className={footerClass}>
- <Copyright {...copyright} />
- {license}
- {navItems ? (
- <NavList aria-label={ariaLabel} className={styles.nav} isInline>
- {navItems.map(({ id, ...link }) => (
- <NavItem key={id}>
- <NavLink {...link} />
- </NavItem>
- ))}
- </NavList>
- ) : null}
+ <Colophon
+ copyright={<Copyright {...copyright} />}
+ license={license}
+ links={navItems}
+ />
<BackToTop
anchor={backToTopAnchor}
className={btnClass}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 8f552e6..d0f951d 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -623,10 +623,6 @@
"defaultMessage": "Dark theme",
"description": "PrismThemeToggle: dark theme label"
},
- "pRzkFR": {
- "defaultMessage": "Footer",
- "description": "SiteFooter: an accessible name for the footer nav"
- },
"pT5nHk": {
"defaultMessage": "Published on:",
"description": "HomePage: publication date label"
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index b499a1f..318e7a0 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -623,10 +623,6 @@
"defaultMessage": "Thème sombre",
"description": "PrismThemeToggle: dark theme label"
},
- "pRzkFR": {
- "defaultMessage": "Pied de page",
- "description": "SiteFooter: an accessible name for the footer nav"
- },
"pT5nHk": {
"defaultMessage": "Publié le :",
"description": "HomePage: publication date label"