mui-atelier 0.1.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 (77) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +32 -0
  4. package/README.md +282 -0
  5. package/dist/components/AddressFields.js +83 -0
  6. package/dist/components/AuthCard.js +94 -0
  7. package/dist/components/AuthSplash.js +88 -0
  8. package/dist/components/BrandHeader.js +81 -0
  9. package/dist/components/BrandLink.js +22 -0
  10. package/dist/components/BrandMark.js +69 -0
  11. package/dist/components/BrandProvider.js +14 -0
  12. package/dist/components/Breadcrumbs.js +62 -0
  13. package/dist/components/CardHead.js +46 -0
  14. package/dist/components/ConfirmProvider.js +77 -0
  15. package/dist/components/DashboardLayout.js +645 -0
  16. package/dist/components/DataTable.js +546 -0
  17. package/dist/components/DetailRows.js +42 -0
  18. package/dist/components/EmptyNote.js +28 -0
  19. package/dist/components/Eyebrow.js +21 -0
  20. package/dist/components/HeadingHelp.js +38 -0
  21. package/dist/components/HeadingLevel.js +18 -0
  22. package/dist/components/IconTile.js +42 -0
  23. package/dist/components/ImagePreviewDialog.js +86 -0
  24. package/dist/components/LabeledField.js +156 -0
  25. package/dist/components/LanguageSwitcher.js +43 -0
  26. package/dist/components/ListRow.js +59 -0
  27. package/dist/components/ModeToggle.js +35 -0
  28. package/dist/components/PageHeader.js +65 -0
  29. package/dist/components/PasswordField.js +43 -0
  30. package/dist/components/PublicLayout.js +27 -0
  31. package/dist/components/PublicNav.js +17 -0
  32. package/dist/components/RouteMessage.js +31 -0
  33. package/dist/components/RowList.js +10 -0
  34. package/dist/components/SafeHtml.js +57 -0
  35. package/dist/components/SaveState.js +13 -0
  36. package/dist/components/SectionPanel.js +48 -0
  37. package/dist/components/SectionTabs.js +139 -0
  38. package/dist/components/SiteNav.js +130 -0
  39. package/dist/components/Skeletons.js +63 -0
  40. package/dist/components/Sortable.js +81 -0
  41. package/dist/components/StatTile.js +66 -0
  42. package/dist/components/StatusChip.js +17 -0
  43. package/dist/components/StatusDot.js +42 -0
  44. package/dist/components/SubCard.js +30 -0
  45. package/dist/components/TabbedCard.js +20 -0
  46. package/dist/components/TableFilters.js +247 -0
  47. package/dist/components/ToastProvider.js +65 -0
  48. package/dist/eslint/chipRules.mjs +65 -0
  49. package/dist/hooks/useDocumentTitle.js +19 -0
  50. package/dist/hooks/useOverflowTabs.js +53 -0
  51. package/dist/hooks/useUnsavedChanges.js +46 -0
  52. package/dist/i18n/loadMessages.js +15 -0
  53. package/dist/i18n/mergeMessages.js +13 -0
  54. package/dist/i18n/messages/en.json +103 -0
  55. package/dist/i18n/messages/es.json +103 -0
  56. package/dist/i18n/messages/zh.json +103 -0
  57. package/dist/lib/authCard.js +4 -0
  58. package/dist/lib/chipTone.js +46 -0
  59. package/dist/lib/countryRegionData.js +29 -0
  60. package/dist/lib/countryRegionData.json +1 -0
  61. package/dist/lib/navLock.js +22 -0
  62. package/dist/lib/navTabSx.js +78 -0
  63. package/dist/lib/orgTime.js +243 -0
  64. package/dist/lib/pageHtml.js +139 -0
  65. package/dist/lib/safeNext.js +13 -0
  66. package/dist/lib/sanitizeHtml.js +36 -0
  67. package/dist/lib/slugify.js +7 -0
  68. package/dist/lib/tableFilter.js +147 -0
  69. package/dist/lib/tableStyle.js +87 -0
  70. package/dist/test/jsdomShims.js +53 -0
  71. package/dist/test/themeContract.js +178 -0
  72. package/dist/theme/OrgThemeRegistry.js +71 -0
  73. package/dist/theme/ThemeRegistry.js +17 -0
  74. package/dist/theme/berryNavRow.js +21 -0
  75. package/dist/theme/createSiteTheme.js +974 -0
  76. package/dist/theme/themes.js +438 -0
  77. package/package.json +118 -0
@@ -0,0 +1,645 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { useTheme } from "@mui/material/styles";
5
+ import AppBar from "@mui/material/AppBar";
6
+ import Toolbar from "@mui/material/Toolbar";
7
+ import Box from "@mui/material/Box";
8
+ import Drawer from "@mui/material/Drawer";
9
+ import List from "@mui/material/List";
10
+ import ListItem from "@mui/material/ListItem";
11
+ import ListItemButton from "@mui/material/ListItemButton";
12
+ import ListItemIcon from "@mui/material/ListItemIcon";
13
+ import ListItemText from "@mui/material/ListItemText";
14
+ import ListSubheader from "@mui/material/ListSubheader";
15
+ import Typography from "@mui/material/Typography";
16
+ import IconButton from "@mui/material/IconButton";
17
+ import Avatar from "@mui/material/Avatar";
18
+ import Menu from "@mui/material/Menu";
19
+ import MenuItem from "@mui/material/MenuItem";
20
+ import ListItemAvatar from "@mui/material/ListItemAvatar";
21
+ import Chip from "@mui/material/Chip";
22
+ import Divider from "@mui/material/Divider";
23
+ import Stack from "@mui/material/Stack";
24
+ import Collapse from "@mui/material/Collapse";
25
+ import Tooltip from "@mui/material/Tooltip";
26
+ import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
27
+ import MenuIcon from "@mui/icons-material/Menu";
28
+ import MenuOpenIcon from "@mui/icons-material/MenuOpen";
29
+ import ButtonBase from "@mui/material/ButtonBase";
30
+ import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined";
31
+ import ApartmentOutlinedIcon from "@mui/icons-material/ApartmentOutlined";
32
+ import ExpandLessIcon from "@mui/icons-material/ExpandLess";
33
+ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
34
+ import Link from "next/link";
35
+ import { useTranslations } from "next-intl";
36
+ import ModeToggle from "./ModeToggle.js";
37
+ import LanguageSwitcher from "./LanguageSwitcher.js";
38
+ import BrandMark from "./BrandMark.js";
39
+ import { useBrand } from "./BrandProvider.js";
40
+ import HeadingHelp from "./HeadingHelp.js";
41
+ import { useNavLocked } from "../lib/navLock.js";
42
+ import berryNavRowSx from "../theme/berryNavRow.js";
43
+ const DRAWER_WIDTH = 260;
44
+ const NAV_GAP = 12;
45
+ const APPBAR_H = 64;
46
+ const MINI_WIDTH = 76;
47
+ const NAV_COLLAPSED_KEY = "rt:nav:collapsed";
48
+ function NavRow({ item, open, onToggle, collapsed = false, onExpand }) {
49
+ const t = useTranslations("uikit");
50
+ const kids = item.children ?? [];
51
+ const hasKids = kids.length > 0;
52
+ const locked = useNavLocked();
53
+ const showsSelected = Boolean(item.selected) || hasKids && !open && kids.some((c) => c.selected);
54
+ const dead = Boolean(item.disabled) || locked;
55
+ const behaviour = hasKids ? collapsed ? { onClick: locked ? void 0 : onExpand } : { onClick: locked ? void 0 : onToggle, "aria-expanded": open } : { component: dead ? "div" : Link, href: dead ? void 0 : item.href };
56
+ const button = /* @__PURE__ */ jsx(ListItem, { disablePadding: true, sx: { mb: 0.25 }, children: /* @__PURE__ */ jsxs(
57
+ ListItemButton,
58
+ {
59
+ ...behaviour,
60
+ selected: showsSelected,
61
+ disabled: dead,
62
+ "aria-disabled": dead || void 0,
63
+ sx: [collapsed && { justifyContent: "center", px: 1.25 }, (theme) => {
64
+ const nav = theme.rt?.ui?.nav;
65
+ if (nav === "inverse") {
66
+ return {
67
+ borderRadius: 2,
68
+ pl: item.sub ? 2.5 : void 0,
69
+ "&:hover": { backgroundColor: "rgba(255,255,255,0.06)" },
70
+ "&.Mui-selected": {
71
+ backgroundImage: "linear-gradient(96deg, var(--rt-grad-from), var(--rt-grad-to))",
72
+ color: "#FFFFFF",
73
+ "& .MuiListItemIcon-root": { color: "#FFFFFF" },
74
+ boxShadow: "0 8px 18px -10px rgba(0,0,0,0.55)",
75
+ "&:hover": { filter: "brightness(1.07)" }
76
+ }
77
+ };
78
+ }
79
+ if (nav === "rail") {
80
+ const railFg = theme.rt?.rail?.fg ?? "#FFFFFF";
81
+ return {
82
+ borderRadius: 2,
83
+ pl: item.sub ? 2.5 : void 0,
84
+ color: railFg,
85
+ "& .MuiListItemIcon-root": { color: `color-mix(in srgb, ${railFg} 78%, transparent)` },
86
+ "&:hover": { backgroundColor: `color-mix(in srgb, ${railFg} 9%, transparent)` },
87
+ "&.Mui-selected": {
88
+ backgroundColor: theme.vars.palette.background.paper,
89
+ color: theme.vars.palette.primary.dark,
90
+ "& .MuiListItemIcon-root": { color: theme.vars.palette.primary.dark },
91
+ boxShadow: "0 1px 2px rgba(0,0,0,0.18), 0 8px 16px -10px rgba(0,0,0,0.45)",
92
+ "&:hover": { backgroundColor: theme.vars.palette.background.paper }
93
+ }
94
+ };
95
+ }
96
+ if (nav === "bar") {
97
+ return {
98
+ borderRadius: 0,
99
+ pl: item.sub ? 4 : 2.5,
100
+ "&:hover": { backgroundColor: theme.vars.palette.action.hover },
101
+ "&.Mui-selected": {
102
+ backgroundColor: `rgba(${theme.vars.palette.primary.mainChannel} / 0.08)`,
103
+ boxShadow: `inset 3px 0 0 ${theme.vars.palette.primary.main}`,
104
+ color: theme.vars.palette.primary.dark,
105
+ "& .MuiListItemIcon-root": { color: theme.vars.palette.primary.main },
106
+ "&:hover": { backgroundColor: `rgba(${theme.vars.palette.primary.mainChannel} / 0.14)` }
107
+ }
108
+ };
109
+ }
110
+ if (nav === "ruled") {
111
+ return {
112
+ borderRadius: 0,
113
+ minHeight: 42,
114
+ pl: item.sub ? 4.5 : 2.75,
115
+ borderBottom: `1px solid color-mix(in srgb, ${theme.vars.palette.divider} 55%, transparent)`,
116
+ "&:hover": { backgroundColor: theme.vars.palette.action.hover },
117
+ "&.Mui-selected": {
118
+ backgroundColor: theme.vars.palette.background.default,
119
+ color: theme.vars.palette.primary.dark,
120
+ "& .MuiListItemIcon-root": { color: theme.vars.palette.primary.main },
121
+ "& .MuiListItemText-primary": { fontWeight: 700 },
122
+ "&::before": {
123
+ content: '"\\25B8"',
124
+ // ▸
125
+ position: "absolute",
126
+ left: 8,
127
+ color: theme.vars.palette.primary.main,
128
+ fontSize: 14,
129
+ lineHeight: 1
130
+ },
131
+ "&:hover": { backgroundColor: theme.vars.palette.background.default }
132
+ }
133
+ };
134
+ }
135
+ if (nav === "quiet") {
136
+ return {
137
+ borderRadius: 0,
138
+ minHeight: 36,
139
+ paddingBlock: "4px",
140
+ pl: item.sub ? 4 : 2.5,
141
+ "& .MuiListItemIcon-root": { minWidth: item.sub ? 28 : 32, "& svg": { fontSize: 18 } },
142
+ "& .MuiListItemText-primary": { fontSize: "0.84rem" },
143
+ "&.Mui-selected": {
144
+ backgroundColor: theme.vars.palette.text.primary,
145
+ color: theme.vars.palette.background.paper,
146
+ "& .MuiListItemIcon-root": { color: "inherit" },
147
+ "&:hover": { backgroundColor: theme.vars.palette.text.primary }
148
+ }
149
+ };
150
+ }
151
+ if (nav === "accent") {
152
+ return {
153
+ borderRadius: 2,
154
+ pl: item.sub ? 2.5 : void 0,
155
+ "&.Mui-selected": {
156
+ backgroundColor: `rgba(${theme.vars.palette.primary.mainChannel} / 0.08)`,
157
+ boxShadow: `inset 0 0 0 1px rgba(${theme.vars.palette.primary.mainChannel} / 0.45)`,
158
+ color: theme.vars.palette.primary.main,
159
+ "& .MuiListItemIcon-root": { color: theme.vars.palette.primary.main },
160
+ "&:hover": { backgroundColor: `rgba(${theme.vars.palette.primary.mainChannel} / 0.14)` }
161
+ }
162
+ };
163
+ }
164
+ if (nav === "pill") {
165
+ return {
166
+ // A pill by default; a square block when the theme has zeroed its
167
+ // radii (a square theme). A theme can also opt THIS left-nav
168
+ // selection out of the pill with radii.nav — matching its own
169
+ // corners instead of taking the full 999 (the floating search bar
170
+ // is separate and keeps its pill). radii.nav is an EXACT px string:
171
+ // in `sx`, a bare number multiplies theme.shape.borderRadius (so 8
172
+ // would render as 8×base, not 8px); 999/0 stay numbers on purpose —
173
+ // any huge multiple caps to a pill, and 0 stays square.
174
+ borderRadius: theme.rt?.radii?.nav != null ? `${theme.rt.radii.nav}px` : theme.rt?.radii?.card === 0 ? 0 : 999,
175
+ pl: item.sub ? 2.5 : void 0,
176
+ "&:hover": { backgroundColor: `rgba(${theme.vars.palette.primary.mainChannel} / 0.10)` },
177
+ "&.Mui-selected": {
178
+ backgroundColor: theme.vars.palette.primary.main,
179
+ color: theme.vars.palette.primary.contrastText,
180
+ "& .MuiListItemIcon-root": { color: theme.vars.palette.primary.contrastText },
181
+ boxShadow: `0 6px 16px -8px rgba(${theme.vars.palette.primary.mainChannel} / 0.55)`,
182
+ "&:hover": { backgroundColor: theme.vars.palette.primary.main, filter: "brightness(1.04)" }
183
+ }
184
+ };
185
+ }
186
+ if (nav === "berry") {
187
+ return {
188
+ ...berryNavRowSx(theme),
189
+ paddingBlock: "10px",
190
+ mb: 0.5,
191
+ pl: item.sub ? 3 : 2,
192
+ "& .MuiListItemIcon-root": { minWidth: 36, "& svg": { fontSize: 20 } }
193
+ };
194
+ }
195
+ return {
196
+ borderRadius: 2,
197
+ pl: item.sub ? 2.5 : void 0,
198
+ "&.Mui-selected": {
199
+ backgroundColor: `rgba(${theme.vars.palette.primary.mainChannel} / 0.12)`,
200
+ color: theme.vars.palette.primary.main,
201
+ "& .MuiListItemIcon-root": { color: theme.vars.palette.primary.main },
202
+ "&:hover": {
203
+ backgroundColor: `rgba(${theme.vars.palette.primary.mainChannel} / 0.18)`
204
+ }
205
+ }
206
+ };
207
+ }],
208
+ children: [
209
+ item.icon ? /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: collapsed ? 0 : item.sub ? 34 : 40, justifyContent: "center", color: "inherit" }, children: item.icon }) : null,
210
+ collapsed ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
211
+ /* @__PURE__ */ jsx(ListItemText, { primary: item.label, slotProps: { primary: { fontWeight: item.selected ? 600 : 500 } } }),
212
+ item.disabled || item.soon ? /* @__PURE__ */ jsx(Chip, { label: t("shell.soon"), size: "small", variant: "outlined" }) : null,
213
+ hasKids ? /* @__PURE__ */ jsx(Box, { component: "span", sx: { display: "flex", color: "text.secondary" }, children: open ? /* @__PURE__ */ jsx(ExpandLessIcon, { fontSize: "small" }) : /* @__PURE__ */ jsx(ExpandMoreIcon, { fontSize: "small" }) }) : null
214
+ ] })
215
+ ]
216
+ }
217
+ ) });
218
+ return collapsed ? /* @__PURE__ */ jsx(Tooltip, { title: item.label, placement: "right", disableInteractive: true, children: button }) : button;
219
+ }
220
+ function NavList({ navItems, brand = null, floating = false, collapsed = false, onExpand, footer = null, barHeight = 64 }) {
221
+ const [toggled, setToggled] = useState({});
222
+ const nav = useTheme().rt?.ui?.nav;
223
+ const berry = nav === "berry";
224
+ return (
225
+ // A column, full height, so an app's `drawerFooter` (a promo card, a
226
+ // version chip) can sit at the bottom of the drawer under any length of nav.
227
+ /* @__PURE__ */ jsxs(Box, { sx: { width: collapsed ? MINI_WIDTH : DRAWER_WIDTH, display: "flex", flexDirection: "column", minHeight: "100%" }, role: "navigation", children: [
228
+ brand ? (
229
+ /*
230
+ * nav:'inverse' only — the rail owns its full column (the AppBar sits
231
+ * beside it, see DashboardLayout), so its top is REACHABLE and carries
232
+ * the reference chrome's tenant card: logo + the title node (which may
233
+ * be an organisation switcher), in a lifted panel. `color: inherit`
234
+ * cascades the rail's light-on-dark text into whatever node the title
235
+ * is; the switcher's Menu is a portal and keeps normal colours.
236
+ */
237
+ /* @__PURE__ */ jsx(Box, { sx: { p: 1.5, pb: 0.5 }, children: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1.25, px: 1.5, py: 1.25, borderRadius: (t) => `${t.rt?.radii?.card ?? 12}px`, bgcolor: "rgba(255,255,255,0.07)", border: "1px solid rgba(255,255,255,0.09)", "& .MuiTypography-root": { color: "inherit" } }, children: [
238
+ brand.logoUrl ? /* @__PURE__ */ jsx(BrandMark, { size: 30, logoUrl: brand.logoUrl, alt: "" }) : null,
239
+ /* @__PURE__ */ jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: brand.title })
240
+ ] }) })
241
+ ) : floating ? (
242
+ /*
243
+ * A floating nav (ui.navFloat) already sits BELOW the AppBar — its paper
244
+ * is inset with a top offset in DashboardLayout — so it needs no spacer
245
+ * to clear the bar, and no divider at the very top of a detached card.
246
+ * A little breathing room at the top of the card is enough.
247
+ */
248
+ /* @__PURE__ */ jsx(Box, { sx: { pt: 1 } })
249
+ ) : berry ? (
250
+ // berry: the bar is 88px, not the Toolbar's 64 — the spacer follows rt.barHeight.
251
+ /* @__PURE__ */ jsx(Box, { sx: { height: barHeight, flexShrink: 0 } })
252
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
253
+ /* @__PURE__ */ jsx(Toolbar, {}),
254
+ /* @__PURE__ */ jsx(Divider, {})
255
+ ] }),
256
+ /* @__PURE__ */ jsx(List, { sx: nav === "quiet" ? { px: 0, py: 0.75 } : berry ? { px: collapsed ? 0.75 : 2, py: 0 } : { px: collapsed ? 0.75 : 1.5, py: 1 }, children: navItems.map((item, i) => (
257
+ // A `{ heading }` item renders a section label; grouped items carry
258
+ // `sub` so they sit slightly indented under it. A section label has no
259
+ // room in an icon rail, so it drops when collapsed.
260
+ /*
261
+ * A `{ divider: true }` item CLOSES the group above it, for a set of
262
+ * rows that has to stay visually aligned with the previous group but
263
+ * is not part of it — e.g. Settings and Help sitting at the previous
264
+ * group's indent so their icons line up; without this the heading
265
+ * would claim them.
266
+ *
267
+ * Dropped when the rail is collapsed, like a heading: an icon-only
268
+ * column has no groups to separate.
269
+ */
270
+ item.divider ? collapsed ? null : /* @__PURE__ */ jsx(Box, { component: "li", sx: { listStyle: "none" }, children: /* @__PURE__ */ jsx(Divider, { sx: { my: 1, mx: 1.5 } }) }, `divider-${i}`) : item.heading ? collapsed ? null : berry ? (
271
+ /*
272
+ * berry: Berry's NavGroup caption — sentence case, 0.875rem/500 in
273
+ * the heading ink, and a divider CLOSING the previous group rather
274
+ * than opening this one (Berry rules under each group).
275
+ */
276
+ /* @__PURE__ */ jsxs(Box, { component: "li", sx: { listStyle: "none" }, children: [
277
+ i === 0 ? null : /* @__PURE__ */ jsx(Divider, { sx: { mt: 0.25, mb: 1.25 } }),
278
+ /* @__PURE__ */ jsx(Typography, { component: "div", sx: { fontSize: "0.875rem", fontWeight: 500, color: "var(--mui-palette-text-heading, inherit)", p: 0.75, mt: i === 0 ? 1.25 : 0, mb: 0.5 }, children: item.heading })
279
+ ] }, `heading-${item.heading}`)
280
+ ) : /* @__PURE__ */ jsx(
281
+ ListSubheader,
282
+ {
283
+ disableSticky: true,
284
+ sx: {
285
+ bgcolor: "transparent",
286
+ color: "text.secondary",
287
+ fontWeight: 700,
288
+ textTransform: "uppercase",
289
+ lineHeight: 2.2,
290
+ mt: i === 0 ? 0 : 1.5,
291
+ // 'quiet' and 'bar' both align headings with their full-bleed
292
+ // rows and speak even smaller; 'rail' and 'ruled' keep the
293
+ // padded-list metrics.
294
+ ...nav === "quiet" || nav === "bar" ? { fontSize: "0.64rem", letterSpacing: "0.1em", px: 2.5 } : { fontSize: "0.68rem", letterSpacing: "0.08em", px: 1.5 }
295
+ },
296
+ children: item.heading
297
+ },
298
+ `heading-${item.heading}`
299
+ ) : (() => {
300
+ const kids = item.children ?? [];
301
+ const open = toggled[item.label] ?? kids.some((c) => c.selected);
302
+ return /* @__PURE__ */ jsx(Box, { component: "li", sx: { display: "block" }, children: /* @__PURE__ */ jsxs(List, { disablePadding: true, children: [
303
+ /* @__PURE__ */ jsx(
304
+ NavRow,
305
+ {
306
+ item,
307
+ open,
308
+ onToggle: () => setToggled((s) => ({ ...s, [item.label]: !open })),
309
+ collapsed,
310
+ onExpand
311
+ }
312
+ ),
313
+ kids.length && !collapsed ? /* @__PURE__ */ jsx(Collapse, { in: open, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx(List, { disablePadding: true, children: kids.map((child) => /* @__PURE__ */ jsx(Box, { sx: { pl: 2 }, children: /* @__PURE__ */ jsx(NavRow, { item: { ...child, sub: true }, open: false, onToggle: () => {
314
+ } }) }, child.label)) }) }) : null
315
+ ] }) }, item.href ?? `group:${item.label}`);
316
+ })()
317
+ )) }),
318
+ footer && !collapsed ? /* @__PURE__ */ jsx(Box, { sx: { mt: "auto", p: 2, pt: 3 }, children: footer }) : null
319
+ ] })
320
+ );
321
+ }
322
+ function DashboardLayout({
323
+ title,
324
+ titleHelp = null,
325
+ logoUrl = null,
326
+ navItems = [],
327
+ user = null,
328
+ onSignOut,
329
+ homeLink = null,
330
+ actions = null,
331
+ search = null,
332
+ notifications = null,
333
+ drawerFooter = null,
334
+ children
335
+ }) {
336
+ const brand = useBrand();
337
+ const [mobileOpen, setMobileOpen] = useState(false);
338
+ const [anchorEl, setAnchorEl] = useState(null);
339
+ const [collapsed, setCollapsed] = useState(() => {
340
+ if (typeof window === "undefined") return false;
341
+ try {
342
+ return window.localStorage.getItem(NAV_COLLAPSED_KEY) === "1";
343
+ } catch {
344
+ return false;
345
+ }
346
+ });
347
+ const setCollapsedRemembered = (next) => {
348
+ setCollapsed(next);
349
+ try {
350
+ window.localStorage.setItem(NAV_COLLAPSED_KEY, next ? "1" : "0");
351
+ } catch {
352
+ }
353
+ };
354
+ const t = useTranslations("uikit");
355
+ const theme = useTheme();
356
+ const rtUi = theme.rt?.ui ?? {};
357
+ const berry = rtUi.chrome === "berry";
358
+ const barH = theme.rt?.barHeight ?? APPBAR_H;
359
+ const navStyle = rtUi.nav;
360
+ const inverseNav = navStyle === "inverse" || navStyle === "rail";
361
+ const floatingNav = !inverseNav && Boolean(rtUi.navFloat);
362
+ const collapsible = !inverseNav && Boolean(rtUi.navCollapsible);
363
+ const mini = collapsible && collapsed;
364
+ const drawerW = mini ? MINI_WIDTH : DRAWER_WIDTH;
365
+ const colW = floatingNav ? drawerW + NAV_GAP * 2 : drawerW;
366
+ const barFloat = !inverseNav && Boolean(rtUi.barFloat);
367
+ return /* @__PURE__ */ jsxs(Box, { sx: [{ display: "flex", minHeight: "100dvh" }, berry && ((t2) => ({ backgroundColor: t2.rt?.chrome?.light ?? t2.vars.palette.background.paper, ...t2.applyStyles("dark", { backgroundColor: t2.rt?.chrome?.dark ?? t2.vars.palette.background.paper }) }))], children: [
368
+ /* @__PURE__ */ jsx(
369
+ Box,
370
+ {
371
+ component: "a",
372
+ href: "#rt-main",
373
+ sx: {
374
+ position: "absolute",
375
+ left: 8,
376
+ top: -64,
377
+ zIndex: (t2) => t2.zIndex.drawer + 3,
378
+ px: 2,
379
+ py: 1,
380
+ borderRadius: 1,
381
+ bgcolor: "background.paper",
382
+ color: "text.primary",
383
+ boxShadow: 3,
384
+ transition: "top 120ms",
385
+ "&:focus-visible": { top: 8 }
386
+ },
387
+ children: t("nav.skipToContent")
388
+ }
389
+ ),
390
+ /* @__PURE__ */ jsx(
391
+ AppBar,
392
+ {
393
+ position: "fixed",
394
+ "data-bar-float": barFloat ? "true" : void 0,
395
+ sx: (t2) => {
396
+ if (inverseNav) return { zIndex: t2.zIndex.drawer - 1, pl: { md: `${DRAWER_WIDTH}px` } };
397
+ if (barFloat) {
398
+ const pad = t2.spacing(3);
399
+ return {
400
+ zIndex: t2.zIndex.drawer + 1,
401
+ top: NAV_GAP,
402
+ right: pad,
403
+ left: { xs: pad, md: `calc(${colW}px + ${pad})` },
404
+ width: "auto",
405
+ borderRadius: `${t2.rt?.radii?.card ?? 16}px`,
406
+ boxShadow: `0 6px 20px -12px rgba(${t2.vars.palette.primary.mainChannel} / 0.30), 0 2px 8px rgba(0,0,0,0.06)`
407
+ };
408
+ }
409
+ return { zIndex: t2.zIndex.drawer + 1 };
410
+ },
411
+ children: /* @__PURE__ */ jsxs(Toolbar, { sx: berry ? { gap: 1, minHeight: `${barH}px !important`, px: 2 } : { gap: 1 }, children: [
412
+ /* @__PURE__ */ jsx(
413
+ IconButton,
414
+ {
415
+ edge: "start",
416
+ onClick: () => setMobileOpen(true),
417
+ sx: { display: { md: "none" } },
418
+ "aria-label": t("shell.openNav"),
419
+ children: /* @__PURE__ */ jsx(MenuIcon, {})
420
+ }
421
+ ),
422
+ collapsible ? /* @__PURE__ */ jsx(
423
+ IconButton,
424
+ {
425
+ onClick: () => setCollapsedRemembered(!collapsed),
426
+ sx: [
427
+ { display: { xs: "none", md: "inline-flex" } },
428
+ berry && ((t2) => ({
429
+ order: 1,
430
+ width: 34,
431
+ height: 34,
432
+ borderRadius: "8px",
433
+ color: t2.vars.palette.primary.dark,
434
+ backgroundColor: `rgba(${t2.vars.palette.primary.mainChannel} / 0.12)`,
435
+ "&:hover": { color: t2.vars.palette.primary.contrastText, backgroundColor: t2.vars.palette.primary.dark },
436
+ "& svg": { fontSize: 20 }
437
+ }))
438
+ ],
439
+ "aria-label": mini ? "Expand navigation" : "Collapse navigation",
440
+ children: mini ? /* @__PURE__ */ jsx(MenuIcon, {}) : /* @__PURE__ */ jsx(MenuOpenIcon, {})
441
+ }
442
+ ) : null,
443
+ /* @__PURE__ */ jsxs(Box, { sx: { flexGrow: berry ? 0 : 1, ...berry ? { minWidth: { md: 186 }, maxWidth: { md: 300 }, mr: 1, order: 0 } : null, minWidth: 0, display: "flex", alignItems: "center", gap: 1, overflow: "hidden", ...inverseNav ? { visibility: { md: "hidden" } } : null }, children: [
444
+ logoUrl ? /* @__PURE__ */ jsx(BrandMark, { size: 26, logoUrl, alt: "" }) : null,
445
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", component: "div", noWrap: true, sx: { minWidth: 0 }, children: title }),
446
+ /* @__PURE__ */ jsx(HeadingHelp, { text: titleHelp, label: typeof title === "string" ? title : void 0, color: "inherit" })
447
+ ] }),
448
+ berry ? /* @__PURE__ */ jsx(Box, { sx: { order: 2, ml: 2, display: { xs: "none", md: "flex" }, alignItems: "center", minWidth: 0 }, children: search }) : search,
449
+ berry ? /* @__PURE__ */ jsx(Box, { sx: { order: 3, flexGrow: 1 } }) : null,
450
+ /* @__PURE__ */ jsxs(Box, { sx: berry ? { order: 4, display: "flex", alignItems: "center", gap: 1 } : { display: "contents" }, children: [
451
+ notifications,
452
+ actions,
453
+ /* @__PURE__ */ jsxs(Box, { sx: { display: { xs: "none", sm: "contents" } }, children: [
454
+ /* @__PURE__ */ jsx(LanguageSwitcher, {}),
455
+ /* @__PURE__ */ jsx(ModeToggle, {})
456
+ ] })
457
+ ] }),
458
+ user ? /* @__PURE__ */ jsxs(Fragment, { children: [
459
+ berry ? (
460
+ /*
461
+ * Berry's ProfileSection: a 48px chip in the data hue's wash —
462
+ * the avatar and a dropdown caret — that opens the account menu.
463
+ * The caret was a settings cog in CodedThemes' original, but a
464
+ * cog beside the account read as a link to Settings and opened
465
+ * the account dropdown instead; the caret says "menu", which is
466
+ * what this is. (Settings has its own gear in the bar's actions.)
467
+ */
468
+ /* @__PURE__ */ jsxs(
469
+ ButtonBase,
470
+ {
471
+ onClick: (e) => setAnchorEl(e.currentTarget),
472
+ "aria-label": t("shell.accountMenu"),
473
+ sx: (t2) => ({
474
+ order: 5,
475
+ ml: 1,
476
+ height: 48,
477
+ pl: 0.75,
478
+ pr: 1.5,
479
+ gap: 1,
480
+ borderRadius: "27px",
481
+ border: `1px solid rgba(${t2.vars.palette.secondary.mainChannel} / 0.12)`,
482
+ backgroundColor: `rgba(${t2.vars.palette.secondary.mainChannel} / 0.12)`,
483
+ color: t2.vars.palette.secondary.main,
484
+ transition: "all .2s ease-in-out",
485
+ "&:hover": { backgroundColor: t2.vars.palette.secondary.main, color: t2.vars.palette.secondary.contrastText }
486
+ }),
487
+ children: [
488
+ /* @__PURE__ */ jsx(Avatar, { sx: { width: 34, height: 34, fontSize: 14, color: "#fff", backgroundImage: "linear-gradient(135deg, var(--rt-grad-from), var(--rt-grad-to))" }, children: (user.email || "?")[0].toUpperCase() }),
489
+ /* @__PURE__ */ jsx(ExpandMoreIcon, { fontSize: "small" })
490
+ ]
491
+ }
492
+ )
493
+ ) : /* @__PURE__ */ jsx(IconButton, { onClick: (e) => setAnchorEl(e.currentTarget), size: "small", "aria-label": t("shell.accountMenu"), children: /* @__PURE__ */ jsx(Avatar, { sx: { width: 30, height: 30, fontSize: 14, color: "#fff", backgroundImage: "linear-gradient(135deg, var(--rt-grad-from), var(--rt-grad-to))" }, children: (user.email || "?")[0].toUpperCase() }) }),
494
+ /* @__PURE__ */ jsxs(Menu, { anchorEl, open: Boolean(anchorEl), onClose: () => setAnchorEl(null), children: [
495
+ /* @__PURE__ */ jsx(MenuItem, { disabled: true, sx: { opacity: "1 !important" }, children: /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { color: "text.secondary" }, children: user.email }) }),
496
+ /* @__PURE__ */ jsx(Divider, {}),
497
+ /* @__PURE__ */ jsxs(Box, { sx: { display: { xs: "block", sm: "none" } }, children: [
498
+ /* @__PURE__ */ jsxs(MenuItem, { disableRipple: true, sx: { gap: 1, justifyContent: "flex-start" }, children: [
499
+ /* @__PURE__ */ jsx(LanguageSwitcher, {}),
500
+ /* @__PURE__ */ jsx(ModeToggle, {})
501
+ ] }),
502
+ /* @__PURE__ */ jsx(Divider, {})
503
+ ] }),
504
+ homeLink ? /* @__PURE__ */ jsx(MenuItem, { component: Link, href: homeLink.href, onClick: () => setAnchorEl(null), children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [
505
+ /* @__PURE__ */ jsx(ApartmentOutlinedIcon, { fontSize: "small" }),
506
+ /* @__PURE__ */ jsx("span", { children: homeLink.label })
507
+ ] }) }) : null,
508
+ /* @__PURE__ */ jsx(MenuItem, { onClick: () => {
509
+ setAnchorEl(null);
510
+ onSignOut?.();
511
+ }, children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [
512
+ /* @__PURE__ */ jsx(LogoutOutlinedIcon, { fontSize: "small" }),
513
+ /* @__PURE__ */ jsx("span", { children: t("account.signOut") })
514
+ ] }) })
515
+ ] })
516
+ ] }) : null
517
+ ] })
518
+ }
519
+ ),
520
+ /* @__PURE__ */ jsx(
521
+ Drawer,
522
+ {
523
+ variant: "temporary",
524
+ open: mobileOpen,
525
+ onClose: () => setMobileOpen(false),
526
+ ModalProps: { keepMounted: true },
527
+ sx: { display: { xs: "block", md: "none" }, zIndex: (t2) => t2.zIndex.drawer + 2 },
528
+ children: /* @__PURE__ */ jsxs(Box, { onClick: (e) => {
529
+ if (e.target.closest("a")) setMobileOpen(false);
530
+ }, children: [
531
+ /* @__PURE__ */ jsx(Box, { sx: { display: "flex", justifyContent: "flex-end", p: 1 }, children: /* @__PURE__ */ jsx(IconButton, { onClick: () => setMobileOpen(false), size: "small", "aria-label": t("nav.close"), children: /* @__PURE__ */ jsx(CloseRoundedIcon, { fontSize: "small" }) }) }),
532
+ /* @__PURE__ */ jsx(NavList, { navItems, brand: inverseNav ? { logoUrl, title } : null, footer: drawerFooter, barHeight: barH })
533
+ ] })
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsx(
537
+ Drawer,
538
+ {
539
+ variant: "permanent",
540
+ "data-float": floatingNav ? "true" : void 0,
541
+ "data-collapsed": mini ? "true" : void 0,
542
+ sx: {
543
+ display: { xs: "none", md: "block" },
544
+ // The floating column reserves a gap on BOTH sides — the paper insets
545
+ // by NAV_GAP on the left, so widen the column by 2× to keep a matching
546
+ // gap between the panel and the content. `drawerW` shrinks both the
547
+ // column and the paper when the rail is collapsed.
548
+ width: floatingNav ? drawerW + NAV_GAP * 2 : drawerW,
549
+ flexShrink: 0,
550
+ // The width animates so collapsing/expanding glides rather than jumps.
551
+ "& .MuiDrawer-paper": floatingNav ? {
552
+ width: drawerW,
553
+ boxSizing: "border-box",
554
+ transition: "width 180ms ease",
555
+ // Detach from the edges: below the bar, inset left, lifted off
556
+ // the bottom, with rounded corners and a soft shadow. height:auto
557
+ // lets top+bottom drive the height so it never runs to the edge.
558
+ left: NAV_GAP,
559
+ // Below the full-width bar normally; but when the bar ALSO floats
560
+ // it sits beside this panel, so the panel runs full height.
561
+ top: barFloat ? NAV_GAP : `calc(${APPBAR_H}px + ${NAV_GAP}px)`,
562
+ bottom: NAV_GAP,
563
+ height: "auto",
564
+ border: 0,
565
+ borderRadius: (t2) => `${t2.rt?.radii?.card ?? 16}px`,
566
+ boxShadow: (t2) => `0 10px 30px -14px rgba(${t2.vars.palette.primary.mainChannel} / 0.30), 0 2px 8px rgba(0,0,0,0.06)`,
567
+ overflowY: "auto"
568
+ } : { width: drawerW, boxSizing: "border-box", transition: "width 180ms ease" }
569
+ },
570
+ open: true,
571
+ children: /* @__PURE__ */ jsx(
572
+ NavList,
573
+ {
574
+ navItems,
575
+ brand: inverseNav ? { logoUrl, title } : null,
576
+ floating: floatingNav,
577
+ collapsed: mini,
578
+ onExpand: () => setCollapsedRemembered(false),
579
+ footer: drawerFooter,
580
+ barHeight: barH
581
+ }
582
+ )
583
+ }
584
+ ),
585
+ berry ? (
586
+ /*
587
+ * Berry's MainContentStyled: the well. Grey ground (background.default),
588
+ * 20px padding, a 20px right margin, rounded TOP corners only, starting
589
+ * under the 88px bar and running to the bottom. The root paints the
590
+ * chrome colour so the corners read against it.
591
+ */
592
+ /* @__PURE__ */ jsxs(
593
+ Box,
594
+ {
595
+ component: "main",
596
+ id: "rt-main",
597
+ tabIndex: -1,
598
+ sx: {
599
+ flexGrow: 1,
600
+ minWidth: 0,
601
+ mt: `${barH}px`,
602
+ mr: { xs: 1.25, md: 2.5 },
603
+ ml: { xs: 1.25, md: 0 },
604
+ p: { xs: 2, md: 2.5 },
605
+ minHeight: `calc(100dvh - ${barH}px)`,
606
+ bgcolor: "background.default",
607
+ borderRadius: `${theme.rt?.radii?.card ?? 8}px ${theme.rt?.radii?.card ?? 8}px 0 0`,
608
+ display: "flex",
609
+ flexDirection: "column"
610
+ },
611
+ children: [
612
+ /* @__PURE__ */ jsx(Box, { sx: { flexGrow: 1 }, children }),
613
+ brand.copyright ? /* @__PURE__ */ jsx(Box, { sx: { pt: 3, mt: "auto" }, children: /* @__PURE__ */ jsxs(Typography, { variant: "caption", sx: { color: "text.secondary" }, children: [
614
+ "\xA9 ",
615
+ (/* @__PURE__ */ new Date()).getFullYear(),
616
+ " ",
617
+ brand.copyright
618
+ ] }) }) : null
619
+ ]
620
+ }
621
+ )
622
+ ) : /* @__PURE__ */ jsxs(
623
+ Box,
624
+ {
625
+ component: "main",
626
+ id: "rt-main",
627
+ tabIndex: -1,
628
+ sx: { flexGrow: 1, p: 3, minWidth: 0, display: "flex", flexDirection: "column" },
629
+ children: [
630
+ /* @__PURE__ */ jsx(Toolbar, {}),
631
+ /* @__PURE__ */ jsx(Box, { sx: { flexGrow: 1 }, children }),
632
+ brand.copyright ? /* @__PURE__ */ jsx(Box, { sx: { pt: 3, mt: "auto" }, children: /* @__PURE__ */ jsxs(Typography, { variant: "caption", sx: { color: "text.secondary" }, children: [
633
+ "\xA9 ",
634
+ (/* @__PURE__ */ new Date()).getFullYear(),
635
+ " ",
636
+ brand.copyright
637
+ ] }) }) : null
638
+ ]
639
+ }
640
+ )
641
+ ] });
642
+ }
643
+ export {
644
+ DashboardLayout as default
645
+ };