@alexgorbatchev/shadcn 0.0.54 → 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 +12 -1
- package/THIRD_PARTY_NOTICES.md +2 -0
- package/dist/{chunk-x7emg3jx.js → chunk-74xynyzg.js} +73 -18
- package/dist/{chunk-d5pabsj2.js → chunk-zwnba1pb.js} +69 -46
- package/dist/components/InlineEdit.d.ts +1 -1
- package/dist/components/ThemeProvider.d.ts +2 -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.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/prototype.js +3 -1
- package/dist/theme/types.d.ts +2 -0
- package/dist/theme.css +99 -6
- 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/components/providers/ThemeProvider.md +8 -0
- package/llms/setup.md +12 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -198,10 +198,15 @@ An independent production Vite consumer installed from the archive verifies RTL
|
|
|
198
198
|
| `ThemeProvider.defaultTheme` | `"light" \| "dark" \| "system"` | `"system"` | Initial preference when storage has no saved value. |
|
|
199
199
|
| `ThemeProvider.storageKey` | `string` | `"shadcn-alexgorbatchev-theme"` | Local storage key for the preference. |
|
|
200
200
|
| `ThemeProvider.forcedTheme` | `"light" \| "dark"` | unset | Overrides the displayed palette without changing the saved preference. |
|
|
201
|
+
| `ThemeProvider.preset` | `"devhost" \| "shadcn"` | `"devhost"` | Document-wide design preset, independent of color mode and density. |
|
|
201
202
|
| `DensityProvider.density` | `"compact" \| "comfortable"` | `"comfortable"` | Density for descendants and their portals. |
|
|
202
203
|
| Component `density` | `"compact" \| "comfortable"` | inherited | Local override on styled components that expose the prop. |
|
|
203
204
|
|
|
204
|
-
`useTheme()` returns `theme`, `resolvedTheme`, and `
|
|
205
|
+
`useTheme()` returns `theme`, `resolvedTheme`, `setTheme`, and `preset`. Call `setTheme("light")`, `setTheme("dark")`, or `setTheme("system")` from a component inside the provider.
|
|
206
|
+
|
|
207
|
+
Use `<ThemeProvider preset="shadcn" defaultTheme="system">` for the [default shadcn Neutral palette](https://ui.shadcn.com/docs/theming), system sans-serif text, and its rounded corner scale. The default `devhost` preset uses the package's monospace design and sharp corners. Both presets support light, dark, and system modes, including portaled content. Code retains the monospace font. Density, component composition, and package-specific status colors remain independent of the preset.
|
|
208
|
+
|
|
209
|
+
The preset is controlled by the prop; changing it updates the document without changing the saved color-mode preference. Persist that prop in your application if needed. Storybook exposes the same choice in its **Design** toolbar. For a page styled before React mounts, set `data-theme-preset="shadcn"` and `data-theme="light"` or `"dark"` on `<html>` to match the provider. The existing `theme.css` import and `@source` setup cover both presets.
|
|
205
210
|
|
|
206
211
|
Override tokens after the theme imports. This example uses the existing success palette as the primary accent:
|
|
207
212
|
|
|
@@ -424,6 +429,8 @@ import {
|
|
|
424
429
|
|
|
425
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.
|
|
426
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
|
+
|
|
427
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.
|
|
428
435
|
|
|
429
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.
|
|
@@ -432,6 +439,10 @@ Column `meta.filter` offers a column in `DataGridFilters`, hidden columns includ
|
|
|
432
439
|
|
|
433
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.
|
|
434
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
|
+
|
|
435
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.
|
|
436
447
|
|
|
437
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/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -14,6 +14,8 @@ Questionnaire adapts the official registry's fifteen styled parts around `@shadc
|
|
|
14
14
|
|
|
15
15
|
## shadcn/ui
|
|
16
16
|
|
|
17
|
+
The `shadcn` design preset in `src/theme.css` adapts the default Neutral light/dark palette and radius scale from https://ui.shadcn.com/docs/theming, retrieved on 2026-10-31. It preserves the package's independent density and status tokens and uses document data attributes for selection. The upstream MIT notice below covers these tokens.
|
|
18
|
+
|
|
17
19
|
Questionnaire's NativeChoiceKeyboard and ItemArrowNavigation Storybook interactions adapt the native choice-navigation and contextual horizontal-arrow browser tests from `packages/react/src/questionnaire/questionnaire.browser.test.tsx` at upstream commit `3d98900e9b73c45f035f8109e9d41ba6a51685e7` (https://github.com/shadcn-ui/ui). Local changes use this package's styled compositions, Storybook's play context and accessible queries, and assert unanswered-item guards, focus, selection and submitted answers. The upstream MIT notice below covers these adaptations.
|
|
18
20
|
|
|
19
21
|
Sidebar adapts the official provider and styled parts, preserving custom render/ref contracts, keyboard toggling within each provider, consumer-owned persistence, and native mobile Sheet behavior. Local changes support density, cancelled toggle events, observation without controlled state, inert offcanvas panels, configured mobile widths, and centered icons with accessible collapsed labels. Separator uses Base UI's current orientation attributes, and Tooltip preserves native state callbacks.
|
|
@@ -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 };
|
|
@@ -1111,13 +1111,15 @@ function StoredThemeProvider({
|
|
|
1111
1111
|
children,
|
|
1112
1112
|
defaultTheme = "system",
|
|
1113
1113
|
storageKey = "shadcn-alexgorbatchev-theme",
|
|
1114
|
-
forcedTheme
|
|
1114
|
+
forcedTheme,
|
|
1115
|
+
preset = "devhost"
|
|
1115
1116
|
}) {
|
|
1116
1117
|
const { isDarkMode, ternaryDarkMode, setTernaryDarkMode } = useTernaryDarkMode({
|
|
1117
1118
|
defaultValue: defaultTheme,
|
|
1118
1119
|
localStorageKey: storageKey
|
|
1119
1120
|
});
|
|
1120
1121
|
return /* @__PURE__ */ jsx12(ThemePalette, {
|
|
1122
|
+
preset,
|
|
1121
1123
|
theme: forcedTheme ?? ternaryDarkMode,
|
|
1122
1124
|
resolvedTheme: forcedTheme ?? (isDarkMode ? "dark" : "light"),
|
|
1123
1125
|
setTheme: setTernaryDarkMode,
|
|
@@ -1128,13 +1130,15 @@ function ThemePalette({
|
|
|
1128
1130
|
children,
|
|
1129
1131
|
theme,
|
|
1130
1132
|
resolvedTheme,
|
|
1131
|
-
setTheme
|
|
1133
|
+
setTheme,
|
|
1134
|
+
preset
|
|
1132
1135
|
}) {
|
|
1133
1136
|
useIsomorphicLayoutEffect(() => {
|
|
1134
1137
|
document.documentElement.dataset.theme = resolvedTheme;
|
|
1138
|
+
document.documentElement.dataset.themePreset = preset;
|
|
1135
1139
|
document.documentElement.style.colorScheme = resolvedTheme;
|
|
1136
|
-
}, [resolvedTheme]);
|
|
1137
|
-
const value = useMemo(() => ({ theme, resolvedTheme, setTheme }), [theme, resolvedTheme, setTheme]);
|
|
1140
|
+
}, [resolvedTheme, preset]);
|
|
1141
|
+
const value = useMemo(() => ({ theme, resolvedTheme, setTheme, preset }), [theme, resolvedTheme, setTheme, preset]);
|
|
1138
1142
|
return /* @__PURE__ */ jsxs8(ThemeContext.Provider, {
|
|
1139
1143
|
value,
|
|
1140
1144
|
children: [
|
|
@@ -1147,7 +1151,8 @@ function ThemePalette({
|
|
|
1147
1151
|
function ControlledThemeProvider({
|
|
1148
1152
|
children,
|
|
1149
1153
|
controlledTheme,
|
|
1150
|
-
forcedTheme
|
|
1154
|
+
forcedTheme,
|
|
1155
|
+
preset = "devhost"
|
|
1151
1156
|
}) {
|
|
1152
1157
|
const isDark = useMediaQuery("(prefers-color-scheme: dark)");
|
|
1153
1158
|
const { theme, setTheme } = controlledTheme;
|
|
@@ -1156,6 +1161,7 @@ function ControlledThemeProvider({
|
|
|
1156
1161
|
theme: forcedTheme ?? theme,
|
|
1157
1162
|
resolvedTheme,
|
|
1158
1163
|
setTheme,
|
|
1164
|
+
preset,
|
|
1159
1165
|
children
|
|
1160
1166
|
});
|
|
1161
1167
|
}
|
|
@@ -2536,7 +2542,7 @@ function AttachmentGroup2({ density, className, ...props }) {
|
|
|
2536
2542
|
});
|
|
2537
2543
|
}
|
|
2538
2544
|
// src/components/InlineEdit.tsx
|
|
2539
|
-
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";
|
|
2540
2546
|
import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2541
2547
|
function InlineEdit2({
|
|
2542
2548
|
analytics,
|
|
@@ -2552,6 +2558,8 @@ function InlineEdit2({
|
|
|
2552
2558
|
onCancel
|
|
2553
2559
|
}) {
|
|
2554
2560
|
const track = useAnalytics2("InlineEdit", analytics);
|
|
2561
|
+
const triggerId = useId2();
|
|
2562
|
+
const errorId = `${triggerId}-error`;
|
|
2555
2563
|
const input = useRef2(null);
|
|
2556
2564
|
const isFinished = useRef2(false);
|
|
2557
2565
|
const shouldCancel = useRef2(false);
|
|
@@ -2582,48 +2590,63 @@ function InlineEdit2({
|
|
|
2582
2590
|
setSaving(false);
|
|
2583
2591
|
}
|
|
2584
2592
|
};
|
|
2585
|
-
return /* @__PURE__ */
|
|
2586
|
-
className: "
|
|
2593
|
+
return /* @__PURE__ */ jsx26("form", {
|
|
2594
|
+
className: "min-w-0",
|
|
2587
2595
|
onSubmit: (event) => {
|
|
2588
2596
|
event.preventDefault();
|
|
2589
2597
|
save();
|
|
2590
2598
|
},
|
|
2591
|
-
children:
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
event
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
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
|
+
})
|
|
2627
2650
|
});
|
|
2628
2651
|
}
|
|
2629
2652
|
// src/components/StarRating.tsx
|
|
@@ -3799,7 +3822,7 @@ function StatCard2({
|
|
|
3799
3822
|
});
|
|
3800
3823
|
}
|
|
3801
3824
|
// src/components/AuthForm.tsx
|
|
3802
|
-
import { useId as
|
|
3825
|
+
import { useId as useId3, useState as useState5 } from "react";
|
|
3803
3826
|
import { jsx as jsx40, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
3804
3827
|
function AuthForm2({
|
|
3805
3828
|
mode = "sign-in",
|
|
@@ -3809,7 +3832,7 @@ function AuthForm2({
|
|
|
3809
3832
|
disabled = false,
|
|
3810
3833
|
density
|
|
3811
3834
|
}) {
|
|
3812
|
-
const id =
|
|
3835
|
+
const id = useId3();
|
|
3813
3836
|
const [pending, setPending] = useState5(false);
|
|
3814
3837
|
const [error, setError] = useState5("");
|
|
3815
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 {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ReactElement, type ReactNode } from "react";
|
|
2
|
-
import type { IThemeContextValue, ResolvedTheme, ThemeMode } from "../theme/types.js";
|
|
2
|
+
import type { IThemeContextValue, ResolvedTheme, ThemeMode, ThemePreset } from "../theme/types.js";
|
|
3
3
|
export interface IThemeProviderProps {
|
|
4
4
|
children: ReactNode;
|
|
5
|
+
preset?: ThemePreset;
|
|
5
6
|
defaultTheme?: ThemeMode;
|
|
6
7
|
storageKey?: string;
|
|
7
8
|
forcedTheme?: ResolvedTheme;
|
|
@@ -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.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export { ScrollGuard } from "./components/ScrollGuard.js";
|
|
|
47
47
|
export type { IScrollGuardProps } from "./components/ScrollGuard.js";
|
|
48
48
|
export type { IThemeProviderProps } from "./components/ThemeProvider.js";
|
|
49
49
|
export { useTheme } from "./hooks/useTheme.js";
|
|
50
|
-
export type { ThemeMode, ResolvedTheme, IThemeContextValue } from "./theme/types.js";
|
|
50
|
+
export type { ThemeMode, ResolvedTheme, ThemePreset, IThemeContextValue } from "./theme/types.js";
|
|
51
51
|
export { useComboboxAnchor } from "./hooks/useComboboxAnchor.js";
|
|
52
52
|
export { cn } from "./utils/cn.js";
|
|
53
53
|
export { Toggle, toggleVariants } from "./components/Toggle.js";
|
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",
|
package/dist/theme/types.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { TernaryDarkMode } from "usehooks-ts";
|
|
2
2
|
export type ThemeMode = TernaryDarkMode;
|
|
3
3
|
export type ResolvedTheme = "light" | "dark";
|
|
4
|
+
export type ThemePreset = "devhost" | "shadcn";
|
|
4
5
|
export interface IThemeContextValue {
|
|
6
|
+
preset: ThemePreset;
|
|
5
7
|
theme: ThemeMode;
|
|
6
8
|
resolvedTheme: ResolvedTheme;
|
|
7
9
|
setTheme: (theme: ThemeMode) => void;
|
package/dist/theme.css
CHANGED
|
@@ -46,16 +46,16 @@
|
|
|
46
46
|
--color-sidebar-border: var(--sidebar-border, var(--border));
|
|
47
47
|
--color-sidebar-ring: var(--sidebar-ring, var(--ring));
|
|
48
48
|
--font-sans: var(--ag-font);
|
|
49
|
-
--font-mono: var(--ag-font);
|
|
49
|
+
--font-mono: var(--ag-font-mono);
|
|
50
50
|
--text-xs: var(--ag-text-xs);
|
|
51
51
|
--text-sm: var(--ag-control-text);
|
|
52
52
|
--text-base: var(--ag-text);
|
|
53
53
|
--text-lg: var(--ag-heading);
|
|
54
|
-
--radius-xs:
|
|
55
|
-
--radius-sm:
|
|
56
|
-
--radius-md:
|
|
57
|
-
--radius-lg:
|
|
58
|
-
--radius-xl:
|
|
54
|
+
--radius-xs: var(--ag-radius-xs);
|
|
55
|
+
--radius-sm: var(--ag-radius-sm);
|
|
56
|
+
--radius-md: var(--ag-radius-md);
|
|
57
|
+
--radius-lg: var(--radius);
|
|
58
|
+
--radius-xl: var(--ag-radius-xl);
|
|
59
59
|
--spacing-control: var(--ag-control-h);
|
|
60
60
|
--spacing-control-xs: calc(var(--ag-control-h) - 4px);
|
|
61
61
|
--spacing-control-sm: calc(var(--ag-control-h) - 2px);
|
|
@@ -74,7 +74,13 @@
|
|
|
74
74
|
@layer base {
|
|
75
75
|
:root {
|
|
76
76
|
--ag-font: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
77
|
+
--ag-font-mono:
|
|
78
|
+
"JetBrains Mono Variable", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
77
79
|
--radius: 4px;
|
|
80
|
+
--ag-radius-xs: 2px;
|
|
81
|
+
--ag-radius-sm: 2px;
|
|
82
|
+
--ag-radius-md: 4px;
|
|
83
|
+
--ag-radius-xl: 4px;
|
|
78
84
|
--ag-text-xs: 11px;
|
|
79
85
|
--ag-control-text: 12px;
|
|
80
86
|
--ag-text: 14px;
|
|
@@ -205,6 +211,93 @@
|
|
|
205
211
|
--ag-shadow: 0 6px 22px rgb(0 0 0 / 0.3);
|
|
206
212
|
}
|
|
207
213
|
|
|
214
|
+
[data-theme-preset="shadcn"] {
|
|
215
|
+
--ag-font:
|
|
216
|
+
ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
217
|
+
--radius: 0.625rem;
|
|
218
|
+
--ag-radius-xs: calc(var(--radius) * 0.4);
|
|
219
|
+
--ag-radius-sm: calc(var(--radius) * 0.6);
|
|
220
|
+
--ag-radius-md: calc(var(--radius) * 0.8);
|
|
221
|
+
--ag-radius-xl: calc(var(--radius) * 1.4);
|
|
222
|
+
--faint-foreground: var(--muted-foreground);
|
|
223
|
+
--destructive-soft: color-mix(in oklab, var(--destructive) 15%, transparent);
|
|
224
|
+
--frame-edge: var(--border);
|
|
225
|
+
--frame-halo: transparent;
|
|
226
|
+
--terminal-background: var(--background);
|
|
227
|
+
--ag-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
[data-theme-preset="shadcn"],
|
|
231
|
+
[data-theme-preset="shadcn"][data-theme="dark"] {
|
|
232
|
+
--background: oklch(0.145 0 0);
|
|
233
|
+
--foreground: oklch(0.985 0 0);
|
|
234
|
+
--card: oklch(0.205 0 0);
|
|
235
|
+
--card-foreground: oklch(0.985 0 0);
|
|
236
|
+
--popover: oklch(0.205 0 0);
|
|
237
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
238
|
+
--primary: oklch(0.922 0 0);
|
|
239
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
240
|
+
--secondary: oklch(0.269 0 0);
|
|
241
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
242
|
+
--muted: oklch(0.269 0 0);
|
|
243
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
244
|
+
--accent: oklch(0.269 0 0);
|
|
245
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
246
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
247
|
+
--destructive-foreground: oklch(0.145 0 0);
|
|
248
|
+
--border: oklch(1 0 0 / 10%);
|
|
249
|
+
--input: oklch(1 0 0 / 15%);
|
|
250
|
+
--ring: oklch(0.556 0 0);
|
|
251
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
252
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
253
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
254
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
255
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
256
|
+
--sidebar: oklch(0.205 0 0);
|
|
257
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
258
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
259
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
260
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
261
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
262
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
263
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
[data-theme-preset="shadcn"][data-theme="light"] {
|
|
267
|
+
--background: oklch(1 0 0);
|
|
268
|
+
--foreground: oklch(0.145 0 0);
|
|
269
|
+
--card: oklch(1 0 0);
|
|
270
|
+
--card-foreground: oklch(0.145 0 0);
|
|
271
|
+
--popover: oklch(1 0 0);
|
|
272
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
273
|
+
--primary: oklch(0.205 0 0);
|
|
274
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
275
|
+
--secondary: oklch(0.97 0 0);
|
|
276
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
277
|
+
--muted: oklch(0.97 0 0);
|
|
278
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
279
|
+
--accent: oklch(0.97 0 0);
|
|
280
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
281
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
282
|
+
--destructive-foreground: oklch(1 0 0);
|
|
283
|
+
--border: oklch(0.922 0 0);
|
|
284
|
+
--input: oklch(0.922 0 0);
|
|
285
|
+
--ring: oklch(0.708 0 0);
|
|
286
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
287
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
288
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
289
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
290
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
291
|
+
--sidebar: oklch(0.985 0 0);
|
|
292
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
293
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
294
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
295
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
296
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
297
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
298
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
299
|
+
}
|
|
300
|
+
|
|
208
301
|
[data-density="comfortable"] {
|
|
209
302
|
--ag-text-xs: 11px;
|
|
210
303
|
--ag-control-text: 12px;
|
|
@@ -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
|
@@ -198,10 +198,15 @@ An independent production Vite consumer installed from the archive verifies RTL
|
|
|
198
198
|
| `ThemeProvider.defaultTheme` | `"light" \| "dark" \| "system"` | `"system"` | Initial preference when storage has no saved value. |
|
|
199
199
|
| `ThemeProvider.storageKey` | `string` | `"shadcn-alexgorbatchev-theme"` | Local storage key for the preference. |
|
|
200
200
|
| `ThemeProvider.forcedTheme` | `"light" \| "dark"` | unset | Overrides the displayed palette without changing the saved preference. |
|
|
201
|
+
| `ThemeProvider.preset` | `"devhost" \| "shadcn"` | `"devhost"` | Document-wide design preset, independent of color mode and density. |
|
|
201
202
|
| `DensityProvider.density` | `"compact" \| "comfortable"` | `"comfortable"` | Density for descendants and their portals. |
|
|
202
203
|
| Component `density` | `"compact" \| "comfortable"` | inherited | Local override on styled components that expose the prop. |
|
|
203
204
|
|
|
204
|
-
`useTheme()` returns `theme`, `resolvedTheme`, and `
|
|
205
|
+
`useTheme()` returns `theme`, `resolvedTheme`, `setTheme`, and `preset`. Call `setTheme("light")`, `setTheme("dark")`, or `setTheme("system")` from a component inside the provider.
|
|
206
|
+
|
|
207
|
+
Use `<ThemeProvider preset="shadcn" defaultTheme="system">` for the [default shadcn Neutral palette](https://ui.shadcn.com/docs/theming), system sans-serif text, and its rounded corner scale. The default `devhost` preset uses the package's monospace design and sharp corners. Both presets support light, dark, and system modes, including portaled content. Code retains the monospace font. Density, component composition, and package-specific status colors remain independent of the preset.
|
|
208
|
+
|
|
209
|
+
The preset is controlled by the prop; changing it updates the document without changing the saved color-mode preference. Persist that prop in your application if needed. Storybook exposes the same choice in its **Design** toolbar. For a page styled before React mounts, set `data-theme-preset="shadcn"` and `data-theme="light"` or `"dark"` on `<html>` to match the provider. The existing `theme.css` import and `@source` setup cover both presets.
|
|
205
210
|
|
|
206
211
|
Override tokens after the theme imports. This example uses the existing success palette as the primary accent:
|
|
207
212
|
|
|
@@ -424,6 +429,8 @@ import {
|
|
|
424
429
|
|
|
425
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.
|
|
426
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
|
+
|
|
427
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.
|
|
428
435
|
|
|
429
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.
|
|
@@ -432,6 +439,10 @@ Column `meta.filter` offers a column in `DataGridFilters`, hidden columns includ
|
|
|
432
439
|
|
|
433
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.
|
|
434
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
|
+
|
|
435
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.
|
|
436
447
|
|
|
437
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",
|