@papernote/ui 2.0.3 → 2.0.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/dist/components/DataTable.d.ts +29 -11
- package/dist/components/DataTable.d.ts.map +1 -1
- package/dist/components/FilterBar.d.ts +1 -1
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/index.d.ts +28 -10
- package/dist/index.esm.js +433 -270
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +433 -270
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -0
- package/package.json +1 -1
- package/src/components/DataTable.tsx +993 -647
- package/src/components/FilterBar.tsx +130 -53
package/dist/index.esm.js
CHANGED
|
@@ -2745,7 +2745,7 @@ function FormControl({ label, required = false, error, helperText, children, cla
|
|
|
2745
2745
|
return (jsxs("div", { className: `${className}`, children: [label && (jsxs("label", { htmlFor: htmlFor, className: "block text-sm font-medium text-ink-700 mb-1", children: [label, required && jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsx("div", { children: children }), (error || helperText) && (jsx("p", { className: `mt-1 text-xs ${error ? 'text-error-600' : 'text-ink-500'}`, children: error || helperText }))] }));
|
|
2746
2746
|
}
|
|
2747
2747
|
|
|
2748
|
-
function FilterBar({ filters, values, onChange, className =
|
|
2748
|
+
function FilterBar({ filters, values, onChange, className = "", onClear, showClearButton = false, }) {
|
|
2749
2749
|
const handleFilterChange = (key, value) => {
|
|
2750
2750
|
onChange({
|
|
2751
2751
|
...values,
|
|
@@ -2759,16 +2759,19 @@ function FilterBar({ filters, values, onChange, className = '', onClear, showCle
|
|
|
2759
2759
|
else {
|
|
2760
2760
|
// Default clear: set all values to null/empty
|
|
2761
2761
|
const clearedValues = {};
|
|
2762
|
-
filters.forEach(filter => {
|
|
2763
|
-
if (filter.type ===
|
|
2764
|
-
clearedValues[filter.key] =
|
|
2762
|
+
filters.forEach((filter) => {
|
|
2763
|
+
if (filter.type === "text" || filter.type === "search") {
|
|
2764
|
+
clearedValues[filter.key] = "";
|
|
2765
2765
|
}
|
|
2766
|
-
else if (filter.type ===
|
|
2766
|
+
else if (filter.type === "dateRange") {
|
|
2767
2767
|
clearedValues[filter.key] = { from: undefined, to: undefined };
|
|
2768
2768
|
}
|
|
2769
|
-
else if (filter.type ===
|
|
2769
|
+
else if (filter.type === "multiSelect") {
|
|
2770
2770
|
clearedValues[filter.key] = [];
|
|
2771
2771
|
}
|
|
2772
|
+
else if (filter.type === "switch") {
|
|
2773
|
+
clearedValues[filter.key] = false;
|
|
2774
|
+
}
|
|
2772
2775
|
else {
|
|
2773
2776
|
clearedValues[filter.key] = null;
|
|
2774
2777
|
}
|
|
@@ -2779,51 +2782,70 @@ function FilterBar({ filters, values, onChange, className = '', onClear, showCle
|
|
|
2779
2782
|
const renderFilter = (filter) => {
|
|
2780
2783
|
const value = values[filter.key];
|
|
2781
2784
|
switch (filter.type) {
|
|
2782
|
-
case
|
|
2783
|
-
return (jsx(Input, { type: "text", placeholder: filter.placeholder || `Filter by ${filter.label}`, value: value ||
|
|
2784
|
-
case
|
|
2785
|
+
case "text":
|
|
2786
|
+
return (jsx(Input, { type: "text", placeholder: filter.placeholder || `Filter by ${filter.label}`, value: value || "", onChange: (e) => handleFilterChange(filter.key, e.target.value) }));
|
|
2787
|
+
case "select": {
|
|
2785
2788
|
const selectOptions = [
|
|
2786
|
-
{ value:
|
|
2787
|
-
...(filter.options?.map(opt => ({
|
|
2789
|
+
{ value: "", label: `All ${filter.label}` },
|
|
2790
|
+
...(filter.options?.map((opt) => ({
|
|
2788
2791
|
value: String(opt.value),
|
|
2789
2792
|
label: opt.label,
|
|
2790
2793
|
})) || []),
|
|
2791
2794
|
];
|
|
2792
|
-
return (jsx(Select, { options: selectOptions, value: String(value ||
|
|
2795
|
+
return (jsx(Select, { options: selectOptions, value: String(value || ""), onChange: (newValue) => handleFilterChange(filter.key, newValue || null) }));
|
|
2793
2796
|
}
|
|
2794
|
-
case
|
|
2795
|
-
return (jsx("input", { type: "date", value: value ||
|
|
2796
|
-
case
|
|
2797
|
-
return (jsx("input", { type: "number", placeholder: filter.placeholder || `Filter by ${filter.label}`, value: value !== null && value !== undefined ? String(value) :
|
|
2798
|
-
case
|
|
2797
|
+
case "date":
|
|
2798
|
+
return (jsx("input", { type: "date", value: value || "", onChange: (e) => handleFilterChange(filter.key, e.target.value), className: "input" }));
|
|
2799
|
+
case "number":
|
|
2800
|
+
return (jsx("input", { type: "number", placeholder: filter.placeholder || `Filter by ${filter.label}`, value: value !== null && value !== undefined ? String(value) : "", onChange: (e) => handleFilterChange(filter.key, e.target.value ? Number(e.target.value) : null), className: "input" }));
|
|
2801
|
+
case "boolean": {
|
|
2799
2802
|
const boolOptions = [
|
|
2800
|
-
{ value:
|
|
2801
|
-
{ value:
|
|
2802
|
-
{ value:
|
|
2803
|
+
{ value: "", label: "All" },
|
|
2804
|
+
{ value: "true", label: "Yes" },
|
|
2805
|
+
{ value: "false", label: "No" },
|
|
2803
2806
|
];
|
|
2804
|
-
return (jsx(Select, { options: boolOptions, value: value === null || value === undefined ?
|
|
2807
|
+
return (jsx(Select, { options: boolOptions, value: value === null || value === undefined ? "" : String(value), onChange: (newValue) => handleFilterChange(filter.key, newValue === "" ? null : newValue === "true") }));
|
|
2805
2808
|
}
|
|
2806
|
-
case
|
|
2807
|
-
return (jsxs("div", { className: "relative", children: [jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none", children: jsx(Search, { className: "h-4 w-4 text-ink-400" }) }), jsx("input", { type: "text", placeholder: filter.placeholder || `Search ${filter.label}...`, value: value ||
|
|
2808
|
-
case
|
|
2809
|
+
case "search":
|
|
2810
|
+
return (jsxs("div", { className: "relative", children: [jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none", children: jsx(Search, { className: "h-4 w-4 text-ink-400" }) }), jsx("input", { type: "text", placeholder: filter.placeholder || `Search ${filter.label}...`, value: value || "", onChange: (e) => handleFilterChange(filter.key, e.target.value), className: "input pl-9" })] }));
|
|
2811
|
+
case "dateRange": {
|
|
2809
2812
|
const rangeValue = value || {};
|
|
2810
|
-
return (jsxs("div", { className: "flex items-center gap-2", children: [jsx("input", { type: "date", value: rangeValue.from ||
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
+
return (jsxs("div", { className: "flex items-center gap-2", children: [jsx("input", { type: "date", value: rangeValue.from || "", onChange: (e) => handleFilterChange(filter.key, {
|
|
2814
|
+
...rangeValue,
|
|
2815
|
+
from: e.target.value || undefined,
|
|
2816
|
+
}), className: "input text-sm", "aria-label": `${filter.label} from` }), jsx("span", { className: "text-ink-400 text-xs", children: "to" }), jsx("input", { type: "date", value: rangeValue.to || "", onChange: (e) => handleFilterChange(filter.key, {
|
|
2817
|
+
...rangeValue,
|
|
2818
|
+
to: e.target.value || undefined,
|
|
2819
|
+
}), className: "input text-sm", "aria-label": `${filter.label} to` })] }));
|
|
2820
|
+
}
|
|
2821
|
+
case "toggle": {
|
|
2813
2822
|
const toggleOptions = [
|
|
2814
|
-
{ value:
|
|
2815
|
-
{ value:
|
|
2816
|
-
{ value:
|
|
2823
|
+
{ value: "", label: "All" },
|
|
2824
|
+
{ value: "true", label: "Yes" },
|
|
2825
|
+
{ value: "false", label: "No" },
|
|
2817
2826
|
];
|
|
2818
|
-
const currentVal = value === null || value === undefined ?
|
|
2819
|
-
return (jsx("div", { className: "flex rounded-lg border border-paper-300 overflow-hidden", role: "group", children: toggleOptions.map((opt) => (jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, opt.value ===
|
|
2820
|
-
?
|
|
2821
|
-
:
|
|
2822
|
-
}
|
|
2823
|
-
case
|
|
2827
|
+
const currentVal = value === null || value === undefined ? "" : String(value);
|
|
2828
|
+
return (jsx("div", { className: "flex rounded-lg border border-paper-300 overflow-hidden", role: "group", children: toggleOptions.map((opt) => (jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, opt.value === "" ? null : opt.value === "true"), className: `px-3 py-1.5 text-xs font-medium transition-colors ${currentVal === opt.value
|
|
2829
|
+
? "bg-accent-500 text-white"
|
|
2830
|
+
: "bg-white text-ink-600 hover:bg-paper-50"} ${opt.value !== "" ? "border-l border-paper-300" : ""}`, children: opt.label }, opt.value))) }));
|
|
2831
|
+
}
|
|
2832
|
+
case "switch": {
|
|
2833
|
+
// Single binary toggle — use when the filter is naturally on/off
|
|
2834
|
+
// (e.g. "Mine only", "Archived"), unlike `boolean` / `toggle` which
|
|
2835
|
+
// present an All/Yes/No tri-state. Stored value is a plain boolean.
|
|
2836
|
+
const checked = value === true;
|
|
2837
|
+
return (jsxs("button", { type: "button", role: "switch", "aria-checked": checked, onClick: () => handleFilterChange(filter.key, !checked), className: `relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-accent-400 focus:ring-offset-2 ${checked ? "bg-accent-500" : "bg-paper-300"}`, children: [jsx("span", { className: `inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${checked ? "translate-x-6" : "translate-x-1"}` }), jsx("span", { className: "sr-only", children: filter.label })] }));
|
|
2838
|
+
}
|
|
2839
|
+
case "multiSelect": {
|
|
2824
2840
|
const selectedValues = Array.isArray(value) ? value : [];
|
|
2825
2841
|
const msOptions = filter.options || [];
|
|
2826
|
-
return (jsxs("div", { className: "relative", children: [jsx(Select, { options: [
|
|
2842
|
+
return (jsxs("div", { className: "relative", children: [jsx(Select, { options: [
|
|
2843
|
+
{ value: "", label: `All ${filter.label}` },
|
|
2844
|
+
...msOptions.map((o) => ({
|
|
2845
|
+
value: String(o.value),
|
|
2846
|
+
label: o.label,
|
|
2847
|
+
})),
|
|
2848
|
+
], value: "", onChange: (newValue) => {
|
|
2827
2849
|
if (!newValue) {
|
|
2828
2850
|
handleFilterChange(filter.key, []);
|
|
2829
2851
|
}
|
|
@@ -2831,8 +2853,8 @@ function FilterBar({ filters, values, onChange, className = '', onClear, showCle
|
|
|
2831
2853
|
handleFilterChange(filter.key, [...selectedValues, newValue]);
|
|
2832
2854
|
}
|
|
2833
2855
|
} }), selectedValues.length > 0 && (jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: selectedValues.map((sv) => {
|
|
2834
|
-
const opt = msOptions.find(o => String(o.value) === sv);
|
|
2835
|
-
return (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-accent-100 text-accent-700 rounded-full", children: [opt?.label || sv, jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, selectedValues.filter(v => v !== sv)), className: "hover:text-accent-900", children: jsx(X, { className: "h-3 w-3" }) })] }, sv));
|
|
2856
|
+
const opt = msOptions.find((o) => String(o.value) === sv);
|
|
2857
|
+
return (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-accent-100 text-accent-700 rounded-full", children: [opt?.label || sv, jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, selectedValues.filter((v) => v !== sv)), className: "hover:text-accent-900", children: jsx(X, { className: "h-3 w-3" }) })] }, sv));
|
|
2836
2858
|
}) }))] }));
|
|
2837
2859
|
}
|
|
2838
2860
|
default:
|
|
@@ -14784,25 +14806,27 @@ function ActionMenu({ actions, item, }) {
|
|
|
14784
14806
|
// Click outside handler
|
|
14785
14807
|
useEffect(() => {
|
|
14786
14808
|
const handleClickOutside = (event) => {
|
|
14787
|
-
if (menuRef.current &&
|
|
14788
|
-
|
|
14809
|
+
if (menuRef.current &&
|
|
14810
|
+
!menuRef.current.contains(event.target) &&
|
|
14811
|
+
buttonRef.current &&
|
|
14812
|
+
!buttonRef.current.contains(event.target)) {
|
|
14789
14813
|
setIsOpen(false);
|
|
14790
14814
|
}
|
|
14791
14815
|
};
|
|
14792
14816
|
if (isOpen) {
|
|
14793
|
-
document.addEventListener(
|
|
14817
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
14794
14818
|
}
|
|
14795
14819
|
return () => {
|
|
14796
|
-
document.removeEventListener(
|
|
14820
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
14797
14821
|
};
|
|
14798
14822
|
}, [isOpen]);
|
|
14799
|
-
const visibleActions = actions.filter(action => !action.show || action.show(item));
|
|
14823
|
+
const visibleActions = actions.filter((action) => !action.show || action.show(item));
|
|
14800
14824
|
if (visibleActions.length === 0)
|
|
14801
14825
|
return null;
|
|
14802
14826
|
const dropdownContent = isOpen && (jsx("div", { ref: menuRef, className: "fixed w-56 bg-white rounded-lg shadow-lg border border-paper-300 py-1", style: {
|
|
14803
14827
|
zIndex: 999999,
|
|
14804
14828
|
top: `${position.top}px`,
|
|
14805
|
-
left: `${position.left}px
|
|
14829
|
+
left: `${position.left}px`,
|
|
14806
14830
|
}, children: visibleActions.map((action, idx) => {
|
|
14807
14831
|
let iconElement = null;
|
|
14808
14832
|
if (action.icon) {
|
|
@@ -14810,7 +14834,9 @@ function ActionMenu({ actions, item, }) {
|
|
|
14810
14834
|
iconElement = action.icon;
|
|
14811
14835
|
}
|
|
14812
14836
|
else {
|
|
14813
|
-
iconElement = React__default.createElement(action.icon, {
|
|
14837
|
+
iconElement = React__default.createElement(action.icon, {
|
|
14838
|
+
className: "h-4 w-4 flex-shrink-0",
|
|
14839
|
+
});
|
|
14814
14840
|
}
|
|
14815
14841
|
}
|
|
14816
14842
|
return (jsxs("button", { type: "button", onClick: async (e) => {
|
|
@@ -14820,12 +14846,12 @@ function ActionMenu({ actions, item, }) {
|
|
|
14820
14846
|
await action.onClick(item);
|
|
14821
14847
|
}
|
|
14822
14848
|
catch (error) {
|
|
14823
|
-
console.error(
|
|
14849
|
+
console.error("DataTable action error:", error);
|
|
14824
14850
|
}
|
|
14825
14851
|
setIsOpen(false);
|
|
14826
|
-
}, className: `w-full flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${action.variant ===
|
|
14827
|
-
?
|
|
14828
|
-
:
|
|
14852
|
+
}, className: `w-full flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${action.variant === "danger"
|
|
14853
|
+
? "text-error-600 hover:bg-error-50 hover:text-error-700"
|
|
14854
|
+
: "text-ink-700 hover:bg-paper-50"}`, title: action.tooltip, children: [iconElement, jsx("span", { className: "flex-1 text-left", children: action.label })] }, idx));
|
|
14829
14855
|
}) }));
|
|
14830
14856
|
return (jsxs(Fragment, { children: [jsx("button", { ref: buttonRef, onClick: (e) => {
|
|
14831
14857
|
e.stopPropagation();
|
|
@@ -14842,13 +14868,20 @@ function getColumnStyle(column, dynamicWidth) {
|
|
|
14842
14868
|
style.width = `${dynamicWidth}px`;
|
|
14843
14869
|
}
|
|
14844
14870
|
else if (column.width !== undefined) {
|
|
14845
|
-
style.width =
|
|
14871
|
+
style.width =
|
|
14872
|
+
typeof column.width === "number" ? `${column.width}px` : column.width;
|
|
14846
14873
|
}
|
|
14847
14874
|
if (column.minWidth !== undefined) {
|
|
14848
|
-
style.minWidth =
|
|
14875
|
+
style.minWidth =
|
|
14876
|
+
typeof column.minWidth === "number"
|
|
14877
|
+
? `${column.minWidth}px`
|
|
14878
|
+
: column.minWidth;
|
|
14849
14879
|
}
|
|
14850
14880
|
if (column.maxWidth !== undefined) {
|
|
14851
|
-
style.maxWidth =
|
|
14881
|
+
style.maxWidth =
|
|
14882
|
+
typeof column.maxWidth === "number"
|
|
14883
|
+
? `${column.maxWidth}px`
|
|
14884
|
+
: column.maxWidth;
|
|
14852
14885
|
}
|
|
14853
14886
|
if (column.flex !== undefined) {
|
|
14854
14887
|
style.flexGrow = column.flex;
|
|
@@ -14907,13 +14940,13 @@ function getColumnStyle(column, dynamicWidth) {
|
|
|
14907
14940
|
* />
|
|
14908
14941
|
* ```
|
|
14909
14942
|
*/
|
|
14910
|
-
function DataTable({ data, columns, loading = false, error = null, emptyMessage =
|
|
14943
|
+
function DataTable({ data, columns, loading = false, error = null, emptyMessage = "No data available", loadingRows = 5, className = "", onSortChange, currentSort = null, onEdit, onDelete, actions = [], enableContextMenu = true, onRowClick, onRowDoubleClick, selectable = false, selectedRows: externalSelectedRows, onRowSelect, keyExtractor, expandable = false, expandedRows: externalExpandedRows, renderExpandedRow, expandedRowConfig, showExpandChevron = false,
|
|
14911
14944
|
// Visual customization props
|
|
14912
|
-
striped = false, stripedColor, density =
|
|
14945
|
+
striped = false, stripedColor, density = "normal", rowClassName, rowHighlight, highlightedRowId, highlightedRows = [], highlightDuration = 2000, bordered = false, borderColor = "border-paper-200", disableHover = false, hiddenColumns = [], headerClassName = "", renderEmptyState: customRenderEmptyState, resizable = false, onColumnResize, reorderable = false, onColumnReorder, virtualized = false, virtualHeight = "600px", virtualRowHeight = 60,
|
|
14913
14946
|
// Pagination props
|
|
14914
14947
|
paginated = false, currentPage = 1, pageSize = 10, totalItems, onPageChange, pageSizeOptions = [10, 25, 50, 100], onPageSizeChange, showPageSizeSelector = true,
|
|
14915
14948
|
// Mobile view props
|
|
14916
|
-
mobileView =
|
|
14949
|
+
mobileView = "auto", cardConfig, cardGap = "md", cardClassName, }) {
|
|
14917
14950
|
// Mobile detection for auto mode
|
|
14918
14951
|
const isMobileViewport = useIsMobile();
|
|
14919
14952
|
// Column resizing state
|
|
@@ -14932,7 +14965,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14932
14965
|
const [hoveredRowKey, setHoveredRowKey] = useState(null);
|
|
14933
14966
|
// Keyboard navigation state
|
|
14934
14967
|
const [focusedCell, setFocusedCell] = useState(null);
|
|
14935
|
-
const [announcement, setAnnouncement] = useState(
|
|
14968
|
+
const [announcement, setAnnouncement] = useState("");
|
|
14936
14969
|
const tableBodyRef = useRef(null);
|
|
14937
14970
|
// Temporary row highlight state (for flash animation)
|
|
14938
14971
|
const [flashingRows, setFlashingRows] = useState(new Set());
|
|
@@ -14944,18 +14977,18 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14944
14977
|
item: null,
|
|
14945
14978
|
});
|
|
14946
14979
|
// Filter columns based on hiddenColumns
|
|
14947
|
-
const baseVisibleColumns = columns.filter(col => !hiddenColumns.includes(String(col.key)));
|
|
14980
|
+
const baseVisibleColumns = columns.filter((col) => !hiddenColumns.includes(String(col.key)));
|
|
14948
14981
|
// Initialize column order on mount or when columns change
|
|
14949
14982
|
useEffect(() => {
|
|
14950
14983
|
if (columnOrder.length === 0) {
|
|
14951
|
-
setColumnOrder(baseVisibleColumns.map(col => String(col.key)));
|
|
14984
|
+
setColumnOrder(baseVisibleColumns.map((col) => String(col.key)));
|
|
14952
14985
|
}
|
|
14953
14986
|
}, [baseVisibleColumns, columnOrder.length]);
|
|
14954
14987
|
// Handle temporary row highlighting (flash animation)
|
|
14955
14988
|
useEffect(() => {
|
|
14956
14989
|
if (highlightedRows.length > 0) {
|
|
14957
14990
|
// Add new highlighted rows to flashing set
|
|
14958
|
-
const newFlashingRows = new Set(highlightedRows.map(id => String(id)));
|
|
14991
|
+
const newFlashingRows = new Set(highlightedRows.map((id) => String(id)));
|
|
14959
14992
|
setFlashingRows(newFlashingRows);
|
|
14960
14993
|
// Clear any existing timeout
|
|
14961
14994
|
if (flashTimeoutRef.current) {
|
|
@@ -14975,25 +15008,25 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14975
15008
|
// Apply column order
|
|
14976
15009
|
const visibleColumns = reorderable && columnOrder.length > 0
|
|
14977
15010
|
? columnOrder
|
|
14978
|
-
.map(key => baseVisibleColumns.find(col => String(col.key) === key))
|
|
15011
|
+
.map((key) => baseVisibleColumns.find((col) => String(col.key) === key))
|
|
14979
15012
|
.filter((col) => col !== undefined)
|
|
14980
15013
|
: baseVisibleColumns;
|
|
14981
15014
|
// Density classes
|
|
14982
15015
|
const densityClasses = {
|
|
14983
15016
|
compact: {
|
|
14984
|
-
cell:
|
|
14985
|
-
text:
|
|
14986
|
-
header:
|
|
15017
|
+
cell: "px-3 py-1",
|
|
15018
|
+
text: "text-xs",
|
|
15019
|
+
header: "px-3 py-2",
|
|
14987
15020
|
},
|
|
14988
15021
|
normal: {
|
|
14989
|
-
cell:
|
|
14990
|
-
text:
|
|
14991
|
-
header:
|
|
15022
|
+
cell: "px-6 py-1.5",
|
|
15023
|
+
text: "text-sm",
|
|
15024
|
+
header: "px-6 py-3",
|
|
14992
15025
|
},
|
|
14993
15026
|
comfortable: {
|
|
14994
|
-
cell:
|
|
14995
|
-
text:
|
|
14996
|
-
header:
|
|
15027
|
+
cell: "px-6 py-3",
|
|
15028
|
+
text: "text-base",
|
|
15029
|
+
header: "px-6 py-4",
|
|
14997
15030
|
},
|
|
14998
15031
|
};
|
|
14999
15032
|
const currentDensity = densityClasses[density];
|
|
@@ -15001,20 +15034,25 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15001
15034
|
const getRowKey = keyExtractor || ((row) => String(row.id));
|
|
15002
15035
|
// Calculate if there are any actions (for keyboard navigation column calculation)
|
|
15003
15036
|
// This is computed early so it can be used in keyboard handlers
|
|
15004
|
-
const hasAnyActions = !!(onEdit ||
|
|
15005
|
-
|
|
15006
|
-
|
|
15037
|
+
const hasAnyActions = !!(onEdit ||
|
|
15038
|
+
onDelete ||
|
|
15039
|
+
actions.length > 0 ||
|
|
15040
|
+
expandedRowConfig?.edit ||
|
|
15041
|
+
expandedRowConfig?.details ||
|
|
15042
|
+
expandedRowConfig?.addRelated?.length ||
|
|
15043
|
+
expandedRowConfig?.manageRelated?.length);
|
|
15007
15044
|
// Get row background class based on striping and highlighting
|
|
15008
15045
|
const getRowBackgroundClass = (item, index) => {
|
|
15009
15046
|
const classes = [];
|
|
15010
15047
|
const rowKey = getRowKey(item);
|
|
15011
15048
|
// Check for temporary flash highlight (takes priority)
|
|
15012
15049
|
if (flashingRows.has(rowKey)) {
|
|
15013
|
-
classes.push(
|
|
15050
|
+
classes.push("animate-row-flash");
|
|
15014
15051
|
}
|
|
15015
15052
|
// Check for highlighted row
|
|
15016
|
-
else if (highlightedRowId !== undefined &&
|
|
15017
|
-
|
|
15053
|
+
else if (highlightedRowId !== undefined &&
|
|
15054
|
+
rowKey === String(highlightedRowId)) {
|
|
15055
|
+
classes.push("bg-accent-100");
|
|
15018
15056
|
}
|
|
15019
15057
|
// Check for custom row highlight
|
|
15020
15058
|
else if (rowHighlight) {
|
|
@@ -15026,24 +15064,27 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15026
15064
|
// Check for striping
|
|
15027
15065
|
else if (striped) {
|
|
15028
15066
|
const isOdd = index % 2 === 0; // 0-indexed, so even index = odd row
|
|
15029
|
-
const shouldStripe = striped === true
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15067
|
+
const shouldStripe = striped === true
|
|
15068
|
+
? isOdd
|
|
15069
|
+
: striped === "odd"
|
|
15070
|
+
? isOdd
|
|
15071
|
+
: striped === "even"
|
|
15072
|
+
? !isOdd
|
|
15073
|
+
: false;
|
|
15033
15074
|
if (shouldStripe) {
|
|
15034
|
-
classes.push(stripedColor ||
|
|
15075
|
+
classes.push(stripedColor || "bg-paper-50");
|
|
15035
15076
|
}
|
|
15036
15077
|
}
|
|
15037
15078
|
// Add custom row class
|
|
15038
15079
|
if (rowClassName) {
|
|
15039
|
-
if (typeof rowClassName ===
|
|
15080
|
+
if (typeof rowClassName === "string") {
|
|
15040
15081
|
classes.push(rowClassName);
|
|
15041
15082
|
}
|
|
15042
15083
|
else {
|
|
15043
15084
|
classes.push(rowClassName(item, index));
|
|
15044
15085
|
}
|
|
15045
15086
|
}
|
|
15046
|
-
return classes.join(
|
|
15087
|
+
return classes.join(" ");
|
|
15047
15088
|
};
|
|
15048
15089
|
// NEW: Expansion mode state management (for expandedRowConfig)
|
|
15049
15090
|
const [expansionState, setExpansionState] = useState(null);
|
|
@@ -15058,12 +15099,12 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15058
15099
|
// Column reorder handlers
|
|
15059
15100
|
const handleDragStart = (e, columnKey) => {
|
|
15060
15101
|
setDraggingColumn(columnKey);
|
|
15061
|
-
e.dataTransfer.effectAllowed =
|
|
15062
|
-
e.dataTransfer.setData(
|
|
15102
|
+
e.dataTransfer.effectAllowed = "move";
|
|
15103
|
+
e.dataTransfer.setData("text/html", columnKey);
|
|
15063
15104
|
};
|
|
15064
15105
|
const handleDragOver = (e, columnKey) => {
|
|
15065
15106
|
e.preventDefault();
|
|
15066
|
-
e.dataTransfer.dropEffect =
|
|
15107
|
+
e.dataTransfer.dropEffect = "move";
|
|
15067
15108
|
if (draggingColumn && draggingColumn !== columnKey) {
|
|
15068
15109
|
setDragOverColumn(columnKey);
|
|
15069
15110
|
}
|
|
@@ -15094,7 +15135,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15094
15135
|
const handleMouseMove = (e) => {
|
|
15095
15136
|
const delta = e.clientX - resizeStartX;
|
|
15096
15137
|
const newWidth = Math.max(50, resizeStartWidth + delta); // Min width 50px
|
|
15097
|
-
setColumnWidths(prev => ({
|
|
15138
|
+
setColumnWidths((prev) => ({
|
|
15098
15139
|
...prev,
|
|
15099
15140
|
[resizingColumn]: newWidth,
|
|
15100
15141
|
}));
|
|
@@ -15105,56 +15146,62 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15105
15146
|
}
|
|
15106
15147
|
setResizingColumn(null);
|
|
15107
15148
|
};
|
|
15108
|
-
document.addEventListener(
|
|
15109
|
-
document.addEventListener(
|
|
15149
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
15150
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
15110
15151
|
return () => {
|
|
15111
|
-
document.removeEventListener(
|
|
15112
|
-
document.removeEventListener(
|
|
15152
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
15153
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
15113
15154
|
};
|
|
15114
|
-
}, [
|
|
15155
|
+
}, [
|
|
15156
|
+
resizingColumn,
|
|
15157
|
+
resizeStartX,
|
|
15158
|
+
resizeStartWidth,
|
|
15159
|
+
columnWidths,
|
|
15160
|
+
onColumnResize,
|
|
15161
|
+
]);
|
|
15115
15162
|
// Build combined actions: built-in edit/delete + custom actions + expansion mode actions
|
|
15116
15163
|
const builtInActions = [];
|
|
15117
15164
|
// Legacy onEdit (still supported)
|
|
15118
15165
|
if (onEdit) {
|
|
15119
15166
|
builtInActions.push({
|
|
15120
|
-
label:
|
|
15167
|
+
label: "Edit",
|
|
15121
15168
|
icon: Edit,
|
|
15122
15169
|
onClick: onEdit,
|
|
15123
|
-
variant:
|
|
15124
|
-
tooltip:
|
|
15170
|
+
variant: "secondary",
|
|
15171
|
+
tooltip: "Edit item",
|
|
15125
15172
|
});
|
|
15126
15173
|
}
|
|
15127
15174
|
// NEW: Edit mode from expandedRowConfig
|
|
15128
15175
|
if (expandedRowConfig?.edit && !onEdit) {
|
|
15129
15176
|
const editConfig = expandedRowConfig.edit;
|
|
15130
15177
|
builtInActions.push({
|
|
15131
|
-
label: editConfig.menuLabel ||
|
|
15178
|
+
label: editConfig.menuLabel || "Edit",
|
|
15132
15179
|
icon: editConfig.menuIcon || Edit,
|
|
15133
15180
|
onClick: (item) => {
|
|
15134
15181
|
const rowKey = getRowKey(item);
|
|
15135
|
-
handleExpansionWithMode(rowKey,
|
|
15182
|
+
handleExpansionWithMode(rowKey, "edit");
|
|
15136
15183
|
},
|
|
15137
|
-
variant:
|
|
15138
|
-
tooltip:
|
|
15184
|
+
variant: "secondary",
|
|
15185
|
+
tooltip: "Edit inline",
|
|
15139
15186
|
});
|
|
15140
15187
|
}
|
|
15141
15188
|
// NEW: View details mode from expandedRowConfig
|
|
15142
15189
|
if (expandedRowConfig?.details) {
|
|
15143
15190
|
const detailsConfig = expandedRowConfig.details;
|
|
15144
15191
|
builtInActions.push({
|
|
15145
|
-
label: detailsConfig.menuLabel ||
|
|
15192
|
+
label: detailsConfig.menuLabel || "View Details",
|
|
15146
15193
|
icon: detailsConfig.menuIcon,
|
|
15147
15194
|
onClick: (item) => {
|
|
15148
15195
|
const rowKey = getRowKey(item);
|
|
15149
|
-
handleExpansionWithMode(rowKey,
|
|
15196
|
+
handleExpansionWithMode(rowKey, "details");
|
|
15150
15197
|
},
|
|
15151
|
-
variant:
|
|
15152
|
-
tooltip:
|
|
15198
|
+
variant: "ghost",
|
|
15199
|
+
tooltip: "View details",
|
|
15153
15200
|
});
|
|
15154
15201
|
}
|
|
15155
15202
|
// NEW: Add related modes from expandedRowConfig
|
|
15156
15203
|
if (expandedRowConfig?.addRelated) {
|
|
15157
|
-
expandedRowConfig.addRelated.forEach(config => {
|
|
15204
|
+
expandedRowConfig.addRelated.forEach((config) => {
|
|
15158
15205
|
if (config.showInMenu !== false) {
|
|
15159
15206
|
builtInActions.push({
|
|
15160
15207
|
label: config.label,
|
|
@@ -15163,15 +15210,15 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15163
15210
|
const rowKey = getRowKey(item);
|
|
15164
15211
|
handleExpansionWithMode(rowKey, `addRelated-${config.key}`);
|
|
15165
15212
|
},
|
|
15166
|
-
variant:
|
|
15167
|
-
tooltip: config.label
|
|
15213
|
+
variant: "secondary",
|
|
15214
|
+
tooltip: config.label,
|
|
15168
15215
|
});
|
|
15169
15216
|
}
|
|
15170
15217
|
});
|
|
15171
15218
|
}
|
|
15172
15219
|
// NEW: Manage related modes from expandedRowConfig
|
|
15173
15220
|
if (expandedRowConfig?.manageRelated) {
|
|
15174
|
-
expandedRowConfig.manageRelated.forEach(config => {
|
|
15221
|
+
expandedRowConfig.manageRelated.forEach((config) => {
|
|
15175
15222
|
if (config.showInMenu !== false) {
|
|
15176
15223
|
builtInActions.push({
|
|
15177
15224
|
label: config.label,
|
|
@@ -15180,8 +15227,8 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15180
15227
|
const rowKey = getRowKey(item);
|
|
15181
15228
|
handleExpansionWithMode(rowKey, `manageRelated-${config.key}`);
|
|
15182
15229
|
},
|
|
15183
|
-
variant:
|
|
15184
|
-
tooltip: config.label
|
|
15230
|
+
variant: "ghost",
|
|
15231
|
+
tooltip: config.label,
|
|
15185
15232
|
});
|
|
15186
15233
|
}
|
|
15187
15234
|
});
|
|
@@ -15191,11 +15238,11 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15191
15238
|
let deleteAction = null;
|
|
15192
15239
|
if (onDelete) {
|
|
15193
15240
|
deleteAction = {
|
|
15194
|
-
label:
|
|
15241
|
+
label: "Delete",
|
|
15195
15242
|
icon: Trash,
|
|
15196
15243
|
onClick: onDelete,
|
|
15197
|
-
variant:
|
|
15198
|
-
tooltip:
|
|
15244
|
+
variant: "danger",
|
|
15245
|
+
tooltip: "Delete item",
|
|
15199
15246
|
};
|
|
15200
15247
|
}
|
|
15201
15248
|
// Build final actions array with consistent ordering:
|
|
@@ -15208,11 +15255,11 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15208
15255
|
const allActions = [
|
|
15209
15256
|
...builtInActions,
|
|
15210
15257
|
...actions,
|
|
15211
|
-
...(deleteAction ? [deleteAction] : [])
|
|
15258
|
+
...(deleteAction ? [deleteAction] : []),
|
|
15212
15259
|
];
|
|
15213
15260
|
// Convert actions to menu items for context menu
|
|
15214
15261
|
const convertActionsToMenuItems = (item) => {
|
|
15215
|
-
const visibleActions = allActions.filter(action => !action.show || action.show(item));
|
|
15262
|
+
const visibleActions = allActions.filter((action) => !action.show || action.show(item));
|
|
15216
15263
|
return visibleActions.map((action, idx) => {
|
|
15217
15264
|
let iconElement = null;
|
|
15218
15265
|
if (action.icon) {
|
|
@@ -15220,7 +15267,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15220
15267
|
iconElement = action.icon;
|
|
15221
15268
|
}
|
|
15222
15269
|
else {
|
|
15223
|
-
iconElement = React__default.createElement(action.icon, {
|
|
15270
|
+
iconElement = React__default.createElement(action.icon, {
|
|
15271
|
+
className: "h-4 w-4",
|
|
15272
|
+
});
|
|
15224
15273
|
}
|
|
15225
15274
|
}
|
|
15226
15275
|
return {
|
|
@@ -15228,7 +15277,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15228
15277
|
label: action.label,
|
|
15229
15278
|
icon: iconElement,
|
|
15230
15279
|
onClick: () => action.onClick(item),
|
|
15231
|
-
danger: action.variant ===
|
|
15280
|
+
danger: action.variant === "danger",
|
|
15232
15281
|
};
|
|
15233
15282
|
});
|
|
15234
15283
|
};
|
|
@@ -15237,7 +15286,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15237
15286
|
// Expansion state management
|
|
15238
15287
|
const [internalExpandedRows, setInternalExpandedRows] = useState(new Set());
|
|
15239
15288
|
// Use external selection if provided, otherwise internal
|
|
15240
|
-
const selectedRowsSet = externalSelectedRows !== undefined
|
|
15289
|
+
const selectedRowsSet = externalSelectedRows !== undefined
|
|
15290
|
+
? externalSelectedRows
|
|
15291
|
+
: internalSelectedRows;
|
|
15241
15292
|
const setSelectedRows = (newSet) => {
|
|
15242
15293
|
if (externalSelectedRows !== undefined) {
|
|
15243
15294
|
// Controlled component - notify parent
|
|
@@ -15271,7 +15322,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15271
15322
|
}
|
|
15272
15323
|
};
|
|
15273
15324
|
// Use external expansion if provided, otherwise internal
|
|
15274
|
-
const expandedRowsSet = externalExpandedRows !== undefined
|
|
15325
|
+
const expandedRowsSet = externalExpandedRows !== undefined
|
|
15326
|
+
? externalExpandedRows
|
|
15327
|
+
: internalExpandedRows;
|
|
15275
15328
|
const setExpandedRows = (newSet) => {
|
|
15276
15329
|
if (externalExpandedRows !== undefined) ;
|
|
15277
15330
|
else {
|
|
@@ -15313,10 +15366,10 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15313
15366
|
const totalCols = visibleColumns.length;
|
|
15314
15367
|
// If no cell is focused, focus first data cell on first arrow key
|
|
15315
15368
|
if (!focusedCell) {
|
|
15316
|
-
if ([
|
|
15369
|
+
if (["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight"].includes(e.key)) {
|
|
15317
15370
|
e.preventDefault();
|
|
15318
15371
|
setFocusedCell({ row: 0, col: 0 });
|
|
15319
|
-
const colHeader = visibleColumns[0]?.header ||
|
|
15372
|
+
const colHeader = visibleColumns[0]?.header || "first column";
|
|
15320
15373
|
setAnnouncement(`Row 1, ${colHeader}`);
|
|
15321
15374
|
return;
|
|
15322
15375
|
}
|
|
@@ -15324,73 +15377,73 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15324
15377
|
}
|
|
15325
15378
|
const { row, col } = focusedCell;
|
|
15326
15379
|
switch (e.key) {
|
|
15327
|
-
case
|
|
15380
|
+
case "ArrowDown":
|
|
15328
15381
|
e.preventDefault();
|
|
15329
15382
|
if (row < totalRows - 1) {
|
|
15330
15383
|
const newRow = row + 1;
|
|
15331
15384
|
setFocusedCell({ row: newRow, col });
|
|
15332
15385
|
const rowItem = data[newRow];
|
|
15333
|
-
const colHeader = visibleColumns[col]?.header ||
|
|
15386
|
+
const colHeader = visibleColumns[col]?.header || "";
|
|
15334
15387
|
const cellValue = rowItem[visibleColumns[col]?.key];
|
|
15335
|
-
setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue ||
|
|
15388
|
+
setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || "empty"}`);
|
|
15336
15389
|
}
|
|
15337
15390
|
break;
|
|
15338
|
-
case
|
|
15391
|
+
case "ArrowUp":
|
|
15339
15392
|
e.preventDefault();
|
|
15340
15393
|
if (row > 0) {
|
|
15341
15394
|
const newRow = row - 1;
|
|
15342
15395
|
setFocusedCell({ row: newRow, col });
|
|
15343
15396
|
const rowItem = data[newRow];
|
|
15344
|
-
const colHeader = visibleColumns[col]?.header ||
|
|
15397
|
+
const colHeader = visibleColumns[col]?.header || "";
|
|
15345
15398
|
const cellValue = rowItem[visibleColumns[col]?.key];
|
|
15346
|
-
setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue ||
|
|
15399
|
+
setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || "empty"}`);
|
|
15347
15400
|
}
|
|
15348
15401
|
break;
|
|
15349
|
-
case
|
|
15402
|
+
case "ArrowRight":
|
|
15350
15403
|
e.preventDefault();
|
|
15351
15404
|
if (col < totalCols - 1) {
|
|
15352
15405
|
const newCol = col + 1;
|
|
15353
15406
|
setFocusedCell({ row, col: newCol });
|
|
15354
15407
|
const rowItem = data[row];
|
|
15355
|
-
const colHeader = visibleColumns[newCol]?.header ||
|
|
15408
|
+
const colHeader = visibleColumns[newCol]?.header || "";
|
|
15356
15409
|
const cellValue = rowItem[visibleColumns[newCol]?.key];
|
|
15357
|
-
setAnnouncement(`${colHeader}: ${cellValue ||
|
|
15410
|
+
setAnnouncement(`${colHeader}: ${cellValue || "empty"}`);
|
|
15358
15411
|
}
|
|
15359
15412
|
break;
|
|
15360
|
-
case
|
|
15413
|
+
case "ArrowLeft":
|
|
15361
15414
|
e.preventDefault();
|
|
15362
15415
|
if (col > 0) {
|
|
15363
15416
|
const newCol = col - 1;
|
|
15364
15417
|
setFocusedCell({ row, col: newCol });
|
|
15365
15418
|
const rowItem = data[row];
|
|
15366
|
-
const colHeader = visibleColumns[newCol]?.header ||
|
|
15419
|
+
const colHeader = visibleColumns[newCol]?.header || "";
|
|
15367
15420
|
const cellValue = rowItem[visibleColumns[newCol]?.key];
|
|
15368
|
-
setAnnouncement(`${colHeader}: ${cellValue ||
|
|
15421
|
+
setAnnouncement(`${colHeader}: ${cellValue || "empty"}`);
|
|
15369
15422
|
}
|
|
15370
15423
|
break;
|
|
15371
|
-
case
|
|
15424
|
+
case "Home":
|
|
15372
15425
|
e.preventDefault();
|
|
15373
15426
|
if (e.ctrlKey) {
|
|
15374
15427
|
// Ctrl+Home: Go to first cell
|
|
15375
15428
|
setFocusedCell({ row: 0, col: 0 });
|
|
15376
|
-
setAnnouncement(`First cell, Row 1, ${visibleColumns[0]?.header ||
|
|
15429
|
+
setAnnouncement(`First cell, Row 1, ${visibleColumns[0]?.header || ""}`);
|
|
15377
15430
|
}
|
|
15378
15431
|
else {
|
|
15379
15432
|
// Home: Go to first cell in current row
|
|
15380
15433
|
setFocusedCell({ row, col: 0 });
|
|
15381
15434
|
const rowItem = data[row];
|
|
15382
15435
|
const cellValue = rowItem[visibleColumns[0]?.key];
|
|
15383
|
-
setAnnouncement(`${visibleColumns[0]?.header ||
|
|
15436
|
+
setAnnouncement(`${visibleColumns[0]?.header || ""}: ${cellValue || "empty"}`);
|
|
15384
15437
|
}
|
|
15385
15438
|
break;
|
|
15386
|
-
case
|
|
15439
|
+
case "End":
|
|
15387
15440
|
e.preventDefault();
|
|
15388
15441
|
if (e.ctrlKey) {
|
|
15389
15442
|
// Ctrl+End: Go to last cell
|
|
15390
15443
|
const lastRow = totalRows - 1;
|
|
15391
15444
|
const lastCol = totalCols - 1;
|
|
15392
15445
|
setFocusedCell({ row: lastRow, col: lastCol });
|
|
15393
|
-
setAnnouncement(`Last cell, Row ${lastRow + 1}, ${visibleColumns[lastCol]?.header ||
|
|
15446
|
+
setAnnouncement(`Last cell, Row ${lastRow + 1}, ${visibleColumns[lastCol]?.header || ""}`);
|
|
15394
15447
|
}
|
|
15395
15448
|
else {
|
|
15396
15449
|
// End: Go to last cell in current row
|
|
@@ -15398,10 +15451,10 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15398
15451
|
setFocusedCell({ row, col: lastCol });
|
|
15399
15452
|
const rowItem = data[row];
|
|
15400
15453
|
const cellValue = rowItem[visibleColumns[lastCol]?.key];
|
|
15401
|
-
setAnnouncement(`${visibleColumns[lastCol]?.header ||
|
|
15454
|
+
setAnnouncement(`${visibleColumns[lastCol]?.header || ""}: ${cellValue || "empty"}`);
|
|
15402
15455
|
}
|
|
15403
15456
|
break;
|
|
15404
|
-
case
|
|
15457
|
+
case "Enter":
|
|
15405
15458
|
e.preventDefault();
|
|
15406
15459
|
{
|
|
15407
15460
|
const rowItem = data[row];
|
|
@@ -15409,27 +15462,27 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15409
15462
|
// Priority: Edit mode > Details mode > Row double-click handler
|
|
15410
15463
|
if (onEdit) {
|
|
15411
15464
|
onEdit(rowItem);
|
|
15412
|
-
setAnnouncement(
|
|
15465
|
+
setAnnouncement("Opening edit mode");
|
|
15413
15466
|
}
|
|
15414
15467
|
else if (expandedRowConfig?.edit) {
|
|
15415
|
-
handleExpansionWithMode(rowKey,
|
|
15416
|
-
setAnnouncement(
|
|
15468
|
+
handleExpansionWithMode(rowKey, "edit");
|
|
15469
|
+
setAnnouncement("Opening inline edit");
|
|
15417
15470
|
}
|
|
15418
15471
|
else if (expandedRowConfig?.details) {
|
|
15419
|
-
handleExpansionWithMode(rowKey,
|
|
15420
|
-
setAnnouncement(
|
|
15472
|
+
handleExpansionWithMode(rowKey, "details");
|
|
15473
|
+
setAnnouncement("Opening details view");
|
|
15421
15474
|
}
|
|
15422
15475
|
else if (onRowDoubleClick) {
|
|
15423
15476
|
onRowDoubleClick(rowItem);
|
|
15424
|
-
setAnnouncement(
|
|
15477
|
+
setAnnouncement("Activating row");
|
|
15425
15478
|
}
|
|
15426
15479
|
else if (onRowClick) {
|
|
15427
15480
|
onRowClick(rowItem);
|
|
15428
|
-
setAnnouncement(
|
|
15481
|
+
setAnnouncement("Row selected");
|
|
15429
15482
|
}
|
|
15430
15483
|
}
|
|
15431
15484
|
break;
|
|
15432
|
-
case
|
|
15485
|
+
case " ":
|
|
15433
15486
|
// Space: Toggle selection if selectable
|
|
15434
15487
|
if (selectable) {
|
|
15435
15488
|
e.preventDefault();
|
|
@@ -15437,60 +15490,82 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15437
15490
|
const rowKey = getRowKey(rowItem);
|
|
15438
15491
|
handleRowSelect(rowKey);
|
|
15439
15492
|
const isNowSelected = !selectedRowsSet.has(rowKey);
|
|
15440
|
-
setAnnouncement(isNowSelected ?
|
|
15493
|
+
setAnnouncement(isNowSelected ? "Row selected" : "Row deselected");
|
|
15441
15494
|
}
|
|
15442
15495
|
break;
|
|
15443
|
-
case
|
|
15496
|
+
case "Escape":
|
|
15444
15497
|
e.preventDefault();
|
|
15445
15498
|
setFocusedCell(null);
|
|
15446
|
-
setAnnouncement(
|
|
15499
|
+
setAnnouncement("Table navigation exited");
|
|
15447
15500
|
// Return focus to table container
|
|
15448
|
-
tableBodyRef.current?.closest(
|
|
15501
|
+
tableBodyRef.current?.closest("table")?.focus();
|
|
15449
15502
|
break;
|
|
15450
|
-
case
|
|
15503
|
+
case "PageDown":
|
|
15451
15504
|
e.preventDefault();
|
|
15452
15505
|
{
|
|
15453
15506
|
const jumpSize = 10;
|
|
15454
15507
|
const newRow = Math.min(row + jumpSize, totalRows - 1);
|
|
15455
15508
|
setFocusedCell({ row: newRow, col });
|
|
15456
|
-
const colHeader = visibleColumns[col]?.header ||
|
|
15509
|
+
const colHeader = visibleColumns[col]?.header || "";
|
|
15457
15510
|
setAnnouncement(`Row ${newRow + 1} of ${totalRows}, ${colHeader}`);
|
|
15458
15511
|
}
|
|
15459
15512
|
break;
|
|
15460
|
-
case
|
|
15513
|
+
case "PageUp":
|
|
15461
15514
|
e.preventDefault();
|
|
15462
15515
|
{
|
|
15463
15516
|
const jumpSize = 10;
|
|
15464
15517
|
const newRow = Math.max(row - jumpSize, 0);
|
|
15465
15518
|
setFocusedCell({ row: newRow, col });
|
|
15466
|
-
const colHeader = visibleColumns[col]?.header ||
|
|
15519
|
+
const colHeader = visibleColumns[col]?.header || "";
|
|
15467
15520
|
setAnnouncement(`Row ${newRow + 1} of ${totalRows}, ${colHeader}`);
|
|
15468
15521
|
}
|
|
15469
15522
|
break;
|
|
15470
15523
|
}
|
|
15471
|
-
}, [
|
|
15524
|
+
}, [
|
|
15525
|
+
data,
|
|
15526
|
+
visibleColumns,
|
|
15527
|
+
focusedCell,
|
|
15528
|
+
selectable,
|
|
15529
|
+
expandedRowConfig,
|
|
15530
|
+
onEdit,
|
|
15531
|
+
onRowDoubleClick,
|
|
15532
|
+
onRowClick,
|
|
15533
|
+
getRowKey,
|
|
15534
|
+
handleExpansionWithMode,
|
|
15535
|
+
handleRowSelect,
|
|
15536
|
+
selectedRowsSet,
|
|
15537
|
+
]);
|
|
15472
15538
|
// Focus the appropriate cell when focusedCell changes
|
|
15473
15539
|
useEffect(() => {
|
|
15474
15540
|
if (focusedCell && tableBodyRef.current) {
|
|
15475
15541
|
const { row, col } = focusedCell;
|
|
15476
|
-
const rows = tableBodyRef.current.querySelectorAll(
|
|
15542
|
+
const rows = tableBodyRef.current.querySelectorAll("tr[data-row-index]");
|
|
15477
15543
|
const targetRow = rows[row];
|
|
15478
15544
|
if (targetRow) {
|
|
15479
15545
|
// Calculate actual column index including extra columns
|
|
15480
15546
|
const hasSelectionCol = selectable;
|
|
15481
15547
|
const hasExpandCol = (expandable || expandedRowConfig) && showExpandChevron;
|
|
15482
15548
|
const hasActionsCol = hasAnyActions;
|
|
15483
|
-
const extraColsBefore = (hasSelectionCol ? 1 : 0) +
|
|
15484
|
-
|
|
15549
|
+
const extraColsBefore = (hasSelectionCol ? 1 : 0) +
|
|
15550
|
+
(hasExpandCol ? 1 : 0) +
|
|
15551
|
+
(hasActionsCol ? 1 : 0);
|
|
15552
|
+
const cells = targetRow.querySelectorAll("td");
|
|
15485
15553
|
const targetCell = cells[col + extraColsBefore];
|
|
15486
15554
|
if (targetCell) {
|
|
15487
15555
|
targetCell.focus();
|
|
15488
15556
|
// Scroll into view if needed
|
|
15489
|
-
targetCell.scrollIntoView({ block:
|
|
15557
|
+
targetCell.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
15490
15558
|
}
|
|
15491
15559
|
}
|
|
15492
15560
|
}
|
|
15493
|
-
}, [
|
|
15561
|
+
}, [
|
|
15562
|
+
focusedCell,
|
|
15563
|
+
selectable,
|
|
15564
|
+
expandable,
|
|
15565
|
+
expandedRowConfig,
|
|
15566
|
+
showExpandChevron,
|
|
15567
|
+
hasAnyActions,
|
|
15568
|
+
]);
|
|
15494
15569
|
// Handle column header click for sorting
|
|
15495
15570
|
const handleSort = (column) => {
|
|
15496
15571
|
if (!column.sortable || !onSortChange)
|
|
@@ -15498,8 +15573,12 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15498
15573
|
const columnKey = String(column.key);
|
|
15499
15574
|
// If clicking the same column, toggle direction
|
|
15500
15575
|
if (currentSort?.key === columnKey) {
|
|
15501
|
-
if (currentSort.direction ===
|
|
15502
|
-
onSortChange({
|
|
15576
|
+
if (currentSort.direction === "asc") {
|
|
15577
|
+
onSortChange({
|
|
15578
|
+
key: columnKey,
|
|
15579
|
+
direction: "desc",
|
|
15580
|
+
label: column.header,
|
|
15581
|
+
});
|
|
15503
15582
|
}
|
|
15504
15583
|
else {
|
|
15505
15584
|
// Remove sort on third click
|
|
@@ -15508,7 +15587,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15508
15587
|
}
|
|
15509
15588
|
else {
|
|
15510
15589
|
// New column - start with ascending
|
|
15511
|
-
onSortChange({ key: columnKey, direction:
|
|
15590
|
+
onSortChange({ key: columnKey, direction: "asc", label: column.header });
|
|
15512
15591
|
}
|
|
15513
15592
|
};
|
|
15514
15593
|
// Get sort icon SVG for column (matches reference ui-preview.html)
|
|
@@ -15517,7 +15596,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15517
15596
|
return null;
|
|
15518
15597
|
const columnKey = String(column.key);
|
|
15519
15598
|
const isActive = currentSort?.key === columnKey;
|
|
15520
|
-
const isAscending = currentSort?.direction ===
|
|
15599
|
+
const isAscending = currentSort?.direction === "asc";
|
|
15521
15600
|
// Inactive state - show neutral up/down arrows
|
|
15522
15601
|
if (!isActive) {
|
|
15523
15602
|
return (jsx("svg", { className: "ml-2 w-4 h-4 text-ink-400 group-hover:text-ink-700", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" }) }));
|
|
@@ -15530,17 +15609,23 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15530
15609
|
return (jsx("svg", { className: "ml-2 w-4 h-4 text-accent-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 17l-4 4m0 0l-4-4m4 4V3" }) }));
|
|
15531
15610
|
};
|
|
15532
15611
|
// Render loading skeleton
|
|
15533
|
-
const renderLoadingSkeleton = () => (jsx(Fragment, { children: Array.from({ length: loadingRows }, (_, i) => (jsxs("tr", { className: `animate-pulse table-row-stable ${bordered ? `border-b ${borderColor}` :
|
|
15612
|
+
const renderLoadingSkeleton = () => (jsx(Fragment, { children: Array.from({ length: loadingRows }, (_, i) => (jsxs("tr", { className: `animate-pulse table-row-stable ${bordered ? `border-b ${borderColor}` : ""}`, children: [selectable && (jsx("td", { className: `sticky left-0 bg-white ${currentDensity.cell} border-b ${borderColor} z-10 align-middle`, children: jsx("div", { className: "h-4 w-4 bg-paper-200 rounded" }) })), expandable && (jsx("td", { className: `sticky left-0 bg-white px-2 ${currentDensity.cell} border-b ${borderColor} z-10`, children: jsx("div", { className: "h-4 w-4 bg-paper-200 rounded" }) })), allActions.length > 0 && (jsx("td", { className: `sticky left-0 bg-white px-2 ${currentDensity.cell} border-b ${borderColor} z-10`, children: jsx("div", { className: "h-8 w-8 bg-paper-200 rounded" }) })), visibleColumns.map((column, colIndex) => {
|
|
15534
15613
|
const columnKey = String(column.key);
|
|
15535
15614
|
const dynamicWidth = columnWidths[columnKey];
|
|
15536
|
-
return (jsxs("td", { className: `${currentDensity.cell} whitespace-nowrap table-row-stable ${bordered ? `border ${borderColor}` :
|
|
15615
|
+
return (jsxs("td", { className: `${currentDensity.cell} whitespace-nowrap table-row-stable ${bordered ? `border ${borderColor}` : ""}`, style: getColumnStyle(column, dynamicWidth), children: [jsx("div", { className: "h-4 bg-paper-200 rounded mb-1" }), jsx("div", { className: "h-3 bg-paper-200 rounded w-3/4" })] }, `loading-${i}-${colIndex}`));
|
|
15537
15616
|
})] }, `loading-${i}`))) }));
|
|
15538
15617
|
// Render empty state
|
|
15539
15618
|
const renderEmptyStateContent = () => {
|
|
15540
15619
|
if (customRenderEmptyState) {
|
|
15541
|
-
return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
|
|
15620
|
+
return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
|
|
15621
|
+
(allActions.length > 0 ? 1 : 0) +
|
|
15622
|
+
(selectable ? 1 : 0) +
|
|
15623
|
+
(expandable ? 1 : 0), children: customRenderEmptyState() }) }));
|
|
15542
15624
|
}
|
|
15543
|
-
return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
|
|
15625
|
+
return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
|
|
15626
|
+
(allActions.length > 0 ? 1 : 0) +
|
|
15627
|
+
(selectable ? 1 : 0) +
|
|
15628
|
+
(expandable ? 1 : 0), className: `${currentDensity.cell} py-8 text-center text-ink-500`, children: error || emptyMessage }) }));
|
|
15544
15629
|
};
|
|
15545
15630
|
// Virtual scrolling calculations
|
|
15546
15631
|
const getVisibleRange = () => {
|
|
@@ -15570,14 +15655,18 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15570
15655
|
const isSelected = selectedRowsSet.has(rowKey);
|
|
15571
15656
|
const isExpanded = expandedRowsSet.has(rowKey);
|
|
15572
15657
|
const rowBgClass = getRowBackgroundClass(item, index);
|
|
15573
|
-
const borderClass = bordered
|
|
15574
|
-
|
|
15658
|
+
const borderClass = bordered
|
|
15659
|
+
? `border-b ${borderColor}`
|
|
15660
|
+
: !visibleColumns.some((col) => !!col.renderSecondary)
|
|
15661
|
+
? `border-b ${borderColor}`
|
|
15662
|
+
: "";
|
|
15663
|
+
const hasSecondaryRow = visibleColumns.some((col) => !!col.renderSecondary);
|
|
15575
15664
|
// Hover state for row pair (primary + secondary)
|
|
15576
15665
|
const isHovered = hoveredRowKey === rowKey;
|
|
15577
|
-
const hoverClass = disableHover ?
|
|
15666
|
+
const hoverClass = disableHover ? "" : isHovered ? "bg-paper-100" : "";
|
|
15578
15667
|
// Check if this row is keyboard-focused
|
|
15579
15668
|
const isKeyboardFocused = focusedCell?.row === index;
|
|
15580
|
-
return (jsxs(React__default.Fragment, { children: [jsxs("tr", { "data-row-index": index, className: `table-row-stable ${onRowDoubleClick || onRowClick || onEdit || expandedRowConfig?.edit || expandedRowConfig?.details || expandedRowConfig?.addRelated?.length || expandedRowConfig?.manageRelated?.length ?
|
|
15669
|
+
return (jsxs(React__default.Fragment, { children: [jsxs("tr", { "data-row-index": index, className: `table-row-stable ${onRowDoubleClick || onRowClick || onEdit || expandedRowConfig?.edit || expandedRowConfig?.details || expandedRowConfig?.addRelated?.length || expandedRowConfig?.manageRelated?.length ? "cursor-pointer" : ""} ${isSelected ? "bg-accent-50 border-l-2 border-accent-500" : hoverClass || rowBgClass} ${borderClass} ${isKeyboardFocused ? "ring-2 ring-inset ring-accent-400" : ""}`, onMouseEnter: () => !disableHover && setHoveredRowKey(rowKey), onMouseLeave: () => !disableHover && setHoveredRowKey(null), onClick: () => onRowClick?.(item), onContextMenu: (e) => {
|
|
15581
15670
|
if (enableContextMenu && allActions.length > 0) {
|
|
15582
15671
|
e.preventDefault();
|
|
15583
15672
|
e.stopPropagation();
|
|
@@ -15596,109 +15685,146 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15596
15685
|
}
|
|
15597
15686
|
// Priority 2: If there's an expandable edit mode, trigger it
|
|
15598
15687
|
else if (expandedRowConfig?.edit) {
|
|
15599
|
-
handleExpansionWithMode(rowKey,
|
|
15688
|
+
handleExpansionWithMode(rowKey, "edit");
|
|
15600
15689
|
}
|
|
15601
15690
|
// Priority 3: If there's an expandable details mode, trigger it
|
|
15602
15691
|
else if (expandedRowConfig?.details) {
|
|
15603
|
-
handleExpansionWithMode(rowKey,
|
|
15692
|
+
handleExpansionWithMode(rowKey, "details");
|
|
15604
15693
|
}
|
|
15605
15694
|
// Priority 4: If there's any addRelated mode, trigger the first one
|
|
15606
|
-
else if (expandedRowConfig?.addRelated &&
|
|
15695
|
+
else if (expandedRowConfig?.addRelated &&
|
|
15696
|
+
expandedRowConfig.addRelated.length > 0) {
|
|
15607
15697
|
handleExpansionWithMode(rowKey, `addRelated-${expandedRowConfig.addRelated[0].key}`);
|
|
15608
15698
|
}
|
|
15609
15699
|
// Priority 5: If there's any manageRelated mode, trigger the first one
|
|
15610
|
-
else if (expandedRowConfig?.manageRelated &&
|
|
15700
|
+
else if (expandedRowConfig?.manageRelated &&
|
|
15701
|
+
expandedRowConfig.manageRelated.length > 0) {
|
|
15611
15702
|
handleExpansionWithMode(rowKey, `manageRelated-${expandedRowConfig.manageRelated[0].key}`);
|
|
15612
15703
|
}
|
|
15613
15704
|
// Priority 6: Legacy onRowDoubleClick handler
|
|
15614
15705
|
else {
|
|
15615
15706
|
onRowDoubleClick?.(item);
|
|
15616
15707
|
}
|
|
15617
|
-
}, title: onEdit
|
|
15618
|
-
|
|
15619
|
-
|
|
15620
|
-
|
|
15621
|
-
|
|
15622
|
-
|
|
15623
|
-
|
|
15624
|
-
|
|
15625
|
-
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
15629
|
-
|
|
15708
|
+
}, title: onEdit
|
|
15709
|
+
? "Double-click to edit"
|
|
15710
|
+
: expandedRowConfig?.edit
|
|
15711
|
+
? "Double-click to edit inline"
|
|
15712
|
+
: expandedRowConfig?.details
|
|
15713
|
+
? "Double-click to view details"
|
|
15714
|
+
: expandedRowConfig?.addRelated &&
|
|
15715
|
+
expandedRowConfig.addRelated.length > 0
|
|
15716
|
+
? `Double-click to ${expandedRowConfig.addRelated[0].label}`
|
|
15717
|
+
: expandedRowConfig?.manageRelated &&
|
|
15718
|
+
expandedRowConfig.manageRelated.length > 0
|
|
15719
|
+
? `Double-click to ${expandedRowConfig.manageRelated[0].label}`
|
|
15720
|
+
: onRowDoubleClick
|
|
15721
|
+
? "Double-click for details"
|
|
15722
|
+
: onRowClick
|
|
15723
|
+
? "Click to select"
|
|
15724
|
+
: undefined, children: [selectable && (jsx("td", { className: `sticky left-0 z-10 ${bordered ? `border ${borderColor}` : ""}`, style: {
|
|
15725
|
+
backgroundColor: "inherit",
|
|
15726
|
+
verticalAlign: "middle",
|
|
15727
|
+
padding: "0.375rem 0.75rem",
|
|
15728
|
+
textAlign: "center",
|
|
15729
|
+
}, rowSpan: hasSecondaryRow ? 2 : 1, children: jsx("input", { type: "checkbox", checked: isSelected, onChange: () => handleRowSelect(rowKey), className: "w-4 h-4 text-accent-600 border-paper-300 rounded focus:ring-accent-400", "aria-label": `Select row ${rowKey}` }) })), (expandable || expandedRowConfig) && showExpandChevron && (jsx("td", { className: `sticky left-0 px-2 ${currentDensity.cell} z-10 ${bordered ? `border ${borderColor}` : ""}`, style: { backgroundColor: "inherit", verticalAlign: "middle" }, rowSpan: hasSecondaryRow ? 2 : 1, children: jsx("button", { onClick: () => {
|
|
15630
15730
|
// NEW: Enhanced logic for expandedRowConfig
|
|
15631
|
-
if (expandedRowConfig?.details &&
|
|
15731
|
+
if (expandedRowConfig?.details &&
|
|
15732
|
+
expandedRowConfig.details.triggerOnExpand !== false) {
|
|
15632
15733
|
// Trigger details mode if configured
|
|
15633
|
-
handleExpansionWithMode(rowKey,
|
|
15734
|
+
handleExpansionWithMode(rowKey, "details");
|
|
15634
15735
|
}
|
|
15635
|
-
else if (expandedRowConfig?.edit &&
|
|
15736
|
+
else if (expandedRowConfig?.edit &&
|
|
15737
|
+
expandedRowConfig.edit.triggerOnDoubleClick !== false) {
|
|
15636
15738
|
// Fallback to edit mode if no details but edit is available
|
|
15637
|
-
handleExpansionWithMode(rowKey,
|
|
15739
|
+
handleExpansionWithMode(rowKey, "edit");
|
|
15638
15740
|
}
|
|
15639
15741
|
else {
|
|
15640
15742
|
// Legacy: use handleRowExpand
|
|
15641
15743
|
handleRowExpand(rowKey);
|
|
15642
15744
|
}
|
|
15643
|
-
}, className: "text-ink-500 hover:text-ink-900 transition-colors", "aria-label": isExpanded ||
|
|
15644
|
-
|
|
15645
|
-
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
15745
|
+
}, className: "text-ink-500 hover:text-ink-900 transition-colors", "aria-label": isExpanded || expansionState?.rowKey === rowKey
|
|
15746
|
+
? "Collapse row"
|
|
15747
|
+
: "Expand row", children: isExpanded || expansionState?.rowKey === rowKey ? (jsx(ChevronDown, { className: "h-4 w-4" })) : (jsx(ChevronRight, { className: "h-4 w-4" })) }) })), allActions.length > 0 && (jsx("td", { className: "sticky left-0 whitespace-nowrap shadow-[4px_0_6px_-2px_rgba(0,0,0,0.1)] z-10", style: {
|
|
15748
|
+
width: "28px",
|
|
15749
|
+
padding: "0",
|
|
15750
|
+
backgroundColor: "inherit",
|
|
15751
|
+
verticalAlign: "middle",
|
|
15752
|
+
}, onClick: (e) => e.stopPropagation(), rowSpan: hasSecondaryRow ? 2 : 1, children: jsx("div", { style: {
|
|
15753
|
+
display: "inline-flex",
|
|
15754
|
+
alignItems: "center",
|
|
15755
|
+
justifyContent: "center",
|
|
15756
|
+
width: "28px",
|
|
15757
|
+
}, children: jsx(ActionMenu, { actions: allActions, item: item }) }) })), visibleColumns.map((column, colIdx) => {
|
|
15649
15758
|
const columnKey = String(column.key);
|
|
15650
15759
|
const dynamicWidth = columnWidths[columnKey];
|
|
15651
|
-
const value = typeof column.key ===
|
|
15760
|
+
const value = typeof column.key === "string"
|
|
15652
15761
|
? item[column.key]
|
|
15653
15762
|
: item[column.key];
|
|
15654
|
-
const primaryContent = column.render
|
|
15763
|
+
const primaryContent = column.render
|
|
15764
|
+
? column.render(item, value)
|
|
15765
|
+
: String(value || "");
|
|
15766
|
+
// Tooltip: caller-provided > raw value stringified. Empty
|
|
15767
|
+
// strings get dropped so we don't render an empty title
|
|
15768
|
+
// attribute (which the browser would still show as a
|
|
15769
|
+
// 0-width tooltip box on hover).
|
|
15770
|
+
const primaryTooltipText = column.tooltip
|
|
15771
|
+
? column.tooltip(item, value)
|
|
15772
|
+
: value !== null && value !== undefined && value !== ""
|
|
15773
|
+
? String(value)
|
|
15774
|
+
: undefined;
|
|
15655
15775
|
// Reduce left padding on first column when there are action buttons
|
|
15656
15776
|
const isFirstColumn = colIdx === 0;
|
|
15657
|
-
const paddingClass = isFirstColumn && allActions.length > 0 ?
|
|
15777
|
+
const paddingClass = isFirstColumn && allActions.length > 0 ? "pl-3" : "";
|
|
15658
15778
|
// Check if this cell is keyboard-focused
|
|
15659
15779
|
const isCellFocused = focusedCell?.row === index && focusedCell?.col === colIdx;
|
|
15660
|
-
return (jsx("td", { className: `${currentDensity.cell} ${paddingClass} ${column.className ||
|
|
15661
|
-
})] }), hasSecondaryRow && (jsx("tr", { className: `secondary-row ${isSelected ?
|
|
15780
|
+
return (jsx("td", { className: `${currentDensity.cell} ${paddingClass} ${column.className || ""} ${bordered ? `border ${borderColor}` : ""} ${isCellFocused ? "outline outline-2 outline-accent-500 outline-offset-[-2px]" : ""}`, style: getColumnStyle(column, dynamicWidth), tabIndex: isCellFocused ? 0 : -1, role: "gridcell", "aria-colindex": colIdx + 1, children: jsx("div", { className: `${currentDensity.text} leading-tight`, title: primaryTooltipText, children: primaryContent }) }, `${item.id}-${columnKey}`));
|
|
15781
|
+
})] }), hasSecondaryRow && (jsx("tr", { className: `secondary-row ${isSelected ? "bg-accent-50 border-l-2 border-accent-500" : hoverClass || rowBgClass} border-b ${borderColor}`, onMouseEnter: () => !disableHover && setHoveredRowKey(rowKey), onMouseLeave: () => !disableHover && setHoveredRowKey(null), children: visibleColumns.map((column, colIdx) => {
|
|
15662
15782
|
const columnKey = String(column.key);
|
|
15663
15783
|
const dynamicWidth = columnWidths[columnKey];
|
|
15664
|
-
const value = typeof column.key ===
|
|
15784
|
+
const value = typeof column.key === "string"
|
|
15665
15785
|
? item[column.key]
|
|
15666
15786
|
: item[column.key];
|
|
15667
|
-
const secondaryContent = column.renderSecondary
|
|
15787
|
+
const secondaryContent = column.renderSecondary
|
|
15788
|
+
? column.renderSecondary(item, value)
|
|
15789
|
+
: null;
|
|
15790
|
+
// Tooltip on the secondary row prefixes the field label when
|
|
15791
|
+
// available so the otherwise-unlabeled second row stays
|
|
15792
|
+
// self-describing on hover. Caller can override entirely
|
|
15793
|
+
// via `secondaryTooltip`.
|
|
15794
|
+
const hasSecondaryValue = value !== null && value !== undefined && value !== "";
|
|
15795
|
+
let secondaryTooltipText;
|
|
15796
|
+
if (column.secondaryTooltip) {
|
|
15797
|
+
secondaryTooltipText = column.secondaryTooltip(item, value);
|
|
15798
|
+
}
|
|
15799
|
+
else if (hasSecondaryValue) {
|
|
15800
|
+
secondaryTooltipText = column.secondaryHeader
|
|
15801
|
+
? `${column.secondaryHeader}: ${value}`
|
|
15802
|
+
: String(value);
|
|
15803
|
+
}
|
|
15804
|
+
else if (column.secondaryHeader) {
|
|
15805
|
+
secondaryTooltipText = column.secondaryHeader;
|
|
15806
|
+
}
|
|
15668
15807
|
// Reduce left padding on first column when there are action buttons
|
|
15669
15808
|
const isFirstColumn = colIdx === 0;
|
|
15670
|
-
const paddingClass = isFirstColumn && allActions.length > 0 ?
|
|
15671
|
-
return (jsx("td", { className: `${currentDensity.cell} py-0.5 ${paddingClass} ${column.className ||
|
|
15809
|
+
const paddingClass = isFirstColumn && allActions.length > 0 ? "pl-3" : "";
|
|
15810
|
+
return (jsx("td", { className: `${currentDensity.cell} py-0.5 ${paddingClass} ${column.className || ""} ${bordered ? `border ${borderColor}` : ""}`, style: getColumnStyle(column, dynamicWidth), children: jsx("div", { className: "text-xs text-ink-500 leading-tight", title: secondaryTooltipText, children: secondaryContent || jsx("span", { className: "invisible", children: "\u2014" }) }) }, `${item.id}-${columnKey}-secondary`));
|
|
15672
15811
|
}) })), expandable && isExpanded && renderExpandedRow && (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
|
|
15673
15812
|
(selectable ? 1 : 0) +
|
|
15674
|
-
((
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
content =
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
}
|
|
15690
|
-
// Details mode
|
|
15691
|
-
else if (mode === 'details' && expandedRowConfig.details) {
|
|
15692
|
-
bgColorClass = 'bg-primary-50/80 border-t border-b border-primary-200/80';
|
|
15693
|
-
content = expandedRowConfig.details.render(item);
|
|
15694
|
-
}
|
|
15695
|
-
// Add related modes
|
|
15696
|
-
else if (mode.startsWith('addRelated-') && expandedRowConfig.addRelated) {
|
|
15697
|
-
const key = mode.replace('addRelated-', '');
|
|
15698
|
-
const config = expandedRowConfig.addRelated.find(c => c.key === key);
|
|
15699
|
-
if (config) {
|
|
15700
|
-
bgColorClass = 'bg-success-50/80 border-t border-b border-success-200/80';
|
|
15701
|
-
content = config.render(item, async (_newItem) => {
|
|
15813
|
+
((expandable || expandedRowConfig) && showExpandChevron
|
|
15814
|
+
? 1
|
|
15815
|
+
: 0) +
|
|
15816
|
+
(allActions.length > 0 ? 1 : 0), className: `${currentDensity.cell} py-4 bg-paper-50`, children: renderExpandedRow(item) }) })), expansionState &&
|
|
15817
|
+
expansionState.rowKey === rowKey &&
|
|
15818
|
+
expandedRowConfig &&
|
|
15819
|
+
(() => {
|
|
15820
|
+
const mode = expansionState.mode;
|
|
15821
|
+
let content = null;
|
|
15822
|
+
let bgColorClass = "bg-paper-50"; // Default
|
|
15823
|
+
// Edit mode
|
|
15824
|
+
if (mode === "edit" && expandedRowConfig.edit) {
|
|
15825
|
+
bgColorClass =
|
|
15826
|
+
"bg-paper-100/80 border-t border-b border-paper-300/80";
|
|
15827
|
+
content = expandedRowConfig.edit.render(item, async (_updated) => {
|
|
15702
15828
|
// Handle save
|
|
15703
15829
|
handleCollapseExpansion();
|
|
15704
15830
|
}, () => {
|
|
@@ -15706,31 +15832,57 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15706
15832
|
handleCollapseExpansion();
|
|
15707
15833
|
});
|
|
15708
15834
|
}
|
|
15709
|
-
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
|
|
15714
|
-
if (config) {
|
|
15715
|
-
bgColorClass = 'bg-slate-50/80 border-t border-b border-slate-200/80';
|
|
15716
|
-
const handleClose = () => setExpansionState(null);
|
|
15717
|
-
content = config.render(item, handleClose);
|
|
15835
|
+
// Details mode
|
|
15836
|
+
else if (mode === "details" && expandedRowConfig.details) {
|
|
15837
|
+
bgColorClass =
|
|
15838
|
+
"bg-primary-50/80 border-t border-b border-primary-200/80";
|
|
15839
|
+
content = expandedRowConfig.details.render(item);
|
|
15718
15840
|
}
|
|
15719
|
-
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15841
|
+
// Add related modes
|
|
15842
|
+
else if (mode.startsWith("addRelated-") &&
|
|
15843
|
+
expandedRowConfig.addRelated) {
|
|
15844
|
+
const key = mode.replace("addRelated-", "");
|
|
15845
|
+
const config = expandedRowConfig.addRelated.find((c) => c.key === key);
|
|
15846
|
+
if (config) {
|
|
15847
|
+
bgColorClass =
|
|
15848
|
+
"bg-success-50/80 border-t border-b border-success-200/80";
|
|
15849
|
+
content = config.render(item, async (_newItem) => {
|
|
15850
|
+
// Handle save
|
|
15851
|
+
handleCollapseExpansion();
|
|
15852
|
+
}, () => {
|
|
15853
|
+
// Handle cancel
|
|
15854
|
+
handleCollapseExpansion();
|
|
15855
|
+
});
|
|
15856
|
+
}
|
|
15857
|
+
}
|
|
15858
|
+
// Manage related modes
|
|
15859
|
+
else if (mode.startsWith("manageRelated-") &&
|
|
15860
|
+
expandedRowConfig.manageRelated) {
|
|
15861
|
+
const key = mode.replace("manageRelated-", "");
|
|
15862
|
+
const config = expandedRowConfig.manageRelated.find((c) => c.key === key);
|
|
15863
|
+
if (config) {
|
|
15864
|
+
bgColorClass =
|
|
15865
|
+
"bg-slate-50/80 border-t border-b border-slate-200/80";
|
|
15866
|
+
const handleClose = () => setExpansionState(null);
|
|
15867
|
+
content = config.render(item, handleClose);
|
|
15868
|
+
}
|
|
15869
|
+
}
|
|
15870
|
+
if (!content)
|
|
15871
|
+
return null;
|
|
15872
|
+
return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
|
|
15873
|
+
(selectable ? 1 : 0) +
|
|
15874
|
+
((expandable || expandedRowConfig) && showExpandChevron
|
|
15875
|
+
? 1
|
|
15876
|
+
: 0) +
|
|
15877
|
+
(allActions.length > 0 ? 1 : 0), className: `${currentDensity.cell} py-4 ${bgColorClass} animate-expand`, children: content }) }, `expanded-${rowKey}`));
|
|
15878
|
+
})()] }, rowKey));
|
|
15727
15879
|
});
|
|
15728
15880
|
};
|
|
15729
|
-
const tableContent = (jsxs("div", { className: `bg-white rounded-lg shadow border-2 ${borderColor} ${virtualized ?
|
|
15881
|
+
const tableContent = (jsxs("div", { className: `bg-white rounded-lg shadow border-2 ${borderColor} ${virtualized ? "overflow-hidden" : "overflow-x-auto overflow-y-visible"} ${className}`, style: { position: "relative" }, children: [loading && data.length > 0 && (jsx("div", { className: "absolute inset-0 bg-white/75 flex items-center justify-center z-20", style: { backdropFilter: "blur(2px)" }, children: jsxs("div", { className: "flex flex-col items-center gap-3", children: [jsx("div", { className: "loading-spinner", style: { width: "32px", height: "32px", borderWidth: "3px" } }), jsx("span", { className: "text-sm font-medium text-ink-600", children: "Loading..." })] }) })), jsxs("table", { className: `table-stable w-full ${bordered ? "border-collapse" : ""}`, role: "grid", "aria-label": "Data table", "aria-rowcount": data.length, "aria-colcount": visibleColumns.length, children: [jsxs("colgroup", { children: [selectable && jsx("col", { className: "w-12" }), (expandable || expandedRowConfig) && showExpandChevron && (jsx("col", { className: "w-10" })), allActions.length > 0 && jsx("col", { style: { width: "28px" } }), visibleColumns.map((column, index) => {
|
|
15730
15882
|
const columnKey = String(column.key);
|
|
15731
15883
|
const dynamicWidth = columnWidths[columnKey];
|
|
15732
15884
|
return (jsx("col", { style: getColumnStyle(column, dynamicWidth) }, index));
|
|
15733
|
-
})] }), jsx("thead", { className: `bg-paper-100 sticky top-0 z-10 ${headerClassName}`, children: jsxs("tr", { className: "table-header-row", children: [selectable && (jsx("th", { className: `sticky left-0 bg-paper-100 ${currentDensity.header} border-b ${borderColor} z-20 w-12 ${bordered ? `border ${borderColor}` :
|
|
15885
|
+
})] }), jsx("thead", { className: `bg-paper-100 sticky top-0 z-10 ${headerClassName}`, children: jsxs("tr", { className: "table-header-row", children: [selectable && (jsx("th", { className: `sticky left-0 bg-paper-100 ${currentDensity.header} border-b ${borderColor} z-20 w-12 ${bordered ? `border ${borderColor}` : ""}`, children: jsx("input", { type: "checkbox", checked: selectedRowsSet.size === data.length && data.length > 0, onChange: handleSelectAll, className: "w-4 h-4 text-accent-600 border-paper-300 rounded focus:ring-accent-400", "aria-label": "Select all rows" }) })), (expandable || expandedRowConfig) && showExpandChevron && (jsx("th", { className: `sticky left-0 bg-paper-100 px-2 ${currentDensity.header} border-b ${borderColor} z-19 w-10 ${bordered ? `border ${borderColor}` : ""}` })), allActions.length > 0 && (jsx("th", { className: `sticky left-0 bg-paper-100 text-center text-xs font-medium text-ink-700 uppercase tracking-wider border-b ${borderColor} z-20 ${bordered ? `border ${borderColor}` : ""}`, style: { width: "28px", padding: "0" } })), visibleColumns.map((column, colIdx) => {
|
|
15734
15886
|
const columnKey = String(column.key);
|
|
15735
15887
|
const dynamicWidth = columnWidths[columnKey];
|
|
15736
15888
|
const thRef = useRef(null);
|
|
@@ -15738,24 +15890,28 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15738
15890
|
const isDragOver = dragOverColumn === columnKey;
|
|
15739
15891
|
// Reduce left padding on first column when there are action buttons (match body cells)
|
|
15740
15892
|
const isFirstColumn = colIdx === 0;
|
|
15741
|
-
const headerPaddingClass = isFirstColumn && allActions.length > 0 ?
|
|
15893
|
+
const headerPaddingClass = isFirstColumn && allActions.length > 0 ? "pl-3" : "";
|
|
15742
15894
|
return (jsxs("th", { ref: thRef, draggable: reorderable, onDragStart: (e) => reorderable && handleDragStart(e, columnKey), onDragOver: (e) => reorderable && handleDragOver(e, columnKey), onDragEnd: handleDragEnd, onDrop: (e) => reorderable && handleDrop(e, columnKey), className: `
|
|
15743
|
-
${currentDensity.header} ${headerPaddingClass} text-left border-b ${borderColor} ${bordered ? `border ${borderColor}` :
|
|
15744
|
-
${reorderable ?
|
|
15745
|
-
${isDragging ?
|
|
15746
|
-
${isDragOver ?
|
|
15895
|
+
${currentDensity.header} ${headerPaddingClass} text-left border-b ${borderColor} ${bordered ? `border ${borderColor}` : ""} relative
|
|
15896
|
+
${reorderable ? "cursor-move" : ""}
|
|
15897
|
+
${isDragging ? "opacity-50" : ""}
|
|
15898
|
+
${isDragOver ? "bg-accent-100" : ""}
|
|
15747
15899
|
`, style: getColumnStyle(column, dynamicWidth), children: [column.sortable ? (jsxs("button", { onClick: () => handleSort(column), className: "group inline-flex items-center text-xs font-medium text-ink-500 uppercase tracking-wider hover:text-ink-900 transition-colors", children: [jsx("span", { children: column.header }), getSortIcon(column)] })) : (jsx("span", { className: "text-xs font-medium text-ink-500 uppercase tracking-wider", children: column.header })), resizable && (jsx("div", { className: "absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-accent-400 group", onMouseDown: (e) => {
|
|
15748
15900
|
const currentWidth = thRef.current?.offsetWidth || 100;
|
|
15749
15901
|
handleResizeStart(e, columnKey, currentWidth);
|
|
15750
15902
|
}, children: jsx("div", { className: "absolute right-0 top-0 bottom-0 w-1 bg-paper-300 group-hover:bg-accent-400 transition-colors" }) }))] }, columnKey));
|
|
15751
|
-
})] }) }), jsx("tbody", { ref: tableBodyRef, className: "bg-white table-loading transition-opacity duration-200", onKeyDown: handleKeyboardNavigation, tabIndex: 0, role: "rowgroup", "aria-label": "Table data", children: loading && data.length === 0
|
|
15903
|
+
})] }) }), jsx("tbody", { ref: tableBodyRef, className: "bg-white table-loading transition-opacity duration-200", onKeyDown: handleKeyboardNavigation, tabIndex: 0, role: "rowgroup", "aria-label": "Table data", children: loading && data.length === 0
|
|
15904
|
+
? renderLoadingSkeleton()
|
|
15905
|
+
: data.length === 0
|
|
15906
|
+
? renderEmptyStateContent()
|
|
15907
|
+
: renderDataRows() })] })] }));
|
|
15752
15908
|
// Wrap in scrollable container if virtualized
|
|
15753
|
-
const finalContent = virtualized ? (jsx("div", { ref: tableContainerRef, onScroll: handleScroll, style: { height: virtualHeight, overflow:
|
|
15909
|
+
const finalContent = virtualized ? (jsx("div", { ref: tableContainerRef, onScroll: handleScroll, style: { height: virtualHeight, overflow: "auto" }, className: "rounded-lg", children: tableContent })) : (tableContent);
|
|
15754
15910
|
// Calculate pagination values
|
|
15755
15911
|
const effectiveTotalItems = totalItems ?? data.length;
|
|
15756
15912
|
const totalPages = Math.ceil(effectiveTotalItems / pageSize);
|
|
15757
15913
|
// Page size selector options
|
|
15758
|
-
const pageSizeSelectOptions = pageSizeOptions.map(size => ({
|
|
15914
|
+
const pageSizeSelectOptions = pageSizeOptions.map((size) => ({
|
|
15759
15915
|
value: String(size),
|
|
15760
15916
|
label: `${size} per page`,
|
|
15761
15917
|
}));
|
|
@@ -15763,17 +15919,20 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15763
15919
|
const renderPaginationControls = () => {
|
|
15764
15920
|
if (!paginated)
|
|
15765
15921
|
return null;
|
|
15766
|
-
return (jsxs("div", { className: "flex items-center justify-between mb-4 px-1", children: [jsxs("div", { className: "flex items-center gap-4", children: [showPageSizeSelector && onPageSizeChange && (jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm text-ink-600", children: "Show:" }), jsx(Select, { options: pageSizeSelectOptions, value: String(pageSize), onChange: (value) => onPageSizeChange?.(Number(value)) })] })), jsx("span", { className: "text-sm text-ink-600", children: effectiveTotalItems > 0 ? (jsxs(Fragment, { children: ["Showing ", (
|
|
15922
|
+
return (jsxs("div", { className: "flex items-center justify-between mb-4 px-1", children: [jsxs("div", { className: "flex items-center gap-4", children: [showPageSizeSelector && onPageSizeChange && (jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm text-ink-600", children: "Show:" }), jsx(Select, { options: pageSizeSelectOptions, value: String(pageSize), onChange: (value) => onPageSizeChange?.(Number(value)) })] })), jsx("span", { className: "text-sm text-ink-600", children: effectiveTotalItems > 0 ? (jsxs(Fragment, { children: ["Showing ", (currentPage - 1) * pageSize + 1, " -", " ", Math.min(currentPage * pageSize, effectiveTotalItems), " of", " ", effectiveTotalItems] })) : ("No items") })] }), totalPages > 1 && onPageChange && (jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: onPageChange }))] }));
|
|
15767
15923
|
};
|
|
15768
15924
|
// Determine if we should show card view
|
|
15769
|
-
const shouldShowCardView = mobileView ===
|
|
15770
|
-
(mobileView === 'auto' && isMobileViewport);
|
|
15925
|
+
const shouldShowCardView = mobileView === "card" || (mobileView === "auto" && isMobileViewport);
|
|
15771
15926
|
// Card view content
|
|
15772
15927
|
const cardViewContent = shouldShowCardView ? (jsx(DataTableCardView, { data: data, columns: visibleColumns, cardConfig: cardConfig, loading: loading, loadingRows: loadingRows, emptyMessage: emptyMessage, onCardClick: onRowClick, onCardLongPress: (item, event) => {
|
|
15773
15928
|
if (enableContextMenu && allActions.length > 0) {
|
|
15774
15929
|
event.preventDefault();
|
|
15775
|
-
const clientX =
|
|
15776
|
-
|
|
15930
|
+
const clientX = "touches" in event
|
|
15931
|
+
? event.touches[0].clientX
|
|
15932
|
+
: event.clientX;
|
|
15933
|
+
const clientY = "touches" in event
|
|
15934
|
+
? event.touches[0].clientY
|
|
15935
|
+
: event.clientY;
|
|
15777
15936
|
setContextMenuState({
|
|
15778
15937
|
isOpen: true,
|
|
15779
15938
|
position: { x: clientX, y: clientY },
|
|
@@ -15782,7 +15941,11 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
15782
15941
|
}
|
|
15783
15942
|
}, selectable: selectable, selectedRows: selectedRowsSet, onSelectionChange: onRowSelect ? (rows) => onRowSelect(rows) : undefined, keyExtractor: getRowKey, actions: allActions, onEdit: onEdit, onDelete: onDelete, className: className, cardClassName: cardClassName, gap: cardGap })) : null;
|
|
15784
15943
|
// Render with context menu
|
|
15785
|
-
return (jsxs(Fragment, { children: [jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: announcement }), renderPaginationControls(), shouldShowCardView ? cardViewContent : finalContent, contextMenuState.isOpen && contextMenuState.item && (jsx(Menu, { items: convertActionsToMenuItems(contextMenuState.item), position: contextMenuState.position, onClose: () => setContextMenuState({
|
|
15944
|
+
return (jsxs(Fragment, { children: [jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: announcement }), renderPaginationControls(), shouldShowCardView ? cardViewContent : finalContent, contextMenuState.isOpen && contextMenuState.item && (jsx(Menu, { items: convertActionsToMenuItems(contextMenuState.item), position: contextMenuState.position, onClose: () => setContextMenuState({
|
|
15945
|
+
isOpen: false,
|
|
15946
|
+
position: { x: 0, y: 0 },
|
|
15947
|
+
item: null,
|
|
15948
|
+
}) }))] }));
|
|
15786
15949
|
}
|
|
15787
15950
|
|
|
15788
15951
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|