@seedgrid/fe-components 2026.3.9 → 2026.3.11
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/digits/segment-digit/SgSegmentDigit.d.ts +15 -0
- package/dist/digits/segment-digit/SgSegmentDigit.d.ts.map +1 -0
- package/dist/digits/segment-digit/SgSegmentDigit.js +96 -0
- package/dist/digits/segment-digit/index.d.ts +3 -0
- package/dist/digits/segment-digit/index.d.ts.map +1 -0
- package/dist/digits/segment-digit/index.js +1 -0
- package/dist/digits/seven-segment-digit/SgSevenSegmentDigit.d.ts +36 -0
- package/dist/digits/seven-segment-digit/SgSevenSegmentDigit.d.ts.map +1 -0
- package/dist/digits/seven-segment-digit/SgSevenSegmentDigit.js +123 -0
- package/dist/digits/seven-segment-digit/index.d.ts +3 -0
- package/dist/digits/seven-segment-digit/index.d.ts.map +1 -0
- package/dist/digits/seven-segment-digit/index.js +1 -0
- package/dist/gadgets/calendar/SgCalendar.d.ts +29 -0
- package/dist/gadgets/calendar/SgCalendar.d.ts.map +1 -0
- package/dist/gadgets/calendar/SgCalendar.js +248 -0
- package/dist/gadgets/calendar/index.d.ts +3 -0
- package/dist/gadgets/calendar/index.d.ts.map +1 -0
- package/dist/gadgets/calendar/index.js +1 -0
- package/dist/gadgets/clock/SgClock.d.ts +4 -1
- package/dist/gadgets/clock/SgClock.d.ts.map +1 -1
- package/dist/gadgets/clock/SgClock.js +74 -115
- package/dist/i18n/en-US.d.ts.map +1 -1
- package/dist/i18n/en-US.js +11 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +11 -1
- package/dist/i18n/pt-BR.d.ts.map +1 -1
- package/dist/i18n/pt-BR.js +11 -1
- package/dist/i18n/pt-PT.d.ts.map +1 -1
- package/dist/i18n/pt-PT.js +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/inputs/SgDatatable.d.ts +104 -0
- package/dist/inputs/SgDatatable.d.ts.map +1 -0
- package/dist/inputs/SgDatatable.js +441 -0
- package/dist/layout/SgCard.d.ts +12 -0
- package/dist/layout/SgCard.d.ts.map +1 -1
- package/dist/layout/SgCard.js +228 -12
- package/dist/overlay/SgConfirmationDialog.d.ts +34 -0
- package/dist/overlay/SgConfirmationDialog.d.ts.map +1 -0
- package/dist/overlay/SgConfirmationDialog.js +81 -0
- package/dist/overlay/SgDialog.d.ts +5 -0
- package/dist/overlay/SgDialog.d.ts.map +1 -1
- package/dist/overlay/SgDialog.js +28 -3
- package/dist/sandbox.cjs +84 -84
- package/package.json +1 -1
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { SgButton } from "../buttons/SgButton";
|
|
5
|
+
import { SgGroupBox } from "../layout/SgGroupBox";
|
|
6
|
+
import { t, useComponentsI18n } from "../i18n";
|
|
7
|
+
function cn(...parts) {
|
|
8
|
+
return parts.filter(Boolean).join(" ");
|
|
9
|
+
}
|
|
10
|
+
function toCssSize(value) {
|
|
11
|
+
if (value === undefined || value === null)
|
|
12
|
+
return undefined;
|
|
13
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
14
|
+
}
|
|
15
|
+
function normalizeFilters(filters) {
|
|
16
|
+
if (!filters)
|
|
17
|
+
return {};
|
|
18
|
+
return Object.entries(filters).reduce((acc, [field, value]) => {
|
|
19
|
+
const text = String(value ?? "").trim();
|
|
20
|
+
if (!text)
|
|
21
|
+
return acc;
|
|
22
|
+
acc[field] = text;
|
|
23
|
+
return acc;
|
|
24
|
+
}, {});
|
|
25
|
+
}
|
|
26
|
+
function getFieldValue(row, field) {
|
|
27
|
+
if (!field)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (!field.includes("."))
|
|
30
|
+
return row[field];
|
|
31
|
+
const chunks = field.split(".");
|
|
32
|
+
let current = row;
|
|
33
|
+
for (const chunk of chunks) {
|
|
34
|
+
if (current === null || current === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (typeof current !== "object")
|
|
37
|
+
return undefined;
|
|
38
|
+
current = current[chunk];
|
|
39
|
+
}
|
|
40
|
+
return current;
|
|
41
|
+
}
|
|
42
|
+
function normalizeText(value) {
|
|
43
|
+
if (value === null || value === undefined)
|
|
44
|
+
return "";
|
|
45
|
+
if (typeof value === "string")
|
|
46
|
+
return value.toLowerCase();
|
|
47
|
+
if (typeof value === "number" || typeof value === "bigint")
|
|
48
|
+
return String(value);
|
|
49
|
+
if (typeof value === "boolean")
|
|
50
|
+
return value ? "true" : "false";
|
|
51
|
+
if (value instanceof Date)
|
|
52
|
+
return value.toISOString().toLowerCase();
|
|
53
|
+
return String(value).toLowerCase();
|
|
54
|
+
}
|
|
55
|
+
function matchesFilter(value, query, mode) {
|
|
56
|
+
const search = query.trim().toLowerCase();
|
|
57
|
+
if (!search)
|
|
58
|
+
return true;
|
|
59
|
+
const source = normalizeText(value);
|
|
60
|
+
if (mode === "startsWith")
|
|
61
|
+
return source.startsWith(search);
|
|
62
|
+
if (mode === "endsWith")
|
|
63
|
+
return source.endsWith(search);
|
|
64
|
+
if (mode === "equals")
|
|
65
|
+
return source === search;
|
|
66
|
+
return source.includes(search);
|
|
67
|
+
}
|
|
68
|
+
function compareValues(a, b) {
|
|
69
|
+
if (a === b)
|
|
70
|
+
return 0;
|
|
71
|
+
if (a === null || a === undefined)
|
|
72
|
+
return -1;
|
|
73
|
+
if (b === null || b === undefined)
|
|
74
|
+
return 1;
|
|
75
|
+
if (typeof a === "number" && typeof b === "number")
|
|
76
|
+
return a - b;
|
|
77
|
+
if (a instanceof Date && b instanceof Date)
|
|
78
|
+
return a.getTime() - b.getTime();
|
|
79
|
+
return String(a).localeCompare(String(b), undefined, {
|
|
80
|
+
sensitivity: "base",
|
|
81
|
+
numeric: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function resolveMessage(translated, key, fallback) {
|
|
85
|
+
return translated === key ? fallback : translated;
|
|
86
|
+
}
|
|
87
|
+
function buildPageWindow(currentPage, pageCount, maxButtons = 5) {
|
|
88
|
+
if (pageCount <= 0)
|
|
89
|
+
return [];
|
|
90
|
+
if (pageCount <= maxButtons)
|
|
91
|
+
return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
92
|
+
const half = Math.floor(maxButtons / 2);
|
|
93
|
+
let start = Math.max(1, currentPage - half);
|
|
94
|
+
let end = start + maxButtons - 1;
|
|
95
|
+
if (end > pageCount) {
|
|
96
|
+
end = pageCount;
|
|
97
|
+
start = end - maxButtons + 1;
|
|
98
|
+
}
|
|
99
|
+
const pages = [];
|
|
100
|
+
for (let page = start; page <= end; page += 1) {
|
|
101
|
+
pages.push(page);
|
|
102
|
+
}
|
|
103
|
+
return pages;
|
|
104
|
+
}
|
|
105
|
+
function getRowIdentity(rowData, dataKey) {
|
|
106
|
+
if (!dataKey)
|
|
107
|
+
return rowData;
|
|
108
|
+
return getFieldValue(rowData, dataKey);
|
|
109
|
+
}
|
|
110
|
+
function rowsAreEqual(rowA, rowB, dataKey) {
|
|
111
|
+
if (!dataKey)
|
|
112
|
+
return rowA === rowB;
|
|
113
|
+
return getRowIdentity(rowA, dataKey) === getRowIdentity(rowB, dataKey);
|
|
114
|
+
}
|
|
115
|
+
function resolveAlignmentClass(align) {
|
|
116
|
+
if (align === "center")
|
|
117
|
+
return "text-center";
|
|
118
|
+
if (align === "right")
|
|
119
|
+
return "text-right";
|
|
120
|
+
return "text-left";
|
|
121
|
+
}
|
|
122
|
+
function SgDatatableBase(props, imperativeRef) {
|
|
123
|
+
const i18n = useComponentsI18n();
|
|
124
|
+
const { id, title, value, columns, dataKey, lazy = false, totalRecords: controlledTotalRecords, paginator = false, rows = 10, first, rowsPerPageOptions, onPage, sortField, sortOrder, onSort, removableSort = true, selectionMode, selection, onSelectionChange, showGlobalFilter = false, globalFilter, globalFilterPlaceholder, onGlobalFilterChange, showColumnFilters = false, filters, onFilter, showClearFiltersButton = false, stripedRows = false, showGridlines = false, hoverableRows = true, loading = false, emptyMessage, className = "", style, tableClassName = "", rowClassName, groupBoxProps = {} } = props;
|
|
125
|
+
const isFirstControlled = first !== undefined;
|
|
126
|
+
const isSortFieldControlled = sortField !== undefined;
|
|
127
|
+
const isSortOrderControlled = sortOrder !== undefined;
|
|
128
|
+
const isSelectionControlled = selection !== undefined;
|
|
129
|
+
const isGlobalFilterControlled = globalFilter !== undefined;
|
|
130
|
+
const isFiltersControlled = filters !== undefined;
|
|
131
|
+
const [internalFirst, setInternalFirst] = React.useState(Math.max(0, first ?? 0));
|
|
132
|
+
const [internalRows, setInternalRows] = React.useState(Math.max(1, rows));
|
|
133
|
+
const [internalSortField, setInternalSortField] = React.useState(sortField ?? null);
|
|
134
|
+
const [internalSortOrder, setInternalSortOrder] = React.useState(sortOrder ?? 0);
|
|
135
|
+
const [internalSelection, setInternalSelection] = React.useState(null);
|
|
136
|
+
const [internalGlobalFilter, setInternalGlobalFilter] = React.useState(globalFilter ?? "");
|
|
137
|
+
const [internalFilters, setInternalFilters] = React.useState(() => normalizeFilters(filters));
|
|
138
|
+
React.useEffect(() => {
|
|
139
|
+
if (!isFirstControlled)
|
|
140
|
+
return;
|
|
141
|
+
setInternalFirst(Math.max(0, first ?? 0));
|
|
142
|
+
}, [isFirstControlled, first]);
|
|
143
|
+
React.useEffect(() => {
|
|
144
|
+
setInternalRows(Math.max(1, rows));
|
|
145
|
+
}, [rows]);
|
|
146
|
+
React.useEffect(() => {
|
|
147
|
+
if (!isSortFieldControlled)
|
|
148
|
+
return;
|
|
149
|
+
setInternalSortField(sortField ?? null);
|
|
150
|
+
}, [isSortFieldControlled, sortField]);
|
|
151
|
+
React.useEffect(() => {
|
|
152
|
+
if (!isSortOrderControlled)
|
|
153
|
+
return;
|
|
154
|
+
setInternalSortOrder(sortOrder ?? 0);
|
|
155
|
+
}, [isSortOrderControlled, sortOrder]);
|
|
156
|
+
React.useEffect(() => {
|
|
157
|
+
if (!isSelectionControlled)
|
|
158
|
+
return;
|
|
159
|
+
setInternalSelection(selection ?? null);
|
|
160
|
+
}, [isSelectionControlled, selection]);
|
|
161
|
+
React.useEffect(() => {
|
|
162
|
+
if (!isGlobalFilterControlled)
|
|
163
|
+
return;
|
|
164
|
+
setInternalGlobalFilter(globalFilter ?? "");
|
|
165
|
+
}, [isGlobalFilterControlled, globalFilter]);
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
if (!isFiltersControlled)
|
|
168
|
+
return;
|
|
169
|
+
setInternalFilters(normalizeFilters(filters));
|
|
170
|
+
}, [isFiltersControlled, filters]);
|
|
171
|
+
const visibleColumns = React.useMemo(() => columns.filter((column) => !column.hidden), [columns]);
|
|
172
|
+
const currentFirst = isFirstControlled ? Math.max(0, first ?? 0) : internalFirst;
|
|
173
|
+
const currentRows = Math.max(1, internalRows);
|
|
174
|
+
const currentSortField = isSortFieldControlled ? sortField ?? null : internalSortField;
|
|
175
|
+
const currentSortOrder = isSortOrderControlled ? sortOrder ?? 0 : internalSortOrder;
|
|
176
|
+
const currentSelection = isSelectionControlled ? selection ?? null : internalSelection;
|
|
177
|
+
const currentGlobalFilter = isGlobalFilterControlled ? globalFilter ?? "" : internalGlobalFilter;
|
|
178
|
+
const currentFilters = isFiltersControlled ? normalizeFilters(filters) : internalFilters;
|
|
179
|
+
const globalFilterColumns = React.useMemo(() => visibleColumns.filter((column) => column.field && !column.excludeGlobalFilter), [visibleColumns]);
|
|
180
|
+
const filteredRows = React.useMemo(() => {
|
|
181
|
+
if (lazy)
|
|
182
|
+
return value;
|
|
183
|
+
return value.filter((rowData) => {
|
|
184
|
+
const matchesGlobal = !currentGlobalFilter.trim()
|
|
185
|
+
? true
|
|
186
|
+
: globalFilterColumns.some((column) => {
|
|
187
|
+
const columnField = column.filterField ?? column.field;
|
|
188
|
+
return matchesFilter(getFieldValue(rowData, columnField), currentGlobalFilter, "contains");
|
|
189
|
+
});
|
|
190
|
+
if (!matchesGlobal)
|
|
191
|
+
return false;
|
|
192
|
+
const columnFilterEntries = Object.entries(currentFilters);
|
|
193
|
+
if (columnFilterEntries.length === 0)
|
|
194
|
+
return true;
|
|
195
|
+
for (const [field, query] of columnFilterEntries) {
|
|
196
|
+
const column = visibleColumns.find((item) => (item.filterField ?? item.field) === field);
|
|
197
|
+
if (!column)
|
|
198
|
+
continue;
|
|
199
|
+
const matchMode = column.filterMatchMode ?? "contains";
|
|
200
|
+
const fieldValue = getFieldValue(rowData, field);
|
|
201
|
+
if (!matchesFilter(fieldValue, query, matchMode))
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
205
|
+
});
|
|
206
|
+
}, [lazy, value, currentGlobalFilter, currentFilters, globalFilterColumns, visibleColumns]);
|
|
207
|
+
const sortedRows = React.useMemo(() => {
|
|
208
|
+
if (lazy)
|
|
209
|
+
return filteredRows;
|
|
210
|
+
if (!currentSortField || currentSortOrder === 0)
|
|
211
|
+
return filteredRows;
|
|
212
|
+
const sortColumn = visibleColumns.find((column) => (column.sortField ?? column.field) === currentSortField);
|
|
213
|
+
const direction = currentSortOrder === -1 ? -1 : 1;
|
|
214
|
+
return [...filteredRows].sort((rowA, rowB) => {
|
|
215
|
+
if (sortColumn?.sortFunction) {
|
|
216
|
+
return sortColumn.sortFunction(rowA, rowB) * direction;
|
|
217
|
+
}
|
|
218
|
+
const sortFieldName = sortColumn?.sortField ?? sortColumn?.field ?? currentSortField;
|
|
219
|
+
const aValue = getFieldValue(rowA, sortFieldName);
|
|
220
|
+
const bValue = getFieldValue(rowB, sortFieldName);
|
|
221
|
+
return compareValues(aValue, bValue) * direction;
|
|
222
|
+
});
|
|
223
|
+
}, [lazy, currentSortField, currentSortOrder, filteredRows, visibleColumns]);
|
|
224
|
+
const resolvedTotalRecords = lazy ? Math.max(0, controlledTotalRecords ?? value.length) : sortedRows.length;
|
|
225
|
+
const pageCount = Math.max(1, Math.ceil(Math.max(resolvedTotalRecords, 1) / currentRows));
|
|
226
|
+
const maxFirst = Math.max(0, (pageCount - 1) * currentRows);
|
|
227
|
+
const safeFirst = Math.min(currentFirst, maxFirst);
|
|
228
|
+
const pagedRows = React.useMemo(() => {
|
|
229
|
+
if (!paginator)
|
|
230
|
+
return sortedRows;
|
|
231
|
+
if (lazy)
|
|
232
|
+
return sortedRows;
|
|
233
|
+
return sortedRows.slice(safeFirst, safeFirst + currentRows);
|
|
234
|
+
}, [paginator, lazy, sortedRows, safeFirst, currentRows]);
|
|
235
|
+
const currentPage = Math.floor(safeFirst / currentRows) + 1;
|
|
236
|
+
const pageWindow = React.useMemo(() => buildPageWindow(currentPage, pageCount, 5), [currentPage, pageCount]);
|
|
237
|
+
const commitPage = React.useCallback((nextFirst, nextRows) => {
|
|
238
|
+
const normalizedRows = Math.max(1, nextRows);
|
|
239
|
+
const normalizedFirst = Math.max(0, nextFirst);
|
|
240
|
+
if (!isFirstControlled)
|
|
241
|
+
setInternalFirst(normalizedFirst);
|
|
242
|
+
setInternalRows(normalizedRows);
|
|
243
|
+
const nextPageCount = Math.max(1, Math.ceil(Math.max(resolvedTotalRecords, 1) / normalizedRows));
|
|
244
|
+
const page = Math.floor(normalizedFirst / normalizedRows) + 1;
|
|
245
|
+
onPage?.({
|
|
246
|
+
first: normalizedFirst,
|
|
247
|
+
rows: normalizedRows,
|
|
248
|
+
page,
|
|
249
|
+
pageCount: nextPageCount,
|
|
250
|
+
totalRecords: resolvedTotalRecords
|
|
251
|
+
});
|
|
252
|
+
}, [isFirstControlled, onPage, resolvedTotalRecords]);
|
|
253
|
+
React.useEffect(() => {
|
|
254
|
+
if (safeFirst === currentFirst)
|
|
255
|
+
return;
|
|
256
|
+
commitPage(safeFirst, currentRows);
|
|
257
|
+
}, [safeFirst, currentFirst, commitPage, currentRows]);
|
|
258
|
+
const commitSort = React.useCallback((nextSortField, nextSortOrder) => {
|
|
259
|
+
if (!isSortFieldControlled)
|
|
260
|
+
setInternalSortField(nextSortField);
|
|
261
|
+
if (!isSortOrderControlled)
|
|
262
|
+
setInternalSortOrder(nextSortOrder);
|
|
263
|
+
onSort?.({ sortField: nextSortField, sortOrder: nextSortOrder });
|
|
264
|
+
}, [isSortFieldControlled, isSortOrderControlled, onSort]);
|
|
265
|
+
const commitSelection = React.useCallback((nextSelection) => {
|
|
266
|
+
if (!isSelectionControlled)
|
|
267
|
+
setInternalSelection(nextSelection);
|
|
268
|
+
onSelectionChange?.(nextSelection);
|
|
269
|
+
}, [isSelectionControlled, onSelectionChange]);
|
|
270
|
+
const commitFilters = React.useCallback((nextFilters, nextGlobalFilter) => {
|
|
271
|
+
if (!isFiltersControlled)
|
|
272
|
+
setInternalFilters(nextFilters);
|
|
273
|
+
if (!isGlobalFilterControlled)
|
|
274
|
+
setInternalGlobalFilter(nextGlobalFilter);
|
|
275
|
+
onGlobalFilterChange?.(nextGlobalFilter);
|
|
276
|
+
onFilter?.({ filters: nextFilters, globalFilter: nextGlobalFilter });
|
|
277
|
+
commitPage(0, currentRows);
|
|
278
|
+
}, [
|
|
279
|
+
isFiltersControlled,
|
|
280
|
+
isGlobalFilterControlled,
|
|
281
|
+
onGlobalFilterChange,
|
|
282
|
+
onFilter,
|
|
283
|
+
commitPage,
|
|
284
|
+
currentRows
|
|
285
|
+
]);
|
|
286
|
+
const clearFilters = React.useCallback(() => {
|
|
287
|
+
commitFilters({}, "");
|
|
288
|
+
}, [commitFilters]);
|
|
289
|
+
const resetSort = React.useCallback(() => {
|
|
290
|
+
commitSort(null, 0);
|
|
291
|
+
}, [commitSort]);
|
|
292
|
+
const clearSelection = React.useCallback(() => {
|
|
293
|
+
if (selectionMode === "multiple") {
|
|
294
|
+
commitSelection([]);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
commitSelection(null);
|
|
298
|
+
}, [selectionMode, commitSelection]);
|
|
299
|
+
const goToPage = React.useCallback((page) => {
|
|
300
|
+
const normalizedPage = Math.max(1, Math.min(page, pageCount));
|
|
301
|
+
commitPage((normalizedPage - 1) * currentRows, currentRows);
|
|
302
|
+
}, [commitPage, currentRows, pageCount]);
|
|
303
|
+
React.useImperativeHandle(imperativeRef, () => ({
|
|
304
|
+
getProcessedRows: () => sortedRows,
|
|
305
|
+
getPagedRows: () => pagedRows,
|
|
306
|
+
clearFilters,
|
|
307
|
+
clearSelection,
|
|
308
|
+
resetSort,
|
|
309
|
+
resetPage: () => commitPage(0, currentRows),
|
|
310
|
+
goToPage
|
|
311
|
+
}), [sortedRows, pagedRows, clearFilters, clearSelection, resetSort, commitPage, currentRows, goToPage]);
|
|
312
|
+
const emptyLabel = emptyMessage ?? resolveMessage(t(i18n, "components.datatable.empty"), "components.datatable.empty", "No records found.");
|
|
313
|
+
const loadingLabel = resolveMessage(t(i18n, "components.datatable.loading"), "components.datatable.loading", "Loading data...");
|
|
314
|
+
const globalFilterLabel = globalFilterPlaceholder ?? resolveMessage(t(i18n, "components.datatable.globalFilterPlaceholder"), "components.datatable.globalFilterPlaceholder", "Search in all columns");
|
|
315
|
+
const clearFiltersLabel = resolveMessage(t(i18n, "components.datatable.clearFilters"), "components.datatable.clearFilters", "Clear filters");
|
|
316
|
+
const prevLabel = resolveMessage(t(i18n, "components.datatable.prev"), "components.datatable.prev", "Prev");
|
|
317
|
+
const nextLabel = resolveMessage(t(i18n, "components.datatable.next"), "components.datatable.next", "Next");
|
|
318
|
+
const rowsPerPageLabel = resolveMessage(t(i18n, "components.datatable.rowsPerPage"), "components.datatable.rowsPerPage", "Rows per page");
|
|
319
|
+
const pageReport = resolveMessage(t(i18n, "components.datatable.pageReport", {
|
|
320
|
+
first: resolvedTotalRecords === 0 ? 0 : safeFirst + 1,
|
|
321
|
+
last: Math.min(safeFirst + currentRows, resolvedTotalRecords),
|
|
322
|
+
total: resolvedTotalRecords
|
|
323
|
+
}), "components.datatable.pageReport", `${resolvedTotalRecords === 0 ? 0 : safeFirst + 1} - ${Math.min(safeFirst + currentRows, resolvedTotalRecords)} of ${resolvedTotalRecords}`);
|
|
324
|
+
const shouldShowToolbar = showGlobalFilter || showClearFiltersButton;
|
|
325
|
+
const shouldShowFiltersRow = showColumnFilters && visibleColumns.some((column) => column.filter);
|
|
326
|
+
const hasFooter = visibleColumns.some((column) => column.footer !== undefined);
|
|
327
|
+
const handleHeaderSort = (column) => {
|
|
328
|
+
if (!column.sortable)
|
|
329
|
+
return;
|
|
330
|
+
const nextSortField = column.sortField ?? column.field;
|
|
331
|
+
if (!nextSortField)
|
|
332
|
+
return;
|
|
333
|
+
if (currentSortField !== nextSortField) {
|
|
334
|
+
commitSort(nextSortField, 1);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (currentSortOrder === 1) {
|
|
338
|
+
commitSort(nextSortField, -1);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (currentSortOrder === -1) {
|
|
342
|
+
if (removableSort) {
|
|
343
|
+
commitSort(null, 0);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
commitSort(nextSortField, 1);
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
commitSort(nextSortField, 1);
|
|
351
|
+
};
|
|
352
|
+
const isRowSelected = React.useCallback((rowData) => {
|
|
353
|
+
if (!selectionMode)
|
|
354
|
+
return false;
|
|
355
|
+
if (selectionMode === "single") {
|
|
356
|
+
return currentSelection !== null && !Array.isArray(currentSelection)
|
|
357
|
+
? rowsAreEqual(currentSelection, rowData, dataKey)
|
|
358
|
+
: false;
|
|
359
|
+
}
|
|
360
|
+
const list = Array.isArray(currentSelection) ? currentSelection : [];
|
|
361
|
+
return list.some((entry) => rowsAreEqual(entry, rowData, dataKey));
|
|
362
|
+
}, [selectionMode, currentSelection, dataKey]);
|
|
363
|
+
const handleRowSelection = React.useCallback((rowData) => {
|
|
364
|
+
if (!selectionMode)
|
|
365
|
+
return;
|
|
366
|
+
if (selectionMode === "single") {
|
|
367
|
+
if (currentSelection !== null && !Array.isArray(currentSelection) && rowsAreEqual(currentSelection, rowData, dataKey)) {
|
|
368
|
+
commitSelection(null);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
commitSelection(rowData);
|
|
372
|
+
}
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const previous = Array.isArray(currentSelection) ? currentSelection : [];
|
|
376
|
+
const exists = previous.some((entry) => rowsAreEqual(entry, rowData, dataKey));
|
|
377
|
+
const next = exists
|
|
378
|
+
? previous.filter((entry) => !rowsAreEqual(entry, rowData, dataKey))
|
|
379
|
+
: [...previous, rowData];
|
|
380
|
+
commitSelection(next);
|
|
381
|
+
}, [selectionMode, currentSelection, dataKey, commitSelection]);
|
|
382
|
+
const resolvedTitle = (groupBoxProps.title ?? title ?? "").trim() || " ";
|
|
383
|
+
return (_jsx("div", { className: className, style: style, children: _jsx(SgGroupBox, { ...groupBoxProps, title: resolvedTitle, children: _jsxs("div", { className: "space-y-3", children: [shouldShowToolbar ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [showGlobalFilter ? (_jsx("input", { value: currentGlobalFilter, onChange: (event) => commitFilters(currentFilters, event.target.value), placeholder: globalFilterLabel, className: "min-w-[220px] flex-1 rounded-md border border-[rgb(var(--sg-border))] bg-white px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-[rgb(var(--sg-ring))]" })) : null, showClearFiltersButton ? (_jsx(SgButton, { size: "sm", appearance: "outline", onClick: clearFilters, children: clearFiltersLabel })) : null] })) : null, _jsx("div", { className: "relative", children: _jsx("div", { className: "overflow-x-auto rounded-lg border border-[rgb(var(--sg-border))] bg-white", children: _jsxs("table", { id: id, className: cn("min-w-full border-collapse text-sm", tableClassName), children: [_jsxs("thead", { children: [_jsx("tr", { className: "bg-[rgb(var(--sg-primary-50))]", children: visibleColumns.map((column, columnIndex) => {
|
|
384
|
+
const sortCandidate = column.sortField ?? column.field;
|
|
385
|
+
const isSorted = Boolean(sortCandidate) && sortCandidate === currentSortField;
|
|
386
|
+
const sortIcon = !column.sortable
|
|
387
|
+
? null
|
|
388
|
+
: isSorted
|
|
389
|
+
? currentSortOrder === 1
|
|
390
|
+
? " ^"
|
|
391
|
+
: currentSortOrder === -1
|
|
392
|
+
? " v"
|
|
393
|
+
: " <>"
|
|
394
|
+
: " <>";
|
|
395
|
+
return (_jsx("th", { className: cn("px-3 py-2 font-semibold text-[rgb(var(--sg-text))]", resolveAlignmentClass(column.align), showGridlines ? "border-b border-r border-[rgb(var(--sg-border))] last:border-r-0" : "border-b border-[rgb(var(--sg-border))]", column.headerClassName, column.className), style: { width: toCssSize(column.width), minWidth: toCssSize(column.minWidth) }, children: column.sortable ? (_jsxs("button", { type: "button", onClick: () => handleHeaderSort(column), className: cn("inline-flex items-center gap-1 font-semibold", resolveAlignmentClass(column.align), "text-[rgb(var(--sg-text))] hover:text-[rgb(var(--sg-primary-600))]"), children: [_jsx("span", { children: column.header }), _jsx("span", { "aria-hidden": "true", children: sortIcon })] })) : (_jsx("span", { children: column.header })) }, `head-${column.field ?? columnIndex}`));
|
|
396
|
+
}) }), shouldShowFiltersRow ? (_jsx("tr", { children: visibleColumns.map((column, columnIndex) => {
|
|
397
|
+
const filterField = column.filterField ?? column.field;
|
|
398
|
+
const filterValue = filterField ? currentFilters[filterField] ?? "" : "";
|
|
399
|
+
return (_jsx("th", { className: cn("px-3 py-2 align-top", showGridlines ? "border-b border-r border-[rgb(var(--sg-border))] last:border-r-0" : "border-b border-[rgb(var(--sg-border))]"), children: column.filter && filterField ? (_jsx("input", { value: filterValue, onChange: (event) => {
|
|
400
|
+
const nextFilters = {
|
|
401
|
+
...currentFilters,
|
|
402
|
+
[filterField]: event.target.value
|
|
403
|
+
};
|
|
404
|
+
const normalized = normalizeFilters(nextFilters);
|
|
405
|
+
commitFilters(normalized, currentGlobalFilter);
|
|
406
|
+
}, placeholder: column.filterPlaceholder ?? `Filter ${String(column.header ?? "")}`, className: "w-full rounded-md border border-[rgb(var(--sg-border))] bg-white px-2 py-1 text-xs outline-none focus:ring-2 focus:ring-[rgb(var(--sg-ring))]" })) : null }, `filter-${column.field ?? columnIndex}`));
|
|
407
|
+
}) })) : null] }), _jsx("tbody", { children: loading ? (_jsx("tr", { children: _jsx("td", { colSpan: Math.max(1, visibleColumns.length), className: "px-4 py-6 text-center text-sm text-[rgb(var(--sg-muted))]", children: loadingLabel }) })) : pagedRows.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: Math.max(1, visibleColumns.length), className: "px-4 py-6 text-center text-sm text-[rgb(var(--sg-muted))]", children: emptyLabel }) })) : (pagedRows.map((rowData, rowIndex) => {
|
|
408
|
+
const absoluteRowIndex = safeFirst + rowIndex;
|
|
409
|
+
const selected = isRowSelected(rowData);
|
|
410
|
+
const customRowClass = typeof rowClassName === "function" ? rowClassName(rowData, absoluteRowIndex) : rowClassName;
|
|
411
|
+
return (_jsx("tr", { "aria-selected": selected || undefined, onClick: (event) => {
|
|
412
|
+
const target = event.target;
|
|
413
|
+
if (target.closest("button,a,input,select,textarea,label,[data-sg-stop-row-select='true']")) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
handleRowSelection(rowData);
|
|
417
|
+
}, className: cn(selectionMode ? "cursor-pointer" : "", stripedRows && absoluteRowIndex % 2 === 1 ? "bg-[rgb(var(--sg-primary-50))]" : "", hoverableRows ? "hover:bg-[rgb(var(--sg-primary-100))]" : "", selected ? "bg-[rgb(var(--sg-primary-100))]" : "", customRowClass), children: visibleColumns.map((column, columnIndex) => {
|
|
418
|
+
const cellField = column.field;
|
|
419
|
+
const cellValue = getFieldValue(rowData, cellField);
|
|
420
|
+
const customCellClass = typeof column.bodyClassName === "function"
|
|
421
|
+
? column.bodyClassName(rowData, absoluteRowIndex)
|
|
422
|
+
: column.bodyClassName;
|
|
423
|
+
const renderedValue = column.body
|
|
424
|
+
? column.body(rowData, {
|
|
425
|
+
rowData,
|
|
426
|
+
rowIndex: absoluteRowIndex,
|
|
427
|
+
field: cellField,
|
|
428
|
+
value: cellValue
|
|
429
|
+
})
|
|
430
|
+
: (_jsx("span", { children: cellValue === null || cellValue === undefined ? "-" : String(cellValue) }));
|
|
431
|
+
return (_jsx("td", { className: cn("px-3 py-2 text-[rgb(var(--sg-text))]", resolveAlignmentClass(column.align), showGridlines ? "border-b border-r border-[rgb(var(--sg-border))] last:border-r-0" : "border-b border-[rgb(var(--sg-border))]", column.className, customCellClass), style: { width: toCssSize(column.width), minWidth: toCssSize(column.minWidth) }, children: renderedValue }, `cell-${cellField ?? columnIndex}-${absoluteRowIndex}`));
|
|
432
|
+
}) }, String(getRowIdentity(rowData, dataKey) ?? absoluteRowIndex)));
|
|
433
|
+
})) }), hasFooter ? (_jsx("tfoot", { children: _jsx("tr", { className: "bg-[rgb(var(--sg-primary-50))]", children: visibleColumns.map((column, columnIndex) => {
|
|
434
|
+
const footerValue = typeof column.footer === "function" ? column.footer(sortedRows) : column.footer;
|
|
435
|
+
return (_jsx("td", { className: cn("px-3 py-2 font-semibold text-[rgb(var(--sg-text))]", resolveAlignmentClass(column.align), showGridlines ? "border-t border-r border-[rgb(var(--sg-border))] last:border-r-0" : "border-t border-[rgb(var(--sg-border))]", column.className), style: { width: toCssSize(column.width), minWidth: toCssSize(column.minWidth) }, children: footerValue ?? null }, `foot-${column.field ?? columnIndex}`));
|
|
436
|
+
}) }) })) : null] }) }) }), paginator ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(SgButton, { size: "sm", appearance: "outline", disabled: currentPage <= 1, onClick: () => goToPage(currentPage - 1), children: prevLabel }), pageWindow.map((pageNumber) => (_jsx(SgButton, { size: "sm", appearance: pageNumber === currentPage ? "solid" : "outline", onClick: () => goToPage(pageNumber), children: String(pageNumber) }, `page-${pageNumber}`))), _jsx(SgButton, { size: "sm", appearance: "outline", disabled: currentPage >= pageCount, onClick: () => goToPage(currentPage + 1), children: nextLabel }), _jsx("span", { className: "min-w-[180px] text-xs text-[rgb(var(--sg-muted))] md:ml-auto", children: pageReport }), rowsPerPageOptions && rowsPerPageOptions.length > 0 ? (_jsxs("label", { className: "ml-auto inline-flex items-center gap-2 text-xs text-[rgb(var(--sg-muted))] md:ml-0", children: [_jsx("span", { children: rowsPerPageLabel }), _jsx("select", { value: currentRows, onChange: (event) => {
|
|
437
|
+
const nextRows = Number(event.target.value) || currentRows;
|
|
438
|
+
commitPage(0, nextRows);
|
|
439
|
+
}, className: "rounded-md border border-[rgb(var(--sg-border))] bg-white px-2 py-1 text-xs", children: rowsPerPageOptions.map((option) => (_jsx("option", { value: option, children: option }, option))) })] })) : null] })) : null] }) }) }));
|
|
440
|
+
}
|
|
441
|
+
export const SgDatatable = React.forwardRef(SgDatatableBase);
|
package/dist/layout/SgCard.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
export type SgCardVariant = "default" | "outlined" | "flat" | "elevated";
|
|
3
3
|
export type SgCardSize = "sm" | "md" | "lg";
|
|
4
|
+
export type SgCardPosition = {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
};
|
|
4
8
|
export type SgCardProps = Omit<React.HTMLAttributes<HTMLElement>, "title" | "onClick"> & {
|
|
5
9
|
className?: string;
|
|
6
10
|
headerClassName?: string;
|
|
7
11
|
bodyClassName?: string;
|
|
8
12
|
footerClassName?: string;
|
|
13
|
+
bgColor?: string;
|
|
14
|
+
bgColorTitle?: string;
|
|
15
|
+
bgColorFooter?: string;
|
|
9
16
|
cardStyle?: SgCardVariant;
|
|
10
17
|
size?: SgCardSize;
|
|
11
18
|
leading?: React.ReactNode;
|
|
@@ -23,8 +30,13 @@ export type SgCardProps = Omit<React.HTMLAttributes<HTMLElement>, "title" | "onC
|
|
|
23
30
|
open?: boolean;
|
|
24
31
|
onOpenChange?: (open: boolean) => void;
|
|
25
32
|
collapseIcon?: React.ReactNode;
|
|
33
|
+
collapseIconSize?: number;
|
|
26
34
|
collapseToggleAlign?: "left" | "right";
|
|
27
35
|
toggleOnHeaderClick?: boolean;
|
|
36
|
+
draggable?: boolean;
|
|
37
|
+
defaultPosition?: SgCardPosition;
|
|
38
|
+
dragPersistKey?: string;
|
|
39
|
+
onPositionChange?: (position: SgCardPosition) => void;
|
|
28
40
|
onClick?: React.MouseEventHandler<HTMLElement>;
|
|
29
41
|
children?: React.ReactNode;
|
|
30
42
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgCard.d.ts","sourceRoot":"","sources":["../../src/layout/SgCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SgCard.d.ts","sourceRoot":"","sources":["../../src/layout/SgCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AACzE,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC5C,MAAM,MAAM,cAAc,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC,GAAG;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAE1B,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAEzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,cAAc,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAEtD,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAC/C,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAiEF,wBAAgB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,2CAielD;yBAjee,MAAM"}
|