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,81 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import AppBar from "@mui/material/AppBar";
5
+ import Toolbar from "@mui/material/Toolbar";
6
+ import Box from "@mui/material/Box";
7
+ import Divider from "@mui/material/Divider";
8
+ import Drawer from "@mui/material/Drawer";
9
+ import IconButton from "@mui/material/IconButton";
10
+ import List from "@mui/material/List";
11
+ import ListItem from "@mui/material/ListItem";
12
+ import ListItemButton from "@mui/material/ListItemButton";
13
+ import ListItemText from "@mui/material/ListItemText";
14
+ import Typography from "@mui/material/Typography";
15
+ import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
16
+ import MenuIcon from "@mui/icons-material/Menu";
17
+ import Link from "next/link";
18
+ import { useTheme } from "@mui/material/styles";
19
+ import { useTranslations } from "next-intl";
20
+ import ModeToggle from "./ModeToggle.js";
21
+ import LanguageSwitcher from "./LanguageSwitcher.js";
22
+ import { useBrand } from "./BrandProvider.js";
23
+ import BrandLink from "./BrandLink.js";
24
+ function BrandHeader({ siteName: siteNameProp, href = "/", logoUrl = null, navItems = [], actions = null }) {
25
+ const brand = useBrand();
26
+ const siteName = siteNameProp === void 0 ? brand.siteName : siteNameProp;
27
+ const { rt } = useTheme();
28
+ const t = useTranslations("uikit");
29
+ const barH = rt?.barHeight ?? null;
30
+ const [open, setOpen] = useState(false);
31
+ const hasNav = navItems.length > 0;
32
+ return /* @__PURE__ */ jsxs(AppBar, { position: "sticky", children: [
33
+ /* @__PURE__ */ jsxs(Toolbar, { sx: barH && barH !== 64 ? { gap: 2, minHeight: `${barH}px !important`, px: 2 } : { gap: 2 }, children: [
34
+ hasNav ? /* @__PURE__ */ jsx(
35
+ IconButton,
36
+ {
37
+ edge: "start",
38
+ onClick: () => setOpen(true),
39
+ sx: { display: { md: "none" }, mr: -1 },
40
+ "aria-label": t("nav.open"),
41
+ children: /* @__PURE__ */ jsx(MenuIcon, {})
42
+ }
43
+ ) : null,
44
+ /* @__PURE__ */ jsx(BrandLink, { siteName, href, logoUrl }),
45
+ /* @__PURE__ */ jsx(Box, { sx: { flexGrow: 1 } }),
46
+ actions ? /* @__PURE__ */ jsx(Box, { sx: { flexShrink: 0, display: "flex", alignItems: "center" }, children: actions }) : null,
47
+ /* @__PURE__ */ jsxs(Box, { sx: { display: hasNav ? { xs: "none", md: "contents" } : "contents" }, children: [
48
+ /* @__PURE__ */ jsx(LanguageSwitcher, {}),
49
+ /* @__PURE__ */ jsx(ModeToggle, {})
50
+ ] })
51
+ ] }),
52
+ hasNav ? /* @__PURE__ */ jsxs(
53
+ Drawer,
54
+ {
55
+ anchor: "right",
56
+ open,
57
+ onClose: () => setOpen(false),
58
+ sx: { display: { md: "none" } },
59
+ slotProps: { paper: { sx: { width: "min(78vw, 320px)" } } },
60
+ children: [
61
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 1, p: 1.5 }, children: [
62
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", sx: { color: "text.secondary" }, noWrap: true, children: siteName }),
63
+ /* @__PURE__ */ jsx(IconButton, { onClick: () => setOpen(false), size: "small", "aria-label": t("nav.close"), children: /* @__PURE__ */ jsx(CloseRoundedIcon, { fontSize: "small" }) })
64
+ ] }),
65
+ /* @__PURE__ */ jsx(Divider, {}),
66
+ /* @__PURE__ */ jsx(List, { onClick: (e) => {
67
+ if (e.target.closest("a")) setOpen(false);
68
+ }, children: navItems.map((item) => /* @__PURE__ */ jsx(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsx(ListItemButton, { component: Link, href: item.href, children: /* @__PURE__ */ jsx(ListItemText, { primary: item.label }) }) }, item.href)) }),
69
+ /* @__PURE__ */ jsx(Divider, { sx: { mt: "auto" } }),
70
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, p: 1.5 }, children: [
71
+ /* @__PURE__ */ jsx(LanguageSwitcher, {}),
72
+ /* @__PURE__ */ jsx(ModeToggle, {})
73
+ ] })
74
+ ]
75
+ }
76
+ ) : null
77
+ ] });
78
+ }
79
+ export {
80
+ BrandHeader as default
81
+ };
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import Stack from "@mui/material/Stack";
4
+ import Typography from "@mui/material/Typography";
5
+ import Skeleton from "@mui/material/Skeleton";
6
+ import Link from "next/link";
7
+ import BrandMark from "./BrandMark.js";
8
+ import { useBrand } from "./BrandProvider.js";
9
+ function BrandLink({ siteName: siteNameProp, href = "/", logoUrl = null }) {
10
+ const brand = useBrand();
11
+ const siteName = siteNameProp === void 0 ? brand.siteName : siteNameProp;
12
+ if (!siteName && !logoUrl) return null;
13
+ const external = /^https?:\/\//.test(href);
14
+ const linkProps = external ? { component: "a", href, target: "_blank", rel: "noopener noreferrer" } : { component: Link, href };
15
+ return /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1.2, ...linkProps, sx: { alignItems: "center", textDecoration: "none", color: "inherit", minWidth: 0 }, children: [
16
+ /* @__PURE__ */ jsx(BrandMark, { size: 28, logoUrl, alt: "" }),
17
+ siteName ? /* @__PURE__ */ jsx(Typography, { variant: "h6", component: "span", className: "rt-wordmark", noWrap: true, sx: { minWidth: 0 }, children: siteName }) : /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: 104, sx: { fontSize: "1.25rem" } })
18
+ ] });
19
+ }
20
+ export {
21
+ BrandLink as default
22
+ };
@@ -0,0 +1,69 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import Box from "@mui/material/Box";
3
+ function BrandMark({ size = 32, logoUrl = null, alt = "", sx }) {
4
+ const radius = Math.round(size * 0.3);
5
+ const frame = {
6
+ width: size,
7
+ height: size,
8
+ flexShrink: 0,
9
+ borderRadius: `var(--rt-mark-radius, ${radius}px)`,
10
+ display: "grid",
11
+ placeItems: "center"
12
+ };
13
+ if (logoUrl) {
14
+ return /* @__PURE__ */ jsx(
15
+ Box,
16
+ {
17
+ component: "img",
18
+ src: logoUrl,
19
+ alt,
20
+ sx: [
21
+ {
22
+ ...frame,
23
+ /*
24
+ * A logo is sized by HEIGHT, not forced into the glyph's square.
25
+ * Most org logos are wordmarks — wide, not square — and a 3:1
26
+ * wordmark contained in a 28x28 box renders as an unreadable 28x9
27
+ * sliver. Fixing the height and letting the width follow gives a
28
+ * square logo exactly the glyph's footprint and a wordmark room to
29
+ * be legible, up to a cap so a very wide one can't push the nav off
30
+ * the header.
31
+ */
32
+ width: "auto",
33
+ maxWidth: size * 4,
34
+ // contain, never cover — a logo cropped to fill its box is a
35
+ // mangled logo.
36
+ objectFit: "contain",
37
+ objectPosition: "left center",
38
+ // No gradient and no glow behind a real logo: an org's own mark
39
+ // shouldn't be made to sit on our brand colour.
40
+ backgroundColor: "transparent"
41
+ },
42
+ ...Array.isArray(sx) ? sx : [sx]
43
+ ]
44
+ }
45
+ );
46
+ }
47
+ return /* @__PURE__ */ jsx(
48
+ Box,
49
+ {
50
+ "aria-hidden": true,
51
+ sx: [
52
+ {
53
+ ...frame,
54
+ backgroundImage: "linear-gradient(135deg, var(--rt-grad-from), var(--rt-grad-to))",
55
+ boxShadow: (theme) => `var(--rt-mark-shadow, 0 3px 9px -2px rgba(${theme.vars.palette.primary.mainChannel} / 0.34))`
56
+ },
57
+ ...Array.isArray(sx) ? sx : [sx]
58
+ ],
59
+ children: /* @__PURE__ */ jsxs(Box, { component: "svg", viewBox: "0 0 24 24", sx: { width: size * 0.58, height: size * 0.58 }, "aria-hidden": true, children: [
60
+ /* @__PURE__ */ jsx("rect", { x: "5", y: "6", width: "14", height: "2.8", rx: "1.4", fill: "#fff", opacity: "0.95" }),
61
+ /* @__PURE__ */ jsx("rect", { x: "5", y: "10.6", width: "14", height: "2.8", rx: "1.4", fill: "#fff", opacity: "0.7" }),
62
+ /* @__PURE__ */ jsx("rect", { x: "5", y: "15.2", width: "9", height: "2.8", rx: "1.4", fill: "#fff", opacity: "0.5" })
63
+ ] })
64
+ }
65
+ );
66
+ }
67
+ export {
68
+ BrandMark as default
69
+ };
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from "react";
4
+ const BrandContext = createContext({ siteName: null, copyright: null });
5
+ function BrandProvider({ siteName = null, copyright = null, children }) {
6
+ return /* @__PURE__ */ jsx(BrandContext.Provider, { value: { siteName, copyright }, children });
7
+ }
8
+ function useBrand() {
9
+ return useContext(BrandContext);
10
+ }
11
+ export {
12
+ BrandProvider as default,
13
+ useBrand
14
+ };
@@ -0,0 +1,62 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import MuiBreadcrumbs from "@mui/material/Breadcrumbs";
4
+ import Link from "@mui/material/Link";
5
+ import Typography from "@mui/material/Typography";
6
+ import NextLink from "next/link";
7
+ import { useTheme } from "@mui/material/styles";
8
+ import { useTranslations } from "next-intl";
9
+ import HomeOutlinedIcon from "@mui/icons-material/HomeOutlined";
10
+ import ChevronRightRoundedIcon from "@mui/icons-material/ChevronRightRounded";
11
+ function Breadcrumbs({ items, sx }) {
12
+ const t = useTranslations("uikit");
13
+ const { rt } = useTheme();
14
+ const shown = (items ?? []).filter((it) => it && it.label);
15
+ const glass = rt?.ui?.surface === "glass";
16
+ const fontFamily = glass ? rt?.mono : rt?.headingFont ?? void 0;
17
+ const separator = glass ? "/" : "\u203A";
18
+ const berry = rt?.ui?.chrome === "berry";
19
+ const firstHref = shown.find((it) => it.href)?.href ?? "";
20
+ const orgSlug = firstHref.split("/")[1];
21
+ const orgHome = orgSlug ? `/${orgSlug}` : null;
22
+ if (berry) {
23
+ return /* @__PURE__ */ jsxs(
24
+ MuiBreadcrumbs,
25
+ {
26
+ "aria-label": t("breadcrumb.aria"),
27
+ separator: /* @__PURE__ */ jsx(ChevronRightRoundedIcon, { sx: { fontSize: 16 } }),
28
+ sx: [
29
+ { mb: 2, fontSize: "0.75rem", "& .MuiBreadcrumbs-separator": { color: "text.secondary", mx: 0.5 }, "& .MuiBreadcrumbs-li": { minWidth: 0, display: "flex" } },
30
+ ...Array.isArray(sx) ? sx : [sx]
31
+ ],
32
+ children: [
33
+ berry && orgHome ? /* @__PURE__ */ jsx(Link, { component: NextLink, href: orgHome, sx: { display: "flex", color: "primary.main" }, "aria-label": t("route.dashboard"), children: /* @__PURE__ */ jsx(HomeOutlinedIcon, { sx: { fontSize: 16 } }) }) : null,
34
+ shown.map((it, i) => it.href && i !== shown.length - 1 ? /* @__PURE__ */ jsx(Link, { component: NextLink, href: it.href, underline: "hover", sx: { color: "text.secondary", fontSize: "0.75rem", fontWeight: 500 }, children: it.label }, i) : /* @__PURE__ */ jsx(Typography, { sx: { color: "text.primary", fontSize: "0.75rem", fontWeight: 500, minWidth: 0, overflowWrap: "anywhere" }, "aria-current": i === shown.length - 1 ? "page" : void 0, children: it.label }, i))
35
+ ]
36
+ }
37
+ );
38
+ }
39
+ return /* @__PURE__ */ jsx(
40
+ MuiBreadcrumbs,
41
+ {
42
+ "aria-label": t("breadcrumb.aria"),
43
+ separator,
44
+ sx: [
45
+ { mb: 2, fontFamily, fontSize: "1rem", "& .MuiBreadcrumbs-separator": { color: "text.secondary", fontWeight: 600 }, "& .MuiBreadcrumbs-li": { minWidth: 0 } },
46
+ ...Array.isArray(sx) ? sx : [sx]
47
+ ],
48
+ children: shown.map((it, i) => it.href && i !== shown.length - 1 ? (
49
+ // Ancestors carry the org ACCENT so they read as live navigation, not
50
+ // muted chrome — the accent is the theme's, so this stays on-brand
51
+ // under every palette (navigation, not status: green stays reserved).
52
+ /* @__PURE__ */ jsx(Link, { component: NextLink, href: it.href, underline: "hover", color: "primary", variant: "body1", sx: { fontFamily, fontWeight: 600 }, children: it.label }, i)
53
+ ) : (
54
+ // The current page is the loudest thing in the trail: full weight, ink.
55
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", sx: { color: "text.primary", fontFamily, fontWeight: 700, minWidth: 0, overflowWrap: "anywhere" }, "aria-current": i === shown.length - 1 ? "page" : void 0, children: it.label }, i)
56
+ ))
57
+ }
58
+ );
59
+ }
60
+ export {
61
+ Breadcrumbs as default
62
+ };
@@ -0,0 +1,46 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import Box from "@mui/material/Box";
3
+ import Typography from "@mui/material/Typography";
4
+ import { useHeadingLevel } from "./HeadingLevel.js";
5
+ function CardHead({ title, action = null, component }) {
6
+ return /* @__PURE__ */ jsxs(
7
+ Box,
8
+ {
9
+ sx: {
10
+ display: "flex",
11
+ alignItems: "center",
12
+ justifyContent: "space-between",
13
+ gap: 2,
14
+ flexWrap: "wrap",
15
+ rowGap: 1.5,
16
+ px: { xs: 2, sm: 3 },
17
+ py: 2,
18
+ borderBottom: 1,
19
+ borderColor: "divider"
20
+ },
21
+ children: [
22
+ title ? /* @__PURE__ */ jsx(CardHeadTitle, { component, children: title }) : /* @__PURE__ */ jsx("span", {}),
23
+ action
24
+ ]
25
+ }
26
+ );
27
+ }
28
+ function CardHeadTitle({ children, component }) {
29
+ const level = useHeadingLevel();
30
+ return (
31
+ // Matches SectionPanel's heading: 1.125rem/500 under a berry chrome.
32
+ /* @__PURE__ */ jsx(
33
+ Typography,
34
+ {
35
+ variant: "h6",
36
+ component: component ?? level,
37
+ sx: (t) => ({ minWidth: 0, ...t.rt?.ui?.surface === "berry" ? { fontSize: "1.125rem", fontWeight: 500 } : null }),
38
+ children
39
+ }
40
+ )
41
+ );
42
+ }
43
+ export {
44
+ CardHeadTitle,
45
+ CardHead as default
46
+ };
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useId, useRef, useState } from "react";
4
+ import { useTranslations } from "next-intl";
5
+ import Button from "@mui/material/Button";
6
+ import Dialog from "@mui/material/Dialog";
7
+ import DialogActions from "@mui/material/DialogActions";
8
+ import DialogContent from "@mui/material/DialogContent";
9
+ import DialogContentText from "@mui/material/DialogContentText";
10
+ import DialogTitle from "@mui/material/DialogTitle";
11
+ import { useOrgThemeScope } from "../theme/OrgThemeRegistry.js";
12
+ const TONES = {
13
+ default: { color: "primary", focus: "confirm" },
14
+ caution: { color: "warning", focus: "cancel" },
15
+ destructive: { color: "error", focus: "cancel" }
16
+ };
17
+ const ConfirmContext = createContext(() => Promise.resolve(false));
18
+ function useConfirm() {
19
+ return useContext(ConfirmContext);
20
+ }
21
+ function ConfirmProvider({ children }) {
22
+ const t = useTranslations("uikit");
23
+ const [opts, setOpts] = useState(null);
24
+ const resolver = useRef(null);
25
+ const themeScope = useOrgThemeScope();
26
+ const titleId = useId();
27
+ const bodyId = useId();
28
+ const confirm = useCallback((options) => new Promise((resolve) => {
29
+ resolver.current = resolve;
30
+ setOpts(options ?? {});
31
+ }), []);
32
+ const settle = (result) => {
33
+ resolver.current?.(result);
34
+ resolver.current = null;
35
+ setOpts(null);
36
+ };
37
+ const tone = TONES[opts?.tone] ?? TONES.default;
38
+ const message = opts?.message ?? opts?.body ?? null;
39
+ const confirmLabel = opts?.confirmLabel ?? (opts?.tone === "destructive" ? t("confirm.delete") : t("confirm.confirm"));
40
+ return /* @__PURE__ */ jsxs(ConfirmContext.Provider, { value: confirm, children: [
41
+ children,
42
+ /* @__PURE__ */ jsxs(
43
+ Dialog,
44
+ {
45
+ open: opts !== null,
46
+ onClose: () => settle(false),
47
+ maxWidth: "xs",
48
+ fullWidth: true,
49
+ className: themeScope,
50
+ "aria-labelledby": titleId,
51
+ "aria-describedby": message ? bodyId : void 0,
52
+ slotProps: { paper: { "data-tone": opts?.tone ?? "default" } },
53
+ children: [
54
+ /* @__PURE__ */ jsx(DialogTitle, { id: titleId, children: opts?.title ?? t("confirm.title") }),
55
+ message ? /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(DialogContentText, { id: bodyId, children: message }) }) : null,
56
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
57
+ /* @__PURE__ */ jsx(Button, { onClick: () => settle(false), color: "inherit", autoFocus: tone.focus === "cancel", children: opts?.cancelLabel ?? t("confirm.cancel") }),
58
+ /* @__PURE__ */ jsx(
59
+ Button,
60
+ {
61
+ onClick: () => settle(true),
62
+ color: tone.color,
63
+ variant: "contained",
64
+ autoFocus: tone.focus === "confirm",
65
+ children: confirmLabel
66
+ }
67
+ )
68
+ ] })
69
+ ]
70
+ }
71
+ )
72
+ ] });
73
+ }
74
+ export {
75
+ ConfirmProvider as default,
76
+ useConfirm
77
+ };