Add support for sections to Select (#34012)

Updates the Select component to accept a set of sections with options as opposed to just a flat list of options.

---------

Signed-off-by: James Brooks <jamesbrooks@spotify.com>
This commit is contained in:
James Brooks
2026-04-28 16:13:39 +01:00
committed by GitHub
parent 080dcbb982
commit e7fc79fb13
12 changed files with 357 additions and 51 deletions
@@ -40,6 +40,33 @@ const skills = [
{ value: 'swift', label: 'Swift' },
];
const sectionedFonts = [
{
title: 'Serif Fonts',
options: [
{ value: 'times', label: 'Times New Roman' },
{ value: 'georgia', label: 'Georgia' },
{ value: 'garamond', label: 'Garamond' },
],
},
{
title: 'Sans-Serif Fonts',
options: [
{ value: 'arial', label: 'Arial' },
{ value: 'helvetica', label: 'Helvetica' },
{ value: 'verdana', label: 'Verdana' },
],
},
{
title: 'Monospace Fonts',
options: [
{ value: 'courier', label: 'Courier New' },
{ value: 'consolas', label: 'Consolas' },
{ value: 'fira', label: 'Fira Code' },
],
},
];
export const Preview = () => (
<Select
label="Font Family"
@@ -148,3 +175,23 @@ export const SearchableMultiple = () => (
style={{ width: 300 }}
/>
);
export const WithSections = () => (
<Select
label="Font Family"
options={sectionedFonts}
name="font"
style={{ width: 300 }}
/>
);
export const SearchableWithSections = () => (
<Select
label="Font Family"
searchable
searchPlaceholder="Search fonts..."
options={sectionedFonts}
name="font"
style={{ width: 300 }}
/>
);
+44 -1
View File
@@ -12,8 +12,14 @@ import {
Searchable,
MultipleSelection,
SearchableMultiple,
WithSections,
SearchableWithSections,
} from './components';
import { selectPropDefs } from './props-definition';
import {
selectPropDefs,
optionPropDefs,
optionSectionPropDefs,
} from './props-definition';
import {
selectUsageSnippet,
selectDefaultSnippet,
@@ -26,6 +32,8 @@ import {
selectMultipleSnippet,
selectSearchableMultipleSnippet,
selectDisabledOptionsSnippet,
selectSectionsSnippet,
selectSearchableSectionsSnippet,
} from './snippets';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
@@ -58,6 +66,18 @@ export const reactAriaUrls = {
<ReactAriaLink component="Select" href={reactAriaUrls.select} />
### Option types
The `options` prop accepts an array containing either of the following shapes.
#### `Option`
<PropsTable data={optionPropDefs} />
#### `OptionSection`
<PropsTable data={optionSectionPropDefs} />
## Examples
### Label and description
@@ -136,6 +156,29 @@ Combine search and multiple selection.
code={selectSearchableMultipleSnippet}
/>
### With sections
Group options under section headings by passing objects with a `title` and a
nested `options` array.
<Snippet
layout="side-by-side"
open
preview={<WithSections />}
code={selectSectionsSnippet}
/>
### Searchable with sections
Sections are preserved when filtering with `searchable`.
<Snippet
layout="side-by-side"
open
preview={<SearchableWithSections />}
code={selectSearchableSectionsSnippet}
/>
### Responsive
Size can change at different breakpoints.
@@ -5,30 +5,48 @@ import {
} from '@/utils/propDefs';
import { Chip } from '@/components/Chip';
export const optionPropDefs: Record<string, PropDef> = {
value: {
type: 'string',
required: true,
description: 'Unique value for the option.',
},
label: {
type: 'string',
required: true,
description: 'Display text for the option.',
},
disabled: {
type: 'boolean',
description: 'Whether the option is disabled.',
},
};
export const optionSectionPropDefs: Record<string, PropDef> = {
title: {
type: 'string',
required: true,
description: 'Heading displayed above the grouped options.',
},
options: {
type: 'enum',
values: ['Option[]'],
required: true,
description: 'Options nested inside the section.',
},
};
export const selectPropDefs: Record<string, PropDef> = {
options: {
type: 'complex',
description: 'Array of options to display in the dropdown.',
complexType: {
name: 'SelectOption[]',
properties: {
value: {
type: 'string',
required: true,
description: 'Unique value for the option.',
},
label: {
type: 'string',
required: true,
description: 'Display text for the option.',
},
disabled: {
type: 'boolean',
required: false,
description: 'Whether the option is disabled.',
},
},
},
type: 'enum',
values: ['(Option | OptionSection)[]'],
description: (
<>
Options to display in the dropdown. Pass <Chip>Option</Chip> objects
directly, or <Chip>OptionSection</Chip> objects to render grouped
options under section headings.
</>
),
},
selectionMode: {
type: 'enum',
@@ -110,3 +110,49 @@ export const selectDisabledOptionsSnippet = `<Select
{ value: 'cursive', label: 'Cursive' },
]}
/>`;
export const selectSectionsSnippet = `<Select
name="font"
label="Font Family"
options={[
{
title: 'Serif Fonts',
options: [
{ value: 'times', label: 'Times New Roman' },
{ value: 'georgia', label: 'Georgia' },
{ value: 'garamond', label: 'Garamond' },
],
},
{
title: 'Sans-Serif Fonts',
options: [
{ value: 'arial', label: 'Arial' },
{ value: 'helvetica', label: 'Helvetica' },
{ value: 'verdana', label: 'Verdana' },
],
},
]}
/>`;
export const selectSearchableSectionsSnippet = `<Select
name="font"
label="Font Family"
searchable
searchPlaceholder="Search fonts..."
options={[
{
title: 'Serif Fonts',
options: [
{ value: 'times', label: 'Times New Roman' },
{ value: 'georgia', label: 'Georgia' },
],
},
{
title: 'Sans-Serif Fonts',
options: [
{ value: 'arial', label: 'Arial' },
{ value: 'helvetica', label: 'Helvetica' },
],
},
]}
/>`;