@crisp-ui-kit/crisp 0.54.2 → 0.54.4
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/board.css +13 -1
- package/dist/board.layered.css +13 -1
- 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 +489 -111
- package/dist/cjs/components/emptystate/emptystate.js +3 -2
- package/dist/cjs/components/emptystate/emptystate.recipe.js +26 -1
- 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/tabletoolbar/tabletoolbar.js +41 -4
- 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/index.js +4 -0
- package/dist/cjs/product/board/board.js +60 -2
- package/dist/cjs/product/board/board.recipe.js +10 -0
- package/dist/cjs/product/comments/comments.js +426 -0
- package/dist/cjs/product/comments/comments.recipe.js +126 -0
- package/dist/cjs/product/comments/index.js +8 -0
- package/dist/cjs/product/headerbar/headerbar.recipe.js +9 -1
- package/dist/cjs/product/listpage/listpage.recipe.js +10 -1
- package/dist/cjs/product/objectsettings/index.js +16 -0
- package/dist/cjs/product/objectsettings/objectsettings.js +365 -0
- package/dist/cjs/product/objectsettings/objectsettings.recipe.js +65 -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/taskstable/taskstable.recipe.js +10 -1
- package/dist/comments.css +80 -0
- package/dist/comments.d.ts +7 -0
- package/dist/comments.layered.css +82 -0
- 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 +488 -111
- 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/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/tabletoolbar/tabletoolbar.js +43 -6
- 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/index.js +4 -0
- package/dist/esm/product/board/board.js +61 -3
- package/dist/esm/product/board/board.recipe.js +10 -0
- package/dist/esm/product/comments/comments.js +388 -0
- package/dist/esm/product/comments/comments.recipe.js +123 -0
- package/dist/esm/product/comments/index.js +2 -0
- package/dist/esm/product/headerbar/headerbar.recipe.js +9 -1
- package/dist/esm/product/listpage/listpage.recipe.js +10 -1
- package/dist/esm/product/objectsettings/index.js +2 -0
- package/dist/esm/product/objectsettings/objectsettings.js +322 -0
- package/dist/esm/product/objectsettings/objectsettings.recipe.js +62 -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/taskstable/taskstable.recipe.js +10 -1
- package/dist/headerbar.css +1 -1
- package/dist/headerbar.layered.css +1 -1
- package/dist/listpage.css +8 -1
- package/dist/listpage.layered.css +8 -1
- package/dist/objectsettings.css +49 -0
- package/dist/objectsettings.d.ts +7 -0
- package/dist/objectsettings.layered.css +51 -0
- 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/styles.css +224 -10
- package/dist/styles.layered.css +224 -10
- package/dist/tabs.css +7 -1
- package/dist/tabs.d.ts +2 -2
- package/dist/tabs.layered.css +7 -1
- package/dist/taskstable.css +8 -1
- package/dist/taskstable.layered.css +8 -1
- package/dist/text.css +9 -0
- package/dist/text.d.ts +7 -0
- package/dist/text.layered.css +11 -0
- 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 +28 -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/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/tabletoolbar/tabletoolbar.d.ts +8 -2
- 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/index.d.ts +4 -0
- package/dist/types/product/board/board.d.ts +20 -1
- package/dist/types/product/comments/comments.d.ts +192 -0
- package/dist/types/product/comments/comments.recipe.d.ts +3 -0
- package/dist/types/product/comments/index.d.ts +2 -0
- package/dist/types/product/objectsettings/index.d.ts +2 -0
- package/dist/types/product/objectsettings/objectsettings.d.ts +261 -0
- package/dist/types/product/objectsettings/objectsettings.recipe.d.ts +3 -0
- package/dist/types/product/recordstable/recordstable.d.ts +24 -2
- package/package.json +21 -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); }
|
|
@@ -26,7 +26,16 @@ const config = (0, core_1.defineRecipe)({
|
|
|
26
26
|
& .crisp-taskstable-colhead-cell { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; min-width: 0; }
|
|
27
27
|
|
|
28
28
|
& .crisp-taskstable-row { display: flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; }
|
|
29
|
-
|
|
29
|
+
/* #642: measured live -- title, due date and assignee name are ALL
|
|
30
|
+
14px/500 on Attio's own Tasks page. Only -title/-due set their own
|
|
31
|
+
font-size (and only -title its own weight), so an unstyled consumer
|
|
32
|
+
slot (a plain PersonChip/RecordChip name span with no font-size of
|
|
33
|
+
its own) fell through to the browser/body default, 16px/400. Set
|
|
34
|
+
the ambient font here so it cascades to any slot content that
|
|
35
|
+
doesn't override it -- -title/-due's own declarations still apply,
|
|
36
|
+
unchanged, they're just now redundant with this baseline rather
|
|
37
|
+
than the only source of it. */
|
|
38
|
+
& .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; }
|
|
30
39
|
|
|
31
40
|
& .crisp-taskstable-check { appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: ${tokens_1.contract.radius.full}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; }
|
|
32
41
|
& .crisp-taskstable-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 30%, transparent); }
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.crisp-comments {
|
|
2
|
+
display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
3
|
+
& * { box-sizing: border-box; }
|
|
4
|
+
& .crisp-comments-head { display: flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; height: 49px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); }
|
|
5
|
+
& .crisp-comments-headbtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
|
|
6
|
+
& .crisp-comments-headbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
7
|
+
& .crisp-comments-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
8
|
+
& .crisp-comments-title { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
9
|
+
& .crisp-comments-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
10
|
+
& .crisp-comments-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
11
|
+
& .crisp-comments-body--pad-top { padding-top: var(--crisp-space-3); }
|
|
12
|
+
/* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line. */
|
|
13
|
+
& .crisp-comments-row { position: relative; display: flex; gap: var(--crisp-space-1-5); }
|
|
14
|
+
& .crisp-comments-row-main { flex: 1; min-width: 0; }
|
|
15
|
+
& .crisp-comments-row-head { display: flex; align-items: baseline; gap: var(--crisp-space-1-5); height: 20px; }
|
|
16
|
+
& .crisp-comments-author { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
|
|
17
|
+
& .crisp-comments-time { font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
|
|
18
|
+
& .crisp-comments-resolved-label { display: flex; align-items: center; gap: var(--crisp-space-1); margin-bottom: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
|
|
19
|
+
& .crisp-comments-body-text { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
|
|
20
|
+
/* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
|
|
21
|
+
& .crisp-comments-row-actions { position: absolute; top: 0; inset-inline-end: 0; display: flex; opacity: 0; pointer-events: none; background: var(--crisp-bg-surface); }
|
|
22
|
+
& .crisp-comments-row:hover .crisp-comments-row-actions, & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
|
|
23
|
+
& .crisp-comments-action { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
|
|
24
|
+
& .crisp-comments-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
25
|
+
& .crisp-comments-action[data-active] { color: var(--crisp-bg-brand-solid); }
|
|
26
|
+
/* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill, padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
|
|
27
|
+
& .crisp-comments-reactions { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-1-5); }
|
|
28
|
+
& .crisp-comments-reaction { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 28px; padding: 4px 8px 4px 6px; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: 12px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; }
|
|
29
|
+
& .crisp-comments-reaction[data-reacted] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
|
|
30
|
+
& .crisp-comments-reaction-add { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: var(--crisp-radius-md); background: transparent; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; opacity: 0; transition: opacity .12s, background-color .12s; }
|
|
31
|
+
& .crisp-comments-row:hover .crisp-comments-reaction-add { opacity: 1; }
|
|
32
|
+
& .crisp-comments-reaction-add:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
33
|
+
/* Reply-in-thread view -- an indented block of replies below the root. */
|
|
34
|
+
& .crisp-comments-replies { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); padding-inline-start: var(--crisp-space-4); border-inline-start: 1px solid var(--crisp-border-subtle); }
|
|
35
|
+
& .crisp-comments-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--crisp-space-1); padding: var(--crisp-space-6) var(--crisp-space-4); text-align: center; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-size: var(--crisp-text-lg); }
|
|
36
|
+
/* Composer -- measured: 14px/20px body text, 16px gap above the reply/mention icon row, Comment button 84x32 padding 6px 10px radius 9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4 disabled / 1 enabled (no color/background change between the two). */
|
|
37
|
+
& .crisp-comments-composer { flex-shrink: 0; padding: var(--crisp-space-3) var(--crisp-space-4) var(--crisp-space-4); border-top: 1px solid var(--crisp-border-subtle); }
|
|
38
|
+
& .crisp-comments-field { min-height: 20px; max-height: 120px; overflow-y: auto; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); outline: none; white-space: pre-wrap; word-break: break-word; }
|
|
39
|
+
& .crisp-comments-field:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: 2px; }
|
|
40
|
+
& .crisp-comments-field:empty::before { content: attr(data-placeholder); color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
|
|
41
|
+
& .crisp-comments-composer-foot { display: flex; align-items: center; justify-content: space-between; margin-top: var(--crisp-space-4); }
|
|
42
|
+
& .crisp-comments-composer-tools { display: flex; align-items: center; gap: var(--crisp-space-1); }
|
|
43
|
+
& .crisp-comments-tool { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); font-family: inherit; cursor: pointer; }
|
|
44
|
+
& .crisp-comments-tool:hover { color: var(--crisp-fg-primary); }
|
|
45
|
+
/* Same fill/shadow as Button's own intent="primary" (crisp/button.recipe.ts) --
|
|
46
|
+
confirmed byte-identical against the live "Comment" button's computed box-shadow, so this reuses that literal rather than re-deriving it. 32px/radius.lg/6px-10px padding has no matching Button `size` yet (24/28/40 are the three that exist), so this stays hand-rolled instead of stretching one of those. */
|
|
47
|
+
& .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-brand-solid); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-on-brand); cursor: pointer; opacity: 1; transition: opacity .12s, background-color .12s; }
|
|
48
|
+
& .crisp-comments-post:hover:not(:disabled) { background: var(--crisp-bg-brand-solid-pressed); }
|
|
49
|
+
& .crisp-comments-post:disabled { opacity: .4; cursor: default; }
|
|
50
|
+
/* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding, elevation.floating -- a plain reuse of crisp's own Menu, so no rules
|
|
51
|
+
live here; kept as a comment for the record. */
|
|
52
|
+
|
|
53
|
+
/* An inserted "@mention" chip in the composer -- same shape as
|
|
54
|
+
RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
|
|
55
|
+
& .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
|
|
56
|
+
|
|
57
|
+
/* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact chrome wasn't independently pixel-measured this pass (screenshot
|
|
58
|
+
capture broke mid-session), so this reuses Menu's already-measured floating-popover shell rather than a new value. */
|
|
59
|
+
& .crisp-comments-reactionpicker { width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); 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); }
|
|
60
|
+
& .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
|
|
61
|
+
& .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
|
|
62
|
+
& .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
|
|
63
|
+
& .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
|
|
64
|
+
& .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
|
|
65
|
+
/* Row badge (CommentCountBadge) -- lives in its own class family so the grid lanes can drop it into a cell without pulling in the panel's
|
|
66
|
+
CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
|
|
67
|
+
icon + 12px count text, both at 55% ink, replacing the dot. */
|
|
68
|
+
& .crisp-comments-badge { display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
|
|
69
|
+
& .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
|
|
70
|
+
& .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
|
|
71
|
+
[dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@media (prefers-reduced-motion: reduce) {
|
|
75
|
+
.crisp-comments, .crisp-comments * {
|
|
76
|
+
animation-duration: 0.01ms !important;
|
|
77
|
+
animation-iteration-count: 1 !important;
|
|
78
|
+
transition-duration: 0.01ms !important;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
@layer crisp-components {
|
|
2
|
+
.crisp-comments {
|
|
3
|
+
display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
4
|
+
& * { box-sizing: border-box; }
|
|
5
|
+
& .crisp-comments-head { display: flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; height: 49px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); }
|
|
6
|
+
& .crisp-comments-headbtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
|
|
7
|
+
& .crisp-comments-headbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
8
|
+
& .crisp-comments-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
9
|
+
& .crisp-comments-title { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
10
|
+
& .crisp-comments-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
11
|
+
& .crisp-comments-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
12
|
+
& .crisp-comments-body--pad-top { padding-top: var(--crisp-space-3); }
|
|
13
|
+
/* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line. */
|
|
14
|
+
& .crisp-comments-row { position: relative; display: flex; gap: var(--crisp-space-1-5); }
|
|
15
|
+
& .crisp-comments-row-main { flex: 1; min-width: 0; }
|
|
16
|
+
& .crisp-comments-row-head { display: flex; align-items: baseline; gap: var(--crisp-space-1-5); height: 20px; }
|
|
17
|
+
& .crisp-comments-author { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
|
|
18
|
+
& .crisp-comments-time { font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
|
|
19
|
+
& .crisp-comments-resolved-label { display: flex; align-items: center; gap: var(--crisp-space-1); margin-bottom: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
|
|
20
|
+
& .crisp-comments-body-text { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
|
|
21
|
+
/* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
|
|
22
|
+
& .crisp-comments-row-actions { position: absolute; top: 0; inset-inline-end: 0; display: flex; opacity: 0; pointer-events: none; background: var(--crisp-bg-surface); }
|
|
23
|
+
& .crisp-comments-row:hover .crisp-comments-row-actions, & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
|
|
24
|
+
& .crisp-comments-action { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
|
|
25
|
+
& .crisp-comments-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
26
|
+
& .crisp-comments-action[data-active] { color: var(--crisp-bg-brand-solid); }
|
|
27
|
+
/* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill, padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
|
|
28
|
+
& .crisp-comments-reactions { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-1-5); }
|
|
29
|
+
& .crisp-comments-reaction { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 28px; padding: 4px 8px 4px 6px; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: 12px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; }
|
|
30
|
+
& .crisp-comments-reaction[data-reacted] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
|
|
31
|
+
& .crisp-comments-reaction-add { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: var(--crisp-radius-md); background: transparent; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; opacity: 0; transition: opacity .12s, background-color .12s; }
|
|
32
|
+
& .crisp-comments-row:hover .crisp-comments-reaction-add { opacity: 1; }
|
|
33
|
+
& .crisp-comments-reaction-add:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
34
|
+
/* Reply-in-thread view -- an indented block of replies below the root. */
|
|
35
|
+
& .crisp-comments-replies { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); padding-inline-start: var(--crisp-space-4); border-inline-start: 1px solid var(--crisp-border-subtle); }
|
|
36
|
+
& .crisp-comments-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--crisp-space-1); padding: var(--crisp-space-6) var(--crisp-space-4); text-align: center; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-size: var(--crisp-text-lg); }
|
|
37
|
+
/* Composer -- measured: 14px/20px body text, 16px gap above the reply/mention icon row, Comment button 84x32 padding 6px 10px radius 9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4 disabled / 1 enabled (no color/background change between the two). */
|
|
38
|
+
& .crisp-comments-composer { flex-shrink: 0; padding: var(--crisp-space-3) var(--crisp-space-4) var(--crisp-space-4); border-top: 1px solid var(--crisp-border-subtle); }
|
|
39
|
+
& .crisp-comments-field { min-height: 20px; max-height: 120px; overflow-y: auto; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); outline: none; white-space: pre-wrap; word-break: break-word; }
|
|
40
|
+
& .crisp-comments-field:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: 2px; }
|
|
41
|
+
& .crisp-comments-field:empty::before { content: attr(data-placeholder); color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
|
|
42
|
+
& .crisp-comments-composer-foot { display: flex; align-items: center; justify-content: space-between; margin-top: var(--crisp-space-4); }
|
|
43
|
+
& .crisp-comments-composer-tools { display: flex; align-items: center; gap: var(--crisp-space-1); }
|
|
44
|
+
& .crisp-comments-tool { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); font-family: inherit; cursor: pointer; }
|
|
45
|
+
& .crisp-comments-tool:hover { color: var(--crisp-fg-primary); }
|
|
46
|
+
/* Same fill/shadow as Button's own intent="primary" (crisp/button.recipe.ts) --
|
|
47
|
+
confirmed byte-identical against the live "Comment" button's computed box-shadow, so this reuses that literal rather than re-deriving it. 32px/radius.lg/6px-10px padding has no matching Button `size` yet (24/28/40 are the three that exist), so this stays hand-rolled instead of stretching one of those. */
|
|
48
|
+
& .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-brand-solid); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-on-brand); cursor: pointer; opacity: 1; transition: opacity .12s, background-color .12s; }
|
|
49
|
+
& .crisp-comments-post:hover:not(:disabled) { background: var(--crisp-bg-brand-solid-pressed); }
|
|
50
|
+
& .crisp-comments-post:disabled { opacity: .4; cursor: default; }
|
|
51
|
+
/* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding, elevation.floating -- a plain reuse of crisp's own Menu, so no rules
|
|
52
|
+
live here; kept as a comment for the record. */
|
|
53
|
+
|
|
54
|
+
/* An inserted "@mention" chip in the composer -- same shape as
|
|
55
|
+
RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
|
|
56
|
+
& .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
|
|
57
|
+
|
|
58
|
+
/* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact chrome wasn't independently pixel-measured this pass (screenshot
|
|
59
|
+
capture broke mid-session), so this reuses Menu's already-measured floating-popover shell rather than a new value. */
|
|
60
|
+
& .crisp-comments-reactionpicker { width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); 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); }
|
|
61
|
+
& .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
|
|
62
|
+
& .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
|
|
63
|
+
& .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
|
|
64
|
+
& .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
|
|
65
|
+
& .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
|
|
66
|
+
/* Row badge (CommentCountBadge) -- lives in its own class family so the grid lanes can drop it into a cell without pulling in the panel's
|
|
67
|
+
CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
|
|
68
|
+
icon + 12px count text, both at 55% ink, replacing the dot. */
|
|
69
|
+
& .crisp-comments-badge { display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
|
|
70
|
+
& .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
|
|
71
|
+
& .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
|
|
72
|
+
[dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@media (prefers-reduced-motion: reduce) {
|
|
76
|
+
.crisp-comments, .crisp-comments * {
|
|
77
|
+
animation-duration: 0.01ms !important;
|
|
78
|
+
animation-iteration-count: 1 !important;
|
|
79
|
+
transition-duration: 0.01ms !important;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
package/dist/emptystate.css
CHANGED
|
@@ -3,7 +3,16 @@
|
|
|
3
3
|
& .crisp-emptystate-icon { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; margin-bottom: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: color-mix(in srgb, var(--crisp-fg-primary) 5%, transparent); color: var(--crisp-fg-muted); }
|
|
4
4
|
& .crisp-emptystate-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
|
|
5
5
|
& .crisp-emptystate-desc { max-width: 320px; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
6
|
-
|
|
6
|
+
/* #603: measured 8px gap between "Import CSV"/"Connect data source" -- inline-flex so one action (the common case) or several lay out the same way, wrapping if the container is narrow. */
|
|
7
|
+
& .crisp-emptystate-action { display: inline-flex; align-items: center; flex-wrap: wrap; justify-content: center; gap: var(--crisp-space-2); margin-top: var(--crisp-space-2); }
|
|
8
|
+
/* #603: Attio's zero-records "Learn more" card -- a label above a
|
|
9
|
+
clickable icon+title tile. Left/start-aligned, unlike the rest of
|
|
10
|
+
this centred panel, so it overrides the base text-align. */
|
|
11
|
+
& .crisp-emptystate-learnmore { display: flex; flex-direction: column; align-items: stretch; gap: var(--crisp-space-2); margin-top: var(--crisp-space-6); text-align: start; }
|
|
12
|
+
& .crisp-emptystate-learnmore-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
|
|
13
|
+
& .crisp-emptystate-learnmore-card { display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3) var(--crisp-space-2) var(--crisp-space-2); margin: 0; border: 1px solid var(--crisp-border-subtle); border-radius: 18px; background: var(--crisp-bg-surface); font: inherit; text-align: start; text-decoration: none; color: inherit; cursor: pointer; }
|
|
14
|
+
& .crisp-emptystate-learnmore-icon { display: inline-flex; align-items: center; justify-content: center; width: 56px; height: 56px; flex-shrink: 0; border-radius: var(--crisp-radius-xl); background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); color: var(--crisp-fg-muted); }
|
|
15
|
+
& .crisp-emptystate-learnmore-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
7
16
|
}
|
|
8
17
|
.crisp-emptystate--variant_default {
|
|
9
18
|
|
|
@@ -18,3 +27,8 @@
|
|
|
18
27
|
& .crisp-emptystate-desc { max-width: 266px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
|
|
19
28
|
& .crisp-emptystate-action { margin-top: var(--crisp-space-2); }
|
|
20
29
|
}
|
|
30
|
+
.crisp-emptystate--variant_records {
|
|
31
|
+
& .crisp-emptystate-icon { width: 138px; height: 138px; margin-bottom: var(--crisp-space-2); border-radius: 0; color: color-mix(in srgb, var(--crisp-fg-primary) 22%, transparent); background: radial-gradient(circle, color-mix(in srgb, var(--crisp-fg-primary) 12%, transparent) 1.2px, transparent 1.2px) 0 0 / 16px 16px; }
|
|
32
|
+
& .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
|
|
33
|
+
& .crisp-emptystate-desc { max-width: 266px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
|
|
34
|
+
}
|
package/dist/emptystate.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RecipeFn } from "../core/types";
|
|
2
2
|
|
|
3
3
|
export interface EmptystateVariants {
|
|
4
|
-
variant?: "default" | "illustrated" | "page";
|
|
4
|
+
variant?: "default" | "illustrated" | "page" | "records";
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export declare const emptystateRecipe: RecipeFn<{ variant: { default: string; illustrated: string; page: string } }>;
|
|
7
|
+
export declare const emptystateRecipe: RecipeFn<{ variant: { default: string; illustrated: string; page: string; records: string } }>;
|
|
@@ -4,7 +4,16 @@
|
|
|
4
4
|
& .crisp-emptystate-icon { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; margin-bottom: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: color-mix(in srgb, var(--crisp-fg-primary) 5%, transparent); color: var(--crisp-fg-muted); }
|
|
5
5
|
& .crisp-emptystate-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
|
|
6
6
|
& .crisp-emptystate-desc { max-width: 320px; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
7
|
-
|
|
7
|
+
/* #603: measured 8px gap between "Import CSV"/"Connect data source" -- inline-flex so one action (the common case) or several lay out the same way, wrapping if the container is narrow. */
|
|
8
|
+
& .crisp-emptystate-action { display: inline-flex; align-items: center; flex-wrap: wrap; justify-content: center; gap: var(--crisp-space-2); margin-top: var(--crisp-space-2); }
|
|
9
|
+
/* #603: Attio's zero-records "Learn more" card -- a label above a
|
|
10
|
+
clickable icon+title tile. Left/start-aligned, unlike the rest of
|
|
11
|
+
this centred panel, so it overrides the base text-align. */
|
|
12
|
+
& .crisp-emptystate-learnmore { display: flex; flex-direction: column; align-items: stretch; gap: var(--crisp-space-2); margin-top: var(--crisp-space-6); text-align: start; }
|
|
13
|
+
& .crisp-emptystate-learnmore-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
|
|
14
|
+
& .crisp-emptystate-learnmore-card { display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3) var(--crisp-space-2) var(--crisp-space-2); margin: 0; border: 1px solid var(--crisp-border-subtle); border-radius: 18px; background: var(--crisp-bg-surface); font: inherit; text-align: start; text-decoration: none; color: inherit; cursor: pointer; }
|
|
15
|
+
& .crisp-emptystate-learnmore-icon { display: inline-flex; align-items: center; justify-content: center; width: 56px; height: 56px; flex-shrink: 0; border-radius: var(--crisp-radius-xl); background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); color: var(--crisp-fg-muted); }
|
|
16
|
+
& .crisp-emptystate-learnmore-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
8
17
|
}
|
|
9
18
|
.crisp-emptystate--variant_default {
|
|
10
19
|
|
|
@@ -19,4 +28,9 @@
|
|
|
19
28
|
& .crisp-emptystate-desc { max-width: 266px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
|
|
20
29
|
& .crisp-emptystate-action { margin-top: var(--crisp-space-2); }
|
|
21
30
|
}
|
|
31
|
+
.crisp-emptystate--variant_records {
|
|
32
|
+
& .crisp-emptystate-icon { width: 138px; height: 138px; margin-bottom: var(--crisp-space-2); border-radius: 0; color: color-mix(in srgb, var(--crisp-fg-primary) 22%, transparent); background: radial-gradient(circle, color-mix(in srgb, var(--crisp-fg-primary) 12%, transparent) 1.2px, transparent 1.2px) 0 0 / 16px 16px; }
|
|
33
|
+
& .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
|
|
34
|
+
& .crisp-emptystate-desc { max-width: 266px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
|
|
35
|
+
}
|
|
22
36
|
}
|
|
@@ -16,7 +16,8 @@ import { chipRecipe } from "./chip.recipe.js";
|
|
|
16
16
|
* that surface can be measured; don't guess the icon, its hit target, or its
|
|
17
17
|
* spacing from the label.
|
|
18
18
|
*/
|
|
19
|
-
export const Chip = React.forwardRef(({ tone, className, asChild, children, ...props }, ref) => {
|
|
20
|
-
|
|
19
|
+
export const Chip = React.forwardRef(({ tone, truncate, className, asChild, children, ...props }, ref) => {
|
|
20
|
+
const content = truncate && !asChild && typeof children === "string" ? (_jsx("span", { className: "crisp-chip-label", children: children })) : (children);
|
|
21
|
+
return (_jsx(Primitive.span, { ref: ref, asChild: asChild, className: chipRecipe({ tone, truncate, className }), ...props, children: content }));
|
|
21
22
|
});
|
|
22
23
|
Chip.displayName = "Chip";
|
|
@@ -17,7 +17,16 @@ const spec = defineSpec({
|
|
|
17
17
|
base: {
|
|
18
18
|
display: "inline-flex",
|
|
19
19
|
alignItems: "center",
|
|
20
|
+
// Measured on Attio's own multi-child chip (Tasks page, "Assigned to"
|
|
21
|
+
// column: a 16px avatar + name in a neutral chip) -- the 3px gap between
|
|
22
|
+
// them. A single plain-text child (the only case before PersonChip) is
|
|
23
|
+
// unaffected, since `gap` does nothing with one flex child.
|
|
24
|
+
gap: "3px",
|
|
20
25
|
height: c.size.chip,
|
|
26
|
+
// Lets the chip shrink below its content width inside a flex/grid cell
|
|
27
|
+
// -- `inline-flex` items don't by default -- which `truncate` below
|
|
28
|
+
// depends on and any consumer constraining a Chip's width already needs.
|
|
29
|
+
minWidth: 0,
|
|
21
30
|
paddingInline: c.space["1"],
|
|
22
31
|
borderRadius: c.radius.chip,
|
|
23
32
|
fontSize: c.text.lg,
|
|
@@ -26,6 +35,33 @@ const spec = defineSpec({
|
|
|
26
35
|
whiteSpace: "nowrap",
|
|
27
36
|
},
|
|
28
37
|
variants: {
|
|
38
|
+
truncate: {
|
|
39
|
+
// Ellipsis + `min-width: 0`, the same convention crisp's own
|
|
40
|
+
// `.crisp-datatable-entity-name`/`.crisp-datatable-th-label` already
|
|
41
|
+
// use for cramped text -- there is no single-overflowing-chip pattern
|
|
42
|
+
// to measure from Attio itself (its multi-value fields show a "+N"
|
|
43
|
+
// badge instead of truncating one chip's own text: Categories,
|
|
44
|
+
// Companies). Targets `.crisp-chip-label`, the inner span `Chip`
|
|
45
|
+
// wraps its string children in when `truncate` is set, not the root
|
|
46
|
+
// -- the root stays a flex row so a leading icon/avatar never shrinks
|
|
47
|
+
// alongside the text.
|
|
48
|
+
true: {
|
|
49
|
+
decls: {},
|
|
50
|
+
parts: [
|
|
51
|
+
{
|
|
52
|
+
selector: "& .crisp-chip-label",
|
|
53
|
+
decls: {
|
|
54
|
+
minWidth: 0,
|
|
55
|
+
overflow: "hidden",
|
|
56
|
+
textOverflow: "ellipsis",
|
|
57
|
+
whiteSpace: "nowrap",
|
|
58
|
+
display: "block",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
false: {},
|
|
64
|
+
},
|
|
29
65
|
tone: {
|
|
30
66
|
neutral: {
|
|
31
67
|
background: soft(c.fgPrimary, 6),
|
|
@@ -69,7 +105,7 @@ const spec = defineSpec({
|
|
|
69
105
|
},
|
|
70
106
|
},
|
|
71
107
|
},
|
|
72
|
-
defaultVariants: { tone: "neutral" },
|
|
108
|
+
defaultVariants: { tone: "neutral", truncate: false },
|
|
73
109
|
});
|
|
74
110
|
const config = specToRecipe(spec);
|
|
75
111
|
export const chipRecipe = Object.assign(resolveRecipe(config), { config });
|