@12-apps/ui 5.2.1 → 5.3.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.
@@ -412,6 +412,7 @@ function SettingsRail({
412
412
  navOpen,
413
413
  onToggleNav,
414
414
  testIdPrefix,
415
+ fillHeight = false,
415
416
  ...bodyProps
416
417
  }) {
417
418
  const { variant, breakpoint, atIndex } = bodyProps;
@@ -431,7 +432,13 @@ function SettingsRail({
431
432
  flex: `0 0 ${RAIL_WIDTH}px`,
432
433
  width: RAIL_WIDTH,
433
434
  borderRight: `1px solid ${theme.palette.divider}`,
434
- pr: 2
435
+ pr: 2,
436
+ // Its own scrollbar, so a settings area with twenty categories does
437
+ // not make the thing you NAVIGATE with scroll away while you read the
438
+ // panel. `minHeight: 0` is the load-bearing half: without it a flex
439
+ // child refuses to shrink below its content and `overflow` never
440
+ // engages.
441
+ ...fillHeight ? { height: "100%", minHeight: 0, overflowY: "auto", overscrollBehavior: "contain" } : {}
435
442
  }
436
443
  }),
437
444
  children: [
@@ -620,6 +627,23 @@ function BackLink({
620
627
  }
621
628
  );
622
629
  }
630
+ function panelSx(breakpoint, hideOnNarrow, fillHeight) {
631
+ return (theme) => ({
632
+ display: hideOnNarrow ? "none" : "block",
633
+ flex: "1 1 auto",
634
+ minWidth: 0,
635
+ width: "100%",
636
+ [atLeastRail(theme, breakpoint)]: {
637
+ ...hideOnNarrow ? { display: "block" } : {},
638
+ ...fillHeight ? {
639
+ height: "100%",
640
+ minHeight: 0,
641
+ overflowY: "auto",
642
+ overscrollBehavior: "contain"
643
+ } : {}
644
+ }
645
+ });
646
+ }
623
647
  function SettingsPanel({
624
648
  inSection,
625
649
  hideOnNarrow,
@@ -632,6 +656,7 @@ function SettingsPanel({
632
656
  linkComponent,
633
657
  onSelectItem,
634
658
  testIdPrefix,
659
+ fillHeight = false,
635
660
  children
636
661
  }) {
637
662
  const showBack = inSection && indexHref !== void 0 && linkComponent !== void 0;
@@ -640,12 +665,7 @@ function SettingsPanel({
640
665
  Box5,
641
666
  {
642
667
  "data-testid": `${testIdPrefix}-panel`,
643
- sx: (theme) => ({
644
- ...hideOnNarrow ? displayAcrossRail(theme, breakpoint, "none", "block") : { display: "block" },
645
- flex: "1 1 auto",
646
- minWidth: 0,
647
- width: "100%"
648
- }),
668
+ sx: panelSx(breakpoint, hideOnNarrow, fillHeight),
649
669
  children: [
650
670
  showBack ? /* @__PURE__ */ jsx5(
651
671
  BackLink,
@@ -696,7 +716,7 @@ var DEFAULTS = {
696
716
  atIndex: false,
697
717
  backLabel: "Back"
698
718
  };
699
- function shellSx(breakpoint) {
719
+ function shellSx(breakpoint, fillHeight) {
700
720
  return (theme) => ({
701
721
  display: "flex",
702
722
  flexDirection: "column",
@@ -704,7 +724,14 @@ function shellSx(breakpoint) {
704
724
  gap: 1.5,
705
725
  width: "100%",
706
726
  minWidth: 0,
707
- [atLeastRail(theme, breakpoint ?? "md")]: { flexDirection: "row", gap: 3 }
727
+ // Only ever at the wide shape. Narrow, the rail is a disclosure or the page
728
+ // itself, and two independent scrollbars on a phone is a trap rather than a
729
+ // convenience.
730
+ [atLeastRail(theme, breakpoint ?? "md")]: {
731
+ flexDirection: "row",
732
+ gap: 3,
733
+ ...fillHeight ? { height: "100%", minHeight: 0 } : {}
734
+ }
708
735
  });
709
736
  }
710
737
  var SettingsLayout = (props) => {
@@ -721,6 +748,7 @@ var SettingsLayout = (props) => {
721
748
  children,
722
749
  testIdPrefix,
723
750
  railBreakpoint,
751
+ fillHeight = false,
724
752
  navVariant,
725
753
  atIndex,
726
754
  indexHref,
@@ -739,7 +767,7 @@ var SettingsLayout = (props) => {
739
767
  "data-testid": testIdPrefix,
740
768
  "data-nav-variant": navVariant,
741
769
  "data-at-index": atIndex ? "true" : "false",
742
- sx: shellSx(railBreakpoint),
770
+ sx: shellSx(railBreakpoint, fillHeight),
743
771
  children: [
744
772
  /* @__PURE__ */ jsx6(
745
773
  SettingsRail,
@@ -761,7 +789,8 @@ var SettingsLayout = (props) => {
761
789
  testIdPrefix,
762
790
  variant: navVariant,
763
791
  breakpoint: railBreakpoint,
764
- atIndex
792
+ atIndex,
793
+ fillHeight
765
794
  }
766
795
  ),
767
796
  /* @__PURE__ */ jsx6(
@@ -778,6 +807,7 @@ var SettingsLayout = (props) => {
778
807
  linkComponent,
779
808
  onSelectItem,
780
809
  testIdPrefix,
810
+ fillHeight,
781
811
  children
782
812
  }
783
813
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/layout/SettingsLayout/SettingsLayout.tsx","../../src/components/layout/SettingsLayout/SettingsLayout.filters.ts","../../src/components/layout/SettingsLayout/SettingsLayout.rail.tsx","../../src/components/layout/SettingsLayout/SettingsLayout.items.tsx","../../src/components/layout/SettingsLayout/SettingsLayout.styles.ts","../../src/components/layout/SettingsLayout/SettingsStatusMarker.tsx","../../src/components/layout/SettingsLayout/SettingsPanel.tsx","../../src/components/layout/SettingsLayout/SettingsSectionChips.tsx"],"sourcesContent":["'use client';\n\nimport { Box, type Theme } from '@mui/material';\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/** Stacked below the rail's breakpoint, two columns at and above it. */\nfunction shellSx(breakpoint: SettingsLayoutProps['railBreakpoint']) {\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 [atLeastRail(theme, breakpoint ?? 'md')]: { flexDirection: 'row', gap: 3 },\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 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)}\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 breakpoint={railBreakpoint}\n atIndex={atIndex}\n />\n\n <SettingsPanel\n inSection={drilldown && !atIndex}\n hideOnNarrow={drilldown && atIndex}\n breakpoint={railBreakpoint}\n indexHref={indexHref}\n backLabel={backLabel}\n ariaLabel={ariaLabel}\n sectionChips={sectionChips}\n activeItemId={activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n testIdPrefix={testIdPrefix}\n >\n {children}\n </SettingsPanel>\n </Box>\n );\n};\n\nSettingsLayout.displayName = 'SettingsLayout';\n","import type { SettingsNavGroup, SettingsNavItem } from './SettingsLayout.types';\n\n/** Case-insensitive filter over item label + keywords; drops emptied groups. */\nexport function filterGroups(groups: SettingsNavGroup[], query: string): SettingsNavGroup[] {\n const needle = query.trim().toLowerCase();\n if (!needle) return groups;\n const matches = (item: SettingsNavItem): boolean =>\n item.label.toLowerCase().includes(needle) ||\n (item.keywords ?? []).some((keyword) => keyword.toLowerCase().includes(needle));\n return groups\n .map((group) => ({ ...group, items: group.items.filter(matches) }))\n .filter((group) => group.items.length > 0);\n}\n\n/** Label of the active item, shown in the `switcher`'s collapsed header. */\nexport function activeItemLabel(\n groups: SettingsNavGroup[],\n activeItemId?: string,\n): string | undefined {\n return groups.flatMap((group) => group.items).find((item) => item.id === activeItemId)?.label;\n}\n","'use client';\n\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport SearchIcon from '@mui/icons-material/Search';\nimport { Box, Button, InputAdornment, TextField, Typography } from '@mui/material';\nimport React from 'react';\n\nimport { SettingsRailGroup, type RailShape } from './SettingsLayout.items';\nimport { atLeastRail, displayAcrossRail, RAIL_WIDTH, TOUCH_TARGET } from './SettingsLayout.styles';\nimport type {\n SettingsEmptySearchAction,\n SettingsLayoutProps,\n SettingsNavGroup,\n SettingsRailBreakpoint,\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: 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: 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}\n\n/** The searchable rail body (search + 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 ...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 <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 {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};\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 ...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 sx={(theme) => ({\n display: hiddenWhileInSection ? 'none' : 'block',\n flex: '0 0 auto',\n width: '100%',\n [atLeastRail(theme, breakpoint)]: {\n display: 'block',\n flex: `0 0 ${RAIL_WIDTH}px`,\n width: RAIL_WIDTH,\n borderRight: `1px solid ${theme.palette.divider}`,\n pr: 2,\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","'use client';\n\nimport {\n alpha,\n Box,\n List,\n ListItem,\n ListItemButton,\n ListItemIcon,\n ListItemText,\n Typography,\n useTheme,\n} from '@mui/material';\nimport React from 'react';\n\nimport { atLeastRail, TOUCH_TARGET } from './SettingsLayout.styles';\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: 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) {\n if (shape.variant !== 'drilldown' || !shape.atIndex) return null;\n return (theme: import('@mui/material').Theme) => ({\n display: 'grid',\n gridTemplateColumns: '1fr',\n columnGap: 8,\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","import type { CSSObject, Theme } from '@mui/material';\n\nimport type { SettingsNavStatus, SettingsRailBreakpoint } from './SettingsLayout.types';\n\n/** Fixed width of the left navigation rail once it has its own column. */\nexport const RAIL_WIDTH = 300;\n\n/** Visually hidden, still read aloud — for text a marker's colour cannot carry. */\nexport const SR_ONLY_SX: CSSObject = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: 0,\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * The media query for \"the rail has its own column\".\n *\n * `theme.breakpoints.up` takes a NUMBER as happily as a name, which is why\n * `railBreakpoint` accepts one: a settings area whose structural switch is at\n * 1024px has no MUI breakpoint to name (the default `lg` is 1200), and rounding\n * the design to the nearest named breakpoint moves 176px of behaviour to keep a\n * string tidy.\n */\nexport function atLeastRail(theme: Theme, breakpoint: SettingsRailBreakpoint): string {\n return theme.breakpoints.up(breakpoint);\n}\n\n/**\n * A `display` pair across that query: `narrow` below it, `wide` at and above.\n *\n * Everything that changes shape between the two navigation modes goes through\n * this, so \"which width am I?\" is answered once, in CSS, for every part at\n * once — rather than by each part asking a media query in JavaScript and\n * answering slightly differently.\n */\nexport function displayAcrossRail(\n theme: Theme,\n breakpoint: SettingsRailBreakpoint,\n narrow: string,\n wide: string,\n): CSSObject {\n return { display: narrow, [atLeastRail(theme, breakpoint)]: { display: wide } };\n}\n\n/** Minimum touch target, in px. Held down to 320px-wide phones, deliberately. */\nexport const TOUCH_TARGET = 44;\n\n/**\n * Palette role per situation.\n *\n * Returned as theme keys rather than hex so a marker follows the host's theme —\n * and so light and dark do not need two copies of this table.\n */\nexport const STATUS_COLOR: Record<Exclude<SettingsNavStatus, 'locked'>, string> = {\n ok: 'success.main',\n off: 'text.disabled',\n new: 'error.main',\n};\n","'use client';\n\nimport LockOutlinedIcon from '@mui/icons-material/LockOutlined';\nimport { Box } from '@mui/material';\nimport React from 'react';\n\nimport { SR_ONLY_SX, STATUS_COLOR } from './SettingsLayout.styles';\nimport type { SettingsNavStatus } from './SettingsLayout.types';\n\nexport interface SettingsStatusMarkerProps {\n /** Which situation to draw. */\n status: SettingsNavStatus;\n /** What it means, in the host's language — the only carrier a screen reader gets. */\n label?: string;\n /** `data-testid` for the marker element. */\n testId?: string;\n}\n\n/**\n * The situation marker beside a settings entry: a coloured dot, or a padlock\n * when the plan does not include the section.\n *\n * The label is rendered as visually-hidden text INSIDE the marker rather than as\n * an `aria-label` on it, so it lands in the accessible name of the row that\n * contains it. A row reading \"Endereço, ligado\" is one utterance; a decorative\n * dot with a title attribute beside it is two, and the second one is skipped.\n *\n * Colour alone never says which of the three states this is — that is the whole\n * reason `label` exists, and why a marker without one is a marker with no\n * meaning attached.\n */\nexport function SettingsStatusMarker({\n status,\n label,\n testId,\n}: SettingsStatusMarkerProps): React.JSX.Element {\n const hidden = label ? <Box component=\"span\" sx={SR_ONLY_SX}>{label}</Box> : null;\n\n if (status === 'locked') {\n return (\n <Box\n component=\"span\"\n data-testid={testId}\n data-status={status}\n sx={{ display: 'inline-flex', alignItems: 'center', color: 'text.disabled' }}\n >\n <LockOutlinedIcon sx={{ fontSize: 16 }} />\n {hidden}\n </Box>\n );\n }\n\n return (\n <Box\n component=\"span\"\n data-testid={testId}\n data-status={status}\n sx={{ display: 'inline-flex', alignItems: 'center' }}\n >\n <Box\n component=\"span\"\n sx={{\n width: 8,\n height: 8,\n borderRadius: '50%',\n flex: '0 0 auto',\n bgcolor: STATUS_COLOR[status],\n }}\n />\n {hidden}\n </Box>\n );\n}\n\nSettingsStatusMarker.displayName = 'SettingsStatusMarker';\n","'use client';\n\nimport ChevronLeftIcon from '@mui/icons-material/ChevronLeft';\nimport { Box } from '@mui/material';\nimport React from 'react';\n\nimport { displayAcrossRail, TOUCH_TARGET } from './SettingsLayout.styles';\nimport { SettingsSectionChips } from './SettingsSectionChips';\nimport type {\n SettingsLayoutProps,\n SettingsNavItem,\n SettingsRailBreakpoint,\n} from './SettingsLayout.types';\n\nexport interface SettingsPanelProps {\n /** True in `drilldown`, inside a section rather than at the index. */\n inSection: boolean;\n /** True in `drilldown`, at the area's index. */\n hideOnNarrow: boolean;\n breakpoint: SettingsRailBreakpoint;\n indexHref?: string;\n backLabel: string;\n ariaLabel: string;\n sectionChips?: SettingsNavItem[];\n activeItemId?: string;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n testIdPrefix: string;\n children: React.ReactNode;\n}\n\n/** The way back to the area's index — narrow widths only. */\nfunction BackLink({\n href,\n label,\n breakpoint,\n linkComponent,\n testIdPrefix,\n}: {\n href: string;\n label: string;\n breakpoint: SettingsRailBreakpoint;\n linkComponent: NonNullable<SettingsLayoutProps['linkComponent']>;\n testIdPrefix: string;\n}): React.JSX.Element {\n return (\n <Box\n component={linkComponent}\n href={href}\n aria-label={label}\n data-testid={`${testIdPrefix}-back`}\n sx={(theme) => ({\n ...displayAcrossRail(theme, breakpoint, 'inline-flex', 'none'),\n alignItems: 'center',\n gap: 0.5,\n minHeight: TOUCH_TARGET,\n pr: 1,\n textDecoration: 'none',\n color: 'text.secondary',\n font: 'inherit',\n fontSize: '0.875rem',\n })}\n >\n <ChevronLeftIcon fontSize=\"small\" />\n {label}\n </Box>\n );\n}\n\n/**\n * The central column: the open screen, and — in `drilldown`, inside a section —\n * the back link and sibling-section strip above it.\n *\n * At the index in `drilldown` this whole column is `display: none` below the\n * breakpoint and the rail becomes the page. Mounted either way: the panel and\n * the list are one tree with `display` between them, not two branches of a\n * render.\n */\nexport function SettingsPanel({\n inSection,\n hideOnNarrow,\n breakpoint,\n indexHref,\n backLabel,\n ariaLabel,\n sectionChips,\n activeItemId,\n linkComponent,\n onSelectItem,\n testIdPrefix,\n children,\n}: SettingsPanelProps): React.JSX.Element {\n const showBack = inSection && indexHref !== undefined && linkComponent !== undefined;\n const showChips = inSection && sectionChips !== undefined && sectionChips.length > 0;\n\n return (\n <Box\n data-testid={`${testIdPrefix}-panel`}\n sx={(theme) => ({\n ...(hideOnNarrow\n ? displayAcrossRail(theme, breakpoint, 'none', 'block')\n : { display: 'block' }),\n flex: '1 1 auto',\n minWidth: 0,\n width: '100%',\n })}\n >\n {showBack ? (\n <BackLink\n href={indexHref}\n label={backLabel}\n breakpoint={breakpoint}\n linkComponent={linkComponent}\n testIdPrefix={testIdPrefix}\n />\n ) : null}\n\n {showChips ? (\n <Box\n sx={(theme) => ({\n ...displayAcrossRail(theme, breakpoint, 'block', 'none'),\n mb: 1,\n minWidth: 0,\n })}\n >\n <SettingsSectionChips\n items={sectionChips}\n activeItemId={activeItemId}\n ariaLabel={ariaLabel}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n testIdPrefix={testIdPrefix}\n />\n </Box>\n ) : null}\n\n {children}\n </Box>\n );\n}\n","'use client';\n\nimport { alpha, Box, useTheme, type Theme } from '@mui/material';\nimport React, { useEffect, useRef, type RefObject } from 'react';\n\nimport { SettingsStatusMarker } from './SettingsStatusMarker';\nimport { TOUCH_TARGET } from './SettingsLayout.styles';\nimport type { SettingsLayoutProps, SettingsNavItem } from './SettingsLayout.types';\n\nexport interface SettingsSectionChipsProps {\n /** The sibling sections to offer — usually the open item's own group. */\n items: SettingsNavItem[];\n /** Which one is open. */\n activeItemId?: string;\n /** Accessible name of the strip, in the host's language. */\n ariaLabel: string;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n testIdPrefix: string;\n}\n\n/** True when the visitor asked the OS for less movement. Re-read per scroll. */\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/**\n * Keep the open section's chip in view, however the visitor got here.\n *\n * Keyed on `activeItemId` in an effect rather than done in a click handler:\n * arriving by the desktop rail, by a card, by a pasted link and by the browser's\n * back button all have to land the same way, and only one of those is a click on\n * a chip. Every re-render otherwise starts the strip at scroll 0, which parks\n * the active chip off the right edge on any list longer than a screen — the\n * visitor is told where they are by an element they cannot see.\n *\n * `itemsKey` rather than `items`: a host almost always rebuilds that array per\n * render (`groups.flatMap(...)`), and depending on it would re-run this on every\n * render and yank the strip back mid-drag.\n *\n * The key joins on NUL — as the ESCAPE `'\\u0000'`, which it must stay. A\n * literal NUL byte works identically at runtime and makes git classify this file\n * as BINARY: `git diff` prints \"Binary file not shown\" and the GitHub review UI\n * shows nothing at all. This file shipped that way once, and the cost was not\n * the byte — it was a whole change nobody could read.\n */\nfunction useCentreActiveChip(\n stripRef: RefObject<HTMLDivElement | null>,\n activeItemId: string | undefined,\n itemsKey: string,\n): void {\n useEffect(() => {\n const strip = stripRef.current;\n if (!strip || !activeItemId) return;\n // Walked rather than selected: an id is host data, and a section id carrying\n // a quote would turn a selector string into a thrown SyntaxError — which\n // here would take the whole strip down to centre one chip.\n const chip = Array.from(strip.children).find(\n (node): node is HTMLElement =>\n node instanceof HTMLElement && node.dataset.chipId === activeItemId,\n );\n if (!chip) return;\n // Feature-checked, not assumed. `Element.scrollTo` is absent in jsdom and in\n // a few embedded webviews, and an unguarded call there THROWS out of an\n // effect — which React escalates to the nearest error boundary. The whole\n // settings area would go down to centre one chip, and the host's own tests\n // could not render the shell at all. Same reasoning as walking the children\n // instead of building a selector: nothing in here is worth a crash.\n if (typeof strip.scrollTo !== 'function') return;\n const target = chip.offsetLeft + chip.offsetWidth / 2 - strip.clientWidth / 2;\n const left = Math.max(0, Math.min(target, strip.scrollWidth - strip.clientWidth));\n strip.scrollTo({ left, behavior: prefersReducedMotion() ? 'auto' : 'smooth' });\n }, [stripRef, activeItemId, itemsKey]);\n}\n\n/** One chip: a link when the host gave it a target, else a button. */\nfunction SectionChip({\n item,\n active,\n theme,\n linkComponent,\n onSelectItem,\n testIdPrefix,\n}: {\n item: SettingsNavItem;\n active: boolean;\n theme: Theme;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const asLink = item.href !== undefined && linkComponent !== undefined;\n const behaviour = asLink\n ? { component: linkComponent, href: item.href }\n : { component: 'button' as const, type: 'button' as const, onClick: () => onSelectItem?.(item.id) };\n\n return (\n <Box\n {...behaviour}\n data-chip-id={item.id}\n data-testid={`${testIdPrefix}-chip-${item.id}`}\n aria-current={active ? 'page' : undefined}\n sx={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 0.75,\n flex: '0 0 auto',\n minHeight: TOUCH_TARGET,\n px: 1.5,\n borderRadius: 999,\n cursor: 'pointer',\n font: 'inherit',\n fontSize: '0.8125rem',\n fontWeight: active ? 700 : 500,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n color: active ? 'primary.main' : 'text.secondary',\n bgcolor: active ? alpha(theme.palette.primary.main, 0.12) : 'transparent',\n border: `1px solid ${active ? alpha(theme.palette.primary.main, 0.4) : theme.palette.divider}`,\n }}\n >\n {item.status ? (\n <SettingsStatusMarker\n status={item.status}\n label={item.statusLabel}\n testId={`${testIdPrefix}-chip-status-${item.id}`}\n />\n ) : null}\n {item.label}\n </Box>\n );\n}\n\n/**\n * The narrow-width section strip: horizontally scrollable chips for the sections\n * beside the open one.\n *\n * It clips its own overflow — `overflow-x: auto` on the strip, never on an\n * ancestor. A strip that pushes the page wide makes every OTHER screen at that\n * width scroll sideways too, and the cause is nowhere near the symptom.\n */\nexport function SettingsSectionChips({\n items,\n activeItemId,\n ariaLabel,\n linkComponent,\n onSelectItem,\n testIdPrefix,\n}: SettingsSectionChipsProps): React.JSX.Element {\n const theme = useTheme();\n const stripRef = useRef<HTMLDivElement | null>(null);\n useCentreActiveChip(stripRef, activeItemId, items.map((item) => item.id).join('\\u0000'));\n\n return (\n <Box\n ref={stripRef}\n component=\"nav\"\n aria-label={ariaLabel}\n data-testid={`${testIdPrefix}-chips`}\n sx={{\n display: 'flex',\n alignItems: 'center',\n gap: 0.75,\n px: 0.25,\n py: 0.75,\n overflowX: 'auto',\n overflowY: 'hidden',\n scrollbarWidth: 'none',\n '&::-webkit-scrollbar': { display: 'none' },\n }}\n >\n {items.map((item) => (\n <SectionChip\n key={item.id}\n item={item}\n active={item.id === activeItemId}\n theme={theme}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n testIdPrefix={testIdPrefix}\n />\n ))}\n </Box>\n );\n}\n\nSettingsSectionChips.displayName = 'SettingsSectionChips';\n"],"mappings":"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","names":["Box","Box","Typography","Box","jsx","jsxs","Box","jsx","jsxs","Box","Typography","Box","alpha","Box","useTheme","jsx","jsxs","Box","alpha","useTheme","jsx","jsxs","Box","jsx","jsxs","Box"]}
1
+ {"version":3,"sources":["../../src/components/layout/SettingsLayout/SettingsLayout.tsx","../../src/components/layout/SettingsLayout/SettingsLayout.filters.ts","../../src/components/layout/SettingsLayout/SettingsLayout.rail.tsx","../../src/components/layout/SettingsLayout/SettingsLayout.items.tsx","../../src/components/layout/SettingsLayout/SettingsLayout.styles.ts","../../src/components/layout/SettingsLayout/SettingsStatusMarker.tsx","../../src/components/layout/SettingsLayout/SettingsPanel.tsx","../../src/components/layout/SettingsLayout/SettingsSectionChips.tsx"],"sourcesContent":["'use client';\n\nimport { Box, type Theme } from '@mui/material';\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/** Stacked below the rail's breakpoint, two columns at and above it. */\nfunction shellSx(breakpoint: SettingsLayoutProps['railBreakpoint'], fillHeight: boolean) {\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 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 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","import type { SettingsNavGroup, SettingsNavItem } from './SettingsLayout.types';\n\n/** Case-insensitive filter over item label + keywords; drops emptied groups. */\nexport function filterGroups(groups: SettingsNavGroup[], query: string): SettingsNavGroup[] {\n const needle = query.trim().toLowerCase();\n if (!needle) return groups;\n const matches = (item: SettingsNavItem): boolean =>\n item.label.toLowerCase().includes(needle) ||\n (item.keywords ?? []).some((keyword) => keyword.toLowerCase().includes(needle));\n return groups\n .map((group) => ({ ...group, items: group.items.filter(matches) }))\n .filter((group) => group.items.length > 0);\n}\n\n/** Label of the active item, shown in the `switcher`'s collapsed header. */\nexport function activeItemLabel(\n groups: SettingsNavGroup[],\n activeItemId?: string,\n): string | undefined {\n return groups.flatMap((group) => group.items).find((item) => item.id === activeItemId)?.label;\n}\n","'use client';\n\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport SearchIcon from '@mui/icons-material/Search';\nimport { Box, Button, InputAdornment, TextField, Typography } from '@mui/material';\nimport React from 'react';\n\nimport { SettingsRailGroup, type RailShape } from './SettingsLayout.items';\nimport { atLeastRail, displayAcrossRail, RAIL_WIDTH, TOUCH_TARGET } from './SettingsLayout.styles';\nimport type {\n SettingsEmptySearchAction,\n SettingsLayoutProps,\n SettingsNavGroup,\n SettingsRailBreakpoint,\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: 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: 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}\n\n/** The searchable rail body (search + 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 ...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 <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 {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};\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 ...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 sx={(theme) => ({\n display: hiddenWhileInSection ? 'none' : 'block',\n flex: '0 0 auto',\n width: '100%',\n [atLeastRail(theme, breakpoint)]: {\n display: 'block',\n flex: `0 0 ${RAIL_WIDTH}px`,\n width: RAIL_WIDTH,\n borderRight: `1px solid ${theme.palette.divider}`,\n pr: 2,\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","'use client';\n\nimport {\n alpha,\n Box,\n List,\n ListItem,\n ListItemButton,\n ListItemIcon,\n ListItemText,\n Typography,\n useTheme,\n} from '@mui/material';\nimport React from 'react';\n\nimport { atLeastRail, TOUCH_TARGET } from './SettingsLayout.styles';\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: 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) {\n if (shape.variant !== 'drilldown' || !shape.atIndex) return null;\n return (theme: import('@mui/material').Theme) => ({\n display: 'grid',\n gridTemplateColumns: '1fr',\n columnGap: 8,\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","import type { CSSObject, Theme } from '@mui/material';\n\nimport type { SettingsNavStatus, SettingsRailBreakpoint } from './SettingsLayout.types';\n\n/** Fixed width of the left navigation rail once it has its own column. */\nexport const RAIL_WIDTH = 300;\n\n/** Visually hidden, still read aloud — for text a marker's colour cannot carry. */\nexport const SR_ONLY_SX: CSSObject = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: 0,\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * The media query for \"the rail has its own column\".\n *\n * `theme.breakpoints.up` takes a NUMBER as happily as a name, which is why\n * `railBreakpoint` accepts one: a settings area whose structural switch is at\n * 1024px has no MUI breakpoint to name (the default `lg` is 1200), and rounding\n * the design to the nearest named breakpoint moves 176px of behaviour to keep a\n * string tidy.\n */\nexport function atLeastRail(theme: Theme, breakpoint: SettingsRailBreakpoint): string {\n return theme.breakpoints.up(breakpoint);\n}\n\n/**\n * A `display` pair across that query: `narrow` below it, `wide` at and above.\n *\n * Everything that changes shape between the two navigation modes goes through\n * this, so \"which width am I?\" is answered once, in CSS, for every part at\n * once — rather than by each part asking a media query in JavaScript and\n * answering slightly differently.\n */\nexport function displayAcrossRail(\n theme: Theme,\n breakpoint: SettingsRailBreakpoint,\n narrow: string,\n wide: string,\n): CSSObject {\n return { display: narrow, [atLeastRail(theme, breakpoint)]: { display: wide } };\n}\n\n/** Minimum touch target, in px. Held down to 320px-wide phones, deliberately. */\nexport const TOUCH_TARGET = 44;\n\n/**\n * Palette role per situation.\n *\n * Returned as theme keys rather than hex so a marker follows the host's theme —\n * and so light and dark do not need two copies of this table.\n */\nexport const STATUS_COLOR: Record<Exclude<SettingsNavStatus, 'locked'>, string> = {\n ok: 'success.main',\n off: 'text.disabled',\n new: 'error.main',\n};\n","'use client';\n\nimport LockOutlinedIcon from '@mui/icons-material/LockOutlined';\nimport { Box } from '@mui/material';\nimport React from 'react';\n\nimport { SR_ONLY_SX, STATUS_COLOR } from './SettingsLayout.styles';\nimport type { SettingsNavStatus } from './SettingsLayout.types';\n\nexport interface SettingsStatusMarkerProps {\n /** Which situation to draw. */\n status: SettingsNavStatus;\n /** What it means, in the host's language — the only carrier a screen reader gets. */\n label?: string;\n /** `data-testid` for the marker element. */\n testId?: string;\n}\n\n/**\n * The situation marker beside a settings entry: a coloured dot, or a padlock\n * when the plan does not include the section.\n *\n * The label is rendered as visually-hidden text INSIDE the marker rather than as\n * an `aria-label` on it, so it lands in the accessible name of the row that\n * contains it. A row reading \"Endereço, ligado\" is one utterance; a decorative\n * dot with a title attribute beside it is two, and the second one is skipped.\n *\n * Colour alone never says which of the three states this is — that is the whole\n * reason `label` exists, and why a marker without one is a marker with no\n * meaning attached.\n */\nexport function SettingsStatusMarker({\n status,\n label,\n testId,\n}: SettingsStatusMarkerProps): React.JSX.Element {\n const hidden = label ? <Box component=\"span\" sx={SR_ONLY_SX}>{label}</Box> : null;\n\n if (status === 'locked') {\n return (\n <Box\n component=\"span\"\n data-testid={testId}\n data-status={status}\n sx={{ display: 'inline-flex', alignItems: 'center', color: 'text.disabled' }}\n >\n <LockOutlinedIcon sx={{ fontSize: 16 }} />\n {hidden}\n </Box>\n );\n }\n\n return (\n <Box\n component=\"span\"\n data-testid={testId}\n data-status={status}\n sx={{ display: 'inline-flex', alignItems: 'center' }}\n >\n <Box\n component=\"span\"\n sx={{\n width: 8,\n height: 8,\n borderRadius: '50%',\n flex: '0 0 auto',\n bgcolor: STATUS_COLOR[status],\n }}\n />\n {hidden}\n </Box>\n );\n}\n\nSettingsStatusMarker.displayName = 'SettingsStatusMarker';\n","\"use client\";\n\nimport ChevronLeftIcon from \"@mui/icons-material/ChevronLeft\";\nimport type { CSSObject, Theme } from \"@mui/material\";\nimport { Box } from \"@mui/material\";\nimport React from \"react\";\n\nimport {\n atLeastRail,\n displayAcrossRail,\n TOUCH_TARGET,\n} from \"./SettingsLayout.styles\";\nimport { SettingsSectionChips } from \"./SettingsSectionChips\";\nimport type {\n SettingsLayoutProps,\n SettingsNavItem,\n SettingsRailBreakpoint,\n} from \"./SettingsLayout.types\";\n\nexport interface SettingsPanelProps {\n /** True in `drilldown`, inside a section rather than at the index. */\n inSection: boolean;\n /** True in `drilldown`, at the area's index. */\n hideOnNarrow: boolean;\n breakpoint: SettingsRailBreakpoint;\n indexHref?: string;\n backLabel: string;\n ariaLabel: string;\n sectionChips?: SettingsNavItem[];\n activeItemId?: string;\n linkComponent?: SettingsLayoutProps[\"linkComponent\"];\n onSelectItem?: (id: string) => void;\n testIdPrefix: string;\n children: React.ReactNode;\n /** Scroll inside the panel rather than with the page. See `fillHeight`. */\n fillHeight?: boolean;\n}\n\n/** The way back to the area's index — narrow widths only. */\nfunction BackLink({\n href,\n label,\n breakpoint,\n linkComponent,\n testIdPrefix,\n}: {\n href: string;\n label: string;\n breakpoint: SettingsRailBreakpoint;\n linkComponent: NonNullable<SettingsLayoutProps[\"linkComponent\"]>;\n testIdPrefix: string;\n}): React.JSX.Element {\n return (\n <Box\n component={linkComponent}\n href={href}\n aria-label={label}\n data-testid={`${testIdPrefix}-back`}\n sx={(theme) => ({\n ...displayAcrossRail(theme, breakpoint, \"inline-flex\", \"none\"),\n alignItems: \"center\",\n gap: 0.5,\n minHeight: TOUCH_TARGET,\n pr: 1,\n textDecoration: \"none\",\n color: \"text.secondary\",\n font: \"inherit\",\n fontSize: \"0.875rem\",\n })}\n >\n <ChevronLeftIcon fontSize=\"small\" />\n {label}\n </Box>\n );\n}\n\n/**\n * The panel's own box, as ONE object per media query rather than two.\n *\n * Both the `display` rule and `fillHeight`'s belong at the same wide-shape\n * query, and spreading them as separate `{ [query]: … }` objects makes the\n * second REPLACE the first — so turning `fillHeight` on silently dropped the\n * `display: block` that `hideOnNarrow` puts there and left the panel\n * `display: none` at every width. That only shows at a drilldown INDEX, where\n * `hideOnNarrow` is true, which is why a section route looked fine.\n *\n * `overflowY` here is also what lets a `position: sticky` toolbar inside\n * `children` pin to the TOP OF THE PANEL — under the host's header — instead\n * of to a document that is not the scroller.\n */\nfunction panelSx(\n breakpoint: SettingsRailBreakpoint,\n hideOnNarrow: boolean,\n fillHeight: boolean,\n): (theme: Theme) => CSSObject {\n return (theme) => ({\n display: hideOnNarrow ? \"none\" : \"block\",\n flex: \"1 1 auto\",\n minWidth: 0,\n width: \"100%\",\n [atLeastRail(theme, breakpoint)]: {\n ...(hideOnNarrow ? { display: \"block\" } : {}),\n ...(fillHeight\n ? {\n height: \"100%\",\n minHeight: 0,\n overflowY: \"auto\",\n overscrollBehavior: \"contain\",\n }\n : {}),\n },\n });\n}\n\n/**\n * The central column: the open screen, and — in `drilldown`, inside a section —\n * the back link and sibling-section strip above it.\n *\n * At the index in `drilldown` this whole column is `display: none` below the\n * breakpoint and the rail becomes the page. Mounted either way: the panel and\n * the list are one tree with `display` between them, not two branches of a\n * render.\n */\nexport function SettingsPanel({\n inSection,\n hideOnNarrow,\n breakpoint,\n indexHref,\n backLabel,\n ariaLabel,\n sectionChips,\n activeItemId,\n linkComponent,\n onSelectItem,\n testIdPrefix,\n fillHeight = false,\n children,\n}: SettingsPanelProps): React.JSX.Element {\n const showBack =\n inSection && indexHref !== undefined && linkComponent !== undefined;\n const showChips =\n inSection && sectionChips !== undefined && sectionChips.length > 0;\n\n return (\n <Box\n data-testid={`${testIdPrefix}-panel`}\n sx={panelSx(breakpoint, hideOnNarrow, fillHeight)}\n >\n {showBack ? (\n <BackLink\n href={indexHref}\n label={backLabel}\n breakpoint={breakpoint}\n linkComponent={linkComponent}\n testIdPrefix={testIdPrefix}\n />\n ) : null}\n\n {showChips ? (\n <Box\n sx={(theme) => ({\n ...displayAcrossRail(theme, breakpoint, \"block\", \"none\"),\n mb: 1,\n minWidth: 0,\n })}\n >\n <SettingsSectionChips\n items={sectionChips}\n activeItemId={activeItemId}\n ariaLabel={ariaLabel}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n testIdPrefix={testIdPrefix}\n />\n </Box>\n ) : null}\n\n {children}\n </Box>\n );\n}\n","'use client';\n\nimport { alpha, Box, useTheme, type Theme } from '@mui/material';\nimport React, { useEffect, useRef, type RefObject } from 'react';\n\nimport { SettingsStatusMarker } from './SettingsStatusMarker';\nimport { TOUCH_TARGET } from './SettingsLayout.styles';\nimport type { SettingsLayoutProps, SettingsNavItem } from './SettingsLayout.types';\n\nexport interface SettingsSectionChipsProps {\n /** The sibling sections to offer — usually the open item's own group. */\n items: SettingsNavItem[];\n /** Which one is open. */\n activeItemId?: string;\n /** Accessible name of the strip, in the host's language. */\n ariaLabel: string;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n testIdPrefix: string;\n}\n\n/** True when the visitor asked the OS for less movement. Re-read per scroll. */\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/**\n * Keep the open section's chip in view, however the visitor got here.\n *\n * Keyed on `activeItemId` in an effect rather than done in a click handler:\n * arriving by the desktop rail, by a card, by a pasted link and by the browser's\n * back button all have to land the same way, and only one of those is a click on\n * a chip. Every re-render otherwise starts the strip at scroll 0, which parks\n * the active chip off the right edge on any list longer than a screen — the\n * visitor is told where they are by an element they cannot see.\n *\n * `itemsKey` rather than `items`: a host almost always rebuilds that array per\n * render (`groups.flatMap(...)`), and depending on it would re-run this on every\n * render and yank the strip back mid-drag.\n *\n * The key joins on NUL — as the ESCAPE `'\\u0000'`, which it must stay. A\n * literal NUL byte works identically at runtime and makes git classify this file\n * as BINARY: `git diff` prints \"Binary file not shown\" and the GitHub review UI\n * shows nothing at all. This file shipped that way once, and the cost was not\n * the byte — it was a whole change nobody could read.\n */\nfunction useCentreActiveChip(\n stripRef: RefObject<HTMLDivElement | null>,\n activeItemId: string | undefined,\n itemsKey: string,\n): void {\n useEffect(() => {\n const strip = stripRef.current;\n if (!strip || !activeItemId) return;\n // Walked rather than selected: an id is host data, and a section id carrying\n // a quote would turn a selector string into a thrown SyntaxError — which\n // here would take the whole strip down to centre one chip.\n const chip = Array.from(strip.children).find(\n (node): node is HTMLElement =>\n node instanceof HTMLElement && node.dataset.chipId === activeItemId,\n );\n if (!chip) return;\n // Feature-checked, not assumed. `Element.scrollTo` is absent in jsdom and in\n // a few embedded webviews, and an unguarded call there THROWS out of an\n // effect — which React escalates to the nearest error boundary. The whole\n // settings area would go down to centre one chip, and the host's own tests\n // could not render the shell at all. Same reasoning as walking the children\n // instead of building a selector: nothing in here is worth a crash.\n if (typeof strip.scrollTo !== 'function') return;\n const target = chip.offsetLeft + chip.offsetWidth / 2 - strip.clientWidth / 2;\n const left = Math.max(0, Math.min(target, strip.scrollWidth - strip.clientWidth));\n strip.scrollTo({ left, behavior: prefersReducedMotion() ? 'auto' : 'smooth' });\n }, [stripRef, activeItemId, itemsKey]);\n}\n\n/** One chip: a link when the host gave it a target, else a button. */\nfunction SectionChip({\n item,\n active,\n theme,\n linkComponent,\n onSelectItem,\n testIdPrefix,\n}: {\n item: SettingsNavItem;\n active: boolean;\n theme: Theme;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const asLink = item.href !== undefined && linkComponent !== undefined;\n const behaviour = asLink\n ? { component: linkComponent, href: item.href }\n : { component: 'button' as const, type: 'button' as const, onClick: () => onSelectItem?.(item.id) };\n\n return (\n <Box\n {...behaviour}\n data-chip-id={item.id}\n data-testid={`${testIdPrefix}-chip-${item.id}`}\n aria-current={active ? 'page' : undefined}\n sx={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 0.75,\n flex: '0 0 auto',\n minHeight: TOUCH_TARGET,\n px: 1.5,\n borderRadius: 999,\n cursor: 'pointer',\n font: 'inherit',\n fontSize: '0.8125rem',\n fontWeight: active ? 700 : 500,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n color: active ? 'primary.main' : 'text.secondary',\n bgcolor: active ? alpha(theme.palette.primary.main, 0.12) : 'transparent',\n border: `1px solid ${active ? alpha(theme.palette.primary.main, 0.4) : theme.palette.divider}`,\n }}\n >\n {item.status ? (\n <SettingsStatusMarker\n status={item.status}\n label={item.statusLabel}\n testId={`${testIdPrefix}-chip-status-${item.id}`}\n />\n ) : null}\n {item.label}\n </Box>\n );\n}\n\n/**\n * The narrow-width section strip: horizontally scrollable chips for the sections\n * beside the open one.\n *\n * It clips its own overflow — `overflow-x: auto` on the strip, never on an\n * ancestor. A strip that pushes the page wide makes every OTHER screen at that\n * width scroll sideways too, and the cause is nowhere near the symptom.\n */\nexport function SettingsSectionChips({\n items,\n activeItemId,\n ariaLabel,\n linkComponent,\n onSelectItem,\n testIdPrefix,\n}: SettingsSectionChipsProps): React.JSX.Element {\n const theme = useTheme();\n const stripRef = useRef<HTMLDivElement | null>(null);\n useCentreActiveChip(stripRef, activeItemId, items.map((item) => item.id).join('\\u0000'));\n\n return (\n <Box\n ref={stripRef}\n component=\"nav\"\n aria-label={ariaLabel}\n data-testid={`${testIdPrefix}-chips`}\n sx={{\n display: 'flex',\n alignItems: 'center',\n gap: 0.75,\n px: 0.25,\n py: 0.75,\n overflowX: 'auto',\n overflowY: 'hidden',\n scrollbarWidth: 'none',\n '&::-webkit-scrollbar': { display: 'none' },\n }}\n >\n {items.map((item) => (\n <SectionChip\n key={item.id}\n item={item}\n active={item.id === activeItemId}\n theme={theme}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n testIdPrefix={testIdPrefix}\n />\n ))}\n </Box>\n );\n}\n\nSettingsSectionChips.displayName = 'SettingsSectionChips';\n"],"mappings":"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","names":["Box","Box","Typography","Box","jsx","jsxs","Box","jsx","jsxs","Box","Typography","Box","alpha","Box","useTheme","jsx","jsxs","Box","alpha","useTheme","jsx","jsxs","Box","jsx","jsxs","Box"]}
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsLayout.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAOjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAkDlE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAyExD,CAAC"}
1
+ {"version":3,"file":"SettingsLayout.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAOjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAyDlE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA4ExD,CAAC"}
@@ -20,7 +20,9 @@ export type SettingsRailProps = RailBodyProps & {
20
20
  title?: React.ReactNode;
21
21
  activeLabel?: string;
22
22
  onToggleNav: () => void;
23
+ /** Scroll inside the rail rather than with the page. See `fillHeight`. */
24
+ fillHeight?: boolean;
23
25
  };
24
26
  /** The navigation column: title, the `switcher`'s disclosure, and the body. */
25
- export declare function SettingsRail({ title, activeLabel, navOpen, onToggleNav, testIdPrefix, ...bodyProps }: SettingsRailProps): React.JSX.Element;
27
+ export declare function SettingsRail({ title, activeLabel, navOpen, onToggleNav, testIdPrefix, fillHeight, ...bodyProps }: SettingsRailProps): React.JSX.Element;
26
28
  //# sourceMappingURL=SettingsLayout.rail.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsLayout.rail.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.rail.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE3E,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACnB,gBAAgB,EAEjB,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;CACtB;AAwED,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;CACzB,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,WAAW,EACX,OAAO,EACP,WAAW,EACX,YAAY,EACZ,GAAG,SAAS,EACb,EAAE,iBAAiB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAuDvC"}
1
+ {"version":3,"file":"SettingsLayout.rail.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.rail.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE3E,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACnB,gBAAgB,EAEjB,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;CACtB;AAwED,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;CACtB,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,WAAW,EACX,OAAO,EACP,WAAW,EACX,YAAY,EACZ,UAAkB,EAClB,GAAG,SAAS,EACb,EAAE,iBAAiB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA+DvC"}
@@ -139,6 +139,24 @@ export interface SettingsLayoutProps {
139
139
  /** Prefix for `data-testid` attributes. Defaults to `"settings"`. */
140
140
  testIdPrefix?: string;
141
141
  /** Width at which the rail takes its own column. Defaults to `"md"`. */
142
+ /**
143
+ * Give the rail and the panel a scrollbar EACH, instead of scrolling the
144
+ * page (default `false`).
145
+ *
146
+ * Off by default because it only works where the host has already given this
147
+ * component a definite height — a `100dvh` flex column with the settings area
148
+ * as a `flex: 1; min-height: 0` child is the usual shape. Turned on without
149
+ * that, the percentage heights below resolve against `auto` and the layout
150
+ * collapses, which is why an opt-in beats a clever guess.
151
+ *
152
+ * What it buys, in a settings area with twenty-odd categories: the rail stops
153
+ * scrolling away when you read down a long panel, so the thing you navigate
154
+ * with is still there when you want it. It also makes the PANEL the scroll
155
+ * container, which is what a `position: sticky` toolbar inside `children`
156
+ * needs in order to pin below the host's header rather than at the top of the
157
+ * document.
158
+ */
159
+ fillHeight?: boolean;
142
160
  railBreakpoint?: SettingsRailBreakpoint;
143
161
  /** Narrow-width navigation shape. Defaults to `"switcher"`. */
144
162
  navVariant?: SettingsNavVariant;
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsLayout.types.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,0FAA0F;IAC1F,EAAE,EAAE,MAAM,CAAC;IACX,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,sBAAsB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAExE;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,WAAW,CAAC;AAE1D,kFAAkF;AAClF,MAAM,WAAW,yBAAyB;IACxC,6CAA6C;IAC7C,KAAK,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,4CAA4C;IAC5C,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,yBAAyB,CAAC;IAC9C;;;;OAIG;IACH,aAAa,CAAC,EAAE,WAAW,CAAC;IAC5B,qEAAqE;IACrE,QAAQ,EAAE,SAAS,CAAC;IACpB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,eAAe,EAAE,CAAC;CAClC"}
1
+ {"version":3,"file":"SettingsLayout.types.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,0FAA0F;IAC1F,EAAE,EAAE,MAAM,CAAC;IACX,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,sBAAsB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAExE;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,WAAW,CAAC;AAE1D,kFAAkF;AAClF,MAAM,WAAW,yBAAyB;IACxC,6CAA6C;IAC7C,KAAK,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,4CAA4C;IAC5C,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,yBAAyB,CAAC;IAC9C;;;;OAIG;IACH,aAAa,CAAC,EAAE,WAAW,CAAC;IAC5B,qEAAqE;IACrE,QAAQ,EAAE,SAAS,CAAC;IACpB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,eAAe,EAAE,CAAC;CAClC"}
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
- import type { SettingsLayoutProps, SettingsNavItem, SettingsRailBreakpoint } from './SettingsLayout.types';
1
+ import React from "react";
2
+ import type { SettingsLayoutProps, SettingsNavItem, SettingsRailBreakpoint } from "./SettingsLayout.types";
3
3
  export interface SettingsPanelProps {
4
4
  /** True in `drilldown`, inside a section rather than at the index. */
5
5
  inSection: boolean;
@@ -11,10 +11,12 @@ export interface SettingsPanelProps {
11
11
  ariaLabel: string;
12
12
  sectionChips?: SettingsNavItem[];
13
13
  activeItemId?: string;
14
- linkComponent?: SettingsLayoutProps['linkComponent'];
14
+ linkComponent?: SettingsLayoutProps["linkComponent"];
15
15
  onSelectItem?: (id: string) => void;
16
16
  testIdPrefix: string;
17
17
  children: React.ReactNode;
18
+ /** Scroll inside the panel rather than with the page. See `fillHeight`. */
19
+ fillHeight?: boolean;
18
20
  }
19
21
  /**
20
22
  * The central column: the open screen, and — in `drilldown`, inside a section —
@@ -25,5 +27,5 @@ export interface SettingsPanelProps {
25
27
  * the list are one tree with `display` between them, not two branches of a
26
28
  * render.
27
29
  */
28
- export declare function SettingsPanel({ inSection, hideOnNarrow, breakpoint, indexHref, backLabel, ariaLabel, sectionChips, activeItemId, linkComponent, onSelectItem, testIdPrefix, children, }: SettingsPanelProps): React.JSX.Element;
30
+ export declare function SettingsPanel({ inSection, hideOnNarrow, breakpoint, indexHref, backLabel, ariaLabel, sectionChips, activeItemId, linkComponent, onSelectItem, testIdPrefix, fillHeight, children, }: SettingsPanelProps): React.JSX.Element;
29
31
  //# sourceMappingURL=SettingsPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsPanel.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsPanel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,sBAAsB,EACvB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,SAAS,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,sBAAsB,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,eAAe,EAAE,CAAC;IACjC,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,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAwCD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,QAAQ,GACT,EAAE,kBAAkB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAgDxC"}
1
+ {"version":3,"file":"SettingsPanel.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsPanel.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,sBAAsB,EACvB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,SAAS,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,sBAAsB,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,eAAe,EAAE,CAAC;IACjC,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,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA8ED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,UAAkB,EAClB,QAAQ,GACT,EAAE,kBAAkB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA2CxC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "5.2.1",
3
+ "version": "5.3.1",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@9.0.0",
6
6
  "type": "module",
@@ -137,6 +137,32 @@ const [active, setActive] = useState('profile');
137
137
  </SettingsLayout>;
138
138
  ```
139
139
 
140
+ ## Two scrollbars, or none (`fillHeight`)
141
+
142
+ By default the page scrolls and the rail goes with it — fine for a handful of
143
+ categories, wrong once there are twenty: the thing you navigate WITH disappears
144
+ while you read the panel.
145
+
146
+ `fillHeight` gives the rail and the panel a scrollbar each, at the wide shape
147
+ only. Narrow, the rail is a disclosure or the page itself, and two independent
148
+ scroll regions on a phone is a trap rather than a convenience.
149
+
150
+ ```tsx
151
+ <SettingsLayout fillHeight railBreakpoint={1024} …>
152
+ ```
153
+
154
+ **It needs a definite height from the host**, which is why it is opt-in rather
155
+ than the default. The usual shape is a `100dvh` flex column with the settings
156
+ area as a `flex: 1; min-height: 0` child; without one the percentage heights
157
+ resolve against `auto` and the layout collapses. Defaulting it on would break
158
+ every existing consumer silently on upgrade.
159
+
160
+ It also changes WHICH element scrolls, and that is the second reason to want
161
+ it: a `position: sticky` toolbar inside `children` pins to the top of the
162
+ **panel** — under the host's own header — instead of to a document that is not
163
+ the scroll container.
164
+
165
+
140
166
  ## Props
141
167
 
142
168
  ### SettingsLayoutProps
@@ -154,6 +180,7 @@ const [active, setActive] = useState('profile');
154
180
  | children | ReactNode | - | Central panel content (the selected screen) |
155
181
  | testIdPrefix | string | `'settings'` | Prefix for `data-testid` attributes |
156
182
  | railBreakpoint | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Width at which the rail takes its own column |
183
+ | fillHeight | boolean | `false` | Rail and panel each scroll themselves (needs a definite height) |
157
184
  | navVariant | `'switcher' \| 'drilldown'` | `'switcher'` | Narrow-width navigation shape |
158
185
  | atIndex | boolean | `false` | `drilldown`: the router is at the area's index, not in a section |
159
186
  | indexHref | string | - | `drilldown`: where the back link goes |
@@ -211,6 +211,8 @@ export type SettingsRailProps = RailBodyProps & {
211
211
  title?: React.ReactNode;
212
212
  activeLabel?: string;
213
213
  onToggleNav: () => void;
214
+ /** Scroll inside the rail rather than with the page. See `fillHeight`. */
215
+ fillHeight?: boolean;
214
216
  };
215
217
 
216
218
  /** The navigation column: title, the `switcher`'s disclosure, and the body. */
@@ -220,6 +222,7 @@ export function SettingsRail({
220
222
  navOpen,
221
223
  onToggleNav,
222
224
  testIdPrefix,
225
+ fillHeight = false,
223
226
  ...bodyProps
224
227
  }: SettingsRailProps): React.JSX.Element {
225
228
  const { variant, breakpoint, atIndex } = bodyProps;
@@ -245,6 +248,14 @@ export function SettingsRail({
245
248
  width: RAIL_WIDTH,
246
249
  borderRight: `1px solid ${theme.palette.divider}`,
247
250
  pr: 2,
251
+ // Its own scrollbar, so a settings area with twenty categories does
252
+ // not make the thing you NAVIGATE with scroll away while you read the
253
+ // panel. `minHeight: 0` is the load-bearing half: without it a flex
254
+ // child refuses to shrink below its content and `overflow` never
255
+ // engages.
256
+ ...(fillHeight
257
+ ? { height: '100%', minHeight: 0, overflowY: 'auto' as const, overscrollBehavior: 'contain' as const }
258
+ : {}),
248
259
  },
249
260
  })}
250
261
  >
@@ -46,7 +46,7 @@ const DEFAULTS = {
46
46
  } satisfies Partial<SettingsLayoutProps>;
47
47
 
48
48
  /** Stacked below the rail's breakpoint, two columns at and above it. */
49
- function shellSx(breakpoint: SettingsLayoutProps['railBreakpoint']) {
49
+ function shellSx(breakpoint: SettingsLayoutProps['railBreakpoint'], fillHeight: boolean) {
50
50
  return (theme: Theme) => ({
51
51
  display: 'flex',
52
52
  flexDirection: 'column' as const,
@@ -54,7 +54,14 @@ function shellSx(breakpoint: SettingsLayoutProps['railBreakpoint']) {
54
54
  gap: 1.5,
55
55
  width: '100%',
56
56
  minWidth: 0,
57
- [atLeastRail(theme, breakpoint ?? 'md')]: { flexDirection: 'row', gap: 3 },
57
+ // Only ever at the wide shape. Narrow, the rail is a disclosure or the page
58
+ // itself, and two independent scrollbars on a phone is a trap rather than a
59
+ // convenience.
60
+ [atLeastRail(theme, breakpoint ?? 'md')]: {
61
+ flexDirection: 'row',
62
+ gap: 3,
63
+ ...(fillHeight ? { height: '100%', minHeight: 0 } : {}),
64
+ },
58
65
  });
59
66
  }
60
67
 
@@ -98,6 +105,7 @@ export const SettingsLayout: React.FC<SettingsLayoutProps> = (props) => {
98
105
  children,
99
106
  testIdPrefix,
100
107
  railBreakpoint,
108
+ fillHeight = false,
101
109
  navVariant,
102
110
  atIndex,
103
111
  indexHref,
@@ -117,7 +125,7 @@ export const SettingsLayout: React.FC<SettingsLayoutProps> = (props) => {
117
125
  data-testid={testIdPrefix}
118
126
  data-nav-variant={navVariant}
119
127
  data-at-index={atIndex ? 'true' : 'false'}
120
- sx={shellSx(railBreakpoint)}
128
+ sx={shellSx(railBreakpoint, fillHeight)}
121
129
  >
122
130
  <SettingsRail
123
131
  title={title}
@@ -138,6 +146,7 @@ export const SettingsLayout: React.FC<SettingsLayoutProps> = (props) => {
138
146
  variant={navVariant}
139
147
  breakpoint={railBreakpoint}
140
148
  atIndex={atIndex}
149
+ fillHeight={fillHeight}
141
150
  />
142
151
 
143
152
  <SettingsPanel
@@ -152,6 +161,7 @@ export const SettingsLayout: React.FC<SettingsLayoutProps> = (props) => {
152
161
  linkComponent={linkComponent}
153
162
  onSelectItem={onSelectItem}
154
163
  testIdPrefix={testIdPrefix}
164
+ fillHeight={fillHeight}
155
165
  >
156
166
  {children}
157
167
  </SettingsPanel>
@@ -146,6 +146,25 @@ export interface SettingsLayoutProps {
146
146
  /** Prefix for `data-testid` attributes. Defaults to `"settings"`. */
147
147
  testIdPrefix?: string;
148
148
  /** Width at which the rail takes its own column. Defaults to `"md"`. */
149
+ /**
150
+ * Give the rail and the panel a scrollbar EACH, instead of scrolling the
151
+ * page (default `false`).
152
+ *
153
+ * Off by default because it only works where the host has already given this
154
+ * component a definite height — a `100dvh` flex column with the settings area
155
+ * as a `flex: 1; min-height: 0` child is the usual shape. Turned on without
156
+ * that, the percentage heights below resolve against `auto` and the layout
157
+ * collapses, which is why an opt-in beats a clever guess.
158
+ *
159
+ * What it buys, in a settings area with twenty-odd categories: the rail stops
160
+ * scrolling away when you read down a long panel, so the thing you navigate
161
+ * with is still there when you want it. It also makes the PANEL the scroll
162
+ * container, which is what a `position: sticky` toolbar inside `children`
163
+ * needs in order to pin below the host's header rather than at the top of the
164
+ * document.
165
+ */
166
+ fillHeight?: boolean;
167
+
149
168
  railBreakpoint?: SettingsRailBreakpoint;
150
169
  /** Narrow-width navigation shape. Defaults to `"switcher"`. */
151
170
  navVariant?: SettingsNavVariant;
@@ -1,16 +1,21 @@
1
- 'use client';
1
+ "use client";
2
2
 
3
- import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
4
- import { Box } from '@mui/material';
5
- import React from 'react';
3
+ import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
4
+ import type { CSSObject, Theme } from "@mui/material";
5
+ import { Box } from "@mui/material";
6
+ import React from "react";
6
7
 
7
- import { displayAcrossRail, TOUCH_TARGET } from './SettingsLayout.styles';
8
- import { SettingsSectionChips } from './SettingsSectionChips';
8
+ import {
9
+ atLeastRail,
10
+ displayAcrossRail,
11
+ TOUCH_TARGET,
12
+ } from "./SettingsLayout.styles";
13
+ import { SettingsSectionChips } from "./SettingsSectionChips";
9
14
  import type {
10
15
  SettingsLayoutProps,
11
16
  SettingsNavItem,
12
17
  SettingsRailBreakpoint,
13
- } from './SettingsLayout.types';
18
+ } from "./SettingsLayout.types";
14
19
 
15
20
  export interface SettingsPanelProps {
16
21
  /** True in `drilldown`, inside a section rather than at the index. */
@@ -23,10 +28,12 @@ export interface SettingsPanelProps {
23
28
  ariaLabel: string;
24
29
  sectionChips?: SettingsNavItem[];
25
30
  activeItemId?: string;
26
- linkComponent?: SettingsLayoutProps['linkComponent'];
31
+ linkComponent?: SettingsLayoutProps["linkComponent"];
27
32
  onSelectItem?: (id: string) => void;
28
33
  testIdPrefix: string;
29
34
  children: React.ReactNode;
35
+ /** Scroll inside the panel rather than with the page. See `fillHeight`. */
36
+ fillHeight?: boolean;
30
37
  }
31
38
 
32
39
  /** The way back to the area's index — narrow widths only. */
@@ -40,7 +47,7 @@ function BackLink({
40
47
  href: string;
41
48
  label: string;
42
49
  breakpoint: SettingsRailBreakpoint;
43
- linkComponent: NonNullable<SettingsLayoutProps['linkComponent']>;
50
+ linkComponent: NonNullable<SettingsLayoutProps["linkComponent"]>;
44
51
  testIdPrefix: string;
45
52
  }): React.JSX.Element {
46
53
  return (
@@ -50,15 +57,15 @@ function BackLink({
50
57
  aria-label={label}
51
58
  data-testid={`${testIdPrefix}-back`}
52
59
  sx={(theme) => ({
53
- ...displayAcrossRail(theme, breakpoint, 'inline-flex', 'none'),
54
- alignItems: 'center',
60
+ ...displayAcrossRail(theme, breakpoint, "inline-flex", "none"),
61
+ alignItems: "center",
55
62
  gap: 0.5,
56
63
  minHeight: TOUCH_TARGET,
57
64
  pr: 1,
58
- textDecoration: 'none',
59
- color: 'text.secondary',
60
- font: 'inherit',
61
- fontSize: '0.875rem',
65
+ textDecoration: "none",
66
+ color: "text.secondary",
67
+ font: "inherit",
68
+ fontSize: "0.875rem",
62
69
  })}
63
70
  >
64
71
  <ChevronLeftIcon fontSize="small" />
@@ -67,6 +74,44 @@ function BackLink({
67
74
  );
68
75
  }
69
76
 
77
+ /**
78
+ * The panel's own box, as ONE object per media query rather than two.
79
+ *
80
+ * Both the `display` rule and `fillHeight`'s belong at the same wide-shape
81
+ * query, and spreading them as separate `{ [query]: … }` objects makes the
82
+ * second REPLACE the first — so turning `fillHeight` on silently dropped the
83
+ * `display: block` that `hideOnNarrow` puts there and left the panel
84
+ * `display: none` at every width. That only shows at a drilldown INDEX, where
85
+ * `hideOnNarrow` is true, which is why a section route looked fine.
86
+ *
87
+ * `overflowY` here is also what lets a `position: sticky` toolbar inside
88
+ * `children` pin to the TOP OF THE PANEL — under the host's header — instead
89
+ * of to a document that is not the scroller.
90
+ */
91
+ function panelSx(
92
+ breakpoint: SettingsRailBreakpoint,
93
+ hideOnNarrow: boolean,
94
+ fillHeight: boolean,
95
+ ): (theme: Theme) => CSSObject {
96
+ return (theme) => ({
97
+ display: hideOnNarrow ? "none" : "block",
98
+ flex: "1 1 auto",
99
+ minWidth: 0,
100
+ width: "100%",
101
+ [atLeastRail(theme, breakpoint)]: {
102
+ ...(hideOnNarrow ? { display: "block" } : {}),
103
+ ...(fillHeight
104
+ ? {
105
+ height: "100%",
106
+ minHeight: 0,
107
+ overflowY: "auto",
108
+ overscrollBehavior: "contain",
109
+ }
110
+ : {}),
111
+ },
112
+ });
113
+ }
114
+
70
115
  /**
71
116
  * The central column: the open screen, and — in `drilldown`, inside a section —
72
117
  * the back link and sibling-section strip above it.
@@ -88,22 +133,18 @@ export function SettingsPanel({
88
133
  linkComponent,
89
134
  onSelectItem,
90
135
  testIdPrefix,
136
+ fillHeight = false,
91
137
  children,
92
138
  }: SettingsPanelProps): React.JSX.Element {
93
- const showBack = inSection && indexHref !== undefined && linkComponent !== undefined;
94
- const showChips = inSection && sectionChips !== undefined && sectionChips.length > 0;
139
+ const showBack =
140
+ inSection && indexHref !== undefined && linkComponent !== undefined;
141
+ const showChips =
142
+ inSection && sectionChips !== undefined && sectionChips.length > 0;
95
143
 
96
144
  return (
97
145
  <Box
98
146
  data-testid={`${testIdPrefix}-panel`}
99
- sx={(theme) => ({
100
- ...(hideOnNarrow
101
- ? displayAcrossRail(theme, breakpoint, 'none', 'block')
102
- : { display: 'block' }),
103
- flex: '1 1 auto',
104
- minWidth: 0,
105
- width: '100%',
106
- })}
147
+ sx={panelSx(breakpoint, hideOnNarrow, fillHeight)}
107
148
  >
108
149
  {showBack ? (
109
150
  <BackLink
@@ -118,7 +159,7 @@ export function SettingsPanel({
118
159
  {showChips ? (
119
160
  <Box
120
161
  sx={(theme) => ({
121
- ...displayAcrossRail(theme, breakpoint, 'block', 'none'),
162
+ ...displayAcrossRail(theme, breakpoint, "block", "none"),
122
163
  mb: 1,
123
164
  minWidth: 0,
124
165
  })}