Skip to content
This repository has been archived by the owner on Mar 23, 2024. It is now read-only.

Commit

Permalink
feat: mobile search (#139)
Browse files Browse the repository at this point in the history
* 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” <[email protected]>
Co-authored-by: Nikita Mashchenko <[email protected]>
  • Loading branch information
3 people committed Dec 16, 2023
1 parent c00d1fe commit e257df5
Show file tree
Hide file tree
Showing 57 changed files with 804 additions and 298 deletions.
7 changes: 6 additions & 1 deletion client/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ export default function RootLayout({ children }: { children: ReactNode }) {
<Toaster
theme='dark'
toastOptions={{
style: { ...inter.style, background: '#2F3239', color: '#fff', border: 'none' },
style: {
...inter.style,
background: 'var(--grey-dark-color)',
color: 'var(--white-color)',
border: 'none',
},
}}
/>
{children}
Expand Down
15 changes: 14 additions & 1 deletion client/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -41,6 +41,19 @@ export default function Home() {
<a href='/login' style={{ color: 'green' }}>
Get to login
</a>

<SearchBar
initialFiltersState={[
{
type: 'text',
label: 'Name',
value: 'name',
placeholder: 'Search by name',
filterValue: '',
},
]}
onChange={() => console.log(1)}
/>
</>
);
}
1 change: 1 addition & 0 deletions client/src/entities/session/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { useLogout } from './useLogout';
export { useRegister } from './useRegister';
export { useResetPassword } from './useResetPassword';
export { useUpdateMe } from './useUpdateMe';
export { useGetUsers } from './useGetUsers';
31 changes: 31 additions & 0 deletions client/src/entities/session/api/useGetUsers.tsx
Original file line number Diff line number Diff line change
@@ -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<IUserProtectedResponse>(`${API_USERS}?${queryString}`);
return data;
},
refetchOnMount: false,
refetchOnWindowFocus: false,
});
};
1 change: 1 addition & 0 deletions client/src/shared/constant/server-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
2 changes: 1 addition & 1 deletion client/src/shared/ui/badge/badge-text/badge-text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const BadgeText: FC<BadgeFrameworkProps> = props => {
<div
className={clsx([className], styles.badge_text)}
style={{
backgroundColor: `${badgeColors[data] ? badgeColors[data] : '#2F3239'}`,
backgroundColor: `${badgeColors[data] ? badgeColors[data] : 'var(--grey-dark-color)'}`,
color: badgeTextColors[data],
maxWidth: `${maxWidth ? maxWidth : '100%'}`,
}}
Expand Down
17 changes: 16 additions & 1 deletion client/src/shared/ui/drawer/drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* @prop {boolean} open - Determines whether the drawer is visible (open) or not.
* @prop {function} onClose - Callback function that gets triggered when the drawer is requested to be closed.
* @prop {boolean} [isFullHeight=false] - Specifies whether the drawer should cover full height of the viewport. Default is `false`.
* @prop {string} [className] - Class for drawer content
* @prop {string} [overlayClassName] - Class for drawer overlay
* @prop {ReactNode} [children] - Content to be displayed inside the drawer.
*
* Usage:
Expand Down Expand Up @@ -41,9 +43,19 @@ interface DrawerProps {
onClose: () => void;
isFullHeight?: boolean;
direction?: 'left' | 'right' | 'top' | 'bottom';
className?: string;
overlayClassName?: string;
}
export const Drawer: FC<PropsWithChildren<DrawerProps>> = 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',
Expand All @@ -54,10 +66,13 @@ export const Drawer: FC<PropsWithChildren<DrawerProps>> = props => {

return (
<DrawerComponent
zIndex={99}
open={open}
onClose={onClose}
direction={direction}
style={style}
className={className}
overlayClassName={overlayClassName}
customIdSuffix='t8s-drawer'
lockBackgroundScroll
>
Expand Down
1 change: 1 addition & 0 deletions client/src/shared/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
1 change: 1 addition & 0 deletions client/src/shared/ui/search-bar/contexts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SearchContext } from './search-context';
4 changes: 4 additions & 0 deletions client/src/shared/ui/search-bar/contexts/search-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createContext } from 'react';
import { SearchContextType } from '../types';

export const SearchContext = createContext<SearchContextType | null>(null);
2 changes: 2 additions & 0 deletions client/src/shared/ui/search-bar/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { useTrackFilterArr } from './useTrackFiltersArr';
export { useFilters } from './useFilters';
12 changes: 12 additions & 0 deletions client/src/shared/ui/search-bar/hooks/useFilters.ts
Original file line number Diff line number Diff line change
@@ -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;
};
46 changes: 46 additions & 0 deletions client/src/shared/ui/search-bar/hooks/useTrackFiltersArr.ts
Original file line number Diff line number Diff line change
@@ -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<ReturnType<typeof setTimeout> | 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<string>(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]);
};
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const SearchBar_default = () => {
filterValue: [],
},
]}
callback={queryString => console.log(queryString)}
onChange={filterValues => console.log(filterValues)}
/>
);
};
83 changes: 83 additions & 0 deletions client/src/shared/ui/search-bar/search-bar.tsx
Original file line number Diff line number Diff line change
@@ -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';
*
* <SearchBar initialFiltersState={[{
* type: 'text',
* label: 'Name',
* value: 'name',
* placeholder: 'Search by name',
* filterValue: '',
* }]}
* onChange={string => console.log(string)}
* />
* ```
*/

interface SearchBarProps {
initialFiltersState: Filter[];
onChange: (filterValues: string | null) => void;
}

export const SearchBar: FC<SearchBarProps> = ({ 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 (
<SearchContext.Provider
value={{
filterArr,
setFilterArr,
filterIndex,
setFilterIndex,
}}
>
<ModalButton onOpen={onOpen} onClose={onClose} />

<Flex direction='column' gap='24px' className={styles.searchbar}>
<Flex className={styles.searchbar_content}>
<FilterSelect />
<SearchInput />
</Flex>
<TagList />
</Flex>

<Modal isOpened={isModalOpened} onClose={onClose} />
</SearchContext.Provider>
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Dispatch, SetStateAction } from 'react';
import { MultiValue } from 'react-select';

interface IFilter {
Expand Down Expand Up @@ -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<SetStateAction<Filter[]>>;
filterIndex: number;
setFilterIndex: Dispatch<SetStateAction<number>>;
}
Original file line number Diff line number Diff line change
@@ -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;
}
19 changes: 19 additions & 0 deletions client/src/shared/ui/search-bar/ui/filter-menu/filter-menu.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement | null>(null);

return (
<Flex direction='column' gap='16px' height='100%'>
<Flex className={styles.searchinput_wrapper}>
<SearchInput menuWrapper={menuWrapper} />
</Flex>
<TagList isOnlyCurrentFilterTags />
<div className={styles.menu_wrapper} ref={setMenuWrapper}></div>
</Flex>
);
};
1 change: 1 addition & 0 deletions client/src/shared/ui/search-bar/ui/filter-menu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FilterMenu } from './filter-menu';
Loading

2 comments on commit e257df5

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for teameights ready!

✅ Preview
https://teameights-iy7btlaqc-exortme1ster.vercel.app

Built with commit e257df5.
This pull request is being automatically deployed with vercel-action

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for teameights-storybook ready!

✅ Preview
https://teameights-storybook-mfh5vzy16-exortme1ster.vercel.app

Built with commit e257df5.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.