From a6ff5eee45215effb3344cb5d631a27a7c0369aa Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 22 Sep 2023 19:34:01 +0200 Subject: refactor(components): rewrite form components --- .../molecules/forms/ackee-toggle.fixture.tsx | 1 - .../molecules/forms/ackee-toggle.module.scss | 6 - .../molecules/forms/ackee-toggle.stories.tsx | 125 --------- .../molecules/forms/ackee-toggle.test.tsx | 15 -- src/components/molecules/forms/ackee-toggle.tsx | 147 ----------- .../molecules/forms/fieldset.fixture.tsx | 6 - .../molecules/forms/fieldset.module.scss | 61 ----- .../molecules/forms/fieldset.stories.tsx | 176 ------------- src/components/molecules/forms/fieldset.test.tsx | 22 -- src/components/molecules/forms/fieldset.tsx | 118 --------- .../molecules/forms/flipping-label.module.scss | 63 ----- .../molecules/forms/flipping-label.stories.tsx | 96 ------- .../molecules/forms/flipping-label.test.tsx | 14 - src/components/molecules/forms/flipping-label.tsx | 37 --- .../flipping-label/flipping-label.module.scss | 63 +++++ .../flipping-label/flipping-label.stories.tsx | 96 +++++++ .../forms/flipping-label/flipping-label.test.tsx | 14 + .../forms/flipping-label/flipping-label.tsx | 37 +++ .../molecules/forms/flipping-label/index.ts | 1 + src/components/molecules/forms/index.ts | 8 +- .../forms/labelled-boolean-field.fixture.tsx | 1 - .../forms/labelled-boolean-field.module.scss | 15 -- .../forms/labelled-boolean-field.stories.tsx | 254 ------------------ .../forms/labelled-boolean-field.test.tsx | 37 --- .../molecules/forms/labelled-boolean-field.tsx | 85 ------ .../molecules/forms/labelled-field.module.scss | 9 - .../molecules/forms/labelled-field.stories.tsx | 293 --------------------- .../molecules/forms/labelled-field.test.tsx | 19 -- src/components/molecules/forms/labelled-field.tsx | 49 ---- .../molecules/forms/labelled-field/index.ts | 1 + .../labelled-field/labelled-field.module.scss | 22 ++ .../labelled-field/labelled-field.stories.tsx | 130 +++++++++ .../forms/labelled-field/labelled-field.test.tsx | 32 +++ .../forms/labelled-field/labelled-field.tsx | 63 +++++ .../molecules/forms/labelled-select.module.scss | 9 - .../molecules/forms/labelled-select.stories.tsx | 236 ----------------- .../molecules/forms/labelled-select.test.tsx | 25 -- src/components/molecules/forms/labelled-select.tsx | 66 ----- .../molecules/forms/motion-toggle.fixture.tsx | 1 - .../molecules/forms/motion-toggle.stories.tsx | 86 ------ .../molecules/forms/motion-toggle.test.tsx | 15 -- src/components/molecules/forms/motion-toggle.tsx | 118 --------- .../molecules/forms/prism-theme-toggle.stories.tsx | 60 ----- .../molecules/forms/prism-theme-toggle.test.tsx | 13 - .../molecules/forms/prism-theme-toggle.tsx | 118 --------- .../molecules/forms/radio-group.fixture.tsx | 47 ---- .../molecules/forms/radio-group.module.scss | 112 -------- .../molecules/forms/radio-group.stories.tsx | 285 -------------------- .../molecules/forms/radio-group.test.tsx | 30 --- src/components/molecules/forms/radio-group.tsx | 157 ----------- .../molecules/forms/radio-group/index.ts | 1 + .../forms/radio-group/radio-group.fixture.tsx | 41 +++ .../forms/radio-group/radio-group.module.scss | 9 + .../forms/radio-group/radio-group.stories.tsx | 75 ++++++ .../forms/radio-group/radio-group.test.tsx | 59 +++++ .../molecules/forms/radio-group/radio-group.tsx | 110 ++++++++ src/components/molecules/forms/switch/index.ts | 1 + .../molecules/forms/switch/switch.module.scss | 105 ++++++++ .../molecules/forms/switch/switch.stories.tsx | 48 ++++ .../molecules/forms/switch/switch.test.tsx | 49 ++++ src/components/molecules/forms/switch/switch.tsx | 132 ++++++++++ .../molecules/forms/theme-toggle.stories.tsx | 60 ----- .../molecules/forms/theme-toggle.test.tsx | 13 - src/components/molecules/forms/theme-toggle.tsx | 106 -------- src/components/molecules/index.ts | 2 +- src/components/molecules/modals/index.ts | 2 - src/components/molecules/modals/modal.module.scss | 34 --- src/components/molecules/modals/modal.stories.tsx | 96 ------- src/components/molecules/modals/modal.test.tsx | 18 -- src/components/molecules/modals/modal.tsx | 88 ------- .../molecules/modals/tooltip.fixture.tsx | 4 - .../molecules/modals/tooltip.module.scss | 46 ---- .../molecules/modals/tooltip.stories.tsx | 84 ------ src/components/molecules/modals/tooltip.test.tsx | 20 -- src/components/molecules/modals/tooltip.tsx | 67 ----- src/components/molecules/tooltip/index.ts | 1 + .../molecules/tooltip/tooltip.module.scss | 72 +++++ .../molecules/tooltip/tooltip.stories.tsx | 42 +++ src/components/molecules/tooltip/tooltip.test.tsx | 39 +++ src/components/molecules/tooltip/tooltip.tsx | 92 +++++++ 80 files changed, 1337 insertions(+), 3673 deletions(-) delete mode 100644 src/components/molecules/forms/ackee-toggle.fixture.tsx delete mode 100644 src/components/molecules/forms/ackee-toggle.module.scss delete mode 100644 src/components/molecules/forms/ackee-toggle.stories.tsx delete mode 100644 src/components/molecules/forms/ackee-toggle.test.tsx delete mode 100644 src/components/molecules/forms/ackee-toggle.tsx delete mode 100644 src/components/molecules/forms/fieldset.fixture.tsx delete mode 100644 src/components/molecules/forms/fieldset.module.scss delete mode 100644 src/components/molecules/forms/fieldset.stories.tsx delete mode 100644 src/components/molecules/forms/fieldset.test.tsx delete mode 100644 src/components/molecules/forms/fieldset.tsx delete mode 100644 src/components/molecules/forms/flipping-label.module.scss delete mode 100644 src/components/molecules/forms/flipping-label.stories.tsx delete mode 100644 src/components/molecules/forms/flipping-label.test.tsx delete mode 100644 src/components/molecules/forms/flipping-label.tsx create mode 100644 src/components/molecules/forms/flipping-label/flipping-label.module.scss create mode 100644 src/components/molecules/forms/flipping-label/flipping-label.stories.tsx create mode 100644 src/components/molecules/forms/flipping-label/flipping-label.test.tsx create mode 100644 src/components/molecules/forms/flipping-label/flipping-label.tsx create mode 100644 src/components/molecules/forms/flipping-label/index.ts delete mode 100644 src/components/molecules/forms/labelled-boolean-field.fixture.tsx delete mode 100644 src/components/molecules/forms/labelled-boolean-field.module.scss delete mode 100644 src/components/molecules/forms/labelled-boolean-field.stories.tsx delete mode 100644 src/components/molecules/forms/labelled-boolean-field.test.tsx delete mode 100644 src/components/molecules/forms/labelled-boolean-field.tsx delete mode 100644 src/components/molecules/forms/labelled-field.module.scss delete mode 100644 src/components/molecules/forms/labelled-field.stories.tsx delete mode 100644 src/components/molecules/forms/labelled-field.test.tsx delete mode 100644 src/components/molecules/forms/labelled-field.tsx create mode 100644 src/components/molecules/forms/labelled-field/index.ts create mode 100644 src/components/molecules/forms/labelled-field/labelled-field.module.scss create mode 100644 src/components/molecules/forms/labelled-field/labelled-field.stories.tsx create mode 100644 src/components/molecules/forms/labelled-field/labelled-field.test.tsx create mode 100644 src/components/molecules/forms/labelled-field/labelled-field.tsx delete mode 100644 src/components/molecules/forms/labelled-select.module.scss delete mode 100644 src/components/molecules/forms/labelled-select.stories.tsx delete mode 100644 src/components/molecules/forms/labelled-select.test.tsx delete mode 100644 src/components/molecules/forms/labelled-select.tsx delete mode 100644 src/components/molecules/forms/motion-toggle.fixture.tsx delete mode 100644 src/components/molecules/forms/motion-toggle.stories.tsx delete mode 100644 src/components/molecules/forms/motion-toggle.test.tsx delete mode 100644 src/components/molecules/forms/motion-toggle.tsx delete mode 100644 src/components/molecules/forms/prism-theme-toggle.stories.tsx delete mode 100644 src/components/molecules/forms/prism-theme-toggle.test.tsx delete mode 100644 src/components/molecules/forms/prism-theme-toggle.tsx delete mode 100644 src/components/molecules/forms/radio-group.fixture.tsx delete mode 100644 src/components/molecules/forms/radio-group.module.scss delete mode 100644 src/components/molecules/forms/radio-group.stories.tsx delete mode 100644 src/components/molecules/forms/radio-group.test.tsx delete mode 100644 src/components/molecules/forms/radio-group.tsx create mode 100644 src/components/molecules/forms/radio-group/index.ts create mode 100644 src/components/molecules/forms/radio-group/radio-group.fixture.tsx create mode 100644 src/components/molecules/forms/radio-group/radio-group.module.scss create mode 100644 src/components/molecules/forms/radio-group/radio-group.stories.tsx create mode 100644 src/components/molecules/forms/radio-group/radio-group.test.tsx create mode 100644 src/components/molecules/forms/radio-group/radio-group.tsx create mode 100644 src/components/molecules/forms/switch/index.ts create mode 100644 src/components/molecules/forms/switch/switch.module.scss create mode 100644 src/components/molecules/forms/switch/switch.stories.tsx create mode 100644 src/components/molecules/forms/switch/switch.test.tsx create mode 100644 src/components/molecules/forms/switch/switch.tsx delete mode 100644 src/components/molecules/forms/theme-toggle.stories.tsx delete mode 100644 src/components/molecules/forms/theme-toggle.test.tsx delete mode 100644 src/components/molecules/forms/theme-toggle.tsx delete mode 100644 src/components/molecules/modals/index.ts delete mode 100644 src/components/molecules/modals/modal.module.scss delete mode 100644 src/components/molecules/modals/modal.stories.tsx delete mode 100644 src/components/molecules/modals/modal.test.tsx delete mode 100644 src/components/molecules/modals/modal.tsx delete mode 100644 src/components/molecules/modals/tooltip.fixture.tsx delete mode 100644 src/components/molecules/modals/tooltip.module.scss delete mode 100644 src/components/molecules/modals/tooltip.stories.tsx delete mode 100644 src/components/molecules/modals/tooltip.test.tsx delete mode 100644 src/components/molecules/modals/tooltip.tsx create mode 100644 src/components/molecules/tooltip/index.ts create mode 100644 src/components/molecules/tooltip/tooltip.module.scss create mode 100644 src/components/molecules/tooltip/tooltip.stories.tsx create mode 100644 src/components/molecules/tooltip/tooltip.test.tsx create mode 100644 src/components/molecules/tooltip/tooltip.tsx (limited to 'src/components/molecules') diff --git a/src/components/molecules/forms/ackee-toggle.fixture.tsx b/src/components/molecules/forms/ackee-toggle.fixture.tsx deleted file mode 100644 index 04602f2..0000000 --- a/src/components/molecules/forms/ackee-toggle.fixture.tsx +++ /dev/null @@ -1 +0,0 @@ -export const storageKey = 'ackee'; diff --git a/src/components/molecules/forms/ackee-toggle.module.scss b/src/components/molecules/forms/ackee-toggle.module.scss deleted file mode 100644 index f238bda..0000000 --- a/src/components/molecules/forms/ackee-toggle.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.wrapper { - display: flex; - flex-flow: row wrap; - align-items: center; - position: relative; -} diff --git a/src/components/molecules/forms/ackee-toggle.stories.tsx b/src/components/molecules/forms/ackee-toggle.stories.tsx deleted file mode 100644 index 779f49d..0000000 --- a/src/components/molecules/forms/ackee-toggle.stories.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { AckeeToggle } from './ackee-toggle'; -import { storageKey } from './ackee-toggle.fixture'; - -/** - * AckeeToggle - Storybook Meta - */ -export default { - title: 'Molecules/Forms/Toggle', - component: AckeeToggle, - argTypes: { - bodyClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the fieldset body wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - buttonClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the help button.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the toggle wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - defaultValue: { - control: { - type: 'select', - }, - description: 'Set the default value.', - options: ['full', 'partial'], - type: { - name: 'string', - required: true, - }, - }, - groupClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the radio group wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - legendClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the legend.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - storageKey: { - control: { - type: 'text', - }, - description: 'Set local storage key.', - type: { - name: 'string', - required: true, - }, - }, - tooltipClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the tooltip wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ( - -); - -/** - * Toggle Stories - Ackee - */ -export const Ackee = Template.bind({}); -Ackee.args = { - defaultValue: 'full', - storageKey, -}; diff --git a/src/components/molecules/forms/ackee-toggle.test.tsx b/src/components/molecules/forms/ackee-toggle.test.tsx deleted file mode 100644 index 97ebbe5..0000000 --- a/src/components/molecules/forms/ackee-toggle.test.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { render, screen } from '../../../../tests/utils'; -import { AckeeToggle } from './ackee-toggle'; -import { storageKey } from './ackee-toggle.fixture'; - -describe('AckeeToggle', () => { - // toHaveValue received undefined. Maybe because of localStorage hook... - it('renders a toggle component', () => { - render(); - expect( - screen.getByRole('radiogroup', { - name: /Tracking:/i, - }) - ).toBeInTheDocument(); - }); -}); diff --git a/src/components/molecules/forms/ackee-toggle.tsx b/src/components/molecules/forms/ackee-toggle.tsx deleted file mode 100644 index 32949b2..0000000 --- a/src/components/molecules/forms/ackee-toggle.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { FC } from 'react'; -import { useIntl } from 'react-intl'; -import { - type AckeeOptions, - useLocalStorage, - useUpdateAckeeOptions, -} from '../../../utils/hooks'; -import { - RadioGroup, - type RadioGroupCallback, - type RadioGroupCallbackProps, - type RadioGroupOption, - type RadioGroupProps, -} from './radio-group'; -import { Tooltip, type TooltipProps } from '../modals/tooltip'; - -export type AckeeToggleProps = Pick< - RadioGroupProps, - | 'bodyClassName' - | 'buttonClassName' - | 'groupClassName' - | 'legendClassName' - | 'legendPosition' -> & { - /** - * Set additional classnames to the toggle wrapper. - */ - className?: string; - /** - * True if motion should be reduced by default. - */ - defaultValue: AckeeOptions; - /** - * The local storage key to save preference. - */ - storageKey: string; - /** - * Set additional classnames to the tooltip wrapper. - */ - tooltipClassName?: TooltipProps['className']; -}; - -/** - * AckeeToggle component - * - * Render a Toggle component to set reduce motion. - */ -export const AckeeToggle: FC = ({ - defaultValue, - storageKey, - tooltipClassName, - ...props -}) => { - const intl = useIntl(); - const { value, setValue } = useLocalStorage( - storageKey, - defaultValue - ); - useUpdateAckeeOptions(value); - - const ackeeLabel = intl.formatMessage({ - defaultMessage: 'Tracking:', - description: 'AckeeToggle: select label', - id: '0gVlI3', - }); - const tooltipTitle = intl.formatMessage({ - defaultMessage: 'Ackee tracking (analytics)', - description: 'AckeeToggle: tooltip title', - id: 'nGss/j', - }); - const tooltipContent = [ - intl.formatMessage({ - defaultMessage: 'Partial includes only page url, views and duration.', - description: 'AckeeToggle: tooltip message', - id: 'ZB/Aw2', - }), - intl.formatMessage({ - defaultMessage: - 'Full includes all information from partial as well as information about referrer, operating system, device, browser, screen size and language.', - description: 'AckeeToggle: tooltip message', - id: '7zDlQo', - }), - ]; - const partialLabel = intl.formatMessage({ - defaultMessage: 'Partial', - description: 'AckeeToggle: partial option name', - id: 'tIZYpD', - }); - const fullLabel = intl.formatMessage({ - defaultMessage: 'Full', - description: 'AckeeToggle: full option name', - id: '5eD6y2', - }); - - const options: RadioGroupOption[] = [ - { - id: 'ackee-full', - label: fullLabel, - name: 'ackee', - value: 'full', - }, - { - id: 'ackee-partial', - label: partialLabel, - name: 'ackee', - value: 'partial', - }, - ]; - - /** - * Handle change events. - * - * @param {RadioGroupCallbackProps} props - An object with choices. - */ - const handleChange: RadioGroupCallback = ({ - choices, - updateChoice, - }: RadioGroupCallbackProps) => { - let newChoice: AckeeOptions = choices.new as AckeeOptions; - - if (choices.new === choices.prev) { - newChoice = choices.new === 'full' ? 'partial' : 'full'; - updateChoice(newChoice); - } - - setValue(newChoice); - }; - - return ( - - } - /> - ); -}; diff --git a/src/components/molecules/forms/fieldset.fixture.tsx b/src/components/molecules/forms/fieldset.fixture.tsx deleted file mode 100644 index b94f340..0000000 --- a/src/components/molecules/forms/fieldset.fixture.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { TooltipProps } from '../modals/tooltip'; -import { Help } from '../modals/tooltip.stories'; - -export const body = 'doloribus magni aut'; -export const legend = 'maiores autem est'; -export const Tooltip = ; diff --git a/src/components/molecules/forms/fieldset.module.scss b/src/components/molecules/forms/fieldset.module.scss deleted file mode 100644 index 38327b4..0000000 --- a/src/components/molecules/forms/fieldset.module.scss +++ /dev/null @@ -1,61 +0,0 @@ -.legend { - float: left; - color: var(--color-primary-darker); - font-size: var(--font-size-md); - font-weight: 600; - - &#{&}--has-tooltip { - margin: 0 var(--spacing-2xs) 0 0; - } -} - -.btn { - margin: 0 var(--spacing-xs) var(--spacing-2xs) 0; - - &--activated { - background: var(--color-primary); - - * { - color: var(--color-fg-inverted); - } - } -} - -.tooltip { - top: calc(100% + var(--spacing-xs)); - transform-origin: top; - transition: all 0.75s ease-in-out 0s; - - &--hidden { - flex: 0 0 0; - opacity: 0; - visibility: hidden; - transform: scale(0); - } - - &--visible { - opacity: 1; - visibility: visible; - transform: scale(1); - } -} - -.wrapper { - display: flex; - flex-flow: row wrap; - align-items: center; - max-width: 100%; - padding: 0; - position: relative; - border: none; - - &--stacked { - .body { - flex: 1 0 100%; - } - } - - .tooltip { - position: absolute; - } -} diff --git a/src/components/molecules/forms/fieldset.stories.tsx b/src/components/molecules/forms/fieldset.stories.tsx deleted file mode 100644 index d53a21a..0000000 --- a/src/components/molecules/forms/fieldset.stories.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { Fieldset as FieldsetComponent } from './fieldset'; -import { body, legend, Tooltip } from './fieldset.fixture'; - -/** - * Fieldset - Storybook Meta - */ -export default { - title: 'Molecules/Forms/Fieldset', - component: FieldsetComponent, - args: { - legendPosition: 'stacked', - role: 'group', - }, - argTypes: { - bodyClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the body wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - buttonClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the help button.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - children: { - control: { - type: null, - }, - description: 'The fieldset body.', - type: { - name: 'string', - required: true, - }, - }, - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the fieldset.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - legend: { - control: { - type: 'text', - }, - description: 'The fieldset legend.', - type: { - name: 'string', - required: true, - }, - }, - legendClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the legend.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - legendPosition: { - control: { - type: 'select', - }, - description: 'Determine the legend position.', - options: ['inline', 'stacked'], - table: { - category: 'Options', - defaultValue: { summary: 'inline' }, - }, - type: { - name: 'string', - required: false, - }, - }, - role: { - control: { - type: 'select', - }, - description: 'An accessible role.', - table: { - category: 'Accessibility', - defaultValue: { summary: 'group' }, - }, - options: ['group', 'radiogroup', 'presentation', 'none'], - type: { - name: 'string', - required: false, - }, - }, - Tooltip: { - control: { - type: null, - }, - description: 'Add an optional tooltip.', - table: { - category: 'Options', - }, - type: { - name: 'function', - required: false, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ( - -); - -/** - * Fieldset Stories - Stacked legend - */ -export const StackedLegend = Template.bind({}); -StackedLegend.args = { - children: body, - legend: legend, -}; - -/** - * Fieldset Stories - Inlined legend - */ -export const InlinedLegend = Template.bind({}); -InlinedLegend.args = { - children: body, - legend: legend, - legendPosition: 'inline', -}; - -/** - * Fieldset Stories - Stacked legend with tooltip - */ -export const StackedLegendWithTooltip = Template.bind({}); -StackedLegendWithTooltip.args = { - children: body, - legend: legend, - Tooltip, -}; - -/** - * Fieldset Stories - Inlined legend with tooltip - */ -export const InlinedLegendWithTooltip = Template.bind({}); -InlinedLegendWithTooltip.args = { - children: body, - legend: legend, - legendPosition: 'inline', - Tooltip, -}; diff --git a/src/components/molecules/forms/fieldset.test.tsx b/src/components/molecules/forms/fieldset.test.tsx deleted file mode 100644 index f397bcd..0000000 --- a/src/components/molecules/forms/fieldset.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { render, screen } from '../../../../tests/utils'; -import { Fieldset } from './fieldset'; -import { body, legend, Tooltip } from './fieldset.fixture'; - -describe('Fieldset', () => { - // Cannot use toBeInTheDocument because of body is not an HTMLElement. - - it('renders a legend and a body', () => { - render(
{body}
); - expect(screen.findByRole('group', { name: legend })).toBeTruthy(); - expect(screen.findByText(body)).toBeTruthy(); - }); - - it('renders a button to open a tooltip', () => { - render( -
- {body} -
- ); - expect(screen.findByRole('button', { name: /Help/i })).toBeTruthy(); - }); -}); diff --git a/src/components/molecules/forms/fieldset.tsx b/src/components/molecules/forms/fieldset.tsx deleted file mode 100644 index 7564d14..0000000 --- a/src/components/molecules/forms/fieldset.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { - cloneElement, - FC, - ReactComponentElement, - ReactNode, - useRef, - useState, -} from 'react'; -import { useOnClickOutside } from '../../../utils/hooks'; -import { HelpButton } from '../buttons'; -import { Tooltip } from '../modals'; -import styles from './fieldset.module.scss'; - -export type FieldsetProps = { - /** - * Set additional classnames to the body wrapper. - */ - bodyClassName?: string; - /** - * Set additional classnames to the help button. - */ - buttonClassName?: string; - /** - * The fieldset body. - */ - children: ReactNode | ReactNode[]; - /** - * Set additional classnames to the fieldset wrapper. - */ - className?: string; - /** - * The fieldset legend. - */ - legend: string; - /** - * Set additional classnames to the legend. - */ - legendClassName?: string; - /** - * The legend position. - * - * @default 'stacked' - */ - legendPosition?: 'inline' | 'stacked'; - /** - * An accessible role. - * - * @default 'group' - */ - role?: 'group' | 'radiogroup' | 'presentation' | 'none'; - /** - * An optional tooltip component. - */ - Tooltip?: ReactComponentElement; -}; - -/** - * Fieldset component - * - * Render a fieldset with a legend. - */ -export const Fieldset: FC = ({ - bodyClassName = '', - buttonClassName = '', - children, - className = '', - legend, - legendClassName = '', - legendPosition = 'stacked', - Tooltip: TooltipComponent, - ...props -}) => { - const [isTooltipOpened, setIsTooltipOpened] = useState(false); - const buttonRef = useRef(null); - const wrapperModifier = `wrapper--${legendPosition}`; - const buttonModifier = isTooltipOpened ? styles['btn--activated'] : ''; - const legendModifier = - TooltipComponent === undefined ? '' : 'legend--has-tooltip'; - const tooltipModifier = isTooltipOpened - ? 'tooltip--visible' - : 'tooltip--hidden'; - - /** - * Close the tooltip if the target is not the button. - * - * @param {Node} target - The event target. - */ - const closeTooltip = (target: Node) => { - if (buttonRef.current && !buttonRef.current.contains(target)) { - setIsTooltipOpened(false); - } - }; - - const tooltipRef = useOnClickOutside(closeTooltip); - const fieldsetClass = `${styles.wrapper} ${styles[wrapperModifier]} ${className}`; - const legendClass = `${styles.legend} ${styles[legendModifier]} ${legendClassName}`; - - return ( -
- {legend} - {TooltipComponent && ( - <> - setIsTooltipOpened(!isTooltipOpened)} - ref={buttonRef} - /> - {cloneElement(TooltipComponent, { - cloneClassName: `${styles.tooltip} ${styles[tooltipModifier]}`, - ref: tooltipRef, - })} - - )} -
{children}
-
- ); -}; diff --git a/src/components/molecules/forms/flipping-label.module.scss b/src/components/molecules/forms/flipping-label.module.scss deleted file mode 100644 index b0452fe..0000000 --- a/src/components/molecules/forms/flipping-label.module.scss +++ /dev/null @@ -1,63 +0,0 @@ -@use "../../../styles/abstracts/functions" as fun; - -.label { - display: block; - width: var(--btn-size, #{fun.convert-px(60)}); - height: var(--btn-size, #{fun.convert-px(60)}); -} - -.front, -.back { - display: flex; - place-content: center; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - backface-visibility: hidden; - transition: all 0.6s ease-in 0s; -} - -.front { - z-index: 20; -} - -.back { - z-index: 10; -} - -.wrapper { - --icon-size: 60%; - - display: flex; - place-content: center; - place-items: center; - width: 100%; - height: 100%; - position: relative; - transition: all 0.5s ease-in-out 0s; - transform-style: preserve-3d; - - &--active { - transform: rotateY(180deg); - - .front { - transform: scale(0.2); - } - - .back { - transform: scale(1) rotateY(180deg); - } - } - - &--inactive { - .front { - transform: scale(1); - } - - .back { - transform: scale(0.2) rotateY(180deg); - } - } -} diff --git a/src/components/molecules/forms/flipping-label.stories.tsx b/src/components/molecules/forms/flipping-label.stories.tsx deleted file mode 100644 index 7dad3cb..0000000 --- a/src/components/molecules/forms/flipping-label.stories.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { useState } from 'react'; -import { MagnifyingGlass } from '../../atoms'; -import { FlippingLabel } from './flipping-label'; - -export default { - title: 'Organisms/Forms/FlippingLabel', - component: FlippingLabel, - argTypes: { - 'aria-label': { - control: { - type: 'text', - }, - description: 'An accessible name for the label.', - table: { - category: 'Accessibility', - }, - type: { - name: 'string', - required: false, - }, - }, - children: { - control: { - type: null, - }, - description: 'An icon for the label front face.', - type: { - name: 'function', - required: true, - }, - }, - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the label.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - htmlFor: { - control: { - type: null, - }, - description: 'Bind the label to a field by id.', - table: { - category: 'Options', - }, - type: { - name: 'string', - required: false, - }, - }, - isActive: { - control: { - type: 'boolean', - }, - description: - 'Which side of the label should be displayed? True for the close icon.', - type: { - name: 'boolean', - required: true, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = ({ - isActive, - ...args -}) => { - const [active, setActive] = useState(isActive); - - return ( -
setActive(!active)}> - -
- ); -}; - -export const Active = Template.bind({}); -Active.args = { - children: , - isActive: true, -}; - -export const Inactive = Template.bind({}); -Inactive.args = { - children: , - isActive: false, -}; diff --git a/src/components/molecules/forms/flipping-label.test.tsx b/src/components/molecules/forms/flipping-label.test.tsx deleted file mode 100644 index 0f5dd30..0000000 --- a/src/components/molecules/forms/flipping-label.test.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { render, screen } from '../../../../tests/utils'; -import { FlippingLabel } from './flipping-label'; - -describe('FlippingLabel', () => { - it('renders a label', () => { - const ariaLabel = 'vero quo inventore'; - render( - - <>Test - - ); - expect(screen.getByLabelText(ariaLabel)).toBeInTheDocument(); - }); -}); diff --git a/src/components/molecules/forms/flipping-label.tsx b/src/components/molecules/forms/flipping-label.tsx deleted file mode 100644 index c85642b..0000000 --- a/src/components/molecules/forms/flipping-label.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { FC } from 'react'; -import { Close, Label, type LabelProps } from '../../atoms'; -import styles from './flipping-label.module.scss'; - -export type FlippingLabelProps = Pick< - LabelProps, - 'aria-label' | 'className' | 'htmlFor' -> & { - /** - * The front icon. - */ - children: JSX.Element; - /** - * Which side of the label should be displayed? True for the close icon. - */ - isActive: boolean; -}; - -export const FlippingLabel: FC = ({ - children, - className = '', - isActive, - ...props -}) => { - const wrapperModifier = isActive ? 'wrapper--active' : 'wrapper--inactive'; - - return ( - - ); -}; diff --git a/src/components/molecules/forms/flipping-label/flipping-label.module.scss b/src/components/molecules/forms/flipping-label/flipping-label.module.scss new file mode 100644 index 0000000..88ef3ec --- /dev/null +++ b/src/components/molecules/forms/flipping-label/flipping-label.module.scss @@ -0,0 +1,63 @@ +@use "../../../../styles/abstracts/functions" as fun; + +.label { + display: block; + width: var(--btn-size, #{fun.convert-px(60)}); + height: var(--btn-size, #{fun.convert-px(60)}); +} + +.front, +.back { + display: flex; + place-content: center; + width: 100%; + height: 100%; + position: absolute; + top: 0; + right: 0; + backface-visibility: hidden; + transition: all 0.6s ease-in 0s; +} + +.front { + z-index: 20; +} + +.back { + z-index: 10; +} + +.wrapper { + --icon-size: 60%; + + display: flex; + place-content: center; + place-items: center; + width: 100%; + height: 100%; + position: relative; + transition: all 0.5s ease-in-out 0s; + transform-style: preserve-3d; + + &--active { + transform: rotateY(180deg); + + .front { + transform: scale(0.2); + } + + .back { + transform: scale(1) rotateY(180deg); + } + } + + &--inactive { + .front { + transform: scale(1); + } + + .back { + transform: scale(0.2) rotateY(180deg); + } + } +} diff --git a/src/components/molecules/forms/flipping-label/flipping-label.stories.tsx b/src/components/molecules/forms/flipping-label/flipping-label.stories.tsx new file mode 100644 index 0000000..3ad3529 --- /dev/null +++ b/src/components/molecules/forms/flipping-label/flipping-label.stories.tsx @@ -0,0 +1,96 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { useState } from 'react'; +import { MagnifyingGlass } from '../../../atoms'; +import { FlippingLabel } from './flipping-label'; + +export default { + title: 'Molecules/Forms/FlippingLabel', + component: FlippingLabel, + argTypes: { + 'aria-label': { + control: { + type: 'text', + }, + description: 'An accessible name for the label.', + table: { + category: 'Accessibility', + }, + type: { + name: 'string', + required: false, + }, + }, + children: { + control: { + type: null, + }, + description: 'An icon for the label front face.', + type: { + name: 'function', + required: true, + }, + }, + className: { + control: { + type: 'text', + }, + description: 'Set additional classnames to the label.', + table: { + category: 'Styles', + }, + type: { + name: 'string', + required: false, + }, + }, + htmlFor: { + control: { + type: null, + }, + description: 'Bind the label to a field by id.', + table: { + category: 'Options', + }, + type: { + name: 'string', + required: false, + }, + }, + isActive: { + control: { + type: 'boolean', + }, + description: + 'Which side of the label should be displayed? True for the close icon.', + type: { + name: 'boolean', + required: true, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = ({ + isActive, + ...args +}) => { + const [active, setActive] = useState(isActive); + + return ( +
setActive(!active)}> + +
+ ); +}; + +export const Active = Template.bind({}); +Active.args = { + children: , + isActive: true, +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + children: , + isActive: false, +}; diff --git a/src/components/molecules/forms/flipping-label/flipping-label.test.tsx b/src/components/molecules/forms/flipping-label/flipping-label.test.tsx new file mode 100644 index 0000000..7813855 --- /dev/null +++ b/src/components/molecules/forms/flipping-label/flipping-label.test.tsx @@ -0,0 +1,14 @@ +import { render, screen } from '../../../../../tests/utils'; +import { FlippingLabel } from './flipping-label'; + +describe('FlippingLabel', () => { + it('renders a label', () => { + const ariaLabel = 'vero quo inventore'; + render( + + <>Test + + ); + expect(screen.getByLabelText(ariaLabel)).toBeInTheDocument(); + }); +}); diff --git a/src/components/molecules/forms/flipping-label/flipping-label.tsx b/src/components/molecules/forms/flipping-label/flipping-label.tsx new file mode 100644 index 0000000..3e23915 --- /dev/null +++ b/src/components/molecules/forms/flipping-label/flipping-label.tsx @@ -0,0 +1,37 @@ +import { FC } from 'react'; +import { Close, Label, type LabelProps } from '../../../atoms'; +import styles from './flipping-label.module.scss'; + +export type FlippingLabelProps = Pick< + LabelProps, + 'aria-label' | 'className' | 'htmlFor' +> & { + /** + * The front icon. + */ + children: JSX.Element; + /** + * Which side of the label should be displayed? True for the close icon. + */ + isActive: boolean; +}; + +export const FlippingLabel: FC = ({ + children, + className = '', + isActive, + ...props +}) => { + const wrapperModifier = isActive ? 'wrapper--active' : 'wrapper--inactive'; + + return ( + + ); +}; diff --git a/src/components/molecules/forms/flipping-label/index.ts b/src/components/molecules/forms/flipping-label/index.ts new file mode 100644 index 0000000..7b50c75 --- /dev/null +++ b/src/components/molecules/forms/flipping-label/index.ts @@ -0,0 +1 @@ +export * from './flipping-label'; diff --git a/src/components/molecules/forms/index.ts b/src/components/molecules/forms/index.ts index 4d0497d..883a033 100644 --- a/src/components/molecules/forms/index.ts +++ b/src/components/molecules/forms/index.ts @@ -1,10 +1,4 @@ -export * from './ackee-toggle'; -export * from './fieldset'; export * from './flipping-label'; -export * from './labelled-boolean-field'; export * from './labelled-field'; -export * from './labelled-select'; -export * from './motion-toggle'; -export * from './prism-theme-toggle'; export * from './radio-group'; -export * from './theme-toggle'; +export * from './switch'; diff --git a/src/components/molecules/forms/labelled-boolean-field.fixture.tsx b/src/components/molecules/forms/labelled-boolean-field.fixture.tsx deleted file mode 100644 index 6b06887..0000000 --- a/src/components/molecules/forms/labelled-boolean-field.fixture.tsx +++ /dev/null @@ -1 +0,0 @@ -export const label = 'Quas et natus'; diff --git a/src/components/molecules/forms/labelled-boolean-field.module.scss b/src/components/molecules/forms/labelled-boolean-field.module.scss deleted file mode 100644 index 10a9eb2..0000000 --- a/src/components/molecules/forms/labelled-boolean-field.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -.label { - &--visible#{&}--left { - margin-right: var(--spacing-2xs); - } - - &--visible#{&}--right { - margin-left: var(--spacing-2xs); - } -} - -.wrapper { - display: inline-flex; - flex-flow: row wrap; - align-items: center; -} diff --git a/src/components/molecules/forms/labelled-boolean-field.stories.tsx b/src/components/molecules/forms/labelled-boolean-field.stories.tsx deleted file mode 100644 index b1f8194..0000000 --- a/src/components/molecules/forms/labelled-boolean-field.stories.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { useState } from 'react'; -import { LabelledBooleanField } from './labelled-boolean-field'; -import { label } from './labelled-boolean-field.fixture'; - -/** - * LabelledBooleanField - Storybook Meta - */ -export default { - title: 'Molecules/Forms/Boolean', - component: LabelledBooleanField, - args: { - checked: false, - hidden: false, - label, - labelSize: 'small', - }, - argTypes: { - checked: { - control: { - type: null, - }, - description: 'Should the option be checked?', - type: { - name: 'boolean', - required: true, - }, - }, - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the labelled field wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - fieldClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the field.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - hidden: { - control: { - type: 'boolean', - }, - description: 'Define if the field should be visually hidden.', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - id: { - control: { - type: 'text', - }, - description: 'The option id.', - type: { - name: 'string', - required: true, - }, - }, - label: { - control: { - type: 'text', - }, - description: 'The radio 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, - }, - }, - labelPosition: { - control: { - type: 'select', - }, - description: 'Determine the label position.', - options: ['left', 'right'], - table: { - category: 'Options', - defaultValue: { summary: 'left' }, - }, - 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 field name.', - type: { - name: 'string', - required: true, - }, - }, - onChange: { - control: { - type: null, - }, - description: 'A callback function to handle field state change.', - table: { - category: 'Events', - }, - type: { - name: 'function', - required: true, - }, - }, - onClick: { - control: { - type: null, - }, - description: 'A callback function to handle click on field.', - table: { - category: 'Events', - }, - type: { - name: 'function', - required: false, - }, - }, - type: { - control: { - type: 'select', - }, - description: 'The field type. Either checkbox or radio.', - options: ['checkbox', 'radio'], - type: { - name: 'string', - required: true, - }, - }, - value: { - control: { - type: 'text', - }, - description: 'The field value.', - type: { - name: 'string', - required: true, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = ({ - checked, - onChange: _onChange, - ...args -}) => { - const [isChecked, setIsChecked] = useState(checked); - - return ( - { - setIsChecked(!isChecked); - }} - {...args} - /> - ); -}; - -/** - * Labelled Boolean Field Stories - Checkbox with left label - */ -export const CheckboxLeftLabel = Template.bind({}); -CheckboxLeftLabel.args = { - id: 'checkbox', - labelPosition: 'left', - name: 'checkbox-left-label', - type: 'checkbox', - value: 'checkbox', -}; - -/** - * Labelled Boolean Field Stories - Checkbox with right label - */ -export const CheckboxRightLabel = Template.bind({}); -CheckboxRightLabel.args = { - id: 'checkbox', - labelPosition: 'right', - name: 'checkbox-right-label', - type: 'checkbox', -}; - -/** - * Labelled Boolean Field Stories - Radio button with left label - */ -export const RadioButtonLeftLabel = Template.bind({}); -RadioButtonLeftLabel.args = { - id: 'radio', - labelPosition: 'left', - name: 'radio-left-label', - type: 'radio', - value: 'radio', -}; - -/** - * Labelled Boolean Field Stories - Radio button with right label - */ -export const RadioButtonRightLabel = Template.bind({}); -RadioButtonRightLabel.args = { - id: 'radio', - labelPosition: 'right', - name: 'radio-right-label', - type: 'radio', - value: 'radio', -}; diff --git a/src/components/molecules/forms/labelled-boolean-field.test.tsx b/src/components/molecules/forms/labelled-boolean-field.test.tsx deleted file mode 100644 index 6916f95..0000000 --- a/src/components/molecules/forms/labelled-boolean-field.test.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { render, screen } from '../../../../tests/utils'; -import { LabelledBooleanField } from './labelled-boolean-field'; -import { label } from './labelled-boolean-field.fixture'; - -describe('LabelledBooleanField', () => { - it('renders a labelled checkbox', () => { - render( - null} - type="checkbox" - value="checkbox" - /> - ); - expect(screen.getByLabelText(label)).toBeInTheDocument(); - expect(screen.getByRole('checkbox')).toBeChecked(); - }); - - it('renders a labelled radio option', () => { - render( - null} - type="radio" - value="radio" - /> - ); - expect(screen.getByLabelText(label)).toBeInTheDocument(); - expect(screen.getByRole('radio')).toBeChecked(); - }); -}); diff --git a/src/components/molecules/forms/labelled-boolean-field.tsx b/src/components/molecules/forms/labelled-boolean-field.tsx deleted file mode 100644 index d110d45..0000000 --- a/src/components/molecules/forms/labelled-boolean-field.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { FC } from 'react'; -import { - BooleanField, - type BooleanFieldProps, - Label, - type LabelProps, -} from '../../atoms'; -import styles from './labelled-boolean-field.module.scss'; - -export type LabelledBooleanFieldProps = Omit< - BooleanFieldProps, - 'aria-labelledby' | 'className' -> & { - /** - * Set additional classnames to the labelled field wrapper. - */ - className?: string; - /** - * Set additional classnames to the field. - */ - fieldClassName?: LabelledBooleanFieldProps['className']; - /** - * The field label. - */ - label: LabelProps['children']; - /** - * Set additional classnames to the label. - */ - labelClassName?: LabelProps['className']; - /** - * The label position. Default: left. - */ - labelPosition?: 'left' | 'right'; - /** - * The label size. - */ - labelSize?: LabelProps['size']; -}; - -/** - * LabelledBooleanField component - * - * Render a checkbox or radio button with a label. - */ -export const LabelledBooleanField: FC = ({ - className = '', - fieldClassName, - hidden, - id, - label, - labelClassName, - labelPosition = 'left', - labelSize, - ...props -}) => { - const labelHiddenModifier = hidden ? 'label--hidden' : 'label--visible'; - const labelPositionModifier = `label--${labelPosition}`; - const labelClass = `${styles[labelPositionModifier]} ${styles[labelHiddenModifier]} ${labelClassName}`; - - return labelPosition === 'left' ? ( - - - - ) : ( - - - ); -}; diff --git a/src/components/molecules/forms/labelled-field.module.scss b/src/components/molecules/forms/labelled-field.module.scss deleted file mode 100644 index 64ef3d0..0000000 --- a/src/components/molecules/forms/labelled-field.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -.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 deleted file mode 100644 index 840421b..0000000 --- a/src/components/molecules/forms/labelled-field.stories.tsx +++ /dev/null @@ -1,293 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { useState } from 'react'; -import { LabelledField } from './labelled-field'; - -/** - * LabelledField - Storybook Meta - */ -export default { - title: 'Molecules/Forms/Field', - component: LabelledField, - args: { - disabled: false, - hideLabel: false, - labelPosition: 'top', - required: false, - }, - argTypes: { - 'aria-labelledby': { - control: { - type: 'text', - }, - description: 'One or more ids that refers to the field name.', - table: { - category: 'Accessibility', - }, - type: { - name: 'string', - required: false, - }, - }, - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the field.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - 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: 'Field id.', - type: { - name: 'string', - required: true, - }, - }, - hideLabel: { - control: { - type: 'boolean', - }, - description: 'Visually hide the field label.', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - label: { - control: { - type: 'text', - }, - description: 'Field label.', - type: { - name: 'string', - 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', - }, - description: 'Maximum value.', - table: { - category: 'Options', - }, - type: { - name: 'number', - required: false, - }, - }, - min: { - control: { - type: 'number', - }, - description: 'Minimum value.', - table: { - category: 'Options', - }, - type: { - name: 'number', - required: false, - }, - }, - name: { - control: { - type: 'text', - }, - description: 'Field name.', - type: { - name: 'string', - required: true, - }, - }, - placeholder: { - control: { - type: 'text', - }, - description: 'A placeholder value.', - table: { - category: 'Options', - }, - type: { - name: 'string', - required: false, - }, - }, - required: { - control: { - type: 'boolean', - }, - description: 'Determine if the field is required.', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - setValue: { - control: { - type: null, - }, - description: 'Callback function to set field value.', - table: { - category: 'Events', - }, - type: { - name: 'function', - required: true, - }, - }, - step: { - control: { - type: 'number', - }, - description: 'Field incremental values that are valid.', - table: { - category: 'Options', - }, - type: { - name: 'number', - required: false, - }, - }, - type: { - control: { - type: 'select', - }, - description: 'Field type: input type or textarea.', - options: [ - 'datetime-local', - 'email', - 'number', - 'search', - 'tel', - 'text', - 'textarea', - 'time', - 'url', - ], - type: { - name: 'string', - required: true, - }, - }, - value: { - control: { - type: null, - }, - description: 'Field value.', - type: { - name: 'string', - required: true, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = ({ - value: _value, - setValue: _setValue, - ...args -}) => { - const [value, setValue] = useState(''); - - return ; -}; - -/** - * Labelled Field Stories - Left - */ -export const Left = Template.bind({}); -Left.args = { - id: 'labelled-field-storybook', - label: 'Labelled field', - labelPosition: 'left', - name: 'labelled-field-storybook', -}; - -/** - * Labelled Field Stories - Top - */ -export const Top = Template.bind({}); -Top.args = { - id: 'labelled-field-storybook', - label: 'Labelled field', - labelPosition: 'top', - name: 'labelled-field-storybook', -}; - -/** - * Labelled Field Stories - Required - */ -export const Required = Template.bind({}); -Required.args = { - id: 'labelled-field-storybook', - label: 'Labelled field', - name: 'labelled-field-storybook', - required: true, -}; - -/** - * Labelled Field Stories - Hidden label - */ -export const HiddenLabel = Template.bind({}); -HiddenLabel.args = { - hideLabel: true, - id: 'labelled-field-storybook', - label: 'Labelled field', - name: 'labelled-field-storybook', -}; - -/** - * Labelled Field Stories - Disabled - */ -export const Disabled = Template.bind({}); -Disabled.args = { - disabled: true, - id: 'labelled-field-storybook', - label: 'Labelled field', - name: 'labelled-field-storybook', -}; diff --git a/src/components/molecules/forms/labelled-field.test.tsx b/src/components/molecules/forms/labelled-field.test.tsx deleted file mode 100644 index e16c129..0000000 --- a/src/components/molecules/forms/labelled-field.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { render, screen } from '../../../../tests/utils'; -import { LabelledField } from './labelled-field'; - -describe('LabelledField', () => { - it('renders a labelled field', () => { - render( - null} - /> - ); - expect(screen.getByLabelText('Jest text field')).toBeInTheDocument(); - expect(screen.getByRole('textbox')).toHaveValue('test'); - }); -}); diff --git a/src/components/molecules/forms/labelled-field.tsx b/src/components/molecules/forms/labelled-field.tsx deleted file mode 100644 index fca1c54..0000000 --- a/src/components/molecules/forms/labelled-field.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { forwardRef, ForwardRefRenderFunction } from 'react'; -import { Field, type FieldProps, Label } from '../../atoms'; -import styles from './labelled-field.module.scss'; - -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 LabelledFieldWithRef: ForwardRefRenderFunction< - HTMLInputElement, - LabelledFieldProps -> = ( - { hideLabel = false, id, label, labelPosition = 'top', required, ...props }, - ref -) => { - const positionModifier = `label--${labelPosition}`; - const visibilityClass = hideLabel ? 'screen-reader-text' : ''; - - return ( - <> - - - - ); -}; - -/** - * LabelledField component - * - * Render a field tied to a label. - */ -export const LabelledField = forwardRef(LabelledFieldWithRef); diff --git a/src/components/molecules/forms/labelled-field/index.ts b/src/components/molecules/forms/labelled-field/index.ts new file mode 100644 index 0000000..b0d9889 --- /dev/null +++ b/src/components/molecules/forms/labelled-field/index.ts @@ -0,0 +1 @@ +export * from './labelled-field'; diff --git a/src/components/molecules/forms/labelled-field/labelled-field.module.scss b/src/components/molecules/forms/labelled-field/labelled-field.module.scss new file mode 100644 index 0000000..bb37dc7 --- /dev/null +++ b/src/components/molecules/forms/labelled-field/labelled-field.module.scss @@ -0,0 +1,22 @@ +.wrapper { + display: flex; + gap: var(--spacing-2xs); + width: fit-content; + + &--inline { + flex-flow: row wrap; + align-items: center; + } + + &--inline#{&}--reverse { + flex-flow: row-reverse wrap; + } + + &--stack { + flex-flow: column wrap; + } + + &--stack#{&}--reverse { + flex-flow: column-reverse wrap; + } +} diff --git a/src/components/molecules/forms/labelled-field/labelled-field.stories.tsx b/src/components/molecules/forms/labelled-field/labelled-field.stories.tsx new file mode 100644 index 0000000..1f29830 --- /dev/null +++ b/src/components/molecules/forms/labelled-field/labelled-field.stories.tsx @@ -0,0 +1,130 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { ChangeEvent, useState } from 'react'; +import { Input, Label } from '../../../atoms'; +import { LabelledField } from './labelled-field'; + +/** + * LabelledField - Storybook Meta + */ +export default { + title: 'Molecules/Forms/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; + +const Template: ComponentStory = ({ ...args }) => { + const id = 'sunt'; + const [value, setValue] = useState(''); + const updateValue = (e: ChangeEvent) => { + setValue(e.target.value); + }; + + return ( + + } + 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, +}; diff --git a/src/components/molecules/forms/labelled-field/labelled-field.test.tsx b/src/components/molecules/forms/labelled-field/labelled-field.test.tsx new file mode 100644 index 0000000..9e39e1f --- /dev/null +++ b/src/components/molecules/forms/labelled-field/labelled-field.test.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '../../../../../tests/utils'; +import { Input, Label } from '../../../atoms'; +import { LabelledField } from './labelled-field'; + +const doNothing = () => { + // Do nothing +}; + +describe('LabelledField', () => { + it('renders a labelled field', () => { + const id = 'enim'; + const label = 'eum aliquam culpa'; + const value = 'vitae'; + + render( + + } + label={} + /> + ); + expect(screen.getByLabelText(label)).toBeInTheDocument(); + expect(screen.getByRole('textbox')).toHaveValue(value); + }); +}); diff --git a/src/components/molecules/forms/labelled-field/labelled-field.tsx b/src/components/molecules/forms/labelled-field/labelled-field.tsx new file mode 100644 index 0000000..af492b3 --- /dev/null +++ b/src/components/molecules/forms/labelled-field/labelled-field.tsx @@ -0,0 +1,63 @@ +import { FC, HTMLAttributes, ReactElement } from 'react'; +import { + CheckboxProps, + InputProps, + LabelProps, + RadioProps, + SelectProps, + TextAreaProps, +} from '../../../atoms'; +import styles from './labelled-field.module.scss'; + +export type LabelledFieldProps = Omit< + HTMLAttributes, + 'children' +> & { + /** + * The field. + */ + field: ReactElement< + CheckboxProps | InputProps | RadioProps | SelectProps | TextAreaProps + >; + /** + * Should the label and the field be inlined? + * + * @default false + */ + isInline?: boolean; + /** + * If true, the label is displayed after the field. + * + * @default false + */ + isReversedOrder?: boolean; + /** + * The field label. + */ + label: ReactElement; +}; + +/** + * LabelledField component + * + * Render a field tied to a label. + */ +export const LabelledField: FC = ({ + className = '', + field, + isInline = false, + isReversedOrder = false, + label, + ...props +}) => { + const layoutClass = isInline ? 'wrapper--inline' : 'wrapper--stack'; + const orderClass = isReversedOrder ? 'wrapper--reverse' : ''; + const wrapperClass = `${styles.wrapper} ${styles[layoutClass]} ${styles[orderClass]} ${className}`; + + return ( +
+ {label} + {field} +
+ ); +}; diff --git a/src/components/molecules/forms/labelled-select.module.scss b/src/components/molecules/forms/labelled-select.module.scss deleted file mode 100644 index 64ef3d0..0000000 --- a/src/components/molecules/forms/labelled-select.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -.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 deleted file mode 100644 index d6029a5..0000000 --- a/src/components/molecules/forms/labelled-select.stories.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { useState } from 'react'; -import { LabelledSelect } from './labelled-select'; - -const selectOptions = [ - { id: 'option1', name: 'Option 1', value: 'option1' }, - { id: 'option2', name: 'Option 2', value: 'option2' }, - { id: 'option3', name: 'Option 3', value: 'option3' }, -]; - -/** - * LabelledSelect - Storybook Meta - */ -export default { - title: 'Molecules/Forms/Select', - component: LabelledSelect, - args: { - disabled: false, - labelPosition: 'top', - required: false, - }, - argTypes: { - 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: 'Field id.', - type: { - name: 'string', - required: true, - }, - }, - label: { - control: { - type: 'text', - }, - description: 'Field 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, - }, - }, - 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', - }, - description: 'Field name.', - type: { - name: 'string', - required: true, - }, - }, - options: { - control: { - type: null, - }, - description: 'Select options.', - type: { - name: 'array', - required: true, - value: { - name: 'string', - }, - }, - }, - required: { - control: { - type: 'boolean', - }, - description: 'Determine if the field is required.', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - 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, - }, - description: 'Callback function to set field value.', - table: { - category: 'Events', - }, - type: { - name: 'function', - required: true, - }, - }, - value: { - control: { - type: null, - }, - description: 'Field value.', - type: { - name: 'string', - required: true, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = ({ - value, - setValue: _setValue, - ...args -}) => { - const [selected, setSelected] = useState(value); - - return ; -}; - -/** - * Labelled Select Stories - Left - */ -export const Left = Template.bind({}); -Left.args = { - id: 'labelled-select-storybook', - label: 'Labelled select', - labelPosition: 'left', - name: 'labelled-select-storybook', - options: selectOptions, - value: 'option1', -}; - -/** - * Labelled Select Stories - Top - */ -export const Top = Template.bind({}); -Top.args = { - id: 'labelled-select-storybook', - label: 'Labelled select', - labelPosition: 'top', - name: 'labelled-select-storybook', - options: selectOptions, - value: 'option1', -}; - -/** - * Labelled Select Stories - Disabled - */ -export const Disabled = Template.bind({}); -Disabled.args = { - disabled: true, - id: 'labelled-select-storybook', - label: 'Labelled select', - name: 'labelled-select-storybook', - options: selectOptions, - value: 'option1', -}; - -/** - * Labelled Select Stories - Required - */ -export const Required = Template.bind({}); -Required.args = { - id: 'labelled-select-storybook', - label: 'Labelled select', - labelPosition: 'top', - name: 'labelled-select-storybook', - options: selectOptions, - required: true, - value: 'option1', -}; diff --git a/src/components/molecules/forms/labelled-select.test.tsx b/src/components/molecules/forms/labelled-select.test.tsx deleted file mode 100644 index 1ef4a25..0000000 --- a/src/components/molecules/forms/labelled-select.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { render, screen } from '../../../../tests/utils'; -import { LabelledSelect } from './labelled-select'; - -const selectOptions = [ - { id: 'option1', name: 'Option 1', value: 'option1' }, - { id: 'option2', name: 'Option 2', value: 'option2' }, - { id: 'option3', name: 'Option 3', value: 'option3' }, -]; - -describe('LabelledSelect', () => { - it('renders a labelled select', () => { - render( - null} - /> - ); - expect(screen.getByLabelText('Jest select field')).toBeInTheDocument(); - expect(screen.getByRole('combobox')).toHaveValue('option1'); - }); -}); diff --git a/src/components/molecules/forms/labelled-select.tsx b/src/components/molecules/forms/labelled-select.tsx deleted file mode 100644 index 300ae8a..0000000 --- a/src/components/molecules/forms/labelled-select.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { FC } from 'react'; -import { Label, type LabelProps, Select, type SelectProps } from '../../atoms'; -import styles from './labelled-select.module.scss'; - -export type LabelledSelectProps = Omit< - SelectProps, - 'aria-labelledby' | 'className' -> & { - /** - * The field label. - */ - label: string; - /** - * Set additional classnames to the label. - */ - labelClassName?: LabelProps['className']; - /** - * The label position. Default: top. - */ - labelPosition?: 'left' | 'top'; - /** - * The label size. - */ - labelSize?: LabelProps['size']; - /** - * Set additional classnames to the select field. - */ - selectClassName?: SelectProps['className']; -}; - -/** - * LabelledSelect component - * - * Render a select with a label. - */ -export const LabelledSelect: FC = ({ - id, - label, - labelClassName = '', - labelPosition = 'top', - labelSize, - required, - selectClassName = '', - ...props -}) => { - const positionModifier = `label--${labelPosition}`; - - return ( - <> - -