@godxjp/ui 18.5.0 → 18.7.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 (101) hide show
  1. package/dist/components/charts/compact-bar-trend.d.ts +2 -1
  2. package/dist/components/charts/index.d.ts +4 -1
  3. package/dist/components/data-display/data-table.d.ts +25 -4
  4. package/dist/components/data-display/data-table.js +13 -2
  5. package/dist/components/data-display/empty-state.js +1 -1
  6. package/dist/components/data-display/index.d.ts +1 -1
  7. package/dist/components/data-display/list-row.d.ts +19 -0
  8. package/dist/components/data-display/list-row.js +2 -0
  9. package/dist/components/data-display/qr-code.d.ts +1 -1
  10. package/dist/components/data-display/table.d.ts +47 -2
  11. package/dist/components/data-display/table.js +29 -4
  12. package/dist/components/data-entry/command.d.ts +10 -10
  13. package/dist/components/data-entry/index.d.ts +1 -0
  14. package/dist/components/data-entry/input-otp.d.ts +1 -0
  15. package/dist/components/feedback/alert.d.ts +20 -2
  16. package/dist/components/feedback/alert.js +11 -1
  17. package/dist/components/feedback/dialog.d.ts +14 -1
  18. package/dist/components/feedback/dialog.js +4 -0
  19. package/dist/components/feedback/index.d.ts +3 -3
  20. package/dist/components/feedback/index.js +5 -1
  21. package/dist/components/general/logo.d.ts +7 -1
  22. package/dist/components/general/logo.js +1 -1
  23. package/dist/components/layout/app-shell.d.ts +1 -1
  24. package/dist/components/layout/app-shell.js +55 -46
  25. package/dist/components/layout/auth-account-summary.d.ts +5 -0
  26. package/dist/components/layout/auth-account-summary.js +31 -0
  27. package/dist/components/layout/auth-shell.d.ts +5 -2
  28. package/dist/components/layout/centered-shell.d.ts +1 -1
  29. package/dist/components/layout/centered-shell.js +28 -19
  30. package/dist/components/layout/error-surface.d.ts +35 -0
  31. package/dist/components/layout/error-surface.js +155 -0
  32. package/dist/components/layout/flex.d.ts +1 -1
  33. package/dist/components/layout/flex.js +4 -0
  34. package/dist/components/layout/index.d.ts +7 -0
  35. package/dist/components/layout/index.js +6 -0
  36. package/dist/components/layout/page-container.d.ts +1 -1
  37. package/dist/components/layout/page-container.js +23 -41
  38. package/dist/components/layout/page-header.d.ts +22 -0
  39. package/dist/components/layout/page-header.js +89 -0
  40. package/dist/components/navigation/filter-bar.d.ts +2 -2
  41. package/dist/components/navigation/filter-bar.js +44 -1
  42. package/dist/components/navigation/index.d.ts +1 -1
  43. package/dist/components/navigation/steps.js +38 -19
  44. package/dist/components/navigation/tabs.js +12 -3
  45. package/dist/components/ui/avatar.d.ts +2 -1
  46. package/dist/components/ui/avatar.js +2 -1
  47. package/dist/components/ui/input-otp.d.ts +4 -1
  48. package/dist/components/ui/input-otp.js +2 -1
  49. package/dist/components/ui/toggle-group.d.ts +7 -4
  50. package/dist/components/ui/toggle-group.js +32 -21
  51. package/dist/email/brand-mark.d.ts +21 -5
  52. package/dist/email/brand-mark.js +5 -0
  53. package/dist/email/color.d.ts +2 -1
  54. package/dist/email/geometry.d.ts +9 -1
  55. package/dist/email/geometry.js +1 -0
  56. package/dist/email/index.d.ts +6 -1
  57. package/dist/email/index.js +6 -0
  58. package/dist/email/inline-style.d.ts +7 -5
  59. package/dist/email/inline-style.js +5 -0
  60. package/dist/email/tokens.generated.d.ts +40 -20
  61. package/dist/email/tokens.generated.js +40 -20
  62. package/dist/email/urgency.d.ts +22 -0
  63. package/dist/email/urgency.js +14 -0
  64. package/dist/i18n/messages/en.json +26 -1
  65. package/dist/i18n/messages/ja.json +23 -1
  66. package/dist/i18n/messages/vi.json +23 -1
  67. package/dist/props/components/data-display.prop.d.ts +63 -1
  68. package/dist/props/components/data-entry.prop.d.ts +6 -0
  69. package/dist/props/components/feedback.prop.d.ts +6 -0
  70. package/dist/props/components/index.d.ts +1 -1
  71. package/dist/props/components/layout.prop.d.ts +244 -4
  72. package/dist/props/components/navigation.prop.d.ts +57 -1
  73. package/dist/props/registry.d.ts +205 -5
  74. package/dist/props/registry.js +270 -6
  75. package/dist/props/vocabulary/data.prop.d.ts +27 -0
  76. package/dist/props/vocabulary/index.d.ts +3 -3
  77. package/dist/props/vocabulary/interaction.prop.d.ts +19 -2
  78. package/dist/props/vocabulary/layout.prop.d.ts +41 -7
  79. package/dist/styles/alert-layout.css +36 -0
  80. package/dist/styles/control.css +28 -0
  81. package/dist/styles/data-display-layout.css +26 -0
  82. package/dist/styles/layout.css +212 -0
  83. package/dist/styles/logo-layout.css +42 -9
  84. package/dist/styles/navigation-layout.css +109 -0
  85. package/dist/styles/shell-layout.css +336 -0
  86. package/dist/styles/table-layout.css +164 -1
  87. package/dist/theme/dxs.canonical.css +5 -4
  88. package/dist/tokens/axes.css +4 -3
  89. package/dist/tokens/base.css +1 -0
  90. package/dist/tokens/components/data-display.css +13 -0
  91. package/dist/tokens/components/email.css +43 -16
  92. package/dist/tokens/components/error-surface.css +36 -0
  93. package/dist/tokens/components/feedback.css +20 -0
  94. package/dist/tokens/components/list-row.css +20 -0
  95. package/dist/tokens/components/logo.css +52 -10
  96. package/dist/tokens/components/navigation.css +41 -0
  97. package/dist/tokens/components/shell.css +109 -0
  98. package/dist/tokens/components/table.css +45 -0
  99. package/dist/tokens/foundation.css +26 -0
  100. package/dist/tokens/semantic/layout.css +38 -0
  101. package/package.json +8 -2
@@ -0,0 +1,22 @@
1
+ /**
2
+ * PageHeader — the canonical page title band (gh#255).
3
+ *
4
+ * This is the geometry `PageContainer` has always rendered, lifted into its own export so a
5
+ * consumer can mount it OUTSIDE a full page shell — a Sheet detail, a `MasterDetail` detail pane, a
6
+ * tab body, a nested route header — without re-authoring `.ui-page-header` CSS locally. There is
7
+ * exactly ONE implementation: `PageContainer` renders this component, so the two can never drift.
8
+ *
9
+ * The band owns: breadcrumbs (a named `<nav>` landmark with `aria-current="page"` on the leaf), the
10
+ * `<h1>` title, the subtitle/description, a status/meta slot, the `extra` action region, the
11
+ * responsive overflow arrangement (`layout`), and the pending state (`loading`). It owns no page
12
+ * chrome — padding, measure and body rhythm stay with `PageContainer`.
13
+ *
14
+ * DENIED / ERROR are deliberately NOT states of this component: a page the user may not see must
15
+ * not render its title, breadcrumb trail or actions at all (they leak the resource's existence and
16
+ * its name). That whole-surface contract belongs to `ErrorSurface`, which is the canonical public
17
+ * replacement — see `docs/CANONICAL-CONTRACTS.md`.
18
+ */
19
+ import * as React from "react";
20
+ import type { PageHeaderProp } from "../../props/components/layout.prop.js";
21
+ export type { PageHeaderProp, PageHeaderProp as PageHeaderProps, } from "../../props/components/layout.prop.js";
22
+ export declare const PageHeader: React.ForwardRefExoticComponent<PageHeaderProp & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,89 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { ChevronRight } from "lucide-react";
5
+ import { useTranslation } from "../../i18n/use-translation.js";
6
+ import { cn } from "../../lib/utils.js";
7
+ const PageHeader = React.forwardRef(function PageHeader2({
8
+ title,
9
+ subtitle,
10
+ meta,
11
+ extra,
12
+ breadcrumb,
13
+ breadcrumbLabel,
14
+ breadcrumbAriaLabel,
15
+ linkComponent: LinkComponent = "a",
16
+ layout = "stack",
17
+ loading = false,
18
+ className
19
+ }, ref) {
20
+ const { t } = useTranslation();
21
+ return (
22
+ // `data-layout` is the only hook the header arrangement needs: `stack` (the default)
23
+ // matches no rule, so the historical geometry is untouched, while `responsive-inline`
24
+ // selects the compact-range rules that keep `extra` beside the title band (gh#231).
25
+ /* @__PURE__ */ jsxs(
26
+ "header",
27
+ {
28
+ ref,
29
+ className: cn("ui-page-header", className),
30
+ "data-layout": layout,
31
+ "aria-busy": loading ? "true" : void 0,
32
+ children: [
33
+ breadcrumb && breadcrumb.length > 0 && /* @__PURE__ */ jsx(
34
+ "nav",
35
+ {
36
+ "aria-label": breadcrumbLabel ?? breadcrumbAriaLabel ?? t("navigation.breadcrumb.ariaLabel"),
37
+ className: "ui-breadcrumb",
38
+ children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: breadcrumb.map((item, i) => {
39
+ const isLast = i === breadcrumb.length - 1;
40
+ return /* @__PURE__ */ jsxs("li", { className: "ui-inline-xs", children: [
41
+ item.to && !isLast ? /* @__PURE__ */ jsx(
42
+ LinkComponent,
43
+ {
44
+ href: item.to,
45
+ to: item.to,
46
+ className: "hover:text-foreground hover:underline",
47
+ children: item.label
48
+ }
49
+ ) : /* @__PURE__ */ jsx(
50
+ "span",
51
+ {
52
+ className: isLast ? "text-foreground" : "",
53
+ "aria-current": isLast ? "page" : void 0,
54
+ children: item.label
55
+ }
56
+ ),
57
+ !isLast && /* @__PURE__ */ jsx(ChevronRight, { className: "size-3", "aria-hidden": "true" })
58
+ ] }, i);
59
+ }) })
60
+ }
61
+ ),
62
+ /* @__PURE__ */ jsxs("div", { className: "ui-page-header-row", children: [
63
+ /* @__PURE__ */ jsxs("div", { className: "ui-page-header-heading min-w-0", children: [
64
+ loading ? /* @__PURE__ */ jsx("h1", { className: "ui-page-title ui-skeleton-block ui-page-title-placeholder", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("layout.pageHeader.loading") }) }) : meta != null ? /* @__PURE__ */ jsxs("div", { className: "ui-page-title-band", children: [
65
+ /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
66
+ /* @__PURE__ */ jsx("div", { className: "ui-page-header-meta", children: meta })
67
+ ] }) : /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
68
+ loading ? (
69
+ // Decorative only — the pending state is already announced once by the heading above,
70
+ // so a second live placeholder here would double-announce it.
71
+ /* @__PURE__ */ jsx(
72
+ "p",
73
+ {
74
+ className: "ui-page-subtitle ui-skeleton-block ui-page-subtitle-placeholder",
75
+ "aria-hidden": "true"
76
+ }
77
+ )
78
+ ) : subtitle && /* @__PURE__ */ jsx("p", { className: "ui-page-subtitle", children: subtitle })
79
+ ] }),
80
+ extra && /* @__PURE__ */ jsx("div", { className: "ui-page-header-extra", children: extra })
81
+ ] })
82
+ ]
83
+ }
84
+ )
85
+ );
86
+ });
87
+ export {
88
+ PageHeader
89
+ };
@@ -1,6 +1,6 @@
1
1
  import type { ToolbarGroupProp, ToolbarProp } from "../../props/components/navigation.prop.js";
2
- export type { FilterBarOverflowProp, ToolbarGroupProp, ToolbarGroupProp as ToolbarGroupProps, ToolbarGroupProp as FilterBarGroupProp, ToolbarGroupProp as FilterBarGroupProps, ToolbarProp, ToolbarProp as ToolbarProps, ToolbarProp as FilterBarProp, ToolbarProp as FilterBarProps, } from "../../props/components/navigation.prop.js";
3
- export declare function Toolbar({ onClear, hasActiveFilters, sticky, overflow, className, children, }: ToolbarProp): import("react").JSX.Element;
2
+ export type { FilterBarChipProp, FilterBarChipProp as FilterBarChipProps, FilterBarOverflowProp, ToolbarGroupProp, ToolbarGroupProp as ToolbarGroupProps, ToolbarGroupProp as FilterBarGroupProp, ToolbarGroupProp as FilterBarGroupProps, ToolbarProp, ToolbarProp as ToolbarProps, ToolbarProp as FilterBarProp, ToolbarProp as FilterBarProps, } from "../../props/components/navigation.prop.js";
3
+ export declare function Toolbar({ onClear, hasActiveFilters, sticky, overflow, search, chips, onChipRemove, resultCount, actions, className, children, }: ToolbarProp): import("react").JSX.Element;
4
4
  export declare function ToolbarGroup({ label, controlId, className, children }: ToolbarGroupProp): import("react").JSX.Element;
5
5
  /**
6
6
  * Canonical list-page filter strip.
@@ -10,10 +10,16 @@ function Toolbar({
10
10
  hasActiveFilters = true,
11
11
  sticky = false,
12
12
  overflow = "wrap",
13
+ search,
14
+ chips,
15
+ onChipRemove,
16
+ resultCount,
17
+ actions,
13
18
  className,
14
19
  children
15
20
  }) {
16
21
  const { t } = useTranslation();
22
+ const hasChips = chips != null && chips.length > 0;
17
23
  return /* @__PURE__ */ jsxs(
18
24
  "div",
19
25
  {
@@ -22,11 +28,48 @@ function Toolbar({
22
28
  "data-overflow": overflow,
23
29
  className: cn("ui-toolbar", sticky && "ui-toolbar-sticky", className),
24
30
  children: [
31
+ search != null && // A region, not a bare child, so the search control gets ONE measure
32
+ // (--filter-bar-search-width) across every list page instead of whatever width the
33
+ // caller's control happened to have.
34
+ /* @__PURE__ */ jsx("div", { className: "ui-filter-bar-search", children: search }),
25
35
  children,
36
+ hasChips && // `group` (not a second toolbar) — a toolbar may own a group, and the chips are a labelled
37
+ // set of related controls rather than an independent toolbar competing for the same role.
38
+ /* @__PURE__ */ jsx(
39
+ "div",
40
+ {
41
+ role: "group",
42
+ "aria-label": t("navigation.filterBar.appliedFilters"),
43
+ className: "ui-filter-bar-chips",
44
+ children: chips.map((chip) => /* @__PURE__ */ jsxs("span", { className: "ui-filter-bar-chip", children: [
45
+ /* @__PURE__ */ jsx("span", { className: "ui-filter-bar-chip-label", children: chip.label }),
46
+ (chip.onRemove || onChipRemove) && /* @__PURE__ */ jsx(
47
+ Button,
48
+ {
49
+ variant: "ghost",
50
+ size: "xs",
51
+ className: "ui-filter-bar-chip-remove",
52
+ "aria-label": t("navigation.filterBar.removeFilter", { label: chip.label }),
53
+ onClick: () => {
54
+ chip.onRemove?.();
55
+ onChipRemove?.(chip.id);
56
+ },
57
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
58
+ }
59
+ )
60
+ ] }, chip.id))
61
+ }
62
+ ),
63
+ resultCount !== void 0 && // The whole reason this is a prop rather than caller markup: a sighted user SEES the table
64
+ // change, and a polite live region is what tells everyone else that filtering happened.
65
+ // The number is grouped by `Intl.NumberFormat` and the noun selected by CLDR plural rules
66
+ // for the active locale — never a hand-built "N items".
67
+ /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", className: "ui-filter-bar-count", children: t("navigation.filterBar.resultCount", { count: resultCount }) }),
26
68
  onClear && hasActiveFilters && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: onClear, className: "ui-toolbar-clear", children: [
27
69
  /* @__PURE__ */ jsx(X, { "aria-hidden": "true" }),
28
70
  t("common.clearFilters")
29
- ] })
71
+ ] }),
72
+ actions != null && /* @__PURE__ */ jsx("div", { className: "ui-filter-bar-actions", children: actions })
30
73
  ]
31
74
  }
32
75
  );
@@ -5,7 +5,7 @@ export { Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarLabel
5
5
  export { NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, } from "./navigation-menu.js";
6
6
  export { Steps } from "./steps.js";
7
7
  export { FilterBar, FilterBarGroup, Toolbar, ToolbarGroup } from "./filter-bar.js";
8
- export type { FilterBarGroupProp, FilterBarGroupProps, FilterBarProp, FilterBarProps, ToolbarGroupProps, ToolbarProps, } from "./filter-bar.js";
8
+ export type { FilterBarChipProp, FilterBarChipProps, FilterBarGroupProp, FilterBarGroupProps, FilterBarProp, FilterBarProps, ToolbarGroupProps, ToolbarProps, } from "./filter-bar.js";
9
9
  export { AppSettingPicker } from "./app-setting-picker.js";
10
10
  export type { AppSettingPickerProp, AppSettingPickerProps, AppSettingKind, } from "./app-setting-picker.js";
11
11
  export type { BreadcrumbItemProp as BreadcrumbItem } from "../../props/vocabulary/navigation.prop.js";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { Check, Circle, Loader2, X } from "lucide-react";
3
+ import { Check, ChevronRight, Circle, Loader2, X } from "lucide-react";
4
4
  import { useTranslation } from "../../i18n/use-translation.js";
5
5
  import { cn } from "../../lib/utils.js";
6
6
  import { controlIconClass } from "../../lib/control-styles.js";
@@ -68,12 +68,13 @@ function Steps({
68
68
  const base = defaultValue;
69
69
  const isVertical = orientation === "vertical";
70
70
  const compact = size === "sm";
71
+ const inline = type === "inline";
71
72
  return /* @__PURE__ */ jsx(
72
73
  "ol",
73
74
  {
74
75
  className: cn(
75
76
  "flex w-full",
76
- isVertical ? "flex-col gap-0" : "flex-row items-start",
77
+ inline ? "ui-steps-inline" : isVertical ? "flex-col gap-0" : "flex-row items-start",
77
78
  className
78
79
  ),
79
80
  "aria-label": t("navigation.steps.ariaLabel"),
@@ -84,12 +85,19 @@ function Steps({
84
85
  const clickable = interactive && !item.disabled;
85
86
  const description = item.description;
86
87
  const isCurrent = absoluteIndex === current;
88
+ const statusLabel = t(
89
+ stepStatus === "finish" ? "navigation.steps.status.finish" : stepStatus === "process" ? "navigation.steps.status.process" : stepStatus === "error" ? "navigation.steps.status.error" : "navigation.steps.status.wait"
90
+ );
87
91
  const stepClassName = cn(
88
92
  "group flex min-w-0",
89
- isVertical ? "flex-row items-start gap-3" : "flex-col items-center",
93
+ inline ? "ui-steps-inline-control" : isVertical ? "flex-row items-start gap-3" : "flex-col items-center",
90
94
  clickable ? "cursor-pointer" : "cursor-default"
91
95
  );
92
- const stepInner = /* @__PURE__ */ jsxs(Fragment, { children: [
96
+ const stepInner = inline ? /* @__PURE__ */ jsxs(Fragment, { children: [
97
+ /* @__PURE__ */ jsx("span", { className: "ui-steps-inline-index", "aria-hidden": "true", children: absoluteIndex + 1 }),
98
+ /* @__PURE__ */ jsx("span", { className: "ui-steps-inline-title", children: item.title }),
99
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: statusLabel })
100
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
93
101
  /* @__PURE__ */ jsx(StepIcon, { status: stepStatus, icon: item.icon, type }),
94
102
  /* @__PURE__ */ jsxs(
95
103
  "div",
@@ -119,26 +127,37 @@ function Steps({
119
127
  }
120
128
  )
121
129
  ] });
122
- return /* @__PURE__ */ jsx(
130
+ return /* @__PURE__ */ jsxs(
123
131
  "li",
124
132
  {
125
133
  "aria-current": isCurrent ? "step" : void 0,
126
134
  className: cn(
127
- "relative flex min-w-0 flex-1",
128
- isVertical ? "flex-row gap-3 pb-8 last:pb-0" : "flex-col items-center text-center",
129
- !isVertical && index < items.length - 1 && "after:bg-border after:absolute after:top-4 after:h-px",
130
- !isVertical && index < items.length - 1 && "after:left-[calc(50%+1.25rem)] after:w-[calc(100%-2.5rem)]"
135
+ "relative flex min-w-0",
136
+ !inline && "flex-1",
137
+ inline ? "ui-steps-inline-item" : isVertical ? "flex-row gap-3 pb-8 last:pb-0" : "flex-col items-center text-center",
138
+ !isVertical && !inline && index < items.length - 1 && "after:bg-border after:absolute after:top-4 after:h-px",
139
+ !isVertical && !inline && index < items.length - 1 && "after:left-[calc(50%+1.25rem)] after:w-[calc(100%-2.5rem)]"
131
140
  ),
132
- children: interactive ? /* @__PURE__ */ jsx(
133
- "button",
134
- {
135
- type: "button",
136
- disabled: !clickable,
137
- onClick: clickable ? () => onValueChange?.(absoluteIndex) : void 0,
138
- className: stepClassName,
139
- children: stepInner
140
- }
141
- ) : /* @__PURE__ */ jsx("span", { className: stepClassName, children: stepInner })
141
+ "data-status": stepStatus,
142
+ children: [
143
+ interactive ? /* @__PURE__ */ jsx(
144
+ "button",
145
+ {
146
+ type: "button",
147
+ disabled: !clickable,
148
+ onClick: clickable ? () => onValueChange?.(absoluteIndex) : void 0,
149
+ className: stepClassName,
150
+ children: stepInner
151
+ }
152
+ ) : /* @__PURE__ */ jsx("span", { className: stepClassName, children: stepInner }),
153
+ inline && index < items.length - 1 ? /* @__PURE__ */ jsx(
154
+ ChevronRight,
155
+ {
156
+ className: "ui-steps-inline-separator rtl:rotate-180",
157
+ "aria-hidden": "true"
158
+ }
159
+ ) : null
160
+ ]
142
161
  },
143
162
  index
144
163
  );
@@ -42,8 +42,9 @@ function Tabs({
42
42
  TabsList,
43
43
  {
44
44
  "data-slot": "tabs-list",
45
+ variant: variant === "line" ? "line" : "default",
45
46
  className: cn(
46
- variant === "line" && "h-auto w-full justify-start rounded-none border-b bg-transparent p-0",
47
+ variant === "line" && "h-auto w-full justify-start border-b p-0",
47
48
  variant === "card" && "w-full justify-start",
48
49
  listClassName
49
50
  ),
@@ -53,7 +54,9 @@ function Tabs({
53
54
  value: item.value,
54
55
  disabled: item.disabled,
55
56
  className: cn(
56
- variant === "line" && "data-[state=active]:border-primary rounded-none border-b-2 border-transparent bg-transparent px-4 py-2 shadow-none data-[state=active]:bg-transparent data-[state=active]:shadow-none",
57
+ // Geometry only the active underline is the token-owned `::after` bar the
58
+ // line variant already owns (never a second, hand-rolled border-b indicator).
59
+ variant === "line" && "rounded-none px-4 py-2",
57
60
  variant === "card" && "data-[state=active]:shadow-sm"
58
61
  ),
59
62
  children: item.label
@@ -117,7 +120,13 @@ const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
117
120
  ref,
118
121
  "data-slot": "tabs-trigger",
119
122
  className: cn(
120
- "text-muted-foreground ring-offset-background hover:text-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-ring/50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:ring-primary/25 after:bg-foreground relative inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:ring-1 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
123
+ // The SELECTED-state ring (`ring-1 ring-primary/25`) is scoped to the default/card lists
124
+ // the `line` variant is underline-only (gh#248) and must NEVER paint a surrounding ring, or
125
+ // it competes with (and at equal specificity overrides) the `:focus-visible` ring. The
126
+ // focus-visible ring/outline below is deliberately left unscoped: every variant keeps a
127
+ // visible keyboard focus indicator (WCAG 2.4.7). The line indicator itself lives in
128
+ // src/styles/navigation-layout.css so it reads the --tabs-indicator-* tokens.
129
+ "text-muted-foreground ring-offset-background hover:text-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-ring/50 data-[state=active]:bg-background data-[state=active]:text-foreground group-data-[variant=default]/tabs-list:data-[state=active]:ring-primary/25 relative inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start group-data-[variant=line]/tabs-list:border-e-0 group-data-[variant=line]/tabs-list:border-b-0 focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=default]/tabs-list:data-[state=active]:ring-1 group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none",
121
130
  className
122
131
  ),
123
132
  ...props
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as AvatarPrimitive from "@radix-ui/react-avatar";
3
- export declare const Avatar: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarProps & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
3
+ import type { AvatarProp } from "../../props/components/data-display.prop.js";
4
+ export declare const Avatar: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarProps & React.RefAttributes<HTMLSpanElement>, "ref"> & Pick<AvatarProp, "shape"> & React.RefAttributes<HTMLSpanElement>>;
4
5
  export declare const AvatarImage: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarImageProps & React.RefAttributes<HTMLImageElement>, "ref"> & React.RefAttributes<HTMLImageElement>>;
5
6
  export declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarFallbackProps & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
@@ -3,11 +3,12 @@ import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import * as AvatarPrimitive from "@radix-ui/react-avatar";
5
5
  import { cn } from "../../lib/utils.js";
6
- const Avatar = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
6
+ const Avatar = React.forwardRef(({ className, shape = "circle", ...props }, ref) => /* @__PURE__ */ jsx(
7
7
  AvatarPrimitive.Root,
8
8
  {
9
9
  ref,
10
10
  "data-slot": "avatar",
11
+ "data-shape": shape === "square" ? "square" : void 0,
11
12
  className: cn("ui-avatar", className),
12
13
  ...props
13
14
  }
@@ -26,7 +26,10 @@ export declare const InputOTP: React.ForwardRefExoticComponent<(Omit<Omit<React.
26
26
  render?: never;
27
27
  children: React.ReactNode;
28
28
  } & React.RefAttributes<HTMLInputElement>, "ref">) & React.RefAttributes<HTMLInputElement>>;
29
- export declare const InputOTPGroup: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
29
+ export type { InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProp as InputOTPGroupProps, } from "../../props/components/data-entry.prop.js";
30
+ export declare const InputOTPGroup: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
31
+ appearance?: import("./input-otp.js").InputOTPGroupAppearanceProp;
32
+ } & React.RefAttributes<HTMLDivElement>>;
30
33
  export declare const InputOTPSlot: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
31
34
  index: number;
32
35
  } & React.RefAttributes<HTMLDivElement>>;
@@ -16,11 +16,12 @@ const InputOTP = React.forwardRef(({ className, containerClassName, ...props },
16
16
  ));
17
17
  InputOTP.displayName = "InputOTP";
18
18
  const InputOTPGroup = React.forwardRef(
19
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
19
+ ({ appearance = "slots", className, ...props }, ref) => /* @__PURE__ */ jsx(
20
20
  "div",
21
21
  {
22
22
  ref,
23
23
  "data-slot": "input-otp-group",
24
+ "data-appearance": appearance,
24
25
  className: cn("ui-otp-group", className),
25
26
  ...props
26
27
  }
@@ -1,11 +1,14 @@
1
1
  import * as React from "react";
2
2
  import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
3
3
  import { type ToggleProps } from "./toggle.js";
4
+ type ToggleGroupVariant = ToggleProps["variant"];
5
+ type ToggleGroupSize = ToggleProps["size"];
4
6
  export declare const ToggleGroup: React.ForwardRefExoticComponent<((Omit<ToggleGroupPrimitive.ToggleGroupSingleProps & React.RefAttributes<HTMLDivElement>, "ref"> | Omit<ToggleGroupPrimitive.ToggleGroupMultipleProps & React.RefAttributes<HTMLDivElement>, "ref">) & {
5
- variant?: ToggleProps["variant"];
6
- size?: ToggleProps["size"];
7
+ variant?: ToggleGroupVariant;
8
+ size?: ToggleGroupSize;
7
9
  }) & React.RefAttributes<HTMLDivElement>>;
8
10
  export declare const ToggleGroupItem: React.ForwardRefExoticComponent<Omit<ToggleGroupPrimitive.ToggleGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
9
- variant?: ToggleProps["variant"];
10
- size?: ToggleProps["size"];
11
+ variant?: ToggleGroupVariant;
12
+ size?: ToggleGroupSize;
11
13
  } & React.RefAttributes<HTMLButtonElement>>;
14
+ export {};
@@ -4,28 +4,39 @@ import * as React from "react";
4
4
  import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
5
5
  import { cn } from "../../lib/utils.js";
6
6
  import { toggleVariants } from "./toggle.js";
7
- const ToggleGroup = React.forwardRef(({ className, variant = "default", size = "default", children, ...props }, ref) => /* @__PURE__ */ jsx(
8
- ToggleGroupPrimitive.Root,
9
- {
10
- ref,
11
- "data-slot": "toggle-group",
12
- "data-variant": variant,
13
- "data-size": size,
14
- className: cn("ui-toggle-group", className),
15
- ...props,
16
- children
17
- }
18
- ));
7
+ const ToggleGroupContext = React.createContext({});
8
+ const ToggleGroup = React.forwardRef(({ className, variant, size, children, ...props }, ref) => {
9
+ const context = React.useMemo(() => ({ variant, size }), [variant, size]);
10
+ return /* @__PURE__ */ jsx(
11
+ ToggleGroupPrimitive.Root,
12
+ {
13
+ ref,
14
+ "data-slot": "toggle-group",
15
+ "data-variant": variant,
16
+ "data-size": size,
17
+ className: cn("ui-toggle-group", className),
18
+ ...props,
19
+ children: /* @__PURE__ */ jsx(ToggleGroupContext.Provider, { value: context, children })
20
+ }
21
+ );
22
+ });
19
23
  ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
20
- const ToggleGroupItem = React.forwardRef(({ className, variant, size, ...props }, ref) => /* @__PURE__ */ jsx(
21
- ToggleGroupPrimitive.Item,
22
- {
23
- ref,
24
- "data-slot": "toggle-group-item",
25
- className: cn(toggleVariants({ variant, size }), className),
26
- ...props
27
- }
28
- ));
24
+ const ToggleGroupItem = React.forwardRef(({ className, variant, size, ...props }, ref) => {
25
+ const context = React.useContext(ToggleGroupContext);
26
+ const resolvedVariant = variant ?? context.variant;
27
+ const resolvedSize = size ?? context.size;
28
+ return /* @__PURE__ */ jsx(
29
+ ToggleGroupPrimitive.Item,
30
+ {
31
+ ref,
32
+ "data-slot": "toggle-group-item",
33
+ "data-variant": resolvedVariant,
34
+ "data-size": resolvedSize,
35
+ className: cn(toggleVariants({ variant: resolvedVariant, size: resolvedSize }), className),
36
+ ...props
37
+ }
38
+ );
39
+ });
29
40
  ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
30
41
  export {
31
42
  ToggleGroup,
@@ -4,7 +4,10 @@
4
4
  * The mark is an emerald CAPSULE with an internal light GLYPH bar — the same artwork the React
5
5
  * `<Logo mark="godx" />` paints, at the same 32×32 viewBox and the same coordinates (a test asserts
6
6
  * the capsule path is byte-identical to the component's, so the two can never diverge). A capsule
7
- * WITHOUT the internal glyph is the incomplete mark and must not ship.
7
+ * WITHOUT the internal glyph is the incomplete mark and must not ship. The ARTWORK matches the
8
+ * canonical SCR-302 raster exactly; only the rendered BOX is email-specific — the canonical email
9
+ * header sets it at 22px (`--email-mark-width`) beside the "GoDX" wordmark, where the web
10
+ * `<Logo mark="godx" />` box is 32px.
8
11
  *
9
12
  * Nothing here references an external or relative asset: an email template has no base URL it can
10
13
  * trust, remote images are blocked by default in most clients, and a `cid:` attachment needs
@@ -16,8 +19,10 @@
16
19
  * Renders everywhere; degrades to a square emerald block with a light bar in Outlook's Word
17
20
  * renderer (which drops `border-radius`) — still recognisably the mark, never a broken image.
18
21
  *
19
- * Colours come from `EMAIL_COLORS` (derived from `--success` / `--success-foreground`, the roles
20
- * `--logo-godx-color` already points at), so a re-themed identity role re-tints the mark.
22
+ * Colours come from `EMAIL_COLORS` (derived from `--brand` / `--brand-foreground`, the roles
23
+ * `--logo-godx-color` points at), so a re-themed identity role re-tints the mark. NOT `--success`:
24
+ * that is the wakatake STATUS green, and binding identity to it rendered the mark ~ΔE76 17 away
25
+ * from the canonical emerald until gh#250 introduced the dedicated `--brand` role.
21
26
  */
22
27
  import { type EmailHex } from "./color.js";
23
28
  /** A rounded rectangle in the mark's 32×32 coordinate space. */
@@ -42,9 +47,9 @@ export declare const EMAIL_BRAND_MARK_GLYPH: EmailBrandMarkRect;
42
47
  export declare function roundedRectPath({ x, y, width, height, radius }: EmailBrandMarkRect): string;
43
48
  /** Options for rendering the mark. Every default comes from the token contract. */
44
49
  export interface EmailBrandMarkOptions {
45
- /** Rendered width in px. Default `EMAIL_BRAND_MARK.widthPx` (32). */
50
+ /** Rendered width in px. Default `EMAIL_BRAND_MARK.widthPx` (the canonical 22px header box). */
46
51
  width?: number;
47
- /** Rendered height in px. Default `EMAIL_BRAND_MARK.heightPx` (32). */
52
+ /** Rendered height in px. Default `EMAIL_BRAND_MARK.heightPx` (the canonical 22px header box). */
48
53
  height?: number;
49
54
  /** Capsule fill. Default `EMAIL_COLORS.brand`. */
50
55
  color?: EmailHex;
@@ -82,6 +87,17 @@ export interface EmailBrandMark {
82
87
  readonly glyph: EmailBrandMarkRect;
83
88
  readonly capsulePath: string;
84
89
  readonly glyphPath: string;
90
+ /**
91
+ * Mark ↔ wordmark separation in the canonical header LOCKUP. The mark alone is not the header:
92
+ * the canonical card sets the mark beside a readable "GoDX" wordmark, and the wordmark — not the
93
+ * mark — carries the accessible name (render the mark with `label: ""` in that pairing).
94
+ */
95
+ readonly gap: string;
96
+ readonly gapPx: number;
97
+ /** Wordmark type size in the lockup. */
98
+ readonly wordmarkFontSize: string;
99
+ readonly wordmarkFontSizePx: number;
100
+ readonly wordmarkFontWeight: number;
85
101
  /** Inline `<svg>` — highest fidelity. */
86
102
  readonly svg: string;
87
103
  /** `data:` URL of the same SVG, for `<img src>`. */
@@ -67,6 +67,11 @@ const EMAIL_BRAND_MARK = Object.freeze({
67
67
  glyph: EMAIL_BRAND_MARK_GLYPH,
68
68
  capsulePath: CAPSULE_PATH,
69
69
  glyphPath: GLYPH_PATH,
70
+ gap: EMAIL_GEOMETRY_SOURCE["--email-mark-gap"],
71
+ gapPx: Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-mark-gap"]),
72
+ wordmarkFontSize: EMAIL_GEOMETRY_SOURCE["--email-wordmark-font-size"],
73
+ wordmarkFontSizePx: Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-wordmark-font-size"]),
74
+ wordmarkFontWeight: Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-wordmark-font-weight"]),
70
75
  svg: emailBrandMarkSvg(),
71
76
  dataUri: emailBrandMarkDataUri(),
72
77
  tableHtml: emailBrandMarkTableHtml()
@@ -41,7 +41,8 @@ export declare function hslToHex(triplet: string): EmailHex;
41
41
  * | `border` | `--border` | card edge, footer hairline |
42
42
  * | `primary` / `primaryForeground`| `--primary` / `--primary-foreground` | the one CTA |
43
43
  * | `focus` | `--ring` | focus affordance in webmail panes |
44
- * | `brand` / `brandForeground` | `--success` / `--success-foreground` | the GoDX capsule + its glyph |
44
+ * | `brand` / `brandForeground` | `--brand` / `--brand-foreground` | the GoDX capsule + its glyph |
45
+ * | `urgency` / `urgencyForeground`| `--attention` / `--attention-foreground` | action-required accent / solid large label |
45
46
  */
46
47
  export declare const EMAIL_COLORS: Readonly<Record<EmailColorToken, EmailHex>>;
47
48
  /**
@@ -45,8 +45,16 @@ export interface EmailShellTokens {
45
45
  export declare const EMAIL_SHELL: EmailShellTokens;
46
46
  /** Type ramp. Sizes are literal px — no email client resolves the rem scale reliably. */
47
47
  export interface EmailTypographyTokens {
48
- /** Email-safe font stack. Web-font `@font-face` is unavailable in most clients — never rely on it. */
48
+ /**
49
+ * The canonical DXS stack, M PLUS 2 first. Web-font `@font-face` is unavailable in most clients,
50
+ * so this NAMES the face and degrades: a client with M PLUS 2 installed (or a webmail that
51
+ * already loaded it) renders the canonical face; everything else falls to Hiragino (macOS/iOS) →
52
+ * Yu Gothic (Windows) → Noto Sans JP → Meiryo → the system UI face → Arial → `sans-serif`. Family
53
+ * names are SINGLE-quoted so the value drops straight into a double-quoted `style="…"` attribute.
54
+ */
49
55
  readonly fontFamily: string;
56
+ /** Mono stack for invoice ids, masked card numbers, ISO dates and amounts. */
57
+ readonly monoFontFamily: string;
50
58
  readonly bodyFontSize: string;
51
59
  readonly bodyFontSizePx: number;
52
60
  readonly bodyLineHeight: number;
@@ -30,6 +30,7 @@ const EMAIL_SHELL = Object.freeze({
30
30
  });
31
31
  const EMAIL_TYPOGRAPHY = Object.freeze({
32
32
  fontFamily: raw("--email-font-family-sans"),
33
+ monoFontFamily: raw("--email-font-family-mono"),
33
34
  bodyFontSize: raw("--email-body-font-size"),
34
35
  bodyFontSizePx: num("--email-body-font-size"),
35
36
  bodyLineHeight: num("--email-body-line-height"),
@@ -23,13 +23,14 @@
23
23
  * ## Usage
24
24
  *
25
25
  * ```ts
26
- * import { EMAIL_COLORS, EMAIL_SHELL, EMAIL_CTA, EMAIL_BRAND_MARK, emailInlineStyle } from "@godxjp/ui/email";
26
+ * import { EMAIL_COLORS, EMAIL_URGENCY, EMAIL_SHELL, EMAIL_CTA, EMAIL_BRAND_MARK, emailInlineStyle } from "@godxjp/ui/email";
27
27
  *
28
28
  * const card = emailInlineStyle({
29
29
  * width: EMAIL_SHELL.width,
30
30
  * padding: EMAIL_SHELL.padding,
31
31
  * backgroundColor: EMAIL_COLORS.surface,
32
32
  * border: `${EMAIL_SHELL.borderWidth} solid ${EMAIL_COLORS.border}`,
33
+ * borderTopColor: EMAIL_URGENCY.accent,
33
34
  * borderRadius: EMAIL_SHELL.radius,
34
35
  * });
35
36
  * ```
@@ -52,14 +53,18 @@ export { hslToHex, EMAIL_COLORS, EMAIL_COLORS_DARK, type EmailColorToken, type E
52
53
  export { EMAIL_SHELL, EMAIL_TYPOGRAPHY, EMAIL_CTA, EMAIL_FOOTER, EMAIL_FOCUS, EMAIL_MOBILE, type EmailTokenName, type EmailShellTokens, type EmailTypographyTokens, type EmailCtaTokens, type EmailFooterTokens, type EmailFocusTokens, type EmailMobileTokens, } from "./geometry.js";
53
54
  export { EMAIL_BRAND_MARK, EMAIL_BRAND_MARK_VIEWBOX, EMAIL_BRAND_MARK_CAPSULE, EMAIL_BRAND_MARK_GLYPH, emailBrandMarkSvg, emailBrandMarkDataUri, emailBrandMarkTableHtml, roundedRectPath, type EmailBrandMark, type EmailBrandMarkOptions, type EmailBrandMarkRect, } from "./brand-mark.js";
54
55
  export { emailInlineStyle, type EmailStyleDeclarations } from "./inline-style.js";
56
+ export { EMAIL_URGENCY, EMAIL_URGENCY_DARK, type EmailUrgencyTokens } from "./urgency.js";
55
57
  export { EMAIL_COLOR_SOURCE, EMAIL_COLOR_SOURCE_DARK, EMAIL_GEOMETRY_SOURCE, type EmailColorSource, } from "./tokens.generated.js";
56
58
  import { EMAIL_COLORS, EMAIL_COLORS_DARK } from "./color.js";
57
59
  import { EMAIL_SHELL, EMAIL_TYPOGRAPHY, EMAIL_CTA, EMAIL_FOOTER, EMAIL_FOCUS, EMAIL_MOBILE } from "./geometry.js";
58
60
  import { EMAIL_BRAND_MARK } from "./brand-mark.js";
61
+ import { EMAIL_URGENCY, EMAIL_URGENCY_DARK } from "./urgency.js";
59
62
  /** The whole contract in one object — the shape `EMAIL_TOKENS_JSON` serialises. */
60
63
  export interface EmailTokens {
61
64
  readonly colors: typeof EMAIL_COLORS;
62
65
  readonly colorsDark: typeof EMAIL_COLORS_DARK;
66
+ readonly urgency: typeof EMAIL_URGENCY;
67
+ readonly urgencyDark: typeof EMAIL_URGENCY_DARK;
63
68
  readonly shell: typeof EMAIL_SHELL;
64
69
  readonly typography: typeof EMAIL_TYPOGRAPHY;
65
70
  readonly cta: typeof EMAIL_CTA;