@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
|
@@ -129,8 +129,8 @@ export default function LoginShowcase() {
|
|
|
129
129
|
/* Page-level controls, pinned to the banner's inline end. */
|
|
130
130
|
actions={
|
|
131
131
|
<>
|
|
132
|
-
<Select value={locale} onValueChange={setLocale}>
|
|
133
|
-
<SelectTrigger size="sm" className="w-32"
|
|
132
|
+
<Select aria-label="言語を選択" value={locale} onValueChange={setLocale}>
|
|
133
|
+
<SelectTrigger size="sm" className="w-32">
|
|
134
134
|
<Languages aria-hidden="true" className="text-muted-foreground" />
|
|
135
135
|
<SelectValue />
|
|
136
136
|
</SelectTrigger>
|
|
@@ -137,7 +137,10 @@ function GrantCell({ granted }: { granted: boolean }) {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// 固定列(先頭 = 権限)— スクロールする本体が下を通るので不透明背景 + トークン端。
|
|
140
|
-
|
|
140
|
+
// `.ui-permission-matrix-pin` is the packaged component's own class (gh#874 fixed its opaque
|
|
141
|
+
// base + tone/hover/selected compositing in `data-display-layout.css`) — reused here rather than
|
|
142
|
+
// hand-rolled so this showcase gets the identical answer instead of drifting from it again.
|
|
143
|
+
const PIN_START = "ui-permission-matrix-pin";
|
|
141
144
|
|
|
142
145
|
export default function Demo() {
|
|
143
146
|
const [compareA, setCompareA] = React.useState("editor");
|
|
@@ -225,11 +228,11 @@ export default function Demo() {
|
|
|
225
228
|
</CardAction>
|
|
226
229
|
</CardHeader>
|
|
227
230
|
<CardContent flush>
|
|
228
|
-
<div className="overflow-x-auto">
|
|
231
|
+
<div className="ui-permission-matrix overflow-x-auto">
|
|
229
232
|
<Table className="min-w-[760px]">
|
|
230
233
|
<TableHeader className="bg-secondary [&_tr]:bg-secondary">
|
|
231
234
|
<TableRow className="bg-secondary hover:bg-secondary">
|
|
232
|
-
<TableHead className={
|
|
235
|
+
<TableHead className={PIN_START}>権限</TableHead>
|
|
233
236
|
{ROLES.map((role) => {
|
|
234
237
|
const isCompared = !sameRole && compared.has(role.id);
|
|
235
238
|
return (
|
|
@@ -263,10 +266,15 @@ export default function Demo() {
|
|
|
263
266
|
return (
|
|
264
267
|
<TableRow
|
|
265
268
|
key={perm.id}
|
|
266
|
-
|
|
269
|
+
// Same hand-off the PermissionMatrix component uses (gh#872): the wash,
|
|
270
|
+
// the leading rail and the hover step belong to `styles/table-layout.css`,
|
|
271
|
+
// which reads `--surface-warning` first — so this hand-composed table is
|
|
272
|
+
// re-groundable by a tenant theme exactly like the packaged one. A local
|
|
273
|
+
// `bg-warning/[0.07]` here would be a second, private warning surface.
|
|
274
|
+
data-tone={isDiffRow ? "warning" : undefined}
|
|
267
275
|
aria-label={isDiffRow ? `${perm.name} · 比較ロール間で差分あり` : undefined}
|
|
268
276
|
>
|
|
269
|
-
<TableCell className={cn(PIN_START, "
|
|
277
|
+
<TableCell className={cn(PIN_START, "align-middle")}>
|
|
270
278
|
<Flex direction="col" className="leading-tight">
|
|
271
279
|
<Flex align="center" gap="xs">
|
|
272
280
|
<Text weight="medium">{perm.name}</Text>
|
|
@@ -312,13 +312,14 @@ function CursorPeriodCard() {
|
|
|
312
312
|
<CardTitle level={2}>カーソル / 期間ジャンプ</CardTitle>
|
|
313
313
|
<CardAction>
|
|
314
314
|
<Select
|
|
315
|
+
aria-label="期間を選択"
|
|
315
316
|
value={period}
|
|
316
317
|
onValueChange={(v: string) => {
|
|
317
318
|
setPeriod(v);
|
|
318
319
|
setCursor(undefined); // jumping period resets the cursor to the first page
|
|
319
320
|
}}
|
|
320
321
|
>
|
|
321
|
-
<SelectTrigger size="sm"
|
|
322
|
+
<SelectTrigger size="sm" className="w-36">
|
|
322
323
|
<SelectValue />
|
|
323
324
|
</SelectTrigger>
|
|
324
325
|
<SelectContent>
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Showcase · tenant-brand-color — "Màu thương hiệu của KHÁCH, ở một vùng, lúc chạy" (gh#861, gh#868).
|
|
3
|
+
*
|
|
4
|
+
* ── Gate 0 verdict: COMPOSITION PATTERN ──────────────────────────────────────────────────────
|
|
5
|
+
* Hai kho tiêu thụ xin một `<TenantTheme primary={hex}>`. Chạy Framework-Component Test
|
|
6
|
+
* (docs/COMPOSITION-VS-COMPONENT.md §3.2) thì nó TRƯỢT: bọc `<div>` không giữ state, không bàn
|
|
7
|
+
* phím, không focus, không ARIA (C2); nó đúng là "token override theo scope" mà chính doctrine
|
|
8
|
+
* chỉ định làm composition (C3); API `primary`/`secondary`/`tenant` không map vào
|
|
9
|
+
* `value`/`onValueChange`/`size` nào (C4); và nó không render chữ nào để `t()` hay APG nói về
|
|
10
|
+
* (C6). Thứ hai consumer thật sự chép tay là PHÉP TÍNH — hex → triplet HSL, và quy tắc tương phản
|
|
11
|
+
* chọn màu chữ. Phép tính ấy ship trong gói (`tenantTheme`, `@godxjp/ui/app`); cái `<div>` ở lại
|
|
12
|
+
* trang này. Không component mới, không token mới.
|
|
13
|
+
*
|
|
14
|
+
* ── Trang này chứng minh bốn thứ, mỗi thứ đo được trên màn hình ──────────────────────────────
|
|
15
|
+
* 1. PHẠM VI. Cùng một hàng component, một bản TRONG vùng `[data-tenant]` và một bản NGOÀI.
|
|
16
|
+
* Chỉ bản trong đổi màu. Không đụng `<html>`, không stylesheet thứ hai.
|
|
17
|
+
* 2. CẶP MÀU. `--primary` không đi một mình: `--primary-foreground` đi kèm, chọn bằng tương
|
|
18
|
+
* phản WCAG 2.2 SC 1.4.3 (4.5:1), và tỷ số đạt được HIỆN trên màn hình chứ không giấu.
|
|
19
|
+
* 3. HOVER / ACTIVE KHÔNG IM LẶNG. `tenantTheme` phát hai bước ấy thành TRIPLET THẬT, không
|
|
20
|
+
* phải `initial` + relative colour, nên màu khách áp ở mọi trạng thái trên mọi engine —
|
|
21
|
+
* không cần `CSS.supports("color", "hsl(from red h s l)")` như bản tự chế của một kho tiêu thụ.
|
|
22
|
+
* 4. CA ĐÃ CHẠY, KHÔNG PHẢI HAPPY PATH. Bốn hàng cuối là hex không hợp lệ (vùng trở về theme
|
|
23
|
+
* của app, trang không vỡ), một cặp do máy chủ cấp KHÔNG đạt AA (API nói KHÔNG, kèm số), và
|
|
24
|
+
* hai màu khách hay chọn nhất mà quy tắc ngây thơ "luôn chữ trắng" sẽ hỏng.
|
|
25
|
+
*
|
|
26
|
+
* ── Điều trang này CỐ TÌNH không làm ─────────────────────────────────────────────────────────
|
|
27
|
+
* Không đụng `--text-link` / `--text-brand` / `--text-primary`. Đó là MỰC trên nền trang, không
|
|
28
|
+
* phải nhãn trên nền nút: độ đọc của nó phụ thuộc `--background` — thứ màu của khách không quyết
|
|
29
|
+
* định — và hướng bước là theo THEME chứ không theo nhãn. Một seed nhạt (#FFD400) cho link 1.4:1
|
|
30
|
+
* trên nền trắng. Xem hàng "mực thương hiệu" trong bảng.
|
|
31
|
+
*/
|
|
32
|
+
import * as React from "react";
|
|
33
|
+
|
|
34
|
+
import { AA_NORMAL_TEXT, tenantTheme } from "@godxjp/ui/app";
|
|
35
|
+
import { Button, Text } from "@godxjp/ui/general";
|
|
36
|
+
import { Flex, PageContainer, ResponsiveGrid } from "@godxjp/ui/layout";
|
|
37
|
+
import {
|
|
38
|
+
Badge,
|
|
39
|
+
Card,
|
|
40
|
+
CardContent,
|
|
41
|
+
CardDescription,
|
|
42
|
+
CardHeader,
|
|
43
|
+
CardTitle,
|
|
44
|
+
CodeBlock,
|
|
45
|
+
DataTable,
|
|
46
|
+
Progress,
|
|
47
|
+
Swatch,
|
|
48
|
+
type ColumnDef,
|
|
49
|
+
} from "@godxjp/ui/data-display";
|
|
50
|
+
import { ColorPicker, FormField, Input, Switch } from "@godxjp/ui/data-entry";
|
|
51
|
+
import { Callout } from "@godxjp/ui/feedback";
|
|
52
|
+
|
|
53
|
+
/** Màu thật của khách — hai đầu mút là chỗ quy tắc "luôn chữ trắng" hỏng. */
|
|
54
|
+
const CUSTOMERS = [
|
|
55
|
+
{ hex: "#0071bd", name: "Biển Đông Logistics" },
|
|
56
|
+
{ hex: "#FFD400", name: "Vàng Mặt Trời" },
|
|
57
|
+
{ hex: "#0A1F44", name: "Hàng hải Bắc Hà" },
|
|
58
|
+
{ hex: "#00A86B", name: "Ngọc Bích Foods" },
|
|
59
|
+
{ hex: "#E30613", name: "Đỏ Sao Vàng" },
|
|
60
|
+
{ hex: "#767676", name: "Xám Trung Tính" },
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
type Row = {
|
|
64
|
+
hex: string;
|
|
65
|
+
name: string;
|
|
66
|
+
foreground: string;
|
|
67
|
+
ratio: string;
|
|
68
|
+
meetsAA: boolean;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const columns: ColumnDef<Row>[] = [
|
|
72
|
+
{
|
|
73
|
+
key: "hex",
|
|
74
|
+
header: "Màu khách",
|
|
75
|
+
render: (r) => (
|
|
76
|
+
<Flex align="center" gap="sm">
|
|
77
|
+
<Swatch color={r.hex} aria-label={`Màu chính của ${r.name}: ${r.hex}`} />
|
|
78
|
+
<Text as="span" size="xs" mono>
|
|
79
|
+
{r.hex}
|
|
80
|
+
</Text>
|
|
81
|
+
</Flex>
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
{ key: "name", header: "Khách", hiddenOnMobile: true },
|
|
85
|
+
{
|
|
86
|
+
key: "foreground",
|
|
87
|
+
header: "Màu chữ đã chọn",
|
|
88
|
+
render: (r) => (
|
|
89
|
+
<Flex align="center" gap="sm">
|
|
90
|
+
<Swatch color={r.foreground} aria-label={`Màu chữ an toàn: ${r.foreground}`} />
|
|
91
|
+
<Text as="span" size="xs" mono>
|
|
92
|
+
{r.foreground}
|
|
93
|
+
</Text>
|
|
94
|
+
</Flex>
|
|
95
|
+
),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
key: "ratio",
|
|
99
|
+
header: "Tương phản",
|
|
100
|
+
align: "right",
|
|
101
|
+
render: (r) => <Text tabular>{r.ratio}:1</Text>,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: "meetsAA",
|
|
105
|
+
header: "WCAG 2.2 AA",
|
|
106
|
+
align: "center",
|
|
107
|
+
render: (r) => (
|
|
108
|
+
<Badge tone={r.meetsAA ? "success" : "destructive"}>{r.meetsAA ? "Đạt" : "Không đạt"}</Badge>
|
|
109
|
+
),
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Một hàng component THẬT đọc `--primary`. Dùng hai lần trên trang: một lần trong vùng khách,
|
|
115
|
+
* một lần ngoài — cùng markup, khác đúng một `style` của thẻ cha.
|
|
116
|
+
*/
|
|
117
|
+
function BrandedRow({ idPrefix }: { idPrefix: string }) {
|
|
118
|
+
return (
|
|
119
|
+
<Flex direction="col" gap="md">
|
|
120
|
+
<Flex direction="row" gap="sm" wrap align="center">
|
|
121
|
+
<Button type="button">Mở ứng dụng</Button>
|
|
122
|
+
<Button type="button" variant="outline">
|
|
123
|
+
Chi tiết
|
|
124
|
+
</Button>
|
|
125
|
+
<Badge tone="info">12 dịch vụ</Badge>
|
|
126
|
+
<Switch id={`${idPrefix}-switch`} defaultChecked aria-label="Bật thông báo" />
|
|
127
|
+
</Flex>
|
|
128
|
+
<FormField id={`${idPrefix}-input`} label="Mã tra cứu" helper="Bấm vào ô để thấy vòng focus">
|
|
129
|
+
<Input id={`${idPrefix}-input`} defaultValue="GDX-1042" />
|
|
130
|
+
</FormField>
|
|
131
|
+
<Progress value={62} label="Hạn mức đã dùng" />
|
|
132
|
+
</Flex>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default function Demo() {
|
|
137
|
+
const [hex, setHex] = React.useState("#0071bd");
|
|
138
|
+
const [serverForeground, setServerForeground] = React.useState(false);
|
|
139
|
+
|
|
140
|
+
// Cặp do máy chủ cấp — Platform lưu sẵn ở `brands.theme_tokens`. #3b82f6 trên #0071bd chỉ đạt
|
|
141
|
+
// 2.02:1: một cặp THẬT có thể sai, và API phải nói ra chứ không lặng lẽ trả về trắng.
|
|
142
|
+
const seed = tenantTheme(hex, { foreground: serverForeground ? "#3b82f6" : null });
|
|
143
|
+
const invalid = tenantTheme("#nope00");
|
|
144
|
+
|
|
145
|
+
const rows: Row[] = CUSTOMERS.map((customer) => {
|
|
146
|
+
const computed = tenantTheme(customer.hex);
|
|
147
|
+
return {
|
|
148
|
+
hex: customer.hex,
|
|
149
|
+
name: customer.name,
|
|
150
|
+
foreground: computed.foreground ?? "—",
|
|
151
|
+
ratio: computed.contrast.toFixed(2),
|
|
152
|
+
meetsAA: computed.meetsAA,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const declarations = Object.entries(seed.vars)
|
|
157
|
+
.map(([name, value]) => ` ${name}: ${value};`)
|
|
158
|
+
.join("\n");
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<PageContainer
|
|
162
|
+
title="Màu thương hiệu của khách · một vùng, lúc chạy"
|
|
163
|
+
subtitle="tenantTheme(hex) → khai báo token cho MỘT vùng. Cặp màu chữ chọn bằng WCAG 2.2 SC 1.4.3; hover/pressed là triplet thật nên không im lặng trên engine thiếu relative colour."
|
|
164
|
+
>
|
|
165
|
+
<Flex direction="col" gap="lg">
|
|
166
|
+
<Card>
|
|
167
|
+
<CardHeader>
|
|
168
|
+
<CardTitle level={2}>Màu khách chọn</CardTitle>
|
|
169
|
+
<CardDescription>
|
|
170
|
+
Hex — đúng thứ `ColorPicker` trả về và đúng thứ mọi kho tiêu thụ lưu. Ba hoặc sáu chữ
|
|
171
|
+
số; giá trị hỏng bị bỏ qua chứ không làm vỡ trang.
|
|
172
|
+
</CardDescription>
|
|
173
|
+
</CardHeader>
|
|
174
|
+
<CardContent>
|
|
175
|
+
<Flex direction="col" gap="md">
|
|
176
|
+
<FormField id="tenant-hex" label="Màu chính của khách">
|
|
177
|
+
<ColorPicker id="tenant-hex" value={hex} onValueChange={setHex} />
|
|
178
|
+
</FormField>
|
|
179
|
+
<Flex direction="row" gap="sm" wrap>
|
|
180
|
+
{CUSTOMERS.map((customer) => (
|
|
181
|
+
<Button
|
|
182
|
+
key={customer.hex}
|
|
183
|
+
type="button"
|
|
184
|
+
variant="outline"
|
|
185
|
+
size="sm"
|
|
186
|
+
onClick={() => setHex(customer.hex)}
|
|
187
|
+
>
|
|
188
|
+
{customer.name}
|
|
189
|
+
</Button>
|
|
190
|
+
))}
|
|
191
|
+
</Flex>
|
|
192
|
+
<Flex direction="row" gap="md" align="center" wrap>
|
|
193
|
+
<Swatch color={seed.primary ?? "#ffffff"} aria-label={`Màu nền: ${seed.primary}`} />
|
|
194
|
+
<Swatch
|
|
195
|
+
color={seed.foreground ?? "#000000"}
|
|
196
|
+
aria-label={`Màu chữ: ${seed.foreground}`}
|
|
197
|
+
/>
|
|
198
|
+
<Text as="span" size="sm">
|
|
199
|
+
Tương phản đo được{" "}
|
|
200
|
+
<Text as="strong" weight="medium" tabular>
|
|
201
|
+
{seed.contrast.toFixed(2)}:1
|
|
202
|
+
</Text>{" "}
|
|
203
|
+
(ngưỡng AA {AA_NORMAL_TEXT}:1)
|
|
204
|
+
</Text>
|
|
205
|
+
<Badge tone={seed.meetsAA ? "success" : "destructive"}>
|
|
206
|
+
{seed.meetsAA ? "Đạt WCAG 2.2 AA" : "KHÔNG đạt WCAG 2.2 AA"}
|
|
207
|
+
</Badge>
|
|
208
|
+
</Flex>
|
|
209
|
+
<Flex direction="row" gap="sm" align="center">
|
|
210
|
+
<Switch
|
|
211
|
+
id="server-foreground"
|
|
212
|
+
checked={serverForeground}
|
|
213
|
+
onCheckedChange={setServerForeground}
|
|
214
|
+
/>
|
|
215
|
+
<Text as="label" size="sm" htmlFor="server-foreground">
|
|
216
|
+
Dùng cặp do máy chủ cấp (#3b82f6) — một cặp THẬT nhưng dưới ngưỡng
|
|
217
|
+
</Text>
|
|
218
|
+
</Flex>
|
|
219
|
+
{!seed.meetsAA ? (
|
|
220
|
+
<Callout kind="warning">
|
|
221
|
+
<Callout.Title>API nói KHÔNG, kèm số</Callout.Title>
|
|
222
|
+
<Callout.Description>
|
|
223
|
+
Cặp được cấp đo {seed.contrast.toFixed(2)}:1 trên {seed.primary} — dưới 4.5:1
|
|
224
|
+
của WCAG 2.2 SC 1.4.3. Gói KHÔNG lặng lẽ đổi sang trắng: nó giữ đúng cặp bạn
|
|
225
|
+
cấp, trả `meetsAA: false`, và cảnh báo ở bản dev. Bỏ trống `foreground` để gói
|
|
226
|
+
tự chọn màu chữ an toàn.
|
|
227
|
+
</Callout.Description>
|
|
228
|
+
</Callout>
|
|
229
|
+
) : null}
|
|
230
|
+
</Flex>
|
|
231
|
+
</CardContent>
|
|
232
|
+
</Card>
|
|
233
|
+
|
|
234
|
+
<ResponsiveGrid columns={2}>
|
|
235
|
+
{/* TRONG vùng — đúng một thẻ cha mang data-tenant + style */}
|
|
236
|
+
<Card data-tenant="customer" style={seed.vars}>
|
|
237
|
+
<CardHeader>
|
|
238
|
+
<CardTitle level={2}>Trong vùng của khách</CardTitle>
|
|
239
|
+
<CardDescription>
|
|
240
|
+
`<div data-tenant="…" style={tenantTheme(hex).vars}>`. Rê chuột và
|
|
241
|
+
giữ chuột trên nút. Hover và pressed đi theo màu khách, không phải xanh mặc định.
|
|
242
|
+
</CardDescription>
|
|
243
|
+
</CardHeader>
|
|
244
|
+
<CardContent>
|
|
245
|
+
<BrandedRow idPrefix="inside" />
|
|
246
|
+
</CardContent>
|
|
247
|
+
</Card>
|
|
248
|
+
|
|
249
|
+
{/* NGOÀI vùng — cùng markup, không style */}
|
|
250
|
+
<Card>
|
|
251
|
+
<CardHeader>
|
|
252
|
+
<CardTitle level={2}>Ngoài vùng</CardTitle>
|
|
253
|
+
<CardDescription>
|
|
254
|
+
Cùng một hàng component, không thẻ cha nào mang khai báo. Vẫn là theme của ứng dụng
|
|
255
|
+
— đây là bằng chứng phạm vi, không phải ảnh chụp thứ hai.
|
|
256
|
+
</CardDescription>
|
|
257
|
+
</CardHeader>
|
|
258
|
+
<CardContent>
|
|
259
|
+
<BrandedRow idPrefix="outside" />
|
|
260
|
+
</CardContent>
|
|
261
|
+
</Card>
|
|
262
|
+
</ResponsiveGrid>
|
|
263
|
+
|
|
264
|
+
<Card>
|
|
265
|
+
<CardHeader>
|
|
266
|
+
<CardTitle level={2}>Khai báo mà vùng đang mang</CardTitle>
|
|
267
|
+
<CardDescription>
|
|
268
|
+
Năm dòng, không hơn. `--ring` có mặt vì `derived.css` khai `--ring: var(--primary)` ở
|
|
269
|
+
`:root`, nơi var() thay thế MỘT LẦN. Một scope dưới `<html>` thừa kế vòng focus
|
|
270
|
+
của root nếu không khai lại (docs/TOKENS.md · freeze rule).
|
|
271
|
+
</CardDescription>
|
|
272
|
+
</CardHeader>
|
|
273
|
+
<CardContent>
|
|
274
|
+
<CodeBlock language="css" wrap={false}>
|
|
275
|
+
{`[data-tenant="customer"] {\n${declarations}\n}`}
|
|
276
|
+
</CodeBlock>
|
|
277
|
+
</CardContent>
|
|
278
|
+
</Card>
|
|
279
|
+
|
|
280
|
+
<Card>
|
|
281
|
+
<CardHeader>
|
|
282
|
+
<CardTitle level={2}>Bảng tương phản · màu khách thật</CardTitle>
|
|
283
|
+
<CardDescription>
|
|
284
|
+
Màu chữ được chọn bằng độ chói tương đối của WCAG, không phải hằng số. Hai hàng đáng
|
|
285
|
+
nhìn nhất: #FFD400 nhận chữ ĐEN (quy tắc "luôn trắng" chỉ đạt 1.29:1) và #0A1F44 nhận
|
|
286
|
+
chữ TRẮNG (quy tắc "luôn đen" chỉ đạt 1.29:1).
|
|
287
|
+
</CardDescription>
|
|
288
|
+
</CardHeader>
|
|
289
|
+
<CardContent flush>
|
|
290
|
+
<DataTable
|
|
291
|
+
columns={columns}
|
|
292
|
+
data={rows}
|
|
293
|
+
getRowId={(row) => row.hex}
|
|
294
|
+
label="Bảng tương phản màu khách"
|
|
295
|
+
/>
|
|
296
|
+
</CardContent>
|
|
297
|
+
</Card>
|
|
298
|
+
|
|
299
|
+
<Card>
|
|
300
|
+
<CardHeader>
|
|
301
|
+
<CardTitle level={2}>Ca đã chạy: hex không hợp lệ</CardTitle>
|
|
302
|
+
</CardHeader>
|
|
303
|
+
<CardContent>
|
|
304
|
+
<Flex direction="col" gap="md">
|
|
305
|
+
<Text as="p" size="sm" tone="muted">
|
|
306
|
+
`tenantTheme("#nope00")` trả `vars` RỖNG ({Object.keys(invalid.vars).length} khai
|
|
307
|
+
báo), nên vùng dưới đây giữ nguyên theme của ứng dụng thay vì vỡ. Cùng hợp đồng
|
|
308
|
+
`ColorPicker` đã có: hex hỏng bị bỏ qua, không ném lỗi.
|
|
309
|
+
</Text>
|
|
310
|
+
<div data-tenant="broken" style={invalid.vars}>
|
|
311
|
+
<BrandedRow idPrefix="broken" />
|
|
312
|
+
</div>
|
|
313
|
+
</Flex>
|
|
314
|
+
</CardContent>
|
|
315
|
+
</Card>
|
|
316
|
+
|
|
317
|
+
<Card>
|
|
318
|
+
<CardHeader>
|
|
319
|
+
<CardTitle level={2}>Mực thương hiệu · thứ gói KHÔNG tự động đổi</CardTitle>
|
|
320
|
+
</CardHeader>
|
|
321
|
+
<CardContent>
|
|
322
|
+
<Callout kind="important">
|
|
323
|
+
<Callout.Title>
|
|
324
|
+
`--text-link` / `--text-brand` / `--text-primary` là việc của bạn
|
|
325
|
+
</Callout.Title>
|
|
326
|
+
<Callout.Description>
|
|
327
|
+
Chúng là mực trên NỀN TRANG, không phải nhãn trên nền nút, nên độ đọc phụ thuộc
|
|
328
|
+
`--background` — thứ màu của khách không quyết định. Một seed nhạt như #FFD400 cho
|
|
329
|
+
link ~1.4:1 trên nền trắng. `tenantTheme` để yên ba vai đó; nếu bạn muốn link mang
|
|
330
|
+
màu thương hiệu, hãy tự đặt chúng SAU KHI đo bằng `contrastRatio()`.
|
|
331
|
+
</Callout.Description>
|
|
332
|
+
</Callout>
|
|
333
|
+
</CardContent>
|
|
334
|
+
</Card>
|
|
335
|
+
</Flex>
|
|
336
|
+
</PageContainer>
|
|
337
|
+
);
|
|
338
|
+
}
|