@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.
Files changed (37) hide show
  1. package/components-catalog.md +7 -0
  2. package/dist/layout/SettingsLayout.js +563 -109
  3. package/dist/layout/SettingsLayout.js.map +1 -1
  4. package/dist/types/components/layout/SettingsLayout/SettingsLayout.d.ts +19 -2
  5. package/dist/types/components/layout/SettingsLayout/SettingsLayout.d.ts.map +1 -1
  6. package/dist/types/components/layout/SettingsLayout/SettingsLayout.filters.d.ts +6 -0
  7. package/dist/types/components/layout/SettingsLayout/SettingsLayout.filters.d.ts.map +1 -0
  8. package/dist/types/components/layout/SettingsLayout/SettingsLayout.items.d.ts +31 -0
  9. package/dist/types/components/layout/SettingsLayout/SettingsLayout.items.d.ts.map +1 -0
  10. package/dist/types/components/layout/SettingsLayout/{SettingsLayout.parts.d.ts → SettingsLayout.rail.d.ts} +6 -9
  11. package/dist/types/components/layout/SettingsLayout/SettingsLayout.rail.d.ts.map +1 -0
  12. package/dist/types/components/layout/SettingsLayout/SettingsLayout.styles.d.ts +35 -0
  13. package/dist/types/components/layout/SettingsLayout/SettingsLayout.styles.d.ts.map +1 -0
  14. package/dist/types/components/layout/SettingsLayout/SettingsLayout.types.d.ts +109 -0
  15. package/dist/types/components/layout/SettingsLayout/SettingsLayout.types.d.ts.map +1 -1
  16. package/dist/types/components/layout/SettingsLayout/SettingsPanel.d.ts +29 -0
  17. package/dist/types/components/layout/SettingsLayout/SettingsPanel.d.ts.map +1 -0
  18. package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.d.ts +26 -0
  19. package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.d.ts.map +1 -0
  20. package/dist/types/components/layout/SettingsLayout/SettingsStatusMarker.d.ts +28 -0
  21. package/dist/types/components/layout/SettingsLayout/SettingsStatusMarker.d.ts.map +1 -0
  22. package/dist/types/components/layout/SettingsLayout/index.d.ts +5 -1
  23. package/dist/types/components/layout/SettingsLayout/index.d.ts.map +1 -1
  24. package/package.json +2 -2
  25. package/src/components/layout/SettingsLayout/SettingsLayout.filters.ts +21 -0
  26. package/src/components/layout/SettingsLayout/SettingsLayout.items.tsx +228 -0
  27. package/src/components/layout/SettingsLayout/SettingsLayout.md +93 -1
  28. package/src/components/layout/SettingsLayout/SettingsLayout.rail.tsx +279 -0
  29. package/src/components/layout/SettingsLayout/SettingsLayout.styles.ts +64 -0
  30. package/src/components/layout/SettingsLayout/SettingsLayout.tsx +118 -25
  31. package/src/components/layout/SettingsLayout/SettingsLayout.types.ts +113 -0
  32. package/src/components/layout/SettingsLayout/SettingsPanel.tsx +140 -0
  33. package/src/components/layout/SettingsLayout/SettingsSectionChips.tsx +0 -0
  34. package/src/components/layout/SettingsLayout/SettingsStatusMarker.tsx +75 -0
  35. package/src/components/layout/SettingsLayout/index.ts +8 -0
  36. package/dist/types/components/layout/SettingsLayout/SettingsLayout.parts.d.ts.map +0 -1
  37. package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +0 -358
@@ -1,34 +1,145 @@
1
+ import {
2
+ withDefaults
3
+ } from "../chunk-Y2JYDXL7.js";
1
4
  import "../chunk-PZ5AY32C.js";
2
5
 
3
6
  // src/components/layout/SettingsLayout/SettingsLayout.tsx
4
- import { Box as Box2 } from "@mui/material";
7
+ import { Box as Box6 } from "@mui/material";
5
8
  import { useMemo, useState } from "react";
6
9
 
7
- // src/components/layout/SettingsLayout/SettingsLayout.parts.tsx
10
+ // src/components/layout/SettingsLayout/SettingsLayout.filters.ts
11
+ function filterGroups(groups, query) {
12
+ const needle = query.trim().toLowerCase();
13
+ if (!needle) return groups;
14
+ const matches = (item) => item.label.toLowerCase().includes(needle) || (item.keywords ?? []).some((keyword) => keyword.toLowerCase().includes(needle));
15
+ return groups.map((group) => ({ ...group, items: group.items.filter(matches) })).filter((group) => group.items.length > 0);
16
+ }
17
+ function activeItemLabel(groups, activeItemId) {
18
+ return groups.flatMap((group) => group.items).find((item) => item.id === activeItemId)?.label;
19
+ }
20
+
21
+ // src/components/layout/SettingsLayout/SettingsLayout.rail.tsx
8
22
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
9
23
  import SearchIcon from "@mui/icons-material/Search";
24
+ import { Box as Box3, Button, InputAdornment, TextField, Typography as Typography2 } from "@mui/material";
25
+
26
+ // src/components/layout/SettingsLayout/SettingsLayout.items.tsx
10
27
  import {
11
28
  alpha,
12
- Box,
13
- InputAdornment,
29
+ Box as Box2,
14
30
  List,
31
+ ListItem,
15
32
  ListItemButton,
16
33
  ListItemIcon,
17
34
  ListItemText,
18
- TextField,
19
35
  Typography,
20
36
  useTheme
21
37
  } from "@mui/material";
22
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
38
+
39
+ // src/components/layout/SettingsLayout/SettingsLayout.styles.ts
23
40
  var RAIL_WIDTH = 300;
24
- function filterGroups(groups, query) {
25
- const needle = query.trim().toLowerCase();
26
- if (!needle) return groups;
27
- const matches = (item) => item.label.toLowerCase().includes(needle) || (item.keywords ?? []).some((keyword) => keyword.toLowerCase().includes(needle));
28
- return groups.map((group) => ({ ...group, items: group.items.filter(matches) })).filter((group) => group.items.length > 0);
41
+ var SR_ONLY_SX = {
42
+ position: "absolute",
43
+ width: "1px",
44
+ height: "1px",
45
+ padding: 0,
46
+ margin: "-1px",
47
+ overflow: "hidden",
48
+ clip: "rect(0, 0, 0, 0)",
49
+ whiteSpace: "nowrap",
50
+ border: 0
51
+ };
52
+ function atLeastRail(theme, breakpoint) {
53
+ return theme.breakpoints.up(breakpoint);
29
54
  }
30
- function activeItemLabel(groups, activeItemId) {
31
- return groups.flatMap((group) => group.items).find((item) => item.id === activeItemId)?.label;
55
+ function displayAcrossRail(theme, breakpoint, narrow, wide) {
56
+ return { display: narrow, [atLeastRail(theme, breakpoint)]: { display: wide } };
57
+ }
58
+ var TOUCH_TARGET = 44;
59
+ var STATUS_COLOR = {
60
+ ok: "success.main",
61
+ off: "text.disabled",
62
+ new: "error.main"
63
+ };
64
+
65
+ // src/components/layout/SettingsLayout/SettingsStatusMarker.tsx
66
+ import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
67
+ import { Box } from "@mui/material";
68
+ import { jsx, jsxs } from "react/jsx-runtime";
69
+ function SettingsStatusMarker({
70
+ status,
71
+ label,
72
+ testId
73
+ }) {
74
+ const hidden = label ? /* @__PURE__ */ jsx(Box, { component: "span", sx: SR_ONLY_SX, children: label }) : null;
75
+ if (status === "locked") {
76
+ return /* @__PURE__ */ jsxs(
77
+ Box,
78
+ {
79
+ component: "span",
80
+ "data-testid": testId,
81
+ "data-status": status,
82
+ sx: { display: "inline-flex", alignItems: "center", color: "text.disabled" },
83
+ children: [
84
+ /* @__PURE__ */ jsx(LockOutlinedIcon, { sx: { fontSize: 16 } }),
85
+ hidden
86
+ ]
87
+ }
88
+ );
89
+ }
90
+ return /* @__PURE__ */ jsxs(
91
+ Box,
92
+ {
93
+ component: "span",
94
+ "data-testid": testId,
95
+ "data-status": status,
96
+ sx: { display: "inline-flex", alignItems: "center" },
97
+ children: [
98
+ /* @__PURE__ */ jsx(
99
+ Box,
100
+ {
101
+ component: "span",
102
+ sx: {
103
+ width: 8,
104
+ height: 8,
105
+ borderRadius: "50%",
106
+ flex: "0 0 auto",
107
+ bgcolor: STATUS_COLOR[status]
108
+ }
109
+ }
110
+ ),
111
+ hidden
112
+ ]
113
+ }
114
+ );
115
+ }
116
+ SettingsStatusMarker.displayName = "SettingsStatusMarker";
117
+
118
+ // src/components/layout/SettingsLayout/SettingsLayout.items.tsx
119
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
120
+ function RailItemContent({
121
+ item,
122
+ active,
123
+ testIdPrefix
124
+ }) {
125
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
126
+ item.icon ? /* @__PURE__ */ jsx2(ListItemIcon, { sx: { minWidth: 0, color: "inherit" }, children: item.icon }) : null,
127
+ /* @__PURE__ */ jsx2(
128
+ ListItemText,
129
+ {
130
+ primary: item.label,
131
+ primaryTypographyProps: { variant: "body2", noWrap: true, fontWeight: active ? 600 : 500 }
132
+ }
133
+ ),
134
+ item.status ? /* @__PURE__ */ jsx2(
135
+ SettingsStatusMarker,
136
+ {
137
+ status: item.status,
138
+ label: item.statusLabel,
139
+ testId: `${testIdPrefix}-status-${item.id}`
140
+ }
141
+ ) : null
142
+ ] });
32
143
  }
33
144
  function SettingsRailItem({
34
145
  item,
@@ -36,17 +147,19 @@ function SettingsRailItem({
36
147
  linkComponent,
37
148
  onSelectItem,
38
149
  onNavigate,
39
- testIdPrefix
150
+ testIdPrefix,
151
+ variant,
152
+ breakpoint
40
153
  }) {
41
154
  const theme = useTheme();
155
+ const touchFloor = variant === "drilldown" ? { minHeight: TOUCH_TARGET, [atLeastRail(theme, breakpoint)]: { minHeight: "auto" } } : null;
42
156
  const shared = {
43
- selected: active,
44
- "aria-current": active ? "page" : void 0,
45
157
  "data-testid": `${testIdPrefix}-item-${item.id}`,
46
158
  sx: {
47
159
  borderRadius: 1.5,
48
160
  mb: 0.25,
49
161
  gap: 1.5,
162
+ ...touchFloor,
50
163
  color: active ? "primary.main" : "text.secondary",
51
164
  "&.Mui-selected": {
52
165
  bgcolor: alpha(theme.palette.primary.main, 0.12),
@@ -54,42 +167,62 @@ function SettingsRailItem({
54
167
  }
55
168
  }
56
169
  };
57
- const content = /* @__PURE__ */ jsxs(Fragment, { children: [
58
- item.icon ? /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 0, color: "inherit" }, children: item.icon }) : null,
59
- /* @__PURE__ */ jsx(
60
- ListItemText,
61
- {
62
- primary: item.label,
63
- primaryTypographyProps: { variant: "body2", noWrap: true, fontWeight: active ? 600 : 500 }
64
- }
65
- )
66
- ] });
170
+ const content = /* @__PURE__ */ jsx2(RailItemContent, { item, active, testIdPrefix });
171
+ if (item.inert) {
172
+ return /* @__PURE__ */ jsx2(ListItem, { ...shared, "data-inert": "true", children: content });
173
+ }
174
+ const interactive = {
175
+ ...shared,
176
+ selected: active,
177
+ "aria-current": active ? "page" : void 0
178
+ };
67
179
  if (item.href && linkComponent) {
68
180
  const LinkComponent = linkComponent;
69
- return /* @__PURE__ */ jsx(ListItemButton, { component: LinkComponent, href: item.href, onClick: onNavigate, ...shared, children: content });
181
+ return /* @__PURE__ */ jsx2(
182
+ ListItemButton,
183
+ {
184
+ component: LinkComponent,
185
+ href: item.href,
186
+ onClick: onNavigate,
187
+ ...interactive,
188
+ children: content
189
+ }
190
+ );
70
191
  }
71
- return /* @__PURE__ */ jsx(
192
+ return /* @__PURE__ */ jsx2(
72
193
  ListItemButton,
73
194
  {
74
195
  onClick: () => {
75
196
  onSelectItem?.(item.id);
76
197
  onNavigate?.();
77
198
  },
78
- ...shared,
199
+ ...interactive,
79
200
  children: content
80
201
  }
81
202
  );
82
203
  }
204
+ function indexListSx(shape) {
205
+ if (shape.variant !== "drilldown" || !shape.atIndex) return null;
206
+ return (theme) => ({
207
+ display: "grid",
208
+ gridTemplateColumns: "1fr",
209
+ columnGap: 8,
210
+ [theme.breakpoints.up("sm")]: { gridTemplateColumns: "1fr 1fr" },
211
+ [atLeastRail(theme, shape.breakpoint)]: { gridTemplateColumns: "1fr" }
212
+ });
213
+ }
83
214
  function SettingsRailGroup({
84
215
  group,
85
216
  activeItemId,
86
217
  linkComponent,
87
218
  onSelectItem,
88
219
  onNavigate,
89
- testIdPrefix
220
+ testIdPrefix,
221
+ ...shape
90
222
  }) {
91
- return /* @__PURE__ */ jsxs(Box, { "data-testid": `${testIdPrefix}-group-${group.id}`, sx: { mb: 2 }, children: [
92
- /* @__PURE__ */ jsx(
223
+ const grid = indexListSx(shape);
224
+ return /* @__PURE__ */ jsxs2(Box2, { "data-testid": `${testIdPrefix}-group-${group.id}`, sx: { mb: 2 }, children: [
225
+ /* @__PURE__ */ jsx2(
93
226
  Typography,
94
227
  {
95
228
  variant: "caption",
@@ -104,30 +237,42 @@ function SettingsRailGroup({
104
237
  children: group.label
105
238
  }
106
239
  ),
107
- group.description ? /* @__PURE__ */ jsx(Typography, { variant: "caption", component: "p", sx: { px: 1, mt: 0.25, color: "text.disabled" }, children: group.description }) : null,
108
- /* @__PURE__ */ jsx(List, { disablePadding: true, sx: { mt: 0.5 }, children: group.items.map((item) => /* @__PURE__ */ jsx(
109
- SettingsRailItem,
240
+ group.description ? /* @__PURE__ */ jsx2(Typography, { variant: "caption", component: "p", sx: { px: 1, mt: 0.25, color: "text.disabled" }, children: group.description }) : null,
241
+ /* @__PURE__ */ jsx2(
242
+ List,
110
243
  {
111
- item,
112
- active: item.id === activeItemId,
113
- linkComponent,
114
- onSelectItem,
115
- onNavigate,
116
- testIdPrefix
117
- },
118
- item.id
119
- )) })
244
+ disablePadding: true,
245
+ sx: (theme) => ({ mt: 0.5, ...grid ? grid(theme) : null }),
246
+ children: group.items.map((item) => /* @__PURE__ */ jsx2(
247
+ SettingsRailItem,
248
+ {
249
+ item,
250
+ active: item.id === activeItemId,
251
+ linkComponent,
252
+ onSelectItem,
253
+ onNavigate,
254
+ testIdPrefix,
255
+ ...shape
256
+ },
257
+ item.id
258
+ ))
259
+ }
260
+ )
120
261
  ] });
121
262
  }
263
+
264
+ // src/components/layout/SettingsLayout/SettingsLayout.rail.tsx
265
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
122
266
  function SettingsRailToggle({
123
267
  title,
124
268
  activeLabel,
125
269
  open,
126
270
  onToggle,
271
+ breakpoint,
127
272
  testIdPrefix
128
273
  }) {
129
- return /* @__PURE__ */ jsxs(
130
- Box,
274
+ return /* @__PURE__ */ jsxs3(
275
+ Box3,
131
276
  {
132
277
  component: "button",
133
278
  type: "button",
@@ -135,12 +280,13 @@ function SettingsRailToggle({
135
280
  "aria-expanded": open,
136
281
  "aria-label": typeof title === "string" ? title : "Settings",
137
282
  "data-testid": `${testIdPrefix}-toggle`,
138
- sx: {
139
- display: { xs: "flex", md: "none" },
283
+ sx: (theme) => ({
284
+ ...displayAcrossRail(theme, breakpoint, "flex", "none"),
140
285
  alignItems: "center",
141
286
  justifyContent: "space-between",
142
287
  gap: 1,
143
288
  width: "100%",
289
+ minHeight: TOUCH_TARGET,
144
290
  px: 1.5,
145
291
  py: 1,
146
292
  mb: 1,
@@ -148,15 +294,15 @@ function SettingsRailToggle({
148
294
  cursor: "pointer",
149
295
  color: "text.primary",
150
296
  background: "transparent",
151
- border: (theme) => `1px solid ${theme.palette.divider}`,
297
+ border: `1px solid ${theme.palette.divider}`,
152
298
  borderRadius: 1.5
153
- },
299
+ }),
154
300
  children: [
155
- /* @__PURE__ */ jsxs(Box, { sx: { minWidth: 0 }, children: [
156
- title ? /* @__PURE__ */ jsx(Typography, { variant: "caption", sx: { display: "block", color: "text.secondary" }, children: title }) : null,
157
- /* @__PURE__ */ jsx(Typography, { variant: "body2", noWrap: true, sx: { fontWeight: 600 }, children: activeLabel ?? "" })
301
+ /* @__PURE__ */ jsxs3(Box3, { sx: { minWidth: 0 }, children: [
302
+ title ? /* @__PURE__ */ jsx3(Typography2, { variant: "caption", sx: { display: "block", color: "text.secondary" }, children: title }) : null,
303
+ /* @__PURE__ */ jsx3(Typography2, { variant: "body2", noWrap: true, sx: { fontWeight: 600 }, children: activeLabel ?? "" })
158
304
  ] }),
159
- /* @__PURE__ */ jsx(
305
+ /* @__PURE__ */ jsx3(
160
306
  ExpandMoreIcon,
161
307
  {
162
308
  fontSize: "small",
@@ -164,7 +310,8 @@ function SettingsRailToggle({
164
310
  flex: "0 0 auto",
165
311
  color: "text.secondary",
166
312
  transition: "transform 150ms ease",
167
- transform: open ? "rotate(180deg)" : "none"
313
+ transform: open ? "rotate(180deg)" : "none",
314
+ "@media (prefers-reduced-motion: reduce)": { transition: "none" }
168
315
  }
169
316
  }
170
317
  )
@@ -172,26 +319,53 @@ function SettingsRailToggle({
172
319
  }
173
320
  );
174
321
  }
322
+ function SettingsSearchEmpty({
323
+ label,
324
+ action,
325
+ onClear,
326
+ testIdPrefix
327
+ }) {
328
+ return /* @__PURE__ */ jsxs3(Box3, { "data-testid": `${testIdPrefix}-empty`, sx: { px: 1, py: 2 }, children: [
329
+ /* @__PURE__ */ jsx3(Typography2, { variant: "body2", color: "text.secondary", children: label }),
330
+ action ? /* @__PURE__ */ jsx3(
331
+ Button,
332
+ {
333
+ size: "small",
334
+ variant: "outlined",
335
+ onClick: () => {
336
+ onClear();
337
+ action.onClear?.();
338
+ },
339
+ "data-testid": `${testIdPrefix}-empty-action`,
340
+ sx: { mt: 1.5, minHeight: TOUCH_TARGET },
341
+ children: action.label
342
+ }
343
+ ) : null
344
+ ] });
345
+ }
175
346
  function SettingsRailBody({
176
347
  navOpen,
177
348
  query,
178
349
  onQueryChange,
179
350
  searchPlaceholder,
180
351
  emptySearchLabel,
352
+ emptySearchAction,
181
353
  filteredGroups,
182
354
  activeItemId,
183
355
  linkComponent,
184
356
  onSelectItem,
185
357
  onNavigate,
186
- testIdPrefix
358
+ testIdPrefix,
359
+ ...shape
187
360
  }) {
188
- return /* @__PURE__ */ jsxs(
189
- Box,
361
+ const hidden = navOpen ? "block" : "none";
362
+ return /* @__PURE__ */ jsxs3(
363
+ Box3,
190
364
  {
191
365
  "data-testid": `${testIdPrefix}-rail-body`,
192
- sx: { display: { xs: navOpen ? "block" : "none", md: "block" } },
366
+ sx: (theme) => shape.variant === "drilldown" ? { display: "block" } : displayAcrossRail(theme, shape.breakpoint, hidden, "block"),
193
367
  children: [
194
- /* @__PURE__ */ jsx(
368
+ /* @__PURE__ */ jsx3(
195
369
  TextField,
196
370
  {
197
371
  value: query,
@@ -202,21 +376,20 @@ function SettingsRailBody({
202
376
  "aria-label": searchPlaceholder,
203
377
  "data-testid": `${testIdPrefix}-search`,
204
378
  InputProps: {
205
- startAdornment: /* @__PURE__ */ jsx(InputAdornment, { position: "start", children: /* @__PURE__ */ jsx(SearchIcon, { fontSize: "small", sx: { color: "text.secondary" } }) })
379
+ startAdornment: /* @__PURE__ */ jsx3(InputAdornment, { position: "start", children: /* @__PURE__ */ jsx3(SearchIcon, { fontSize: "small", sx: { color: "text.secondary" } }) })
206
380
  },
207
381
  sx: { mb: 2 }
208
382
  }
209
383
  ),
210
- filteredGroups.length === 0 ? /* @__PURE__ */ jsx(
211
- Typography,
384
+ filteredGroups.length === 0 ? /* @__PURE__ */ jsx3(
385
+ SettingsSearchEmpty,
212
386
  {
213
- variant: "body2",
214
- color: "text.secondary",
215
- "data-testid": `${testIdPrefix}-empty`,
216
- sx: { px: 1, py: 2 },
217
- children: emptySearchLabel
387
+ label: emptySearchLabel,
388
+ action: emptySearchAction,
389
+ onClear: () => onQueryChange(""),
390
+ testIdPrefix
218
391
  }
219
- ) : filteredGroups.map((group) => /* @__PURE__ */ jsx(
392
+ ) : filteredGroups.map((group) => /* @__PURE__ */ jsx3(
220
393
  SettingsRailGroup,
221
394
  {
222
395
  group,
@@ -224,7 +397,8 @@ function SettingsRailBody({
224
397
  linkComponent,
225
398
  onSelectItem,
226
399
  onNavigate,
227
- testIdPrefix
400
+ testIdPrefix,
401
+ ...shape
228
402
  },
229
403
  group.id
230
404
  ))
@@ -240,75 +414,333 @@ function SettingsRail({
240
414
  testIdPrefix,
241
415
  ...bodyProps
242
416
  }) {
243
- const theme = useTheme();
244
- return /* @__PURE__ */ jsxs(
245
- Box,
417
+ const { variant, breakpoint, atIndex } = bodyProps;
418
+ const hiddenWhileInSection = variant === "drilldown" && !atIndex;
419
+ return /* @__PURE__ */ jsxs3(
420
+ Box3,
246
421
  {
247
422
  component: "nav",
248
423
  "aria-label": typeof title === "string" ? title : "Settings",
249
424
  "data-testid": `${testIdPrefix}-rail`,
250
- sx: {
251
- flex: { xs: "0 0 auto", md: `0 0 ${RAIL_WIDTH}px` },
252
- width: { xs: "100%", md: RAIL_WIDTH },
253
- borderRight: { md: `1px solid ${theme.palette.divider}` },
254
- pr: { md: 2 }
255
- },
425
+ sx: (theme) => ({
426
+ display: hiddenWhileInSection ? "none" : "block",
427
+ flex: "0 0 auto",
428
+ width: "100%",
429
+ [atLeastRail(theme, breakpoint)]: {
430
+ display: "block",
431
+ flex: `0 0 ${RAIL_WIDTH}px`,
432
+ width: RAIL_WIDTH,
433
+ borderRight: `1px solid ${theme.palette.divider}`,
434
+ pr: 2
435
+ }
436
+ }),
256
437
  children: [
257
- title ? /* @__PURE__ */ jsx(
258
- Typography,
438
+ title ? /* @__PURE__ */ jsx3(
439
+ Typography2,
259
440
  {
260
441
  variant: "h6",
261
442
  "data-testid": `${testIdPrefix}-title`,
262
- sx: { display: { xs: "none", md: "block" }, fontWeight: 700, mb: 1.5 },
443
+ sx: (theme) => ({
444
+ ...variant === "drilldown" ? { display: "block" } : displayAcrossRail(theme, breakpoint, "none", "block"),
445
+ fontWeight: 700,
446
+ mb: 1.5
447
+ }),
263
448
  children: title
264
449
  }
265
450
  ) : null,
266
- /* @__PURE__ */ jsx(
451
+ variant === "switcher" ? /* @__PURE__ */ jsx3(
267
452
  SettingsRailToggle,
268
453
  {
269
454
  title,
270
455
  activeLabel,
271
456
  open: navOpen,
272
457
  onToggle: onToggleNav,
458
+ breakpoint,
273
459
  testIdPrefix
274
460
  }
275
- ),
276
- /* @__PURE__ */ jsx(SettingsRailBody, { navOpen, testIdPrefix, ...bodyProps })
461
+ ) : null,
462
+ /* @__PURE__ */ jsx3(SettingsRailBody, { navOpen, testIdPrefix, ...bodyProps })
277
463
  ]
278
464
  }
279
465
  );
280
466
  }
281
467
 
282
- // src/components/layout/SettingsLayout/SettingsLayout.tsx
283
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
284
- var SettingsLayout = ({
285
- title,
286
- groups,
468
+ // src/components/layout/SettingsLayout/SettingsPanel.tsx
469
+ import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
470
+ import { Box as Box5 } from "@mui/material";
471
+
472
+ // src/components/layout/SettingsLayout/SettingsSectionChips.tsx
473
+ import { alpha as alpha2, Box as Box4, useTheme as useTheme2 } from "@mui/material";
474
+ import { useEffect, useRef } from "react";
475
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
476
+ function prefersReducedMotion() {
477
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
478
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
479
+ }
480
+ function useCentreActiveChip(stripRef, activeItemId, itemsKey) {
481
+ useEffect(() => {
482
+ const strip = stripRef.current;
483
+ if (!strip || !activeItemId) return;
484
+ const chip = Array.from(strip.children).find(
485
+ (node) => node instanceof HTMLElement && node.dataset.chipId === activeItemId
486
+ );
487
+ if (!chip) return;
488
+ const target = chip.offsetLeft + chip.offsetWidth / 2 - strip.clientWidth / 2;
489
+ const left = Math.max(0, Math.min(target, strip.scrollWidth - strip.clientWidth));
490
+ strip.scrollTo({ left, behavior: prefersReducedMotion() ? "auto" : "smooth" });
491
+ }, [stripRef, activeItemId, itemsKey]);
492
+ }
493
+ function SectionChip({
494
+ item,
495
+ active,
496
+ theme,
497
+ linkComponent,
498
+ onSelectItem,
499
+ testIdPrefix
500
+ }) {
501
+ const asLink = item.href !== void 0 && linkComponent !== void 0;
502
+ const behaviour = asLink ? { component: linkComponent, href: item.href } : { component: "button", type: "button", onClick: () => onSelectItem?.(item.id) };
503
+ return /* @__PURE__ */ jsxs4(
504
+ Box4,
505
+ {
506
+ ...behaviour,
507
+ "data-chip-id": item.id,
508
+ "data-testid": `${testIdPrefix}-chip-${item.id}`,
509
+ "aria-current": active ? "page" : void 0,
510
+ sx: {
511
+ display: "inline-flex",
512
+ alignItems: "center",
513
+ gap: 0.75,
514
+ flex: "0 0 auto",
515
+ minHeight: TOUCH_TARGET,
516
+ px: 1.5,
517
+ borderRadius: 999,
518
+ cursor: "pointer",
519
+ font: "inherit",
520
+ fontSize: "0.8125rem",
521
+ fontWeight: active ? 700 : 500,
522
+ textDecoration: "none",
523
+ whiteSpace: "nowrap",
524
+ color: active ? "primary.main" : "text.secondary",
525
+ bgcolor: active ? alpha2(theme.palette.primary.main, 0.12) : "transparent",
526
+ border: `1px solid ${active ? alpha2(theme.palette.primary.main, 0.4) : theme.palette.divider}`
527
+ },
528
+ children: [
529
+ item.status ? /* @__PURE__ */ jsx4(
530
+ SettingsStatusMarker,
531
+ {
532
+ status: item.status,
533
+ label: item.statusLabel,
534
+ testId: `${testIdPrefix}-chip-status-${item.id}`
535
+ }
536
+ ) : null,
537
+ item.label
538
+ ]
539
+ }
540
+ );
541
+ }
542
+ function SettingsSectionChips({
543
+ items,
287
544
  activeItemId,
545
+ ariaLabel,
546
+ linkComponent,
288
547
  onSelectItem,
289
- searchPlaceholder = "Search settings",
290
- emptySearchLabel = "No settings match your search.",
548
+ testIdPrefix
549
+ }) {
550
+ const theme = useTheme2();
551
+ const stripRef = useRef(null);
552
+ useCentreActiveChip(stripRef, activeItemId, items.map((item) => item.id).join("\0"));
553
+ return /* @__PURE__ */ jsx4(
554
+ Box4,
555
+ {
556
+ ref: stripRef,
557
+ component: "nav",
558
+ "aria-label": ariaLabel,
559
+ "data-testid": `${testIdPrefix}-chips`,
560
+ sx: {
561
+ display: "flex",
562
+ alignItems: "center",
563
+ gap: 0.75,
564
+ px: 0.25,
565
+ py: 0.75,
566
+ overflowX: "auto",
567
+ overflowY: "hidden",
568
+ scrollbarWidth: "none",
569
+ "&::-webkit-scrollbar": { display: "none" }
570
+ },
571
+ children: items.map((item) => /* @__PURE__ */ jsx4(
572
+ SectionChip,
573
+ {
574
+ item,
575
+ active: item.id === activeItemId,
576
+ theme,
577
+ linkComponent,
578
+ onSelectItem,
579
+ testIdPrefix
580
+ },
581
+ item.id
582
+ ))
583
+ }
584
+ );
585
+ }
586
+ SettingsSectionChips.displayName = "SettingsSectionChips";
587
+
588
+ // src/components/layout/SettingsLayout/SettingsPanel.tsx
589
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
590
+ function BackLink({
591
+ href,
592
+ label,
593
+ breakpoint,
291
594
  linkComponent,
292
- children,
293
- testIdPrefix = "settings"
294
- }) => {
295
- const [query, setQuery] = useState("");
595
+ testIdPrefix
596
+ }) {
597
+ return /* @__PURE__ */ jsxs5(
598
+ Box5,
599
+ {
600
+ component: linkComponent,
601
+ href,
602
+ "aria-label": label,
603
+ "data-testid": `${testIdPrefix}-back`,
604
+ sx: (theme) => ({
605
+ ...displayAcrossRail(theme, breakpoint, "inline-flex", "none"),
606
+ alignItems: "center",
607
+ gap: 0.5,
608
+ minHeight: TOUCH_TARGET,
609
+ pr: 1,
610
+ textDecoration: "none",
611
+ color: "text.secondary",
612
+ font: "inherit",
613
+ fontSize: "0.875rem"
614
+ }),
615
+ children: [
616
+ /* @__PURE__ */ jsx5(ChevronLeftIcon, { fontSize: "small" }),
617
+ label
618
+ ]
619
+ }
620
+ );
621
+ }
622
+ function SettingsPanel({
623
+ inSection,
624
+ hideOnNarrow,
625
+ breakpoint,
626
+ indexHref,
627
+ backLabel,
628
+ ariaLabel,
629
+ sectionChips,
630
+ activeItemId,
631
+ linkComponent,
632
+ onSelectItem,
633
+ testIdPrefix,
634
+ children
635
+ }) {
636
+ const showBack = inSection && indexHref !== void 0 && linkComponent !== void 0;
637
+ const showChips = inSection && sectionChips !== void 0 && sectionChips.length > 0;
638
+ return /* @__PURE__ */ jsxs5(
639
+ Box5,
640
+ {
641
+ "data-testid": `${testIdPrefix}-panel`,
642
+ sx: (theme) => ({
643
+ ...hideOnNarrow ? displayAcrossRail(theme, breakpoint, "none", "block") : { display: "block" },
644
+ flex: "1 1 auto",
645
+ minWidth: 0,
646
+ width: "100%"
647
+ }),
648
+ children: [
649
+ showBack ? /* @__PURE__ */ jsx5(
650
+ BackLink,
651
+ {
652
+ href: indexHref,
653
+ label: backLabel,
654
+ breakpoint,
655
+ linkComponent,
656
+ testIdPrefix
657
+ }
658
+ ) : null,
659
+ showChips ? /* @__PURE__ */ jsx5(
660
+ Box5,
661
+ {
662
+ sx: (theme) => ({
663
+ ...displayAcrossRail(theme, breakpoint, "block", "none"),
664
+ mb: 1,
665
+ minWidth: 0
666
+ }),
667
+ children: /* @__PURE__ */ jsx5(
668
+ SettingsSectionChips,
669
+ {
670
+ items: sectionChips,
671
+ activeItemId,
672
+ ariaLabel,
673
+ linkComponent,
674
+ onSelectItem,
675
+ testIdPrefix
676
+ }
677
+ )
678
+ }
679
+ ) : null,
680
+ children
681
+ ]
682
+ }
683
+ );
684
+ }
685
+
686
+ // src/components/layout/SettingsLayout/SettingsLayout.tsx
687
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
688
+ var DEFAULTS = {
689
+ searchPlaceholder: "Search settings",
690
+ defaultQuery: "",
691
+ emptySearchLabel: "No settings match your search.",
692
+ testIdPrefix: "settings",
693
+ railBreakpoint: "md",
694
+ navVariant: "switcher",
695
+ atIndex: false,
696
+ backLabel: "Back"
697
+ };
698
+ function shellSx(breakpoint) {
699
+ return (theme) => ({
700
+ display: "flex",
701
+ flexDirection: "column",
702
+ alignItems: "stretch",
703
+ gap: 1.5,
704
+ width: "100%",
705
+ minWidth: 0,
706
+ [atLeastRail(theme, breakpoint ?? "md")]: { flexDirection: "row", gap: 3 }
707
+ });
708
+ }
709
+ var SettingsLayout = (props) => {
710
+ const {
711
+ title,
712
+ groups,
713
+ activeItemId,
714
+ onSelectItem,
715
+ searchPlaceholder,
716
+ defaultQuery,
717
+ emptySearchLabel,
718
+ emptySearchAction,
719
+ linkComponent,
720
+ children,
721
+ testIdPrefix,
722
+ railBreakpoint,
723
+ navVariant,
724
+ atIndex,
725
+ indexHref,
726
+ backLabel,
727
+ sectionChips
728
+ } = withDefaults(props, DEFAULTS);
729
+ const [query, setQuery] = useState(defaultQuery);
296
730
  const [navOpen, setNavOpen] = useState(false);
297
731
  const filteredGroups = useMemo(() => filterGroups(groups, query), [groups, query]);
298
732
  const activeLabel = useMemo(() => activeItemLabel(groups, activeItemId), [groups, activeItemId]);
299
- return /* @__PURE__ */ jsxs2(
300
- Box2,
733
+ const drilldown = navVariant === "drilldown";
734
+ const ariaLabel = typeof title === "string" ? title : "Settings";
735
+ return /* @__PURE__ */ jsxs6(
736
+ Box6,
301
737
  {
302
738
  "data-testid": testIdPrefix,
303
- sx: {
304
- display: "flex",
305
- flexDirection: { xs: "column", md: "row" },
306
- alignItems: "stretch",
307
- gap: { xs: 1.5, md: 3 },
308
- width: "100%"
309
- },
739
+ "data-nav-variant": navVariant,
740
+ "data-at-index": atIndex ? "true" : "false",
741
+ sx: shellSx(railBreakpoint),
310
742
  children: [
311
- /* @__PURE__ */ jsx2(
743
+ /* @__PURE__ */ jsx6(
312
744
  SettingsRail,
313
745
  {
314
746
  title,
@@ -319,21 +751,43 @@ var SettingsLayout = ({
319
751
  onQueryChange: setQuery,
320
752
  searchPlaceholder,
321
753
  emptySearchLabel,
754
+ emptySearchAction,
322
755
  filteredGroups,
323
756
  activeItemId,
324
757
  linkComponent,
325
758
  onSelectItem,
326
759
  onNavigate: () => setNavOpen(false),
327
- testIdPrefix
760
+ testIdPrefix,
761
+ variant: navVariant,
762
+ breakpoint: railBreakpoint,
763
+ atIndex
328
764
  }
329
765
  ),
330
- /* @__PURE__ */ jsx2(Box2, { "data-testid": `${testIdPrefix}-panel`, sx: { flex: "1 1 auto", minWidth: 0, width: "100%" }, children })
766
+ /* @__PURE__ */ jsx6(
767
+ SettingsPanel,
768
+ {
769
+ inSection: drilldown && !atIndex,
770
+ hideOnNarrow: drilldown && atIndex,
771
+ breakpoint: railBreakpoint,
772
+ indexHref,
773
+ backLabel,
774
+ ariaLabel,
775
+ sectionChips,
776
+ activeItemId,
777
+ linkComponent,
778
+ onSelectItem,
779
+ testIdPrefix,
780
+ children
781
+ }
782
+ )
331
783
  ]
332
784
  }
333
785
  );
334
786
  };
335
787
  SettingsLayout.displayName = "SettingsLayout";
336
788
  export {
337
- SettingsLayout
789
+ SettingsLayout,
790
+ SettingsSectionChips,
791
+ SettingsStatusMarker
338
792
  };
339
793
  //# sourceMappingURL=SettingsLayout.js.map