diff options
Diffstat (limited to 'src/components/atoms/forms')
| -rw-r--r-- | src/components/atoms/forms/checkbox.stories.tsx | 96 | ||||
| -rw-r--r-- | src/components/atoms/forms/checkbox.test.tsx | 28 | ||||
| -rw-r--r-- | src/components/atoms/forms/checkbox.tsx | 46 | ||||
| -rw-r--r-- | src/components/atoms/forms/field.stories.tsx | 201 | ||||
| -rw-r--r-- | src/components/atoms/forms/field.test.tsx | 30 | ||||
| -rw-r--r-- | src/components/atoms/forms/field.tsx | 107 | ||||
| -rw-r--r-- | src/components/atoms/forms/form.test.tsx | 9 | ||||
| -rw-r--r-- | src/components/atoms/forms/form.tsx | 73 | ||||
| -rw-r--r-- | src/components/atoms/forms/forms.module.scss | 53 | ||||
| -rw-r--r-- | src/components/atoms/forms/label.module.scss | 17 | ||||
| -rw-r--r-- | src/components/atoms/forms/label.stories.tsx | 85 | ||||
| -rw-r--r-- | src/components/atoms/forms/label.test.tsx | 9 | ||||
| -rw-r--r-- | src/components/atoms/forms/label.tsx | 45 | ||||
| -rw-r--r-- | src/components/atoms/forms/select.stories.tsx | 145 | ||||
| -rw-r--r-- | src/components/atoms/forms/select.test.tsx | 30 | ||||
| -rw-r--r-- | src/components/atoms/forms/select.tsx | 99 |
16 files changed, 1073 insertions, 0 deletions
diff --git a/src/components/atoms/forms/checkbox.stories.tsx b/src/components/atoms/forms/checkbox.stories.tsx new file mode 100644 index 0000000..7faf343 --- /dev/null +++ b/src/components/atoms/forms/checkbox.stories.tsx @@ -0,0 +1,96 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { useState } from 'react'; +import CheckboxComponent from './checkbox'; + +export default { + title: 'Atoms/Forms', + component: CheckboxComponent, + argTypes: { + 'aria-labelledby': { + control: { + type: 'text', + }, + description: 'One or more ids that refers to the checkbox name.', + table: { + category: 'Accessibility', + }, + type: { + name: 'string', + required: false, + }, + }, + className: { + control: { + type: 'text', + }, + description: 'Set additional classnames to the checkbox.', + table: { + category: 'Styles', + }, + type: { + name: 'string', + required: false, + }, + }, + id: { + control: { + type: 'text', + }, + description: 'The checkbox id.', + type: { + name: 'string', + required: true, + }, + }, + name: { + control: { + type: 'text', + }, + description: 'The checkbox name.', + type: { + name: 'string', + required: true, + }, + }, + setValue: { + control: { + type: null, + }, + description: 'A callback function to handle checkbox state.', + type: { + name: 'function', + required: true, + }, + }, + value: { + control: { + type: null, + }, + description: + 'The checkbox state: either checked (true) or unchecked (false).', + type: { + name: 'boolean', + required: true, + }, + }, + }, +} as ComponentMeta<typeof CheckboxComponent>; + +const Template: ComponentStory<typeof CheckboxComponent> = ({ + value, + setValue: _setValue, + ...args +}) => { + const [isChecked, setIsChecked] = useState<boolean>(value); + + return ( + <CheckboxComponent value={isChecked} setValue={setIsChecked} {...args} /> + ); +}; + +export const Checkbox = Template.bind({}); +Checkbox.args = { + id: 'storybook-checkbox', + name: 'storybook-checkbox', + value: false, +}; diff --git a/src/components/atoms/forms/checkbox.test.tsx b/src/components/atoms/forms/checkbox.test.tsx new file mode 100644 index 0000000..3b54549 --- /dev/null +++ b/src/components/atoms/forms/checkbox.test.tsx @@ -0,0 +1,28 @@ +import { render, screen } from '@test-utils'; +import Checkbox from './checkbox'; + +describe('Checkbox', () => { + it('renders an unchecked checkbox', () => { + render( + <Checkbox + id="jest-checkbox" + name="jest-checkbox" + value={false} + setValue={() => null} + /> + ); + expect(screen.getByRole('checkbox')).not.toBeChecked(); + }); + + it('renders a checked checkbox', () => { + render( + <Checkbox + id="jest-checkbox" + name="jest-checkbox" + value={true} + setValue={() => null} + /> + ); + expect(screen.getByRole('checkbox')).toBeChecked(); + }); +}); diff --git a/src/components/atoms/forms/checkbox.tsx b/src/components/atoms/forms/checkbox.tsx new file mode 100644 index 0000000..8babcc8 --- /dev/null +++ b/src/components/atoms/forms/checkbox.tsx @@ -0,0 +1,46 @@ +import { SetStateAction, VFC } from 'react'; + +export type CheckboxProps = { + /** + * One or more ids that refers to the checkbox name. + */ + 'aria-labelledby'?: string; + /** + * Add classnames to the checkbox. + */ + className?: string; + /** + * Checkbox id attribute. + */ + id: string; + /** + * Checkbox name attribute. + */ + name: string; + /** + * Callback function to set checkbox value. + */ + setValue: (value: SetStateAction<boolean>) => void; + /** + * Checkbox value. + */ + value: boolean; +}; + +/** + * Checkbox component + * + * Render a checkbox type input. + */ +const Checkbox: VFC<CheckboxProps> = ({ value, setValue, ...props }) => { + return ( + <input + type="checkbox" + checked={value} + onChange={() => setValue(!value)} + {...props} + /> + ); +}; + +export default Checkbox; diff --git a/src/components/atoms/forms/field.stories.tsx b/src/components/atoms/forms/field.stories.tsx new file mode 100644 index 0000000..ec81922 --- /dev/null +++ b/src/components/atoms/forms/field.stories.tsx @@ -0,0 +1,201 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { useState } from 'react'; +import FieldComponent from './field'; + +export default { + title: 'Atoms/Forms', + component: FieldComponent, + args: { + disabled: false, + required: false, + type: 'text', + }, + 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: 'Add 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, + }, + }, + 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<typeof FieldComponent>; + +const Template: ComponentStory<typeof FieldComponent> = ({ + value: _value, + setValue: _setValue, + ...args +}) => { + const [value, setValue] = useState<string>(''); + + return <FieldComponent value={value} setValue={setValue} {...args} />; +}; + +export const Field = Template.bind({}); +Field.args = { + id: 'field-storybook', + name: 'field-storybook', +}; diff --git a/src/components/atoms/forms/field.test.tsx b/src/components/atoms/forms/field.test.tsx new file mode 100644 index 0000000..a04a976 --- /dev/null +++ b/src/components/atoms/forms/field.test.tsx @@ -0,0 +1,30 @@ +import { render, screen } from '@test-utils'; +import Field from './field'; + +describe('Field', () => { + it('renders a text input', () => { + render( + <Field + id="text-field" + name="text-field" + type="text" + value="" + setValue={() => null} + /> + ); + expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text'); + }); + + it('renders a search input', () => { + render( + <Field + id="search-field" + name="search-field" + type="search" + value="" + setValue={() => null} + /> + ); + expect(screen.getByRole('searchbox')).toHaveAttribute('type', 'search'); + }); +}); diff --git a/src/components/atoms/forms/field.tsx b/src/components/atoms/forms/field.tsx new file mode 100644 index 0000000..2e75d0f --- /dev/null +++ b/src/components/atoms/forms/field.tsx @@ -0,0 +1,107 @@ +import { ChangeEvent, SetStateAction, VFC } from 'react'; +import styles from './forms.module.scss'; + +export type FieldType = + | 'datetime-local' + | 'email' + | 'number' + | 'search' + | 'tel' + | 'text' + | 'textarea' + | 'time' + | 'url'; + +export type FieldProps = { + /** + * One or more ids that refers to the field name. + */ + 'aria-labelledby'?: string; + /** + * Add classnames to the field. + */ + className?: string; + /** + * Field state. Either enabled (false) or disabled (true). + */ + disabled?: boolean; + /** + * Field id attribute. + */ + id: string; + /** + * Field maximum value. + */ + max?: number | string; + /** + * Field minimum value. + */ + min?: number | string; + /** + * Field name attribute. + */ + name: string; + /** + * Placeholder value. + */ + placeholder?: string; + /** + * True if the field is required. Default: false. + */ + required?: boolean; + /** + * Callback function to set field value. + */ + setValue: (value: SetStateAction<string>) => void; + /** + * Field incremental values that are valid. + */ + step?: number | string; + /** + * Field type. Default: text. + */ + type: FieldType; + /** + * Field value. + */ + value: string; +}; + +/** + * Field component. + * + * Render either an input or a textarea. + */ +const Field: VFC<FieldProps> = ({ + className = '', + setValue, + type, + ...props +}) => { + /** + * Update select value when an option is selected. + * @param e - The option change event. + */ + const updateValue = ( + e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> + ) => { + setValue(e.target.value); + }; + + return type === 'textarea' ? ( + <textarea + onChange={updateValue} + className={`${styles.field} ${styles['field--textarea']} ${className}`} + {...props} + /> + ) : ( + <input + type={type} + onChange={updateValue} + className={`${styles.field} ${className}`} + {...props} + /> + ); +}; + +export default Field; diff --git a/src/components/atoms/forms/form.test.tsx b/src/components/atoms/forms/form.test.tsx new file mode 100644 index 0000000..9cd3c58 --- /dev/null +++ b/src/components/atoms/forms/form.test.tsx @@ -0,0 +1,9 @@ +import { render, screen } from '@test-utils'; +import Form from './form'; + +describe('Form', () => { + it('renders a form', () => { + render(<Form aria-label="Jest form" onSubmit={() => null}></Form>); + expect(screen.getByRole('form', { name: 'Jest form' })).toBeInTheDocument(); + }); +}); diff --git a/src/components/atoms/forms/form.tsx b/src/components/atoms/forms/form.tsx new file mode 100644 index 0000000..8e80930 --- /dev/null +++ b/src/components/atoms/forms/form.tsx @@ -0,0 +1,73 @@ +import { Children, FC, FormEvent, Fragment } from 'react'; +import styles from './forms.module.scss'; + +export type FormProps = { + /** + * An accessible name. + */ + 'aria-label'?: string; + /** + * One or more ids that refers to the form name. + */ + 'aria-labelledby'?: string; + /** + * Set additional classnames to the form wrapper. + */ + className?: string; + /** + * Wrap each items with a div. Default: true. + */ + grouped?: boolean; + /** + * A callback function to execute on submit. + */ + onSubmit: () => void; +}; + +/** + * Form component. + * + * Render children wrapped in a form element. + */ +const Form: FC<FormProps> = ({ + children, + className = '', + grouped = true, + onSubmit, + ...props +}) => { + const arrayChildren = Children.toArray(children); + + /** + * Get the form items. + * @returns {JSX.Element[]} An array of child elements wrapped in a div. + */ + const getFormItems = (): JSX.Element[] => { + return arrayChildren.map((child, index) => + grouped ? ( + <div key={`item-${index}`} className={styles.item}> + {child} + </div> + ) : ( + <Fragment key={`item-${index}`}>{child}</Fragment> + ) + ); + }; + + /** + * Handle form submit. + * @param {FormEvent} e - The form event. + */ + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + onSubmit(); + }; + + return ( + <form onSubmit={handleSubmit} className={className} {...props}> + {getFormItems()} + </form> + ); +}; + +export default Form; diff --git a/src/components/atoms/forms/forms.module.scss b/src/components/atoms/forms/forms.module.scss new file mode 100644 index 0000000..19c7aee --- /dev/null +++ b/src/components/atoms/forms/forms.module.scss @@ -0,0 +1,53 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; + +.item { + margin: var(--spacing-xs) 0; + width: 100%; + max-width: 45ch; +} + +.field { + padding: var(--spacing-2xs) var(--spacing-xs); + background: var(--color-bg-tertiary); + border: fun.convert-px(2) solid var(--color-border); + box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-shadow); + transition: all 0.25s linear 0s; + + &--select { + cursor: pointer; + + @include mix.pointer("fine") { + padding: fun.convert-px(3) var(--spacing-xs); + } + } + + &--textarea { + min-height: fun.convert-px(200); + } + + &:disabled { + background: var(--color-bg-secondary); + border: fun.convert-px(2) solid var(--color-border-light); + box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 + var(--color-shadow-light); + cursor: not-allowed; + } + + &:not(:disabled) { + &:hover { + box-shadow: fun.convert-px(5) fun.convert-px(5) 0 fun.convert-px(1) + var(--color-shadow); + transform: translate(#{fun.convert-px(-3)}, #{fun.convert-px(-3)}); + } + + &:focus { + background: var(--color-bg); + border-color: var(--color-primary); + box-shadow: 0 0 0 0 var(--color-shadow); + transform: translate(#{fun.convert-px(3)}, #{fun.convert-px(3)}); + outline: none; + transition: all 0.2s ease-in-out 0s, transform 0.3s ease-out 0s; + } + } +} diff --git a/src/components/atoms/forms/label.module.scss b/src/components/atoms/forms/label.module.scss new file mode 100644 index 0000000..f900925 --- /dev/null +++ b/src/components/atoms/forms/label.module.scss @@ -0,0 +1,17 @@ +.label { + color: var(--color-primary-darker); + font-weight: 600; + + &--small { + font-size: var(--font-size-sm); + font-variant: small-caps; + } + + &--medium { + font-size: var(--font-size-md); + } +} + +.required { + color: var(--color-secondary); +} diff --git a/src/components/atoms/forms/label.stories.tsx b/src/components/atoms/forms/label.stories.tsx new file mode 100644 index 0000000..463e8ac --- /dev/null +++ b/src/components/atoms/forms/label.stories.tsx @@ -0,0 +1,85 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import LabelComponent from './label'; + +export default { + title: 'Atoms/Forms', + component: LabelComponent, + args: { + required: false, + size: 'small', + }, + argTypes: { + className: { + control: { + type: 'text', + }, + description: 'Add classnames to the label.', + table: { + category: 'Styles', + }, + type: { + name: 'string', + required: false, + }, + }, + children: { + control: { + type: 'text', + }, + description: 'The label body.', + type: { + name: 'string', + required: true, + }, + }, + htmlFor: { + control: { + type: 'text', + }, + description: 'The field id.', + type: { + name: 'string', + required: true, + }, + }, + required: { + control: { + type: 'boolean', + }, + description: 'Set to true if the field is required.', + table: { + category: 'Options', + defaultValue: { summary: false }, + }, + type: { + name: 'boolean', + required: false, + }, + }, + size: { + control: { + type: 'select', + }, + description: 'The label size.', + options: ['medium', 'small'], + table: { + category: 'Options', + defaultValue: { summary: 'small' }, + }, + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta<typeof LabelComponent>; + +const Template: ComponentStory<typeof LabelComponent> = ({ + children, + ...args +}) => <LabelComponent {...args}>{children}</LabelComponent>; + +export const Label = Template.bind({}); +Label.args = { + children: 'A label', +}; diff --git a/src/components/atoms/forms/label.test.tsx b/src/components/atoms/forms/label.test.tsx new file mode 100644 index 0000000..14257c3 --- /dev/null +++ b/src/components/atoms/forms/label.test.tsx @@ -0,0 +1,9 @@ +import { render, screen } from '@test-utils'; +import Label from './label'; + +describe('Label', () => { + it('renders a field label', () => { + render(<Label>A label</Label>); + expect(screen.getByText('A label')).toBeDefined(); + }); +}); diff --git a/src/components/atoms/forms/label.tsx b/src/components/atoms/forms/label.tsx new file mode 100644 index 0000000..8d57ee2 --- /dev/null +++ b/src/components/atoms/forms/label.tsx @@ -0,0 +1,45 @@ +import { FC } from 'react'; +import styles from './label.module.scss'; + +export type LabelProps = { + /** + * Add classnames to the label. + */ + className?: string; + /** + * The field id. + */ + htmlFor?: string; + /** + * Is the field required? Default: false. + */ + required?: boolean; + /** + * The label size. Default: small. + */ + size?: 'medium' | 'small'; +}; + +/** + * Label Component + * + * Render a HTML label element. + */ +const Label: FC<LabelProps> = ({ + children, + className = '', + required = false, + size = 'small', + ...props +}) => { + const sizeClass = styles[`label--${size}`]; + + return ( + <label className={`${styles.label} ${sizeClass} ${className}`} {...props}> + {children} + {required && <span className={styles.required}> *</span>} + </label> + ); +}; + +export default Label; diff --git a/src/components/atoms/forms/select.stories.tsx b/src/components/atoms/forms/select.stories.tsx new file mode 100644 index 0000000..c2fb8c6 --- /dev/null +++ b/src/components/atoms/forms/select.stories.tsx @@ -0,0 +1,145 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { useState } from 'react'; +import SelectComponent from './select'; + +const selectOptions = [ + { id: 'option1', name: 'Option 1', value: 'option1' }, + { id: 'option2', name: 'Option 2', value: 'option2' }, + { id: 'option3', name: 'Option 3', value: 'option3' }, +]; + +export default { + title: 'Atoms/Forms', + component: SelectComponent, + args: { + disabled: false, + required: false, + }, + argTypes: { + 'aria-labelledby': { + control: { + type: 'text', + }, + description: 'One or more ids that refers to the select field name.', + table: { + category: 'Accessibility', + }, + type: { + name: 'string', + required: false, + }, + }, + className: { + control: { + type: 'text', + }, + description: 'Add classnames to the select 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, + }, + }, + name: { + control: { + type: 'text', + }, + description: 'Field name.', + type: { + name: 'string', + required: true, + }, + }, + options: { + 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, + }, + }, + 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<typeof SelectComponent>; + +const Template: ComponentStory<typeof SelectComponent> = ({ + value, + setValue: _setValue, + ...args +}) => { + const [selected, setSelected] = useState<string>(value); + + return <SelectComponent value={selected} setValue={setSelected} {...args} />; +}; + +export const Select = Template.bind({}); +Select.args = { + id: 'storybook-select', + name: 'storybook-select', + options: selectOptions, + value: 'option2', +}; diff --git a/src/components/atoms/forms/select.test.tsx b/src/components/atoms/forms/select.test.tsx new file mode 100644 index 0000000..22efb86 --- /dev/null +++ b/src/components/atoms/forms/select.test.tsx @@ -0,0 +1,30 @@ +import { render, screen } from '@test-utils'; +import Select from './select'; + +const selectOptions = [ + { id: 'option1', name: 'Option 1', value: 'option1' }, + { id: 'option2', name: 'Option 2', value: 'option2' }, + { id: 'option3', name: 'Option 3', value: 'option3' }, +]; +const selected = selectOptions[0]; + +describe('Select', () => { + it('should correctly set default option', () => { + render( + <Select + id="jest-select" + name="jest-select" + options={selectOptions} + value={selected.value} + setValue={() => null} + /> + ); + expect(screen.getByRole('combobox')).toHaveValue(selected.value); + expect(screen.queryByRole('combobox')).not.toHaveValue( + selectOptions[1].value + ); + expect(screen.queryByRole('combobox')).not.toHaveValue( + selectOptions[2].value + ); + }); +}); diff --git a/src/components/atoms/forms/select.tsx b/src/components/atoms/forms/select.tsx new file mode 100644 index 0000000..25e86e0 --- /dev/null +++ b/src/components/atoms/forms/select.tsx @@ -0,0 +1,99 @@ +import { ChangeEvent, SetStateAction, VFC } from 'react'; +import styles from './forms.module.scss'; + +export type SelectOptions = { + /** + * The option id. + */ + id: string; + /** + * The option name. + */ + name: string; + /** + * The option value. + */ + value: string; +}; + +export type SelectProps = { + /** + * One or more ids that refers to the select field name. + */ + 'aria-labelledby'?: string; + /** + * Add classnames to the select field. + */ + className?: string; + /** + * Field state. Either enabled (false) or disabled (true). + */ + disabled?: boolean; + /** + * Field id attribute. + */ + id: string; + /** + * Field name attribute. + */ + name: string; + /** + * True if the field is required. Default: false. + */ + options: SelectOptions[]; + /** + * True if the field is required. Default: false. + */ + required?: boolean; + /** + * Callback function to set field value. + */ + setValue: (value: SetStateAction<string>) => void; + /** + * Field value. + */ + value: string; +}; + +/** + * Select component + * + * Render a HTML select element. + */ +const Select: VFC<SelectProps> = ({ + className = '', + options, + setValue, + ...props +}) => { + /** + * Update select value when an option is selected. + * @param e - The option change event. + */ + const updateValue = (e: ChangeEvent<HTMLSelectElement>) => { + setValue(e.target.value); + }; + + /** + * Get the option elements. + * @returns {JSX.Element[]} An array of HTML option elements. + */ + const getOptions = (): JSX.Element[] => + options.map((option) => ( + <option key={option.id} value={option.value}> + {option.name} + </option> + )); + + return ( + <select + className={`${styles.field} ${styles['field--select']} ${className}`} + onChange={updateValue} + {...props} + > + {getOptions()} + </select> + ); +}; + +export default Select; |
