@seedgrid/fe-components 2026.10.3 → 2026.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/seedgrid-components.manifest.json +22 -4
- package/dist/generated/compiledStyles.d.ts +1 -1
- package/dist/generated/compiledStyles.d.ts.map +1 -1
- package/dist/generated/compiledStyles.js +1 -1
- package/dist/i18n/en-US.d.ts.map +1 -1
- package/dist/i18n/en-US.js +4 -0
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +4 -0
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/fr.js +4 -0
- package/dist/i18n/pt-BR.d.ts.map +1 -1
- package/dist/i18n/pt-BR.js +4 -0
- package/dist/i18n/pt-PT.d.ts.map +1 -1
- package/dist/i18n/pt-PT.js +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/inputs/SgDatatable.d.ts +10 -0
- package/dist/inputs/SgDatatable.d.ts.map +1 -1
- package/dist/inputs/SgDatatable.js +75 -14
- package/dist/inputs/SgDatatable.meta.d.ts.map +1 -1
- package/dist/inputs/SgDatatable.meta.js +6 -4
- package/dist/inputs/useSgContainerWidth.d.ts +13 -0
- package/dist/inputs/useSgContainerWidth.d.ts.map +1 -0
- package/dist/inputs/useSgContainerWidth.js +42 -0
- package/dist/sandbox.cjs +42 -42
- package/dist/style.css +1 -1
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
import { ArrowDown, ArrowUp, Check, ChevronFirst, ChevronLast, ChevronLeft, ChevronRight, ChevronsUpDown, GripVertical, MoreHorizontal, SlidersHorizontal } from "lucide-react";
|
|
@@ -7,6 +7,9 @@ import { SgButton } from "../buttons/SgButton";
|
|
|
7
7
|
import { useHasSgEnvironmentProvider, useSgPersistence } from "../environment/SgEnvironmentProvider";
|
|
8
8
|
import { SgGroupBox } from "../layout/SgGroupBox";
|
|
9
9
|
import { t, useComponentsI18n } from "../i18n";
|
|
10
|
+
import { useSgContainerWidth } from "./useSgContainerWidth";
|
|
11
|
+
/** Largura (px) do container abaixo da qual o modo `cards` entra. Igual ao corte do Flutter. */
|
|
12
|
+
export const SG_DATATABLE_MOBILE_BREAKPOINT = 600;
|
|
10
13
|
function cn(...parts) {
|
|
11
14
|
return parts.filter(Boolean).join(" ");
|
|
12
15
|
}
|
|
@@ -223,7 +226,7 @@ function SgDatatableBase(props, imperativeRef) {
|
|
|
223
226
|
const i18n = useComponentsI18n();
|
|
224
227
|
const hasEnvironmentProvider = useHasSgEnvironmentProvider();
|
|
225
228
|
const persistence = useSgPersistence();
|
|
226
|
-
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 = {}, showColumnManager = true, columnManagerLabel, columnManagerMaxHeight = 320 } = props;
|
|
229
|
+
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 = {}, showColumnManager = true, columnManagerLabel, columnManagerMaxHeight = 320, mobileMode = "scroll" } = props;
|
|
227
230
|
const isFirstControlled = first !== undefined;
|
|
228
231
|
const isSortFieldControlled = sortField !== undefined;
|
|
229
232
|
const isSortOrderControlled = sortOrder !== undefined;
|
|
@@ -237,6 +240,10 @@ function SgDatatableBase(props, imperativeRef) {
|
|
|
237
240
|
const [internalSelection, setInternalSelection] = React.useState(null);
|
|
238
241
|
const [internalGlobalFilter, setInternalGlobalFilter] = React.useState(globalFilter ?? "");
|
|
239
242
|
const [internalFilters, setInternalFilters] = React.useState(() => normalizeFilters(filters));
|
|
243
|
+
const rootRef = React.useRef(null);
|
|
244
|
+
const containerWidth = useSgContainerWidth(rootRef, mobileMode === "cards");
|
|
245
|
+
const cardsActive = mobileMode === "cards" && containerWidth !== null && containerWidth < SG_DATATABLE_MOBILE_BREAKPOINT;
|
|
246
|
+
const [isCardFiltersOpen, setIsCardFiltersOpen] = React.useState(false);
|
|
240
247
|
const columnManagerRef = React.useRef(null);
|
|
241
248
|
const columnManagerButtonRef = React.useRef(null);
|
|
242
249
|
const columnManagerPanelRef = React.useRef(null);
|
|
@@ -718,8 +725,37 @@ function SgDatatableBase(props, imperativeRef) {
|
|
|
718
725
|
return next;
|
|
719
726
|
});
|
|
720
727
|
}, [columnDescriptors]);
|
|
728
|
+
const renderCellContent = (column, rowData, absoluteRowIndex) => {
|
|
729
|
+
const cellField = column.field;
|
|
730
|
+
const cellValue = getFieldValue(rowData, cellField);
|
|
731
|
+
return column.body ? (column.body(rowData, {
|
|
732
|
+
rowData,
|
|
733
|
+
rowIndex: absoluteRowIndex,
|
|
734
|
+
field: cellField,
|
|
735
|
+
value: cellValue
|
|
736
|
+
})) : (_jsx("span", { children: cellValue === null || cellValue === undefined ? "-" : String(cellValue) }));
|
|
737
|
+
};
|
|
738
|
+
// Modo cartoes: o titulo e' a primeira coluna marcada com cardTitle; as demais viram "rotulo: valor",
|
|
739
|
+
// exceto as marcadas com cardHidden.
|
|
740
|
+
const cardTitleColumn = cardsActive ? visibleColumns.find((column) => column.cardTitle) : undefined;
|
|
741
|
+
const cardBodyColumns = cardsActive
|
|
742
|
+
? visibleColumns.filter((column) => column !== cardTitleColumn && !column.cardHidden)
|
|
743
|
+
: [];
|
|
744
|
+
const cardFilterColumns = cardsActive
|
|
745
|
+
? visibleColumns.filter((column) => column.filter && (column.filterField ?? column.field))
|
|
746
|
+
: [];
|
|
747
|
+
const cardSortColumns = cardsActive
|
|
748
|
+
? visibleColumns.filter((column) => column.sortable && (column.sortField ?? column.field))
|
|
749
|
+
: [];
|
|
750
|
+
const cardHeaderLabel = (column) => typeof column.header === "string" || typeof column.header === "number"
|
|
751
|
+
? String(column.header)
|
|
752
|
+
: column.field ?? "";
|
|
753
|
+
const sortByLabel = resolveMessage(t(i18n, "components.datatable.sortBy"), "components.datatable.sortBy", "Ordenar por");
|
|
754
|
+
const sortNoneLabel = resolveMessage(t(i18n, "components.datatable.sortNone"), "components.datatable.sortNone", "Sem ordem");
|
|
755
|
+
const sortDirectionLabel = resolveMessage(t(i18n, "components.datatable.sortDirection"), "components.datatable.sortDirection", "Inverter ordem");
|
|
756
|
+
const filtersPanelLabel = resolveMessage(t(i18n, "components.datatable.filters"), "components.datatable.filters", "Filtros");
|
|
721
757
|
const resolvedTitle = (groupBoxProps.title ?? title ?? "").trim() || " ";
|
|
722
|
-
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-[rgb(var(--sg-input-bg,var(--sg-surface)))] px-3 py-2 text-sm text-[rgb(var(--sg-input-fg,var(--sg-text)))] placeholder:text-[rgb(var(--sg-input-placeholder,var(--sg-muted)))] 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, _jsxs("div", { className: "relative", ref: columnManagerRef, children: [showColumnManager ? (_jsx("div", { className: "mb-2 flex items-center justify-start", children: _jsx("button", { ref: columnManagerButtonRef, type: "button", "aria-label": resolvedColumnManagerLabel, title: resolvedColumnManagerLabel, onClick: () => setIsColumnManagerOpen((prev) => !prev), className: "inline-flex h-9 w-9 items-center justify-center rounded-md border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-surface))] text-[rgb(var(--sg-primary-600))] shadow-sm transition hover:bg-[rgb(var(--sg-primary-50))] hover:text-[rgb(var(--sg-primary-700))]", children: _jsx(SlidersHorizontal, { size: 18 }) }) })) : null, showColumnManager && isColumnManagerOpen && columnManagerPosition && typeof document !== "undefined"
|
|
758
|
+
return (_jsx("div", { ref: rootRef, 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-[rgb(var(--sg-input-bg,var(--sg-surface)))] px-3 py-2 text-sm text-[rgb(var(--sg-input-fg,var(--sg-text)))] placeholder:text-[rgb(var(--sg-input-placeholder,var(--sg-muted)))] 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, _jsxs("div", { className: "relative", ref: columnManagerRef, children: [showColumnManager ? (_jsx("div", { className: "mb-2 flex items-center justify-start", children: _jsx("button", { ref: columnManagerButtonRef, type: "button", "aria-label": resolvedColumnManagerLabel, title: resolvedColumnManagerLabel, onClick: () => setIsColumnManagerOpen((prev) => !prev), className: "inline-flex h-9 w-9 items-center justify-center rounded-md border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-surface))] text-[rgb(var(--sg-primary-600))] shadow-sm transition hover:bg-[rgb(var(--sg-primary-50))] hover:text-[rgb(var(--sg-primary-700))]", children: _jsx(SlidersHorizontal, { size: 18 }) }) })) : null, showColumnManager && isColumnManagerOpen && columnManagerPosition && typeof document !== "undefined"
|
|
723
759
|
? createPortal(_jsxs("div", { ref: columnManagerPanelRef, className: "fixed z-[1400] w-[300px] rounded-xl border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-surface))] shadow-xl", style: {
|
|
724
760
|
top: columnManagerPosition.top,
|
|
725
761
|
left: columnManagerPosition.left
|
|
@@ -749,7 +785,40 @@ function SgDatatableBase(props, imperativeRef) {
|
|
|
749
785
|
? "text-[rgb(var(--sg-muted))]"
|
|
750
786
|
: "text-[rgb(var(--sg-text))]", !canHideThisColumn ? "cursor-not-allowed" : ""), children: columnLabel }), _jsx("div", { className: cn("inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-[rgb(var(--sg-muted))]", isReorderable ? "cursor-grab active:cursor-grabbing" : "opacity-40"), title: dragHandleLabel, children: _jsx(GripVertical, { size: 16 }) })] }, `manager-${item.key}`));
|
|
751
787
|
}) }) }), _jsxs("div", { className: "flex items-center gap-2 border-t border-[rgb(var(--sg-border))] px-3 py-3", children: [_jsx(SgButton, { size: "sm", appearance: "outline", onClick: restoreDefaultColumns, children: restoreColumnsLabel }), _jsx(SgButton, { size: "sm", appearance: "outline", onClick: showAllColumns, children: showAllColumnsLabel })] })] }), document.body)
|
|
752
|
-
: null,
|
|
788
|
+
: null, cardsActive ? (_jsxs("div", { "data-sg-datatable-cards": "true", className: "space-y-2", children: [cardSortColumns.length > 0 || (showColumnFilters && cardFilterColumns.length > 0) ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [cardSortColumns.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs("select", { "aria-label": sortByLabel, value: currentSortOrder === 0 || !currentSortField ? "" : currentSortField, onChange: (event) => {
|
|
789
|
+
const next = event.target.value;
|
|
790
|
+
if (!next)
|
|
791
|
+
commitSort(null, 0);
|
|
792
|
+
else
|
|
793
|
+
commitSort(next, 1);
|
|
794
|
+
}, className: "min-w-0 flex-1 rounded-md border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-input-bg,var(--sg-surface)))] px-2 py-2 text-sm text-[rgb(var(--sg-input-fg,var(--sg-text)))]", children: [_jsx("option", { value: "", children: sortNoneLabel }), cardSortColumns.map((column, columnIndex) => {
|
|
795
|
+
const key = (column.sortField ?? column.field);
|
|
796
|
+
return (_jsx("option", { value: key, children: cardHeaderLabel(column) }, `sort-${column.columnId ?? key}-${columnIndex}`));
|
|
797
|
+
})] }), _jsx(SgButton, { size: "sm", appearance: "outline", iconOnly: true, "aria-label": sortDirectionLabel, title: sortDirectionLabel, disabled: !currentSortField || currentSortOrder === 0, onClick: () => {
|
|
798
|
+
if (!currentSortField)
|
|
799
|
+
return;
|
|
800
|
+
commitSort(currentSortField, currentSortOrder === 1 ? -1 : 1);
|
|
801
|
+
}, leftIcon: currentSortOrder === -1 ? _jsx(ArrowDown, { size: 16 }) : _jsx(ArrowUp, { size: 16 }) })] })) : null, showColumnFilters && cardFilterColumns.length > 0 ? (_jsx(SgButton, { size: "sm", appearance: "outline", "aria-expanded": isCardFiltersOpen, onClick: () => setIsCardFiltersOpen((prev) => !prev), children: filtersPanelLabel })) : null] })) : null, showColumnFilters && isCardFiltersOpen && cardFilterColumns.length > 0 ? (_jsx("div", { "data-sg-datatable-filters-panel": "true", className: "space-y-2 rounded-lg border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-surface))] p-3", children: cardFilterColumns.map((column, columnIndex) => {
|
|
802
|
+
const filterField = (column.filterField ?? column.field);
|
|
803
|
+
const filterValue = currentFilters[filterField] ?? "";
|
|
804
|
+
return (_jsxs("label", { className: "block space-y-1", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--sg-muted))]", children: column.header }), _jsx("input", { value: filterValue, onChange: (event) => {
|
|
805
|
+
const normalized = normalizeFilters({ ...currentFilters, [filterField]: event.target.value });
|
|
806
|
+
commitFilters(normalized, currentGlobalFilter);
|
|
807
|
+
}, placeholder: column.filterPlaceholder ?? t(i18n, "components.datatable.columnFilterPlaceholder", { column: String(column.header ?? "") }), className: "w-full rounded-md border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-input-bg,var(--sg-surface)))] px-3 py-2 text-sm text-[rgb(var(--sg-input-fg,var(--sg-text)))] placeholder:text-[rgb(var(--sg-input-placeholder,var(--sg-muted)))] outline-none focus:ring-2 focus:ring-[rgb(var(--sg-ring))]" })] }, `cardfilter-${column.columnId ?? filterField}-${columnIndex}`));
|
|
808
|
+
}) })) : null, loading ? (_jsx("p", { "data-sg-card-state": "loading", className: "px-4 py-6 text-center text-sm text-[rgb(var(--sg-muted))]", children: loadingLabel })) : pagedRows.length === 0 ? (_jsx("p", { "data-sg-card-state": "empty", className: "px-4 py-6 text-center text-sm text-[rgb(var(--sg-muted))]", children: emptyLabel })) : (_jsx("ul", { role: "list", className: "space-y-2", children: pagedRows.map((rowData, rowIndex) => {
|
|
809
|
+
const absoluteRowIndex = safeFirst + rowIndex;
|
|
810
|
+
const selected = isRowSelected(rowData);
|
|
811
|
+
const customRowClass = typeof rowClassName === "function" ? rowClassName(rowData, absoluteRowIndex) : rowClassName;
|
|
812
|
+
return (_jsxs("li", { "data-sg-card": "true", "aria-selected": selected || undefined, onClick: (event) => {
|
|
813
|
+
const target = event.target;
|
|
814
|
+
if (target.closest("button,a,input,select,textarea,label,[data-sg-stop-row-select='true']")) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
handleRowSelection(rowData);
|
|
818
|
+
}, className: cn("rounded-lg border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-surface))] p-3", selectionMode ? "cursor-pointer" : "", stripedRows && absoluteRowIndex % 2 === 1 ? "bg-[rgb(var(--sg-primary-50))]" : "", selected ? "border-[rgb(var(--sg-primary-600))] bg-[rgb(var(--sg-primary-100))]" : "", customRowClass), children: [cardTitleColumn ? (_jsx("div", { "data-sg-card-title": "true", className: "mb-2 text-base font-semibold text-[rgb(var(--sg-text))]", children: renderCellContent(cardTitleColumn, rowData, absoluteRowIndex) })) : null, _jsx("dl", { className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 text-sm", children: cardBodyColumns.map((column, columnIndex) => (_jsxs(React.Fragment, { children: [_jsx("dt", { className: "font-semibold text-[rgb(var(--sg-muted))]", children: column.header }), _jsx("dd", { className: cn("min-w-0 break-words text-[rgb(var(--sg-text))]", column.className), children: renderCellContent(column, rowData, absoluteRowIndex) })] }, `card-${column.columnId ?? column.field ?? columnIndex}-${absoluteRowIndex}`))) })] }, String(getRowIdentity(rowData, dataKey) ?? absoluteRowIndex)));
|
|
819
|
+
}) })), hasFooter ? (_jsx("dl", { "data-sg-card-footer": "true", className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 rounded-lg bg-[rgb(var(--sg-primary-50))] p-3 text-sm font-semibold text-[rgb(var(--sg-text))]", children: visibleColumns
|
|
820
|
+
.filter((column) => column.footer !== undefined && !column.cardHidden)
|
|
821
|
+
.map((column, columnIndex) => (_jsxs(React.Fragment, { children: [_jsx("dt", { children: column.header }), _jsx("dd", { children: typeof column.footer === "function" ? column.footer(sortedRows) : column.footer })] }, `cardfoot-${column.columnId ?? column.field ?? columnIndex}`))) })) : null] })) : (_jsx("div", { className: "overflow-x-auto rounded-lg border border-[rgb(var(--sg-border))] bg-[rgb(var(--sg-surface))]", 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) => {
|
|
753
822
|
const sortCandidate = column.sortField ?? column.field;
|
|
754
823
|
const isSorted = Boolean(sortCandidate) && sortCandidate === currentSortField;
|
|
755
824
|
// O indicador de ordenacao era TEXTO literal (" ^", " v", " <>"), entao o
|
|
@@ -782,24 +851,16 @@ function SgDatatableBase(props, imperativeRef) {
|
|
|
782
851
|
handleRowSelection(rowData);
|
|
783
852
|
}, 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) => {
|
|
784
853
|
const cellField = column.field;
|
|
785
|
-
const cellValue = getFieldValue(rowData, cellField);
|
|
786
854
|
const customCellClass = typeof column.bodyClassName === "function"
|
|
787
855
|
? column.bodyClassName(rowData, absoluteRowIndex)
|
|
788
856
|
: column.bodyClassName;
|
|
789
|
-
const renderedValue = column
|
|
790
|
-
? column.body(rowData, {
|
|
791
|
-
rowData,
|
|
792
|
-
rowIndex: absoluteRowIndex,
|
|
793
|
-
field: cellField,
|
|
794
|
-
value: cellValue
|
|
795
|
-
})
|
|
796
|
-
: (_jsx("span", { children: cellValue === null || cellValue === undefined ? "-" : String(cellValue) }));
|
|
857
|
+
const renderedValue = renderCellContent(column, rowData, absoluteRowIndex);
|
|
797
858
|
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-${column.columnId ?? cellField ?? columnIndex}-${absoluteRowIndex}`));
|
|
798
859
|
}) }, String(getRowIdentity(rowData, dataKey) ?? absoluteRowIndex)));
|
|
799
860
|
})) }), hasFooter ? (_jsx("tfoot", { children: _jsx("tr", { className: "bg-[rgb(var(--sg-primary-50))]", children: visibleColumns.map((column, columnIndex) => {
|
|
800
861
|
const footerValue = typeof column.footer === "function" ? column.footer(sortedRows) : column.footer;
|
|
801
862
|
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.columnId ?? column.field ?? columnIndex}`));
|
|
802
|
-
}) }) })) : null] }) })] }), paginator ? (_jsxs("div", { ref: paginatorRef, className: "flex flex-wrap items-center gap-1.5", children: [_jsxs("nav", { "aria-label": t(i18n, "components.datatable.pagination"), className: "flex flex-wrap items-center gap-1.5", children: [_jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": firstLabel, title: firstLabel, disabled: currentPage <= 1, onClick: () => goToPage(1), leftIcon: _jsx(ChevronFirst, { size: 16 }) }), _jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": prevLabel, title: prevLabel, disabled: currentPage <= 1, onClick: () => goToPage(currentPage - 1), leftIcon: _jsx(ChevronLeft, { size: 16 }) }), pageTokens.map((token, tokenIndex) => {
|
|
863
|
+
}) }) })) : null] }) }))] }), paginator ? (_jsxs("div", { ref: paginatorRef, className: "flex flex-wrap items-center gap-1.5", children: [_jsxs("nav", { "aria-label": t(i18n, "components.datatable.pagination"), className: "flex flex-wrap items-center gap-1.5", children: [_jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": firstLabel, title: firstLabel, disabled: currentPage <= 1, onClick: () => goToPage(1), leftIcon: _jsx(ChevronFirst, { size: 16 }) }), _jsx(SgButton, { size: "sm", appearance: "outline", shape: "default", iconOnly: true, "aria-label": prevLabel, title: prevLabel, disabled: currentPage <= 1, onClick: () => goToPage(currentPage - 1), leftIcon: _jsx(ChevronLeft, { size: 16 }) }), pageTokens.map((token, tokenIndex) => {
|
|
803
864
|
if (token.type === "ellipsis") {
|
|
804
865
|
const ellipsisLabel = token.direction === "backward" ? jumpBackwardLabel : jumpForwardLabel;
|
|
805
866
|
return (_jsx(SgButton, { size: "sm", appearance: "ghost", shape: "default", iconOnly: true, "aria-label": ellipsisLabel, title: ellipsisLabel, onClick: () => goToPage(token.target), leftIcon: _jsx(MoreHorizontal, { size: 16 }) }, `ellipsis-${token.direction}-${tokenIndex}`));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgDatatable.meta.d.ts","sourceRoot":"","sources":["../../src/inputs/SgDatatable.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"SgDatatable.meta.d.ts","sourceRoot":"","sources":["../../src/inputs/SgDatatable.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QA+BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAoBrB,CAAC"}
|
|
@@ -7,9 +7,9 @@ export const sgMeta = {
|
|
|
7
7
|
displayName: "SgDatatable",
|
|
8
8
|
category: "data",
|
|
9
9
|
subcategory: "datatable",
|
|
10
|
-
description: "Tabela de dados interativa com colunas configuraveis, paginacao, ordenacao, filtro, selecao e persistencia de layout de colunas.",
|
|
10
|
+
description: "Tabela de dados interativa com colunas configuraveis, paginacao, ordenacao, filtro, selecao e persistencia de layout de colunas. Em celular pode virar lista de cartoes (mobileMode=\"cards\").",
|
|
11
11
|
tags: ["data", "table", "grid", "listing", "interactive"],
|
|
12
|
-
capabilities: ["pagination", "sorting", "filtering", "selection", "column-config", "column-layout-persistence", "row-actions"],
|
|
12
|
+
capabilities: ["pagination", "sorting", "filtering", "selection", "column-config", "column-layout-persistence", "row-actions", "mobile-cards"],
|
|
13
13
|
fieldSemantics: ["tabularData", "dataGrid", "listing", "collectionView"],
|
|
14
14
|
props: [
|
|
15
15
|
{ name: "id", type: "string", description: "Identificador da tabela usado para DOM e persistencia do layout de colunas.", semanticRole: "data", bindable: true },
|
|
@@ -17,6 +17,7 @@ export const sgMeta = {
|
|
|
17
17
|
{ name: "rows", type: "number", description: "Linhas por pagina quando a paginacao esta ativa (os dados vao em value).", semanticRole: "behavior", bindable: true },
|
|
18
18
|
{ name: "selectionMode", type: "\"single\" | \"multiple\"", description: "Modo de selecao de linhas.", semanticRole: "behavior", bindable: true },
|
|
19
19
|
{ name: "loading", type: "boolean", default: false, description: "Indica estado de carregamento.", semanticRole: "appearance", bindable: true },
|
|
20
|
+
{ name: "mobileMode", type: "\"scroll\" | \"cards\"", default: "scroll", description: "Comportamento em tela estreita (container abaixo de 600 px): scroll rola a tabela na horizontal; cards mostra cada linha como cartao 'coluna: valor' mantendo ordenacao, selecao, acoes, rodape e paginacao.", semanticRole: "behavior", bindable: true },
|
|
20
21
|
{ name: "paginator", type: "boolean", default: false, description: "Ativa paginacao.", semanticRole: "behavior", bindable: true },
|
|
21
22
|
{ name: "onPage", type: "(event: SgDatatablePageEvent) => void", description: "Callback de paginacao.", semanticRole: "event", bindable: false },
|
|
22
23
|
{ name: "onSort", type: "(event: SgDatatableSortEvent) => void", description: "Callback de ordenacao.", semanticRole: "event", bindable: false }
|
|
@@ -34,14 +35,15 @@ export const aiHints = {
|
|
|
34
35
|
preferredUseCases: [
|
|
35
36
|
"Exibicao de colecoes tabulares com interacao.",
|
|
36
37
|
"Listagens administrativas com filtro, ordenacao e pagina.",
|
|
37
|
-
"Tabelas com acoes por linha e selecao."
|
|
38
|
+
"Tabelas com acoes por linha e selecao.",
|
|
39
|
+
"Listagens que precisam funcionar em celular: mobileMode=\"cards\" mostra cada linha como cartao (tabela que se adapta ao celular)."
|
|
38
40
|
],
|
|
39
41
|
avoidUseCases: [
|
|
40
42
|
"Formularios de entrada simples.",
|
|
41
43
|
"Colecoes muito visuais em formato de cards.",
|
|
42
44
|
"Dados pequenos onde uma lista simples basta."
|
|
43
45
|
],
|
|
44
|
-
synonyms: ["datatable", "data grid", "table", "listing grid"],
|
|
46
|
+
synonyms: ["datatable", "tabela no celular", "tabela responsiva", "data grid", "table", "listing grid", "tabela mobile", "lista de cartoes", "cards no celular", "responsive table", "mobile table"],
|
|
45
47
|
relatedEntityFields: ["rows", "records", "items", "results", "tableData"],
|
|
46
48
|
compositionHints: [
|
|
47
49
|
"Combinar com filtros acima da tabela usando inputs de busca e selecao.",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Mede a largura (px) do elemento observado e acompanha o redimensionamento.
|
|
4
|
+
*
|
|
5
|
+
* - Devolve `null` enquanto a largura nao e conhecida (SSR, antes da primeira medicao,
|
|
6
|
+
* elemento oculto/sem layout). Quem consome deve tratar `null` como "nao estreito",
|
|
7
|
+
* para nunca trocar o layout no servidor nem piscar na hidratacao.
|
|
8
|
+
* - Mede o CONTAINER (e nao a janela), como o `LayoutBuilder` do Flutter: a tabela dentro de
|
|
9
|
+
* um painel estreito num desktop tambem e considerada estreita.
|
|
10
|
+
* - `enabled=false` nao observa nada (custo zero para quem nao usa).
|
|
11
|
+
*/
|
|
12
|
+
export declare function useSgContainerWidth(ref: React.RefObject<HTMLElement | null>, enabled?: boolean): number | null;
|
|
13
|
+
//# sourceMappingURL=useSgContainerWidth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSgContainerWidth.d.ts","sourceRoot":"","sources":["../../src/inputs/useSgContainerWidth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EACxC,OAAO,UAAO,GACb,MAAM,GAAG,IAAI,CAiCf"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
|
|
4
|
+
/**
|
|
5
|
+
* Mede a largura (px) do elemento observado e acompanha o redimensionamento.
|
|
6
|
+
*
|
|
7
|
+
* - Devolve `null` enquanto a largura nao e conhecida (SSR, antes da primeira medicao,
|
|
8
|
+
* elemento oculto/sem layout). Quem consome deve tratar `null` como "nao estreito",
|
|
9
|
+
* para nunca trocar o layout no servidor nem piscar na hidratacao.
|
|
10
|
+
* - Mede o CONTAINER (e nao a janela), como o `LayoutBuilder` do Flutter: a tabela dentro de
|
|
11
|
+
* um painel estreito num desktop tambem e considerada estreita.
|
|
12
|
+
* - `enabled=false` nao observa nada (custo zero para quem nao usa).
|
|
13
|
+
*/
|
|
14
|
+
export function useSgContainerWidth(ref, enabled = true) {
|
|
15
|
+
const [width, setWidth] = React.useState(null);
|
|
16
|
+
useIsomorphicLayoutEffect(() => {
|
|
17
|
+
if (!enabled) {
|
|
18
|
+
setWidth((prev) => (prev === null ? prev : null));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const element = ref.current;
|
|
22
|
+
if (!element)
|
|
23
|
+
return;
|
|
24
|
+
const measure = () => {
|
|
25
|
+
const next = element.getBoundingClientRect().width;
|
|
26
|
+
const resolved = next > 0 ? Math.round(next) : null;
|
|
27
|
+
setWidth((prev) => (prev === resolved ? prev : resolved));
|
|
28
|
+
};
|
|
29
|
+
measure();
|
|
30
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
31
|
+
const observer = new ResizeObserver(measure);
|
|
32
|
+
observer.observe(element);
|
|
33
|
+
return () => observer.disconnect();
|
|
34
|
+
}
|
|
35
|
+
if (typeof window !== "undefined") {
|
|
36
|
+
window.addEventListener("resize", measure);
|
|
37
|
+
return () => window.removeEventListener("resize", measure);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}, [ref, enabled]);
|
|
41
|
+
return width;
|
|
42
|
+
}
|