@crisp-ui-kit/crisp 0.54.9 → 0.54.11
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/cjs/components/datatable/datatable.js +175 -12
- package/dist/cjs/components/datatable/datatable.recipe.js +30 -0
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +25 -2
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +159 -63
- package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +41 -9
- package/dist/cjs/components/text/text.recipe.js +17 -7
- package/dist/cjs/product/recordpage/column.js +19 -0
- package/dist/cjs/product/recordpage/recordpage.js +5 -4
- package/dist/cjs/product/recordpage/test-fixtures.js +55 -0
- package/dist/cjs/product/recordstable/index.js +1 -1
- package/dist/cjs/product/recordstable/recordstable.js +5 -0
- package/dist/cjs/product/savedviews/savedviews.recipe.js +13 -11
- package/dist/cjs/product/taskstable/taskstable.js +5 -0
- package/dist/datatable.css +17 -0
- package/dist/datatable.layered.css +17 -0
- package/dist/esm/components/datatable/datatable.js +175 -12
- package/dist/esm/components/datatable/datatable.recipe.js +30 -0
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +25 -2
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +159 -63
- package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +41 -9
- package/dist/esm/components/text/text.recipe.js +17 -7
- package/dist/esm/product/recordpage/column.js +16 -0
- package/dist/esm/product/recordpage/recordpage.js +2 -1
- package/dist/esm/product/recordpage/test-fixtures.js +52 -0
- package/dist/esm/product/recordstable/index.js +1 -1
- package/dist/esm/product/recordstable/recordstable.js +5 -0
- package/dist/esm/product/savedviews/savedviews.recipe.js +13 -11
- package/dist/esm/product/taskstable/taskstable.js +5 -0
- package/dist/recordstable.css +19 -2
- package/dist/recordstable.layered.css +19 -2
- package/dist/savedviews.css +6 -18
- package/dist/savedviews.layered.css +6 -18
- package/dist/styles.css +78 -24
- package/dist/styles.layered.css +78 -24
- package/dist/tabletoolbar.css +35 -3
- package/dist/tabletoolbar.layered.css +35 -3
- package/dist/text.css +1 -1
- package/dist/text.layered.css +1 -1
- package/dist/types/components/datatable/datatable.d.ts +38 -3
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +26 -1
- package/dist/types/components/text/text.recipe.d.ts +1 -1
- package/dist/types/product/recordpage/column.d.ts +112 -0
- package/dist/types/product/recordpage/recordpage.d.ts +1 -1
- package/dist/types/product/recordpage/test-fixtures.d.ts +3 -0
- package/dist/types/product/recordpanel/recordpanel.d.ts +1 -1
- package/dist/types/product/recordstable/index.d.ts +1 -1
- package/dist/types/product/recordstable/recordstable.d.ts +6 -1
- package/dist/types/product/taskstable/taskstable.d.ts +5 -0
- package/package.json +1 -1
- /package/dist/cjs/product/{recordstable → recordpage}/editable-field.js +0 -0
- /package/dist/esm/product/{recordstable → recordpage}/editable-field.js +0 -0
- /package/dist/types/product/{recordstable → recordpage}/editable-field.d.ts +0 -0
|
@@ -40,11 +40,41 @@ exports.TableToolbar = TableToolbar;
|
|
|
40
40
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
41
41
|
const lucide_react_1 = require("lucide-react");
|
|
42
42
|
const React = __importStar(require("react"));
|
|
43
|
+
const react_dom_1 = require("react-dom");
|
|
43
44
|
const core_1 = require("../../core");
|
|
44
45
|
const datepicker_1 = require("../datepicker");
|
|
45
46
|
const menu_1 = require("../menu");
|
|
46
47
|
const select_1 = require("../select");
|
|
47
48
|
const tabletoolbar_recipe_1 = require("./tabletoolbar.recipe");
|
|
49
|
+
// crisp/#766, #772: portals View settings/Import-Export into a
|
|
50
|
+
// consumer-supplied target (e.g. a `<span>` rendered next to `SavedViews`,
|
|
51
|
+
// or another next to the primary action) instead of this toolbar's own row,
|
|
52
|
+
// when one is given -- a plain passthrough otherwise, so the default (no
|
|
53
|
+
// ref given) stays byte-identical to before this existed. Kept outside the
|
|
54
|
+
// main component (no closure over its state) so it's a stable reference
|
|
55
|
+
// across renders.
|
|
56
|
+
//
|
|
57
|
+
// #772 fix: every `.crisp-tabletoolbar-*` chrome rule is nested under this
|
|
58
|
+
// recipe's own root class (`& .crisp-tabletoolbar-tool { ... }`), which
|
|
59
|
+
// compiles to a real CSS descendant combinator -- it needs actual DOM
|
|
60
|
+
// containment under an element carrying that class. A portal target
|
|
61
|
+
// rendered by the CONSUMER, outside this component's own root `<div>`,
|
|
62
|
+
// breaks that containment even though React still considers the portaled
|
|
63
|
+
// content a child for event/context purposes: the classes were still
|
|
64
|
+
// applied, they just matched nothing, so the buttons rendered as unstyled
|
|
65
|
+
// native chrome. Same failure class `select.recipe.ts`'s own doc comment
|
|
66
|
+
// describes for its listbox portal to `document.body`. Fixed the same way
|
|
67
|
+
// `ToolbarTool` (below) already lets a bare `.crisp-tabletoolbar-tool` be
|
|
68
|
+
// used outside an actual `TableToolbar` tree: wrap the portaled content in
|
|
69
|
+
// its own `tabletoolbarRecipe()`-classed span. `display: contents` on that
|
|
70
|
+
// recipe's own base rule means the wrapper contributes no box of its own,
|
|
71
|
+
// so it changes nothing about layout -- only re-establishes the selector
|
|
72
|
+
// match.
|
|
73
|
+
function ViewBarSlot({ target, children, }) {
|
|
74
|
+
return target
|
|
75
|
+
? (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("span", { className: (0, tabletoolbar_recipe_1.tabletoolbarRecipe)(), children: children }), target)
|
|
76
|
+
: children;
|
|
77
|
+
}
|
|
48
78
|
// #592: shared by every direction-bearing ChevronRight usage below (the
|
|
49
79
|
// field picker's own disclosure chevron, a flattened search result's
|
|
50
80
|
// breadcrumb separator, and a filter chip's own breadcrumb separator) so
|
|
@@ -338,7 +368,7 @@ function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
338
368
|
* this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
|
|
339
369
|
* Created by), which one shared list can't reproduce.
|
|
340
370
|
*/
|
|
341
|
-
function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, showSort = true, showViewSettings = true, viewColumns: viewColumnsProp, shownKeys: shownKeysProp, defaultShownKeys, onShownKeysChange, viewSettingsLabel: viewSettingsLabelProp, reorderAttributeLabel: reorderAttributeLabelProp, hideAttributeLabel, addAttributeToViewLabel: addAttributeToViewLabelProp, searchAttributesPlaceholder: searchAttributesPlaceholderProp, importExportLabel: importExportLabelProp, onImportCsv, onExportCsv, onExportExcel, importExportHelpHref, importCsvLabel: importCsvLabelProp, exportCsvLabel: exportCsvLabelProp, exportExcelLabel: exportExcelLabelProp, learnImportExportLabel: learnImportExportLabelProp, backLabel: backLabelProp, nestedFieldPlaceholder, ...rest }) {
|
|
371
|
+
function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, showSort = true, showViewSettings = true, viewSettingsRef, importExportRef, viewColumns: viewColumnsProp, shownKeys: shownKeysProp, defaultShownKeys, onShownKeysChange, viewSettingsLabel: viewSettingsLabelProp, reorderAttributeLabel: reorderAttributeLabelProp, hideAttributeLabel, addAttributeToViewLabel: addAttributeToViewLabelProp, searchAttributesPlaceholder: searchAttributesPlaceholderProp, importExportLabel: importExportLabelProp, onImportCsv, onExportCsv, onExportExcel, importExportHelpHref, importCsvLabel: importCsvLabelProp, exportCsvLabel: exportCsvLabelProp, exportExcelLabel: exportExcelLabelProp, learnImportExportLabel: learnImportExportLabelProp, backLabel: backLabelProp, nestedFieldPlaceholder, ...rest }) {
|
|
342
372
|
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
343
373
|
// CrispMessagesProvider's translation of the English default below, or
|
|
344
374
|
// that default itself with no provider — unchanged from before this
|
|
@@ -408,6 +438,19 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
408
438
|
setShownKeysState(next);
|
|
409
439
|
onShownKeysChange?.(next);
|
|
410
440
|
};
|
|
441
|
+
// crisp/#766: a ref's `.current` is null during THIS render (a ref only
|
|
442
|
+
// attaches during commit, and the consumer's own target element may not
|
|
443
|
+
// even exist in the DOM yet on first paint) -- an effect after mount picks
|
|
444
|
+
// it up and re-renders once, the same one-render-late pattern the floating
|
|
445
|
+
// popovers elsewhere in this file already use for a portal target.
|
|
446
|
+
const [viewSettingsTarget, setViewSettingsTarget] = React.useState(null);
|
|
447
|
+
React.useEffect(() => {
|
|
448
|
+
setViewSettingsTarget(viewSettingsRef?.current ?? null);
|
|
449
|
+
}, [viewSettingsRef]);
|
|
450
|
+
const [importExportTarget, setImportExportTarget] = React.useState(null);
|
|
451
|
+
React.useEffect(() => {
|
|
452
|
+
setImportExportTarget(importExportRef?.current ?? null);
|
|
453
|
+
}, [importExportRef]);
|
|
411
454
|
const [openMenu, setOpenMenu] = React.useState(null);
|
|
412
455
|
const [dragSort, setDragSort] = React.useState(null);
|
|
413
456
|
const [filterQuery, setFilterQuery] = React.useState("");
|
|
@@ -416,6 +459,12 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
416
459
|
// [team, company] is Team -> Company, etc. Reset to [] wherever
|
|
417
460
|
// `filterQuery` already resets (opening the picker fresh), same lifetime.
|
|
418
461
|
const [fieldPickerPath, setFieldPickerPath] = React.useState([]);
|
|
462
|
+
// #754: which sort row's own field picker is open (its index into
|
|
463
|
+
// `sorts`), or `null` for the normal sort-row list — the same
|
|
464
|
+
// "one sub-screen behind a flag" split View settings' own
|
|
465
|
+
// `addingAttribute` already uses, parameterized by index since more than
|
|
466
|
+
// one sort row exists.
|
|
467
|
+
const [sortFieldPickerIndex, setSortFieldPickerIndex] = React.useState(null);
|
|
419
468
|
// filters the "isanyof" checkbox list, same lifetime as filterQuery above
|
|
420
469
|
const [anyOfQuery, setAnyOfQuery] = React.useState("");
|
|
421
470
|
const [dragAttr, setDragAttr] = React.useState(null);
|
|
@@ -429,7 +478,6 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
429
478
|
// built here -- hiding an attribute is a trailing control instead, see
|
|
430
479
|
// the hide button below).
|
|
431
480
|
const [addingAttribute, setAddingAttribute] = React.useState(false);
|
|
432
|
-
const [addAttrQuery, setAddAttrQuery] = React.useState("");
|
|
433
481
|
// close any open menu on outside click (anything not inside a menu or its
|
|
434
482
|
// trigger) or Escape
|
|
435
483
|
React.useEffect(() => {
|
|
@@ -512,10 +560,27 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
512
560
|
};
|
|
513
561
|
// #590: shown attributes, in `shownKeys`' order -- a key with no matching
|
|
514
562
|
// `viewColumns` entry (stale state after a column is removed upstream)
|
|
515
|
-
// drops out rather than rendering a blank row.
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
563
|
+
// drops out rather than rendering a blank row. #754: `shownKeys` can now
|
|
564
|
+
// carry a dot-joined nested path ("team.jobTitle") from the add-attribute
|
|
565
|
+
// screen's own nested picker, so each key resolves via the same
|
|
566
|
+
// `resolveColumnPath` the filter chip uses -- the LEAF column's icon
|
|
567
|
+
// drives the row's own icon, and the label is the full breadcrumb
|
|
568
|
+
// ("Team › Job title"), joined as plain text rather than the chip's own
|
|
569
|
+
// muted/chevron treatment, which was measured specifically for the
|
|
570
|
+
// filter chip and wasn't re-verified for this row shape.
|
|
571
|
+
const shownAttrs = shownKeys.flatMap((k) => {
|
|
572
|
+
const path = resolveColumnPath(viewColumns, k);
|
|
573
|
+
const leaf = path.at(-1);
|
|
574
|
+
if (!leaf)
|
|
575
|
+
return [];
|
|
576
|
+
return [
|
|
577
|
+
{
|
|
578
|
+
key: k,
|
|
579
|
+
icon: leaf.icon,
|
|
580
|
+
label: path.length > 1 ? path.map((c) => c.label).join(" › ") : leaf.label,
|
|
581
|
+
},
|
|
582
|
+
];
|
|
583
|
+
});
|
|
519
584
|
const hiddenAttrs = viewColumns.filter((c) => !shownKeys.includes(c.key));
|
|
520
585
|
const moveAttr = (from, to) => {
|
|
521
586
|
if (from === to)
|
|
@@ -529,7 +594,8 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
529
594
|
const showAttr = (key) => {
|
|
530
595
|
putShownKeys([...shownKeys, key]);
|
|
531
596
|
setAddingAttribute(false);
|
|
532
|
-
|
|
597
|
+
setFieldPickerPath([]);
|
|
598
|
+
setFilterQuery("");
|
|
533
599
|
};
|
|
534
600
|
// #594: the filter chip's kebab is a real role="menu" (crisp/#188's shared
|
|
535
601
|
// model — arrows, Home/End, typeahead, Escape), not a hand-rolled div like
|
|
@@ -562,15 +628,20 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
562
628
|
close: () => setOpenMenu(null),
|
|
563
629
|
contentRef: importExportMenuRef,
|
|
564
630
|
});
|
|
565
|
-
//
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
|
|
631
|
+
// #754: generalized from the Filter-only `filterFieldPicker` (#592) to
|
|
632
|
+
// also drive Sort's own field picker and View settings' add-attribute
|
|
633
|
+
// screen — all three offer the identical nested-drill-down construct on
|
|
634
|
+
// live Attio (confirmed: "Team" with a count badge appears, and drills
|
|
635
|
+
// into the same People-attribute screen, in all three pickers). Shared
|
|
636
|
+
// `fieldPickerPath`/`filterQuery` state across all three call sites is
|
|
637
|
+
// safe: `openMenu` already only ever names ONE open popover at a time,
|
|
638
|
+
// so only one of Filter/Sort/View-settings' own picker can be showing.
|
|
639
|
+
// `fieldPickerPath` tracks the current scope ([] is the top level),
|
|
640
|
+
// reset to [] wherever a picker opens fresh (every trigger's own
|
|
641
|
+
// onClick).
|
|
642
|
+
const nestedFieldPicker = (columns, onSelect, topLevelPlaceholder, topLevelLabel) => {
|
|
572
643
|
const scope = fieldPickerPath.at(-1);
|
|
573
|
-
const scopeColumns = scope ? (scope.nestedColumns ?? []) :
|
|
644
|
+
const scopeColumns = scope ? (scope.nestedColumns ?? []) : columns;
|
|
574
645
|
const query = filterQuery.trim().toLowerCase();
|
|
575
646
|
const direct = scopeColumns.filter((c) => c.label.toLowerCase().includes(query));
|
|
576
647
|
// #592: one level of flattening from wherever the picker is currently
|
|
@@ -598,9 +669,9 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
598
669
|
setFilterQuery("");
|
|
599
670
|
}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: nestedFieldPlaceholder
|
|
600
671
|
? nestedFieldPlaceholder(scope.nestedObjectLabel ?? scope.label.toLowerCase())
|
|
601
|
-
: `Search ${scope.nestedObjectLabel ?? scope.label.toLowerCase()} attributes…`, "aria-label":
|
|
672
|
+
: `Search ${scope.nestedObjectLabel ?? scope.label.toLowerCase()} attributes…`, "aria-label": topLevelLabel,
|
|
602
673
|
// biome-ignore lint/a11y/noAutofocus: focus the picker on open
|
|
603
|
-
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) })] })) : ((0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder:
|
|
674
|
+
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) })] })) : ((0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: topLevelPlaceholder, "aria-label": topLevelLabel,
|
|
604
675
|
// biome-ignore lint/a11y/noAutofocus: focus the picker on open
|
|
605
676
|
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) })), scope && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => selectPath(fieldPickerPath), children: [scope.icon ? ((0, jsx_runtime_1.jsx)(scope.icon, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)("span", { style: { width: 15 } })), scope.label] })), direct.map((col) => col.nestedColumns?.length ? ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item crisp-tabletoolbar-menu-item--expand", onClick: () => drillIn(col), children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)("span", { style: { width: 15 } })), col.label, (0, jsx_runtime_1.jsx)(RtlChevronRight, { size: 15, className: "crisp-tabletoolbar-menu-item-chevron" })] }, col.key)) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => selectPath([...fieldPickerPath, col]), children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)("span", { style: { width: 15 } })), col.label] }, col.key))), nested.map(({ parent, col }) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => selectPath([...fieldPickerPath, parent, col]), children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)("span", { style: { width: 15 } })), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-menu-item-path", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-menu-item-path-parent", children: parent.label }), (0, jsx_runtime_1.jsx)(RtlChevronRight, { size: 15, className: "crisp-tabletoolbar-menu-item-path-sep" }), (0, jsx_runtime_1.jsx)("span", { children: col.label })] })] }, `${parent.key}.${col.key}`)))] }));
|
|
606
677
|
};
|
|
@@ -613,23 +684,49 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
613
684
|
// worse than an imperfect word order); `crispMessages` rather than a
|
|
614
685
|
// second `useCrispMessage` call, since this only fires on the branch that
|
|
615
686
|
// needs it.
|
|
687
|
+
// crisp/#788: "Sorted by" and the field name are NOT a font-weight split
|
|
688
|
+
// (both measure 500/medium live) -- the difference is colour/opacity only:
|
|
689
|
+
// "Sorted by" at 63% black, the field name at full-strength near-black
|
|
690
|
+
// (rgb(16,17,18)). The "+N" suffix is smaller (12px vs 14px) and its own
|
|
691
|
+
// mid-grey (rgb(80,81,85)), not muted-63%-black either -- three distinct
|
|
692
|
+
// treatments, not two. `sortField` isolates the resolved label so the
|
|
693
|
+
// default (non-`sortTriggerLabel`) render can wrap each part in its own
|
|
694
|
+
// span; `sortedLabel` stays a single string for the `sortTriggerLabel`
|
|
695
|
+
// escape hatch (a caller-supplied formatter can't be split, by definition)
|
|
696
|
+
// and the empty-state `sortLabel`.
|
|
697
|
+
const sortField = resolveColumnPath(sortColumns, sorts[0]?.key ?? "").at(-1)?.label;
|
|
616
698
|
const sortedLabel = sortTriggerLabel
|
|
617
699
|
? sortTriggerLabel(sorts, sortColumns)
|
|
618
700
|
: sorts.length
|
|
619
|
-
? `${crispMessages["Sorted by"] ?? "Sorted by"} ${
|
|
701
|
+
? `${crispMessages["Sorted by"] ?? "Sorted by"} ${sortField}${sorts.length > 1 ? ` +${sorts.length - 1}` : ""}`
|
|
620
702
|
: sortLabel;
|
|
621
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: (0, tabletoolbar_recipe_1.tabletoolbarRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-toolbar", "data-inline": inline ? "" : undefined, children: [showSort && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `crisp-tabletoolbar-tool${sorts.length ? " crisp-tabletoolbar-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "sort" ? null : "sort"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "sort", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sortedLabel] }), openMenu === "sort" && ((0, jsx_runtime_1.jsxs)("div", { ref: sortListRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-empty", children: noSortsLabel })) : (sorts.map((s, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
703
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, tabletoolbar_recipe_1.tabletoolbarRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-toolbar", "data-inline": inline ? "" : undefined, children: [showSort && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `crisp-tabletoolbar-tool${sorts.length ? " crisp-tabletoolbar-tool--active" : " crisp-tabletoolbar-tool--dashed"}`, onClick: () => setOpenMenu(openMenu === "sort" ? null : "sort"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "sort", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sortTriggerLabel || !sorts.length ? (sortedLabel) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-sorted-prefix", children: crispMessages["Sorted by"] ?? "Sorted by" }), " ", (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-sorted-field", children: sortField }), sorts.length > 1 && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-sorted-count", children: [" ", "+", sorts.length - 1] }))] }))] }), openMenu === "sort" && ((0, jsx_runtime_1.jsxs)("div", { ref: sortListRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-empty", children: noSortsLabel })) : (sorts.map((s, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
622
704
|
if (dragSort !== null)
|
|
623
705
|
e.preventDefault();
|
|
624
706
|
}, onDrop: () => {
|
|
625
707
|
if (dragSort !== null)
|
|
626
708
|
moveSort(dragSort, i);
|
|
627
709
|
setDragSort(null);
|
|
628
|
-
}, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderSortLabel, draggable: true, onDragStart: () => setDragSort(i), onDragEnd: () => setDragSort(null), children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 15, strokeWidth: 1.75 }) }), (
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
710
|
+
}, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderSortLabel, draggable: true, onDragStart: () => setDragSort(i), onDragEnd: () => setDragSort(null), children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 15, strokeWidth: 1.75 }) }), (() => {
|
|
711
|
+
// #754: the sort row's own field picker -- the
|
|
712
|
+
// same nested-drill-down construct Filter's field
|
|
713
|
+
// picker already has (#592), measured live:
|
|
714
|
+
// clicking a sort row's field (or "Add sort") opens
|
|
715
|
+
// an identical picker, "Team" (a relationship
|
|
716
|
+
// field) drills into the same People-attribute
|
|
717
|
+
// screen Filter's own "Team" does.
|
|
718
|
+
const sortColPath = resolveColumnPath(sortColumns, s.key);
|
|
719
|
+
const sortCol = sortColPath.at(-1);
|
|
720
|
+
const pickerOpen = sortFieldPickerIndex === i;
|
|
721
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-sort-field-picker", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-sort-field-trigger", "aria-haspopup": "dialog", "aria-expanded": pickerOpen, "aria-label": sortFieldLabel, onClick: () => {
|
|
722
|
+
setFieldPickerPath([]);
|
|
723
|
+
setFilterQuery("");
|
|
724
|
+
setSortFieldPickerIndex(pickerOpen ? null : i);
|
|
725
|
+
}, children: [sortCol?.icon ? ((0, jsx_runtime_1.jsx)(sortCol.icon, { size: 15, strokeWidth: 1.75 })) : null, sortColPath.length > 1 ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fcond-path", children: sortColPath.map((c, pi) => ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [pi > 0 && ((0, jsx_runtime_1.jsx)(RtlChevronRight, { size: 14, className: "crisp-tabletoolbar-fcond-path-sep" })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fcond-path-seg", children: c.label })] }, c.key))) })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-sort-field-label", children: sortCol?.label }))] }), pickerOpen && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": sortFieldLabel, children: nestedFieldPicker(sortColumns, (key) => {
|
|
726
|
+
setSortField(i, key);
|
|
727
|
+
setSortFieldPickerIndex(null);
|
|
728
|
+
}, searchAttributesPlaceholder, sortFieldLabel) }))] }));
|
|
729
|
+
})(), (0, jsx_runtime_1.jsx)(select_1.Select, { className: "crisp-tabletoolbar-sort-dir", value: s.dir, onValueChange: (d) => setSortDir(i, d), options: [
|
|
633
730
|
{
|
|
634
731
|
value: "asc",
|
|
635
732
|
label: ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-sort-optlabel", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpNarrowWide, { size: 15, strokeWidth: 1.75 }), ascendingLabel] })),
|
|
@@ -640,14 +737,14 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
640
737
|
label: ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-sort-optlabel", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDownWideNarrow, { size: 15, strokeWidth: 1.75 }), descendingLabel] })),
|
|
641
738
|
keywords: descendingLabel,
|
|
642
739
|
},
|
|
643
|
-
], "aria-label": sortDirectionLabel }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), (0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { ref: addSortRef, type: "button", className: "crisp-tabletoolbar-sort-add", onClick: addSort, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), addSortLabel] }) }), helpHref && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-foot", children: (0, jsx_runtime_1.jsxs)("a", { className: "crisp-tabletoolbar-sort-learn", href: helpHref, target: "_blank", rel: "noreferrer", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CircleHelp, { size: 14, strokeWidth: 1.75 }), learnSortingLabel] }) }))] }))] })), showSort && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" })), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `crisp-tabletoolbar-tool
|
|
740
|
+
], "aria-label": sortDirectionLabel }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), (0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { ref: addSortRef, type: "button", className: "crisp-tabletoolbar-sort-add", onClick: addSort, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), addSortLabel] }) }), helpHref && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-foot", children: (0, jsx_runtime_1.jsxs)("a", { className: "crisp-tabletoolbar-sort-learn", href: helpHref, target: "_blank", rel: "noreferrer", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CircleHelp, { size: 14, strokeWidth: 1.75 }), learnSortingLabel] }) }))] }))] })), showSort && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" })), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `crisp-tabletoolbar-tool${activeFilters ? " crisp-tabletoolbar-tool--active" : " crisp-tabletoolbar-tool--dashed"}`, onClick: () => {
|
|
644
741
|
setFieldPickerPath([]);
|
|
645
742
|
setOpenMenu(openMenu === "filter" ? null : "filter");
|
|
646
743
|
}, "aria-haspopup": "dialog", "aria-expanded": openMenu === "filter", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), filterTriggerLabel
|
|
647
744
|
? filterTriggerLabel(activeFilters)
|
|
648
745
|
: activeFilters
|
|
649
746
|
? `${filterLabel} · ${activeFilters}`
|
|
650
|
-
: filterLabel] }), openMenu === "filter" && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children:
|
|
747
|
+
: filterLabel] }), openMenu === "filter" && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children: nestedFieldPicker(filterColumns, addFilter, filterFieldPlaceholder, filterFieldLabel) }))] }), filters.map((cond) => {
|
|
651
748
|
// #592: cond.key may be a dot-joined nested path
|
|
652
749
|
// ("team.twitterFollowerCount") — resolve the full path so the
|
|
653
750
|
// field segment can render its breadcrumb; `col` (the leaf) still
|
|
@@ -683,7 +780,7 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
683
780
|
? col?.type === "date"
|
|
684
781
|
? formatFilterDate(cond.value)
|
|
685
782
|
: cond.value
|
|
686
|
-
: filterValuePlaceholder })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-fcond-seg crisp-tabletoolbar-fcond-seg--kebab", "data-menu-trigger": true, "aria-haspopup": "menu", "aria-expanded": kebabOpen, "aria-label": `${col?.label} ${filterOptionsLabel}`, onClick: () => setOpenMenu(kebabOpen ? null : `fkebab:${cond.id}`), children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreVertical, { size: 13, strokeWidth: 2 }) })] }), kebabOpen && ((0, jsx_runtime_1.jsx)("div", { ref: kebabMenuRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-menu--right crisp-tabletoolbar-menu--hdr", role: "menu", "aria-label": `${col?.label} ${filterOptionsLabel}`, onKeyDown: onKebabKeyDown, children: (0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item crisp-tabletoolbar-menu-item--danger", onClick: () => removeFilter(cond.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 16, strokeWidth: 1.75 }), deleteFilterLabel] }) })), fieldOpen && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children:
|
|
783
|
+
: filterValuePlaceholder })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-fcond-seg crisp-tabletoolbar-fcond-seg--kebab", "data-menu-trigger": true, "aria-haspopup": "menu", "aria-expanded": kebabOpen, "aria-label": `${col?.label} ${filterOptionsLabel}`, onClick: () => setOpenMenu(kebabOpen ? null : `fkebab:${cond.id}`), children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreVertical, { size: 13, strokeWidth: 2 }) })] }), kebabOpen && ((0, jsx_runtime_1.jsx)("div", { ref: kebabMenuRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-menu--right crisp-tabletoolbar-menu--hdr", role: "menu", "aria-label": `${col?.label} ${filterOptionsLabel}`, onKeyDown: onKebabKeyDown, children: (0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item crisp-tabletoolbar-menu-item--danger", onClick: () => removeFilter(cond.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 16, strokeWidth: 1.75 }), deleteFilterLabel] }) })), fieldOpen && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children: nestedFieldPicker(filterColumns, (key) => changeFilterField(cond.id, key), filterFieldPlaceholder, filterFieldLabel) })), opOpen && ((0, jsx_runtime_1.jsx)("div", { ref: opMenuRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-menu--op", role: "menu", "aria-label": `${col?.label} ${crispMessages.condition ?? "condition"}`, onKeyDown: onOpKeyDown, children: opsFor(col).map((op) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", "data-on": cond.op === op ? "" : undefined, onClick: () => pickOp(cond.id, op), children: [crispMessages[OP_LABELS[op]] ?? OP_LABELS[op], cond.op === op ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "crisp-tabletoolbar-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, op))) })), valOpen && showValueSeg && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-menu crisp-tabletoolbar-menu--val", role: "dialog", "aria-label": `${col?.label} filter value`, children: cond.op === "isanyof" && col?.values ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter values`,
|
|
687
784
|
// biome-ignore lint/a11y/noAutofocus: focus the search field
|
|
688
785
|
autoFocus: true, value: anyOfQuery, onChange: (e) => setAnyOfQuery(e.target.value) }), col.values
|
|
689
786
|
.filter((v) => v
|
|
@@ -739,44 +836,43 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
739
836
|
}, children: label }, label))) })] })) : ((0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", type: col?.type === "number" ? "number" : "text", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter value`,
|
|
740
837
|
// biome-ignore lint/a11y/noAutofocus: focus the value field
|
|
741
838
|
autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) })) }))] }, cond.id));
|
|
742
|
-
}), showViewSettings && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" })), showViewSettings && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-tool crisp-tabletoolbar-tool--
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
`${crispMessages.Hide ?? "Hide"} ${col.label} ${crispMessages["from view"] ?? "from view"}`, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, col.key))), (0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-sort-add", onClick: () => setAddingAttribute(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), addAttributeToViewLabel] }) })] })))] })), (onImportCsv ||
|
|
839
|
+
}), showViewSettings && !viewSettingsTarget && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" })), showViewSettings && ((0, jsx_runtime_1.jsx)(ViewBarSlot, { target: viewSettingsTarget, children: (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-tool crisp-tabletoolbar-tool--raised", onClick: () => {
|
|
840
|
+
if (openMenu === "viewsettings") {
|
|
841
|
+
setOpenMenu(null);
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
setAddingAttribute(false);
|
|
845
|
+
setFieldPickerPath([]);
|
|
846
|
+
setFilterQuery("");
|
|
847
|
+
setOpenMenu("viewsettings");
|
|
848
|
+
}
|
|
849
|
+
}, "aria-haspopup": "dialog", "aria-expanded": openMenu === "viewsettings", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), viewSettingsLabel, (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" })] }), openMenu === "viewsettings" &&
|
|
850
|
+
(addingAttribute ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": addAttributeToViewLabel, children: nestedFieldPicker(hiddenAttrs, showAttr, searchAttributesPlaceholder, searchAttributesPlaceholder) })) : ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": viewSettingsLabel, children: [shownAttrs.map((col, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
851
|
+
if (dragAttr !== null)
|
|
852
|
+
e.preventDefault();
|
|
853
|
+
}, onDrop: () => {
|
|
854
|
+
if (dragAttr !== null)
|
|
855
|
+
moveAttr(dragAttr, i);
|
|
856
|
+
setDragAttr(null);
|
|
857
|
+
}, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderAttributeLabel, draggable: true, onDragStart: () => setDragAttr(i), onDragEnd: () => setDragAttr(null), children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-sort-optlabel crisp-tabletoolbar-sort-field", children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : null, col.label] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => hideAttr(col.key), "aria-label": hideAttributeLabel?.(col.label) ??
|
|
858
|
+
`${crispMessages.Hide ?? "Hide"} ${col.label} ${crispMessages["from view"] ?? "from view"}`, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, col.key))), (0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-sort-add", onClick: () => {
|
|
859
|
+
setAddingAttribute(true);
|
|
860
|
+
setFieldPickerPath([]);
|
|
861
|
+
setFilterQuery("");
|
|
862
|
+
}, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), addAttributeToViewLabel] }) })] })))] }) })), (onImportCsv ||
|
|
767
863
|
onExportCsv ||
|
|
768
864
|
onExportExcel ||
|
|
769
|
-
importExportHelpHref) && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-tool crisp-tabletoolbar-tool--raised", onClick: () => setOpenMenu(openMenu === "importexport" ? null : "importexport"), "aria-haspopup": "menu", "aria-expanded": openMenu === "importexport", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Upload, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), importExportLabel, (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" })] }), openMenu === "importexport" && ((0, jsx_runtime_1.jsxs)("div", { ref: importExportMenuRef, className: "crisp-tabletoolbar-menu", role: "menu", "aria-label": importExportLabel, onKeyDown: onImportExportKeyDown, children: [onImportCsv && ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", onClick: () => {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
865
|
+
importExportHelpHref) && ((0, jsx_runtime_1.jsx)(ViewBarSlot, { target: importExportTarget, children: (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-tool crisp-tabletoolbar-tool--raised", onClick: () => setOpenMenu(openMenu === "importexport" ? null : "importexport"), "aria-haspopup": "menu", "aria-expanded": openMenu === "importexport", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Upload, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), importExportLabel, (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" })] }), openMenu === "importexport" && ((0, jsx_runtime_1.jsxs)("div", { ref: importExportMenuRef, className: "crisp-tabletoolbar-menu", role: "menu", "aria-label": importExportLabel, onKeyDown: onImportExportKeyDown, children: [onImportCsv && ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", onClick: () => {
|
|
866
|
+
onImportCsv();
|
|
867
|
+
setOpenMenu(null);
|
|
868
|
+
}, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Upload, { size: 15, strokeWidth: 1.75 }), importCsvLabel] })), onImportCsv && (onExportCsv || onExportExcel) && ((0, jsx_runtime_1.jsx)("hr", { className: "crisp-tabletoolbar-menu-sep" })), onExportCsv && ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", onClick: () => {
|
|
869
|
+
onExportCsv();
|
|
870
|
+
setOpenMenu(null);
|
|
871
|
+
}, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Download, { size: 15, strokeWidth: 1.75 }), exportCsvLabel] })), onExportExcel && ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", onClick: () => {
|
|
872
|
+
onExportExcel();
|
|
873
|
+
setOpenMenu(null);
|
|
874
|
+
}, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Download, { size: 15, strokeWidth: 1.75 }), exportExcelLabel] })), importExportHelpHref &&
|
|
875
|
+
(onImportCsv || onExportCsv || onExportExcel) && ((0, jsx_runtime_1.jsx)("hr", { className: "crisp-tabletoolbar-menu-sep" })), importExportHelpHref && ((0, jsx_runtime_1.jsxs)("a", { className: "crisp-tabletoolbar-menu-item", role: "menuitem", href: importExportHelpHref, target: "_blank", rel: "noreferrer", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CircleHelp, { size: 15, strokeWidth: 1.75 }), learnImportExportLabel] }))] }))] }) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-spacer" }), count != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-count", children: count })), children] }) }));
|
|
780
876
|
}
|
|
781
877
|
/**
|
|
782
878
|
* The Sort/Filter chip's own chrome (`.crisp-tabletoolbar-tool`), standalone
|
|
@@ -56,19 +56,39 @@ const filterChipChrome = `
|
|
|
56
56
|
& .crisp-tabletoolbar-fcond-seg--val { font-weight: ${tokens_1.contract.weight.medium}; max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
|
|
57
57
|
& .crisp-tabletoolbar-fcond-seg--kebab { background: ${tokens_1.contract.bgSurface}; justify-content: center; width: 28px; padding: 0; border-radius: 0 ${tokens_1.contract.radius.md} ${tokens_1.contract.radius.md} 0; color: ${tokens_1.contract.fgMuted}; }
|
|
58
58
|
& .crisp-tabletoolbar-fcond-seg--kebab:hover { color: ${tokens_1.contract.fgPrimary}; }
|
|
59
|
+
/* #754: the sort row's own field trigger, replacing a plain Select
|
|
60
|
+
with the same nested-drill-down popover Filter's field picker
|
|
61
|
+
already has — reuses Select's own already-measured trigger chrome
|
|
62
|
+
verbatim (its class is scoped under Select's own root class, so it
|
|
63
|
+
can't be reused as a bare class name outside an actual Select tree;
|
|
64
|
+
these values are copied, not re-measured). */
|
|
65
|
+
& .crisp-tabletoolbar-sort-field-picker { position: relative; display: inline-flex; flex: 1; min-width: 0; }
|
|
66
|
+
& .crisp-tabletoolbar-sort-field-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; height: ${tokens_1.contract.size.menuRow}; width: 100%; min-width: 0; margin: 0; padding: 0 ${tokens_1.contract.space["1.5"]} 0 ${tokens_1.contract.space["2"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 7%, transparent); font-family: inherit; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgPrimary}; cursor: pointer; transition: background-color .12s ease, box-shadow .12s ease; }
|
|
67
|
+
& .crisp-tabletoolbar-sort-field-trigger:hover { background: ${tokens_1.contract.bgRaised}; }
|
|
68
|
+
& .crisp-tabletoolbar-sort-field-trigger[aria-expanded="true"],
|
|
69
|
+
& .crisp-tabletoolbar-sort-field-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; background: ${tokens_1.contract.bgSurface}; }
|
|
70
|
+
& .crisp-tabletoolbar-sort-field-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
|
|
71
|
+
& .crisp-tabletoolbar-sort-field-picker .crisp-tabletoolbar-menu { min-width: 200px; }
|
|
59
72
|
& .crisp-tabletoolbar-menu--val { min-width: 134px; }
|
|
60
73
|
& .crisp-tabletoolbar-datepop { width: 300px; }
|
|
61
74
|
& .crisp-tabletoolbar-datepop-presets { display: flex; flex-wrap: wrap; gap: ${tokens_1.contract.space["1"]}; margin-top: ${tokens_1.contract.space["2"]}; padding: 0 ${tokens_1.contract.space["1"]}; }
|
|
62
75
|
& .crisp-tabletoolbar-datepop-preset { height: ${tokens_1.contract.size.chip}; padding: 0 ${tokens_1.contract.space["2"]}; border: none; border-radius: ${tokens_1.contract.radius.chip}; background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; font-family: inherit; font-size: ${tokens_1.contract.text.sm}; cursor: pointer; }
|
|
63
76
|
& .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 8%, transparent); }`;
|
|
64
|
-
// #595
|
|
65
|
-
// view bar: `28px` tall, `radius.md`
|
|
66
|
-
// (`rgb(
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
77
|
+
// #595, corrected #772 (re-measured live 2026-09-25): the view bar's raised
|
|
78
|
+
// trigger chrome — `app.attio.com`'s own view bar: `28px` tall, `radius.md`
|
|
79
|
+
// (8px), background `bgSurface` (white, `rgb(255,255,255)` exact match — the
|
|
80
|
+
// #595 pass originally measured `bgSurfaceMuted`/`rgb(246,247,247)`, which
|
|
81
|
+
// re-measuring live this pass shows was wrong, not a later Attio change: ALL
|
|
82
|
+
// THREE of the switcher, View settings and Import/Export read pure white)
|
|
83
|
+
// and `elevation.field`'s exact 3-layer shadow (`inset 0 0 0 1px transparent,
|
|
84
|
+
// 0 0 2px rgba(28,40,64,.18), 0 1px 3px rgba(0,0,0,.04)`) — distinct from
|
|
85
|
+
// Sort/Filter's plain ringed `.tool` (no elevation shadow; see `--dashed` in
|
|
86
|
+
// `tabletoolbar.chrome.ts` for their own OWN empty-state chrome). Originally
|
|
87
|
+
// built for Import/Export alone ("the only trigger... with a trailing
|
|
88
|
+
// chevron") — that was also wrong: View settings and the view switcher both
|
|
89
|
+
// carry one too, confirmed live (3 children each: icon, label, chevron).
|
|
90
|
+
// Now shared by View settings' own trigger (`tabletoolbar.tsx`) as well as
|
|
91
|
+
// Import/Export's. Overrides `.tool`'s own base background/shadow by cascade
|
|
72
92
|
// order (this block is appended after `toolbarChromeBody`, which defines
|
|
73
93
|
// them), the same way `.tool--ghost` already does. `border`/`font-family`/
|
|
74
94
|
// `cursor` are repeated here too, even though `.tool`'s own base rule (in
|
|
@@ -78,7 +98,18 @@ const filterChipChrome = `
|
|
|
78
98
|
// behind `chrome.ts`'s function call is invisible to it; harmless repeats,
|
|
79
99
|
// not a real gap.
|
|
80
100
|
const importExportChrome = `
|
|
81
|
-
& .crisp-tabletoolbar-tool--raised { border: none; background: ${tokens_1.contract.
|
|
101
|
+
& .crisp-tabletoolbar-tool--raised { border: none; background: ${tokens_1.contract.bgSurface}; box-shadow: ${tokens_1.elevation.field}; font-family: inherit; cursor: pointer; }`;
|
|
102
|
+
// crisp/#788: the Sort trigger's own "Sorted by X (+N)" label, measured live
|
|
103
|
+
// against Attio's own chip -- three parts, not one flat string, and NOT a
|
|
104
|
+
// font-weight split (all three read 500/medium): "Sorted by" is muted (63%
|
|
105
|
+
// black), the resolved field name is full-strength near-black, and a
|
|
106
|
+
// trailing "+N" (when more than one sort is active) is both smaller (12px
|
|
107
|
+
// vs 14px) and its own mid-grey, distinct from the "Sorted by" muted tone.
|
|
108
|
+
// TableToolbar-only, same reasoning as `filterChipChrome` above (RecordsTable
|
|
109
|
+
// keeps its own older Sort label markup, not touched here).
|
|
110
|
+
const sortedLabelChrome = `
|
|
111
|
+
& .crisp-tabletoolbar-sorted-prefix { color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); }
|
|
112
|
+
& .crisp-tabletoolbar-sorted-count { font-size: ${tokens_1.contract.text.sm}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 73%, transparent); }`;
|
|
82
113
|
// The table toolbar, measured: 28px controls, the Sort/Filter buttons and
|
|
83
114
|
// the menus they open, sort-condition rows, and the filter chips. The chrome
|
|
84
115
|
// itself lives in ./tabletoolbar.chrome.ts because RecordsTable renders the
|
|
@@ -97,6 +128,7 @@ ${(0, tabletoolbar_chrome_1.toolbarChromeHead)("tabletoolbar")}
|
|
|
97
128
|
${(0, tabletoolbar_chrome_1.toolbarChromeBody)("tabletoolbar")}
|
|
98
129
|
${filterChipChrome}
|
|
99
130
|
${importExportChrome}
|
|
131
|
+
${sortedLabelChrome}
|
|
100
132
|
${core_1.rtlFlipCss}
|
|
101
133
|
`,
|
|
102
134
|
});
|
|
@@ -13,13 +13,23 @@ const tokens_1 = require("../../tokens");
|
|
|
13
13
|
// the same reason (see its own doc).
|
|
14
14
|
const spec = (0, core_1.defineSpec)({
|
|
15
15
|
name: "text",
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// `core/resolve.ts`'s `if (base) classes.push(prefixedName)` treats
|
|
19
|
-
// falsy string as "no base rule",
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
16
|
+
// Needs a real, always-present declaration so `resolveRecipe` emits the
|
|
17
|
+
// `crisp-text` class at all -- an EMPTY base compiles to an empty string,
|
|
18
|
+
// and `core/resolve.ts`'s `if (base) classes.push(prefixedName)` treats
|
|
19
|
+
// that falsy string as "no base rule", dropping the class itself,
|
|
20
|
+
// silently, from every instance (this file's own tests catch that).
|
|
21
|
+
// `display: "inline"` filled that role until a real bug: `asChild`
|
|
22
|
+
// (Primitive.span -> Radix Slot) merges this class onto WHATEVER element
|
|
23
|
+
// the caller passes, not a `<span>` -- a `<div>`/`<p>`/`<li>` child's own
|
|
24
|
+
// block default lost to `.crisp-text`'s class-specificity `display:
|
|
25
|
+
// inline`, silently collapsing its layout (internal/grc's own Settings.tsx
|
|
26
|
+
// ran two rows onto one line this way). `vertical-align: baseline` fills
|
|
27
|
+
// the same "always-present, never empty" role without that risk: it's a
|
|
28
|
+
// span's own already-inherited default (so nothing changes for the plain,
|
|
29
|
+
// non-asChild case `Primitive.span` renders), and it has NO effect at all
|
|
30
|
+
// on a block-level box, so an `asChild` div/p/li keeps its own display
|
|
31
|
+
// exactly as its own tag already gives it.
|
|
32
|
+
base: { verticalAlign: "baseline" },
|
|
23
33
|
variants: {
|
|
24
34
|
tone: {
|
|
25
35
|
default: { color: tokens_1.contract.fgPrimary },
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tagTone = tagTone;
|
|
4
|
+
// deterministic pastel colour per tag value (a select option keeps its colour)
|
|
5
|
+
const TAG_TONES = [
|
|
6
|
+
"brand",
|
|
7
|
+
"success",
|
|
8
|
+
"warning",
|
|
9
|
+
"danger",
|
|
10
|
+
"purple",
|
|
11
|
+
"cyan",
|
|
12
|
+
"orange",
|
|
13
|
+
];
|
|
14
|
+
function tagTone(t) {
|
|
15
|
+
let h = 0;
|
|
16
|
+
for (let i = 0; i < t.length; i++)
|
|
17
|
+
h = (h * 31 + t.charCodeAt(i)) >>> 0;
|
|
18
|
+
return TAG_TONES[h % TAG_TONES.length];
|
|
19
|
+
}
|
|
@@ -47,7 +47,8 @@ const spinner_1 = require("../../components/spinner");
|
|
|
47
47
|
const switch_1 = require("../../components/switch");
|
|
48
48
|
const tabs_1 = require("../../components/tabs");
|
|
49
49
|
const core_1 = require("../../core");
|
|
50
|
-
const
|
|
50
|
+
const column_1 = require("./column");
|
|
51
|
+
const editable_field_1 = require("./editable-field");
|
|
51
52
|
const recordpage_recipe_1 = require("./recordpage.recipe");
|
|
52
53
|
function valueEmpty(v) {
|
|
53
54
|
return v == null || v === "" || (Array.isArray(v) && v.length === 0);
|
|
@@ -212,7 +213,7 @@ function TagsValue({ values }) {
|
|
|
212
213
|
const [expanded, setExpanded] = React.useState(false);
|
|
213
214
|
const overflow = values.length - 1;
|
|
214
215
|
const shown = expanded ? values : values.slice(0, 1);
|
|
215
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [shown.map((t) => ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordstable-tag", "data-tone": (0,
|
|
216
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [shown.map((t) => ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordstable-tag", "data-tone": (0, column_1.tagTone)(t), children: t }, t))), !expanded && overflow > 0 && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-morevalues", onClick: () => setExpanded(true), children: ["+", overflow] }))] }));
|
|
216
217
|
}
|
|
217
218
|
function FieldValue({ col, row, labels, }) {
|
|
218
219
|
const v = row[col.key];
|
|
@@ -316,7 +317,7 @@ function EmptyCard({ icon, children, }) {
|
|
|
316
317
|
*/
|
|
317
318
|
function RecordDetails({ record, columns, sections, onEdit, labels, className, ...rest }) {
|
|
318
319
|
const t = useRecordPageLabels(labels);
|
|
319
|
-
const { editingKey, pendingKey, error, openEditing, closeEditing, commit } = (0,
|
|
320
|
+
const { editingKey, pendingKey, error, openEditing, closeEditing, commit } = (0, editable_field_1.useEditableField)(onEdit);
|
|
320
321
|
const [detailsOpen, setDetailsOpen] = React.useState(true);
|
|
321
322
|
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
322
323
|
const [fieldQuery, setFieldQuery] = React.useState("");
|
|
@@ -406,7 +407,7 @@ function RecordDetails({ record, columns, sections, onEdit, labels, className, .
|
|
|
406
407
|
}
|
|
407
408
|
if (canEdit && editingKey === col.key) {
|
|
408
409
|
const value = record[col.key];
|
|
409
|
-
const editor = (0,
|
|
410
|
+
const editor = (0, editable_field_1.editableInputType)(col.type);
|
|
410
411
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-recordpage-edit",
|
|
411
412
|
// biome-ignore lint/a11y/noAutofocus: focus the field the moment it opens
|
|
412
413
|
autoFocus: true, ...editor, disabled: isPending, defaultValue: value == null ? "" : String(value), "aria-label": `Edit ${col.label}`, onKeyDown: (e) => {
|