Add InputDropdown
This commit is contained in:
89
app/src/components/InputDropdown.tsx
Normal file
89
app/src/components/InputDropdown.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
Input,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
Icon,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
VStack,
|
||||
HStack,
|
||||
Button,
|
||||
Text,
|
||||
useDisclosure,
|
||||
type PopoverContentProps,
|
||||
type InputGroupProps,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import { FiChevronDown } from "react-icons/fi";
|
||||
import { BiCheck } from "react-icons/bi";
|
||||
|
||||
type InputDropdownProps<T> = {
|
||||
options: ReadonlyArray<T>;
|
||||
selectedOption: T;
|
||||
onSelect: (option: T) => void;
|
||||
inputGroupProps?: InputGroupProps;
|
||||
popoverContentProps?: PopoverContentProps;
|
||||
};
|
||||
|
||||
const InputDropdown = <T,>({
|
||||
options,
|
||||
selectedOption,
|
||||
onSelect,
|
||||
inputGroupProps,
|
||||
popoverContentProps,
|
||||
}: InputDropdownProps<T>) => {
|
||||
const popover = useDisclosure();
|
||||
|
||||
return (
|
||||
<Popover placement="bottom-start" {...popover}>
|
||||
<PopoverTrigger>
|
||||
<InputGroup cursor="pointer" w={360} {...inputGroupProps}>
|
||||
<Input
|
||||
value={selectedOption as string}
|
||||
cursor="pointer"
|
||||
borderWidth={popover.isOpen ? 2 : undefined}
|
||||
borderColor={popover.isOpen ? "blue.500" : undefined}
|
||||
_hover={popover.isOpen ? { borderColor: "blue.500" } : undefined}
|
||||
contentEditable={false}
|
||||
// disable focus
|
||||
onFocus={(e) => {
|
||||
e.target.blur();
|
||||
}}
|
||||
/>
|
||||
<InputRightElement>
|
||||
<Icon as={FiChevronDown} />
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent boxShadow="0 0 40px 4px rgba(0, 0, 0, 0.1);" w={224} {...popoverContentProps}>
|
||||
<VStack spacing={0}>
|
||||
{options?.map((option, index) => (
|
||||
<HStack
|
||||
key={index}
|
||||
as={Button}
|
||||
onClick={() => {
|
||||
onSelect(option);
|
||||
popover.onClose();
|
||||
}}
|
||||
w="full"
|
||||
variant="ghost"
|
||||
justifyContent="space-between"
|
||||
fontWeight="semibold"
|
||||
borderRadius={0}
|
||||
colorScheme="blue"
|
||||
color="black"
|
||||
fontSize="sm"
|
||||
borderBottomWidth={1}
|
||||
>
|
||||
<Text>{option as string}</Text>
|
||||
{option === selectedOption && <Icon as={BiCheck} color="blue.500" boxSize={5} />}
|
||||
</HStack>
|
||||
))}
|
||||
</VStack>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputDropdown;
|
||||
@@ -1,20 +1,17 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { HStack, IconButton, Input, Select } from "@chakra-ui/react";
|
||||
import { HStack, IconButton, Input } from "@chakra-ui/react";
|
||||
import { BsTrash } from "react-icons/bs";
|
||||
|
||||
import { type LogFilter, comparators } from "~/state/logFiltersSlice";
|
||||
import { type LogFilter } from "~/state/logFiltersSlice";
|
||||
import { useAppStore } from "~/state/store";
|
||||
import { useFilterableFields } from "~/utils/hooks";
|
||||
import { debounce } from "lodash-es";
|
||||
import SelectFieldDropdown from "./SelectFieldDropdown";
|
||||
import SelectComparatorDropdown from "./SelectComparatorDropdown";
|
||||
|
||||
const LogFilter = ({ filter, index }: { filter: LogFilter; index: number }) => {
|
||||
const filterableFields = useFilterableFields();
|
||||
|
||||
const updateFilter = useAppStore((s) => s.logFilters.updateFilter);
|
||||
const deleteFilter = useAppStore((s) => s.logFilters.deleteFilter);
|
||||
|
||||
const { field, comparator, value } = filter;
|
||||
|
||||
const [editedValue, setEditedValue] = useState("");
|
||||
|
||||
const debouncedUpdateFilter = useCallback(
|
||||
@@ -31,31 +28,8 @@ const LogFilter = ({ filter, index }: { filter: LogFilter; index: number }) => {
|
||||
|
||||
return (
|
||||
<HStack>
|
||||
<Select
|
||||
value={field}
|
||||
onChange={(e) => updateFilter(index, { ...filter, field: e.target.value })}
|
||||
>
|
||||
{filterableFields.data?.map((field) => (
|
||||
<option key={field} value={field}>
|
||||
{field}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
value={comparator}
|
||||
onChange={(e) =>
|
||||
updateFilter(index, {
|
||||
...filter,
|
||||
comparator: e.target.value as (typeof comparators)[number],
|
||||
})
|
||||
}
|
||||
>
|
||||
{comparators.map((comparator) => (
|
||||
<option key={comparator} value={comparator}>
|
||||
{comparator}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<SelectFieldDropdown filter={filter} index={index} />
|
||||
<SelectComparatorDropdown filter={filter} index={index} />
|
||||
<Input
|
||||
value={editedValue}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { comparators, type LogFilter } from "~/state/logFiltersSlice";
|
||||
import { useAppStore } from "~/state/store";
|
||||
import InputDropdown from "~/components/InputDropdown";
|
||||
|
||||
const SelectComparatorDropdown = ({ filter, index }: { filter: LogFilter; index: number }) => {
|
||||
const updateFilter = useAppStore((s) => s.logFilters.updateFilter);
|
||||
|
||||
const { comparator } = filter;
|
||||
|
||||
return (
|
||||
<InputDropdown
|
||||
options={comparators}
|
||||
selectedOption={comparator}
|
||||
onSelect={(option) => updateFilter(index, { ...filter, comparator: option })}
|
||||
inputGroupProps={{ w: 300 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectComparatorDropdown;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { type LogFilter } from "~/state/logFiltersSlice";
|
||||
import { useAppStore } from "~/state/store";
|
||||
import { useFilterableFields } from "~/utils/hooks";
|
||||
import InputDropdown from "~/components/InputDropdown";
|
||||
|
||||
const SelectFieldDropdown = ({ filter, index }: { filter: LogFilter; index: number }) => {
|
||||
const filterableFields = useFilterableFields().data;
|
||||
|
||||
const updateFilter = useAppStore((s) => s.logFilters.updateFilter);
|
||||
|
||||
const { field } = filter;
|
||||
|
||||
return (
|
||||
<InputDropdown
|
||||
options={filterableFields || []}
|
||||
selectedOption={field}
|
||||
onSelect={(option) => updateFilter(index, { ...filter, field: option })}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectFieldDropdown;
|
||||
Reference in New Issue
Block a user