@pallasoft/ps-table-components 1.10.0 → 1.12.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.
@@ -17,6 +17,8 @@ export interface PsDataTableProps<RowData> {
17
17
  getRowLabel?: (row: RowData) => string;
18
18
  getSearchText?: (row: RowData) => string;
19
19
  grouping?: PsTableGrouping<RowData>;
20
+ isLoading?: boolean;
21
+ layout?: 'auto' | 'fixed';
20
22
  onPageChange?: (page: number) => void;
21
23
  onQueryChange?: (query: PsTableQuery) => void;
22
24
  onRowClick?: (row: RowData) => void;
@@ -30,4 +32,4 @@ export interface PsDataTableProps<RowData> {
30
32
  toolbarActions?: ReactNode;
31
33
  totalRows?: number;
32
34
  }
33
- export declare const PsDataTable: <RowData>({ caption, className, columns, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, grouping, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
35
+ export declare const PsDataTable: <RowData>({ caption, className, columns, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading, layout, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
@@ -3,6 +3,7 @@ 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");
@@ -32,7 +33,7 @@ const alignClassNames = {
32
33
  left: 'text-left',
33
34
  right: 'text-right tabular-nums',
34
35
  };
35
- const PsDataTable = ({ caption, className, columns, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, 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, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
36
37
  const containerRef = (0, react_1.useRef)(null);
37
38
  const [uncontrolledPage, setUncontrolledPage] = (0, react_1.useState)(1);
38
39
  const [uncontrolledQuery, setUncontrolledQuery] = (0, react_1.useState)(() => ps_table_query_service_1.PsTableQueryService.empty(defaultSort ? defaultSort : null));
@@ -77,6 +78,8 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
77
78
  if (!isExpanded) {
78
79
  setOpenedGroupKeys((current) => [...current.filter((key) => key !== groupKey), groupKey]);
79
80
  flipGroup(groupKey);
81
+ window.setTimeout(() => setOpenedGroupKeys((current) => current.filter((key) => key !== groupKey)), table_config_1.PsTableConfig.groupOpenMs +
82
+ table_config_1.PsTableConfig.groupStaggerLimit * table_config_1.PsTableConfig.groupRowStaggerMs);
80
83
  return;
81
84
  }
82
85
  setClosingGroupKeys((current) => [...current, groupKey]);
@@ -162,8 +165,10 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
162
165
  }
163
166
  : undefined, style: motion && motion.delayMs !== undefined
164
167
  ? { animationDelay: `${motion.delayMs}ms` }
165
- : 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));
168
+ : 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', layout === 'fixed' && !column.wrap ? 'truncate' : '', '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));
166
169
  };
170
+ if (isLoading)
171
+ return ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: table_config_1.PsTableConfig.skeletonRows }));
167
172
  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
168
173
  .filter((column) => column.canHide)
169
174
  .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) => {
@@ -179,7 +184,7 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
179
184
  ? grouping.renderGroupSummary(group.rows)
180
185
  : 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}`));
181
186
  })
182
- : 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) => {
187
+ : 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: ps_design_system_1.PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), 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) => {
183
188
  const align = column.align !== null && column.align !== undefined ? column.align : 'left';
184
189
  const isSorted = sort !== null && sort.columnId === column.id;
185
190
  return ((0, jsx_runtime_1.jsx)("th", { "aria-sort": isSorted
@@ -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 ? ((0, jsx_runtime_1.jsx)("div", { className: 'ml-auto flex min-w-0 flex-wrap items-center justify-end gap-2', children: actions })) : null] }));
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,8 +1,10 @@
1
1
  export declare const PsTableConfig: {
2
2
  readonly groupCloseMs: 160;
3
+ readonly groupOpenMs: 280;
3
4
  readonly groupRowStaggerMs: 16;
4
5
  readonly groupStaggerLimit: 12;
5
6
  readonly noMatchesDescription: "Try a different search or clear the filters.";
6
7
  readonly noMatchesTitle: "Nothing matches";
7
8
  readonly searchPlaceholder: "Search";
9
+ readonly skeletonRows: 6;
8
10
  };
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsTableConfig = void 0;
4
4
  exports.PsTableConfig = {
5
5
  groupCloseMs: 160,
6
+ groupOpenMs: 280,
6
7
  groupRowStaggerMs: 16,
7
8
  groupStaggerLimit: 12,
8
9
  noMatchesDescription: 'Try a different search or clear the filters.',
9
10
  noMatchesTitle: 'Nothing matches',
10
11
  searchPlaceholder: 'Search',
12
+ skeletonRows: 6,
11
13
  };
@@ -1,5 +1,6 @@
1
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';
@@ -29,7 +30,7 @@ const alignClassNames = {
29
30
  left: 'text-left',
30
31
  right: 'text-right tabular-nums',
31
32
  };
32
- export const PsDataTable = ({ caption, className, columns, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, 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, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, rows, searchPlaceholder = PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
33
34
  const containerRef = useRef(null);
34
35
  const [uncontrolledPage, setUncontrolledPage] = useState(1);
35
36
  const [uncontrolledQuery, setUncontrolledQuery] = useState(() => PsTableQueryService.empty(defaultSort ? defaultSort : null));
@@ -74,6 +75,8 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
74
75
  if (!isExpanded) {
75
76
  setOpenedGroupKeys((current) => [...current.filter((key) => key !== groupKey), groupKey]);
76
77
  flipGroup(groupKey);
78
+ window.setTimeout(() => setOpenedGroupKeys((current) => current.filter((key) => key !== groupKey)), PsTableConfig.groupOpenMs +
79
+ PsTableConfig.groupStaggerLimit * PsTableConfig.groupRowStaggerMs);
77
80
  return;
78
81
  }
79
82
  setClosingGroupKeys((current) => [...current, groupKey]);
@@ -159,8 +162,10 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
159
162
  }
160
163
  : undefined, style: motion && motion.delayMs !== undefined
161
164
  ? { animationDelay: `${motion.delayMs}ms` }
162
- : 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));
165
+ : 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', layout === 'fixed' && !column.wrap ? 'truncate' : '', '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));
163
166
  };
167
+ if (isLoading)
168
+ return (_jsx(PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: PsTableConfig.skeletonRows }));
164
169
  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
165
170
  .filter((column) => column.canHide)
166
171
  .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) => {
@@ -176,7 +181,7 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
176
181
  ? grouping.renderGroupSummary(group.rows)
177
182
  : undefined }), _jsx(PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
178
183
  })
179
- : 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) => {
184
+ : 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: PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), 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) => {
180
185
  const align = column.align !== null && column.align !== undefined ? column.align : 'left';
181
186
  const isSorted = sort !== null && sort.columnId === column.id;
182
187
  return (_jsx("th", { "aria-sort": isSorted
@@ -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 ? (_jsx("div", { className: 'ml-auto flex min-w-0 flex-wrap items-center justify-end gap-2', children: actions })) : null] }));
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,8 +1,10 @@
1
1
  export const PsTableConfig = {
2
2
  groupCloseMs: 160,
3
+ groupOpenMs: 280,
3
4
  groupRowStaggerMs: 16,
4
5
  groupStaggerLimit: 12,
5
6
  noMatchesDescription: 'Try a different search or clear the filters.',
6
7
  noMatchesTitle: 'Nothing matches',
7
8
  searchPlaceholder: 'Search',
9
+ skeletonRows: 6,
8
10
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-table-components",
3
- "version": "1.10.0",
3
+ "version": "1.12.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.3.1",
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",