diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-02-25 19:17:09 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-02-25 19:17:09 +0100 |
| commit | e26d821f738525477472e631d170d9ed218c1603 (patch) | |
| tree | 70ec0c29d003d462de6926f1faa09354e3ff6d90 /src | |
| parent | cb4764f8670f67627c407591c89b8d3637c190a7 (diff) | |
chore: combine input/textarea/select in a single component
Diffstat (limited to 'src')
24 files changed, 230 insertions, 259 deletions
diff --git a/src/components/CommentForm/CommentForm.tsx b/src/components/CommentForm/CommentForm.tsx index 3539311..c409d04 100644 --- a/src/components/CommentForm/CommentForm.tsx +++ b/src/components/CommentForm/CommentForm.tsx @@ -1,5 +1,5 @@ import { ButtonSubmit } from '@components/Buttons'; -import { Form, FormItem, Input, Label, TextArea } from '@components/Form'; +import { Field, Form, FormItem, Label } from '@components/FormElements'; import Notice from '@components/Notice/Notice'; import Spinner from '@components/Spinner/Spinner'; import { createComment } from '@services/graphql/mutations'; @@ -144,30 +144,32 @@ const CommentForm = ( </h2> <Form submitHandler={submitHandler} - modifier={isReply ? 'centered' : undefined} + kind={isReply ? 'centered' : undefined} > <FormItem> - <Input + <Field id="commenter-name" name="commenter-name" label={getLabel(nameLabelBody, 'commenter-name', true)} value={name} setValue={setName} + required={true} ref={ref} /> </FormItem> <FormItem> - <Input + <Field id="commenter-email" name="commenter-email" - type="email" + kind="email" label={getLabel(emailLabelBody, 'commenter-email', true)} value={email} setValue={setEmail} + required={true} /> </FormItem> <FormItem> - <Input + <Field id="commenter-website" name="commenter-website" label={getLabel(websiteLabelBody, 'commenter-website')} @@ -176,12 +178,14 @@ const CommentForm = ( /> </FormItem> <FormItem> - <TextArea + <Field id="commenter-comment" name="commenter-comment" + kind="textarea" label={getLabel(commentLabelBody, 'commenter-comment', true)} value={comment} setValue={setComment} + required={true} /> </FormItem> <FormItem> diff --git a/src/components/ContactForm/ContactForm.tsx b/src/components/ContactForm/ContactForm.tsx index d0e5ebe..48772a0 100644 --- a/src/components/ContactForm/ContactForm.tsx +++ b/src/components/ContactForm/ContactForm.tsx @@ -1,5 +1,5 @@ import { ButtonSubmit } from '@components/Buttons'; -import { Form, FormItem, Input, Label, TextArea } from '@components/Form'; +import { Field, Form, FormItem, Label } from '@components/FormElements'; import { sendMail } from '@services/graphql/mutations'; import { settings } from '@utils/config'; import { FormEvent, useState } from 'react'; @@ -115,26 +115,28 @@ const ContactForm = () => { <> <Form submitHandler={submitHandler}> <FormItem> - <Input + <Field id="contact-name" name="name" value={name} setValue={setName} + required={true} label={getLabel(nameLabelBody, 'contact-name', true)} /> </FormItem> <FormItem> - <Input + <Field id="contact-email" - type="email" + kind="email" name="email" value={email} setValue={setEmail} + required={true} label={getLabel(emailLabelBody, 'contact-email', true)} /> </FormItem> <FormItem> - <Input + <Field id="contact-subject" name="subject" value={subject} @@ -143,11 +145,13 @@ const ContactForm = () => { /> </FormItem> <FormItem> - <TextArea + <Field id="contact-message" + kind="textarea" name="message" value={message} setValue={setMessage} + required={true} label={getLabel(messageLabelBody, 'contact-message', true)} /> </FormItem> diff --git a/src/components/Form/Form.module.scss b/src/components/Form/Form.module.scss deleted file mode 100644 index 6ccdb11..0000000 --- a/src/components/Form/Form.module.scss +++ /dev/null @@ -1,70 +0,0 @@ -@use "@styles/abstracts/functions" as fun; - -.wrapper { - width: 100%; - - &--search, - &--toggle { - display: flex; - flex-flow: row nowrap; - align-items: center; - } - - &--toggle { - position: relative; - margin: var(--spacing-sm) 0; - } - - &--centered { - max-width: 45ch; - margin-left: auto; - margin-right: auto; - } -} - -.item { - margin: var(--spacing-xs) 0; - max-width: 45ch; -} - -.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; - - &: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-darker); - 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; - } -} - -.textarea { - min-height: fun.convert-px(200); -} - -.wrapper--search { - > input { - padding-right: calc(var(--btn-size) + var(--spacing-2xs)); - - &:hover ~ button { - transform: translate(fun.convert-px(-3), fun.convert-px(-3)); - } - - &:focus ~ button { - transform: translate(fun.convert-px(3), fun.convert-px(3)); - } - } -} diff --git a/src/components/Form/FormItem/FormItem.tsx b/src/components/Form/FormItem/FormItem.tsx deleted file mode 100644 index 0f12e64..0000000 --- a/src/components/Form/FormItem/FormItem.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import styles from '../Form.module.scss'; - -const FormItem: React.FunctionComponent = ({ children }) => { - return <div className={styles.item}>{children}</div>; -}; - -export default FormItem; diff --git a/src/components/Form/Input/Input.tsx b/src/components/Form/Input/Input.tsx deleted file mode 100644 index 07f0410..0000000 --- a/src/components/Form/Input/Input.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { - ChangeEvent, - ForwardedRef, - forwardRef, - ReactElement, - SetStateAction, -} from 'react'; -import styles from '../Form.module.scss'; - -type InputType = 'text' | 'email' | 'number' | 'search'; - -const Input = ( - { - id, - name, - value, - setValue, - type = 'text', - label, - }: { - id: string; - name: string; - value: string; - setValue: (value: SetStateAction<string>) => void; - type?: InputType; - label?: ReactElement; - }, - ref: ForwardedRef<HTMLInputElement> -) => { - const updateValue = (e: ChangeEvent<HTMLInputElement>) => { - setValue(e.target.value); - }; - - return ( - <> - {label} - <input - ref={ref} - type={type} - id={id} - name={name} - value={value} - onChange={updateValue} - className={styles.field} - /> - </> - ); -}; - -export default forwardRef(Input); diff --git a/src/components/Form/Select/Select.tsx b/src/components/Form/Select/Select.tsx deleted file mode 100644 index feab991..0000000 --- a/src/components/Form/Select/Select.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { ChangeEvent, ReactElement, SetStateAction } from 'react'; -import styles from './Select.module.scss'; - -type SelectOptions = { - id: string; - name: string; - value: string; -}; - -const Select = ({ - options, - id, - name, - value, - setValue, - required = false, - label, -}: { - options: SelectOptions[]; - id: string; - name: string; - value: string; - setValue: (value: SetStateAction<string>) => void; - required?: boolean; - label?: ReactElement; -}) => { - const getOptions = () => { - return options.map((option) => ( - <option key={option.id} value={option.value}> - {option.name} - </option> - )); - }; - - const handleChange = (event: ChangeEvent<HTMLSelectElement>) => { - setValue(event.target.value); - }; - - return ( - <> - {label} - <select - name={name} - id={id} - value={value} - onChange={handleChange} - required={required} - className={styles.wrapper} - > - {getOptions()} - </select> - </> - ); -}; - -export default Select; diff --git a/src/components/Form/TextArea/TextArea.tsx b/src/components/Form/TextArea/TextArea.tsx deleted file mode 100644 index b8894ab..0000000 --- a/src/components/Form/TextArea/TextArea.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { ChangeEvent, ReactElement, SetStateAction } from 'react'; -import styles from '../Form.module.scss'; - -const TextArea = ({ - id, - name, - value, - setValue, - label, -}: { - id: string; - name: string; - value: string; - setValue: (value: SetStateAction<string>) => void; - label?: ReactElement; -}) => { - const updateValue = (e: ChangeEvent<HTMLTextAreaElement>) => { - setValue(e.target.value); - }; - - return ( - <> - {label} - <textarea - id={id} - name={name} - value={value} - onChange={updateValue} - className={`${styles.field} ${styles.textarea}`} - /> - </> - ); -}; - -export default TextArea; diff --git a/src/components/Form/index.tsx b/src/components/Form/index.tsx deleted file mode 100644 index cbe1ec4..0000000 --- a/src/components/Form/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import Form from './Form'; -import FormItem from './FormItem/FormItem'; -import Input from './Input/Input'; -import Label from './Label/Label'; -import Select from './Select/Select'; -import TextArea from './TextArea/TextArea'; -import Toggle from './Toggle/Toggle'; - -export { Form, FormItem, Input, Label, Select, TextArea, Toggle }; diff --git a/src/components/Form/Select/Select.module.scss b/src/components/FormElements/Field/Field.module.scss index d4a40eb..3836856 100644 --- a/src/components/Form/Select/Select.module.scss +++ b/src/components/FormElements/Field/Field.module.scss @@ -1,12 +1,35 @@ @use "@styles/abstracts/functions" as fun; -.wrapper { - padding: fun.convert-px(3) var(--spacing-xs); +.field { 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; + + &:not(.select) { + width: 100%; + padding: var(--spacing-2xs) var(--spacing-xs); + } + + &: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; + } +} + +.select { + padding: fun.convert-px(3) var(--spacing-xs); cursor: pointer; - transition: all 0.3s ease-in-out 0s; &:hover { box-shadow: fun.convert-px(4) fun.convert-px(4) 0 fun.convert-px(1) @@ -15,9 +38,11 @@ } &: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)}); } } + +.textarea { + min-height: fun.convert-px(200); +} diff --git a/src/components/FormElements/Field/Field.tsx b/src/components/FormElements/Field/Field.tsx new file mode 100644 index 0000000..c8df0f9 --- /dev/null +++ b/src/components/FormElements/Field/Field.tsx @@ -0,0 +1,106 @@ +import { + ChangeEvent, + ForwardedRef, + forwardRef, + ReactElement, + SetStateAction, +} from 'react'; +import styles from './Field.module.scss'; + +type FieldType = 'email' | 'number' | 'search' | 'select' | 'text' | 'textarea'; +type SelectOptions = { + id: string; + name: string; + value: string; +}; + +const Field = ( + { + id, + name, + value, + setValue, + required = false, + kind = 'text', + label, + options, + }: { + id: string; + name: string; + value: string; + setValue: (value: SetStateAction<string>) => void; + required?: boolean; + kind?: FieldType; + label?: ReactElement; + options?: SelectOptions[]; + }, + ref: ForwardedRef<HTMLInputElement | HTMLTextAreaElement> +) => { + const updateValue = ( + e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> + ) => { + setValue(e.target.value); + }; + + const getOptions = () => { + return options + ? options.map((option) => ( + <option key={option.id} value={option.value}> + {option.name} + </option> + )) + : ''; + }; + + const getField = () => { + switch (kind) { + case 'select': + return ( + <select + name={name} + id={id} + value={value} + onChange={updateValue} + required={required} + className={`${styles.field} ${styles.select}`} + > + {getOptions()} + </select> + ); + case 'textarea': + return ( + <textarea + ref={ref as ForwardedRef<HTMLTextAreaElement>} + id={id} + name={name} + value={value} + required={required} + onChange={updateValue} + className={`${styles.field} ${styles.textarea}`} + /> + ); + default: + return ( + <input + ref={ref as ForwardedRef<HTMLInputElement>} + type={kind} + id={id} + name={name} + value={value} + required={required} + onChange={updateValue} + className={styles.field} + /> + ); + } + }; + + return ( + <> + {label} + {getField()} + </> + ); +}; + +export default forwardRef(Field); diff --git a/src/components/FormElements/Form/Form.module.scss b/src/components/FormElements/Form/Form.module.scss new file mode 100644 index 0000000..0f7c437 --- /dev/null +++ b/src/components/FormElements/Form/Form.module.scss @@ -0,0 +1,37 @@ +@use "@styles/abstracts/functions" as fun; + +.wrapper { + width: 100%; +} + +.centered { + max-width: 45ch; + margin-left: auto; + margin-right: auto; +} + +.search { + display: flex; + flex-flow: row nowrap; + align-items: center; + + > input { + padding-right: calc(var(--btn-size) + var(--spacing-2xs)); + + &:hover ~ button { + transform: translate(fun.convert-px(-3), fun.convert-px(-3)); + } + + &:focus ~ button { + transform: translate(fun.convert-px(3), fun.convert-px(3)); + } + } +} + +.settings { + display: flex; + flex-flow: row nowrap; + align-items: center; + margin: var(--spacing-sm) 0; + position: relative; +} diff --git a/src/components/Form/Form.tsx b/src/components/FormElements/Form/Form.tsx index 44763c4..10fdcdf 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/FormElements/Form/Form.tsx @@ -1,19 +1,21 @@ import { ReactNode } from 'react'; import styles from './Form.module.scss'; +type FormKind = 'centered' | 'search' | 'settings'; + const Form = ({ children, submitHandler, - modifier = '', + kind, id, }: { children: ReactNode; submitHandler: any; - modifier?: string; + kind?: FormKind; id?: string; }) => { - const withModifier = modifier ? styles[`wrapper--${modifier}`] : ''; - const classes = `${styles.wrapper} ${withModifier}`; + const kindStyles = kind ? styles[kind] : ''; + const classes = `${styles.wrapper} ${kindStyles}`; return ( <form onSubmit={submitHandler} className={classes} id={id}> diff --git a/src/components/FormElements/FormItem/FormItem.module.scss b/src/components/FormElements/FormItem/FormItem.module.scss new file mode 100644 index 0000000..07ef56f --- /dev/null +++ b/src/components/FormElements/FormItem/FormItem.module.scss @@ -0,0 +1,4 @@ +.wrapper { + margin: var(--spacing-xs) 0; + max-width: 45ch; +} diff --git a/src/components/FormElements/FormItem/FormItem.tsx b/src/components/FormElements/FormItem/FormItem.tsx new file mode 100644 index 0000000..8d674f1 --- /dev/null +++ b/src/components/FormElements/FormItem/FormItem.tsx @@ -0,0 +1,7 @@ +import styles from './FormItem.module.scss'; + +const FormItem: React.FunctionComponent = ({ children }) => { + return <div className={styles.wrapper}>{children}</div>; +}; + +export default FormItem; diff --git a/src/components/Form/Label/Label.module.scss b/src/components/FormElements/Label/Label.module.scss index c527b16..c527b16 100644 --- a/src/components/Form/Label/Label.module.scss +++ b/src/components/FormElements/Label/Label.module.scss diff --git a/src/components/Form/Label/Label.tsx b/src/components/FormElements/Label/Label.tsx index baedff0..baedff0 100644 --- a/src/components/Form/Label/Label.tsx +++ b/src/components/FormElements/Label/Label.tsx diff --git a/src/components/Form/Toggle/Toggle.module.scss b/src/components/FormElements/Toggle/Toggle.module.scss index 48c88f6..48c88f6 100644 --- a/src/components/Form/Toggle/Toggle.module.scss +++ b/src/components/FormElements/Toggle/Toggle.module.scss diff --git a/src/components/Form/Toggle/Toggle.tsx b/src/components/FormElements/Toggle/Toggle.tsx index 36636e4..4db7d43 100644 --- a/src/components/Form/Toggle/Toggle.tsx +++ b/src/components/FormElements/Toggle/Toggle.tsx @@ -24,7 +24,7 @@ const Toggle = ({ }; return ( - <Form modifier="toggle" submitHandler={onSubmit}> + <Form kind="settings" submitHandler={onSubmit}> <input className={styles.checkbox} type="checkbox" diff --git a/src/components/FormElements/index.tsx b/src/components/FormElements/index.tsx new file mode 100644 index 0000000..8ca69b4 --- /dev/null +++ b/src/components/FormElements/index.tsx @@ -0,0 +1,7 @@ +import Field from './Field/Field'; +import Form from './Form/Form'; +import FormItem from './FormItem/FormItem'; +import Label from './Label/Label'; +import Toggle from './Toggle/Toggle'; + +export { Field, Form, FormItem, Label, Toggle }; diff --git a/src/components/SearchForm/SearchForm.tsx b/src/components/SearchForm/SearchForm.tsx index fa3ad07..fe809a3 100644 --- a/src/components/SearchForm/SearchForm.tsx +++ b/src/components/SearchForm/SearchForm.tsx @@ -1,5 +1,5 @@ import { ButtonSubmit } from '@components/Buttons'; -import { Form, Input } from '@components/Form'; +import { Field, Form } from '@components/FormElements'; import { SearchIcon } from '@components/Icons'; import { useRouter } from 'next/router'; import { FormEvent, useEffect, useRef, useState } from 'react'; @@ -34,20 +34,21 @@ const SearchForm = ({ isOpened }: { isOpened: boolean }) => { description: 'SearchForm : form title', })} </div> - <Form submitHandler={launchSearch} modifier="search" id="search"> + <Form submitHandler={launchSearch} kind="search" id="search"> <label htmlFor="search-query" className="screen-reader-text"> {intl.formatMessage({ defaultMessage: 'Keywords:', description: 'SearchForm: search field label', })} </label> - <Input + <Field ref={inputRef} id="search-query" name="search-query" - type="search" + kind="search" value={query} setValue={setQuery} + required={true} /> <ButtonSubmit modifier="search"> <SearchIcon /> diff --git a/src/components/Settings/AckeeSelect/AckeeSelect.tsx b/src/components/Settings/AckeeSelect/AckeeSelect.tsx index 43110c4..e1a8ed8 100644 --- a/src/components/Settings/AckeeSelect/AckeeSelect.tsx +++ b/src/components/Settings/AckeeSelect/AckeeSelect.tsx @@ -1,4 +1,4 @@ -import { Label, Select } from '@components/Form'; +import { Field, Label } from '@components/FormElements'; import Tooltip from '@components/Tooltip/Tooltip'; import { LocalStorage } from '@services/local-storage'; import { useAckeeTracker } from '@utils/providers/ackee'; @@ -67,9 +67,10 @@ const AckeeSelect = () => { return ( <div className={styles.wrapper}> - <Select + <Field id="ackee-settings" name="ackee-settings" + kind="select" label={label} options={options} value={value} diff --git a/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx b/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx index 06f7ac8..6f42f86 100644 --- a/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx +++ b/src/components/Settings/PrismThemeToggle/PrismThemeToggle.tsx @@ -1,4 +1,4 @@ -import { Toggle } from '@components/Form'; +import { Toggle } from '@components/FormElements'; import { MoonIcon, SunIcon } from '@components/Icons'; import Spinner from '@components/Spinner/Spinner'; import { usePrismTheme } from '@utils/providers/prism'; diff --git a/src/components/Settings/ReduceMotion/ReduceMotion.tsx b/src/components/Settings/ReduceMotion/ReduceMotion.tsx index c7b5775..d0f7980 100644 --- a/src/components/Settings/ReduceMotion/ReduceMotion.tsx +++ b/src/components/Settings/ReduceMotion/ReduceMotion.tsx @@ -1,4 +1,4 @@ -import { Toggle } from '@components/Form'; +import { Toggle } from '@components/FormElements'; import { LocalStorage } from '@services/local-storage'; import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; diff --git a/src/components/Settings/ThemeToggle/ThemeToggle.tsx b/src/components/Settings/ThemeToggle/ThemeToggle.tsx index 5b7a34d..77b2797 100644 --- a/src/components/Settings/ThemeToggle/ThemeToggle.tsx +++ b/src/components/Settings/ThemeToggle/ThemeToggle.tsx @@ -1,4 +1,4 @@ -import { Toggle } from '@components/Form'; +import { Toggle } from '@components/FormElements'; import { MoonIcon, SunIcon } from '@components/Icons'; import Spinner from '@components/Spinner/Spinner'; import { useTheme } from 'next-themes'; |
