@crisp-ui-kit/crisp 0.54.10 → 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 +121 -10
- package/dist/cjs/components/datatable/datatable.recipe.js +24 -0
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +25 -2
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +93 -43
- package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +28 -9
- 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 +14 -0
- package/dist/datatable.layered.css +14 -0
- package/dist/esm/components/datatable/datatable.js +121 -10
- package/dist/esm/components/datatable/datatable.recipe.js +24 -0
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +25 -2
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +93 -43
- package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +28 -9
- 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 +62 -23
- package/dist/styles.layered.css +62 -23
- package/dist/tabletoolbar.css +23 -3
- package/dist/tabletoolbar.layered.css +23 -3
- package/dist/types/components/datatable/datatable.d.ts +30 -3
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +26 -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("");
|
|
@@ -602,16 +645,23 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
602
645
|
// worse than an imperfect word order); `crispMessages` rather than a
|
|
603
646
|
// second `useCrispMessage` call, since this only fires on the branch that
|
|
604
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;
|
|
605
659
|
const sortedLabel = sortTriggerLabel
|
|
606
660
|
? sortTriggerLabel(sorts, sortColumns)
|
|
607
661
|
: sorts.length
|
|
608
|
-
? `${crispMessages["Sorted by"] ?? "Sorted by"} ${
|
|
609
|
-
// #754: sorts[0].key may be a dot-joined nested path
|
|
610
|
-
// ("team.jobTitle") — resolve it the same way the chip and sort
|
|
611
|
-
// row do, rather than a flat find that fails on a nested key.
|
|
612
|
-
resolveColumnPath(sortColumns, sorts[0].key).at(-1)?.label}${sorts.length > 1 ? ` +${sorts.length - 1}` : ""}`
|
|
662
|
+
? `${crispMessages["Sorted by"] ?? "Sorted by"} ${sortField}${sorts.length > 1 ? ` +${sorts.length - 1}` : ""}`
|
|
613
663
|
: sortLabel;
|
|
614
|
-
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) => {
|
|
615
665
|
if (dragSort !== null)
|
|
616
666
|
e.preventDefault();
|
|
617
667
|
}, onDrop: () => {
|
|
@@ -648,7 +698,7 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
648
698
|
label: (_jsxs("span", { className: "crisp-tabletoolbar-sort-optlabel", children: [_jsx(ArrowDownWideNarrow, { size: 15, strokeWidth: 1.75 }), descendingLabel] })),
|
|
649
699
|
keywords: descendingLabel,
|
|
650
700
|
},
|
|
651
|
-
], "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: () => {
|
|
652
702
|
setFieldPickerPath([]);
|
|
653
703
|
setOpenMenu(openMenu === "filter" ? null : "filter");
|
|
654
704
|
}, "aria-haspopup": "dialog", "aria-expanded": openMenu === "filter", "data-menu-trigger": true, children: [_jsx(ListFilter, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), filterTriggerLabel
|
|
@@ -747,43 +797,43 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
747
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`,
|
|
748
798
|
// biome-ignore lint/a11y/noAutofocus: focus the value field
|
|
749
799
|
autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) })) }))] }, cond.id));
|
|
750
|
-
}), 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--
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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 ||
|
|
774
824
|
onExportCsv ||
|
|
775
825
|
onExportExcel ||
|
|
776
|
-
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: () => {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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] }) }));
|
|
787
837
|
}
|
|
788
838
|
/**
|
|
789
839
|
* The Sort/Filter chip's own chrome (`.crisp-tabletoolbar-tool`), standalone
|
|
@@ -71,14 +71,21 @@ const filterChipChrome = `
|
|
|
71
71
|
& .crisp-tabletoolbar-datepop-presets { display: flex; flex-wrap: wrap; gap: ${c.space["1"]}; margin-top: ${c.space["2"]}; padding: 0 ${c.space["1"]}; }
|
|
72
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; }
|
|
73
73
|
& .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, ${c.fgPrimary} 8%, transparent); }`;
|
|
74
|
-
// #595
|
|
75
|
-
// view bar: `28px` tall, `radius.md`
|
|
76
|
-
// (`rgb(
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
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
|
|
82
89
|
// order (this block is appended after `toolbarChromeBody`, which defines
|
|
83
90
|
// them), the same way `.tool--ghost` already does. `border`/`font-family`/
|
|
84
91
|
// `cursor` are repeated here too, even though `.tool`'s own base rule (in
|
|
@@ -88,7 +95,18 @@ const filterChipChrome = `
|
|
|
88
95
|
// behind `chrome.ts`'s function call is invisible to it; harmless repeats,
|
|
89
96
|
// not a real gap.
|
|
90
97
|
const importExportChrome = `
|
|
91
|
-
& .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); }`;
|
|
92
110
|
// The table toolbar, measured: 28px controls, the Sort/Filter buttons and
|
|
93
111
|
// the menus they open, sort-condition rows, and the filter chips. The chrome
|
|
94
112
|
// itself lives in ./tabletoolbar.chrome.ts because RecordsTable renders the
|
|
@@ -107,6 +125,7 @@ ${toolbarChromeHead("tabletoolbar")}
|
|
|
107
125
|
${toolbarChromeBody("tabletoolbar")}
|
|
108
126
|
${filterChipChrome}
|
|
109
127
|
${importExportChrome}
|
|
128
|
+
${sortedLabelChrome}
|
|
110
129
|
${rtlFlipCss}
|
|
111
130
|
`,
|
|
112
131
|
});
|
|
@@ -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";
|
|
@@ -512,6 +512,11 @@ function CreateRecordDialog({ open, onOpenChange, columns, entityLabel, onCreate
|
|
|
512
512
|
* editing (double-click a text/number/url/date cell), add-row and add-column,
|
|
513
513
|
* and a floating bulk-action bar (delete / deselect) when rows are selected.
|
|
514
514
|
* Selection can be controlled via `selectedIds`/`onSelectionChange`.
|
|
515
|
+
*
|
|
516
|
+
* @deprecated Reach for `DataTable` instead — a single Attio-exact records
|
|
517
|
+
* grid replaces the separate table variants. `RecordsTable` has no
|
|
518
|
+
* consumers left (checked GRC and the fms/Mercury admin) and is slated for
|
|
519
|
+
* removal in a future minor.
|
|
515
520
|
*/
|
|
516
521
|
export function RecordsTable({ labels, columns: columnsProp = defaultColumns, rows: rowsProp = defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", emptyStateExtra, className, ...rest }) {
|
|
517
522
|
const [cols, setCols] = React.useState(columnsProp);
|
|
@@ -23,24 +23,26 @@ const config = defineRecipe({
|
|
|
23
23
|
position: relative; display: inline-flex; font-family: ${c.font.sans};
|
|
24
24
|
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
25
25
|
|
|
26
|
-
/*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
/* crisp/#772 follow-up, re-measured live 2026-09-25 in the trigger's
|
|
27
|
+
actual position (Attio's real view bar, next to View settings --
|
|
28
|
+
fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255)
|
|
29
|
+
exact) with elevation.field's 3-layer shadow, the same raised
|
|
30
|
+
chrome View settings and Import/Export both carry
|
|
31
|
+
(tabletoolbar.recipe.ts's own --raised, not reused here directly
|
|
32
|
+
since this is a separate recipe/component, but the identical measured
|
|
33
|
+
values). Supersedes the original #555 measurement (rgb(246,247,247),
|
|
34
|
+
reused as bgRaised -- a flat grey fill with no shadow at all), which
|
|
35
|
+
was wrong: this trigger was never re-checked once it moved into its
|
|
36
|
+
real view-bar position. */
|
|
35
37
|
& .crisp-savedviews-trigger {
|
|
36
38
|
box-sizing: border-box; display: inline-flex; align-items: center; gap: ${c.space["1.5"]};
|
|
37
39
|
height: ${c.size.row}; margin: 0; padding: ${c.space["1"]} ${c.space["1.5"]};
|
|
38
|
-
border: none; border-radius: ${c.radius.md}; background: ${c.
|
|
40
|
+
border: none; border-radius: ${c.radius.md}; background: ${c.bgSurface}; box-shadow: ${e.field};
|
|
39
41
|
font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary};
|
|
40
42
|
cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
|
|
41
43
|
}
|
|
42
44
|
& .crisp-savedviews-trigger:hover,
|
|
43
|
-
& .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, ${c.fgPrimary} 4%, ${c.
|
|
45
|
+
& .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, ${c.fgPrimary} 4%, ${c.bgSurface}); }
|
|
44
46
|
& .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: ${c.size.icon}; height: ${c.size.icon}; }
|
|
45
47
|
& .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
46
48
|
& .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: ${c.fgMuted}; }
|
|
@@ -11,6 +11,11 @@ function Cell({ flex, className, children, }) {
|
|
|
11
11
|
* taskstable.recipe.ts) with a typed column/cell layer: a title+checkbox
|
|
12
12
|
* row grouped into due-date sections, a due-date cell that goes red past
|
|
13
13
|
* due, and consumer-supplied record/assignee chip slots.
|
|
14
|
+
*
|
|
15
|
+
* @deprecated Reach for `DataTable`'s `groupBy` instead — a single
|
|
16
|
+
* Attio-exact records grid replaces the separate table variants.
|
|
17
|
+
* `TasksTable` has no consumers left (checked GRC and the fms/Mercury
|
|
18
|
+
* admin) and is slated for removal in a future minor.
|
|
14
19
|
*/
|
|
15
20
|
export function TasksTable({ columns, groups, leading, trailing, emptyLabel: emptyLabelProp, completeLabel: completeLabelProp, incompleteLabel: incompleteLabelProp, className, ...rest }) {
|
|
16
21
|
const crispMessages = useCrispMessages();
|
package/dist/recordstable.css
CHANGED
|
@@ -8,12 +8,29 @@
|
|
|
8
8
|
& .crisp-recordstable-search svg { flex-shrink: 0; }
|
|
9
9
|
& .crisp-recordstable-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
|
|
10
10
|
& .crisp-recordstable-search input::placeholder { color: var(--crisp-fg-muted); }
|
|
11
|
-
& .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background:
|
|
11
|
+
& .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
|
|
12
12
|
& .crisp-recordstable-tool:hover { background: var(--crisp-bg-raised); }
|
|
13
13
|
& .crisp-recordstable-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
14
|
-
|
|
14
|
+
/* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
|
|
15
|
+
base .tool rule above now already sets; this override existed to
|
|
16
|
+
distinguish active from a base state that used to be transparent.
|
|
17
|
+
Kept explicit (not just deleted) so a future change to the base
|
|
18
|
+
rule's background can't silently change this one too. */
|
|
19
|
+
& .crisp-recordstable-tool--active { background: var(--crisp-bg-surface); }
|
|
15
20
|
& .crisp-recordstable-tool--ghost { box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); color: var(--crisp-fg-muted); }
|
|
16
21
|
& .crisp-recordstable-tool--ghost:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
|
|
22
|
+
/* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
|
|
23
|
+
trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
|
|
24
|
+
a dashed ::before (1px dashed, border-radius: inherit, absolutely
|
|
25
|
+
positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
|
|
26
|
+
instead (the --active modifier, unchanged) -- confirmed on Sort with
|
|
27
|
+
an actual sort applied. color-mix (not the raw rgba Attio computes
|
|
28
|
+
to) so both the border and the fill flip correctly in dark mode, the
|
|
29
|
+
same reasoning --ghost above already uses. Only wired into
|
|
30
|
+
TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. */
|
|
31
|
+
& .crisp-recordstable-tool--dashed { position: relative; box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); }
|
|
32
|
+
& .crisp-recordstable-tool--dashed::before { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 1px dashed color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); pointer-events: none; }
|
|
33
|
+
& .crisp-recordstable-tool--dashed:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
|
|
17
34
|
& .crisp-recordstable-toolgroup { position: relative; display: inline-flex; }
|
|
18
35
|
& .crisp-recordstable-divider { flex-shrink: 0; width: 1px; height: 20px; background: var(--crisp-border-subtle); }
|
|
19
36
|
& .crisp-recordstable-menu { position: absolute; top: calc(100% + var(--crisp-space-1)); left: 0; z-index: 30; min-width: 232px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); display: flex; flex-direction: column; gap: var(--crisp-space-px); transform-origin: top; opacity: 1; transform: none; transition: opacity .13s ease, transform .13s ease; }
|
|
@@ -9,12 +9,29 @@
|
|
|
9
9
|
& .crisp-recordstable-search svg { flex-shrink: 0; }
|
|
10
10
|
& .crisp-recordstable-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
|
|
11
11
|
& .crisp-recordstable-search input::placeholder { color: var(--crisp-fg-muted); }
|
|
12
|
-
& .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background:
|
|
12
|
+
& .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
|
|
13
13
|
& .crisp-recordstable-tool:hover { background: var(--crisp-bg-raised); }
|
|
14
14
|
& .crisp-recordstable-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
15
|
-
|
|
15
|
+
/* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
|
|
16
|
+
base .tool rule above now already sets; this override existed to
|
|
17
|
+
distinguish active from a base state that used to be transparent.
|
|
18
|
+
Kept explicit (not just deleted) so a future change to the base
|
|
19
|
+
rule's background can't silently change this one too. */
|
|
20
|
+
& .crisp-recordstable-tool--active { background: var(--crisp-bg-surface); }
|
|
16
21
|
& .crisp-recordstable-tool--ghost { box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); color: var(--crisp-fg-muted); }
|
|
17
22
|
& .crisp-recordstable-tool--ghost:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
|
|
23
|
+
/* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
|
|
24
|
+
trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
|
|
25
|
+
a dashed ::before (1px dashed, border-radius: inherit, absolutely
|
|
26
|
+
positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
|
|
27
|
+
instead (the --active modifier, unchanged) -- confirmed on Sort with
|
|
28
|
+
an actual sort applied. color-mix (not the raw rgba Attio computes
|
|
29
|
+
to) so both the border and the fill flip correctly in dark mode, the
|
|
30
|
+
same reasoning --ghost above already uses. Only wired into
|
|
31
|
+
TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. */
|
|
32
|
+
& .crisp-recordstable-tool--dashed { position: relative; box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); }
|
|
33
|
+
& .crisp-recordstable-tool--dashed::before { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 1px dashed color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); pointer-events: none; }
|
|
34
|
+
& .crisp-recordstable-tool--dashed:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
|
|
18
35
|
& .crisp-recordstable-toolgroup { position: relative; display: inline-flex; }
|
|
19
36
|
& .crisp-recordstable-divider { flex-shrink: 0; width: 1px; height: 20px; background: var(--crisp-border-subtle); }
|
|
20
37
|
& .crisp-recordstable-menu { position: absolute; top: calc(100% + var(--crisp-space-1)); left: 0; z-index: 30; min-width: 232px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); display: flex; flex-direction: column; gap: var(--crisp-space-px); transform-origin: top; opacity: 1; transform: none; transition: opacity .13s ease, transform .13s ease; }
|
package/dist/savedviews.css
CHANGED
|
@@ -73,24 +73,12 @@
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
.crisp-savedviews {
|
|
76
|
-
position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /*
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
wrong (a flat colour that stayed visible where the reference had
|
|
83
|
-
none) — this is the opposite risk (a token that already flips per
|
|
84
|
-
theme, applied to the closest measured match). */
|
|
85
|
-
& .crisp-savedviews-trigger {
|
|
86
|
-
box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5);
|
|
87
|
-
height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5);
|
|
88
|
-
border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised);
|
|
89
|
-
font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary);
|
|
90
|
-
cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
|
|
91
|
-
}
|
|
92
|
-
& .crisp-savedviews-trigger:hover,
|
|
93
|
-
& .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-raised)); }
|
|
76
|
+
position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* crisp/#772 follow-up, re-measured live 2026-09-25 in the trigger's
|
|
77
|
+
actual position (Attio's real view bar, next to View settings -- fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255) exact) with elevation.field's 3-layer shadow, the same raised
|
|
78
|
+
chrome View settings and Import/Export both carry
|
|
79
|
+
(tabletoolbar.recipe.ts's own --raised, not reused here directly since this is a separate recipe/component, but the identical measured values). Supersedes the original #555 measurement (rgb(246,247,247), reused as bgRaised -- a flat grey fill with no shadow at all), which was wrong: this trigger was never re-checked once it moved into its real view-bar position. */
|
|
80
|
+
& .crisp-savedviews-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; white-space: nowrap; transition: background-color .12s ease; }
|
|
81
|
+
& .crisp-savedviews-trigger:hover, & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
|
|
94
82
|
& .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
|
|
95
83
|
& .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
96
84
|
& .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
@@ -74,24 +74,12 @@
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
.crisp-savedviews {
|
|
77
|
-
position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /*
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
wrong (a flat colour that stayed visible where the reference had
|
|
84
|
-
none) — this is the opposite risk (a token that already flips per
|
|
85
|
-
theme, applied to the closest measured match). */
|
|
86
|
-
& .crisp-savedviews-trigger {
|
|
87
|
-
box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5);
|
|
88
|
-
height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5);
|
|
89
|
-
border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised);
|
|
90
|
-
font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary);
|
|
91
|
-
cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
|
|
92
|
-
}
|
|
93
|
-
& .crisp-savedviews-trigger:hover,
|
|
94
|
-
& .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-raised)); }
|
|
77
|
+
position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* crisp/#772 follow-up, re-measured live 2026-09-25 in the trigger's
|
|
78
|
+
actual position (Attio's real view bar, next to View settings -- fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255) exact) with elevation.field's 3-layer shadow, the same raised
|
|
79
|
+
chrome View settings and Import/Export both carry
|
|
80
|
+
(tabletoolbar.recipe.ts's own --raised, not reused here directly since this is a separate recipe/component, but the identical measured values). Supersedes the original #555 measurement (rgb(246,247,247), reused as bgRaised -- a flat grey fill with no shadow at all), which was wrong: this trigger was never re-checked once it moved into its real view-bar position. */
|
|
81
|
+
& .crisp-savedviews-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; white-space: nowrap; transition: background-color .12s ease; }
|
|
82
|
+
& .crisp-savedviews-trigger:hover, & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
|
|
95
83
|
& .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
|
|
96
84
|
& .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
97
85
|
& .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); }
|