diff options
Diffstat (limited to 'src/components/molecules/forms')
3 files changed, 406 insertions, 219 deletions
diff --git a/src/components/molecules/forms/labelled-field/labelled-field.stories.tsx b/src/components/molecules/forms/labelled-field/labelled-field.stories.tsx index 1d1af70..47ded7b 100644 --- a/src/components/molecules/forms/labelled-field/labelled-field.stories.tsx +++ b/src/components/molecules/forms/labelled-field/labelled-field.stories.tsx @@ -1,130 +1,270 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import { type ChangeEvent, useState, useCallback } from 'react'; -import { Input, Label } from '../../../atoms'; +import type { Meta, StoryObj } from '@storybook/react'; +import { type ChangeEvent, useCallback, useState } from 'react'; +import { + Checkbox, + type CheckboxProps, + Radio, + type RadioProps, + Input, + type InputProps, + type TextAreaProps, + TextArea, + Label, +} from '../../../atoms'; +import { ControlledSelect } from '../../../atoms/forms/fields/select/select.stories'; import { LabelledField } from './labelled-field'; -/** - * LabelledField - Storybook Meta - */ -export default { - title: 'Molecules/Forms/Field', +const meta = { + title: 'Molecules/Forms/Labelled Field', component: LabelledField, - argTypes: { - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the field.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - field: { - control: { - type: null, - }, - description: 'A component: Checkbox, Input, Select, Radio or TextArea.', - type: { - name: 'function', - required: true, - }, - }, - label: { - control: { - type: null, - }, - description: 'A Label component.', - type: { - name: 'function', - required: true, - }, - }, - isInline: { - control: { - type: 'boolean', - }, - description: 'Should the label and the field be inlined?', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - isReversedOrder: { - control: { - type: 'boolean', - }, - description: 'Should the label and the field be reversed?', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - }, -} as ComponentMeta<typeof LabelledField>; - -const Template: ComponentStory<typeof LabelledField> = ({ ...args }) => { - const id = 'sunt'; - const [value, setValue] = useState<string>(''); - const updateValue = useCallback((e: ChangeEvent<HTMLInputElement>) => { +} satisfies Meta<typeof LabelledField>; + +export default meta; + +type Story = StoryObj<typeof meta>; + +const ControlledCheckbox = ({ + isChecked: checked = false, + ...args +}: CheckboxProps) => { + const [isChecked, setIsChecked] = useState<boolean>(checked); + + const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { + setIsChecked(e.target.checked); + }, []); + + return <Checkbox {...args} isChecked={isChecked} onChange={handleChange} />; +}; + +const ControlledInput = ({ value: defaultValue, ...args }: InputProps) => { + const [value, setValue] = useState(defaultValue); + + const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { + setValue(e.target.value); + }, []); + + return <Input {...args} onChange={handleChange} value={value} />; +}; + +const ControlledRadio = ({ + isChecked: checked = false, + ...args +}: RadioProps) => { + const [isChecked, setIsChecked] = useState<boolean>(checked); + + const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { + setIsChecked(e.target.checked); + }, []); + + return <Radio {...args} isChecked={isChecked} onChange={handleChange} />; +}; + +const ControlledTextArea = ({ + value: defaultValue, + ...args +}: TextAreaProps) => { + const [value, setValue] = useState(defaultValue); + + const handleChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => { setValue(e.target.value); }, []); - return ( - <LabelledField - {...args} - field={ - <Input - id={id} - name={id} - onChange={updateValue} - type="text" - value={value} - /> - } - label={<Label htmlFor={id}>A label</Label>} - /> - ); -}; - -/** - * Labelled Field Stories - Left - */ -export const Left = Template.bind({}); -Left.args = { - isInline: true, -}; - -/** - * Labelled Field Stories - Right - */ -export const Right = Template.bind({}); -Right.args = { - isInline: true, - isReversedOrder: true, -}; - -/** - * Labelled Field Stories - Top - */ -export const Top = Template.bind({}); -Top.args = {}; - -/** - * Labelled Field Stories - Bottom - */ -export const Bottom = Template.bind({}); -Bottom.args = { - isReversedOrder: true, + return <TextArea {...args} onChange={handleChange} value={value} />; +}; + +export const Default: Story = { + args: { + field: ( + <ControlledInput id="default-field" name="default-field" type="text" /> + ), + label: <Label>A field label</Label>, + }, +}; + +export const LabelledCheckbox: Story = { + name: 'Field: Checkbox', + args: { + ...Default.args, + field: ( + <ControlledCheckbox + id="checkbox-field" + name="checkbox-field" + value="checkbox-field" + /> + ), + }, +}; + +export const LabelledRadio: Story = { + name: 'Field: Radio', + args: { + ...Default.args, + field: ( + <ControlledRadio + id="radio-field" + name="radio-field" + value="radio-field" + /> + ), + }, +}; + +export const LabelledDateField: Story = { + name: 'Field: Date', + args: { + ...Default.args, + field: <ControlledInput id="date-field" name="date-field" type="date" />, + }, +}; + +export const LabelledDateTimeField: Story = { + name: 'Field: Datetime', + args: { + ...Default.args, + field: ( + <ControlledInput + id="datetime-field" + name="datetime-field" + type="datetime-local" + /> + ), + }, +}; + +export const LabelledEmailField: Story = { + name: 'Field: Email', + args: { + ...Default.args, + field: <ControlledInput id="email-field" name="email-field" type="email" />, + }, +}; + +export const LabelledMonthField: Story = { + name: 'Field: Month', + args: { + ...Default.args, + field: <ControlledInput id="month-field" name="month-field" type="month" />, + }, +}; + +export const LabelledNumberField: Story = { + name: 'Field: Number', + args: { + ...Default.args, + field: ( + <ControlledInput id="number-field" name="number-field" type="number" /> + ), + }, +}; + +export const LabelledPasswordField: Story = { + name: 'Field: Password', + args: { + ...Default.args, + field: ( + <ControlledInput + id="password-field" + name="password-field" + type="password" + /> + ), + }, +}; + +export const LabelledSearchField: Story = { + name: 'Field: Search', + args: { + ...Default.args, + field: ( + <ControlledInput id="search-field" name="search-field" type="search" /> + ), + }, +}; + +export const LabelledSelect: Story = { + name: 'Field: Select', + args: { + ...Default.args, + field: ( + <ControlledSelect + id="select-field" + name="select-field" + options={[]} + value="" + /> + ), + }, +}; + +export const LabelledTelField: Story = { + name: 'Field: Tel', + args: { + ...Default.args, + field: <ControlledInput id="tel-field" name="tel-field" type="tel" />, + }, +}; + +export const LabelledTextField: Story = { + name: 'Field: Text', + args: { + ...Default.args, + }, +}; + +export const LabelledTextArea: Story = { + name: 'Field: Textarea', + args: { + ...Default.args, + field: <ControlledTextArea id="textarea-field" name="textarea-field" />, + }, +}; + +export const LabelledTimeField: Story = { + name: 'Field: Time', + args: { + ...Default.args, + field: <ControlledInput id="time-field" name="time-field" type="time" />, + }, +}; + +export const LabelledUrlField: Story = { + name: 'Field: Url', + args: { + ...Default.args, + field: <ControlledInput id="url-field" name="url-field" type="url" />, + }, +}; + +export const LayoutColumn: Story = { + name: 'Layout: Column', + args: { + ...LabelledCheckbox.args, + isInline: false, + }, +}; + +export const LayoutReversedColumn: Story = { + name: 'Layout: Reversed column', + args: { + ...LabelledCheckbox.args, + isInline: false, + isReversedOrder: true, + }, +}; + +export const LayoutRow: Story = { + name: 'Layout: Row', + args: { + ...LabelledCheckbox.args, + isInline: true, + }, +}; + +export const LayoutReversedRow: Story = { + name: 'Layout: Reversed row', + args: { + ...LabelledCheckbox.args, + isInline: true, + isReversedOrder: true, + }, }; diff --git a/src/components/molecules/forms/radio-group/radio-group.stories.tsx b/src/components/molecules/forms/radio-group/radio-group.stories.tsx index 4b92c34..2917efb 100644 --- a/src/components/molecules/forms/radio-group/radio-group.stories.tsx +++ b/src/components/molecules/forms/radio-group/radio-group.stories.tsx @@ -1,55 +1,9 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { type ChangeEventHandler, useCallback, useState } from 'react'; import { Legend } from '../../../atoms'; -import { RadioGroup as RadioGroupComponent } from './radio-group'; -import { getOptions, initialChoice } from './radio-group.fixture'; +import { RadioGroup, type RadioGroupProps } from './radio-group'; -/** - * RadioGroup - Storybook Meta - */ -export default { - title: 'Molecules/Forms', - component: RadioGroupComponent, - args: {}, - argTypes: { - onChange: { - control: { - type: null, - }, - description: 'A callback function to handle selected option change.', - table: { - category: 'Events', - }, - type: { - name: 'function', - required: false, - }, - }, - options: { - description: 'An array of radio option object.', - type: { - name: 'object', - required: true, - value: {}, - }, - }, - value: { - control: { - type: 'text', - }, - description: 'The default selected option id.', - type: { - name: 'string', - required: true, - }, - }, - }, -} as ComponentMeta<typeof RadioGroupComponent>; - -const Template: ComponentStory<typeof RadioGroupComponent> = ({ - value, - ...args -}) => { +const ControlledRadioGroup = ({ value, ...props }: RadioGroupProps) => { const [selection, setSelection] = useState(value); const handleChange: ChangeEventHandler<HTMLInputElement> = useCallback( @@ -59,17 +13,83 @@ const Template: ComponentStory<typeof RadioGroupComponent> = ({ [] ); - return ( - <RadioGroupComponent {...args} onSwitch={handleChange} value={selection} /> - ); + return <RadioGroup {...props} onSwitch={handleChange} value={selection} />; }; -/** - * Radio Group Story - */ -export const RadioGroup = Template.bind({}); -RadioGroup.args = { - legend: <Legend>Options:</Legend>, - options: getOptions('group1'), - value: initialChoice, +const meta = { + title: 'Molecules/Forms/Radio Group', + component: RadioGroup, + render: ControlledRadioGroup, +} satisfies Meta<typeof RadioGroup>; + +export default meta; + +type Story = StoryObj<typeof meta>; + +export const Example: Story = { + args: { + legend: <Legend>Select your preferred option:</Legend>, + name: 'example', + options: [ + { id: 'example-1', label: 'Option 1', value: 'example-1' }, + { id: 'example-2', label: 'Option 2', value: 'example-2' }, + { id: 'example-3', label: 'Option 3', value: 'example-3' }, + ], + }, +}; + +export const Inline: Story = { + args: { + ...Example.args, + isInline: true, + name: 'inline', + options: [ + { id: 'inline-1', label: 'Option 1', value: 'inline-1' }, + { id: 'inline-2', label: 'Option 2', value: 'inline-2' }, + { id: 'inline-3', label: 'Option 3', value: 'inline-3' }, + ], + }, +}; + +export const DisabledGroup: Story = { + args: { + ...Example.args, + isDisabled: true, + name: 'disabled', + options: [ + { id: 'disabled-1', label: 'Option 1', value: 'disabled-1' }, + { id: 'disabled-2', label: 'Option 2', value: 'disabled-2' }, + { id: 'disabled-3', label: 'Option 3', value: 'disabled-3' }, + ], + }, +}; + +export const DisabledOption: Story = { + args: { + ...Example.args, + name: 'disabled-option', + options: [ + { id: 'option-1', label: 'Option 1', value: 'option-1' }, + { + id: 'option-2', + isDisabled: true, + label: 'Option 2', + value: 'option-2', + }, + { id: 'option-3', label: 'Option 3', value: 'option-3' }, + ], + }, +}; + +export const DefaultValue: Story = { + args: { + ...Example.args, + name: 'default-value', + options: [ + { id: 'value-1', label: 'Option 1', value: 'value-1' }, + { id: 'value-2', label: 'Option 2', value: 'value-2' }, + { id: 'value-3', label: 'Option 3', value: 'value-3' }, + ], + value: 'value-2', + }, }; diff --git a/src/components/molecules/forms/switch/switch.stories.tsx b/src/components/molecules/forms/switch/switch.stories.tsx index a88e6ab..9a59b83 100644 --- a/src/components/molecules/forms/switch/switch.stories.tsx +++ b/src/components/molecules/forms/switch/switch.stories.tsx @@ -1,25 +1,14 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { type ChangeEventHandler, useCallback, useState } from 'react'; -import { Legend } from '../../../atoms'; -import { Switch as SwitchComponent, type SwitchOption } from './switch'; - -/** - * Switch - Storybook Meta - */ -export default { - title: 'Molecules/Forms', - component: SwitchComponent, - args: {}, - argTypes: {}, -} as ComponentMeta<typeof SwitchComponent>; - -const Template: ComponentStory<typeof SwitchComponent> = ({ - value, - ...args -}) => { - const [selection, setSelection] = useState(value); - - const handleChange: ChangeEventHandler<HTMLInputElement> = useCallback( +import { Icon } from '../../../atoms'; +import { Switch, type SwitchProps } from './switch'; + +type ControlledSwitchProps = Omit<SwitchProps, 'onSwitch' | 'selectedItem'>; + +const ControlledSwitch = ({ items, ...props }: ControlledSwitchProps) => { + const [selection, setSelection] = useState(items[0].value); + + const handleSwitch: ChangeEventHandler<HTMLInputElement> = useCallback( (e) => { setSelection(e.target.value); }, @@ -27,22 +16,60 @@ const Template: ComponentStory<typeof SwitchComponent> = ({ ); return ( - <SwitchComponent {...args} onSwitch={handleChange} value={selection} /> + <Switch + {...props} + items={items} + onSwitch={handleSwitch} + value={selection} + /> ); }; -const items: [SwitchOption, SwitchOption] = [ - { id: 'option-1', label: 'Choice 1', value: 'option-1' }, - { id: 'option-2', label: 'Choice 2', value: 'option-2' }, -]; - -/** - * Radio Group Story - */ -export const Switch = Template.bind({}); -Switch.args = { - items, - legend: <Legend>Choose the best option:</Legend>, - name: 'example', - value: items[0].value, +const meta = { + title: 'Molecules/Forms/Switch', + component: Switch, + render: ControlledSwitch, +} satisfies Meta<typeof Switch>; + +export default meta; + +type Story = StoryObj<typeof ControlledSwitch>; + +export const Example: Story = { + args: { + items: [ + { id: 'item-1', label: 'Item 1', value: 'item-1' }, + { id: 'item-2', label: 'Item 2', value: 'item-2' }, + ], + name: 'example', + }, +}; + +export const Disabled: Story = { + args: { + isDisabled: true, + items: [ + { id: 'disabled-item-1', label: 'Item 1', value: 'item-1' }, + { id: 'disabled-item-2', label: 'Item 2', value: 'item-2' }, + ], + name: 'disabled', + }, +}; + +export const Icons: Story = { + args: { + items: [ + { + id: 'light-theme', + label: <Icon aria-label="Light theme" shape="sun" size="xs" />, + value: 'light-theme', + }, + { + id: 'dark-theme', + label: <Icon aria-label="Dark theme" shape="moon" size="xs" />, + value: 'dark-theme', + }, + ], + name: 'theme', + }, }; |
