@alexgorbatchev/shadcn 0.0.55 → 0.0.56
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 +6 -0
- package/dist/{chunk-x7emg3jx.js → chunk-74xynyzg.js} +73 -18
- package/dist/{chunk-aye7r5mq.js → chunk-zwnba1pb.js} +58 -41
- package/dist/components/InlineEdit.d.ts +1 -1
- package/dist/components/data-grid/DataGridColumnFitContext.d.ts +6 -0
- package/dist/components/data-grid/DataGridColumnResizer.d.ts +1 -1
- package/dist/components/data-grid/constrainDataGridColumnSizing.d.ts +9 -0
- package/dist/components/data-grid/fitDataGridColumnSizing.d.ts +8 -0
- package/dist/components/data-grid/getDataGridColumnFit.d.ts +4 -0
- package/dist/components/data-grid/getDataGridColumnFitMaximum.d.ts +4 -0
- package/dist/components/data-grid/hooks/useDataGridColumnFit.d.ts +6 -0
- package/dist/components/data-grid/index.d.ts +2 -0
- package/dist/components/data-grid/index.js +52 -10
- package/dist/components/json-render/index.js +1 -1
- package/dist/components/virtual-data-grid/VirtualDataGrid.d.ts +11 -1
- package/dist/components/virtual-data-grid/index.js +81 -23
- package/dist/index.js +1 -1
- package/dist/prototype.js +3 -1
- package/llms/components/data-display/VirtualDataGrid.md +16 -0
- package/llms/components/forms/InlineEdit.md +23 -1
- package/llms/components/forms/RichTextEditor.md +6 -0
- package/llms/setup.md +6 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -429,6 +429,8 @@ import {
|
|
|
429
429
|
|
|
430
430
|
`InlineEdit` mounts an edit session with a controlled `value`, `onValueChange`, `onCommit`, and `onCancel`. Mount it in place of the displayed label. Its input focuses and selects its text by default; `shouldSelectOnMount={false}` preserves the initial caret. Enter or blur commits once, Escape cancels without committing, and asynchronous saves disable the input until completion. Set `hasChanges={false}` to cancel an unchanged edit before calling `onCommit`; the caller owns normalization and comparison. It defaults to true, so creation sessions can commit their initial value. Rejected saves display a generic error and allow retry; `renderError` can map the unknown failure to application-authored guidance.
|
|
431
431
|
|
|
432
|
+
The editor inherits the containing label's font size and line height and occupies that single line. Keep the same text context around display and edit states. Validation appears in an anchored tooltip with an announced alert, so errors do not move neighboring content.
|
|
433
|
+
|
|
432
434
|
`VirtualTable` renders a bounded, dynamically measured set of native table rows using TanStack Virtual. Supply `items`, stable `getItemKey`, `header`, `label`, and `renderRow`; spread the supplied measurement props onto each native row. Optional `columns` accepts a `TableColumnGroup`; responsive columns keep their widths aligned with cells that share their `visibleFrom`. `maxHeight` defaults to `65vh`, `estimateRowHeight` to 80 pixels, and `overscan` to five rows. `retainedKeys` keeps editing rows mounted; the focused row is retained automatically. Filtering and selection belong to the consumer.
|
|
433
435
|
|
|
434
436
|
`VirtualDataGrid` renders a TanStack Table instance built with `dataGridFeatures` through `VirtualTable`: every filtered and sorted row, without pagination, in a fixed-layout table. Column `meta` sets `label`, `textAlign`, `truncate`, `visibleFrom`, `fill`, and `sortColumnIds`; a `fill` column takes the width the sized columns leave, and `sortColumnIds` names other columns the header sorts with its own buttons so its `aria-sort` reports them. Other resizable columns get a window-splitter handle that resizes by dragging, by the arrow keys, Home, and End, and resets on double-click. Set `columnResizeMode: "onChange"` so columns follow the pointer while dragging; TanStack's default `"onEnd"` resizes only on release. Widths change through CSS variables, so a `useTable` selector can omit `columnSizing` and `columnResizing` to keep resizing from rendering rows. `renderRow` receives each row and its measurement props; spread the props onto a `TableRow` and render `DataGridCells` inside it to wrap rows in menus or drag sources. `retainedRowIds` keeps rows mounted while they hold editors. `DataGridColumnOptions` shows, hides, and reorders the columns that allow hiding; its reset restores the table's initial visibility, order, and sizes.
|
|
@@ -437,6 +439,10 @@ Column `meta.filter` offers a column in `DataGridFilters`, hidden columns includ
|
|
|
437
439
|
|
|
438
440
|
Import `DataGridSelectHeader` and `DataGridSelectCell` from `@alexgorbatchev/shadcn/data-grid`. Pass the current `useTable` result as `table` to both controls, and pass `row` and an accessible `label` to each cell. The header selects selectable filtered rows across all pages and preserves selections hidden by filters. It shows a mixed state when some of those rows are selected and disables itself when none are selectable. Its default label is "Select all filtered rows"; provide `label` to name the scope in your application. Cells follow native row selection rules, including disabled rows and partially selected parents. The controls observe selection changes without requiring the parent table selector to include selection state.
|
|
439
441
|
|
|
442
|
+
For grids that must fit their container, set `VirtualDataGrid fitColumns`. Use exactly one `meta.fill` column with an explicit `minSize`; required columns' minima plus that minimum must fit the smallest supported container. The grid reserves that space and temporarily hides optional columns that do not fit, prioritizing their chosen order. It lists those columns and explains how to reveal them. This mode replaces `meta.visibleFrom` breakpoints and offers resize handles on leaf headers only.
|
|
443
|
+
|
|
444
|
+
Keep preferred widths separate from the table's rendered sizing atom. In `onFitWidthChange`, store the reported width in a ref and write `fitDataGridColumnSizing(table, preferredWidths, width)` into the sizing atom. The helper temporarily shrinks required resizable columns toward their minima when needed and restores their preferred widths when space returns. In `onColumnSizingChange`, obtain new preferences with `constrainDataGridColumnSizing(table, updater, widthRef.current, preferredWidths)`, then derive the sizing atom with `fitDataGridColumnSizing`. Persist only the preferred widths, after the resize settles. Native pointer and keyboard resizing starts at the displayed width, stays within the available space, and leaves other columns' preferences unchanged. Container changes never overwrite user visibility, order, or width preferences. The `VirtualDataGridExample` fixture demonstrates this controlled integration.
|
|
445
|
+
|
|
440
446
|
`ActionsMenu` shares one `items` definition between a dropdown action and an optional context menu. Supply an accessible `label`; the default trigger is a ghost icon button. `size="sm"` displays the label, and `variant`, `disabled`, and `trigger` customize the action. Supplying `contextTarget` composes the native context trigger with that element and adds its popup. Compose items with `MenuItem`, `MenuLinkItem`, checkbox/radio parts, groups, labels, separators, shortcuts, and submenu parts; dropdown and context menus share these implementations. `MenuLinkItem` retains its native anchor and menu keyboard semantics.
|
|
441
447
|
|
|
442
448
|
For nested layouts, compose `ActionsMenuTrigger`, `ActionsMenuContextTarget`, and `ActionsMenuContextContent` explicitly inside `ActionsMenu`. The dropdown trigger owns its local native dropdown root and popup; the context target and content share an independent context root. A tree row can use `TreeItem render={<ActionsMenuContextTarget />}` and put the trigger in its action slot. For a table row, render the context target as the actual `tr`, then place the dropdown trigger and context content inside a `td`: native popup portals insert focus guards at their declaration, which must remain valid table children.
|
|
@@ -46,10 +46,51 @@ import {
|
|
|
46
46
|
tableFeatures
|
|
47
47
|
} from "@tanstack/react-table";
|
|
48
48
|
import { ArrowDownIcon, ArrowUpIcon, ChevronsUpDownIcon } from "lucide-react";
|
|
49
|
-
import { useId } from "react";
|
|
49
|
+
import { useContext as useContext2, useId } from "react";
|
|
50
|
+
|
|
51
|
+
// src/components/data-grid/DataGridColumnFitContext.ts
|
|
52
|
+
import { createContext } from "react";
|
|
53
|
+
var DataGridColumnFitContext = createContext(undefined);
|
|
50
54
|
|
|
51
55
|
// src/components/data-grid/DataGridColumnResizer.tsx
|
|
52
56
|
import { Subscribe } from "@tanstack/react-table";
|
|
57
|
+
import { useContext } from "react";
|
|
58
|
+
|
|
59
|
+
// src/components/data-grid/getDataGridColumnFit.ts
|
|
60
|
+
function getDataGridColumnFit(table, width) {
|
|
61
|
+
const columns = table.getVisibleLeafColumns();
|
|
62
|
+
let remaining = width;
|
|
63
|
+
for (const column of columns) {
|
|
64
|
+
if (column.columnDef.meta?.fill)
|
|
65
|
+
remaining -= column.columnDef.minSize ?? 20;
|
|
66
|
+
else if (!column.getCanHide())
|
|
67
|
+
remaining -= column.getSize();
|
|
68
|
+
}
|
|
69
|
+
const hidden = [];
|
|
70
|
+
for (const column of columns) {
|
|
71
|
+
if (column.columnDef.meta?.fill || !column.getCanHide())
|
|
72
|
+
continue;
|
|
73
|
+
if (column.getSize() <= remaining)
|
|
74
|
+
remaining -= column.getSize();
|
|
75
|
+
else
|
|
76
|
+
hidden.push(column.id);
|
|
77
|
+
}
|
|
78
|
+
return hidden;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/components/data-grid/getDataGridColumnFitMaximum.ts
|
|
82
|
+
function getDataGridColumnFitMaximum(table, columnId, width) {
|
|
83
|
+
const hidden = getDataGridColumnFit(table, width);
|
|
84
|
+
const column = table.getColumn(columnId);
|
|
85
|
+
const occupied = table.getVisibleLeafColumns().reduce((sum, other) => {
|
|
86
|
+
if (other.id === columnId || hidden.includes(other.id))
|
|
87
|
+
return sum;
|
|
88
|
+
return sum + (other.columnDef.meta?.fill ? other.columnDef.minSize ?? 20 : other.getSize());
|
|
89
|
+
}, 0);
|
|
90
|
+
return Math.max(column?.columnDef.minSize ?? 20, Math.min(column?.columnDef.maxSize ?? Number.MAX_SAFE_INTEGER, Math.floor(width - occupied)));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/components/data-grid/DataGridColumnResizer.tsx
|
|
53
94
|
import { jsx } from "react/jsx-runtime";
|
|
54
95
|
var KEYBOARD_STEP = 16;
|
|
55
96
|
function DataGridColumnResizer({
|
|
@@ -58,14 +99,16 @@ function DataGridColumnResizer({
|
|
|
58
99
|
}) {
|
|
59
100
|
const column = header.column;
|
|
60
101
|
const table = column.table;
|
|
102
|
+
const fit = useContext(DataGridColumnFitContext);
|
|
61
103
|
const minSize = column.columnDef.minSize ?? 0;
|
|
62
|
-
const
|
|
104
|
+
const getMaxSize = () => fit ? getDataGridColumnFitMaximum(table, column.id, fit.width) : column.columnDef.maxSize ?? Number.MAX_SAFE_INTEGER;
|
|
63
105
|
const resize = header.getResizeHandler();
|
|
64
|
-
const setSize = (size) => table.setColumnSizing((sizes) => ({ ...sizes, [column.id]: Math.min(
|
|
106
|
+
const setSize = (size) => table.setColumnSizing((sizes) => ({ ...sizes, [column.id]: Math.min(getMaxSize(), Math.max(minSize, size)) }));
|
|
65
107
|
const onKeyDown = (event) => {
|
|
66
108
|
const growKey = table.options.columnResizeDirection === "rtl" ? "ArrowLeft" : "ArrowRight";
|
|
67
109
|
const shrinkKey = growKey === "ArrowRight" ? "ArrowLeft" : "ArrowRight";
|
|
68
110
|
const size = column.getSize();
|
|
111
|
+
const maxSize = getMaxSize();
|
|
69
112
|
const next = event.key === growKey ? size + KEYBOARD_STEP : event.key === shrinkKey ? size - KEYBOARD_STEP : event.key === "Home" ? minSize : event.key === "End" && maxSize < Number.MAX_SAFE_INTEGER ? maxSize : undefined;
|
|
70
113
|
if (next === undefined)
|
|
71
114
|
return;
|
|
@@ -74,14 +117,14 @@ function DataGridColumnResizer({
|
|
|
74
117
|
};
|
|
75
118
|
return /* @__PURE__ */ jsx(Subscribe, {
|
|
76
119
|
source: table.store,
|
|
77
|
-
selector: (state) => `${state.columnSizing[column.id]}:${state.columnResizing.isResizingColumn === column.id}`,
|
|
120
|
+
selector: (state) => `${state.columnSizing[column.id]}:${getMaxSize()}:${state.columnResizing.isResizingColumn === column.id}`,
|
|
78
121
|
children: () => /* @__PURE__ */ jsx("div", {
|
|
79
122
|
role: "separator",
|
|
80
123
|
"aria-orientation": "vertical",
|
|
81
124
|
"aria-label": `Resize ${label}`,
|
|
82
125
|
"aria-valuenow": column.getSize(),
|
|
83
126
|
"aria-valuemin": minSize,
|
|
84
|
-
"aria-valuemax":
|
|
127
|
+
"aria-valuemax": getMaxSize() < Number.MAX_SAFE_INTEGER ? getMaxSize() : undefined,
|
|
85
128
|
tabIndex: 0,
|
|
86
129
|
"data-slot": "data-grid-column-resizer",
|
|
87
130
|
"data-resizing": column.getIsResizing() || undefined,
|
|
@@ -139,18 +182,23 @@ function getAriaSort(column) {
|
|
|
139
182
|
const sorted = sortable.map((candidate) => candidate?.getIsSorted()).find(Boolean);
|
|
140
183
|
return sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : "none";
|
|
141
184
|
}
|
|
142
|
-
function DataGridHeaderCell({
|
|
185
|
+
function DataGridHeaderCell({
|
|
186
|
+
header,
|
|
187
|
+
isResizable,
|
|
188
|
+
colSpan
|
|
189
|
+
}) {
|
|
143
190
|
const contentId = useId();
|
|
191
|
+
const fit = useContext2(DataGridColumnFitContext);
|
|
144
192
|
const meta = header.column.columnDef.meta;
|
|
145
|
-
const hasResizer = isResizable && !meta?.fill && header.column.getCanResize();
|
|
193
|
+
const hasResizer = isResizable && !meta?.fill && header.column.getCanResize() && (!fit || header.subHeaders.length === 0);
|
|
146
194
|
return /* @__PURE__ */ jsx2(TableHead2, {
|
|
147
195
|
scope: "col",
|
|
148
|
-
colSpan
|
|
196
|
+
colSpan,
|
|
149
197
|
"aria-sort": getAriaSort(header.column),
|
|
150
198
|
"aria-labelledby": hasResizer ? contentId : undefined,
|
|
151
199
|
textAlign: meta?.textAlign,
|
|
152
200
|
truncate: meta?.truncate,
|
|
153
|
-
visibleFrom: meta?.visibleFrom,
|
|
201
|
+
visibleFrom: fit ? undefined : meta?.visibleFrom,
|
|
154
202
|
className: hasResizer ? "relative" : undefined,
|
|
155
203
|
children: hasResizer ? /* @__PURE__ */ jsxs(Fragment, {
|
|
156
204
|
children: [
|
|
@@ -174,23 +222,29 @@ function DataGridHeaderRows({
|
|
|
174
222
|
table,
|
|
175
223
|
isResizable = false
|
|
176
224
|
}) {
|
|
225
|
+
const fit = useContext2(DataGridColumnFitContext);
|
|
177
226
|
return /* @__PURE__ */ jsx2(Fragment, {
|
|
178
227
|
children: table.getHeaderGroups().map((group) => /* @__PURE__ */ jsx2(TableRow2, {
|
|
179
|
-
children: group.headers.map((header) =>
|
|
180
|
-
header
|
|
181
|
-
|
|
182
|
-
|
|
228
|
+
children: group.headers.map((header) => {
|
|
229
|
+
const colSpan = fit ? header.getLeafHeaders().filter((leaf) => leaf.subHeaders.length === 0 && !fit.hiddenColumnIds.includes(leaf.column.id)).length : header.colSpan;
|
|
230
|
+
return colSpan > 0 ? /* @__PURE__ */ jsx2(DataGridHeaderCell, {
|
|
231
|
+
header,
|
|
232
|
+
isResizable,
|
|
233
|
+
colSpan
|
|
234
|
+
}, header.id) : null;
|
|
235
|
+
})
|
|
183
236
|
}, group.id))
|
|
184
237
|
});
|
|
185
238
|
}
|
|
186
239
|
function DataGridCells2({ row }) {
|
|
240
|
+
const fit = useContext2(DataGridColumnFitContext);
|
|
187
241
|
return /* @__PURE__ */ jsx2(Fragment, {
|
|
188
|
-
children: row.getVisibleCells().map((cell) => {
|
|
242
|
+
children: row.getVisibleCells().filter((cell) => !fit?.hiddenColumnIds.includes(cell.column.id)).map((cell) => {
|
|
189
243
|
const meta = cell.column.columnDef.meta;
|
|
190
244
|
return /* @__PURE__ */ jsx2(TableCell2, {
|
|
191
245
|
textAlign: meta?.textAlign,
|
|
192
246
|
truncate: meta?.truncate,
|
|
193
|
-
visibleFrom: meta?.visibleFrom,
|
|
247
|
+
visibleFrom: fit ? undefined : meta?.visibleFrom,
|
|
194
248
|
children: /* @__PURE__ */ jsx2(FlexRender, {
|
|
195
249
|
cell
|
|
196
250
|
})
|
|
@@ -199,13 +253,14 @@ function DataGridCells2({ row }) {
|
|
|
199
253
|
});
|
|
200
254
|
}
|
|
201
255
|
function DataGridColumns({ table }) {
|
|
256
|
+
const fit = useContext2(DataGridColumnFitContext);
|
|
202
257
|
const variables = getDataGridColumnSizeVariables(table);
|
|
203
258
|
return /* @__PURE__ */ jsx2(TableColumnGroup2, {
|
|
204
|
-
children: table.getVisibleLeafColumns().map((column) => {
|
|
259
|
+
children: table.getVisibleLeafColumns().filter((column) => !fit?.hiddenColumnIds.includes(column.id)).map((column) => {
|
|
205
260
|
const meta = column.columnDef.meta;
|
|
206
261
|
const variable = variables.get(column.id);
|
|
207
262
|
return /* @__PURE__ */ jsx2(TableColumn2, {
|
|
208
|
-
visibleFrom: meta?.visibleFrom,
|
|
263
|
+
visibleFrom: fit ? undefined : meta?.visibleFrom,
|
|
209
264
|
width: meta?.fill ? undefined : `var(${variable}, ${column.getSize()}px)`
|
|
210
265
|
}, column.id);
|
|
211
266
|
})
|
|
@@ -364,4 +419,4 @@ function DataGridPagination2({ table }) {
|
|
|
364
419
|
});
|
|
365
420
|
}
|
|
366
421
|
|
|
367
|
-
export { getDataGridColumnSizeVariables, dataGridFeatures2, getDataGridColumnLabel, DataGridHeaderRows, DataGridCells2, DataGridColumns, DataGrid2, DataGridColumnHeader2, DataGridFilter2, DataGridPagination2 };
|
|
422
|
+
export { DataGridColumnFitContext, getDataGridColumnFit, getDataGridColumnFitMaximum, getDataGridColumnSizeVariables, dataGridFeatures2, getDataGridColumnLabel, DataGridHeaderRows, DataGridCells2, DataGridColumns, DataGrid2, DataGridColumnHeader2, DataGridFilter2, DataGridPagination2 };
|
|
@@ -2542,7 +2542,7 @@ function AttachmentGroup2({ density, className, ...props }) {
|
|
|
2542
2542
|
});
|
|
2543
2543
|
}
|
|
2544
2544
|
// src/components/InlineEdit.tsx
|
|
2545
|
-
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
2545
|
+
import { useEffect as useEffect2, useId as useId2, useRef as useRef2, useState as useState2 } from "react";
|
|
2546
2546
|
import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2547
2547
|
function InlineEdit2({
|
|
2548
2548
|
analytics,
|
|
@@ -2558,6 +2558,8 @@ function InlineEdit2({
|
|
|
2558
2558
|
onCancel
|
|
2559
2559
|
}) {
|
|
2560
2560
|
const track = useAnalytics2("InlineEdit", analytics);
|
|
2561
|
+
const triggerId = useId2();
|
|
2562
|
+
const errorId = `${triggerId}-error`;
|
|
2561
2563
|
const input = useRef2(null);
|
|
2562
2564
|
const isFinished = useRef2(false);
|
|
2563
2565
|
const shouldCancel = useRef2(false);
|
|
@@ -2588,48 +2590,63 @@ function InlineEdit2({
|
|
|
2588
2590
|
setSaving(false);
|
|
2589
2591
|
}
|
|
2590
2592
|
};
|
|
2591
|
-
return /* @__PURE__ */
|
|
2592
|
-
className: "
|
|
2593
|
+
return /* @__PURE__ */ jsx26("form", {
|
|
2594
|
+
className: "min-w-0",
|
|
2593
2595
|
onSubmit: (event) => {
|
|
2594
2596
|
event.preventDefault();
|
|
2595
2597
|
save();
|
|
2596
2598
|
},
|
|
2597
|
-
children:
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
event
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2599
|
+
children: /* @__PURE__ */ jsxs15(Tooltip2, {
|
|
2600
|
+
open: Boolean(error),
|
|
2601
|
+
triggerId,
|
|
2602
|
+
children: [
|
|
2603
|
+
/* @__PURE__ */ jsx26(TooltipTrigger2, {
|
|
2604
|
+
id: triggerId,
|
|
2605
|
+
render: /* @__PURE__ */ jsx26(Input2, {
|
|
2606
|
+
analytics: false,
|
|
2607
|
+
ref: input,
|
|
2608
|
+
density,
|
|
2609
|
+
className: "h-[1lh] rounded-none border-0 px-0 py-0 text-[length:inherit] leading-[inherit]",
|
|
2610
|
+
"aria-label": label,
|
|
2611
|
+
"aria-invalid": Boolean(error),
|
|
2612
|
+
"aria-errormessage": error ? errorId : undefined,
|
|
2613
|
+
"aria-description": "Enter saves. Escape cancels.",
|
|
2614
|
+
value,
|
|
2615
|
+
disabled: isDisabled || isSaving,
|
|
2616
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
2617
|
+
onBlur: () => {
|
|
2618
|
+
if (shouldCancel.current && !isFinished.current) {
|
|
2619
|
+
isFinished.current = true;
|
|
2620
|
+
onCancel();
|
|
2621
|
+
} else
|
|
2622
|
+
save();
|
|
2623
|
+
},
|
|
2624
|
+
onKeyDown: (event) => {
|
|
2625
|
+
if (event.key === "Escape") {
|
|
2626
|
+
event.preventDefault();
|
|
2627
|
+
event.stopPropagation();
|
|
2628
|
+
shouldCancel.current = true;
|
|
2629
|
+
event.currentTarget.blur();
|
|
2630
|
+
} else if (event.key === "Enter") {
|
|
2631
|
+
event.preventDefault();
|
|
2632
|
+
event.currentTarget.blur();
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
})
|
|
2636
|
+
}),
|
|
2637
|
+
error && /* @__PURE__ */ jsx26(TooltipContent2, {
|
|
2638
|
+
density,
|
|
2639
|
+
side: "bottom",
|
|
2640
|
+
align: "start",
|
|
2641
|
+
children: /* @__PURE__ */ jsx26("p", {
|
|
2642
|
+
id: errorId,
|
|
2643
|
+
role: "alert",
|
|
2644
|
+
className: "text-destructive",
|
|
2645
|
+
children: error
|
|
2646
|
+
})
|
|
2647
|
+
})
|
|
2648
|
+
]
|
|
2649
|
+
})
|
|
2633
2650
|
});
|
|
2634
2651
|
}
|
|
2635
2652
|
// src/components/StarRating.tsx
|
|
@@ -3805,7 +3822,7 @@ function StatCard2({
|
|
|
3805
3822
|
});
|
|
3806
3823
|
}
|
|
3807
3824
|
// src/components/AuthForm.tsx
|
|
3808
|
-
import { useId as
|
|
3825
|
+
import { useId as useId3, useState as useState5 } from "react";
|
|
3809
3826
|
import { jsx as jsx40, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
3810
3827
|
function AuthForm2({
|
|
3811
3828
|
mode = "sign-in",
|
|
@@ -3815,7 +3832,7 @@ function AuthForm2({
|
|
|
3815
3832
|
disabled = false,
|
|
3816
3833
|
density
|
|
3817
3834
|
}) {
|
|
3818
|
-
const id =
|
|
3835
|
+
const id = useId3();
|
|
3819
3836
|
const [pending, setPending] = useState5(false);
|
|
3820
3837
|
const [error, setError] = useState5("");
|
|
3821
3838
|
const title = mode === "sign-up" ? "Create an account" : mode === "reset-password" ? "Reset your password" : "Sign in";
|
|
@@ -12,6 +12,6 @@ interface IInlineEditProps extends IDensityProps, IAnalyticsProps {
|
|
|
12
12
|
onCommit: (value: string) => void | Promise<void>;
|
|
13
13
|
onCancel: () => void;
|
|
14
14
|
}
|
|
15
|
-
/** A mounted edit session
|
|
15
|
+
/** A mounted edit session that inherits its text line's size; validation floats without moving adjacent content. */
|
|
16
16
|
export declare function InlineEdit({ analytics, label, value, density, isDisabled, shouldSelectOnMount, hasChanges, renderError, onValueChange, onCommit, onCancel, }: IInlineEditProps): ReactElement;
|
|
17
17
|
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface IDataGridColumnFit {
|
|
2
|
+
width: number;
|
|
3
|
+
hiddenColumnIds: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
/** Layout-only visibility; the table's column choices remain unchanged. */
|
|
6
|
+
export declare const DataGridColumnFitContext: import("react").Context<IDataGridColumnFit | undefined>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CellData, type Header, type RowData } from "@tanstack/react-table";
|
|
2
|
-
import type
|
|
2
|
+
import { type ReactElement } from "react";
|
|
3
3
|
import type { DataGridFeatures } from "./DataGrid.js";
|
|
4
4
|
interface IDataGridColumnResizerProps<T extends RowData> {
|
|
5
5
|
header: Header<DataGridFeatures, T, CellData>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ColumnSizingState, type RowData, type Table, type Updater } from "@tanstack/react-table";
|
|
2
|
+
import type { DataGridFeatures } from "./DataGrid.js";
|
|
3
|
+
/**
|
|
4
|
+
* Apply a user resize without displacing another fitted column or collapsing a fill column. Use in the table's
|
|
5
|
+
* `onColumnSizingChange`, passing the width reported by `VirtualDataGrid.onFitWidthChange`, then store the result in
|
|
6
|
+
* preferred sizes. Then pass those preferences through `fitDataGridColumnSizing` into the table's sizing atom.
|
|
7
|
+
* Container changes never call this helper or rewrite preferred sizes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function constrainDataGridColumnSizing<T extends RowData>(table: Table<DataGridFeatures, T>, updater: Updater<ColumnSizingState>, width: number, preferred: ColumnSizingState): ColumnSizingState;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ColumnSizingState, RowData, Table } from "@tanstack/react-table";
|
|
2
|
+
import type { DataGridFeatures } from "./DataGrid.js";
|
|
3
|
+
/**
|
|
4
|
+
* Derive rendered sizes from preferred sizes. Required resizable columns give up space toward their minima only
|
|
5
|
+
* when needed to preserve the fill minimum; widening restores the preferences. Store this result in the table's
|
|
6
|
+
* sizing atom, separately from preferences, so native resize handlers start at the width the user sees.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fitDataGridColumnSizing<T extends RowData>(table: Table<DataGridFeatures, T>, preferred: ColumnSizingState, width: number): ColumnSizingState;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RowData, Table } from "@tanstack/react-table";
|
|
2
|
+
import type { DataGridFeatures } from "./DataGrid.js";
|
|
3
|
+
/** Columns that do not fit after reserving each fill column's minimum and every required fixed column. */
|
|
4
|
+
export declare function getDataGridColumnFit<T extends RowData>(table: Table<DataGridFeatures, T>, width: number): string[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RowData, Table } from "@tanstack/react-table";
|
|
2
|
+
import type { DataGridFeatures } from "./DataGrid.js";
|
|
3
|
+
/** The resize ceiling that keeps the current fitted columns and the fill columns' minimum space. */
|
|
4
|
+
export declare function getDataGridColumnFitMaximum<T extends RowData>(table: Table<DataGridFeatures, T>, columnId: string, width: number): number;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RowData, Table } from "@tanstack/react-table";
|
|
2
|
+
import { type RefObject } from "react";
|
|
3
|
+
import type { DataGridFeatures } from "../DataGrid.js";
|
|
4
|
+
import type { IDataGridColumnFit } from "../DataGridColumnFitContext.js";
|
|
5
|
+
/** Measures the rendered table and derives temporary visibility without changing the table's saved choices. */
|
|
6
|
+
export declare function useDataGridColumnFit<T extends RowData>(table: Table<DataGridFeatures, T>, scope: RefObject<HTMLElement | null>, isEnabled: boolean, onWidthChange?: (width: number) => void): IDataGridColumnFit | undefined;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { DataGrid, DataGridCells, DataGridColumnHeader, DataGridFilter, DataGridPagination, dataGridFeatures, } from "./DataGrid.js";
|
|
2
2
|
export type { DataGridColumn, DataGridColumnFilter, DataGridFeatures, DataGridProps, DataGridRow, DataGridTable, IDataGridColumnMeta, IDataGridControlProps, DataGridFilterOptions, IDataGridFilterControlProps, IDataGridOptionsFilter, IDataGridRangeFilter, } from "./DataGrid.js";
|
|
3
3
|
export { DataGridColumnOptions } from "./DataGridColumnOptions.js";
|
|
4
|
+
export { constrainDataGridColumnSizing } from "./constrainDataGridColumnSizing.js";
|
|
5
|
+
export { fitDataGridColumnSizing } from "./fitDataGridColumnSizing.js";
|
|
4
6
|
export { DataGridFilters } from "./DataGridFilters.js";
|
|
5
7
|
export { DataGridFilterSummary } from "./DataGridFilterSummary.js";
|
|
6
8
|
export { DataGridSelectHeader } from "./DataGridSelectHeader.js";
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDataGridColumnFitMaximum,
|
|
3
|
+
dataGridFeatures2,
|
|
4
|
+
getDataGridColumnLabel,
|
|
5
|
+
DataGridCells2,
|
|
6
|
+
DataGrid2,
|
|
7
|
+
DataGridColumnHeader2,
|
|
8
|
+
DataGridFilter2,
|
|
9
|
+
DataGridPagination2
|
|
10
|
+
} from "../../chunk-74xynyzg.js";
|
|
1
11
|
import"../../chunk-7ny0vyek.js";
|
|
2
12
|
import"../../chunk-8ssktt4b.js";
|
|
3
13
|
import"../../chunk-en44w1pn.js";
|
|
@@ -11,15 +21,6 @@ import"../../chunk-tmr0jqv8.js";
|
|
|
11
21
|
import"../../chunk-p6z2m3zp.js";
|
|
12
22
|
import"../../chunk-7jpsybag.js";
|
|
13
23
|
import"../../chunk-7vg045gd.js";
|
|
14
|
-
import {
|
|
15
|
-
dataGridFeatures2,
|
|
16
|
-
getDataGridColumnLabel,
|
|
17
|
-
DataGridCells2,
|
|
18
|
-
DataGrid2,
|
|
19
|
-
DataGridColumnHeader2,
|
|
20
|
-
DataGridFilter2,
|
|
21
|
-
DataGridPagination2
|
|
22
|
-
} from "../../chunk-x7emg3jx.js";
|
|
23
24
|
import {
|
|
24
25
|
Checkbox2
|
|
25
26
|
} from "../../chunk-b5s5y5wq.js";
|
|
@@ -170,6 +171,45 @@ function DataGridColumnOptions({
|
|
|
170
171
|
]
|
|
171
172
|
});
|
|
172
173
|
}
|
|
174
|
+
// src/components/data-grid/constrainDataGridColumnSizing.ts
|
|
175
|
+
import {
|
|
176
|
+
functionalUpdate
|
|
177
|
+
} from "@tanstack/react-table";
|
|
178
|
+
function constrainDataGridColumnSizing(table, updater, width, preferred) {
|
|
179
|
+
const previous = table.atoms.columnSizing.get();
|
|
180
|
+
const next = functionalUpdate(updater, previous);
|
|
181
|
+
return Object.fromEntries(Object.entries(next).map(([id, size]) => {
|
|
182
|
+
const column = table.getColumn(id);
|
|
183
|
+
if (size === previous[id])
|
|
184
|
+
return [id, preferred[id]];
|
|
185
|
+
const fitted = width <= 0 || !column?.getIsVisible() || column.columnDef.meta?.fill ? size : Math.max(column.columnDef.minSize ?? 20, Math.min(size, getDataGridColumnFitMaximum(table, id, width)));
|
|
186
|
+
return [id, fitted === (column?.getSize() ?? previous[id]) ? preferred[id] : fitted];
|
|
187
|
+
}).filter(([, size]) => size !== undefined));
|
|
188
|
+
}
|
|
189
|
+
// src/components/data-grid/fitDataGridColumnSizing.ts
|
|
190
|
+
function fitDataGridColumnSizing(table, preferred, width) {
|
|
191
|
+
const next = { ...preferred };
|
|
192
|
+
if (width > 0) {
|
|
193
|
+
const required = table.getVisibleLeafColumns().filter((column) => !column.getCanHide() || column.columnDef.meta?.fill);
|
|
194
|
+
const preferredSize = (column) => Math.min(column.columnDef.maxSize ?? Number.MAX_SAFE_INTEGER, Math.max(column.columnDef.minSize ?? 20, preferred[column.id] ?? column.columnDef.size ?? 150));
|
|
195
|
+
let excess = required.reduce((sum, column) => sum + (column.columnDef.meta?.fill ? column.columnDef.minSize ?? 20 : preferredSize(column)), 0) - width;
|
|
196
|
+
for (const column of required.toReversed()) {
|
|
197
|
+
if (excess <= 0)
|
|
198
|
+
break;
|
|
199
|
+
if (column.columnDef.meta?.fill || !column.getCanResize())
|
|
200
|
+
continue;
|
|
201
|
+
const size = preferredSize(column);
|
|
202
|
+
const shrink = Math.min(excess, size - (column.columnDef.minSize ?? 20));
|
|
203
|
+
if (shrink > 0) {
|
|
204
|
+
next[column.id] = size - shrink;
|
|
205
|
+
excess -= shrink;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const current = table.atoms.columnSizing.get();
|
|
210
|
+
const entries = Object.entries(next);
|
|
211
|
+
return entries.length === Object.keys(current).length && entries.every(([id, size]) => current[id] === size) ? current : next;
|
|
212
|
+
}
|
|
173
213
|
// src/components/data-grid/DataGridFilters.tsx
|
|
174
214
|
import { Subscribe as Subscribe2 } from "@tanstack/react-table";
|
|
175
215
|
import { ListFilterIcon } from "lucide-react";
|
|
@@ -577,5 +617,7 @@ export {
|
|
|
577
617
|
DataGridPagination2 as DataGridPagination,
|
|
578
618
|
DataGridSelectCell,
|
|
579
619
|
DataGridSelectHeader,
|
|
580
|
-
|
|
620
|
+
constrainDataGridColumnSizing,
|
|
621
|
+
dataGridFeatures2 as dataGridFeatures,
|
|
622
|
+
fitDataGridColumnSizing
|
|
581
623
|
};
|
|
@@ -8,6 +8,16 @@ interface IVirtualDataGridProps<T extends RowData, TSelected> {
|
|
|
8
8
|
maxHeight?: number | string;
|
|
9
9
|
estimateRowHeight?: number;
|
|
10
10
|
overscan?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Reserve one fill column's `minSize` and hide optional columns that do not fit, in their chosen order. Replaces
|
|
13
|
+
* `meta.visibleFrom` breakpoints; saved visibility and widths are unchanged. Use exactly one fill column, and
|
|
14
|
+
* ensure required columns' minima plus its minimum fit the smallest supported container. Only leaf headers resize.
|
|
15
|
+
* Pair with `fitDataGridColumnSizing` and `constrainDataGridColumnSizing` to preserve preferred widths separately
|
|
16
|
+
* from rendered widths and constrain native pointer resizing.
|
|
17
|
+
*/
|
|
18
|
+
fitColumns?: boolean;
|
|
19
|
+
/** Reports usable width; derive the sizing atom with `fitDataGridColumnSizing`, keeping preferences unchanged. */
|
|
20
|
+
onFitWidthChange?: (width: number) => void;
|
|
11
21
|
/** Row IDs that stay rendered while scrolled out of view, such as rows with an open editor. */
|
|
12
22
|
retainedRowIds?: readonly string[];
|
|
13
23
|
/**
|
|
@@ -22,5 +32,5 @@ interface IVirtualDataGridProps<T extends RowData, TSelected> {
|
|
|
22
32
|
* Column widths change through CSS variables, so a `useTable` selector can leave out sizing state and resizing never
|
|
23
33
|
* renders rows again.
|
|
24
34
|
*/
|
|
25
|
-
export declare function VirtualDataGrid<T extends RowData, TSelected>({ table, label, maxHeight, estimateRowHeight, overscan, retainedRowIds, renderRow, }: IVirtualDataGridProps<T, TSelected>): ReactElement;
|
|
35
|
+
export declare function VirtualDataGrid<T extends RowData, TSelected>({ table, label, maxHeight, estimateRowHeight, overscan, fitColumns, onFitWidthChange, retainedRowIds, renderRow, }: IVirtualDataGridProps<T, TSelected>): ReactElement;
|
|
26
36
|
export {};
|
|
@@ -2,6 +2,15 @@ import"../../chunk-en44w1pn.js";
|
|
|
2
2
|
import {
|
|
3
3
|
TableRow2
|
|
4
4
|
} from "../../chunk-tmr0jqv8.js";
|
|
5
|
+
import {
|
|
6
|
+
DataGridColumnFitContext,
|
|
7
|
+
getDataGridColumnFit,
|
|
8
|
+
getDataGridColumnSizeVariables,
|
|
9
|
+
getDataGridColumnLabel,
|
|
10
|
+
DataGridHeaderRows,
|
|
11
|
+
DataGridCells2,
|
|
12
|
+
DataGridColumns
|
|
13
|
+
} from "../../chunk-74xynyzg.js";
|
|
5
14
|
import"../../chunk-7ny0vyek.js";
|
|
6
15
|
import"../../chunk-8ssktt4b.js";
|
|
7
16
|
import"../../chunk-e6hk39ev.js";
|
|
@@ -11,12 +20,6 @@ import"../../chunk-bd09gygt.js";
|
|
|
11
20
|
import"../../chunk-p6z2m3zp.js";
|
|
12
21
|
import"../../chunk-7jpsybag.js";
|
|
13
22
|
import"../../chunk-7vg045gd.js";
|
|
14
|
-
import {
|
|
15
|
-
getDataGridColumnSizeVariables,
|
|
16
|
-
DataGridHeaderRows,
|
|
17
|
-
DataGridCells2,
|
|
18
|
-
DataGridColumns
|
|
19
|
-
} from "../../chunk-x7emg3jx.js";
|
|
20
23
|
import {
|
|
21
24
|
VirtualTable2
|
|
22
25
|
} from "../../chunk-bmyw7q4w.js";
|
|
@@ -45,8 +48,39 @@ function useDataGridColumnSizes(table, scope) {
|
|
|
45
48
|
});
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
// src/components/data-grid/hooks/useDataGridColumnFit.ts
|
|
52
|
+
import { useLayoutEffect as useLayoutEffect2, useState } from "react";
|
|
53
|
+
function useDataGridColumnFit(table, scope, isEnabled, onWidthChange) {
|
|
54
|
+
const [fit, setFit] = useState();
|
|
55
|
+
useLayoutEffect2(() => {
|
|
56
|
+
const element = scope.current;
|
|
57
|
+
if (!isEnabled || !element)
|
|
58
|
+
return;
|
|
59
|
+
const container = element.querySelector('[data-slot="table-container"]');
|
|
60
|
+
if (!container)
|
|
61
|
+
return;
|
|
62
|
+
const update = () => {
|
|
63
|
+
const width = container.clientWidth;
|
|
64
|
+
if (width <= 0)
|
|
65
|
+
return;
|
|
66
|
+
onWidthChange?.(width);
|
|
67
|
+
const hiddenColumnIds = getDataGridColumnFit(table, width);
|
|
68
|
+
setFit((previous) => previous?.width === width && previous.hiddenColumnIds.join("\x00") === hiddenColumnIds.join("\x00") ? previous : { width, hiddenColumnIds });
|
|
69
|
+
};
|
|
70
|
+
update();
|
|
71
|
+
const observer = new ResizeObserver(update);
|
|
72
|
+
observer.observe(container);
|
|
73
|
+
const subscription = table.atoms.columnSizing.subscribe(update);
|
|
74
|
+
return () => {
|
|
75
|
+
observer.disconnect();
|
|
76
|
+
subscription.unsubscribe();
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
return isEnabled ? fit : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
48
82
|
// src/components/virtual-data-grid/VirtualDataGrid.tsx
|
|
49
|
-
import { jsx } from "react/jsx-runtime";
|
|
83
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
50
84
|
function renderDefaultRow(row, props) {
|
|
51
85
|
return /* @__PURE__ */ jsx(TableRow2, {
|
|
52
86
|
...props,
|
|
@@ -62,31 +96,55 @@ function VirtualDataGrid({
|
|
|
62
96
|
maxHeight,
|
|
63
97
|
estimateRowHeight,
|
|
64
98
|
overscan,
|
|
99
|
+
fitColumns = false,
|
|
100
|
+
onFitWidthChange,
|
|
65
101
|
retainedRowIds,
|
|
66
102
|
renderRow = renderDefaultRow
|
|
67
103
|
}) {
|
|
68
104
|
const scope = useRef(null);
|
|
69
105
|
useDataGridColumnSizes(table, scope);
|
|
106
|
+
const fit = useDataGridColumnFit(table, scope, fitColumns, onFitWidthChange);
|
|
107
|
+
const hiddenLabels = table.getVisibleLeafColumns().filter((column) => fit?.hiddenColumnIds.includes(column.id)).map(getDataGridColumnLabel);
|
|
70
108
|
return /* @__PURE__ */ jsx("div", {
|
|
71
109
|
ref: scope,
|
|
72
110
|
"data-testid": "VirtualDataGrid",
|
|
73
111
|
className: "min-w-0",
|
|
74
|
-
children: /* @__PURE__ */
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
children: /* @__PURE__ */ jsxs(DataGridColumnFitContext, {
|
|
113
|
+
value: fit,
|
|
114
|
+
children: [
|
|
115
|
+
hiddenLabels.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
116
|
+
className: "mb-2 text-xs text-muted-foreground",
|
|
117
|
+
children: [
|
|
118
|
+
/* @__PURE__ */ jsxs("p", {
|
|
119
|
+
children: [
|
|
120
|
+
"Hidden to fit: ",
|
|
121
|
+
hiddenLabels.join(", "),
|
|
122
|
+
"."
|
|
123
|
+
]
|
|
124
|
+
}),
|
|
125
|
+
/* @__PURE__ */ jsx("p", {
|
|
126
|
+
children: "Widen the table or choose fewer columns. Move a column earlier to prioritize it."
|
|
127
|
+
})
|
|
128
|
+
]
|
|
129
|
+
}),
|
|
130
|
+
/* @__PURE__ */ jsx(VirtualTable2, {
|
|
131
|
+
items: table.getPrePaginatedRowModel().rows,
|
|
132
|
+
getItemKey: (row) => row.id,
|
|
133
|
+
label,
|
|
134
|
+
maxHeight,
|
|
135
|
+
estimateRowHeight,
|
|
136
|
+
overscan,
|
|
137
|
+
retainedKeys: retainedRowIds,
|
|
138
|
+
columns: /* @__PURE__ */ jsx(DataGridColumns, {
|
|
139
|
+
table
|
|
140
|
+
}),
|
|
141
|
+
header: /* @__PURE__ */ jsx(DataGridHeaderRows, {
|
|
142
|
+
table,
|
|
143
|
+
isResizable: true
|
|
144
|
+
}),
|
|
145
|
+
renderRow
|
|
146
|
+
})
|
|
147
|
+
]
|
|
90
148
|
})
|
|
91
149
|
});
|
|
92
150
|
}
|
package/dist/index.js
CHANGED
package/dist/prototype.js
CHANGED
|
@@ -2051,7 +2051,7 @@ var program = new Command;
|
|
|
2051
2051
|
// package.json
|
|
2052
2052
|
var package_default = {
|
|
2053
2053
|
name: "@alexgorbatchev/shadcn",
|
|
2054
|
-
version: "0.0.
|
|
2054
|
+
version: "0.0.56",
|
|
2055
2055
|
description: "Sharp, monospace React components with compact and comfortable densities.",
|
|
2056
2056
|
license: "MIT",
|
|
2057
2057
|
repository: {
|
|
@@ -3149,8 +3149,10 @@ var package_default = {
|
|
|
3149
3149
|
"@storybook/addon-vitest": "^10.6.1",
|
|
3150
3150
|
"@storybook/react-vite": "^10.6.1",
|
|
3151
3151
|
"@tailwindcss/vite": "^4.3.3",
|
|
3152
|
+
"@tanstack/react-store": "0.11.2",
|
|
3152
3153
|
"@tanstack/react-table": "9.2.8",
|
|
3153
3154
|
"@tanstack/react-virtual": "3.14.13",
|
|
3155
|
+
"@tanstack/table-core": "9.2.8",
|
|
3154
3156
|
"@testing-library/react": "^16.3.3",
|
|
3155
3157
|
"@tiptap/core": "3.32.0",
|
|
3156
3158
|
"@tiptap/pm": "3.32.0",
|
|
@@ -75,3 +75,19 @@ Storybook CSF excerpts. Default args and render apply unless overridden. Local h
|
|
|
75
75
|
render: () => <VirtualDataGridExample files={fixture_gridFiles} width={360}/>
|
|
76
76
|
}
|
|
77
77
|
```
|
|
78
|
+
|
|
79
|
+
### FittedColumns
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
{
|
|
83
|
+
render: () => <VirtualDataGridExample files={fixture_gridFiles} width={420} fitColumns/>
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### FittedColumnGroups
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
{
|
|
91
|
+
render: () => <VirtualDataGridExample files={fixture_gridFiles} width={420} fitColumns grouped/>
|
|
92
|
+
}
|
|
93
|
+
```
|
|
@@ -8,7 +8,7 @@ Source: src/components/stories/forms/InlineEdit.stories.tsx
|
|
|
8
8
|
## Public imports
|
|
9
9
|
|
|
10
10
|
```tsx
|
|
11
|
-
import { InlineEdit } from "@alexgorbatchev/shadcn";
|
|
11
|
+
import { InlineEdit, Button } from "@alexgorbatchev/shadcn";
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Supporting imports
|
|
@@ -98,3 +98,25 @@ Storybook CSF excerpts. Default args and render apply unless overridden. Local h
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
```
|
|
101
|
+
|
|
102
|
+
### StableRowLayout
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
{
|
|
106
|
+
render: function Example(args): ReactElement {
|
|
107
|
+
const [isEditing, setEditing] = useState(false);
|
|
108
|
+
const [title, setTitle] = useState("Playlist title");
|
|
109
|
+
return (<div className="w-64 text-sm leading-5">
|
|
110
|
+
<div data-testid="editable-row" className="grid">
|
|
111
|
+
{isEditing ? (<InlineEdit {...args} value={title} onValueChange={setTitle} onCancel={() => setEditing(false)} onCommit={async (value) => {
|
|
112
|
+
await args.onCommit(value);
|
|
113
|
+
setEditing(false);
|
|
114
|
+
}}/>) : (<Button size="inline" variant="link" align="start" onClick={() => setEditing(true)}>
|
|
115
|
+
<span className="leading-5">{title}</span>
|
|
116
|
+
</Button>)}
|
|
117
|
+
</div>
|
|
118
|
+
<p>Next row</p>
|
|
119
|
+
</div>);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
@@ -11,6 +11,12 @@ Source: src/components/stories/forms/RichTextEditor.stories.tsx
|
|
|
11
11
|
import { RichTextEditor, useEditor, StarterKit } from "@alexgorbatchev/shadcn/editor";
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
## Supporting imports
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { isMacOS, isiOS } from "@tiptap/core";
|
|
18
|
+
```
|
|
19
|
+
|
|
14
20
|
## Default component
|
|
15
21
|
|
|
16
22
|
```tsx
|
package/llms/setup.md
CHANGED
|
@@ -429,6 +429,8 @@ import {
|
|
|
429
429
|
|
|
430
430
|
`InlineEdit` mounts an edit session with a controlled `value`, `onValueChange`, `onCommit`, and `onCancel`. Mount it in place of the displayed label. Its input focuses and selects its text by default; `shouldSelectOnMount={false}` preserves the initial caret. Enter or blur commits once, Escape cancels without committing, and asynchronous saves disable the input until completion. Set `hasChanges={false}` to cancel an unchanged edit before calling `onCommit`; the caller owns normalization and comparison. It defaults to true, so creation sessions can commit their initial value. Rejected saves display a generic error and allow retry; `renderError` can map the unknown failure to application-authored guidance.
|
|
431
431
|
|
|
432
|
+
The editor inherits the containing label's font size and line height and occupies that single line. Keep the same text context around display and edit states. Validation appears in an anchored tooltip with an announced alert, so errors do not move neighboring content.
|
|
433
|
+
|
|
432
434
|
`VirtualTable` renders a bounded, dynamically measured set of native table rows using TanStack Virtual. Supply `items`, stable `getItemKey`, `header`, `label`, and `renderRow`; spread the supplied measurement props onto each native row. Optional `columns` accepts a `TableColumnGroup`; responsive columns keep their widths aligned with cells that share their `visibleFrom`. `maxHeight` defaults to `65vh`, `estimateRowHeight` to 80 pixels, and `overscan` to five rows. `retainedKeys` keeps editing rows mounted; the focused row is retained automatically. Filtering and selection belong to the consumer.
|
|
433
435
|
|
|
434
436
|
`VirtualDataGrid` renders a TanStack Table instance built with `dataGridFeatures` through `VirtualTable`: every filtered and sorted row, without pagination, in a fixed-layout table. Column `meta` sets `label`, `textAlign`, `truncate`, `visibleFrom`, `fill`, and `sortColumnIds`; a `fill` column takes the width the sized columns leave, and `sortColumnIds` names other columns the header sorts with its own buttons so its `aria-sort` reports them. Other resizable columns get a window-splitter handle that resizes by dragging, by the arrow keys, Home, and End, and resets on double-click. Set `columnResizeMode: "onChange"` so columns follow the pointer while dragging; TanStack's default `"onEnd"` resizes only on release. Widths change through CSS variables, so a `useTable` selector can omit `columnSizing` and `columnResizing` to keep resizing from rendering rows. `renderRow` receives each row and its measurement props; spread the props onto a `TableRow` and render `DataGridCells` inside it to wrap rows in menus or drag sources. `retainedRowIds` keeps rows mounted while they hold editors. `DataGridColumnOptions` shows, hides, and reorders the columns that allow hiding; its reset restores the table's initial visibility, order, and sizes.
|
|
@@ -437,6 +439,10 @@ Column `meta.filter` offers a column in `DataGridFilters`, hidden columns includ
|
|
|
437
439
|
|
|
438
440
|
Import `DataGridSelectHeader` and `DataGridSelectCell` from `@alexgorbatchev/shadcn/data-grid`. Pass the current `useTable` result as `table` to both controls, and pass `row` and an accessible `label` to each cell. The header selects selectable filtered rows across all pages and preserves selections hidden by filters. It shows a mixed state when some of those rows are selected and disables itself when none are selectable. Its default label is "Select all filtered rows"; provide `label` to name the scope in your application. Cells follow native row selection rules, including disabled rows and partially selected parents. The controls observe selection changes without requiring the parent table selector to include selection state.
|
|
439
441
|
|
|
442
|
+
For grids that must fit their container, set `VirtualDataGrid fitColumns`. Use exactly one `meta.fill` column with an explicit `minSize`; required columns' minima plus that minimum must fit the smallest supported container. The grid reserves that space and temporarily hides optional columns that do not fit, prioritizing their chosen order. It lists those columns and explains how to reveal them. This mode replaces `meta.visibleFrom` breakpoints and offers resize handles on leaf headers only.
|
|
443
|
+
|
|
444
|
+
Keep preferred widths separate from the table's rendered sizing atom. In `onFitWidthChange`, store the reported width in a ref and write `fitDataGridColumnSizing(table, preferredWidths, width)` into the sizing atom. The helper temporarily shrinks required resizable columns toward their minima when needed and restores their preferred widths when space returns. In `onColumnSizingChange`, obtain new preferences with `constrainDataGridColumnSizing(table, updater, widthRef.current, preferredWidths)`, then derive the sizing atom with `fitDataGridColumnSizing`. Persist only the preferred widths, after the resize settles. Native pointer and keyboard resizing starts at the displayed width, stays within the available space, and leaves other columns' preferences unchanged. Container changes never overwrite user visibility, order, or width preferences. The `VirtualDataGridExample` fixture demonstrates this controlled integration.
|
|
445
|
+
|
|
440
446
|
`ActionsMenu` shares one `items` definition between a dropdown action and an optional context menu. Supply an accessible `label`; the default trigger is a ghost icon button. `size="sm"` displays the label, and `variant`, `disabled`, and `trigger` customize the action. Supplying `contextTarget` composes the native context trigger with that element and adds its popup. Compose items with `MenuItem`, `MenuLinkItem`, checkbox/radio parts, groups, labels, separators, shortcuts, and submenu parts; dropdown and context menus share these implementations. `MenuLinkItem` retains its native anchor and menu keyboard semantics.
|
|
441
447
|
|
|
442
448
|
For nested layouts, compose `ActionsMenuTrigger`, `ActionsMenuContextTarget`, and `ActionsMenuContextContent` explicitly inside `ActionsMenu`. The dropdown trigger owns its local native dropdown root and popup; the context target and content share an independent context root. A tree row can use `TreeItem render={<ActionsMenuContextTarget />}` and put the trigger in its action slot. For a table row, render the context target as the actual `tr`, then place the dropdown trigger and context content inside a `td`: native popup portals insert focus guards at their declaration, which must remain valid table children.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexgorbatchev/shadcn",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.56",
|
|
4
4
|
"description": "Sharp, monospace React components with compact and comfortable densities.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -1098,8 +1098,10 @@
|
|
|
1098
1098
|
"@storybook/addon-vitest": "^10.6.1",
|
|
1099
1099
|
"@storybook/react-vite": "^10.6.1",
|
|
1100
1100
|
"@tailwindcss/vite": "^4.3.3",
|
|
1101
|
+
"@tanstack/react-store": "0.11.2",
|
|
1101
1102
|
"@tanstack/react-table": "9.2.8",
|
|
1102
1103
|
"@tanstack/react-virtual": "3.14.13",
|
|
1104
|
+
"@tanstack/table-core": "9.2.8",
|
|
1103
1105
|
"@testing-library/react": "^16.3.3",
|
|
1104
1106
|
"@tiptap/core": "3.32.0",
|
|
1105
1107
|
"@tiptap/pm": "3.32.0",
|