Skip to content

Commit 3822b2e

Browse files
committed
feat: add stories for MultiSelectCombobox
1 parent 6eb6987 commit 3822b2e

File tree

4 files changed

+114
-9
lines changed

4 files changed

+114
-9
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { expect, userEvent, waitFor, within } from "@storybook/test";
3+
import { MockOrganization, MockOrganization2 } from "testHelpers/entities";
4+
import { MultiSelectCombobox } from "./MultiSelectCombobox";
5+
6+
const organizations = [MockOrganization, MockOrganization2];
7+
8+
const meta: Meta<typeof MultiSelectCombobox> = {
9+
title: "components/MultiSelectCombobox",
10+
component: MultiSelectCombobox,
11+
args: {
12+
hidePlaceholderWhenSelected: true,
13+
placeholder: "Select organization",
14+
emptyIndicator: (
15+
<p className="text-center text-md text-content-primary">
16+
All organizations selected
17+
</p>
18+
),
19+
defaultOptions: organizations.map((org) => ({
20+
label: org.display_name,
21+
value: org.id,
22+
})),
23+
},
24+
};
25+
26+
export default meta;
27+
type Story = StoryObj<typeof MultiSelectCombobox>;
28+
29+
export const Default: Story = {};
30+
31+
export const OpenCombobox: Story = {
32+
play: async ({ canvasElement }) => {
33+
const canvas = within(canvasElement);
34+
await userEvent.click(canvas.getByPlaceholderText("Select organization"));
35+
36+
await waitFor(() =>
37+
expect(canvas.getByText("My Organization")).toBeInTheDocument(),
38+
);
39+
},
40+
};
41+
42+
export const SelectComboboxItem: Story = {
43+
play: async ({ canvasElement }) => {
44+
const canvas = within(canvasElement);
45+
await userEvent.click(canvas.getByPlaceholderText("Select organization"));
46+
await userEvent.click(
47+
canvas.getByRole("option", { name: "My Organization" }),
48+
);
49+
},
50+
};
51+
52+
export const SelectAllComboboxItems: Story = {
53+
play: async ({ canvasElement }) => {
54+
const canvas = within(canvasElement);
55+
await userEvent.click(canvas.getByPlaceholderText("Select organization"));
56+
await userEvent.click(
57+
canvas.getByRole("option", { name: "My Organization" }),
58+
);
59+
await userEvent.click(
60+
canvas.getByRole("option", { name: "My Organization 2" }),
61+
);
62+
63+
await waitFor(() =>
64+
expect(
65+
canvas.getByText("All organizations selected"),
66+
).toBeInTheDocument(),
67+
);
68+
},
69+
};
70+
71+
export const ClearFirstSelectedItem: Story = {
72+
play: async ({ canvasElement }) => {
73+
const canvas = within(canvasElement);
74+
await userEvent.click(canvas.getByPlaceholderText("Select organization"));
75+
await userEvent.click(
76+
canvas.getByRole("option", { name: "My Organization" }),
77+
);
78+
await userEvent.click(
79+
canvas.getByRole("option", { name: "My Organization 2" }),
80+
);
81+
await userEvent.click(canvas.getAllByTestId("clear-option-button")[0]);
82+
},
83+
};
84+
85+
export const ClearAllComboboxItems: Story = {
86+
play: async ({ canvasElement }) => {
87+
const canvas = within(canvasElement);
88+
await userEvent.click(canvas.getByPlaceholderText("Select organization"));
89+
await userEvent.click(
90+
canvas.getByRole("option", { name: "My Organization" }),
91+
);
92+
await userEvent.click(canvas.getByTestId("clear-all-button"));
93+
94+
await waitFor(() =>
95+
expect(
96+
canvas.getByPlaceholderText("Select organization"),
97+
).toBeInTheDocument(),
98+
);
99+
},
100+
};

site/src/components/ui/MultipleSelector.tsx renamed to site/src/components/MultiSelectCombobox/MultiSelectCombobox.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
CommandGroup,
1515
CommandItem,
1616
CommandList,
17-
} from "components/ui/Command";
17+
} from "components/Command/Command";
1818
import { cn } from "utils/cn";
1919

2020
export interface Option {
@@ -30,7 +30,7 @@ interface GroupOption {
3030
[key: string]: Option[];
3131
}
3232

33-
interface MultipleSelectorProps {
33+
interface MultiSelectComboboxProps {
3434
value?: Option[];
3535
defaultOptions?: Option[];
3636
/** manually controlled options */
@@ -87,7 +87,7 @@ interface MultipleSelectorProps {
8787
hideClearAllButton?: boolean;
8888
}
8989

90-
export interface MultipleSelectorRef {
90+
export interface MultiSelectComboboxRef {
9191
selectedValue: Option[];
9292
input: HTMLInputElement;
9393
focus: () => void;
@@ -157,9 +157,9 @@ const CommandEmpty = forwardRef<
157157
);
158158
});
159159

160-
export const MultipleSelector = React.forwardRef<
161-
MultipleSelectorRef,
162-
MultipleSelectorProps
160+
export const MultiSelectCombobox = React.forwardRef<
161+
MultiSelectComboboxRef,
162+
MultiSelectComboboxProps
163163
>(
164164
(
165165
{
@@ -186,7 +186,7 @@ export const MultipleSelector = React.forwardRef<
186186
commandProps,
187187
inputProps,
188188
hideClearAllButton = false,
189-
}: MultipleSelectorProps,
189+
}: MultiSelectComboboxProps,
190190
ref,
191191
) => {
192192
const inputRef = React.useRef<HTMLInputElement>(null);
@@ -478,6 +478,7 @@ export const MultipleSelector = React.forwardRef<
478478
{option.label}
479479
<button
480480
type="button"
481+
data-testid="clear-option-button"
481482
className={cn(
482483
`ml-1 pr-0 rounded-full bg-transparent border-none outline-none
483484
focus:ring-2 focus:ring-surface-invert-primary focus:ml-2.5 focus:pl-0 cursor-pointer`,
@@ -539,6 +540,7 @@ export const MultipleSelector = React.forwardRef<
539540
<div className="flex items-center justify-between">
540541
<button
541542
type="button"
543+
data-testid="clear-all-button"
542544
onClick={() => {
543545
setSelected(fixedOptions);
544546
onChange?.(fixedOptions);

site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ import { EmptyState } from "components/EmptyState/EmptyState";
1818
import { Input } from "components/Input/Input";
1919
import { Label } from "components/Label/Label";
2020
import { Loader } from "components/Loader/Loader";
21+
import {
22+
MultiSelectCombobox,
23+
type Option,
24+
} from "components/MultiSelectCombobox/MultiSelectCombobox";
2125
import { Switch } from "components/Switch/Switch";
2226
import {
2327
TableLoaderSkeleton,
2428
TableRowSkeleton,
2529
} from "components/TableLoader/TableLoader";
26-
import { MultipleSelector, type Option } from "components/ui/MultipleSelector";
2730
import { useFormik } from "formik";
2831
import { Plus, SquareArrowOutUpRight, Trash } from "lucide-react";
2932
import type React from "react";
@@ -163,7 +166,7 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
163166
<Label className="text-sm" htmlFor=":r1d:">
164167
Coder organization
165168
</Label>
166-
<MultipleSelector
169+
<MultiSelectCombobox
167170
className="min-w-60 max-w-3xl"
168171
value={coderOrgs}
169172
onChange={setCoderOrgs}

0 commit comments

Comments
 (0)