Skip to content

Commit 2af4f78

Browse files
Jeffrey Lauwersclaude
andcommitted
feat(Drawer): zijpaneel dat vanuit links of rechts de viewport inschuift
Sluit issue #115. Implementeert het Drawer component als native <dialog>-element met modal (showModal) en non-modal (.show) varianten, slide-animatie via @starting-style, scroll-affordance body en Storybook controls voor side en modal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 22a9d34 commit 2af4f78

11 files changed

Lines changed: 1400 additions & 0 deletions

File tree

.claude/commands/new-component-issue.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ Het [ComponentName] component wordt gebruikt voor:
183183

184184
- [ ] `[ComponentName]` component met [props opsomming]
185185
- [ ] `React.forwardRef<HTML[Element]Element>`
186+
- [ ] `index.ts` barrel file aangemaakt in de componentdirectory (exporteert component + prop types)
186187
- [ ] Export toegevoegd aan `packages/components-react/src/index.ts`
187188

188189
### Storybook
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/* ===== Drawer ===== */
2+
3+
/*
4+
* dsn-drawer — Zijpaneel dat vanuit links of rechts de viewport inschuift
5+
*
6+
* Gebaseerd op het native <dialog> element.
7+
* Modaal (modal prop): openen via .showModal() — natieve focus-trap + ::backdrop.
8+
* Non-modaal: openen via .show() — achtergrond blijft interactief.
9+
*
10+
* HTML-structuur:
11+
*
12+
* <dialog class="dsn-drawer dsn-drawer--side-right" aria-labelledby="drawer-title">
13+
* <div class="dsn-drawer__header">
14+
* <h2 class="dsn-drawer-heading" id="drawer-title">Titel</h2>
15+
* <button type="button" class="dsn-button dsn-button--subtle dsn-button--size-small dsn-button--icon-only">
16+
* <svg class="dsn-icon" aria-hidden="true"><!-- x --></svg>
17+
* <span class="dsn-button__label">Sluiten</span>
18+
* </button>
19+
* </div>
20+
* <div class="dsn-drawer__body">
21+
* <!-- inhoud -->
22+
* </div>
23+
* <div class="dsn-drawer__footer">
24+
* <div class="dsn-action-group">
25+
* <!-- knoppen -->
26+
* </div>
27+
* </div>
28+
* </dialog>
29+
*/
30+
31+
.dsn-drawer {
32+
/* Reset native <dialog> styling */
33+
border: none;
34+
box-shadow: var(--dsn-drawer-box-shadow);
35+
background: var(--dsn-drawer-background);
36+
padding: 0;
37+
38+
/* Grootte */
39+
inline-size: min(
40+
var(--dsn-drawer-max-width),
41+
calc(100svw - var(--dsn-drawer-min-gap))
42+
);
43+
block-size: 100svh;
44+
max-block-size: 100svh;
45+
46+
/* Positionering — override UA's auto-centrering voor zowel modal als non-modal */
47+
position: fixed;
48+
inset-block: 0;
49+
margin-block: 0;
50+
51+
/* Altijd flex-direction column, ook tijdens sluitanimatie */
52+
flex-direction: column;
53+
54+
/* Animatietransitie (ook voor sluitanimatie) */
55+
opacity: 1;
56+
transform: translateX(0);
57+
transition:
58+
opacity var(--dsn-transition-duration-normal)
59+
var(--dsn-transition-easing-default),
60+
transform var(--dsn-transition-duration-normal)
61+
var(--dsn-transition-easing-default),
62+
display var(--dsn-transition-duration-normal)
63+
var(--dsn-transition-easing-default) allow-discrete,
64+
overlay var(--dsn-transition-duration-normal)
65+
var(--dsn-transition-easing-default) allow-discrete;
66+
}
67+
68+
/*
69+
* Flexbox layout alleen bij open staat.
70+
* Gebruik [open] zodat de UA-stylesheet's display:none van kracht blijft
71+
* wanneer de drawer gesloten is — anders overschrijft display:flex de UA.
72+
*/
73+
.dsn-drawer[open] {
74+
display: flex;
75+
}
76+
77+
/* Animatie uitschakelen bij prefers-reduced-motion */
78+
@media (prefers-reduced-motion: reduce) {
79+
.dsn-drawer {
80+
transition: none;
81+
}
82+
}
83+
84+
/* ===== Side: Right (standaard) ===== */
85+
86+
.dsn-drawer--side-right {
87+
right: 0;
88+
left: auto;
89+
90+
/* Alleen de rand aan de kant van de pagina (links) */
91+
border-inline-start: var(--dsn-drawer-border-width) solid
92+
var(--dsn-drawer-border-color);
93+
}
94+
95+
/* Openingsanimatie rechts: start buiten beeld rechts */
96+
@starting-style {
97+
.dsn-drawer--side-right[open] {
98+
opacity: 0;
99+
transform: translateX(100%);
100+
}
101+
}
102+
103+
/* Native backdrop (alleen bij .showModal()) */
104+
.dsn-drawer--side-right::backdrop {
105+
background: var(--dsn-backdrop-background, rgba(0, 0, 0, 0.5));
106+
backdrop-filter: var(--dsn-backdrop-blur, blur(2px));
107+
opacity: 1;
108+
transition:
109+
opacity var(--dsn-transition-duration-normal)
110+
var(--dsn-transition-easing-default),
111+
display var(--dsn-transition-duration-normal)
112+
var(--dsn-transition-easing-default) allow-discrete,
113+
overlay var(--dsn-transition-duration-normal)
114+
var(--dsn-transition-easing-default) allow-discrete;
115+
}
116+
117+
@starting-style {
118+
.dsn-drawer--side-right[open]::backdrop {
119+
opacity: 0;
120+
}
121+
}
122+
123+
@media (prefers-reduced-motion: reduce) {
124+
.dsn-drawer--side-right::backdrop {
125+
transition: none;
126+
}
127+
}
128+
129+
/* ===== Side: Left ===== */
130+
131+
.dsn-drawer--side-left {
132+
left: 0;
133+
right: auto;
134+
135+
/* Alleen de rand aan de kant van de pagina (rechts) */
136+
border-inline-end: var(--dsn-drawer-border-width) solid
137+
var(--dsn-drawer-border-color);
138+
}
139+
140+
/* Openingsanimatie links: start buiten beeld links */
141+
@starting-style {
142+
.dsn-drawer--side-left[open] {
143+
opacity: 0;
144+
transform: translateX(-100%);
145+
}
146+
}
147+
148+
/* Native backdrop (alleen bij .showModal()) */
149+
.dsn-drawer--side-left::backdrop {
150+
background: var(--dsn-backdrop-background, rgba(0, 0, 0, 0.5));
151+
backdrop-filter: var(--dsn-backdrop-blur, blur(2px));
152+
opacity: 1;
153+
transition:
154+
opacity var(--dsn-transition-duration-normal)
155+
var(--dsn-transition-easing-default),
156+
display var(--dsn-transition-duration-normal)
157+
var(--dsn-transition-easing-default) allow-discrete,
158+
overlay var(--dsn-transition-duration-normal)
159+
var(--dsn-transition-easing-default) allow-discrete;
160+
}
161+
162+
@starting-style {
163+
.dsn-drawer--side-left[open]::backdrop {
164+
opacity: 0;
165+
}
166+
}
167+
168+
@media (prefers-reduced-motion: reduce) {
169+
.dsn-drawer--side-left::backdrop {
170+
transition: none;
171+
}
172+
}
173+
174+
/* ===== Header ===== */
175+
176+
.dsn-drawer__header {
177+
display: flex;
178+
align-items: center;
179+
gap: var(--dsn-space-inline-xl);
180+
flex-shrink: 0;
181+
182+
padding-block-start: var(--dsn-drawer-header-padding-block-start);
183+
padding-block-end: var(--dsn-drawer-header-padding-block-end);
184+
padding-inline: var(--dsn-drawer-header-padding-inline);
185+
186+
/* Scheidingslijn onder de header */
187+
border-block-end: var(--dsn-drawer-border-width) solid
188+
var(--dsn-drawer-border-color);
189+
}
190+
191+
/* ===== Heading ===== */
192+
193+
.dsn-drawer-heading {
194+
flex: 1;
195+
margin: 0;
196+
197+
font-family: var(--dsn-drawer-heading-font-family);
198+
font-weight: var(--dsn-drawer-heading-font-weight);
199+
font-size: var(--dsn-drawer-heading-font-size);
200+
line-height: var(--dsn-drawer-heading-line-height);
201+
color: var(--dsn-drawer-heading-color);
202+
}
203+
204+
/* ===== Body met scroll-affordance ===== */
205+
/*
206+
* Scroll-affordance schaduw (Lea Verou techniek — verticale variant):
207+
* - Dekkende overlagen (background-attachment: local) scrollen mee met de content
208+
* en verbergen de schaduwen wanneer je aan het begin of einde bent.
209+
* - Schaduw-overlagen (background-attachment: scroll) blijven gefixeerd.
210+
* - Resultaat: schaduw is zichtbaar zodra er content is om naartoe te scrollen.
211+
*
212+
* ⚠️ Vereist dat de achtergrondkleur overeenkomt met de drawer-achtergrond.
213+
*/
214+
215+
.dsn-drawer__body {
216+
--_bg: var(--dsn-drawer-background);
217+
--_shadow: var(--dsn-color-shadow-scroll, rgba(0, 0, 0, 0.12));
218+
219+
flex: 1;
220+
overflow-y: auto;
221+
222+
padding-block: var(--dsn-drawer-body-padding-block);
223+
padding-inline: var(--dsn-drawer-body-padding-inline);
224+
225+
background-color: var(--_bg);
226+
background-image:
227+
/* Top-cover (local): verbergt de bovenschaduw als je helemaal boven bent */
228+
linear-gradient(to bottom, var(--_bg) 0%, transparent 100%),
229+
/* Onder-cover (local): verbergt de onderschaduw als je helemaal onder bent */
230+
linear-gradient(to top, var(--_bg) 0%, transparent 100%),
231+
/* Bovenschaduw (scroll): altijd aanwezig */
232+
linear-gradient(to bottom, var(--_shadow) 0%, transparent 100%),
233+
/* Onderschaduw (scroll): altijd aanwezig */
234+
linear-gradient(to top, var(--_shadow) 0%, transparent 100%);
235+
background-repeat: no-repeat;
236+
background-size:
237+
100% 4rem,
238+
100% 4rem,
239+
100% 2rem,
240+
100% 2rem;
241+
background-position: top, bottom, top, bottom;
242+
background-attachment: local, local, scroll, scroll;
243+
}
244+
245+
/* ===== Footer ===== */
246+
247+
.dsn-drawer__footer {
248+
flex-shrink: 0;
249+
250+
padding-block-start: var(--dsn-drawer-footer-padding-block-start);
251+
padding-block-end: var(--dsn-drawer-footer-padding-block-end);
252+
padding-inline: var(--dsn-drawer-footer-padding-inline);
253+
254+
/* Scheidingslijn boven de footer */
255+
border-block-start: var(--dsn-drawer-border-width) solid
256+
var(--dsn-drawer-border-color);
257+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import '../../../components-html/src/drawer/drawer.css';

0 commit comments

Comments
 (0)