@comitor/ui 3.12.0 → 3.13.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.
Files changed (55) hide show
  1. package/dist/chat/chat-thread.js +1 -1
  2. package/dist/chat/chat-thread.js.map +1 -1
  3. package/dist/components/composite/data-table.d.ts +2 -5
  4. package/dist/components/composite/data-table.js +9 -33
  5. package/dist/components/composite/data-table.js.map +1 -1
  6. package/dist/components/composite/form-section.js +5 -5
  7. package/dist/components/composite/form-section.js.map +1 -1
  8. package/dist/components/composite/gantt.js +47 -28
  9. package/dist/components/composite/gantt.js.map +1 -1
  10. package/dist/components/composite/inline-edit.js +33 -38
  11. package/dist/components/composite/inline-edit.js.map +1 -1
  12. package/dist/components/composite/search-filter-bar.js +2 -1
  13. package/dist/components/composite/search-filter-bar.js.map +1 -1
  14. package/dist/components/composite/status-config.d.ts +17 -15
  15. package/dist/components/composite/status-config.js +6 -2
  16. package/dist/components/composite/status-config.js.map +1 -1
  17. package/dist/components/composite/tree-view.js +4 -1
  18. package/dist/components/composite/tree-view.js.map +1 -1
  19. package/dist/components/ui/breadcrumb.js +1 -4
  20. package/dist/components/ui/breadcrumb.js.map +1 -1
  21. package/dist/components/ui/button.js +7 -7
  22. package/dist/components/ui/button.js.map +1 -1
  23. package/dist/components/ui/circular-progress.d.ts +9 -4
  24. package/dist/components/ui/circular-progress.js +58 -24
  25. package/dist/components/ui/circular-progress.js.map +1 -1
  26. package/dist/components/ui/progress.d.ts +6 -0
  27. package/dist/components/ui/progress.js +2 -2
  28. package/dist/components/ui/progress.js.map +1 -1
  29. package/dist/components/ui/table.d.ts +7 -13
  30. package/dist/components/ui/table.js +1 -1
  31. package/dist/components/ui/table.js.map +1 -1
  32. package/dist/components/ui/tabs-variants.js +2 -2
  33. package/dist/components/ui/tabs-variants.js.map +1 -1
  34. package/dist/components/ui/timeline.js +19 -14
  35. package/dist/components/ui/timeline.js.map +1 -1
  36. package/dist/i18n/namespaces/file-upload.js +5 -1
  37. package/dist/i18n/namespaces/file-upload.js.map +1 -1
  38. package/dist/i18n/namespaces/image-upload-field.js +10 -0
  39. package/dist/i18n/namespaces/image-upload-field.js.map +1 -1
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.js +2 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/lib/avatar-tone.d.ts +4 -0
  44. package/dist/lib/avatar-tone.js.map +1 -1
  45. package/dist/uploader/file-upload-constants.d.ts +26 -2
  46. package/dist/uploader/file-upload-constants.js.map +1 -1
  47. package/dist/uploader/file-upload.d.ts +18 -1
  48. package/dist/uploader/file-upload.js +50 -5
  49. package/dist/uploader/file-upload.js.map +1 -1
  50. package/dist/uploader/image-upload-field.d.ts +31 -2
  51. package/dist/uploader/image-upload-field.js +139 -8
  52. package/dist/uploader/image-upload-field.js.map +1 -1
  53. package/dist/uploader/index.d.ts +2 -2
  54. package/dist/uploader/index.js.map +1 -1
  55. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/timeline.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/timeline) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { collectItemElements } from \"../../lib/collect-item-elements.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"vertical\" | \"horizontal\";\ntype Variant = \"default\" | \"alternate\";\ntype Status = \"completed\" | \"active\" | \"pending\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\ntype ItemElement = React.ComponentRef<typeof TimelineItem>;\n\nconst ROOT_NAME = \"Timeline\";\nconst ITEM_NAME = \"TimelineItem\";\nconst DOT_NAME = \"TimelineDot\";\nconst CONNECTOR_NAME = \"TimelineConnector\";\nconst CONTENT_NAME = \"TimelineContent\";\n\nfunction getItemStatus(itemIndex: number, activeIndex?: number): Status {\n /*\n * ⚠ Khác thượng nguồn: mục đăng ký chỉ số của mình trong layout effect, nên ở lượt render phía\n * server (và lượt đầu trước hydrate) MỌI mục đều mang chỉ số -1. Bản gốc so thẳng `-1 < activeIndex`\n * → mọi chấm cùng ra \"completed\" (tô gold) cho tới khi hydrate xong — một trạng thái SAI, không\n * chỉ là thiếu. Chưa biết vị trí thì coi là \"pending\": trung tính, và đúng với phần lớn mục.\n * Từ 3.1.0 các lượt đó đọc vị trí từ cây phần tử (`getItemsFromChildren`); -1 chỉ còn khi đọc\n * không được — mục nằm trong component của app — và nhánh này vẫn giữ nó ở \"pending\".\n */\n if (activeIndex === undefined || itemIndex < 0) return \"pending\";\n if (itemIndex < activeIndex) return \"completed\";\n if (itemIndex === activeIndex) return \"active\";\n return \"pending\";\n}\n\nfunction getSortedEntries(entries: [string, React.RefObject<ItemElement | null>][]) {\n return entries.sort((a, b) => {\n const elementA = a[1].current;\n const elementB = b[1].current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;\n if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;\n return 0;\n });\n}\n\n/*\n * ⚠ Khác thượng nguồn (3.1.0): vị trí các mục ĐỌC TỪ CÂY PHẦN TỬ của `Timeline`, theo thứ tự tài\n * liệu — `TimelineItem` viết trong gốc qua mảng, fragment, thẻ HTML (luật ở `collectItemElements`).\n * Mục không có `value`, nên vị trí khoá bằng CHÍNH object props của phần tử: React đưa đúng object\n * đó cho `TimelineItem` khi render nó — ở server cũng như ở trình duyệt. Mục do component của app\n * render thì không có trong bảng, nên không có vị trí (-1). `WeakMap` để không giữ props của lượt\n * đầu sống mãi.\n */\ninterface InitialItems {\n indexes: WeakMap<object, number>;\n count: number;\n}\n\nfunction getItemsFromChildren(children: React.ReactNode): InitialItems {\n const indexes = new WeakMap<object, number>();\n const items = collectItemElements(children, { item: TimelineItem }) ?? [];\n for (const [index, { props }] of items.entries()) {\n // Cùng MỘT phần tử đặt hai lần thì hai mục chung một object props — không phân biệt được.\n if (indexes.has(props)) return { indexes: new WeakMap(), count: 0 };\n indexes.set(props, index);\n }\n return { indexes, count: items.length };\n}\n\n/*\n * ⚠ Khác thượng nguồn (3.1.0): `initialSelector` là giá trị đọc từ cây phần tử (xem `Timeline`).\n * Nó là snapshot SERVER — React dùng ở server và ở lượt hydrate, nên hai bên ra cùng một HTML — và\n * là snapshot client cho tới layout effect đầu của gốc. Bản gốc dùng `selector` cho mọi lượt.\n */\nfunction useStore<T>(selector: (store: Store) => T, initialSelector: (store: Store) => T): T {\n const store = React.useContext(StoreContext);\n if (!store) {\n throw new Error(`\\`useStore\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(\n () => (store.hasRegistered() ? selector(store) : initialSelector(store)),\n [store, selector, initialSelector]\n );\n const getServerSnapshot = React.useCallback(() => initialSelector(store), [store, initialSelector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getServerSnapshot);\n}\n\ninterface StoreState {\n items: Map<string, React.RefObject<ItemElement | null>>;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n notify: () => void;\n onItemRegister: (id: string, ref: React.RefObject<ItemElement | null>) => void;\n onItemUnregister: (id: string) => void;\n getNextItemStatus: (id: string, activeIndex?: number) => Status | undefined;\n getItemIndex: (id: string) => number;\n hasRegistered: () => boolean;\n getInitialItemIndex: (itemProps: object) => number;\n getInitialNextItemStatus: (initialIndex: number, activeIndex?: number) => Status | undefined;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface TimelineContextValue {\n dir: Direction;\n orientation: Orientation;\n variant: Variant;\n activeIndex?: number;\n}\n\nconst TimelineContext = React.createContext<TimelineContextValue | null>(null);\n\nfunction useTimelineContext(consumerName: string) {\n const context = React.useContext(TimelineContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nconst timelineVariants = cva(\"relative flex [--timeline-connector-thickness:0.125rem] [--timeline-dot-size:0.875rem]\", {\n variants: {\n orientation: {\n vertical: \"flex-col\",\n horizontal: \"flex-row items-start\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n }\n },\n compoundVariants: [\n {\n orientation: \"vertical\",\n variant: \"default\",\n class: \"gap-6\"\n },\n {\n orientation: \"horizontal\",\n variant: \"default\",\n class: \"gap-8\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n class: \"relative w-full gap-3\"\n },\n {\n orientation: \"horizontal\",\n variant: \"alternate\",\n class: \"items-center gap-4\"\n }\n ],\n defaultVariants: {\n orientation: \"vertical\",\n variant: \"default\"\n }\n});\n\ninterface TimelineProps extends DivProps {\n dir?: Direction;\n orientation?: Orientation;\n variant?: Variant;\n activeIndex?: number;\n}\n\nfunction Timeline(props: TimelineProps) {\n const {\n orientation = \"vertical\",\n variant = \"default\",\n dir: dirProp,\n activeIndex,\n asChild,\n className,\n ...rootProps\n } = props;\n\n const dir = useDirection(dirProp);\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<StoreState>(() => ({\n items: new Map()\n }));\n /*\n * ⚠ Khác thượng nguồn (3.1.0): vị trí cho mọi lượt render TRƯỚC khi các mục đăng ký. Bản gốc chỉ\n * biết vị trí qua đăng ký trong layout effect, nên HTML server — và khung hình đầu, cả lúc hydrate\n * lẫn lúc mount thuần client — có mọi mục \"pending\", KHÔNG vạch nối nào, và ở `variant=\"alternate\"`\n * mọi mục dồn một bên; mount xong mới tự đúng, thành một khung nháy (kèm bố cục nhảy).\n * - Chụp MỘT lần, ở lượt render đầu, và không bao giờ sửa: nó là snapshot SERVER của `useStore`,\n * nên phải đúng là cái server đã render — kể cả cho phần hydrate muộn hơn gốc.\n * - Chỉ có hiệu lực tới layout effect đầu của gốc (`registeredRef`). Effect của con chạy trước của\n * cha, nên lúc đó mọi mục đọc được đã đăng ký, và thứ tự DOM của chúng chính là thứ tự đọc được —\n * đổi nguồn không đổi gì trên màn hình. Từ đó thứ tự DOM của các mục đã đăng ký thắng như cũ.\n * - Đọc không được (mục nằm trong component của app, hay cây viết thẳng trong Server Component) thì\n * không mục nào có vị trí — y như trước bản này (xem `getItemStatus`).\n */\n const initialItemsRef = useLazyRef(() => getItemsFromChildren(props.children));\n const registeredRef = React.useRef(false);\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n },\n onItemRegister: (id: string, ref: React.RefObject<ItemElement | null>) => {\n stateRef.current.items.set(id, ref);\n store.notify();\n },\n onItemUnregister: (id: string) => {\n stateRef.current.items.delete(id);\n store.notify();\n },\n getNextItemStatus: (id: string, activeIndex?: number) => {\n const entries = Array.from(stateRef.current.items.entries());\n const sortedEntries = getSortedEntries(entries);\n\n const currentIndex = sortedEntries.findIndex(([key]) => key === id);\n if (currentIndex === -1 || currentIndex === sortedEntries.length - 1) {\n return undefined;\n }\n\n const nextItemIndex = currentIndex + 1;\n return getItemStatus(nextItemIndex, activeIndex);\n },\n getItemIndex: (id: string) => {\n const entries = Array.from(stateRef.current.items.entries());\n const sortedEntries = getSortedEntries(entries);\n return sortedEntries.findIndex(([key]) => key === id);\n },\n hasRegistered: () => registeredRef.current,\n getInitialItemIndex: (itemProps: object) => initialItemsRef.current.indexes.get(itemProps) ?? -1,\n // Cùng luật với `getNextItemStatus`, trên vị trí đọc từ cây.\n getInitialNextItemStatus: (initialIndex: number, activeIndex?: number) => {\n if (initialIndex === -1 || initialIndex === initialItemsRef.current.count - 1) {\n return undefined;\n }\n return getItemStatus(initialIndex + 1, activeIndex);\n }\n };\n }, [listenersRef, stateRef, initialItemsRef]);\n\n // ⚠ Khác thượng nguồn (3.1.0): từ đây vị trí ĐĂNG KÝ là nguồn sự thật — xem `initialItemsRef`.\n useIsomorphicLayoutEffect(() => {\n registeredRef.current = true;\n store.notify();\n }, [store]);\n\n const contextValue = React.useMemo<TimelineContextValue>(\n () => ({\n dir,\n orientation,\n variant,\n activeIndex\n }),\n [dir, orientation, variant, activeIndex]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <StoreContext.Provider value={store}>\n <TimelineContext.Provider value={contextValue}>\n {/*\n * ⚠ Khác thượng nguồn: bỏ `aria-orientation` khỏi thẻ gốc. `role=\"list\"` không nhận thuộc\n * tính đó theo ARIA 1.2 (axe báo `aria-allowed-attr`); hướng vẫn có ở `data-orientation`.\n */}\n <RootPrimitive\n role=\"list\"\n data-slot=\"timeline\"\n data-orientation={orientation}\n data-variant={variant}\n dir={dir}\n {...rootProps}\n className={cn(timelineVariants({ orientation, variant, className }))}\n />\n </TimelineContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface TimelineItemContextValue {\n id: string;\n status: Status;\n isAlternateRight: boolean;\n /** Vị trí đọc từ cây phần tử (-1 nếu không đọc được) — cho snapshot server của `TimelineConnector`. */\n initialIndex: number;\n}\n\nconst TimelineItemContext = React.createContext<TimelineItemContextValue | null>(null);\n\nfunction useTimelineItemContext(consumerName: string) {\n const context = React.useContext(TimelineItemContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ITEM_NAME}\\``);\n }\n return context;\n}\n\nconst timelineItemVariants = cva(\"relative flex\", {\n variants: {\n orientation: {\n vertical: \"\",\n horizontal: \"\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n },\n isAlternateRight: {\n true: \"\",\n false: \"\"\n }\n },\n compoundVariants: [\n {\n orientation: \"vertical\",\n variant: \"default\",\n class: \"gap-3 pb-8 last:pb-0\"\n },\n {\n orientation: \"horizontal\",\n variant: \"default\",\n class: \"flex-col gap-3\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n isAlternateRight: false,\n class: \"w-1/2 gap-3 pr-6 pb-12 last:pb-0\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n isAlternateRight: true,\n class: \"ml-auto w-1/2 flex-row-reverse gap-3 pb-12 pl-6 last:pb-0\"\n },\n {\n orientation: \"horizontal\",\n variant: \"alternate\",\n class: \"grid min-w-0 grid-rows-[1fr_auto_1fr] gap-3\"\n }\n ],\n defaultVariants: {\n orientation: \"vertical\",\n variant: \"default\",\n isAlternateRight: false\n }\n});\n\nfunction TimelineItem(props: DivProps) {\n const { asChild, className, id, ref, ...itemProps } = props;\n\n const { dir, orientation, variant, activeIndex } = useTimelineContext(ITEM_NAME);\n const store = useStoreContext(ITEM_NAME);\n\n const instanceId = React.useId();\n const itemId = id ?? instanceId;\n const itemRef = React.useRef<ItemElement | null>(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n // ⚠ Khác thượng nguồn (3.1.0): trước khi đăng ký, vị trí là vị trí đọc từ cây phần tử của gốc —\n // tra bằng chính object `props` này (xem `getItemsFromChildren`).\n const initialIndex = store.getInitialItemIndex(props);\n const itemIndex = useStore(\n (state) => state.getItemIndex(itemId),\n () => initialIndex\n );\n\n const status = React.useMemo<Status>(() => {\n return getItemStatus(itemIndex, activeIndex);\n }, [activeIndex, itemIndex]);\n\n // ⚠ Khác thượng nguồn: phụ thuộc là `itemId` (thứ thật sự được dùng) thay cho `id` — cùng\n // nghĩa, vì `itemId = id ?? instanceId` và `instanceId` không đổi suốt vòng đời.\n useIsomorphicLayoutEffect(() => {\n store.onItemRegister(itemId, itemRef);\n return () => {\n store.onItemUnregister(itemId);\n };\n }, [itemId, store]);\n\n const isAlternateRight = variant === \"alternate\" && itemIndex % 2 === 1;\n\n const itemContextValue = React.useMemo<TimelineItemContextValue>(\n () => ({ id: itemId, status, isAlternateRight, initialIndex }),\n [itemId, status, isAlternateRight, initialIndex]\n );\n\n const ItemPrimitive = asChild ? Slot : \"div\";\n\n return (\n <TimelineItemContext.Provider value={itemContextValue}>\n <ItemPrimitive\n role=\"listitem\"\n aria-current={status === \"active\" ? \"step\" : undefined}\n data-slot=\"timeline-item\"\n data-status={status}\n data-orientation={orientation}\n data-alternate-right={isAlternateRight ? \"\" : undefined}\n id={itemId}\n dir={dir}\n {...itemProps}\n ref={composedRef}\n className={cn(\n timelineItemVariants({\n orientation,\n variant,\n isAlternateRight,\n className\n })\n )}\n />\n </TimelineItemContext.Provider>\n );\n}\n\nconst timelineContentVariants = cva(\"flex-1\", {\n variants: {\n orientation: {\n vertical: \"\",\n horizontal: \"\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n },\n isAlternateRight: {\n true: \"\",\n false: \"\"\n }\n },\n compoundVariants: [\n {\n variant: \"alternate\",\n orientation: \"vertical\",\n isAlternateRight: false,\n class: \"text-right\"\n },\n {\n variant: \"alternate\",\n orientation: \"horizontal\",\n isAlternateRight: false,\n class: \"row-start-3 pt-2\"\n },\n {\n variant: \"alternate\",\n orientation: \"horizontal\",\n isAlternateRight: true,\n class: \"row-start-1 pb-2\"\n }\n ],\n defaultVariants: {\n orientation: \"vertical\",\n variant: \"default\",\n isAlternateRight: false\n }\n});\n\nfunction TimelineContent(props: DivProps) {\n const { asChild, className, ...contentProps } = props;\n\n const { variant, orientation } = useTimelineContext(CONTENT_NAME);\n const { status, isAlternateRight } = useTimelineItemContext(CONTENT_NAME);\n\n const ContentPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ContentPrimitive\n data-slot=\"timeline-content\"\n data-status={status}\n {...contentProps}\n className={cn(\n timelineContentVariants({\n orientation,\n variant,\n isAlternateRight,\n className\n })\n )}\n />\n );\n}\n\n/*\n * ⚠ Khác thượng nguồn — màu chấm đi qua token VAI TRÒ (docs/hop-dong-mau.md §1):\n * chấm là thành phần PHI VĂN BẢN mang nghĩa (đánh dấu từng mốc và trạng thái của nó) → viền chịu\n * 3:1 với nền trang.\n * - đã xong / đang diễn ra: `border-primary-ink` thay `border-primary` (token TÔ NỀN, ~1,86:1 với\n * nền trang; bậc ink đạt 6,51:1 light · 12,67:1 dark ở bảng tương phản cao).\n * - chờ: `border-muted-foreground` thay `border-border` (bậc TRANG TRÍ 1,16:1, và cổng bắt viền trang\n * trí khi phần tử có bo góc + cỡ cố định). Cùng lựa chọn với tone `neutral` của `StatusPill`\n * (`STATUS_TONES.neutral.borderColor`): 5,23:1 light · 5,70:1 dark.\n */\nconst timelineDotVariants = cva(\n \"relative z-10 flex size-[var(--timeline-dot-size)] shrink-0 items-center justify-center rounded-full border-2 bg-background\",\n {\n variants: {\n status: {\n completed: \"border-primary-ink\",\n active: \"border-primary-ink\",\n pending: \"border-muted-foreground\"\n },\n orientation: {\n vertical: \"\",\n horizontal: \"\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n },\n isAlternateRight: {\n true: \"\",\n false: \"\"\n }\n },\n compoundVariants: [\n {\n variant: \"alternate\",\n orientation: \"vertical\",\n isAlternateRight: false,\n class: \"absolute -right-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] bg-background\"\n },\n {\n variant: \"alternate\",\n orientation: \"vertical\",\n isAlternateRight: true,\n class: \"absolute -left-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] bg-background\"\n },\n {\n variant: \"alternate\",\n orientation: \"horizontal\",\n class: \"row-start-2 bg-background\"\n },\n {\n variant: \"alternate\",\n status: \"completed\",\n class: \"bg-background\"\n },\n {\n variant: \"alternate\",\n status: \"active\",\n class: \"bg-background\"\n }\n ],\n defaultVariants: {\n status: \"pending\",\n orientation: \"vertical\",\n variant: \"default\",\n isAlternateRight: false\n }\n }\n);\n\nfunction TimelineDot(props: DivProps) {\n const { asChild, className, ...dotProps } = props;\n\n const { orientation, variant } = useTimelineContext(DOT_NAME);\n const { status, isAlternateRight } = useTimelineItemContext(DOT_NAME);\n\n const DotPrimitive = asChild ? Slot : \"div\";\n\n return (\n <DotPrimitive\n data-slot=\"timeline-dot\"\n data-status={status}\n data-orientation={orientation}\n {...dotProps}\n className={cn(\n timelineDotVariants({\n status,\n orientation,\n variant,\n isAlternateRight,\n className\n })\n )}\n />\n );\n}\n\n/*\n * ⚠ Khác thượng nguồn: đoạn nối ĐÃ XONG mang nghĩa tiến độ → `bg-primary-ink` thay `bg-primary`\n * (token tô nền, ~1,86:1 với nền trang). Đoạn chưa tới giữ `bg-border` — đường kẻ trang trí.\n */\nconst timelineConnectorVariants = cva(\"absolute z-0\", {\n variants: {\n isCompleted: {\n true: \"bg-primary-ink\",\n false: \"bg-border\"\n },\n orientation: {\n vertical: \"\",\n horizontal: \"\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n },\n isAlternateRight: {\n true: \"\",\n false: \"\"\n }\n },\n compoundVariants: [\n {\n orientation: \"vertical\",\n variant: \"default\",\n class:\n \"start-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] top-3 h-[calc(100%+0.5rem)] w-[var(--timeline-connector-thickness)]\"\n },\n {\n orientation: \"horizontal\",\n variant: \"default\",\n class:\n \"start-3 top-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] h-[var(--timeline-connector-thickness)] w-[calc(100%+0.5rem)]\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n isAlternateRight: false,\n class: \"top-2 -right-[calc(var(--timeline-connector-thickness)/2)] h-full w-[var(--timeline-connector-thickness)]\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n isAlternateRight: true,\n class: \"top-2 -left-[calc(var(--timeline-connector-thickness)/2)] h-full w-[var(--timeline-connector-thickness)]\"\n },\n {\n orientation: \"horizontal\",\n variant: \"alternate\",\n class:\n \"top-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] left-3 row-start-2 h-[var(--timeline-connector-thickness)] w-[calc(100%+0.5rem)]\"\n }\n ],\n defaultVariants: {\n isCompleted: false,\n orientation: \"vertical\",\n variant: \"default\",\n isAlternateRight: false\n }\n});\n\ninterface TimelineConnectorProps extends DivProps {\n forceMount?: boolean;\n}\n\nfunction TimelineConnector(props: TimelineConnectorProps) {\n const { asChild, forceMount, className, ...connectorProps } = props;\n\n const { orientation, variant, activeIndex } = useTimelineContext(CONNECTOR_NAME);\n const { id, status, isAlternateRight, initialIndex } = useTimelineItemContext(CONNECTOR_NAME);\n\n const nextItemStatus = useStore(\n (state) => state.getNextItemStatus(id, activeIndex),\n (state) => state.getInitialNextItemStatus(initialIndex, activeIndex)\n );\n\n const isLastItem = nextItemStatus === undefined;\n\n if (!forceMount && isLastItem) return null;\n\n const isConnectorCompleted = nextItemStatus === \"completed\" || nextItemStatus === \"active\";\n\n const ConnectorPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ConnectorPrimitive\n aria-hidden=\"true\"\n data-slot=\"timeline-connector\"\n data-completed={isConnectorCompleted ? \"\" : undefined}\n data-status={status}\n data-orientation={orientation}\n {...connectorProps}\n className={cn(\n timelineConnectorVariants({\n isCompleted: isConnectorCompleted,\n orientation,\n variant,\n isAlternateRight,\n className\n })\n )}\n />\n );\n}\n\nfunction TimelineHeader(props: DivProps) {\n const { asChild, className, ...headerProps } = props;\n\n const HeaderPrimitive = asChild ? Slot : \"div\";\n\n return (\n <HeaderPrimitive data-slot=\"timeline-header\" {...headerProps} className={cn(\"flex flex-col gap-1\", className)} />\n );\n}\n\nfunction TimelineTitle(props: DivProps) {\n const { asChild, className, ...titleProps } = props;\n\n const TitlePrimitive = asChild ? Slot : \"div\";\n\n return (\n <TitlePrimitive\n data-slot=\"timeline-title\"\n {...titleProps}\n className={cn(\"leading-none font-semibold\", className)}\n />\n );\n}\n\nfunction TimelineDescription(props: DivProps) {\n const { asChild, className, ...descriptionProps } = props;\n\n const DescriptionPrimitive = asChild ? Slot : \"div\";\n\n return (\n <DescriptionPrimitive\n data-slot=\"timeline-description\"\n {...descriptionProps}\n className={cn(\"text-sm text-muted-foreground\", className)}\n />\n );\n}\n\ninterface TimelineTimeProps extends React.ComponentProps<\"time\"> {\n asChild?: boolean;\n}\n\n/** Mốc thời gian — gói KHÔNG tự định dạng ngày: app truyền chuỗi đã định dạng + `dateTime` máy đọc. */\nfunction TimelineTime(props: TimelineTimeProps) {\n const { asChild, className, ...timeProps } = props;\n\n const TimePrimitive = asChild ? Slot : \"time\";\n\n return (\n <TimePrimitive\n data-slot=\"timeline-time\"\n {...timeProps}\n className={cn(\"text-xs text-muted-foreground\", className)}\n />\n );\n}\n\nexport {\n Timeline,\n TimelineConnector,\n type TimelineConnectorProps,\n TimelineContent,\n TimelineDescription,\n TimelineDot,\n TimelineHeader,\n TimelineItem,\n type TimelineProps,\n TimelineTime,\n type TimelineTimeProps,\n TimelineTitle\n};\n"],"mappings":"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","names":["activeIndex"]}
1
+ {"version":3,"sources":["../../../src/components/ui/timeline.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/timeline) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { collectItemElements } from \"../../lib/collect-item-elements.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"vertical\" | \"horizontal\";\ntype Variant = \"default\" | \"alternate\";\ntype Status = \"completed\" | \"active\" | \"pending\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\ntype ItemElement = React.ComponentRef<typeof TimelineItem>;\n\nconst ROOT_NAME = \"Timeline\";\nconst ITEM_NAME = \"TimelineItem\";\nconst DOT_NAME = \"TimelineDot\";\nconst CONNECTOR_NAME = \"TimelineConnector\";\nconst CONTENT_NAME = \"TimelineContent\";\n\nfunction getItemStatus(itemIndex: number, activeIndex?: number): Status {\n /*\n * ⚠ Khác thượng nguồn: mục đăng ký chỉ số của mình trong layout effect, nên ở lượt render phía\n * server (và lượt đầu trước hydrate) MỌI mục đều mang chỉ số -1. Bản gốc so thẳng `-1 < activeIndex`\n * → mọi chấm cùng ra \"completed\" (tô gold) cho tới khi hydrate xong — một trạng thái SAI, không\n * chỉ là thiếu. Chưa biết vị trí thì coi là \"pending\": trung tính, và đúng với phần lớn mục.\n * Từ 3.1.0 các lượt đó đọc vị trí từ cây phần tử (`getItemsFromChildren`); -1 chỉ còn khi đọc\n * không được — mục nằm trong component của app — và nhánh này vẫn giữ nó ở \"pending\".\n */\n if (activeIndex === undefined || itemIndex < 0) return \"pending\";\n if (itemIndex < activeIndex) return \"completed\";\n if (itemIndex === activeIndex) return \"active\";\n return \"pending\";\n}\n\nfunction getSortedEntries(entries: [string, React.RefObject<ItemElement | null>][]) {\n return entries.sort((a, b) => {\n const elementA = a[1].current;\n const elementB = b[1].current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;\n if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;\n return 0;\n });\n}\n\n/*\n * ⚠ Khác thượng nguồn (3.1.0): vị trí các mục ĐỌC TỪ CÂY PHẦN TỬ của `Timeline`, theo thứ tự tài\n * liệu — `TimelineItem` viết trong gốc qua mảng, fragment, thẻ HTML (luật ở `collectItemElements`).\n * Mục không có `value`, nên vị trí khoá bằng CHÍNH object props của phần tử: React đưa đúng object\n * đó cho `TimelineItem` khi render nó — ở server cũng như ở trình duyệt. Mục do component của app\n * render thì không có trong bảng, nên không có vị trí (-1). `WeakMap` để không giữ props của lượt\n * đầu sống mãi.\n */\ninterface InitialItems {\n indexes: WeakMap<object, number>;\n count: number;\n}\n\nfunction getItemsFromChildren(children: React.ReactNode): InitialItems {\n const indexes = new WeakMap<object, number>();\n const items = collectItemElements(children, { item: TimelineItem }) ?? [];\n for (const [index, { props }] of items.entries()) {\n // Cùng MỘT phần tử đặt hai lần thì hai mục chung một object props — không phân biệt được.\n if (indexes.has(props)) return { indexes: new WeakMap(), count: 0 };\n indexes.set(props, index);\n }\n return { indexes, count: items.length };\n}\n\n/*\n * ⚠ Khác thượng nguồn (3.1.0): `initialSelector` là giá trị đọc từ cây phần tử (xem `Timeline`).\n * Nó là snapshot SERVER — React dùng ở server và ở lượt hydrate, nên hai bên ra cùng một HTML — và\n * là snapshot client cho tới layout effect đầu của gốc. Bản gốc dùng `selector` cho mọi lượt.\n */\nfunction useStore<T>(selector: (store: Store) => T, initialSelector: (store: Store) => T): T {\n const store = React.useContext(StoreContext);\n if (!store) {\n throw new Error(`\\`useStore\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(\n () => (store.hasRegistered() ? selector(store) : initialSelector(store)),\n [store, selector, initialSelector]\n );\n const getServerSnapshot = React.useCallback(() => initialSelector(store), [store, initialSelector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getServerSnapshot);\n}\n\ninterface StoreState {\n items: Map<string, React.RefObject<ItemElement | null>>;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n notify: () => void;\n onItemRegister: (id: string, ref: React.RefObject<ItemElement | null>) => void;\n onItemUnregister: (id: string) => void;\n getNextItemStatus: (id: string, activeIndex?: number) => Status | undefined;\n getItemIndex: (id: string) => number;\n hasRegistered: () => boolean;\n getInitialItemIndex: (itemProps: object) => number;\n getInitialNextItemStatus: (initialIndex: number, activeIndex?: number) => Status | undefined;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface TimelineContextValue {\n dir: Direction;\n orientation: Orientation;\n variant: Variant;\n activeIndex?: number;\n}\n\nconst TimelineContext = React.createContext<TimelineContextValue | null>(null);\n\nfunction useTimelineContext(consumerName: string) {\n const context = React.useContext(TimelineContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nconst timelineVariants = cva(\"relative flex [--timeline-connector-thickness:0.125rem] [--timeline-dot-size:0.875rem]\", {\n variants: {\n orientation: {\n vertical: \"flex-col\",\n horizontal: \"flex-row items-start\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n }\n },\n /*\n * ⚠ Khác thượng nguồn: chiều dọc KHÔNG có `gap` ở gốc. Thượng nguồn CỘNG `gap` ở đây với `pb` của từng mục —\n * `default` 24 + 32 = 56px, `alternate` 12 + 48 = 60px giữa hai mục. Khoảng cách nay chỉ nằm ở `pb-6` của\n * `TimelineItem`, để vạch nối (đo theo chiều cao mục) chạm đúng chấm kế tiếp.\n */\n compoundVariants: [\n {\n orientation: \"horizontal\",\n variant: \"default\",\n class: \"gap-8\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n class: \"relative w-full\"\n },\n {\n orientation: \"horizontal\",\n variant: \"alternate\",\n class: \"items-center gap-4\"\n }\n ],\n defaultVariants: {\n orientation: \"vertical\",\n variant: \"default\"\n }\n});\n\ninterface TimelineProps extends DivProps {\n dir?: Direction;\n orientation?: Orientation;\n variant?: Variant;\n activeIndex?: number;\n}\n\nfunction Timeline(props: TimelineProps) {\n const {\n orientation = \"vertical\",\n variant = \"default\",\n dir: dirProp,\n activeIndex,\n asChild,\n className,\n ...rootProps\n } = props;\n\n const dir = useDirection(dirProp);\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<StoreState>(() => ({\n items: new Map()\n }));\n /*\n * ⚠ Khác thượng nguồn (3.1.0): vị trí cho mọi lượt render TRƯỚC khi các mục đăng ký. Bản gốc chỉ\n * biết vị trí qua đăng ký trong layout effect, nên HTML server — và khung hình đầu, cả lúc hydrate\n * lẫn lúc mount thuần client — có mọi mục \"pending\", KHÔNG vạch nối nào, và ở `variant=\"alternate\"`\n * mọi mục dồn một bên; mount xong mới tự đúng, thành một khung nháy (kèm bố cục nhảy).\n * - Chụp MỘT lần, ở lượt render đầu, và không bao giờ sửa: nó là snapshot SERVER của `useStore`,\n * nên phải đúng là cái server đã render — kể cả cho phần hydrate muộn hơn gốc.\n * - Chỉ có hiệu lực tới layout effect đầu của gốc (`registeredRef`). Effect của con chạy trước của\n * cha, nên lúc đó mọi mục đọc được đã đăng ký, và thứ tự DOM của chúng chính là thứ tự đọc được —\n * đổi nguồn không đổi gì trên màn hình. Từ đó thứ tự DOM của các mục đã đăng ký thắng như cũ.\n * - Đọc không được (mục nằm trong component của app, hay cây viết thẳng trong Server Component) thì\n * không mục nào có vị trí — y như trước bản này (xem `getItemStatus`).\n */\n const initialItemsRef = useLazyRef(() => getItemsFromChildren(props.children));\n const registeredRef = React.useRef(false);\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n },\n onItemRegister: (id: string, ref: React.RefObject<ItemElement | null>) => {\n stateRef.current.items.set(id, ref);\n store.notify();\n },\n onItemUnregister: (id: string) => {\n stateRef.current.items.delete(id);\n store.notify();\n },\n getNextItemStatus: (id: string, activeIndex?: number) => {\n const entries = Array.from(stateRef.current.items.entries());\n const sortedEntries = getSortedEntries(entries);\n\n const currentIndex = sortedEntries.findIndex(([key]) => key === id);\n if (currentIndex === -1 || currentIndex === sortedEntries.length - 1) {\n return undefined;\n }\n\n const nextItemIndex = currentIndex + 1;\n return getItemStatus(nextItemIndex, activeIndex);\n },\n getItemIndex: (id: string) => {\n const entries = Array.from(stateRef.current.items.entries());\n const sortedEntries = getSortedEntries(entries);\n return sortedEntries.findIndex(([key]) => key === id);\n },\n hasRegistered: () => registeredRef.current,\n getInitialItemIndex: (itemProps: object) => initialItemsRef.current.indexes.get(itemProps) ?? -1,\n // Cùng luật với `getNextItemStatus`, trên vị trí đọc từ cây.\n getInitialNextItemStatus: (initialIndex: number, activeIndex?: number) => {\n if (initialIndex === -1 || initialIndex === initialItemsRef.current.count - 1) {\n return undefined;\n }\n return getItemStatus(initialIndex + 1, activeIndex);\n }\n };\n }, [listenersRef, stateRef, initialItemsRef]);\n\n // ⚠ Khác thượng nguồn (3.1.0): từ đây vị trí ĐĂNG KÝ là nguồn sự thật — xem `initialItemsRef`.\n useIsomorphicLayoutEffect(() => {\n registeredRef.current = true;\n store.notify();\n }, [store]);\n\n const contextValue = React.useMemo<TimelineContextValue>(\n () => ({\n dir,\n orientation,\n variant,\n activeIndex\n }),\n [dir, orientation, variant, activeIndex]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <StoreContext.Provider value={store}>\n <TimelineContext.Provider value={contextValue}>\n {/*\n * ⚠ Khác thượng nguồn: bỏ `aria-orientation` khỏi thẻ gốc. `role=\"list\"` không nhận thuộc\n * tính đó theo ARIA 1.2 (axe báo `aria-allowed-attr`); hướng vẫn có ở `data-orientation`.\n */}\n <RootPrimitive\n role=\"list\"\n data-slot=\"timeline\"\n data-orientation={orientation}\n data-variant={variant}\n dir={dir}\n {...rootProps}\n className={cn(timelineVariants({ orientation, variant, className }))}\n />\n </TimelineContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface TimelineItemContextValue {\n id: string;\n status: Status;\n isAlternateRight: boolean;\n /** Vị trí đọc từ cây phần tử (-1 nếu không đọc được) — cho snapshot server của `TimelineConnector`. */\n initialIndex: number;\n}\n\nconst TimelineItemContext = React.createContext<TimelineItemContextValue | null>(null);\n\nfunction useTimelineItemContext(consumerName: string) {\n const context = React.useContext(TimelineItemContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ITEM_NAME}\\``);\n }\n return context;\n}\n\nconst timelineItemVariants = cva(\"relative flex\", {\n variants: {\n orientation: {\n vertical: \"\",\n horizontal: \"\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n },\n isAlternateRight: {\n true: \"\",\n false: \"\"\n }\n },\n compoundVariants: [\n {\n orientation: \"vertical\",\n variant: \"default\",\n class: \"gap-3 pb-6 last:pb-0\"\n },\n {\n orientation: \"horizontal\",\n variant: \"default\",\n class: \"flex-col gap-3\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n isAlternateRight: false,\n class: \"w-1/2 gap-3 pr-6 pb-6 last:pb-0\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n isAlternateRight: true,\n class: \"ml-auto w-1/2 flex-row-reverse gap-3 pb-6 pl-6 last:pb-0\"\n },\n {\n orientation: \"horizontal\",\n variant: \"alternate\",\n class: \"grid min-w-0 grid-rows-[1fr_auto_1fr] gap-3\"\n }\n ],\n defaultVariants: {\n orientation: \"vertical\",\n variant: \"default\",\n isAlternateRight: false\n }\n});\n\nfunction TimelineItem(props: DivProps) {\n const { asChild, className, id, ref, ...itemProps } = props;\n\n const { dir, orientation, variant, activeIndex } = useTimelineContext(ITEM_NAME);\n const store = useStoreContext(ITEM_NAME);\n\n const instanceId = React.useId();\n const itemId = id ?? instanceId;\n const itemRef = React.useRef<ItemElement | null>(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n // ⚠ Khác thượng nguồn (3.1.0): trước khi đăng ký, vị trí là vị trí đọc từ cây phần tử của gốc —\n // tra bằng chính object `props` này (xem `getItemsFromChildren`).\n const initialIndex = store.getInitialItemIndex(props);\n const itemIndex = useStore(\n (state) => state.getItemIndex(itemId),\n () => initialIndex\n );\n\n const status = React.useMemo<Status>(() => {\n return getItemStatus(itemIndex, activeIndex);\n }, [activeIndex, itemIndex]);\n\n // ⚠ Khác thượng nguồn: phụ thuộc là `itemId` (thứ thật sự được dùng) thay cho `id` — cùng\n // nghĩa, vì `itemId = id ?? instanceId` và `instanceId` không đổi suốt vòng đời.\n useIsomorphicLayoutEffect(() => {\n store.onItemRegister(itemId, itemRef);\n return () => {\n store.onItemUnregister(itemId);\n };\n }, [itemId, store]);\n\n const isAlternateRight = variant === \"alternate\" && itemIndex % 2 === 1;\n\n const itemContextValue = React.useMemo<TimelineItemContextValue>(\n () => ({ id: itemId, status, isAlternateRight, initialIndex }),\n [itemId, status, isAlternateRight, initialIndex]\n );\n\n const ItemPrimitive = asChild ? Slot : \"div\";\n\n return (\n <TimelineItemContext.Provider value={itemContextValue}>\n <ItemPrimitive\n role=\"listitem\"\n aria-current={status === \"active\" ? \"step\" : undefined}\n data-slot=\"timeline-item\"\n data-status={status}\n data-orientation={orientation}\n data-alternate-right={isAlternateRight ? \"\" : undefined}\n id={itemId}\n dir={dir}\n {...itemProps}\n ref={composedRef}\n className={cn(\n timelineItemVariants({\n orientation,\n variant,\n isAlternateRight,\n className\n })\n )}\n />\n </TimelineItemContext.Provider>\n );\n}\n\nconst timelineContentVariants = cva(\"flex-1\", {\n variants: {\n orientation: {\n vertical: \"\",\n horizontal: \"\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n },\n isAlternateRight: {\n true: \"\",\n false: \"\"\n }\n },\n compoundVariants: [\n {\n variant: \"alternate\",\n orientation: \"vertical\",\n isAlternateRight: false,\n class: \"text-right\"\n },\n {\n variant: \"alternate\",\n orientation: \"horizontal\",\n isAlternateRight: false,\n class: \"row-start-3 pt-2\"\n },\n {\n variant: \"alternate\",\n orientation: \"horizontal\",\n isAlternateRight: true,\n class: \"row-start-1 pb-2\"\n }\n ],\n defaultVariants: {\n orientation: \"vertical\",\n variant: \"default\",\n isAlternateRight: false\n }\n});\n\nfunction TimelineContent(props: DivProps) {\n const { asChild, className, ...contentProps } = props;\n\n const { variant, orientation } = useTimelineContext(CONTENT_NAME);\n const { status, isAlternateRight } = useTimelineItemContext(CONTENT_NAME);\n\n const ContentPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ContentPrimitive\n data-slot=\"timeline-content\"\n data-status={status}\n {...contentProps}\n className={cn(\n timelineContentVariants({\n orientation,\n variant,\n isAlternateRight,\n className\n })\n )}\n />\n );\n}\n\n/*\n * ⚠ Khác thượng nguồn — màu chấm đi qua token VAI TRÒ (docs/hop-dong-mau.md §1):\n * chấm là thành phần PHI VĂN BẢN mang nghĩa (đánh dấu từng mốc và trạng thái của nó) → viền chịu\n * 3:1 với nền trang.\n * - đã xong / đang diễn ra: `border-primary-ink` thay `border-primary` (token TÔ NỀN, ~1,86:1 với\n * nền trang; bậc ink đạt 6,51:1 light · 12,67:1 dark ở bảng tương phản cao).\n * - chờ: `border-muted-foreground` thay `border-border` (bậc TRANG TRÍ 1,16:1, và cổng bắt viền trang\n * trí khi phần tử có bo góc + cỡ cố định). Cùng lựa chọn với tone `neutral` của `StatusPill`\n * (`STATUS_TONES.neutral.borderColor`): 5,23:1 light · 5,70:1 dark.\n */\nconst timelineDotVariants = cva(\n \"relative z-10 flex size-[var(--timeline-dot-size)] shrink-0 items-center justify-center rounded-full border-2 bg-background\",\n {\n variants: {\n status: {\n completed: \"border-primary-ink\",\n active: \"border-primary-ink\",\n pending: \"border-muted-foreground\"\n },\n orientation: {\n vertical: \"\",\n horizontal: \"\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n },\n isAlternateRight: {\n true: \"\",\n false: \"\"\n }\n },\n /*\n * ⚠ Khác thượng nguồn: chấm dọc `alternate` lùi đúng NỬA CHẤM ra ngoài mép mục, để tâm chấm nằm trên mép —\n * nơi vạch nối (`-right/-left: thickness/2`) đặt TÂM của nó. Thượng nguồn trừ thêm nửa bề dày vạch, nên tâm\n * chấm lệch 1px vào trong mục: mục trái lệch trái, mục phải lệch phải — chấm đi zíc zắc quanh vạch.\n */\n compoundVariants: [\n {\n variant: \"alternate\",\n orientation: \"vertical\",\n isAlternateRight: false,\n class: \"absolute -right-[calc(var(--timeline-dot-size)/2)] bg-background\"\n },\n {\n variant: \"alternate\",\n orientation: \"vertical\",\n isAlternateRight: true,\n class: \"absolute -left-[calc(var(--timeline-dot-size)/2)] bg-background\"\n },\n {\n variant: \"alternate\",\n orientation: \"horizontal\",\n class: \"row-start-2 bg-background\"\n },\n {\n variant: \"alternate\",\n status: \"completed\",\n class: \"bg-background\"\n },\n {\n variant: \"alternate\",\n status: \"active\",\n class: \"bg-background\"\n }\n ],\n defaultVariants: {\n status: \"pending\",\n orientation: \"vertical\",\n variant: \"default\",\n isAlternateRight: false\n }\n }\n);\n\nfunction TimelineDot(props: DivProps) {\n const { asChild, className, ...dotProps } = props;\n\n const { orientation, variant } = useTimelineContext(DOT_NAME);\n const { status, isAlternateRight } = useTimelineItemContext(DOT_NAME);\n\n const DotPrimitive = asChild ? Slot : \"div\";\n\n return (\n <DotPrimitive\n data-slot=\"timeline-dot\"\n data-status={status}\n data-orientation={orientation}\n {...dotProps}\n className={cn(\n timelineDotVariants({\n status,\n orientation,\n variant,\n isAlternateRight,\n className\n })\n )}\n />\n );\n}\n\n/*\n * ⚠ Khác thượng nguồn: đoạn nối ĐÃ XONG mang nghĩa tiến độ → `bg-primary-ink` thay `bg-primary`\n * (token tô nền, ~1,86:1 với nền trang). Đoạn chưa tới giữ `bg-border` — đường kẻ trang trí.\n */\nconst timelineConnectorVariants = cva(\"absolute z-0\", {\n variants: {\n isCompleted: {\n true: \"bg-primary-ink\",\n false: \"bg-border\"\n },\n orientation: {\n vertical: \"\",\n horizontal: \"\"\n },\n variant: {\n default: \"\",\n alternate: \"\"\n },\n isAlternateRight: {\n true: \"\",\n false: \"\"\n }\n },\n compoundVariants: [\n {\n orientation: \"vertical\",\n variant: \"default\",\n class:\n \"start-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] top-[var(--timeline-dot-size)] h-[calc(100%-var(--timeline-dot-size))] w-[var(--timeline-connector-thickness)]\"\n },\n {\n orientation: \"horizontal\",\n variant: \"default\",\n class:\n \"start-3 top-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] h-[var(--timeline-connector-thickness)] w-[calc(100%+0.5rem)]\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n isAlternateRight: false,\n class:\n \"top-[var(--timeline-dot-size)] -right-[calc(var(--timeline-connector-thickness)/2)] h-[calc(100%-var(--timeline-dot-size))] w-[var(--timeline-connector-thickness)]\"\n },\n {\n orientation: \"vertical\",\n variant: \"alternate\",\n isAlternateRight: true,\n class:\n \"top-[var(--timeline-dot-size)] -left-[calc(var(--timeline-connector-thickness)/2)] h-[calc(100%-var(--timeline-dot-size))] w-[var(--timeline-connector-thickness)]\"\n },\n {\n orientation: \"horizontal\",\n variant: \"alternate\",\n class:\n \"top-[calc(var(--timeline-dot-size)/2-var(--timeline-connector-thickness)/2)] left-3 row-start-2 h-[var(--timeline-connector-thickness)] w-[calc(100%+0.5rem)]\"\n }\n ],\n defaultVariants: {\n isCompleted: false,\n orientation: \"vertical\",\n variant: \"default\",\n isAlternateRight: false\n }\n});\n\ninterface TimelineConnectorProps extends DivProps {\n forceMount?: boolean;\n}\n\nfunction TimelineConnector(props: TimelineConnectorProps) {\n const { asChild, forceMount, className, ...connectorProps } = props;\n\n const { orientation, variant, activeIndex } = useTimelineContext(CONNECTOR_NAME);\n const { id, status, isAlternateRight, initialIndex } = useTimelineItemContext(CONNECTOR_NAME);\n\n const nextItemStatus = useStore(\n (state) => state.getNextItemStatus(id, activeIndex),\n (state) => state.getInitialNextItemStatus(initialIndex, activeIndex)\n );\n\n const isLastItem = nextItemStatus === undefined;\n\n if (!forceMount && isLastItem) return null;\n\n const isConnectorCompleted = nextItemStatus === \"completed\" || nextItemStatus === \"active\";\n\n const ConnectorPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ConnectorPrimitive\n aria-hidden=\"true\"\n data-slot=\"timeline-connector\"\n data-completed={isConnectorCompleted ? \"\" : undefined}\n data-status={status}\n data-orientation={orientation}\n {...connectorProps}\n className={cn(\n timelineConnectorVariants({\n isCompleted: isConnectorCompleted,\n orientation,\n variant,\n isAlternateRight,\n className\n })\n )}\n />\n );\n}\n\nfunction TimelineHeader(props: DivProps) {\n const { asChild, className, ...headerProps } = props;\n\n const HeaderPrimitive = asChild ? Slot : \"div\";\n\n return (\n <HeaderPrimitive data-slot=\"timeline-header\" {...headerProps} className={cn(\"flex flex-col gap-1\", className)} />\n );\n}\n\nfunction TimelineTitle(props: DivProps) {\n const { asChild, className, ...titleProps } = props;\n\n const TitlePrimitive = asChild ? Slot : \"div\";\n\n return (\n <TitlePrimitive\n data-slot=\"timeline-title\"\n {...titleProps}\n className={cn(\"leading-none font-semibold\", className)}\n />\n );\n}\n\nfunction TimelineDescription(props: DivProps) {\n const { asChild, className, ...descriptionProps } = props;\n\n const DescriptionPrimitive = asChild ? Slot : \"div\";\n\n return (\n <DescriptionPrimitive\n data-slot=\"timeline-description\"\n {...descriptionProps}\n className={cn(\"text-sm text-muted-foreground\", className)}\n />\n );\n}\n\ninterface TimelineTimeProps extends React.ComponentProps<\"time\"> {\n asChild?: boolean;\n}\n\n/** Mốc thời gian — gói KHÔNG tự định dạng ngày: app truyền chuỗi đã định dạng + `dateTime` máy đọc. */\nfunction TimelineTime(props: TimelineTimeProps) {\n const { asChild, className, ...timeProps } = props;\n\n const TimePrimitive = asChild ? Slot : \"time\";\n\n return (\n <TimePrimitive\n data-slot=\"timeline-time\"\n {...timeProps}\n className={cn(\"text-xs text-muted-foreground\", className)}\n />\n );\n}\n\nexport {\n Timeline,\n TimelineConnector,\n type TimelineConnectorProps,\n TimelineContent,\n TimelineDescription,\n TimelineDot,\n TimelineHeader,\n TimelineItem,\n type TimelineProps,\n TimelineTime,\n type TimelineTimeProps,\n TimelineTitle\n};\n"],"mappings":"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","names":["activeIndex"]}
@@ -1,11 +1,15 @@
1
1
  const fileUploadEn = {
2
+ dropTitle: "Drag & drop files here",
3
+ dropDescription: ({ maxFiles, maxSize }) => maxFiles > 1 ? `Or click to browse (max ${maxFiles} files, up to ${maxSize} each)` : `Or click to browse (up to ${maxSize})`,
2
4
  dropHint: "Drag files here, or",
3
- choose: "Choose file",
5
+ choose: "Browse files",
4
6
  uploading: "Uploading\u2026",
5
7
  uploadFailed: "Upload failed. Try again.",
6
8
  missingDependency: "File upload isn't available: the @uppy/core and @uppy/xhr-upload packages are missing."
7
9
  };
8
10
  const fileUploadVi = {
11
+ dropTitle: "K\xE9o th\u1EA3 t\u1EC7p v\xE0o \u0111\xE2y",
12
+ dropDescription: ({ maxFiles, maxSize }) => maxFiles > 1 ? `Ho\u1EB7c b\u1EA5m \u0111\u1EC3 ch\u1ECDn (t\u1ED1i \u0111a ${maxFiles} t\u1EC7p, m\u1ED7i t\u1EC7p \u0111\u1EBFn ${maxSize})` : `Ho\u1EB7c b\u1EA5m \u0111\u1EC3 ch\u1ECDn (t\u1ED1i \u0111a ${maxSize})`,
9
13
  dropHint: "K\xE9o t\u1EC7p v\xE0o \u0111\xE2y, ho\u1EB7c",
10
14
  choose: "Ch\u1ECDn t\u1EC7p",
11
15
  uploading: "\u0110ang t\u1EA3i l\xEAn\u2026",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/i18n/namespaces/file-upload.ts"],"sourcesContent":["import type { FileUploadLabels } from \"../../uploader/file-upload-constants.js\";\n\n/**\n * Chữ của `FileUpload` — và của `useFileUpload` khi hook được gọi thẳng mà không truyền `labels`\n * (hai khoá `uploadFailed` / `missingDependency`). Hình dạng + chú thích từng khoá:\n * `FileUploadLabels`.\n */\nexport const fileUploadEn: FileUploadLabels = {\n dropHint: \"Drag files here, or\",\n choose: \"Choose file\",\n uploading: \"Uploading…\",\n uploadFailed: \"Upload failed. Try again.\",\n missingDependency: \"File upload isn't available: the @uppy/core and @uppy/xhr-upload packages are missing.\"\n};\n\nexport const fileUploadVi: FileUploadLabels = {\n dropHint: \"Kéo tệp vào đây, hoặc\",\n choose: \"Chọn tệp\",\n uploading: \"Đang tải lên…\",\n uploadFailed: \"Không tải lên được. Thử lại.\",\n missingDependency: \"Chức năng tải tệp chưa sẵn sàng: thiếu gói @uppy/core và @uppy/xhr-upload.\"\n};\n"],"mappings":"AAOO,MAAM,eAAiC;AAAA,EAC5C,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,cAAc;AAAA,EACd,mBAAmB;AACrB;AAEO,MAAM,eAAiC;AAAA,EAC5C,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,cAAc;AAAA,EACd,mBAAmB;AACrB;","names":[]}
1
+ {"version":3,"sources":["../../../src/i18n/namespaces/file-upload.ts"],"sourcesContent":["import type { FileUploadLabels } from \"../../uploader/file-upload-constants.js\";\n\n/**\n * Chữ của `FileUpload` — và của `useFileUpload` khi hook được gọi thẳng mà không truyền `labels`\n * (hai khoá `uploadFailed` / `missingDependency`). Hình dạng + chú thích từng khoá:\n * `FileUploadLabels`.\n */\nexport const fileUploadEn: FileUploadLabels = {\n dropTitle: \"Drag & drop files here\",\n dropDescription: ({ maxFiles, maxSize }) =>\n maxFiles > 1\n ? `Or click to browse (max ${maxFiles} files, up to ${maxSize} each)`\n : `Or click to browse (up to ${maxSize})`,\n dropHint: \"Drag files here, or\",\n choose: \"Browse files\",\n uploading: \"Uploading…\",\n uploadFailed: \"Upload failed. Try again.\",\n missingDependency: \"File upload isn't available: the @uppy/core and @uppy/xhr-upload packages are missing.\"\n};\n\nexport const fileUploadVi: FileUploadLabels = {\n dropTitle: \"Kéo thả tệp vào đây\",\n dropDescription: ({ maxFiles, maxSize }) =>\n maxFiles > 1\n ? `Hoặc bấm để chọn (tối đa ${maxFiles} tệp, mỗi tệp đến ${maxSize})`\n : `Hoặc bấm để chọn (tối đa ${maxSize})`,\n dropHint: \"Kéo tệp vào đây, hoặc\",\n choose: \"Chọn tệp\",\n uploading: \"Đang tải lên…\",\n uploadFailed: \"Không tải lên được. Thử lại.\",\n missingDependency: \"Chức năng tải tệp chưa sẵn sàng: thiếu gói @uppy/core và @uppy/xhr-upload.\"\n};\n"],"mappings":"AAOO,MAAM,eAAiC;AAAA,EAC5C,WAAW;AAAA,EACX,iBAAiB,CAAC,EAAE,UAAU,QAAQ,MACpC,WAAW,IACP,2BAA2B,QAAQ,iBAAiB,OAAO,WAC3D,6BAA6B,OAAO;AAAA,EAC1C,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,cAAc;AAAA,EACd,mBAAmB;AACrB;AAEO,MAAM,eAAiC;AAAA,EAC5C,WAAW;AAAA,EACX,iBAAiB,CAAC,EAAE,UAAU,QAAQ,MACpC,WAAW,IACP,+DAA4B,QAAQ,8CAAqB,OAAO,MAChE,+DAA4B,OAAO;AAAA,EACzC,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,cAAc;AAAA,EACd,mBAAmB;AACrB;","names":[]}
@@ -4,6 +4,11 @@ const imageUploadFieldEn = {
4
4
  remove: "Remove",
5
5
  hint: "Drag an image here or click Upload.",
6
6
  dropPrompt: "Drop image here",
7
+ cropTitle: "Crop your picture",
8
+ cropDescription: "Drag to move it. Scroll, pinch or use the slider to zoom.",
9
+ cropZoom: "Zoom",
10
+ cropSave: "Save",
11
+ cropCancel: "Cancel",
7
12
  uploadFailed: "Upload failed. Try again.",
8
13
  missingDependency: "File upload isn't available: the @uppy/core and @uppy/xhr-upload packages are missing."
9
14
  };
@@ -13,6 +18,11 @@ const imageUploadFieldVi = {
13
18
  remove: "G\u1EE1",
14
19
  hint: "K\xE9o \u1EA3nh v\xE0o \u0111\xE2y ho\u1EB7c b\u1EA5m T\u1EA3i l\xEAn.",
15
20
  dropPrompt: "Th\u1EA3 \u1EA3nh v\xE0o \u0111\xE2y",
21
+ cropTitle: "C\u1EAFt \u1EA3nh",
22
+ cropDescription: "K\xE9o \u0111\u1EC3 di chuy\u1EC3n. Cu\u1ED9n, ch\u1EE5m hai ng\xF3n ho\u1EB7c d\xF9ng thanh tr\u01B0\u1EE3t \u0111\u1EC3 ph\xF3ng to.",
23
+ cropZoom: "Ph\xF3ng to",
24
+ cropSave: "L\u01B0u",
25
+ cropCancel: "Hu\u1EF7",
16
26
  uploadFailed: "Kh\xF4ng t\u1EA3i l\xEAn \u0111\u01B0\u1EE3c. Th\u1EED l\u1EA1i.",
17
27
  missingDependency: "Ch\u1EE9c n\u0103ng t\u1EA3i t\u1EC7p ch\u01B0a s\u1EB5n s\xE0ng: thi\u1EBFu g\xF3i @uppy/core v\xE0 @uppy/xhr-upload."
18
28
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/i18n/namespaces/image-upload-field.ts"],"sourcesContent":["import type { ImageUploadFieldLabels } from \"../../uploader/file-upload-constants.js\";\n\n/**\n * Chữ của `ImageUploadField`. Hình dạng + chú thích từng khoá: `ImageUploadFieldLabels`.\n *\n * ⚠ `hint` nói CẢ việc kéo-thả được — nó là chỗ duy nhất người dùng học ra điều đó (xem\n * `ImageUploadFieldLabels`). Và nó nhắc đúng chữ của nút `choose`, nên dịch hai khoá cùng nhau.\n */\nexport const imageUploadFieldEn: ImageUploadFieldLabels = {\n choose: \"Upload\",\n uploading: \"Uploading…\",\n remove: \"Remove\",\n hint: \"Drag an image here or click Upload.\",\n dropPrompt: \"Drop image here\",\n uploadFailed: \"Upload failed. Try again.\",\n missingDependency: \"File upload isn't available: the @uppy/core and @uppy/xhr-upload packages are missing.\"\n};\n\nexport const imageUploadFieldVi: ImageUploadFieldLabels = {\n choose: \"Tải lên\",\n uploading: \"Đang tải lên…\",\n remove: \"Gỡ\",\n hint: \"Kéo ảnh vào đây hoặc bấm Tải lên.\",\n dropPrompt: \"Thả ảnh vào đây\",\n uploadFailed: \"Không tải lên được. Thử lại.\",\n missingDependency: \"Chức năng tải tệp chưa sẵn sàng: thiếu gói @uppy/core và @uppy/xhr-upload.\"\n};\n"],"mappings":"AAQO,MAAM,qBAA6C;AAAA,EACxD,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,mBAAmB;AACrB;AAEO,MAAM,qBAA6C;AAAA,EACxD,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,mBAAmB;AACrB;","names":[]}
1
+ {"version":3,"sources":["../../../src/i18n/namespaces/image-upload-field.ts"],"sourcesContent":["import type { ImageUploadFieldLabels } from \"../../uploader/file-upload-constants.js\";\n\n/**\n * Chữ của `ImageUploadField`. Hình dạng + chú thích từng khoá: `ImageUploadFieldLabels`.\n *\n * ⚠ `hint` nói CẢ việc kéo-thả được — nó là chỗ duy nhất người dùng học ra điều đó (xem\n * `ImageUploadFieldLabels`). Và nó nhắc đúng chữ của nút `choose`, nên dịch hai khoá cùng nhau.\n */\nexport const imageUploadFieldEn: ImageUploadFieldLabels = {\n choose: \"Upload\",\n uploading: \"Uploading…\",\n remove: \"Remove\",\n hint: \"Drag an image here or click Upload.\",\n dropPrompt: \"Drop image here\",\n cropTitle: \"Crop your picture\",\n cropDescription: \"Drag to move it. Scroll, pinch or use the slider to zoom.\",\n cropZoom: \"Zoom\",\n cropSave: \"Save\",\n cropCancel: \"Cancel\",\n uploadFailed: \"Upload failed. Try again.\",\n missingDependency: \"File upload isn't available: the @uppy/core and @uppy/xhr-upload packages are missing.\"\n};\n\nexport const imageUploadFieldVi: ImageUploadFieldLabels = {\n choose: \"Tải lên\",\n uploading: \"Đang tải lên…\",\n remove: \"Gỡ\",\n hint: \"Kéo ảnh vào đây hoặc bấm Tải lên.\",\n dropPrompt: \"Thả ảnh vào đây\",\n cropTitle: \"Cắt ảnh\",\n cropDescription: \"Kéo để di chuyển. Cuộn, chụm hai ngón hoặc dùng thanh trượt để phóng to.\",\n cropZoom: \"Phóng to\",\n cropSave: \"Lưu\",\n cropCancel: \"Huỷ\",\n uploadFailed: \"Không tải lên được. Thử lại.\",\n missingDependency: \"Chức năng tải tệp chưa sẵn sàng: thiếu gói @uppy/core và @uppy/xhr-upload.\"\n};\n"],"mappings":"AAQO,MAAM,qBAA6C;AAAA,EACxD,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,mBAAmB;AACrB;AAEO,MAAM,qBAA6C;AAAA,EACxD,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,mBAAmB;AACrB;","names":[]}
package/dist/index.d.ts CHANGED
@@ -54,7 +54,7 @@ export { Calendar, CalendarDayButton, CalendarDayButtonProps, CalendarProps } fr
54
54
  export { Card, CardAction, CardActionProps, CardContent, CardContentProps, CardDescription, CardDescriptionProps, CardFooter, CardFooterProps, CardHeader, CardHeaderProps, CardProps, CardTitle, CardTitleProps, cardVariants } from './components/ui/card.js';
55
55
  export { Carousel, CarouselApi, CarouselContent, CarouselContentProps, CarouselContextProps, CarouselItem, CarouselItemProps, CarouselNext, CarouselNextProps, CarouselOptions, CarouselOwnProps, CarouselPlugin, CarouselPrevious, CarouselPreviousProps, CarouselProps, useCarousel } from './components/ui/carousel.js';
56
56
  export { Checkbox, CheckboxProps } from './components/ui/checkbox.js';
57
- export { CircularProgress, CircularProgressCombined, CircularProgressIndicator, CircularProgressProps, CircularProgressRange, CircularProgressTrack, CircularProgressValueText, CircularProgressValueTextProps } from './components/ui/circular-progress.js';
57
+ export { CircularProgress, CircularProgressCombined, CircularProgressIndicator, CircularProgressProps, CircularProgressRange, CircularProgressTone, CircularProgressTrack, CircularProgressValueText, CircularProgressValueTextProps } from './components/ui/circular-progress.js';
58
58
  export { Collapsible, CollapsibleContent, CollapsibleContentProps, CollapsibleProps, CollapsibleTrigger, CollapsibleTriggerProps } from './components/ui/collapsible.js';
59
59
  export { Command, CommandDialog, CommandDialogProps, CommandEmpty, CommandEmptyProps, CommandGroup, CommandGroupProps, CommandInput, CommandInputProps, CommandItem, CommandItemProps, CommandList, CommandListProps, CommandProps, CommandSeparator, CommandSeparatorProps, CommandShortcut, CommandShortcutProps } from './components/ui/command.js';
60
60
  export { ContextMenu, ContextMenuCheckboxItem, ContextMenuCheckboxItemProps, ContextMenuContent, ContextMenuContentProps, ContextMenuGroup, ContextMenuGroupProps, ContextMenuItem, ContextMenuItemProps, ContextMenuItemVariant, ContextMenuLabel, ContextMenuLabelProps, ContextMenuPortal, ContextMenuPortalProps, ContextMenuProps, ContextMenuRadioGroup, ContextMenuRadioGroupProps, ContextMenuRadioItem, ContextMenuRadioItemProps, ContextMenuSeparator, ContextMenuSeparatorProps, ContextMenuShortcut, ContextMenuShortcutProps, ContextMenuSub, ContextMenuSubContent, ContextMenuSubContentProps, ContextMenuSubProps, ContextMenuSubTrigger, ContextMenuSubTriggerProps, ContextMenuTrigger, ContextMenuTriggerProps } from './components/ui/context-menu.js';
@@ -117,7 +117,7 @@ export { DEFAULT_TOUR_LABELS, TourLabels } from './components/ui/tour-constants.
117
117
  export { ComitorLanguage, DEFAULT_COMITOR_LOCALE, getComitorLanguage } from './i18n/locale.js';
118
118
  export { ComitorLocaleProvider, ComitorLocaleProviderProps, useComitorLocale, useComitorMessages } from './i18n/locale-provider.js';
119
119
  export { COMITOR_MESSAGES, ComitorMessages, ComitorMessagesOverride, comitorMessagesEn, comitorMessagesVi, getComitorMessages, mergeComitorMessages } from './i18n/messages.js';
120
- export { AVATAR_TONE_NAMES, AvatarToneColors, AvatarToneName, getAvatarToneIndex } from './lib/avatar-tone.js';
120
+ export { AVATAR_TONE_NAMES, AvatarToneColors, AvatarToneName, LEGACY_AVATAR_TONE_ALIASES, getAvatarToneIndex } from './lib/avatar-tone.js';
121
121
  export { cn } from './lib/cn.js';
122
122
  export { ThemeMode, accent, accentForeground, accentInk, ash, brandNeutral, chart, getAvatarToneColors, gold, highContrast, layout, semantic, status, typography } from './tokens.js';
123
123
  export { DateRange } from 'react-day-picker';
package/dist/index.js CHANGED
@@ -141,7 +141,7 @@ import {
141
141
  getComitorMessages,
142
142
  mergeComitorMessages
143
143
  } from "./i18n/messages.js";
144
- import { AVATAR_TONE_NAMES } from "./lib/avatar-tone.js";
144
+ import { AVATAR_TONE_NAMES, LEGACY_AVATAR_TONE_ALIASES } from "./lib/avatar-tone.js";
145
145
  export * from "./lib/cn.js";
146
146
  export * from "./tokens.js";
147
147
  export {
@@ -150,6 +150,7 @@ export {
150
150
  ComitorLocaleProvider,
151
151
  DEFAULT_COMITOR_LOCALE,
152
152
  IconAvatar,
153
+ LEGACY_AVATAR_TONE_ALIASES,
153
154
  LetterAvatar,
154
155
  MASK_PATTERNS,
155
156
  applyCurrencyMask,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Entry chính \"@comitor/ui\" — Tier 1 (primitive) + Tier 2 (composite) + lib + token.\n *\n * Quy tắc của entry này: **chỉ được phụ thuộc peer BẮT BUỘC** (react, react-dom,\n * lucide-react, tailwindcss). Không `next/*`, không `next-themes`, không `recharts`,\n * không `react-hook-form` — đó đều là peer OPTIONAL. Vì `tsup` chạy `bundle: false`\n * (ESM per-file), chỉ cần barrel này re-export một file có import optional peer là\n * mọi app import \"@comitor/ui\" đều buộc phải cài gói đó, nếu không bundler gãy ở\n * bước resolve. Vì vậy 3 nhánh dưới đây nằm ở entry riêng:\n *\n * - Tier 3 shell (next/*, next-themes) → \"@comitor/ui/shell\"\n * - chart (recharts) → \"@comitor/ui/chart\"\n * - binding react-hook-form → \"@comitor/ui/form\"\n *\n * ⚠ Hai file trong `components/ui/` CỐ Ý không có mặt ở đây:\n * - `form.tsx` → xuất qua \"@comitor/ui/form\" (react-hook-form).\n * - `sonner.tsx` → xuất qua \"@comitor/ui/shell\" dưới tên `SonnerToaster`\n * (nó gọi `useTheme()` của next-themes). Bản dùng hằng ngày là composite\n * `Toaster`/`toast` ở Tier 2 — không phụ thuộc next-themes, có sẵn ở entry này.\n *\n * ⚠ Mọi import nội bộ phải giữ đuôi `.js`: output là ESM per-file, Node ESM thuần\n * không tự suy ra phần mở rộng (bundler thì tha, Node thì không).\n */\n\n/*\n * ⚠ Các file `*-constants` bên dưới — ở CẢ Tier 2 lẫn Tier 1 (từ `2.1.0`: `action-bar`, `cropper`,\n * `kanban`, `key-value`, `mask-input`, `phone-input`, `stepper`, `time-picker`, `tour`) — KHÔNG phải\n * chỗ để gom code cho gọn — chúng là RANH GIỚI SERVER/CLIENT. Component chủ (`command-palette.tsx`,\n * `data-table.tsx`, `kanban.tsx`…) đều mở đầu bằng `\"use client\"`,\n * mà mọi export của một module client đến tay Server Component dưới dạng client-reference proxy:\n * đọc ra `undefined`, KHÔNG throw, KHÔNG warning, trong khi TypeScript vẫn khai kiểu thật. Nhãn mặc\n * định / preset / sentinel là hằng dữ liệu thuần nên phải nằm ngoài ranh giới đó, và barrel này\n * phải xuất chúng THẲNG — re-export vòng qua file client là vô hiệu.\n * Cùng lý do với `chart/chart-colors.ts`; chi tiết ghi ở đầu file đó.\n * `scripts/verify-directives.mjs` canh không cho hằng thuần lọt ngược vào file client.\n */\n\n// ─── Tier 2 — composite (pattern lặp lại ở mọi app Comitor) ───\nexport * from \"./components/composite/avatar-group.js\";\nexport * from \"./components/composite/combobox.js\";\nexport * from \"./components/composite/command-palette.js\";\nexport * from \"./components/composite/command-palette-constants.js\";\nexport * from \"./components/composite/confirm-dialog.js\";\nexport * from \"./components/composite/copy-button.js\";\nexport * from \"./components/composite/data-table.js\";\nexport * from \"./components/composite/data-table-constants.js\";\nexport * from \"./components/composite/date-picker.js\";\nexport * from \"./components/composite/date-picker-constants.js\";\nexport * from \"./components/composite/empty-state.js\";\nexport * from \"./components/composite/filter-chips.js\";\nexport * from \"./components/composite/form-field.js\";\nexport * from \"./components/composite/form-field-constants.js\";\nexport * from \"./components/composite/form-section.js\";\nexport * from \"./components/composite/gantt.js\";\nexport * from \"./components/composite/gantt-constants.js\";\nexport { IconAvatar, type IconAvatarIcon, type IconAvatarProps } from \"./components/composite/icon-avatar.js\";\nexport * from \"./components/composite/inline-edit.js\";\nexport * from \"./components/composite/key-sequence.js\";\nexport * from \"./components/composite/keyboard-hint.js\";\nexport * from \"./components/composite/keyboard-shortcuts-dialog.js\";\nexport type { LetterAvatarProps, LetterAvatarSize } from \"./components/composite/letter-avatar.js\";\n// Xuất CHỌN LỌC (không `export *`): file này còn `AVATAR_TONES` nhập từ `lib/avatar-tone.js`\n// và các helper nội bộ khác. Liệt kê tường minh thì thêm helper riêng tư vào file KHÔNG tự động\n// biến nó thành API công khai — hàng rào mà `export *` không dựng được.\nexport { getAvatarToneClasses, LetterAvatar } from \"./components/composite/letter-avatar.js\";\nexport * from \"./components/composite/page-container.js\";\nexport * from \"./components/composite/page-header.js\";\nexport * from \"./components/composite/relative-time.js\";\nexport * from \"./components/composite/route-tabs.js\";\nexport * from \"./components/composite/search-filter-bar.js\";\nexport * from \"./components/composite/search-filter-bar-constants.js\";\nexport * from \"./components/composite/stat-card.js\";\nexport * from \"./components/composite/status-config.js\";\nexport * from \"./components/composite/status-pill.js\";\nexport * from \"./components/composite/table-pagination.js\";\nexport * from \"./components/composite/table-pagination-constants.js\";\n// Xuất CHỌN LỌC vì cùng lý do với `letter-avatar` ở trên: `text-utils.ts` là chỗ dễ bị thêm\n// helper nội bộ nhất của Tier 2 (0.1.0 từng rò `hashString`/`pickByString` ra API công khai\n// đúng theo đường này). Năm hàm dưới đây là hợp đồng, phần còn lại của file thì không.\nexport {\n getVietnameseInitials,\n matchesSearch,\n normalizeVi,\n removeVietnameseTones,\n truncate\n} from \"./components/composite/text-utils.js\";\nexport * from \"./components/composite/toast.js\";\nexport * from \"./components/composite/tree-view.js\";\nexport * from \"./components/composite/tree-view-constants.js\";\nexport * from \"./components/composite/use-key-sequences.js\";\n// ─── Tier 1 — primitive (shadcn/ui \"new-york\", đã custom theo brand Comitor) ───\nexport * from \"./components/ui/accordion.js\";\nexport * from \"./components/ui/action-bar.js\";\nexport * from \"./components/ui/action-bar-constants.js\";\nexport * from \"./components/ui/alert.js\";\nexport * from \"./components/ui/alert-dialog.js\";\nexport * from \"./components/ui/aspect-ratio.js\";\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/badge-overflow.js\";\nexport * from \"./components/ui/banner.js\";\nexport * from \"./components/ui/breadcrumb.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/button-group.js\";\nexport * from \"./components/ui/calendar.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/carousel.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/circular-progress.js\";\nexport * from \"./components/ui/collapsible.js\";\nexport * from \"./components/ui/command.js\";\nexport * from \"./components/ui/context-menu.js\";\nexport * from \"./components/ui/cropper.js\";\nexport * from \"./components/ui/cropper-constants.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/drawer.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/empty.js\";\nexport * from \"./components/ui/field.js\";\nexport * from \"./components/ui/hover-card.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/input-group.js\";\nexport * from \"./components/ui/input-otp.js\";\nexport * from \"./components/ui/item.js\";\nexport * from \"./components/ui/kanban.js\";\nexport * from \"./components/ui/kanban-constants.js\";\nexport * from \"./components/ui/kbd.js\";\nexport * from \"./components/ui/key-value.js\";\nexport * from \"./components/ui/key-value-constants.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/mask-input.js\";\n/*\n * Xuất CHỌN LỌC: `mask-input-constants.ts` giữ cả bộ máy thuần của MaskInput (tách khỏi file\n * \"use client\" để hằng `MASK_PATTERNS` không thành proxy ở Server Component), gồm những helper nội\n * bộ như `DEFAULT_CURRENCY` / `REGEX_CACHE`. Chỉ đúng các tên thượng nguồn Dice UI xuất là API.\n */\nexport {\n applyCurrencyMask,\n applyMask,\n applyPercentageMask,\n fromUnmaskedIndex,\n getUnmaskedValue,\n MASK_PATTERNS,\n type MaskPattern,\n toUnmaskedIndex\n} from \"./components/ui/mask-input-constants.js\";\nexport * from \"./components/ui/mention.js\";\nexport * from \"./components/ui/menubar.js\";\nexport * from \"./components/ui/navigation-menu.js\";\nexport * from \"./components/ui/pagination.js\";\nexport * from \"./components/ui/phone-input.js\";\nexport * from \"./components/ui/phone-input-constants.js\";\nexport * from \"./components/ui/popover.js\";\nexport * from \"./components/ui/progress.js\";\nexport * from \"./components/ui/radio-group.js\";\nexport * from \"./components/ui/rating.js\";\nexport * from \"./components/ui/resizable.js\";\nexport * from \"./components/ui/responsive-dialog.js\";\nexport * from \"./components/ui/scroll-area.js\";\nexport * from \"./components/ui/scroll-spy.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/skeleton.js\";\nexport * from \"./components/ui/slider.js\";\nexport * from \"./components/ui/spinner.js\";\nexport * from \"./components/ui/stepper.js\";\nexport * from \"./components/ui/stepper-constants.js\";\nexport * from \"./components/ui/switch.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\n/* Bảng lớp của Tabs xuất THẲNG từ module thuần — `RouteTabs` và `Tabs` dùng chung nó, và hằng\n dữ liệu đi vòng qua file \"use client\" sẽ thành proxy ở Server Component (CHANGELOG 0.2.2 §2). */\nexport * from \"./components/ui/tabs-variants.js\";\nexport * from \"./components/ui/tags-input.js\";\nexport * from \"./components/ui/textarea.js\";\nexport * from \"./components/ui/time-picker.js\";\nexport * from \"./components/ui/time-picker-constants.js\";\nexport * from \"./components/ui/timeline.js\";\nexport * from \"./components/ui/toggle.js\";\nexport * from \"./components/ui/toggle-group.js\";\nexport * from \"./components/ui/tooltip.js\";\nexport * from \"./components/ui/tour.js\";\nexport * from \"./components/ui/tour-constants.js\";\n// ─── Ngôn ngữ (từ 3.0.0) — một provider cho CẢ gói, mặc định tiếng Anh ───\n/*\n * Xuất CHỌN LỌC: `locale-provider.tsx` còn `useComitorLabels` — hook nội bộ các component dùng để\n * trộn `labels` lên bảng chữ, không phải API. Bảng chữ và hàm thuần xuất THẲNG từ module thuần\n * (`messages.ts`, `locale.ts`) chứ không vòng qua file \"use client\" — cùng lý do với các file\n * `*-constants` bên dưới.\n */\nexport { type ComitorLanguage, DEFAULT_COMITOR_LOCALE, getComitorLanguage } from \"./i18n/locale.js\";\nexport {\n ComitorLocaleProvider,\n type ComitorLocaleProviderProps,\n useComitorLocale,\n useComitorMessages\n} from \"./i18n/locale-provider.js\";\nexport {\n COMITOR_MESSAGES,\n type ComitorMessages,\n type ComitorMessagesOverride,\n comitorMessagesEn,\n comitorMessagesVi,\n getComitorMessages,\n mergeComitorMessages\n} from \"./i18n/messages.js\";\n/*\n * `IconAvatar` — em ruột của `LetterAvatar`, dùng chung bảng tone. Xuất kèm `AVATAR_TONE_NAMES`\n * và kiểu `AvatarToneName` vì prop `tone` chỉ có nghĩa khi chỗ gọi liệt kê được tập chọn (màn\n * quản trị cần dựng một `<Select>` từ đúng tập đó).\n *\n * ⚠ KHÔNG xuất `AVATAR_TONES` lẫn `getAvatarToneIndex` ra entry này: bảng thô và hàm băm vẫn là\n * nội bộ, đúng như `lib/avatar-tone.ts` dặn — xuất chúng là mời app dựng lại phép chọn tone của\n * riêng nó, tức cùng một tên ra hai màu.\n */\nexport { AVATAR_TONE_NAMES, type AvatarToneName } from \"./lib/avatar-tone.js\";\n\n// ─── lib + token ───\nexport * from \"./lib/cn.js\";\nexport * from \"./tokens.js\";\n"],"mappings":"AAsCA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,kBAA6D;AACtE,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAKd,SAAS,sBAAsB,oBAAoB;AACnD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAMd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAQd,SAA+B,wBAAwB,0BAA0B;AACjF;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,SAAS,yBAA8C;AAGvD,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Entry chính \"@comitor/ui\" — Tier 1 (primitive) + Tier 2 (composite) + lib + token.\n *\n * Quy tắc của entry này: **chỉ được phụ thuộc peer BẮT BUỘC** (react, react-dom,\n * lucide-react, tailwindcss). Không `next/*`, không `next-themes`, không `recharts`,\n * không `react-hook-form` — đó đều là peer OPTIONAL. Vì `tsup` chạy `bundle: false`\n * (ESM per-file), chỉ cần barrel này re-export một file có import optional peer là\n * mọi app import \"@comitor/ui\" đều buộc phải cài gói đó, nếu không bundler gãy ở\n * bước resolve. Vì vậy 3 nhánh dưới đây nằm ở entry riêng:\n *\n * - Tier 3 shell (next/*, next-themes) → \"@comitor/ui/shell\"\n * - chart (recharts) → \"@comitor/ui/chart\"\n * - binding react-hook-form → \"@comitor/ui/form\"\n *\n * ⚠ Hai file trong `components/ui/` CỐ Ý không có mặt ở đây:\n * - `form.tsx` → xuất qua \"@comitor/ui/form\" (react-hook-form).\n * - `sonner.tsx` → xuất qua \"@comitor/ui/shell\" dưới tên `SonnerToaster`\n * (nó gọi `useTheme()` của next-themes). Bản dùng hằng ngày là composite\n * `Toaster`/`toast` ở Tier 2 — không phụ thuộc next-themes, có sẵn ở entry này.\n *\n * ⚠ Mọi import nội bộ phải giữ đuôi `.js`: output là ESM per-file, Node ESM thuần\n * không tự suy ra phần mở rộng (bundler thì tha, Node thì không).\n */\n\n/*\n * ⚠ Các file `*-constants` bên dưới — ở CẢ Tier 2 lẫn Tier 1 (từ `2.1.0`: `action-bar`, `cropper`,\n * `kanban`, `key-value`, `mask-input`, `phone-input`, `stepper`, `time-picker`, `tour`) — KHÔNG phải\n * chỗ để gom code cho gọn — chúng là RANH GIỚI SERVER/CLIENT. Component chủ (`command-palette.tsx`,\n * `data-table.tsx`, `kanban.tsx`…) đều mở đầu bằng `\"use client\"`,\n * mà mọi export của một module client đến tay Server Component dưới dạng client-reference proxy:\n * đọc ra `undefined`, KHÔNG throw, KHÔNG warning, trong khi TypeScript vẫn khai kiểu thật. Nhãn mặc\n * định / preset / sentinel là hằng dữ liệu thuần nên phải nằm ngoài ranh giới đó, và barrel này\n * phải xuất chúng THẲNG — re-export vòng qua file client là vô hiệu.\n * Cùng lý do với `chart/chart-colors.ts`; chi tiết ghi ở đầu file đó.\n * `scripts/verify-directives.mjs` canh không cho hằng thuần lọt ngược vào file client.\n */\n\n// ─── Tier 2 — composite (pattern lặp lại ở mọi app Comitor) ───\nexport * from \"./components/composite/avatar-group.js\";\nexport * from \"./components/composite/combobox.js\";\nexport * from \"./components/composite/command-palette.js\";\nexport * from \"./components/composite/command-palette-constants.js\";\nexport * from \"./components/composite/confirm-dialog.js\";\nexport * from \"./components/composite/copy-button.js\";\nexport * from \"./components/composite/data-table.js\";\nexport * from \"./components/composite/data-table-constants.js\";\nexport * from \"./components/composite/date-picker.js\";\nexport * from \"./components/composite/date-picker-constants.js\";\nexport * from \"./components/composite/empty-state.js\";\nexport * from \"./components/composite/filter-chips.js\";\nexport * from \"./components/composite/form-field.js\";\nexport * from \"./components/composite/form-field-constants.js\";\nexport * from \"./components/composite/form-section.js\";\nexport * from \"./components/composite/gantt.js\";\nexport * from \"./components/composite/gantt-constants.js\";\nexport { IconAvatar, type IconAvatarIcon, type IconAvatarProps } from \"./components/composite/icon-avatar.js\";\nexport * from \"./components/composite/inline-edit.js\";\nexport * from \"./components/composite/key-sequence.js\";\nexport * from \"./components/composite/keyboard-hint.js\";\nexport * from \"./components/composite/keyboard-shortcuts-dialog.js\";\nexport type { LetterAvatarProps, LetterAvatarSize } from \"./components/composite/letter-avatar.js\";\n// Xuất CHỌN LỌC (không `export *`): file này còn `AVATAR_TONES` nhập từ `lib/avatar-tone.js`\n// và các helper nội bộ khác. Liệt kê tường minh thì thêm helper riêng tư vào file KHÔNG tự động\n// biến nó thành API công khai — hàng rào mà `export *` không dựng được.\nexport { getAvatarToneClasses, LetterAvatar } from \"./components/composite/letter-avatar.js\";\nexport * from \"./components/composite/page-container.js\";\nexport * from \"./components/composite/page-header.js\";\nexport * from \"./components/composite/relative-time.js\";\nexport * from \"./components/composite/route-tabs.js\";\nexport * from \"./components/composite/search-filter-bar.js\";\nexport * from \"./components/composite/search-filter-bar-constants.js\";\nexport * from \"./components/composite/stat-card.js\";\nexport * from \"./components/composite/status-config.js\";\nexport * from \"./components/composite/status-pill.js\";\nexport * from \"./components/composite/table-pagination.js\";\nexport * from \"./components/composite/table-pagination-constants.js\";\n// Xuất CHỌN LỌC vì cùng lý do với `letter-avatar` ở trên: `text-utils.ts` là chỗ dễ bị thêm\n// helper nội bộ nhất của Tier 2 (0.1.0 từng rò `hashString`/`pickByString` ra API công khai\n// đúng theo đường này). Năm hàm dưới đây là hợp đồng, phần còn lại của file thì không.\nexport {\n getVietnameseInitials,\n matchesSearch,\n normalizeVi,\n removeVietnameseTones,\n truncate\n} from \"./components/composite/text-utils.js\";\nexport * from \"./components/composite/toast.js\";\nexport * from \"./components/composite/tree-view.js\";\nexport * from \"./components/composite/tree-view-constants.js\";\nexport * from \"./components/composite/use-key-sequences.js\";\n// ─── Tier 1 — primitive (shadcn/ui \"new-york\", đã custom theo brand Comitor) ───\nexport * from \"./components/ui/accordion.js\";\nexport * from \"./components/ui/action-bar.js\";\nexport * from \"./components/ui/action-bar-constants.js\";\nexport * from \"./components/ui/alert.js\";\nexport * from \"./components/ui/alert-dialog.js\";\nexport * from \"./components/ui/aspect-ratio.js\";\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/badge-overflow.js\";\nexport * from \"./components/ui/banner.js\";\nexport * from \"./components/ui/breadcrumb.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/button-group.js\";\nexport * from \"./components/ui/calendar.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/carousel.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/circular-progress.js\";\nexport * from \"./components/ui/collapsible.js\";\nexport * from \"./components/ui/command.js\";\nexport * from \"./components/ui/context-menu.js\";\nexport * from \"./components/ui/cropper.js\";\nexport * from \"./components/ui/cropper-constants.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/drawer.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/empty.js\";\nexport * from \"./components/ui/field.js\";\nexport * from \"./components/ui/hover-card.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/input-group.js\";\nexport * from \"./components/ui/input-otp.js\";\nexport * from \"./components/ui/item.js\";\nexport * from \"./components/ui/kanban.js\";\nexport * from \"./components/ui/kanban-constants.js\";\nexport * from \"./components/ui/kbd.js\";\nexport * from \"./components/ui/key-value.js\";\nexport * from \"./components/ui/key-value-constants.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/mask-input.js\";\n/*\n * Xuất CHỌN LỌC: `mask-input-constants.ts` giữ cả bộ máy thuần của MaskInput (tách khỏi file\n * \"use client\" để hằng `MASK_PATTERNS` không thành proxy ở Server Component), gồm những helper nội\n * bộ như `DEFAULT_CURRENCY` / `REGEX_CACHE`. Chỉ đúng các tên thượng nguồn Dice UI xuất là API.\n */\nexport {\n applyCurrencyMask,\n applyMask,\n applyPercentageMask,\n fromUnmaskedIndex,\n getUnmaskedValue,\n MASK_PATTERNS,\n type MaskPattern,\n toUnmaskedIndex\n} from \"./components/ui/mask-input-constants.js\";\nexport * from \"./components/ui/mention.js\";\nexport * from \"./components/ui/menubar.js\";\nexport * from \"./components/ui/navigation-menu.js\";\nexport * from \"./components/ui/pagination.js\";\nexport * from \"./components/ui/phone-input.js\";\nexport * from \"./components/ui/phone-input-constants.js\";\nexport * from \"./components/ui/popover.js\";\nexport * from \"./components/ui/progress.js\";\nexport * from \"./components/ui/radio-group.js\";\nexport * from \"./components/ui/rating.js\";\nexport * from \"./components/ui/resizable.js\";\nexport * from \"./components/ui/responsive-dialog.js\";\nexport * from \"./components/ui/scroll-area.js\";\nexport * from \"./components/ui/scroll-spy.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/skeleton.js\";\nexport * from \"./components/ui/slider.js\";\nexport * from \"./components/ui/spinner.js\";\nexport * from \"./components/ui/stepper.js\";\nexport * from \"./components/ui/stepper-constants.js\";\nexport * from \"./components/ui/switch.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\n/* Bảng lớp của Tabs xuất THẲNG từ module thuần — `RouteTabs` và `Tabs` dùng chung nó, và hằng\n dữ liệu đi vòng qua file \"use client\" sẽ thành proxy ở Server Component (CHANGELOG 0.2.2 §2). */\nexport * from \"./components/ui/tabs-variants.js\";\nexport * from \"./components/ui/tags-input.js\";\nexport * from \"./components/ui/textarea.js\";\nexport * from \"./components/ui/time-picker.js\";\nexport * from \"./components/ui/time-picker-constants.js\";\nexport * from \"./components/ui/timeline.js\";\nexport * from \"./components/ui/toggle.js\";\nexport * from \"./components/ui/toggle-group.js\";\nexport * from \"./components/ui/tooltip.js\";\nexport * from \"./components/ui/tour.js\";\nexport * from \"./components/ui/tour-constants.js\";\n// ─── Ngôn ngữ (từ 3.0.0) — một provider cho CẢ gói, mặc định tiếng Anh ───\n/*\n * Xuất CHỌN LỌC: `locale-provider.tsx` còn `useComitorLabels` — hook nội bộ các component dùng để\n * trộn `labels` lên bảng chữ, không phải API. Bảng chữ và hàm thuần xuất THẲNG từ module thuần\n * (`messages.ts`, `locale.ts`) chứ không vòng qua file \"use client\" — cùng lý do với các file\n * `*-constants` bên dưới.\n */\nexport { type ComitorLanguage, DEFAULT_COMITOR_LOCALE, getComitorLanguage } from \"./i18n/locale.js\";\nexport {\n ComitorLocaleProvider,\n type ComitorLocaleProviderProps,\n useComitorLocale,\n useComitorMessages\n} from \"./i18n/locale-provider.js\";\nexport {\n COMITOR_MESSAGES,\n type ComitorMessages,\n type ComitorMessagesOverride,\n comitorMessagesEn,\n comitorMessagesVi,\n getComitorMessages,\n mergeComitorMessages\n} from \"./i18n/messages.js\";\n/*\n * `IconAvatar` — em ruột của `LetterAvatar`, dùng chung bảng tone. Xuất kèm `AVATAR_TONE_NAMES`\n * và kiểu `AvatarToneName` vì prop `tone` chỉ có nghĩa khi chỗ gọi liệt kê được tập chọn (màn\n * quản trị cần dựng một `<Select>` từ đúng tập đó). `LEGACY_AVATAR_TONE_ALIASES` (tên cũ → tên\n * mới, 3.10.0) đi cùng vì cùng lý do: màn ấy đọc `apps.icon_color` cũ phải ra đúng mục của\n * `<Select>` — CHANGELOG 3.10.0 đã ghi nó là tên mới nhưng tới 3.12.1 mới thật sự xuất ra.\n *\n * ⚠ KHÔNG xuất `AVATAR_TONES` lẫn `getAvatarToneIndex` ra entry này: bảng thô và hàm băm vẫn là\n * nội bộ, đúng như `lib/avatar-tone.ts` dặn — xuất chúng là mời app dựng lại phép chọn tone của\n * riêng nó, tức cùng một tên ra hai màu.\n */\nexport { AVATAR_TONE_NAMES, type AvatarToneName, LEGACY_AVATAR_TONE_ALIASES } from \"./lib/avatar-tone.js\";\n\n// ─── lib + token ───\nexport * from \"./lib/cn.js\";\nexport * from \"./tokens.js\";\n"],"mappings":"AAsCA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,kBAA6D;AACtE,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAKd,SAAS,sBAAsB,oBAAoB;AACnD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAMd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAQd,SAA+B,wBAAwB,0BAA0B;AACjF;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAYP,SAAS,mBAAwC,kCAAkC;AAGnF,cAAc;AACd,cAAc;","names":[]}
@@ -124,6 +124,10 @@ type AvatarToneName = (typeof AVATAR_TONE_NAMES)[number];
124
124
  * Bí danh thay vì giữ tên cũ cho màu mới: tên là hợp đồng (cột `apps.icon_color` của Account), và
125
125
  * một tên nói dối về màu ("ash" mà tím) còn tệ hơn một lần đổi tên có lối lùi. Bí danh đi theo
126
126
  * cùng VỊ TRÍ, nên hàng database còn ghi tên cũ ra đúng màu mà phép băm cũng sẽ cho ở chỗ đó.
127
+ *
128
+ * Xuất ở `@comitor/ui` từ 3.12.1 — app ánh xạ tên cũ đọc từ database bằng chính bảng này thay vì
129
+ * chép một bản. ⚠ `Record` trần: khoá đến từ dữ liệu ngoài thì tra qua `Object.hasOwn` —
130
+ * `LEGACY_AVATAR_TONE_ALIASES["constructor"]` ra một hàm, không ra `undefined`.
127
131
  */
128
132
  declare const LEGACY_AVATAR_TONE_ALIASES: Readonly<Record<string, AvatarToneName>>;
129
133
  declare const AVATAR_TONES: AvatarToneTable<AvatarTone>;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/avatar-tone.ts"],"sourcesContent":["/**\n * NGUỒN SỰ THẬT DUY NHẤT của màu avatar chữ cái — cả gói chỉ có bảng này.\n *\n * File nội bộ (không nằm trong barrel nào): app không import trực tiếp được, mà đi qua đúng\n * hai cửa tuỳ theo chỗ vẽ:\n * • có Tailwind (DOM) → `getAvatarToneClasses(name)` ở `composite/letter-avatar.tsx`\n * • không có Tailwind (canvas, → `getAvatarToneColors(name, mode)` ở `tokens.ts`\n * email HTML, PDF)\n *\n * Vì sao MỘT bảng chứ không phải hai (một bảng class + một bảng hex \"ảnh chiếu\"): hai bảng viết\n * tay song song thì chỉ cần một lần thêm/xoá/đảo tone là cùng một người ra hai màu khác nhau giữa\n * web và email — mà không có gì báo. Ở đây mỗi tone là MỘT phần tử mang cả chuỗi class lẫn công\n * thức hex tương ứng, nằm sát nhau trên vài dòng liền: thêm tone là thêm cả ba, không quên được.\n * Thứ tự phần tử = chỉ số băm, nên thứ tự cũng chỉ tồn tại ở một chỗ.\n *\n * Vì sao công thức hex là HÀM nhận `palette` chứ không đọc thẳng token: `tokens.ts` là entry\n * `@comitor/ui/tokens`; nếu file này `import { accent } from \"../tokens.js\"` thì thành vòng import\n * (tokens.ts phải import ngược lại bảng này) — vòng có khởi tạo hằng ở giữa là lỗi TDZ lúc chạy.\n * Nhận bảng màu qua tham số thì phụ thuộc chỉ đi MỘT chiều: tokens.ts → file này.\n */\n\n/** Bảy sắc màu phụ mà bảng tone đọc (gold đi riêng qua thang của nó). */\ninterface AvatarToneHues {\n readonly teal: string;\n readonly green: string;\n readonly red: string;\n readonly yellow: string;\n readonly blue: string;\n readonly purple: string;\n readonly pink: string;\n}\n\n/** Đúng 8 tone — chốt ở tầng kiểu: `getAvatarToneIndex` băm theo độ dài này. */\ntype AvatarToneTable<T> = readonly [T, T, T, T, T, T, T, T];\n\n/** Cặp nền + màu chữ (hex đục) của một tone avatar. */\nexport interface AvatarToneColors {\n background: string;\n foreground: string;\n}\n\n/**\n * Các bậc màu mà công thức hex bên dưới đọc vào — `tokens.ts` truyền xuống một bộ cho light,\n * một bộ cho dark. Khai tường minh bằng `string` (không `typeof gold`) vì bản light và bản dark\n * là hai bộ giá trị khác nhau, không cùng một kiểu literal.\n */\nexport interface AvatarTonePalette {\n /** Thang gold — cần bậc 100 · 500 · 600. */\n gold: { readonly 100: string; readonly 500: string; readonly 600: string };\n /** Màu phụ ở vai trò TÔ NỀN (`accent.light` hoặc `accent.dark`). */\n accent: AvatarToneHues;\n /** Cùng màu phụ đó ở vai trò CHỮ trên nền trang (`accentInk.light` hoặc `accentInk.dark`). */\n accentInk: AvatarToneHues;\n /** Nền mà tint hợp thành lên — `--card` của theme. */\n card: string;\n}\n\ninterface AvatarTone {\n /**\n * Tên gọi của tone — để chỗ dùng CHỌN TAY một màu thay vì để hàm băm quyết.\n *\n * ⚠ Tên là một phần của HỢP ĐỒNG: nó đi vào cột database của app (`apps.icon_color` bên\n * `comitor-account`) và vào prop `tone` của `IconAvatar`. Đổi tên một tone là làm mọi hàng đã\n * lưu trỏ vào hư không — và hỏng đó IM LẶNG, vì giá trị lạ chỉ rơi về màu suy theo tên.\n * Thêm tone thì thêm tên mới; đừng đổi tên cũ.\n */\n readonly name: AvatarToneName;\n /** Vai trò DOM: chuỗi class token tĩnh (kèm biến thể `dark:`). */\n readonly classes: string;\n /** Vai trò canvas/email/PDF: đúng những gì `classes` nói, tính ra hex đục ở light. */\n readonly light: (p: AvatarTonePalette) => AvatarToneColors;\n /** Nhánh `dark:` của `classes`. */\n readonly dark: (p: AvatarTonePalette) => AvatarToneColors;\n}\n\n/**\n * Trộn `color` phủ `alpha` lên nền `over`, trả về hex đục.\n *\n * Tái hiện đúng phép mà trình duyệt làm với `bg-teal/15`: Tailwind sinh ra màu CÓ alpha, còn nền\n * phía dưới (`--card`) mới quyết định hex cuối cùng. Nơi không có CSS thì không có gì để hợp\n * thành, nên hex phải mang sẵn kết quả đã trộn.\n */\nfunction tintOver(color: string, over: string, alpha: number): string {\n const channel = (hex: string, i: number) => Number.parseInt(hex.slice(1 + i * 2, 3 + i * 2), 16);\n const mixed = [0, 1, 2].map((i) => Math.round(channel(color, i) * alpha + channel(over, i) * (1 - alpha)));\n return `#${mixed.map((v) => v.toString(16).padStart(2, \"0\")).join(\"\")}`.toUpperCase();\n}\n\n/**\n * 8 tone deterministic. Phía class TẤT CẢ là token tĩnh — không ghép chuỗi lúc chạy (`bg-${c}-100`)\n * vì Tailwind quét tĩnh, class ghép động sẽ không có mặt trong CSS của app; cũng không dùng hex\n * inline vì như vậy là mất dark mode.\n *\n * ─ VÌ SAO LÀ MÀU PHỤ + CHART CHỨ KHÔNG PHẢI TOKEN SEMANTIC ─\n * Đây là màu ĐỊNH DANH (phân biệt người), không phải màu TRẠNG THÁI — gán `--success` cho một con\n * người là sai nghĩa. Bộ màu phụ + bảng chart là nhóm token DUY NHẤT có đủ 8 sắc khác nhau và có\n * bản riêng cho dark, nên vẫn đổi theo theme và app vẫn ghi đè được.\n *\n * ─ TƯƠNG PHẢN: VÌ SAO BA TONE PHẢI HẠ XUỐNG BẬC INK Ở LIGHT ─\n * Chữ initials tuy `aria-hidden` nhưng người sáng mắt vẫn đọc, nên vẫn phải đạt AA 4,5:1. Bản đầu\n * dùng thẳng `text-teal/green/red` trên nền tint 15% và HỎNG ở light (đo theo WCAG, nền tint đã\n * trộn với `--card` trắng):\n *\n * teal #1F8A8B trên bg-teal/15 = 3,44:1 ✗\n * green #2EA56A trên bg-green/15 = 2,69:1 ✗ (dưới cả ngưỡng 3:1)\n * red #D64545 trên bg-red/15 = 3,57:1 ✗\n *\n * Sửa đúng khuôn `Badge`: dùng bậc `--x-ink` cho light, giữ token đã nâng sáng cho dark. Đo lại\n * trên đúng hex mà `getAvatarToneColors` trả ra (styles.css đã đóng băng bậc ink, xem dưới):\n *\n * teal #1A6668 trên #DDEDEE = 5,54:1 ✓ · dark #2DD4BF trên #193435 = 7,12:1 ✓\n * green #216A48 trên #E0F2E9 = 5,60:1 ✓ · dark #4ADE80 trên #1E362C = 7,44:1 ✓\n * red #9A3537 trên #F9E3E3 = 5,83:1 ✓ · dark #F87171 trên #38252A = 5,18:1 ✓\n *\n * (Bộ số 5,56 / 5,58 / 7,10 / 7,47 / 5,17 ghi ở bản trước là kết quả của chuỗi `color-mix` TRƯỚC\n * khi hex được đóng băng vào token — lệch ở chữ, không lệch ở màu.)\n *\n * Class viết thẳng `text-teal-ink` / `text-green-ink` / `text-red-ink` / `text-yellow-ink` chứ\n * KHÔNG nhắc lại chuỗi `color-mix(...)` — hai cách cho ra đúng cùng một hex (styles.css đã đóng\n * băng kết quả: #1A6668 · #216A48 · #9A3537), và ở `.dark` thì `--teal-ink` vốn trỏ về `--teal`\n * nên không cần biến thể `dark:` nào. Nhờ vậy tên trong class khớp đúng tên trong công thức hex\n * ngay bên dưới (`p.accentInk.teal`) — soi hai vế cạnh nhau là thấy lệch ngay.\n */\n/**\n * Tên của 8 tone, ĐÚNG THỨ TỰ bảng bên dưới — thứ tự ấy cũng là chỉ số băm, nên hai thứ không\n * được trôi khỏi nhau. Đây là bảng màu mà chỗ dùng chọn tay khi không muốn để tên quyết định.\n *\n * Tên lấy theo SẮC, không theo vai trò ngữ nghĩa: đây là màu ĐỊNH DANH, không phải màu trạng\n * thái — xem lý do dài ở docblock của `AVATAR_TONES`.\n */\nexport const AVATAR_TONE_NAMES = [\"gold\", \"teal\", \"green\", \"red\", \"yellow\", \"blue\", \"purple\", \"pink\"] as const;\n\nexport type AvatarToneName = (typeof AVATAR_TONE_NAMES)[number];\n\n/**\n * Tên tone CŨ → tên tone hôm nay (3.10.0). Ba tone cuối từng là `navy` · `ash` · `neutral` — cả ba\n * đều là xám ám xanh ở các độ đậm khác nhau, nên ô icon của ba app khác nhau trông như cùng một\n * màu. Chúng được THAY bằng ba sắc thật, đứng ĐÚNG vị trí cũ trong bảng (chỉ số băm giữ nguyên:\n * người/app nào trước ra `navy` thì nay ra `blue`, không ai nhảy sang tone khác).\n *\n * Bí danh thay vì giữ tên cũ cho màu mới: tên là hợp đồng (cột `apps.icon_color` của Account), và\n * một tên nói dối về màu (\"ash\" mà tím) còn tệ hơn một lần đổi tên có lối lùi. Bí danh đi theo\n * cùng VỊ TRÍ, nên hàng database còn ghi tên cũ ra đúng màu mà phép băm cũng sẽ cho ở chỗ đó.\n */\nexport const LEGACY_AVATAR_TONE_ALIASES: Readonly<Record<string, AvatarToneName>> = {\n navy: \"blue\",\n ash: \"purple\",\n neutral: \"pink\"\n};\n\nexport const AVATAR_TONES: AvatarToneTable<AvatarTone> = [\n {\n name: \"gold\",\n classes: \"bg-gold-100 text-gold-600 dark:bg-gold-500/30 dark:text-gold-100\",\n light: (p) => ({ background: p.gold[100], foreground: p.gold[600] }),\n dark: (p) => ({ background: tintOver(p.gold[500], p.card, 0.3), foreground: p.gold[100] })\n },\n {\n name: \"teal\",\n classes: \"bg-teal/15 text-teal-ink\",\n light: (p) => ({ background: tintOver(p.accent.teal, p.card, 0.15), foreground: p.accentInk.teal }),\n dark: (p) => ({ background: tintOver(p.accent.teal, p.card, 0.15), foreground: p.accentInk.teal })\n },\n {\n name: \"green\",\n classes: \"bg-green/15 text-green-ink\",\n light: (p) => ({ background: tintOver(p.accent.green, p.card, 0.15), foreground: p.accentInk.green }),\n dark: (p) => ({ background: tintOver(p.accent.green, p.card, 0.15), foreground: p.accentInk.green })\n },\n {\n name: \"red\",\n classes: \"bg-red/15 text-red-ink\",\n light: (p) => ({ background: tintOver(p.accent.red, p.card, 0.15), foreground: p.accentInk.red }),\n dark: (p) => ({ background: tintOver(p.accent.red, p.card, 0.15), foreground: p.accentInk.red })\n },\n {\n name: \"yellow\",\n classes: \"bg-yellow/25 text-yellow-ink dark:bg-yellow/20\",\n light: (p) => ({ background: tintOver(p.accent.yellow, p.card, 0.25), foreground: p.accentInk.yellow }),\n dark: (p) => ({ background: tintOver(p.accent.yellow, p.card, 0.2), foreground: p.accentInk.yellow })\n },\n {\n // Ba tone cuối: ba sắc ĐỊNH DANH riêng của bảng này (styles.css, khối \"Màu phụ\") — thay navy ·\n // ash · neutral từ 3.10.0, xem `LEGACY_AVATAR_TONE_ALIASES`. Cùng khuôn teal/green/red: tint /15\n // + bậc ink; ở dark `--x-ink` trỏ về bản dark riêng của fill nên một chuỗi class đủ cả hai theme.\n // Chữ trên tint /15: blue 5,67 · purple 5,72 · pink 5,33:1 (light) · 5,76 · 5,17 · 5,37:1 (dark).\n name: \"blue\",\n classes: \"bg-blue/15 text-blue-ink\",\n light: (p) => ({ background: tintOver(p.accent.blue, p.card, 0.15), foreground: p.accentInk.blue }),\n dark: (p) => ({ background: tintOver(p.accent.blue, p.card, 0.15), foreground: p.accentInk.blue })\n },\n {\n name: \"purple\",\n classes: \"bg-purple/15 text-purple-ink\",\n light: (p) => ({ background: tintOver(p.accent.purple, p.card, 0.15), foreground: p.accentInk.purple }),\n dark: (p) => ({ background: tintOver(p.accent.purple, p.card, 0.15), foreground: p.accentInk.purple })\n },\n {\n name: \"pink\",\n classes: \"bg-pink/15 text-pink-ink\",\n light: (p) => ({ background: tintOver(p.accent.pink, p.card, 0.15), foreground: p.accentInk.pink }),\n dark: (p) => ({ background: tintOver(p.accent.pink, p.card, 0.15), foreground: p.accentInk.pink })\n }\n];\n\n/**\n * Hash chuỗi → số nguyên không âm (biến thể FNV-1a 32 bit).\n *\n * Nội bộ, KHÔNG xuất ra API: công dụng duy nhất của nó là chọn tone avatar ngay dưới đây. Xuất\n * một hàm băm ra ngoài chỉ mời app dựng lại phép chọn tone của riêng nó — đúng thứ sinh ra chuyện\n * cùng một người hai màu. Chạy trên code point (không phải byte) để tên có dấu vẫn ổn định giữa\n * server và client.\n */\nfunction hashString(input: string): number {\n let hash = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return Math.abs(hash | 0);\n}\n\n/**\n * Chỉ số tone theo tên — phép chọn màu avatar DUY NHẤT của cả gói.\n *\n * Khoá băm là **tên hiển thị** đã `trim` + hạ chữ thường theo locale `vi-VN` (không phải\n * email/slug: chỗ nào hiển thị avatar cũng chắc chắn có tên, còn email/slug thì không — lấy khoá\n * khác nhau ở từng chỗ là cách nhanh nhất để cùng một người ra hai màu).\n */\nexport function getAvatarToneIndex(name: string): number {\n return hashString(name.trim().toLocaleLowerCase(\"vi-VN\")) % AVATAR_TONES.length;\n}\n\n/** Tone (cả class lẫn công thức hex) ứng với một tên. */\nexport function getAvatarTone(name: string): AvatarTone {\n // `noUncheckedIndexedAccess` bật → phải có nhánh lùi, dù chỉ số luôn nằm trong [0, 8).\n return AVATAR_TONES[getAvatarToneIndex(name)] ?? AVATAR_TONES[0];\n}\n\n/**\n * Tone theo TÊN — cửa cho chỗ dùng chọn tay một màu.\n *\n * Trả `undefined` cho tên lạ, và nơi gọi hiểu đó là \"rơi về màu suy theo tên\". KHÔNG ném: giá trị\n * này đến từ database (`apps.icon_color`), nơi một hàng cũ hoàn toàn có thể mang tên của một tone\n * đã bị xoá — làm sập cả dải ứng dụng vì một chữ trong một hàng là đổi sai lấy tệ hơn. Tên đã\n * ĐỔI thì đi qua `LEGACY_AVATAR_TONE_ALIASES` trước khi tra.\n *\n * ⚠ `LEGACY_AVATAR_TONE_ALIASES` dùng `Record` trần: `\"constructor\"` tra ra một hàm, không phải\n * tên tone — nhưng hàm thì không bằng `tone.name` nào, nên kết quả vẫn là `undefined`.\n */\nexport function getAvatarToneByName(name: string | null | undefined): AvatarTone | undefined {\n if (!name) return undefined;\n const current = LEGACY_AVATAR_TONE_ALIASES[name] ?? name;\n return AVATAR_TONES.find((tone) => tone.name === current);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/lib/avatar-tone.ts"],"sourcesContent":["/**\n * NGUỒN SỰ THẬT DUY NHẤT của màu avatar chữ cái — cả gói chỉ có bảng này.\n *\n * File nội bộ (không nằm trong barrel nào): app không import trực tiếp được, mà đi qua đúng\n * hai cửa tuỳ theo chỗ vẽ:\n * • có Tailwind (DOM) → `getAvatarToneClasses(name)` ở `composite/letter-avatar.tsx`\n * • không có Tailwind (canvas, → `getAvatarToneColors(name, mode)` ở `tokens.ts`\n * email HTML, PDF)\n *\n * Vì sao MỘT bảng chứ không phải hai (một bảng class + một bảng hex \"ảnh chiếu\"): hai bảng viết\n * tay song song thì chỉ cần một lần thêm/xoá/đảo tone là cùng một người ra hai màu khác nhau giữa\n * web và email — mà không có gì báo. Ở đây mỗi tone là MỘT phần tử mang cả chuỗi class lẫn công\n * thức hex tương ứng, nằm sát nhau trên vài dòng liền: thêm tone là thêm cả ba, không quên được.\n * Thứ tự phần tử = chỉ số băm, nên thứ tự cũng chỉ tồn tại ở một chỗ.\n *\n * Vì sao công thức hex là HÀM nhận `palette` chứ không đọc thẳng token: `tokens.ts` là entry\n * `@comitor/ui/tokens`; nếu file này `import { accent } from \"../tokens.js\"` thì thành vòng import\n * (tokens.ts phải import ngược lại bảng này) — vòng có khởi tạo hằng ở giữa là lỗi TDZ lúc chạy.\n * Nhận bảng màu qua tham số thì phụ thuộc chỉ đi MỘT chiều: tokens.ts → file này.\n */\n\n/** Bảy sắc màu phụ mà bảng tone đọc (gold đi riêng qua thang của nó). */\ninterface AvatarToneHues {\n readonly teal: string;\n readonly green: string;\n readonly red: string;\n readonly yellow: string;\n readonly blue: string;\n readonly purple: string;\n readonly pink: string;\n}\n\n/** Đúng 8 tone — chốt ở tầng kiểu: `getAvatarToneIndex` băm theo độ dài này. */\ntype AvatarToneTable<T> = readonly [T, T, T, T, T, T, T, T];\n\n/** Cặp nền + màu chữ (hex đục) của một tone avatar. */\nexport interface AvatarToneColors {\n background: string;\n foreground: string;\n}\n\n/**\n * Các bậc màu mà công thức hex bên dưới đọc vào — `tokens.ts` truyền xuống một bộ cho light,\n * một bộ cho dark. Khai tường minh bằng `string` (không `typeof gold`) vì bản light và bản dark\n * là hai bộ giá trị khác nhau, không cùng một kiểu literal.\n */\nexport interface AvatarTonePalette {\n /** Thang gold — cần bậc 100 · 500 · 600. */\n gold: { readonly 100: string; readonly 500: string; readonly 600: string };\n /** Màu phụ ở vai trò TÔ NỀN (`accent.light` hoặc `accent.dark`). */\n accent: AvatarToneHues;\n /** Cùng màu phụ đó ở vai trò CHỮ trên nền trang (`accentInk.light` hoặc `accentInk.dark`). */\n accentInk: AvatarToneHues;\n /** Nền mà tint hợp thành lên — `--card` của theme. */\n card: string;\n}\n\ninterface AvatarTone {\n /**\n * Tên gọi của tone — để chỗ dùng CHỌN TAY một màu thay vì để hàm băm quyết.\n *\n * ⚠ Tên là một phần của HỢP ĐỒNG: nó đi vào cột database của app (`apps.icon_color` bên\n * `comitor-account`) và vào prop `tone` của `IconAvatar`. Đổi tên một tone là làm mọi hàng đã\n * lưu trỏ vào hư không — và hỏng đó IM LẶNG, vì giá trị lạ chỉ rơi về màu suy theo tên.\n * Thêm tone thì thêm tên mới; đừng đổi tên cũ.\n */\n readonly name: AvatarToneName;\n /** Vai trò DOM: chuỗi class token tĩnh (kèm biến thể `dark:`). */\n readonly classes: string;\n /** Vai trò canvas/email/PDF: đúng những gì `classes` nói, tính ra hex đục ở light. */\n readonly light: (p: AvatarTonePalette) => AvatarToneColors;\n /** Nhánh `dark:` của `classes`. */\n readonly dark: (p: AvatarTonePalette) => AvatarToneColors;\n}\n\n/**\n * Trộn `color` phủ `alpha` lên nền `over`, trả về hex đục.\n *\n * Tái hiện đúng phép mà trình duyệt làm với `bg-teal/15`: Tailwind sinh ra màu CÓ alpha, còn nền\n * phía dưới (`--card`) mới quyết định hex cuối cùng. Nơi không có CSS thì không có gì để hợp\n * thành, nên hex phải mang sẵn kết quả đã trộn.\n */\nfunction tintOver(color: string, over: string, alpha: number): string {\n const channel = (hex: string, i: number) => Number.parseInt(hex.slice(1 + i * 2, 3 + i * 2), 16);\n const mixed = [0, 1, 2].map((i) => Math.round(channel(color, i) * alpha + channel(over, i) * (1 - alpha)));\n return `#${mixed.map((v) => v.toString(16).padStart(2, \"0\")).join(\"\")}`.toUpperCase();\n}\n\n/**\n * 8 tone deterministic. Phía class TẤT CẢ là token tĩnh — không ghép chuỗi lúc chạy (`bg-${c}-100`)\n * vì Tailwind quét tĩnh, class ghép động sẽ không có mặt trong CSS của app; cũng không dùng hex\n * inline vì như vậy là mất dark mode.\n *\n * ─ VÌ SAO LÀ MÀU PHỤ + CHART CHỨ KHÔNG PHẢI TOKEN SEMANTIC ─\n * Đây là màu ĐỊNH DANH (phân biệt người), không phải màu TRẠNG THÁI — gán `--success` cho một con\n * người là sai nghĩa. Bộ màu phụ + bảng chart là nhóm token DUY NHẤT có đủ 8 sắc khác nhau và có\n * bản riêng cho dark, nên vẫn đổi theo theme và app vẫn ghi đè được.\n *\n * ─ TƯƠNG PHẢN: VÌ SAO BA TONE PHẢI HẠ XUỐNG BẬC INK Ở LIGHT ─\n * Chữ initials tuy `aria-hidden` nhưng người sáng mắt vẫn đọc, nên vẫn phải đạt AA 4,5:1. Bản đầu\n * dùng thẳng `text-teal/green/red` trên nền tint 15% và HỎNG ở light (đo theo WCAG, nền tint đã\n * trộn với `--card` trắng):\n *\n * teal #1F8A8B trên bg-teal/15 = 3,44:1 ✗\n * green #2EA56A trên bg-green/15 = 2,69:1 ✗ (dưới cả ngưỡng 3:1)\n * red #D64545 trên bg-red/15 = 3,57:1 ✗\n *\n * Sửa đúng khuôn `Badge`: dùng bậc `--x-ink` cho light, giữ token đã nâng sáng cho dark. Đo lại\n * trên đúng hex mà `getAvatarToneColors` trả ra (styles.css đã đóng băng bậc ink, xem dưới):\n *\n * teal #1A6668 trên #DDEDEE = 5,54:1 ✓ · dark #2DD4BF trên #193435 = 7,12:1 ✓\n * green #216A48 trên #E0F2E9 = 5,60:1 ✓ · dark #4ADE80 trên #1E362C = 7,44:1 ✓\n * red #9A3537 trên #F9E3E3 = 5,83:1 ✓ · dark #F87171 trên #38252A = 5,18:1 ✓\n *\n * (Bộ số 5,56 / 5,58 / 7,10 / 7,47 / 5,17 ghi ở bản trước là kết quả của chuỗi `color-mix` TRƯỚC\n * khi hex được đóng băng vào token — lệch ở chữ, không lệch ở màu.)\n *\n * Class viết thẳng `text-teal-ink` / `text-green-ink` / `text-red-ink` / `text-yellow-ink` chứ\n * KHÔNG nhắc lại chuỗi `color-mix(...)` — hai cách cho ra đúng cùng một hex (styles.css đã đóng\n * băng kết quả: #1A6668 · #216A48 · #9A3537), và ở `.dark` thì `--teal-ink` vốn trỏ về `--teal`\n * nên không cần biến thể `dark:` nào. Nhờ vậy tên trong class khớp đúng tên trong công thức hex\n * ngay bên dưới (`p.accentInk.teal`) — soi hai vế cạnh nhau là thấy lệch ngay.\n */\n/**\n * Tên của 8 tone, ĐÚNG THỨ TỰ bảng bên dưới — thứ tự ấy cũng là chỉ số băm, nên hai thứ không\n * được trôi khỏi nhau. Đây là bảng màu mà chỗ dùng chọn tay khi không muốn để tên quyết định.\n *\n * Tên lấy theo SẮC, không theo vai trò ngữ nghĩa: đây là màu ĐỊNH DANH, không phải màu trạng\n * thái — xem lý do dài ở docblock của `AVATAR_TONES`.\n */\nexport const AVATAR_TONE_NAMES = [\"gold\", \"teal\", \"green\", \"red\", \"yellow\", \"blue\", \"purple\", \"pink\"] as const;\n\nexport type AvatarToneName = (typeof AVATAR_TONE_NAMES)[number];\n\n/**\n * Tên tone CŨ → tên tone hôm nay (3.10.0). Ba tone cuối từng là `navy` · `ash` · `neutral` — cả ba\n * đều là xám ám xanh ở các độ đậm khác nhau, nên ô icon của ba app khác nhau trông như cùng một\n * màu. Chúng được THAY bằng ba sắc thật, đứng ĐÚNG vị trí cũ trong bảng (chỉ số băm giữ nguyên:\n * người/app nào trước ra `navy` thì nay ra `blue`, không ai nhảy sang tone khác).\n *\n * Bí danh thay vì giữ tên cũ cho màu mới: tên là hợp đồng (cột `apps.icon_color` của Account), và\n * một tên nói dối về màu (\"ash\" mà tím) còn tệ hơn một lần đổi tên có lối lùi. Bí danh đi theo\n * cùng VỊ TRÍ, nên hàng database còn ghi tên cũ ra đúng màu mà phép băm cũng sẽ cho ở chỗ đó.\n *\n * Xuất ở `@comitor/ui` từ 3.12.1 — app ánh xạ tên cũ đọc từ database bằng chính bảng này thay vì\n * chép một bản. ⚠ `Record` trần: khoá đến từ dữ liệu ngoài thì tra qua `Object.hasOwn` —\n * `LEGACY_AVATAR_TONE_ALIASES[\"constructor\"]` ra một hàm, không ra `undefined`.\n */\nexport const LEGACY_AVATAR_TONE_ALIASES: Readonly<Record<string, AvatarToneName>> = {\n navy: \"blue\",\n ash: \"purple\",\n neutral: \"pink\"\n};\n\nexport const AVATAR_TONES: AvatarToneTable<AvatarTone> = [\n {\n name: \"gold\",\n classes: \"bg-gold-100 text-gold-600 dark:bg-gold-500/30 dark:text-gold-100\",\n light: (p) => ({ background: p.gold[100], foreground: p.gold[600] }),\n dark: (p) => ({ background: tintOver(p.gold[500], p.card, 0.3), foreground: p.gold[100] })\n },\n {\n name: \"teal\",\n classes: \"bg-teal/15 text-teal-ink\",\n light: (p) => ({ background: tintOver(p.accent.teal, p.card, 0.15), foreground: p.accentInk.teal }),\n dark: (p) => ({ background: tintOver(p.accent.teal, p.card, 0.15), foreground: p.accentInk.teal })\n },\n {\n name: \"green\",\n classes: \"bg-green/15 text-green-ink\",\n light: (p) => ({ background: tintOver(p.accent.green, p.card, 0.15), foreground: p.accentInk.green }),\n dark: (p) => ({ background: tintOver(p.accent.green, p.card, 0.15), foreground: p.accentInk.green })\n },\n {\n name: \"red\",\n classes: \"bg-red/15 text-red-ink\",\n light: (p) => ({ background: tintOver(p.accent.red, p.card, 0.15), foreground: p.accentInk.red }),\n dark: (p) => ({ background: tintOver(p.accent.red, p.card, 0.15), foreground: p.accentInk.red })\n },\n {\n name: \"yellow\",\n classes: \"bg-yellow/25 text-yellow-ink dark:bg-yellow/20\",\n light: (p) => ({ background: tintOver(p.accent.yellow, p.card, 0.25), foreground: p.accentInk.yellow }),\n dark: (p) => ({ background: tintOver(p.accent.yellow, p.card, 0.2), foreground: p.accentInk.yellow })\n },\n {\n // Ba tone cuối: ba sắc ĐỊNH DANH riêng của bảng này (styles.css, khối \"Màu phụ\") — thay navy ·\n // ash · neutral từ 3.10.0, xem `LEGACY_AVATAR_TONE_ALIASES`. Cùng khuôn teal/green/red: tint /15\n // + bậc ink; ở dark `--x-ink` trỏ về bản dark riêng của fill nên một chuỗi class đủ cả hai theme.\n // Chữ trên tint /15: blue 5,67 · purple 5,72 · pink 5,33:1 (light) · 5,76 · 5,17 · 5,37:1 (dark).\n name: \"blue\",\n classes: \"bg-blue/15 text-blue-ink\",\n light: (p) => ({ background: tintOver(p.accent.blue, p.card, 0.15), foreground: p.accentInk.blue }),\n dark: (p) => ({ background: tintOver(p.accent.blue, p.card, 0.15), foreground: p.accentInk.blue })\n },\n {\n name: \"purple\",\n classes: \"bg-purple/15 text-purple-ink\",\n light: (p) => ({ background: tintOver(p.accent.purple, p.card, 0.15), foreground: p.accentInk.purple }),\n dark: (p) => ({ background: tintOver(p.accent.purple, p.card, 0.15), foreground: p.accentInk.purple })\n },\n {\n name: \"pink\",\n classes: \"bg-pink/15 text-pink-ink\",\n light: (p) => ({ background: tintOver(p.accent.pink, p.card, 0.15), foreground: p.accentInk.pink }),\n dark: (p) => ({ background: tintOver(p.accent.pink, p.card, 0.15), foreground: p.accentInk.pink })\n }\n];\n\n/**\n * Hash chuỗi → số nguyên không âm (biến thể FNV-1a 32 bit).\n *\n * Nội bộ, KHÔNG xuất ra API: công dụng duy nhất của nó là chọn tone avatar ngay dưới đây. Xuất\n * một hàm băm ra ngoài chỉ mời app dựng lại phép chọn tone của riêng nó — đúng thứ sinh ra chuyện\n * cùng một người hai màu. Chạy trên code point (không phải byte) để tên có dấu vẫn ổn định giữa\n * server và client.\n */\nfunction hashString(input: string): number {\n let hash = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return Math.abs(hash | 0);\n}\n\n/**\n * Chỉ số tone theo tên — phép chọn màu avatar DUY NHẤT của cả gói.\n *\n * Khoá băm là **tên hiển thị** đã `trim` + hạ chữ thường theo locale `vi-VN` (không phải\n * email/slug: chỗ nào hiển thị avatar cũng chắc chắn có tên, còn email/slug thì không — lấy khoá\n * khác nhau ở từng chỗ là cách nhanh nhất để cùng một người ra hai màu).\n */\nexport function getAvatarToneIndex(name: string): number {\n return hashString(name.trim().toLocaleLowerCase(\"vi-VN\")) % AVATAR_TONES.length;\n}\n\n/** Tone (cả class lẫn công thức hex) ứng với một tên. */\nexport function getAvatarTone(name: string): AvatarTone {\n // `noUncheckedIndexedAccess` bật → phải có nhánh lùi, dù chỉ số luôn nằm trong [0, 8).\n return AVATAR_TONES[getAvatarToneIndex(name)] ?? AVATAR_TONES[0];\n}\n\n/**\n * Tone theo TÊN — cửa cho chỗ dùng chọn tay một màu.\n *\n * Trả `undefined` cho tên lạ, và nơi gọi hiểu đó là \"rơi về màu suy theo tên\". KHÔNG ném: giá trị\n * này đến từ database (`apps.icon_color`), nơi một hàng cũ hoàn toàn có thể mang tên của một tone\n * đã bị xoá — làm sập cả dải ứng dụng vì một chữ trong một hàng là đổi sai lấy tệ hơn. Tên đã\n * ĐỔI thì đi qua `LEGACY_AVATAR_TONE_ALIASES` trước khi tra.\n *\n * ⚠ `LEGACY_AVATAR_TONE_ALIASES` dùng `Record` trần: `\"constructor\"` tra ra một hàm, không phải\n * tên tone — nhưng hàm thì không bằng `tone.name` nào, nên kết quả vẫn là `undefined`.\n */\nexport function getAvatarToneByName(name: string | null | undefined): AvatarTone | undefined {\n if (!name) return undefined;\n const current = LEGACY_AVATAR_TONE_ALIASES[name] ?? name;\n return AVATAR_TONES.find((tone) => tone.name === current);\n}\n"],"mappings":"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","names":[]}
@@ -25,8 +25,22 @@ interface FileUploadLogicLabels {
25
25
  */
26
26
  missingDependency: string;
27
27
  }
28
+ /** Giới hạn đã định dạng sẵn, đưa vào `dropDescription`. */
29
+ interface FileUploadLimits {
30
+ /** `maxNumberOfFiles` của component. */
31
+ maxFiles: number;
32
+ /** `maxFileSize` đã đổi sang chữ theo locale, vd `"5 MB"`. */
33
+ maxSize: string;
34
+ }
28
35
  interface FileUploadLabels extends FileUploadLogicLabels {
29
- /** Chữ trong vùng kéo thả. */
36
+ /**
37
+ * Dòng tiêu đề của vùng kéo thả (bố cục mặc định). TUỲ CHỌN trong kiểu — thêm sau `3.12.1`, để bảng nhãn
38
+ * app tự dựng đủ khoá từ trước không gãy type-check; thiếu thì dùng bản tiếng Anh.
39
+ */
40
+ dropTitle?: string;
41
+ /** Dòng phụ dưới tiêu đề — nhận giới hạn để nói "tối đa mấy tệp, mỗi tệp bao nhiêu". Tuỳ chọn như `dropTitle`. */
42
+ dropDescription?: (limits: FileUploadLimits) => string;
43
+ /** Chữ của bố cục `compact` (một dòng chữ + nút). */
30
44
  dropHint: string;
31
45
  /** Nhãn nút mở hộp chọn tệp. */
32
46
  choose: string;
@@ -56,6 +70,16 @@ interface ImageUploadFieldLabels extends FileUploadLogicLabels {
56
70
  hint: string;
57
71
  /** Chữ hiện đè lên khối khi đang rê tệp qua. */
58
72
  dropPrompt: string;
73
+ /** Tiêu đề hộp cắt ảnh. */
74
+ cropTitle?: string;
75
+ /** Dòng hướng dẫn dưới tiêu đề. */
76
+ cropDescription?: string;
77
+ /** `aria-label` của thanh trượt phóng to. */
78
+ cropZoom?: string;
79
+ /** Nút xác nhận — cắt rồi tải lên. */
80
+ cropSave?: string;
81
+ /** Nút huỷ. */
82
+ cropCancel?: string;
59
83
  }
60
84
  /**
61
85
  * Nhãn TIẾNG ANH mặc định của `ImageUploadField` (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng
@@ -64,4 +88,4 @@ interface ImageUploadFieldLabels extends FileUploadLogicLabels {
64
88
  */
65
89
  declare const DEFAULT_IMAGE_UPLOAD_FIELD_LABELS: ImageUploadFieldLabels;
66
90
 
67
- export { DEFAULT_FILE_UPLOAD_LABELS, DEFAULT_IMAGE_UPLOAD_FIELD_LABELS, type FileUploadLabels, type FileUploadLogicLabels, type ImageUploadFieldLabels };
91
+ export { DEFAULT_FILE_UPLOAD_LABELS, DEFAULT_IMAGE_UPLOAD_FIELD_LABELS, type FileUploadLabels, type FileUploadLimits, type FileUploadLogicLabels, type ImageUploadFieldLabels };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploader/file-upload-constants.ts"],"sourcesContent":["/**\n * Nhãn của `FileUpload` — file RIÊNG, không `\"use client\"`.\n *\n * ⚠ `verify:directives` chặn việc khai hằng dữ liệu thuần trong một file `\"use client\"`: làm vậy\n * thì mọi chỗ import hằng đó (kể cả Server Component chỉ cần đọc một chuỗi) bị kéo sang phía\n * client. Đó là lý do file này tách ra, cùng khuôn với `data-table-constants.ts`.\n *\n * Chữ theo `ComitorLocaleProvider` (namespace `fileUpload` / `imageUploadField`, mặc định tiếng\n * Anh, bảng tiếng Việt dựng sẵn); override từng khoá qua prop `labels`.\n */\n\nimport { fileUploadEn } from \"../i18n/namespaces/file-upload.js\";\nimport { imageUploadFieldEn } from \"../i18n/namespaces/image-upload-field.js\";\n\n/**\n * Hai chuỗi mà phần MÁY MÓC dùng — `useFileUpload` chỉ nhận đúng chúng.\n *\n * Tách ra vì ba chuỗi còn lại của `FileUploadLabels` thuần bố cục: để chúng lọt vào chữ ký của hook\n * là mời người viết truyền một prop không bao giờ chạy, và kiểu thì không báo gì.\n */\nexport interface FileUploadLogicLabels {\n /** Thông điệp dự phòng khi endpoint không trả `error.code` đọc được. */\n uploadFailed: string;\n /**\n * Khi `@uppy/core` / `@uppy/xhr-upload` chưa được cài.\n *\n * Chúng là peer TUỲ CHỌN nên `import()` sẽ hỏng ở đúng lúc người dùng chọn tệp. Không có câu này\n * thì thứ họ thấy là một `ChunkLoadError`, và người sửa đi tìm nguyên nhân ở webpack.\n */\n missingDependency: string;\n}\n\nexport interface FileUploadLabels extends FileUploadLogicLabels {\n /** Chữ trong vùng kéo thả. */\n dropHint: string;\n /** Nhãn nút mở hộp chọn tệp. */\n choose: string;\n /** Nhãn nút và `aria-label` của thanh tiến độ khi đang tải. */\n uploading: string;\n}\n\n/**\n * Nhãn TIẾNG ANH mặc định của `FileUpload` (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:\n * `comitorMessagesVi.fileUpload`. Component (và `useFileUpload`) không đọc thẳng hằng này — chúng\n * đọc bảng của `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.\n */\nexport const DEFAULT_FILE_UPLOAD_LABELS: FileUploadLabels = { ...fileUploadEn };\n\n/**\n * Nhãn của `ImageUploadField` — ô ảnh đại diện / logo.\n *\n * ⚠ `hint` là chỗ DUY NHẤT người dùng học được rằng kéo-thả chạy được, vì bố cục này KHÔNG có khung\n * đứt nét thường trực để tự nói ra điều đó. Đừng rút gọn nó thành mỗi giới hạn dung lượng.\n */\nexport interface ImageUploadFieldLabels extends FileUploadLogicLabels {\n /** Nhãn nút mở hộp chọn tệp. */\n choose: string;\n /** Nhãn nút lúc đang tải. */\n uploading: string;\n /** Nhãn nút gỡ ảnh. */\n remove: string;\n /** Dòng chú thích dưới hai nút — nói cả giới hạn LẪN việc kéo-thả được. */\n hint: string;\n /** Chữ hiện đè lên khối khi đang rê tệp qua. */\n dropPrompt: string;\n}\n\n/**\n * Nhãn TIẾNG ANH mặc định của `ImageUploadField` (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng\n * Việt: `comitorMessagesVi.imageUploadField`. Component không đọc thẳng hằng này — nó đọc bảng của\n * `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.\n */\nexport const DEFAULT_IMAGE_UPLOAD_FIELD_LABELS: ImageUploadFieldLabels = { ...imageUploadFieldEn };\n"],"mappings":"AAWA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AAkC5B,MAAM,6BAA+C,EAAE,GAAG,aAAa;AA0BvE,MAAM,oCAA4D,EAAE,GAAG,mBAAmB;","names":[]}
1
+ {"version":3,"sources":["../../src/uploader/file-upload-constants.ts"],"sourcesContent":["/**\n * Nhãn của `FileUpload` — file RIÊNG, không `\"use client\"`.\n *\n * ⚠ `verify:directives` chặn việc khai hằng dữ liệu thuần trong một file `\"use client\"`: làm vậy\n * thì mọi chỗ import hằng đó (kể cả Server Component chỉ cần đọc một chuỗi) bị kéo sang phía\n * client. Đó là lý do file này tách ra, cùng khuôn với `data-table-constants.ts`.\n *\n * Chữ theo `ComitorLocaleProvider` (namespace `fileUpload` / `imageUploadField`, mặc định tiếng\n * Anh, bảng tiếng Việt dựng sẵn); override từng khoá qua prop `labels`.\n */\n\nimport { fileUploadEn } from \"../i18n/namespaces/file-upload.js\";\nimport { imageUploadFieldEn } from \"../i18n/namespaces/image-upload-field.js\";\n\n/**\n * Hai chuỗi mà phần MÁY MÓC dùng — `useFileUpload` chỉ nhận đúng chúng.\n *\n * Tách ra vì ba chuỗi còn lại của `FileUploadLabels` thuần bố cục: để chúng lọt vào chữ ký của hook\n * là mời người viết truyền một prop không bao giờ chạy, và kiểu thì không báo gì.\n */\nexport interface FileUploadLogicLabels {\n /** Thông điệp dự phòng khi endpoint không trả `error.code` đọc được. */\n uploadFailed: string;\n /**\n * Khi `@uppy/core` / `@uppy/xhr-upload` chưa được cài.\n *\n * Chúng là peer TUỲ CHỌN nên `import()` sẽ hỏng ở đúng lúc người dùng chọn tệp. Không có câu này\n * thì thứ họ thấy là một `ChunkLoadError`, và người sửa đi tìm nguyên nhân ở webpack.\n */\n missingDependency: string;\n}\n\n/** Giới hạn đã định dạng sẵn, đưa vào `dropDescription`. */\nexport interface FileUploadLimits {\n /** `maxNumberOfFiles` của component. */\n maxFiles: number;\n /** `maxFileSize` đã đổi sang chữ theo locale, vd `\"5 MB\"`. */\n maxSize: string;\n}\n\nexport interface FileUploadLabels extends FileUploadLogicLabels {\n /**\n * Dòng tiêu đề của vùng kéo thả (bố cục mặc định). TUỲ CHỌN trong kiểu — thêm sau `3.12.1`, để bảng nhãn\n * app tự dựng đủ khoá từ trước không gãy type-check; thiếu thì dùng bản tiếng Anh.\n */\n dropTitle?: string;\n /** Dòng phụ dưới tiêu đề — nhận giới hạn để nói \"tối đa mấy tệp, mỗi tệp bao nhiêu\". Tuỳ chọn như `dropTitle`. */\n dropDescription?: (limits: FileUploadLimits) => string;\n /** Chữ của bố cục `compact` (một dòng chữ + nút). */\n dropHint: string;\n /** Nhãn nút mở hộp chọn tệp. */\n choose: string;\n /** Nhãn nút và `aria-label` của thanh tiến độ khi đang tải. */\n uploading: string;\n}\n\n/**\n * Nhãn TIẾNG ANH mặc định của `FileUpload` (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:\n * `comitorMessagesVi.fileUpload`. Component (và `useFileUpload`) không đọc thẳng hằng này — chúng\n * đọc bảng của `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.\n */\nexport const DEFAULT_FILE_UPLOAD_LABELS: FileUploadLabels = { ...fileUploadEn };\n\n/**\n * Nhãn của `ImageUploadField` — ô ảnh đại diện / logo.\n *\n * ⚠ `hint` là chỗ DUY NHẤT người dùng học được rằng kéo-thả chạy được, vì bố cục này KHÔNG có khung\n * đứt nét thường trực để tự nói ra điều đó. Đừng rút gọn nó thành mỗi giới hạn dung lượng.\n */\nexport interface ImageUploadFieldLabels extends FileUploadLogicLabels {\n /** Nhãn nút mở hộp chọn tệp. */\n choose: string;\n /** Nhãn nút lúc đang tải. */\n uploading: string;\n /** Nhãn nút gỡ ảnh. */\n remove: string;\n /** Dòng chú thích dưới hai nút — nói cả giới hạn LẪN việc kéo-thả được. */\n hint: string;\n /** Chữ hiện đè lên khối khi đang rê tệp qua. */\n dropPrompt: string;\n /*\n * Năm khoá của hộp cắt ảnh (`crop`). TUỲ CHỌN trong kiểu — thêm sau `3.12.1`, để bảng nhãn app tự dựng\n * đủ khoá từ trước không gãy type-check; thiếu thì dùng bản tiếng Anh.\n */\n /** Tiêu đề hộp cắt ảnh. */\n cropTitle?: string;\n /** Dòng hướng dẫn dưới tiêu đề. */\n cropDescription?: string;\n /** `aria-label` của thanh trượt phóng to. */\n cropZoom?: string;\n /** Nút xác nhận — cắt rồi tải lên. */\n cropSave?: string;\n /** Nút huỷ. */\n cropCancel?: string;\n}\n\n/**\n * Nhãn TIẾNG ANH mặc định của `ImageUploadField` (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng\n * Việt: `comitorMessagesVi.imageUploadField`. Component không đọc thẳng hằng này — nó đọc bảng của\n * `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.\n */\nexport const DEFAULT_IMAGE_UPLOAD_FIELD_LABELS: ImageUploadFieldLabels = { ...imageUploadFieldEn };\n"],"mappings":"AAWA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AAiD5B,MAAM,6BAA+C,EAAE,GAAG,aAAa;AAwCvE,MAAM,oCAA4D,EAAE,GAAG,mBAAmB;","names":[]}
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { ReactNode } from 'react';
2
3
  import { FileUploadLabels } from './file-upload-constants.js';
3
4
  import { FileUploadResult } from './use-file-upload.js';
4
5
 
@@ -60,9 +61,25 @@ interface FileUploadProps<T = unknown> {
60
61
  * (không có provider → tiếng Anh); truyền `Partial<>` để đổi từng chuỗi.
61
62
  */
62
63
  labels?: Partial<FileUploadLabels>;
64
+ /**
65
+ * `"default"`: khối lớn — icon trong vòng tròn, tiêu đề, dòng giới hạn, nút. `"compact"`: một dòng chữ +
66
+ * nút (bố cục cũ, cho form chật). Mọi phần của `"default"` còn ẩn/thay được riêng qua các prop dưới.
67
+ */
68
+ layout?: "default" | "compact";
69
+ /** Icon trong vòng tròn (bố cục `default`). Mặc định `ImageIcon` của lucide; `null` = bỏ cả vòng tròn. */
70
+ icon?: ReactNode | null;
71
+ /** Dòng tiêu đề. Mặc định `labels.dropTitle`; `null` = ẩn. */
72
+ title?: ReactNode | null;
73
+ /** Dòng phụ. Mặc định `labels.dropDescription` dựng từ giới hạn; `null` = ẩn. */
74
+ description?: ReactNode | null;
75
+ /**
76
+ * Hiện nút mở hộp chọn tệp (mặc định `true`). Tắt thì cả vùng vẫn bấm được, và Tab vẫn tới ô chọn tệp —
77
+ * vùng nhận vòng focus thay cho nút.
78
+ */
79
+ showButton?: boolean;
63
80
  disabled?: boolean;
64
81
  className?: string;
65
82
  }
66
- declare function FileUpload<T = unknown>({ endpoint, fieldName, maxFileSize, allowedFileTypes, maxNumberOfFiles, onUploaded, onError, labels, disabled, className }: FileUploadProps<T>): React.JSX.Element;
83
+ declare function FileUpload<T = unknown>({ endpoint, fieldName, maxFileSize, allowedFileTypes, maxNumberOfFiles, onUploaded, onError, labels, layout, icon, title, description, showButton, disabled, className }: FileUploadProps<T>): React.JSX.Element;
67
84
 
68
85
  export { FileUpload, type FileUploadProps };