@godxjp/ui 28.13.0 → 30.0.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/agent/START-HERE.md +29 -10
- package/agent/components/Anchor.json +6 -1
- package/agent/components/AppShell.json +1 -1
- package/agent/components/AreaChart.json +19 -1
- package/agent/components/BarChart.json +11 -1
- package/agent/components/Cascader.json +1 -1
- package/agent/components/CompactBarTrend.json +1 -1
- package/agent/components/DataState.json +1 -1
- package/agent/components/DataTable.json +4 -4
- package/agent/components/FormField.json +1 -1
- package/agent/components/InfiniteQueryState.json +1 -1
- package/agent/components/Input.json +1 -1
- package/agent/components/LineChart.json +20 -2
- package/agent/components/ListRow.json +1 -1
- package/agent/components/Masonry.json +1 -1
- package/agent/components/MasterDetail.json +1 -1
- package/agent/components/PasswordStrength.json +1 -1
- package/agent/components/PermissionMatrix.json +1 -1
- package/agent/components/Select.json +1 -0
- package/agent/components/Sidebar.json +1 -1
- package/agent/components/Table.json +8 -3
- package/agent/components/ThemeScope.json +49 -0
- package/agent/components/Topbar.json +1 -0
- package/agent/components/TopbarItem.json +2 -1
- package/agent/components/Transfer.json +1 -1
- package/agent/components/TreeSelect.json +1 -1
- package/agent/components/UploadCropDialog.json +1 -1
- package/agent/components/formatDate.json +1 -1
- package/agent/components-index.json +5 -0
- package/agent/components.json +138 -30
- package/agent/index.json +19 -9
- package/agent/llms.txt +10 -10
- package/agent/patterns/tenant-brand-color.json +28 -0
- package/agent/patterns-index.json +27 -0
- package/agent/patterns.json +28 -0
- package/agent/rules.json +15 -0
- package/agent/tokens.json +5055 -1060
- package/dist/app/index.d.ts +3 -0
- package/dist/app/index.js +3 -0
- package/dist/app/tenant-theme.d.ts +80 -0
- package/dist/app/tenant-theme.js +154 -0
- package/dist/app/theme-axes.d.ts +14 -1
- package/dist/app/theme-axes.js +24 -31
- package/dist/components/charts/chart-cartesian.d.ts +5 -1
- package/dist/components/charts/chart-cartesian.js +15 -8
- package/dist/components/data-display/badge.d.ts +1 -1
- package/dist/components/data-display/badge.js +21 -3
- package/dist/components/data-display/carousel.js +4 -4
- package/dist/components/data-display/data-table.js +13 -2
- package/dist/components/data-display/permission-matrix.js +1 -1
- package/dist/components/data-display/table.d.ts +11 -2
- package/dist/components/data-display/table.js +19 -3
- package/dist/components/data-entry/control-appearance.d.ts +12 -6
- package/dist/components/data-entry/control-appearance.js +1 -1
- package/dist/components/data-entry/select.js +4 -3
- package/dist/components/feedback/dialog.js +6 -3
- package/dist/components/feedback/overlay-header-tone.d.ts +7 -0
- package/dist/components/feedback/overlay-header-tone.js +4 -4
- package/dist/components/feedback/sheet.d.ts +1 -1
- package/dist/components/feedback/sheet.js +6 -9
- package/dist/components/feedback/sonner.js +16 -3
- package/dist/components/general/button.js +22 -5
- package/dist/components/layout/affix.js +15 -1
- package/dist/components/layout/sidebar.js +7 -1
- package/dist/components/navigation/anchor.d.ts +1 -1
- package/dist/components/navigation/anchor.js +5 -4
- package/dist/components/navigation/app-setting-picker.js +1 -1
- package/dist/components/navigation/pagination.js +1 -1
- package/dist/components/navigation/tabs.js +15 -2
- package/dist/components/query/infinite-query-state.d.ts +22 -6
- package/dist/contracts/measurement.json +1 -1
- package/dist/lib/control-styles.d.ts +31 -11
- package/dist/lib/control-styles.js +6 -6
- package/dist/lib/overlay-portal.d.ts +20 -0
- package/dist/lib/overlay-portal.js +93 -0
- package/dist/props/components/app.prop.d.ts +12 -0
- package/dist/props/components/charts.prop.d.ts +30 -0
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/navigation.prop.d.ts +21 -2
- package/dist/props/components/query.prop.d.ts +36 -2
- package/dist/props/registry.d.ts +46 -1
- package/dist/props/registry.js +38 -3
- package/dist/styles/alert-layout.css +39 -19
- package/dist/styles/badge-layout.css +11 -7
- package/dist/styles/base.css +14 -5
- package/dist/styles/card-layout.css +29 -16
- package/dist/styles/chart-layout.css +27 -5
- package/dist/styles/control.css +225 -103
- package/dist/styles/data-display-layout.css +178 -76
- package/dist/styles/data-entry-layout.css +35 -99
- package/dist/styles/dialog-layout.css +58 -28
- package/dist/styles/float-button-layout.css +5 -5
- package/dist/styles/focus-ring.css +11 -7
- package/dist/styles/layout.css +53 -25
- package/dist/styles/logo-layout.css +3 -3
- package/dist/styles/motion.css +2 -2
- package/dist/styles/navigation-layout.css +117 -44
- package/dist/styles/shell-layout.css +90 -124
- package/dist/styles/table-layout.css +66 -24
- package/dist/styles/text-layout.css +13 -4
- package/dist/styles/toggle.css +9 -3
- package/dist/tokens/components/actions.css +1 -1
- package/dist/tokens/components/attachments.css +4 -4
- package/dist/tokens/components/badge.css +4 -4
- package/dist/tokens/components/banner.css +1 -1
- package/dist/tokens/components/callout.css +1 -1
- package/dist/tokens/components/card.css +13 -8
- package/dist/tokens/components/chart.css +11 -2
- package/dist/tokens/components/chat-bubble.css +2 -2
- package/dist/tokens/components/control.css +41 -22
- package/dist/tokens/components/conversations.css +2 -1
- package/dist/tokens/components/data-display.css +23 -18
- package/dist/tokens/components/data-entry.css +1 -1
- package/dist/tokens/components/descriptions.css +2 -2
- package/dist/tokens/components/draggable-panel.css +2 -2
- package/dist/tokens/components/feedback.css +29 -9
- package/dist/tokens/components/float-button.css +1 -1
- package/dist/tokens/components/legal-document.css +2 -2
- package/dist/tokens/components/logo.css +2 -2
- package/dist/tokens/components/mega-menu.css +6 -4
- package/dist/tokens/components/navigation.css +27 -13
- package/dist/tokens/components/segmented.css +11 -6
- package/dist/tokens/components/separator.css +1 -1
- package/dist/tokens/components/shell.css +24 -10
- package/dist/tokens/components/table.css +13 -5
- package/dist/tokens/components/thought-chain.css +2 -2
- package/dist/tokens/components/toggle.css +3 -1
- package/dist/tokens/components/tree.css +3 -1
- package/dist/tokens/components/upload.css +8 -8
- package/dist/tokens/components/welcome.css +1 -1
- package/dist/tokens/foundation.css +30 -3
- package/docs/COMPOSITION-VS-COMPONENT.md +31 -0
- package/docs/CUSTOMER-THEMING.md +637 -1
- package/docs/FRAME-COVERAGE-REPORT.md +3 -2
- package/docs/GLASSMORPHISM-STANDARD.md +196 -0
- package/docs/THEME-API-COVERAGE.md +538 -0
- package/docs/TOKEN-RESOLUTION.md +195 -0
- package/docs/TOKENS.md +63 -24
- package/docs/asset-modules.d.ts +7 -0
- package/docs/data-display/charts.tsx +80 -0
- package/docs/data-display/data-table/index.tsx +30 -0
- package/docs/data-display/popover.tsx +1 -1
- package/docs/data-display/table.tsx +52 -0
- package/docs/feedback/sheet.tsx +10 -10
- package/docs/foundation/density.tsx +4 -4
- package/docs/i18n/messages/en.json +506 -0
- package/docs/i18n/messages/ja.json +506 -0
- package/docs/i18n/messages/vi.json +506 -0
- package/docs/layout/account-chip.tsx +2 -2
- package/docs/layout/responsive-grid.tsx +1 -1
- package/docs/navigation/toolbar.tsx +20 -12
- package/docs/providers/theme-scope.tsx +186 -0
- package/docs/showcase/caimono-price-comparison.tsx +911 -0
- package/docs/showcase/case4-login.tsx +2 -2
- package/docs/showcase/permission-matrix.tsx +13 -5
- package/docs/showcase/table-pagination.tsx +2 -1
- package/docs/showcase/tenant-brand-color.tsx +338 -0
- package/docs/showcase/theme-lab.tsx +2125 -0
- package/docs/themes/flat.css +462 -0
- package/docs/themes/glassmorphism.css +958 -0
- package/docs/themes/index.ts +219 -0
- package/docs/themes/neubrutalism.css +475 -0
- package/package.json +4 -3
- package/scripts/explain-token.mjs +382 -0
package/dist/app/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./time-format-labels.js";
|
|
|
7
7
|
export * from "./date-format-labels.js";
|
|
8
8
|
export * from "./storage.js";
|
|
9
9
|
export * from "./theme-axes.js";
|
|
10
|
+
export * from "./tenant-theme.js";
|
|
10
11
|
export * from "./request-headers.js";
|
|
11
12
|
export { AppProvider, useAppContext, useOptionalAppContext, useAppLocale, useAppTimezone, useAppTimeFormat, useAppDateFormat, } from "./app-provider.js";
|
|
12
13
|
export { useFormatting, useDateTime } from "./use-formatting.js";
|
|
@@ -14,3 +15,5 @@ export { syncDatetimeContext, getDatetimeContext, formatDate, isFormatDateValue,
|
|
|
14
15
|
export { useTranslation, usePickerLocales } from "../i18n/use-translation.js";
|
|
15
16
|
export { OverlayPortalProvider } from "../lib/overlay-portal.js";
|
|
16
17
|
export type { OverlayPortalProviderProps } from "../lib/overlay-portal.js";
|
|
18
|
+
export { ThemeScope } from "../lib/overlay-portal.js";
|
|
19
|
+
export type { ThemeScopeProps } from "../lib/overlay-portal.js";
|
package/dist/app/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./time-format-labels.js";
|
|
|
7
7
|
export * from "./date-format-labels.js";
|
|
8
8
|
export * from "./storage.js";
|
|
9
9
|
export * from "./theme-axes.js";
|
|
10
|
+
export * from "./tenant-theme.js";
|
|
10
11
|
export * from "./request-headers.js";
|
|
11
12
|
import {
|
|
12
13
|
AppProvider,
|
|
@@ -37,9 +38,11 @@ import {
|
|
|
37
38
|
} from "../lib/datetime/index.js";
|
|
38
39
|
import { useTranslation, usePickerLocales } from "../i18n/use-translation.js";
|
|
39
40
|
import { OverlayPortalProvider } from "../lib/overlay-portal.js";
|
|
41
|
+
import { ThemeScope } from "../lib/overlay-portal.js";
|
|
40
42
|
export {
|
|
41
43
|
AppProvider,
|
|
42
44
|
OverlayPortalProvider,
|
|
45
|
+
ThemeScope,
|
|
43
46
|
detectFormatDateKind,
|
|
44
47
|
formatAppDate,
|
|
45
48
|
formatAppDateLong,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** A bare CSS HSL channel triplet, `"H S% L%"` — the shape every `@godxjp/ui` colour role uses. */
|
|
2
|
+
export type HslTriplet = string;
|
|
3
|
+
/** WCAG 2.2 SC 1.4.3 Contrast (Minimum) — normal text. */
|
|
4
|
+
export declare const AA_NORMAL_TEXT = 4.5;
|
|
5
|
+
/** WCAG 2.2 SC 1.4.3 Contrast (Minimum) — large text (≥18.66px bold / ≥24px). */
|
|
6
|
+
export declare const AA_LARGE_TEXT = 3;
|
|
7
|
+
/**
|
|
8
|
+
* L = 0.179, the pivot of the label choice (solve 1.05/(L+0.05) = (L+0.05)/0.05). A surface above it
|
|
9
|
+
* is one an ink walks DOWN away from, below it one an ink walks UP — and either end of the ramp is
|
|
10
|
+
* achromatic, since `hsl(h s 0%)` is black and `hsl(h s 100%)` is white at every hue and saturation.
|
|
11
|
+
* So the walk always reaches at least 4.58:1 and the guarantee is a property of the whole sRGB cube
|
|
12
|
+
* rather than of the seeds we happened to try.
|
|
13
|
+
*
|
|
14
|
+
* EXPORTED because a component that paints its own fill has to make the same choice against it and
|
|
15
|
+
* must not re-derive the number (gh#884: `Sidebar`'s brand mark).
|
|
16
|
+
*/
|
|
17
|
+
export declare const CONTRAST_PIVOT = 0.179;
|
|
18
|
+
/**
|
|
19
|
+
* WCAG 2.2 relative luminance of an sRGB colour, per the definition quoted at the top of this file.
|
|
20
|
+
* Accepts `#abc` / `#aabbcc`; returns null for anything else.
|
|
21
|
+
*/
|
|
22
|
+
export declare function relativeLuminance(hex: string): number | null;
|
|
23
|
+
/**
|
|
24
|
+
* WCAG 2.2 contrast ratio between two hex colours, `(L_lighter + 0.05) / (L_darker + 0.05)`, in
|
|
25
|
+
* 1–21. Returns null if either colour is not a hex. Compare against {@link AA_NORMAL_TEXT}.
|
|
26
|
+
*/
|
|
27
|
+
export declare function contrastRatio(a: string, b: string): number | null;
|
|
28
|
+
/**
|
|
29
|
+
* `#0071bd` → `"204.13 100% 37.06%"` — the direction the package was missing (gh#868: `hslToHex`
|
|
30
|
+
* appears 9 times in `dist/`, `hexToHsl` zero). The exact inverse of
|
|
31
|
+
* {@link import("../email/color.js").hslToHex} within one hex step, which `tenant-theme.test.ts`
|
|
32
|
+
* round-trips over a grid. Returns null for anything that is not a 3- or 6-digit hex, so an
|
|
33
|
+
* unparseable customer value is ignored rather than breaking the page.
|
|
34
|
+
*/
|
|
35
|
+
export declare function hexToHsl(hex: string): HslTriplet | null;
|
|
36
|
+
export type TenantThemeOptions = {
|
|
37
|
+
/**
|
|
38
|
+
* The label to paint ON the seed, as hex. Omit and the contrast-safe choice is made here. Supply
|
|
39
|
+
* it — Platform stores a server-computed, WCAG-checked pair — and it is used as given, with
|
|
40
|
+
* `contrast` / `meetsAA` reporting what it achieves and a dev warning under 4.5:1.
|
|
41
|
+
*/
|
|
42
|
+
foreground?: string | null;
|
|
43
|
+
/**
|
|
44
|
+
* The surface the brand INK sits on, as hex — the DARKEST one it lands on in a light theme, the
|
|
45
|
+
* LIGHTEST in a dark one, because the guarantee has to hold in the hovered state too. Default: the
|
|
46
|
+
* package's light `--accent`, `#ebe9e5`. A region inside the dark theme passes `#3c3a34`
|
|
47
|
+
* (`--accent` there); a themed consumer passes its own. Anything that is not a hex is ignored.
|
|
48
|
+
*/
|
|
49
|
+
surface?: string | null;
|
|
50
|
+
};
|
|
51
|
+
export type TenantTheme = {
|
|
52
|
+
/** The seed, normalized to `#rrggbb`, or null when the input was not a hex colour. */
|
|
53
|
+
primary: string | null;
|
|
54
|
+
/** The label on that seed, `#rrggbb`, or null when there is no seed. */
|
|
55
|
+
foreground: string | null;
|
|
56
|
+
/** WCAG 2.2 ratio of `foreground` on `primary`, 1–21. `0` when there is no seed. */
|
|
57
|
+
contrast: number;
|
|
58
|
+
/** `contrast >= 4.5` — WCAG 2.2 SC 1.4.3, normal text. */
|
|
59
|
+
meetsAA: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The custom properties to declare on the scope. Spread straight into a `style` prop, or
|
|
62
|
+
* serialize into a `<style>` block for SSR. EMPTY for an unusable seed, so an invalid customer
|
|
63
|
+
* value degrades to the app's own theme instead of breaking the page.
|
|
64
|
+
*/
|
|
65
|
+
vars: Readonly<Record<string, string>>;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Turn a customer's hex into the scoped token declarations that make one REGION wear it —
|
|
69
|
+
* `--primary`, `--primary-foreground`, `--ring`, the hover / pressed steps, and the three brand INKS
|
|
70
|
+
* (`--text-link` / `--text-brand` / `--text-primary`) — all as literals, so the colour cannot
|
|
71
|
+
* half-apply on an engine without CSS relative colour and the ink cannot land under 4.5:1.
|
|
72
|
+
*
|
|
73
|
+
* ```tsx
|
|
74
|
+
* const brand = tenantTheme(tenant.primary_color, { foreground: tenant.primary_foreground });
|
|
75
|
+
* <div data-tenant={tenant.slug} style={brand.vars}>…</div>
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* Never throws and never returns null: an unusable seed yields empty `vars`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function tenantTheme(primary: string, options?: TenantThemeOptions): TenantTheme;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { isDevelopment } from "../lib/dev.js";
|
|
2
|
+
const AA_NORMAL_TEXT = 4.5;
|
|
3
|
+
const AA_LARGE_TEXT = 3;
|
|
4
|
+
const STEPS = {
|
|
5
|
+
darken: { hover: (l) => l - 8.4, active: (l) => l - 15.5 },
|
|
6
|
+
lighten: {
|
|
7
|
+
hover: (l) => l + 5.8,
|
|
8
|
+
active: (l) => l > 83.4 ? l - 5.8 : l + 11.6
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const INK_STEPS = {
|
|
12
|
+
light: { link: (l) => l - 8.4, pressed: (l) => l - 15.5 },
|
|
13
|
+
dark: { link: (l) => l, pressed: (l) => l + 5.8 }
|
|
14
|
+
};
|
|
15
|
+
const CONTRAST_PIVOT = 0.179;
|
|
16
|
+
const INK_SURFACE_LIGHT = "#ebe9e5";
|
|
17
|
+
const HEX = /^#(?:[\da-f]{3}|[\da-f]{6})$/i;
|
|
18
|
+
const clamp = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
19
|
+
function parseHex(value) {
|
|
20
|
+
if (typeof value !== "string" || !HEX.test(value.trim())) return null;
|
|
21
|
+
const body = value.trim().slice(1);
|
|
22
|
+
const full = body.length === 3 ? body.replace(/./g, (c) => c + c) : body;
|
|
23
|
+
return [0, 2, 4].map((offset) => parseInt(full.slice(offset, offset + 2), 16) / 255);
|
|
24
|
+
}
|
|
25
|
+
const toHex = (rgb) => `#${rgb.map(
|
|
26
|
+
(v) => Math.round(clamp(v, 0, 1) * 255).toString(16).padStart(2, "0")
|
|
27
|
+
).join("")}`;
|
|
28
|
+
function relativeLuminance(hex) {
|
|
29
|
+
const rgb = parseHex(hex);
|
|
30
|
+
return rgb === null ? null : luminanceOf(rgb);
|
|
31
|
+
}
|
|
32
|
+
function luminanceOf(rgb) {
|
|
33
|
+
const coefficients = [0.2126, 0.7152, 0.0722];
|
|
34
|
+
return rgb.reduce(
|
|
35
|
+
(sum, channel, index) => sum + (channel <= 0.03928 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4) * coefficients[index],
|
|
36
|
+
0
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
function contrastRatio(a, b) {
|
|
40
|
+
const [first, second] = [relativeLuminance(a), relativeLuminance(b)];
|
|
41
|
+
if (first === null || second === null) return null;
|
|
42
|
+
const [hi, lo] = first >= second ? [first, second] : [second, first];
|
|
43
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
44
|
+
}
|
|
45
|
+
function hexToHsl(hex) {
|
|
46
|
+
const rgb = parseHex(hex);
|
|
47
|
+
return rgb === null ? null : tripletOf(rgb);
|
|
48
|
+
}
|
|
49
|
+
const round = (value, places) => {
|
|
50
|
+
const factor = 10 ** places;
|
|
51
|
+
return Math.round(value * factor) / factor;
|
|
52
|
+
};
|
|
53
|
+
const formatHsl = ([h, s, l]) => `${round(h, 2)} ${round(s, 2)}% ${round(l, 2)}%`;
|
|
54
|
+
function tripletOf(rgb) {
|
|
55
|
+
return formatHsl(toHsl(rgb));
|
|
56
|
+
}
|
|
57
|
+
function fromHsl([h, s, l]) {
|
|
58
|
+
const hue = (h % 360 + 360) % 360 / 60;
|
|
59
|
+
const chroma = (1 - Math.abs(2 * l / 100 - 1)) * (s / 100);
|
|
60
|
+
const second = chroma * (1 - Math.abs(hue % 2 - 1));
|
|
61
|
+
const base = l / 100 - chroma / 2;
|
|
62
|
+
const [r, g, b] = hue < 1 ? [chroma, second, 0] : hue < 2 ? [second, chroma, 0] : hue < 3 ? [0, chroma, second] : hue < 4 ? [0, second, chroma] : hue < 5 ? [second, 0, chroma] : [chroma, 0, second];
|
|
63
|
+
return [r, g, b].map((c) => Math.round(clamp(c + base, 0, 1) * 255) / 255);
|
|
64
|
+
}
|
|
65
|
+
function inkLightness(h, s, from, surface) {
|
|
66
|
+
const clears = (l) => {
|
|
67
|
+
const ink = luminanceOf(fromHsl([h, s, l]));
|
|
68
|
+
const [hi, lo] = ink >= surface ? [ink, surface] : [surface, ink];
|
|
69
|
+
return (hi + 0.05) / (lo + 0.05) >= AA_NORMAL_TEXT;
|
|
70
|
+
};
|
|
71
|
+
const start = clamp(from, 0, 100);
|
|
72
|
+
if (clears(start)) return start;
|
|
73
|
+
const limit = surface > CONTRAST_PIVOT ? 0 : 1e3;
|
|
74
|
+
let fails = clamp(limit === 0 ? Math.floor(start * 10) : Math.ceil(start * 10), 0, 1e3);
|
|
75
|
+
if (clears(fails / 10)) return fails / 10;
|
|
76
|
+
let passes = limit;
|
|
77
|
+
if (!clears(passes / 10)) return passes / 10;
|
|
78
|
+
while (Math.abs(passes - fails) > 1) {
|
|
79
|
+
const mid = Math.round((passes + fails) / 2);
|
|
80
|
+
if (clears(mid / 10)) passes = mid;
|
|
81
|
+
else fails = mid;
|
|
82
|
+
}
|
|
83
|
+
return passes / 10;
|
|
84
|
+
}
|
|
85
|
+
function toHsl(rgb) {
|
|
86
|
+
const [r, g, b] = rgb;
|
|
87
|
+
const max = Math.max(r, g, b);
|
|
88
|
+
const min = Math.min(r, g, b);
|
|
89
|
+
const delta = max - min;
|
|
90
|
+
const lightness = (max + min) / 2;
|
|
91
|
+
const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1));
|
|
92
|
+
const hue = delta === 0 ? 0 : 60 * (max === r ? ((g - b) / delta + 6) % 6 : max === g ? (b - r) / delta + 2 : (r - g) / delta + 4);
|
|
93
|
+
return [hue, clamp(saturation, 0, 1) * 100, lightness * 100];
|
|
94
|
+
}
|
|
95
|
+
const EMPTY = Object.freeze({
|
|
96
|
+
primary: null,
|
|
97
|
+
foreground: null,
|
|
98
|
+
contrast: 0,
|
|
99
|
+
meetsAA: false,
|
|
100
|
+
vars: Object.freeze({})
|
|
101
|
+
});
|
|
102
|
+
function tenantTheme(primary, options = {}) {
|
|
103
|
+
const seed = parseHex(primary);
|
|
104
|
+
if (seed === null) return EMPTY;
|
|
105
|
+
const supplied = options.foreground ? parseHex(options.foreground) : null;
|
|
106
|
+
const label = supplied ?? (luminanceOf(seed) > CONTRAST_PIVOT ? [0, 0, 0] : [1, 1, 1]);
|
|
107
|
+
const seedLuminance = luminanceOf(seed);
|
|
108
|
+
const labelLuminance = luminanceOf(label);
|
|
109
|
+
const [hi, lo] = labelLuminance >= seedLuminance ? [labelLuminance, seedLuminance] : [seedLuminance, labelLuminance];
|
|
110
|
+
const contrast = (hi + 0.05) / (lo + 0.05);
|
|
111
|
+
if (isDevelopment() && supplied !== null && contrast < AA_NORMAL_TEXT) {
|
|
112
|
+
console.warn(
|
|
113
|
+
`[godxjp-ui] tenantTheme: the supplied foreground ${toHex(label)} measures ${round(contrast, 2)}:1 on ${toHex(seed)} \u2014 below WCAG 2.2 SC 1.4.3 AA (4.5:1). Text on this brand fill will be hard to read. Omit \`foreground\` to let the contrast-safe label be chosen, or raise the pair upstream.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const steps = STEPS[labelLuminance > 0.5 ? "darken" : "lighten"];
|
|
117
|
+
const [h, s, l] = toHsl(seed);
|
|
118
|
+
const surface = luminanceOf(
|
|
119
|
+
(options.surface ? parseHex(options.surface) : null) ?? parseHex(INK_SURFACE_LIGHT)
|
|
120
|
+
);
|
|
121
|
+
const ink = INK_STEPS[surface > CONTRAST_PIVOT ? "light" : "dark"];
|
|
122
|
+
const [inkH, inkS] = [round(h, 2), round(s, 2)];
|
|
123
|
+
const inkAt = (step) => formatHsl([inkH, inkS, inkLightness(inkH, inkS, round(step(l), 2), surface)]);
|
|
124
|
+
const [linkInk, pressedInk] = [inkAt(ink.link), inkAt(ink.pressed)];
|
|
125
|
+
return Object.freeze({
|
|
126
|
+
primary: toHex(seed),
|
|
127
|
+
foreground: toHex(label),
|
|
128
|
+
contrast,
|
|
129
|
+
meetsAA: contrast >= AA_NORMAL_TEXT,
|
|
130
|
+
vars: Object.freeze({
|
|
131
|
+
"--primary": tripletOf(seed),
|
|
132
|
+
"--primary-foreground": tripletOf(label),
|
|
133
|
+
// derived.css declares `--ring: var(--primary)` at `:root`, where a var() substitutes ONCE;
|
|
134
|
+
// a scope below it inherits the root's ring unless it restates it (the freeze rule).
|
|
135
|
+
"--ring": tripletOf(seed),
|
|
136
|
+
"--primary-hover": formatHsl([h, s, clamp(steps.hover(l), 0, 100)]),
|
|
137
|
+
"--primary-active": formatHsl([h, s, clamp(steps.active(l), 0, 100)]),
|
|
138
|
+
// The three brand INKS, each ≥ 4.5:1 on `surface` — the roles derived.css already publishes,
|
|
139
|
+
// now with the floor its lightness steps never had. `--text-primary` also seeds `--mark-*`.
|
|
140
|
+
"--text-link": linkInk,
|
|
141
|
+
"--text-brand": linkInk,
|
|
142
|
+
"--text-primary": pressedInk
|
|
143
|
+
})
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
export {
|
|
147
|
+
AA_LARGE_TEXT,
|
|
148
|
+
AA_NORMAL_TEXT,
|
|
149
|
+
CONTRAST_PIVOT,
|
|
150
|
+
contrastRatio,
|
|
151
|
+
hexToHsl,
|
|
152
|
+
relativeLuminance,
|
|
153
|
+
tenantTheme
|
|
154
|
+
};
|
package/dist/app/theme-axes.d.ts
CHANGED
|
@@ -43,5 +43,18 @@ export declare const isAppFontSize: (v: unknown) => v is AppFontSize;
|
|
|
43
43
|
* `data-brand` so the app's own `--primary` token applies.
|
|
44
44
|
*/
|
|
45
45
|
export declare function applyThemeAxes(el: HTMLElement, axes: Partial<AppThemeAxes>): void;
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Apply an entity's action palette to an existing themed tree, including portals. Call the returned
|
|
48
|
+
* cleanup on scope exit.
|
|
49
|
+
*
|
|
50
|
+
* The arithmetic — hex → HSL triplet, and the WCAG 2.2 label choice — is {@link tenantTheme}'s, so
|
|
51
|
+
* there is one formula rather than two. This entry point adds what a RE-SEED needs and a fresh
|
|
52
|
+
* region does not: the knobs an ancestor service theme may have pinned to the PREVIOUS brand are
|
|
53
|
+
* reset to `initial`, so the new seed cannot be outranked by a stale literal (gh#678, gh#664). The
|
|
54
|
+
* three brand INKS are reset and then re-declared from the seed, because `tenantTheme` computes a
|
|
55
|
+
* 4.5:1 floor for them and a bare reset would discard it (gh#887 — see the palette below).
|
|
56
|
+
*
|
|
57
|
+
* Use {@link tenantTheme} instead when you are painting a region with a customer's colour — it
|
|
58
|
+
* returns declarations to put in `style`, with no effect, no unmount restore and no SSR flash.
|
|
59
|
+
*/
|
|
47
60
|
export declare function applyPrimaryColor(root: HTMLElement, color: string, foreground?: string | null): () => void;
|
package/dist/app/theme-axes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { relativeLuminance, tenantTheme } from "./tenant-theme.js";
|
|
1
2
|
const APP_THEMES = ["light", "dark", "system"];
|
|
2
3
|
const APP_BRANDS = [
|
|
3
4
|
"brand",
|
|
@@ -40,43 +41,35 @@ function applyThemeAxes(el, axes) {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
function applyPrimaryColor(root, color, foreground) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
return [1, 3, 5].map((offset) => parseInt(value.slice(offset, offset + 2), 16) / 255);
|
|
44
|
+
const seed = tenantTheme(color, { foreground });
|
|
45
|
+
if (seed.primary === null) return () => {
|
|
46
46
|
};
|
|
47
|
-
const
|
|
48
|
-
if (!rgb) return () => {
|
|
49
|
-
};
|
|
50
|
-
const luminance = (channels) => channels.reduce(
|
|
51
|
-
(sum, value, index) => sum + (value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4) * [0.2126, 0.7152, 0.0722][index],
|
|
52
|
-
0
|
|
53
|
-
);
|
|
54
|
-
const text = foreground && parse(foreground) || (luminance(rgb) > 0.179 ? [0, 0, 0] : [1, 1, 1]);
|
|
55
|
-
const hsl = (channels) => {
|
|
56
|
-
const [r, g, b] = channels;
|
|
57
|
-
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
58
|
-
const delta = max - min, lightness = (max + min) / 2;
|
|
59
|
-
const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1));
|
|
60
|
-
const hue = delta === 0 ? 0 : 60 * (max === r ? ((g - b) / delta + 6) % 6 : max === g ? (b - r) / delta + 2 : (r - g) / delta + 4);
|
|
61
|
-
return `${hue} ${saturation * 100}% ${lightness * 100}%`;
|
|
62
|
-
};
|
|
63
|
-
const polarity = luminance(text) > 0.5 ? "darken" : "lighten";
|
|
47
|
+
const polarity = (relativeLuminance(seed.foreground) ?? 0) > 0.5 ? "darken" : "lighten";
|
|
64
48
|
const palette = {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*
|
|
73
|
-
*
|
|
49
|
+
/* THE STALE-PIN RESETS COME FIRST, AND THE ORDER IS THE WHOLE POINT (gh#887).
|
|
50
|
+
*
|
|
51
|
+
* They exist because a theme above this element may have pinned a knob to the PREVIOUS brand,
|
|
52
|
+
* and an inherited literal would outrank a seed that only re-derives in CSS (gh#678, gh#664):
|
|
53
|
+
* a link colour pinned for the old brand would survive on a re-tinted product. That reason
|
|
54
|
+
* still holds, so nothing is deleted.
|
|
55
|
+
*
|
|
56
|
+
* But `tenantTheme` now RETURNS the three brand inks as clamped literals (gh#887), and these
|
|
57
|
+
* three keys used to sit AFTER the spread — so the same call that walked each ink to 4.5:1 on
|
|
58
|
+
* the surface threw the result away one line later, and a re-seeded tree got the unclamped
|
|
59
|
+
* ramp step back. Declared BEFORE the spread they are overwritten by those literals, which
|
|
60
|
+
* defeat an ancestor's pin on their own (a literal ON the element beats an inherited one), and
|
|
61
|
+
* the guard still stands if that emission ever stops.
|
|
62
|
+
*
|
|
63
|
+
* `--primary-border` / `--control-outline` are NOT in `vars` — the reset IS their value here,
|
|
64
|
+
* and they go on deriving in CSS from the `--primary` in scope. */
|
|
74
65
|
"--text-link": "initial",
|
|
75
66
|
"--text-brand": "initial",
|
|
76
67
|
"--text-primary": "initial",
|
|
68
|
+
...seed.vars,
|
|
69
|
+
"--primary-border": "initial",
|
|
70
|
+
"--control-outline": "initial",
|
|
77
71
|
"--primary-hover-channels": `var(--primary-hover-${polarity}-channels)`,
|
|
78
|
-
"--primary-active-channels": `var(--primary-active-${polarity}-channels)
|
|
79
|
-
"--ring": hsl(rgb)
|
|
72
|
+
"--primary-active-channels": `var(--primary-active-${polarity}-channels)`
|
|
80
73
|
};
|
|
81
74
|
const previous = Object.keys(palette).map((key) => [
|
|
82
75
|
key,
|
|
@@ -17,16 +17,20 @@ type CartesianChartProps = {
|
|
|
17
17
|
height?: number;
|
|
18
18
|
showLegend?: boolean;
|
|
19
19
|
showGrid?: boolean;
|
|
20
|
+
valueDomain?: [number, number];
|
|
21
|
+
valueTicks?: number[];
|
|
20
22
|
numberFormat?: Intl.NumberFormatOptions;
|
|
21
23
|
emptyMessage?: string;
|
|
22
24
|
className?: string;
|
|
23
25
|
id?: string;
|
|
24
26
|
/** line/area */
|
|
25
27
|
curved?: boolean;
|
|
28
|
+
/** line/area */
|
|
29
|
+
showDots?: boolean;
|
|
26
30
|
/** bar/area */
|
|
27
31
|
stacked?: boolean;
|
|
28
32
|
/** bar */
|
|
29
33
|
horizontal?: boolean;
|
|
30
34
|
};
|
|
31
|
-
export declare function CartesianChart({ kind, data, series, categoryKey, label, showCaption, description, size, height, showLegend, showGrid, numberFormat, emptyMessage, className, id, curved, stacked, horizontal, }: CartesianChartProps): React.JSX.Element;
|
|
35
|
+
export declare function CartesianChart({ kind, data, series, categoryKey, label, showCaption, description, size, height, showLegend, showGrid, valueDomain, valueTicks, numberFormat, emptyMessage, className, id, curved, showDots, stacked, horizontal, }: CartesianChartProps): React.JSX.Element;
|
|
32
36
|
export {};
|
|
@@ -35,11 +35,14 @@ function CartesianChart({
|
|
|
35
35
|
height,
|
|
36
36
|
showLegend = true,
|
|
37
37
|
showGrid = true,
|
|
38
|
+
valueDomain,
|
|
39
|
+
valueTicks,
|
|
38
40
|
numberFormat,
|
|
39
41
|
emptyMessage,
|
|
40
42
|
className,
|
|
41
43
|
id,
|
|
42
44
|
curved = false,
|
|
45
|
+
showDots = false,
|
|
43
46
|
stacked = false,
|
|
44
47
|
horizontal = false
|
|
45
48
|
}) {
|
|
@@ -59,6 +62,10 @@ function CartesianChart({
|
|
|
59
62
|
const resolvedHeight = chartHeight(size, height);
|
|
60
63
|
const tickFormatter = (value) => fmt.format(value);
|
|
61
64
|
const curve = curved ? "monotone" : "linear";
|
|
65
|
+
const valueAxisScale = {
|
|
66
|
+
...valueDomain ? { domain: valueDomain } : null,
|
|
67
|
+
...valueTicks ? { ticks: valueTicks } : null
|
|
68
|
+
};
|
|
62
69
|
const measuresCategoryAxis = kind === "bar" && horizontal;
|
|
63
70
|
const canvasRef = React.useRef(null);
|
|
64
71
|
const probeRef = React.useRef(null);
|
|
@@ -95,7 +102,7 @@ function CartesianChart({
|
|
|
95
102
|
);
|
|
96
103
|
};
|
|
97
104
|
const axes = (numberAxisVertical) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
98
|
-
showGrid ? /* @__PURE__ */ jsx(CartesianGrid, {
|
|
105
|
+
showGrid ? /* @__PURE__ */ jsx(CartesianGrid, { stroke: "hsl(var(--border))" }) : null,
|
|
99
106
|
numberAxisVertical ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
100
107
|
/* @__PURE__ */ jsx(
|
|
101
108
|
XAxis,
|
|
@@ -112,7 +119,8 @@ function CartesianChart({
|
|
|
112
119
|
tickFormatter,
|
|
113
120
|
tickLine: false,
|
|
114
121
|
stroke: "hsl(var(--muted-foreground))",
|
|
115
|
-
fontSize: 12
|
|
122
|
+
fontSize: 12,
|
|
123
|
+
...valueAxisScale
|
|
116
124
|
}
|
|
117
125
|
)
|
|
118
126
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -123,7 +131,8 @@ function CartesianChart({
|
|
|
123
131
|
tickFormatter,
|
|
124
132
|
tickLine: false,
|
|
125
133
|
stroke: "hsl(var(--muted-foreground))",
|
|
126
|
-
fontSize: 12
|
|
134
|
+
fontSize: 12,
|
|
135
|
+
...valueAxisScale
|
|
127
136
|
}
|
|
128
137
|
),
|
|
129
138
|
/* @__PURE__ */ jsx(
|
|
@@ -153,8 +162,7 @@ function CartesianChart({
|
|
|
153
162
|
dataKey: s.dataKey,
|
|
154
163
|
name: s.label ?? s.dataKey,
|
|
155
164
|
stroke: chartColor(i, s.color),
|
|
156
|
-
|
|
157
|
-
dot: false
|
|
165
|
+
dot: showDots
|
|
158
166
|
},
|
|
159
167
|
s.dataKey
|
|
160
168
|
))
|
|
@@ -171,9 +179,8 @@ function CartesianChart({
|
|
|
171
179
|
name: s.label ?? s.dataKey,
|
|
172
180
|
stackId: stacked ? "stack" : void 0,
|
|
173
181
|
stroke: chartColor(i, s.color),
|
|
174
|
-
fill: chartColor(i, s.color),
|
|
175
|
-
|
|
176
|
-
strokeWidth: 2
|
|
182
|
+
fill: s.fillColor ?? chartColor(i, s.color),
|
|
183
|
+
dot: showDots
|
|
177
184
|
},
|
|
178
185
|
s.dataKey
|
|
179
186
|
))
|
|
@@ -9,7 +9,7 @@ export type BadgeVariant = "default" | "secondary" | "outline" | "dashed";
|
|
|
9
9
|
*/
|
|
10
10
|
export type BadgeTone = ToneProp | "primary";
|
|
11
11
|
declare const badgeVariants: (props?: ({
|
|
12
|
-
variant?: "dashed" | "default" | "outline" | "secondary" | "tinted" | null | undefined;
|
|
12
|
+
variant?: "dashed" | "default" | "outline" | "secondary" | "tinted" | "toned" | null | undefined;
|
|
13
13
|
shape?: "default" | "pill" | "sharp" | null | undefined;
|
|
14
14
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
15
|
export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">, Omit<VariantProps<typeof badgeVariants>, "variant" | "shape"> {
|
|
@@ -55,7 +55,7 @@ const badgeVariants = cva(
|
|
|
55
55
|
// `ui-focus-ring` = the system's single focus source (styles/focus-ring.css). It replaces a
|
|
56
56
|
// hand-rolled `focus:ring-2 focus:ring-ring`, which was token-blind (no --focus-ring-* knob
|
|
57
57
|
// reached it) and fired on `:focus`, so a mouse click on an interactive badge lit a ring.
|
|
58
|
-
"ui-focus-ring inline-flex items-center
|
|
58
|
+
"ui-focus-ring inline-flex items-center font-medium transition-colors",
|
|
59
59
|
{
|
|
60
60
|
variants: {
|
|
61
61
|
variant: {
|
|
@@ -68,7 +68,22 @@ const badgeVariants = cva(
|
|
|
68
68
|
* off the element so badge-layout.css's `[data-tinted]` rule is
|
|
69
69
|
* reachable (a utility beats a components-layer rule whatever the
|
|
70
70
|
* specificity). Selected by the `color` prop, never by hand. */
|
|
71
|
-
tinted: ""
|
|
71
|
+
tinted: "",
|
|
72
|
+
/* A STATUS TONE owns the whole chip — fill, border and ink — so the variant contributes
|
|
73
|
+
* none of them. Selected automatically whenever `tone` is not `default`, never by hand.
|
|
74
|
+
*
|
|
75
|
+
* This used to be left to `cn` (tailwind-merge): `default` painted `bg-primary`, the tone
|
|
76
|
+
* added `bg-success/10`, twMerge saw one conflict group and deleted the first. gh#866
|
|
77
|
+
* respelled the tone fill as `[background-color:var(--surface-success, …)]` so a brand's
|
|
78
|
+
* own pale ground could reach it — and an ARBITRARY PROPERTY is a different twMerge group,
|
|
79
|
+
* so nothing was deleted, both survived, and plain CSS source order handed the chip to
|
|
80
|
+
* `bg-primary`. Every toned Badge painted violet under tone-coloured ink: measured 1.07:1
|
|
81
|
+
* (warning) to 1.30:1 (info), against 4.5:1 for AA.
|
|
82
|
+
*
|
|
83
|
+
* The classes were "correct" before and after; only the cascade moved. So the fix is to
|
|
84
|
+
* stop emitting the conflict at all rather than to restore a spelling that happened to
|
|
85
|
+
* collide — a fill deleted by a class-merging library is a fill nobody declared. */
|
|
86
|
+
toned: ""
|
|
72
87
|
},
|
|
73
88
|
// Corner shape — default inherits the badge radius token; pill/sharp override via the tokens.
|
|
74
89
|
shape: {
|
|
@@ -137,7 +152,10 @@ function Badge({
|
|
|
137
152
|
"data-removable": onRemove ? "" : void 0,
|
|
138
153
|
className: cn(
|
|
139
154
|
badgeVariants({
|
|
140
|
-
|
|
155
|
+
// A status tone paints the chip itself (`badgeToneClass` below), so the variant must not
|
|
156
|
+
// also paint one — see the `toned` variant. An EXPLICIT `variant` still wins, which is
|
|
157
|
+
// how `<Badge variant="outline" tone="success">` keeps its border treatment.
|
|
158
|
+
variant: tinted ? "tinted" : variant ?? (resolvedTone === "default" ? "default" : "toned"),
|
|
141
159
|
shape: shape ?? "default"
|
|
142
160
|
}),
|
|
143
161
|
tinted ? void 0 : badgeToneClass[resolvedTone],
|
|
@@ -131,7 +131,7 @@ const CarouselItem = React.forwardRef(
|
|
|
131
131
|
)
|
|
132
132
|
);
|
|
133
133
|
CarouselItem.displayName = "CarouselItem";
|
|
134
|
-
const CarouselPrevious = React.forwardRef(({ className, ...props }, ref) => {
|
|
134
|
+
const CarouselPrevious = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
135
135
|
const { t } = useTranslation();
|
|
136
136
|
const { canScrollPrev, scrollPrev } = useCarousel();
|
|
137
137
|
return /* @__PURE__ */ jsxs(
|
|
@@ -145,14 +145,14 @@ const CarouselPrevious = React.forwardRef(({ className, ...props }, ref) => {
|
|
|
145
145
|
onClick: scrollPrev,
|
|
146
146
|
...props,
|
|
147
147
|
children: [
|
|
148
|
-
/* @__PURE__ */ jsx(
|
|
148
|
+
/* @__PURE__ */ jsx("span", { className: "ui-carousel-arrow", "aria-hidden": "true", children: children ?? /* @__PURE__ */ jsx(ChevronLeft, {}) }),
|
|
149
149
|
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t("dataDisplay.carousel.previous") })
|
|
150
150
|
]
|
|
151
151
|
}
|
|
152
152
|
);
|
|
153
153
|
});
|
|
154
154
|
CarouselPrevious.displayName = "CarouselPrevious";
|
|
155
|
-
const CarouselNext = React.forwardRef(({ className, ...props }, ref) => {
|
|
155
|
+
const CarouselNext = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
156
156
|
const { t } = useTranslation();
|
|
157
157
|
const { canScrollNext, scrollNext } = useCarousel();
|
|
158
158
|
return /* @__PURE__ */ jsxs(
|
|
@@ -166,7 +166,7 @@ const CarouselNext = React.forwardRef(({ className, ...props }, ref) => {
|
|
|
166
166
|
onClick: scrollNext,
|
|
167
167
|
...props,
|
|
168
168
|
children: [
|
|
169
|
-
/* @__PURE__ */ jsx(
|
|
169
|
+
/* @__PURE__ */ jsx("span", { className: "ui-carousel-arrow", "aria-hidden": "true", children: children ?? /* @__PURE__ */ jsx(ChevronRight, {}) }),
|
|
170
170
|
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t("dataDisplay.carousel.next") })
|
|
171
171
|
]
|
|
172
172
|
}
|
|
@@ -86,6 +86,11 @@ function columnHideBelowProps(col) {
|
|
|
86
86
|
const step = resolveColumnHideBelow(col);
|
|
87
87
|
return step ? { "data-hide-below": step } : {};
|
|
88
88
|
}
|
|
89
|
+
function stackedCellLabel(col) {
|
|
90
|
+
const header = col.header;
|
|
91
|
+
const headerIsEmpty = header == null || header === "" || header === false;
|
|
92
|
+
return headerIsEmpty ? col.ariaLabel : header;
|
|
93
|
+
}
|
|
89
94
|
function dataTableFeatures() {
|
|
90
95
|
return tableFeatures({
|
|
91
96
|
rowSortingFeature,
|
|
@@ -798,6 +803,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
798
803
|
} = useDataTableContext();
|
|
799
804
|
const { t } = useTranslation();
|
|
800
805
|
const presetAttr = preset === "default" ? void 0 : preset;
|
|
806
|
+
const stacked = preset === "stacked-record-collection";
|
|
801
807
|
const isDenied = denied != null && denied !== false;
|
|
802
808
|
const isError = !isDenied && error != null && error !== false;
|
|
803
809
|
const rowPadding = tableRowHeightClass;
|
|
@@ -1166,7 +1172,11 @@ DataTable.Content = function DataTableContent() {
|
|
|
1166
1172
|
// Hover highlight when rows are clickable OR explicitly hoverable… The
|
|
1167
1173
|
// --accent step TableRow itself uses, not --muted/50: that sat BELOW the
|
|
1168
1174
|
// zebra stripe (--muted/0.8, gh#700), so hovering a striped row lightened it.
|
|
1169
|
-
|
|
1175
|
+
// Same token-driven formula as TableRow's own hover fill (gh#894) — a bare
|
|
1176
|
+
// `/70` here would silently outrank a theme's retuned default at equal
|
|
1177
|
+
// specificity, and `color-mix` (not `hsl(x / var(y, 70%))`) because a `/`
|
|
1178
|
+
// inside a Tailwind arbitrary property is read as ITS OWN opacity suffix.
|
|
1179
|
+
(onRowClick || hoverable) && "hover:[background-color:color-mix(in_oklab,hsl(var(--accent))_var(--table-row-hover-background-alpha,70%),transparent)]",
|
|
1170
1180
|
// …but the affordance (cursor + focus mark) only when clickable.
|
|
1171
1181
|
//
|
|
1172
1182
|
// `ui-focus-ring` = the single focus source (styles/focus-ring.css). It
|
|
@@ -1282,6 +1292,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1282
1292
|
priority: col.priority,
|
|
1283
1293
|
"data-align": col.align,
|
|
1284
1294
|
title,
|
|
1295
|
+
label: stacked ? stackedCellLabel(col) : void 0,
|
|
1285
1296
|
...fixedCellProps(col.key, fixedEdge(col)),
|
|
1286
1297
|
...columnHideBelowProps(col),
|
|
1287
1298
|
style: columnCellStyle(col),
|
|
@@ -1379,12 +1390,12 @@ function NumberedPagination({ pageSizeOptions, className }) {
|
|
|
1379
1390
|
{
|
|
1380
1391
|
value: String(pageSize),
|
|
1381
1392
|
onValueChange: (v) => table.setPageSize(Number(v)),
|
|
1393
|
+
"aria-label": t("dataGrid.rowsPerPage"),
|
|
1382
1394
|
children: [
|
|
1383
1395
|
/* @__PURE__ */ jsx(
|
|
1384
1396
|
SelectTrigger,
|
|
1385
1397
|
{
|
|
1386
1398
|
size: "sm",
|
|
1387
|
-
"aria-label": t("dataGrid.rowsPerPage"),
|
|
1388
1399
|
className: "ui-data-table-page-size-trigger w-auto shrink-0 tabular-nums",
|
|
1389
1400
|
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
1390
1401
|
}
|
|
@@ -162,7 +162,7 @@ const PermissionMatrix = React.forwardRef(
|
|
|
162
162
|
return /* @__PURE__ */ jsxs(
|
|
163
163
|
TableRow,
|
|
164
164
|
{
|
|
165
|
-
|
|
165
|
+
"data-tone": isDiffRow ? "warning" : void 0,
|
|
166
166
|
children: [
|
|
167
167
|
/* @__PURE__ */ jsx(TableCell, { className: cn(PIN_START, "align-middle"), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col leading-tight", children: [
|
|
168
168
|
/* @__PURE__ */ jsxs("span", { className: "ui-permission-matrix-name", children: [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { BreakpointProp, FlushProp, LabelProp, TableCellIndentProp, TableColumnPriorityProp, TablePresetProp, WidthProp } from "../../props/vocabulary/index.js";
|
|
2
|
+
import type { BreakpointProp, FlushProp, LabelProp, TableCellIndentProp, TableColumnPriorityProp, TablePresetProp, TableRowToneProp, WidthProp } from "../../props/vocabulary/index.js";
|
|
3
3
|
export type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
|
4
4
|
/**
|
|
5
5
|
* Whether the Table owns its own horizontal-scroll region (default `true`). When `true` a table
|
|
@@ -157,7 +157,16 @@ export declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes
|
|
|
157
157
|
} & React.RefAttributes<HTMLTableElement>>;
|
|
158
158
|
export declare const TableHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
|
|
159
159
|
export declare const TableBody: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
|
|
160
|
-
|
|
160
|
+
/**
|
|
161
|
+
* The row's STATE — same wash/rail/`--surface-*` grounding `DataTable`'s own `rowTone` already
|
|
162
|
+
* writes onto `data-tone` (gh#866). Typed here so a hand-composed row can discover it instead of
|
|
163
|
+
* reaching for the raw attribute; the attribute keeps working (`TableRow data-tone="warning"`
|
|
164
|
+
* is untouched — this is an addition, not a migration), and `tone` writes the same attribute.
|
|
165
|
+
*/
|
|
166
|
+
type TableRowTone = {
|
|
167
|
+
tone?: TableRowToneProp;
|
|
168
|
+
};
|
|
169
|
+
export declare const TableRow: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableRowElement> & TableRowTone & React.RefAttributes<HTMLTableRowElement>>;
|
|
161
170
|
/**
|
|
162
171
|
* Column priority carried by BOTH the header cell and the body cells of a column. Read only by
|
|
163
172
|
* `Table preset="action-collection"` below its collapse step, where it selects the column's
|