@godxjp/ui 18.4.0 → 18.5.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/README.md +16 -1
- package/dist/app/theme-axes.d.ts +11 -3
- package/dist/app/theme-axes.js +2 -1
- package/dist/components/charts/chart-frame.d.ts +17 -3
- package/dist/components/charts/chart-frame.js +7 -1
- package/dist/components/charts/chart-summary.d.ts +21 -0
- package/dist/components/charts/chart-summary.js +32 -5
- package/dist/components/charts/compact-bar-trend.d.ts +29 -0
- package/dist/components/charts/compact-bar-trend.js +74 -0
- package/dist/components/charts/index.d.ts +3 -0
- package/dist/components/charts/index.js +2 -0
- package/dist/components/data-display/badge.d.ts +8 -0
- package/dist/components/data-display/badge.js +7 -1
- package/dist/components/data-display/data-table.d.ts +16 -1
- package/dist/components/data-display/data-table.js +56 -0
- package/dist/components/data-display/index.d.ts +3 -1
- package/dist/components/data-display/index.js +10 -1
- package/dist/components/data-display/list-row.d.ts +22 -4
- package/dist/components/data-display/list-row.js +21 -3
- package/dist/components/data-display/service-launcher-card.d.ts +40 -0
- package/dist/components/data-display/service-launcher-card.js +97 -0
- package/dist/components/data-entry/command-palette.d.ts +37 -0
- package/dist/components/data-entry/command-palette.js +125 -0
- package/dist/components/data-entry/index.d.ts +2 -0
- package/dist/components/data-entry/index.js +2 -0
- package/dist/components/feedback/dialog.d.ts +2 -0
- package/dist/components/feedback/dialog.js +42 -33
- package/dist/components/feedback/index.d.ts +4 -1
- package/dist/components/feedback/index.js +6 -2
- package/dist/components/feedback/sheet.d.ts +26 -4
- package/dist/components/feedback/sheet.js +54 -7
- package/dist/components/feedback/two-factor-setup.d.ts +31 -0
- package/dist/components/feedback/two-factor-setup.js +97 -0
- package/dist/components/general/index.d.ts +1 -1
- package/dist/components/general/logo.d.ts +42 -8
- package/dist/components/general/logo.js +67 -14
- package/dist/components/layout/app-shell.js +21 -5
- package/dist/components/layout/auth-divider.d.ts +7 -0
- package/dist/components/layout/auth-divider.js +21 -0
- package/dist/components/layout/auth-footer.d.ts +5 -0
- package/dist/components/layout/auth-footer.js +24 -0
- package/dist/components/layout/auth-identity.d.ts +5 -0
- package/dist/components/layout/auth-identity.js +17 -0
- package/dist/components/layout/auth-shell.d.ts +8 -1
- package/dist/components/layout/auth-shell.js +25 -6
- package/dist/components/layout/auth-stack.d.ts +4 -0
- package/dist/components/layout/auth-stack.js +8 -0
- package/dist/components/layout/centered-shell.d.ts +5 -2
- package/dist/components/layout/centered-shell.js +2 -1
- package/dist/components/layout/index.d.ts +18 -3
- package/dist/components/layout/index.js +17 -2
- package/dist/components/layout/legal-document-shell.d.ts +34 -0
- package/dist/components/layout/legal-document-shell.js +186 -0
- package/dist/components/layout/master-detail.d.ts +24 -0
- package/dist/components/layout/master-detail.js +48 -0
- package/dist/components/layout/org-switcher.d.ts +9 -0
- package/dist/components/layout/org-switcher.js +218 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +3 -2
- package/dist/components/layout/sidebar-link.d.ts +43 -0
- package/dist/components/layout/sidebar-link.js +13 -0
- package/dist/components/layout/sidebar.d.ts +42 -5
- package/dist/components/layout/sidebar.js +170 -85
- package/dist/components/navigation/app-setting-picker.js +17 -6
- package/dist/components/navigation/filter-bar.d.ts +12 -3
- package/dist/components/navigation/filter-bar.js +8 -2
- package/dist/components/navigation/index.d.ts +2 -2
- package/dist/components/navigation/index.js +3 -1
- package/dist/components/navigation/tabs-scroll.d.ts +70 -0
- package/dist/components/navigation/tabs-scroll.js +76 -0
- package/dist/components/navigation/tabs.js +35 -20
- package/dist/email/brand-mark.d.ts +92 -0
- package/dist/email/brand-mark.js +83 -0
- package/dist/email/color.d.ts +52 -0
- package/dist/email/color.js +37 -0
- package/dist/email/geometry.d.ts +112 -0
- package/dist/email/geometry.js +89 -0
- package/dist/email/index.d.ts +78 -0
- package/dist/email/index.js +76 -0
- package/dist/email/inline-style.d.ts +25 -0
- package/dist/email/inline-style.js +21 -0
- package/dist/email/tokens.generated.d.ts +147 -0
- package/dist/email/tokens.generated.js +142 -0
- package/dist/i18n/messages/en.json +21 -2
- package/dist/i18n/messages/ja.json +21 -2
- package/dist/i18n/messages/vi.json +21 -2
- package/dist/inertia/index.d.ts +50 -0
- package/dist/inertia/index.js +5 -0
- package/dist/props/components/app.prop.d.ts +11 -1
- package/dist/props/components/charts.prop.d.ts +40 -1
- package/dist/props/components/data-display.prop.d.ts +13 -1
- package/dist/props/components/feedback.prop.d.ts +10 -0
- package/dist/props/components/index.d.ts +2 -2
- package/dist/props/components/layout.prop.d.ts +373 -10
- package/dist/props/components/layout.prop.js +1 -0
- package/dist/props/components/navigation.prop.d.ts +19 -1
- package/dist/props/registry.d.ts +291 -7
- package/dist/props/registry.js +341 -7
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/props/vocabulary/interaction.prop.d.ts +1 -1
- package/dist/props/vocabulary/layout.prop.d.ts +18 -0
- package/dist/styles/base.css +8 -2
- package/dist/styles/card-layout.css +161 -7
- package/dist/styles/chart-layout.css +97 -0
- package/dist/styles/control.css +48 -2
- package/dist/styles/data-display-layout.css +52 -6
- package/dist/styles/dialog-layout.css +105 -4
- package/dist/styles/fonts.css +15 -4
- package/dist/styles/index.css +11 -3
- package/dist/styles/layout.css +367 -1
- package/dist/styles/logo-layout.css +62 -0
- package/dist/styles/navigation-layout.css +43 -0
- package/dist/styles/shell-layout.css +488 -40
- package/dist/theme/dxs.canonical.css +76 -0
- package/dist/tokens/axes.css +39 -0
- package/dist/tokens/base.css +5 -0
- package/dist/tokens/components/card.css +48 -0
- package/dist/tokens/components/chart.css +47 -0
- package/dist/tokens/components/control.css +16 -0
- package/dist/tokens/components/email.css +66 -0
- package/dist/tokens/components/feedback.css +17 -0
- package/dist/tokens/components/legal-document.css +67 -0
- package/dist/tokens/components/list-row.css +23 -2
- package/dist/tokens/components/logo.css +32 -0
- package/dist/tokens/components/navigation.css +13 -0
- package/dist/tokens/components/sheet.css +19 -0
- package/dist/tokens/components/shell.css +138 -0
- package/dist/tokens/components/sidebar.css +33 -0
- package/dist/tokens/foundation.css +5 -2
- package/dist/tokens/semantic/layout.css +44 -2
- package/package.json +16 -5
|
@@ -4,10 +4,38 @@ import * as React from "react";
|
|
|
4
4
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
5
5
|
import { cva } from "class-variance-authority";
|
|
6
6
|
import { X } from "lucide-react";
|
|
7
|
+
import { useMediaQuery } from "../../lib/hooks.js";
|
|
7
8
|
import { cn } from "../../lib/utils.js";
|
|
8
9
|
import { overlayHeaderToneClass } from "./overlay-header-tone.js";
|
|
9
10
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
10
11
|
const toCssLength = (v) => typeof v === "number" ? `${v}px` : v;
|
|
12
|
+
const SHEET_BREAKPOINT_TOKEN = "--sheet-responsive-breakpoint-width";
|
|
13
|
+
const SHEET_BREAKPOINT_FALLBACK_QUERY = "(max-width: 768px)";
|
|
14
|
+
function cssLengthToPx(value, rootFontSize) {
|
|
15
|
+
const match = /^(-?\d*\.?\d+)(px|rem|em)?$/.exec(value.trim());
|
|
16
|
+
if (match == null) return void 0;
|
|
17
|
+
const amount = Number(match[1]);
|
|
18
|
+
if (!Number.isFinite(amount)) return void 0;
|
|
19
|
+
return match[2] === "rem" || match[2] === "em" ? amount * rootFontSize : amount;
|
|
20
|
+
}
|
|
21
|
+
function readSheetBreakpointQuery() {
|
|
22
|
+
if (typeof document === "undefined" || typeof window.getComputedStyle !== "function") {
|
|
23
|
+
return SHEET_BREAKPOINT_FALLBACK_QUERY;
|
|
24
|
+
}
|
|
25
|
+
const rootStyle = window.getComputedStyle(document.documentElement);
|
|
26
|
+
const rootFontSize = cssLengthToPx(rootStyle.fontSize || "16px", 16) ?? 16;
|
|
27
|
+
const px = cssLengthToPx(rootStyle.getPropertyValue(SHEET_BREAKPOINT_TOKEN), rootFontSize);
|
|
28
|
+
return px == null ? SHEET_BREAKPOINT_FALLBACK_QUERY : `(max-width: ${String(px)}px)`;
|
|
29
|
+
}
|
|
30
|
+
function useSheetResponsiveMode(responsive = "side") {
|
|
31
|
+
const [query, setQuery] = React.useState(SHEET_BREAKPOINT_FALLBACK_QUERY);
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
setQuery(readSheetBreakpointQuery());
|
|
34
|
+
}, []);
|
|
35
|
+
const compact = useMediaQuery(query);
|
|
36
|
+
if (responsive === "side" || responsive === "bottom") return responsive;
|
|
37
|
+
return compact ? "bottom" : "side";
|
|
38
|
+
}
|
|
11
39
|
function Sheet(props) {
|
|
12
40
|
return /* @__PURE__ */ jsx(DialogPrimitive.Root, { "data-slot": "sheet", ...props });
|
|
13
41
|
}
|
|
@@ -44,9 +72,9 @@ const sheetVariants = cva(
|
|
|
44
72
|
// the Radix/shadcn Sheet convention. The physical classes below are intended.
|
|
45
73
|
side: {
|
|
46
74
|
/* rtl-ignore: named physical side */
|
|
47
|
-
right: "inset-y-0 right-0 h-full w-
|
|
75
|
+
right: "inset-y-0 right-0 h-full w-[min(var(--sheet-width-default),100%)] max-w-none border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right",
|
|
48
76
|
/* rtl-ignore: named physical side */
|
|
49
|
-
left: "inset-y-0 left-0 h-full w-
|
|
77
|
+
left: "inset-y-0 left-0 h-full w-[min(var(--sheet-width-default),100%)] max-w-none border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left",
|
|
50
78
|
top: "inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
51
79
|
bottom: "inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom"
|
|
52
80
|
}
|
|
@@ -55,23 +83,41 @@ const sheetVariants = cva(
|
|
|
55
83
|
}
|
|
56
84
|
);
|
|
57
85
|
const SheetContent = React.forwardRef(
|
|
58
|
-
({
|
|
86
|
+
({
|
|
87
|
+
side = "right",
|
|
88
|
+
className,
|
|
89
|
+
children,
|
|
90
|
+
showCloseButton = true,
|
|
91
|
+
overlayClassName,
|
|
92
|
+
width,
|
|
93
|
+
responsive = "side",
|
|
94
|
+
style,
|
|
95
|
+
...props
|
|
96
|
+
}, ref) => {
|
|
59
97
|
const { t } = useTranslation();
|
|
60
|
-
const
|
|
98
|
+
const presentation = useSheetResponsiveMode(responsive);
|
|
99
|
+
const resolvedSide = presentation === "bottom" ? "bottom" : side;
|
|
100
|
+
const bottomSheet = responsive !== "side" && presentation === "bottom";
|
|
101
|
+
const horizontal = resolvedSide === "left" || resolvedSide === "right";
|
|
61
102
|
const widthSet = width != null && horizontal;
|
|
62
103
|
const mergedStyle = widthSet ? { ...style, ["--sheet-width"]: toCssLength(width) } : style;
|
|
63
104
|
return /* @__PURE__ */ jsxs(SheetPortal, { children: [
|
|
64
|
-
/* @__PURE__ */ jsx(SheetOverlay, {}),
|
|
105
|
+
/* @__PURE__ */ jsx(SheetOverlay, { className: overlayClassName }),
|
|
65
106
|
/* @__PURE__ */ jsxs(
|
|
66
107
|
DialogPrimitive.Content,
|
|
67
108
|
{
|
|
68
109
|
ref,
|
|
69
110
|
"data-slot": "sheet-content",
|
|
111
|
+
"data-responsive": responsive,
|
|
112
|
+
"data-side": resolvedSide,
|
|
70
113
|
style: mergedStyle,
|
|
71
114
|
className: cn(
|
|
72
|
-
sheetVariants({ side }),
|
|
115
|
+
sheetVariants({ side: resolvedSide }),
|
|
73
116
|
// `width` caps at the viewport: full-width panel on a small screen, capped on a large one.
|
|
74
117
|
widthSet && "w-[min(var(--sheet-width),100%)] max-w-none sm:max-w-none",
|
|
118
|
+
// Only the RESPONSIVE bottom presentation is capped — a plain `side="bottom"` sheet keeps
|
|
119
|
+
// its content-sized height so existing usage is untouched.
|
|
120
|
+
bottomSheet && "max-h-[var(--sheet-bottom-max-height)]",
|
|
75
121
|
className
|
|
76
122
|
),
|
|
77
123
|
...props,
|
|
@@ -193,5 +239,6 @@ export {
|
|
|
193
239
|
SheetOverlay,
|
|
194
240
|
SheetPortal,
|
|
195
241
|
SheetTitle,
|
|
196
|
-
SheetTrigger
|
|
242
|
+
SheetTrigger,
|
|
243
|
+
useSheetResponsiveMode
|
|
197
244
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type TwoFactorSetupLabels = {
|
|
3
|
+
title: React.ReactNode;
|
|
4
|
+
description: React.ReactNode;
|
|
5
|
+
qrLabel: string;
|
|
6
|
+
manualKeyLabel: React.ReactNode;
|
|
7
|
+
codeLabel: string;
|
|
8
|
+
codePlaceholder?: string;
|
|
9
|
+
recoveryNotice: React.ReactNode;
|
|
10
|
+
cancel: React.ReactNode;
|
|
11
|
+
confirm: React.ReactNode;
|
|
12
|
+
acknowledge: React.ReactNode;
|
|
13
|
+
};
|
|
14
|
+
export type TwoFactorSetupProps = {
|
|
15
|
+
open: boolean;
|
|
16
|
+
onOpenChange: (open: boolean) => void;
|
|
17
|
+
qrValue?: string;
|
|
18
|
+
manualKey: string;
|
|
19
|
+
code: string;
|
|
20
|
+
onCodeChange: (code: string) => void;
|
|
21
|
+
onConfirm: () => void;
|
|
22
|
+
recoveryCodes?: string[];
|
|
23
|
+
onAcknowledge: () => void;
|
|
24
|
+
labels: TwoFactorSetupLabels;
|
|
25
|
+
pending?: boolean;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Canonical two-factor setup overlay. Network requests and state persistence remain application
|
|
29
|
+
* concerns; this component only owns the QR/manual-key/OTP/recovery presentation contract.
|
|
30
|
+
*/
|
|
31
|
+
export declare function TwoFactorSetup({ open, onOpenChange, qrValue, manualKey, code, onCodeChange, onConfirm, recoveryCodes, onAcknowledge, labels, pending, }: TwoFactorSetupProps): React.JSX.Element;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { CredentialReveal } from "../data-display/credential-reveal.js";
|
|
4
|
+
import { QrCode } from "../data-display/qr-code.js";
|
|
5
|
+
import { Input } from "../data-entry/input.js";
|
|
6
|
+
import { Button } from "../general/button.js";
|
|
7
|
+
import { Text } from "../general/typography.js";
|
|
8
|
+
import {
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogBody,
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogDescription,
|
|
13
|
+
DialogFooter,
|
|
14
|
+
DialogHeader,
|
|
15
|
+
DialogTitle
|
|
16
|
+
} from "./dialog.js";
|
|
17
|
+
function TwoFactorSetup({
|
|
18
|
+
open,
|
|
19
|
+
onOpenChange,
|
|
20
|
+
qrValue,
|
|
21
|
+
manualKey,
|
|
22
|
+
code,
|
|
23
|
+
onCodeChange,
|
|
24
|
+
onConfirm,
|
|
25
|
+
recoveryCodes = [],
|
|
26
|
+
onAcknowledge,
|
|
27
|
+
labels,
|
|
28
|
+
pending = false
|
|
29
|
+
}) {
|
|
30
|
+
const showingRecovery = recoveryCodes.length > 0;
|
|
31
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
|
|
32
|
+
DialogContent,
|
|
33
|
+
{
|
|
34
|
+
className: "ui-two-factor-setup",
|
|
35
|
+
overlayClassName: "ui-two-factor-setup-overlay",
|
|
36
|
+
showCloseButton: !pending,
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
39
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: labels.title }),
|
|
40
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: labels.description })
|
|
41
|
+
] }),
|
|
42
|
+
/* @__PURE__ */ jsx(DialogBody, { children: showingRecovery ? /* @__PURE__ */ jsxs(
|
|
43
|
+
"section",
|
|
44
|
+
{
|
|
45
|
+
className: "ui-two-factor-setup-recovery",
|
|
46
|
+
"aria-live": "polite",
|
|
47
|
+
"data-state": "recovery",
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ jsx(Text, { as: "p", tone: "muted", children: labels.recoveryNotice }),
|
|
50
|
+
/* @__PURE__ */ jsx("ul", { className: "ui-two-factor-setup-recovery-codes", children: recoveryCodes.map((recoveryCode) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: recoveryCode }) }, recoveryCode)) })
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
) : /* @__PURE__ */ jsxs("div", { className: "ui-two-factor-setup-enrollment", "data-state": "enrollment", children: [
|
|
54
|
+
/* @__PURE__ */ jsx(Text, { as: "p", tone: "muted", children: labels.description }),
|
|
55
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-two-factor-setup-grid", children: [
|
|
56
|
+
qrValue ? /* @__PURE__ */ jsx(QrCode, { value: qrValue, label: labels.qrLabel, size: "sm" }) : null,
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-two-factor-setup-fields", children: [
|
|
58
|
+
/* @__PURE__ */ jsx(
|
|
59
|
+
CredentialReveal,
|
|
60
|
+
{
|
|
61
|
+
secret: manualKey,
|
|
62
|
+
label: labels.manualKeyLabel,
|
|
63
|
+
warning: null,
|
|
64
|
+
defaultRevealed: true,
|
|
65
|
+
size: "sm"
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ jsxs("label", { className: "ui-two-factor-setup-code", children: [
|
|
69
|
+
/* @__PURE__ */ jsx(Text, { as: "span", size: "sm", weight: "medium", children: labels.codeLabel }),
|
|
70
|
+
/* @__PURE__ */ jsx(
|
|
71
|
+
Input,
|
|
72
|
+
{
|
|
73
|
+
value: code,
|
|
74
|
+
onChange: (event) => onCodeChange(event.target.value.replace(/\D/g, "").slice(0, 6)),
|
|
75
|
+
inputMode: "numeric",
|
|
76
|
+
autoComplete: "one-time-code",
|
|
77
|
+
maxLength: 6,
|
|
78
|
+
placeholder: labels.codePlaceholder,
|
|
79
|
+
"aria-label": labels.codeLabel
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
] })
|
|
83
|
+
] })
|
|
84
|
+
] }),
|
|
85
|
+
/* @__PURE__ */ jsx(Text, { as: "p", size: "sm", tone: "muted", className: "ui-two-factor-setup-notice", children: labels.recoveryNotice })
|
|
86
|
+
] }) }),
|
|
87
|
+
/* @__PURE__ */ jsx(DialogFooter, { children: showingRecovery ? /* @__PURE__ */ jsx(Button, { onClick: onAcknowledge, children: labels.acknowledge }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
88
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: pending, children: labels.cancel }),
|
|
89
|
+
/* @__PURE__ */ jsx(Button, { onClick: onConfirm, disabled: pending || code.length !== 6, loading: pending, children: labels.confirm })
|
|
90
|
+
] }) })
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
) });
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
TwoFactorSetup
|
|
97
|
+
};
|
|
@@ -3,6 +3,6 @@ export type { ButtonProps } from "./button.js";
|
|
|
3
3
|
export { Text, Heading } from "./typography.js";
|
|
4
4
|
export type { TextProps, HeadingProps, TextProp, HeadingProp } from "./typography.js";
|
|
5
5
|
export { Logo } from "./logo.js";
|
|
6
|
-
export type { LogoProps, LogoSize } from "./logo.js";
|
|
6
|
+
export type { LogoMark, LogoProps, LogoSize, LogoTone } from "./logo.js";
|
|
7
7
|
export { Reveal } from "./reveal.js";
|
|
8
8
|
export type { RevealProp, RevealProps } from "./reveal.js";
|
|
@@ -1,27 +1,61 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
export type LogoSize = "xs" | "sm" | "md" | "lg";
|
|
3
|
+
export type LogoTone = "primary" | "success";
|
|
4
|
+
export type LogoMark = "glyph" | "godx";
|
|
3
5
|
export interface LogoProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
|
|
4
6
|
/**
|
|
5
7
|
* The brand glyph — a short mark (a letter/initials, default `"g"`) or a custom node such as an
|
|
6
8
|
* inline `<svg>`. Keep it to 1–2 glyphs; the box is square and centres its content.
|
|
7
9
|
*/
|
|
8
10
|
glyph?: React.ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Semantic mark artwork. `"godx"` renders the canonical GoDX identity mark as an inline vector;
|
|
13
|
+
* `"glyph"` preserves the configurable boxed-glyph treatment.
|
|
14
|
+
*/
|
|
15
|
+
mark?: LogoMark;
|
|
9
16
|
/** Box size tier (tokenised). Default `"md"` (1.75rem). */
|
|
10
17
|
size?: LogoSize;
|
|
18
|
+
/**
|
|
19
|
+
* Semantic fill role. `"success"` provides the canonical green identity mark without changing
|
|
20
|
+
* the application's primary action colour. Default `"primary"` preserves existing behaviour.
|
|
21
|
+
*/
|
|
22
|
+
tone?: LogoTone;
|
|
23
|
+
/**
|
|
24
|
+
* Readable product name rendered BESIDE the mark as one lockup. Pass the localized product name
|
|
25
|
+
* (a string, or a node when the name needs its own markup). When set, `Logo` renders
|
|
26
|
+
* `mark + wordmark` in a single inline-flex lockup: the mark becomes decorative and the wordmark
|
|
27
|
+
* carries the accessible name, so the pair is announced once. The wordmark reads
|
|
28
|
+
* `--logo-wordmark-*` tokens — its colour defaults to the identity role for the GoDX/`success`
|
|
29
|
+
* lockup and NEVER to `--primary`, so a re-themed action colour cannot recolour the brand.
|
|
30
|
+
* Omit for the bare mark (today's behaviour, unchanged).
|
|
31
|
+
*
|
|
32
|
+
* NOTE: the library ships no wordmark ARTWORK — the wordmark is set in the design-system
|
|
33
|
+
* typeface (`--logo-wordmark-font-family`, default `--font-family-display`). Pass an inline
|
|
34
|
+
* `<svg>` here when a real logotype asset exists.
|
|
35
|
+
*/
|
|
36
|
+
wordmark?: React.ReactNode;
|
|
11
37
|
/**
|
|
12
38
|
* Accessible name for the mark. When set, the logo is exposed to assistive tech as an image with
|
|
13
39
|
* this name; when omitted the mark is decorative (`aria-hidden`) — the correct default when a
|
|
14
|
-
* readable wordmark sits beside it.
|
|
40
|
+
* readable wordmark sits beside it. With `wordmark` set, `label` overrides the lockup's
|
|
41
|
+
* accessible name (the wordmark text is otherwise the name).
|
|
15
42
|
*/
|
|
16
43
|
label?: string;
|
|
17
44
|
}
|
|
18
45
|
/**
|
|
19
|
-
* Logo — the product brand-mark box: a glyph on the primary fill
|
|
20
|
-
* bare span with a fixed square size, a
|
|
21
|
-
*
|
|
22
|
-
* a bare span (rules #45/#46). Size,
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
46
|
+
* Logo — the product brand-mark box: a glyph on the primary fill, or (with `wordmark`) the full
|
|
47
|
+
* mark + wordmark LOCKUP. Use it INSTEAD of hand-rolling a bare span with a fixed square size, a
|
|
48
|
+
* literal radius, and `bg-primary` + type utilities in shell headers, auth screens, and topbars —
|
|
49
|
+
* that repeats literal size/radius and puts type utilities on a bare span (rules #45/#46). Size,
|
|
50
|
+
* radius, per-tier font-size, the wordmark's face/weight/tracking/colour and the mark↔wordmark gap
|
|
51
|
+
* are all tokens.
|
|
52
|
+
*
|
|
53
|
+
* COLOUR: the boxed glyph fill reads the primary role, so a re-themed `--primary` re-tints it.
|
|
54
|
+
* `mark="godx"` / `tone="success"` instead read the IDENTITY tokens (`--logo-godx-color`,
|
|
55
|
+
* `--logo-success-*`, `--logo-wordmark-color`), which never reference `--primary` — the canonical
|
|
56
|
+
* brand-green GoDX lockup therefore survives any action-colour re-theme with zero consumer CSS.
|
|
57
|
+
*
|
|
58
|
+
* A11Y: with a `wordmark`, the mark is decorative and the wordmark text carries the accessible
|
|
59
|
+
* name (announced once). Without one, set `label` when the mark stands alone.
|
|
26
60
|
*/
|
|
27
61
|
export declare const Logo: React.ForwardRefExoticComponent<LogoProps & React.RefAttributes<HTMLSpanElement>>;
|
|
@@ -1,21 +1,74 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../../lib/utils.js";
|
|
4
|
-
|
|
5
|
-
(
|
|
6
|
-
|
|
4
|
+
function MarkArtwork({ mark, glyph }) {
|
|
5
|
+
if (mark !== "godx") return /* @__PURE__ */ jsx(Fragment, { children: glyph });
|
|
6
|
+
return /* @__PURE__ */ jsx(
|
|
7
|
+
"svg",
|
|
7
8
|
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"aria-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
"data-slot": "logo-artwork",
|
|
10
|
+
viewBox: "0 0 32 32",
|
|
11
|
+
width: "32",
|
|
12
|
+
height: "32",
|
|
13
|
+
focusable: "false",
|
|
14
|
+
"aria-hidden": "true",
|
|
15
|
+
children: /* @__PURE__ */ jsx(
|
|
16
|
+
"path",
|
|
17
|
+
{
|
|
18
|
+
fill: "currentColor",
|
|
19
|
+
fillRule: "evenodd",
|
|
20
|
+
d: "M8 7h16a7 7 0 0 1 7 7v4a7 7 0 0 1-7 7H8a7 7 0 0 1-7-7v-4a7 7 0 0 1 7-7Zm0 6a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H8Z"
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const Logo = React.forwardRef(
|
|
27
|
+
({
|
|
28
|
+
glyph = "g",
|
|
29
|
+
mark = "glyph",
|
|
30
|
+
size = "md",
|
|
31
|
+
tone = "primary",
|
|
32
|
+
wordmark,
|
|
33
|
+
label,
|
|
34
|
+
className,
|
|
35
|
+
...props
|
|
36
|
+
}, ref) => {
|
|
37
|
+
const hasWordmark = wordmark !== void 0 && wordmark !== null && wordmark !== false;
|
|
38
|
+
const identity = { "data-mark": mark, "data-size": size, "data-tone": tone };
|
|
39
|
+
if (hasWordmark) {
|
|
40
|
+
return /* @__PURE__ */ jsxs(
|
|
41
|
+
"span",
|
|
42
|
+
{
|
|
43
|
+
ref,
|
|
44
|
+
"data-slot": "logo-lockup",
|
|
45
|
+
...identity,
|
|
46
|
+
className: cn("ui-logo-lockup", className),
|
|
47
|
+
role: label ? "img" : void 0,
|
|
48
|
+
"aria-label": label,
|
|
49
|
+
...props,
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsx("span", { "data-slot": "logo", ...identity, className: "ui-logo", "aria-hidden": "true", children: /* @__PURE__ */ jsx(MarkArtwork, { mark, glyph }) }),
|
|
52
|
+
/* @__PURE__ */ jsx("span", { "data-slot": "logo-wordmark", className: "ui-logo-wordmark", children: wordmark })
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
);
|
|
17
56
|
}
|
|
18
|
-
|
|
57
|
+
return /* @__PURE__ */ jsx(
|
|
58
|
+
"span",
|
|
59
|
+
{
|
|
60
|
+
ref,
|
|
61
|
+
"data-slot": "logo",
|
|
62
|
+
...identity,
|
|
63
|
+
className: cn("ui-logo", className),
|
|
64
|
+
role: label ? "img" : void 0,
|
|
65
|
+
"aria-label": label,
|
|
66
|
+
"aria-hidden": label ? void 0 : true,
|
|
67
|
+
...props,
|
|
68
|
+
children: /* @__PURE__ */ jsx(MarkArtwork, { mark, glyph })
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
19
72
|
);
|
|
20
73
|
Logo.displayName = "Logo";
|
|
21
74
|
export {
|
|
@@ -55,14 +55,30 @@ function AppShell({
|
|
|
55
55
|
children: /* @__PURE__ */ jsx(Menu, { className: "size-5", "aria-hidden": "true" })
|
|
56
56
|
}
|
|
57
57
|
) }),
|
|
58
|
-
/* @__PURE__ */ jsxs(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
/* @__PURE__ */ jsxs(
|
|
59
|
+
SheetContent,
|
|
60
|
+
{
|
|
61
|
+
side: "left",
|
|
62
|
+
width: "var(--app-shell-mobile-nav-width)",
|
|
63
|
+
className: "app-mobile-nav-drawer",
|
|
64
|
+
overlayClassName: "app-mobile-nav-overlay",
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ jsx(SheetHeader, { title: mobileNavLabel ?? t("layout.appShell.navLabel") }),
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
SheetBody,
|
|
69
|
+
{
|
|
70
|
+
className: "app-mobile-nav-body px-[var(--app-shell-mobile-nav-inset)]",
|
|
71
|
+
onClick: handleDrawerClick,
|
|
72
|
+
children: drawerNav
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
)
|
|
62
78
|
] }),
|
|
63
79
|
resolvedTopbar
|
|
64
80
|
] }),
|
|
65
|
-
/* @__PURE__ */ jsxs("main", { className: "app-main", "aria-label": t("layout.appShell.mainLabel"), children: [
|
|
81
|
+
/* @__PURE__ */ jsxs("main", { className: "app-main", "aria-label": t("layout.appShell.mainLabel"), tabIndex: 0, children: [
|
|
66
82
|
breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
|
|
67
83
|
children
|
|
68
84
|
] }),
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuthDividerProp } from "../../props/components/layout.prop.js";
|
|
2
|
+
export type { AuthDividerProp, AuthDividerProp as AuthDividerProps, } from "../../props/components/layout.prop.js";
|
|
3
|
+
/**
|
|
4
|
+
* AuthDivider — a centred, localized conjunction between two equal separator rules. The grid
|
|
5
|
+
* composition keeps the label optically and geometrically centred regardless of translation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function AuthDivider({ label, className }: AuthDividerProp): import("react").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
function AuthDivider({ label, className }) {
|
|
4
|
+
return /* @__PURE__ */ jsxs(
|
|
5
|
+
"div",
|
|
6
|
+
{
|
|
7
|
+
"data-slot": "auth-divider",
|
|
8
|
+
className: cn("ui-auth-divider", className),
|
|
9
|
+
role: "separator",
|
|
10
|
+
"aria-label": label,
|
|
11
|
+
children: [
|
|
12
|
+
/* @__PURE__ */ jsx("span", { className: "ui-auth-divider-rule", "aria-hidden": "true" }),
|
|
13
|
+
/* @__PURE__ */ jsx("span", { className: "ui-auth-divider-label", "aria-hidden": "true", children: label }),
|
|
14
|
+
/* @__PURE__ */ jsx("span", { className: "ui-auth-divider-rule", "aria-hidden": "true" })
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
AuthDivider
|
|
21
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AuthFooterProp } from "../../props/components/layout.prop.js";
|
|
2
|
+
export type { AuthFooterProp } from "../../props/components/layout.prop.js";
|
|
3
|
+
export type { AuthFooterProp as AuthFooterProps } from "../../props/components/layout.prop.js";
|
|
4
|
+
/** Canonical mono legal line shared by hosted identity screens. */
|
|
5
|
+
export declare function AuthFooter({ product, terms, privacy, locale, className }: AuthFooterProp): import("react").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
const SLOTS = ["product", "terms", "privacy", "locale"];
|
|
4
|
+
function AuthFooter({ product, terms, privacy, locale, className }) {
|
|
5
|
+
const bySlot = { product, terms, privacy, locale };
|
|
6
|
+
const items = SLOTS.filter(
|
|
7
|
+
(slot) => bySlot[slot] !== null && bySlot[slot] !== void 0 && bySlot[slot] !== false
|
|
8
|
+
);
|
|
9
|
+
return /* @__PURE__ */ jsx("div", { "data-slot": "auth-legal-footer", className: cn("ui-auth-legal-footer", className), children: items.map((slot, index) => /* @__PURE__ */ jsxs(
|
|
10
|
+
"span",
|
|
11
|
+
{
|
|
12
|
+
"data-slot": `auth-legal-footer-${slot}`,
|
|
13
|
+
className: "ui-auth-legal-footer-item",
|
|
14
|
+
children: [
|
|
15
|
+
index > 0 ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ui-auth-legal-footer-separator", children: "\xB7" }) : null,
|
|
16
|
+
bySlot[slot]
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
slot
|
|
20
|
+
)) });
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
AuthFooter
|
|
24
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AuthIdentityProp } from "../../props/components/layout.prop.js";
|
|
2
|
+
export type { AuthIdentityProp } from "../../props/components/layout.prop.js";
|
|
3
|
+
export type { AuthIdentityProp as AuthIdentityProps } from "../../props/components/layout.prop.js";
|
|
4
|
+
/** Canonical hosted-identity mark, heading and optional real requesting-client context. */
|
|
5
|
+
export declare function AuthIdentity({ title, requester, className }: AuthIdentityProp): import("react").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Clock } from "lucide-react";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { Heading, Logo, Text } from "../general/index.js";
|
|
5
|
+
function AuthIdentity({ title, requester, className }) {
|
|
6
|
+
return /* @__PURE__ */ jsxs("div", { "data-slot": "auth-identity", className: cn("ui-auth-identity", className), children: [
|
|
7
|
+
/* @__PURE__ */ jsx(Logo, { mark: "godx", tone: "success" }),
|
|
8
|
+
/* @__PURE__ */ jsx(Heading, { level: 1, children: title }),
|
|
9
|
+
requester !== void 0 && requester !== null ? /* @__PURE__ */ jsxs("div", { "data-slot": "auth-requester", className: "ui-auth-requester", children: [
|
|
10
|
+
/* @__PURE__ */ jsx("span", { "data-slot": "auth-requester-icon", className: "ui-auth-requester-icon", children: /* @__PURE__ */ jsx(Clock, { "aria-hidden": "true" }) }),
|
|
11
|
+
/* @__PURE__ */ jsx(Text, { as: "span", size: "xs", tone: "muted", children: requester })
|
|
12
|
+
] }) : null
|
|
13
|
+
] });
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
AuthIdentity
|
|
17
|
+
};
|
|
@@ -7,5 +7,12 @@ export type { AuthShellProp, AuthShellProp as AuthShellProps, } from "../../prop
|
|
|
7
7
|
* touch floor) and the auth heading is bumped up, so forms read at the right size — replacing a
|
|
8
8
|
* consumer's `.auth-shell-*` / `.ui-auth-scope` classes. Motion is delegated to `Reveal` (wrap the
|
|
9
9
|
* card) so `prefers-reduced-motion` is honoured in one place.
|
|
10
|
+
*
|
|
11
|
+
* `preset` names the flow MEASURE (card max-width + desktop/mobile page gutters) so a consumer
|
|
12
|
+
* never overrides `--auth-shell-card-max-width` by hand: `"device-authorization"` = 380px card with
|
|
13
|
+
* a 5px inline gutter at 390px; `"context-selection"` = 25rem card, edge-to-edge on mobile;
|
|
14
|
+
* `"account-recovery"` = the 432px SCR-008 panel measure shared by password recovery and the
|
|
15
|
+
* sign-in MFA challenge, with a 15px inline gutter at 390px. It is orthogonal to `variant` —
|
|
16
|
+
* combine `variant="canonical"` with any preset.
|
|
10
17
|
*/
|
|
11
|
-
export declare function AuthShell({ brand, footer, children, className }: AuthShellProp): import("react").JSX.Element;
|
|
18
|
+
export declare function AuthShell({ brand, footer, children, variant, preset, density, className, }: AuthShellProp): import("react").JSX.Element;
|
|
@@ -2,13 +2,32 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
|
-
function AuthShell({
|
|
5
|
+
function AuthShell({
|
|
6
|
+
brand,
|
|
7
|
+
footer,
|
|
8
|
+
children,
|
|
9
|
+
variant = "default",
|
|
10
|
+
preset = "default",
|
|
11
|
+
density,
|
|
12
|
+
className
|
|
13
|
+
}) {
|
|
6
14
|
const { t } = useTranslation();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
const resolvedDensity = density ?? (variant === "canonical" ? "compact" : "comfortable");
|
|
16
|
+
return /* @__PURE__ */ jsxs(
|
|
17
|
+
"div",
|
|
18
|
+
{
|
|
19
|
+
"data-slot": "auth-shell",
|
|
20
|
+
"data-variant": variant,
|
|
21
|
+
"data-preset": preset === "default" ? void 0 : preset,
|
|
22
|
+
"data-density": resolvedDensity,
|
|
23
|
+
className: cn("ui-auth-shell", className),
|
|
24
|
+
children: [
|
|
25
|
+
brand !== void 0 && /* @__PURE__ */ jsx("header", { className: "ui-auth-shell-bar", "aria-label": t("layout.authShell.brandLabel"), children: brand }),
|
|
26
|
+
/* @__PURE__ */ jsx("main", { className: "ui-auth-shell-main", "aria-label": t("layout.authShell.mainLabel"), children: /* @__PURE__ */ jsx("div", { className: "ui-auth-shell-card", children }) }),
|
|
27
|
+
footer !== void 0 && /* @__PURE__ */ jsx("footer", { className: "ui-auth-shell-footer", "aria-label": t("layout.authShell.footerLabel"), children: footer })
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
);
|
|
12
31
|
}
|
|
13
32
|
export {
|
|
14
33
|
AuthShell
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
export type AuthStackProps = HTMLAttributes<HTMLDivElement>;
|
|
3
|
+
/** Canonical 12px vertical rhythm for the direct sections inside an auth card. */
|
|
4
|
+
export declare function AuthStack({ className, ...props }: AuthStackProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
function AuthStack({ className, ...props }) {
|
|
4
|
+
return /* @__PURE__ */ jsx("div", { "data-slot": "auth-stack", className: cn("ui-auth-stack", className), ...props });
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
AuthStack
|
|
8
|
+
};
|
|
@@ -14,9 +14,12 @@ export type { CenteredShellProp, CenteredShellProp as CenteredShellProps, } from
|
|
|
14
14
|
* `.app-topbar` (padding-inline · border · backdrop) WITHOUT a sidebar, a scrollable `main` whose
|
|
15
15
|
* content is a **centred column** of a configurable medium width (`width` = sm|md|lg, all wider than
|
|
16
16
|
* the 24rem auth card) **top-aligned** so sections flow and scroll (not vertically centred), and an
|
|
17
|
-
* optional footer (contentinfo)
|
|
17
|
+
* optional footer (contentinfo) — or, with `align="center"`, the column centred in the viewport for
|
|
18
|
+
* a SYSTEM-level standalone surface (a 500/503 error page, a maintenance notice), so that full-page
|
|
19
|
+
* geometry stays package-owned instead of a consumer re-implementing `min-h-dvh` + flex centring.
|
|
20
|
+
* A hosted account page thus needs ZERO custom CSS and never
|
|
18
21
|
* hand-rolls a bar (the bare `Topbar` primitive ships no padding — the `.ui-topbar` zero-inset
|
|
19
22
|
* footgun). Layout-only: delegate motion to `Reveal` (wrap a section) so `prefers-reduced-motion`
|
|
20
23
|
* is honoured in one place.
|
|
21
24
|
*/
|
|
22
|
-
export declare function CenteredShell({ topbar, footer, children, width, className, }: CenteredShellProp): import("react").JSX.Element;
|
|
25
|
+
export declare function CenteredShell({ topbar, footer, children, width, align, className, }: CenteredShellProp): import("react").JSX.Element;
|
|
@@ -7,6 +7,7 @@ function CenteredShell({
|
|
|
7
7
|
footer,
|
|
8
8
|
children,
|
|
9
9
|
width = "md",
|
|
10
|
+
align = "start",
|
|
10
11
|
className
|
|
11
12
|
}) {
|
|
12
13
|
const { t } = useTranslation();
|
|
@@ -19,7 +20,7 @@ function CenteredShell({
|
|
|
19
20
|
children: topbar
|
|
20
21
|
}
|
|
21
22
|
),
|
|
22
|
-
/* @__PURE__ */ jsx("main", { className: "ui-centered-shell-main", "aria-label": t("layout.centeredShell.mainLabel"), children: /* @__PURE__ */ jsx("div", { className: "ui-centered-shell-column", "data-width": width, children }) }),
|
|
23
|
+
/* @__PURE__ */ jsx("main", { className: "ui-centered-shell-main", "aria-label": t("layout.centeredShell.mainLabel"), children: /* @__PURE__ */ jsx("div", { className: "ui-centered-shell-column", "data-width": width, "data-align": align, children }) }),
|
|
23
24
|
footer !== void 0 && /* @__PURE__ */ jsx(
|
|
24
25
|
"footer",
|
|
25
26
|
{
|