flikkui 0.2.0-beta.4 → 0.2.0-beta.5
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/README.md +92 -0
- package/dist/components/core/Table/Table.animations.d.ts +5 -16
- package/dist/components/core/Table/Table.animations.js +46 -0
- package/dist/components/core/Table/Table.d.ts +0 -27
- package/dist/components/core/Table/Table.js +58 -156
- package/dist/components/core/Table/Table.theme.js +28 -19
- package/dist/components/core/Table/Table.types.d.ts +95 -8
- package/dist/components/core/Table/Table.utils.d.ts +7 -0
- package/dist/components/core/Table/Table.utils.js +11 -1
- package/dist/components/core/Table/{components/TableActions/TableActions.d.ts → TableActions.d.ts} +3 -3
- package/dist/components/core/Table/{components/TableActions/TableActions.js → TableActions.js} +14 -24
- package/dist/components/core/Table/{components/TableActions/TableActionsMenu.d.ts → TableActionsMenu.d.ts} +1 -1
- package/dist/components/core/Table/{components/TableActions/TableActionsMenu.js → TableActionsMenu.js} +4 -4
- package/dist/components/core/Table/{components/core/TableBody.d.ts → TableBody.d.ts} +1 -1
- package/dist/components/core/Table/{components/core/TableBody.js → TableBody.js} +14 -20
- package/dist/components/core/Table/{components/core/TableCell.d.ts → TableCell.d.ts} +1 -9
- package/dist/components/core/Table/{components/core/TableCell.js → TableCell.js} +5 -13
- package/dist/components/core/Table/TableColumnManager.d.ts +3 -0
- package/dist/components/core/Table/TableColumnManager.js +34 -0
- package/dist/components/core/Table/{components/DeclarativeComponents.d.ts → TableDeclarative.d.ts} +1 -1
- package/dist/components/core/Table/{components/DeclarativeComponents.js → TableDeclarative.js} +6 -56
- package/dist/components/core/Table/TableFilter.d.ts +3 -0
- package/dist/components/core/Table/TableFilter.js +122 -0
- package/dist/components/core/Table/{components/core/TableHeader.d.ts → TableHeader.d.ts} +1 -1
- package/dist/components/core/Table/{components/core/TableHeader.js → TableHeader.js} +15 -29
- package/dist/components/core/Table/TablePagination.d.ts +7 -0
- package/dist/components/core/Table/{components/TablePagination/TablePagination.js → TablePagination.js} +5 -16
- package/dist/components/core/Table/TableRow.d.ts +8 -0
- package/dist/components/core/Table/TableRow.js +45 -0
- package/dist/components/core/Table/TableSelectionHeader.d.ts +7 -0
- package/dist/components/core/Table/{components/TableSelectionHeader/TableSelectionHeader.js → TableSelectionHeader.js} +4 -5
- package/dist/components/core/Table/hooks/index.d.ts +10 -0
- package/dist/components/core/Table/hooks/useTableColumns.d.ts +16 -0
- package/dist/components/core/Table/hooks/useTableColumns.js +67 -0
- package/dist/components/core/Table/hooks/useTableExpansion.d.ts +8 -0
- package/dist/components/core/Table/hooks/useTableExpansion.js +15 -0
- package/dist/components/core/Table/hooks/useTableFilter.d.ts +12 -0
- package/dist/components/core/Table/hooks/useTableFilter.js +37 -0
- package/dist/components/core/Table/hooks/useTablePagination.d.ts +12 -0
- package/dist/components/core/Table/hooks/useTablePagination.js +13 -0
- package/dist/components/core/Table/hooks/useTableSelection.d.ts +17 -0
- package/dist/components/core/Table/hooks/useTableSelection.js +40 -0
- package/dist/components/core/Table/index.d.ts +9 -8
- package/dist/components/core/Table/index.js +7 -5
- package/dist/components/core/index.js +9 -3
- package/dist/index.js +9 -3
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/components/core/Table/components/TableActions/TableActions.types.d.ts +0 -40
- package/dist/components/core/Table/components/TableActions/index.d.ts +0 -3
- package/dist/components/core/Table/components/TableActionsMenu.d.ts +0 -6
- package/dist/components/core/Table/components/TablePagination/TablePagination.d.ts +0 -17
- package/dist/components/core/Table/components/TablePagination/TablePagination.types.d.ts +0 -21
- package/dist/components/core/Table/components/TablePagination/index.d.ts +0 -2
- package/dist/components/core/Table/components/TableSelectionHeader/TableSelectionHeader.d.ts +0 -15
- package/dist/components/core/Table/components/TableSelectionHeader/index.d.ts +0 -3
- package/dist/components/core/Table/components/core/TableRow.d.ts +0 -3
- package/dist/components/core/Table/components/core/TableRow.js +0 -44
- package/dist/components/core/Table/components/core/index.d.ts +0 -4
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React__default, { useState } from 'react';
|
|
2
|
+
import { Button } from '../Button/Button.js';
|
|
3
|
+
import { Input } from '../../forms/Input/Input.js';
|
|
4
|
+
import '../../forms/Input/Input.theme.js';
|
|
5
|
+
import { Select } from '../../forms/Select/Select.js';
|
|
6
|
+
import '../../forms/Select/Select.theme.js';
|
|
7
|
+
import { FunnelIcon, XMarkIcon, PlusIcon } from '@heroicons/react/24/outline';
|
|
8
|
+
import { cn } from '../../../utils/cn.js';
|
|
9
|
+
|
|
10
|
+
const operatorOptions = [
|
|
11
|
+
{ id: 'contains', label: 'Contains', value: 'contains' },
|
|
12
|
+
{ id: 'equals', label: 'Equals', value: 'equals' },
|
|
13
|
+
{ id: 'startsWith', label: 'Starts with', value: 'startsWith' },
|
|
14
|
+
{ id: 'endsWith', label: 'Ends with', value: 'endsWith' },
|
|
15
|
+
{ id: 'greaterThan', label: 'Greater than', value: 'greaterThan' },
|
|
16
|
+
{ id: 'lessThan', label: 'Less than', value: 'lessThan' },
|
|
17
|
+
];
|
|
18
|
+
function TableFilter({ columns, filterConfig, onFilterChange, className, }) {
|
|
19
|
+
const filterableColumns = columns.filter(col => col.filterable);
|
|
20
|
+
const [activeFilters, setActiveFilters] = useState(() => filterConfig.map((fc, i) => ({
|
|
21
|
+
id: `filter-${i}`,
|
|
22
|
+
columnId: fc.columnId,
|
|
23
|
+
operator: fc.operator || 'contains',
|
|
24
|
+
value: String(fc.value || ''),
|
|
25
|
+
})));
|
|
26
|
+
if (filterableColumns.length === 0)
|
|
27
|
+
return null;
|
|
28
|
+
const columnOptions = filterableColumns.map(col => ({
|
|
29
|
+
id: col.id,
|
|
30
|
+
label: typeof col.header === 'string' ? col.header : col.id,
|
|
31
|
+
value: col.id,
|
|
32
|
+
}));
|
|
33
|
+
const getFilterOperatorOptions = (column) => {
|
|
34
|
+
if (!column)
|
|
35
|
+
return operatorOptions;
|
|
36
|
+
if (column.filterType === 'number') {
|
|
37
|
+
return operatorOptions;
|
|
38
|
+
}
|
|
39
|
+
// Text filters: no greaterThan/lessThan
|
|
40
|
+
return operatorOptions.filter(op => op.value !== 'greaterThan' && op.value !== 'lessThan');
|
|
41
|
+
};
|
|
42
|
+
const syncFilters = (filters) => {
|
|
43
|
+
const newFilterConfig = filters
|
|
44
|
+
.filter(f => f.columnId && f.value)
|
|
45
|
+
.map(f => ({
|
|
46
|
+
columnId: f.columnId,
|
|
47
|
+
value: f.value,
|
|
48
|
+
operator: f.operator,
|
|
49
|
+
}));
|
|
50
|
+
onFilterChange(newFilterConfig);
|
|
51
|
+
};
|
|
52
|
+
const handleAddFilter = () => {
|
|
53
|
+
const defaultColumn = filterableColumns[0];
|
|
54
|
+
if (!defaultColumn)
|
|
55
|
+
return;
|
|
56
|
+
setActiveFilters(prev => [
|
|
57
|
+
...prev,
|
|
58
|
+
{
|
|
59
|
+
id: `filter-${Date.now()}`,
|
|
60
|
+
columnId: defaultColumn.id,
|
|
61
|
+
operator: 'contains',
|
|
62
|
+
value: '',
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
};
|
|
66
|
+
const handleRemoveFilter = (filterId) => {
|
|
67
|
+
setActiveFilters(prev => {
|
|
68
|
+
const updated = prev.filter(f => f.id !== filterId);
|
|
69
|
+
syncFilters(updated);
|
|
70
|
+
return updated;
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
const handleFilterColumnChange = (filterId, columnId) => {
|
|
74
|
+
setActiveFilters(prev => {
|
|
75
|
+
const updated = prev.map(f => f.id === filterId ? { ...f, columnId, value: '' } : f);
|
|
76
|
+
syncFilters(updated);
|
|
77
|
+
return updated;
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
const handleFilterOperatorChange = (filterId, operator) => {
|
|
81
|
+
setActiveFilters(prev => {
|
|
82
|
+
const updated = prev.map(f => f.id === filterId ? { ...f, operator } : f);
|
|
83
|
+
syncFilters(updated);
|
|
84
|
+
return updated;
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
const handleFilterValueChange = (filterId, value) => {
|
|
88
|
+
setActiveFilters(prev => {
|
|
89
|
+
const updated = prev.map(f => f.id === filterId ? { ...f, value } : f);
|
|
90
|
+
syncFilters(updated);
|
|
91
|
+
return updated;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const handleClearAll = () => {
|
|
95
|
+
setActiveFilters([]);
|
|
96
|
+
onFilterChange([]);
|
|
97
|
+
};
|
|
98
|
+
const getSelectedColumn = (columnId) => filterableColumns.find(col => col.id === columnId);
|
|
99
|
+
return (React__default.createElement("div", { className: cn('space-y-2', className) },
|
|
100
|
+
activeFilters.map((filter) => {
|
|
101
|
+
const selectedColumn = getSelectedColumn(filter.columnId);
|
|
102
|
+
const filteredOperatorOptions = getFilterOperatorOptions(selectedColumn);
|
|
103
|
+
return (React__default.createElement("div", { key: filter.id, className: "flex items-center gap-2" },
|
|
104
|
+
React__default.createElement(FunnelIcon, { className: "size-4 flex-shrink-0 text-[var(--color-text-muted)]" }),
|
|
105
|
+
React__default.createElement(Select, { value: filter.columnId, onChange: (value) => handleFilterColumnChange(filter.id, value), options: columnOptions, size: "sm", className: "w-36" }),
|
|
106
|
+
React__default.createElement(Select, { value: filter.operator, onChange: (value) => handleFilterOperatorChange(filter.id, value), options: filteredOperatorOptions, size: "sm", className: "w-32" }),
|
|
107
|
+
(selectedColumn === null || selectedColumn === void 0 ? void 0 : selectedColumn.filterType) === 'select' && selectedColumn.filterOptions ? (React__default.createElement(Select, { value: filter.value, onChange: (value) => handleFilterValueChange(filter.id, value), options: selectedColumn.filterOptions.map(opt => ({
|
|
108
|
+
id: opt.value,
|
|
109
|
+
label: opt.label,
|
|
110
|
+
value: opt.value,
|
|
111
|
+
})), size: "sm", className: "w-40" })) : (React__default.createElement(Input, { value: filter.value, onChange: (e) => handleFilterValueChange(filter.id, e.target.value), placeholder: "Filter value...", size: "sm", type: (selectedColumn === null || selectedColumn === void 0 ? void 0 : selectedColumn.filterType) === 'number' ? 'number' : 'text', className: "w-40" })),
|
|
112
|
+
React__default.createElement(Button, { color: "neutral", variant: "ghost", size: "sm", iconOnly: true, onClick: () => handleRemoveFilter(filter.id), "aria-label": "Remove filter" },
|
|
113
|
+
React__default.createElement(XMarkIcon, { className: "size-4" }))));
|
|
114
|
+
}),
|
|
115
|
+
React__default.createElement("div", { className: "flex items-center gap-2" },
|
|
116
|
+
React__default.createElement(Button, { color: "neutral", variant: "ghost", size: "sm", onClick: handleAddFilter },
|
|
117
|
+
React__default.createElement(PlusIcon, { className: "size-4" }),
|
|
118
|
+
"Add Filter"),
|
|
119
|
+
activeFilters.length > 0 && (React__default.createElement(Button, { color: "neutral", variant: "ghost", size: "sm", onClick: handleClearAll }, "Clear All")))));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { TableFilter };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { InternalTableHeaderProps } from '
|
|
2
|
+
import { InternalTableHeaderProps } from './Table.types';
|
|
3
3
|
export declare const TableHeader: <T extends Record<string, any>>(props: InternalTableHeaderProps<T>) => React.ReactElement;
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import React__default from 'react';
|
|
1
|
+
import React__default, { useCallback } from 'react';
|
|
2
2
|
import { TableCell } from './TableCell.js';
|
|
3
|
-
import { Checkbox } from '
|
|
4
|
-
import { getRowId } from '
|
|
3
|
+
import { Checkbox } from '../../forms/Checkbox/Checkbox.js';
|
|
4
|
+
import { getRowId, UTILITY_COLUMN_WIDTH, UTILITY_COLUMN_ID } from './Table.utils.js';
|
|
5
5
|
|
|
6
|
-
function TableHeaderComponent({ columns, sortConfig = [], onSortChange,
|
|
7
|
-
const handleSort = (columnId) => {
|
|
6
|
+
function TableHeaderComponent({ columns, sortConfig = [], onSortChange, freezeHeader, freezeFirstColumn, freezeLastColumn, expandable, expandedRowRender, selectable, selectionType, selectedRows = [], onSelectionChange, theme, data = [], rowKey, }) {
|
|
7
|
+
const handleSort = useCallback((columnId) => {
|
|
8
8
|
if (!onSortChange)
|
|
9
9
|
return;
|
|
10
10
|
const currentSort = sortConfig.find(sort => sort.columnId === columnId);
|
|
11
11
|
const newSortConfig = [...sortConfig.filter(sort => sort.columnId !== columnId)];
|
|
12
12
|
if (!currentSort) {
|
|
13
|
-
// Add new sort
|
|
14
13
|
newSortConfig.push({
|
|
15
14
|
columnId,
|
|
16
15
|
direction: 'asc',
|
|
@@ -18,58 +17,45 @@ function TableHeaderComponent({ columns, sortConfig = [], onSortChange, filterCo
|
|
|
18
17
|
});
|
|
19
18
|
}
|
|
20
19
|
else if (currentSort.direction === 'asc') {
|
|
21
|
-
// Change to desc
|
|
22
20
|
newSortConfig.push({
|
|
23
21
|
columnId,
|
|
24
22
|
direction: 'desc',
|
|
25
23
|
priority: currentSort.priority,
|
|
26
24
|
});
|
|
27
25
|
}
|
|
28
|
-
// If desc, remove sort
|
|
29
26
|
onSortChange(newSortConfig);
|
|
30
|
-
};
|
|
31
|
-
const handleSelectAll = (checked) => {
|
|
27
|
+
}, [sortConfig, onSortChange]);
|
|
28
|
+
const handleSelectAll = useCallback((checked) => {
|
|
32
29
|
if (!onSelectionChange || selectionType !== 'checkbox')
|
|
33
30
|
return;
|
|
34
31
|
if (checked) {
|
|
35
|
-
// Select all rows - generate row IDs using the same logic as TableBody
|
|
36
32
|
const allRowIds = data.map((row, index) => getRowId(row, index, rowKey));
|
|
37
33
|
onSelectionChange(allRowIds);
|
|
38
34
|
}
|
|
39
35
|
else {
|
|
40
|
-
// Deselect all
|
|
41
36
|
onSelectionChange([]);
|
|
42
37
|
}
|
|
43
|
-
};
|
|
44
|
-
// Calculate select all checkbox state
|
|
38
|
+
}, [data, rowKey, selectionType, onSelectionChange]);
|
|
45
39
|
const dataLength = data.length;
|
|
46
40
|
const isAllSelected = selectedRows.length === dataLength && dataLength > 0;
|
|
47
41
|
const isIndeterminate = selectedRows.length > 0 && selectedRows.length < dataLength;
|
|
48
42
|
return (React__default.createElement("thead", { className: freezeHeader ? theme === null || theme === void 0 ? void 0 : theme.frozenHeaderStyle : '' },
|
|
49
43
|
React__default.createElement("tr", null,
|
|
50
|
-
selectable && (React__default.createElement(TableCell, { column: {
|
|
51
|
-
id:
|
|
44
|
+
(selectable || (expandable && expandedRowRender)) && (React__default.createElement(TableCell, { column: {
|
|
45
|
+
id: UTILITY_COLUMN_ID,
|
|
52
46
|
header: '',
|
|
53
47
|
accessor: () => '',
|
|
54
|
-
width:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
expandable && expandedRowRender && (React__default.createElement(TableCell, { column: {
|
|
58
|
-
id: 'expand',
|
|
59
|
-
header: '',
|
|
60
|
-
accessor: () => '',
|
|
61
|
-
width: '48px',
|
|
62
|
-
align: 'center'
|
|
63
|
-
}, row: {}, rowId: "", isHeader: true, theme: theme })),
|
|
48
|
+
width: UTILITY_COLUMN_WIDTH,
|
|
49
|
+
}, row: {}, rowId: "", isHeader: true, theme: theme },
|
|
50
|
+
React__default.createElement("div", { className: "flex items-center gap-2" }, selectable && selectionType === 'checkbox' && (React__default.createElement(Checkbox, { id: "table-select-all", name: "table-select-all", value: "select-all", checked: isAllSelected, indeterminate: isIndeterminate, onChange: handleSelectAll, "aria-label": "Select all rows" }))))),
|
|
64
51
|
columns.map((column, index) => {
|
|
65
52
|
const currentSort = sortConfig.find(sort => sort.columnId === column.id);
|
|
66
53
|
const isSortable = column.sortable && onSortChange;
|
|
67
|
-
return (React__default.createElement(TableCell, { key: column.id, column: column, row: {}, rowId: "header", isHeader: true, isFrozen: freezeFirstColumn && index === 0, isLastFrozen: freezeLastColumn && index === columns.length - 1, theme: theme }, isSortable ? (React__default.createElement("button", { onClick: () => handleSort(column.id), className: "flex items-center gap-1 w-full text-left hover:text-primary
|
|
54
|
+
return (React__default.createElement(TableCell, { key: column.id, column: column, row: {}, rowId: "header", isHeader: true, isFrozen: freezeFirstColumn && index === 0, isLastFrozen: freezeLastColumn && index === columns.length - 1, theme: theme }, isSortable ? (React__default.createElement("button", { onClick: () => handleSort(column.id), className: "flex items-center gap-1 w-full text-left hover:text-[var(--color-primary)] transition-colors", "aria-label": `Sort by ${typeof column.header === 'string' ? column.header : column.id}` },
|
|
68
55
|
column.header,
|
|
69
|
-
currentSort && (React__default.createElement("span", { className: "text-xs" }, currentSort.direction === 'asc' ? '
|
|
56
|
+
currentSort && (React__default.createElement("span", { className: "text-xs" }, currentSort.direction === 'asc' ? '\u2191' : '\u2193')))) : (column.header)));
|
|
70
57
|
}))));
|
|
71
58
|
}
|
|
72
|
-
// Export with proper type annotation
|
|
73
59
|
const TableHeader = TableHeaderComponent;
|
|
74
60
|
|
|
75
61
|
export { TableHeader };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { TablePaginationProps } from "./Table.types";
|
|
3
|
+
/**
|
|
4
|
+
* TablePagination provides a standardized pagination interface for tables
|
|
5
|
+
* Combines the core Pagination component with page size selection controls
|
|
6
|
+
*/
|
|
7
|
+
export declare const TablePagination: React.FC<TablePaginationProps>;
|
|
@@ -1,34 +1,23 @@
|
|
|
1
1
|
import React__default from 'react';
|
|
2
|
-
import { Pagination } from '
|
|
3
|
-
import { Select } from '
|
|
4
|
-
import '
|
|
5
|
-
import { cn } from '
|
|
2
|
+
import { Pagination } from '../Pagination/Pagination.js';
|
|
3
|
+
import { Select } from '../../forms/Select/Select.js';
|
|
4
|
+
import '../../forms/Select/Select.theme.js';
|
|
5
|
+
import { cn } from '../../../utils/cn.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* TablePagination provides a standardized pagination interface for tables
|
|
9
9
|
* Combines the core Pagination component with page size selection controls
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* <Table.Pagination
|
|
13
|
-
* currentPage={currentPage}
|
|
14
|
-
* totalItems={data.length}
|
|
15
|
-
* pageSize={pageSize}
|
|
16
|
-
* onPageChange={setCurrentPage}
|
|
17
|
-
* onPageSizeChange={setPageSize}
|
|
18
|
-
* pageSizeOptions={[10, 25, 50, 100]}
|
|
19
|
-
* />
|
|
20
10
|
*/
|
|
21
11
|
const TablePagination = ({ currentPage, totalItems, pageSize, pageSizeOptions = [10, 25, 50, 100], onPageChange, onPageSizeChange, showPageSizeSelector = true, className, }) => {
|
|
22
12
|
const totalPages = Math.ceil(totalItems / pageSize);
|
|
23
13
|
const startItem = (currentPage - 1) * pageSize + 1;
|
|
24
14
|
const endItem = Math.min(currentPage * pageSize, totalItems);
|
|
25
|
-
// Create options for the Select component
|
|
26
15
|
const pageSizeSelectOptions = pageSizeOptions.map((option) => ({
|
|
27
16
|
id: option,
|
|
28
17
|
label: option.toString(),
|
|
29
18
|
value: option,
|
|
30
19
|
}));
|
|
31
|
-
return (React__default.createElement("div", { className: cn("flex items-center justify-between px-4 py-3 border-t border-border bg-white", className) },
|
|
20
|
+
return (React__default.createElement("div", { className: cn("flex items-center justify-between px-4 py-3 border-t border-[var(--color-border)] bg-white", className) },
|
|
32
21
|
React__default.createElement("div", { className: "flex items-center gap-4" },
|
|
33
22
|
showPageSizeSelector && (React__default.createElement("div", { className: "flex items-center gap-2" },
|
|
34
23
|
React__default.createElement("span", { className: "text-sm text-[var(--color-text-muted)]" }, "Rows per page:"),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { InternalTableRowProps } from "./Table.types";
|
|
3
|
+
declare function TableRowComponent<T extends Record<string, any>>({ row, rowId, columns, expandedRowRender, isExpanded, onExpandChange, selectable, selectionType, isSelected, onSelectionChange, freezeFirstColumn, freezeLastColumn, theme, }: InternalTableRowProps<T>): React.JSX.Element;
|
|
4
|
+
declare namespace TableRowComponent {
|
|
5
|
+
var displayName: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const TableRow: typeof TableRowComponent;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
3
|
+
import { TableCell } from './TableCell.js';
|
|
4
|
+
import { Button } from '../Button/Button.js';
|
|
5
|
+
import { Checkbox } from '../../forms/Checkbox/Checkbox.js';
|
|
6
|
+
import { Radio } from '../../forms/Radio/Radio.js';
|
|
7
|
+
import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline';
|
|
8
|
+
import { UTILITY_COLUMN_WIDTH, UTILITY_COLUMN_ID } from './Table.utils.js';
|
|
9
|
+
import { tableExpandAnimations, tableExpandContentAnimations } from './Table.animations.js';
|
|
10
|
+
|
|
11
|
+
function TableRowComponent({ row, rowId, columns, expandedRowRender, isExpanded = false, onExpandChange, selectable, selectionType, isSelected = false, onSelectionChange, freezeFirstColumn, freezeLastColumn, theme, }) {
|
|
12
|
+
const rowClasses = [
|
|
13
|
+
(theme === null || theme === void 0 ? void 0 : theme.rowStyle) || "",
|
|
14
|
+
isSelected ? (theme === null || theme === void 0 ? void 0 : theme.selectedRowStyle) || "" : "",
|
|
15
|
+
]
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.join(" ");
|
|
18
|
+
const handleExpandClick = (e) => {
|
|
19
|
+
e.stopPropagation();
|
|
20
|
+
onExpandChange === null || onExpandChange === void 0 ? void 0 : onExpandChange(!isExpanded);
|
|
21
|
+
};
|
|
22
|
+
const colSpan = columns.length + (selectable || !!expandedRowRender ? 1 : 0);
|
|
23
|
+
return (React__default.createElement(React__default.Fragment, null,
|
|
24
|
+
React__default.createElement("tr", { className: rowClasses, "data-selected": isSelected, role: "row", "aria-selected": selectable ? isSelected : undefined, "aria-expanded": Boolean(expandedRowRender) ? isExpanded : undefined },
|
|
25
|
+
(selectable || expandedRowRender) && (React__default.createElement(TableCell, { column: {
|
|
26
|
+
id: UTILITY_COLUMN_ID,
|
|
27
|
+
header: "",
|
|
28
|
+
accessor: () => "",
|
|
29
|
+
width: UTILITY_COLUMN_WIDTH,
|
|
30
|
+
}, row: row, rowId: rowId, theme: theme },
|
|
31
|
+
React__default.createElement("div", { className: "flex items-center gap-2" },
|
|
32
|
+
selectable &&
|
|
33
|
+
(selectionType === "checkbox" ? (React__default.createElement(Checkbox, { id: `table-checkbox-${rowId}`, name: `table-selection-${rowId}`, value: String(rowId), checked: isSelected, onChange: (checked) => onSelectionChange === null || onSelectionChange === void 0 ? void 0 : onSelectionChange(checked), onClick: (e) => e.stopPropagation(), "aria-label": "Select row" })) : (React__default.createElement("div", { onClick: (e) => e.stopPropagation() },
|
|
34
|
+
React__default.createElement(Radio, { id: `table-radio-${rowId}`, name: "table-selection", value: String(rowId), checked: isSelected, onChange: (checked) => onSelectionChange === null || onSelectionChange === void 0 ? void 0 : onSelectionChange(checked), "aria-label": "Select row" })))),
|
|
35
|
+
expandedRowRender && (React__default.createElement(Button, { color: "neutral", variant: "outline", size: "sm", iconOnly: true, onClick: handleExpandClick, "aria-label": isExpanded ? "Collapse row" : "Expand row" }, isExpanded ? (React__default.createElement(MinusIcon, { className: "size-4", strokeWidth: 2 })) : (React__default.createElement(PlusIcon, { className: "size-4", strokeWidth: 2 }))))))),
|
|
36
|
+
columns.map((column, index) => (React__default.createElement(TableCell, { key: column.id, column: column, row: row, rowId: rowId, isFrozen: freezeFirstColumn && index === 0, isLastFrozen: freezeLastColumn && index === columns.length - 1, theme: theme })))),
|
|
37
|
+
React__default.createElement(AnimatePresence, { initial: false }, isExpanded && expandedRowRender && (React__default.createElement(motion.tr, { key: `${rowId}-expanded`, initial: "collapsed", animate: "expanded", exit: "collapsed" },
|
|
38
|
+
React__default.createElement("td", { colSpan: colSpan, className: "p-0 border-b border-[var(--color-border)] dark:border-[var(--color-neutral-700)]" },
|
|
39
|
+
React__default.createElement(motion.div, { variants: tableExpandAnimations, style: { overflow: "hidden" } },
|
|
40
|
+
React__default.createElement(motion.div, { variants: tableExpandContentAnimations }, expandedRowRender(row)))))))));
|
|
41
|
+
}
|
|
42
|
+
TableRowComponent.displayName = "TableRow";
|
|
43
|
+
const TableRow = React__default.memo(TableRowComponent);
|
|
44
|
+
|
|
45
|
+
export { TableRow };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { TableSelectionHeaderProps } from "./Table.types";
|
|
3
|
+
/**
|
|
4
|
+
* Selection header that appears when rows are selected
|
|
5
|
+
* Shows selection count and bulk action buttons
|
|
6
|
+
*/
|
|
7
|
+
export declare const TableSelectionHeader: React.FC<TableSelectionHeaderProps>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React__default from 'react';
|
|
2
2
|
import { XMarkIcon } from '@heroicons/react/24/outline';
|
|
3
|
-
import { Button } from '
|
|
4
|
-
import { cn } from '
|
|
3
|
+
import { Button } from '../Button/Button.js';
|
|
4
|
+
import { cn } from '../../../utils/cn.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Selection header that appears when rows are selected
|
|
@@ -10,13 +10,12 @@ import { cn } from '../../../../../utils/cn.js';
|
|
|
10
10
|
const TableSelectionHeader = ({ selectedCount, totalCount, selectedRows, bulkActions = [], onClearSelection, className, }) => {
|
|
11
11
|
if (selectedCount === 0)
|
|
12
12
|
return null;
|
|
13
|
-
return (React__default.createElement("div", { className: cn("flex items-center justify-between px-3 py-1 rounded-lg shadow-xl bg-
|
|
13
|
+
return (React__default.createElement("div", { className: cn("flex items-center justify-between px-3 py-1 rounded-lg shadow-xl bg-[var(--color-background-secondary)] z-10", className) },
|
|
14
14
|
React__default.createElement("div", { className: "flex items-center gap-3" },
|
|
15
|
-
React__default.createElement("span", { className: "text-[var(--color-text-muted)] text-sm border-r border-border pr-3" }, selectedCount === totalCount
|
|
15
|
+
React__default.createElement("span", { className: "text-[var(--color-text-muted)] text-sm border-r border-[var(--color-border)] pr-3" }, selectedCount === totalCount
|
|
16
16
|
? `All ${selectedCount} selected`
|
|
17
17
|
: `${selectedCount} selected`),
|
|
18
18
|
bulkActions.length > 0 && (React__default.createElement("div", { className: "flex items-center gap-2" }, bulkActions.map((action) => {
|
|
19
|
-
action.icon;
|
|
20
19
|
return (React__default.createElement(Button, { key: action.id, color: "neutral", variant: "filled", onClick: () => action.onClick(selectedRows), className: "text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]", size: "sm" }, action.label));
|
|
21
20
|
})))),
|
|
22
21
|
React__default.createElement(Button, { color: "primary", variant: "link", onClick: onClearSelection, "aria-label": "Clear selection" },
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { useTableColumns } from './useTableColumns';
|
|
2
|
+
export { useTableSelection } from './useTableSelection';
|
|
3
|
+
export { useTableExpansion } from './useTableExpansion';
|
|
4
|
+
export { useTablePagination } from './useTablePagination';
|
|
5
|
+
export { useTableFilter } from './useTableFilter';
|
|
6
|
+
export type { UseTableColumnsReturn } from './useTableColumns';
|
|
7
|
+
export type { UseTableSelectionReturn } from './useTableSelection';
|
|
8
|
+
export type { UseTableExpansionReturn } from './useTableExpansion';
|
|
9
|
+
export type { UseTablePaginationReturn } from './useTablePagination';
|
|
10
|
+
export type { UseTableFilterReturn } from './useTableFilter';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Column } from '../Table.types';
|
|
2
|
+
export interface UseTableColumnsOptions<T> {
|
|
3
|
+
columns?: Column<T>[];
|
|
4
|
+
initialVisibleColumns?: string[];
|
|
5
|
+
onVisibleColumnsChange?: (visibleColumns: string[]) => void;
|
|
6
|
+
onColumnReorder?: (fromIndex: number, toIndex: number) => void;
|
|
7
|
+
}
|
|
8
|
+
export interface UseTableColumnsReturn<T> {
|
|
9
|
+
visibleColumns: string[];
|
|
10
|
+
columnOrder: string[];
|
|
11
|
+
visibleColumnsList: Column<T>[];
|
|
12
|
+
handleToggleColumn: (columnId: string) => void;
|
|
13
|
+
handleColumnReorder: (fromIndex: number, toIndex: number) => void;
|
|
14
|
+
handleResetToDefault: () => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function useTableColumns<T>({ columns, initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, }: UseTableColumnsOptions<T>): UseTableColumnsReturn<T>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
function useTableColumns({ columns, initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, }) {
|
|
4
|
+
const defaultVisibleColumns = initialVisibleColumns || (columns ? columns.map((col) => col.id) : []);
|
|
5
|
+
const defaultColumnOrder = columns ? columns.map((col) => col.id) : [];
|
|
6
|
+
const [visibleColumns, setVisibleColumns] = useState(defaultVisibleColumns);
|
|
7
|
+
const [columnOrder, setColumnOrder] = useState(defaultColumnOrder);
|
|
8
|
+
// Sync column order when columns prop changes
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (columns) {
|
|
11
|
+
setColumnOrder(columns.map((col) => col.id));
|
|
12
|
+
}
|
|
13
|
+
}, [columns]);
|
|
14
|
+
const handleVisibleColumnsChange = useCallback((newVisibleColumns) => {
|
|
15
|
+
setVisibleColumns(newVisibleColumns);
|
|
16
|
+
onVisibleColumnsChange === null || onVisibleColumnsChange === void 0 ? void 0 : onVisibleColumnsChange(newVisibleColumns);
|
|
17
|
+
}, [onVisibleColumnsChange]);
|
|
18
|
+
const handleToggleColumn = useCallback((columnId) => {
|
|
19
|
+
const column = columns === null || columns === void 0 ? void 0 : columns.find(col => col.id === columnId);
|
|
20
|
+
if (column === null || column === void 0 ? void 0 : column.locked)
|
|
21
|
+
return;
|
|
22
|
+
setVisibleColumns(prev => {
|
|
23
|
+
const newVisibleColumns = prev.includes(columnId)
|
|
24
|
+
? prev.filter((id) => id !== columnId)
|
|
25
|
+
: [...prev, columnId];
|
|
26
|
+
onVisibleColumnsChange === null || onVisibleColumnsChange === void 0 ? void 0 : onVisibleColumnsChange(newVisibleColumns);
|
|
27
|
+
return newVisibleColumns;
|
|
28
|
+
});
|
|
29
|
+
}, [columns, onVisibleColumnsChange]);
|
|
30
|
+
const handleColumnReorder = useCallback((fromIndex, toIndex) => {
|
|
31
|
+
setColumnOrder(prev => {
|
|
32
|
+
const newColumnOrder = [...prev];
|
|
33
|
+
const [movedColumn] = newColumnOrder.splice(fromIndex, 1);
|
|
34
|
+
newColumnOrder.splice(toIndex, 0, movedColumn);
|
|
35
|
+
return newColumnOrder;
|
|
36
|
+
});
|
|
37
|
+
onColumnReorder === null || onColumnReorder === void 0 ? void 0 : onColumnReorder(fromIndex, toIndex);
|
|
38
|
+
}, [onColumnReorder]);
|
|
39
|
+
const handleResetToDefault = useCallback(() => {
|
|
40
|
+
const lockedColumnIds = (columns === null || columns === void 0 ? void 0 : columns.filter(col => col.locked).map(col => col.id)) || [];
|
|
41
|
+
const resetVisibleColumns = Array.from(new Set([...defaultVisibleColumns, ...lockedColumnIds]));
|
|
42
|
+
handleVisibleColumnsChange(resetVisibleColumns);
|
|
43
|
+
if (columns) {
|
|
44
|
+
setColumnOrder(columns.map((col) => col.id));
|
|
45
|
+
}
|
|
46
|
+
}, [columns, defaultVisibleColumns, handleVisibleColumnsChange]);
|
|
47
|
+
const visibleColumnsList = useMemo(() => {
|
|
48
|
+
if (!columns)
|
|
49
|
+
return [];
|
|
50
|
+
if (columnOrder.length > 0) {
|
|
51
|
+
return columnOrder
|
|
52
|
+
.map(colId => columns.find(col => col.id === colId))
|
|
53
|
+
.filter((col) => col !== undefined && visibleColumns.includes(col.id));
|
|
54
|
+
}
|
|
55
|
+
return columns.filter((col) => visibleColumns.includes(col.id));
|
|
56
|
+
}, [columns, columnOrder, visibleColumns]);
|
|
57
|
+
return {
|
|
58
|
+
visibleColumns,
|
|
59
|
+
columnOrder,
|
|
60
|
+
visibleColumnsList,
|
|
61
|
+
handleToggleColumn,
|
|
62
|
+
handleColumnReorder,
|
|
63
|
+
handleResetToDefault,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { useTableColumns };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface UseTableExpansionOptions {
|
|
2
|
+
expandedRows?: string[];
|
|
3
|
+
onExpandedRowsChange?: (expandedRows: string[]) => void;
|
|
4
|
+
}
|
|
5
|
+
export interface UseTableExpansionReturn {
|
|
6
|
+
handleExpandChange: (rowId: string, expanded: boolean) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function useTableExpansion({ expandedRows, onExpandedRowsChange, }: UseTableExpansionOptions): UseTableExpansionReturn;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
function useTableExpansion({ expandedRows = [], onExpandedRowsChange, }) {
|
|
4
|
+
const handleExpandChange = useCallback((rowId, expanded) => {
|
|
5
|
+
if (!onExpandedRowsChange)
|
|
6
|
+
return;
|
|
7
|
+
const newExpandedRows = expanded
|
|
8
|
+
? [...expandedRows, rowId]
|
|
9
|
+
: expandedRows.filter(id => id !== rowId);
|
|
10
|
+
onExpandedRowsChange(newExpandedRows);
|
|
11
|
+
}, [expandedRows, onExpandedRowsChange]);
|
|
12
|
+
return { handleExpandChange };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { useTableExpansion };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FilterConfig, FilterOperator } from '../Table.types';
|
|
2
|
+
export interface UseTableFilterOptions {
|
|
3
|
+
filterConfig?: FilterConfig[];
|
|
4
|
+
onFilterChange?: (filterConfig: FilterConfig[]) => void;
|
|
5
|
+
}
|
|
6
|
+
export interface UseTableFilterReturn {
|
|
7
|
+
handleAddFilter: (columnId: string, value: any, operator?: FilterOperator) => void;
|
|
8
|
+
handleRemoveFilter: (columnId: string) => void;
|
|
9
|
+
handleUpdateFilter: (columnId: string, value: any, operator?: FilterOperator) => void;
|
|
10
|
+
handleClearFilters: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function useTableFilter({ filterConfig, onFilterChange, }: UseTableFilterOptions): UseTableFilterReturn;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
function useTableFilter({ filterConfig = [], onFilterChange, }) {
|
|
4
|
+
const handleAddFilter = useCallback((columnId, value, operator) => {
|
|
5
|
+
if (!onFilterChange)
|
|
6
|
+
return;
|
|
7
|
+
const existing = filterConfig.find(f => f.columnId === columnId);
|
|
8
|
+
if (existing) {
|
|
9
|
+
const updated = filterConfig.map(f => f.columnId === columnId ? { ...f, value, operator } : f);
|
|
10
|
+
onFilterChange(updated);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
onFilterChange([...filterConfig, { columnId, value, operator }]);
|
|
14
|
+
}
|
|
15
|
+
}, [filterConfig, onFilterChange]);
|
|
16
|
+
const handleRemoveFilter = useCallback((columnId) => {
|
|
17
|
+
if (!onFilterChange)
|
|
18
|
+
return;
|
|
19
|
+
onFilterChange(filterConfig.filter(f => f.columnId !== columnId));
|
|
20
|
+
}, [filterConfig, onFilterChange]);
|
|
21
|
+
const handleUpdateFilter = useCallback((columnId, value, operator) => {
|
|
22
|
+
if (!onFilterChange)
|
|
23
|
+
return;
|
|
24
|
+
onFilterChange(filterConfig.map(f => f.columnId === columnId ? { ...f, value, operator } : f));
|
|
25
|
+
}, [filterConfig, onFilterChange]);
|
|
26
|
+
const handleClearFilters = useCallback(() => {
|
|
27
|
+
onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange([]);
|
|
28
|
+
}, [onFilterChange]);
|
|
29
|
+
return {
|
|
30
|
+
handleAddFilter,
|
|
31
|
+
handleRemoveFilter,
|
|
32
|
+
handleUpdateFilter,
|
|
33
|
+
handleClearFilters,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { useTableFilter };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface UseTablePaginationOptions<T> {
|
|
2
|
+
data?: T[];
|
|
3
|
+
pagination?: {
|
|
4
|
+
pageSize: number;
|
|
5
|
+
currentPage: number;
|
|
6
|
+
totalItems: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface UseTablePaginationReturn<T> {
|
|
10
|
+
paginatedData: T[];
|
|
11
|
+
}
|
|
12
|
+
export declare function useTablePagination<T>({ data, pagination, }: UseTablePaginationOptions<T>): UseTablePaginationReturn<T>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
function useTablePagination({ data, pagination, }) {
|
|
4
|
+
const paginatedData = useMemo(() => {
|
|
5
|
+
if (pagination && data) {
|
|
6
|
+
return data.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
|
|
7
|
+
}
|
|
8
|
+
return data || [];
|
|
9
|
+
}, [data, pagination]);
|
|
10
|
+
return { paginatedData };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { useTablePagination };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SelectionType } from '../Table.types';
|
|
2
|
+
export interface UseTableSelectionOptions<T> {
|
|
3
|
+
data: T[];
|
|
4
|
+
rowKey?: keyof T | ((row: T, index: number) => string | number);
|
|
5
|
+
selectable?: boolean;
|
|
6
|
+
selectionType?: SelectionType;
|
|
7
|
+
selectedRows?: string[];
|
|
8
|
+
onSelectionChange?: (selectedRows: string[]) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface UseTableSelectionReturn {
|
|
11
|
+
isAllSelected: boolean;
|
|
12
|
+
isIndeterminate: boolean;
|
|
13
|
+
handleSelectAll: (checked: boolean) => void;
|
|
14
|
+
handleSelectionChange: (rowId: string, selected: boolean) => void;
|
|
15
|
+
handleClearSelection: () => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function useTableSelection<T extends Record<string, any>>({ data, rowKey, selectionType, selectedRows, onSelectionChange, }: UseTableSelectionOptions<T>): UseTableSelectionReturn;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useMemo, useCallback } from 'react';
|
|
2
|
+
import { getRowId } from '../Table.utils.js';
|
|
3
|
+
|
|
4
|
+
function useTableSelection({ data, rowKey, selectionType = 'checkbox', selectedRows = [], onSelectionChange, }) {
|
|
5
|
+
const isAllSelected = useMemo(() => selectedRows.length === data.length && data.length > 0, [selectedRows.length, data.length]);
|
|
6
|
+
const isIndeterminate = useMemo(() => selectedRows.length > 0 && selectedRows.length < data.length, [selectedRows.length, data.length]);
|
|
7
|
+
const handleSelectAll = useCallback((checked) => {
|
|
8
|
+
if (!onSelectionChange || selectionType !== 'checkbox')
|
|
9
|
+
return;
|
|
10
|
+
if (checked) {
|
|
11
|
+
const allRowIds = data.map((row, index) => getRowId(row, index, rowKey));
|
|
12
|
+
onSelectionChange(allRowIds);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
onSelectionChange([]);
|
|
16
|
+
}
|
|
17
|
+
}, [data, rowKey, selectionType, onSelectionChange]);
|
|
18
|
+
const handleSelectionChange = useCallback((rowId, selected) => {
|
|
19
|
+
if (!onSelectionChange)
|
|
20
|
+
return;
|
|
21
|
+
const newSelectedRows = selectionType === 'checkbox'
|
|
22
|
+
? selected
|
|
23
|
+
? [...selectedRows, rowId]
|
|
24
|
+
: selectedRows.filter(id => id !== rowId)
|
|
25
|
+
: selected ? [rowId] : [];
|
|
26
|
+
onSelectionChange(newSelectedRows);
|
|
27
|
+
}, [selectionType, selectedRows, onSelectionChange]);
|
|
28
|
+
const handleClearSelection = useCallback(() => {
|
|
29
|
+
onSelectionChange === null || onSelectionChange === void 0 ? void 0 : onSelectionChange([]);
|
|
30
|
+
}, [onSelectionChange]);
|
|
31
|
+
return {
|
|
32
|
+
isAllSelected,
|
|
33
|
+
isIndeterminate,
|
|
34
|
+
handleSelectAll,
|
|
35
|
+
handleSelectionChange,
|
|
36
|
+
handleClearSelection,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { useTableSelection };
|