@crispy-seed/data-table 0.6.0 → 0.8.0
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/dist/index.cjs +504 -153
- package/dist/index.d.ts +98 -39
- package/dist/index.js +504 -153
- package/dist/{record-panel-recipe.css → record-preview-recipe.css} +35 -35
- package/dist/{record-panel-recipe.layered.css → record-preview-recipe.layered.css} +35 -35
- package/dist/resizable-panel-recipe.css +62 -22
- package/dist/resizable-panel-recipe.layered.css +55 -21
- package/dist/styles.css +90 -56
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -769,21 +769,31 @@ function isFunction$1(value) {
|
|
|
769
769
|
|
|
770
770
|
const DATA_TABLE_UNIVERSAL_CALC_OPS = [
|
|
771
771
|
"count",
|
|
772
|
-
"
|
|
772
|
+
"empty",
|
|
773
|
+
"notEmpty"
|
|
773
774
|
];
|
|
774
775
|
const DATA_TABLE_NUMERIC_CALC_OPS = [
|
|
775
776
|
"sum",
|
|
776
777
|
"average",
|
|
778
|
+
"median",
|
|
777
779
|
"min",
|
|
778
|
-
"max"
|
|
780
|
+
"max",
|
|
781
|
+
"range"
|
|
782
|
+
];
|
|
783
|
+
/** Offered instead of `DATA_TABLE_NUMERIC_CALC_OPS` on a non-numeric column. */ const DATA_TABLE_NON_NUMERIC_CALC_OPS = [
|
|
784
|
+
"countUnique"
|
|
779
785
|
];
|
|
780
786
|
const DATA_TABLE_CALC_LABEL = {
|
|
781
787
|
count: "Count",
|
|
782
|
-
|
|
788
|
+
empty: "Empty",
|
|
789
|
+
notEmpty: "Not empty",
|
|
783
790
|
sum: "Sum",
|
|
784
791
|
average: "Average",
|
|
792
|
+
median: "Median",
|
|
785
793
|
min: "Min",
|
|
786
|
-
max: "Max"
|
|
794
|
+
max: "Max",
|
|
795
|
+
range: "Range",
|
|
796
|
+
countUnique: "Count unique"
|
|
787
797
|
};
|
|
788
798
|
/** A value counts as empty when it is null/undefined, an empty string, or an empty
|
|
789
799
|
* array -- crisp's own `isEmptyValue` (columncalc.tsx), reused verbatim. */ function isEmptyValueForCalc(value) {
|
|
@@ -792,29 +802,50 @@ const DATA_TABLE_CALC_LABEL = {
|
|
|
792
802
|
if (Array.isArray(value)) return value.length === 0;
|
|
793
803
|
return false;
|
|
794
804
|
}
|
|
805
|
+
/** The middle value of a SORTED, non-empty numeric array -- the mean of the two
|
|
806
|
+
* middle entries on an even-length array, same convention as every stats library. */ function median(sortedNumbers) {
|
|
807
|
+
const mid = Math.floor(sortedNumbers.length / 2);
|
|
808
|
+
return sortedNumbers.length % 2 === 0 ? (sortedNumbers[mid - 1] + sortedNumbers[mid]) / 2 : sortedNumbers[mid];
|
|
809
|
+
}
|
|
795
810
|
/**
|
|
796
811
|
* Pure so the arithmetic is independently (and mutation-) testable without rendering a
|
|
797
|
-
* table at all. `sum`/`average`/`min`/`max` skip
|
|
798
|
-
* (coerced via `Number`, dropping `NaN`); they
|
|
799
|
-
* misleading 0 -- when there is nothing
|
|
812
|
+
* table at all. The numeric ops (`sum`/`average`/`median`/`min`/`max`/`range`) skip
|
|
813
|
+
* empty values and non-numeric ones (coerced via `Number`, dropping `NaN`); they
|
|
814
|
+
* return `null` -- render nothing, not a misleading 0 -- when there is nothing
|
|
815
|
+
* numeric left to reduce over. `countUnique` counts distinct NON-EMPTY values
|
|
816
|
+
* (`JSON.stringify`-normalized, so two arrays/objects with the same shape count
|
|
817
|
+
* once) -- empties are already their own op (`empty`), so folding them into "one
|
|
818
|
+
* more unique value" here would double-count the same information two different
|
|
819
|
+
* ways in the same menu.
|
|
800
820
|
*/ function computeColumnCalc(values, op) {
|
|
801
821
|
switch(op){
|
|
802
822
|
case "count":
|
|
803
823
|
return values.length;
|
|
804
|
-
case "
|
|
824
|
+
case "empty":
|
|
825
|
+
return values.filter(isEmptyValueForCalc).length;
|
|
826
|
+
case "notEmpty":
|
|
805
827
|
return values.filter((value)=>!isEmptyValueForCalc(value)).length;
|
|
828
|
+
case "countUnique":
|
|
829
|
+
{
|
|
830
|
+
const seen = new Set(values.filter((value)=>!isEmptyValueForCalc(value)).map((value)=>JSON.stringify(value)));
|
|
831
|
+
return seen.size;
|
|
832
|
+
}
|
|
806
833
|
}
|
|
807
|
-
const numbers = values.filter((value)=>!isEmptyValueForCalc(value)).map((value)=>typeof value === "number" ? value : Number(value)).filter((value)=>Number.isFinite(value));
|
|
834
|
+
const numbers = values.filter((value)=>!isEmptyValueForCalc(value)).map((value)=>typeof value === "number" ? value : Number(value)).filter((value)=>Number.isFinite(value)).sort((a, b)=>a - b);
|
|
808
835
|
if (numbers.length === 0) return null;
|
|
809
836
|
switch(op){
|
|
810
837
|
case "sum":
|
|
811
838
|
return numbers.reduce((total, value)=>total + value, 0);
|
|
812
839
|
case "average":
|
|
813
840
|
return numbers.reduce((total, value)=>total + value, 0) / numbers.length;
|
|
841
|
+
case "median":
|
|
842
|
+
return median(numbers);
|
|
814
843
|
case "min":
|
|
815
|
-
return
|
|
844
|
+
return numbers[0];
|
|
816
845
|
case "max":
|
|
817
|
-
return
|
|
846
|
+
return numbers[numbers.length - 1];
|
|
847
|
+
case "range":
|
|
848
|
+
return numbers[numbers.length - 1] - numbers[0];
|
|
818
849
|
default:
|
|
819
850
|
return null;
|
|
820
851
|
}
|
|
@@ -1219,31 +1250,48 @@ const EMPTY_RECORD = {};
|
|
|
1219
1250
|
]);
|
|
1220
1251
|
const endColumnDrag = React.useCallback(()=>setDraggingColumnKey(null), []);
|
|
1221
1252
|
// -- drag-to-resize ------------------------------------------------------------------
|
|
1222
|
-
// `resizeStartRef` holds
|
|
1223
|
-
//
|
|
1224
|
-
//
|
|
1225
|
-
//
|
|
1253
|
+
// `resizeStartRef` holds everything that must survive across pointermove events
|
|
1254
|
+
// WITHOUT triggering a re-render on every pixel: the drag's origin, AND (as of the
|
|
1255
|
+
// live-review fix below) the latest computed width, so `endColumnResize` has
|
|
1256
|
+
// something to commit. State (`resizingColumnKey`) is only for what the render
|
|
1257
|
+
// actually needs: which column's handle should show its active/dragging look.
|
|
1258
|
+
//
|
|
1259
|
+
// `updateColumnResize` used to call `setColumnWidth` on every single pointermove --
|
|
1260
|
+
// a `setColumnWidths` state update (and therefore a full DataTable re-render) per
|
|
1261
|
+
// PIXEL of drag, confirmed live as the stutter a column-border drag produced. It now
|
|
1262
|
+
// only computes+returns the clamped width; the styled layer (`resize.tsx`) writes
|
|
1263
|
+
// that directly to the dragged column's own `<col>` element (`col.style.width`,
|
|
1264
|
+
// outside React), which is all a resize needs frame-to-frame -- table layout already
|
|
1265
|
+
// recalculates from a `<col>`'s own inline style with no re-render involved. React
|
|
1266
|
+
// state (and this hook's `onColumnWidthsChange` contract) is only touched ONCE, in
|
|
1267
|
+
// `endColumnResize`, committing the final width exactly as a controlled consumer
|
|
1268
|
+
// already expects.
|
|
1226
1269
|
const resizeStartRef = React.useRef(null);
|
|
1227
1270
|
const [resizingColumnKey, setResizingColumnKey] = React.useState(null);
|
|
1228
1271
|
const beginColumnResize = React.useCallback((key, clientX, currentWidth)=>{
|
|
1229
1272
|
resizeStartRef.current = {
|
|
1230
1273
|
key,
|
|
1231
1274
|
startX: clientX,
|
|
1232
|
-
startWidth: currentWidth
|
|
1275
|
+
startWidth: currentWidth,
|
|
1276
|
+
currentWidth
|
|
1233
1277
|
};
|
|
1234
1278
|
setResizingColumnKey(key);
|
|
1235
1279
|
}, []);
|
|
1236
1280
|
const updateColumnResize = React.useCallback((clientX)=>{
|
|
1237
1281
|
const start = resizeStartRef.current;
|
|
1238
|
-
if (!start) return;
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
]);
|
|
1282
|
+
if (!start) return undefined;
|
|
1283
|
+
const width = clampColumnWidth(start.startWidth + (clientX - start.startX));
|
|
1284
|
+
start.currentWidth = width;
|
|
1285
|
+
return width;
|
|
1286
|
+
}, []);
|
|
1243
1287
|
const endColumnResize = React.useCallback(()=>{
|
|
1288
|
+
const start = resizeStartRef.current;
|
|
1289
|
+
if (start) setColumnWidth(start.key, start.currentWidth);
|
|
1244
1290
|
resizeStartRef.current = null;
|
|
1245
1291
|
setResizingColumnKey(null);
|
|
1246
|
-
}, [
|
|
1292
|
+
}, [
|
|
1293
|
+
setColumnWidth
|
|
1294
|
+
]);
|
|
1247
1295
|
return {
|
|
1248
1296
|
columnOrder,
|
|
1249
1297
|
visibleColumnOrder,
|
|
@@ -1593,11 +1641,16 @@ function useDataTableSort(props) {
|
|
|
1593
1641
|
const [sort, setSort] = useControllableState$1({
|
|
1594
1642
|
prop: sortProp,
|
|
1595
1643
|
defaultProp: defaultSort,
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1644
|
+
// Live-review fix: this used to only fire for a DEFINED next sort ("`toggleSort`
|
|
1645
|
+
// never clears it, so why would this ever see `undefined`") -- true until
|
|
1646
|
+
// `clearSort` (below) existed. Guarding the callback on truthiness meant a
|
|
1647
|
+
// consumer controlling `sort` had no way to ever learn the column sort was
|
|
1648
|
+
// cleared: `setSort(undefined)` would run, but the controlled VALUE never moves
|
|
1649
|
+
// without the consumer's own state updating from this callback, and the guard
|
|
1650
|
+
// swallowed exactly the call that would have told it to. Every other value this
|
|
1651
|
+
// hook contract carries (`onColumnWidthsChange`, `onColumnCalcOpsChange`, ...)
|
|
1652
|
+
// already passes its "cleared" shape through unconditionally; this now matches.
|
|
1653
|
+
onChange: onSortChange
|
|
1601
1654
|
});
|
|
1602
1655
|
const toggleSort = React.useCallback((key)=>{
|
|
1603
1656
|
setSort((current)=>{
|
|
@@ -1613,6 +1666,12 @@ function useDataTableSort(props) {
|
|
|
1613
1666
|
}, [
|
|
1614
1667
|
setSort
|
|
1615
1668
|
]);
|
|
1669
|
+
/** Attio/GRC's "Remove sort" -- the header click cycle (above) only ever toggles
|
|
1670
|
+
* asc/desc once a column has been clicked once; this is the only way back to no
|
|
1671
|
+
* sort at all. Exposed for a column's own header menu (`column-menu.tsx`'s
|
|
1672
|
+
* `ColumnMenu`) to offer as a "Clear sort" item when that column is the sorted one. */ const clearSort = React.useCallback(()=>setSort(undefined), [
|
|
1673
|
+
setSort
|
|
1674
|
+
]);
|
|
1616
1675
|
const getHeaderCellProps = React.useCallback((columnKey, sortable)=>{
|
|
1617
1676
|
const isSorted = Boolean(columnKey) && sort?.key === columnKey;
|
|
1618
1677
|
const canSort = Boolean(sortable && columnKey);
|
|
@@ -1642,6 +1701,7 @@ function useDataTableSort(props) {
|
|
|
1642
1701
|
return {
|
|
1643
1702
|
sort,
|
|
1644
1703
|
toggleSort,
|
|
1704
|
+
clearSort,
|
|
1645
1705
|
getHeaderCellProps
|
|
1646
1706
|
};
|
|
1647
1707
|
}
|
|
@@ -4163,7 +4223,7 @@ function formatCalcValue(value) {
|
|
|
4163
4223
|
]);
|
|
4164
4224
|
const ops = [
|
|
4165
4225
|
...DATA_TABLE_UNIVERSAL_CALC_OPS,
|
|
4166
|
-
...numeric ? DATA_TABLE_NUMERIC_CALC_OPS :
|
|
4226
|
+
...numeric ? DATA_TABLE_NUMERIC_CALC_OPS : DATA_TABLE_NON_NUMERIC_CALC_OPS
|
|
4167
4227
|
];
|
|
4168
4228
|
return /*#__PURE__*/ jsxRuntime.jsxs(MenuRoot, {
|
|
4169
4229
|
children: [
|
|
@@ -4229,8 +4289,32 @@ function formatCalcValue(value) {
|
|
|
4229
4289
|
* once captured, this SAME element keeps receiving `pointermove`/`pointerup` even
|
|
4230
4290
|
* after the cursor leaves it, so the drag math can live entirely in this one
|
|
4231
4291
|
* handler set with no effect/cleanup to get wrong.
|
|
4292
|
+
*
|
|
4293
|
+
* Live-review fix (stutter): `updateColumnResize` used to also commit the new width
|
|
4294
|
+
* to React state on every `pointermove` -- a full DataTable re-render per pixel of
|
|
4295
|
+
* drag, confirmed as the stutter. During the drag this component now writes the
|
|
4296
|
+
* computed width straight to the dragged column's own `<col>` element (found once,
|
|
4297
|
+
* at pointerdown, via its `data-column-key`, per `column-layout`'s `<colgroup>`
|
|
4298
|
+
* comment: a `<col>`'s own inline `width` is the SINGLE place table layout reads a
|
|
4299
|
+
* column's width from, so writing it directly reflows the table with no React
|
|
4300
|
+
* render at all), coalesced to at most one write per animation frame so a fast
|
|
4301
|
+
* mouse doesn't queue more layout work than the screen can show. React state (and
|
|
4302
|
+
* a controlled consumer's `onColumnWidthsChange`) is only touched ONCE more, in
|
|
4303
|
+
* `endColumnResize`, which is unchanged: it commits whatever `updateColumnResize`
|
|
4304
|
+
* last computed.
|
|
4232
4305
|
*/ function ColumnResizeHandle({ columnKey, width }) {
|
|
4233
4306
|
const { beginColumnResize, updateColumnResize, endColumnResize, isColumnResizing } = useDataTableContext();
|
|
4307
|
+
// Survive across pointermove events without a re-render: the `<col>` this drag
|
|
4308
|
+
// writes to (resolved once, at pointerdown) and the rAF coalescing the writes.
|
|
4309
|
+
const colElementRef = React.useRef(null);
|
|
4310
|
+
const pendingWidthRef = React.useRef(null);
|
|
4311
|
+
const rafIdRef = React.useRef(null);
|
|
4312
|
+
const flushPendingWidth = ()=>{
|
|
4313
|
+
rafIdRef.current = null;
|
|
4314
|
+
const col = colElementRef.current;
|
|
4315
|
+
const pendingWidth = pendingWidthRef.current;
|
|
4316
|
+
if (col && pendingWidth != null) col.style.width = `${pendingWidth}px`;
|
|
4317
|
+
};
|
|
4234
4318
|
return /*#__PURE__*/ jsxRuntime.jsx("span", {
|
|
4235
4319
|
// Mouse/pointer-drag only for now (no keyboard equivalent yet) -- `aria-hidden`
|
|
4236
4320
|
// rather than `role="separator"` + `aria-label`, which would otherwise fold
|
|
@@ -4246,16 +4330,32 @@ function formatCalcValue(value) {
|
|
|
4246
4330
|
event.stopPropagation();
|
|
4247
4331
|
event.preventDefault();
|
|
4248
4332
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
4249
|
-
const
|
|
4333
|
+
const headerCell = event.currentTarget.closest('[role="columnheader"]');
|
|
4334
|
+
const startWidth = width ?? headerCell?.getBoundingClientRect().width ?? 150;
|
|
4335
|
+
colElementRef.current = event.currentTarget.closest("table")?.querySelector(`col[data-column-key="${CSS.escape(columnKey)}"]`) ?? null;
|
|
4250
4336
|
beginColumnResize(columnKey, event.clientX, startWidth);
|
|
4251
4337
|
},
|
|
4252
4338
|
onPointerMove: (event)=>{
|
|
4253
|
-
if (event.currentTarget.hasPointerCapture(event.pointerId))
|
|
4254
|
-
|
|
4339
|
+
if (!event.currentTarget.hasPointerCapture(event.pointerId)) return;
|
|
4340
|
+
const nextWidth = updateColumnResize(event.clientX);
|
|
4341
|
+
if (nextWidth == null) return;
|
|
4342
|
+
pendingWidthRef.current = nextWidth;
|
|
4343
|
+
if (rafIdRef.current == null) {
|
|
4344
|
+
rafIdRef.current = requestAnimationFrame(flushPendingWidth);
|
|
4255
4345
|
}
|
|
4256
4346
|
},
|
|
4257
4347
|
onPointerUp: (event)=>{
|
|
4258
4348
|
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
4349
|
+
if (rafIdRef.current != null) {
|
|
4350
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
4351
|
+
rafIdRef.current = null;
|
|
4352
|
+
}
|
|
4353
|
+
// Apply any not-yet-flushed width immediately so the `<col>` matches the
|
|
4354
|
+
// exact value `endColumnResize` is about to commit to React state -- with
|
|
4355
|
+
// no more pointermoves coming, nothing else would flush it.
|
|
4356
|
+
flushPendingWidth();
|
|
4357
|
+
pendingWidthRef.current = null;
|
|
4358
|
+
colElementRef.current = null;
|
|
4259
4359
|
endColumnResize();
|
|
4260
4360
|
},
|
|
4261
4361
|
children: /*#__PURE__*/ jsxRuntime.jsx("span", {
|
|
@@ -4446,68 +4546,100 @@ function ColumnRenameInput({ column, initialLabel, onDone }) {
|
|
|
4446
4546
|
* permanently, so this one carries its own marker attribute distinct from that
|
|
4447
4547
|
* slot's OTHER occupants (the sort chevron, the filter trigger), which stay visible
|
|
4448
4548
|
* under their own separate rules regardless of hover. */ function ColumnMenu({ column, onRename }) {
|
|
4449
|
-
const { moveColumn, hideColumn, isColumnPinned, togglePinColumn } = useDataTableContext();
|
|
4549
|
+
const { moveColumn, hideColumn, isColumnPinned, togglePinColumn, sort, clearSort } = useDataTableContext();
|
|
4450
4550
|
const columnLabel = typeof column.header === "string" ? column.header : column.key;
|
|
4451
4551
|
const pinned = isColumnPinned(column.key);
|
|
4552
|
+
// Attio/GRC's "Remove sort" (live-review fix D) -- the header's own click-to-sort
|
|
4553
|
+
// (`SortIndicator`/`toggleSort`, above) only cycles asc/desc once a column has been
|
|
4554
|
+
// clicked; this is the only affordance back to no sort at all, so it only makes
|
|
4555
|
+
// sense to show on the column that IS currently sorted.
|
|
4556
|
+
const isSorted = sort?.key === column.key;
|
|
4452
4557
|
const stopPropagation = (event)=>event.stopPropagation();
|
|
4453
|
-
return
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4558
|
+
return(// Live-review fix (D): a `MenuItem` inside `MenuPositioner` renders through a
|
|
4559
|
+
// `FloatingPortal` -- a REAL React portal (`ReactDOM.createPortal`), which
|
|
4560
|
+
// bubbles a click/keydown up through the REACT TREE (this component, then the
|
|
4561
|
+
// columnheader that renders it), NOT the DOM tree the portal actually appends
|
|
4562
|
+
// to. Confirmed live: clicking ANY item here (not just "Clear sort" -- "Move
|
|
4563
|
+
// left" on a column with no sort at all reproduced it too) also fired the
|
|
4564
|
+
// COLUMNHEADER's own `toggleSort`, because nothing stopped that bubble before
|
|
4565
|
+
// it reached the header's `onClick`/`onKeyDown` (`getHeaderCellProps`). The
|
|
4566
|
+
// trigger's own `stopPropagation` (on `DataTableHeaderCellIcon` below) only
|
|
4567
|
+
// covers clicks that start on the trigger itself -- a SIBLING of
|
|
4568
|
+
// `MenuPositioner` in this tree, not an ancestor of it, so it never sees a
|
|
4569
|
+
// menu-item event at all. This wrapper is a genuine DOM node sitting between
|
|
4570
|
+
// BOTH of them and the header, so it catches the bubble from either one;
|
|
4571
|
+
// `display: contents` keeps it invisible to `DataTableHeaderCellInner`'s own
|
|
4572
|
+
// flex layout.
|
|
4573
|
+
// biome-ignore lint/a11y/noStaticElementInteractions: not a widget -- this span never responds to interaction on ITSELF, only stops propagation of events that already occurred on a real interactive descendant (the trigger button, the portaled menu items). No role fits it because it isn't a control.
|
|
4574
|
+
/*#__PURE__*/ jsxRuntime.jsx("span", {
|
|
4575
|
+
onClick: stopPropagation,
|
|
4576
|
+
onKeyDown: stopPropagation,
|
|
4577
|
+
style: {
|
|
4578
|
+
display: "contents"
|
|
4579
|
+
},
|
|
4580
|
+
children: /*#__PURE__*/ jsxRuntime.jsxs(MenuRoot, {
|
|
4581
|
+
children: [
|
|
4582
|
+
/*#__PURE__*/ jsxRuntime.jsx(DataTableHeaderCellIcon, {
|
|
4583
|
+
asChild: true,
|
|
4584
|
+
"data-column-menu": "",
|
|
4585
|
+
draggable: false,
|
|
4586
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(MenuTrigger, {
|
|
4587
|
+
"aria-label": `${columnLabel} column menu`,
|
|
4588
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(IconDot3HorizontalLine_default, {
|
|
4589
|
+
size: 16
|
|
4590
|
+
})
|
|
4465
4591
|
})
|
|
4466
|
-
})
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
})
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
})
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
})
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
})
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4592
|
+
}),
|
|
4593
|
+
/*#__PURE__*/ jsxRuntime.jsx(MenuPositioner, {
|
|
4594
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(MenuContent, {
|
|
4595
|
+
style: {
|
|
4596
|
+
minWidth: 160
|
|
4597
|
+
},
|
|
4598
|
+
children: /*#__PURE__*/ jsxRuntime.jsxs(MenuScrollArea, {
|
|
4599
|
+
children: [
|
|
4600
|
+
/*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
|
|
4601
|
+
onClick: onRename,
|
|
4602
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
|
|
4603
|
+
children: "Rename"
|
|
4604
|
+
})
|
|
4605
|
+
}),
|
|
4606
|
+
/*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
|
|
4607
|
+
onClick: ()=>moveColumn(column.key, "left"),
|
|
4608
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
|
|
4609
|
+
children: "Move left"
|
|
4610
|
+
})
|
|
4611
|
+
}),
|
|
4612
|
+
/*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
|
|
4613
|
+
onClick: ()=>moveColumn(column.key, "right"),
|
|
4614
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
|
|
4615
|
+
children: "Move right"
|
|
4616
|
+
})
|
|
4617
|
+
}),
|
|
4618
|
+
isSorted && /*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
|
|
4619
|
+
onClick: clearSort,
|
|
4620
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
|
|
4621
|
+
children: "Clear sort"
|
|
4622
|
+
})
|
|
4623
|
+
}),
|
|
4624
|
+
/*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
|
|
4625
|
+
onClick: ()=>togglePinColumn(column.key),
|
|
4626
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
|
|
4627
|
+
children: pinned ? "Unpin column" : "Pin column"
|
|
4628
|
+
})
|
|
4629
|
+
}),
|
|
4630
|
+
/*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
|
|
4631
|
+
onClick: ()=>hideColumn(column.key),
|
|
4632
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
|
|
4633
|
+
children: "Hide column"
|
|
4634
|
+
})
|
|
4503
4635
|
})
|
|
4504
|
-
|
|
4505
|
-
|
|
4636
|
+
]
|
|
4637
|
+
})
|
|
4506
4638
|
})
|
|
4507
4639
|
})
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
});
|
|
4640
|
+
]
|
|
4641
|
+
})
|
|
4642
|
+
}));
|
|
4511
4643
|
}
|
|
4512
4644
|
|
|
4513
4645
|
/**
|
|
@@ -10224,6 +10356,25 @@ function DataTableInner(props, ref) {
|
|
|
10224
10356
|
// column/direction is active. A consumer that pre-sorts server-side can pass
|
|
10225
10357
|
// a controlled `sort`; this still sorts the rows it's given, which for
|
|
10226
10358
|
// already-sorted data is a no-op.
|
|
10359
|
+
//
|
|
10360
|
+
// Live-review fix (D, "Clear sort"): the JSX below passes the RAW `sort` prop
|
|
10361
|
+
// (not `resolvedSort`) on to `DataTablePrimitiveRoot`, plus `defaultSort` --
|
|
10362
|
+
// passing `resolvedSort` used to feed the headless `useDataTableSort`'s own
|
|
10363
|
+
// `useControllableState` a value that starts `undefined` (uncontrolled) and
|
|
10364
|
+
// BECOMES defined the instant a header is first clicked (once `internalSort`
|
|
10365
|
+
// picks it up), flipping that inner hook from uncontrolled to controlled
|
|
10366
|
+
// mid-lifecycle -- `@radix-ui/react-use-controllable-state` warns about
|
|
10367
|
+
// exactly this and, worse, silently misbehaves on it: its own internal
|
|
10368
|
+
// uncontrolled state stops updating the moment it's controlled, so when
|
|
10369
|
+
// `clearSort` later drove `resolvedSort` back to `undefined` (flipping BACK
|
|
10370
|
+
// to uncontrolled), the hook fell through to that stale, never-updated
|
|
10371
|
+
// internal state instead -- still the pre-clear value, confirmed live
|
|
10372
|
+
// (`aria-sort` stayed "ascending" after "Clear sort"). The RAW `sort` prop
|
|
10373
|
+
// never has this problem: for the common uncontrolled case it's simply
|
|
10374
|
+
// always `undefined`, so the headless hook is uncontrolled for its entire
|
|
10375
|
+
// lifetime and owns its state correctly; a consumer that genuinely controls
|
|
10376
|
+
// `sort` behaves exactly as before (`resolvedSort === sort` whenever `sort`
|
|
10377
|
+
// is defined, so nothing changes for that path).
|
|
10227
10378
|
const [internalSort, setInternalSort] = React__namespace.useState(defaultSort);
|
|
10228
10379
|
const resolvedSort = sort !== undefined ? sort : internalSort;
|
|
10229
10380
|
const handleSortChange = React__namespace.useCallback((next)=>{
|
|
@@ -10329,7 +10480,8 @@ function DataTableInner(props, ref) {
|
|
|
10329
10480
|
selectedIds: selectedIds,
|
|
10330
10481
|
defaultSelectedIds: defaultSelectedIds,
|
|
10331
10482
|
onSelectionChange: onSelectionChange,
|
|
10332
|
-
sort:
|
|
10483
|
+
sort: sort,
|
|
10484
|
+
defaultSort: defaultSort,
|
|
10333
10485
|
onSortChange: handleSortChange,
|
|
10334
10486
|
query: resolvedQuery,
|
|
10335
10487
|
onQueryChange: handleQueryChange,
|
|
@@ -10592,6 +10744,10 @@ const DataTableTable = withContext$1(DataTableTable$1, "table");
|
|
|
10592
10744
|
}
|
|
10593
10745
|
}),
|
|
10594
10746
|
effective.map(({ column, width })=>/*#__PURE__*/ jsxRuntime.jsx("col", {
|
|
10747
|
+
// Read by `resize.tsx`'s own pointermove handler (`data-column-key`,
|
|
10748
|
+
// not a ref) to write the dragged width straight to this element
|
|
10749
|
+
// during a drag -- see that file's own comment for why.
|
|
10750
|
+
"data-column-key": column.key,
|
|
10595
10751
|
style: width === undefined ? undefined : {
|
|
10596
10752
|
width
|
|
10597
10753
|
}
|
|
@@ -11020,7 +11176,7 @@ DataTableViewsControl.displayName = "DataTableViewsControl";
|
|
|
11020
11176
|
});
|
|
11021
11177
|
}
|
|
11022
11178
|
|
|
11023
|
-
__insertCSS(".seed-resizable-panel__root{position:relative;display:flex;height:100%}.seed-resizable-panel__spacer{flex:0 0 auto;height:100%;transition:width .14s}.seed-resizable-panel__panel{position:absolute;top:0;bottom:0;box-sizing:border-box;overflow:hidden;background:var(--seed-color-bg-layer-default)}.seed-resizable-panel__panel[data-side=left]{left:0}.seed-resizable-panel__panel[data-side=right]{right:0}.seed-resizable-panel__panel[data-floating=false]{border-radius:0;box-shadow:none}.seed-resizable-panel__panel[data-floating=false][data-side=left]{border-inline-end:1px solid var(--seed-color-stroke-neutral-muted)}.seed-resizable-panel__panel[data-floating=false][data-side=right]{border-inline-start:1px solid var(--seed-color-stroke-neutral-muted)}.seed-resizable-panel__panel[data-floating=true]{position:
|
|
11179
|
+
__insertCSS(".seed-resizable-panel__root{position:relative;display:flex;height:100%}.seed-resizable-panel__spacer{flex:0 0 auto;height:100%;transition:width .14s}.seed-resizable-panel__panel{position:absolute;top:0;bottom:0;box-sizing:border-box;overflow:hidden;background:var(--seed-color-bg-layer-default)}.seed-resizable-panel__panel[data-side=left]{left:0}.seed-resizable-panel__panel[data-side=right]{right:0}.seed-resizable-panel__panel[data-floating=false]{border-radius:0;box-shadow:none}.seed-resizable-panel__panel[data-floating=false][data-side=left]{border-inline-end:1px solid var(--seed-color-stroke-neutral-muted)}.seed-resizable-panel__panel[data-floating=false][data-side=right]{border-inline-start:1px solid var(--seed-color-stroke-neutral-muted)}.seed-resizable-panel__panel[data-floating=true]{position:absolute;top:8px;bottom:8px;z-index:20;border-inline-end:none;border-inline-start:none;border-radius:12px;box-shadow:0 0 0 1px rgba(28,40,64,.04),0 4px 8px -4px rgba(28,40,64,.12),0 4px 12px -2px rgba(24,41,75,.16);transform:none;opacity:1}.seed-resizable-panel__panel[data-floating=true][data-side=left]{left:8px}.seed-resizable-panel__panel[data-floating=true][data-side=right]{right:8px}.seed-resizable-panel__panel[data-floating=true][data-side=left][data-state=open]{animation:seed-enter;animation-timing-function:cubic-bezier(0.16,1,0.3,1);animation-duration:.18s;--seed-enter-translate-x:calc(-100% - 8px);--seed-enter-translate-y:0;--seed-enter-opacity:0;--seed-enter-scale:1}@media (prefers-reduced-motion:reduce){.seed-resizable-panel__panel[data-floating=true][data-side=left][data-state=open]{--seed-enter-translate-x:0}}.seed-resizable-panel__panel[data-floating=true][data-side=left][data-state=closed]{animation:seed-exit;animation-timing-function:cubic-bezier(0.4,0,1,1);animation-duration:.14s;animation-fill-mode:forwards;--seed-exit-translate-x:calc(-100% - 8px);--seed-exit-translate-y:0;--seed-exit-opacity:0;--seed-exit-scale:1}@media (prefers-reduced-motion:reduce){.seed-resizable-panel__panel[data-floating=true][data-side=left][data-state=closed]{--seed-exit-translate-x:0}}.seed-resizable-panel__panel[data-floating=true][data-side=right][data-state=open]{animation:seed-enter;animation-timing-function:cubic-bezier(0.16,1,0.3,1);animation-duration:.18s;--seed-enter-translate-x:calc(100% + 8px);--seed-enter-translate-y:0;--seed-enter-opacity:0;--seed-enter-scale:1}@media (prefers-reduced-motion:reduce){.seed-resizable-panel__panel[data-floating=true][data-side=right][data-state=open]{--seed-enter-translate-x:0}}.seed-resizable-panel__panel[data-floating=true][data-side=right][data-state=closed]{animation:seed-exit;animation-timing-function:cubic-bezier(0.4,0,1,1);animation-duration:.14s;animation-fill-mode:forwards;--seed-exit-translate-x:calc(100% + 8px);--seed-exit-translate-y:0;--seed-exit-opacity:0;--seed-exit-scale:1}@media (prefers-reduced-motion:reduce){.seed-resizable-panel__panel[data-floating=true][data-side=right][data-state=closed]{--seed-exit-translate-x:0}}.seed-resizable-panel__inner{height:100%}.seed-resizable-panel__handle{position:absolute;top:0;width:10px;height:100%;z-index:2;cursor:col-resize;background:0 0;touch-action:none}.seed-resizable-panel__handle[data-side=left]{right:0}.seed-resizable-panel__handle[data-side=right]{left:0}.seed-resizable-panel__handle::before{content:\"\";position:absolute;top:0;width:1px;height:100%;background:var(--seed-color-stroke-neutral-muted);transition:background-color .16s,height .16s}.seed-resizable-panel__handle[data-side=left]::before{right:0}.seed-resizable-panel__handle[data-side=right]::before{left:0}.seed-resizable-panel__handle:hover::before,.seed-resizable-panel__handle[data-dragging]::before{width:2px;background:#0298d9}.seed-resizable-panel__handle.crisp-seed-resizable-panel-handle--column{display:flex;align-items:center;justify-content:center;width:7px;left:-3px;right:-3px;z-index:1;cursor:col-resize}.seed-resizable-panel__handle.crisp-seed-resizable-panel-handle--column::before{content:none}.seed-resizable-panel__handleInner{width:3px;height:100%;border-radius:3px;background:#0298d9;opacity:0;transition:opacity .1s .1s}.seed-resizable-panel__handle:hover .seed-resizable-panel__handleInner,.seed-resizable-panel__handle[data-dragging] .seed-resizable-panel__handleInner{opacity:1}");
|
|
11024
11180
|
|
|
11025
11181
|
const resizablePanelSlotNames = [
|
|
11026
11182
|
[
|
|
@@ -11224,16 +11380,26 @@ Object.assign(resizablePanel, {
|
|
|
11224
11380
|
// selector), so this component and that recipe stay in lockstep without a
|
|
11225
11381
|
// second source of truth for the string.
|
|
11226
11382
|
const COLUMN_HANDLE_CLASS = "crisp-seed-resizable-panel-handle--column";
|
|
11383
|
+
// Kept in lock-step with `resizable-panel.ts`'s own `FLOATING_GUTTER` (a
|
|
11384
|
+
// literal there too, for the same reason) -- the floating/inset-card variant
|
|
11385
|
+
// needs an outward-facing gutter that DOCKED never reserves. Read on at
|
|
11386
|
+
// `spacer`'s own render below for why this can't just be one more CSS rule.
|
|
11387
|
+
const FLOATING_GUTTER_PX = 8;
|
|
11227
11388
|
/**
|
|
11228
11389
|
* An adjustable-panel primitive: a spacer that reserves layout width plus a
|
|
11229
|
-
* panel that overlays it, so
|
|
11230
|
-
*
|
|
11231
|
-
*
|
|
11232
|
-
*
|
|
11233
|
-
*
|
|
11234
|
-
*
|
|
11235
|
-
*
|
|
11236
|
-
|
|
11390
|
+
* panel that overlays it, so it can resize (or animate open/closed) without
|
|
11391
|
+
* a reflow glitch on the rest of the layout -- the spacer always reserves
|
|
11392
|
+
* the panel's real width, in BOTH the docked and floating visual variants
|
|
11393
|
+
* (floating changes only the panel's own look -- gutter, radius, shadow --
|
|
11394
|
+
* never its `position`, so a consumer laying this out as a row-flex sibling
|
|
11395
|
+
* of e.g. a table sees that table genuinely shrink to make room, matching
|
|
11396
|
+
* Attio's own measured peek). Drag the edge handle to resize; constraints
|
|
11397
|
+
* are CSS `min-width`/`max-width`, never re-clamped in JS. Ported from
|
|
11398
|
+
* your-moon/crisp's `ResizablePanel` (`components/resizablepanel/`);
|
|
11399
|
+
* state/drag behavior lives in `useResizablePanel`
|
|
11400
|
+
* (`@seed-design/react-resizable-panel`), this component owns the DOM +
|
|
11401
|
+
* recipe classes on top of it.
|
|
11402
|
+
*/ const ResizablePanel = /*#__PURE__*/ React.forwardRef(({ side = "left", width: sizeProp, defaultSize = 280, onWidthChange, min = 200, max, floating = false, handleVariant = "panel", resizable = true, storageKey, animationState, panelElementRef, className, children, ...rest }, forwardedRef)=>{
|
|
11237
11403
|
const { width, dragging, containerRef, panelRef, handleHandlers } = useResizablePanel({
|
|
11238
11404
|
side,
|
|
11239
11405
|
size: sizeProp,
|
|
@@ -11241,9 +11407,29 @@ const COLUMN_HANDLE_CLASS = "crisp-seed-resizable-panel-handle--column";
|
|
|
11241
11407
|
onSizeChange: onWidthChange,
|
|
11242
11408
|
storageKey
|
|
11243
11409
|
});
|
|
11410
|
+
const composedPanelRef = useComposedRefs(panelRef, panelElementRef);
|
|
11244
11411
|
const classNames = resizablePanel();
|
|
11412
|
+
// The floating/inset-card variant sits INSET from `root`'s own outward
|
|
11413
|
+
// edge by `FLOATING_GUTTER_PX`, flush against the inward edge (Attio's
|
|
11414
|
+
// own peek: no gap on the side facing the table it shrinks). `width`
|
|
11415
|
+
// (the prop a consumer sets, e.g. `RecordPreview`'s own 497px default)
|
|
11416
|
+
// is the RESERVED FOOTPRINT -- what the table beside it actually gives
|
|
11417
|
+
// up -- so the panel's own rendered card shrinks by the gutter INSIDE
|
|
11418
|
+
// that footprint, rather than the footprint growing past it: reserving
|
|
11419
|
+
// the panel's full width AND adding an outward gutter on top of it
|
|
11420
|
+
// over-claims space the surrounding layout was never asked for --
|
|
11421
|
+
// confirmed live, it pushed the whole page 8px into horizontal
|
|
11422
|
+
// overflow. Docked (no gutter) keeps `width` as both the reservation
|
|
11423
|
+
// and the render, as before.
|
|
11424
|
+
const panelGutter = floating ? FLOATING_GUTTER_PX : 0;
|
|
11245
11425
|
const maxStyle = max == null ? undefined : typeof max === "number" ? `${max}px` : max;
|
|
11426
|
+
const panelMaxStyle = panelGutter === 0 || maxStyle == null ? maxStyle : `calc(${maxStyle} - ${panelGutter}px)`;
|
|
11246
11427
|
const panelSizeStyle = {
|
|
11428
|
+
width: width - panelGutter,
|
|
11429
|
+
minWidth: min - panelGutter,
|
|
11430
|
+
maxWidth: panelMaxStyle
|
|
11431
|
+
};
|
|
11432
|
+
const spacerSizeStyle = {
|
|
11247
11433
|
width,
|
|
11248
11434
|
minWidth: min,
|
|
11249
11435
|
maxWidth: maxStyle
|
|
@@ -11259,19 +11445,14 @@ const COLUMN_HANDLE_CLASS = "crisp-seed-resizable-panel-handle--column";
|
|
|
11259
11445
|
children: [
|
|
11260
11446
|
/*#__PURE__*/ jsxRuntime.jsx(Primitive.div, {
|
|
11261
11447
|
className: classNames.spacer,
|
|
11262
|
-
style:
|
|
11263
|
-
width: 0
|
|
11264
|
-
} : {
|
|
11265
|
-
width,
|
|
11266
|
-
minWidth: min,
|
|
11267
|
-
maxWidth: maxStyle
|
|
11268
|
-
}
|
|
11448
|
+
style: spacerSizeStyle
|
|
11269
11449
|
}),
|
|
11270
11450
|
/*#__PURE__*/ jsxRuntime.jsxs(Primitive.div, {
|
|
11271
|
-
ref:
|
|
11451
|
+
ref: composedPanelRef,
|
|
11272
11452
|
className: classNames.panel,
|
|
11273
11453
|
"data-side": side,
|
|
11274
11454
|
"data-floating": floating ? "true" : "false",
|
|
11455
|
+
"data-state": animationState,
|
|
11275
11456
|
style: panelSizeStyle,
|
|
11276
11457
|
children: [
|
|
11277
11458
|
/*#__PURE__*/ jsxRuntime.jsx(Primitive.div, {
|
|
@@ -11296,132 +11477,276 @@ const COLUMN_HANDLE_CLASS = "crisp-seed-resizable-panel-handle--column";
|
|
|
11296
11477
|
});
|
|
11297
11478
|
ResizablePanel.displayName = "ResizablePanel";
|
|
11298
11479
|
|
|
11299
|
-
__insertCSS(".seed-record-
|
|
11480
|
+
__insertCSS(".seed-record-preview__root{display:flex;flex-direction:column;height:100%;overflow:hidden;background:var(--seed-color-bg-layer-default);font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.seed-record-preview__toolbar{display:flex;align-items:center;height:49px;flex-shrink:0;padding:8px 10px;border-bottom:1px solid var(--seed-color-stroke-neutral-muted)}.seed-record-preview__toolbarSpacer{flex:1}.seed-record-preview__pager{display:flex;align-items:center;gap:6px;margin-inline-start:12px}.seed-record-preview__counter{margin-inline-start:12px;font-size:12px;font-weight:500;color:var(--seed-color-fg-neutral-subtle);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.seed-record-preview__iconbtn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;flex-shrink:0;padding:0;border:none;border-radius:6px;background:0 0;color:var(--seed-color-fg-neutral-subtle);font-family:inherit;cursor:pointer;transition:background-color .12s}.seed-record-preview__iconbtn:hover{background:var(--seed-color-bg-layer-fill);color:var(--seed-color-fg-neutral)}.seed-record-preview__navbtn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;flex-shrink:0;padding:0;border:none;border-radius:6px;background:var(--seed-color-bg-layer-default);box-shadow:inset 0 0 0 1px rgba(0,0,0,.06),0 0 2px rgba(28,40,64,.18),0 1px 3px rgba(0,0,0,.04);color:var(--seed-color-fg-neutral);font-family:inherit;cursor:pointer}.seed-record-preview__navbtn:hover:not(:disabled){background:var(--seed-color-bg-layer-fill)}.seed-record-preview__navbtn:disabled{opacity:.4;cursor:not-allowed}.seed-record-preview__header{display:flex;align-items:center;gap:8px;height:52px;flex-shrink:0;padding:0 12px}.seed-record-preview__fav{width:22px;height:22px;border-radius:4px;flex-shrink:0;object-fit:cover;background:var(--seed-color-bg-layer-fill)}.seed-record-preview__title{flex:1;min-width:0;font-size:16px;font-weight:600;letter-spacing:-.2px;color:var(--seed-color-fg-neutral);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.seed-record-preview__body{flex:1;min-height:0;overflow-y:auto;padding:8px 12px}.seed-record-preview__section{padding:8px 4px 0}.seed-record-preview__sectionHead{display:flex;align-items:center;justify-content:space-between;gap:8px;height:20px;margin-bottom:8px}.seed-record-preview__sectionLabel{font-size:14px;font-weight:500;color:var(--seed-color-fg-neutral-subtle)}.seed-record-preview__sectionAction{border:none;background:0 0;padding:0;font-family:inherit;font-size:10px;font-weight:400;color:color-mix(in srgb,var(--seed-color-fg-neutral) 55%,transparent);cursor:pointer}.seed-record-preview__sectionAction:hover{color:var(--seed-color-fg-neutral)}.seed-record-preview__highlights{display:grid;grid-template-columns:1fr 1fr;gap:8px}.seed-record-preview__highlight{display:flex;flex-direction:column;gap:6px;min-width:0}.seed-record-preview__highlightLabel{display:flex;align-items:center;gap:4px;font-size:12px;font-weight:500;color:var(--seed-color-fg-neutral-subtle)}.seed-record-preview__highlightLabel span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.seed-record-preview__highlightLabel svg{flex-shrink:0;color:var(--seed-color-fg-neutral-subtle)}.seed-record-preview__highlightValue{font-size:14px;font-weight:500;color:var(--seed-color-fg-neutral);overflow:hidden;text-overflow:ellipsis}.seed-record-preview__activity{font-size:14px}.seed-record-preview__sections{display:flex;flex-direction:column;padding-top:8px}.seed-record-preview__sectionTrigger{display:flex;align-items:center;gap:4px;width:100%;height:20px;padding:0 8px 0 12px;border:none;background:0 0;font-family:inherit;text-align:start;cursor:pointer}.seed-record-preview__sectionTrigger[data-inert]{cursor:default}.seed-record-preview__sectionRowLabel{font-size:14px;font-weight:500;color:var(--seed-color-fg-neutral)}.seed-record-preview__sectionRowCount{font-size:14px;color:var(--seed-color-fg-neutral-subtle)}.seed-record-preview__sectionChevron{margin-inline-start:auto;flex-shrink:0;color:var(--seed-color-fg-neutral-subtle);transition:transform .15s ease}.seed-record-preview__sectionChevron[data-open]{transform:rotate(180deg)}.seed-record-preview__sectionContent{padding:8px 8px 8px 12px}.seed-record-preview__footer{flex-shrink:0;display:flex;align-items:center;gap:8px;padding:8px 12px;border-top:1px solid var(--seed-color-stroke-neutral-muted)}");
|
|
11300
11481
|
|
|
11301
|
-
const
|
|
11482
|
+
const recordPreviewSlotNames = [
|
|
11302
11483
|
[
|
|
11303
11484
|
"root",
|
|
11304
|
-
"seed-record-
|
|
11485
|
+
"seed-record-preview__root"
|
|
11305
11486
|
],
|
|
11306
11487
|
[
|
|
11307
11488
|
"toolbar",
|
|
11308
|
-
"seed-record-
|
|
11489
|
+
"seed-record-preview__toolbar"
|
|
11309
11490
|
],
|
|
11310
11491
|
[
|
|
11311
11492
|
"toolbarSpacer",
|
|
11312
|
-
"seed-record-
|
|
11493
|
+
"seed-record-preview__toolbarSpacer"
|
|
11313
11494
|
],
|
|
11314
11495
|
[
|
|
11315
11496
|
"pager",
|
|
11316
|
-
"seed-record-
|
|
11497
|
+
"seed-record-preview__pager"
|
|
11317
11498
|
],
|
|
11318
11499
|
[
|
|
11319
11500
|
"counter",
|
|
11320
|
-
"seed-record-
|
|
11501
|
+
"seed-record-preview__counter"
|
|
11321
11502
|
],
|
|
11322
11503
|
[
|
|
11323
11504
|
"iconbtn",
|
|
11324
|
-
"seed-record-
|
|
11505
|
+
"seed-record-preview__iconbtn"
|
|
11325
11506
|
],
|
|
11326
11507
|
[
|
|
11327
11508
|
"navbtn",
|
|
11328
|
-
"seed-record-
|
|
11509
|
+
"seed-record-preview__navbtn"
|
|
11329
11510
|
],
|
|
11330
11511
|
[
|
|
11331
11512
|
"header",
|
|
11332
|
-
"seed-record-
|
|
11513
|
+
"seed-record-preview__header"
|
|
11333
11514
|
],
|
|
11334
11515
|
[
|
|
11335
11516
|
"fav",
|
|
11336
|
-
"seed-record-
|
|
11517
|
+
"seed-record-preview__fav"
|
|
11337
11518
|
],
|
|
11338
11519
|
[
|
|
11339
11520
|
"title",
|
|
11340
|
-
"seed-record-
|
|
11521
|
+
"seed-record-preview__title"
|
|
11341
11522
|
],
|
|
11342
11523
|
[
|
|
11343
11524
|
"body",
|
|
11344
|
-
"seed-record-
|
|
11525
|
+
"seed-record-preview__body"
|
|
11345
11526
|
],
|
|
11346
11527
|
[
|
|
11347
11528
|
"section",
|
|
11348
|
-
"seed-record-
|
|
11529
|
+
"seed-record-preview__section"
|
|
11349
11530
|
],
|
|
11350
11531
|
[
|
|
11351
11532
|
"sectionHead",
|
|
11352
|
-
"seed-record-
|
|
11533
|
+
"seed-record-preview__sectionHead"
|
|
11353
11534
|
],
|
|
11354
11535
|
[
|
|
11355
11536
|
"sectionLabel",
|
|
11356
|
-
"seed-record-
|
|
11537
|
+
"seed-record-preview__sectionLabel"
|
|
11357
11538
|
],
|
|
11358
11539
|
[
|
|
11359
11540
|
"sectionAction",
|
|
11360
|
-
"seed-record-
|
|
11541
|
+
"seed-record-preview__sectionAction"
|
|
11361
11542
|
],
|
|
11362
11543
|
[
|
|
11363
11544
|
"highlights",
|
|
11364
|
-
"seed-record-
|
|
11545
|
+
"seed-record-preview__highlights"
|
|
11365
11546
|
],
|
|
11366
11547
|
[
|
|
11367
11548
|
"highlight",
|
|
11368
|
-
"seed-record-
|
|
11549
|
+
"seed-record-preview__highlight"
|
|
11369
11550
|
],
|
|
11370
11551
|
[
|
|
11371
11552
|
"highlightLabel",
|
|
11372
|
-
"seed-record-
|
|
11553
|
+
"seed-record-preview__highlightLabel"
|
|
11373
11554
|
],
|
|
11374
11555
|
[
|
|
11375
11556
|
"highlightValue",
|
|
11376
|
-
"seed-record-
|
|
11557
|
+
"seed-record-preview__highlightValue"
|
|
11377
11558
|
],
|
|
11378
11559
|
[
|
|
11379
11560
|
"activity",
|
|
11380
|
-
"seed-record-
|
|
11561
|
+
"seed-record-preview__activity"
|
|
11381
11562
|
],
|
|
11382
11563
|
[
|
|
11383
11564
|
"sections",
|
|
11384
|
-
"seed-record-
|
|
11565
|
+
"seed-record-preview__sections"
|
|
11385
11566
|
],
|
|
11386
11567
|
[
|
|
11387
11568
|
"sectionTrigger",
|
|
11388
|
-
"seed-record-
|
|
11569
|
+
"seed-record-preview__sectionTrigger"
|
|
11389
11570
|
],
|
|
11390
11571
|
[
|
|
11391
11572
|
"sectionRowLabel",
|
|
11392
|
-
"seed-record-
|
|
11573
|
+
"seed-record-preview__sectionRowLabel"
|
|
11393
11574
|
],
|
|
11394
11575
|
[
|
|
11395
11576
|
"sectionRowCount",
|
|
11396
|
-
"seed-record-
|
|
11577
|
+
"seed-record-preview__sectionRowCount"
|
|
11397
11578
|
],
|
|
11398
11579
|
[
|
|
11399
11580
|
"sectionChevron",
|
|
11400
|
-
"seed-record-
|
|
11581
|
+
"seed-record-preview__sectionChevron"
|
|
11401
11582
|
],
|
|
11402
11583
|
[
|
|
11403
11584
|
"sectionContent",
|
|
11404
|
-
"seed-record-
|
|
11585
|
+
"seed-record-preview__sectionContent"
|
|
11405
11586
|
],
|
|
11406
11587
|
[
|
|
11407
11588
|
"footer",
|
|
11408
|
-
"seed-record-
|
|
11589
|
+
"seed-record-preview__footer"
|
|
11409
11590
|
]
|
|
11410
11591
|
];
|
|
11411
11592
|
const defaultVariant = {};
|
|
11412
11593
|
const compoundVariants = [];
|
|
11413
|
-
const
|
|
11414
|
-
function
|
|
11415
|
-
return Object.fromEntries(
|
|
11594
|
+
const recordPreviewVariantMap = {};
|
|
11595
|
+
function recordPreview(props) {
|
|
11596
|
+
return Object.fromEntries(recordPreviewSlotNames.map(([slot, className])=>{
|
|
11416
11597
|
return [
|
|
11417
11598
|
slot,
|
|
11418
11599
|
createClassName(className, mergeVariants(defaultVariant, props), compoundVariants)
|
|
11419
11600
|
];
|
|
11420
11601
|
}));
|
|
11421
11602
|
}
|
|
11422
|
-
Object.assign(
|
|
11423
|
-
splitVariantProps: (props)=>splitVariantProps(props,
|
|
11424
|
-
}); // @recipe(seed): record-
|
|
11603
|
+
Object.assign(recordPreview, {
|
|
11604
|
+
splitVariantProps: (props)=>splitVariantProps(props, recordPreviewVariantMap)
|
|
11605
|
+
}); // @recipe(seed): record-preview
|
|
11606
|
+
|
|
11607
|
+
// This code includes portions derived from radix-ui/primitives (https://github.com/radix-ui/primitives)
|
|
11608
|
+
// Used under the MIT License: https://opensource.org/licenses/MIT
|
|
11609
|
+
function usePresence(present) {
|
|
11610
|
+
const [node, setNode] = React__namespace.useState();
|
|
11611
|
+
const stylesRef = React__namespace.useRef(null);
|
|
11612
|
+
const prevPresentRef = React__namespace.useRef(present);
|
|
11613
|
+
const prevAnimationNameRef = React__namespace.useRef("none");
|
|
11614
|
+
const initialState = present ? "mounted" : "unmounted";
|
|
11615
|
+
const [state, send] = useStateMachine(initialState, {
|
|
11616
|
+
mounted: {
|
|
11617
|
+
UNMOUNT: "unmounted",
|
|
11618
|
+
ANIMATION_OUT: "unmountSuspended"
|
|
11619
|
+
},
|
|
11620
|
+
unmountSuspended: {
|
|
11621
|
+
MOUNT: "mounted",
|
|
11622
|
+
ANIMATION_END: "unmounted"
|
|
11623
|
+
},
|
|
11624
|
+
unmounted: {
|
|
11625
|
+
MOUNT: "mounted"
|
|
11626
|
+
}
|
|
11627
|
+
});
|
|
11628
|
+
React__namespace.useEffect(()=>{
|
|
11629
|
+
const currentAnimationName = getAnimationName(stylesRef.current);
|
|
11630
|
+
prevAnimationNameRef.current = state === "mounted" ? currentAnimationName : "none";
|
|
11631
|
+
}, [
|
|
11632
|
+
state
|
|
11633
|
+
]);
|
|
11634
|
+
useLayoutEffect2(()=>{
|
|
11635
|
+
const styles = stylesRef.current;
|
|
11636
|
+
const wasPresent = prevPresentRef.current;
|
|
11637
|
+
const hasPresentChanged = wasPresent !== present;
|
|
11638
|
+
if (hasPresentChanged) {
|
|
11639
|
+
const prevAnimationName = prevAnimationNameRef.current;
|
|
11640
|
+
const currentAnimationName = getAnimationName(styles);
|
|
11641
|
+
if (present) {
|
|
11642
|
+
send("MOUNT");
|
|
11643
|
+
} else if (currentAnimationName === "none" || styles?.display === "none") {
|
|
11644
|
+
// If there is no exit animation or the element is hidden, animations won't run
|
|
11645
|
+
// so we unmount instantly
|
|
11646
|
+
send("UNMOUNT");
|
|
11647
|
+
} else {
|
|
11648
|
+
/**
|
|
11649
|
+
* When `present` changes to `false`, we check changes to animation-name to
|
|
11650
|
+
* determine whether an animation has started. We chose this approach (reading
|
|
11651
|
+
* computed styles) because there is no `animationrun` event and `animationstart`
|
|
11652
|
+
* fires after `animation-delay` has expired which would be too late.
|
|
11653
|
+
*/ const isAnimating = prevAnimationName !== currentAnimationName;
|
|
11654
|
+
if (wasPresent && isAnimating) {
|
|
11655
|
+
send("ANIMATION_OUT");
|
|
11656
|
+
} else {
|
|
11657
|
+
send("UNMOUNT");
|
|
11658
|
+
}
|
|
11659
|
+
}
|
|
11660
|
+
prevPresentRef.current = present;
|
|
11661
|
+
}
|
|
11662
|
+
}, [
|
|
11663
|
+
present,
|
|
11664
|
+
send
|
|
11665
|
+
]);
|
|
11666
|
+
useLayoutEffect2(()=>{
|
|
11667
|
+
if (node) {
|
|
11668
|
+
let timeoutId;
|
|
11669
|
+
const ownerWindow = node.ownerDocument.defaultView ?? window;
|
|
11670
|
+
/**
|
|
11671
|
+
* Triggering an ANIMATION_OUT during an ANIMATION_IN will fire an `animationcancel`
|
|
11672
|
+
* event for ANIMATION_IN after we have entered `unmountSuspended` state. So, we
|
|
11673
|
+
* make sure we only trigger ANIMATION_END for the currently active animation.
|
|
11674
|
+
*/ const handleAnimationEnd = (event)=>{
|
|
11675
|
+
const currentAnimationName = getAnimationName(stylesRef.current);
|
|
11676
|
+
const isCurrentAnimation = currentAnimationName.includes(event.animationName);
|
|
11677
|
+
if (event.target === node && isCurrentAnimation) {
|
|
11678
|
+
// With React 18 concurrency this update is applied a frame after the
|
|
11679
|
+
// animation ends, creating a flash of visible content. By setting the
|
|
11680
|
+
// animation fill mode to "forwards", we force the node to keep the
|
|
11681
|
+
// styles of the last keyframe, removing the flash.
|
|
11682
|
+
//
|
|
11683
|
+
// Previously we flushed the update via ReactDom.flushSync, but with
|
|
11684
|
+
// exit animations this resulted in the node being removed from the
|
|
11685
|
+
// DOM before the synthetic animationEnd event was dispatched, meaning
|
|
11686
|
+
// user-provided event handlers would not be called.
|
|
11687
|
+
// https://github.com/radix-ui/primitives/pull/1849
|
|
11688
|
+
send("ANIMATION_END");
|
|
11689
|
+
if (!prevPresentRef.current) {
|
|
11690
|
+
const currentFillMode = node.style.animationFillMode;
|
|
11691
|
+
node.style.animationFillMode = "forwards";
|
|
11692
|
+
// Reset the style after the node had time to unmount (for cases
|
|
11693
|
+
// where the component chooses not to unmount). Doing this any
|
|
11694
|
+
// sooner than `setTimeout` (e.g. with `requestAnimationFrame`)
|
|
11695
|
+
// still causes a flash.
|
|
11696
|
+
timeoutId = ownerWindow.setTimeout(()=>{
|
|
11697
|
+
if (node.style.animationFillMode === "forwards") {
|
|
11698
|
+
node.style.animationFillMode = currentFillMode;
|
|
11699
|
+
}
|
|
11700
|
+
});
|
|
11701
|
+
}
|
|
11702
|
+
}
|
|
11703
|
+
};
|
|
11704
|
+
const handleAnimationStart = (event)=>{
|
|
11705
|
+
if (event.target === node) {
|
|
11706
|
+
// if animation occurred, store its name as the previous animation.
|
|
11707
|
+
prevAnimationNameRef.current = getAnimationName(stylesRef.current);
|
|
11708
|
+
}
|
|
11709
|
+
};
|
|
11710
|
+
node.addEventListener("animationstart", handleAnimationStart);
|
|
11711
|
+
node.addEventListener("animationcancel", handleAnimationEnd);
|
|
11712
|
+
node.addEventListener("animationend", handleAnimationEnd);
|
|
11713
|
+
return ()=>{
|
|
11714
|
+
ownerWindow.clearTimeout(timeoutId);
|
|
11715
|
+
node.removeEventListener("animationstart", handleAnimationStart);
|
|
11716
|
+
node.removeEventListener("animationcancel", handleAnimationEnd);
|
|
11717
|
+
node.removeEventListener("animationend", handleAnimationEnd);
|
|
11718
|
+
};
|
|
11719
|
+
} else {
|
|
11720
|
+
// Transition to the unmounted state if the node is removed prematurely.
|
|
11721
|
+
// We avoid doing so during cleanup as the node may change but still exist.
|
|
11722
|
+
send("ANIMATION_END");
|
|
11723
|
+
}
|
|
11724
|
+
}, [
|
|
11725
|
+
node,
|
|
11726
|
+
send
|
|
11727
|
+
]);
|
|
11728
|
+
return {
|
|
11729
|
+
isPresent: [
|
|
11730
|
+
"mounted",
|
|
11731
|
+
"unmountSuspended"
|
|
11732
|
+
].includes(state),
|
|
11733
|
+
ref: React__namespace.useCallback((node)=>{
|
|
11734
|
+
if (node) stylesRef.current = getComputedStyle(node);
|
|
11735
|
+
setNode(node ?? undefined);
|
|
11736
|
+
}, [])
|
|
11737
|
+
};
|
|
11738
|
+
}
|
|
11739
|
+
/* -----------------------------------------------------------------------------------------------*/ function getAnimationName(styles) {
|
|
11740
|
+
return styles?.animationName || "none";
|
|
11741
|
+
}
|
|
11742
|
+
function useStateMachine(initialState, machine) {
|
|
11743
|
+
return React__namespace.useReducer((state, event)=>{
|
|
11744
|
+
const nextState = machine[state][event];
|
|
11745
|
+
return nextState ?? state;
|
|
11746
|
+
}, initialState);
|
|
11747
|
+
}
|
|
11748
|
+
const PresenceContext = /*#__PURE__*/ React.createContext(null);
|
|
11749
|
+
PresenceContext.Provider;
|
|
11425
11750
|
|
|
11426
11751
|
const RESIZE_MIN_WIDTH = 360;
|
|
11427
11752
|
const RESIZE_MAX_WIDTH = 720;
|
|
@@ -11431,12 +11756,35 @@ const RESIZE_MAX_WIDTH = 720;
|
|
|
11431
11756
|
* `RecordPanel` (`product/recordpanel/`), built ON `ResizablePanel`
|
|
11432
11757
|
* (`side="right" floating`) rather than crisp's own duplicate absolute-
|
|
11433
11758
|
* positioning/slide-in/resize-handle implementation -- the outer shell (the
|
|
11434
|
-
*
|
|
11759
|
+
* enter/exit keyframe animation, the floating elevation, the drag handle
|
|
11435
11760
|
* itself) all come from that primitive; this component only renders the
|
|
11436
11761
|
* CONTENT inside it.
|
|
11437
|
-
|
|
11762
|
+
*
|
|
11763
|
+
* Enter/exit: closing this panel is a state change (`open` -> false) that
|
|
11764
|
+
* typically clears `record` in the SAME update (see the DataTable story's
|
|
11765
|
+
* own `onClose={() => setPreviewRow(null)}`), so by the time an exit
|
|
11766
|
+
* animation should play, the values this component would render are
|
|
11767
|
+
* already gone. `usePresence` (`@seed-design/react-presence`, the same
|
|
11768
|
+
* idiom Dialog/Drawer already use for their own floating layers) keeps the
|
|
11769
|
+
* tree mounted for exactly the exit animation's duration; the snapshot
|
|
11770
|
+
* below keeps the LAST visible props around for that same window, since
|
|
11771
|
+
* without it the panel would animate out empty.
|
|
11772
|
+
*/ function RecordPreview(props) {
|
|
11773
|
+
const shouldRender = props.open && props.record != null;
|
|
11774
|
+
const { isPresent, ref: panelElementRef } = usePresence(shouldRender);
|
|
11775
|
+
const lastVisiblePropsRef = React.useRef(props);
|
|
11776
|
+
if (shouldRender) lastVisiblePropsRef.current = props;
|
|
11777
|
+
if (!isPresent) return null;
|
|
11778
|
+
return /*#__PURE__*/ jsxRuntime.jsx(RecordPreviewPanel, {
|
|
11779
|
+
...shouldRender ? props : lastVisiblePropsRef.current,
|
|
11780
|
+
animationState: shouldRender ? "open" : "closed",
|
|
11781
|
+
panelElementRef: panelElementRef
|
|
11782
|
+
});
|
|
11783
|
+
}
|
|
11784
|
+
RecordPreview.displayName = "RecordPreview";
|
|
11785
|
+
function RecordPreviewPanel({ open: _open, record, onClose, className, animationState, panelElementRef, closeLabel = "Close panel", onOpenRecord, openRecordLabel = "Open record", onPrevious, previousRecordLabel = "Previous record", onNext, nextRecordLabel = "Next record", title, avatar, highlightsLabel = "Highlights", highlights, counterLabel, resizable = false, width, defaultWidth = 497, onWidthChange, minWidth = RESIZE_MIN_WIDTH, maxWidth = RESIZE_MAX_WIDTH, activityLabel = "Activity", viewAllActivityLabel = "View all", onViewAllActivity, activity, sections, footer, ...rest }) {
|
|
11438
11786
|
const titleId = React.useId();
|
|
11439
|
-
const classNames =
|
|
11787
|
+
const classNames = recordPreview();
|
|
11440
11788
|
// Which sections (your-moon/crisp#413) are expanded -- uncontrolled and
|
|
11441
11789
|
// independent (Attio's own Emails/Notes/Tasks open without closing one
|
|
11442
11790
|
// another): a plain `Set`, reset whenever the record itself changes so a
|
|
@@ -11454,28 +11802,31 @@ const RESIZE_MAX_WIDTH = 720;
|
|
|
11454
11802
|
else next.add(key);
|
|
11455
11803
|
return next;
|
|
11456
11804
|
});
|
|
11457
|
-
// Close on Escape
|
|
11458
|
-
//
|
|
11459
|
-
//
|
|
11460
|
-
//
|
|
11461
|
-
//
|
|
11462
|
-
// propagation on its own Escape handler.
|
|
11805
|
+
// Close on Escape. Simpler than crisp's own version (crisp additionally
|
|
11806
|
+
// checks `hasOpenFloatingLayer()` so an Escape meant for a nested Menu/
|
|
11807
|
+
// Select/Popover doesn't also close this panel) -- crispy-seed has no
|
|
11808
|
+
// equivalent floating-stack registry to read, so a consumer nesting its
|
|
11809
|
+
// own floating layers inside `footer`/`sections` content should stop
|
|
11810
|
+
// propagation on its own Escape handler. This component is only ever
|
|
11811
|
+
// mounted (by `RecordPreview`, above) while genuinely open or animating
|
|
11812
|
+
// out, so there's no separate `open` flag to gate on here.
|
|
11463
11813
|
React.useEffect(()=>{
|
|
11464
|
-
if (!open) return;
|
|
11465
11814
|
const onKey = (event)=>{
|
|
11466
11815
|
if (event.key === "Escape") onClose();
|
|
11467
11816
|
};
|
|
11468
11817
|
document.addEventListener("keydown", onKey);
|
|
11469
11818
|
return ()=>document.removeEventListener("keydown", onKey);
|
|
11470
11819
|
}, [
|
|
11471
|
-
open,
|
|
11472
11820
|
onClose
|
|
11473
11821
|
]);
|
|
11474
|
-
|
|
11822
|
+
// Defensive only -- `RecordPreview` never mounts this without a record.
|
|
11823
|
+
if (!record) return null;
|
|
11475
11824
|
const heading = title ?? record.name ?? "Record";
|
|
11476
11825
|
return /*#__PURE__*/ jsxRuntime.jsx(ResizablePanel, {
|
|
11477
11826
|
side: "right",
|
|
11478
11827
|
floating: true,
|
|
11828
|
+
animationState: animationState,
|
|
11829
|
+
panelElementRef: panelElementRef,
|
|
11479
11830
|
resizable: resizable,
|
|
11480
11831
|
width: width,
|
|
11481
11832
|
defaultSize: defaultWidth,
|
|
@@ -11687,9 +12038,9 @@ const RESIZE_MAX_WIDTH = 720;
|
|
|
11687
12038
|
})
|
|
11688
12039
|
});
|
|
11689
12040
|
}
|
|
11690
|
-
|
|
12041
|
+
RecordPreviewPanel.displayName = "RecordPreviewPanel";
|
|
11691
12042
|
|
|
11692
12043
|
exports.DataTable = DataTable;
|
|
11693
12044
|
exports.DataTableViewsControl = DataTableViewsControl;
|
|
11694
|
-
exports.
|
|
12045
|
+
exports.RecordPreview = RecordPreview;
|
|
11695
12046
|
exports.ResizablePanel = ResizablePanel;
|