@api7/ui-components 0.7.0 → 0.8.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 +169 -8
- package/dist/components/anchor/anchor.d.ts +28 -0
- package/dist/components/anchor/anchor.js +249 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts +15 -0
- package/dist/components/breadcrumb/breadcrumb.js +60 -0
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/button/button.js +1 -1
- package/dist/components/calendar/calendar.js +24 -3
- package/dist/components/cascader/cascader-data.d.ts +26 -0
- package/dist/components/cascader/cascader-data.js +110 -0
- package/dist/components/cascader/cascader.d.ts +41 -0
- package/dist/components/cascader/cascader.js +513 -0
- package/dist/components/code/code.d.ts +9 -0
- package/dist/components/code/code.js +20 -0
- package/dist/components/code/copy-button.js +42 -0
- package/dist/components/code-block/code-block.d.ts +16 -0
- package/dist/components/code-block/code-block.js +45 -0
- package/dist/components/code-block/highlight.js +67 -0
- package/dist/components/collapsible/collapsible.d.ts +14 -0
- package/dist/components/collapsible/collapsible.js +36 -0
- package/dist/components/combobox/combobox-loading.d.ts +18 -0
- package/dist/components/combobox/combobox-loading.js +148 -0
- package/dist/components/combobox/combobox.d.ts +13 -6
- package/dist/components/combobox/combobox.js +58 -10
- package/dist/components/data-table/data-table.d.ts +27 -3
- package/dist/components/data-table/data-table.js +155 -40
- package/dist/components/data-table/expandable-row.js +40 -0
- package/dist/components/data-table/filter-header.d.ts +28 -5
- package/dist/components/data-table/filter-header.js +103 -40
- package/dist/components/data-table/index.d.ts +3 -3
- package/dist/components/data-table/use-table-layout.js +84 -0
- package/dist/components/date-picker/date-picker.js +11 -2
- package/dist/components/description-list/description-list.d.ts +1 -1
- package/dist/components/dialog/dialog.js +1 -1
- package/dist/components/drawer/drawer.js +1 -1
- package/dist/components/empty/empty.d.ts +1 -1
- package/dist/components/field/labeled-field.js +1 -1
- package/dist/components/file-upload/file-upload.d.ts +44 -0
- package/dist/components/file-upload/file-upload.js +195 -0
- package/dist/components/form/choice-fields.js +2 -2
- package/dist/components/form/date-fields.d.ts +1 -1
- package/dist/components/form/date-fields.js +1 -1
- package/dist/components/form/form-item.js +1 -1
- package/dist/components/form/form.js +1 -1
- package/dist/components/form/index.d.ts +2 -2
- package/dist/components/form/index.js +4 -4
- package/dist/components/form/text-field.js +2 -2
- package/dist/components/form/toggle-fields.d.ts +1 -1
- package/dist/components/form/toggle-fields.js +3 -3
- package/dist/components/pagination/pagination.d.ts +20 -4
- package/dist/components/pagination/pagination.js +83 -32
- package/dist/components/relative-date-range-picker/relative-date-range-picker.d.ts +14 -0
- package/dist/components/relative-date-range-picker/relative-date-range-picker.js +245 -0
- package/dist/components/relative-date-range-picker/relative-date-range.d.ts +26 -0
- package/dist/components/relative-date-range-picker/relative-date-range.js +109 -0
- package/dist/components/select/select.js +1 -1
- package/dist/components/sidebar/sidebar.js +2 -2
- package/dist/components/steps/steps.d.ts +23 -0
- package/dist/components/steps/steps.js +86 -0
- package/dist/components/table/table.d.ts +2 -1
- package/dist/components/table/table.js +2 -1
- package/dist/components/toast/toast.d.ts +1 -1
- package/dist/components/toast/toast.js +1 -1
- package/dist/config/config-provider.d.ts +2 -2
- package/dist/config/messages.d.ts +115 -1
- package/dist/config/messages.js +140 -2
- package/dist/hooks/use-clipboard.d.ts +11 -0
- package/dist/hooks/use-clipboard.js +75 -0
- package/dist/hooks/use-field-label-with-text.js +32 -0
- package/dist/index.d.ts +26 -13
- package/dist/index.js +15 -4
- package/dist/tokens/index.d.ts +1 -1
- package/dist/tokens/index.js +1 -1
- package/package.json +7 -6
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ExpandableRow } from "./expandable-row.js";
|
|
3
|
+
import { FilterHeader } from "./filter-header.js";
|
|
4
|
+
import { SortHeader } from "./sort-header.js";
|
|
5
|
+
import { useTableLayout } from "./use-table-layout.js";
|
|
4
6
|
import { DEFAULT_PAGE_SIZE_OPTIONS, Pagination } from "../pagination/pagination.js";
|
|
5
7
|
import { Skeleton } from "../skeleton/skeleton.js";
|
|
6
8
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../table/table.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { useEffect, useState } from "react";
|
|
9
|
+
import { useMessages } from "../../config/config-provider.js";
|
|
10
|
+
import { cn } from "../../lib/cn.js";
|
|
11
|
+
import { useEffect, useId, useState } from "react";
|
|
10
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
import { columnFilteringFeature, columnVisibilityFeature, constructFilterFn, createColumnHelper, createFilteredRowModel, createPaginatedRowModel, createSortedRowModel, flexRender, rowPaginationFeature, rowSortingFeature, sortFn_alphanumeric, sortFn_basic, sortFn_datetime, sortFn_text, tableFeatures, useTable } from "@tanstack/react-table";
|
|
13
|
+
import { columnFilteringFeature, columnPinningFeature, columnSizingFeature, columnVisibilityFeature, constructFilterFn, createColumnHelper, createFilteredRowModel, createPaginatedRowModel, createSortedRowModel, flexRender, rowExpandingFeature, rowPaginationFeature, rowSortingFeature, sortFn_alphanumeric, sortFn_basic, sortFn_datetime, sortFn_text, tableFeatures, useTable } from "@tanstack/react-table";
|
|
12
14
|
// src/components/data-table/data-table.tsx
|
|
13
15
|
const LOADING_ROW_COUNT = 4;
|
|
16
|
+
const EXPAND_COLUMN_WIDTH = 48;
|
|
14
17
|
const filterFn_oneOf = constructFilterFn({
|
|
15
18
|
filter: (value, chosen) => chosen.includes(String(value)),
|
|
16
19
|
autoRemove: (chosen) => !Array.isArray(chosen) || chosen.length === 0
|
|
17
20
|
});
|
|
18
21
|
const dataTableFeatures = tableFeatures({
|
|
19
22
|
columnFilteringFeature,
|
|
23
|
+
columnPinningFeature,
|
|
24
|
+
columnSizingFeature,
|
|
20
25
|
columnVisibilityFeature,
|
|
21
26
|
rowSortingFeature,
|
|
27
|
+
rowExpandingFeature,
|
|
22
28
|
rowPaginationFeature,
|
|
23
29
|
filteredRowModel: createFilteredRowModel(),
|
|
24
30
|
sortedRowModel: createSortedRowModel(),
|
|
@@ -34,8 +40,20 @@ const dataTableFeatures = tableFeatures({
|
|
|
34
40
|
function createDataTableColumnHelper() {
|
|
35
41
|
return createColumnHelper();
|
|
36
42
|
}
|
|
37
|
-
function
|
|
43
|
+
function hasColumnSize(columns) {
|
|
44
|
+
return columns.some((column) => column.size !== void 0 || "columns" in column && hasColumnSize(column.columns ?? []));
|
|
45
|
+
}
|
|
46
|
+
function DataTable({ columns, data, getRowId, renderExpandedRow, getRowCanExpand, expanded, defaultExpanded, onExpandedChange, columnPinning, defaultColumnPinning, onColumnPinningChange, sorting, defaultSorting, onSortingChange, manualSorting, columnFilters, onColumnFiltersChange, manualFiltering, pagination, defaultPagination, onPaginationChange, manualPagination, rowCount, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, hidePagination, paginationLabel, loading, showHeader = true, empty, toolbar, className }) {
|
|
38
47
|
const messages = useMessages();
|
|
48
|
+
const expansionColumnId = useId();
|
|
49
|
+
const expandable = renderExpandedRow !== void 0;
|
|
50
|
+
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded ?? {});
|
|
51
|
+
const expandedState = expanded ?? internalExpanded;
|
|
52
|
+
const [internalPinning, setInternalPinning] = useState(defaultColumnPinning ?? {
|
|
53
|
+
start: [],
|
|
54
|
+
end: []
|
|
55
|
+
});
|
|
56
|
+
const pinningState = columnPinning ?? internalPinning;
|
|
39
57
|
const [internalSorting, setInternalSorting] = useState(defaultSorting ?? []);
|
|
40
58
|
const sortingState = sorting ?? internalSorting;
|
|
41
59
|
const [internalFilters, setInternalFilters] = useState([]);
|
|
@@ -62,9 +80,23 @@ function DataTable({ columns, data, getRowId, sorting, defaultSorting, onSorting
|
|
|
62
80
|
columns,
|
|
63
81
|
getRowId,
|
|
64
82
|
state: {
|
|
83
|
+
expanded: expandedState,
|
|
65
84
|
sorting: sortingState,
|
|
66
85
|
columnFilters: filtersState,
|
|
67
|
-
pagination: shownPagination
|
|
86
|
+
pagination: shownPagination,
|
|
87
|
+
columnPinning: pinningState
|
|
88
|
+
},
|
|
89
|
+
getRowCanExpand: (row) => expandable && (getRowCanExpand?.(row.original) ?? true),
|
|
90
|
+
autoResetExpanded: false,
|
|
91
|
+
onExpandedChange: (updater) => {
|
|
92
|
+
const next = typeof updater === "function" ? updater(expandedState) : updater;
|
|
93
|
+
if (expanded === void 0) setInternalExpanded(next);
|
|
94
|
+
onExpandedChange?.(next);
|
|
95
|
+
},
|
|
96
|
+
onColumnPinningChange: (updater) => {
|
|
97
|
+
const next = typeof updater === "function" ? updater(pinningState) : updater;
|
|
98
|
+
if (columnPinning === void 0) setInternalPinning(next);
|
|
99
|
+
onColumnPinningChange?.(next);
|
|
68
100
|
},
|
|
69
101
|
onColumnFiltersChange: (updater) => {
|
|
70
102
|
const next = typeof updater === "function" ? updater(filtersState) : updater;
|
|
@@ -109,8 +141,25 @@ function DataTable({ columns, data, getRowId, sorting, defaultSorting, onSorting
|
|
|
109
141
|
paginationState.pageSize,
|
|
110
142
|
hidePagination
|
|
111
143
|
]);
|
|
144
|
+
const startColumns = table.getStartVisibleLeafColumns();
|
|
145
|
+
const endColumns = table.getEndVisibleLeafColumns();
|
|
146
|
+
const visibleColumns = [
|
|
147
|
+
...startColumns,
|
|
148
|
+
...table.getCenterVisibleLeafColumns(),
|
|
149
|
+
...endColumns
|
|
150
|
+
];
|
|
151
|
+
const sized = visibleColumns.some((column) => column.getIsPinned()) || hasColumnSize(columns);
|
|
152
|
+
const { tableRef, containerRef, containerWidth, columnStyles, cellClass: pinnedCellClass } = useTableLayout(JSON.stringify([
|
|
153
|
+
sized,
|
|
154
|
+
expandable,
|
|
155
|
+
visibleColumns.map((column) => [column.id, column.getSize()])
|
|
156
|
+
]), expandable && startColumns.length > 0 ? [{
|
|
157
|
+
id: expansionColumnId,
|
|
158
|
+
getSize: () => EXPAND_COLUMN_WIDTH
|
|
159
|
+
}, ...startColumns] : startColumns, endColumns);
|
|
160
|
+
const cellClass = (column, header = false) => cn(sized && "[overflow-wrap:anywhere]", pinnedCellClass(column.id, header), column.columnDef.meta?.className);
|
|
112
161
|
return /* @__PURE__ */ jsxs("div", {
|
|
113
|
-
className: cn("w-full", className),
|
|
162
|
+
className: cn("w-full min-w-0", className),
|
|
114
163
|
"data-slot": "data-table",
|
|
115
164
|
children: [
|
|
116
165
|
toolbar ? /* @__PURE__ */ jsx("div", {
|
|
@@ -118,38 +167,104 @@ function DataTable({ columns, data, getRowId, sorting, defaultSorting, onSorting
|
|
|
118
167
|
"data-slot": "data-table-toolbar",
|
|
119
168
|
children: toolbar
|
|
120
169
|
}) : null,
|
|
121
|
-
/* @__PURE__ */ jsxs(Table, {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
className:
|
|
136
|
-
children: [
|
|
137
|
-
|
|
138
|
-
|
|
170
|
+
/* @__PURE__ */ jsxs(Table, {
|
|
171
|
+
ref: tableRef,
|
|
172
|
+
containerRef,
|
|
173
|
+
className: sized ? cn("table-fixed border-separate border-spacing-0 [&_td]:border-b [&_td]:border-border", showHeader && "[&_th]:border-b [&_th]:border-border") : void 0,
|
|
174
|
+
style: sized ? { minWidth: table.getTotalSize() + (expandable ? EXPAND_COLUMN_WIDTH : 0) } : void 0,
|
|
175
|
+
children: [
|
|
176
|
+
(sized || expandable) && /* @__PURE__ */ jsxs("colgroup", { children: [expandable && /* @__PURE__ */ jsx("col", {
|
|
177
|
+
"data-column-id": expansionColumnId,
|
|
178
|
+
style: { width: EXPAND_COLUMN_WIDTH }
|
|
179
|
+
}), visibleColumns.map((column) => /* @__PURE__ */ jsx("col", {
|
|
180
|
+
"data-column-id": column.id,
|
|
181
|
+
style: sized ? { width: expandable ? `${column.getSize() / table.getTotalSize() * 100}%` : column.getSize() } : void 0
|
|
182
|
+
}, column.id))] }),
|
|
183
|
+
/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup, index) => /* @__PURE__ */ jsxs(TableRow, {
|
|
184
|
+
className: showHeader ? void 0 : "border-b-0",
|
|
185
|
+
children: [expandable && index === 0 && /* @__PURE__ */ jsx(TableHead, {
|
|
186
|
+
rowSpan: table.getHeaderGroups().length,
|
|
187
|
+
className: cn(showHeader ? "px-2" : "h-0 p-0", pinnedCellClass(expansionColumnId, true)),
|
|
188
|
+
style: columnStyles.get(expansionColumnId),
|
|
189
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
190
|
+
className: "sr-only",
|
|
191
|
+
children: messages.dataTable.rowDetails
|
|
192
|
+
})
|
|
193
|
+
}), headerGroup.headers.map((header) => {
|
|
194
|
+
const canSort = header.column.getCanSort();
|
|
195
|
+
const sorted = canSort ? header.column.getIsSorted() : false;
|
|
196
|
+
const ariaSort = sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : void 0;
|
|
197
|
+
if (!showHeader) {
|
|
198
|
+
const definition = header.column.columnDef;
|
|
199
|
+
const label = definition.meta?.accessibleLabel ?? (typeof definition.header === "string" ? definition.header : header.column.id);
|
|
200
|
+
return /* @__PURE__ */ jsx(TableHead, {
|
|
201
|
+
colSpan: header.colSpan,
|
|
202
|
+
"aria-sort": ariaSort,
|
|
203
|
+
className: "h-0 p-0",
|
|
204
|
+
children: header.isPlaceholder ? null : /* @__PURE__ */ jsx("span", {
|
|
205
|
+
className: "sr-only",
|
|
206
|
+
children: label
|
|
207
|
+
})
|
|
208
|
+
}, header.id);
|
|
209
|
+
}
|
|
210
|
+
const filter = header.column.columnDef.meta?.filter;
|
|
211
|
+
const content = flexRender(header.column.columnDef.header, header.getContext());
|
|
212
|
+
const title = canSort ? /* @__PURE__ */ jsx(SortHeader, {
|
|
213
|
+
column: header.column,
|
|
214
|
+
children: content
|
|
215
|
+
}) : content;
|
|
216
|
+
return /* @__PURE__ */ jsx(TableHead, {
|
|
217
|
+
colSpan: header.colSpan,
|
|
218
|
+
className: cellClass(header.column, true),
|
|
219
|
+
style: columnStyles.get(header.column.id),
|
|
220
|
+
"aria-sort": ariaSort,
|
|
221
|
+
children: header.isPlaceholder ? null : filter ? /* @__PURE__ */ jsxs("div", {
|
|
222
|
+
className: "flex items-center gap-1",
|
|
223
|
+
children: [title, /* @__PURE__ */ jsx(FilterHeader, {
|
|
224
|
+
column: header.column,
|
|
225
|
+
filter
|
|
226
|
+
})]
|
|
227
|
+
}) : title
|
|
228
|
+
}, header.id);
|
|
139
229
|
})]
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
230
|
+
}, headerGroup.id)) }),
|
|
231
|
+
/* @__PURE__ */ jsx(TableBody, {
|
|
232
|
+
className: "[&_[data-expanded-row]:hover]:bg-transparent",
|
|
233
|
+
children: loading ? Array.from({ length: LOADING_ROW_COUNT }).map((_, rowIndex) => /* @__PURE__ */ jsxs(TableRow, {
|
|
234
|
+
className: "h-14",
|
|
235
|
+
children: [expandable && /* @__PURE__ */ jsx(TableCell, {
|
|
236
|
+
className: pinnedCellClass(expansionColumnId),
|
|
237
|
+
style: columnStyles.get(expansionColumnId)
|
|
238
|
+
}), visibleColumns.map((column) => /* @__PURE__ */ jsx(TableCell, {
|
|
239
|
+
className: cellClass(column),
|
|
240
|
+
style: columnStyles.get(column.id),
|
|
241
|
+
children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" })
|
|
242
|
+
}, column.id))]
|
|
243
|
+
}, `skeleton-${rowIndex}`)) : table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
|
|
244
|
+
colSpan: visibleColumns.length + (expandable ? 1 : 0),
|
|
245
|
+
className: "h-[200px] text-center text-xl font-medium text-muted-foreground",
|
|
246
|
+
children: empty ?? messages.dataTable.empty
|
|
247
|
+
}) }) : table.getRowModel().rows.map((row) => {
|
|
248
|
+
const cells = row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, {
|
|
249
|
+
className: cellClass(cell.column),
|
|
250
|
+
style: columnStyles.get(cell.column.id),
|
|
251
|
+
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
252
|
+
}, cell.id));
|
|
253
|
+
return renderExpandedRow ? /* @__PURE__ */ jsx(ExpandableRow, {
|
|
254
|
+
canExpand: row.getCanExpand(),
|
|
255
|
+
expanded: row.getIsExpanded(),
|
|
256
|
+
onToggle: row.getToggleExpandedHandler(),
|
|
257
|
+
renderDetails: () => renderExpandedRow(row.original),
|
|
258
|
+
colSpan: visibleColumns.length + 1,
|
|
259
|
+
detailsWidth: startColumns.length || endColumns.length ? containerWidth : void 0,
|
|
260
|
+
controlClassName: pinnedCellClass(expansionColumnId),
|
|
261
|
+
controlStyle: columnStyles.get(expansionColumnId),
|
|
262
|
+
children: cells
|
|
263
|
+
}, row.id) : /* @__PURE__ */ jsx(TableRow, { children: cells }, row.id);
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
]
|
|
267
|
+
}),
|
|
153
268
|
hidePagination ? null : /* @__PURE__ */ jsx(Pagination, {
|
|
154
269
|
label: paginationLabel,
|
|
155
270
|
pageIndex: shownPagination.pageIndex,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { TableCell, TableRow } from "../table/table.js";
|
|
3
|
+
import { useMessages } from "../../config/config-provider.js";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { useId, useState } from "react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
// src/components/data-table/expandable-row.tsx
|
|
8
|
+
function ExpandableRow({ children, canExpand, expanded, onToggle, renderDetails, colSpan, detailsWidth, controlClassName, controlStyle }) {
|
|
9
|
+
const messages = useMessages();
|
|
10
|
+
const detailsId = useId();
|
|
11
|
+
const [visited, setVisited] = useState(expanded);
|
|
12
|
+
if (expanded && !visited) setVisited(true);
|
|
13
|
+
const hasDetails = canExpand && (expanded || visited);
|
|
14
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(TableRow, { children: [/* @__PURE__ */ jsx(TableCell, {
|
|
15
|
+
className: cn("px-2 text-center", controlClassName),
|
|
16
|
+
style: controlStyle,
|
|
17
|
+
children: canExpand && /* @__PURE__ */ jsx("button", {
|
|
18
|
+
type: "button",
|
|
19
|
+
"aria-label": expanded ? messages.dataTable.collapseRow : messages.dataTable.expandRow,
|
|
20
|
+
"aria-expanded": expanded,
|
|
21
|
+
"aria-controls": hasDetails ? detailsId : void 0,
|
|
22
|
+
onClick: onToggle,
|
|
23
|
+
className: cn("relative inline-flex size-4 cursor-pointer align-sub rounded-sm border border-border bg-background text-foreground outline-none hover:border-primary-text hover:text-primary-text focus-visible:ring-2 focus-visible:ring-ring/40", "before:absolute before:inset-x-[3px] before:top-1/2 before:h-px before:-translate-y-1/2 before:bg-current before:content-['']", "after:absolute after:inset-y-[3px] after:left-1/2 after:w-px after:-translate-x-1/2 after:bg-current after:transition-transform after:duration-300 after:content-[''] motion-reduce:after:transition-none", expanded && "after:rotate-90")
|
|
24
|
+
})
|
|
25
|
+
}), children] }), hasDetails && /* @__PURE__ */ jsx(TableRow, {
|
|
26
|
+
id: detailsId,
|
|
27
|
+
hidden: !expanded,
|
|
28
|
+
"data-expanded-row": "",
|
|
29
|
+
children: /* @__PURE__ */ jsx(TableCell, {
|
|
30
|
+
colSpan,
|
|
31
|
+
className: "bg-foreground/[0.02] p-4 leading-[1.5714285714]",
|
|
32
|
+
children: detailsWidth === void 0 ? renderDetails() : /* @__PURE__ */ jsx("div", {
|
|
33
|
+
className: "sticky start-0 -m-4 overflow-hidden p-4",
|
|
34
|
+
style: { width: detailsWidth || void 0 },
|
|
35
|
+
children: renderDetails()
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
})] });
|
|
39
|
+
}
|
|
40
|
+
export { ExpandableRow };
|
|
@@ -1,20 +1,43 @@
|
|
|
1
1
|
import "./data-table.js";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
|
-
import
|
|
3
|
+
import "@tanstack/react-table";
|
|
4
4
|
export interface DataTableColumnFilterOption {
|
|
5
5
|
value: string;
|
|
6
6
|
label: ReactNode;
|
|
7
7
|
/** Shown at the end of the option, for example how many rows have this value. */
|
|
8
8
|
count?: number;
|
|
9
9
|
}
|
|
10
|
-
export interface
|
|
10
|
+
export interface DataTableFilterPanelProps {
|
|
11
|
+
/** Current choices, including uncommitted edits when applyOn is `close`. */
|
|
12
|
+
value: string[];
|
|
13
|
+
/** Updates the selection, or only the draft when applyOn is `close`. */
|
|
14
|
+
onValueChange: (value: string[]) => void;
|
|
15
|
+
/** Restores defaultValue or clears the selection, following the same commit timing. */
|
|
16
|
+
reset: () => void;
|
|
17
|
+
/** Closes the panel and commits a changed draft once when applyOn is `close`. */
|
|
18
|
+
close: () => void;
|
|
19
|
+
}
|
|
20
|
+
interface DataTableColumnFilterBase {
|
|
11
21
|
/** Names the filter for assistive technology, for example `Status`. */
|
|
12
22
|
label: string;
|
|
13
|
-
|
|
23
|
+
/** Classes for the custom panel surface, for example a compact, unpadded grouped list. */
|
|
24
|
+
contentClassName?: string;
|
|
25
|
+
/** Defaults to immediate updates. `close` keeps a draft until dismissal (including Escape). */
|
|
26
|
+
applyOn?: 'change' | 'close';
|
|
27
|
+
onOpenChange?: (open: boolean) => void;
|
|
14
28
|
/**
|
|
15
29
|
* The values that are checked while the column has no filter, for a filter whose unfiltered state is "every
|
|
16
|
-
* value", so that what is checked agrees with the rows on screen. A column with
|
|
30
|
+
* value", so that what is checked agrees with the rows on screen. A column with nonempty defaults never has nothing
|
|
17
31
|
* checked: the last checked value cannot be unchecked, and Reset checks the default again.
|
|
18
32
|
*/
|
|
19
33
|
defaultValue?: string[];
|
|
20
|
-
}
|
|
34
|
+
}
|
|
35
|
+
export type DataTableColumnFilter = DataTableColumnFilterBase & ({
|
|
36
|
+
options: DataTableColumnFilterOption[];
|
|
37
|
+
render?: never;
|
|
38
|
+
} | {
|
|
39
|
+
/** Custom controls in a Popover, sharing the column's selection and commit timing. */
|
|
40
|
+
render: (props: DataTableFilterPanelProps) => ReactNode;
|
|
41
|
+
options?: never;
|
|
42
|
+
});
|
|
43
|
+
export {};
|
|
@@ -1,54 +1,117 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useMessages } from "../../config/config-provider.js";
|
|
3
2
|
import { Button } from "../button/button.js";
|
|
4
3
|
import { Menu, MenuCheckboxItem, MenuContent, MenuItem, MenuSeparator, MenuTrigger } from "../menu/menu.js";
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../popover/popover.js";
|
|
5
|
+
import { useMessages } from "../../config/config-provider.js";
|
|
6
|
+
import { cn } from "../../lib/cn.js";
|
|
7
|
+
import { useRef, useState } from "react";
|
|
5
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
9
|
import { Funnel } from "lucide-react";
|
|
7
10
|
// src/components/data-table/filter-header.tsx
|
|
8
11
|
function FilterHeader({ column, filter }) {
|
|
9
12
|
const messages = useMessages();
|
|
10
13
|
const filterValue = column.getFilterValue();
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
+
const committed = filterValue?.length ? filterValue : filter.defaultValue ?? [];
|
|
15
|
+
const [open, setOpen] = useState(false);
|
|
16
|
+
const [draft, setDraft] = useState();
|
|
17
|
+
const pending = useRef(void 0);
|
|
18
|
+
const deferred = filter.applyOn === "close";
|
|
19
|
+
const chosen = deferred && open ? draft ?? committed : committed;
|
|
20
|
+
const required = Boolean(filter.defaultValue?.length);
|
|
21
|
+
const normalize = (next) => {
|
|
22
|
+
const isDefault = required && equalValues(filter.defaultValue, next);
|
|
23
|
+
return next.length > 0 && !isDefault ? next : void 0;
|
|
24
|
+
};
|
|
25
|
+
const commit = (next) => {
|
|
26
|
+
const normalized = normalize(next);
|
|
27
|
+
if (!equalValues(normalized ?? [], filterValue ?? [])) column.setFilterValue(normalized);
|
|
28
|
+
};
|
|
14
29
|
const change = (next) => {
|
|
15
|
-
|
|
16
|
-
|
|
30
|
+
if (required && next.length === 0) return;
|
|
31
|
+
if (deferred) {
|
|
32
|
+
pending.current = next;
|
|
33
|
+
setDraft(next);
|
|
34
|
+
} else commit(next);
|
|
17
35
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
36
|
+
const changeOpen = (next) => {
|
|
37
|
+
if (next === open) return;
|
|
38
|
+
if (next) {
|
|
39
|
+
pending.current = void 0;
|
|
40
|
+
setDraft(void 0);
|
|
41
|
+
} else if (deferred && pending.current !== void 0) {
|
|
42
|
+
commit(pending.current);
|
|
43
|
+
pending.current = void 0;
|
|
44
|
+
}
|
|
45
|
+
setOpen(next);
|
|
46
|
+
filter.onOpenChange?.(next);
|
|
47
|
+
};
|
|
48
|
+
const reset = () => change(filter.defaultValue ?? []);
|
|
49
|
+
const indicated = deferred && open ? normalize(chosen) : filterValue;
|
|
50
|
+
const active = Boolean(indicated?.length);
|
|
51
|
+
const trigger = /* @__PURE__ */ jsx(Button, {
|
|
52
|
+
variant: "ghost",
|
|
53
|
+
size: "icon",
|
|
54
|
+
className: "size-6",
|
|
55
|
+
"aria-label": active ? messages.dataTable.filterActive({
|
|
56
|
+
label: filter.label,
|
|
57
|
+
count: indicated.length
|
|
58
|
+
}) : messages.dataTable.filter({ label: filter.label })
|
|
59
|
+
});
|
|
60
|
+
const icon = /* @__PURE__ */ jsx(Funnel, {
|
|
61
|
+
"aria-hidden": "true",
|
|
62
|
+
className: active ? "text-primary-text fill-current" : "text-muted-foreground"
|
|
63
|
+
});
|
|
64
|
+
if (filter.render) return /* @__PURE__ */ jsxs(Popover, {
|
|
65
|
+
open,
|
|
66
|
+
onOpenChange: changeOpen,
|
|
67
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
68
|
+
render: trigger,
|
|
69
|
+
children: icon
|
|
70
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
71
|
+
"aria-label": messages.dataTable.filter({ label: filter.label }),
|
|
72
|
+
className: cn("max-h-80 overflow-y-auto", filter.contentClassName),
|
|
73
|
+
children: /* @__PURE__ */ jsx(CustomFilterContent, {
|
|
74
|
+
render: filter.render,
|
|
75
|
+
value: chosen,
|
|
76
|
+
onValueChange: change,
|
|
77
|
+
reset,
|
|
78
|
+
close: () => changeOpen(false)
|
|
50
79
|
})
|
|
51
|
-
]
|
|
52
|
-
})
|
|
80
|
+
})]
|
|
81
|
+
});
|
|
82
|
+
return /* @__PURE__ */ jsxs(Menu, {
|
|
83
|
+
open,
|
|
84
|
+
onOpenChange: changeOpen,
|
|
85
|
+
children: [/* @__PURE__ */ jsx(MenuTrigger, {
|
|
86
|
+
render: trigger,
|
|
87
|
+
children: icon
|
|
88
|
+
}), /* @__PURE__ */ jsxs(MenuContent, {
|
|
89
|
+
align: "start",
|
|
90
|
+
className: "min-w-48",
|
|
91
|
+
children: [
|
|
92
|
+
filter.options.map((option) => /* @__PURE__ */ jsxs(MenuCheckboxItem, {
|
|
93
|
+
checked: chosen.includes(option.value),
|
|
94
|
+
disabled: required && chosen.length === 1 && chosen.includes(option.value),
|
|
95
|
+
onCheckedChange: (on) => change(on ? [...chosen, option.value] : chosen.filter((value) => value !== option.value)),
|
|
96
|
+
children: [option.label, option.count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
97
|
+
className: "ml-auto pl-4 text-muted-foreground",
|
|
98
|
+
children: option.count
|
|
99
|
+
})]
|
|
100
|
+
}, option.value)),
|
|
101
|
+
/* @__PURE__ */ jsx(MenuSeparator, {}),
|
|
102
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
103
|
+
disabled: normalize(chosen) === void 0,
|
|
104
|
+
onClick: reset,
|
|
105
|
+
children: messages.dataTable.resetFilter
|
|
106
|
+
})
|
|
107
|
+
]
|
|
108
|
+
})]
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function CustomFilterContent({ render, ...props }) {
|
|
112
|
+
return render(props);
|
|
113
|
+
}
|
|
114
|
+
function equalValues(a, b) {
|
|
115
|
+
return a.length === b.length && a.every((value) => b.includes(value));
|
|
53
116
|
}
|
|
54
117
|
export { FilterHeader };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DataTableColumnFilter, DataTableColumnFilterOption } from "./filter-header.js";
|
|
2
1
|
import { DataTable, DataTableColumnDef, DataTableFeatures, DataTableProps, DataTableRowActions, createDataTableColumnHelper, dataTableFeatures } from "./data-table.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import { DataTableColumnFilter, DataTableColumnFilterOption, DataTableFilterPanelProps } from "./filter-header.js";
|
|
3
|
+
import { ColumnFiltersState, ColumnPinningState, ExpandedState, PaginationState, SortingState } from "@tanstack/react-table";
|
|
4
|
+
export { type ColumnFiltersState, type ColumnPinningState, DataTable, type DataTableColumnDef, type DataTableColumnFilter, type DataTableColumnFilterOption, type DataTableFeatures, type DataTableFilterPanelProps, type DataTableProps, DataTableRowActions, type ExpandedState, type PaginationState, type SortingState, createDataTableColumnHelper, dataTableFeatures };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/cn.js";
|
|
3
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
// src/components/data-table/use-table-layout.ts
|
|
5
|
+
/** Measure the rendered colgroup: table layout may distribute spare width beyond column.size. */
|
|
6
|
+
function useTableLayout(columnKey, startColumns, endColumns) {
|
|
7
|
+
const tableRef = useRef(null);
|
|
8
|
+
const containerRef = useRef(null);
|
|
9
|
+
const [layout, setLayout] = useState({
|
|
10
|
+
widths: {},
|
|
11
|
+
start: false,
|
|
12
|
+
end: false,
|
|
13
|
+
rtl: false,
|
|
14
|
+
containerWidth: 0
|
|
15
|
+
});
|
|
16
|
+
useLayoutEffect(() => {
|
|
17
|
+
const table = tableRef.current;
|
|
18
|
+
const container = containerRef.current;
|
|
19
|
+
if (!table || !container) return;
|
|
20
|
+
const scrollEdges = () => {
|
|
21
|
+
const offset = Math.abs(container.scrollLeft);
|
|
22
|
+
return {
|
|
23
|
+
start: offset > 1,
|
|
24
|
+
end: container.scrollWidth - container.clientWidth - offset > 1
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const updateScrollEdges = () => {
|
|
28
|
+
const edges = scrollEdges();
|
|
29
|
+
setLayout((previous) => previous.start === edges.start && previous.end === edges.end ? previous : {
|
|
30
|
+
...previous,
|
|
31
|
+
...edges
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
const measure = () => {
|
|
35
|
+
const widths = Object.fromEntries([...table.querySelectorAll("col[data-column-id]")].map((col) => [col.dataset.columnId, col.getBoundingClientRect().width]));
|
|
36
|
+
const containerWidth = container.clientWidth;
|
|
37
|
+
const edges = scrollEdges();
|
|
38
|
+
const rtl = getComputedStyle(container).direction === "rtl";
|
|
39
|
+
setLayout((previous) => {
|
|
40
|
+
return Object.keys(previous.widths).length === Object.keys(widths).length && Object.entries(widths).every(([id, width]) => previous.widths[id] === width) && previous.start === edges.start && previous.end === edges.end && previous.rtl === rtl && previous.containerWidth === containerWidth ? previous : {
|
|
41
|
+
widths,
|
|
42
|
+
...edges,
|
|
43
|
+
rtl,
|
|
44
|
+
containerWidth
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
measure();
|
|
49
|
+
const observer = new ResizeObserver(measure);
|
|
50
|
+
observer.observe(container);
|
|
51
|
+
observer.observe(table);
|
|
52
|
+
container.addEventListener("scroll", updateScrollEdges, { passive: true });
|
|
53
|
+
return () => {
|
|
54
|
+
observer.disconnect();
|
|
55
|
+
container.removeEventListener("scroll", updateScrollEdges);
|
|
56
|
+
};
|
|
57
|
+
}, [columnKey]);
|
|
58
|
+
const columnStyles = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const side of ["start", "end"]) {
|
|
60
|
+
const region = side === "start" ? startColumns : [...endColumns].reverse();
|
|
61
|
+
let offset = 0;
|
|
62
|
+
for (const [index, column] of region.entries()) {
|
|
63
|
+
const shadow = index === region.length - 1 && layout[side];
|
|
64
|
+
const shadowDirection = side === "start" !== layout.rtl ? 1 : -1;
|
|
65
|
+
columnStyles.set(column.id, {
|
|
66
|
+
position: "sticky",
|
|
67
|
+
[side === "start" ? "insetInlineStart" : "insetInlineEnd"]: offset,
|
|
68
|
+
zIndex: 1,
|
|
69
|
+
"--data-table-shadow": shadow ? `inset ${shadowDirection * 10}px 0 8px -8px color-mix(in srgb, var(--color-foreground) 7%, transparent)` : "none",
|
|
70
|
+
"--data-table-shadow-start": side === "start" ? "100%" : "-30px"
|
|
71
|
+
});
|
|
72
|
+
offset += layout.widths[column.id] ?? column.getSize();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const cellClass = (columnId, header = false) => columnStyles.has(columnId) ? cn("after:pointer-events-none after:absolute after:top-0 after:-bottom-px after:start-[var(--data-table-shadow-start)] after:w-[30px] after:shadow-[var(--data-table-shadow)] after:content-['']", !header && "bg-background group-hover/row:bg-[color-mix(in_srgb,var(--color-muted)_50%,var(--color-background))]") : void 0;
|
|
76
|
+
return {
|
|
77
|
+
tableRef,
|
|
78
|
+
containerRef,
|
|
79
|
+
containerWidth: layout.containerWidth,
|
|
80
|
+
columnStyles,
|
|
81
|
+
cellClass
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export { useTableLayout };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useMessages } from "../../config/config-provider.js";
|
|
3
|
-
import { cn } from "../../lib/cn.js";
|
|
4
2
|
import { Calendar } from "../calendar/calendar.js";
|
|
5
3
|
import { Popover, PopoverContent, PopoverTrigger } from "../popover/popover.js";
|
|
4
|
+
import { useMessages } from "../../config/config-provider.js";
|
|
5
|
+
import { useFieldLabelWithText } from "../../hooks/use-field-label-with-text.js";
|
|
6
|
+
import { cn } from "../../lib/cn.js";
|
|
6
7
|
import { controlFocus, controlSurface } from "../../lib/control.js";
|
|
7
8
|
import { useCallback, useState } from "react";
|
|
8
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -67,15 +68,19 @@ function DatePicker(props) {
|
|
|
67
68
|
setValue(withTimeString(currentValue, timeString));
|
|
68
69
|
}, [currentValue, setValue]);
|
|
69
70
|
const resolvedPlaceholder = placeholder ?? messages.datePicker.placeholder;
|
|
71
|
+
const { ref: triggerRef, textId, labelledBy } = useFieldLabelWithText();
|
|
70
72
|
return /* @__PURE__ */ jsxs(Popover, {
|
|
71
73
|
onOpenChange: (open) => onOpenChange?.(open),
|
|
72
74
|
children: [/* @__PURE__ */ jsx(Field.Control, {
|
|
73
75
|
disabled,
|
|
74
76
|
render: /* @__PURE__ */ jsxs(PopoverTrigger, {
|
|
77
|
+
ref: triggerRef,
|
|
78
|
+
"aria-labelledby": labelledBy,
|
|
75
79
|
disabled,
|
|
76
80
|
onBlur,
|
|
77
81
|
className: cn(controlSurface, controlFocus, "flex h-8 items-center gap-2 px-3 text-left", className),
|
|
78
82
|
children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "size-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsx("span", {
|
|
83
|
+
id: textId,
|
|
79
84
|
className: cn("min-w-0 flex-1 truncate", !currentValue && "text-muted-foreground"),
|
|
80
85
|
children: currentValue ? format(currentValue) : resolvedPlaceholder
|
|
81
86
|
})]
|
|
@@ -159,6 +164,7 @@ function DateRangePicker(props) {
|
|
|
159
164
|
onOpenChange?.(false);
|
|
160
165
|
}, [onOpenChange, setValue]);
|
|
161
166
|
const resolvedPlaceholder = placeholder ?? messages.datePicker.rangePlaceholder;
|
|
167
|
+
const { ref: triggerRef, textId, labelledBy } = useFieldLabelWithText();
|
|
162
168
|
const displayText = currentValue?.from ? currentValue.to ? `${format(currentValue.from)} ~ ${format(currentValue.to)}` : format(currentValue.from) : void 0;
|
|
163
169
|
return /* @__PURE__ */ jsxs(Popover, {
|
|
164
170
|
open,
|
|
@@ -169,10 +175,13 @@ function DateRangePicker(props) {
|
|
|
169
175
|
children: [/* @__PURE__ */ jsx(Field.Control, {
|
|
170
176
|
disabled,
|
|
171
177
|
render: /* @__PURE__ */ jsxs(PopoverTrigger, {
|
|
178
|
+
ref: triggerRef,
|
|
179
|
+
"aria-labelledby": labelledBy,
|
|
172
180
|
disabled,
|
|
173
181
|
onBlur,
|
|
174
182
|
className: cn(controlSurface, controlFocus, "flex h-8 items-center gap-2 px-3 text-left", className),
|
|
175
183
|
children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "size-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsx("span", {
|
|
184
|
+
id: textId,
|
|
176
185
|
className: cn("min-w-0 flex-1 truncate", !displayText && "text-muted-foreground"),
|
|
177
186
|
children: displayText ?? resolvedPlaceholder
|
|
178
187
|
})]
|