diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-04-08 22:36:24 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-04-08 23:31:58 +0200 |
| commit | 0b3146f7278929c4d1b33dd8f94f34e351e5e5a9 (patch) | |
| tree | 6a784b197a283a7da07c2e1df80a29fee8b3790a /src/components/molecules/forms | |
| parent | 61278678ea8a8febee0574cd0f6006492d7b15cb (diff) | |
chore: add a Settings modal component
Diffstat (limited to 'src/components/molecules/forms')
15 files changed, 344 insertions, 65 deletions
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} /> ); }; |
