@attrus-ds/core 0.1.1 → 0.3.1

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.
Files changed (82) hide show
  1. package/LICENSE +27 -27
  2. package/README.md +63 -9
  3. package/dist/assets/flags/Brazil-rounded.svg +15 -15
  4. package/dist/assets/flags/Brazil-square.svg +16 -16
  5. package/dist/assets/flags/Chile-rounded.svg +10 -10
  6. package/dist/assets/flags/Chile-square.svg +10 -10
  7. package/dist/assets/flags/Colombia-rounded.svg +9 -9
  8. package/dist/assets/flags/Colombia-square.svg +9 -9
  9. package/dist/assets/flags/EUA-rounded.svg +10 -10
  10. package/dist/assets/flags/EUA-square.svg +10 -10
  11. package/dist/assets/flags/EUR-rounded.svg +8 -8
  12. package/dist/assets/flags/EUR-square.svg +8 -8
  13. package/dist/assets/flags/Mexico-rounded.svg +18 -18
  14. package/dist/assets/flags/Mexico-square.svg +18 -18
  15. package/dist/assets/icon-attrus-dark.svg +2 -2
  16. package/dist/assets/icon-attrus-light.svg +2 -2
  17. package/dist/assets/logo-attrus-dark.svg +7 -7
  18. package/dist/assets/logo-attrus-light.svg +7 -7
  19. package/dist/css/components/buttons.css +301 -301
  20. package/dist/css/components/callout.css +195 -195
  21. package/dist/css/components/cards.css +272 -272
  22. package/dist/css/components/combobox.css +319 -303
  23. package/dist/css/components/empty-states.css +61 -61
  24. package/dist/css/components/inputs.css +975 -1027
  25. package/dist/css/components/number-input.css +138 -138
  26. package/dist/css/components/overlays.css +374 -405
  27. package/dist/css/components/primitives.css +314 -305
  28. package/dist/css/components/sidebar.css +179 -179
  29. package/dist/css/components/smart-search.css +91 -0
  30. package/dist/css/components/status-pills.css +207 -207
  31. package/dist/css/components/stepper.css +309 -309
  32. package/dist/css/components/table.css +602 -600
  33. package/dist/css/components/tabs.css +319 -319
  34. package/dist/css/components/toast.css +243 -239
  35. package/dist/css/components/tooltip.css +231 -226
  36. package/dist/css/components/topbar.css +148 -148
  37. package/dist/css/index.css +1 -0
  38. package/dist/css/tokens.css +666 -666
  39. package/dist/react/AppSidebar/AppSidebar.d.ts +50 -0
  40. package/dist/react/AppSidebar/AppSidebar.tsx +197 -0
  41. package/dist/react/AppTopbar/AppTopbar.d.ts +58 -0
  42. package/dist/react/AppTopbar/AppTopbar.tsx +203 -0
  43. package/dist/react/Atoms/Atoms.d.ts +149 -0
  44. package/dist/react/Atoms/Atoms.tsx +407 -0
  45. package/dist/react/Button/Button.d.ts +25 -0
  46. package/dist/react/Button/Button.tsx +83 -0
  47. package/dist/react/Callout/Callout.d.ts +29 -0
  48. package/dist/react/Callout/Callout.tsx +98 -0
  49. package/dist/react/Card/Card.d.ts +38 -0
  50. package/dist/react/Card/Card.tsx +93 -0
  51. package/dist/react/Combobox/Combobox.d.ts +52 -0
  52. package/dist/react/Combobox/Combobox.tsx +254 -0
  53. package/dist/react/DataTable/DataTable.d.ts +81 -0
  54. package/dist/react/DataTable/DataTable.tsx +308 -0
  55. package/dist/react/Drawer/Drawer.d.ts +35 -0
  56. package/dist/react/Drawer/Drawer.tsx +226 -0
  57. package/dist/react/Input/Input.d.ts +155 -0
  58. package/dist/react/Input/Input.tsx +478 -0
  59. package/dist/react/Modal/Modal.d.ts +27 -0
  60. package/dist/react/Modal/Modal.tsx +173 -0
  61. package/dist/react/NumberInput/NumberInput.d.ts +25 -0
  62. package/dist/react/NumberInput/NumberInput.tsx +117 -0
  63. package/dist/react/Pill/Pill.d.ts +31 -0
  64. package/dist/react/Pill/Pill.tsx +104 -0
  65. package/dist/react/SearchPill/SearchPill.d.ts +14 -0
  66. package/dist/react/SearchPill/SearchPill.tsx +40 -0
  67. package/dist/react/SmartSearch/SmartSearch.d.ts +65 -0
  68. package/dist/react/SmartSearch/SmartSearch.tsx +271 -0
  69. package/dist/react/Stepper/Stepper.d.ts +25 -0
  70. package/dist/react/Stepper/Stepper.tsx +94 -0
  71. package/dist/react/Tabs/Tabs.d.ts +32 -0
  72. package/dist/react/Tabs/Tabs.tsx +147 -0
  73. package/dist/react/Toast/Toast.d.ts +30 -0
  74. package/dist/react/Toast/Toast.tsx +94 -0
  75. package/dist/react/Tooltip/Tooltip.d.ts +23 -0
  76. package/dist/react/Tooltip/Tooltip.tsx +80 -0
  77. package/dist/react/ZeroState/ZeroState.d.ts +17 -0
  78. package/dist/react/ZeroState/ZeroState.tsx +45 -0
  79. package/dist/react/index.ts +35 -0
  80. package/dist/tokens/attrus-tokens.flat.json +1234 -1234
  81. package/dist/tokens/attrus-tokens.json +1089 -1089
  82. package/package.json +64 -54
@@ -1,405 +1,374 @@
1
- /* ============================================================
2
- OVERLAY SYSTEM — Modal + Drawer
3
- Both share scrim, entry/exit motion, focus management.
4
- All values from tokens.
5
- ============================================================ */
6
-
7
- /* =================== SCRIM =================== */
8
- .scrim {
9
- position: fixed;
10
- inset: 0;
11
- background: var(--color-scrim);
12
- backdrop-filter: blur(var(--scrim-blur));
13
- -webkit-backdrop-filter: blur(var(--scrim-blur));
14
- z-index: 9000;
15
- opacity: 0;
16
- pointer-events: none;
17
- transition: opacity var(--duration-base) var(--ease-standard);
18
- }
19
- .scrim.is-open {
20
- opacity: 1;
21
- pointer-events: auto;
22
- }
23
-
24
- /* Fallback for browsers without backdrop-filter — strengthen the tint to compensate. */
25
- @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
26
- .scrim { background: rgba(10, 15, 31, 0.55); }
27
- }
28
-
29
- /* =================== MODAL =================== */
30
- .modal {
31
- position: fixed;
32
- z-index: 9100;
33
- top: 50%; left: 50%;
34
- transform: translate(-50%, calc(-50% + 32px)) scale(0.96);
35
- width: min(560px, calc(100vw - var(--space-8)));
36
- max-height: calc(100vh - var(--space-12));
37
- display: flex;
38
- flex-direction: column;
39
- background: var(--color-background-surface);
40
- border-radius: var(--radius-lg);
41
- box-shadow: var(--shadow-2xl);
42
- opacity: 0;
43
- pointer-events: none;
44
- will-change: transform, opacity;
45
- /* Exit (default state) — quick fade + small slide */
46
- transition:
47
- opacity var(--duration-base) var(--ease-standard),
48
- transform var(--duration-base) var(--ease-standard);
49
- }
50
- .modal.is-open {
51
- opacity: 1;
52
- pointer-events: auto;
53
- transform: translate(-50%, -50%) scale(1);
54
- /* Entry — page-level pace, emphasized for the "land" feel */
55
- transition:
56
- opacity var(--duration-slower) var(--ease-standard),
57
- transform var(--duration-deliberate) var(--ease-emphasis);
58
- }
59
-
60
- /* Sizes */
61
- .modal-sm { width: min(420px, calc(100vw - var(--space-8))); }
62
- .modal-md { width: min(560px, calc(100vw - var(--space-8))); }
63
- .modal-lg { width: min(720px, calc(100vw - var(--space-8))); }
64
- .modal-xl { width: min(960px, calc(100vw - var(--space-8))); }
65
-
66
- /* Header */
67
- .modal-header {
68
- display: flex;
69
- align-items: flex-start;
70
- justify-content: space-between;
71
- gap: var(--space-4);
72
- padding: var(--space-6) var(--space-6) var(--space-4);
73
- }
74
- .modal-header-text { flex: 1; min-width: 0; }
75
- .modal-title {
76
- font-family: var(--font-sans);
77
- font-size: var(--fs-xl);
78
- font-weight: var(--w-bold);
79
- letter-spacing: var(--ls-title);
80
- line-height: var(--lh-tight);
81
- color: var(--color-foreground-default);
82
- margin: 0 0 var(--space-1);
83
- }
84
- .modal-subtitle {
85
- font-size: var(--fs-sm);
86
- color: var(--color-foreground-secondary);
87
- line-height: var(--lh-normal);
88
- margin: 0;
89
- }
90
- .modal-close {
91
- display: inline-flex;
92
- align-items: center;
93
- justify-content: center;
94
- width: 32px; height: 32px;
95
- background: transparent;
96
- border: none;
97
- border-radius: var(--radius-sm);
98
- color: var(--color-foreground-muted);
99
- cursor: pointer;
100
- flex-shrink: 0;
101
- transition: var(--transition-fast);
102
- }
103
- .modal-close:hover {
104
- background: var(--color-background-hover);
105
- color: var(--color-foreground-default);
106
- }
107
- .modal-close:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
108
- .modal-close i, .modal-close svg { width: 18px; height: 18px; stroke-width: var(--icon-stroke); }
109
-
110
- /* Body */
111
- .modal-body {
112
- padding: var(--space-2) var(--space-6) var(--space-6);
113
- overflow-y: auto;
114
- font-size: var(--fs-sm);
115
- color: var(--color-foreground-default);
116
- line-height: var(--lh-relaxed);
117
- flex: 1;
118
- }
119
- .modal-body p { margin: 0 0 var(--space-3); }
120
- .modal-body p:last-child { margin-bottom: 0; }
121
-
122
- /* Footer */
123
- .modal-footer {
124
- display: flex;
125
- align-items: center;
126
- justify-content: flex-end;
127
- gap: var(--space-3);
128
- padding: var(--space-4) var(--space-6) var(--space-6);
129
- border-top: 1px solid var(--color-border-subtle);
130
- margin-top: auto;
131
- flex-shrink: 0;
132
- }
133
- .modal-footer-spread { justify-content: space-between; }
134
- .modal-footer-aux {
135
- display: flex;
136
- align-items: center;
137
- gap: var(--space-2);
138
- font-size: var(--fs-xs);
139
- color: var(--color-foreground-muted);
140
- }
141
-
142
- /* Variant: destructive — top accent strip */
143
- .modal-destructive .modal-header {
144
- border-top: 3px solid var(--color-feedback-danger-foreground);
145
- padding-top: calc(var(--space-6) - 3px);
146
- border-top-left-radius: var(--radius-lg);
147
- border-top-right-radius: var(--radius-lg);
148
- }
149
- .modal-destructive .modal-icon {
150
- display: inline-flex;
151
- align-items: center;
152
- justify-content: center;
153
- width: 40px; height: 40px;
154
- background: var(--color-feedback-danger-background);
155
- color: var(--color-feedback-danger-foreground);
156
- border-radius: var(--radius-pill);
157
- margin-bottom: var(--space-3);
158
- }
159
- .modal-destructive .modal-icon i, .modal-destructive .modal-icon svg { width: 20px; height: 20px; stroke-width: var(--icon-stroke); }
160
-
161
-
162
- /* =================== DRAWER =================== */
163
- .drawer {
164
- position: fixed;
165
- z-index: 9100;
166
- background: var(--color-background-surface);
167
- display: flex;
168
- flex-direction: column;
169
- overflow-y: auto;
170
- overflow-x: hidden;
171
- box-shadow: var(--shadow-2xl);
172
- opacity: 0;
173
- pointer-events: none;
174
- will-change: transform, opacity;
175
- /* Exit — slide back out + fade */
176
- transition:
177
- transform var(--duration-slow) var(--ease-standard),
178
- opacity var(--duration-base) var(--ease-standard);
179
- }
180
- .drawer.is-open {
181
- opacity: 1;
182
- pointer-events: auto;
183
- /* Entry — page-level pace, emphasized */
184
- transition:
185
- transform var(--duration-deliberate) var(--ease-emphasis),
186
- opacity var(--duration-slower) var(--ease-standard);
187
- }
188
-
189
- /* Right (default).
190
- --drawer-w drives both the width AND stack offsets so size variants
191
- automatically scale the stacking math. --drawer-offset shifts the panel
192
- inward when stacked behind another drawer. */
193
- .drawer-right {
194
- --drawer-w: min(440px, 90vw);
195
- --drawer-offset: 0px;
196
- top: 0; right: 0; bottom: 0;
197
- width: var(--drawer-w);
198
- border-top-left-radius: var(--radius-xs);
199
- border-bottom-left-radius: var(--radius-xs);
200
- transform: translateX(100%);
201
- }
202
- .drawer-right.is-open { transform: translateX(calc(-1 * var(--drawer-offset))); }
203
-
204
- /* Left */
205
- .drawer-left {
206
- --drawer-w: min(320px, 85vw);
207
- --drawer-offset: 0px;
208
- top: 0; left: 0; bottom: 0;
209
- width: var(--drawer-w);
210
- border-top-right-radius: var(--radius-xs);
211
- border-bottom-right-radius: var(--radius-xs);
212
- transform: translateX(-100%);
213
- }
214
- .drawer-left.is-open { transform: translateX(var(--drawer-offset)); }
215
-
216
- /* =================== STACKED DRAWERS ===================
217
- Open a second drawer alongside the first instead of replacing it.
218
- --drawer-stack = how many slots inward to shift; 0 = frontmost panel.
219
- Slot width derived from --drawer-w so size variants stack correctly.
220
- Cap at 3 stacked panels for screens ≥1280px; 2 below.
221
- Usage: <aside class="drawer drawer-right drawer-md" data-stack="1"> */
222
- .drawer-right[data-stack="1"], .drawer-left[data-stack="1"] { --drawer-offset: var(--drawer-w); }
223
- .drawer-right[data-stack="2"], .drawer-left[data-stack="2"] { --drawer-offset: calc(var(--drawer-w) * 2); }
224
- .drawer-right[data-stack="3"], .drawer-left[data-stack="3"] { --drawer-offset: calc(var(--drawer-w) * 3); }
225
-
226
- /* Stacked panels lose their inner radius — they butt up against a sibling.
227
- Separator is placed on the edge that TOUCHES the adjacent panel.
228
- `:has(~)` only matches FOLLOWING siblings, so we apply rules from the
229
- panel that comes EARLIER in the DOM (the back panel, which is rendered
230
- before the front panel in modal-drawer.html). */
231
-
232
- /* Back panel (stack ≥ 1): when followed by an open front panel,
233
- only flatten the INNER edge (right side) and add separator there.
234
- Outer edge (left side) keeps the rounded corners. */
235
- .drawer-right[data-stack]:not([data-stack="0"]).is-open:has(~ .drawer-right[data-stack="0"].is-open) {
236
- border-top-right-radius: 0;
237
- border-bottom-right-radius: 0;
238
- border-right: 2px solid var(--color-border-default);
239
- }
240
- .drawer-left[data-stack]:not([data-stack="0"]).is-open:has(~ .drawer-left[data-stack="0"].is-open) {
241
- border-top-left-radius: 0;
242
- border-bottom-left-radius: 0;
243
- border-left: 2px solid var(--color-border-default);
244
- }
245
-
246
- /* Front panel (stack=0): when PRECEDED by an open back panel, flatten
247
- the inner-side radius and add the matching separator. Use the parent's
248
- `:has` to detect the configuration, then target the front. */
249
- :has(> .drawer-right[data-stack]:not([data-stack="0"]).is-open) > .drawer-right[data-stack="0"].is-open {
250
- border-top-left-radius: 0;
251
- border-bottom-left-radius: 0;
252
- border-left: 2px solid var(--color-border-default);
253
- }
254
- :has(> .drawer-left[data-stack]:not([data-stack="0"]).is-open) > .drawer-left[data-stack="0"].is-open {
255
- border-top-right-radius: 0;
256
- border-bottom-right-radius: 0;
257
- border-right: 2px solid var(--color-border-default);
258
- }
259
-
260
- /* Bottom */
261
- .drawer-bottom {
262
- left: 0; right: 0; bottom: 0;
263
- max-height: 80vh;
264
- border-top-left-radius: var(--radius-sm);
265
- border-top-right-radius: var(--radius-sm);
266
- transform: translateY(100%);
267
- }
268
- .drawer-bottom.is-open { transform: translateY(0); }
269
-
270
- /* Drawer sizes (right/left) — feed --drawer-w so stacking math scales */
271
- .drawer-sm { --drawer-w: min(360px, 90vw); width: var(--drawer-w); }
272
- .drawer-md { --drawer-w: min(440px, 90vw); width: var(--drawer-w); }
273
- .drawer-lg { --drawer-w: min(560px, 92vw); width: var(--drawer-w); }
274
- .drawer-xl { --drawer-w: min(720px, 94vw); width: var(--drawer-w); }
275
-
276
- /* Drawer parts (mirror modal vocabulary).
277
- Header and footer are STICKY with a liquid-glass background: body
278
- content scrolls underneath them with a frosted blur. The drawer
279
- itself is the scroll container (see .drawer overflow-y above). */
280
- .drawer-header {
281
- position: sticky;
282
- top: 0;
283
- z-index: 2;
284
- display: flex;
285
- align-items: flex-start;
286
- justify-content: space-between;
287
- gap: var(--space-4);
288
- padding: var(--space-6);
289
- background: var(--glass-white);
290
- -webkit-backdrop-filter: var(--glass-blur);
291
- backdrop-filter: var(--glass-blur);
292
- border-bottom: 1px solid var(--color-border-subtle);
293
- flex-shrink: 0;
294
- }
295
- @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
296
- .drawer-header, .drawer-footer { background: var(--color-background-surface); }
297
- }
298
- .drawer-header-text { flex: 1; min-width: 0; }
299
- .drawer-title {
300
- font-family: var(--font-sans);
301
- font-size: var(--fs-lg);
302
- font-weight: var(--w-bold);
303
- letter-spacing: var(--ls-title);
304
- line-height: var(--lh-tight);
305
- color: var(--color-foreground-default);
306
- margin: 0 0 var(--space-1);
307
- }
308
- .drawer-subtitle {
309
- font-size: var(--fs-sm);
310
- color: var(--color-foreground-secondary);
311
- line-height: var(--lh-normal);
312
- margin: 0;
313
- }
314
- .drawer-close {
315
- display: inline-flex;
316
- align-items: center;
317
- justify-content: center;
318
- align-self: flex-start;
319
- width: 32px; height: 32px;
320
- background: transparent;
321
- border: none;
322
- border-radius: var(--radius-sm);
323
- color: var(--color-foreground-muted);
324
- cursor: pointer;
325
- flex-shrink: 0;
326
- transition: var(--transition-fast);
327
- }
328
- .drawer-close:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
329
- .drawer-close:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
330
- .drawer-close i, .drawer-close svg { width: 18px; height: 18px; stroke-width: var(--icon-stroke); }
331
-
332
- /* Header lead tile — standardized icon container on the LEFT of the header.
333
- 40×40, top-aligned, consistent radius; caller sets bg/color per context. */
334
- .drawer-lead {
335
- width: 40px; height: 40px;
336
- flex-shrink: 0;
337
- align-self: flex-start;
338
- display: inline-flex;
339
- align-items: center;
340
- justify-content: center;
341
- border-radius: var(--radius-md);
342
- }
343
- .drawer-lead i, .drawer-lead svg { width: 20px; height: 20px; stroke-width: var(--icon-stroke); }
344
-
345
- .drawer-body {
346
- padding: var(--space-6);
347
- flex: 1 0 auto;
348
- font-size: var(--fs-sm);
349
- color: var(--color-foreground-default);
350
- line-height: var(--lh-relaxed);
351
- }
352
-
353
- .drawer-footer {
354
- position: sticky;
355
- bottom: 0;
356
- z-index: 2;
357
- display: flex;
358
- align-items: center;
359
- justify-content: flex-end;
360
- gap: var(--space-3);
361
- padding: var(--space-5) var(--space-6);
362
- background: var(--glass-white);
363
- -webkit-backdrop-filter: var(--glass-blur);
364
- backdrop-filter: var(--glass-blur);
365
- border-top: 1px solid var(--color-border-subtle);
366
- flex-shrink: 0;
367
- }
368
-
369
- /* Drawer-bottom drag handle (mobile-style) */
370
- .drawer-handle {
371
- position: sticky;
372
- top: 0;
373
- z-index: 3;
374
- display: flex;
375
- justify-content: center;
376
- padding: var(--space-3) 0 var(--space-1);
377
- background: var(--glass-white);
378
- -webkit-backdrop-filter: var(--glass-blur);
379
- backdrop-filter: var(--glass-blur);
380
- flex-shrink: 0;
381
- }
382
- /* When a handle precedes the header (bottom drawer), the header no longer
383
- needs its own glass top — they read as one frosted bar. */
384
- .drawer-handle + .drawer-header { border-top: 0; }
385
- .drawer-handle::before {
386
- content: "";
387
- width: 36px;
388
- height: 4px;
389
- background: var(--color-border-strong);
390
- border-radius: var(--radius-pill);
391
- }
392
-
393
- /* Reduced motion */
394
- @media (prefers-reduced-motion: reduce) {
395
- .scrim, .modal, .drawer, .modal.is-open, .drawer.is-open {
396
- transition: opacity var(--duration-instant) linear !important;
397
- }
398
- .modal.is-open { transform: translate(-50%, -50%); }
399
- .drawer-right.is-open,
400
- .drawer-left.is-open,
401
- .drawer-bottom.is-open { transform: none; }
402
- .drawer-right { transform: none; }
403
- .drawer-left { transform: none; }
404
- .drawer-bottom { transform: none; }
405
- }
1
+ /* ============================================================
2
+ OVERLAY SYSTEM — Modal + Drawer
3
+ Both share scrim, entry/exit motion, focus management.
4
+ All values from tokens.
5
+ ============================================================ */
6
+
7
+ /* =================== SCRIM =================== */
8
+ .scrim {
9
+ position: fixed;
10
+ inset: 0;
11
+ background: var(--color-scrim);
12
+ backdrop-filter: blur(var(--scrim-blur));
13
+ -webkit-backdrop-filter: blur(var(--scrim-blur));
14
+ z-index: 9000;
15
+ opacity: 0;
16
+ pointer-events: none;
17
+ transition: opacity var(--duration-base) var(--ease-standard);
18
+ }
19
+ .scrim.is-open {
20
+ opacity: 1;
21
+ pointer-events: auto;
22
+ }
23
+
24
+ /* Fallback for browsers without backdrop-filter — strengthen the tint to compensate. */
25
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
26
+ .scrim { background: rgba(10, 15, 31, 0.55); }
27
+ }
28
+
29
+ /* =================== MODAL =================== */
30
+ .modal {
31
+ position: fixed;
32
+ z-index: 9100;
33
+ top: 50%; left: 50%;
34
+ transform: translate(-50%, calc(-50% + 32px)) scale(0.96);
35
+ width: min(560px, calc(100vw - var(--space-8)));
36
+ max-height: calc(100vh - var(--space-12));
37
+ display: flex;
38
+ flex-direction: column;
39
+ background: var(--color-background-surface);
40
+ border-radius: var(--radius-lg);
41
+ box-shadow: var(--shadow-2xl);
42
+ opacity: 0;
43
+ pointer-events: none;
44
+ will-change: transform, opacity;
45
+ /* Exit (default state) — quick fade + small slide */
46
+ transition:
47
+ opacity var(--duration-base) var(--ease-standard),
48
+ transform var(--duration-base) var(--ease-standard);
49
+ }
50
+ .modal.is-open {
51
+ opacity: 1;
52
+ pointer-events: auto;
53
+ transform: translate(-50%, -50%) scale(1);
54
+ /* Entry — page-level pace, emphasized for the "land" feel */
55
+ transition:
56
+ opacity var(--duration-slower) var(--ease-standard),
57
+ transform var(--duration-deliberate) var(--ease-emphasis);
58
+ }
59
+
60
+ /* Sizes */
61
+ .modal-sm { width: min(420px, calc(100vw - var(--space-8))); }
62
+ .modal-md { width: min(560px, calc(100vw - var(--space-8))); }
63
+ .modal-lg { width: min(720px, calc(100vw - var(--space-8))); }
64
+ .modal-xl { width: min(960px, calc(100vw - var(--space-8))); }
65
+
66
+ /* Header */
67
+ .modal-header {
68
+ display: flex;
69
+ align-items: flex-start;
70
+ justify-content: space-between;
71
+ gap: var(--space-4);
72
+ padding: var(--space-6) var(--space-6) var(--space-4);
73
+ }
74
+ .modal-header-text { flex: 1; min-width: 0; }
75
+ .modal-title {
76
+ font-family: var(--font-sans);
77
+ font-size: var(--fs-xl);
78
+ font-weight: var(--w-bold);
79
+ letter-spacing: var(--ls-title);
80
+ line-height: var(--lh-tight);
81
+ color: var(--color-foreground-default);
82
+ margin: 0 0 var(--space-1);
83
+ }
84
+ .modal-subtitle {
85
+ font-size: var(--fs-sm);
86
+ color: var(--color-foreground-secondary);
87
+ line-height: var(--lh-normal);
88
+ margin: 0;
89
+ }
90
+ .modal-close {
91
+ display: inline-flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ width: 32px; height: 32px;
95
+ background: transparent;
96
+ border: none;
97
+ border-radius: var(--radius-sm);
98
+ color: var(--color-foreground-muted);
99
+ cursor: pointer;
100
+ flex-shrink: 0;
101
+ transition: var(--transition-fast);
102
+ }
103
+ .modal-close:hover {
104
+ background: var(--color-background-hover);
105
+ color: var(--color-foreground-default);
106
+ }
107
+ .modal-close:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
108
+ .modal-close i, .modal-close svg { width: 18px; height: 18px; stroke-width: var(--icon-stroke); }
109
+
110
+ /* Body */
111
+ .modal-body {
112
+ padding: var(--space-2) var(--space-6) var(--space-6);
113
+ overflow-y: auto;
114
+ font-size: var(--fs-sm);
115
+ color: var(--color-foreground-default);
116
+ line-height: var(--lh-relaxed);
117
+ flex: 1;
118
+ }
119
+ .modal-body p { margin: 0 0 var(--space-3); }
120
+ .modal-body p:last-child { margin-bottom: 0; }
121
+
122
+ /* Footer */
123
+ .modal-footer {
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: flex-end;
127
+ gap: var(--space-3);
128
+ padding: var(--space-4) var(--space-6) var(--space-6);
129
+ border-top: 1px solid var(--color-border-subtle);
130
+ margin-top: auto;
131
+ flex-shrink: 0;
132
+ }
133
+ .modal-footer-spread { justify-content: space-between; }
134
+ .modal-footer-aux {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: var(--space-2);
138
+ font-size: var(--fs-xs);
139
+ color: var(--color-foreground-muted);
140
+ }
141
+
142
+ /* Variant: destructive — top accent strip */
143
+ .modal-destructive .modal-header {
144
+ border-top: 3px solid var(--color-feedback-danger-foreground);
145
+ padding-top: calc(var(--space-6) - 3px);
146
+ border-top-left-radius: var(--radius-lg);
147
+ border-top-right-radius: var(--radius-lg);
148
+ }
149
+ .modal-destructive .modal-icon {
150
+ display: inline-flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ width: 40px; height: 40px;
154
+ background: var(--color-feedback-danger-background);
155
+ color: var(--color-feedback-danger-foreground);
156
+ border-radius: var(--radius-pill);
157
+ margin-bottom: var(--space-3);
158
+ }
159
+ .modal-destructive .modal-icon i, .modal-destructive .modal-icon svg { width: 20px; height: 20px; stroke-width: var(--icon-stroke); }
160
+
161
+
162
+ /* =================== DRAWER =================== */
163
+ .drawer {
164
+ position: fixed;
165
+ z-index: 9100;
166
+ background: var(--color-background-surface);
167
+ display: flex;
168
+ flex-direction: column;
169
+ overflow-y: auto;
170
+ overflow-x: hidden;
171
+ box-shadow: var(--shadow-2xl);
172
+ opacity: 0;
173
+ pointer-events: none;
174
+ will-change: transform, opacity;
175
+ /* Exit — slide back out + fade */
176
+ transition:
177
+ transform var(--duration-slow) var(--ease-standard),
178
+ opacity var(--duration-base) var(--ease-standard);
179
+ }
180
+ .drawer.is-open {
181
+ opacity: 1;
182
+ pointer-events: auto;
183
+ /* Entry — page-level pace, emphasized */
184
+ transition:
185
+ transform var(--duration-deliberate) var(--ease-emphasis),
186
+ opacity var(--duration-slower) var(--ease-standard);
187
+ }
188
+
189
+ /* Right (default). --drawer-w drives the width per size variant. */
190
+ .drawer-right {
191
+ --drawer-w: min(440px, 90vw); /* @kind spacing */
192
+ top: 0; right: 0; bottom: 0;
193
+ width: var(--drawer-w);
194
+ border-top-left-radius: var(--radius-xs);
195
+ border-bottom-left-radius: var(--radius-xs);
196
+ transform: translateX(100%);
197
+ }
198
+ .drawer-right.is-open { transform: translateX(0); }
199
+
200
+ /* Left */
201
+ .drawer-left {
202
+ --drawer-w: min(320px, 85vw); /* @kind spacing */
203
+ top: 0; left: 0; bottom: 0;
204
+ width: var(--drawer-w);
205
+ border-top-right-radius: var(--radius-xs);
206
+ border-bottom-right-radius: var(--radius-xs);
207
+ transform: translateX(-100%);
208
+ }
209
+ .drawer-left.is-open { transform: translateX(0); }
210
+
211
+ /* Bottom */
212
+ .drawer-bottom {
213
+ left: 0; right: 0; bottom: 0;
214
+ max-height: 80vh;
215
+ border-top-left-radius: var(--radius-sm);
216
+ border-top-right-radius: var(--radius-sm);
217
+ transform: translateY(100%);
218
+ }
219
+ .drawer-bottom.is-open { transform: translateY(0); }
220
+
221
+ /* Drawer sizes (right/left) — feed --drawer-w so widths scale per variant */
222
+ .drawer-sm { --drawer-w: min(360px, 90vw); /* @kind spacing */ width: var(--drawer-w); }
223
+ .drawer-md { --drawer-w: min(440px, 90vw); /* @kind spacing */ width: var(--drawer-w); }
224
+ .drawer-lg { --drawer-w: min(560px, 92vw); /* @kind spacing */ width: var(--drawer-w); }
225
+ .drawer-xl { --drawer-w: min(720px, 94vw); /* @kind spacing */ width: var(--drawer-w); }
226
+
227
+ /* Drawer parts (mirror modal vocabulary).
228
+ Header and footer are STICKY with a liquid-glass background: body
229
+ content scrolls underneath them with a frosted blur. The drawer
230
+ itself is the scroll container (see .drawer overflow-y above). */
231
+ .drawer-header {
232
+ position: sticky;
233
+ top: 0;
234
+ z-index: 2;
235
+ display: flex;
236
+ align-items: flex-start;
237
+ justify-content: space-between;
238
+ gap: var(--space-4);
239
+ padding: var(--space-6);
240
+ background: var(--glass-white);
241
+ -webkit-backdrop-filter: var(--glass-blur);
242
+ backdrop-filter: var(--glass-blur);
243
+ border-bottom: 1px solid var(--color-border-subtle);
244
+ flex-shrink: 0;
245
+ }
246
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
247
+ .drawer-header, .drawer-footer { background: var(--color-background-surface); }
248
+ }
249
+ .drawer-header-text { flex: 1; min-width: 0; }
250
+ .drawer-title {
251
+ font-family: var(--font-sans);
252
+ font-size: var(--fs-lg);
253
+ font-weight: var(--w-bold);
254
+ letter-spacing: var(--ls-title);
255
+ line-height: var(--lh-tight);
256
+ color: var(--color-foreground-default);
257
+ margin: 0 0 var(--space-1);
258
+ }
259
+ .drawer-subtitle {
260
+ font-size: var(--fs-sm);
261
+ color: var(--color-foreground-secondary);
262
+ line-height: var(--lh-normal);
263
+ margin: 0;
264
+ }
265
+ .drawer-close {
266
+ display: inline-flex;
267
+ align-items: center;
268
+ justify-content: center;
269
+ align-self: flex-start;
270
+ width: 32px; height: 32px;
271
+ background: transparent;
272
+ border: none;
273
+ border-radius: var(--radius-sm);
274
+ color: var(--color-foreground-muted);
275
+ cursor: pointer;
276
+ flex-shrink: 0;
277
+ transition: var(--transition-fast);
278
+ }
279
+ .drawer-close:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
280
+
281
+ /* Footer aux — left-side total/note when the footer is space-between (footerAux prop) */
282
+ .drawer-footer-aux { font-size: var(--fs-xs); color: var(--color-foreground-muted); min-width: 0; }
283
+
284
+ /* Mobile: footer actions stack full-width (doc §Header & Footer, variant 7) */
285
+ @media (max-width: 560px) {
286
+ .drawer-footer { flex-direction: column; align-items: stretch; gap: var(--space-2); padding-bottom: max(var(--space-4), env(safe-area-inset-bottom)); }
287
+ .drawer-footer .btn { width: 100%; justify-content: center; }
288
+ .drawer-footer-aux { text-align: center; margin-bottom: var(--space-1); }
289
+ }
290
+
291
+ /* Dense header — tight chrome for utility drawers (filters) */
292
+ .drawer-header.is-dense { padding: var(--space-3) var(--space-4); }
293
+ .drawer-header.is-dense .drawer-title { font-size: var(--fs-md); }
294
+
295
+ /* Header with a second control row (search pill, tabs) */
296
+ .drawer-header.has-extra { flex-wrap: wrap; }
297
+ .drawer-header > .drawer-header-extra { flex-basis: 100%; min-width: 0; }
298
+ .drawer-close:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
299
+ .drawer-close i, .drawer-close svg { width: 18px; height: 18px; stroke-width: var(--icon-stroke); }
300
+
301
+ /* Header lead tile — standardized icon container on the LEFT of the header.
302
+ 40×40, top-aligned, consistent radius; caller sets bg/color per context. */
303
+ .drawer-lead {
304
+ width: 40px; height: 40px;
305
+ flex-shrink: 0;
306
+ align-self: flex-start;
307
+ display: inline-flex;
308
+ align-items: center;
309
+ justify-content: center;
310
+ border-radius: var(--radius-md);
311
+ }
312
+ .drawer-lead i, .drawer-lead svg { width: 20px; height: 20px; stroke-width: var(--icon-stroke); }
313
+
314
+ .drawer-body {
315
+ padding: var(--space-6);
316
+ flex: 1 0 auto;
317
+ font-size: var(--fs-sm);
318
+ color: var(--color-foreground-default);
319
+ line-height: var(--lh-relaxed);
320
+ }
321
+
322
+ .drawer-footer {
323
+ position: sticky;
324
+ bottom: 0;
325
+ z-index: 2;
326
+ display: flex;
327
+ align-items: center;
328
+ justify-content: flex-end;
329
+ gap: var(--space-3);
330
+ padding: var(--space-5) var(--space-6);
331
+ background: var(--glass-white);
332
+ -webkit-backdrop-filter: var(--glass-blur);
333
+ backdrop-filter: var(--glass-blur);
334
+ border-top: 1px solid var(--color-border-subtle);
335
+ flex-shrink: 0;
336
+ }
337
+
338
+ /* Drawer-bottom drag handle (mobile-style) */
339
+ .drawer-handle {
340
+ position: sticky;
341
+ top: 0;
342
+ z-index: 3;
343
+ display: flex;
344
+ justify-content: center;
345
+ padding: var(--space-3) 0 var(--space-1);
346
+ background: var(--glass-white);
347
+ -webkit-backdrop-filter: var(--glass-blur);
348
+ backdrop-filter: var(--glass-blur);
349
+ flex-shrink: 0;
350
+ }
351
+ /* When a handle precedes the header (bottom drawer), the header no longer
352
+ needs its own glass top — they read as one frosted bar. */
353
+ .drawer-handle + .drawer-header { border-top: 0; }
354
+ .drawer-handle::before {
355
+ content: "";
356
+ width: 36px;
357
+ height: 4px;
358
+ background: var(--color-border-strong);
359
+ border-radius: var(--radius-pill);
360
+ }
361
+
362
+ /* Reduced motion */
363
+ @media (prefers-reduced-motion: reduce) {
364
+ .scrim, .modal, .drawer, .modal.is-open, .drawer.is-open {
365
+ transition: opacity var(--duration-instant) linear !important;
366
+ }
367
+ .modal.is-open { transform: translate(-50%, -50%); }
368
+ .drawer-right.is-open,
369
+ .drawer-left.is-open,
370
+ .drawer-bottom.is-open { transform: none; }
371
+ .drawer-right { transform: none; }
372
+ .drawer-left { transform: none; }
373
+ .drawer-bottom { transform: none; }
374
+ }