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 { 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 { BsTrash } from "react-icons/bs";
|
||||||
|
|
||||||
import { type LogFilter, comparators } from "~/state/logFiltersSlice";
|
import { type LogFilter } from "~/state/logFiltersSlice";
|
||||||
import { useAppStore } from "~/state/store";
|
import { useAppStore } from "~/state/store";
|
||||||
import { useFilterableFields } from "~/utils/hooks";
|
|
||||||
import { debounce } from "lodash-es";
|
import { debounce } from "lodash-es";
|
||||||
|
import SelectFieldDropdown from "./SelectFieldDropdown";
|
||||||
|
import SelectComparatorDropdown from "./SelectComparatorDropdown";
|
||||||
|
|
||||||
const LogFilter = ({ filter, index }: { filter: LogFilter; index: number }) => {
|
const LogFilter = ({ filter, index }: { filter: LogFilter; index: number }) => {
|
||||||
const filterableFields = useFilterableFields();
|
|
||||||
|
|
||||||
const updateFilter = useAppStore((s) => s.logFilters.updateFilter);
|
const updateFilter = useAppStore((s) => s.logFilters.updateFilter);
|
||||||
const deleteFilter = useAppStore((s) => s.logFilters.deleteFilter);
|
const deleteFilter = useAppStore((s) => s.logFilters.deleteFilter);
|
||||||
|
|
||||||
const { field, comparator, value } = filter;
|
|
||||||
|
|
||||||
const [editedValue, setEditedValue] = useState("");
|
const [editedValue, setEditedValue] = useState("");
|
||||||
|
|
||||||
const debouncedUpdateFilter = useCallback(
|
const debouncedUpdateFilter = useCallback(
|
||||||
@@ -31,31 +28,8 @@ const LogFilter = ({ filter, index }: { filter: LogFilter; index: number }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack>
|
<HStack>
|
||||||
<Select
|
<SelectFieldDropdown filter={filter} index={index} />
|
||||||
value={field}
|
<SelectComparatorDropdown filter={filter} index={index} />
|
||||||
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>
|
|
||||||
<Input
|
<Input
|
||||||
value={editedValue}
|
value={editedValue}
|
||||||
onChange={(e) => {
|
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