@comitor/ui 0.2.1 → 0.3.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 +236 -30
- package/dist/chart/chart-colors.d.ts +56 -0
- package/dist/chart/chart-colors.js +21 -0
- package/dist/chart/chart-colors.js.map +1 -0
- package/dist/chart/chart.d.ts +40 -0
- package/dist/chart/chart.js.map +1 -1
- package/dist/chart/comitor-chart.d.ts +21 -43
- package/dist/chart/comitor-chart.js +13 -24
- package/dist/chart/comitor-chart.js.map +1 -1
- package/dist/chart/index.d.ts +2 -1
- package/dist/chart/index.js +1 -0
- package/dist/chart/index.js.map +1 -1
- package/dist/components/composite/data-table-constants.d.ts +40 -0
- package/dist/components/composite/data-table-constants.js +13 -0
- package/dist/components/composite/data-table-constants.js.map +1 -0
- package/dist/components/composite/data-table.d.ts +2 -27
- package/dist/components/composite/data-table.js +1 -10
- package/dist/components/composite/data-table.js.map +1 -1
- package/dist/components/composite/date-picker-constants.d.ts +21 -0
- package/dist/components/composite/date-picker-constants.js +39 -0
- package/dist/components/composite/date-picker-constants.js.map +1 -0
- package/dist/components/composite/date-picker.d.ts +2 -18
- package/dist/components/composite/date-picker.js +6 -48
- package/dist/components/composite/date-picker.js.map +1 -1
- package/dist/components/composite/form-field-constants.d.ts +26 -0
- package/dist/components/composite/form-field-constants.js +13 -0
- package/dist/components/composite/form-field-constants.js.map +1 -0
- package/dist/components/composite/form-field.d.ts +1 -13
- package/dist/components/composite/form-field.js +1 -11
- package/dist/components/composite/form-field.js.map +1 -1
- package/dist/components/composite/search-filter-bar-constants.d.ts +52 -0
- package/dist/components/composite/search-filter-bar-constants.js +18 -0
- package/dist/components/composite/search-filter-bar-constants.js.map +1 -0
- package/dist/components/composite/search-filter-bar.d.ts +3 -37
- package/dist/components/composite/search-filter-bar.js +2 -15
- package/dist/components/composite/search-filter-bar.js.map +1 -1
- package/dist/components/composite/table-pagination-constants.d.ts +56 -0
- package/dist/components/composite/table-pagination-constants.js +18 -0
- package/dist/components/composite/table-pagination-constants.js.map +1 -0
- package/dist/components/composite/table-pagination.d.ts +2 -43
- package/dist/components/composite/table-pagination.js +4 -15
- package/dist/components/composite/table-pagination.js.map +1 -1
- package/dist/components/ui/badge.js +3 -3
- package/dist/components/ui/badge.js.map +1 -1
- package/dist/components/ui/button.js +4 -4
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/calendar.js +1 -1
- package/dist/components/ui/calendar.js.map +1 -1
- package/dist/components/ui/checkbox.js +2 -2
- package/dist/components/ui/checkbox.js.map +1 -1
- package/dist/components/ui/command.js +1 -1
- package/dist/components/ui/command.js.map +1 -1
- package/dist/components/ui/input-group.js +1 -1
- package/dist/components/ui/input-group.js.map +1 -1
- package/dist/components/ui/input-otp.js +1 -1
- package/dist/components/ui/input-otp.js.map +1 -1
- package/dist/components/ui/navigation-menu.js +1 -1
- package/dist/components/ui/navigation-menu.js.map +1 -1
- package/dist/components/ui/radio-group.js +1 -1
- package/dist/components/ui/radio-group.js.map +1 -1
- package/dist/components/ui/resizable.js +1 -1
- package/dist/components/ui/resizable.js.map +1 -1
- package/dist/components/ui/slider.js +1 -1
- package/dist/components/ui/slider.js.map +1 -1
- package/dist/components/ui/switch.js +1 -1
- package/dist/components/ui/switch.js.map +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/textarea.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/shell/contrast-constants.d.ts +30 -0
- package/dist/shell/contrast-constants.js +16 -0
- package/dist/shell/contrast-constants.js.map +1 -0
- package/dist/shell/contrast-provider.d.ts +60 -0
- package/dist/shell/contrast-provider.js +61 -0
- package/dist/shell/contrast-provider.js.map +1 -0
- package/dist/shell/contrast-toggle.d.ts +24 -0
- package/dist/shell/contrast-toggle.js +29 -0
- package/dist/shell/contrast-toggle.js.map +1 -0
- package/dist/shell/index.d.ts +3 -0
- package/dist/shell/index.js +3 -0
- package/dist/shell/index.js.map +1 -1
- package/dist/shell/shell-context.js +3 -2
- package/dist/shell/shell-context.js.map +1 -1
- package/dist/tokens.d.ts +170 -70
- package/dist/tokens.js +124 -47
- package/dist/tokens.js.map +1 -1
- package/package.json +3 -2
- package/styles.css +255 -120
package/README.md
CHANGED
|
@@ -53,6 +53,11 @@ pnpm add react-hook-form # cho @comitor/ui/form
|
|
|
53
53
|
>
|
|
54
54
|
> `lucide-react` cũng là `>=` vì cùng lý do: thang phiên bản của nó nhảy `0.x` → `1.x` mà API
|
|
55
55
|
> icon không đổi. Đã kiểm typecheck + build với `1.33.0`.
|
|
56
|
+
>
|
|
57
|
+
> **Hiệu ứng vẽ dần của recharts 2.15.x hỏng với React 19.2**, nên `ComitorAreaChart` /
|
|
58
|
+
> `ComitorBarChart` / `ComitorLineChart` mặc định **tắt** hiệu ứng từ `0.2.2` (§4.4). Nếu tự lắp
|
|
59
|
+
> recharts trong `<ChartContainer>` thì gói không tắt hộ được — app phải tự ghi
|
|
60
|
+
> `isAnimationActive={false}` lên từng `<Bar>`/`<Line>`/`<Area>` (§4.4 mục 1b).
|
|
56
61
|
|
|
57
62
|
## 2. Nạp `styles.css` — ĐỌC KỸ, đây là chỗ dễ sai nhất
|
|
58
63
|
|
|
@@ -334,11 +339,13 @@ Luật khi đã có nó:
|
|
|
334
339
|
> sang render phía client khi build tĩnh. Cái giá đó phải do app chọn, không phải hệ quả của việc
|
|
335
340
|
> import shell.
|
|
336
341
|
|
|
337
|
-
>
|
|
338
|
-
>
|
|
339
|
-
>
|
|
340
|
-
>
|
|
341
|
-
>
|
|
342
|
+
> **Từ `0.2.2`, cả 5 entry — kể cả `/shell` — nạp được bằng Node ESM thuần.** Ở `0.2.1` thì không:
|
|
343
|
+
> `next@16` không khai `exports` map, nên với Node thì `next/link` chỉ là một đường dẫn file thiếu
|
|
344
|
+
> đuôi và `import("@comitor/ui/shell")` chết ngay lúc nạp
|
|
345
|
+
> (`ERR_MODULE_NOT_FOUND … Did you mean next/link.js?`), trong khi app Next không hề hấn vì bundler
|
|
346
|
+
> tự thêm đuôi. Nay shell ghi thẳng `next/link.js` / `next/navigation.js` — **cùng một file** mà
|
|
347
|
+
> bundler vẫn resolve tới, nên client-side navigation và `usePathname()` không đổi.
|
|
348
|
+
> `pnpm smoke:entries` canh lại điều này ở mỗi lần publish.
|
|
342
349
|
|
|
343
350
|
### 4.3 Token ngoài Tailwind
|
|
344
351
|
|
|
@@ -359,11 +366,130 @@ const { background, foreground } = getAvatarToneColors("Nguyễn Đức Thành",
|
|
|
359
366
|
> Tailwind → trả hex đục đã trộn sẵn tint). Cùng bảng tone, cùng hàm băm, nên một người ra một màu ở cả
|
|
360
367
|
> hai nơi. **Đừng** tự băm tên rồi bốc màu từ `accent` — đó là đường ngắn nhất để web và email lệch màu.
|
|
361
368
|
|
|
369
|
+
### 4.4 Biểu đồ, và hằng của gói khi dùng ở Server Component
|
|
370
|
+
|
|
371
|
+
Hai điều phải biết trước khi vẽ biểu đồ đầu tiên. Cả hai đều do app mẫu `comitor-starter` phát
|
|
372
|
+
hiện bằng cách *dùng thật*, và cả hai đều IM LẶNG — không có lỗi, không có warning. Mục 1 có **hai
|
|
373
|
+
nửa**: một nửa gói tự lo (`Comitor*Chart`), một nửa app phải tự lo (`ChartContainer` + recharts tự
|
|
374
|
+
lắp) — đừng đọc nửa đầu rồi bỏ nửa sau.
|
|
375
|
+
|
|
376
|
+
**1. Hiệu ứng vẽ dần mặc định TẮT (từ `0.2.2`).** Với `recharts` 2.15.x + React 19.2, hiệu ứng
|
|
377
|
+
không bao giờ chạy tới khung hình cuối, nên mark đứng nguyên ở khung đầu — tức là vô hình. Đo trên
|
|
378
|
+
DOM thật, ba dạng cùng một lỗi:
|
|
379
|
+
|
|
380
|
+
| | biểu hiện trong SVG | thấy trên màn hình |
|
|
381
|
+
|---|---|---|
|
|
382
|
+
| `<Bar>` | `<g class="recharts-bar-rectangle">` **rỗng** (`Rectangle` trả `null` khi cao 0) | không có cột |
|
|
383
|
+
| `<Line>` | path có `d` đúng nhưng `stroke-dasharray="0px 634px"` | không có đường |
|
|
384
|
+
| `<Area>` | path có `d` đúng nhưng `<clipPath><rect width="0">` | không có vùng |
|
|
385
|
+
|
|
386
|
+
Kết quả giống hệt nhau: đủ trục, đủ lưới, đủ chú giải, KHÔNG có dữ liệu — và chờ bao lâu cũng
|
|
387
|
+
không tự hết. Nay ba component nhận prop **optional** `isAnimationActive`, mặc định `false`:
|
|
388
|
+
|
|
389
|
+
```tsx
|
|
390
|
+
<ComitorBarChart data={data} xAxisKey="thang" series={series} /> // hiện ngay
|
|
391
|
+
<ComitorBarChart data={data} xAxisKey="thang" series={series} isAnimationActive /> // bật lại
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Bật lại chỉ nên làm khi app chạy **recharts 3.x**; trên 2.15.x nó dựng lại đúng cái biểu đồ trống
|
|
395
|
+
ở trên.
|
|
396
|
+
|
|
397
|
+
**1b. Tự lắp recharts (`ChartContainer`) thì PHẢI TỰ TẮT — gói không đặt hộ được.** Mặc định mới ở
|
|
398
|
+
trên chỉ có hiệu lực bên trong ba wrapper `Comitor*Chart`. Entry `/chart` còn một đường dùng thứ
|
|
399
|
+
hai, đúng lối shadcn quen thuộc: `<ChartContainer>` + recharts do app tự lắp. Ở đường đó
|
|
400
|
+
`<Bar>`/`<Line>`/`<Area>` là **children do app viết**, container không sờ vào được, nên trên
|
|
401
|
+
`recharts` 2.15.x app gặp lại y nguyên lỗi im lặng của `0.2.1` — đủ trục, đủ lưới, KHÔNG có dữ liệu:
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
"use client";
|
|
405
|
+
import { ChartContainer } from "@comitor/ui/chart";
|
|
406
|
+
import { Bar, BarChart, XAxis } from "recharts";
|
|
407
|
+
|
|
408
|
+
// ✗ recharts 2.15.x — <g class="recharts-bar-rectangle"> rỗng, không cột nào
|
|
409
|
+
<ChartContainer config={{ doanhThu: { label: "Doanh thu", color: "var(--chart-1)" } }}>
|
|
410
|
+
<BarChart data={data}>
|
|
411
|
+
<XAxis dataKey="thang" />
|
|
412
|
+
<Bar dataKey="doanhThu" fill="var(--color-doanhThu)" />
|
|
413
|
+
</BarChart>
|
|
414
|
+
</ChartContainer>
|
|
415
|
+
|
|
416
|
+
// ✓ tắt tay, trên TỪNG mark
|
|
417
|
+
<Bar dataKey="doanhThu" fill="var(--color-doanhThu)" isAnimationActive={false} />
|
|
418
|
+
<Line dataKey="doanhThu" stroke="var(--color-doanhThu)" isAnimationActive={false} />
|
|
419
|
+
<Area dataKey="doanhThu" fill="var(--color-doanhThu)" isAnimationActive={false} />
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Đo trên DOM thật (Next 16.3.2 · React 19.2.8 · recharts 2.15.4, `next build` + `next start`, chờ
|
|
423
|
+
5000ms sau hydrate), cùng một trang chỉ khác một prop:
|
|
424
|
+
|
|
425
|
+
| mark trong `<ChartContainer>` | thiếu prop | có `isAnimationActive={false}` |
|
|
426
|
+
|---|---|---|
|
|
427
|
+
| `<Bar>` | 3 `<g.recharts-bar-rectangle>`, **0** `path.recharts-rectangle` | **3/3** path, cao 273/410/342px |
|
|
428
|
+
| `<Line>` | `stroke-dasharray="0px 818.84px"` | `stroke-dasharray` = `null` |
|
|
429
|
+
| `<Area>` | `<clipPath><rect width="0">` | không còn rect `width="0"` |
|
|
430
|
+
|
|
431
|
+
Trên **recharts 3.x** thì bỏ prop đi cũng được — lỗi không tồn tại ở đó. Nhưng dải peer của gói là
|
|
432
|
+
`>=2.15.0`, nên cứ ghi `isAnimationActive={false}`: trên 3.x nó chỉ còn nghĩa "không hoạt hình".
|
|
433
|
+
|
|
434
|
+
**2. `CHART_COLORS` (và mọi hằng khác của gói) đọc được từ Server Component.** Ở `0.2.1` thì
|
|
435
|
+
không: hai hằng đó export từ `chart/comitor-chart.tsx`, mà file đó có `"use client"`. Bundler RSC
|
|
436
|
+
cắt ranh giới theo FILE, nên mọi export của một module client tới phía server đều là
|
|
437
|
+
client-reference proxy — `CHART_COLORS.gold` ra `undefined`, **không throw, không warning**, trong
|
|
438
|
+
khi TypeScript vẫn khai kiểu literal. Kịch bản gãy đúng như JSDoc của gói đang khuyên viết:
|
|
439
|
+
|
|
440
|
+
```tsx
|
|
441
|
+
// page.tsx — Server Component
|
|
442
|
+
const series = [{ key: "loi", color: CHART_COLORS.red }]; // 0.2.1: color = undefined (im lặng)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`0.2.2` chuyển **10 hằng** sang module không có directive (`chart/chart-colors.ts`,
|
|
446
|
+
`components/composite/*-constants.ts`) và cho barrel xuất thẳng từ đó. Tên export không đổi, chỗ
|
|
447
|
+
import không đổi:
|
|
448
|
+
|
|
449
|
+
| Đọc từ Server Component | `0.2.1` | `0.2.2` |
|
|
450
|
+
|---|---|---|
|
|
451
|
+
| `CHART_COLORS.gold` | `undefined` | `"var(--chart-1)"` |
|
|
452
|
+
| `CHART_SERIES_COLORS[0]` | `undefined` | `"var(--chart-1)"` |
|
|
453
|
+
| `DEFAULT_DATA_TABLE_LABELS.emptyTitle` | `undefined` | `"Chưa có dữ liệu"` |
|
|
454
|
+
| `VI_DATE_FORMAT` | `undefined` | `"dd/MM/yyyy"` |
|
|
455
|
+
| `DEFAULT_DATE_PRESETS.length` | `0` | `3` |
|
|
456
|
+
| `DEFAULT_DATE_RANGE_PRESETS.length` | `0` | `5` |
|
|
457
|
+
| `DEFAULT_PAGE_SIZE_OPTIONS[0]` | `undefined` | `20` |
|
|
458
|
+
| `SELECT_EMPTY_VALUE` | `undefined` | `"__empty__"` |
|
|
459
|
+
| `DEFAULT_TABLE_PAGINATION_LABELS.region` | `undefined` | `"Phân trang"` |
|
|
460
|
+
| `DEFAULT_SEARCH_FILTER_BAR_LABELS.searchPlaceholder` | `undefined` | `"Tìm kiếm…"` |
|
|
461
|
+
|
|
462
|
+
`toSelectValue` / `fromSelectValue` đi cùng `SELECT_EMPTY_VALUE`, nên nay gọi được trong Server
|
|
463
|
+
Action. `pnpm verify:directives` canh không cho hằng thuần lọt ngược vào file `"use client"`.
|
|
464
|
+
|
|
465
|
+
> **Vẫn còn nằm sau ranh giới client — 13 tên, và tất cả đều BÁO LỖI TO, không im lặng.** Chúng là
|
|
466
|
+
> hàm export từ file `"use client"`; gọi ở Server Component ném `Attempted to call <tên>() from the
|
|
467
|
+
> server but <tên> is on the client`. Đã kiểm bằng trang Server Component prerender thật
|
|
468
|
+
> (Next 16.3.2, `next build`, đọc HTML sinh ra trên đĩa): **13/13 ném đúng lỗi đó, 10/10 đối chứng
|
|
469
|
+
> chạy bình thường**.
|
|
470
|
+
>
|
|
471
|
+
> | | tên | vì sao vẫn ở đó |
|
|
472
|
+
> |---|---|---|
|
|
473
|
+
> | dựng chuỗi class (`cva`) | `buttonVariants`, `toggleVariants`, `fieldVariants`, `inputGroupAddonVariants`, `inputGroupButtonVariants` | sống cùng file với component client tương ứng |
|
|
474
|
+
> | gọi lệnh toast | `toast`, `dismissToast`, `toastPromise` | vốn chỉ có nghĩa trong trình duyệt |
|
|
475
|
+
> | hook | `useIsMac`, `useCarousel`, `useShell` (`/shell`), `useChart` (`/chart`), `useFormField` (`/form`) | hook thì đương nhiên client |
|
|
476
|
+
>
|
|
477
|
+
> Nhóm đầu là nhóm dễ vấp nhất, vì chúng chỉ ghép chuỗi class — không có lý do trực giác nào để
|
|
478
|
+
> chúng không chạy ở trang server. Muốn dựng class nút cho một `<a>` ở trang server thì để phần đó
|
|
479
|
+
> trong một client component.
|
|
480
|
+
>
|
|
481
|
+
> Chín helper `cva` còn lại **KHÔNG** kẹt — `alertVariants`, `badgeVariants`, `buttonGroupVariants`,
|
|
482
|
+
> `cardVariants`, `emptyMediaVariants`, `itemVariants`, `itemMediaVariants`,
|
|
483
|
+
> `navigationMenuTriggerStyle`, `statCardIconVariants`, cùng `cn`, đều gọi được từ Server Component
|
|
484
|
+
> (đã đo cùng lượt, cùng trang). Danh sách trên là đầy đủ, không phải trích: nó dựng từ đồ thị
|
|
485
|
+
> re-export của cả 5 entry, lọc mọi tên có giá trị runtime mà khai báo nằm trong file `"use client"`.
|
|
486
|
+
|
|
362
487
|
## 5. Bảng component
|
|
363
488
|
|
|
364
|
-
Số liệu thực tế của `0.2.
|
|
489
|
+
Số liệu thực tế của `0.2.2`: **51 file primitive · 29 file composite · 18 file shell · 3 file chart**
|
|
365
490
|
(chưa kể barrel) — số tên xuất ra theo entry: `.` **637** · `/shell` **70** · `/chart` **29** · `/form`
|
|
366
|
-
**15** · `/tokens` **15** (xem §3 cho các tên xuất hiện ở nhiều entry).
|
|
491
|
+
**15** · `/tokens` **15** (xem §3 cho các tên xuất hiện ở nhiều entry). `0.2.2` **không thêm/bớt tên
|
|
492
|
+
nào**; số file tăng vì hằng dữ liệu được tách sang module riêng (§4.4). Bảng dưới liệt kê component gốc;
|
|
367
493
|
các thành phần con (`CardHeader`, `DialogFooter`, `SelectItem`…) export kèm theo file tương ứng.
|
|
368
494
|
|
|
369
495
|
### Tier 1 — primitive (`@comitor/ui`)
|
|
@@ -458,7 +584,8 @@ Kèm 2 module thuần (không phải component, dùng được cả hai phía):
|
|
|
458
584
|
| `AppLauncher`, `AppLauncherPanel` | **Trục 2** — lưới 3×3 đổi sản phẩm; app chưa mua hiện khoá và gọi `onUpsell` |
|
|
459
585
|
| `AppHeader`, `Sidebar`, `SidebarGroup`, `SidebarMenuItem`, `MobileMenu` | Thành phần khung điều hướng |
|
|
460
586
|
| `UserMenu`, `UserAvatar`, `NotificationsButton` | Vùng phải của header |
|
|
461
|
-
| `ThemeProvider`, `ThemeToggle`, `SonnerToaster` | Theme (next-themes) + toast bám theme |
|
|
587
|
+
| `ThemeProvider`, `ThemeToggle`, `SonnerToaster` | Theme sáng/tối (next-themes) + toast bám theme |
|
|
588
|
+
| `ContrastProvider`, `ContrastToggle`, `useContrast`, `ContrastScript` | **Trục thứ hai của giao diện**: chọn BẢNG MÀU (bản duyệt comitor-ds ⇄ bản tương phản cao). Độc lập với sáng/tối; lưu ở localStorage `comitor-contrast`. Provider tự render script chống nháy — xem §6.0 |
|
|
462
589
|
| `ShellNavLink` | Link nội bộ của shell; thay `next/link` bằng `LinkComponent` khi không chạy Next |
|
|
463
590
|
| `ComitorLogo`, `ComitorLockup` | Vector brand — **không** có `"use client"`, RSC render thẳng (hex trong SVG là brand book, không tokenize) |
|
|
464
591
|
| `DEFAULT_SHELL_LABELS` + `utils` | Chuỗi hiển thị mặc định (`ShellLabels`, 32 khoá); helper `flattenNavItems`, `isNavItemActive`, `isNavBranchActive`, `toNavGroups`, `resolveCurrentApp`, `getEntitledApps`, `getLockedApps`, `appAccentStyle`, `normalizeVi`, `getVietnameseInitials` |
|
|
@@ -470,10 +597,11 @@ Hợp đồng dữ liệu (type): `Workspace`, `AppDescriptor`, `NavItem`, `NavG
|
|
|
470
597
|
|
|
471
598
|
| Component | Công dụng |
|
|
472
599
|
|---|---|
|
|
473
|
-
| `ChartContainer`, `ChartTooltip`, `ChartTooltipContent`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `useChart` | Lớp shadcn bọc recharts (giữ tên trần theo API gốc) |
|
|
600
|
+
| `ChartContainer`, `ChartTooltip`, `ChartTooltipContent`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `useChart` | Lớp shadcn bọc recharts (giữ tên trần theo API gốc). ⚠ Tự lắp `<Bar>`/`<Line>`/`<Area>` ở đây thì trên **recharts 2.15.x phải tự ghi `isAnimationActive={false}` lên từng mark** — container không đặt hộ được, quên là biểu đồ trống không báo lỗi (§4.4 mục 1b) |
|
|
474
601
|
| `ComitorAreaChart`, `ComitorBarChart`, `ComitorLineChart` | Bản dựng sẵn theo brand — truyền `data` + `series` là xong |
|
|
475
602
|
| `ComitorChartContainer`, `ComitorChartTooltip` | Bản tiện dụng; **có tiền tố `Comitor`** để không đụng tên trần ở trên |
|
|
476
|
-
| `CHART_COLORS`, `CHART_SERIES_COLORS` | Bảng màu series mặc định |
|
|
603
|
+
| `CHART_COLORS`, `CHART_SERIES_COLORS` | Bảng màu series mặc định — **hằng dữ liệu thuần, đọc được từ Server Component** (§4.4) |
|
|
604
|
+
| `ChartColorName` | Khoá của `CHART_COLORS` |
|
|
477
605
|
|
|
478
606
|
### Form (`@comitor/ui/form`)
|
|
479
607
|
|
|
@@ -530,6 +658,63 @@ trong component.
|
|
|
530
658
|
|
|
531
659
|
Toàn bộ token nằm ở [`styles.css`](./styles.css); bản mirror TS ở [`src/tokens.ts`](./src/tokens.ts).
|
|
532
660
|
|
|
661
|
+
### 6.0 HAI bảng màu — đọc trước §6.1
|
|
662
|
+
|
|
663
|
+
Gói có **hai** bảng màu, cùng một bộ component:
|
|
664
|
+
|
|
665
|
+
| | Bật bằng | Là gì |
|
|
666
|
+
|---|---|---|
|
|
667
|
+
| **Mặc định** | (không cần gì) | Bảng của **comitor-ds** — repo design system đã được duyệt. Đây là nguồn sự thật về hình thức. |
|
|
668
|
+
| **Tương phản cao** | `<html data-contrast="high">` | Bảng đã đo WCAG (chính là bảng của `0.2.x`). Dành cho màn hình độ sáng thấp, màn hình rẻ, hoặc dùng ngoài trời. |
|
|
669
|
+
|
|
670
|
+
Bảng mặc định **cố ý có những cặp dưới ngưỡng** — đó là quyết định của người duyệt, không phải lỗi bỏ
|
|
671
|
+
sót. Danh sách đầy đủ nằm ở khối `NGUỒN SỰ THẬT` đầu `styles.css`; những khoản chính:
|
|
672
|
+
|
|
673
|
+
```
|
|
674
|
+
viền control (--input) trên nền trang 1,16:1 (1.4.11 đòi 3:1)
|
|
675
|
+
rãnh Switch BẬT, gạch active Tabs (gold) 1,86:1 (1.4.11 đòi 3:1)
|
|
676
|
+
--muted-foreground trên --muted 3,58:1 (1.4.3 đòi 4,5:1)
|
|
677
|
+
chữ trắng trên nền --destructive 4,38:1 (1.4.3 đòi 4,5:1)
|
|
678
|
+
Button variant link (--gold-400) 3,15:1 (1.4.3 đòi 4,5:1)
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
Hai ngoại lệ giữ bản sửa ở **cả hai** bảng, vì màu chữ của bản duyệt gần trùng màu nền tới mức không
|
|
682
|
+
đọc nổi trên bất kỳ màn hình nào: `Badge` variant `warning` (1,52:1 → 6,24:1) và `success`
|
|
683
|
+
(2,82:1 → 5,88:1). Nền giữ nguyên của bản duyệt, chỉ đậm chữ.
|
|
684
|
+
|
|
685
|
+
**Cách bật trong app** — hai bước, app không sở hữu màu nào:
|
|
686
|
+
|
|
687
|
+
```tsx
|
|
688
|
+
// app/layout.tsx (Server Component)
|
|
689
|
+
import { ContrastProvider, ThemeProvider } from "@comitor/ui/shell";
|
|
690
|
+
|
|
691
|
+
<html lang="vi" suppressHydrationWarning>
|
|
692
|
+
<body>
|
|
693
|
+
<ThemeProvider>
|
|
694
|
+
<ContrastProvider>{children}</ContrastProvider>
|
|
695
|
+
</ThemeProvider>
|
|
696
|
+
</body>
|
|
697
|
+
</html>
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
// trang cài đặt (client)
|
|
702
|
+
import { ContrastToggle } from "@comitor/ui/shell";
|
|
703
|
+
|
|
704
|
+
<ContrastToggle />
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
`ContrastProvider` tự render script chống nháy trong `<body>` — app **không** phải thêm gì vào
|
|
708
|
+
`<head>`, và **đừng** đặt `<ContrastScript />` vào `<head>`: React 19 báo lỗi console ở mọi lần
|
|
709
|
+
render client (script inline không nằm trong nhóm phần tử React hoisted được).
|
|
710
|
+
|
|
711
|
+
> ⚠ **Điều kiện để hai bảng cùng sống**: mọi component đi qua **token vai trò**
|
|
712
|
+
> (`--control-on`, `--control-edge`, `--control-check`, `--switch-track`, `--slider-track`,
|
|
713
|
+
> `--input-fill`, `--primary-ink`, `--link-ink`…) chứ **không** viết thẳng `bg-gold-300` /
|
|
714
|
+
> `bg-ash-300` / `text-gold-400`. Viết thẳng thang màu là khoá cứng một bảng, và bảng kia lặng lẽ
|
|
715
|
+
> sai. Cổng `pnpm lint:a11y` bắt được lỗi này vì nó đo **bảng tương phản cao**: giá trị viết thẳng
|
|
716
|
+
> không đổi theo bảng nên sẽ hiện ra ở đó đúng bằng con số của bảng mặc định — tức là đỏ.
|
|
717
|
+
|
|
533
718
|
### 6.1 Hợp đồng BA VAI TRÒ của màu — đọc trước khi dùng bất kỳ màu nào
|
|
534
719
|
|
|
535
720
|
Đây là quy tắc quan trọng nhất của gói. Mỗi màu có **ba** biến thể, đọc tên là biết dùng ở đâu:
|
|
@@ -550,9 +735,15 @@ green #2EA56A trên #E0F2E9 = 2,69:1 ✗ → --green-ink #216A48 = 5,60:1
|
|
|
550
735
|
red #D64545 trên #F9E3E3 = 3,57:1 ✗ → --red-ink #9A3537 = 5,83:1 ✓
|
|
551
736
|
```
|
|
552
737
|
|
|
553
|
-
Ở **dark** không có bậc riêng:
|
|
554
|
-
|
|
555
|
-
hai theme
|
|
738
|
+
Ở **dark** không có bậc riêng: `--x-ink` trỏ về `var(--x)`. Nhờ vậy `text-teal-ink` viết **một lần**
|
|
739
|
+
là đúng cả hai theme, không cần biến thể `dark:`. Ở bảng **mặc định**, comitor-ds dùng cùng một hex cho
|
|
740
|
+
cả hai theme nên bậc ink ở dark bằng bậc ở light; ở bảng **tương phản cao**, khối
|
|
741
|
+
`.dark[data-contrast="high"]` nâng `--teal/--green/--red/--yellow` lên bậc sáng và `--x-ink` đi theo.
|
|
742
|
+
|
|
743
|
+
> ⚠ Hợp đồng `-ink` chỉ còn ràng buộc chặt ở các cụm **tonal** mà comitor-ds không có (`Badge`
|
|
744
|
+
> success/warning, `StatusPill`, `LetterAvatar`, `StatCard`, `Toast`) — đó là chỗ chữ đứng trên tint
|
|
745
|
+
> của chính nó và bậc ink là thứ duy nhất giữ chữ đọc được. Với primitive **có** trong comitor-ds, bản
|
|
746
|
+
> duyệt thắng: ở bảng mặc định `--primary-ink` = `--primary` và `--destructive-ink` = `--destructive`.
|
|
556
747
|
|
|
557
748
|
Ba vai trò áp cho mọi họ token:
|
|
558
749
|
|
|
@@ -563,29 +754,36 @@ Ba vai trò áp cho mọi họ token:
|
|
|
563
754
|
| Brand | `--primary` | `--primary-foreground` | `--primary-ink` |
|
|
564
755
|
| Accent theo app | `--app-accent` | `--app-accent-foreground` | `--app-accent-ink` (mặc định `= --primary-ink`) |
|
|
565
756
|
|
|
566
|
-
Brand cũng
|
|
567
|
-
làm ranh giới trạng thái trên nền trang chỉ được **1,86:1
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
757
|
+
Brand cũng có bậc `-ink`, nhưng **giá trị của nó phụ thuộc bảng màu** (§6.0): gold `#E8B824` là màu
|
|
758
|
+
**sáng** nên tô nền thì tuyệt, còn làm ranh giới trạng thái trên nền trang chỉ được **1,86:1**.
|
|
759
|
+
comitor-ds chấp nhận con số đó, nên ở bảng mặc định `--primary-ink` = `--primary`; ở bảng tương phản
|
|
760
|
+
cao nó mượn `--gold-500` `#7A5800` (**6,51:1** ✓ light) và trỏ về `--primary` ở dark (**12,67:1** ✓).
|
|
761
|
+
Đây là màu của rãnh `Switch` khi BẬT và ô `Checkbox` đã chọn — component viết `bg-control-on`, token
|
|
762
|
+
đó trỏ vào `--primary-ink`. `--ring` **cố ý vẫn là** `--primary` ở cả hai bảng: vòng focus thuộc tiêu
|
|
763
|
+
chí AAA của WCAG 2.2, ngoài mục tiêu của gói.
|
|
571
764
|
|
|
572
|
-
|
|
765
|
+
Các lỗi hay gặp nhất, và cách sửa. **Số đo trong bảng là của bảng TƯƠNG PHẢN CAO** — đó là bảng có hợp
|
|
766
|
+
đồng; ở bảng mặc định vài cặp thấp hơn theo đúng bản duyệt (§6.0):
|
|
573
767
|
|
|
574
768
|
| Viết sai | Hỏng ở đâu | Viết đúng |
|
|
575
769
|
|---|---|---|
|
|
576
770
|
| `text-red` cho dòng báo lỗi | 4,38:1 ở light ✗; ở dark thì đây là thang brand thô | `text-destructive-ink` |
|
|
577
771
|
| `text-success` / `text-info` / `text-warning` cho chữ hoặc icon trạng thái | Token TÔ NỀN: trên tint `/15` của chính nó chỉ 2,69:1 / 3,44:1 ✗ ở light; trên nền trang 3,13:1 / 4,14:1 ✗ | `text-success-ink` / `text-info-ink` / `text-warning-ink` (5,5–6,7:1 light · 7,1–12,4:1 dark) |
|
|
578
|
-
| `bg-primary` làm rãnh `Switch` khi BẬT
|
|
579
|
-
| `
|
|
772
|
+
| `bg-primary` / `bg-gold-300` làm rãnh `Switch` khi BẬT | Khoá cứng một bảng màu: bảng tương phản cao không đổi được nó nữa | `bg-control-on` |
|
|
773
|
+
| `bg-ash-300` làm rãnh `Switch` khi TẮT, `bg-muted` làm rãnh `Slider` | Cùng lý do — thang màu thô không theo bảng | `bg-switch-track` / `bg-slider-track` |
|
|
774
|
+
| `text-ink` làm dấu check của `Checkbox` | Ở bảng tương phản cao nền ô là gold-500 TỐI, dấu mực chỉ 1,86:1 ✗ | `text-control-check` |
|
|
775
|
+
| `border-border` làm viền của control | `--border` là ranh giới **TRANG TRÍ**; `--control-edge` mới là thứ bảng tương phản cao kéo đậm lên | `border-input` (= `--control-edge`) |
|
|
776
|
+
| `bg-input/30` làm lớp tô ô nhập ở dark | Ở bảng tương phản cao `--input` là #7E8290, tô 30% ra mảng xám sáng làm placeholder tụt còn 3,22:1 ✗ | `dark:bg-input-fill` |
|
|
580
777
|
| `text-app-accent` cho icon/vạch active | accent mặc định là gold `#E8B824` → 1,7–1,9:1 trên nền sidebar ✗ | `text-app-accent-ink`, hoặc nền ĐẶC + `text-app-accent-foreground` |
|
|
581
|
-
| `text-destructive` cho chữ trên nền trang | `--destructive` là màu **NỀN
|
|
778
|
+
| `text-destructive` cho chữ trên nền trang | `--destructive` là màu **NỀN**; ở bảng tương phản cao nó là bậc đậm cho chữ trắng đứng lên và làm chữ thì chỉ 3,76:1 ✗ ở dark | `text-destructive-ink` (7,15:1 light / 6,42:1 dark ở bảng tương phản cao; = `--destructive` ở bảng mặc định, đúng `text-destructive` của comitor-ds) |
|
|
582
779
|
|
|
583
780
|
> ⚠ **App ghi đè `--app-accent` thì PHẢI set kèm `--app-accent-ink`** (và `--app-accent-foreground` nếu
|
|
584
781
|
> accent sáng). Gói cố ý **không** dẫn xuất `-ink` bằng `color-mix` từ `--app-accent`: dẫn xuất tự động
|
|
585
782
|
> cho ra con số tương phản không ai kiểm, và mỗi màu nền cần một hệ số khác nhau.
|
|
586
783
|
>
|
|
587
|
-
> ⚠ **Ghi đè `--primary`
|
|
588
|
-
>
|
|
784
|
+
> ⚠ **Ghi đè `--primary` thì nên set kèm `--primary-ink`** nếu app muốn một bậc tối riêng cho brand
|
|
785
|
+
> của mình. Ở bảng mặc định hai biến này trùng nhau nên quên cũng không sai màu; ở bảng tương phản cao
|
|
786
|
+
> thì `--primary-ink` là thứ điều khiển control BẬT, quên là control giữ nguyên gold-500 mặc định.
|
|
589
787
|
>
|
|
590
788
|
> ⚠ **Đổi `--teal` / `--green` / `--red` thì phải tính lại bậc `-ink` tương ứng** — chúng là hex đã đóng
|
|
591
789
|
> băng, không tự đi theo. Đóng băng là chủ ý: một chỗ duy nhất để đo lại, component không phải nhắc lại
|
|
@@ -599,13 +797,15 @@ Bản mirror TS đi theo đúng hợp đồng này: `accent` (nền) ⇄ `accent
|
|
|
599
797
|
|
|
600
798
|
| Nhóm | Token | Ghi chú |
|
|
601
799
|
|---|---|---|
|
|
602
|
-
| Brand | `gold-50 … gold-600` | `gold-300 #E8B824` = `--primary` (light); dark nâng lên `gold-200 #F5D060`. `--primary-ink` = `gold-500 #7A5800` (light) / `= --primary` (dark) —
|
|
800
|
+
| Brand | `gold-50 … gold-600` | `gold-300 #E8B824` = `--primary` (light); dark nâng lên `gold-200 #F5D060`. `--primary-ink` = `--primary` ở bảng mặc định; ở bảng tương phản cao là `gold-500 #7A5800` (light) / `= --primary` (dark) — xem §6.0 và §6.1 |
|
|
603
801
|
| Trung tính brand | `ink`, `ink-soft`, `paper`, `warm` | `warm #EDE8D8` = `--secondary` (light) |
|
|
604
802
|
| Thang xám | `ash-50/100/300/500/700/900` | **Không phải `gray-*`** — xem cạm bẫy bên dưới |
|
|
605
|
-
| Màu phụ (NỀN) | `navy`, `teal`, `red`, `green`, `yellow` | Vai trò **tô nền** (fill đặc hoặc tint).
|
|
803
|
+
| Màu phụ (NỀN) | `navy`, `teal`, `red`, `green`, `yellow` | Vai trò **tô nền** (fill đặc hoặc tint). Bảng mặc định dùng một hex cho cả hai theme (đúng comitor-ds); bảng tương phản cao nâng sáng ở dark. ⚠ `--destructive` là token RIÊNG (nền nút xoá): `#D64545` ở bảng mặc định, `#C93B3B` ở bảng tương phản cao để chữ trắng đạt 5,03:1 |
|
|
606
804
|
| Mực của màu phụ | `teal-ink`, `green-ink`, `red-ink`, `yellow-ink`, `navy-ink` | Cùng màu đó ở vai trò **chữ/icon/viền trên nền trang** — xem §6.1. Ở light là hex đã đóng băng (`#1A6668` / `#216A48` / `#9A3537` / `= gold-500` / `= navy`); ở dark trỏ về chính màu phụ |
|
|
607
|
-
| Trạng thái | `success`, `warning`, `info`, `destructive` (+ `-foreground`,
|
|
608
|
-
| Ranh giới control | `control-edge`
|
|
805
|
+
| Trạng thái | `success`, `warning`, `info`, `destructive` (+ `-foreground`, `-ink`, `destructive-hover`) | Ba cái đầu alias sang `green` / `yellow` / `teal`; `destructive` ghim một giá trị cho cả hai theme. `-ink` là bậc để làm **chữ** trạng thái. `--destructive-hover` phải là token riêng vì hai bảng đi **ngược chiều**: comitor-ds viết `hover:bg-red/90` (nhạt dần), bảng tương phản cao phải đậm thêm |
|
|
806
|
+
| Ranh giới control | `control-edge`, `control-edge-strong`, `input`, `input-fill` | **Ranh giới của control** (viền ô nhập, ô checkbox chưa chọn). `--control-edge` trỏ vào `--input`. Bảng mặc định: `#EDEEF1` / `#1E2128` — bằng bậc trang trí, đúng comitor-ds. Bảng tương phản cao: `#7E8290` cho cả hai theme (3,83:1 / 4,94:1). `--control-edge-strong` là bậc hover. `--input-fill` là lớp tô mờ ở dark (`dark:bg-input-fill`) — ở bảng mặc định nó là thứ duy nhất còn khoanh vùng ô Radio/OTP, ở bảng tương phản cao nó trong suốt |
|
|
807
|
+
| Control BẬT / ĐÃ CHỌN | `control-on`, `control-knob`, `control-check`, `switch-track`, `slider-track` | Rãnh `Switch` BẬT / ô `Checkbox` đã chọn dùng `bg-control-on` (= `--primary-ink`); núm Switch là `bg-control-knob`; dấu check và chữ trên ngày đang chọn của `Calendar` là `text-control-check` (MỰC ở bảng mặc định vì nền là gold sáng, màu nền trang ở bảng tương phản cao vì nền là gold-500 tối). `switch-track`/`slider-track` là rãnh lúc TẮT — comitor-ds cho hai thứ này hai bậc khác nhau nên không gộp được |
|
|
808
|
+
| Link | `link-ink`, `link-ink-hover` | Button variant `link`. Tách khỏi `--primary-ink` vì comitor-ds dùng `gold-400` cho chữ link nhưng `gold-300` cho viền focus |
|
|
609
809
|
| Biểu đồ | `chart-1 … chart-5` | Đổi bảng màu biểu đồ độc lập với brand. `chart-3` trỏ vào `--navy-ink` (nét biểu đồ phải NỔI trên nền trang) |
|
|
610
810
|
| Avatar | `getAvatarToneColors(name, mode)`, `getAvatarToneIndex(name)`, type `AvatarToneColors` | Trả `{ background, foreground }` dạng **hex đục** cho canvas/email/PDF — đã trộn sẵn tint với nền `--card` vì ngoài CSS không có gì để hợp thành. Cùng bảng tone, cùng hàm băm với `getAvatarToneClasses(name)` ở `@comitor/ui`, nên một người luôn ra một màu ở mọi nơi. Khoá băm là **tên hiển thị**, không phải email/slug |
|
|
611
811
|
| Theo app | `app-accent`, `app-accent-foreground`, `app-accent-ink` | Mỗi app Comitor tô một accent riêng — khai ở CSS của app (có nhánh `.dark`), không cần build lại package. Ghi đè `--app-accent` thì **phải** set kèm `--app-accent-ink` (§6.1) |
|
|
@@ -673,7 +873,8 @@ pnpm lint:fix
|
|
|
673
873
|
pnpm lint:a11y # hợp đồng màu: vai trò token + tương phản WCAG ở CẢ hai theme
|
|
674
874
|
pnpm test:cong-a11y # tự kiểm chính cổng trên: 56 khẳng định, cổng còn bắt đủ không
|
|
675
875
|
pnpm test:nav-active # active matching của sidebar (hàm thuần) — 34 ca
|
|
676
|
-
pnpm verify:directives #
|
|
876
|
+
pnpm verify:directives # ranh giới server/client: directive còn sau build + không hằng thuần kẹt trong file client
|
|
877
|
+
pnpm smoke:entries # nạp cả 5 entry bằng Node ESM thuần, qua đúng `exports` map (chạy SAU build)
|
|
677
878
|
pnpm check:exports # publint --strict + attw
|
|
678
879
|
```
|
|
679
880
|
|
|
@@ -701,7 +902,12 @@ in ra đúng con số này — đừng chép số từ đây mà không chạy l
|
|
|
701
902
|
|
|
702
903
|
`scripts/check-contrast.mjs` quét **toàn bộ** `src/**/*.{ts,tsx}` theo QUY TẮC (không theo danh sách
|
|
703
904
|
phát hiện) và đối chiếu với `styles.css`: token TÔ NỀN đem làm màu chữ/icon/viền là sai *vai trò*, và
|
|
704
|
-
mọi cặp chữ/nền phải đạt 4,5:1 (chữ) hoặc 3:1 (thành phần phi văn bản) ở **cả** light lẫn dark.
|
|
905
|
+
mọi cặp chữ/nền phải đạt 4,5:1 (chữ) hoặc 3:1 (thành phần phi văn bản) ở **cả** light lẫn dark.
|
|
906
|
+
|
|
907
|
+
⚠ Cổng đo **bảng tương phản cao** (`:root` + `[data-contrast="high"]`), không đo bảng mặc định — bảng
|
|
908
|
+
mặc định là bản duyệt và đã được chấp nhận với đúng những con số dưới ngưỡng ở §6.0; bắt nó là bắt một
|
|
909
|
+
quyết định, và một cổng lúc nào cũng đỏ thì lần sau không ai chạy nữa. Hệ quả có lợi: component nào viết
|
|
910
|
+
thẳng thang màu thay vì token vai trò sẽ đỏ ngay, vì giá trị viết thẳng không đổi theo bảng. Mọi loại
|
|
705
911
|
phát hiện đều `exit 1` — kể cả lớp dùng bảng màu mặc định của Tailwind và miễn trừ đã thừa. Miễn trừ ghi
|
|
706
912
|
ở `scripts/contrast-exemptions.json`, mỗi mục bắt buộc kèm lý do bằng chữ. Hợp đồng đầy đủ + giới hạn đã
|
|
707
913
|
biết: [docs/hop-dong-mau.md](docs/hop-dong-mau.md).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bảng màu biểu đồ — **module dữ liệu thuần, CỐ Ý không có `"use client"`**.
|
|
3
|
+
*
|
|
4
|
+
* ─ VÌ SAO TÁCH RA KHỎI `comitor-chart.tsx` ─
|
|
5
|
+
* Hai hằng này từng nằm chung file với `<ComitorBarChart>`, mà file đó bắt buộc phải mở đầu
|
|
6
|
+
* bằng `"use client"` (recharts đo kích thước bằng DOM). Hệ quả trong React Server Components:
|
|
7
|
+
* mọi export của một module client — kể cả hằng dữ liệu — đến tay Server Component dưới dạng
|
|
8
|
+
* **client-reference proxy**, không phải giá trị thật. Đọc `CHART_COLORS.gold` ở trang server
|
|
9
|
+
* trả về `undefined`, KHÔNG throw, KHÔNG warning, trong khi TypeScript vẫn khai kiểu literal
|
|
10
|
+
* `"var(--chart-1)"`. Bằng chứng đo được (Next 16, `next build`, trang server prerender):
|
|
11
|
+
*
|
|
12
|
+
* CHART_COLORS.gold → undefined (khi export từ file có "use client")
|
|
13
|
+
* gold[300] @ ui/tokens → "#E8B824" (module thuần, cùng lúc, cùng trang)
|
|
14
|
+
*
|
|
15
|
+
* Kịch bản gãy điển hình: dựng mảng `series` ngay trong Server Component rồi truyền xuống
|
|
16
|
+
* biểu đồ — `series: [{ key: "loi", color: CHART_COLORS.red }]` ra `color: undefined`, biểu đồ
|
|
17
|
+
* lặng lẽ rơi về màu xoay vòng mặc định. Chính JSDoc của gói đang khuyên viết như vậy.
|
|
18
|
+
*
|
|
19
|
+
* ─ RE-EXPORT PHẢI ĐI THẲNG TỪ BARREL ─
|
|
20
|
+
* `chart/index.ts` phải `export * from "./chart-colors.js"` TRỰC TIẾP. Re-export vòng qua
|
|
21
|
+
* `comitor-chart.tsx` là vô nghĩa: bundler RSC cắt ranh giới ở FILE, nên bất cứ thứ gì đi qua
|
|
22
|
+
* một file `"use client"` — kể cả re-export — đều thành proxy trở lại. Cũng vì thế
|
|
23
|
+
* `comitor-chart.tsx` chỉ được `import` hai hằng này, tuyệt đối không `export` lại chúng
|
|
24
|
+
* (vừa vô hiệu, vừa đụng tên với `export *` của barrel).
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Bảng màu series mặc định — trỏ vào `--chart-1…5` trong `styles.css`.
|
|
28
|
+
*
|
|
29
|
+
* Vì sao không lấy thẳng `var(--gold-300)`, `var(--teal)`… như comitor-ds: bảng màu biểu đồ
|
|
30
|
+
* cần đổi ĐỘC LẬP với brand (app đổi `--chart-*` ở `<body>` là xong), và `--chart-3` có bản
|
|
31
|
+
* dark riêng (navy nguyên bản chìm vào nền tối).
|
|
32
|
+
*
|
|
33
|
+
* Dùng `var(--chart-N)` chứ KHÔNG dùng `var(--color-chart-N)`: bản `--chart-*` được khai
|
|
34
|
+
* tường minh trong `:root` của `styles.css` nên chắc chắn tồn tại, còn `--color-chart-*` là
|
|
35
|
+
* biến do `@theme inline` của Tailwind sinh ra.
|
|
36
|
+
*/
|
|
37
|
+
declare const CHART_SERIES_COLORS: readonly ["var(--chart-1)", "var(--chart-2)", "var(--chart-3)", "var(--chart-4)", "var(--chart-5)"];
|
|
38
|
+
/**
|
|
39
|
+
* Màu gọi theo tên — dùng khi muốn chỉ định màu cho một series cụ thể
|
|
40
|
+
* (`series: [{ key: "loi", color: CHART_COLORS.red }]`).
|
|
41
|
+
*
|
|
42
|
+
* ⚠ Khoá `gray` của comitor-ds đổi thành `ash`: thang xám của package là `--ash-*`,
|
|
43
|
+
* `gray-*` để nguyên cho Tailwind (xem chú thích thang màu trong `styles.css`).
|
|
44
|
+
*/
|
|
45
|
+
declare const CHART_COLORS: {
|
|
46
|
+
readonly gold: "var(--chart-1)";
|
|
47
|
+
readonly goldLight: "var(--gold-100)";
|
|
48
|
+
readonly teal: "var(--chart-2)";
|
|
49
|
+
readonly navy: "var(--chart-3)";
|
|
50
|
+
readonly green: "var(--chart-4)";
|
|
51
|
+
readonly red: "var(--chart-5)";
|
|
52
|
+
readonly ash: "var(--ash-300)";
|
|
53
|
+
};
|
|
54
|
+
type ChartColorName = keyof typeof CHART_COLORS;
|
|
55
|
+
|
|
56
|
+
export { CHART_COLORS, CHART_SERIES_COLORS, type ChartColorName };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const CHART_SERIES_COLORS = [
|
|
2
|
+
"var(--chart-1)",
|
|
3
|
+
"var(--chart-2)",
|
|
4
|
+
"var(--chart-3)",
|
|
5
|
+
"var(--chart-4)",
|
|
6
|
+
"var(--chart-5)"
|
|
7
|
+
];
|
|
8
|
+
const CHART_COLORS = {
|
|
9
|
+
gold: "var(--chart-1)",
|
|
10
|
+
goldLight: "var(--gold-100)",
|
|
11
|
+
teal: "var(--chart-2)",
|
|
12
|
+
navy: "var(--chart-3)",
|
|
13
|
+
green: "var(--chart-4)",
|
|
14
|
+
red: "var(--chart-5)",
|
|
15
|
+
ash: "var(--ash-300)"
|
|
16
|
+
};
|
|
17
|
+
export {
|
|
18
|
+
CHART_COLORS,
|
|
19
|
+
CHART_SERIES_COLORS
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=chart-colors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/chart/chart-colors.ts"],"sourcesContent":["/**\n * Bảng màu biểu đồ — **module dữ liệu thuần, CỐ Ý không có `\"use client\"`**.\n *\n * ─ VÌ SAO TÁCH RA KHỎI `comitor-chart.tsx` ─\n * Hai hằng này từng nằm chung file với `<ComitorBarChart>`, mà file đó bắt buộc phải mở đầu\n * bằng `\"use client\"` (recharts đo kích thước bằng DOM). Hệ quả trong React Server Components:\n * mọi export của một module client — kể cả hằng dữ liệu — đến tay Server Component dưới dạng\n * **client-reference proxy**, không phải giá trị thật. Đọc `CHART_COLORS.gold` ở trang server\n * trả về `undefined`, KHÔNG throw, KHÔNG warning, trong khi TypeScript vẫn khai kiểu literal\n * `\"var(--chart-1)\"`. Bằng chứng đo được (Next 16, `next build`, trang server prerender):\n *\n * CHART_COLORS.gold → undefined (khi export từ file có \"use client\")\n * gold[300] @ ui/tokens → \"#E8B824\" (module thuần, cùng lúc, cùng trang)\n *\n * Kịch bản gãy điển hình: dựng mảng `series` ngay trong Server Component rồi truyền xuống\n * biểu đồ — `series: [{ key: \"loi\", color: CHART_COLORS.red }]` ra `color: undefined`, biểu đồ\n * lặng lẽ rơi về màu xoay vòng mặc định. Chính JSDoc của gói đang khuyên viết như vậy.\n *\n * ─ RE-EXPORT PHẢI ĐI THẲNG TỪ BARREL ─\n * `chart/index.ts` phải `export * from \"./chart-colors.js\"` TRỰC TIẾP. Re-export vòng qua\n * `comitor-chart.tsx` là vô nghĩa: bundler RSC cắt ranh giới ở FILE, nên bất cứ thứ gì đi qua\n * một file `\"use client\"` — kể cả re-export — đều thành proxy trở lại. Cũng vì thế\n * `comitor-chart.tsx` chỉ được `import` hai hằng này, tuyệt đối không `export` lại chúng\n * (vừa vô hiệu, vừa đụng tên với `export *` của barrel).\n */\n\n/**\n * Bảng màu series mặc định — trỏ vào `--chart-1…5` trong `styles.css`.\n *\n * Vì sao không lấy thẳng `var(--gold-300)`, `var(--teal)`… như comitor-ds: bảng màu biểu đồ\n * cần đổi ĐỘC LẬP với brand (app đổi `--chart-*` ở `<body>` là xong), và `--chart-3` có bản\n * dark riêng (navy nguyên bản chìm vào nền tối).\n *\n * Dùng `var(--chart-N)` chứ KHÔNG dùng `var(--color-chart-N)`: bản `--chart-*` được khai\n * tường minh trong `:root` của `styles.css` nên chắc chắn tồn tại, còn `--color-chart-*` là\n * biến do `@theme inline` của Tailwind sinh ra.\n */\nexport const CHART_SERIES_COLORS = [\n \"var(--chart-1)\",\n \"var(--chart-2)\",\n \"var(--chart-3)\",\n \"var(--chart-4)\",\n \"var(--chart-5)\"\n] as const;\n\n/**\n * Màu gọi theo tên — dùng khi muốn chỉ định màu cho một series cụ thể\n * (`series: [{ key: \"loi\", color: CHART_COLORS.red }]`).\n *\n * ⚠ Khoá `gray` của comitor-ds đổi thành `ash`: thang xám của package là `--ash-*`,\n * `gray-*` để nguyên cho Tailwind (xem chú thích thang màu trong `styles.css`).\n */\nexport const CHART_COLORS = {\n gold: \"var(--chart-1)\",\n goldLight: \"var(--gold-100)\",\n teal: \"var(--chart-2)\",\n navy: \"var(--chart-3)\",\n green: \"var(--chart-4)\",\n red: \"var(--chart-5)\",\n ash: \"var(--ash-300)\"\n} as const;\n\nexport type ChartColorName = keyof typeof CHART_COLORS;\n"],"mappings":"AAqCO,MAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AASO,MAAM,eAAe;AAAA,EAC1B,MAAM;AAAA,EACN,WAAW;AAAA,EACX,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,KAAK;AAAA,EACL,KAAK;AACP;","names":[]}
|
package/dist/chart/chart.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ import * as RechartsPrimitive from 'recharts';
|
|
|
6
6
|
* `shadcn add` vẫn dùng lại được. Đây là tầng "thô": bạn tự lắp recharts bên trong.
|
|
7
7
|
* Muốn dựng nhanh 3 loại biểu đồ quen thuộc thì xem `comitor-chart.tsx`.
|
|
8
8
|
*
|
|
9
|
+
* ⚠⚠ TỰ LẮP RECHARTS 2.15.x THÌ PHẢI TỰ TẮT HIỆU ỨNG VẼ DẦN ⚠⚠
|
|
10
|
+
* Xem chú thích của {@link ChartContainer} ngay dưới. Tầng này KHÔNG đặt hộ được
|
|
11
|
+
* `isAnimationActive={false}` như `Comitor*Chart` làm, vì mark là children do app viết.
|
|
12
|
+
*
|
|
9
13
|
* Cơ chế màu (điểm hay nhất, và cũng dễ hiểu sai nhất):
|
|
10
14
|
* `<ChartContainer>` render kèm `<ChartStyle>` — thẻ này sinh ra biến CSS `--color-<key>`
|
|
11
15
|
* cho từng key trong `config`, giới hạn phạm vi bằng `[data-chart=<id>]`. Series recharts
|
|
@@ -46,6 +50,42 @@ type ChartContainerProps = React.ComponentProps<"div"> & {
|
|
|
46
50
|
/**
|
|
47
51
|
* Khung biểu đồ: cấp context `config`, bơm biến màu, và bọc sẵn `ResponsiveContainer`.
|
|
48
52
|
* Tỉ lệ mặc định `aspect-video`; muốn cao cố định thì truyền `className="h-64 aspect-auto"`.
|
|
53
|
+
*
|
|
54
|
+
* ─ ⚠ RECHARTS 2.15.x: PHẢI TỰ GHI `isAnimationActive={false}` LÊN TỪNG MARK ─
|
|
55
|
+
* Với `recharts` 2.15.x + React 19.2, hiệu ứng vẽ dần (mặc định BẬT của recharts) không bao giờ
|
|
56
|
+
* chạy tới khung hình cuối: mark đứng nguyên ở khung đầu, tức là **vô hình**. Biểu đồ ra đủ trục,
|
|
57
|
+
* đủ lưới, đủ chú giải, KHÔNG có dữ liệu — không lỗi, không warning, chờ bao lâu cũng không tự hết.
|
|
58
|
+
*
|
|
59
|
+
* `<ComitorBarChart>` / `<ComitorLineChart>` / `<ComitorAreaChart>` đã đặt sẵn `false` từ `0.2.2`.
|
|
60
|
+
* **`<ChartContainer>` thì KHÔNG đặt hộ được** — `<Bar>` / `<Line>` / `<Area>` là children do app
|
|
61
|
+
* viết, container không sờ vào. Đi lối shadcn quen thuộc mà quên prop này là gặp đúng lỗi im lặng
|
|
62
|
+
* của `0.2.1`:
|
|
63
|
+
*
|
|
64
|
+
* ```tsx
|
|
65
|
+
* // ✗ recharts 2.15.x: <g class="recharts-bar-rectangle"> rỗng, không cột nào
|
|
66
|
+
* <ChartContainer config={config}>
|
|
67
|
+
* <BarChart data={data}>
|
|
68
|
+
* <XAxis dataKey="thang" />
|
|
69
|
+
* <Bar dataKey="doanhThu" fill="var(--color-doanhThu)" />
|
|
70
|
+
* </BarChart>
|
|
71
|
+
* </ChartContainer>
|
|
72
|
+
*
|
|
73
|
+
* // ✓ tắt tay trên TỪNG mark
|
|
74
|
+
* <Bar dataKey="doanhThu" fill="var(--color-doanhThu)" isAnimationActive={false} />
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* Đo trên DOM thật (Next 16.3.2 · React 19.2.8 · recharts 2.15.4, `next build` + `next start`,
|
|
78
|
+
* chờ 5000ms sau hydrate) — cùng một trang, chỉ khác một prop:
|
|
79
|
+
*
|
|
80
|
+
* | mark | thiếu prop | có `isAnimationActive={false}` |
|
|
81
|
+
* |---|---|---|
|
|
82
|
+
* | `<Bar>` | 3 `<g.recharts-bar-rectangle>` nhưng **0** `path.recharts-rectangle` | **3/3** path, cao 273/410/342px |
|
|
83
|
+
* | `<Line>` | `stroke-dasharray="0px 818.84px"` | `stroke-dasharray` = `null` |
|
|
84
|
+
* | `<Area>` | `<clipPath><rect width="0">` | không còn rect width 0 |
|
|
85
|
+
*
|
|
86
|
+
* `recharts` 3.x không dính lỗi này — bỏ prop đi là được. Dải peer của gói là `>=2.15.0` nên
|
|
87
|
+
* 2.15.x vẫn là cấu hình hợp lệ và phổ biến; cứ ghi `isAnimationActive={false}` cho chắc, trên
|
|
88
|
+
* 3.x nó chỉ có nghĩa "không hoạt hình".
|
|
49
89
|
*/
|
|
50
90
|
declare function ChartContainer({ id, className, children, config, ...props }: ChartContainerProps): React.JSX.Element;
|
|
51
91
|
type ChartStyleProps = {
|