From 7a05e1ea5a8dc6ee5456601b3d93f2f4720329a8 Mon Sep 17 00:00:00 2001 From: Eduardo Picolo Date: Tue, 12 Oct 2021 19:31:50 -0300 Subject: [PATCH 1/5] Criado componente RangeInput Co-authored-by: Pedrok99 Co-authored-by: Igorq937 --- package.json | 1 + src/components/RangeInput/index.tsx | 70 ++++++++++++++++++++++++++ src/components/RangeInput/styles.ts | 76 +++++++++++++++++++++++++++++ yarn.lock | 5 ++ 4 files changed, 152 insertions(+) create mode 100644 src/components/RangeInput/index.tsx create mode 100644 src/components/RangeInput/styles.ts diff --git a/package.json b/package.json index 8f6e5fe..deba421 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "react-dom": "17.0.2", "react-hook-form": "^7.15.3", "react-icons": "^4.2.0", + "react-range": "^1.8.11", "reflexbox": "^4.0.6", "styled-components": "^5.3.1", "swr": "^1.0.1", diff --git a/src/components/RangeInput/index.tsx b/src/components/RangeInput/index.tsx new file mode 100644 index 0000000..aa94451 --- /dev/null +++ b/src/components/RangeInput/index.tsx @@ -0,0 +1,70 @@ +import { useState } from 'react'; +import { Range } from 'react-range'; + +import * as S from './styles'; + +export interface IRangeProps { + min?: number; + max?: number; + step?: number; + onFinalChange?(values: number[]): void; + initialValue?: [number] | [number, number]; + formatValue?(value: number): any; + allowOverlap?: boolean; + draggableTrack?: boolean; +} + +const RangeInput = ({ + min = 0, + max = 100, + step = 1, + onFinalChange, + initialValue = [max / 2], + formatValue = (val) => val, + allowOverlap = false, + draggableTrack = false, +}: IRangeProps) => { + const [values, setValues] = useState(initialValue); + + return ( + + setValues(inputValues)} + onFinalChange={onFinalChange} + allowOverlap={allowOverlap} + draggableTrack={draggableTrack} + renderTrack={({ props, children }) => ( + + {children} + + )} + renderThumb={({ props, value }) => { + return ( + + {formatValue(value)} + + ); + }} + /> + + ); +}; + +export default RangeInput; diff --git a/src/components/RangeInput/styles.ts b/src/components/RangeInput/styles.ts new file mode 100644 index 0000000..bfafa84 --- /dev/null +++ b/src/components/RangeInput/styles.ts @@ -0,0 +1,76 @@ +import { getTrackBackground } from 'react-range'; +import styled, { css, DefaultTheme } from 'styled-components'; + +export interface ITrackProps { + min: number; + max: number; + values: number[]; +} + +export const RangeContainer = styled.div` + display: block; + padding: 3.5rem 0 1rem; +`; + +export const Track = styled.div.attrs( + ({ theme, min, max, values }: ITrackProps & { theme: DefaultTheme }) => { + const colors = + values.length === 1 + ? [theme.colors.primary, theme.colors.darkGray400] + : [ + theme.colors.darkGray400, + theme.colors.primary, + theme.colors.darkGray400, + ]; + + return { + trackBackground: getTrackBackground({ + values, + colors, + min, + max, + }), + }; + } +)` + ${({ theme, trackBackground }) => css` + position: relative; + height: 5px; + width: 100%; + border-radius: ${theme.border.radius}; + background: ${trackBackground}; + `} +`; + +export const Thumb = styled.div` + ${({ theme }) => css` + display: flex; + justify-content: center; + height: 1.8rem; + width: 1.8rem; + border-radius: 50%; + outline: 1px solid ${theme.colors.darkGray400}; + background-color: ${theme.colors.white}; + box-shadow: ${theme.shadows.soft}; + cursor: grab; + + &:focus { + box-shadow: 0 0 1rem ${theme.colors.primary}; + outline: 1px solid ${theme.colors.primary}; + } + `} +`; + +export const Value = styled.span` + ${({ theme }) => css` + position: absolute; + top: -28px; + padding: calc(${theme.spacings.xsmall} / 2); + color: ${theme.colors.white}; + font-size: ${theme.font.sizes.small}; + font-weight: ${theme.font.weight.medium}; + border-radius: ${theme.border.radius}; + background-color: ${theme.colors.primary}; + box-shadow: ${theme.shadows.medium}; + `} +`; diff --git a/yarn.lock b/yarn.lock index 6babf74..4ab8ad5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3858,6 +3858,11 @@ react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-range@^1.8.11: + version "1.8.11" + resolved "https://registry.yarnpkg.com/react-range/-/react-range-1.8.11.tgz#7d9ae1e4a263108566897acee2dde8d5b78901e1" + integrity sha512-LaF5xwYy6u0Rnp0hRBBLKqJx0DmNpW2GqMSoj8OJ0IYiCM7GmSNf0UKxmN4cN0BCb2EwzD3zA9nw6Fkug2c3mg== + react-refresh@0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" From d71b137680e3d175f104a373dc2dfbea9884e627 Mon Sep 17 00:00:00 2001 From: Eduardo Picolo Date: Tue, 12 Oct 2021 19:32:41 -0300 Subject: [PATCH 2/5] Implementado context para filtros Co-authored-by: Pedrok99 Co-authored-by: Igorq937 --- src/contexts/Filter/index.tsx | 54 +++++++++++++++++++++++++++++++++++ src/pages/produtos.tsx | 16 +++++++---- 2 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 src/contexts/Filter/index.tsx diff --git a/src/contexts/Filter/index.tsx b/src/contexts/Filter/index.tsx new file mode 100644 index 0000000..a2c3ceb --- /dev/null +++ b/src/contexts/Filter/index.tsx @@ -0,0 +1,54 @@ +import React, { createContext, Dispatch, useContext, useState } from 'react'; +// import { useRouter } from 'next/router'; + +export type FilterContextType = { + filters: Record; + setFilters: Dispatch; + removeFilters(): void; +}; + +export const filterContextDefaultValues: FilterContextType = { + filters: {}, + setFilters: () => {}, + removeFilters: () => {}, +}; + +export const FiltersContext = createContext( + filterContextDefaultValues +); + +export type Filters = Record; + +export const FilterProvider: React.FC = ({ children }) => { + // const router = useRouter(); + // const { query } = router; + + const [filters, setFilters] = useState({}); + + // const setFilters = () => {}; + const removeFilters = () => {}; + + return ( + <> + + {children} + + + ); +}; + +export function useFilter() { + const context = useContext(FiltersContext); + + if (!context) { + throw new Error('useFilter must be used within a FilterProvider'); + } + + return context; +} diff --git a/src/pages/produtos.tsx b/src/pages/produtos.tsx index 6ab0837..7e3ff28 100644 --- a/src/pages/produtos.tsx +++ b/src/pages/produtos.tsx @@ -1,3 +1,5 @@ +import { FilterProvider } from '@contexts/Filter'; + import ProductFilters from '@components/ProductFilters'; import ProductList from '@components/ProductList'; import PageTemplate from '@templates/Page'; @@ -6,12 +8,14 @@ import { Grid } from '@styles/grid'; const Products = () => { return ( - - - - - - + + + + + + + + ); }; From 99c13a29e938438b23ba40e55537f90a68569e8a Mon Sep 17 00:00:00 2001 From: Eduardo Picolo Date: Tue, 12 Oct 2021 19:33:55 -0300 Subject: [PATCH 3/5] Adicionado context de filtros na pagina de produtos Co-authored-by: Pedrok99 Co-authored-by: Igorq937 --- src/components/FormFields/Checkbox/index.tsx | 4 +- src/components/ProductFilters/index.tsx | 95 ++++++++++++++++++-- src/components/ProductList/index.tsx | 6 +- src/components/SearchBar/index.tsx | 41 +++------ 4 files changed, 106 insertions(+), 40 deletions(-) diff --git a/src/components/FormFields/Checkbox/index.tsx b/src/components/FormFields/Checkbox/index.tsx index 640b35a..770d83b 100644 --- a/src/components/FormFields/Checkbox/index.tsx +++ b/src/components/FormFields/Checkbox/index.tsx @@ -22,12 +22,12 @@ const Checkbox = forwardRef((props, ref) => { - {label} + {label} {error && {error?.message}} ); diff --git a/src/components/ProductFilters/index.tsx b/src/components/ProductFilters/index.tsx index d17d431..ca8fc1a 100644 --- a/src/components/ProductFilters/index.tsx +++ b/src/components/ProductFilters/index.tsx @@ -1,22 +1,105 @@ /* eslint-disable jsx-a11y/label-has-associated-control */ +import { useForm } from 'react-hook-form'; +import { useFilter } from '@contexts/Filter'; + +import Button from '@components/Button'; import Checkbox from '@components/FormFields/Checkbox'; +import RangeInput from '@components/RangeInput'; import * as S from './styles'; +interface FormFields { + orderByPrice: string; + priceRange: { minPrice: number; maxPrice: number }; +} + const ProductFilters = () => { + const { filters, setFilters } = useFilter(); + const { register, watch, reset, setValue, getValues } = useForm(); + + const priceRange = register('priceRange'); + const orderByPrice = register('orderByPrice'); + return ( + {Object?.keys(filters)?.length > 0 ? ( + + + + ) : null} - - + { + orderByPrice.onChange(e); + setFilters({ + ...filters, + orderByPrice: getValues('orderByPrice'), + }); + }} + onClick={() => { + if (getValues('orderByPrice') === 'ASC') { + setValue('orderByPrice', null); + delete filters.orderByPrice; + setFilters({ ...filters }); + } + }} + checked={watch('orderByPrice') === 'ASC'} // NÃO TIRA! + /> + { + orderByPrice.onChange(e); + setFilters({ + ...filters, + orderByPrice: getValues('orderByPrice'), + }); + }} + onClick={() => { + if (getValues('orderByPrice') === 'DESC') { + setValue('orderByPrice', null); + delete filters.orderByPrice; + setFilters({ ...filters }); + } + }} + /> - - - - + + `$${val}`} + {...priceRange} + onFinalChange={(prices) => { + setValue('priceRange', { + minPrice: prices[0], + maxPrice: prices[1], + }); + setFilters({ + ...filters, + ...getValues('priceRange'), + }); + }} + /> diff --git a/src/components/ProductList/index.tsx b/src/components/ProductList/index.tsx index 88dc8c7..263af24 100644 --- a/src/components/ProductList/index.tsx +++ b/src/components/ProductList/index.tsx @@ -1,4 +1,4 @@ -import { useRouter } from 'next/router'; +import { useFilter } from '@contexts/Filter'; import { product } from '@prisma/client'; import Loader from '@components/Loader'; @@ -10,8 +10,8 @@ import { getProducts } from '@services/productsServices'; import * as S from './styles'; const ProductList = () => { - const router = useRouter(); - const { data, isLoading } = useRequest(getProducts(router.query)); + const { filters } = useFilter(); + const { data, isLoading } = useRequest(getProducts(filters)); return ( diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index b7a2111..fb697f2 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -1,7 +1,8 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; import { useForm } from 'react-hook-form'; import { BiSearchAlt2 } from 'react-icons/bi'; +import { useFilter } from '@contexts/Filter'; import Button from '@components/Button'; import useDebounce from '@hooks/useDebounce'; @@ -15,40 +16,19 @@ export interface ISearchbarProps { const SearchBar = ({ variant, automaticSearch }: ISearchbarProps) => { const router = useRouter(); - const { query } = router; + const { filters, setFilters } = useFilter(); const [value, setValue] = useState(''); const { register, getValues } = useForm<{ searchText: string }>({ defaultValues: { - searchText: query?.searchText as string, + searchText: filters?.searchText, }, }); const debouncedValue = useDebounce(value, 500); - const pushUrl = useCallback( - (params) => { - router.push( - { - pathname: '/produtos', - query: { ...params }, - }, - undefined, - { shallow: true } - ); - }, - [router] - ); - - const setParams = useCallback( - (param, paramValue: string | number) => { - pushUrl({ ...query, [param]: paramValue }); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [pushUrl] - ); - useEffect( - () => debouncedValue && setParams('searchText', debouncedValue), + () => + debouncedValue && setFilters({ ...filters, searchText: debouncedValue }), // eslint-disable-next-line react-hooks/exhaustive-deps [debouncedValue] ); @@ -64,8 +44,8 @@ const SearchBar = ({ variant, automaticSearch }: ISearchbarProps) => { if (automaticSearch) { setValue(e.target.value); if (!e.target.value) { - delete query.searchText; - pushUrl(query); + delete filters.searchText; + setFilters({ ...filters }); } } }} @@ -74,7 +54,10 @@ const SearchBar = ({ variant, automaticSearch }: ISearchbarProps) => { /> {!automaticSearch ? (