@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.
Files changed (131) hide show
  1. package/README.md +16 -1
  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 +21 -5
  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 +373 -10
  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 +8 -2
  103. package/dist/styles/card-layout.css +161 -7
  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 +15 -4
  109. package/dist/styles/index.css +11 -3
  110. package/dist/styles/layout.css +367 -1
  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 +488 -40
  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 +48 -0
  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 +138 -0
  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 +16 -5
package/README.md CHANGED
@@ -144,12 +144,20 @@ per-layer files you need (mirrors the JS subpaths — the CSS tree-shakes too):
144
144
  @import "@godxjp/ui/styles/form-layout"; /* FormField */
145
145
  @import "@godxjp/ui/styles/dialog-layout"; /* Dialog */
146
146
  /* …only the layers you use. Layer files need `base` first (they use @layer/@apply). */
147
+ @import "@godxjp/ui/styles/fonts"; /* optional bundled faces — MUST come after `base` */
147
148
  ```
148
149
 
149
150
  Skip `@godxjp/ui/styles/fonts` when you manage fonts yourself (next/font, etc.)
150
151
  and set the font tokens instead (see below). A marketing site using ~10
151
152
  components typically drops component CSS from ~142K → ~26K gzip.
152
153
 
154
+ > **Import order rule — `fonts` after `base`.** `styles/base` pulls in the token
155
+ > layers, whose font-agnostic `:root { --font-sans-base: <system stack> }` sits at
156
+ > the same specificity (0,1,0) as the bundle's fill, so the later import wins.
157
+ > Import `styles/fonts` first and the bundled faces silently never apply while the
158
+ > ~800 KB of `@font-face` still ships. The all-in-one `@godxjp/ui/styles` entry
159
+ > already orders this correctly.
160
+
153
161
  ### Fonts — token-driven, per-language, no library hardcoding
154
162
 
155
163
  The base ships NO hardcoded brand face. Supply your own faces and set tokens —
@@ -165,7 +173,14 @@ one face everywhere, or per-language (no `[lang]` selectors to write):
165
173
  ```
166
174
 
167
175
  `styles/base.css` wires each `[lang]` to its slot (falling back to
168
- `--font-sans-base`); `styles/fonts` fills these slots for the bundled faces.
176
+ `--font-sans-base`); the opt-in `styles/fonts` overwrites `--font-sans-base` (and
177
+ `--font-sans-vi`) with the bundled DXS stack — **M PLUS 2** primary with **Noto
178
+ Sans JP** as the CJK fallback.
179
+
180
+ > **The bundled faces changed in v18.** v16 bundled Noto Sans JP + Montserrat;
181
+ > v18 bundles M PLUS 2 (primary, incl. Vietnamese coverage) + Noto Sans JP (CJK
182
+ > fallback). If your design spec named the v16 faces, set the tokens yourself
183
+ > instead of relying on the bundle.
169
184
 
170
185
  ```tsx
171
186
  import { AppProvider } from "@godxjp/ui/app"; // locale, tz, date/time format
@@ -1,14 +1,22 @@
1
1
  import type { PageDensityProp } from "../props/vocabulary/layout.prop.js";
2
2
  /** Light / dark spine — `data-theme` (alias of the legacy `.dark` class). */
3
3
  export type AppTheme = "light" | "dark";
4
- /** Primary-palette preset — `data-brand`. `null` = keep the app's own `--primary`. */
5
- export type AppBrand = "brand" | "crm" | "logistics" | "partner" | "slate";
4
+ /**
5
+ * Primary-palette preset — `data-brand`. `null` = keep the app's own `--primary`.
6
+ *
7
+ * `"dxs"` is the CANONICAL DXS preset: unlike the per-vertical tints it also binds the canonical
8
+ * hosted-identity surface contract (auth measure/density/insets + the brand-green identity mark),
9
+ * so every surface matches the canonical artboards with no consumer page CSS (gh#214). See
10
+ * `src/tokens/axes.css` for the bindings and `@godxjp/ui/theme/dxs.canonical.css` for the
11
+ * copy-free stylesheet entry point.
12
+ */
13
+ export type AppBrand = "brand" | "crm" | "logistics" | "partner" | "slate" | "dxs";
6
14
  /** Control / table / spacing density — `data-density` (same vocab as PageContainer). */
7
15
  export type AppDensity = PageDensityProp;
8
16
  /** Base type size — `data-font-size`; a preset rescales the whole golden scale. */
9
17
  export type AppFontSize = "sm" | "default" | "lg";
10
18
  export declare const APP_THEMES: readonly ["light", "dark"];
11
- export declare const APP_BRANDS: readonly ["brand", "crm", "logistics", "partner", "slate"];
19
+ export declare const APP_BRANDS: readonly ["brand", "crm", "logistics", "partner", "slate", "dxs"];
12
20
  export declare const APP_DENSITIES: readonly ["compact", "default", "comfortable"];
13
21
  export declare const APP_FONT_SIZES: readonly ["sm", "default", "lg"];
14
22
  /** The axes as held by AppProvider. `brand: null` opts out (app token wins).
@@ -4,7 +4,8 @@ const APP_BRANDS = [
4
4
  "crm",
5
5
  "logistics",
6
6
  "partner",
7
- "slate"
7
+ "slate",
8
+ "dxs"
8
9
  ];
9
10
  const APP_DENSITIES = [
10
11
  "compact",
@@ -30,15 +30,29 @@ type ChartFrameProps = {
30
30
  imgSummary: string;
31
31
  /** True when there is data to plot (otherwise children render an empty state). */
32
32
  hasData: boolean;
33
- height: number;
33
+ /**
34
+ * Canvas height in px. OPTIONAL: recharts needs a measured pixel box, but a
35
+ * CSS-drawn chart leaves it undefined and sizes its plot from a token tier
36
+ * instead (`size` → `data-size` → `--*-plot-height`), so no geometry is baked
37
+ * into an inline style.
38
+ */
39
+ height?: number;
40
+ /** Mirrored onto the figure as `data-size` so CSS can pick a token height tier. */
41
+ size?: "xs" | "sm" | "md" | "lg";
42
+ /**
43
+ * Content rendered BELOW the plot, outside the `role="img"` canvas — an activity
44
+ * footer, a delta, a link. Kept outside so interactive children stay reachable.
45
+ */
46
+ footer?: React.ReactNode;
34
47
  className?: string;
35
48
  id?: string;
36
49
  children: React.ReactNode;
50
+ ref?: React.Ref<HTMLElement>;
37
51
  };
38
52
  /**
39
- * The accessible figure wrapper. The recharts SVG passed as `children` is wrapped
53
+ * The accessible figure wrapper. The chart body passed as `children` is wrapped
40
54
  * in a `role="img"` element with a short `aria-label`; the full data is exposed as
41
55
  * a sibling visually-hidden list referenced by the figure's `aria-describedby`.
42
56
  */
43
- export declare function ChartFrame({ label, description, summaryRows, imgSummary, hasData, height, className, id, children, }: ChartFrameProps): React.JSX.Element;
57
+ export declare function ChartFrame({ label, description, summaryRows, imgSummary, hasData, height, size, footer, className, id, children, ref, }: ChartFrameProps): React.JSX.Element;
44
58
  export {};
@@ -34,9 +34,12 @@ function ChartFrame({
34
34
  imgSummary,
35
35
  hasData,
36
36
  height,
37
+ size,
38
+ footer,
37
39
  className,
38
40
  id,
39
- children
41
+ children,
42
+ ref
40
43
  }) {
41
44
  const reactId = React.useId();
42
45
  const titleId = `${id ?? reactId}-title`;
@@ -44,14 +47,17 @@ function ChartFrame({
44
47
  return /* @__PURE__ */ jsxs(
45
48
  "figure",
46
49
  {
50
+ ref,
47
51
  id,
48
52
  "data-slot": "chart",
53
+ "data-size": size,
49
54
  className: cn("ui-chart", className),
50
55
  "aria-labelledby": titleId,
51
56
  "aria-describedby": descId,
52
57
  children: [
53
58
  /* @__PURE__ */ jsx("figcaption", { id: titleId, className: "ui-chart-title", children: label }),
54
59
  /* @__PURE__ */ jsx("div", { className: "ui-chart-canvas", style: { height }, role: "img", "aria-label": imgSummary, children }),
60
+ footer ? /* @__PURE__ */ jsx("div", { className: "ui-chart-footer", children: footer }) : null,
55
61
  /* @__PURE__ */ jsxs("p", { id: descId, className: "sr-only", children: [
56
62
  description ? /* @__PURE__ */ jsxs(Fragment, { children: [
57
63
  description,
@@ -14,8 +14,29 @@ export declare function buildCartesianSummary(data: ChartDatum[], series: ChartS
14
14
  series: number;
15
15
  points: number;
16
16
  }) => string): ChartSummary;
17
+ /**
18
+ * Build the "category: value" rows for a SINGLE-series chart (pie slices, a
19
+ * compact bar trend…). `annotate` decorates the row of a highlighted datum —
20
+ * a text alternative for an emphasis that is otherwise colour-only (WCAG 1.4.1).
21
+ */
22
+ export declare function buildSeriesRows(data: ChartDatum[], categoryKey: string, valueKey: string, fmt: Formatter, annotate?: (index: number) => string | undefined): string[];
17
23
  /** Build the per-slice rows + a one-line summary for a pie chart. */
18
24
  export declare function buildPieSummary(data: ChartDatum[], dataKey: string, nameKey: string, fmt: Formatter, summaryLine: (counts: {
19
25
  slices: number;
20
26
  }) => string): ChartSummary;
27
+ /** Build the per-category rows + a one-line summary for a single-series trend. */
28
+ export declare function buildTrendSummary(data: ChartDatum[], categoryKey: string, valueKey: string, fmt: Formatter, summaryLine: (counts: {
29
+ points: number;
30
+ }) => string, annotate?: (index: number) => string | undefined): ChartSummary;
31
+ /**
32
+ * Normalize raw values to 0..1 bar ratios against the largest magnitude.
33
+ * Negative values plot at the baseline (a compact trend has no negative axis) but
34
+ * still read their true figure in the text alternative.
35
+ */
36
+ export declare function trendRatios(data: ChartDatum[], valueKey: string): number[];
37
+ /**
38
+ * Resolve `emphasizedIndex` against a row count. Negative counts from the end
39
+ * (`-1` = the latest datum). Out-of-range → no emphasis.
40
+ */
41
+ export declare function resolveEmphasizedIndex(index: number | undefined, length: number): number;
21
42
  export {};
@@ -11,13 +11,40 @@ function buildCartesianSummary(data, series, categoryKey, fmt, list, summaryLine
11
11
  });
12
12
  return { rows, img: summaryLine({ series: series.length, points: data.length }) };
13
13
  }
14
+ function buildSeriesRows(data, categoryKey, valueKey, fmt, annotate) {
15
+ return data.map((datum, index) => {
16
+ const row = `${String(datum[categoryKey] ?? "")}: ${fmt.format(numeric(datum[valueKey]))}`;
17
+ const note = annotate?.(index);
18
+ return note ? `${row} (${note})` : row;
19
+ });
20
+ }
14
21
  function buildPieSummary(data, dataKey, nameKey, fmt, summaryLine) {
15
- const rows = data.map(
16
- (datum) => `${String(datum[nameKey] ?? "")}: ${fmt.format(numeric(datum[dataKey]))}`
17
- );
18
- return { rows, img: summaryLine({ slices: data.length }) };
22
+ return {
23
+ rows: buildSeriesRows(data, nameKey, dataKey, fmt),
24
+ img: summaryLine({ slices: data.length })
25
+ };
26
+ }
27
+ function buildTrendSummary(data, categoryKey, valueKey, fmt, summaryLine, annotate) {
28
+ return {
29
+ rows: buildSeriesRows(data, categoryKey, valueKey, fmt, annotate),
30
+ img: summaryLine({ points: data.length })
31
+ };
32
+ }
33
+ function trendRatios(data, valueKey) {
34
+ const values = data.map((datum) => Math.max(0, numeric(datum[valueKey])));
35
+ const max = values.reduce((acc, value) => value > acc ? value : acc, 0);
36
+ return values.map((value) => max > 0 ? value / max : 0);
37
+ }
38
+ function resolveEmphasizedIndex(index, length) {
39
+ if (index === void 0 || !Number.isInteger(index) || length === 0) return -1;
40
+ const resolved = index < 0 ? length + index : index;
41
+ return resolved >= 0 && resolved < length ? resolved : -1;
19
42
  }
20
43
  export {
21
44
  buildCartesianSummary,
22
- buildPieSummary
45
+ buildPieSummary,
46
+ buildSeriesRows,
47
+ buildTrendSummary,
48
+ resolveEmphasizedIndex,
49
+ trendRatios
23
50
  };
@@ -0,0 +1,29 @@
1
+ import * as React from "react";
2
+ import type { CompactBarTrendProp } from "../../props/components/charts.prop.js";
3
+ export type { CompactBarTrendProp, CompactBarTrendProp as CompactBarTrendProps, } from "../../props/components/charts.prop.js";
4
+ /**
5
+ * CompactBarTrend — a **dependency-free** compact vertical bar trend for dashboard
6
+ * summary cards (a seven-day "new organizations" strip, a weekly activity pulse…).
7
+ *
8
+ * Unlike `BarChart` it does NOT need the `recharts` optional peer: the marks are
9
+ * token-sized CSS blocks, so a consumer that may not add dependencies still gets a
10
+ * real framework chart instead of a page-local grid with inline heights.
11
+ *
12
+ * - **Geometry is entirely token-owned** — `--chart-trend-plot-height-{xs,sm,md,lg}`,
13
+ * `--chart-trend-bar-{gap,radius,max-width,min-height}`. The only inline value is
14
+ * the datum itself (`--chart-trend-bar-value`, a 0..1 ratio of the largest bar).
15
+ * - **Colour is token-owned** — muted marks read `--chart-trend-bar-background`
16
+ * (default `hsl(var(--muted-foreground) / .75)`), the emphasized mark reads
17
+ * `--chart-trend-bar-emphasis-background` (default `hsl(var(--primary))`).
18
+ * - **a11y** — reuses `ChartFrame`: a `<figure>` with a visible `<figcaption>`, a
19
+ * `role="img"` plot named by a localized one-line summary, and a visually-hidden
20
+ * per-category value list referenced through `aria-describedby` (WCAG 1.1.1). The
21
+ * emphasized bar is ALSO named in that list, so the highlight is never
22
+ * colour-only (WCAG 1.4.1). The graphic holds no focusable elements — anything
23
+ * interactive belongs in `footer`, which renders outside the graphic.
24
+ * - **i18n** — values are formatted with the active locale's `Intl.NumberFormat`;
25
+ * every generated string comes from `t()`.
26
+ *
27
+ * Import from the tree-shaken `@godxjp/ui/charts` entry.
28
+ */
29
+ export declare function CompactBarTrend({ data, categoryKey, valueKey, label, description, size, emphasizedIndex, showCategoryLabels, numberFormat, footer, emptyMessage, className, id, ref, }: CompactBarTrendProp): React.JSX.Element;
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { EmptyState } from "../data-display/empty-state.js";
6
+ import { useTranslation } from "../../i18n/use-translation.js";
7
+ import { ChartFrame, useChartNumberFormat } from "./chart-frame.js";
8
+ import { buildTrendSummary, resolveEmphasizedIndex, trendRatios } from "./chart-summary.js";
9
+ function CompactBarTrend({
10
+ data,
11
+ categoryKey,
12
+ valueKey,
13
+ label,
14
+ description,
15
+ size = "xs",
16
+ emphasizedIndex,
17
+ showCategoryLabels = true,
18
+ numberFormat,
19
+ footer,
20
+ emptyMessage,
21
+ className,
22
+ id,
23
+ ref
24
+ }) {
25
+ const { t } = useTranslation();
26
+ const fmt = useChartNumberFormat(numberFormat);
27
+ const hasData = data.length > 0;
28
+ const emphasized = resolveEmphasizedIndex(emphasizedIndex, data.length);
29
+ const ratios = React.useMemo(() => trendRatios(data, valueKey), [data, valueKey]);
30
+ const summary = buildTrendSummary(
31
+ data,
32
+ categoryKey,
33
+ valueKey,
34
+ fmt,
35
+ (counts) => t("chart.summaryTrend", { points: fmt.format(counts.points) }),
36
+ (index) => index === emphasized ? t("chart.trendCurrent") : void 0
37
+ );
38
+ return /* @__PURE__ */ jsx(
39
+ ChartFrame,
40
+ {
41
+ ref,
42
+ label,
43
+ description,
44
+ summaryRows: summary.rows,
45
+ imgSummary: summary.img,
46
+ hasData,
47
+ size,
48
+ footer,
49
+ className: cn("ui-chart-trend", className),
50
+ id,
51
+ children: hasData ? /* @__PURE__ */ jsxs("div", { className: "ui-chart-trend-body", children: [
52
+ /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-plot", "aria-hidden": "true", children: data.map((datum, index) => /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-column", children: /* @__PURE__ */ jsx(
53
+ "div",
54
+ {
55
+ className: "ui-chart-trend-bar",
56
+ "data-emphasized": index === emphasized ? "true" : void 0,
57
+ style: { "--chart-trend-bar-value": ratios[index] }
58
+ }
59
+ ) }, `${String(datum[categoryKey])}-${index}`)) }),
60
+ showCategoryLabels ? /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-ticks", "aria-hidden": "true", children: data.map((datum, index) => /* @__PURE__ */ jsx(
61
+ "span",
62
+ {
63
+ className: "ui-chart-trend-tick",
64
+ children: String(datum[categoryKey] ?? "")
65
+ },
66
+ `${String(datum[categoryKey])}-${index}`
67
+ )) }) : null
68
+ ] }) : /* @__PURE__ */ jsx(EmptyState, { title: emptyMessage ?? t("chart.empty") })
69
+ }
70
+ );
71
+ }
72
+ export {
73
+ CompactBarTrend
74
+ };
@@ -8,6 +8,9 @@ export { LineChart } from "./line-chart.js";
8
8
  export type { LineChartProp, LineChartProps } from "./line-chart.js";
9
9
  export { BarChart } from "./bar-chart.js";
10
10
  export type { BarChartProp, BarChartProps } from "./bar-chart.js";
11
+ /** Dependency-free (no recharts) compact bar trend for dashboard summary cards. */
12
+ export { CompactBarTrend } from "./compact-bar-trend.js";
13
+ export type { CompactBarTrendProp, CompactBarTrendProps } from "./compact-bar-trend.js";
11
14
  export { AreaChart } from "./area-chart.js";
12
15
  export type { AreaChartProp, AreaChartProps } from "./area-chart.js";
13
16
  export { PieChart } from "./pie-chart.js";
@@ -1,5 +1,6 @@
1
1
  import { LineChart } from "./line-chart.js";
2
2
  import { BarChart } from "./bar-chart.js";
3
+ import { CompactBarTrend } from "./compact-bar-trend.js";
3
4
  import { AreaChart } from "./area-chart.js";
4
5
  import { PieChart } from "./pie-chart.js";
5
6
  import { CHART_COLORS } from "./chart-frame.js";
@@ -7,6 +8,7 @@ export {
7
8
  AreaChart,
8
9
  BarChart,
9
10
  CHART_COLORS,
11
+ CompactBarTrend,
10
12
  LineChart,
11
13
  PieChart
12
14
  };
@@ -24,4 +24,12 @@ export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
24
24
  children?: React.ReactNode;
25
25
  }
26
26
  export declare function Badge({ className, variant, shape, tone, icon, status, children, ...props }: BadgeProps): React.JSX.Element;
27
+ /**
28
+ * Status-aware badge with the shared domain-status-to-tone mapping.
29
+ *
30
+ * `Badge` remains the general-purpose primitive. Prefer `StatusBadge` when the
31
+ * `status` prop drives presentation so status colours remain consistent across
32
+ * products.
33
+ */
34
+ export declare const StatusBadge: typeof Badge;
27
35
  export {};
@@ -29,6 +29,10 @@ const STATUS_MAP = {
29
29
  done: { tone: "success", icon: CheckCircle2 },
30
30
  permanent: { tone: "success", icon: CheckCircle2 },
31
31
  succeeded: { tone: "success", icon: CheckCircle2 },
32
+ trialing: { tone: "info", icon: Clock },
33
+ past_due: { tone: "warning", icon: AlertCircle },
34
+ incomplete: { tone: "default", icon: Circle },
35
+ canceled: { tone: "destructive", icon: XCircle },
32
36
  draft: { tone: "neutral", icon: Circle },
33
37
  pending: { tone: "warning", icon: Clock },
34
38
  scheduled: { tone: "info", icon: Clock },
@@ -109,6 +113,8 @@ function Badge({
109
113
  }
110
114
  );
111
115
  }
116
+ const StatusBadge = Badge;
112
117
  export {
113
- Badge
118
+ Badge,
119
+ StatusBadge
114
120
  };
@@ -52,6 +52,21 @@ interface DataTableProps<T> {
52
52
  loading?: boolean;
53
53
  /** Custom empty content when `data` is empty; defaults to a built-in EmptyState. */
54
54
  empty?: React.ReactNode;
55
+ /**
56
+ * Failure state (#216). `true` renders the built-in localized error message; any other node
57
+ * REPLACES it (e.g. an `Alert` carrying an error code + request id). Pass `undefined`/`false`
58
+ * when the read succeeded. Never pass a raw `Error` object — it is not renderable.
59
+ * Announced via `role="alert"`; precedence is loading → denied → error → empty → rows.
60
+ */
61
+ error?: React.ReactNode;
62
+ /**
63
+ * Permission-denied state (#216) — the read was refused, not failed. `true` renders the
64
+ * built-in localized "no access" message; any other node replaces it. Distinct from `error`
65
+ * so a 403 never offers a pointless retry.
66
+ */
67
+ denied?: React.ReactNode;
68
+ /** Retry handler for the built-in `error` state; omit to hide the retry action. */
69
+ onRetry?: () => void;
55
70
  /** Zebra-stripe the body rows (even rows get a subtle fill). */
56
71
  striped?: boolean;
57
72
  /** Highlight a row on hover even when it is not clickable (no `onRowClick`). */
@@ -67,7 +82,7 @@ interface DataTableProps<T> {
67
82
  className?: string;
68
83
  children?: React.ReactNode;
69
84
  }
70
- export declare function DataTable<T>({ data, columns, getRowId, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: controlledPagination, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, striped, hoverable, stickyHeader, rowClassName, className, children, }: DataTableProps<T>): React.JSX.Element;
85
+ export declare function DataTable<T>({ data, columns, getRowId, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: controlledPagination, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, rowClassName, className, children, }: DataTableProps<T>): React.JSX.Element;
71
86
  export declare namespace DataTable {
72
87
  var Toolbar: ({ children, className, }: {
73
88
  children?: React.ReactNode;
@@ -10,6 +10,7 @@ import {
10
10
  useReactTable
11
11
  } from "@tanstack/react-table";
12
12
  import {
13
+ AlertCircle,
13
14
  ArrowDown,
14
15
  ArrowUp,
15
16
  ChevronLeft,
@@ -18,6 +19,8 @@ import {
18
19
  Layers,
19
20
  Layers2,
20
21
  MoreHorizontal,
22
+ RefreshCw,
23
+ ShieldAlert,
21
24
  SlidersHorizontal
22
25
  } from "lucide-react";
23
26
  import { useTranslation } from "../../i18n/use-translation.js";
@@ -113,6 +116,9 @@ function DataTable({
113
116
  manualPagination = false,
114
117
  loading = false,
115
118
  empty,
119
+ error,
120
+ denied,
121
+ onRetry,
116
122
  striped = false,
117
123
  hoverable = false,
118
124
  stickyHeader = true,
@@ -207,6 +213,9 @@ function DataTable({
207
213
  onRowClick,
208
214
  loading,
209
215
  empty,
216
+ error,
217
+ denied,
218
+ onRetry,
210
219
  striped,
211
220
  hoverable,
212
221
  stickyHeader,
@@ -378,12 +387,17 @@ DataTable.Content = function DataTableContent() {
378
387
  onRowClick,
379
388
  loading,
380
389
  empty,
390
+ error,
391
+ denied,
392
+ onRetry,
381
393
  striped,
382
394
  hoverable,
383
395
  stickyHeader,
384
396
  rowClassName
385
397
  } = useDataTableContext();
386
398
  const { t } = useTranslation();
399
+ const isDenied = denied != null && denied !== false;
400
+ const isError = !isDenied && error != null && error !== false;
387
401
  const rowPadding = tableRowHeightClass;
388
402
  const cellPadding = tableCellPaddingClass;
389
403
  const visibleColumns = table.getVisibleLeafColumns().map((c) => c.columnDef.meta?.lean).filter((c) => !!c);
@@ -503,6 +517,48 @@ DataTable.Content = function DataTableContent() {
503
517
  col.key
504
518
  ))
505
519
  ] }, `skeleton-${i}`))
520
+ ) : isDenied ? (
521
+ // DENIED (#216): the read was REFUSED, not failed — no retry is offered, because
522
+ // repeating a 403 cannot succeed. `warning` (not destructive) keeps it a policy
523
+ // statement rather than a system fault. Announced politely: a permission boundary
524
+ // is expected information, not an interruption.
525
+ /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
526
+ TableCell,
527
+ {
528
+ colSpan: emptyColSpan,
529
+ className: "ui-data-table-empty",
530
+ "aria-live": "polite",
531
+ children: denied === true ? /* @__PURE__ */ jsx(
532
+ EmptyState,
533
+ {
534
+ icon: ShieldAlert,
535
+ tone: "warning",
536
+ title: t("dataTable.denied"),
537
+ description: t("dataTable.deniedDescription"),
538
+ titleAs: "p"
539
+ }
540
+ ) : denied
541
+ }
542
+ ) })
543
+ ) : isError ? (
544
+ // ERROR (#216): a failed read. Announced assertively (`role="alert"`) because the
545
+ // user's request did not complete — unlike empty/denied, which are states of the data
546
+ // itself. The role sits on an inner wrapper, never on the <td>: overriding a cell's
547
+ // role would strip it out of the table's grid semantics for AT.
548
+ /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(TableCell, { colSpan: emptyColSpan, className: "ui-data-table-empty", children: /* @__PURE__ */ jsx("div", { role: "alert", children: error === true ? /* @__PURE__ */ jsx(
549
+ EmptyState,
550
+ {
551
+ icon: AlertCircle,
552
+ tone: "destructive",
553
+ title: t("dataTable.error"),
554
+ description: t("dataTable.errorDescription"),
555
+ titleAs: "p",
556
+ action: onRetry ? /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
557
+ /* @__PURE__ */ jsx(RefreshCw, { "aria-hidden": "true" }),
558
+ t("common.retry")
559
+ ] }) : void 0
560
+ }
561
+ ) : error }) }) })
506
562
  ) : rowCount === 0 ? /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
507
563
  TableCell,
508
564
  {
@@ -1,4 +1,4 @@
1
- export { Badge } from "./badge.js";
1
+ export { Badge, StatusBadge } from "./badge.js";
2
2
  export type { BadgeProps } from "./badge.js";
3
3
  export { ListRow } from "./list-row.js";
4
4
  export type { ListRowProps } from "./list-row.js";
@@ -10,6 +10,8 @@ export type { QrCodeProp } from "../../props/components/data-display.prop.js";
10
10
  export { Avatar, AvatarImage, AvatarFallback } from "./avatar.js";
11
11
  export { Card, CardBar, CardContent, CardCover, CardDescription, CardFooter, CardHeader, CardTitle, CardAction, StatCard, } from "./card.js";
12
12
  export type { StatCardProps, CardBarProps } from "./card.js";
13
+ export { ServiceCatalogCta, ServiceLauncherCard, ServiceLauncherCardSkeleton, } from "./service-launcher-card.js";
14
+ export type { ServiceCatalogCtaProps, ServiceLauncherCardProps, ServiceLauncherCardSkeletonProps, ServiceLauncherStatusTone, } from "./service-launcher-card.js";
13
15
  export { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js";
14
16
  export { Descriptions } from "./descriptions.js";
15
17
  export { DataTable, flexRender } from "./data-table.js";
@@ -1,4 +1,4 @@
1
- import { Badge } from "./badge.js";
1
+ import { Badge, StatusBadge } from "./badge.js";
2
2
  import { ListRow } from "./list-row.js";
3
3
  import { CredentialReveal } from "./credential-reveal.js";
4
4
  import { QrCode } from "./qr-code.js";
@@ -15,6 +15,11 @@ import {
15
15
  CardAction,
16
16
  StatCard
17
17
  } from "./card.js";
18
+ import {
19
+ ServiceCatalogCta,
20
+ ServiceLauncherCard,
21
+ ServiceLauncherCardSkeleton
22
+ } from "./service-launcher-card.js";
18
23
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js";
19
24
  import { Descriptions } from "./descriptions.js";
20
25
  import { DataTable, flexRender } from "./data-table.js";
@@ -90,7 +95,11 @@ export {
90
95
  QrCode,
91
96
  ScrollArea,
92
97
  ScrollBar,
98
+ ServiceCatalogCta,
99
+ ServiceLauncherCard,
100
+ ServiceLauncherCardSkeleton,
93
101
  StatCard,
102
+ StatusBadge,
94
103
  Table,
95
104
  TableBody,
96
105
  TableCell,
@@ -1,24 +1,42 @@
1
1
  import * as React from "react";
2
2
  /**
3
- * ListRow — a single-line entity row (leading · title/description · trailing) for SHORT lists
4
- * inside a Card: active sessions, API tokens, linked accounts, passkeys, MFA factors, invitations.
3
+ * ListRow — a single-line entity row (indicator · leading · title/description · trailing) for
4
+ * SHORT lists inside a Card: active sessions, API tokens, linked accounts, passkeys, MFA factors,
5
+ * invitations, notifications.
5
6
  * Use it INSTEAD of a hand-rolled `<div className="flex items-center justify-between border-b …">`.
6
7
  * DataTable is too heavy for a 2–4 item list, and nesting a Card per row would be card-in-card —
7
8
  * ListRow is the in-between surface. Drop rows into a `<Card><CardContent flush>…` and they stack
8
9
  * with a quiet divider; the trailing slot holds the row's action (Button / DropdownMenu / Switch).
10
+ *
11
+ * Overflow (#224): the content column always shrinks below its intrinsic width
12
+ * (`min-inline-size: min(--list-row-body-min-width, 100%)`) and the trailing actions wrap onto
13
+ * their own line at narrow container widths — a long title plus two Buttons never widens the page
14
+ * root. `overflow` picks how the long content resolves: `truncate` (default, one line + ellipsis)
15
+ * or `wrap` (multi-line, `overflow-wrap: anywhere`).
9
16
  */
10
17
  export interface ListRowProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
11
18
  /** Render element — `div` (default) or `li` when the parent is a `<ul>`/`<ol>`. */
12
19
  as?: "div" | "li";
13
20
  /** Leading slot — a decorative icon or an Avatar. Mark a purely decorative icon `aria-hidden`. */
14
21
  leading?: React.ReactNode;
15
- /** Primary line — rendered in medium weight; truncates to a single line. */
22
+ /** Primary line — rendered in medium weight. */
16
23
  title: React.ReactNode;
17
- /** Secondary line under the title (muted, xs); truncates to a single line. */
24
+ /** Secondary line under the title (muted, xs). */
18
25
  description?: React.ReactNode;
19
26
  /** Trailing slot — the row action(s): a Button / DropdownMenu trigger, a Badge, or a Switch. */
20
27
  trailing?: React.ReactNode;
21
28
  /** Cross-axis alignment of the columns — `center` (default) or `start` for multi-line content. */
22
29
  align?: "center" | "start";
30
+ /**
31
+ * How title/description resolve content too long for the row — `truncate` (default: one line
32
+ * with an ellipsis) or `wrap` (multi-line; long unbroken tokens break so the row never widens).
33
+ */
34
+ overflow?: "truncate" | "wrap";
35
+ /**
36
+ * Read/unread state — renders the indicator dot (with localized `sr-only` text, never colour
37
+ * alone) and the tokenized unread surface. OMIT the prop entirely for rows that have no read
38
+ * state; pass `false` for a read row so its title stays on the same optical axis as unread ones.
39
+ */
40
+ unread?: boolean;
23
41
  }
24
42
  export declare const ListRow: React.ForwardRefExoticComponent<ListRowProps & React.RefAttributes<HTMLDivElement>>;