@comitor/ui 0.2.0 → 0.2.2
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 +221 -14
- 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 +75 -3
- package/dist/chart/chart.js.map +1 -1
- package/dist/chart/comitor-chart.d.ts +33 -42
- package/dist/chart/comitor-chart.js +13 -24
- package/dist/chart/comitor-chart.js.map +1 -1
- package/dist/chart/index.d.ts +3 -2
- package/dist/chart/index.js +1 -0
- package/dist/chart/index.js.map +1 -1
- package/dist/components/composite/combobox.d.ts +9 -2
- package/dist/components/composite/combobox.js +7 -0
- package/dist/components/composite/combobox.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/button.js +1 -1
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/card.d.ts +35 -2
- package/dist/components/ui/card.js +17 -3
- package/dist/components/ui/card.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/shell/app-shell.d.ts +2 -0
- package/dist/shell/index.d.ts +1 -1
- package/dist/shell/shell-context.d.ts +28 -1
- package/dist/shell/shell-context.js +8 -3
- package/dist/shell/shell-context.js.map +1 -1
- package/dist/shell/sidebar-menu-item.js +3 -3
- package/dist/shell/sidebar-menu-item.js.map +1 -1
- package/dist/shell/utils.d.ts +27 -3
- package/dist/shell/utils.js +52 -9
- package/dist/shell/utils.js.map +1 -1
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -41,9 +41,24 @@ pnpm add react-hook-form # cho @comitor/ui/form
|
|
|
41
41
|
| `lucide-react` | `>=0.540.0` | ✅ | luôn (tránh 2 bản icon trong bundle) |
|
|
42
42
|
| `next` | `>=15` | — | `/shell` |
|
|
43
43
|
| `next-themes` | `>=0.4` | — | `/shell` (context singleton — app và package phải dùng **cùng một bản**) |
|
|
44
|
-
| `recharts` |
|
|
44
|
+
| `recharts` | `>=2.15.0` | — | `/chart` (kiểu đã dựng cho **cả 2.x lẫn 3.x**, xem ghi chú dưới) |
|
|
45
45
|
| `react-hook-form` | `^7.54.0` | — | `/form` (context singleton) |
|
|
46
46
|
|
|
47
|
+
> **Vì sao `recharts` là `>=` chứ không phải `^`.** `^2.15.0` loại thẳng recharts 3 — mà 3.x
|
|
48
|
+
> chạy tốt với gói này, nên app nào đã lên 3.x cũng ăn một dòng `unmet peer` không có cách sửa.
|
|
49
|
+
> Kiểu của tầng `/chart` không còn dẫn xuất `payload`/`label` từ `TooltipProps`/`LegendProps`
|
|
50
|
+
> của recharts (3.x đã bỏ hai trường đó khỏi props, chuyển sang context nội bộ) mà tự khai
|
|
51
|
+
> `ChartPayloadItem` — phần giao của hai bản. `pnpm typecheck && pnpm build` được chạy **trên cả
|
|
52
|
+
> recharts 2.15.4 lẫn 3.10.1** trước khi nới dải này.
|
|
53
|
+
>
|
|
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
|
+
> 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).
|
|
61
|
+
|
|
47
62
|
## 2. Nạp `styles.css` — ĐỌC KỸ, đây là chỗ dễ sai nhất
|
|
48
63
|
|
|
49
64
|
Đặt import trong **`app/globals.css`**, ngay sau `@import "tailwindcss"`:
|
|
@@ -132,7 +147,7 @@ barrel chính re-export **một** file có `import` tới peer optional là mọ
|
|
|
132
147
|
### Tên xuất hiện ở nhiều entry
|
|
133
148
|
|
|
134
149
|
Số tên mỗi entry xuất ra (đo bằng trình biên dịch TypeScript trên đúng 5 file entry, giải alias về khai
|
|
135
|
-
báo gốc — không đếm tay): `.` **637** · `/shell` **
|
|
150
|
+
báo gốc — không đếm tay): `.` **637** · `/shell` **70** · `/chart` **29** · `/form` **15** · `/tokens`
|
|
136
151
|
**15**. Đúng **19 tên** xuất hiện ở nhiều hơn một entry, gom thành 4 nhóm dưới đây — `export *` chỉ báo
|
|
137
152
|
đỏ khi trùng tên trong **cùng một** entry, nên trùng xuyên entry phải tra bằng bảng này:
|
|
138
153
|
|
|
@@ -291,11 +306,46 @@ import { ThemeProvider } from "@comitor/ui/shell";
|
|
|
291
306
|
App **không chạy trên Next**: truyền `pathname` và `LinkComponent` cho `ShellProvider`/`AppShell`
|
|
292
307
|
để khỏi chạm `next/navigation`.
|
|
293
308
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
309
|
+
#### Mục menu phân biệt nhau bằng query — prop `searchParams`
|
|
310
|
+
|
|
311
|
+
`usePathname()` của Next **không bao giờ** chứa `?query`. Vì vậy mục khai
|
|
312
|
+
`href: "/cai-dat?tab=thanh-vien"` được so bằng phần ĐƯỜNG DẪN (`/cai-dat`) — nó sáng khi bạn đang
|
|
313
|
+
ở `/cai-dat`, và nếu menu có nhiều tab cùng đường dẫn thì **tất cả cùng sáng**, vì shell không
|
|
314
|
+
thấy query.
|
|
315
|
+
|
|
316
|
+
Muốn đúng từng tab thì đưa query cho shell qua prop **optional** `searchParams`:
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
"use client";
|
|
320
|
+
import { useSearchParams } from "next/navigation";
|
|
321
|
+
import { AppShell } from "@comitor/ui/shell";
|
|
322
|
+
|
|
323
|
+
export function Shell({ children }: { children: React.ReactNode }) {
|
|
324
|
+
const searchParams = useSearchParams(); // nhớ có <Suspense> ở trên trang
|
|
325
|
+
return <AppShell nav={nav} searchParams={searchParams}>{children}</AppShell>;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Nhận cả `URLSearchParams`, chuỗi `"?tab=x"`, lẫn object `searchParams` của Server Component.
|
|
330
|
+
Luật khi đã có nó:
|
|
331
|
+
|
|
332
|
+
- mục có query sáng khi query của nó là **tập con** của URL hiện tại (`?tab=a` vẫn sáng ở
|
|
333
|
+
`?tab=a&q=abc&trang=2`);
|
|
334
|
+
- mục **không** query nhường cho mục cùng đường dẫn có query khớp — tab mặc định không sáng cùng
|
|
335
|
+
lúc với tab đang mở;
|
|
336
|
+
- luật cũ "chỉ `startsWith` khi không có mục nào khớp chính xác" giữ nguyên.
|
|
337
|
+
|
|
338
|
+
> Shell **không** tự gọi `useSearchParams()`: hook đó ép trang phải bọc `<Suspense>` và đẩy trang
|
|
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
|
|
340
|
+
> import shell.
|
|
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.
|
|
299
349
|
|
|
300
350
|
### 4.3 Token ngoài Tailwind
|
|
301
351
|
|
|
@@ -316,11 +366,130 @@ const { background, foreground } = getAvatarToneColors("Nguyễn Đức Thành",
|
|
|
316
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ả
|
|
317
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.
|
|
318
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
|
+
|
|
319
487
|
## 5. Bảng component
|
|
320
488
|
|
|
321
|
-
Số liệu thực tế của `0.2.
|
|
322
|
-
(chưa kể barrel) — số tên xuất ra theo entry: `.` **637** · `/shell` **
|
|
323
|
-
**15** · `/tokens` **15** (xem §3 cho các tên xuất hiện ở nhiều entry).
|
|
489
|
+
Số liệu thực tế của `0.2.2`: **51 file primitive · 29 file composite · 18 file shell · 3 file chart**
|
|
490
|
+
(chưa kể barrel) — số tên xuất ra theo entry: `.` **637** · `/shell` **70** · `/chart` **29** · `/form`
|
|
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;
|
|
324
493
|
các thành phần con (`CardHeader`, `DialogFooter`, `SelectItem`…) export kèm theo file tương ứng.
|
|
325
494
|
|
|
326
495
|
### Tier 1 — primitive (`@comitor/ui`)
|
|
@@ -344,6 +513,35 @@ shadcn/ui style `new-york`, đã tùy biến token theo brand Comitor.
|
|
|
344
513
|
> cookie riêng. App nào thật sự cần bản shadcn thì `npx shadcn@latest add sidebar` vào repo của mình.
|
|
345
514
|
> Lý do đầy đủ: [docs/quyet-dinh-tich-hop.md](docs/quyet-dinh-tich-hop.md) §17.
|
|
346
515
|
|
|
516
|
+
#### `CardHeader` có nút ở góc phải — prop `actions`
|
|
517
|
+
|
|
518
|
+
`CardHeader` mặc định xếp DỌC (`flex-col space-y-1.5`). Thẻ có nút ở góc phải vì thế phải ghi đè
|
|
519
|
+
đúng một chuỗi bốn lớp, ở mọi app, mọi thẻ:
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
/* ✗ Cách cũ — chép tay, dễ sót `space-y-0` (thiếu nó thì nút bị đẩy lệch xuống) */
|
|
523
|
+
<CardHeader className="flex-row items-start justify-between gap-3 space-y-0">
|
|
524
|
+
<div>
|
|
525
|
+
<CardTitle>Thông tin đơn vị</CardTitle>
|
|
526
|
+
<CardDescription>Cập nhật lần cuối hôm qua</CardDescription>
|
|
527
|
+
</div>
|
|
528
|
+
<Button size="sm" variant="ghost">Sửa</Button>
|
|
529
|
+
</CardHeader>
|
|
530
|
+
|
|
531
|
+
/* ✓ Từ 0.2.1 — `actions` gói đúng chuỗi đó lại */
|
|
532
|
+
<CardHeader actions={<Button size="sm" variant="ghost">Sửa</Button>}>
|
|
533
|
+
<CardTitle>Thông tin đơn vị</CardTitle>
|
|
534
|
+
<CardDescription>Cập nhật lần cuối hôm qua</CardDescription>
|
|
535
|
+
</CardHeader>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
Không truyền `actions` thì lớp và cây DOM **y hệt `0.2.0`** — prop này chỉ thêm một nhánh, không
|
|
539
|
+
đổi mặc định. Cụm nút nhận `actionsClassName` riêng khi cần đổi `gap`/cho xuống dòng.
|
|
540
|
+
|
|
541
|
+
Quan hệ với `CardAction`: `CardAction` là ô `ml-auto` để tự lắp khi bạn **đã** tự đặt header thành
|
|
542
|
+
hàng ngang; `actions` làm cả hai việc (đổi bố cục *và* đặt chỗ) nên là đường đi mặc định.
|
|
543
|
+
`CardAction` giữ nguyên cho ai đã dùng.
|
|
544
|
+
|
|
347
545
|
### Tier 2 — composite (`@comitor/ui`)
|
|
348
546
|
|
|
349
547
|
Giá trị thật của package: pattern lặp đi lặp lại ở mọi app, ghép từ nhiều primitive.
|
|
@@ -356,7 +554,7 @@ Giá trị thật của package: pattern lặp đi lặp lại ở mọi app, gh
|
|
|
356
554
|
| `SearchFilterBar`, `FilterChips` | Thanh tìm + lọc + đếm kết quả. `SearchFilterBar` override chuỗi qua `labels` — `SearchFilterBarLabels` / `DEFAULT_SEARCH_FILTER_BAR_LABELS`; `FilterChips` nhận nhãn thẳng trong dữ liệu chip nên không có `labels` |
|
|
357
555
|
| `StatCard`, `StatusPill`, `EmptyState` | Số liệu có delta, nhãn trạng thái, trạng thái rỗng |
|
|
358
556
|
| `FormSection`, `FormField` | Bố cục form 12 cột; uỷ quyền label/mô tả/lỗi xuống `Field` |
|
|
359
|
-
| `Combobox`, `MultiCombobox` | Chọn có tìm kiếm, **khớp không dấu tiếng Việt** |
|
|
557
|
+
| `Combobox`, `MultiCombobox` | Chọn có tìm kiếm, **khớp không dấu tiếng Việt**. `aria-invalid` đổi trigger sang **viền đỏ** (`--destructive-ink`), cùng tín hiệu với `Input`; nhận cả `boolean` lẫn `"true"`/`"false"` để dùng thẳng `aria-invalid={!!errors.x}` |
|
|
360
558
|
| `DatePicker`, `DateRangePicker` | Chọn ngày/khoảng ngày, locale `vi`, preset dựng sẵn |
|
|
361
559
|
| `LetterAvatar`, `AvatarGroup` | Avatar chữ cái theo tên tiếng Việt (tông màu deterministic), nhóm người tham gia. Lấy tone bằng `getAvatarToneClasses(name)`; bản hex cho canvas/email/PDF là `getAvatarToneColors(name, mode)` ở `/tokens` — **cùng một bảng tone**, cùng một hàm băm |
|
|
362
560
|
| `ConfirmDialog`, `CommandPalette` | Hộp xác nhận, bảng lệnh `⌘K` |
|
|
@@ -398,10 +596,11 @@ Hợp đồng dữ liệu (type): `Workspace`, `AppDescriptor`, `NavItem`, `NavG
|
|
|
398
596
|
|
|
399
597
|
| Component | Công dụng |
|
|
400
598
|
|---|---|
|
|
401
|
-
| `ChartContainer`, `ChartTooltip`, `ChartTooltipContent`, `ChartLegend`, `ChartLegendContent`, `ChartStyle`, `useChart` | Lớp shadcn bọc recharts (giữ tên trần theo API gốc) |
|
|
599
|
+
| `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) |
|
|
402
600
|
| `ComitorAreaChart`, `ComitorBarChart`, `ComitorLineChart` | Bản dựng sẵn theo brand — truyền `data` + `series` là xong |
|
|
403
601
|
| `ComitorChartContainer`, `ComitorChartTooltip` | Bản tiện dụng; **có tiền tố `Comitor`** để không đụng tên trần ở trên |
|
|
404
|
-
| `CHART_COLORS`, `CHART_SERIES_COLORS` | Bảng màu series mặc định |
|
|
602
|
+
| `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) |
|
|
603
|
+
| `ChartColorName` | Khoá của `CHART_COLORS` |
|
|
405
604
|
|
|
406
605
|
### Form (`@comitor/ui/form`)
|
|
407
606
|
|
|
@@ -599,10 +798,18 @@ pnpm typecheck # tsc --noEmit
|
|
|
599
798
|
pnpm lint # biome check .
|
|
600
799
|
pnpm lint:fix
|
|
601
800
|
pnpm lint:a11y # hợp đồng màu: vai trò token + tương phản WCAG ở CẢ hai theme
|
|
602
|
-
pnpm
|
|
801
|
+
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
|
|
802
|
+
pnpm test:nav-active # active matching của sidebar (hàm thuần) — 34 ca
|
|
803
|
+
pnpm verify:directives # ranh giới server/client: directive còn sau build + không hằng thuần kẹt trong file client
|
|
804
|
+
pnpm smoke:entries # nạp cả 5 entry bằng Node ESM thuần, qua đúng `exports` map (chạy SAU build)
|
|
603
805
|
pnpm check:exports # publint --strict + attw
|
|
604
806
|
```
|
|
605
807
|
|
|
808
|
+
`pnpm test:nav-active` chạy `scripts/kiem-nav-active.mjs`: Node trần bóc kiểu ngay trên
|
|
809
|
+
`src/shell/utils.ts`, không test runner, không dependency mới. Nó khoá lại đúng lỗi mà
|
|
810
|
+
comitor-starter báo về ở `0.2.0` — mục menu có `?query` trong `href` không bao giờ sáng — cùng
|
|
811
|
+
toàn bộ hành vi active matching cũ, để lần sửa sau không đổi ngầm luật nào.
|
|
812
|
+
|
|
606
813
|
> Gặp `ERR_PNPM_IGNORED_BUILDS: esbuild` khi cài? Đã xử lý sẵn trong `pnpm-workspace.yaml`
|
|
607
814
|
> (`allowBuilds: { esbuild: true }`) — pnpm 11 không còn đọc setting từ trường `pnpm` của
|
|
608
815
|
> `package.json` nữa. esbuild cần chạy postinstall để nạp binary theo nền tảng, cấm chạy thì
|
|
@@ -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 = {
|
|
@@ -60,7 +100,35 @@ type ChartStyleProps = {
|
|
|
60
100
|
declare const ChartStyle: ({ id, config }: ChartStyleProps) => React.JSX.Element | null;
|
|
61
101
|
/** `Tooltip` của recharts — đặt `content={<ChartTooltipContent />}` để có giao diện theo token. */
|
|
62
102
|
declare const ChartTooltip: typeof RechartsPrimitive.Tooltip;
|
|
103
|
+
/**
|
|
104
|
+
* Một phần tử `payload` do recharts bơm vào `content` của Tooltip/Legend.
|
|
105
|
+
*
|
|
106
|
+
* ─ VÌ SAO KHAI TẠI ĐÂY, KHÔNG DẪN XUẤT TỪ `TooltipProps`/`LegendProps` ─
|
|
107
|
+
* recharts **3** đã bỏ `payload` (và `label`) khỏi props của `<Tooltip>`/`<Legend>` — hai
|
|
108
|
+
* trường đó nay đi qua context nội bộ, nên `ComponentProps<typeof Tooltip>["payload"]` và
|
|
109
|
+
* `Pick<LegendProps, "payload">` **không còn tồn tại** ở 3.x. Dẫn xuất từ chúng khiến chính
|
|
110
|
+
* file `.d.ts` phát hành ra npm hỏng kiểu ngay khi app cài recharts 3 (app nào tắt
|
|
111
|
+
* `skipLibCheck` là thấy liền), tức peer `>=2.15.0` sẽ là một lời hứa suông.
|
|
112
|
+
*
|
|
113
|
+
* Hình dạng dưới đây là phần GIAO của 2.x và 3.x, và chỉ gồm đúng những trường component
|
|
114
|
+
* này thật sự đọc. Ở 2.x nó **giao** với kiểu gốc của recharts (props Tooltip vẫn còn
|
|
115
|
+
* `payload`) nên không nới lỏng gì; ở 3.x nó là nguồn duy nhất.
|
|
116
|
+
*/
|
|
117
|
+
interface ChartPayloadItem {
|
|
118
|
+
value?: number;
|
|
119
|
+
name?: string;
|
|
120
|
+
dataKey?: string | number;
|
|
121
|
+
color?: string;
|
|
122
|
+
/** Bản ghi gốc của điểm dữ liệu — nơi `fill` và các cột khác của hàng nằm. */
|
|
123
|
+
payload?: Record<string, unknown> & {
|
|
124
|
+
fill?: string;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
63
127
|
type ChartTooltipContentProps = React.ComponentProps<typeof RechartsPrimitive.Tooltip> & React.ComponentProps<"div"> & {
|
|
128
|
+
/** Do recharts bơm vào — đừng truyền tay. */
|
|
129
|
+
payload?: ChartPayloadItem[];
|
|
130
|
+
/** Do recharts bơm vào — đừng truyền tay. */
|
|
131
|
+
label?: React.ReactNode;
|
|
64
132
|
/** Ẩn dòng nhãn (trục X) ở đầu tooltip. */
|
|
65
133
|
hideLabel?: boolean;
|
|
66
134
|
/** Ẩn ô màu đứng trước tên series. */
|
|
@@ -75,8 +143,12 @@ type ChartTooltipContentProps = React.ComponentProps<typeof RechartsPrimitive.To
|
|
|
75
143
|
/** Nội dung tooltip theo design token Comitor: nền `bg-background`, viền `border-border/50`. */
|
|
76
144
|
declare function ChartTooltipContent({ active, payload, className, indicator, hideLabel, hideIndicator, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey }: ChartTooltipContentProps): React.JSX.Element | null;
|
|
77
145
|
/** `Legend` của recharts — đặt `content={<ChartLegendContent />}` để lấy nhãn từ `config`. */
|
|
78
|
-
declare const ChartLegend:
|
|
79
|
-
type ChartLegendContentProps = React.ComponentProps<"div"> &
|
|
146
|
+
declare const ChartLegend: React.MemoExoticComponent<(outsideProps: RechartsPrimitive.LegendProps) => React.ReactPortal | null>;
|
|
147
|
+
type ChartLegendContentProps = React.ComponentProps<"div"> & {
|
|
148
|
+
/** Do recharts bơm vào — đừng truyền tay. Xem `ChartPayloadItem` về lý do không dẫn xuất từ `LegendProps`. */
|
|
149
|
+
payload?: ChartPayloadItem[];
|
|
150
|
+
/** Do recharts bơm vào — quyết định chú giải đệm trên hay đệm dưới. */
|
|
151
|
+
verticalAlign?: "top" | "middle" | "bottom";
|
|
80
152
|
/** Ẩn icon riêng của series (nếu `config` có khai `icon`). */
|
|
81
153
|
hideIcon?: boolean;
|
|
82
154
|
/** Khoá dùng để tra `config` (mặc định `dataKey`). */
|
|
@@ -85,4 +157,4 @@ type ChartLegendContentProps = React.ComponentProps<"div"> & Pick<RechartsPrimit
|
|
|
85
157
|
/** Chú giải nằm ngang, nhãn lấy từ `config` chứ không phải `dataKey` thô. */
|
|
86
158
|
declare function ChartLegendContent({ className, hideIcon, payload, verticalAlign, nameKey }: ChartLegendContentProps): React.JSX.Element | null;
|
|
87
159
|
|
|
88
|
-
export { type ChartConfig, ChartContainer, type ChartContainerProps, ChartLegend, ChartLegendContent, type ChartLegendContentProps, ChartStyle, type ChartStyleProps, ChartTooltip, ChartTooltipContent, type ChartTooltipContentProps, useChart };
|
|
160
|
+
export { type ChartConfig, ChartContainer, type ChartContainerProps, ChartLegend, ChartLegendContent, type ChartLegendContentProps, type ChartPayloadItem, ChartStyle, type ChartStyleProps, ChartTooltip, ChartTooltipContent, type ChartTooltipContentProps, useChart };
|
package/dist/chart/chart.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/chart/chart.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Chart primitive của shadcn/ui (style new-york) — port nguyên trạng từ comitor-ds để\n * `shadcn add` vẫn dùng lại được. Đây là tầng \"thô\": bạn tự lắp recharts bên trong.\n * Muốn dựng nhanh 3 loại biểu đồ quen thuộc thì xem `comitor-chart.tsx`.\n *\n * Cơ chế màu (điểm hay nhất, và cũng dễ hiểu sai nhất):\n * `<ChartContainer>` render kèm `<ChartStyle>` — thẻ này sinh ra biến CSS `--color-<key>`\n * cho từng key trong `config`, giới hạn phạm vi bằng `[data-chart=<id>]`. Series recharts\n * chỉ việc trỏ `fill=\"var(--color-desktop)\"`. Nhờ vậy màu vẫn nằm trong TOKEN (không hex\n * rải trong JSX) và đổi được theo light/dark khi config khai `theme: { light, dark }`.\n */\n\n// Ánh xạ { TÊN_THEME: CSS_SELECTOR }. Chuỗi rỗng = light (không cần selector bọc ngoài).\nconst THEMES = { light: \"\", dark: \".dark\" } as const;\n\n/**\n * Khai báo hiển thị cho từng series: nhãn, icon và màu.\n * Dùng `color` cho màu dùng chung, hoặc `theme` khi cần màu khác nhau giữa light/dark.\n * Giá trị màu nên là token: `\"var(--chart-1)\"`, `\"var(--teal)\"`… — đừng ghi hex.\n */\nexport type ChartConfig = {\n [k in string]: {\n label?: React.ReactNode;\n icon?: React.ComponentType;\n } & ({ color?: string; theme?: never } | { color?: never; theme: Record<keyof typeof THEMES, string> });\n};\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\n/** Đọc `config` của `<ChartContainer>` gần nhất. Ném lỗi nếu gọi ngoài container. */\nfunction useChart(): ChartContextProps {\n const context = React.useContext(ChartContext);\n\n if (!context) {\n throw new Error(\"useChart phải được gọi bên trong <ChartContainer />\");\n }\n\n return context;\n}\n\nexport type ChartContainerProps = React.ComponentProps<\"div\"> & {\n /** Khai báo nhãn/màu cho từng series — nguồn của biến `--color-<key>`. */\n config: ChartConfig;\n children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>[\"children\"];\n};\n\n/**\n * Khung biểu đồ: cấp context `config`, bơm biến màu, và bọc sẵn `ResponsiveContainer`.\n * Tỉ lệ mặc định `aspect-video`; muốn cao cố định thì truyền `className=\"h-64 aspect-auto\"`.\n */\nfunction ChartContainer({ id, className, children, config, ...props }: ChartContainerProps) {\n const uniqueId = React.useId();\n const chartId = `chart-${id || uniqueId.replace(/:/g, \"\")}`;\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-slot=\"chart\"\n data-chart={chartId}\n className={cn(\n \"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n className\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n );\n}\n\nexport type ChartStyleProps = {\n /** Giá trị của thuộc tính `data-chart` trên container — dùng làm phạm vi cho biến màu. */\n id: string;\n config: ChartConfig;\n};\n\n/**\n * Sinh thẻ `<style>` chứa `--color-<key>` cho từng series, phạm vi `[data-chart=<id>]`.\n * `ChartContainer` đã render sẵn; chỉ export riêng cho ai tự dựng container.\n */\nconst ChartStyle = ({ id, config }: ChartStyleProps) => {\n const colorConfig = Object.entries(config).filter(([, itemConfig]) => itemConfig.theme || itemConfig.color);\n\n if (!colorConfig.length) {\n return null;\n }\n\n // Một khối CSS cho mỗi theme: light không cần selector bọc ngoài, dark bọc bằng `.dark`.\n const css = Object.entries(THEMES)\n .map(([theme, prefix]) => {\n const declarations = colorConfig\n .map(([key, itemConfig]) => {\n const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color;\n return color ? ` --color-${key}: ${color};` : null;\n })\n .filter(Boolean)\n .join(\"\\n\");\n\n return `${prefix} [data-chart=${id}] {\\n${declarations}\\n}`;\n })\n .join(\"\\n\");\n\n // biome-ignore lint/security/noDangerouslySetInnerHtml: phải bơm nguyên khối CSS gồm nhiều selector (light + `.dark`) — React không có API nào khác cho việc này; nội dung ghép từ `config` do chính app khai, không phải dữ liệu người dùng nhập.\n return <style dangerouslySetInnerHTML={{ __html: css }} />;\n};\n\n/** `Tooltip` của recharts — đặt `content={<ChartTooltipContent />}` để có giao diện theo token. */\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nexport type ChartTooltipContentProps = React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n React.ComponentProps<\"div\"> & {\n /** Ẩn dòng nhãn (trục X) ở đầu tooltip. */\n hideLabel?: boolean;\n /** Ẩn ô màu đứng trước tên series. */\n hideIndicator?: boolean;\n /** Kiểu ô màu: chấm tròn / vạch đứng / vạch đứt. */\n indicator?: \"line\" | \"dot\" | \"dashed\";\n /** Khoá dùng để tra `config` cho TÊN series (mặc định `name` rồi tới `dataKey`). */\n nameKey?: string;\n /** Khoá dùng để tra `config` cho NHÃN trục X. */\n labelKey?: string;\n };\n\n/** Nội dung tooltip theo design token Comitor: nền `bg-background`, viền `border-border/50`. */\nfunction ChartTooltipContent({\n active,\n payload,\n className,\n indicator = \"dot\",\n hideLabel = false,\n hideIndicator = false,\n label,\n labelFormatter,\n labelClassName,\n formatter,\n color,\n nameKey,\n labelKey\n}: ChartTooltipContentProps) {\n const { config } = useChart();\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload?.length) {\n return null;\n }\n\n const [item] = payload;\n const key = `${labelKey || item?.dataKey || item?.name || \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const value = !labelKey && typeof label === \"string\" ? config[label]?.label || label : itemConfig?.label;\n\n if (labelFormatter) {\n return <div className={cn(\"font-medium\", labelClassName)}>{labelFormatter(value, payload)}</div>;\n }\n\n if (!value) {\n return null;\n }\n\n return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>;\n }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);\n\n if (!active || !payload?.length) {\n return null;\n }\n\n // Chỉ một series + không phải chấm tròn → nhét nhãn vào trong hàng cho gọn.\n const nestLabel = payload.length === 1 && indicator !== \"dot\";\n\n return (\n <div\n className={cn(\n \"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-overlay\",\n className\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload.map((item, index) => {\n const key = `${nameKey || item.name || item.dataKey || \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const indicatorColor = color || item.payload?.fill || item.color;\n const rowKey = `${item.dataKey ?? item.name ?? index}`;\n\n return (\n <div\n key={rowKey}\n className={cn(\n \"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5\",\n indicator === \"dot\" && \"items-center\"\n )}\n >\n {formatter && item?.value !== undefined && item.name ? (\n formatter(item.value, item.name, item, index, payload)\n ) : (\n <>\n {itemConfig?.icon ? (\n <itemConfig.icon />\n ) : (\n !hideIndicator && (\n <div\n className={cn(\"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)\", {\n \"h-2.5 w-2.5\": indicator === \"dot\",\n \"w-1\": indicator === \"line\",\n \"w-0 border-[1.5px] border-dashed bg-transparent\": indicator === \"dashed\",\n \"my-0.5\": nestLabel && indicator === \"dashed\"\n })}\n style={\n {\n \"--color-bg\": indicatorColor,\n \"--color-border\": indicatorColor\n } as React.CSSProperties\n }\n />\n )\n )}\n <div\n className={cn(\"flex flex-1 justify-between leading-none\", nestLabel ? \"items-end\" : \"items-center\")}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-muted-foreground\">{itemConfig?.label || item.name}</span>\n </div>\n {item.value !== undefined && (\n <span className=\"text-foreground font-mono font-medium tabular-nums\">\n {item.value.toLocaleString(\"vi-VN\")}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\n/** `Legend` của recharts — đặt `content={<ChartLegendContent />}` để lấy nhãn từ `config`. */\nconst ChartLegend = RechartsPrimitive.Legend;\n\nexport type ChartLegendContentProps = React.ComponentProps<\"div\"> &\n Pick<RechartsPrimitive.LegendProps, \"payload\" | \"verticalAlign\"> & {\n /** Ẩn icon riêng của series (nếu `config` có khai `icon`). */\n hideIcon?: boolean;\n /** Khoá dùng để tra `config` (mặc định `dataKey`). */\n nameKey?: string;\n };\n\n/** Chú giải nằm ngang, nhãn lấy từ `config` chứ không phải `dataKey` thô. */\nfunction ChartLegendContent({\n className,\n hideIcon = false,\n payload,\n verticalAlign = \"bottom\",\n nameKey\n}: ChartLegendContentProps) {\n const { config } = useChart();\n\n if (!payload?.length) {\n return null;\n }\n\n return (\n <div className={cn(\"flex items-center justify-center gap-4\", verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\", className)}>\n {payload.map((item, index) => {\n const key = `${nameKey || item.dataKey || \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const rowKey = `${item.value ?? item.dataKey ?? index}`;\n\n return (\n <div key={rowKey} className=\"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3\">\n {itemConfig?.icon && !hideIcon ? (\n <itemConfig.icon />\n ) : (\n <div\n className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n style={{\n backgroundColor: item.color\n }}\n />\n )}\n {itemConfig?.label}\n </div>\n );\n })}\n </div>\n );\n}\n\n/**\n * Tra `config` từ một phần tử payload của recharts.\n * Payload có 2 tầng (`item` và `item.payload`) và khoá tra có thể nằm ở tầng nào cũng được —\n * hàm này thử cả hai trước khi rơi về chính `key`.\n */\nfunction getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n if (typeof payload !== \"object\" || payload === null) {\n return undefined;\n }\n\n const payloadPayload =\n \"payload\" in payload && typeof payload.payload === \"object\" && payload.payload !== null\n ? payload.payload\n : undefined;\n\n let configLabelKey: string = key;\n\n if (key in payload && typeof payload[key as keyof typeof payload] === \"string\") {\n configLabelKey = payload[key as keyof typeof payload] as string;\n } else if (\n payloadPayload &&\n key in payloadPayload &&\n typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n ) {\n configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;\n }\n\n return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport { ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, useChart };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/chart/chart.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Chart primitive của shadcn/ui (style new-york) — port nguyên trạng từ comitor-ds để\n * `shadcn add` vẫn dùng lại được. Đây là tầng \"thô\": bạn tự lắp recharts bên trong.\n * Muốn dựng nhanh 3 loại biểu đồ quen thuộc thì xem `comitor-chart.tsx`.\n *\n * ⚠⚠ TỰ LẮP RECHARTS 2.15.x THÌ PHẢI TỰ TẮT HIỆU ỨNG VẼ DẦN ⚠⚠\n * Xem chú thích của {@link ChartContainer} ngay dưới. Tầng này KHÔNG đặt hộ được\n * `isAnimationActive={false}` như `Comitor*Chart` làm, vì mark là children do app viết.\n *\n * Cơ chế màu (điểm hay nhất, và cũng dễ hiểu sai nhất):\n * `<ChartContainer>` render kèm `<ChartStyle>` — thẻ này sinh ra biến CSS `--color-<key>`\n * cho từng key trong `config`, giới hạn phạm vi bằng `[data-chart=<id>]`. Series recharts\n * chỉ việc trỏ `fill=\"var(--color-desktop)\"`. Nhờ vậy màu vẫn nằm trong TOKEN (không hex\n * rải trong JSX) và đổi được theo light/dark khi config khai `theme: { light, dark }`.\n */\n\n// Ánh xạ { TÊN_THEME: CSS_SELECTOR }. Chuỗi rỗng = light (không cần selector bọc ngoài).\nconst THEMES = { light: \"\", dark: \".dark\" } as const;\n\n/**\n * Khai báo hiển thị cho từng series: nhãn, icon và màu.\n * Dùng `color` cho màu dùng chung, hoặc `theme` khi cần màu khác nhau giữa light/dark.\n * Giá trị màu nên là token: `\"var(--chart-1)\"`, `\"var(--teal)\"`… — đừng ghi hex.\n */\nexport type ChartConfig = {\n [k in string]: {\n label?: React.ReactNode;\n icon?: React.ComponentType;\n } & ({ color?: string; theme?: never } | { color?: never; theme: Record<keyof typeof THEMES, string> });\n};\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\n/** Đọc `config` của `<ChartContainer>` gần nhất. Ném lỗi nếu gọi ngoài container. */\nfunction useChart(): ChartContextProps {\n const context = React.useContext(ChartContext);\n\n if (!context) {\n throw new Error(\"useChart phải được gọi bên trong <ChartContainer />\");\n }\n\n return context;\n}\n\nexport type ChartContainerProps = React.ComponentProps<\"div\"> & {\n /** Khai báo nhãn/màu cho từng series — nguồn của biến `--color-<key>`. */\n config: ChartConfig;\n children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>[\"children\"];\n};\n\n/**\n * Khung biểu đồ: cấp context `config`, bơm biến màu, và bọc sẵn `ResponsiveContainer`.\n * Tỉ lệ mặc định `aspect-video`; muốn cao cố định thì truyền `className=\"h-64 aspect-auto\"`.\n *\n * ─ ⚠ RECHARTS 2.15.x: PHẢI TỰ GHI `isAnimationActive={false}` LÊN TỪNG MARK ─\n * 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ờ\n * 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,\n * đủ 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.\n *\n * `<ComitorBarChart>` / `<ComitorLineChart>` / `<ComitorAreaChart>` đã đặt sẵn `false` từ `0.2.2`.\n * **`<ChartContainer>` thì KHÔNG đặt hộ được** — `<Bar>` / `<Line>` / `<Area>` là children do app\n * 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\n * của `0.2.1`:\n *\n * ```tsx\n * // ✗ recharts 2.15.x: <g class=\"recharts-bar-rectangle\"> rỗng, không cột nào\n * <ChartContainer config={config}>\n * <BarChart data={data}>\n * <XAxis dataKey=\"thang\" />\n * <Bar dataKey=\"doanhThu\" fill=\"var(--color-doanhThu)\" />\n * </BarChart>\n * </ChartContainer>\n *\n * // ✓ tắt tay trên TỪNG mark\n * <Bar dataKey=\"doanhThu\" fill=\"var(--color-doanhThu)\" isAnimationActive={false} />\n * ```\n *\n * Đo trên DOM thật (Next 16.3.2 · React 19.2.8 · recharts 2.15.4, `next build` + `next start`,\n * chờ 5000ms sau hydrate) — cùng một trang, chỉ khác một prop:\n *\n * | mark | thiếu prop | có `isAnimationActive={false}` |\n * |---|---|---|\n * | `<Bar>` | 3 `<g.recharts-bar-rectangle>` nhưng **0** `path.recharts-rectangle` | **3/3** path, cao 273/410/342px |\n * | `<Line>` | `stroke-dasharray=\"0px 818.84px\"` | `stroke-dasharray` = `null` |\n * | `<Area>` | `<clipPath><rect width=\"0\">` | không còn rect width 0 |\n *\n * `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\n * 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\n * 3.x nó chỉ có nghĩa \"không hoạt hình\".\n */\nfunction ChartContainer({ id, className, children, config, ...props }: ChartContainerProps) {\n const uniqueId = React.useId();\n const chartId = `chart-${id || uniqueId.replace(/:/g, \"\")}`;\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-slot=\"chart\"\n data-chart={chartId}\n className={cn(\n \"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n className\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n );\n}\n\nexport type ChartStyleProps = {\n /** Giá trị của thuộc tính `data-chart` trên container — dùng làm phạm vi cho biến màu. */\n id: string;\n config: ChartConfig;\n};\n\n/**\n * Sinh thẻ `<style>` chứa `--color-<key>` cho từng series, phạm vi `[data-chart=<id>]`.\n * `ChartContainer` đã render sẵn; chỉ export riêng cho ai tự dựng container.\n */\nconst ChartStyle = ({ id, config }: ChartStyleProps) => {\n const colorConfig = Object.entries(config).filter(([, itemConfig]) => itemConfig.theme || itemConfig.color);\n\n if (!colorConfig.length) {\n return null;\n }\n\n // Một khối CSS cho mỗi theme: light không cần selector bọc ngoài, dark bọc bằng `.dark`.\n const css = Object.entries(THEMES)\n .map(([theme, prefix]) => {\n const declarations = colorConfig\n .map(([key, itemConfig]) => {\n const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color;\n return color ? ` --color-${key}: ${color};` : null;\n })\n .filter(Boolean)\n .join(\"\\n\");\n\n return `${prefix} [data-chart=${id}] {\\n${declarations}\\n}`;\n })\n .join(\"\\n\");\n\n // biome-ignore lint/security/noDangerouslySetInnerHtml: phải bơm nguyên khối CSS gồm nhiều selector (light + `.dark`) — React không có API nào khác cho việc này; nội dung ghép từ `config` do chính app khai, không phải dữ liệu người dùng nhập.\n return <style dangerouslySetInnerHTML={{ __html: css }} />;\n};\n\n/** `Tooltip` của recharts — đặt `content={<ChartTooltipContent />}` để có giao diện theo token. */\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\n/**\n * Một phần tử `payload` do recharts bơm vào `content` của Tooltip/Legend.\n *\n * ─ VÌ SAO KHAI TẠI ĐÂY, KHÔNG DẪN XUẤT TỪ `TooltipProps`/`LegendProps` ─\n * recharts **3** đã bỏ `payload` (và `label`) khỏi props của `<Tooltip>`/`<Legend>` — hai\n * trường đó nay đi qua context nội bộ, nên `ComponentProps<typeof Tooltip>[\"payload\"]` và\n * `Pick<LegendProps, \"payload\">` **không còn tồn tại** ở 3.x. Dẫn xuất từ chúng khiến chính\n * file `.d.ts` phát hành ra npm hỏng kiểu ngay khi app cài recharts 3 (app nào tắt\n * `skipLibCheck` là thấy liền), tức peer `>=2.15.0` sẽ là một lời hứa suông.\n *\n * Hình dạng dưới đây là phần GIAO của 2.x và 3.x, và chỉ gồm đúng những trường component\n * này thật sự đọc. Ở 2.x nó **giao** với kiểu gốc của recharts (props Tooltip vẫn còn\n * `payload`) nên không nới lỏng gì; ở 3.x nó là nguồn duy nhất.\n */\nexport interface ChartPayloadItem {\n value?: number;\n name?: string;\n dataKey?: string | number;\n color?: string;\n /** Bản ghi gốc của điểm dữ liệu — nơi `fill` và các cột khác của hàng nằm. */\n payload?: Record<string, unknown> & { fill?: string };\n}\n\n/**\n * Chữ ký của `formatter`/`labelFormatter` mà component này THẬT SỰ gọi.\n *\n * Kiểu khai báo của hai prop đó vẫn là kiểu của recharts (đến từ `ComponentProps<typeof\n * Tooltip>`, để app truyền vào không bị hẹp đi), nhưng recharts 3 đòi tham số `item` là\n * `Payload` **đầy đủ** — gồm cả trường nội bộ `graphicalItemId` mà không ai ngoài recharts\n * dựng được. Nên chỗ GỌI đi qua hai chữ ký dưới đây kèm một lần ép kiểu tường minh: giá trị\n * truyền vào là thứ recharts vừa bơm ra, chỉ là TypeScript không lần được đường vòng đó.\n *\n * Tham số để `unknown` là CỐ Ý, không phải lười: hình dạng thật của `value`/`item` khác nhau\n * giữa hai bản recharts (2.x cho `value: ValueType` = `string | number | (string|number)[]`,\n * 3.x cho `Payload` có thêm trường nội bộ), nên ghim bất kỳ bên nào cũng làm bên kia đỏ. Đây\n * là cái cầu giữa hai bản, chỗ duy nhất trong gói không kiểm được kiểu — và nó dài đúng hai\n * lời gọi.\n */\ntype ChartValueFormatter = (\n value: unknown,\n name: unknown,\n item: unknown,\n index: number,\n payload: unknown\n) => React.ReactNode;\n\ntype ChartLabelFormatter = (label: unknown, payload: unknown) => React.ReactNode;\n\nexport type ChartTooltipContentProps = React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n React.ComponentProps<\"div\"> & {\n /** Do recharts bơm vào — đừng truyền tay. */\n payload?: ChartPayloadItem[];\n /** Do recharts bơm vào — đừng truyền tay. */\n label?: React.ReactNode;\n /** Ẩn dòng nhãn (trục X) ở đầu tooltip. */\n hideLabel?: boolean;\n /** Ẩn ô màu đứng trước tên series. */\n hideIndicator?: boolean;\n /** Kiểu ô màu: chấm tròn / vạch đứng / vạch đứt. */\n indicator?: \"line\" | \"dot\" | \"dashed\";\n /** Khoá dùng để tra `config` cho TÊN series (mặc định `name` rồi tới `dataKey`). */\n nameKey?: string;\n /** Khoá dùng để tra `config` cho NHÃN trục X. */\n labelKey?: string;\n };\n\n/** Nội dung tooltip theo design token Comitor: nền `bg-background`, viền `border-border/50`. */\nfunction ChartTooltipContent({\n active,\n payload,\n className,\n indicator = \"dot\",\n hideLabel = false,\n hideIndicator = false,\n label,\n labelFormatter,\n labelClassName,\n formatter,\n color,\n nameKey,\n labelKey\n}: ChartTooltipContentProps) {\n const { config } = useChart();\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload?.length) {\n return null;\n }\n\n const [item] = payload;\n const key = `${labelKey || item?.dataKey || item?.name || \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const value = !labelKey && typeof label === \"string\" ? config[label]?.label || label : itemConfig?.label;\n\n if (labelFormatter) {\n return (\n <div className={cn(\"font-medium\", labelClassName)}>\n {(labelFormatter as unknown as ChartLabelFormatter)(value, payload)}\n </div>\n );\n }\n\n if (!value) {\n return null;\n }\n\n return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>;\n }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);\n\n if (!active || !payload?.length) {\n return null;\n }\n\n // Chỉ một series + không phải chấm tròn → nhét nhãn vào trong hàng cho gọn.\n const nestLabel = payload.length === 1 && indicator !== \"dot\";\n\n return (\n <div\n className={cn(\n \"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-overlay\",\n className\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload.map((item, index) => {\n const key = `${nameKey || item.name || item.dataKey || \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const indicatorColor = color || item.payload?.fill || item.color;\n const rowKey = `${item.dataKey ?? item.name ?? index}`;\n\n return (\n <div\n key={rowKey}\n className={cn(\n \"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5\",\n indicator === \"dot\" && \"items-center\"\n )}\n >\n {formatter && item?.value !== undefined && item.name ? (\n (formatter as unknown as ChartValueFormatter)(item.value, item.name, item, index, payload)\n ) : (\n <>\n {itemConfig?.icon ? (\n <itemConfig.icon />\n ) : (\n !hideIndicator && (\n <div\n className={cn(\"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)\", {\n \"h-2.5 w-2.5\": indicator === \"dot\",\n \"w-1\": indicator === \"line\",\n \"w-0 border-[1.5px] border-dashed bg-transparent\": indicator === \"dashed\",\n \"my-0.5\": nestLabel && indicator === \"dashed\"\n })}\n style={\n {\n \"--color-bg\": indicatorColor,\n \"--color-border\": indicatorColor\n } as React.CSSProperties\n }\n />\n )\n )}\n <div\n className={cn(\"flex flex-1 justify-between leading-none\", nestLabel ? \"items-end\" : \"items-center\")}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-muted-foreground\">{itemConfig?.label || item.name}</span>\n </div>\n {item.value !== undefined && (\n <span className=\"text-foreground font-mono font-medium tabular-nums\">\n {item.value.toLocaleString(\"vi-VN\")}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\n/** `Legend` của recharts — đặt `content={<ChartLegendContent />}` để lấy nhãn từ `config`. */\nconst ChartLegend = RechartsPrimitive.Legend;\n\nexport type ChartLegendContentProps = React.ComponentProps<\"div\"> & {\n /** Do recharts bơm vào — đừng truyền tay. Xem `ChartPayloadItem` về lý do không dẫn xuất từ `LegendProps`. */\n payload?: ChartPayloadItem[];\n /** Do recharts bơm vào — quyết định chú giải đệm trên hay đệm dưới. */\n verticalAlign?: \"top\" | \"middle\" | \"bottom\";\n /** Ẩn icon riêng của series (nếu `config` có khai `icon`). */\n hideIcon?: boolean;\n /** Khoá dùng để tra `config` (mặc định `dataKey`). */\n nameKey?: string;\n};\n\n/** Chú giải nằm ngang, nhãn lấy từ `config` chứ không phải `dataKey` thô. */\nfunction ChartLegendContent({\n className,\n hideIcon = false,\n payload,\n verticalAlign = \"bottom\",\n nameKey\n}: ChartLegendContentProps) {\n const { config } = useChart();\n\n if (!payload?.length) {\n return null;\n }\n\n return (\n <div className={cn(\"flex items-center justify-center gap-4\", verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\", className)}>\n {payload.map((item, index) => {\n const key = `${nameKey || item.dataKey || \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const rowKey = `${item.value ?? item.dataKey ?? index}`;\n\n return (\n <div key={rowKey} className=\"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3\">\n {itemConfig?.icon && !hideIcon ? (\n <itemConfig.icon />\n ) : (\n <div\n className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n style={{\n backgroundColor: item.color\n }}\n />\n )}\n {itemConfig?.label}\n </div>\n );\n })}\n </div>\n );\n}\n\n/**\n * Tra `config` từ một phần tử payload của recharts.\n * Payload có 2 tầng (`item` và `item.payload`) và khoá tra có thể nằm ở tầng nào cũng được —\n * hàm này thử cả hai trước khi rơi về chính `key`.\n */\nfunction getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n if (typeof payload !== \"object\" || payload === null) {\n return undefined;\n }\n\n const payloadPayload =\n \"payload\" in payload && typeof payload.payload === \"object\" && payload.payload !== null\n ? payload.payload\n : undefined;\n\n let configLabelKey: string = key;\n\n if (key in payload && typeof payload[key as keyof typeof payload] === \"string\") {\n configLabelKey = payload[key as keyof typeof payload] as string;\n } else if (\n payloadPayload &&\n key in payloadPayload &&\n typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n ) {\n configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;\n }\n\n return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport { ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, useChart };\n"],"mappings":"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","names":[]}
|