@godxjp/ui 18.4.0 → 18.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/app/theme-axes.d.ts +11 -3
- package/dist/app/theme-axes.js +2 -1
- package/dist/components/charts/chart-frame.d.ts +17 -3
- package/dist/components/charts/chart-frame.js +7 -1
- package/dist/components/charts/chart-summary.d.ts +21 -0
- package/dist/components/charts/chart-summary.js +32 -5
- package/dist/components/charts/compact-bar-trend.d.ts +29 -0
- package/dist/components/charts/compact-bar-trend.js +74 -0
- package/dist/components/charts/index.d.ts +3 -0
- package/dist/components/charts/index.js +2 -0
- package/dist/components/data-display/badge.d.ts +8 -0
- package/dist/components/data-display/badge.js +7 -1
- package/dist/components/data-display/data-table.d.ts +16 -1
- package/dist/components/data-display/data-table.js +56 -0
- package/dist/components/data-display/index.d.ts +3 -1
- package/dist/components/data-display/index.js +10 -1
- package/dist/components/data-display/list-row.d.ts +22 -4
- package/dist/components/data-display/list-row.js +21 -3
- package/dist/components/data-display/service-launcher-card.d.ts +40 -0
- package/dist/components/data-display/service-launcher-card.js +97 -0
- package/dist/components/data-entry/command-palette.d.ts +37 -0
- package/dist/components/data-entry/command-palette.js +125 -0
- package/dist/components/data-entry/index.d.ts +2 -0
- package/dist/components/data-entry/index.js +2 -0
- package/dist/components/feedback/dialog.d.ts +2 -0
- package/dist/components/feedback/dialog.js +42 -33
- package/dist/components/feedback/index.d.ts +4 -1
- package/dist/components/feedback/index.js +6 -2
- package/dist/components/feedback/sheet.d.ts +26 -4
- package/dist/components/feedback/sheet.js +54 -7
- package/dist/components/feedback/two-factor-setup.d.ts +31 -0
- package/dist/components/feedback/two-factor-setup.js +97 -0
- package/dist/components/general/index.d.ts +1 -1
- package/dist/components/general/logo.d.ts +42 -8
- package/dist/components/general/logo.js +67 -14
- package/dist/components/layout/app-shell.js +21 -5
- package/dist/components/layout/auth-divider.d.ts +7 -0
- package/dist/components/layout/auth-divider.js +21 -0
- package/dist/components/layout/auth-footer.d.ts +5 -0
- package/dist/components/layout/auth-footer.js +24 -0
- package/dist/components/layout/auth-identity.d.ts +5 -0
- package/dist/components/layout/auth-identity.js +17 -0
- package/dist/components/layout/auth-shell.d.ts +8 -1
- package/dist/components/layout/auth-shell.js +25 -6
- package/dist/components/layout/auth-stack.d.ts +4 -0
- package/dist/components/layout/auth-stack.js +8 -0
- package/dist/components/layout/centered-shell.d.ts +5 -2
- package/dist/components/layout/centered-shell.js +2 -1
- package/dist/components/layout/index.d.ts +18 -3
- package/dist/components/layout/index.js +17 -2
- package/dist/components/layout/legal-document-shell.d.ts +34 -0
- package/dist/components/layout/legal-document-shell.js +186 -0
- package/dist/components/layout/master-detail.d.ts +24 -0
- package/dist/components/layout/master-detail.js +48 -0
- package/dist/components/layout/org-switcher.d.ts +9 -0
- package/dist/components/layout/org-switcher.js +218 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +3 -2
- package/dist/components/layout/sidebar-link.d.ts +43 -0
- package/dist/components/layout/sidebar-link.js +13 -0
- package/dist/components/layout/sidebar.d.ts +42 -5
- package/dist/components/layout/sidebar.js +170 -85
- package/dist/components/navigation/app-setting-picker.js +17 -6
- package/dist/components/navigation/filter-bar.d.ts +12 -3
- package/dist/components/navigation/filter-bar.js +8 -2
- package/dist/components/navigation/index.d.ts +2 -2
- package/dist/components/navigation/index.js +3 -1
- package/dist/components/navigation/tabs-scroll.d.ts +70 -0
- package/dist/components/navigation/tabs-scroll.js +76 -0
- package/dist/components/navigation/tabs.js +35 -20
- package/dist/email/brand-mark.d.ts +92 -0
- package/dist/email/brand-mark.js +83 -0
- package/dist/email/color.d.ts +52 -0
- package/dist/email/color.js +37 -0
- package/dist/email/geometry.d.ts +112 -0
- package/dist/email/geometry.js +89 -0
- package/dist/email/index.d.ts +78 -0
- package/dist/email/index.js +76 -0
- package/dist/email/inline-style.d.ts +25 -0
- package/dist/email/inline-style.js +21 -0
- package/dist/email/tokens.generated.d.ts +147 -0
- package/dist/email/tokens.generated.js +142 -0
- package/dist/i18n/messages/en.json +21 -2
- package/dist/i18n/messages/ja.json +21 -2
- package/dist/i18n/messages/vi.json +21 -2
- package/dist/inertia/index.d.ts +50 -0
- package/dist/inertia/index.js +5 -0
- package/dist/props/components/app.prop.d.ts +11 -1
- package/dist/props/components/charts.prop.d.ts +40 -1
- package/dist/props/components/data-display.prop.d.ts +13 -1
- package/dist/props/components/feedback.prop.d.ts +10 -0
- package/dist/props/components/index.d.ts +2 -2
- package/dist/props/components/layout.prop.d.ts +373 -10
- package/dist/props/components/layout.prop.js +1 -0
- package/dist/props/components/navigation.prop.d.ts +19 -1
- package/dist/props/registry.d.ts +291 -7
- package/dist/props/registry.js +341 -7
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/props/vocabulary/interaction.prop.d.ts +1 -1
- package/dist/props/vocabulary/layout.prop.d.ts +18 -0
- package/dist/styles/base.css +8 -2
- package/dist/styles/card-layout.css +161 -7
- package/dist/styles/chart-layout.css +97 -0
- package/dist/styles/control.css +48 -2
- package/dist/styles/data-display-layout.css +52 -6
- package/dist/styles/dialog-layout.css +105 -4
- package/dist/styles/fonts.css +15 -4
- package/dist/styles/index.css +11 -3
- package/dist/styles/layout.css +367 -1
- package/dist/styles/logo-layout.css +62 -0
- package/dist/styles/navigation-layout.css +43 -0
- package/dist/styles/shell-layout.css +488 -40
- package/dist/theme/dxs.canonical.css +76 -0
- package/dist/tokens/axes.css +39 -0
- package/dist/tokens/base.css +5 -0
- package/dist/tokens/components/card.css +48 -0
- package/dist/tokens/components/chart.css +47 -0
- package/dist/tokens/components/control.css +16 -0
- package/dist/tokens/components/email.css +66 -0
- package/dist/tokens/components/feedback.css +17 -0
- package/dist/tokens/components/legal-document.css +67 -0
- package/dist/tokens/components/list-row.css +23 -2
- package/dist/tokens/components/logo.css +32 -0
- package/dist/tokens/components/navigation.css +13 -0
- package/dist/tokens/components/sheet.css +19 -0
- package/dist/tokens/components/shell.css +138 -0
- package/dist/tokens/components/sidebar.css +33 -0
- package/dist/tokens/foundation.css +5 -2
- package/dist/tokens/semantic/layout.css +44 -2
- package/package.json +16 -5
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`
|
|
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
|
package/dist/app/theme-axes.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
5
|
-
|
|
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).
|
package/dist/app/theme-axes.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
16
|
-
(
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
4
|
-
* inside a Card: active sessions, API tokens, linked accounts, passkeys, MFA factors,
|
|
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
|
|
22
|
+
/** Primary line — rendered in medium weight. */
|
|
16
23
|
title: React.ReactNode;
|
|
17
|
-
/** Secondary line under the title (muted, xs)
|
|
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>>;
|