diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-04-14 19:42:51 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-04-14 19:42:51 +0200 |
| commit | 70b4e5c311999501cd6eff632ddbcac6e75ff035 (patch) | |
| tree | f9de55c3d1aa6565a164cf451c73d9fa5c0f7b87 /src/components/organisms/forms | |
| parent | 1d162d7aafb3cfe2c3351b5fd891bbf6d476e9b2 (diff) | |
chore: add a SearchForm component
Diffstat (limited to 'src/components/organisms/forms')
4 files changed, 165 insertions, 0 deletions
diff --git a/src/components/organisms/forms/search-form.module.scss b/src/components/organisms/forms/search-form.module.scss new file mode 100644 index 0000000..1d388a4 --- /dev/null +++ b/src/components/organisms/forms/search-form.module.scss @@ -0,0 +1,58 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; + +.wrapper { + display: flex; + align-items: center; + position: relative; + + @include mix.media("screen") { + @include mix.dimensions("sm") { + max-width: 35ch; + } + } +} + +.btn { + position: absolute; + right: 0; + + &__icon { + transform: scale(0.85); + transition: all 0.3s ease-in-out 0s; + } + + &:focus { + outline: var(--color-primary-light) solid fun.convert-px(3); + } + + &:active { + outline: none; + } + + &:hover &, + &:focus & { + &__icon { + transform: scale(0.85) rotate(20deg) translateY(#{fun.convert-px(3)}); + } + } + + &:active & { + &__icon { + transform: scale(0.7); + } + } +} + +.field { + width: 100%; + padding-right: var(--spacing-lg); + + &:hover ~ .btn { + transform: translate(fun.convert-px(-3), fun.convert-px(-3)); + } + + &:focus ~ .btn { + transform: translate(fun.convert-px(3), fun.convert-px(3)); + } +} diff --git a/src/components/organisms/forms/search-form.stories.tsx b/src/components/organisms/forms/search-form.stories.tsx new file mode 100644 index 0000000..4ec1c21 --- /dev/null +++ b/src/components/organisms/forms/search-form.stories.tsx @@ -0,0 +1,34 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import SearchFormComponent from './search-form'; + +export default { + title: 'Organisms/Forms', + component: SearchFormComponent, + argTypes: { + className: { + control: { + type: 'text', + }, + description: 'Set additional classnames to the form wrapper.', + table: { + category: 'Styles', + }, + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta<typeof SearchFormComponent>; + +const Template: ComponentStory<typeof SearchFormComponent> = (args) => ( + <IntlProvider locale="en"> + <SearchFormComponent {...args} /> + </IntlProvider> +); + +export const SearchForm = Template.bind({}); +SearchForm.args = { + hideLabel: true, +}; diff --git a/src/components/organisms/forms/search-form.test.tsx b/src/components/organisms/forms/search-form.test.tsx new file mode 100644 index 0000000..4e3d285 --- /dev/null +++ b/src/components/organisms/forms/search-form.test.tsx @@ -0,0 +1,16 @@ +import { render, screen } from '@test-utils'; +import SearchForm from './search-form'; + +describe('SearchForm', () => { + it('renders a search input', () => { + render(<SearchForm />); + expect( + screen.getByRole('searchbox', { name: 'Search for:' }) + ).toBeInTheDocument(); + }); + + it('renders a submit button', () => { + render(<SearchForm />); + expect(screen.getByRole('button', { name: 'Search' })).toBeInTheDocument(); + }); +}); diff --git a/src/components/organisms/forms/search-form.tsx b/src/components/organisms/forms/search-form.tsx new file mode 100644 index 0000000..351d93c --- /dev/null +++ b/src/components/organisms/forms/search-form.tsx @@ -0,0 +1,57 @@ +import Button from '@components/atoms/buttons/button'; +import Form from '@components/atoms/forms/form'; +import MagnifyingGlass from '@components/atoms/icons/magnifying-glass'; +import LabelledField, { + LabelledFieldProps, +} from '@components/molecules/forms/labelled-field'; +import { useState, VFC } from 'react'; +import { useIntl } from 'react-intl'; +import styles from './search-form.module.scss'; + +export type SearchFormProps = Pick<LabelledFieldProps, 'hideLabel'>; + +const SearchForm: VFC<SearchFormProps> = ({ hideLabel }) => { + const intl = useIntl(); + const fieldLabel = intl.formatMessage({ + defaultMessage: 'Search for:', + description: 'SearchForm: field accessible label', + id: 'X8oujO', + }); + const buttonLabel = intl.formatMessage({ + defaultMessage: 'Search', + description: 'SearchForm: button accessible name', + id: 'WMqQrv', + }); + + const [value, setValue] = useState<string>(''); + + const submitHandler = () => { + return; + }; + + return ( + <Form grouped={false} onSubmit={submitHandler} className={styles.wrapper}> + <LabelledField + type="search" + id="search-form" + name="search-form" + label={fieldLabel} + value={value} + setValue={setValue} + hideLabel={hideLabel} + className={styles.field} + /> + <Button + type="submit" + kind="neutral" + shape="initial" + className={styles.btn} + aria-label={buttonLabel} + > + <MagnifyingGlass className={styles.btn__icon} /> + </Button> + </Form> + ); +}; + +export default SearchForm; |
