@comitor/ui 0.6.2 → 0.7.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 CHANGED
@@ -501,6 +501,14 @@ shadcn/ui style `new-york`, đã tùy biến token theo brand Comitor.
501
501
  | Hành động | `Button`, `ButtonGroup`, `Toggle`, `ToggleGroup`, `Kbd` |
502
502
  | Nhập liệu | `Input`, `InputGroup`, `InputOTP`, `Textarea`, `Label`, `Field`, `Checkbox`, `RadioGroup`, `Switch`, `Slider`, `Select`, `Calendar` |
503
503
  | Hiển thị | `Card`, `Badge`, `Avatar`, `Table`, `Separator`, `AspectRatio`, `Skeleton`, `Spinner`, `Progress`, `Empty`, `Item`, `Alert` |
504
+
505
+ `Alert` có ba variant: `default` · `warning` · `destructive` — hai variant sau tô CHỮ bằng bậc `-ink`
506
+ trên `bg-card`, không tô nền màu (bậc tô nền làm chữ chỉ được 1,59:1 ở light ✗).
507
+
508
+ `Progress` có `tone`: `default` · `success` · `destructive`. **Cố ý không có `warning`**: ở bảng màu
509
+ light `--warning-ink` và `--primary-ink` là cùng một giá trị (`#7A5800`), nên thanh "cảnh báo" sẽ
510
+ trông y hệt thanh thường. Cần báo sắp-chạm-ngưỡng thì nói bằng `StatusPill`/`Badge` tông warning
511
+ hoặc một dòng chữ cạnh thanh — xem chú thích đầu `progress.tsx`.
504
512
  | Lớp phủ | `Dialog`, `AlertDialog`, `Sheet`, `Drawer`, `Popover`, `HoverCard`, `Tooltip`, `DropdownMenu`, `ContextMenu`, `Menubar`, `Command` |
505
513
  | Điều hướng | `Tabs`, `Breadcrumb`, `Pagination`, `NavigationMenu`, `Accordion`, `Collapsible` |
506
514
  | Khác | `ScrollArea`, `Resizable`, `Carousel` |
@@ -560,7 +568,7 @@ Giá trị thật của package: pattern lặp đi lặp lại ở mọi app, gh
560
568
  | `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 |
561
569
  | `ConfirmDialog`, `CommandPalette` | Hộp xác nhận, bảng lệnh `⌘K` |
562
570
  | `Toaster`, `toast()`, `toastPromise()`, `dismissToast()` | Thông báo có icon theo trạng thái |
563
- | `CopyButton`, `RelativeTime`, `InlineEdit`, `KeyboardHint` | Tiện ích nhỏ dùng khắp nơi |
571
+ | `CopyButton`, `RelativeTime`, `InlineEdit`, `KeyboardHint` | Tiện ích nhỏ dùng khắp nơi. `RelativeTime` nhận `now?: Date` để đo từ một mốc CỐ ĐỊNH thay vì đồng hồ thật — dùng cho dữ liệu đứng yên (bản mẫu, ảnh chụp báo cáo, test); có `now` thì nó tự tắt hẹn giờ cập nhật và kết quả trở nên tất định |
564
572
  | `useIsMac` | Hook đi kèm `KeyboardHint` — `true` khi chạy trên macOS (render đầu luôn `false`, xem §3). **Cửa xuất duy nhất của cả gói**; `/shell` không re-export |
565
573
 
566
574
  Kèm 2 module thuần (không phải component, dùng được cả hai phía):
@@ -4,9 +4,25 @@ import { ComponentProps } from 'react';
4
4
  interface RelativeTimeProps extends Omit<ComponentProps<"time">, "dateTime" | "children"> {
5
5
  /** Thời điểm cần hiển thị: Date, ISO string hoặc epoch ms */
6
6
  value: Date | string | number;
7
+ /**
8
+ * Mốc để ĐO TỪ ĐÓ. Bỏ trống = đồng hồ thật (hành vi mặc định, đúng cho bản ghi từ backend).
9
+ *
10
+ * Truyền `now` khi dữ liệu ĐỨNG YÊN — bản mẫu, ảnh chụp báo cáo, kết quả test: đồng hồ thật đo
11
+ * trên mốc cố định thì kết quả trôi mỗi ngày, và mốc nào lỡ nằm ở tương lai in ra "2 giờ nữa",
12
+ * một câu vô nghĩa mà TypeScript lẫn linter đều thấy hợp lệ.
13
+ *
14
+ * Có `now` thì component KHÔNG tự cập nhật nữa (xem `updateIntervalMs`) và kết quả trở nên tất
15
+ * định — server và client cho ra cùng một chuỗi.
16
+ */
17
+ now?: Date;
7
18
  /** Thêm hậu tố "trước"/"nữa" (mặc định true) */
8
19
  addSuffix?: boolean;
9
- /** Chu kỳ tự cập nhật (ms). 0 = không tự cập nhật. Mặc định 60_000. */
20
+ /**
21
+ * Chu kỳ tự cập nhật (ms). 0 = không tự cập nhật. Mặc định 60_000.
22
+ *
23
+ * ⚠ Bị BỎ QUA khi có `now`: đo từ một mốc cố định thì chuỗi hiển thị không bao giờ đổi, nên hẹn
24
+ * giờ chỉ để render lại y nguyên mỗi phút.
25
+ */
10
26
  updateIntervalMs?: number;
11
27
  /** Định dạng của tooltip `title` (mặc định "HH:mm 'ngày' dd/MM/yyyy") */
12
28
  titleFormat?: string;
@@ -21,8 +37,12 @@ interface RelativeTimeProps extends Omit<ComponentProps<"time">, "dateTime" | "c
21
37
  * "45 ngày trước" là hai câu chuyện khác hẳn nhau.
22
38
  *
23
39
  * `title` luôn giữ mốc thời gian tuyệt đối để người dùng rà chuột là biết chính xác.
24
- * Có `suppressHydrationWarning` vì đồng hồ server và client gần như luôn lệch vài giây.
40
+ * Có `suppressHydrationWarning` vì đồng hồ server và client gần như luôn lệch vài giây — trừ khi
41
+ * truyền `now`, lúc đó kết quả tất định và thuộc tính này chỉ còn là vô hại.
42
+ *
43
+ * Hai lối đo dùng CHUNG một cách diễn đạt (`…Strict` của date-fns, locale `vi`), nên đổi giữa
44
+ * `now` và đồng hồ thật không làm chữ đổi giọng: vẫn "12 phút trước", không phải "khoảng 12 phút".
25
45
  */
26
- declare function RelativeTime({ value, addSuffix, updateIntervalMs, titleFormat, fallback, className, ...props }: RelativeTimeProps): React.JSX.Element;
46
+ declare function RelativeTime({ value, now, addSuffix, updateIntervalMs, titleFormat, fallback, className, ...props }: RelativeTimeProps): React.JSX.Element;
27
47
 
28
48
  export { RelativeTime, type RelativeTimeProps };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { format as formatDate, formatDistanceToNowStrict, isValid } from "date-fns";
3
+ import { format as formatDate, formatDistanceStrict, formatDistanceToNowStrict, isValid } from "date-fns";
4
4
  import { vi } from "date-fns/locale";
5
5
  import { useEffect, useState } from "react";
6
6
  function toDate(value) {
@@ -8,6 +8,7 @@ function toDate(value) {
8
8
  }
9
9
  function RelativeTime({
10
10
  value,
11
+ now,
11
12
  addSuffix = true,
12
13
  updateIntervalMs = 6e4,
13
14
  titleFormat = "HH:mm 'ng\xE0y' dd/MM/yyyy",
@@ -17,12 +18,13 @@ function RelativeTime({
17
18
  }) {
18
19
  const date = toDate(value);
19
20
  const valid = isValid(date);
21
+ const tickIntervalMs = now ? 0 : updateIntervalMs;
20
22
  const [, setTick] = useState(0);
21
23
  useEffect(() => {
22
- if (!valid || !updateIntervalMs) return;
23
- const timer = setInterval(() => setTick((current) => current + 1), updateIntervalMs);
24
+ if (!valid || !tickIntervalMs) return;
25
+ const timer = setInterval(() => setTick((current) => current + 1), tickIntervalMs);
24
26
  return () => clearInterval(timer);
25
- }, [valid, updateIntervalMs]);
27
+ }, [valid, tickIntervalMs]);
26
28
  if (!valid) {
27
29
  return /* @__PURE__ */ jsx("span", { className, children: fallback });
28
30
  }
@@ -34,7 +36,7 @@ function RelativeTime({
34
36
  suppressHydrationWarning: true,
35
37
  className,
36
38
  ...props,
37
- children: formatDistanceToNowStrict(date, { addSuffix, locale: vi })
39
+ children: now ? formatDistanceStrict(date, now, { addSuffix, locale: vi }) : formatDistanceToNowStrict(date, { addSuffix, locale: vi })
38
40
  }
39
41
  );
40
42
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/composite/relative-time.tsx"],"sourcesContent":["\"use client\";\n\nimport { format as formatDate, formatDistanceToNowStrict, isValid } from \"date-fns\";\nimport { vi } from \"date-fns/locale\";\nimport { type ComponentProps, useEffect, useState } from \"react\";\n\nexport interface RelativeTimeProps extends Omit<ComponentProps<\"time\">, \"dateTime\" | \"children\"> {\n /** Thời điểm cần hiển thị: Date, ISO string hoặc epoch ms */\n value: Date | string | number;\n /** Thêm hậu tố \"trước\"/\"nữa\" (mặc định true) */\n addSuffix?: boolean;\n /** Chu kỳ tự cập nhật (ms). 0 = không tự cập nhật. Mặc định 60_000. */\n updateIntervalMs?: number;\n /** Định dạng của tooltip `title` (mặc định \"HH:mm 'ngày' dd/MM/yyyy\") */\n titleFormat?: string;\n /** Nội dung thay thế khi `value` không hợp lệ */\n fallback?: string;\n}\n\nfunction toDate(value: Date | string | number): Date {\n return value instanceof Date ? value : new Date(value);\n}\n\n/**\n * Thời gian tương đối kiểu \"3 phút trước\", tự cập nhật theo chu kỳ.\n *\n * Dùng `formatDistanceToNowStrict` (không phải bản thường): bản thường làm tròn kiểu \"khoảng 1\n * tháng\" cho mọi thứ từ 26 ngày đến 45 ngày — trong danh sách công việc thì \"26 ngày trước\" và\n * \"45 ngày trước\" là hai câu chuyện khác hẳn nhau.\n *\n * `title` luôn giữ mốc thời gian tuyệt đối để người dùng rà chuột là biết chính xác.\n * Có `suppressHydrationWarning` vì đồng hồ server và client gần như luôn lệch vài giây.\n */\nexport function RelativeTime({\n value,\n addSuffix = true,\n updateIntervalMs = 60_000,\n titleFormat = \"HH:mm 'ngày' dd/MM/yyyy\",\n fallback = \"—\",\n className,\n ...props\n}: RelativeTimeProps) {\n const date = toDate(value);\n const valid = isValid(date);\n\n const [, setTick] = useState(0);\n\n useEffect(() => {\n if (!valid || !updateIntervalMs) return;\n const timer = setInterval(() => setTick((current) => current + 1), updateIntervalMs);\n return () => clearInterval(timer);\n }, [valid, updateIntervalMs]);\n\n // Ngày hỏng (chuỗi rỗng, null từ API) → KHÔNG render <time>: thẻ này bắt buộc phải có\n // `datetime` hợp lệ, đưa \"Invalid Date\" vào là tạo dữ liệu sai cho trình đọc màn hình và bộ máy tìm kiếm.\n if (!valid) {\n return <span className={className}>{fallback}</span>;\n }\n\n return (\n <time\n dateTime={date.toISOString()}\n title={formatDate(date, titleFormat, { locale: vi })}\n suppressHydrationWarning\n className={className}\n {...props}\n >\n {formatDistanceToNowStrict(date, { addSuffix, locale: vi })}\n </time>\n );\n}\n"],"mappings":";AAwDW;AAtDX,SAAS,UAAU,YAAY,2BAA2B,eAAe;AACzE,SAAS,UAAU;AACnB,SAA8B,WAAW,gBAAgB;AAezD,SAAS,OAAO,OAAqC;AACnD,SAAO,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAK;AACvD;AAYO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,OAAO,OAAO,KAAK;AACzB,QAAM,QAAQ,QAAQ,IAAI;AAE1B,QAAM,CAAC,EAAE,OAAO,IAAI,SAAS,CAAC;AAE9B,YAAU,MAAM;AACd,QAAI,CAAC,SAAS,CAAC,iBAAkB;AACjC,UAAM,QAAQ,YAAY,MAAM,QAAQ,CAAC,YAAY,UAAU,CAAC,GAAG,gBAAgB;AACnF,WAAO,MAAM,cAAc,KAAK;AAAA,EAClC,GAAG,CAAC,OAAO,gBAAgB,CAAC;AAI5B,MAAI,CAAC,OAAO;AACV,WAAO,oBAAC,UAAK,WAAuB,oBAAS;AAAA,EAC/C;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,KAAK,YAAY;AAAA,MAC3B,OAAO,WAAW,MAAM,aAAa,EAAE,QAAQ,GAAG,CAAC;AAAA,MACnD,0BAAwB;AAAA,MACxB;AAAA,MACC,GAAG;AAAA,MAEH,oCAA0B,MAAM,EAAE,WAAW,QAAQ,GAAG,CAAC;AAAA;AAAA,EAC5D;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/composite/relative-time.tsx"],"sourcesContent":["\"use client\";\n\nimport { format as formatDate, formatDistanceStrict, formatDistanceToNowStrict, isValid } from \"date-fns\";\nimport { vi } from \"date-fns/locale\";\nimport { type ComponentProps, useEffect, useState } from \"react\";\n\nexport interface RelativeTimeProps extends Omit<ComponentProps<\"time\">, \"dateTime\" | \"children\"> {\n /** Thời điểm cần hiển thị: Date, ISO string hoặc epoch ms */\n value: Date | string | number;\n /**\n * Mốc để ĐO TỪ ĐÓ. Bỏ trống = đồng hồ thật (hành vi mặc định, đúng cho bản ghi từ backend).\n *\n * Truyền `now` khi dữ liệu ĐỨNG YÊN — bản mẫu, ảnh chụp báo cáo, kết quả test: đồng hồ thật đo\n * trên mốc cố định thì kết quả trôi mỗi ngày, và mốc nào lỡ nằm ở tương lai in ra \"2 giờ nữa\",\n * một câu vô nghĩa mà TypeScript lẫn linter đều thấy hợp lệ.\n *\n * Có `now` thì component KHÔNG tự cập nhật nữa (xem `updateIntervalMs`) và kết quả trở nên tất\n * định — server và client cho ra cùng một chuỗi.\n */\n now?: Date;\n /** Thêm hậu tố \"trước\"/\"nữa\" (mặc định true) */\n addSuffix?: boolean;\n /**\n * Chu kỳ tự cập nhật (ms). 0 = không tự cập nhật. Mặc định 60_000.\n *\n * ⚠ Bị BỎ QUA khi có `now`: đo từ một mốc cố định thì chuỗi hiển thị không bao giờ đổi, nên hẹn\n * giờ chỉ để render lại y nguyên mỗi phút.\n */\n updateIntervalMs?: number;\n /** Định dạng của tooltip `title` (mặc định \"HH:mm 'ngày' dd/MM/yyyy\") */\n titleFormat?: string;\n /** Nội dung thay thế khi `value` không hợp lệ */\n fallback?: string;\n}\n\nfunction toDate(value: Date | string | number): Date {\n return value instanceof Date ? value : new Date(value);\n}\n\n/**\n * Thời gian tương đối kiểu \"3 phút trước\", tự cập nhật theo chu kỳ.\n *\n * Dùng `formatDistanceToNowStrict` (không phải bản thường): bản thường làm tròn kiểu \"khoảng 1\n * tháng\" cho mọi thứ từ 26 ngày đến 45 ngày — trong danh sách công việc thì \"26 ngày trước\" và\n * \"45 ngày trước\" là hai câu chuyện khác hẳn nhau.\n *\n * `title` luôn giữ mốc thời gian tuyệt đối để người dùng rà chuột là biết chính xác.\n * Có `suppressHydrationWarning` vì đồng hồ server và client gần như luôn lệch vài giây — trừ khi\n * truyền `now`, lúc đó kết quả tất định và thuộc tính này chỉ còn là vô hại.\n *\n * Hai lối đo dùng CHUNG một cách diễn đạt (`…Strict` của date-fns, locale `vi`), nên đổi giữa\n * `now` và đồng hồ thật không làm chữ đổi giọng: vẫn \"12 phút trước\", không phải \"khoảng 12 phút\".\n */\nexport function RelativeTime({\n value,\n now,\n addSuffix = true,\n updateIntervalMs = 60_000,\n titleFormat = \"HH:mm 'ngày' dd/MM/yyyy\",\n fallback = \"—\",\n className,\n ...props\n}: RelativeTimeProps) {\n const date = toDate(value);\n const valid = isValid(date);\n\n // Mốc cố định thì chuỗi không bao giờ đổi — tắt hẳn hẹn giờ, kể cả khi người gọi vẫn truyền\n // `updateIntervalMs`. Nếu không, component render lại mỗi phút để in ra đúng chữ cũ.\n const tickIntervalMs = now ? 0 : updateIntervalMs;\n\n const [, setTick] = useState(0);\n\n useEffect(() => {\n if (!valid || !tickIntervalMs) return;\n const timer = setInterval(() => setTick((current) => current + 1), tickIntervalMs);\n return () => clearInterval(timer);\n }, [valid, tickIntervalMs]);\n\n // Ngày hỏng (chuỗi rỗng, null từ API) → KHÔNG render <time>: thẻ này bắt buộc phải có\n // `datetime` hợp lệ, đưa \"Invalid Date\" vào là tạo dữ liệu sai cho trình đọc màn hình và bộ máy tìm kiếm.\n if (!valid) {\n return <span className={className}>{fallback}</span>;\n }\n\n return (\n <time\n dateTime={date.toISOString()}\n title={formatDate(date, titleFormat, { locale: vi })}\n suppressHydrationWarning\n className={className}\n {...props}\n >\n {now\n ? formatDistanceStrict(date, now, { addSuffix, locale: vi })\n : formatDistanceToNowStrict(date, { addSuffix, locale: vi })}\n </time>\n );\n}\n"],"mappings":";AAiFW;AA/EX,SAAS,UAAU,YAAY,sBAAsB,2BAA2B,eAAe;AAC/F,SAAS,UAAU;AACnB,SAA8B,WAAW,gBAAgB;AA+BzD,SAAS,OAAO,OAAqC;AACnD,SAAO,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAK;AACvD;AAgBO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,OAAO,OAAO,KAAK;AACzB,QAAM,QAAQ,QAAQ,IAAI;AAI1B,QAAM,iBAAiB,MAAM,IAAI;AAEjC,QAAM,CAAC,EAAE,OAAO,IAAI,SAAS,CAAC;AAE9B,YAAU,MAAM;AACd,QAAI,CAAC,SAAS,CAAC,eAAgB;AAC/B,UAAM,QAAQ,YAAY,MAAM,QAAQ,CAAC,YAAY,UAAU,CAAC,GAAG,cAAc;AACjF,WAAO,MAAM,cAAc,KAAK;AAAA,EAClC,GAAG,CAAC,OAAO,cAAc,CAAC;AAI1B,MAAI,CAAC,OAAO;AACV,WAAO,oBAAC,UAAK,WAAuB,oBAAS;AAAA,EAC/C;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,KAAK,YAAY;AAAA,MAC3B,OAAO,WAAW,MAAM,aAAa,EAAE,QAAQ,GAAG,CAAC;AAAA,MACnD,0BAAwB;AAAA,MACxB;AAAA,MACC,GAAG;AAAA,MAEH,gBACG,qBAAqB,MAAM,KAAK,EAAE,WAAW,QAAQ,GAAG,CAAC,IACzD,0BAA0B,MAAM,EAAE,WAAW,QAAQ,GAAG,CAAC;AAAA;AAAA,EAC/D;AAEJ;","names":[]}
@@ -3,7 +3,7 @@ import { VariantProps } from 'class-variance-authority';
3
3
  import * as React from 'react';
4
4
 
5
5
  declare const alertVariants: (props?: ({
6
- variant?: "default" | "destructive" | null | undefined;
6
+ variant?: "default" | "warning" | "destructive" | null | undefined;
7
7
  } & class_variance_authority_types.ClassProp) | undefined) => string;
8
8
  interface AlertProps extends React.ComponentProps<"div">, VariantProps<typeof alertVariants> {
9
9
  }
@@ -7,6 +7,20 @@ const alertVariants = cva(
7
7
  variants: {
8
8
  variant: {
9
9
  default: "bg-card text-card-foreground",
10
+ /*
11
+ * `warning` cùng khuôn với `destructive`, chỉ đổi bậc ink — và cũng vì lý do đó: `--warning`
12
+ * là màu TÔ NỀN (#E8B824 ở light), làm chữ trên `bg-card` chỉ 1,59:1 ✗. Bậc ink đạt AA dư
13
+ * biên ở cả hai theme (đo trên `--card`):
14
+ * tiêu đề 6,51:1 (light) · 11,60:1 (dark) ✓
15
+ * mô tả /90 5,17:1 (light) · 9,56:1 (dark) ✓
16
+ *
17
+ * ⚠ Ở bảng màu LIGHT, `--warning-ink` và `--primary-ink` là CÙNG một giá trị (#7A5800 —
18
+ * `--yellow-ink` trỏ về `--gold-500`). Với `Alert` chuyện đó vô hại vì variant `default`
19
+ * dùng `--card-foreground`, nên `warning` vẫn khác hẳn nó (2,90:1). Nhưng nó là lý do
20
+ * `Progress` KHÔNG có tông `warning` — xem chú thích ở đó trước khi thêm `warning` cho một
21
+ * component nào khác mà mặc định đang là brand.
22
+ */
23
+ warning: "text-warning-ink bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-warning-ink/90",
10
24
  destructive: "text-destructive-ink bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive-ink/90"
11
25
  }
12
26
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/alert.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/*\n * Variant `destructive` dùng bậc `--destructive-ink`, KHÔNG dùng `--destructive`.\n * `--destructive` là màu TÔ NỀN (nút xoá) và cố ý ghim một giá trị ở cả hai theme, nên làm CHỮ\n * trên `bg-card` chỉ được 3,53:1 ở dark ✗; phần mô tả còn bị hạ thêm bằng `/90` nên hỏng ở CẢ\n * hai theme (4,32:1 light · 3,08:1 dark ✗).\n * Sau khi đổi (đo trên `--card`): tiêu đề 7,15:1 (light) / 6,42:1 (dark); mô tả `/90` 5,78:1\n * (light) / 5,40:1 (dark) ✓ — bậc `/90` giữ lại vì nó là phân cấp thị giác, và ở bậc ink nó vẫn\n * còn dư biên so với AA 4,5:1.\n */\nconst alertVariants = cva(\n \"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current\",\n {\n variants: {\n variant: {\n default: \"bg-card text-card-foreground\",\n destructive:\n \"text-destructive-ink bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive-ink/90\"\n }\n },\n defaultVariants: {\n variant: \"default\"\n }\n }\n);\n\nexport interface AlertProps extends React.ComponentProps<\"div\">, VariantProps<typeof alertVariants> {}\nexport type AlertTitleProps = React.ComponentProps<\"div\">;\nexport type AlertDescriptionProps = React.ComponentProps<\"div\">;\n\nfunction Alert({ className, variant, ...props }: AlertProps) {\n return <div data-slot=\"alert\" role=\"alert\" className={cn(alertVariants({ variant }), className)} {...props} />;\n}\n\nfunction AlertTitle({ className, ...props }: AlertTitleProps) {\n return (\n <div\n data-slot=\"alert-title\"\n className={cn(\"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight\", className)}\n {...props}\n />\n );\n}\n\nfunction AlertDescription({ className, ...props }: AlertDescriptionProps) {\n return (\n <div\n data-slot=\"alert-description\"\n className={cn(\n \"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Alert, AlertDescription, AlertTitle, alertVariants };\n"],"mappings":"AAkCS;AAlCT,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAWnB,MAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,aACE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAMA,SAAS,MAAM,EAAE,WAAW,SAAS,GAAG,MAAM,GAAe;AAC3D,SAAO,oBAAC,SAAI,aAAU,SAAQ,MAAK,SAAQ,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS,GAAI,GAAG,OAAO;AAC9G;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAAoB;AAC5D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,+DAA+D,SAAS;AAAA,MACrF,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,iBAAiB,EAAE,WAAW,GAAG,MAAM,GAA0B;AACxE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/alert.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/*\n * Variant `destructive` dùng bậc `--destructive-ink`, KHÔNG dùng `--destructive`.\n * `--destructive` là màu TÔ NỀN (nút xoá) và cố ý ghim một giá trị ở cả hai theme, nên làm CHỮ\n * trên `bg-card` chỉ được 3,53:1 ở dark ✗; phần mô tả còn bị hạ thêm bằng `/90` nên hỏng ở CẢ\n * hai theme (4,32:1 light · 3,08:1 dark ✗).\n * Sau khi đổi (đo trên `--card`): tiêu đề 7,15:1 (light) / 6,42:1 (dark); mô tả `/90` 5,78:1\n * (light) / 5,40:1 (dark) ✓ — bậc `/90` giữ lại vì nó là phân cấp thị giác, và ở bậc ink nó vẫn\n * còn dư biên so với AA 4,5:1.\n */\nconst alertVariants = cva(\n \"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current\",\n {\n variants: {\n variant: {\n default: \"bg-card text-card-foreground\",\n /*\n * `warning` cùng khuôn với `destructive`, chỉ đổi bậc ink — và cũng vì lý do đó: `--warning`\n * là màu TÔ NỀN (#E8B824 ở light), làm chữ trên `bg-card` chỉ 1,59:1 ✗. Bậc ink đạt AA dư\n * biên ở cả hai theme (đo trên `--card`):\n * tiêu đề 6,51:1 (light) · 11,60:1 (dark) ✓\n * mô tả /90 5,17:1 (light) · 9,56:1 (dark) ✓\n *\n * ⚠ Ở bảng màu LIGHT, `--warning-ink` và `--primary-ink` là CÙNG một giá trị (#7A5800 —\n * `--yellow-ink` trỏ về `--gold-500`). Với `Alert` chuyện đó vô hại vì variant `default`\n * dùng `--card-foreground`, nên `warning` vẫn khác hẳn nó (2,90:1). Nhưng nó là lý do\n * `Progress` KHÔNG có tông `warning` — xem chú thích ở đó trước khi thêm `warning` cho một\n * component nào khác mà mặc định đang là brand.\n */\n warning: \"text-warning-ink bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-warning-ink/90\",\n destructive:\n \"text-destructive-ink bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive-ink/90\"\n }\n },\n defaultVariants: {\n variant: \"default\"\n }\n }\n);\n\nexport interface AlertProps extends React.ComponentProps<\"div\">, VariantProps<typeof alertVariants> {}\nexport type AlertTitleProps = React.ComponentProps<\"div\">;\nexport type AlertDescriptionProps = React.ComponentProps<\"div\">;\n\nfunction Alert({ className, variant, ...props }: AlertProps) {\n return <div data-slot=\"alert\" role=\"alert\" className={cn(alertVariants({ variant }), className)} {...props} />;\n}\n\nfunction AlertTitle({ className, ...props }: AlertTitleProps) {\n return (\n <div\n data-slot=\"alert-title\"\n className={cn(\"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight\", className)}\n {...props}\n />\n );\n}\n\nfunction AlertDescription({ className, ...props }: AlertDescriptionProps) {\n return (\n <div\n data-slot=\"alert-description\"\n className={cn(\n \"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Alert, AlertDescription, AlertTitle, alertVariants };\n"],"mappings":"AAgDS;AAhDT,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAWnB,MAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcT,SAAS;AAAA,QACT,aACE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAMA,SAAS,MAAM,EAAE,WAAW,SAAS,GAAG,MAAM,GAAe;AAC3D,SAAO,oBAAC,SAAI,aAAU,SAAQ,MAAK,SAAQ,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS,GAAI,GAAG,OAAO;AAC9G;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAAoB;AAC5D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,+DAA+D,SAAS;AAAA,MACrF,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,iBAAiB,EAAE,WAAW,GAAG,MAAM,GAA0B;AACxE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -1,4 +1,6 @@
1
+ import * as class_variance_authority_types from 'class-variance-authority/types';
1
2
  import * as ProgressPrimitive from '@radix-ui/react-progress';
3
+ import { VariantProps } from 'class-variance-authority';
2
4
  import * as React from 'react';
3
5
 
4
6
  /**
@@ -31,8 +33,38 @@ import * as React from 'react';
31
33
  *
32
34
  * Đây đúng vai trò hợp đồng đặt cho `--control-edge` (styles.css §RANH GIỚI): "khi nền control
33
35
  * trong suốt/nhạt thì VIỀN là thứ duy nhất khoanh vùng".
36
+ *
37
+ * ─ TÔNG: BA GIÁ TRỊ, VÀ KHÔNG CÓ `warning` ─
38
+ * `tone` đổi CẢ phần đã chạy lẫn rãnh sang cùng một bậc ink, nên tỉ lệ đọc-được giữ nguyên cấu
39
+ * trúc của bản mặc định. Đo trên ba bề mặt (`--background`, `--card`, `--muted`), tệ nhất mỗi cột:
40
+ *
41
+ * tông đã chạy ⇄ nền trang đã chạy ⇄ rãnh cùng tông /20
42
+ * default 6,09:1 (light) 10,80 (dark) 4,54:1 (light) 6,52:1 (dark) ✓
43
+ * success 6,09:1 9,25 4,55:1 5,88:1 ✓
44
+ * destructive 6,68:1 5,82 4,85:1 4,25:1 ✓
45
+ *
46
+ * ⚠ KHÔNG có `warning`, và đây là quyết định ĐO ĐƯỢC chứ không phải bỏ sót: ở bảng màu light
47
+ * `--warning-ink` và `--primary-ink` là CÙNG một giá trị (#7A5800 — `--yellow-ink` trỏ về
48
+ * `--gold-500`, xem styles.css:174), nên một thanh `tone="warning"` sẽ trông y hệt thanh mặc định.
49
+ * Ở dark hai bậc cũng chỉ cách nhau một nấc vàng (#FACC15 ⇄ #F5D060). Một API đổi tông mà không
50
+ * đổi được gì là thứ tệ hơn không có API: chỗ gọi tưởng đã cảnh báo, người dùng không thấy gì.
51
+ *
52
+ * Cần báo "sắp chạm ngưỡng" thì nói bằng thứ KHÁC màu thanh — `StatusPill`/`Badge` tông warning,
53
+ * hoặc một dòng chữ `text-warning-ink` cạnh thanh; cả hai đều tương phản tốt với nền trang vì
54
+ * chúng không phải cạnh tranh với chính màu brand. Muốn có `tone="warning"` thật thì phải tách
55
+ * `--warning-ink` khỏi `--gold-500` ở TẦNG TOKEN trước — đó là thay đổi bảng màu, không phải thay
56
+ * đổi component, và nó đụng mọi bề mặt warning đang có (Badge, StatusPill, StatCard, Toast).
34
57
  */
35
- type ProgressProps = React.ComponentProps<typeof ProgressPrimitive.Root>;
36
- declare function Progress({ className, value, ...props }: ProgressProps): React.JSX.Element;
58
+ declare const progressTrackVariants: (props?: ({
59
+ tone?: "default" | "success" | "destructive" | null | undefined;
60
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
61
+ declare const progressBarVariants: (props?: ({
62
+ tone?: "default" | "success" | "destructive" | null | undefined;
63
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
64
+ /** Tông của thanh — xem chú thích ở trên về việc vì sao không có `warning`. */
65
+ type ProgressTone = NonNullable<VariantProps<typeof progressBarVariants>["tone"]>;
66
+ interface ProgressProps extends React.ComponentProps<typeof ProgressPrimitive.Root>, VariantProps<typeof progressBarVariants> {
67
+ }
68
+ declare function Progress({ className, value, tone, ...props }: ProgressProps): React.JSX.Element;
37
69
 
38
- export { Progress, type ProgressProps };
70
+ export { Progress, type ProgressProps, type ProgressTone, progressBarVariants, progressTrackVariants };
@@ -1,23 +1,42 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as ProgressPrimitive from "@radix-ui/react-progress";
4
+ import { cva } from "class-variance-authority";
4
5
  import { cn } from "../../lib/cn.js";
5
- function Progress({ className, value, ...props }) {
6
+ const progressTrackVariants = cva("border-control-edge relative h-2 w-full overflow-hidden rounded-full border", {
7
+ variants: {
8
+ tone: {
9
+ default: "bg-primary-ink/20",
10
+ success: "bg-success-ink/20",
11
+ destructive: "bg-destructive-ink/20"
12
+ }
13
+ },
14
+ defaultVariants: { tone: "default" }
15
+ });
16
+ const progressBarVariants = cva("h-full w-full flex-1 transition-transform duration-150 ease-out", {
17
+ variants: {
18
+ tone: {
19
+ default: "bg-primary-ink",
20
+ success: "bg-success-ink",
21
+ destructive: "bg-destructive-ink"
22
+ }
23
+ },
24
+ defaultVariants: { tone: "default" }
25
+ });
26
+ function Progress({ className, value, tone = "default", ...props }) {
6
27
  return /* @__PURE__ */ jsx(
7
28
  ProgressPrimitive.Root,
8
29
  {
9
30
  "data-slot": "progress",
10
- className: cn(
11
- "bg-primary-ink/20 border border-control-edge relative h-2 w-full overflow-hidden rounded-full",
12
- className
13
- ),
31
+ "data-tone": tone,
32
+ className: cn(progressTrackVariants({ tone }), className),
14
33
  value,
15
34
  ...props,
16
35
  children: /* @__PURE__ */ jsx(
17
36
  ProgressPrimitive.Indicator,
18
37
  {
19
38
  "data-slot": "progress-indicator",
20
- className: "bg-primary-ink h-full w-full flex-1 transition-transform duration-150 ease-out",
39
+ className: progressBarVariants({ tone }),
21
40
  style: { transform: `translateX(-${100 - (value || 0)}%)` }
22
41
  }
23
42
  )
@@ -25,6 +44,8 @@ function Progress({ className, value, ...props }) {
25
44
  );
26
45
  }
27
46
  export {
28
- Progress
47
+ Progress,
48
+ progressBarVariants,
49
+ progressTrackVariants
29
50
  };
30
51
  //# sourceMappingURL=progress.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Progress (Radix) — thanh tiến trình xác định (`value` 0…100).\n * Bỏ trống `value` để về trạng thái indeterminate của Radix (thanh đứng yên ở 0);\n * animation cho indeterminate là việc của app.\n *\n * ─ MÀU: `--primary-ink`, KHÔNG PHẢI `--primary` ─\n * Phần đã chạy là thông tin thị giác (WCAG 1.4.11, 3:1) và phải nổi trên CẢ nền trang lẫn phần\n * rãnh còn lại. `bg-primary` gold #E8B824 ở light chỉ **1,86:1** với nền trắng và **1,64:1** với\n * rãnh `bg-primary/20` — thanh 8px vàng nhạt trên nền trắng gần như không thấy. Bậc ink của\n * brand đạt cả hai phía ở cả hai theme:\n *\n * đã chạy ⇄ nền trang : 6,51:1 (light) · 12,67:1 (dark) ✓\n * đã chạy ⇄ rãnh `/20` : 4,85:1 (light) · 7,97:1 (dark) ✓\n * (tệ nhất khi rãnh nằm trên --muted: 4,54:1 · 6,52:1 ✓)\n *\n * ─ RÃNH: TINT `/20` GIỮ NGUYÊN, NHƯNG PHẢI CÓ VIỀN `--control-edge` ─\n * Bản trước chỉ có tint: rãnh ⇄ nền trang mới **1,34:1** (light) và **1,59–1,66:1** (dark), tức\n * KHÔNG nhìn thấy mốc 100% — ở `value` nhỏ không đọc được TỈ LỆ, ở `value=0` cả control biến mất.\n * Cách sửa hiển nhiên (đổi rãnh sang `bg-control-edge` như Slider/Switch) lại hỏng chỗ khác:\n * `--primary-ink` ⇄ `--control-edge` chỉ **1,70:1** (light) / **2,57:1** (dark), mà Progress\n * KHÔNG có núm nên ranh giới đã-chạy/chưa-chạy là thứ DUY NHẤT mang giá trị — mất nó là mất\n * thông tin. Với nền trang trắng không tồn tại cặp màu nào vừa ≥3:1 với nhau vừa ≥3:1 với nền\n * (chứng minh trong chú thích của `Slider`), nên hai ràng buộc phải tách ra hai lớp:\n *\n * khoanh vùng control → `border-control-edge` ⇄ nền trang : 3,83:1 (light) · 4,94:1 (dark) ✓\n * ⇄ --muted : 3,58:1 · 4,21:1 ✓\n * đọc tỉ lệ → đã chạy ⇄ rãnh `/20` : 4,85:1 · 7,97:1 ✓\n *\n * Đây đúng vai trò hợp đồng đặt cho `--control-edge` (styles.css §RANH GIỚI): \"khi nền control\n * trong suốt/nhạt thì VIỀN là thứ duy nhất khoanh vùng\".\n */\n\nexport type ProgressProps = React.ComponentProps<typeof ProgressPrimitive.Root>;\n\nfunction Progress({ className, value, ...props }: ProgressProps) {\n return (\n <ProgressPrimitive.Root\n data-slot=\"progress\"\n className={cn(\n \"bg-primary-ink/20 border border-control-edge relative h-2 w-full overflow-hidden rounded-full\",\n className\n )}\n value={value}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n className=\"bg-primary-ink h-full w-full flex-1 transition-transform duration-150 ease-out\"\n style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n />\n </ProgressPrimitive.Root>\n );\n}\n\nexport { Progress };\n"],"mappings":";AAmDM;AAjDN,YAAY,uBAAuB;AAEnC,SAAS,UAAU;AAoCnB,SAAS,SAAS,EAAE,WAAW,OAAO,GAAG,MAAM,GAAkB;AAC/D,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,kBAAkB;AAAA,QAAlB;AAAA,UACC,aAAU;AAAA,UACV,WAAU;AAAA,UACV,OAAO,EAAE,WAAW,eAAe,OAAO,SAAS,EAAE,KAAK;AAAA;AAAA,MAC5D;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Progress (Radix) — thanh tiến trình xác định (`value` 0…100).\n * Bỏ trống `value` để về trạng thái indeterminate của Radix (thanh đứng yên ở 0);\n * animation cho indeterminate là việc của app.\n *\n * ─ MÀU: `--primary-ink`, KHÔNG PHẢI `--primary` ─\n * Phần đã chạy là thông tin thị giác (WCAG 1.4.11, 3:1) và phải nổi trên CẢ nền trang lẫn phần\n * rãnh còn lại. `bg-primary` gold #E8B824 ở light chỉ **1,86:1** với nền trắng và **1,64:1** với\n * rãnh `bg-primary/20` — thanh 8px vàng nhạt trên nền trắng gần như không thấy. Bậc ink của\n * brand đạt cả hai phía ở cả hai theme:\n *\n * đã chạy ⇄ nền trang : 6,51:1 (light) · 12,67:1 (dark) ✓\n * đã chạy ⇄ rãnh `/20` : 4,85:1 (light) · 7,97:1 (dark) ✓\n * (tệ nhất khi rãnh nằm trên --muted: 4,54:1 · 6,52:1 ✓)\n *\n * ─ RÃNH: TINT `/20` GIỮ NGUYÊN, NHƯNG PHẢI CÓ VIỀN `--control-edge` ─\n * Bản trước chỉ có tint: rãnh ⇄ nền trang mới **1,34:1** (light) và **1,59–1,66:1** (dark), tức\n * KHÔNG nhìn thấy mốc 100% — ở `value` nhỏ không đọc được TỈ LỆ, ở `value=0` cả control biến mất.\n * Cách sửa hiển nhiên (đổi rãnh sang `bg-control-edge` như Slider/Switch) lại hỏng chỗ khác:\n * `--primary-ink` ⇄ `--control-edge` chỉ **1,70:1** (light) / **2,57:1** (dark), mà Progress\n * KHÔNG có núm nên ranh giới đã-chạy/chưa-chạy là thứ DUY NHẤT mang giá trị — mất nó là mất\n * thông tin. Với nền trang trắng không tồn tại cặp màu nào vừa ≥3:1 với nhau vừa ≥3:1 với nền\n * (chứng minh trong chú thích của `Slider`), nên hai ràng buộc phải tách ra hai lớp:\n *\n * khoanh vùng control → `border-control-edge` ⇄ nền trang : 3,83:1 (light) · 4,94:1 (dark) ✓\n * ⇄ --muted : 3,58:1 · 4,21:1 ✓\n * đọc tỉ lệ → đã chạy ⇄ rãnh `/20` : 4,85:1 · 7,97:1 ✓\n *\n * Đây đúng vai trò hợp đồng đặt cho `--control-edge` (styles.css §RANH GIỚI): \"khi nền control\n * trong suốt/nhạt thì VIỀN là thứ duy nhất khoanh vùng\".\n *\n * ─ TÔNG: BA GIÁ TRỊ, VÀ KHÔNG CÓ `warning` ─\n * `tone` đổi CẢ phần đã chạy lẫn rãnh sang cùng một bậc ink, nên tỉ lệ đọc-được giữ nguyên cấu\n * trúc của bản mặc định. Đo trên ba bề mặt (`--background`, `--card`, `--muted`), tệ nhất mỗi cột:\n *\n * tông đã chạy ⇄ nền trang đã chạy ⇄ rãnh cùng tông /20\n * default 6,09:1 (light) 10,80 (dark) 4,54:1 (light) 6,52:1 (dark) ✓\n * success 6,09:1 9,25 4,55:1 5,88:1 ✓\n * destructive 6,68:1 5,82 4,85:1 4,25:1 ✓\n *\n * ⚠ KHÔNG có `warning`, và đây là quyết định ĐO ĐƯỢC chứ không phải bỏ sót: ở bảng màu light\n * `--warning-ink` và `--primary-ink` là CÙNG một giá trị (#7A5800 — `--yellow-ink` trỏ về\n * `--gold-500`, xem styles.css:174), nên một thanh `tone=\"warning\"` sẽ trông y hệt thanh mặc định.\n * Ở dark hai bậc cũng chỉ cách nhau một nấc vàng (#FACC15 ⇄ #F5D060). Một API đổi tông mà không\n * đổi được gì là thứ tệ hơn không có API: chỗ gọi tưởng đã cảnh báo, người dùng không thấy gì.\n *\n * Cần báo \"sắp chạm ngưỡng\" thì nói bằng thứ KHÁC màu thanh — `StatusPill`/`Badge` tông warning,\n * hoặc một dòng chữ `text-warning-ink` cạnh thanh; cả hai đều tương phản tốt với nền trang vì\n * chúng không phải cạnh tranh với chính màu brand. Muốn có `tone=\"warning\"` thật thì phải tách\n * `--warning-ink` khỏi `--gold-500` ở TẦNG TOKEN trước — đó là thay đổi bảng màu, không phải thay\n * đổi component, và nó đụng mọi bề mặt warning đang có (Badge, StatusPill, StatCard, Toast).\n */\n\n/*\n * HAI `cva`, không phải hai bảng `Record`.\n *\n * Không phải chuyện phong cách: cổng màu (`scripts/check-contrast.mjs`) chỉ bóc chuỗi lớp từ ba\n * chỗ — thuộc tính `className`, `cva(base, { variants })`, và đối số TRỰC TIẾP của `cn()`. Class\n * nằm trong một `Record` rồi đi vào `cn()` qua biến là VÔ HÌNH với cổng: nó không báo lỗi, nó\n * lặng lẽ không đo — và miễn trừ của rãnh sẽ thành \"ruỗng\", đúng thứ cổng coi là nơi giấu lỗi.\n * Dạng `cva` cho cổng thấy cả base lẫn từng lá biến thể.\n */\nconst progressTrackVariants = cva(\"border-control-edge relative h-2 w-full overflow-hidden rounded-full border\", {\n variants: {\n tone: {\n default: \"bg-primary-ink/20\",\n success: \"bg-success-ink/20\",\n destructive: \"bg-destructive-ink/20\"\n }\n },\n defaultVariants: { tone: \"default\" }\n});\n\nconst progressBarVariants = cva(\"h-full w-full flex-1 transition-transform duration-150 ease-out\", {\n variants: {\n tone: {\n default: \"bg-primary-ink\",\n success: \"bg-success-ink\",\n destructive: \"bg-destructive-ink\"\n }\n },\n defaultVariants: { tone: \"default\" }\n});\n\n/** Tông của thanh — xem chú thích ở trên về việc vì sao không có `warning`. */\nexport type ProgressTone = NonNullable<VariantProps<typeof progressBarVariants>[\"tone\"]>;\n\nexport interface ProgressProps\n extends React.ComponentProps<typeof ProgressPrimitive.Root>,\n VariantProps<typeof progressBarVariants> {}\n\nfunction Progress({ className, value, tone = \"default\", ...props }: ProgressProps) {\n return (\n <ProgressPrimitive.Root\n data-slot=\"progress\"\n data-tone={tone}\n className={cn(progressTrackVariants({ tone }), className)}\n value={value}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n className={progressBarVariants({ tone })}\n style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n />\n </ProgressPrimitive.Root>\n );\n}\n\nexport { Progress, progressBarVariants, progressTrackVariants };\n"],"mappings":";AA2GM;AAzGN,YAAY,uBAAuB;AACnC,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAgEnB,MAAM,wBAAwB,IAAI,+EAA+E;AAAA,EAC/G,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CAAC;AAED,MAAM,sBAAsB,IAAI,mEAAmE;AAAA,EACjG,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CAAC;AASD,SAAS,SAAS,EAAE,WAAW,OAAO,OAAO,WAAW,GAAG,MAAM,GAAkB;AACjF,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,aAAU;AAAA,MACV,aAAW;AAAA,MACX,WAAW,GAAG,sBAAsB,EAAE,KAAK,CAAC,GAAG,SAAS;AAAA,MACxD;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,kBAAkB;AAAA,QAAlB;AAAA,UACC,aAAU;AAAA,UACV,WAAW,oBAAoB,EAAE,KAAK,CAAC;AAAA,UACvC,OAAO,EAAE,WAAW,eAAe,OAAO,SAAS,EAAE,KAAK;AAAA;AAAA,MAC5D;AAAA;AAAA,EACF;AAEJ;","names":[]}
package/dist/index.d.ts CHANGED
@@ -60,7 +60,7 @@ export { Menubar, MenubarCheckboxItem, MenubarCheckboxItemProps, MenubarContent,
60
60
  export { NavigationMenu, NavigationMenuContent, NavigationMenuContentProps, NavigationMenuIndicator, NavigationMenuIndicatorProps, NavigationMenuItem, NavigationMenuItemProps, NavigationMenuLink, NavigationMenuLinkProps, NavigationMenuList, NavigationMenuListProps, NavigationMenuProps, NavigationMenuTrigger, NavigationMenuTriggerProps, NavigationMenuViewport, NavigationMenuViewportProps, navigationMenuTriggerStyle } from './components/ui/navigation-menu.js';
61
61
  export { Pagination, PaginationContent, PaginationContentProps, PaginationEllipsis, PaginationEllipsisProps, PaginationItem, PaginationItemProps, PaginationLink, PaginationLinkProps, PaginationNext, PaginationNextProps, PaginationPrevious, PaginationPreviousProps, PaginationProps } from './components/ui/pagination.js';
62
62
  export { Popover, PopoverAnchor, PopoverAnchorProps, PopoverContent, PopoverContentProps, PopoverProps, PopoverTrigger, PopoverTriggerProps } from './components/ui/popover.js';
63
- export { Progress, ProgressProps } from './components/ui/progress.js';
63
+ export { Progress, ProgressProps, ProgressTone, progressBarVariants, progressTrackVariants } from './components/ui/progress.js';
64
64
  export { RadioGroup, RadioGroupItem, RadioGroupItemProps, RadioGroupProps } from './components/ui/radio-group.js';
65
65
  export { ResizableHandle, ResizableHandleProps, ResizablePanel, ResizablePanelGroup, ResizablePanelGroupProps, ResizablePanelProps } from './components/ui/resizable.js';
66
66
  export { ScrollArea, ScrollAreaProps, ScrollBar, ScrollBarProps } from './components/ui/scroll-area.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comitor/ui",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "Comitor design system — Shadcn/UI tùy biến theo brand Comitor + component dùng chung cho hệ sinh thái SaaS (Tasks, Chat, CRM, HR…)",
5
5
  "license": "MIT",
6
6
  "type": "module",