@sigmela/router 0.2.7 → 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 (39) hide show
  1. package/README.md +41 -0
  2. package/lib/module/Drawer/Drawer.js +250 -0
  3. package/lib/module/Drawer/DrawerContext.js +4 -0
  4. package/lib/module/Drawer/DrawerIcon.web.js +47 -0
  5. package/lib/module/Drawer/RenderDrawer.native.js +241 -0
  6. package/lib/module/Drawer/RenderDrawer.web.js +197 -0
  7. package/lib/module/Drawer/useDrawer.js +11 -0
  8. package/lib/module/Navigation.js +4 -2
  9. package/lib/module/NavigationStack.js +22 -5
  10. package/lib/module/Router.js +214 -60
  11. package/lib/module/RouterContext.js +1 -1
  12. package/lib/module/ScreenStack/ScreenStack.web.js +78 -12
  13. package/lib/module/ScreenStackItem/ScreenStackItem.js +7 -7
  14. package/lib/module/ScreenStackItem/ScreenStackItem.web.js +65 -6
  15. package/lib/module/ScreenStackSheetItem/ScreenStackSheetItem.native.js +4 -5
  16. package/lib/module/SplitView/RenderSplitView.web.js +5 -7
  17. package/lib/module/SplitView/SplitView.js +10 -2
  18. package/lib/module/StackRenderer.js +10 -4
  19. package/lib/module/TabBar/RenderTabBar.native.js +10 -9
  20. package/lib/module/TabBar/RenderTabBar.web.js +25 -2
  21. package/lib/module/TabBar/TabBar.js +8 -1
  22. package/lib/module/TabBar/TabIcon.web.js +12 -7
  23. package/lib/module/index.js +2 -0
  24. package/lib/module/styles.css +247 -106
  25. package/lib/typescript/src/Drawer/Drawer.d.ts +100 -0
  26. package/lib/typescript/src/Drawer/DrawerContext.d.ts +3 -0
  27. package/lib/typescript/src/Drawer/DrawerIcon.web.d.ts +7 -0
  28. package/lib/typescript/src/Drawer/RenderDrawer.native.d.ts +8 -0
  29. package/lib/typescript/src/Drawer/RenderDrawer.web.d.ts +8 -0
  30. package/lib/typescript/src/Drawer/useDrawer.d.ts +2 -0
  31. package/lib/typescript/src/NavigationStack.d.ts +6 -4
  32. package/lib/typescript/src/Router.d.ts +13 -0
  33. package/lib/typescript/src/ScreenStack/ScreenStackContext.d.ts +1 -1
  34. package/lib/typescript/src/ScreenStack/animationHelpers.d.ts +1 -1
  35. package/lib/typescript/src/SplitView/SplitView.d.ts +2 -0
  36. package/lib/typescript/src/TabBar/TabBar.d.ts +1 -0
  37. package/lib/typescript/src/index.d.ts +5 -0
  38. package/lib/typescript/src/types.d.ts +10 -1
  39. 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;
@@ -72,38 +76,24 @@
72
76
 
73
77
  /* Overlay on enter — fade in via transition */
74
78
  .screen-stack-item.modal.transition-entering > .stack-modal-overlay,
75
- .screen-stack-item.modal.phase-active.transition-preEnter > .stack-modal-overlay,
76
79
  .screen-stack-item.modal-right.transition-entering > .stack-modal-overlay,
77
- .screen-stack-item.modal-right.phase-active.transition-preEnter > .stack-modal-overlay,
78
80
  .screen-stack-item.contained-modal.transition-entering > .stack-modal-overlay,
79
- .screen-stack-item.contained-modal.phase-active.transition-preEnter > .stack-modal-overlay,
80
81
  .screen-stack-item.fullscreen-modal.transition-entering > .stack-modal-overlay,
81
- .screen-stack-item.fullscreen-modal.phase-active.transition-preEnter > .stack-modal-overlay,
82
82
  .screen-stack-item.formsheet.transition-entering > .stack-modal-overlay,
83
- .screen-stack-item.formsheet.phase-active.transition-preEnter > .stack-modal-overlay,
84
83
  .screen-stack-item.pagesheet.transition-entering > .stack-modal-overlay,
85
- .screen-stack-item.pagesheet.phase-active.transition-preEnter > .stack-modal-overlay,
86
- .screen-stack-item.sheet.transition-entering > .stack-modal-overlay,
87
- .screen-stack-item.sheet.phase-active.transition-preEnter > .stack-modal-overlay {
84
+ .screen-stack-item.sheet.transition-entering > .stack-modal-overlay {
88
85
  background: rgba(0, 0, 0, 0.5);
89
86
  pointer-events: none;
90
87
  opacity: 0.5;
91
88
  }
92
89
 
93
90
  /* For modal-like in active / entered states — fully visible */
94
- .screen-stack-item.modal.phase-active > .stack-modal-overlay,
95
91
  .screen-stack-item.modal.transition-entered > .stack-modal-overlay,
96
- .screen-stack-item.modal-right.phase-active > .stack-modal-overlay,
97
92
  .screen-stack-item.modal-right.transition-entered > .stack-modal-overlay,
98
- .screen-stack-item.contained-modal.phase-active > .stack-modal-overlay,
99
93
  .screen-stack-item.contained-modal.transition-entered > .stack-modal-overlay,
100
- .screen-stack-item.fullscreen-modal.phase-active > .stack-modal-overlay,
101
94
  .screen-stack-item.fullscreen-modal.transition-entered > .stack-modal-overlay,
102
- .screen-stack-item.formsheet.phase-active > .stack-modal-overlay,
103
95
  .screen-stack-item.formsheet.transition-entered > .stack-modal-overlay,
104
- .screen-stack-item.pagesheet.phase-active > .stack-modal-overlay,
105
96
  .screen-stack-item.pagesheet.transition-entered > .stack-modal-overlay,
106
- .screen-stack-item.sheet.phase-active > .stack-modal-overlay,
107
97
  .screen-stack-item.sheet.transition-entered > .stack-modal-overlay {
108
98
  opacity: 0.5;
109
99
  background: rgba(0, 0, 0, 0.5);
@@ -166,7 +156,6 @@
166
156
  /* Overridden via animation classes (push-enter, pop-enter, etc.) */
167
157
  .screen-stack > .screen-stack-item.phase-active.transition-entered:not(.push-enter):not(.pop-enter):not(.modal):not(.sheet) {
168
158
  transform: translateX(0);
169
- filter: none;
170
159
  }
171
160
 
172
161
  /* Background (inactive) — visible, but without interactions */
@@ -203,11 +192,10 @@
203
192
 
204
193
  /* ==================== PUSH ANIMATIONS ==================== */
205
194
 
206
- /* Forward navigation (push) — opening screen 300ms */
195
+ /* Forward navigation (push) — opening screen */
207
196
  .screen-stack-item.push.push-enter {
208
197
  transition:
209
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
210
- 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);
211
199
  }
212
200
 
213
201
  /* PUSH ENTER - regular screen enters from right */
@@ -230,8 +218,7 @@
230
218
  .screen-stack-item.pagesheet.push-background {
231
219
  transform: translateX(-25%) !important;
232
220
  transition:
233
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
234
- 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);
235
222
  }
236
223
 
237
224
  /* POP BACKGROUND - background screen on pop stays at -25% */
@@ -244,15 +231,13 @@
244
231
  .screen-stack-item.pagesheet.pop-background {
245
232
  transform: translateX(-25%) !important;
246
233
  /* Don't set transition, so element stays in place without animation */
247
- filter: none;
248
234
  }
249
235
 
250
- /* Backward navigation (pop) — closing screen 300ms */
236
+ /* Backward navigation (pop) — closing screen */
251
237
  /* IMPORTANT: transition is set on element with pop-exit */
252
238
  .screen-stack-item.push.pop-exit {
253
239
  transition:
254
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
255
- 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);
256
241
  }
257
242
 
258
243
  /* POP EXIT - top screen exits to right */
@@ -261,7 +246,6 @@
261
246
  .screen-stack-item.push.pop-exit.phase-exiting,
262
247
  .screen-stack-item.push.pop-exit.transition-exiting {
263
248
  transform: translateX(100%);
264
- filter: none;
265
249
  }
266
250
 
267
251
  /* POP ENTER - screen returns to center from -25% */
@@ -270,28 +254,25 @@
270
254
  /* Simplified approach as in old version: phase-active simply becomes 0 */
271
255
  .screen-stack-item.push.pop-enter {
272
256
  transition:
273
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
274
- 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);
275
258
  }
276
259
 
277
260
  /* Active element on pop simply in center (0) - final state */
278
- /* As in old version: .phase-active on pop transform: translateX(0) */
261
+ /* As in old version: .phase-active on pop -> transform: translateX(0) */
279
262
  .screen-stack-item.push.pop-enter.phase-active,
280
263
  .screen-stack-item.push.pop-enter.phase-active.transition-entered {
281
264
  transform: translateX(0);
282
- filter: none;
283
265
  }
284
266
 
285
267
  /* ==================== MODAL STACK: don't move background ==================== */
286
268
 
287
269
  /* If there's a modal on top — don't move previous screen and don't dim it,
288
270
  overlay is responsible for dimming. */
289
- .screen-stack:has(> .screen-stack-item.modal.phase-active)
271
+ .screen-stack[data-has-active-modal="true"]
290
272
  > .screen-stack-item.push.phase-inactive,
291
- .screen-stack:has(> .screen-stack-item.modal.phase-active)
273
+ .screen-stack[data-has-active-modal="true"]
292
274
  > .screen-stack-item.push.phase-inactive.transition-entered {
293
275
  transform: translate3d(0, 0, 0) !important;
294
- filter: none;
295
276
  }
296
277
 
297
278
  /* ==================== MOBILE MODAL (<= 639px) — bottom sheet ==================== */
@@ -306,19 +287,17 @@
306
287
  border-top-right-radius: 38px;
307
288
  align-self: flex-end; /* align to bottom edge of parent */
308
289
  overflow: hidden;
309
- background: #fff; /* or var(--modal-bg) */
290
+ background: var(--modal-bg, #fff);
310
291
 
311
292
  transform: translateY(0);
312
293
  transition:
313
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
314
- 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);
315
295
  }
316
296
 
317
297
  /* MODAL ENTER - modal enters (bottom to top) */
318
298
  .screen-stack-item.modal.modal-enter.transition-preEnter .stack-modal-container,
319
299
  .screen-stack-item.modal-right.modal-right-enter.transition-preEnter .stack-modal-container {
320
300
  transform: translateY(100%);
321
- filter: none;
322
301
  }
323
302
 
324
303
  .screen-stack-item.modal.modal-enter.transition-entering .stack-modal-container,
@@ -326,17 +305,14 @@
326
305
  .screen-stack-item.modal-right.modal-right-enter.transition-entering .stack-modal-container,
327
306
  .screen-stack-item.modal-right.modal-right-enter.transition-entered .stack-modal-container {
328
307
  transform: translateY(0);
329
- filter: none;
330
308
  }
331
309
 
332
310
  /* MODAL EXIT - modal closes (exits downward) */
333
311
  .screen-stack-item.modal.modal-exit.transition-exiting .stack-modal-container,
334
312
  .screen-stack-item.modal-right.modal-right-exit.transition-exiting .stack-modal-container {
335
313
  transform: translateY(100%);
336
- filter: none;
337
314
  transition:
338
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
339
- 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);
340
316
  }
341
317
 
342
318
  /* SHEET - sheet styles for mobile version */
@@ -348,33 +324,28 @@
348
324
  border-top-right-radius: 38px;
349
325
  align-self: flex-end;
350
326
  overflow: hidden;
351
- background: #fff;
327
+ background: var(--modal-bg, #fff);
352
328
 
353
329
  transform: translateY(0);
354
330
  transition:
355
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
356
- 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);
357
332
  }
358
333
 
359
334
  /* SHEET ENTER - sheet enters (bottom to top) */
360
335
  .screen-stack-item.sheet.sheet-enter.transition-preEnter .stack-modal-container {
361
336
  transform: translateY(100%);
362
- filter: none;
363
337
  }
364
338
 
365
339
  .screen-stack-item.sheet.sheet-enter.transition-entering .stack-modal-container,
366
340
  .screen-stack-item.sheet.sheet-enter.transition-entered .stack-modal-container {
367
341
  transform: translateY(0);
368
- filter: none;
369
342
  }
370
343
 
371
344
  /* SHEET EXIT - sheet closes */
372
345
  .screen-stack-item.sheet.sheet-exit.transition-exiting .stack-modal-container {
373
346
  transform: translateY(100%);
374
- filter: none;
375
347
  transition:
376
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
377
- 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);
378
349
  }
379
350
  }
380
351
 
@@ -389,12 +360,11 @@
389
360
  margin: auto; /* center in parent */
390
361
  border-radius: 38px;
391
362
  overflow: hidden;
392
- background: #fff;
363
+ background: var(--modal-bg, #fff);
393
364
 
394
365
  transform: translateY(0);
395
366
  transition:
396
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
397
- 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);
398
368
  }
399
369
 
400
370
  /* Center the modal container using flexbox on parent */
@@ -407,22 +377,18 @@
407
377
  /* MODAL ENTER - modal enters (bottom to center) */
408
378
  .screen-stack-item.modal.modal-enter.transition-preEnter .stack-modal-container {
409
379
  transform: translateY(100vh);
410
- filter: none;
411
380
  }
412
381
 
413
382
  .screen-stack-item.modal.modal-enter.transition-entering .stack-modal-container,
414
383
  .screen-stack-item.modal.modal-enter.transition-entered .stack-modal-container {
415
384
  transform: translateY(0);
416
- filter: none;
417
385
  }
418
386
 
419
387
  /* MODAL EXIT - modal closes (exits downward) */
420
388
  .screen-stack-item.modal.modal-exit.transition-exiting .stack-modal-container {
421
389
  transform: translateY(100vh);
422
- filter: none;
423
390
  transition:
424
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
425
- 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);
426
392
  }
427
393
 
428
394
  /* ==================== MODAL-RIGHT — slide from right (previous modal behavior) ==================== */
@@ -436,40 +402,34 @@
436
402
  margin-left: auto; /* align to right edge */
437
403
  border-radius: 16px;
438
404
  overflow: hidden;
439
- background: #fff;
405
+ background: var(--modal-bg, #fff);
440
406
 
441
407
  transform: translateX(0);
442
408
  transition:
443
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
444
- 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);
445
410
  }
446
411
 
447
412
  /* MODAL-RIGHT ENTER - modal enters (right to left) */
448
413
  .screen-stack-item.modal-right.modal-right-enter.transition-preEnter .stack-modal-container {
449
414
  transform: translateX(100%);
450
- filter: none;
451
415
  }
452
416
 
453
417
  .screen-stack-item.modal-right.modal-right-enter.transition-entering .stack-modal-container,
454
418
  .screen-stack-item.modal-right.modal-right-enter.transition-entered .stack-modal-container {
455
419
  transform: translateX(0);
456
- filter: none;
457
420
  }
458
421
 
459
422
  /* MODAL-RIGHT EXIT - modal closes (exits to right) */
460
423
  .screen-stack-item.modal-right.modal-right-exit.transition-exiting .stack-modal-container {
461
424
  transform: translateX(100%);
462
- filter: none;
463
425
  transition:
464
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
465
- 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);
466
427
  }
467
428
 
468
429
  /* SHEET ENTER - sheet enters (right to left) */
469
430
  .screen-stack-item.sheet.sheet-enter.transition-preEnter
470
431
  .stack-modal-container {
471
432
  transform: translateX(100%);
472
- filter: none;
473
433
  }
474
434
 
475
435
  .screen-stack-item.sheet.sheet-enter.transition-entering
@@ -477,30 +437,22 @@
477
437
  .screen-stack-item.sheet.sheet-enter.transition-entered
478
438
  .stack-modal-container {
479
439
  transform: translateX(0);
480
- filter: none;
481
440
  }
482
441
 
483
442
  /* SHEET EXIT - sheet closes */
484
443
  .screen-stack-item.sheet.sheet-exit.transition-exiting
485
444
  .stack-modal-container {
486
445
  transform: translateX(100%);
487
- filter: none;
488
446
  transition:
489
- transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
490
- 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);
491
448
  }
492
449
 
493
450
  /* Background screens under modal — don't move, don't dim (overlay does this) */
494
- .screen-stack:has(> .screen-stack-item.modal.phase-active)
495
- > .screen-stack-item.push.phase-inactive,
496
- .screen-stack:has(> .screen-stack-item.modal.phase-active)
497
- > .screen-stack-item.push.phase-inactive.transition-entered,
498
- .screen-stack:has(> .screen-stack-item.modal-right.phase-active)
451
+ .screen-stack[data-has-active-modal="true"]
499
452
  > .screen-stack-item.push.phase-inactive,
500
- .screen-stack:has(> .screen-stack-item.modal-right.phase-active)
453
+ .screen-stack[data-has-active-modal="true"]
501
454
  > .screen-stack-item.push.phase-inactive.transition-entered {
502
455
  transform: translate3d(0, 0, 0) !important;
503
- filter: none;
504
456
  }
505
457
  }
506
458
 
@@ -508,10 +460,9 @@
508
460
 
509
461
  /* Inactive screens (background stack elements)
510
462
  Background elements on pop get animationType: pop-background and stay at -25%
511
- (except modal case, there we override via :has) */
463
+ (except modal case, there we override via data-has-active-modal) */
512
464
  .screen-stack > .screen-stack-item.phase-inactive:not(.push-background):not(.pop-background):not(.pop-enter):not(.pop-exit):not(.modal):not(.sheet) {
513
465
  /* Don't set transform explicitly - element will preserve previous position */
514
- filter: none;
515
466
  }
516
467
 
517
468
  /* ==================== SPECIAL CASES ==================== */
@@ -527,7 +478,6 @@
527
478
  .screen-stack-item.no-animate.pop-background {
528
479
  transform: translate3d(0, 0, 0) !important;
529
480
  transition: none !important;
530
- filter: none !important;
531
481
  }
532
482
 
533
483
  /* NONE - no animation (initial mount) */
@@ -535,7 +485,6 @@
535
485
  .screen-stack-item.none:not(.pop-background) {
536
486
  transform: translate3d(0, 0, 0) !important;
537
487
  transition: none !important;
538
- filter: none !important;
539
488
  }
540
489
 
541
490
 
@@ -544,13 +493,14 @@
544
493
  display: flex;
545
494
  flex-direction: column;
546
495
  flex: 1 1 auto;
496
+ min-height: 100vh;
547
497
  min-height: 100dvh;
548
498
  position: relative;
549
499
  }
550
500
 
551
501
  .tab-stacks-container > .screen-stack,
552
502
  .tab-stacks-container > .split-view-container {
553
- padding-bottom: calc(73px + env(safe-area-inset-bottom));
503
+ padding-bottom: calc(73px + env(safe-area-inset-bottom));
554
504
  }
555
505
 
556
506
  .tab-bar {
@@ -565,22 +515,7 @@
565
515
  /* Used by active indicator (set inline in RenderTabBar.web.tsx) */
566
516
  --tabbar-tabs-count: 1;
567
517
  --tabbar-active-index: 0;
568
- }
569
-
570
- .tab-bar-blur-overlay {
571
- position: fixed;
572
- bottom: 0;
573
- left: 0;
574
- right: 0;
575
- height: calc(73px + 16px + env(safe-area-inset-bottom));
576
- backdrop-filter: blur(20px) saturate(180%);
577
- background-color: rgba(255, 255, 255, 0.05);
578
- /* -webkit-backdrop-filter: blur(20px) saturate(180%); */
579
- pointer-events: none;
580
- z-index: 99;
581
- }
582
518
 
583
- .tab-bar {
584
519
  display: flex;
585
520
  flex-direction: row;
586
521
  align-items: center;
@@ -596,6 +531,19 @@
596
531
  z-index: 100;
597
532
  }
598
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
+
599
547
  .tab-bar-inner {
600
548
  position: relative;
601
549
  display: flex;
@@ -604,8 +552,6 @@
604
552
  border-radius: var(--tabbar-radius);
605
553
  overflow: hidden;
606
554
  isolation: isolate;
607
- width: 100%;
608
- max-width: 100%;
609
555
  }
610
556
 
611
557
  .tab-bar-glass {
@@ -683,6 +629,7 @@
683
629
  }
684
630
 
685
631
  .tab-item:hover:not(.active) {
632
+ background: rgba(10, 10, 10, 0.06);
686
633
  background: color-mix(in srgb, #0a0a0a 6%, transparent);
687
634
  }
688
635
 
@@ -690,12 +637,13 @@
690
637
  transform: scale(0.98);
691
638
  }
692
639
 
693
- .tab-item:focus {
640
+ .tab-item:focus:not(:focus-visible) {
694
641
  outline: none;
695
642
  }
696
643
 
697
644
  .tab-item:focus-visible {
698
645
  box-shadow:
646
+ 0 0 0 2px rgba(0, 0, 0, 0.4),
699
647
  0 0 0 2px color-mix(in srgb, currentColor 40%, transparent),
700
648
  0 10px 28px rgba(0, 0, 0, 0.12);
701
649
  }
@@ -744,8 +692,8 @@
744
692
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
745
693
  }
746
694
 
747
- /* ==================== MOBILE TAB BAR (<= 640px) — iOS-like floating glass pill ==================== */
748
- @media (max-width: 640px) {
695
+ /* ==================== MOBILE TAB BAR (<= 639px) — iOS-like floating glass pill ==================== */
696
+ @media (max-width: 639px) {
749
697
  .tab-bar-inner {
750
698
  max-width: 560px;
751
699
  margin: 0 auto;
@@ -775,12 +723,13 @@
775
723
  }
776
724
 
777
725
  .tab-item:hover:not(.active) {
726
+ background: rgba(255, 255, 255, 0.22);
778
727
  background: color-mix(in srgb, #ffffff 22%, transparent);
779
728
  }
780
729
  }
781
730
 
782
- /* ==================== DESKTOP TAB BAR (>= 641px) ==================== */
783
- @media (min-width: 641px) {
731
+ /* ==================== DESKTOP TAB BAR (>= 640px) ==================== */
732
+ @media (min-width: 640px) {
784
733
  .tab-stacks-container {
785
734
  flex-direction: row;
786
735
  }
@@ -894,7 +843,9 @@
894
843
  .split-view-container {
895
844
  position: relative;
896
845
  display: block; /* narrow by default; wide enabled via injected @media rule */
846
+ height: 100vh;
897
847
  height: 100dvh;
848
+ min-height: 100vh;
898
849
  min-height: 100dvh;
899
850
  width: 100%;
900
851
  overflow: hidden;
@@ -926,6 +877,7 @@
926
877
  display: none;
927
878
  }
928
879
 
880
+
929
881
  .split-view-container:has(.split-view-secondary:has(.screen-stack-item.phase-active)) .split-view-primary,
930
882
  .split-view-container:has(.split-view-secondary:has(.screen-stack-item.phase-exiting)) .split-view-primary,
931
883
  .split-view-container:has(.split-view-secondary:has(.screen-stack-item.transition-entering)) .split-view-primary,
@@ -959,3 +911,192 @@
959
911
  flex: 1 1 auto;
960
912
  }
961
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
+ }