summaryrefslogtreecommitdiffstats
path: root/src/components/organisms/layout
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-04-12 16:55:59 +0200
committerArmand Philippot <git@armandphilippot.com>2022-04-12 17:07:12 +0200
commit017d01680a933897df6ddd11d2e081730756250b (patch)
tree487170c0a92efa61f82c2d45c0da2a6dfea0ef5d /src/components/organisms/layout
parent4a6f3dbde9adaa671e622215654a1dd9b329610d (diff)
chore: add a Footer component
Diffstat (limited to 'src/components/organisms/layout')
-rw-r--r--src/components/organisms/layout/footer.module.scss39
-rw-r--r--src/components/organisms/layout/footer.stories.tsx74
-rw-r--r--src/components/organisms/layout/footer.test.tsx33
-rw-r--r--src/components/organisms/layout/footer.tsx52
4 files changed, 198 insertions, 0 deletions
diff --git a/src/components/organisms/layout/footer.module.scss b/src/components/organisms/layout/footer.module.scss
new file mode 100644
index 0000000..a809d3c
--- /dev/null
+++ b/src/components/organisms/layout/footer.module.scss
@@ -0,0 +1,39 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
+
+.wrapper {
+ display: flex;
+ flex-flow: column wrap;
+ gap: var(--spacing-xs);
+ place-items: center;
+ place-content: center;
+ padding: var(--spacing-md) 0 calc(var(--toolbar-size) + var(--spacing-md));
+
+ @include mix.media("screen") {
+ @include mix.dimensions("sm") {
+ flex-flow: row wrap;
+ font-size: var(--font-size-sm);
+ }
+ }
+}
+
+.nav {
+ display: flex;
+ flex-flow: row wrap;
+
+ @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));
+ right: var(--spacing-md);
+ transition: all 0.4s ease-in 0s;
+}
diff --git a/src/components/organisms/layout/footer.stories.tsx b/src/components/organisms/layout/footer.stories.tsx
new file mode 100644
index 0000000..2ce7ee1
--- /dev/null
+++ b/src/components/organisms/layout/footer.stories.tsx
@@ -0,0 +1,74 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { IntlProvider } from 'react-intl';
+import FooterComponent from './footer';
+
+export default {
+ title: 'Organisms/Layout',
+ component: FooterComponent,
+ argTypes: {
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Set additional classnames to the footer element.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ copyright: {
+ description: 'The copyright information.',
+ type: {
+ name: 'object',
+ required: true,
+ value: {},
+ },
+ },
+ navItems: {
+ description: 'The footer nav items.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'object',
+ required: false,
+ value: {},
+ },
+ },
+ topId: {
+ control: {
+ type: 'text',
+ },
+ description:
+ 'An element id (without hashtag) used as target by back to top button.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta<typeof FooterComponent>;
+
+const Template: ComponentStory<typeof FooterComponent> = (args) => (
+ <IntlProvider locale="en">
+ <FooterComponent {...args} />
+ </IntlProvider>
+);
+
+const copyright = {
+ dates: { start: '2017', end: '2022' },
+ owner: 'Lorem ipsum',
+ icon: 'CC',
+};
+
+const navItems = [{ id: 'legal-notice', href: '#', label: 'Legal notice' }];
+
+export const Footer = Template.bind({});
+Footer.args = {
+ copyright,
+ navItems,
+ topId: 'top',
+};
diff --git a/src/components/organisms/layout/footer.test.tsx b/src/components/organisms/layout/footer.test.tsx
new file mode 100644
index 0000000..bc23732
--- /dev/null
+++ b/src/components/organisms/layout/footer.test.tsx
@@ -0,0 +1,33 @@
+import { render, screen } from '@test-utils';
+import Footer, { type FooterProps } from './footer';
+
+const copyright: FooterProps['copyright'] = {
+ dates: { start: '2017', end: '2022' },
+ owner: 'Lorem ipsum',
+ icon: 'CC',
+};
+
+const navItems: FooterProps['navItems'] = [
+ { id: 'legal-notice', href: '#', label: 'Legal notice' },
+];
+
+describe('Footer', () => {
+ it('renders the website copyright', () => {
+ render(<Footer copyright={copyright} topId="top" />);
+ expect(screen.getByText(copyright.owner)).toBeInTheDocument();
+ });
+
+ it('renders a back to top link', () => {
+ render(<Footer copyright={copyright} topId="top" />);
+ expect(
+ screen.getByRole('link', { name: 'Back to top' })
+ ).toBeInTheDocument();
+ });
+
+ it('renders some nav items', () => {
+ render(<Footer copyright={copyright} navItems={navItems} topId="top" />);
+ expect(
+ screen.getByRole('link', { name: navItems[0].label })
+ ).toBeInTheDocument();
+ });
+});
diff --git a/src/components/organisms/layout/footer.tsx b/src/components/organisms/layout/footer.tsx
new file mode 100644
index 0000000..c9cb067
--- /dev/null
+++ b/src/components/organisms/layout/footer.tsx
@@ -0,0 +1,52 @@
+import Copyright, { CopyrightProps } from '@components/atoms/layout/copyright';
+import BackToTop from '@components/molecules/buttons/back-to-top';
+import Nav, { type NavItem } from '@components/molecules/nav/nav';
+import { VFC } from 'react';
+import styles from './footer.module.scss';
+
+export type FooterProps = {
+ /**
+ * Set additional classnames to the footer element.
+ */
+ className?: string;
+ /**
+ * Set the copyright information.
+ */
+ copyright: CopyrightProps;
+ /**
+ * The footer nav items.
+ */
+ navItems?: NavItem[];
+ /**
+ * An element id (without hashtag) used as anchor for back to top button.
+ */
+ topId: string;
+};
+
+/**
+ * Footer component
+ *
+ * Renders a footer with copyright and nav;
+ */
+const Footer: VFC<FooterProps> = ({
+ className,
+ copyright,
+ navItems,
+ topId,
+}) => {
+ return (
+ <footer className={`${styles.wrapper} ${className}`}>
+ <Copyright
+ dates={copyright.dates}
+ owner={copyright.owner}
+ icon={copyright.icon}
+ />
+ {navItems && (
+ <Nav kind="footer" items={navItems} className={styles.nav} />
+ )}
+ <BackToTop target={topId} className={styles['back-to-top']} />
+ </footer>
+ );
+};
+
+export default Footer;