@12-apps/ui 6.76.0 → 6.76.1

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  SettingsRailGroup,
4
4
  SettingsRailItem
5
- } from "../../../../chunk-4BLVVMY7.js";
5
+ } from "../../../../chunk-SL56MOKE.js";
6
6
  import "../../../../chunk-7Y2VWMGS.js";
7
7
  import "../../../../chunk-NK4T624E.js";
8
8
  import "../../../../chunk-YKCAJ2TN.js";
@@ -1,13 +1,13 @@
1
1
  "use client";
2
2
  import {
3
3
  SettingsLayout
4
- } from "../../../../chunk-6KM3TL4Q.js";
4
+ } from "../../../../chunk-SO5OU2ZV.js";
5
5
  import "../../../../chunk-BXC6GRLE.js";
6
6
  import "../../../../chunk-LY5I3ZSV.js";
7
7
  import "../../../../chunk-GBVNY2LG.js";
8
8
  import "../../../../chunk-AJUSBPWG.js";
9
- import "../../../../chunk-SWQXAUUG.js";
10
- import "../../../../chunk-4BLVVMY7.js";
9
+ import "../../../../chunk-TJO4CBYT.js";
10
+ import "../../../../chunk-SL56MOKE.js";
11
11
  import "../../../../chunk-7Y2VWMGS.js";
12
12
  import "../../../../chunk-WOXNVM52.js";
13
13
  import "../../../../chunk-NK4T624E.js";
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import {
3
3
  SettingsRail
4
- } from "../../../../chunk-SWQXAUUG.js";
5
- import "../../../../chunk-4BLVVMY7.js";
4
+ } from "../../../../chunk-TJO4CBYT.js";
5
+ import "../../../../chunk-SL56MOKE.js";
6
6
  import "../../../../chunk-7Y2VWMGS.js";
7
7
  import "../../../../chunk-NK4T624E.js";
8
8
  import "../../../../chunk-YKCAJ2TN.js";
@@ -6,7 +6,8 @@ import {
6
6
  atLeastRail
7
7
  } from "./chunk-NK4T624E.js";
8
8
  import {
9
- rem
9
+ rem,
10
+ sxRem
10
11
  } from "./chunk-YKCAJ2TN.js";
11
12
 
12
13
  // src/components/layout/SettingsLayout/SettingsLayout.items.tsx
@@ -43,6 +44,21 @@ function RailItemContent({
43
44
  ) : null
44
45
  ] });
45
46
  }
47
+ function railItemColor(active, card) {
48
+ if (active) return card ? "primary.dark" : "primary.main";
49
+ return card ? "text.primary" : "text.secondary";
50
+ }
51
+ var FOCUS_RING_SX = {
52
+ "&.Mui-focusVisible": {
53
+ outlineStyle: "solid",
54
+ outlineWidth: sxRem(2),
55
+ outlineColor: "primary.main",
56
+ outlineOffset: sxRem(-2)
57
+ }
58
+ };
59
+ function cardRowSx(theme, breakpoint) {
60
+ return { [atLeastRail(theme, breakpoint)]: { minHeight: rem(theme, 36), py: sxRem(6) } };
61
+ }
46
62
  function SettingsRailItem({
47
63
  item,
48
64
  active,
@@ -51,7 +67,8 @@ function SettingsRailItem({
51
67
  onNavigate,
52
68
  testIdPrefix,
53
69
  variant,
54
- breakpoint
70
+ breakpoint,
71
+ card = false
55
72
  }) {
56
73
  const theme = useTheme();
57
74
  const touchFloor = variant === "drilldown" ? { minHeight: rem(theme, TOUCH_TARGET), [atLeastRail(theme, breakpoint)]: { minHeight: "auto" } } : null;
@@ -62,11 +79,13 @@ function SettingsRailItem({
62
79
  mb: 0.25,
63
80
  gap: 1.5,
64
81
  ...touchFloor,
65
- color: active ? "primary.main" : "text.secondary",
82
+ ...card ? cardRowSx(theme, breakpoint) : null,
83
+ color: railItemColor(active, card),
66
84
  "&.Mui-selected": {
67
85
  bgcolor: alpha(theme.palette.primary.main, 0.12),
68
86
  "&:hover": { bgcolor: alpha(theme.palette.primary.main, 0.18) }
69
- }
87
+ },
88
+ ...FOCUS_RING_SX
70
89
  }
71
90
  };
72
91
  const content = /* @__PURE__ */ jsx(RailItemContent, { item, active, testIdPrefix });
@@ -111,7 +130,9 @@ function indexListSx(shape) {
111
130
  // One spacing unit (8px). This is an `sx` callback, where a bare 8 is
112
131
  // EIGHT units — it drew a 64px gutter between the two columns.
113
132
  columnGap: 1,
114
- [theme.breakpoints.up("sm")]: { gridTemplateColumns: "1fr 1fr" },
133
+ // From `md`, not `sm`: at 600 two columns of bare text rows read as one
134
+ // list with a hole in it, and a row's status dot sat nearer the next column.
135
+ [theme.breakpoints.up("md")]: { gridTemplateColumns: "1fr 1fr" },
115
136
  [atLeastRail(theme, shape.breakpoint)]: { gridTemplateColumns: "1fr" }
116
137
  });
117
138
  }
@@ -169,4 +190,4 @@ export {
169
190
  SettingsRailItem,
170
191
  SettingsRailGroup
171
192
  };
172
- //# sourceMappingURL=chunk-4BLVVMY7.js.map
193
+ //# sourceMappingURL=chunk-SL56MOKE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/layout/SettingsLayout/SettingsLayout.items.tsx"],"sourcesContent":["'use client';\n\nimport Box from '@mui/material/Box/index.js';\nimport List from '@mui/material/List/index.js';\nimport ListItem from '@mui/material/ListItem/index.js';\nimport ListItemButton from '@mui/material/ListItemButton/index.js';\nimport ListItemIcon from '@mui/material/ListItemIcon/index.js';\nimport ListItemText from '@mui/material/ListItemText/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport { alpha, useTheme } from '@mui/material/styles/index.js';\nimport type { SxProps, Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { atLeastRail, TOUCH_TARGET } from './SettingsLayout.styles';\nimport { rem, sxRem } from '../../../tokens/relative';\nimport { SettingsStatusMarker } from './SettingsStatusMarker';\nimport type {\n SettingsLayoutProps,\n SettingsNavGroup,\n SettingsNavItem,\n SettingsNavVariant,\n SettingsRailBreakpoint,\n} from './SettingsLayout.types';\n\n/** The shape-deciding inputs every rail part needs to know about. */\nexport interface RailShape {\n variant: SettingsNavVariant;\n breakpoint: SettingsRailBreakpoint;\n atIndex: boolean;\n /** The rail is drawn as a card (`railVariant=\"card\"`): denser rows, darker text. */\n card?: boolean;\n}\n\ninterface RailItemProps extends RailShape {\n item: SettingsNavItem;\n active: boolean;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n /** Called on activation so the `switcher`'s rail can collapse after a choice. */\n onNavigate?: () => void;\n testIdPrefix: string;\n}\n\n/** Label, plus the situation marker when the host resolved one. */\nfunction RailItemContent({\n item,\n active,\n testIdPrefix,\n}: {\n item: SettingsNavItem;\n active: boolean;\n testIdPrefix: string;\n}): React.JSX.Element {\n return (\n <>\n {item.icon ? (\n <ListItemIcon sx={{ minWidth: 0, color: 'inherit' }}>{item.icon}</ListItemIcon>\n ) : null}\n <ListItemText\n primary={item.label}\n primaryTypographyProps={{ variant: 'body2', noWrap: true, fontWeight: active ? 600 : 500 }}\n />\n {item.status ? (\n <SettingsStatusMarker\n status={item.status}\n label={item.statusLabel}\n testId={`${testIdPrefix}-status-${item.id}`}\n />\n ) : null}\n </>\n );\n}\n\n/**\n * The row's text colour. On the card the active row's red is the darker one,\n * since `primary.main` on its 12% wash measured 4.19:1 — under AA at 16px —\n * and the rest read in the body colour, as the card's prototype draws them.\n */\nfunction railItemColor(active: boolean, card: boolean): string {\n if (active) return card ? 'primary.dark' : 'primary.main';\n return card ? 'text.primary' : 'text.secondary';\n}\n\n/**\n * Keyboard focus as a ring, not MUI's grey fill: the fill measured 1.3:1\n * against the rail, under the 3:1 a focus indicator needs.\n */\nconst FOCUS_RING_SX = {\n '&.Mui-focusVisible': {\n outlineStyle: 'solid',\n outlineWidth: sxRem(2),\n outlineColor: 'primary.main',\n outlineOffset: sxRem(-2),\n },\n} as const;\n\n/** The card's rows from the breakpoint up: 36px, the prototype's rail density. */\nfunction cardRowSx(theme: Theme, breakpoint: SettingsRailBreakpoint): Record<string, unknown> {\n return { [atLeastRail(theme, breakpoint)]: { minHeight: rem(theme, 36), py: sxRem(6) } };\n}\n\n/** One rail entry — a link when it has `href`, inert text when it says so, else a button. */\nexport function SettingsRailItem({\n item,\n active,\n linkComponent,\n onSelectItem,\n onNavigate,\n testIdPrefix,\n variant,\n breakpoint,\n card = false,\n}: RailItemProps): React.JSX.Element {\n const theme = useTheme();\n // In `drilldown` the narrow rail IS the page's list, so its rows are the\n // primary touch targets rather than a secondary column — hence the 44px floor\n // below the breakpoint. Above it, the row goes back to rail density.\n const touchFloor =\n variant === 'drilldown'\n ? { minHeight: rem(theme, TOUCH_TARGET), [atLeastRail(theme, breakpoint)]: { minHeight: 'auto' } }\n : null;\n const shared = {\n 'data-testid': `${testIdPrefix}-item-${item.id}`,\n sx: {\n borderRadius: 1.5,\n mb: 0.25,\n gap: 1.5,\n ...touchFloor,\n ...(card ? cardRowSx(theme, breakpoint) : null),\n color: railItemColor(active, card),\n '&.Mui-selected': {\n bgcolor: alpha(theme.palette.primary.main, 0.12),\n '&:hover': { bgcolor: alpha(theme.palette.primary.main, 0.18) },\n },\n ...FOCUS_RING_SX,\n },\n };\n const content = <RailItemContent item={item} active={active} testIdPrefix={testIdPrefix} />;\n\n // Listed but not reachable from here. Deliberately not a disabled button: a\n // disabled control says \"you may not\", and the true statement is \"not from\n // this screen\" — the section exists and someone else's screen owns it.\n if (item.inert) {\n return (\n <ListItem {...shared} data-inert=\"true\">\n {content}\n </ListItem>\n );\n }\n\n const interactive = {\n ...shared,\n selected: active,\n 'aria-current': active ? ('page' as const) : undefined,\n };\n\n if (item.href && linkComponent) {\n const LinkComponent = linkComponent;\n return (\n <ListItemButton\n component={LinkComponent}\n href={item.href}\n onClick={onNavigate}\n {...interactive}\n >\n {content}\n </ListItemButton>\n );\n }\n return (\n <ListItemButton\n onClick={() => {\n onSelectItem?.(item.id);\n onNavigate?.();\n }}\n {...interactive}\n >\n {content}\n </ListItemButton>\n );\n}\n\ninterface RailGroupProps extends RailShape {\n group: SettingsNavGroup;\n activeItemId?: string;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n onNavigate?: () => void;\n testIdPrefix: string;\n}\n\n/**\n * The list's own grid, in `drilldown` at the index only.\n *\n * At the index on a wide phone or a tablet the list has room for two columns;\n * the rail column never does, so the second column is undone at exactly the\n * width where this stops being the page.\n */\nfunction indexListSx(shape: RailShape): Extract<SxProps<Theme>, (theme: Theme) => unknown> | null {\n if (shape.variant !== 'drilldown' || !shape.atIndex) return null;\n return (theme: import('@mui/material').Theme) => ({\n display: 'grid',\n gridTemplateColumns: '1fr',\n // One spacing unit (8px). This is an `sx` callback, where a bare 8 is\n // EIGHT units — it drew a 64px gutter between the two columns.\n columnGap: 1,\n // From `md`, not `sm`: at 600 two columns of bare text rows read as one\n // list with a hole in it, and a row's status dot sat nearer the next column.\n [theme.breakpoints.up('md')]: { gridTemplateColumns: '1fr 1fr' },\n [atLeastRail(theme, shape.breakpoint)]: { gridTemplateColumns: '1fr' },\n });\n}\n\n/** A category: header + optional description + its items. */\nexport function SettingsRailGroup({\n group,\n activeItemId,\n linkComponent,\n onSelectItem,\n onNavigate,\n testIdPrefix,\n ...shape\n}: RailGroupProps): React.JSX.Element {\n const grid = indexListSx(shape);\n return (\n <Box data-testid={`${testIdPrefix}-group-${group.id}`} sx={{ mb: 2 }}>\n <Typography\n variant=\"caption\"\n sx={{\n display: 'block',\n px: 1,\n color: 'text.secondary',\n fontWeight: 600,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n }}\n >\n {group.label}\n </Typography>\n {group.description ? (\n <Typography variant=\"caption\" component=\"p\" sx={{ px: 1, mt: 0.25, color: 'text.disabled' }}>\n {group.description}\n </Typography>\n ) : null}\n <List\n disablePadding\n sx={(theme) => ({ mt: 0.5, ...(grid ? grid(theme) : null) })}\n >\n {group.items.map((item) => (\n <SettingsRailItem\n key={item.id}\n item={item}\n active={item.id === activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n onNavigate={onNavigate}\n testIdPrefix={testIdPrefix}\n {...shape}\n />\n ))}\n </List>\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-LY5I3ZSV.js";
8
8
  import {
9
9
  SettingsRail
10
- } from "./chunk-SWQXAUUG.js";
10
+ } from "./chunk-TJO4CBYT.js";
11
11
  import {
12
12
  atLeastRail
13
13
  } from "./chunk-NK4T624E.js";
@@ -35,7 +35,7 @@ function railLookOf({
35
35
  }) {
36
36
  return { searchable, railVariant };
37
37
  }
38
- function shellSx(breakpoint, fillHeight) {
38
+ function shellSx(breakpoint, fillHeight, railVariant) {
39
39
  return (theme) => ({
40
40
  display: "flex",
41
41
  flexDirection: "column",
@@ -48,7 +48,9 @@ function shellSx(breakpoint, fillHeight) {
48
48
  // convenience.
49
49
  [atLeastRail(theme, breakpoint ?? "md")]: {
50
50
  flexDirection: "row",
51
- gap: 3,
51
+ // A card stands apart from the panel by 32px, as its prototype draws it;
52
+ // the ruled column keeps its 24.
53
+ gap: railVariant === "card" ? 4 : 3,
52
54
  ...fillHeight ? { height: "100%", minHeight: 0 } : {}
53
55
  }
54
56
  });
@@ -86,7 +88,7 @@ var SettingsLayout = (props) => {
86
88
  "data-testid": testIdPrefix,
87
89
  "data-nav-variant": navVariant,
88
90
  "data-at-index": atIndex ? "true" : "false",
89
- sx: shellSx(railBreakpoint, fillHeight),
91
+ sx: shellSx(railBreakpoint, fillHeight, railLookOf(props).railVariant),
90
92
  children: [
91
93
  /* @__PURE__ */ jsx(
92
94
  SettingsRail,
@@ -142,4 +144,4 @@ SettingsLayout.displayName = "SettingsLayout";
142
144
  export {
143
145
  SettingsLayout
144
146
  };
145
- //# sourceMappingURL=chunk-6KM3TL4Q.js.map
147
+ //# sourceMappingURL=chunk-SO5OU2ZV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/layout/SettingsLayout/SettingsLayout.tsx"],"sourcesContent":["'use client';\n\nimport Box from '@mui/material/Box/index.js';\nimport type { SxProps, Theme } from '@mui/material/styles/index.js';\nimport React, { useMemo, useState } from 'react';\n\nimport { withDefaults } from '../../../utils/withDefaults';\nimport { activeItemLabel, filterGroups } from './SettingsLayout.filters';\nimport { SettingsRail } from './SettingsLayout.rail';\nimport { atLeastRail } from './SettingsLayout.styles';\nimport { SettingsPanel } from './SettingsPanel';\nimport type { SettingsLayoutProps } from './SettingsLayout.types';\n\n/**\n * The props with every default already applied.\n *\n * A table through `withDefaults` rather than a `= default` per destructured\n * prop: each of those is a branch, and seven of them put the component over the\n * complexity bar before it renders anything. The single cast is what buys the\n * body non-optional values — without it every use site would restate the\n * default as a `??`, which is the same default written twice and free to drift.\n */\ntype ResolvedProps = SettingsLayoutProps &\n Required<\n Pick<\n SettingsLayoutProps,\n | 'searchPlaceholder'\n | 'defaultQuery'\n | 'emptySearchLabel'\n | 'testIdPrefix'\n | 'railBreakpoint'\n | 'navVariant'\n | 'atIndex'\n | 'backLabel'\n >\n >;\n\nconst DEFAULTS = {\n searchPlaceholder: 'Search settings',\n defaultQuery: '',\n emptySearchLabel: 'No settings match your search.',\n testIdPrefix: 'settings',\n railBreakpoint: 'md',\n navVariant: 'switcher',\n atIndex: false,\n backLabel: 'Back',\n} satisfies Partial<SettingsLayoutProps>;\n\n/**\n * How the rail looks: its search field, and its frame from the breakpoint up.\n * Both default to the shape every consumer had before they existed.\n */\nfunction railLookOf({\n searchable = true,\n railVariant = 'divided',\n}: SettingsLayoutProps): Required<Pick<SettingsLayoutProps, 'searchable' | 'railVariant'>> {\n return { searchable, railVariant };\n}\n\n/** Stacked below the rail's breakpoint, two columns at and above it. */\nfunction shellSx(\n breakpoint: SettingsLayoutProps['railBreakpoint'],\n fillHeight: boolean,\n railVariant: SettingsLayoutProps['railVariant'],\n): SxProps<Theme> {\n return (theme: Theme) => ({\n display: 'flex',\n flexDirection: 'column' as const,\n alignItems: 'stretch',\n gap: 1.5,\n width: '100%',\n minWidth: 0,\n // Only ever at the wide shape. Narrow, the rail is a disclosure or the page\n // itself, and two independent scrollbars on a phone is a trap rather than a\n // convenience.\n [atLeastRail(theme, breakpoint ?? 'md')]: {\n flexDirection: 'row',\n // A card stands apart from the panel by 32px, as its prototype draws it;\n // the ruled column keeps its 24.\n gap: railVariant === 'card' ? 4 : 3,\n ...(fillHeight ? { height: '100%', minHeight: 0 } : {}),\n },\n });\n}\n\n/**\n * A reusable, agnostic Facebook-style settings shell: a searchable left rail of\n * grouped categories/subcategories and a central panel that renders the selected\n * configuration screen (`children`). Navigation is data-driven via `groups`; the\n * host owns routing (pass `linkComponent` + item `href`, or handle `onSelectItem`).\n *\n * ## Two narrow-width shapes\n *\n * `navVariant=\"switcher\"` (the default) folds the rail into a disclosure above\n * the panel. `navVariant=\"drilldown\"` gives the phone shape instead: at the\n * area's index the LIST is the page, and inside a section the panel is the page\n * with a back link and a scrollable strip of sibling sections above it.\n *\n * ## Why the width decision is CSS and not JavaScript\n *\n * Every part that changes between the two shapes does it through a `display`\n * pair keyed on `railBreakpoint`. Nothing here asks a media query in JS and\n * renders one branch.\n *\n * That is not a performance preference. A layout that RENDERS one navigation and\n * not the other can offer the narrow width less than the wide one, and no test\n * or review catches it — the missing control simply is not in the tree to be\n * asserted about. With both forms always mounted, \"the narrow width reaches\n * everything the wide one does\" holds by construction, and an audit that counts\n * controls per width gets the same answer at both.\n */\nexport const SettingsLayout: React.FC<SettingsLayoutProps> = (props) => {\n const {\n title,\n groups,\n activeItemId,\n onSelectItem,\n searchPlaceholder,\n defaultQuery,\n emptySearchLabel,\n emptySearchAction,\n linkComponent,\n children,\n testIdPrefix,\n railBreakpoint,\n fillHeight = false,\n navVariant,\n atIndex,\n indexHref,\n backLabel,\n sectionChips,\n } = withDefaults(props, DEFAULTS) as ResolvedProps;\n const [query, setQuery] = useState(defaultQuery);\n const [navOpen, setNavOpen] = useState(false);\n const filteredGroups = useMemo(() => filterGroups(groups, query), [groups, query]);\n const activeLabel = useMemo(() => activeItemLabel(groups, activeItemId), [groups, activeItemId]);\n\n const drilldown = navVariant === 'drilldown';\n const ariaLabel = typeof title === 'string' ? title : 'Settings';\n\n return (\n <Box\n data-testid={testIdPrefix}\n data-nav-variant={navVariant}\n data-at-index={atIndex ? 'true' : 'false'}\n sx={shellSx(railBreakpoint, fillHeight, railLookOf(props).railVariant)}\n >\n <SettingsRail\n title={title}\n activeLabel={activeLabel}\n navOpen={navOpen}\n onToggleNav={() => setNavOpen((open) => !open)}\n query={query}\n onQueryChange={setQuery}\n searchPlaceholder={searchPlaceholder}\n emptySearchLabel={emptySearchLabel}\n emptySearchAction={emptySearchAction}\n filteredGroups={filteredGroups}\n activeItemId={activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n onNavigate={() => setNavOpen(false)}\n testIdPrefix={testIdPrefix}\n variant={navVariant}\n {...railLookOf(props)}\n breakpoint={railBreakpoint}\n atIndex={atIndex}\n fillHeight={fillHeight}\n />\n\n <SettingsPanel\n inSection={drilldown && !atIndex}\n hideOnNarrow={drilldown && atIndex}\n breakpoint={railBreakpoint}\n indexHref={indexHref}\n sectionTitle={props.sectionTitle}\n sectionDescription={props.sectionDescription}\n backLabel={backLabel}\n ariaLabel={ariaLabel}\n sectionChips={sectionChips}\n activeItemId={activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n testIdPrefix={testIdPrefix}\n fillHeight={fillHeight}\n >\n {children}\n </SettingsPanel>\n </Box>\n );\n};\n\nSettingsLayout.displayName = 'SettingsLayout';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAEA,OAAO,SAAS;AAEhB,SAAgB,SAAS,gBAAgB;AAyIrC,SAME,KANF;AAxGJ,IAAM,WAAW;AAAA,EACf,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,WAAW;AACb;AAMA,SAAS,WAAW;AAAA,EAClB,aAAa;AAAA,EACb,cAAc;AAChB,GAA2F;AACzF,SAAO,EAAE,YAAY,YAAY;AACnC;AAGA,SAAS,QACP,YACA,YACA,aACgB;AAChB,SAAO,CAAC,WAAkB;AAAA,IACxB,SAAS;AAAA,IACT,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,KAAK;AAAA,IACL,OAAO;AAAA,IACP,UAAU;AAAA;AAAA;AAAA;AAAA,IAIV,CAAC,YAAY,OAAO,cAAc,IAAI,CAAC,GAAG;AAAA,MACxC,eAAe;AAAA;AAAA;AAAA,MAGf,KAAK,gBAAgB,SAAS,IAAI;AAAA,MAClC,GAAI,aAAa,EAAE,QAAQ,QAAQ,WAAW,EAAE,IAAI,CAAC;AAAA,IACvD;AAAA,EACF;AACF;AA4BO,IAAM,iBAAgD,CAAC,UAAU;AACtE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,aAAa,OAAO,QAAQ;AAChC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,YAAY;AAC/C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,iBAAiB,QAAQ,MAAM,aAAa,QAAQ,KAAK,GAAG,CAAC,QAAQ,KAAK,CAAC;AACjF,QAAM,cAAc,QAAQ,MAAM,gBAAgB,QAAQ,YAAY,GAAG,CAAC,QAAQ,YAAY,CAAC;AAE/F,QAAM,YAAY,eAAe;AACjC,QAAM,YAAY,OAAO,UAAU,WAAW,QAAQ;AAEtD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAa;AAAA,MACb,oBAAkB;AAAA,MAClB,iBAAe,UAAU,SAAS;AAAA,MAClC,IAAI,QAAQ,gBAAgB,YAAY,WAAW,KAAK,EAAE,WAAW;AAAA,MAErE;AAAA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA,aAAa,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI;AAAA,YAC7C;AAAA,YACA,eAAe;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,YAAY,MAAM,WAAW,KAAK;AAAA,YAClC;AAAA,YACA,SAAS;AAAA,YACR,GAAG,WAAW,KAAK;AAAA,YACpB,YAAY;AAAA,YACZ;AAAA,YACA;AAAA;AAAA,QACF;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW,aAAa,CAAC;AAAA,YACzB,cAAc,aAAa;AAAA,YAC3B,YAAY;AAAA,YACZ;AAAA,YACA,cAAc,MAAM;AAAA,YACpB,oBAAoB,MAAM;AAAA,YAC1B;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YAEC;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,eAAe,cAAc;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SettingsRailGroup
3
- } from "./chunk-4BLVVMY7.js";
3
+ } from "./chunk-SL56MOKE.js";
4
4
  import {
5
5
  TOUCH_TARGET,
6
6
  atLeastRail,
@@ -258,7 +258,15 @@ function SettingsRail({
258
258
  testIdPrefix
259
259
  }
260
260
  ) : null,
261
- /* @__PURE__ */ jsx(SettingsRailBody, { navOpen, testIdPrefix, ...bodyProps })
261
+ /* @__PURE__ */ jsx(
262
+ SettingsRailBody,
263
+ {
264
+ navOpen,
265
+ testIdPrefix,
266
+ card: railVariant === "card",
267
+ ...bodyProps
268
+ }
269
+ )
262
270
  ]
263
271
  }
264
272
  );
@@ -267,4 +275,4 @@ function SettingsRail({
267
275
  export {
268
276
  SettingsRail
269
277
  };
270
- //# sourceMappingURL=chunk-SWQXAUUG.js.map
278
+ //# sourceMappingURL=chunk-TJO4CBYT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/layout/SettingsLayout/SettingsLayout.rail.tsx"],"sourcesContent":["'use client';\n\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport SearchIcon from '@mui/icons-material/Search';\nimport Box from '@mui/material/Box/index.js';\nimport Button from '@mui/material/Button/index.js';\nimport InputAdornment from '@mui/material/InputAdornment/index.js';\nimport TextField from '@mui/material/TextField/index.js';\nimport type { Theme } from '@mui/material/styles/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport React from 'react';\n\nimport { SettingsRailGroup, type RailShape } from './SettingsLayout.items';\nimport { atLeastRail, displayAcrossRail, railCardWidth, railWidth, TOUCH_TARGET } from './SettingsLayout.styles';\nimport { rem, sxRem } from '../../../tokens/relative';\nimport type {\n SettingsEmptySearchAction,\n SettingsLayoutProps,\n SettingsNavGroup,\n SettingsRailBreakpoint,\n SettingsRailVariant,\n} from './SettingsLayout.types';\n\ninterface RailToggleProps {\n title?: React.ReactNode;\n activeLabel?: string;\n open: boolean;\n onToggle: () => void;\n breakpoint: SettingsRailBreakpoint;\n testIdPrefix: string;\n}\n\n/**\n * `switcher`-only collapse control: shows the current section (a compact\n * switcher) so the panel content stays at the top of the screen. Hidden once the\n * rail has its own column.\n */\nfunction SettingsRailToggle({\n title,\n activeLabel,\n open,\n onToggle,\n breakpoint,\n testIdPrefix,\n}: RailToggleProps): React.JSX.Element {\n return (\n <Box\n component=\"button\"\n type=\"button\"\n onClick={onToggle}\n aria-expanded={open}\n aria-label={typeof title === 'string' ? title : 'Settings'}\n data-testid={`${testIdPrefix}-toggle`}\n sx={(theme) => ({\n ...displayAcrossRail(theme, breakpoint, 'flex', 'none'),\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 1,\n width: '100%',\n minHeight: rem(theme, TOUCH_TARGET),\n px: 1.5,\n py: 1,\n mb: 1,\n textAlign: 'left',\n cursor: 'pointer',\n color: 'text.primary',\n background: 'transparent',\n border: `1px solid ${theme.palette.divider}`,\n borderRadius: 1.5,\n })}\n >\n <Box sx={{ minWidth: 0 }}>\n {title ? (\n <Typography variant=\"caption\" sx={{ display: 'block', color: 'text.secondary' }}>\n {title}\n </Typography>\n ) : null}\n <Typography variant=\"body2\" noWrap sx={{ fontWeight: 600 }}>\n {activeLabel ?? ''}\n </Typography>\n </Box>\n <ExpandMoreIcon\n fontSize=\"small\"\n sx={{\n flex: '0 0 auto',\n color: 'text.secondary',\n transition: 'transform 150ms ease',\n transform: open ? 'rotate(180deg)' : 'none',\n '@media (prefers-reduced-motion: reduce)': { transition: 'none' },\n }}\n />\n </Box>\n );\n}\n\n/** The empty-search state, with the way out of it rendered inside. */\nfunction SettingsSearchEmpty({\n label,\n action,\n onClear,\n testIdPrefix,\n}: {\n label: React.ReactNode;\n action?: SettingsEmptySearchAction;\n onClear: () => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n return (\n <Box data-testid={`${testIdPrefix}-empty`} sx={{ px: 1, py: 2 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {label}\n </Typography>\n {action ? (\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n onClear();\n action.onClear?.();\n }}\n data-testid={`${testIdPrefix}-empty-action`}\n sx={{ mt: 1.5, minHeight: sxRem(TOUCH_TARGET) }}\n >\n {action.label}\n </Button>\n ) : null}\n </Box>\n );\n}\n\nexport interface RailBodyProps extends RailShape {\n navOpen: boolean;\n query: string;\n onQueryChange: (value: string) => void;\n searchPlaceholder: string;\n emptySearchLabel: React.ReactNode;\n emptySearchAction?: SettingsEmptySearchAction;\n filteredGroups: SettingsNavGroup[];\n activeItemId?: string;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n onNavigate?: () => void;\n testIdPrefix: string;\n /** Whether the search field is drawn (`SettingsLayoutProps.searchable`). */\n searchable: boolean;\n}\n\n/** The rail's search field. */\nfunction RailSearch({\n query,\n onQueryChange,\n searchPlaceholder,\n testIdPrefix,\n}: Pick<RailBodyProps, 'query' | 'onQueryChange' | 'searchPlaceholder' | 'testIdPrefix'>): React.JSX.Element {\n return (\n <TextField\n value={query}\n onChange={(event) => onQueryChange(event.target.value)}\n placeholder={searchPlaceholder}\n size=\"small\"\n fullWidth\n aria-label={searchPlaceholder}\n data-testid={`${testIdPrefix}-search`}\n InputProps={{\n startAdornment: (\n <InputAdornment position=\"start\">\n <SearchIcon fontSize=\"small\" sx={{ color: 'text.secondary' }} />\n </InputAdornment>\n ),\n }}\n sx={{ mb: 2 }}\n />\n );\n}\n\n/** The rail body: the search field when the area has one, then the groups. */\nfunction SettingsRailBody({\n navOpen,\n query,\n onQueryChange,\n searchPlaceholder,\n emptySearchLabel,\n emptySearchAction,\n filteredGroups,\n activeItemId,\n linkComponent,\n onSelectItem,\n onNavigate,\n testIdPrefix,\n searchable,\n ...shape\n}: RailBodyProps): React.JSX.Element {\n // `drilldown` has no disclosure to be closed behind: below the breakpoint the\n // body is the page, above it the rail. Only `switcher` hides it.\n const hidden = navOpen ? 'block' : 'none';\n return (\n <Box\n data-testid={`${testIdPrefix}-rail-body`}\n sx={(theme) =>\n shape.variant === 'drilldown'\n ? { display: 'block' }\n : displayAcrossRail(theme, shape.breakpoint, hidden, 'block')\n }\n >\n {searchable ? (\n <RailSearch\n query={query}\n onQueryChange={onQueryChange}\n searchPlaceholder={searchPlaceholder}\n testIdPrefix={testIdPrefix}\n />\n ) : null}\n {filteredGroups.length === 0 ? (\n <SettingsSearchEmpty\n label={emptySearchLabel}\n action={emptySearchAction}\n onClear={() => onQueryChange('')}\n testIdPrefix={testIdPrefix}\n />\n ) : (\n filteredGroups.map((group) => (\n <SettingsRailGroup\n key={group.id}\n group={group}\n activeItemId={activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n onNavigate={onNavigate}\n testIdPrefix={testIdPrefix}\n {...shape}\n />\n ))\n )}\n </Box>\n );\n}\n\n/** Props for {@link SettingsRail}: the rail body's inputs plus the mobile chrome. */\nexport type SettingsRailProps = RailBodyProps & {\n title?: React.ReactNode;\n activeLabel?: string;\n onToggleNav: () => void;\n /** Scroll inside the rail rather than with the page. See `fillHeight`. */\n fillHeight?: boolean;\n /** How the rail's own column is drawn. See `SettingsLayoutProps.railVariant`. */\n railVariant?: SettingsRailVariant;\n};\n\n/** The rail's own column, as the variant draws it: ruled off, or a card. */\nfunction railColumnSx(theme: Theme, railVariant: SettingsRailVariant): Record<string, unknown> {\n if (railVariant === 'card') {\n const width = railCardWidth(theme);\n return {\n flex: `0 0 ${width}`,\n width,\n alignSelf: 'flex-start',\n border: `1px solid ${theme.palette.divider}`,\n borderRadius: sxRem(14),\n bgcolor: 'background.paper',\n px: sxRem(12),\n py: sxRem(16),\n };\n }\n const width = railWidth(theme);\n return { flex: `0 0 ${width}`, width, borderRight: `1px solid ${theme.palette.divider}`, pr: sxRem(16) };\n}\n\n/** The navigation column: title, the `switcher`'s disclosure, and the body. */\nexport function SettingsRail({\n title,\n activeLabel,\n navOpen,\n onToggleNav,\n testIdPrefix,\n fillHeight = false,\n railVariant = 'divided',\n ...bodyProps\n}: SettingsRailProps): React.JSX.Element {\n const { variant, breakpoint, atIndex } = bodyProps;\n // In `drilldown` the rail is the index's list below the breakpoint and the\n // left column above it — so inside a section it steps aside for the chip\n // strip. It is `display: none`, never unmounted: the two navigation forms\n // both stay in the DOM at every width, which is what makes \"the narrow width\n // offers what the wide one offers\" true by construction instead of by review.\n const hiddenWhileInSection = variant === 'drilldown' && !atIndex;\n\n return (\n <Box\n component=\"nav\"\n aria-label={typeof title === 'string' ? title : 'Settings'}\n data-testid={`${testIdPrefix}-rail`}\n data-rail-variant={railVariant}\n sx={(theme) => ({\n display: hiddenWhileInSection ? 'none' : 'block',\n flex: '0 0 auto',\n width: '100%',\n [atLeastRail(theme, breakpoint)]: {\n display: 'block',\n ...railColumnSx(theme, railVariant),\n // Its own scrollbar, so a settings area with twenty categories does\n // not make the thing you NAVIGATE with scroll away while you read the\n // panel. `minHeight: 0` is the load-bearing half: without it a flex\n // child refuses to shrink below its content and `overflow` never\n // engages.\n ...(fillHeight\n ? { height: '100%', minHeight: 0, overflowY: 'auto' as const, overscrollBehavior: 'contain' as const }\n : {}),\n },\n })}\n >\n {title ? (\n <Typography\n variant=\"h6\"\n data-testid={`${testIdPrefix}-title`}\n sx={(theme) => ({\n ...(variant === 'drilldown'\n ? { display: 'block' }\n : displayAcrossRail(theme, breakpoint, 'none', 'block')),\n fontWeight: 700,\n mb: 1.5,\n })}\n >\n {title}\n </Typography>\n ) : null}\n {variant === 'switcher' ? (\n <SettingsRailToggle\n title={title}\n activeLabel={activeLabel}\n open={navOpen}\n onToggle={onToggleNav}\n breakpoint={breakpoint}\n testIdPrefix={testIdPrefix}\n />\n ) : null}\n <SettingsRailBody navOpen={navOpen} testIdPrefix={testIdPrefix} {...bodyProps} />\n </Box>\n );\n}\n"],"mappings":"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","names":["width"]}
1
+ {"version":3,"sources":["../src/components/layout/SettingsLayout/SettingsLayout.rail.tsx"],"sourcesContent":["'use client';\n\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport SearchIcon from '@mui/icons-material/Search';\nimport Box from '@mui/material/Box/index.js';\nimport Button from '@mui/material/Button/index.js';\nimport InputAdornment from '@mui/material/InputAdornment/index.js';\nimport TextField from '@mui/material/TextField/index.js';\nimport type { Theme } from '@mui/material/styles/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport React from 'react';\n\nimport { SettingsRailGroup, type RailShape } from './SettingsLayout.items';\nimport { atLeastRail, displayAcrossRail, railCardWidth, railWidth, TOUCH_TARGET } from './SettingsLayout.styles';\nimport { rem, sxRem } from '../../../tokens/relative';\nimport type {\n SettingsEmptySearchAction,\n SettingsLayoutProps,\n SettingsNavGroup,\n SettingsRailBreakpoint,\n SettingsRailVariant,\n} from './SettingsLayout.types';\n\ninterface RailToggleProps {\n title?: React.ReactNode;\n activeLabel?: string;\n open: boolean;\n onToggle: () => void;\n breakpoint: SettingsRailBreakpoint;\n testIdPrefix: string;\n}\n\n/**\n * `switcher`-only collapse control: shows the current section (a compact\n * switcher) so the panel content stays at the top of the screen. Hidden once the\n * rail has its own column.\n */\nfunction SettingsRailToggle({\n title,\n activeLabel,\n open,\n onToggle,\n breakpoint,\n testIdPrefix,\n}: RailToggleProps): React.JSX.Element {\n return (\n <Box\n component=\"button\"\n type=\"button\"\n onClick={onToggle}\n aria-expanded={open}\n aria-label={typeof title === 'string' ? title : 'Settings'}\n data-testid={`${testIdPrefix}-toggle`}\n sx={(theme) => ({\n ...displayAcrossRail(theme, breakpoint, 'flex', 'none'),\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 1,\n width: '100%',\n minHeight: rem(theme, TOUCH_TARGET),\n px: 1.5,\n py: 1,\n mb: 1,\n textAlign: 'left',\n cursor: 'pointer',\n color: 'text.primary',\n background: 'transparent',\n border: `1px solid ${theme.palette.divider}`,\n borderRadius: 1.5,\n })}\n >\n <Box sx={{ minWidth: 0 }}>\n {title ? (\n <Typography variant=\"caption\" sx={{ display: 'block', color: 'text.secondary' }}>\n {title}\n </Typography>\n ) : null}\n <Typography variant=\"body2\" noWrap sx={{ fontWeight: 600 }}>\n {activeLabel ?? ''}\n </Typography>\n </Box>\n <ExpandMoreIcon\n fontSize=\"small\"\n sx={{\n flex: '0 0 auto',\n color: 'text.secondary',\n transition: 'transform 150ms ease',\n transform: open ? 'rotate(180deg)' : 'none',\n '@media (prefers-reduced-motion: reduce)': { transition: 'none' },\n }}\n />\n </Box>\n );\n}\n\n/** The empty-search state, with the way out of it rendered inside. */\nfunction SettingsSearchEmpty({\n label,\n action,\n onClear,\n testIdPrefix,\n}: {\n label: React.ReactNode;\n action?: SettingsEmptySearchAction;\n onClear: () => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n return (\n <Box data-testid={`${testIdPrefix}-empty`} sx={{ px: 1, py: 2 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {label}\n </Typography>\n {action ? (\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n onClear();\n action.onClear?.();\n }}\n data-testid={`${testIdPrefix}-empty-action`}\n sx={{ mt: 1.5, minHeight: sxRem(TOUCH_TARGET) }}\n >\n {action.label}\n </Button>\n ) : null}\n </Box>\n );\n}\n\nexport interface RailBodyProps extends RailShape {\n navOpen: boolean;\n query: string;\n onQueryChange: (value: string) => void;\n searchPlaceholder: string;\n emptySearchLabel: React.ReactNode;\n emptySearchAction?: SettingsEmptySearchAction;\n filteredGroups: SettingsNavGroup[];\n activeItemId?: string;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n onNavigate?: () => void;\n testIdPrefix: string;\n /** Whether the search field is drawn (`SettingsLayoutProps.searchable`). */\n searchable: boolean;\n}\n\n/** The rail's search field. */\nfunction RailSearch({\n query,\n onQueryChange,\n searchPlaceholder,\n testIdPrefix,\n}: Pick<RailBodyProps, 'query' | 'onQueryChange' | 'searchPlaceholder' | 'testIdPrefix'>): React.JSX.Element {\n return (\n <TextField\n value={query}\n onChange={(event) => onQueryChange(event.target.value)}\n placeholder={searchPlaceholder}\n size=\"small\"\n fullWidth\n aria-label={searchPlaceholder}\n data-testid={`${testIdPrefix}-search`}\n InputProps={{\n startAdornment: (\n <InputAdornment position=\"start\">\n <SearchIcon fontSize=\"small\" sx={{ color: 'text.secondary' }} />\n </InputAdornment>\n ),\n }}\n sx={{ mb: 2 }}\n />\n );\n}\n\n/** The rail body: the search field when the area has one, then the groups. */\nfunction SettingsRailBody({\n navOpen,\n query,\n onQueryChange,\n searchPlaceholder,\n emptySearchLabel,\n emptySearchAction,\n filteredGroups,\n activeItemId,\n linkComponent,\n onSelectItem,\n onNavigate,\n testIdPrefix,\n searchable,\n ...shape\n}: RailBodyProps): React.JSX.Element {\n // `drilldown` has no disclosure to be closed behind: below the breakpoint the\n // body is the page, above it the rail. Only `switcher` hides it.\n const hidden = navOpen ? 'block' : 'none';\n return (\n <Box\n data-testid={`${testIdPrefix}-rail-body`}\n sx={(theme) =>\n shape.variant === 'drilldown'\n ? { display: 'block' }\n : displayAcrossRail(theme, shape.breakpoint, hidden, 'block')\n }\n >\n {searchable ? (\n <RailSearch\n query={query}\n onQueryChange={onQueryChange}\n searchPlaceholder={searchPlaceholder}\n testIdPrefix={testIdPrefix}\n />\n ) : null}\n {filteredGroups.length === 0 ? (\n <SettingsSearchEmpty\n label={emptySearchLabel}\n action={emptySearchAction}\n onClear={() => onQueryChange('')}\n testIdPrefix={testIdPrefix}\n />\n ) : (\n filteredGroups.map((group) => (\n <SettingsRailGroup\n key={group.id}\n group={group}\n activeItemId={activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n onNavigate={onNavigate}\n testIdPrefix={testIdPrefix}\n {...shape}\n />\n ))\n )}\n </Box>\n );\n}\n\n/** Props for {@link SettingsRail}: the rail body's inputs plus the mobile chrome. */\nexport type SettingsRailProps = RailBodyProps & {\n title?: React.ReactNode;\n activeLabel?: string;\n onToggleNav: () => void;\n /** Scroll inside the rail rather than with the page. See `fillHeight`. */\n fillHeight?: boolean;\n /** How the rail's own column is drawn. See `SettingsLayoutProps.railVariant`. */\n railVariant?: SettingsRailVariant;\n};\n\n/** The rail's own column, as the variant draws it: ruled off, or a card. */\nfunction railColumnSx(theme: Theme, railVariant: SettingsRailVariant): Record<string, unknown> {\n if (railVariant === 'card') {\n const width = railCardWidth(theme);\n return {\n flex: `0 0 ${width}`,\n width,\n alignSelf: 'flex-start',\n border: `1px solid ${theme.palette.divider}`,\n borderRadius: sxRem(14),\n bgcolor: 'background.paper',\n px: sxRem(12),\n py: sxRem(16),\n };\n }\n const width = railWidth(theme);\n return { flex: `0 0 ${width}`, width, borderRight: `1px solid ${theme.palette.divider}`, pr: sxRem(16) };\n}\n\n/** The navigation column: title, the `switcher`'s disclosure, and the body. */\nexport function SettingsRail({\n title,\n activeLabel,\n navOpen,\n onToggleNav,\n testIdPrefix,\n fillHeight = false,\n railVariant = 'divided',\n ...bodyProps\n}: SettingsRailProps): React.JSX.Element {\n const { variant, breakpoint, atIndex } = bodyProps;\n // In `drilldown` the rail is the index's list below the breakpoint and the\n // left column above it — so inside a section it steps aside for the chip\n // strip. It is `display: none`, never unmounted: the two navigation forms\n // both stay in the DOM at every width, which is what makes \"the narrow width\n // offers what the wide one offers\" true by construction instead of by review.\n const hiddenWhileInSection = variant === 'drilldown' && !atIndex;\n\n return (\n <Box\n component=\"nav\"\n aria-label={typeof title === 'string' ? title : 'Settings'}\n data-testid={`${testIdPrefix}-rail`}\n data-rail-variant={railVariant}\n sx={(theme) => ({\n display: hiddenWhileInSection ? 'none' : 'block',\n flex: '0 0 auto',\n width: '100%',\n [atLeastRail(theme, breakpoint)]: {\n display: 'block',\n ...railColumnSx(theme, railVariant),\n // Its own scrollbar, so a settings area with twenty categories does\n // not make the thing you NAVIGATE with scroll away while you read the\n // panel. `minHeight: 0` is the load-bearing half: without it a flex\n // child refuses to shrink below its content and `overflow` never\n // engages.\n ...(fillHeight\n ? { height: '100%', minHeight: 0, overflowY: 'auto' as const, overscrollBehavior: 'contain' as const }\n : {}),\n },\n })}\n >\n {title ? (\n <Typography\n variant=\"h6\"\n data-testid={`${testIdPrefix}-title`}\n sx={(theme) => ({\n ...(variant === 'drilldown'\n ? { display: 'block' }\n : displayAcrossRail(theme, breakpoint, 'none', 'block')),\n fontWeight: 700,\n mb: 1.5,\n })}\n >\n {title}\n </Typography>\n ) : null}\n {variant === 'switcher' ? (\n <SettingsRailToggle\n title={title}\n activeLabel={activeLabel}\n open={navOpen}\n onToggle={onToggleNav}\n breakpoint={breakpoint}\n testIdPrefix={testIdPrefix}\n />\n ) : null}\n <SettingsRailBody\n navOpen={navOpen}\n testIdPrefix={testIdPrefix}\n card={railVariant === 'card'}\n {...bodyProps}\n />\n </Box>\n );\n}\n"],"mappings":"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","names":["width"]}
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  SettingsLayout
3
- } from "../chunk-6KM3TL4Q.js";
3
+ } from "../chunk-SO5OU2ZV.js";
4
4
  import "../chunk-BXC6GRLE.js";
5
5
  import "../chunk-LY5I3ZSV.js";
6
6
  import {
7
7
  SettingsSectionChips
8
8
  } from "../chunk-GBVNY2LG.js";
9
9
  import "../chunk-AJUSBPWG.js";
10
- import "../chunk-SWQXAUUG.js";
11
- import "../chunk-4BLVVMY7.js";
10
+ import "../chunk-TJO4CBYT.js";
11
+ import "../chunk-SL56MOKE.js";
12
12
  import {
13
13
  SettingsStatusMarker
14
14
  } from "../chunk-7Y2VWMGS.js";
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsLayout.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.tsx"],"names":[],"mappings":"AAIA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAOjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAoElE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA+ExD,CAAC"}
1
+ {"version":3,"file":"SettingsLayout.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.tsx"],"names":[],"mappings":"AAIA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAOjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AA0ElE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA+ExD,CAAC"}
@@ -5,6 +5,8 @@ export interface RailShape {
5
5
  variant: SettingsNavVariant;
6
6
  breakpoint: SettingsRailBreakpoint;
7
7
  atIndex: boolean;
8
+ /** The rail is drawn as a card (`railVariant="card"`): denser rows, darker text. */
9
+ card?: boolean;
8
10
  }
9
11
  interface RailItemProps extends RailShape {
10
12
  item: SettingsNavItem;
@@ -16,7 +18,7 @@ interface RailItemProps extends RailShape {
16
18
  testIdPrefix: string;
17
19
  }
18
20
  /** One rail entry — a link when it has `href`, inert text when it says so, else a button. */
19
- export declare function SettingsRailItem({ item, active, linkComponent, onSelectItem, onNavigate, testIdPrefix, variant, breakpoint, }: RailItemProps): React.JSX.Element;
21
+ export declare function SettingsRailItem({ item, active, linkComponent, onSelectItem, onNavigate, testIdPrefix, variant, breakpoint, card, }: RailItemProps): React.JSX.Element;
20
22
  interface RailGroupProps extends RailShape {
21
23
  group: SettingsNavGroup;
22
24
  activeItemId?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsLayout.items.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.items.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,wBAAwB,CAAC;AAEhC,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,EAAE,sBAAsB,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,aAAc,SAAQ,SAAS;IACvC,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAgCD,6FAA6F;AAC7F,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,MAAM,EACN,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,OAAO,EACP,UAAU,GACX,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAkEnC;AAED,UAAU,cAAe,SAAQ,SAAS;IACxC,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAsBD,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAyCpC"}
1
+ {"version":3,"file":"SettingsLayout.items.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.items.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,wBAAwB,CAAC;AAEhC,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,EAAE,sBAAsB,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,oFAAoF;IACpF,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,aAAc,SAAQ,SAAS;IACvC,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AA4DD,6FAA6F;AAC7F,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,MAAM,EACN,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,OAAO,EACP,UAAU,EACV,IAAY,GACb,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAoEnC;AAED,UAAU,cAAe,SAAQ,SAAS;IACxC,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAwBD,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAyCpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsLayout.rail.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.rail.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAG3E,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACnB,gBAAgB,EAEhB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AA6GhC,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,iBAAiB,CAAC,EAAE,yBAAyB,CAAC;IAC9C,cAAc,EAAE,gBAAgB,EAAE,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,UAAU,EAAE,OAAO,CAAC;CACrB;AA4FD,qFAAqF;AACrF,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG;IAC9C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iFAAiF;IACjF,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACnC,CAAC;AAqBF,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,WAAW,EACX,OAAO,EACP,WAAW,EACX,YAAY,EACZ,UAAkB,EAClB,WAAuB,EACvB,GAAG,SAAS,EACb,EAAE,iBAAiB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA6DvC"}
1
+ {"version":3,"file":"SettingsLayout.rail.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.rail.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAG3E,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACnB,gBAAgB,EAEhB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AA6GhC,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,iBAAiB,CAAC,EAAE,yBAAyB,CAAC;IAC9C,cAAc,EAAE,gBAAgB,EAAE,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,UAAU,EAAE,OAAO,CAAC;CACrB;AA4FD,qFAAqF;AACrF,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG;IAC9C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iFAAiF;IACjF,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACnC,CAAC;AAqBF,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,WAAW,EACX,OAAO,EACP,WAAW,EACX,YAAY,EACZ,UAAkB,EAClB,WAAuB,EACvB,GAAG,SAAS,EACb,EAAE,iBAAiB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAkEvC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.76.0",
3
+ "version": "6.76.1",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@10.34.5",
6
6
  "type": "module",
@@ -12,7 +12,7 @@ import type { SxProps, Theme } from '@mui/material/styles/index.js';
12
12
  import React from 'react';
13
13
 
14
14
  import { atLeastRail, TOUCH_TARGET } from './SettingsLayout.styles';
15
- import { rem } from '../../../tokens/relative';
15
+ import { rem, sxRem } from '../../../tokens/relative';
16
16
  import { SettingsStatusMarker } from './SettingsStatusMarker';
17
17
  import type {
18
18
  SettingsLayoutProps,
@@ -27,6 +27,8 @@ export interface RailShape {
27
27
  variant: SettingsNavVariant;
28
28
  breakpoint: SettingsRailBreakpoint;
29
29
  atIndex: boolean;
30
+ /** The rail is drawn as a card (`railVariant="card"`): denser rows, darker text. */
31
+ card?: boolean;
30
32
  }
31
33
 
32
34
  interface RailItemProps extends RailShape {
@@ -69,6 +71,34 @@ function RailItemContent({
69
71
  );
70
72
  }
71
73
 
74
+ /**
75
+ * The row's text colour. On the card the active row's red is the darker one,
76
+ * since `primary.main` on its 12% wash measured 4.19:1 — under AA at 16px —
77
+ * and the rest read in the body colour, as the card's prototype draws them.
78
+ */
79
+ function railItemColor(active: boolean, card: boolean): string {
80
+ if (active) return card ? 'primary.dark' : 'primary.main';
81
+ return card ? 'text.primary' : 'text.secondary';
82
+ }
83
+
84
+ /**
85
+ * Keyboard focus as a ring, not MUI's grey fill: the fill measured 1.3:1
86
+ * against the rail, under the 3:1 a focus indicator needs.
87
+ */
88
+ const FOCUS_RING_SX = {
89
+ '&.Mui-focusVisible': {
90
+ outlineStyle: 'solid',
91
+ outlineWidth: sxRem(2),
92
+ outlineColor: 'primary.main',
93
+ outlineOffset: sxRem(-2),
94
+ },
95
+ } as const;
96
+
97
+ /** The card's rows from the breakpoint up: 36px, the prototype's rail density. */
98
+ function cardRowSx(theme: Theme, breakpoint: SettingsRailBreakpoint): Record<string, unknown> {
99
+ return { [atLeastRail(theme, breakpoint)]: { minHeight: rem(theme, 36), py: sxRem(6) } };
100
+ }
101
+
72
102
  /** One rail entry — a link when it has `href`, inert text when it says so, else a button. */
73
103
  export function SettingsRailItem({
74
104
  item,
@@ -79,6 +109,7 @@ export function SettingsRailItem({
79
109
  testIdPrefix,
80
110
  variant,
81
111
  breakpoint,
112
+ card = false,
82
113
  }: RailItemProps): React.JSX.Element {
83
114
  const theme = useTheme();
84
115
  // In `drilldown` the narrow rail IS the page's list, so its rows are the
@@ -95,11 +126,13 @@ export function SettingsRailItem({
95
126
  mb: 0.25,
96
127
  gap: 1.5,
97
128
  ...touchFloor,
98
- color: active ? 'primary.main' : 'text.secondary',
129
+ ...(card ? cardRowSx(theme, breakpoint) : null),
130
+ color: railItemColor(active, card),
99
131
  '&.Mui-selected': {
100
132
  bgcolor: alpha(theme.palette.primary.main, 0.12),
101
133
  '&:hover': { bgcolor: alpha(theme.palette.primary.main, 0.18) },
102
134
  },
135
+ ...FOCUS_RING_SX,
103
136
  },
104
137
  };
105
138
  const content = <RailItemContent item={item} active={active} testIdPrefix={testIdPrefix} />;
@@ -171,7 +204,9 @@ function indexListSx(shape: RailShape): Extract<SxProps<Theme>, (theme: Theme) =
171
204
  // One spacing unit (8px). This is an `sx` callback, where a bare 8 is
172
205
  // EIGHT units — it drew a 64px gutter between the two columns.
173
206
  columnGap: 1,
174
- [theme.breakpoints.up('sm')]: { gridTemplateColumns: '1fr 1fr' },
207
+ // From `md`, not `sm`: at 600 two columns of bare text rows read as one
208
+ // list with a hole in it, and a row's status dot sat nearer the next column.
209
+ [theme.breakpoints.up('md')]: { gridTemplateColumns: '1fr 1fr' },
175
210
  [atLeastRail(theme, shape.breakpoint)]: { gridTemplateColumns: '1fr' },
176
211
  });
177
212
  }
@@ -333,7 +333,12 @@ export function SettingsRail({
333
333
  testIdPrefix={testIdPrefix}
334
334
  />
335
335
  ) : null}
336
- <SettingsRailBody navOpen={navOpen} testIdPrefix={testIdPrefix} {...bodyProps} />
336
+ <SettingsRailBody
337
+ navOpen={navOpen}
338
+ testIdPrefix={testIdPrefix}
339
+ card={railVariant === 'card'}
340
+ {...bodyProps}
341
+ />
337
342
  </Box>
338
343
  );
339
344
  }
@@ -58,7 +58,11 @@ function railLookOf({
58
58
  }
59
59
 
60
60
  /** Stacked below the rail's breakpoint, two columns at and above it. */
61
- function shellSx(breakpoint: SettingsLayoutProps['railBreakpoint'], fillHeight: boolean): SxProps<Theme> {
61
+ function shellSx(
62
+ breakpoint: SettingsLayoutProps['railBreakpoint'],
63
+ fillHeight: boolean,
64
+ railVariant: SettingsLayoutProps['railVariant'],
65
+ ): SxProps<Theme> {
62
66
  return (theme: Theme) => ({
63
67
  display: 'flex',
64
68
  flexDirection: 'column' as const,
@@ -71,7 +75,9 @@ function shellSx(breakpoint: SettingsLayoutProps['railBreakpoint'], fillHeight:
71
75
  // convenience.
72
76
  [atLeastRail(theme, breakpoint ?? 'md')]: {
73
77
  flexDirection: 'row',
74
- gap: 3,
78
+ // A card stands apart from the panel by 32px, as its prototype draws it;
79
+ // the ruled column keeps its 24.
80
+ gap: railVariant === 'card' ? 4 : 3,
75
81
  ...(fillHeight ? { height: '100%', minHeight: 0 } : {}),
76
82
  },
77
83
  });
@@ -137,7 +143,7 @@ export const SettingsLayout: React.FC<SettingsLayoutProps> = (props) => {
137
143
  data-testid={testIdPrefix}
138
144
  data-nav-variant={navVariant}
139
145
  data-at-index={atIndex ? 'true' : 'false'}
140
- sx={shellSx(railBreakpoint, fillHeight)}
146
+ sx={shellSx(railBreakpoint, fillHeight, railLookOf(props).railVariant)}
141
147
  >
142
148
  <SettingsRail
143
149
  title={title}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/layout/SettingsLayout/SettingsLayout.items.tsx"],"sourcesContent":["'use client';\n\nimport Box from '@mui/material/Box/index.js';\nimport List from '@mui/material/List/index.js';\nimport ListItem from '@mui/material/ListItem/index.js';\nimport ListItemButton from '@mui/material/ListItemButton/index.js';\nimport ListItemIcon from '@mui/material/ListItemIcon/index.js';\nimport ListItemText from '@mui/material/ListItemText/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport { alpha, useTheme } from '@mui/material/styles/index.js';\nimport type { SxProps, Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { atLeastRail, TOUCH_TARGET } from './SettingsLayout.styles';\nimport { rem } from '../../../tokens/relative';\nimport { SettingsStatusMarker } from './SettingsStatusMarker';\nimport type {\n SettingsLayoutProps,\n SettingsNavGroup,\n SettingsNavItem,\n SettingsNavVariant,\n SettingsRailBreakpoint,\n} from './SettingsLayout.types';\n\n/** The shape-deciding inputs every rail part needs to know about. */\nexport interface RailShape {\n variant: SettingsNavVariant;\n breakpoint: SettingsRailBreakpoint;\n atIndex: boolean;\n}\n\ninterface RailItemProps extends RailShape {\n item: SettingsNavItem;\n active: boolean;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n /** Called on activation so the `switcher`'s rail can collapse after a choice. */\n onNavigate?: () => void;\n testIdPrefix: string;\n}\n\n/** Label, plus the situation marker when the host resolved one. */\nfunction RailItemContent({\n item,\n active,\n testIdPrefix,\n}: {\n item: SettingsNavItem;\n active: boolean;\n testIdPrefix: string;\n}): React.JSX.Element {\n return (\n <>\n {item.icon ? (\n <ListItemIcon sx={{ minWidth: 0, color: 'inherit' }}>{item.icon}</ListItemIcon>\n ) : null}\n <ListItemText\n primary={item.label}\n primaryTypographyProps={{ variant: 'body2', noWrap: true, fontWeight: active ? 600 : 500 }}\n />\n {item.status ? (\n <SettingsStatusMarker\n status={item.status}\n label={item.statusLabel}\n testId={`${testIdPrefix}-status-${item.id}`}\n />\n ) : null}\n </>\n );\n}\n\n/** One rail entry — a link when it has `href`, inert text when it says so, else a button. */\nexport function SettingsRailItem({\n item,\n active,\n linkComponent,\n onSelectItem,\n onNavigate,\n testIdPrefix,\n variant,\n breakpoint,\n}: RailItemProps): React.JSX.Element {\n const theme = useTheme();\n // In `drilldown` the narrow rail IS the page's list, so its rows are the\n // primary touch targets rather than a secondary column — hence the 44px floor\n // below the breakpoint. Above it, the row goes back to rail density.\n const touchFloor =\n variant === 'drilldown'\n ? { minHeight: rem(theme, TOUCH_TARGET), [atLeastRail(theme, breakpoint)]: { minHeight: 'auto' } }\n : null;\n const shared = {\n 'data-testid': `${testIdPrefix}-item-${item.id}`,\n sx: {\n borderRadius: 1.5,\n mb: 0.25,\n gap: 1.5,\n ...touchFloor,\n color: active ? 'primary.main' : 'text.secondary',\n '&.Mui-selected': {\n bgcolor: alpha(theme.palette.primary.main, 0.12),\n '&:hover': { bgcolor: alpha(theme.palette.primary.main, 0.18) },\n },\n },\n };\n const content = <RailItemContent item={item} active={active} testIdPrefix={testIdPrefix} />;\n\n // Listed but not reachable from here. Deliberately not a disabled button: a\n // disabled control says \"you may not\", and the true statement is \"not from\n // this screen\" — the section exists and someone else's screen owns it.\n if (item.inert) {\n return (\n <ListItem {...shared} data-inert=\"true\">\n {content}\n </ListItem>\n );\n }\n\n const interactive = {\n ...shared,\n selected: active,\n 'aria-current': active ? ('page' as const) : undefined,\n };\n\n if (item.href && linkComponent) {\n const LinkComponent = linkComponent;\n return (\n <ListItemButton\n component={LinkComponent}\n href={item.href}\n onClick={onNavigate}\n {...interactive}\n >\n {content}\n </ListItemButton>\n );\n }\n return (\n <ListItemButton\n onClick={() => {\n onSelectItem?.(item.id);\n onNavigate?.();\n }}\n {...interactive}\n >\n {content}\n </ListItemButton>\n );\n}\n\ninterface RailGroupProps extends RailShape {\n group: SettingsNavGroup;\n activeItemId?: string;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n onNavigate?: () => void;\n testIdPrefix: string;\n}\n\n/**\n * The list's own grid, in `drilldown` at the index only.\n *\n * At the index on a wide phone or a tablet the list has room for two columns;\n * the rail column never does, so the second column is undone at exactly the\n * width where this stops being the page.\n */\nfunction indexListSx(shape: RailShape): Extract<SxProps<Theme>, (theme: Theme) => unknown> | null {\n if (shape.variant !== 'drilldown' || !shape.atIndex) return null;\n return (theme: import('@mui/material').Theme) => ({\n display: 'grid',\n gridTemplateColumns: '1fr',\n // One spacing unit (8px). This is an `sx` callback, where a bare 8 is\n // EIGHT units — it drew a 64px gutter between the two columns.\n columnGap: 1,\n [theme.breakpoints.up('sm')]: { gridTemplateColumns: '1fr 1fr' },\n [atLeastRail(theme, shape.breakpoint)]: { gridTemplateColumns: '1fr' },\n });\n}\n\n/** A category: header + optional description + its items. */\nexport function SettingsRailGroup({\n group,\n activeItemId,\n linkComponent,\n onSelectItem,\n onNavigate,\n testIdPrefix,\n ...shape\n}: RailGroupProps): React.JSX.Element {\n const grid = indexListSx(shape);\n return (\n <Box data-testid={`${testIdPrefix}-group-${group.id}`} sx={{ mb: 2 }}>\n <Typography\n variant=\"caption\"\n sx={{\n display: 'block',\n px: 1,\n color: 'text.secondary',\n fontWeight: 600,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n }}\n >\n {group.label}\n </Typography>\n {group.description ? (\n <Typography variant=\"caption\" component=\"p\" sx={{ px: 1, mt: 0.25, color: 'text.disabled' }}>\n {group.description}\n </Typography>\n ) : null}\n <List\n disablePadding\n sx={(theme) => ({ mt: 0.5, ...(grid ? grid(theme) : null) })}\n >\n {group.items.map((item) => (\n <SettingsRailItem\n key={item.id}\n item={item}\n active={item.id === activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n onNavigate={onNavigate}\n testIdPrefix={testIdPrefix}\n {...shape}\n />\n ))}\n </List>\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/layout/SettingsLayout/SettingsLayout.tsx"],"sourcesContent":["'use client';\n\nimport Box from '@mui/material/Box/index.js';\nimport type { SxProps, Theme } from '@mui/material/styles/index.js';\nimport React, { useMemo, useState } from 'react';\n\nimport { withDefaults } from '../../../utils/withDefaults';\nimport { activeItemLabel, filterGroups } from './SettingsLayout.filters';\nimport { SettingsRail } from './SettingsLayout.rail';\nimport { atLeastRail } from './SettingsLayout.styles';\nimport { SettingsPanel } from './SettingsPanel';\nimport type { SettingsLayoutProps } from './SettingsLayout.types';\n\n/**\n * The props with every default already applied.\n *\n * A table through `withDefaults` rather than a `= default` per destructured\n * prop: each of those is a branch, and seven of them put the component over the\n * complexity bar before it renders anything. The single cast is what buys the\n * body non-optional values — without it every use site would restate the\n * default as a `??`, which is the same default written twice and free to drift.\n */\ntype ResolvedProps = SettingsLayoutProps &\n Required<\n Pick<\n SettingsLayoutProps,\n | 'searchPlaceholder'\n | 'defaultQuery'\n | 'emptySearchLabel'\n | 'testIdPrefix'\n | 'railBreakpoint'\n | 'navVariant'\n | 'atIndex'\n | 'backLabel'\n >\n >;\n\nconst DEFAULTS = {\n searchPlaceholder: 'Search settings',\n defaultQuery: '',\n emptySearchLabel: 'No settings match your search.',\n testIdPrefix: 'settings',\n railBreakpoint: 'md',\n navVariant: 'switcher',\n atIndex: false,\n backLabel: 'Back',\n} satisfies Partial<SettingsLayoutProps>;\n\n/**\n * How the rail looks: its search field, and its frame from the breakpoint up.\n * Both default to the shape every consumer had before they existed.\n */\nfunction railLookOf({\n searchable = true,\n railVariant = 'divided',\n}: SettingsLayoutProps): Required<Pick<SettingsLayoutProps, 'searchable' | 'railVariant'>> {\n return { searchable, railVariant };\n}\n\n/** Stacked below the rail's breakpoint, two columns at and above it. */\nfunction shellSx(breakpoint: SettingsLayoutProps['railBreakpoint'], fillHeight: boolean): SxProps<Theme> {\n return (theme: Theme) => ({\n display: 'flex',\n flexDirection: 'column' as const,\n alignItems: 'stretch',\n gap: 1.5,\n width: '100%',\n minWidth: 0,\n // Only ever at the wide shape. Narrow, the rail is a disclosure or the page\n // itself, and two independent scrollbars on a phone is a trap rather than a\n // convenience.\n [atLeastRail(theme, breakpoint ?? 'md')]: {\n flexDirection: 'row',\n gap: 3,\n ...(fillHeight ? { height: '100%', minHeight: 0 } : {}),\n },\n });\n}\n\n/**\n * A reusable, agnostic Facebook-style settings shell: a searchable left rail of\n * grouped categories/subcategories and a central panel that renders the selected\n * configuration screen (`children`). Navigation is data-driven via `groups`; the\n * host owns routing (pass `linkComponent` + item `href`, or handle `onSelectItem`).\n *\n * ## Two narrow-width shapes\n *\n * `navVariant=\"switcher\"` (the default) folds the rail into a disclosure above\n * the panel. `navVariant=\"drilldown\"` gives the phone shape instead: at the\n * area's index the LIST is the page, and inside a section the panel is the page\n * with a back link and a scrollable strip of sibling sections above it.\n *\n * ## Why the width decision is CSS and not JavaScript\n *\n * Every part that changes between the two shapes does it through a `display`\n * pair keyed on `railBreakpoint`. Nothing here asks a media query in JS and\n * renders one branch.\n *\n * That is not a performance preference. A layout that RENDERS one navigation and\n * not the other can offer the narrow width less than the wide one, and no test\n * or review catches it — the missing control simply is not in the tree to be\n * asserted about. With both forms always mounted, \"the narrow width reaches\n * everything the wide one does\" holds by construction, and an audit that counts\n * controls per width gets the same answer at both.\n */\nexport const SettingsLayout: React.FC<SettingsLayoutProps> = (props) => {\n const {\n title,\n groups,\n activeItemId,\n onSelectItem,\n searchPlaceholder,\n defaultQuery,\n emptySearchLabel,\n emptySearchAction,\n linkComponent,\n children,\n testIdPrefix,\n railBreakpoint,\n fillHeight = false,\n navVariant,\n atIndex,\n indexHref,\n backLabel,\n sectionChips,\n } = withDefaults(props, DEFAULTS) as ResolvedProps;\n const [query, setQuery] = useState(defaultQuery);\n const [navOpen, setNavOpen] = useState(false);\n const filteredGroups = useMemo(() => filterGroups(groups, query), [groups, query]);\n const activeLabel = useMemo(() => activeItemLabel(groups, activeItemId), [groups, activeItemId]);\n\n const drilldown = navVariant === 'drilldown';\n const ariaLabel = typeof title === 'string' ? title : 'Settings';\n\n return (\n <Box\n data-testid={testIdPrefix}\n data-nav-variant={navVariant}\n data-at-index={atIndex ? 'true' : 'false'}\n sx={shellSx(railBreakpoint, fillHeight)}\n >\n <SettingsRail\n title={title}\n activeLabel={activeLabel}\n navOpen={navOpen}\n onToggleNav={() => setNavOpen((open) => !open)}\n query={query}\n onQueryChange={setQuery}\n searchPlaceholder={searchPlaceholder}\n emptySearchLabel={emptySearchLabel}\n emptySearchAction={emptySearchAction}\n filteredGroups={filteredGroups}\n activeItemId={activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n onNavigate={() => setNavOpen(false)}\n testIdPrefix={testIdPrefix}\n variant={navVariant}\n {...railLookOf(props)}\n breakpoint={railBreakpoint}\n atIndex={atIndex}\n fillHeight={fillHeight}\n />\n\n <SettingsPanel\n inSection={drilldown && !atIndex}\n hideOnNarrow={drilldown && atIndex}\n breakpoint={railBreakpoint}\n indexHref={indexHref}\n sectionTitle={props.sectionTitle}\n sectionDescription={props.sectionDescription}\n backLabel={backLabel}\n ariaLabel={ariaLabel}\n sectionChips={sectionChips}\n activeItemId={activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n testIdPrefix={testIdPrefix}\n fillHeight={fillHeight}\n >\n {children}\n </SettingsPanel>\n </Box>\n );\n};\n\nSettingsLayout.displayName = 'SettingsLayout';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAEA,OAAO,SAAS;AAEhB,SAAgB,SAAS,gBAAgB;AAmIrC,SAME,KANF;AAlGJ,IAAM,WAAW;AAAA,EACf,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,WAAW;AACb;AAMA,SAAS,WAAW;AAAA,EAClB,aAAa;AAAA,EACb,cAAc;AAChB,GAA2F;AACzF,SAAO,EAAE,YAAY,YAAY;AACnC;AAGA,SAAS,QAAQ,YAAmD,YAAqC;AACvG,SAAO,CAAC,WAAkB;AAAA,IACxB,SAAS;AAAA,IACT,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,KAAK;AAAA,IACL,OAAO;AAAA,IACP,UAAU;AAAA;AAAA;AAAA;AAAA,IAIV,CAAC,YAAY,OAAO,cAAc,IAAI,CAAC,GAAG;AAAA,MACxC,eAAe;AAAA,MACf,KAAK;AAAA,MACL,GAAI,aAAa,EAAE,QAAQ,QAAQ,WAAW,EAAE,IAAI,CAAC;AAAA,IACvD;AAAA,EACF;AACF;AA4BO,IAAM,iBAAgD,CAAC,UAAU;AACtE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,aAAa,OAAO,QAAQ;AAChC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,YAAY;AAC/C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,iBAAiB,QAAQ,MAAM,aAAa,QAAQ,KAAK,GAAG,CAAC,QAAQ,KAAK,CAAC;AACjF,QAAM,cAAc,QAAQ,MAAM,gBAAgB,QAAQ,YAAY,GAAG,CAAC,QAAQ,YAAY,CAAC;AAE/F,QAAM,YAAY,eAAe;AACjC,QAAM,YAAY,OAAO,UAAU,WAAW,QAAQ;AAEtD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAa;AAAA,MACb,oBAAkB;AAAA,MAClB,iBAAe,UAAU,SAAS;AAAA,MAClC,IAAI,QAAQ,gBAAgB,UAAU;AAAA,MAEtC;AAAA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA,aAAa,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI;AAAA,YAC7C;AAAA,YACA,eAAe;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,YAAY,MAAM,WAAW,KAAK;AAAA,YAClC;AAAA,YACA,SAAS;AAAA,YACR,GAAG,WAAW,KAAK;AAAA,YACpB,YAAY;AAAA,YACZ;AAAA,YACA;AAAA;AAAA,QACF;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW,aAAa,CAAC;AAAA,YACzB,cAAc,aAAa;AAAA,YAC3B,YAAY;AAAA,YACZ;AAAA,YACA,cAAc,MAAM;AAAA,YACpB,oBAAoB,MAAM;AAAA,YAC1B;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YAEC;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,eAAe,cAAc;","names":[]}