Skip to content

Commit 7f0cc0d

Browse files
eirikbackerBarsnes
andauthored
fix(Select): option and optgroup compond components (#2415)
- Fixes #2403 - Also omits `multiple` parameter after talk with Lasse as this there is no design for this, and the user experience is not very good --------- Co-authored-by: Tobias Barsnes <tobias.barsnes@digdir.no>
1 parent 722fbe4 commit 7f0cc0d

File tree

13 files changed

+143
-67
lines changed

13 files changed

+143
-67
lines changed

.changeset/shaggy-rockets-repair.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@digdir/designsystemet-react": patch
3+
---
4+
5+
Select:
6+
- Add Select.Option and Select.Optgroup compond components
7+
- Remove `multiple` prop

apps/theme/components/Previews/Components/Components.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ export const Components = () => {
8787
<div className={classes.tableHeader}>
8888
<div className={classes.tableAction}>
8989
<Select label='Velg handling' size='sm' hideLabel>
90-
<option value='blank'>Velg handling</option>
91-
<option value='everest'>Dupliser</option>
92-
<option value='aconcagua'>Slett</option>
93-
<option value='denali'>Oppdater</option>
90+
<Select.Option value='blank'>Velg handling</Select.Option>
91+
<Select.Option value='everest'>Dupliser</Select.Option>
92+
<Select.Option value='aconcagua'>Slett</Select.Option>
93+
<Select.Option value='denali'>Oppdater</Select.Option>
9494
</Select>
9595
<Button className={classes.tableBtn} size='sm'>
9696
Utfør

apps/theme/components/ThemeToolbar/ThemeToolbar.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ export const ThemeToolbar = ({
116116
onContrastModeChanged(e.target.value as 'aa' | 'aaa');
117117
}}
118118
>
119-
<option value='aa'>AA</option>
120-
<option value='aaa'>AAA (WIP)</option>
119+
<Select.Option value='aa'>AA</Select.Option>
120+
<Select.Option value='aaa'>AAA (WIP)</Select.Option>
121121
</Select>
122122
</div>
123123
<div className={classes.borderRadii}>
@@ -132,15 +132,15 @@ export const ThemeToolbar = ({
132132
}}
133133
>
134134
{Object.entries(borderRadii).map(([key, value]) => (
135-
<option
135+
<Select.Option
136136
key={key}
137137
value={value}
138138
style={{
139139
textTransform: 'capitalize',
140140
}}
141141
>
142142
{key}
143-
</option>
143+
</Select.Option>
144144
))}
145145
</Select>
146146
</div>

packages/react/src/components/Card/Card.stories.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,9 @@ export const Composed: Story = () => (
289289
<Card.Content>
290290
<Select label='Velg rolle'>
291291
{options.map(({ value, label }, index) => (
292-
<option key={index} value={value}>
292+
<Select.Option key={index} value={value}>
293293
{label}
294-
</option>
294+
</Select.Option>
295295
))}
296296
</Select>
297297
<Textfield label='Fødsels- eller d-nummer' />

packages/react/src/components/form/Select/Select.mdx

+5-5
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ For eksempel er det ikke mulig å overstyre formateringen av alternativene.
2222
import { Select } from '@digdir/designsystemet-react';
2323

2424
<Select label='Ta et valg'>
25-
<option value='1'>Alternativ 1</option>
26-
<option value='2'>Alternativ 2</option>
27-
<option value='3'>Alternativ 3</option>
25+
<Select.Option value='1'>Alternativ 1</Select.Option>
26+
<Select.Option value='2'>Alternativ 2</Select.Option>
27+
<Select.Option value='3'>Alternativ 3</Select.Option>
2828
</Select>;
2929
```
3030

@@ -38,6 +38,6 @@ import { Select } from '@digdir/designsystemet-react';
3838

3939
<Canvas of={SelectStories.WithError} />
4040

41-
### Flervalg
41+
### Med gruppering
4242

43-
<Canvas of={SelectStories.Multiple} />
43+
<Canvas of={SelectStories.WithOptgroup} />
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Meta, StoryFn } from '@storybook/react';
22

3-
import { Select } from './Select';
3+
import { Select } from './';
44

55
export default {
66
title: 'Komponenter/Select',
@@ -12,15 +12,15 @@ export default {
1212

1313
export const Preview: StoryFn<typeof Select> = (args) => (
1414
<Select {...args}>
15-
<option value='blank'>Velg &hellip;</option>
16-
<option value='everest'>Mount Everest</option>
17-
<option value='aconcagua'>Aconcagua</option>
18-
<option value='denali'>Denali</option>
19-
<option value='kilimanjaro'>Kilimanjaro</option>
20-
<option value='elbrus'>Elbrus</option>
21-
<option value='vinson'>Mount Vinson</option>
22-
<option value='puncakjaya'>Puncak Jaya</option>
23-
<option value='kosciuszko'>Mount Kosciuszko</option>
15+
<Select.Option value='blank'>Velg &hellip;</Select.Option>
16+
<Select.Option value='everest'>Mount Everest</Select.Option>
17+
<Select.Option value='aconcagua'>Aconcagua</Select.Option>
18+
<Select.Option value='denali'>Denali</Select.Option>
19+
<Select.Option value='kilimanjaro'>Kilimanjaro</Select.Option>
20+
<Select.Option value='elbrus'>Elbrus</Select.Option>
21+
<Select.Option value='vinson'>Mount Vinson</Select.Option>
22+
<Select.Option value='puncakjaya'>Puncak Jaya</Select.Option>
23+
<Select.Option value='kosciuszko'>Mount Kosciuszko</Select.Option>
2424
</Select>
2525
);
2626

@@ -33,15 +33,15 @@ Preview.args = {
3333

3434
export const Disabled: StoryFn<typeof Select> = (args) => (
3535
<Select {...args}>
36-
<option value='blank'>Velg &hellip;</option>
37-
<option value='everest'>Mount Everest</option>
38-
<option value='aconcagua'>Aconcagua</option>
39-
<option value='denali'>Denali</option>
40-
<option value='kilimanjaro'>Kilimanjaro</option>
41-
<option value='elbrus'>Elbrus</option>
42-
<option value='vinson'>Mount Vinson</option>
43-
<option value='puncakjaya'>Puncak Jaya</option>
44-
<option value='kosciuszko'>Mount Kosciuszko</option>
36+
<Select.Option value='blank'>Velg &hellip;</Select.Option>
37+
<Select.Option value='everest'>Mount Everest</Select.Option>
38+
<Select.Option value='aconcagua'>Aconcagua</Select.Option>
39+
<Select.Option value='denali'>Denali</Select.Option>
40+
<Select.Option value='kilimanjaro'>Kilimanjaro</Select.Option>
41+
<Select.Option value='elbrus'>Elbrus</Select.Option>
42+
<Select.Option value='vinson'>Mount Vinson</Select.Option>
43+
<Select.Option value='puncakjaya'>Puncak Jaya</Select.Option>
44+
<Select.Option value='kosciuszko'>Mount Kosciuszko</Select.Option>
4545
</Select>
4646
);
4747

@@ -52,15 +52,15 @@ Disabled.args = {
5252

5353
export const WithError: StoryFn<typeof Select> = (args) => (
5454
<Select {...args}>
55-
<option value='blank'>Velg &hellip;</option>
56-
<option value='everest'>Mount Everest</option>
57-
<option value='aconcagua'>Aconcagua</option>
58-
<option value='denali'>Denali</option>
59-
<option value='kilimanjaro'>Kilimanjaro</option>
60-
<option value='elbrus'>Elbrus</option>
61-
<option value='vinson'>Mount Vinson</option>
62-
<option value='puncakjaya'>Puncak Jaya</option>
63-
<option value='kosciuszko'>Mount Kosciuszko</option>
55+
<Select.Option value='blank'>Velg &hellip;</Select.Option>
56+
<Select.Option value='everest'>Mount Everest</Select.Option>
57+
<Select.Option value='aconcagua'>Aconcagua</Select.Option>
58+
<Select.Option value='denali'>Denali</Select.Option>
59+
<Select.Option value='kilimanjaro'>Kilimanjaro</Select.Option>
60+
<Select.Option value='elbrus'>Elbrus</Select.Option>
61+
<Select.Option value='vinson'>Mount Vinson</Select.Option>
62+
<Select.Option value='puncakjaya'>Puncak Jaya</Select.Option>
63+
<Select.Option value='kosciuszko'>Mount Kosciuszko</Select.Option>
6464
</Select>
6565
);
6666

@@ -69,20 +69,23 @@ WithError.args = {
6969
error: 'Du må velge et fjell',
7070
};
7171

72-
export const Multiple: StoryFn<typeof Select> = (args) => (
72+
export const WithOptgroup: StoryFn<typeof Select> = (args) => (
7373
<Select {...args}>
74-
<option value='everest'>Mount Everest</option>
75-
<option value='aconcagua'>Aconcagua</option>
76-
<option value='denali'>Denali</option>
77-
<option value='kilimanjaro'>Kilimanjaro</option>
78-
<option value='elbrus'>Elbrus</option>
79-
<option value='vinson'>Mount Vinson</option>
80-
<option value='puncakjaya'>Puncak Jaya</option>
81-
<option value='kosciuszko'>Mount Kosciuszko</option>
74+
<Select.Optgroup label='Gruppe 1'>
75+
<Select.Option value='everest'>Mount Everest</Select.Option>
76+
<Select.Option value='aconcagua'>Aconcagua</Select.Option>
77+
<Select.Option value='denali'>Denali</Select.Option>
78+
<Select.Option value='kilimanjaro'>Kilimanjaro</Select.Option>
79+
</Select.Optgroup>
80+
<Select.Optgroup label='Gruppe 2'>
81+
<Select.Option value='elbrus'>Elbrus</Select.Option>
82+
<Select.Option value='vinson'>Mount Vinson</Select.Option>
83+
<Select.Option value='puncakjaya'>Puncak Jaya</Select.Option>
84+
<Select.Option value='kosciuszko'>Mount Kosciuszko</Select.Option>
85+
</Select.Optgroup>
8286
</Select>
8387
);
8488

85-
Multiple.args = {
89+
WithOptgroup.args = {
8690
label: 'Velg fjell',
87-
multiple: true,
8891
};

packages/react/src/components/form/Select/Select.test.tsx

+12-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import userEvent from '@testing-library/user-event';
33
import type { RefObject } from 'react';
44
import { act, createRef } from 'react';
55

6-
import type { SelectProps } from './Select';
7-
import { Select } from './Select';
6+
import { Select, type SelectProps } from './';
87

98
const user = userEvent.setup();
109

@@ -15,9 +14,9 @@ const options: { label: string; value: string }[] = [
1514
{ label: 'Option 3', value: '3' },
1615
];
1716
const children = options.map(({ label, value }) => (
18-
<option key={value} value={value}>
17+
<Select.Option key={value} value={value}>
1918
{label}
20-
</option>
19+
</Select.Option>
2120
));
2221
const defaultProps: SelectProps = {
2322
children,
@@ -59,6 +58,15 @@ describe('Select', () => {
5958
}
6059
});
6160

61+
it('Renders with optgroup', () => {
62+
render({
63+
children: <Select.Optgroup label='Group'>{children}</Select.Optgroup>,
64+
});
65+
const optgroup = screen.getByRole('group');
66+
expect(optgroup).toBeInTheDocument();
67+
expect(optgroup.children.length).toBe(options.length);
68+
});
69+
6270
it('Lets the user select a value', async () => {
6371
render();
6472
const select = screen.getByRole('combobox');

packages/react/src/components/form/Select/Select.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export type SelectProps = {
3838
* @default 0
3939
*/
4040
htmlSize?: number;
41-
} & Omit<SelectHTMLAttributes<HTMLSelectElement>, 'size'>;
41+
} & Omit<SelectHTMLAttributes<HTMLSelectElement>, 'size' | 'multiple'>;
4242

4343
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
4444
function Select(props, ref: ForwardedRef<HTMLSelectElement>) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Slot } from '@radix-ui/react-slot';
2+
import { forwardRef } from 'react';
3+
import type { OptgroupHTMLAttributes } from 'react';
4+
5+
export type SelectOptgroupProps = {
6+
/**
7+
* Change the default rendered element for the one passed as a child, merging their props and behavior.
8+
* @default false
9+
*/
10+
asChild?: boolean;
11+
} & OptgroupHTMLAttributes<HTMLOptGroupElement>;
12+
13+
export const SelectOptgroup = forwardRef<
14+
HTMLOptGroupElement,
15+
SelectOptgroupProps
16+
>(function SelectOptgroup({ asChild, ...rest }, ref) {
17+
const Component = asChild ? Slot : 'optgroup';
18+
19+
return <Component {...rest} ref={ref} />;
20+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Slot } from '@radix-ui/react-slot';
2+
import { forwardRef } from 'react';
3+
import type { OptionHTMLAttributes } from 'react';
4+
5+
export type SelectOptionProps = {
6+
/**
7+
* Change the default rendered element for the one passed as a child, merging their props and behavior.
8+
* @default false
9+
*/
10+
asChild?: boolean;
11+
} & OptionHTMLAttributes<HTMLOptionElement>;
12+
13+
export const SelectOption = forwardRef<HTMLOptionElement, SelectOptionProps>(
14+
function SelectOption({ asChild, ...rest }, ref) {
15+
const Component = asChild ? Slot : 'option';
16+
17+
return <Component {...rest} ref={ref} />;
18+
},
19+
);
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,21 @@
1-
export { Select } from './Select';
1+
import { Select as SelectParent } from './Select';
2+
import { SelectOptgroup } from './SelectOptgroup';
3+
import { SelectOption } from './SelectOption';
4+
5+
type SelectComponent = typeof SelectParent & {
6+
Option: typeof SelectOption;
7+
Optgroup: typeof SelectOptgroup;
8+
};
9+
10+
const Select = SelectParent as SelectComponent;
11+
12+
Select.Option = SelectOption;
13+
Select.Optgroup = SelectOptgroup;
14+
15+
Select.Option.displayName = 'Select.Option';
16+
Select.Optgroup.displayName = 'Select.Optgroup';
17+
218
export type { SelectProps } from './Select';
19+
export type { SelectOptionProps } from './SelectOption';
20+
export type { SelectOptgroupProps } from './SelectOptgroup';
21+
export { Select, SelectOption, SelectOptgroup };

packages/react/stories/showcase.stories.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ export const Showcase: StoryFn = () => {
8787
<div className={classes.tableHeader}>
8888
<div className={classes.tableAction}>
8989
<Select label='' size='sm'>
90-
<option value='blank'>Velg handling</option>
91-
<option value='everest'>Dupliser</option>
92-
<option value='aconcagua'>Slett</option>
93-
<option value='denali'>Oppdater</option>
90+
<Select.Option value='blank'>Velg handling</Select.Option>
91+
<Select.Option value='everest'>Dupliser</Select.Option>
92+
<Select.Option value='aconcagua'>Slett</Select.Option>
93+
<Select.Option value='denali'>Oppdater</Select.Option>
9494
</Select>
9595
<Button className={classes.tableBtn} size='sm'>
9696
Utfør

packages/react/stories/testing.stories.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ export const MediumRow: StoryFn<{
5252
/>
5353

5454
<Select size={size}>
55-
<option>opt1</option>
56-
<option>opt2</option>
57-
<option>opt3</option>
55+
<Select.Option>opt1</Select.Option>
56+
<Select.Option>opt2</Select.Option>
57+
<Select.Option>opt3</Select.Option>
5858
</Select>
5959
<Button size={size}>Knapp</Button>
6060
<Combobox size={size}>

0 commit comments

Comments
 (0)