feat: add user groups (#376)
* feat: add user groups * wip: add unit tests * wip: add more tests and normalized name for creation and update * test: add unit tests for group router * fix: type issues, missing mysql schema, rename column creator_id to owner_id * fix: lint and format issues * fix: deepsource issues * fix: forgot to add log message * fix: build not working * chore: address pull request feedback * feat: add mysql migration and fix merge conflicts * fix: format issue and test issue
This commit is contained in:
59
packages/ui/src/components/search-input.tsx
Normal file
59
packages/ui/src/components/search-input.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
"use client";
|
||||
|
||||
import type { ChangeEvent } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { Loader, TextInput } from "@mantine/core";
|
||||
import { useDebouncedCallback } from "@mantine/hooks";
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
|
||||
interface SearchInputProps {
|
||||
defaultValue?: string;
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
export const SearchInput = ({
|
||||
placeholder,
|
||||
defaultValue,
|
||||
}: SearchInputProps) => {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const { replace } = useRouter();
|
||||
const pathName = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleSearchDebounced = useDebouncedCallback((value: string) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
params.set("search", value.toString());
|
||||
if (params.has("page")) params.set("page", "1"); // Reset page to 1
|
||||
replace(`${pathName}?${params.toString()}`);
|
||||
setLoading(false);
|
||||
}, 250);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setLoading(true);
|
||||
handleSearchDebounced(event.currentTarget.value);
|
||||
},
|
||||
[setLoading, handleSearchDebounced],
|
||||
);
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
leftSection={<LeftSection loading={loading} />}
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleSearch}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface LeftSectionProps {
|
||||
loading: boolean;
|
||||
}
|
||||
const LeftSection = ({ loading }: LeftSectionProps) => {
|
||||
if (loading) {
|
||||
return <Loader size="xs" />;
|
||||
}
|
||||
|
||||
return <IconSearch size={20} stroke={1.5} />;
|
||||
};
|
||||
Reference in New Issue
Block a user