filter
This commit is contained in:
@@ -1,124 +1,120 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { cn } from '../../lib/cn';
|
import { cn } from '../../lib/cn';
|
||||||
import { checkMark, chevroneDropDownList } from '../../assets/icons/input';
|
import { checkMark } from '../../assets/icons/input';
|
||||||
import { useClickOutside } from '../../hooks/useClickOutside';
|
import { useClickOutside } from '../../hooks/useClickOutside';
|
||||||
|
import { Sort, SortActive } from '../../assets/icons/filters';
|
||||||
|
|
||||||
export interface FilterItem {
|
export interface FilterItem {
|
||||||
text: string;
|
text: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterProps {
|
interface FilterProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
onChange: (state: string[]) => void; // теперь массив выбранных значений
|
onChange: (items: FilterItem[]) => void;
|
||||||
defaultState?: FilterItem[];
|
defaultState?: FilterItem[];
|
||||||
items: FilterItem[];
|
items: FilterItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Filter: React.FC<FilterProps> = ({
|
export const Filter: React.FC<FilterProps> = ({
|
||||||
className = '',
|
disabled = false,
|
||||||
onChange,
|
className = '',
|
||||||
defaultState = [],
|
onChange,
|
||||||
items = [{ text: '', value: '' }],
|
defaultState = [],
|
||||||
|
items = [],
|
||||||
}) => {
|
}) => {
|
||||||
if (items.length === 0) items.push({ text: '', value: '' });
|
const [value, setValue] = React.useState<FilterItem[]>(defaultState);
|
||||||
|
const [active, setActive] = React.useState(false);
|
||||||
|
|
||||||
const [selectedValues, setSelectedValues] =
|
const ref = React.useRef<HTMLDivElement>(null);
|
||||||
React.useState<FilterItem[]>(defaultState);
|
|
||||||
const [active, setActive] = React.useState<boolean>(false);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
useClickOutside(ref, () => {
|
||||||
onChange(selectedValues.map((v) => v.value));
|
setActive(false);
|
||||||
}, [selectedValues]);
|
});
|
||||||
|
|
||||||
const ref = React.useRef<HTMLDivElement>(null);
|
React.useEffect(() => {
|
||||||
useClickOutside(ref, () => setActive(false));
|
onChange(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
const toggleItem = (item: FilterItem) => {
|
const toggleItem = (item: FilterItem) => {
|
||||||
const exists = selectedValues.some((v) => v.value === item.value);
|
const exists = value.some((val) => val.value === item.value);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
setSelectedValues(
|
setValue(value.filter((val) => val.value !== item.value));
|
||||||
selectedValues.filter((v) => v.value !== item.value),
|
} else {
|
||||||
);
|
setValue([...value, item]);
|
||||||
} else {
|
}
|
||||||
setSelectedValues([...selectedValues, item]);
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const displayText =
|
return (
|
||||||
selectedValues.length > 0
|
<div className={cn('relative', className)} ref={ref}>
|
||||||
? selectedValues.map((v) => v.text).join(', ')
|
<div
|
||||||
: 'Выберите...';
|
className={cn(
|
||||||
|
'items-center h-[40px] rounded-full bg-liquid-lighter w-[40px] flex',
|
||||||
return (
|
'text-[18px] font-bold cursor-pointer select-none',
|
||||||
<div className={cn('relative', className)} ref={ref}>
|
'overflow-hidden',
|
||||||
<div
|
(active || value.length > 0) && 'w-fit border-liquid-brightmain border-[1px] border-solid',
|
||||||
className={cn(
|
)}
|
||||||
'flex items-center h-[40px] rounded-[10px] bg-liquid-lighter px-[16px] w-[220px]',
|
onClick={() => {
|
||||||
'text-[16px] font-bold cursor-pointer select-none',
|
if (!disabled) setActive(!active);
|
||||||
'transition-all active:scale-95 duration-300 overflow-hidden whitespace-nowrap text-ellipsis',
|
}}
|
||||||
)}
|
>
|
||||||
onClick={() => setActive(!active)}
|
<div className="text-liquid-brightmain pl-[42px] pr-[16px] w-fit">
|
||||||
title={displayText}
|
{value.length}
|
||||||
>
|
|
||||||
{displayText}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<img
|
|
||||||
src={chevroneDropDownList}
|
|
||||||
className={cn(
|
|
||||||
'absolute right-[16px] h-[24px] w-[24px] top-[8.5px] rotate-0 transition-all duration-300 pointer-events-none',
|
|
||||||
active && 'rotate-180',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'absolute rounded-[10px] bg-liquid-lighter w-[220px] left-0 top-[48px] z-50 transition-all duration-300',
|
|
||||||
'grid overflow-hidden',
|
|
||||||
active
|
|
||||||
? 'grid-rows-[1fr] opacity-100'
|
|
||||||
: 'grid-rows-[0fr] opacity-0',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="overflow-hidden p-[8px]">
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'overflow-y-scroll max-h-[220px] thin-scrollbar pr-[8px]',
|
|
||||||
'grid grid-cols-2 gap-[4px]',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{items.map((v, i) => {
|
|
||||||
const isSelected = selectedValues.some(
|
|
||||||
(sel) => sel.value === v.value,
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className={cn(
|
|
||||||
'cursor-pointer h-[36px] relative transition-all duration-300 flex items-center pl-[8px] pr-[24px] rounded-[6px]',
|
|
||||||
'text-[14px] font-medium select-none',
|
|
||||||
'hover:bg-liquid-background',
|
|
||||||
isSelected &&
|
|
||||||
'bg-liquid-background font-semibold',
|
|
||||||
)}
|
|
||||||
onClick={() => toggleItem(v)}
|
|
||||||
>
|
|
||||||
{v.text}
|
|
||||||
|
|
||||||
{isSelected && (
|
|
||||||
<img
|
|
||||||
src={checkMark}
|
|
||||||
className="absolute right-[6px] w-[16px] h-[16px]"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
|
||||||
|
{/* Sort icons */}
|
||||||
|
<img
|
||||||
|
src={Sort}
|
||||||
|
className={cn(
|
||||||
|
'absolute left-[8px] top-[8px] h-[24px] w-[24px] rotate-0 transition-all duration-300 pointer-events-none',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={SortActive}
|
||||||
|
className={cn(
|
||||||
|
'absolute left-[8px] top-[8px] h-[24px] w-[24px] rotate-0 transition-all duration-300 pointer-events-none opacity-0',
|
||||||
|
(active || value.length > 0) && 'opacity-100',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Dropdown */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'absolute rounded-[10px] bg-liquid-lighter w-[460px] left-0 top-[48px] z-50 transition-all duration-300',
|
||||||
|
'grid overflow-hidden',
|
||||||
|
active ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="overflow-hidden p-[8px]">
|
||||||
|
<div className="overflow-y-scroll max-h-[200px] thin-scrollbar pr-[8px] grid grid-cols-2 gap-[20px]">
|
||||||
|
{items.map((v) => {
|
||||||
|
const selected = value.some((val) => val.value === v.value);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={v.value}
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer h-[36px] relative transition-all duration-300',
|
||||||
|
'text-[16px] font-medium select-none flex items-center pl-[8px]',
|
||||||
|
'hover:bg-liquid-background rounded-[10px]',
|
||||||
|
selected && 'bg-liquid-background/50',
|
||||||
|
)}
|
||||||
|
onClick={() => toggleItem(v)}
|
||||||
|
>
|
||||||
|
{v.text}
|
||||||
|
{selected && (
|
||||||
|
<img
|
||||||
|
src={checkMark}
|
||||||
|
className="absolute right-[8px] h-[20px] w-[20px]"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ export const Sorter: React.FC<SorterProps> = ({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'grid items-center h-[40px] rounded-full bg-liquid-lighter grid-cols-[40px]',
|
'grid items-center h-[40px] rounded-full bg-liquid-lighter grid-cols-[40px]',
|
||||||
'text-[18px] font-bold cursor-pointer select-none',
|
'text-[18px] font-bold cursor-pointer select-none',
|
||||||
'transitin-all active:scale-95 duration-300 overflow-hidden',
|
'overflow-hidden',
|
||||||
active && ' grid-cols-[1fr]',
|
active && ' grid-cols-[1fr] border-liquid-brightmain border-[1px] border-solid',
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActive(!active);
|
setActive(!active);
|
||||||
@@ -54,7 +54,7 @@ export const Sorter: React.FC<SorterProps> = ({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-liquid-brightmain pl-[40px] pr-[16px]',
|
'text-liquid-brightmain pl-[42px] pr-[16px]',
|
||||||
active && '',
|
active && '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -63,7 +63,7 @@ export const Sorter: React.FC<SorterProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-[24px] w-[24px] bg-red-500"></div>
|
<div className="h-[24px] w-[24px]"></div>
|
||||||
<img
|
<img
|
||||||
src={Sort}
|
src={Sort}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const Filters = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" h-[50px] mb-[20px] flex">
|
<div className=" h-[50px] mb-[20px] flex gap-[20px]">
|
||||||
<div></div>
|
<div></div>
|
||||||
|
|
||||||
<Sorter
|
<Sorter
|
||||||
@@ -35,12 +35,11 @@ const Filters = () => {
|
|||||||
onChange={(v) => console.log(v)}
|
onChange={(v) => console.log(v)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <Filter
|
<Filter
|
||||||
items={items}
|
items={items}
|
||||||
defaultState={[items[0], items[3]]} // начальные выбранные элементы
|
defaultState={[]}
|
||||||
onChange={(values) => console.log(values)} // обработчик изменения
|
onChange={(values) => console.log(values)}
|
||||||
className="w-[240px]"
|
/>
|
||||||
/> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user