From 782cc0a31a866519fb7c3e18a523b347d3e40238 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Mon, 30 May 2022 19:27:21 +0200
Subject: chore: replace Checkbox component with a BooleanField component

Checkbox and radio buttons are working the same way so I decided to
group them in a same component.
---
 src/components/molecules/forms/toggle.tsx | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

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

diff --git a/src/components/molecules/forms/toggle.tsx b/src/components/molecules/forms/toggle.tsx
index 0fac45c..2f3e778 100644
--- a/src/components/molecules/forms/toggle.tsx
+++ b/src/components/molecules/forms/toggle.tsx
@@ -1,4 +1,6 @@
-import Checkbox, { type CheckboxProps } from '@components/atoms/forms/checkbox';
+import BooleanField, {
+  type BooleanFieldProps,
+} from '@components/atoms/forms/boolean-field';
 import Label, { type LabelProps } from '@components/atoms/forms/label';
 import { FC, ReactNode } from 'react';
 import styles from './toggle.module.scss';
@@ -14,7 +16,7 @@ export type ToggleChoices = {
   right: ReactNode;
 };
 
-export type ToggleProps = Pick<CheckboxProps, 'id' | 'name'> & {
+export type ToggleProps = Pick<BooleanFieldProps, 'id' | 'name'> & {
   /**
    * The toggle choices.
    */
@@ -63,12 +65,13 @@ const Toggle: FC<ToggleProps> = ({
 }) => {
   return (
     <>
-      <Checkbox
-        name={name}
-        id={id}
-        value={value}
-        setValue={() => setValue(!value)}
+      <BooleanField
+        checked={value}
         className={styles.checkbox}
+        id={id}
+        name={name}
+        onChange={() => setValue(!value)}
+        type="checkbox"
       />
       <Label
         size={labelSize}
-- 
cgit v1.2.3


From 8320b1d39ea6402c32e907dbb35082efc6af9f5a Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Tue, 31 May 2022 19:40:23 +0200
Subject: chore: replace the toggle component

---
 src/components/atoms/forms/fieldset.test.tsx       |   4 +-
 .../molecules/forms/ackee-select.fixture.tsx       |   1 +
 .../molecules/forms/ackee-select.stories.tsx       |   2 +
 .../molecules/forms/ackee-select.test.tsx          |   5 +-
 .../molecules/forms/motion-toggle.fixture.tsx      |   1 +
 .../molecules/forms/motion-toggle.stories.tsx      |  30 ++---
 .../molecules/forms/motion-toggle.test.tsx         |  12 +-
 src/components/molecules/forms/motion-toggle.tsx   |  82 ++++++++++---
 .../molecules/forms/prism-theme-toggle.stories.tsx |  17 +--
 .../molecules/forms/prism-theme-toggle.test.tsx    |   4 +-
 .../molecules/forms/prism-theme-toggle.tsx         |  81 +++++++++----
 .../molecules/forms/radio-group.module.scss        | 111 ++++++++++++++++-
 .../molecules/forms/radio-group.stories.tsx        |  54 +++++++++
 src/components/molecules/forms/radio-group.tsx     |  81 +++++++++++--
 .../molecules/forms/theme-toggle.stories.tsx       |  17 +--
 .../molecules/forms/theme-toggle.test.tsx          |   4 +-
 src/components/molecules/forms/theme-toggle.tsx    |  73 +++++++----
 src/components/molecules/forms/toggle.module.scss  |  75 ------------
 src/components/molecules/forms/toggle.stories.tsx  | 134 ---------------------
 src/components/molecules/forms/toggle.test.tsx     |  29 -----
 src/components/molecules/forms/toggle.tsx          |  90 --------------
 .../organisms/forms/settings-form.module.scss      |  69 +++++++++--
 .../organisms/forms/settings-form.stories.tsx      |   6 +
 .../organisms/forms/settings-form.test.tsx         |   6 +-
 src/components/organisms/forms/settings-form.tsx   |  21 ++--
 .../organisms/toolbar/toolbar.module.scss          |   2 +-
 src/utils/hooks/use-state-change.tsx               |  19 +++
 27 files changed, 534 insertions(+), 496 deletions(-)
 create mode 100644 src/components/molecules/forms/ackee-select.fixture.tsx
 create mode 100644 src/components/molecules/forms/motion-toggle.fixture.tsx
 delete mode 100644 src/components/molecules/forms/toggle.module.scss
 delete mode 100644 src/components/molecules/forms/toggle.stories.tsx
 delete mode 100644 src/components/molecules/forms/toggle.test.tsx
 delete mode 100644 src/components/molecules/forms/toggle.tsx
 create mode 100644 src/utils/hooks/use-state-change.tsx

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

diff --git a/src/components/atoms/forms/fieldset.test.tsx b/src/components/atoms/forms/fieldset.test.tsx
index 0f84f83..1d1d246 100644
--- a/src/components/atoms/forms/fieldset.test.tsx
+++ b/src/components/atoms/forms/fieldset.test.tsx
@@ -5,7 +5,7 @@ import { body, legend } from './fieldset.fixture';
 describe('Fieldset', () => {
   it('renders a legend and a body', () => {
     render(<Fieldset legend={legend}>{body}</Fieldset>);
-    expect(screen.findByRole('group', { name: legend })).toBeInTheDocument();
-    expect(screen.findByText(body)).toBeInTheDocument();
+    expect(screen.findByRole('group', { name: legend })).toBeTruthy();
+    expect(screen.findByText(body)).toBeTruthy();
   });
 });
diff --git a/src/components/molecules/forms/ackee-select.fixture.tsx b/src/components/molecules/forms/ackee-select.fixture.tsx
new file mode 100644
index 0000000..04602f2
--- /dev/null
+++ b/src/components/molecules/forms/ackee-select.fixture.tsx
@@ -0,0 +1 @@
+export const storageKey = 'ackee';
diff --git a/src/components/molecules/forms/ackee-select.stories.tsx b/src/components/molecules/forms/ackee-select.stories.tsx
index 81eb5df..f8d04f6 100644
--- a/src/components/molecules/forms/ackee-select.stories.tsx
+++ b/src/components/molecules/forms/ackee-select.stories.tsx
@@ -1,5 +1,6 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
 import AckeeSelect from './ackee-select';
+import { storageKey } from './ackee-select.fixture';
 
 /**
  * AckeeSelect - Storybook Meta
@@ -81,4 +82,5 @@ const Template: ComponentStory<typeof AckeeSelect> = (args) => (
 export const Ackee = Template.bind({});
 Ackee.args = {
   initialValue: 'full',
+  storageKey,
 };
diff --git a/src/components/molecules/forms/ackee-select.test.tsx b/src/components/molecules/forms/ackee-select.test.tsx
index 0089c06..d255b00 100644
--- a/src/components/molecules/forms/ackee-select.test.tsx
+++ b/src/components/molecules/forms/ackee-select.test.tsx
@@ -1,16 +1,17 @@
 import user from '@testing-library/user-event';
 import { act, render, screen } from '@test-utils';
 import AckeeSelect from './ackee-select';
+import { storageKey } from './ackee-select.fixture';
 
 describe('Select', () => {
   it('should correctly set default option', () => {
-    render(<AckeeSelect storageKey="ackee-tracking" initialValue="full" />);
+    render(<AckeeSelect storageKey={storageKey} 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 storageKey="ackee-tracking" initialValue="full" />);
+    render(<AckeeSelect storageKey={storageKey} initialValue="full" />);
 
     await act(async () => {
       await user.selectOptions(
diff --git a/src/components/molecules/forms/motion-toggle.fixture.tsx b/src/components/molecules/forms/motion-toggle.fixture.tsx
new file mode 100644
index 0000000..f13658a
--- /dev/null
+++ b/src/components/molecules/forms/motion-toggle.fixture.tsx
@@ -0,0 +1 @@
+export const storageKey = 'reduced-motion';
diff --git a/src/components/molecules/forms/motion-toggle.stories.tsx b/src/components/molecules/forms/motion-toggle.stories.tsx
index e9939bd..5c524a8 100644
--- a/src/components/molecules/forms/motion-toggle.stories.tsx
+++ b/src/components/molecules/forms/motion-toggle.stories.tsx
@@ -1,5 +1,6 @@
 import { ComponentMeta, ComponentStory } from '@storybook/react';
 import MotionToggleComponent from './motion-toggle';
+import { storageKey } from './motion-toggle.fixture';
 
 /**
  * MotionToggle - Storybook Meta
@@ -8,24 +9,22 @@ export default {
   title: 'Molecules/Forms/Toggle',
   component: MotionToggleComponent,
   argTypes: {
-    className: {
+    defaultValue: {
       control: {
-        type: 'text',
-      },
-      description: 'Set additional classnames to the toggle wrapper.',
-      table: {
-        category: 'Styles',
+        type: 'select',
       },
+      description: 'Set the default value.',
+      options: ['on', 'off'],
       type: {
         name: 'string',
-        required: false,
+        required: true,
       },
     },
-    labelClassName: {
+    legendClassName: {
       control: {
         type: 'text',
       },
-      description: 'Set additional classnames to the label wrapper.',
+      description: 'Set additional classnames to the legend.',
       table: {
         category: 'Styles',
       },
@@ -44,16 +43,6 @@ export default {
         required: true,
       },
     },
-    value: {
-      control: {
-        type: null,
-      },
-      description: 'The reduce motion value.',
-      type: {
-        name: 'boolean',
-        required: true,
-      },
-    },
   },
 } as ComponentMeta<typeof MotionToggleComponent>;
 
@@ -66,5 +55,6 @@ const Template: ComponentStory<typeof MotionToggleComponent> = (args) => (
  */
 export const Motion = Template.bind({});
 Motion.args = {
-  value: false,
+  defaultValue: 'on',
+  storageKey,
 };
diff --git a/src/components/molecules/forms/motion-toggle.test.tsx b/src/components/molecules/forms/motion-toggle.test.tsx
index 4fd6b31..04c22a9 100644
--- a/src/components/molecules/forms/motion-toggle.test.tsx
+++ b/src/components/molecules/forms/motion-toggle.test.tsx
@@ -1,13 +1,15 @@
 import { render, screen } from '@test-utils';
 import MotionToggle from './motion-toggle';
+import { storageKey } from './motion-toggle.fixture';
 
 describe('MotionToggle', () => {
-  it('renders a checked toggle (deactivate animations choice)', () => {
-    render(<MotionToggle storageKey="reduced-motion" value={true} />);
+  // toHaveValue received undefined. Maybe because of localStorage hook...
+  it('renders a toggle component', () => {
+    render(<MotionToggle storageKey={storageKey} defaultValue="on" />);
     expect(
-      screen.getByRole('checkbox', {
-        name: `Animations: On Off`,
+      screen.getByRole('radiogroup', {
+        name: /Animations:/i,
       })
-    ).toBeChecked();
+    ).toBeInTheDocument();
   });
 });
diff --git a/src/components/molecules/forms/motion-toggle.tsx b/src/components/molecules/forms/motion-toggle.tsx
index 55ff150..6925248 100644
--- a/src/components/molecules/forms/motion-toggle.tsx
+++ b/src/components/molecules/forms/motion-toggle.tsx
@@ -1,16 +1,24 @@
-import Toggle, {
-  type ToggleChoices,
-  type ToggleProps,
-} from '@components/molecules/forms/toggle';
 import useAttributes from '@utils/hooks/use-attributes';
 import useLocalStorage from '@utils/hooks/use-local-storage';
 import { FC } from 'react';
 import { useIntl } from 'react-intl';
+import RadioGroup, {
+  type RadioGroupCallback,
+  type RadioGroupCallbackProps,
+  type RadioGroupOption,
+  type RadioGroupProps,
+} from './radio-group';
+
+export type MotionToggleValue = 'on' | 'off';
 
 export type MotionToggleProps = Pick<
-  ToggleProps,
-  'className' | 'labelClassName' | 'value'
+  RadioGroupProps,
+  'groupClassName' | 'legendClassName'
 > & {
+  /**
+   * True if motion should be reduced by default.
+   */
+  defaultValue: 'on' | 'off';
   /**
    * The local storage key to save preference.
    */
@@ -23,14 +31,14 @@ export type MotionToggleProps = Pick<
  * Render a Toggle component to set reduce motion.
  */
 const MotionToggle: FC<MotionToggleProps> = ({
+  defaultValue,
   storageKey,
-  value,
   ...props
 }) => {
   const intl = useIntl();
   const { value: isReduced, setValue: setIsReduced } = useLocalStorage<boolean>(
     storageKey,
-    value
+    defaultValue === 'on' ? false : true
   );
   useAttributes({
     element: document.documentElement || undefined,
@@ -53,20 +61,56 @@ const MotionToggle: FC<MotionToggleProps> = ({
     description: 'MotionToggle: deactivate reduce motion label',
     id: 'pWKyyR',
   });
-  const reduceMotionChoices: ToggleChoices = {
-    left: onLabel,
-    right: offLabel,
+
+  const options: RadioGroupOption[] = [
+    {
+      id: 'reduced-motion-on',
+      label: onLabel,
+      name: 'reduced-motion',
+      value: 'on',
+    },
+    {
+      id: 'reduced-motion-off',
+      label: offLabel,
+      name: 'reduced-motion',
+      value: 'off',
+    },
+  ];
+
+  /**
+   * Update the current setting.
+   *
+   * @param {string} newValue - A boolean as string.
+   */
+  const updateSetting = (newValue: MotionToggleValue) => {
+    setIsReduced(newValue === 'on' ? false : true);
+  };
+
+  /**
+   * Handle change events.
+   *
+   * @param {RadioGroupCallbackProps} props - An object with choices.
+   */
+  const handleChange: RadioGroupCallback = ({
+    choices,
+    updateChoice,
+  }: RadioGroupCallbackProps) => {
+    if (choices.new === choices.prev) {
+      const newChoice = choices.new === 'on' ? 'off' : 'on';
+      updateChoice(newChoice);
+      updateSetting(newChoice);
+    } else {
+      updateSetting(choices.new as MotionToggleValue);
+    }
   };
 
   return (
-    <Toggle
-      id="reduce-motion-settings"
-      name="reduce-motion-settings"
-      label={reduceMotionLabel}
-      labelSize="medium"
-      choices={reduceMotionChoices}
-      value={isReduced}
-      setValue={setIsReduced}
+    <RadioGroup
+      initialChoice={defaultValue}
+      kind="toggle"
+      legend={reduceMotionLabel}
+      onChange={handleChange}
+      options={options}
       {...props}
     />
   );
diff --git a/src/components/molecules/forms/prism-theme-toggle.stories.tsx b/src/components/molecules/forms/prism-theme-toggle.stories.tsx
index 6a88e51..3f57fa5 100644
--- a/src/components/molecules/forms/prism-theme-toggle.stories.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.stories.tsx
@@ -8,24 +8,11 @@ export default {
   title: 'Molecules/Forms/Toggle',
   component: PrismThemeToggle,
   argTypes: {
-    className: {
+    legendClassName: {
       control: {
         type: 'text',
       },
-      description: 'Set additional classnames to the toggle wrapper.',
-      table: {
-        category: 'Styles',
-      },
-      type: {
-        name: 'string',
-        required: false,
-      },
-    },
-    labelClassName: {
-      control: {
-        type: 'text',
-      },
-      description: 'Set additional classnames to the label wrapper.',
+      description: 'Set additional classnames to the legend.',
       table: {
         category: 'Styles',
       },
diff --git a/src/components/molecules/forms/prism-theme-toggle.test.tsx b/src/components/molecules/forms/prism-theme-toggle.test.tsx
index c9d7894..91e8e2e 100644
--- a/src/components/molecules/forms/prism-theme-toggle.test.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.test.tsx
@@ -5,8 +5,8 @@ describe('PrismThemeToggle', () => {
   it('renders a toggle component', () => {
     render(<PrismThemeToggle />);
     expect(
-      screen.getByRole('checkbox', {
-        name: `Code blocks: Light theme Dark theme`,
+      screen.getByRole('radiogroup', {
+        name: /Code blocks:/i,
       })
     ).toBeInTheDocument();
   });
diff --git a/src/components/molecules/forms/prism-theme-toggle.tsx b/src/components/molecules/forms/prism-theme-toggle.tsx
index e0b795f..66be056 100644
--- a/src/components/molecules/forms/prism-theme-toggle.tsx
+++ b/src/components/molecules/forms/prism-theme-toggle.tsx
@@ -1,16 +1,18 @@
 import Moon from '@components/atoms/icons/moon';
 import Sun from '@components/atoms/icons/sun';
-import Toggle, {
-  type ToggleChoices,
-  type ToggleProps,
-} from '@components/molecules/forms/toggle';
-import { usePrismTheme } from '@utils/providers/prism-theme';
+import { type PrismTheme, usePrismTheme } from '@utils/providers/prism-theme';
 import { FC } from 'react';
 import { useIntl } from 'react-intl';
+import RadioGroup, {
+  type RadioGroupCallback,
+  type RadioGroupCallbackProps,
+  type RadioGroupOption,
+  type RadioGroupProps,
+} from './radio-group';
 
 export type PrismThemeToggleProps = Pick<
-  ToggleProps,
-  'className' | 'labelClassName'
+  RadioGroupProps,
+  'groupClassName' | 'legendClassName'
 >;
 
 /**
@@ -18,7 +20,7 @@ export type PrismThemeToggleProps = Pick<
  *
  * Render a Toggle component to set code blocks theme.
  */
-const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ ...props }) => {
+const PrismThemeToggle: FC<PrismThemeToggleProps> = (props) => {
   const intl = useIntl();
   const { theme, setTheme, resolvedTheme } = usePrismTheme();
 
@@ -27,16 +29,36 @@ const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ ...props }) => {
    *
    * @returns {boolean} True if it is dark theme.
    */
-  const isDarkTheme = (): boolean => {
-    if (theme === 'system') return resolvedTheme === 'dark';
-    return theme === 'dark';
+  const isDarkTheme = (prismTheme?: PrismTheme): boolean => {
+    if (prismTheme === 'system') return resolvedTheme === 'dark';
+    return prismTheme === 'dark';
   };
 
   /**
    * Update the theme.
+   *
+   * @param {string} newTheme - A theme name.
+   */
+  const updateTheme = (newTheme: string) => {
+    setTheme(newTheme === 'light' ? 'light' : 'dark');
+  };
+
+  /**
+   * Handle change events.
+   *
+   * @param {RadioGroupCallbackProps} props - An object with choices.
    */
-  const updateTheme = () => {
-    setTheme(isDarkTheme() ? 'light' : 'dark');
+  const handleChange: RadioGroupCallback = ({
+    choices,
+    updateChoice,
+  }: RadioGroupCallbackProps) => {
+    if (choices.new === choices.prev) {
+      const newTheme = choices.new === 'light' ? 'dark' : 'light';
+      updateChoice(newTheme);
+      updateTheme(newTheme);
+    } else {
+      updateTheme(choices.new);
+    }
   };
 
   const themeLabel = intl.formatMessage({
@@ -54,20 +76,29 @@ const PrismThemeToggle: FC<PrismThemeToggleProps> = ({ ...props }) => {
     description: 'PrismThemeToggle: dark theme label',
     id: 'og/zWL',
   });
-  const themeChoices: ToggleChoices = {
-    left: <Sun title={lightThemeLabel} />,
-    right: <Moon title={darkThemeLabel} />,
-  };
+
+  const options: RadioGroupOption[] = [
+    {
+      id: 'code-blocks-light',
+      label: <Sun title={lightThemeLabel} />,
+      name: 'code-blocks',
+      value: 'light',
+    },
+    {
+      id: 'code-blocks-dark',
+      label: <Moon title={darkThemeLabel} />,
+      name: 'code-blocks',
+      value: 'dark',
+    },
+  ];
 
   return (
-    <Toggle
-      id="prism-theme-settings"
-      name="prism-theme-settings"
-      label={themeLabel}
-      labelSize="medium"
-      choices={themeChoices}
-      value={isDarkTheme()}
-      setValue={updateTheme}
+    <RadioGroup
+      initialChoice={isDarkTheme(theme) ? 'dark' : 'light'}
+      kind="toggle"
+      legend={themeLabel}
+      onChange={handleChange}
+      options={options}
       {...props}
     />
   );
diff --git a/src/components/molecules/forms/radio-group.module.scss b/src/components/molecules/forms/radio-group.module.scss
index feda9bd..0bd34b9 100644
--- a/src/components/molecules/forms/radio-group.module.scss
+++ b/src/components/molecules/forms/radio-group.module.scss
@@ -1,13 +1,112 @@
-.option {
-  &:not(:last-of-type) {
-    margin-right: var(--spacing-xs);
-  }
-}
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
 
 .wrapper {
-  &--inline {
+  &--inline#{&}--regular {
     .option:first-of-type {
       margin-left: var(--spacing-2xs);
     }
   }
+
+  &--regular {
+    .option {
+      &:not(:last-of-type) {
+        margin-right: var(--spacing-xs);
+      }
+    }
+  }
+}
+
+.toggle {
+  display: inline-flex;
+  flex-flow: row wrap;
+  align-items: center;
+  width: fit-content;
+  position: relative;
+  background: var(--color-shadow-light);
+  border: fun.convert-px(2) solid var(--color-primary);
+  border-radius: fun.convert-px(32);
+  box-shadow: inset 0 0 fun.convert-px(1) fun.convert-px(1)
+      var(--color-shadow-dark),
+    inset 0 0 fun.convert-px(3) fun.convert-px(2) var(--color-shadow);
+
+  .label {
+    display: flex;
+    align-items: center;
+    min-height: 5ex;
+    padding: fun.convert-px(6) var(--spacing-2xs);
+    border-top: fun.convert-px(2) solid var(--color-border);
+    border-bottom: fun.convert-px(2) solid var(--color-border);
+    transition: all 0.15s linear 0s;
+
+    @include mix.pointer("fine") {
+      min-height: 3ex;
+    }
+  }
+
+  &:focus-within {
+    outline: fun.convert-px(2) solid var(--color-primary-light);
+  }
+
+  .option:first-of-type {
+    .label {
+      border-left: fun.convert-px(2) solid var(--color-border);
+      border-top-left-radius: fun.convert-px(32);
+      border-bottom-left-radius: fun.convert-px(32);
+    }
+  }
+
+  .option:last-of-type {
+    .label {
+      border-right: fun.convert-px(2) solid var(--color-border);
+      border-top-right-radius: fun.convert-px(32);
+      border-bottom-right-radius: fun.convert-px(32);
+    }
+  }
+
+  .radio {
+    &:checked + .label {
+      background: var(--color-primary);
+      box-shadow: inset 0 0 fun.convert-px(1) fun.convert-px(2)
+          var(--color-primary-dark),
+        inset 0 0 fun.convert-px(3) fun.convert-px(2)
+          var(--color-primary-darker);
+      color: var(--color-fg-inverted);
+
+      svg {
+        fill: var(--color-fg-inverted);
+        stroke: var(--color-fg-inverted);
+      }
+    }
+
+    &:not(:checked) + .label {
+      svg {
+        fill: var(--color-primary-darker);
+      }
+    }
+
+    &:checked + .label:hover {
+      background: var(--color-primary-lighter);
+      box-shadow: inset 0 0 fun.convert-px(1) fun.convert-px(2)
+          var(--color-primary-light),
+        inset 0 0 fun.convert-px(3) fun.convert-px(2) var(--color-primary);
+    }
+
+    &:not(:checked) + .label:hover {
+      background: var(--color-shadow-light);
+      box-shadow: inset 0 0 0 fun.convert-px(1) var(--color-shadow-dark),
+        inset 0 0 fun.convert-px(3) fun.convert-px(2) var(--color-shadow);
+    }
+
+    &:not(:checked):focus + .label {
+      background: var(--color-shadow-light);
+    }
+
+    &:checked:focus + .label {
+      background: var(--color-primary-lighter);
+      box-shadow: inset 0 0 fun.convert-px(1) fun.convert-px(2)
+          var(--color-primary-light),
+        inset 0 0 fun.convert-px(3) fun.convert-px(2) var(--color-primary);
+    }
+  }
 }
diff --git a/src/components/molecules/forms/radio-group.stories.tsx b/src/components/molecules/forms/radio-group.stories.tsx
index b4c913a..3c01af5 100644
--- a/src/components/molecules/forms/radio-group.stories.tsx
+++ b/src/components/molecules/forms/radio-group.stories.tsx
@@ -9,6 +9,7 @@ export default {
   title: 'Molecules/Forms/RadioGroup',
   component: RadioGroup,
   args: {
+    kind: 'regular',
     labelSize: 'small',
   },
   argTypes: {
@@ -35,6 +36,21 @@ export default {
         required: true,
       },
     },
+    kind: {
+      control: {
+        type: 'select',
+      },
+      description: 'The radio group kind.',
+      options: ['regular', 'toggle'],
+      table: {
+        category: 'Options',
+        defaultValue: { summary: 'regular' },
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
     labelPosition: {
       control: {
         type: 'select',
@@ -102,6 +118,32 @@ export default {
         required: false,
       },
     },
+    onChange: {
+      control: {
+        type: null,
+      },
+      description: 'A callback function to handle selected option change.',
+      table: {
+        category: 'Events',
+      },
+      type: {
+        name: 'function',
+        required: false,
+      },
+    },
+    optionClassName: {
+      control: {
+        type: 'text',
+      },
+      description: 'Set additional classnames to the option wrapper.',
+      table: {
+        category: 'Styles',
+      },
+      type: {
+        name: 'string',
+        required: false,
+      },
+    },
     options: {
       description: 'An array of radio option object.',
       type: {
@@ -164,3 +206,15 @@ StackedLegendRightLabel.args = {
   legendPosition: 'stacked',
   options: getOptions('group4'),
 };
+
+/**
+ * Radio Group Stories - Toggle
+ */
+export const Toggle = Template.bind({});
+Toggle.args = {
+  initialChoice: initialChoice,
+  kind: 'toggle',
+  labelPosition: 'right',
+  legend: legend,
+  options: getOptions('group5'),
+};
diff --git a/src/components/molecules/forms/radio-group.tsx b/src/components/molecules/forms/radio-group.tsx
index 68a8adf..45f585e 100644
--- a/src/components/molecules/forms/radio-group.tsx
+++ b/src/components/molecules/forms/radio-group.tsx
@@ -1,10 +1,21 @@
 import Fieldset, { type FieldsetProps } from '@components/atoms/forms/fieldset';
-import { ChangeEvent, FC, useState } from 'react';
+import useStateChange from '@utils/hooks/use-state-change';
+import { ChangeEvent, FC, MouseEvent, SetStateAction, useState } from 'react';
 import LabelledBooleanField, {
   type LabelledBooleanFieldProps,
 } from './labelled-boolean-field';
 import styles from './radio-group.module.scss';
 
+export type RadioGroupCallbackProps = {
+  choices: {
+    new: string;
+    prev: string;
+  };
+  updateChoice: (value: SetStateAction<string>) => void;
+};
+
+export type RadioGroupCallback = (props: RadioGroupCallbackProps) => void;
+
 export type RadioGroupOption = Pick<
   LabelledBooleanFieldProps,
   'id' | 'label' | 'name' | 'value'
@@ -15,14 +26,34 @@ export type RadioGroupProps = Pick<
   'className' | 'legend' | 'legendClassName'
 > &
   Pick<LabelledBooleanFieldProps, 'labelPosition' | 'labelSize'> & {
+    /**
+     * Set additional classnames to the radio group wrapper when kind is toggle.
+     */
+    groupClassName?: string;
     /**
      * The default option value.
      */
     initialChoice: string;
+    /**
+     * The radio group kind. Default: regular.
+     */
+    kind?: 'regular' | 'toggle';
     /**
      * The legend position. Default: inline.
      */
     legendPosition?: FieldsetProps['legendPosition'];
+    /**
+     * A callback function to execute when choice is changed.
+     */
+    onChange?: RadioGroupCallback;
+    /**
+     * A callback function to execute when clicking on a choice.
+     */
+    onClick?: RadioGroupCallback;
+    /**
+     * Set additional classnames to the labelled field wrapper.
+     */
+    optionClassName?: string;
     /**
      * The options.
      */
@@ -36,23 +67,38 @@ export type RadioGroupProps = Pick<
  */
 const RadioGroup: FC<RadioGroupProps> = ({
   className,
+  groupClassName = '',
   initialChoice,
+  kind = 'regular',
   labelPosition,
   labelSize,
   legendPosition = 'inline',
+  onChange,
+  optionClassName = '',
   options,
   ...props
 }) => {
-  const [selectedChoice, setSelectedChoice] = useState<string>(initialChoice);
-  const wrapperModifier = `wrapper--${legendPosition}`;
+  const [selectedChoice, setSelectedChoice] =
+    useStateChange<string>(initialChoice);
+  const isToggle = kind === 'toggle';
+  const alignmentModifier = `wrapper--${legendPosition}`;
+  const toggleModifier = isToggle ? 'wrapper--toggle' : 'wrapper--regular';
 
   /**
-   * Update the selected choice based on the change event target.
-   *
-   * @param {ChangeEvent<HTMLInputElement>} e - The change event.
+   * Update the selected choice on click or change event.
    */
-  const updateChoice = (e: ChangeEvent<HTMLInputElement>) => {
-    setSelectedChoice(e.target.value);
+  const updateChoice = (
+    e:
+      | ChangeEvent<HTMLInputElement>
+      | MouseEvent<HTMLInputElement, globalThis.MouseEvent>
+  ) => {
+    const input = e.target as HTMLInputElement;
+    onChange &&
+      onChange({
+        choices: { new: input.value, prev: selectedChoice },
+        updateChoice: setSelectedChoice,
+      });
+    if (e.type === 'change') setSelectedChoice(input.value);
   };
 
   /**
@@ -65,10 +111,14 @@ const RadioGroup: FC<RadioGroupProps> = ({
       <LabelledBooleanField
         key={option.id}
         checked={selectedChoice === option.value}
-        className={styles.option}
-        labelPosition={labelPosition}
+        className={`${styles.option} ${optionClassName}`}
+        fieldClassName={styles.radio}
+        hidden={isToggle}
+        labelClassName={styles.label}
+        labelPosition={kind === 'toggle' ? 'right' : labelPosition}
         labelSize={labelSize}
         onChange={updateChoice}
+        onClick={updateChoice}
         type="radio"
         {...option}
       />
@@ -77,11 +127,18 @@ const RadioGroup: FC<RadioGroupProps> = ({
 
   return (
     <Fieldset
-      className={`${styles.wrapper} ${styles[wrapperModifier]} ${className}`}
+      className={`${styles.wrapper} ${styles[alignmentModifier]} ${styles[toggleModifier]} ${className}`}
       legendPosition={legendPosition}
+      role="radiogroup"
       {...props}
     >
-      {getOptions()}
+      {isToggle ? (
+        <span className={`${styles.toggle} ${groupClassName}`}>
+          {getOptions()}
+        </span>
+      ) : (
+        getOptions()
+      )}
     </Fieldset>
   );
 };
diff --git a/src/components/molecules/forms/theme-toggle.stories.tsx b/src/components/molecules/forms/theme-toggle.stories.tsx
index a7bebb4..cd59d7e 100644
--- a/src/components/molecules/forms/theme-toggle.stories.tsx
+++ b/src/components/molecules/forms/theme-toggle.stories.tsx
@@ -8,24 +8,11 @@ export default {
   title: 'Molecules/Forms/Toggle',
   component: ThemeToggle,
   argTypes: {
-    className: {
+    legendClassName: {
       control: {
         type: 'text',
       },
-      description: 'Set additional classnames to the toggle wrapper.',
-      table: {
-        category: 'Styles',
-      },
-      type: {
-        name: 'string',
-        required: false,
-      },
-    },
-    labelClassName: {
-      control: {
-        type: 'text',
-      },
-      description: 'Set additional classnames to the label wrapper.',
+      description: 'Set additional classnames to the legend.',
       table: {
         category: 'Styles',
       },
diff --git a/src/components/molecules/forms/theme-toggle.test.tsx b/src/components/molecules/forms/theme-toggle.test.tsx
index 0600c5e..ed8b312 100644
--- a/src/components/molecules/forms/theme-toggle.test.tsx
+++ b/src/components/molecules/forms/theme-toggle.test.tsx
@@ -5,8 +5,8 @@ describe('ThemeToggle', () => {
   it('renders a toggle component', () => {
     render(<ThemeToggle />);
     expect(
-      screen.getByRole('checkbox', {
-        name: `Theme: Light theme Dark theme`,
+      screen.getByRole('radiogroup', {
+        name: /Theme:/i,
       })
     ).toBeInTheDocument();
   });
diff --git a/src/components/molecules/forms/theme-toggle.tsx b/src/components/molecules/forms/theme-toggle.tsx
index e9dd5e4..30bc55c 100644
--- a/src/components/molecules/forms/theme-toggle.tsx
+++ b/src/components/molecules/forms/theme-toggle.tsx
@@ -1,16 +1,18 @@
 import Moon from '@components/atoms/icons/moon';
 import Sun from '@components/atoms/icons/sun';
-import Toggle, {
-  type ToggleChoices,
-  type ToggleProps,
-} from '@components/molecules/forms/toggle';
 import { useTheme } from 'next-themes';
 import { FC } from 'react';
 import { useIntl } from 'react-intl';
+import RadioGroup, {
+  type RadioGroupCallback,
+  type RadioGroupCallbackProps,
+  type RadioGroupOption,
+  type RadioGroupProps,
+} from './radio-group';
 
 export type ThemeToggleProps = Pick<
-  ToggleProps,
-  'className' | 'labelClassName'
+  RadioGroupProps,
+  'groupClassName' | 'legendClassName'
 >;
 
 /**
@@ -18,16 +20,36 @@ export type ThemeToggleProps = Pick<
  *
  * Render a Toggle component to set theme.
  */
-const ThemeToggle: FC<ThemeToggleProps> = ({ ...props }) => {
+const ThemeToggle: FC<ThemeToggleProps> = (props) => {
   const intl = useIntl();
   const { resolvedTheme, setTheme } = useTheme();
   const isDarkTheme = resolvedTheme === 'dark';
 
   /**
    * Update the theme.
+   *
+   * @param {string} theme - A theme name.
    */
-  const updateTheme = () => {
-    setTheme(isDarkTheme ? 'light' : 'dark');
+  const updateTheme = (theme: string) => {
+    setTheme(theme === 'light' ? 'light' : 'dark');
+  };
+
+  /**
+   * Handle change events.
+   *
+   * @param {RadioGroupCallbackProps} props - An object with choices.
+   */
+  const handleChange: RadioGroupCallback = ({
+    choices,
+    updateChoice,
+  }: RadioGroupCallbackProps) => {
+    if (choices.new === choices.prev) {
+      const newTheme = choices.new === 'light' ? 'dark' : 'light';
+      updateChoice(newTheme);
+      updateTheme(newTheme);
+    } else {
+      updateTheme(choices.new);
+    }
   };
 
   const themeLabel = intl.formatMessage({
@@ -45,20 +67,29 @@ const ThemeToggle: FC<ThemeToggleProps> = ({ ...props }) => {
     description: 'ThemeToggle: dark theme label',
     id: '2QwvtS',
   });
-  const themeChoices: ToggleChoices = {
-    left: <Sun title={lightThemeLabel} />,
-    right: <Moon title={darkThemeLabel} />,
-  };
+
+  const options: RadioGroupOption[] = [
+    {
+      id: 'theme-light',
+      label: <Sun title={lightThemeLabel} />,
+      name: 'theme',
+      value: 'light',
+    },
+    {
+      id: 'theme-dark',
+      label: <Moon title={darkThemeLabel} />,
+      name: 'theme',
+      value: 'dark',
+    },
+  ];
 
   return (
-    <Toggle
-      id="theme-settings"
-      name="theme-settings"
-      label={themeLabel}
-      labelSize="medium"
-      choices={themeChoices}
-      value={isDarkTheme}
-      setValue={updateTheme}
+    <RadioGroup
+      initialChoice={isDarkTheme ? 'dark' : 'light'}
+      kind="toggle"
+      legend={themeLabel}
+      onChange={handleChange}
+      options={options}
       {...props}
     />
   );
diff --git a/src/components/molecules/forms/toggle.module.scss b/src/components/molecules/forms/toggle.module.scss
deleted file mode 100644
index 2e8a49f..0000000
--- a/src/components/molecules/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/molecules/forms/toggle.stories.tsx b/src/components/molecules/forms/toggle.stories.tsx
deleted file mode 100644
index f1b8296..0000000
--- a/src/components/molecules/forms/toggle.stories.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { useState } from 'react';
-import Toggle from './toggle';
-
-/**
- * ThemeToggle - Storybook Meta
- */
-export default {
-  title: 'Molecules/Forms/Toggle',
-  component: Toggle,
-  argTypes: {
-    choices: {
-      description: 'The toggle choices.',
-      type: {
-        name: 'object',
-        required: true,
-        value: {},
-      },
-    },
-    className: {
-      control: {
-        type: 'text',
-      },
-      description: 'Set additional classnames to the toggle wrapper.',
-      table: {
-        category: 'Styles',
-      },
-      type: {
-        name: 'string',
-        required: false,
-      },
-    },
-    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 Toggle>;
-
-const Template: ComponentStory<typeof Toggle> = ({
-  value: _value,
-  setValue: _setValue,
-  ...args
-}) => {
-  const [isChecked, setIsChecked] = useState<boolean>(false);
-  return <Toggle value={isChecked} setValue={setIsChecked} {...args} />;
-};
-
-/**
- * Toggle Stories - Default
- */
-export const Default = Template.bind({});
-Default.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
deleted file mode 100644
index fb97adc..0000000
--- a/src/components/molecules/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/molecules/forms/toggle.tsx b/src/components/molecules/forms/toggle.tsx
deleted file mode 100644
index 2f3e778..0000000
--- a/src/components/molecules/forms/toggle.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import BooleanField, {
-  type BooleanFieldProps,
-} from '@components/atoms/forms/boolean-field';
-import Label, { type LabelProps } from '@components/atoms/forms/label';
-import { FC, ReactNode } 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 = Pick<BooleanFieldProps, 'id' | 'name'> & {
-  /**
-   * The toggle choices.
-   */
-  choices: ToggleChoices;
-  /**
-   * Set additional classnames to the toggle wrapper.
-   */
-  className?: string;
-  /**
-   * The toggle label.
-   */
-  label: string;
-  /**
-   * Set additional classnames to the label.
-   */
-  labelClassName?: LabelProps['className'];
-  /**
-   * The label size.
-   */
-  labelSize?: LabelProps['size'];
-  /**
-   * 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: FC<ToggleProps> = ({
-  choices,
-  className = '',
-  id,
-  label,
-  labelClassName = '',
-  labelSize,
-  name,
-  setValue,
-  value,
-}) => {
-  return (
-    <>
-      <BooleanField
-        checked={value}
-        className={styles.checkbox}
-        id={id}
-        name={name}
-        onChange={() => setValue(!value)}
-        type="checkbox"
-      />
-      <Label
-        size={labelSize}
-        htmlFor={id}
-        className={`${styles.label} ${className}`}
-      >
-        <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/organisms/forms/settings-form.module.scss b/src/components/organisms/forms/settings-form.module.scss
index a05c60c..6174304 100644
--- a/src/components/organisms/forms/settings-form.module.scss
+++ b/src/components/organisms/forms/settings-form.module.scss
@@ -1,26 +1,77 @@
 @use "@styles/abstracts/mixins" as mix;
 
-.label {
-  margin-right: auto;
-}
+.wrapper {
+  display: flex;
+  flex-flow: row wrap;
+  align-items: flex-start;
+  align-content: flex-start;
 
-.setting,
-.label--select {
   @include mix.media("screen") {
-    @include mix.dimensions(null, "2xs") {
+    @include mix.dimensions(null, "2xs", "height") {
+      gap: var(--spacing-md);
       font-size: var(--font-size-sm);
     }
+  }
 
-    @include mix.dimensions(null, "2xs", "height") {
-      font-size: var(--font-size-sm);
+  .label {
+    @include mix.media("screen") {
+      @include mix.dimensions(null, "2xs", "height") {
+        font-size: var(--font-size-sm);
+
+        &:not(.label--select) {
+          float: none;
+          margin: 0 auto;
+        }
+      }
+    }
+
+    &.label--select {
+      @include mix.media("screen") {
+        @include mix.dimensions("2xs", null, "height") {
+          margin: 0 auto 0 0;
+        }
+      }
+    }
+  }
+
+  .tooltip {
+    @include mix.media("screen") {
+      @include mix.dimensions(null, "2xs") {
+        font-size: var(--font-size-sm);
+      }
     }
   }
 }
 
 .items {
+  margin: var(--spacing-2xs) 0;
+
+  @include mix.media("screen") {
+    @include mix.dimensions(null, "2xs", "height") {
+      display: flex;
+      flex-flow: column wrap;
+      width: fit-content;
+
+      &:last-of-type {
+        flex: 0 0 100%;
+        margin: 0;
+      }
+    }
+  }
+}
+
+.setting {
+  &--select {
+    flex: 0 0 100%;
+  }
+}
+
+.group {
+  margin-left: auto;
+
   @include mix.media("screen") {
     @include mix.dimensions(null, "2xs", "height") {
-      margin: var(--spacing-2xs) 0;
+      margin: auto;
     }
   }
 }
diff --git a/src/components/organisms/forms/settings-form.stories.tsx b/src/components/organisms/forms/settings-form.stories.tsx
index 70e1844..ceb08c7 100644
--- a/src/components/organisms/forms/settings-form.stories.tsx
+++ b/src/components/organisms/forms/settings-form.stories.tsx
@@ -1,3 +1,5 @@
+import { storageKey as ackeeStorageKey } from '@components/molecules/forms/ackee-select.fixture';
+import { storageKey as motionStorageKey } from '@components/molecules/forms/motion-toggle.fixture';
 import { ComponentMeta, ComponentStory } from '@storybook/react';
 import SettingsForm from './settings-form';
 
@@ -65,3 +67,7 @@ const Template: ComponentStory<typeof SettingsForm> = (args) => (
  * Form Stories - Settings
  */
 export const Settings = Template.bind({});
+Settings.args = {
+  ackeeStorageKey,
+  motionStorageKey,
+};
diff --git a/src/components/organisms/forms/settings-form.test.tsx b/src/components/organisms/forms/settings-form.test.tsx
index 43d546e..90a2751 100644
--- a/src/components/organisms/forms/settings-form.test.tsx
+++ b/src/components/organisms/forms/settings-form.test.tsx
@@ -25,7 +25,7 @@ describe('SettingsForm', () => {
       />
     );
     expect(
-      screen.getByRole('checkbox', { name: /^Theme:/i })
+      screen.getByRole('radiogroup', { name: /^Theme:/i })
     ).toBeInTheDocument();
   });
 
@@ -37,7 +37,7 @@ describe('SettingsForm', () => {
       />
     );
     expect(
-      screen.getByRole('checkbox', { name: /^Code blocks:/i })
+      screen.getByRole('radiogroup', { name: /^Code blocks:/i })
     ).toBeInTheDocument();
   });
 
@@ -49,7 +49,7 @@ describe('SettingsForm', () => {
       />
     );
     expect(
-      screen.getByRole('checkbox', { name: /^Animations:/i })
+      screen.getByRole('radiogroup', { name: /^Animations:/i })
     ).toBeInTheDocument();
   });
 
diff --git a/src/components/organisms/forms/settings-form.tsx b/src/components/organisms/forms/settings-form.tsx
index 9c2cd2c..9dc0e90 100644
--- a/src/components/organisms/forms/settings-form.tsx
+++ b/src/components/organisms/forms/settings-form.tsx
@@ -3,7 +3,7 @@ import AckeeSelect, {
   type AckeeSelectProps,
 } from '@components/molecules/forms/ackee-select';
 import MotionToggle, {
-  MotionToggleProps,
+  type MotionToggleProps,
 } from '@components/molecules/forms/motion-toggle';
 import PrismThemeToggle from '@components/molecules/forms/prism-theme-toggle';
 import ThemeToggle from '@components/molecules/forms/theme-toggle';
@@ -37,25 +37,28 @@ const SettingsForm: FC<SettingsFormProps> = ({
   return (
     <Form
       aria-label={ariaLabel}
+      className={styles.wrapper}
       itemsClassName={styles.items}
       onSubmit={() => null}
     >
-      <ThemeToggle className={styles.setting} labelClassName={styles.label} />
+      <ThemeToggle
+        groupClassName={styles.group}
+        legendClassName={styles.label}
+      />
       <PrismThemeToggle
-        className={styles.setting}
-        labelClassName={styles.label}
+        groupClassName={styles.group}
+        legendClassName={styles.label}
       />
       <MotionToggle
-        className={styles.setting}
-        labelClassName={styles.label}
+        defaultValue="on"
+        groupClassName={styles.group}
+        legendClassName={styles.label}
         storageKey={motionStorageKey}
-        value={false}
       />
       <AckeeSelect
-        className={styles.setting}
         initialValue="full"
         labelClassName={`${styles.label} ${styles['label--select']}`}
-        tooltipClassName={tooltipClassName}
+        tooltipClassName={`${styles.tooltip} ${tooltipClassName}`}
         storageKey={ackeeStorageKey}
       />
     </Form>
diff --git a/src/components/organisms/toolbar/toolbar.module.scss b/src/components/organisms/toolbar/toolbar.module.scss
index 4bcabcb..ca9cd33 100644
--- a/src/components/organisms/toolbar/toolbar.module.scss
+++ b/src/components/organisms/toolbar/toolbar.module.scss
@@ -40,7 +40,7 @@
     &--settings {
       @include mix.media("screen") {
         @include mix.dimensions("sm") {
-          min-width: 35ch;
+          min-width: 32ch;
         }
       }
     }
diff --git a/src/utils/hooks/use-state-change.tsx b/src/utils/hooks/use-state-change.tsx
new file mode 100644
index 0000000..063fb8e
--- /dev/null
+++ b/src/utils/hooks/use-state-change.tsx
@@ -0,0 +1,19 @@
+import { useEffect, useState } from 'react';
+
+/**
+ * Use React useState hook and update it if initial data change.
+ *
+ * @param initial - The initial value.
+ * @returns The state and a setter.
+ */
+const useStateChange = <T,>(initial: T) => {
+  const [state, setState] = useState<T>(initial);
+
+  useEffect(() => {
+    setState(initial);
+  }, [initial]);
+
+  return [state, setState] as const;
+};
+
+export default useStateChange;
-- 
cgit v1.2.3

