diff options
| author | Armand Philippot <git@armandphilippot.com> | 2021-12-20 19:06:49 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2021-12-20 19:06:49 +0100 |
| commit | 43cdb7607d9423109758334155acfe844eab6ea5 (patch) | |
| tree | 0798fbb6f00bfbdbc3bd64759ffdb305dee43f0c | |
| parent | f9df5cbce7db38ce83cc8b40346c9cabde5debc4 (diff) | |
chore: define search form visibility
| -rw-r--r-- | src/components/Form/Form.module.scss | 5 | ||||
| -rw-r--r-- | src/components/Form/Form.tsx | 9 | ||||
| -rw-r--r-- | src/components/Form/Input/Input.tsx | 44 | ||||
| -rw-r--r-- | src/components/SearchForm/SearchForm.tsx | 37 | ||||
| -rw-r--r-- | src/components/Toolbar/Toolbar.module.scss | 53 | ||||
| -rw-r--r-- | src/components/Toolbar/Toolbar.tsx | 8 | ||||
| -rw-r--r-- | src/styles/abstracts/_variables.scss | 1 | ||||
| -rw-r--r-- | src/styles/abstracts/mixins/_media-queries.scss | 27 |
8 files changed, 155 insertions, 29 deletions
diff --git a/src/components/Form/Form.module.scss b/src/components/Form/Form.module.scss index f23dfde..891db19 100644 --- a/src/components/Form/Form.module.scss +++ b/src/components/Form/Form.module.scss @@ -2,6 +2,11 @@ .wrapper { width: 100%; + + &--search { + display: flex; + flex-flow: row nowrap; + } } .item { diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 5e26e81..dd1bc63 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -4,12 +4,19 @@ import styles from './Form.module.scss'; const Form = ({ children, submitHandler, + modifier = '', }: { children: ReactNode; submitHandler: any; + modifier?: string; }) => { + const classes = + modifier !== '' + ? `${styles.wrapper} ${styles[`wrapper--${modifier}`]}` + : styles.wrapper; + return ( - <form onSubmit={submitHandler} className={styles.wrapper}> + <form onSubmit={submitHandler} className={classes}> {children} </form> ); diff --git a/src/components/Form/Input/Input.tsx b/src/components/Form/Input/Input.tsx index 901b9ab..2ee214b 100644 --- a/src/components/Form/Input/Input.tsx +++ b/src/components/Form/Input/Input.tsx @@ -1,25 +1,28 @@ -import { ChangeEvent, SetStateAction } from 'react'; +import { ChangeEvent, ForwardedRef, forwardRef, SetStateAction } from 'react'; import styles from '../Form.module.scss'; -type InputType = 'text' | 'number'; +type InputType = 'text' | 'number' | 'search'; -const Input = ({ - id, - name, - value, - setValue, - type = 'text', - required = false, - label, -}: { - id: string; - name: string; - value: string; - setValue: (value: SetStateAction<string>) => void; - type?: InputType; - required?: boolean; - label?: string; -}) => { +const Input = ( + { + id, + name, + value, + setValue, + type = 'text', + required = false, + label, + }: { + id: string; + name: string; + value: string; + setValue: (value: SetStateAction<string>) => void; + type?: InputType; + required?: boolean; + label?: string; + }, + ref: ForwardedRef<HTMLInputElement> +) => { const updateValue = (e: ChangeEvent<HTMLInputElement>) => { setValue(e.target.value); }; @@ -33,6 +36,7 @@ const Input = ({ </label> )} <input + ref={ref} type={type} id={id} name={name} @@ -44,4 +48,4 @@ const Input = ({ ); }; -export default Input; +export default forwardRef(Input); diff --git a/src/components/SearchForm/SearchForm.tsx b/src/components/SearchForm/SearchForm.tsx new file mode 100644 index 0000000..c02c224 --- /dev/null +++ b/src/components/SearchForm/SearchForm.tsx @@ -0,0 +1,37 @@ +import { ButtonSubmit } from '@components/Buttons'; +import { Form, Input } from '@components/Form'; +import { t } from '@lingui/macro'; +import { FormEvent, useEffect, useRef, useState } from 'react'; + +const SearchForm = ({ isOpened }: { isOpened: boolean }) => { + const [query, setQuery] = useState(''); + const inputRef = useRef<HTMLInputElement>(null); + + useEffect(() => { + setTimeout(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, 800); + }, [isOpened]); + + const launchSearch = (e: FormEvent) => { + e.preventDefault(); + }; + + return ( + <Form submitHandler={launchSearch} modifier="search"> + <Input + ref={inputRef} + id="search-query" + name="search-query" + type="search" + value={query} + setValue={setQuery} + /> + <ButtonSubmit>{t`Search`}</ButtonSubmit> + </Form> + ); +}; + +export default SearchForm; diff --git a/src/components/Toolbar/Toolbar.module.scss b/src/components/Toolbar/Toolbar.module.scss index df1ad7e..18212a2 100644 --- a/src/components/Toolbar/Toolbar.module.scss +++ b/src/components/Toolbar/Toolbar.module.scss @@ -16,11 +16,12 @@ left: 0; z-index: 5; background: var(--color-bg); - box-shadow: 0 fun.convert-px(-1) fun.convert-px(3) 0 var(--color-shadow); + box-shadow: 0 fun.convert-px(-2) fun.convert-px(3) fun.convert-px(-1) + var(--color-shadow); @include mix.media("screen") { @include mix.dimensions(null, "2xs", "height") { - --toolbar-size: #{fun.convert-px(50)}; + --toolbar-size: #{fun.convert-px(52)}; --btn-size: #{fun.convert-px(42)}; } @@ -37,3 +38,51 @@ } } } + +.search { + padding: var(--spacing-md); + position: absolute; + bottom: 100%; + left: 0; + right: 0; + background: var(--color-bg); + box-shadow: fun.convert-px(2) fun.convert-px(-2) fun.convert-px(3) + fun.convert-px(-1) var(--color-shadow); + transition: all 0.7s ease-in-out 0s; + + &--closed { + transform: translateX(-100%); + visibility: hidden; + } + + &--opened { + transform: translateX(0); + visibility: visible; + } + + @include mix.media("screen") { + @include mix.dimensions("sm") { + width: fun.convert-px(500); + left: unset; + right: unset; + top: 200%; + bottom: unset; + background: var(--color-bg-opacity); + box-shadow: fun.convert-px(2) fun.convert-px(2) fun.convert-px(3) + fun.convert-px(1) var(--color-shadow); + transform-origin: 100% -200%; + transition: all 0.8s ease-in-out 0s; + + &--closed { + opacity: 0; + transform: perspective(20rem) translate3d(0, 100%, -20rem); + visibility: hidden; + } + + &--opened { + opacity: 1; + transform: none; + } + } + } +} diff --git a/src/components/Toolbar/Toolbar.tsx b/src/components/Toolbar/Toolbar.tsx index 6be2029..fdab76a 100644 --- a/src/components/Toolbar/Toolbar.tsx +++ b/src/components/Toolbar/Toolbar.tsx @@ -1,5 +1,6 @@ import { ButtonSearch } from '@components/Buttons'; import MainNav from '@components/MainNav/MainNav'; +import SearchForm from '@components/SearchForm/SearchForm'; import { useEffect, useState } from 'react'; import styles from './Toolbar.module.scss'; @@ -15,6 +16,10 @@ const Toolbar = () => { if (isSearchOpened) setIsNavOpened(false); }, [isSearchOpened]); + const searchClasses = `${styles.search} ${ + isSearchOpened ? styles['search--opened'] : styles['search--closed'] + }`; + return ( <div className={styles.wrapper}> <MainNav isOpened={isNavOpened} setIsOpened={setIsNavOpened} /> @@ -22,6 +27,9 @@ const Toolbar = () => { isActivated={isSearchOpened} setIsActivated={setIsSearchOpened} /> + <div className={searchClasses}> + <SearchForm isOpened={isSearchOpened} /> + </div> </div> ); }; diff --git a/src/styles/abstracts/_variables.scss b/src/styles/abstracts/_variables.scss index 1ea1d5d..7dfc720 100644 --- a/src/styles/abstracts/_variables.scss +++ b/src/styles/abstracts/_variables.scss @@ -38,6 +38,7 @@ $ratios: ( /// @prop {String} keys - Keys are identifiers mapped to a given length /// @prop {Map} values - Values are actual breakpoints expressed in pixels $breakpoints: ( + "2xs": fun.convert-px(400, "em"), "xs": fun.convert-px(600, "em"), "sm": fun.convert-px(800, "em"), "md": fun.convert-px(1280, "em"), diff --git a/src/styles/abstracts/mixins/_media-queries.scss b/src/styles/abstracts/mixins/_media-queries.scss index db0b568..e72f572 100644 --- a/src/styles/abstracts/mixins/_media-queries.scss +++ b/src/styles/abstracts/mixins/_media-queries.scss @@ -17,18 +17,26 @@ } } -/// Media query: min-width / max-width -/// @param {String} $from - min-width breakpoint. -/// @param {String} $until - max-width breakpoint. +/// Media query: min-width / max-width or min-height / max-height +/// @param {String} $from - min size breakpoint. +/// @param {String} $until - max size breakpoint. +/// @param {String} $kind - width or height. Default: width. /// @example scss - `@media (min-width: "md")` equivalent is: /// @include dimensions("md"); -@mixin dimensions($from: null, $until: null) { +@mixin dimensions($from: null, $until: null, $kind: "width") { $query: ""; @if $from { @if type-of($from) == "string" { $size: map.get(var.$breakpoints, $from); - $query: "(min-width: #{$size})"; + + @if $kind == "width" { + $query: "(min-width: #{$size})"; + } @else if $kind == "height" { + $query: "(min-height: #{$size})"; + } @else { + @error "`$kind` must be either width or height."; + } } @else { @error "`$from` must be a string."; } @@ -42,7 +50,14 @@ @if type-of($until) == "string" { $size: map.get(var.$breakpoints, $until); $size: calc(#{$size} - 1px); - $query: $query + "(max-width: #{$size})"; + + @if $kind == "width" { + $query: $query + "(max-width: #{$size})"; + } @else if $kind == "height" { + $query: $query + "(max-height: #{$size})"; + } @else { + @error "`$kind` must be either width or height."; + } } @else { @error "`$until` must be a string."; } |
