From 76a85a0a9bab7d01dd78abc1ce8fd64a4af5f2d8 Mon Sep 17 00:00:00 2001 From: Katie George Date: Thu, 23 Jan 2025 03:33:41 -0800 Subject: [PATCH] feat: Inline multiselect tokens (#3185) Co-authored-by: Katie George --- pages/multiselect/constants.ts | 22 +++++++++++++++++++ pages/multiselect/multiselect.test.page.tsx | 7 +++--- .../__snapshots__/documenter.test.ts.snap | 6 +++++ .../__tests__/multiselect.test.tsx | 10 ++++----- src/multiselect/interfaces.ts | 4 ++++ src/multiselect/internal.tsx | 2 +- 6 files changed, 42 insertions(+), 9 deletions(-) diff --git a/pages/multiselect/constants.ts b/pages/multiselect/constants.ts index 21b4d782f4..0c7ba8e221 100644 --- a/pages/multiselect/constants.ts +++ b/pages/multiselect/constants.ts @@ -11,3 +11,25 @@ export const deselectAriaLabel = (option: MultiselectProps.Option) => { const label = option?.value || option?.label; return label ? `Deselect ${label}` : 'no label'; }; + +export const getInlineAriaLabel = (selectedOptions: MultiselectProps.Options) => { + let label; + + if (selectedOptions.length === 0) { + label = 0; + } + + if (selectedOptions.length === 1) { + label = selectedOptions[0].label; + } + + if (selectedOptions.length === 2) { + label = `${selectedOptions[0].label} and ${selectedOptions[1].label}`; + } + + if (selectedOptions.length > 2) { + label = `${selectedOptions[0].label}, ${selectedOptions[1].label}, and ${selectedOptions.length - 2} more`; + } + + return label + ' selected'; +}; diff --git a/pages/multiselect/multiselect.test.page.tsx b/pages/multiselect/multiselect.test.page.tsx index 5a19befb3d..fbbf1c107f 100644 --- a/pages/multiselect/multiselect.test.page.tsx +++ b/pages/multiselect/multiselect.test.page.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import Box from '~components/box'; import Multiselect, { MultiselectProps } from '~components/multiselect'; -import { deselectAriaLabel, i18nStrings } from './constants'; +import { deselectAriaLabel, getInlineAriaLabel, i18nStrings } from './constants'; const _selectedOptions1 = [ { @@ -113,6 +113,7 @@ const options2 = [ ], }, ]; + export default function MultiselectPage() { const [selectedOptions1, setSelectedOptions1] = React.useState(_selectedOptions1); const [selectedOptions2, setSelectedOptions2] = React.useState(_selectedOptions1); @@ -235,14 +236,14 @@ export default function MultiselectPage() { Test: Inline tokens
{ setSelectedOptions7(event.detail.selectedOptions); }} diff --git a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap index 0fed0e3e02..f83e1c2da5 100644 --- a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap +++ b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap @@ -11259,6 +11259,12 @@ use the \`id\` attribute, consider setting it on a parent element instead.", "optional": true, "type": "string", }, + { + "description": "Shows tokens inside the trigger instead of below it.", + "name": "inlineTokens", + "optional": true, + "type": "boolean", + }, { "description": "Overrides the invalidation state. Usually the invalid state comes from the parent \`FormField\`component, diff --git a/src/multiselect/__tests__/multiselect.test.tsx b/src/multiselect/__tests__/multiselect.test.tsx index 00962a04df..b82eb7c0dd 100644 --- a/src/multiselect/__tests__/multiselect.test.tsx +++ b/src/multiselect/__tests__/multiselect.test.tsx @@ -650,10 +650,10 @@ test('Trigger receives focus when autofocus is true', () => { expect(document.activeElement).toBe(wrapper.findTrigger().getElement()); }); -describe('With inline tokens (private API)', () => { +describe('With inline tokens', () => { it('can render inline tokens', () => { const { wrapper } = renderMultiselect( - + ); // Trigger contains token labels and the number of selected items @@ -666,7 +666,7 @@ describe('With inline tokens (private API)', () => { it('shows placeholder when no items are selected', () => { const { wrapper } = renderMultiselect( - + ); expect(wrapper.findTrigger().getElement()).toHaveTextContent('Choose something'); @@ -677,7 +677,7 @@ describe('With inline tokens (private API)', () => { { value: '1', label: 'First', description: 'description', tags: ['tag'], labelTag: 'label' }, ]; const { wrapper } = renderMultiselect( - + ); expect(wrapper.findTrigger().getElement()).toHaveTextContent('First'); @@ -689,7 +689,7 @@ describe('With inline tokens (private API)', () => { it('shows multiple selected options inline', () => { const { wrapper } = renderMultiselect( diff --git a/src/multiselect/interfaces.ts b/src/multiselect/interfaces.ts index 7b4cf65540..5ebe51e7ee 100644 --- a/src/multiselect/interfaces.ts +++ b/src/multiselect/interfaces.ts @@ -23,6 +23,10 @@ export interface MultiselectProps extends BaseSelectProps { * Only use this if the selected options are displayed elsewhere on the page. */ hideTokens?: boolean; + /** + * Shows tokens inside the trigger instead of below it. + */ + inlineTokens?: boolean; /** * Specifies an `aria-label` for the token deselection button. * @i18n diff --git a/src/multiselect/internal.tsx b/src/multiselect/internal.tsx index f2ed2cf44b..72fc1a3b34 100644 --- a/src/multiselect/internal.tsx +++ b/src/multiselect/internal.tsx @@ -28,7 +28,7 @@ type InternalMultiselectProps = SomeRequired< MultiselectProps, 'options' | 'selectedOptions' | 'filteringType' | 'statusType' | 'keepOpen' | 'hideTokens' > & - InternalBaseComponentProps & { inlineTokens?: boolean }; + InternalBaseComponentProps; const InternalMultiselect = React.forwardRef( (