@matteoaliano/forest-ui 1.2.3 → 1.2.5

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/dist/index.js CHANGED
@@ -222,7 +222,7 @@ __export(src_exports, {
222
222
  TextField: () => import_TextField.default,
223
223
  TextareaAutosize: () => import_TextareaAutosize.default,
224
224
  ToggleButton: () => ToggleButton,
225
- ToggleButtonGroup: () => import_ToggleButtonGroup.default,
225
+ ToggleButtonGroup: () => ToggleButtonGroup,
226
226
  Toolbar: () => import_Toolbar.default,
227
227
  Tooltip: () => Tooltip,
228
228
  Typography: () => import_Typography.default,
@@ -591,19 +591,13 @@ function buildComponents(tokens2) {
591
591
  }
592
592
  };
593
593
  const bevelHover = {
594
- borderTopWidth: 4,
595
- borderBottomWidth: 1,
596
- borderTopLeftRadius: radius4.lg,
597
- borderTopRightRadius: radius4.lg,
598
- borderBottomLeftRadius: radius4.md,
599
- borderBottomRightRadius: radius4.md
594
+ borderTopWidth: 3,
595
+ borderBottomWidth: 1
600
596
  };
601
597
  const bevelPress = {
602
598
  // Snap the press faster than the 300ms hover flip so it feels tactile.
603
599
  transition: "border-width 120ms ease, border-radius 120ms ease, opacity 120ms ease",
604
600
  borderTopWidth: 1,
605
- borderTopLeftRadius: radius4.md,
606
- borderTopRightRadius: radius4.md,
607
601
  opacity: 0.75
608
602
  };
609
603
  const bevel3D = {
@@ -614,9 +608,7 @@ function buildComponents(tokens2) {
614
608
  borderTopWidth: 1,
615
609
  borderRightWidth: 1,
616
610
  borderLeftWidth: 1,
617
- borderBottomWidth: 4,
618
- borderBottomLeftRadius: radius4.lg,
619
- borderBottomRightRadius: radius4.lg,
611
+ borderBottomWidth: 3,
620
612
  "&::before": {
621
613
  content: '""',
622
614
  position: "absolute",
@@ -653,6 +645,21 @@ function buildComponents(tokens2) {
653
645
  }
654
646
  }
655
647
  },
648
+ // ─── ButtonBase (global) ────────────────────────────────────────
649
+ // Kills MUI's TouchRipple everywhere in one place. Every clickable in the
650
+ // library renders through ButtonBase, so this reaches Button, IconButton,
651
+ // Fab, ToggleButton, CardActionArea, ListItemButton, MenuItem, Tab, Chip,
652
+ // PaginationItem, StepButton, BottomNavigationAction, SpeedDialAction and
653
+ // the Pickers' day/month/year cells without a per-component opt-out.
654
+ //
655
+ // The press feedback is the faux-3D bevel instead: the raised bottom edge
656
+ // collapses and the face dims on :active (see `bevelPress`). An expanding
657
+ // circle on top of that reads as a second, competing press affordance.
658
+ MuiButtonBase: {
659
+ defaultProps: {
660
+ disableRipple: true
661
+ }
662
+ },
656
663
  // ─── Button ─────────────────────────────────────────────────────
657
664
  MuiButton: {
658
665
  defaultProps: {
@@ -672,7 +679,7 @@ function buildComponents(tokens2) {
672
679
  const colorScales = { error: error2, warning: warning2, success: success2, info: info2 };
673
680
  const faceScale = ownerState.color ? colorScales[ownerState.color] : void 0;
674
681
  return {
675
- borderRadius: radius4.md,
682
+ borderRadius: radius4.lg,
676
683
  fontWeight: 600,
677
684
  boxShadow: tokenShadows.xs,
678
685
  height: 32,
@@ -690,12 +697,7 @@ function buildComponents(tokens2) {
690
697
  borderTopWidth: 1,
691
698
  borderRightWidth: 1,
692
699
  borderLeftWidth: 1,
693
- borderBottomWidth: 4,
694
- // Round the thick-bevel side more than the rest so the inner
695
- // surface isn't squared off. Follows the bevel: bottom at rest,
696
- // top on hover.
697
- borderBottomLeftRadius: radius4.lg,
698
- borderBottomRightRadius: radius4.lg,
700
+ borderBottomWidth: 3,
699
701
  // Contained buttons have no border of their own — give them a
700
702
  // translucent bevel. Outlined keeps its own border color.
701
703
  ...isContained && {
@@ -720,12 +722,8 @@ function buildComponents(tokens2) {
720
722
  transition: "top 500ms ease-in-out"
721
723
  },
722
724
  "&:hover": {
723
- borderTopWidth: 4,
725
+ borderTopWidth: 3,
724
726
  borderBottomWidth: 1,
725
- borderTopLeftRadius: radius4.lg,
726
- borderTopRightRadius: radius4.lg,
727
- borderBottomLeftRadius: radius4.md,
728
- borderBottomRightRadius: radius4.md,
729
727
  // One step lighter than the default bevel (60% → 70%) so it
730
728
  // stays visible against the brightened face on hover.
731
729
  ...isContained && {
@@ -752,8 +750,6 @@ function buildComponents(tokens2) {
752
750
  // like a tactile click rather than a slow settle.
753
751
  transition: "border-width 120ms ease, border-radius 120ms ease, opacity 120ms ease",
754
752
  borderTopWidth: 1,
755
- borderTopLeftRadius: radius4.md,
756
- borderTopRightRadius: radius4.md,
757
753
  opacity: 0.75
758
754
  },
759
755
  // Unified disabled treatment across the button family (Button,
@@ -768,7 +764,7 @@ function buildComponents(tokens2) {
768
764
  borderTopWidth: 1,
769
765
  borderRightWidth: 1,
770
766
  borderLeftWidth: 1,
771
- borderBottomWidth: 4
767
+ borderBottomWidth: 3
772
768
  },
773
769
  "@media (prefers-reduced-motion: reduce)": {
774
770
  transition: "none",
@@ -803,7 +799,7 @@ function buildComponents(tokens2) {
803
799
  },
804
800
  styleOverrides: {
805
801
  root: {
806
- borderRadius: radius4.md,
802
+ borderRadius: radius4.lg,
807
803
  // contained group → children already render as contained Buttons; only
808
804
  // re-pin the bevel border color, which MUI's own grouped-contained
809
805
  // default (palette grey) otherwise overrides away from the standalone
@@ -843,7 +839,7 @@ function buildComponents(tokens2) {
843
839
  backgroundColor: "transparent",
844
840
  color: buttonColors4.tertiaryColor.fg,
845
841
  borderWidth: 0,
846
- borderRadius: radius4.md,
842
+ borderRadius: radius4.lg,
847
843
  boxShadow: "none",
848
844
  "&::before": { display: "none" },
849
845
  "&:active": { opacity: 1 },
@@ -875,17 +871,17 @@ function buildComponents(tokens2) {
875
871
  MuiIconButton: {
876
872
  styleOverrides: {
877
873
  root: ({ theme }) => ({
878
- borderRadius: radius4.md,
874
+ borderRadius: radius4.lg,
879
875
  padding: 6,
880
876
  "& .MuiSvgIcon-root": {
881
877
  fontSize: 20
882
878
  },
883
879
  "&.ForestIconButton": {
884
880
  ...bevel3D,
885
- // Square 32×32 with border-box so the faux-3D bevel (1px top + 4px
881
+ // Square 32×32 with border-box so the faux-3D bevel (1px top + 3px
886
882
  // bottom, 1px left/right) is absorbed rather than added on top —
887
- // otherwise it rendered a 37×34 control instead of matching the
888
- // standalone Button's 32px height.
883
+ // otherwise it rendered an oversized control instead of matching
884
+ // the standalone Button's 32px height.
889
885
  height: 32,
890
886
  width: 32,
891
887
  boxSizing: "border-box",
@@ -942,8 +938,7 @@ function buildComponents(tokens2) {
942
938
  // ─── FAB ────────────────────────────────────────────────────────
943
939
  // A Fab is a round contained Button: same faux-3D bevel, light sweep,
944
940
  // brightness lift, and press. The border radii are left untouched so the
945
- // circular / extended silhouettes survive the bevel flip (no radius swap
946
- // like Button/IconButton do on their rounded corners).
941
+ // circular / extended silhouettes survive the bevel flip.
947
942
  MuiFab: {
948
943
  styleOverrides: {
949
944
  root: ({ theme, ownerState }) => ({
@@ -959,7 +954,7 @@ function buildComponents(tokens2) {
959
954
  borderTopWidth: 1,
960
955
  borderRightWidth: 1,
961
956
  borderLeftWidth: 1,
962
- borderBottomWidth: 4,
957
+ borderBottomWidth: 3,
963
958
  "&::before": {
964
959
  content: '""',
965
960
  position: "absolute",
@@ -977,7 +972,7 @@ function buildComponents(tokens2) {
977
972
  },
978
973
  "&:hover": {
979
974
  boxShadow: tokenShadows.xs,
980
- borderTopWidth: 4,
975
+ borderTopWidth: 3,
981
976
  borderBottomWidth: 1,
982
977
  // Light mode: dark face, brightness lift reads as hover. Dark
983
978
  // mode: the face is already light — a lift blows out the top
@@ -1192,7 +1187,10 @@ function buildComponents(tokens2) {
1192
1187
  MuiAutocomplete: {
1193
1188
  styleOverrides: {
1194
1189
  paper: {
1195
- borderRadius: radius4.md,
1190
+ // No borderRadius here on purpose — it inherits MuiPaper's `rounded`
1191
+ // (radius.lg), so the popup matches Menu, Popover and every other
1192
+ // Paper surface. It used to set radius.md and was the one popup in
1193
+ // the system with a sharper corner.
1196
1194
  boxShadow: tokenShadows.lg,
1197
1195
  border: `1px solid ${border4.secondary}`,
1198
1196
  marginTop: 4
@@ -1206,10 +1204,42 @@ function buildComponents(tokens2) {
1206
1204
  lineHeight: "20px"
1207
1205
  }
1208
1206
  },
1207
+ // MUI hardcodes this slot's own padding/marginRight in its source
1208
+ // (padding: 2, marginRight: -2), which wins over the global
1209
+ // MuiIconButton root override at equal specificity — left alone, the
1210
+ // popup toggle renders as a small 24px button that doesn't fill the
1211
+ // 32px input row and stops short of the edge. Restated here at the
1212
+ // same size/inset as the DatePicker's open-picker button (32px,
1213
+ // padding 6, flush against the input's 12px right padding) so both
1214
+ // end-of-input icon buttons match.
1215
+ popupIndicator: {
1216
+ padding: 6,
1217
+ // Not a straight copy of the DatePicker's -12px: that value cancels
1218
+ // the input row's 12px padding directly, but this button's
1219
+ // grandparent (`.MuiAutocomplete-endAdornment`) is itself inset 9px
1220
+ // from the input's true right edge by MUI's own variant styles.
1221
+ // -9px reaches through that inset to land flush, matching the
1222
+ // DatePicker's open-picker button exactly.
1223
+ marginRight: -9
1224
+ },
1225
+ // Same MUI-hardcoded-slot problem as popupIndicator, one size down
1226
+ // (padding: 4, marginRight: -2) — sized to match it here. The
1227
+ // popupIndicator's own marginRight anchors it to the input edge
1228
+ // independent of clearIndicator's box, so this needs no matching
1229
+ // offset: with both buttons the same size, 0 sits them edge-to-edge,
1230
+ // and their equal padding already reads as the gap between the icons.
1231
+ clearIndicator: {
1232
+ padding: 6,
1233
+ marginRight: 0
1234
+ },
1209
1235
  option: {
1210
1236
  fontSize: 14,
1211
1237
  lineHeight: "20px",
1212
- padding: "6px 12px",
1238
+ // An Autocomplete option is a plain <li class="MuiAutocomplete-option">,
1239
+ // not a MenuItem, so MuiMenuItem's radius never reaches it. Match it
1240
+ // here so the hover/selected highlight reads as the same pill a Select
1241
+ // draws, inset by the listbox's 4px padding.
1242
+ borderRadius: radius4.md,
1213
1243
  // Hover + selected backgrounds come from MUI's theme-aware action
1214
1244
  // overlays (a light overlay on dark, so they stay visible — a solid
1215
1245
  // dark-grey tier here reads as "no hover"). Selected keeps the text
@@ -1219,7 +1249,14 @@ function buildComponents(tokens2) {
1219
1249
  }
1220
1250
  },
1221
1251
  listbox: {
1222
- padding: 4
1252
+ padding: 4,
1253
+ // Padding has to live here, not in the `option` slot: MUI nests its
1254
+ // own option styles under the listbox, so a rule from `option` ties
1255
+ // on specificity and loses on source order. This selector outranks
1256
+ // it. 6px 8px matches MuiMenuItem.
1257
+ "& .MuiAutocomplete-option": {
1258
+ padding: "6px 8px"
1259
+ }
1223
1260
  }
1224
1261
  }
1225
1262
  },
@@ -1231,7 +1268,7 @@ function buildComponents(tokens2) {
1231
1268
  styleOverrides: {
1232
1269
  root: {
1233
1270
  color: border4.primary,
1234
- borderRadius: radius4.xs,
1271
+ borderRadius: radius4.sm,
1235
1272
  padding: 0,
1236
1273
  width: 20,
1237
1274
  height: 20,
@@ -1248,7 +1285,7 @@ function buildComponents(tokens2) {
1248
1285
  "&.Mui-focusVisible": {
1249
1286
  outline: "none",
1250
1287
  boxShadow: focusRings4.brand,
1251
- borderRadius: radius4.xs
1288
+ borderRadius: radius4.sm
1252
1289
  },
1253
1290
  "&.Mui-disabled": {
1254
1291
  color: border4.primary
@@ -1384,12 +1421,18 @@ function buildComponents(tokens2) {
1384
1421
  color: buttonColors4.primary.fg,
1385
1422
  borderTopWidth: 1,
1386
1423
  borderBottomWidth: 1,
1387
- borderTopLeftRadius: radius4.md,
1388
- borderTopRightRadius: radius4.md,
1389
- borderBottomLeftRadius: radius4.md,
1390
- borderBottomRightRadius: radius4.md,
1424
+ borderTopLeftRadius: radius4.lg,
1425
+ borderTopRightRadius: radius4.lg,
1426
+ borderBottomLeftRadius: radius4.lg,
1427
+ borderBottomRightRadius: radius4.lg,
1391
1428
  opacity: 0.75,
1392
- boxShadow: "inset 0 2px 4px 0 rgba(0,0,0,0.3)",
1429
+ // The pressed-in cue is carried by the flattened bevel (bottom
1430
+ // border 3px -> 1px) alone in light mode. On the dark-mode face
1431
+ // that geometry reads too faintly, so it keeps the inner shadow.
1432
+ boxShadow: "none",
1433
+ ...theme.applyStyles("dark", {
1434
+ boxShadow: "inset 0 2px 4px 0 rgba(0,0,0,0.3)"
1435
+ }),
1393
1436
  "&::before": { display: "none" },
1394
1437
  // Stays pressed on hover — no bevel re-raise, no brightness lift.
1395
1438
  "&:hover": {
@@ -1397,8 +1440,8 @@ function buildComponents(tokens2) {
1397
1440
  filter: "none",
1398
1441
  borderTopWidth: 1,
1399
1442
  borderBottomWidth: 1,
1400
- borderTopLeftRadius: radius4.md,
1401
- borderTopRightRadius: radius4.md
1443
+ borderTopLeftRadius: radius4.lg,
1444
+ borderTopRightRadius: radius4.lg
1402
1445
  }
1403
1446
  },
1404
1447
  // variant="outlined" (Forest wrapper class): the outlined <Button> /
@@ -1442,7 +1485,7 @@ function buildComponents(tokens2) {
1442
1485
  borderTopWidth: 1,
1443
1486
  borderRightWidth: 1,
1444
1487
  borderLeftWidth: 1,
1445
- borderBottomWidth: 4,
1488
+ borderBottomWidth: 3,
1446
1489
  "&::before": { display: "none" },
1447
1490
  // selected+disabled keeps the pressed-in cue, greyed.
1448
1491
  "&.Mui-selected": {
@@ -1459,7 +1502,7 @@ function buildComponents(tokens2) {
1459
1502
  MuiToggleButtonGroup: {
1460
1503
  styleOverrides: {
1461
1504
  root: {
1462
- borderRadius: radius4.md,
1505
+ borderRadius: radius4.lg,
1463
1506
  overflow: "hidden"
1464
1507
  },
1465
1508
  // The child ToggleButton's selected/hover/active rules set per-corner
@@ -1476,14 +1519,14 @@ function buildComponents(tokens2) {
1476
1519
  },
1477
1520
  "&:first-of-type": {
1478
1521
  "&, &:hover, &:active, &.Mui-selected": {
1479
- borderTopLeftRadius: radius4.md,
1480
- borderBottomLeftRadius: radius4.md
1522
+ borderTopLeftRadius: radius4.lg,
1523
+ borderBottomLeftRadius: radius4.lg
1481
1524
  }
1482
1525
  },
1483
1526
  "&:last-of-type": {
1484
1527
  "&, &:hover, &:active, &.Mui-selected": {
1485
- borderTopRightRadius: radius4.md,
1486
- borderBottomRightRadius: radius4.md
1528
+ borderTopRightRadius: radius4.lg,
1529
+ borderBottomRightRadius: radius4.lg
1487
1530
  }
1488
1531
  }
1489
1532
  }
@@ -1509,7 +1552,27 @@ function buildComponents(tokens2) {
1509
1552
  border: `1px solid ${border4.secondary}`,
1510
1553
  backgroundColor: bg4.primary,
1511
1554
  boxShadow: tokenShadows.xs,
1512
- overflow: "hidden"
1555
+ overflow: "hidden",
1556
+ // Which surface a wrapped CardActionArea should paint. The action
1557
+ // area can't read the Card's own `background-color` (the Card goes
1558
+ // transparent when it wraps one, see below), so the colour is handed
1559
+ // down as a custom property instead. `<Card color="secondary">`
1560
+ // overrides it alongside its own background.
1561
+ "--forest-card-surface": bg4.primary,
1562
+ // A Card wrapping a CardActionArea hands its whole frame over: the
1563
+ // action area already draws the border, surface, radius and shadow,
1564
+ // and flips the radius on hover. Keeping the Card's own stacks a
1565
+ // second, static border just outside the moving one.
1566
+ "&:has(> .MuiCardActionArea-root)": {
1567
+ border: "none",
1568
+ borderRadius: 0,
1569
+ backgroundColor: "transparent",
1570
+ boxShadow: "none",
1571
+ // The action area clips its own content (it has overflow: hidden
1572
+ // for the glaze), so the Card must not also clip — its square
1573
+ // corners would cut the action area's rounded ones.
1574
+ overflow: "visible"
1575
+ }
1513
1576
  }
1514
1577
  }
1515
1578
  },
@@ -2607,52 +2670,67 @@ function buildComponents(tokens2) {
2607
2670
  }
2608
2671
  },
2609
2672
  // ─── CardActionArea ─────────────────────────────────────────────
2610
- // Clickable-card affordance: mirrors the Button's faux-3D bevel (thick edge
2611
- // that flips bottom->top on hover), light glaze sweep, border-color shift,
2612
- // and press-to-sink. This is what visually marks a card as clickable use
2613
- // <Card><CardActionArea>…</CardActionArea></Card> for any interactive card.
2673
+ // Clickable-card affordance: the outlined Button's interaction at card
2674
+ // scale. Same faux-3D bevel (thick edge that flips bottom->top on hover),
2675
+ // same glaze sweep, same brightness darken on hover, same press-to-sink
2676
+ // the only difference is the radius, which stays on the card steps
2677
+ // (xl / 2xl) rather than the button's (md / lg). Use
2678
+ // <Card><CardActionArea>…</CardActionArea></Card> for any interactive card;
2679
+ // the Card hands over its border and surface when it sees one.
2614
2680
  MuiCardActionArea: {
2615
2681
  styleOverrides: {
2616
2682
  root: {
2617
2683
  position: "relative",
2618
2684
  overflow: "hidden",
2619
- borderRadius: radius4.xl,
2685
+ borderRadius: radius4["2xl"],
2620
2686
  borderStyle: "solid",
2621
- borderColor: border4.primary,
2687
+ // The token the outlined Button reads, not `border.primary`, so the
2688
+ // two stay identical under any preset.
2689
+ borderColor: buttonColors4.secondaryColor.border,
2690
+ // Painted here rather than on the Card, which goes transparent so its
2691
+ // square box can't clip these corners. Falls back to the default card
2692
+ // surface when a CardActionArea is used outside a Card.
2693
+ backgroundColor: `var(--forest-card-surface, ${bg4.primary})`,
2622
2694
  borderTopWidth: 1,
2623
2695
  borderRightWidth: 1,
2624
2696
  borderLeftWidth: 1,
2625
- borderBottomWidth: 4,
2626
- borderBottomLeftRadius: radius4["2xl"],
2627
- borderBottomRightRadius: radius4["2xl"],
2697
+ borderBottomWidth: 3,
2628
2698
  boxShadow: tokenShadows.xs,
2629
- transition: "box-shadow 300ms ease, border-width 300ms ease, border-radius 300ms ease, border-color 300ms ease, opacity 300ms ease",
2630
- // Moving glaze — a soft light streak that sweeps top->bottom on hover.
2699
+ transition: "filter 300ms ease, border-width 300ms ease, border-radius 300ms ease, opacity 300ms ease, background-color 300ms ease",
2700
+ "&:focus-visible": {
2701
+ boxShadow: focusRings4.brandShadowXs
2702
+ },
2703
+ // Moving glaze — a soft light streak that sweeps top->bottom on
2704
+ // hover. Same parameters as the outlined Button's sweep.
2631
2705
  "&::before": {
2632
2706
  content: '""',
2633
2707
  position: "absolute",
2634
2708
  left: 0,
2635
2709
  top: "-150%",
2636
2710
  width: "100%",
2637
- height: 6,
2711
+ height: 5,
2638
2712
  borderRadius: 4,
2639
- backgroundColor: "color-mix(in srgb, currentColor 40%, #fff)",
2640
- boxShadow: "0 0 28px 6px color-mix(in srgb, currentColor 40%, #fff)",
2641
- opacity: 0.35,
2713
+ backgroundColor: "color-mix(in srgb, currentColor 45%, #fff)",
2714
+ boxShadow: "0 0 24px 4px color-mix(in srgb, currentColor 45%, #fff)",
2715
+ opacity: 0.2,
2642
2716
  filter: "blur(3px)",
2643
2717
  pointerEvents: "none",
2644
- transition: "top 600ms ease-in-out",
2718
+ transition: "top 500ms ease-in-out",
2645
2719
  zIndex: 1
2646
2720
  },
2647
2721
  "&:hover": {
2648
- borderColor: border4.brand,
2649
- borderTopWidth: 4,
2650
- borderBottomWidth: 1,
2651
- borderTopLeftRadius: radius4["2xl"],
2652
- borderTopRightRadius: radius4["2xl"],
2653
- borderBottomLeftRadius: radius4.xl,
2654
- borderBottomRightRadius: radius4.xl,
2655
- boxShadow: tokenShadows.lg
2722
+ // Darken the whole surface via brightness, the same mechanism the
2723
+ // outlined Button uses — no border-color shift; the border stays
2724
+ // put and only the bevel geometry moves. Lighter than the Button's
2725
+ // 0.97 on purpose: the filter dims a card's media and copy along
2726
+ // with its surface, so the same amount reads much heavier here.
2727
+ filter: "brightness(0.995)",
2728
+ // The Button drops its resting shadow on hover (MUI's
2729
+ // disableElevation), so the raised edge is the only depth cue
2730
+ // mid-interaction. Matched here for parity.
2731
+ boxShadow: "none",
2732
+ borderTopWidth: 3,
2733
+ borderBottomWidth: 1
2656
2734
  },
2657
2735
  "&:hover::before": {
2658
2736
  top: "150%"
@@ -2662,15 +2740,13 @@ function buildComponents(tokens2) {
2662
2740
  "&:active": {
2663
2741
  transition: "border-width 120ms ease, border-radius 120ms ease, opacity 120ms ease",
2664
2742
  borderTopWidth: 1,
2665
- borderTopLeftRadius: radius4.xl,
2666
- borderTopRightRadius: radius4.xl,
2667
- opacity: 0.85
2668
- },
2669
- "&:hover .MuiCardActionArea-focusHighlight": {
2670
- opacity: 0.04
2743
+ opacity: 0.75
2671
2744
  },
2672
- "&.Mui-focusVisible .MuiCardActionArea-focusHighlight": {
2673
- opacity: 0.08
2745
+ // MUI's own hover/focus tint overlay. The outlined Button has no
2746
+ // equivalent (it suppresses the variant hover background), so the
2747
+ // brightness darken above is the only hover feedback.
2748
+ "& .MuiCardActionArea-focusHighlight": {
2749
+ opacity: 0
2674
2750
  },
2675
2751
  "@media (prefers-reduced-motion: reduce)": {
2676
2752
  transition: "none",
@@ -3425,7 +3501,12 @@ var display = {
3425
3501
  var container = {
3426
3502
  maxWidthDesktop: 1280,
3427
3503
  paddingDesktop: 32,
3428
- paddingMobile: 16
3504
+ paddingMobile: 16,
3505
+ // Corner radius of the App Shell's content surface where it meets the
3506
+ // chrome. Deliberately a layout value rather than a step on the `radius`
3507
+ // scale: that scale governs components (and the alkemy+ preset caps it at
3508
+ // 22), so a 24 sitting in it would misread as a component radius.
3509
+ shellRadius: 24
3429
3510
  };
3430
3511
  var widths = {
3431
3512
  xxs: 320,
@@ -3691,15 +3772,15 @@ var alpha = {
3691
3772
  var highlight = magenta;
3692
3773
  var radius2 = {
3693
3774
  ...radius,
3694
- xxs: 1,
3695
- xs: 2,
3696
- sm: 3,
3697
- md: 4,
3698
- lg: 5,
3699
- xl: 6,
3700
- "2xl": 8,
3701
- "3xl": 10,
3702
- "4xl": 12
3775
+ xxs: 2,
3776
+ xs: 4,
3777
+ sm: 6,
3778
+ md: 8,
3779
+ lg: 11,
3780
+ xl: 14,
3781
+ "2xl": 16,
3782
+ "3xl": 20,
3783
+ "4xl": 22
3703
3784
  };
3704
3785
  var text2 = {
3705
3786
  primary: gray[50],
@@ -4014,15 +4095,15 @@ var colors = {
4014
4095
  };
4015
4096
  var radius3 = {
4016
4097
  ...radius,
4017
- xxs: 1,
4018
- xs: 2,
4019
- sm: 3,
4020
- md: 4,
4021
- lg: 5,
4022
- xl: 6,
4023
- "2xl": 8,
4024
- "3xl": 10,
4025
- "4xl": 12
4098
+ xxs: 2,
4099
+ xs: 4,
4100
+ sm: 6,
4101
+ md: 8,
4102
+ lg: 11,
4103
+ xl: 14,
4104
+ "2xl": 16,
4105
+ "3xl": 20,
4106
+ "4xl": 22
4026
4107
  };
4027
4108
  var text3 = {
4028
4109
  primary: gray[900],
@@ -4356,7 +4437,7 @@ function Button({ variant = "contained", ...props }) {
4356
4437
  // src/components/ButtonGroup/index.tsx
4357
4438
  var import_ButtonGroup = __toESM(require("@mui/material/ButtonGroup"));
4358
4439
  var import_jsx_runtime3 = require("react/jsx-runtime");
4359
- function ButtonGroup({ variant = "contained", ...props }) {
4440
+ function ButtonGroup({ variant = "outlined", ...props }) {
4360
4441
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ButtonGroup.default, { variant, ...props });
4361
4442
  }
4362
4443
 
@@ -5108,15 +5189,28 @@ var Logo = (0, import_react3.forwardRef)(
5108
5189
  Logo.displayName = "Logo";
5109
5190
 
5110
5191
  // src/components/ToggleButton/index.tsx
5192
+ var import_react4 = require("react");
5111
5193
  var import_ToggleButton = __toESM(require("@mui/material/ToggleButton"));
5112
5194
  var import_ToggleButtonGroup = __toESM(require("@mui/material/ToggleButtonGroup"));
5113
5195
  var import_jsx_runtime10 = require("react/jsx-runtime");
5196
+ var ToggleButtonVariantContext = (0, import_react4.createContext)(
5197
+ void 0
5198
+ );
5199
+ function ToggleButtonGroup({
5200
+ variant = "outlined",
5201
+ ...props
5202
+ }) {
5203
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToggleButtonVariantContext.Provider, { value: variant, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ToggleButtonGroup.default, { ...props }) });
5204
+ }
5114
5205
  function ToggleButton({
5115
- variant = "contained",
5206
+ variant,
5116
5207
  className,
5117
5208
  ...props
5118
5209
  }) {
5119
- const cls = variant === "outlined" ? ["ForestToggleButton-outlined", className].filter(Boolean).join(" ") : className;
5210
+ var _a;
5211
+ const groupVariant = (0, import_react4.useContext)(ToggleButtonVariantContext);
5212
+ const resolved = (_a = variant != null ? variant : groupVariant) != null ? _a : "outlined";
5213
+ const cls = resolved === "outlined" ? ["ForestToggleButton-outlined", className].filter(Boolean).join(" ") : className;
5120
5214
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ToggleButton.default, { className: cls, ...props });
5121
5215
  }
5122
5216
 
@@ -5349,7 +5443,12 @@ function Card({ color = "primary", sx, ...props }) {
5349
5443
  {
5350
5444
  ...props,
5351
5445
  sx: [
5352
- color === "secondary" && { backgroundColor: "background.paper" },
5446
+ color === "secondary" && {
5447
+ backgroundColor: "background.paper",
5448
+ // Also hand the surface to a wrapped CardActionArea, which paints the
5449
+ // card's background itself (the Card goes transparent around one).
5450
+ "--forest-card-surface": "var(--mui-palette-background-paper)"
5451
+ },
5353
5452
  ...Array.isArray(sx) ? sx : [sx]
5354
5453
  ]
5355
5454
  }
@@ -5464,7 +5563,7 @@ var import_Tab = __toESM(require("@mui/material/Tab"));
5464
5563
  var import_TabScrollButton = __toESM(require("@mui/material/TabScrollButton"));
5465
5564
 
5466
5565
  // src/components/Sidebar/index.tsx
5467
- var import_react4 = require("react");
5566
+ var import_react5 = require("react");
5468
5567
  var import_Drawer2 = __toESM(require("@mui/material/Drawer"));
5469
5568
  var import_Box = __toESM(require("@mui/material/Box"));
5470
5569
  var import_ChevronLeftOutlined = __toESM(require("@mui/icons-material/ChevronLeftOutlined"));
@@ -5474,14 +5573,15 @@ var import_ListItemIcon2 = __toESM(require("@mui/material/ListItemIcon"));
5474
5573
  var import_ListItemText3 = __toESM(require("@mui/material/ListItemText"));
5475
5574
  var import_Tooltip2 = __toESM(require("@mui/material/Tooltip"));
5476
5575
  var import_jsx_runtime18 = require("react/jsx-runtime");
5477
- var SidebarContext = (0, import_react4.createContext)({
5576
+ var SidebarContext = (0, import_react5.createContext)({
5478
5577
  open: true,
5479
5578
  collapsedWidth: 48,
5480
5579
  expandedWidth: 240
5481
5580
  });
5482
5581
  function useSidebar() {
5483
- return (0, import_react4.useContext)(SidebarContext);
5582
+ return (0, import_react5.useContext)(SidebarContext);
5484
5583
  }
5584
+ var TOGGLE_SIZE = 28;
5485
5585
  function SidebarNav({
5486
5586
  open,
5487
5587
  onOpenChange,
@@ -5492,6 +5592,7 @@ function SidebarNav({
5492
5592
  children,
5493
5593
  header,
5494
5594
  footer,
5595
+ toggleEdgeAllowance = 12,
5495
5596
  sx,
5496
5597
  ...rest
5497
5598
  }) {
@@ -5499,6 +5600,60 @@ function SidebarNav({
5499
5600
  const drawerWidth = open ? expandedWidth : collapsedWidth;
5500
5601
  const layoutWidth = isHover ? collapsedWidth : drawerWidth;
5501
5602
  const shouldShowToggle = showToggle != null ? showToggle : !isHover;
5603
+ const wrapperRef = (0, import_react5.useRef)(null);
5604
+ const toggleRef = (0, import_react5.useRef)(null);
5605
+ const edgeYRef = (0, import_react5.useRef)(null);
5606
+ const [edgeActive, setEdgeActive] = (0, import_react5.useState)(false);
5607
+ const [toggleFocused, setToggleFocused] = (0, import_react5.useState)(false);
5608
+ const writeY = (y) => {
5609
+ edgeYRef.current = y;
5610
+ if (y === null) return;
5611
+ const el = toggleRef.current;
5612
+ if (!el) return;
5613
+ el.style.top = "0px";
5614
+ el.style.transform = `translate3d(50%, ${y - TOGGLE_SIZE / 2}px, 0)`;
5615
+ };
5616
+ const resetY = () => {
5617
+ edgeYRef.current = null;
5618
+ const el = toggleRef.current;
5619
+ if (!el) return;
5620
+ el.style.top = "";
5621
+ el.style.transform = "";
5622
+ };
5623
+ (0, import_react5.useEffect)(() => {
5624
+ if (!shouldShowToggle) return;
5625
+ const onMove = (e) => {
5626
+ const el = wrapperRef.current;
5627
+ if (!el) return;
5628
+ const r = el.getBoundingClientRect();
5629
+ const withinBand = Math.abs(e.clientX - r.right) <= toggleEdgeAllowance && e.clientY >= r.top && e.clientY <= r.bottom;
5630
+ const half = TOGGLE_SIZE / 2;
5631
+ const cur = edgeYRef.current;
5632
+ const overToggle = cur !== null && Math.abs(e.clientX - r.right) <= half && Math.abs(e.clientY - (r.top + cur)) <= half;
5633
+ let next = null;
5634
+ if (withinBand || overToggle) {
5635
+ const max = Math.max(r.height - half, half);
5636
+ next = Math.min(Math.max(e.clientY - r.top, half), max);
5637
+ }
5638
+ const prev = edgeYRef.current;
5639
+ if (prev === next) return;
5640
+ if (prev !== null && next !== null && Math.abs(prev - next) < 0.5) return;
5641
+ writeY(next);
5642
+ if (prev === null !== (next === null)) setEdgeActive(next !== null);
5643
+ };
5644
+ const onLeave = () => {
5645
+ if (edgeYRef.current === null) return;
5646
+ writeY(null);
5647
+ setEdgeActive(false);
5648
+ };
5649
+ window.addEventListener("pointermove", onMove, { passive: true });
5650
+ document.addEventListener("pointerleave", onLeave);
5651
+ return () => {
5652
+ window.removeEventListener("pointermove", onMove);
5653
+ document.removeEventListener("pointerleave", onLeave);
5654
+ };
5655
+ }, [shouldShowToggle, toggleEdgeAllowance]);
5656
+ const toggleVisible = edgeActive || toggleFocused;
5502
5657
  const hoverHandlers = isHover ? {
5503
5658
  onMouseEnter: () => onOpenChange(true),
5504
5659
  onMouseLeave: () => onOpenChange(false)
@@ -5506,6 +5661,7 @@ function SidebarNav({
5506
5661
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SidebarContext.Provider, { value: { open, collapsedWidth, expandedWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5507
5662
  import_Box.default,
5508
5663
  {
5664
+ ref: wrapperRef,
5509
5665
  sx: {
5510
5666
  position: "relative",
5511
5667
  width: layoutWidth,
@@ -5513,7 +5669,13 @@ function SidebarNav({
5513
5669
  // Match the drawer paper's width animation so the absolutely-
5514
5670
  // positioned toggle (pinned to this box's right edge) glides with the
5515
5671
  // sidebar instead of jumping. Constant in hover mode → no-op there.
5516
- transition: "width 200ms ease-in-out"
5672
+ transition: "width 200ms ease-in-out",
5673
+ // A hover-expanded sidebar has to overlay the content, which in
5674
+ // AppShell sits at drawer + 1. This lives on the wrapper rather than
5675
+ // on `& .MuiDrawer-paper` because a consumer's `sx` replaces that
5676
+ // nested key wholesale (AppShell does). Only while expanded, so the
5677
+ // resting rail still takes the content surface's shadow.
5678
+ ...isHover && open && { zIndex: (theme) => theme.zIndex.drawer + 2 }
5517
5679
  },
5518
5680
  children: [
5519
5681
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
@@ -5529,8 +5691,7 @@ function SidebarNav({
5529
5691
  width: drawerWidth,
5530
5692
  transition: "width 200ms ease-in-out",
5531
5693
  overflowX: "hidden",
5532
- boxSizing: "border-box",
5533
- ...isHover && { zIndex: (theme) => theme.zIndex.drawer + 2 }
5694
+ boxSizing: "border-box"
5534
5695
  },
5535
5696
  ...sx
5536
5697
  },
@@ -5545,53 +5706,113 @@ function SidebarNav({
5545
5706
  },
5546
5707
  children: [
5547
5708
  header && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box.default, { sx: { flexShrink: 0 }, children: header }),
5548
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box.default, { sx: { flexGrow: 1, overflowY: "auto", overflowX: "hidden" }, children }),
5709
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5710
+ import_Box.default,
5711
+ {
5712
+ sx: {
5713
+ flexGrow: 1,
5714
+ overflowY: "auto",
5715
+ overflowX: "hidden",
5716
+ px: 1,
5717
+ "& > .MuiList-root": { paddingInline: 0 }
5718
+ },
5719
+ children
5720
+ }
5721
+ ),
5549
5722
  footer && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box.default, { sx: { flexShrink: 0 }, children: footer })
5550
5723
  ]
5551
5724
  }
5552
5725
  )
5553
5726
  }
5554
5727
  ),
5555
- shouldShowToggle && // The forest Button carries the faux-3D bevel/glaze/press, sized down
5556
- // to a square icon toggle. Face is the tonal secondary (light grey in
5557
- // light / charcoal in dark), not the primary brand fill — the toggle
5558
- // is a utility control, not a call to action.
5728
+ shouldShowToggle && // Positioning shell: pinned to the wrapper's top-right, moved down the
5729
+ // edge with a transform written by the pointermove handler (see
5730
+ // `writeY`). Only opacity transitions position snaps to the pointer
5731
+ // so the toggle appears under the cursor and tracks it 1:1.
5559
5732
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5560
- Button,
5733
+ import_Box.default,
5561
5734
  {
5562
- "aria-label": open ? "Collapse sidebar" : "Expand sidebar",
5563
- onClick: () => onOpenChange(!open),
5735
+ ref: toggleRef,
5564
5736
  sx: (theme) => ({
5565
5737
  position: "absolute",
5566
- top: "50%",
5567
5738
  right: 0,
5568
- transform: "translate(50%, -50%)",
5569
- zIndex: theme.zIndex.drawer + 1,
5570
- minWidth: 0,
5571
- width: 28,
5572
- height: 28,
5573
- padding: 0,
5574
- // Match the shell surface (bg.primary = lightest grey) so the
5575
- // toggle reads as part of the sidebar, not a darker chip.
5576
- backgroundColor: `var(--mui-palette-bg-primary, ${bg.primary})`,
5577
- color: `var(--mui-palette-text-primary, ${text.primary})`,
5578
- // Light-grey standard border instead of the contained bevel's
5579
- // darker greyscale (fg.tertiary) the toggle is a light utility
5580
- // control, so the heavier border read too dark.
5581
- borderColor: `var(--mui-palette-border-primary, ${border.primary})`,
5582
- "& .MuiSvgIcon-root": {
5583
- fontSize: 18
5739
+ width: TOGGLE_SIZE,
5740
+ height: TOGGLE_SIZE,
5741
+ // Resting spot for keyboard focus and touch, where no pointer
5742
+ // sets a transform: vertically centred on the edge. `writeY`
5743
+ // overrides both `top` and `transform` inline while tracking
5744
+ // and leaves them in place on hide so the exit fades in situ.
5745
+ top: "50%",
5746
+ transform: "translate3d(50%, -50%, 0)",
5747
+ // Hidden until the pointer reaches the edge band. Kept mounted so
5748
+ // it stays in the tab order and can be reached by keyboard.
5749
+ opacity: toggleVisible ? 1 : 0,
5750
+ pointerEvents: toggleVisible ? "auto" : "none",
5751
+ // Enter fast with a strong ease-out so it reads as "already
5752
+ // there"; exit a touch shorter — the user has moved on.
5753
+ transition: toggleVisible ? "opacity 120ms cubic-bezier(0.19, 1, 0.22, 1)" : "opacity 100ms ease",
5754
+ "@media (prefers-reduced-motion: reduce)": {
5755
+ transition: "none"
5584
5756
  },
5585
- "&:hover": {
5586
- backgroundColor: `var(--mui-palette-bg-quaternary, ${bg.quaternary})`,
5587
- // The contained brightness lift assumes a dark face — on the
5588
- // light-grey light-mode face it would blow out. In dark the
5589
- // hover token matches the face, so a small lift is the cue.
5590
- filter: "none",
5591
- ...theme.applyStyles("dark", { filter: "brightness(1.15)" })
5592
- }
5757
+ // No hover on touch: there is no pointer to bring the toggle out,
5758
+ // so it stays visible at its resting position.
5759
+ "@media (hover: none)": {
5760
+ opacity: 1,
5761
+ pointerEvents: "auto"
5762
+ },
5763
+ // Above both the AppShell content surface (drawer + 1, which
5764
+ // paints its shadow over the sidebar) and the hover-expanded
5765
+ // drawer (drawer + 2) — the toggle straddles the seam and has
5766
+ // to stay clickable and whole on top of both.
5767
+ zIndex: theme.zIndex.drawer + 3
5593
5768
  }),
5594
- children: open ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ChevronLeftOutlined.default, {}) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ChevronRightOutlined.default, {})
5769
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5770
+ Button,
5771
+ {
5772
+ "aria-label": open ? "Collapse sidebar" : "Expand sidebar",
5773
+ onClick: (e) => {
5774
+ onOpenChange(!open);
5775
+ writeY(null);
5776
+ setEdgeActive(false);
5777
+ if (!e.currentTarget.matches(":focus-visible")) {
5778
+ e.currentTarget.blur();
5779
+ }
5780
+ },
5781
+ onFocus: (e) => {
5782
+ const kb = e.currentTarget.matches(":focus-visible");
5783
+ if (kb) resetY();
5784
+ setToggleFocused(kb);
5785
+ },
5786
+ onBlur: () => setToggleFocused(false),
5787
+ sx: (theme) => ({
5788
+ minWidth: 0,
5789
+ width: TOGGLE_SIZE,
5790
+ height: TOGGLE_SIZE,
5791
+ padding: 0,
5792
+ transition: "background-color 300ms ease",
5793
+ // Match the shell surface (bg.primary = lightest grey) so the
5794
+ // toggle reads as part of the sidebar, not a darker chip.
5795
+ backgroundColor: `var(--mui-palette-bg-primary, ${bg.primary})`,
5796
+ color: `var(--mui-palette-text-primary, ${text.primary})`,
5797
+ // Light-grey standard border instead of the contained bevel's
5798
+ // darker greyscale (fg.tertiary) — the toggle is a light
5799
+ // utility control, so the heavier border read too dark.
5800
+ borderColor: `var(--mui-palette-border-primary, ${border.primary})`,
5801
+ "& .MuiSvgIcon-root": {
5802
+ fontSize: 18
5803
+ },
5804
+ "&:hover": {
5805
+ backgroundColor: `var(--mui-palette-bg-quaternary, ${bg.quaternary})`,
5806
+ // The contained brightness lift assumes a dark face — on the
5807
+ // light-grey light-mode face it would blow out. In dark the
5808
+ // hover token matches the face, so a small lift is the cue.
5809
+ filter: "none",
5810
+ ...theme.applyStyles("dark", { filter: "brightness(1.15)" })
5811
+ }
5812
+ }),
5813
+ children: open ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ChevronLeftOutlined.default, {}) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ChevronRightOutlined.default, {})
5814
+ }
5815
+ )
5595
5816
  }
5596
5817
  )
5597
5818
  ]
@@ -5599,8 +5820,13 @@ function SidebarNav({
5599
5820
  ) });
5600
5821
  }
5601
5822
  var itemSx = {
5602
- // Preset radius.md (4px) same corner radius as buttons and inputs.
5603
- borderRadius: "4px",
5823
+ // Off-scale on purpose: the alkemy+ preset's radius steps run 1→12, so 11px
5824
+ // sits between "3xl" (10) and "4xl" (12) with no token to name it.
5825
+ borderRadius: "11px",
5826
+ // The MuiListItemButton override sets 12px; 8px here absorbs the container's
5827
+ // new 8px gutter so the icon column stays exactly 16px from the sidebar edge,
5828
+ // where it has always been.
5829
+ paddingInline: "8px",
5604
5830
  // 4px gap between stacked items, independent of the container (List/flex).
5605
5831
  marginBottom: "4px",
5606
5832
  "&:last-of-type": { marginBottom: 0 },
@@ -5662,7 +5888,7 @@ function SidebarItem({
5662
5888
  }
5663
5889
 
5664
5890
  // src/components/AppShell/index.tsx
5665
- var import_react5 = require("react");
5891
+ var import_react6 = require("react");
5666
5892
  var import_Box2 = __toESM(require("@mui/material/Box"));
5667
5893
  var import_Drawer3 = __toESM(require("@mui/material/Drawer"));
5668
5894
  var import_List2 = __toESM(require("@mui/material/List"));
@@ -5713,8 +5939,8 @@ function AppShell({
5713
5939
  }) {
5714
5940
  const theme = (0, import_styles4.useTheme)();
5715
5941
  const isMobile = (0, import_useMediaQuery.default)(theme.breakpoints.down("sm"));
5716
- const [sidebarOpen, setSidebarOpen] = (0, import_react5.useState)(defaultSidebarOpen);
5717
- const [mobileOpen, setMobileOpen] = (0, import_react5.useState)(false);
5942
+ const [sidebarOpen, setSidebarOpen] = (0, import_react6.useState)(defaultSidebarOpen);
5943
+ const [mobileOpen, setMobileOpen] = (0, import_react6.useState)(false);
5718
5944
  const navContent = nav != null ? nav : navItems && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_List2.default, { children: navItems.map(({ label, icon, active, endAdornment, ...link }) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5719
5945
  SidebarItem,
5720
5946
  {
@@ -5728,63 +5954,135 @@ function AppShell({
5728
5954
  )) });
5729
5955
  const brandEl = brand != null ? brand : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ShellBrand, { product, logoVariant });
5730
5956
  const wrapLogomark = !brand && logoVariant === "logomark" && !isMobile;
5731
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Box2.default, { sx: { display: "flex", flexDirection: "column", height: "100vh" }, children: [
5732
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5733
- import_AppBar.default,
5957
+ return (
5958
+ // Clipping lives here rather than on the body row below, so the content
5959
+ // surface's shadow can spill up over the AppBar instead of being cut off
5960
+ // at the row's top edge.
5961
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5962
+ import_Box2.default,
5734
5963
  {
5735
- position: "sticky",
5736
- elevation: 0,
5737
- sx: { zIndex: theme.zIndex.drawer + 1 },
5738
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Toolbar.default, { variant: "dense", sx: { px: { xs: "16px", sm: "16px" } }, children: [
5739
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Box2.default, { sx: { flex: 1, display: "flex", alignItems: "center", justifyContent: "flex-start", gap: 1 }, children: [
5740
- isMobile && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5741
- IconButton2,
5964
+ sx: {
5965
+ display: "flex",
5966
+ flexDirection: "column",
5967
+ height: "100vh",
5968
+ overflow: "hidden",
5969
+ // The chrome fill belongs here, not only on the AppBar and Drawer.
5970
+ // `main`'s rounded top-left corner clips its own background, and the
5971
+ // notch between that arc and the square box corner is reached by
5972
+ // neither the bar nor the sidebar — so without this, whatever sits
5973
+ // behind the shell shows through it. Storybook's docs container is
5974
+ // white, which is exactly how it surfaced.
5975
+ backgroundColor: "background.default"
5976
+ },
5977
+ children: [
5978
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5979
+ import_AppBar.default,
5980
+ {
5981
+ position: "sticky",
5982
+ elevation: 0,
5983
+ sx: { zIndex: theme.zIndex.drawer + 1, borderBottom: "none" },
5984
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Toolbar.default, { variant: "dense", sx: { px: { xs: "16px", sm: "16px" } }, children: [
5985
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Box2.default, { sx: { flex: 1, display: "flex", alignItems: "center", justifyContent: "flex-start", gap: 1 }, children: [
5986
+ isMobile && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5987
+ IconButton2,
5988
+ {
5989
+ variant: "icon",
5990
+ size: "small",
5991
+ color: "inherit",
5992
+ "aria-label": "Open navigation",
5993
+ onClick: () => setMobileOpen(true),
5994
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_MenuOutlined.default, { fontSize: "small" })
5995
+ }
5996
+ ),
5997
+ wrapLogomark ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_Box2.default, { sx: { width: 48, ml: "-16px", display: "flex", justifyContent: "center" }, children: brandEl }) : brandEl
5998
+ ] }),
5999
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_Box2.default, { sx: { flex: 1, display: "flex", justifyContent: "center" }, children: search === true ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Search, { size: "small", placeholder: "Search\u2026", sx: { width: "100%", maxWidth: 480 } }) : search || null }),
6000
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Box2.default, { sx: { flex: 1, display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 1 }, children: [
6001
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ColorModeToggle, {}),
6002
+ actions
6003
+ ] })
6004
+ ] })
6005
+ }
6006
+ ),
6007
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Box2.default, { sx: { display: "flex", flexGrow: 1, minHeight: 0 }, children: [
6008
+ !isMobile && navContent && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
6009
+ SidebarNav,
5742
6010
  {
5743
- variant: "icon",
5744
- size: "small",
5745
- color: "inherit",
5746
- "aria-label": "Open navigation",
5747
- onClick: () => setMobileOpen(true),
5748
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_MenuOutlined.default, { fontSize: "small" })
6011
+ open: sidebarOpen,
6012
+ onOpenChange: setSidebarOpen,
6013
+ behavior: sidebarBehavior,
6014
+ sx: {
6015
+ height: "100%",
6016
+ "& .MuiDrawer-paper": {
6017
+ position: "relative",
6018
+ height: "100%",
6019
+ // Same reason as the AppBar's border: the chrome is one piece.
6020
+ borderRight: "none"
6021
+ }
6022
+ },
6023
+ children: navContent
5749
6024
  }
5750
6025
  ),
5751
- wrapLogomark ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_Box2.default, { sx: { width: 48, ml: "-16px", display: "flex", justifyContent: "center" }, children: brandEl }) : brandEl
6026
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
6027
+ import_Box2.default,
6028
+ {
6029
+ component: "main",
6030
+ sx: (t) => ({
6031
+ flexGrow: 1,
6032
+ overflow: "auto",
6033
+ backgroundColor: "background.paper",
6034
+ // Lift the surface above the chrome so its shadow lands ON the
6035
+ // sidebar and the AppBar rather than under them. Level with the
6036
+ // AppBar and later in the DOM, so it wins there; the hover
6037
+ // sidebar sits a level higher still and keeps overlaying content.
6038
+ position: "relative",
6039
+ zIndex: t.zIndex.drawer + 1,
6040
+ // Longhands, not `borderTop: "1px solid"` — the shorthand resets
6041
+ // border-*-color to currentColor and the hairline renders as text
6042
+ // black however `borderColor` is ordered around it.
6043
+ borderStyle: "solid",
6044
+ borderColor: "divider",
6045
+ borderTopWidth: "1px",
6046
+ borderRightWidth: 0,
6047
+ borderBottomWidth: 0,
6048
+ borderTopLeftRadius: `${container.shellRadius}px`,
6049
+ // With no sidebar below "sm" a single left corner reads lopsided,
6050
+ // so the surface rounds both top corners and drops the left edge.
6051
+ borderLeftWidth: { xs: 0, sm: "1px" },
6052
+ borderTopRightRadius: { xs: `${container.shellRadius}px`, sm: 0 },
6053
+ // Up-and-left, so the lift falls onto the chrome. In dark the
6054
+ // surface is already a step lighter than the chrome and does most
6055
+ // of the lifting itself, so the shadow only has to seat the edge.
6056
+ // Wide and low-contrast rather than tight and dark — a diffuse
6057
+ // falloff reads as lift without drawing a line.
6058
+ boxShadow: "-2px -2px 20px -16px #1018281a, -1px -1px 6px -3px #1018281c",
6059
+ // Dark keeps the same `divider` hairline as light — gray-900 on
6060
+ // gray-950 is only a ~15-level tonal step, so the line still does
6061
+ // the work of defining the edge. Only the shadow changes: one
6062
+ // contact layer, since the surface being lighter than the chrome
6063
+ // already reads as raised.
6064
+ ...t.applyStyles("dark", {
6065
+ boxShadow: "-1px -1px 6px -3px #00000059"
6066
+ })
6067
+ }),
6068
+ children
6069
+ }
6070
+ )
5752
6071
  ] }),
5753
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_Box2.default, { sx: { flex: 1, display: "flex", justifyContent: "center" }, children: search === true ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Search, { size: "small", placeholder: "Search\u2026", sx: { width: "100%", maxWidth: 480 } }) : search || null }),
5754
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Box2.default, { sx: { flex: 1, display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 1 }, children: [
5755
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ColorModeToggle, {}),
5756
- actions
5757
- ] })
5758
- ] })
5759
- }
5760
- ),
5761
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Box2.default, { sx: { display: "flex", flexGrow: 1, overflow: "hidden" }, children: [
5762
- !isMobile && navContent && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5763
- SidebarNav,
5764
- {
5765
- open: sidebarOpen,
5766
- onOpenChange: setSidebarOpen,
5767
- behavior: sidebarBehavior,
5768
- sx: {
5769
- height: "100%",
5770
- "& .MuiDrawer-paper": { position: "relative", height: "100%" }
5771
- },
5772
- children: navContent
5773
- }
5774
- ),
5775
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_Box2.default, { component: "main", sx: { flexGrow: 1, overflow: "auto" }, children })
5776
- ] }),
5777
- isMobile && navContent && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5778
- import_Drawer3.default,
5779
- {
5780
- open: mobileOpen,
5781
- onClose: () => setMobileOpen(false),
5782
- onClick: () => setMobileOpen(false),
5783
- sx: { "& .MuiDrawer-paper": { width: 240 } },
5784
- children: navContent
6072
+ isMobile && navContent && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
6073
+ import_Drawer3.default,
6074
+ {
6075
+ open: mobileOpen,
6076
+ onClose: () => setMobileOpen(false),
6077
+ onClick: () => setMobileOpen(false),
6078
+ sx: { "& .MuiDrawer-paper": { width: 240 } },
6079
+ children: navContent
6080
+ }
6081
+ )
6082
+ ]
5785
6083
  }
5786
6084
  )
5787
- ] });
6085
+ );
5788
6086
  }
5789
6087
 
5790
6088
  // src/components/Page/index.tsx