@comitor/ui 1.10.0 → 2.0.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 (110) hide show
  1. package/README.md +24 -5
  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 +1738 -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 +207 -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 +161 -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 +23 -0
  21. package/dist/data-grid/data-grid-filter-menu.js +725 -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 +31 -0
  24. package/dist/data-grid/data-grid-keyboard-shortcuts.js +229 -0
  25. package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -0
  26. package/dist/data-grid/data-grid-locale.d.ts +24 -0
  27. package/dist/data-grid/data-grid-locale.js +25 -0
  28. package/dist/data-grid/data-grid-locale.js.map +1 -0
  29. package/dist/data-grid/data-grid-paste-dialog.d.ts +11 -0
  30. package/dist/data-grid/data-grid-paste-dialog.js +104 -0
  31. package/dist/data-grid/data-grid-paste-dialog.js.map +1 -0
  32. package/dist/data-grid/data-grid-presence.d.ts +14 -0
  33. package/dist/data-grid/data-grid-presence.js +16 -0
  34. package/dist/data-grid/data-grid-presence.js.map +1 -0
  35. package/dist/data-grid/data-grid-row-height-menu.d.ts +25 -0
  36. package/dist/data-grid/data-grid-row-height-menu.js +53 -0
  37. package/dist/data-grid/data-grid-row-height-menu.js.map +1 -0
  38. package/dist/data-grid/data-grid-row.d.ts +28 -0
  39. package/dist/data-grid/data-grid-row.js +206 -0
  40. package/dist/data-grid/data-grid-row.js.map +1 -0
  41. package/dist/data-grid/data-grid-search.d.ts +10 -0
  42. package/dist/data-grid/data-grid-search.js +184 -0
  43. package/dist/data-grid/data-grid-search.js.map +1 -0
  44. package/dist/data-grid/data-grid-select-column.d.ts +12 -0
  45. package/dist/data-grid/data-grid-select-column.js +179 -0
  46. package/dist/data-grid/data-grid-select-column.js.map +1 -0
  47. package/dist/data-grid/data-grid-skeleton.d.ts +13 -0
  48. package/dist/data-grid/data-grid-skeleton.js +52 -0
  49. package/dist/data-grid/data-grid-skeleton.js.map +1 -0
  50. package/dist/data-grid/data-grid-sort-menu.d.ts +23 -0
  51. package/dist/data-grid/data-grid-sort-menu.js +283 -0
  52. package/dist/data-grid/data-grid-sort-menu.js.map +1 -0
  53. package/dist/data-grid/data-grid-view-menu.d.ts +24 -0
  54. package/dist/data-grid/data-grid-view-menu.js +74 -0
  55. package/dist/data-grid/data-grid-view-menu.js.map +1 -0
  56. package/dist/data-grid/data-grid.d.ts +44 -0
  57. package/dist/data-grid/data-grid.js +238 -0
  58. package/dist/data-grid/data-grid.js.map +1 -0
  59. package/dist/data-grid/filters.d.ts +27 -0
  60. package/dist/data-grid/filters.js +233 -0
  61. package/dist/data-grid/filters.js.map +1 -0
  62. package/dist/data-grid/index.d.ts +25 -0
  63. package/dist/data-grid/index.js +95 -0
  64. package/dist/data-grid/index.js.map +1 -0
  65. package/dist/data-grid/messages-en.d.ts +294 -0
  66. package/dist/data-grid/messages-en.js +279 -0
  67. package/dist/data-grid/messages-en.js.map +1 -0
  68. package/dist/data-grid/messages-vi.d.ts +12 -0
  69. package/dist/data-grid/messages-vi.js +279 -0
  70. package/dist/data-grid/messages-vi.js.map +1 -0
  71. package/dist/data-grid/messages.d.ts +567 -0
  72. package/dist/data-grid/messages.js +20 -0
  73. package/dist/data-grid/messages.js.map +1 -0
  74. package/dist/data-grid/sortable.d.ts +50 -0
  75. package/dist/data-grid/sortable.js +361 -0
  76. package/dist/data-grid/sortable.js.map +1 -0
  77. package/dist/data-grid/toast.d.ts +10 -0
  78. package/dist/data-grid/toast.js +21 -0
  79. package/dist/data-grid/toast.js.map +1 -0
  80. package/dist/data-grid/types.d.ts +189 -0
  81. package/dist/data-grid/types.js +1 -0
  82. package/dist/data-grid/types.js.map +1 -0
  83. package/dist/data-grid/use-as-ref.d.ts +5 -0
  84. package/dist/data-grid/use-as-ref.js +13 -0
  85. package/dist/data-grid/use-as-ref.js.map +1 -0
  86. package/dist/data-grid/use-badge-overflow.d.ts +24 -0
  87. package/dist/data-grid/use-badge-overflow.js +123 -0
  88. package/dist/data-grid/use-badge-overflow.js.map +1 -0
  89. package/dist/data-grid/use-callback-ref.d.ts +10 -0
  90. package/dist/data-grid/use-callback-ref.js +12 -0
  91. package/dist/data-grid/use-callback-ref.js.map +1 -0
  92. package/dist/data-grid/use-data-grid-undo-redo.d.ts +45 -0
  93. package/dist/data-grid/use-data-grid-undo-redo.js +348 -0
  94. package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -0
  95. package/dist/data-grid/use-data-grid.d.ts +372 -0
  96. package/dist/data-grid/use-data-grid.js +2697 -0
  97. package/dist/data-grid/use-data-grid.js.map +1 -0
  98. package/dist/data-grid/use-debounced-callback.d.ts +6 -0
  99. package/dist/data-grid/use-debounced-callback.js +19 -0
  100. package/dist/data-grid/use-debounced-callback.js.map +1 -0
  101. package/dist/data-grid/use-isomorphic-layout-effect.d.ts +5 -0
  102. package/dist/data-grid/use-isomorphic-layout-effect.js +6 -0
  103. package/dist/data-grid/use-isomorphic-layout-effect.js.map +1 -0
  104. package/dist/data-grid/use-lazy-ref.d.ts +5 -0
  105. package/dist/data-grid/use-lazy-ref.js +12 -0
  106. package/dist/data-grid/use-lazy-ref.js.map +1 -0
  107. package/dist/data-grid/utils.d.ts +81 -0
  108. package/dist/data-grid/utils.js +409 -0
  109. package/dist/data-grid/utils.js.map +1 -0
  110. package/package.json +33 -15
@@ -0,0 +1,207 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { ChevronDownIcon, ChevronUpIcon, EyeOffIcon, PinIcon, PinOffIcon, XIcon } from "lucide-react";
4
+ import * as React from "react";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuCheckboxItem,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger
12
+ } from "../components/ui/dropdown-menu.js";
13
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../components/ui/tooltip.js";
14
+ import { cn } from "../lib/cn.js";
15
+ import { useDataGridMessages } from "./data-grid-locale.js";
16
+ import { getColumnVariant } from "./utils.js";
17
+ function DataGridColumnHeader({
18
+ header,
19
+ table,
20
+ className,
21
+ onPointerDown,
22
+ ...props
23
+ }) {
24
+ const msg = useDataGridMessages();
25
+ const column = header.column;
26
+ const label = column.columnDef.meta?.label ? column.columnDef.meta.label : typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
27
+ const isAnyColumnResizing = table.getState().columnSizingInfo.isResizingColumn;
28
+ const cellVariant = column.columnDef.meta?.cell;
29
+ const columnVariant = getColumnVariant(cellVariant?.variant);
30
+ const pinnedPosition = column.getIsPinned();
31
+ const isPinnedLeft = pinnedPosition === "left";
32
+ const isPinnedRight = pinnedPosition === "right";
33
+ const onSortingChange = React.useCallback(
34
+ (direction) => {
35
+ table.setSorting((prev) => {
36
+ const existingSortIndex = prev.findIndex((sort) => sort.id === column.id);
37
+ const newSort = {
38
+ id: column.id,
39
+ desc: direction === "desc"
40
+ };
41
+ if (existingSortIndex >= 0) {
42
+ const updated = [...prev];
43
+ updated[existingSortIndex] = newSort;
44
+ return updated;
45
+ } else {
46
+ return [...prev, newSort];
47
+ }
48
+ });
49
+ },
50
+ [column.id, table]
51
+ );
52
+ const onSortRemove = React.useCallback(() => {
53
+ table.setSorting((prev) => prev.filter((sort) => sort.id !== column.id));
54
+ }, [column.id, table]);
55
+ const onLeftPin = React.useCallback(() => {
56
+ column.pin("left");
57
+ }, [column]);
58
+ const onRightPin = React.useCallback(() => {
59
+ column.pin("right");
60
+ }, [column]);
61
+ const onUnpin = React.useCallback(() => {
62
+ column.pin(false);
63
+ }, [column]);
64
+ const onTriggerPointerDown = React.useCallback(
65
+ (event) => {
66
+ onPointerDown?.(event);
67
+ if (event.defaultPrevented) return;
68
+ if (event.button !== 0) {
69
+ return;
70
+ }
71
+ table.options.meta?.onColumnClick?.(column.id);
72
+ },
73
+ [table.options.meta, column.id, onPointerDown]
74
+ );
75
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
76
+ /* @__PURE__ */ jsxs(DropdownMenu, { modal: false, children: [
77
+ /* @__PURE__ */ jsxs(
78
+ DropdownMenuTrigger,
79
+ {
80
+ className: cn(
81
+ "flex size-full items-center justify-between gap-2 p-2 text-sm hover:bg-accent/40 data-[state=open]:bg-accent/40 [&_svg]:size-4",
82
+ isAnyColumnResizing && "pointer-events-none",
83
+ className
84
+ ),
85
+ onPointerDown: onTriggerPointerDown,
86
+ ...props,
87
+ children: [
88
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [
89
+ columnVariant && /* @__PURE__ */ jsxs(Tooltip, { delayDuration: 100, children: [
90
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(columnVariant.icon, { className: "size-3.5 shrink-0 text-muted-foreground" }) }),
91
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: columnVariant.label })
92
+ ] }),
93
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
94
+ ] }),
95
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3.5 shrink-0 text-muted-foreground" })
96
+ ]
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "start", sideOffset: 0, className: "w-60", children: [
100
+ column.getCanSort() && /* @__PURE__ */ jsxs(Fragment, { children: [
101
+ /* @__PURE__ */ jsxs(
102
+ DropdownMenuCheckboxItem,
103
+ {
104
+ className: "gap-2 [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground",
105
+ checked: column.getIsSorted() === "asc",
106
+ onSelect: () => onSortingChange("asc"),
107
+ children: [
108
+ /* @__PURE__ */ jsx(ChevronUpIcon, {}),
109
+ msg.column.sortAsc
110
+ ]
111
+ }
112
+ ),
113
+ /* @__PURE__ */ jsxs(
114
+ DropdownMenuCheckboxItem,
115
+ {
116
+ className: "gap-2 [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground",
117
+ checked: column.getIsSorted() === "desc",
118
+ onSelect: () => onSortingChange("desc"),
119
+ children: [
120
+ /* @__PURE__ */ jsx(ChevronDownIcon, {}),
121
+ msg.column.sortDesc
122
+ ]
123
+ }
124
+ ),
125
+ column.getIsSorted() && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onSortRemove, children: [
126
+ /* @__PURE__ */ jsx(XIcon, {}),
127
+ msg.column.removeSort
128
+ ] })
129
+ ] }),
130
+ column.getCanPin() && /* @__PURE__ */ jsxs(Fragment, { children: [
131
+ column.getCanSort() && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
132
+ isPinnedLeft ? /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "[&_svg]:text-muted-foreground", onSelect: onUnpin, children: [
133
+ /* @__PURE__ */ jsx(PinOffIcon, {}),
134
+ msg.column.unpinLeft
135
+ ] }) : /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "[&_svg]:text-muted-foreground", onSelect: onLeftPin, children: [
136
+ /* @__PURE__ */ jsx(PinIcon, {}),
137
+ msg.column.pinLeft
138
+ ] }),
139
+ isPinnedRight ? /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "[&_svg]:text-muted-foreground", onSelect: onUnpin, children: [
140
+ /* @__PURE__ */ jsx(PinOffIcon, {}),
141
+ msg.column.unpinRight
142
+ ] }) : /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "[&_svg]:text-muted-foreground", onSelect: onRightPin, children: [
143
+ /* @__PURE__ */ jsx(PinIcon, {}),
144
+ msg.column.pinRight
145
+ ] })
146
+ ] }),
147
+ column.getCanHide() && /* @__PURE__ */ jsxs(Fragment, { children: [
148
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
149
+ /* @__PURE__ */ jsxs(
150
+ DropdownMenuItem,
151
+ {
152
+ className: "[&_svg]:text-muted-foreground",
153
+ onSelect: () => column.toggleVisibility(false),
154
+ children: [
155
+ /* @__PURE__ */ jsx(EyeOffIcon, {}),
156
+ msg.column.hide
157
+ ]
158
+ }
159
+ )
160
+ ] })
161
+ ] })
162
+ ] }),
163
+ header.column.getCanResize() && /* @__PURE__ */ jsx(DataGridColumnResizer, { header, table, label })
164
+ ] });
165
+ }
166
+ const DataGridColumnResizer = React.memo(DataGridColumnResizerImpl, (prev, next) => {
167
+ const prevColumn = prev.header.column;
168
+ const nextColumn = next.header.column;
169
+ if (prevColumn.getIsResizing() !== nextColumn.getIsResizing() || prevColumn.getSize() !== nextColumn.getSize()) {
170
+ return false;
171
+ }
172
+ if (prev.label !== next.label) return false;
173
+ return true;
174
+ });
175
+ function DataGridColumnResizerImpl({ header, table, label }) {
176
+ const msg = useDataGridMessages();
177
+ const defaultColumnDef = table._getDefaultColumnDef();
178
+ const onDoubleClick = React.useCallback(() => {
179
+ header.column.resetSize();
180
+ }, [header.column]);
181
+ return /* @__PURE__ */ jsx(
182
+ "div",
183
+ {
184
+ role: "separator",
185
+ "aria-orientation": "vertical",
186
+ "aria-label": msg.column.resize(label),
187
+ "aria-valuenow": header.column.getSize(),
188
+ "aria-valuemin": defaultColumnDef.minSize,
189
+ "aria-valuemax": defaultColumnDef.maxSize,
190
+ tabIndex: 0,
191
+ className: cn(
192
+ // Tay kéo chỉ hiện khi rê/focus/đang kéo, nên nó CHỈ mang đúng một màu — `--primary-ink`
193
+ // (bậc NÉT). `--primary` trần là bậc tô NỀN: gold #E8B824 chỉ 1,73:1 với nền trang ở
194
+ // light, tức một tay kéo nhìn thấy được ở dark mà gần như vô hình ở light.
195
+ "absolute -end-px top-0 z-50 h-full w-0.5 cursor-ew-resize touch-none select-none bg-primary-ink transition-opacity after:absolute after:inset-y-0 after:start-1/2 after:h-full after:w-[18px] after:-translate-x-1/2 after:content-[''] focus:outline-none",
196
+ header.column.getIsResizing() ? "opacity-100" : "opacity-0 hover:opacity-100 focus:opacity-100"
197
+ ),
198
+ onDoubleClick,
199
+ onMouseDown: header.getResizeHandler(),
200
+ onTouchStart: header.getResizeHandler()
201
+ }
202
+ );
203
+ }
204
+ export {
205
+ DataGridColumnHeader
206
+ };
207
+ //# sourceMappingURL=data-grid-column-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data-grid/data-grid-column-header.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ColumnSort, Header, SortDirection, SortingState, Table } from \"@tanstack/react-table\";\nimport { ChevronDownIcon, ChevronUpIcon, EyeOffIcon, PinIcon, PinOffIcon, XIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger\n} from \"../components/ui/dropdown-menu.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useDataGridMessages } from \"./data-grid-locale.js\";\nimport { getColumnVariant } from \"./utils.js\";\n\ninterface DataGridColumnHeaderProps<TData, TValue> extends React.ComponentProps<typeof DropdownMenuTrigger> {\n header: Header<TData, TValue>;\n table: Table<TData>;\n}\n\nexport function DataGridColumnHeader<TData, TValue>({\n header,\n table,\n className,\n onPointerDown,\n ...props\n}: DataGridColumnHeaderProps<TData, TValue>) {\n const msg = useDataGridMessages();\n\n const column = header.column;\n const label = column.columnDef.meta?.label\n ? column.columnDef.meta.label\n : typeof column.columnDef.header === \"string\"\n ? column.columnDef.header\n : column.id;\n\n const isAnyColumnResizing = table.getState().columnSizingInfo.isResizingColumn;\n\n const cellVariant = column.columnDef.meta?.cell;\n const columnVariant = getColumnVariant(cellVariant?.variant);\n\n const pinnedPosition = column.getIsPinned();\n const isPinnedLeft = pinnedPosition === \"left\";\n const isPinnedRight = pinnedPosition === \"right\";\n\n const onSortingChange = React.useCallback(\n (direction: SortDirection) => {\n table.setSorting((prev: SortingState) => {\n const existingSortIndex = prev.findIndex((sort) => sort.id === column.id);\n const newSort: ColumnSort = {\n id: column.id,\n desc: direction === \"desc\"\n };\n\n if (existingSortIndex >= 0) {\n const updated = [...prev];\n updated[existingSortIndex] = newSort;\n return updated;\n } else {\n return [...prev, newSort];\n }\n });\n },\n [column.id, table]\n );\n\n const onSortRemove = React.useCallback(() => {\n table.setSorting((prev: SortingState) => prev.filter((sort) => sort.id !== column.id));\n }, [column.id, table]);\n\n const onLeftPin = React.useCallback(() => {\n column.pin(\"left\");\n }, [column]);\n\n const onRightPin = React.useCallback(() => {\n column.pin(\"right\");\n }, [column]);\n\n const onUnpin = React.useCallback(() => {\n column.pin(false);\n }, [column]);\n\n const onTriggerPointerDown = React.useCallback(\n (event: React.PointerEvent<HTMLButtonElement>) => {\n onPointerDown?.(event);\n if (event.defaultPrevented) return;\n\n if (event.button !== 0) {\n return;\n }\n table.options.meta?.onColumnClick?.(column.id);\n },\n [table.options.meta, column.id, onPointerDown]\n );\n\n return (\n <>\n <DropdownMenu modal={false}>\n <DropdownMenuTrigger\n className={cn(\n \"flex size-full items-center justify-between gap-2 p-2 text-sm hover:bg-accent/40 data-[state=open]:bg-accent/40 [&_svg]:size-4\",\n isAnyColumnResizing && \"pointer-events-none\",\n className\n )}\n onPointerDown={onTriggerPointerDown}\n {...props}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5\">\n {columnVariant && (\n <Tooltip delayDuration={100}>\n <TooltipTrigger asChild>\n <columnVariant.icon className=\"size-3.5 shrink-0 text-muted-foreground\" />\n </TooltipTrigger>\n {/* `TooltipContent` của gói TỰ portal ra ngoài — cần thiết vì hộp lưới\n có overflow-auto, không portal thì tooltip bị cắt. */}\n <TooltipContent side=\"top\">{columnVariant.label}</TooltipContent>\n </Tooltip>\n )}\n <span className=\"truncate\">{label}</span>\n </div>\n <ChevronDownIcon className=\"size-3.5 shrink-0 text-muted-foreground\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" sideOffset={0} className=\"w-60\">\n {column.getCanSort() && (\n <>\n <DropdownMenuCheckboxItem\n className=\"gap-2 [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground\"\n checked={column.getIsSorted() === \"asc\"}\n onSelect={() => onSortingChange(\"asc\")}\n >\n <ChevronUpIcon />\n {msg.column.sortAsc}\n </DropdownMenuCheckboxItem>\n <DropdownMenuCheckboxItem\n className=\"gap-2 [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground\"\n checked={column.getIsSorted() === \"desc\"}\n onSelect={() => onSortingChange(\"desc\")}\n >\n <ChevronDownIcon />\n {msg.column.sortDesc}\n </DropdownMenuCheckboxItem>\n {column.getIsSorted() && (\n <DropdownMenuItem onSelect={onSortRemove}>\n <XIcon />\n {msg.column.removeSort}\n </DropdownMenuItem>\n )}\n </>\n )}\n {column.getCanPin() && (\n <>\n {column.getCanSort() && <DropdownMenuSeparator />}\n\n {isPinnedLeft ? (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onUnpin}>\n <PinOffIcon />\n {msg.column.unpinLeft}\n </DropdownMenuItem>\n ) : (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onLeftPin}>\n <PinIcon />\n {msg.column.pinLeft}\n </DropdownMenuItem>\n )}\n {isPinnedRight ? (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onUnpin}>\n <PinOffIcon />\n {msg.column.unpinRight}\n </DropdownMenuItem>\n ) : (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onRightPin}>\n <PinIcon />\n {msg.column.pinRight}\n </DropdownMenuItem>\n )}\n </>\n )}\n {column.getCanHide() && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"[&_svg]:text-muted-foreground\"\n onSelect={() => column.toggleVisibility(false)}\n >\n <EyeOffIcon />\n {msg.column.hide}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n {header.column.getCanResize() && <DataGridColumnResizer header={header} table={table} label={label} />}\n </>\n );\n}\n\nconst DataGridColumnResizer = React.memo(DataGridColumnResizerImpl, (prev, next) => {\n const prevColumn = prev.header.column;\n const nextColumn = next.header.column;\n\n if (prevColumn.getIsResizing() !== nextColumn.getIsResizing() || prevColumn.getSize() !== nextColumn.getSize()) {\n return false;\n }\n\n if (prev.label !== next.label) return false;\n\n return true;\n}) as typeof DataGridColumnResizerImpl;\n\ninterface DataGridColumnResizerProps<TData, TValue> extends DataGridColumnHeaderProps<TData, TValue> {\n label: string;\n}\n\nfunction DataGridColumnResizerImpl<TData, TValue>({ header, table, label }: DataGridColumnResizerProps<TData, TValue>) {\n const msg = useDataGridMessages();\n const defaultColumnDef = table._getDefaultColumnDef();\n\n const onDoubleClick = React.useCallback(() => {\n header.column.resetSize();\n }, [header.column]);\n\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label={msg.column.resize(label)}\n aria-valuenow={header.column.getSize()}\n aria-valuemin={defaultColumnDef.minSize}\n aria-valuemax={defaultColumnDef.maxSize}\n tabIndex={0}\n className={cn(\n // Tay kéo chỉ hiện khi rê/focus/đang kéo, nên nó CHỈ mang đúng một màu — `--primary-ink`\n // (bậc NÉT). `--primary` trần là bậc tô NỀN: gold #E8B824 chỉ 1,73:1 với nền trang ở\n // light, tức một tay kéo nhìn thấy được ở dark mà gần như vô hình ở light.\n \"absolute -end-px top-0 z-50 h-full w-0.5 cursor-ew-resize touch-none select-none bg-primary-ink transition-opacity after:absolute after:inset-y-0 after:start-1/2 after:h-full after:w-[18px] after:-translate-x-1/2 after:content-[''] focus:outline-none\",\n header.column.getIsResizing() ? \"opacity-100\" : \"opacity-0 hover:opacity-100 focus:opacity-100\"\n )}\n onDoubleClick={onDoubleClick}\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,12 @@
1
+ import { TableMeta, ColumnDef } from '@tanstack/react-table';
2
+ import * as React from 'react';
3
+ import { ContextMenuState } from './types.js';
4
+
5
+ interface DataGridContextMenuProps<TData> {
6
+ tableMeta: TableMeta<TData>;
7
+ columns: Array<ColumnDef<TData>>;
8
+ contextMenu: ContextMenuState;
9
+ }
10
+ declare function DataGridContextMenu<TData>({ tableMeta, columns, contextMenu }: DataGridContextMenuProps<TData>): React.JSX.Element | null;
11
+
12
+ export { DataGridContextMenu };
@@ -0,0 +1,161 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { CopyIcon, EraserIcon, ScissorsIcon, Trash2Icon } from "lucide-react";
4
+ import * as React from "react";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuSeparator,
10
+ DropdownMenuTrigger
11
+ } from "../components/ui/dropdown-menu.js";
12
+ import { useDataGridMessages } from "./data-grid-locale.js";
13
+ import { toastSuccess } from "./toast.js";
14
+ import { useAsRef } from "./use-as-ref.js";
15
+ import { getEmptyCellValue, parseCellKey } from "./utils.js";
16
+ function DataGridContextMenu({ tableMeta, columns, contextMenu }) {
17
+ const onContextMenuOpenChange = tableMeta?.onContextMenuOpenChange;
18
+ const selectionState = tableMeta?.selectionState;
19
+ const dataGridRef = tableMeta?.dataGridRef;
20
+ const onDataUpdate = tableMeta?.onDataUpdate;
21
+ const onRowsDelete = tableMeta?.onRowsDelete;
22
+ const onCellsCopy = tableMeta?.onCellsCopy;
23
+ const onCellsCut = tableMeta?.onCellsCut;
24
+ if (!contextMenu.open) return null;
25
+ return /* @__PURE__ */ jsx(
26
+ ContextMenu,
27
+ {
28
+ tableMeta,
29
+ columns,
30
+ dataGridRef,
31
+ contextMenu,
32
+ onContextMenuOpenChange,
33
+ selectionState,
34
+ onDataUpdate,
35
+ onRowsDelete,
36
+ onCellsCopy,
37
+ onCellsCut
38
+ }
39
+ );
40
+ }
41
+ const ContextMenu = React.memo(ContextMenuImpl, (prev, next) => {
42
+ if (prev.contextMenu.open !== next.contextMenu.open) return false;
43
+ if (!next.contextMenu.open) return true;
44
+ if (prev.contextMenu.x !== next.contextMenu.x) return false;
45
+ if (prev.contextMenu.y !== next.contextMenu.y) return false;
46
+ const prevSize = prev.selectionState?.selectedCells?.size ?? 0;
47
+ const nextSize = next.selectionState?.selectedCells?.size ?? 0;
48
+ if (prevSize !== nextSize) return false;
49
+ return true;
50
+ });
51
+ function ContextMenuImpl({
52
+ tableMeta,
53
+ columns,
54
+ dataGridRef,
55
+ contextMenu,
56
+ onContextMenuOpenChange,
57
+ selectionState,
58
+ onDataUpdate,
59
+ onRowsDelete,
60
+ onCellsCopy,
61
+ onCellsCut
62
+ }) {
63
+ const msg = useDataGridMessages();
64
+ const propsRef = useAsRef({
65
+ dataGridRef,
66
+ selectionState,
67
+ onDataUpdate,
68
+ onRowsDelete,
69
+ onCellsCopy,
70
+ onCellsCut,
71
+ columns
72
+ });
73
+ const triggerStyle = React.useMemo(
74
+ () => ({
75
+ position: "fixed",
76
+ left: `${contextMenu.x}px`,
77
+ top: `${contextMenu.y}px`,
78
+ width: "1px",
79
+ height: "1px",
80
+ padding: 0,
81
+ margin: 0,
82
+ border: "none",
83
+ background: "transparent",
84
+ pointerEvents: "none",
85
+ opacity: 0
86
+ }),
87
+ [contextMenu.x, contextMenu.y]
88
+ );
89
+ const onCloseAutoFocus = React.useCallback(
90
+ (event) => {
91
+ event.preventDefault();
92
+ propsRef.current.dataGridRef?.current?.focus();
93
+ },
94
+ [propsRef]
95
+ );
96
+ const onCopy = React.useCallback(() => {
97
+ propsRef.current.onCellsCopy?.();
98
+ }, [propsRef]);
99
+ const onCut = React.useCallback(() => {
100
+ propsRef.current.onCellsCut?.();
101
+ }, [propsRef]);
102
+ const onClear = React.useCallback(() => {
103
+ const { selectionState: selectionState2, columns: columns2, onDataUpdate: onDataUpdate2 } = propsRef.current;
104
+ if (!selectionState2?.selectedCells || selectionState2.selectedCells.size === 0) return;
105
+ const updates = [];
106
+ for (const cellKey of selectionState2.selectedCells) {
107
+ const { rowIndex, columnId } = parseCellKey(cellKey);
108
+ const column = columns2.find((col) => {
109
+ if (col.id) return col.id === columnId;
110
+ if ("accessorKey" in col) return col.accessorKey === columnId;
111
+ return false;
112
+ });
113
+ const cellVariant = column?.meta?.cell?.variant;
114
+ const emptyValue = getEmptyCellValue(cellVariant);
115
+ updates.push({ rowIndex, columnId, value: emptyValue });
116
+ }
117
+ onDataUpdate2?.(updates);
118
+ toastSuccess(msg.toast.cellsCleared(updates.length));
119
+ }, [propsRef, msg]);
120
+ const onDelete = React.useCallback(async () => {
121
+ const { selectionState: selectionState2, onRowsDelete: onRowsDelete2 } = propsRef.current;
122
+ if (!selectionState2?.selectedCells || selectionState2.selectedCells.size === 0) return;
123
+ const rowIndices = /* @__PURE__ */ new Set();
124
+ for (const cellKey of selectionState2.selectedCells) {
125
+ const { rowIndex } = parseCellKey(cellKey);
126
+ rowIndices.add(rowIndex);
127
+ }
128
+ const rowIndicesArray = Array.from(rowIndices).sort((a, b) => a - b);
129
+ const rowCount = rowIndicesArray.length;
130
+ await onRowsDelete2?.(rowIndicesArray);
131
+ toastSuccess(msg.toast.rowsDeleted(rowCount));
132
+ }, [propsRef, msg]);
133
+ return /* @__PURE__ */ jsxs(DropdownMenu, { open: contextMenu.open, onOpenChange: onContextMenuOpenChange, children: [
134
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { style: triggerStyle }),
135
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { "data-grid-popover": "", align: "start", className: "w-48", onCloseAutoFocus, children: [
136
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onCopy, children: [
137
+ /* @__PURE__ */ jsx(CopyIcon, {}),
138
+ msg.contextMenu.copy
139
+ ] }),
140
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onCut, disabled: tableMeta?.readOnly, children: [
141
+ /* @__PURE__ */ jsx(ScissorsIcon, {}),
142
+ msg.contextMenu.cut
143
+ ] }),
144
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onClear, disabled: tableMeta?.readOnly, children: [
145
+ /* @__PURE__ */ jsx(EraserIcon, {}),
146
+ msg.contextMenu.clear
147
+ ] }),
148
+ onRowsDelete && /* @__PURE__ */ jsxs(Fragment, { children: [
149
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
150
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { variant: "destructive", onSelect: onDelete, children: [
151
+ /* @__PURE__ */ jsx(Trash2Icon, {}),
152
+ msg.contextMenu.deleteRows
153
+ ] })
154
+ ] })
155
+ ] })
156
+ ] });
157
+ }
158
+ export {
159
+ DataGridContextMenu
160
+ };
161
+ //# sourceMappingURL=data-grid-context-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data-grid/data-grid-context-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ColumnDef, TableMeta } from \"@tanstack/react-table\";\nimport { CopyIcon, EraserIcon, ScissorsIcon, Trash2Icon } from \"lucide-react\";\nimport * as React from \"react\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger\n} from \"../components/ui/dropdown-menu.js\";\nimport { useDataGridMessages } from \"./data-grid-locale.js\";\nimport { toastSuccess } from \"./toast.js\";\nimport type { CellUpdate, ContextMenuState } from \"./types.js\";\nimport { useAsRef } from \"./use-as-ref.js\";\nimport { getEmptyCellValue, parseCellKey } from \"./utils.js\";\n\ninterface DataGridContextMenuProps<TData> {\n tableMeta: TableMeta<TData>;\n columns: Array<ColumnDef<TData>>;\n contextMenu: ContextMenuState;\n}\n\nexport function DataGridContextMenu<TData>({ tableMeta, columns, contextMenu }: DataGridContextMenuProps<TData>) {\n const onContextMenuOpenChange = tableMeta?.onContextMenuOpenChange;\n const selectionState = tableMeta?.selectionState;\n const dataGridRef = tableMeta?.dataGridRef;\n const onDataUpdate = tableMeta?.onDataUpdate;\n const onRowsDelete = tableMeta?.onRowsDelete;\n const onCellsCopy = tableMeta?.onCellsCopy;\n const onCellsCut = tableMeta?.onCellsCut;\n\n if (!contextMenu.open) return null;\n\n return (\n <ContextMenu\n tableMeta={tableMeta}\n columns={columns}\n dataGridRef={dataGridRef}\n contextMenu={contextMenu}\n onContextMenuOpenChange={onContextMenuOpenChange}\n selectionState={selectionState}\n onDataUpdate={onDataUpdate}\n onRowsDelete={onRowsDelete}\n onCellsCopy={onCellsCopy}\n onCellsCut={onCellsCut}\n />\n );\n}\n\ninterface ContextMenuProps<TData>\n extends Pick<\n TableMeta<TData>,\n | \"dataGridRef\"\n | \"onContextMenuOpenChange\"\n | \"selectionState\"\n | \"onDataUpdate\"\n | \"onRowsDelete\"\n | \"onCellsCopy\"\n | \"onCellsCut\"\n | \"readOnly\"\n >,\n Required<Pick<TableMeta<TData>, \"contextMenu\">> {\n tableMeta: TableMeta<TData>;\n columns: Array<ColumnDef<TData>>;\n}\n\nconst ContextMenu = React.memo(ContextMenuImpl, (prev, next) => {\n if (prev.contextMenu.open !== next.contextMenu.open) return false;\n if (!next.contextMenu.open) return true;\n if (prev.contextMenu.x !== next.contextMenu.x) return false;\n if (prev.contextMenu.y !== next.contextMenu.y) return false;\n\n const prevSize = prev.selectionState?.selectedCells?.size ?? 0;\n const nextSize = next.selectionState?.selectedCells?.size ?? 0;\n if (prevSize !== nextSize) return false;\n\n return true;\n}) as typeof ContextMenuImpl;\n\nfunction ContextMenuImpl<TData>({\n tableMeta,\n columns,\n dataGridRef,\n contextMenu,\n onContextMenuOpenChange,\n selectionState,\n onDataUpdate,\n onRowsDelete,\n onCellsCopy,\n onCellsCut\n}: ContextMenuProps<TData>) {\n const msg = useDataGridMessages();\n\n const propsRef = useAsRef({\n dataGridRef,\n selectionState,\n onDataUpdate,\n onRowsDelete,\n onCellsCopy,\n onCellsCut,\n columns\n });\n\n const triggerStyle = React.useMemo<React.CSSProperties>(\n () => ({\n position: \"fixed\",\n left: `${contextMenu.x}px`,\n top: `${contextMenu.y}px`,\n width: \"1px\",\n height: \"1px\",\n padding: 0,\n margin: 0,\n border: \"none\",\n background: \"transparent\",\n pointerEvents: \"none\",\n opacity: 0\n }),\n [contextMenu.x, contextMenu.y]\n );\n\n const onCloseAutoFocus: NonNullable<React.ComponentProps<typeof DropdownMenuContent>[\"onCloseAutoFocus\"]> =\n React.useCallback(\n (event) => {\n event.preventDefault();\n propsRef.current.dataGridRef?.current?.focus();\n },\n [propsRef]\n );\n\n const onCopy = React.useCallback(() => {\n propsRef.current.onCellsCopy?.();\n }, [propsRef]);\n\n const onCut = React.useCallback(() => {\n propsRef.current.onCellsCut?.();\n }, [propsRef]);\n\n const onClear = React.useCallback(() => {\n const { selectionState, columns, onDataUpdate } = propsRef.current;\n\n if (!selectionState?.selectedCells || selectionState.selectedCells.size === 0) return;\n\n const updates: Array<CellUpdate> = [];\n\n for (const cellKey of selectionState.selectedCells) {\n const { rowIndex, columnId } = parseCellKey(cellKey);\n\n // Get column from columns array\n const column = columns.find((col) => {\n if (col.id) return col.id === columnId;\n if (\"accessorKey\" in col) return col.accessorKey === columnId;\n return false;\n });\n const cellVariant = column?.meta?.cell?.variant;\n\n const emptyValue = getEmptyCellValue(cellVariant);\n\n updates.push({ rowIndex, columnId, value: emptyValue });\n }\n\n onDataUpdate?.(updates);\n\n toastSuccess(msg.toast.cellsCleared(updates.length));\n }, [propsRef, msg]);\n\n const onDelete = React.useCallback(async () => {\n const { selectionState, onRowsDelete } = propsRef.current;\n\n if (!selectionState?.selectedCells || selectionState.selectedCells.size === 0) return;\n\n const rowIndices = new Set<number>();\n for (const cellKey of selectionState.selectedCells) {\n const { rowIndex } = parseCellKey(cellKey);\n rowIndices.add(rowIndex);\n }\n\n const rowIndicesArray = Array.from(rowIndices).sort((a, b) => a - b);\n const rowCount = rowIndicesArray.length;\n\n await onRowsDelete?.(rowIndicesArray);\n\n toastSuccess(msg.toast.rowsDeleted(rowCount));\n }, [propsRef, msg]);\n\n return (\n <DropdownMenu open={contextMenu.open} onOpenChange={onContextMenuOpenChange}>\n <DropdownMenuTrigger style={triggerStyle} />\n <DropdownMenuContent data-grid-popover=\"\" align=\"start\" className=\"w-48\" onCloseAutoFocus={onCloseAutoFocus}>\n <DropdownMenuItem onSelect={onCopy}>\n <CopyIcon />\n {msg.contextMenu.copy}\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={onCut} disabled={tableMeta?.readOnly}>\n <ScissorsIcon />\n {msg.contextMenu.cut}\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={onClear} disabled={tableMeta?.readOnly}>\n <EraserIcon />\n {msg.contextMenu.clear}\n </DropdownMenuItem>\n {onRowsDelete && (\n <>\n <DropdownMenuSeparator />\n {/* `variant=\"destructive\"` của gói đã đi qua `--destructive-ink` (bậc CHỮ) cho cả\n chữ, icon và nền focus — đừng tự viết lại bằng `text-destructive` (bậc TÔ NỀN). */}\n <DropdownMenuItem variant=\"destructive\" onSelect={onDelete}>\n <Trash2Icon />\n {msg.contextMenu.deleteRows}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"mappings":"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","names":["selectionState","columns","onDataUpdate","onRowsDelete"]}
@@ -0,0 +1,23 @@
1
+ import { Table } from '@tanstack/react-table';
2
+ import * as React from 'react';
3
+ import { PopoverContent } from '../components/ui/popover.js';
4
+ import { DataGridLocale } from './messages.js';
5
+ import { DataGridMessages } from './messages-en.js';
6
+ import '@radix-ui/react-popover';
7
+ import './messages-vi.js';
8
+
9
+ interface DataGridFilterMenuProps<TData> extends React.ComponentProps<typeof PopoverContent> {
10
+ table: Table<TData>;
11
+ disabled?: boolean;
12
+ /**
13
+ * Ngôn ngữ của menu: `"en"` (mặc định) · `"vi"`, hoặc cả một bảng chuỗi tự soạn.
14
+ *
15
+ * ⚠ Thanh công cụ nằm NGOÀI `<DataGrid>`, nên `locale` truyền cho lưới không với tới đây. Bọc
16
+ * cả màn bằng `DataGridLocaleProvider` (một lần, đúng cho mọi mảnh), hoặc truyền prop này cho
17
+ * từng menu. Prop thắng provider.
18
+ */
19
+ locale?: DataGridLocale | DataGridMessages;
20
+ }
21
+ declare function DataGridFilterMenu<TData>({ table, disabled, className, locale, ...props }: DataGridFilterMenuProps<TData>): React.JSX.Element;
22
+
23
+ export { DataGridFilterMenu };