Skip to content

React/input search #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions js/react/lib/components/input-search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./input-search.component";
97 changes: 97 additions & 0 deletions js/react/lib/components/input-search/input-search.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useBoolean } from "@/hooks/use-boolean";
import { Filter } from "@/icons/filter";
import { Search } from "@/icons/search";
import { cn } from "@/utils/tw-merge";
import { Fragment } from "react/jsx-runtime";
import { Tag } from "../tag";
import { InputHTMLAttributes } from "react";

type Option = { label: string; value: string };
type InputSearchProps = {
filters?: Array<Option>;
activeFilters?: Array<Option>;
onChangeFilter?: (option: Array<Option>) => void;
} & InputHTMLAttributes<HTMLInputElement>;

export const InputSearch = ({
className,
filters,
activeFilters = [],
onChangeFilter,
...props
}: InputSearchProps) => {
const filterModal = useBoolean();

const hasFilter = filters?.length;

const handleSelectFilter = (filter: Option, isSelected: boolean) => {
if (!isSelected) return onChangeFilter?.([...activeFilters, filter]);
onChangeFilter?.(
activeFilters.filter(({ value }) => value !== filter.value)
);
};

const handleCloseOnClickInput = () => hasFilter && filterModal.setFalse();

return (
<div className={cn(["rustlanges-input-search-container", className])}>
<label
className={cn([
"rustlanges-input-search",
hasFilter && "rustlanges-input-search--filter",
])}
>
<Search className="" width={24} height={24} />
<input
type="text"
placeholder="Buscar"
onClick={handleCloseOnClickInput}
className="text-caption"
{...props}
/>
</label>
<div className="rustlanges-input-search__filter">
{hasFilter ? (
<Fragment>
<button onClick={filterModal.toggle} tabIndex={0}>
<Filter width={24} height={24} />
</button>
</Fragment>
) : null}
<div
className={cn([
"rustlanges-input-search-backdrop__content",
filterModal.value
? "rustlanges-input-search-backdrop__content--open"
: "rustlanges-input-search-backdrop__content--closed",
])}
>
{filterModal.value && (
<Fragment>
<ul className="rustlanges-input-search-backdrop__list">
{filters?.map(filter => {
const isSelected = activeFilters?.some(
({ value }) => value === filter.value
);
return (
<li
key={filter.value}
onClick={() => handleSelectFilter(filter, isSelected)}
>
<Tag
as="button"
tabIndex={0}
selected={isSelected}
label={filter.label}
/>
</li>
);
})}
</ul>
</Fragment>
)}
</div>
</div>
</div>
);
};
29 changes: 29 additions & 0 deletions js/react/lib/hooks/use-boolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useCallback, useState } from "react";

import type { Dispatch, SetStateAction } from "react";

type UseBooleanReturn = {
value: boolean;
setValue: Dispatch<SetStateAction<boolean>>;
setTrue: () => void;
setFalse: () => void;
toggle: () => void;
};

export function useBoolean(defaultValue = false): UseBooleanReturn {
const [value, setValue] = useState(defaultValue);

const setTrue = useCallback(() => {
setValue(true);
}, []);

const setFalse = useCallback(() => {
setValue(false);
}, []);

const toggle = useCallback(() => {
setValue(x => !x);
}, []);

return { value, setValue, setTrue, setFalse, toggle };
}
16 changes: 16 additions & 0 deletions js/react/lib/icons/filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SVGProps } from "react";
export const Filter = (props: SVGProps<SVGSVGElement>) => (
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
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"
fill="currentColor"
/>
</svg>
);
18 changes: 18 additions & 0 deletions js/react/lib/icons/search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SVGProps } from "react";
export const Search = (props: SVGProps<SVGSVGElement>) => (
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
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"
fill="currentColor"
/>
</svg>
);
1 change: 1 addition & 0 deletions js/react/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export * from "./components/badge";
export * from "./components/dropdown";
export * from "./components/calendar";
export * from "./components/dropdown-tree";
export * from "./components/input-search";
export * from "./icons";
32 changes: 32 additions & 0 deletions js/react/showcase/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
Calendar,
CalendarRangeDate,
DropdownTree,
InputSearch,
} from "@rustlanges/react";
import { ShowComponent } from "./ShowComponent";
import { Fragment, useState } from "react";
Expand Down Expand Up @@ -124,10 +125,30 @@ const tree = {
],
};

const filters = [
{ label: "Reciente", value: "reciente" },
{ label: "ES", value: "es" },
{ label: "EN", value: "en" },
{ label: "Libros", value: "libros" },
{ label: "Guías", value: "guias" },
{ label: "Frameworks", value: "frameworks" },
{ label: "Librerías", value: "librerias" },
{ label: "Multinivel", value: "multinivel" },
{ label: "Principiante", value: "principiante" },
{ label: "Intermedio", value: "intermedio" },
{ label: "Avanzado", value: "avanzado" },
];

export function App() {
const [single, setSingle] = useState<Date | null>(new Date());
const [multiple, setMultiple] = useState<Record<string, Date> | null>(null);
const [range, setRange] = useState<CalendarRangeDate | null>(null);
const [activeFilters, setActiveFilters] = useState<
Array<{
label: string;
value: string;
}>
>([]);

return (
<div className="mx-auto mt-10 max-w-[1024px] px-5">
Expand Down Expand Up @@ -480,6 +501,17 @@ export function App() {
</div>
</div>
</ShowComponent>
<ShowComponent title="Input Search">
<div className="flex min-h-60 w-full flex-wrap justify-evenly gap-40 p-5">
<InputSearch
activeFilters={activeFilters}
onChangeFilter={setActiveFilters}
filters={filters}
className="max-w-80"
/>
<InputSearch className="max-w-80" />
</div>
</ShowComponent>
</div>
);
}
4 changes: 3 additions & 1 deletion js/react/showcase/ShowComponent/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export function ShowComponentContainer(
</span>
</summary>
<div
className={"flex flex-col gap-5 sm:flex-row " + props.contentClassName}
className={
"flex w-full flex-col gap-5 sm:flex-row" + props.contentClassName
}
>
{props.children}
</div>
Expand Down
2 changes: 1 addition & 1 deletion js/react/showcase/ShowComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function ShowComponentInner(
const [childrenProps, inputs] = processProps(normalizedProps);

return (
<ShowComponentContainer title={props.title} className="bg-gray-50">
<ShowComponentContainer title={props.title} className="w-full bg-gray-50">
{!!Object.keys(inputs).length && (
<div className="border-r-1 flex w-full max-w-xs flex-col gap-2 border-r-gray-300 pr-2 pt-2">
{inputs}
Expand Down
1 change: 1 addition & 0 deletions styles/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
@import "./components/dropdown-tree-topic.css";
@import "./components/dropdown-tree-subtopic.css";
@import "./components/dropdown-tree-end.css";
@import "./components/input-search.css";
62 changes: 62 additions & 0 deletions styles/components/input-search.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@layer components {


Copy link
Member

Choose a reason for hiding this comment

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

format please 👍

.rustlanges-input-search-container {
@apply relative flex h-fit w-full;

svg {
@apply text-gray-600 dark:text-gray-400;
}
}

.rustlanges-input-search {
@apply shadow-rb-black flex h-11 w-full items-center gap-2.5 rounded-xl border border-black pl-2.5 transition;
@apply bg-light dark:bg-neutral-950;

@variant has-focus {
@apply border-primary-500 shadow-rb-primary-500;
}
& > input[type="text"] {
@apply z-50 h-full flex-1 appearance-none pr-2.5 outline-none transition-all;
@apply text-dark placeholder:text-neutral-200;
@variant dark {
@apply text-light placeholder:text-neutral-400;
}
}
}
.rustlanges-input-search--filter {
@apply rounded-r-none;
}

.rustlanges-input-search__filter {
& > button {
@apply shadow-rb-black grid size-11 cursor-pointer place-items-center items-center rounded-r-xl border border-black transition;
@apply bg-light dark:bg-neutral-950;
}
}

.rustlanges-input-search-button {
@apply grid size-11 cursor-pointer place-content-center;
}
.rustlanges-input-search-backdrop__content {
@apply absolute left-0 top-full z-[99] mt-2 w-full transition duration-200;
}

.rustlanges-input-search-backdrop__content--open {
@apply visible opacity-100;
}

.rustlanges-input-search-backdrop__content--closed {
@apply invisible opacity-0;
}

.rustlanges-input-search-backdrop__list {
@apply shadow-rb-black flex flex-wrap gap-2 p-4 transition;
@apply rounded-sm border border-black;
@apply bg-light dark:bg-neutral-950;

& > li {
@apply cursor-pointer;
}
}
}
1 change: 0 additions & 1 deletion styles/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@

/* Fonts */
--text-xxs: 10px;
--text-caption: 12px;
--text-caption--font-weight: 400;
--text-caption--letter-spacing: 0em;
--text-caption--line-height: 16px;
Expand Down