add filter
This commit is contained in:
@@ -2,119 +2,124 @@ import React from 'react';
|
||||
import { cn } from '../../lib/cn';
|
||||
import { checkMark } from '../../assets/icons/input';
|
||||
import { useClickOutside } from '../../hooks/useClickOutside';
|
||||
import { Sort, SortActive } from '../../assets/icons/filters';
|
||||
import { iconFilter, iconFilterActive } from '../../assets/icons/filters';
|
||||
|
||||
export interface FilterItem {
|
||||
text: string;
|
||||
value: string;
|
||||
text: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface FilterProps {
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
onChange: (items: FilterItem[]) => void;
|
||||
defaultState?: FilterItem[];
|
||||
items: FilterItem[];
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
onChange: (items: FilterItem[]) => void;
|
||||
defaultState?: FilterItem[];
|
||||
items: FilterItem[];
|
||||
}
|
||||
|
||||
export const Filter: React.FC<FilterProps> = ({
|
||||
disabled = false,
|
||||
className = '',
|
||||
onChange,
|
||||
defaultState = [],
|
||||
items = [],
|
||||
export const FilterDropDown: React.FC<FilterProps> = ({
|
||||
disabled = false,
|
||||
className = '',
|
||||
onChange,
|
||||
defaultState = [],
|
||||
items = [],
|
||||
}) => {
|
||||
const [value, setValue] = React.useState<FilterItem[]>(defaultState);
|
||||
const [active, setActive] = React.useState(false);
|
||||
const [value, setValue] = React.useState<FilterItem[]>(defaultState);
|
||||
const [active, setActive] = React.useState(false);
|
||||
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
useClickOutside(ref, () => {
|
||||
setActive(false);
|
||||
});
|
||||
useClickOutside(ref, () => {
|
||||
setActive(false);
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
onChange(value);
|
||||
}, [value]);
|
||||
React.useEffect(() => {
|
||||
onChange(value);
|
||||
}, [value]);
|
||||
|
||||
const toggleItem = (item: FilterItem) => {
|
||||
const exists = value.some((val) => val.value === item.value);
|
||||
if (exists) {
|
||||
setValue(value.filter((val) => val.value !== item.value));
|
||||
} else {
|
||||
setValue([...value, item]);
|
||||
}
|
||||
};
|
||||
const toggleItem = (item: FilterItem) => {
|
||||
const exists = value.some((val) => val.value === item.value);
|
||||
if (exists) {
|
||||
setValue(value.filter((val) => val.value !== item.value));
|
||||
} else {
|
||||
setValue([...value, item]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn('relative', className)} ref={ref}>
|
||||
<div
|
||||
className={cn(
|
||||
'items-center h-[40px] rounded-full bg-liquid-lighter w-[40px] flex',
|
||||
'text-[18px] font-bold cursor-pointer select-none',
|
||||
'overflow-hidden',
|
||||
(active || value.length > 0) && 'w-fit border-liquid-brightmain border-[1px] border-solid',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (!disabled) setActive(!active);
|
||||
}}
|
||||
>
|
||||
<div className="text-liquid-brightmain pl-[42px] pr-[16px] w-fit">
|
||||
{value.length}
|
||||
</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]"
|
||||
/>
|
||||
)}
|
||||
return (
|
||||
<div className={cn('relative', className)} ref={ref}>
|
||||
<div
|
||||
className={cn(
|
||||
'items-center h-[40px] rounded-full bg-liquid-lighter w-[40px] flex',
|
||||
'text-[18px] font-bold cursor-pointer select-none',
|
||||
'overflow-hidden',
|
||||
(active || value.length > 0) &&
|
||||
'w-fit border-liquid-brightmain border-[1px] border-solid',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (!disabled) setActive(!active);
|
||||
}}
|
||||
>
|
||||
<div className="text-liquid-brightmain pl-[42px] pr-[16px] w-fit">
|
||||
{value.length}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filter icons */}
|
||||
<img
|
||||
src={iconFilter}
|
||||
className={cn(
|
||||
'absolute left-[8px] top-[8px] h-[24px] w-[24px] rotate-0 transition-all duration-300 pointer-events-none',
|
||||
)}
|
||||
/>
|
||||
<img
|
||||
src={iconFilterActive}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import { cn } from '../../lib/cn';
|
||||
import { checkMark } from '../../assets/icons/input';
|
||||
import { useClickOutside } from '../../hooks/useClickOutside';
|
||||
import { Sort, SortActive } from '../../assets/icons/filters';
|
||||
import { iconSort, iconSortActive } from '../../assets/icons/filters';
|
||||
|
||||
export interface SorterItem {
|
||||
text: string;
|
||||
@@ -17,7 +17,7 @@ interface SorterProps {
|
||||
items: SorterItem[];
|
||||
}
|
||||
|
||||
export const Sorter: React.FC<SorterProps> = ({
|
||||
export const SorterDropDown: FC<SorterProps> = ({
|
||||
// disabled = false,
|
||||
className = '',
|
||||
onChange,
|
||||
@@ -26,14 +26,15 @@ export const Sorter: React.FC<SorterProps> = ({
|
||||
}) => {
|
||||
if (items.length == 0) items.push({ text: '', value: '' });
|
||||
|
||||
const [value, setValue] = React.useState<SorterItem>(
|
||||
const [value, setValue] = useState<SorterItem>(
|
||||
defaultState != undefined ? defaultState : items[0],
|
||||
);
|
||||
const [active, setActive] = React.useState<boolean>(false);
|
||||
const [active, setActive] = useState<boolean>(false);
|
||||
const [activate, setActivate] = useState<Boolean>(false);
|
||||
|
||||
React.useEffect(() => onChange(value.value), [value]);
|
||||
useEffect(() => onChange(value.value), [value]);
|
||||
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useClickOutside(ref, () => {
|
||||
setActive(false);
|
||||
@@ -46,7 +47,8 @@ export const Sorter: React.FC<SorterProps> = ({
|
||||
'grid items-center h-[40px] rounded-full bg-liquid-lighter grid-cols-[40px]',
|
||||
'text-[18px] font-bold cursor-pointer select-none',
|
||||
'overflow-hidden',
|
||||
active && ' grid-cols-[1fr] border-liquid-brightmain border-[1px] border-solid',
|
||||
(active || activate) &&
|
||||
' grid-cols-[1fr] border-liquid-brightmain border-[1px] border-solid',
|
||||
)}
|
||||
onClick={() => {
|
||||
setActive(!active);
|
||||
@@ -63,18 +65,17 @@ export const Sorter: React.FC<SorterProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[24px] w-[24px]"></div>
|
||||
<img
|
||||
src={Sort}
|
||||
src={iconSort}
|
||||
className={cn(
|
||||
' absolute right-[16px] h-[24px] w-[24px] top-[8px] left-[8px] rotate-0 transition-all duration-300 pointer-events-none',
|
||||
)}
|
||||
/>
|
||||
<img
|
||||
src={SortActive}
|
||||
src={iconSortActive}
|
||||
className={cn(
|
||||
' absolute right-[16px] h-[24px] w-[24px] top-[8px] left-[8px] rotate-0 transition-all duration-300 pointer-events-none opacity-0',
|
||||
active && ' opacity-100',
|
||||
(active || activate) && ' opacity-100',
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -107,6 +108,7 @@ export const Sorter: React.FC<SorterProps> = ({
|
||||
onClick={() => {
|
||||
setValue(v);
|
||||
setActive(false);
|
||||
setActivate(true);
|
||||
}}
|
||||
>
|
||||
{v.text}
|
||||
|
||||
59
src/components/input/SearchInput.tsx
Normal file
59
src/components/input/SearchInput.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { cn } from '../../lib/cn';
|
||||
import { iconSearch } from '../../assets/icons/filters';
|
||||
|
||||
interface searchInputProps {
|
||||
name?: string;
|
||||
error?: string;
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
onChange: (state: string) => void;
|
||||
defaultState?: string;
|
||||
autocomplete?: string;
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export const SearchInput: React.FC<searchInputProps> = ({
|
||||
placeholder = '',
|
||||
className = '',
|
||||
onChange,
|
||||
defaultState = '',
|
||||
name = '',
|
||||
autocomplete = '',
|
||||
onKeyDown,
|
||||
}) => {
|
||||
const [value, setValue] = React.useState<string>(defaultState);
|
||||
|
||||
React.useEffect(() => onChange(value), [value]);
|
||||
React.useEffect(() => setValue(defaultState), [defaultState]);
|
||||
|
||||
return (
|
||||
<label
|
||||
className={cn(
|
||||
'relative bg-liquid-lighter w-[200px] h-[40px] rounded-full px-[16px] pl-[50px] py-[8px] cursor-text',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<input
|
||||
className={cn(
|
||||
'placeholder:text-liquid-light w-full bg-transparent outline-none text-liquid-white',
|
||||
)}
|
||||
value={value}
|
||||
name={name}
|
||||
autoComplete={autocomplete}
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value);
|
||||
}}
|
||||
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (onKeyDown) onKeyDown(e);
|
||||
}}
|
||||
/>
|
||||
<img src={iconSearch} className=" absolute top-[8px] left-[16px]" />
|
||||
</label>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user