From e257df5f406e51ac176b286932bd38d7393b355d Mon Sep 17 00:00:00 2001 From: merankori <96338248+merankori@users.noreply.github.com> Date: Sat, 16 Dec 2023 21:29:00 +0300 Subject: [PATCH] feat: mobile search (#139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add context for search-bar props * feat: add draft functionality of mobile-search * fix: refactor * feat: add portaling for search select * fix: icons naming * fix: style fixes * feat: add useGetUsers hook * feat: add drawer in modal and optimize modal button * fix: getting data and modal button fixes * fix: drawer class types * fix: codestyle fixes * feat: move search-bar in shared * fix: refactor and codestyle fixes * fix: small issue * fix: use client --------- Co-authored-by: “merankori” Co-authored-by: Nikita Mashchenko --- client/src/app/layout.tsx | 7 +- client/src/app/page.tsx | 15 ++- client/src/entities/session/api/index.ts | 1 + .../src/entities/session/api/useGetUsers.tsx | 31 +++++ client/src/shared/constant/server-routes.ts | 1 + .../shared/ui/badge/badge-text/badge-text.tsx | 2 +- client/src/shared/ui/drawer/drawer.tsx | 17 ++- client/src/shared/ui/index.ts | 1 + .../shared/ui/search-bar/contexts/index.ts | 1 + .../ui/search-bar/contexts/search-context.ts | 4 + .../src/shared/ui/search-bar/hooks/index.ts | 2 + .../shared/ui/search-bar/hooks/useFilters.ts | 12 ++ .../ui/search-bar/hooks/useTrackFiltersArr.ts | 46 +++++++ .../search => shared}/ui/search-bar/index.tsx | 0 .../ui/search-bar/search-bar.module.scss | 2 +- .../ui/search-bar/search-bar.stories.tsx | 2 +- .../src/shared/ui/search-bar/search-bar.tsx | 83 ++++++++++++ .../ui/search-bar}/types/index.ts | 0 .../ui/search-bar}/types/types.ts | 12 ++ .../ui/filter-menu/filter-menu.module.scss | 13 ++ .../search-bar/ui/filter-menu/filter-menu.tsx | 19 +++ .../ui/search-bar/ui/filter-menu/index.ts | 1 + .../filter-select/filter-select.module.scss | 0 .../ui/filter-select/filter-select.tsx | 22 +--- .../ui/search-bar}/ui/filter-select/index.ts | 0 .../ui/search-bar/ui/modal-button/index.ts | 1 + .../ui/modal-button/modal-button.module.scss | 20 +++ .../ui/modal-button/modal-button.tsx | 24 ++++ .../ui/search-bar/ui/modal-menu/index.ts | 1 + .../ui/modal-menu/modal-menu.module.scss | 17 +++ .../search-bar/ui/modal-menu/modal-menu.tsx | 62 +++++++++ .../shared/ui/search-bar/ui/modal/index.ts | 1 + .../ui/search-bar/ui/modal/modal.module.scss | 61 +++++++++ .../shared/ui/search-bar/ui/modal/modal.tsx | 123 +++++++++++++++++ .../ui/search-bar}/ui/search-input/index.ts | 0 .../ui/search-input/search-input.tsx | 16 ++- .../ui/search-bar}/ui/search-select/index.ts | 0 .../search-select/search-select.module.scss | 0 .../ui/search-select/search-select.tsx | 20 +++ .../search-bar}/ui/search-tag-menu/index.ts | 0 .../search-tag-menu.module.scss | 14 ++ .../ui/search-tag-menu/search-tag-menu.tsx | 27 ++-- .../ui/search-bar}/ui/tag-list/index.ts | 0 .../ui/tag-list/tag-list.module.scss | 0 .../ui/search-bar/ui/tag-list/tag-list.tsx | 124 ++++++++++++++++++ .../ui/search-bar}/ui/tag/index.ts | 0 .../ui/search-bar}/ui/tag/tag.module.scss | 4 + .../ui/search-bar}/ui/tag/tag.tsx | 19 ++- .../ui/search-bar}/ui/text-input/index.ts | 0 .../ui/text-input/text-input.module.scss | 0 .../search-bar}/ui/text-input/text-input.tsx | 0 .../shared/ui/select/lib/select-styles.tsx | 37 +++--- .../src/shared/ui/select/ui/select/select.tsx | 5 +- client/src/widgets/search/index.tsx | 1 - .../search/lib/hooks/useTrackFiltersArr.ts | 52 -------- .../search/ui/search-bar/search-bar.tsx | 64 --------- .../widgets/search/ui/tag-list/tag-list.tsx | 115 ---------------- 57 files changed, 804 insertions(+), 298 deletions(-) create mode 100644 client/src/entities/session/api/useGetUsers.tsx create mode 100644 client/src/shared/ui/search-bar/contexts/index.ts create mode 100644 client/src/shared/ui/search-bar/contexts/search-context.ts create mode 100644 client/src/shared/ui/search-bar/hooks/index.ts create mode 100644 client/src/shared/ui/search-bar/hooks/useFilters.ts create mode 100644 client/src/shared/ui/search-bar/hooks/useTrackFiltersArr.ts rename client/src/{widgets/search => shared}/ui/search-bar/index.tsx (100%) rename client/src/{widgets/search => shared}/ui/search-bar/search-bar.module.scss (95%) rename client/src/{widgets/search => shared}/ui/search-bar/search-bar.stories.tsx (96%) create mode 100644 client/src/shared/ui/search-bar/search-bar.tsx rename client/src/{widgets/search => shared/ui/search-bar}/types/index.ts (100%) rename client/src/{widgets/search => shared/ui/search-bar}/types/types.ts (70%) create mode 100644 client/src/shared/ui/search-bar/ui/filter-menu/filter-menu.module.scss create mode 100644 client/src/shared/ui/search-bar/ui/filter-menu/filter-menu.tsx create mode 100644 client/src/shared/ui/search-bar/ui/filter-menu/index.ts rename client/src/{widgets/search => shared/ui/search-bar}/ui/filter-select/filter-select.module.scss (100%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/filter-select/filter-select.tsx (68%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/filter-select/index.ts (100%) create mode 100644 client/src/shared/ui/search-bar/ui/modal-button/index.ts create mode 100644 client/src/shared/ui/search-bar/ui/modal-button/modal-button.module.scss create mode 100644 client/src/shared/ui/search-bar/ui/modal-button/modal-button.tsx create mode 100644 client/src/shared/ui/search-bar/ui/modal-menu/index.ts create mode 100644 client/src/shared/ui/search-bar/ui/modal-menu/modal-menu.module.scss create mode 100644 client/src/shared/ui/search-bar/ui/modal-menu/modal-menu.tsx create mode 100644 client/src/shared/ui/search-bar/ui/modal/index.ts create mode 100644 client/src/shared/ui/search-bar/ui/modal/modal.module.scss create mode 100644 client/src/shared/ui/search-bar/ui/modal/modal.tsx rename client/src/{widgets/search => shared/ui/search-bar}/ui/search-input/index.ts (100%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/search-input/search-input.tsx (75%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/search-select/index.ts (100%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/search-select/search-select.module.scss (100%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/search-select/search-select.tsx (70%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/search-tag-menu/index.ts (100%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/search-tag-menu/search-tag-menu.module.scss (66%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/search-tag-menu/search-tag-menu.tsx (75%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/tag-list/index.ts (100%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/tag-list/tag-list.module.scss (100%) create mode 100644 client/src/shared/ui/search-bar/ui/tag-list/tag-list.tsx rename client/src/{widgets/search => shared/ui/search-bar}/ui/tag/index.ts (100%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/tag/tag.module.scss (93%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/tag/tag.tsx (67%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/text-input/index.ts (100%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/text-input/text-input.module.scss (100%) rename client/src/{widgets/search => shared/ui/search-bar}/ui/text-input/text-input.tsx (100%) delete mode 100644 client/src/widgets/search/index.tsx delete mode 100644 client/src/widgets/search/lib/hooks/useTrackFiltersArr.ts delete mode 100644 client/src/widgets/search/ui/search-bar/search-bar.tsx delete mode 100644 client/src/widgets/search/ui/tag-list/tag-list.tsx diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index 7b8c2849a..f20570d48 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -21,7 +21,12 @@ export default function RootLayout({ children }: { children: ReactNode }) { {children} diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 198372ba2..ee65c07ce 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Button, Typography } from '@/shared/ui'; +import { Button, SearchBar, Typography } from '@/shared/ui'; import { useGetScreenWidth } from '@/shared/lib'; import { useGetMe, useLogout, useLogin, useUpdateMe, useRegister } from '@/entities/session'; @@ -41,6 +41,19 @@ export default function Home() { Get to login + + console.log(1)} + /> ); } diff --git a/client/src/entities/session/api/index.ts b/client/src/entities/session/api/index.ts index 3cf32888c..839cf9753 100644 --- a/client/src/entities/session/api/index.ts +++ b/client/src/entities/session/api/index.ts @@ -9,3 +9,4 @@ export { useLogout } from './useLogout'; export { useRegister } from './useRegister'; export { useResetPassword } from './useResetPassword'; export { useUpdateMe } from './useUpdateMe'; +export { useGetUsers } from './useGetUsers'; diff --git a/client/src/entities/session/api/useGetUsers.tsx b/client/src/entities/session/api/useGetUsers.tsx new file mode 100644 index 000000000..81c107960 --- /dev/null +++ b/client/src/entities/session/api/useGetUsers.tsx @@ -0,0 +1,31 @@ +'use client'; +import { useQuery } from '@tanstack/react-query'; +import { IUserProtectedResponse } from '@teameights/types'; +import { API } from '@/shared/api'; +import { API_USERS } from '@/shared/constant'; + +interface IQueryParams { + page?: number; + limit?: number; + filters?: string | null; + sort?: string; +} + +export const useGetUsers = ({ page, limit, filters, sort }: IQueryParams) => { + const queryString = [ + page && `page=${page}`, + limit && `limit=${limit}`, + filters && `filters=${filters}`, + sort && `sort=${sort}`, + ].join(''); + + return useQuery({ + queryKey: ['useGetUsers', queryString], + queryFn: async () => { + const { data } = await API.get(`${API_USERS}?${queryString}`); + return data; + }, + refetchOnMount: false, + refetchOnWindowFocus: false, + }); +}; diff --git a/client/src/shared/constant/server-routes.ts b/client/src/shared/constant/server-routes.ts index 1a8b25e66..555cb6fca 100644 --- a/client/src/shared/constant/server-routes.ts +++ b/client/src/shared/constant/server-routes.ts @@ -9,3 +9,4 @@ export const API_REFRESH = '/auth/refresh'; export const API_LOGOUT = '/auth/logout'; export const API_GOOGLE_LOGIN = '/auth/google/login'; export const API_GITHUB_LOGIN = '/auth/github/login'; +export const API_USERS = '/users'; diff --git a/client/src/shared/ui/badge/badge-text/badge-text.tsx b/client/src/shared/ui/badge/badge-text/badge-text.tsx index 7d46d8046..01373a863 100644 --- a/client/src/shared/ui/badge/badge-text/badge-text.tsx +++ b/client/src/shared/ui/badge/badge-text/badge-text.tsx @@ -41,7 +41,7 @@ export const BadgeText: FC = props => {
void; isFullHeight?: boolean; direction?: 'left' | 'right' | 'top' | 'bottom'; + className?: string; + overlayClassName?: string; } export const Drawer: FC> = props => { - const { open, onClose, isFullHeight, direction = 'bottom', children } = props; + const { + open, + onClose, + isFullHeight, + direction = 'bottom', + className, + overlayClassName, + children, + } = props; const style = { borderRadius: isFullHeight ? '0' : '15px 15px 0 0', @@ -54,10 +66,13 @@ export const Drawer: FC> = props => { return ( diff --git a/client/src/shared/ui/index.ts b/client/src/shared/ui/index.ts index d872f0df9..4e33a67bc 100644 --- a/client/src/shared/ui/index.ts +++ b/client/src/shared/ui/index.ts @@ -12,6 +12,7 @@ export * from './flex'; export * from './icon-wrapper'; export * from './drawer'; export * from './image-loader'; +export * from './search-bar'; export * from './progress-bar'; export * from './logo'; export * from './need-help'; diff --git a/client/src/shared/ui/search-bar/contexts/index.ts b/client/src/shared/ui/search-bar/contexts/index.ts new file mode 100644 index 000000000..5b3f7d994 --- /dev/null +++ b/client/src/shared/ui/search-bar/contexts/index.ts @@ -0,0 +1 @@ +export { SearchContext } from './search-context'; diff --git a/client/src/shared/ui/search-bar/contexts/search-context.ts b/client/src/shared/ui/search-bar/contexts/search-context.ts new file mode 100644 index 000000000..6c0ce1bb6 --- /dev/null +++ b/client/src/shared/ui/search-bar/contexts/search-context.ts @@ -0,0 +1,4 @@ +import { createContext } from 'react'; +import { SearchContextType } from '../types'; + +export const SearchContext = createContext(null); diff --git a/client/src/shared/ui/search-bar/hooks/index.ts b/client/src/shared/ui/search-bar/hooks/index.ts new file mode 100644 index 000000000..20b654d16 --- /dev/null +++ b/client/src/shared/ui/search-bar/hooks/index.ts @@ -0,0 +1,2 @@ +export { useTrackFilterArr } from './useTrackFiltersArr'; +export { useFilters } from './useFilters'; diff --git a/client/src/shared/ui/search-bar/hooks/useFilters.ts b/client/src/shared/ui/search-bar/hooks/useFilters.ts new file mode 100644 index 000000000..cb93a3a05 --- /dev/null +++ b/client/src/shared/ui/search-bar/hooks/useFilters.ts @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { SearchContext } from '../contexts'; + +export const useFilters = () => { + const context = useContext(SearchContext); + + if (!context) { + throw new Error(); + } + + return context; +}; diff --git a/client/src/shared/ui/search-bar/hooks/useTrackFiltersArr.ts b/client/src/shared/ui/search-bar/hooks/useTrackFiltersArr.ts new file mode 100644 index 000000000..ae94ed702 --- /dev/null +++ b/client/src/shared/ui/search-bar/hooks/useTrackFiltersArr.ts @@ -0,0 +1,46 @@ +import { useEffect, useRef } from 'react'; +import { Filter, IFilterParams } from '../types'; + +export const useTrackFilterArr = ( + filterArr: Filter[], + onChange: (filterValues: string | null) => void +) => { + const timerRef = useRef | null>(null); + useEffect(() => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + + timerRef.current = setTimeout(() => { + const filterValues: IFilterParams = filterArr.reduce<{ + [key: string]: string | string[] | [number, number]; + }>((acc, curr) => { + switch (curr.type) { + case 'text': + if (curr.filterValue.length) { + acc[curr.value] = curr.filterValue; + } + + return acc; + + case 'multiple': + case 'checkbox': + if (curr.filterValue.length) { + acc[curr.value] = curr.filterValue.map(item => item.value); + } + + return acc; + + case 'range': + if (curr.filterValue?.length) { + acc[curr.value] = curr.filterValue; + } + + return acc; + } + }, {}); + + onChange(Object.keys(filterValues).length ? JSON.stringify(filterValues) : null); + }, 1300); + }, [filterArr, onChange]); +}; diff --git a/client/src/widgets/search/ui/search-bar/index.tsx b/client/src/shared/ui/search-bar/index.tsx similarity index 100% rename from client/src/widgets/search/ui/search-bar/index.tsx rename to client/src/shared/ui/search-bar/index.tsx diff --git a/client/src/widgets/search/ui/search-bar/search-bar.module.scss b/client/src/shared/ui/search-bar/search-bar.module.scss similarity index 95% rename from client/src/widgets/search/ui/search-bar/search-bar.module.scss rename to client/src/shared/ui/search-bar/search-bar.module.scss index a33bf3a1c..7e94fde37 100644 --- a/client/src/widgets/search/ui/search-bar/search-bar.module.scss +++ b/client/src/shared/ui/search-bar/search-bar.module.scss @@ -1,11 +1,11 @@ .searchbar { width: 100%; max-width: 1176px; - min-height: 40px; padding-left: 24px; padding-right: 24px; &_content { + min-height: 40px; border: 1px solid var(--green-normal-color); border-radius: 10px; overflow: hidden; diff --git a/client/src/widgets/search/ui/search-bar/search-bar.stories.tsx b/client/src/shared/ui/search-bar/search-bar.stories.tsx similarity index 96% rename from client/src/widgets/search/ui/search-bar/search-bar.stories.tsx rename to client/src/shared/ui/search-bar/search-bar.stories.tsx index f0b402acf..3aae5a49a 100644 --- a/client/src/widgets/search/ui/search-bar/search-bar.stories.tsx +++ b/client/src/shared/ui/search-bar/search-bar.stories.tsx @@ -60,7 +60,7 @@ export const SearchBar_default = () => { filterValue: [], }, ]} - callback={queryString => console.log(queryString)} + onChange={filterValues => console.log(filterValues)} /> ); }; diff --git a/client/src/shared/ui/search-bar/search-bar.tsx b/client/src/shared/ui/search-bar/search-bar.tsx new file mode 100644 index 000000000..0019d3bb0 --- /dev/null +++ b/client/src/shared/ui/search-bar/search-bar.tsx @@ -0,0 +1,83 @@ +'use client'; + +import { FC, useState } from 'react'; +import styles from './search-bar.module.scss'; +import { Filter } from './types'; +import { FilterSelect } from './ui/filter-select'; +import { SearchInput } from './ui/search-input'; +import { TagList } from './ui/tag-list'; +import { Flex } from '@/shared/ui'; +import { useTrackFilterArr } from './hooks'; +import { SearchContext } from './contexts'; +import { ModalButton } from './ui/modal-button'; +import { Modal } from './ui/modal'; + +/** + * Search-bar Component + * + * Used to search by query parameters. + * + * Props: + * + * @prop {Filter[]} [initialFiltersState] - Initial state as an array with filters. + * @prop {(filterValues: string | null) => void} [onChange] - A callback that takes a string of query parameters as an argument. Must be used for requests to the server. + * + * Usage: + * + * ```tsx + * import { SearchBar } from '@/widgets/search'; + * + * console.log(string)} + * /> + * ``` + */ + +interface SearchBarProps { + initialFiltersState: Filter[]; + onChange: (filterValues: string | null) => void; +} + +export const SearchBar: FC = ({ initialFiltersState, onChange }) => { + const [filterArr, setFilterArr] = useState(initialFiltersState); + const [filterIndex, setFilterIndex] = useState(0); + const [isModalOpened, setIsModalOpened] = useState(false); + useTrackFilterArr(filterArr, onChange); + + const onOpen = () => { + setIsModalOpened(true); + }; + + const onClose = () => { + setIsModalOpened(false); + }; + + return ( + + + + + + + + + + + + + + ); +}; diff --git a/client/src/widgets/search/types/index.ts b/client/src/shared/ui/search-bar/types/index.ts similarity index 100% rename from client/src/widgets/search/types/index.ts rename to client/src/shared/ui/search-bar/types/index.ts diff --git a/client/src/widgets/search/types/types.ts b/client/src/shared/ui/search-bar/types/types.ts similarity index 70% rename from client/src/widgets/search/types/types.ts rename to client/src/shared/ui/search-bar/types/types.ts index e692f538f..c271993e0 100644 --- a/client/src/widgets/search/types/types.ts +++ b/client/src/shared/ui/search-bar/types/types.ts @@ -1,3 +1,4 @@ +import { Dispatch, SetStateAction } from 'react'; import { MultiValue } from 'react-select'; interface IFilter { @@ -36,3 +37,14 @@ export interface IRangeFilter extends IFilter { } export type Filter = ITextFilter | ICheckboxFilter | IMultipleFilter | IRangeFilter; + +export interface IFilterParams { + [key: string]: string | [number, number] | string[]; +} + +export interface SearchContextType { + filterArr: Filter[]; + setFilterArr: Dispatch>; + filterIndex: number; + setFilterIndex: Dispatch>; +} diff --git a/client/src/shared/ui/search-bar/ui/filter-menu/filter-menu.module.scss b/client/src/shared/ui/search-bar/ui/filter-menu/filter-menu.module.scss new file mode 100644 index 000000000..63c8f2178 --- /dev/null +++ b/client/src/shared/ui/search-bar/ui/filter-menu/filter-menu.module.scss @@ -0,0 +1,13 @@ +.searchinput_wrapper { + width: 100%; + min-height: 40px; + border: 1px solid var(--green-normal-color); + border-radius: 10px; + overflow: hidden; +} + +.menu_wrapper { + height: 100%; + margin-bottom: 10px; + overflow: hidden; +} diff --git a/client/src/shared/ui/search-bar/ui/filter-menu/filter-menu.tsx b/client/src/shared/ui/search-bar/ui/filter-menu/filter-menu.tsx new file mode 100644 index 000000000..fd97f53d3 --- /dev/null +++ b/client/src/shared/ui/search-bar/ui/filter-menu/filter-menu.tsx @@ -0,0 +1,19 @@ +import { Flex } from '@/shared/ui'; +import { SearchInput } from '../search-input'; +import { TagList } from '../tag-list'; +import styles from './filter-menu.module.scss'; +import { useState } from 'react'; + +export const FilterMenu = () => { + const [menuWrapper, setMenuWrapper] = useState(null); + + return ( + + + + + +
+
+ ); +}; diff --git a/client/src/shared/ui/search-bar/ui/filter-menu/index.ts b/client/src/shared/ui/search-bar/ui/filter-menu/index.ts new file mode 100644 index 000000000..83e1b27a6 --- /dev/null +++ b/client/src/shared/ui/search-bar/ui/filter-menu/index.ts @@ -0,0 +1 @@ +export { FilterMenu } from './filter-menu'; diff --git a/client/src/widgets/search/ui/filter-select/filter-select.module.scss b/client/src/shared/ui/search-bar/ui/filter-select/filter-select.module.scss similarity index 100% rename from client/src/widgets/search/ui/filter-select/filter-select.module.scss rename to client/src/shared/ui/search-bar/ui/filter-select/filter-select.module.scss diff --git a/client/src/widgets/search/ui/filter-select/filter-select.tsx b/client/src/shared/ui/search-bar/ui/filter-select/filter-select.tsx similarity index 68% rename from client/src/widgets/search/ui/filter-select/filter-select.tsx rename to client/src/shared/ui/search-bar/ui/filter-select/filter-select.tsx index 673b85660..af6894d23 100644 --- a/client/src/widgets/search/ui/filter-select/filter-select.tsx +++ b/client/src/shared/ui/search-bar/ui/filter-select/filter-select.tsx @@ -1,21 +1,13 @@ -import { FC, useState } from 'react'; +import { useState } from 'react'; import styles from './filter-select.module.scss'; import { SingleValue } from 'react-select'; import { Select } from '@/shared/ui'; import { Filter } from '../../types'; +import { useFilters } from '../../hooks'; -interface FilterSelectProps { - filtersArr: Filter[]; - filterIndex: number; - setFilterIndex: (index: number) => void; -} - -export const FilterSelect: FC = ({ - filtersArr, - filterIndex, - setFilterIndex, -}) => { +export const FilterSelect = () => { const [isMenuOpened, setIsMenuOpened] = useState(false); + const { filterArr, filterIndex, setFilterIndex } = useFilters(); const handleMenuClose = () => { setIsMenuOpened(false); @@ -23,7 +15,7 @@ export const FilterSelect: FC = ({ const handleChange = (newValue: SingleValue) => { if (newValue) { - const index = filtersArr.findIndex((item: Filter) => item.value === newValue.value); + const index = filterArr.findIndex((item: Filter) => item.value === newValue.value); setFilterIndex(index); } }; @@ -32,7 +24,7 @@ export const FilterSelect: FC = ({
({ padding: '8px 0', + ...(menuWrapper ? { boxShadow: 'none' } : {}), }), + ...(menuWrapper + ? { + menu: () => ({ + background: 'var(--grey-dark-color)', + maxHeight: 'none', + height: '100%', + position: 'static', + }), + menuPortal: () => ({ + height: '100%', + width: 'auto', + position: 'static', + }), + } + : {}), }} classNames={{ menuList: () => styles.menu_list, @@ -52,6 +71,7 @@ export const SearchSelect: FC = ({ isIndicatorAllowed={false} isCheckbox={isCheckbox} isMulti + menuPortalTarget={menuWrapper} /> diff --git a/client/src/widgets/search/ui/search-tag-menu/index.ts b/client/src/shared/ui/search-bar/ui/search-tag-menu/index.ts similarity index 100% rename from client/src/widgets/search/ui/search-tag-menu/index.ts rename to client/src/shared/ui/search-bar/ui/search-tag-menu/index.ts diff --git a/client/src/widgets/search/ui/search-tag-menu/search-tag-menu.module.scss b/client/src/shared/ui/search-bar/ui/search-tag-menu/search-tag-menu.module.scss similarity index 66% rename from client/src/widgets/search/ui/search-tag-menu/search-tag-menu.module.scss rename to client/src/shared/ui/search-bar/ui/search-tag-menu/search-tag-menu.module.scss index bea6ec68b..2673ea231 100644 --- a/client/src/widgets/search/ui/search-tag-menu/search-tag-menu.module.scss +++ b/client/src/shared/ui/search-bar/ui/search-tag-menu/search-tag-menu.module.scss @@ -1,6 +1,20 @@ .container { + position: relative; +} + +.menu_wrapper { + border-radius: 5px; + overflow: hidden; +} + +.menu_list { + position: absolute; + z-index: 5; + top: 0; + left: 0; border-radius: 5px; overflow: hidden; + box-shadow: 0px 4px 24px 0px rgba(17, 20, 27, 0.25); } .clear_all_button { diff --git a/client/src/widgets/search/ui/search-tag-menu/search-tag-menu.tsx b/client/src/shared/ui/search-bar/ui/search-tag-menu/search-tag-menu.tsx similarity index 75% rename from client/src/widgets/search/ui/search-tag-menu/search-tag-menu.tsx rename to client/src/shared/ui/search-bar/ui/search-tag-menu/search-tag-menu.tsx index 8d3c568e7..c0857c922 100644 --- a/client/src/widgets/search/ui/search-tag-menu/search-tag-menu.tsx +++ b/client/src/shared/ui/search-bar/ui/search-tag-menu/search-tag-menu.tsx @@ -22,16 +22,24 @@ export const SearchTagMenu: FC = ({ const filterListRef = useClickOutside(() => setIsListOpened(false)); return ( -
setIsListOpened(true)} ref={filterListRef}> +
+
setIsListOpened(true)}> + + +{filterItem.filterValue.length - 1}{' '} + {filterItem.filterValue.length > 2 ? 'items' : 'item'} + +
{isListOpened ? ( -
    +
      {filterItem.filterValue.slice(1).map((item: IOptionItem, index: number) => (
    • onClearOption(filterIndex, index + 1)} isWithCross - text={item.label} - /> + isRounded={false} + onClick={() => onClearOption(filterIndex, index + 1)} + > + {item.label} +
    • ))}
    • @@ -47,14 +55,7 @@ export const SearchTagMenu: FC = ({
    - ) : ( - 2 ? 'items' : 'item' - }`} - /> - )} + ) : null}
); }; diff --git a/client/src/widgets/search/ui/tag-list/index.ts b/client/src/shared/ui/search-bar/ui/tag-list/index.ts similarity index 100% rename from client/src/widgets/search/ui/tag-list/index.ts rename to client/src/shared/ui/search-bar/ui/tag-list/index.ts diff --git a/client/src/widgets/search/ui/tag-list/tag-list.module.scss b/client/src/shared/ui/search-bar/ui/tag-list/tag-list.module.scss similarity index 100% rename from client/src/widgets/search/ui/tag-list/tag-list.module.scss rename to client/src/shared/ui/search-bar/ui/tag-list/tag-list.module.scss diff --git a/client/src/shared/ui/search-bar/ui/tag-list/tag-list.tsx b/client/src/shared/ui/search-bar/ui/tag-list/tag-list.tsx new file mode 100644 index 000000000..c5ea2a6db --- /dev/null +++ b/client/src/shared/ui/search-bar/ui/tag-list/tag-list.tsx @@ -0,0 +1,124 @@ +import { FC } from 'react'; +import styles from './tag-list.module.scss'; +import { Tag } from '../tag'; +import { SearchTagMenu } from '../search-tag-menu'; +import { useFilters } from '../../hooks'; +import { Filter } from '../../types'; + +interface TagListProps { + isOnlyCurrentFilterTags?: boolean; +} + +export const TagList: FC = ({ isOnlyCurrentFilterTags = false }) => { + const { filterArr, setFilterArr, filterIndex } = useFilters(); + const currentFilter = filterArr[filterIndex]; + + if (!filterArr.length) { + return null; + } + + const handleClearTextFilter = (filterIndex: number) => { + setFilterArr(prev => + prev.map((item, index) => { + if (filterIndex === index) { + item.filterValue = ''; + } + + return item; + }) + ); + }; + + const handleClearMultipleOption = (filterIndex: number, index: number) => { + setFilterArr(prev => { + const filter = prev[filterIndex]; + + if (filter.type === 'checkbox' || filter.type === 'multiple') { + const newFilterValue = filter.filterValue.filter((item, i) => i !== index); + + return prev.map((item, i) => { + if (filterIndex === i) { + item.filterValue = newFilterValue; + } + + return item; + }); + } + + return prev; + }); + }; + + const handleClearAllMultipleOptions = (filterIndex: number) => { + setFilterArr(prev => { + const filter = prev[filterIndex]; + + if (filter.type === 'checkbox' || filter.type === 'multiple') { + const newFilterValue = [filter.filterValue[0]]; + + return prev.map((item, i) => { + if (filterIndex === i) { + item.filterValue = newFilterValue; + } + + return item; + }); + } + + return prev; + }); + }; + + const renderTag = (filterItem: Filter, index: number) => { + switch (filterItem.type) { + case 'text': + return filterItem.filterValue.length ? ( +
  • + handleClearTextFilter(index)}> + {filterItem.filterValue} + +
  • + ) : null; + + case 'multiple': + case 'checkbox': + if (filterItem.filterValue.length) { + return ( +
  • + handleClearMultipleOption(index, 0)}> + {filterItem.filterValue[0].label} + + {filterItem.filterValue.length > 1 && ( + + )} +
  • + ); + } + + return null; + + default: + } + }; + + const tagElements = isOnlyCurrentFilterTags + ? renderTag(currentFilter, filterIndex) + : filterArr.map((item, index) => renderTag(item, index)); + + const tagList = tagElements ? ( + Array.isArray(tagElements) ? ( + tagElements.length && tagElements.some(item => item) ? ( +
      {tagElements}
    + ) : null + ) : ( +
      {tagElements}
    + ) + ) : null; + + return tagList; +}; diff --git a/client/src/widgets/search/ui/tag/index.ts b/client/src/shared/ui/search-bar/ui/tag/index.ts similarity index 100% rename from client/src/widgets/search/ui/tag/index.ts rename to client/src/shared/ui/search-bar/ui/tag/index.ts diff --git a/client/src/widgets/search/ui/tag/tag.module.scss b/client/src/shared/ui/search-bar/ui/tag/tag.module.scss similarity index 93% rename from client/src/widgets/search/ui/tag/tag.module.scss rename to client/src/shared/ui/search-bar/ui/tag/tag.module.scss index da402ee92..59768ca76 100644 --- a/client/src/widgets/search/ui/tag/tag.module.scss +++ b/client/src/shared/ui/search-bar/ui/tag/tag.module.scss @@ -6,6 +6,10 @@ background: var(--grey-dark-color); cursor: pointer; + &_rounded { + border-radius: 5px; + } + &:hover { .remove { svg { diff --git a/client/src/widgets/search/ui/tag/tag.tsx b/client/src/shared/ui/search-bar/ui/tag/tag.tsx similarity index 67% rename from client/src/widgets/search/ui/tag/tag.tsx rename to client/src/shared/ui/search-bar/ui/tag/tag.tsx index 68c1b7c22..d8f5886e2 100644 --- a/client/src/widgets/search/ui/tag/tag.tsx +++ b/client/src/shared/ui/search-bar/ui/tag/tag.tsx @@ -1,24 +1,31 @@ -import { FC } from 'react'; +import { FC, PropsWithChildren } from 'react'; import styles from './tag.module.scss'; import { XIcon } from '@/shared/assets'; import { Flex, Typography } from '@/shared/ui'; import clsx from 'clsx'; interface TagProps { - text: string; isWithCross?: boolean; isFilledWhileHover?: boolean; + isRounded?: boolean; onClick?: () => void; } -export const Tag: FC = ({ - text, +export const Tag: FC> = ({ onClick, isWithCross = false, isFilledWhileHover = false, + isRounded = true, + children, }) => { return ( - + = ({ size='body_s' variant='p' > - {text} + {children} {isWithCross && ( diff --git a/client/src/widgets/search/ui/text-input/index.ts b/client/src/shared/ui/search-bar/ui/text-input/index.ts similarity index 100% rename from client/src/widgets/search/ui/text-input/index.ts rename to client/src/shared/ui/search-bar/ui/text-input/index.ts diff --git a/client/src/widgets/search/ui/text-input/text-input.module.scss b/client/src/shared/ui/search-bar/ui/text-input/text-input.module.scss similarity index 100% rename from client/src/widgets/search/ui/text-input/text-input.module.scss rename to client/src/shared/ui/search-bar/ui/text-input/text-input.module.scss diff --git a/client/src/widgets/search/ui/text-input/text-input.tsx b/client/src/shared/ui/search-bar/ui/text-input/text-input.tsx similarity index 100% rename from client/src/widgets/search/ui/text-input/text-input.tsx rename to client/src/shared/ui/search-bar/ui/text-input/text-input.tsx diff --git a/client/src/shared/ui/select/lib/select-styles.tsx b/client/src/shared/ui/select/lib/select-styles.tsx index 6e6eaf805..19dcf59f3 100644 --- a/client/src/shared/ui/select/lib/select-styles.tsx +++ b/client/src/shared/ui/select/lib/select-styles.tsx @@ -2,17 +2,17 @@ import { CSSObjectWithLabel, GroupBase, OptionProps, StylesConfig } from 'react- const _colors = { grey: { - normal: '#8f9094', - dark: '#2f3239', - medium: '#434752', + normal: 'var(--grey-normal-color)', + dark: 'var(--grey-dark-color)', + medium: 'var(--grey-medium-color)', }, green: { - bright: '#5bd424', - dark: '#064006', - active: '#1c8111', + bright: 'var(--green-bright-color)', + dark: 'var(--green-dark-color)', + active: 'var(--green-active-color)', }, - white: '#fff', - red: '#d42422', + white: 'var(--white-color)', + red: 'var(--red-error-color)', }; const getFocusAndActiveStyles = (isWithBorder?: boolean) => ({ @@ -70,6 +70,7 @@ export const selectStyles = < const customStyles = styles || {}; return { + ...customStyles, control: (base, props) => ({ ...base, outline: 'none', @@ -89,33 +90,33 @@ export const selectStyles = < ':focus-within': { ...getFocusAndActiveStyles(isWithBorder) }, caretColor: _colors.green.bright, // Spreading custom styles - ...(customStyles.control ? customStyles.control({}, props) : {}), + ...(customStyles.control && customStyles.control({}, props)), }), dropdownIndicator: (base, props) => ({ ...base, padding: 0, cursor: 'pointer', - ...(customStyles.dropdownIndicator ? customStyles.dropdownIndicator({}, props) : {}), + ...(customStyles.dropdownIndicator && customStyles.dropdownIndicator({}, props)), }), valueContainer: (base, props) => ({ ...base, padding: 0, margin: 0, - ...(customStyles.valueContainer ? customStyles.valueContainer({}, props) : {}), + ...(customStyles.valueContainer && customStyles.valueContainer({}, props)), }), singleValue: (base, props) => ({ ...base, color: _colors.white, padding: 0, margin: 0, - ...(customStyles.singleValue ? customStyles.singleValue({}, props) : {}), + ...(customStyles.singleValue && customStyles.singleValue({}, props)), }), input: (base, props) => ({ ...base, color: _colors.white, padding: 0, margin: 0, - ...(customStyles.input ? customStyles.input({}, props) : {}), + ...(customStyles.input && customStyles.input({}, props)), }), menu: (base, props) => ({ ...base, @@ -124,7 +125,7 @@ export const selectStyles = < background: 'transparent', paddingTop: '8px', maxHeight: '300px', - ...(customStyles.menu ? customStyles.menu({}, props) : {}), + ...(customStyles.menu && customStyles.menu({}, props)), }), menuList: (base, props) => ({ ...base, @@ -134,7 +135,7 @@ export const selectStyles = < overflowY: 'auto', boxShadow: '0 4px 24px 0 rgb(17 20 27 / 25%)', background: _colors.grey.dark, - ...(customStyles.menuList ? customStyles.menuList({}, props) : {}), + ...(customStyles.menuList && customStyles.menuList({}, props)), }), option: (styles, props) => customStyles.option @@ -152,13 +153,13 @@ export const selectStyles = < ':hover': { background: _colors.grey.medium, }, - ...(customStyles.multiValue ? customStyles.multiValue({}, props) : {}), + ...(customStyles.multiValue && customStyles.multiValue({}, props)), }), multiValueLabel: (base, props) => ({ ...base, color: _colors.white, fontSize: '100%', - ...(customStyles.multiValueLabel ? customStyles.multiValueLabel({}, props) : {}), + ...(customStyles.multiValueLabel && customStyles.multiValueLabel({}, props)), }), multiValueRemove: (base, props) => ({ ...base, @@ -168,7 +169,7 @@ export const selectStyles = < fill: _colors.red, }, }, - ...(customStyles.multiValueRemove ? customStyles.multiValueRemove({}, props) : {}), + ...(customStyles.multiValueRemove && customStyles.multiValueRemove({}, props)), }), }; }; diff --git a/client/src/shared/ui/select/ui/select/select.tsx b/client/src/shared/ui/select/ui/select/select.tsx index 999f3d44c..d6cba231b 100644 --- a/client/src/shared/ui/select/ui/select/select.tsx +++ b/client/src/shared/ui/select/ui/select/select.tsx @@ -78,6 +78,7 @@ export const Select = < isWithBorder = true, isIndicatorAllowed = true, styles: customStyles, + menuPortalTarget, ...rest } = props; return ( @@ -112,7 +113,9 @@ export const Select = < isMulti={isMulti} isClearable={false} hideSelectedOptions={!isCheckbox} - menuPortalTarget={typeof window !== 'undefined' ? document.body : null} + menuPortalTarget={ + menuPortalTarget ? menuPortalTarget : typeof window !== 'undefined' ? document.body : null + } /> {error && (
      - {filtersArr.map((item, index) => { - switch (item.type) { - case 'text': - return item.filterValue.length ? ( -
    • - handleClearTextFilter(index)} - /> -
    • - ) : null; - - case 'multiple': - case 'checkbox': - if (item.filterValue.length) { - return ( -
    • -
      handleClearMultipleOption(index, 0)} - > - -
      - {item.filterValue.length > 1 && ( - - )} -
    • - ); - } - - return null; - - default: - } - })} -
    - ); -};