@comitor/ui 3.13.0 → 3.13.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.
@@ -73,6 +73,10 @@ interface DateRangePickerProps {
73
73
  dateFormat?: string;
74
74
  /** Đè nhãn `sr-only` — không truyền thì theo namespace `datePicker` của `ComitorLocaleProvider` */
75
75
  labels?: Partial<DatePickerLabels>;
76
+ /**
77
+ * Áp lên KHUNG ngoài (như `DatePicker`), nút bên trong luôn `w-full`. Trước 3.13.2 nó tới NÚT, nên
78
+ * `className="w-60"` không thu được khung `w-full` và ô chiếm trọn hàng flex, đẩy ô bên cạnh xuống dòng.
79
+ */
76
80
  className?: string;
77
81
  id?: string;
78
82
  "aria-label"?: string;
@@ -208,7 +208,7 @@ function DateRangePicker({
208
208
  return (
209
209
  /* Nút xóa nằm NGOÀI trigger (anh em), không lồng trong <button> — vừa hợp lệ về HTML vừa
210
210
  đến được bằng phím Tab. */
211
- /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
211
+ /* @__PURE__ */ jsxs("div", { className: cn("relative w-full", className), children: [
212
212
  /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
213
213
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
214
214
  Button,
@@ -223,8 +223,7 @@ function DateRangePicker({
223
223
  // + 4, chỉ cần khi nút xóa có mặt.
224
224
  "w-full justify-start gap-2 px-3 font-normal",
225
225
  !label && "text-muted-foreground",
226
- showClear && "pr-10",
227
- className
226
+ showClear && "pr-10"
228
227
  ),
229
228
  ...aria,
230
229
  children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/composite/date-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { format as formatDate, isAfter, isBefore, isValid, type Locale, parse, startOfDay } from \"date-fns\";\nimport { CalendarDays, X } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport type { DateRange, Matcher } from \"react-day-picker\";\nimport { useComitorLabels, useComitorLocale } from \"../../i18n/locale-provider.js\";\nimport type { DatePresetMessages } from \"../../i18n/namespaces/date-picker.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Calendar } from \"../ui/calendar.js\";\nimport { Input } from \"../ui/input.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../ui/popover.js\";\nimport {\n type DatePickerLabels,\n type DatePreset,\n type DateRangePreset,\n DEFAULT_DATE_PRESETS,\n DEFAULT_DATE_RANGE_PRESETS,\n dateFormatForLocale\n} from \"./date-picker-constants.js\";\n\nexport type { DateRange };\n\n/** `id` của preset mặc định → khoá nhãn trong namespace `datePicker.presets`. */\nconst PRESET_MESSAGE_KEY_BY_ID = {\n today: \"today\",\n yesterday: \"yesterday\",\n \"week-ago\": \"weekAgo\",\n \"last-7\": \"last7Days\",\n \"last-30\": \"last30Days\",\n \"this-month\": \"thisMonth\",\n \"last-month\": \"lastMonth\"\n} satisfies Record<string, keyof DatePresetMessages>;\n\n/**\n * CHÍNH các object preset của gói → khoá nhãn của chúng. Nhận ra bằng THAM CHIẾU, không bằng `id`:\n * · preset app tự dựng hiện đúng `label` app đưa, kể cả khi trùng `id` (\"today\") với mặc định;\n * · app truyền lại object của gói — `presets={DEFAULT_DATE_PRESETS}` (cách DUY NHẤT để\n * `DatePicker` có preset), hay một mảng `.filter()` từ đó — thì nhãn vẫn theo provider thay vì\n * kẹt ở `label` tiếng Anh của hằng.\n */\nconst DEFAULT_PRESET_MESSAGE_KEYS = new Map<DatePreset | DateRangePreset, keyof DatePresetMessages>(\n [...DEFAULT_DATE_PRESETS, ...DEFAULT_DATE_RANGE_PRESETS].flatMap((preset) => {\n const key = PRESET_MESSAGE_KEY_BY_ID[preset.id as keyof typeof PRESET_MESSAGE_KEY_BY_ID];\n return key ? [[preset, key] as const] : [];\n })\n);\n\n/** Nhãn hiện ra của một preset: preset của gói theo provider, preset của app nguyên văn. */\nfunction presetLabel(preset: DatePreset | DateRangePreset, messages: DatePresetMessages): string {\n const key = DEFAULT_PRESET_MESSAGE_KEYS.get(preset);\n return key ? messages[key] : preset.label;\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n if (minDate && isBefore(date, startOfDay(minDate))) return true;\n if (maxDate && isAfter(date, startOfDay(maxDate))) return true;\n return false;\n}\n\n/**\n * Chặn ngày ngoài khoảng cho phép — trả về MẢNG matcher, không phải một object.\n *\n * Cạm bẫy react-day-picker: `{ before, after }` trong CÙNG một object là `DateInterval`, nghĩa là\n * \"các ngày NẰM GIỮA\" — ngược hẳn ý định. Tách thành hai matcher riêng mới ra \"trước min HOẶC sau max\".\n */\nfunction buildDisabledMatchers(minDate?: Date, maxDate?: Date): Matcher[] | undefined {\n const matchers: Matcher[] = [];\n if (minDate) matchers.push({ before: startOfDay(minDate) });\n if (maxDate) matchers.push({ after: startOfDay(maxDate) });\n return matchers.length > 0 ? matchers : undefined;\n}\n\nexport interface DatePickerProps {\n value?: Date | null;\n onValueChange: (value: Date | null) => void;\n placeholder?: string;\n disabled?: boolean;\n /** Ngày nhỏ nhất / lớn nhất được chọn */\n minDate?: Date;\n maxDate?: Date;\n /** Hiện nút xoá khi đã có giá trị (mặc định true) */\n clearable?: boolean;\n /**\n * Cột preset bên trái lịch (mặc định ẩn). `DEFAULT_DATE_PRESETS` hiện nhãn theo\n * `ComitorLocaleProvider`; preset app tự dựng hiện nguyên `label`.\n */\n presets?: DatePreset[] | false;\n /**\n * Ngôn ngữ của date-fns — quyết định tên thứ/tháng trong lịch VÀ thứ tự ngày/tháng của ô nhập\n * (gõ, hiển thị lẫn placeholder), qua `dateFormatForLocale()`.\n *\n * Không truyền thì theo `ComitorLocaleProvider` (`vi` → `dd/MM/yyyy`, còn lại `enUS` →\n * `MM/dd/yyyy`). Trước `3.0.0` mặc định cứng là `vi`, nên app tiếng Anh phải nhớ truyền.\n */\n locale?: Locale;\n /**\n * Ghi đè định dạng gõ/hiển thị. HIẾM khi cần: bỏ trống thì nó suy từ `locale`, và đó gần như\n * luôn là thứ đúng. Chỉ dùng khi một màn hình phải ghim cứng một định dạng bất kể ngôn ngữ.\n */\n dateFormat?: string;\n /** Đè nhãn `sr-only` — không truyền thì theo namespace `datePicker` của `ComitorLocaleProvider` */\n labels?: Partial<DatePickerLabels>;\n className?: string;\n id?: string;\n name?: string;\n \"aria-label\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: true;\n \"aria-required\"?: true;\n}\n\n/**\n * Ô chọn ngày: gõ tay theo định dạng của `locale` (`dd/MM/yyyy`, `MM/dd/yyyy`…) HOẶC bấm lịch.\n *\n * Vì sao vẫn giữ ô gõ tay thay vì chỉ có nút mở lịch: người nhập liệu chuyên nghiệp (kế toán,\n * nhân sự) gõ nhanh hơn bấm nhiều lần, và ngày sinh năm 1975 mà bắt lật lịch 50 năm thì không\n * ai chịu nổi. Chuỗi gõ dở KHÔNG bị ghi đè trong lúc gõ; sai định dạng thì trả về giá trị cũ\n * khi rời ô, không âm thầm nuốt dữ liệu.\n */\nexport function DatePicker({\n value,\n onValueChange,\n placeholder,\n disabled = false,\n minDate,\n maxDate,\n clearable = true,\n presets = false,\n locale: localeProp,\n dateFormat,\n labels,\n className,\n id,\n name,\n ...aria\n}: DatePickerProps) {\n const { dateFnsLocale } = useComitorLocale();\n const locale = localeProp ?? dateFnsLocale;\n const pattern = dateFormat ?? dateFormatForLocale(locale);\n const label = useComitorLabels(\"datePicker\", labels);\n\n const [open, setOpen] = useState(false);\n const [text, setText] = useState(() => (value ? formatDate(value, pattern, { locale }) : \"\"));\n\n // Đồng bộ khi giá trị đổi từ bên ngoài (reset form, chọn preset, load dữ liệu về) — VÀ khi đổi\n // ngôn ngữ giữa chừng: chuỗi đang hiện là `dd/MM/yyyy` mà ô nay chờ `MM/dd/yyyy` thì lần rời ô\n // kế tiếp sẽ đọc sai ngày.\n useEffect(() => {\n setText(value ? formatDate(value, pattern, { locale }) : \"\");\n }, [value, pattern, locale]);\n\n const commitText = (raw: string) => {\n const trimmed = raw.trim();\n if (trimmed === \"\") {\n onValueChange(null);\n return;\n }\n const parsed = parse(trimmed, pattern, new Date(), { locale });\n if (!isValid(parsed) || isOutOfRange(parsed, minDate, maxDate)) {\n // Không hợp lệ → khôi phục hiển thị theo giá trị đang có (không đổi dữ liệu).\n setText(value ? formatDate(value, pattern, { locale }) : \"\");\n return;\n }\n onValueChange(startOfDay(parsed));\n };\n\n const presetList = presets === false ? [] : presets;\n\n return (\n <div className={cn(\"relative\", className)}>\n <Input\n id={id}\n name={name}\n value={text}\n disabled={disabled}\n placeholder={placeholder ?? pattern}\n inputMode=\"numeric\"\n autoComplete=\"off\"\n onChange={(event) => setText(event.target.value)}\n onBlur={(event) => commitText(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n commitText(text);\n }\n }}\n className=\"pr-16\"\n {...aria}\n />\n\n {/* Nút `size-6`: glyph 16px nằm giữa ô 24px nên nó thụt thêm 4px so với mép ô — cộng với\n `pr-2` của hàng là glyph rơi đúng mốc 12px của `Select`/`Combobox`. Ba loại control này\n hay đứng chung một cột biểu mẫu, lệch vài px là mép phải nhìn răng cưa.\n\n `border border-transparent`: hàng nằm ngoài `Input` nên đo từ mép BORDER, còn icon nằm\n trong ô thì đo từ mép CONTENT — mượn lại viền 1px là hai mốc trùng nhau.\n\n Icon lấy `text-muted-foreground`: `Button variant=\"ghost\"` mặc định là `text-foreground`,\n đậm hơn hẳn chevron của `Select` (vốn `text-muted-foreground`). Chúng là cùng một hạng\n trang trí của ô nhập thì phải cùng một sắc. */}\n {/* `pointer-events-none` ở hàng, `pointer-events-auto` ở từng nút: phần đệm của hàng phủ lên\n dải sát mép phải của ô nhập, để nguyên là bấm vào đó không đặt được con trỏ vào ô. */}\n <div className=\"pointer-events-none absolute inset-y-0 right-0 flex items-center border border-transparent pr-2\">\n {clearable && value && !disabled && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-events-auto size-6 text-muted-foreground hover:text-foreground\"\n onClick={() => onValueChange(null)}\n >\n <X aria-hidden=\"true\" className=\"size-3.5\" />\n <span className=\"sr-only\">{label.clear}</span>\n </Button>\n )}\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-events-auto size-6 text-muted-foreground hover:text-foreground\"\n disabled={disabled}\n >\n <CalendarDays aria-hidden=\"true\" className=\"size-4\" />\n <span className=\"sr-only\">{label.openCalendar}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"flex w-auto gap-2 p-2\">\n {presetList.length > 0 && (\n <div className=\"flex w-32 flex-col gap-1 border-r border-border pr-2\">\n {presetList.map((preset) => (\n <Button\n key={preset.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"justify-start\"\n onClick={() => {\n onValueChange(preset.getValue());\n setOpen(false);\n }}\n >\n {presetLabel(preset, label.presets)}\n </Button>\n ))}\n </div>\n )}\n <Calendar\n mode=\"single\"\n locale={locale}\n autoFocus\n selected={value ?? undefined}\n defaultMonth={value ?? undefined}\n disabled={buildDisabledMatchers(minDate, maxDate)}\n onSelect={(date) => {\n onValueChange(date ? startOfDay(date) : null);\n setOpen(false);\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n </div>\n );\n}\n\nexport interface DateRangePickerProps {\n value?: DateRange | null;\n onValueChange: (value: DateRange | null) => void;\n /** Chữ khi chưa chọn — không truyền thì theo khoá `rangePlaceholder` của namespace `datePicker` */\n placeholder?: string;\n disabled?: boolean;\n minDate?: Date;\n maxDate?: Date;\n clearable?: boolean;\n /**\n * Cột preset bên trái (mặc định `DEFAULT_DATE_RANGE_PRESETS`, nhãn theo `ComitorLocaleProvider`;\n * `false` để tắt). Preset app tự dựng hiện nguyên `label`.\n */\n presets?: DateRangePreset[] | false;\n /** Số tháng hiện cùng lúc (mặc định 2 trên màn rộng) */\n numberOfMonths?: number;\n /** Ngôn ngữ date-fns (mặc định theo `ComitorLocaleProvider`) — xem `DatePickerProps.locale`. */\n locale?: Locale;\n /** Ghi đè định dạng ngày; bỏ trống thì suy từ `locale`. */\n dateFormat?: string;\n /** Đè nhãn `sr-only` — không truyền thì theo namespace `datePicker` của `ComitorLocaleProvider` */\n labels?: Partial<DatePickerLabels>;\n className?: string;\n id?: string;\n \"aria-label\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: true;\n}\n\nfunction formatRange(range: DateRange | null | undefined, pattern: string, locale: Locale): string | null {\n if (!range?.from) return null;\n const from = formatDate(range.from, pattern, { locale });\n if (!range.to) return from;\n return `${from} – ${formatDate(range.to, pattern, { locale })}`;\n}\n\n/**\n * Ô chọn khoảng ngày cho báo cáo và bộ lọc danh sách: cột preset + lịch 2 tháng.\n *\n * Preset đứng trước lịch vì 9/10 lần người dùng muốn \"7 ngày qua\" chứ không phải một khoảng\n * tuỳ ý — bắt họ bấm hai lần trên lịch mỗi lần xem báo cáo là thuế lặp lại vô ích.\n */\nexport function DateRangePicker({\n value,\n onValueChange,\n placeholder: placeholderProp,\n disabled = false,\n minDate,\n maxDate,\n clearable = true,\n presets = DEFAULT_DATE_RANGE_PRESETS,\n numberOfMonths = 2,\n locale: localeProp,\n dateFormat,\n labels,\n className,\n id,\n ...aria\n}: DateRangePickerProps) {\n const { dateFnsLocale } = useComitorLocale();\n const locale = localeProp ?? dateFnsLocale;\n const pattern = dateFormat ?? dateFormatForLocale(locale);\n const text = useComitorLabels(\"datePicker\", labels);\n const placeholder = placeholderProp ?? text.rangePlaceholder;\n\n const [open, setOpen] = useState(false);\n const label = formatRange(value, pattern, locale);\n const presetList = presets === false ? [] : presets;\n\n const showClear = clearable && Boolean(label) && !disabled;\n\n return (\n /* Nút xóa nằm NGOÀI trigger (anh em), không lồng trong <button> — vừa hợp lệ về HTML vừa\n đến được bằng phím Tab. */\n <div className=\"relative w-full\">\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n id={id}\n variant=\"outline\"\n disabled={disabled}\n className={cn(\n // `px-3` chứ không phải `px-4` của `Button`: đây là một Ô NHẬP nằm cùng cột với\n // `Input`/`Select`/`Combobox`, và cả ba đều thụt 12px. `pr-10` = 12 + 24 (nút xóa)\n // + 4, chỉ cần khi nút xóa có mặt.\n \"w-full justify-start gap-2 px-3 font-normal\",\n !label && \"text-muted-foreground\",\n showClear && \"pr-10\",\n className\n )}\n {...aria}\n >\n <CalendarDays aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{label ?? placeholder}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"flex w-auto flex-col gap-2 p-2 sm:flex-row\">\n {presetList.length > 0 && (\n <div className=\"flex flex-row flex-wrap gap-1 sm:w-36 sm:flex-col sm:border-r sm:border-border sm:pr-2\">\n {presetList.map((preset) => (\n <Button\n key={preset.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"justify-start\"\n onClick={() => {\n onValueChange(preset.getValue());\n setOpen(false);\n }}\n >\n {presetLabel(preset, text.presets)}\n </Button>\n ))}\n </div>\n )}\n <Calendar\n mode=\"range\"\n locale={locale}\n autoFocus\n numberOfMonths={numberOfMonths}\n selected={value ?? undefined}\n defaultMonth={value?.from ?? undefined}\n disabled={buildDisabledMatchers(minDate, maxDate)}\n onSelect={(range) => onValueChange(range ?? null)}\n />\n </PopoverContent>\n </Popover>\n\n {showClear && (\n // Cùng hình dạng với nút xóa của `DatePicker`: ô 24px ở `right-2`. Là `<Button>` thật nên\n // có viền focus và vùng bấm đạt 24px — bản `<button>` thô trước đây chỉ 18px.\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"absolute right-2 top-1/2 size-6 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n onClick={() => onValueChange(null)}\n >\n <X aria-hidden=\"true\" className=\"size-3.5\" />\n <span className=\"sr-only\">{text.clearRange}</span>\n </Button>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/composite/date-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { format as formatDate, isAfter, isBefore, isValid, type Locale, parse, startOfDay } from \"date-fns\";\nimport { CalendarDays, X } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport type { DateRange, Matcher } from \"react-day-picker\";\nimport { useComitorLabels, useComitorLocale } from \"../../i18n/locale-provider.js\";\nimport type { DatePresetMessages } from \"../../i18n/namespaces/date-picker.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Calendar } from \"../ui/calendar.js\";\nimport { Input } from \"../ui/input.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../ui/popover.js\";\nimport {\n type DatePickerLabels,\n type DatePreset,\n type DateRangePreset,\n DEFAULT_DATE_PRESETS,\n DEFAULT_DATE_RANGE_PRESETS,\n dateFormatForLocale\n} from \"./date-picker-constants.js\";\n\nexport type { DateRange };\n\n/** `id` của preset mặc định → khoá nhãn trong namespace `datePicker.presets`. */\nconst PRESET_MESSAGE_KEY_BY_ID = {\n today: \"today\",\n yesterday: \"yesterday\",\n \"week-ago\": \"weekAgo\",\n \"last-7\": \"last7Days\",\n \"last-30\": \"last30Days\",\n \"this-month\": \"thisMonth\",\n \"last-month\": \"lastMonth\"\n} satisfies Record<string, keyof DatePresetMessages>;\n\n/**\n * CHÍNH các object preset của gói → khoá nhãn của chúng. Nhận ra bằng THAM CHIẾU, không bằng `id`:\n * · preset app tự dựng hiện đúng `label` app đưa, kể cả khi trùng `id` (\"today\") với mặc định;\n * · app truyền lại object của gói — `presets={DEFAULT_DATE_PRESETS}` (cách DUY NHẤT để\n * `DatePicker` có preset), hay một mảng `.filter()` từ đó — thì nhãn vẫn theo provider thay vì\n * kẹt ở `label` tiếng Anh của hằng.\n */\nconst DEFAULT_PRESET_MESSAGE_KEYS = new Map<DatePreset | DateRangePreset, keyof DatePresetMessages>(\n [...DEFAULT_DATE_PRESETS, ...DEFAULT_DATE_RANGE_PRESETS].flatMap((preset) => {\n const key = PRESET_MESSAGE_KEY_BY_ID[preset.id as keyof typeof PRESET_MESSAGE_KEY_BY_ID];\n return key ? [[preset, key] as const] : [];\n })\n);\n\n/** Nhãn hiện ra của một preset: preset của gói theo provider, preset của app nguyên văn. */\nfunction presetLabel(preset: DatePreset | DateRangePreset, messages: DatePresetMessages): string {\n const key = DEFAULT_PRESET_MESSAGE_KEYS.get(preset);\n return key ? messages[key] : preset.label;\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n if (minDate && isBefore(date, startOfDay(minDate))) return true;\n if (maxDate && isAfter(date, startOfDay(maxDate))) return true;\n return false;\n}\n\n/**\n * Chặn ngày ngoài khoảng cho phép — trả về MẢNG matcher, không phải một object.\n *\n * Cạm bẫy react-day-picker: `{ before, after }` trong CÙNG một object là `DateInterval`, nghĩa là\n * \"các ngày NẰM GIỮA\" — ngược hẳn ý định. Tách thành hai matcher riêng mới ra \"trước min HOẶC sau max\".\n */\nfunction buildDisabledMatchers(minDate?: Date, maxDate?: Date): Matcher[] | undefined {\n const matchers: Matcher[] = [];\n if (minDate) matchers.push({ before: startOfDay(minDate) });\n if (maxDate) matchers.push({ after: startOfDay(maxDate) });\n return matchers.length > 0 ? matchers : undefined;\n}\n\nexport interface DatePickerProps {\n value?: Date | null;\n onValueChange: (value: Date | null) => void;\n placeholder?: string;\n disabled?: boolean;\n /** Ngày nhỏ nhất / lớn nhất được chọn */\n minDate?: Date;\n maxDate?: Date;\n /** Hiện nút xoá khi đã có giá trị (mặc định true) */\n clearable?: boolean;\n /**\n * Cột preset bên trái lịch (mặc định ẩn). `DEFAULT_DATE_PRESETS` hiện nhãn theo\n * `ComitorLocaleProvider`; preset app tự dựng hiện nguyên `label`.\n */\n presets?: DatePreset[] | false;\n /**\n * Ngôn ngữ của date-fns — quyết định tên thứ/tháng trong lịch VÀ thứ tự ngày/tháng của ô nhập\n * (gõ, hiển thị lẫn placeholder), qua `dateFormatForLocale()`.\n *\n * Không truyền thì theo `ComitorLocaleProvider` (`vi` → `dd/MM/yyyy`, còn lại `enUS` →\n * `MM/dd/yyyy`). Trước `3.0.0` mặc định cứng là `vi`, nên app tiếng Anh phải nhớ truyền.\n */\n locale?: Locale;\n /**\n * Ghi đè định dạng gõ/hiển thị. HIẾM khi cần: bỏ trống thì nó suy từ `locale`, và đó gần như\n * luôn là thứ đúng. Chỉ dùng khi một màn hình phải ghim cứng một định dạng bất kể ngôn ngữ.\n */\n dateFormat?: string;\n /** Đè nhãn `sr-only` — không truyền thì theo namespace `datePicker` của `ComitorLocaleProvider` */\n labels?: Partial<DatePickerLabels>;\n className?: string;\n id?: string;\n name?: string;\n \"aria-label\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: true;\n \"aria-required\"?: true;\n}\n\n/**\n * Ô chọn ngày: gõ tay theo định dạng của `locale` (`dd/MM/yyyy`, `MM/dd/yyyy`…) HOẶC bấm lịch.\n *\n * Vì sao vẫn giữ ô gõ tay thay vì chỉ có nút mở lịch: người nhập liệu chuyên nghiệp (kế toán,\n * nhân sự) gõ nhanh hơn bấm nhiều lần, và ngày sinh năm 1975 mà bắt lật lịch 50 năm thì không\n * ai chịu nổi. Chuỗi gõ dở KHÔNG bị ghi đè trong lúc gõ; sai định dạng thì trả về giá trị cũ\n * khi rời ô, không âm thầm nuốt dữ liệu.\n */\nexport function DatePicker({\n value,\n onValueChange,\n placeholder,\n disabled = false,\n minDate,\n maxDate,\n clearable = true,\n presets = false,\n locale: localeProp,\n dateFormat,\n labels,\n className,\n id,\n name,\n ...aria\n}: DatePickerProps) {\n const { dateFnsLocale } = useComitorLocale();\n const locale = localeProp ?? dateFnsLocale;\n const pattern = dateFormat ?? dateFormatForLocale(locale);\n const label = useComitorLabels(\"datePicker\", labels);\n\n const [open, setOpen] = useState(false);\n const [text, setText] = useState(() => (value ? formatDate(value, pattern, { locale }) : \"\"));\n\n // Đồng bộ khi giá trị đổi từ bên ngoài (reset form, chọn preset, load dữ liệu về) — VÀ khi đổi\n // ngôn ngữ giữa chừng: chuỗi đang hiện là `dd/MM/yyyy` mà ô nay chờ `MM/dd/yyyy` thì lần rời ô\n // kế tiếp sẽ đọc sai ngày.\n useEffect(() => {\n setText(value ? formatDate(value, pattern, { locale }) : \"\");\n }, [value, pattern, locale]);\n\n const commitText = (raw: string) => {\n const trimmed = raw.trim();\n if (trimmed === \"\") {\n onValueChange(null);\n return;\n }\n const parsed = parse(trimmed, pattern, new Date(), { locale });\n if (!isValid(parsed) || isOutOfRange(parsed, minDate, maxDate)) {\n // Không hợp lệ → khôi phục hiển thị theo giá trị đang có (không đổi dữ liệu).\n setText(value ? formatDate(value, pattern, { locale }) : \"\");\n return;\n }\n onValueChange(startOfDay(parsed));\n };\n\n const presetList = presets === false ? [] : presets;\n\n return (\n <div className={cn(\"relative\", className)}>\n <Input\n id={id}\n name={name}\n value={text}\n disabled={disabled}\n placeholder={placeholder ?? pattern}\n inputMode=\"numeric\"\n autoComplete=\"off\"\n onChange={(event) => setText(event.target.value)}\n onBlur={(event) => commitText(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n commitText(text);\n }\n }}\n className=\"pr-16\"\n {...aria}\n />\n\n {/* Nút `size-6`: glyph 16px nằm giữa ô 24px nên nó thụt thêm 4px so với mép ô — cộng với\n `pr-2` của hàng là glyph rơi đúng mốc 12px của `Select`/`Combobox`. Ba loại control này\n hay đứng chung một cột biểu mẫu, lệch vài px là mép phải nhìn răng cưa.\n\n `border border-transparent`: hàng nằm ngoài `Input` nên đo từ mép BORDER, còn icon nằm\n trong ô thì đo từ mép CONTENT — mượn lại viền 1px là hai mốc trùng nhau.\n\n Icon lấy `text-muted-foreground`: `Button variant=\"ghost\"` mặc định là `text-foreground`,\n đậm hơn hẳn chevron của `Select` (vốn `text-muted-foreground`). Chúng là cùng một hạng\n trang trí của ô nhập thì phải cùng một sắc. */}\n {/* `pointer-events-none` ở hàng, `pointer-events-auto` ở từng nút: phần đệm của hàng phủ lên\n dải sát mép phải của ô nhập, để nguyên là bấm vào đó không đặt được con trỏ vào ô. */}\n <div className=\"pointer-events-none absolute inset-y-0 right-0 flex items-center border border-transparent pr-2\">\n {clearable && value && !disabled && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-events-auto size-6 text-muted-foreground hover:text-foreground\"\n onClick={() => onValueChange(null)}\n >\n <X aria-hidden=\"true\" className=\"size-3.5\" />\n <span className=\"sr-only\">{label.clear}</span>\n </Button>\n )}\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-events-auto size-6 text-muted-foreground hover:text-foreground\"\n disabled={disabled}\n >\n <CalendarDays aria-hidden=\"true\" className=\"size-4\" />\n <span className=\"sr-only\">{label.openCalendar}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"flex w-auto gap-2 p-2\">\n {presetList.length > 0 && (\n <div className=\"flex w-32 flex-col gap-1 border-r border-border pr-2\">\n {presetList.map((preset) => (\n <Button\n key={preset.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"justify-start\"\n onClick={() => {\n onValueChange(preset.getValue());\n setOpen(false);\n }}\n >\n {presetLabel(preset, label.presets)}\n </Button>\n ))}\n </div>\n )}\n <Calendar\n mode=\"single\"\n locale={locale}\n autoFocus\n selected={value ?? undefined}\n defaultMonth={value ?? undefined}\n disabled={buildDisabledMatchers(minDate, maxDate)}\n onSelect={(date) => {\n onValueChange(date ? startOfDay(date) : null);\n setOpen(false);\n }}\n />\n </PopoverContent>\n </Popover>\n </div>\n </div>\n );\n}\n\nexport interface DateRangePickerProps {\n value?: DateRange | null;\n onValueChange: (value: DateRange | null) => void;\n /** Chữ khi chưa chọn — không truyền thì theo khoá `rangePlaceholder` của namespace `datePicker` */\n placeholder?: string;\n disabled?: boolean;\n minDate?: Date;\n maxDate?: Date;\n clearable?: boolean;\n /**\n * Cột preset bên trái (mặc định `DEFAULT_DATE_RANGE_PRESETS`, nhãn theo `ComitorLocaleProvider`;\n * `false` để tắt). Preset app tự dựng hiện nguyên `label`.\n */\n presets?: DateRangePreset[] | false;\n /** Số tháng hiện cùng lúc (mặc định 2 trên màn rộng) */\n numberOfMonths?: number;\n /** Ngôn ngữ date-fns (mặc định theo `ComitorLocaleProvider`) — xem `DatePickerProps.locale`. */\n locale?: Locale;\n /** Ghi đè định dạng ngày; bỏ trống thì suy từ `locale`. */\n dateFormat?: string;\n /** Đè nhãn `sr-only` — không truyền thì theo namespace `datePicker` của `ComitorLocaleProvider` */\n labels?: Partial<DatePickerLabels>;\n /**\n * Áp lên KHUNG ngoài (như `DatePicker`), nút bên trong luôn `w-full`. Trước 3.13.2 nó tới NÚT, nên\n * `className=\"w-60\"` không thu được khung `w-full` và ô chiếm trọn hàng flex, đẩy ô bên cạnh xuống dòng.\n */\n className?: string;\n id?: string;\n \"aria-label\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: true;\n}\n\nfunction formatRange(range: DateRange | null | undefined, pattern: string, locale: Locale): string | null {\n if (!range?.from) return null;\n const from = formatDate(range.from, pattern, { locale });\n if (!range.to) return from;\n return `${from} – ${formatDate(range.to, pattern, { locale })}`;\n}\n\n/**\n * Ô chọn khoảng ngày cho báo cáo và bộ lọc danh sách: cột preset + lịch 2 tháng.\n *\n * Preset đứng trước lịch vì 9/10 lần người dùng muốn \"7 ngày qua\" chứ không phải một khoảng\n * tuỳ ý — bắt họ bấm hai lần trên lịch mỗi lần xem báo cáo là thuế lặp lại vô ích.\n */\nexport function DateRangePicker({\n value,\n onValueChange,\n placeholder: placeholderProp,\n disabled = false,\n minDate,\n maxDate,\n clearable = true,\n presets = DEFAULT_DATE_RANGE_PRESETS,\n numberOfMonths = 2,\n locale: localeProp,\n dateFormat,\n labels,\n className,\n id,\n ...aria\n}: DateRangePickerProps) {\n const { dateFnsLocale } = useComitorLocale();\n const locale = localeProp ?? dateFnsLocale;\n const pattern = dateFormat ?? dateFormatForLocale(locale);\n const text = useComitorLabels(\"datePicker\", labels);\n const placeholder = placeholderProp ?? text.rangePlaceholder;\n\n const [open, setOpen] = useState(false);\n const label = formatRange(value, pattern, locale);\n const presetList = presets === false ? [] : presets;\n\n const showClear = clearable && Boolean(label) && !disabled;\n\n return (\n /* Nút xóa nằm NGOÀI trigger (anh em), không lồng trong <button> — vừa hợp lệ về HTML vừa\n đến được bằng phím Tab. */\n <div className={cn(\"relative w-full\", className)}>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n id={id}\n variant=\"outline\"\n disabled={disabled}\n className={cn(\n // `px-3` chứ không phải `px-4` của `Button`: đây là một Ô NHẬP nằm cùng cột với\n // `Input`/`Select`/`Combobox`, và cả ba đều thụt 12px. `pr-10` = 12 + 24 (nút xóa)\n // + 4, chỉ cần khi nút xóa có mặt.\n \"w-full justify-start gap-2 px-3 font-normal\",\n !label && \"text-muted-foreground\",\n showClear && \"pr-10\"\n )}\n {...aria}\n >\n <CalendarDays aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{label ?? placeholder}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"flex w-auto flex-col gap-2 p-2 sm:flex-row\">\n {presetList.length > 0 && (\n <div className=\"flex flex-row flex-wrap gap-1 sm:w-36 sm:flex-col sm:border-r sm:border-border sm:pr-2\">\n {presetList.map((preset) => (\n <Button\n key={preset.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"justify-start\"\n onClick={() => {\n onValueChange(preset.getValue());\n setOpen(false);\n }}\n >\n {presetLabel(preset, text.presets)}\n </Button>\n ))}\n </div>\n )}\n <Calendar\n mode=\"range\"\n locale={locale}\n autoFocus\n numberOfMonths={numberOfMonths}\n selected={value ?? undefined}\n defaultMonth={value?.from ?? undefined}\n disabled={buildDisabledMatchers(minDate, maxDate)}\n onSelect={(range) => onValueChange(range ?? null)}\n />\n </PopoverContent>\n </Popover>\n\n {showClear && (\n // Cùng hình dạng với nút xóa của `DatePicker`: ô 24px ở `right-2`. Là `<Button>` thật nên\n // có viền focus và vùng bấm đạt 24px — bản `<button>` thô trước đây chỉ 18px.\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"absolute right-2 top-1/2 size-6 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n onClick={() => onValueChange(null)}\n >\n <X aria-hidden=\"true\" className=\"size-3.5\" />\n <span className=\"sr-only\">{text.clearRange}</span>\n </Button>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -20,11 +20,11 @@ import * as React from 'react';
20
20
  * `aria-valuetext`, kèm lỗi console; nay thanh vẫn đầy VÀ trình đọc màn hình đọc "100%". Nói rõ là
21
21
  * đã vượt (bao nhiêu, làm gì tiếp) vẫn là việc của chữ cạnh thanh và `tone`, không của thanh.
22
22
  *
23
- * ─ TÔNG `default`: cặp `gold` của avatar (chưa phát hành) ─
24
- * Thanh `gold-600` (dark `gold-100`), rãnh `gold-100` (dark `gold-500/30`) — cùng cặp với `StatusPill`
25
- * `primary`, `Gantt` và `CircularProgress`. Lý do: ở bảng màu MẶC ĐỊNH `--primary-ink` là vàng thương hiệu
26
- * #E8B824 (1,73:1 với nền, 1,55:1 với rãnh — xem dưới), thanh vàng nhạt gần như không thấy. Phần lý lẽ
27
- * bên dưới về `--primary-ink` là lịch sử trước đợt đổi này; ba tông còn lại vẫn đúng như mô tả.
23
+ * ─ ⛔ ĐỪNG ĐỔI TÔNG `default` SANG MÀU ĐẬM HƠN (chốt 2026-11-01) ─
24
+ * 3.13.0 đổi thanh sang `gold-600` / rãnh `gold-100` để qua tương phản ở bảng mặc định; trên giao diện thật thanh nâu
25
+ * sẫm nặng hơn mọi thứ quanh nó và bị trả về đây ở 3.13.1. Mức 1,73:1 (thanh ⇄ nền) / 1,55:1 (thanh ⇄ rãnh) ở bảng
26
+ * MẶC ĐỊNH là ĐÃ CHẤP NHẬN — bỏ qua phép đo tương phản cho phần tô này. Cần rõ hơn thì chữ cạnh thanh, `tone`, hoặc
27
+ * bảng tương phản cao (`data-contrast="high"`, nơi các số đo bên dưới đạt). Xem CLAUDE.md, mục màu.
28
28
  *
29
29
  * ─ MÀU: `--primary-ink`, KHÔNG PHẢI `--primary` ─
30
30
  * 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
@@ -6,7 +6,7 @@ import { cn } from "../../lib/cn.js";
6
6
  const progressTrackVariants = cva("border-control-edge relative h-2 w-full overflow-hidden rounded-full border", {
7
7
  variants: {
8
8
  tone: {
9
- default: "bg-gold-100 dark:bg-gold-500/30",
9
+ default: "bg-primary-ink/20",
10
10
  success: "bg-success-ink/20",
11
11
  warning: "bg-warning-ink/20",
12
12
  destructive: "bg-destructive-ink/20"
@@ -17,7 +17,7 @@ const progressTrackVariants = cva("border-control-edge relative h-2 w-full overf
17
17
  const progressBarVariants = cva("h-full w-full flex-1 transition-transform duration-150 ease-out", {
18
18
  variants: {
19
19
  tone: {
20
- default: "bg-gold-600 dark:bg-gold-100",
20
+ default: "bg-primary-ink",
21
21
  success: "bg-success-ink",
22
22
  // `hazard-stripes` là PHẦN BẮT BUỘC của tông này, không phải trang trí — xem chú thích đầu file.
23
23
  warning: "bg-warning-ink hazard-stripes",
@@ -1 +1 @@
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…`max`, `max` mặc định 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 * ─ THANH VẼ THEO `value / max`, VÀ THANH VỚI `aria-*` NÓI CÙNG MỘT CON SỐ ─\n * Radix đọc `max` để dựng `aria-valuemax` và `aria-valuetext` (`round(value / max · 100)%`). Trước\n * `3.3.3` phần đã chạy lại vẽ bằng `100 - value` — bỏ qua `max` —, nên `max={30} value={24}` vẽ 24%\n * trong khi trình đọc màn hình đọc 80%: hai người nhìn cùng một control nhận hai con số.\n *\n * Nay cả hai đi từ MỘT giá trị: `value` được KẸP vào `[0, max]` rồi mới giao cho Radix (`max` không\n * phải số dương → 100, đúng luật Radix). Kẹp chứ không coi là indeterminate, vì ca vượt ngưỡng hay\n * gặp nhất là CÓ Ý: hạn mức dùng 120% phải là thanh ĐẦY, không phải thanh rỗng. Trước `3.3.3` ca đó\n * tình cờ đã đầy (`translateX(--20%)` là CSS vô hiệu) nhưng Radix coi `value` sai → không có\n * `aria-valuetext`, kèm lỗi console; nay thanh vẫn đầy VÀ trình đọc màn hình đọc \"100%\". Nói rõ là\n * đã vượt (bao nhiêu, làm gì tiếp) vẫn là việc của chữ cạnh thanh và `tone`, không của thanh.\n *\n * ─ TÔNG `default`: cặp `gold` của avatar (chưa phát hành) ─\n * Thanh `gold-600` (dark `gold-100`), rãnh `gold-100` (dark `gold-500/30`) — cùng cặp với `StatusPill`\n * `primary`, `Gantt` và `CircularProgress`. Lý do: ở bảng màu MẶC ĐỊNH `--primary-ink` là vàng thương hiệu\n * #E8B824 (1,73:1 với nền, 1,55:1 với rãnh — xem dưới), thanh vàng nhạt gần như không thấy. Phần lý lẽ\n * bên dưới về `--primary-ink` là lịch sử trước đợt đổi này; ba tông còn lại vẫn đúng như mô tả.\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: BỐN GIÁ TRỊ, VÀ `warning` KHÔNG ĐƯỢC PHÉP CHỈ LÀ MỘT MÀU KHÁC ─\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 * ⚠ Cột \"light\"/\"dark\" dưới đây là **bảng TƯƠNG PHẢN CAO** — đúng bảng mà cổng màu đo, và lý do\n * ghi ở đầu `scripts/a11y/tailwind.mjs`. Ở bảng mặc định (bản duyệt comitor-ds) `--primary-ink`\n * chính là `--primary` gold #E8B824, nên tông `default` chỉ được 1,73:1 với nền và 1,55:1 với\n * rãnh: đó là con số của BẢN DUYỆT, không phải lỗi của component — đổi nó là đổi bảng màu.\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 * warning 6,09:1 10,52 4,54:1 6,47:1 ✓\n * destructive 6,68:1 5,82 4,85:1 4,25:1 ✓\n *\n * ⚠ NHƯNG `warning` KHÔNG phân biệt được với `default` BẰNG MÀU, và đó là số đo chứ không phải\n * cảm tính. Đo `--warning-ink` ⇄ `--primary-ink` trên cả BỐN bảng màu của gói:\n *\n * light (mặc định) #7A5800 ⇄ #E8B824 3,51:1 ✓\n * light (tương phản cao) #7A5800 ⇄ #7A5800 1,00:1 ✗ CÙNG MỘT HEX\n * dark (mặc định) #F2C94C ⇄ #F5D060 1,06:1 ✗\n * dark (tương phản cao) #FACC15 ⇄ #F5D060 1,03:1 ✗\n *\n * Ba trên bốn bảng màu cho ra hai thanh trông y hệt nhau. Đây KHÔNG sửa được ở tầng token: brand\n * của Comitor là gold, warning là yellow — cùng một sắc. Muốn tách hai bậc ink ra 3:1 thì một\n * trong hai phải rời khỏi vùng đọc-được: với nền trắng, chữ đạt 4,5:1 đòi độ chói ≤ 0,183, mà\n * cách `--gold-500` (0,111) đúng 3:1 thì phải ≤ 0,004 (gần như đen) hoặc ≥ 0,433 (quá sáng để\n * làm chữ). Hai ràng buộc không giao nhau — không có hex nào thoả cả hai.\n *\n * Nên tông `warning` nói bằng thứ KHÔNG PHẢI MÀU: `hazard-stripes` (khai ở styles.css) khoét\n * những rãnh chéo xuyên phần đã chạy để nền rãnh lộ ra. Ranh giới vạch ⇄ rãnh chính là cặp\n * \"đã chạy ⇄ rãnh\" đã đo ở bảng trên (4,54:1 light · 6,47:1 dark ✓), nên vạch KHÔNG thêm màu nào\n * và không cần đo thêm. Đây đúng là WCAG 1.4.1: tín hiệu không được chỉ nằm ở màu — bản vạch còn\n * đọc ra ở chế độ xám, chỗ mà mọi khác biệt sắc độ đều biến mất.\n *\n * ⚠ Vì vậy: KHÔNG được \"đơn giản hoá\" `warning` xuống còn mỗi `bg-warning-ink`. Bỏ vạch đi là\n * API đổi tông mà không đổi được gì ở ba trên bốn bảng màu — chỗ gọi tưởng đã cảnh báo, người\n * dùng không thấy gì. Cùng lý do đó, ĐỪNG thêm tông thứ năm nào nữa mà chưa đo lại bảng trên.\n *\n * Vạch chỉ là cách nói \"sắp chạm ngưỡng\" trên chính cái thanh; nó KHÔNG thay được một câu chữ.\n * Chỗ nào cần nói rõ ngưỡng là bao nhiêu và làm gì tiếp thì vẫn phải có `Alert`/`StatusPill` hoặc\n * một dòng `text-warning-ink` cạnh thanh.\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-gold-100 dark:bg-gold-500/30\",\n success: \"bg-success-ink/20\",\n warning: \"bg-warning-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-gold-600 dark:bg-gold-100\",\n success: \"bg-success-ink\",\n // `hazard-stripes` là PHẦN BẮT BUỘC của tông này, không phải trang trí — xem chú thích đầu file.\n warning: \"bg-warning-ink hazard-stripes\",\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 `warning` phải kèm vạch chéo. */\nexport type ProgressTone = NonNullable<VariantProps<typeof progressBarVariants>[\"tone\"]>;\n\nexport interface ProgressProps\n extends React.ComponentProps<typeof ProgressPrimitive.Root>,\n VariantProps<typeof progressBarVariants> {}\n\n/** Mặc định của Radix — `max` không hợp lệ thì Radix cũng rơi về đúng số này cho `aria-*`. */\nconst DEFAULT_MAX = 100;\n\n/**\n * Kẹp `value` vào `[0, max]` — giá trị DUY NHẤT mà cả thanh lẫn Radix (`aria-*`) đọc (xem chú thích\n * đầu file). Không phải số hữu hạn (`null`, `undefined`, `NaN`) thì giữ nguyên: Radix coi là\n * indeterminate và thanh đứng ở 0.\n */\nfunction clampValue(value: number | null | undefined, max: number): number | null | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return value;\n return Math.min(Math.max(value, 0), max);\n}\n\nfunction Progress({ className, value, max, tone = \"default\", ...props }: ProgressProps) {\n const validMax = typeof max === \"number\" && max > 0 ? max : DEFAULT_MAX;\n const clamped = clampValue(value, validMax);\n const percent = typeof clamped === \"number\" && Number.isFinite(clamped) ? (clamped / validMax) * 100 : 0;\n\n return (\n <ProgressPrimitive.Root\n data-slot=\"progress\"\n data-tone={tone}\n className={cn(progressTrackVariants({ tone }), className)}\n value={clamped}\n max={max}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n className={progressBarVariants({ tone })}\n style={{ transform: `translateX(-${100 - percent}%)` }}\n />\n </ProgressPrimitive.Root>\n );\n}\n\nexport { Progress, progressBarVariants, progressTrackVariants };\n"],"mappings":";AAwKM;AAtKN,YAAY,uBAAuB;AACnC,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAwGnB,MAAM,wBAAwB,IAAI,+EAA+E;AAAA,EAC/G,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,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;AAAA,MAET,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CAAC;AAUD,MAAM,cAAc;AAOpB,SAAS,WAAW,OAAkC,KAAwC;AAC5F,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACjE,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,GAAG;AACzC;AAEA,SAAS,SAAS,EAAE,WAAW,OAAO,KAAK,OAAO,WAAW,GAAG,MAAM,GAAkB;AACtF,QAAM,WAAW,OAAO,QAAQ,YAAY,MAAM,IAAI,MAAM;AAC5D,QAAM,UAAU,WAAW,OAAO,QAAQ;AAC1C,QAAM,UAAU,OAAO,YAAY,YAAY,OAAO,SAAS,OAAO,IAAK,UAAU,WAAY,MAAM;AAEvG,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,aAAU;AAAA,MACV,aAAW;AAAA,MACX,WAAW,GAAG,sBAAsB,EAAE,KAAK,CAAC,GAAG,SAAS;AAAA,MACxD,OAAO;AAAA,MACP;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,MAAM,OAAO,KAAK;AAAA;AAAA,MACvD;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…`max`, `max` mặc định 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 * ─ THANH VẼ THEO `value / max`, VÀ THANH VỚI `aria-*` NÓI CÙNG MỘT CON SỐ ─\n * Radix đọc `max` để dựng `aria-valuemax` và `aria-valuetext` (`round(value / max · 100)%`). Trước\n * `3.3.3` phần đã chạy lại vẽ bằng `100 - value` — bỏ qua `max` —, nên `max={30} value={24}` vẽ 24%\n * trong khi trình đọc màn hình đọc 80%: hai người nhìn cùng một control nhận hai con số.\n *\n * Nay cả hai đi từ MỘT giá trị: `value` được KẸP vào `[0, max]` rồi mới giao cho Radix (`max` không\n * phải số dương → 100, đúng luật Radix). Kẹp chứ không coi là indeterminate, vì ca vượt ngưỡng hay\n * gặp nhất là CÓ Ý: hạn mức dùng 120% phải là thanh ĐẦY, không phải thanh rỗng. Trước `3.3.3` ca đó\n * tình cờ đã đầy (`translateX(--20%)` là CSS vô hiệu) nhưng Radix coi `value` sai → không có\n * `aria-valuetext`, kèm lỗi console; nay thanh vẫn đầy VÀ trình đọc màn hình đọc \"100%\". Nói rõ là\n * đã vượt (bao nhiêu, làm gì tiếp) vẫn là việc của chữ cạnh thanh và `tone`, không của thanh.\n *\n * ─ ⛔ ĐỪNG ĐỔI TÔNG `default` SANG MÀU ĐẬM HƠN (chốt 2026-11-01) ─\n * 3.13.0 đổi thanh sang `gold-600` / rãnh `gold-100` để qua tương phản ở bảng mặc định; trên giao diện thật thanh nâu\n * sẫm nặng hơn mọi thứ quanh nó và bị trả về đây ở 3.13.1. Mức 1,73:1 (thanh ⇄ nền) / 1,55:1 (thanh ⇄ rãnh) ở bảng\n * MẶC ĐỊNH là ĐÃ CHẤP NHẬN — bỏ qua phép đo tương phản cho phần tô này. Cần rõ hơn thì chữ cạnh thanh, `tone`, hoặc\n * bảng tương phản cao (`data-contrast=\"high\"`, nơi các số đo bên dưới đạt). Xem CLAUDE.md, mục màu.\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: BỐN GIÁ TRỊ, VÀ `warning` KHÔNG ĐƯỢC PHÉP CHỈ LÀ MỘT MÀU KHÁC ─\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 * ⚠ Cột \"light\"/\"dark\" dưới đây là **bảng TƯƠNG PHẢN CAO** — đúng bảng mà cổng màu đo, và lý do\n * ghi ở đầu `scripts/a11y/tailwind.mjs`. Ở bảng mặc định (bản duyệt comitor-ds) `--primary-ink`\n * chính là `--primary` gold #E8B824, nên tông `default` chỉ được 1,73:1 với nền và 1,55:1 với\n * rãnh: đó là con số của BẢN DUYỆT, không phải lỗi của component — đổi nó là đổi bảng màu.\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 * warning 6,09:1 10,52 4,54:1 6,47:1 ✓\n * destructive 6,68:1 5,82 4,85:1 4,25:1 ✓\n *\n * ⚠ NHƯNG `warning` KHÔNG phân biệt được với `default` BẰNG MÀU, và đó là số đo chứ không phải\n * cảm tính. Đo `--warning-ink` ⇄ `--primary-ink` trên cả BỐN bảng màu của gói:\n *\n * light (mặc định) #7A5800 ⇄ #E8B824 3,51:1 ✓\n * light (tương phản cao) #7A5800 ⇄ #7A5800 1,00:1 ✗ CÙNG MỘT HEX\n * dark (mặc định) #F2C94C ⇄ #F5D060 1,06:1 ✗\n * dark (tương phản cao) #FACC15 ⇄ #F5D060 1,03:1 ✗\n *\n * Ba trên bốn bảng màu cho ra hai thanh trông y hệt nhau. Đây KHÔNG sửa được ở tầng token: brand\n * của Comitor là gold, warning là yellow — cùng một sắc. Muốn tách hai bậc ink ra 3:1 thì một\n * trong hai phải rời khỏi vùng đọc-được: với nền trắng, chữ đạt 4,5:1 đòi độ chói ≤ 0,183, mà\n * cách `--gold-500` (0,111) đúng 3:1 thì phải ≤ 0,004 (gần như đen) hoặc ≥ 0,433 (quá sáng để\n * làm chữ). Hai ràng buộc không giao nhau — không có hex nào thoả cả hai.\n *\n * Nên tông `warning` nói bằng thứ KHÔNG PHẢI MÀU: `hazard-stripes` (khai ở styles.css) khoét\n * những rãnh chéo xuyên phần đã chạy để nền rãnh lộ ra. Ranh giới vạch ⇄ rãnh chính là cặp\n * \"đã chạy ⇄ rãnh\" đã đo ở bảng trên (4,54:1 light · 6,47:1 dark ✓), nên vạch KHÔNG thêm màu nào\n * và không cần đo thêm. Đây đúng là WCAG 1.4.1: tín hiệu không được chỉ nằm ở màu — bản vạch còn\n * đọc ra ở chế độ xám, chỗ mà mọi khác biệt sắc độ đều biến mất.\n *\n * ⚠ Vì vậy: KHÔNG được \"đơn giản hoá\" `warning` xuống còn mỗi `bg-warning-ink`. Bỏ vạch đi là\n * API đổi tông mà không đổi được gì ở ba trên bốn bảng màu — chỗ gọi tưởng đã cảnh báo, người\n * dùng không thấy gì. Cùng lý do đó, ĐỪNG thêm tông thứ năm nào nữa mà chưa đo lại bảng trên.\n *\n * Vạch chỉ là cách nói \"sắp chạm ngưỡng\" trên chính cái thanh; nó KHÔNG thay được một câu chữ.\n * Chỗ nào cần nói rõ ngưỡng là bao nhiêu và làm gì tiếp thì vẫn phải có `Alert`/`StatusPill` hoặc\n * một dòng `text-warning-ink` cạnh thanh.\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 warning: \"bg-warning-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 // `hazard-stripes` là PHẦN BẮT BUỘC của tông này, không phải trang trí — xem chú thích đầu file.\n warning: \"bg-warning-ink hazard-stripes\",\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 `warning` phải kèm vạch chéo. */\nexport type ProgressTone = NonNullable<VariantProps<typeof progressBarVariants>[\"tone\"]>;\n\nexport interface ProgressProps\n extends React.ComponentProps<typeof ProgressPrimitive.Root>,\n VariantProps<typeof progressBarVariants> {}\n\n/** Mặc định của Radix — `max` không hợp lệ thì Radix cũng rơi về đúng số này cho `aria-*`. */\nconst DEFAULT_MAX = 100;\n\n/**\n * Kẹp `value` vào `[0, max]` — giá trị DUY NHẤT mà cả thanh lẫn Radix (`aria-*`) đọc (xem chú thích\n * đầu file). Không phải số hữu hạn (`null`, `undefined`, `NaN`) thì giữ nguyên: Radix coi là\n * indeterminate và thanh đứng ở 0.\n */\nfunction clampValue(value: number | null | undefined, max: number): number | null | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return value;\n return Math.min(Math.max(value, 0), max);\n}\n\nfunction Progress({ className, value, max, tone = \"default\", ...props }: ProgressProps) {\n const validMax = typeof max === \"number\" && max > 0 ? max : DEFAULT_MAX;\n const clamped = clampValue(value, validMax);\n const percent = typeof clamped === \"number\" && Number.isFinite(clamped) ? (clamped / validMax) * 100 : 0;\n\n return (\n <ProgressPrimitive.Root\n data-slot=\"progress\"\n data-tone={tone}\n className={cn(progressTrackVariants({ tone }), className)}\n value={clamped}\n max={max}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n className={progressBarVariants({ tone })}\n style={{ transform: `translateX(-${100 - percent}%)` }}\n />\n </ProgressPrimitive.Root>\n );\n}\n\nexport { Progress, progressBarVariants, progressTrackVariants };\n"],"mappings":";AAwKM;AAtKN,YAAY,uBAAuB;AACnC,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAwGnB,MAAM,wBAAwB,IAAI,+EAA+E;AAAA,EAC/G,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,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;AAAA,MAET,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CAAC;AAUD,MAAM,cAAc;AAOpB,SAAS,WAAW,OAAkC,KAAwC;AAC5F,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACjE,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,GAAG;AACzC;AAEA,SAAS,SAAS,EAAE,WAAW,OAAO,KAAK,OAAO,WAAW,GAAG,MAAM,GAAkB;AACtF,QAAM,WAAW,OAAO,QAAQ,YAAY,MAAM,IAAI,MAAM;AAC5D,QAAM,UAAU,WAAW,OAAO,QAAQ;AAC1C,QAAM,UAAU,OAAO,YAAY,YAAY,OAAO,SAAS,OAAO,IAAK,UAAU,WAAY,MAAM;AAEvG,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,aAAU;AAAA,MACV,aAAW;AAAA,MACX,WAAW,GAAG,sBAAsB,EAAE,KAAK,CAAC,GAAG,SAAS;AAAA,MACxD,OAAO;AAAA,MACP;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,MAAM,OAAO,KAAK;AAAA;AAAA,MACvD;AAAA;AAAA,EACF;AAEJ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comitor/ui",
3
- "version": "3.13.0",
3
+ "version": "3.13.2",
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",