@nuvui/table 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +57 -2
- package/dist/components/data-table/content.d.ts +67 -0
- package/dist/components/data-table/content.js +278 -0
- package/dist/components/data-table/context.d.ts +23 -0
- package/dist/components/data-table/context.js +51 -0
- package/dist/components/data-table/create-data-table-hook.d.ts +78 -0
- package/dist/components/data-table/create-data-table-hook.js +103 -0
- package/dist/components/data-table/data-table.d.ts +76 -0
- package/dist/components/data-table/data-table.js +178 -0
- package/dist/components/data-table/header.d.ts +23 -0
- package/dist/components/data-table/header.js +120 -0
- package/dist/components/data-table/index.d.ts +11 -0
- package/dist/components/data-table/index.js +10 -0
- package/dist/components/data-table/labels.d.ts +50 -0
- package/dist/components/data-table/labels.js +35 -0
- package/dist/components/data-table/pagination.d.ts +18 -0
- package/dist/components/data-table/pagination.js +126 -0
- package/dist/components/data-table/selection.d.ts +48 -0
- package/dist/components/data-table/selection.js +153 -0
- package/dist/components/data-table/toolbar.d.ts +51 -0
- package/dist/components/data-table/toolbar.js +123 -0
- package/dist/components/data-table/use-data-table-request.d.ts +44 -0
- package/dist/components/data-table/use-data-table-request.js +72 -0
- package/dist/components/table/index.d.ts +2 -0
- package/dist/components/table/index.js +2 -0
- package/dist/components/table/table.d.ts +100 -0
- package/dist/components/table/table.js +130 -0
- package/dist/css/data-table.css +278 -0
- package/dist/css/table.css +164 -0
- package/dist/full.d.ts +264 -0
- package/dist/full.js +44 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +11 -0
- package/dist/styles.css +441 -0
- package/dist/utils/cx.js +6 -0
- package/dist/utils/use-shift-click.js +32 -0
- package/dist/virtual.d.ts +42 -0
- package/dist/virtual.js +133 -0
- package/package.json +101 -3
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { supports, useDataTableView } from "./context.js";
|
|
3
|
+
import { cx } from "../../utils/cx.js";
|
|
4
|
+
import { useId } from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Button } from "@nuvui/react/button";
|
|
7
|
+
import { NativeSelect } from "@nuvui/react/native-select";
|
|
8
|
+
//#region src/components/data-table/pagination.tsx
|
|
9
|
+
const paths = {
|
|
10
|
+
first: "M11 4l-4 4 4 4M5 4v8",
|
|
11
|
+
previous: "M10 4l-4 4 4 4",
|
|
12
|
+
next: "M6 4l4 4-4 4",
|
|
13
|
+
last: "M5 4l4 4-4 4M11 4v8"
|
|
14
|
+
};
|
|
15
|
+
function PageButton({ icon, label, disabled, onClick }) {
|
|
16
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
17
|
+
intent: "ghost",
|
|
18
|
+
size: "sm",
|
|
19
|
+
className: "nuv-data-table__page-button",
|
|
20
|
+
"aria-label": label,
|
|
21
|
+
"aria-disabled": disabled || void 0,
|
|
22
|
+
onClick: () => {
|
|
23
|
+
if (!disabled) onClick();
|
|
24
|
+
},
|
|
25
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
26
|
+
"aria-hidden": "true",
|
|
27
|
+
className: "nuv-data-table__page-icon",
|
|
28
|
+
viewBox: "0 0 16 16",
|
|
29
|
+
width: "16",
|
|
30
|
+
height: "16",
|
|
31
|
+
fill: "none",
|
|
32
|
+
stroke: "currentColor",
|
|
33
|
+
strokeWidth: "1.5",
|
|
34
|
+
strokeLinecap: "round",
|
|
35
|
+
strokeLinejoin: "round",
|
|
36
|
+
children: /* @__PURE__ */ jsx("path", { d: paths[icon] })
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const defaultSizes = [
|
|
41
|
+
10,
|
|
42
|
+
20,
|
|
43
|
+
50,
|
|
44
|
+
100
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* The bar under the table: rows per page, which rows are showing out of
|
|
48
|
+
* how many, and buttons for the first, previous, next and last page. It
|
|
49
|
+
* needs `rowPaginationFeature`, and shows nothing without it.
|
|
50
|
+
*/
|
|
51
|
+
function DataTablePagination({ pageSizes = defaultSizes, className, ...props }) {
|
|
52
|
+
const { table, labels } = useDataTableView("DataTablePagination");
|
|
53
|
+
const id = useId();
|
|
54
|
+
if (!supports.pagination(table)) return null;
|
|
55
|
+
const { pageIndex, pageSize } = table.atoms.pagination.get();
|
|
56
|
+
const total = table.getRowCount();
|
|
57
|
+
const pages = Math.max(table.getPageCount(), 1);
|
|
58
|
+
const from = total === 0 ? 0 : pageIndex * pageSize + 1;
|
|
59
|
+
const to = Math.min((pageIndex + 1) * pageSize, total);
|
|
60
|
+
const sizes = pageSizes.includes(pageSize) ? pageSizes : [...pageSizes, pageSize].sort((a, b) => a - b);
|
|
61
|
+
const back = table.getCanPreviousPage();
|
|
62
|
+
const forward = table.getCanNextPage();
|
|
63
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
64
|
+
role: "group",
|
|
65
|
+
"aria-label": labels.pagination,
|
|
66
|
+
className: cx("nuv-data-table__pagination", className),
|
|
67
|
+
...props,
|
|
68
|
+
children: [
|
|
69
|
+
pageSizes.length > 0 ? /* @__PURE__ */ jsxs("div", {
|
|
70
|
+
className: "nuv-data-table__page-size",
|
|
71
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
72
|
+
htmlFor: id,
|
|
73
|
+
className: "nuv-data-table__page-size-label",
|
|
74
|
+
children: labels.rowsPerPage
|
|
75
|
+
}), /* @__PURE__ */ jsx(NativeSelect, {
|
|
76
|
+
id,
|
|
77
|
+
value: pageSize,
|
|
78
|
+
onChange: (event) => table.setPageSize(Number(event.target.value)),
|
|
79
|
+
children: sizes.map((size) => /* @__PURE__ */ jsx("option", {
|
|
80
|
+
value: size,
|
|
81
|
+
children: size
|
|
82
|
+
}, size))
|
|
83
|
+
})]
|
|
84
|
+
}) : null,
|
|
85
|
+
/* @__PURE__ */ jsx("span", {
|
|
86
|
+
className: "nuv-data-table__page-range",
|
|
87
|
+
children: labels.pageRange(from, to, total)
|
|
88
|
+
}),
|
|
89
|
+
/* @__PURE__ */ jsxs("div", {
|
|
90
|
+
className: "nuv-data-table__pages",
|
|
91
|
+
children: [
|
|
92
|
+
/* @__PURE__ */ jsx(PageButton, {
|
|
93
|
+
icon: "first",
|
|
94
|
+
label: labels.firstPage,
|
|
95
|
+
disabled: !back,
|
|
96
|
+
onClick: () => table.firstPage()
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ jsx(PageButton, {
|
|
99
|
+
icon: "previous",
|
|
100
|
+
label: labels.previousPage,
|
|
101
|
+
disabled: !back,
|
|
102
|
+
onClick: () => table.previousPage()
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ jsx("span", {
|
|
105
|
+
className: "nuv-data-table__page",
|
|
106
|
+
children: labels.page(pageIndex + 1, pages)
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ jsx(PageButton, {
|
|
109
|
+
icon: "next",
|
|
110
|
+
label: labels.nextPage,
|
|
111
|
+
disabled: !forward,
|
|
112
|
+
onClick: () => table.nextPage()
|
|
113
|
+
}),
|
|
114
|
+
/* @__PURE__ */ jsx(PageButton, {
|
|
115
|
+
icon: "last",
|
|
116
|
+
label: labels.lastPage,
|
|
117
|
+
disabled: !forward,
|
|
118
|
+
onClick: () => table.lastPage()
|
|
119
|
+
})
|
|
120
|
+
]
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { DataTablePagination };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Row, RowData, TableFeatures } from "@tanstack/react-table";
|
|
2
|
+
import { HTMLAttributes, ReactNode } from "react";
|
|
3
|
+
//#region src/components/data-table/selection.d.ts
|
|
4
|
+
export interface DataTableSelectAllProps {
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The checkbox in the heading of the selection column. In a table with
|
|
9
|
+
* pages it selects the rows on the page. Without pages, all of them. It
|
|
10
|
+
* shows a dash while some are selected and not all.
|
|
11
|
+
*/
|
|
12
|
+
export declare function DataTableSelectAll({ className }: DataTableSelectAllProps): import("react").JSX.Element | null;
|
|
13
|
+
export interface DataTableSelectRowProps<TFeatures extends TableFeatures = any, TData extends RowData = any> {
|
|
14
|
+
/** The row. Inside a column's `cell` it's found without being passed. */
|
|
15
|
+
row?: Row<TFeatures, TData>;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A row's checkbox. Shift and a click selects every row from the last one
|
|
20
|
+
* clicked to this one.
|
|
21
|
+
*/
|
|
22
|
+
export declare function DataTableSelectRow<TFeatures extends TableFeatures, TData extends RowData>({ row: given, className }: DataTableSelectRowProps<TFeatures, TData>): import("react").JSX.Element | null;
|
|
23
|
+
export interface DataTableExpandToggleProps<TFeatures extends TableFeatures = any, TData extends RowData = any> {
|
|
24
|
+
/** The row. Inside a column's `cell` it's found without being passed. */
|
|
25
|
+
row?: Row<TFeatures, TData>;
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The button that opens and closes a row: its detail panel, or the rows
|
|
30
|
+
* under it. In a tree it's indented by how deep the row is, and a row with
|
|
31
|
+
* nothing under it gets a gap of the same width, so the text after it
|
|
32
|
+
* lines up.
|
|
33
|
+
*/
|
|
34
|
+
export declare function DataTableExpandToggle<TFeatures extends TableFeatures, TData extends RowData>({ row: given, className }: DataTableExpandToggleProps<TFeatures, TData>): import("react").JSX.Element;
|
|
35
|
+
export interface DataTableBulkActionsProps<TFeatures extends TableFeatures = any, TData extends RowData = any> extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
36
|
+
/**
|
|
37
|
+
* The actions, usually buttons. As a function it's given the selected
|
|
38
|
+
* rows that are loaded. With a server doing the paging those are only
|
|
39
|
+
* the ones on this page: use `table.getSelectedRowIds()` for all of them.
|
|
40
|
+
*/
|
|
41
|
+
children?: ReactNode | ((rows: Row<TFeatures, TData>[]) => ReactNode);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A bar that's there while rows are selected. It says how many, holds the
|
|
45
|
+
* actions you give it, and has a button that clears the selection.
|
|
46
|
+
*/
|
|
47
|
+
export declare function DataTableBulkActions<TFeatures extends TableFeatures, TData extends RowData>({ children, className, ...props }: DataTableBulkActionsProps<TFeatures, TData>): import("react").JSX.Element | null;
|
|
48
|
+
//#endregion
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { canExpand, expanded, supports, useCell, useDataTableView } from "./context.js";
|
|
3
|
+
import { cx } from "../../utils/cx.js";
|
|
4
|
+
import { useShiftClick } from "../../utils/use-shift-click.js";
|
|
5
|
+
import { useLayoutEffect, useRef } from "react";
|
|
6
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Button } from "@nuvui/react/button";
|
|
8
|
+
import { Checkbox } from "@nuvui/react/checkbox";
|
|
9
|
+
//#region src/components/data-table/selection.tsx
|
|
10
|
+
const keepText = (event) => {
|
|
11
|
+
if (event.shiftKey) event.preventDefault();
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The checkbox in the heading of the selection column. In a table with
|
|
15
|
+
* pages it selects the rows on the page. Without pages, all of them. It
|
|
16
|
+
* shows a dash while some are selected and not all.
|
|
17
|
+
*/
|
|
18
|
+
function DataTableSelectAll({ className }) {
|
|
19
|
+
const { table, labels } = useDataTableView("DataTableSelectAll");
|
|
20
|
+
if (!supports.selection(table)) return null;
|
|
21
|
+
const paged = supports.pagination(table);
|
|
22
|
+
const all = paged ? table.getIsAllPageRowsSelected() : table.getIsAllRowsSelected();
|
|
23
|
+
const some = paged ? table.getIsSomePageRowsSelected() : table.getIsSomeRowsSelected();
|
|
24
|
+
const none = table.getRowModel().rows.length === 0;
|
|
25
|
+
return /* @__PURE__ */ jsx(Checkbox, {
|
|
26
|
+
className,
|
|
27
|
+
"data-nuv-select-all": "",
|
|
28
|
+
"aria-label": paged ? labels.selectPage : labels.selectAll,
|
|
29
|
+
disabled: none,
|
|
30
|
+
checked: all ? true : some ? "indeterminate" : false,
|
|
31
|
+
onClick: () => paged ? table.toggleAllPageRowsSelected(!all) : table.toggleAllRowsSelected(!all)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A row's checkbox. Shift and a click selects every row from the last one
|
|
36
|
+
* clicked to this one.
|
|
37
|
+
*/
|
|
38
|
+
function DataTableSelectRow({ row: given, className }) {
|
|
39
|
+
const { labels, rowName } = useDataTableView("DataTableSelectRow");
|
|
40
|
+
const click = useShiftClick();
|
|
41
|
+
const cell = useCell();
|
|
42
|
+
const row = given ?? cell?.row;
|
|
43
|
+
if (!row) throw new Error("DataTableSelectRow needs a row, or to be inside a column's cell.");
|
|
44
|
+
if (typeof row.getIsSelected !== "function") return null;
|
|
45
|
+
const checked = row.getIsSelected();
|
|
46
|
+
return /* @__PURE__ */ jsx(Checkbox, {
|
|
47
|
+
className,
|
|
48
|
+
"aria-label": labels.selectRow(rowName(row)),
|
|
49
|
+
disabled: !row.getCanSelect(),
|
|
50
|
+
checked: checked ? true : row.getIsSomeSelected() ? "indeterminate" : false,
|
|
51
|
+
onMouseDown: keepText,
|
|
52
|
+
...click.props,
|
|
53
|
+
onClick: (event) => row.getToggleSelectedHandler()({
|
|
54
|
+
target: { checked: !checked },
|
|
55
|
+
shiftKey: click.shift(event)
|
|
56
|
+
})
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The button that opens and closes a row: its detail panel, or the rows
|
|
61
|
+
* under it. In a tree it's indented by how deep the row is, and a row with
|
|
62
|
+
* nothing under it gets a gap of the same width, so the text after it
|
|
63
|
+
* lines up.
|
|
64
|
+
*/
|
|
65
|
+
function DataTableExpandToggle({ row: given, className }) {
|
|
66
|
+
const { labels, rowName } = useDataTableView("DataTableExpandToggle");
|
|
67
|
+
const cell = useCell();
|
|
68
|
+
const row = given ?? cell?.row;
|
|
69
|
+
if (!row) throw new Error("DataTableExpandToggle needs a row, or to be inside a column's cell.");
|
|
70
|
+
const indent = { "--nuv-data-table-depth": row.depth };
|
|
71
|
+
if (!canExpand(row)) return /* @__PURE__ */ jsx("span", {
|
|
72
|
+
className: cx("nuv-data-table__expand-gap", className),
|
|
73
|
+
style: indent
|
|
74
|
+
});
|
|
75
|
+
const open = expanded(row);
|
|
76
|
+
const name = rowName(row);
|
|
77
|
+
return /* @__PURE__ */ jsx("button", {
|
|
78
|
+
type: "button",
|
|
79
|
+
className: cx("nuv-data-table__expand", className),
|
|
80
|
+
style: indent,
|
|
81
|
+
"aria-expanded": open,
|
|
82
|
+
"aria-label": open ? labels.collapseRow(name) : labels.expandRow(name),
|
|
83
|
+
onClick: row.getToggleExpandedHandler(),
|
|
84
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
85
|
+
"aria-hidden": "true",
|
|
86
|
+
className: "nuv-data-table__expand-icon",
|
|
87
|
+
viewBox: "0 0 16 16",
|
|
88
|
+
width: "16",
|
|
89
|
+
height: "16",
|
|
90
|
+
fill: "none",
|
|
91
|
+
stroke: "currentColor",
|
|
92
|
+
strokeWidth: "1.5",
|
|
93
|
+
strokeLinecap: "round",
|
|
94
|
+
strokeLinejoin: "round",
|
|
95
|
+
children: /* @__PURE__ */ jsx("path", { d: "M6 4l4 4-4 4" })
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A bar that's there while rows are selected. It says how many, holds the
|
|
101
|
+
* actions you give it, and has a button that clears the selection.
|
|
102
|
+
*/
|
|
103
|
+
function DataTableBulkActions({ children, className, ...props }) {
|
|
104
|
+
const { table, labels } = useDataTableView("DataTableBulkActions");
|
|
105
|
+
const bar = useRef(null);
|
|
106
|
+
const heldFocus = useRef(false);
|
|
107
|
+
const available = supports.selection(table);
|
|
108
|
+
const count = available ? table.getSelectedRowIds().length : 0;
|
|
109
|
+
useLayoutEffect(() => {
|
|
110
|
+
if (count > 0 || !heldFocus.current) return;
|
|
111
|
+
heldFocus.current = false;
|
|
112
|
+
const active = document.activeElement;
|
|
113
|
+
if (active && active !== document.body) return;
|
|
114
|
+
const root = bar.current?.closest(".nuv-data-table");
|
|
115
|
+
const target = root?.querySelector("[data-nuv-select-all]:not(:disabled)") ?? root?.querySelector(".nuv-table-container");
|
|
116
|
+
if (!target) return;
|
|
117
|
+
if (!target.hasAttribute("tabindex") && target.tagName === "DIV") target.setAttribute("tabindex", "-1");
|
|
118
|
+
target.focus();
|
|
119
|
+
}, [count]);
|
|
120
|
+
if (!available) return null;
|
|
121
|
+
return /* @__PURE__ */ jsx("div", {
|
|
122
|
+
ref: bar,
|
|
123
|
+
role: "group",
|
|
124
|
+
"aria-label": labels.bulkActions,
|
|
125
|
+
className: cx("nuv-data-table__bulk", className),
|
|
126
|
+
hidden: count === 0,
|
|
127
|
+
onFocus: () => {
|
|
128
|
+
heldFocus.current = true;
|
|
129
|
+
},
|
|
130
|
+
onBlur: (event) => {
|
|
131
|
+
if (event.relatedTarget) heldFocus.current = false;
|
|
132
|
+
},
|
|
133
|
+
...props,
|
|
134
|
+
children: count > 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
135
|
+
/* @__PURE__ */ jsx("span", {
|
|
136
|
+
className: "nuv-data-table__bulk-count",
|
|
137
|
+
children: labels.selected(count)
|
|
138
|
+
}),
|
|
139
|
+
/* @__PURE__ */ jsx("div", {
|
|
140
|
+
className: "nuv-data-table__bulk-actions",
|
|
141
|
+
children: typeof children === "function" ? children(table.getSelectedRowModel().rows) : children
|
|
142
|
+
}),
|
|
143
|
+
/* @__PURE__ */ jsx(Button, {
|
|
144
|
+
intent: "ghost",
|
|
145
|
+
size: "sm",
|
|
146
|
+
onClick: () => table.resetRowSelection(true),
|
|
147
|
+
children: labels.clearSelection
|
|
148
|
+
})
|
|
149
|
+
] }) : null
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { DataTableBulkActions, DataTableExpandToggle, DataTableSelectAll, DataTableSelectRow };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Column, RowData, TableFeatures } from "@tanstack/react-table";
|
|
2
|
+
import { HTMLAttributes } from "react";
|
|
3
|
+
import { InputProps } from "@nuvui/react/input";
|
|
4
|
+
//#region src/components/data-table/toolbar.d.ts
|
|
5
|
+
export interface DataTableFilterOwnProps {
|
|
6
|
+
/**
|
|
7
|
+
* How long to wait after the last keystroke before filtering, in
|
|
8
|
+
* milliseconds. Set it when each change asks a server.
|
|
9
|
+
* @default 0
|
|
10
|
+
*/
|
|
11
|
+
debounce?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DataTableFilterProps extends DataTableFilterOwnProps, Omit<InputProps, "value" | "defaultValue" | "onChange" | "type"> {}
|
|
14
|
+
/**
|
|
15
|
+
* A search field over every column that can be searched. It's the table's
|
|
16
|
+
* global filter. It needs `globalFilteringFeature`, and shows nothing
|
|
17
|
+
* without it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function DataTableFilter({ debounce, className, ...props }: DataTableFilterProps): import("react").JSX.Element | null;
|
|
20
|
+
export interface DataTableColumnFilterProps<TFeatures extends TableFeatures = any, TData extends RowData = any> extends Omit<InputProps, "value" | "defaultValue" | "onChange"> {
|
|
21
|
+
/**
|
|
22
|
+
* The column to filter. Inside a column's `header` it's found without
|
|
23
|
+
* being passed.
|
|
24
|
+
*/
|
|
25
|
+
column?: Column<TFeatures, TData, any>;
|
|
26
|
+
/** @default 0 */
|
|
27
|
+
debounce?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A text field that filters one column. For a filter that isn't text, such
|
|
31
|
+
* as a select of statuses, use any control and call
|
|
32
|
+
* `column.setFilterValue`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function DataTableColumnFilter<TFeatures extends TableFeatures, TData extends RowData>({ column: given, debounce, className, ...props }: DataTableColumnFilterProps<TFeatures, TData>): import("react").JSX.Element | null;
|
|
35
|
+
export interface DataTableColumnChooserProps {
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A button that opens a list of the columns, each with a tick that shows
|
|
40
|
+
* or hides it. Columns with `enableHiding: false` aren't in the list. It
|
|
41
|
+
* needs `columnVisibilityFeature`, and shows nothing without it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function DataTableColumnChooser({ className }: DataTableColumnChooserProps): import("react").JSX.Element | null;
|
|
44
|
+
export type DataTableToolbarProps = HTMLAttributes<HTMLDivElement>;
|
|
45
|
+
/**
|
|
46
|
+
* The row of controls above the table. With no children it holds the
|
|
47
|
+
* search field and the column chooser, each only if the table has the
|
|
48
|
+
* feature for it, and is left out when it has neither.
|
|
49
|
+
*/
|
|
50
|
+
export declare function DataTableToolbar({ className, children, ...props }: DataTableToolbarProps): import("react").JSX.Element | null;
|
|
51
|
+
//#endregion
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { canHide, columnLabel, supports, useDataTableView, useHeader } from "./context.js";
|
|
3
|
+
import { cx } from "../../utils/cx.js";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Button } from "@nuvui/react/button";
|
|
7
|
+
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger } from "@nuvui/react/dropdown-menu";
|
|
8
|
+
import { Input } from "@nuvui/react/input";
|
|
9
|
+
//#region src/components/data-table/toolbar.tsx
|
|
10
|
+
function useDebounced(value, commit, debounce) {
|
|
11
|
+
const [text, setText] = useState(value);
|
|
12
|
+
const sent = useRef(value);
|
|
13
|
+
const timer = useRef(void 0);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (value !== sent.current) {
|
|
16
|
+
sent.current = value;
|
|
17
|
+
setText(value);
|
|
18
|
+
}
|
|
19
|
+
}, [value]);
|
|
20
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
21
|
+
const change = (next) => {
|
|
22
|
+
setText(next);
|
|
23
|
+
clearTimeout(timer.current);
|
|
24
|
+
const send = () => {
|
|
25
|
+
sent.current = next;
|
|
26
|
+
commit(next);
|
|
27
|
+
};
|
|
28
|
+
if (debounce > 0) timer.current = setTimeout(send, debounce);
|
|
29
|
+
else send();
|
|
30
|
+
};
|
|
31
|
+
return [text, change];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A search field over every column that can be searched. It's the table's
|
|
35
|
+
* global filter. It needs `globalFilteringFeature`, and shows nothing
|
|
36
|
+
* without it.
|
|
37
|
+
*/
|
|
38
|
+
function DataTableFilter({ debounce = 0, className, ...props }) {
|
|
39
|
+
const { table, labels } = useDataTableView("DataTableFilter");
|
|
40
|
+
const available = supports.globalFilter(table);
|
|
41
|
+
const [text, change] = useDebounced(available ? String(table.atoms.globalFilter.get() ?? "") : "", (next) => table.setGlobalFilter(next), debounce);
|
|
42
|
+
if (!available) return null;
|
|
43
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
44
|
+
type: "search",
|
|
45
|
+
"aria-label": labels.search,
|
|
46
|
+
placeholder: labels.search,
|
|
47
|
+
className: cx("nuv-data-table__filter", className),
|
|
48
|
+
...props,
|
|
49
|
+
value: text,
|
|
50
|
+
onChange: (event) => change(event.target.value)
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A text field that filters one column. For a filter that isn't text, such
|
|
55
|
+
* as a select of statuses, use any control and call
|
|
56
|
+
* `column.setFilterValue`.
|
|
57
|
+
*/
|
|
58
|
+
function DataTableColumnFilter({ column: given, debounce = 0, className, ...props }) {
|
|
59
|
+
const { labels } = useDataTableView("DataTableColumnFilter");
|
|
60
|
+
const header = useHeader();
|
|
61
|
+
const column = given ?? header?.column;
|
|
62
|
+
if (!column) throw new Error("DataTableColumnFilter needs a column, or to be inside a column's header.");
|
|
63
|
+
const available = typeof column.getCanFilter === "function" && column.getCanFilter();
|
|
64
|
+
const [text, change] = useDebounced(available ? String(column.getFilterValue() ?? "") : "", (next) => column.setFilterValue(next === "" ? void 0 : next), debounce);
|
|
65
|
+
if (!available) return null;
|
|
66
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
67
|
+
type: "search",
|
|
68
|
+
"aria-label": labels.filterColumn(columnLabel(column)),
|
|
69
|
+
className: cx("nuv-data-table__column-filter", className),
|
|
70
|
+
...props,
|
|
71
|
+
value: text,
|
|
72
|
+
onChange: (event) => change(event.target.value)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A button that opens a list of the columns, each with a tick that shows
|
|
77
|
+
* or hides it. Columns with `enableHiding: false` aren't in the list. It
|
|
78
|
+
* needs `columnVisibilityFeature`, and shows nothing without it.
|
|
79
|
+
*/
|
|
80
|
+
function DataTableColumnChooser({ className }) {
|
|
81
|
+
const { table, labels } = useDataTableView("DataTableColumnChooser");
|
|
82
|
+
if (!supports.visibility(table)) return null;
|
|
83
|
+
const columns = table.getAllLeafColumns().filter(canHide);
|
|
84
|
+
if (columns.length === 0) return null;
|
|
85
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
86
|
+
asChild: true,
|
|
87
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
88
|
+
intent: "secondary",
|
|
89
|
+
size: "sm",
|
|
90
|
+
className: cx("nuv-data-table__chooser", className),
|
|
91
|
+
children: labels.columns
|
|
92
|
+
})
|
|
93
|
+
}), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
94
|
+
align: "end",
|
|
95
|
+
children: columns.map((column) => /* @__PURE__ */ jsx(DropdownMenuCheckboxItem, {
|
|
96
|
+
checked: column.getIsVisible(),
|
|
97
|
+
onCheckedChange: (checked) => column.toggleVisibility(checked),
|
|
98
|
+
onSelect: (event) => event.preventDefault(),
|
|
99
|
+
children: columnLabel(column)
|
|
100
|
+
}, column.id))
|
|
101
|
+
})] });
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The row of controls above the table. With no children it holds the
|
|
105
|
+
* search field and the column chooser, each only if the table has the
|
|
106
|
+
* feature for it, and is left out when it has neither.
|
|
107
|
+
*/
|
|
108
|
+
function DataTableToolbar({ className, children, ...props }) {
|
|
109
|
+
const { table } = useDataTableView("DataTableToolbar");
|
|
110
|
+
const standard = supports.globalFilter(table) || supports.visibility(table) && table.getAllLeafColumns().some(canHide);
|
|
111
|
+
if (children == null && !standard) return null;
|
|
112
|
+
return /* @__PURE__ */ jsx("div", {
|
|
113
|
+
className: cx("nuv-data-table__toolbar", className),
|
|
114
|
+
...props,
|
|
115
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
116
|
+
/* @__PURE__ */ jsx(DataTableFilter, {}),
|
|
117
|
+
/* @__PURE__ */ jsx("span", { className: "nuv-data-table__toolbar-gap" }),
|
|
118
|
+
/* @__PURE__ */ jsx(DataTableColumnChooser, {})
|
|
119
|
+
] })
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { DataTableColumnChooser, DataTableColumnFilter, DataTableFilter, DataTableToolbar };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ColumnFiltersState, PaginationState, SortingState, Updater } from "@tanstack/react-table";
|
|
2
|
+
//#region src/components/data-table/use-data-table-request.d.ts
|
|
3
|
+
/** What a server is asked for: which page, in what order, filtered how. */
|
|
4
|
+
export interface DataTableRequest {
|
|
5
|
+
pagination: PaginationState;
|
|
6
|
+
sorting: SortingState;
|
|
7
|
+
columnFilters: ColumnFiltersState;
|
|
8
|
+
globalFilter: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DataTableRequestOptions {
|
|
11
|
+
state: DataTableRequest;
|
|
12
|
+
onPaginationChange: (updater: Updater<PaginationState>) => void;
|
|
13
|
+
onSortingChange: (updater: Updater<SortingState>) => void;
|
|
14
|
+
onColumnFiltersChange: (updater: Updater<ColumnFiltersState>) => void;
|
|
15
|
+
onGlobalFilterChange: (updater: Updater<string>) => void;
|
|
16
|
+
manualPagination: true;
|
|
17
|
+
manualSorting: true;
|
|
18
|
+
manualFiltering: true;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* For a table whose sorting, filtering and paging are done by a server.
|
|
22
|
+
*
|
|
23
|
+
* It holds what the table is asking for, and returns it with the options
|
|
24
|
+
* that make the table ask and not do the work itself. Spread the options
|
|
25
|
+
* into `useDataTable`, fetch with the request, and pass the rows you get
|
|
26
|
+
* back as `data` with the total as `rowCount`.
|
|
27
|
+
*
|
|
28
|
+
* A new sort or filter goes back to the first page. TanStack does that
|
|
29
|
+
* itself when it does the paging, and not when a server does.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const [request, options] = useDataTableRequest({
|
|
33
|
+
* pagination: { pageIndex: 0, pageSize: 20 },
|
|
34
|
+
* });
|
|
35
|
+
* const page = useQuery({ queryKey: ["people", request], ... });
|
|
36
|
+
* const table = useDataTable({
|
|
37
|
+
* columns,
|
|
38
|
+
* data: page.data?.rows ?? none,
|
|
39
|
+
* rowCount: page.data?.total,
|
|
40
|
+
* ...options,
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
export declare function useDataTableRequest(initial?: Partial<DataTableRequest>): [request: DataTableRequest, options: DataTableRequestOptions];
|
|
44
|
+
//#endregion
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { functionalUpdate } from "@tanstack/react-table";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
//#region src/components/data-table/use-data-table-request.ts
|
|
5
|
+
const firstPage = (request) => ({
|
|
6
|
+
...request,
|
|
7
|
+
pagination: {
|
|
8
|
+
...request.pagination,
|
|
9
|
+
pageIndex: 0
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* For a table whose sorting, filtering and paging are done by a server.
|
|
14
|
+
*
|
|
15
|
+
* It holds what the table is asking for, and returns it with the options
|
|
16
|
+
* that make the table ask and not do the work itself. Spread the options
|
|
17
|
+
* into `useDataTable`, fetch with the request, and pass the rows you get
|
|
18
|
+
* back as `data` with the total as `rowCount`.
|
|
19
|
+
*
|
|
20
|
+
* A new sort or filter goes back to the first page. TanStack does that
|
|
21
|
+
* itself when it does the paging, and not when a server does.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const [request, options] = useDataTableRequest({
|
|
25
|
+
* pagination: { pageIndex: 0, pageSize: 20 },
|
|
26
|
+
* });
|
|
27
|
+
* const page = useQuery({ queryKey: ["people", request], ... });
|
|
28
|
+
* const table = useDataTable({
|
|
29
|
+
* columns,
|
|
30
|
+
* data: page.data?.rows ?? none,
|
|
31
|
+
* rowCount: page.data?.total,
|
|
32
|
+
* ...options,
|
|
33
|
+
* });
|
|
34
|
+
*/
|
|
35
|
+
function useDataTableRequest(initial = {}) {
|
|
36
|
+
const [request, setRequest] = useState(() => ({
|
|
37
|
+
pagination: {
|
|
38
|
+
pageIndex: 0,
|
|
39
|
+
pageSize: 10
|
|
40
|
+
},
|
|
41
|
+
sorting: [],
|
|
42
|
+
columnFilters: [],
|
|
43
|
+
globalFilter: "",
|
|
44
|
+
...initial
|
|
45
|
+
}));
|
|
46
|
+
return [request, {
|
|
47
|
+
state: request,
|
|
48
|
+
...useMemo(() => ({
|
|
49
|
+
onPaginationChange: (updater) => setRequest((old) => ({
|
|
50
|
+
...old,
|
|
51
|
+
pagination: functionalUpdate(updater, old.pagination)
|
|
52
|
+
})),
|
|
53
|
+
onSortingChange: (updater) => setRequest((old) => firstPage({
|
|
54
|
+
...old,
|
|
55
|
+
sorting: functionalUpdate(updater, old.sorting)
|
|
56
|
+
})),
|
|
57
|
+
onColumnFiltersChange: (updater) => setRequest((old) => firstPage({
|
|
58
|
+
...old,
|
|
59
|
+
columnFilters: functionalUpdate(updater, old.columnFilters)
|
|
60
|
+
})),
|
|
61
|
+
onGlobalFilterChange: (updater) => setRequest((old) => firstPage({
|
|
62
|
+
...old,
|
|
63
|
+
globalFilter: functionalUpdate(updater, old.globalFilter) ?? ""
|
|
64
|
+
})),
|
|
65
|
+
manualPagination: true,
|
|
66
|
+
manualSorting: true,
|
|
67
|
+
manualFiltering: true
|
|
68
|
+
}), [])
|
|
69
|
+
}];
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { useDataTableRequest };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Table, TableBody, TableBodyProps, TableCaption, TableCaptionOwnProps, TableCaptionProps, TableCell, TableCellOwnProps, TableCellProps, TableContainer, TableContainerProps, TableFooter, TableFooterProps, TableHead, TableHeadProps, TableHeader, TableHeaderProps, TableOwnProps, TableProps, TableRow, TableRowOwnProps, TableRowProps } from "./table.js";
|
|
2
|
+
export { Table, TableBody, type TableBodyProps, TableCaption, type TableCaptionOwnProps, type TableCaptionProps, TableCell, type TableCellOwnProps, type TableCellProps, TableContainer, type TableContainerProps, TableFooter, type TableFooterProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableOwnProps, type TableProps, TableRow, type TableRowOwnProps, type TableRowProps };
|