@pallasoft/ps-table-components 1.9.0 → 1.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/components/ps-data-table.d.ts +4 -1
- package/dist/components/ps-data-table.js +92 -23
- package/dist/components/ps-table-bulk-bar.js +7 -1
- package/dist/components/ps-table-group-header.d.ts +9 -0
- package/dist/components/ps-table-group-header.js +8 -0
- package/dist/components/ps-table-toolbar.js +1 -1
- package/dist/constants/table-config.d.ts +4 -0
- package/dist/constants/table-config.js +4 -0
- package/dist/esm/components/ps-data-table.js +93 -24
- package/dist/esm/components/ps-table-bulk-bar.js +8 -2
- package/dist/esm/components/ps-table-group-header.js +4 -0
- package/dist/esm/components/ps-table-toolbar.js +1 -1
- package/dist/esm/constants/table-config.js +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/interfaces/table-grouping.js +1 -0
- package/dist/esm/interfaces/table-row-group.js +1 -0
- package/dist/esm/interfaces/table-row-motion.js +1 -0
- package/dist/esm/services/ps-table-group-service.js +14 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/interfaces/table-grouping.d.ts +7 -0
- package/dist/interfaces/table-grouping.js +2 -0
- package/dist/interfaces/table-row-group.d.ts +4 -0
- package/dist/interfaces/table-row-group.js +2 -0
- package/dist/interfaces/table-row-motion.d.ts +4 -0
- package/dist/interfaces/table-row-motion.js +2 -0
- package/dist/services/ps-table-group-service.d.ts +4 -0
- package/dist/services/ps-table-group-service.js +18 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { PsTableColumn } from '../interfaces/table-column';
|
|
3
|
+
import type { PsTableGrouping } from '../interfaces/table-grouping';
|
|
3
4
|
import type { PsTableQuery } from '../interfaces/table-query';
|
|
4
5
|
import type { PsTableRowAction } from '../interfaces/table-row-action';
|
|
5
6
|
import type { PsTableSort } from '../interfaces/table-sort';
|
|
@@ -15,6 +16,8 @@ export interface PsDataTableProps<RowData> {
|
|
|
15
16
|
getRowId: (row: RowData) => string;
|
|
16
17
|
getRowLabel?: (row: RowData) => string;
|
|
17
18
|
getSearchText?: (row: RowData) => string;
|
|
19
|
+
grouping?: PsTableGrouping<RowData>;
|
|
20
|
+
isLoading?: boolean;
|
|
18
21
|
onPageChange?: (page: number) => void;
|
|
19
22
|
onQueryChange?: (query: PsTableQuery) => void;
|
|
20
23
|
onRowClick?: (row: RowData) => void;
|
|
@@ -28,4 +31,4 @@ export interface PsDataTableProps<RowData> {
|
|
|
28
31
|
toolbarActions?: ReactNode;
|
|
29
32
|
totalRows?: number;
|
|
30
33
|
}
|
|
31
|
-
export declare const PsDataTable: <RowData>({ caption, className, columns, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
|
|
34
|
+
export declare const PsDataTable: <RowData>({ caption, className, columns, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
|
|
@@ -3,14 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PsDataTable = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const react_1 = require("react");
|
|
6
|
+
const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
6
7
|
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
7
8
|
const ps_input_components_1 = require("@pallasoft/ps-input-components");
|
|
8
9
|
const ps_motion_components_1 = require("@pallasoft/ps-motion-components");
|
|
9
10
|
const table_config_1 = require("../constants/table-config");
|
|
11
|
+
const ps_table_group_service_1 = require("../services/ps-table-group-service");
|
|
10
12
|
const ps_table_query_service_1 = require("../services/ps-table-query-service");
|
|
11
13
|
const ps_table_bulk_bar_1 = require("./ps-table-bulk-bar");
|
|
12
14
|
const ps_table_card_list_1 = require("./ps-table-card-list");
|
|
13
15
|
const ps_table_column_picker_1 = require("./ps-table-column-picker");
|
|
16
|
+
const ps_table_group_header_1 = require("./ps-table-group-header");
|
|
14
17
|
const ps_table_pagination_1 = require("./ps-table-pagination");
|
|
15
18
|
const ps_table_sort_header_1 = require("./ps-table-sort-header");
|
|
16
19
|
const ps_table_toolbar_1 = require("./ps-table-toolbar");
|
|
@@ -30,11 +33,14 @@ const alignClassNames = {
|
|
|
30
33
|
left: 'text-left',
|
|
31
34
|
right: 'text-right tabular-nums',
|
|
32
35
|
};
|
|
33
|
-
const PsDataTable = ({ caption, className, columns, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
|
|
36
|
+
const PsDataTable = ({ caption, className, columns, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
|
|
34
37
|
const containerRef = (0, react_1.useRef)(null);
|
|
35
38
|
const [uncontrolledPage, setUncontrolledPage] = (0, react_1.useState)(1);
|
|
36
39
|
const [uncontrolledQuery, setUncontrolledQuery] = (0, react_1.useState)(() => ps_table_query_service_1.PsTableQueryService.empty(defaultSort ? defaultSort : null));
|
|
37
40
|
const [selectedIds, setSelectedIds] = (0, react_1.useState)([]);
|
|
41
|
+
const [toggledGroupKeys, setToggledGroupKeys] = (0, react_1.useState)([]);
|
|
42
|
+
const [closingGroupKeys, setClosingGroupKeys] = (0, react_1.useState)([]);
|
|
43
|
+
const [openedGroupKeys, setOpenedGroupKeys] = (0, react_1.useState)([]);
|
|
38
44
|
const currentQuery = query ? query : uncontrolledQuery;
|
|
39
45
|
const { filters, hiddenColumnIds, search, sort } = currentQuery;
|
|
40
46
|
const visibleColumns = columns.filter((column) => !hiddenColumnIds.includes(column.id));
|
|
@@ -60,11 +66,56 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
60
66
|
const safePage = Math.min(Math.max(currentPage, 1), pageCount);
|
|
61
67
|
const isQueried = search.trim() !== '' ||
|
|
62
68
|
Object.values(filters).some((value) => ps_table_query_service_1.PsTableQueryService.isActive(value));
|
|
69
|
+
const groups = (0, react_1.useMemo)(() => (grouping ? ps_table_group_service_1.PsTableGroupService.group(queriedRows, grouping.getGroupKey) : []), [grouping, queriedRows]);
|
|
70
|
+
const initialExpandedCount = grouping && grouping.initialExpandedCount !== undefined ? grouping.initialExpandedCount : 1;
|
|
71
|
+
const isGroupExpanded = (groupKey, groupIndex) => isQueried || groupIndex < initialExpandedCount !== toggledGroupKeys.includes(groupKey);
|
|
72
|
+
const flipGroup = (groupKey) => setToggledGroupKeys((current) => current.includes(groupKey)
|
|
73
|
+
? current.filter((key) => key !== groupKey)
|
|
74
|
+
: [...current, groupKey]);
|
|
75
|
+
const toggleGroup = (groupKey, isExpanded) => {
|
|
76
|
+
if (closingGroupKeys.includes(groupKey))
|
|
77
|
+
return;
|
|
78
|
+
if (!isExpanded) {
|
|
79
|
+
setOpenedGroupKeys((current) => [...current.filter((key) => key !== groupKey), groupKey]);
|
|
80
|
+
flipGroup(groupKey);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
setClosingGroupKeys((current) => [...current, groupKey]);
|
|
84
|
+
window.setTimeout(() => {
|
|
85
|
+
flipGroup(groupKey);
|
|
86
|
+
setOpenedGroupKeys((current) => current.filter((key) => key !== groupKey));
|
|
87
|
+
setClosingGroupKeys((current) => current.filter((key) => key !== groupKey));
|
|
88
|
+
}, table_config_1.PsTableConfig.groupCloseMs);
|
|
89
|
+
};
|
|
90
|
+
const groupRowMotion = (groupKey, rowIndex) => {
|
|
91
|
+
if (closingGroupKeys.includes(groupKey))
|
|
92
|
+
return { className: 'ps-table-row-exit' };
|
|
93
|
+
if (!openedGroupKeys.includes(groupKey))
|
|
94
|
+
return undefined;
|
|
95
|
+
return {
|
|
96
|
+
className: 'ps-table-row-enter',
|
|
97
|
+
delayMs: Math.min(rowIndex, table_config_1.PsTableConfig.groupStaggerLimit) * table_config_1.PsTableConfig.groupRowStaggerMs,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
63
100
|
const visibleRows = (0, react_1.useMemo)(() => {
|
|
101
|
+
if (grouping)
|
|
102
|
+
return groups.flatMap((group, groupIndex) => isQueried || groupIndex < initialExpandedCount !== toggledGroupKeys.includes(group.key)
|
|
103
|
+
? group.rows
|
|
104
|
+
: []);
|
|
64
105
|
if (!pageSize || isServerPaginated)
|
|
65
106
|
return queriedRows;
|
|
66
107
|
return queriedRows.slice((safePage - 1) * pageSize, safePage * pageSize);
|
|
67
|
-
}, [
|
|
108
|
+
}, [
|
|
109
|
+
grouping,
|
|
110
|
+
groups,
|
|
111
|
+
initialExpandedCount,
|
|
112
|
+
isQueried,
|
|
113
|
+
isServerPaginated,
|
|
114
|
+
pageSize,
|
|
115
|
+
queriedRows,
|
|
116
|
+
safePage,
|
|
117
|
+
toggledGroupKeys,
|
|
118
|
+
]);
|
|
68
119
|
const changePage = (next) => {
|
|
69
120
|
if (onPageChange) {
|
|
70
121
|
onPageChange(next);
|
|
@@ -96,6 +147,26 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
96
147
|
? current.filter((rowId) => !pageIds.includes(rowId))
|
|
97
148
|
: [...new Set([...current, ...pageIds])]);
|
|
98
149
|
const toggleRow = (rowId) => setSelectedIds((current) => current.includes(rowId) ? current.filter((id) => id !== rowId) : [...current, rowId]);
|
|
150
|
+
const renderCardList = (cardRows, emptyContent) => ((0, jsx_runtime_1.jsx)(ps_table_card_list_1.PsTableCardList, { columns: visibleColumns, emptyContent: emptyContent, expandedRowIds: expandedRowIds, footerColumns: hasFooter ? visibleColumns.filter((column) => column.footer !== undefined) : [], getRowId: getRowId, getRowLabel: (row) => (getRowLabel ? getRowLabel(row) : getRowId(row)), isSelectable: isSelectable, onRowClick: onRowClick, onRowHover: onRowHover, onToggleRow: toggleRow, renderExpandedRow: renderExpandedRow, rows: cardRows, selectedIds: selectedIds }));
|
|
151
|
+
const renderRow = (row, motion) => {
|
|
152
|
+
const rowId = getRowId(row);
|
|
153
|
+
const isExpanded = expandedRowIds ? expandedRowIds.includes(rowId) : false;
|
|
154
|
+
const rowLabel = getRowLabel ? getRowLabel(row) : rowId;
|
|
155
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("tr", { "aria-expanded": renderExpandedRow ? isExpanded : undefined, "aria-label": onRowClick ? rowLabel : undefined, className: ps_design_system_1.PallasoftClassNameService.merge('align-middle transition-colors', motion ? motion.className : '', onRowClick ? 'ps-focus-ring cursor-pointer hover:bg-mist/60' : 'hover:bg-mist/40', isExpanded ? 'bg-mist/40' : ''), onClick: onRowClick ? () => onRowClick(row) : undefined, onFocus: onRowHover ? () => onRowHover(row) : undefined, onPointerEnter: onRowHover ? () => onRowHover(row) : undefined, onKeyDown: onRowClick
|
|
156
|
+
? (event) => {
|
|
157
|
+
if (event.target !== event.currentTarget)
|
|
158
|
+
return;
|
|
159
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
160
|
+
return;
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
onRowClick(row);
|
|
163
|
+
}
|
|
164
|
+
: undefined, style: motion && motion.delayMs !== undefined
|
|
165
|
+
? { animationDelay: `${motion.delayMs}ms` }
|
|
166
|
+
: undefined, tabIndex: onRowClick ? 0 : undefined, children: [isSelectable ? ((0, jsx_runtime_1.jsx)("td", { className: 'w-10 pl-4 pr-0', onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: (0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { isSelected: selectedIds.includes(rowId), label: `Select ${rowLabel}`, onChange: () => toggleRow(rowId) }) })) : null, visibleColumns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined ? column.align : 'left'], column.wrap ? '' : 'whitespace-nowrap', 'text-sm text-ink'), style: columnStyle(column), children: column.render(row) }, column.id)))] }), renderExpandedRow ? ((0, jsx_runtime_1.jsx)("tr", { "aria-hidden": !isExpanded, className: isExpanded ? 'border-t-0' : 'border-0', children: (0, jsx_runtime_1.jsx)("td", { className: 'p-0', colSpan: columnCount, children: (0, jsx_runtime_1.jsx)(ps_motion_components_1.PsCollapse, { isOpen: isExpanded, children: (0, jsx_runtime_1.jsx)("div", { className: 'w-0 min-w-full border-l-2 border-primary bg-mist/40 px-4 py-4', children: renderExpandedRow(row) }) }) }) })) : null] }, rowId));
|
|
167
|
+
};
|
|
168
|
+
if (isLoading)
|
|
169
|
+
return ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: table_config_1.PsTableConfig.skeletonRows }));
|
|
99
170
|
return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, className: ps_design_system_1.PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [isSelectable && selectedRows.length > 0 && getBulkActions ? ((0, jsx_runtime_1.jsx)(ps_table_bulk_bar_1.PsTableBulkBar, { actions: getBulkActions(selectedRows), onClear: () => setSelectedIds([]), selectedCount: selectedRows.length })) : hasToolbar || columns.some((column) => column.canHide) ? ((0, jsx_runtime_1.jsx)(ps_table_toolbar_1.PsTableToolbar, { actions: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [toolbarActions, columns.some((column) => column.canHide) ? ((0, jsx_runtime_1.jsx)(ps_table_column_picker_1.PsTableColumnPicker, { columns: columns
|
|
100
171
|
.filter((column) => column.canHide)
|
|
101
172
|
.map((column) => ({ id: column.id, label: column.label })), hiddenColumnIds: hiddenColumnIds, onChange: (nextHidden) => updateQuery({ hiddenColumnIds: nextHidden }) })) : null] }), columns: hasFilters ? columns : [], filters: filters, isSearchable: isSearchable, onClearFilters: clearFilters, onFilterChange: (columnId, value) => {
|
|
@@ -104,7 +175,14 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
104
175
|
}, onSearchChange: (next) => {
|
|
105
176
|
updateQuery({ search: next });
|
|
106
177
|
resetPage();
|
|
107
|
-
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, (0, jsx_runtime_1.jsxs)("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [mobileLayout === 'cards' ? ((0, jsx_runtime_1.jsx)("div", { className: 'sm:hidden', children:
|
|
178
|
+
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, (0, jsx_runtime_1.jsxs)("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [mobileLayout === 'cards' ? ((0, jsx_runtime_1.jsx)("div", { className: 'sm:hidden', children: grouping
|
|
179
|
+
? groups.map((group, groupIndex) => {
|
|
180
|
+
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
181
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: 'border-b border-line last:border-b-0', children: [(0, jsx_runtime_1.jsx)(ps_table_group_header_1.PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
182
|
+
? grouping.renderGroupSummary(group.rows)
|
|
183
|
+
: undefined }), (0, jsx_runtime_1.jsx)(ps_motion_components_1.PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
|
|
184
|
+
})
|
|
185
|
+
: renderCardList(visibleRows, visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsxs)("div", { className: 'px-4 py-10 text-center', children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: table_config_1.PsTableConfig.noMatchesTitle }), (0, jsx_runtime_1.jsx)("button", { className: 'ps-focus-ring mt-2 rounded-full px-3 py-1.5 text-sm font-semibold text-primary', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null) })) : null, (0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-scrollbar overflow-x-auto', mobileLayout === 'cards' ? 'hidden sm:block' : ''), children: (0, jsx_runtime_1.jsxs)("table", { className: 'w-full border-collapse text-left', children: [caption ? (0, jsx_runtime_1.jsx)("caption", { className: 'sr-only', children: caption }) : null, (0, jsx_runtime_1.jsx)("thead", { className: 'bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', children: (0, jsx_runtime_1.jsxs)("tr", { children: [isSelectable ? ((0, jsx_runtime_1.jsx)("th", { className: 'w-10 pl-4 pr-0', scope: 'col', children: (0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { isIndeterminate: !isPageSelected && pageIds.some((rowId) => selectedIds.includes(rowId)), isSelected: isPageSelected, label: 'Select all rows on this page', onChange: togglePage }) })) : null, visibleColumns.map((column) => {
|
|
108
186
|
const align = column.align !== null && column.align !== undefined ? column.align : 'left';
|
|
109
187
|
const isSorted = sort !== null && sort.columnId === column.id;
|
|
110
188
|
return ((0, jsx_runtime_1.jsx)("th", { "aria-sort": isSorted
|
|
@@ -112,26 +190,17 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
112
190
|
: column.sortValue && !isServerPaginated
|
|
113
191
|
? 'none'
|
|
114
192
|
: undefined, className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].head, alignClassNames[align], 'whitespace-nowrap'), scope: 'col', style: columnStyle(column), children: column.sortValue && !isServerPaginated ? ((0, jsx_runtime_1.jsx)(ps_table_sort_header_1.PsTableSortHeader, { align: align, columnId: column.id, label: column.label, onSort: updateSort, sort: sort })) : (column.label) }, column.id));
|
|
115
|
-
})] }) }), (0, jsx_runtime_1.jsxs)("tbody", { className: 'divide-y divide-line', children: [visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsxs)("td", { className: 'px-4 py-12 text-center', colSpan: columnCount, children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: table_config_1.PsTableConfig.noMatchesTitle }), (0, jsx_runtime_1.jsx)("p", { className: 'mt-1 text-sm text-ink/55', children: table_config_1.PsTableConfig.noMatchesDescription }), (0, jsx_runtime_1.jsx)("button", { className: 'ps-focus-ring mt-3 rounded-full px-3 py-1.5 text-sm font-semibold text-primary hover:underline', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (event.key !== 'Enter' && event.key !== ' ')
|
|
126
|
-
return;
|
|
127
|
-
event.preventDefault();
|
|
128
|
-
onRowClick(row);
|
|
129
|
-
}
|
|
130
|
-
: undefined, tabIndex: onRowClick ? 0 : undefined, children: [isSelectable ? ((0, jsx_runtime_1.jsx)("td", { className: 'w-10 pl-4 pr-0', onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: (0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { isSelected: selectedIds.includes(rowId), label: `Select ${rowLabel}`, onChange: () => toggleRow(rowId) }) })) : null, visibleColumns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
|
|
131
|
-
? column.align
|
|
132
|
-
: 'left'], column.wrap ? '' : 'whitespace-nowrap', 'text-sm text-ink'), style: columnStyle(column), children: column.render(row) }, column.id)))] }), renderExpandedRow && isExpanded ? ((0, jsx_runtime_1.jsx)("tr", { className: 'border-t-0', children: (0, jsx_runtime_1.jsx)("td", { className: 'p-0', colSpan: columnCount, children: (0, jsx_runtime_1.jsx)(ps_motion_components_1.PsCollapse, { isOpen: true, children: (0, jsx_runtime_1.jsx)("div", { className: 'w-0 min-w-full border-l-2 border-primary bg-mist/40 px-4 py-4', children: renderExpandedRow(row) }) }) }) })) : null] }, rowId));
|
|
133
|
-
})] }), hasFooter ? ((0, jsx_runtime_1.jsx)("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: (0, jsx_runtime_1.jsxs)("tr", { children: [isSelectable ? (0, jsx_runtime_1.jsx)("td", {}) : null, visibleColumns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
|
|
193
|
+
})] }) }), (0, jsx_runtime_1.jsxs)("tbody", { className: 'divide-y divide-line', children: [visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsxs)("td", { className: 'px-4 py-12 text-center', colSpan: columnCount, children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: table_config_1.PsTableConfig.noMatchesTitle }), (0, jsx_runtime_1.jsx)("p", { className: 'mt-1 text-sm text-ink/55', children: table_config_1.PsTableConfig.noMatchesDescription }), (0, jsx_runtime_1.jsx)("button", { className: 'ps-focus-ring mt-3 rounded-full px-3 py-1.5 text-sm font-semibold text-primary hover:underline', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, grouping
|
|
194
|
+
? groups.map((group, groupIndex) => {
|
|
195
|
+
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
196
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsx)("td", { className: 'p-0', colSpan: columnCount, children: (0, jsx_runtime_1.jsx)(ps_table_group_header_1.PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
197
|
+
? grouping.renderGroupSummary(group.rows)
|
|
198
|
+
: undefined }) }) }), isExpanded
|
|
199
|
+
? group.rows.map((row, rowIndex) => renderRow(row, groupRowMotion(group.key, rowIndex)))
|
|
200
|
+
: null] }, `group-${group.key}`));
|
|
201
|
+
})
|
|
202
|
+
: visibleRows.map((row) => renderRow(row))] }), hasFooter ? ((0, jsx_runtime_1.jsx)("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: (0, jsx_runtime_1.jsxs)("tr", { children: [isSelectable ? (0, jsx_runtime_1.jsx)("td", {}) : null, visibleColumns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
|
|
134
203
|
? column.align
|
|
135
|
-
: 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }) }), pageSize ? ((0, jsx_runtime_1.jsx)(ps_table_pagination_1.PsTablePagination, { onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
|
|
204
|
+
: 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }) }), pageSize && !grouping ? ((0, jsx_runtime_1.jsx)(ps_table_pagination_1.PsTablePagination, { onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
|
|
136
205
|
};
|
|
137
206
|
exports.PsDataTable = PsDataTable;
|
|
@@ -3,5 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PsTableBulkBar = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
6
|
-
const
|
|
6
|
+
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
7
|
+
const PsTableBulkBar = ({ actions, onClear, selectedCount }) => {
|
|
8
|
+
const safeActions = actions.filter((action) => !action.isDanger);
|
|
9
|
+
const dangerActions = actions.filter((action) => action.isDanger);
|
|
10
|
+
const renderAction = (action) => ((0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring inline-flex min-h-9 shrink-0 items-center whitespace-nowrap rounded-full px-3 text-sm font-semibold transition-colors disabled:opacity-40', action.isDanger ? 'text-danger hover:bg-danger/10' : 'text-ink hover:bg-ink/5'), disabled: action.isDisabled, onClick: action.onSelect, type: 'button', children: action.label }, action.id));
|
|
11
|
+
return ((0, jsx_runtime_1.jsxs)("div", { "aria-live": 'polite', className: 'flex min-h-12 items-center gap-2 rounded-2xl border border-primary/30 bg-primary/10 py-1.5 pl-2 pr-1.5', children: [(0, jsx_runtime_1.jsxs)("span", { className: 'inline-flex min-h-9 shrink-0 items-center rounded-full bg-primary px-3 text-sm font-semibold tabular-nums text-white', children: [selectedCount, " selected"] }), (0, jsx_runtime_1.jsxs)("div", { className: 'ps-scrollbar-hidden flex min-w-0 flex-1 items-center gap-1 overflow-x-auto', children: [safeActions.map(renderAction), dangerActions.length > 0 && safeActions.length > 0 ? ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": 'true', className: 'mx-1 h-5 w-px shrink-0 bg-line' })) : null, dangerActions.map(renderAction)] }), (0, jsx_runtime_1.jsx)("button", { "aria-label": 'Clear selection', className: 'ps-focus-ring inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-ink/60 transition-colors hover:bg-ink/5 hover:text-ink', onClick: onClear, title: 'Clear selection', type: 'button', children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'h-4 w-4', iconKey: ps_icons_1.PsIconKey.Close }) })] }));
|
|
12
|
+
};
|
|
7
13
|
exports.PsTableBulkBar = PsTableBulkBar;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface PsTableGroupHeaderProps {
|
|
3
|
+
count: number;
|
|
4
|
+
isExpanded: boolean;
|
|
5
|
+
label: string;
|
|
6
|
+
onToggle: () => void;
|
|
7
|
+
summary?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export declare const PsTableGroupHeader: ({ count, isExpanded, label, onToggle, summary, }: PsTableGroupHeaderProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsTableGroupHeader = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
6
|
+
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
7
|
+
const PsTableGroupHeader = ({ count, isExpanded, label, onToggle, summary, }) => ((0, jsx_runtime_1.jsxs)("button", { "aria-expanded": isExpanded, className: 'ps-focus-ring flex w-full flex-wrap items-center gap-x-3 gap-y-2 bg-mist/60 px-4 py-3 text-left transition-colors hover:bg-mist', onClick: onToggle, type: 'button', children: [(0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: ps_design_system_1.PallasoftClassNameService.merge('h-4 w-4 shrink-0 text-ink/50 transition-transform duration-200', isExpanded ? '' : '-rotate-90'), iconKey: ps_icons_1.PsIconKey.Chevron }), (0, jsx_runtime_1.jsx)("span", { className: 'text-sm font-semibold text-ink', children: label }), (0, jsx_runtime_1.jsx)("span", { className: 'rounded-full bg-surface px-2 py-0.5 text-xs font-semibold tabular-nums text-ink/60', children: count }), summary ? ((0, jsx_runtime_1.jsx)("span", { className: 'ml-auto flex flex-wrap items-center justify-end gap-2', children: summary })) : null] }));
|
|
8
|
+
exports.PsTableGroupHeader = PsTableGroupHeader;
|
|
@@ -13,6 +13,6 @@ const PsTableToolbar = ({ actions, columns, filters, isSearchable, onClearFilter
|
|
|
13
13
|
const filterableColumns = columns.filter((column) => column.filter);
|
|
14
14
|
const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Dropdown);
|
|
15
15
|
const activeCount = filterableColumns.filter((column) => ps_table_query_service_1.PsTableQueryService.isActive(filters[column.id])).length;
|
|
16
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-wrap items-center gap-2', children: [isSearchable ? ((0, jsx_runtime_1.jsxs)("label", { className: 'relative basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto sm:max-w-xs', children: [(0, jsx_runtime_1.jsx)("span", { className: 'sr-only', children: searchPlaceholder }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/45', iconKey: ps_icons_1.PsIconKey.Search }), (0, jsx_runtime_1.jsx)("input", { className: 'ps-input-control ps-focus-ring min-h-10 rounded-full pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, filterableColumns.length > 0 ? ((0, jsx_runtime_1.jsxs)(react_aria_components_1.DialogTrigger, { children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.Button, { className: toolbarButtonClassName, children: [(0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: ps_icons_1.PsIconKey.Filter }), (0, jsx_runtime_1.jsx)("span", { className: 'sr-only sm:not-sr-only', children: "Filters" }), activeCount > 0 ? ((0, jsx_runtime_1.jsx)("span", { className: 'inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-semibold text-[color:var(--ps-color-primary-contrast)]', children: activeCount })) : null] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-[min(34rem,80vh)] w-[min(22rem,calc(100vw-2rem))] overflow-y-auto outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: (0, jsx_runtime_1.jsx)(react_aria_components_1.Dialog, { "aria-label": 'Filters', className: 'flex flex-col gap-4 p-4 outline-none', children: (0, jsx_runtime_1.jsxs)(ps_core_components_1.PsLayer, { zIndex: zIndex, children: [filterableColumns.map((column) => ((0, jsx_runtime_1.jsx)(ps_table_filter_field_1.PsTableFilterField, { kind: column.filter ? column.filter.kind : 'text', label: column.label, onChange: (value) => onFilterChange(column.id, value), options: ps_table_query_service_1.PsTableQueryService.options(rows, column), value: filters[column.id] ? filters[column.id] : {} }, column.id))), (0, jsx_runtime_1.jsx)("div", { className: 'flex justify-end border-t border-line pt-3', children: (0, jsx_runtime_1.jsx)("button", { className: 'ps-focus-ring rounded-lg px-2 py-1 text-sm font-semibold text-ink/65 hover:text-ink disabled:opacity-40', disabled: activeCount === 0, onClick: onClearFilters, type: 'button', children: "Clear all" }) })] }) }) })] })) : null, activeCount > 0 ? ((0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring rounded-full px-3 py-2 text-sm font-semibold text-ink/65 hover:text-ink'), onClick: onClearFilters, type: 'button', children: "Clear filters" })) : null, actions ? (
|
|
16
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-wrap items-center gap-2', children: [isSearchable ? ((0, jsx_runtime_1.jsxs)("label", { className: 'relative basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto sm:max-w-xs', children: [(0, jsx_runtime_1.jsx)("span", { className: 'sr-only', children: searchPlaceholder }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/45', iconKey: ps_icons_1.PsIconKey.Search }), (0, jsx_runtime_1.jsx)("input", { className: 'ps-input-control ps-focus-ring min-h-10 rounded-full pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: 'ps-scrollbar-hidden flex w-full min-w-0 items-center gap-2 overflow-x-auto sm:w-auto sm:flex-1 sm:overflow-visible', children: [filterableColumns.length > 0 ? ((0, jsx_runtime_1.jsxs)(react_aria_components_1.DialogTrigger, { children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.Button, { className: toolbarButtonClassName, children: [(0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: ps_icons_1.PsIconKey.Filter }), (0, jsx_runtime_1.jsx)("span", { className: 'sr-only sm:not-sr-only', children: "Filters" }), activeCount > 0 ? ((0, jsx_runtime_1.jsx)("span", { className: 'inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-semibold text-[color:var(--ps-color-primary-contrast)]', children: activeCount })) : null] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-[min(34rem,80vh)] w-[min(22rem,calc(100vw-2rem))] overflow-y-auto outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: (0, jsx_runtime_1.jsx)(react_aria_components_1.Dialog, { "aria-label": 'Filters', className: 'flex flex-col gap-4 p-4 outline-none', children: (0, jsx_runtime_1.jsxs)(ps_core_components_1.PsLayer, { zIndex: zIndex, children: [filterableColumns.map((column) => ((0, jsx_runtime_1.jsx)(ps_table_filter_field_1.PsTableFilterField, { kind: column.filter ? column.filter.kind : 'text', label: column.label, onChange: (value) => onFilterChange(column.id, value), options: ps_table_query_service_1.PsTableQueryService.options(rows, column), value: filters[column.id] ? filters[column.id] : {} }, column.id))), (0, jsx_runtime_1.jsx)("div", { className: 'flex justify-end border-t border-line pt-3', children: (0, jsx_runtime_1.jsx)("button", { className: 'ps-focus-ring rounded-lg px-2 py-1 text-sm font-semibold text-ink/65 hover:text-ink disabled:opacity-40', disabled: activeCount === 0, onClick: onClearFilters, type: 'button', children: "Clear all" }) })] }) }) })] })) : null, activeCount > 0 ? ((0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring rounded-full px-3 py-2 text-sm font-semibold text-ink/65 hover:text-ink'), onClick: onClearFilters, type: 'button', children: "Clear filters" })) : null, actions ? (0, jsx_runtime_1.jsx)("div", { className: 'ml-auto flex shrink-0 items-center gap-2', children: actions }) : null] })] }));
|
|
17
17
|
};
|
|
18
18
|
exports.PsTableToolbar = PsTableToolbar;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export declare const PsTableConfig: {
|
|
2
|
+
readonly groupCloseMs: 160;
|
|
3
|
+
readonly groupRowStaggerMs: 16;
|
|
4
|
+
readonly groupStaggerLimit: 12;
|
|
2
5
|
readonly noMatchesDescription: "Try a different search or clear the filters.";
|
|
3
6
|
readonly noMatchesTitle: "Nothing matches";
|
|
4
7
|
readonly searchPlaceholder: "Search";
|
|
8
|
+
readonly skeletonRows: 6;
|
|
5
9
|
};
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PsTableConfig = void 0;
|
|
4
4
|
exports.PsTableConfig = {
|
|
5
|
+
groupCloseMs: 160,
|
|
6
|
+
groupRowStaggerMs: 16,
|
|
7
|
+
groupStaggerLimit: 12,
|
|
5
8
|
noMatchesDescription: 'Try a different search or clear the filters.',
|
|
6
9
|
noMatchesTitle: 'Nothing matches',
|
|
7
10
|
searchPlaceholder: 'Search',
|
|
11
|
+
skeletonRows: 6,
|
|
8
12
|
};
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { PsSkeletonTable } from '@pallasoft/ps-core-components';
|
|
3
4
|
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
4
5
|
import { PsCheckbox } from '@pallasoft/ps-input-components';
|
|
5
6
|
import { PsCollapse } from '@pallasoft/ps-motion-components';
|
|
6
7
|
import { PsTableConfig } from '../constants/table-config';
|
|
8
|
+
import { PsTableGroupService } from '../services/ps-table-group-service';
|
|
7
9
|
import { PsTableQueryService } from '../services/ps-table-query-service';
|
|
8
10
|
import { PsTableBulkBar } from './ps-table-bulk-bar';
|
|
9
11
|
import { PsTableCardList } from './ps-table-card-list';
|
|
10
12
|
import { PsTableColumnPicker } from './ps-table-column-picker';
|
|
13
|
+
import { PsTableGroupHeader } from './ps-table-group-header';
|
|
11
14
|
import { PsTablePagination } from './ps-table-pagination';
|
|
12
15
|
import { PsTableSortHeader } from './ps-table-sort-header';
|
|
13
16
|
import { PsTableToolbar } from './ps-table-toolbar';
|
|
@@ -27,11 +30,14 @@ const alignClassNames = {
|
|
|
27
30
|
left: 'text-left',
|
|
28
31
|
right: 'text-right tabular-nums',
|
|
29
32
|
};
|
|
30
|
-
export const PsDataTable = ({ caption, className, columns, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder = PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
|
|
33
|
+
export const PsDataTable = ({ caption, className, columns, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder = PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
|
|
31
34
|
const containerRef = useRef(null);
|
|
32
35
|
const [uncontrolledPage, setUncontrolledPage] = useState(1);
|
|
33
36
|
const [uncontrolledQuery, setUncontrolledQuery] = useState(() => PsTableQueryService.empty(defaultSort ? defaultSort : null));
|
|
34
37
|
const [selectedIds, setSelectedIds] = useState([]);
|
|
38
|
+
const [toggledGroupKeys, setToggledGroupKeys] = useState([]);
|
|
39
|
+
const [closingGroupKeys, setClosingGroupKeys] = useState([]);
|
|
40
|
+
const [openedGroupKeys, setOpenedGroupKeys] = useState([]);
|
|
35
41
|
const currentQuery = query ? query : uncontrolledQuery;
|
|
36
42
|
const { filters, hiddenColumnIds, search, sort } = currentQuery;
|
|
37
43
|
const visibleColumns = columns.filter((column) => !hiddenColumnIds.includes(column.id));
|
|
@@ -57,11 +63,56 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
57
63
|
const safePage = Math.min(Math.max(currentPage, 1), pageCount);
|
|
58
64
|
const isQueried = search.trim() !== '' ||
|
|
59
65
|
Object.values(filters).some((value) => PsTableQueryService.isActive(value));
|
|
66
|
+
const groups = useMemo(() => (grouping ? PsTableGroupService.group(queriedRows, grouping.getGroupKey) : []), [grouping, queriedRows]);
|
|
67
|
+
const initialExpandedCount = grouping && grouping.initialExpandedCount !== undefined ? grouping.initialExpandedCount : 1;
|
|
68
|
+
const isGroupExpanded = (groupKey, groupIndex) => isQueried || groupIndex < initialExpandedCount !== toggledGroupKeys.includes(groupKey);
|
|
69
|
+
const flipGroup = (groupKey) => setToggledGroupKeys((current) => current.includes(groupKey)
|
|
70
|
+
? current.filter((key) => key !== groupKey)
|
|
71
|
+
: [...current, groupKey]);
|
|
72
|
+
const toggleGroup = (groupKey, isExpanded) => {
|
|
73
|
+
if (closingGroupKeys.includes(groupKey))
|
|
74
|
+
return;
|
|
75
|
+
if (!isExpanded) {
|
|
76
|
+
setOpenedGroupKeys((current) => [...current.filter((key) => key !== groupKey), groupKey]);
|
|
77
|
+
flipGroup(groupKey);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
setClosingGroupKeys((current) => [...current, groupKey]);
|
|
81
|
+
window.setTimeout(() => {
|
|
82
|
+
flipGroup(groupKey);
|
|
83
|
+
setOpenedGroupKeys((current) => current.filter((key) => key !== groupKey));
|
|
84
|
+
setClosingGroupKeys((current) => current.filter((key) => key !== groupKey));
|
|
85
|
+
}, PsTableConfig.groupCloseMs);
|
|
86
|
+
};
|
|
87
|
+
const groupRowMotion = (groupKey, rowIndex) => {
|
|
88
|
+
if (closingGroupKeys.includes(groupKey))
|
|
89
|
+
return { className: 'ps-table-row-exit' };
|
|
90
|
+
if (!openedGroupKeys.includes(groupKey))
|
|
91
|
+
return undefined;
|
|
92
|
+
return {
|
|
93
|
+
className: 'ps-table-row-enter',
|
|
94
|
+
delayMs: Math.min(rowIndex, PsTableConfig.groupStaggerLimit) * PsTableConfig.groupRowStaggerMs,
|
|
95
|
+
};
|
|
96
|
+
};
|
|
60
97
|
const visibleRows = useMemo(() => {
|
|
98
|
+
if (grouping)
|
|
99
|
+
return groups.flatMap((group, groupIndex) => isQueried || groupIndex < initialExpandedCount !== toggledGroupKeys.includes(group.key)
|
|
100
|
+
? group.rows
|
|
101
|
+
: []);
|
|
61
102
|
if (!pageSize || isServerPaginated)
|
|
62
103
|
return queriedRows;
|
|
63
104
|
return queriedRows.slice((safePage - 1) * pageSize, safePage * pageSize);
|
|
64
|
-
}, [
|
|
105
|
+
}, [
|
|
106
|
+
grouping,
|
|
107
|
+
groups,
|
|
108
|
+
initialExpandedCount,
|
|
109
|
+
isQueried,
|
|
110
|
+
isServerPaginated,
|
|
111
|
+
pageSize,
|
|
112
|
+
queriedRows,
|
|
113
|
+
safePage,
|
|
114
|
+
toggledGroupKeys,
|
|
115
|
+
]);
|
|
65
116
|
const changePage = (next) => {
|
|
66
117
|
if (onPageChange) {
|
|
67
118
|
onPageChange(next);
|
|
@@ -93,6 +144,26 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
93
144
|
? current.filter((rowId) => !pageIds.includes(rowId))
|
|
94
145
|
: [...new Set([...current, ...pageIds])]);
|
|
95
146
|
const toggleRow = (rowId) => setSelectedIds((current) => current.includes(rowId) ? current.filter((id) => id !== rowId) : [...current, rowId]);
|
|
147
|
+
const renderCardList = (cardRows, emptyContent) => (_jsx(PsTableCardList, { columns: visibleColumns, emptyContent: emptyContent, expandedRowIds: expandedRowIds, footerColumns: hasFooter ? visibleColumns.filter((column) => column.footer !== undefined) : [], getRowId: getRowId, getRowLabel: (row) => (getRowLabel ? getRowLabel(row) : getRowId(row)), isSelectable: isSelectable, onRowClick: onRowClick, onRowHover: onRowHover, onToggleRow: toggleRow, renderExpandedRow: renderExpandedRow, rows: cardRows, selectedIds: selectedIds }));
|
|
148
|
+
const renderRow = (row, motion) => {
|
|
149
|
+
const rowId = getRowId(row);
|
|
150
|
+
const isExpanded = expandedRowIds ? expandedRowIds.includes(rowId) : false;
|
|
151
|
+
const rowLabel = getRowLabel ? getRowLabel(row) : rowId;
|
|
152
|
+
return (_jsxs(Fragment, { children: [_jsxs("tr", { "aria-expanded": renderExpandedRow ? isExpanded : undefined, "aria-label": onRowClick ? rowLabel : undefined, className: PallasoftClassNameService.merge('align-middle transition-colors', motion ? motion.className : '', onRowClick ? 'ps-focus-ring cursor-pointer hover:bg-mist/60' : 'hover:bg-mist/40', isExpanded ? 'bg-mist/40' : ''), onClick: onRowClick ? () => onRowClick(row) : undefined, onFocus: onRowHover ? () => onRowHover(row) : undefined, onPointerEnter: onRowHover ? () => onRowHover(row) : undefined, onKeyDown: onRowClick
|
|
153
|
+
? (event) => {
|
|
154
|
+
if (event.target !== event.currentTarget)
|
|
155
|
+
return;
|
|
156
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
157
|
+
return;
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
onRowClick(row);
|
|
160
|
+
}
|
|
161
|
+
: undefined, style: motion && motion.delayMs !== undefined
|
|
162
|
+
? { animationDelay: `${motion.delayMs}ms` }
|
|
163
|
+
: undefined, tabIndex: onRowClick ? 0 : undefined, children: [isSelectable ? (_jsx("td", { className: 'w-10 pl-4 pr-0', onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: _jsx(PsCheckbox, { isSelected: selectedIds.includes(rowId), label: `Select ${rowLabel}`, onChange: () => toggleRow(rowId) }) })) : null, visibleColumns.map((column) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined ? column.align : 'left'], column.wrap ? '' : 'whitespace-nowrap', 'text-sm text-ink'), style: columnStyle(column), children: column.render(row) }, column.id)))] }), renderExpandedRow ? (_jsx("tr", { "aria-hidden": !isExpanded, className: isExpanded ? 'border-t-0' : 'border-0', children: _jsx("td", { className: 'p-0', colSpan: columnCount, children: _jsx(PsCollapse, { isOpen: isExpanded, children: _jsx("div", { className: 'w-0 min-w-full border-l-2 border-primary bg-mist/40 px-4 py-4', children: renderExpandedRow(row) }) }) }) })) : null] }, rowId));
|
|
164
|
+
};
|
|
165
|
+
if (isLoading)
|
|
166
|
+
return (_jsx(PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: PsTableConfig.skeletonRows }));
|
|
96
167
|
return (_jsxs("div", { ref: containerRef, className: PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [isSelectable && selectedRows.length > 0 && getBulkActions ? (_jsx(PsTableBulkBar, { actions: getBulkActions(selectedRows), onClear: () => setSelectedIds([]), selectedCount: selectedRows.length })) : hasToolbar || columns.some((column) => column.canHide) ? (_jsx(PsTableToolbar, { actions: _jsxs(_Fragment, { children: [toolbarActions, columns.some((column) => column.canHide) ? (_jsx(PsTableColumnPicker, { columns: columns
|
|
97
168
|
.filter((column) => column.canHide)
|
|
98
169
|
.map((column) => ({ id: column.id, label: column.label })), hiddenColumnIds: hiddenColumnIds, onChange: (nextHidden) => updateQuery({ hiddenColumnIds: nextHidden }) })) : null] }), columns: hasFilters ? columns : [], filters: filters, isSearchable: isSearchable, onClearFilters: clearFilters, onFilterChange: (columnId, value) => {
|
|
@@ -101,7 +172,14 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
101
172
|
}, onSearchChange: (next) => {
|
|
102
173
|
updateQuery({ search: next });
|
|
103
174
|
resetPage();
|
|
104
|
-
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, _jsxs("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [mobileLayout === 'cards' ? (_jsx("div", { className: 'sm:hidden', children:
|
|
175
|
+
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, _jsxs("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [mobileLayout === 'cards' ? (_jsx("div", { className: 'sm:hidden', children: grouping
|
|
176
|
+
? groups.map((group, groupIndex) => {
|
|
177
|
+
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
178
|
+
return (_jsxs("div", { className: 'border-b border-line last:border-b-0', children: [_jsx(PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
179
|
+
? grouping.renderGroupSummary(group.rows)
|
|
180
|
+
: undefined }), _jsx(PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
|
|
181
|
+
})
|
|
182
|
+
: renderCardList(visibleRows, visibleRows.length === 0 && isQueried ? (_jsxs("div", { className: 'px-4 py-10 text-center', children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsTableConfig.noMatchesTitle }), _jsx("button", { className: 'ps-focus-ring mt-2 rounded-full px-3 py-1.5 text-sm font-semibold text-primary', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null) })) : null, _jsx("div", { className: PallasoftClassNameService.merge('ps-scrollbar overflow-x-auto', mobileLayout === 'cards' ? 'hidden sm:block' : ''), children: _jsxs("table", { className: 'w-full border-collapse text-left', children: [caption ? _jsx("caption", { className: 'sr-only', children: caption }) : null, _jsx("thead", { className: 'bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', children: _jsxs("tr", { children: [isSelectable ? (_jsx("th", { className: 'w-10 pl-4 pr-0', scope: 'col', children: _jsx(PsCheckbox, { isIndeterminate: !isPageSelected && pageIds.some((rowId) => selectedIds.includes(rowId)), isSelected: isPageSelected, label: 'Select all rows on this page', onChange: togglePage }) })) : null, visibleColumns.map((column) => {
|
|
105
183
|
const align = column.align !== null && column.align !== undefined ? column.align : 'left';
|
|
106
184
|
const isSorted = sort !== null && sort.columnId === column.id;
|
|
107
185
|
return (_jsx("th", { "aria-sort": isSorted
|
|
@@ -109,25 +187,16 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
109
187
|
: column.sortValue && !isServerPaginated
|
|
110
188
|
? 'none'
|
|
111
189
|
: undefined, className: PallasoftClassNameService.merge(densityClassNames[density].head, alignClassNames[align], 'whitespace-nowrap'), scope: 'col', style: columnStyle(column), children: column.sortValue && !isServerPaginated ? (_jsx(PsTableSortHeader, { align: align, columnId: column.id, label: column.label, onSort: updateSort, sort: sort })) : (column.label) }, column.id));
|
|
112
|
-
})] }) }), _jsxs("tbody", { className: 'divide-y divide-line', children: [visibleRows.length === 0 && isQueried ? (_jsx("tr", { children: _jsxs("td", { className: 'px-4 py-12 text-center', colSpan: columnCount, children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsTableConfig.noMatchesTitle }), _jsx("p", { className: 'mt-1 text-sm text-ink/55', children: PsTableConfig.noMatchesDescription }), _jsx("button", { className: 'ps-focus-ring mt-3 rounded-full px-3 py-1.5 text-sm font-semibold text-primary hover:underline', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (event.key !== 'Enter' && event.key !== ' ')
|
|
123
|
-
return;
|
|
124
|
-
event.preventDefault();
|
|
125
|
-
onRowClick(row);
|
|
126
|
-
}
|
|
127
|
-
: undefined, tabIndex: onRowClick ? 0 : undefined, children: [isSelectable ? (_jsx("td", { className: 'w-10 pl-4 pr-0', onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: _jsx(PsCheckbox, { isSelected: selectedIds.includes(rowId), label: `Select ${rowLabel}`, onChange: () => toggleRow(rowId) }) })) : null, visibleColumns.map((column) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
|
|
128
|
-
? column.align
|
|
129
|
-
: 'left'], column.wrap ? '' : 'whitespace-nowrap', 'text-sm text-ink'), style: columnStyle(column), children: column.render(row) }, column.id)))] }), renderExpandedRow && isExpanded ? (_jsx("tr", { className: 'border-t-0', children: _jsx("td", { className: 'p-0', colSpan: columnCount, children: _jsx(PsCollapse, { isOpen: true, children: _jsx("div", { className: 'w-0 min-w-full border-l-2 border-primary bg-mist/40 px-4 py-4', children: renderExpandedRow(row) }) }) }) })) : null] }, rowId));
|
|
130
|
-
})] }), hasFooter ? (_jsx("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: _jsxs("tr", { children: [isSelectable ? _jsx("td", {}) : null, visibleColumns.map((column) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
|
|
190
|
+
})] }) }), _jsxs("tbody", { className: 'divide-y divide-line', children: [visibleRows.length === 0 && isQueried ? (_jsx("tr", { children: _jsxs("td", { className: 'px-4 py-12 text-center', colSpan: columnCount, children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsTableConfig.noMatchesTitle }), _jsx("p", { className: 'mt-1 text-sm text-ink/55', children: PsTableConfig.noMatchesDescription }), _jsx("button", { className: 'ps-focus-ring mt-3 rounded-full px-3 py-1.5 text-sm font-semibold text-primary hover:underline', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, grouping
|
|
191
|
+
? groups.map((group, groupIndex) => {
|
|
192
|
+
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
193
|
+
return (_jsxs(Fragment, { children: [_jsx("tr", { children: _jsx("td", { className: 'p-0', colSpan: columnCount, children: _jsx(PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
194
|
+
? grouping.renderGroupSummary(group.rows)
|
|
195
|
+
: undefined }) }) }), isExpanded
|
|
196
|
+
? group.rows.map((row, rowIndex) => renderRow(row, groupRowMotion(group.key, rowIndex)))
|
|
197
|
+
: null] }, `group-${group.key}`));
|
|
198
|
+
})
|
|
199
|
+
: visibleRows.map((row) => renderRow(row))] }), hasFooter ? (_jsx("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: _jsxs("tr", { children: [isSelectable ? _jsx("td", {}) : null, visibleColumns.map((column) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
|
|
131
200
|
? column.align
|
|
132
|
-
: 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }) }), pageSize ? (_jsx(PsTablePagination, { onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
|
|
201
|
+
: 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }) }), pageSize && !grouping ? (_jsx(PsTablePagination, { onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
|
|
133
202
|
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
-
|
|
3
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
+
export const PsTableBulkBar = ({ actions, onClear, selectedCount }) => {
|
|
5
|
+
const safeActions = actions.filter((action) => !action.isDanger);
|
|
6
|
+
const dangerActions = actions.filter((action) => action.isDanger);
|
|
7
|
+
const renderAction = (action) => (_jsx("button", { className: PallasoftClassNameService.merge('ps-focus-ring inline-flex min-h-9 shrink-0 items-center whitespace-nowrap rounded-full px-3 text-sm font-semibold transition-colors disabled:opacity-40', action.isDanger ? 'text-danger hover:bg-danger/10' : 'text-ink hover:bg-ink/5'), disabled: action.isDisabled, onClick: action.onSelect, type: 'button', children: action.label }, action.id));
|
|
8
|
+
return (_jsxs("div", { "aria-live": 'polite', className: 'flex min-h-12 items-center gap-2 rounded-2xl border border-primary/30 bg-primary/10 py-1.5 pl-2 pr-1.5', children: [_jsxs("span", { className: 'inline-flex min-h-9 shrink-0 items-center rounded-full bg-primary px-3 text-sm font-semibold tabular-nums text-white', children: [selectedCount, " selected"] }), _jsxs("div", { className: 'ps-scrollbar-hidden flex min-w-0 flex-1 items-center gap-1 overflow-x-auto', children: [safeActions.map(renderAction), dangerActions.length > 0 && safeActions.length > 0 ? (_jsx("span", { "aria-hidden": 'true', className: 'mx-1 h-5 w-px shrink-0 bg-line' })) : null, dangerActions.map(renderAction)] }), _jsx("button", { "aria-label": 'Clear selection', className: 'ps-focus-ring inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-ink/60 transition-colors hover:bg-ink/5 hover:text-ink', onClick: onClear, title: 'Clear selection', type: 'button', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'h-4 w-4', iconKey: PsIconKey.Close }) })] }));
|
|
9
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
+
export const PsTableGroupHeader = ({ count, isExpanded, label, onToggle, summary, }) => (_jsxs("button", { "aria-expanded": isExpanded, className: 'ps-focus-ring flex w-full flex-wrap items-center gap-x-3 gap-y-2 bg-mist/60 px-4 py-3 text-left transition-colors hover:bg-mist', onClick: onToggle, type: 'button', children: [_jsx(PsIcon, { "aria-hidden": 'true', className: PallasoftClassNameService.merge('h-4 w-4 shrink-0 text-ink/50 transition-transform duration-200', isExpanded ? '' : '-rotate-90'), iconKey: PsIconKey.Chevron }), _jsx("span", { className: 'text-sm font-semibold text-ink', children: label }), _jsx("span", { className: 'rounded-full bg-surface px-2 py-0.5 text-xs font-semibold tabular-nums text-ink/60', children: count }), summary ? (_jsx("span", { className: 'ml-auto flex flex-wrap items-center justify-end gap-2', children: summary })) : null] }));
|
|
@@ -10,5 +10,5 @@ export const PsTableToolbar = ({ actions, columns, filters, isSearchable, onClea
|
|
|
10
10
|
const filterableColumns = columns.filter((column) => column.filter);
|
|
11
11
|
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
12
12
|
const activeCount = filterableColumns.filter((column) => PsTableQueryService.isActive(filters[column.id])).length;
|
|
13
|
-
return (_jsxs("div", { className: 'flex flex-wrap items-center gap-2', children: [isSearchable ? (_jsxs("label", { className: 'relative basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto sm:max-w-xs', children: [_jsx("span", { className: 'sr-only', children: searchPlaceholder }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/45', iconKey: PsIconKey.Search }), _jsx("input", { className: 'ps-input-control ps-focus-ring min-h-10 rounded-full pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, filterableColumns.length > 0 ? (_jsxs(DialogTrigger, { children: [_jsxs(Button, { className: toolbarButtonClassName, children: [_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Filter }), _jsx("span", { className: 'sr-only sm:not-sr-only', children: "Filters" }), activeCount > 0 ? (_jsx("span", { className: 'inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-semibold text-[color:var(--ps-color-primary-contrast)]', children: activeCount })) : null] }), _jsx(Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-[min(34rem,80vh)] w-[min(22rem,calc(100vw-2rem))] overflow-y-auto outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(Dialog, { "aria-label": 'Filters', className: 'flex flex-col gap-4 p-4 outline-none', children: _jsxs(PsLayer, { zIndex: zIndex, children: [filterableColumns.map((column) => (_jsx(PsTableFilterField, { kind: column.filter ? column.filter.kind : 'text', label: column.label, onChange: (value) => onFilterChange(column.id, value), options: PsTableQueryService.options(rows, column), value: filters[column.id] ? filters[column.id] : {} }, column.id))), _jsx("div", { className: 'flex justify-end border-t border-line pt-3', children: _jsx("button", { className: 'ps-focus-ring rounded-lg px-2 py-1 text-sm font-semibold text-ink/65 hover:text-ink disabled:opacity-40', disabled: activeCount === 0, onClick: onClearFilters, type: 'button', children: "Clear all" }) })] }) }) })] })) : null, activeCount > 0 ? (_jsx("button", { className: PallasoftClassNameService.merge('ps-focus-ring rounded-full px-3 py-2 text-sm font-semibold text-ink/65 hover:text-ink'), onClick: onClearFilters, type: 'button', children: "Clear filters" })) : null, actions ?
|
|
13
|
+
return (_jsxs("div", { className: 'flex flex-wrap items-center gap-2', children: [isSearchable ? (_jsxs("label", { className: 'relative basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto sm:max-w-xs', children: [_jsx("span", { className: 'sr-only', children: searchPlaceholder }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/45', iconKey: PsIconKey.Search }), _jsx("input", { className: 'ps-input-control ps-focus-ring min-h-10 rounded-full pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, _jsxs("div", { className: 'ps-scrollbar-hidden flex w-full min-w-0 items-center gap-2 overflow-x-auto sm:w-auto sm:flex-1 sm:overflow-visible', children: [filterableColumns.length > 0 ? (_jsxs(DialogTrigger, { children: [_jsxs(Button, { className: toolbarButtonClassName, children: [_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Filter }), _jsx("span", { className: 'sr-only sm:not-sr-only', children: "Filters" }), activeCount > 0 ? (_jsx("span", { className: 'inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-semibold text-[color:var(--ps-color-primary-contrast)]', children: activeCount })) : null] }), _jsx(Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-[min(34rem,80vh)] w-[min(22rem,calc(100vw-2rem))] overflow-y-auto outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(Dialog, { "aria-label": 'Filters', className: 'flex flex-col gap-4 p-4 outline-none', children: _jsxs(PsLayer, { zIndex: zIndex, children: [filterableColumns.map((column) => (_jsx(PsTableFilterField, { kind: column.filter ? column.filter.kind : 'text', label: column.label, onChange: (value) => onFilterChange(column.id, value), options: PsTableQueryService.options(rows, column), value: filters[column.id] ? filters[column.id] : {} }, column.id))), _jsx("div", { className: 'flex justify-end border-t border-line pt-3', children: _jsx("button", { className: 'ps-focus-ring rounded-lg px-2 py-1 text-sm font-semibold text-ink/65 hover:text-ink disabled:opacity-40', disabled: activeCount === 0, onClick: onClearFilters, type: 'button', children: "Clear all" }) })] }) }) })] })) : null, activeCount > 0 ? (_jsx("button", { className: PallasoftClassNameService.merge('ps-focus-ring rounded-full px-3 py-2 text-sm font-semibold text-ink/65 hover:text-ink'), onClick: onClearFilters, type: 'button', children: "Clear filters" })) : null, actions ? _jsx("div", { className: 'ml-auto flex shrink-0 items-center gap-2', children: actions }) : null] })] }));
|
|
14
14
|
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export const PsTableConfig = {
|
|
2
|
+
groupCloseMs: 160,
|
|
3
|
+
groupRowStaggerMs: 16,
|
|
4
|
+
groupStaggerLimit: 12,
|
|
2
5
|
noMatchesDescription: 'Try a different search or clear the filters.',
|
|
3
6
|
noMatchesTitle: 'Nothing matches',
|
|
4
7
|
searchPlaceholder: 'Search',
|
|
8
|
+
skeletonRows: 6,
|
|
5
9
|
};
|
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './components/ps-data-table';
|
|
2
|
+
export * from './components/ps-table-group-header';
|
|
2
3
|
export * from './components/ps-table-pagination';
|
|
3
4
|
export * from './constants/table-card-role';
|
|
4
5
|
export * from './constants/table-filter-kind';
|
|
@@ -6,8 +7,11 @@ export * from './interfaces/table-column';
|
|
|
6
7
|
export * from './interfaces/table-column-filter';
|
|
7
8
|
export * from './interfaces/table-filter-option';
|
|
8
9
|
export * from './interfaces/table-filter-value';
|
|
10
|
+
export * from './interfaces/table-grouping';
|
|
9
11
|
export * from './interfaces/table-query';
|
|
10
12
|
export * from './interfaces/table-row-action';
|
|
13
|
+
export * from './interfaces/table-row-group';
|
|
11
14
|
export * from './interfaces/table-sort';
|
|
15
|
+
export * from './services/ps-table-group-service';
|
|
12
16
|
export * from './services/ps-table-query-service';
|
|
13
17
|
export * from './services/ps-table-search-service';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class PsTableGroupService {
|
|
2
|
+
static group(rows, getGroupKey) {
|
|
3
|
+
const groups = new Map();
|
|
4
|
+
for (const row of rows) {
|
|
5
|
+
const key = getGroupKey(row);
|
|
6
|
+
const existing = groups.get(key);
|
|
7
|
+
if (existing)
|
|
8
|
+
existing.push(row);
|
|
9
|
+
else
|
|
10
|
+
groups.set(key, [row]);
|
|
11
|
+
}
|
|
12
|
+
return [...groups.entries()].map(([key, groupRows]) => ({ key, rows: groupRows }));
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './components/ps-data-table';
|
|
2
|
+
export * from './components/ps-table-group-header';
|
|
2
3
|
export * from './components/ps-table-pagination';
|
|
3
4
|
export * from './constants/table-card-role';
|
|
4
5
|
export * from './constants/table-filter-kind';
|
|
@@ -6,8 +7,11 @@ export * from './interfaces/table-column';
|
|
|
6
7
|
export * from './interfaces/table-column-filter';
|
|
7
8
|
export * from './interfaces/table-filter-option';
|
|
8
9
|
export * from './interfaces/table-filter-value';
|
|
10
|
+
export * from './interfaces/table-grouping';
|
|
9
11
|
export * from './interfaces/table-query';
|
|
10
12
|
export * from './interfaces/table-row-action';
|
|
13
|
+
export * from './interfaces/table-row-group';
|
|
11
14
|
export * from './interfaces/table-sort';
|
|
15
|
+
export * from './services/ps-table-group-service';
|
|
12
16
|
export * from './services/ps-table-query-service';
|
|
13
17
|
export * from './services/ps-table-search-service';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./components/ps-data-table"), exports);
|
|
18
|
+
__exportStar(require("./components/ps-table-group-header"), exports);
|
|
18
19
|
__exportStar(require("./components/ps-table-pagination"), exports);
|
|
19
20
|
__exportStar(require("./constants/table-card-role"), exports);
|
|
20
21
|
__exportStar(require("./constants/table-filter-kind"), exports);
|
|
@@ -22,8 +23,11 @@ __exportStar(require("./interfaces/table-column"), exports);
|
|
|
22
23
|
__exportStar(require("./interfaces/table-column-filter"), exports);
|
|
23
24
|
__exportStar(require("./interfaces/table-filter-option"), exports);
|
|
24
25
|
__exportStar(require("./interfaces/table-filter-value"), exports);
|
|
26
|
+
__exportStar(require("./interfaces/table-grouping"), exports);
|
|
25
27
|
__exportStar(require("./interfaces/table-query"), exports);
|
|
26
28
|
__exportStar(require("./interfaces/table-row-action"), exports);
|
|
29
|
+
__exportStar(require("./interfaces/table-row-group"), exports);
|
|
27
30
|
__exportStar(require("./interfaces/table-sort"), exports);
|
|
31
|
+
__exportStar(require("./services/ps-table-group-service"), exports);
|
|
28
32
|
__exportStar(require("./services/ps-table-query-service"), exports);
|
|
29
33
|
__exportStar(require("./services/ps-table-search-service"), exports);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface PsTableGrouping<RowData> {
|
|
3
|
+
getGroupKey: (row: RowData) => string;
|
|
4
|
+
getGroupLabel: (groupKey: string, rows: RowData[]) => string;
|
|
5
|
+
initialExpandedCount?: number;
|
|
6
|
+
renderGroupSummary?: (rows: RowData[]) => ReactNode;
|
|
7
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsTableGroupService = void 0;
|
|
4
|
+
class PsTableGroupService {
|
|
5
|
+
static group(rows, getGroupKey) {
|
|
6
|
+
const groups = new Map();
|
|
7
|
+
for (const row of rows) {
|
|
8
|
+
const key = getGroupKey(row);
|
|
9
|
+
const existing = groups.get(key);
|
|
10
|
+
if (existing)
|
|
11
|
+
existing.push(row);
|
|
12
|
+
else
|
|
13
|
+
groups.set(key, [row]);
|
|
14
|
+
}
|
|
15
|
+
return [...groups.entries()].map(([key, groupRows]) => ({ key, rows: groupRows }));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.PsTableGroupService = PsTableGroupService;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pallasoft/ps-table-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"url": "git+https://github.com/pallasoft/ps-table-components.git"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@pallasoft/ps-core-components": "^2.
|
|
74
|
+
"@pallasoft/ps-core-components": "^2.5.0",
|
|
75
75
|
"@pallasoft/ps-design-system": "^1.3.0",
|
|
76
76
|
"@pallasoft/ps-icons": "^1.5.0",
|
|
77
77
|
"@pallasoft/ps-input-components": "^1.9.0",
|