@comitor/ui 3.13.1 → 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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comitor/ui",
3
- "version": "3.13.1",
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",