@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
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowDownWideNarrow, ArrowUpDown, ArrowUpNarrowWide, Check, ChevronDown, ChevronLeft, ChevronRight, CircleHelp, Download, GripVertical, ListFilter, MoreVertical, Plus, SlidersHorizontal, Trash2, Upload, X, } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
4
5
|
import { isInsideLaterFloatingLayer, useCrispMessage, useCrispMessages, useFocusAfterRemoval, } from "../../core/index.js";
|
|
5
6
|
import { DatePicker, } from "../datepicker/index.js";
|
|
6
7
|
import { MenuCheckboxItem, useMenuKeyboard } from "../menu/index.js";
|
|
7
8
|
import { Select } from "../select/index.js";
|
|
8
9
|
import { tabletoolbarRecipe } from "./tabletoolbar.recipe.js";
|
|
10
|
+
// crisp/#766, #772: portals View settings/Import-Export into a
|
|
11
|
+
// consumer-supplied target (e.g. a `<span>` rendered next to `SavedViews`,
|
|
12
|
+
// or another next to the primary action) instead of this toolbar's own row,
|
|
13
|
+
// when one is given -- a plain passthrough otherwise, so the default (no
|
|
14
|
+
// ref given) stays byte-identical to before this existed. Kept outside the
|
|
15
|
+
// main component (no closure over its state) so it's a stable reference
|
|
16
|
+
// across renders.
|
|
17
|
+
//
|
|
18
|
+
// #772 fix: every `.crisp-tabletoolbar-*` chrome rule is nested under this
|
|
19
|
+
// recipe's own root class (`& .crisp-tabletoolbar-tool { ... }`), which
|
|
20
|
+
// compiles to a real CSS descendant combinator -- it needs actual DOM
|
|
21
|
+
// containment under an element carrying that class. A portal target
|
|
22
|
+
// rendered by the CONSUMER, outside this component's own root `<div>`,
|
|
23
|
+
// breaks that containment even though React still considers the portaled
|
|
24
|
+
// content a child for event/context purposes: the classes were still
|
|
25
|
+
// applied, they just matched nothing, so the buttons rendered as unstyled
|
|
26
|
+
// native chrome. Same failure class `select.recipe.ts`'s own doc comment
|
|
27
|
+
// describes for its listbox portal to `document.body`. Fixed the same way
|
|
28
|
+
// `ToolbarTool` (below) already lets a bare `.crisp-tabletoolbar-tool` be
|
|
29
|
+
// used outside an actual `TableToolbar` tree: wrap the portaled content in
|
|
30
|
+
// its own `tabletoolbarRecipe()`-classed span. `display: contents` on that
|
|
31
|
+
// recipe's own base rule means the wrapper contributes no box of its own,
|
|
32
|
+
// so it changes nothing about layout -- only re-establishes the selector
|
|
33
|
+
// match.
|
|
34
|
+
function ViewBarSlot({ target, children, }) {
|
|
35
|
+
return target
|
|
36
|
+
? createPortal(_jsx("span", { className: tabletoolbarRecipe(), children: children }), target)
|
|
37
|
+
: children;
|
|
38
|
+
}
|
|
9
39
|
// #592: shared by every direction-bearing ChevronRight usage below (the
|
|
10
40
|
// field picker's own disclosure chevron, a flattened search result's
|
|
11
41
|
// breadcrumb separator, and a filter chip's own breadcrumb separator) so
|
|
@@ -299,7 +329,7 @@ export function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
299
329
|
* this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
|
|
300
330
|
* Created by), which one shared list can't reproduce.
|
|
301
331
|
*/
|
|
302
|
-
export 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 }) {
|
|
332
|
+
export 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 }) {
|
|
303
333
|
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
304
334
|
// CrispMessagesProvider's translation of the English default below, or
|
|
305
335
|
// that default itself with no provider — unchanged from before this
|
|
@@ -369,6 +399,19 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
369
399
|
setShownKeysState(next);
|
|
370
400
|
onShownKeysChange?.(next);
|
|
371
401
|
};
|
|
402
|
+
// crisp/#766: a ref's `.current` is null during THIS render (a ref only
|
|
403
|
+
// attaches during commit, and the consumer's own target element may not
|
|
404
|
+
// even exist in the DOM yet on first paint) -- an effect after mount picks
|
|
405
|
+
// it up and re-renders once, the same one-render-late pattern the floating
|
|
406
|
+
// popovers elsewhere in this file already use for a portal target.
|
|
407
|
+
const [viewSettingsTarget, setViewSettingsTarget] = React.useState(null);
|
|
408
|
+
React.useEffect(() => {
|
|
409
|
+
setViewSettingsTarget(viewSettingsRef?.current ?? null);
|
|
410
|
+
}, [viewSettingsRef]);
|
|
411
|
+
const [importExportTarget, setImportExportTarget] = React.useState(null);
|
|
412
|
+
React.useEffect(() => {
|
|
413
|
+
setImportExportTarget(importExportRef?.current ?? null);
|
|
414
|
+
}, [importExportRef]);
|
|
372
415
|
const [openMenu, setOpenMenu] = React.useState(null);
|
|
373
416
|
const [dragSort, setDragSort] = React.useState(null);
|
|
374
417
|
const [filterQuery, setFilterQuery] = React.useState("");
|
|
@@ -377,6 +420,12 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
377
420
|
// [team, company] is Team -> Company, etc. Reset to [] wherever
|
|
378
421
|
// `filterQuery` already resets (opening the picker fresh), same lifetime.
|
|
379
422
|
const [fieldPickerPath, setFieldPickerPath] = React.useState([]);
|
|
423
|
+
// #754: which sort row's own field picker is open (its index into
|
|
424
|
+
// `sorts`), or `null` for the normal sort-row list — the same
|
|
425
|
+
// "one sub-screen behind a flag" split View settings' own
|
|
426
|
+
// `addingAttribute` already uses, parameterized by index since more than
|
|
427
|
+
// one sort row exists.
|
|
428
|
+
const [sortFieldPickerIndex, setSortFieldPickerIndex] = React.useState(null);
|
|
380
429
|
// filters the "isanyof" checkbox list, same lifetime as filterQuery above
|
|
381
430
|
const [anyOfQuery, setAnyOfQuery] = React.useState("");
|
|
382
431
|
const [dragAttr, setDragAttr] = React.useState(null);
|
|
@@ -390,7 +439,6 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
390
439
|
// built here -- hiding an attribute is a trailing control instead, see
|
|
391
440
|
// the hide button below).
|
|
392
441
|
const [addingAttribute, setAddingAttribute] = React.useState(false);
|
|
393
|
-
const [addAttrQuery, setAddAttrQuery] = React.useState("");
|
|
394
442
|
// close any open menu on outside click (anything not inside a menu or its
|
|
395
443
|
// trigger) or Escape
|
|
396
444
|
React.useEffect(() => {
|
|
@@ -473,10 +521,27 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
473
521
|
};
|
|
474
522
|
// #590: shown attributes, in `shownKeys`' order -- a key with no matching
|
|
475
523
|
// `viewColumns` entry (stale state after a column is removed upstream)
|
|
476
|
-
// drops out rather than rendering a blank row.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
524
|
+
// drops out rather than rendering a blank row. #754: `shownKeys` can now
|
|
525
|
+
// carry a dot-joined nested path ("team.jobTitle") from the add-attribute
|
|
526
|
+
// screen's own nested picker, so each key resolves via the same
|
|
527
|
+
// `resolveColumnPath` the filter chip uses -- the LEAF column's icon
|
|
528
|
+
// drives the row's own icon, and the label is the full breadcrumb
|
|
529
|
+
// ("Team › Job title"), joined as plain text rather than the chip's own
|
|
530
|
+
// muted/chevron treatment, which was measured specifically for the
|
|
531
|
+
// filter chip and wasn't re-verified for this row shape.
|
|
532
|
+
const shownAttrs = shownKeys.flatMap((k) => {
|
|
533
|
+
const path = resolveColumnPath(viewColumns, k);
|
|
534
|
+
const leaf = path.at(-1);
|
|
535
|
+
if (!leaf)
|
|
536
|
+
return [];
|
|
537
|
+
return [
|
|
538
|
+
{
|
|
539
|
+
key: k,
|
|
540
|
+
icon: leaf.icon,
|
|
541
|
+
label: path.length > 1 ? path.map((c) => c.label).join(" › ") : leaf.label,
|
|
542
|
+
},
|
|
543
|
+
];
|
|
544
|
+
});
|
|
480
545
|
const hiddenAttrs = viewColumns.filter((c) => !shownKeys.includes(c.key));
|
|
481
546
|
const moveAttr = (from, to) => {
|
|
482
547
|
if (from === to)
|
|
@@ -490,7 +555,8 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
490
555
|
const showAttr = (key) => {
|
|
491
556
|
putShownKeys([...shownKeys, key]);
|
|
492
557
|
setAddingAttribute(false);
|
|
493
|
-
|
|
558
|
+
setFieldPickerPath([]);
|
|
559
|
+
setFilterQuery("");
|
|
494
560
|
};
|
|
495
561
|
// #594: the filter chip's kebab is a real role="menu" (crisp/#188's shared
|
|
496
562
|
// model — arrows, Home/End, typeahead, Escape), not a hand-rolled div like
|
|
@@ -523,15 +589,20 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
523
589
|
close: () => setOpenMenu(null),
|
|
524
590
|
contentRef: importExportMenuRef,
|
|
525
591
|
});
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
|
|
592
|
+
// #754: generalized from the Filter-only `filterFieldPicker` (#592) to
|
|
593
|
+
// also drive Sort's own field picker and View settings' add-attribute
|
|
594
|
+
// screen — all three offer the identical nested-drill-down construct on
|
|
595
|
+
// live Attio (confirmed: "Team" with a count badge appears, and drills
|
|
596
|
+
// into the same People-attribute screen, in all three pickers). Shared
|
|
597
|
+
// `fieldPickerPath`/`filterQuery` state across all three call sites is
|
|
598
|
+
// safe: `openMenu` already only ever names ONE open popover at a time,
|
|
599
|
+
// so only one of Filter/Sort/View-settings' own picker can be showing.
|
|
600
|
+
// `fieldPickerPath` tracks the current scope ([] is the top level),
|
|
601
|
+
// reset to [] wherever a picker opens fresh (every trigger's own
|
|
602
|
+
// onClick).
|
|
603
|
+
const nestedFieldPicker = (columns, onSelect, topLevelPlaceholder, topLevelLabel) => {
|
|
533
604
|
const scope = fieldPickerPath.at(-1);
|
|
534
|
-
const scopeColumns = scope ? (scope.nestedColumns ?? []) :
|
|
605
|
+
const scopeColumns = scope ? (scope.nestedColumns ?? []) : columns;
|
|
535
606
|
const query = filterQuery.trim().toLowerCase();
|
|
536
607
|
const direct = scopeColumns.filter((c) => c.label.toLowerCase().includes(query));
|
|
537
608
|
// #592: one level of flattening from wherever the picker is currently
|
|
@@ -559,9 +630,9 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
559
630
|
setFilterQuery("");
|
|
560
631
|
}, children: _jsx(ChevronLeft, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }) }), _jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder: nestedFieldPlaceholder
|
|
561
632
|
? nestedFieldPlaceholder(scope.nestedObjectLabel ?? scope.label.toLowerCase())
|
|
562
|
-
: `Search ${scope.nestedObjectLabel ?? scope.label.toLowerCase()} attributes…`, "aria-label":
|
|
633
|
+
: `Search ${scope.nestedObjectLabel ?? scope.label.toLowerCase()} attributes…`, "aria-label": topLevelLabel,
|
|
563
634
|
// biome-ignore lint/a11y/noAutofocus: focus the picker on open
|
|
564
|
-
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) })] })) : (_jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder:
|
|
635
|
+
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) })] })) : (_jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder: topLevelPlaceholder, "aria-label": topLevelLabel,
|
|
565
636
|
// biome-ignore lint/a11y/noAutofocus: focus the picker on open
|
|
566
637
|
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) })), scope && (_jsxs("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => selectPath(fieldPickerPath), children: [scope.icon ? (_jsx(scope.icon, { size: 15, strokeWidth: 1.75 })) : (_jsx("span", { style: { width: 15 } })), scope.label] })), direct.map((col) => col.nestedColumns?.length ? (_jsxs("button", { type: "button", className: "crisp-tabletoolbar-menu-item crisp-tabletoolbar-menu-item--expand", onClick: () => drillIn(col), children: [col.icon ? (_jsx(col.icon, { size: 15, strokeWidth: 1.75 })) : (_jsx("span", { style: { width: 15 } })), col.label, _jsx(RtlChevronRight, { size: 15, className: "crisp-tabletoolbar-menu-item-chevron" })] }, col.key)) : (_jsxs("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => selectPath([...fieldPickerPath, col]), children: [col.icon ? (_jsx(col.icon, { size: 15, strokeWidth: 1.75 })) : (_jsx("span", { style: { width: 15 } })), col.label] }, col.key))), nested.map(({ parent, col }) => (_jsxs("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => selectPath([...fieldPickerPath, parent, col]), children: [col.icon ? (_jsx(col.icon, { size: 15, strokeWidth: 1.75 })) : (_jsx("span", { style: { width: 15 } })), _jsxs("span", { className: "crisp-tabletoolbar-menu-item-path", children: [_jsx("span", { className: "crisp-tabletoolbar-menu-item-path-parent", children: parent.label }), _jsx(RtlChevronRight, { size: 15, className: "crisp-tabletoolbar-menu-item-path-sep" }), _jsx("span", { children: col.label })] })] }, `${parent.key}.${col.key}`)))] }));
|
|
567
638
|
};
|
|
@@ -574,23 +645,49 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
574
645
|
// worse than an imperfect word order); `crispMessages` rather than a
|
|
575
646
|
// second `useCrispMessage` call, since this only fires on the branch that
|
|
576
647
|
// needs it.
|
|
648
|
+
// crisp/#788: "Sorted by" and the field name are NOT a font-weight split
|
|
649
|
+
// (both measure 500/medium live) -- the difference is colour/opacity only:
|
|
650
|
+
// "Sorted by" at 63% black, the field name at full-strength near-black
|
|
651
|
+
// (rgb(16,17,18)). The "+N" suffix is smaller (12px vs 14px) and its own
|
|
652
|
+
// mid-grey (rgb(80,81,85)), not muted-63%-black either -- three distinct
|
|
653
|
+
// treatments, not two. `sortField` isolates the resolved label so the
|
|
654
|
+
// default (non-`sortTriggerLabel`) render can wrap each part in its own
|
|
655
|
+
// span; `sortedLabel` stays a single string for the `sortTriggerLabel`
|
|
656
|
+
// escape hatch (a caller-supplied formatter can't be split, by definition)
|
|
657
|
+
// and the empty-state `sortLabel`.
|
|
658
|
+
const sortField = resolveColumnPath(sortColumns, sorts[0]?.key ?? "").at(-1)?.label;
|
|
577
659
|
const sortedLabel = sortTriggerLabel
|
|
578
660
|
? sortTriggerLabel(sorts, sortColumns)
|
|
579
661
|
: sorts.length
|
|
580
|
-
? `${crispMessages["Sorted by"] ?? "Sorted by"} ${
|
|
662
|
+
? `${crispMessages["Sorted by"] ?? "Sorted by"} ${sortField}${sorts.length > 1 ? ` +${sorts.length - 1}` : ""}`
|
|
581
663
|
: sortLabel;
|
|
582
|
-
return (_jsx("div", { className: tabletoolbarRecipe({ className }), ...rest, children: _jsxs("div", { className: "crisp-tabletoolbar-toolbar", "data-inline": inline ? "" : undefined, children: [showSort && (_jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_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: [_jsx(ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sortedLabel] }), openMenu === "sort" && (_jsxs("div", { ref: sortListRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? (_jsx("div", { className: "crisp-tabletoolbar-sort-empty", children: noSortsLabel })) : (sorts.map((s, i) => (_jsxs("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
664
|
+
return (_jsx("div", { className: tabletoolbarRecipe({ className }), ...rest, children: _jsxs("div", { className: "crisp-tabletoolbar-toolbar", "data-inline": inline ? "" : undefined, children: [showSort && (_jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_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: [_jsx(ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sortTriggerLabel || !sorts.length ? (sortedLabel) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "crisp-tabletoolbar-sorted-prefix", children: crispMessages["Sorted by"] ?? "Sorted by" }), " ", _jsx("span", { className: "crisp-tabletoolbar-sorted-field", children: sortField }), sorts.length > 1 && (_jsxs("span", { className: "crisp-tabletoolbar-sorted-count", children: [" ", "+", sorts.length - 1] }))] }))] }), openMenu === "sort" && (_jsxs("div", { ref: sortListRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? (_jsx("div", { className: "crisp-tabletoolbar-sort-empty", children: noSortsLabel })) : (sorts.map((s, i) => (_jsxs("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
583
665
|
if (dragSort !== null)
|
|
584
666
|
e.preventDefault();
|
|
585
667
|
}, onDrop: () => {
|
|
586
668
|
if (dragSort !== null)
|
|
587
669
|
moveSort(dragSort, i);
|
|
588
670
|
setDragSort(null);
|
|
589
|
-
}, children: [_jsx("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderSortLabel, draggable: true, onDragStart: () => setDragSort(i), onDragEnd: () => setDragSort(null), children: _jsx(GripVertical, { size: 15, strokeWidth: 1.75 }) }),
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
671
|
+
}, children: [_jsx("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderSortLabel, draggable: true, onDragStart: () => setDragSort(i), onDragEnd: () => setDragSort(null), children: _jsx(GripVertical, { size: 15, strokeWidth: 1.75 }) }), (() => {
|
|
672
|
+
// #754: the sort row's own field picker -- the
|
|
673
|
+
// same nested-drill-down construct Filter's field
|
|
674
|
+
// picker already has (#592), measured live:
|
|
675
|
+
// clicking a sort row's field (or "Add sort") opens
|
|
676
|
+
// an identical picker, "Team" (a relationship
|
|
677
|
+
// field) drills into the same People-attribute
|
|
678
|
+
// screen Filter's own "Team" does.
|
|
679
|
+
const sortColPath = resolveColumnPath(sortColumns, s.key);
|
|
680
|
+
const sortCol = sortColPath.at(-1);
|
|
681
|
+
const pickerOpen = sortFieldPickerIndex === i;
|
|
682
|
+
return (_jsxs("span", { className: "crisp-tabletoolbar-sort-field-picker", children: [_jsxs("button", { type: "button", className: "crisp-tabletoolbar-sort-field-trigger", "aria-haspopup": "dialog", "aria-expanded": pickerOpen, "aria-label": sortFieldLabel, onClick: () => {
|
|
683
|
+
setFieldPickerPath([]);
|
|
684
|
+
setFilterQuery("");
|
|
685
|
+
setSortFieldPickerIndex(pickerOpen ? null : i);
|
|
686
|
+
}, children: [sortCol?.icon ? (_jsx(sortCol.icon, { size: 15, strokeWidth: 1.75 })) : null, sortColPath.length > 1 ? (_jsx("span", { className: "crisp-tabletoolbar-fcond-path", children: sortColPath.map((c, pi) => (_jsxs(React.Fragment, { children: [pi > 0 && (_jsx(RtlChevronRight, { size: 14, className: "crisp-tabletoolbar-fcond-path-sep" })), _jsx("span", { className: "crisp-tabletoolbar-fcond-path-seg", children: c.label })] }, c.key))) })) : (_jsx("span", { className: "crisp-tabletoolbar-sort-field-label", children: sortCol?.label }))] }), pickerOpen && (_jsx("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": sortFieldLabel, children: nestedFieldPicker(sortColumns, (key) => {
|
|
687
|
+
setSortField(i, key);
|
|
688
|
+
setSortFieldPickerIndex(null);
|
|
689
|
+
}, searchAttributesPlaceholder, sortFieldLabel) }))] }));
|
|
690
|
+
})(), _jsx(Select, { className: "crisp-tabletoolbar-sort-dir", value: s.dir, onValueChange: (d) => setSortDir(i, d), options: [
|
|
594
691
|
{
|
|
595
692
|
value: "asc",
|
|
596
693
|
label: (_jsxs("span", { className: "crisp-tabletoolbar-sort-optlabel", children: [_jsx(ArrowUpNarrowWide, { size: 15, strokeWidth: 1.75 }), ascendingLabel] })),
|
|
@@ -601,14 +698,14 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
601
698
|
label: (_jsxs("span", { className: "crisp-tabletoolbar-sort-optlabel", children: [_jsx(ArrowDownWideNarrow, { size: 15, strokeWidth: 1.75 }), descendingLabel] })),
|
|
602
699
|
keywords: descendingLabel,
|
|
603
700
|
},
|
|
604
|
-
], "aria-label": sortDirectionLabel }), _jsx("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: _jsx(X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), _jsx("div", { className: "crisp-tabletoolbar-sort-addrow", children: _jsxs("button", { ref: addSortRef, type: "button", className: "crisp-tabletoolbar-sort-add", onClick: addSort, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), addSortLabel] }) }), helpHref && (_jsx("div", { className: "crisp-tabletoolbar-sort-foot", children: _jsxs("a", { className: "crisp-tabletoolbar-sort-learn", href: helpHref, target: "_blank", rel: "noreferrer", children: [_jsx(CircleHelp, { size: 14, strokeWidth: 1.75 }), learnSortingLabel] }) }))] }))] })), showSort && (_jsx("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" })), _jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: `crisp-tabletoolbar-tool
|
|
701
|
+
], "aria-label": sortDirectionLabel }), _jsx("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: _jsx(X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), _jsx("div", { className: "crisp-tabletoolbar-sort-addrow", children: _jsxs("button", { ref: addSortRef, type: "button", className: "crisp-tabletoolbar-sort-add", onClick: addSort, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), addSortLabel] }) }), helpHref && (_jsx("div", { className: "crisp-tabletoolbar-sort-foot", children: _jsxs("a", { className: "crisp-tabletoolbar-sort-learn", href: helpHref, target: "_blank", rel: "noreferrer", children: [_jsx(CircleHelp, { size: 14, strokeWidth: 1.75 }), learnSortingLabel] }) }))] }))] })), showSort && (_jsx("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" })), _jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: `crisp-tabletoolbar-tool${activeFilters ? " crisp-tabletoolbar-tool--active" : " crisp-tabletoolbar-tool--dashed"}`, onClick: () => {
|
|
605
702
|
setFieldPickerPath([]);
|
|
606
703
|
setOpenMenu(openMenu === "filter" ? null : "filter");
|
|
607
704
|
}, "aria-haspopup": "dialog", "aria-expanded": openMenu === "filter", "data-menu-trigger": true, children: [_jsx(ListFilter, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), filterTriggerLabel
|
|
608
705
|
? filterTriggerLabel(activeFilters)
|
|
609
706
|
: activeFilters
|
|
610
707
|
? `${filterLabel} · ${activeFilters}`
|
|
611
|
-
: filterLabel] }), openMenu === "filter" && (_jsx("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children:
|
|
708
|
+
: filterLabel] }), openMenu === "filter" && (_jsx("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children: nestedFieldPicker(filterColumns, addFilter, filterFieldPlaceholder, filterFieldLabel) }))] }), filters.map((cond) => {
|
|
612
709
|
// #592: cond.key may be a dot-joined nested path
|
|
613
710
|
// ("team.twitterFollowerCount") — resolve the full path so the
|
|
614
711
|
// field segment can render its breadcrumb; `col` (the leaf) still
|
|
@@ -644,7 +741,7 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
644
741
|
? col?.type === "date"
|
|
645
742
|
? formatFilterDate(cond.value)
|
|
646
743
|
: cond.value
|
|
647
|
-
: filterValuePlaceholder })), _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: _jsx(MoreVertical, { size: 13, strokeWidth: 2 }) })] }), kebabOpen && (_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: _jsxs("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item crisp-tabletoolbar-menu-item--danger", onClick: () => removeFilter(cond.id), children: [_jsx(Trash2, { size: 16, strokeWidth: 1.75 }), deleteFilterLabel] }) })), fieldOpen && (_jsx("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children:
|
|
744
|
+
: filterValuePlaceholder })), _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: _jsx(MoreVertical, { size: 13, strokeWidth: 2 }) })] }), kebabOpen && (_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: _jsxs("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item crisp-tabletoolbar-menu-item--danger", onClick: () => removeFilter(cond.id), children: [_jsx(Trash2, { size: 16, strokeWidth: 1.75 }), deleteFilterLabel] }) })), fieldOpen && (_jsx("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children: nestedFieldPicker(filterColumns, (key) => changeFilterField(cond.id, key), filterFieldPlaceholder, filterFieldLabel) })), opOpen && (_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) => (_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 ? (_jsx(Check, { className: "crisp-tabletoolbar-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, op))) })), valOpen && showValueSeg && (_jsx("div", { className: "crisp-tabletoolbar-menu crisp-tabletoolbar-menu--val", role: "dialog", "aria-label": `${col?.label} filter value`, children: cond.op === "isanyof" && col?.values ? (_jsxs(_Fragment, { children: [_jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter values`,
|
|
648
745
|
// biome-ignore lint/a11y/noAutofocus: focus the search field
|
|
649
746
|
autoFocus: true, value: anyOfQuery, onChange: (e) => setAnyOfQuery(e.target.value) }), col.values
|
|
650
747
|
.filter((v) => v
|
|
@@ -700,44 +797,43 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
700
797
|
}, children: label }, label))) })] })) : (_jsx("input", { className: "crisp-tabletoolbar-menu-input", type: col?.type === "number" ? "number" : "text", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter value`,
|
|
701
798
|
// biome-ignore lint/a11y/noAutofocus: focus the value field
|
|
702
799
|
autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) })) }))] }, cond.id));
|
|
703
|
-
}), showViewSettings && (_jsx("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" })), showViewSettings && (_jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: "crisp-tabletoolbar-tool crisp-tabletoolbar-tool--
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
`${crispMessages.Hide ?? "Hide"} ${col.label} ${crispMessages["from view"] ?? "from view"}`, children: _jsx(X, { size: 15, strokeWidth: 1.75 }) })] }, col.key))), _jsx("div", { className: "crisp-tabletoolbar-sort-addrow", children: _jsxs("button", { type: "button", className: "crisp-tabletoolbar-sort-add", onClick: () => setAddingAttribute(true), children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), addAttributeToViewLabel] }) })] })))] })), (onImportCsv ||
|
|
800
|
+
}), showViewSettings && !viewSettingsTarget && (_jsx("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" })), showViewSettings && (_jsx(ViewBarSlot, { target: viewSettingsTarget, children: _jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: "crisp-tabletoolbar-tool crisp-tabletoolbar-tool--raised", onClick: () => {
|
|
801
|
+
if (openMenu === "viewsettings") {
|
|
802
|
+
setOpenMenu(null);
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
setAddingAttribute(false);
|
|
806
|
+
setFieldPickerPath([]);
|
|
807
|
+
setFilterQuery("");
|
|
808
|
+
setOpenMenu("viewsettings");
|
|
809
|
+
}
|
|
810
|
+
}, "aria-haspopup": "dialog", "aria-expanded": openMenu === "viewsettings", "data-menu-trigger": true, children: [_jsx(SlidersHorizontal, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), viewSettingsLabel, _jsx(ChevronDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" })] }), openMenu === "viewsettings" &&
|
|
811
|
+
(addingAttribute ? (_jsx("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": addAttributeToViewLabel, children: nestedFieldPicker(hiddenAttrs, showAttr, searchAttributesPlaceholder, searchAttributesPlaceholder) })) : (_jsxs("div", { className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": viewSettingsLabel, children: [shownAttrs.map((col, i) => (_jsxs("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
812
|
+
if (dragAttr !== null)
|
|
813
|
+
e.preventDefault();
|
|
814
|
+
}, onDrop: () => {
|
|
815
|
+
if (dragAttr !== null)
|
|
816
|
+
moveAttr(dragAttr, i);
|
|
817
|
+
setDragAttr(null);
|
|
818
|
+
}, children: [_jsx("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderAttributeLabel, draggable: true, onDragStart: () => setDragAttr(i), onDragEnd: () => setDragAttr(null), children: _jsx(GripVertical, { size: 15, strokeWidth: 1.75 }) }), _jsxs("span", { className: "crisp-tabletoolbar-sort-optlabel crisp-tabletoolbar-sort-field", children: [col.icon ? (_jsx(col.icon, { size: 15, strokeWidth: 1.75 })) : null, col.label] }), _jsx("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => hideAttr(col.key), "aria-label": hideAttributeLabel?.(col.label) ??
|
|
819
|
+
`${crispMessages.Hide ?? "Hide"} ${col.label} ${crispMessages["from view"] ?? "from view"}`, children: _jsx(X, { size: 15, strokeWidth: 1.75 }) })] }, col.key))), _jsx("div", { className: "crisp-tabletoolbar-sort-addrow", children: _jsxs("button", { type: "button", className: "crisp-tabletoolbar-sort-add", onClick: () => {
|
|
820
|
+
setAddingAttribute(true);
|
|
821
|
+
setFieldPickerPath([]);
|
|
822
|
+
setFilterQuery("");
|
|
823
|
+
}, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), addAttributeToViewLabel] }) })] })))] }) })), (onImportCsv ||
|
|
728
824
|
onExportCsv ||
|
|
729
825
|
onExportExcel ||
|
|
730
|
-
importExportHelpHref) && (_jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_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: [_jsx(Upload, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), importExportLabel, _jsx(ChevronDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" })] }), openMenu === "importexport" && (_jsxs("div", { ref: importExportMenuRef, className: "crisp-tabletoolbar-menu", role: "menu", "aria-label": importExportLabel, onKeyDown: onImportExportKeyDown, children: [onImportCsv && (_jsxs("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", onClick: () => {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
826
|
+
importExportHelpHref) && (_jsx(ViewBarSlot, { target: importExportTarget, children: _jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_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: [_jsx(Upload, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), importExportLabel, _jsx(ChevronDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" })] }), openMenu === "importexport" && (_jsxs("div", { ref: importExportMenuRef, className: "crisp-tabletoolbar-menu", role: "menu", "aria-label": importExportLabel, onKeyDown: onImportExportKeyDown, children: [onImportCsv && (_jsxs("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", onClick: () => {
|
|
827
|
+
onImportCsv();
|
|
828
|
+
setOpenMenu(null);
|
|
829
|
+
}, children: [_jsx(Upload, { size: 15, strokeWidth: 1.75 }), importCsvLabel] })), onImportCsv && (onExportCsv || onExportExcel) && (_jsx("hr", { className: "crisp-tabletoolbar-menu-sep" })), onExportCsv && (_jsxs("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", onClick: () => {
|
|
830
|
+
onExportCsv();
|
|
831
|
+
setOpenMenu(null);
|
|
832
|
+
}, children: [_jsx(Download, { size: 15, strokeWidth: 1.75 }), exportCsvLabel] })), onExportExcel && (_jsxs("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", onClick: () => {
|
|
833
|
+
onExportExcel();
|
|
834
|
+
setOpenMenu(null);
|
|
835
|
+
}, children: [_jsx(Download, { size: 15, strokeWidth: 1.75 }), exportExcelLabel] })), importExportHelpHref &&
|
|
836
|
+
(onImportCsv || onExportCsv || onExportExcel) && (_jsx("hr", { className: "crisp-tabletoolbar-menu-sep" })), importExportHelpHref && (_jsxs("a", { className: "crisp-tabletoolbar-menu-item", role: "menuitem", href: importExportHelpHref, target: "_blank", rel: "noreferrer", children: [_jsx(CircleHelp, { size: 15, strokeWidth: 1.75 }), learnImportExportLabel] }))] }))] }) })), _jsx("span", { className: "crisp-tabletoolbar-spacer" }), count != null && (_jsx("span", { className: "crisp-tabletoolbar-count", children: count })), children] }) }));
|
|
741
837
|
}
|
|
742
838
|
/**
|
|
743
839
|
* The Sort/Filter chip's own chrome (`.crisp-tabletoolbar-tool`), standalone
|
|
@@ -53,19 +53,39 @@ const filterChipChrome = `
|
|
|
53
53
|
& .crisp-tabletoolbar-fcond-seg--val { font-weight: ${c.weight.medium}; max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
|
|
54
54
|
& .crisp-tabletoolbar-fcond-seg--kebab { background: ${c.bgSurface}; justify-content: center; width: 28px; padding: 0; border-radius: 0 ${c.radius.md} ${c.radius.md} 0; color: ${c.fgMuted}; }
|
|
55
55
|
& .crisp-tabletoolbar-fcond-seg--kebab:hover { color: ${c.fgPrimary}; }
|
|
56
|
+
/* #754: the sort row's own field trigger, replacing a plain Select
|
|
57
|
+
with the same nested-drill-down popover Filter's field picker
|
|
58
|
+
already has — reuses Select's own already-measured trigger chrome
|
|
59
|
+
verbatim (its class is scoped under Select's own root class, so it
|
|
60
|
+
can't be reused as a bare class name outside an actual Select tree;
|
|
61
|
+
these values are copied, not re-measured). */
|
|
62
|
+
& .crisp-tabletoolbar-sort-field-picker { position: relative; display: inline-flex; flex: 1; min-width: 0; }
|
|
63
|
+
& .crisp-tabletoolbar-sort-field-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; height: ${c.size.menuRow}; width: 100%; min-width: 0; margin: 0; padding: 0 ${c.space["1.5"]} 0 ${c.space["2"]}; border: none; border-radius: ${c.radius.md}; background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${c.fgPrimary} 7%, transparent); font-family: inherit; font-size: ${c.text.lg}; color: ${c.fgPrimary}; cursor: pointer; transition: background-color .12s ease, box-shadow .12s ease; }
|
|
64
|
+
& .crisp-tabletoolbar-sort-field-trigger:hover { background: ${c.bgRaised}; }
|
|
65
|
+
& .crisp-tabletoolbar-sort-field-trigger[aria-expanded="true"],
|
|
66
|
+
& .crisp-tabletoolbar-sort-field-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px ${c.bgBrandSolid}; background: ${c.bgSurface}; }
|
|
67
|
+
& .crisp-tabletoolbar-sort-field-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
|
|
68
|
+
& .crisp-tabletoolbar-sort-field-picker .crisp-tabletoolbar-menu { min-width: 200px; }
|
|
56
69
|
& .crisp-tabletoolbar-menu--val { min-width: 134px; }
|
|
57
70
|
& .crisp-tabletoolbar-datepop { width: 300px; }
|
|
58
71
|
& .crisp-tabletoolbar-datepop-presets { display: flex; flex-wrap: wrap; gap: ${c.space["1"]}; margin-top: ${c.space["2"]}; padding: 0 ${c.space["1"]}; }
|
|
59
72
|
& .crisp-tabletoolbar-datepop-preset { height: ${c.size.chip}; padding: 0 ${c.space["2"]}; border: none; border-radius: ${c.radius.chip}; background: ${c.bgRaised}; color: ${c.fgPrimary}; font-family: inherit; font-size: ${c.text.sm}; cursor: pointer; }
|
|
60
73
|
& .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, ${c.fgPrimary} 8%, transparent); }`;
|
|
61
|
-
// #595
|
|
62
|
-
// view bar: `28px` tall, `radius.md`
|
|
63
|
-
// (`rgb(
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
74
|
+
// #595, corrected #772 (re-measured live 2026-09-25): the view bar's raised
|
|
75
|
+
// trigger chrome — `app.attio.com`'s own view bar: `28px` tall, `radius.md`
|
|
76
|
+
// (8px), background `bgSurface` (white, `rgb(255,255,255)` exact match — the
|
|
77
|
+
// #595 pass originally measured `bgSurfaceMuted`/`rgb(246,247,247)`, which
|
|
78
|
+
// re-measuring live this pass shows was wrong, not a later Attio change: ALL
|
|
79
|
+
// THREE of the switcher, View settings and Import/Export read pure white)
|
|
80
|
+
// and `elevation.field`'s exact 3-layer shadow (`inset 0 0 0 1px transparent,
|
|
81
|
+
// 0 0 2px rgba(28,40,64,.18), 0 1px 3px rgba(0,0,0,.04)`) — distinct from
|
|
82
|
+
// Sort/Filter's plain ringed `.tool` (no elevation shadow; see `--dashed` in
|
|
83
|
+
// `tabletoolbar.chrome.ts` for their own OWN empty-state chrome). Originally
|
|
84
|
+
// built for Import/Export alone ("the only trigger... with a trailing
|
|
85
|
+
// chevron") — that was also wrong: View settings and the view switcher both
|
|
86
|
+
// carry one too, confirmed live (3 children each: icon, label, chevron).
|
|
87
|
+
// Now shared by View settings' own trigger (`tabletoolbar.tsx`) as well as
|
|
88
|
+
// Import/Export's. Overrides `.tool`'s own base background/shadow by cascade
|
|
69
89
|
// order (this block is appended after `toolbarChromeBody`, which defines
|
|
70
90
|
// them), the same way `.tool--ghost` already does. `border`/`font-family`/
|
|
71
91
|
// `cursor` are repeated here too, even though `.tool`'s own base rule (in
|
|
@@ -75,7 +95,18 @@ const filterChipChrome = `
|
|
|
75
95
|
// behind `chrome.ts`'s function call is invisible to it; harmless repeats,
|
|
76
96
|
// not a real gap.
|
|
77
97
|
const importExportChrome = `
|
|
78
|
-
& .crisp-tabletoolbar-tool--raised { border: none; background: ${c.
|
|
98
|
+
& .crisp-tabletoolbar-tool--raised { border: none; background: ${c.bgSurface}; box-shadow: ${e.field}; font-family: inherit; cursor: pointer; }`;
|
|
99
|
+
// crisp/#788: the Sort trigger's own "Sorted by X (+N)" label, measured live
|
|
100
|
+
// against Attio's own chip -- three parts, not one flat string, and NOT a
|
|
101
|
+
// font-weight split (all three read 500/medium): "Sorted by" is muted (63%
|
|
102
|
+
// black), the resolved field name is full-strength near-black, and a
|
|
103
|
+
// trailing "+N" (when more than one sort is active) is both smaller (12px
|
|
104
|
+
// vs 14px) and its own mid-grey, distinct from the "Sorted by" muted tone.
|
|
105
|
+
// TableToolbar-only, same reasoning as `filterChipChrome` above (RecordsTable
|
|
106
|
+
// keeps its own older Sort label markup, not touched here).
|
|
107
|
+
const sortedLabelChrome = `
|
|
108
|
+
& .crisp-tabletoolbar-sorted-prefix { color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
|
|
109
|
+
& .crisp-tabletoolbar-sorted-count { font-size: ${c.text.sm}; color: color-mix(in srgb, ${c.fgPrimary} 73%, transparent); }`;
|
|
79
110
|
// The table toolbar, measured: 28px controls, the Sort/Filter buttons and
|
|
80
111
|
// the menus they open, sort-condition rows, and the filter chips. The chrome
|
|
81
112
|
// itself lives in ./tabletoolbar.chrome.ts because RecordsTable renders the
|
|
@@ -94,6 +125,7 @@ ${toolbarChromeHead("tabletoolbar")}
|
|
|
94
125
|
${toolbarChromeBody("tabletoolbar")}
|
|
95
126
|
${filterChipChrome}
|
|
96
127
|
${importExportChrome}
|
|
128
|
+
${sortedLabelChrome}
|
|
97
129
|
${rtlFlipCss}
|
|
98
130
|
`,
|
|
99
131
|
});
|
|
@@ -10,13 +10,23 @@ import { contract as c } from "../../tokens/index.js";
|
|
|
10
10
|
// the same reason (see its own doc).
|
|
11
11
|
const spec = defineSpec({
|
|
12
12
|
name: "text",
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// `core/resolve.ts`'s `if (base) classes.push(prefixedName)` treats
|
|
16
|
-
// falsy string as "no base rule",
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
13
|
+
// Needs a real, always-present declaration so `resolveRecipe` emits the
|
|
14
|
+
// `crisp-text` class at all -- an EMPTY base compiles to an empty string,
|
|
15
|
+
// and `core/resolve.ts`'s `if (base) classes.push(prefixedName)` treats
|
|
16
|
+
// that falsy string as "no base rule", dropping the class itself,
|
|
17
|
+
// silently, from every instance (this file's own tests catch that).
|
|
18
|
+
// `display: "inline"` filled that role until a real bug: `asChild`
|
|
19
|
+
// (Primitive.span -> Radix Slot) merges this class onto WHATEVER element
|
|
20
|
+
// the caller passes, not a `<span>` -- a `<div>`/`<p>`/`<li>` child's own
|
|
21
|
+
// block default lost to `.crisp-text`'s class-specificity `display:
|
|
22
|
+
// inline`, silently collapsing its layout (internal/grc's own Settings.tsx
|
|
23
|
+
// ran two rows onto one line this way). `vertical-align: baseline` fills
|
|
24
|
+
// the same "always-present, never empty" role without that risk: it's a
|
|
25
|
+
// span's own already-inherited default (so nothing changes for the plain,
|
|
26
|
+
// non-asChild case `Primitive.span` renders), and it has NO effect at all
|
|
27
|
+
// on a block-level box, so an `asChild` div/p/li keeps its own display
|
|
28
|
+
// exactly as its own tag already gives it.
|
|
29
|
+
base: { verticalAlign: "baseline" },
|
|
20
30
|
variants: {
|
|
21
31
|
tone: {
|
|
22
32
|
default: { color: c.fgPrimary },
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// deterministic pastel colour per tag value (a select option keeps its colour)
|
|
2
|
+
const TAG_TONES = [
|
|
3
|
+
"brand",
|
|
4
|
+
"success",
|
|
5
|
+
"warning",
|
|
6
|
+
"danger",
|
|
7
|
+
"purple",
|
|
8
|
+
"cyan",
|
|
9
|
+
"orange",
|
|
10
|
+
];
|
|
11
|
+
export function tagTone(t) {
|
|
12
|
+
let h = 0;
|
|
13
|
+
for (let i = 0; i < t.length; i++)
|
|
14
|
+
h = (h * 31 + t.charCodeAt(i)) >>> 0;
|
|
15
|
+
return TAG_TONES[h % TAG_TONES.length];
|
|
16
|
+
}
|
|
@@ -9,7 +9,8 @@ import { Spinner } from "../../components/spinner/index.js";
|
|
|
9
9
|
import { Switch } from "../../components/switch/index.js";
|
|
10
10
|
import { Tabs } from "../../components/tabs/index.js";
|
|
11
11
|
import { cx, isRtlElement, useCrispComponentDefaults } from "../../core/index.js";
|
|
12
|
-
import {
|
|
12
|
+
import { tagTone } from "./column.js";
|
|
13
|
+
import { editableInputType, useEditableField } from "./editable-field.js";
|
|
13
14
|
import { recordpageRecipe } from "./recordpage.recipe.js";
|
|
14
15
|
function valueEmpty(v) {
|
|
15
16
|
return v == null || v === "" || (Array.isArray(v) && v.length === 0);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AtSign, Briefcase, Building2, Calendar, Globe, Hash, MapPin, Tag, TrendingUp, Users, } from "lucide-react";
|
|
2
|
+
// Moved here from product/recordstable/recordstable.tsx's `defaultColumns`/
|
|
3
|
+
// `defaultRows` (crisp#786): RecordsTable is deprecated and its whole
|
|
4
|
+
// directory is being deleted, but RecordPage's own tests (and the shared
|
|
5
|
+
// RTL-keyboard-convention tests that live alongside them) still need a
|
|
6
|
+
// real, 12-column-deep dataset to exercise "a field past the first 5 needs
|
|
7
|
+
// View all values" and similar shape-dependent assertions. Simplified from
|
|
8
|
+
// the original: no baked-in company-logo data URIs (nothing here asserts on
|
|
9
|
+
// avatars), a plain favicon URL instead.
|
|
10
|
+
export const testColumns = [
|
|
11
|
+
{ key: "company", label: "Company", type: "entity", icon: Building2 },
|
|
12
|
+
{ key: "domain", label: "Domain", type: "url", icon: Globe },
|
|
13
|
+
{
|
|
14
|
+
key: "categories",
|
|
15
|
+
label: "Categories",
|
|
16
|
+
type: "tags",
|
|
17
|
+
icon: Tag,
|
|
18
|
+
width: 240,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
key: "employees",
|
|
22
|
+
label: "Employees",
|
|
23
|
+
type: "number",
|
|
24
|
+
icon: Users,
|
|
25
|
+
width: 120,
|
|
26
|
+
},
|
|
27
|
+
{ key: "location", label: "Location", type: "text", icon: MapPin },
|
|
28
|
+
{ key: "stage", label: "Stage", type: "tags", icon: TrendingUp, width: 150 },
|
|
29
|
+
{ key: "ceo", label: "CEO", type: "text", icon: Briefcase, width: 170 },
|
|
30
|
+
{ key: "twitter", label: "Twitter", type: "url", icon: AtSign, width: 150 },
|
|
31
|
+
{
|
|
32
|
+
key: "followers",
|
|
33
|
+
label: "Followers",
|
|
34
|
+
type: "number",
|
|
35
|
+
icon: Users,
|
|
36
|
+
width: 120,
|
|
37
|
+
},
|
|
38
|
+
{ key: "linkedin", label: "LinkedIn", type: "url", icon: Globe, width: 180 },
|
|
39
|
+
{
|
|
40
|
+
key: "founded",
|
|
41
|
+
label: "Founded",
|
|
42
|
+
type: "date",
|
|
43
|
+
icon: Calendar,
|
|
44
|
+
width: 110,
|
|
45
|
+
},
|
|
46
|
+
{ key: "arr", label: "Est. ARR", type: "number", icon: Hash, width: 130 },
|
|
47
|
+
];
|
|
48
|
+
// biome-ignore format: one row per line keeps the wide demo dataset readable
|
|
49
|
+
export const testRows = [
|
|
50
|
+
{ id: "google", company: { name: "Google", avatar: "https://www.google.com/s2/favicons?domain=google.com&sz=64" }, domain: "google.com", categories: ["Search", "Ads", "Cloud", "AI", "Android", "Maps", "Hardware", "Workspace", "Chrome"], employees: 182000, location: "Mountain View, CA", stage: ["Public"], ceo: "Sundar Pichai", twitter: "twitter.com/google", followers: 29800000, linkedin: "linkedin.com/company/google", founded: "1998", arr: 307000000000 },
|
|
51
|
+
{ id: "stripe", company: { name: "Stripe", avatar: "https://www.google.com/s2/favicons?domain=stripe.com&sz=64" }, domain: "stripe.com", categories: ["Payments", "Fintech"], employees: 8000, location: "San Francisco, CA", stage: ["Series I"], ceo: "Patrick Collison", twitter: "twitter.com/stripe", followers: 1200000, linkedin: "linkedin.com/company/stripe", founded: "2010", arr: 16000000000 },
|
|
52
|
+
];
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { editableInputType, useEditableField, } from "
|
|
1
|
+
export { editableInputType, useEditableField, } from "../recordpage/editable-field.js";
|
|
2
2
|
export { defaultColumns, defaultRows, RecordsTable, tagTone, } from "./recordstable.js";
|
|
3
3
|
export { recordstableRecipe } from "./recordstable.recipe.js";
|