aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/templates/layout/site-footer
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-11-21 19:01:18 +0100
committerArmand Philippot <git@armandphilippot.com>2023-11-22 12:52:35 +0100
commitd4045fbcbfa8208ec31539744417f315f1f6fad8 (patch)
tree54746d3e28cc6e4a2d7d1e54a4b2e3e1e74a6896 /src/components/templates/layout/site-footer
parentc6212f927daf3c928f479afa052e4772216a2d8a (diff)
refactor(components): split Layout component in smaller components
The previous component was too long and hardly readable. So I splitted it in different part and added tests.
Diffstat (limited to 'src/components/templates/layout/site-footer')
-rw-r--r--src/components/templates/layout/site-footer/index.ts1
-rw-r--r--src/components/templates/layout/site-footer/site-footer.module.scss42
-rw-r--r--src/components/templates/layout/site-footer/site-footer.test.tsx20
-rw-r--r--src/components/templates/layout/site-footer/site-footer.tsx93
4 files changed, 156 insertions, 0 deletions
diff --git a/src/components/templates/layout/site-footer/index.ts b/src/components/templates/layout/site-footer/index.ts
new file mode 100644
index 0000000..cef0a6f
--- /dev/null
+++ b/src/components/templates/layout/site-footer/index.ts
@@ -0,0 +1 @@
+export * from './site-footer';
diff --git a/src/components/templates/layout/site-footer/site-footer.module.scss b/src/components/templates/layout/site-footer/site-footer.module.scss
new file mode 100644
index 0000000..935c163
--- /dev/null
+++ b/src/components/templates/layout/site-footer/site-footer.module.scss
@@ -0,0 +1,42 @@
+@use "../../../../styles/abstracts/functions" as fun;
+@use "../../../../styles/abstracts/mixins" as mix;
+
+.footer {
+ --navbar-size: #{fun.convert-px(80)};
+
+ display: flex;
+ flex-flow: column wrap;
+ gap: var(--spacing-xs);
+ place-items: center;
+ place-content: center;
+ padding: var(--spacing-md) 0 calc(var(--navbar-size) + var(--spacing-md));
+ border-top: fun.convert-px(3) solid var(--color-border-light);
+
+ @include mix.media("screen") {
+ @include mix.dimensions("sm") {
+ --navbar-size: 0px;
+
+ flex-flow: row wrap;
+ font-size: var(--font-size-sm);
+ }
+ }
+}
+
+.back-to-top {
+ position: fixed;
+ bottom: calc(var(--navbar-size, 0px) + var(--spacing-md));
+ right: var(--spacing-md);
+ transition: all 0.4s ease-in 0s;
+
+ &--hidden {
+ opacity: 0;
+ transform: translateY(calc(var(--button-height) + var(--spacing-md)));
+ visibility: hidden;
+ }
+
+ &--visible {
+ opacity: 1;
+ transform: translateY(0);
+ visibility: visible;
+ }
+}
diff --git a/src/components/templates/layout/site-footer/site-footer.test.tsx b/src/components/templates/layout/site-footer/site-footer.test.tsx
new file mode 100644
index 0000000..fa60b8f
--- /dev/null
+++ b/src/components/templates/layout/site-footer/site-footer.test.tsx
@@ -0,0 +1,20 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '../../../../../tests/utils';
+import { CONFIG } from '../../../../utils/config';
+import { ROUTES } from '../../../../utils/constants';
+import { SiteFooter } from './site-footer';
+
+describe('SiteFooter', () => {
+ it('renders the website colophon', () => {
+ render(<SiteFooter />);
+
+ expect(rtlScreen.getByRole('contentinfo')).toBeInTheDocument();
+ expect(rtlScreen.getByText(CONFIG.copyright.startYear)).toBeInTheDocument();
+ expect(rtlScreen.getByText(CONFIG.copyright.endYear)).toBeInTheDocument();
+ expect(rtlScreen.getByText(new RegExp(CONFIG.name))).toBeInTheDocument();
+ expect(rtlScreen.getByTitle('CC BY SA')).toBeInTheDocument();
+ expect(
+ rtlScreen.getByRole('link', { name: 'Legal notice' })
+ ).toHaveAttribute('href', ROUTES.LEGAL_NOTICE);
+ });
+});
diff --git a/src/components/templates/layout/site-footer/site-footer.tsx b/src/components/templates/layout/site-footer/site-footer.tsx
new file mode 100644
index 0000000..b852b32
--- /dev/null
+++ b/src/components/templates/layout/site-footer/site-footer.tsx
@@ -0,0 +1,93 @@
+import { type ForwardRefRenderFunction, forwardRef } from 'react';
+import { useIntl } from 'react-intl';
+import { CONFIG } from '../../../../utils/config';
+import { ROUTES } from '../../../../utils/constants';
+import { useScrollPosition } from '../../../../utils/hooks';
+import { Footer, type FooterProps, Icon } from '../../../atoms';
+import {
+ BackToTop,
+ Colophon,
+ type ColophonLink,
+ Copyright,
+} from '../../../molecules';
+import styles from './site-footer.module.scss';
+
+export type SiteFooterProps = Omit<FooterProps, 'children'> & {
+ /**
+ * An id that will be use as anchor for the back to top button.
+ */
+ topId?: string;
+};
+
+const SiteFooterWithRef: ForwardRefRenderFunction<
+ HTMLElement,
+ SiteFooterProps
+> = ({ className = '', topId, ...props }, ref) => {
+ const footerClass = `${styles.footer} ${className}`;
+ const intl = useIntl();
+ const licenseName = intl.formatMessage({
+ defaultMessage: 'CC BY SA',
+ description: 'SiteFooter: the license name',
+ id: 'iTLvLX',
+ });
+ const backToTop = intl.formatMessage({
+ defaultMessage: 'Back to top',
+ description: 'SiteFooter: an accessible name for the back to top button',
+ id: 'OHvb01',
+ });
+ const footerNav: ColophonLink[] = [
+ {
+ id: 'legal-notice',
+ label: intl.formatMessage({
+ defaultMessage: 'Legal notice',
+ description: 'SiteFooter: Legal notice link label',
+ id: 'lsmD4c',
+ }),
+ href: ROUTES.LEGAL_NOTICE,
+ },
+ ];
+ const scrollPos = useScrollPosition();
+ const backToTopVisibilityBreakpoint = 300;
+ const backToTopClassName = [
+ styles['back-to-top'],
+ styles[
+ scrollPos.y > backToTopVisibilityBreakpoint
+ ? 'back-to-top--visible'
+ : 'back-to-top--hidden'
+ ],
+ ].join(' ');
+ const backToTopAnchor = topId ? `#${topId}` : undefined;
+
+ return (
+ <Footer {...props} className={footerClass} ref={ref}>
+ <Colophon
+ copyright={
+ <Copyright
+ from={CONFIG.copyright.startYear}
+ owner={CONFIG.name}
+ to={CONFIG.copyright.endYear}
+ />
+ }
+ license={
+ <Icon
+ heading={licenseName}
+ // eslint-disable-next-line react/jsx-no-literals
+ shape="cc-by-sa"
+ // eslint-disable-next-line react/jsx-no-literals
+ size="lg"
+ />
+ }
+ links={footerNav}
+ />
+ {backToTopAnchor ? (
+ <BackToTop
+ anchor={backToTopAnchor}
+ className={backToTopClassName}
+ label={backToTop}
+ />
+ ) : null}
+ </Footer>
+ );
+};
+
+export const SiteFooter = forwardRef(SiteFooterWithRef);