diff --git a/.changeset/plenty-bottles-sell.md b/.changeset/plenty-bottles-sell.md new file mode 100644 index 0000000..bf6befb --- /dev/null +++ b/.changeset/plenty-bottles-sell.md @@ -0,0 +1,5 @@ +--- +"@fn-sphere/filter": patch +--- + +Add multiple select diff --git a/packages/filter/src/theme/preset.ts b/packages/filter/src/theme/preset.ts index e0b9e6a..dc18124 100644 --- a/packages/filter/src/theme/preset.ts +++ b/packages/filter/src/theme/preset.ts @@ -1,4 +1,9 @@ -import { ButtonView, InputView, SelectView } from "../views/components.js"; +import { + ButtonView, + InputView, + SingleSelectView, + MultiSelectView, +} from "../views/components.js"; import { presetDataInputSpecs } from "../views/data-input-views.js"; import { FieldSelect } from "../views/field-select.js"; import { FilterDataInput } from "../views/filter-data-input.js"; @@ -16,7 +21,8 @@ export const presetTheme: ThemeSpec = { components: { Button: ButtonView, Input: InputView, - Select: SelectView, + Select: SingleSelectView, + MultipleSelect: MultiSelectView, }, templates: { SingleFilter: SingleFilterView, diff --git a/packages/filter/src/theme/types.ts b/packages/filter/src/theme/types.ts index 1f45938..d725106 100644 --- a/packages/filter/src/theme/types.ts +++ b/packages/filter/src/theme/types.ts @@ -8,7 +8,10 @@ import type { RefAttributes, } from "react"; import type { z } from "zod"; -import type { SelectProps } from "../views/components.js"; +import type { + SingleSelectProps, + MultiSelectProps, +} from "../views/components.js"; import type { FieldSelectProps } from "../views/field-select.js"; import type { DataInputProps } from "../views/filter-data-input.js"; import type { FilterGroupContainerProps } from "../views/filter-group-container.js"; @@ -63,10 +66,10 @@ export type ThemeSpec = { >; // Select: ComponentType & RefAttributes>; Select: ( - props: SelectProps & { ref?: Ref }, + props: SingleSelectProps & { ref?: Ref }, ) => ReactNode; MultipleSelect: ( - props: SelectProps & { ref?: Ref }, + props: MultiSelectProps & { ref?: Ref }, ) => ReactNode; }; templates: { diff --git a/packages/filter/src/views/components.tsx b/packages/filter/src/views/components.tsx index bb27123..0552f93 100644 --- a/packages/filter/src/views/components.tsx +++ b/packages/filter/src/views/components.tsx @@ -33,54 +33,46 @@ export const InputView = forwardRef< return ; }); -export type SelectProps = SingleSelectProps | MultiSelectProps; - -type SingleSelectProps = Omit< +export type SingleSelectProps = Omit< SelectHTMLAttributes, - "value" | "onChange" | "children" + "value" | "onChange" | "children" | "multiple" > & { - multiple?: false | undefined; value?: T | undefined; options?: { value: T; label: string }[] | undefined; onChange?: (value: T) => void; }; -type MultiSelectProps = Omit< +export type MultiSelectProps = Omit< SelectHTMLAttributes, - "value" | "onChange" | "children" + "value" | "onChange" | "children" | "multiple" > & { - multiple: true; value?: T[] | undefined; options?: { value: T; label: string }[] | undefined; onChange?: (value: T[]) => void; }; -const MultiSelectView = forwardRef< +export const SingleSelectView = forwardRef< HTMLSelectElement, - MultiSelectProps ->(({ options = [], value = [], onChange, ...props }, ref) => { + SingleSelectProps +>(({ options = [], value, onChange, ...props }, ref) => { const SelectPrimitive = usePrimitives("select"); const OptionPrimitive = usePrimitives("option"); - const selectedIndices = value.map((val) => - String(options.findIndex((option) => option.value === val)), - ); + const selectedIdx = options.findIndex((option) => option.value === value); const handleChange = useCallback( (e: ChangeEvent) => { - const selectedOptions = Array.from( - e.target.selectedOptions, - (option) => options[Number(option.value)].value, - ); - onChange?.(selectedOptions); + const index = Number(e.target.value); + onChange?.(options[index].value); }, [options, onChange], ); return ( + {options.map(({ label }, index) => ( {label} @@ -88,30 +80,36 @@ const MultiSelectView = forwardRef< ))} ); -}); +}) as ( + p: SingleSelectProps & { ref?: Ref }, +) => ReactNode; -const SingleSelectView = forwardRef< +export const MultiSelectView = forwardRef< HTMLSelectElement, - SingleSelectProps ->(({ options = [], value, onChange, ...props }, ref) => { + MultiSelectProps +>(({ options = [], value = [], onChange, ...props }, ref) => { const SelectPrimitive = usePrimitives("select"); const OptionPrimitive = usePrimitives("option"); - const selectedIdx = options.findIndex((option) => option.value === value); + const selectedIndices = value.map((val) => + String(options.findIndex((option) => option.value === val)), + ); const handleChange = useCallback( (e: ChangeEvent) => { - const index = Number(e.target.value); - onChange?.(options[index].value); + const selectedOptions = Array.from( + e.target.selectedOptions, + (option) => options[Number(option.value)].value, + ); + onChange?.(selectedOptions); }, [options, onChange], ); return ( - {options.map(({ label }, index) => ( {label} @@ -119,13 +117,6 @@ const SingleSelectView = forwardRef< ))} ); -}); - -export const SelectView = forwardRef>( - (props, ref) => { - if (props.multiple) { - return ; - } - return ; - }, -) as (p: SelectProps & { ref?: Ref }) => ReactNode; +}) as ( + p: MultiSelectProps & { ref?: Ref }, +) => ReactNode; diff --git a/packages/filter/src/views/data-input-views.tsx b/packages/filter/src/views/data-input-views.tsx index 868505e..564b14a 100644 --- a/packages/filter/src/views/data-input-views.tsx +++ b/packages/filter/src/views/data-input-views.tsx @@ -146,7 +146,7 @@ export const presetDataInputSpecs: DataInputViewSpec[] = [ ); }, view: forwardRef(({ requiredDataSchema, rule, updateInput }) => { - const { Select: SelectView } = useView("components"); + const { MultipleSelect: MultipleSelectView } = useView("components"); const { getLocaleText } = useRootRule(); const arraySchema = requiredDataSchema[0] as z.ZodArray< z.ZodUnion<[z.ZodLiteral]> @@ -158,8 +158,7 @@ export const presetDataInputSpecs: DataInputViewSpec[] = [ })); const value = (rule.args?.[0] ?? []) as z.Primitive[]; return ( - - multiple + value={value} options={options} onChange={(newValue) => {