@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,357 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useMemo, useState, useRef, useEffect, useCallback } from "react";
3
+ import { X } from "lucide-react";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { Button } from "../ui/button.js";
6
+ import { ButtonFooter } from "../ui/button-footer.js";
7
+ import { Input } from "../ui/input.js";
8
+ import { Combobox } from "../ui/combobox.js";
9
+ import { hasActiveManagedFilters } from "./managed-filters.js";
10
+ function isValueEmpty(v) {
11
+ if (v === void 0) return true;
12
+ if (Array.isArray(v)) return v.length === 0 || v.every((x) => x === "" || x == null);
13
+ if (typeof v === "string") return v.length === 0;
14
+ if (typeof v === "object" && v !== null) {
15
+ return Object.values(v).every((x) => isValueEmpty(x));
16
+ }
17
+ return false;
18
+ }
19
+ function deriveInitialState(fields, filters) {
20
+ const byId = new Map(filters.map((f) => [f.columnId, f.value]));
21
+ const next = {};
22
+ for (const id of fields) next[id] = byId.get(id);
23
+ return next;
24
+ }
25
+ function FilterFormPanel({
26
+ filterConfigs,
27
+ fields,
28
+ filters,
29
+ onApply,
30
+ onClear,
31
+ title = "Filters",
32
+ className,
33
+ id,
34
+ onClose,
35
+ hiddenSubFilterKeys
36
+ }) {
37
+ const hiddenSubSet = useMemo(() => new Set(hiddenSubFilterKeys ?? []), [hiddenSubFilterKeys]);
38
+ const renderableFields = useMemo(
39
+ () => fields.filter((id2) => {
40
+ if (filterConfigs[id2]) return true;
41
+ console.warn(`[FilterFormPanel] Unknown columnId in 'fields': ${id2}`);
42
+ return false;
43
+ }),
44
+ [fields, filterConfigs]
45
+ );
46
+ const [state, setState] = useState(
47
+ () => deriveInitialState(renderableFields, filters)
48
+ );
49
+ const touchedRef = useRef(/* @__PURE__ */ new Set());
50
+ useEffect(() => {
51
+ const fresh = deriveInitialState(renderableFields, filters);
52
+ setState((prev) => {
53
+ const next = { ...prev };
54
+ let changed = false;
55
+ for (const fieldId of renderableFields) {
56
+ if (touchedRef.current.has(fieldId)) continue;
57
+ if (next[fieldId] !== fresh[fieldId]) {
58
+ next[fieldId] = fresh[fieldId];
59
+ changed = true;
60
+ }
61
+ }
62
+ return changed ? next : prev;
63
+ });
64
+ }, [filters, renderableFields]);
65
+ const setField = useCallback((id2, value) => {
66
+ touchedRef.current.add(id2);
67
+ setState((prev) => ({ ...prev, [id2]: value }));
68
+ }, []);
69
+ const handleApply = useCallback(() => {
70
+ const activeIds = new Set(filters.map((f) => f.columnId));
71
+ const changes = [];
72
+ for (const id2 of renderableFields) {
73
+ const value = state[id2];
74
+ if (isValueEmpty(value)) {
75
+ if (activeIds.has(id2)) changes.push({ columnId: id2, action: "remove" });
76
+ } else {
77
+ changes.push({ columnId: id2, action: "set", value });
78
+ }
79
+ }
80
+ onApply(changes);
81
+ touchedRef.current.clear();
82
+ }, [filters, onApply, renderableFields, state]);
83
+ const handleClear = useCallback(() => {
84
+ setState(deriveInitialState(renderableFields, []));
85
+ onClear(renderableFields);
86
+ touchedRef.current.clear();
87
+ }, [onClear, renderableFields]);
88
+ const isClearDisabled = !hasActiveManagedFilters(filters, renderableFields) && renderableFields.every((fieldId) => isValueEmpty(state[fieldId]));
89
+ return /* @__PURE__ */ jsxs(
90
+ "form",
91
+ {
92
+ id,
93
+ "aria-label": title,
94
+ onSubmit: (e) => {
95
+ e.preventDefault();
96
+ handleApply();
97
+ },
98
+ className: cn(
99
+ "mb-3 rounded-md border border-[var(--border-subtle)] bg-[var(--bg-surface-sunken)]",
100
+ className
101
+ ),
102
+ children: [
103
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 pb-2 pt-4", children: [
104
+ /* @__PURE__ */ jsx("h2", { className: "text-[12px] font-semibold uppercase tracking-[0.08em] text-[var(--text-secondary)]", children: title }),
105
+ onClose ? /* @__PURE__ */ jsx(
106
+ Button,
107
+ {
108
+ type: "button",
109
+ variant: "ghost",
110
+ size: "icon",
111
+ "aria-label": "Close filters",
112
+ title: "Close filters",
113
+ onClick: onClose,
114
+ className: "h-7 w-7",
115
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
116
+ }
117
+ ) : null
118
+ ] }),
119
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] items-end gap-3 px-6 pb-4", children: renderableFields.map((fieldId) => {
120
+ const config = filterConfigs[fieldId];
121
+ if (!config) return null;
122
+ return /* @__PURE__ */ jsx(
123
+ Field,
124
+ {
125
+ config,
126
+ value: state[fieldId],
127
+ onChange: (v) => setField(fieldId, v),
128
+ fieldId,
129
+ hiddenSubSet
130
+ },
131
+ fieldId
132
+ );
133
+ }) }),
134
+ /* @__PURE__ */ jsx("div", { className: "px-6 pb-4", children: /* @__PURE__ */ jsxs(ButtonFooter, { align: "end", children: [
135
+ /* @__PURE__ */ jsx(
136
+ Button,
137
+ {
138
+ type: "button",
139
+ variant: "secondary",
140
+ size: "sm",
141
+ onClick: handleClear,
142
+ disabled: isClearDisabled,
143
+ children: "Clear"
144
+ }
145
+ ),
146
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", children: "Apply" })
147
+ ] }) })
148
+ ]
149
+ }
150
+ );
151
+ }
152
+ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
153
+ if (config.type === "text") {
154
+ const text = Array.isArray(value) ? value[0] ?? "" : "";
155
+ return /* @__PURE__ */ jsxs("label", { className: "flex min-w-0 flex-col gap-1.5", children: [
156
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
157
+ /* @__PURE__ */ jsx(
158
+ Input,
159
+ {
160
+ "aria-label": config.label,
161
+ value: text,
162
+ onChange: (e) => {
163
+ const v = e.target.value;
164
+ onChange(v === "" ? void 0 : [v]);
165
+ },
166
+ className: "h-9 min-w-0"
167
+ }
168
+ )
169
+ ] });
170
+ }
171
+ if (config.type === "enum") {
172
+ const selected = Array.isArray(value) ? value[0] ?? "" : "";
173
+ return /* @__PURE__ */ jsxs("label", { className: "flex min-w-0 flex-col gap-1.5", children: [
174
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
175
+ /* @__PURE__ */ jsx(
176
+ Combobox,
177
+ {
178
+ value: selected || null,
179
+ onChange: (v) => onChange(v ? [v] : void 0),
180
+ options: config.options ?? [],
181
+ placeholder: "Any",
182
+ triggerAriaLabel: config.label,
183
+ triggerClassName: "h-9 min-w-0"
184
+ }
185
+ )
186
+ ] });
187
+ }
188
+ if (config.type === "date") {
189
+ const range = value && !Array.isArray(value) && typeof value === "object" ? value : {};
190
+ const update = (key) => (e) => {
191
+ const v = e.target.value;
192
+ const next = { ...range, [key]: v || void 0 };
193
+ const cleaned = next.from || next.to ? next : void 0;
194
+ onChange(cleaned);
195
+ };
196
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1.5 [grid-column:span_2]", children: [
197
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
198
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 gap-2", children: [
199
+ /* @__PURE__ */ jsx(
200
+ Input,
201
+ {
202
+ type: "date",
203
+ "aria-label": `${config.label} — from`,
204
+ value: range.from ?? "",
205
+ onChange: update("from"),
206
+ className: "h-9 min-w-0 flex-1"
207
+ }
208
+ ),
209
+ /* @__PURE__ */ jsx(
210
+ Input,
211
+ {
212
+ type: "date",
213
+ "aria-label": `${config.label} — to`,
214
+ value: range.to ?? "",
215
+ onChange: update("to"),
216
+ className: "h-9 min-w-0 flex-1"
217
+ }
218
+ )
219
+ ] })
220
+ ] });
221
+ }
222
+ if (config.type === "number-range") {
223
+ const range = value && !Array.isArray(value) && typeof value === "object" ? value : {};
224
+ const update = (key) => (e) => {
225
+ const raw = e.target.value;
226
+ const parsed = raw === "" ? void 0 : Number(raw);
227
+ const next = { ...range, [key]: Number.isFinite(parsed) ? parsed : void 0 };
228
+ const cleaned = next.from !== void 0 || next.to !== void 0 ? next : void 0;
229
+ onChange(cleaned);
230
+ };
231
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1.5 [grid-column:span_2]", children: [
232
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
233
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 gap-2", children: [
234
+ /* @__PURE__ */ jsx(
235
+ Input,
236
+ {
237
+ type: "number",
238
+ "aria-label": `${config.label} — from`,
239
+ value: range.from ?? "",
240
+ onChange: update("from"),
241
+ placeholder: "From",
242
+ className: "h-9 min-w-0 flex-1"
243
+ }
244
+ ),
245
+ /* @__PURE__ */ jsx(
246
+ Input,
247
+ {
248
+ type: "number",
249
+ "aria-label": `${config.label} — to`,
250
+ value: range.to ?? "",
251
+ onChange: update("to"),
252
+ placeholder: "To",
253
+ className: "h-9 min-w-0 flex-1"
254
+ }
255
+ )
256
+ ] })
257
+ ] });
258
+ }
259
+ if (config.type === "compound") {
260
+ const compound = value && !Array.isArray(value) && typeof value === "object" ? value : {};
261
+ const updateSub = (subField, next) => {
262
+ const rest = { ...compound };
263
+ delete rest[subField];
264
+ const merged = next === void 0 || isValueEmpty(next) ? rest : { ...rest, [subField]: next };
265
+ onChange(Object.keys(merged).length === 0 ? void 0 : merged);
266
+ };
267
+ const visibleSubs = (config.subFilters ?? []).filter(
268
+ (sub) => !hiddenSubSet.has(`${fieldId}:${sub.key}`)
269
+ );
270
+ if (visibleSubs.length === 0) return null;
271
+ const subCount = visibleSubs.length;
272
+ return /* @__PURE__ */ jsxs(
273
+ "div",
274
+ {
275
+ className: "flex min-w-0 flex-col gap-1.5",
276
+ style: { gridColumn: `span ${Math.max(2, Math.min(subCount, 4))}` },
277
+ children: [
278
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-[var(--text-secondary)]", children: config.label }),
279
+ /* @__PURE__ */ jsx("div", { className: "grid min-w-0 gap-2 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]", children: visibleSubs.map((sub) => {
280
+ const subValue = compound[sub.field];
281
+ if (sub.type === "text") {
282
+ const text = Array.isArray(subValue) ? subValue[0] ?? "" : "";
283
+ return /* @__PURE__ */ jsx(
284
+ Input,
285
+ {
286
+ "aria-label": sub.label,
287
+ placeholder: sub.label,
288
+ value: text,
289
+ onChange: (e) => {
290
+ const v = e.target.value;
291
+ updateSub(sub.field, v === "" ? void 0 : [v]);
292
+ },
293
+ className: "h-9 min-w-0"
294
+ },
295
+ sub.key
296
+ );
297
+ }
298
+ if (sub.type === "enum") {
299
+ const selected = Array.isArray(subValue) ? subValue[0] ?? "" : "";
300
+ return /* @__PURE__ */ jsx(
301
+ Combobox,
302
+ {
303
+ value: selected || null,
304
+ onChange: (v) => updateSub(sub.field, v ? [v] : void 0),
305
+ options: sub.options ?? [],
306
+ placeholder: sub.label,
307
+ triggerAriaLabel: sub.label,
308
+ triggerClassName: "h-9 min-w-0"
309
+ },
310
+ sub.key
311
+ );
312
+ }
313
+ if (sub.type === "number-range") {
314
+ const range = subValue && !Array.isArray(subValue) && typeof subValue === "object" ? subValue : {};
315
+ const updateRange = (key) => (e) => {
316
+ const raw = e.target.value;
317
+ const parsed = raw === "" ? void 0 : Number(raw);
318
+ const next = { ...range, [key]: Number.isFinite(parsed) ? parsed : void 0 };
319
+ const cleaned = next.from !== void 0 || next.to !== void 0 ? next : void 0;
320
+ updateSub(sub.field, cleaned);
321
+ };
322
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 gap-2", children: [
323
+ /* @__PURE__ */ jsx(
324
+ Input,
325
+ {
326
+ type: "number",
327
+ "aria-label": `${sub.label} — from`,
328
+ value: range.from ?? "",
329
+ onChange: updateRange("from"),
330
+ placeholder: `${sub.label} from`,
331
+ className: "h-9 min-w-0 flex-1"
332
+ }
333
+ ),
334
+ /* @__PURE__ */ jsx(
335
+ Input,
336
+ {
337
+ type: "number",
338
+ "aria-label": `${sub.label} — to`,
339
+ value: range.to ?? "",
340
+ onChange: updateRange("to"),
341
+ placeholder: `${sub.label} to`,
342
+ className: "h-9 min-w-0 flex-1"
343
+ }
344
+ )
345
+ ] }, sub.key);
346
+ }
347
+ return null;
348
+ }) })
349
+ ]
350
+ }
351
+ );
352
+ }
353
+ return null;
354
+ }
355
+ export {
356
+ FilterFormPanel
357
+ };
@@ -0,0 +1,77 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Filter, ChevronDown } from "lucide-react";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { Button } from "../ui/button.js";
6
+ const STORAGE_PREFIX = "tablewright.filters.open.";
7
+ function readPersisted(persistKey, fallback) {
8
+ if (typeof window === "undefined") return fallback;
9
+ const raw = window.localStorage.getItem(STORAGE_PREFIX + persistKey);
10
+ if (raw === "1") return true;
11
+ if (raw === "0") return false;
12
+ return fallback;
13
+ }
14
+ function useFilterFormOpen(persistKey, defaultOpen = false) {
15
+ const [open, setOpen] = useState(() => readPersisted(persistKey, defaultOpen));
16
+ useEffect(() => {
17
+ if (typeof window === "undefined") return;
18
+ window.localStorage.setItem(STORAGE_PREFIX + persistKey, open ? "1" : "0");
19
+ }, [open, persistKey]);
20
+ return [open, setOpen];
21
+ }
22
+ function FilterFormToggle({
23
+ open,
24
+ onOpenChange,
25
+ hasActiveFilters = false,
26
+ controlsId,
27
+ variant = "icon",
28
+ label = "Filter",
29
+ className
30
+ }) {
31
+ const showDot = !open && hasActiveFilters;
32
+ const isButton = variant === "button";
33
+ return /* @__PURE__ */ jsxs(
34
+ Button,
35
+ {
36
+ type: "button",
37
+ variant: "secondary",
38
+ size: isButton ? "default" : "icon",
39
+ "aria-label": isButton ? void 0 : "Filter form",
40
+ "aria-expanded": open,
41
+ "aria-controls": controlsId,
42
+ title: open ? "Hide filters" : "Show filters",
43
+ onClick: () => onOpenChange(!open),
44
+ className: cn(
45
+ "relative",
46
+ open && "border-[var(--border-accent,#6366f1)] bg-[var(--bg-accent-subtle,rgba(99,102,241,0.12))] text-[var(--text-accent,#3730a3)]",
47
+ className
48
+ ),
49
+ children: [
50
+ /* @__PURE__ */ jsx(Filter, { className: "h-4 w-4" }),
51
+ isButton ? /* @__PURE__ */ jsx("span", { children: label }) : null,
52
+ isButton ? /* @__PURE__ */ jsx(
53
+ ChevronDown,
54
+ {
55
+ className: cn(
56
+ "h-4 w-4 text-[var(--text-tertiary)] transition-transform duration-200",
57
+ open && "rotate-180"
58
+ ),
59
+ "aria-hidden": "true"
60
+ }
61
+ ) : null,
62
+ showDot ? /* @__PURE__ */ jsx(
63
+ "span",
64
+ {
65
+ "data-testid": "filter-form-toggle-dot",
66
+ className: "absolute right-0.5 top-0.5 h-1.5 w-1.5 rounded-full border border-[var(--bg-elevated,#ffffff)] bg-[var(--bg-accent,#6366f1)]",
67
+ "aria-hidden": "true"
68
+ }
69
+ ) : null
70
+ ]
71
+ }
72
+ );
73
+ }
74
+ export {
75
+ FilterFormToggle,
76
+ useFilterFormOpen
77
+ };
@@ -0,0 +1,19 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/utils.js";
3
+ import { Button } from "../ui/button.js";
4
+ import { ButtonFooter } from "../ui/button-footer.js";
5
+ function FilterPopoverFooter({
6
+ onApply,
7
+ onClear,
8
+ applyLabel = "Apply",
9
+ clearLabel = "Clear",
10
+ className
11
+ }) {
12
+ return /* @__PURE__ */ jsxs(ButtonFooter, { className: cn("px-3 py-2.5", className), children: [
13
+ onClear && /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: onClear, children: clearLabel }),
14
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: onApply, children: applyLabel })
15
+ ] });
16
+ }
17
+ export {
18
+ FilterPopoverFooter
19
+ };
@@ -0,0 +1,131 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Columns3, ChevronDown } from "lucide-react";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { Button } from "../ui/button.js";
5
+ import { SegmentedControl } from "../ui/segmented-control.js";
6
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuCheckboxItem } from "../ui/dropdown-menu.js";
7
+ function Root({
8
+ children,
9
+ className,
10
+ ...props
11
+ }) {
12
+ return /* @__PURE__ */ jsx("section", { className: cn("py-3", className), ...props, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-start gap-2.5 lg:items-center", children }) });
13
+ }
14
+ function SearchForm({
15
+ children,
16
+ className,
17
+ ...props
18
+ }) {
19
+ return /* @__PURE__ */ jsx(
20
+ "form",
21
+ {
22
+ className: cn(
23
+ "flex min-w-[280px] flex-[0_1_360px] items-center gap-2 md:max-w-[460px]",
24
+ className
25
+ ),
26
+ ...props,
27
+ children
28
+ }
29
+ );
30
+ }
31
+ function Filters({
32
+ children,
33
+ className,
34
+ ...props
35
+ }) {
36
+ return /* @__PURE__ */ jsx(
37
+ "div",
38
+ {
39
+ className: cn("flex min-w-0 flex-wrap items-center gap-2.5 lg:flex-nowrap lg:gap-2", className),
40
+ ...props,
41
+ children
42
+ }
43
+ );
44
+ }
45
+ function Utility({
46
+ children,
47
+ className,
48
+ ...props
49
+ }) {
50
+ return /* @__PURE__ */ jsx(
51
+ "div",
52
+ {
53
+ className: cn("flex flex-wrap items-center gap-2 lg:flex-nowrap", className),
54
+ ...props,
55
+ children
56
+ }
57
+ );
58
+ }
59
+ function Actions({
60
+ children,
61
+ className,
62
+ ...props
63
+ }) {
64
+ return /* @__PURE__ */ jsx(
65
+ "div",
66
+ {
67
+ className: cn("flex flex-wrap items-center gap-2 xl:ml-auto", className),
68
+ ...props,
69
+ children
70
+ }
71
+ );
72
+ }
73
+ function ActiveFilters({
74
+ children,
75
+ className
76
+ }) {
77
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap items-center gap-2", className), children });
78
+ }
79
+ function ColumnVisibilityMenu({
80
+ columns,
81
+ onColumnToggle,
82
+ label = "Show/Hide columns",
83
+ buttonClassName
84
+ }) {
85
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
86
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "secondary", className: cn("shrink-0", buttonClassName), children: [
87
+ /* @__PURE__ */ jsx(Columns3, { className: "h-4 w-4" }),
88
+ label,
89
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
90
+ ] }) }),
91
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "start", className: "w-64", children: /* @__PURE__ */ jsx(DropdownMenuGroup, { children: columns.map((column) => /* @__PURE__ */ jsx(
92
+ DropdownMenuCheckboxItem,
93
+ {
94
+ checked: column.checked,
95
+ disabled: column.disabled,
96
+ onCheckedChange: () => onColumnToggle(column.id),
97
+ children: column.label
98
+ },
99
+ column.id
100
+ )) }) })
101
+ ] });
102
+ }
103
+ function DensityControl({
104
+ density,
105
+ onDensityChange,
106
+ className
107
+ }) {
108
+ return /* @__PURE__ */ jsx(
109
+ SegmentedControl,
110
+ {
111
+ value: density,
112
+ options: [
113
+ { value: "normal", label: "Normal" },
114
+ { value: "dense", label: "Dense" }
115
+ ],
116
+ onValueChange: onDensityChange,
117
+ variant: "toolbar",
118
+ className
119
+ }
120
+ );
121
+ }
122
+ export {
123
+ Actions,
124
+ ActiveFilters,
125
+ ColumnVisibilityMenu,
126
+ DensityControl,
127
+ Filters,
128
+ Root,
129
+ SearchForm,
130
+ Utility
131
+ };
@@ -0,0 +1,46 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { Columns, ChevronDown } from "lucide-react";
3
+ import { Button } from "../ui/button.js";
4
+ import { Checkbox } from "../ui/checkbox.js";
5
+ import { Popover, PopoverTrigger, PopoverContent } from "../ui/popover.js";
6
+ function FlatColumnToggle({
7
+ toggleableColumnIds,
8
+ columnLabels,
9
+ columnVisibility,
10
+ onColumnToggle,
11
+ open,
12
+ onOpenChange
13
+ }) {
14
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange, children: [
15
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "secondary", children: [
16
+ /* @__PURE__ */ jsx(Columns, { size: 14 }),
17
+ "Columns",
18
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 text-[var(--text-tertiary)]" })
19
+ ] }) }),
20
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-48 p-1", children: toggleableColumnIds.map((columnId) => {
21
+ const isChecked = columnVisibility[columnId] !== false;
22
+ return /* @__PURE__ */ jsxs(
23
+ "label",
24
+ {
25
+ className: "flex w-full cursor-pointer items-center gap-2.5 rounded-md px-2 py-2 text-left hover:bg-[var(--bg-hover)]",
26
+ style: { fontSize: 13 },
27
+ children: [
28
+ /* @__PURE__ */ jsx(
29
+ Checkbox,
30
+ {
31
+ checked: isChecked,
32
+ onCheckedChange: () => onColumnToggle(columnId),
33
+ className: "h-3.5 w-3.5 rounded-[3px]"
34
+ }
35
+ ),
36
+ /* @__PURE__ */ jsx("span", { children: columnLabels[columnId] ?? columnId })
37
+ ]
38
+ },
39
+ columnId
40
+ );
41
+ }) })
42
+ ] });
43
+ }
44
+ export {
45
+ FlatColumnToggle
46
+ };
@@ -0,0 +1,29 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ const GAP_HEADER_ATTR = "data-gap-header";
3
+ const GAP_CELL_ATTR = "data-gap-cell";
4
+ function GapIndicator({
5
+ onClick,
6
+ ariaLabel = "Show hidden columns",
7
+ children = "‹›"
8
+ }) {
9
+ return /* @__PURE__ */ jsx(
10
+ "button",
11
+ {
12
+ type: "button",
13
+ ...{ [GAP_HEADER_ATTR]: true },
14
+ className: "absolute inset-0 flex cursor-pointer items-center justify-center bg-[rgba(128,128,128,0.03)] text-[10px] text-[var(--text-secondary)] transition-colors hover:bg-[rgba(128,128,128,0.08)] hover:text-[var(--text-accent,var(--text-primary))]",
15
+ onClick,
16
+ "aria-label": ariaLabel,
17
+ children
18
+ }
19
+ );
20
+ }
21
+ function GapCell() {
22
+ return /* @__PURE__ */ jsx("div", { ...{ [GAP_CELL_ATTR]: true } });
23
+ }
24
+ export {
25
+ GAP_CELL_ATTR,
26
+ GAP_HEADER_ATTR,
27
+ GapCell,
28
+ GapIndicator
29
+ };