aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-04-22 18:46:48 +0200
committerArmand Philippot <git@armandphilippot.com>2022-04-22 18:46:48 +0200
commit5a6584777e42e6e3e55294d357cb0adafe2853e7 (patch)
tree121dd5fbadf395a5f281fd99bf35c7be25ebe13a
parent947a06bfdfdc5bca62c27fa2ee27f0ab9fefa0ea (diff)
chore: add a Layout component
It defines the different components used by all other layouts.
-rw-r--r--src/components/organisms/toolbar/toolbar.module.scss6
-rw-r--r--src/components/organisms/toolbar/toolbar.stories.tsx3
-rw-r--r--src/components/templates/layout/layout.module.scss35
-rw-r--r--src/components/templates/layout/layout.stories.tsx57
-rw-r--r--src/components/templates/layout/layout.test.tsx34
-rw-r--r--src/components/templates/layout/layout.tsx125
-rw-r--r--src/styles/base/_fonts.scss12
-rw-r--r--src/styles/base/_helpers.scss6
-rw-r--r--src/styles/base/_spacings.scss10
-rw-r--r--src/utils/config.ts2
10 files changed, 267 insertions, 23 deletions
diff --git a/src/components/organisms/toolbar/toolbar.module.scss b/src/components/organisms/toolbar/toolbar.module.scss
index bb0a91f..cda9b37 100644
--- a/src/components/organisms/toolbar/toolbar.module.scss
+++ b/src/components/organisms/toolbar/toolbar.module.scss
@@ -26,7 +26,11 @@
.modal {
&--search,
&--settings {
- min-width: 30ch;
+ @include mix.media("screen") {
+ @include mix.dimensions("sm") {
+ min-width: 35ch;
+ }
+ }
}
}
diff --git a/src/components/organisms/toolbar/toolbar.stories.tsx b/src/components/organisms/toolbar/toolbar.stories.tsx
index de94e31..4f9a3de 100644
--- a/src/components/organisms/toolbar/toolbar.stories.tsx
+++ b/src/components/organisms/toolbar/toolbar.stories.tsx
@@ -38,6 +38,9 @@ export default {
</IntlProvider>
),
],
+ parameters: {
+ layout: 'fullscreen',
+ },
} as ComponentMeta<typeof ToolbarComponent>;
const Template: ComponentStory<typeof ToolbarComponent> = (args) => (
diff --git a/src/components/templates/layout/layout.module.scss b/src/components/templates/layout/layout.module.scss
new file mode 100644
index 0000000..eb84c70
--- /dev/null
+++ b/src/components/templates/layout/layout.module.scss
@@ -0,0 +1,35 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
+
+:global {
+ #__next {
+ flex: 1;
+ display: flex;
+ flex-flow: column nowrap;
+ min-height: 100vh;
+ }
+}
+
+.header {
+ padding: var(--spacing-sm) 0 var(--spacing-md);
+ border-bottom: fun.convert-px(3) solid var(--color-border-light);
+}
+
+.main {
+ flex: 1;
+}
+
+.footer {
+ border-top: fun.convert-px(3) solid var(--color-border-light);
+}
+
+.noscript-spacing {
+ width: 100%;
+ height: fun.convert-px(75);
+
+ @include mix.media("screen") {
+ @include mix.dimensions("xs") {
+ height: fun.convert-px(50);
+ }
+ }
+}
diff --git a/src/components/templates/layout/layout.stories.tsx b/src/components/templates/layout/layout.stories.tsx
new file mode 100644
index 0000000..f3579e3
--- /dev/null
+++ b/src/components/templates/layout/layout.stories.tsx
@@ -0,0 +1,57 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { IntlProvider } from 'react-intl';
+import LayoutComponent from './layout';
+
+/**
+ * Layout - Storybook Meta
+ */
+export default {
+ title: 'Templates/LayoutBase',
+ component: LayoutComponent,
+ argTypes: {
+ children: {
+ control: {
+ type: 'text',
+ },
+ description: 'The article content.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Set additional classnames to the article element.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ },
+ decorators: [
+ (Story) => (
+ <IntlProvider locale="en">
+ <div id="__next">
+ <Story />
+ </div>
+ </IntlProvider>
+ ),
+ ],
+ parameters: {
+ layout: 'fullscreen',
+ },
+} as ComponentMeta<typeof LayoutComponent>;
+
+const Template: ComponentStory<typeof LayoutComponent> = (args) => (
+ <LayoutComponent {...args} />
+);
+
+/**
+ * Layout Stories - Default
+ */
+export const LayoutBase = Template.bind({});
diff --git a/src/components/templates/layout/layout.test.tsx b/src/components/templates/layout/layout.test.tsx
new file mode 100644
index 0000000..914e1cd
--- /dev/null
+++ b/src/components/templates/layout/layout.test.tsx
@@ -0,0 +1,34 @@
+import { render, screen } from '@test-utils';
+import Layout from './layout';
+
+const body =
+ 'Sit dolorem eveniet. Sit sit odio nemo vitae corrupti modi sint est rerum. Pariatur quidem maiores distinctio. Quia et illum aspernatur est cum.';
+
+describe('Layout', () => {
+ it('renders the website header', () => {
+ render(<Layout>{body}</Layout>);
+ expect(screen.getByRole('banner')).toBeInTheDocument();
+ });
+
+ it('renders the website main content', () => {
+ render(<Layout>{body}</Layout>);
+ expect(screen.getByRole('main')).toBeInTheDocument();
+ });
+
+ it('renders the website footer', () => {
+ render(<Layout>{body}</Layout>);
+ expect(screen.getByRole('contentinfo')).toBeInTheDocument();
+ });
+
+ it('renders a skip to content link', () => {
+ render(<Layout>{body}</Layout>);
+ expect(
+ screen.getByRole('link', { name: 'Skip to content' })
+ ).toBeInTheDocument();
+ });
+
+ it('renders an article', () => {
+ render(<Layout>{body}</Layout>);
+ expect(screen.getByRole('article')).toHaveTextContent(body);
+ });
+});
diff --git a/src/components/templates/layout/layout.tsx b/src/components/templates/layout/layout.tsx
new file mode 100644
index 0000000..601ced4
--- /dev/null
+++ b/src/components/templates/layout/layout.tsx
@@ -0,0 +1,125 @@
+import photo from '@assets/images/armand-philippot.jpg';
+import ButtonLink from '@components/atoms/buttons/button-link';
+import Career from '@components/atoms/icons/career';
+import CCBySA from '@components/atoms/icons/cc-by-sa';
+import ComputerScreen from '@components/atoms/icons/computer-screen';
+import Envelop from '@components/atoms/icons/envelop';
+import Home from '@components/atoms/icons/home';
+import PostsStack from '@components/atoms/icons/posts-stack';
+import Main from '@components/atoms/layout/main';
+import NoScript from '@components/atoms/layout/no-script';
+import Footer from '@components/organisms/layout/footer';
+import Header, { HeaderProps } from '@components/organisms/layout/header';
+import { settings } from '@utils/config';
+import { FC, ReactNode } from 'react';
+import { useIntl } from 'react-intl';
+import styles from './layout.module.scss';
+
+export type LayoutProps = Pick<HeaderProps, 'isHome'> & {
+ /**
+ * The layout main content.
+ */
+ children: ReactNode;
+ /**
+ * Set additional classnames to the article element.
+ */
+ className?: string;
+};
+
+/**
+ * Layout component
+ *
+ * Render the base layout used by all pages.
+ */
+const Layout: FC<LayoutProps> = ({ children, isHome, ...props }) => {
+ const intl = useIntl();
+ const skipToContent = intl.formatMessage({
+ defaultMessage: 'Skip to content',
+ description: 'Layout: Skip to content link',
+ id: 'K4rYdT',
+ });
+ const noScript = intl.formatMessage({
+ defaultMessage:
+ 'Warning: If you want to benefit from all features (search for example), please activate Javascript.',
+ description: 'Layout: noscript message',
+ id: '7jVUT6',
+ });
+
+ const copyright = {
+ dates: {
+ start: settings.copyright.startYear,
+ end: settings.copyright.endYear,
+ },
+ owner: settings.name,
+ icon: <CCBySA />,
+ };
+
+ const homeLabel = intl.formatMessage({
+ defaultMessage: 'Home',
+ description: 'Layout: main nav - home link',
+ id: 'bojYF5',
+ });
+ const blogLabel = intl.formatMessage({
+ defaultMessage: 'Blog',
+ description: 'Layout: main nav - blog link',
+ id: 'D8vB38',
+ });
+ const projectsLabel = intl.formatMessage({
+ defaultMessage: 'Projects',
+ description: 'Layout: main nav - projects link',
+ id: 'qnwsWV',
+ });
+ const cvLabel = intl.formatMessage({
+ defaultMessage: 'CV',
+ description: 'Layout: main nav - cv link',
+ id: 'R895yC',
+ });
+ const contactLabel = intl.formatMessage({
+ defaultMessage: 'Contact',
+ description: 'Layout: main nav - contact link',
+ id: 'AE4kCD',
+ });
+
+ const nav: HeaderProps['nav'] = [
+ { id: 'home', label: homeLabel, href: '#', logo: <Home /> },
+ { id: 'blog', label: blogLabel, href: '#', logo: <PostsStack /> },
+ {
+ id: 'projects',
+ label: projectsLabel,
+ href: '#',
+ logo: <ComputerScreen />,
+ },
+ { id: 'cv', label: cvLabel, href: '#', logo: <Career /> },
+ { id: 'contact', label: contactLabel, href: '#', logo: <Envelop /> },
+ ];
+
+ return (
+ <>
+ <noscript>
+ <div className={styles['noscript-spacing']}></div>
+ </noscript>
+ <span tabIndex={-1}></span>
+ <ButtonLink target="#main" className="screen-reader-text">
+ {skipToContent}
+ </ButtonLink>
+ <Header
+ title={settings.name}
+ baseline={settings.baseline.fr}
+ photo={photo.src}
+ nav={nav}
+ isHome={isHome}
+ className={styles.header}
+ withLink={true}
+ />
+ <Main id="main" className={styles.main}>
+ <article {...props}>{children}</article>
+ </Main>
+ <Footer copyright={copyright} topId="top" className={styles.footer} />
+ <noscript>
+ <NoScript message={noScript} position="top" />
+ </noscript>
+ </>
+ );
+};
+
+export default Layout;
diff --git a/src/styles/base/_fonts.scss b/src/styles/base/_fonts.scss
index 88850bb..c8695d4 100644
--- a/src/styles/base/_fonts.scss
+++ b/src/styles/base/_fonts.scss
@@ -139,32 +139,32 @@
--font-family-mono: #{var.$font-family_mono};
--font-size-sm: clamp(
#{math.div(var.font-size("sm"), 1.2)},
- 1ex + 1vw,
+ 2ex + 1vmin,
#{var.font-size("sm")}
);
--font-size-md: clamp(
#{var.font-size("sm")},
- 1ex + 2vw,
+ 2ex + 2vmin,
#{var.font-size("md")}
);
--font-size-lg: clamp(
#{var.font-size("md")},
- 1ex + 3vw,
+ 2ex + 3vmin,
#{var.font-size("lg")}
);
--font-size-xl: clamp(
#{var.font-size("lg")},
- 1ex + 4vw,
+ 2ex + 4vmin,
#{var.font-size("xl")}
);
--font-size-2xl: clamp(
#{var.font-size("xl")},
- 1ex + 5vw,
+ 2ex + 5vmin,
#{var.font-size("2xl")}
);
--font-size-3xl: clamp(
#{var.font-size("2xl")},
- 1ex + 6vw,
+ 2ex + 6vmin,
#{var.font-size("3xl")}
);
--line-height: #{var.$line-height};
diff --git a/src/styles/base/_helpers.scss b/src/styles/base/_helpers.scss
index d28811c..3879643 100644
--- a/src/styles/base/_helpers.scss
+++ b/src/styles/base/_helpers.scss
@@ -28,17 +28,11 @@
display: block;
width: auto;
height: auto;
- padding: var(--spacing-xs) var(--spacing-sm);
left: var(--spacing-2xs);
top: var(--spacing-xs);
z-index: 100000;
- background: var(--color-primary);
- box-shadow: fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0
- var(--color-shadow-dark);
clip: auto !important;
color: var(--color-fg-inverted);
- font-size: var(--font-size-md);
- font-weight: 600;
}
}
diff --git a/src/styles/base/_spacings.scss b/src/styles/base/_spacings.scss
index 08c3c3f..7c8b210 100644
--- a/src/styles/base/_spacings.scss
+++ b/src/styles/base/_spacings.scss
@@ -24,13 +24,5 @@
--spacing-xl: clamp(#{var.spacing("lg")}, 1ex + 4vw, #{var.spacing("xl")});
--spacing-2xl: clamp(#{var.spacing("xl")}, 1ex + 5vw, #{var.spacing("2xl")});
--spacing-3xl: clamp(#{var.spacing("2xl")}, 1ex + 6vw, #{var.spacing("3xl")});
- --toolbar-size: #{fun.convert-px(65)};
-}
-
-@include mix.media("screen") {
- @include mix.dimensions("sm") {
- :root {
- --toolbar-size: 0px;
- }
- }
+ --toolbar-size: #{fun.convert-px(80)};
}
diff --git a/src/utils/config.ts b/src/utils/config.ts
index 874a24c..6ec8c82 100644
--- a/src/utils/config.ts
+++ b/src/utils/config.ts
@@ -18,7 +18,7 @@ export const settings = {
},
copyright: {
startYear: '2012',
- endYear: new Date().getFullYear(),
+ endYear: new Date().getFullYear().toString(),
},
locales: {
defaultLocale: 'fr',