aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2021-12-20 19:06:49 +0100
committerArmand Philippot <git@armandphilippot.com>2021-12-20 19:06:49 +0100
commit43cdb7607d9423109758334155acfe844eab6ea5 (patch)
tree0798fbb6f00bfbdbc3bd64759ffdb305dee43f0c
parentf9df5cbce7db38ce83cc8b40346c9cabde5debc4 (diff)
chore: define search form visibility
-rw-r--r--src/components/Form/Form.module.scss5
-rw-r--r--src/components/Form/Form.tsx9
-rw-r--r--src/components/Form/Input/Input.tsx44
-rw-r--r--src/components/SearchForm/SearchForm.tsx37
-rw-r--r--src/components/Toolbar/Toolbar.module.scss53
-rw-r--r--src/components/Toolbar/Toolbar.tsx8
-rw-r--r--src/styles/abstracts/_variables.scss1
-rw-r--r--src/styles/abstracts/mixins/_media-queries.scss27
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.";
}