@comitor/ui 1.10.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 (101) 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/package.json +33 -15
@@ -0,0 +1,714 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useDirection } from "@radix-ui/react-direction";
4
+ import { vi } from "date-fns/locale";
5
+ import { CalendarIcon, Check, ChevronsUpDown, GripVertical, ListFilter, Trash2 } from "lucide-react";
6
+ import * as React from "react";
7
+ import { badgeVariants } from "../components/ui/badge.js";
8
+ import { Button } from "../components/ui/button.js";
9
+ import { Calendar } from "../components/ui/calendar.js";
10
+ import {
11
+ Command,
12
+ CommandEmpty,
13
+ CommandGroup,
14
+ CommandInput,
15
+ CommandItem,
16
+ CommandList
17
+ } from "../components/ui/command.js";
18
+ import { Input } from "../components/ui/input.js";
19
+ import { Popover, PopoverContent, PopoverTrigger } from "../components/ui/popover.js";
20
+ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select.js";
21
+ import { cn } from "../lib/cn.js";
22
+ import { getDefaultOperator, getOperatorsForVariant } from "./filters.js";
23
+ import { msg } from "./messages.js";
24
+ import { Sortable, SortableContent, SortableItem, SortableItemHandle, SortableOverlay } from "./sortable.js";
25
+ import { useDebouncedCallback } from "./use-debounced-callback.js";
26
+ import { formatDate } from "./utils.js";
27
+ const FILTER_SHORTCUT_KEY = "f";
28
+ const REMOVE_FILTER_SHORTCUTS = /* @__PURE__ */ new Set(["backspace", "delete"]);
29
+ const FILTER_DEBOUNCE_MS = 300;
30
+ const OPERATORS_WITHOUT_VALUE = /* @__PURE__ */ new Set(["isEmpty", "isNotEmpty", "isTrue", "isFalse"]);
31
+ function DataGridFilterMenu({ table, disabled, className, ...props }) {
32
+ const dir = useDirection();
33
+ const id = React.useId();
34
+ const labelId = React.useId();
35
+ const descriptionId = React.useId();
36
+ const [open, setOpen] = React.useState(false);
37
+ const addButtonRef = React.useRef(null);
38
+ const columnFilters = table.getState().columnFilters;
39
+ const { columnLabels, columns, columnVariants } = React.useMemo(() => {
40
+ const labels = /* @__PURE__ */ new Map();
41
+ const variants = /* @__PURE__ */ new Map();
42
+ const filteringIds = new Set(columnFilters.map((f) => f.id));
43
+ const availableColumns = [];
44
+ for (const column of table.getAllColumns()) {
45
+ if (!column.getCanFilter()) continue;
46
+ const label = column.columnDef.meta?.label ?? column.id;
47
+ const variant = column.columnDef.meta?.cell?.variant ?? "short-text";
48
+ labels.set(column.id, label);
49
+ variants.set(column.id, variant);
50
+ if (!filteringIds.has(column.id)) {
51
+ availableColumns.push({ id: column.id, label });
52
+ }
53
+ }
54
+ return {
55
+ columnLabels: labels,
56
+ columns: availableColumns,
57
+ columnVariants: variants
58
+ };
59
+ }, [columnFilters, table]);
60
+ const onFilterAdd = React.useCallback(() => {
61
+ const firstColumn = columns[0];
62
+ if (!firstColumn) return;
63
+ const variant = columnVariants.get(firstColumn.id) ?? "short-text";
64
+ const defaultOperator = getDefaultOperator(variant);
65
+ table.setColumnFilters((prevFilters) => [
66
+ ...prevFilters,
67
+ {
68
+ id: firstColumn.id,
69
+ value: {
70
+ operator: defaultOperator,
71
+ value: ""
72
+ }
73
+ }
74
+ ]);
75
+ }, [columns, columnVariants, table]);
76
+ const onFilterUpdate = React.useCallback(
77
+ (filterId, updates) => {
78
+ table.setColumnFilters((prevFilters) => {
79
+ if (!prevFilters) return prevFilters;
80
+ return prevFilters.map((filter) => filter.id === filterId ? { ...filter, ...updates } : filter);
81
+ });
82
+ },
83
+ [table]
84
+ );
85
+ const onFilterRemove = React.useCallback(
86
+ (filterId) => {
87
+ table.setColumnFilters((prevFilters) => prevFilters.filter((item) => item.id !== filterId));
88
+ },
89
+ [table]
90
+ );
91
+ const onFiltersReset = React.useCallback(() => {
92
+ table.setColumnFilters(table.initialState.columnFilters ?? []);
93
+ }, [table]);
94
+ React.useEffect(() => {
95
+ function onKeyDown(event) {
96
+ if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLElement && event.target.contentEditable === "true") {
97
+ return;
98
+ }
99
+ if (event.key.toLowerCase() === FILTER_SHORTCUT_KEY && (event.ctrlKey || event.metaKey) && event.shiftKey) {
100
+ event.preventDefault();
101
+ setOpen((prev) => !prev);
102
+ }
103
+ }
104
+ window.addEventListener("keydown", onKeyDown);
105
+ return () => window.removeEventListener("keydown", onKeyDown);
106
+ }, []);
107
+ const onTriggerKeyDown = React.useCallback(
108
+ (event) => {
109
+ if (REMOVE_FILTER_SHORTCUTS.has(event.key.toLowerCase()) && columnFilters.length > 0) {
110
+ event.preventDefault();
111
+ onFiltersReset();
112
+ }
113
+ },
114
+ [columnFilters.length, onFiltersReset]
115
+ );
116
+ return /* @__PURE__ */ jsxs(Sortable, { value: columnFilters, onValueChange: table.setColumnFilters, getItemValue: (item) => item.id, children: [
117
+ /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
118
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
119
+ Button,
120
+ {
121
+ dir,
122
+ variant: "outline",
123
+ className: "h-8 gap-2 font-normal",
124
+ onKeyDown: onTriggerKeyDown,
125
+ disabled,
126
+ children: [
127
+ /* @__PURE__ */ jsx(ListFilter, { "aria-hidden": "true", className: "text-muted-foreground" }),
128
+ msg.filter.trigger,
129
+ columnFilters.length > 0 && /* @__PURE__ */ jsx(
130
+ "span",
131
+ {
132
+ className: cn(
133
+ badgeVariants({ variant: "secondary" }),
134
+ "h-5 rounded px-1.5 font-mono text-[11px] font-medium"
135
+ ),
136
+ children: columnFilters.length
137
+ }
138
+ )
139
+ ]
140
+ }
141
+ ) }),
142
+ /* @__PURE__ */ jsxs(
143
+ PopoverContent,
144
+ {
145
+ "aria-labelledby": labelId,
146
+ "aria-describedby": descriptionId,
147
+ dir,
148
+ className: cn(
149
+ "flex w-full max-w-[--radix-popover-content-available-width] flex-col gap-3.5 p-4 sm:min-w-[480px]",
150
+ className
151
+ ),
152
+ ...props,
153
+ children: [
154
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
155
+ /* @__PURE__ */ jsx("h4", { id: labelId, className: "font-medium leading-none", children: columnFilters.length > 0 ? msg.filter.title : msg.filter.emptyTitle }),
156
+ /* @__PURE__ */ jsx(
157
+ "p",
158
+ {
159
+ id: descriptionId,
160
+ className: cn("text-muted-foreground text-sm", columnFilters.length > 0 && "sr-only"),
161
+ children: columnFilters.length > 0 ? msg.filter.description : msg.filter.emptyDescription
162
+ }
163
+ )
164
+ ] }),
165
+ columnFilters.length > 0 && /* @__PURE__ */ jsx(SortableContent, { asChild: true, children: /* @__PURE__ */ jsx("div", { role: "list", className: "flex max-h-[400px] flex-col gap-2 overflow-y-auto p-1", children: columnFilters.map((filter, index) => /* @__PURE__ */ jsx(
166
+ DataGridFilterItem,
167
+ {
168
+ filter,
169
+ index,
170
+ filterItemId: `${id}-filter-${filter.id}`,
171
+ dir,
172
+ columns,
173
+ columnLabels,
174
+ columnVariants,
175
+ table,
176
+ onFilterUpdate,
177
+ onFilterRemove
178
+ },
179
+ filter.id
180
+ )) }) }),
181
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
182
+ /* @__PURE__ */ jsx(
183
+ Button,
184
+ {
185
+ size: "sm",
186
+ className: "rounded",
187
+ ref: addButtonRef,
188
+ onClick: onFilterAdd,
189
+ disabled: columns.length === 0,
190
+ children: msg.filter.add
191
+ }
192
+ ),
193
+ columnFilters.length > 0 && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "rounded", onClick: onFiltersReset, children: msg.filter.reset })
194
+ ] })
195
+ ]
196
+ }
197
+ )
198
+ ] }),
199
+ /* @__PURE__ */ jsx(SortableOverlay, { children: /* @__PURE__ */ jsxs("div", { dir, className: "flex items-center gap-2", children: [
200
+ /* @__PURE__ */ jsx("div", { className: "h-8 min-w-[72px] rounded-sm bg-muted" }),
201
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-32 rounded-sm bg-muted" }),
202
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-32 rounded-sm bg-muted" }),
203
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-36 rounded-sm bg-muted" }),
204
+ /* @__PURE__ */ jsx("div", { className: "size-8 shrink-0 rounded-sm bg-muted" }),
205
+ /* @__PURE__ */ jsx("div", { className: "size-8 shrink-0 rounded-sm bg-muted" })
206
+ ] }) })
207
+ ] });
208
+ }
209
+ function DataGridFilterItem({
210
+ filter,
211
+ index,
212
+ filterItemId,
213
+ dir,
214
+ columns,
215
+ columnLabels,
216
+ columnVariants,
217
+ table,
218
+ onFilterUpdate,
219
+ onFilterRemove
220
+ }) {
221
+ const fieldListboxId = `${filterItemId}-field-listbox`;
222
+ const fieldTriggerId = `${filterItemId}-field-trigger`;
223
+ const operatorListboxId = `${filterItemId}-operator-listbox`;
224
+ const inputId = `${filterItemId}-input`;
225
+ const [showFieldSelector, setShowFieldSelector] = React.useState(false);
226
+ const [showOperatorSelector, setShowOperatorSelector] = React.useState(false);
227
+ const variant = columnVariants.get(filter.id) ?? "short-text";
228
+ const filterValue = filter.value;
229
+ const operator = filterValue?.operator ?? getDefaultOperator(variant);
230
+ const operators = getOperatorsForVariant(variant);
231
+ const needsValue = !OPERATORS_WITHOUT_VALUE.has(operator);
232
+ const column = table.getColumn(filter.id);
233
+ const onItemKeyDown = React.useCallback(
234
+ (event) => {
235
+ if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
236
+ return;
237
+ }
238
+ if (showFieldSelector || showOperatorSelector) {
239
+ return;
240
+ }
241
+ if (REMOVE_FILTER_SHORTCUTS.has(event.key.toLowerCase())) {
242
+ event.preventDefault();
243
+ onFilterRemove(filter.id);
244
+ }
245
+ },
246
+ [filter.id, showFieldSelector, showOperatorSelector, onFilterRemove]
247
+ );
248
+ const onOperatorChange = React.useCallback(
249
+ (newOperator) => {
250
+ onFilterUpdate(filter.id, {
251
+ value: {
252
+ operator: newOperator,
253
+ value: filterValue?.value,
254
+ endValue: filterValue?.endValue
255
+ }
256
+ });
257
+ },
258
+ [filter.id, filterValue?.value, filterValue?.endValue, onFilterUpdate]
259
+ );
260
+ const onValueChange = React.useCallback(
261
+ (newValue) => {
262
+ onFilterUpdate(filter.id, {
263
+ value: {
264
+ operator,
265
+ value: newValue,
266
+ endValue: filterValue?.endValue
267
+ }
268
+ });
269
+ },
270
+ [filter.id, operator, filterValue?.endValue, onFilterUpdate]
271
+ );
272
+ const onEndValueChange = React.useCallback(
273
+ (newValue) => {
274
+ onFilterUpdate(filter.id, {
275
+ value: {
276
+ operator,
277
+ value: filterValue?.value,
278
+ endValue: newValue
279
+ }
280
+ });
281
+ },
282
+ [filter.id, operator, filterValue?.value, onFilterUpdate]
283
+ );
284
+ return /* @__PURE__ */ jsx(SortableItem, { value: filter.id, asChild: true, children: /* @__PURE__ */ jsxs(
285
+ "div",
286
+ {
287
+ role: "listitem",
288
+ id: filterItemId,
289
+ tabIndex: -1,
290
+ className: "flex items-center gap-2",
291
+ onKeyDown: onItemKeyDown,
292
+ children: [
293
+ /* @__PURE__ */ jsx("div", { className: "min-w-[72px] text-center", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: index === 0 ? msg.filter.where : msg.filter.and }) }),
294
+ /* @__PURE__ */ jsxs(Popover, { open: showFieldSelector, onOpenChange: setShowFieldSelector, children: [
295
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
296
+ Button,
297
+ {
298
+ id: fieldTriggerId,
299
+ "aria-controls": fieldListboxId,
300
+ dir,
301
+ variant: "outline",
302
+ className: "h-8 w-32 justify-between rounded font-normal",
303
+ children: [
304
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: columnLabels.get(filter.id) }),
305
+ /* @__PURE__ */ jsx(ChevronsUpDown, { "aria-hidden": "true", className: "opacity-50" })
306
+ ]
307
+ }
308
+ ) }),
309
+ /* @__PURE__ */ jsx(PopoverContent, { id: fieldListboxId, dir, align: "start", className: "w-40 p-0", children: /* @__PURE__ */ jsxs(Command, { children: [
310
+ /* @__PURE__ */ jsx(CommandInput, { placeholder: msg.filter.fieldPlaceholder, "aria-label": msg.filter.fieldPlaceholder }),
311
+ /* @__PURE__ */ jsxs(CommandList, { children: [
312
+ /* @__PURE__ */ jsx(CommandEmpty, { children: msg.filter.fieldEmpty }),
313
+ /* @__PURE__ */ jsx(CommandGroup, { children: columns.map((column2) => /* @__PURE__ */ jsxs(
314
+ CommandItem,
315
+ {
316
+ value: column2.id,
317
+ onSelect: (value) => {
318
+ const newVariant = columnVariants.get(value) ?? "short-text";
319
+ const newOperator = getDefaultOperator(newVariant);
320
+ table.setColumnFilters(
321
+ (prevFilters) => prevFilters.map(
322
+ (f) => f.id === filter.id ? {
323
+ id: value,
324
+ value: {
325
+ operator: newOperator,
326
+ value: ""
327
+ }
328
+ } : f
329
+ )
330
+ );
331
+ setShowFieldSelector(false);
332
+ },
333
+ children: [
334
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: column2.label }),
335
+ /* @__PURE__ */ jsx(Check, { className: cn("ms-auto", column2.id === filter.id ? "opacity-100" : "opacity-0") })
336
+ ]
337
+ },
338
+ column2.id
339
+ )) })
340
+ ] })
341
+ ] }) })
342
+ ] }),
343
+ /* @__PURE__ */ jsxs(
344
+ Select,
345
+ {
346
+ open: showOperatorSelector,
347
+ onOpenChange: setShowOperatorSelector,
348
+ value: operator,
349
+ onValueChange: onOperatorChange,
350
+ children: [
351
+ /* @__PURE__ */ jsx(SelectTrigger, { "aria-controls": operatorListboxId, "aria-label": msg.filter.title, className: "h-8 w-36 rounded", children: /* @__PURE__ */ jsx("div", { className: "truncate", children: /* @__PURE__ */ jsx(SelectValue, {}) }) }),
352
+ /* @__PURE__ */ jsx(SelectContent, { id: operatorListboxId, children: /* @__PURE__ */ jsx(SelectGroup, { children: operators.map((op) => /* @__PURE__ */ jsx(SelectItem, { value: op.value, children: op.label }, op.value)) }) })
353
+ ]
354
+ }
355
+ ),
356
+ /* @__PURE__ */ jsx("div", { className: "min-w-36 max-w-60 flex-1", children: needsValue && column ? /* @__PURE__ */ jsx(
357
+ DataGridFilterInput,
358
+ {
359
+ variant,
360
+ operator,
361
+ column,
362
+ inputId,
363
+ dir,
364
+ value: filterValue?.value,
365
+ endValue: filterValue?.endValue,
366
+ onValueChange,
367
+ onEndValueChange
368
+ },
369
+ filter.id
370
+ ) : /* @__PURE__ */ jsx(
371
+ "div",
372
+ {
373
+ id: inputId,
374
+ role: "status",
375
+ "aria-label": msg.filter.emptyValue(columnLabels.get(filter.id) ?? filter.id),
376
+ "aria-live": "polite",
377
+ className: "h-8 w-full rounded border bg-transparent"
378
+ }
379
+ ) }),
380
+ /* @__PURE__ */ jsx(
381
+ Button,
382
+ {
383
+ "aria-controls": filterItemId,
384
+ "aria-label": msg.filter.remove,
385
+ variant: "outline",
386
+ size: "icon",
387
+ className: "size-8 rounded",
388
+ onClick: () => onFilterRemove(filter.id),
389
+ children: /* @__PURE__ */ jsx(Trash2, { "aria-hidden": "true" })
390
+ }
391
+ ),
392
+ /* @__PURE__ */ jsx(SortableItemHandle, { asChild: true, children: /* @__PURE__ */ jsx(Button, { "aria-label": msg.filter.drag, variant: "outline", size: "icon", className: "size-8 rounded", children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" }) }) })
393
+ ]
394
+ }
395
+ ) });
396
+ }
397
+ function DataGridFilterInput({
398
+ variant,
399
+ operator,
400
+ dir,
401
+ placeholder = msg.filter.valuePlaceholder,
402
+ value,
403
+ endValue,
404
+ column,
405
+ inputId,
406
+ onValueChange,
407
+ onEndValueChange
408
+ }) {
409
+ const [showValueSelector, setShowValueSelector] = React.useState(false);
410
+ const [localValue, setLocalValue] = React.useState(value);
411
+ const [localEndValue, setLocalEndValue] = React.useState(endValue);
412
+ const debouncedOnChange = useDebouncedCallback((newValue) => {
413
+ onValueChange(newValue);
414
+ }, FILTER_DEBOUNCE_MS);
415
+ const debouncedOnEndValueChange = useDebouncedCallback((newValue) => {
416
+ onEndValueChange?.(newValue);
417
+ }, FILTER_DEBOUNCE_MS);
418
+ const cellVariant = column.columnDef.meta?.cell;
419
+ const selectOptions = React.useMemo(() => {
420
+ return cellVariant?.variant === "select" || cellVariant?.variant === "multi-select" ? cellVariant.options : [];
421
+ }, [cellVariant]);
422
+ const isBetween = operator === "isBetween";
423
+ if (variant === "number") {
424
+ if (isBetween) {
425
+ return /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
426
+ /* @__PURE__ */ jsx(
427
+ Input,
428
+ {
429
+ id: inputId,
430
+ type: "number",
431
+ inputMode: "numeric",
432
+ placeholder: msg.filter.rangeStart,
433
+ "aria-label": msg.filter.rangeStart,
434
+ value: localValue ?? "",
435
+ onChange: (event) => {
436
+ const val = event.target.value;
437
+ const newValue = val === "" ? void 0 : Number(val);
438
+ setLocalValue(newValue);
439
+ debouncedOnChange(newValue);
440
+ },
441
+ className: "h-8 w-full flex-1 rounded"
442
+ }
443
+ ),
444
+ /* @__PURE__ */ jsx(
445
+ Input,
446
+ {
447
+ id: `${inputId}-end`,
448
+ type: "number",
449
+ inputMode: "numeric",
450
+ placeholder: msg.filter.rangeEnd,
451
+ "aria-label": msg.filter.rangeEnd,
452
+ value: localEndValue ?? "",
453
+ onChange: (event) => {
454
+ const val = event.target.value;
455
+ const newValue = val === "" ? void 0 : Number(val);
456
+ setLocalEndValue(newValue);
457
+ debouncedOnEndValueChange(newValue);
458
+ },
459
+ className: "h-8 w-full flex-1 rounded"
460
+ }
461
+ )
462
+ ] });
463
+ }
464
+ return /* @__PURE__ */ jsx(
465
+ Input,
466
+ {
467
+ id: inputId,
468
+ type: "number",
469
+ inputMode: "numeric",
470
+ placeholder,
471
+ "aria-label": placeholder,
472
+ value: localValue ?? "",
473
+ onChange: (event) => {
474
+ const val = event.target.value;
475
+ const newValue = val === "" ? void 0 : Number(val);
476
+ setLocalValue(newValue);
477
+ debouncedOnChange(newValue);
478
+ },
479
+ className: "h-8 w-full rounded"
480
+ }
481
+ );
482
+ }
483
+ if (variant === "date") {
484
+ const inputListboxId = `${inputId}-listbox`;
485
+ if (isBetween) {
486
+ const startDate = localValue && typeof localValue === "string" ? new Date(localValue) : void 0;
487
+ const endDate = localEndValue && typeof localEndValue === "string" ? new Date(localEndValue) : void 0;
488
+ const isSameDate = startDate && endDate && startDate.toDateString() === endDate.toDateString();
489
+ const displayValue = startDate && endDate && !isSameDate ? `${formatDate(startDate)} - ${formatDate(endDate)}` : startDate ? formatDate(startDate) : msg.filter.pickRange;
490
+ return /* @__PURE__ */ jsxs(Popover, { open: showValueSelector, onOpenChange: setShowValueSelector, children: [
491
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
492
+ Button,
493
+ {
494
+ id: inputId,
495
+ "aria-controls": inputListboxId,
496
+ dir,
497
+ variant: "outline",
498
+ className: cn(
499
+ "h-8 w-full justify-start gap-2 rounded font-normal",
500
+ !startDate && "text-muted-foreground"
501
+ ),
502
+ children: [
503
+ /* @__PURE__ */ jsx(CalendarIcon, { "aria-hidden": "true" }),
504
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: displayValue })
505
+ ]
506
+ }
507
+ ) }),
508
+ /* @__PURE__ */ jsx(PopoverContent, { id: inputListboxId, dir, align: "start", className: "w-auto p-0", children: /* @__PURE__ */ jsx(
509
+ Calendar,
510
+ {
511
+ mode: "range",
512
+ locale: vi,
513
+ autoFocus: true,
514
+ selected: startDate ? { from: startDate, to: endDate } : void 0,
515
+ onSelect: (range) => {
516
+ const fromValue = range?.from ? range.from.toISOString() : void 0;
517
+ const toValue = range?.to ? range.to.toISOString() : void 0;
518
+ setLocalValue(fromValue);
519
+ setLocalEndValue(toValue);
520
+ onValueChange(fromValue);
521
+ onEndValueChange?.(toValue);
522
+ }
523
+ }
524
+ ) })
525
+ ] });
526
+ }
527
+ const dateValue = localValue && typeof localValue === "string" ? new Date(localValue) : void 0;
528
+ return /* @__PURE__ */ jsxs(Popover, { open: showValueSelector, onOpenChange: setShowValueSelector, children: [
529
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
530
+ Button,
531
+ {
532
+ id: inputId,
533
+ "aria-controls": inputListboxId,
534
+ dir,
535
+ variant: "outline",
536
+ className: cn("h-8 w-full justify-start gap-2 rounded font-normal", !dateValue && "text-muted-foreground"),
537
+ children: [
538
+ /* @__PURE__ */ jsx(CalendarIcon, { "aria-hidden": "true" }),
539
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: dateValue ? formatDate(dateValue) : msg.filter.pickDate })
540
+ ]
541
+ }
542
+ ) }),
543
+ /* @__PURE__ */ jsx(PopoverContent, { id: inputListboxId, dir, align: "start", className: "w-auto p-0", children: /* @__PURE__ */ jsx(
544
+ Calendar,
545
+ {
546
+ mode: "single",
547
+ locale: vi,
548
+ autoFocus: true,
549
+ selected: dateValue,
550
+ onSelect: (date) => {
551
+ if (!date) return;
552
+ const newValue = date.toISOString();
553
+ setLocalValue(newValue);
554
+ onValueChange(newValue);
555
+ setShowValueSelector(false);
556
+ }
557
+ }
558
+ ) })
559
+ ] });
560
+ }
561
+ const isSelectVariant = variant === "select" || variant === "multi-select";
562
+ const isMultiValueOperator = operator === "isAnyOf" || operator === "isNoneOf";
563
+ if (isSelectVariant && selectOptions.length > 0) {
564
+ const inputListboxId = `${inputId}-listbox`;
565
+ if (isMultiValueOperator) {
566
+ const selectedValues = Array.isArray(value) ? value : [];
567
+ const selectedOptions = selectOptions.filter((option) => selectedValues.includes(option.value));
568
+ const selectedOptionsWithIcons = selectedOptions.filter((selectedOption2) => selectedOption2.icon);
569
+ return /* @__PURE__ */ jsxs(Popover, { open: showValueSelector, onOpenChange: setShowValueSelector, children: [
570
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
571
+ Button,
572
+ {
573
+ id: inputId,
574
+ "aria-controls": inputListboxId,
575
+ dir,
576
+ variant: "outline",
577
+ className: "h-8 w-full justify-start rounded font-normal",
578
+ children: selectedOptions.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder }) : /* @__PURE__ */ jsxs(Fragment, { children: [
579
+ selectedOptionsWithIcons.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center -space-x-2 rtl:space-x-reverse", children: selectedOptionsWithIcons.map(
580
+ (selectedOption2) => selectedOption2.icon && /* @__PURE__ */ jsx("div", { className: "rounded-full border bg-background p-0.5", children: /* @__PURE__ */ jsx(selectedOption2.icon, { className: "size-3.5" }) }, selectedOption2.value)
581
+ ) }),
582
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedOptions.length > 1 ? msg.filter.selectedCount(selectedOptions.length) : selectedOptions[0]?.label })
583
+ ] })
584
+ }
585
+ ) }),
586
+ /* @__PURE__ */ jsx(PopoverContent, { id: inputListboxId, dir, align: "start", className: "w-48 p-0", children: /* @__PURE__ */ jsxs(Command, { children: [
587
+ /* @__PURE__ */ jsx(CommandInput, { placeholder: msg.filter.optionPlaceholder, "aria-label": msg.filter.optionPlaceholder }),
588
+ /* @__PURE__ */ jsxs(CommandList, { children: [
589
+ /* @__PURE__ */ jsx(CommandEmpty, { children: msg.filter.optionEmpty }),
590
+ /* @__PURE__ */ jsx(CommandGroup, { children: selectOptions.map((option) => {
591
+ const isSelected = selectedValues.includes(option.value);
592
+ return /* @__PURE__ */ jsxs(
593
+ CommandItem,
594
+ {
595
+ value: option.value,
596
+ onSelect: () => {
597
+ const newValues = isSelected ? selectedValues.filter((v) => v !== option.value) : [...selectedValues, option.value];
598
+ onValueChange(newValues.length > 0 ? newValues : void 0);
599
+ },
600
+ children: [
601
+ option.icon && /* @__PURE__ */ jsx(option.icon, {}),
602
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: option.label }),
603
+ option.count && /* @__PURE__ */ jsx("span", { className: "ms-auto font-mono text-xs", children: option.count }),
604
+ /* @__PURE__ */ jsx(Check, { className: cn("ms-auto", isSelected ? "opacity-100" : "opacity-0") })
605
+ ]
606
+ },
607
+ option.value
608
+ );
609
+ }) })
610
+ ] })
611
+ ] }) })
612
+ ] });
613
+ }
614
+ const selectedOption = selectOptions.find((opt) => opt.value === value);
615
+ return /* @__PURE__ */ jsxs(Popover, { open: showValueSelector, onOpenChange: setShowValueSelector, children: [
616
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
617
+ Button,
618
+ {
619
+ id: inputId,
620
+ "aria-controls": inputListboxId,
621
+ dir,
622
+ variant: "outline",
623
+ className: "h-8 w-full justify-start rounded font-normal",
624
+ children: selectedOption ? /* @__PURE__ */ jsxs(Fragment, { children: [
625
+ selectedOption.icon && /* @__PURE__ */ jsx(selectedOption.icon, {}),
626
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedOption.label })
627
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder })
628
+ }
629
+ ) }),
630
+ /* @__PURE__ */ jsx(PopoverContent, { id: inputListboxId, dir, align: "start", className: "w-[200px] p-0", children: /* @__PURE__ */ jsxs(Command, { children: [
631
+ /* @__PURE__ */ jsx(CommandInput, { placeholder: msg.filter.optionPlaceholder, "aria-label": msg.filter.optionPlaceholder }),
632
+ /* @__PURE__ */ jsxs(CommandList, { children: [
633
+ /* @__PURE__ */ jsx(CommandEmpty, { children: msg.filter.optionEmpty }),
634
+ /* @__PURE__ */ jsx(CommandGroup, { children: selectOptions.map((option) => /* @__PURE__ */ jsxs(
635
+ CommandItem,
636
+ {
637
+ value: option.value,
638
+ onSelect: () => {
639
+ onValueChange(option.value);
640
+ setShowValueSelector(false);
641
+ },
642
+ children: [
643
+ option.icon && /* @__PURE__ */ jsx(option.icon, {}),
644
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: option.label }),
645
+ option.count && /* @__PURE__ */ jsx("span", { className: "ms-auto font-mono text-xs", children: option.count }),
646
+ /* @__PURE__ */ jsx(Check, { className: cn("ms-auto", value === option.value ? "opacity-100" : "opacity-0") })
647
+ ]
648
+ },
649
+ option.value
650
+ )) })
651
+ ] })
652
+ ] }) })
653
+ ] });
654
+ }
655
+ if (isBetween) {
656
+ return /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
657
+ /* @__PURE__ */ jsx(
658
+ Input,
659
+ {
660
+ id: inputId,
661
+ type: "text",
662
+ placeholder: msg.filter.rangeStart,
663
+ "aria-label": msg.filter.rangeStart,
664
+ className: "h-8 w-full flex-1 rounded",
665
+ value: localValue ?? "",
666
+ onChange: (event) => {
667
+ const val = event.target.value;
668
+ const newValue = val === "" ? void 0 : val;
669
+ setLocalValue(newValue);
670
+ debouncedOnChange(newValue);
671
+ }
672
+ }
673
+ ),
674
+ /* @__PURE__ */ jsx(
675
+ Input,
676
+ {
677
+ id: `${inputId}-end`,
678
+ type: "text",
679
+ placeholder: msg.filter.rangeEnd,
680
+ "aria-label": msg.filter.rangeEnd,
681
+ className: "h-8 w-full flex-1 rounded",
682
+ value: localEndValue ?? "",
683
+ onChange: (event) => {
684
+ const val = event.target.value;
685
+ const newValue = val === "" ? void 0 : val;
686
+ setLocalEndValue(newValue);
687
+ debouncedOnEndValueChange(newValue);
688
+ }
689
+ }
690
+ )
691
+ ] });
692
+ }
693
+ return /* @__PURE__ */ jsx(
694
+ Input,
695
+ {
696
+ id: inputId,
697
+ type: "text",
698
+ placeholder,
699
+ "aria-label": placeholder,
700
+ className: "h-8 w-full rounded",
701
+ value: localValue ?? "",
702
+ onChange: (event) => {
703
+ const val = event.target.value;
704
+ const newValue = val === "" ? void 0 : val;
705
+ setLocalValue(newValue);
706
+ debouncedOnChange(newValue);
707
+ }
708
+ }
709
+ );
710
+ }
711
+ export {
712
+ DataGridFilterMenu
713
+ };
714
+ //# sourceMappingURL=data-grid-filter-menu.js.map