@comitor/ui 1.9.0 → 1.11.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 (105) hide show
  1. package/README.md +15 -4
  2. package/dist/data-grid/compose-refs.d.ts +19 -0
  3. package/dist/data-grid/compose-refs.js +41 -0
  4. package/dist/data-grid/compose-refs.js.map +1 -0
  5. package/dist/data-grid/data-grid-cell-variants.d.ts +15 -0
  6. package/dist/data-grid/data-grid-cell-variants.js +1734 -0
  7. package/dist/data-grid/data-grid-cell-variants.js.map +1 -0
  8. package/dist/data-grid/data-grid-cell-wrapper.d.ts +9 -0
  9. package/dist/data-grid/data-grid-cell-wrapper.js +179 -0
  10. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -0
  11. package/dist/data-grid/data-grid-cell.d.ts +8 -0
  12. package/dist/data-grid/data-grid-cell.js +101 -0
  13. package/dist/data-grid/data-grid-cell.js.map +1 -0
  14. package/dist/data-grid/data-grid-column-header.d.ts +12 -0
  15. package/dist/data-grid/data-grid-column-header.js +205 -0
  16. package/dist/data-grid/data-grid-column-header.js.map +1 -0
  17. package/dist/data-grid/data-grid-context-menu.d.ts +12 -0
  18. package/dist/data-grid/data-grid-context-menu.js +160 -0
  19. package/dist/data-grid/data-grid-context-menu.js.map +1 -0
  20. package/dist/data-grid/data-grid-filter-menu.d.ts +12 -0
  21. package/dist/data-grid/data-grid-filter-menu.js +714 -0
  22. package/dist/data-grid/data-grid-filter-menu.js.map +1 -0
  23. package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +20 -0
  24. package/dist/data-grid/data-grid-keyboard-shortcuts.js +227 -0
  25. package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -0
  26. package/dist/data-grid/data-grid-paste-dialog.d.ts +11 -0
  27. package/dist/data-grid/data-grid-paste-dialog.js +103 -0
  28. package/dist/data-grid/data-grid-paste-dialog.js.map +1 -0
  29. package/dist/data-grid/data-grid-presence.d.ts +14 -0
  30. package/dist/data-grid/data-grid-presence.js +16 -0
  31. package/dist/data-grid/data-grid-presence.js.map +1 -0
  32. package/dist/data-grid/data-grid-row-height-menu.d.ts +15 -0
  33. package/dist/data-grid/data-grid-row-height-menu.js +56 -0
  34. package/dist/data-grid/data-grid-row-height-menu.js.map +1 -0
  35. package/dist/data-grid/data-grid-row.d.ts +28 -0
  36. package/dist/data-grid/data-grid-row.js +206 -0
  37. package/dist/data-grid/data-grid-row.js.map +1 -0
  38. package/dist/data-grid/data-grid-search.d.ts +10 -0
  39. package/dist/data-grid/data-grid-search.js +183 -0
  40. package/dist/data-grid/data-grid-search.js.map +1 -0
  41. package/dist/data-grid/data-grid-select-column.d.ts +12 -0
  42. package/dist/data-grid/data-grid-select-column.js +177 -0
  43. package/dist/data-grid/data-grid-select-column.js.map +1 -0
  44. package/dist/data-grid/data-grid-skeleton.d.ts +13 -0
  45. package/dist/data-grid/data-grid-skeleton.js +52 -0
  46. package/dist/data-grid/data-grid-skeleton.js.map +1 -0
  47. package/dist/data-grid/data-grid-sort-menu.d.ts +12 -0
  48. package/dist/data-grid/data-grid-sort-menu.js +271 -0
  49. package/dist/data-grid/data-grid-sort-menu.js.map +1 -0
  50. package/dist/data-grid/data-grid-view-menu.d.ts +13 -0
  51. package/dist/data-grid/data-grid-view-menu.js +66 -0
  52. package/dist/data-grid/data-grid-view-menu.js.map +1 -0
  53. package/dist/data-grid/data-grid.d.ts +32 -0
  54. package/dist/data-grid/data-grid.js +235 -0
  55. package/dist/data-grid/data-grid.js.map +1 -0
  56. package/dist/data-grid/filters.d.ts +31 -0
  57. package/dist/data-grid/filters.js +212 -0
  58. package/dist/data-grid/filters.js.map +1 -0
  59. package/dist/data-grid/index.d.ts +22 -0
  60. package/dist/data-grid/index.js +77 -0
  61. package/dist/data-grid/index.js.map +1 -0
  62. package/dist/data-grid/messages.d.ts +280 -0
  63. package/dist/data-grid/messages.js +279 -0
  64. package/dist/data-grid/messages.js.map +1 -0
  65. package/dist/data-grid/sortable.d.ts +50 -0
  66. package/dist/data-grid/sortable.js +361 -0
  67. package/dist/data-grid/sortable.js.map +1 -0
  68. package/dist/data-grid/toast.d.ts +10 -0
  69. package/dist/data-grid/toast.js +21 -0
  70. package/dist/data-grid/toast.js.map +1 -0
  71. package/dist/data-grid/types.d.ts +189 -0
  72. package/dist/data-grid/types.js +1 -0
  73. package/dist/data-grid/types.js.map +1 -0
  74. package/dist/data-grid/use-as-ref.d.ts +5 -0
  75. package/dist/data-grid/use-as-ref.js +13 -0
  76. package/dist/data-grid/use-as-ref.js.map +1 -0
  77. package/dist/data-grid/use-badge-overflow.d.ts +24 -0
  78. package/dist/data-grid/use-badge-overflow.js +123 -0
  79. package/dist/data-grid/use-badge-overflow.js.map +1 -0
  80. package/dist/data-grid/use-callback-ref.d.ts +10 -0
  81. package/dist/data-grid/use-callback-ref.js +12 -0
  82. package/dist/data-grid/use-callback-ref.js.map +1 -0
  83. package/dist/data-grid/use-data-grid-undo-redo.d.ts +33 -0
  84. package/dist/data-grid/use-data-grid-undo-redo.js +345 -0
  85. package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -0
  86. package/dist/data-grid/use-data-grid.d.ts +92 -0
  87. package/dist/data-grid/use-data-grid.js +2692 -0
  88. package/dist/data-grid/use-data-grid.js.map +1 -0
  89. package/dist/data-grid/use-debounced-callback.d.ts +6 -0
  90. package/dist/data-grid/use-debounced-callback.js +19 -0
  91. package/dist/data-grid/use-debounced-callback.js.map +1 -0
  92. package/dist/data-grid/use-isomorphic-layout-effect.d.ts +5 -0
  93. package/dist/data-grid/use-isomorphic-layout-effect.js +6 -0
  94. package/dist/data-grid/use-isomorphic-layout-effect.js.map +1 -0
  95. package/dist/data-grid/use-lazy-ref.d.ts +5 -0
  96. package/dist/data-grid/use-lazy-ref.js +12 -0
  97. package/dist/data-grid/use-lazy-ref.js.map +1 -0
  98. package/dist/data-grid/utils.d.ts +73 -0
  99. package/dist/data-grid/utils.js +408 -0
  100. package/dist/data-grid/utils.js.map +1 -0
  101. package/dist/shell/app-launcher.js +16 -16
  102. package/dist/shell/app-launcher.js.map +1 -1
  103. package/dist/shell/types.d.ts +13 -0
  104. package/dist/shell/types.js.map +1 -1
  105. package/package.json +33 -15
@@ -44,6 +44,16 @@ interface AppDescriptor {
44
44
  description?: string;
45
45
  icon: ShellIcon;
46
46
  href: string;
47
+ /**
48
+ * Tone màu định danh — MỘT tên trong `AVATAR_TONE_NAMES` của `@comitor/ui`.
49
+ *
50
+ * Bỏ trống thì màu TỰ SUY từ `id` bằng bảng tone dùng chung với `LetterAvatar`, nên một sản
51
+ * phẩm mới có màu ngay mà không ai phải cấu hình. Tên lạ cũng rơi về màu suy ra — không ném.
52
+ *
53
+ * ⚠ Hạt giống là `id` (khoá app), KHÔNG phải `name`: tên đổi theo ngôn ngữ giao diện, và đo
54
+ * được 4/6 app đổi màu khi chuyển vi ⇄ en nếu băm theo tên. Xem `IconAvatar` prop `seed`.
55
+ */
56
+ tone?: string;
47
57
  /**
48
58
  * Màu nhấn riêng của app — bất kỳ giá trị màu CSS hợp lệ (nên là `var(--…)`).
49
59
  * Shell gán vào biến `--app-accent` bằng inline style, KHÔNG sinh class động.
@@ -52,8 +62,10 @@ interface AppDescriptor {
52
62
  * (và `accentForeground` nếu nền đặc của bạn không mang được mực) — xem
53
63
  * `docs/hop-dong-mau.md` §5.
54
64
  */
65
+ /** @deprecated Từ 1.10.0 `AppLauncher` KHÔNG đọc prop này nữa — dùng `tone`. Xem ghi chú trên. */
55
66
  accent?: string;
56
67
  /** Màu chữ/icon nằm TRÊN nền `--app-accent` ĐẶC; mặc định giữ `--app-accent-foreground` của theme. */
68
+ /** @deprecated Từ 1.10.0 — dùng `tone`. */
57
69
  accentForeground?: string;
58
70
  /**
59
71
  * Chính màu app khi nó đi lên NỀN TRANG (chữ, icon rời, viền, vạch active, dấu chọn)
@@ -66,6 +78,7 @@ interface AppDescriptor {
66
78
  * **6,24:1** trên `--sidebar`, **5,96:1** trên `--sidebar-accent`. Bỏ trống trường này mà
67
79
  * vẫn đổi `accent` thì mọi chỗ dùng `text-app-accent-ink` giữ nguyên gold — sai màu app.
68
80
  */
81
+ /** @deprecated Từ 1.10.0 — dùng `tone`. */
69
82
  accentInk?: string;
70
83
  /** `false` = workspace chưa bật/chưa mua → hiện khoá và gọi `onUpsell` thay vì điều hướng. */
71
84
  entitled?: boolean;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/types.ts"],"sourcesContent":["/**\n * Hợp đồng dữ liệu của Tier 3 (shell).\n *\n * Nguyên tắc: package KHÔNG chứa dữ liệu app. Toàn bộ workspace / danh sách app /\n * menu / user đều là *type* — app truyền vào qua props. File này là **module thuần**\n * (chỉ type + hằng nhãn, không hook, không directive) nên dùng được ở cả server lẫn client.\n *\n * Hai trục điều hướng của Comitor — một user thuộc NHIỀU workspace, và mỗi workspace\n * lại bật NHIỀU sản phẩm. Hai chiều đó vuông góc nhau (đổi workspace không đổi app đang\n * dùng, và ngược lại) nên không gộp được vào một menu phẳng:\n * trục 1 — WORKSPACE: đổi ngữ cảnh dữ liệu (Acme Corp ▸ Beta Ltd)\n * trục 2 — APP: đổi sản phẩm trong cùng workspace (Tasks ▸ Chat ▸ CRM ▸ HR)\n */\n\nimport type { LucideIcon } from \"lucide-react\";\nimport type { AnchorHTMLAttributes, ComponentType } from \"react\";\n\n/**\n * Icon dùng trong shell: `LucideIcon` là trường hợp phổ biến, nhưng app có thể\n * truyền component SVG riêng miễn nhận `className` (để shell điều khiển kích thước).\n */\nexport type ShellIcon = LucideIcon | ComponentType<{ className?: string }>;\n\n/** Workspace (trục 1) — một tổ chức/không gian làm việc mà user thuộc về. */\nexport interface Workspace {\n id: string;\n name: string;\n /** Định danh trên URL/subdomain — shell chỉ hiển thị, không tự điều hướng. */\n slug: string;\n logoUrl?: string;\n /** \"Free\" | \"Pro\" | \"Business\"… — chỉ để hiển thị nhãn gói, shell không suy luận quyền từ đây. */\n plan?: string;\n /** Vai trò của user trong workspace này (chuỗi hiển thị sẵn). */\n role?: string;\n memberCount?: number;\n}\n\n/** Một sản phẩm trong hệ sinh thái (trục 2) — Tasks, Chat, CRM, HR… */\nexport interface AppDescriptor {\n /** \"tasks\" | \"chat\" | \"crm\" | \"hr\" | … */\n id: string;\n /** Tên hiển thị (mặc định tiếng Việt). */\n name: string;\n description?: string;\n icon: ShellIcon;\n href: string;\n /**\n * Màu nhấn riêng của app — bất kỳ giá trị màu CSS hợp lệ (nên là `var(--…)`).\n * Shell gán vào biến `--app-accent` bằng inline style, KHÔNG sinh class động.\n *\n * ⚠ `--app-accent` CHỈ để TÔ NỀN. Khai `accent` thì phải khai kèm `accentInk`\n * (và `accentForeground` nếu nền đặc của bạn không mang được mực) — xem\n * `docs/hop-dong-mau.md` §5.\n */\n accent?: string;\n /** Màu chữ/icon nằm TRÊN nền `--app-accent` ĐẶC; mặc định giữ `--app-accent-foreground` của theme. */\n accentForeground?: string;\n /**\n * Chính màu app khi nó đi lên NỀN TRANG (chữ, icon rời, viền, vạch active, dấu chọn)\n * → biến `--app-accent-ink`. Mặc định giữ bậc ink của theme.\n *\n * Vì sao phải khai riêng chứ không dẫn xuất từ `accent`: bậc nền và bậc mực có ràng buộc\n * NGƯỢC NHAU — nền càng đúng brand thì cùng màu đó làm chữ càng chìm. Gold mặc định là ví\n * dụ: `--app-accent` #E8B824 làm chữ trên nền trắng chỉ **1,86:1**, trên chính tint 15%\n * của nó **1,69:1**; `--app-accent-ink` #7A5800 đạt **6,51:1** trên `--background`,\n * **6,24:1** trên `--sidebar`, **5,96:1** trên `--sidebar-accent`. Bỏ trống trường này mà\n * vẫn đổi `accent` thì mọi chỗ dùng `text-app-accent-ink` giữ nguyên gold — sai màu app.\n */\n accentInk?: string;\n /** `false` = workspace chưa bật/chưa mua → hiện khoá và gọi `onUpsell` thay vì điều hướng. */\n entitled?: boolean;\n badge?: number | string;\n}\n\n/** Variant badge của mục menu — map thẳng sang variant của primitive `Badge`. */\nexport type NavBadgeVariant = \"default\" | \"destructive\" | \"warning\" | \"success\";\n\n/** Một mục điều hướng trong sidebar. */\nexport interface NavItem {\n id: string;\n label: string;\n href: string;\n icon?: ShellIcon;\n badge?: number | string;\n badgeVariant?: NavBadgeVariant;\n /** Mục con — render trong `Collapsible`; cha có children thì click cha chỉ mở/đóng. */\n children?: NavItem[];\n /**\n * Ép trạng thái active thay vì để shell tự suy từ `pathname`\n * (dùng khi route thật khác href, vd trang chi tiết `/tasks/123`).\n */\n isActive?: boolean;\n /** Mở link ở tab mới (link ngoài: tài liệu, trạng thái hệ thống…). */\n external?: boolean;\n disabled?: boolean;\n}\n\n/** Nhóm mục menu (Linear-style: nhãn nhỏ + thu gọn được). */\nexport interface NavGroup {\n id: string;\n /** Bỏ trống = nhóm không nhãn (dùng cho nhóm đầu tiên). */\n label?: string;\n items: NavItem[];\n /** Cho phép thu gọn cả nhóm; mặc định true khi có `label`. */\n collapsible?: boolean;\n /** Trạng thái ban đầu khi `collapsible` — mặc định mở. */\n defaultCollapsed?: boolean;\n}\n\n/**\n * Menu app truyền vào: mảng phẳng `NavItem[]` (app đơn giản) hoặc `NavGroup[]`.\n * Shell chuẩn hoá về `NavGroup[]` bằng `toNavGroups()` trong `utils.ts`.\n */\nexport type ShellNav = NavItem[] | NavGroup[];\n\n/** Người dùng đang đăng nhập. */\nexport interface ShellUser {\n id: string;\n name: string;\n email?: string;\n avatarUrl?: string;\n /** Vai trò trong workspace hiện tại — chuỗi hiển thị sẵn, shell không map mã. */\n role?: string;\n}\n\n/** Mục bổ sung do app chèn vào `UserMenu`. */\nexport interface UserMenuItem {\n id: string;\n label: string;\n icon?: ShellIcon;\n /** Có `href` thì render link; không thì render nút gọi `onSelect`. */\n href?: string;\n onSelect?: () => void;\n /** Nhãn phím tắt hiển thị bên phải, vd \"Ctrl+P\" (chỉ hiển thị, không tự gắn phím). */\n shortcut?: string;\n variant?: \"default\" | \"destructive\";\n}\n\n/**\n * Props của component link do app cung cấp (`LinkComponent`).\n * Mặc định shell dùng `next/link`; app không-Next (Vite, Storybook) truyền\n * component router của mình miễn khớp hình dạng props này.\n */\nexport interface ShellLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\"> {\n /** Chỉ nhận chuỗi — shell không dùng dạng `UrlObject` của Next để adapter khác còn dùng lại được. */\n href: string;\n}\n\n/** Component link thay thế `next/link`. */\nexport type ShellLinkComponent = ComponentType<ShellLinkProps>;\n\n/**\n * Toàn bộ chuỗi hiển thị của shell — app tiếng Anh (hoặc ngôn ngữ khác) override\n * từng khoá qua prop `labels`. Mặc định là tiếng Việt.\n */\nexport interface ShellLabels {\n /* Sidebar & header */\n collapseSidebar: string;\n /**\n * Nhãn NGẮN hiện trên chính nút thu gọn (khi thanh bên đang mở rộng).\n *\n * Tách khỏi `collapseSidebar` vì hai chuỗi có hai việc khác nhau: chuỗi dài là TÊN TRUY CẬP\n * (`aria-label` + tooltip) — nó phải nói rõ thu gọn cái gì, vì người dùng trình đọc màn hình\n * nghe nó tách khỏi ngữ cảnh; chuỗi ngắn là chữ NHÌN THẤY trong một nút rộng 240px nằm ngay\n * dưới thanh bên, nơi ngữ cảnh đã hiển nhiên và chữ dài chỉ làm nút chật.\n *\n * ⚠ WCAG 2.5.3 (Label in Name): chuỗi ngắn PHẢI là một phần của chuỗi dài, nếu không lệnh thoại\n * \"bấm Thu gọn\" sẽ không khớp tên truy cập. \"Thu gọn\" ⊂ \"Thu gọn thanh bên\" ✓ — giữ quan hệ đó\n * khi dịch sang ngôn ngữ khác.\n */\n collapseSidebarShort: string;\n expandSidebar: string;\n openMenu: string;\n closeMenu: string;\n search: string;\n searchPlaceholder: string;\n notifications: string;\n navigationLabel: string;\n /* Workspace switcher */\n workspace: string;\n workspaceSwitcherLabel: string;\n workspaceSearchPlaceholder: string;\n workspaceEmpty: string;\n createWorkspace: string;\n workspaceSettings: string;\n inviteMembers: string;\n /**\n * Đơn vị đếm thành viên của một workspace. Là HÀM chứ không phải chuỗi vì nó CHÈN SỐ, và\n * trật tự từ đổi theo ngôn ngữ: tiếng Việt đặt số trước (\"12 thành viên\"), tiếng Anh cũng vậy\n * (\"12 members\"), nhưng nhiều ngôn ngữ thì không — nối `${count} ${đơn_vị}` ở phía component\n * là khoá cứng trật tự tiếng Việt vào mọi bản dịch. Cùng khuôn với `range`/`pageStatus` của\n * `TablePagination` (README §5, ba quy tắc của cơ chế `labels`).\n */\n members: (count: number) => string;\n /* App launcher */\n apps: string;\n appLauncherLabel: string;\n myApps: string;\n discoverApps: string;\n locked: string;\n lockedHint: string;\n /* User menu */\n account: string;\n profile: string;\n settings: string;\n appearance: string;\n help: string;\n logout: string;\n /* Theme */\n themeLight: string;\n themeDark: string;\n themeSystem: string;\n toggleTheme: string;\n}\n\n/** Nhãn tiếng Việt mặc định — mọi khoá đều override được qua prop `labels`. */\nexport const DEFAULT_SHELL_LABELS: ShellLabels = {\n collapseSidebar: \"Thu gọn thanh bên\",\n collapseSidebarShort: \"Thu gọn\",\n expandSidebar: \"Mở rộng thanh bên\",\n openMenu: \"Mở menu\",\n closeMenu: \"Đóng menu\",\n search: \"Tìm kiếm\",\n searchPlaceholder: \"Tìm kiếm…\",\n notifications: \"Thông báo\",\n navigationLabel: \"Điều hướng chính\",\n workspace: \"Không gian làm việc\",\n workspaceSwitcherLabel: \"Đổi không gian làm việc\",\n workspaceSearchPlaceholder: \"Tìm không gian làm việc…\",\n workspaceEmpty: \"Không tìm thấy không gian làm việc\",\n createWorkspace: \"Tạo không gian làm việc\",\n workspaceSettings: \"Cài đặt không gian làm việc\",\n inviteMembers: \"Mời thành viên\",\n members: (count) => `${count} thành viên`,\n apps: \"Ứng dụng\",\n appLauncherLabel: \"Mở danh sách ứng dụng\",\n myApps: \"Ứng dụng của bạn\",\n discoverApps: \"Khám phá thêm\",\n locked: \"Chưa mở khoá\",\n lockedHint: \"Ứng dụng chưa có trong gói hiện tại\",\n account: \"Tài khoản\",\n profile: \"Hồ sơ\",\n settings: \"Cài đặt\",\n appearance: \"Giao diện\",\n help: \"Trợ giúp\",\n logout: \"Đăng xuất\",\n themeLight: \"Sáng\",\n themeDark: \"Tối\",\n themeSystem: \"Hệ thống\",\n toggleTheme: \"Đổi giao diện\"\n};\n"],"mappings":"AAwNO,MAAM,uBAAoC;AAAA,EAC/C,iBAAiB;AAAA,EACjB,sBAAsB;AAAA,EACtB,eAAe;AAAA,EACf,UAAU;AAAA,EACV,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,wBAAwB;AAAA,EACxB,4BAA4B;AAAA,EAC5B,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,SAAS,CAAC,UAAU,GAAG,KAAK;AAAA,EAC5B,MAAM;AAAA,EACN,kBAAkB;AAAA,EAClB,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AACf;","names":[]}
1
+ {"version":3,"sources":["../../src/shell/types.ts"],"sourcesContent":["/**\n * Hợp đồng dữ liệu của Tier 3 (shell).\n *\n * Nguyên tắc: package KHÔNG chứa dữ liệu app. Toàn bộ workspace / danh sách app /\n * menu / user đều là *type* — app truyền vào qua props. File này là **module thuần**\n * (chỉ type + hằng nhãn, không hook, không directive) nên dùng được ở cả server lẫn client.\n *\n * Hai trục điều hướng của Comitor — một user thuộc NHIỀU workspace, và mỗi workspace\n * lại bật NHIỀU sản phẩm. Hai chiều đó vuông góc nhau (đổi workspace không đổi app đang\n * dùng, và ngược lại) nên không gộp được vào một menu phẳng:\n * trục 1 — WORKSPACE: đổi ngữ cảnh dữ liệu (Acme Corp ▸ Beta Ltd)\n * trục 2 — APP: đổi sản phẩm trong cùng workspace (Tasks ▸ Chat ▸ CRM ▸ HR)\n */\n\nimport type { LucideIcon } from \"lucide-react\";\nimport type { AnchorHTMLAttributes, ComponentType } from \"react\";\n\n/**\n * Icon dùng trong shell: `LucideIcon` là trường hợp phổ biến, nhưng app có thể\n * truyền component SVG riêng miễn nhận `className` (để shell điều khiển kích thước).\n */\nexport type ShellIcon = LucideIcon | ComponentType<{ className?: string }>;\n\n/** Workspace (trục 1) — một tổ chức/không gian làm việc mà user thuộc về. */\nexport interface Workspace {\n id: string;\n name: string;\n /** Định danh trên URL/subdomain — shell chỉ hiển thị, không tự điều hướng. */\n slug: string;\n logoUrl?: string;\n /** \"Free\" | \"Pro\" | \"Business\"… — chỉ để hiển thị nhãn gói, shell không suy luận quyền từ đây. */\n plan?: string;\n /** Vai trò của user trong workspace này (chuỗi hiển thị sẵn). */\n role?: string;\n memberCount?: number;\n}\n\n/** Một sản phẩm trong hệ sinh thái (trục 2) — Tasks, Chat, CRM, HR… */\nexport interface AppDescriptor {\n /** \"tasks\" | \"chat\" | \"crm\" | \"hr\" | … */\n id: string;\n /** Tên hiển thị (mặc định tiếng Việt). */\n name: string;\n description?: string;\n icon: ShellIcon;\n href: string;\n /**\n * Tone màu định danh — MỘT tên trong `AVATAR_TONE_NAMES` của `@comitor/ui`.\n *\n * Bỏ trống thì màu TỰ SUY từ `id` bằng bảng tone dùng chung với `LetterAvatar`, nên một sản\n * phẩm mới có màu ngay mà không ai phải cấu hình. Tên lạ cũng rơi về màu suy ra — không ném.\n *\n * ⚠ Hạt giống là `id` (khoá app), KHÔNG phải `name`: tên đổi theo ngôn ngữ giao diện, và đo\n * được 4/6 app đổi màu khi chuyển vi ⇄ en nếu băm theo tên. Xem `IconAvatar` prop `seed`.\n */\n tone?: string;\n /**\n * Màu nhấn riêng của app — bất kỳ giá trị màu CSS hợp lệ (nên là `var(--…)`).\n * Shell gán vào biến `--app-accent` bằng inline style, KHÔNG sinh class động.\n *\n * ⚠ `--app-accent` CHỈ để TÔ NỀN. Khai `accent` thì phải khai kèm `accentInk`\n * (và `accentForeground` nếu nền đặc của bạn không mang được mực) — xem\n * `docs/hop-dong-mau.md` §5.\n */\n /** @deprecated Từ 1.10.0 `AppLauncher` KHÔNG đọc prop này nữa — dùng `tone`. Xem ghi chú trên. */\n accent?: string;\n /** Màu chữ/icon nằm TRÊN nền `--app-accent` ĐẶC; mặc định giữ `--app-accent-foreground` của theme. */\n /** @deprecated Từ 1.10.0 — dùng `tone`. */\n accentForeground?: string;\n /**\n * Chính màu app khi nó đi lên NỀN TRANG (chữ, icon rời, viền, vạch active, dấu chọn)\n * → biến `--app-accent-ink`. Mặc định giữ bậc ink của theme.\n *\n * Vì sao phải khai riêng chứ không dẫn xuất từ `accent`: bậc nền và bậc mực có ràng buộc\n * NGƯỢC NHAU — nền càng đúng brand thì cùng màu đó làm chữ càng chìm. Gold mặc định là ví\n * dụ: `--app-accent` #E8B824 làm chữ trên nền trắng chỉ **1,86:1**, trên chính tint 15%\n * của nó **1,69:1**; `--app-accent-ink` #7A5800 đạt **6,51:1** trên `--background`,\n * **6,24:1** trên `--sidebar`, **5,96:1** trên `--sidebar-accent`. Bỏ trống trường này mà\n * vẫn đổi `accent` thì mọi chỗ dùng `text-app-accent-ink` giữ nguyên gold — sai màu app.\n */\n /** @deprecated Từ 1.10.0 — dùng `tone`. */\n accentInk?: string;\n /** `false` = workspace chưa bật/chưa mua → hiện khoá và gọi `onUpsell` thay vì điều hướng. */\n entitled?: boolean;\n badge?: number | string;\n}\n\n/** Variant badge của mục menu — map thẳng sang variant của primitive `Badge`. */\nexport type NavBadgeVariant = \"default\" | \"destructive\" | \"warning\" | \"success\";\n\n/** Một mục điều hướng trong sidebar. */\nexport interface NavItem {\n id: string;\n label: string;\n href: string;\n icon?: ShellIcon;\n badge?: number | string;\n badgeVariant?: NavBadgeVariant;\n /** Mục con — render trong `Collapsible`; cha có children thì click cha chỉ mở/đóng. */\n children?: NavItem[];\n /**\n * Ép trạng thái active thay vì để shell tự suy từ `pathname`\n * (dùng khi route thật khác href, vd trang chi tiết `/tasks/123`).\n */\n isActive?: boolean;\n /** Mở link ở tab mới (link ngoài: tài liệu, trạng thái hệ thống…). */\n external?: boolean;\n disabled?: boolean;\n}\n\n/** Nhóm mục menu (Linear-style: nhãn nhỏ + thu gọn được). */\nexport interface NavGroup {\n id: string;\n /** Bỏ trống = nhóm không nhãn (dùng cho nhóm đầu tiên). */\n label?: string;\n items: NavItem[];\n /** Cho phép thu gọn cả nhóm; mặc định true khi có `label`. */\n collapsible?: boolean;\n /** Trạng thái ban đầu khi `collapsible` — mặc định mở. */\n defaultCollapsed?: boolean;\n}\n\n/**\n * Menu app truyền vào: mảng phẳng `NavItem[]` (app đơn giản) hoặc `NavGroup[]`.\n * Shell chuẩn hoá về `NavGroup[]` bằng `toNavGroups()` trong `utils.ts`.\n */\nexport type ShellNav = NavItem[] | NavGroup[];\n\n/** Người dùng đang đăng nhập. */\nexport interface ShellUser {\n id: string;\n name: string;\n email?: string;\n avatarUrl?: string;\n /** Vai trò trong workspace hiện tại — chuỗi hiển thị sẵn, shell không map mã. */\n role?: string;\n}\n\n/** Mục bổ sung do app chèn vào `UserMenu`. */\nexport interface UserMenuItem {\n id: string;\n label: string;\n icon?: ShellIcon;\n /** Có `href` thì render link; không thì render nút gọi `onSelect`. */\n href?: string;\n onSelect?: () => void;\n /** Nhãn phím tắt hiển thị bên phải, vd \"Ctrl+P\" (chỉ hiển thị, không tự gắn phím). */\n shortcut?: string;\n variant?: \"default\" | \"destructive\";\n}\n\n/**\n * Props của component link do app cung cấp (`LinkComponent`).\n * Mặc định shell dùng `next/link`; app không-Next (Vite, Storybook) truyền\n * component router của mình miễn khớp hình dạng props này.\n */\nexport interface ShellLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\"> {\n /** Chỉ nhận chuỗi — shell không dùng dạng `UrlObject` của Next để adapter khác còn dùng lại được. */\n href: string;\n}\n\n/** Component link thay thế `next/link`. */\nexport type ShellLinkComponent = ComponentType<ShellLinkProps>;\n\n/**\n * Toàn bộ chuỗi hiển thị của shell — app tiếng Anh (hoặc ngôn ngữ khác) override\n * từng khoá qua prop `labels`. Mặc định là tiếng Việt.\n */\nexport interface ShellLabels {\n /* Sidebar & header */\n collapseSidebar: string;\n /**\n * Nhãn NGẮN hiện trên chính nút thu gọn (khi thanh bên đang mở rộng).\n *\n * Tách khỏi `collapseSidebar` vì hai chuỗi có hai việc khác nhau: chuỗi dài là TÊN TRUY CẬP\n * (`aria-label` + tooltip) — nó phải nói rõ thu gọn cái gì, vì người dùng trình đọc màn hình\n * nghe nó tách khỏi ngữ cảnh; chuỗi ngắn là chữ NHÌN THẤY trong một nút rộng 240px nằm ngay\n * dưới thanh bên, nơi ngữ cảnh đã hiển nhiên và chữ dài chỉ làm nút chật.\n *\n * ⚠ WCAG 2.5.3 (Label in Name): chuỗi ngắn PHẢI là một phần của chuỗi dài, nếu không lệnh thoại\n * \"bấm Thu gọn\" sẽ không khớp tên truy cập. \"Thu gọn\" ⊂ \"Thu gọn thanh bên\" ✓ — giữ quan hệ đó\n * khi dịch sang ngôn ngữ khác.\n */\n collapseSidebarShort: string;\n expandSidebar: string;\n openMenu: string;\n closeMenu: string;\n search: string;\n searchPlaceholder: string;\n notifications: string;\n navigationLabel: string;\n /* Workspace switcher */\n workspace: string;\n workspaceSwitcherLabel: string;\n workspaceSearchPlaceholder: string;\n workspaceEmpty: string;\n createWorkspace: string;\n workspaceSettings: string;\n inviteMembers: string;\n /**\n * Đơn vị đếm thành viên của một workspace. Là HÀM chứ không phải chuỗi vì nó CHÈN SỐ, và\n * trật tự từ đổi theo ngôn ngữ: tiếng Việt đặt số trước (\"12 thành viên\"), tiếng Anh cũng vậy\n * (\"12 members\"), nhưng nhiều ngôn ngữ thì không — nối `${count} ${đơn_vị}` ở phía component\n * là khoá cứng trật tự tiếng Việt vào mọi bản dịch. Cùng khuôn với `range`/`pageStatus` của\n * `TablePagination` (README §5, ba quy tắc của cơ chế `labels`).\n */\n members: (count: number) => string;\n /* App launcher */\n apps: string;\n appLauncherLabel: string;\n myApps: string;\n discoverApps: string;\n locked: string;\n lockedHint: string;\n /* User menu */\n account: string;\n profile: string;\n settings: string;\n appearance: string;\n help: string;\n logout: string;\n /* Theme */\n themeLight: string;\n themeDark: string;\n themeSystem: string;\n toggleTheme: string;\n}\n\n/** Nhãn tiếng Việt mặc định — mọi khoá đều override được qua prop `labels`. */\nexport const DEFAULT_SHELL_LABELS: ShellLabels = {\n collapseSidebar: \"Thu gọn thanh bên\",\n collapseSidebarShort: \"Thu gọn\",\n expandSidebar: \"Mở rộng thanh bên\",\n openMenu: \"Mở menu\",\n closeMenu: \"Đóng menu\",\n search: \"Tìm kiếm\",\n searchPlaceholder: \"Tìm kiếm…\",\n notifications: \"Thông báo\",\n navigationLabel: \"Điều hướng chính\",\n workspace: \"Không gian làm việc\",\n workspaceSwitcherLabel: \"Đổi không gian làm việc\",\n workspaceSearchPlaceholder: \"Tìm không gian làm việc…\",\n workspaceEmpty: \"Không tìm thấy không gian làm việc\",\n createWorkspace: \"Tạo không gian làm việc\",\n workspaceSettings: \"Cài đặt không gian làm việc\",\n inviteMembers: \"Mời thành viên\",\n members: (count) => `${count} thành viên`,\n apps: \"Ứng dụng\",\n appLauncherLabel: \"Mở danh sách ứng dụng\",\n myApps: \"Ứng dụng của bạn\",\n discoverApps: \"Khám phá thêm\",\n locked: \"Chưa mở khoá\",\n lockedHint: \"Ứng dụng chưa có trong gói hiện tại\",\n account: \"Tài khoản\",\n profile: \"Hồ sơ\",\n settings: \"Cài đặt\",\n appearance: \"Giao diện\",\n help: \"Trợ giúp\",\n logout: \"Đăng xuất\",\n themeLight: \"Sáng\",\n themeDark: \"Tối\",\n themeSystem: \"Hệ thống\",\n toggleTheme: \"Đổi giao diện\"\n};\n"],"mappings":"AAqOO,MAAM,uBAAoC;AAAA,EAC/C,iBAAiB;AAAA,EACjB,sBAAsB;AAAA,EACtB,eAAe;AAAA,EACf,UAAU;AAAA,EACV,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,wBAAwB;AAAA,EACxB,4BAA4B;AAAA,EAC5B,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,SAAS,CAAC,UAAU,GAAG,KAAK;AAAA,EAC5B,MAAM;AAAA,EACN,kBAAkB;AAAA,EAClB,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AACf;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comitor/ui",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Comitor design system — Shadcn/UI tùy biến theo brand Comitor + component dùng chung cho hệ sinh thái SaaS (Tasks, Chat, CRM, HR…)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -42,6 +42,10 @@
42
42
  "types": "./dist/chart/index.d.ts",
43
43
  "import": "./dist/chart/index.js"
44
44
  },
45
+ "./data-grid": {
46
+ "types": "./dist/data-grid/index.d.ts",
47
+ "import": "./dist/data-grid/index.js"
48
+ },
45
49
  "./uploader": {
46
50
  "types": "./dist/uploader/index.d.ts",
47
51
  "import": "./dist/uploader/index.js"
@@ -57,7 +61,24 @@
57
61
  "./styles.css": "./styles.css",
58
62
  "./package.json": "./package.json"
59
63
  },
64
+ "scripts": {
65
+ "build": "tsup",
66
+ "typecheck": "tsc --noEmit",
67
+ "version:check": "node scripts/check-version.mjs",
68
+ "lint": "biome check --error-on-warnings .",
69
+ "lint:fix": "biome check --write .",
70
+ "lint:a11y": "node scripts/check-contrast.mjs",
71
+ "test:a11y-gate": "node scripts/a11y/self-test.mjs",
72
+ "test:nav-active": "node scripts/check-nav-active.mjs",
73
+ "test:grid-render": "node scripts/check-data-grid-render.mjs",
74
+ "verify:directives": "node scripts/verify-directives.mjs",
75
+ "smoke:entries": "node scripts/smoke-entries.mjs",
76
+ "check:exports": "publint --strict && attw --pack . --profile esm-only --exclude-entrypoints styles.css",
77
+ "prepublishOnly": "pnpm run typecheck && pnpm run build && pnpm run verify:directives && pnpm run smoke:entries && pnpm run check:exports && pnpm run test:nav-active && pnpm run test:grid-contract && pnpm run test:grid-render && pnpm run test:a11y-gate && pnpm run lint:a11y",
78
+ "test:grid-contract": "node scripts/check-grid-contract.mjs"
79
+ },
60
80
  "peerDependencies": {
81
+ "@tanstack/react-table": ">=8.21.0 <9",
61
82
  "@uppy/core": "^6.0.0",
62
83
  "@uppy/xhr-upload": "^6.0.0",
63
84
  "lucide-react": ">=0.540.0",
@@ -70,6 +91,9 @@
70
91
  "tailwindcss": "^4.1.0"
71
92
  },
72
93
  "peerDependenciesMeta": {
94
+ "@tanstack/react-table": {
95
+ "optional": true
96
+ },
73
97
  "@uppy/core": {
74
98
  "optional": true
75
99
  },
@@ -90,6 +114,10 @@
90
114
  }
91
115
  },
92
116
  "dependencies": {
117
+ "@dnd-kit/core": "^6.3.1",
118
+ "@dnd-kit/modifiers": "^9.0.0",
119
+ "@dnd-kit/sortable": "^10.0.0",
120
+ "@dnd-kit/utilities": "^3.2.2",
93
121
  "@radix-ui/react-accordion": "1.2.12",
94
122
  "@radix-ui/react-alert-dialog": "1.1.15",
95
123
  "@radix-ui/react-aspect-ratio": "1.1.8",
@@ -98,6 +126,7 @@
98
126
  "@radix-ui/react-collapsible": "1.1.12",
99
127
  "@radix-ui/react-context-menu": "2.2.16",
100
128
  "@radix-ui/react-dialog": "1.1.15",
129
+ "@radix-ui/react-direction": "1.1.1",
101
130
  "@radix-ui/react-dropdown-menu": "2.1.16",
102
131
  "@radix-ui/react-hover-card": "1.1.15",
103
132
  "@radix-ui/react-label": "2.1.8",
@@ -116,6 +145,7 @@
116
145
  "@radix-ui/react-toggle": "1.1.10",
117
146
  "@radix-ui/react-toggle-group": "1.1.11",
118
147
  "@radix-ui/react-tooltip": "1.2.8",
148
+ "@tanstack/react-virtual": "^3.14.13",
119
149
  "class-variance-authority": "^0.7.1",
120
150
  "clsx": "^2.1.1",
121
151
  "cmdk": "1.1.1",
@@ -132,6 +162,7 @@
132
162
  "devDependencies": {
133
163
  "@arethetypeswrong/cli": "^0.18.4",
134
164
  "@biomejs/biome": "^2.4.11",
165
+ "@tanstack/react-table": "^8.21.3",
135
166
  "@types/node": "^22.0.0",
136
167
  "@types/react": "^19.0.0",
137
168
  "@types/react-dom": "^19.0.0",
@@ -148,18 +179,5 @@
148
179
  "tailwindcss": "^4.2.0",
149
180
  "tsup": "^8.3.5",
150
181
  "typescript": "^5.7.0"
151
- },
152
- "scripts": {
153
- "build": "tsup",
154
- "typecheck": "tsc --noEmit",
155
- "lint": "biome check --error-on-warnings .",
156
- "lint:fix": "biome check --write .",
157
- "lint:a11y": "node scripts/check-contrast.mjs",
158
- "test:a11y-gate": "node scripts/a11y/self-test.mjs",
159
- "test:nav-active": "node scripts/check-nav-active.mjs",
160
- "verify:directives": "node scripts/verify-directives.mjs",
161
- "smoke:entries": "node scripts/smoke-entries.mjs",
162
- "check:exports": "publint --strict && attw --pack . --profile esm-only --exclude-entrypoints styles.css",
163
- "test:grid-contract": "node scripts/check-grid-contract.mjs"
164
182
  }
165
- }
183
+ }