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:
committed by
GitHub
parent
50ab46cf2a
commit
bc94d58af7
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
Binary file not shown.
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,7 +127,10 @@ export const MultiSelectInput = ({
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{filteredOptionsInDropDown.map((option) => {
|
||||
{filteredOptionsInDropDown.length === 0 ? (
|
||||
<MenuItem disabled text={t`No option found`} accent="placeholder" />
|
||||
) : (
|
||||
filteredOptionsInDropDown.map((option) => {
|
||||
return (
|
||||
<SelectableListItem
|
||||
key={option.value}
|
||||
@ -146,7 +152,8 @@ export const MultiSelectInput = ({
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
})}
|
||||
})
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
</SelectableList>
|
||||
|
||||
@ -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']);
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user