@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/{chunk-NXIZI6F7.mjs → chunk-B5MZMBLL.mjs} +188 -107
- package/dist/chunk-B5MZMBLL.mjs.map +1 -0
- package/dist/index.d.mts +27 -6
- package/dist/index.d.ts +27 -6
- package/dist/index.js +504 -206
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +525 -308
- package/dist/index.mjs.map +1 -1
- package/dist/theme.d.mts +1 -0
- package/dist/theme.d.ts +1 -0
- package/dist/theme.js +187 -106
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs +1 -1
- package/package.json +1 -1
- package/skills/forest-alkemy-plus/SKILL.md +2 -2
- package/skills/forest-alkemy-plus/references/components.md +3 -3
- package/skills/forest-alkemy-plus/references/patterns.md +2 -1
- package/dist/chunk-NXIZI6F7.mjs.map +0 -1
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: () =>
|
|
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:
|
|
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:
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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 +
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1388
|
-
borderTopRightRadius: radius4.
|
|
1389
|
-
borderBottomLeftRadius: radius4.
|
|
1390
|
-
borderBottomRightRadius: radius4.
|
|
1424
|
+
borderTopLeftRadius: radius4.lg,
|
|
1425
|
+
borderTopRightRadius: radius4.lg,
|
|
1426
|
+
borderBottomLeftRadius: radius4.lg,
|
|
1427
|
+
borderBottomRightRadius: radius4.lg,
|
|
1391
1428
|
opacity: 0.75,
|
|
1392
|
-
|
|
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.
|
|
1401
|
-
borderTopRightRadius: radius4.
|
|
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:
|
|
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.
|
|
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.
|
|
1480
|
-
borderBottomLeftRadius: radius4.
|
|
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.
|
|
1486
|
-
borderBottomRightRadius: radius4.
|
|
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:
|
|
2611
|
-
// that flips bottom->top on hover),
|
|
2612
|
-
//
|
|
2613
|
-
//
|
|
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
|
|
2685
|
+
borderRadius: radius4["2xl"],
|
|
2620
2686
|
borderStyle: "solid",
|
|
2621
|
-
|
|
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:
|
|
2626
|
-
borderBottomLeftRadius: radius4["2xl"],
|
|
2627
|
-
borderBottomRightRadius: radius4["2xl"],
|
|
2697
|
+
borderBottomWidth: 3,
|
|
2628
2698
|
boxShadow: tokenShadows.xs,
|
|
2629
|
-
transition: "
|
|
2630
|
-
|
|
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:
|
|
2711
|
+
height: 5,
|
|
2638
2712
|
borderRadius: 4,
|
|
2639
|
-
backgroundColor: "color-mix(in srgb, currentColor
|
|
2640
|
-
boxShadow: "0 0
|
|
2641
|
-
opacity: 0.
|
|
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
|
|
2718
|
+
transition: "top 500ms ease-in-out",
|
|
2645
2719
|
zIndex: 1
|
|
2646
2720
|
},
|
|
2647
2721
|
"&:hover": {
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
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
|
-
|
|
2666
|
-
borderTopRightRadius: radius4.xl,
|
|
2667
|
-
opacity: 0.85
|
|
2668
|
-
},
|
|
2669
|
-
"&:hover .MuiCardActionArea-focusHighlight": {
|
|
2670
|
-
opacity: 0.04
|
|
2743
|
+
opacity: 0.75
|
|
2671
2744
|
},
|
|
2672
|
-
|
|
2673
|
-
|
|
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:
|
|
3695
|
-
xs:
|
|
3696
|
-
sm:
|
|
3697
|
-
md:
|
|
3698
|
-
lg:
|
|
3699
|
-
xl:
|
|
3700
|
-
"2xl":
|
|
3701
|
-
"3xl":
|
|
3702
|
-
"4xl":
|
|
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:
|
|
4018
|
-
xs:
|
|
4019
|
-
sm:
|
|
4020
|
-
md:
|
|
4021
|
-
lg:
|
|
4022
|
-
xl:
|
|
4023
|
-
"2xl":
|
|
4024
|
-
"3xl":
|
|
4025
|
-
"4xl":
|
|
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 = "
|
|
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
|
|
5206
|
+
variant,
|
|
5116
5207
|
className,
|
|
5117
5208
|
...props
|
|
5118
5209
|
}) {
|
|
5119
|
-
|
|
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" && {
|
|
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
|
|
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,
|
|
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,
|
|
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)(
|
|
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 && //
|
|
5556
|
-
//
|
|
5557
|
-
//
|
|
5558
|
-
//
|
|
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
|
-
|
|
5733
|
+
import_Box.default,
|
|
5561
5734
|
{
|
|
5562
|
-
|
|
5563
|
-
onClick: () => onOpenChange(!open),
|
|
5735
|
+
ref: toggleRef,
|
|
5564
5736
|
sx: (theme) => ({
|
|
5565
5737
|
position: "absolute",
|
|
5566
|
-
top: "50%",
|
|
5567
5738
|
right: 0,
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
//
|
|
5581
|
-
|
|
5582
|
-
"
|
|
5583
|
-
|
|
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
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
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:
|
|
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
|
-
//
|
|
5603
|
-
|
|
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
|
|
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,
|
|
5717
|
-
const [mobileOpen, setMobileOpen] = (0,
|
|
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
|
|
5732
|
-
|
|
5733
|
-
|
|
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
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
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
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
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
|