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