css-drawer 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -8
- package/dist/drawer-BWZh2Fyp.cjs +0 -0
- package/dist/drawer-C21nwiwE.css +331 -0
- package/dist/drawer-CXCJQa45.css +333 -0
- package/dist/drawer-CXCJQa45.css.map +1 -0
- package/dist/drawer-CiHZcyXE.mjs +1 -0
- package/dist/index.cjs +4 -3
- package/dist/index.d.cts +5 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.mjs +1 -1
- package/dist/react.mjs.map +1 -1
- package/package.json +5 -4
- package/{dist/react.css → src/drawer.css} +26 -3
- package/dist/react.css.map +0 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ A near drop-in replacement for [Vaul](https://vaul.emilkowal.ski) using native `
|
|
|
12
12
|
| Animation engine | JavaScript | Pure CSS |
|
|
13
13
|
| Nesting | Manual setup | Automatic (CSS `:has()`) |
|
|
14
14
|
| Accessibility | Built-in | Automatic (native `<dialog>` + `inert`) |
|
|
15
|
-
| API | Controlled state | Native refs |
|
|
15
|
+
| API | Controlled state | Native refs or controlled state |
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
@@ -58,7 +58,7 @@ function App() {
|
|
|
58
58
|
|
|
59
59
|
```ts
|
|
60
60
|
import { open, close } from 'css-drawer'
|
|
61
|
-
|
|
61
|
+
// Styles are auto-injected
|
|
62
62
|
|
|
63
63
|
document.querySelector('#open-btn').onclick = () => open('my-drawer')
|
|
64
64
|
```
|
|
@@ -68,7 +68,7 @@ document.querySelector('#open-btn').onclick = () => open('my-drawer')
|
|
|
68
68
|
|
|
69
69
|
<dialog class="drawer" id="my-drawer">
|
|
70
70
|
<div class="drawer-handle"></div>
|
|
71
|
-
<div
|
|
71
|
+
<div class="drawer-content">
|
|
72
72
|
<h2>Title</h2>
|
|
73
73
|
<p>Description</p>
|
|
74
74
|
<button onclick="this.closest('dialog').close()">Close</button>
|
|
@@ -76,6 +76,44 @@ document.querySelector('#open-btn').onclick = () => open('my-drawer')
|
|
|
76
76
|
</dialog>
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### Angular
|
|
80
|
+
|
|
81
|
+
Angular's build system doesn't process CSS imports from JS modules. Import styles in your global `styles.css`:
|
|
82
|
+
|
|
83
|
+
```css
|
|
84
|
+
/* src/styles.css */
|
|
85
|
+
@import 'css-drawer/styles';
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then use the native dialog API in your component:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { Component } from '@angular/core';
|
|
92
|
+
|
|
93
|
+
@Component({
|
|
94
|
+
selector: 'app-example',
|
|
95
|
+
template: `
|
|
96
|
+
<button (click)="openDrawer(drawer)">Open</button>
|
|
97
|
+
|
|
98
|
+
<dialog #drawer class="drawer" data-direction="modal">
|
|
99
|
+
<div class="drawer-content">
|
|
100
|
+
<h2>Title</h2>
|
|
101
|
+
<button (click)="closeDrawer(drawer)">Close</button>
|
|
102
|
+
</div>
|
|
103
|
+
</dialog>
|
|
104
|
+
`
|
|
105
|
+
})
|
|
106
|
+
export class ExampleComponent {
|
|
107
|
+
openDrawer(dialog: HTMLDialogElement) {
|
|
108
|
+
dialog.showModal();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
closeDrawer(dialog: HTMLDialogElement) {
|
|
112
|
+
dialog.close();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
79
117
|
---
|
|
80
118
|
|
|
81
119
|
## React API
|
|
@@ -99,12 +137,16 @@ Provides context for direction. Wrap your drawer content.
|
|
|
99
137
|
|
|
100
138
|
| Prop | Type | Default | Description |
|
|
101
139
|
|------|------|---------|-------------|
|
|
102
|
-
| `direction` | `'bottom' \| 'top' \| 'left' \| 'right'` | `'bottom'` | Direction the drawer opens from |
|
|
140
|
+
| `direction` | `'bottom' \| 'top' \| 'left' \| 'right' \| 'modal'` | `'bottom'` | Direction the drawer opens from |
|
|
103
141
|
| `children` | `ReactNode` | - | Drawer content |
|
|
104
142
|
|
|
105
143
|
### Drawer.Content
|
|
106
144
|
|
|
107
|
-
The dialog element.
|
|
145
|
+
The dialog element. Supports both uncontrolled (refs) and controlled (state) modes.
|
|
146
|
+
|
|
147
|
+
> **Note:** `open`/`onOpenChange` props are on `Content`, not `Root`. This is intentional - `Content` wraps the native `<dialog>` element, so open/close control lives where the element lives. `Root` only provides configuration (direction).
|
|
148
|
+
|
|
149
|
+
#### Uncontrolled Mode (Refs)
|
|
108
150
|
|
|
109
151
|
```tsx
|
|
110
152
|
const ref = useRef<HTMLDialogElement>(null)
|
|
@@ -118,9 +160,29 @@ ref.current?.close()
|
|
|
118
160
|
<Drawer.Content ref={ref}>...</Drawer.Content>
|
|
119
161
|
```
|
|
120
162
|
|
|
163
|
+
#### Controlled Mode (State)
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
167
|
+
|
|
168
|
+
<Drawer.Content open={isOpen} onOpenChange={setIsOpen}>
|
|
169
|
+
...
|
|
170
|
+
</Drawer.Content>
|
|
171
|
+
|
|
172
|
+
// Open programmatically
|
|
173
|
+
<button onClick={() => setIsOpen(true)}>Open</button>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The `onOpenChange` callback fires when:
|
|
177
|
+
- User presses Escape
|
|
178
|
+
- User clicks the backdrop (if `closeOnOutsideClick` is true)
|
|
179
|
+
- You call `setIsOpen(false)`
|
|
180
|
+
|
|
121
181
|
| Prop | Type | Default | Description |
|
|
122
182
|
|------|------|---------|-------------|
|
|
123
|
-
| `ref` | `Ref<HTMLDialogElement>` | - | Ref to control the dialog |
|
|
183
|
+
| `ref` | `Ref<HTMLDialogElement>` | - | Ref to control the dialog (uncontrolled mode) |
|
|
184
|
+
| `open` | `boolean` | - | Controlled open state |
|
|
185
|
+
| `onOpenChange` | `(open: boolean) => void` | - | Called when open state changes |
|
|
124
186
|
| `closeOnOutsideClick` | `boolean` | `true` | Close when clicking outside the drawer |
|
|
125
187
|
| `className` | `string` | - | Additional CSS classes |
|
|
126
188
|
| `...props` | `DialogHTMLAttributes` | - | All native dialog props |
|
|
@@ -169,7 +231,7 @@ Semantic description for accessibility.
|
|
|
169
231
|
|
|
170
232
|
```ts
|
|
171
233
|
import { open, close, closeAll } from 'css-drawer'
|
|
172
|
-
|
|
234
|
+
// Styles are auto-injected
|
|
173
235
|
```
|
|
174
236
|
|
|
175
237
|
### open(drawer)
|
|
@@ -262,6 +324,7 @@ open(drawer)
|
|
|
262
324
|
|--------|------|---------|-------------|
|
|
263
325
|
| `id` | `string` | - | Drawer ID |
|
|
264
326
|
| `content` | `string` | `''` | HTML content |
|
|
327
|
+
| `direction` | `DrawerDirection` | `'bottom'` | Direction the drawer opens from |
|
|
265
328
|
| `handle` | `boolean` | `true` | Include drag handle |
|
|
266
329
|
| `className` | `string` | `''` | Additional CSS classes |
|
|
267
330
|
| `closeOnOutsideClick` | `boolean` | `true` | Close when clicking outside |
|
|
@@ -353,6 +416,7 @@ const isMobile = useMediaQuery('(max-width: 768px)')
|
|
|
353
416
|
| `top` | Opens from top |
|
|
354
417
|
| `left` | Opens from left |
|
|
355
418
|
| `right` | Opens from right |
|
|
419
|
+
| `modal` | Centered modal with scale animation |
|
|
356
420
|
|
|
357
421
|
---
|
|
358
422
|
|
|
@@ -360,6 +424,8 @@ const isMobile = useMediaQuery('(max-width: 768px)')
|
|
|
360
424
|
|
|
361
425
|
Drawers automatically stack when opened. No configuration needed.
|
|
362
426
|
|
|
427
|
+
### With Refs
|
|
428
|
+
|
|
363
429
|
```tsx
|
|
364
430
|
const drawer1 = useRef<HTMLDialogElement>(null)
|
|
365
431
|
const drawer2 = useRef<HTMLDialogElement>(null)
|
|
@@ -372,6 +438,34 @@ drawer2.current?.showModal()
|
|
|
372
438
|
// drawer1 automatically scales down and dims
|
|
373
439
|
```
|
|
374
440
|
|
|
441
|
+
### With Controlled State
|
|
442
|
+
|
|
443
|
+
```tsx
|
|
444
|
+
const [settingsOpen, setSettingsOpen] = useState(false)
|
|
445
|
+
const [confirmOpen, setConfirmOpen] = useState(false)
|
|
446
|
+
|
|
447
|
+
<>
|
|
448
|
+
<button onClick={() => setSettingsOpen(true)}>Settings</button>
|
|
449
|
+
|
|
450
|
+
<Drawer.Root>
|
|
451
|
+
<Drawer.Content open={settingsOpen} onOpenChange={setSettingsOpen}>
|
|
452
|
+
<button onClick={() => setConfirmOpen(true)}>Delete Account</button>
|
|
453
|
+
</Drawer.Content>
|
|
454
|
+
</Drawer.Root>
|
|
455
|
+
|
|
456
|
+
<Drawer.Root>
|
|
457
|
+
<Drawer.Content open={confirmOpen} onOpenChange={setConfirmOpen}>
|
|
458
|
+
<button onClick={() => {
|
|
459
|
+
setConfirmOpen(false)
|
|
460
|
+
setSettingsOpen(false)
|
|
461
|
+
}}>
|
|
462
|
+
Confirm
|
|
463
|
+
</button>
|
|
464
|
+
</Drawer.Content>
|
|
465
|
+
</Drawer.Root>
|
|
466
|
+
</>
|
|
467
|
+
```
|
|
468
|
+
|
|
375
469
|
Works up to 5 levels. CSS `:has()` selectors handle the visual stacking.
|
|
376
470
|
|
|
377
471
|
---
|
|
@@ -454,6 +548,7 @@ Override any of these CSS custom properties to customize the drawer:
|
|
|
454
548
|
--drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);
|
|
455
549
|
--drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);
|
|
456
550
|
--drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);
|
|
551
|
+
--drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);
|
|
457
552
|
|
|
458
553
|
/* Animation */
|
|
459
554
|
--drawer-duration: 0.5s;
|
|
@@ -507,6 +602,7 @@ Override any of these CSS custom properties to customize the drawer:
|
|
|
507
602
|
| `--drawer-shadow-top` | `0 10px 60px hsl(0 0% 0% / 0.12), ...` | Darker |
|
|
508
603
|
| `--drawer-shadow-left` | `10px 0 60px hsl(0 0% 0% / 0.12), ...` | Darker |
|
|
509
604
|
| `--drawer-shadow-right` | `-10px 0 60px hsl(0 0% 0% / 0.12), ...` | Darker |
|
|
605
|
+
| `--drawer-shadow-modal` | `0 25px 50px -12px hsl(0 0% 0% / 0.25)` | Darker |
|
|
510
606
|
|
|
511
607
|
#### Animation
|
|
512
608
|
|
|
@@ -677,6 +773,7 @@ Uses `@starting-style`, `:has()`, `allow-discrete`, and `dvh` units.
|
|
|
677
773
|
Full TypeScript support included.
|
|
678
774
|
|
|
679
775
|
```tsx
|
|
776
|
+
// React
|
|
680
777
|
import {
|
|
681
778
|
Drawer,
|
|
682
779
|
type DrawerRootProps,
|
|
@@ -684,11 +781,14 @@ import {
|
|
|
684
781
|
type DrawerDirection
|
|
685
782
|
} from 'css-drawer/react'
|
|
686
783
|
|
|
784
|
+
// Vanilla JS
|
|
687
785
|
import {
|
|
688
786
|
open,
|
|
689
787
|
close,
|
|
788
|
+
create,
|
|
690
789
|
type DrawerElement,
|
|
691
|
-
type DrawerRef
|
|
790
|
+
type DrawerRef,
|
|
791
|
+
type DrawerDirection
|
|
692
792
|
} from 'css-drawer'
|
|
693
793
|
```
|
|
694
794
|
|
|
File without changes
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/* CSS Drawer - Vaul-quality drawer with auto-nesting and directions */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* Visual */
|
|
5
|
+
--drawer-bg: #fff;
|
|
6
|
+
--drawer-radius: 24px;
|
|
7
|
+
--drawer-backdrop: hsl(0 0% 0% / 0.4);
|
|
8
|
+
--drawer-backdrop-blur: 4px;
|
|
9
|
+
|
|
10
|
+
/* Sizing */
|
|
11
|
+
--drawer-max-width: 500px;
|
|
12
|
+
--drawer-max-height: 96dvh;
|
|
13
|
+
|
|
14
|
+
/* Handle */
|
|
15
|
+
--drawer-handle-bg: hsl(0 0% 80%);
|
|
16
|
+
--drawer-handle-bg-hover: hsl(0 0% 60%);
|
|
17
|
+
--drawer-handle-width: 48px;
|
|
18
|
+
--drawer-handle-width-hover: 56px;
|
|
19
|
+
--drawer-handle-height: 5px;
|
|
20
|
+
--drawer-handle-padding-block: 1rem 0.5rem;
|
|
21
|
+
--drawer-handle-padding-inline: 0;
|
|
22
|
+
|
|
23
|
+
/* Shadows */
|
|
24
|
+
--drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.12), 0 -4px 20px hsl(0 0% 0% / 0.08);
|
|
25
|
+
--drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);
|
|
26
|
+
--drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);
|
|
27
|
+
--drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);
|
|
28
|
+
--drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);
|
|
29
|
+
|
|
30
|
+
/* Animation */
|
|
31
|
+
--drawer-duration: 0.5s;
|
|
32
|
+
--drawer-duration-close: 0.35s;
|
|
33
|
+
--drawer-ease: cubic-bezier(0.32, 0.72, 0, 1);
|
|
34
|
+
|
|
35
|
+
/* Nesting effects */
|
|
36
|
+
--drawer-nested-scale: 0.94;
|
|
37
|
+
--drawer-nested-offset: 20px;
|
|
38
|
+
--drawer-nested-brightness: 0.92;
|
|
39
|
+
--drawer-nested-backdrop: hsl(0 0% 0% / 0.15);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@media (prefers-color-scheme: dark) {
|
|
43
|
+
:root {
|
|
44
|
+
--drawer-bg: hsl(0 0% 12%);
|
|
45
|
+
--drawer-handle-bg: hsl(0 0% 35%);
|
|
46
|
+
--drawer-handle-bg-hover: hsl(0 0% 50%);
|
|
47
|
+
--drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.4), 0 -4px 20px hsl(0 0% 0% / 0.3);
|
|
48
|
+
--drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);
|
|
49
|
+
--drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);
|
|
50
|
+
--drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);
|
|
51
|
+
--drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.5);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Background scale effect */
|
|
56
|
+
body {
|
|
57
|
+
transition: scale var(--drawer-duration) var(--drawer-ease), border-radius var(--drawer-duration) var(--drawer-ease);
|
|
58
|
+
transform-origin: center top;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
body:has(.drawer[open]) {
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
scale: var(--drawer-nested-scale);
|
|
64
|
+
border-radius: var(--drawer-radius);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Base drawer */
|
|
68
|
+
.drawer {
|
|
69
|
+
border: none;
|
|
70
|
+
padding: 0;
|
|
71
|
+
margin: 0;
|
|
72
|
+
max-width: 100%;
|
|
73
|
+
max-height: 100%;
|
|
74
|
+
position: fixed;
|
|
75
|
+
background: var(--drawer-bg);
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
opacity: 0;
|
|
78
|
+
transition:
|
|
79
|
+
display var(--drawer-duration-close) allow-discrete,
|
|
80
|
+
overlay var(--drawer-duration-close) allow-discrete,
|
|
81
|
+
translate var(--drawer-duration-close) var(--drawer-ease),
|
|
82
|
+
scale var(--drawer-duration-close) var(--drawer-ease),
|
|
83
|
+
filter var(--drawer-duration-close) ease,
|
|
84
|
+
opacity var(--drawer-duration-close) ease;
|
|
85
|
+
|
|
86
|
+
/* Default: bottom */
|
|
87
|
+
--_translate-closed: 0 100%;
|
|
88
|
+
--_border-radius: var(--drawer-radius) var(--drawer-radius) 0 0;
|
|
89
|
+
inset: auto 0 0 0;
|
|
90
|
+
margin-inline: auto;
|
|
91
|
+
width: 100%;
|
|
92
|
+
max-width: var(--drawer-max-width);
|
|
93
|
+
height: auto;
|
|
94
|
+
max-height: var(--drawer-max-height);
|
|
95
|
+
border-radius: var(--drawer-border-radius, var(--_border-radius));
|
|
96
|
+
box-shadow: var(--drawer-shadow-bottom);
|
|
97
|
+
translate: var(--_translate-closed);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.drawer::backdrop {
|
|
101
|
+
background: var(--drawer-backdrop);
|
|
102
|
+
opacity: 0;
|
|
103
|
+
backdrop-filter: blur(var(--drawer-backdrop-blur));
|
|
104
|
+
-webkit-backdrop-filter: blur(var(--drawer-backdrop-blur));
|
|
105
|
+
transition:
|
|
106
|
+
display var(--drawer-duration-close) allow-discrete,
|
|
107
|
+
overlay var(--drawer-duration-close) allow-discrete,
|
|
108
|
+
opacity var(--drawer-duration-close) ease;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.drawer[open] {
|
|
112
|
+
opacity: 1;
|
|
113
|
+
translate: 0 0;
|
|
114
|
+
transition:
|
|
115
|
+
display var(--drawer-duration) allow-discrete,
|
|
116
|
+
overlay var(--drawer-duration) allow-discrete,
|
|
117
|
+
translate var(--drawer-duration) var(--drawer-ease),
|
|
118
|
+
scale var(--drawer-duration) var(--drawer-ease),
|
|
119
|
+
filter var(--drawer-duration) ease,
|
|
120
|
+
opacity 0.15s ease;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.drawer[open]::backdrop {
|
|
124
|
+
opacity: 1;
|
|
125
|
+
transition: display var(--drawer-duration) allow-discrete, overlay var(--drawer-duration) allow-discrete, opacity 0.2s ease;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@starting-style {
|
|
129
|
+
.drawer[open] { opacity: 0; translate: var(--_translate-closed); }
|
|
130
|
+
.drawer[open]::backdrop { opacity: 0; }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ===== DIRECTIONS ===== */
|
|
134
|
+
|
|
135
|
+
/* Right */
|
|
136
|
+
.drawer[data-direction="right"] {
|
|
137
|
+
--_translate-closed: 100% 0;
|
|
138
|
+
--_border-radius: var(--drawer-radius) 0 0 var(--drawer-radius);
|
|
139
|
+
inset: 0 0 0 auto;
|
|
140
|
+
margin: 0;
|
|
141
|
+
width: 100%;
|
|
142
|
+
max-width: var(--drawer-max-width);
|
|
143
|
+
height: 100dvh;
|
|
144
|
+
max-height: 100dvh;
|
|
145
|
+
box-shadow: var(--drawer-shadow-right);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Left */
|
|
149
|
+
.drawer[data-direction="left"] {
|
|
150
|
+
--_translate-closed: -100% 0;
|
|
151
|
+
--_border-radius: 0 var(--drawer-radius) var(--drawer-radius) 0;
|
|
152
|
+
inset: 0 auto 0 0;
|
|
153
|
+
margin: 0;
|
|
154
|
+
width: 100%;
|
|
155
|
+
max-width: var(--drawer-max-width);
|
|
156
|
+
height: 100dvh;
|
|
157
|
+
max-height: 100dvh;
|
|
158
|
+
box-shadow: var(--drawer-shadow-left);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Top */
|
|
162
|
+
.drawer[data-direction="top"] {
|
|
163
|
+
--_translate-closed: 0 -100%;
|
|
164
|
+
--_border-radius: 0 0 var(--drawer-radius) var(--drawer-radius);
|
|
165
|
+
inset: 0 0 auto 0;
|
|
166
|
+
margin-inline: auto;
|
|
167
|
+
width: 100%;
|
|
168
|
+
max-width: var(--drawer-max-width);
|
|
169
|
+
height: auto;
|
|
170
|
+
max-height: var(--drawer-max-height);
|
|
171
|
+
box-shadow: var(--drawer-shadow-top);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Modal (centered) */
|
|
175
|
+
.drawer[data-direction="modal"] {
|
|
176
|
+
--_translate-closed: 0 0;
|
|
177
|
+
--_border-radius: var(--drawer-radius);
|
|
178
|
+
inset: 0;
|
|
179
|
+
margin: auto;
|
|
180
|
+
width: fit-content;
|
|
181
|
+
max-width: var(--drawer-max-width);
|
|
182
|
+
height: fit-content;
|
|
183
|
+
max-height: var(--drawer-max-height);
|
|
184
|
+
box-shadow: var(--drawer-shadow-modal);
|
|
185
|
+
scale: var(--drawer-nested-scale);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.drawer[data-direction="modal"][open] {
|
|
189
|
+
scale: 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@starting-style {
|
|
193
|
+
.drawer[data-direction="modal"][open] {
|
|
194
|
+
scale: var(--drawer-nested-scale);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* ===== AUTO-NESTING (up to 5 levels) ===== */
|
|
199
|
+
/* Uses sibling combinators to count open drawers */
|
|
200
|
+
|
|
201
|
+
/* 1+ open drawers after */
|
|
202
|
+
.drawer[open]:has(~ .drawer[open]) {
|
|
203
|
+
scale: var(--drawer-nested-scale);
|
|
204
|
+
translate: 0 calc(-1 * var(--drawer-nested-offset));
|
|
205
|
+
border-radius: var(--drawer-radius);
|
|
206
|
+
filter: brightness(var(--drawer-nested-brightness));
|
|
207
|
+
pointer-events: none;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* 2+ open drawers after */
|
|
211
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open]) {
|
|
212
|
+
scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale));
|
|
213
|
+
translate: 0 calc(-2 * var(--drawer-nested-offset));
|
|
214
|
+
filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* 3+ open drawers after */
|
|
218
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
|
|
219
|
+
scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
|
|
220
|
+
translate: 0 calc(-3 * var(--drawer-nested-offset));
|
|
221
|
+
filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* 4+ open drawers after */
|
|
225
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
|
|
226
|
+
scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
|
|
227
|
+
translate: 0 calc(-4 * var(--drawer-nested-offset));
|
|
228
|
+
filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* 5+ open drawers after */
|
|
232
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
|
|
233
|
+
scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
|
|
234
|
+
translate: 0 calc(-5 * var(--drawer-nested-offset));
|
|
235
|
+
filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Lighter backdrop for stacked drawers */
|
|
239
|
+
.drawer[open] ~ .drawer[open]::backdrop {
|
|
240
|
+
background: var(--drawer-nested-backdrop);
|
|
241
|
+
backdrop-filter: none;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Handle */
|
|
245
|
+
.drawer-handle {
|
|
246
|
+
display: flex;
|
|
247
|
+
justify-content: center;
|
|
248
|
+
padding-block: var(--drawer-handle-padding-block);
|
|
249
|
+
padding-inline: var(--drawer-handle-padding-inline);
|
|
250
|
+
cursor: grab;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.drawer-handle::before {
|
|
254
|
+
content: '';
|
|
255
|
+
width: var(--drawer-handle-width);
|
|
256
|
+
height: var(--drawer-handle-height);
|
|
257
|
+
background: var(--drawer-handle-bg);
|
|
258
|
+
border-radius: 100px;
|
|
259
|
+
transition: width 0.2s var(--drawer-ease), height 0.2s var(--drawer-ease), background 0.2s ease;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.drawer-handle:hover::before {
|
|
263
|
+
width: var(--drawer-handle-width-hover);
|
|
264
|
+
background: var(--drawer-handle-bg-hover);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Vertical handle for left/right drawers */
|
|
268
|
+
.drawer[data-direction="left"] .drawer-handle,
|
|
269
|
+
.drawer[data-direction="right"] .drawer-handle {
|
|
270
|
+
flex-direction: column;
|
|
271
|
+
align-items: center;
|
|
272
|
+
justify-content: center;
|
|
273
|
+
padding-block: var(--drawer-handle-padding-inline);
|
|
274
|
+
padding-inline: var(--drawer-handle-padding-block);
|
|
275
|
+
height: 100%;
|
|
276
|
+
position: absolute;
|
|
277
|
+
top: 0;
|
|
278
|
+
writing-mode: vertical-lr;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.drawer[data-direction="left"] .drawer-handle { right: 0; }
|
|
282
|
+
.drawer[data-direction="right"] .drawer-handle { left: 0; }
|
|
283
|
+
|
|
284
|
+
.drawer[data-direction="left"] .drawer-handle::before,
|
|
285
|
+
.drawer[data-direction="right"] .drawer-handle::before {
|
|
286
|
+
width: var(--drawer-handle-height);
|
|
287
|
+
height: var(--drawer-handle-width);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.drawer[data-direction="left"] .drawer-handle:hover::before,
|
|
291
|
+
.drawer[data-direction="right"] .drawer-handle:hover::before {
|
|
292
|
+
width: var(--drawer-handle-height);
|
|
293
|
+
height: var(--drawer-handle-width-hover);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Content - structural only, no opinionated padding */
|
|
297
|
+
.drawer-content {
|
|
298
|
+
overflow-x: hidden;
|
|
299
|
+
overflow-y: auto;
|
|
300
|
+
overscroll-behavior: contain;
|
|
301
|
+
flex: 1;
|
|
302
|
+
min-height: 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* Content sizing for directions */
|
|
306
|
+
.drawer[data-direction="left"] .drawer-content,
|
|
307
|
+
.drawer[data-direction="right"] .drawer-content {
|
|
308
|
+
height: 100%;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Reduced motion */
|
|
312
|
+
@media (prefers-reduced-motion: reduce) {
|
|
313
|
+
*, *::before, *::after {
|
|
314
|
+
transition-duration: 0.01ms !important;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
body:has(.drawer[open]) {
|
|
318
|
+
scale: 1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.drawer[open]:has(~ .drawer[open]),
|
|
322
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open]),
|
|
323
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),
|
|
324
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),
|
|
325
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
|
|
326
|
+
scale: 1;
|
|
327
|
+
translate: 0 0;
|
|
328
|
+
filter: none;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/* CSS Drawer - Vaul-quality drawer with auto-nesting and directions */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* Visual */
|
|
5
|
+
--drawer-bg: #fff;
|
|
6
|
+
--drawer-radius: 24px;
|
|
7
|
+
--drawer-backdrop: hsl(0 0% 0% / 0.4);
|
|
8
|
+
--drawer-backdrop-blur: 4px;
|
|
9
|
+
|
|
10
|
+
/* Sizing */
|
|
11
|
+
--drawer-max-width: 500px;
|
|
12
|
+
--drawer-max-height: 96dvh;
|
|
13
|
+
|
|
14
|
+
/* Handle */
|
|
15
|
+
--drawer-handle-bg: hsl(0 0% 80%);
|
|
16
|
+
--drawer-handle-bg-hover: hsl(0 0% 60%);
|
|
17
|
+
--drawer-handle-width: 48px;
|
|
18
|
+
--drawer-handle-width-hover: 56px;
|
|
19
|
+
--drawer-handle-height: 5px;
|
|
20
|
+
--drawer-handle-padding-block: 1rem 0.5rem;
|
|
21
|
+
--drawer-handle-padding-inline: 0;
|
|
22
|
+
|
|
23
|
+
/* Shadows */
|
|
24
|
+
--drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.12), 0 -4px 20px hsl(0 0% 0% / 0.08);
|
|
25
|
+
--drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);
|
|
26
|
+
--drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);
|
|
27
|
+
--drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);
|
|
28
|
+
--drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);
|
|
29
|
+
|
|
30
|
+
/* Animation */
|
|
31
|
+
--drawer-duration: 0.5s;
|
|
32
|
+
--drawer-duration-close: 0.35s;
|
|
33
|
+
--drawer-ease: cubic-bezier(0.32, 0.72, 0, 1);
|
|
34
|
+
|
|
35
|
+
/* Nesting effects */
|
|
36
|
+
--drawer-nested-scale: 0.94;
|
|
37
|
+
--drawer-nested-offset: 20px;
|
|
38
|
+
--drawer-nested-brightness: 0.92;
|
|
39
|
+
--drawer-nested-backdrop: hsl(0 0% 0% / 0.15);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@media (prefers-color-scheme: dark) {
|
|
43
|
+
:root {
|
|
44
|
+
--drawer-bg: hsl(0 0% 12%);
|
|
45
|
+
--drawer-handle-bg: hsl(0 0% 35%);
|
|
46
|
+
--drawer-handle-bg-hover: hsl(0 0% 50%);
|
|
47
|
+
--drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.4), 0 -4px 20px hsl(0 0% 0% / 0.3);
|
|
48
|
+
--drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);
|
|
49
|
+
--drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);
|
|
50
|
+
--drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);
|
|
51
|
+
--drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.5);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Background scale effect */
|
|
56
|
+
body {
|
|
57
|
+
transition: scale var(--drawer-duration) var(--drawer-ease), border-radius var(--drawer-duration) var(--drawer-ease);
|
|
58
|
+
transform-origin: center top;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
body:has(.drawer[open]) {
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
scale: var(--drawer-nested-scale);
|
|
64
|
+
border-radius: var(--drawer-radius);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Base drawer */
|
|
68
|
+
.drawer {
|
|
69
|
+
border: none;
|
|
70
|
+
padding: 0;
|
|
71
|
+
margin: 0;
|
|
72
|
+
max-width: 100%;
|
|
73
|
+
max-height: 100%;
|
|
74
|
+
position: fixed;
|
|
75
|
+
background: var(--drawer-bg);
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
opacity: 0;
|
|
78
|
+
transition:
|
|
79
|
+
display var(--drawer-duration-close) allow-discrete,
|
|
80
|
+
overlay var(--drawer-duration-close) allow-discrete,
|
|
81
|
+
translate var(--drawer-duration-close) var(--drawer-ease),
|
|
82
|
+
scale var(--drawer-duration-close) var(--drawer-ease),
|
|
83
|
+
filter var(--drawer-duration-close) ease,
|
|
84
|
+
opacity var(--drawer-duration-close) ease;
|
|
85
|
+
|
|
86
|
+
/* Default: bottom */
|
|
87
|
+
--_translate-closed: 0 100%;
|
|
88
|
+
--_border-radius: var(--drawer-radius) var(--drawer-radius) 0 0;
|
|
89
|
+
inset: auto 0 0 0;
|
|
90
|
+
margin-inline: auto;
|
|
91
|
+
width: 100%;
|
|
92
|
+
max-width: var(--drawer-max-width);
|
|
93
|
+
height: auto;
|
|
94
|
+
max-height: var(--drawer-max-height);
|
|
95
|
+
border-radius: var(--drawer-border-radius, var(--_border-radius));
|
|
96
|
+
box-shadow: var(--drawer-shadow-bottom);
|
|
97
|
+
translate: var(--_translate-closed);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.drawer::backdrop {
|
|
101
|
+
background: var(--drawer-backdrop);
|
|
102
|
+
opacity: 0;
|
|
103
|
+
backdrop-filter: blur(var(--drawer-backdrop-blur));
|
|
104
|
+
-webkit-backdrop-filter: blur(var(--drawer-backdrop-blur));
|
|
105
|
+
transition:
|
|
106
|
+
display var(--drawer-duration-close) allow-discrete,
|
|
107
|
+
overlay var(--drawer-duration-close) allow-discrete,
|
|
108
|
+
opacity var(--drawer-duration-close) ease;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.drawer[open] {
|
|
112
|
+
opacity: 1;
|
|
113
|
+
translate: 0 0;
|
|
114
|
+
transition:
|
|
115
|
+
display var(--drawer-duration) allow-discrete,
|
|
116
|
+
overlay var(--drawer-duration) allow-discrete,
|
|
117
|
+
translate var(--drawer-duration) var(--drawer-ease),
|
|
118
|
+
scale var(--drawer-duration) var(--drawer-ease),
|
|
119
|
+
filter var(--drawer-duration) ease,
|
|
120
|
+
opacity 0.15s ease;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.drawer[open]::backdrop {
|
|
124
|
+
opacity: 1;
|
|
125
|
+
transition: display var(--drawer-duration) allow-discrete, overlay var(--drawer-duration) allow-discrete, opacity 0.2s ease;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@starting-style {
|
|
129
|
+
.drawer[open] { opacity: 0; translate: var(--_translate-closed); }
|
|
130
|
+
.drawer[open]::backdrop { opacity: 0; }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ===== DIRECTIONS ===== */
|
|
134
|
+
|
|
135
|
+
/* Right */
|
|
136
|
+
.drawer[data-direction="right"] {
|
|
137
|
+
--_translate-closed: 100% 0;
|
|
138
|
+
--_border-radius: var(--drawer-radius) 0 0 var(--drawer-radius);
|
|
139
|
+
inset: 0 0 0 auto;
|
|
140
|
+
margin: 0;
|
|
141
|
+
width: 100%;
|
|
142
|
+
max-width: var(--drawer-max-width);
|
|
143
|
+
height: 100dvh;
|
|
144
|
+
max-height: 100dvh;
|
|
145
|
+
box-shadow: var(--drawer-shadow-right);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Left */
|
|
149
|
+
.drawer[data-direction="left"] {
|
|
150
|
+
--_translate-closed: -100% 0;
|
|
151
|
+
--_border-radius: 0 var(--drawer-radius) var(--drawer-radius) 0;
|
|
152
|
+
inset: 0 auto 0 0;
|
|
153
|
+
margin: 0;
|
|
154
|
+
width: 100%;
|
|
155
|
+
max-width: var(--drawer-max-width);
|
|
156
|
+
height: 100dvh;
|
|
157
|
+
max-height: 100dvh;
|
|
158
|
+
box-shadow: var(--drawer-shadow-left);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Top */
|
|
162
|
+
.drawer[data-direction="top"] {
|
|
163
|
+
--_translate-closed: 0 -100%;
|
|
164
|
+
--_border-radius: 0 0 var(--drawer-radius) var(--drawer-radius);
|
|
165
|
+
inset: 0 0 auto 0;
|
|
166
|
+
margin-inline: auto;
|
|
167
|
+
width: 100%;
|
|
168
|
+
max-width: var(--drawer-max-width);
|
|
169
|
+
height: auto;
|
|
170
|
+
max-height: var(--drawer-max-height);
|
|
171
|
+
box-shadow: var(--drawer-shadow-top);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Modal (centered) */
|
|
175
|
+
.drawer[data-direction="modal"] {
|
|
176
|
+
--_translate-closed: 0 0;
|
|
177
|
+
--_border-radius: var(--drawer-radius);
|
|
178
|
+
inset: 0;
|
|
179
|
+
margin: auto;
|
|
180
|
+
width: fit-content;
|
|
181
|
+
max-width: var(--drawer-max-width);
|
|
182
|
+
height: fit-content;
|
|
183
|
+
max-height: var(--drawer-max-height);
|
|
184
|
+
box-shadow: var(--drawer-shadow-modal);
|
|
185
|
+
scale: var(--drawer-nested-scale);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.drawer[data-direction="modal"][open] {
|
|
189
|
+
scale: 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@starting-style {
|
|
193
|
+
.drawer[data-direction="modal"][open] {
|
|
194
|
+
scale: var(--drawer-nested-scale);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* ===== AUTO-NESTING (up to 5 levels) ===== */
|
|
199
|
+
/* Uses sibling combinators to count open drawers */
|
|
200
|
+
|
|
201
|
+
/* 1+ open drawers after */
|
|
202
|
+
.drawer[open]:has(~ .drawer[open]) {
|
|
203
|
+
scale: var(--drawer-nested-scale);
|
|
204
|
+
translate: 0 calc(-1 * var(--drawer-nested-offset));
|
|
205
|
+
border-radius: var(--drawer-radius);
|
|
206
|
+
filter: brightness(var(--drawer-nested-brightness));
|
|
207
|
+
pointer-events: none;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* 2+ open drawers after */
|
|
211
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open]) {
|
|
212
|
+
scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale));
|
|
213
|
+
translate: 0 calc(-2 * var(--drawer-nested-offset));
|
|
214
|
+
filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* 3+ open drawers after */
|
|
218
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
|
|
219
|
+
scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
|
|
220
|
+
translate: 0 calc(-3 * var(--drawer-nested-offset));
|
|
221
|
+
filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* 4+ open drawers after */
|
|
225
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
|
|
226
|
+
scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
|
|
227
|
+
translate: 0 calc(-4 * var(--drawer-nested-offset));
|
|
228
|
+
filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* 5+ open drawers after */
|
|
232
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
|
|
233
|
+
scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
|
|
234
|
+
translate: 0 calc(-5 * var(--drawer-nested-offset));
|
|
235
|
+
filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Lighter backdrop for stacked drawers */
|
|
239
|
+
.drawer[open] ~ .drawer[open]::backdrop {
|
|
240
|
+
background: var(--drawer-nested-backdrop);
|
|
241
|
+
backdrop-filter: none;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Handle */
|
|
245
|
+
.drawer-handle {
|
|
246
|
+
display: flex;
|
|
247
|
+
justify-content: center;
|
|
248
|
+
padding-block: var(--drawer-handle-padding-block);
|
|
249
|
+
padding-inline: var(--drawer-handle-padding-inline);
|
|
250
|
+
cursor: grab;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.drawer-handle::before {
|
|
254
|
+
content: '';
|
|
255
|
+
width: var(--drawer-handle-width);
|
|
256
|
+
height: var(--drawer-handle-height);
|
|
257
|
+
background: var(--drawer-handle-bg);
|
|
258
|
+
border-radius: 100px;
|
|
259
|
+
transition: width 0.2s var(--drawer-ease), height 0.2s var(--drawer-ease), background 0.2s ease;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.drawer-handle:hover::before {
|
|
263
|
+
width: var(--drawer-handle-width-hover);
|
|
264
|
+
background: var(--drawer-handle-bg-hover);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Vertical handle for left/right drawers */
|
|
268
|
+
.drawer[data-direction="left"] .drawer-handle,
|
|
269
|
+
.drawer[data-direction="right"] .drawer-handle {
|
|
270
|
+
flex-direction: column;
|
|
271
|
+
align-items: center;
|
|
272
|
+
justify-content: center;
|
|
273
|
+
padding-block: var(--drawer-handle-padding-inline);
|
|
274
|
+
padding-inline: var(--drawer-handle-padding-block);
|
|
275
|
+
height: 100%;
|
|
276
|
+
position: absolute;
|
|
277
|
+
top: 0;
|
|
278
|
+
writing-mode: vertical-lr;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.drawer[data-direction="left"] .drawer-handle { right: 0; }
|
|
282
|
+
.drawer[data-direction="right"] .drawer-handle { left: 0; }
|
|
283
|
+
|
|
284
|
+
.drawer[data-direction="left"] .drawer-handle::before,
|
|
285
|
+
.drawer[data-direction="right"] .drawer-handle::before {
|
|
286
|
+
width: var(--drawer-handle-height);
|
|
287
|
+
height: var(--drawer-handle-width);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.drawer[data-direction="left"] .drawer-handle:hover::before,
|
|
291
|
+
.drawer[data-direction="right"] .drawer-handle:hover::before {
|
|
292
|
+
width: var(--drawer-handle-height);
|
|
293
|
+
height: var(--drawer-handle-width-hover);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Content - structural only, no opinionated padding */
|
|
297
|
+
.drawer-content {
|
|
298
|
+
overflow-x: hidden;
|
|
299
|
+
overflow-y: auto;
|
|
300
|
+
overscroll-behavior: contain;
|
|
301
|
+
flex: 1;
|
|
302
|
+
min-height: 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* Content sizing for directions */
|
|
306
|
+
.drawer[data-direction="left"] .drawer-content,
|
|
307
|
+
.drawer[data-direction="right"] .drawer-content {
|
|
308
|
+
height: 100%;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Reduced motion */
|
|
312
|
+
@media (prefers-reduced-motion: reduce) {
|
|
313
|
+
*, *::before, *::after {
|
|
314
|
+
transition-duration: 0.01ms !important;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
body:has(.drawer[open]) {
|
|
318
|
+
scale: 1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.drawer[open]:has(~ .drawer[open]),
|
|
322
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open]),
|
|
323
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),
|
|
324
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),
|
|
325
|
+
.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
|
|
326
|
+
scale: 1;
|
|
327
|
+
translate: 0 0;
|
|
328
|
+
filter: none;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
/*# sourceMappingURL=drawer-CXCJQa45.css.map*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawer-CXCJQa45.css","names":[],"sources":["../src/drawer.css"],"sourcesContent":["/* CSS Drawer - Vaul-quality drawer with auto-nesting and directions */\n\n:root {\n /* Visual */\n --drawer-bg: #fff;\n --drawer-radius: 24px;\n --drawer-backdrop: hsl(0 0% 0% / 0.4);\n --drawer-backdrop-blur: 4px;\n\n /* Sizing */\n --drawer-max-width: 500px;\n --drawer-max-height: 96dvh;\n\n /* Handle */\n --drawer-handle-bg: hsl(0 0% 80%);\n --drawer-handle-bg-hover: hsl(0 0% 60%);\n --drawer-handle-width: 48px;\n --drawer-handle-width-hover: 56px;\n --drawer-handle-height: 5px;\n --drawer-handle-padding-block: 1rem 0.5rem;\n --drawer-handle-padding-inline: 0;\n\n /* Shadows */\n --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.12), 0 -4px 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);\n\n /* Animation */\n --drawer-duration: 0.5s;\n --drawer-duration-close: 0.35s;\n --drawer-ease: cubic-bezier(0.32, 0.72, 0, 1);\n\n /* Nesting effects */\n --drawer-nested-scale: 0.94;\n --drawer-nested-offset: 20px;\n --drawer-nested-brightness: 0.92;\n --drawer-nested-backdrop: hsl(0 0% 0% / 0.15);\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --drawer-bg: hsl(0 0% 12%);\n --drawer-handle-bg: hsl(0 0% 35%);\n --drawer-handle-bg-hover: hsl(0 0% 50%);\n --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.4), 0 -4px 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.5);\n }\n}\n\n/* Background scale effect */\nbody {\n transition: scale var(--drawer-duration) var(--drawer-ease), border-radius var(--drawer-duration) var(--drawer-ease);\n transform-origin: center top;\n}\n\nbody:has(.drawer[open]) {\n overflow: hidden;\n scale: var(--drawer-nested-scale);\n border-radius: var(--drawer-radius);\n}\n\n/* Base drawer */\n.drawer {\n border: none;\n padding: 0;\n margin: 0;\n max-width: 100%;\n max-height: 100%;\n position: fixed;\n background: var(--drawer-bg);\n overflow: hidden;\n opacity: 0;\n transition:\n display var(--drawer-duration-close) allow-discrete,\n overlay var(--drawer-duration-close) allow-discrete,\n translate var(--drawer-duration-close) var(--drawer-ease),\n scale var(--drawer-duration-close) var(--drawer-ease),\n filter var(--drawer-duration-close) ease,\n opacity var(--drawer-duration-close) ease;\n\n /* Default: bottom */\n --_translate-closed: 0 100%;\n --_border-radius: var(--drawer-radius) var(--drawer-radius) 0 0;\n inset: auto 0 0 0;\n margin-inline: auto;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: auto;\n max-height: var(--drawer-max-height);\n border-radius: var(--drawer-border-radius, var(--_border-radius));\n box-shadow: var(--drawer-shadow-bottom);\n translate: var(--_translate-closed);\n}\n\n.drawer::backdrop {\n background: var(--drawer-backdrop);\n opacity: 0;\n backdrop-filter: blur(var(--drawer-backdrop-blur));\n -webkit-backdrop-filter: blur(var(--drawer-backdrop-blur));\n transition:\n display var(--drawer-duration-close) allow-discrete,\n overlay var(--drawer-duration-close) allow-discrete,\n opacity var(--drawer-duration-close) ease;\n}\n\n.drawer[open] {\n opacity: 1;\n translate: 0 0;\n transition:\n display var(--drawer-duration) allow-discrete,\n overlay var(--drawer-duration) allow-discrete,\n translate var(--drawer-duration) var(--drawer-ease),\n scale var(--drawer-duration) var(--drawer-ease),\n filter var(--drawer-duration) ease,\n opacity 0.15s ease;\n}\n\n.drawer[open]::backdrop {\n opacity: 1;\n transition: display var(--drawer-duration) allow-discrete, overlay var(--drawer-duration) allow-discrete, opacity 0.2s ease;\n}\n\n@starting-style {\n .drawer[open] { opacity: 0; translate: var(--_translate-closed); }\n .drawer[open]::backdrop { opacity: 0; }\n}\n\n/* ===== DIRECTIONS ===== */\n\n/* Right */\n.drawer[data-direction=\"right\"] {\n --_translate-closed: 100% 0;\n --_border-radius: var(--drawer-radius) 0 0 var(--drawer-radius);\n inset: 0 0 0 auto;\n margin: 0;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: 100dvh;\n max-height: 100dvh;\n box-shadow: var(--drawer-shadow-right);\n}\n\n/* Left */\n.drawer[data-direction=\"left\"] {\n --_translate-closed: -100% 0;\n --_border-radius: 0 var(--drawer-radius) var(--drawer-radius) 0;\n inset: 0 auto 0 0;\n margin: 0;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: 100dvh;\n max-height: 100dvh;\n box-shadow: var(--drawer-shadow-left);\n}\n\n/* Top */\n.drawer[data-direction=\"top\"] {\n --_translate-closed: 0 -100%;\n --_border-radius: 0 0 var(--drawer-radius) var(--drawer-radius);\n inset: 0 0 auto 0;\n margin-inline: auto;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: auto;\n max-height: var(--drawer-max-height);\n box-shadow: var(--drawer-shadow-top);\n}\n\n/* Modal (centered) */\n.drawer[data-direction=\"modal\"] {\n --_translate-closed: 0 0;\n --_border-radius: var(--drawer-radius);\n inset: 0;\n margin: auto;\n width: fit-content;\n max-width: var(--drawer-max-width);\n height: fit-content;\n max-height: var(--drawer-max-height);\n box-shadow: var(--drawer-shadow-modal);\n scale: var(--drawer-nested-scale);\n}\n\n.drawer[data-direction=\"modal\"][open] {\n scale: 1;\n}\n\n@starting-style {\n .drawer[data-direction=\"modal\"][open] {\n scale: var(--drawer-nested-scale);\n }\n}\n\n/* ===== AUTO-NESTING (up to 5 levels) ===== */\n/* Uses sibling combinators to count open drawers */\n\n/* 1+ open drawers after */\n.drawer[open]:has(~ .drawer[open]) {\n scale: var(--drawer-nested-scale);\n translate: 0 calc(-1 * var(--drawer-nested-offset));\n border-radius: var(--drawer-radius);\n filter: brightness(var(--drawer-nested-brightness));\n pointer-events: none;\n}\n\n/* 2+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-2 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 3+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-3 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 4+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-4 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 5+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-5 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* Lighter backdrop for stacked drawers */\n.drawer[open] ~ .drawer[open]::backdrop {\n background: var(--drawer-nested-backdrop);\n backdrop-filter: none;\n}\n\n/* Handle */\n.drawer-handle {\n display: flex;\n justify-content: center;\n padding-block: var(--drawer-handle-padding-block);\n padding-inline: var(--drawer-handle-padding-inline);\n cursor: grab;\n}\n\n.drawer-handle::before {\n content: '';\n width: var(--drawer-handle-width);\n height: var(--drawer-handle-height);\n background: var(--drawer-handle-bg);\n border-radius: 100px;\n transition: width 0.2s var(--drawer-ease), height 0.2s var(--drawer-ease), background 0.2s ease;\n}\n\n.drawer-handle:hover::before {\n width: var(--drawer-handle-width-hover);\n background: var(--drawer-handle-bg-hover);\n}\n\n/* Vertical handle for left/right drawers */\n.drawer[data-direction=\"left\"] .drawer-handle,\n.drawer[data-direction=\"right\"] .drawer-handle {\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding-block: var(--drawer-handle-padding-inline);\n padding-inline: var(--drawer-handle-padding-block);\n height: 100%;\n position: absolute;\n top: 0;\n writing-mode: vertical-lr;\n}\n\n.drawer[data-direction=\"left\"] .drawer-handle { right: 0; }\n.drawer[data-direction=\"right\"] .drawer-handle { left: 0; }\n\n.drawer[data-direction=\"left\"] .drawer-handle::before,\n.drawer[data-direction=\"right\"] .drawer-handle::before {\n width: var(--drawer-handle-height);\n height: var(--drawer-handle-width);\n}\n\n.drawer[data-direction=\"left\"] .drawer-handle:hover::before,\n.drawer[data-direction=\"right\"] .drawer-handle:hover::before {\n width: var(--drawer-handle-height);\n height: var(--drawer-handle-width-hover);\n}\n\n/* Content - structural only, no opinionated padding */\n.drawer-content {\n overflow-x: hidden;\n overflow-y: auto;\n overscroll-behavior: contain;\n flex: 1;\n min-height: 0;\n}\n\n/* Content sizing for directions */\n.drawer[data-direction=\"left\"] .drawer-content,\n.drawer[data-direction=\"right\"] .drawer-content {\n height: 100%;\n}\n\n/* Reduced motion */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n transition-duration: 0.01ms !important;\n }\n\n body:has(.drawer[open]) {\n scale: 1;\n }\n\n .drawer[open]:has(~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: 1;\n translate: 0 0;\n filter: none;\n }\n}\n"],"mappings":"AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
require('./drawer-C21nwiwE.css');
|
|
2
|
+
if(require(`./drawer-BWZh2Fyp.cjs`),typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}function e(e){return e?typeof e==`string`?document.getElementById(e):e:null}function t(t){e(t)?.showModal()}function n(t){e(t)?.close()}function r(){Array.from(document.querySelectorAll(`dialog.drawer[open]`)).reverse().forEach(e=>e.close())}function i(t){return e(t)?.open??!1}function a(){return Array.from(document.querySelectorAll(`dialog.drawer[open]`))}function o(){let e=a();return e[e.length-1]??null}function s(e={}){let{id:t,content:n=``,direction:r,handle:i=!0,className:a=``,closeOnOutsideClick:o=!0}=e,s=document.createElement(`dialog`);return s.className=`drawer ${a}`.trim(),t&&(s.id=t),r&&(s.dataset.direction=r),o||(s.dataset.closeOnOutsideClick=`false`),s.innerHTML=`
|
|
3
|
+
${i?`<div class="drawer-handle"></div>`:``}
|
|
3
4
|
<div class="drawer-content">${n}</div>
|
|
4
|
-
`,
|
|
5
|
+
`,s.addEventListener(`click`,e=>{e.target===s&&s.dataset.closeOnOutsideClick!==`false`&&s.close()}),s}function c(e){return document.body.appendChild(e),e}function l(t){e(t)?.remove()}function u(){let e=e=>{let t=e.target;t.matches(`dialog.drawer`)&&t.dataset.closeOnOutsideClick!==`false`&&t.close()};return document.addEventListener(`click`,e),()=>document.removeEventListener(`click`,e)}function d(t,n){let r=e(t);if(!r)return()=>{};let{onOpen:i,onClose:a,onCancel:o}=n,s=()=>a?.(),c=()=>o?.(),l=new MutationObserver(e=>{for(let t of e)t.attributeName===`open`&&r.open&&i?.()});return r.addEventListener(`close`,s),r.addEventListener(`cancel`,c),l.observe(r,{attributes:!0}),()=>{r.removeEventListener(`close`,s),r.removeEventListener(`cancel`,c),l.disconnect()}}function f(e,t){let n=t?.closeOnOutsideClick??!0;return{id:e,className:`drawer`,"data-close-on-outside-click":n?void 0:`false`,onClick:e=>{n&&e.target===e.currentTarget&&e.currentTarget.close()}}}exports.close=n,exports.closeAll=r,exports.create=s,exports.getOpen=a,exports.getTop=o,exports.init=u,exports.isOpen=i,exports.mount=c,exports.open=t,exports.props=f,exports.subscribe=d,exports.unmount=l;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
//#region src/index.d.ts
|
|
2
|
-
|
|
3
|
-
* CSS Drawer - Headless drawer component
|
|
4
|
-
* Works with any framework: React, Vue, Svelte, vanilla JS
|
|
5
|
-
*/
|
|
2
|
+
|
|
6
3
|
type DrawerElement = HTMLDialogElement;
|
|
7
4
|
type DrawerRef = string | DrawerElement | null | undefined;
|
|
5
|
+
type DrawerDirection = 'bottom' | 'top' | 'left' | 'right' | 'modal';
|
|
8
6
|
interface CreateDrawerOptions {
|
|
9
7
|
/** Drawer ID */
|
|
10
8
|
id?: string;
|
|
11
9
|
/** HTML content for the drawer */
|
|
12
10
|
content?: string;
|
|
11
|
+
/** Direction the drawer opens from (default: 'bottom') */
|
|
12
|
+
direction?: DrawerDirection;
|
|
13
13
|
/** Include drag handle (default: true) */
|
|
14
14
|
handle?: boolean;
|
|
15
15
|
/** Additional CSS classes */
|
|
@@ -85,5 +85,5 @@ declare function props(id: string, options?: {
|
|
|
85
85
|
readonly onClick: (e: MouseEvent) => void;
|
|
86
86
|
};
|
|
87
87
|
//#endregion
|
|
88
|
-
export { CreateDrawerOptions, DrawerElement, DrawerEventHandlers, DrawerRef, close, closeAll, create, getOpen, getTop, init, isOpen, mount, open, props, subscribe, unmount };
|
|
88
|
+
export { CreateDrawerOptions, DrawerDirection, DrawerElement, DrawerEventHandlers, DrawerRef, close, closeAll, create, getOpen, getTop, init, isOpen, mount, open, props, subscribe, unmount };
|
|
89
89
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AA8DiB,KArBL,aAAA,GAAgB,iBAqBQ;AAuBpB,KA1CJ,SAAA,GA0CiB,MAAA,GA1CI,aA0CK,GAAA,IAAA,GAAA,SAAA;AAQtB,KAhDJ,eAAA,GAgDkB,QAAS,GAAA,KAAA,GAAA,MAAA,GAAA,OAAA,GAAA,OAAA;AAQvB,UAtDC,mBAAA,CAsDO;EAQR;EAQA,EAAA,CAAA,EAAA,MAAO;EAOP;EAQA,OAAA,CAAA,EAAM,MAAA;EA6BN;EAQA,SAAA,CAAO,EApHT,eAoHkB;EAUhB;EAgBA,MAAA,CAAA,EAAA,OAAS;EAoCT;;;;;UAzKC,mBAAA;;;;;;;;;;;iBAuBD,IAAA,SAAa;;;;iBAQb,KAAA,SAAc;;;;iBAQd,QAAA,CAAA;;;;iBAQA,MAAA,SAAe;;;;iBAQf,OAAA,CAAA,GAAW;;;;iBAOX,MAAA,CAAA,GAAU;;;;iBAQV,MAAA,WAAgB,sBAA2B;;;;iBA6B3C,KAAA,SAAc,gBAAgB;;;;iBAQ9B,OAAA,SAAgB;;;;;;iBAUhB,IAAA,CAAA;;;;;iBAgBA,SAAA,SACN,qBACE;;;;;iBAkCI,KAAA;;;;;;wBAMC"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
//#region src/index.d.ts
|
|
2
|
-
|
|
3
|
-
* CSS Drawer - Headless drawer component
|
|
4
|
-
* Works with any framework: React, Vue, Svelte, vanilla JS
|
|
5
|
-
*/
|
|
2
|
+
|
|
6
3
|
type DrawerElement = HTMLDialogElement;
|
|
7
4
|
type DrawerRef = string | DrawerElement | null | undefined;
|
|
5
|
+
type DrawerDirection = 'bottom' | 'top' | 'left' | 'right' | 'modal';
|
|
8
6
|
interface CreateDrawerOptions {
|
|
9
7
|
/** Drawer ID */
|
|
10
8
|
id?: string;
|
|
11
9
|
/** HTML content for the drawer */
|
|
12
10
|
content?: string;
|
|
11
|
+
/** Direction the drawer opens from (default: 'bottom') */
|
|
12
|
+
direction?: DrawerDirection;
|
|
13
13
|
/** Include drag handle (default: true) */
|
|
14
14
|
handle?: boolean;
|
|
15
15
|
/** Additional CSS classes */
|
|
@@ -85,5 +85,5 @@ declare function props(id: string, options?: {
|
|
|
85
85
|
readonly onClick: (e: MouseEvent) => void;
|
|
86
86
|
};
|
|
87
87
|
//#endregion
|
|
88
|
-
export { CreateDrawerOptions, DrawerElement, DrawerEventHandlers, DrawerRef, close, closeAll, create, getOpen, getTop, init, isOpen, mount, open, props, subscribe, unmount };
|
|
88
|
+
export { CreateDrawerOptions, DrawerDirection, DrawerElement, DrawerEventHandlers, DrawerRef, close, closeAll, create, getOpen, getTop, init, isOpen, mount, open, props, subscribe, unmount };
|
|
89
89
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AA8DiB,KArBL,aAAA,GAAgB,iBAqBQ;AAuBpB,KA1CJ,SAAA,GA0CiB,MAAA,GA1CI,aA0CK,GAAA,IAAA,GAAA,SAAA;AAQtB,KAhDJ,eAAA,GAgDkB,QAAS,GAAA,KAAA,GAAA,MAAA,GAAA,OAAA,GAAA,OAAA;AAQvB,UAtDC,mBAAA,CAsDO;EAQR;EAQA,EAAA,CAAA,EAAA,MAAO;EAOP;EAQA,OAAA,CAAA,EAAM,MAAA;EA6BN;EAQA,SAAA,CAAO,EApHT,eAoHkB;EAUhB;EAgBA,MAAA,CAAA,EAAA,OAAS;EAoCT;;;;;UAzKC,mBAAA;;;;;;;;;;;iBAuBD,IAAA,SAAa;;;;iBAQb,KAAA,SAAc;;;;iBAQd,QAAA,CAAA;;;;iBAQA,MAAA,SAAe;;;;iBAQf,OAAA,CAAA,GAAW;;;;iBAOX,MAAA,CAAA,GAAU;;;;iBAQV,MAAA,WAAgB,sBAA2B;;;;iBA6B3C,KAAA,SAAc,gBAAgB;;;;iBAQ9B,OAAA,SAAgB;;;;;;iBAUhB,IAAA,CAAA;;;;;iBAgBA,SAAA,SACN,qBACE;;;;;iBAkCI,KAAA;;;;;;wBAMC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import"./drawer-CiHZcyXE.mjs";import './drawer-CXCJQa45.css';
|
|
2
|
+
if(typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}function e(e){return e?typeof e==`string`?document.getElementById(e):e:null}function t(t){e(t)?.showModal()}function n(t){e(t)?.close()}function r(){Array.from(document.querySelectorAll(`dialog.drawer[open]`)).reverse().forEach(e=>e.close())}function i(t){return e(t)?.open??!1}function a(){return Array.from(document.querySelectorAll(`dialog.drawer[open]`))}function o(){let e=a();return e[e.length-1]??null}function s(e={}){let{id:t,content:n=``,direction:r,handle:i=!0,className:a=``,closeOnOutsideClick:o=!0}=e,s=document.createElement(`dialog`);return s.className=`drawer ${a}`.trim(),t&&(s.id=t),r&&(s.dataset.direction=r),o||(s.dataset.closeOnOutsideClick=`false`),s.innerHTML=`
|
|
3
|
+
${i?`<div class="drawer-handle"></div>`:``}
|
|
3
4
|
<div class="drawer-content">${n}</div>
|
|
4
|
-
`,
|
|
5
|
+
`,s.addEventListener(`click`,e=>{e.target===s&&s.dataset.closeOnOutsideClick!==`false`&&s.close()}),s}function c(e){return document.body.appendChild(e),e}function l(t){e(t)?.remove()}function u(){let e=e=>{let t=e.target;t.matches(`dialog.drawer`)&&t.dataset.closeOnOutsideClick!==`false`&&t.close()};return document.addEventListener(`click`,e),()=>document.removeEventListener(`click`,e)}function d(t,n){let r=e(t);if(!r)return()=>{};let{onOpen:i,onClose:a,onCancel:o}=n,s=()=>a?.(),c=()=>o?.(),l=new MutationObserver(e=>{for(let t of e)t.attributeName===`open`&&r.open&&i?.()});return r.addEventListener(`close`,s),r.addEventListener(`cancel`,c),l.observe(r,{attributes:!0}),()=>{r.removeEventListener(`close`,s),r.removeEventListener(`cancel`,c),l.disconnect()}}function f(e,t){let n=t?.closeOnOutsideClick??!0;return{id:e,className:`drawer`,"data-close-on-outside-click":n?void 0:`false`,onClick:e=>{n&&e.target===e.currentTarget&&e.currentTarget.close()}}}export{n as close,r as closeAll,s as create,a as getOpen,o as getTop,u as init,i as isOpen,c as mount,t as open,f as props,d as subscribe,l as unmount};
|
|
5
6
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["open"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * CSS Drawer - Headless drawer component\n * Works with any framework: React, Vue, Svelte, vanilla JS\n */\n\n/* ===== Auto-enable accessibility for stacked drawers ===== */\nif (typeof window !== 'undefined') {\n const updateInertState = () => {\n const openDrawers = Array.from(\n document.querySelectorAll<HTMLDialogElement>('dialog.drawer[open]')\n )\n openDrawers.forEach((drawer, index) => {\n if (index === openDrawers.length - 1) {\n drawer.removeAttribute('inert')\n } else {\n drawer.setAttribute('inert', '')\n }\n })\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (\n mutation.type === 'attributes' &&\n mutation.attributeName === 'open' &&\n (mutation.target as HTMLElement).classList.contains('drawer')\n ) {\n updateInertState()\n break\n }\n }\n })\n\n observer.observe(document.body, {\n subtree: true,\n attributes: true,\n attributeFilter: ['open'],\n })\n}\n\nexport type DrawerElement = HTMLDialogElement\n\nexport type DrawerRef = string | DrawerElement | null | undefined\n\nexport interface CreateDrawerOptions {\n /** Drawer ID */\n id?: string\n /** HTML content for the drawer */\n content?: string\n /** Include drag handle (default: true) */\n handle?: boolean\n /** Additional CSS classes */\n className?: string\n /** Close when clicking outside (default: true) */\n closeOnOutsideClick?: boolean\n}\n\nexport interface DrawerEventHandlers {\n /** Called when drawer opens */\n onOpen?: () => void\n /** Called when drawer closes */\n onClose?: () => void\n /** Called when drawer is cancelled (backdrop click or Escape) */\n onCancel?: () => void\n}\n\n/**\n * Resolve a drawer reference to an element\n */\nfunction resolve(drawer: DrawerRef): DrawerElement | null {\n if (!drawer) return null\n if (typeof drawer === 'string') {\n return document.getElementById(drawer) as DrawerElement | null\n }\n return drawer\n}\n\n/**\n * Open a drawer by ID or element reference\n */\nexport function open(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.showModal()\n}\n\n/**\n * Close a drawer by ID or element reference\n */\nexport function close(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.close()\n}\n\n/**\n * Close all open drawers (in reverse DOM order for proper animation)\n */\nexport function closeAll(): void {\n const drawers = Array.from(document.querySelectorAll<DrawerElement>('dialog.drawer[open]'))\n drawers.reverse().forEach((d) => d.close())\n}\n\n/**\n * Check if a drawer is open\n */\nexport function isOpen(drawer: DrawerRef): boolean {\n const el = resolve(drawer)\n return el?.open ?? false\n}\n\n/**\n * Get all open drawers\n */\nexport function getOpen(): DrawerElement[] {\n return Array.from(document.querySelectorAll<DrawerElement>('dialog.drawer[open]'))\n}\n\n/**\n * Get the topmost open drawer\n */\nexport function getTop(): DrawerElement | null {\n const open = getOpen()\n return open[open.length - 1] ?? null\n}\n\n/**\n * Create a drawer element programmatically\n */\nexport function create(options: CreateDrawerOptions = {}): DrawerElement {\n const { id, content = '', handle = true, className = '', closeOnOutsideClick = true } = options\n\n const dialog = document.createElement('dialog') as DrawerElement\n dialog.className = `drawer ${className}`.trim()\n if (id) dialog.id = id\n if (!closeOnOutsideClick) {\n dialog.dataset.closeOnOutsideClick = 'false'\n }\n\n dialog.innerHTML = `\n ${handle ? '<div class=\"drawer-handle\"></div>' : ''}\n <div class=\"drawer-content\">${content}</div>\n `\n\n // Backdrop click to close (respects data attribute)\n dialog.addEventListener('click', (e) => {\n if (e.target === dialog && dialog.dataset.closeOnOutsideClick !== 'false') {\n dialog.close()\n }\n })\n\n return dialog\n}\n\n/**\n * Mount a drawer to the DOM (appends to body)\n */\nexport function mount(drawer: DrawerElement): DrawerElement {\n document.body.appendChild(drawer)\n return drawer\n}\n\n/**\n * Unmount a drawer from the DOM\n */\nexport function unmount(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.remove()\n}\n\n/**\n * Initialize global backdrop-click-to-close behavior\n * Alternative to adding onclick to each drawer\n * Respects data-close-on-outside-click=\"false\" attribute\n */\nexport function init(): () => void {\n const handler = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n if (target.matches('dialog.drawer') && (target as DrawerElement).dataset.closeOnOutsideClick !== 'false') {\n ;(target as DrawerElement).close()\n }\n }\n\n document.addEventListener('click', handler)\n return () => document.removeEventListener('click', handler)\n}\n\n/**\n * Subscribe to drawer events\n * @returns Cleanup function\n */\nexport function subscribe(\n drawer: DrawerRef,\n handlers: DrawerEventHandlers\n): () => void {\n const el = resolve(drawer)\n if (!el) return () => {}\n\n const { onOpen, onClose, onCancel } = handlers\n\n const handleClose = () => onClose?.()\n const handleCancel = () => onCancel?.()\n\n // Use MutationObserver to detect open attribute\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.attributeName === 'open' && el.open) {\n onOpen?.()\n }\n }\n })\n\n el.addEventListener('close', handleClose)\n el.addEventListener('cancel', handleCancel)\n observer.observe(el, { attributes: true })\n\n return () => {\n el.removeEventListener('close', handleClose)\n el.removeEventListener('cancel', handleCancel)\n observer.disconnect()\n }\n}\n\n/**\n * React-friendly hook helper - returns props to spread on dialog\n * Usage: <dialog {...drawer.props('my-drawer')} />\n */\nexport function props(id: string, options?: { closeOnOutsideClick?: boolean }) {\n const closeOnOutsideClick = options?.closeOnOutsideClick ?? true\n return {\n id,\n className: 'drawer',\n 'data-close-on-outside-click': closeOnOutsideClick ? undefined : 'false',\n onClick: (e: MouseEvent) => {\n if (closeOnOutsideClick && e.target === e.currentTarget) {\n ;(e.currentTarget as DrawerElement).close()\n }\n },\n } as const\n}\n\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["open"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * CSS Drawer - Headless drawer component\n * Works with any framework: React, Vue, Svelte, vanilla JS\n */\nimport './drawer.css'\n\n/* ===== Auto-enable accessibility for stacked drawers ===== */\nif (typeof window !== 'undefined') {\n const updateInertState = () => {\n const openDrawers = Array.from(\n document.querySelectorAll<HTMLDialogElement>('dialog.drawer[open]')\n )\n openDrawers.forEach((drawer, index) => {\n if (index === openDrawers.length - 1) {\n drawer.removeAttribute('inert')\n } else {\n drawer.setAttribute('inert', '')\n }\n })\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (\n mutation.type === 'attributes' &&\n mutation.attributeName === 'open' &&\n (mutation.target as HTMLElement).classList.contains('drawer')\n ) {\n updateInertState()\n break\n }\n }\n })\n\n observer.observe(document.body, {\n subtree: true,\n attributes: true,\n attributeFilter: ['open'],\n })\n}\n\nexport type DrawerElement = HTMLDialogElement\n\nexport type DrawerRef = string | DrawerElement | null | undefined\n\nexport type DrawerDirection = 'bottom' | 'top' | 'left' | 'right' | 'modal'\n\nexport interface CreateDrawerOptions {\n /** Drawer ID */\n id?: string\n /** HTML content for the drawer */\n content?: string\n /** Direction the drawer opens from (default: 'bottom') */\n direction?: DrawerDirection\n /** Include drag handle (default: true) */\n handle?: boolean\n /** Additional CSS classes */\n className?: string\n /** Close when clicking outside (default: true) */\n closeOnOutsideClick?: boolean\n}\n\nexport interface DrawerEventHandlers {\n /** Called when drawer opens */\n onOpen?: () => void\n /** Called when drawer closes */\n onClose?: () => void\n /** Called when drawer is cancelled (backdrop click or Escape) */\n onCancel?: () => void\n}\n\n/**\n * Resolve a drawer reference to an element\n */\nfunction resolve(drawer: DrawerRef): DrawerElement | null {\n if (!drawer) return null\n if (typeof drawer === 'string') {\n return document.getElementById(drawer) as DrawerElement | null\n }\n return drawer\n}\n\n/**\n * Open a drawer by ID or element reference\n */\nexport function open(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.showModal()\n}\n\n/**\n * Close a drawer by ID or element reference\n */\nexport function close(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.close()\n}\n\n/**\n * Close all open drawers (in reverse DOM order for proper animation)\n */\nexport function closeAll(): void {\n const drawers = Array.from(document.querySelectorAll<DrawerElement>('dialog.drawer[open]'))\n drawers.reverse().forEach((d) => d.close())\n}\n\n/**\n * Check if a drawer is open\n */\nexport function isOpen(drawer: DrawerRef): boolean {\n const el = resolve(drawer)\n return el?.open ?? false\n}\n\n/**\n * Get all open drawers\n */\nexport function getOpen(): DrawerElement[] {\n return Array.from(document.querySelectorAll<DrawerElement>('dialog.drawer[open]'))\n}\n\n/**\n * Get the topmost open drawer\n */\nexport function getTop(): DrawerElement | null {\n const open = getOpen()\n return open[open.length - 1] ?? null\n}\n\n/**\n * Create a drawer element programmatically\n */\nexport function create(options: CreateDrawerOptions = {}): DrawerElement {\n const { id, content = '', direction, handle = true, className = '', closeOnOutsideClick = true } = options\n\n const dialog = document.createElement('dialog') as DrawerElement\n dialog.className = `drawer ${className}`.trim()\n if (id) dialog.id = id\n if (direction) dialog.dataset.direction = direction\n if (!closeOnOutsideClick) {\n dialog.dataset.closeOnOutsideClick = 'false'\n }\n\n dialog.innerHTML = `\n ${handle ? '<div class=\"drawer-handle\"></div>' : ''}\n <div class=\"drawer-content\">${content}</div>\n `\n\n // Backdrop click to close (respects data attribute)\n dialog.addEventListener('click', (e) => {\n if (e.target === dialog && dialog.dataset.closeOnOutsideClick !== 'false') {\n dialog.close()\n }\n })\n\n return dialog\n}\n\n/**\n * Mount a drawer to the DOM (appends to body)\n */\nexport function mount(drawer: DrawerElement): DrawerElement {\n document.body.appendChild(drawer)\n return drawer\n}\n\n/**\n * Unmount a drawer from the DOM\n */\nexport function unmount(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.remove()\n}\n\n/**\n * Initialize global backdrop-click-to-close behavior\n * Alternative to adding onclick to each drawer\n * Respects data-close-on-outside-click=\"false\" attribute\n */\nexport function init(): () => void {\n const handler = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n if (target.matches('dialog.drawer') && (target as DrawerElement).dataset.closeOnOutsideClick !== 'false') {\n ;(target as DrawerElement).close()\n }\n }\n\n document.addEventListener('click', handler)\n return () => document.removeEventListener('click', handler)\n}\n\n/**\n * Subscribe to drawer events\n * @returns Cleanup function\n */\nexport function subscribe(\n drawer: DrawerRef,\n handlers: DrawerEventHandlers\n): () => void {\n const el = resolve(drawer)\n if (!el) return () => {}\n\n const { onOpen, onClose, onCancel } = handlers\n\n const handleClose = () => onClose?.()\n const handleCancel = () => onCancel?.()\n\n // Use MutationObserver to detect open attribute\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.attributeName === 'open' && el.open) {\n onOpen?.()\n }\n }\n })\n\n el.addEventListener('close', handleClose)\n el.addEventListener('cancel', handleCancel)\n observer.observe(el, { attributes: true })\n\n return () => {\n el.removeEventListener('close', handleClose)\n el.removeEventListener('cancel', handleCancel)\n observer.disconnect()\n }\n}\n\n/**\n * React-friendly hook helper - returns props to spread on dialog\n * Usage: <dialog {...drawer.props('my-drawer')} />\n */\nexport function props(id: string, options?: { closeOnOutsideClick?: boolean }) {\n const closeOnOutsideClick = options?.closeOnOutsideClick ?? true\n return {\n id,\n className: 'drawer',\n 'data-close-on-outside-click': closeOnOutsideClick ? undefined : 'false',\n onClick: (e: MouseEvent) => {\n if (closeOnOutsideClick && e.target === e.currentTarget) {\n ;(e.currentTarget as DrawerElement).close()\n }\n },\n } as const\n}\n\n"],"mappings":"8BAOA,GAAI,OAAO,OAAW,IAAa,CACjC,IAAM,MAAyB,CAC7B,IAAM,EAAc,MAAM,KACxB,SAAS,iBAAoC,sBAAsB,CACpE,CACD,EAAY,SAAS,EAAQ,IAAU,CACjC,IAAU,EAAY,OAAS,EACjC,EAAO,gBAAgB,QAAQ,CAE/B,EAAO,aAAa,QAAS,GAAG,EAElC,EAGa,IAAI,iBAAkB,GAAc,CACnD,IAAK,IAAM,KAAY,EACrB,GACE,EAAS,OAAS,cAClB,EAAS,gBAAkB,QAC1B,EAAS,OAAuB,UAAU,SAAS,SAAS,CAC7D,CACA,GAAkB,CAClB,QAGJ,CAEO,QAAQ,SAAS,KAAM,CAC9B,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,OAAO,CAC1B,CAAC,CAoCJ,SAAS,EAAQ,EAAyC,CAKxD,OAJK,EACD,OAAO,GAAW,SACb,SAAS,eAAe,EAAO,CAEjC,EAJa,KAUtB,SAAgB,EAAK,EAAyB,CACjC,EAAQ,EAAO,EACtB,WAAW,CAMjB,SAAgB,EAAM,EAAyB,CAClC,EAAQ,EAAO,EACtB,OAAO,CAMb,SAAgB,GAAiB,CACf,MAAM,KAAK,SAAS,iBAAgC,sBAAsB,CAAC,CACnF,SAAS,CAAC,QAAS,GAAM,EAAE,OAAO,CAAC,CAM7C,SAAgB,EAAO,EAA4B,CAEjD,OADW,EAAQ,EAAO,EACf,MAAQ,GAMrB,SAAgB,GAA2B,CACzC,OAAO,MAAM,KAAK,SAAS,iBAAgC,sBAAsB,CAAC,CAMpF,SAAgB,GAA+B,CAC7C,IAAMA,EAAO,GAAS,CACtB,OAAOA,EAAKA,EAAK,OAAS,IAAM,KAMlC,SAAgB,EAAO,EAA+B,EAAE,CAAiB,CACvE,GAAM,CAAE,KAAI,UAAU,GAAI,YAAW,SAAS,GAAM,YAAY,GAAI,sBAAsB,IAAS,EAE7F,EAAS,SAAS,cAAc,SAAS,CAoB/C,MAnBA,GAAO,UAAY,UAAU,IAAY,MAAM,CAC3C,IAAI,EAAO,GAAK,GAChB,IAAW,EAAO,QAAQ,UAAY,GACrC,IACH,EAAO,QAAQ,oBAAsB,SAGvC,EAAO,UAAY;MACf,EAAS,oCAAsC,GAAG;kCACtB,EAAQ;IAIxC,EAAO,iBAAiB,QAAU,GAAM,CAClC,EAAE,SAAW,GAAU,EAAO,QAAQ,sBAAwB,SAChE,EAAO,OAAO,EAEhB,CAEK,EAMT,SAAgB,EAAM,EAAsC,CAE1D,OADA,SAAS,KAAK,YAAY,EAAO,CAC1B,EAMT,SAAgB,EAAQ,EAAyB,CACpC,EAAQ,EAAO,EACtB,QAAQ,CAQd,SAAgB,GAAmB,CACjC,IAAM,EAAW,GAAkB,CACjC,IAAM,EAAS,EAAE,OACb,EAAO,QAAQ,gBAAgB,EAAK,EAAyB,QAAQ,sBAAwB,SAC7F,EAAyB,OAAO,EAKtC,OADA,SAAS,iBAAiB,QAAS,EAAQ,KAC9B,SAAS,oBAAoB,QAAS,EAAQ,CAO7D,SAAgB,EACd,EACA,EACY,CACZ,IAAM,EAAK,EAAQ,EAAO,CAC1B,GAAI,CAAC,EAAI,UAAa,GAEtB,GAAM,CAAE,SAAQ,UAAS,YAAa,EAEhC,MAAoB,KAAW,CAC/B,MAAqB,KAAY,CAGjC,EAAW,IAAI,iBAAkB,GAAc,CACnD,IAAK,IAAM,KAAY,EACjB,EAAS,gBAAkB,QAAU,EAAG,MAC1C,KAAU,EAGd,CAMF,OAJA,EAAG,iBAAiB,QAAS,EAAY,CACzC,EAAG,iBAAiB,SAAU,EAAa,CAC3C,EAAS,QAAQ,EAAI,CAAE,WAAY,GAAM,CAAC,KAE7B,CACX,EAAG,oBAAoB,QAAS,EAAY,CAC5C,EAAG,oBAAoB,SAAU,EAAa,CAC9C,EAAS,YAAY,EAQzB,SAAgB,EAAM,EAAY,EAA6C,CAC7E,IAAM,EAAsB,GAAS,qBAAuB,GAC5D,MAAO,CACL,KACA,UAAW,SACX,8BAA+B,EAAsB,IAAA,GAAY,QACjE,QAAU,GAAkB,CACtB,GAAuB,EAAE,SAAW,EAAE,eACtC,EAAE,cAAgC,OAAO,EAGhD"}
|
package/dist/react.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require('./
|
|
1
|
+
require(`./drawer-BWZh2Fyp.cjs`);require('./drawer-C21nwiwE.css');
|
|
2
2
|
let e=require(`react`),t=require(`react/jsx-runtime`);if(typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}const n=(0,e.createContext)({direction:void 0});function r(){return(0,e.useContext)(n)}function i({children:e,direction:r}){return(0,t.jsx)(n.Provider,{value:{direction:r},children:e})}const a=(0,e.forwardRef)(({children:n,className:i,open:a,onOpenChange:o,closeOnOutsideClick:s=!0,...c},l)=>{let{direction:u}=r(),d=(0,e.useRef)(null),f=l||d;return(0,e.useEffect)(()=>{let e=f.current;e&&(a&&!e.open?(e.showModal(),o?.(!0)):a===!1&&e.open&&e.close())},[a]),(0,t.jsx)(`dialog`,{ref:f,className:`drawer ${i??``}`.trim(),"data-direction":u,onClose:e=>{c.onClose?.(e),o?.(!1)},onClick:e=>{c.onClick?.(e),s&&e.target===e.currentTarget&&e.currentTarget.close()},...c,children:n})});a.displayName=`Drawer.Content`;const o=(0,e.forwardRef)(({className:e,...n},r)=>(0,t.jsx)(`div`,{ref:r,className:`drawer-handle ${e??``}`.trim(),"aria-hidden":`true`,...n}));o.displayName=`Drawer.Handle`;const s=(0,e.forwardRef)((e,n)=>(0,t.jsx)(`h2`,{ref:n,...e}));s.displayName=`Drawer.Title`;const c=(0,e.forwardRef)((e,n)=>(0,t.jsx)(`p`,{ref:n,...e}));c.displayName=`Drawer.Description`;const l={Root:i,Content:a,Handle:o,Title:s,Description:c};exports.Drawer=l;
|
package/dist/react.d.cts
CHANGED
|
@@ -3,7 +3,7 @@ import * as react0 from "react";
|
|
|
3
3
|
import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/react.d.ts
|
|
6
|
-
type Direction = 'bottom' | 'top' | 'left' | 'right';
|
|
6
|
+
type Direction = 'bottom' | 'top' | 'left' | 'right' | 'modal';
|
|
7
7
|
interface RootProps {
|
|
8
8
|
children: ReactNode;
|
|
9
9
|
/** Direction the drawer opens from */
|
package/dist/react.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
|
3
3
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/react.d.ts
|
|
6
|
-
type Direction = 'bottom' | 'top' | 'left' | 'right';
|
|
6
|
+
type Direction = 'bottom' | 'top' | 'left' | 'right' | 'modal';
|
|
7
7
|
interface RootProps {
|
|
8
8
|
children: ReactNode;
|
|
9
9
|
/** Direction the drawer opens from */
|
package/dist/react.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createContext as e,forwardRef as t,useContext as n,useEffect as r,useRef as i}from"react";import{jsx as a}from"react/jsx-runtime";import './
|
|
1
|
+
import"./drawer-CiHZcyXE.mjs";import{createContext as e,forwardRef as t,useContext as n,useEffect as r,useRef as i}from"react";import{jsx as a}from"react/jsx-runtime";import './drawer-CXCJQa45.css';
|
|
2
2
|
if(typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}const o=e({direction:void 0});function s(){return n(o)}function c({children:e,direction:t}){return a(o.Provider,{value:{direction:t},children:e})}const l=t(({children:e,className:t,open:n,onOpenChange:o,closeOnOutsideClick:c=!0,...l},u)=>{let{direction:d}=s(),f=i(null),p=u||f;return r(()=>{let e=p.current;e&&(n&&!e.open?(e.showModal(),o?.(!0)):n===!1&&e.open&&e.close())},[n]),a(`dialog`,{ref:p,className:`drawer ${t??``}`.trim(),"data-direction":d,onClose:e=>{l.onClose?.(e),o?.(!1)},onClick:e=>{l.onClick?.(e),c&&e.target===e.currentTarget&&e.currentTarget.close()},...l,children:e})});l.displayName=`Drawer.Content`;const u=t(({className:e,...t},n)=>a(`div`,{ref:n,className:`drawer-handle ${e??``}`.trim(),"aria-hidden":`true`,...t}));u.displayName=`Drawer.Handle`;const d=t((e,t)=>a(`h2`,{ref:t,...e}));d.displayName=`Drawer.Title`;const f=t((e,t)=>a(`p`,{ref:t,...e}));f.displayName=`Drawer.Description`;const p={Root:c,Content:l,Handle:u,Title:d,Description:f};export{p as Drawer};
|
|
3
3
|
//# sourceMappingURL=react.mjs.map
|
package/dist/react.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.mjs","names":[],"sources":["../src/react.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n forwardRef,\n useEffect,\n useRef,\n type ReactNode,\n type ComponentPropsWithoutRef,\n} from 'react'\nimport './drawer.css'\n\n/* ===== Auto-enable accessibility for stacked drawers ===== */\nif (typeof window !== 'undefined') {\n const updateInertState = () => {\n const openDrawers = Array.from(\n document.querySelectorAll<HTMLDialogElement>('dialog.drawer[open]')\n )\n openDrawers.forEach((drawer, index) => {\n const isTopmost = index === openDrawers.length - 1\n if (isTopmost) {\n drawer.removeAttribute('inert')\n } else {\n drawer.setAttribute('inert', '')\n }\n })\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (\n mutation.type === 'attributes' &&\n mutation.attributeName === 'open' &&\n (mutation.target as HTMLElement).classList.contains('drawer')\n ) {\n updateInertState()\n break\n }\n }\n })\n\n observer.observe(document.body, {\n subtree: true,\n attributes: true,\n attributeFilter: ['open'],\n })\n}\n\n/* ===== Types ===== */\ntype Direction = 'bottom' | 'top' | 'left' | 'right'\n\ninterface DrawerContextValue {\n direction?: Direction\n}\n\n/* ===== Context ===== */\nconst DrawerContext = createContext<DrawerContextValue>({ direction: undefined })\n\nfunction useDrawerContext() {\n return useContext(DrawerContext)\n}\n\n/* ===== Root ===== */\ninterface RootProps {\n children: ReactNode\n /** Direction the drawer opens from */\n direction?: Direction\n}\n\nfunction Root({ children, direction }: RootProps) {\n return (\n <DrawerContext.Provider value={{ direction }}>\n {children}\n </DrawerContext.Provider>\n )\n}\n\n/* ===== Content ===== */\ninterface ContentProps extends Omit<ComponentPropsWithoutRef<'dialog'>, 'open'> {\n /** Controlled open state */\n open?: boolean\n /** Called when open state changes */\n onOpenChange?: (open: boolean) => void\n /** Close when clicking outside the drawer (default: true) */\n closeOnOutsideClick?: boolean\n}\n\nconst Content = forwardRef<HTMLDialogElement, ContentProps>(\n ({ children, className, open, onOpenChange, closeOnOutsideClick = true, ...props }, ref) => {\n const { direction } = useDrawerContext()\n const internalRef = useRef<HTMLDialogElement>(null)\n const dialogRef = (ref as React.RefObject<HTMLDialogElement>) || internalRef\n\n useEffect(() => {\n const dialog = dialogRef.current\n if (!dialog) return\n\n if (open && !dialog.open) {\n dialog.showModal()\n onOpenChange?.(true)\n } else if (open === false && dialog.open) {\n dialog.close()\n }\n }, [open])\n\n return (\n <dialog\n ref={dialogRef}\n className={`drawer ${className ?? ''}`.trim()}\n data-direction={direction}\n onClose={(e) => {\n props.onClose?.(e)\n onOpenChange?.(false)\n }}\n onClick={(e) => {\n props.onClick?.(e)\n // Backdrop click - only if clicking the dialog element itself\n if (closeOnOutsideClick && e.target === e.currentTarget) {\n e.currentTarget.close()\n }\n }}\n {...props}\n >\n {children}\n </dialog>\n )\n }\n)\nContent.displayName = 'Drawer.Content'\n\n/* ===== Handle ===== */\ninterface HandleProps extends ComponentPropsWithoutRef<'div'> {}\n\nconst Handle = forwardRef<HTMLDivElement, HandleProps>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={`drawer-handle ${className ?? ''}`.trim()}\n aria-hidden=\"true\"\n {...props}\n />\n))\nHandle.displayName = 'Drawer.Handle'\n\n/* ===== Title ===== */\ninterface TitleProps extends ComponentPropsWithoutRef<'h2'> {}\n\nconst Title = forwardRef<HTMLHeadingElement, TitleProps>((props, ref) => (\n <h2 ref={ref} {...props} />\n))\nTitle.displayName = 'Drawer.Title'\n\n/* ===== Description ===== */\ninterface DescriptionProps extends ComponentPropsWithoutRef<'p'> {}\n\nconst Description = forwardRef<HTMLParagraphElement, DescriptionProps>((props, ref) => (\n <p ref={ref} {...props} />\n))\nDescription.displayName = 'Drawer.Description'\n\n/* ===== Namespace Export ===== */\nexport const Drawer = {\n Root,\n Content,\n Handle,\n Title,\n Description,\n}\n\nexport type {\n RootProps as DrawerRootProps,\n ContentProps as DrawerContentProps,\n HandleProps as DrawerHandleProps,\n TitleProps as DrawerTitleProps,\n DescriptionProps as DrawerDescriptionProps,\n Direction as DrawerDirection,\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"react.mjs","names":[],"sources":["../src/react.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n forwardRef,\n useEffect,\n useRef,\n type ReactNode,\n type ComponentPropsWithoutRef,\n} from 'react'\nimport './drawer.css'\n\n/* ===== Auto-enable accessibility for stacked drawers ===== */\nif (typeof window !== 'undefined') {\n const updateInertState = () => {\n const openDrawers = Array.from(\n document.querySelectorAll<HTMLDialogElement>('dialog.drawer[open]')\n )\n openDrawers.forEach((drawer, index) => {\n const isTopmost = index === openDrawers.length - 1\n if (isTopmost) {\n drawer.removeAttribute('inert')\n } else {\n drawer.setAttribute('inert', '')\n }\n })\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (\n mutation.type === 'attributes' &&\n mutation.attributeName === 'open' &&\n (mutation.target as HTMLElement).classList.contains('drawer')\n ) {\n updateInertState()\n break\n }\n }\n })\n\n observer.observe(document.body, {\n subtree: true,\n attributes: true,\n attributeFilter: ['open'],\n })\n}\n\n/* ===== Types ===== */\ntype Direction = 'bottom' | 'top' | 'left' | 'right' | 'modal'\n\ninterface DrawerContextValue {\n direction?: Direction\n}\n\n/* ===== Context ===== */\nconst DrawerContext = createContext<DrawerContextValue>({ direction: undefined })\n\nfunction useDrawerContext() {\n return useContext(DrawerContext)\n}\n\n/* ===== Root ===== */\ninterface RootProps {\n children: ReactNode\n /** Direction the drawer opens from */\n direction?: Direction\n}\n\nfunction Root({ children, direction }: RootProps) {\n return (\n <DrawerContext.Provider value={{ direction }}>\n {children}\n </DrawerContext.Provider>\n )\n}\n\n/* ===== Content ===== */\ninterface ContentProps extends Omit<ComponentPropsWithoutRef<'dialog'>, 'open'> {\n /** Controlled open state */\n open?: boolean\n /** Called when open state changes */\n onOpenChange?: (open: boolean) => void\n /** Close when clicking outside the drawer (default: true) */\n closeOnOutsideClick?: boolean\n}\n\nconst Content = forwardRef<HTMLDialogElement, ContentProps>(\n ({ children, className, open, onOpenChange, closeOnOutsideClick = true, ...props }, ref) => {\n const { direction } = useDrawerContext()\n const internalRef = useRef<HTMLDialogElement>(null)\n const dialogRef = (ref as React.RefObject<HTMLDialogElement>) || internalRef\n\n useEffect(() => {\n const dialog = dialogRef.current\n if (!dialog) return\n\n if (open && !dialog.open) {\n dialog.showModal()\n onOpenChange?.(true)\n } else if (open === false && dialog.open) {\n dialog.close()\n }\n }, [open])\n\n return (\n <dialog\n ref={dialogRef}\n className={`drawer ${className ?? ''}`.trim()}\n data-direction={direction}\n onClose={(e) => {\n props.onClose?.(e)\n onOpenChange?.(false)\n }}\n onClick={(e) => {\n props.onClick?.(e)\n // Backdrop click - only if clicking the dialog element itself\n if (closeOnOutsideClick && e.target === e.currentTarget) {\n e.currentTarget.close()\n }\n }}\n {...props}\n >\n {children}\n </dialog>\n )\n }\n)\nContent.displayName = 'Drawer.Content'\n\n/* ===== Handle ===== */\ninterface HandleProps extends ComponentPropsWithoutRef<'div'> {}\n\nconst Handle = forwardRef<HTMLDivElement, HandleProps>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={`drawer-handle ${className ?? ''}`.trim()}\n aria-hidden=\"true\"\n {...props}\n />\n))\nHandle.displayName = 'Drawer.Handle'\n\n/* ===== Title ===== */\ninterface TitleProps extends ComponentPropsWithoutRef<'h2'> {}\n\nconst Title = forwardRef<HTMLHeadingElement, TitleProps>((props, ref) => (\n <h2 ref={ref} {...props} />\n))\nTitle.displayName = 'Drawer.Title'\n\n/* ===== Description ===== */\ninterface DescriptionProps extends ComponentPropsWithoutRef<'p'> {}\n\nconst Description = forwardRef<HTMLParagraphElement, DescriptionProps>((props, ref) => (\n <p ref={ref} {...props} />\n))\nDescription.displayName = 'Drawer.Description'\n\n/* ===== Namespace Export ===== */\nexport const Drawer = {\n Root,\n Content,\n Handle,\n Title,\n Description,\n}\n\nexport type {\n RootProps as DrawerRootProps,\n ContentProps as DrawerContentProps,\n HandleProps as DrawerHandleProps,\n TitleProps as DrawerTitleProps,\n DescriptionProps as DrawerDescriptionProps,\n Direction as DrawerDirection,\n}\n"],"mappings":"uKAYA,GAAI,OAAO,OAAW,IAAa,CACjC,IAAM,MAAyB,CAC7B,IAAM,EAAc,MAAM,KACxB,SAAS,iBAAoC,sBAAsB,CACpE,CACD,EAAY,SAAS,EAAQ,IAAU,CACnB,IAAU,EAAY,OAAS,EAE/C,EAAO,gBAAgB,QAAQ,CAE/B,EAAO,aAAa,QAAS,GAAG,EAElC,EAGa,IAAI,iBAAkB,GAAc,CACnD,IAAK,IAAM,KAAY,EACrB,GACE,EAAS,OAAS,cAClB,EAAS,gBAAkB,QAC1B,EAAS,OAAuB,UAAU,SAAS,SAAS,CAC7D,CACA,GAAkB,CAClB,QAGJ,CAEO,QAAQ,SAAS,KAAM,CAC9B,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,OAAO,CAC1B,CAAC,CAWJ,MAAM,EAAgB,EAAkC,CAAE,UAAW,IAAA,GAAW,CAAC,CAEjF,SAAS,GAAmB,CAC1B,OAAO,EAAW,EAAc,CAUlC,SAAS,EAAK,CAAE,WAAU,aAAwB,CAChD,OACE,EAAC,EAAc,SAAA,CAAS,MAAO,CAAE,YAAW,CACzC,YACsB,CAc7B,MAAM,EAAU,GACb,CAAE,WAAU,YAAW,OAAM,eAAc,sBAAsB,GAAM,GAAG,GAAS,IAAQ,CAC1F,GAAM,CAAE,aAAc,GAAkB,CAClC,EAAc,EAA0B,KAAK,CAC7C,EAAa,GAA8C,EAcjE,OAZA,MAAgB,CACd,IAAM,EAAS,EAAU,QACpB,IAED,GAAQ,CAAC,EAAO,MAClB,EAAO,WAAW,CAClB,IAAe,GAAK,EACX,IAAS,IAAS,EAAO,MAClC,EAAO,OAAO,GAEf,CAAC,EAAK,CAAC,CAGR,EAAC,SAAA,CACC,IAAK,EACL,UAAW,UAAU,GAAa,KAAK,MAAM,CAC7C,iBAAgB,EAChB,QAAU,GAAM,CACd,EAAM,UAAU,EAAE,CAClB,IAAe,GAAM,EAEvB,QAAU,GAAM,CACd,EAAM,UAAU,EAAE,CAEd,GAAuB,EAAE,SAAW,EAAE,eACxC,EAAE,cAAc,OAAO,EAG3B,GAAI,EAEH,YACM,EAGd,CACD,EAAQ,YAAc,iBAKtB,MAAM,EAAS,GAAyC,CAAE,YAAW,GAAG,GAAS,IAC/E,EAAC,MAAA,CACM,MACL,UAAW,iBAAiB,GAAa,KAAK,MAAM,CACpD,cAAY,OACZ,GAAI,GACJ,CACF,CACF,EAAO,YAAc,gBAKrB,MAAM,EAAQ,GAA4C,EAAO,IAC/D,EAAC,KAAA,CAAQ,MAAK,GAAI,GAAS,CAC3B,CACF,EAAM,YAAc,eAKpB,MAAM,EAAc,GAAoD,EAAO,IAC7E,EAAC,IAAA,CAAO,MAAK,GAAI,GAAS,CAC1B,CACF,EAAY,YAAc,qBAG1B,MAAa,EAAS,CACpB,OACA,UACA,SACA,QACA,cACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "css-drawer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Vaul-quality drawer component using native <dialog> and pure CSS animations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -27,11 +27,12 @@
|
|
|
27
27
|
"default": "./dist/react.cjs"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
|
-
"./styles": "./
|
|
31
|
-
"./drawer.css": "./
|
|
30
|
+
"./styles": "./src/drawer.css",
|
|
31
|
+
"./drawer.css": "./src/drawer.css"
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
|
-
"dist"
|
|
34
|
+
"dist",
|
|
35
|
+
"src/drawer.css"
|
|
35
36
|
],
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"react": ">=18.0.0",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
--drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);
|
|
26
26
|
--drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);
|
|
27
27
|
--drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);
|
|
28
|
+
--drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);
|
|
28
29
|
|
|
29
30
|
/* Animation */
|
|
30
31
|
--drawer-duration: 0.5s;
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
--drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);
|
|
48
49
|
--drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);
|
|
49
50
|
--drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);
|
|
51
|
+
--drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.5);
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -169,6 +171,30 @@ body:has(.drawer[open]) {
|
|
|
169
171
|
box-shadow: var(--drawer-shadow-top);
|
|
170
172
|
}
|
|
171
173
|
|
|
174
|
+
/* Modal (centered) */
|
|
175
|
+
.drawer[data-direction="modal"] {
|
|
176
|
+
--_translate-closed: 0 0;
|
|
177
|
+
--_border-radius: var(--drawer-radius);
|
|
178
|
+
inset: 0;
|
|
179
|
+
margin: auto;
|
|
180
|
+
width: fit-content;
|
|
181
|
+
max-width: var(--drawer-max-width);
|
|
182
|
+
height: fit-content;
|
|
183
|
+
max-height: var(--drawer-max-height);
|
|
184
|
+
box-shadow: var(--drawer-shadow-modal);
|
|
185
|
+
scale: var(--drawer-nested-scale);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.drawer[data-direction="modal"][open] {
|
|
189
|
+
scale: 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@starting-style {
|
|
193
|
+
.drawer[data-direction="modal"][open] {
|
|
194
|
+
scale: var(--drawer-nested-scale);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
172
198
|
/* ===== AUTO-NESTING (up to 5 levels) ===== */
|
|
173
199
|
/* Uses sibling combinators to count open drawers */
|
|
174
200
|
|
|
@@ -302,6 +328,3 @@ body:has(.drawer[open]) {
|
|
|
302
328
|
filter: none;
|
|
303
329
|
}
|
|
304
330
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
/*# sourceMappingURL=react.css.map*/
|
package/dist/react.css.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"react.css","names":[],"sources":["../src/drawer.css"],"sourcesContent":["/* CSS Drawer - Vaul-quality drawer with auto-nesting and directions */\n\n:root {\n /* Visual */\n --drawer-bg: #fff;\n --drawer-radius: 24px;\n --drawer-backdrop: hsl(0 0% 0% / 0.4);\n --drawer-backdrop-blur: 4px;\n\n /* Sizing */\n --drawer-max-width: 500px;\n --drawer-max-height: 96dvh;\n\n /* Handle */\n --drawer-handle-bg: hsl(0 0% 80%);\n --drawer-handle-bg-hover: hsl(0 0% 60%);\n --drawer-handle-width: 48px;\n --drawer-handle-width-hover: 56px;\n --drawer-handle-height: 5px;\n --drawer-handle-padding-block: 1rem 0.5rem;\n --drawer-handle-padding-inline: 0;\n\n /* Shadows */\n --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.12), 0 -4px 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);\n\n /* Animation */\n --drawer-duration: 0.5s;\n --drawer-duration-close: 0.35s;\n --drawer-ease: cubic-bezier(0.32, 0.72, 0, 1);\n\n /* Nesting effects */\n --drawer-nested-scale: 0.94;\n --drawer-nested-offset: 20px;\n --drawer-nested-brightness: 0.92;\n --drawer-nested-backdrop: hsl(0 0% 0% / 0.15);\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --drawer-bg: hsl(0 0% 12%);\n --drawer-handle-bg: hsl(0 0% 35%);\n --drawer-handle-bg-hover: hsl(0 0% 50%);\n --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.4), 0 -4px 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);\n }\n}\n\n/* Background scale effect */\nbody {\n transition: scale var(--drawer-duration) var(--drawer-ease), border-radius var(--drawer-duration) var(--drawer-ease);\n transform-origin: center top;\n}\n\nbody:has(.drawer[open]) {\n overflow: hidden;\n scale: var(--drawer-nested-scale);\n border-radius: var(--drawer-radius);\n}\n\n/* Base drawer */\n.drawer {\n border: none;\n padding: 0;\n margin: 0;\n max-width: 100%;\n max-height: 100%;\n position: fixed;\n background: var(--drawer-bg);\n overflow: hidden;\n opacity: 0;\n transition:\n display var(--drawer-duration-close) allow-discrete,\n overlay var(--drawer-duration-close) allow-discrete,\n translate var(--drawer-duration-close) var(--drawer-ease),\n scale var(--drawer-duration-close) var(--drawer-ease),\n filter var(--drawer-duration-close) ease,\n opacity var(--drawer-duration-close) ease;\n\n /* Default: bottom */\n --_translate-closed: 0 100%;\n --_border-radius: var(--drawer-radius) var(--drawer-radius) 0 0;\n inset: auto 0 0 0;\n margin-inline: auto;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: auto;\n max-height: var(--drawer-max-height);\n border-radius: var(--drawer-border-radius, var(--_border-radius));\n box-shadow: var(--drawer-shadow-bottom);\n translate: var(--_translate-closed);\n}\n\n.drawer::backdrop {\n background: var(--drawer-backdrop);\n opacity: 0;\n backdrop-filter: blur(var(--drawer-backdrop-blur));\n -webkit-backdrop-filter: blur(var(--drawer-backdrop-blur));\n transition:\n display var(--drawer-duration-close) allow-discrete,\n overlay var(--drawer-duration-close) allow-discrete,\n opacity var(--drawer-duration-close) ease;\n}\n\n.drawer[open] {\n opacity: 1;\n translate: 0 0;\n transition:\n display var(--drawer-duration) allow-discrete,\n overlay var(--drawer-duration) allow-discrete,\n translate var(--drawer-duration) var(--drawer-ease),\n scale var(--drawer-duration) var(--drawer-ease),\n filter var(--drawer-duration) ease,\n opacity 0.15s ease;\n}\n\n.drawer[open]::backdrop {\n opacity: 1;\n transition: display var(--drawer-duration) allow-discrete, overlay var(--drawer-duration) allow-discrete, opacity 0.2s ease;\n}\n\n@starting-style {\n .drawer[open] { opacity: 0; translate: var(--_translate-closed); }\n .drawer[open]::backdrop { opacity: 0; }\n}\n\n/* ===== DIRECTIONS ===== */\n\n/* Right */\n.drawer[data-direction=\"right\"] {\n --_translate-closed: 100% 0;\n --_border-radius: var(--drawer-radius) 0 0 var(--drawer-radius);\n inset: 0 0 0 auto;\n margin: 0;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: 100dvh;\n max-height: 100dvh;\n box-shadow: var(--drawer-shadow-right);\n}\n\n/* Left */\n.drawer[data-direction=\"left\"] {\n --_translate-closed: -100% 0;\n --_border-radius: 0 var(--drawer-radius) var(--drawer-radius) 0;\n inset: 0 auto 0 0;\n margin: 0;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: 100dvh;\n max-height: 100dvh;\n box-shadow: var(--drawer-shadow-left);\n}\n\n/* Top */\n.drawer[data-direction=\"top\"] {\n --_translate-closed: 0 -100%;\n --_border-radius: 0 0 var(--drawer-radius) var(--drawer-radius);\n inset: 0 0 auto 0;\n margin-inline: auto;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: auto;\n max-height: var(--drawer-max-height);\n box-shadow: var(--drawer-shadow-top);\n}\n\n/* ===== AUTO-NESTING (up to 5 levels) ===== */\n/* Uses sibling combinators to count open drawers */\n\n/* 1+ open drawers after */\n.drawer[open]:has(~ .drawer[open]) {\n scale: var(--drawer-nested-scale);\n translate: 0 calc(-1 * var(--drawer-nested-offset));\n border-radius: var(--drawer-radius);\n filter: brightness(var(--drawer-nested-brightness));\n pointer-events: none;\n}\n\n/* 2+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-2 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 3+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-3 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 4+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-4 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 5+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-5 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* Lighter backdrop for stacked drawers */\n.drawer[open] ~ .drawer[open]::backdrop {\n background: var(--drawer-nested-backdrop);\n backdrop-filter: none;\n}\n\n/* Handle */\n.drawer-handle {\n display: flex;\n justify-content: center;\n padding-block: var(--drawer-handle-padding-block);\n padding-inline: var(--drawer-handle-padding-inline);\n cursor: grab;\n}\n\n.drawer-handle::before {\n content: '';\n width: var(--drawer-handle-width);\n height: var(--drawer-handle-height);\n background: var(--drawer-handle-bg);\n border-radius: 100px;\n transition: width 0.2s var(--drawer-ease), height 0.2s var(--drawer-ease), background 0.2s ease;\n}\n\n.drawer-handle:hover::before {\n width: var(--drawer-handle-width-hover);\n background: var(--drawer-handle-bg-hover);\n}\n\n/* Vertical handle for left/right drawers */\n.drawer[data-direction=\"left\"] .drawer-handle,\n.drawer[data-direction=\"right\"] .drawer-handle {\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding-block: var(--drawer-handle-padding-inline);\n padding-inline: var(--drawer-handle-padding-block);\n height: 100%;\n position: absolute;\n top: 0;\n writing-mode: vertical-lr;\n}\n\n.drawer[data-direction=\"left\"] .drawer-handle { right: 0; }\n.drawer[data-direction=\"right\"] .drawer-handle { left: 0; }\n\n.drawer[data-direction=\"left\"] .drawer-handle::before,\n.drawer[data-direction=\"right\"] .drawer-handle::before {\n width: var(--drawer-handle-height);\n height: var(--drawer-handle-width);\n}\n\n.drawer[data-direction=\"left\"] .drawer-handle:hover::before,\n.drawer[data-direction=\"right\"] .drawer-handle:hover::before {\n width: var(--drawer-handle-height);\n height: var(--drawer-handle-width-hover);\n}\n\n/* Content - structural only, no opinionated padding */\n.drawer-content {\n overflow-x: hidden;\n overflow-y: auto;\n overscroll-behavior: contain;\n flex: 1;\n min-height: 0;\n}\n\n/* Content sizing for directions */\n.drawer[data-direction=\"left\"] .drawer-content,\n.drawer[data-direction=\"right\"] .drawer-content {\n height: 100%;\n}\n\n/* Reduced motion */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n transition-duration: 0.01ms !important;\n }\n\n body:has(.drawer[open]) {\n scale: 1;\n }\n\n .drawer[open]:has(~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: 1;\n translate: 0 0;\n filter: none;\n }\n}\n"],"mappings":"AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|