diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-05-24 17:54:23 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-05-24 17:54:23 +0200 |
| commit | 691646c97b09f9150ac823670d6c661358c81c1c (patch) | |
| tree | cc2115a23d9dae87b7cdfab9223f1366aa629c69 /src | |
| parent | 041fb0974f624368a45316c296c2a3e3c229dae2 (diff) | |
chore: give autofocus to the toolbar search form
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/atoms/forms/field.tsx | 24 | ||||
| -rw-r--r-- | src/components/molecules/forms/labelled-field.tsx | 21 | ||||
| -rw-r--r-- | src/components/organisms/forms/search-form.tsx | 24 | ||||
| -rw-r--r-- | src/components/organisms/modals/search-modal.tsx | 25 | ||||
| -rw-r--r-- | src/components/organisms/toolbar/search.tsx | 13 | ||||
| -rw-r--r-- | src/utils/hooks/use-input-autofocus.tsx | 39 |
6 files changed, 98 insertions, 48 deletions
diff --git a/src/components/atoms/forms/field.tsx b/src/components/atoms/forms/field.tsx index e45a8a7..377e1b0 100644 --- a/src/components/atoms/forms/field.tsx +++ b/src/components/atoms/forms/field.tsx @@ -1,4 +1,9 @@ -import { ChangeEvent, FC, SetStateAction } from 'react'; +import { + ChangeEvent, + forwardRef, + ForwardRefRenderFunction, + SetStateAction, +} from 'react'; import styles from './forms.module.scss'; export type FieldType = @@ -72,12 +77,10 @@ export type FieldProps = { * * Render either an input or a textarea. */ -const Field: FC<FieldProps> = ({ - className = '', - setValue, - type, - ...props -}) => { +const Field: ForwardRefRenderFunction<HTMLInputElement, FieldProps> = ( + { className = '', setValue, type, ...props }, + ref +) => { /** * Update select value when an option is selected. * @param e - The option change event. @@ -96,12 +99,13 @@ const Field: FC<FieldProps> = ({ /> ) : ( <input - type={type} - onChange={updateValue} className={`${styles.field} ${className}`} + onChange={updateValue} + ref={ref} + type={type} {...props} /> ); }; -export default Field; +export default forwardRef(Field); diff --git a/src/components/molecules/forms/labelled-field.tsx b/src/components/molecules/forms/labelled-field.tsx index ecc9255..6a00a3e 100644 --- a/src/components/molecules/forms/labelled-field.tsx +++ b/src/components/molecules/forms/labelled-field.tsx @@ -1,6 +1,6 @@ import Field, { type FieldProps } from '@components/atoms/forms/field'; import Label from '@components/atoms/forms/label'; -import { FC } from 'react'; +import { forwardRef, ForwardRefRenderFunction } from 'react'; import styles from './labelled-field.module.scss'; export type LabelledFieldProps = FieldProps & { @@ -23,14 +23,13 @@ export type LabelledFieldProps = FieldProps & { * * Render a field tied to a label. */ -const LabelledField: FC<LabelledFieldProps> = ({ - hideLabel = false, - id, - label, - labelPosition = 'top', - required, - ...props -}) => { +const LabelledField: ForwardRefRenderFunction< + HTMLInputElement, + LabelledFieldProps +> = ( + { hideLabel = false, id, label, labelPosition = 'top', required, ...props }, + ref +) => { const positionModifier = `label--${labelPosition}`; const visibilityClass = hideLabel ? 'screen-reader-text' : ''; @@ -43,9 +42,9 @@ const LabelledField: FC<LabelledFieldProps> = ({ > {label} </Label> - <Field id={id} required={required} {...props} /> + <Field id={id} ref={ref} required={required} {...props} /> </> ); }; -export default LabelledField; +export default forwardRef(LabelledField); diff --git a/src/components/organisms/forms/search-form.tsx b/src/components/organisms/forms/search-form.tsx index 56d3895..1b5f662 100644 --- a/src/components/organisms/forms/search-form.tsx +++ b/src/components/organisms/forms/search-form.tsx @@ -5,7 +5,7 @@ import LabelledField, { type LabelledFieldProps, } from '@components/molecules/forms/labelled-field'; import { useRouter } from 'next/router'; -import { FC, useState } from 'react'; +import { forwardRef, ForwardRefRenderFunction, useId, useState } from 'react'; import { useIntl } from 'react-intl'; import styles from './search-form.module.scss'; @@ -21,7 +21,10 @@ export type SearchFormProps = Pick<LabelledFieldProps, 'hideLabel'> & { * * Render a search form. */ -const SearchForm: FC<SearchFormProps> = ({ hideLabel, searchPage }) => { +const SearchForm: ForwardRefRenderFunction< + HTMLInputElement, + SearchFormProps +> = ({ hideLabel, searchPage }, ref) => { const intl = useIntl(); const fieldLabel = intl.formatMessage({ defaultMessage: 'Search for:', @@ -42,17 +45,20 @@ const SearchForm: FC<SearchFormProps> = ({ hideLabel, searchPage }) => { setValue(''); }; + const id = useId(); + return ( <Form grouped={false} onSubmit={submitHandler} className={styles.wrapper}> <LabelledField - type="search" - id="search-form" - name="search-form" + className={styles.field} + hideLabel={hideLabel} + id={`search-form-${id}`} label={fieldLabel} - value={value} + name="search-form" + ref={ref} setValue={setValue} - hideLabel={hideLabel} - className={styles.field} + type="search" + value={value} /> <Button type="submit" @@ -67,4 +73,4 @@ const SearchForm: FC<SearchFormProps> = ({ hideLabel, searchPage }) => { ); }; -export default SearchForm; +export default forwardRef(SearchForm); diff --git a/src/components/organisms/modals/search-modal.tsx b/src/components/organisms/modals/search-modal.tsx index c731048..ed6084a 100644 --- a/src/components/organisms/modals/search-modal.tsx +++ b/src/components/organisms/modals/search-modal.tsx @@ -1,20 +1,10 @@ -import Spinner from '@components/atoms/loaders/spinner'; import Modal, { type ModalProps } from '@components/molecules/modals/modal'; -import dynamic from 'next/dynamic'; -import { FC } from 'react'; +import { forwardRef, ForwardRefRenderFunction } from 'react'; import { useIntl } from 'react-intl'; -import { type SearchFormProps } from '../forms/search-form'; +import SearchForm, { type SearchFormProps } from '../forms/search-form'; import styles from './search-modal.module.scss'; -const DynamicSearchForm = dynamic( - () => import('@components/organisms/forms/search-form'), - { - loading: () => <Spinner />, - ssr: false, - } -); - -export type SearchModalProps = Pick<SearchFormProps, 'searchPage'> & { +export type SearchModalProps = SearchFormProps & { /** * Set additional classnames to modal wrapper. */ @@ -26,7 +16,10 @@ export type SearchModalProps = Pick<SearchFormProps, 'searchPage'> & { * * Render a search form modal. */ -const SearchModal: FC<SearchModalProps> = ({ className, searchPage }) => { +const SearchModal: ForwardRefRenderFunction< + HTMLInputElement, + SearchModalProps +> = ({ className, searchPage }, ref) => { const intl = useIntl(); const modalTitle = intl.formatMessage({ defaultMessage: 'Search', @@ -36,9 +29,9 @@ const SearchModal: FC<SearchModalProps> = ({ className, searchPage }) => { return ( <Modal title={modalTitle} className={`${styles.wrapper} ${className}`}> - <DynamicSearchForm hideLabel={true} searchPage={searchPage} /> + <SearchForm hideLabel={true} ref={ref} searchPage={searchPage} /> </Modal> ); }; -export default SearchModal; +export default forwardRef(SearchModal); diff --git a/src/components/organisms/toolbar/search.tsx b/src/components/organisms/toolbar/search.tsx index dc71c49..6a8af26 100644 --- a/src/components/organisms/toolbar/search.tsx +++ b/src/components/organisms/toolbar/search.tsx @@ -1,7 +1,8 @@ import Checkbox, { type CheckboxProps } from '@components/atoms/forms/checkbox'; import MagnifyingGlass from '@components/atoms/icons/magnifying-glass'; import FlippingLabel from '@components/molecules/forms/flipping-label'; -import { forwardRef, ForwardRefRenderFunction } from 'react'; +import useInputAutofocus from '@utils/hooks/use-input-autofocus'; +import { forwardRef, ForwardRefRenderFunction, useRef } from 'react'; import { useIntl } from 'react-intl'; import SearchModal, { type SearchModalProps } from '../modals/search-modal'; import searchStyles from './search.module.scss'; @@ -43,6 +44,13 @@ const Search: ForwardRefRenderFunction<HTMLDivElement, SearchProps> = ( description: 'Search: Open label', }); + const searchInputRef = useRef<HTMLInputElement>(null); + useInputAutofocus({ + condition: isActive, + delay: 360, + ref: searchInputRef, + }); + return ( <div className={`${sharedStyles.item} ${searchStyles.item}`} ref={ref}> <Checkbox @@ -61,8 +69,9 @@ const Search: ForwardRefRenderFunction<HTMLDivElement, SearchProps> = ( <MagnifyingGlass /> </FlippingLabel> <SearchModal - searchPage={searchPage} className={`${sharedStyles.modal} ${searchStyles.modal} ${className}`} + ref={searchInputRef} + searchPage={searchPage} /> </div> ); diff --git a/src/utils/hooks/use-input-autofocus.tsx b/src/utils/hooks/use-input-autofocus.tsx new file mode 100644 index 0000000..c7700e9 --- /dev/null +++ b/src/utils/hooks/use-input-autofocus.tsx @@ -0,0 +1,39 @@ +import { RefObject, useEffect } from 'react'; + +export type UseInputAutofocusProps = { + /** + * The focus condition. True give focus to the input. + */ + condition: boolean; + /** + * An optional delay. Default: 0. + */ + delay?: number; + /** + * A reference to the input element. + */ + ref: RefObject<HTMLInputElement>; +}; + +/** + * Set focus on an input with an optional delay. + */ +const useInputAutofocus = ({ + condition, + delay = 0, + ref, +}: UseInputAutofocusProps) => { + useEffect(() => { + const timer = setTimeout(() => { + if (ref.current && condition) { + ref.current.focus(); + } + }, delay); + + return () => { + clearTimeout(timer); + }; + }, [condition, delay, ref]); +}; + +export default useInputAutofocus; |
