@hyddenlabs/hydn-ui 0.3.2 → 0.3.4
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.
- package/dist/components/data-display/data-grid/data-grid-cell.d.ts +37 -0
- package/dist/components/data-display/data-grid/data-grid-cell.d.ts.map +1 -0
- package/dist/components/data-display/data-grid/data-grid-cell.js +19 -0
- package/dist/components/data-display/data-grid/data-grid-cell.js.map +1 -0
- package/dist/components/data-display/data-grid/data-grid.d.ts +204 -0
- package/dist/components/data-display/data-grid/data-grid.d.ts.map +1 -0
- package/dist/components/data-display/data-grid/data-grid.js +566 -0
- package/dist/components/data-display/data-grid/data-grid.js.map +1 -0
- package/dist/components/data-display/data-grid/index.d.ts +5 -0
- package/dist/components/data-display/data-grid/index.d.ts.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/navigation/dropdown/dropdown.d.ts +8 -2
- package/dist/components/navigation/dropdown/dropdown.d.ts.map +1 -1
- package/dist/components/navigation/dropdown/dropdown.js +18 -4
- package/dist/components/navigation/dropdown/dropdown.js.map +1 -1
- package/dist/components/navigation/navbar/navbar.d.ts.map +1 -1
- package/dist/components/navigation/navbar/navbar.js +1 -2
- package/dist/components/navigation/navbar/navbar.js.map +1 -1
- package/dist/index.js +154 -150
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/theme/size-tokens.d.ts +3 -3
- package/dist/theme/size-tokens.js +3 -3
- package/dist/theme/size-tokens.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect } from "react";
|
|
3
|
+
import { Icon } from "../../system/icon/icon.js";
|
|
4
|
+
import IconButton from "../../forms/button/icon-button.js";
|
|
5
|
+
import Input from "../../forms/input/input.js";
|
|
6
|
+
import InputGroup from "../../forms/input-group/input-group.js";
|
|
7
|
+
import Checkbox from "../../forms/checkbox/checkbox.js";
|
|
8
|
+
import Button from "../../forms/button/button.js";
|
|
9
|
+
import Tooltip from "../../feedback/tooltip/tooltip.js";
|
|
10
|
+
import Stack from "../../layout/stack/stack.js";
|
|
11
|
+
import Badge from "../badge/badge.js";
|
|
12
|
+
import Text from "../../typography/text/text.js";
|
|
13
|
+
import useTable from "../data-table/use-table.js";
|
|
14
|
+
import EmptyState from "../empty-state/empty-state.js";
|
|
15
|
+
import { Link } from "react-router-dom";
|
|
16
|
+
const INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["A", "BUTTON", "INPUT", "SELECT", "TEXTAREA", "LABEL"]);
|
|
17
|
+
function isInteractiveElement(element, container) {
|
|
18
|
+
let current = element;
|
|
19
|
+
while (current && current !== container) {
|
|
20
|
+
if (INTERACTIVE_TAGS.has(current.tagName)) return true;
|
|
21
|
+
if (current.getAttribute("role") === "button") return true;
|
|
22
|
+
current = current.parentElement;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const MOBILE_BREAKPOINT = 640;
|
|
27
|
+
function DataGrid({
|
|
28
|
+
data,
|
|
29
|
+
columns,
|
|
30
|
+
className = "",
|
|
31
|
+
striped = false,
|
|
32
|
+
hoverable = true,
|
|
33
|
+
compact = false,
|
|
34
|
+
sortable = true,
|
|
35
|
+
paginated = false,
|
|
36
|
+
pageSize = 10,
|
|
37
|
+
selectable = false,
|
|
38
|
+
searchable = false,
|
|
39
|
+
searchKeys,
|
|
40
|
+
searchPlaceholder = "Search...",
|
|
41
|
+
onSearchChange,
|
|
42
|
+
actions,
|
|
43
|
+
actionsLabel = "Actions",
|
|
44
|
+
rowHref,
|
|
45
|
+
onRowClick,
|
|
46
|
+
onSelectionChange,
|
|
47
|
+
emptyState = {
|
|
48
|
+
title: "No data available",
|
|
49
|
+
description: void 0,
|
|
50
|
+
buttonText: void 0,
|
|
51
|
+
onButtonClick: void 0
|
|
52
|
+
},
|
|
53
|
+
headerActions,
|
|
54
|
+
title,
|
|
55
|
+
initialSort
|
|
56
|
+
}) {
|
|
57
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
58
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
59
|
+
const handleSearchChange = (value) => {
|
|
60
|
+
setSearchQuery(value);
|
|
61
|
+
if (onSearchChange) {
|
|
62
|
+
onSearchChange(value);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const containerRef = useRef(null);
|
|
66
|
+
const [visibleColumns, setVisibleColumns] = useState(() => columns);
|
|
67
|
+
const calculateActionsMinWidth = () => {
|
|
68
|
+
if (!actions) return 0;
|
|
69
|
+
let maxActionCount = 0;
|
|
70
|
+
if (Array.isArray(actions)) {
|
|
71
|
+
maxActionCount = actions.length;
|
|
72
|
+
} else {
|
|
73
|
+
data.forEach((row, index) => {
|
|
74
|
+
const result = actions(row, index);
|
|
75
|
+
if (Array.isArray(result)) {
|
|
76
|
+
maxActionCount = Math.max(maxActionCount, result.length);
|
|
77
|
+
} else {
|
|
78
|
+
maxActionCount = Math.max(maxActionCount, 2);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (maxActionCount === 0) {
|
|
83
|
+
return 60;
|
|
84
|
+
}
|
|
85
|
+
const actionWidth = 24;
|
|
86
|
+
const gapWidth = 8;
|
|
87
|
+
const extraPadding = 16;
|
|
88
|
+
return actionWidth * maxActionCount + gapWidth * Math.max(0, maxActionCount - 1) + extraPadding;
|
|
89
|
+
};
|
|
90
|
+
const actionsMinWidth = calculateActionsMinWidth();
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const container = containerRef.current;
|
|
93
|
+
if (!container) return;
|
|
94
|
+
const hasHideableColumns = columns.some((col) => col.hidePriority !== void 0 || col.minWidth !== void 0);
|
|
95
|
+
const updateLayout = () => {
|
|
96
|
+
const containerWidth = container.offsetWidth;
|
|
97
|
+
const nowMobile = containerWidth < MOBILE_BREAKPOINT;
|
|
98
|
+
setIsMobile(nowMobile);
|
|
99
|
+
if (nowMobile || !hasHideableColumns) {
|
|
100
|
+
setVisibleColumns(columns);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const isHideable = (col) => col.hidePriority !== void 0 || col.minWidth !== void 0;
|
|
104
|
+
const hideableColumns = columns.map((col, index) => ({ col, originalIndex: index })).filter(({ col }) => isHideable(col)).sort((a, b) => {
|
|
105
|
+
const aPriority = a.col.hidePriority;
|
|
106
|
+
const bPriority = b.col.hidePriority;
|
|
107
|
+
if (aPriority !== void 0 && bPriority !== void 0) {
|
|
108
|
+
if (aPriority !== bPriority) return aPriority - bPriority;
|
|
109
|
+
}
|
|
110
|
+
if (aPriority !== void 0 && bPriority === void 0) return -1;
|
|
111
|
+
if (aPriority === void 0 && bPriority !== void 0) return 1;
|
|
112
|
+
const aMin = a.col.minWidth ?? 0;
|
|
113
|
+
const bMin = b.col.minWidth ?? 0;
|
|
114
|
+
if (aMin !== bMin) return bMin - aMin;
|
|
115
|
+
return b.originalIndex - a.originalIndex;
|
|
116
|
+
});
|
|
117
|
+
const nonHideableColumns = columns.filter((col) => !isHideable(col));
|
|
118
|
+
const estimateColumnWidth = (col) => {
|
|
119
|
+
if (col.minWidth) return col.minWidth;
|
|
120
|
+
if (col.width) {
|
|
121
|
+
const match = col.width.match(/(\d+)/);
|
|
122
|
+
if (match) {
|
|
123
|
+
const num = parseInt(match[1], 10);
|
|
124
|
+
if (col.width.includes("px")) return num;
|
|
125
|
+
return num * 4;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return 150;
|
|
129
|
+
};
|
|
130
|
+
const horizontalPadding = compact ? 24 : 32;
|
|
131
|
+
const gridGap = 16;
|
|
132
|
+
let currentWidth = nonHideableColumns.reduce((sum, col) => sum + estimateColumnWidth(col), 0);
|
|
133
|
+
let totalColumns = nonHideableColumns.length;
|
|
134
|
+
if (selectable) {
|
|
135
|
+
currentWidth += 48;
|
|
136
|
+
totalColumns += 1;
|
|
137
|
+
}
|
|
138
|
+
if (actions) {
|
|
139
|
+
currentWidth += actionsMinWidth;
|
|
140
|
+
totalColumns += 1;
|
|
141
|
+
}
|
|
142
|
+
const columnsToShow = [...nonHideableColumns];
|
|
143
|
+
for (const { col } of hideableColumns) {
|
|
144
|
+
const colWidth = estimateColumnWidth(col);
|
|
145
|
+
const newTotalColumns = totalColumns + 1;
|
|
146
|
+
const totalGaps = Math.max(0, newTotalColumns - 1) * gridGap;
|
|
147
|
+
const totalWidthNeeded = currentWidth + colWidth + totalGaps + horizontalPadding;
|
|
148
|
+
if (totalWidthNeeded <= containerWidth) {
|
|
149
|
+
columnsToShow.push(col);
|
|
150
|
+
currentWidth += colWidth;
|
|
151
|
+
totalColumns = newTotalColumns;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const orderedVisibleColumns = columns.filter((col) => columnsToShow.includes(col));
|
|
155
|
+
setVisibleColumns(orderedVisibleColumns);
|
|
156
|
+
};
|
|
157
|
+
updateLayout();
|
|
158
|
+
const observer = new ResizeObserver(() => {
|
|
159
|
+
updateLayout();
|
|
160
|
+
});
|
|
161
|
+
observer.observe(container);
|
|
162
|
+
return () => {
|
|
163
|
+
observer.disconnect();
|
|
164
|
+
};
|
|
165
|
+
}, [columns, selectable, actions, actionsMinWidth, compact, data]);
|
|
166
|
+
const effectiveSearchKeys = searchKeys || visibleColumns.map((col) => col.key);
|
|
167
|
+
const tableOptions = {
|
|
168
|
+
data,
|
|
169
|
+
initialSort,
|
|
170
|
+
pageSize,
|
|
171
|
+
searchQuery,
|
|
172
|
+
searchKeys: effectiveSearchKeys
|
|
173
|
+
};
|
|
174
|
+
const {
|
|
175
|
+
currentData,
|
|
176
|
+
sortedData,
|
|
177
|
+
filteredData,
|
|
178
|
+
sortConfig,
|
|
179
|
+
handleSort,
|
|
180
|
+
currentPage,
|
|
181
|
+
totalPages,
|
|
182
|
+
nextPage,
|
|
183
|
+
prevPage,
|
|
184
|
+
canNextPage,
|
|
185
|
+
canPrevPage,
|
|
186
|
+
selectedRows,
|
|
187
|
+
toggleRow,
|
|
188
|
+
toggleAll,
|
|
189
|
+
isRowSelected,
|
|
190
|
+
isAllSelected
|
|
191
|
+
} = useTable(tableOptions);
|
|
192
|
+
const displayData = paginated ? currentData : sortedData;
|
|
193
|
+
const hasHeader = Boolean(
|
|
194
|
+
title && String(title).length > 0 || (Array.isArray(headerActions) ? headerActions.length > 0 : headerActions) || searchable
|
|
195
|
+
);
|
|
196
|
+
const handleToggleRow = (index) => {
|
|
197
|
+
toggleRow(index);
|
|
198
|
+
if (onSelectionChange) {
|
|
199
|
+
const newSelection = new Set(selectedRows);
|
|
200
|
+
if (newSelection.has(index)) {
|
|
201
|
+
newSelection.delete(index);
|
|
202
|
+
} else {
|
|
203
|
+
newSelection.add(index);
|
|
204
|
+
}
|
|
205
|
+
onSelectionChange(Array.from(newSelection));
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const handleToggleAll = () => {
|
|
209
|
+
toggleAll();
|
|
210
|
+
if (onSelectionChange) {
|
|
211
|
+
if (isAllSelected) {
|
|
212
|
+
onSelectionChange([]);
|
|
213
|
+
} else {
|
|
214
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
215
|
+
const allIndices = displayData.map((_, idx) => startIndex + idx);
|
|
216
|
+
onSelectionChange(allIndices);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const formatValue = (value, column) => {
|
|
221
|
+
if (value == null) return "";
|
|
222
|
+
switch (column.format) {
|
|
223
|
+
case "date": {
|
|
224
|
+
const dateValue = value instanceof Date ? value : new Date(value);
|
|
225
|
+
return new Intl.DateTimeFormat("en-US", column.formatOptions).format(dateValue);
|
|
226
|
+
}
|
|
227
|
+
case "currency":
|
|
228
|
+
return new Intl.NumberFormat("en-US", {
|
|
229
|
+
style: "currency",
|
|
230
|
+
currency: "USD",
|
|
231
|
+
...column.formatOptions
|
|
232
|
+
}).format(Number(value));
|
|
233
|
+
case "number":
|
|
234
|
+
return new Intl.NumberFormat("en-US", column.formatOptions).format(Number(value));
|
|
235
|
+
case "percent":
|
|
236
|
+
return new Intl.NumberFormat("en-US", {
|
|
237
|
+
style: "percent",
|
|
238
|
+
...column.formatOptions
|
|
239
|
+
}).format(Number(value));
|
|
240
|
+
default:
|
|
241
|
+
return String(value);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const renderCellContent = (value, column, row, rowIndex) => {
|
|
245
|
+
const isEmpty = value == null || value === "";
|
|
246
|
+
if (isEmpty && column.fallback !== void 0) {
|
|
247
|
+
return column.fallback;
|
|
248
|
+
}
|
|
249
|
+
if (column.render) {
|
|
250
|
+
return column.render(value, row, rowIndex);
|
|
251
|
+
}
|
|
252
|
+
if (column.badgeMap) {
|
|
253
|
+
const stringValue = String(value);
|
|
254
|
+
const badgeConfig = column.badgeMap[stringValue] || column.badgeMap["*"] || { variant: "neutral" };
|
|
255
|
+
return /* @__PURE__ */ jsx(Badge, { variant: badgeConfig.variant || "neutral", size: column.componentSize || "md", children: badgeConfig.label || stringValue });
|
|
256
|
+
}
|
|
257
|
+
if (column.statusDotMap) {
|
|
258
|
+
const stringValue = String(value);
|
|
259
|
+
const dotConfig = column.statusDotMap[stringValue] || column.statusDotMap["*"] || { color: "neutral" };
|
|
260
|
+
const dotColor = dotConfig.color || "neutral";
|
|
261
|
+
const dotColorClasses = {
|
|
262
|
+
primary: "bg-primary",
|
|
263
|
+
accent: "bg-accent",
|
|
264
|
+
neutral: "bg-muted-foreground",
|
|
265
|
+
success: "bg-success",
|
|
266
|
+
warning: "bg-warning",
|
|
267
|
+
error: "bg-error",
|
|
268
|
+
info: "bg-info"
|
|
269
|
+
};
|
|
270
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 min-w-0", children: [
|
|
271
|
+
/* @__PURE__ */ jsx("span", { className: `size-2 rounded-full shrink-0 ${dotColorClasses[dotColor]}` }),
|
|
272
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: dotConfig.label || stringValue })
|
|
273
|
+
] });
|
|
274
|
+
}
|
|
275
|
+
if (column.renderAsBadges) {
|
|
276
|
+
if (Array.isArray(value)) {
|
|
277
|
+
if (value.length === 0 && column.fallback !== void 0) {
|
|
278
|
+
return column.fallback;
|
|
279
|
+
}
|
|
280
|
+
return /* @__PURE__ */ jsx(Stack, { direction: "horizontal", spacing: "xs", wrap: true, children: value.map((item, idx) => /* @__PURE__ */ jsx(Badge, { variant: column.badgeVariant || "neutral", size: column.componentSize || "md", children: String(item) }, idx)) });
|
|
281
|
+
} else {
|
|
282
|
+
return /* @__PURE__ */ jsx(Badge, { variant: column.badgeVariant || "neutral", size: column.componentSize || "md", children: String(value) });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (column.format) {
|
|
286
|
+
return formatValue(value, column);
|
|
287
|
+
}
|
|
288
|
+
return String(value ?? "");
|
|
289
|
+
};
|
|
290
|
+
const renderSortIcon = (columnKey) => {
|
|
291
|
+
if (!sortable) return null;
|
|
292
|
+
const isSorted = sortConfig?.key === columnKey;
|
|
293
|
+
if (!isSorted) {
|
|
294
|
+
return /* @__PURE__ */ jsx(Icon, { name: "selector", size: "xs", color: "neutral" });
|
|
295
|
+
}
|
|
296
|
+
if (sortConfig?.direction === "asc") {
|
|
297
|
+
return /* @__PURE__ */ jsx(Icon, { name: "chevron-up", size: "xs", color: "primary" });
|
|
298
|
+
}
|
|
299
|
+
return /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: "xs", color: "primary" });
|
|
300
|
+
};
|
|
301
|
+
const renderActions = (row, actualIndex) => {
|
|
302
|
+
if (!actions) return null;
|
|
303
|
+
let rowActions;
|
|
304
|
+
if (Array.isArray(actions)) {
|
|
305
|
+
rowActions = actions;
|
|
306
|
+
} else {
|
|
307
|
+
const result = actions(row, actualIndex);
|
|
308
|
+
if (Array.isArray(result)) {
|
|
309
|
+
rowActions = result;
|
|
310
|
+
} else {
|
|
311
|
+
return /* @__PURE__ */ jsx("div", { "data-interactive": true, children: result });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return /* @__PURE__ */ jsx(Stack, { direction: "horizontal", spacing: "sm", "data-interactive": true, children: rowActions.map((action, actionIndex) => {
|
|
315
|
+
if (action && typeof action === "object" && "onClick" in action) {
|
|
316
|
+
const actionConfig = action;
|
|
317
|
+
const button = /* @__PURE__ */ jsx(
|
|
318
|
+
IconButton,
|
|
319
|
+
{
|
|
320
|
+
icon: actionConfig.icon,
|
|
321
|
+
iconSize: actionConfig.iconSize || "md",
|
|
322
|
+
buttonStyle: "ghost",
|
|
323
|
+
variant: actionConfig.variant || "neutral",
|
|
324
|
+
iconColor: actionConfig.iconColor,
|
|
325
|
+
hoverIcon: actionConfig.hoverIcon,
|
|
326
|
+
ariaLabel: actionConfig.label,
|
|
327
|
+
onClick: () => actionConfig.onClick(row, actualIndex),
|
|
328
|
+
noPadding: true
|
|
329
|
+
},
|
|
330
|
+
actionIndex
|
|
331
|
+
);
|
|
332
|
+
return actionConfig.tooltip ? /* @__PURE__ */ jsx(Tooltip, { content: actionConfig.tooltip, children: button }, actionIndex) : button;
|
|
333
|
+
} else {
|
|
334
|
+
return /* @__PURE__ */ jsx("div", { "data-interactive": true, children: action }, actionIndex);
|
|
335
|
+
}
|
|
336
|
+
}) });
|
|
337
|
+
};
|
|
338
|
+
const rowPadding = compact ? "py-2 px-3" : "py-3 px-4";
|
|
339
|
+
const buildGridTemplateColumns = () => {
|
|
340
|
+
const parts = [];
|
|
341
|
+
if (selectable) {
|
|
342
|
+
parts.push("24px");
|
|
343
|
+
}
|
|
344
|
+
visibleColumns.forEach((col) => {
|
|
345
|
+
if (col.width) {
|
|
346
|
+
const widthMatch = col.width.match(/(\d+)px/);
|
|
347
|
+
if (widthMatch) {
|
|
348
|
+
parts.push(`${widthMatch[1]}px`);
|
|
349
|
+
} else {
|
|
350
|
+
parts.push("1fr");
|
|
351
|
+
}
|
|
352
|
+
} else if (col.minWidth && col.maxWidth) {
|
|
353
|
+
parts.push(`minmax(${col.minWidth}px, ${col.maxWidth}px)`);
|
|
354
|
+
} else if (col.minWidth) {
|
|
355
|
+
parts.push(`minmax(${col.minWidth}px, 1fr)`);
|
|
356
|
+
} else if (col.maxWidth) {
|
|
357
|
+
parts.push(`minmax(auto, ${col.maxWidth}px)`);
|
|
358
|
+
} else {
|
|
359
|
+
parts.push("1fr");
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
if (actions) {
|
|
363
|
+
parts.push(`minmax(${actionsMinWidth}px, max-content)`);
|
|
364
|
+
}
|
|
365
|
+
return parts.join(" ");
|
|
366
|
+
};
|
|
367
|
+
const gridTemplateColumns = !isMobile ? buildGridTemplateColumns() : void 0;
|
|
368
|
+
const renderRow = (row, rowIndex) => {
|
|
369
|
+
const actualIndex = paginated ? (currentPage - 1) * pageSize + rowIndex : rowIndex;
|
|
370
|
+
const isSelected = isRowSelected(actualIndex);
|
|
371
|
+
const href = rowHref?.(row, actualIndex);
|
|
372
|
+
const hasHref = Boolean(href);
|
|
373
|
+
const hasOnRowClick = Boolean(onRowClick);
|
|
374
|
+
const isClickable = hasHref || hasOnRowClick;
|
|
375
|
+
const rowClasses = [
|
|
376
|
+
"relative",
|
|
377
|
+
"border-b border-border/50",
|
|
378
|
+
striped && rowIndex % 2 === 1 ? "bg-muted/30" : "",
|
|
379
|
+
hoverable && isClickable ? "hover:bg-muted/50 transition-colors" : "",
|
|
380
|
+
isSelected ? "bg-primary/5" : ""
|
|
381
|
+
].filter(Boolean).join(" ");
|
|
382
|
+
const layoutClasses = isMobile ? "flex flex-col gap-3" : "grid items-center gap-4";
|
|
383
|
+
const rowStyle = !isMobile && gridTemplateColumns ? { gridTemplateColumns } : void 0;
|
|
384
|
+
const rowClickProps = !hasHref && hasOnRowClick ? {
|
|
385
|
+
onClick: (e) => {
|
|
386
|
+
if (isInteractiveElement(e.target, e.currentTarget)) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
onRowClick?.(row, actualIndex);
|
|
390
|
+
},
|
|
391
|
+
onKeyDown: (e) => {
|
|
392
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
393
|
+
e.preventDefault();
|
|
394
|
+
onRowClick?.(row, actualIndex);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
role: "button",
|
|
398
|
+
tabIndex: 0,
|
|
399
|
+
className: "cursor-pointer"
|
|
400
|
+
} : {};
|
|
401
|
+
return /* @__PURE__ */ jsxs(
|
|
402
|
+
"div",
|
|
403
|
+
{
|
|
404
|
+
className: `${layoutClasses} ${rowPadding} ${rowClasses} ${rowClickProps.className || ""}`,
|
|
405
|
+
style: rowStyle,
|
|
406
|
+
onClick: rowClickProps.onClick,
|
|
407
|
+
onKeyDown: rowClickProps.onKeyDown,
|
|
408
|
+
role: rowClickProps.role,
|
|
409
|
+
tabIndex: rowClickProps.tabIndex,
|
|
410
|
+
children: [
|
|
411
|
+
hasHref && href && /* @__PURE__ */ jsx(Link, { to: href, className: "absolute inset-0 z-1", "aria-label": `View details`, tabIndex: 0 }),
|
|
412
|
+
selectable && /* @__PURE__ */ jsx("div", { className: `relative z-10 ${isMobile ? "" : "shrink-0"}`, children: /* @__PURE__ */ jsx(
|
|
413
|
+
Checkbox,
|
|
414
|
+
{
|
|
415
|
+
checked: isSelected,
|
|
416
|
+
onChange: () => handleToggleRow(actualIndex),
|
|
417
|
+
ariaLabel: `Select row ${actualIndex + 1}`
|
|
418
|
+
}
|
|
419
|
+
) }),
|
|
420
|
+
visibleColumns.map((column) => {
|
|
421
|
+
const value = row[column.key];
|
|
422
|
+
const content = renderCellContent(value, column, row, actualIndex);
|
|
423
|
+
const showLabel = isMobile && column.showLabelOnMobile !== false;
|
|
424
|
+
const alignClass = column.align === "center" ? "text-center" : column.align === "right" ? "text-right" : "text-left";
|
|
425
|
+
const pointerClass = hasHref ? "pointer-events-none [&_a]:pointer-events-auto [&_button]:pointer-events-auto [&_input]:pointer-events-auto [&_select]:pointer-events-auto [&_textarea]:pointer-events-auto [&_label]:pointer-events-auto [&_[data-interactive]]:pointer-events-auto" : "";
|
|
426
|
+
return /* @__PURE__ */ jsxs(
|
|
427
|
+
"div",
|
|
428
|
+
{
|
|
429
|
+
className: `${alignClass} ${isMobile ? "flex flex-col gap-0.5" : "min-w-0 overflow-hidden"} ${pointerClass}`,
|
|
430
|
+
children: [
|
|
431
|
+
showLabel && /* @__PURE__ */ jsx(Text, { size: "xs", variant: "muted", className: "uppercase tracking-wide", children: column.label }),
|
|
432
|
+
/* @__PURE__ */ jsx("div", { className: column.wrapText ? "" : "truncate", children: content })
|
|
433
|
+
]
|
|
434
|
+
},
|
|
435
|
+
String(column.key)
|
|
436
|
+
);
|
|
437
|
+
}),
|
|
438
|
+
actions && /* @__PURE__ */ jsx("div", { className: `relative z-10 shrink-0 ${isMobile ? "flex justify-end pt-1" : "justify-self-end"}`, children: renderActions(row, actualIndex) })
|
|
439
|
+
]
|
|
440
|
+
},
|
|
441
|
+
actualIndex
|
|
442
|
+
);
|
|
443
|
+
};
|
|
444
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `overflow-hidden ${className}`, children: [
|
|
445
|
+
hasHeader && /* @__PURE__ */ jsx("div", { className: "mb-3", children: searchable ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
446
|
+
title && /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: title }),
|
|
447
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row items-stretch sm:items-center gap-3 sm:justify-between", children: [
|
|
448
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 sm:max-w-md", children: /* @__PURE__ */ jsx(InputGroup, { prefix: /* @__PURE__ */ jsx(Icon, { name: "search", size: "sm" }), className: "w-full", children: /* @__PURE__ */ jsx(
|
|
449
|
+
Input,
|
|
450
|
+
{
|
|
451
|
+
type: "text",
|
|
452
|
+
value: searchQuery,
|
|
453
|
+
onChange: (e) => handleSearchChange(e.target.value),
|
|
454
|
+
placeholder: searchPlaceholder
|
|
455
|
+
}
|
|
456
|
+
) }) }),
|
|
457
|
+
Array.isArray(headerActions) && headerActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-wrap sm:flex-nowrap ml-auto", children: headerActions.map((act, idx) => /* @__PURE__ */ jsxs(
|
|
458
|
+
Button,
|
|
459
|
+
{
|
|
460
|
+
onClick: act.onClick,
|
|
461
|
+
variant: act.variant,
|
|
462
|
+
style: act.style,
|
|
463
|
+
size: act.size,
|
|
464
|
+
className: act.className,
|
|
465
|
+
ariaLabel: act.ariaLabel || (!act.label && act.icon ? "Action" : void 0),
|
|
466
|
+
children: [
|
|
467
|
+
act.icon ? /* @__PURE__ */ jsx(Icon, { name: act.icon, size: "sm" }) : null,
|
|
468
|
+
act.label
|
|
469
|
+
]
|
|
470
|
+
},
|
|
471
|
+
idx
|
|
472
|
+
)) })
|
|
473
|
+
] })
|
|
474
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3", children: [
|
|
475
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1", children: title && /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: title }) }),
|
|
476
|
+
Array.isArray(headerActions) && headerActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-wrap sm:flex-nowrap", children: headerActions.map((act, idx) => /* @__PURE__ */ jsxs(
|
|
477
|
+
Button,
|
|
478
|
+
{
|
|
479
|
+
onClick: act.onClick,
|
|
480
|
+
variant: act.variant,
|
|
481
|
+
style: act.style,
|
|
482
|
+
size: act.size,
|
|
483
|
+
className: act.className,
|
|
484
|
+
ariaLabel: act.ariaLabel || (!act.label && act.icon ? "Action" : void 0),
|
|
485
|
+
children: [
|
|
486
|
+
act.icon ? /* @__PURE__ */ jsx(Icon, { name: act.icon, size: "sm" }) : null,
|
|
487
|
+
act.label
|
|
488
|
+
]
|
|
489
|
+
},
|
|
490
|
+
idx
|
|
491
|
+
)) })
|
|
492
|
+
] }) }),
|
|
493
|
+
data.length === 0 ? /* @__PURE__ */ jsx("div", { className: "border border-border rounded-lg", children: /* @__PURE__ */ jsx(
|
|
494
|
+
EmptyState,
|
|
495
|
+
{
|
|
496
|
+
title: emptyState.title,
|
|
497
|
+
description: emptyState.description,
|
|
498
|
+
buttonText: emptyState.buttonText,
|
|
499
|
+
onButtonClick: emptyState.onButtonClick
|
|
500
|
+
}
|
|
501
|
+
) }) : filteredData.length === 0 && searchQuery.trim() ? /* @__PURE__ */ jsx("div", { className: "border border-border rounded-lg", children: /* @__PURE__ */ jsx(
|
|
502
|
+
EmptyState,
|
|
503
|
+
{
|
|
504
|
+
title: "No results found",
|
|
505
|
+
description: `No items match "${searchQuery}". Try adjusting your search.`
|
|
506
|
+
}
|
|
507
|
+
) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
508
|
+
!isMobile && /* @__PURE__ */ jsxs(
|
|
509
|
+
"div",
|
|
510
|
+
{
|
|
511
|
+
className: `grid items-center gap-4 ${rowPadding} border-b border-border bg-muted/30`,
|
|
512
|
+
style: gridTemplateColumns ? { gridTemplateColumns } : void 0,
|
|
513
|
+
children: [
|
|
514
|
+
selectable && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Checkbox, { checked: isAllSelected, onChange: handleToggleAll, ariaLabel: "Select all rows" }) }),
|
|
515
|
+
visibleColumns.map((column) => {
|
|
516
|
+
const alignClass = column.align === "center" ? "text-center" : column.align === "right" ? "text-right" : "text-left";
|
|
517
|
+
return /* @__PURE__ */ jsx("div", { className: `min-w-0 ${alignClass}`, children: column.sortable !== false && sortable ? /* @__PURE__ */ jsxs(
|
|
518
|
+
"button",
|
|
519
|
+
{
|
|
520
|
+
onClick: () => handleSort(column.key),
|
|
521
|
+
className: "flex items-center gap-1 hover:text-foreground transition-colors font-medium text-sm text-muted-foreground",
|
|
522
|
+
type: "button",
|
|
523
|
+
children: [
|
|
524
|
+
column.label,
|
|
525
|
+
renderSortIcon(column.key)
|
|
526
|
+
]
|
|
527
|
+
}
|
|
528
|
+
) : /* @__PURE__ */ jsx(Text, { size: "sm", variant: "muted", weight: "medium", children: column.label }) }, String(column.key));
|
|
529
|
+
}),
|
|
530
|
+
actions && /* @__PURE__ */ jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsx(Text, { size: "sm", variant: "muted", weight: "medium", children: actionsLabel }) })
|
|
531
|
+
]
|
|
532
|
+
}
|
|
533
|
+
),
|
|
534
|
+
isMobile && selectable && /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-2 ${rowPadding} border-b border-border`, children: [
|
|
535
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: isAllSelected, onChange: handleToggleAll, ariaLabel: "Select all rows" }),
|
|
536
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", variant: "muted", children: "Select all" })
|
|
537
|
+
] }),
|
|
538
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y divide-border/50", children: displayData.map((row, idx) => renderRow(row, idx)) }),
|
|
539
|
+
paginated && totalPages > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-t border-border", children: [
|
|
540
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
|
|
541
|
+
"Page ",
|
|
542
|
+
currentPage,
|
|
543
|
+
" of ",
|
|
544
|
+
totalPages,
|
|
545
|
+
" (",
|
|
546
|
+
filteredData.length,
|
|
547
|
+
" ",
|
|
548
|
+
searchQuery.trim() ? "filtered" : "total",
|
|
549
|
+
" ",
|
|
550
|
+
"rows",
|
|
551
|
+
searchQuery.trim() && data.length !== filteredData.length ? ` of ${data.length}` : "",
|
|
552
|
+
")"
|
|
553
|
+
] }),
|
|
554
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
555
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", style: "outline", onClick: prevPage, disabled: !canPrevPage, children: "Previous" }),
|
|
556
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", style: "outline", onClick: nextPage, disabled: !canNextPage, children: "Next" })
|
|
557
|
+
] })
|
|
558
|
+
] })
|
|
559
|
+
] })
|
|
560
|
+
] });
|
|
561
|
+
}
|
|
562
|
+
DataGrid.displayName = "DataGrid";
|
|
563
|
+
export {
|
|
564
|
+
DataGrid as default
|
|
565
|
+
};
|
|
566
|
+
//# sourceMappingURL=data-grid.js.map
|