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

Commit

Permalink
feat: new search bar (#133) 🤟🏻
Browse files Browse the repository at this point in the history
* feat: add border disable for ui select

* feat: add filter-select draft functionality

* fix: closing select menu and codestyle

* fix: conflicts with develop

* fix: removed server actions

* fix: imports

* fix: select and input border

* fix: filter-select

* fix: some fixes for select

* fix: interfaces of searchbar

* feat: add draft search-input functionality

* fix: filter type in filter select

* fix: some fixes for selects

* feat: add draft functionality for tags-list

* feat: add placeholders and tags-list for searchbar

* feat: add outside click closing for multiple-tag

* fix: fix text-filter clearing

* fix: some tag fixes

* feat: add clear all button in tags list

* fix: change file structure

* feat: add select indicator toggle

* fix: fix search-bar styles

* feat: add api request handler for search-input

* fix: move tag in entities

* fix: move tag in features

* feat: add some filters for search-bar on page

* fix: tags-list filtersArr typization

* feat: replace react hook form functionality from search bar with native react

* fix: checkbox-list functionality typization

* fix: codestyle fixes

* feat: add query string functionality for search-bar

* fix: replace flex divs with Flex component

* feat: add search-bar in storybook

* feat: add multiple value filter functionality

* fix: add multiple filter in stories / fix timer of search-bar

* fix: codestyle fix in tag-menu

* fix: destructurize fix

* fix: state changing in search-bar

* feat: add custom styles support in shared select

* fix: margins and paddings of search-bar

* fix: design fixes

* fix: class naming fixes

* fix: menu columns in search-bar input-select

* fix: style fixes

* fix: clear logs and fix naming of callbacks

* fix: codereview fixes

---------

Co-authored-by: Nikita Mashchenko <[email protected]>
Co-authored-by: skiselev <[email protected]>
  • Loading branch information
3 people committed Nov 13, 2023
1 parent dbc86d8 commit 639523f
Show file tree
Hide file tree
Showing 34 changed files with 901 additions and 36 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"eslint-config-next": "13.4.12",
"lodash.debounce": "^4.0.8",
"next": "13.4.12",
"qs": "^6.11.2",
"react": "18.2.0",
"react-content-loader": "^6.2.1",
"react-dom": "18.2.0",
Expand Down
13 changes: 8 additions & 5 deletions client/src/shared/ui/input/input/input.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,12 @@
overflow: hidden;
background: inherit;
padding: 8px 4px;
border-bottom: 1px solid var(--grey-normal-color);
caret-color: var(--green-bright-color);
color: var(--white-color);
transition: all 0.1s ease 0s;
font: var(--font-body-weight-m) var(--font-body-size-m) / var(--font-body-line-m)
var(--font-rubik);

&:focus {
border-bottom: 1px solid var(--white-color);
}

&:hover:not(:disabled) {
background-color: var(--grey-dark-color);
}
Expand All @@ -54,6 +49,14 @@
&__two_icons_end {
padding-right: 64px;
}

&_withBorder {
border-bottom: 1px solid var(--grey-normal-color);

&:focus {
border-bottom: 1px solid var(--white-color);
}
}
}

.input[type='password']:not(:placeholder-shown) {
Expand Down
3 changes: 3 additions & 0 deletions client/src/shared/ui/input/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
maxWidth?: string;
subIconPosition?: IconPosition;
subIcon?: JSX.Element;
isWithBorder?: boolean;
}

const InputComponent: ForwardRefRenderFunction<HTMLInputElement, InputProps> = (
Expand All @@ -60,6 +61,7 @@ const InputComponent: ForwardRefRenderFunction<HTMLInputElement, InputProps> = (
disabled,
subIconPosition = 'end',
subIcon,
isWithBorder = true,
...rest
} = props;

Expand All @@ -81,6 +83,7 @@ const InputComponent: ForwardRefRenderFunction<HTMLInputElement, InputProps> = (
[styles.input__icon_start]: subIcon && subIconPosition === 'start',
[styles.input__icon_end]: (subIcon && subIconPosition === 'end') || error,
[styles.input__two_icons_end]: subIcon && subIconPosition === 'end' && error,
[styles.input__withBorder]: isWithBorder,
})}
aria-invalid={Boolean(error)}
aria-describedby={error ? `${name}-error` : undefined}
Expand Down
76 changes: 48 additions & 28 deletions client/src/shared/ui/select/lib/select-styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ const _colors = {
red: '#d42422',
};

const _focusAndActiveStyles = {
const getFocusAndActiveStyles = (isWithBorder?: boolean) => ({
boxShadow: 'none',
borderBottom: `1px solid ${_colors.white}`,
};
borderBottom: isWithBorder ? `1px solid ${_colors.white}` : 'none',
});
const _checkboxOption = <
OptionType,
IsMultiType extends boolean = false,
Expand Down Expand Up @@ -63,11 +63,15 @@ export const selectStyles = <
IsMultiType extends boolean = false,
GroupType extends GroupBase<OptionType> = GroupBase<OptionType>,
>(
isCheckbox = false
styles?: StylesConfig<OptionType, IsMultiType, GroupType>,
isCheckbox = false,
isWithBorder = true
): StylesConfig<OptionType, IsMultiType, GroupType> => {
const customStyles = styles || {};

return {
control: styles => ({
...styles,
control: (base, props) => ({
...base,
outline: 'none',
border: 'none',
borderRadius: 0,
Expand All @@ -77,56 +81,69 @@ export const selectStyles = <
cursor: 'text',
padding: '8px 4px',
fontSize: '100%',
borderBottom: `1px solid ${_colors.grey.normal}`,
borderBottom: isWithBorder ? `1px solid ${_colors.grey.normal}` : 'none',
// This line disable the blue border
boxShadow: 'none',
':active': { ..._focusAndActiveStyles },
':focus': { ..._focusAndActiveStyles },
':focus-within': { ..._focusAndActiveStyles },
':active': { ...getFocusAndActiveStyles(isWithBorder) },
':focus': { ...getFocusAndActiveStyles(isWithBorder) },
':focus-within': { ...getFocusAndActiveStyles(isWithBorder) },
caretColor: _colors.green.bright,
// Spreading custom styles
...(customStyles.control ? customStyles.control({}, props) : {}),
}),
dropdownIndicator: styles => ({
...styles,
dropdownIndicator: (base, props) => ({
...base,
padding: 0,
cursor: 'pointer',
...(customStyles.dropdownIndicator ? customStyles.dropdownIndicator({}, props) : {}),
}),
valueContainer: styles => ({
...styles,
valueContainer: (base, props) => ({
...base,
padding: 0,
margin: 0,
...(customStyles.valueContainer ? customStyles.valueContainer({}, props) : {}),
}),
singleValue: styles => ({
...styles,
singleValue: (base, props) => ({
...base,
color: _colors.white,
padding: 0,
margin: 0,
...(customStyles.singleValue ? customStyles.singleValue({}, props) : {}),
}),
input: styles => ({
...styles,
input: (base, props) => ({
...base,
color: _colors.white,
padding: 0,
margin: 0,
...(customStyles.input ? customStyles.input({}, props) : {}),
}),
menu: () => ({
menu: (base, props) => ({
...base,
padding: 0,
margin: 0,
background: 'transparent',
paddingTop: '8px',
maxHeight: '300px',
...(customStyles.menu ? customStyles.menu({}, props) : {}),
}),
menuList: styles => ({
...styles,
menuList: (base, props) => ({
...base,
padding: 0,
margin: 0,
borderRadius: '5px',
overflowY: 'auto',
boxShadow: '0 4px 24px 0 rgb(17 20 27 / 25%)',
background: _colors.grey.dark,
...(customStyles.menuList ? customStyles.menuList({}, props) : {}),
}),
option: (styles, props) =>
isCheckbox ? _checkboxOption(styles, props) : _regularOption(styles, props),
multiValue: styles => ({
...styles,
customStyles.option
? customStyles.option({}, props)
: isCheckbox
? _checkboxOption(styles, props)
: _regularOption(styles, props),
multiValue: (base, props) => ({
...base,
cursor: 'pointer',
borderRadius: '5px',
padding: '4px 8px',
Expand All @@ -135,20 +152,23 @@ export const selectStyles = <
':hover': {
background: _colors.grey.medium,
},
...(customStyles.multiValue ? customStyles.multiValue({}, props) : {}),
}),
multiValueLabel: styles => ({
...styles,
multiValueLabel: (base, props) => ({
...base,
color: _colors.white,
fontSize: '100%',
...(customStyles.multiValueLabel ? customStyles.multiValueLabel({}, props) : {}),
}),
multiValueRemove: styles => ({
...styles,
multiValueRemove: (base, props) => ({
...base,
background: 'none',
':hover': {
svg: {
fill: _colors.red,
},
},
...(customStyles.multiValueRemove ? customStyles.multiValueRemove({}, props) : {}),
}),
};
};
25 changes: 22 additions & 3 deletions client/src/shared/ui/select/ui/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { Option } from './option';
* @prop {string} [label] - Label to display above the select component.
* @prop {boolean} [disabled=false] - Specifies if the select should be disabled.
* @prop {boolean} [isCheckbox=false] - If true, the options in the select will be presented as checkboxes.
* @prop {boolean} [isWithBorder=true] - If true, then a border will appear under the select.
* @prop {boolean} [isIndicatorAllowed=true] - If true, then there will be a dropdown indicator near the value.
* @prop {Option[]} options - Array of options to be displayed in the select. Each option should have a `label` and `value`.
* @prop ... and all other props supported by `ReactSelect`.
*
Expand Down Expand Up @@ -54,6 +56,8 @@ interface CustomSelectProps {
label?: string;
disabled?: boolean;
isCheckbox?: boolean;
isWithBorder?: boolean;
isIndicatorAllowed?: boolean;
options: Option[];
}

Expand All @@ -64,7 +68,18 @@ export const Select = <
>(
props: Props<Option, IsMulti, Group> & CustomSelectProps
) => {
const { error, label, disabled, isMulti, isCheckbox = false, name, ...rest } = props;
const {
error,
label,
disabled,
name,
isMulti,
isCheckbox,
isWithBorder = true,
isIndicatorAllowed = true,
styles: customStyles,
...rest
} = props;
return (
<div
className={clsx(styles.container, {
Expand All @@ -80,10 +95,14 @@ export const Select = <
{...rest}
instanceId='t8s-select'
closeMenuOnSelect={!isMulti}
styles={selectStyles<Option, IsMulti, Group>(isCheckbox)}
styles={selectStyles<Option, IsMulti, Group>(customStyles, isCheckbox, isWithBorder)}
name={name}
components={{
DropdownIndicator: error ? ErrorIndicator : DropdownIndicator,
DropdownIndicator: isIndicatorAllowed
? error
? ErrorIndicator
: DropdownIndicator
: () => null,
IndicatorSeparator: () => null,
MultiValueRemove,
...(isCheckbox ? { Option } : {}), // Conditionally include custom Option component
Expand Down
1 change: 1 addition & 0 deletions client/src/widgets/search/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SearchBar } from './ui/search-bar';
52 changes: 52 additions & 0 deletions client/src/widgets/search/lib/hooks/useTrackFiltersArr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useEffect, useRef } from 'react';
import qs from 'qs';
import { Filter } from '../../types';

export const useTrackFiltersArr = (
filtersArr: Filter[],
callback: (queryString: string) => void
) => {
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}

timerRef.current = setTimeout(() => {
const filtersValues = {
filters: filtersArr.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;
}
},
{}
),
};

const queryString = qs.stringify(filtersValues);

callback(queryString);
}, 1300);
}, [filtersArr, callback]);
};
1 change: 1 addition & 0 deletions client/src/widgets/search/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './types';
38 changes: 38 additions & 0 deletions client/src/widgets/search/types/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { MultiValue } from 'react-select';

interface IFilter {
label: string;
value: string;
placeholder: string;
}

interface ITextFilter extends IFilter {
type: 'text';
filterValue: string;
}

export interface IOptionItem {
label: string;
value: string;
}

export interface ICheckboxFilter extends IFilter {
type: 'checkbox';
optionsArr: IOptionItem[];
filterValue: MultiValue<IOptionItem>;
}

export interface IMultipleFilter extends IFilter {
type: 'multiple';
optionsArr: IOptionItem[];
filterValue: MultiValue<IOptionItem>;
}

export interface IRangeFilter extends IFilter {
type: 'range';
min: number;
max: number;
filterValue: null | [number, number];
}

export type Filter = ITextFilter | ICheckboxFilter | IMultipleFilter | IRangeFilter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.container {
min-width: 170px;
border-right: 1px solid var(--green-normal-color);
height: 100%;
transition: all 0.1s ease 0s;

&:hover {
background-color: var(--grey-dark-color);
}
}

.select {
&_active {
background-color: var(--grey-dark-color);
}
}

.control,
.menu {
padding: 0px 8px;
}
Loading

2 comments on commit 639523f

@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-7nht7v5yt-exortme1ster.vercel.app

Built with commit 639523f.
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-d16pidh4l-exortme1ster.vercel.app

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

Please sign in to comment.