@sigmela/router 0.2.8 → 0.3.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.
Files changed (38) hide show
  1. package/lib/module/Drawer/Drawer.js +250 -0
  2. package/lib/module/Drawer/DrawerContext.js +4 -0
  3. package/lib/module/Drawer/DrawerIcon.web.js +47 -0
  4. package/lib/module/Drawer/RenderDrawer.native.js +241 -0
  5. package/lib/module/Drawer/RenderDrawer.web.js +197 -0
  6. package/lib/module/Drawer/useDrawer.js +11 -0
  7. package/lib/module/Navigation.js +4 -2
  8. package/lib/module/NavigationStack.js +14 -4
  9. package/lib/module/Router.js +214 -60
  10. package/lib/module/RouterContext.js +1 -1
  11. package/lib/module/ScreenStack/ScreenStack.web.js +78 -12
  12. package/lib/module/ScreenStackItem/ScreenStackItem.js +7 -7
  13. package/lib/module/ScreenStackItem/ScreenStackItem.web.js +65 -6
  14. package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.native.js +4 -5
  15. package/lib/module/SplitView/RenderSplitView.web.js +5 -7
  16. package/lib/module/SplitView/SplitView.js +10 -2
  17. package/lib/module/StackRenderer.js +10 -4
  18. package/lib/module/TabBar/RenderTabBar.native.js +10 -9
  19. package/lib/module/TabBar/RenderTabBar.web.js +25 -2
  20. package/lib/module/TabBar/TabBar.js +8 -1
  21. package/lib/module/TabBar/TabIcon.web.js +12 -7
  22. package/lib/module/index.js +2 -0
  23. package/lib/module/styles.css +246 -91
  24. package/lib/typescript/src/Drawer/Drawer.d.ts +100 -0
  25. package/lib/typescript/src/Drawer/DrawerContext.d.ts +3 -0
  26. package/lib/typescript/src/Drawer/DrawerIcon.web.d.ts +7 -0
  27. package/lib/typescript/src/Drawer/RenderDrawer.native.d.ts +8 -0
  28. package/lib/typescript/src/Drawer/RenderDrawer.web.d.ts +8 -0
  29. package/lib/typescript/src/Drawer/useDrawer.d.ts +2 -0
  30. package/lib/typescript/src/NavigationStack.d.ts +1 -0
  31. package/lib/typescript/src/Router.d.ts +13 -0
  32. package/lib/typescript/src/ScreenStack/ScreenStackContext.d.ts +1 -1
  33. package/lib/typescript/src/ScreenStack/animationHelpers.d.ts +1 -1
  34. package/lib/typescript/src/SplitView/SplitView.d.ts +2 -0
  35. package/lib/typescript/src/TabBar/TabBar.d.ts +1 -0
  36. package/lib/typescript/src/index.d.ts +5 -0
  37. package/lib/typescript/src/types.d.ts +1 -1
  38. package/package.json +15 -4
@@ -3,6 +3,7 @@
3
3
  width: 100%;
4
4
  display: flex;
5
5
  position: relative;
6
+ height: 100vh;
6
7
  height: 100dvh;
7
8
  overflow: hidden;
8
9
  }
@@ -18,11 +19,14 @@
18
19
  /* Base transition for opening screen */
19
20
  transition:
20
21
  transform var(--stack-transition-time, 300ms)
21
- cubic-bezier(0.22, 0.61, 0.36, 1),
22
- filter var(--stack-transition-time, 300ms)
23
22
  cubic-bezier(0.22, 0.61, 0.36, 1);
24
23
  }
25
24
 
25
+ /* will-change hint for animating stacks */
26
+ .screen-stack[data-animation="true"] > .screen-stack-item {
27
+ will-change: transform;
28
+ }
29
+
26
30
  /* Inner container for regular screen */
27
31
  .stack-screen-container {
28
32
  display: flex;
@@ -152,7 +156,6 @@
152
156
  /* Overridden via animation classes (push-enter, pop-enter, etc.) */
153
157
  .screen-stack > .screen-stack-item.phase-active.transition-entered:not(.push-enter):not(.pop-enter):not(.modal):not(.sheet) {
154
158
  transform: translateX(0);
155
- filter: none;
156
159
  }
157
160
 
158
161
  /* Background (inactive) — visible, but without interactions */
@@ -189,11 +192,10 @@
189
192
 
190
193
  /* ==================== PUSH ANIMATIONS ==================== */
191
194
 
192
- /* Forward navigation (push) — opening screen 300ms */
195
+ /* Forward navigation (push) — opening screen */
193
196
  .screen-stack-item.push.push-enter {
194
197
  transition:
195
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
196
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
198
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
197
199
  }
198
200
 
199
201
  /* PUSH ENTER - regular screen enters from right */
@@ -216,8 +218,7 @@
216
218
  .screen-stack-item.pagesheet.push-background {
217
219
  transform: translateX(-25%) !important;
218
220
  transition:
219
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
220
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
221
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
221
222
  }
222
223
 
223
224
  /* POP BACKGROUND - background screen on pop stays at -25% */
@@ -230,15 +231,13 @@
230
231
  .screen-stack-item.pagesheet.pop-background {
231
232
  transform: translateX(-25%) !important;
232
233
  /* Don't set transition, so element stays in place without animation */
233
- filter: none;
234
234
  }
235
235
 
236
- /* Backward navigation (pop) — closing screen 300ms */
236
+ /* Backward navigation (pop) — closing screen */
237
237
  /* IMPORTANT: transition is set on element with pop-exit */
238
238
  .screen-stack-item.push.pop-exit {
239
239
  transition:
240
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
241
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
240
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
242
241
  }
243
242
 
244
243
  /* POP EXIT - top screen exits to right */
@@ -247,7 +246,6 @@
247
246
  .screen-stack-item.push.pop-exit.phase-exiting,
248
247
  .screen-stack-item.push.pop-exit.transition-exiting {
249
248
  transform: translateX(100%);
250
- filter: none;
251
249
  }
252
250
 
253
251
  /* POP ENTER - screen returns to center from -25% */
@@ -256,28 +254,25 @@
256
254
  /* Simplified approach as in old version: phase-active simply becomes 0 */
257
255
  .screen-stack-item.push.pop-enter {
258
256
  transition:
259
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
260
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
257
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
261
258
  }
262
259
 
263
260
  /* Active element on pop simply in center (0) - final state */
264
- /* As in old version: .phase-active on pop transform: translateX(0) */
261
+ /* As in old version: .phase-active on pop -> transform: translateX(0) */
265
262
  .screen-stack-item.push.pop-enter.phase-active,
266
263
  .screen-stack-item.push.pop-enter.phase-active.transition-entered {
267
264
  transform: translateX(0);
268
- filter: none;
269
265
  }
270
266
 
271
267
  /* ==================== MODAL STACK: don't move background ==================== */
272
268
 
273
269
  /* If there's a modal on top — don't move previous screen and don't dim it,
274
270
  overlay is responsible for dimming. */
275
- .screen-stack:has(> .screen-stack-item.modal.phase-active)
271
+ .screen-stack[data-has-active-modal="true"]
276
272
  > .screen-stack-item.push.phase-inactive,
277
- .screen-stack:has(> .screen-stack-item.modal.phase-active)
273
+ .screen-stack[data-has-active-modal="true"]
278
274
  > .screen-stack-item.push.phase-inactive.transition-entered {
279
275
  transform: translate3d(0, 0, 0) !important;
280
- filter: none;
281
276
  }
282
277
 
283
278
  /* ==================== MOBILE MODAL (<= 639px) — bottom sheet ==================== */
@@ -292,19 +287,17 @@
292
287
  border-top-right-radius: 38px;
293
288
  align-self: flex-end; /* align to bottom edge of parent */
294
289
  overflow: hidden;
295
- background: #fff; /* or var(--modal-bg) */
290
+ background: var(--modal-bg, #fff);
296
291
 
297
292
  transform: translateY(0);
298
293
  transition:
299
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
300
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
294
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
301
295
  }
302
296
 
303
297
  /* MODAL ENTER - modal enters (bottom to top) */
304
298
  .screen-stack-item.modal.modal-enter.transition-preEnter .stack-modal-container,
305
299
  .screen-stack-item.modal-right.modal-right-enter.transition-preEnter .stack-modal-container {
306
300
  transform: translateY(100%);
307
- filter: none;
308
301
  }
309
302
 
310
303
  .screen-stack-item.modal.modal-enter.transition-entering .stack-modal-container,
@@ -312,17 +305,14 @@
312
305
  .screen-stack-item.modal-right.modal-right-enter.transition-entering .stack-modal-container,
313
306
  .screen-stack-item.modal-right.modal-right-enter.transition-entered .stack-modal-container {
314
307
  transform: translateY(0);
315
- filter: none;
316
308
  }
317
309
 
318
310
  /* MODAL EXIT - modal closes (exits downward) */
319
311
  .screen-stack-item.modal.modal-exit.transition-exiting .stack-modal-container,
320
312
  .screen-stack-item.modal-right.modal-right-exit.transition-exiting .stack-modal-container {
321
313
  transform: translateY(100%);
322
- filter: none;
323
314
  transition:
324
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
325
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
315
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
326
316
  }
327
317
 
328
318
  /* SHEET - sheet styles for mobile version */
@@ -334,33 +324,28 @@
334
324
  border-top-right-radius: 38px;
335
325
  align-self: flex-end;
336
326
  overflow: hidden;
337
- background: #fff;
327
+ background: var(--modal-bg, #fff);
338
328
 
339
329
  transform: translateY(0);
340
330
  transition:
341
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
342
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
331
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
343
332
  }
344
333
 
345
334
  /* SHEET ENTER - sheet enters (bottom to top) */
346
335
  .screen-stack-item.sheet.sheet-enter.transition-preEnter .stack-modal-container {
347
336
  transform: translateY(100%);
348
- filter: none;
349
337
  }
350
338
 
351
339
  .screen-stack-item.sheet.sheet-enter.transition-entering .stack-modal-container,
352
340
  .screen-stack-item.sheet.sheet-enter.transition-entered .stack-modal-container {
353
341
  transform: translateY(0);
354
- filter: none;
355
342
  }
356
343
 
357
344
  /* SHEET EXIT - sheet closes */
358
345
  .screen-stack-item.sheet.sheet-exit.transition-exiting .stack-modal-container {
359
346
  transform: translateY(100%);
360
- filter: none;
361
347
  transition:
362
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
363
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
348
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
364
349
  }
365
350
  }
366
351
 
@@ -375,12 +360,11 @@
375
360
  margin: auto; /* center in parent */
376
361
  border-radius: 38px;
377
362
  overflow: hidden;
378
- background: #fff;
363
+ background: var(--modal-bg, #fff);
379
364
 
380
365
  transform: translateY(0);
381
366
  transition:
382
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
383
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
367
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
384
368
  }
385
369
 
386
370
  /* Center the modal container using flexbox on parent */
@@ -393,22 +377,18 @@
393
377
  /* MODAL ENTER - modal enters (bottom to center) */
394
378
  .screen-stack-item.modal.modal-enter.transition-preEnter .stack-modal-container {
395
379
  transform: translateY(100vh);
396
- filter: none;
397
380
  }
398
381
 
399
382
  .screen-stack-item.modal.modal-enter.transition-entering .stack-modal-container,
400
383
  .screen-stack-item.modal.modal-enter.transition-entered .stack-modal-container {
401
384
  transform: translateY(0);
402
- filter: none;
403
385
  }
404
386
 
405
387
  /* MODAL EXIT - modal closes (exits downward) */
406
388
  .screen-stack-item.modal.modal-exit.transition-exiting .stack-modal-container {
407
389
  transform: translateY(100vh);
408
- filter: none;
409
390
  transition:
410
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
411
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
391
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
412
392
  }
413
393
 
414
394
  /* ==================== MODAL-RIGHT — slide from right (previous modal behavior) ==================== */
@@ -422,40 +402,34 @@
422
402
  margin-left: auto; /* align to right edge */
423
403
  border-radius: 16px;
424
404
  overflow: hidden;
425
- background: #fff;
405
+ background: var(--modal-bg, #fff);
426
406
 
427
407
  transform: translateX(0);
428
408
  transition:
429
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
430
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
409
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
431
410
  }
432
411
 
433
412
  /* MODAL-RIGHT ENTER - modal enters (right to left) */
434
413
  .screen-stack-item.modal-right.modal-right-enter.transition-preEnter .stack-modal-container {
435
414
  transform: translateX(100%);
436
- filter: none;
437
415
  }
438
416
 
439
417
  .screen-stack-item.modal-right.modal-right-enter.transition-entering .stack-modal-container,
440
418
  .screen-stack-item.modal-right.modal-right-enter.transition-entered .stack-modal-container {
441
419
  transform: translateX(0);
442
- filter: none;
443
420
  }
444
421
 
445
422
  /* MODAL-RIGHT EXIT - modal closes (exits to right) */
446
423
  .screen-stack-item.modal-right.modal-right-exit.transition-exiting .stack-modal-container {
447
424
  transform: translateX(100%);
448
- filter: none;
449
425
  transition:
450
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
451
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
426
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
452
427
  }
453
428
 
454
429
  /* SHEET ENTER - sheet enters (right to left) */
455
430
  .screen-stack-item.sheet.sheet-enter.transition-preEnter
456
431
  .stack-modal-container {
457
432
  transform: translateX(100%);
458
- filter: none;
459
433
  }
460
434
 
461
435
  .screen-stack-item.sheet.sheet-enter.transition-entering
@@ -463,30 +437,22 @@
463
437
  .screen-stack-item.sheet.sheet-enter.transition-entered
464
438
  .stack-modal-container {
465
439
  transform: translateX(0);
466
- filter: none;
467
440
  }
468
441
 
469
442
  /* SHEET EXIT - sheet closes */
470
443
  .screen-stack-item.sheet.sheet-exit.transition-exiting
471
444
  .stack-modal-container {
472
445
  transform: translateX(100%);
473
- filter: none;
474
446
  transition:
475
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
476
- filter 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
447
+ transform var(--stack-transition-time, 300ms) cubic-bezier(0.22, 0.61, 0.36, 1);
477
448
  }
478
449
 
479
450
  /* Background screens under modal — don't move, don't dim (overlay does this) */
480
- .screen-stack:has(> .screen-stack-item.modal.phase-active)
481
- > .screen-stack-item.push.phase-inactive,
482
- .screen-stack:has(> .screen-stack-item.modal.phase-active)
483
- > .screen-stack-item.push.phase-inactive.transition-entered,
484
- .screen-stack:has(> .screen-stack-item.modal-right.phase-active)
451
+ .screen-stack[data-has-active-modal="true"]
485
452
  > .screen-stack-item.push.phase-inactive,
486
- .screen-stack:has(> .screen-stack-item.modal-right.phase-active)
453
+ .screen-stack[data-has-active-modal="true"]
487
454
  > .screen-stack-item.push.phase-inactive.transition-entered {
488
455
  transform: translate3d(0, 0, 0) !important;
489
- filter: none;
490
456
  }
491
457
  }
492
458
 
@@ -494,10 +460,9 @@
494
460
 
495
461
  /* Inactive screens (background stack elements)
496
462
  Background elements on pop get animationType: pop-background and stay at -25%
497
- (except modal case, there we override via :has) */
463
+ (except modal case, there we override via data-has-active-modal) */
498
464
  .screen-stack > .screen-stack-item.phase-inactive:not(.push-background):not(.pop-background):not(.pop-enter):not(.pop-exit):not(.modal):not(.sheet) {
499
465
  /* Don't set transform explicitly - element will preserve previous position */
500
- filter: none;
501
466
  }
502
467
 
503
468
  /* ==================== SPECIAL CASES ==================== */
@@ -513,7 +478,6 @@
513
478
  .screen-stack-item.no-animate.pop-background {
514
479
  transform: translate3d(0, 0, 0) !important;
515
480
  transition: none !important;
516
- filter: none !important;
517
481
  }
518
482
 
519
483
  /* NONE - no animation (initial mount) */
@@ -521,7 +485,6 @@
521
485
  .screen-stack-item.none:not(.pop-background) {
522
486
  transform: translate3d(0, 0, 0) !important;
523
487
  transition: none !important;
524
- filter: none !important;
525
488
  }
526
489
 
527
490
 
@@ -530,13 +493,14 @@
530
493
  display: flex;
531
494
  flex-direction: column;
532
495
  flex: 1 1 auto;
496
+ min-height: 100vh;
533
497
  min-height: 100dvh;
534
498
  position: relative;
535
499
  }
536
500
 
537
501
  .tab-stacks-container > .screen-stack,
538
502
  .tab-stacks-container > .split-view-container {
539
- padding-bottom: calc(73px + env(safe-area-inset-bottom));
503
+ padding-bottom: calc(73px + env(safe-area-inset-bottom));
540
504
  }
541
505
 
542
506
  .tab-bar {
@@ -551,22 +515,7 @@
551
515
  /* Used by active indicator (set inline in RenderTabBar.web.tsx) */
552
516
  --tabbar-tabs-count: 1;
553
517
  --tabbar-active-index: 0;
554
- }
555
-
556
- .tab-bar-blur-overlay {
557
- position: fixed;
558
- bottom: 0;
559
- left: 0;
560
- right: 0;
561
- height: calc(73px + 16px + env(safe-area-inset-bottom));
562
- backdrop-filter: blur(20px) saturate(180%);
563
- background-color: rgba(255, 255, 255, 0.05);
564
- /* -webkit-backdrop-filter: blur(20px) saturate(180%); */
565
- pointer-events: none;
566
- z-index: 99;
567
- }
568
518
 
569
- .tab-bar {
570
519
  display: flex;
571
520
  flex-direction: row;
572
521
  align-items: center;
@@ -582,6 +531,19 @@
582
531
  z-index: 100;
583
532
  }
584
533
 
534
+ .tab-bar-blur-overlay {
535
+ position: fixed;
536
+ bottom: 0;
537
+ left: 0;
538
+ right: 0;
539
+ height: calc(73px + 16px + env(safe-area-inset-bottom));
540
+ backdrop-filter: blur(20px) saturate(180%);
541
+ background-color: rgba(255, 255, 255, 0.05);
542
+ /* -webkit-backdrop-filter: blur(20px) saturate(180%); */
543
+ pointer-events: none;
544
+ z-index: 99;
545
+ }
546
+
585
547
  .tab-bar-inner {
586
548
  position: relative;
587
549
  display: flex;
@@ -590,8 +552,6 @@
590
552
  border-radius: var(--tabbar-radius);
591
553
  overflow: hidden;
592
554
  isolation: isolate;
593
- width: 100%;
594
- max-width: 100%;
595
555
  }
596
556
 
597
557
  .tab-bar-glass {
@@ -669,6 +629,7 @@
669
629
  }
670
630
 
671
631
  .tab-item:hover:not(.active) {
632
+ background: rgba(10, 10, 10, 0.06);
672
633
  background: color-mix(in srgb, #0a0a0a 6%, transparent);
673
634
  }
674
635
 
@@ -676,12 +637,13 @@
676
637
  transform: scale(0.98);
677
638
  }
678
639
 
679
- .tab-item:focus {
640
+ .tab-item:focus:not(:focus-visible) {
680
641
  outline: none;
681
642
  }
682
643
 
683
644
  .tab-item:focus-visible {
684
645
  box-shadow:
646
+ 0 0 0 2px rgba(0, 0, 0, 0.4),
685
647
  0 0 0 2px color-mix(in srgb, currentColor 40%, transparent),
686
648
  0 10px 28px rgba(0, 0, 0, 0.12);
687
649
  }
@@ -730,8 +692,8 @@
730
692
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
731
693
  }
732
694
 
733
- /* ==================== MOBILE TAB BAR (<= 640px) — iOS-like floating glass pill ==================== */
734
- @media (max-width: 640px) {
695
+ /* ==================== MOBILE TAB BAR (<= 639px) — iOS-like floating glass pill ==================== */
696
+ @media (max-width: 639px) {
735
697
  .tab-bar-inner {
736
698
  max-width: 560px;
737
699
  margin: 0 auto;
@@ -761,12 +723,13 @@
761
723
  }
762
724
 
763
725
  .tab-item:hover:not(.active) {
726
+ background: rgba(255, 255, 255, 0.22);
764
727
  background: color-mix(in srgb, #ffffff 22%, transparent);
765
728
  }
766
729
  }
767
730
 
768
- /* ==================== DESKTOP TAB BAR (>= 641px) ==================== */
769
- @media (min-width: 641px) {
731
+ /* ==================== DESKTOP TAB BAR (>= 640px) ==================== */
732
+ @media (min-width: 640px) {
770
733
  .tab-stacks-container {
771
734
  flex-direction: row;
772
735
  }
@@ -880,7 +843,9 @@
880
843
  .split-view-container {
881
844
  position: relative;
882
845
  display: block; /* narrow by default; wide enabled via injected @media rule */
846
+ height: 100vh;
883
847
  height: 100dvh;
848
+ min-height: 100vh;
884
849
  min-height: 100dvh;
885
850
  width: 100%;
886
851
  overflow: hidden;
@@ -912,6 +877,7 @@
912
877
  display: none;
913
878
  }
914
879
 
880
+
915
881
  .split-view-container:has(.split-view-secondary:has(.screen-stack-item.phase-active)) .split-view-primary,
916
882
  .split-view-container:has(.split-view-secondary:has(.screen-stack-item.phase-exiting)) .split-view-primary,
917
883
  .split-view-container:has(.split-view-secondary:has(.screen-stack-item.transition-entering)) .split-view-primary,
@@ -945,3 +911,192 @@
945
911
  flex: 1 1 auto;
946
912
  }
947
913
  }
914
+
915
+ /* ==================== DRAWER ==================== */
916
+
917
+ .drawer-container {
918
+ --drawer-bg: #ffffff;
919
+ --drawer-width: 280px;
920
+ --drawer-transition: 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
921
+
922
+ display: flex;
923
+ flex-direction: row;
924
+ flex: 1 1 auto;
925
+ min-height: 100dvh;
926
+ position: relative;
927
+ overflow: hidden;
928
+ }
929
+
930
+ .drawer-sidebar {
931
+ display: flex;
932
+ flex-direction: column;
933
+ width: var(--drawer-width);
934
+ flex-shrink: 0;
935
+ background: var(--drawer-bg);
936
+ overflow-y: auto;
937
+ overflow-x: hidden;
938
+ padding: 12px 0 0 0;
939
+ }
940
+
941
+ .drawer-sidebar-content {
942
+ display: flex;
943
+ flex-direction: column;
944
+ align-items: stretch;
945
+ gap: 0;
946
+ }
947
+
948
+ .drawer-main {
949
+ flex: 1 1 auto;
950
+ min-width: 0;
951
+ position: relative;
952
+ }
953
+
954
+ .drawer-overlay {
955
+ display: none;
956
+ }
957
+
958
+ /* Drawer item — identical to desktop TabBar .tab-item */
959
+ .drawer-item {
960
+ appearance: none;
961
+ background: transparent;
962
+ border: 0;
963
+ margin: 0 12px;
964
+ margin-bottom: 4px;
965
+ padding: 0 12px;
966
+ height: 44px;
967
+ color: inherit;
968
+ display: flex;
969
+ flex: 0 0 auto;
970
+ flex-direction: row;
971
+ align-items: center;
972
+ justify-content: flex-start;
973
+ cursor: pointer;
974
+ border-radius: 10px;
975
+ transition: background-color 200ms cubic-bezier(0.22, 0.61, 0.36, 1);
976
+ }
977
+
978
+ .drawer-item.active {
979
+ background: var(--backgroundTertiaryTrans, rgba(201, 204, 209, 0.32));
980
+ }
981
+
982
+ .drawer-item:hover:not(.active) {
983
+ background: color-mix(in srgb, #0a0a0a 6%, transparent);
984
+ }
985
+
986
+ .drawer-item:active {
987
+ transform: none;
988
+ }
989
+
990
+ .drawer-item:focus {
991
+ outline: none;
992
+ }
993
+
994
+ .drawer-item:focus-visible {
995
+ box-shadow:
996
+ 0 0 0 2px color-mix(in srgb, currentColor 40%, transparent),
997
+ 0 10px 28px rgba(0, 0, 0, 0.12);
998
+ }
999
+
1000
+ .drawer-item-icon {
1001
+ width: 24px;
1002
+ height: 24px;
1003
+ padding-right: 12px;
1004
+ display: flex;
1005
+ align-items: center;
1006
+ justify-content: center;
1007
+ flex-shrink: 0;
1008
+ }
1009
+
1010
+ .drawer-item-icon > * {
1011
+ width: 24px;
1012
+ height: 24px;
1013
+ display: block;
1014
+ object-fit: contain;
1015
+ }
1016
+
1017
+ .drawer-item-label {
1018
+ flex: 1;
1019
+ font-size: 14px;
1020
+ line-height: 20px;
1021
+ font-weight: 600;
1022
+ text-align: left;
1023
+ padding-right: 3px;
1024
+ white-space: nowrap;
1025
+ }
1026
+
1027
+ .drawer-item-badge {
1028
+ position: static;
1029
+ transform: none;
1030
+ margin-left: auto;
1031
+ min-width: 18px;
1032
+ height: 18px;
1033
+ padding: 0 6px;
1034
+ border-radius: 9999px;
1035
+ background-color: #dc3545;
1036
+ color: #fff;
1037
+ font-size: 11px;
1038
+ font-weight: 600;
1039
+ line-height: 18px;
1040
+ display: inline-flex;
1041
+ align-items: center;
1042
+ justify-content: center;
1043
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
1044
+ }
1045
+
1046
+ /* ==================== MOBILE DRAWER (<= 640px) ==================== */
1047
+ @media (max-width: 640px) {
1048
+ .drawer-container {
1049
+ overflow: hidden;
1050
+ }
1051
+
1052
+ .drawer-sidebar {
1053
+ position: absolute;
1054
+ top: 0;
1055
+ left: 0;
1056
+ bottom: 0;
1057
+ z-index: 101;
1058
+ transform: translateX(calc(-1 * var(--drawer-width)));
1059
+ transition: transform var(--drawer-transition);
1060
+ }
1061
+
1062
+ .drawer-main {
1063
+ width: 100%;
1064
+ transition: transform var(--drawer-transition);
1065
+ }
1066
+
1067
+ .drawer-overlay {
1068
+ display: block;
1069
+ position: absolute;
1070
+ inset: 0;
1071
+ z-index: 100;
1072
+ background: rgba(0, 0, 0, 0);
1073
+ opacity: 0;
1074
+ pointer-events: none;
1075
+ transition:
1076
+ opacity var(--drawer-transition),
1077
+ background var(--drawer-transition);
1078
+ }
1079
+
1080
+ /* Open state */
1081
+ .drawer-container[data-drawer-open='true'] .drawer-sidebar {
1082
+ transform: translateX(0);
1083
+ }
1084
+
1085
+ .drawer-container[data-drawer-open='true'] .drawer-main {
1086
+ transform: translateX(var(--drawer-width));
1087
+ }
1088
+
1089
+ .drawer-container[data-drawer-open='true'] .drawer-overlay {
1090
+ opacity: 1;
1091
+ background: rgba(0, 0, 0, 0.3);
1092
+ pointer-events: auto;
1093
+ }
1094
+ }
1095
+
1096
+ /* ==================== DESKTOP DRAWER (>= 641px) ==================== */
1097
+ @media (min-width: 641px) {
1098
+ /* On desktop, drawer is always visible — data-drawer-open is irrelevant */
1099
+ .drawer-sidebar {
1100
+ border-right: 1px solid rgba(0, 0, 0, 0.08);
1101
+ }
1102
+ }