Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/strange-grapes-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-select': minor
---

Add `dataAttributes` prop to allow passing data attributes (e.g., `data-testid`) to the select container
3 changes: 3 additions & 0 deletions docs/examples/BasicMulti.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ export default () => (
options={colourOptions}
className="basic-multi-select"
classNamePrefix="select"
dataAttributes={{
'data-testid': 'basic-multi-select',
}}
/>
);
3 changes: 3 additions & 0 deletions docs/examples/BasicSingle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export default () => {
isSearchable={isSearchable}
name="color"
options={colourOptions}
dataAttributes={{
'data-testid': 'basic-single-select',
}}
/>

<div
Expand Down
12 changes: 10 additions & 2 deletions packages/react-select/src/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ export interface Props<
form?: string;
/** Marks the value-holding input as required for form validation */
required?: boolean;
/** Data attributes to be applied to the select container */
dataAttributes?: Record<string, string>;
}

export const defaultProps = {
Expand Down Expand Up @@ -405,7 +407,11 @@ function buildCategorizedOptions<
): CategorizedGroupOrOption<Option, Group>[] {
return props.options
.map((groupOrOption, groupOrOptionIndex) => {
if ('options' in groupOrOption) {
if (
groupOrOption &&
typeof groupOrOption === 'object' &&
'options' in groupOrOption
) {
const categorizedOptions = groupOrOption.options
.map((option, optionIndex) =>
toCategorizedOption(props, option, selectValue, optionIndex)
Expand Down Expand Up @@ -2202,7 +2208,8 @@ export default class Select<
const { Control, IndicatorsContainer, SelectContainer, ValueContainer } =
this.getComponents();

const { className, id, isDisabled, menuIsOpen } = this.props;
const { className, id, isDisabled, menuIsOpen, dataAttributes } =
this.props;
const { isFocused } = this.state;
const commonProps = (this.commonProps = this.getCommonProps());

Expand All @@ -2213,6 +2220,7 @@ export default class Select<
innerProps={{
id: id,
onKeyDown: this.onKeyDown,
...(dataAttributes || {}),
}}
isDisabled={isDisabled}
isFocused={isFocused}
Expand Down
26 changes: 26 additions & 0 deletions packages/react-select/src/__tests__/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2318,6 +2318,32 @@ cases(
}
);

cases(
'accessibility > passes through dataAttributes prop',
({
props = {
...BASIC_PROPS,
dataAttributes: { 'data-testid': 'test-select' },
},
}) => {
let { container } = render(<Select {...props} />);
// The data attributes should be on the outermost div (SelectContainer)
const selectContainer = container.querySelector('div[data-testid]');
expect(selectContainer).toBeTruthy();
expect(selectContainer!.getAttribute('data-testid')).toBe('test-select');
},
{
'single select > should pass dataAttributes prop down to the container': {},
'multi select > should pass dataAttributes prop down to the container': {
props: {
...BASIC_PROPS,
dataAttributes: { 'data-testid': 'test-select' },
isMulti: true,
},
},
}
);

test('accessibility > to show the number of options available in A11yText when the menu is Open', () => {
let { container, rerender } = render(
<Select {...BASIC_PROPS} inputValue={''} autoFocus menuIsOpen />
Expand Down