Skip to content

Commit 7dceadd

Browse files
authored
feat(dropdownmenu): streamline API (#2432)
part of #2221 I changed to using our `Popover`, which means we can handle testing state there. New API is this: ```tsx import { DropdownMenu } from '@digdir/designsystemet-react'; // med context <DropdownMenu.Context> <DropdownMenu.Trigger>Trigger</DropdownMenu.Trigger> <DropdownMenu> <DropdownMenu.Heading>Heading</DropdownMenu.Heading> <DropdownMenu.List> <DropdownMenu.Item>Item</DropdownMenu.Item> </DropdownMenu.List> </DropdownMenu> </DropdownMenu.Context> // uten context <Button popovertarget="my-dropdown">Trigger</Button> <Dropdown id="my-dropdown"> <DropdownMenu.Heading>Heading</DropdownMenu.Heading> <DropdownMenu.List> <DropdownMenu.Item>Item</DropdownMenu.Item> </DropdownMenu.List> </Dropdown> ```
1 parent 4b3b1bc commit 7dceadd

31 files changed

+578
-853
lines changed

.changeset/proud-walls-flash.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@digdir/designsystemet-css": patch
3+
"@digdir/designsystemet-react": patch
4+
---
5+
6+
DropdownMenu:
7+
- Rename from `DropdownMenu` to `Dropdown`
8+
- Change API and structure
9+
- Rename `.Root` to `.Context`
10+
- Rename `.Content` to `Dropdown`

apps/storefront/app/komponenter/component-list.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ export const data = [
5656
},
5757
{
5858
title: 'Dropdown Menu',
59-
image: 'DropdownMenu.svg',
60-
url: 'https://storybook.designsystemet.no/?path=/docs/komponenter-dropdownmenu--docs',
59+
image: 'Dropdown.svg',
60+
url: 'https://storybook.designsystemet.no/?path=/docs/komponenter-dropdown--docs',
6161
},
6262
{
6363
title: 'Error Summary',

apps/storefront/components/Tokens/TokenList/TokenList.tsx

+25-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22
import {
3-
DropdownMenu,
3+
Dropdown,
44
Heading,
55
Link,
66
Paragraph,
@@ -220,40 +220,40 @@ const TokenList = ({
220220
{(showThemePicker || showModeSwitcher) && (
221221
<div className={classes.toggleGroup}>
222222
{showThemePicker && (
223-
<DropdownMenu.Root>
224-
<DropdownMenu.Trigger variant='secondary'>
223+
<Dropdown.Context>
224+
<Dropdown.Trigger variant='secondary'>
225225
Brand: {capitalizeString(brand)}
226-
</DropdownMenu.Trigger>
227-
<DropdownMenu.Content>
228-
<DropdownMenu.Item onClick={() => setBrand('digdir')}>
226+
</Dropdown.Trigger>
227+
<Dropdown>
228+
<Dropdown.Item onClick={() => setBrand('digdir')}>
229229
Digdir
230-
</DropdownMenu.Item>
231-
<DropdownMenu.Item onClick={() => setBrand('altinn')}>
230+
</Dropdown.Item>
231+
<Dropdown.Item onClick={() => setBrand('altinn')}>
232232
Altinn
233-
</DropdownMenu.Item>
234-
<DropdownMenu.Item onClick={() => setBrand('tilsynet')}>
233+
</Dropdown.Item>
234+
<Dropdown.Item onClick={() => setBrand('tilsynet')}>
235235
Tilsynet
236-
</DropdownMenu.Item>
237-
<DropdownMenu.Item onClick={() => setBrand('portal')}>
236+
</Dropdown.Item>
237+
<Dropdown.Item onClick={() => setBrand('portal')}>
238238
Brreg
239-
</DropdownMenu.Item>
240-
</DropdownMenu.Content>
241-
</DropdownMenu.Root>
239+
</Dropdown.Item>
240+
</Dropdown>
241+
</Dropdown.Context>
242242
)}
243243
{showModeSwitcher && (
244-
<DropdownMenu.Root>
245-
<DropdownMenu.Trigger variant='secondary'>
244+
<Dropdown.Context>
245+
<Dropdown.Trigger variant='secondary'>
246246
Mode: {capitalizeString(mode)}
247-
</DropdownMenu.Trigger>
248-
<DropdownMenu.Content>
249-
<DropdownMenu.Item onClick={() => setMode('light')}>
247+
</Dropdown.Trigger>
248+
<Dropdown>
249+
<Dropdown.Item onClick={() => setMode('light')}>
250250
Light
251-
</DropdownMenu.Item>
252-
<DropdownMenu.Item onClick={() => setMode('dark')}>
251+
</Dropdown.Item>
252+
<Dropdown.Item onClick={() => setMode('dark')}>
253253
Dark
254-
</DropdownMenu.Item>
255-
</DropdownMenu.Content>
256-
</DropdownMenu.Root>
254+
</Dropdown.Item>
255+
</Dropdown>
256+
</Dropdown.Context>
257257
)}
258258
</div>
259259
)}

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

+10-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
Checkbox,
77
Chip,
88
Combobox,
9-
DropdownMenu,
9+
Dropdown,
1010
Fieldset,
1111
Heading,
1212
HelpText,
@@ -390,18 +390,18 @@ export const Components = () => {
390390
</Alert>
391391
</div>
392392
<div className={cl(classes.card, classes.dropdown)}>
393-
<DropdownMenu.Root placement='top'>
394-
<DropdownMenu.Trigger>Velg språk</DropdownMenu.Trigger>
395-
<DropdownMenu.Content>
396-
<DropdownMenu.Item>Norsk</DropdownMenu.Item>
397-
<DropdownMenu.Item>Engelsk</DropdownMenu.Item>
398-
<DropdownMenu.Item>Spansk</DropdownMenu.Item>
399-
<DropdownMenu.Item>Fransk</DropdownMenu.Item>
400-
</DropdownMenu.Content>
393+
<Dropdown.Context>
394+
<Dropdown.Trigger>Velg språk</Dropdown.Trigger>
395+
<Dropdown placement='top'>
396+
<Dropdown.Item>Norsk</Dropdown.Item>
397+
<Dropdown.Item>Engelsk</Dropdown.Item>
398+
<Dropdown.Item>Spansk</Dropdown.Item>
399+
<Dropdown.Item>Fransk</Dropdown.Item>
400+
</Dropdown>
401401
<HelpText title='Du har ikke valgt språk'>
402402
Velg språk for å endre innholdet på siden
403403
</HelpText>
404-
</DropdownMenu.Root>
404+
</Dropdown.Context>
405405
</div>
406406
<div className={cl(classes.card, classes.loaders)}>
407407
<div className={classes.loadersRest}>

packages/css/dropdown.css

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
.ds-dropdown {
2+
--dsc-dropdown-padding: var(--ds-spacing-3) var(--ds-spacing-2);
3+
--dsc-dropdown-min-width: 16rem;
4+
--dsc-dropdown-item-padding: 0 var(--ds-spacing-4);
5+
--dsc-dropdown-header-padding: var(--ds-spacing-2) var(--ds-spacing-4);
6+
7+
padding: var(--dsc-dropdown-padding);
8+
list-style: none;
9+
border-radius: min(1rem, var(--ds-border-radius-md));
10+
box-shadow: var(--ds-shadow-md);
11+
background-color: var(--ds-color-neutral-background-default);
12+
border: 1px solid var(--ds-color-neutral-border-subtle);
13+
min-width: var(--dsc-dropdown-min-width);
14+
15+
/* Remove popover arrow */
16+
&::before {
17+
display: none;
18+
}
19+
20+
&[data-size='sm'] {
21+
--dsc-dropdown-padding: var(--ds-spacing-2);
22+
--dsc-dropdown-min-width: 15rem;
23+
}
24+
25+
&[data-size='lg'] {
26+
--dsc-dropdown-padding: var(--ds-spacing-4) var(--ds-spacing-2);
27+
--dsc-dropdown-min-width: 18rem;
28+
}
29+
30+
& :is(a, button, [role='button']) {
31+
justify-content: start;
32+
padding: var(--dsc-dropdown-item-padding);
33+
width: 100%;
34+
}
35+
36+
.ds-dropdown__list {
37+
margin: 0;
38+
padding: 0;
39+
list-style: none;
40+
}
41+
42+
.ds-dropdown__heading {
43+
padding: var(--dsc-dropdown-header-padding);
44+
}
45+
}

packages/css/dropdownmenu.css

-43
This file was deleted.

packages/css/index.css

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@import url('./utilities.css') layer(ds.utilities);
1313
@import url('./button.css') layer(ds.components);
1414
@import url('./alert.css') layer(ds.components);
15+
@import url('./popover.css') layer(ds.components);
1516
@import url('./skiplink.css') layer(ds.components);
1617
@import url('./accordion.css') layer(ds.components);
1718
@import url('./switch.css') layer(ds.components);
@@ -27,12 +28,11 @@
2728
@import url('./card.css') layer(ds.components);
2829
@import url('./link.css') layer(ds.components);
2930
@import url('./fieldset.css') layer(ds.components);
30-
@import url('./dropdownmenu.css') layer(ds.components);
31+
@import url('./dropdown.css') layer(ds.components);
3132
@import url('./chip') layer(ds.components);
3233
@import url('./divider.css') layer(ds.components);
3334
@import url('./tabs.css') layer(ds.components);
3435
@import url('./pagination.css') layer(ds.components);
35-
@import url('./popover.css') layer(ds.components);
3636
@import url('./skeleton.css') layer(ds.components);
3737
@import url('./tag.css') layer(ds.components);
3838
@import url('./error-summary.css') layer(ds.components);

packages/react/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
529529

530530
### Bug Fixes
531531

532-
- **DropDownMenuItem:** add list style none ([#1190](https://github.com/digdir/designsystemet/issues/1190)) ([11bd19b](https://github.com/digdir/designsystemet/commit/11bd19bfb6ac76b2c697a22e876117c4128be3bd))
532+
- **DropdownMenuItem:** add list style none ([#1190](https://github.com/digdir/designsystemet/issues/1190)) ([11bd19b](https://github.com/digdir/designsystemet/commit/11bd19bfb6ac76b2c697a22e876117c4128be3bd))
533533
- **List:** Wrap in `div` to allow access to `Heading` ([#1217](https://github.com/digdir/designsystemet/issues/1217)) ([afcadb7](https://github.com/digdir/designsystemet/commit/afcadb7c4cb4b368d247af0c41ed8debf53c4b66))
534534
- **Pagination:** Only use needed space for buttons ([#1220](https://github.com/digdir/designsystemet/issues/1220)) ([4bf3d74](https://github.com/digdir/designsystemet/commit/4bf3d745888f500259df5aadf4edee97ec4f95bc))
535535
- **Select:** Select not working properly in Modal ([#1195](https://github.com/digdir/designsystemet/issues/1195)) ([fb8be6a](https://github.com/digdir/designsystemet/commit/fb8be6a647ba0da8b5b23e65813508f34e09c8c1))

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

+15-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Meta, StoryFn } from '@storybook/react';
33

44
import { BriefcaseIcon } from '@navikt/aksel-icons';
55
import { Avatar } from '.';
6-
import { Badge, DropdownMenu } from '../';
6+
import { Badge, Dropdown } from '../';
77

88
type Story = StoryFn<typeof Avatar>;
99

@@ -93,33 +93,34 @@ export const WithImage: Story = () => (
9393
</Avatar>
9494
);
9595

96-
export const InDropdownMenu: Story = () => (
97-
<DropdownMenu.Root placement='bottom-end' size='md' portal>
98-
<DropdownMenu.Trigger variant='tertiary'>
96+
export const InDropdown: Story = () => (
97+
<Dropdown.Context>
98+
<Dropdown.Trigger variant='tertiary'>
9999
<Avatar aria-label='Ola Nordmann' size='sm'>
100100
ON
101101
</Avatar>
102102
Velg Profil
103-
</DropdownMenu.Trigger>
104-
<DropdownMenu.Content>
105-
<DropdownMenu.Group heading='Alle kontoer'>
106-
<DropdownMenu.Item>
103+
</Dropdown.Trigger>
104+
<Dropdown placement='bottom-end' size='md'>
105+
<Dropdown.Heading>Alle kontoer</Dropdown.Heading>
106+
<Dropdown.List>
107+
<Dropdown.Item>
107108
<Badge overlap='circle' color='danger' size='sm'>
108109
<Avatar aria-label='Ola Nordmann' size='xs'>
109110
ON
110111
</Avatar>
111112
</Badge>
112113
Ola Nordmann
113-
</DropdownMenu.Item>
114-
<DropdownMenu.Item>
114+
</Dropdown.Item>
115+
<Dropdown.Item>
115116
<Avatar size='xs' color='brand1' aria-label='Sogndal Kommune'>
116117
<BriefcaseIcon fontSize='5em' />
117118
</Avatar>
118119
Sogndal kommune
119-
</DropdownMenu.Item>
120-
</DropdownMenu.Group>
121-
</DropdownMenu.Content>
122-
</DropdownMenu.Root>
120+
</Dropdown.Item>
121+
</Dropdown.List>
122+
</Dropdown>
123+
</Dropdown.Context>
123124
);
124125

125126
export const AsLink: Story = () => (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Meta, Canvas, Controls, Primary, ArgTypes } from '@storybook/blocks';
2+
3+
import * as DropdownStories from './Dropdown.stories';
4+
5+
import { Dropdown } from './';
6+
7+
<Meta of={DropdownStories} />
8+
9+
# Dropdown
10+
11+
<Primary />
12+
<Controls />
13+
14+
## Slik bruker du `Dropdown`
15+
16+
```tsx
17+
import { Dropdown } from '@digdir/designsystemet-react';
18+
19+
// med context
20+
<Dropdown.Context>
21+
<Dropdown.Trigger>Trigger</Dropdown.Trigger>
22+
<Dropdown>
23+
<Dropdown.Heading>Heading</Dropdown.Heading>
24+
<Dropdown.List>
25+
<Dropdown.Item>Item</Dropdown.Item>
26+
</Dropdown.List>
27+
</Dropdown>
28+
</Dropdown.Context>
29+
30+
// uten context
31+
<Button popovertarget="my-dropdown">Trigger</Button>
32+
<Dropdown id="my-dropdown">
33+
<Dropdown.Heading>Heading</Dropdown.Heading>
34+
<Dropdown.List>
35+
<Dropdown.Item>Item</Dropdown.Item>
36+
</Dropdown.List>
37+
</Dropdown>
38+
```
39+
40+
## Eksempler på bruk
41+
42+
### Kontrollert
43+
44+
Dersom du sender inn `open`, så bruker du `Dropdown` kontrollert. Du kan bruke `onClose` for å få beskjed når `Dropdown` vil lukkes.
45+
46+
<Canvas of={DropdownStories.Controlled} />
47+
48+
### Ikoner
49+
50+
Du kan legge ikon rett inn i `Dropdown.Item`, dersom det blir mye mellomrom til kanten kan du legge på din egen klasse og endre på `padding`.
51+
52+
<Canvas of={DropdownStories.Icons} />
53+
54+
### Uten Trigger
55+
56+
`Dropdown` bruker popover APIet, så du kan bruke `Dropdown` uten `Dropdown.Trigger`.
57+
Du må da legge til `popovertarget={id}``Dropdown`, og `id``Dropdown`.
58+
59+
<Canvas of={DropdownStories.WithoutTrigger} />
60+
61+
## Tilgjengelighet
62+
63+
Det er innebygd tilgjengelighet i `Dropdown.Trigger` med `aria-expanded={true/false}` i henhold til åpne/lukket tilstand, og `aria-haspopup='menu'`.
64+
65+
### `Dropdown.List`
66+
67+
<ArgTypes of={Dropdown.List} />
68+
69+
### `Dropdown.Trigger`
70+
71+
Triggeren er en [Button](/docs/komponenter-button--docs) som standard.
72+
73+
Bruk `Dropdown.Trigger` til å aktivere `Dropdown`. Du kan bruke `asChild` for å endre `Dropdown.Trigger` elementet.
74+
Dersom du skal legge på funksjoner som `onClick`, legg det på ditt element, og legg `asChild``Dropdown.Trigger`.
75+
76+
### Referanser
77+
78+
Vi bruker `ul` og `li` tags i dropdownen, valget er basert på denne:
79+
80+
- https://www.w3.org/WAI/tutorials/menus/flyout/#flyoutnavkbbtn
81+
- https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/../Dropdown.stories.

0 commit comments

Comments
 (0)