@undevy-org/tablewright 0.1.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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/dist/es/components/dashboard/ReportSummaryCard.js +67 -0
  4. package/dist/es/components/data-table/AppliedStateChips.js +113 -0
  5. package/dist/es/components/data-table/ColumnFilterPopover.js +548 -0
  6. package/dist/es/components/data-table/ColumnHeaderMenu.js +144 -0
  7. package/dist/es/components/data-table/CompoundFilterPopover.js +508 -0
  8. package/dist/es/components/data-table/CopyButton.js +35 -0
  9. package/dist/es/components/data-table/CopyableText.js +26 -0
  10. package/dist/es/components/data-table/DataTableShell.js +145 -0
  11. package/dist/es/components/data-table/DrawerActionGroup.js +59 -0
  12. package/dist/es/components/data-table/DrawerEntityCard.js +21 -0
  13. package/dist/es/components/data-table/DrawerField.js +10 -0
  14. package/dist/es/components/data-table/DrawerFlagItem.js +20 -0
  15. package/dist/es/components/data-table/DrawerSection.js +26 -0
  16. package/dist/es/components/data-table/DrawerShell.js +141 -0
  17. package/dist/es/components/data-table/DrawerSummaryCard.js +11 -0
  18. package/dist/es/components/data-table/EmptyState.js +14 -0
  19. package/dist/es/components/data-table/ExpiryTimestampCell.js +43 -0
  20. package/dist/es/components/data-table/FilterChip.js +47 -0
  21. package/dist/es/components/data-table/FilterFormPanel.js +357 -0
  22. package/dist/es/components/data-table/FilterFormToggle.js +77 -0
  23. package/dist/es/components/data-table/FilterPopoverFooter.js +19 -0
  24. package/dist/es/components/data-table/FilterToolbar.js +131 -0
  25. package/dist/es/components/data-table/FlatColumnToggle.js +46 -0
  26. package/dist/es/components/data-table/GapIndicator.js +29 -0
  27. package/dist/es/components/data-table/GroupedColumnToggle.js +112 -0
  28. package/dist/es/components/data-table/RowActionsMenu.js +74 -0
  29. package/dist/es/components/data-table/RowControlCell.js +104 -0
  30. package/dist/es/components/data-table/SortableHeader.js +102 -0
  31. package/dist/es/components/data-table/TableFooter.js +132 -0
  32. package/dist/es/components/data-table/TableLayout.js +14 -0
  33. package/dist/es/components/data-table/TimestampCell.js +16 -0
  34. package/dist/es/components/data-table/TwoPanelMenu.js +64 -0
  35. package/dist/es/components/data-table/ViewChip.js +25 -0
  36. package/dist/es/components/data-table/cells/AmountCell.js +127 -0
  37. package/dist/es/components/data-table/cells/ConfirmedAmountCell.js +115 -0
  38. package/dist/es/components/data-table/cells/CurrencyAmountCell.js +26 -0
  39. package/dist/es/components/data-table/cells/DualLineCell.js +59 -0
  40. package/dist/es/components/data-table/cells/EnumBadgeCell.js +9 -0
  41. package/dist/es/components/data-table/cells/MonoIdCell.js +31 -0
  42. package/dist/es/components/data-table/hooks/use-column-resize.js +39 -0
  43. package/dist/es/components/data-table/hooks/use-row-drag.js +40 -0
  44. package/dist/es/components/data-table/hooks/use-table-pagination.js +14 -0
  45. package/dist/es/components/data-table/hooks/use-table-sort.js +35 -0
  46. package/dist/es/components/data-table/managed-filters.js +34 -0
  47. package/dist/es/components/data-table/sticky-utils.js +25 -0
  48. package/dist/es/components/layout/AppShell.js +113 -0
  49. package/dist/es/components/layout/Sidebar.js +128 -0
  50. package/dist/es/components/layout/SidebarAccountMenu.js +129 -0
  51. package/dist/es/components/layout/SidebarUser.js +38 -0
  52. package/dist/es/components/layout/TopBar.js +25 -0
  53. package/dist/es/components/layout/app-shell-context.js +9 -0
  54. package/dist/es/components/ui/badge.js +31 -0
  55. package/dist/es/components/ui/button-footer.js +25 -0
  56. package/dist/es/components/ui/button.js +44 -0
  57. package/dist/es/components/ui/checkbox.js +24 -0
  58. package/dist/es/components/ui/combobox.js +179 -0
  59. package/dist/es/components/ui/dialog.js +80 -0
  60. package/dist/es/components/ui/dropdown-menu.js +66 -0
  61. package/dist/es/components/ui/input.js +24 -0
  62. package/dist/es/components/ui/popover.js +33 -0
  63. package/dist/es/components/ui/segmented-control.js +60 -0
  64. package/dist/es/components/ui/select.js +66 -0
  65. package/dist/es/components/ui/tabs.js +41 -0
  66. package/dist/es/components/ui/theme-switch.js +47 -0
  67. package/dist/es/constants/drawer.js +4 -0
  68. package/dist/es/context/drawer-expand-context.js +10 -0
  69. package/dist/es/context/portal-container-context.js +10 -0
  70. package/dist/es/hooks/table-orchestration-store.js +165 -0
  71. package/dist/es/hooks/use-scroll-activity.js +33 -0
  72. package/dist/es/hooks/useRowDragOverride.js +18 -0
  73. package/dist/es/hooks/useTableOrchestration.js +452 -0
  74. package/dist/es/index.js +199 -0
  75. package/dist/es/lib/column-gaps.js +23 -0
  76. package/dist/es/lib/filter-stats.js +67 -0
  77. package/dist/es/lib/format-enum.js +6 -0
  78. package/dist/es/lib/format-timestamp.js +37 -0
  79. package/dist/es/lib/style-scope.js +4 -0
  80. package/dist/es/lib/utils.js +8 -0
  81. package/dist/fonts.css +5 -0
  82. package/dist/preflight.css +502 -0
  83. package/dist/styles.css +1731 -0
  84. package/dist/tablewright.cjs +5473 -0
  85. package/dist/tablewright.css +1579 -0
  86. package/dist/tokens.css +150 -0
  87. package/dist/types/components/dashboard/ReportSummaryCard.d.ts +15 -0
  88. package/dist/types/components/data-table/AppliedStateChips.d.ts +28 -0
  89. package/dist/types/components/data-table/ColumnFilterPopover.d.ts +14 -0
  90. package/dist/types/components/data-table/ColumnHeaderMenu.d.ts +27 -0
  91. package/dist/types/components/data-table/CompoundFilterPopover.d.ts +14 -0
  92. package/dist/types/components/data-table/CopyButton.d.ts +8 -0
  93. package/dist/types/components/data-table/CopyableText.d.ts +10 -0
  94. package/dist/types/components/data-table/DataTableShell.d.ts +30 -0
  95. package/dist/types/components/data-table/DrawerActionGroup.d.ts +8 -0
  96. package/dist/types/components/data-table/DrawerEntityCard.d.ts +13 -0
  97. package/dist/types/components/data-table/DrawerField.d.ts +7 -0
  98. package/dist/types/components/data-table/DrawerFlagItem.d.ts +10 -0
  99. package/dist/types/components/data-table/DrawerSection.d.ts +8 -0
  100. package/dist/types/components/data-table/DrawerShell.d.ts +33 -0
  101. package/dist/types/components/data-table/DrawerSummaryCard.d.ts +7 -0
  102. package/dist/types/components/data-table/EmptyState.d.ts +6 -0
  103. package/dist/types/components/data-table/ExpiryTimestampCell.d.ts +6 -0
  104. package/dist/types/components/data-table/FilterChip.d.ts +8 -0
  105. package/dist/types/components/data-table/FilterFormPanel.d.ts +34 -0
  106. package/dist/types/components/data-table/FilterFormToggle.d.ts +18 -0
  107. package/dist/types/components/data-table/FilterPopoverFooter.d.ts +9 -0
  108. package/dist/types/components/data-table/FilterToolbar.d.ts +29 -0
  109. package/dist/types/components/data-table/FlatColumnToggle.d.ts +10 -0
  110. package/dist/types/components/data-table/GapIndicator.d.ts +11 -0
  111. package/dist/types/components/data-table/GroupedColumnToggle.d.ts +12 -0
  112. package/dist/types/components/data-table/RowActionsMenu.d.ts +10 -0
  113. package/dist/types/components/data-table/RowControlCell.d.ts +16 -0
  114. package/dist/types/components/data-table/SortableHeader.d.ts +16 -0
  115. package/dist/types/components/data-table/TableFooter.d.ts +31 -0
  116. package/dist/types/components/data-table/TableLayout.d.ts +12 -0
  117. package/dist/types/components/data-table/TimestampCell.d.ts +3 -0
  118. package/dist/types/components/data-table/TwoPanelMenu.d.ts +22 -0
  119. package/dist/types/components/data-table/ViewChip.d.ts +6 -0
  120. package/dist/types/components/data-table/cells/AmountCell.d.ts +11 -0
  121. package/dist/types/components/data-table/cells/ConfirmedAmountCell.d.ts +10 -0
  122. package/dist/types/components/data-table/cells/CurrencyAmountCell.d.ts +8 -0
  123. package/dist/types/components/data-table/cells/DualLineCell.d.ts +44 -0
  124. package/dist/types/components/data-table/cells/EnumBadgeCell.d.ts +9 -0
  125. package/dist/types/components/data-table/cells/MonoIdCell.d.ts +19 -0
  126. package/dist/types/components/data-table/filter-types.d.ts +79 -0
  127. package/dist/types/components/data-table/hooks/use-column-resize.d.ts +7 -0
  128. package/dist/types/components/data-table/hooks/use-row-drag.d.ts +14 -0
  129. package/dist/types/components/data-table/hooks/use-table-pagination.d.ts +10 -0
  130. package/dist/types/components/data-table/hooks/use-table-sort.d.ts +6 -0
  131. package/dist/types/components/data-table/managed-filters.d.ts +18 -0
  132. package/dist/types/components/data-table/sticky-utils.d.ts +4 -0
  133. package/dist/types/components/data-table/types.d.ts +35 -0
  134. package/dist/types/components/layout/AppShell.d.ts +23 -0
  135. package/dist/types/components/layout/Sidebar.d.ts +16 -0
  136. package/dist/types/components/layout/SidebarAccountMenu.d.ts +20 -0
  137. package/dist/types/components/layout/SidebarUser.d.ts +8 -0
  138. package/dist/types/components/layout/TopBar.d.ts +7 -0
  139. package/dist/types/components/layout/app-shell-context.d.ts +8 -0
  140. package/dist/types/components/layout/index.d.ts +10 -0
  141. package/dist/types/components/layout/types.d.ts +11 -0
  142. package/dist/types/components/ui/badge.d.ts +9 -0
  143. package/dist/types/components/ui/button-footer.d.ts +7 -0
  144. package/dist/types/components/ui/button.d.ts +11 -0
  145. package/dist/types/components/ui/checkbox.d.ts +4 -0
  146. package/dist/types/components/ui/combobox.d.ts +92 -0
  147. package/dist/types/components/ui/dialog.d.ts +15 -0
  148. package/dist/types/components/ui/dropdown-menu.d.ts +10 -0
  149. package/dist/types/components/ui/input.d.ts +6 -0
  150. package/dist/types/components/ui/popover.d.ts +7 -0
  151. package/dist/types/components/ui/segmented-control.d.ts +17 -0
  152. package/dist/types/components/ui/select.d.ts +8 -0
  153. package/dist/types/components/ui/tabs.d.ts +10 -0
  154. package/dist/types/components/ui/theme-switch.d.ts +12 -0
  155. package/dist/types/constants/drawer.d.ts +1 -0
  156. package/dist/types/context/drawer-expand-context.d.ts +7 -0
  157. package/dist/types/context/portal-container-context.d.ts +14 -0
  158. package/dist/types/hooks/table-orchestration-store.d.ts +75 -0
  159. package/dist/types/hooks/use-scroll-activity.d.ts +1 -0
  160. package/dist/types/hooks/useDemoTheme.d.ts +8 -0
  161. package/dist/types/hooks/useRowDragOverride.d.ts +6 -0
  162. package/dist/types/hooks/useTableOrchestration.d.ts +115 -0
  163. package/dist/types/index.d.ts +107 -0
  164. package/dist/types/lib/column-gaps.d.ts +3 -0
  165. package/dist/types/lib/filter-stats.d.ts +2 -0
  166. package/dist/types/lib/format-enum.d.ts +1 -0
  167. package/dist/types/lib/format-timestamp.d.ts +4 -0
  168. package/dist/types/lib/style-scope.d.ts +19 -0
  169. package/dist/types/lib/utils.d.ts +2 -0
  170. package/package.json +114 -0
@@ -0,0 +1,548 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect } from "react";
3
+ import { Input } from "../ui/input.js";
4
+ import { Checkbox } from "../ui/checkbox.js";
5
+ import { Popover, PopoverTrigger, PopoverContent } from "../ui/popover.js";
6
+ import { FilterChip } from "./FilterChip.js";
7
+ import { FilterPopoverFooter } from "./FilterPopoverFooter.js";
8
+ const DEFAULT_LABELS = {
9
+ searchPlaceholder: "Search...",
10
+ addValuePlaceholder: "Add value...",
11
+ noData: "no data",
12
+ statsAllPrefix: "All:",
13
+ statsFilteredPrefix: "Filtered:",
14
+ statsTextAllPrefix: "e.g. All:",
15
+ statsTextFilteredPrefix: "e.g. Filt:",
16
+ chipFrom: "from",
17
+ chipTo: "to",
18
+ chipMin: "min",
19
+ chipMax: "max",
20
+ valuesCount: (n) => `${n} values`,
21
+ selectedCount: (n) => `${n} selected`
22
+ };
23
+ function formatChipDate(iso) {
24
+ const d = new Date(iso);
25
+ if (Number.isNaN(d.getTime())) return iso;
26
+ return d.toLocaleDateString(void 0, { day: "2-digit", month: "short" });
27
+ }
28
+ function isEmptyValue(value, type) {
29
+ if (type === "text") return !Array.isArray(value) || value.length === 0;
30
+ if (type === "enum") return !Array.isArray(value) || value.length === 0;
31
+ if (type === "date")
32
+ return typeof value !== "object" || Array.isArray(value) || !value.from && !value.to;
33
+ if (type === "number-range")
34
+ return typeof value !== "object" || Array.isArray(value) || value.from === void 0 && value.to === void 0;
35
+ return true;
36
+ }
37
+ function ColumnFilterPopover({
38
+ config,
39
+ filter,
40
+ onChange,
41
+ onRemove,
42
+ initialOpen = false,
43
+ chipClassName,
44
+ stats,
45
+ requestOpen,
46
+ onRequestOpenHandled
47
+ }) {
48
+ const [isOpen, setIsOpen] = useState(false);
49
+ const onRequestOpenHandledRef = useRef(onRequestOpenHandled);
50
+ useEffect(() => {
51
+ onRequestOpenHandledRef.current = onRequestOpenHandled;
52
+ });
53
+ useEffect(() => {
54
+ if (!initialOpen) return;
55
+ const id = setTimeout(() => setIsOpen(true), 0);
56
+ return () => clearTimeout(id);
57
+ }, []);
58
+ useEffect(() => {
59
+ if (!requestOpen) return;
60
+ const id = setTimeout(() => {
61
+ setIsOpen(true);
62
+ onRequestOpenHandledRef.current?.();
63
+ }, 0);
64
+ return () => clearTimeout(id);
65
+ }, [requestOpen]);
66
+ const [draftTags, setDraftTags] = useState(
67
+ Array.isArray(filter.value) && config.type === "text" ? filter.value : []
68
+ );
69
+ const [draftInput, setDraftInput] = useState("");
70
+ const [draftEnum, setDraftEnum] = useState(
71
+ Array.isArray(filter.value) ? filter.value : []
72
+ );
73
+ const [draftDate, setDraftDate] = useState(
74
+ typeof filter.value === "object" && !Array.isArray(filter.value) ? {
75
+ from: String(filter.value.from ?? ""),
76
+ to: String(filter.value.to ?? "")
77
+ } : { from: "", to: "" }
78
+ );
79
+ const [draftNumberFrom, setDraftNumberFrom] = useState(
80
+ typeof filter.value === "object" && !Array.isArray(filter.value) && typeof filter.value.from === "number" ? String(filter.value.from) : ""
81
+ );
82
+ const [draftNumberTo, setDraftNumberTo] = useState(
83
+ typeof filter.value === "object" && !Array.isArray(filter.value) && typeof filter.value.to === "number" ? String(filter.value.to) : ""
84
+ );
85
+ const handleOpenChange = (nextOpen) => {
86
+ if (!nextOpen) {
87
+ if (isEmptyValue(filter.value, config.type)) {
88
+ onRemove();
89
+ return;
90
+ }
91
+ setDraftTags(Array.isArray(filter.value) && config.type === "text" ? filter.value : []);
92
+ setDraftInput("");
93
+ setDraftEnum(Array.isArray(filter.value) ? filter.value : []);
94
+ setDraftDate(
95
+ typeof filter.value === "object" && !Array.isArray(filter.value) ? {
96
+ from: String(filter.value.from ?? ""),
97
+ to: String(filter.value.to ?? "")
98
+ } : { from: "", to: "" }
99
+ );
100
+ if (typeof filter.value === "object" && !Array.isArray(filter.value)) {
101
+ const numVal = filter.value;
102
+ setDraftNumberFrom(typeof numVal.from === "number" ? String(numVal.from) : "");
103
+ setDraftNumberTo(typeof numVal.to === "number" ? String(numVal.to) : "");
104
+ } else {
105
+ setDraftNumberFrom("");
106
+ setDraftNumberTo("");
107
+ }
108
+ }
109
+ setIsOpen(nextOpen);
110
+ };
111
+ const handleApply = () => {
112
+ let value;
113
+ switch (config.type) {
114
+ case "text": {
115
+ const trimmed = draftInput.trim();
116
+ const all = trimmed.length > 0 && !draftTags.includes(trimmed) ? [...draftTags, trimmed] : [...draftTags];
117
+ value = all;
118
+ break;
119
+ }
120
+ case "enum":
121
+ value = draftEnum;
122
+ break;
123
+ case "date":
124
+ value = draftDate;
125
+ break;
126
+ case "number-range": {
127
+ const from = draftNumberFrom.trim() !== "" ? Number(draftNumberFrom) : void 0;
128
+ const to = draftNumberTo.trim() !== "" ? Number(draftNumberTo) : void 0;
129
+ value = { from, to };
130
+ break;
131
+ }
132
+ default:
133
+ return;
134
+ }
135
+ if (isEmptyValue(value, config.type)) {
136
+ onRemove();
137
+ } else {
138
+ onChange(value);
139
+ }
140
+ setIsOpen(false);
141
+ };
142
+ const handleClear = () => {
143
+ setDraftTags([]);
144
+ setDraftInput("");
145
+ setDraftEnum([]);
146
+ setDraftDate({ from: "", to: "" });
147
+ setDraftNumberFrom("");
148
+ setDraftNumberTo("");
149
+ };
150
+ const renderContent = () => {
151
+ const allStats = stats?.all ?? null;
152
+ const filteredStats = stats?.filtered ?? null;
153
+ const chipCls = (active) => active ? "rounded border border-[var(--tag-blue-text)]/20 bg-[var(--tag-blue-bg)] px-1.5 py-0.5 text-[11px] text-[var(--tag-blue-text)] cursor-pointer transition-colors" : "rounded border border-[var(--border-subtle)] bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-primary)] hover:bg-[var(--bg-hover)] cursor-pointer transition-colors";
154
+ const truncate = (s, max = 20) => s.length > max ? s.slice(0, max) + "…" : s;
155
+ switch (config.type) {
156
+ case "text": {
157
+ const inTagsCls = "rounded border border-[var(--tag-blue-text)]/20 bg-[var(--tag-blue-bg)] px-1.5 py-0.5 text-[11px] text-[var(--tag-blue-text)] opacity-60 cursor-pointer transition-colors";
158
+ const toggleTag = (s) => {
159
+ setDraftTags((prev) => prev.includes(s) ? prev.filter((t) => t !== s) : [...prev, s]);
160
+ };
161
+ const addTag = (raw) => {
162
+ const trimmed = raw.trim();
163
+ if (trimmed.length === 0 || draftTags.includes(trimmed)) return false;
164
+ setDraftTags((prev) => [...prev, trimmed]);
165
+ setDraftInput("");
166
+ return true;
167
+ };
168
+ const handleTextKeyDown = (e) => {
169
+ const isModEnter = e.key === "Enter" && (e.metaKey || e.ctrlKey);
170
+ if (isModEnter) {
171
+ e.preventDefault();
172
+ handleApply();
173
+ return;
174
+ }
175
+ if (e.key === "Enter") {
176
+ e.preventDefault();
177
+ if (draftInput.trim().length === 0) {
178
+ handleApply();
179
+ } else if (draftTags.length === 0) {
180
+ addTag(draftInput);
181
+ handleApply();
182
+ } else {
183
+ addTag(draftInput);
184
+ }
185
+ return;
186
+ }
187
+ if (e.key === "Backspace" && draftInput.length === 0 && draftTags.length > 0) {
188
+ setDraftTags((prev) => prev.slice(0, -1));
189
+ }
190
+ };
191
+ const renderTextRow = (label, textStats) => {
192
+ if (!textStats || textStats.type !== "text") {
193
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
194
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
195
+ /* @__PURE__ */ jsx("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-tertiary)] italic", children: DEFAULT_LABELS.noData })
196
+ ] });
197
+ }
198
+ const { samples, totalUnique } = textStats;
199
+ const remaining = totalUnique - samples.length;
200
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
201
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
202
+ samples.map((s) => {
203
+ const isInTags = draftTags.includes(s);
204
+ return /* @__PURE__ */ jsx(
205
+ "button",
206
+ {
207
+ type: "button",
208
+ className: isInTags ? inTagsCls : chipCls(false),
209
+ onClick: () => toggleTag(s),
210
+ children: truncate(s)
211
+ },
212
+ s
213
+ );
214
+ }),
215
+ remaining > 0 && /* @__PURE__ */ jsxs("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[9px] text-[var(--text-tertiary)]", children: [
216
+ "+",
217
+ remaining
218
+ ] })
219
+ ] });
220
+ };
221
+ const allText = allStats?.type === "text" ? allStats : null;
222
+ const filtText = filteredStats?.type === "text" ? filteredStats : null;
223
+ return /* @__PURE__ */ jsxs("div", { className: "p-3 w-64 space-y-3", children: [
224
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: [
225
+ "Contains",
226
+ draftTags.length >= 2 && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[10px] font-semibold text-[var(--tag-blue-text)] bg-[var(--tag-blue-bg)] rounded px-1 py-0.5 uppercase", children: "OR" })
227
+ ] }),
228
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 rounded-md border border-[var(--border-subtle)] bg-[var(--bg-surface)] px-2 py-1.5 focus-within:border-[var(--border-focus)]", children: [
229
+ draftTags.map((tag) => /* @__PURE__ */ jsxs(
230
+ "span",
231
+ {
232
+ className: "inline-flex items-center gap-0.5 rounded bg-[var(--tag-blue-bg)] px-1.5 py-0.5 text-[11px] text-[var(--tag-blue-text)] max-w-[140px]",
233
+ children: [
234
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: tag }),
235
+ /* @__PURE__ */ jsx(
236
+ "button",
237
+ {
238
+ type: "button",
239
+ className: "ml-0.5 text-[var(--tag-blue-text)]/60 hover:text-[var(--tag-blue-text)] text-xs leading-none",
240
+ onClick: () => setDraftTags((prev) => prev.filter((t) => t !== tag)),
241
+ children: "✕"
242
+ }
243
+ )
244
+ ]
245
+ },
246
+ tag
247
+ )),
248
+ /* @__PURE__ */ jsx(
249
+ "input",
250
+ {
251
+ autoFocus: true,
252
+ className: "flex-1 min-w-[60px] bg-transparent text-sm text-[var(--text-primary)] outline-none placeholder:text-[var(--text-tertiary)]",
253
+ placeholder: draftTags.length === 0 ? DEFAULT_LABELS.searchPlaceholder : DEFAULT_LABELS.addValuePlaceholder,
254
+ value: draftInput,
255
+ onChange: (e) => setDraftInput(e.target.value),
256
+ onKeyDown: handleTextKeyDown
257
+ }
258
+ )
259
+ ] }),
260
+ allText && /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--border-subtle)] pt-2 space-y-2", children: [
261
+ renderTextRow(DEFAULT_LABELS.statsTextAllPrefix, allText),
262
+ filteredStats !== null && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderTextRow(DEFAULT_LABELS.statsTextFilteredPrefix, filtText) })
263
+ ] })
264
+ ] });
265
+ }
266
+ case "enum": {
267
+ const enumAllStats = allStats?.type === "enum" ? allStats : null;
268
+ const enumFiltStats = filteredStats?.type === "enum" ? filteredStats : null;
269
+ return /* @__PURE__ */ jsx("div", { className: "p-2 w-56 space-y-1", children: config.options?.map((option) => {
270
+ const allCount = enumAllStats?.counts[option.value];
271
+ const filtCount = enumFiltStats?.counts[option.value];
272
+ return /* @__PURE__ */ jsxs(
273
+ "label",
274
+ {
275
+ className: "flex items-center gap-2 px-2 py-1.5 hover:bg-[var(--bg-secondary)] rounded-md cursor-pointer transition-colors",
276
+ children: [
277
+ /* @__PURE__ */ jsx(
278
+ Checkbox,
279
+ {
280
+ checked: draftEnum.includes(option.value),
281
+ onCheckedChange: (checked) => {
282
+ setDraftEnum(
283
+ (prev) => checked ? [...prev, option.value] : prev.filter((v) => v !== option.value)
284
+ );
285
+ }
286
+ }
287
+ ),
288
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm text-[var(--text-primary)]", children: option.label }),
289
+ allCount !== void 0 && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-[var(--text-tertiary)] tabular-nums", children: filtCount !== void 0 ? `${filtCount} / ${allCount}` : allCount })
290
+ ]
291
+ },
292
+ option.value
293
+ );
294
+ }) });
295
+ }
296
+ case "date": {
297
+ const renderDateRow = (label, dateStats) => {
298
+ if (!dateStats || dateStats.type !== "date") {
299
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
300
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
301
+ /* @__PURE__ */ jsx("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-tertiary)] italic", children: DEFAULT_LABELS.noData })
302
+ ] });
303
+ }
304
+ const { min, max } = dateStats;
305
+ if (min === max) {
306
+ const isActive = draftDate.from === min && draftDate.to === min;
307
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
308
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
309
+ /* @__PURE__ */ jsx(
310
+ "button",
311
+ {
312
+ type: "button",
313
+ className: chipCls(isActive),
314
+ onClick: () => setDraftDate({ from: min, to: min }),
315
+ children: formatChipDate(min)
316
+ }
317
+ )
318
+ ] });
319
+ }
320
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
321
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
322
+ /* @__PURE__ */ jsxs(
323
+ "button",
324
+ {
325
+ type: "button",
326
+ className: chipCls(draftDate.from === min),
327
+ onClick: () => setDraftDate((prev) => ({ ...prev, from: min })),
328
+ children: [
329
+ DEFAULT_LABELS.chipFrom,
330
+ " ",
331
+ formatChipDate(min)
332
+ ]
333
+ }
334
+ ),
335
+ /* @__PURE__ */ jsxs(
336
+ "button",
337
+ {
338
+ type: "button",
339
+ className: chipCls(draftDate.to === max),
340
+ onClick: () => setDraftDate((prev) => ({ ...prev, to: max })),
341
+ children: [
342
+ DEFAULT_LABELS.chipTo,
343
+ " ",
344
+ formatChipDate(max)
345
+ ]
346
+ }
347
+ )
348
+ ] });
349
+ };
350
+ const allDate = allStats?.type === "date" ? allStats : null;
351
+ const filtDate = filteredStats?.type === "date" ? filteredStats : null;
352
+ return /* @__PURE__ */ jsxs("div", { className: "p-3 w-64 space-y-4", children: [
353
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
354
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: DEFAULT_LABELS.chipFrom }),
355
+ /* @__PURE__ */ jsx(
356
+ Input,
357
+ {
358
+ type: "date",
359
+ value: draftDate.from,
360
+ onChange: (e) => setDraftDate((prev) => ({ ...prev, from: e.target.value }))
361
+ }
362
+ )
363
+ ] }),
364
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
365
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: DEFAULT_LABELS.chipTo }),
366
+ /* @__PURE__ */ jsx(
367
+ Input,
368
+ {
369
+ type: "date",
370
+ value: draftDate.to,
371
+ onChange: (e) => setDraftDate((prev) => ({ ...prev, to: e.target.value }))
372
+ }
373
+ )
374
+ ] }),
375
+ allDate && /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--border-subtle)] pt-2 space-y-2", children: [
376
+ renderDateRow(DEFAULT_LABELS.statsAllPrefix, allDate),
377
+ filteredStats !== null && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderDateRow(DEFAULT_LABELS.statsFilteredPrefix, filtDate) })
378
+ ] })
379
+ ] });
380
+ }
381
+ case "number-range": {
382
+ const renderNumRow = (label, numStats) => {
383
+ if (!numStats || numStats.type !== "number-range") {
384
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
385
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
386
+ /* @__PURE__ */ jsx("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-tertiary)] italic", children: DEFAULT_LABELS.noData })
387
+ ] });
388
+ }
389
+ const { min, max } = numStats;
390
+ if (min === max) {
391
+ const isActive = draftNumberFrom === String(min) && draftNumberTo === String(min);
392
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
393
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
394
+ /* @__PURE__ */ jsx(
395
+ "button",
396
+ {
397
+ type: "button",
398
+ className: chipCls(isActive),
399
+ onClick: () => {
400
+ setDraftNumberFrom(String(min));
401
+ setDraftNumberTo(String(min));
402
+ },
403
+ children: min
404
+ }
405
+ )
406
+ ] });
407
+ }
408
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
409
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
410
+ /* @__PURE__ */ jsxs(
411
+ "button",
412
+ {
413
+ type: "button",
414
+ className: chipCls(draftNumberFrom === String(min)),
415
+ onClick: () => setDraftNumberFrom(String(min)),
416
+ children: [
417
+ DEFAULT_LABELS.chipMin,
418
+ " ",
419
+ min
420
+ ]
421
+ }
422
+ ),
423
+ /* @__PURE__ */ jsxs(
424
+ "button",
425
+ {
426
+ type: "button",
427
+ className: chipCls(draftNumberTo === String(max)),
428
+ onClick: () => setDraftNumberTo(String(max)),
429
+ children: [
430
+ DEFAULT_LABELS.chipMax,
431
+ " ",
432
+ max
433
+ ]
434
+ }
435
+ )
436
+ ] });
437
+ };
438
+ const allNum = allStats?.type === "number-range" ? allStats : null;
439
+ const filtNum = filteredStats?.type === "number-range" ? filteredStats : null;
440
+ return /* @__PURE__ */ jsxs("div", { className: "p-3 w-64 space-y-4", children: [
441
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
442
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: DEFAULT_LABELS.chipMin }),
443
+ /* @__PURE__ */ jsx(
444
+ Input,
445
+ {
446
+ type: "number",
447
+ autoFocus: true,
448
+ placeholder: allNum ? String(allNum.min) : "0",
449
+ value: draftNumberFrom,
450
+ onChange: (e) => setDraftNumberFrom(e.target.value),
451
+ onKeyDown: (e) => {
452
+ if (e.key === "Enter") {
453
+ e.preventDefault();
454
+ handleApply();
455
+ }
456
+ }
457
+ }
458
+ )
459
+ ] }),
460
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
461
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: DEFAULT_LABELS.chipMax }),
462
+ /* @__PURE__ */ jsx(
463
+ Input,
464
+ {
465
+ type: "number",
466
+ placeholder: allNum ? String(allNum.max) : "0",
467
+ value: draftNumberTo,
468
+ onChange: (e) => setDraftNumberTo(e.target.value),
469
+ onKeyDown: (e) => {
470
+ if (e.key === "Enter") {
471
+ e.preventDefault();
472
+ handleApply();
473
+ }
474
+ }
475
+ }
476
+ )
477
+ ] }),
478
+ allNum && /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--border-subtle)] pt-2 space-y-2", children: [
479
+ renderNumRow(DEFAULT_LABELS.statsAllPrefix, allNum),
480
+ filteredStats !== null && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderNumRow(DEFAULT_LABELS.statsFilteredPrefix, filtNum) })
481
+ ] })
482
+ ] });
483
+ }
484
+ default:
485
+ return null;
486
+ }
487
+ };
488
+ const getDisplayValue = () => {
489
+ const { value } = filter;
490
+ if (isEmptyValue(value, config.type)) return void 0;
491
+ if (config.type === "text" && Array.isArray(value)) {
492
+ if (value.length === 1) {
493
+ return value[0].length > 20 ? `${value[0].slice(0, 20)}…` : value[0];
494
+ }
495
+ if (value.length === 2) {
496
+ const joined = value.join(", ");
497
+ return joined.length > 24 ? `${value.length} values` : joined;
498
+ }
499
+ return DEFAULT_LABELS.valuesCount(value.length);
500
+ }
501
+ if (config.type === "enum" && Array.isArray(value)) {
502
+ if (value.length === 1) {
503
+ return config.options?.find((o) => o.value === value[0])?.label ?? value[0];
504
+ }
505
+ return DEFAULT_LABELS.selectedCount(value.length);
506
+ }
507
+ if (config.type === "date" && typeof value === "object" && !Array.isArray(value)) {
508
+ const { from, to } = value;
509
+ if (from && to) return `${from} — ${to}`;
510
+ if (from) return `${DEFAULT_LABELS.chipFrom} ${from}`;
511
+ if (to) return `${DEFAULT_LABELS.chipTo} ${to}`;
512
+ }
513
+ if (config.type === "number-range" && typeof value === "object" && !Array.isArray(value)) {
514
+ const { from, to } = value;
515
+ if (from !== void 0 && to !== void 0) return `${from}–${to}`;
516
+ if (from !== void 0) return `≥${from}`;
517
+ if (to !== void 0) return `≤${to}`;
518
+ }
519
+ return void 0;
520
+ };
521
+ return /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: handleOpenChange, children: [
522
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
523
+ FilterChip,
524
+ {
525
+ label: config.label,
526
+ value: getDisplayValue(),
527
+ onRemove,
528
+ active: !!getDisplayValue(),
529
+ className: chipClassName
530
+ }
531
+ ) }),
532
+ /* @__PURE__ */ jsxs(
533
+ PopoverContent,
534
+ {
535
+ align: "start",
536
+ collisionPadding: 8,
537
+ className: "w-auto p-0 [box-shadow:var(--shadow-menu)] border-[var(--border-subtle)] flex flex-col max-h-[min(var(--radix-popover-content-available-height),520px)] overflow-hidden",
538
+ children: [
539
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto overscroll-contain", children: renderContent() }),
540
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 bg-[var(--bg-surface)]", children: /* @__PURE__ */ jsx(FilterPopoverFooter, { onApply: handleApply, onClear: handleClear }) })
541
+ ]
542
+ }
543
+ )
544
+ ] });
545
+ }
546
+ export {
547
+ ColumnFilterPopover
548
+ };