Skip to content

Commit 4697da7

Browse files
adamalstonannawen1
andauthored
fix: update Dropdown and MultiSelect to handle interactive labels (#18643)
* fix: update Dropdown and MultiSelect to handle interactive labels * fix: conditionally add props to Dropdown and MultiSelect labels --------- Co-authored-by: Anna Wen <54281166+annawen1@users.noreply.github.com>
1 parent 2d59305 commit 4697da7

File tree

6 files changed

+147
-13
lines changed

6 files changed

+147
-13
lines changed

packages/react/src/components/Dropdown/Dropdown.stories.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
77

88
import React from 'react';
9+
import { FolderOpen, Folders, Information, View } from '@carbon/icons-react';
910

1011
import { WithLayer } from '../../../.storybook/templates/WithLayer';
1112

1213
import { default as Dropdown, DropdownSkeleton } from './';
1314
import Button from '../Button';
1415
import { AILabel, AILabelContent, AILabelActions } from '../AILabel';
16+
import {
17+
Toggletip,
18+
ToggletipActions,
19+
ToggletipButton,
20+
ToggletipContent,
21+
ToggletipLabel,
22+
} from '../Toggletip';
1523
import { IconButton } from '../IconButton';
16-
import { View, FolderOpen, Folders } from '@carbon/icons-react';
1724
import mdx from './Dropdown.mdx';
25+
import Link from '../Link';
1826

1927
export default {
2028
title: 'Components/Dropdown',
@@ -452,3 +460,36 @@ export const withAILabel = (args) => {
452460
withAILabel.argTypes = {
453461
...sharedArgTypes,
454462
};
463+
464+
export const withToggletipLabel = () => {
465+
return (
466+
<div>
467+
<Dropdown
468+
label="placeholder"
469+
id="dropdown"
470+
items={[]}
471+
titleText={
472+
<div style={{ display: 'flex', alignItems: 'center' }}>
473+
<ToggletipLabel>Dropdown title</ToggletipLabel>
474+
<Toggletip>
475+
<ToggletipButton label="Show information">
476+
<Information />
477+
</ToggletipButton>
478+
<ToggletipContent>
479+
<p>
480+
Lorem ipsum dolor sit amet, di os consectetur adipiscing elit,
481+
sed do eiusmod tempor incididunt ut fsil labore et dolore
482+
magna aliqua.
483+
</p>
484+
<ToggletipActions>
485+
<Link href="#">Link action</Link>
486+
<Button size="sm">Button</Button>
487+
</ToggletipActions>
488+
</ToggletipContent>
489+
</Toggletip>
490+
</div>
491+
}
492+
/>
493+
</div>
494+
);
495+
};

packages/react/src/components/Dropdown/Dropdown.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
/**
2-
* Copyright IBM Corp. 2022
2+
* Copyright IBM Corp. 2022, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
77

88
import React, {
9-
useCallback,
10-
useContext,
11-
useState,
129
FocusEvent,
1310
ForwardedRef,
11+
isValidElement,
1412
MouseEvent,
1513
ReactNode,
14+
useCallback,
15+
useContext,
1616
useEffect,
1717
useMemo,
18-
ReactElement,
18+
useState,
1919
} from 'react';
2020
import {
2121
useSelect,
@@ -608,10 +608,12 @@ const Dropdown = React.forwardRef(
608608
return React.isValidElement(element) ? element : null;
609609
}, [slug, decorator]);
610610

611+
const labelProps = !isValidElement(titleText) ? getLabelProps() : null;
612+
611613
return (
612614
<div className={wrapperClasses} {...other}>
613615
{titleText && (
614-
<label className={titleClasses} {...getLabelProps()}>
616+
<label className={titleClasses} {...labelProps}>
615617
{titleText}
616618
</label>
617619
)}

packages/react/src/components/Dropdown/__tests__/Dropdown-test.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -458,4 +458,20 @@ describe('Test useEffect ', () => {
458458
await waitForPosition();
459459
assertMenuClosed();
460460
});
461+
462+
it('should add label props when `titleText` is a string', () => {
463+
render(<Dropdown {...mockProps} titleText="Dropdown Title" />);
464+
465+
const label = screen.getByText('Dropdown Title').closest('label');
466+
467+
expect(label).toHaveAttribute('id');
468+
});
469+
470+
it('should not add label props when `titleText` is an element', () => {
471+
render(<Dropdown {...mockProps} titleText={<span>Dropdown Title</span>} />);
472+
473+
const label = screen.getByText('Dropdown Title').closest('label');
474+
475+
expect(label).not.toHaveAttribute('id');
476+
});
461477
});

packages/react/src/components/MultiSelect/MultiSelect.stories.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
77

88
import React, { useEffect, useRef, useState } from 'react';
9+
import { View, FolderOpen, Folders, Information } from '@carbon/icons-react';
910
import { action } from '@storybook/addon-actions';
1011

1112
import { WithLayer } from '../../../.storybook/templates/WithLayer';
@@ -17,7 +18,14 @@ import Button from '../Button';
1718
import ButtonSet from '../ButtonSet';
1819
import { AILabel, AILabelContent, AILabelActions } from '../AILabel';
1920
import { IconButton } from '../IconButton';
20-
import { View, FolderOpen, Folders } from '@carbon/icons-react';
21+
import {
22+
Toggletip,
23+
ToggletipActions,
24+
ToggletipButton,
25+
ToggletipContent,
26+
ToggletipLabel,
27+
} from '../Toggletip';
28+
import Link from '../Link';
2129

2230
export default {
2331
title: 'Components/MultiSelect',
@@ -595,3 +603,40 @@ export const ExperimentalAutoAlign = (args) => {
595603
};
596604

597605
ExperimentalAutoAlign.argTypes = { ...sharedArgTypes };
606+
607+
export const withToggletipLabel = (args) => {
608+
return (
609+
<div>
610+
<MultiSelect
611+
label="Multiselect Label"
612+
id="carbon-multiselect-example"
613+
titleText={
614+
<div style={{ display: 'flex', alignItems: 'center' }}>
615+
<ToggletipLabel>Multiselect title</ToggletipLabel>
616+
<Toggletip>
617+
<ToggletipButton label="Show information">
618+
<Information />
619+
</ToggletipButton>
620+
<ToggletipContent>
621+
<p>
622+
Lorem ipsum dolor sit amet, di os consectetur adipiscing elit,
623+
sed do eiusmod tempor incididunt ut fsil labore et dolore
624+
magna aliqua.
625+
</p>
626+
<ToggletipActions>
627+
<Link href="#">Link action</Link>
628+
<Button size="sm">Button</Button>
629+
</ToggletipActions>
630+
</ToggletipContent>
631+
</Toggletip>
632+
</div>
633+
}
634+
helperText="This is helper text"
635+
items={items}
636+
itemToString={(item) => (item ? item.text : '')}
637+
selectionFeedback="top-after-reopen"
638+
{...args}
639+
/>
640+
</div>
641+
);
642+
};

packages/react/src/components/MultiSelect/MultiSelect.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import React, {
2222
useMemo,
2323
ReactNode,
2424
useLayoutEffect,
25+
isValidElement,
2526
} from 'react';
2627
import ListBox, {
2728
ListBoxSize,
@@ -729,9 +730,11 @@ const MultiSelect = React.forwardRef(
729730
[enableFloatingStyles, getMenuProps, refs.setFloating]
730731
);
731732

733+
const labelProps = !isValidElement(titleText) ? getLabelProps() : null;
734+
732735
return (
733736
<div className={wrapperClasses}>
734-
<label className={titleClasses} {...getLabelProps()}>
737+
<label className={titleClasses} {...labelProps}>
735738
{titleText && titleText}
736739
{selectedItems.length > 0 && (
737740
<span className={`${prefix}--visually-hidden`}>

packages/react/src/components/MultiSelect/__tests__/MultiSelect-test.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -24,9 +24,18 @@ import userEvent from '@testing-library/user-event';
2424

2525
const prefix = 'cds';
2626
const waitForPosition = () => act(async () => {});
27+
2728
describe('MultiSelect', () => {
29+
let mockProps;
2830
beforeEach(() => {
2931
jest.mock('../../../internal/deprecateFieldOnObject');
32+
mockProps = {
33+
id: 'test-multiselect',
34+
initialSelectedItems: [],
35+
items: generateItems(5, generateGenericItem),
36+
label: 'Test label',
37+
onChange: jest.fn(),
38+
};
3039
});
3140

3241
describe.skip('automated accessibility tests', () => {
@@ -925,4 +934,22 @@ describe('MultiSelect', () => {
925934
expect(result).toBe('');
926935
expect(mockItemToString).not.toHaveBeenCalled();
927936
});
937+
938+
it('should add label props when `titleText` is a string', () => {
939+
render(<MultiSelect {...mockProps} titleText="MultiSelect Title" />);
940+
941+
const label = screen.getByText('MultiSelect Title').closest('label');
942+
943+
expect(label).toHaveAttribute('id');
944+
});
945+
946+
it('should not add label props when `titleText` is an element', () => {
947+
render(
948+
<MultiSelect {...mockProps} titleText={<span>MultiSelect Title</span>} />
949+
);
950+
951+
const label = screen.getByText('MultiSelect Title').closest('label');
952+
953+
expect(label).not.toHaveAttribute('id');
954+
});
928955
});

0 commit comments

Comments
 (0)