@godxjp/ui 18.3.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.
Files changed (131) hide show
  1. package/README.md +18 -3
  2. package/dist/app/theme-axes.d.ts +11 -3
  3. package/dist/app/theme-axes.js +2 -1
  4. package/dist/components/charts/chart-frame.d.ts +17 -3
  5. package/dist/components/charts/chart-frame.js +7 -1
  6. package/dist/components/charts/chart-summary.d.ts +21 -0
  7. package/dist/components/charts/chart-summary.js +32 -5
  8. package/dist/components/charts/compact-bar-trend.d.ts +29 -0
  9. package/dist/components/charts/compact-bar-trend.js +74 -0
  10. package/dist/components/charts/index.d.ts +3 -0
  11. package/dist/components/charts/index.js +2 -0
  12. package/dist/components/data-display/badge.d.ts +8 -0
  13. package/dist/components/data-display/badge.js +7 -1
  14. package/dist/components/data-display/data-table.d.ts +16 -1
  15. package/dist/components/data-display/data-table.js +56 -0
  16. package/dist/components/data-display/index.d.ts +3 -1
  17. package/dist/components/data-display/index.js +10 -1
  18. package/dist/components/data-display/list-row.d.ts +22 -4
  19. package/dist/components/data-display/list-row.js +21 -3
  20. package/dist/components/data-display/service-launcher-card.d.ts +40 -0
  21. package/dist/components/data-display/service-launcher-card.js +97 -0
  22. package/dist/components/data-entry/command-palette.d.ts +37 -0
  23. package/dist/components/data-entry/command-palette.js +125 -0
  24. package/dist/components/data-entry/index.d.ts +2 -0
  25. package/dist/components/data-entry/index.js +2 -0
  26. package/dist/components/feedback/dialog.d.ts +2 -0
  27. package/dist/components/feedback/dialog.js +42 -33
  28. package/dist/components/feedback/index.d.ts +4 -1
  29. package/dist/components/feedback/index.js +6 -2
  30. package/dist/components/feedback/sheet.d.ts +26 -4
  31. package/dist/components/feedback/sheet.js +54 -7
  32. package/dist/components/feedback/two-factor-setup.d.ts +31 -0
  33. package/dist/components/feedback/two-factor-setup.js +97 -0
  34. package/dist/components/general/index.d.ts +1 -1
  35. package/dist/components/general/logo.d.ts +42 -8
  36. package/dist/components/general/logo.js +67 -14
  37. package/dist/components/layout/app-shell.js +22 -6
  38. package/dist/components/layout/auth-divider.d.ts +7 -0
  39. package/dist/components/layout/auth-divider.js +21 -0
  40. package/dist/components/layout/auth-footer.d.ts +5 -0
  41. package/dist/components/layout/auth-footer.js +24 -0
  42. package/dist/components/layout/auth-identity.d.ts +5 -0
  43. package/dist/components/layout/auth-identity.js +17 -0
  44. package/dist/components/layout/auth-shell.d.ts +8 -1
  45. package/dist/components/layout/auth-shell.js +25 -6
  46. package/dist/components/layout/auth-stack.d.ts +4 -0
  47. package/dist/components/layout/auth-stack.js +8 -0
  48. package/dist/components/layout/centered-shell.d.ts +5 -2
  49. package/dist/components/layout/centered-shell.js +2 -1
  50. package/dist/components/layout/index.d.ts +18 -3
  51. package/dist/components/layout/index.js +17 -2
  52. package/dist/components/layout/legal-document-shell.d.ts +34 -0
  53. package/dist/components/layout/legal-document-shell.js +186 -0
  54. package/dist/components/layout/master-detail.d.ts +24 -0
  55. package/dist/components/layout/master-detail.js +48 -0
  56. package/dist/components/layout/org-switcher.d.ts +9 -0
  57. package/dist/components/layout/org-switcher.js +218 -0
  58. package/dist/components/layout/page-container.d.ts +1 -1
  59. package/dist/components/layout/page-container.js +3 -2
  60. package/dist/components/layout/sidebar-link.d.ts +43 -0
  61. package/dist/components/layout/sidebar-link.js +13 -0
  62. package/dist/components/layout/sidebar.d.ts +42 -5
  63. package/dist/components/layout/sidebar.js +170 -85
  64. package/dist/components/navigation/app-setting-picker.js +17 -6
  65. package/dist/components/navigation/filter-bar.d.ts +12 -3
  66. package/dist/components/navigation/filter-bar.js +8 -2
  67. package/dist/components/navigation/index.d.ts +2 -2
  68. package/dist/components/navigation/index.js +3 -1
  69. package/dist/components/navigation/tabs-scroll.d.ts +70 -0
  70. package/dist/components/navigation/tabs-scroll.js +76 -0
  71. package/dist/components/navigation/tabs.js +35 -20
  72. package/dist/email/brand-mark.d.ts +92 -0
  73. package/dist/email/brand-mark.js +83 -0
  74. package/dist/email/color.d.ts +52 -0
  75. package/dist/email/color.js +37 -0
  76. package/dist/email/geometry.d.ts +112 -0
  77. package/dist/email/geometry.js +89 -0
  78. package/dist/email/index.d.ts +78 -0
  79. package/dist/email/index.js +76 -0
  80. package/dist/email/inline-style.d.ts +25 -0
  81. package/dist/email/inline-style.js +21 -0
  82. package/dist/email/tokens.generated.d.ts +147 -0
  83. package/dist/email/tokens.generated.js +142 -0
  84. package/dist/i18n/messages/en.json +21 -2
  85. package/dist/i18n/messages/ja.json +21 -2
  86. package/dist/i18n/messages/vi.json +21 -2
  87. package/dist/inertia/index.d.ts +50 -0
  88. package/dist/inertia/index.js +5 -0
  89. package/dist/props/components/app.prop.d.ts +11 -1
  90. package/dist/props/components/charts.prop.d.ts +40 -1
  91. package/dist/props/components/data-display.prop.d.ts +13 -1
  92. package/dist/props/components/feedback.prop.d.ts +10 -0
  93. package/dist/props/components/index.d.ts +2 -2
  94. package/dist/props/components/layout.prop.d.ts +374 -11
  95. package/dist/props/components/layout.prop.js +1 -0
  96. package/dist/props/components/navigation.prop.d.ts +19 -1
  97. package/dist/props/registry.d.ts +291 -7
  98. package/dist/props/registry.js +341 -7
  99. package/dist/props/vocabulary/index.d.ts +1 -1
  100. package/dist/props/vocabulary/interaction.prop.d.ts +1 -1
  101. package/dist/props/vocabulary/layout.prop.d.ts +18 -0
  102. package/dist/styles/base.css +9 -3
  103. package/dist/styles/card-layout.css +162 -8
  104. package/dist/styles/chart-layout.css +97 -0
  105. package/dist/styles/control.css +48 -2
  106. package/dist/styles/data-display-layout.css +52 -6
  107. package/dist/styles/dialog-layout.css +105 -4
  108. package/dist/styles/fonts.css +29 -20
  109. package/dist/styles/index.css +11 -3
  110. package/dist/styles/layout.css +368 -2
  111. package/dist/styles/logo-layout.css +62 -0
  112. package/dist/styles/navigation-layout.css +43 -0
  113. package/dist/styles/shell-layout.css +505 -62
  114. package/dist/theme/dxs.canonical.css +76 -0
  115. package/dist/tokens/axes.css +39 -0
  116. package/dist/tokens/base.css +5 -0
  117. package/dist/tokens/components/card.css +52 -5
  118. package/dist/tokens/components/chart.css +47 -0
  119. package/dist/tokens/components/control.css +16 -0
  120. package/dist/tokens/components/email.css +66 -0
  121. package/dist/tokens/components/feedback.css +17 -0
  122. package/dist/tokens/components/legal-document.css +67 -0
  123. package/dist/tokens/components/list-row.css +23 -2
  124. package/dist/tokens/components/logo.css +32 -0
  125. package/dist/tokens/components/navigation.css +13 -0
  126. package/dist/tokens/components/sheet.css +19 -0
  127. package/dist/tokens/components/shell.css +149 -1
  128. package/dist/tokens/components/sidebar.css +33 -0
  129. package/dist/tokens/foundation.css +5 -2
  130. package/dist/tokens/semantic/layout.css +44 -2
  131. package/package.json +17 -6
@@ -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-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-md",
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-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-md",
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
- ({ side = "right", className, children, showCloseButton = true, width, style, ...props }, ref) => {
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 horizontal = side === "left" || side === "right";
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. Use it INSTEAD of hand-rolling a
20
- * bare span with a fixed square size, a literal radius, and `bg-primary` + type utilities in shell
21
- * headers, auth screens, and topbars that repeats literal size/radius and puts type utilities on
22
- * a bare span (rules #45/#46). Size, radius, and per-tier font-size are tokens; the fill reads the
23
- * primary role, so a re-themed `--primary` re-tints the mark automatically. Pair with a wordmark for
24
- * the full lockup (leave `label` unset so the mark stays decorative and the wordmark carries the
25
- * accessible name).
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
- const Logo = React.forwardRef(
5
- ({ glyph = "g", size = "md", label, className, ...props }, ref) => /* @__PURE__ */ jsx(
6
- "span",
4
+ function MarkArtwork({ mark, glyph }) {
5
+ if (mark !== "godx") return /* @__PURE__ */ jsx(Fragment, { children: glyph });
6
+ return /* @__PURE__ */ jsx(
7
+ "svg",
7
8
  {
8
- ref,
9
- "data-slot": "logo",
10
- "data-size": size,
11
- className: cn("ui-logo", className),
12
- role: label ? "img" : void 0,
13
- "aria-label": label,
14
- "aria-hidden": label ? void 0 : true,
15
- ...props,
16
- children: glyph
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 {
@@ -49,20 +49,36 @@ function AppShell({
49
49
  type: "button",
50
50
  variant: "ghost",
51
51
  size: "sm",
52
- className: "app-mobile-nav-trigger hidden max-lg:inline-flex",
52
+ className: "app-mobile-nav-trigger hidden max-[900px]:inline-flex",
53
53
  "aria-label": t("layout.appShell.openNav"),
54
54
  "aria-haspopup": "dialog",
55
55
  children: /* @__PURE__ */ jsx(Menu, { className: "size-5", "aria-hidden": "true" })
56
56
  }
57
57
  ) }),
58
- /* @__PURE__ */ jsxs(SheetContent, { side: "left", className: "app-mobile-nav-drawer", children: [
59
- /* @__PURE__ */ jsx(SheetHeader, { title: mobileNavLabel ?? t("layout.appShell.navLabel") }),
60
- /* @__PURE__ */ jsx(SheetBody, { className: "app-mobile-nav-body", onClick: handleDrawerClick, children: drawerNav })
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({ brand, footer, children, className }) {
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
- return /* @__PURE__ */ jsxs("div", { "data-slot": "auth-shell", className: cn("ui-auth-shell", className), children: [
8
- brand !== void 0 && /* @__PURE__ */ jsx("header", { className: "ui-auth-shell-bar", "aria-label": t("layout.authShell.brandLabel"), children: brand }),
9
- /* @__PURE__ */ jsx("main", { className: "ui-auth-shell-main", "aria-label": t("layout.authShell.mainLabel"), children: /* @__PURE__ */ jsx("div", { className: "ui-auth-shell-card", children }) }),
10
- footer !== void 0 && /* @__PURE__ */ jsx("footer", { className: "ui-auth-shell-footer", "aria-label": t("layout.authShell.footerLabel"), children: footer })
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). A hosted account page thus needs ZERO custom CSS and never
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
  {