@dragunio/fe-libs 0.1.0 → 0.1.1
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 +22 -0
- package/dist/index.d.mts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +112 -2
- package/dist/index.mjs +123 -4
- package/dist/styles.css +84 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,4 +61,26 @@ import { Button, PlusIcon, Table } from '@dragunio/fe-libs';
|
|
|
61
61
|
import '@dragunio/fe-libs/styles.css';
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
### Resizable data table
|
|
65
|
+
|
|
66
|
+
Pass `headers` and `data` to have `Table` derive the maximum column count and an initial width for each column. The resize callback emits pixel widths, ready to persist in user preferences.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
const [columnSizes, setColumnSizes] = useState<number[]>();
|
|
70
|
+
|
|
71
|
+
<Table
|
|
72
|
+
headers={['Name', 'Email']}
|
|
73
|
+
data={[
|
|
74
|
+
['Ada Lovelace', 'ada@example.com'],
|
|
75
|
+
['Grace Hopper', 'grace@example.com'],
|
|
76
|
+
]}
|
|
77
|
+
striped
|
|
78
|
+
defaultColumnSizes={[180, 280]}
|
|
79
|
+
columnSizes={columnSizes}
|
|
80
|
+
onColumnResize={(nextSizes) => setColumnSizes(nextSizes)}
|
|
81
|
+
/>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Use `minColumnWidth` to change the minimum resize limit. The header resize handles support pointer dragging and left/right arrow keys.
|
|
85
|
+
|
|
64
86
|
The library exposes ESM, CommonJS, TypeScript declarations, and keeps React as a peer dependency.
|
package/dist/index.d.mts
CHANGED
|
@@ -51,10 +51,29 @@ declare function ChevronDownIcon(props: IconProps): react.JSX.Element;
|
|
|
51
51
|
declare function CloseIcon(props: IconProps): react.JSX.Element;
|
|
52
52
|
declare function SearchIcon(props: IconProps): react.JSX.Element;
|
|
53
53
|
|
|
54
|
+
type TableData = readonly (readonly ReactNode[])[];
|
|
55
|
+
type ColumnResizeHandler = (columnSizes: number[], columnIndex: number) => void;
|
|
54
56
|
type TableProps = TableHTMLAttributes<HTMLTableElement> & {
|
|
57
|
+
/** Enables alternating background colors for body rows. */
|
|
55
58
|
striped?: boolean;
|
|
59
|
+
/** Column labels. Passing headers or data enables the data-driven table API. */
|
|
60
|
+
headers?: readonly ReactNode[];
|
|
61
|
+
/** Rows of cells. A row can contain fewer or more cells than headers. */
|
|
62
|
+
data?: TableData;
|
|
63
|
+
/** Initial widths, in pixels, for the matching column indexes. */
|
|
64
|
+
defaultColumnSizes?: readonly number[];
|
|
65
|
+
/** Controlled column widths, in pixels. */
|
|
66
|
+
columnSizes?: readonly number[];
|
|
67
|
+
/** Called while a user resizes a column, with the complete current widths. */
|
|
68
|
+
onColumnResize?: ColumnResizeHandler;
|
|
69
|
+
/** Smallest width, in pixels, that a user can resize a column to. */
|
|
70
|
+
minColumnWidth?: number;
|
|
56
71
|
};
|
|
57
|
-
|
|
72
|
+
/**
|
|
73
|
+
* A semantic table. Use its child primitives for custom markup, or pass headers
|
|
74
|
+
* and data for an automatically sized, striped and resizable table.
|
|
75
|
+
*/
|
|
76
|
+
declare function Table({ headers, data, striped, children, ...props }: TableProps): react.JSX.Element;
|
|
58
77
|
declare function TableHead(props: HTMLAttributes<HTMLTableSectionElement>): react.JSX.Element;
|
|
59
78
|
declare function TableBody(props: HTMLAttributes<HTMLTableSectionElement>): react.JSX.Element;
|
|
60
79
|
declare function TableRow({ children, ...props }: HTMLAttributes<HTMLTableRowElement>): react.JSX.Element;
|
|
@@ -62,4 +81,4 @@ declare function TableCell({ children, ...props }: HTMLAttributes<HTMLTableCellE
|
|
|
62
81
|
declare function TableHeader({ children, ...props }: HTMLAttributes<HTMLTableCellElement>): react.JSX.Element;
|
|
63
82
|
declare function TableCaption(props: HTMLAttributes<HTMLTableCaptionElement>): react.JSX.Element;
|
|
64
83
|
|
|
65
|
-
export { Button, type ButtonColor, type ButtonHierarchy, type ButtonProps, type ButtonSize, type ButtonVariant, ChevronDownIcon, CloseIcon, type IconProps, PlusIcon, SearchIcon, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, type TableProps, TableRow, getIconSize };
|
|
84
|
+
export { Button, type ButtonColor, type ButtonHierarchy, type ButtonProps, type ButtonSize, type ButtonVariant, ChevronDownIcon, CloseIcon, type ColumnResizeHandler, type IconProps, PlusIcon, SearchIcon, Table, TableBody, TableCaption, TableCell, type TableData, TableHead, TableHeader, type TableProps, TableRow, getIconSize };
|
package/dist/index.d.ts
CHANGED
|
@@ -51,10 +51,29 @@ declare function ChevronDownIcon(props: IconProps): react.JSX.Element;
|
|
|
51
51
|
declare function CloseIcon(props: IconProps): react.JSX.Element;
|
|
52
52
|
declare function SearchIcon(props: IconProps): react.JSX.Element;
|
|
53
53
|
|
|
54
|
+
type TableData = readonly (readonly ReactNode[])[];
|
|
55
|
+
type ColumnResizeHandler = (columnSizes: number[], columnIndex: number) => void;
|
|
54
56
|
type TableProps = TableHTMLAttributes<HTMLTableElement> & {
|
|
57
|
+
/** Enables alternating background colors for body rows. */
|
|
55
58
|
striped?: boolean;
|
|
59
|
+
/** Column labels. Passing headers or data enables the data-driven table API. */
|
|
60
|
+
headers?: readonly ReactNode[];
|
|
61
|
+
/** Rows of cells. A row can contain fewer or more cells than headers. */
|
|
62
|
+
data?: TableData;
|
|
63
|
+
/** Initial widths, in pixels, for the matching column indexes. */
|
|
64
|
+
defaultColumnSizes?: readonly number[];
|
|
65
|
+
/** Controlled column widths, in pixels. */
|
|
66
|
+
columnSizes?: readonly number[];
|
|
67
|
+
/** Called while a user resizes a column, with the complete current widths. */
|
|
68
|
+
onColumnResize?: ColumnResizeHandler;
|
|
69
|
+
/** Smallest width, in pixels, that a user can resize a column to. */
|
|
70
|
+
minColumnWidth?: number;
|
|
56
71
|
};
|
|
57
|
-
|
|
72
|
+
/**
|
|
73
|
+
* A semantic table. Use its child primitives for custom markup, or pass headers
|
|
74
|
+
* and data for an automatically sized, striped and resizable table.
|
|
75
|
+
*/
|
|
76
|
+
declare function Table({ headers, data, striped, children, ...props }: TableProps): react.JSX.Element;
|
|
58
77
|
declare function TableHead(props: HTMLAttributes<HTMLTableSectionElement>): react.JSX.Element;
|
|
59
78
|
declare function TableBody(props: HTMLAttributes<HTMLTableSectionElement>): react.JSX.Element;
|
|
60
79
|
declare function TableRow({ children, ...props }: HTMLAttributes<HTMLTableRowElement>): react.JSX.Element;
|
|
@@ -62,4 +81,4 @@ declare function TableCell({ children, ...props }: HTMLAttributes<HTMLTableCellE
|
|
|
62
81
|
declare function TableHeader({ children, ...props }: HTMLAttributes<HTMLTableCellElement>): react.JSX.Element;
|
|
63
82
|
declare function TableCaption(props: HTMLAttributes<HTMLTableCaptionElement>): react.JSX.Element;
|
|
64
83
|
|
|
65
|
-
export { Button, type ButtonColor, type ButtonHierarchy, type ButtonProps, type ButtonSize, type ButtonVariant, ChevronDownIcon, CloseIcon, type IconProps, PlusIcon, SearchIcon, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, type TableProps, TableRow, getIconSize };
|
|
84
|
+
export { Button, type ButtonColor, type ButtonHierarchy, type ButtonProps, type ButtonSize, type ButtonVariant, ChevronDownIcon, CloseIcon, type ColumnResizeHandler, type IconProps, PlusIcon, SearchIcon, Table, TableBody, TableCaption, TableCell, type TableData, TableHead, TableHeader, type TableProps, TableRow, getIconSize };
|
package/dist/index.js
CHANGED
|
@@ -117,8 +117,118 @@ function SearchIcon(props) {
|
|
|
117
117
|
var import_react2 = require("react");
|
|
118
118
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
119
119
|
var TableContext = (0, import_react2.createContext)({ striped: false });
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
var MIN_COLUMN_WIDTH = 96;
|
|
121
|
+
var MAX_COLUMN_WIDTH = 1200;
|
|
122
|
+
function getColumnCount(headers, data) {
|
|
123
|
+
return Math.max(headers.length, ...data.map((row) => row.length), 0);
|
|
124
|
+
}
|
|
125
|
+
function getText(value) {
|
|
126
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
127
|
+
if (Array.isArray(value)) return value.map(getText).join("");
|
|
128
|
+
if ((0, import_react2.isValidElement)(value)) return getText(value.props.children);
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
function autoColumnSize(columnIndex, headers, data) {
|
|
132
|
+
const values = [headers[columnIndex], ...data.map((row) => row[columnIndex])];
|
|
133
|
+
const longestText = values.reduce((longest, value) => Math.max(longest, getText(value).length), 0);
|
|
134
|
+
return Math.min(MAX_COLUMN_WIDTH, Math.max(MIN_COLUMN_WIDTH, longestText * 8 + 40));
|
|
135
|
+
}
|
|
136
|
+
function getInitialColumnSizes(headers, data, sizes, minColumnWidth = MIN_COLUMN_WIDTH) {
|
|
137
|
+
return Array.from({ length: getColumnCount(headers, data) }, (_, columnIndex) => {
|
|
138
|
+
const suppliedSize = sizes?.[columnIndex];
|
|
139
|
+
return typeof suppliedSize === "number" && Number.isFinite(suppliedSize) ? Math.min(MAX_COLUMN_WIDTH, Math.max(minColumnWidth, suppliedSize)) : autoColumnSize(columnIndex, headers, data);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function sameSizes(left, right) {
|
|
143
|
+
return left.length === right.length && left.every((size, index) => size === right[index]);
|
|
144
|
+
}
|
|
145
|
+
function DataTable({
|
|
146
|
+
headers,
|
|
147
|
+
data,
|
|
148
|
+
striped = true,
|
|
149
|
+
defaultColumnSizes,
|
|
150
|
+
columnSizes,
|
|
151
|
+
onColumnResize,
|
|
152
|
+
minColumnWidth = MIN_COLUMN_WIDTH,
|
|
153
|
+
className,
|
|
154
|
+
...props
|
|
155
|
+
}) {
|
|
156
|
+
const columnCount = getColumnCount(headers, data);
|
|
157
|
+
const safeMinWidth = Math.max(1, minColumnWidth);
|
|
158
|
+
const generatedSizes = (0, import_react2.useMemo)(
|
|
159
|
+
() => getInitialColumnSizes(headers, data, defaultColumnSizes, safeMinWidth),
|
|
160
|
+
[headers, data, defaultColumnSizes, safeMinWidth]
|
|
161
|
+
);
|
|
162
|
+
const [uncontrolledSizes, setUncontrolledSizes] = (0, import_react2.useState)(generatedSizes);
|
|
163
|
+
const resizeState = (0, import_react2.useRef)();
|
|
164
|
+
const [isResizing, setIsResizing] = (0, import_react2.useState)(false);
|
|
165
|
+
const sizes = columnSizes ? getInitialColumnSizes(headers, data, columnSizes, safeMinWidth).map((size) => Math.max(safeMinWidth, size)) : uncontrolledSizes.map((size) => Math.max(safeMinWidth, size));
|
|
166
|
+
(0, import_react2.useEffect)(() => {
|
|
167
|
+
if (columnSizes) return;
|
|
168
|
+
setUncontrolledSizes((currentSizes) => {
|
|
169
|
+
const nextSizes = generatedSizes.map((generatedSize, index) => currentSizes[index] ?? generatedSize);
|
|
170
|
+
return sameSizes(currentSizes, nextSizes) ? currentSizes : nextSizes;
|
|
171
|
+
});
|
|
172
|
+
}, [columnSizes, generatedSizes]);
|
|
173
|
+
const updateColumnSize = (0, import_react2.useCallback)((columnIndex, nextSize) => {
|
|
174
|
+
const clampedSize = Math.min(MAX_COLUMN_WIDTH, Math.max(safeMinWidth, Math.round(nextSize)));
|
|
175
|
+
const nextSizes = sizes.map((size, index) => index === columnIndex ? clampedSize : size);
|
|
176
|
+
if (!columnSizes) setUncontrolledSizes(nextSizes);
|
|
177
|
+
onColumnResize?.(nextSizes, columnIndex);
|
|
178
|
+
}, [columnSizes, onColumnResize, safeMinWidth, sizes]);
|
|
179
|
+
(0, import_react2.useEffect)(() => {
|
|
180
|
+
const onPointerMove = (event) => {
|
|
181
|
+
const activeResize = resizeState.current;
|
|
182
|
+
if (!activeResize) return;
|
|
183
|
+
updateColumnSize(activeResize.columnIndex, activeResize.startWidth + event.clientX - activeResize.startX);
|
|
184
|
+
};
|
|
185
|
+
const stopResizing = () => {
|
|
186
|
+
resizeState.current = void 0;
|
|
187
|
+
setIsResizing(false);
|
|
188
|
+
};
|
|
189
|
+
window.addEventListener("pointermove", onPointerMove);
|
|
190
|
+
window.addEventListener("pointerup", stopResizing);
|
|
191
|
+
window.addEventListener("pointercancel", stopResizing);
|
|
192
|
+
return () => {
|
|
193
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
194
|
+
window.removeEventListener("pointerup", stopResizing);
|
|
195
|
+
window.removeEventListener("pointercancel", stopResizing);
|
|
196
|
+
};
|
|
197
|
+
}, [updateColumnSize]);
|
|
198
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fe-table", "data-resizing": isResizing || void 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { className: ["fe-table__table", className].filter(Boolean).join(" "), "data-fe-table": true, "data-striped": striped || void 0, ...props, children: [
|
|
199
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("colgroup", { children: sizes.map((size, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("col", { style: { width: `${size}px` } }, index)) }),
|
|
200
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tr", { children: Array.from({ length: columnCount }, (_, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("th", { scope: "col", children: [
|
|
201
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fe-table__header-content", children: headers[columnIndex] ?? `Column ${columnIndex + 1}` }),
|
|
202
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
203
|
+
"span",
|
|
204
|
+
{
|
|
205
|
+
className: "fe-table__resize-handle",
|
|
206
|
+
role: "separator",
|
|
207
|
+
"aria-label": `Resize ${getText(headers[columnIndex]) || `column ${columnIndex + 1}`}`,
|
|
208
|
+
"aria-orientation": "vertical",
|
|
209
|
+
"aria-valuemin": safeMinWidth,
|
|
210
|
+
"aria-valuemax": MAX_COLUMN_WIDTH,
|
|
211
|
+
"aria-valuenow": sizes[columnIndex] ?? safeMinWidth,
|
|
212
|
+
tabIndex: 0,
|
|
213
|
+
onPointerDown: (event) => {
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
resizeState.current = { columnIndex, startX: event.clientX, startWidth: sizes[columnIndex] ?? safeMinWidth };
|
|
216
|
+
setIsResizing(true);
|
|
217
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
218
|
+
},
|
|
219
|
+
onKeyDown: (event) => {
|
|
220
|
+
if (event.key === "ArrowLeft") updateColumnSize(columnIndex, (sizes[columnIndex] ?? safeMinWidth) - 16);
|
|
221
|
+
if (event.key === "ArrowRight") updateColumnSize(columnIndex, (sizes[columnIndex] ?? safeMinWidth) + 16);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
] }, columnIndex)) }) }),
|
|
226
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tr", { children: Array.from({ length: columnCount }, (_, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { children: row[columnIndex] }, columnIndex)) }, rowIndex)) })
|
|
227
|
+
] }) });
|
|
228
|
+
}
|
|
229
|
+
function Table({ headers, data, striped, children, ...props }) {
|
|
230
|
+
if (headers || data) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DataTable, { headers: headers ?? [], data: data ?? [], striped: striped ?? true, ...props });
|
|
231
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TableContext.Provider, { value: { striped: striped ?? false }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("table", { "data-fe-table": true, "data-striped": striped || void 0, ...props, children }) });
|
|
122
232
|
}
|
|
123
233
|
function TableHead(props) {
|
|
124
234
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { ...props });
|
package/dist/index.mjs
CHANGED
|
@@ -76,11 +76,130 @@ function SearchIcon(props) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// packages/table/src/index.tsx
|
|
79
|
-
import {
|
|
80
|
-
|
|
79
|
+
import {
|
|
80
|
+
createContext,
|
|
81
|
+
isValidElement,
|
|
82
|
+
useCallback,
|
|
83
|
+
useContext,
|
|
84
|
+
useEffect,
|
|
85
|
+
useMemo,
|
|
86
|
+
useRef,
|
|
87
|
+
useState
|
|
88
|
+
} from "react";
|
|
89
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
81
90
|
var TableContext = createContext({ striped: false });
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
var MIN_COLUMN_WIDTH = 96;
|
|
92
|
+
var MAX_COLUMN_WIDTH = 1200;
|
|
93
|
+
function getColumnCount(headers, data) {
|
|
94
|
+
return Math.max(headers.length, ...data.map((row) => row.length), 0);
|
|
95
|
+
}
|
|
96
|
+
function getText(value) {
|
|
97
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
98
|
+
if (Array.isArray(value)) return value.map(getText).join("");
|
|
99
|
+
if (isValidElement(value)) return getText(value.props.children);
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
function autoColumnSize(columnIndex, headers, data) {
|
|
103
|
+
const values = [headers[columnIndex], ...data.map((row) => row[columnIndex])];
|
|
104
|
+
const longestText = values.reduce((longest, value) => Math.max(longest, getText(value).length), 0);
|
|
105
|
+
return Math.min(MAX_COLUMN_WIDTH, Math.max(MIN_COLUMN_WIDTH, longestText * 8 + 40));
|
|
106
|
+
}
|
|
107
|
+
function getInitialColumnSizes(headers, data, sizes, minColumnWidth = MIN_COLUMN_WIDTH) {
|
|
108
|
+
return Array.from({ length: getColumnCount(headers, data) }, (_, columnIndex) => {
|
|
109
|
+
const suppliedSize = sizes?.[columnIndex];
|
|
110
|
+
return typeof suppliedSize === "number" && Number.isFinite(suppliedSize) ? Math.min(MAX_COLUMN_WIDTH, Math.max(minColumnWidth, suppliedSize)) : autoColumnSize(columnIndex, headers, data);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function sameSizes(left, right) {
|
|
114
|
+
return left.length === right.length && left.every((size, index) => size === right[index]);
|
|
115
|
+
}
|
|
116
|
+
function DataTable({
|
|
117
|
+
headers,
|
|
118
|
+
data,
|
|
119
|
+
striped = true,
|
|
120
|
+
defaultColumnSizes,
|
|
121
|
+
columnSizes,
|
|
122
|
+
onColumnResize,
|
|
123
|
+
minColumnWidth = MIN_COLUMN_WIDTH,
|
|
124
|
+
className,
|
|
125
|
+
...props
|
|
126
|
+
}) {
|
|
127
|
+
const columnCount = getColumnCount(headers, data);
|
|
128
|
+
const safeMinWidth = Math.max(1, minColumnWidth);
|
|
129
|
+
const generatedSizes = useMemo(
|
|
130
|
+
() => getInitialColumnSizes(headers, data, defaultColumnSizes, safeMinWidth),
|
|
131
|
+
[headers, data, defaultColumnSizes, safeMinWidth]
|
|
132
|
+
);
|
|
133
|
+
const [uncontrolledSizes, setUncontrolledSizes] = useState(generatedSizes);
|
|
134
|
+
const resizeState = useRef();
|
|
135
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
136
|
+
const sizes = columnSizes ? getInitialColumnSizes(headers, data, columnSizes, safeMinWidth).map((size) => Math.max(safeMinWidth, size)) : uncontrolledSizes.map((size) => Math.max(safeMinWidth, size));
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (columnSizes) return;
|
|
139
|
+
setUncontrolledSizes((currentSizes) => {
|
|
140
|
+
const nextSizes = generatedSizes.map((generatedSize, index) => currentSizes[index] ?? generatedSize);
|
|
141
|
+
return sameSizes(currentSizes, nextSizes) ? currentSizes : nextSizes;
|
|
142
|
+
});
|
|
143
|
+
}, [columnSizes, generatedSizes]);
|
|
144
|
+
const updateColumnSize = useCallback((columnIndex, nextSize) => {
|
|
145
|
+
const clampedSize = Math.min(MAX_COLUMN_WIDTH, Math.max(safeMinWidth, Math.round(nextSize)));
|
|
146
|
+
const nextSizes = sizes.map((size, index) => index === columnIndex ? clampedSize : size);
|
|
147
|
+
if (!columnSizes) setUncontrolledSizes(nextSizes);
|
|
148
|
+
onColumnResize?.(nextSizes, columnIndex);
|
|
149
|
+
}, [columnSizes, onColumnResize, safeMinWidth, sizes]);
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const onPointerMove = (event) => {
|
|
152
|
+
const activeResize = resizeState.current;
|
|
153
|
+
if (!activeResize) return;
|
|
154
|
+
updateColumnSize(activeResize.columnIndex, activeResize.startWidth + event.clientX - activeResize.startX);
|
|
155
|
+
};
|
|
156
|
+
const stopResizing = () => {
|
|
157
|
+
resizeState.current = void 0;
|
|
158
|
+
setIsResizing(false);
|
|
159
|
+
};
|
|
160
|
+
window.addEventListener("pointermove", onPointerMove);
|
|
161
|
+
window.addEventListener("pointerup", stopResizing);
|
|
162
|
+
window.addEventListener("pointercancel", stopResizing);
|
|
163
|
+
return () => {
|
|
164
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
165
|
+
window.removeEventListener("pointerup", stopResizing);
|
|
166
|
+
window.removeEventListener("pointercancel", stopResizing);
|
|
167
|
+
};
|
|
168
|
+
}, [updateColumnSize]);
|
|
169
|
+
return /* @__PURE__ */ jsx3("div", { className: "fe-table", "data-resizing": isResizing || void 0, children: /* @__PURE__ */ jsxs3("table", { className: ["fe-table__table", className].filter(Boolean).join(" "), "data-fe-table": true, "data-striped": striped || void 0, ...props, children: [
|
|
170
|
+
/* @__PURE__ */ jsx3("colgroup", { children: sizes.map((size, index) => /* @__PURE__ */ jsx3("col", { style: { width: `${size}px` } }, index)) }),
|
|
171
|
+
/* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsx3("tr", { children: Array.from({ length: columnCount }, (_, columnIndex) => /* @__PURE__ */ jsxs3("th", { scope: "col", children: [
|
|
172
|
+
/* @__PURE__ */ jsx3("span", { className: "fe-table__header-content", children: headers[columnIndex] ?? `Column ${columnIndex + 1}` }),
|
|
173
|
+
/* @__PURE__ */ jsx3(
|
|
174
|
+
"span",
|
|
175
|
+
{
|
|
176
|
+
className: "fe-table__resize-handle",
|
|
177
|
+
role: "separator",
|
|
178
|
+
"aria-label": `Resize ${getText(headers[columnIndex]) || `column ${columnIndex + 1}`}`,
|
|
179
|
+
"aria-orientation": "vertical",
|
|
180
|
+
"aria-valuemin": safeMinWidth,
|
|
181
|
+
"aria-valuemax": MAX_COLUMN_WIDTH,
|
|
182
|
+
"aria-valuenow": sizes[columnIndex] ?? safeMinWidth,
|
|
183
|
+
tabIndex: 0,
|
|
184
|
+
onPointerDown: (event) => {
|
|
185
|
+
event.preventDefault();
|
|
186
|
+
resizeState.current = { columnIndex, startX: event.clientX, startWidth: sizes[columnIndex] ?? safeMinWidth };
|
|
187
|
+
setIsResizing(true);
|
|
188
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
189
|
+
},
|
|
190
|
+
onKeyDown: (event) => {
|
|
191
|
+
if (event.key === "ArrowLeft") updateColumnSize(columnIndex, (sizes[columnIndex] ?? safeMinWidth) - 16);
|
|
192
|
+
if (event.key === "ArrowRight") updateColumnSize(columnIndex, (sizes[columnIndex] ?? safeMinWidth) + 16);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
] }, columnIndex)) }) }),
|
|
197
|
+
/* @__PURE__ */ jsx3("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ jsx3("tr", { children: Array.from({ length: columnCount }, (_, columnIndex) => /* @__PURE__ */ jsx3("td", { children: row[columnIndex] }, columnIndex)) }, rowIndex)) })
|
|
198
|
+
] }) });
|
|
199
|
+
}
|
|
200
|
+
function Table({ headers, data, striped, children, ...props }) {
|
|
201
|
+
if (headers || data) return /* @__PURE__ */ jsx3(DataTable, { headers: headers ?? [], data: data ?? [], striped: striped ?? true, ...props });
|
|
202
|
+
return /* @__PURE__ */ jsx3(TableContext.Provider, { value: { striped: striped ?? false }, children: /* @__PURE__ */ jsx3("table", { "data-fe-table": true, "data-striped": striped || void 0, ...props, children }) });
|
|
84
203
|
}
|
|
85
204
|
function TableHead(props) {
|
|
86
205
|
return /* @__PURE__ */ jsx3("thead", { ...props });
|
package/dist/styles.css
CHANGED
|
@@ -212,4 +212,88 @@
|
|
|
212
212
|
outline-offset: 2px;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
/* packages/table/src/styles.css */
|
|
216
|
+
.fe-table {
|
|
217
|
+
width: 100%;
|
|
218
|
+
overflow-x: auto;
|
|
219
|
+
border: 1px solid var(--fe-table-border, #e3e7ee);
|
|
220
|
+
border-radius: var(--fe-table-radius, 10px);
|
|
221
|
+
background: var(--fe-table-background, #fff);
|
|
222
|
+
}
|
|
223
|
+
.fe-table__table,
|
|
224
|
+
table[data-fe-table] {
|
|
225
|
+
width: max-content;
|
|
226
|
+
min-width: 100%;
|
|
227
|
+
margin: 0;
|
|
228
|
+
border-collapse: collapse;
|
|
229
|
+
table-layout: fixed;
|
|
230
|
+
color: var(--fe-table-text, #242a35);
|
|
231
|
+
text-align: left;
|
|
232
|
+
}
|
|
233
|
+
.fe-table__table th,
|
|
234
|
+
.fe-table__table td,
|
|
235
|
+
table[data-fe-table] th,
|
|
236
|
+
table[data-fe-table] td {
|
|
237
|
+
min-width: 0;
|
|
238
|
+
padding: var(--fe-table-cell-padding, 12px 16px);
|
|
239
|
+
border-bottom: 1px solid var(--fe-table-border, #e3e7ee);
|
|
240
|
+
overflow-wrap: anywhere;
|
|
241
|
+
vertical-align: middle;
|
|
242
|
+
}
|
|
243
|
+
.fe-table__table th,
|
|
244
|
+
table[data-fe-table] th {
|
|
245
|
+
position: relative;
|
|
246
|
+
color: var(--fe-table-header-text, #596273);
|
|
247
|
+
background: var(--fe-table-header-background, #f8fafc);
|
|
248
|
+
font-size: var(--fe-table-header-font-size, 12px);
|
|
249
|
+
font-weight: 700;
|
|
250
|
+
letter-spacing: .04em;
|
|
251
|
+
text-transform: uppercase;
|
|
252
|
+
}
|
|
253
|
+
.fe-table__table tbody tr:last-child td,
|
|
254
|
+
table[data-fe-table] tbody tr:last-child td {
|
|
255
|
+
border-bottom: 0;
|
|
256
|
+
}
|
|
257
|
+
.fe-table__table[data-striped] tbody tr:nth-child(even),
|
|
258
|
+
table[data-fe-table][data-striped] tbody tr:nth-child(even) {
|
|
259
|
+
background: var(--fe-table-stripe-background, #f8fafc);
|
|
260
|
+
}
|
|
261
|
+
.fe-table__table tbody tr:hover,
|
|
262
|
+
table[data-fe-table] tbody tr:hover {
|
|
263
|
+
background: var(--fe-table-hover-background, #edf5ff);
|
|
264
|
+
}
|
|
265
|
+
.fe-table__header-content {
|
|
266
|
+
display: block;
|
|
267
|
+
padding-right: 16px;
|
|
268
|
+
}
|
|
269
|
+
.fe-table__resize-handle {
|
|
270
|
+
position: absolute;
|
|
271
|
+
z-index: 1;
|
|
272
|
+
top: 7px;
|
|
273
|
+
right: 4px;
|
|
274
|
+
width: 4px;
|
|
275
|
+
height: calc(100% - 14px);
|
|
276
|
+
border-radius: 4px;
|
|
277
|
+
background: var(--fe-table-resize-handle-background, #d6deea);
|
|
278
|
+
cursor: col-resize;
|
|
279
|
+
touch-action: none;
|
|
280
|
+
outline: none;
|
|
281
|
+
}
|
|
282
|
+
.fe-table__resize-handle::after {
|
|
283
|
+
}
|
|
284
|
+
.fe-table__resize-handle:hover,
|
|
285
|
+
.fe-table__resize-handle:focus-visible,
|
|
286
|
+
.fe-table[data-resizing] .fe-table__resize-handle {
|
|
287
|
+
background: var(--fe-table-resize-handle-active-background, #bfdbfe);
|
|
288
|
+
}
|
|
289
|
+
.fe-table__resize-handle:hover::after,
|
|
290
|
+
.fe-table__resize-handle:focus-visible::after,
|
|
291
|
+
.fe-table[data-resizing] .fe-table__resize-handle::after {
|
|
292
|
+
background: var(--fe-table-resize-handle, #1d4ed8);
|
|
293
|
+
box-shadow: -3px 0 var(--fe-table-resize-handle, #1d4ed8), 3px 0 var(--fe-table-resize-handle, #1d4ed8);
|
|
294
|
+
}
|
|
295
|
+
.fe-table__resize-handle:focus-visible {
|
|
296
|
+
box-shadow: 0 0 0 2px var(--fe-table-focus-ring, #2563eb);
|
|
297
|
+
}
|
|
298
|
+
|
|
215
299
|
/* src/styles.css */
|
package/package.json
CHANGED