mtrl 0.6.3 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mtrl",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "A functional TypeScript/JavaScript component library with composable architecture based on Material Design 3",
5
5
  "author": "floor",
6
6
  "license": "MIT License",
@@ -60,9 +60,6 @@
60
60
  "scripts": {
61
61
  "start": "bun run server.js",
62
62
  "dev": "bun --watch server.js",
63
- "build:demo": "bun run demo/build.ts",
64
- "dev:demo": "bun run demo/build.ts --watch",
65
- "clean:demo": "rm -rf demo/dist",
66
63
  "build:js": "bun build demo/main.js --outfile=demo/dist/bundle.js --format=esm",
67
64
  "build:css": "sass src/styles/main.scss:demo/dist/styles.css --style=compressed",
68
65
  "build": "bun run build:js && bun run build:css",
@@ -390,7 +390,7 @@ $component: '#{base.$prefix}-button-group';
390
390
  // DARK THEME ADJUSTMENTS
391
391
  // =============================================================================
392
392
 
393
- body[data-theme-mode=dark] {
393
+ html[data-theme-mode=dark] {
394
394
  .#{$component} {
395
395
  &--outlined {
396
396
  > .#{base.$prefix}-button {
@@ -717,7 +717,7 @@ $component: '#{base.$prefix}-button';
717
717
  }
718
718
 
719
719
  // FIX: Disabled on dark theme (recommandations do not work as expected)
720
- body[data-theme-mode=dark] {
720
+ html[data-theme-mode=dark] {
721
721
  // Interactive states
722
722
  .#{$component} {
723
723
  &:disabled {
@@ -0,0 +1,611 @@
1
+ // src/styles/components/_drawer.scss
2
+ @use "../../styles/abstract/base" as base;
3
+ @use "../../styles/abstract/variables" as v;
4
+ @use "../../styles/abstract/functions" as f;
5
+ @use "../../styles/abstract/mixins" as m;
6
+ @use "../../styles/abstract/theme" as t;
7
+
8
+ $component: "#{base.$prefix}-drawer";
9
+
10
+ // Must match DRAWER_DEFAULTS.WIDTH in constants.ts
11
+ // Only used as a CSS fallback if --drawer-width is not set by JS
12
+ $default-width: 360px;
13
+
14
+ // ==========================================================================
15
+ // ROOT CONTAINER
16
+ // ==========================================================================
17
+
18
+ .#{$component} {
19
+ display: flex;
20
+ flex-direction: column;
21
+ position: relative;
22
+ height: 100%;
23
+ z-index: 1200;
24
+ pointer-events: none;
25
+
26
+ // --open modifier enables interaction
27
+ &--open {
28
+ pointer-events: auto;
29
+
30
+ .#{$component}__sheet {
31
+ transform: translateX(0);
32
+ visibility: visible;
33
+ }
34
+
35
+ .#{$component}__scrim {
36
+ pointer-events: auto;
37
+ }
38
+
39
+ .#{$component}__scrim--visible {
40
+ opacity: 1;
41
+ }
42
+ }
43
+
44
+ // ========================================================================
45
+ // POSITION VARIANTS
46
+ // ========================================================================
47
+
48
+ &--start {
49
+ .#{$component}__sheet {
50
+ left: 0;
51
+ right: auto;
52
+ border-top-right-radius: 16px;
53
+ border-bottom-right-radius: 16px;
54
+ border-top-left-radius: 0;
55
+ border-bottom-left-radius: 0;
56
+ transform: translateX(-100%);
57
+ }
58
+
59
+ @include m.rtl {
60
+ .#{$component}__sheet {
61
+ left: auto;
62
+ right: 0;
63
+ border-top-right-radius: 0;
64
+ border-bottom-right-radius: 0;
65
+ border-top-left-radius: 16px;
66
+ border-bottom-left-radius: 16px;
67
+ transform: translateX(100%);
68
+ }
69
+ }
70
+
71
+ &.#{$component}--open {
72
+ .#{$component}__sheet {
73
+ transform: translateX(0);
74
+ }
75
+ }
76
+ }
77
+
78
+ &--end {
79
+ .#{$component}__sheet {
80
+ left: auto;
81
+ right: 0;
82
+ border-top-left-radius: 16px;
83
+ border-bottom-left-radius: 16px;
84
+ border-top-right-radius: 0;
85
+ border-bottom-right-radius: 0;
86
+ transform: translateX(100%);
87
+ }
88
+
89
+ @include m.rtl {
90
+ .#{$component}__sheet {
91
+ left: 0;
92
+ right: auto;
93
+ border-top-left-radius: 0;
94
+ border-bottom-left-radius: 0;
95
+ border-top-right-radius: 16px;
96
+ border-bottom-right-radius: 16px;
97
+ transform: translateX(-100%);
98
+ }
99
+ }
100
+
101
+ &.#{$component}--open {
102
+ .#{$component}__sheet {
103
+ transform: translateX(0);
104
+ }
105
+ }
106
+ }
107
+
108
+ // ========================================================================
109
+ // VARIANT: STANDARD
110
+ // ========================================================================
111
+
112
+ &--standard {
113
+ position: relative;
114
+ z-index: auto;
115
+ pointer-events: auto;
116
+ flex-shrink: 0;
117
+ overflow: hidden;
118
+ // Closed: collapse to 0. --drawer-width is set inline by the JS component.
119
+ width: 0;
120
+ transition: width v.motion("duration-short4")
121
+ v.motion("easing-standard");
122
+
123
+ .#{$component}__sheet {
124
+ position: relative;
125
+ background-color: t.color("surface");
126
+ @include m.elevation(0);
127
+ }
128
+
129
+ // Open — expand to the concrete width value via custom property
130
+ &.#{$component}--open {
131
+ width: var(--drawer-width, $default-width);
132
+ }
133
+
134
+ // Border on the end edge (standard variant visual separator)
135
+ &.#{$component}--start {
136
+ .#{$component}__sheet {
137
+ border-right: 1px solid t.color("outline-variant");
138
+ border-top-right-radius: 0;
139
+ border-bottom-right-radius: 0;
140
+ }
141
+
142
+ @include m.rtl {
143
+ .#{$component}__sheet {
144
+ border-right: none;
145
+ border-left: 1px solid t.color("outline-variant");
146
+ border-top-left-radius: 0;
147
+ border-bottom-left-radius: 0;
148
+ }
149
+ }
150
+ }
151
+
152
+ &.#{$component}--end {
153
+ .#{$component}__sheet {
154
+ border-left: 1px solid t.color("outline-variant");
155
+ border-top-left-radius: 0;
156
+ border-bottom-left-radius: 0;
157
+ }
158
+
159
+ @include m.rtl {
160
+ .#{$component}__sheet {
161
+ border-left: none;
162
+ border-right: 1px solid t.color("outline-variant");
163
+ border-top-right-radius: 0;
164
+ border-bottom-right-radius: 0;
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ // ========================================================================
171
+ // VARIANT: MODAL
172
+ // ========================================================================
173
+
174
+ &--modal {
175
+ position: fixed;
176
+ top: 0;
177
+ left: 0;
178
+ right: 0;
179
+ bottom: 0;
180
+ z-index: 1200;
181
+
182
+ .#{$component}__sheet {
183
+ position: absolute;
184
+ top: 0;
185
+ bottom: 0;
186
+ background-color: t.color("surface-container-low");
187
+ @include m.elevation(1);
188
+ }
189
+ }
190
+
191
+ // ========================================================================
192
+ // SHEET (inner panel)
193
+ // ========================================================================
194
+
195
+ &__sheet {
196
+ display: flex;
197
+ flex-direction: column;
198
+ width: var(--drawer-width, $default-width);
199
+ max-width: calc(100vw - 56px);
200
+ height: 100%;
201
+ overflow-y: auto;
202
+ overflow-x: hidden;
203
+ box-sizing: border-box;
204
+ padding: 12px 0;
205
+ @include m.motion-transition(transform, visibility);
206
+
207
+ // Smooth scrollbar
208
+ scrollbar-width: thin;
209
+ scrollbar-color: t.color("outline-variant") transparent;
210
+ }
211
+
212
+ // ========================================================================
213
+ // SCRIM (modal overlay)
214
+ // ========================================================================
215
+
216
+ &__scrim {
217
+ position: fixed;
218
+ top: 0;
219
+ left: 0;
220
+ right: 0;
221
+ bottom: 0;
222
+ background-color: t.alpha("scrim", 0.32);
223
+ opacity: 0;
224
+ pointer-events: none;
225
+ z-index: -1;
226
+ @include m.motion-transition(opacity);
227
+ }
228
+
229
+ // ========================================================================
230
+ // HEADLINE
231
+ // ========================================================================
232
+
233
+ &__headline {
234
+ @include m.typography("title-small");
235
+ color: t.color("on-surface-variant");
236
+ padding: 16px 28px 0;
237
+ margin-bottom: 4px;
238
+ white-space: nowrap;
239
+ overflow: hidden;
240
+ text-overflow: ellipsis;
241
+ }
242
+
243
+ // ========================================================================
244
+ // ITEMS CONTAINER
245
+ // ========================================================================
246
+
247
+ &__items {
248
+ display: flex;
249
+ flex-direction: column;
250
+ flex: 1;
251
+ padding: 0;
252
+ margin: 0;
253
+ }
254
+
255
+ // ========================================================================
256
+ // NAVIGATION ITEM
257
+ // ========================================================================
258
+
259
+ &__item {
260
+ display: flex;
261
+ align-items: center;
262
+ position: relative;
263
+ width: calc(100% - 24px);
264
+ height: 56px;
265
+ margin: 0 12px;
266
+ padding: 0 24px 0 16px;
267
+ gap: 12px;
268
+ border: none;
269
+ border-radius: 28px;
270
+ background: none;
271
+ cursor: pointer;
272
+ color: t.color("on-surface-variant");
273
+ text-decoration: none;
274
+ box-sizing: border-box;
275
+ outline: none;
276
+ -webkit-tap-highlight-color: transparent;
277
+ @include m.typography("label-large");
278
+ @include m.motion-transition(background-color, color);
279
+
280
+ // Active indicator (background shape — positioned behind content)
281
+ .#{$component}__active-indicator {
282
+ position: absolute;
283
+ inset: 0;
284
+ border-radius: 28px;
285
+ background-color: transparent;
286
+ pointer-events: none;
287
+ @include m.motion-transition(background-color);
288
+ }
289
+
290
+ // ======================================================================
291
+ // HOVER STATE
292
+ // ======================================================================
293
+
294
+ &:hover:not([disabled]) {
295
+ .#{$component}__active-indicator {
296
+ background-color: t.alpha("on-surface", 0.08);
297
+ }
298
+ }
299
+
300
+ // ======================================================================
301
+ // FOCUS-VISIBLE STATE
302
+ // ======================================================================
303
+
304
+ &:focus-visible {
305
+ .#{$component}__active-indicator {
306
+ background-color: t.alpha("on-surface", 0.12);
307
+ }
308
+ }
309
+
310
+ // ======================================================================
311
+ // PRESSED STATE
312
+ // ======================================================================
313
+
314
+ &:active:not([disabled]) {
315
+ .#{$component}__active-indicator {
316
+ background-color: t.alpha("on-surface", 0.12);
317
+ }
318
+ }
319
+
320
+ // ======================================================================
321
+ // ACTIVE / SELECTED STATE
322
+ // ======================================================================
323
+
324
+ &--active {
325
+ color: t.color("on-secondary-container");
326
+
327
+ .#{$component}__active-indicator {
328
+ background-color: t.color("secondary-container");
329
+ }
330
+
331
+ &:hover:not([disabled]) {
332
+ .#{$component}__active-indicator {
333
+ background-color: t.color("secondary-container");
334
+
335
+ // Overlay hover state on top of active indicator
336
+ &::before {
337
+ content: "";
338
+ position: absolute;
339
+ inset: 0;
340
+ border-radius: inherit;
341
+ background-color: t.alpha(
342
+ "on-secondary-container",
343
+ 0.08
344
+ );
345
+ pointer-events: none;
346
+ }
347
+ }
348
+ }
349
+
350
+ &:focus-visible {
351
+ .#{$component}__active-indicator {
352
+ background-color: t.color("secondary-container");
353
+
354
+ &::before {
355
+ content: "";
356
+ position: absolute;
357
+ inset: 0;
358
+ border-radius: inherit;
359
+ background-color: t.alpha(
360
+ "on-secondary-container",
361
+ 0.12
362
+ );
363
+ pointer-events: none;
364
+ }
365
+ }
366
+ }
367
+
368
+ &:active:not([disabled]) {
369
+ .#{$component}__active-indicator {
370
+ background-color: t.color("secondary-container");
371
+
372
+ &::before {
373
+ content: "";
374
+ position: absolute;
375
+ inset: 0;
376
+ border-radius: inherit;
377
+ background-color: t.alpha(
378
+ "on-secondary-container",
379
+ 0.12
380
+ );
381
+ pointer-events: none;
382
+ }
383
+ }
384
+ }
385
+
386
+ .#{$component}__item-icon {
387
+ color: t.color("on-secondary-container");
388
+ }
389
+
390
+ .#{$component}__item-label {
391
+ color: t.color("on-secondary-container");
392
+ font-weight: 700;
393
+ }
394
+
395
+ .#{$component}__item-badge {
396
+ color: t.color("on-secondary-container");
397
+ }
398
+ }
399
+
400
+ // ======================================================================
401
+ // DISABLED STATE
402
+ // ======================================================================
403
+
404
+ &[disabled] {
405
+ cursor: default;
406
+ opacity: 0.38;
407
+ pointer-events: none;
408
+ }
409
+ }
410
+
411
+ // ========================================================================
412
+ // ITEM ICON
413
+ // ========================================================================
414
+
415
+ &__item-icon {
416
+ display: flex;
417
+ align-items: center;
418
+ justify-content: center;
419
+ flex-shrink: 0;
420
+ width: 24px;
421
+ height: 24px;
422
+ color: inherit;
423
+ position: relative;
424
+ z-index: 1;
425
+
426
+ svg {
427
+ width: 24px;
428
+ height: 24px;
429
+ fill: currentColor;
430
+ }
431
+ }
432
+
433
+ // ========================================================================
434
+ // ITEM LABEL
435
+ // ========================================================================
436
+
437
+ &__item-label {
438
+ flex: 1;
439
+ text-align: left;
440
+ white-space: nowrap;
441
+ overflow: hidden;
442
+ text-overflow: ellipsis;
443
+ position: relative;
444
+ z-index: 1;
445
+ color: inherit;
446
+
447
+ @include m.rtl {
448
+ text-align: right;
449
+ }
450
+ }
451
+
452
+ // ========================================================================
453
+ // ITEM BADGE
454
+ // ========================================================================
455
+
456
+ &__item-badge {
457
+ @include m.typography("label-large");
458
+ color: t.color("on-surface-variant");
459
+ margin-left: auto;
460
+ position: relative;
461
+ z-index: 1;
462
+ flex-shrink: 0;
463
+
464
+ @include m.rtl {
465
+ margin-left: 0;
466
+ margin-right: auto;
467
+ }
468
+ }
469
+
470
+ // ========================================================================
471
+ // DIVIDER
472
+ // ========================================================================
473
+
474
+ &__divider {
475
+ width: calc(100% - 56px);
476
+ height: 1px;
477
+ margin: 8px 28px;
478
+ border: none;
479
+ background-color: t.color("outline-variant");
480
+ }
481
+
482
+ // ========================================================================
483
+ // SECTION LABEL
484
+ // ========================================================================
485
+
486
+ &__section-label {
487
+ @include m.typography("title-small");
488
+ color: t.color("on-surface-variant");
489
+ padding: 18px 28px 4px;
490
+ white-space: nowrap;
491
+ overflow: hidden;
492
+ text-overflow: ellipsis;
493
+ }
494
+
495
+ // ========================================================================
496
+ // DENSE VARIANT — compact items for admin UIs
497
+ // ========================================================================
498
+
499
+ &--dense {
500
+ .#{$component}__sheet {
501
+ padding: 8px 0;
502
+ }
503
+
504
+ .#{$component}__headline {
505
+ @include m.typography("label-large");
506
+ padding: 10px 20px 0;
507
+ margin-bottom: 2px;
508
+ }
509
+
510
+ .#{$component}__item {
511
+ height: 36px;
512
+ margin: 0 8px;
513
+ padding: 0 16px 0 12px;
514
+ width: calc(100% - 16px);
515
+ gap: 8px;
516
+ border-radius: 18px;
517
+ @include m.typography("body-medium");
518
+
519
+ .#{$component}__active-indicator {
520
+ border-radius: 18px;
521
+ }
522
+ }
523
+
524
+ .#{$component}__item-icon {
525
+ width: 20px;
526
+ height: 20px;
527
+
528
+ svg {
529
+ width: 20px;
530
+ height: 20px;
531
+ }
532
+ }
533
+
534
+ .#{$component}__item-badge {
535
+ @include m.typography("label-medium");
536
+ }
537
+
538
+ .#{$component}__divider {
539
+ width: calc(100% - 40px);
540
+ margin: 4px 20px;
541
+ }
542
+
543
+ .#{$component}__section-label {
544
+ @include m.typography("label-large");
545
+ padding: 10px 20px 2px;
546
+ }
547
+ }
548
+
549
+ // ========================================================================
550
+ // RTL — item padding direction
551
+ // ========================================================================
552
+
553
+ @include m.rtl {
554
+ &__item {
555
+ padding: 0 16px 0 24px;
556
+ }
557
+
558
+ &--dense {
559
+ .#{$component}__item {
560
+ padding: 0 12px 0 16px;
561
+ }
562
+ }
563
+ }
564
+
565
+ // ========================================================================
566
+ // REDUCED MOTION
567
+ // ========================================================================
568
+
569
+ @include m.reduced-motion {
570
+ &__sheet {
571
+ transition: none;
572
+ }
573
+
574
+ &__scrim {
575
+ transition: none;
576
+ }
577
+
578
+ &__item,
579
+ &__active-indicator {
580
+ transition: none;
581
+ }
582
+ }
583
+
584
+ // ========================================================================
585
+ // HIGH CONTRAST (forced-colors)
586
+ // ========================================================================
587
+
588
+ @include m.high-contrast {
589
+ &__sheet {
590
+ border: 1px solid currentColor;
591
+ }
592
+
593
+ &__item {
594
+ &--active {
595
+ .#{$component}__active-indicator {
596
+ outline: 2px solid currentColor;
597
+ outline-offset: -2px;
598
+ }
599
+ }
600
+
601
+ &:focus-visible {
602
+ outline: 2px solid currentColor;
603
+ outline-offset: -2px;
604
+ }
605
+ }
606
+
607
+ &__divider {
608
+ background-color: currentColor;
609
+ }
610
+ }
611
+ }
@@ -290,7 +290,7 @@ $component: '#{base.$prefix}-extended-fab';
290
290
 
291
291
 
292
292
  // FIX: Disabled on dark theme (recommandations do not work as expected)
293
- body[data-theme-mode=dark] {
293
+ html[data-theme-mode=dark] {
294
294
  // Interactive states
295
295
  .#{$component} {
296
296
  &:disabled {
@@ -225,7 +225,7 @@ $component: '#{base.$prefix}-fab';
225
225
  }
226
226
 
227
227
  // FIX: Disabled on dark theme (recommandations do not work as expected)
228
- body[data-theme-mode=dark] {
228
+ html[data-theme-mode=dark] {
229
229
  // Interactive states
230
230
  .#{$component} {
231
231
  &:disabled {
@@ -534,7 +534,7 @@ $widths: (
534
534
  // =============================================================================
535
535
  // Dark theme adjustments
536
536
  // =============================================================================
537
- body[data-theme-mode="dark"] {
537
+ html[data-theme-mode="dark"] {
538
538
  .#{$component} {
539
539
  &:disabled,
540
540
  &.#{$component}--disabled {
@@ -225,6 +225,31 @@ $component: "#{$prefix}-menu";
225
225
  }
226
226
  }
227
227
 
228
+ // Dense variant — compact items for toolbars and dense UIs
229
+ &--dense {
230
+ padding: 4px 0;
231
+
232
+ .#{$component}-item {
233
+ @include m.typography("body-medium");
234
+ min-height: 32px;
235
+ padding: 4px 12px;
236
+ padding-right: 32px;
237
+
238
+ &--submenu {
239
+ padding-right: 36px;
240
+ }
241
+
242
+ &-icon svg {
243
+ width: 16px;
244
+ height: 16px;
245
+ }
246
+ }
247
+
248
+ .#{$component}-divider {
249
+ margin: 4px 0;
250
+ }
251
+ }
252
+
228
253
  // RTL Support
229
254
  @include m.rtl {
230
255
  transform-origin: top right;