Skip to content

Commit 595a75b

Browse files
authored
fix(Accordion): implement css adjustments and native details (#2363)
- Fixes #2100 🥳 - Deprecates #2176, #2190 - Built on top of `<u-details>` for [better accessibility on mobile](https://u-elements.github.io/u-elements/elements/u-details#accessibility) - Removes `level` from `AccordionHeader` as this is not supported by native `<details>` - Removes `onHeaderClick` from `AccordionHeader` as this is identical to adding a `onClick` handler - JS-based animation can be removed and replaced by CSS when `calc-size(auto)` is fully supported 🚀 - Fixes shrinking chevron on mobile/zoom - Fixes text-align in AccordionHeader on mobile/zoom - Follows: #2295 - Also works in `dir="rtl"` Question: It is now implemented so search-in-page only works when using `defaultOpen`, as a controlled `open` should not be affected by user interaction. Just checking - does this make sense to you guys as well? :)
1 parent ec741bc commit 595a75b

File tree

15 files changed

+371
-368
lines changed

15 files changed

+371
-368
lines changed

apps/storefront/app/monstre/feilmeldinger/page.mdx

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
CardContent,
44
Heading,
55
Paragraph,
6-
AccordionRoot,
6+
Accordion,
77
AccordionItem,
88
AccordionContent,
99
AccordionHeading
@@ -183,11 +183,11 @@ Her må vi gjøre det så tydelig som mulig for brukeren at flere felt påvirker
183183

184184
I dette eksempelet har vi en gruppe med felt, der brukeren ikke nødvendigvis har alle opplysningene, men må fylle ut minst ett felt.
185185

186-
<AccordionRoot
186+
<Accordion
187187
color='neutral'
188188
>
189189
<AccordionItem>
190-
<AccordionHeading level={3}>Eksempel på feilmelding som gjelder flere felt</AccordionHeading>
190+
<AccordionHeading>Eksempel på feilmelding som gjelder flere felt</AccordionHeading>
191191
<AccordionContent
192192
style={{
193193
paddingTop: '0px',
@@ -216,7 +216,7 @@ I dette eksempelet har vi en gruppe med felt, der brukeren ikke nødvendigvis ha
216216
/>
217217
</AccordionContent>
218218
</AccordionItem>
219-
</AccordionRoot>
219+
</Accordion>
220220

221221
## Kode
222222

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,9 @@ export const Components = () => {
339339
<Heading size='xs' className={classes.cardTitle}>
340340
Ofte stillte spørsmål
341341
</Heading>
342-
<Accordion.Root color='brand3' border className={classes.accordion}>
342+
<Accordion color='brand3' border className={classes.accordion}>
343343
<Accordion.Item>
344-
<Accordion.Heading level={3}>
344+
<Accordion.Heading>
345345
Hvem kan registrere seg i Frivillighetsregisteret?
346346
</Accordion.Heading>
347347
<Accordion.Content>
@@ -353,7 +353,7 @@ export const Components = () => {
353353
</Accordion.Content>
354354
</Accordion.Item>
355355
<Accordion.Item>
356-
<Accordion.Heading level={3}>
356+
<Accordion.Heading>
357357
Hvordan går jeg fram for å registrere i Frivillighetsregisteret?
358358
</Accordion.Heading>
359359
<Accordion.Content>
@@ -363,7 +363,7 @@ export const Components = () => {
363363
</Accordion.Content>
364364
</Accordion.Item>
365365
<Accordion.Item>
366-
<Accordion.Heading level={3}>
366+
<Accordion.Heading>
367367
Hvordan går jeg fram for å registrere i Frivillighetsregisteret?
368368
</Accordion.Heading>
369369
<Accordion.Content>
@@ -372,7 +372,7 @@ export const Components = () => {
372372
registrene samtidig i Samordnet registermelding.
373373
</Accordion.Content>
374374
</Accordion.Item>
375-
</Accordion.Root>
375+
</Accordion>
376376
</div>
377377
<div className={cl(classes.card, classes.alert)}>
378378
<Alert color='info'>

packages/css/accordion.css

+104-124
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,119 @@
1-
.ds-accordion {
2-
--dsc-accordion-border-radius: min(1rem, var(--ds-border-radius-md));
3-
--dsc-accordion-border-color: var(--ds-color-neutral-border-subtle);
1+
.ds-accordion-group {
42
--dsc-accordion-background: var(--ds-color-neutral-background-default);
5-
--dsc-accordion-button-background: var(--ds-color-neutral-background-default);
6-
--dsc-accordion-button-background-open: var(--ds-color-neutral-background-subtle);
7-
--dsc-accordion-icon-background-hover: var(--ds-color-neutral-surface-default);
8-
--dsc-accordion-icon-background-active: var(--ds-color-neutral-surface-default);
9-
10-
border-bottom: 1px solid var(--dsc-accordion-border-color);
11-
box-sizing: border-box;
12-
background-color: var(--dsc-accordion-background);
13-
}
14-
15-
.ds-accordion--border {
16-
border: 1px solid var(--dsc-accordion-border-color);
17-
border-radius: var(--dsc-accordion-border-radius);
18-
}
19-
20-
.ds-accordion__expand-icon {
21-
border-radius: var(--ds-border-radius-md);
22-
color: var(--ds-color-neutral-text-default);
23-
}
24-
25-
.ds-accordion__content {
26-
padding: var(--ds-spacing-5, 1rem);
27-
overflow: hidden;
28-
text-overflow: ellipsis;
29-
}
30-
31-
.ds-accordion__heading {
32-
margin: 0;
33-
width: 100%;
34-
display: flex;
35-
justify-content: flex-start;
36-
align-items: center;
37-
gap: var(--ds-spacing-2);
38-
text-align: left;
39-
border: none;
40-
border-top: 1px solid var(--dsc-accordion-border-color);
41-
background-color: var(--dsc-accordion-button-background);
42-
}
43-
44-
.ds-accordion__button {
45-
cursor: pointer;
46-
width: 100%;
47-
display: flex;
48-
justify-content: flex-start;
49-
align-items: center;
50-
gap: var(--ds-spacing-2);
51-
margin: 0;
52-
padding: var(--ds-spacing-4);
53-
background-color: transparent;
54-
border: none;
55-
font-family: inherit;
56-
}
57-
58-
.ds-accordion__item--open .ds-accordion__heading {
59-
background-color: var(--dsc-accordion-button-background-open);
60-
}
61-
62-
.ds-accordion__item:focus-within {
63-
position: relative;
64-
}
65-
66-
.ds-accordion__item:where(.ds-accordion__item--open) .ds-accordion__expand-icon {
67-
transform: rotateZ(180deg);
68-
}
3+
--dsc-accordion-border-radius: min(1rem, var(--ds-border-radius-md));
4+
--dsc-accordion-border: 1px solid var(--ds-color-neutral-border-subtle);
5+
--dsc-accordion-chevron-gap: var(--ds-spacing-2);
6+
--dsc-accordion-chevron-size: var(--ds-spacing-6);
7+
--dsc-accordion-chevron-rotate: 0deg;
8+
--dsc-accordion-heading-background--hover: var(--ds-color-neutral-surface-default);
9+
--dsc-accordion-heading-background--open: var(--ds-color-neutral-background-subtle);
10+
--dsc-accordion-heading-background: var(--ds-color-neutral-background-default);
11+
--dsc-accordion-padding: var(--ds-spacing-4);
12+
13+
&[data-border] > * {
14+
border: var(--dsc-accordion-border);
15+
16+
&:first-child,
17+
&:first-child > :is(summary, u-summary) {
18+
border-top-left-radius: var(--dsc-accordion-border-radius);
19+
border-top-right-radius: var(--dsc-accordion-border-radius);
20+
}
21+
22+
&:last-child,
23+
&:last-child:not([open]) > :is(summary, u-summary) {
24+
border-bottom-left-radius: var(--dsc-accordion-border-radius);
25+
border-bottom-right-radius: var(--dsc-accordion-border-radius);
26+
}
27+
}
6928

70-
.ds-accordion__item:not(:first-child) .ds-accordion__heading {
71-
border-top: 1px solid var(--dsc-accordion-border-color);
72-
}
29+
&[data-color='subtle'] {
30+
--dsc-accordion-background: var(--ds-color-neutral-background-subtle);
31+
--dsc-accordion-border: 1px solid var(--ds-color-neutral-border-subtle);
32+
--dsc-accordion-heading-background--hover: var(--ds-color-neutral-surface-hover);
33+
--dsc-accordion-heading-background--open: var(--ds-color-neutral-surface-default);
34+
--dsc-accordion-heading-background: var(--ds-color-neutral-background-subtle);
35+
}
7336

74-
.ds-accordion--border .ds-accordion__item:first-child .ds-accordion__heading {
75-
border-top: 0;
76-
}
37+
&[data-color='brand1'] {
38+
--dsc-accordion-background: var(--ds-color-brand1-background-subtle);
39+
--dsc-accordion-border: 1px solid var(--ds-color-brand1-border-subtle);
40+
--dsc-accordion-heading-background--hover: var(--ds-color-brand1-surface-hover);
41+
--dsc-accordion-heading-background--open: var(--ds-color-brand1-surface-default);
42+
--dsc-accordion-heading-background: var(--ds-color-brand1-surface-default);
43+
}
7744

78-
.ds-accordion--border .ds-accordion__item:first-of-type .ds-accordion__heading:first-of-type {
79-
border-top-left-radius: var(--dsc-accordion-border-radius);
80-
border-top-right-radius: var(--dsc-accordion-border-radius);
81-
}
45+
&[data-color='brand2'] {
46+
--dsc-accordion-background: var(--ds-color-brand2-background-subtle);
47+
--dsc-accordion-border: 1px solid var(--ds-color-brand2-border-subtle);
48+
--dsc-accordion-heading-background--hover: var(--ds-color-brand2-surface-hover);
49+
--dsc-accordion-heading-background--open: var(--ds-color-brand2-surface-default);
50+
--dsc-accordion-heading-background: var(--ds-color-brand2-surface-default);
51+
}
8252

83-
.ds-accordion--border .ds-accordion__item:last-of-type:not(.ds-accordion__item--open) .ds-accordion__heading:first-of-type {
84-
border-bottom-left-radius: var(--dsc-accordion-border-radius);
85-
border-bottom-right-radius: var(--dsc-accordion-border-radius);
53+
&[data-color='brand3'] {
54+
--dsc-accordion-background: var(--ds-color-brand3-background-subtle);
55+
--dsc-accordion-border: 1px solid var(--ds-color-brand3-border-subtle);
56+
--dsc-accordion-heading-background--hover: var(--ds-color-brand3-surface-hover);
57+
--dsc-accordion-heading-background--open: var(--ds-color-brand3-surface-default);
58+
--dsc-accordion-heading-background: var(--ds-color-brand3-surface-default);
59+
}
8660
}
8761

88-
@media (hover: hover) and (pointer: fine) {
89-
.ds-accordion__heading:hover .ds-accordion__expand-icon {
90-
background-color: var(--dsc-accordion-icon-background-hover);
62+
.ds-accordion__item {
63+
background: var(--dsc-accordion-background);
64+
border-block: var(--dsc-accordion-border);
65+
66+
& > :is(summary, u-summary) {
67+
background: var(--dsc-accordion-heading-background);
68+
box-sizing: border-box;
69+
cursor: pointer;
70+
list-style: none;
71+
outline: none;
72+
padding-block: var(--dsc-accordion-padding);
73+
padding-inline: calc(var(--dsc-accordion-padding) + var(--dsc-accordion-chevron-size) + var(--dsc-accordion-chevron-gap)) var(--dsc-accordion-padding);
74+
75+
&:focus-visible {
76+
position: relative; /* Ensure foucs outline renders on top */
77+
}
78+
79+
&::before {
80+
background: currentcolor;
81+
border-radius: var(--ds-border-radius-md);
82+
content: '';
83+
height: var(--dsc-accordion-chevron-size);
84+
mask: 50% / contain no-repeat
85+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5.97 9.47a.75.75 0 0 1 1.06 0L12 14.44l4.97-4.97a.75.75 0 1 1 1.06 1.06l-5.5 5.5a.75.75 0 0 1-1.06 0l-5.5-5.5a.75.75 0 0 1 0-1.06'/%3E%3C/svg%3E");
86+
position: absolute;
87+
rotate: var(--dsc-accordion-chevron-rotate);
88+
margin-inline: calc(
89+
(var(--dsc-accordion-chevron-size) + var(--dsc-accordion-chevron-gap)) * -1
90+
); /* Using margin instead of top/left to avoid position: relative and to support dir="rtl" */
91+
92+
width: var(--dsc-accordion-chevron-size);
93+
}
9194
}
9295

93-
.ds-accordion__item--open .ds-accordion__heading:hover .ds-accordion__expand-icon {
94-
background-color: var(--dsc-accordion-icon-background-active);
96+
& + & {
97+
border-top: 0; /* Skip border-top when .accordion__item is followed by .accordion__item */
9598
}
96-
}
9799

98-
.ds-accordion--neutral {
99-
--dsc-accordion-background: var(--ds-color-neutral-background-default);
100-
--dsc-accordion-button-background: var(--ds-color-neutral-background-default);
101-
--dsc-accordion-button-background-open: var(--ds-color-neutral-background-subtle);
102-
--dsc-accordion-icon-background-hover: var(--ds-color-neutral-surface-default);
103-
}
104-
105-
.ds-accordion--subtle {
106-
--dsc-accordion-background: var(--ds-color-neutral-background-subtle);
107-
--dsc-accordion-border-color: var(--ds-color-neutral-border-default);
108-
--dsc-accordion-button-background: var(--ds-color-neutral-background-subtle);
109-
--dsc-accordion-button-background-open: var(--ds-color-neutral-surface-default);
110-
--dsc-accordion-icon-background-hover: var(--ds-color-neutral-surface-default);
111-
--dsc-accordion-icon-background-active: var(--ds-color-neutral-surface-active);
112-
}
100+
& > :not(summary, u-summary) {
101+
border-radius: inherit;
102+
padding: var(--ds-spacing-5, 1rem);
103+
}
113104

114-
.ds-accordion--brand1 {
115-
--dsc-accordion-background: var(--ds-color-brand1-background-subtle);
116-
--dsc-accordion-border-color: var(--ds-color-brand1-border-subtle);
117-
--dsc-accordion-button-background: var(--ds-color-brand1-surface-default);
118-
--dsc-accordion-button-background-open: var(--ds-color-brand1-surface-hover);
119-
--dsc-accordion-icon-background-hover: var(--ds-color-brand1-surface-active);
120-
--dsc-accordion-icon-background-active: var(--ds-color-brand1-surface-active);
121-
}
105+
&[open] > :is(summary, u-summary) {
106+
background: var(--dsc-accordion-heading-background--open);
107+
}
122108

123-
.ds-accordion--brand2 {
124-
--dsc-accordion-background: var(--ds-color-brand2-background-subtle);
125-
--dsc-accordion-border-color: var(--ds-color-brand2-border-subtle);
126-
--dsc-accordion-button-background: var(--ds-color-brand2-surface-default);
127-
--dsc-accordion-button-background-open: var(--ds-color-brand2-surface-hover);
128-
--dsc-accordion-icon-background-hover: var(--ds-color-brand2-surface-active);
129-
--dsc-accordion-icon-background-active: var(--ds-color-brand2-surface-active);
130-
}
109+
/* Make flip on click */
110+
&[open]:not([data-chevron-open='false']) {
111+
--dsc-accordion-chevron-rotate: -180deg;
112+
}
131113

132-
.ds-accordion--brand3 {
133-
--dsc-accordion-background: var(--ds-color-brand3-background-subtle);
134-
--dsc-accordion-border-color: var(--ds-color-brand3-border-subtle);
135-
--dsc-accordion-button-background: var(--ds-color-brand3-surface-default);
136-
--dsc-accordion-button-background-open: var(--ds-color-brand3-surface-hover);
137-
--dsc-accordion-icon-background-hover: var(--ds-color-brand3-surface-active);
138-
--dsc-accordion-icon-background-active: var(--ds-color-brand3-surface-active);
114+
@media (hover: hover) and (pointer: fine) {
115+
& > :is(summary, u-summary):hover {
116+
background: var(--dsc-accordion-heading-background--hover);
117+
}
118+
}
139119
}

packages/react/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"@floating-ui/react": "0.26.23",
4040
"@navikt/aksel-icons": "^6.14.0",
4141
"@radix-ui/react-slot": "^1.1.0",
42-
"@tanstack/react-virtual": "^3.10.7"
42+
"@tanstack/react-virtual": "^3.10.7",
43+
"@u-elements/u-details": "^0.0.5"
4344
},
4445
"devDependencies": {
4546
"@rollup/plugin-commonjs": "^26.0.1",

packages/react/src/components/Accordion/Accordion.mdx

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Med `Accordion` kan du presentere mye innhold på liten plass i en eller flere r
2525
```tsx
2626
import { Accordion } from '@digdir/designsystemet-react';
2727

28-
<Accordion.Root>
28+
<Accordion>
2929
<Accordion.Item>
3030
<Accordion.Heading>Accordion heading text</Accordion.Heading>
3131
<Accordion.Content>Accordion content</Accordion.Content>
@@ -34,7 +34,7 @@ import { Accordion } from '@digdir/designsystemet-react';
3434
<Accordion.Heading>Accordion heading text</Accordion.Heading>
3535
<Accordion.Content>Accordion content</Accordion.Content>
3636
</Accordion.Item>
37-
</Accordion.Root>;
37+
</Accordion>;
3838
```
3939

4040
## Eksempler

0 commit comments

Comments
 (0)