Skip to content

Commit 054e35f

Browse files
authored
feat(react): add InputSearch component (#50)
1 parent edc31e7 commit 054e35f

File tree

12 files changed

+261
-3
lines changed

12 files changed

+261
-3
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./input-search.component";
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { useBoolean } from "@/hooks/use-boolean";
2+
import { Filter } from "@/icons/filter";
3+
import { Search } from "@/icons/search";
4+
import { cn } from "@/utils/tw-merge";
5+
import { Fragment } from "react/jsx-runtime";
6+
import { Tag } from "../tag";
7+
import { InputHTMLAttributes } from "react";
8+
9+
type Option = { label: string; value: string };
10+
type InputSearchProps = {
11+
filters?: Array<Option>;
12+
activeFilters?: Array<Option>;
13+
onChangeFilter?: (option: Array<Option>) => void;
14+
} & InputHTMLAttributes<HTMLInputElement>;
15+
16+
export const InputSearch = ({
17+
className,
18+
filters,
19+
activeFilters = [],
20+
onChangeFilter,
21+
...props
22+
}: InputSearchProps) => {
23+
const filterModal = useBoolean();
24+
25+
const hasFilter = filters?.length;
26+
27+
const handleSelectFilter = (filter: Option, isSelected: boolean) => {
28+
if (!isSelected) return onChangeFilter?.([...activeFilters, filter]);
29+
onChangeFilter?.(
30+
activeFilters.filter(({ value }) => value !== filter.value)
31+
);
32+
};
33+
34+
const handleCloseOnClickInput = () => hasFilter && filterModal.setFalse();
35+
36+
return (
37+
<div className={cn(["rustlanges-input-search-container", className])}>
38+
<label
39+
className={cn([
40+
"rustlanges-input-search",
41+
hasFilter && "rustlanges-input-search--filter",
42+
])}
43+
>
44+
<Search className="" width={24} height={24} />
45+
<input
46+
type="text"
47+
placeholder="Buscar"
48+
onClick={handleCloseOnClickInput}
49+
className="text-caption"
50+
{...props}
51+
/>
52+
</label>
53+
<div className="rustlanges-input-search__filter">
54+
{hasFilter ? (
55+
<Fragment>
56+
<button onClick={filterModal.toggle} tabIndex={0}>
57+
<Filter width={24} height={24} />
58+
</button>
59+
</Fragment>
60+
) : null}
61+
<div
62+
className={cn([
63+
"rustlanges-input-search-backdrop__content",
64+
filterModal.value
65+
? "rustlanges-input-search-backdrop__content--open"
66+
: "rustlanges-input-search-backdrop__content--closed",
67+
])}
68+
>
69+
{filterModal.value && (
70+
<Fragment>
71+
<ul className="rustlanges-input-search-backdrop__list">
72+
{filters?.map(filter => {
73+
const isSelected = activeFilters?.some(
74+
({ value }) => value === filter.value
75+
);
76+
return (
77+
<li
78+
key={filter.value}
79+
onClick={() => handleSelectFilter(filter, isSelected)}
80+
>
81+
<Tag
82+
as="button"
83+
tabIndex={0}
84+
selected={isSelected}
85+
label={filter.label}
86+
/>
87+
</li>
88+
);
89+
})}
90+
</ul>
91+
</Fragment>
92+
)}
93+
</div>
94+
</div>
95+
</div>
96+
);
97+
};

js/react/lib/hooks/use-boolean.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { useCallback, useState } from "react";
2+
3+
import type { Dispatch, SetStateAction } from "react";
4+
5+
type UseBooleanReturn = {
6+
value: boolean;
7+
setValue: Dispatch<SetStateAction<boolean>>;
8+
setTrue: () => void;
9+
setFalse: () => void;
10+
toggle: () => void;
11+
};
12+
13+
export function useBoolean(defaultValue = false): UseBooleanReturn {
14+
const [value, setValue] = useState(defaultValue);
15+
16+
const setTrue = useCallback(() => {
17+
setValue(true);
18+
}, []);
19+
20+
const setFalse = useCallback(() => {
21+
setValue(false);
22+
}, []);
23+
24+
const toggle = useCallback(() => {
25+
setValue(x => !x);
26+
}, []);
27+
28+
return { value, setValue, setTrue, setFalse, toggle };
29+
}

js/react/lib/icons/filter.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { SVGProps } from "react";
2+
export const Filter = (props: SVGProps<SVGSVGElement>) => (
3+
<svg
4+
width="1em"
5+
height="1em"
6+
viewBox="0 0 24 24"
7+
fill="none"
8+
xmlns="http://www.w3.org/2000/svg"
9+
{...props}
10+
>
11+
<path
12+
d="M9 5.00001C8.73478 5.00001 8.48043 5.10537 8.29289 5.2929C8.10536 5.48044 8 5.73479 8 6.00001C8 6.26523 8.10536 6.51958 8.29289 6.70712C8.48043 6.89465 8.73478 7.00001 9 7.00001C9.26522 7.00001 9.51957 6.89465 9.70711 6.70712C9.89464 6.51958 10 6.26523 10 6.00001C10 5.73479 9.89464 5.48044 9.70711 5.2929C9.51957 5.10537 9.26522 5.00001 9 5.00001ZM6.17 5.00001C6.3766 4.41448 6.75974 3.90744 7.2666 3.5488C7.77346 3.19015 8.37909 2.99756 9 2.99756C9.62091 2.99756 10.2265 3.19015 10.7334 3.5488C11.2403 3.90744 11.6234 4.41448 11.83 5.00001H19C19.2652 5.00001 19.5196 5.10537 19.7071 5.2929C19.8946 5.48044 20 5.73479 20 6.00001C20 6.26523 19.8946 6.51958 19.7071 6.70712C19.5196 6.89465 19.2652 7.00001 19 7.00001H11.83C11.6234 7.58554 11.2403 8.09258 10.7334 8.45122C10.2265 8.80986 9.62091 9.00246 9 9.00246C8.37909 9.00246 7.77346 8.80986 7.2666 8.45122C6.75974 8.09258 6.3766 7.58554 6.17 7.00001H5C4.73478 7.00001 4.48043 6.89465 4.29289 6.70712C4.10536 6.51958 4 6.26523 4 6.00001C4 5.73479 4.10536 5.48044 4.29289 5.2929C4.48043 5.10537 4.73478 5.00001 5 5.00001H6.17ZM15 11C14.7348 11 14.4804 11.1054 14.2929 11.2929C14.1054 11.4804 14 11.7348 14 12C14 12.2652 14.1054 12.5196 14.2929 12.7071C14.4804 12.8947 14.7348 13 15 13C15.2652 13 15.5196 12.8947 15.7071 12.7071C15.8946 12.5196 16 12.2652 16 12C16 11.7348 15.8946 11.4804 15.7071 11.2929C15.5196 11.1054 15.2652 11 15 11ZM12.17 11C12.3766 10.4145 12.7597 9.90744 13.2666 9.5488C13.7735 9.19015 14.3791 8.99756 15 8.99756C15.6209 8.99756 16.2265 9.19015 16.7334 9.5488C17.2403 9.90744 17.6234 10.4145 17.83 11H19C19.2652 11 19.5196 11.1054 19.7071 11.2929C19.8946 11.4804 20 11.7348 20 12C20 12.2652 19.8946 12.5196 19.7071 12.7071C19.5196 12.8947 19.2652 13 19 13H17.83C17.6234 13.5855 17.2403 14.0926 16.7334 14.4512C16.2265 14.8099 15.6209 15.0025 15 15.0025C14.3791 15.0025 13.7735 14.8099 13.2666 14.4512C12.7597 14.0926 12.3766 13.5855 12.17 13H5C4.73478 13 4.48043 12.8947 4.29289 12.7071C4.10536 12.5196 4 12.2652 4 12C4 11.7348 4.10536 11.4804 4.29289 11.2929C4.48043 11.1054 4.73478 11 5 11H12.17ZM9 17C8.73478 17 8.48043 17.1054 8.29289 17.2929C8.10536 17.4804 8 17.7348 8 18C8 18.2652 8.10536 18.5196 8.29289 18.7071C8.48043 18.8947 8.73478 19 9 19C9.26522 19 9.51957 18.8947 9.70711 18.7071C9.89464 18.5196 10 18.2652 10 18C10 17.7348 9.89464 17.4804 9.70711 17.2929C9.51957 17.1054 9.26522 17 9 17ZM6.17 17C6.3766 16.4145 6.75974 15.9074 7.2666 15.5488C7.77346 15.1902 8.37909 14.9976 9 14.9976C9.62091 14.9976 10.2265 15.1902 10.7334 15.5488C11.2403 15.9074 11.6234 16.4145 11.83 17H19C19.2652 17 19.5196 17.1054 19.7071 17.2929C19.8946 17.4804 20 17.7348 20 18C20 18.2652 19.8946 18.5196 19.7071 18.7071C19.5196 18.8947 19.2652 19 19 19H11.83C11.6234 19.5855 11.2403 20.0926 10.7334 20.4512C10.2265 20.8099 9.62091 21.0025 9 21.0025C8.37909 21.0025 7.77346 20.8099 7.2666 20.4512C6.75974 20.0926 6.3766 19.5855 6.17 19H5C4.73478 19 4.48043 18.8947 4.29289 18.7071C4.10536 18.5196 4 18.2652 4 18C4 17.7348 4.10536 17.4804 4.29289 17.2929C4.48043 17.1054 4.73478 17 5 17H6.17Z"
13+
fill="currentColor"
14+
/>
15+
</svg>
16+
);

js/react/lib/icons/search.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { SVGProps } from "react";
2+
export const Search = (props: SVGProps<SVGSVGElement>) => (
3+
<svg
4+
width="1em"
5+
height="1em"
6+
viewBox="0 0 24 24"
7+
fill="none"
8+
xmlns="http://www.w3.org/2000/svg"
9+
{...props}
10+
>
11+
<path
12+
fillRule="evenodd"
13+
clipRule="evenodd"
14+
d="M11 2C9.56238 2.00016 8.14571 2.3447 6.86859 3.00479C5.59146 3.66489 4.49105 4.62132 3.65947 5.79402C2.82788 6.96672 2.28933 8.32158 2.08889 9.74516C1.88844 11.1687 2.03194 12.6196 2.50738 13.9764C2.98281 15.3331 3.77634 16.5562 4.82154 17.5433C5.86673 18.5304 7.13318 19.2527 8.51487 19.6498C9.89656 20.0469 11.3533 20.1073 12.7631 19.8258C14.1729 19.5443 15.4947 18.9292 16.618 18.032L20.293 21.707C20.4816 21.8892 20.7342 21.99 20.9964 21.9877C21.2586 21.9854 21.5094 21.8802 21.6948 21.6948C21.8802 21.5094 21.9854 21.2586 21.9877 20.9964C21.99 20.7342 21.8892 20.4816 21.707 20.293L18.032 16.618C19.09 15.2939 19.7526 13.6979 19.9435 12.0138C20.1344 10.3297 19.8459 8.62586 19.1112 7.0985C18.3764 5.57113 17.2253 4.28228 15.7904 3.38029C14.3554 2.47831 12.6949 1.99985 11 2ZM5 11C5 10.2121 5.1552 9.43185 5.45673 8.7039C5.75825 7.97595 6.20021 7.31451 6.75736 6.75736C7.31451 6.20021 7.97595 5.75825 8.7039 5.45672C9.43186 5.15519 10.2121 5 11 5C11.7879 5 12.5682 5.15519 13.2961 5.45672C14.0241 5.75825 14.6855 6.20021 15.2426 6.75736C15.7998 7.31451 16.2418 7.97595 16.5433 8.7039C16.8448 9.43185 17 10.2121 17 11C17 12.5913 16.3679 14.1174 15.2426 15.2426C14.1174 16.3679 12.5913 17 11 17C9.4087 17 7.88258 16.3679 6.75736 15.2426C5.63214 14.1174 5 12.5913 5 11Z"
15+
fill="currentColor"
16+
/>
17+
</svg>
18+
);

js/react/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export * from "./components/badge";
1212
export * from "./components/dropdown";
1313
export * from "./components/calendar";
1414
export * from "./components/dropdown-tree";
15+
export * from "./components/input-search";
1516
export * from "./icons";

js/react/showcase/App.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
Calendar,
1616
CalendarRangeDate,
1717
DropdownTree,
18+
InputSearch,
1819
} from "@rustlanges/react";
1920
import { ShowComponent } from "./ShowComponent";
2021
import { Fragment, useState } from "react";
@@ -124,10 +125,30 @@ const tree = {
124125
],
125126
};
126127

128+
const filters = [
129+
{ label: "Reciente", value: "reciente" },
130+
{ label: "ES", value: "es" },
131+
{ label: "EN", value: "en" },
132+
{ label: "Libros", value: "libros" },
133+
{ label: "Guías", value: "guias" },
134+
{ label: "Frameworks", value: "frameworks" },
135+
{ label: "Librerías", value: "librerias" },
136+
{ label: "Multinivel", value: "multinivel" },
137+
{ label: "Principiante", value: "principiante" },
138+
{ label: "Intermedio", value: "intermedio" },
139+
{ label: "Avanzado", value: "avanzado" },
140+
];
141+
127142
export function App() {
128143
const [single, setSingle] = useState<Date | null>(new Date());
129144
const [multiple, setMultiple] = useState<Record<string, Date> | null>(null);
130145
const [range, setRange] = useState<CalendarRangeDate | null>(null);
146+
const [activeFilters, setActiveFilters] = useState<
147+
Array<{
148+
label: string;
149+
value: string;
150+
}>
151+
>([]);
131152

132153
return (
133154
<div className="mx-auto mt-10 max-w-[1024px] px-5">
@@ -480,6 +501,17 @@ export function App() {
480501
</div>
481502
</div>
482503
</ShowComponent>
504+
<ShowComponent title="Input Search">
505+
<div className="flex min-h-60 w-full flex-wrap justify-evenly gap-40 p-5">
506+
<InputSearch
507+
activeFilters={activeFilters}
508+
onChangeFilter={setActiveFilters}
509+
filters={filters}
510+
className="max-w-80"
511+
/>
512+
<InputSearch className="max-w-80" />
513+
</div>
514+
</ShowComponent>
483515
</div>
484516
);
485517
}

js/react/showcase/ShowComponent/Container.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ export function ShowComponentContainer(
2323
</span>
2424
</summary>
2525
<div
26-
className={"flex flex-col gap-5 sm:flex-row " + props.contentClassName}
26+
className={
27+
"flex w-full flex-col gap-5 sm:flex-row" + props.contentClassName
28+
}
2729
>
2830
{props.children}
2931
</div>

js/react/showcase/ShowComponent/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ function ShowComponentInner(
101101
const [childrenProps, inputs] = processProps(normalizedProps);
102102

103103
return (
104-
<ShowComponentContainer title={props.title} className="bg-gray-50">
104+
<ShowComponentContainer title={props.title} className="w-full bg-gray-50">
105105
{!!Object.keys(inputs).length && (
106106
<div className="border-r-1 flex w-full max-w-xs flex-col gap-2 border-r-gray-300 pr-2 pt-2">
107107
{inputs}

styles/components.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
@import "./components/dropdown-tree-topic.css";
1515
@import "./components/dropdown-tree-subtopic.css";
1616
@import "./components/dropdown-tree-end.css";
17+
@import "./components/input-search.css";

0 commit comments

Comments
 (0)