From b145ed4492de834f5cea9437e9772c4f7fbe90ec Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Thu, 31 Mar 2022 17:57:39 +0200 Subject: chore: add a Field component --- src/components/atoms/forms/field.stories.tsx | 176 +++++++++++++++++++++++++++ src/components/atoms/forms/field.test.tsx | 14 +++ src/components/atoms/forms/field.tsx | 94 ++++++++++++++ src/components/atoms/forms/forms.module.scss | 39 ++++++ 4 files changed, 323 insertions(+) create mode 100644 src/components/atoms/forms/field.stories.tsx create mode 100644 src/components/atoms/forms/field.test.tsx create mode 100644 src/components/atoms/forms/field.tsx create mode 100644 src/components/atoms/forms/forms.module.scss (limited to 'src') diff --git a/src/components/atoms/forms/field.stories.tsx b/src/components/atoms/forms/field.stories.tsx new file mode 100644 index 0000000..0406f10 --- /dev/null +++ b/src/components/atoms/forms/field.stories.tsx @@ -0,0 +1,176 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import FieldComponent from './field'; + +export default { + title: 'Atoms/Forms', + component: FieldComponent, + args: { + disabled: false, + required: false, + setValue: () => null, + type: 'text', + value: '', + }, + 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.', + table: { + category: 'Options', + }, + 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.', + table: { + category: 'Options', + }, + type: { + name: 'string', + required: false, + }, + }, + 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: 'text', + }, + description: 'Field value.', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +export const Field = Template.bind({}); +Field.args = { + setValue: () => null, + value: '', +}; diff --git a/src/components/atoms/forms/field.test.tsx b/src/components/atoms/forms/field.test.tsx new file mode 100644 index 0000000..5488220 --- /dev/null +++ b/src/components/atoms/forms/field.test.tsx @@ -0,0 +1,14 @@ +import { render, screen } from '@test-utils'; +import Field from './field'; + +describe('Field', () => { + it('renders a text input', () => { + render( null} />); + expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text'); + }); + + it('renders a search input', () => { + render( 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..7d1ac93 --- /dev/null +++ b/src/components/atoms/forms/field.tsx @@ -0,0 +1,94 @@ +import { ChangeEvent, FC, SetStateAction } from 'react'; +import styles from './forms.module.scss'; + +type FieldType = + | 'datetime-local' + | 'email' + | 'number' + | 'search' + | 'tel' + | 'text' + | 'textarea' + | 'time' + | 'url'; + +type FieldProps = { + /** + * 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) => 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: FC = ({ setValue, type, ...props }) => { + /** + * Update select value when an option is selected. + * @param e - The option change event. + */ + const updateValue = ( + e: ChangeEvent + ) => { + setValue(e.target.value); + }; + + return type === 'textarea' ? ( +