aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/molecules
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/components/molecules
parent9492414d4ae94045eff4e06f636529bc0e71cb06 (diff)
feat(components): add a Colophon component
Diffstat (limited to 'src/components/molecules')
-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
6 files changed, 201 insertions, 0 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';