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,18 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from "react";
4
+ const HeadingLevelContext = createContext(2);
5
+ function useHeadingLevel() {
6
+ return `h${useContext(HeadingLevelContext)}`;
7
+ }
8
+ function useNestedHeadingLevel() {
9
+ return Math.min(useContext(HeadingLevelContext) + 1, 6);
10
+ }
11
+ function HeadingLevel({ level, children }) {
12
+ return /* @__PURE__ */ jsx(HeadingLevelContext.Provider, { value: Math.min(Math.max(level, 2), 6), children });
13
+ }
14
+ export {
15
+ HeadingLevel as default,
16
+ useHeadingLevel,
17
+ useNestedHeadingLevel
18
+ };
@@ -0,0 +1,42 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import Box from "@mui/material/Box";
3
+ import { useTheme } from "@mui/material/styles";
4
+ const TINTS = {
5
+ accent: { channel: "primary", fg: "primary.main" },
6
+ success: { channel: "success", fg: "success.main" },
7
+ warning: { channel: "warning", fg: "warning.main" },
8
+ neutral: { channel: "secondary", fg: "text.secondary" }
9
+ };
10
+ function IconTile({ children, tint = "accent", size = 40, sx }) {
11
+ const { rt } = useTheme();
12
+ const ui = rt?.ui ?? {};
13
+ const t = TINTS[tint] ?? TINTS.accent;
14
+ const flat = ui.surface === "flat";
15
+ const bare = flat && !ui.dense;
16
+ const radius = ui.surface === "berry" ? 8 : flat ? ui.dense ? 6 : 3 : Math.round(size * 0.32);
17
+ return /* @__PURE__ */ jsx(
18
+ Box,
19
+ {
20
+ "aria-hidden": true,
21
+ sx: [
22
+ {
23
+ width: size,
24
+ height: size,
25
+ flexShrink: 0,
26
+ borderRadius: `${radius}px`,
27
+ display: "grid",
28
+ placeItems: "center",
29
+ fontSize: size * (bare ? 0.56 : 0.5),
30
+ color: t.fg,
31
+ backgroundColor: bare ? "transparent" : (theme) => `rgba(${theme.vars.palette[t.channel].mainChannel} / ${ui.dense ? 0.11 : 0.14})`,
32
+ "& > svg": { fontSize: "inherit" }
33
+ },
34
+ ...Array.isArray(sx) ? sx : [sx]
35
+ ],
36
+ children
37
+ }
38
+ );
39
+ }
40
+ export {
41
+ IconTile as default
42
+ };
@@ -0,0 +1,86 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useTranslations } from "next-intl";
4
+ import Box from "@mui/material/Box";
5
+ import CircularProgress from "@mui/material/CircularProgress";
6
+ import Dialog from "@mui/material/Dialog";
7
+ import IconButton from "@mui/material/IconButton";
8
+ import Stack from "@mui/material/Stack";
9
+ import Typography from "@mui/material/Typography";
10
+ import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
11
+ import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
12
+ import ChevronRightIcon from "@mui/icons-material/ChevronRight";
13
+ function ImagePreviewDialog({ open, onClose, url, title, onPrev, onNext, counter }) {
14
+ const t = useTranslations("uikit");
15
+ const nav = Boolean(onPrev || onNext);
16
+ return /* @__PURE__ */ jsxs(
17
+ Dialog,
18
+ {
19
+ open,
20
+ onClose,
21
+ maxWidth: false,
22
+ "aria-label": title || "Image preview",
23
+ slotProps: { paper: { sx: { position: "relative", m: 2, maxWidth: "92vw", bgcolor: "background.paper", overflow: "visible" } } },
24
+ children: [
25
+ /* @__PURE__ */ jsx(
26
+ IconButton,
27
+ {
28
+ onClick: onClose,
29
+ "aria-label": t("image.close"),
30
+ size: "small",
31
+ sx: {
32
+ position: "absolute",
33
+ top: -14,
34
+ right: -14,
35
+ zIndex: 1,
36
+ bgcolor: "background.paper",
37
+ boxShadow: 2,
38
+ "&:hover": { bgcolor: "action.hover" }
39
+ },
40
+ children: /* @__PURE__ */ jsx(CloseRoundedIcon, { fontSize: "small" })
41
+ }
42
+ ),
43
+ /* @__PURE__ */ jsxs(Box, { sx: { p: 1.5, display: "flex", flexDirection: "column", alignItems: "center", gap: 1 }, children: [
44
+ /* @__PURE__ */ jsxs(Box, { sx: { position: "relative", display: "flex", alignItems: "center", justifyContent: "center" }, children: [
45
+ url ? /* @__PURE__ */ jsx(
46
+ Box,
47
+ {
48
+ component: "img",
49
+ src: url,
50
+ alt: title || "",
51
+ sx: { display: "block", maxWidth: "88vw", maxHeight: "82vh", width: "auto", height: "auto", borderRadius: (t2) => `${t2.rt?.radii?.button ?? 4}px` }
52
+ }
53
+ ) : /* @__PURE__ */ jsx(Box, { sx: { width: "min(60vw, 360px)", height: 240, display: "grid", placeItems: "center" }, children: /* @__PURE__ */ jsx(CircularProgress, { size: 28 }) }),
54
+ nav ? /* @__PURE__ */ jsxs(Fragment, { children: [
55
+ /* @__PURE__ */ jsx(
56
+ IconButton,
57
+ {
58
+ "aria-label": t("image.previous"),
59
+ onClick: onPrev,
60
+ sx: { position: "absolute", left: 8, top: "50%", transform: "translateY(-50%)", bgcolor: "background.paper", boxShadow: 2, "&:hover": { bgcolor: "action.hover" } },
61
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
62
+ }
63
+ ),
64
+ /* @__PURE__ */ jsx(
65
+ IconButton,
66
+ {
67
+ "aria-label": t("image.next"),
68
+ onClick: onNext,
69
+ sx: { position: "absolute", right: 8, top: "50%", transform: "translateY(-50%)", bgcolor: "background.paper", boxShadow: 2, "&:hover": { bgcolor: "action.hover" } },
70
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
71
+ }
72
+ )
73
+ ] }) : null
74
+ ] }),
75
+ title || counter ? /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { maxWidth: "88vw", alignItems: "center", justifyContent: "center" }, children: [
76
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { color: "text.secondary", textAlign: "center", wordBreak: "break-word" }, children: title }) : null,
77
+ counter ? /* @__PURE__ */ jsx(Typography, { variant: "caption", sx: { color: "text.secondary", flexShrink: 0 }, children: counter }) : null
78
+ ] }) : null
79
+ ] })
80
+ ]
81
+ }
82
+ );
83
+ }
84
+ export {
85
+ ImagePreviewDialog as default
86
+ };
@@ -0,0 +1,156 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext, useId } from "react";
4
+ import { useTranslations } from "next-intl";
5
+ import Box from "@mui/material/Box";
6
+ import { useTheme } from "@mui/material/styles";
7
+ import FormControl from "@mui/material/FormControl";
8
+ import Select from "@mui/material/Select";
9
+ import Typography from "@mui/material/Typography";
10
+ const AuthInsetContext = createContext(false);
11
+ function OptionalMark() {
12
+ const t = useTranslations("uikit");
13
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
14
+ " ",
15
+ /* @__PURE__ */ jsx(Typography, { component: "span", variant: "caption", sx: { ml: 0.25, color: "text.secondary", fontWeight: 400 }, children: t("optional") })
16
+ ] });
17
+ }
18
+ function LabeledField({
19
+ label,
20
+ help,
21
+ helpColor,
22
+ error = false,
23
+ required = false,
24
+ optional = false,
25
+ group = false,
26
+ sx,
27
+ children
28
+ }) {
29
+ const id = useId();
30
+ const labelId = useId();
31
+ const helpId = useId();
32
+ const inAuthCard = useContext(AuthInsetContext);
33
+ const theme = useTheme();
34
+ const inset = inAuthCard && Boolean(theme.rt?.ui?.authInset);
35
+ const describedBy = help ? helpId : void 0;
36
+ const tone = helpColor ?? (error ? "error.main" : "text.secondary");
37
+ const marker = required ? /* @__PURE__ */ jsx(Box, { component: "span", "aria-hidden": true, sx: { color: "error.main", ml: 0.25 }, children: "*" }) : optional ? /* @__PURE__ */ jsx(OptionalMark, {}) : null;
38
+ const helpLine = help ? /* @__PURE__ */ jsx(Typography, { id: helpId, variant: "caption", sx: { display: "block", mt: 0.5, color: tone }, children: help }) : null;
39
+ if (inset) {
40
+ return /* @__PURE__ */ jsxs(Box, { sx, children: [
41
+ /* @__PURE__ */ jsxs(
42
+ Box,
43
+ {
44
+ sx: {
45
+ position: "relative",
46
+ "& label": {
47
+ position: "absolute",
48
+ top: 9,
49
+ left: 14,
50
+ zIndex: 1,
51
+ pointerEvents: "none",
52
+ fontSize: "0.75rem",
53
+ lineHeight: 1,
54
+ color: "text.secondary"
55
+ },
56
+ "& .MuiOutlinedInput-input": { paddingTop: "30.5px !important", paddingBottom: "11.5px !important" },
57
+ "& .MuiOutlinedInput-notchedOutline legend": { display: "none" }
58
+ },
59
+ children: [
60
+ /* @__PURE__ */ jsxs(Typography, { component: "label", id: labelId, htmlFor: id, variant: "caption", children: [
61
+ label,
62
+ marker
63
+ ] }),
64
+ children({ id, labelId, describedBy })
65
+ ]
66
+ }
67
+ ),
68
+ helpLine
69
+ ] });
70
+ }
71
+ if (group) {
72
+ return /* @__PURE__ */ jsxs(
73
+ Box,
74
+ {
75
+ component: "fieldset",
76
+ "aria-describedby": describedBy,
77
+ sx: { border: 0, m: 0, p: 0, minWidth: 0, ...sx },
78
+ children: [
79
+ /* @__PURE__ */ jsxs(
80
+ Typography,
81
+ {
82
+ component: "legend",
83
+ id: labelId,
84
+ variant: "body2",
85
+ sx: { fontWeight: 500, display: "block", mb: 0.75, p: 0 },
86
+ children: [
87
+ label,
88
+ marker
89
+ ]
90
+ }
91
+ ),
92
+ children({ id, labelId, describedBy }),
93
+ helpLine
94
+ ]
95
+ }
96
+ );
97
+ }
98
+ return /* @__PURE__ */ jsxs(Box, { sx, children: [
99
+ /* @__PURE__ */ jsxs(
100
+ Typography,
101
+ {
102
+ component: "label",
103
+ id: labelId,
104
+ htmlFor: id,
105
+ variant: "body2",
106
+ sx: { fontWeight: 500, display: "block", mb: 0.75 },
107
+ children: [
108
+ label,
109
+ marker
110
+ ]
111
+ }
112
+ ),
113
+ children({ id, labelId, describedBy }),
114
+ helpLine
115
+ ] });
116
+ }
117
+ function LabeledSelect({
118
+ labelId,
119
+ describedBy,
120
+ ariaLabel,
121
+ value,
122
+ onChange,
123
+ disabled = false,
124
+ error = false,
125
+ multiple = false,
126
+ renderValue,
127
+ displayEmpty = true,
128
+ fullWidth = true,
129
+ size,
130
+ sx,
131
+ children
132
+ }) {
133
+ return /* @__PURE__ */ jsx(FormControl, { fullWidth, size, disabled, error, children: /* @__PURE__ */ jsx(
134
+ Select,
135
+ {
136
+ value,
137
+ onChange,
138
+ multiple,
139
+ renderValue,
140
+ displayEmpty,
141
+ SelectDisplayProps: {
142
+ "aria-labelledby": labelId,
143
+ "aria-describedby": describedBy,
144
+ "aria-label": labelId ? void 0 : ariaLabel
145
+ },
146
+ sx,
147
+ children
148
+ }
149
+ ) });
150
+ }
151
+ export {
152
+ AuthInsetContext,
153
+ LabeledSelect,
154
+ OptionalMark,
155
+ LabeledField as default
156
+ };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { useLocale, useTranslations } from "next-intl";
5
+ import { useRouter } from "next/navigation";
6
+ import IconButton from "@mui/material/IconButton";
7
+ import Tooltip from "@mui/material/Tooltip";
8
+ import Menu from "@mui/material/Menu";
9
+ import MenuItem from "@mui/material/MenuItem";
10
+ import ListItemText from "@mui/material/ListItemText";
11
+ import CheckRoundedIcon from "@mui/icons-material/CheckRounded";
12
+ import TranslateOutlinedIcon from "@mui/icons-material/TranslateOutlined";
13
+ const DEFAULT_LOCALES = [
14
+ { code: "en", label: "English" },
15
+ { code: "es", label: "Espa\xF1ol" },
16
+ { code: "zh", label: "\u4E2D\u6587" }
17
+ ];
18
+ const ONE_YEAR = 60 * 60 * 24 * 365;
19
+ function writeLocaleCookie(code) {
20
+ document.cookie = `NEXT_LOCALE=${code}; path=/; max-age=${ONE_YEAR}; samesite=lax`;
21
+ }
22
+ function LanguageSwitcher({ locales = DEFAULT_LOCALES }) {
23
+ const locale = useLocale();
24
+ const t = useTranslations("uikit");
25
+ const router = useRouter();
26
+ const [anchorEl, setAnchorEl] = useState(null);
27
+ function pick(code) {
28
+ writeLocaleCookie(code);
29
+ setAnchorEl(null);
30
+ if (code !== locale) router.refresh();
31
+ }
32
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
33
+ /* @__PURE__ */ jsx(Tooltip, { title: t("language.label"), children: /* @__PURE__ */ jsx(IconButton, { size: "small", onClick: (e) => setAnchorEl(e.currentTarget), "aria-label": t("language.label"), children: /* @__PURE__ */ jsx(TranslateOutlinedIcon, { fontSize: "small" }) }) }),
34
+ /* @__PURE__ */ jsx(Menu, { anchorEl, open: Boolean(anchorEl), onClose: () => setAnchorEl(null), children: locales.map(({ code, label }) => /* @__PURE__ */ jsxs(MenuItem, { selected: code === locale, onClick: () => pick(code), children: [
35
+ /* @__PURE__ */ jsx(ListItemText, { children: label }),
36
+ code === locale ? /* @__PURE__ */ jsx(CheckRoundedIcon, { fontSize: "small", sx: { ml: 2, opacity: 0.7 } }) : null
37
+ ] }, code)) })
38
+ ] });
39
+ }
40
+ export {
41
+ DEFAULT_LOCALES,
42
+ LanguageSwitcher as default
43
+ };
@@ -0,0 +1,59 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import Box from "@mui/material/Box";
3
+ import Stack from "@mui/material/Stack";
4
+ import Typography from "@mui/material/Typography";
5
+ import { useTheme } from "@mui/material/styles";
6
+ import IconTile from "./IconTile.js";
7
+ const TINT_CHANNEL = { accent: "primary", success: "success", warning: "warning", neutral: "secondary" };
8
+ function ListRow({ icon, iconTint = "accent", primary, secondary, trailing, tinted, sx }) {
9
+ const { rt } = useTheme();
10
+ const ui = rt?.ui ?? {};
11
+ const channel = TINT_CHANNEL[iconTint] ?? "primary";
12
+ const flat = ui.surface === "flat";
13
+ const glass = ui.surface === "glass";
14
+ const berry = ui.surface === "berry";
15
+ const dense = !!ui.dense;
16
+ const doTint = tinted ?? true;
17
+ const iconSize = dense ? 30 : flat ? 34 : glass || berry ? 40 : 42;
18
+ const rowSx = berry ? {
19
+ alignItems: "center",
20
+ px: 0,
21
+ py: 1.5,
22
+ borderBottom: "1px solid",
23
+ borderColor: "divider",
24
+ "&:last-of-type": { borderBottom: "none" }
25
+ } : {
26
+ alignItems: "center",
27
+ p: dense ? 1 : 1.25,
28
+ borderRadius: `${Math.min(rt?.radii?.card ?? 12, 16)}px`,
29
+ backgroundColor: doTint ? (theme) => `rgba(${theme.vars.palette[channel].mainChannel} / ${flat ? 0.05 : 0.06})` : "transparent",
30
+ // Flat themes (editorial/pro) read best with a crisp edge instead of a wash alone.
31
+ ...flat ? { border: "1px solid", borderColor: "divider" } : null
32
+ };
33
+ return /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: dense ? 1.25 : 1.75, sx: [rowSx, ...Array.isArray(sx) ? sx : [sx]], children: [
34
+ icon ? /* @__PURE__ */ jsx(IconTile, { tint: iconTint, size: iconSize, children: icon }) : null,
35
+ /* @__PURE__ */ jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [
36
+ /* @__PURE__ */ jsx(
37
+ Typography,
38
+ {
39
+ component: "div",
40
+ variant: dense ? "body2" : "subtitle2",
41
+ sx: { fontWeight: berry ? 500 : 600, fontSize: dense ? "0.9rem" : flat ? "1.05rem" : berry ? "1rem" : void 0, fontFamily: rt?.headingFont ?? void 0, ...berry ? { color: "var(--mui-palette-text-heading, inherit)" } : null },
42
+ children: primary
43
+ }
44
+ ),
45
+ secondary ? /* @__PURE__ */ jsx(
46
+ Typography,
47
+ {
48
+ variant: "caption",
49
+ sx: { color: "text.secondary", display: "block", overflowWrap: "anywhere", fontFamily: glass ? rt.mono : void 0, fontSize: glass ? "0.72rem" : void 0, letterSpacing: glass ? "0.02em" : void 0 },
50
+ children: secondary
51
+ }
52
+ ) : null
53
+ ] }),
54
+ trailing
55
+ ] });
56
+ }
57
+ export {
58
+ ListRow as default
59
+ };
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useSyncExternalStore } from "react";
4
+ import { useColorScheme } from "@mui/material/styles";
5
+ import { useTranslations } from "next-intl";
6
+ import IconButton from "@mui/material/IconButton";
7
+ import Tooltip from "@mui/material/Tooltip";
8
+ import LightModeOutlinedIcon from "@mui/icons-material/LightModeOutlined";
9
+ import DarkModeOutlinedIcon from "@mui/icons-material/DarkModeOutlined";
10
+ import SettingsBrightnessOutlinedIcon from "@mui/icons-material/SettingsBrightnessOutlined";
11
+ const NEXT = { light: "dark", dark: "system", system: "light" };
12
+ const emptySubscribe = () => () => {
13
+ };
14
+ const useMounted = () => useSyncExternalStore(emptySubscribe, () => true, () => false);
15
+ function ModeToggle() {
16
+ const t = useTranslations("uikit");
17
+ const { mode, setMode } = useColorScheme();
18
+ const mounted = useMounted();
19
+ if (!mounted || !mode) {
20
+ return /* @__PURE__ */ jsx(IconButton, { size: "small", disabled: true, "aria-label": t("mode.label"), children: /* @__PURE__ */ jsx(SettingsBrightnessOutlinedIcon, { fontSize: "small" }) });
21
+ }
22
+ const icon = mode === "light" ? /* @__PURE__ */ jsx(LightModeOutlinedIcon, { fontSize: "small" }) : mode === "dark" ? /* @__PURE__ */ jsx(DarkModeOutlinedIcon, { fontSize: "small" }) : /* @__PURE__ */ jsx(SettingsBrightnessOutlinedIcon, { fontSize: "small" });
23
+ return (
24
+ /*
25
+ * Named through the catalogue like everything else in this bar. It was
26
+ * `Colour mode: dark` in a template literal — untranslated in a trilingual
27
+ * product, and spelled the American way in a codebase that writes
28
+ * "organisation" everywhere else.
29
+ */
30
+ /* @__PURE__ */ jsx(Tooltip, { title: t(`mode.${mode}`), children: /* @__PURE__ */ jsx(IconButton, { size: "small", onClick: () => setMode(NEXT[mode]), "aria-label": t(`mode.${mode}`), children: icon }) })
31
+ );
32
+ }
33
+ export {
34
+ ModeToggle as default
35
+ };
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import Box from "@mui/material/Box";
4
+ import { useTheme } from "@mui/material/styles";
5
+ function PageHeader({ children, sx, ...rest }) {
6
+ const theme = useTheme();
7
+ const pill = !!theme.rt?.ui?.pillHeading;
8
+ const berry = theme.rt?.ui?.chrome === "berry";
9
+ const berrySx = berry ? {
10
+ backgroundColor: "background.paper",
11
+ // The same shadow the theme's cards carry (createSiteTheme's berry
12
+ // surface, one definition) — this strip is a card, and sat flat
13
+ // beside shadowed ones.
14
+ boxShadow: theme.rt?.cardShadow ?? "none",
15
+ borderRadius: `${theme.rt?.radii?.card ?? 8}px`,
16
+ px: 2,
17
+ py: 1.25,
18
+ /*
19
+ * A two-column grid: everything the page wrote stacks in the first
20
+ * column, the trail alone takes the second, on the first row, pinned
21
+ * right — so a one-line header reads "title … trail" on one row, and
22
+ * a tall one (a record's title and its meta) keeps the trail at
23
+ * its top-right corner rather than under the meta.
24
+ */
25
+ display: "grid",
26
+ gridTemplateColumns: "minmax(0, 1fr) auto",
27
+ columnGap: 2,
28
+ rowGap: 1,
29
+ alignItems: "center",
30
+ // The header is a size container so the trail can move by the WIDTH
31
+ // IT HAS, not the viewport's: a page that caps its header (the
32
+ // project editor's 680px) is narrow on a wide screen.
33
+ containerType: "inline-size",
34
+ "& > *": { minWidth: 0, gridColumn: 1 },
35
+ // A bare back-link button in the header keeps its size; stretched to
36
+ // the column it would centre its label across the card.
37
+ "& > .MuiButton-root, & > a": { justifySelf: "start" },
38
+ "& > .MuiBreadcrumbs-root": {
39
+ gridColumn: 2,
40
+ gridRow: 1,
41
+ justifySelf: "end",
42
+ alignSelf: "start",
43
+ mb: 0,
44
+ fontSize: "0.75rem",
45
+ fontWeight: 500,
46
+ // Too narrow for a title and a trail side by side: the trail takes
47
+ // the whole first row, right-aligned, and the title drops under it.
48
+ "@container (max-width: 860px)": { gridColumn: "1 / -1", "& .MuiBreadcrumbs-ol": { justifyContent: "flex-end" } }
49
+ },
50
+ "& h1": { fontSize: "1rem", fontWeight: 500 },
51
+ // A standing subtitle under the title: Berry's strip has no room for a
52
+ // sentence, so it becomes the caption size.
53
+ "& h1 + .MuiTypography-root": { fontSize: "0.75rem" }
54
+ } : {};
55
+ const pillSx = pill ? {
56
+ backgroundColor: "background.paper",
57
+ borderRadius: `${theme.rt?.radii?.card ?? 16}px`,
58
+ p: { xs: 2, sm: 2.5 },
59
+ boxShadow: "0 1px 2px rgba(0,0,0,0.04), 0 8px 24px -18px rgba(0,0,0,0.18)"
60
+ } : {};
61
+ return /* @__PURE__ */ jsx(Box, { "data-pill": pill ? "on" : "off", "data-chrome": berry ? "berry" : void 0, sx: { ...pillSx, ...berrySx, ...sx }, ...rest, children });
62
+ }
63
+ export {
64
+ PageHeader as default
65
+ };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import IconButton from "@mui/material/IconButton";
5
+ import InputAdornment from "@mui/material/InputAdornment";
6
+ import TextField from "@mui/material/TextField";
7
+ import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
8
+ import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined";
9
+ import { useTranslations } from "next-intl";
10
+ function PasswordField({ id, describedBy, ...rest }) {
11
+ const t = useTranslations("uikit");
12
+ const [shown, setShown] = useState(false);
13
+ return /* @__PURE__ */ jsx(
14
+ TextField,
15
+ {
16
+ id,
17
+ fullWidth: true,
18
+ ...rest,
19
+ type: shown ? "text" : "password",
20
+ slotProps: {
21
+ ...rest.slotProps,
22
+ htmlInput: { "aria-describedby": describedBy, ...rest.slotProps?.htmlInput },
23
+ input: {
24
+ ...rest.slotProps?.input,
25
+ endAdornment: /* @__PURE__ */ jsx(InputAdornment, { position: "end", children: /* @__PURE__ */ jsx(
26
+ IconButton,
27
+ {
28
+ onClick: () => setShown((v) => !v),
29
+ edge: "end",
30
+ size: "small",
31
+ "aria-label": t(shown ? "password.hide" : "password.show"),
32
+ tabIndex: -1,
33
+ children: shown ? /* @__PURE__ */ jsx(VisibilityOffOutlinedIcon, { fontSize: "small" }) : /* @__PURE__ */ jsx(VisibilityOutlinedIcon, { fontSize: "small" })
34
+ }
35
+ ) })
36
+ }
37
+ }
38
+ }
39
+ );
40
+ }
41
+ export {
42
+ PasswordField as default
43
+ };
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import Box from "@mui/material/Box";
4
+ import Container from "@mui/material/Container";
5
+ import Typography from "@mui/material/Typography";
6
+ import Stack from "@mui/material/Stack";
7
+ import BrandHeader from "./BrandHeader.js";
8
+ import PublicNav from "./PublicNav.js";
9
+ function PublicLayout({ siteName, siteHref = "/", logoUrl = null, footerName = siteName, navItems = [], actions = null, footer = null, children }) {
10
+ return /* @__PURE__ */ jsxs(Box, { sx: { minHeight: "100dvh", display: "flex", flexDirection: "column" }, children: [
11
+ /* @__PURE__ */ jsx(BrandHeader, { siteName, href: siteHref, logoUrl, navItems, actions }),
12
+ navItems.length > 0 ? /* @__PURE__ */ jsx(PublicNav, { items: navItems }) : null,
13
+ /* @__PURE__ */ jsx(Box, { component: "main", sx: { flexGrow: 1 }, children }),
14
+ /* @__PURE__ */ jsx(Box, { component: "footer", sx: { borderTop: 1, borderColor: "divider", py: 3, mt: 8 }, children: /* @__PURE__ */ jsx(Container, { maxWidth: "lg", children: /* @__PURE__ */ jsxs(Stack, { direction: { xs: "column", sm: "row" }, spacing: 2, sx: { alignItems: "center", justifyContent: "space-between" }, children: [
15
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", sx: { color: "text.secondary" }, children: [
16
+ "\xA9 ",
17
+ (/* @__PURE__ */ new Date()).getFullYear(),
18
+ " ",
19
+ footerName
20
+ ] }),
21
+ footer
22
+ ] }) }) })
23
+ ] });
24
+ }
25
+ export {
26
+ PublicLayout as default
27
+ };
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { usePathname } from "next/navigation";
4
+ import SiteNav from "./SiteNav.js";
5
+ function PublicNav({ items = [] }) {
6
+ const pathname = usePathname() || "";
7
+ if (items.length === 0) return null;
8
+ return /* @__PURE__ */ jsx(
9
+ SiteNav,
10
+ {
11
+ items: items.map((item) => ({ key: item.href, label: item.label, href: item.href, active: pathname === item.href }))
12
+ }
13
+ );
14
+ }
15
+ export {
16
+ PublicNav as default
17
+ };
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import Box from "@mui/material/Box";
4
+ import Button from "@mui/material/Button";
5
+ import Container from "@mui/material/Container";
6
+ import Stack from "@mui/material/Stack";
7
+ import Typography from "@mui/material/Typography";
8
+ import NextLink from "next/link";
9
+ import { useTranslations } from "next-intl";
10
+ function RouteMessage({
11
+ title,
12
+ body,
13
+ cta,
14
+ href,
15
+ onRetry,
16
+ retryLabel
17
+ }) {
18
+ const t = useTranslations("uikit");
19
+ return /* @__PURE__ */ jsxs(Container, { maxWidth: "sm", sx: { py: { xs: 8, sm: 12 }, textAlign: "center" }, children: [
20
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", component: "h1", gutterBottom: true, children: title }),
21
+ body ? /* @__PURE__ */ jsx(Typography, { variant: "body1", sx: { color: "text.secondary", mb: 4 }, children: body }) : null,
22
+ /* @__PURE__ */ jsxs(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1.5, sx: { justifyContent: "center" }, children: [
23
+ onRetry ? /* @__PURE__ */ jsx(Button, { variant: "contained", onClick: onRetry, children: retryLabel ?? t("route.retry") }) : null,
24
+ href ? /* @__PURE__ */ jsx(Button, { component: NextLink, href, variant: onRetry ? "outlined" : "contained", children: cta ?? t("route.home") }) : null,
25
+ !href && !onRetry ? /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Button, { variant: "outlined", onClick: () => window.history.back(), children: t("route.back") }) }) : null
26
+ ] })
27
+ ] });
28
+ }
29
+ export {
30
+ RouteMessage as default
31
+ };
@@ -0,0 +1,10 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import Stack from "@mui/material/Stack";
3
+ import { useTheme } from "@mui/material/styles";
4
+ function RowList({ children, sx }) {
5
+ const berry = useTheme().rt?.ui?.surface === "berry";
6
+ return /* @__PURE__ */ jsx(Stack, { spacing: berry ? 0 : 1, sx, children });
7
+ }
8
+ export {
9
+ RowList as default
10
+ };