From 581632a626f81bce522be1cd809e9832d5b11c99 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Thu, 31 Mar 2022 18:21:30 +0200
Subject: chore: add a Label component

---
 src/components/atoms/forms/label.test.tsx | 9 +++++++++
 1 file changed, 9 insertions(+)
 create mode 100644 src/components/atoms/forms/label.test.tsx

(limited to 'src/components/atoms/forms/label.test.tsx')

diff --git a/src/components/atoms/forms/label.test.tsx b/src/components/atoms/forms/label.test.tsx
new file mode 100644
index 0000000..fcf1731
--- /dev/null
+++ b/src/components/atoms/forms/label.test.tsx
@@ -0,0 +1,9 @@
+import { render, screen } from '@test-utils';
+import Label from './label';
+
+describe('Label', () => {
+  it('renders a field label', () => {
+    render(<Label htmlFor="a-field-id">A label</Label>);
+    expect(screen.getByText('A label')).toBeDefined();
+  });
+});
-- 
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/atoms/forms/label.test.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

