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.
- package/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/NOTICE +32 -0
- package/README.md +282 -0
- package/dist/components/AddressFields.js +83 -0
- package/dist/components/AuthCard.js +94 -0
- package/dist/components/AuthSplash.js +88 -0
- package/dist/components/BrandHeader.js +81 -0
- package/dist/components/BrandLink.js +22 -0
- package/dist/components/BrandMark.js +69 -0
- package/dist/components/BrandProvider.js +14 -0
- package/dist/components/Breadcrumbs.js +62 -0
- package/dist/components/CardHead.js +46 -0
- package/dist/components/ConfirmProvider.js +77 -0
- package/dist/components/DashboardLayout.js +645 -0
- package/dist/components/DataTable.js +546 -0
- package/dist/components/DetailRows.js +42 -0
- package/dist/components/EmptyNote.js +28 -0
- package/dist/components/Eyebrow.js +21 -0
- package/dist/components/HeadingHelp.js +38 -0
- package/dist/components/HeadingLevel.js +18 -0
- package/dist/components/IconTile.js +42 -0
- package/dist/components/ImagePreviewDialog.js +86 -0
- package/dist/components/LabeledField.js +156 -0
- package/dist/components/LanguageSwitcher.js +43 -0
- package/dist/components/ListRow.js +59 -0
- package/dist/components/ModeToggle.js +35 -0
- package/dist/components/PageHeader.js +65 -0
- package/dist/components/PasswordField.js +43 -0
- package/dist/components/PublicLayout.js +27 -0
- package/dist/components/PublicNav.js +17 -0
- package/dist/components/RouteMessage.js +31 -0
- package/dist/components/RowList.js +10 -0
- package/dist/components/SafeHtml.js +57 -0
- package/dist/components/SaveState.js +13 -0
- package/dist/components/SectionPanel.js +48 -0
- package/dist/components/SectionTabs.js +139 -0
- package/dist/components/SiteNav.js +130 -0
- package/dist/components/Skeletons.js +63 -0
- package/dist/components/Sortable.js +81 -0
- package/dist/components/StatTile.js +66 -0
- package/dist/components/StatusChip.js +17 -0
- package/dist/components/StatusDot.js +42 -0
- package/dist/components/SubCard.js +30 -0
- package/dist/components/TabbedCard.js +20 -0
- package/dist/components/TableFilters.js +247 -0
- package/dist/components/ToastProvider.js +65 -0
- package/dist/eslint/chipRules.mjs +65 -0
- package/dist/hooks/useDocumentTitle.js +19 -0
- package/dist/hooks/useOverflowTabs.js +53 -0
- package/dist/hooks/useUnsavedChanges.js +46 -0
- package/dist/i18n/loadMessages.js +15 -0
- package/dist/i18n/mergeMessages.js +13 -0
- package/dist/i18n/messages/en.json +103 -0
- package/dist/i18n/messages/es.json +103 -0
- package/dist/i18n/messages/zh.json +103 -0
- package/dist/lib/authCard.js +4 -0
- package/dist/lib/chipTone.js +46 -0
- package/dist/lib/countryRegionData.js +29 -0
- package/dist/lib/countryRegionData.json +1 -0
- package/dist/lib/navLock.js +22 -0
- package/dist/lib/navTabSx.js +78 -0
- package/dist/lib/orgTime.js +243 -0
- package/dist/lib/pageHtml.js +139 -0
- package/dist/lib/safeNext.js +13 -0
- package/dist/lib/sanitizeHtml.js +36 -0
- package/dist/lib/slugify.js +7 -0
- package/dist/lib/tableFilter.js +147 -0
- package/dist/lib/tableStyle.js +87 -0
- package/dist/test/jsdomShims.js +53 -0
- package/dist/test/themeContract.js +178 -0
- package/dist/theme/OrgThemeRegistry.js +71 -0
- package/dist/theme/ThemeRegistry.js +17 -0
- package/dist/theme/berryNavRow.js +21 -0
- package/dist/theme/createSiteTheme.js +974 -0
- package/dist/theme/themes.js +438 -0
- package/package.json +118 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import Box from "@mui/material/Box";
|
|
5
|
+
import { sanitizeHtml } from "../lib/sanitizeHtml.js";
|
|
6
|
+
const RICH_SX = {
|
|
7
|
+
"& > :first-of-type": { mt: 0 },
|
|
8
|
+
"& > :last-child": { mb: 0 },
|
|
9
|
+
"& h1, & h2, & h3, & h4, & h5, & h6": { mt: 2, mb: 1, lineHeight: 1.3 },
|
|
10
|
+
"& p, & ul, & ol, & blockquote, & pre": { mt: 0, mb: 1.5 },
|
|
11
|
+
"& p, & ul, & ol, & blockquote": { maxWidth: "68ch" },
|
|
12
|
+
"& ul, & ol": { pl: 3 },
|
|
13
|
+
"& li": { mb: 0.5 },
|
|
14
|
+
"& blockquote": {
|
|
15
|
+
m: 0,
|
|
16
|
+
pl: 2,
|
|
17
|
+
borderLeft: 3,
|
|
18
|
+
borderColor: "divider",
|
|
19
|
+
color: "text.secondary"
|
|
20
|
+
},
|
|
21
|
+
"& pre": { p: 1.5, borderRadius: (t) => `${t.rt?.radii?.base ?? 8}px`, bgcolor: "action.hover", overflowX: "auto" },
|
|
22
|
+
"& code": { fontFamily: "monospace", fontSize: "0.875em" },
|
|
23
|
+
"& hr": { border: 0, borderTop: 1, borderColor: "divider", my: 2 },
|
|
24
|
+
"& table": {
|
|
25
|
+
display: "block",
|
|
26
|
+
width: "100%",
|
|
27
|
+
overflowX: "auto",
|
|
28
|
+
borderCollapse: "collapse",
|
|
29
|
+
mb: 1.5
|
|
30
|
+
},
|
|
31
|
+
"& th, & td": { border: 1, borderColor: "divider", px: 1, py: 0.5, textAlign: "left" },
|
|
32
|
+
"& th": { bgcolor: "action.hover", fontWeight: 600 },
|
|
33
|
+
"& caption": { captionSide: "bottom", pt: 1, color: "text.secondary", fontSize: "0.8125rem" }
|
|
34
|
+
};
|
|
35
|
+
function SafeHtml({ html, block = false, rich = false, sx, ...rest }) {
|
|
36
|
+
const clean = useMemo(() => sanitizeHtml(html, { block, rich }), [html, block, rich]);
|
|
37
|
+
if (!clean) return null;
|
|
38
|
+
const asBlock = block || rich;
|
|
39
|
+
return /* @__PURE__ */ jsx(
|
|
40
|
+
Box,
|
|
41
|
+
{
|
|
42
|
+
component: asBlock ? "div" : "span",
|
|
43
|
+
suppressHydrationWarning: true,
|
|
44
|
+
sx: {
|
|
45
|
+
"& a": { color: "primary.main", textDecoration: "underline" },
|
|
46
|
+
...asBlock ? { "& p": { m: 0 }, "& p:not(:last-child)": { mb: 0.5 } } : {},
|
|
47
|
+
...rich ? RICH_SX : {},
|
|
48
|
+
...sx
|
|
49
|
+
},
|
|
50
|
+
...rest,
|
|
51
|
+
dangerouslySetInnerHTML: { __html: clean }
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
SafeHtml as default
|
|
57
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import Stack from "@mui/material/Stack";
|
|
4
|
+
import Typography from "@mui/material/Typography";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
6
|
+
import StatusChip from "./StatusChip.js";
|
|
7
|
+
function SaveState({ dirty, saving }) {
|
|
8
|
+
const t = useTranslations("uikit");
|
|
9
|
+
return /* @__PURE__ */ jsx(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, role: "status", "aria-live": "polite", children: saving ? /* @__PURE__ */ jsx(Typography, { variant: "caption", sx: { color: "text.secondary" }, children: t("save.saving") }) : dirty ? /* @__PURE__ */ jsx(StatusChip, { size: "small", role: "caution", variant: "outlined", label: t("save.unsaved") }) : null });
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
SaveState as default
|
|
13
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Card from "@mui/material/Card";
|
|
3
|
+
import Box from "@mui/material/Box";
|
|
4
|
+
import Stack from "@mui/material/Stack";
|
|
5
|
+
import Typography from "@mui/material/Typography";
|
|
6
|
+
import { useTheme } from "@mui/material/styles";
|
|
7
|
+
import HeadingHelp from "./HeadingHelp.js";
|
|
8
|
+
import HeadingLevel, { useHeadingLevel, useNestedHeadingLevel } from "./HeadingLevel.js";
|
|
9
|
+
function SectionPanel({ title, subtitle, help, action, component, children, sx }) {
|
|
10
|
+
const hasHeader = title || action;
|
|
11
|
+
const level = useHeadingLevel();
|
|
12
|
+
const headingComponent = component ?? level;
|
|
13
|
+
const nested = useNestedHeadingLevel();
|
|
14
|
+
const berry = useTheme().rt?.ui?.surface === "berry";
|
|
15
|
+
return /* @__PURE__ */ jsxs(
|
|
16
|
+
Card,
|
|
17
|
+
{
|
|
18
|
+
sx: [
|
|
19
|
+
berry ? { p: 3 } : { p: { xs: 2, sm: 2.5 } },
|
|
20
|
+
...Array.isArray(sx) ? sx : [sx]
|
|
21
|
+
],
|
|
22
|
+
children: [
|
|
23
|
+
hasHeader ? /* @__PURE__ */ jsxs(
|
|
24
|
+
Stack,
|
|
25
|
+
{
|
|
26
|
+
direction: "row",
|
|
27
|
+
spacing: 1.5,
|
|
28
|
+
sx: berry && children ? { alignItems: "center", mx: -3, px: 3, pb: 3, mb: 3, borderBottom: "1px solid", borderColor: "divider", "& .MuiTypography-h6": { fontSize: "1.125rem", fontWeight: 500 } } : { alignItems: "center", mb: children ? 2 : 0 },
|
|
29
|
+
children: [
|
|
30
|
+
/* @__PURE__ */ jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [
|
|
31
|
+
title ? /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 0.25, sx: { alignItems: "center" }, children: [
|
|
32
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h6", component: headingComponent, children: title }),
|
|
33
|
+
/* @__PURE__ */ jsx(HeadingHelp, { text: help, label: typeof title === "string" ? title : void 0 })
|
|
34
|
+
] }) : null,
|
|
35
|
+
subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { color: "text.secondary" }, children: subtitle }) : null
|
|
36
|
+
] }),
|
|
37
|
+
action
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
) : null,
|
|
41
|
+
title ? /* @__PURE__ */ jsx(HeadingLevel, { level: nested, children }) : children
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
SectionPanel as default
|
|
48
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Children, cloneElement, isValidElement } from "react";
|
|
4
|
+
import Box from "@mui/material/Box";
|
|
5
|
+
import Tabs from "@mui/material/Tabs";
|
|
6
|
+
import { useTheme } from "@mui/material/styles";
|
|
7
|
+
const SIZES = {
|
|
8
|
+
medium: { pad: 4, minHeight: 38, px: 2, fontSize: "0.9375rem" },
|
|
9
|
+
small: { pad: 3, minHeight: 30, px: 1.25, fontSize: "0.8125rem" }
|
|
10
|
+
};
|
|
11
|
+
function SectionTabs({ size = "medium", children, sx, ...rest }) {
|
|
12
|
+
const { rt } = useTheme();
|
|
13
|
+
const radius = rt?.radii?.button ?? 8;
|
|
14
|
+
const { pad, minHeight, px, fontSize } = SIZES[size] ?? SIZES.medium;
|
|
15
|
+
if (rt?.ui?.tabs === "underline") {
|
|
16
|
+
const tabs = Children.map(children, (child) => isValidElement(child) && child.props?.value !== void 0 ? cloneElement(child, {
|
|
17
|
+
id: tabId(child.props.value),
|
|
18
|
+
"aria-controls": tabPanelId(child.props.value)
|
|
19
|
+
}) : child);
|
|
20
|
+
return /* @__PURE__ */ jsx(
|
|
21
|
+
Tabs,
|
|
22
|
+
{
|
|
23
|
+
variant: "scrollable",
|
|
24
|
+
scrollButtons: "auto",
|
|
25
|
+
sx: [{ minHeight: 0, "& .MuiTab-root": { minHeight: size === "small" ? 36 : 48, fontSize, px: 2 } }, ...Array.isArray(sx) ? sx : [sx]],
|
|
26
|
+
...rest,
|
|
27
|
+
children: tabs
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
Tabs,
|
|
33
|
+
{
|
|
34
|
+
variant: "scrollable",
|
|
35
|
+
scrollButtons: "auto",
|
|
36
|
+
sx: [
|
|
37
|
+
(theme) => ({
|
|
38
|
+
minHeight: 0,
|
|
39
|
+
// inline-flex so the strip hugs its tabs. Stretched full-width it
|
|
40
|
+
// would read as a toolbar, and the ground would run out past the
|
|
41
|
+
// last tab to the edge of the page.
|
|
42
|
+
display: "inline-flex",
|
|
43
|
+
maxWidth: "100%",
|
|
44
|
+
p: `${pad}px`,
|
|
45
|
+
borderRadius: `${radius + pad}px`,
|
|
46
|
+
/*
|
|
47
|
+
* The WELL: a BLACK wash, in both modes, closed with a hairline.
|
|
48
|
+
*
|
|
49
|
+
* `action.hover` was the obvious choice and it is wrong here, because
|
|
50
|
+
* MUI flips it to a WHITE overlay in dark mode. That made the track
|
|
51
|
+
* lighter than the page while the chip stayed at `background.paper`,
|
|
52
|
+
* so on every dark theme the current tab was DARKER than the track it
|
|
53
|
+
* sat in — the depth cue exactly inverted, a chip that reads as a
|
|
54
|
+
* hole. Measured on `editorial` dark: chip #1B1D24 inside a #292722
|
|
55
|
+
* track.
|
|
56
|
+
*
|
|
57
|
+
* Darkening in both modes keeps one rule true everywhere: the track
|
|
58
|
+
* is below the page, the chip is at page level, so the chip is always
|
|
59
|
+
* the thing standing up.
|
|
60
|
+
*/
|
|
61
|
+
bgcolor: "rgba(0, 0, 0, 0.05)",
|
|
62
|
+
...theme.applyStyles?.("dark", { backgroundColor: "rgba(0, 0, 0, 0.32)" }),
|
|
63
|
+
border: 1,
|
|
64
|
+
borderColor: "divider",
|
|
65
|
+
"& .MuiTabs-list": { gap: `${pad}px` },
|
|
66
|
+
/*
|
|
67
|
+
* The indicator IS the chip: full height, behind the labels. It is
|
|
68
|
+
* painted after the tabs in the DOM, so the tabs need the z-index —
|
|
69
|
+
* without it the chip covers its own label.
|
|
70
|
+
*
|
|
71
|
+
* `background.paper` over `action.hover` is the whole illusion: the
|
|
72
|
+
* chip is the one thing here at page level, so it looks lifted OUT of
|
|
73
|
+
* the well rather than painted onto it.
|
|
74
|
+
*
|
|
75
|
+
* PAPER + A `divider` HAIRLINE + `rt.lift.rest` — the same three
|
|
76
|
+
* things a raised input and a raised page button are made of, so the
|
|
77
|
+
* current tab, a search field and an outlined action all read as one
|
|
78
|
+
* kind of surface. It used to carry a flat grey shadow and a fake 1px
|
|
79
|
+
* ring painted as a second box-shadow layer, which is a hardcoded
|
|
80
|
+
* approximation of both: the ring did not follow `divider` when a
|
|
81
|
+
* theme changed its hairline, and the shadow was neutral grey under
|
|
82
|
+
* every palette while everything beside it was tinted.
|
|
83
|
+
*
|
|
84
|
+
* `lift.rest` and not `raised`: the chip is at page level, not
|
|
85
|
+
* floating above it, and a tab is not an action — the pages this sits
|
|
86
|
+
* on already argue that at length above.
|
|
87
|
+
*/
|
|
88
|
+
"& .MuiTabs-indicator": {
|
|
89
|
+
height: "100%",
|
|
90
|
+
borderRadius: `${radius}px`,
|
|
91
|
+
bgcolor: "background.paper",
|
|
92
|
+
border: "1px solid",
|
|
93
|
+
borderColor: "divider",
|
|
94
|
+
// Fallbacks for a bare MUI theme, which carries no `rt`.
|
|
95
|
+
boxShadow: rt?.lift?.rest ?? theme.shadows[1],
|
|
96
|
+
...theme.applyStyles?.("dark", { boxShadow: rt?.lift?.restDark ?? theme.shadows[1] })
|
|
97
|
+
},
|
|
98
|
+
"& .MuiTab-root": {
|
|
99
|
+
zIndex: 1,
|
|
100
|
+
minWidth: 0,
|
|
101
|
+
// MUI's 90px floor makes "Body" a pill wider than its word
|
|
102
|
+
minHeight,
|
|
103
|
+
px,
|
|
104
|
+
borderRadius: `${radius}px`,
|
|
105
|
+
textTransform: "none",
|
|
106
|
+
fontSize,
|
|
107
|
+
fontWeight: 600,
|
|
108
|
+
color: "text.secondary",
|
|
109
|
+
transition: "color 150ms",
|
|
110
|
+
"&:hover": { color: "text.primary" },
|
|
111
|
+
/*
|
|
112
|
+
* The accent lands HERE — on the label of the raised chip, over
|
|
113
|
+
* paper. `primary.main` is guaranteed readable on `background.paper`
|
|
114
|
+
* (it is what every link and text Button in the app already does);
|
|
115
|
+
* `contrastText` would be the pairing for an accent FILL, which is
|
|
116
|
+
* exactly the button look this control is avoiding.
|
|
117
|
+
*/
|
|
118
|
+
"&.Mui-selected": { color: "primary.main" }
|
|
119
|
+
}
|
|
120
|
+
}),
|
|
121
|
+
...Array.isArray(sx) ? sx : [sx]
|
|
122
|
+
],
|
|
123
|
+
...rest,
|
|
124
|
+
children
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const tabId = (value) => `rt-tab-${value}`;
|
|
129
|
+
const tabPanelId = (value) => `rt-tabpanel-${value}`;
|
|
130
|
+
function SectionTabPanel({ value, active, children }) {
|
|
131
|
+
if (value !== active) return null;
|
|
132
|
+
return /* @__PURE__ */ jsx(Box, { role: "tabpanel", id: tabPanelId(value), "aria-labelledby": tabId(value), tabIndex: 0, children });
|
|
133
|
+
}
|
|
134
|
+
export {
|
|
135
|
+
SectionTabPanel,
|
|
136
|
+
SectionTabs as default,
|
|
137
|
+
tabId,
|
|
138
|
+
tabPanelId
|
|
139
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useState } from "react";
|
|
4
|
+
import NextLink from "next/link";
|
|
5
|
+
import Box from "@mui/material/Box";
|
|
6
|
+
import ButtonBase from "@mui/material/ButtonBase";
|
|
7
|
+
import Container from "@mui/material/Container";
|
|
8
|
+
import Menu from "@mui/material/Menu";
|
|
9
|
+
import MenuItem from "@mui/material/MenuItem";
|
|
10
|
+
import ExpandMoreRoundedIcon from "@mui/icons-material/ExpandMoreRounded";
|
|
11
|
+
import { useTheme } from "@mui/material/styles";
|
|
12
|
+
import { useTranslations } from "next-intl";
|
|
13
|
+
import useOverflowTabs from "../hooks/useOverflowTabs.js";
|
|
14
|
+
import { WELL_PAD, underlineRowSx, underlineTabSx, wellSx, wellTabSx } from "../lib/navTabSx.js";
|
|
15
|
+
function SiteNav({ items, label, moreLabel, hideBelow = "md", sx }) {
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
const t = useTranslations("uikit");
|
|
18
|
+
const barH = theme.rt?.barHeight ?? null;
|
|
19
|
+
const navH = theme.rt?.navHeight ?? 48;
|
|
20
|
+
const underline = theme.rt?.ui?.tabs === "underline";
|
|
21
|
+
const navLabel = label ?? t("nav.sections");
|
|
22
|
+
const more = moreLabel ?? t("nav.more");
|
|
23
|
+
const activeIndex = items.findIndex((i) => i.active);
|
|
24
|
+
const { containerRef, measureRef, visibleCount } = useOverflowTabs(items.length, activeIndex);
|
|
25
|
+
let shown = items.slice(0, visibleCount);
|
|
26
|
+
let hidden = items.slice(visibleCount);
|
|
27
|
+
if (activeIndex >= visibleCount) {
|
|
28
|
+
const active = items[activeIndex];
|
|
29
|
+
shown = [...items.slice(0, Math.max(0, visibleCount - 1)), active];
|
|
30
|
+
hidden = items.filter((i) => !shown.includes(i));
|
|
31
|
+
}
|
|
32
|
+
const hiddenActive = hidden.some((i) => i.active);
|
|
33
|
+
const [anchor, setAnchor] = useState(null);
|
|
34
|
+
const menuId = useId();
|
|
35
|
+
const tabSx = (active) => underline ? underlineTabSx(active) : wellTabSx(theme, active);
|
|
36
|
+
const rowSx = underline ? underlineRowSx() : wellSx(theme);
|
|
37
|
+
return /* @__PURE__ */ jsxs(
|
|
38
|
+
Box,
|
|
39
|
+
{
|
|
40
|
+
component: "nav",
|
|
41
|
+
"aria-label": navLabel,
|
|
42
|
+
"data-tabs": underline ? "underline" : "well",
|
|
43
|
+
sx: [
|
|
44
|
+
{
|
|
45
|
+
display: hideBelow ? { xs: "none", [hideBelow]: "block" } : "block",
|
|
46
|
+
position: "sticky",
|
|
47
|
+
top: barH && barH !== 64 ? barH : { xs: 56, sm: 64 },
|
|
48
|
+
zIndex: (th) => th.zIndex.appBar - 1,
|
|
49
|
+
bgcolor: "background.paper",
|
|
50
|
+
borderBottom: "1px solid",
|
|
51
|
+
borderColor: "divider"
|
|
52
|
+
},
|
|
53
|
+
...Array.isArray(sx) ? sx : [sx]
|
|
54
|
+
],
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ jsxs(Container, { maxWidth: false, sx: { position: "relative" }, children: [
|
|
57
|
+
/* @__PURE__ */ jsx(Box, { ref: containerRef, sx: { display: "flex", justifyContent: "center", alignItems: "center", minHeight: navH }, children: /* @__PURE__ */ jsxs(Box, { sx: rowSx, children: [
|
|
58
|
+
shown.map((i) => /* @__PURE__ */ jsx(Box, { component: NextLink, href: i.href, "aria-current": i.active ? "page" : void 0, sx: tabSx(i.active), children: i.label }, i.key ?? i.href)),
|
|
59
|
+
hidden.length > 0 ? /* @__PURE__ */ jsxs(
|
|
60
|
+
ButtonBase,
|
|
61
|
+
{
|
|
62
|
+
onClick: (e) => setAnchor(e.currentTarget),
|
|
63
|
+
"aria-haspopup": "menu",
|
|
64
|
+
"aria-expanded": anchor ? "true" : "false",
|
|
65
|
+
"aria-controls": anchor ? menuId : void 0,
|
|
66
|
+
"aria-current": hiddenActive ? "page" : void 0,
|
|
67
|
+
sx: { ...tabSx(hiddenActive), pr: 1.25 },
|
|
68
|
+
children: [
|
|
69
|
+
more,
|
|
70
|
+
/* @__PURE__ */ jsx(ExpandMoreRoundedIcon, { sx: { fontSize: 18, ml: -0.25 } })
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
) : null
|
|
74
|
+
] }) }),
|
|
75
|
+
/* @__PURE__ */ jsxs(
|
|
76
|
+
Box,
|
|
77
|
+
{
|
|
78
|
+
ref: measureRef,
|
|
79
|
+
"aria-hidden": "true",
|
|
80
|
+
sx: {
|
|
81
|
+
...rowSx,
|
|
82
|
+
position: "absolute",
|
|
83
|
+
top: 0,
|
|
84
|
+
left: 0,
|
|
85
|
+
height: 0,
|
|
86
|
+
overflow: "hidden",
|
|
87
|
+
visibility: "hidden",
|
|
88
|
+
pointerEvents: "none",
|
|
89
|
+
maxWidth: "none",
|
|
90
|
+
...underline ? null : { gap: `${WELL_PAD}px` }
|
|
91
|
+
},
|
|
92
|
+
children: [
|
|
93
|
+
items.map((i) => /* @__PURE__ */ jsx(Box, { component: "span", sx: tabSx(false), children: i.label }, i.key ?? i.href)),
|
|
94
|
+
/* @__PURE__ */ jsxs(Box, { component: "span", sx: { ...tabSx(false), pr: 1.25 }, children: [
|
|
95
|
+
more,
|
|
96
|
+
/* @__PURE__ */ jsx(ExpandMoreRoundedIcon, { sx: { fontSize: 18, ml: -0.25 } })
|
|
97
|
+
] })
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
] }),
|
|
102
|
+
hidden.length > 0 ? /* @__PURE__ */ jsx(
|
|
103
|
+
Menu,
|
|
104
|
+
{
|
|
105
|
+
id: menuId,
|
|
106
|
+
anchorEl: anchor,
|
|
107
|
+
open: Boolean(anchor),
|
|
108
|
+
onClose: () => setAnchor(null),
|
|
109
|
+
slotProps: { list: { "aria-label": more } },
|
|
110
|
+
children: hidden.map((i) => /* @__PURE__ */ jsx(
|
|
111
|
+
MenuItem,
|
|
112
|
+
{
|
|
113
|
+
component: NextLink,
|
|
114
|
+
href: i.href,
|
|
115
|
+
selected: i.active,
|
|
116
|
+
"aria-current": i.active ? "page" : void 0,
|
|
117
|
+
onClick: () => setAnchor(null),
|
|
118
|
+
children: i.label
|
|
119
|
+
},
|
|
120
|
+
i.key ?? i.href
|
|
121
|
+
))
|
|
122
|
+
}
|
|
123
|
+
) : null
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
export {
|
|
129
|
+
SiteNav as default
|
|
130
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Box from "@mui/material/Box";
|
|
4
|
+
import Paper from "@mui/material/Paper";
|
|
5
|
+
import Skeleton from "@mui/material/Skeleton";
|
|
6
|
+
import Stack from "@mui/material/Stack";
|
|
7
|
+
function TableSkeleton({ rows = 8, columns = 5 }) {
|
|
8
|
+
return /* @__PURE__ */ jsxs(Paper, { variant: "outlined", sx: { borderRadius: (t) => `${t.rt?.radii?.card ?? 10}px`, overflow: "hidden" }, children: [
|
|
9
|
+
/* @__PURE__ */ jsxs(Box, { sx: { px: 2, py: 1.5, borderBottom: 1, borderColor: "divider", display: "flex", gap: 1.5, alignItems: "center" }, children: [
|
|
10
|
+
/* @__PURE__ */ jsx(Skeleton, { animation: "wave", variant: "rounded", width: 320, height: 36 }),
|
|
11
|
+
/* @__PURE__ */ jsx(Skeleton, { animation: "wave", variant: "rounded", width: 90, height: 30 })
|
|
12
|
+
] }),
|
|
13
|
+
/* @__PURE__ */ jsx(Box, { sx: { p: 2 }, children: /* @__PURE__ */ jsx(Stack, { spacing: 1.5, children: Array.from({ length: rows }).map((_, row) => /* @__PURE__ */ jsx(Stack, { direction: "row", spacing: 2, sx: { alignItems: "center" }, children: Array.from({ length: columns }).map((_2, col) => /* @__PURE__ */ jsx(
|
|
14
|
+
Skeleton,
|
|
15
|
+
{
|
|
16
|
+
animation: "wave",
|
|
17
|
+
height: 20,
|
|
18
|
+
sx: { flex: col === 0 ? 2 : 1 }
|
|
19
|
+
},
|
|
20
|
+
col
|
|
21
|
+
)) }, row)) }) })
|
|
22
|
+
] });
|
|
23
|
+
}
|
|
24
|
+
function CardListSkeleton({ count = 4, height = 84 }) {
|
|
25
|
+
return /* @__PURE__ */ jsx(Stack, { spacing: 1.5, children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { animation: "wave", variant: "rounded", height, sx: { borderRadius: (t) => `${t.rt?.radii?.card ?? 10}px` } }, i)) });
|
|
26
|
+
}
|
|
27
|
+
function FormSkeleton({ fields = 4 }) {
|
|
28
|
+
return /* @__PURE__ */ jsxs(Stack, { spacing: 3, children: [
|
|
29
|
+
/* @__PURE__ */ jsx(Skeleton, { animation: "wave", variant: "text", width: 200, height: 36 }),
|
|
30
|
+
Array.from({ length: fields }).map((_, i) => /* @__PURE__ */ jsxs(Box, { children: [
|
|
31
|
+
/* @__PURE__ */ jsx(Skeleton, { animation: "wave", variant: "text", width: 120, height: 18, sx: { mb: 0.75 } }),
|
|
32
|
+
/* @__PURE__ */ jsx(Skeleton, { animation: "wave", variant: "rounded", height: 40 })
|
|
33
|
+
] }, i))
|
|
34
|
+
] });
|
|
35
|
+
}
|
|
36
|
+
function ShellSkeleton() {
|
|
37
|
+
return /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", minHeight: "100dvh" }, "aria-hidden": true, children: [
|
|
38
|
+
/* @__PURE__ */ jsx(Box, { sx: { width: { xs: 0, md: 260 }, flexShrink: 0, borderRight: { md: 1 }, borderColor: "divider", p: 2, display: { xs: "none", md: "block" } }, children: /* @__PURE__ */ jsx(Stack, { spacing: 1.5, sx: { mt: 9 }, children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { animation: "wave", variant: "rounded", height: 34 }, i)) }) }),
|
|
39
|
+
/* @__PURE__ */ jsxs(Box, { sx: { flexGrow: 1, minWidth: 0 }, children: [
|
|
40
|
+
/* @__PURE__ */ jsxs(Box, { sx: { height: 64, borderBottom: 1, borderColor: "divider", display: "flex", alignItems: "center", px: 2, gap: 2 }, children: [
|
|
41
|
+
/* @__PURE__ */ jsx(Skeleton, { animation: "wave", variant: "circular", width: 28, height: 28 }),
|
|
42
|
+
/* @__PURE__ */ jsx(Skeleton, { animation: "wave", variant: "text", width: 180, height: 24 })
|
|
43
|
+
] }),
|
|
44
|
+
/* @__PURE__ */ jsx(Box, { sx: { p: 3 }, children: /* @__PURE__ */ jsx(CardListSkeleton, { count: 3, height: 120 }) })
|
|
45
|
+
] })
|
|
46
|
+
] });
|
|
47
|
+
}
|
|
48
|
+
const PAGE_SHAPES = {
|
|
49
|
+
cards: (props) => /* @__PURE__ */ jsx(CardListSkeleton, { count: 3, height: 120, ...props }),
|
|
50
|
+
table: (props) => /* @__PURE__ */ jsx(TableSkeleton, { rows: 8, columns: 5, ...props }),
|
|
51
|
+
form: (props) => /* @__PURE__ */ jsx(FormSkeleton, { fields: 5, ...props })
|
|
52
|
+
};
|
|
53
|
+
function PageSkeleton({ shape = "cards", ...props }) {
|
|
54
|
+
const Render = PAGE_SHAPES[shape] ?? PAGE_SHAPES.cards;
|
|
55
|
+
return /* @__PURE__ */ jsx(Box, { sx: { py: 1 }, children: Render(props) });
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
CardListSkeleton,
|
|
59
|
+
FormSkeleton,
|
|
60
|
+
PageSkeleton,
|
|
61
|
+
ShellSkeleton,
|
|
62
|
+
TableSkeleton
|
|
63
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { DndContext, DragOverlay, closestCenter, PointerSensor, KeyboardSensor, useSensor, useSensors } from "@dnd-kit/core";
|
|
6
|
+
import { SortableContext, verticalListSortingStrategy, useSortable, sortableKeyboardCoordinates } from "@dnd-kit/sortable";
|
|
7
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
8
|
+
import Box from "@mui/material/Box";
|
|
9
|
+
import IconButton from "@mui/material/IconButton";
|
|
10
|
+
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
|
11
|
+
import { useOrgThemeScope } from "../theme/OrgThemeRegistry.js";
|
|
12
|
+
const overlayHandle = /* @__PURE__ */ jsx(
|
|
13
|
+
IconButton,
|
|
14
|
+
{
|
|
15
|
+
size: "small",
|
|
16
|
+
"aria-hidden": true,
|
|
17
|
+
tabIndex: -1,
|
|
18
|
+
sx: { cursor: "grabbing", color: "text.disabled", flexShrink: 0 },
|
|
19
|
+
children: /* @__PURE__ */ jsx(DragIndicatorIcon, { fontSize: "small" })
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
function SortableList({ ids, onMove, children, renderOverlay }) {
|
|
23
|
+
const [activeId, setActiveId] = useState(null);
|
|
24
|
+
const themeScope = useOrgThemeScope();
|
|
25
|
+
const sensors = useSensors(
|
|
26
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
27
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
28
|
+
);
|
|
29
|
+
function onDragEnd(e) {
|
|
30
|
+
setActiveId(null);
|
|
31
|
+
const { active, over } = e;
|
|
32
|
+
if (!over || active.id === over.id) return;
|
|
33
|
+
const from = ids.indexOf(active.id);
|
|
34
|
+
const to = ids.indexOf(over.id);
|
|
35
|
+
if (from !== -1 && to !== -1) onMove(from, to);
|
|
36
|
+
}
|
|
37
|
+
return /* @__PURE__ */ jsxs(
|
|
38
|
+
DndContext,
|
|
39
|
+
{
|
|
40
|
+
sensors,
|
|
41
|
+
collisionDetection: closestCenter,
|
|
42
|
+
onDragStart: (e) => setActiveId(e.active.id),
|
|
43
|
+
onDragEnd,
|
|
44
|
+
onDragCancel: () => setActiveId(null),
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsx(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children }),
|
|
47
|
+
/* @__PURE__ */ jsx(DragOverlay, { children: activeId != null && renderOverlay ? /* @__PURE__ */ jsx("div", { className: themeScope, children: renderOverlay(activeId) }) : null })
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function SortableItem({ id, disabled, children }) {
|
|
53
|
+
const t = useTranslations("uikit");
|
|
54
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id, disabled });
|
|
55
|
+
const style = {
|
|
56
|
+
// Translate only — never scale. Shifting siblings should slide, not stretch.
|
|
57
|
+
transform: CSS.Translate.toString(transform),
|
|
58
|
+
transition,
|
|
59
|
+
// The row the user picked up is drawn by the DragOverlay; leave a dimmed
|
|
60
|
+
// placeholder in the flow so the drop target stays visible.
|
|
61
|
+
opacity: isDragging ? 0.4 : 1
|
|
62
|
+
};
|
|
63
|
+
const handle = /* @__PURE__ */ jsx(
|
|
64
|
+
IconButton,
|
|
65
|
+
{
|
|
66
|
+
size: "small",
|
|
67
|
+
"aria-label": t("sortable.drag"),
|
|
68
|
+
disabled,
|
|
69
|
+
sx: { cursor: disabled ? "default" : "grab", touchAction: "none", color: "text.disabled", flexShrink: 0 },
|
|
70
|
+
...attributes,
|
|
71
|
+
...listeners,
|
|
72
|
+
children: /* @__PURE__ */ jsx(DragIndicatorIcon, { fontSize: "small" })
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
return /* @__PURE__ */ jsx(Box, { ref: setNodeRef, style, children: children(handle) });
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
SortableItem,
|
|
79
|
+
SortableList,
|
|
80
|
+
overlayHandle
|
|
81
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Box from "@mui/material/Box";
|
|
4
|
+
import Paper from "@mui/material/Paper";
|
|
5
|
+
import Stack from "@mui/material/Stack";
|
|
6
|
+
import Typography from "@mui/material/Typography";
|
|
7
|
+
import { useTheme } from "@mui/material/styles";
|
|
8
|
+
const GRAD = "linear-gradient(135deg, var(--rt-grad-from), var(--rt-grad-to))";
|
|
9
|
+
const SIZES = {
|
|
10
|
+
medium: { p: 2.5, minHeight: 132, gap: 1.5, value: 30, label: "subtitle2" },
|
|
11
|
+
compact: { p: 2, minHeight: 88, gap: 0.75, value: 24, label: "body2" }
|
|
12
|
+
};
|
|
13
|
+
function StatTile({ label, value, caption, icon, chart, size = "medium", sx, ...rest }) {
|
|
14
|
+
const theme = useTheme();
|
|
15
|
+
const m = SIZES[size] ?? SIZES.medium;
|
|
16
|
+
const gradient = theme.rt?.ui?.stat === "gradient";
|
|
17
|
+
const pc = theme.vars?.palette?.primary?.mainChannel ?? theme.palette.primary.mainChannel;
|
|
18
|
+
const gradientSx = gradient ? {
|
|
19
|
+
backgroundImage: GRAD,
|
|
20
|
+
color: theme.vars?.palette?.primary?.contrastText ?? theme.palette.primary.contrastText,
|
|
21
|
+
border: "none",
|
|
22
|
+
boxShadow: `0 6px 18px -8px rgba(${pc} / 0.45)`
|
|
23
|
+
} : {};
|
|
24
|
+
return /* @__PURE__ */ jsxs(
|
|
25
|
+
Paper,
|
|
26
|
+
{
|
|
27
|
+
"data-treatment": gradient ? "gradient" : "plain",
|
|
28
|
+
"data-size": size,
|
|
29
|
+
sx: { p: m.p, display: "flex", flexDirection: "column", gap: m.gap, minHeight: m.minHeight, ...gradientSx, ...sx },
|
|
30
|
+
...rest,
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1.5, sx: { alignItems: "flex-start" }, children: [
|
|
33
|
+
icon ? /* @__PURE__ */ jsx(
|
|
34
|
+
Box,
|
|
35
|
+
{
|
|
36
|
+
"aria-hidden": true,
|
|
37
|
+
sx: {
|
|
38
|
+
width: 40,
|
|
39
|
+
height: 40,
|
|
40
|
+
borderRadius: 2,
|
|
41
|
+
flexShrink: 0,
|
|
42
|
+
display: "grid",
|
|
43
|
+
placeItems: "center",
|
|
44
|
+
// On the gradient, a translucent chip in the tile's own text colour;
|
|
45
|
+
// on the plain surface, a soft accent-tinted chip with the accent icon.
|
|
46
|
+
...gradient ? { backgroundColor: "rgba(255,255,255,0.20)", color: "inherit" } : { backgroundColor: `rgba(${pc} / 0.12)`, color: "primary.main" }
|
|
47
|
+
},
|
|
48
|
+
children: icon
|
|
49
|
+
}
|
|
50
|
+
) : null,
|
|
51
|
+
/* @__PURE__ */ jsxs(Box, { sx: { minWidth: 0 }, children: [
|
|
52
|
+
/* @__PURE__ */ jsx(Typography, { variant: m.label, sx: { fontWeight: 600, lineHeight: 1.25 }, children: label }),
|
|
53
|
+
caption ? /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: gradient ? { opacity: 0.82 } : { color: "text.secondary" }, children: caption }) : null
|
|
54
|
+
] })
|
|
55
|
+
] }),
|
|
56
|
+
/* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 1, mt: "auto" }, children: [
|
|
57
|
+
/* @__PURE__ */ jsx(Typography, { component: "div", sx: { fontSize: m.value, fontWeight: 700, lineHeight: 1, letterSpacing: "-0.01em", fontVariantNumeric: "tabular-nums" }, children: value }),
|
|
58
|
+
chart ? /* @__PURE__ */ jsx(Box, { sx: { flexShrink: 0, opacity: gradient ? 0.9 : 1 }, children: chart }) : null
|
|
59
|
+
] })
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
StatTile as default
|
|
66
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import Chip from "@mui/material/Chip";
|
|
4
|
+
import { useTheme } from "@mui/material/styles";
|
|
5
|
+
import { chipColor, tagStyle } from "../lib/chipTone.js";
|
|
6
|
+
function StatusChip({ role = "idle", tagKey, tagIndex, ...rest }) {
|
|
7
|
+
const theme = useTheme();
|
|
8
|
+
const secondaryIsOwnColour = theme.palette.secondary?.main !== theme.palette.info?.main && theme.palette.secondary?.main !== theme.palette.text?.secondary;
|
|
9
|
+
if (role === "tag") {
|
|
10
|
+
const { color, variant } = tagStyle(tagKey ?? rest.label, secondaryIsOwnColour, tagIndex);
|
|
11
|
+
return /* @__PURE__ */ jsx(Chip, { color, variant, ...rest });
|
|
12
|
+
}
|
|
13
|
+
return /* @__PURE__ */ jsx(Chip, { color: chipColor(role, tagKey ?? rest.label, secondaryIsOwnColour), ...rest });
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
StatusChip as default
|
|
17
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
import { chipColor } from "../lib/chipTone.js";
|
|
6
|
+
function StatusDot({ role = "idle", label, size = 8, sx, ...rest }) {
|
|
7
|
+
const theme = useTheme();
|
|
8
|
+
const palette = chipColor(role, label);
|
|
9
|
+
const hidden = rest["aria-hidden"] === true || rest["aria-hidden"] === "true";
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
Box,
|
|
12
|
+
{
|
|
13
|
+
component: "span",
|
|
14
|
+
role: hidden ? void 0 : "img",
|
|
15
|
+
"aria-label": hidden ? void 0 : label,
|
|
16
|
+
title: hidden ? void 0 : label,
|
|
17
|
+
...rest,
|
|
18
|
+
sx: [
|
|
19
|
+
{
|
|
20
|
+
display: "inline-block",
|
|
21
|
+
flexShrink: 0,
|
|
22
|
+
width: size,
|
|
23
|
+
height: size,
|
|
24
|
+
borderRadius: "50%",
|
|
25
|
+
/*
|
|
26
|
+
* `default` is the one role with no palette entry of its own — it is
|
|
27
|
+
* the absence of a colour owed, which reads as the muted mark a
|
|
28
|
+
* disabled control uses rather than as a hue.
|
|
29
|
+
*/
|
|
30
|
+
bgcolor: palette === "default" ? "text.disabled" : `${palette}.main`,
|
|
31
|
+
// A pale theme's success on a pale ground is a dot you have to look
|
|
32
|
+
// for; the ring is what makes it land at 8px.
|
|
33
|
+
boxShadow: palette === "default" ? "none" : `0 0 0 2px rgba(${theme.vars.palette[palette].mainChannel} / 0.18)`
|
|
34
|
+
},
|
|
35
|
+
...Array.isArray(sx) ? sx : [sx]
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
StatusDot as default
|
|
42
|
+
};
|