@12-apps/ui 5.0.1 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components-catalog.md +7 -0
- package/dist/layout/SettingsLayout.js +563 -109
- package/dist/layout/SettingsLayout.js.map +1 -1
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.d.ts +19 -2
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.d.ts.map +1 -1
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.filters.d.ts +6 -0
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.filters.d.ts.map +1 -0
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.items.d.ts +31 -0
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.items.d.ts.map +1 -0
- package/dist/types/components/layout/SettingsLayout/{SettingsLayout.parts.d.ts → SettingsLayout.rail.d.ts} +6 -9
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.rail.d.ts.map +1 -0
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.styles.d.ts +35 -0
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.styles.d.ts.map +1 -0
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.types.d.ts +109 -0
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.types.d.ts.map +1 -1
- package/dist/types/components/layout/SettingsLayout/SettingsPanel.d.ts +29 -0
- package/dist/types/components/layout/SettingsLayout/SettingsPanel.d.ts.map +1 -0
- package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.d.ts +26 -0
- package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.d.ts.map +1 -0
- package/dist/types/components/layout/SettingsLayout/SettingsStatusMarker.d.ts +28 -0
- package/dist/types/components/layout/SettingsLayout/SettingsStatusMarker.d.ts.map +1 -0
- package/dist/types/components/layout/SettingsLayout/index.d.ts +5 -1
- package/dist/types/components/layout/SettingsLayout/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/layout/SettingsLayout/SettingsLayout.filters.ts +21 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.items.tsx +228 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.md +93 -1
- package/src/components/layout/SettingsLayout/SettingsLayout.rail.tsx +279 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.styles.ts +64 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.tsx +118 -25
- package/src/components/layout/SettingsLayout/SettingsLayout.types.ts +113 -0
- package/src/components/layout/SettingsLayout/SettingsPanel.tsx +140 -0
- package/src/components/layout/SettingsLayout/SettingsSectionChips.tsx +0 -0
- package/src/components/layout/SettingsLayout/SettingsStatusMarker.tsx +75 -0
- package/src/components/layout/SettingsLayout/index.ts +8 -0
- package/dist/types/components/layout/SettingsLayout/SettingsLayout.parts.d.ts.map +0 -1
- package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +0 -358
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/layout/SettingsLayout/SettingsLayout.tsx","../../src/components/layout/SettingsLayout/SettingsLayout.parts.tsx"],"sourcesContent":["'use client';\n\nimport { Box } from '@mui/material';\nimport React, { useMemo, useState } from 'react';\n\nimport { activeItemLabel, filterGroups, SettingsRail } from './SettingsLayout.parts';\nimport type { SettingsLayoutProps } from './SettingsLayout.types';\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 * Below `md` the rail collapses into a compact section-switcher (tap to reveal the\n * search + groups) so the panel content stays at the top on phones.\n */\nexport const SettingsLayout: React.FC<SettingsLayoutProps> = ({\n title,\n groups,\n activeItemId,\n onSelectItem,\n searchPlaceholder = 'Search settings',\n emptySearchLabel = 'No settings match your search.',\n linkComponent,\n children,\n testIdPrefix = 'settings',\n}) => {\n const [query, setQuery] = useState('');\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 return (\n <Box\n data-testid={testIdPrefix}\n sx={{\n display: 'flex',\n flexDirection: { xs: 'column', md: 'row' },\n alignItems: 'stretch',\n gap: { xs: 1.5, md: 3 },\n width: '100%',\n }}\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 filteredGroups={filteredGroups}\n activeItemId={activeItemId}\n linkComponent={linkComponent}\n onSelectItem={onSelectItem}\n onNavigate={() => setNavOpen(false)}\n testIdPrefix={testIdPrefix}\n />\n\n <Box data-testid={`${testIdPrefix}-panel`} sx={{ flex: '1 1 auto', minWidth: 0, width: '100%' }}>\n {children}\n </Box>\n </Box>\n );\n};\n\nSettingsLayout.displayName = 'SettingsLayout';\n","'use client';\n\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport SearchIcon from '@mui/icons-material/Search';\nimport {\n alpha,\n Box,\n InputAdornment,\n List,\n ListItemButton,\n ListItemIcon,\n ListItemText,\n TextField,\n Typography,\n useTheme,\n} from '@mui/material';\nimport React from 'react';\n\nimport type { SettingsLayoutProps, SettingsNavGroup, SettingsNavItem } from './SettingsLayout.types';\n\n/** Fixed width of the left navigation rail on `md`+ screens. */\nconst RAIL_WIDTH = 300;\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 mobile section-switcher 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\ninterface RailItemProps {\n item: SettingsNavItem;\n active: boolean;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n /** Called on activation so the mobile rail can collapse after a choice. */\n onNavigate?: () => void;\n testIdPrefix: string;\n}\n\n/** One rail entry — rendered as a link when it has `href`, else a button. */\nfunction SettingsRailItem({\n item,\n active,\n linkComponent,\n onSelectItem,\n onNavigate,\n testIdPrefix,\n}: RailItemProps): React.JSX.Element {\n const theme = useTheme();\n const shared = {\n selected: active,\n 'aria-current': active ? ('page' as const) : undefined,\n 'data-testid': `${testIdPrefix}-item-${item.id}`,\n sx: {\n borderRadius: 1.5,\n mb: 0.25,\n gap: 1.5,\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 = (\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 </>\n );\n\n if (item.href && linkComponent) {\n const LinkComponent = linkComponent;\n return (\n <ListItemButton component={LinkComponent} href={item.href} onClick={onNavigate} {...shared}>\n {content}\n </ListItemButton>\n );\n }\n return (\n <ListItemButton\n onClick={() => {\n onSelectItem?.(item.id);\n onNavigate?.();\n }}\n {...shared}\n >\n {content}\n </ListItemButton>\n );\n}\n\ninterface RailGroupProps {\n group: SettingsNavGroup;\n activeItemId?: string;\n linkComponent?: SettingsLayoutProps['linkComponent'];\n onSelectItem?: (id: string) => void;\n onNavigate?: () => void;\n testIdPrefix: string;\n}\n\n/** A category: header + optional description + its items. */\nfunction SettingsRailGroup({\n group,\n activeItemId,\n linkComponent,\n onSelectItem,\n onNavigate,\n testIdPrefix,\n}: RailGroupProps): React.JSX.Element {\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 disablePadding sx={{ mt: 0.5 }}>\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 />\n ))}\n </List>\n </Box>\n );\n}\n\ninterface RailToggleProps {\n title?: React.ReactNode;\n activeLabel?: string;\n open: boolean;\n onToggle: () => void;\n testIdPrefix: string;\n}\n\n/**\n * Mobile-only collapse control: shows the current section (a compact switcher)\n * so the panel content stays at the top of the screen. Hidden on `md`+.\n */\nfunction SettingsRailToggle({\n title,\n activeLabel,\n open,\n onToggle,\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={{\n display: { xs: 'flex', md: 'none' },\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 1,\n width: '100%',\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: (theme) => `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 }}\n />\n </Box>\n );\n}\n\ninterface RailBodyProps {\n navOpen: boolean;\n query: string;\n onQueryChange: (value: string) => void;\n searchPlaceholder: string;\n emptySearchLabel: React.ReactNode;\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); hidden on mobile until expanded. */\nfunction SettingsRailBody({\n navOpen,\n query,\n onQueryChange,\n searchPlaceholder,\n emptySearchLabel,\n filteredGroups,\n activeItemId,\n linkComponent,\n onSelectItem,\n onNavigate,\n testIdPrefix,\n}: RailBodyProps): React.JSX.Element {\n return (\n <Box\n data-testid={`${testIdPrefix}-rail-body`}\n sx={{ display: { xs: navOpen ? 'block' : 'none', md: 'block' } }}\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 <Typography\n variant=\"body2\"\n color=\"text.secondary\"\n data-testid={`${testIdPrefix}-empty`}\n sx={{ px: 1, py: 2 }}\n >\n {emptySearchLabel}\n </Typography>\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 />\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 left navigation column: desktop title, mobile switcher, 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 theme = useTheme();\n return (\n <Box\n component=\"nav\"\n aria-label={typeof title === 'string' ? title : 'Settings'}\n data-testid={`${testIdPrefix}-rail`}\n sx={{\n flex: { xs: '0 0 auto', md: `0 0 ${RAIL_WIDTH}px` },\n width: { xs: '100%', md: RAIL_WIDTH },\n borderRight: { md: `1px solid ${theme.palette.divider}` },\n pr: { md: 2 },\n }}\n >\n {title ? (\n <Typography\n variant=\"h6\"\n data-testid={`${testIdPrefix}-title`}\n sx={{ display: { xs: 'none', md: 'block' }, fontWeight: 700, mb: 1.5 }}\n >\n {title}\n </Typography>\n ) : null}\n <SettingsRailToggle\n title={title}\n activeLabel={activeLabel}\n open={navOpen}\n onToggle={onToggleNav}\n testIdPrefix={testIdPrefix}\n />\n <SettingsRailBody navOpen={navOpen} testIdPrefix={testIdPrefix} {...bodyProps} />\n </Box>\n );\n}\n"],"mappings":"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","names":["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']) {\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 */\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 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"]}
|
|
@@ -6,8 +6,25 @@ import type { SettingsLayoutProps } from './SettingsLayout.types';
|
|
|
6
6
|
* configuration screen (`children`). Navigation is data-driven via `groups`; the
|
|
7
7
|
* host owns routing (pass `linkComponent` + item `href`, or handle `onSelectItem`).
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* ## Two narrow-width shapes
|
|
10
|
+
*
|
|
11
|
+
* `navVariant="switcher"` (the default) folds the rail into a disclosure above
|
|
12
|
+
* the panel. `navVariant="drilldown"` gives the phone shape instead: at the
|
|
13
|
+
* area's index the LIST is the page, and inside a section the panel is the page
|
|
14
|
+
* with a back link and a scrollable strip of sibling sections above it.
|
|
15
|
+
*
|
|
16
|
+
* ## Why the width decision is CSS and not JavaScript
|
|
17
|
+
*
|
|
18
|
+
* Every part that changes between the two shapes does it through a `display`
|
|
19
|
+
* pair keyed on `railBreakpoint`. Nothing here asks a media query in JS and
|
|
20
|
+
* renders one branch.
|
|
21
|
+
*
|
|
22
|
+
* That is not a performance preference. A layout that RENDERS one navigation and
|
|
23
|
+
* not the other can offer the narrow width less than the wide one, and no test
|
|
24
|
+
* or review catches it — the missing control simply is not in the tree to be
|
|
25
|
+
* asserted about. With both forms always mounted, "the narrow width reaches
|
|
26
|
+
* everything the wide one does" holds by construction, and an audit that counts
|
|
27
|
+
* controls per width gets the same answer at both.
|
|
11
28
|
*/
|
|
12
29
|
export declare const SettingsLayout: React.FC<SettingsLayoutProps>;
|
|
13
30
|
//# sourceMappingURL=SettingsLayout.d.ts.map
|
|
@@ -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;
|
|
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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SettingsNavGroup } from './SettingsLayout.types';
|
|
2
|
+
/** Case-insensitive filter over item label + keywords; drops emptied groups. */
|
|
3
|
+
export declare function filterGroups(groups: SettingsNavGroup[], query: string): SettingsNavGroup[];
|
|
4
|
+
/** Label of the active item, shown in the `switcher`'s collapsed header. */
|
|
5
|
+
export declare function activeItemLabel(groups: SettingsNavGroup[], activeItemId?: string): string | undefined;
|
|
6
|
+
//# sourceMappingURL=SettingsLayout.filters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SettingsLayout.filters.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.filters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAmB,MAAM,wBAAwB,CAAC;AAEhF,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAS1F;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAC7B,MAAM,EAAE,gBAAgB,EAAE,EAC1B,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,GAAG,SAAS,CAEpB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SettingsLayoutProps, SettingsNavGroup, SettingsNavItem, SettingsNavVariant, SettingsRailBreakpoint } from './SettingsLayout.types';
|
|
3
|
+
/** The shape-deciding inputs every rail part needs to know about. */
|
|
4
|
+
export interface RailShape {
|
|
5
|
+
variant: SettingsNavVariant;
|
|
6
|
+
breakpoint: SettingsRailBreakpoint;
|
|
7
|
+
atIndex: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface RailItemProps extends RailShape {
|
|
10
|
+
item: SettingsNavItem;
|
|
11
|
+
active: boolean;
|
|
12
|
+
linkComponent?: SettingsLayoutProps['linkComponent'];
|
|
13
|
+
onSelectItem?: (id: string) => void;
|
|
14
|
+
/** Called on activation so the `switcher`'s rail can collapse after a choice. */
|
|
15
|
+
onNavigate?: () => void;
|
|
16
|
+
testIdPrefix: string;
|
|
17
|
+
}
|
|
18
|
+
/** One rail entry — a link when it has `href`, inert text when it says so, else a button. */
|
|
19
|
+
export declare function SettingsRailItem({ item, active, linkComponent, onSelectItem, onNavigate, testIdPrefix, variant, breakpoint, }: RailItemProps): React.JSX.Element;
|
|
20
|
+
interface RailGroupProps extends RailShape {
|
|
21
|
+
group: SettingsNavGroup;
|
|
22
|
+
activeItemId?: string;
|
|
23
|
+
linkComponent?: SettingsLayoutProps['linkComponent'];
|
|
24
|
+
onSelectItem?: (id: string) => void;
|
|
25
|
+
onNavigate?: () => void;
|
|
26
|
+
testIdPrefix: string;
|
|
27
|
+
}
|
|
28
|
+
/** A category: header + optional description + its items. */
|
|
29
|
+
export declare function SettingsRailGroup({ group, activeItemId, linkComponent, onSelectItem, onNavigate, testIdPrefix, ...shape }: RailGroupProps): React.JSX.Element;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=SettingsLayout.items.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SettingsLayout.items.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.items.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,wBAAwB,CAAC;AAEhC,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,EAAE,sBAAsB,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,aAAc,SAAQ,SAAS;IACvC,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAgCD,6FAA6F;AAC7F,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,MAAM,EACN,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,OAAO,EACP,UAAU,GACX,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAkEnC;AAED,UAAU,cAAe,SAAQ,SAAS;IACxC,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAoBD,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAyCpC"}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
/** Label of the active item, shown in the mobile section-switcher header. */
|
|
6
|
-
export declare function activeItemLabel(groups: SettingsNavGroup[], activeItemId?: string): string | undefined;
|
|
7
|
-
interface RailBodyProps {
|
|
2
|
+
import { type RailShape } from './SettingsLayout.items';
|
|
3
|
+
import type { SettingsEmptySearchAction, SettingsLayoutProps, SettingsNavGroup } from './SettingsLayout.types';
|
|
4
|
+
export interface RailBodyProps extends RailShape {
|
|
8
5
|
navOpen: boolean;
|
|
9
6
|
query: string;
|
|
10
7
|
onQueryChange: (value: string) => void;
|
|
11
8
|
searchPlaceholder: string;
|
|
12
9
|
emptySearchLabel: React.ReactNode;
|
|
10
|
+
emptySearchAction?: SettingsEmptySearchAction;
|
|
13
11
|
filteredGroups: SettingsNavGroup[];
|
|
14
12
|
activeItemId?: string;
|
|
15
13
|
linkComponent?: SettingsLayoutProps['linkComponent'];
|
|
@@ -23,7 +21,6 @@ export type SettingsRailProps = RailBodyProps & {
|
|
|
23
21
|
activeLabel?: string;
|
|
24
22
|
onToggleNav: () => void;
|
|
25
23
|
};
|
|
26
|
-
/** The
|
|
24
|
+
/** The navigation column: title, the `switcher`'s disclosure, and the body. */
|
|
27
25
|
export declare function SettingsRail({ title, activeLabel, navOpen, onToggleNav, testIdPrefix, ...bodyProps }: SettingsRailProps): React.JSX.Element;
|
|
28
|
-
|
|
29
|
-
//# sourceMappingURL=SettingsLayout.parts.d.ts.map
|
|
26
|
+
//# sourceMappingURL=SettingsLayout.rail.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CSSObject, Theme } from '@mui/material';
|
|
2
|
+
import type { SettingsNavStatus, SettingsRailBreakpoint } from './SettingsLayout.types';
|
|
3
|
+
/** Fixed width of the left navigation rail once it has its own column. */
|
|
4
|
+
export declare const RAIL_WIDTH = 300;
|
|
5
|
+
/** Visually hidden, still read aloud — for text a marker's colour cannot carry. */
|
|
6
|
+
export declare const SR_ONLY_SX: CSSObject;
|
|
7
|
+
/**
|
|
8
|
+
* The media query for "the rail has its own column".
|
|
9
|
+
*
|
|
10
|
+
* `theme.breakpoints.up` takes a NUMBER as happily as a name, which is why
|
|
11
|
+
* `railBreakpoint` accepts one: a settings area whose structural switch is at
|
|
12
|
+
* 1024px has no MUI breakpoint to name (the default `lg` is 1200), and rounding
|
|
13
|
+
* the design to the nearest named breakpoint moves 176px of behaviour to keep a
|
|
14
|
+
* string tidy.
|
|
15
|
+
*/
|
|
16
|
+
export declare function atLeastRail(theme: Theme, breakpoint: SettingsRailBreakpoint): string;
|
|
17
|
+
/**
|
|
18
|
+
* A `display` pair across that query: `narrow` below it, `wide` at and above.
|
|
19
|
+
*
|
|
20
|
+
* Everything that changes shape between the two navigation modes goes through
|
|
21
|
+
* this, so "which width am I?" is answered once, in CSS, for every part at
|
|
22
|
+
* once — rather than by each part asking a media query in JavaScript and
|
|
23
|
+
* answering slightly differently.
|
|
24
|
+
*/
|
|
25
|
+
export declare function displayAcrossRail(theme: Theme, breakpoint: SettingsRailBreakpoint, narrow: string, wide: string): CSSObject;
|
|
26
|
+
/** Minimum touch target, in px. Held down to 320px-wide phones, deliberately. */
|
|
27
|
+
export declare const TOUCH_TARGET = 44;
|
|
28
|
+
/**
|
|
29
|
+
* Palette role per situation.
|
|
30
|
+
*
|
|
31
|
+
* Returned as theme keys rather than hex so a marker follows the host's theme —
|
|
32
|
+
* and so light and dark do not need two copies of this table.
|
|
33
|
+
*/
|
|
34
|
+
export declare const STATUS_COLOR: Record<Exclude<SettingsNavStatus, 'locked'>, string>;
|
|
35
|
+
//# sourceMappingURL=SettingsLayout.styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SettingsLayout.styles.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsLayout.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAExF,0EAA0E;AAC1E,eAAO,MAAM,UAAU,MAAM,CAAC;AAE9B,mFAAmF;AACnF,eAAO,MAAM,UAAU,EAAE,SAUxB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,sBAAsB,GAAG,MAAM,CAEpF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,sBAAsB,EAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,SAAS,CAEX;AAED,iFAAiF;AACjF,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,iBAAiB,EAAE,QAAQ,CAAC,EAAE,MAAM,CAI7E,CAAC"}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import type { ElementType, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* How an entry is doing, as a marker beside its label.
|
|
4
|
+
*
|
|
5
|
+
* The host RESOLVES this — the rail never derives it from the screen behind the
|
|
6
|
+
* row. A settings shell cannot know whether "Endereço" counts as configured, and
|
|
7
|
+
* a rail that guesses disagrees with the screen the moment the rule moves.
|
|
8
|
+
*
|
|
9
|
+
* - `ok` — set up and working.
|
|
10
|
+
* - `off` — exists, switched off.
|
|
11
|
+
* - `new` — never opened.
|
|
12
|
+
* - `locked` — the plan does not include it, so it has no configuration state at
|
|
13
|
+
* all. Rendered as a padlock rather than a coloured dot for exactly that
|
|
14
|
+
* reason: a locked row is not "off", and colouring it grey would say it is.
|
|
15
|
+
*/
|
|
16
|
+
export type SettingsNavStatus = 'ok' | 'off' | 'new' | 'locked';
|
|
2
17
|
/**
|
|
3
18
|
* A single navigable configuration entry inside a {@link SettingsNavGroup}. The
|
|
4
19
|
* `id` drives the active highlight and `data-testid`; `label` (plus optional
|
|
@@ -15,6 +30,23 @@ export interface SettingsNavItem {
|
|
|
15
30
|
href?: string;
|
|
16
31
|
/** Extra search terms so an item is findable beyond its visible label. */
|
|
17
32
|
keywords?: string[];
|
|
33
|
+
/** Host-resolved situation marker. Omit to render no marker at all. */
|
|
34
|
+
status?: SettingsNavStatus;
|
|
35
|
+
/**
|
|
36
|
+
* What the marker MEANS, in the host's language — "Ligado", "Não visitado",
|
|
37
|
+
* "Incluído no plano Pro". Rendered as text only a screen reader reaches, so
|
|
38
|
+
* colour is never the sole carrier of the meaning. Required alongside
|
|
39
|
+
* `status`: a marker nobody can name is decoration.
|
|
40
|
+
*/
|
|
41
|
+
statusLabel?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Render the entry as inert text rather than a control.
|
|
44
|
+
*
|
|
45
|
+
* For a row that is listed but not reachable from here. It is deliberately not
|
|
46
|
+
* a disabled button: disabled says "you cannot", and the honest statement is
|
|
47
|
+
* "not from this screen".
|
|
48
|
+
*/
|
|
49
|
+
inert?: boolean;
|
|
18
50
|
}
|
|
19
51
|
/** A labelled category grouping related items in the left rail. */
|
|
20
52
|
export interface SettingsNavGroup {
|
|
@@ -27,6 +59,40 @@ export interface SettingsNavGroup {
|
|
|
27
59
|
/** The subcategories/items in this group. */
|
|
28
60
|
items: SettingsNavItem[];
|
|
29
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Which width the rail appears at — and, in `drilldown`, where the whole shell
|
|
64
|
+
* changes shape. Below it the list is the page and a chip strip carries the
|
|
65
|
+
* sibling sections; at and above it the rail takes the left column.
|
|
66
|
+
*
|
|
67
|
+
* A raw pixel number is as valid as a breakpoint name, because a settings area's
|
|
68
|
+
* structural switch is a design decision and not always one of five named
|
|
69
|
+
* widths — 1024px, the tablet-landscape switch, is nowhere in MUI's defaults
|
|
70
|
+
* (`lg` is 1200), and rounding to the nearest name would move 176px of
|
|
71
|
+
* behaviour to keep a string tidy.
|
|
72
|
+
*/
|
|
73
|
+
export type SettingsRailBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | number;
|
|
74
|
+
/**
|
|
75
|
+
* How navigation behaves BELOW {@link SettingsLayoutProps.railBreakpoint}.
|
|
76
|
+
*
|
|
77
|
+
* - `switcher` (default) — the rail folds into a collapsed disclosure stacked
|
|
78
|
+
* above the panel. Backwards-compatible, and right for a shell whose panel is
|
|
79
|
+
* always the point.
|
|
80
|
+
* - `drilldown` — list first, then screen, with a way back. At the area's index
|
|
81
|
+
* the LIST is the page; inside a section the panel is the page and a scrollable
|
|
82
|
+
* chip strip carries its siblings. This is what every mobile settings app
|
|
83
|
+
* does, and what fits at 375px, where a 300px rail would leave 75px of content.
|
|
84
|
+
*/
|
|
85
|
+
export type SettingsNavVariant = 'switcher' | 'drilldown';
|
|
86
|
+
/** The exit offered inside the empty-search state, so the dead end has a door. */
|
|
87
|
+
export interface SettingsEmptySearchAction {
|
|
88
|
+
/** Button label, e.g. `"Limpar a busca"`. */
|
|
89
|
+
label: ReactNode;
|
|
90
|
+
/**
|
|
91
|
+
* Extra work to do on top of clearing the field. The layout always clears its
|
|
92
|
+
* own query; a host that mirrors the term elsewhere clears that here.
|
|
93
|
+
*/
|
|
94
|
+
onClear?: () => void;
|
|
95
|
+
}
|
|
30
96
|
/**
|
|
31
97
|
* Props for {@link SettingsLayout} — an agnostic, data-driven two-pane settings
|
|
32
98
|
* shell (search + grouped left rail, central panel). It renders navigation from
|
|
@@ -43,8 +109,25 @@ export interface SettingsLayoutProps {
|
|
|
43
109
|
onSelectItem?: (id: string) => void;
|
|
44
110
|
/** Placeholder for the search field. Defaults to `"Search settings"`. */
|
|
45
111
|
searchPlaceholder?: string;
|
|
112
|
+
/**
|
|
113
|
+
* The term the search field opens with. Uncontrolled — the layout owns the
|
|
114
|
+
* field from there, and typing (or the empty state's exit) changes it.
|
|
115
|
+
*
|
|
116
|
+
* For a host that deep-links into its own settings search (`?q=…`), and for a
|
|
117
|
+
* story or a test that needs to OPEN in a filtered — or empty — state rather
|
|
118
|
+
* than type its way there. A screen named for a state it cannot be opened in
|
|
119
|
+
* is a screen nobody looks at in that state.
|
|
120
|
+
*/
|
|
121
|
+
defaultQuery?: string;
|
|
46
122
|
/** Shown when the search matches no items. Defaults to a generic string. */
|
|
47
123
|
emptySearchLabel?: ReactNode;
|
|
124
|
+
/**
|
|
125
|
+
* A way OUT of the empty-search state, rendered inside it.
|
|
126
|
+
*
|
|
127
|
+
* Without one the search that matches nothing is a screen with no next step —
|
|
128
|
+
* the operator's only move is to guess which characters to delete.
|
|
129
|
+
*/
|
|
130
|
+
emptySearchAction?: SettingsEmptySearchAction;
|
|
48
131
|
/**
|
|
49
132
|
* Element type used to render an item that has an `href` (e.g. a Next.js
|
|
50
133
|
* `Link`). Rendered as the `ListItemButton` `component`, so navigation stays a
|
|
@@ -55,5 +138,31 @@ export interface SettingsLayoutProps {
|
|
|
55
138
|
children: ReactNode;
|
|
56
139
|
/** Prefix for `data-testid` attributes. Defaults to `"settings"`. */
|
|
57
140
|
testIdPrefix?: string;
|
|
141
|
+
/** Width at which the rail takes its own column. Defaults to `"md"`. */
|
|
142
|
+
railBreakpoint?: SettingsRailBreakpoint;
|
|
143
|
+
/** Narrow-width navigation shape. Defaults to `"switcher"`. */
|
|
144
|
+
navVariant?: SettingsNavVariant;
|
|
145
|
+
/**
|
|
146
|
+
* True when the router is at the area's index rather than inside a section.
|
|
147
|
+
*
|
|
148
|
+
* `drilldown` only. It decides which of the two — the list or the panel — is
|
|
149
|
+
* the page below the breakpoint. Both are always in the DOM; only `display`
|
|
150
|
+
* moves, so the two widths cannot drift apart in what they offer.
|
|
151
|
+
*/
|
|
152
|
+
atIndex?: boolean;
|
|
153
|
+
/** Where "back" goes in `drilldown`: the area's index. */
|
|
154
|
+
indexHref?: string;
|
|
155
|
+
/** Label on the back control. Defaults to `"Back"`. */
|
|
156
|
+
backLabel?: string;
|
|
157
|
+
/**
|
|
158
|
+
* The sibling sections carried by the narrow-width chip strip — usually the
|
|
159
|
+
* open item's own group.
|
|
160
|
+
*
|
|
161
|
+
* The strip lives in the DOM at EVERY width and is hidden by CSS above the
|
|
162
|
+
* breakpoint, alongside the rail that replaces it. That is the whole point:
|
|
163
|
+
* navigation chosen by a JS media query is navigation that can exist at one
|
|
164
|
+
* width and not the other, and nothing catches it.
|
|
165
|
+
*/
|
|
166
|
+
sectionChips?: SettingsNavItem[];
|
|
58
167
|
}
|
|
59
168
|
//# sourceMappingURL=SettingsLayout.types.d.ts.map
|
|
@@ -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;;;;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;
|
|
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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SettingsLayoutProps, SettingsNavItem, SettingsRailBreakpoint } from './SettingsLayout.types';
|
|
3
|
+
export interface SettingsPanelProps {
|
|
4
|
+
/** True in `drilldown`, inside a section rather than at the index. */
|
|
5
|
+
inSection: boolean;
|
|
6
|
+
/** True in `drilldown`, at the area's index. */
|
|
7
|
+
hideOnNarrow: boolean;
|
|
8
|
+
breakpoint: SettingsRailBreakpoint;
|
|
9
|
+
indexHref?: string;
|
|
10
|
+
backLabel: string;
|
|
11
|
+
ariaLabel: string;
|
|
12
|
+
sectionChips?: SettingsNavItem[];
|
|
13
|
+
activeItemId?: string;
|
|
14
|
+
linkComponent?: SettingsLayoutProps['linkComponent'];
|
|
15
|
+
onSelectItem?: (id: string) => void;
|
|
16
|
+
testIdPrefix: string;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The central column: the open screen, and — in `drilldown`, inside a section —
|
|
21
|
+
* the back link and sibling-section strip above it.
|
|
22
|
+
*
|
|
23
|
+
* At the index in `drilldown` this whole column is `display: none` below the
|
|
24
|
+
* breakpoint and the rail becomes the page. Mounted either way: the panel and
|
|
25
|
+
* the list are one tree with `display` between them, not two branches of a
|
|
26
|
+
* render.
|
|
27
|
+
*/
|
|
28
|
+
export declare function SettingsPanel({ inSection, hideOnNarrow, breakpoint, indexHref, backLabel, ariaLabel, sectionChips, activeItemId, linkComponent, onSelectItem, testIdPrefix, children, }: SettingsPanelProps): React.JSX.Element;
|
|
29
|
+
//# sourceMappingURL=SettingsPanel.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SettingsLayoutProps, SettingsNavItem } from './SettingsLayout.types';
|
|
3
|
+
export interface SettingsSectionChipsProps {
|
|
4
|
+
/** The sibling sections to offer — usually the open item's own group. */
|
|
5
|
+
items: SettingsNavItem[];
|
|
6
|
+
/** Which one is open. */
|
|
7
|
+
activeItemId?: string;
|
|
8
|
+
/** Accessible name of the strip, in the host's language. */
|
|
9
|
+
ariaLabel: string;
|
|
10
|
+
linkComponent?: SettingsLayoutProps['linkComponent'];
|
|
11
|
+
onSelectItem?: (id: string) => void;
|
|
12
|
+
testIdPrefix: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The narrow-width section strip: horizontally scrollable chips for the sections
|
|
16
|
+
* beside the open one.
|
|
17
|
+
*
|
|
18
|
+
* It clips its own overflow — `overflow-x: auto` on the strip, never on an
|
|
19
|
+
* ancestor. A strip that pushes the page wide makes every OTHER screen at that
|
|
20
|
+
* width scroll sideways too, and the cause is nowhere near the symptom.
|
|
21
|
+
*/
|
|
22
|
+
export declare function SettingsSectionChips({ items, activeItemId, ariaLabel, linkComponent, onSelectItem, testIdPrefix, }: SettingsSectionChipsProps): React.JSX.Element;
|
|
23
|
+
export declare namespace SettingsSectionChips {
|
|
24
|
+
var displayName: string;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=SettingsSectionChips.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SettingsSectionChips.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsSectionChips.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA4C,MAAM,OAAO,CAAC;AAIjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEnF,MAAM,WAAW,yBAAyB;IACxC,yEAAyE;IACzE,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAsGD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,YAAY,EACZ,SAAS,EACT,aAAa,EACb,YAAY,EACZ,YAAY,GACb,EAAE,yBAAyB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAoC/C;yBA3Ce,oBAAoB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SettingsNavStatus } from './SettingsLayout.types';
|
|
3
|
+
export interface SettingsStatusMarkerProps {
|
|
4
|
+
/** Which situation to draw. */
|
|
5
|
+
status: SettingsNavStatus;
|
|
6
|
+
/** What it means, in the host's language — the only carrier a screen reader gets. */
|
|
7
|
+
label?: string;
|
|
8
|
+
/** `data-testid` for the marker element. */
|
|
9
|
+
testId?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The situation marker beside a settings entry: a coloured dot, or a padlock
|
|
13
|
+
* when the plan does not include the section.
|
|
14
|
+
*
|
|
15
|
+
* The label is rendered as visually-hidden text INSIDE the marker rather than as
|
|
16
|
+
* an `aria-label` on it, so it lands in the accessible name of the row that
|
|
17
|
+
* contains it. A row reading "Endereço, ligado" is one utterance; a decorative
|
|
18
|
+
* dot with a title attribute beside it is two, and the second one is skipped.
|
|
19
|
+
*
|
|
20
|
+
* Colour alone never says which of the three states this is — that is the whole
|
|
21
|
+
* reason `label` exists, and why a marker without one is a marker with no
|
|
22
|
+
* meaning attached.
|
|
23
|
+
*/
|
|
24
|
+
export declare function SettingsStatusMarker({ status, label, testId, }: SettingsStatusMarkerProps): React.JSX.Element;
|
|
25
|
+
export declare namespace SettingsStatusMarker {
|
|
26
|
+
var displayName: string;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=SettingsStatusMarker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SettingsStatusMarker.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/SettingsStatusMarker.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,WAAW,yBAAyB;IACxC,+BAA+B;IAC/B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,KAAK,EACL,MAAM,GACP,EAAE,yBAAyB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAqC/C;yBAzCe,oBAAoB"}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export { SettingsLayout } from './SettingsLayout';
|
|
2
|
-
export
|
|
2
|
+
export { SettingsSectionChips } from './SettingsSectionChips';
|
|
3
|
+
export type { SettingsSectionChipsProps } from './SettingsSectionChips';
|
|
4
|
+
export { SettingsStatusMarker } from './SettingsStatusMarker';
|
|
5
|
+
export type { SettingsStatusMarkerProps } from './SettingsStatusMarker';
|
|
6
|
+
export type { SettingsEmptySearchAction, SettingsLayoutProps, SettingsNavGroup, SettingsNavItem, SettingsNavStatus, SettingsNavVariant, SettingsRailBreakpoint, } from './SettingsLayout.types';
|
|
3
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/SettingsLayout/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,YAAY,EACV,yBAAyB,EACzB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC"}
|