Add empty state to multi select input (#13029)

Took inspiration from `RecordPickerNoRecordFoundMenuItem`.

## Before


https://github.com/user-attachments/assets/a8056418-d225-4cd1-b3b8-d7a9792c4f92

## After


https://github.com/user-attachments/assets/126681cd-def4-48d7-a93e-99674993c90e
This commit is contained in:
Baptiste Devessier
2025-07-03 16:38:54 +02:00
committed by GitHub
parent 50ab46cf2a
commit bc94d58af7
64 changed files with 558 additions and 54 deletions

View File

@ -4001,6 +4001,11 @@ msgstr "Geen maatstawedata beskikbaar"
#~ msgid "No more members to assign"
#~ msgstr "Geen meer lede om toe te wys nie"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "لا توجد بيانات قياسات متاحة"
#~ msgid "No more members to assign"
#~ msgstr "لا يوجد المزيد من الأعضاء لتعيينهم"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "No hi ha dades de mètriques disponibles"
#~ msgid "No more members to assign"
#~ msgstr "No hi ha més membres per assignar"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Žádná data metrik nejsou k dispozici"
#~ msgid "No more members to assign"
#~ msgstr "Žádní další členové k přiřazení"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Ingen metrikdata tilgængelige"
#~ msgid "No more members to assign"
#~ msgstr "Ingen flere medlemmer at tildele"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Keine Metrik-Daten verfügbar"
#~ msgid "No more members to assign"
#~ msgstr "Keine Mitglieder mehr zuzuweisen"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Δεν υπάρχουν διαθέσιμα δεδομένα μετρήσ
#~ msgid "No more members to assign"
#~ msgstr "Δεν υπάρχουν άλλοι μέλη για ανάθεση"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -3996,6 +3996,11 @@ msgstr "No metrics data available"
#~ msgid "No more members to assign"
#~ msgstr "No more members to assign"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr "No option found"
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "No hay datos de métricas disponibles"
#~ msgid "No more members to assign"
#~ msgstr "No hay más miembros para asignar"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Ei metriikkatietoja saatavilla"
#~ msgid "No more members to assign"
#~ msgstr "Ei enää jäseniä osoitettavana"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Aucune donnée de métriques disponible"
#~ msgid "No more members to assign"
#~ msgstr "Plus de membres à assigner"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4001,6 +4001,11 @@ msgstr "אין נתונים זמינים"
#~ msgid "No more members to assign"
#~ msgstr "אין עוד חברים להקצות"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Nincs elérhető metrikai adat"
#~ msgid "No more members to assign"
#~ msgstr "Nincs több hozzárendelhető tag"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Nessun dato metrico disponibile"
#~ msgid "No more members to assign"
#~ msgstr "Non ci sono più membri da assegnare"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "利用可能なメトリックスデータがありません"
#~ msgid "No more members to assign"
#~ msgstr "これ以上のメンバーを割り当てることはできません"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "사용 가능한 메트릭스 데이터 없음"
#~ msgid "No more members to assign"
#~ msgstr "지정할 멤버가 더 이상 없습니다"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Geen metrische gegevens beschikbaar"
#~ msgid "No more members to assign"
#~ msgstr "Geen extra leden om toe te wijzen"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Ingen metrikkdata tilgjengelig"
#~ msgid "No more members to assign"
#~ msgstr "Ingen flere medlemmer å tildele"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Brak dostępnych danych o metrykach"
#~ msgid "No more members to assign"
#~ msgstr "Brak więcej członków do przypisania"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -3993,6 +3993,11 @@ msgstr ""
#~ msgid "No more members to assign"
#~ msgstr ""
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Nenhum dado de métricas disponível"
#~ msgid "No more members to assign"
#~ msgstr "Não há mais membros para atribuir"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Não há dados de métricas disponíveis"
#~ msgid "No more members to assign"
#~ msgstr "Sem mais membros para atribuir"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Nu sunt disponibile date metrice"
#~ msgid "No more members to assign"
#~ msgstr "Nu mai sunt membri de atribuit"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Нема доступних података о метрикама"
#~ msgid "No more members to assign"
#~ msgstr "Нема више чланова за доделу"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Ingen metrikdata tillgänglig"
#~ msgid "No more members to assign"
#~ msgstr "Inga fler medlemmar att tilldela"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Metrik verisi yok"
#~ msgid "No more members to assign"
#~ msgstr "Atanacak başka üye yok"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Дані метрик недоступні"
#~ msgid "No more members to assign"
#~ msgstr "Більше немає учасників для призначення"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "Không có dữ liệu chỉ số khả dụng"
#~ msgid "No more members to assign"
#~ msgstr "Không còn thành viên nào để chỉ định"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "没有可用的指标数据"
#~ msgid "No more members to assign"
#~ msgstr "没有更多成员可分配"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -4001,6 +4001,11 @@ msgstr "沒有可用的數據指標"
#~ msgid "No more members to assign"
#~ msgstr "沒有更多成員可以指派"
#. js-lingui-id: daCzI1
#: src/modules/ui/field/input/components/MultiSelectInput.tsx
msgid "No option found"
msgstr ""
#. js-lingui-id: tTItk7
#: src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx
msgid "No output available"

View File

@ -15,9 +15,10 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import { SelectOption } from 'twenty-ui/input';
import { MenuItemMultiSelectTag } from 'twenty-ui/navigation';
import { MenuItem, MenuItemMultiSelectTag } from 'twenty-ui/navigation';
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
type MultiSelectInputProps = {
@ -39,6 +40,8 @@ export const MultiSelectInput = ({
onOptionSelected,
dropdownWidth,
}: MultiSelectInputProps) => {
const { t } = useLingui();
const { resetSelectedItem } = useSelectableList(
selectableListComponentInstanceId,
);
@ -124,29 +127,33 @@ export const MultiSelectInput = ({
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{filteredOptionsInDropDown.map((option) => {
return (
<SelectableListItem
key={option.value}
itemId={option.value}
onEnter={() => {
onOptionSelected(formatNewSelectedOptions(option.value));
}}
>
<MenuItemMultiSelectTag
{filteredOptionsInDropDown.length === 0 ? (
<MenuItem disabled text={t`No option found`} accent="placeholder" />
) : (
filteredOptionsInDropDown.map((option) => {
return (
<SelectableListItem
key={option.value}
selected={values?.includes(option.value) || false}
text={option.label}
color={option.color ?? 'transparent'}
Icon={option.Icon ?? undefined}
onClick={() =>
onOptionSelected(formatNewSelectedOptions(option.value))
}
isKeySelected={selectedItemId === option.value}
/>
</SelectableListItem>
);
})}
itemId={option.value}
onEnter={() => {
onOptionSelected(formatNewSelectedOptions(option.value));
}}
>
<MenuItemMultiSelectTag
key={option.value}
selected={values?.includes(option.value) || false}
text={option.label}
color={option.color ?? 'transparent'}
Icon={option.Icon ?? undefined}
onClick={() =>
onOptionSelected(formatNewSelectedOptions(option.value))
}
isKeySelected={selectedItemId === option.value}
/>
</SelectableListItem>
);
})
)}
</DropdownMenuItemsContainer>
</DropdownContent>
</SelectableList>

View File

@ -0,0 +1,347 @@
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, waitFor, within } from '@storybook/test';
import { useEffect, useState } from 'react';
import {
IconBolt,
IconBrandGoogle,
IconBrandLinkedin,
IconCheck,
IconHeart,
IconRocket,
IconTag,
IconTarget,
} from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
import { ComponentDecorator } from 'twenty-ui/testing';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { MultiSelectInput } from '../MultiSelectInput';
type RenderProps = {
values: FieldMultiSelectValue;
options: SelectOption[];
onOptionSelected: (value: FieldMultiSelectValue) => void;
onCancel?: () => void;
dropdownWidth?: number;
};
const sampleOptions: SelectOption[] = [
{
value: 'social-media',
label: 'Social Media',
color: 'blue',
Icon: IconTag,
},
{
value: 'search-engine',
label: 'Search Engine',
color: 'green',
Icon: IconBrandGoogle,
},
{
value: 'professional',
label: 'Professional Network',
color: 'purple',
Icon: IconBrandLinkedin,
},
{ value: 'referral', label: 'Referral', color: 'orange', Icon: IconTag },
{
value: 'advertising',
label: 'Advertising',
color: 'red',
Icon: IconTarget,
},
{
value: 'content',
label: 'Content Marketing',
color: 'yellow',
Icon: IconCheck,
},
{ value: 'email', label: 'Email Campaign', color: 'pink', Icon: IconHeart },
{
value: 'viral',
label: 'Viral Marketing',
color: 'turquoise',
Icon: IconBolt,
},
{ value: 'growth', label: 'Growth Hacking', color: 'gray', Icon: IconRocket },
];
const priorityOptions: SelectOption[] = [
{ value: 'low', label: 'Low Priority', color: 'green' },
{ value: 'medium', label: 'Medium Priority', color: 'yellow' },
{ value: 'high', label: 'High Priority', color: 'orange' },
{ value: 'urgent', label: 'Urgent', color: 'red' },
];
const Render = ({
values,
options,
onOptionSelected,
onCancel,
dropdownWidth,
}: RenderProps) => {
const [currentValues, setCurrentValues] =
useState<FieldMultiSelectValue>(values);
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
useEffect(() => {
pushFocusItemToFocusStack({
focusId: TableHotkeyScope.CellEditMode,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: getFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
}),
},
hotkeyScope: {
scope: TableHotkeyScope.CellEditMode,
},
memoizeKey: getFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
}),
});
}, [pushFocusItemToFocusStack]);
const handleOptionSelected = (newValues: FieldMultiSelectValue) => {
setCurrentValues(newValues);
onOptionSelected(newValues);
};
return (
<div style={{ height: '400px', padding: '20px' }}>
<MultiSelectInput
selectableListComponentInstanceId="multi-select-story"
values={currentValues}
options={options}
focusId={DEFAULT_CELL_SCOPE.scope}
onCancel={onCancel}
onOptionSelected={handleOptionSelected}
dropdownWidth={dropdownWidth}
/>
</div>
);
};
const meta: Meta<typeof MultiSelectInput> = {
title: 'UI/Field/Input/MultiSelectInput',
component: MultiSelectInput,
decorators: [ComponentDecorator, I18nFrontDecorator],
args: {
values: [],
options: sampleOptions,
onOptionSelected: fn(),
},
render: Render,
};
export default meta;
type Story = StoryObj<typeof MultiSelectInput>;
export const Default: Story = {
args: {
values: [],
options: sampleOptions,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await waitFor(() => {
expect(canvas.getByRole('textbox')).toBeVisible();
});
for (const option of sampleOptions) {
expect(canvas.getByText(option.label)).toBeVisible();
}
},
};
export const WithPreselectedValues: Story = {
args: {
values: ['social-media', 'search-engine'],
options: sampleOptions,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await waitFor(() => {
expect(canvas.getByRole('textbox')).toBeVisible();
});
await waitFor(() => {
const checkboxes = canvas.getAllByRole('checkbox', { checked: true });
expect(checkboxes).toHaveLength(2);
});
for (const option of sampleOptions) {
expect(canvas.getByText(option.label)).toBeVisible();
}
},
};
export const SingleSelection: Story = {
args: {
values: ['professional'],
options: sampleOptions,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await waitFor(() => {
expect(canvas.getByRole('textbox')).toBeVisible();
});
await waitFor(() => {
const checkboxes = canvas.getAllByRole('checkbox', { checked: true });
expect(checkboxes).toHaveLength(1);
});
for (const option of sampleOptions) {
expect(canvas.getByText(option.label)).toBeVisible();
}
await userEvent.click(canvas.getByText('Professional Network'));
await waitFor(() => {
const checkboxes = canvas.queryAllByRole('checkbox', { checked: true });
expect(checkboxes).toHaveLength(0);
});
},
};
export const EmptyOptions: Story = {
args: {
values: [],
options: [],
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await waitFor(() => {
expect(canvas.getByRole('textbox')).toBeVisible();
});
expect(canvas.getByText('No option found')).toBeVisible();
},
};
export const LongLabels: Story = {
args: {
values: ['long-option-1'],
options: [
{
value: 'long-option-1',
label:
'This is a very long option label that might overflow the container',
color: 'blue',
},
{
value: 'long-option-2',
label:
'Another extremely long option label to test text wrapping behavior',
color: 'green',
},
{
value: 'short',
label: 'Short',
color: 'red',
},
],
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await waitFor(() => {
expect(canvas.getByRole('textbox')).toBeVisible();
});
expect(
canvas.getByText(
'This is a very long option label that might overflow the container',
),
).toBeVisible();
expect(canvas.getByText('Short')).toBeVisible();
},
};
export const SearchFiltering: Story = {
args: {
values: [],
options: sampleOptions,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const searchInput = canvas.getByRole('textbox');
await userEvent.type(searchInput, 'marketing');
await waitFor(() => {
expect(canvas.getByText('Content Marketing')).toBeVisible();
expect(canvas.getByText('Viral Marketing')).toBeVisible();
});
expect(canvas.queryByText('Social Media')).not.toBeInTheDocument();
expect(canvas.getAllByRole('checkbox')).toHaveLength(2);
},
};
export const NoResultsFound: Story = {
args: {
values: [],
options: sampleOptions,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const searchInput = canvas.getByRole('textbox');
await userEvent.type(searchInput, 'xyz123');
await waitFor(() => {
expect(canvas.getByText('No option found')).toBeVisible();
});
},
};
export const KeyboardNavigation: Story = {
args: {
values: [],
options: priorityOptions,
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const searchInput = await canvas.findByRole('textbox');
await userEvent.click(searchInput);
await waitFor(() => {
expect(searchInput).toHaveFocus();
});
await userEvent.keyboard('{ArrowDown}');
await userEvent.keyboard('{ArrowDown}');
const secondOption = await canvas.findByText('Medium Priority');
expect(secondOption).toBeVisible();
await userEvent.keyboard('{Enter}');
await waitFor(() => {
expect(args.onOptionSelected).toHaveBeenCalledWith(['medium']);
});
},
};