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/forms.module.scss | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/components/atoms/forms/forms.module.scss (limited to 'src/components/atoms/forms/forms.module.scss') diff --git a/src/components/atoms/forms/forms.module.scss b/src/components/atoms/forms/forms.module.scss new file mode 100644 index 0000000..5347bad --- /dev/null +++ b/src/components/atoms/forms/forms.module.scss @@ -0,0 +1,39 @@ +@use "@styles/abstracts/functions" as fun; + +.field { + width: 100%; + 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; + + &--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; + } + } +} -- cgit v1.2.3 From d67328f866fa469b67e2525556388d4bcc283737 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Thu, 31 Mar 2022 18:04:43 +0200 Subject: chore: add a Select component --- src/components/atoms/forms/forms.module.scss | 4 + src/components/atoms/forms/select.stories.tsx | 116 ++++++++++++++++++++++++++ src/components/atoms/forms/select.tsx | 77 +++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 src/components/atoms/forms/select.stories.tsx create mode 100644 src/components/atoms/forms/select.tsx (limited to 'src/components/atoms/forms/forms.module.scss') diff --git a/src/components/atoms/forms/forms.module.scss b/src/components/atoms/forms/forms.module.scss index 5347bad..5a61522 100644 --- a/src/components/atoms/forms/forms.module.scss +++ b/src/components/atoms/forms/forms.module.scss @@ -8,6 +8,10 @@ box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-shadow); transition: all 0.25s linear 0s; + &--select { + cursor: pointer; + } + &--textarea { min-height: fun.convert-px(200); } diff --git a/src/components/atoms/forms/select.stories.tsx b/src/components/atoms/forms/select.stories.tsx new file mode 100644 index 0000000..683e3b5 --- /dev/null +++ b/src/components/atoms/forms/select.stories.tsx @@ -0,0 +1,116 @@ +import { ComponentMeta, ComponentStory } from '@storybook/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, + 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, + }, + }, + name: { + control: { + type: 'text', + }, + description: 'Field name.', + table: { + category: 'Options', + }, + type: { + name: 'string', + required: false, + }, + }, + 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, + }, + }, + setValue: { + control: { + type: null, + }, + description: 'Callback function to set field value.', + table: { + category: 'Events', + }, + type: { + name: 'function', + required: true, + }, + }, + value: { + control: { + type: 'text', + }, + description: 'Field value.', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +export const Select = Template.bind({}); +Select.args = { + options: selectOptions, + setValue: () => null, + value: 'option2', +}; diff --git a/src/components/atoms/forms/select.tsx b/src/components/atoms/forms/select.tsx new file mode 100644 index 0000000..6e46660 --- /dev/null +++ b/src/components/atoms/forms/select.tsx @@ -0,0 +1,77 @@ +import { ChangeEvent, FC, SetStateAction } from 'react'; +import styles from './forms.module.scss'; + +type SelectOptions = { + id: string; + name: string; + value: string; +}; + +type SelectProps = { + /** + * 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) => void; + /** + * Field value. + */ + value: string; +}; + +/** + * Select component + * + * Render a HTML select element. + */ +const Select: FC = ({ options, setValue, ...props }) => { + /** + * Update select value when an option is selected. + * @param e - The option change event. + */ + const updateValue = (e: ChangeEvent) => { + setValue(e.target.value); + }; + + /** + * Get the option elements. + * @returns {JSX.Element[]} An array of HTML option elements. + */ + const getOptions = (): JSX.Element[] => + options.map((option) => ( + + )); + + return ( + + ); +}; + +export default Select; -- cgit v1.2.3 From 581632a626f81bce522be1cd809e9832d5b11c99 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Thu, 31 Mar 2022 18:21:30 +0200 Subject: chore: add a Label component --- src/components/atoms/forms/forms.module.scss | 12 +++++++ src/components/atoms/forms/label.stories.tsx | 53 ++++++++++++++++++++++++++++ src/components/atoms/forms/label.test.tsx | 9 +++++ src/components/atoms/forms/label.tsx | 23 ++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 src/components/atoms/forms/label.stories.tsx create mode 100644 src/components/atoms/forms/label.test.tsx create mode 100644 src/components/atoms/forms/label.tsx (limited to 'src/components/atoms/forms/forms.module.scss') diff --git a/src/components/atoms/forms/forms.module.scss b/src/components/atoms/forms/forms.module.scss index 5a61522..689a318 100644 --- a/src/components/atoms/forms/forms.module.scss +++ b/src/components/atoms/forms/forms.module.scss @@ -41,3 +41,15 @@ } } } + +.label { + display: block; + color: var(--color-primary-darker); + font-size: var(--font-size-sm); + font-variant: small-caps; + font-weight: 600; +} + +.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..06e8eb9 --- /dev/null +++ b/src/components/atoms/forms/label.stories.tsx @@ -0,0 +1,53 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import LabelComponent from './label'; + +export default { + title: 'Atoms/Forms', + component: LabelComponent, + argTypes: { + 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', + }, + type: { + name: 'boolean', + required: false, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => { + const { children, ...props } = args; + return {children}; +}; + +export const Label = Template.bind({}); +Label.args = { + children: 'A label', + htmlFor: 'a-field-id', +}; diff --git a/src/components/atoms/forms/label.test.tsx b/src/components/atoms/forms/label.test.tsx new file mode 100644 index 0000000..fcf1731 --- /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(); + 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..860cd73 --- /dev/null +++ b/src/components/atoms/forms/label.tsx @@ -0,0 +1,23 @@ +import { FC } from 'react'; +import styles from './forms.module.scss'; + +type LabelProps = { + htmlFor: string; + required?: boolean; +}; + +/** + * Label Component + * + * Render a HTML label element. + */ +const Label: FC = ({ children, required = false, ...props }) => { + return ( + + ); +}; + +export default Label; -- cgit v1.2.3 From 0b3146f7278929c4d1b33dd8f94f34e351e5e5a9 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 8 Apr 2022 22:36:24 +0200 Subject: chore: add a Settings modal component --- src/components/atoms/forms/field.stories.tsx | 45 +++++++++--- src/components/atoms/forms/field.tsx | 21 ++++-- src/components/atoms/forms/form.test.tsx | 9 +++ src/components/atoms/forms/form.tsx | 73 ++++++++++++++++++++ src/components/atoms/forms/forms.module.scss | 23 +++---- src/components/atoms/forms/label.module.scss | 17 +++++ src/components/atoms/forms/label.stories.tsx | 42 ++++++++++-- src/components/atoms/forms/label.test.tsx | 2 +- src/components/atoms/forms/label.tsx | 32 +++++++-- src/components/atoms/forms/select.stories.tsx | 44 +++++++++--- src/components/atoms/forms/select.tsx | 25 +++++-- src/components/atoms/forms/toggle.module.scss | 2 +- src/components/atoms/forms/toggle.tsx | 7 +- .../molecules/forms/labelled-field.module.scss | 9 +++ .../molecules/forms/labelled-field.stories.tsx | 33 +++++++-- src/components/molecules/forms/labelled-field.tsx | 34 +++++++-- .../molecules/forms/labelled-select.module.scss | 9 +++ .../molecules/forms/labelled-select.stories.tsx | 80 ++++++++++++++++++++-- src/components/molecules/forms/labelled-select.tsx | 51 ++++++++++++-- .../molecules/forms/motion-toggle.stories.tsx | 15 ++++ src/components/molecules/forms/motion-toggle.tsx | 8 ++- .../molecules/forms/prism-theme-toggle.stories.tsx | 15 ++++ .../molecules/forms/prism-theme-toggle.tsx | 11 ++- .../forms/select-with-tooltip.module.scss | 10 +-- .../forms/select-with-tooltip.stories.tsx | 77 +++++++++++++++++---- .../molecules/forms/select-with-tooltip.tsx | 34 +++++---- .../molecules/forms/theme-toggle.stories.tsx | 15 ++++ src/components/molecules/forms/theme-toggle.tsx | 8 ++- .../organisms/modals/settings-modal.module.scss | 14 ++++ .../organisms/modals/settings-modal.stories.tsx | 31 +++++++++ .../organisms/modals/settings-modal.test.tsx | 34 +++++++++ src/components/organisms/modals/settings-modal.tsx | 51 ++++++++++++++ 32 files changed, 761 insertions(+), 120 deletions(-) create mode 100644 src/components/atoms/forms/form.test.tsx create mode 100644 src/components/atoms/forms/form.tsx create mode 100644 src/components/atoms/forms/label.module.scss create mode 100644 src/components/molecules/forms/labelled-field.module.scss create mode 100644 src/components/molecules/forms/labelled-select.module.scss create mode 100644 src/components/organisms/modals/settings-modal.module.scss create mode 100644 src/components/organisms/modals/settings-modal.stories.tsx create mode 100644 src/components/organisms/modals/settings-modal.test.tsx create mode 100644 src/components/organisms/modals/settings-modal.tsx (limited to 'src/components/atoms/forms/forms.module.scss') diff --git a/src/components/atoms/forms/field.stories.tsx b/src/components/atoms/forms/field.stories.tsx index 02681e7..ec81922 100644 --- a/src/components/atoms/forms/field.stories.tsx +++ b/src/components/atoms/forms/field.stories.tsx @@ -1,4 +1,5 @@ import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { useState } from 'react'; import FieldComponent from './field'; export default { @@ -7,11 +8,35 @@ export default { args: { disabled: false, required: false, - setValue: () => null, type: 'text', - value: '', }, 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', @@ -148,7 +173,7 @@ export default { }, value: { control: { - type: 'text', + type: null, }, description: 'Field value.', type: { @@ -159,14 +184,18 @@ export default { }, } as ComponentMeta; -const Template: ComponentStory = (args) => ( - -); +const Template: ComponentStory = ({ + value: _value, + setValue: _setValue, + ...args +}) => { + const [value, setValue] = useState(''); + + return ; +}; export const Field = Template.bind({}); Field.args = { id: 'field-storybook', name: 'field-storybook', - setValue: () => null, - value: '', }; diff --git a/src/components/atoms/forms/field.tsx b/src/components/atoms/forms/field.tsx index 513d2ba..2e75d0f 100644 --- a/src/components/atoms/forms/field.tsx +++ b/src/components/atoms/forms/field.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, FC, SetStateAction } from 'react'; +import { ChangeEvent, SetStateAction, VFC } from 'react'; import styles from './forms.module.scss'; export type FieldType = @@ -13,6 +13,14 @@ export type FieldType = | '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). */ @@ -64,7 +72,12 @@ export type FieldProps = { * * Render either an input or a textarea. */ -const Field: FC = ({ setValue, type, ...props }) => { +const Field: VFC = ({ + className = '', + setValue, + type, + ...props +}) => { /** * Update select value when an option is selected. * @param e - The option change event. @@ -78,14 +91,14 @@ const Field: FC = ({ setValue, type, ...props }) => { return type === 'textarea' ? (