From c7b134621301227ead150d30fe9b222487a356b7 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Thu, 7 Apr 2022 11:51:59 +0200
Subject: chore: add a PrismThemeToggle component

---
 .../molecules/forms/prism-theme-toggle.tsx         | 52 ++++++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100644 src/components/molecules/forms/prism-theme-toggle.tsx

(limited to 'src/components/molecules/forms/prism-theme-toggle.tsx')

diff --git a/src/components/molecules/forms/prism-theme-toggle.tsx b/src/components/molecules/forms/prism-theme-toggle.tsx
new file mode 100644
index 0000000..cedb71a
--- /dev/null
+++ b/src/components/molecules/forms/prism-theme-toggle.tsx
@@ -0,0 +1,52 @@
+import Toggle, {
+  ToggleChoices,
+  ToggleProps,
+} from '@components/atoms/forms/toggle';
+import Moon from '@components/atoms/icons/moon';
+import Sun from '@components/atoms/icons/sun';
+import { FC, useState } from 'react';
+import { useIntl } from 'react-intl';
+
+export type PrismThemeToggleProps = Pick<ToggleProps, 'value'>;
+
+/**
+ * PrismThemeToggle component
+ *
+ * Render a Toggle component to set code blocks theme.
+ */
+const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ value }) => {
+  const intl = useIntl();
+  const [isDarkTheme, setIsDarkTheme] = useState<boolean>(value);
+  const themeLabel = intl.formatMessage({
+    defaultMessage: 'Code blocks:',
+    description: 'PrismThemeToggle: theme label',
+    id: 'ftXN+0',
+  });
+  const lightThemeLabel = intl.formatMessage({
+    defaultMessage: 'Light theme',
+    description: 'PrismThemeToggle: light theme label',
+    id: 'tsWh8x',
+  });
+  const darkThemeLabel = intl.formatMessage({
+    defaultMessage: 'Dark theme',
+    description: 'PrismThemeToggle: dark theme label',
+    id: 'og/zWL',
+  });
+  const themeChoices: ToggleChoices = {
+    left: <Sun title={lightThemeLabel} />,
+    right: <Moon title={darkThemeLabel} />,
+  };
+
+  return (
+    <Toggle
+      id="prism-theme-settings"
+      name="prism-theme-settings"
+      label={themeLabel}
+      choices={themeChoices}
+      value={isDarkTheme}
+      setValue={setIsDarkTheme}
+    />
+  );
+};
+
+export default PrismThemeToggle;
-- 
cgit v1.2.3


From 0b3146f7278929c4d1b33dd8f94f34e351e5e5a9 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Fri, 8 Apr 2022 22:36:24 +0200
Subject: chore: add a Settings modal component

---
 src/components/atoms/forms/field.stories.tsx       | 45 +++++++++---
 src/components/atoms/forms/field.tsx               | 21 ++++--
 src/components/atoms/forms/form.test.tsx           |  9 +++
 src/components/atoms/forms/form.tsx                | 73 ++++++++++++++++++++
 src/components/atoms/forms/forms.module.scss       | 23 +++----
 src/components/atoms/forms/label.module.scss       | 17 +++++
 src/components/atoms/forms/label.stories.tsx       | 42 ++++++++++--
 src/components/atoms/forms/label.test.tsx          |  2 +-
 src/components/atoms/forms/label.tsx               | 32 +++++++--
 src/components/atoms/forms/select.stories.tsx      | 44 +++++++++---
 src/components/atoms/forms/select.tsx              | 25 +++++--
 src/components/atoms/forms/toggle.module.scss      |  2 +-
 src/components/atoms/forms/toggle.tsx              |  7 +-
 .../molecules/forms/labelled-field.module.scss     |  9 +++
 .../molecules/forms/labelled-field.stories.tsx     | 33 +++++++--
 src/components/molecules/forms/labelled-field.tsx  | 34 +++++++--
 .../molecules/forms/labelled-select.module.scss    |  9 +++
 .../molecules/forms/labelled-select.stories.tsx    | 80 ++++++++++++++++++++--
 src/components/molecules/forms/labelled-select.tsx | 51 ++++++++++++--
 .../molecules/forms/motion-toggle.stories.tsx      | 15 ++++
 src/components/molecules/forms/motion-toggle.tsx   |  8 ++-
 .../molecules/forms/prism-theme-toggle.stories.tsx | 15 ++++
 .../molecules/forms/prism-theme-toggle.tsx         | 11 ++-
 .../forms/select-with-tooltip.module.scss          | 10 +--
 .../forms/select-with-tooltip.stories.tsx          | 77 +++++++++++++++++----
 .../molecules/forms/select-with-tooltip.tsx        | 34 +++++----
 .../molecules/forms/theme-toggle.stories.tsx       | 15 ++++
 src/components/molecules/forms/theme-toggle.tsx    |  8 ++-
 .../organisms/modals/settings-modal.module.scss    | 14 ++++
 .../organisms/modals/settings-modal.stories.tsx    | 31 +++++++++
 .../organisms/modals/settings-modal.test.tsx       | 34 +++++++++
 src/components/organisms/modals/settings-modal.tsx | 51 ++++++++++++++
 32 files changed, 761 insertions(+), 120 deletions(-)
 create mode 100644 src/components/atoms/forms/form.test.tsx
 create mode 100644 src/components/atoms/forms/form.tsx
 create mode 100644 src/components/atoms/forms/label.module.scss
 create mode 100644 src/components/molecules/forms/labelled-field.module.scss
 create mode 100644 src/components/molecules/forms/labelled-select.module.scss
 create mode 100644 src/components/organisms/modals/settings-modal.module.scss
 create mode 100644 src/components/organisms/modals/settings-modal.stories.tsx
 create mode 100644 src/components/organisms/modals/settings-modal.test.tsx
 create mode 100644 src/components/organisms/modals/settings-modal.tsx

(limited to 'src/components/molecules/forms/prism-theme-toggle.tsx')

diff --git a/src/components/atoms/forms/field.stories.tsx b/src/components/atoms/forms/field.stories.tsx
index 02681e7..ec81922 100644
--- a/src/components/atoms/forms/field.stories.tsx
+++ b/src/components/atoms/forms/field.stories.tsx
@@ -1,4 +1,5 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { useState } from 'react';
 import FieldComponent from './field';
 
 export default {
@@ -7,11 +8,35 @@ export default {
   args: {
     disabled: false,
     required: false,
-    setValue: () => null,
     type: 'text',
-    value: '',
   },
   argTypes: {
+    'aria-labelledby': {
+      control: {
+        type: 'text',
+      },
+      description: 'One or more ids that refers to the field name.',
+      table: {
+        category: 'Accessibility',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
+    className: {
+      control: {
+        type: 'text',
+      },
+      description: 'Add classnames to the field.',
+      table: {
+        category: 'Styles',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
     disabled: {
       control: {
         type: 'boolean',
@@ -148,7 +173,7 @@ export default {
     },
     value: {
       control: {
-        type: 'text',
+        type: null,
       },
       description: 'Field value.',
       type: {
@@ -159,14 +184,18 @@ export default {
   },
 } as ComponentMeta<typeof FieldComponent>;
 
-const Template: ComponentStory<typeof FieldComponent> = (args) => (
-  <FieldComponent {...args} />
-);
+const Template: ComponentStory<typeof FieldComponent> = ({
+  value: _value,
+  setValue: _setValue,
+  ...args
+}) => {
+  const [value, setValue] = useState<string>('');
+
+  return <FieldComponent value={value} setValue={setValue} {...args} />;
+};
 
 export const Field = Template.bind({});
 Field.args = {
   id: 'field-storybook',
   name: 'field-storybook',
-  setValue: () => null,
-  value: '',
 };
diff --git a/src/components/atoms/forms/field.tsx b/src/components/atoms/forms/field.tsx
index 513d2ba..2e75d0f 100644
--- a/src/components/atoms/forms/field.tsx
+++ b/src/components/atoms/forms/field.tsx
@@ -1,4 +1,4 @@
-import { ChangeEvent, FC, SetStateAction } from 'react';
+import { ChangeEvent, SetStateAction, VFC } from 'react';
 import styles from './forms.module.scss';
 
 export type FieldType =
@@ -13,6 +13,14 @@ export type FieldType =
   | 'url';
 
 export type FieldProps = {
+  /**
+   * One or more ids that refers to the field name.
+   */
+  'aria-labelledby'?: string;
+  /**
+   * Add classnames to the field.
+   */
+  className?: string;
   /**
    * Field state. Either enabled (false) or disabled (true).
    */
@@ -64,7 +72,12 @@ export type FieldProps = {
  *
  * Render either an input or a textarea.
  */
-const Field: FC<FieldProps> = ({ setValue, type, ...props }) => {
+const Field: VFC<FieldProps> = ({
+  className = '',
+  setValue,
+  type,
+  ...props
+}) => {
   /**
    * Update select value when an option is selected.
    * @param e - The option change event.
@@ -78,14 +91,14 @@ const Field: FC<FieldProps> = ({ setValue, type, ...props }) => {
   return type === 'textarea' ? (
     <textarea
       onChange={updateValue}
-      className={`${styles.field} ${styles['field--textarea']}`}
+      className={`${styles.field} ${styles['field--textarea']} ${className}`}
       {...props}
     />
   ) : (
     <input
       type={type}
       onChange={updateValue}
-      className={styles.field}
+      className={`${styles.field} ${className}`}
       {...props}
     />
   );
diff --git a/src/components/atoms/forms/form.test.tsx b/src/components/atoms/forms/form.test.tsx
new file mode 100644
index 0000000..9cd3c58
--- /dev/null
+++ b/src/components/atoms/forms/form.test.tsx
@@ -0,0 +1,9 @@
+import { render, screen } from '@test-utils';
+import Form from './form';
+
+describe('Form', () => {
+  it('renders a form', () => {
+    render(<Form aria-label="Jest form" onSubmit={() => null}></Form>);
+    expect(screen.getByRole('form', { name: 'Jest form' })).toBeInTheDocument();
+  });
+});
diff --git a/src/components/atoms/forms/form.tsx b/src/components/atoms/forms/form.tsx
new file mode 100644
index 0000000..8e80930
--- /dev/null
+++ b/src/components/atoms/forms/form.tsx
@@ -0,0 +1,73 @@
+import { Children, FC, FormEvent, Fragment } from 'react';
+import styles from './forms.module.scss';
+
+export type FormProps = {
+  /**
+   * An accessible name.
+   */
+  'aria-label'?: string;
+  /**
+   * One or more ids that refers to the form name.
+   */
+  'aria-labelledby'?: string;
+  /**
+   * Set additional classnames to the form wrapper.
+   */
+  className?: string;
+  /**
+   * Wrap each items with a div. Default: true.
+   */
+  grouped?: boolean;
+  /**
+   * A callback function to execute on submit.
+   */
+  onSubmit: () => void;
+};
+
+/**
+ * Form component.
+ *
+ * Render children wrapped in a form element.
+ */
+const Form: FC<FormProps> = ({
+  children,
+  className = '',
+  grouped = true,
+  onSubmit,
+  ...props
+}) => {
+  const arrayChildren = Children.toArray(children);
+
+  /**
+   * Get the form items.
+   * @returns {JSX.Element[]} An array of child elements wrapped in a div.
+   */
+  const getFormItems = (): JSX.Element[] => {
+    return arrayChildren.map((child, index) =>
+      grouped ? (
+        <div key={`item-${index}`} className={styles.item}>
+          {child}
+        </div>
+      ) : (
+        <Fragment key={`item-${index}`}>{child}</Fragment>
+      )
+    );
+  };
+
+  /**
+   * Handle form submit.
+   * @param {FormEvent} e - The form event.
+   */
+  const handleSubmit = (e: FormEvent) => {
+    e.preventDefault();
+    onSubmit();
+  };
+
+  return (
+    <form onSubmit={handleSubmit} className={className} {...props}>
+      {getFormItems()}
+    </form>
+  );
+};
+
+export default Form;
diff --git a/src/components/atoms/forms/forms.module.scss b/src/components/atoms/forms/forms.module.scss
index 689a318..279c185 100644
--- a/src/components/atoms/forms/forms.module.scss
+++ b/src/components/atoms/forms/forms.module.scss
@@ -1,7 +1,12 @@
 @use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
+
+.item {
+  margin: var(--spacing-xs) 0;
+  max-width: 45ch;
+}
 
 .field {
-  width: 100%;
   padding: var(--spacing-2xs) var(--spacing-xs);
   background: var(--color-bg-tertiary);
   border: fun.convert-px(2) solid var(--color-border);
@@ -10,6 +15,10 @@
 
   &--select {
     cursor: pointer;
+
+    @include mix.pointer("fine") {
+      padding: fun.convert-px(3) var(--spacing-xs);
+    }
   }
 
   &--textarea {
@@ -41,15 +50,3 @@
     }
   }
 }
-
-.label {
-  display: block;
-  color: var(--color-primary-darker);
-  font-size: var(--font-size-sm);
-  font-variant: small-caps;
-  font-weight: 600;
-}
-
-.required {
-  color: var(--color-secondary);
-}
diff --git a/src/components/atoms/forms/label.module.scss b/src/components/atoms/forms/label.module.scss
new file mode 100644
index 0000000..f900925
--- /dev/null
+++ b/src/components/atoms/forms/label.module.scss
@@ -0,0 +1,17 @@
+.label {
+  color: var(--color-primary-darker);
+  font-weight: 600;
+
+  &--small {
+    font-size: var(--font-size-sm);
+    font-variant: small-caps;
+  }
+
+  &--medium {
+    font-size: var(--font-size-md);
+  }
+}
+
+.required {
+  color: var(--color-secondary);
+}
diff --git a/src/components/atoms/forms/label.stories.tsx b/src/components/atoms/forms/label.stories.tsx
index 06e8eb9..463e8ac 100644
--- a/src/components/atoms/forms/label.stories.tsx
+++ b/src/components/atoms/forms/label.stories.tsx
@@ -4,7 +4,24 @@ import LabelComponent from './label';
 export default {
   title: 'Atoms/Forms',
   component: LabelComponent,
+  args: {
+    required: false,
+    size: 'small',
+  },
   argTypes: {
+    className: {
+      control: {
+        type: 'text',
+      },
+      description: 'Add classnames to the label.',
+      table: {
+        category: 'Styles',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
     children: {
       control: {
         type: 'text',
@@ -32,22 +49,37 @@ export default {
       description: 'Set to true if the field is required.',
       table: {
         category: 'Options',
+        defaultValue: { summary: false },
       },
       type: {
         name: 'boolean',
         required: false,
       },
     },
+    size: {
+      control: {
+        type: 'select',
+      },
+      description: 'The label size.',
+      options: ['medium', 'small'],
+      table: {
+        category: 'Options',
+        defaultValue: { summary: 'small' },
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
   },
 } as ComponentMeta<typeof LabelComponent>;
 
-const Template: ComponentStory<typeof LabelComponent> = (args) => {
-  const { children, ...props } = args;
-  return <LabelComponent {...props}>{children}</LabelComponent>;
-};
+const Template: ComponentStory<typeof LabelComponent> = ({
+  children,
+  ...args
+}) => <LabelComponent {...args}>{children}</LabelComponent>;
 
 export const Label = Template.bind({});
 Label.args = {
   children: 'A label',
-  htmlFor: 'a-field-id',
 };
diff --git a/src/components/atoms/forms/label.test.tsx b/src/components/atoms/forms/label.test.tsx
index fcf1731..14257c3 100644
--- a/src/components/atoms/forms/label.test.tsx
+++ b/src/components/atoms/forms/label.test.tsx
@@ -3,7 +3,7 @@ import Label from './label';
 
 describe('Label', () => {
   it('renders a field label', () => {
-    render(<Label htmlFor="a-field-id">A label</Label>);
+    render(<Label>A label</Label>);
     expect(screen.getByText('A label')).toBeDefined();
   });
 });
diff --git a/src/components/atoms/forms/label.tsx b/src/components/atoms/forms/label.tsx
index 860cd73..8d57ee2 100644
--- a/src/components/atoms/forms/label.tsx
+++ b/src/components/atoms/forms/label.tsx
@@ -1,9 +1,23 @@
 import { FC } from 'react';
-import styles from './forms.module.scss';
+import styles from './label.module.scss';
 
-type LabelProps = {
-  htmlFor: string;
+export type LabelProps = {
+  /**
+   * Add classnames to the label.
+   */
+  className?: string;
+  /**
+   * The field id.
+   */
+  htmlFor?: string;
+  /**
+   * Is the field required? Default: false.
+   */
   required?: boolean;
+  /**
+   * The label size. Default: small.
+   */
+  size?: 'medium' | 'small';
 };
 
 /**
@@ -11,9 +25,17 @@ type LabelProps = {
  *
  * Render a HTML label element.
  */
-const Label: FC<LabelProps> = ({ children, required = false, ...props }) => {
+const Label: FC<LabelProps> = ({
+  children,
+  className = '',
+  required = false,
+  size = 'small',
+  ...props
+}) => {
+  const sizeClass = styles[`label--${size}`];
+
   return (
-    <label className={styles.label} {...props}>
+    <label className={`${styles.label} ${sizeClass} ${className}`} {...props}>
       {children}
       {required && <span className={styles.required}> *</span>}
     </label>
diff --git a/src/components/atoms/forms/select.stories.tsx b/src/components/atoms/forms/select.stories.tsx
index c7bb253..c2fb8c6 100644
--- a/src/components/atoms/forms/select.stories.tsx
+++ b/src/components/atoms/forms/select.stories.tsx
@@ -1,4 +1,5 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { useState } from 'react';
 import SelectComponent from './select';
 
 const selectOptions = [
@@ -10,14 +11,31 @@ const selectOptions = [
 export default {
   title: 'Atoms/Forms',
   component: SelectComponent,
+  args: {
+    disabled: false,
+    required: false,
+  },
   argTypes: {
-    classes: {
+    'aria-labelledby': {
       control: {
         type: 'text',
       },
-      description: 'Set additional classes',
+      description: 'One or more ids that refers to the select field name.',
       table: {
-        category: 'Options',
+        category: 'Accessibility',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
+    className: {
+      control: {
+        type: 'text',
+      },
+      description: 'Add classnames to the select field.',
+      table: {
+        category: 'Styles',
       },
       type: {
         name: 'string',
@@ -59,9 +77,6 @@ export default {
       },
     },
     options: {
-      control: {
-        type: null,
-      },
       description: 'Select options.',
       type: {
         name: 'array',
@@ -100,7 +115,7 @@ export default {
     },
     value: {
       control: {
-        type: 'text',
+        type: null,
       },
       description: 'Field value.',
       type: {
@@ -111,13 +126,20 @@ export default {
   },
 } as ComponentMeta<typeof SelectComponent>;
 
-const Template: ComponentStory<typeof SelectComponent> = (args) => (
-  <SelectComponent {...args} />
-);
+const Template: ComponentStory<typeof SelectComponent> = ({
+  value,
+  setValue: _setValue,
+  ...args
+}) => {
+  const [selected, setSelected] = useState<string>(value);
+
+  return <SelectComponent value={selected} setValue={setSelected} {...args} />;
+};
 
 export const Select = Template.bind({});
 Select.args = {
+  id: 'storybook-select',
+  name: 'storybook-select',
   options: selectOptions,
-  setValue: () => null,
   value: 'option2',
 };
diff --git a/src/components/atoms/forms/select.tsx b/src/components/atoms/forms/select.tsx
index a42dbda..25e86e0 100644
--- a/src/components/atoms/forms/select.tsx
+++ b/src/components/atoms/forms/select.tsx
@@ -1,17 +1,30 @@
-import { ChangeEvent, FC, SetStateAction } from 'react';
+import { ChangeEvent, SetStateAction, VFC } from 'react';
 import styles from './forms.module.scss';
 
 export type SelectOptions = {
+  /**
+   * The option id.
+   */
   id: string;
+  /**
+   * The option name.
+   */
   name: string;
+  /**
+   * The option value.
+   */
   value: string;
 };
 
 export type SelectProps = {
   /**
-   * Set additional classes.
+   * One or more ids that refers to the select field name.
+   */
+  'aria-labelledby'?: string;
+  /**
+   * Add classnames to the select field.
    */
-  classes?: string;
+  className?: string;
   /**
    * Field state. Either enabled (false) or disabled (true).
    */
@@ -47,8 +60,8 @@ export type SelectProps = {
  *
  * Render a HTML select element.
  */
-const Select: FC<SelectProps> = ({
-  classes = '',
+const Select: VFC<SelectProps> = ({
+  className = '',
   options,
   setValue,
   ...props
@@ -74,7 +87,7 @@ const Select: FC<SelectProps> = ({
 
   return (
     <select
-      className={`${styles.field} ${styles['field--select']} ${classes}`}
+      className={`${styles.field} ${styles['field--select']} ${className}`}
       onChange={updateValue}
       {...props}
     >
diff --git a/src/components/atoms/forms/toggle.module.scss b/src/components/atoms/forms/toggle.module.scss
index 24b867e..2e8a49f 100644
--- a/src/components/atoms/forms/toggle.module.scss
+++ b/src/components/atoms/forms/toggle.module.scss
@@ -10,7 +10,7 @@
 }
 
 .title {
-  margin-right: auto;
+  margin-right: var(--spacing-2xs);
 }
 
 .toggle {
diff --git a/src/components/atoms/forms/toggle.tsx b/src/components/atoms/forms/toggle.tsx
index 7ef40ed..c3bc09d 100644
--- a/src/components/atoms/forms/toggle.tsx
+++ b/src/components/atoms/forms/toggle.tsx
@@ -26,6 +26,10 @@ export type ToggleProps = {
    * The toggle label.
    */
   label: string;
+  /**
+   * Set additional classnames to the label.
+   */
+  labelClassName?: string;
   /**
    * The label size.
    */
@@ -53,6 +57,7 @@ const Toggle: VFC<ToggleProps> = ({
   choices,
   id,
   label,
+  labelClassName = '',
   labelSize,
   name,
   setValue,
@@ -69,7 +74,7 @@ const Toggle: VFC<ToggleProps> = ({
         className={styles.checkbox}
       />
       <Label size={labelSize} htmlFor={id} className={styles.label}>
-        <span className={styles.title}>{label}</span>
+        <span className={`${styles.title} ${labelClassName}`}>{label}</span>
         {choices.left}
         <span className={styles.toggle}></span>
         {choices.right}
diff --git a/src/components/molecules/forms/labelled-field.module.scss b/src/components/molecules/forms/labelled-field.module.scss
new file mode 100644
index 0000000..64ef3d0
--- /dev/null
+++ b/src/components/molecules/forms/labelled-field.module.scss
@@ -0,0 +1,9 @@
+.label {
+  &--left {
+    margin-right: var(--spacing-2xs);
+  }
+
+  &--top {
+    display: block;
+  }
+}
diff --git a/src/components/molecules/forms/labelled-field.stories.tsx b/src/components/molecules/forms/labelled-field.stories.tsx
index eb7f8b5..b77d71e 100644
--- a/src/components/molecules/forms/labelled-field.stories.tsx
+++ b/src/components/molecules/forms/labelled-field.stories.tsx
@@ -1,4 +1,5 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { useState } from 'react';
 import LabelledFieldComponent from './labelled-field';
 
 export default {
@@ -6,6 +7,7 @@ export default {
   component: LabelledFieldComponent,
   args: {
     disabled: false,
+    labelPosition: 'top',
     required: false,
   },
   argTypes: {
@@ -43,6 +45,21 @@ export default {
         required: true,
       },
     },
+    labelPosition: {
+      control: {
+        type: 'select',
+      },
+      description: 'The label position.',
+      options: ['left', 'top'],
+      table: {
+        category: 'Options',
+        defaultValue: { summary: 'top' },
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
     max: {
       control: {
         type: 'number',
@@ -155,7 +172,7 @@ export default {
     },
     value: {
       control: {
-        type: 'text',
+        type: null,
       },
       description: 'Field value.',
       type: {
@@ -166,15 +183,19 @@ export default {
   },
 } as ComponentMeta<typeof LabelledFieldComponent>;
 
-const Template: ComponentStory<typeof LabelledFieldComponent> = (args) => (
-  <LabelledFieldComponent {...args} />
-);
+const Template: ComponentStory<typeof LabelledFieldComponent> = ({
+  value: _value,
+  setValue: _setValue,
+  ...args
+}) => {
+  const [value, setValue] = useState<string>('');
+
+  return <LabelledFieldComponent value={value} setValue={setValue} {...args} />;
+};
 
 export const LabelledField = Template.bind({});
 LabelledField.args = {
   id: 'labelled-field-storybook',
   label: 'Labelled field',
   name: 'labelled-field-storybook',
-  setValue: () => null,
-  value: '',
 };
diff --git a/src/components/molecules/forms/labelled-field.tsx b/src/components/molecules/forms/labelled-field.tsx
index 7f81e23..08d0126 100644
--- a/src/components/molecules/forms/labelled-field.tsx
+++ b/src/components/molecules/forms/labelled-field.tsx
@@ -1,20 +1,46 @@
 import Field, { type FieldProps } from '@components/atoms/forms/field';
 import Label from '@components/atoms/forms/label';
-import { FC } from 'react';
+import { VFC } from 'react';
+import styles from './labelled-field.module.scss';
 
-type LabelledFieldProps = FieldProps & {
+export type LabelledFieldProps = FieldProps & {
+  /**
+   * Visually hide the field label. Default: false.
+   */
+  hideLabel?: boolean;
+  /**
+   * The field label.
+   */
   label: string;
+  /**
+   * The label position. Default: top.
+   */
+  labelPosition?: 'left' | 'top';
 };
 
-const LabelledField: FC<LabelledFieldProps> = ({
+/**
+ * LabelledField component
+ *
+ * Render a field tied to a label.
+ */
+const LabelledField: VFC<LabelledFieldProps> = ({
+  hideLabel = false,
   id,
   label,
+  labelPosition = 'top',
   required,
   ...props
 }) => {
+  const positionModifier = `label--${labelPosition}`;
+  const visibilityClass = hideLabel ? 'screen-reader-text' : '';
+
   return (
     <>
-      <Label htmlFor={id} required={required}>
+      <Label
+        htmlFor={id}
+        required={required}
+        className={`${visibilityClass} ${styles[positionModifier]}`}
+      >
         {label}
       </Label>
       <Field id={id} required={required} {...props} />
diff --git a/src/components/molecules/forms/labelled-select.module.scss b/src/components/molecules/forms/labelled-select.module.scss
new file mode 100644
index 0000000..64ef3d0
--- /dev/null
+++ b/src/components/molecules/forms/labelled-select.module.scss
@@ -0,0 +1,9 @@
+.label {
+  &--left {
+    margin-right: var(--spacing-2xs);
+  }
+
+  &--top {
+    display: block;
+  }
+}
diff --git a/src/components/molecules/forms/labelled-select.stories.tsx b/src/components/molecules/forms/labelled-select.stories.tsx
index 0966e13..0c569f5 100644
--- a/src/components/molecules/forms/labelled-select.stories.tsx
+++ b/src/components/molecules/forms/labelled-select.stories.tsx
@@ -1,4 +1,5 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { useState } from 'react';
 import LabelledSelectComponent from './labelled-select';
 
 const selectOptions = [
@@ -12,6 +13,7 @@ export default {
   component: LabelledSelectComponent,
   args: {
     disabled: false,
+    labelPosition: 'top',
     required: false,
   },
   argTypes: {
@@ -49,6 +51,48 @@ export default {
         required: true,
       },
     },
+    labelClassName: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set additional classnames to the label.',
+      table: {
+        category: 'Styles',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
+    labelPosition: {
+      control: {
+        type: 'select',
+      },
+      description: 'The label position.',
+      options: ['left', 'top'],
+      table: {
+        category: 'Options',
+        defaultValue: { summary: 'top' },
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
+    labelSize: {
+      control: {
+        type: 'select',
+      },
+      description: 'The label size.',
+      options: ['medium', 'small'],
+      table: {
+        category: 'Options',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
     name: {
       control: {
         type: 'text',
@@ -86,6 +130,19 @@ export default {
         required: false,
       },
     },
+    selectClassName: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set additional classnames to the select field.',
+      table: {
+        category: 'Styles',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
     setValue: {
       control: {
         type: null,
@@ -101,7 +158,7 @@ export default {
     },
     value: {
       control: {
-        type: 'text',
+        type: null,
       },
       description: 'Field value.',
       type: {
@@ -112,9 +169,21 @@ export default {
   },
 } as ComponentMeta<typeof LabelledSelectComponent>;
 
-const Template: ComponentStory<typeof LabelledSelectComponent> = (args) => (
-  <LabelledSelectComponent {...args} />
-);
+const Template: ComponentStory<typeof LabelledSelectComponent> = ({
+  value,
+  setValue: _setValue,
+  ...args
+}) => {
+  const [selected, setSelected] = useState<string>(value);
+
+  return (
+    <LabelledSelectComponent
+      value={selected}
+      setValue={setSelected}
+      {...args}
+    />
+  );
+};
 
 export const LabelledSelect = Template.bind({});
 LabelledSelect.args = {
@@ -122,6 +191,5 @@ LabelledSelect.args = {
   label: 'Labelled select',
   name: 'labelled-select-storybook',
   options: selectOptions,
-  setValue: () => null,
-  value: '',
+  value: 'option1',
 };
diff --git a/src/components/molecules/forms/labelled-select.tsx b/src/components/molecules/forms/labelled-select.tsx
index 442e91a..7d4237a 100644
--- a/src/components/molecules/forms/labelled-select.tsx
+++ b/src/components/molecules/forms/labelled-select.tsx
@@ -1,23 +1,62 @@
-import Label from '@components/atoms/forms/label';
+import Label, { LabelProps } from '@components/atoms/forms/label';
 import Select, { type SelectProps } from '@components/atoms/forms/select';
-import { FC } from 'react';
+import { VFC } from 'react';
+import styles from './labelled-select.module.scss';
 
-type LabelledSelectProps = SelectProps & {
+export type LabelledSelectProps = Omit<
+  SelectProps,
+  'aria-labelledby' | 'className'
+> & {
+  /**
+   * The field label.
+   */
   label: string;
+  /**
+   * Set additional classnames to the label.
+   */
+  labelClassName?: string;
+  /**
+   * The label position. Default: top.
+   */
+  labelPosition?: 'left' | 'top';
+  /**
+   * The label size.
+   */
+  labelSize?: LabelProps['size'];
+  /**
+   * Set additional classnames to the select field.
+   */
+  selectClassName?: string;
 };
 
-const LabelledSelect: FC<LabelledSelectProps> = ({
+const LabelledSelect: VFC<LabelledSelectProps> = ({
   id,
   label,
+  labelClassName = '',
+  labelPosition = 'top',
+  labelSize,
   required,
+  selectClassName = '',
   ...props
 }) => {
+  const positionModifier = `label--${labelPosition}`;
+
   return (
     <>
-      <Label htmlFor={id} required={required}>
+      <Label
+        htmlFor={id}
+        required={required}
+        size={labelSize}
+        className={`${styles[positionModifier]} ${labelClassName}`}
+      >
         {label}
       </Label>
-      <Select id={id} required={required} {...props} />
+      <Select
+        id={id}
+        required={required}
+        {...props}
+        className={selectClassName}
+      />
     </>
   );
 };
diff --git a/src/components/molecules/forms/motion-toggle.stories.tsx b/src/components/molecules/forms/motion-toggle.stories.tsx
index 4fc199a..dc4d2a9 100644
--- a/src/components/molecules/forms/motion-toggle.stories.tsx
+++ b/src/components/molecules/forms/motion-toggle.stories.tsx
@@ -5,6 +5,18 @@ import MotionToggleComponent from './motion-toggle';
 export default {
   title: 'Molecules/Forms',
   component: MotionToggleComponent,
+  argTypes: {
+    value: {
+      control: {
+        type: null,
+      },
+      description: 'The reduce motion value.',
+      type: {
+        name: 'boolean',
+        required: true,
+      },
+    },
+  },
 } as ComponentMeta<typeof MotionToggleComponent>;
 
 const Template: ComponentStory<typeof MotionToggleComponent> = (args) => (
@@ -14,3 +26,6 @@ const Template: ComponentStory<typeof MotionToggleComponent> = (args) => (
 );
 
 export const MotionToggle = Template.bind({});
+MotionToggle.args = {
+  value: false,
+};
diff --git a/src/components/molecules/forms/motion-toggle.tsx b/src/components/molecules/forms/motion-toggle.tsx
index 77291ed..d4f7d11 100644
--- a/src/components/molecules/forms/motion-toggle.tsx
+++ b/src/components/molecules/forms/motion-toggle.tsx
@@ -2,17 +2,17 @@ import Toggle, {
   ToggleChoices,
   ToggleProps,
 } from '@components/atoms/forms/toggle';
-import { FC, useState } from 'react';
+import { useState, VFC } from 'react';
 import { useIntl } from 'react-intl';
 
-export type MotionToggleProps = Pick<ToggleProps, 'value'>;
+export type MotionToggleProps = Pick<ToggleProps, 'labelClassName' | 'value'>;
 
 /**
  * MotionToggle component
  *
  * Render a Toggle component to set reduce motion.
  */
-const MotionToggle: FC<MotionToggleProps> = ({ value }) => {
+const MotionToggle: VFC<MotionToggleProps> = ({ value, ...props }) => {
   const intl = useIntl();
   const [isDeactivated, setIsDeactivated] = useState<boolean>(value);
   const reduceMotionLabel = intl.formatMessage({
@@ -40,9 +40,11 @@ const MotionToggle: FC<MotionToggleProps> = ({ value }) => {
       id="reduce-motion-settings"
       name="reduce-motion-settings"
       label={reduceMotionLabel}
+      labelSize="medium"
       choices={reduceMotionChoices}
       value={isDeactivated}
       setValue={setIsDeactivated}
+      {...props}
     />
   );
 };
diff --git a/src/components/molecules/forms/prism-theme-toggle.stories.tsx b/src/components/molecules/forms/prism-theme-toggle.stories.tsx
index fee54ef..dc9090b 100644
--- a/src/components/molecules/forms/prism-theme-toggle.stories.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.stories.tsx
@@ -5,6 +5,18 @@ import PrismThemeToggleComponent from './prism-theme-toggle';
 export default {
   title: 'Molecules/Forms',
   component: PrismThemeToggleComponent,
+  argTypes: {
+    value: {
+      control: {
+        type: null,
+      },
+      description: 'The prism theme value.',
+      type: {
+        name: 'boolean',
+        required: true,
+      },
+    },
+  },
 } as ComponentMeta<typeof PrismThemeToggleComponent>;
 
 const Template: ComponentStory<typeof PrismThemeToggleComponent> = (args) => (
@@ -14,3 +26,6 @@ const Template: ComponentStory<typeof PrismThemeToggleComponent> = (args) => (
 );
 
 export const PrismThemeToggle = Template.bind({});
+PrismThemeToggle.args = {
+  value: false,
+};
diff --git a/src/components/molecules/forms/prism-theme-toggle.tsx b/src/components/molecules/forms/prism-theme-toggle.tsx
index cedb71a..81a211b 100644
--- a/src/components/molecules/forms/prism-theme-toggle.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.tsx
@@ -4,17 +4,20 @@ import Toggle, {
 } from '@components/atoms/forms/toggle';
 import Moon from '@components/atoms/icons/moon';
 import Sun from '@components/atoms/icons/sun';
-import { FC, useState } from 'react';
+import { useState, VFC } from 'react';
 import { useIntl } from 'react-intl';
 
-export type PrismThemeToggleProps = Pick<ToggleProps, 'value'>;
+export type PrismThemeToggleProps = Pick<
+  ToggleProps,
+  'labelClassName' | 'value'
+>;
 
 /**
  * PrismThemeToggle component
  *
  * Render a Toggle component to set code blocks theme.
  */
-const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ value }) => {
+const PrismThemeToggle: VFC<PrismThemeToggleProps> = ({ value, ...props }) => {
   const intl = useIntl();
   const [isDarkTheme, setIsDarkTheme] = useState<boolean>(value);
   const themeLabel = intl.formatMessage({
@@ -42,9 +45,11 @@ const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ value }) => {
       id="prism-theme-settings"
       name="prism-theme-settings"
       label={themeLabel}
+      labelSize="medium"
       choices={themeChoices}
       value={isDarkTheme}
       setValue={setIsDarkTheme}
+      {...props}
     />
   );
 };
diff --git a/src/components/molecules/forms/select-with-tooltip.module.scss b/src/components/molecules/forms/select-with-tooltip.module.scss
index 1f91f74..bfadece 100644
--- a/src/components/molecules/forms/select-with-tooltip.module.scss
+++ b/src/components/molecules/forms/select-with-tooltip.module.scss
@@ -5,14 +5,9 @@
   display: flex;
   flex-flow: row wrap;
   align-items: center;
-  gap: var(--spacing-xs);
   position: relative;
 }
 
-.label {
-  margin-right: auto;
-}
-
 .select {
   width: auto;
 
@@ -22,6 +17,8 @@
 }
 
 .btn {
+  margin-left: var(--spacing-xs);
+
   &--activated {
     background: var(--color-primary);
 
@@ -34,8 +31,7 @@
 .tooltip {
   position: absolute;
   top: calc(100% + var(--spacing-xs));
-  right: 0;
-  transform-origin: top right;
+  transform-origin: top;
   transition: all 0.75s ease-in-out 0s;
 
   &--hidden {
diff --git a/src/components/molecules/forms/select-with-tooltip.stories.tsx b/src/components/molecules/forms/select-with-tooltip.stories.tsx
index d2d36fa..c63e9b8 100644
--- a/src/components/molecules/forms/select-with-tooltip.stories.tsx
+++ b/src/components/molecules/forms/select-with-tooltip.stories.tsx
@@ -17,11 +17,25 @@ export default {
         required: true,
       },
     },
-    title: {
+    disabled: {
+      control: {
+        type: 'boolean',
+      },
+      description: 'Field state: either enabled or disabled.',
+      table: {
+        category: 'Options',
+        defaultValue: { summary: false },
+      },
+      type: {
+        name: 'boolean',
+        required: false,
+      },
+    },
+    id: {
       control: {
         type: 'text',
       },
-      description: 'The tooltip title',
+      description: 'Field id.',
       type: {
         name: 'string',
         required: true,
@@ -37,28 +51,31 @@ export default {
         required: true,
       },
     },
-    disabled: {
+    labelClassName: {
       control: {
-        type: 'boolean',
+        type: 'text',
       },
-      description: 'Field state: either enabled or disabled.',
+      description: 'Set additional classnames to the label.',
       table: {
-        category: 'Options',
-        defaultValue: { summary: false },
+        category: 'Styles',
       },
       type: {
-        name: 'boolean',
+        name: 'string',
         required: false,
       },
     },
-    id: {
+    labelSize: {
       control: {
-        type: 'text',
+        type: 'select',
+      },
+      description: 'The label size.',
+      options: ['medium', 'small'],
+      table: {
+        category: 'Options',
       },
-      description: 'Field id.',
       type: {
         name: 'string',
-        required: true,
+        required: false,
       },
     },
     name: {
@@ -98,6 +115,19 @@ export default {
         required: false,
       },
     },
+    selectClassName: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set additional classnames to the select field.',
+      table: {
+        category: 'Styles',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
     setValue: {
       control: {
         type: null,
@@ -111,6 +141,29 @@ export default {
         required: true,
       },
     },
+    title: {
+      control: {
+        type: 'text',
+      },
+      description: 'The tooltip title',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
+    tooltipClassName: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set additional classnames to the tooltip.',
+      table: {
+        category: 'Styles',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
     value: {
       control: {
         type: 'text',
diff --git a/src/components/molecules/forms/select-with-tooltip.tsx b/src/components/molecules/forms/select-with-tooltip.tsx
index 5e48d62..f537e1e 100644
--- a/src/components/molecules/forms/select-with-tooltip.tsx
+++ b/src/components/molecules/forms/select-with-tooltip.tsx
@@ -1,19 +1,22 @@
-import Select, { SelectProps } from '@components/atoms/forms/select';
-import { FC, useState } from 'react';
+import { useState, VFC } from 'react';
 import HelpButton from '../buttons/help-button';
-import Tooltip, { TooltipProps } from '../modals/tooltip';
+import Tooltip, { type TooltipProps } from '../modals/tooltip';
+import LabelledSelect, { type LabelledSelectProps } from './labelled-select';
 import styles from './select-with-tooltip.module.scss';
 
-export type SelectWithTooltipProps = SelectProps &
+export type SelectWithTooltipProps = Omit<
+  LabelledSelectProps,
+  'labelPosition'
+> &
   Pick<TooltipProps, 'title' | 'content'> & {
     /**
      * The select label.
      */
     label: string;
     /**
-     * Set additional classes to the tooltip wrapper.
+     * Set additional classnames to the tooltip wrapper.
      */
-    tooltipClasses?: string;
+    tooltipClassName?: string;
   };
 
 /**
@@ -21,12 +24,11 @@ export type SelectWithTooltipProps = SelectProps &
  *
  * Render a select with a button to display a tooltip about options.
  */
-const SelectWithTooltip: FC<SelectWithTooltipProps> = ({
+const SelectWithTooltip: VFC<SelectWithTooltipProps> = ({
   title,
   content,
   id,
-  label,
-  tooltipClasses = '',
+  tooltipClassName = '',
   ...props
 }) => {
   const [isTooltipOpened, setIsTooltipOpened] = useState<boolean>(false);
@@ -37,19 +39,21 @@ const SelectWithTooltip: FC<SelectWithTooltipProps> = ({
 
   return (
     <div className={styles.wrapper}>
-      <label htmlFor={id} className={styles.label}>
-        {label}
-      </label>
-      <Select id={id} {...props} classes={styles.select} />
+      <LabelledSelect
+        labelPosition="left"
+        id={id}
+        labelClassName={styles.label}
+        {...props}
+      />
       <HelpButton
         onClick={() => setIsTooltipOpened(!isTooltipOpened)}
-        classes={buttonModifier}
+        className={`${styles.btn} ${buttonModifier}`}
       />
       <Tooltip
         title={title}
         content={content}
         icon="?"
-        classes={`${styles.tooltip} ${tooltipModifier} ${tooltipClasses}`}
+        className={`${styles.tooltip} ${tooltipModifier} ${tooltipClassName}`}
       />
     </div>
   );
diff --git a/src/components/molecules/forms/theme-toggle.stories.tsx b/src/components/molecules/forms/theme-toggle.stories.tsx
index 5970afd..a9bcf73 100644
--- a/src/components/molecules/forms/theme-toggle.stories.tsx
+++ b/src/components/molecules/forms/theme-toggle.stories.tsx
@@ -5,6 +5,18 @@ import ThemeToggleComponent from './theme-toggle';
 export default {
   title: 'Molecules/Forms',
   component: ThemeToggleComponent,
+  argTypes: {
+    value: {
+      control: {
+        type: null,
+      },
+      description: 'The theme value.',
+      type: {
+        name: 'boolean',
+        required: true,
+      },
+    },
+  },
 } as ComponentMeta<typeof ThemeToggleComponent>;
 
 const Template: ComponentStory<typeof ThemeToggleComponent> = (args) => (
@@ -14,3 +26,6 @@ const Template: ComponentStory<typeof ThemeToggleComponent> = (args) => (
 );
 
 export const ThemeToggle = Template.bind({});
+ThemeToggle.args = {
+  value: false,
+};
diff --git a/src/components/molecules/forms/theme-toggle.tsx b/src/components/molecules/forms/theme-toggle.tsx
index c927151..6d54591 100644
--- a/src/components/molecules/forms/theme-toggle.tsx
+++ b/src/components/molecules/forms/theme-toggle.tsx
@@ -4,17 +4,17 @@ import Toggle, {
 } from '@components/atoms/forms/toggle';
 import Moon from '@components/atoms/icons/moon';
 import Sun from '@components/atoms/icons/sun';
-import { FC, useState } from 'react';
+import { useState, VFC } from 'react';
 import { useIntl } from 'react-intl';
 
-export type ThemeToggleProps = Pick<ToggleProps, 'value'>;
+export type ThemeToggleProps = Pick<ToggleProps, 'labelClassName' | 'value'>;
 
 /**
  * ThemeToggle component
  *
  * Render a Toggle component to set theme.
  */
-const ThemeToggle: FC<ThemeToggleProps> = ({ value }) => {
+const ThemeToggle: VFC<ThemeToggleProps> = ({ value, ...props }) => {
   const intl = useIntl();
   const [isDarkTheme, setIsDarkTheme] = useState<boolean>(value);
   const themeLabel = intl.formatMessage({
@@ -42,9 +42,11 @@ const ThemeToggle: FC<ThemeToggleProps> = ({ value }) => {
       id="theme-settings"
       name="theme-settings"
       label={themeLabel}
+      labelSize="medium"
       choices={themeChoices}
       value={isDarkTheme}
       setValue={setIsDarkTheme}
+      {...props}
     />
   );
 };
diff --git a/src/components/organisms/modals/settings-modal.module.scss b/src/components/organisms/modals/settings-modal.module.scss
new file mode 100644
index 0000000..f17c9b3
--- /dev/null
+++ b/src/components/organisms/modals/settings-modal.module.scss
@@ -0,0 +1,14 @@
+.wrapper {
+  max-width: 30ch;
+
+  .label {
+    margin-right: auto;
+  }
+}
+
+.tooltip {
+  width: 120%;
+  top: calc(100% + var(--spacing-sm));
+  right: -10%;
+  transform-origin: top right;
+}
diff --git a/src/components/organisms/modals/settings-modal.stories.tsx b/src/components/organisms/modals/settings-modal.stories.tsx
new file mode 100644
index 0000000..c19a6d7
--- /dev/null
+++ b/src/components/organisms/modals/settings-modal.stories.tsx
@@ -0,0 +1,31 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { IntlProvider } from 'react-intl';
+import SettingsModal from './settings-modal';
+
+export default {
+  title: 'Organisms/Modals',
+  component: SettingsModal,
+  argTypes: {
+    className: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set additional classnames to the modal wrapper.',
+      table: {
+        category: 'Styles',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
+  },
+} as ComponentMeta<typeof SettingsModal>;
+
+const Template: ComponentStory<typeof SettingsModal> = (args) => (
+  <IntlProvider locale="en">
+    <SettingsModal {...args} />
+  </IntlProvider>
+);
+
+export const Settings = Template.bind({});
diff --git a/src/components/organisms/modals/settings-modal.test.tsx b/src/components/organisms/modals/settings-modal.test.tsx
new file mode 100644
index 0000000..44695d7
--- /dev/null
+++ b/src/components/organisms/modals/settings-modal.test.tsx
@@ -0,0 +1,34 @@
+import { render, screen } from '@test-utils';
+import SettingsModal from './settings-modal';
+
+jest.mock('next/dynamic', () => () => 'dynamic-import');
+
+describe('SettingsModal', () => {
+  it('renders a theme toggle setting', () => {
+    render(<SettingsModal />);
+    expect(
+      screen.getByRole('checkbox', { name: /^Theme:/i })
+    ).toBeInTheDocument();
+  });
+
+  it('renders a code blocks toggle setting', () => {
+    render(<SettingsModal />);
+    expect(
+      screen.getByRole('checkbox', { name: /^Code blocks:/i })
+    ).toBeInTheDocument();
+  });
+
+  it('renders a motion setting', () => {
+    render(<SettingsModal />);
+    expect(
+      screen.getByRole('checkbox', { name: /^Animations:/i })
+    ).toBeInTheDocument();
+  });
+
+  it('renders a Ackee setting', () => {
+    render(<SettingsModal />);
+    expect(
+      screen.getByRole('combobox', { name: /^Tracking:/i })
+    ).toBeInTheDocument();
+  });
+});
diff --git a/src/components/organisms/modals/settings-modal.tsx b/src/components/organisms/modals/settings-modal.tsx
new file mode 100644
index 0000000..0fac332
--- /dev/null
+++ b/src/components/organisms/modals/settings-modal.tsx
@@ -0,0 +1,51 @@
+import Form from '@components/atoms/forms/form';
+import AckeeSelect from '@components/molecules/forms/ackee-select';
+import MotionToggle from '@components/molecules/forms/motion-toggle';
+import PrismThemeToggle from '@components/molecules/forms/prism-theme-toggle';
+import ThemeToggle from '@components/molecules/forms/theme-toggle';
+import Modal from '@components/molecules/modals/modal';
+import { VFC } from 'react';
+import { useIntl } from 'react-intl';
+import styles from './settings-modal.module.scss';
+
+export type SettingsModalProps = {
+  /**
+   * Set additional classnames to modal wrapper.
+   */
+  className?: string;
+};
+
+/**
+ * SettingsModal component
+ *
+ * Render a modal with settings options.
+ */
+const SettingsModal: VFC<SettingsModalProps> = ({ className }) => {
+  const intl = useIntl();
+  const title = intl.formatMessage({
+    defaultMessage: 'Settings',
+    description: 'SettingsModal: title',
+    id: 'gPfT/K',
+  });
+
+  return (
+    <Modal
+      title={title}
+      icon="cogs"
+      className={`${styles.wrapper} ${className}`}
+    >
+      <Form onSubmit={() => null}>
+        <ThemeToggle labelClassName={styles.label} value={false} />
+        <PrismThemeToggle labelClassName={styles.label} value={false} />
+        <MotionToggle labelClassName={styles.label} value={false} />
+        <AckeeSelect
+          initialValue="full"
+          labelClassName={styles.label}
+          tooltipClassName={styles.tooltip}
+        />
+      </Form>
+    </Modal>
+  );
+};
+
+export default SettingsModal;
-- 
cgit v1.2.3


From 9cbef657ac9484cbf79234527ec6bfe6a451ece8 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Sat, 9 Apr 2022 19:16:30 +0200
Subject: refactor(toggle): use Checkbox component and move it to molecules

---
 src/components/atoms/forms/toggle.module.scss      |  75 -------------
 src/components/atoms/forms/toggle.stories.tsx      | 104 ------------------
 src/components/atoms/forms/toggle.test.tsx         |  29 -----
 src/components/atoms/forms/toggle.tsx              |  86 ---------------
 src/components/molecules/forms/motion-toggle.tsx   |   2 +-
 .../molecules/forms/prism-theme-toggle.tsx         |   6 +-
 src/components/molecules/forms/theme-toggle.tsx    |   6 +-
 src/components/molecules/forms/toggle.module.scss  |  75 +++++++++++++
 src/components/molecules/forms/toggle.stories.tsx  | 117 +++++++++++++++++++++
 src/components/molecules/forms/toggle.test.tsx     |  29 +++++
 src/components/molecules/forms/toggle.tsx          |  86 +++++++++++++++
 11 files changed, 314 insertions(+), 301 deletions(-)
 delete mode 100644 src/components/atoms/forms/toggle.module.scss
 delete mode 100644 src/components/atoms/forms/toggle.stories.tsx
 delete mode 100644 src/components/atoms/forms/toggle.test.tsx
 delete mode 100644 src/components/atoms/forms/toggle.tsx
 create mode 100644 src/components/molecules/forms/toggle.module.scss
 create mode 100644 src/components/molecules/forms/toggle.stories.tsx
 create mode 100644 src/components/molecules/forms/toggle.test.tsx
 create mode 100644 src/components/molecules/forms/toggle.tsx

(limited to 'src/components/molecules/forms/prism-theme-toggle.tsx')

diff --git a/src/components/atoms/forms/toggle.module.scss b/src/components/atoms/forms/toggle.module.scss
deleted file mode 100644
index 2e8a49f..0000000
--- a/src/components/atoms/forms/toggle.module.scss
+++ /dev/null
@@ -1,75 +0,0 @@
-@use "@styles/abstracts/functions" as fun;
-
-.label {
-  --toggle-width: #{fun.convert-px(45)};
-  --toggle-height: calc(var(--toggle-width) / 2);
-
-  display: inline-flex;
-  align-items: center;
-  width: 100%;
-}
-
-.title {
-  margin-right: var(--spacing-2xs);
-}
-
-.toggle {
-  display: inline-flex;
-  align-items: center;
-  width: var(--toggle-width);
-  height: var(--toggle-height);
-  background: var(--color-shadow-light);
-  border: fun.convert-px(1) solid var(--color-primary);
-  border-radius: fun.convert-px(32);
-  box-shadow: inset 0 0 fun.convert-px(3) 0 var(--color-shadow-dark);
-  margin: 0 var(--spacing-2xs);
-  position: relative;
-
-  &::after {
-    content: "";
-    display: block;
-    width: calc((var(--toggle-width) / 2) - 1px);
-    height: calc((var(--toggle-width) / 2) - 1px);
-    background: var(--color-primary-light);
-    border: fun.convert-px(1) solid var(--color-primary);
-    border-radius: 50%;
-    box-shadow: inset 0 0 fun.convert-px(1) fun.convert-px(1)
-        var(--color-shadow),
-      0 0 fun.convert-px(2) fun.convert-px(1) var(--color-shadow-light);
-    position: absolute;
-    left: fun.convert-px(-2);
-    transition: all 0.3s ease-in-out 0s;
-  }
-}
-
-.checkbox {
-  position: absolute;
-  opacity: 0;
-  cursor: pointer;
-
-  &:checked ~ .label {
-    .toggle::after {
-      position: absolute;
-      left: calc(100% - (var(--toggle-width) / 2) + #{fun.convert-px(2)});
-    }
-  }
-
-  &:hover,
-  &:focus {
-    ~ .label {
-      .toggle::after {
-        background: var(--color-primary-lighter);
-      }
-    }
-  }
-
-  &:focus ~ .label {
-    .title {
-      text-decoration: underline solid var(--color-primary) fun.convert-px(2);
-    }
-
-    .toggle {
-      outline: var(--color-border) solid fun.convert-px(5);
-    }
-  }
-}
diff --git a/src/components/atoms/forms/toggle.stories.tsx b/src/components/atoms/forms/toggle.stories.tsx
deleted file mode 100644
index ea08694..0000000
--- a/src/components/atoms/forms/toggle.stories.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { useState } from 'react';
-import ToggleComponent from './toggle';
-
-export default {
-  title: 'Atoms/Forms',
-  component: ToggleComponent,
-  argTypes: {
-    choices: {
-      description: 'The toggle choices.',
-      type: {
-        name: 'object',
-        required: true,
-        value: {},
-      },
-    },
-    id: {
-      control: {
-        type: 'text',
-      },
-      description: 'The input id.',
-      type: {
-        name: 'string',
-        required: true,
-      },
-    },
-    label: {
-      control: {
-        type: 'text',
-      },
-      description: 'The toggle label.',
-      type: {
-        name: 'string',
-        required: true,
-      },
-    },
-    labelSize: {
-      control: {
-        type: 'select',
-      },
-      description: 'The label size.',
-      options: ['medium', 'small'],
-      table: {
-        category: 'Options',
-      },
-      type: {
-        name: 'string',
-        required: false,
-      },
-    },
-    name: {
-      control: {
-        type: 'text',
-      },
-      description: 'The input name.',
-      type: {
-        name: 'string',
-        required: true,
-      },
-    },
-    setValue: {
-      control: {
-        type: null,
-      },
-      description: 'A callback function to update the toggle value.',
-      type: {
-        name: 'function',
-        required: true,
-      },
-    },
-    value: {
-      control: {
-        type: null,
-      },
-      description: 'The toggle value. True if checked.',
-      type: {
-        name: 'boolean',
-        required: true,
-      },
-    },
-  },
-} as ComponentMeta<typeof ToggleComponent>;
-
-const Template: ComponentStory<typeof ToggleComponent> = ({
-  value: _value,
-  setValue: _setValue,
-  ...args
-}) => {
-  const [isChecked, setIsChecked] = useState<boolean>(false);
-  return (
-    <ToggleComponent value={isChecked} setValue={setIsChecked} {...args} />
-  );
-};
-
-export const Toggle = Template.bind({});
-Toggle.args = {
-  choices: {
-    left: 'On',
-    right: 'Off',
-  },
-  id: 'toggle-example',
-  label: 'Activate setting:',
-  name: 'toggle-example',
-};
diff --git a/src/components/atoms/forms/toggle.test.tsx b/src/components/atoms/forms/toggle.test.tsx
deleted file mode 100644
index fb97adc..0000000
--- a/src/components/atoms/forms/toggle.test.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { render, screen } from '@test-utils';
-import Toggle from './toggle';
-
-const choices = {
-  left: 'On',
-  right: 'Off',
-};
-
-const label = 'Activate this setting:';
-
-describe('Toggle', () => {
-  it('renders a checked toggle', () => {
-    render(
-      <Toggle
-        id="toggle-example"
-        name="toggle-example"
-        choices={choices}
-        label={label}
-        value={true}
-        setValue={(__value) => null}
-      />
-    );
-    expect(
-      screen.getByRole('checkbox', {
-        name: `${label} ${choices.left} ${choices.right}`,
-      })
-    ).toBeChecked();
-  });
-});
diff --git a/src/components/atoms/forms/toggle.tsx b/src/components/atoms/forms/toggle.tsx
deleted file mode 100644
index c3bc09d..0000000
--- a/src/components/atoms/forms/toggle.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { ReactNode, VFC } from 'react';
-import Label, { LabelProps } from './label';
-import styles from './toggle.module.scss';
-
-export type ToggleChoices = {
-  /**
-   * The left part of the toggle field (unchecked).
-   */
-  left: ReactNode;
-  /**
-   * The right part of the toggle field (checked).
-   */
-  right: ReactNode;
-};
-
-export type ToggleProps = {
-  /**
-   * The toggle choices.
-   */
-  choices: ToggleChoices;
-  /**
-   * The input id.
-   */
-  id: string;
-  /**
-   * The toggle label.
-   */
-  label: string;
-  /**
-   * Set additional classnames to the label.
-   */
-  labelClassName?: string;
-  /**
-   * The label size.
-   */
-  labelSize?: LabelProps['size'];
-  /**
-   * The input name.
-   */
-  name: string;
-  /**
-   * The toggle value. True if checked.
-   */
-  value: boolean;
-  /**
-   * A callback function to update the toggle value.
-   */
-  setValue: (value: boolean) => void;
-};
-
-/**
- * Toggle component
- *
- * Render a toggle with a label and two choices.
- */
-const Toggle: VFC<ToggleProps> = ({
-  choices,
-  id,
-  label,
-  labelClassName = '',
-  labelSize,
-  name,
-  setValue,
-  value,
-}) => {
-  return (
-    <>
-      <input
-        type="checkbox"
-        name={name}
-        id={id}
-        checked={value}
-        onChange={() => setValue(!value)}
-        className={styles.checkbox}
-      />
-      <Label size={labelSize} htmlFor={id} className={styles.label}>
-        <span className={`${styles.title} ${labelClassName}`}>{label}</span>
-        {choices.left}
-        <span className={styles.toggle}></span>
-        {choices.right}
-      </Label>
-    </>
-  );
-};
-
-export default Toggle;
diff --git a/src/components/molecules/forms/motion-toggle.tsx b/src/components/molecules/forms/motion-toggle.tsx
index d4f7d11..9f30b42 100644
--- a/src/components/molecules/forms/motion-toggle.tsx
+++ b/src/components/molecules/forms/motion-toggle.tsx
@@ -1,7 +1,7 @@
 import Toggle, {
   ToggleChoices,
   ToggleProps,
-} from '@components/atoms/forms/toggle';
+} from '@components/molecules/forms/toggle';
 import { useState, VFC } from 'react';
 import { useIntl } from 'react-intl';
 
diff --git a/src/components/molecules/forms/prism-theme-toggle.tsx b/src/components/molecules/forms/prism-theme-toggle.tsx
index 81a211b..daee6bd 100644
--- a/src/components/molecules/forms/prism-theme-toggle.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.tsx
@@ -1,9 +1,9 @@
+import Moon from '@components/atoms/icons/moon';
+import Sun from '@components/atoms/icons/sun';
 import Toggle, {
   ToggleChoices,
   ToggleProps,
-} from '@components/atoms/forms/toggle';
-import Moon from '@components/atoms/icons/moon';
-import Sun from '@components/atoms/icons/sun';
+} from '@components/molecules/forms/toggle';
 import { useState, VFC } from 'react';
 import { useIntl } from 'react-intl';
 
diff --git a/src/components/molecules/forms/theme-toggle.tsx b/src/components/molecules/forms/theme-toggle.tsx
index 6d54591..eb56ce9 100644
--- a/src/components/molecules/forms/theme-toggle.tsx
+++ b/src/components/molecules/forms/theme-toggle.tsx
@@ -1,9 +1,9 @@
+import Moon from '@components/atoms/icons/moon';
+import Sun from '@components/atoms/icons/sun';
 import Toggle, {
   ToggleChoices,
   ToggleProps,
-} from '@components/atoms/forms/toggle';
-import Moon from '@components/atoms/icons/moon';
-import Sun from '@components/atoms/icons/sun';
+} from '@components/molecules/forms/toggle';
 import { useState, VFC } from 'react';
 import { useIntl } from 'react-intl';
 
diff --git a/src/components/molecules/forms/toggle.module.scss b/src/components/molecules/forms/toggle.module.scss
new file mode 100644
index 0000000..2e8a49f
--- /dev/null
+++ b/src/components/molecules/forms/toggle.module.scss
@@ -0,0 +1,75 @@
+@use "@styles/abstracts/functions" as fun;
+
+.label {
+  --toggle-width: #{fun.convert-px(45)};
+  --toggle-height: calc(var(--toggle-width) / 2);
+
+  display: inline-flex;
+  align-items: center;
+  width: 100%;
+}
+
+.title {
+  margin-right: var(--spacing-2xs);
+}
+
+.toggle {
+  display: inline-flex;
+  align-items: center;
+  width: var(--toggle-width);
+  height: var(--toggle-height);
+  background: var(--color-shadow-light);
+  border: fun.convert-px(1) solid var(--color-primary);
+  border-radius: fun.convert-px(32);
+  box-shadow: inset 0 0 fun.convert-px(3) 0 var(--color-shadow-dark);
+  margin: 0 var(--spacing-2xs);
+  position: relative;
+
+  &::after {
+    content: "";
+    display: block;
+    width: calc((var(--toggle-width) / 2) - 1px);
+    height: calc((var(--toggle-width) / 2) - 1px);
+    background: var(--color-primary-light);
+    border: fun.convert-px(1) solid var(--color-primary);
+    border-radius: 50%;
+    box-shadow: inset 0 0 fun.convert-px(1) fun.convert-px(1)
+        var(--color-shadow),
+      0 0 fun.convert-px(2) fun.convert-px(1) var(--color-shadow-light);
+    position: absolute;
+    left: fun.convert-px(-2);
+    transition: all 0.3s ease-in-out 0s;
+  }
+}
+
+.checkbox {
+  position: absolute;
+  opacity: 0;
+  cursor: pointer;
+
+  &:checked ~ .label {
+    .toggle::after {
+      position: absolute;
+      left: calc(100% - (var(--toggle-width) / 2) + #{fun.convert-px(2)});
+    }
+  }
+
+  &:hover,
+  &:focus {
+    ~ .label {
+      .toggle::after {
+        background: var(--color-primary-lighter);
+      }
+    }
+  }
+
+  &:focus ~ .label {
+    .title {
+      text-decoration: underline solid var(--color-primary) fun.convert-px(2);
+    }
+
+    .toggle {
+      outline: var(--color-border) solid fun.convert-px(5);
+    }
+  }
+}
diff --git a/src/components/molecules/forms/toggle.stories.tsx b/src/components/molecules/forms/toggle.stories.tsx
new file mode 100644
index 0000000..078a34c
--- /dev/null
+++ b/src/components/molecules/forms/toggle.stories.tsx
@@ -0,0 +1,117 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { useState } from 'react';
+import ToggleComponent from './toggle';
+
+export default {
+  title: 'Molecules/Forms',
+  component: ToggleComponent,
+  argTypes: {
+    choices: {
+      description: 'The toggle choices.',
+      type: {
+        name: 'object',
+        required: true,
+        value: {},
+      },
+    },
+    id: {
+      control: {
+        type: 'text',
+      },
+      description: 'The input id.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
+    label: {
+      control: {
+        type: 'text',
+      },
+      description: 'The toggle label.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
+    labelClassName: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set additional classnames to the label.',
+      table: {
+        category: 'Styles',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
+    labelSize: {
+      control: {
+        type: 'select',
+      },
+      description: 'The label size.',
+      options: ['medium', 'small'],
+      table: {
+        category: 'Options',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
+    name: {
+      control: {
+        type: 'text',
+      },
+      description: 'The input name.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
+    setValue: {
+      control: {
+        type: null,
+      },
+      description: 'A callback function to update the toggle value.',
+      type: {
+        name: 'function',
+        required: true,
+      },
+    },
+    value: {
+      control: {
+        type: null,
+      },
+      description: 'The toggle value. True if checked.',
+      type: {
+        name: 'boolean',
+        required: true,
+      },
+    },
+  },
+} as ComponentMeta<typeof ToggleComponent>;
+
+const Template: ComponentStory<typeof ToggleComponent> = ({
+  value: _value,
+  setValue: _setValue,
+  ...args
+}) => {
+  const [isChecked, setIsChecked] = useState<boolean>(false);
+  return (
+    <ToggleComponent value={isChecked} setValue={setIsChecked} {...args} />
+  );
+};
+
+export const Toggle = Template.bind({});
+Toggle.args = {
+  choices: {
+    left: 'On',
+    right: 'Off',
+  },
+  id: 'toggle-example',
+  label: 'Activate setting:',
+  name: 'toggle-example',
+};
diff --git a/src/components/molecules/forms/toggle.test.tsx b/src/components/molecules/forms/toggle.test.tsx
new file mode 100644
index 0000000..fb97adc
--- /dev/null
+++ b/src/components/molecules/forms/toggle.test.tsx
@@ -0,0 +1,29 @@
+import { render, screen } from '@test-utils';
+import Toggle from './toggle';
+
+const choices = {
+  left: 'On',
+  right: 'Off',
+};
+
+const label = 'Activate this setting:';
+
+describe('Toggle', () => {
+  it('renders a checked toggle', () => {
+    render(
+      <Toggle
+        id="toggle-example"
+        name="toggle-example"
+        choices={choices}
+        label={label}
+        value={true}
+        setValue={(__value) => null}
+      />
+    );
+    expect(
+      screen.getByRole('checkbox', {
+        name: `${label} ${choices.left} ${choices.right}`,
+      })
+    ).toBeChecked();
+  });
+});
diff --git a/src/components/molecules/forms/toggle.tsx b/src/components/molecules/forms/toggle.tsx
new file mode 100644
index 0000000..dff2d2d
--- /dev/null
+++ b/src/components/molecules/forms/toggle.tsx
@@ -0,0 +1,86 @@
+import Checkbox from '@components/atoms/forms/checkbox';
+import Label, { type LabelProps } from '@components/atoms/forms/label';
+import { ReactNode, VFC } from 'react';
+import styles from './toggle.module.scss';
+
+export type ToggleChoices = {
+  /**
+   * The left part of the toggle field (unchecked).
+   */
+  left: ReactNode;
+  /**
+   * The right part of the toggle field (checked).
+   */
+  right: ReactNode;
+};
+
+export type ToggleProps = {
+  /**
+   * The toggle choices.
+   */
+  choices: ToggleChoices;
+  /**
+   * The input id.
+   */
+  id: string;
+  /**
+   * The toggle label.
+   */
+  label: string;
+  /**
+   * Set additional classnames to the label.
+   */
+  labelClassName?: string;
+  /**
+   * The label size.
+   */
+  labelSize?: LabelProps['size'];
+  /**
+   * The input name.
+   */
+  name: string;
+  /**
+   * The toggle value. True if checked.
+   */
+  value: boolean;
+  /**
+   * A callback function to update the toggle value.
+   */
+  setValue: (value: boolean) => void;
+};
+
+/**
+ * Toggle component
+ *
+ * Render a toggle with a label and two choices.
+ */
+const Toggle: VFC<ToggleProps> = ({
+  choices,
+  id,
+  label,
+  labelClassName = '',
+  labelSize,
+  name,
+  setValue,
+  value,
+}) => {
+  return (
+    <>
+      <Checkbox
+        name={name}
+        id={id}
+        value={value}
+        setValue={() => setValue(!value)}
+        className={styles.checkbox}
+      />
+      <Label size={labelSize} htmlFor={id} className={styles.label}>
+        <span className={`${styles.title} ${labelClassName}`}>{label}</span>
+        {choices.left}
+        <span className={styles.toggle}></span>
+        {choices.right}
+      </Label>
+    </>
+  );
+};
+
+export default Toggle;
-- 
cgit v1.2.3


From 5a6e4eea16047083e2de0e91a1b3ed9be8d6eb68 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Sat, 16 Apr 2022 16:08:49 +0200
Subject: refactor: support React 18

I replaced the deprecated VFC type with FC type and made all children
explicits.
Formatjs is still not compatible with React 18 so I need to skip type
checking when comitting. There are some type errors because of
IntlProvider in Storybook stories.
---
 jest.setup.js                                      |  1 +
 src/components/atoms/buttons/button-link.tsx       |  6 +-
 src/components/atoms/buttons/button.tsx            |  6 +-
 src/components/atoms/forms/checkbox.tsx            |  4 +-
 src/components/atoms/forms/field.tsx               |  4 +-
 src/components/atoms/forms/form.test.tsx           |  6 +-
 src/components/atoms/forms/form.tsx                |  6 +-
 src/components/atoms/forms/label.tsx               |  6 +-
 src/components/atoms/forms/select.tsx              |  4 +-
 src/components/atoms/headings/heading.tsx          |  6 +-
 src/components/atoms/icons/arrow.tsx               |  4 +-
 src/components/atoms/icons/career.tsx              |  4 +-
 src/components/atoms/icons/cc-by-sa.tsx            |  4 +-
 src/components/atoms/icons/close.tsx               |  4 +-
 src/components/atoms/icons/cog.tsx                 |  4 +-
 src/components/atoms/icons/computer-screen.tsx     |  4 +-
 src/components/atoms/icons/envelop.tsx             |  4 +-
 src/components/atoms/icons/hamburger.tsx           |  2 +-
 src/components/atoms/icons/home.tsx                |  4 +-
 src/components/atoms/icons/magnifying-glass.tsx    |  4 +-
 src/components/atoms/icons/moon.tsx                |  6 +-
 src/components/atoms/icons/plus-minus.tsx          |  2 +-
 src/components/atoms/icons/posts-stack.tsx         |  4 +-
 src/components/atoms/icons/sun.tsx                 |  6 +-
 src/components/atoms/images/logo.tsx               |  6 +-
 src/components/atoms/layout/copyright.tsx          |  4 +-
 src/components/atoms/layout/main.tsx               |  6 +-
 src/components/atoms/layout/no-script.tsx          |  4 +-
 src/components/atoms/layout/notice.tsx             |  4 +-
 src/components/atoms/layout/section.tsx            |  4 +-
 src/components/atoms/links/link.module.scss        | 20 ++++--
 src/components/atoms/links/link.tsx                |  6 +-
 src/components/atoms/links/nav-link.tsx            |  4 +-
 src/components/atoms/links/sharing-link.tsx        |  4 +-
 src/components/atoms/links/social-link.tsx         |  4 +-
 src/components/atoms/lists/description-list.tsx    |  4 +-
 src/components/atoms/lists/list.tsx                |  4 +-
 src/components/atoms/loaders/progress-bar.tsx      |  4 +-
 src/components/atoms/loaders/spinner.tsx           |  4 +-
 src/components/molecules/buttons/back-to-top.tsx   | 14 ++--
 .../molecules/buttons/heading-button.tsx           |  4 +-
 src/components/molecules/buttons/help-button.tsx   |  6 +-
 .../molecules/forms/ackee-select.test.tsx          | 16 +++--
 src/components/molecules/forms/ackee-select.tsx    |  6 +-
 src/components/molecules/forms/labelled-field.tsx  |  4 +-
 src/components/molecules/forms/labelled-select.tsx | 15 +++--
 src/components/molecules/forms/motion-toggle.tsx   |  8 +--
 .../molecules/forms/prism-theme-toggle.tsx         |  8 +--
 .../molecules/forms/select-with-tooltip.tsx        | 10 +--
 src/components/molecules/forms/theme-toggle.tsx    |  8 +--
 src/components/molecules/forms/toggle.tsx          | 18 ++---
 .../molecules/images/responsive-image.tsx          | 10 +--
 src/components/molecules/layout/branding.tsx       | 12 ++--
 src/components/molecules/layout/card.tsx           |  8 +--
 src/components/molecules/layout/flipping-logo.tsx  | 10 +--
 src/components/molecules/layout/meta.tsx           |  4 +-
 src/components/molecules/layout/widget.tsx         | 10 ++-
 src/components/molecules/modals/modal.test.tsx     | 13 +++-
 src/components/molecules/modals/modal.tsx          | 14 ++--
 src/components/molecules/modals/tooltip.tsx        |  4 +-
 src/components/molecules/nav/breadcrumb.tsx        |  4 +-
 src/components/molecules/nav/nav.tsx               |  4 +-
 .../organisms/forms/comment-form.stories.tsx       |  1 -
 src/components/organisms/forms/comment-form.tsx    |  4 +-
 src/components/organisms/forms/contact-form.tsx    |  4 +-
 src/components/organisms/forms/search-form.tsx     | 11 +++-
 src/components/organisms/layout/cards-list.tsx     |  4 +-
 src/components/organisms/layout/footer.tsx         | 13 ++--
 src/components/organisms/layout/overview.tsx       |  4 +-
 .../organisms/layout/summary.stories.tsx           |  2 +-
 src/components/organisms/layout/summary.test.tsx   |  2 +-
 src/components/organisms/layout/summary.tsx        | 77 +++++++++++++---------
 src/components/organisms/modals/search-modal.tsx   |  8 +--
 src/components/organisms/modals/settings-modal.tsx | 14 ++--
 src/components/organisms/toolbar/main-nav.tsx      | 18 +++--
 src/components/organisms/toolbar/search.tsx        | 16 ++---
 src/components/organisms/toolbar/settings.tsx      | 14 ++--
 src/components/organisms/toolbar/toolbar.tsx       |  4 +-
 src/components/organisms/widgets/image-widget.tsx  | 34 ++++------
 .../organisms/widgets/links-list-widget.tsx        |  9 ++-
 src/components/organisms/widgets/sharing.tsx       |  6 +-
 81 files changed, 353 insertions(+), 289 deletions(-)

(limited to 'src/components/molecules/forms/prism-theme-toggle.tsx')

diff --git a/jest.setup.js b/jest.setup.js
index 5c5ecbc..d50c988 100644
--- a/jest.setup.js
+++ b/jest.setup.js
@@ -2,3 +2,4 @@ import '@testing-library/jest-dom/extend-expect';
 import './__tests__/jest/__mocks__/matchMedia.mock';
 
 jest.mock('next/dist/client/router', () => require('next-router-mock'));
+jest.mock('next/dynamic', () => () => 'dynamic-import');
diff --git a/src/components/atoms/buttons/button-link.tsx b/src/components/atoms/buttons/button-link.tsx
index 77a7f7b..906fa76 100644
--- a/src/components/atoms/buttons/button-link.tsx
+++ b/src/components/atoms/buttons/button-link.tsx
@@ -1,5 +1,5 @@
 import Link from 'next/link';
-import { FC } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './buttons.module.scss';
 
 export type ButtonLinkProps = {
@@ -7,6 +7,10 @@ export type ButtonLinkProps = {
    * ButtonLink accessible label.
    */
   'aria-label'?: string;
+  /**
+   * The button link body.
+   */
+  children: ReactNode;
   /**
    * Set additional classnames to the button link.
    */
diff --git a/src/components/atoms/buttons/button.tsx b/src/components/atoms/buttons/button.tsx
index 545c5c5..a6eef8b 100644
--- a/src/components/atoms/buttons/button.tsx
+++ b/src/components/atoms/buttons/button.tsx
@@ -1,7 +1,11 @@
-import { FC, MouseEventHandler } from 'react';
+import { FC, MouseEventHandler, ReactNode } from 'react';
 import styles from './buttons.module.scss';
 
 export type ButtonProps = {
+  /**
+   * The button body.
+   */
+  children: ReactNode;
   /**
    * Set additional classnames to the button wrapper.
    */
diff --git a/src/components/atoms/forms/checkbox.tsx b/src/components/atoms/forms/checkbox.tsx
index 8babcc8..aec97f0 100644
--- a/src/components/atoms/forms/checkbox.tsx
+++ b/src/components/atoms/forms/checkbox.tsx
@@ -1,4 +1,4 @@
-import { SetStateAction, VFC } from 'react';
+import { FC, SetStateAction } from 'react';
 
 export type CheckboxProps = {
   /**
@@ -32,7 +32,7 @@ export type CheckboxProps = {
  *
  * Render a checkbox type input.
  */
-const Checkbox: VFC<CheckboxProps> = ({ value, setValue, ...props }) => {
+const Checkbox: FC<CheckboxProps> = ({ value, setValue, ...props }) => {
   return (
     <input
       type="checkbox"
diff --git a/src/components/atoms/forms/field.tsx b/src/components/atoms/forms/field.tsx
index 2e75d0f..e45a8a7 100644
--- a/src/components/atoms/forms/field.tsx
+++ b/src/components/atoms/forms/field.tsx
@@ -1,4 +1,4 @@
-import { ChangeEvent, SetStateAction, VFC } from 'react';
+import { ChangeEvent, FC, SetStateAction } from 'react';
 import styles from './forms.module.scss';
 
 export type FieldType =
@@ -72,7 +72,7 @@ export type FieldProps = {
  *
  * Render either an input or a textarea.
  */
-const Field: VFC<FieldProps> = ({
+const Field: FC<FieldProps> = ({
   className = '',
   setValue,
   type,
diff --git a/src/components/atoms/forms/form.test.tsx b/src/components/atoms/forms/form.test.tsx
index 9cd3c58..8b534f1 100644
--- a/src/components/atoms/forms/form.test.tsx
+++ b/src/components/atoms/forms/form.test.tsx
@@ -3,7 +3,11 @@ import Form from './form';
 
 describe('Form', () => {
   it('renders a form', () => {
-    render(<Form aria-label="Jest form" onSubmit={() => null}></Form>);
+    render(
+      <Form aria-label="Jest form" onSubmit={() => null}>
+        Fields
+      </Form>
+    );
     expect(screen.getByRole('form', { name: 'Jest form' })).toBeInTheDocument();
   });
 });
diff --git a/src/components/atoms/forms/form.tsx b/src/components/atoms/forms/form.tsx
index 8e80930..ef8dce4 100644
--- a/src/components/atoms/forms/form.tsx
+++ b/src/components/atoms/forms/form.tsx
@@ -1,4 +1,4 @@
-import { Children, FC, FormEvent, Fragment } from 'react';
+import { Children, FC, FormEvent, Fragment, ReactNode } from 'react';
 import styles from './forms.module.scss';
 
 export type FormProps = {
@@ -10,6 +10,10 @@ export type FormProps = {
    * One or more ids that refers to the form name.
    */
   'aria-labelledby'?: string;
+  /**
+   * The form body.
+   */
+  children: ReactNode;
   /**
    * Set additional classnames to the form wrapper.
    */
diff --git a/src/components/atoms/forms/label.tsx b/src/components/atoms/forms/label.tsx
index 8d57ee2..ce4c70f 100644
--- a/src/components/atoms/forms/label.tsx
+++ b/src/components/atoms/forms/label.tsx
@@ -1,7 +1,11 @@
-import { FC } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './label.module.scss';
 
 export type LabelProps = {
+  /**
+   * The label body.
+   */
+  children: ReactNode;
   /**
    * Add classnames to the label.
    */
diff --git a/src/components/atoms/forms/select.tsx b/src/components/atoms/forms/select.tsx
index 25e86e0..dbe9b37 100644
--- a/src/components/atoms/forms/select.tsx
+++ b/src/components/atoms/forms/select.tsx
@@ -1,4 +1,4 @@
-import { ChangeEvent, SetStateAction, VFC } from 'react';
+import { ChangeEvent, FC, SetStateAction } from 'react';
 import styles from './forms.module.scss';
 
 export type SelectOptions = {
@@ -60,7 +60,7 @@ export type SelectProps = {
  *
  * Render a HTML select element.
  */
-const Select: VFC<SelectProps> = ({
+const Select: FC<SelectProps> = ({
   className = '',
   options,
   setValue,
diff --git a/src/components/atoms/headings/heading.tsx b/src/components/atoms/headings/heading.tsx
index 4703b5d..c5bf4ca 100644
--- a/src/components/atoms/headings/heading.tsx
+++ b/src/components/atoms/headings/heading.tsx
@@ -1,9 +1,13 @@
-import { FC } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './heading.module.scss';
 
 export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
 
 export type HeadingProps = {
+  /**
+   * The heading body.
+   */
+  children: ReactNode;
   /**
    * Set additional classnames.
    */
diff --git a/src/components/atoms/icons/arrow.tsx b/src/components/atoms/icons/arrow.tsx
index 5f3c460..2962530 100644
--- a/src/components/atoms/icons/arrow.tsx
+++ b/src/components/atoms/icons/arrow.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './arrow.module.scss';
 
 export type ArrowDirection = 'top' | 'right' | 'bottom' | 'left';
@@ -19,7 +19,7 @@ export type ArrowProps = {
  *
  * Render a svg arrow icon.
  */
-const Arrow: VFC<ArrowProps> = ({ className = '', direction }) => {
+const Arrow: FC<ArrowProps> = ({ className = '', direction }) => {
   const directionClass = styles[`icon--${direction}`];
   const classes = `${styles.icon} ${directionClass} ${className}`;
 
diff --git a/src/components/atoms/icons/career.tsx b/src/components/atoms/icons/career.tsx
index 28edcc7..f28d399 100644
--- a/src/components/atoms/icons/career.tsx
+++ b/src/components/atoms/icons/career.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './career.module.scss';
 
 export type CareerProps = {
@@ -13,7 +13,7 @@ export type CareerProps = {
  *
  * Render a career svg icon.
  */
-const Career: VFC<CareerProps> = ({ className = '' }) => {
+const Career: FC<CareerProps> = ({ className = '' }) => {
   return (
     <svg
       viewBox="0 0 100 100"
diff --git a/src/components/atoms/icons/cc-by-sa.tsx b/src/components/atoms/icons/cc-by-sa.tsx
index 552504e..8239154 100644
--- a/src/components/atoms/icons/cc-by-sa.tsx
+++ b/src/components/atoms/icons/cc-by-sa.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './cc-by-sa.module.scss';
 
@@ -14,7 +14,7 @@ export type CCBySAProps = {
  *
  * Render a CC BY SA svg icon.
  */
-const CCBySA: VFC<CCBySAProps> = ({ className = '' }) => {
+const CCBySA: FC<CCBySAProps> = ({ className = '' }) => {
   const intl = useIntl();
 
   return (
diff --git a/src/components/atoms/icons/close.tsx b/src/components/atoms/icons/close.tsx
index eb9ce7c..3e0adb5 100644
--- a/src/components/atoms/icons/close.tsx
+++ b/src/components/atoms/icons/close.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './close.module.scss';
 
 export type CloseProps = {
@@ -13,7 +13,7 @@ export type CloseProps = {
  *
  * Render a close svg icon.
  */
-const Close: VFC<CloseProps> = ({ className = '' }) => {
+const Close: FC<CloseProps> = ({ className = '' }) => {
   return (
     <svg
       viewBox="0 0 100 100"
diff --git a/src/components/atoms/icons/cog.tsx b/src/components/atoms/icons/cog.tsx
index df6d54d..9e78a7b 100644
--- a/src/components/atoms/icons/cog.tsx
+++ b/src/components/atoms/icons/cog.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './cog.module.scss';
 
 export type CogProps = {
@@ -13,7 +13,7 @@ export type CogProps = {
  *
  * Render a cog svg icon.
  */
-const Cog: VFC<CogProps> = ({ className = '' }) => {
+const Cog: FC<CogProps> = ({ className = '' }) => {
   return (
     <svg
       viewBox="0 0 100 100"
diff --git a/src/components/atoms/icons/computer-screen.tsx b/src/components/atoms/icons/computer-screen.tsx
index 310836f..8786139 100644
--- a/src/components/atoms/icons/computer-screen.tsx
+++ b/src/components/atoms/icons/computer-screen.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './computer-screen.module.scss';
 
 export type ComputerScreenProps = {
@@ -13,7 +13,7 @@ export type ComputerScreenProps = {
  *
  * Render a computer screen svg icon.
  */
-const ComputerScreen: VFC<ComputerScreenProps> = ({ className = '' }) => {
+const ComputerScreen: FC<ComputerScreenProps> = ({ className = '' }) => {
   return (
     <svg
       viewBox="0 0 100 100"
diff --git a/src/components/atoms/icons/envelop.tsx b/src/components/atoms/icons/envelop.tsx
index 7b50d1d..84dca97 100644
--- a/src/components/atoms/icons/envelop.tsx
+++ b/src/components/atoms/icons/envelop.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './envelop.module.scss';
 
 export type EnvelopProps = {
@@ -13,7 +13,7 @@ export type EnvelopProps = {
  *
  * Render an envelop svg icon.
  */
-const Envelop: VFC<EnvelopProps> = ({ className = '' }) => {
+const Envelop: FC<EnvelopProps> = ({ className = '' }) => {
   return (
     <svg
       viewBox="0 0 100 100"
diff --git a/src/components/atoms/icons/hamburger.tsx b/src/components/atoms/icons/hamburger.tsx
index 7e7c2c9..93aed2a 100644
--- a/src/components/atoms/icons/hamburger.tsx
+++ b/src/components/atoms/icons/hamburger.tsx
@@ -1,7 +1,7 @@
 import { FC } from 'react';
 import styles from './hamburger.module.scss';
 
-type HamburgerProps = {
+export type HamburgerProps = {
   /**
    * Set additional classnames to the icon wrapper.
    */
diff --git a/src/components/atoms/icons/home.tsx b/src/components/atoms/icons/home.tsx
index 71bbc4a..3b6732d 100644
--- a/src/components/atoms/icons/home.tsx
+++ b/src/components/atoms/icons/home.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './home.module.scss';
 
 export type HomeProps = {
@@ -13,7 +13,7 @@ export type HomeProps = {
  *
  * Render a home svg icon.
  */
-const Home: VFC<HomeProps> = ({ className = '' }) => {
+const Home: FC<HomeProps> = ({ className = '' }) => {
   return (
     <svg
       viewBox="0 0 100 100"
diff --git a/src/components/atoms/icons/magnifying-glass.tsx b/src/components/atoms/icons/magnifying-glass.tsx
index 445ef10..1ca2a44 100644
--- a/src/components/atoms/icons/magnifying-glass.tsx
+++ b/src/components/atoms/icons/magnifying-glass.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './magnifying-glass.module.scss';
 
 export type MagnifyingGlassProps = {
@@ -13,7 +13,7 @@ export type MagnifyingGlassProps = {
  *
  * Render a magnifying glass svg icon.
  */
-const MagnifyingGlass: VFC<MagnifyingGlassProps> = ({ className = '' }) => {
+const MagnifyingGlass: FC<MagnifyingGlassProps> = ({ className = '' }) => {
   return (
     <svg
       viewBox="0 0 100 100"
diff --git a/src/components/atoms/icons/moon.tsx b/src/components/atoms/icons/moon.tsx
index 4f52319..ec4fa0c 100644
--- a/src/components/atoms/icons/moon.tsx
+++ b/src/components/atoms/icons/moon.tsx
@@ -1,7 +1,7 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './moon.module.scss';
 
-type MoonProps = {
+export type MoonProps = {
   /**
    * Set additional classnames to the icon.
    */
@@ -12,7 +12,7 @@ type MoonProps = {
   title?: string;
 };
 
-const Moon: VFC<MoonProps> = ({ className = '', title }) => {
+const Moon: FC<MoonProps> = ({ className = '', title }) => {
   return (
     <svg
       className={`${styles.icon} ${className}`}
diff --git a/src/components/atoms/icons/plus-minus.tsx b/src/components/atoms/icons/plus-minus.tsx
index 78aa14a..e8897b7 100644
--- a/src/components/atoms/icons/plus-minus.tsx
+++ b/src/components/atoms/icons/plus-minus.tsx
@@ -1,7 +1,7 @@
 import { FC } from 'react';
 import styles from './plus-minus.module.scss';
 
-type PlusMinusProps = {
+export type PlusMinusProps = {
   /**
    * Set additional classnames to the icon.
    */
diff --git a/src/components/atoms/icons/posts-stack.tsx b/src/components/atoms/icons/posts-stack.tsx
index 1998d25..ab21323 100644
--- a/src/components/atoms/icons/posts-stack.tsx
+++ b/src/components/atoms/icons/posts-stack.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './posts-stack.module.scss';
 
 export type PostsStackProps = {
@@ -13,7 +13,7 @@ export type PostsStackProps = {
  *
  * Render a posts stack svg icon.
  */
-const PostsStack: VFC<PostsStackProps> = ({ className = '' }) => {
+const PostsStack: FC<PostsStackProps> = ({ className = '' }) => {
   return (
     <svg
       viewBox="0 0 100 100"
diff --git a/src/components/atoms/icons/sun.tsx b/src/components/atoms/icons/sun.tsx
index fa9d922..ca31747 100644
--- a/src/components/atoms/icons/sun.tsx
+++ b/src/components/atoms/icons/sun.tsx
@@ -1,7 +1,7 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './sun.module.scss';
 
-type SunProps = {
+export type SunProps = {
   /**
    * Set additional classnames to the icon.
    */
@@ -17,7 +17,7 @@ type SunProps = {
  *
  * Render a svg sun icon.
  */
-const Sun: VFC<SunProps> = ({ className = '', title }) => {
+const Sun: FC<SunProps> = ({ className = '', title }) => {
   return (
     <svg
       className={`${styles.sun} ${className}`}
diff --git a/src/components/atoms/images/logo.tsx b/src/components/atoms/images/logo.tsx
index 2e52110..3978882 100644
--- a/src/components/atoms/images/logo.tsx
+++ b/src/components/atoms/images/logo.tsx
@@ -1,7 +1,7 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './logo.module.scss';
 
-type LogoProps = {
+export type LogoProps = {
   /**
    * SVG Image title.
    */
@@ -13,7 +13,7 @@ type LogoProps = {
  *
  * Render a SVG logo.
  */
-const Logo: VFC<LogoProps> = ({ title }) => {
+const Logo: FC<LogoProps> = ({ title }) => {
   return (
     <svg
       viewBox="0 0 512 512"
diff --git a/src/components/atoms/layout/copyright.tsx b/src/components/atoms/layout/copyright.tsx
index 76252ee..f70695d 100644
--- a/src/components/atoms/layout/copyright.tsx
+++ b/src/components/atoms/layout/copyright.tsx
@@ -1,4 +1,4 @@
-import { ReactNode, VFC } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './copyright.module.scss';
 
 export type CopyrightDates = {
@@ -32,7 +32,7 @@ export type CopyrightProps = {
  *
  * Renders a copyright information (owner, dates, license icon).
  */
-const Copyright: VFC<CopyrightProps> = ({ owner, dates, icon }) => {
+const Copyright: FC<CopyrightProps> = ({ owner, dates, icon }) => {
   const getFormattedDate = (date: string) => {
     const datetime = new Date(date).toISOString();
 
diff --git a/src/components/atoms/layout/main.tsx b/src/components/atoms/layout/main.tsx
index 4549328..d92a5c7 100644
--- a/src/components/atoms/layout/main.tsx
+++ b/src/components/atoms/layout/main.tsx
@@ -1,6 +1,10 @@
-import { FC } from 'react';
+import { FC, ReactNode } from 'react';
 
 export type MainProps = {
+  /**
+   * The main body.
+   */
+  children: ReactNode;
   /**
    * Set additional classnames to the main element.
    */
diff --git a/src/components/atoms/layout/no-script.tsx b/src/components/atoms/layout/no-script.tsx
index 6358cf8..a503e0c 100644
--- a/src/components/atoms/layout/no-script.tsx
+++ b/src/components/atoms/layout/no-script.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './no-script.module.scss';
 
 export type NoScriptProps = {
@@ -12,7 +12,7 @@ export type NoScriptProps = {
   position?: 'initial' | 'top';
 };
 
-const NoScript: VFC<NoScriptProps> = ({ message, position = 'initial' }) => {
+const NoScript: FC<NoScriptProps> = ({ message, position = 'initial' }) => {
   const positionClass = styles[`noscript--${position}`];
 
   return <div className={`${styles.noscript} ${positionClass}`}>{message}</div>;
diff --git a/src/components/atoms/layout/notice.tsx b/src/components/atoms/layout/notice.tsx
index b6e09c5..115bd9c 100644
--- a/src/components/atoms/layout/notice.tsx
+++ b/src/components/atoms/layout/notice.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './notice.module.scss';
 
 export type NoticeKind = 'error' | 'info' | 'success' | 'warning';
@@ -19,7 +19,7 @@ export type NoticeProps = {
  *
  * Render a colored message depending on notice kind.
  */
-const Notice: VFC<NoticeProps> = ({ kind, message }) => {
+const Notice: FC<NoticeProps> = ({ kind, message }) => {
   const kindClass = `wrapper--${kind}`;
 
   return (
diff --git a/src/components/atoms/layout/section.tsx b/src/components/atoms/layout/section.tsx
index f1bbb34..cb727ff 100644
--- a/src/components/atoms/layout/section.tsx
+++ b/src/components/atoms/layout/section.tsx
@@ -1,4 +1,4 @@
-import { ReactNode, VFC } from 'react';
+import { FC, ReactNode } from 'react';
 import Heading from '../headings/heading';
 import styles from './section.module.scss';
 
@@ -32,7 +32,7 @@ export type SectionProps = {
  *
  * Render a section element.
  */
-const Section: VFC<SectionProps> = ({
+const Section: FC<SectionProps> = ({
   className = '',
   content,
   title,
diff --git a/src/components/atoms/links/link.module.scss b/src/components/atoms/links/link.module.scss
index e7ead86..d23667a 100644
--- a/src/components/atoms/links/link.module.scss
+++ b/src/components/atoms/links/link.module.scss
@@ -5,7 +5,9 @@
   &[hreflang] {
     &::after {
       display: inline-block;
-      content: "\0000a0["attr(hreflang) "]";
+      /* Prettier is removing spacing between content parts. */
+      /* prettier-ignore */
+      content: "\0000a0[" attr(hreflang) "]";
       font-size: var(--font-size-sm);
     }
   }
@@ -13,22 +15,30 @@
   &--external {
     &::after {
       display: inline-block;
-      content: "\0000a0"url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
+      /* Prettier is removing spacing between content parts. */
+      /* prettier-ignore */
+      content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
     }
 
     &:focus:not(:active)::after {
-      content: "\0000a0"url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
+      /* Prettier is removing spacing between content parts. */
+      /* prettier-ignore */
+      content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
     }
 
     &[hreflang] {
       &::after {
-        content: "\0000a0["attr(hreflang) "]\0000a0"url(fun.encode-svg(
+        /* Prettier is removing spacing between content parts. */
+        /* prettier-ignore */
+        content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg(
             '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'
           ));
       }
 
       &:focus:not(:active)::after {
-        content: "\0000a0["attr(hreflang) "]\0000a0"url(fun.encode-svg(
+        /* Prettier is removing spacing between content parts. */
+        /* prettier-ignore */
+        content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg(
             '<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'
           ));
       }
diff --git a/src/components/atoms/links/link.tsx b/src/components/atoms/links/link.tsx
index 87f11fc..674c07b 100644
--- a/src/components/atoms/links/link.tsx
+++ b/src/components/atoms/links/link.tsx
@@ -1,8 +1,12 @@
 import NextLink from 'next/link';
-import { FC } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './link.module.scss';
 
 export type LinkProps = {
+  /**
+   * The link body.
+   */
+  children: ReactNode;
   /**
    * Set additional classnames to the link.
    */
diff --git a/src/components/atoms/links/nav-link.tsx b/src/components/atoms/links/nav-link.tsx
index 25c0e7d..7c6fede 100644
--- a/src/components/atoms/links/nav-link.tsx
+++ b/src/components/atoms/links/nav-link.tsx
@@ -1,5 +1,5 @@
 import Link from 'next/link';
-import { VFC, ReactNode } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './nav-link.module.scss';
 
 export type NavLinkProps = {
@@ -22,7 +22,7 @@ export type NavLinkProps = {
  *
  * Render a navigation link.
  */
-const NavLink: VFC<NavLinkProps> = ({ href, label, logo }) => {
+const NavLink: FC<NavLinkProps> = ({ href, label, logo }) => {
   return (
     <Link href={href}>
       <a className={styles.link}>
diff --git a/src/components/atoms/links/sharing-link.tsx b/src/components/atoms/links/sharing-link.tsx
index 3cd2dd1..ca53ef9 100644
--- a/src/components/atoms/links/sharing-link.tsx
+++ b/src/components/atoms/links/sharing-link.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './sharing-link.module.scss';
 
@@ -26,7 +26,7 @@ export type SharingLinkProps = {
  *
  * Render a sharing link.
  */
-const SharingLink: VFC<SharingLinkProps> = ({ medium, url }) => {
+const SharingLink: FC<SharingLinkProps> = ({ medium, url }) => {
   const intl = useIntl();
   const text = intl.formatMessage(
     {
diff --git a/src/components/atoms/links/social-link.tsx b/src/components/atoms/links/social-link.tsx
index 8c7c790..464bc60 100644
--- a/src/components/atoms/links/social-link.tsx
+++ b/src/components/atoms/links/social-link.tsx
@@ -2,7 +2,7 @@ import GithubIcon from '@assets/images/social-media/github.svg';
 import GitlabIcon from '@assets/images/social-media/gitlab.svg';
 import LinkedInIcon from '@assets/images/social-media/linkedin.svg';
 import TwitterIcon from '@assets/images/social-media/twitter.svg';
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './social-link.module.scss';
 
 export type SocialWebsite = 'Github' | 'Gitlab' | 'LinkedIn' | 'Twitter';
@@ -23,7 +23,7 @@ export type SocialLinkProps = {
  *
  * Render a social icon link.
  */
-const SocialLink: VFC<SocialLinkProps> = ({ name, url }) => {
+const SocialLink: FC<SocialLinkProps> = ({ name, url }) => {
   /**
    * Retrieve a social link icon by id.
    * @param {string} id - The social website id.
diff --git a/src/components/atoms/lists/description-list.tsx b/src/components/atoms/lists/description-list.tsx
index 0a92465..a60a6a1 100644
--- a/src/components/atoms/lists/description-list.tsx
+++ b/src/components/atoms/lists/description-list.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './description-list.module.scss';
 
 export type DescriptionListItem = {
@@ -52,7 +52,7 @@ export type DescriptionListProps = {
  *
  * Render a description list.
  */
-const DescriptionList: VFC<DescriptionListProps> = ({
+const DescriptionList: FC<DescriptionListProps> = ({
   className = '',
   descriptionClassName = '',
   groupClassName = '',
diff --git a/src/components/atoms/lists/list.tsx b/src/components/atoms/lists/list.tsx
index d100a31..6726802 100644
--- a/src/components/atoms/lists/list.tsx
+++ b/src/components/atoms/lists/list.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './list.module.scss';
 
 export type ListItem = {
@@ -44,7 +44,7 @@ export type ListProps = {
  *
  * Render either an ordered or an unordered list.
  */
-const List: VFC<ListProps> = ({
+const List: FC<ListProps> = ({
   className = '',
   items,
   itemsClassName = '',
diff --git a/src/components/atoms/loaders/progress-bar.tsx b/src/components/atoms/loaders/progress-bar.tsx
index 1b1ff06..9bac847 100644
--- a/src/components/atoms/loaders/progress-bar.tsx
+++ b/src/components/atoms/loaders/progress-bar.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './progress-bar.module.scss';
 
 export type ProgressBarProps = {
@@ -29,7 +29,7 @@ export type ProgressBarProps = {
  *
  * Render a progress bar.
  */
-const ProgressBar: VFC<ProgressBarProps> = ({
+const ProgressBar: FC<ProgressBarProps> = ({
   current,
   info,
   min,
diff --git a/src/components/atoms/loaders/spinner.tsx b/src/components/atoms/loaders/spinner.tsx
index bff0f25..6655141 100644
--- a/src/components/atoms/loaders/spinner.tsx
+++ b/src/components/atoms/loaders/spinner.tsx
@@ -1,4 +1,4 @@
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './spinner.module.scss';
 
@@ -14,7 +14,7 @@ export type SpinnerProps = {
  *
  * Render a loading message with animation.
  */
-const Spinner: VFC<SpinnerProps> = ({ message }) => {
+const Spinner: FC<SpinnerProps> = ({ message }) => {
   const intl = useIntl();
 
   return (
diff --git a/src/components/molecules/buttons/back-to-top.tsx b/src/components/molecules/buttons/back-to-top.tsx
index 8a52231..bd1925a 100644
--- a/src/components/molecules/buttons/back-to-top.tsx
+++ b/src/components/molecules/buttons/back-to-top.tsx
@@ -1,18 +1,16 @@
-import ButtonLink from '@components/atoms/buttons/button-link';
+import ButtonLink, {
+  type ButtonLinkProps,
+} from '@components/atoms/buttons/button-link';
 import Arrow from '@components/atoms/icons/arrow';
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './back-to-top.module.scss';
 
-export type BackToTopProps = {
+export type BackToTopProps = Pick<ButtonLinkProps, 'target'> & {
   /**
    * Set additional classnames to the button wrapper.
    */
   className?: string;
-  /**
-   * An element id (without hashtag) to use as anchor.
-   */
-  target: string;
 };
 
 /**
@@ -20,7 +18,7 @@ export type BackToTopProps = {
  *
  * Render a back to top link.
  */
-const BackToTop: VFC<BackToTopProps> = ({ className = '', target }) => {
+const BackToTop: FC<BackToTopProps> = ({ className = '', target }) => {
   const intl = useIntl();
   const linkName = intl.formatMessage({
     defaultMessage: 'Back to top',
diff --git a/src/components/molecules/buttons/heading-button.tsx b/src/components/molecules/buttons/heading-button.tsx
index fc79749..0ed9a76 100644
--- a/src/components/molecules/buttons/heading-button.tsx
+++ b/src/components/molecules/buttons/heading-button.tsx
@@ -1,6 +1,6 @@
 import Heading, { type HeadingProps } from '@components/atoms/headings/heading';
 import PlusMinus from '@components/atoms/icons/plus-minus';
-import { SetStateAction, VFC } from 'react';
+import { FC, SetStateAction } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './heading-button.module.scss';
 
@@ -28,7 +28,7 @@ export type HeadingButtonProps = Pick<HeadingProps, 'level'> & {
  *
  * Render a button as accordion title to toggle body.
  */
-const HeadingButton: VFC<HeadingButtonProps> = ({
+const HeadingButton: FC<HeadingButtonProps> = ({
   className = '',
   expanded,
   level,
diff --git a/src/components/molecules/buttons/help-button.tsx b/src/components/molecules/buttons/help-button.tsx
index aeb84ec..f19322f 100644
--- a/src/components/molecules/buttons/help-button.tsx
+++ b/src/components/molecules/buttons/help-button.tsx
@@ -1,5 +1,5 @@
-import Button, { ButtonProps } from '@components/atoms/buttons/button';
-import { VFC } from 'react';
+import Button, { type ButtonProps } from '@components/atoms/buttons/button';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './help-button.module.scss';
 
@@ -15,7 +15,7 @@ export type HelpButtonProps = Pick<ButtonProps, 'onClick'> & {
  *
  * Render a button with an interrogation mark icon.
  */
-const HelpButton: VFC<HelpButtonProps> = ({ className = '', onClick }) => {
+const HelpButton: FC<HelpButtonProps> = ({ className = '', onClick }) => {
   const intl = useIntl();
   const text = intl.formatMessage({
     defaultMessage: 'Help',
diff --git a/src/components/molecules/forms/ackee-select.test.tsx b/src/components/molecules/forms/ackee-select.test.tsx
index e1e6b2d..ec27922 100644
--- a/src/components/molecules/forms/ackee-select.test.tsx
+++ b/src/components/molecules/forms/ackee-select.test.tsx
@@ -1,5 +1,5 @@
-import userEvent from '@testing-library/user-event';
-import { render, screen } from '@test-utils';
+import user from '@testing-library/user-event';
+import { act, render, screen } from '@test-utils';
 import AckeeSelect from './ackee-select';
 
 describe('Select', () => {
@@ -9,13 +9,15 @@ describe('Select', () => {
     expect(screen.queryByRole('combobox')).not.toHaveValue('partial');
   });
 
-  it('should correctly change value when user choose another option', () => {
+  it('should correctly change value when user choose another option', async () => {
     render(<AckeeSelect initialValue="full" />);
 
-    userEvent.selectOptions(
-      screen.getByRole('combobox'),
-      screen.getByRole('option', { name: 'Partial' })
-    );
+    await act(async () => {
+      await user.selectOptions(
+        screen.getByRole('combobox'),
+        screen.getByRole('option', { name: 'Partial' })
+      );
+    });
 
     expect(screen.getByRole('combobox')).toHaveValue('partial');
     expect(screen.queryByRole('combobox')).not.toHaveValue('full');
diff --git a/src/components/molecules/forms/ackee-select.tsx b/src/components/molecules/forms/ackee-select.tsx
index 4a8410c..101e5b5 100644
--- a/src/components/molecules/forms/ackee-select.tsx
+++ b/src/components/molecules/forms/ackee-select.tsx
@@ -1,8 +1,8 @@
 import { SelectOptions } from '@components/atoms/forms/select';
-import { Dispatch, SetStateAction, useState, VFC } from 'react';
+import { Dispatch, FC, SetStateAction, useState } from 'react';
 import { useIntl } from 'react-intl';
 import SelectWithTooltip, {
-  SelectWithTooltipProps,
+  type SelectWithTooltipProps,
 } from './select-with-tooltip';
 
 export type AckeeOptions = 'full' | 'partial';
@@ -22,7 +22,7 @@ export type AckeeSelectProps = Pick<
  *
  * Render a select to set Ackee settings.
  */
-const AckeeSelect: VFC<AckeeSelectProps> = ({ initialValue, ...props }) => {
+const AckeeSelect: FC<AckeeSelectProps> = ({ initialValue, ...props }) => {
   const intl = useIntl();
   const [value, setValue] = useState<AckeeOptions>(initialValue);
 
diff --git a/src/components/molecules/forms/labelled-field.tsx b/src/components/molecules/forms/labelled-field.tsx
index 08d0126..ecc9255 100644
--- a/src/components/molecules/forms/labelled-field.tsx
+++ b/src/components/molecules/forms/labelled-field.tsx
@@ -1,6 +1,6 @@
 import Field, { type FieldProps } from '@components/atoms/forms/field';
 import Label from '@components/atoms/forms/label';
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './labelled-field.module.scss';
 
 export type LabelledFieldProps = FieldProps & {
@@ -23,7 +23,7 @@ export type LabelledFieldProps = FieldProps & {
  *
  * Render a field tied to a label.
  */
-const LabelledField: VFC<LabelledFieldProps> = ({
+const LabelledField: FC<LabelledFieldProps> = ({
   hideLabel = false,
   id,
   label,
diff --git a/src/components/molecules/forms/labelled-select.tsx b/src/components/molecules/forms/labelled-select.tsx
index 7d4237a..23057d0 100644
--- a/src/components/molecules/forms/labelled-select.tsx
+++ b/src/components/molecules/forms/labelled-select.tsx
@@ -1,6 +1,6 @@
-import Label, { LabelProps } from '@components/atoms/forms/label';
+import Label, { type LabelProps } from '@components/atoms/forms/label';
 import Select, { type SelectProps } from '@components/atoms/forms/select';
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './labelled-select.module.scss';
 
 export type LabelledSelectProps = Omit<
@@ -14,7 +14,7 @@ export type LabelledSelectProps = Omit<
   /**
    * Set additional classnames to the label.
    */
-  labelClassName?: string;
+  labelClassName?: LabelProps['className'];
   /**
    * The label position. Default: top.
    */
@@ -26,10 +26,15 @@ export type LabelledSelectProps = Omit<
   /**
    * Set additional classnames to the select field.
    */
-  selectClassName?: string;
+  selectClassName?: SelectProps['className'];
 };
 
-const LabelledSelect: VFC<LabelledSelectProps> = ({
+/**
+ * LabelledSelect component
+ *
+ * Render a select with a label.
+ */
+const LabelledSelect: FC<LabelledSelectProps> = ({
   id,
   label,
   labelClassName = '',
diff --git a/src/components/molecules/forms/motion-toggle.tsx b/src/components/molecules/forms/motion-toggle.tsx
index 9f30b42..24b54ae 100644
--- a/src/components/molecules/forms/motion-toggle.tsx
+++ b/src/components/molecules/forms/motion-toggle.tsx
@@ -1,8 +1,8 @@
 import Toggle, {
-  ToggleChoices,
-  ToggleProps,
+  type ToggleChoices,
+  type ToggleProps,
 } from '@components/molecules/forms/toggle';
-import { useState, VFC } from 'react';
+import { FC, useState } from 'react';
 import { useIntl } from 'react-intl';
 
 export type MotionToggleProps = Pick<ToggleProps, 'labelClassName' | 'value'>;
@@ -12,7 +12,7 @@ export type MotionToggleProps = Pick<ToggleProps, 'labelClassName' | 'value'>;
  *
  * Render a Toggle component to set reduce motion.
  */
-const MotionToggle: VFC<MotionToggleProps> = ({ value, ...props }) => {
+const MotionToggle: FC<MotionToggleProps> = ({ value, ...props }) => {
   const intl = useIntl();
   const [isDeactivated, setIsDeactivated] = useState<boolean>(value);
   const reduceMotionLabel = intl.formatMessage({
diff --git a/src/components/molecules/forms/prism-theme-toggle.tsx b/src/components/molecules/forms/prism-theme-toggle.tsx
index daee6bd..0b9c447 100644
--- a/src/components/molecules/forms/prism-theme-toggle.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.tsx
@@ -1,10 +1,10 @@
 import Moon from '@components/atoms/icons/moon';
 import Sun from '@components/atoms/icons/sun';
 import Toggle, {
-  ToggleChoices,
-  ToggleProps,
+  type ToggleChoices,
+  type ToggleProps,
 } from '@components/molecules/forms/toggle';
-import { useState, VFC } from 'react';
+import { FC, useState } from 'react';
 import { useIntl } from 'react-intl';
 
 export type PrismThemeToggleProps = Pick<
@@ -17,7 +17,7 @@ export type PrismThemeToggleProps = Pick<
  *
  * Render a Toggle component to set code blocks theme.
  */
-const PrismThemeToggle: VFC<PrismThemeToggleProps> = ({ value, ...props }) => {
+const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ value, ...props }) => {
   const intl = useIntl();
   const [isDarkTheme, setIsDarkTheme] = useState<boolean>(value);
   const themeLabel = intl.formatMessage({
diff --git a/src/components/molecules/forms/select-with-tooltip.tsx b/src/components/molecules/forms/select-with-tooltip.tsx
index f537e1e..cf7b041 100644
--- a/src/components/molecules/forms/select-with-tooltip.tsx
+++ b/src/components/molecules/forms/select-with-tooltip.tsx
@@ -1,4 +1,4 @@
-import { useState, VFC } from 'react';
+import { FC, useState } from 'react';
 import HelpButton from '../buttons/help-button';
 import Tooltip, { type TooltipProps } from '../modals/tooltip';
 import LabelledSelect, { type LabelledSelectProps } from './labelled-select';
@@ -9,14 +9,10 @@ export type SelectWithTooltipProps = Omit<
   'labelPosition'
 > &
   Pick<TooltipProps, 'title' | 'content'> & {
-    /**
-     * The select label.
-     */
-    label: string;
     /**
      * Set additional classnames to the tooltip wrapper.
      */
-    tooltipClassName?: string;
+    tooltipClassName?: TooltipProps['className'];
   };
 
 /**
@@ -24,7 +20,7 @@ export type SelectWithTooltipProps = Omit<
  *
  * Render a select with a button to display a tooltip about options.
  */
-const SelectWithTooltip: VFC<SelectWithTooltipProps> = ({
+const SelectWithTooltip: FC<SelectWithTooltipProps> = ({
   title,
   content,
   id,
diff --git a/src/components/molecules/forms/theme-toggle.tsx b/src/components/molecules/forms/theme-toggle.tsx
index eb56ce9..10c6c47 100644
--- a/src/components/molecules/forms/theme-toggle.tsx
+++ b/src/components/molecules/forms/theme-toggle.tsx
@@ -1,10 +1,10 @@
 import Moon from '@components/atoms/icons/moon';
 import Sun from '@components/atoms/icons/sun';
 import Toggle, {
-  ToggleChoices,
-  ToggleProps,
+  type ToggleChoices,
+  type ToggleProps,
 } from '@components/molecules/forms/toggle';
-import { useState, VFC } from 'react';
+import { FC, useState } from 'react';
 import { useIntl } from 'react-intl';
 
 export type ThemeToggleProps = Pick<ToggleProps, 'labelClassName' | 'value'>;
@@ -14,7 +14,7 @@ export type ThemeToggleProps = Pick<ToggleProps, 'labelClassName' | 'value'>;
  *
  * Render a Toggle component to set theme.
  */
-const ThemeToggle: VFC<ThemeToggleProps> = ({ value, ...props }) => {
+const ThemeToggle: FC<ThemeToggleProps> = ({ value, ...props }) => {
   const intl = useIntl();
   const [isDarkTheme, setIsDarkTheme] = useState<boolean>(value);
   const themeLabel = intl.formatMessage({
diff --git a/src/components/molecules/forms/toggle.tsx b/src/components/molecules/forms/toggle.tsx
index dff2d2d..288062d 100644
--- a/src/components/molecules/forms/toggle.tsx
+++ b/src/components/molecules/forms/toggle.tsx
@@ -1,6 +1,6 @@
-import Checkbox from '@components/atoms/forms/checkbox';
+import Checkbox, { type CheckboxProps } from '@components/atoms/forms/checkbox';
 import Label, { type LabelProps } from '@components/atoms/forms/label';
-import { ReactNode, VFC } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './toggle.module.scss';
 
 export type ToggleChoices = {
@@ -14,15 +14,11 @@ export type ToggleChoices = {
   right: ReactNode;
 };
 
-export type ToggleProps = {
+export type ToggleProps = Pick<CheckboxProps, 'id' | 'name'> & {
   /**
    * The toggle choices.
    */
   choices: ToggleChoices;
-  /**
-   * The input id.
-   */
-  id: string;
   /**
    * The toggle label.
    */
@@ -30,15 +26,11 @@ export type ToggleProps = {
   /**
    * Set additional classnames to the label.
    */
-  labelClassName?: string;
+  labelClassName?: LabelProps['className'];
   /**
    * The label size.
    */
   labelSize?: LabelProps['size'];
-  /**
-   * The input name.
-   */
-  name: string;
   /**
    * The toggle value. True if checked.
    */
@@ -54,7 +46,7 @@ export type ToggleProps = {
  *
  * Render a toggle with a label and two choices.
  */
-const Toggle: VFC<ToggleProps> = ({
+const Toggle: FC<ToggleProps> = ({
   choices,
   id,
   label,
diff --git a/src/components/molecules/images/responsive-image.tsx b/src/components/molecules/images/responsive-image.tsx
index 1d8787e..31cbcd1 100644
--- a/src/components/molecules/images/responsive-image.tsx
+++ b/src/components/molecules/images/responsive-image.tsx
@@ -1,6 +1,6 @@
-import Link from '@components/atoms/links/link';
-import Image, { ImageProps } from 'next/image';
-import { VFC } from 'react';
+import Link, { type LinkProps } from '@components/atoms/links/link';
+import Image, { type ImageProps } from 'next/image';
+import { FC } from 'react';
 import styles from './responsive-image.module.scss';
 
 export type ResponsiveImageProps = Omit<
@@ -26,7 +26,7 @@ export type ResponsiveImageProps = Omit<
   /**
    * A link target.
    */
-  target?: string;
+  target?: LinkProps['href'];
   /**
    * The image width.
    */
@@ -38,7 +38,7 @@ export type ResponsiveImageProps = Omit<
  *
  * Render a responsive image wrapped in a figure element.
  */
-const ResponsiveImage: VFC<ResponsiveImageProps> = ({
+const ResponsiveImage: FC<ResponsiveImageProps> = ({
   alt,
   caption,
   className = '',
diff --git a/src/components/molecules/layout/branding.tsx b/src/components/molecules/layout/branding.tsx
index 9f564bf..9fe89e7 100644
--- a/src/components/molecules/layout/branding.tsx
+++ b/src/components/molecules/layout/branding.tsx
@@ -1,11 +1,11 @@
 import Heading from '@components/atoms/headings/heading';
 import Link from 'next/link';
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './branding.module.scss';
-import FlippingLogo from './flipping-logo';
+import FlippingLogo, { type FlippingLogoProps } from './flipping-logo';
 
-type BrandingProps = {
+export type BrandingProps = Pick<FlippingLogoProps, 'photo'> & {
   /**
    * The Branding baseline.
    */
@@ -14,10 +14,6 @@ type BrandingProps = {
    * Use H1 if the current page is homepage. Default: false.
    */
   isHome?: boolean;
-  /**
-   * A photography URL.
-   */
-  photo: string;
   /**
    * The Branding title;
    */
@@ -33,7 +29,7 @@ type BrandingProps = {
  *
  * Render the branding logo, title and optional baseline.
  */
-const Branding: VFC<BrandingProps> = ({
+const Branding: FC<BrandingProps> = ({
   baseline,
   isHome = false,
   photo,
diff --git a/src/components/molecules/layout/card.tsx b/src/components/molecules/layout/card.tsx
index 23a0e54..89f100e 100644
--- a/src/components/molecules/layout/card.tsx
+++ b/src/components/molecules/layout/card.tsx
@@ -1,11 +1,11 @@
 import ButtonLink from '@components/atoms/buttons/button-link';
 import Heading, { type HeadingLevel } from '@components/atoms/headings/heading';
 import DescriptionList, {
-  DescriptionListItem,
+  type DescriptionListItem,
 } from '@components/atoms/lists/description-list';
-import { VFC } from 'react';
+import { FC } from 'react';
 import ResponsiveImage, {
-  ResponsiveImageProps,
+  type ResponsiveImageProps,
 } from '../images/responsive-image';
 import styles from './card.module.scss';
 
@@ -68,7 +68,7 @@ export type CardProps = {
  *
  * Render a link with minimal information about its content.
  */
-const Card: VFC<CardProps> = ({
+const Card: FC<CardProps> = ({
   className = '',
   cover,
   coverFit = 'cover',
diff --git a/src/components/molecules/layout/flipping-logo.tsx b/src/components/molecules/layout/flipping-logo.tsx
index 6f7645f..4a216ef 100644
--- a/src/components/molecules/layout/flipping-logo.tsx
+++ b/src/components/molecules/layout/flipping-logo.tsx
@@ -1,9 +1,9 @@
-import Logo from '@components/atoms/images/logo';
+import Logo, { type LogoProps } from '@components/atoms/images/logo';
 import Image from 'next/image';
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './flipping-logo.module.scss';
 
-type FlippingLogoProps = {
+export type FlippingLogoProps = {
   /**
    * Set additional classnames to the logo wrapper.
    */
@@ -15,7 +15,7 @@ type FlippingLogoProps = {
   /**
    * Logo image title.
    */
-  logoTitle?: string;
+  logoTitle?: LogoProps['title'];
   /**
    * Photo url.
    */
@@ -27,7 +27,7 @@ type FlippingLogoProps = {
  *
  * Render a logo and a photo with a flipping effect.
  */
-const FlippingLogo: VFC<FlippingLogoProps> = ({
+const FlippingLogo: FC<FlippingLogoProps> = ({
   className = '',
   altText,
   logoTitle,
diff --git a/src/components/molecules/layout/meta.tsx b/src/components/molecules/layout/meta.tsx
index 218ebd9..fcce473 100644
--- a/src/components/molecules/layout/meta.tsx
+++ b/src/components/molecules/layout/meta.tsx
@@ -2,7 +2,7 @@ import DescriptionList, {
   type DescriptionListProps,
   type DescriptionListItem,
 } from '@components/atoms/lists/description-list';
-import { ReactNode, VFC } from 'react';
+import { FC, ReactNode } from 'react';
 
 export type MetaItem = {
   /**
@@ -43,7 +43,7 @@ export type MetaProps = {
  *
  * Renders the page metadata.
  */
-const Meta: VFC<MetaProps> = ({ data, ...props }) => {
+const Meta: FC<MetaProps> = ({ data, ...props }) => {
   /**
    * Transform the metadata to description list item format.
    *
diff --git a/src/components/molecules/layout/widget.tsx b/src/components/molecules/layout/widget.tsx
index c04362a..feb2add 100644
--- a/src/components/molecules/layout/widget.tsx
+++ b/src/components/molecules/layout/widget.tsx
@@ -1,11 +1,17 @@
-import { FC, useState } from 'react';
-import HeadingButton, { HeadingButtonProps } from '../buttons/heading-button';
+import { FC, ReactNode, useState } from 'react';
+import HeadingButton, {
+  type HeadingButtonProps,
+} from '../buttons/heading-button';
 import styles from './widget.module.scss';
 
 export type WidgetProps = Pick<
   HeadingButtonProps,
   'expanded' | 'level' | 'title'
 > & {
+  /**
+   * The widget body.
+   */
+  children: ReactNode;
   /**
    * Set additional classnames to the widget wrapper.
    */
diff --git a/src/components/molecules/modals/modal.test.tsx b/src/components/molecules/modals/modal.test.tsx
index 14fb224..9a0e237 100644
--- a/src/components/molecules/modals/modal.test.tsx
+++ b/src/components/molecules/modals/modal.test.tsx
@@ -1,9 +1,18 @@
 import { render, screen } from '@test-utils';
 import Modal from './modal';
 
+const title = 'A custom title';
+const children =
+  'Labore ullam delectus sit modi quam dolores. Ratione id sint aliquid facilis ipsum. Unde necessitatibus provident minus.';
+
 describe('Modal', () => {
   it('renders a title', () => {
-    render(<Modal title="A custom title" />);
-    expect(screen.getByText('A custom title')).toBeInTheDocument();
+    render(<Modal title={title}>{children}</Modal>);
+    expect(screen.getByText(title)).toBeInTheDocument();
+  });
+
+  it('renders the modal body', () => {
+    render(<Modal title={title}>{children}</Modal>);
+    expect(screen.getByText(children)).toBeInTheDocument();
   });
 });
diff --git a/src/components/molecules/modals/modal.tsx b/src/components/molecules/modals/modal.tsx
index 52ada57..58f5fa0 100644
--- a/src/components/molecules/modals/modal.tsx
+++ b/src/components/molecules/modals/modal.tsx
@@ -1,13 +1,17 @@
-import Heading from '@components/atoms/headings/heading';
-import { CogProps } from '@components/atoms/icons/cog';
-import { MagnifyingGlassProps } from '@components/atoms/icons/magnifying-glass';
+import Heading, { type HeadingProps } from '@components/atoms/headings/heading';
+import { type CogProps } from '@components/atoms/icons/cog';
+import { type MagnifyingGlassProps } from '@components/atoms/icons/magnifying-glass';
 import dynamic from 'next/dynamic';
-import { FC } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './modal.module.scss';
 
 export type Icons = 'cogs' | 'search';
 
 export type ModalProps = {
+  /**
+   * The modal body.
+   */
+  children: ReactNode;
   /**
    * Set additional classnames.
    */
@@ -15,7 +19,7 @@ export type ModalProps = {
   /**
    * Set additional classnames to the heading.
    */
-  headingClassName?: string;
+  headingClassName?: HeadingProps['className'];
   /**
    * A icon to illustrate the modal.
    */
diff --git a/src/components/molecules/modals/tooltip.tsx b/src/components/molecules/modals/tooltip.tsx
index 73f36e7..80721f3 100644
--- a/src/components/molecules/modals/tooltip.tsx
+++ b/src/components/molecules/modals/tooltip.tsx
@@ -1,5 +1,5 @@
 import List, { type ListItem } from '@components/atoms/lists/list';
-import { ReactNode, VFC } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './tooltip.module.scss';
 
 export type TooltipProps = {
@@ -26,7 +26,7 @@ export type TooltipProps = {
  *
  * Render a tooltip modal.
  */
-const Tooltip: VFC<TooltipProps> = ({
+const Tooltip: FC<TooltipProps> = ({
   className = '',
   content,
   icon,
diff --git a/src/components/molecules/nav/breadcrumb.tsx b/src/components/molecules/nav/breadcrumb.tsx
index 33af735..6dc86a0 100644
--- a/src/components/molecules/nav/breadcrumb.tsx
+++ b/src/components/molecules/nav/breadcrumb.tsx
@@ -1,7 +1,7 @@
 import Link from '@components/atoms/links/link';
 import { settings } from '@utils/config';
 import Script from 'next/script';
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import { BreadcrumbList, ListItem, WithContext } from 'schema-dts';
 import styles from './breadcrumb.module.scss';
@@ -37,7 +37,7 @@ export type BreadcrumbProps = {
  *
  * Render a breadcrumb navigation.
  */
-const Breadcrumb: VFC<BreadcrumbProps> = ({ items, ...props }) => {
+const Breadcrumb: FC<BreadcrumbProps> = ({ items, ...props }) => {
   const intl = useIntl();
 
   /**
diff --git a/src/components/molecules/nav/nav.tsx b/src/components/molecules/nav/nav.tsx
index 6ef9158..2666ea2 100644
--- a/src/components/molecules/nav/nav.tsx
+++ b/src/components/molecules/nav/nav.tsx
@@ -1,6 +1,6 @@
 import Link from '@components/atoms/links/link';
 import NavLink from '@components/atoms/links/nav-link';
-import { ReactNode, VFC } from 'react';
+import { FC, ReactNode } from 'react';
 import styles from './nav.module.scss';
 
 export type NavItem = {
@@ -46,7 +46,7 @@ export type NavProps = {
  *
  * Render the nav links.
  */
-const Nav: VFC<NavProps> = ({
+const Nav: FC<NavProps> = ({
   className = '',
   items,
   kind,
diff --git a/src/components/organisms/forms/comment-form.stories.tsx b/src/components/organisms/forms/comment-form.stories.tsx
index 1ab7cf2..670176c 100644
--- a/src/components/organisms/forms/comment-form.stories.tsx
+++ b/src/components/organisms/forms/comment-form.stories.tsx
@@ -1,4 +1,3 @@
-import Notice from '@components/atoms/layout/notice';
 import { ComponentMeta, ComponentStory } from '@storybook/react';
 import { IntlProvider } from 'react-intl';
 import CommentFormComponent from './comment-form';
diff --git a/src/components/organisms/forms/comment-form.tsx b/src/components/organisms/forms/comment-form.tsx
index 6acbf94..d7cb0f5 100644
--- a/src/components/organisms/forms/comment-form.tsx
+++ b/src/components/organisms/forms/comment-form.tsx
@@ -3,7 +3,7 @@ import Form from '@components/atoms/forms/form';
 import Heading, { type HeadingLevel } from '@components/atoms/headings/heading';
 import Spinner from '@components/atoms/loaders/spinner';
 import LabelledField from '@components/molecules/forms/labelled-field';
-import { ReactNode, useState, VFC } from 'react';
+import { FC, ReactNode, useState } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './comment-form.module.scss';
 
@@ -31,7 +31,7 @@ export type CommentFormProps = {
   titleLevel?: HeadingLevel;
 };
 
-const CommentForm: VFC<CommentFormProps> = ({
+const CommentForm: FC<CommentFormProps> = ({
   className = '',
   Notice,
   saveComment,
diff --git a/src/components/organisms/forms/contact-form.tsx b/src/components/organisms/forms/contact-form.tsx
index 994244a..4a6902b 100644
--- a/src/components/organisms/forms/contact-form.tsx
+++ b/src/components/organisms/forms/contact-form.tsx
@@ -2,7 +2,7 @@ import Button from '@components/atoms/buttons/button';
 import Form from '@components/atoms/forms/form';
 import Spinner from '@components/atoms/loaders/spinner';
 import LabelledField from '@components/molecules/forms/labelled-field';
-import { ReactNode, useState, VFC } from 'react';
+import { FC, ReactNode, useState } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './contact-form.module.scss';
 
@@ -27,7 +27,7 @@ export type ContactFormProps = {
  *
  * Render a contact form.
  */
-const ContactForm: VFC<ContactFormProps> = ({
+const ContactForm: FC<ContactFormProps> = ({
   className = '',
   Notice,
   sendMail,
diff --git a/src/components/organisms/forms/search-form.tsx b/src/components/organisms/forms/search-form.tsx
index 351d93c..18b7c08 100644
--- a/src/components/organisms/forms/search-form.tsx
+++ b/src/components/organisms/forms/search-form.tsx
@@ -2,15 +2,20 @@ import Button from '@components/atoms/buttons/button';
 import Form from '@components/atoms/forms/form';
 import MagnifyingGlass from '@components/atoms/icons/magnifying-glass';
 import LabelledField, {
-  LabelledFieldProps,
+  type LabelledFieldProps,
 } from '@components/molecules/forms/labelled-field';
-import { useState, VFC } from 'react';
+import { FC, useState } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './search-form.module.scss';
 
 export type SearchFormProps = Pick<LabelledFieldProps, 'hideLabel'>;
 
-const SearchForm: VFC<SearchFormProps> = ({ hideLabel }) => {
+/**
+ * SearchForm component
+ *
+ * Render a search form.
+ */
+const SearchForm: FC<SearchFormProps> = ({ hideLabel }) => {
   const intl = useIntl();
   const fieldLabel = intl.formatMessage({
     defaultMessage: 'Search for:',
diff --git a/src/components/organisms/layout/cards-list.tsx b/src/components/organisms/layout/cards-list.tsx
index a53df0d..33ffe23 100644
--- a/src/components/organisms/layout/cards-list.tsx
+++ b/src/components/organisms/layout/cards-list.tsx
@@ -3,7 +3,7 @@ import List, {
   type ListProps,
 } from '@components/atoms/lists/list';
 import Card, { type CardProps } from '@components/molecules/layout/card';
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './cards-list.module.scss';
 
 export type CardsListItem = Omit<
@@ -37,7 +37,7 @@ export type CardsListProps = {
  *
  * Return a list of Card components.
  */
-const CardsList: VFC<CardsListProps> = ({
+const CardsList: FC<CardsListProps> = ({
   coverFit,
   items,
   kind = 'unordered',
diff --git a/src/components/organisms/layout/footer.tsx b/src/components/organisms/layout/footer.tsx
index c9cb067..15bfa24 100644
--- a/src/components/organisms/layout/footer.tsx
+++ b/src/components/organisms/layout/footer.tsx
@@ -1,7 +1,9 @@
-import Copyright, { CopyrightProps } from '@components/atoms/layout/copyright';
+import Copyright, {
+  type 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 { FC } from 'react';
 import styles from './footer.module.scss';
 
 export type FooterProps = {
@@ -28,12 +30,7 @@ export type FooterProps = {
  *
  * Renders a footer with copyright and nav;
  */
-const Footer: VFC<FooterProps> = ({
-  className,
-  copyright,
-  navItems,
-  topId,
-}) => {
+const Footer: FC<FooterProps> = ({ className, copyright, navItems, topId }) => {
   return (
     <footer className={`${styles.wrapper} ${className}`}>
       <Copyright
diff --git a/src/components/organisms/layout/overview.tsx b/src/components/organisms/layout/overview.tsx
index 3f83342..42d0431 100644
--- a/src/components/organisms/layout/overview.tsx
+++ b/src/components/organisms/layout/overview.tsx
@@ -2,7 +2,7 @@ import ResponsiveImage, {
   type ResponsiveImageProps,
 } from '@components/molecules/images/responsive-image';
 import Meta, { type MetaMap } from '@components/molecules/layout/meta';
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './overview.module.scss';
 
 export type OverviewProps = {
@@ -15,7 +15,7 @@ export type OverviewProps = {
  *
  * Render an overview.
  */
-const Overview: VFC<OverviewProps> = ({ cover, meta }) => {
+const Overview: FC<OverviewProps> = ({ cover, meta }) => {
   return (
     <div className={styles.wrapper}>
       {cover && (
diff --git a/src/components/organisms/layout/summary.stories.tsx b/src/components/organisms/layout/summary.stories.tsx
index 5214d70..b33acde 100644
--- a/src/components/organisms/layout/summary.stories.tsx
+++ b/src/components/organisms/layout/summary.stories.tsx
@@ -103,7 +103,7 @@ Summary.args = {
   cover: {
     alt: 'A cover',
     height: 480,
-    url: 'http://placeimg.com/640/480',
+    src: 'http://placeimg.com/640/480',
     width: 640,
   },
   excerpt:
diff --git a/src/components/organisms/layout/summary.test.tsx b/src/components/organisms/layout/summary.test.tsx
index ce87c0c..4944805 100644
--- a/src/components/organisms/layout/summary.test.tsx
+++ b/src/components/organisms/layout/summary.test.tsx
@@ -4,7 +4,7 @@ import Summary from './summary';
 const cover = {
   alt: 'A cover',
   height: 480,
-  url: 'http://placeimg.com/640/480',
+  src: 'http://placeimg.com/640/480',
   width: 640,
 };
 
diff --git a/src/components/organisms/layout/summary.tsx b/src/components/organisms/layout/summary.tsx
index 3624e5d..733a660 100644
--- a/src/components/organisms/layout/summary.tsx
+++ b/src/components/organisms/layout/summary.tsx
@@ -2,18 +2,18 @@ import ButtonLink from '@components/atoms/buttons/button-link';
 import Heading, { type HeadingLevel } from '@components/atoms/headings/heading';
 import Arrow from '@components/atoms/icons/arrow';
 import Link from '@components/atoms/links/link';
-import ResponsiveImage from '@components/molecules/images/responsive-image';
+import ResponsiveImage, {
+  type ResponsiveImageProps,
+} from '@components/molecules/images/responsive-image';
 import Meta, { type MetaItem } from '@components/molecules/layout/meta';
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './summary.module.scss';
 
-export type Cover = {
-  alt: string;
-  height: number;
-  url: string;
-  width: number;
-};
+export type Cover = Pick<
+  ResponsiveImageProps,
+  'alt' | 'src' | 'width' | 'height'
+>;
 
 export type RequiredMetaKey = 'publication';
 
@@ -35,11 +35,29 @@ export type OptionalMeta = {
 export type Meta = RequiredMeta & OptionalMeta;
 
 export type SummaryProps = {
+  /**
+   * The post cover.
+   */
   cover?: Cover;
+  /**
+   * The post excerpt.
+   */
   excerpt: string;
+  /**
+   * The post meta.
+   */
   meta: Meta;
+  /**
+   * The post title.
+   */
   title: string;
+  /**
+   * The heading level (hn).
+   */
   titleLevel?: HeadingLevel;
+  /**
+   * The post url.
+   */
   url: string;
 };
 
@@ -48,7 +66,7 @@ export type SummaryProps = {
  *
  * Render a page summary.
  */
-const Summary: VFC<SummaryProps> = ({
+const Summary: FC<SummaryProps> = ({
   cover,
   excerpt,
   meta,
@@ -57,18 +75,23 @@ const Summary: VFC<SummaryProps> = ({
   url,
 }) => {
   const intl = useIntl();
+  const readMore = intl.formatMessage(
+    {
+      defaultMessage: 'Read more<a11y> about {title}</a11y>',
+      description: 'Summary: read more link',
+      id: 'Zpgv+f',
+    },
+    {
+      title,
+      a11y: (chunks: string) => (
+        <span className="screen-reader-text">{chunks}</span>
+      ),
+    }
+  );
 
   return (
     <article className={styles.wrapper}>
-      {cover && (
-        <ResponsiveImage
-          alt={cover.alt}
-          src={cover.url}
-          width={cover.width}
-          height={cover.height}
-          className={styles.cover}
-        />
-      )}
+      {cover && <ResponsiveImage className={styles.cover} {...cover} />}
       <header className={styles.header}>
         <Link href={url}>
           <Heading level={titleLevel} className={styles.title}>
@@ -79,20 +102,10 @@ const Summary: VFC<SummaryProps> = ({
       <div className={styles.body}>
         {excerpt}
         <ButtonLink target={url} className={styles['read-more']}>
-          {intl.formatMessage(
-            {
-              defaultMessage: 'Read more<a11y> about {title}</a11y>',
-              description: 'Summary: read more link',
-              id: 'Zpgv+f',
-            },
-            {
-              title,
-              a11y: (chunks: string) => (
-                <span className="screen-reader-text">{chunks}</span>
-              ),
-            }
-          )}
-          <Arrow direction="right" />
+          <>
+            {readMore}
+            <Arrow direction="right" />
+          </>
         </ButtonLink>
       </div>
       <footer className={styles.footer}>
diff --git a/src/components/organisms/modals/search-modal.tsx b/src/components/organisms/modals/search-modal.tsx
index 883b783..0e0ceed 100644
--- a/src/components/organisms/modals/search-modal.tsx
+++ b/src/components/organisms/modals/search-modal.tsx
@@ -1,5 +1,5 @@
-import Modal from '@components/molecules/modals/modal';
-import { VFC } from 'react';
+import Modal, { type ModalProps } from '@components/molecules/modals/modal';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import SearchForm from '../forms/search-form';
 import styles from './search-modal.module.scss';
@@ -8,7 +8,7 @@ export type SearchModalProps = {
   /**
    * Set additional classnames to modal wrapper.
    */
-  className?: string;
+  className?: ModalProps['className'];
 };
 
 /**
@@ -16,7 +16,7 @@ export type SearchModalProps = {
  *
  * Render a search form modal.
  */
-const SearchModal: VFC<SearchModalProps> = ({ className }) => {
+const SearchModal: FC<SearchModalProps> = ({ className }) => {
   const intl = useIntl();
   const modalTitle = intl.formatMessage({
     defaultMessage: 'Search',
diff --git a/src/components/organisms/modals/settings-modal.tsx b/src/components/organisms/modals/settings-modal.tsx
index 25d6f6f..20d2605 100644
--- a/src/components/organisms/modals/settings-modal.tsx
+++ b/src/components/organisms/modals/settings-modal.tsx
@@ -1,10 +1,12 @@
 import Form from '@components/atoms/forms/form';
-import AckeeSelect from '@components/molecules/forms/ackee-select';
+import AckeeSelect, {
+  type AckeeSelectProps,
+} from '@components/molecules/forms/ackee-select';
 import MotionToggle from '@components/molecules/forms/motion-toggle';
 import PrismThemeToggle from '@components/molecules/forms/prism-theme-toggle';
 import ThemeToggle from '@components/molecules/forms/theme-toggle';
-import Modal from '@components/molecules/modals/modal';
-import { VFC } from 'react';
+import Modal, { type ModalProps } from '@components/molecules/modals/modal';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './settings-modal.module.scss';
 
@@ -12,11 +14,11 @@ export type SettingsModalProps = {
   /**
    * Set additional classnames to the modal wrapper.
    */
-  className?: string;
+  className?: ModalProps['className'];
   /**
    * Set additional classnames to the tooltip wrapper.
    */
-  tooltipClassName?: string;
+  tooltipClassName?: AckeeSelectProps['tooltipClassName'];
 };
 
 /**
@@ -24,7 +26,7 @@ export type SettingsModalProps = {
  *
  * Render a modal with settings options.
  */
-const SettingsModal: VFC<SettingsModalProps> = ({
+const SettingsModal: FC<SettingsModalProps> = ({
   className = '',
   tooltipClassName = '',
 }) => {
diff --git a/src/components/organisms/toolbar/main-nav.tsx b/src/components/organisms/toolbar/main-nav.tsx
index 50bbe8b..35e3fd6 100644
--- a/src/components/organisms/toolbar/main-nav.tsx
+++ b/src/components/organisms/toolbar/main-nav.tsx
@@ -1,17 +1,20 @@
 import Checkbox, { type CheckboxProps } from '@components/atoms/forms/checkbox';
 import Label from '@components/atoms/forms/label';
 import Hamburger from '@components/atoms/icons/hamburger';
-import Nav, { type NavItem } from '@components/molecules/nav/nav';
-import { VFC } from 'react';
+import Nav, {
+  type NavProps,
+  type NavItem,
+} from '@components/molecules/nav/nav';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
-import sharedStyles from './toolbar-items.module.scss';
 import mainNavStyles from './main-nav.module.scss';
+import sharedStyles from './toolbar-items.module.scss';
 
 export type MainNavProps = {
   /**
    * Set additional classnames to the nav element.
    */
-  className?: string;
+  className?: NavProps['className'];
   /**
    * The button state.
    */
@@ -26,7 +29,12 @@ export type MainNavProps = {
   setIsActive: CheckboxProps['setValue'];
 };
 
-const MainNav: VFC<MainNavProps> = ({
+/**
+ * MainNav component
+ *
+ * Render the main navigation.
+ */
+const MainNav: FC<MainNavProps> = ({
   className = '',
   isActive,
   items,
diff --git a/src/components/organisms/toolbar/search.tsx b/src/components/organisms/toolbar/search.tsx
index 070bce0..72cd576 100644
--- a/src/components/organisms/toolbar/search.tsx
+++ b/src/components/organisms/toolbar/search.tsx
@@ -1,17 +1,17 @@
-import Checkbox, { CheckboxProps } from '@components/atoms/forms/checkbox';
+import Checkbox, { type CheckboxProps } from '@components/atoms/forms/checkbox';
 import Label from '@components/atoms/forms/label';
 import MagnifyingGlass from '@components/atoms/icons/magnifying-glass';
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
-import SearchModal from '../modals/search-modal';
-import sharedStyles from './toolbar-items.module.scss';
+import SearchModal, { type SearchModalProps } from '../modals/search-modal';
 import searchStyles from './search.module.scss';
+import sharedStyles from './toolbar-items.module.scss';
 
 export type SearchProps = {
   /**
    * Set additional classnames to the modal wrapper.
    */
-  className?: string;
+  className?: SearchModalProps['className'];
   /**
    * The button state.
    */
@@ -22,11 +22,7 @@ export type SearchProps = {
   setIsActive: CheckboxProps['setValue'];
 };
 
-const Search: VFC<SearchProps> = ({
-  className = '',
-  isActive,
-  setIsActive,
-}) => {
+const Search: FC<SearchProps> = ({ className = '', isActive, setIsActive }) => {
   const intl = useIntl();
   const label = isActive
     ? intl.formatMessage({
diff --git a/src/components/organisms/toolbar/settings.tsx b/src/components/organisms/toolbar/settings.tsx
index 88539fb..3b10226 100644
--- a/src/components/organisms/toolbar/settings.tsx
+++ b/src/components/organisms/toolbar/settings.tsx
@@ -1,11 +1,13 @@
-import Checkbox, { CheckboxProps } from '@components/atoms/forms/checkbox';
+import Checkbox, { type CheckboxProps } from '@components/atoms/forms/checkbox';
 import Label from '@components/atoms/forms/label';
 import Cog from '@components/atoms/icons/cog';
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
-import SettingsModal from '../modals/settings-modal';
-import sharedStyles from './toolbar-items.module.scss';
+import SettingsModal, {
+  type SettingsModalProps,
+} from '../modals/settings-modal';
 import settingsStyles from './settings.module.scss';
+import sharedStyles from './toolbar-items.module.scss';
 
 export type SettingsProps = {
   /**
@@ -23,10 +25,10 @@ export type SettingsProps = {
   /**
    * Set additional classnames to the tooltip wrapper.
    */
-  tooltipClassName?: string;
+  tooltipClassName?: SettingsModalProps['tooltipClassName'];
 };
 
-const Settings: VFC<SettingsProps> = ({
+const Settings: FC<SettingsProps> = ({
   className = '',
   isActive,
   setIsActive,
diff --git a/src/components/organisms/toolbar/toolbar.tsx b/src/components/organisms/toolbar/toolbar.tsx
index 81e80cf..f1ce530 100644
--- a/src/components/organisms/toolbar/toolbar.tsx
+++ b/src/components/organisms/toolbar/toolbar.tsx
@@ -1,4 +1,4 @@
-import { useState, VFC } from 'react';
+import { FC, useState } from 'react';
 import MainNav, { type MainNavProps } from '../toolbar/main-nav';
 import Search from '../toolbar/search';
 import Settings from '../toolbar/settings';
@@ -20,7 +20,7 @@ export type ToolbarProps = {
  *
  * Render the website toolbar.
  */
-const Toolbar: VFC<ToolbarProps> = ({ className = '', nav }) => {
+const Toolbar: FC<ToolbarProps> = ({ className = '', nav }) => {
   const [isNavOpened, setIsNavOpened] = useState<boolean>(false);
   const [isSettingsOpened, setIsSettingsOpened] = useState<boolean>(false);
   const [isSearchOpened, setIsSearchOpened] = useState<boolean>(false);
diff --git a/src/components/organisms/widgets/image-widget.tsx b/src/components/organisms/widgets/image-widget.tsx
index 928d5ea..ba04c6a 100644
--- a/src/components/organisms/widgets/image-widget.tsx
+++ b/src/components/organisms/widgets/image-widget.tsx
@@ -1,28 +1,16 @@
-import ResponsiveImage from '@components/molecules/images/responsive-image';
+import ResponsiveImage, {
+  type ResponsiveImageProps,
+} from '@components/molecules/images/responsive-image';
 import Widget, { type WidgetProps } from '@components/molecules/layout/widget';
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './image-widget.module.scss';
 
 export type Alignment = 'left' | 'center' | 'right';
 
-export type Image = {
-  /**
-   * An alternative text for the image.
-   */
-  alt: string;
-  /**
-   * The image height.
-   */
-  height: number;
-  /**
-   * The image source.
-   */
-  src: string;
-  /**
-   * The image width.
-   */
-  width: number;
-};
+export type Image = Pick<
+  ResponsiveImageProps,
+  'alt' | 'height' | 'src' | 'width'
+>;
 
 export type ImageWidgetProps = Pick<
   WidgetProps,
@@ -35,7 +23,7 @@ export type ImageWidgetProps = Pick<
   /**
    * Add a caption to the image.
    */
-  description?: string;
+  description?: ResponsiveImageProps['caption'];
   /**
    * An object describing the image.
    */
@@ -43,7 +31,7 @@ export type ImageWidgetProps = Pick<
   /**
    * Add a link to the image.
    */
-  url?: string;
+  url?: ResponsiveImageProps['target'];
 };
 
 /**
@@ -51,7 +39,7 @@ export type ImageWidgetProps = Pick<
  *
  * Renders a widget that print an image and an optional text.
  */
-const ImageWidget: VFC<ImageWidgetProps> = ({
+const ImageWidget: FC<ImageWidgetProps> = ({
   alignment = 'left',
   description,
   img,
diff --git a/src/components/organisms/widgets/links-list-widget.tsx b/src/components/organisms/widgets/links-list-widget.tsx
index 155354e..559d0b6 100644
--- a/src/components/organisms/widgets/links-list-widget.tsx
+++ b/src/components/organisms/widgets/links-list-widget.tsx
@@ -1,8 +1,11 @@
 import Link from '@components/atoms/links/link';
-import List, { ListProps, type ListItem } from '@components/atoms/lists/list';
+import List, {
+  type ListProps,
+  type ListItem,
+} from '@components/atoms/lists/list';
 import Widget, { type WidgetProps } from '@components/molecules/layout/widget';
 import { slugify } from '@utils/helpers/slugify';
-import { VFC } from 'react';
+import { FC } from 'react';
 import styles from './links-list-widget.module.scss';
 
 export type LinksListItems = {
@@ -33,7 +36,7 @@ export type LinksListWidgetProps = Pick<WidgetProps, 'level' | 'title'> &
  *
  * Render a list of links inside a widget.
  */
-const LinksListWidget: VFC<LinksListWidgetProps> = ({
+const LinksListWidget: FC<LinksListWidgetProps> = ({
   items,
   kind = 'unordered',
   ...props
diff --git a/src/components/organisms/widgets/sharing.tsx b/src/components/organisms/widgets/sharing.tsx
index ccd3a21..571c1a4 100644
--- a/src/components/organisms/widgets/sharing.tsx
+++ b/src/components/organisms/widgets/sharing.tsx
@@ -2,7 +2,7 @@ import SharingLink, {
   type SharingMedium,
 } from '@components/atoms/links/sharing-link';
 import Widget, { type WidgetProps } from '@components/molecules/layout/widget';
-import { VFC } from 'react';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './sharing.module.scss';
 
@@ -21,7 +21,7 @@ export type SharingData = {
   url: string;
 };
 
-export type SharingProps = WidgetProps & {
+export type SharingProps = Omit<WidgetProps, 'children'> & {
   /**
    * The page data to share.
    */
@@ -37,7 +37,7 @@ export type SharingProps = WidgetProps & {
  *
  * Render a list of sharing links inside a widget.
  */
-const Sharing: VFC<SharingProps> = ({ data, media, ...props }) => {
+const Sharing: FC<SharingProps> = ({ data, media, ...props }) => {
   const intl = useIntl();
 
   /**
-- 
cgit v1.2.3


From 584bd42f871d2e1618ca414749f09c38f0143a44 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Wed, 18 May 2022 22:40:59 +0200
Subject: chore: handle settings change

---
 .../molecules/forms/ackee-select.stories.tsx       | 18 +++++-----
 .../molecules/forms/ackee-select.test.tsx          |  4 +--
 src/components/molecules/forms/ackee-select.tsx    | 26 +++++++++++----
 .../molecules/forms/motion-toggle.stories.tsx      | 18 +++++-----
 .../molecules/forms/motion-toggle.test.tsx         |  2 +-
 src/components/molecules/forms/motion-toggle.tsx   | 34 +++++++++++++++----
 .../molecules/forms/prism-theme-toggle.stories.tsx | 21 ------------
 .../molecules/forms/prism-theme-toggle.test.tsx    |  2 +-
 .../molecules/forms/prism-theme-toggle.tsx         | 34 ++++++++++++++-----
 .../molecules/forms/theme-toggle.stories.tsx       | 21 ------------
 .../molecules/forms/theme-toggle.test.tsx          |  2 +-
 src/components/molecules/forms/theme-toggle.tsx    | 20 +++++++++---
 .../organisms/forms/settings-form.test.tsx         | 38 +++++++++++++++++++---
 src/components/organisms/forms/settings-form.tsx   | 32 ++++++++++++++----
 src/components/organisms/layout/header.stories.tsx | 22 +++++++++++++
 src/components/organisms/layout/header.test.tsx    | 19 ++++++++---
 src/components/organisms/layout/header.tsx         | 22 +++++++++++--
 src/components/organisms/modals/search-modal.tsx   |  1 +
 .../organisms/modals/settings-modal.test.tsx       |  7 +++-
 src/components/organisms/modals/settings-modal.tsx |  6 +++-
 .../organisms/toolbar/settings.stories.tsx         | 24 ++++++++++++++
 src/components/organisms/toolbar/settings.test.tsx | 18 ++++++++--
 src/components/organisms/toolbar/settings.tsx      | 22 +++++++++----
 .../organisms/toolbar/toolbar-items.module.scss    |  2 +-
 .../organisms/toolbar/toolbar.stories.tsx          | 22 +++++++++++++
 src/components/organisms/toolbar/toolbar.test.tsx  |  9 ++++-
 src/components/organisms/toolbar/toolbar.tsx       | 37 +++++++++++++--------
 src/components/templates/layout/layout.tsx         | 10 +++---
 src/components/templates/page/page-layout.tsx      |  3 ++
 src/services/local-storage/index.ts                |  6 ++--
 src/utils/hooks/use-attributes.tsx                 | 33 +++++++++++++++++++
 src/utils/hooks/use-code-blocks-theme.tsx          | 22 +++++++++++++
 src/utils/hooks/use-local-storage.tsx              | 35 ++++++++++++++++++++
 src/utils/hooks/use-query-selector-all.tsx         | 16 +++++++++
 src/utils/hooks/use-update-ackee-options.tsx       | 19 +++++++++++
 src/utils/providers/prism-theme.tsx                |  2 +-
 36 files changed, 487 insertions(+), 142 deletions(-)
 create mode 100644 src/utils/hooks/use-attributes.tsx
 create mode 100644 src/utils/hooks/use-code-blocks-theme.tsx
 create mode 100644 src/utils/hooks/use-local-storage.tsx
 create mode 100644 src/utils/hooks/use-query-selector-all.tsx
 create mode 100644 src/utils/hooks/use-update-ackee-options.tsx

(limited to 'src/components/molecules/forms/prism-theme-toggle.tsx')

diff --git a/src/components/molecules/forms/ackee-select.stories.tsx b/src/components/molecules/forms/ackee-select.stories.tsx
index 4e6adf1..6b42b71 100644
--- a/src/components/molecules/forms/ackee-select.stories.tsx
+++ b/src/components/molecules/forms/ackee-select.stories.tsx
@@ -1,5 +1,4 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { IntlProvider } from 'react-intl';
 import AckeeSelect from './ackee-select';
 
 /**
@@ -33,6 +32,16 @@ export default {
         required: false,
       },
     },
+    storageKey: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set Ackee settings local storage key.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
     tooltipClassName: {
       control: {
         type: 'text',
@@ -47,13 +56,6 @@ export default {
       },
     },
   },
-  decorators: [
-    (Story) => (
-      <IntlProvider locale="en">
-        <Story />
-      </IntlProvider>
-    ),
-  ],
 } as ComponentMeta<typeof AckeeSelect>;
 
 const Template: ComponentStory<typeof AckeeSelect> = (args) => (
diff --git a/src/components/molecules/forms/ackee-select.test.tsx b/src/components/molecules/forms/ackee-select.test.tsx
index ec27922..0089c06 100644
--- a/src/components/molecules/forms/ackee-select.test.tsx
+++ b/src/components/molecules/forms/ackee-select.test.tsx
@@ -4,13 +4,13 @@ import AckeeSelect from './ackee-select';
 
 describe('Select', () => {
   it('should correctly set default option', () => {
-    render(<AckeeSelect initialValue="full" />);
+    render(<AckeeSelect storageKey="ackee-tracking" initialValue="full" />);
     expect(screen.getByRole('combobox')).toHaveValue('full');
     expect(screen.queryByRole('combobox')).not.toHaveValue('partial');
   });
 
   it('should correctly change value when user choose another option', async () => {
-    render(<AckeeSelect initialValue="full" />);
+    render(<AckeeSelect storageKey="ackee-tracking" initialValue="full" />);
 
     await act(async () => {
       await user.selectOptions(
diff --git a/src/components/molecules/forms/ackee-select.tsx b/src/components/molecules/forms/ackee-select.tsx
index 101e5b5..34850fb 100644
--- a/src/components/molecules/forms/ackee-select.tsx
+++ b/src/components/molecules/forms/ackee-select.tsx
@@ -1,12 +1,14 @@
-import { SelectOptions } from '@components/atoms/forms/select';
-import { Dispatch, FC, SetStateAction, useState } from 'react';
+import { type SelectOptions } from '@components/atoms/forms/select';
+import useLocalStorage from '@utils/hooks/use-local-storage';
+import useUpdateAckeeOptions, {
+  type AckeeOptions,
+} from '@utils/hooks/use-update-ackee-options';
+import { Dispatch, FC, SetStateAction } from 'react';
 import { useIntl } from 'react-intl';
 import SelectWithTooltip, {
   type SelectWithTooltipProps,
 } from './select-with-tooltip';
 
-export type AckeeOptions = 'full' | 'partial';
-
 export type AckeeSelectProps = Pick<
   SelectWithTooltipProps,
   'labelClassName' | 'tooltipClassName'
@@ -15,6 +17,10 @@ export type AckeeSelectProps = Pick<
    * A default value for Ackee settings.
    */
   initialValue: AckeeOptions;
+  /**
+   * The local storage key to save preference.
+   */
+  storageKey: string;
 };
 
 /**
@@ -22,9 +28,17 @@ export type AckeeSelectProps = Pick<
  *
  * Render a select to set Ackee settings.
  */
-const AckeeSelect: FC<AckeeSelectProps> = ({ initialValue, ...props }) => {
+const AckeeSelect: FC<AckeeSelectProps> = ({
+  initialValue,
+  storageKey,
+  ...props
+}) => {
   const intl = useIntl();
-  const [value, setValue] = useState<AckeeOptions>(initialValue);
+  const { value, setValue } = useLocalStorage<AckeeOptions>(
+    storageKey,
+    initialValue
+  );
+  useUpdateAckeeOptions(value);
 
   const ackeeLabel = intl.formatMessage({
     defaultMessage: 'Tracking:',
diff --git a/src/components/molecules/forms/motion-toggle.stories.tsx b/src/components/molecules/forms/motion-toggle.stories.tsx
index dcfc68d..60430d5 100644
--- a/src/components/molecules/forms/motion-toggle.stories.tsx
+++ b/src/components/molecules/forms/motion-toggle.stories.tsx
@@ -1,5 +1,4 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { IntlProvider } from 'react-intl';
 import MotionToggleComponent from './motion-toggle';
 
 /**
@@ -22,6 +21,16 @@ export default {
         required: false,
       },
     },
+    storageKey: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set local storage key.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
     value: {
       control: {
         type: null,
@@ -33,13 +42,6 @@ export default {
       },
     },
   },
-  decorators: [
-    (Story) => (
-      <IntlProvider locale="en">
-        <Story />
-      </IntlProvider>
-    ),
-  ],
 } as ComponentMeta<typeof MotionToggleComponent>;
 
 const Template: ComponentStory<typeof MotionToggleComponent> = (args) => (
diff --git a/src/components/molecules/forms/motion-toggle.test.tsx b/src/components/molecules/forms/motion-toggle.test.tsx
index 77bc17c..4fd6b31 100644
--- a/src/components/molecules/forms/motion-toggle.test.tsx
+++ b/src/components/molecules/forms/motion-toggle.test.tsx
@@ -3,7 +3,7 @@ import MotionToggle from './motion-toggle';
 
 describe('MotionToggle', () => {
   it('renders a checked toggle (deactivate animations choice)', () => {
-    render(<MotionToggle value={true} />);
+    render(<MotionToggle storageKey="reduced-motion" value={true} />);
     expect(
       screen.getByRole('checkbox', {
         name: `Animations: On Off`,
diff --git a/src/components/molecules/forms/motion-toggle.tsx b/src/components/molecules/forms/motion-toggle.tsx
index 24b54ae..e3bb11a 100644
--- a/src/components/molecules/forms/motion-toggle.tsx
+++ b/src/components/molecules/forms/motion-toggle.tsx
@@ -2,19 +2,41 @@ import Toggle, {
   type ToggleChoices,
   type ToggleProps,
 } from '@components/molecules/forms/toggle';
-import { FC, useState } from 'react';
+import useAttributes from '@utils/hooks/use-attributes';
+import useLocalStorage from '@utils/hooks/use-local-storage';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 
-export type MotionToggleProps = Pick<ToggleProps, 'labelClassName' | 'value'>;
+export type MotionToggleProps = Pick<
+  ToggleProps,
+  'labelClassName' | 'value'
+> & {
+  /**
+   * The local storage key to save preference.
+   */
+  storageKey: string;
+};
 
 /**
  * MotionToggle component
  *
  * Render a Toggle component to set reduce motion.
  */
-const MotionToggle: FC<MotionToggleProps> = ({ value, ...props }) => {
+const MotionToggle: FC<MotionToggleProps> = ({
+  storageKey,
+  value,
+  ...props
+}) => {
   const intl = useIntl();
-  const [isDeactivated, setIsDeactivated] = useState<boolean>(value);
+  const { value: isReduced, setValue: setIsReduced } = useLocalStorage<boolean>(
+    storageKey,
+    value
+  );
+  useAttributes({
+    attribute: 'reducedMotion',
+    value: `${isReduced}`,
+  });
+
   const reduceMotionLabel = intl.formatMessage({
     defaultMessage: 'Animations:',
     description: 'MotionToggle: reduce motion label',
@@ -42,8 +64,8 @@ const MotionToggle: FC<MotionToggleProps> = ({ value, ...props }) => {
       label={reduceMotionLabel}
       labelSize="medium"
       choices={reduceMotionChoices}
-      value={isDeactivated}
-      setValue={setIsDeactivated}
+      value={isReduced}
+      setValue={setIsReduced}
       {...props}
     />
   );
diff --git a/src/components/molecules/forms/prism-theme-toggle.stories.tsx b/src/components/molecules/forms/prism-theme-toggle.stories.tsx
index 513ebfc..ef4ed6e 100644
--- a/src/components/molecules/forms/prism-theme-toggle.stories.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.stories.tsx
@@ -1,5 +1,4 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { IntlProvider } from 'react-intl';
 import PrismThemeToggle from './prism-theme-toggle';
 
 /**
@@ -22,24 +21,7 @@ export default {
         required: false,
       },
     },
-    value: {
-      control: {
-        type: null,
-      },
-      description: 'The prism theme value.',
-      type: {
-        name: 'boolean',
-        required: true,
-      },
-    },
   },
-  decorators: [
-    (Story) => (
-      <IntlProvider locale="en">
-        <Story />
-      </IntlProvider>
-    ),
-  ],
 } as ComponentMeta<typeof PrismThemeToggle>;
 
 const Template: ComponentStory<typeof PrismThemeToggle> = (args) => (
@@ -50,6 +32,3 @@ const Template: ComponentStory<typeof PrismThemeToggle> = (args) => (
  * Toggle Stories - Prism theme
  */
 export const PrismTheme = Template.bind({});
-PrismTheme.args = {
-  value: false,
-};
diff --git a/src/components/molecules/forms/prism-theme-toggle.test.tsx b/src/components/molecules/forms/prism-theme-toggle.test.tsx
index 0dceb92..d831b82 100644
--- a/src/components/molecules/forms/prism-theme-toggle.test.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.test.tsx
@@ -3,7 +3,7 @@ import PrismThemeToggle from './prism-theme-toggle';
 
 describe('PrismThemeToggle', () => {
   it('renders a checked toggle (dark theme choice)', () => {
-    render(<PrismThemeToggle value={true} />);
+    render(<PrismThemeToggle />);
     expect(
       screen.getByRole('checkbox', {
         name: `Code blocks: Light theme Dark theme`,
diff --git a/src/components/molecules/forms/prism-theme-toggle.tsx b/src/components/molecules/forms/prism-theme-toggle.tsx
index 0b9c447..3320722 100644
--- a/src/components/molecules/forms/prism-theme-toggle.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.tsx
@@ -4,22 +4,38 @@ import Toggle, {
   type ToggleChoices,
   type ToggleProps,
 } from '@components/molecules/forms/toggle';
-import { FC, useState } from 'react';
+import { usePrismTheme } from '@utils/providers/prism-theme';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 
-export type PrismThemeToggleProps = Pick<
-  ToggleProps,
-  'labelClassName' | 'value'
->;
+export type PrismThemeToggleProps = Pick<ToggleProps, 'labelClassName'>;
 
 /**
  * PrismThemeToggle component
  *
  * Render a Toggle component to set code blocks theme.
  */
-const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ value, ...props }) => {
+const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ ...props }) => {
   const intl = useIntl();
-  const [isDarkTheme, setIsDarkTheme] = useState<boolean>(value);
+  const { theme, setTheme, resolvedTheme } = usePrismTheme();
+
+  /**
+   * Check if the resolved or chosen theme is dark theme.
+   *
+   * @returns {boolean} True if it is dark theme.
+   */
+  const isDarkTheme = (): boolean => {
+    if (theme === 'system') return resolvedTheme === 'dark';
+    return theme === 'dark';
+  };
+
+  /**
+   * Update the theme.
+   */
+  const updateTheme = () => {
+    setTheme(isDarkTheme() ? 'light' : 'dark');
+  };
+
   const themeLabel = intl.formatMessage({
     defaultMessage: 'Code blocks:',
     description: 'PrismThemeToggle: theme label',
@@ -47,8 +63,8 @@ const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ value, ...props }) => {
       label={themeLabel}
       labelSize="medium"
       choices={themeChoices}
-      value={isDarkTheme}
-      setValue={setIsDarkTheme}
+      value={isDarkTheme()}
+      setValue={updateTheme}
       {...props}
     />
   );
diff --git a/src/components/molecules/forms/theme-toggle.stories.tsx b/src/components/molecules/forms/theme-toggle.stories.tsx
index 05d94b9..5ebf5a2 100644
--- a/src/components/molecules/forms/theme-toggle.stories.tsx
+++ b/src/components/molecules/forms/theme-toggle.stories.tsx
@@ -1,5 +1,4 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { IntlProvider } from 'react-intl';
 import ThemeToggle from './theme-toggle';
 
 /**
@@ -22,24 +21,7 @@ export default {
         required: false,
       },
     },
-    value: {
-      control: {
-        type: null,
-      },
-      description: 'The theme value.',
-      type: {
-        name: 'boolean',
-        required: true,
-      },
-    },
   },
-  decorators: [
-    (Story) => (
-      <IntlProvider locale="en">
-        <Story />
-      </IntlProvider>
-    ),
-  ],
 } as ComponentMeta<typeof ThemeToggle>;
 
 const Template: ComponentStory<typeof ThemeToggle> = (args) => (
@@ -50,6 +32,3 @@ const Template: ComponentStory<typeof ThemeToggle> = (args) => (
  * Toggle Stories - Theme
  */
 export const Theme = Template.bind({});
-Theme.args = {
-  value: false,
-};
diff --git a/src/components/molecules/forms/theme-toggle.test.tsx b/src/components/molecules/forms/theme-toggle.test.tsx
index 5cd3209..89b4f6f 100644
--- a/src/components/molecules/forms/theme-toggle.test.tsx
+++ b/src/components/molecules/forms/theme-toggle.test.tsx
@@ -3,7 +3,7 @@ import ThemeToggle from './theme-toggle';
 
 describe('ThemeToggle', () => {
   it('renders a checked toggle (dark theme choice)', () => {
-    render(<ThemeToggle value={true} />);
+    render(<ThemeToggle />);
     expect(
       screen.getByRole('checkbox', {
         name: `Theme: Light theme Dark theme`,
diff --git a/src/components/molecules/forms/theme-toggle.tsx b/src/components/molecules/forms/theme-toggle.tsx
index 10c6c47..61ee4c6 100644
--- a/src/components/molecules/forms/theme-toggle.tsx
+++ b/src/components/molecules/forms/theme-toggle.tsx
@@ -4,19 +4,29 @@ import Toggle, {
   type ToggleChoices,
   type ToggleProps,
 } from '@components/molecules/forms/toggle';
-import { FC, useState } from 'react';
+import { useTheme } from 'next-themes';
+import { FC } from 'react';
 import { useIntl } from 'react-intl';
 
-export type ThemeToggleProps = Pick<ToggleProps, 'labelClassName' | 'value'>;
+export type ThemeToggleProps = Pick<ToggleProps, 'labelClassName'>;
 
 /**
  * ThemeToggle component
  *
  * Render a Toggle component to set theme.
  */
-const ThemeToggle: FC<ThemeToggleProps> = ({ value, ...props }) => {
+const ThemeToggle: FC<ThemeToggleProps> = ({ ...props }) => {
   const intl = useIntl();
-  const [isDarkTheme, setIsDarkTheme] = useState<boolean>(value);
+  const { resolvedTheme, setTheme } = useTheme();
+  const isDarkTheme = resolvedTheme === 'dark';
+
+  /**
+   * Update the theme.
+   */
+  const updateTheme = () => {
+    setTheme(isDarkTheme ? 'light' : 'dark');
+  };
+
   const themeLabel = intl.formatMessage({
     defaultMessage: 'Theme:',
     description: 'ThemeToggle: theme label',
@@ -45,7 +55,7 @@ const ThemeToggle: FC<ThemeToggleProps> = ({ value, ...props }) => {
       labelSize="medium"
       choices={themeChoices}
       value={isDarkTheme}
-      setValue={setIsDarkTheme}
+      setValue={updateTheme}
       {...props}
     />
   );
diff --git a/src/components/organisms/forms/settings-form.test.tsx b/src/components/organisms/forms/settings-form.test.tsx
index beb65ec..43d546e 100644
--- a/src/components/organisms/forms/settings-form.test.tsx
+++ b/src/components/organisms/forms/settings-form.test.tsx
@@ -1,37 +1,65 @@
 import { render, screen } from '@test-utils';
 import SettingsForm from './settings-form';
 
+const ackeeStorageKey = 'ackee-tracking';
+const motionStorageKey = 'reduce-motion';
+
 describe('SettingsForm', () => {
   it('renders a form', () => {
-    render(<SettingsForm />);
+    render(
+      <SettingsForm
+        ackeeStorageKey={ackeeStorageKey}
+        motionStorageKey={motionStorageKey}
+      />
+    );
     expect(
       screen.getByRole('form', { name: /^Settings form/i })
     ).toBeInTheDocument();
   });
 
   it('renders a theme toggle setting', () => {
-    render(<SettingsForm />);
+    render(
+      <SettingsForm
+        ackeeStorageKey={ackeeStorageKey}
+        motionStorageKey={motionStorageKey}
+      />
+    );
     expect(
       screen.getByRole('checkbox', { name: /^Theme:/i })
     ).toBeInTheDocument();
   });
 
   it('renders a code blocks toggle setting', () => {
-    render(<SettingsForm />);
+    render(
+      <SettingsForm
+        ackeeStorageKey={ackeeStorageKey}
+        motionStorageKey={motionStorageKey}
+      />
+    );
     expect(
       screen.getByRole('checkbox', { name: /^Code blocks:/i })
     ).toBeInTheDocument();
   });
 
   it('renders a motion setting', () => {
-    render(<SettingsForm />);
+    render(
+      <SettingsForm
+        ackeeStorageKey={ackeeStorageKey}
+        motionStorageKey={motionStorageKey}
+      />
+    );
     expect(
       screen.getByRole('checkbox', { name: /^Animations:/i })
     ).toBeInTheDocument();
   });
 
   it('renders a Ackee setting', () => {
-    render(<SettingsForm />);
+    render(
+      <SettingsForm
+        ackeeStorageKey={ackeeStorageKey}
+        motionStorageKey={motionStorageKey}
+      />
+    );
     expect(
       screen.getByRole('combobox', { name: /^Tracking:/i })
     ).toBeInTheDocument();
diff --git a/src/components/organisms/forms/settings-form.tsx b/src/components/organisms/forms/settings-form.tsx
index 0a34601..c897fa5 100644
--- a/src/components/organisms/forms/settings-form.tsx
+++ b/src/components/organisms/forms/settings-form.tsx
@@ -2,16 +2,31 @@ import Form from '@components/atoms/forms/form';
 import AckeeSelect, {
   type AckeeSelectProps,
 } from '@components/molecules/forms/ackee-select';
-import MotionToggle from '@components/molecules/forms/motion-toggle';
+import MotionToggle, {
+  MotionToggleProps,
+} from '@components/molecules/forms/motion-toggle';
 import PrismThemeToggle from '@components/molecules/forms/prism-theme-toggle';
 import ThemeToggle from '@components/molecules/forms/theme-toggle';
 import { FC } from 'react';
 import { useIntl } from 'react-intl';
 import styles from './settings-form.module.scss';
 
-export type SettingsFormProps = Pick<AckeeSelectProps, 'tooltipClassName'>;
+export type SettingsFormProps = Pick<AckeeSelectProps, 'tooltipClassName'> & {
+  /**
+   * The local storage key for Ackee settings.
+   */
+  ackeeStorageKey: AckeeSelectProps['storageKey'];
+  /**
+   * The local storage key for Reduce motion settings.
+   */
+  motionStorageKey: MotionToggleProps['storageKey'];
+};
 
-const SettingsForm: FC<SettingsFormProps> = ({ tooltipClassName }) => {
+const SettingsForm: FC<SettingsFormProps> = ({
+  ackeeStorageKey,
+  motionStorageKey,
+  tooltipClassName,
+}) => {
   const intl = useIntl();
   const ariaLabel = intl.formatMessage({
     defaultMessage: 'Settings form',
@@ -21,13 +36,18 @@ const SettingsForm: FC<SettingsFormProps> = ({ tooltipClassName }) => {
 
   return (
     <Form aria-label={ariaLabel} onSubmit={() => null}>
-      <ThemeToggle labelClassName={styles.label} value={false} />
-      <PrismThemeToggle labelClassName={styles.label} value={false} />
-      <MotionToggle labelClassName={styles.label} value={false} />
+      <ThemeToggle labelClassName={styles.label} />
+      <PrismThemeToggle labelClassName={styles.label} />
+      <MotionToggle
+        labelClassName={styles.label}
+        storageKey={motionStorageKey}
+        value={false}
+      />
       <AckeeSelect
         initialValue="full"
         labelClassName={styles.label}
         tooltipClassName={tooltipClassName}
+        storageKey={ackeeStorageKey}
       />
     </Form>
   );
diff --git a/src/components/organisms/layout/header.stories.tsx b/src/components/organisms/layout/header.stories.tsx
index 98d6377..3ceb337 100644
--- a/src/components/organisms/layout/header.stories.tsx
+++ b/src/components/organisms/layout/header.stories.tsx
@@ -8,11 +8,23 @@ export default {
   title: 'Organisms/Layout',
   component: HeaderComponent,
   args: {
+    ackeeStorageKey: 'ackee-tracking',
     isHome: false,
+    motionStorageKey: 'reduced-motion',
     searchPage: '#',
     withLink: false,
   },
   argTypes: {
+    ackeeStorageKey: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set Ackee settings local storage key.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
     baseline: {
       control: {
         type: 'text',
@@ -52,6 +64,16 @@ export default {
         required: false,
       },
     },
+    motionStorageKey: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set Reduced motion settings local storage key.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
     nav: {
       description: 'The main navigation items.',
       type: {
diff --git a/src/components/organisms/layout/header.test.tsx b/src/components/organisms/layout/header.test.tsx
index a9896f8..414d96f 100644
--- a/src/components/organisms/layout/header.test.tsx
+++ b/src/components/organisms/layout/header.test.tsx
@@ -16,11 +16,13 @@ describe('Header', () => {
   it('renders the website title', () => {
     render(
       <Header
+        ackeeStorageKey="ackee-tracking"
+        isHome={true}
+        motionStorageKey="reduced-motion"
+        nav={nav}
+        photo={photo}
         searchPage="#"
         title={title}
-        photo={photo}
-        nav={nav}
-        isHome={true}
       />
     );
     expect(
@@ -29,7 +31,16 @@ describe('Header', () => {
   });
 
   it('renders the main nav', () => {
-    render(<Header searchPage="#" title={title} photo={photo} nav={nav} />);
+    render(
+      <Header
+        ackeeStorageKey="ackee-tracking"
+        motionStorageKey="reduced-motion"
+        nav={nav}
+        photo={photo}
+        searchPage="#"
+        title={title}
+      />
+    );
     expect(screen.getByRole('navigation')).toBeInTheDocument();
   });
 });
diff --git a/src/components/organisms/layout/header.tsx b/src/components/organisms/layout/header.tsx
index 18ebb31..f6212c3 100644
--- a/src/components/organisms/layout/header.tsx
+++ b/src/components/organisms/layout/header.tsx
@@ -6,7 +6,10 @@ import Toolbar, { type ToolbarProps } from '../toolbar/toolbar';
 import styles from './header.module.scss';
 
 export type HeaderProps = BrandingProps &
-  Pick<ToolbarProps, 'nav' | 'searchPage'> & {
+  Pick<
+    ToolbarProps,
+    'ackeeStorageKey' | 'motionStorageKey' | 'nav' | 'searchPage'
+  > & {
     /**
      * Set additional classnames to the header element.
      */
@@ -18,12 +21,25 @@ export type HeaderProps = BrandingProps &
  *
  * Render the website header.
  */
-const Header: FC<HeaderProps> = ({ className, nav, searchPage, ...props }) => {
+const Header: FC<HeaderProps> = ({
+  ackeeStorageKey,
+  className,
+  motionStorageKey,
+  nav,
+  searchPage,
+  ...props
+}) => {
   return (
     <header className={`${styles.wrapper} ${className}`}>
       <div className={styles.body}>
         <Branding {...props} />
-        <Toolbar nav={nav} searchPage={searchPage} className={styles.toolbar} />
+        <Toolbar
+          ackeeStorageKey={ackeeStorageKey}
+          className={styles.toolbar}
+          motionStorageKey={motionStorageKey}
+          nav={nav}
+          searchPage={searchPage}
+        />
       </div>
     </header>
   );
diff --git a/src/components/organisms/modals/search-modal.tsx b/src/components/organisms/modals/search-modal.tsx
index e92bf1b..c731048 100644
--- a/src/components/organisms/modals/search-modal.tsx
+++ b/src/components/organisms/modals/search-modal.tsx
@@ -10,6 +10,7 @@ const DynamicSearchForm = dynamic(
   () => import('@components/organisms/forms/search-form'),
   {
     loading: () => <Spinner />,
+    ssr: false,
   }
 );
 
diff --git a/src/components/organisms/modals/settings-modal.test.tsx b/src/components/organisms/modals/settings-modal.test.tsx
index acbf7d1..d6ed989 100644
--- a/src/components/organisms/modals/settings-modal.test.tsx
+++ b/src/components/organisms/modals/settings-modal.test.tsx
@@ -3,7 +3,12 @@ import SettingsModal from './settings-modal';
 
 describe('SettingsModal', () => {
   it('renders a fake heading', () => {
-    render(<SettingsModal />);
+    render(
+      <SettingsModal
+        ackeeStorageKey="ackee-tracking"
+        motionStorageKey="reduce-motion"
+      />
+    );
     expect(screen.getByText(/Settings/i)).toBeInTheDocument();
   });
 });
diff --git a/src/components/organisms/modals/settings-modal.tsx b/src/components/organisms/modals/settings-modal.tsx
index e724076..5d14836 100644
--- a/src/components/organisms/modals/settings-modal.tsx
+++ b/src/components/organisms/modals/settings-modal.tsx
@@ -10,11 +10,15 @@ const DynamicSettingsForm = dynamic(
   () => import('@components/organisms/forms/settings-form'),
   {
     loading: () => <Spinner />,
+    ssr: false,
   }
 );
 
 export type SettingsModalProps = Pick<ModalProps, 'className'> &
-  Pick<SettingsFormProps, 'tooltipClassName'>;
+  Pick<
+    SettingsFormProps,
+    'ackeeStorageKey' | 'motionStorageKey' | 'tooltipClassName'
+  >;
 
 /**
  * SettingsModal component
diff --git a/src/components/organisms/toolbar/settings.stories.tsx b/src/components/organisms/toolbar/settings.stories.tsx
index aab4b9e..9d04932 100644
--- a/src/components/organisms/toolbar/settings.stories.tsx
+++ b/src/components/organisms/toolbar/settings.stories.tsx
@@ -8,7 +8,21 @@ import Settings from './settings';
 export default {
   title: 'Organisms/Toolbar/Settings',
   component: Settings,
+  args: {
+    ackeeStorageKey: 'ackee-tracking',
+    motionStorageKey: 'reduced-motion',
+  },
   argTypes: {
+    ackeeStorageKey: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set Ackee settings local storage key.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
     className: {
       control: {
         type: 'text',
@@ -32,6 +46,16 @@ export default {
         required: true,
       },
     },
+    motionStorageKey: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set Reduced motion settings local storage key.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
     setIsActive: {
       control: {
         type: null,
diff --git a/src/components/organisms/toolbar/settings.test.tsx b/src/components/organisms/toolbar/settings.test.tsx
index 96a32c9..7ccb234 100644
--- a/src/components/organisms/toolbar/settings.test.tsx
+++ b/src/components/organisms/toolbar/settings.test.tsx
@@ -3,14 +3,28 @@ import Settings from './settings';
 
 describe('Settings', () => {
   it('renders a button to open settings modal', () => {
-    render(<Settings isActive={false} setIsActive={() => null} />);
+    render(
+      <Settings
+        ackeeStorageKey="ackee-tracking"
+        motionStorageKey="reduced-motion"
+        isActive={false}
+        setIsActive={() => null}
+      />
+    );
     expect(
       screen.getByRole('checkbox', { name: 'Open settings' })
     ).toBeInTheDocument();
   });
 
   it('renders a button to close settings modal', () => {
-    render(<Settings isActive={true} setIsActive={() => null} />);
+    render(
+      <Settings
+        ackeeStorageKey="ackee-tracking"
+        motionStorageKey="reduced-motion"
+        isActive={true}
+        setIsActive={() => null}
+      />
+    );
     expect(
       screen.getByRole('checkbox', { name: 'Close settings' })
     ).toBeInTheDocument();
diff --git a/src/components/organisms/toolbar/settings.tsx b/src/components/organisms/toolbar/settings.tsx
index 43d3190..9985ba0 100644
--- a/src/components/organisms/toolbar/settings.tsx
+++ b/src/components/organisms/toolbar/settings.tsx
@@ -1,7 +1,7 @@
 import Checkbox, { type CheckboxProps } from '@components/atoms/forms/checkbox';
 import Label from '@components/atoms/forms/label';
 import Cog from '@components/atoms/icons/cog';
-import { FC, forwardRef, ForwardRefRenderFunction } from 'react';
+import { forwardRef, ForwardRefRenderFunction } from 'react';
 import { useIntl } from 'react-intl';
 import SettingsModal, {
   type SettingsModalProps,
@@ -9,7 +9,10 @@ import SettingsModal, {
 import settingsStyles from './settings.module.scss';
 import sharedStyles from './toolbar-items.module.scss';
 
-export type SettingsProps = {
+export type SettingsProps = Pick<
+  SettingsModalProps,
+  'ackeeStorageKey' | 'motionStorageKey' | 'tooltipClassName'
+> & {
   /**
    * Set additional classnames to the modal wrapper.
    */
@@ -22,14 +25,17 @@ export type SettingsProps = {
    * A callback function to handle button state.
    */
   setIsActive: CheckboxProps['setValue'];
-  /**
-   * Set additional classnames to the tooltip wrapper.
-   */
-  tooltipClassName?: SettingsModalProps['tooltipClassName'];
 };
 
 const Settings: ForwardRefRenderFunction<HTMLDivElement, SettingsProps> = (
-  { className = '', isActive, setIsActive, tooltipClassName = '' },
+  {
+    ackeeStorageKey,
+    className = '',
+    isActive,
+    motionStorageKey,
+    setIsActive,
+    tooltipClassName = '',
+  },
   ref
 ) => {
   const intl = useIntl();
@@ -62,7 +68,9 @@ const Settings: ForwardRefRenderFunction<HTMLDivElement, SettingsProps> = (
         <Cog />
       </Label>
       <SettingsModal
+        ackeeStorageKey={ackeeStorageKey}
         className={`${sharedStyles.modal} ${settingsStyles.modal} ${className}`}
+        motionStorageKey={motionStorageKey}
         tooltipClassName={tooltipClassName}
       />
     </div>
diff --git a/src/components/organisms/toolbar/toolbar-items.module.scss b/src/components/organisms/toolbar/toolbar-items.module.scss
index c970b71..20abe01 100644
--- a/src/components/organisms/toolbar/toolbar-items.module.scss
+++ b/src/components/organisms/toolbar/toolbar-items.module.scss
@@ -22,7 +22,7 @@
 .modal {
   position: absolute;
   top: var(--toolbar-size, calc(var(--btn-size) + var(--spacing-2xs)));
-  transition: all 0.8s ease-in-out 0s;
+  transition: all 0.8s ease-in-out 0s, background 0s;
 
   @include mix.media("screen") {
     @include mix.dimensions(null, "sm") {
diff --git a/src/components/organisms/toolbar/toolbar.stories.tsx b/src/components/organisms/toolbar/toolbar.stories.tsx
index 477cb55..1f07e79 100644
--- a/src/components/organisms/toolbar/toolbar.stories.tsx
+++ b/src/components/organisms/toolbar/toolbar.stories.tsx
@@ -8,9 +8,21 @@ export default {
   title: 'Organisms/Toolbar',
   component: ToolbarComponent,
   args: {
+    ackeeStorageKey: 'ackee-tracking',
+    motionStorageKey: 'reduced-motion',
     searchPage: '#',
   },
   argTypes: {
+    ackeeStorageKey: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set Ackee settings local storage key.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
     className: {
       control: {
         type: 'text',
@@ -24,6 +36,16 @@ export default {
         required: false,
       },
     },
+    motionStorageKey: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set Reduced motion settings local storage key.',
+      type: {
+        name: 'string',
+        required: true,
+      },
+    },
     nav: {
       description: 'The main nav items.',
       type: {
diff --git a/src/components/organisms/toolbar/toolbar.test.tsx b/src/components/organisms/toolbar/toolbar.test.tsx
index 05e84ff..72965e8 100644
--- a/src/components/organisms/toolbar/toolbar.test.tsx
+++ b/src/components/organisms/toolbar/toolbar.test.tsx
@@ -10,7 +10,14 @@ const nav = [
 
 describe('Toolbar', () => {
   it('renders a navigation menu', () => {
-    render(<Toolbar nav={nav} searchPage="#" />);
+    render(
+      <Toolbar
+        ackeeStorageKey="ackee-tracking"
+        motionStorageKey="reduced-motion"
+        nav={nav}
+        searchPage="#"
+      />
+    );
     expect(screen.getByRole('navigation')).toBeInTheDocument();
   });
 });
diff --git a/src/components/organisms/toolbar/toolbar.tsx b/src/components/organisms/toolbar/toolbar.tsx
index e4188fe..e196c09 100644
--- a/src/components/organisms/toolbar/toolbar.tsx
+++ b/src/components/organisms/toolbar/toolbar.tsx
@@ -2,26 +2,33 @@ import useClickOutside from '@utils/hooks/use-click-outside';
 import { FC, useRef, useState } from 'react';
 import MainNav, { type MainNavProps } from '../toolbar/main-nav';
 import Search, { type SearchProps } from '../toolbar/search';
-import Settings from '../toolbar/settings';
+import Settings, { SettingsProps } from '../toolbar/settings';
 import styles from './toolbar.module.scss';
 
-export type ToolbarProps = Pick<SearchProps, 'searchPage'> & {
-  /**
-   * Set additional classnames to the toolbar wrapper.
-   */
-  className?: string;
-  /**
-   * The main nav items.
-   */
-  nav: MainNavProps['items'];
-};
+export type ToolbarProps = Pick<SearchProps, 'searchPage'> &
+  Pick<SettingsProps, 'ackeeStorageKey' | 'motionStorageKey'> & {
+    /**
+     * Set additional classnames to the toolbar wrapper.
+     */
+    className?: string;
+    /**
+     * The main nav items.
+     */
+    nav: MainNavProps['items'];
+  };
 
 /**
  * Toolbar component
  *
  * Render the website toolbar.
  */
-const Toolbar: FC<ToolbarProps> = ({ className = '', nav, searchPage }) => {
+const Toolbar: FC<ToolbarProps> = ({
+  ackeeStorageKey,
+  className = '',
+  motionStorageKey,
+  nav,
+  searchPage,
+}) => {
   const [isNavOpened, setIsNavOpened] = useState<boolean>(false);
   const [isSearchOpened, setIsSearchOpened] = useState<boolean>(false);
   const [isSettingsOpened, setIsSettingsOpened] = useState<boolean>(false);
@@ -53,11 +60,13 @@ const Toolbar: FC<ToolbarProps> = ({ className = '', nav, searchPage }) => {
         ref={searchRef}
       />
       <Settings
+        ackeeStorageKey={ackeeStorageKey}
+        className={`${styles.modal} ${styles['modal--settings']}`}
         isActive={isSettingsOpened}
+        motionStorageKey={motionStorageKey}
+        ref={settingsRef}
         setIsActive={setIsSettingsOpened}
-        className={`${styles.modal} ${styles['modal--settings']}`}
         tooltipClassName={styles.tooltip}
-        ref={settingsRef}
       />
     </div>
   );
diff --git a/src/components/templates/layout/layout.tsx b/src/components/templates/layout/layout.tsx
index 559eaed..8f0d4e7 100644
--- a/src/components/templates/layout/layout.tsx
+++ b/src/components/templates/layout/layout.tsx
@@ -193,14 +193,16 @@ const Layout: FC<LayoutProps> = ({
         {skipToContent}
       </ButtonLink>
       <Header
-        title={name}
+        ackeeStorageKey="ackee-tracking"
         baseline={baseline}
-        photo={picture}
+        className={styles.header}
+        isHome={isHome}
+        motionStorageKey="reduced-motion"
         nav={mainNav}
+        photo={picture}
         searchPage="/recherche"
-        isHome={isHome}
+        title={name}
         withLink={true}
-        className={styles.header}
       />
       <Main id="main" className={styles.main}>
         <article
diff --git a/src/components/templates/page/page-layout.tsx b/src/components/templates/page/page-layout.tsx
index d171944..54b8d6e 100644
--- a/src/components/templates/page/page-layout.tsx
+++ b/src/components/templates/page/page-layout.tsx
@@ -20,6 +20,7 @@ import CommentsList, {
 import TableOfContents from '@components/organisms/widgets/table-of-contents';
 import { type SendCommentVars } from '@services/graphql/api';
 import { sendComment } from '@services/graphql/comments';
+import useCodeBlocksTheme from '@utils/hooks/use-code-blocks-theme';
 import useIsMounted from '@utils/hooks/use-is-mounted';
 import Script from 'next/script';
 import { FC, HTMLAttributes, ReactNode, useRef, useState } from 'react';
@@ -183,6 +184,8 @@ const PageLayout: FC<PageLayoutProps> = ({
     return Object.values(meta).every((value) => value === null);
   };
 
+  useCodeBlocksTheme(bodyRef);
+
   return (
     <>
       <Script
diff --git a/src/services/local-storage/index.ts b/src/services/local-storage/index.ts
index 8ed9ebf..65235a7 100644
--- a/src/services/local-storage/index.ts
+++ b/src/services/local-storage/index.ts
@@ -1,15 +1,15 @@
 export const LocalStorage = {
-  get(key: string): string | null | undefined {
+  get<T>(key: string): T | undefined {
     try {
       const serialItem = localStorage.getItem(key);
       if (!serialItem) return undefined;
-      return JSON.parse(serialItem);
+      return JSON.parse(serialItem) as T;
     } catch (e) {
       console.log(e);
       return undefined;
     }
   },
-  set(key: string, value: string) {
+  set<T>(key: string, value: T) {
     try {
       const serialItem = JSON.stringify(value);
       localStorage.setItem(key, serialItem);
diff --git a/src/utils/hooks/use-attributes.tsx b/src/utils/hooks/use-attributes.tsx
new file mode 100644
index 0000000..97a7b43
--- /dev/null
+++ b/src/utils/hooks/use-attributes.tsx
@@ -0,0 +1,33 @@
+import { useEffect } from 'react';
+
+export type useAttributesProps = {
+  /**
+   * An HTML element.
+   */
+  element?: HTMLElement;
+  /**
+   * The attribute name.
+   */
+  attribute: string;
+  /**
+   * The attribute value.
+   */
+  value: string;
+};
+
+/**
+ * Set HTML attributes to the given element or to the HTML document.
+ *
+ * @param props - An object with element, attribute name and value.
+ */
+const useAttributes = ({ element, attribute, value }: useAttributesProps) => {
+  useEffect(() => {
+    if (element) {
+      element.dataset[attribute] = value;
+    } else {
+      document.documentElement.dataset[attribute] = value;
+    }
+  }, [attribute, element, value]);
+};
+
+export default useAttributes;
diff --git a/src/utils/hooks/use-code-blocks-theme.tsx b/src/utils/hooks/use-code-blocks-theme.tsx
new file mode 100644
index 0000000..beb7b29
--- /dev/null
+++ b/src/utils/hooks/use-code-blocks-theme.tsx
@@ -0,0 +1,22 @@
+import { usePrismTheme } from '@utils/providers/prism-theme';
+import { useRouter } from 'next/router';
+import { RefObject, useEffect, useState } from 'react';
+import useIsMounted from './use-is-mounted';
+
+const useCodeBlocksTheme = (el: RefObject<HTMLDivElement>) => {
+  const [preElements, setPreElements] = useState<NodeListOf<HTMLPreElement>>();
+  const isMounted = useIsMounted(el);
+  const { setCodeBlocks } = usePrismTheme();
+  const { asPath } = useRouter();
+
+  useEffect(() => {
+    const result = document.querySelectorAll<HTMLPreElement>('pre');
+    setPreElements(result);
+  }, [asPath]);
+
+  useEffect(() => {
+    isMounted && preElements && setCodeBlocks(preElements);
+  }, [isMounted, preElements, setCodeBlocks]);
+};
+
+export default useCodeBlocksTheme;
diff --git a/src/utils/hooks/use-local-storage.tsx b/src/utils/hooks/use-local-storage.tsx
new file mode 100644
index 0000000..da0292b
--- /dev/null
+++ b/src/utils/hooks/use-local-storage.tsx
@@ -0,0 +1,35 @@
+import { LocalStorage } from '@services/local-storage';
+import { Dispatch, SetStateAction, useEffect, useState } from 'react';
+
+export type UseLocalStorageReturn<T> = {
+  value: T;
+  setValue: Dispatch<SetStateAction<T>>;
+};
+
+/**
+ * Use the local storage.
+ *
+ * @param {string} key - The storage local key.
+ * @param {T} [fallbackValue] - A fallback value if local storage is empty.
+ * @returns {UseLocalStorageReturn<T>} An object with value and setValue.
+ */
+const useLocalStorage = <T extends unknown>(
+  key: string,
+  fallbackValue: T
+): UseLocalStorageReturn<T> => {
+  const getInitialValue = () => {
+    if (typeof window === 'undefined') return fallbackValue;
+    const storedValue = LocalStorage.get<T>(key);
+    return storedValue || fallbackValue;
+  };
+
+  const [value, setValue] = useState<T>(getInitialValue);
+
+  useEffect(() => {
+    LocalStorage.set(key, value);
+  }, [key, value]);
+
+  return { value, setValue };
+};
+
+export default useLocalStorage;
diff --git a/src/utils/hooks/use-query-selector-all.tsx b/src/utils/hooks/use-query-selector-all.tsx
new file mode 100644
index 0000000..dbeec90
--- /dev/null
+++ b/src/utils/hooks/use-query-selector-all.tsx
@@ -0,0 +1,16 @@
+import { useEffect, useState } from 'react';
+
+const useQuerySelectorAll = <T extends keyof HTMLElementTagNameMap>(
+  query: string
+) => {
+  const [elements, setElements] =
+    useState<NodeListOf<HTMLElementTagNameMap[T]>>();
+
+  useEffect(() => {
+    setElements(document.querySelectorAll(query));
+  }, [query]);
+
+  return elements;
+};
+
+export default useQuerySelectorAll;
diff --git a/src/utils/hooks/use-update-ackee-options.tsx b/src/utils/hooks/use-update-ackee-options.tsx
new file mode 100644
index 0000000..7c1d98a
--- /dev/null
+++ b/src/utils/hooks/use-update-ackee-options.tsx
@@ -0,0 +1,19 @@
+import { useAckeeTracker } from '@utils/providers/ackee';
+import { useEffect } from 'react';
+
+export type AckeeOptions = 'full' | 'partial';
+
+/**
+ * Update Ackee settings with the given choice.
+ *
+ * @param {AckeeOptions} value - Either `full` or `partial`.
+ */
+const useUpdateAckeeOptions = (value: AckeeOptions) => {
+  const { setDetailed } = useAckeeTracker();
+
+  useEffect(() => {
+    setDetailed(value === 'full');
+  }, [value, setDetailed]);
+};
+
+export default useUpdateAckeeOptions;
diff --git a/src/utils/providers/prism-theme.tsx b/src/utils/providers/prism-theme.tsx
index 62bcf56..23a2a36 100644
--- a/src/utils/providers/prism-theme.tsx
+++ b/src/utils/providers/prism-theme.tsx
@@ -55,7 +55,7 @@ const isValidTheme = (theme: string): boolean => {
 
 const getTheme = (key: string): PrismTheme | undefined => {
   if (typeof window === 'undefined') return undefined;
-  const storageValue = LocalStorage.get(key);
+  const storageValue = LocalStorage.get<string>(key);
 
   return storageValue && isValidTheme(storageValue)
     ? (storageValue as PrismTheme)
-- 
cgit v1.2.3

