From 87766b0f4335c2a3519d8c0714bed14d26c9ecc2 Mon Sep 17 00:00:00 2001 From: Whitewater Date: Fri, 16 Aug 2024 17:54:09 +0800 Subject: [PATCH 1/2] feat: add multiple select Add `MultipleSelect` component to `ThemeSpec` and split `Select` into `SingleSelectView` and `MultiSelectView` components. * **packages/filter/src/views/components.tsx** - Export `SingleSelectView` and `MultiSelectView` components. - Remove `SelectView` component. - Ensure correct types for `SingleSelectView` and `MultiSelectView`. * **packages/filter/src/theme/preset.ts** - Use `SingleSelectView` for `Select` component in `ThemeSpec`. - Use `MultiSelectView` for `MultipleSelect` component in `ThemeSpec`. * **packages/filter/src/theme/types.ts** - Define `SingleSelectProps` and `MultiSelectProps` types. - Update `ThemeSpec` to include `MultipleSelect` component. * **packages/filter/src/views/data-input-views.tsx** - Use `MultipleSelectView` for `literal array` data input view. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/lawvs/fn-sphere?shareId=XXXX-XXXX-XXXX-XXXX). --- packages/filter/src/theme/preset.ts | 10 ++- packages/filter/src/theme/types.ts | 9 ++- packages/filter/src/views/components.tsx | 69 ++++++++----------- .../filter/src/views/data-input-views.tsx | 5 +- 4 files changed, 46 insertions(+), 47 deletions(-) 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) => { From 38f41b463d6abaff95837d5204142aff97b1e3ad Mon Sep 17 00:00:00 2001 From: Whitewater Date: Fri, 16 Aug 2024 17:58:10 +0800 Subject: [PATCH 2/2] chore: add changeset --- .changeset/plenty-bottles-sell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/plenty-bottles-sell.md 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