Skip to content

Commit 361f64a

Browse files
authored
enhance MultiSelect component with maxCount, maxTagCount, and maxTagTextLength props (#1016)
1 parent 3da1d1e commit 361f64a

File tree

4 files changed

+143
-4
lines changed

4 files changed

+143
-4
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@ensembleui/react-kitchen-sink": patch
3+
"@ensembleui/react-runtime": patch
4+
---
5+
6+
enhance MultiSelect component with maxCount, maxTagCount, and maxTagTextLength props

apps/kitchen-sink/src/ensemble/screens/forms.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ View:
201201
placeholder: "Select From Groups"
202202
value: ["hbingley1@plala.or.jp", "val 2"]
203203
data: getData.body.users
204+
maxCount: 5
205+
maxTagCount: 3
206+
maxTagTextLength: 5
204207
labelKey: firstName
205208
valueKey: email
206209
items: ${ensemble.storage.get('dummyData')}

packages/runtime/src/widgets/Form/MultiSelect.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,15 @@ import {
1414
} from "@ensembleui/react-framework";
1515
import { PlusCircleOutlined } from "@ant-design/icons";
1616
import { Select as SelectComponent, Space, Form } from "antd";
17-
import { get, isArray, isEmpty, isEqual, isObject, isString } from "lodash-es";
17+
import {
18+
get,
19+
isArray,
20+
isEmpty,
21+
isEqual,
22+
isObject,
23+
isString,
24+
toNumber,
25+
} from "lodash-es";
1826
import { useDebounce } from "react-use";
1927
import { WidgetRegistry } from "../../registry";
2028
import { useEnsembleAction } from "../../runtime/hooks/useEnsembleAction";
@@ -59,6 +67,12 @@ export type MultiSelectProps = {
5967
} & EnsembleAction;
6068
hintStyle?: EnsembleWidgetStyles;
6169
allowCreateOptions?: boolean;
70+
/** The max number of items can be selected */
71+
maxCount: Expression<number>;
72+
/** Max tag count to show */
73+
maxTagCount: Expression<number | "responsive">;
74+
/** Max tag text length to show */
75+
maxTagTextLength: Expression<number>;
6276
} & EnsembleWidgetProps<MultiSelectStyles> &
6377
FormInputProps<object[] | string[]>;
6478

@@ -98,8 +112,8 @@ const MultiSelect: React.FC<MultiSelectProps> = (props) => {
98112
isObject(item)
99113
? {
100114
...(item as { [key: string]: unknown }),
101-
label: get(item, values?.labelKey || "label") as string,
102-
value: get(item, values?.valueKey || "value") as string,
115+
label: get(item, values.labelKey || "label") as string,
116+
value: get(item, values.valueKey || "value") as string,
103117
}
104118
: item,
105119
);
@@ -174,7 +188,7 @@ const MultiSelect: React.FC<MultiSelectProps> = (props) => {
174188
onSearchAction.callback({ search: searchValue });
175189
}
176190
},
177-
props?.onSearch?.debounceMs || 0,
191+
props.onSearch?.debounceMs || 0,
178192
[searchValue],
179193
);
180194

@@ -333,6 +347,15 @@ const MultiSelect: React.FC<MultiSelectProps> = (props) => {
333347
filterOption={props.onSearch ? false : handleFilterOption}
334348
id={values?.id}
335349
labelRender={labelRender}
350+
maxCount={values?.maxCount ? toNumber(values.maxCount) : undefined}
351+
maxTagCount={
352+
values?.maxTagCount as number | "responsive" | undefined
353+
}
354+
maxTagTextLength={
355+
values?.maxTagTextLength
356+
? toNumber(values.maxTagTextLength)
357+
: undefined
358+
}
336359
mode={values?.allowCreateOptions ? "tags" : "multiple"}
337360
notFoundContent="No Results"
338361
onChange={handleChange}

packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,5 +538,112 @@ describe("MultiSelect Widget", () => {
538538
expect(screen.queryByText("Option 44", { selector })).toBeVisible();
539539
});
540540
});
541+
542+
test("respects maxCount limit when selecting options", async () => {
543+
render(
544+
<Form
545+
children={[
546+
{
547+
name: "MultiSelect",
548+
properties: {
549+
id: "multiSelect",
550+
label: "Choose Option",
551+
maxCount: 2,
552+
data: [
553+
{ label: "Option 1", value: "option1" },
554+
{ label: "Option 2", value: "option2" },
555+
{ label: "Option 3", value: "option3" },
556+
{ label: "Option 4", value: "option4" },
557+
],
558+
},
559+
},
560+
...defaultFormButton,
561+
]}
562+
id="form"
563+
/>,
564+
{ wrapper: FormTestWrapper },
565+
);
566+
567+
const getValueButton = screen.getByText("Get Value");
568+
569+
userEvent.click(screen.getByRole("combobox"));
570+
userEvent.click(screen.getByTitle("Option 1"));
571+
userEvent.click(screen.getByTitle("Option 2"));
572+
573+
// should not be selectable
574+
userEvent.click(screen.getByTitle("Option 3"));
575+
576+
fireEvent.click(getValueButton);
577+
578+
await waitFor(() => {
579+
expect(logSpy).toHaveBeenCalledWith(
580+
expect.objectContaining({ multiSelect: ["option1", "option2"] }),
581+
);
582+
});
583+
});
584+
585+
test("displays correct number of tags based on maxTagCount", async () => {
586+
render(
587+
<Form
588+
children={[
589+
{
590+
name: "MultiSelect",
591+
properties: {
592+
id: "multiSelect",
593+
label: "Choose Option",
594+
maxTagCount: 1,
595+
data: [
596+
{ label: "Option 1", value: "option1" },
597+
{ label: "Option 2", value: "option2" },
598+
{ label: "Option 3", value: "option3" },
599+
{ label: "Option 4", value: "option4" },
600+
],
601+
value: `\${["option1", "option2", "option3", "option4"]}`,
602+
},
603+
},
604+
]}
605+
id="form"
606+
/>,
607+
{ wrapper: FormTestWrapper },
608+
);
609+
610+
await waitFor(() => {
611+
expect(screen.getByText("Option 1")).toBeInTheDocument();
612+
expect(screen.getByText("+ 3 ...")).toBeInTheDocument();
613+
614+
expect(screen.queryByText("Option 2")).toBeNull();
615+
expect(screen.queryByText("Option 3")).toBeNull();
616+
expect(screen.queryByText("Option 4")).toBeNull();
617+
});
618+
});
619+
620+
test("truncates tag text according to maxTagTextLength", async () => {
621+
render(
622+
<Form
623+
children={[
624+
{
625+
name: "MultiSelect",
626+
properties: {
627+
id: "multiSelect",
628+
label: "Choose Option",
629+
maxTagTextLength: 6,
630+
data: [
631+
{ label: "Very Long Option One", value: "option1" },
632+
{ label: "Another Long Option", value: "option2" },
633+
],
634+
value: `\${["option1", "option2"]}`,
635+
},
636+
},
637+
]}
638+
id="form"
639+
/>,
640+
{ wrapper: FormTestWrapper },
641+
);
642+
643+
await waitFor(() => {
644+
expect(screen.getByText("Very L...")).toBeInTheDocument();
645+
expect(screen.getByText("Anothe...")).toBeInTheDocument();
646+
});
647+
});
541648
});
542649
/* eslint-enable react/no-children-prop */

0 commit comments

Comments
 (0)