@crisp-ui-kit/crisp 0.54.1 → 0.54.3
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/chip.css +7 -1
- package/dist/chip.d.ts +2 -1
- package/dist/chip.layered.css +7 -1
- package/dist/cjs/components/chip/chip.js +3 -2
- package/dist/cjs/components/chip/chip.recipe.js +37 -1
- package/dist/cjs/components/datatable/datatable.js +490 -111
- package/dist/cjs/components/datatable/datatable.recipe.js +4 -0
- package/dist/cjs/components/emptystate/emptystate.js +3 -2
- package/dist/cjs/components/emptystate/emptystate.recipe.js +26 -1
- package/dist/cjs/components/mentionchip/index.js +7 -0
- package/dist/cjs/components/mentionchip/mentionchip.js +57 -0
- package/dist/cjs/components/mentionchip/mentionchip.recipe.js +38 -0
- package/dist/cjs/components/personchip/index.js +17 -0
- package/dist/cjs/components/personchip/personchip.js +50 -0
- package/dist/cjs/components/table/table.js +2 -2
- package/dist/cjs/components/table/table.recipe.js +15 -3
- package/dist/cjs/components/tabs/tabs.js +2 -2
- package/dist/cjs/components/tabs/tabs.recipe.js +30 -3
- package/dist/cjs/components/text/index.js +7 -0
- package/dist/cjs/components/text/text.js +55 -0
- package/dist/cjs/components/text/text.recipe.js +34 -0
- package/dist/cjs/core/floating-stack.js +18 -0
- package/dist/cjs/core/index.js +2 -1
- package/dist/cjs/index.js +8 -0
- package/dist/cjs/product/formdialog/formdialog.js +171 -0
- package/dist/cjs/product/formdialog/formdialog.recipe.js +26 -0
- package/dist/cjs/product/formdialog/index.js +8 -0
- package/dist/cjs/product/headerbar/headerbar.recipe.js +9 -1
- package/dist/cjs/product/importdialog/importdialog.js +134 -0
- package/dist/cjs/product/importdialog/importdialog.recipe.js +92 -0
- package/dist/cjs/product/importdialog/index.js +18 -0
- package/dist/cjs/product/recordpage/recordpage.recipe.js +10 -1
- package/dist/cjs/product/recordstable/recordstable.js +89 -6
- package/dist/cjs/product/richtext/richtext.recipe.js +26 -1
- package/dist/cjs/product/savedviews/index.js +7 -0
- package/dist/cjs/product/savedviews/savedviews.js +207 -0
- package/dist/cjs/product/savedviews/savedviews.recipe.js +103 -0
- package/dist/cjs/product/taskstable/index.js +7 -0
- package/dist/cjs/product/taskstable/taskstable.js +27 -0
- package/dist/cjs/product/taskstable/taskstable.recipe.js +47 -0
- package/dist/cjs/product/themepicker/index.js +17 -0
- package/dist/cjs/product/themepicker/themepicker.js +32 -0
- package/dist/cjs/product/themepicker/themepicker.recipe.js +98 -0
- package/dist/cjs/tokens/palette.js +30 -13
- package/dist/cjs/tokens/semantic.js +6 -0
- package/dist/datatable.css +20 -21
- package/dist/datatable.layered.css +20 -21
- package/dist/emptystate.css +15 -1
- package/dist/emptystate.d.ts +2 -2
- package/dist/emptystate.layered.css +15 -1
- package/dist/esm/components/chip/chip.js +3 -2
- package/dist/esm/components/chip/chip.recipe.js +37 -1
- package/dist/esm/components/datatable/datatable.js +489 -111
- package/dist/esm/components/datatable/datatable.recipe.js +4 -0
- package/dist/esm/components/emptystate/emptystate.js +4 -3
- package/dist/esm/components/emptystate/emptystate.recipe.js +26 -1
- package/dist/esm/components/emptystate/index.js +1 -1
- package/dist/esm/components/mentionchip/index.js +2 -0
- package/dist/esm/components/mentionchip/mentionchip.js +21 -0
- package/dist/esm/components/mentionchip/mentionchip.recipe.js +35 -0
- package/dist/esm/components/personchip/index.js +1 -0
- package/dist/esm/components/personchip/personchip.js +47 -0
- package/dist/esm/components/table/table.js +2 -2
- package/dist/esm/components/table/table.recipe.js +15 -3
- package/dist/esm/components/tabs/tabs.js +2 -2
- package/dist/esm/components/tabs/tabs.recipe.js +30 -3
- package/dist/esm/components/text/index.js +2 -0
- package/dist/esm/components/text/text.js +19 -0
- package/dist/esm/components/text/text.recipe.js +31 -0
- package/dist/esm/core/floating-stack.js +17 -0
- package/dist/esm/core/index.js +1 -1
- package/dist/esm/index.js +8 -0
- package/dist/esm/product/formdialog/formdialog.js +134 -0
- package/dist/esm/product/formdialog/formdialog.recipe.js +23 -0
- package/dist/esm/product/formdialog/index.js +2 -0
- package/dist/esm/product/headerbar/headerbar.recipe.js +9 -1
- package/dist/esm/product/importdialog/importdialog.js +97 -0
- package/dist/esm/product/importdialog/importdialog.recipe.js +89 -0
- package/dist/esm/product/importdialog/index.js +2 -0
- package/dist/esm/product/recordpage/recordpage.recipe.js +10 -1
- package/dist/esm/product/recordstable/recordstable.js +90 -8
- package/dist/esm/product/richtext/richtext.recipe.js +26 -1
- package/dist/esm/product/savedviews/index.js +2 -0
- package/dist/esm/product/savedviews/savedviews.js +171 -0
- package/dist/esm/product/savedviews/savedviews.recipe.js +100 -0
- package/dist/esm/product/taskstable/index.js +2 -0
- package/dist/esm/product/taskstable/taskstable.js +24 -0
- package/dist/esm/product/taskstable/taskstable.recipe.js +44 -0
- package/dist/esm/product/themepicker/index.js +1 -0
- package/dist/esm/product/themepicker/themepicker.js +29 -0
- package/dist/esm/product/themepicker/themepicker.recipe.js +95 -0
- package/dist/esm/tokens/palette.js +30 -13
- package/dist/esm/tokens/semantic.js +6 -0
- package/dist/formdialog.css +6 -0
- package/dist/formdialog.d.ts +7 -0
- package/dist/formdialog.layered.css +8 -0
- package/dist/headerbar.css +1 -1
- package/dist/headerbar.layered.css +1 -1
- package/dist/importdialog.css +38 -0
- package/dist/importdialog.d.ts +7 -0
- package/dist/importdialog.layered.css +40 -0
- package/dist/mentionchip.css +3 -0
- package/dist/mentionchip.d.ts +7 -0
- package/dist/mentionchip.layered.css +5 -0
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +14 -6
- package/dist/recordpage.css +13 -2
- package/dist/recordpage.layered.css +13 -2
- package/dist/richtext.css +11 -1
- package/dist/richtext.layered.css +11 -1
- package/dist/savedviews.css +78 -0
- package/dist/savedviews.d.ts +7 -0
- package/dist/savedviews.layered.css +80 -0
- package/dist/styles.css +287 -37
- package/dist/styles.layered.css +287 -37
- package/dist/table.css +15 -6
- package/dist/table.layered.css +15 -6
- package/dist/tabs.css +7 -1
- package/dist/tabs.d.ts +2 -2
- package/dist/tabs.layered.css +7 -1
- package/dist/taskstable.css +23 -0
- package/dist/taskstable.d.ts +7 -0
- package/dist/taskstable.layered.css +25 -0
- package/dist/text.css +9 -0
- package/dist/text.d.ts +7 -0
- package/dist/text.layered.css +11 -0
- package/dist/themepicker.css +27 -0
- package/dist/themepicker.d.ts +7 -0
- package/dist/themepicker.layered.css +29 -0
- package/dist/tokens.css +7 -3
- package/dist/tokens.json +14 -6
- package/dist/types/components/chip/chip.d.ts +32 -0
- package/dist/types/components/chip/chip.recipe.d.ts +31 -0
- package/dist/types/components/datatable/datatable.d.ts +39 -4
- package/dist/types/components/emptystate/emptystate.d.ts +29 -4
- package/dist/types/components/emptystate/emptystate.recipe.d.ts +2 -0
- package/dist/types/components/emptystate/index.d.ts +1 -1
- package/dist/types/components/mentionchip/index.d.ts +2 -0
- package/dist/types/components/mentionchip/mentionchip.d.ts +20 -0
- package/dist/types/components/mentionchip/mentionchip.recipe.d.ts +18 -0
- package/dist/types/components/personchip/index.d.ts +1 -0
- package/dist/types/components/personchip/personchip.d.ts +37 -0
- package/dist/types/components/table/table.d.ts +36 -0
- package/dist/types/components/tabs/tabs.d.ts +4 -2
- package/dist/types/components/tabs/tabs.recipe.d.ts +18 -3
- package/dist/types/components/text/index.d.ts +2 -0
- package/dist/types/components/text/text.d.ts +32 -0
- package/dist/types/components/text/text.recipe.d.ts +40 -0
- package/dist/types/core/floating-stack.d.ts +14 -0
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/index.d.ts +8 -0
- package/dist/types/product/formdialog/formdialog.d.ts +57 -0
- package/dist/types/product/formdialog/formdialog.recipe.d.ts +3 -0
- package/dist/types/product/formdialog/index.d.ts +2 -0
- package/dist/types/product/importdialog/importdialog.d.ts +86 -0
- package/dist/types/product/importdialog/importdialog.recipe.d.ts +3 -0
- package/dist/types/product/importdialog/index.d.ts +2 -0
- package/dist/types/product/recordstable/recordstable.d.ts +24 -2
- package/dist/types/product/savedviews/index.d.ts +2 -0
- package/dist/types/product/savedviews/savedviews.d.ts +55 -0
- package/dist/types/product/savedviews/savedviews.recipe.d.ts +3 -0
- package/dist/types/product/taskstable/index.d.ts +2 -0
- package/dist/types/product/taskstable/taskstable.d.ts +54 -0
- package/dist/types/product/taskstable/taskstable.recipe.d.ts +3 -0
- package/dist/types/product/themepicker/index.d.ts +1 -0
- package/dist/types/product/themepicker/themepicker.d.ts +21 -0
- package/dist/types/product/themepicker/themepicker.recipe.d.ts +3 -0
- package/dist/types/tokens/contract.d.ts +2 -0
- package/dist/types/tokens/palette.d.ts +11 -3
- package/dist/types/tokens/semantic.d.ts +6 -0
- package/package.json +41 -1
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.defaultRows = exports.defaultColumns = exports.parseCellDate = exports.formatCellDate = exports.recordsTableLabels = void 0;
|
|
37
|
+
exports.isLabelTruncated = isLabelTruncated;
|
|
37
38
|
exports.tagTone = tagTone;
|
|
38
39
|
exports.RecordsTable = RecordsTable;
|
|
39
40
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
@@ -52,9 +53,61 @@ const menu_1 = require("../../components/menu");
|
|
|
52
53
|
const multiselect_1 = require("../../components/multiselect");
|
|
53
54
|
const select_1 = require("../../components/select");
|
|
54
55
|
const switch_1 = require("../../components/switch");
|
|
56
|
+
const tooltip_1 = require("../../components/tooltip");
|
|
55
57
|
const core_1 = require("../../core");
|
|
56
58
|
const company_logos_1 = require("./company-logos");
|
|
57
59
|
const recordstable_recipe_1 = require("./recordstable.recipe");
|
|
60
|
+
// #534: whether an element's own content is being cut off by its own box --
|
|
61
|
+
// the same fact `.crisp-recordstable-th-label`'s CSS ellipsis is silently
|
|
62
|
+
// applying, exported so the comparison itself (not layout) can be
|
|
63
|
+
// mutation-tested without a real browser. Identical to DataTable's own
|
|
64
|
+
// `isLabelTruncated` (components/datatable/datatable.tsx) -- not shared
|
|
65
|
+
// because `product/` can depend on `components/` but not the other way, and
|
|
66
|
+
// this file already duplicates DataTable's other measured header behaviour
|
|
67
|
+
// rather than reach across that boundary.
|
|
68
|
+
function isLabelTruncated(el) {
|
|
69
|
+
return el.scrollWidth > el.clientWidth;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The header label span: plain when it fits, wrapped in the shared `Tooltip`
|
|
73
|
+
* (full text, `asChild` so the wrapper never displaces the ellipsis CSS the
|
|
74
|
+
* span already carries -- see `Tooltip`'s own `asChild` doc) once it's
|
|
75
|
+
* actually cut off. Re-measures on mount and on resize (column drag,
|
|
76
|
+
* viewport change, label change) via `ResizeObserver`; SSR-safe the same way
|
|
77
|
+
* `MiddleTruncate` is -- the DOM is only touched inside an effect, so the
|
|
78
|
+
* server (and the first client paint) renders the plain span.
|
|
79
|
+
*/
|
|
80
|
+
function ThLabel({ label }) {
|
|
81
|
+
const ref = React.useRef(null);
|
|
82
|
+
const [overflowing, setOverflowing] = React.useState(false);
|
|
83
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `label` isn't read in the body, but a rename (col.label changing with the column's own width unchanged) needs a re-measure a ResizeObserver on `el` won't fire for -- same reasoning as Tabs' own re-measure effect.
|
|
84
|
+
React.useLayoutEffect(() => {
|
|
85
|
+
const el = ref.current;
|
|
86
|
+
if (!el)
|
|
87
|
+
return;
|
|
88
|
+
const measure = () => setOverflowing(isLabelTruncated(el));
|
|
89
|
+
measure();
|
|
90
|
+
if (typeof ResizeObserver === "undefined")
|
|
91
|
+
return;
|
|
92
|
+
const ro = new ResizeObserver(measure);
|
|
93
|
+
ro.observe(el);
|
|
94
|
+
return () => ro.disconnect();
|
|
95
|
+
}, [label]);
|
|
96
|
+
// `Tooltip` always wraps the SAME span (never swapped in/out of the tree
|
|
97
|
+
// based on `overflowing`) so the span's own DOM node identity survives
|
|
98
|
+
// every toggle -- a real-browser-only bug (happy-dom has no
|
|
99
|
+
// ResizeObserver, so the test suite never exercised this path): swapping
|
|
100
|
+
// between a bare `<span>` and `<Tooltip asChild><span/></Tooltip>` changes
|
|
101
|
+
// the element type at this position, so React unmounts the old span and
|
|
102
|
+
// mounts a new one; the effect above never re-runs (its deps are
|
|
103
|
+
// `[label]`, unchanged), so its ResizeObserver keeps watching the now
|
|
104
|
+
// -detached old node, whose deferred initial callback then reports 0x0 and
|
|
105
|
+
// flips `overflowing` straight back to false -- the tooltip would never
|
|
106
|
+
// actually show. `open={false}` when it fits keeps the Tooltip mounted
|
|
107
|
+
// (asChild costs no extra DOM either way) but pins it permanently closed,
|
|
108
|
+
// so hovering a non-overflowing header still opens nothing.
|
|
109
|
+
return ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { content: label, asChild: true, open: overflowing ? undefined : false, children: (0, jsx_runtime_1.jsx)("span", { ref: ref, className: "crisp-recordstable-th-label", children: label }) }));
|
|
110
|
+
}
|
|
58
111
|
const EDITABLE = new Set(["text", "number", "url", "currency"]);
|
|
59
112
|
// types whose cell is an always-interactive picker (not double-click-to-edit)
|
|
60
113
|
const PICKER = new Set(["status", "reference", "date", "tags"]);
|
|
@@ -138,6 +191,8 @@ exports.recordsTableLabels = {
|
|
|
138
191
|
setRecordPlaceholder: "Set a record…",
|
|
139
192
|
noRecords: "No records yet",
|
|
140
193
|
untitled: "Untitled",
|
|
194
|
+
importCsv: "Import CSV",
|
|
195
|
+
connectDataSource: "Connect data source",
|
|
141
196
|
agg: {
|
|
142
197
|
none: "None",
|
|
143
198
|
count: "Count all",
|
|
@@ -149,6 +204,9 @@ exports.recordsTableLabels = {
|
|
|
149
204
|
},
|
|
150
205
|
op: {
|
|
151
206
|
contains: "contains",
|
|
207
|
+
doesnotcontain: "does not contain",
|
|
208
|
+
startswith: "starts with",
|
|
209
|
+
endswith: "ends with",
|
|
152
210
|
is: "is",
|
|
153
211
|
isnot: "is not",
|
|
154
212
|
empty: "is empty",
|
|
@@ -175,8 +233,17 @@ const compact = new Intl.NumberFormat("en", {
|
|
|
175
233
|
const CHECK_W = 40;
|
|
176
234
|
const ADDCOL_W = 44;
|
|
177
235
|
const colWidth = (col) => col.width ?? (col.type === "entity" ? 240 : 180);
|
|
178
|
-
const OP_ORDER = [
|
|
179
|
-
|
|
236
|
+
const OP_ORDER = [
|
|
237
|
+
"contains",
|
|
238
|
+
"doesnotcontain",
|
|
239
|
+
"startswith",
|
|
240
|
+
"endswith",
|
|
241
|
+
"is",
|
|
242
|
+
"isnot",
|
|
243
|
+
"empty",
|
|
244
|
+
"notempty",
|
|
245
|
+
];
|
|
246
|
+
const opNeedsValue = (op) => op !== "empty" && op !== "notempty";
|
|
180
247
|
function matchesFilter(col, row, cond) {
|
|
181
248
|
if (cond.op === "empty")
|
|
182
249
|
return !isFilled(col, row);
|
|
@@ -190,6 +257,12 @@ function matchesFilter(col, row, cond) {
|
|
|
190
257
|
return true;
|
|
191
258
|
if (cond.op === "contains")
|
|
192
259
|
return t.includes(v);
|
|
260
|
+
if (cond.op === "doesnotcontain")
|
|
261
|
+
return !t.includes(v);
|
|
262
|
+
if (cond.op === "startswith")
|
|
263
|
+
return t.startsWith(v);
|
|
264
|
+
if (cond.op === "endswith")
|
|
265
|
+
return t.endsWith(v);
|
|
193
266
|
if (cond.op === "is")
|
|
194
267
|
return t === v;
|
|
195
268
|
return t !== v; // isnot
|
|
@@ -472,7 +545,7 @@ function CreateRecordDialog({ open, onOpenChange, columns, entityLabel, onCreate
|
|
|
472
545
|
* and a floating bulk-action bar (delete / deselect) when rows are selected.
|
|
473
546
|
* Selection can be controlled via `selectedIds`/`onSelectionChange`.
|
|
474
547
|
*/
|
|
475
|
-
function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, rows: rowsProp = exports.defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", className, ...rest }) {
|
|
548
|
+
function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, rows: rowsProp = exports.defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", emptyStateExtra, className, ...rest }) {
|
|
476
549
|
const [cols, setCols] = React.useState(columnsProp);
|
|
477
550
|
const [data, setData] = React.useState(rowsProp);
|
|
478
551
|
// The table owns `cols`/`data` so it can rename a column, edit a cell, or
|
|
@@ -1140,7 +1213,7 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
|
|
|
1140
1213
|
setDragCol(col.key);
|
|
1141
1214
|
e.dataTransfer.effectAllowed = "move";
|
|
1142
1215
|
}
|
|
1143
|
-
: undefined, onDragEnd: () => setDragCol(null), onClick: (e) => openColumnMenu(col.key, e.currentTarget), children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 16, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)(
|
|
1216
|
+
: undefined, onDragEnd: () => setDragCol(null), onClick: (e) => openColumnMenu(col.key, e.currentTarget), children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 16, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)(ThLabel, { label: col.label }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordstable-sort", children: (0, jsx_runtime_1.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("path", { d: "m4 6 4 4 4-4" }) }) })] })), openMenu === `col:${col.key}` &&
|
|
1144
1217
|
rootRef.current &&
|
|
1145
1218
|
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { ref: menuRef, className: "crisp-recordstable-menu crisp-recordstable-menu--fixed crisp-recordstable-menu--hdr", role: "menu", "aria-label": `${col.label} column`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "asc"), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUp, { size: 15, strokeWidth: 1.75 }), t.sortAscending] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "desc"), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDown, { size: 15, strokeWidth: 1.75 }), t.sortDescending] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i <= 1, onClick: () => moveColumn(col.key, -1), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowLeft, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }), t.moveLeft] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i === 0 || i >= cols.length - 1, onClick: () => moveColumn(col.key, 1), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }), t.moveRight] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordstable-menu-sep" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => {
|
|
1146
1219
|
setRenamingCol(col.key);
|
|
@@ -1209,9 +1282,19 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
|
|
|
1209
1282
|
}, children: res ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-recordstable-sum-label", children: t.agg[a] }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordstable-sum-value", children: res.value })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordstable-sum-label", children: t.calculate })) }), sumOpen &&
|
|
1210
1283
|
rootRef.current &&
|
|
1211
1284
|
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { ref: menuRef, className: "crisp-recordstable-menu crisp-recordstable-menu--fixed crisp-recordstable-menu--up", role: "menu", "aria-label": `${col.label} summary`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: aggOptions(col).map((opt) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitemradio", tabIndex: -1, "aria-checked": a === opt, "data-on": a === opt ? "" : undefined, onClick: () => setAggValue(col.key, opt), children: [t.agg[opt], a === opt ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "crisp-recordstable-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, opt))) }), rootRef.current)] }) }, col.key));
|
|
1212
|
-
}), (0, jsx_runtime_1.jsx)("td", { className: "crisp-recordstable-sum crisp-recordstable-addcol" })] }) })] }), visibleRows.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordstable-empty", children: (0, jsx_runtime_1.jsx)(emptystate_1.EmptyState
|
|
1285
|
+
}), (0, jsx_runtime_1.jsx)("td", { className: "crisp-recordstable-sum crisp-recordstable-addcol" })] }) })] }), visibleRows.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordstable-empty", children: (0, jsx_runtime_1.jsx)(emptystate_1.EmptyState
|
|
1286
|
+
// #603: the unfiltered zero-records state, when the consumer
|
|
1287
|
+
// opts into the extra CTAs, uses Attio's own larger measured
|
|
1288
|
+
// tile (Users/Workspaces, 138px) instead of the 45px
|
|
1289
|
+
// filtered-to-zero ring -- two different Attio surfaces.
|
|
1290
|
+
, {
|
|
1291
|
+
// #603: the unfiltered zero-records state, when the consumer
|
|
1292
|
+
// opts into the extra CTAs, uses Attio's own larger measured
|
|
1293
|
+
// tile (Users/Workspaces, 138px) instead of the 45px
|
|
1294
|
+
// filtered-to-zero ring -- two different Attio surfaces.
|
|
1295
|
+
variant: !activeFilters && emptyStateExtra ? "records" : "page", icon: activeFilters ? ((0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Inbox, { size: !activeFilters && emptyStateExtra ? 40 : 18, strokeWidth: 1.25, "aria-hidden": "true" })), title: activeFilters ? "No records match that filter" : t.noRecords, description: activeFilters
|
|
1213
1296
|
? "None of your records match the filters that are set."
|
|
1214
|
-
: `Create your first ${cols.find((c) => c.type === "entity")?.label?.toLowerCase() ?? "record"} to get started.`, action: activeFilters ? ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: () => setFilterConds([]), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 15, strokeWidth: 1.75 }), t.clearFilter] })) : ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), t.newRecord] })) }) })) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-addrow", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), t.newRecord] }))] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordstable-live", role: "status", "aria-live": "polite", children: selectionAnnouncement }), selected.size > 0 && ((0, jsx_runtime_1.jsxs)(bulkbar_1.BulkBar, { count: selected.size, countLabel: t.selectedCount, onClear: () => setSelected(new Set()), children: [(0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.ListPlus, { size: 15, strokeWidth: 1.75 }), onClick: addToList, children: t.addToList }), (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 15, strokeWidth: 1.75 }), onClick: sendEmail, children: t.sendEmail }), (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Workflow, { size: 15, strokeWidth: 1.75 }), onClick: runWorkflow, children: t.runWorkflow }), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.moreActions, trigger: (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 15, strokeWidth: 1.75 }), children: t.more }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Download, { size: 15, strokeWidth: 1.75 }), onSelect: exportSelected, children: t.exportCsv }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, onSelect: deleteSelected, children: t.delete })] })] })), (0, jsx_runtime_1.jsx)(CreateRecordDialog, { labels: t, open: creating, onOpenChange: setCreating, columns: cols, entityLabel: cols.find((c) => c.type === "entity")?.label ?? "record", onCreate: (row) => setData((prev) => [...prev, row]) }), ctxMenu &&
|
|
1297
|
+
: `Create your first ${cols.find((c) => c.type === "entity")?.label?.toLowerCase() ?? "record"} to get started.`, action: activeFilters ? ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: () => setFilterConds([]), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 15, strokeWidth: 1.75 }), t.clearFilter] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), t.newRecord] }), emptyStateExtra?.onImportCsv && ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "neutral", size: "sm", onClick: emptyStateExtra.onImportCsv, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Upload, { size: 15, strokeWidth: 1.75 }), t.importCsv] })), emptyStateExtra?.onConnectDataSource && ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "neutral", size: "sm", onClick: emptyStateExtra.onConnectDataSource, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plug, { size: 15, strokeWidth: 1.75 }), t.connectDataSource] }))] })), learnMore: !activeFilters ? emptyStateExtra?.learnMore : undefined }) })) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-addrow", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), t.newRecord] }))] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordstable-live", role: "status", "aria-live": "polite", children: selectionAnnouncement }), selected.size > 0 && ((0, jsx_runtime_1.jsxs)(bulkbar_1.BulkBar, { count: selected.size, countLabel: t.selectedCount, onClear: () => setSelected(new Set()), children: [(0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.ListPlus, { size: 15, strokeWidth: 1.75 }), onClick: addToList, children: t.addToList }), (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 15, strokeWidth: 1.75 }), onClick: sendEmail, children: t.sendEmail }), (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Workflow, { size: 15, strokeWidth: 1.75 }), onClick: runWorkflow, children: t.runWorkflow }), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.moreActions, trigger: (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 15, strokeWidth: 1.75 }), children: t.more }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Download, { size: 15, strokeWidth: 1.75 }), onSelect: exportSelected, children: t.exportCsv }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, onSelect: deleteSelected, children: t.delete })] })] })), (0, jsx_runtime_1.jsx)(CreateRecordDialog, { labels: t, open: creating, onOpenChange: setCreating, columns: cols, entityLabel: cols.find((c) => c.type === "entity")?.label ?? "record", onCreate: (row) => setData((prev) => [...prev, row]) }), ctxMenu &&
|
|
1215
1298
|
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { ref: ctxRef, className: "crisp-recordstable-menu crisp-recordstable-ctx", role: "menu", "aria-label": t.recordActions, style: { position: "fixed", top: ctxMenu.y, left: ctxMenu.x }, onKeyDown: onCtxKeyDown, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => copyRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), t.copy] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => duplicateRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CopyPlus, { size: 15, strokeWidth: 1.75 }), t.duplicate] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => pinRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), t.addToList] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordstable-menu-item crisp-recordstable-menu-item--danger", role: "menuitem", tabIndex: -1, onClick: () => deleteRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), t.delete] })] }), rootRef.current ?? document.body)] }));
|
|
1216
1299
|
}
|
|
1217
1300
|
exports.defaultColumns = [
|
|
@@ -43,7 +43,32 @@ const config = (0, core_1.defineRecipe)({
|
|
|
43
43
|
& .crisp-rte-body h1 { font-size: ${tokens_1.contract.text.xl}; line-height: 28px; font-weight: ${tokens_1.contract.weight.semibold}; letter-spacing: -0.3px; margin-top: ${tokens_1.contract.space["4"]}; }
|
|
44
44
|
& .crisp-rte-body h2 { font-size: 17px; line-height: 24px; font-weight: ${tokens_1.contract.weight.semibold}; letter-spacing: -0.2px; margin-top: ${tokens_1.contract.space["3"]}; }
|
|
45
45
|
& .crisp-rte-body h3 { font-size: ${tokens_1.contract.text.lg}; line-height: 20px; font-weight: ${tokens_1.contract.weight.semibold}; margin-top: ${tokens_1.contract.space["2.5"]}; }
|
|
46
|
-
|
|
46
|
+
/* #575: a consumer's CSS reset (e.g. Tailwind preflight's un-scoped
|
|
47
|
+
ul, ol { list-style: none }, specificity 0,0,1) was winning every
|
|
48
|
+
list marker outright -- this rule set padding but never declared
|
|
49
|
+
list-style-type, so there was no higher-specificity value (this
|
|
50
|
+
selector is 0,1,1) to beat the reset with. Split into two
|
|
51
|
+
single-selector rules, each declaring its own marker type, rather
|
|
52
|
+
than the old single comma-joined rule ( .crisp-rte-body ul, &
|
|
53
|
+
.crisp-rte-body ol ) the issue flagged as malformed.
|
|
54
|
+
#611: padding-inline-start re-measured live once CRUD on the
|
|
55
|
+
measurement workspace was allowed (a "zz-parity" note with a
|
|
56
|
+
bulleted list, a nested bulleted list, and a numbered list) --
|
|
57
|
+
Attio's own <ul>/<ol> measures 36px (getComputedStyle,
|
|
58
|
+
~36.1353px), not the 20px placeholder #575 shipped with no real
|
|
59
|
+
content to check against. 36px isn't in tokens/scale.ts (steps
|
|
60
|
+
stop at 24px, jump to 32px) and is a single component-intrinsic
|
|
61
|
+
value, so it stays a raw px per AUTHORING.md's golden rule 4b
|
|
62
|
+
rather than being rounded to an existing token or added to the
|
|
63
|
+
shared scale. Marker colour (currentColor via the already-measured
|
|
64
|
+
fgPrimary, confirmed to equal Attio's measured rgb(16,17,18) ==
|
|
65
|
+
#101112 exactly) and the ~4px li-to-li gap (the existing li
|
|
66
|
+
margin-top rule below) were re-checked at the same time and
|
|
67
|
+
already matched -- only the padding was wrong. Attio doesn't vary
|
|
68
|
+
marker type for a nested list either (nested <ul> is disc too,
|
|
69
|
+
not circle), which this already doesn't attempt. */
|
|
70
|
+
& .crisp-rte-body ul { list-style-type: disc; padding-inline-start: 36px; }
|
|
71
|
+
& .crisp-rte-body ol { list-style-type: decimal; padding-inline-start: 36px; }
|
|
47
72
|
& .crisp-rte-body li { margin-top: ${tokens_1.contract.space["1"]}; }
|
|
48
73
|
& .crisp-rte-body blockquote { padding-inline-start: ${tokens_1.contract.space["3"]}; border-inline-start: 2px solid ${tokens_1.contract.borderDefault}; color: ${tokens_1.contract.fgMuted}; }
|
|
49
74
|
& .crisp-rte-body code { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; font-size: ${tokens_1.contract.text.md}; padding: 1px ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius.sm}; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent); }
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.savedviewsRecipe = exports.SavedViews = void 0;
|
|
4
|
+
var savedviews_1 = require("./savedviews");
|
|
5
|
+
Object.defineProperty(exports, "SavedViews", { enumerable: true, get: function () { return savedviews_1.SavedViews; } });
|
|
6
|
+
var savedviews_recipe_1 = require("./savedviews.recipe");
|
|
7
|
+
Object.defineProperty(exports, "savedviewsRecipe", { enumerable: true, get: function () { return savedviews_recipe_1.savedviewsRecipe; } });
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SavedViews = SavedViews;
|
|
37
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
const lucide_react_1 = require("lucide-react");
|
|
39
|
+
const React = __importStar(require("react"));
|
|
40
|
+
const menu_1 = require("../../components/menu");
|
|
41
|
+
const select_1 = require("../../components/select");
|
|
42
|
+
const core_1 = require("../../core");
|
|
43
|
+
const savedviews_recipe_1 = require("./savedviews.recipe");
|
|
44
|
+
/**
|
|
45
|
+
* A saved-view switcher — measured from Attio's "All Companies ▾" menu. A
|
|
46
|
+
* chip trigger opens a popover listing every saved view as a `role="option"`
|
|
47
|
+
* row (leading coloured icon, name, a hover-revealed kebab of
|
|
48
|
+
* rename/duplicate/delete), plus a trailing "Create new view" row. The
|
|
49
|
+
* active view is marked `aria-selected`/`aria-current="page"` and mirrored
|
|
50
|
+
* onto the trigger's own icon/label.
|
|
51
|
+
*
|
|
52
|
+
* Keyboard: while the trigger has focus and the panel is open, ↑/↓ move a
|
|
53
|
+
* virtual highlight (`aria-activedescendant`, the same "focus stays on the
|
|
54
|
+
* trigger" model `Select`/`MultiSelect` use without a search field), Enter
|
|
55
|
+
* chooses the highlighted row, Escape closes — deferring to a still-open
|
|
56
|
+
* kebab submenu first (`hasOpenFloatingLayer`), so Escape closes one layer
|
|
57
|
+
* at a time.
|
|
58
|
+
*/
|
|
59
|
+
function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename, onDuplicate, onDelete, onToggleFavorite, createLabel: createLabelProp, renameLabel: renameLabelProp, duplicateLabel: duplicateLabelProp, deleteLabel: deleteLabelProp, favoriteLabel: favoriteLabelProp, viewOptionsLabel: viewOptionsLabelProp, className, "aria-label": ariaLabel, ...rest }) {
|
|
60
|
+
const createLabel = (0, core_1.useCrispMessage)(createLabelProp, "Create new view");
|
|
61
|
+
const renameLabel = (0, core_1.useCrispMessage)(renameLabelProp, "Rename");
|
|
62
|
+
const duplicateLabel = (0, core_1.useCrispMessage)(duplicateLabelProp, "Duplicate");
|
|
63
|
+
const deleteLabel = (0, core_1.useCrispMessage)(deleteLabelProp, "Delete");
|
|
64
|
+
const favoriteLabel = (0, core_1.useCrispMessage)(favoriteLabelProp, "Add to favorites");
|
|
65
|
+
const viewOptionsLabel = (0, core_1.useCrispMessage)(viewOptionsLabelProp, "View options");
|
|
66
|
+
const autoId = React.useId();
|
|
67
|
+
const triggerId = `${autoId}-trigger`;
|
|
68
|
+
// role="combobox" (below) does not take its name from content — unlike a
|
|
69
|
+
// plain button, aria-activedescendant is only valid on a widget role that
|
|
70
|
+
// supports it (Select's/MultiSelect's own !searchable trigger is the same
|
|
71
|
+
// shape). aria-labelledby, resolved recursively, DOES read a referenced
|
|
72
|
+
// node's own text, so pointing it at the visible label span gives the
|
|
73
|
+
// trigger a name equal to what's on screen without a second, redundant
|
|
74
|
+
// aria-label prop — self-referencing (aria-labelledby={triggerId} on the
|
|
75
|
+
// trigger itself) does not work, since the referenced node's own
|
|
76
|
+
// name-from-content restriction still applies to it.
|
|
77
|
+
const triggerLabelId = `${autoId}-trigger-label`;
|
|
78
|
+
const panelId = `${autoId}-panel`;
|
|
79
|
+
const optionId = (i) => `${autoId}-opt-${i}`;
|
|
80
|
+
const [open, setOpen] = React.useState(false);
|
|
81
|
+
const [up, setUp] = React.useState(false);
|
|
82
|
+
const [pos, setPos] = React.useState({});
|
|
83
|
+
const [highlight, setHighlight] = React.useState(-1);
|
|
84
|
+
const rootRef = React.useRef(null);
|
|
85
|
+
const triggerRef = React.useRef(null);
|
|
86
|
+
const panelRef = React.useRef(null);
|
|
87
|
+
const activeView = views.find((v) => v.id === activeId) ?? null;
|
|
88
|
+
// the trailing "Create new view" row is one more enabled index, past every
|
|
89
|
+
// real view
|
|
90
|
+
const createIndex = views.length;
|
|
91
|
+
const openPanel = () => {
|
|
92
|
+
const rect = triggerRef.current?.getBoundingClientRect();
|
|
93
|
+
if (rect) {
|
|
94
|
+
const { style, up: flipUp } = (0, core_1.placeFloating)({
|
|
95
|
+
rect,
|
|
96
|
+
gap: 4,
|
|
97
|
+
flipBelow: 280,
|
|
98
|
+
anchor: triggerRef.current,
|
|
99
|
+
});
|
|
100
|
+
setUp(flipUp);
|
|
101
|
+
setPos(style);
|
|
102
|
+
}
|
|
103
|
+
setHighlight(-1);
|
|
104
|
+
setOpen(true);
|
|
105
|
+
};
|
|
106
|
+
const close = React.useCallback(() => {
|
|
107
|
+
setOpen(false);
|
|
108
|
+
triggerRef.current?.focus({ preventScroll: true });
|
|
109
|
+
}, []);
|
|
110
|
+
const commit = () => {
|
|
111
|
+
if (highlight === createIndex) {
|
|
112
|
+
onCreate();
|
|
113
|
+
close();
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
const v = views[highlight];
|
|
117
|
+
if (v) {
|
|
118
|
+
onSelect(v.id);
|
|
119
|
+
close();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const { onKeyDown: onNavKeys } = (0, select_1.useListboxKeyboard)({
|
|
124
|
+
enabledIndexes: Array.from({ length: views.length + 1 }, (_, i) => i),
|
|
125
|
+
highlight,
|
|
126
|
+
setHighlight,
|
|
127
|
+
commit,
|
|
128
|
+
close,
|
|
129
|
+
dismiss: () => setOpen(false),
|
|
130
|
+
});
|
|
131
|
+
// Registers the portaled panel on the shared floating-layer stack (#339)
|
|
132
|
+
// so a kebab's own Menu (also portaled, also registered) counts as
|
|
133
|
+
// "inside" this layer for outside-click purposes, and so this panel's own
|
|
134
|
+
// Escape can defer to it below.
|
|
135
|
+
React.useEffect(() => {
|
|
136
|
+
if (!open || !panelRef.current)
|
|
137
|
+
return;
|
|
138
|
+
return (0, core_1.registerFloatingLayer)(panelRef.current);
|
|
139
|
+
}, [open]);
|
|
140
|
+
React.useEffect(() => {
|
|
141
|
+
if (!open)
|
|
142
|
+
return;
|
|
143
|
+
const onDown = (e) => {
|
|
144
|
+
const target = e.target;
|
|
145
|
+
if (rootRef.current?.contains(target))
|
|
146
|
+
return;
|
|
147
|
+
if (panelRef.current?.contains(target))
|
|
148
|
+
return;
|
|
149
|
+
if ((0, core_1.isInsideLaterFloatingLayer)(target, panelRef.current))
|
|
150
|
+
return;
|
|
151
|
+
setOpen(false);
|
|
152
|
+
};
|
|
153
|
+
document.addEventListener("pointerdown", onDown);
|
|
154
|
+
return () => document.removeEventListener("pointerdown", onDown);
|
|
155
|
+
}, [open]);
|
|
156
|
+
// Escape defers to a still-open kebab submenu (core/floating-stack.ts):
|
|
157
|
+
// without this, both this panel's listener AND the kebab Menu's own
|
|
158
|
+
// Escape listener fire on the same keydown and both close at once, which
|
|
159
|
+
// is the double-close bug that module's doc comment warns about. One
|
|
160
|
+
// Escape now closes the kebab; a second closes the panel.
|
|
161
|
+
//
|
|
162
|
+
// `hasNestedFloatingLayer`, not `hasOpenFloatingLayer`: this panel
|
|
163
|
+
// registers ITSELF on the stack (below), so a plain "is the stack
|
|
164
|
+
// non-empty" check would always be true the moment this panel is open and
|
|
165
|
+
// Escape would never reach `close()` — `hasNestedFloatingLayer` excludes
|
|
166
|
+
// this panel's own slot and only counts a layer registered after it.
|
|
167
|
+
React.useEffect(() => {
|
|
168
|
+
if (!open)
|
|
169
|
+
return;
|
|
170
|
+
const onKey = (e) => {
|
|
171
|
+
if (e.key !== "Escape")
|
|
172
|
+
return;
|
|
173
|
+
if ((0, core_1.hasNestedFloatingLayer)(panelRef.current))
|
|
174
|
+
return;
|
|
175
|
+
close();
|
|
176
|
+
};
|
|
177
|
+
document.addEventListener("keydown", onKey);
|
|
178
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
179
|
+
}, [open, close]);
|
|
180
|
+
const triggerIcon = activeView ? activeView.icon : icon;
|
|
181
|
+
const triggerLabel = activeView ? activeView.name : label;
|
|
182
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, savedviews_recipe_1.savedviewsRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("button", { ref: triggerRef, id: triggerId, type: "button",
|
|
183
|
+
// combobox, not the bare default "button" role: aria-activedescendant
|
|
184
|
+
// (the virtual-focus highlight, same model Select/MultiSelect use
|
|
185
|
+
// without a search field) is only valid on a widget role that
|
|
186
|
+
// supports it, and combobox's aria-haspopup explicitly allows
|
|
187
|
+
// "dialog" — the value Attio's own trigger measures — alongside the
|
|
188
|
+
// more common "listbox".
|
|
189
|
+
role: "combobox", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": open ? panelId : undefined, "aria-activedescendant": open && highlight >= 0 ? optionId(highlight) : undefined, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerLabelId, "data-open": open ? "" : undefined, className: "crisp-savedviews-trigger", onClick: () => (open ? setOpen(false) : openPanel()), onKeyDown: (e) => {
|
|
190
|
+
if (open)
|
|
191
|
+
onNavKeys(e);
|
|
192
|
+
else if (e.key === "ArrowDown" || e.key === "Enter") {
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
openPanel();
|
|
195
|
+
}
|
|
196
|
+
}, children: [triggerIcon != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-trigger-icon", children: triggerIcon })), (0, jsx_runtime_1.jsx)("span", { id: triggerLabelId, className: "crisp-savedviews-trigger-label", children: triggerLabel }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-savedviews-trigger-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsxs)("div", { ref: panelRef, id: panelId, role: "listbox", "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerLabelId, className: "crisp-savedviews-panel", "data-up": up ? "" : undefined, style: pos, children: [views.map((v, i) => {
|
|
197
|
+
const selected = v.id === activeId;
|
|
198
|
+
return ((0, jsx_runtime_1.jsxs)("div", { id: optionId(i), role: "option", "aria-selected": selected, "aria-current": selected ? "page" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-savedviews-row", onMouseEnter: () => setHighlight(i), onClick: () => {
|
|
199
|
+
onSelect(v.id);
|
|
200
|
+
close();
|
|
201
|
+
}, children: [v.icon != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-icon", children: v.icon })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-label", children: v.name }), v.shared && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-shared", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Users2, { size: 12, strokeWidth: 1.75 }) })), (0, jsx_runtime_1.jsx)("div", { className: "crisp-savedviews-kebab-slot", onClick: (e) => e.stopPropagation(), children: (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-savedviews-kebab", "aria-label": viewOptionsLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreVertical, { size: 14, strokeWidth: 1.75 }) }), children: [onToggleFavorite && ((0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onToggleFavorite(v.id), children: favoriteLabel })), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onRename(v.id), children: renameLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onDuplicate(v.id), children: duplicateLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { danger: true, onSelect: () => onDelete(v.id), children: deleteLabel })] }) })] }, v.id));
|
|
202
|
+
}), (0, jsx_runtime_1.jsxs)("div", { id: optionId(createIndex), role: "option", "aria-selected": false, "data-highlighted": highlight === createIndex ? "" : undefined, className: "crisp-savedviews-row", onMouseEnter: () => setHighlight(createIndex), onClick: () => {
|
|
203
|
+
onCreate();
|
|
204
|
+
close();
|
|
205
|
+
}, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-icon", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 16, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-label", children: createLabel })] })] }) }))] }));
|
|
206
|
+
}
|
|
207
|
+
SavedViews.displayName = "SavedViews";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.savedviewsRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// A saved-view switcher — measured live from Attio's own "All Companies ▾"
|
|
7
|
+
// trigger (app.attio.com/techpartners/companies): a 28px chip opens a
|
|
8
|
+
// popover of 32px `role="option"` rows, each revealing a 20px kebab on
|
|
9
|
+
// hover that opens a second, identical-chrome popover of rename/duplicate/
|
|
10
|
+
// delete actions. The trigger stays un-portaled (`&`-nested); the panel and
|
|
11
|
+
// its rows portal to document.body (#339), so they live in `globalCss`, not
|
|
12
|
+
// nested under `&` — the same split Menu/MultiSelect use and for the same
|
|
13
|
+
// reason: a descendant selector needs real DOM containment, which the
|
|
14
|
+
// portal breaks.
|
|
15
|
+
//
|
|
16
|
+
// Panel chrome (radius 12 / 4px padding / elevation.floating) and row height
|
|
17
|
+
// (32px) are the exact values Menu's own `crisp-menu-content`/`crisp-menu-item`
|
|
18
|
+
// already carry — reused by reference (radius.xl, space["1"], size.menuRow,
|
|
19
|
+
// elevation.floating), not re-measured, since it is the same overlay
|
|
20
|
+
// surface. The panel's measured 308.75×182 size is deliberately NOT
|
|
21
|
+
// hardcoded — it grows with the number of views — so width/height are left
|
|
22
|
+
// to content, with the same 200px floor Menu's own popover uses.
|
|
23
|
+
const config = (0, core_1.defineRecipe)({
|
|
24
|
+
name: "savedviews",
|
|
25
|
+
base: `
|
|
26
|
+
position: relative; display: inline-flex; font-family: ${tokens_1.contract.font.sans};
|
|
27
|
+
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
28
|
+
|
|
29
|
+
/* Trigger fill measured rgb(246,247,247); bgRaised's light value is
|
|
30
|
+
#f4f4f5 (244,244,245) — a 2-unit-per-channel difference, inside
|
|
31
|
+
screenshot/anti-aliasing noise, not a second real colour. Reusing the
|
|
32
|
+
token (Lesson 3 — tokens, not raw px/hex) gets a correct,
|
|
33
|
+
already-established dark-mode fill for free instead of a light-only
|
|
34
|
+
literal; AUTHORING.md Lesson 29 is the case where that shortcut went
|
|
35
|
+
wrong (a flat colour that stayed visible where the reference had
|
|
36
|
+
none) — this is the opposite risk (a token that already flips per
|
|
37
|
+
theme, applied to the closest measured match). */
|
|
38
|
+
& .crisp-savedviews-trigger {
|
|
39
|
+
box-sizing: border-box; display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]};
|
|
40
|
+
height: ${tokens_1.contract.size.row}; margin: 0; padding: ${tokens_1.contract.space["1"]} ${tokens_1.contract.space["1.5"]};
|
|
41
|
+
border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgRaised};
|
|
42
|
+
font-family: inherit; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary};
|
|
43
|
+
cursor: pointer; white-space: nowrap; transition: background-color .12s ease;
|
|
44
|
+
}
|
|
45
|
+
& .crisp-savedviews-trigger:hover,
|
|
46
|
+
& .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, ${tokens_1.contract.bgRaised}); }
|
|
47
|
+
& .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: ${tokens_1.contract.size.icon}; height: ${tokens_1.contract.size.icon}; }
|
|
48
|
+
& .crisp-savedviews-trigger-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
49
|
+
& .crisp-savedviews-trigger-chevron { flex-shrink: 0; color: ${tokens_1.contract.fgMuted}; }
|
|
50
|
+
`,
|
|
51
|
+
globalCss: `
|
|
52
|
+
/* pointer-events: auto (#375) — see Menu/MultiSelect's matching comment:
|
|
53
|
+
a modal Dialog sets pointer-events: none on document.body, and a
|
|
54
|
+
portaled layer outside Zag's knowledge inherits that lock. */
|
|
55
|
+
.crisp-savedviews-panel {
|
|
56
|
+
font-family: ${tokens_1.contract.font.sans}; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
57
|
+
position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: ${tokens_1.contract.space.px};
|
|
58
|
+
min-width: 200px; max-height: 360px; overflow-y: auto; padding: ${tokens_1.contract.space["1"]};
|
|
59
|
+
border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgPopover};
|
|
60
|
+
box-shadow: ${tokens_1.elevation.floating};
|
|
61
|
+
transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease;
|
|
62
|
+
}
|
|
63
|
+
.crisp-savedviews-panel[data-up] { transform-origin: bottom left; }
|
|
64
|
+
.crisp-savedviews-panel { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
|
|
65
|
+
|
|
66
|
+
.crisp-savedviews-row {
|
|
67
|
+
box-sizing: border-box; position: relative; display: flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]};
|
|
68
|
+
height: ${tokens_1.contract.size.menuRow}; flex-shrink: 0; padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]};
|
|
69
|
+
border-radius: ${tokens_1.contract.radius.md}; cursor: pointer; -webkit-user-select: none; user-select: none;
|
|
70
|
+
}
|
|
71
|
+
.crisp-savedviews-row:hover, .crisp-savedviews-row[data-highlighted] { background: ${tokens_1.contract.bgRaised}; }
|
|
72
|
+
.crisp-savedviews-row-icon { flex-shrink: 0; display: inline-flex; width: ${tokens_1.contract.size.icon}; height: ${tokens_1.contract.size.icon}; color: ${tokens_1.contract.fgMuted}; }
|
|
73
|
+
.crisp-savedviews-row-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; }
|
|
74
|
+
.crisp-savedviews-row[aria-selected="true"] .crisp-savedviews-row-label { font-weight: ${tokens_1.contract.weight.semibold}; }
|
|
75
|
+
.crisp-savedviews-row-shared { flex-shrink: 0; display: inline-flex; color: ${tokens_1.contract.fgMuted}; }
|
|
76
|
+
|
|
77
|
+
/* The kebab's own <Menu> is wrapped in a slot div (savedviews.tsx) that
|
|
78
|
+
stops a click from bubbling — through the REACT tree, which a portal
|
|
79
|
+
does not leave, even though the Menu's content is a DOM sibling under
|
|
80
|
+
document.body — into the row's own onClick. The slot, not the kebab
|
|
81
|
+
button itself, is what the row's flex layout pushes to the trailing
|
|
82
|
+
edge. */
|
|
83
|
+
.crisp-savedviews-kebab-slot { flex-shrink: 0; margin-inline-start: auto; }
|
|
84
|
+
/* 20px / 4px padding is a component-intrinsic size (AUTHORING.md §4b —
|
|
85
|
+
"a badge's 20px" is the named example), used once here: raw, not a
|
|
86
|
+
scale step. */
|
|
87
|
+
.crisp-savedviews-kebab {
|
|
88
|
+
box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
|
|
89
|
+
width: 20px; height: 20px; padding: ${tokens_1.contract.space["1"]}; margin: 0;
|
|
90
|
+
border: none; border-radius: ${tokens_1.contract.radius.sm}; background: none; color: ${tokens_1.contract.fgMuted}; cursor: pointer;
|
|
91
|
+
opacity: 0; transition: opacity .1s ease, background-color .12s ease, color .12s ease;
|
|
92
|
+
}
|
|
93
|
+
.crisp-savedviews-row:hover .crisp-savedviews-kebab,
|
|
94
|
+
.crisp-savedviews-row:focus-within .crisp-savedviews-kebab,
|
|
95
|
+
.crisp-savedviews-kebab[data-open] { opacity: 1; }
|
|
96
|
+
.crisp-savedviews-kebab:hover, .crisp-savedviews-kebab[data-open] { background: ${tokens_1.contract.bgSurface}; color: ${tokens_1.contract.fgPrimary}; }
|
|
97
|
+
`,
|
|
98
|
+
variants: {},
|
|
99
|
+
defaultVariants: {},
|
|
100
|
+
compoundVariants: [],
|
|
101
|
+
});
|
|
102
|
+
exports.savedviewsRecipe = (0, core_1.resolveRecipe)(config);
|
|
103
|
+
exports.default = config;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskstableRecipe = exports.TasksTable = void 0;
|
|
4
|
+
var taskstable_1 = require("./taskstable");
|
|
5
|
+
Object.defineProperty(exports, "TasksTable", { enumerable: true, get: function () { return taskstable_1.TasksTable; } });
|
|
6
|
+
var taskstable_recipe_1 = require("./taskstable.recipe");
|
|
7
|
+
Object.defineProperty(exports, "taskstableRecipe", { enumerable: true, get: function () { return taskstable_recipe_1.taskstableRecipe; } });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TasksTable = TasksTable;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const core_1 = require("../../core");
|
|
6
|
+
const listpage_1 = require("../listpage");
|
|
7
|
+
const taskstable_recipe_1 = require("./taskstable.recipe");
|
|
8
|
+
function Cell({ flex, className, children, }) {
|
|
9
|
+
return ((0, jsx_runtime_1.jsx)("span", { className: (0, core_1.cx)("crisp-taskstable-cell", className), style: { flex }, children: children }));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A grouped due-date table pattern — Attio's Tasks page. Composes
|
|
13
|
+
* `ListPage`'s toolbar and group-row chrome (both class-compatible; see
|
|
14
|
+
* taskstable.recipe.ts) with a typed column/cell layer: a title+checkbox
|
|
15
|
+
* row grouped into due-date sections, a due-date cell that goes red past
|
|
16
|
+
* due, and consumer-supplied record/assignee chip slots.
|
|
17
|
+
*/
|
|
18
|
+
function TasksTable({ columns, groups, leading, trailing, emptyLabel: emptyLabelProp, completeLabel: completeLabelProp, incompleteLabel: incompleteLabelProp, className, ...rest }) {
|
|
19
|
+
const crispMessages = (0, core_1.useCrispMessages)();
|
|
20
|
+
const emptyLabel = emptyLabelProp ?? crispMessages["No tasks"] ?? "No tasks";
|
|
21
|
+
const completeLabel = (0, core_1.useCrispMessage)(completeLabelProp, "Mark complete");
|
|
22
|
+
const incompleteLabel = (0, core_1.useCrispMessage)(incompleteLabelProp, "Mark incomplete");
|
|
23
|
+
const flexOf = (key) => columns.find((col) => col.key === key)?.flex ?? 1;
|
|
24
|
+
const total = groups.reduce((s, g) => s + g.rows.length, 0);
|
|
25
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, core_1.cx)((0, taskstable_recipe_1.taskstableRecipe)({ className }), (0, listpage_1.listpageRecipe)()), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-listpage-toolbar", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-listpage-toolbar-lead", children: leading }), trailing != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-listpage-toolbar-trail", children: trailing }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-listpage-body", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-taskstable-colhead", children: columns.map((col) => ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-taskstable-colhead-cell", style: { flex: col.flex ?? 1 }, children: [col.icon, col.label] }, col.key))) }), total === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-taskstable-empty", children: emptyLabel })) : (groups.map((g) => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-listpage-group", children: [g.label, (0, jsx_runtime_1.jsx)("span", { className: "crisp-listpage-group-count", children: g.count })] }), g.rows.map((row) => ((0, jsx_runtime_1.jsx)(listpage_1.ListRow, { children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-taskstable-row", style: { flex: 1 }, children: [(0, jsx_runtime_1.jsxs)(Cell, { flex: flexOf("title"), children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", className: "crisp-taskstable-check", checked: !!row.done, "aria-label": row.done ? incompleteLabel : completeLabel, onChange: () => row.onToggle?.(!row.done) }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-taskstable-title", children: row.title })] }), (0, jsx_runtime_1.jsx)(Cell, { flex: flexOf("due"), children: row.due != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-taskstable-due", "data-overdue": row.due.overdue ? "" : undefined, children: row.due.label })) }), (0, jsx_runtime_1.jsx)(Cell, { flex: flexOf("record"), children: row.record }), (0, jsx_runtime_1.jsx)(Cell, { flex: flexOf("assignee"), children: row.assignee })] }) }, row.id)))] }, g.id))))] })] }));
|
|
26
|
+
}
|
|
27
|
+
TasksTable.displayName = "TasksTable";
|