@crisp-ui-kit/crisp 0.52.0 → 0.52.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/datatable/datatable.js +98 -24
- package/dist/cjs/product/recordpage/recordpage.js +86 -7
- package/dist/cjs/product/recordpage/recordpage.recipe.js +7 -0
- package/dist/esm/components/datatable/datatable.js +98 -24
- package/dist/esm/product/recordpage/recordpage.js +86 -7
- package/dist/esm/product/recordpage/recordpage.recipe.js +7 -0
- package/dist/recordpage.css +18 -1
- package/dist/recordpage.layered.css +18 -1
- package/dist/styles.css +18 -1
- package/dist/styles.layered.css +18 -1
- package/dist/types/components/datatable/datatable.d.ts +11 -10
- package/package.json +1 -1
|
@@ -254,12 +254,61 @@ const CREATE_REFERENCE_VALUE = "__crisp_create_reference__";
|
|
|
254
254
|
function dataReferenceOptionLabel(o) {
|
|
255
255
|
return ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-refoption", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", className: "crisp-datatable-refoption-avatar", children: o.avatar ? ((0, jsx_runtime_1.jsx)("img", { src: o.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.CircleUserRound, { size: 12, strokeWidth: 1.75, "aria-hidden": "true" })) }), o.label] }));
|
|
256
256
|
}
|
|
257
|
+
/** `Select` owns its own closed-by-default `open` state with no prop or
|
|
258
|
+
* imperative way to start it open (see select.tsx) -- the only thing that
|
|
259
|
+
* opens it is a real click on its trigger button. Attio's own picker opens
|
|
260
|
+
* the instant a `select`/`reference` cell enters edit mode (measured live
|
|
261
|
+
* against a GRC test company, 2026-09-24), not after a second click on an
|
|
262
|
+
* already-mounted, still-closed trigger, so drive that click ourselves.
|
|
263
|
+
* Guarded against firing twice (StrictMode double-invokes effects) --
|
|
264
|
+
* `Select`'s own trigger toggles, so a second click would close what the
|
|
265
|
+
* first one opened. */
|
|
266
|
+
function useAutoOpenSelect() {
|
|
267
|
+
const ref = React.useRef(null);
|
|
268
|
+
const opened = React.useRef(false);
|
|
269
|
+
React.useEffect(() => {
|
|
270
|
+
if (opened.current)
|
|
271
|
+
return;
|
|
272
|
+
opened.current = true;
|
|
273
|
+
ref.current
|
|
274
|
+
?.querySelector(".crisp-select-trigger")
|
|
275
|
+
?.click();
|
|
276
|
+
}, []);
|
|
277
|
+
return ref;
|
|
278
|
+
}
|
|
279
|
+
/** `select`/`reference` open crisp's own `Select`, which closes its own
|
|
280
|
+
* popover on Escape (see select.tsx's "document-level fallback for Escape")
|
|
281
|
+
* but has no callback telling the CELL to leave editing mode -- and, same
|
|
282
|
+
* problem that fallback exists for, DOM focus doesn't reliably end up
|
|
283
|
+
* inside the newly-opened popover, so a `TableCell`-level `onKeyDown` can't
|
|
284
|
+
* be trusted to see the key at all (#453). Mirror Select's own fix for
|
|
285
|
+
* that: a raw `document` listener, not a focus-dependent React one. */
|
|
286
|
+
function useEscapeExitsEditing(onCancel) {
|
|
287
|
+
React.useEffect(() => {
|
|
288
|
+
const onKey = (e) => {
|
|
289
|
+
if (e.key === "Escape")
|
|
290
|
+
onCancel();
|
|
291
|
+
};
|
|
292
|
+
document.addEventListener("keydown", onKey);
|
|
293
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
294
|
+
}, [onCancel]);
|
|
295
|
+
}
|
|
296
|
+
/** `editor: "select"`'s picker, its own component (not inline in the
|
|
297
|
+
* row-render loop, like `DataReferenceEditor` below) because it needs the
|
|
298
|
+
* open-on-mount and escape-exits-editing effects above (#453). */
|
|
299
|
+
function DataSelectEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
300
|
+
const ref = useAutoOpenSelect();
|
|
301
|
+
useEscapeExitsEditing(onCancel);
|
|
302
|
+
return ((0, jsx_runtime_1.jsx)("span", { ref: ref, style: { display: "contents" }, children: (0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", "aria-label": `Edit ${col.key}`, value: asText(value) || undefined, disabled: isPending, options: col.options ?? [], onValueChange: onCommit }) }));
|
|
303
|
+
}
|
|
257
304
|
/** `editor: "reference"`'s picker, its own component (not inline in the
|
|
258
305
|
* row-render loop) because it needs its own `useState` for the search query
|
|
259
306
|
* -- one instance per cell, same reasoning `select`/`switch` don't need a
|
|
260
307
|
* component of their own but this does. */
|
|
261
|
-
function DataReferenceEditor({ col, value, isPending, onCommit, }) {
|
|
308
|
+
function DataReferenceEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
262
309
|
const [query, setQuery] = React.useState("");
|
|
310
|
+
const ref = useAutoOpenSelect();
|
|
311
|
+
useEscapeExitsEditing(onCancel);
|
|
263
312
|
const options = (col.refOptions ?? []).map((o) => ({
|
|
264
313
|
value: o.value,
|
|
265
314
|
label: dataReferenceOptionLabel(o),
|
|
@@ -274,12 +323,12 @@ function DataReferenceEditor({ col, value, isPending, onCommit, }) {
|
|
|
274
323
|
keywords: query,
|
|
275
324
|
});
|
|
276
325
|
}
|
|
277
|
-
return ((0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", searchable: true, searchPlaceholder: col.refSearchPlaceholder, onSearchChange: setQuery, "aria-label": `Edit ${col.key}`, value: asText(value) || undefined, disabled: isPending, options: options, onValueChange: (v) => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
326
|
+
return ((0, jsx_runtime_1.jsx)("span", { ref: ref, style: { display: "contents" }, children: (0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", searchable: true, searchPlaceholder: col.refSearchPlaceholder, onSearchChange: setQuery, "aria-label": `Edit ${col.key}`, value: asText(value) || undefined, disabled: isPending, options: options, onValueChange: (v) => {
|
|
327
|
+
if (v === CREATE_REFERENCE_VALUE)
|
|
328
|
+
col.onCreateReference?.(query);
|
|
329
|
+
else
|
|
330
|
+
onCommit(v);
|
|
331
|
+
} }) }));
|
|
283
332
|
}
|
|
284
333
|
function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel = "Select all rows", selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement = (count) => count === 0 ? "Selection cleared" : `${count} selected`, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder = "Search…", searchLabel = "Search table", onEdit, stickyFirst = true, stickyLast = false, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel = "New record", empty = "No results.", showCount = true, countLabel = (shown, total) => shown === total
|
|
285
334
|
? `${total} ${total === 1 ? "row" : "rows"}`
|
|
@@ -502,10 +551,11 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
502
551
|
* throughout, and the browser's own picker already
|
|
503
552
|
* carries both a calendar and a time control.
|
|
504
553
|
* text/entity a plain text field
|
|
505
|
-
* `
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
554
|
+
* `editor: "select"`/`"reference"` open their own picker instead of this
|
|
555
|
+
* native-input family, but through the SAME double-click gate (#453) --
|
|
556
|
+
* see the row-render loop's `isEditing` branch for those, this function
|
|
557
|
+
* only covers the native-input-backed types. `editor: "switch"` is the
|
|
558
|
+
* one editor with no double-click gate at all (always live).
|
|
509
559
|
*/
|
|
510
560
|
function editorFor(col) {
|
|
511
561
|
switch (col.type) {
|
|
@@ -559,11 +609,12 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
559
609
|
return;
|
|
560
610
|
}
|
|
561
611
|
else if (e.key === "Enter") {
|
|
562
|
-
//
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
|
|
612
|
+
// #453: `switch` has no separate editing mode to enter -- it's
|
|
613
|
+
// already its own live widget (#385); Enter there is a no-op, same
|
|
614
|
+
// as any other non-editable cell that isn't wired to onRowClick.
|
|
615
|
+
// `select`/`reference` DO have one now (like every other editor),
|
|
616
|
+
// so Enter opens them too.
|
|
617
|
+
if (columns[c]?.editable && columns[c]?.editor !== "switch") {
|
|
567
618
|
e.preventDefault();
|
|
568
619
|
openEditing(selCell.id, selCell.key);
|
|
569
620
|
}
|
|
@@ -794,14 +845,37 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
794
845
|
const errorHere = editError?.id === row.id && editError?.key === col.key
|
|
795
846
|
? editError.message
|
|
796
847
|
: null;
|
|
797
|
-
//
|
|
798
|
-
// live widget --
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
|
|
804
|
-
|
|
848
|
+
// #453: a `switch` editable column IS always its own
|
|
849
|
+
// live widget -- flipping the toggle IS the commit
|
|
850
|
+
// gesture, there's no meaningfully different "rest"
|
|
851
|
+
// display for a boolean. `select`/`reference` are
|
|
852
|
+
// NOT this: at rest they must show the column's own
|
|
853
|
+
// render/format like every other editable type
|
|
854
|
+
// (#412 empty handling included), and only open on
|
|
855
|
+
// double-click (#411) -- this branch previously
|
|
856
|
+
// rendered them unconditionally, replacing every
|
|
857
|
+
// row's own render with a picker trigger, which is
|
|
858
|
+
// the regression #453 reported (also #432/#449,
|
|
859
|
+
// which never had a "rest" state of their own to get
|
|
860
|
+
// right). See the `isEditing` block below for their
|
|
861
|
+
// actual editor.
|
|
862
|
+
if (col.editable && col.editor === "switch") {
|
|
863
|
+
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-edit-picker", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { "aria-label": `Edit ${col.key}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commitEdit(row.id, col.key, String(checked)) }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
864
|
+
}
|
|
865
|
+
if (isEditing &&
|
|
866
|
+
(col.editor === "select" || col.editor === "reference")) {
|
|
867
|
+
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined,
|
|
868
|
+
// Same as the plain text editor's own cell:
|
|
869
|
+
// opening/using the picker must not also fire
|
|
870
|
+
// the row's onRowClick. Escape is handled by
|
|
871
|
+
// `DataSelectEditor`/`DataReferenceEditor`
|
|
872
|
+
// themselves (a document-level listener, not a
|
|
873
|
+
// `TableCell`-level `onKeyDown`) -- opening the
|
|
874
|
+
// picker moves DOM focus into a floating,
|
|
875
|
+
// portaled popover, so a handler here can't
|
|
876
|
+
// rely on the key event ever bubbling through
|
|
877
|
+
// this element (#453).
|
|
878
|
+
onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-edit-picker", children: [col.editor === "select" ? ((0, jsx_runtime_1.jsx)(DataSelectEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })) : ((0, jsx_runtime_1.jsx)(DataReferenceEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
805
879
|
}
|
|
806
880
|
if (isEditing) {
|
|
807
881
|
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), ...sticky, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-datatable-edit",
|
|
@@ -125,6 +125,53 @@ exports.recordPageLabels = {
|
|
|
125
125
|
recordCreated: (name) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("b", { children: name }), " record was created"] })),
|
|
126
126
|
noEmailsWith: (name) => (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["No emails logged with ", name, " yet."] }),
|
|
127
127
|
};
|
|
128
|
+
/** `Select` owns its own closed-by-default `open` state with no prop or
|
|
129
|
+
* imperative way to start it open -- the only thing that opens it is a real
|
|
130
|
+
* click on its trigger button. Mounting one on click (rather than rendering
|
|
131
|
+
* it already open) leaves the reader with a second click to make before
|
|
132
|
+
* they can pick anything, so drive that click ourselves the moment it
|
|
133
|
+
* mounts. Mirrors `DataTable`'s own `useAutoOpenSelect` (#453) -- kept as
|
|
134
|
+
* its own copy here rather than a shared import, the same "duplicated per
|
|
135
|
+
* product surface" choice already made for `EditableOption`/
|
|
136
|
+
* `editableInputType` above. Guarded against firing twice (StrictMode
|
|
137
|
+
* double-invokes effects): `Select`'s own trigger toggles, so a second
|
|
138
|
+
* click would close what the first one opened. */
|
|
139
|
+
function useAutoOpenSelect() {
|
|
140
|
+
const ref = React.useRef(null);
|
|
141
|
+
const opened = React.useRef(false);
|
|
142
|
+
React.useEffect(() => {
|
|
143
|
+
if (opened.current)
|
|
144
|
+
return;
|
|
145
|
+
opened.current = true;
|
|
146
|
+
ref.current
|
|
147
|
+
?.querySelector(".crisp-select-trigger")
|
|
148
|
+
?.click();
|
|
149
|
+
}, []);
|
|
150
|
+
return ref;
|
|
151
|
+
}
|
|
152
|
+
/** `Select` closes its own popover on Escape but has no callback telling
|
|
153
|
+
* the FIELD to leave editing mode -- and DOM focus doesn't reliably end up
|
|
154
|
+
* inside the newly-opened popover either, so a focus-dependent React
|
|
155
|
+
* `onKeyDown` can't be trusted to see the key at all. Mirrors `Select`'s
|
|
156
|
+
* own "document-level fallback for Escape" (see select.tsx) and
|
|
157
|
+
* `DataTable`'s identical fix for the same problem (#453). */
|
|
158
|
+
function useEscapeExitsEditing(onCancel) {
|
|
159
|
+
React.useEffect(() => {
|
|
160
|
+
const onKey = (e) => {
|
|
161
|
+
if (e.key === "Escape")
|
|
162
|
+
onCancel();
|
|
163
|
+
};
|
|
164
|
+
document.addEventListener("keydown", onKey);
|
|
165
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
166
|
+
}, [onCancel]);
|
|
167
|
+
}
|
|
168
|
+
/** `editor: "select"`'s picker, its own component (not inline in the field
|
|
169
|
+
* loop) because it needs the two hooks above (#457). */
|
|
170
|
+
function RecordPageSelectEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
171
|
+
const ref = useAutoOpenSelect();
|
|
172
|
+
useEscapeExitsEditing(onCancel);
|
|
173
|
+
return ((0, jsx_runtime_1.jsxs)("span", { ref: ref, className: "crisp-recordpage-edit-picker", children: [(0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", "aria-label": `Edit ${col.label}`, value: value != null ? String(value) : undefined, disabled: isPending, options: col.editOptions ?? [], onValueChange: onCommit }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-recordpage-edit-pending" }))] }));
|
|
174
|
+
}
|
|
128
175
|
function TagsValue({ values }) {
|
|
129
176
|
const [expanded, setExpanded] = React.useState(false);
|
|
130
177
|
const overflow = values.length - 1;
|
|
@@ -135,6 +182,21 @@ function FieldValue({ col, row, labels, }) {
|
|
|
135
182
|
const v = row[col.key];
|
|
136
183
|
if (valueEmpty(v))
|
|
137
184
|
return (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-empty", children: labels.setAValue });
|
|
185
|
+
// `editor: "select"` at rest (#457) -- checked ahead of `col.type` since
|
|
186
|
+
// the editor is orthogonal to it. Same dot+label shape as RecordsTable's
|
|
187
|
+
// own measured status chip, but this component's own classes
|
|
188
|
+
// (`crisp-recordpage-status[-dot]`): RecordsTable's own pair is scoped
|
|
189
|
+
// under `.crisp-recordstable`'s recipe root, so reusing its classnames
|
|
190
|
+
// verbatim here would resolve to nothing. Shown when the matched option
|
|
191
|
+
// carries a `color`, same as every other status-shaped value in this
|
|
192
|
+
// codebase; a colourless option (or no match) falls back to plain text.
|
|
193
|
+
if (col.editor === "select") {
|
|
194
|
+
const opt = col.editOptions?.find((o) => o.value === v);
|
|
195
|
+
const text = opt?.label ?? String(v);
|
|
196
|
+
if (!opt?.color)
|
|
197
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: text });
|
|
198
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-status", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-status-dot", style: { background: opt.color } }), text] }));
|
|
199
|
+
}
|
|
138
200
|
switch (col.type) {
|
|
139
201
|
case "tags":
|
|
140
202
|
return (0, jsx_runtime_1.jsx)(TagsValue, { values: v });
|
|
@@ -455,14 +517,31 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
455
517
|
const isPending = pendingKey === col.key;
|
|
456
518
|
const fieldError = error?.key === col.key ? error.message : null;
|
|
457
519
|
const label = ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-label", children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : null, (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-field-label-text", children: col.label })] }));
|
|
458
|
-
//
|
|
459
|
-
// click-to-open, no separate edit/view mode
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
if (canEdit && col.editor) {
|
|
520
|
+
// `switch` is always its own live widget -- no
|
|
521
|
+
// click-to-open, no separate edit/view mode: flipping the
|
|
522
|
+
// toggle IS the commit gesture, and there's no
|
|
523
|
+
// meaningfully different "rest" display for a boolean
|
|
524
|
+
// (same exception `DataTable`'s own `editor` makes, #453).
|
|
525
|
+
if (canEdit && col.editor === "switch") {
|
|
526
|
+
const value = record[col.key];
|
|
527
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-edit-picker", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { "aria-label": `Edit ${col.label}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commit(record.id, col.key, String(checked)) }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-recordpage-edit-pending" }))] }), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
528
|
+
}
|
|
529
|
+
// `select` opens crisp's own `Select` on a SINGLE click
|
|
530
|
+
// (#457, corrects #431/#385's original always-live
|
|
531
|
+
// copy of the same regression #453 fixed on `DataTable`)
|
|
532
|
+
// -- like every other editable type here, at rest it
|
|
533
|
+
// shows its value (see `FieldValue`'s own `col.editor
|
|
534
|
+
// === "select"` case) via the generic click-to-open
|
|
535
|
+
// branch below, and only mounts `Select` once
|
|
536
|
+
// `editingKey` matches. `DataTable`'s own double-click
|
|
537
|
+
// gate doesn't apply: `RecordPage`'s plain text/date
|
|
538
|
+
// fields already open on a single click (measured live
|
|
539
|
+
// for #431), so `select` matches that, not `DataTable`.
|
|
540
|
+
if (canEdit &&
|
|
541
|
+
editingKey === col.key &&
|
|
542
|
+
col.editor === "select") {
|
|
464
543
|
const value = record[col.key];
|
|
465
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.
|
|
544
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.jsx)(RecordPageSelectEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commit(record.id, col.key, v), onCancel: closeEditing }), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
466
545
|
}
|
|
467
546
|
if (canEdit && editingKey === col.key) {
|
|
468
547
|
const value = record[col.key];
|
|
@@ -108,6 +108,13 @@ const config = (0, core_1.defineRecipe)({
|
|
|
108
108
|
& .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
109
109
|
& .crisp-recordpage-edit-pending { flex-shrink: 0; color: ${tokens_1.contract.fgMuted}; }
|
|
110
110
|
& .crisp-recordpage-edit-error { flex-basis: 100%; margin-top: ${tokens_1.contract.space["0.5"]}; font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.bgDanger}; }
|
|
111
|
+
/* #457: an \`editor: "select"\` field's at-rest chip -- same shape as
|
|
112
|
+
RecordsTable's own \`.crisp-recordstable-status[-dot]\` (already
|
|
113
|
+
measured against Attio there), but its own classes: that pair is
|
|
114
|
+
scoped under \`.crisp-recordstable\`'s own recipe root, so reusing the
|
|
115
|
+
classnames verbatim here would resolve to nothing. */
|
|
116
|
+
& .crisp-recordpage-status { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; min-width: 0; }
|
|
117
|
+
& .crisp-recordpage-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: ${tokens_1.contract.radius.full}; }
|
|
111
118
|
& .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; width: 100%; }
|
|
112
119
|
& .crisp-recordpage-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
|
|
113
120
|
& .crisp-recordpage-link { color: ${tokens_1.contract.fgPrimary}; text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
@@ -218,12 +218,61 @@ const CREATE_REFERENCE_VALUE = "__crisp_create_reference__";
|
|
|
218
218
|
function dataReferenceOptionLabel(o) {
|
|
219
219
|
return (_jsxs("span", { className: "crisp-datatable-refoption", children: [_jsx(Avatar, { size: "sm", className: "crisp-datatable-refoption-avatar", children: o.avatar ? (_jsx("img", { src: o.avatar, alt: "" })) : (_jsx(CircleUserRound, { size: 12, strokeWidth: 1.75, "aria-hidden": "true" })) }), o.label] }));
|
|
220
220
|
}
|
|
221
|
+
/** `Select` owns its own closed-by-default `open` state with no prop or
|
|
222
|
+
* imperative way to start it open (see select.tsx) -- the only thing that
|
|
223
|
+
* opens it is a real click on its trigger button. Attio's own picker opens
|
|
224
|
+
* the instant a `select`/`reference` cell enters edit mode (measured live
|
|
225
|
+
* against a GRC test company, 2026-09-24), not after a second click on an
|
|
226
|
+
* already-mounted, still-closed trigger, so drive that click ourselves.
|
|
227
|
+
* Guarded against firing twice (StrictMode double-invokes effects) --
|
|
228
|
+
* `Select`'s own trigger toggles, so a second click would close what the
|
|
229
|
+
* first one opened. */
|
|
230
|
+
function useAutoOpenSelect() {
|
|
231
|
+
const ref = React.useRef(null);
|
|
232
|
+
const opened = React.useRef(false);
|
|
233
|
+
React.useEffect(() => {
|
|
234
|
+
if (opened.current)
|
|
235
|
+
return;
|
|
236
|
+
opened.current = true;
|
|
237
|
+
ref.current
|
|
238
|
+
?.querySelector(".crisp-select-trigger")
|
|
239
|
+
?.click();
|
|
240
|
+
}, []);
|
|
241
|
+
return ref;
|
|
242
|
+
}
|
|
243
|
+
/** `select`/`reference` open crisp's own `Select`, which closes its own
|
|
244
|
+
* popover on Escape (see select.tsx's "document-level fallback for Escape")
|
|
245
|
+
* but has no callback telling the CELL to leave editing mode -- and, same
|
|
246
|
+
* problem that fallback exists for, DOM focus doesn't reliably end up
|
|
247
|
+
* inside the newly-opened popover, so a `TableCell`-level `onKeyDown` can't
|
|
248
|
+
* be trusted to see the key at all (#453). Mirror Select's own fix for
|
|
249
|
+
* that: a raw `document` listener, not a focus-dependent React one. */
|
|
250
|
+
function useEscapeExitsEditing(onCancel) {
|
|
251
|
+
React.useEffect(() => {
|
|
252
|
+
const onKey = (e) => {
|
|
253
|
+
if (e.key === "Escape")
|
|
254
|
+
onCancel();
|
|
255
|
+
};
|
|
256
|
+
document.addEventListener("keydown", onKey);
|
|
257
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
258
|
+
}, [onCancel]);
|
|
259
|
+
}
|
|
260
|
+
/** `editor: "select"`'s picker, its own component (not inline in the
|
|
261
|
+
* row-render loop, like `DataReferenceEditor` below) because it needs the
|
|
262
|
+
* open-on-mount and escape-exits-editing effects above (#453). */
|
|
263
|
+
function DataSelectEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
264
|
+
const ref = useAutoOpenSelect();
|
|
265
|
+
useEscapeExitsEditing(onCancel);
|
|
266
|
+
return (_jsx("span", { ref: ref, style: { display: "contents" }, children: _jsx(Select, { variant: "ghost", "aria-label": `Edit ${col.key}`, value: asText(value) || undefined, disabled: isPending, options: col.options ?? [], onValueChange: onCommit }) }));
|
|
267
|
+
}
|
|
221
268
|
/** `editor: "reference"`'s picker, its own component (not inline in the
|
|
222
269
|
* row-render loop) because it needs its own `useState` for the search query
|
|
223
270
|
* -- one instance per cell, same reasoning `select`/`switch` don't need a
|
|
224
271
|
* component of their own but this does. */
|
|
225
|
-
function DataReferenceEditor({ col, value, isPending, onCommit, }) {
|
|
272
|
+
function DataReferenceEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
226
273
|
const [query, setQuery] = React.useState("");
|
|
274
|
+
const ref = useAutoOpenSelect();
|
|
275
|
+
useEscapeExitsEditing(onCancel);
|
|
227
276
|
const options = (col.refOptions ?? []).map((o) => ({
|
|
228
277
|
value: o.value,
|
|
229
278
|
label: dataReferenceOptionLabel(o),
|
|
@@ -238,12 +287,12 @@ function DataReferenceEditor({ col, value, isPending, onCommit, }) {
|
|
|
238
287
|
keywords: query,
|
|
239
288
|
});
|
|
240
289
|
}
|
|
241
|
-
return (_jsx(Select, { variant: "ghost", searchable: true, searchPlaceholder: col.refSearchPlaceholder, onSearchChange: setQuery, "aria-label": `Edit ${col.key}`, value: asText(value) || undefined, disabled: isPending, options: options, onValueChange: (v) => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
290
|
+
return (_jsx("span", { ref: ref, style: { display: "contents" }, children: _jsx(Select, { variant: "ghost", searchable: true, searchPlaceholder: col.refSearchPlaceholder, onSearchChange: setQuery, "aria-label": `Edit ${col.key}`, value: asText(value) || undefined, disabled: isPending, options: options, onValueChange: (v) => {
|
|
291
|
+
if (v === CREATE_REFERENCE_VALUE)
|
|
292
|
+
col.onCreateReference?.(query);
|
|
293
|
+
else
|
|
294
|
+
onCommit(v);
|
|
295
|
+
} }) }));
|
|
247
296
|
}
|
|
248
297
|
export function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel = "Select all rows", selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement = (count) => count === 0 ? "Selection cleared" : `${count} selected`, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder = "Search…", searchLabel = "Search table", onEdit, stickyFirst = true, stickyLast = false, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel = "New record", empty = "No results.", showCount = true, countLabel = (shown, total) => shown === total
|
|
249
298
|
? `${total} ${total === 1 ? "row" : "rows"}`
|
|
@@ -466,10 +515,11 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
466
515
|
* throughout, and the browser's own picker already
|
|
467
516
|
* carries both a calendar and a time control.
|
|
468
517
|
* text/entity a plain text field
|
|
469
|
-
* `
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
518
|
+
* `editor: "select"`/`"reference"` open their own picker instead of this
|
|
519
|
+
* native-input family, but through the SAME double-click gate (#453) --
|
|
520
|
+
* see the row-render loop's `isEditing` branch for those, this function
|
|
521
|
+
* only covers the native-input-backed types. `editor: "switch"` is the
|
|
522
|
+
* one editor with no double-click gate at all (always live).
|
|
473
523
|
*/
|
|
474
524
|
function editorFor(col) {
|
|
475
525
|
switch (col.type) {
|
|
@@ -523,11 +573,12 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
523
573
|
return;
|
|
524
574
|
}
|
|
525
575
|
else if (e.key === "Enter") {
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
|
|
576
|
+
// #453: `switch` has no separate editing mode to enter -- it's
|
|
577
|
+
// already its own live widget (#385); Enter there is a no-op, same
|
|
578
|
+
// as any other non-editable cell that isn't wired to onRowClick.
|
|
579
|
+
// `select`/`reference` DO have one now (like every other editor),
|
|
580
|
+
// so Enter opens them too.
|
|
581
|
+
if (columns[c]?.editable && columns[c]?.editor !== "switch") {
|
|
531
582
|
e.preventDefault();
|
|
532
583
|
openEditing(selCell.id, selCell.key);
|
|
533
584
|
}
|
|
@@ -758,14 +809,37 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
758
809
|
const errorHere = editError?.id === row.id && editError?.key === col.key
|
|
759
810
|
? editError.message
|
|
760
811
|
: null;
|
|
761
|
-
//
|
|
762
|
-
// live widget --
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
//
|
|
767
|
-
|
|
768
|
-
|
|
812
|
+
// #453: a `switch` editable column IS always its own
|
|
813
|
+
// live widget -- flipping the toggle IS the commit
|
|
814
|
+
// gesture, there's no meaningfully different "rest"
|
|
815
|
+
// display for a boolean. `select`/`reference` are
|
|
816
|
+
// NOT this: at rest they must show the column's own
|
|
817
|
+
// render/format like every other editable type
|
|
818
|
+
// (#412 empty handling included), and only open on
|
|
819
|
+
// double-click (#411) -- this branch previously
|
|
820
|
+
// rendered them unconditionally, replacing every
|
|
821
|
+
// row's own render with a picker trigger, which is
|
|
822
|
+
// the regression #453 reported (also #432/#449,
|
|
823
|
+
// which never had a "rest" state of their own to get
|
|
824
|
+
// right). See the `isEditing` block below for their
|
|
825
|
+
// actual editor.
|
|
826
|
+
if (col.editable && col.editor === "switch") {
|
|
827
|
+
return (_jsxs(TableCell, { align: alignFor(col), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [_jsxs("span", { className: "crisp-datatable-edit-picker", children: [_jsx(Switch, { "aria-label": `Edit ${col.key}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commitEdit(row.id, col.key, String(checked)) }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
828
|
+
}
|
|
829
|
+
if (isEditing &&
|
|
830
|
+
(col.editor === "select" || col.editor === "reference")) {
|
|
831
|
+
return (_jsxs(TableCell, { align: alignFor(col), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined,
|
|
832
|
+
// Same as the plain text editor's own cell:
|
|
833
|
+
// opening/using the picker must not also fire
|
|
834
|
+
// the row's onRowClick. Escape is handled by
|
|
835
|
+
// `DataSelectEditor`/`DataReferenceEditor`
|
|
836
|
+
// themselves (a document-level listener, not a
|
|
837
|
+
// `TableCell`-level `onKeyDown`) -- opening the
|
|
838
|
+
// picker moves DOM focus into a floating,
|
|
839
|
+
// portaled popover, so a handler here can't
|
|
840
|
+
// rely on the key event ever bubbling through
|
|
841
|
+
// this element (#453).
|
|
842
|
+
onClick: (e) => e.stopPropagation(), children: [_jsxs("span", { className: "crisp-datatable-edit-picker", children: [col.editor === "select" ? (_jsx(DataSelectEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })) : (_jsx(DataReferenceEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
769
843
|
}
|
|
770
844
|
if (isEditing) {
|
|
771
845
|
return (_jsxs(TableCell, { align: alignFor(col), ...sticky, onClick: (e) => e.stopPropagation(), children: [_jsx("input", { className: "crisp-datatable-edit",
|
|
@@ -88,6 +88,53 @@ export const recordPageLabels = {
|
|
|
88
88
|
recordCreated: (name) => (_jsxs(_Fragment, { children: [_jsx("b", { children: name }), " record was created"] })),
|
|
89
89
|
noEmailsWith: (name) => _jsxs(_Fragment, { children: ["No emails logged with ", name, " yet."] }),
|
|
90
90
|
};
|
|
91
|
+
/** `Select` owns its own closed-by-default `open` state with no prop or
|
|
92
|
+
* imperative way to start it open -- the only thing that opens it is a real
|
|
93
|
+
* click on its trigger button. Mounting one on click (rather than rendering
|
|
94
|
+
* it already open) leaves the reader with a second click to make before
|
|
95
|
+
* they can pick anything, so drive that click ourselves the moment it
|
|
96
|
+
* mounts. Mirrors `DataTable`'s own `useAutoOpenSelect` (#453) -- kept as
|
|
97
|
+
* its own copy here rather than a shared import, the same "duplicated per
|
|
98
|
+
* product surface" choice already made for `EditableOption`/
|
|
99
|
+
* `editableInputType` above. Guarded against firing twice (StrictMode
|
|
100
|
+
* double-invokes effects): `Select`'s own trigger toggles, so a second
|
|
101
|
+
* click would close what the first one opened. */
|
|
102
|
+
function useAutoOpenSelect() {
|
|
103
|
+
const ref = React.useRef(null);
|
|
104
|
+
const opened = React.useRef(false);
|
|
105
|
+
React.useEffect(() => {
|
|
106
|
+
if (opened.current)
|
|
107
|
+
return;
|
|
108
|
+
opened.current = true;
|
|
109
|
+
ref.current
|
|
110
|
+
?.querySelector(".crisp-select-trigger")
|
|
111
|
+
?.click();
|
|
112
|
+
}, []);
|
|
113
|
+
return ref;
|
|
114
|
+
}
|
|
115
|
+
/** `Select` closes its own popover on Escape but has no callback telling
|
|
116
|
+
* the FIELD to leave editing mode -- and DOM focus doesn't reliably end up
|
|
117
|
+
* inside the newly-opened popover either, so a focus-dependent React
|
|
118
|
+
* `onKeyDown` can't be trusted to see the key at all. Mirrors `Select`'s
|
|
119
|
+
* own "document-level fallback for Escape" (see select.tsx) and
|
|
120
|
+
* `DataTable`'s identical fix for the same problem (#453). */
|
|
121
|
+
function useEscapeExitsEditing(onCancel) {
|
|
122
|
+
React.useEffect(() => {
|
|
123
|
+
const onKey = (e) => {
|
|
124
|
+
if (e.key === "Escape")
|
|
125
|
+
onCancel();
|
|
126
|
+
};
|
|
127
|
+
document.addEventListener("keydown", onKey);
|
|
128
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
129
|
+
}, [onCancel]);
|
|
130
|
+
}
|
|
131
|
+
/** `editor: "select"`'s picker, its own component (not inline in the field
|
|
132
|
+
* loop) because it needs the two hooks above (#457). */
|
|
133
|
+
function RecordPageSelectEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
134
|
+
const ref = useAutoOpenSelect();
|
|
135
|
+
useEscapeExitsEditing(onCancel);
|
|
136
|
+
return (_jsxs("span", { ref: ref, className: "crisp-recordpage-edit-picker", children: [_jsx(Select, { variant: "ghost", "aria-label": `Edit ${col.label}`, value: value != null ? String(value) : undefined, disabled: isPending, options: col.editOptions ?? [], onValueChange: onCommit }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-recordpage-edit-pending" }))] }));
|
|
137
|
+
}
|
|
91
138
|
function TagsValue({ values }) {
|
|
92
139
|
const [expanded, setExpanded] = React.useState(false);
|
|
93
140
|
const overflow = values.length - 1;
|
|
@@ -98,6 +145,21 @@ function FieldValue({ col, row, labels, }) {
|
|
|
98
145
|
const v = row[col.key];
|
|
99
146
|
if (valueEmpty(v))
|
|
100
147
|
return _jsx("span", { className: "crisp-recordpage-empty", children: labels.setAValue });
|
|
148
|
+
// `editor: "select"` at rest (#457) -- checked ahead of `col.type` since
|
|
149
|
+
// the editor is orthogonal to it. Same dot+label shape as RecordsTable's
|
|
150
|
+
// own measured status chip, but this component's own classes
|
|
151
|
+
// (`crisp-recordpage-status[-dot]`): RecordsTable's own pair is scoped
|
|
152
|
+
// under `.crisp-recordstable`'s recipe root, so reusing its classnames
|
|
153
|
+
// verbatim here would resolve to nothing. Shown when the matched option
|
|
154
|
+
// carries a `color`, same as every other status-shaped value in this
|
|
155
|
+
// codebase; a colourless option (or no match) falls back to plain text.
|
|
156
|
+
if (col.editor === "select") {
|
|
157
|
+
const opt = col.editOptions?.find((o) => o.value === v);
|
|
158
|
+
const text = opt?.label ?? String(v);
|
|
159
|
+
if (!opt?.color)
|
|
160
|
+
return _jsx(_Fragment, { children: text });
|
|
161
|
+
return (_jsxs("span", { className: "crisp-recordpage-status", children: [_jsx("span", { className: "crisp-recordpage-status-dot", style: { background: opt.color } }), text] }));
|
|
162
|
+
}
|
|
101
163
|
switch (col.type) {
|
|
102
164
|
case "tags":
|
|
103
165
|
return _jsx(TagsValue, { values: v });
|
|
@@ -418,14 +480,31 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
|
|
|
418
480
|
const isPending = pendingKey === col.key;
|
|
419
481
|
const fieldError = error?.key === col.key ? error.message : null;
|
|
420
482
|
const label = (_jsxs("span", { className: "crisp-recordpage-field-label", children: [col.icon ? (_jsx(col.icon, { size: 15, strokeWidth: 1.75 })) : null, _jsx("span", { className: "crisp-recordpage-field-label-text", children: col.label })] }));
|
|
421
|
-
//
|
|
422
|
-
// click-to-open, no separate edit/view mode
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
if (canEdit && col.editor) {
|
|
483
|
+
// `switch` is always its own live widget -- no
|
|
484
|
+
// click-to-open, no separate edit/view mode: flipping the
|
|
485
|
+
// toggle IS the commit gesture, and there's no
|
|
486
|
+
// meaningfully different "rest" display for a boolean
|
|
487
|
+
// (same exception `DataTable`'s own `editor` makes, #453).
|
|
488
|
+
if (canEdit && col.editor === "switch") {
|
|
489
|
+
const value = record[col.key];
|
|
490
|
+
return (_jsxs("div", { className: "crisp-recordpage-field", children: [label, _jsxs("span", { className: "crisp-recordpage-field-value", children: [_jsxs("span", { className: "crisp-recordpage-edit-picker", children: [_jsx(Switch, { "aria-label": `Edit ${col.label}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commit(record.id, col.key, String(checked)) }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-recordpage-edit-pending" }))] }), fieldError && (_jsx("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
491
|
+
}
|
|
492
|
+
// `select` opens crisp's own `Select` on a SINGLE click
|
|
493
|
+
// (#457, corrects #431/#385's original always-live
|
|
494
|
+
// copy of the same regression #453 fixed on `DataTable`)
|
|
495
|
+
// -- like every other editable type here, at rest it
|
|
496
|
+
// shows its value (see `FieldValue`'s own `col.editor
|
|
497
|
+
// === "select"` case) via the generic click-to-open
|
|
498
|
+
// branch below, and only mounts `Select` once
|
|
499
|
+
// `editingKey` matches. `DataTable`'s own double-click
|
|
500
|
+
// gate doesn't apply: `RecordPage`'s plain text/date
|
|
501
|
+
// fields already open on a single click (measured live
|
|
502
|
+
// for #431), so `select` matches that, not `DataTable`.
|
|
503
|
+
if (canEdit &&
|
|
504
|
+
editingKey === col.key &&
|
|
505
|
+
col.editor === "select") {
|
|
427
506
|
const value = record[col.key];
|
|
428
|
-
return (_jsxs("div", { className: "crisp-recordpage-field", children: [label, _jsxs("span", { className: "crisp-recordpage-field-value", children: [
|
|
507
|
+
return (_jsxs("div", { className: "crisp-recordpage-field", children: [label, _jsxs("span", { className: "crisp-recordpage-field-value", children: [_jsx(RecordPageSelectEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commit(record.id, col.key, v), onCancel: closeEditing }), fieldError && (_jsx("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
429
508
|
}
|
|
430
509
|
if (canEdit && editingKey === col.key) {
|
|
431
510
|
const value = record[col.key];
|
|
@@ -105,6 +105,13 @@ const config = defineRecipe({
|
|
|
105
105
|
& .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
106
106
|
& .crisp-recordpage-edit-pending { flex-shrink: 0; color: ${c.fgMuted}; }
|
|
107
107
|
& .crisp-recordpage-edit-error { flex-basis: 100%; margin-top: ${c.space["0.5"]}; font-size: ${c.text.sm}; color: ${c.bgDanger}; }
|
|
108
|
+
/* #457: an \`editor: "select"\` field's at-rest chip -- same shape as
|
|
109
|
+
RecordsTable's own \`.crisp-recordstable-status[-dot]\` (already
|
|
110
|
+
measured against Attio there), but its own classes: that pair is
|
|
111
|
+
scoped under \`.crisp-recordstable\`'s own recipe root, so reusing the
|
|
112
|
+
classnames verbatim here would resolve to nothing. */
|
|
113
|
+
& .crisp-recordpage-status { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; min-width: 0; }
|
|
114
|
+
& .crisp-recordpage-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: ${c.radius.full}; }
|
|
108
115
|
& .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: ${c.space["1"]}; width: 100%; }
|
|
109
116
|
& .crisp-recordpage-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
|
|
110
117
|
& .crisp-recordpage-link { color: ${c.fgPrimary}; text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
package/dist/recordpage.css
CHANGED
|
@@ -83,23 +83,33 @@
|
|
|
83
83
|
& .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
84
84
|
& .crisp-recordpage-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
85
85
|
& .crisp-recordpage-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
|
|
86
|
+
/* #457: an `editor: "select"` field's at-rest chip -- same shape as
|
|
87
|
+
RecordsTable's own `.crisp-recordstable-status[-dot]` (already measured against Attio there), but its own classes: that pair is scoped under `.crisp-recordstable`'s own recipe root, so reusing the
|
|
88
|
+
classnames verbatim here would resolve to nothing. */
|
|
89
|
+
& .crisp-recordpage-status { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
|
|
90
|
+
& .crisp-recordpage-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
|
|
86
91
|
& .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
|
|
87
92
|
& .crisp-recordpage-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
|
|
88
93
|
& .crisp-recordpage-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
89
94
|
& .crisp-recordpage-link:hover { text-decoration: underline; }
|
|
90
95
|
& .crisp-recordpage-emptystate { padding: var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
96
|
+
|
|
91
97
|
& .crisp-recordpage-main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; padding: var(--crisp-space-3) var(--crisp-space-5); }
|
|
98
|
+
|
|
92
99
|
& .crisp-recordpage-panel { padding-top: var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
93
100
|
& .crisp-recordpage-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); background: var(--crisp-bg-surface); overflow: hidden; }
|
|
94
101
|
& .crisp-recordpage-card-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.14px; color: var(--crisp-fg-primary); padding: var(--crisp-space-3) var(--crisp-space-3) var(--crisp-space-2); }
|
|
95
102
|
& .crisp-recordpage-hl-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); overflow: hidden; }
|
|
96
|
-
/* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
|
|
103
|
+
/* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
|
|
104
|
+
column count, so the grid stays responsive (auto-fit) without per-cell
|
|
105
|
+
nth-child border hacks that break when the column count changes. */
|
|
97
106
|
& .crisp-recordpage-hl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1px; background: var(--crisp-border-subtle); }
|
|
98
107
|
& .crisp-recordpage-hl-cell { position: relative; min-height: 84px; padding: var(--crisp-space-2-5) var(--crisp-space-3); background: var(--crisp-bg-surface); }
|
|
99
108
|
& .crisp-recordpage-hl-icon { position: absolute; top: var(--crisp-space-2-5); inset-inline-end: var(--crisp-space-3); color: var(--crisp-fg-muted); }
|
|
100
109
|
& .crisp-recordpage-hl-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); margin-bottom: var(--crisp-space-2-5); padding-inline-end: var(--crisp-space-4); }
|
|
101
110
|
& .crisp-recordpage-hl-value { display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
102
111
|
& .crisp-recordpage-hl-value--empty { color: var(--crisp-fg-muted); font-weight: var(--crisp-weight-regular); }
|
|
112
|
+
|
|
103
113
|
& .crisp-recordpage-feed { display: flex; flex-direction: column; }
|
|
104
114
|
& .crisp-recordpage-feed-item { display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2) 0; }
|
|
105
115
|
& .crisp-recordpage-feed-dot { position: relative; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex-shrink: 0; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
|
|
@@ -108,6 +118,7 @@
|
|
|
108
118
|
& .crisp-recordpage-feed-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); margin-top: 2px; }
|
|
109
119
|
& .crisp-recordpage-feed-more { align-self: flex-start; margin-top: var(--crisp-space-1); border: none; background: none; padding: var(--crisp-space-1) 0; color: var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
110
120
|
& .crisp-recordpage-feed-more:hover { text-decoration: underline; }
|
|
121
|
+
|
|
111
122
|
& .crisp-recordpage-section-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-1); }
|
|
112
123
|
& .crisp-recordpage-section-head .crisp-recordpage-section-title { padding: 0; }
|
|
113
124
|
& .crisp-recordpage-section-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
|
|
@@ -115,6 +126,7 @@
|
|
|
115
126
|
& .crisp-recordpage-addbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 26px; padding: 0 var(--crisp-space-1-5); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
116
127
|
& .crisp-recordpage-addbtn:hover { background: var(--crisp-bg-raised); }
|
|
117
128
|
& .crisp-recordpage-addbtn svg { flex-shrink: 0; }
|
|
129
|
+
|
|
118
130
|
& .crisp-recordpage-composer { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding: var(--crisp-space-2); border: 1px solid var(--crisp-border-default); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); margin-bottom: var(--crisp-space-2); }
|
|
119
131
|
& .crisp-recordpage-composer textarea { width: 100%; min-height: 56px; resize: vertical; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
|
|
120
132
|
& .crisp-recordpage-composer textarea:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); border-radius: var(--crisp-radius-md); }
|
|
@@ -124,18 +136,23 @@
|
|
|
124
136
|
& .crisp-recordpage-btn:hover { background: var(--crisp-bg-raised); }
|
|
125
137
|
& .crisp-recordpage-btn--primary { box-shadow: none; background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); }
|
|
126
138
|
& .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
139
|
+
|
|
127
140
|
& .crisp-recordpage-note { display: flex; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
|
|
128
141
|
& .crisp-recordpage-note-body { flex: 1; min-width: 0; }
|
|
129
142
|
& .crisp-recordpage-note-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-bottom: var(--crisp-space-0-5); }
|
|
130
143
|
& .crisp-recordpage-note-author { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
131
144
|
& .crisp-recordpage-note-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
|
|
132
145
|
& .crisp-recordpage-note-text { font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
|
|
146
|
+
|
|
133
147
|
& .crisp-recordpage-task { display: flex; align-items: flex-start; gap: var(--crisp-space-2); padding: var(--crisp-space-1-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
|
|
134
148
|
& .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); padding-top: 1px; }
|
|
135
149
|
& .crisp-recordpage-task[data-done] .crisp-recordpage-task-text { text-decoration: line-through; color: var(--crisp-fg-muted); }
|
|
150
|
+
|
|
136
151
|
& .crisp-recordpage-emptycard { display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-6) var(--crisp-space-3); border: 1px dashed var(--crisp-border-default); border-radius: var(--crisp-radius-xl); color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); text-align: center; }
|
|
137
152
|
& .crisp-recordpage-emptycard svg { color: var(--crisp-fg-muted); opacity: 0.7; }
|
|
153
|
+
|
|
138
154
|
& .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
|
|
155
|
+
|
|
139
156
|
& .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
|
|
140
157
|
& .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
|
|
141
158
|
& .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); }
|
|
@@ -84,23 +84,33 @@
|
|
|
84
84
|
& .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
85
85
|
& .crisp-recordpage-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
86
86
|
& .crisp-recordpage-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
|
|
87
|
+
/* #457: an `editor: "select"` field's at-rest chip -- same shape as
|
|
88
|
+
RecordsTable's own `.crisp-recordstable-status[-dot]` (already measured against Attio there), but its own classes: that pair is scoped under `.crisp-recordstable`'s own recipe root, so reusing the
|
|
89
|
+
classnames verbatim here would resolve to nothing. */
|
|
90
|
+
& .crisp-recordpage-status { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
|
|
91
|
+
& .crisp-recordpage-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
|
|
87
92
|
& .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
|
|
88
93
|
& .crisp-recordpage-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
|
|
89
94
|
& .crisp-recordpage-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
90
95
|
& .crisp-recordpage-link:hover { text-decoration: underline; }
|
|
91
96
|
& .crisp-recordpage-emptystate { padding: var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
97
|
+
|
|
92
98
|
& .crisp-recordpage-main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; padding: var(--crisp-space-3) var(--crisp-space-5); }
|
|
99
|
+
|
|
93
100
|
& .crisp-recordpage-panel { padding-top: var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
94
101
|
& .crisp-recordpage-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); background: var(--crisp-bg-surface); overflow: hidden; }
|
|
95
102
|
& .crisp-recordpage-card-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.14px; color: var(--crisp-fg-primary); padding: var(--crisp-space-3) var(--crisp-space-3) var(--crisp-space-2); }
|
|
96
103
|
& .crisp-recordpage-hl-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); overflow: hidden; }
|
|
97
|
-
/* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
|
|
104
|
+
/* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
|
|
105
|
+
column count, so the grid stays responsive (auto-fit) without per-cell
|
|
106
|
+
nth-child border hacks that break when the column count changes. */
|
|
98
107
|
& .crisp-recordpage-hl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1px; background: var(--crisp-border-subtle); }
|
|
99
108
|
& .crisp-recordpage-hl-cell { position: relative; min-height: 84px; padding: var(--crisp-space-2-5) var(--crisp-space-3); background: var(--crisp-bg-surface); }
|
|
100
109
|
& .crisp-recordpage-hl-icon { position: absolute; top: var(--crisp-space-2-5); inset-inline-end: var(--crisp-space-3); color: var(--crisp-fg-muted); }
|
|
101
110
|
& .crisp-recordpage-hl-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); margin-bottom: var(--crisp-space-2-5); padding-inline-end: var(--crisp-space-4); }
|
|
102
111
|
& .crisp-recordpage-hl-value { display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
103
112
|
& .crisp-recordpage-hl-value--empty { color: var(--crisp-fg-muted); font-weight: var(--crisp-weight-regular); }
|
|
113
|
+
|
|
104
114
|
& .crisp-recordpage-feed { display: flex; flex-direction: column; }
|
|
105
115
|
& .crisp-recordpage-feed-item { display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2) 0; }
|
|
106
116
|
& .crisp-recordpage-feed-dot { position: relative; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex-shrink: 0; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
|
|
@@ -109,6 +119,7 @@
|
|
|
109
119
|
& .crisp-recordpage-feed-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); margin-top: 2px; }
|
|
110
120
|
& .crisp-recordpage-feed-more { align-self: flex-start; margin-top: var(--crisp-space-1); border: none; background: none; padding: var(--crisp-space-1) 0; color: var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
111
121
|
& .crisp-recordpage-feed-more:hover { text-decoration: underline; }
|
|
122
|
+
|
|
112
123
|
& .crisp-recordpage-section-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-1); }
|
|
113
124
|
& .crisp-recordpage-section-head .crisp-recordpage-section-title { padding: 0; }
|
|
114
125
|
& .crisp-recordpage-section-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
|
|
@@ -116,6 +127,7 @@
|
|
|
116
127
|
& .crisp-recordpage-addbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 26px; padding: 0 var(--crisp-space-1-5); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
117
128
|
& .crisp-recordpage-addbtn:hover { background: var(--crisp-bg-raised); }
|
|
118
129
|
& .crisp-recordpage-addbtn svg { flex-shrink: 0; }
|
|
130
|
+
|
|
119
131
|
& .crisp-recordpage-composer { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding: var(--crisp-space-2); border: 1px solid var(--crisp-border-default); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); margin-bottom: var(--crisp-space-2); }
|
|
120
132
|
& .crisp-recordpage-composer textarea { width: 100%; min-height: 56px; resize: vertical; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
|
|
121
133
|
& .crisp-recordpage-composer textarea:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); border-radius: var(--crisp-radius-md); }
|
|
@@ -125,18 +137,23 @@
|
|
|
125
137
|
& .crisp-recordpage-btn:hover { background: var(--crisp-bg-raised); }
|
|
126
138
|
& .crisp-recordpage-btn--primary { box-shadow: none; background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); }
|
|
127
139
|
& .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
140
|
+
|
|
128
141
|
& .crisp-recordpage-note { display: flex; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
|
|
129
142
|
& .crisp-recordpage-note-body { flex: 1; min-width: 0; }
|
|
130
143
|
& .crisp-recordpage-note-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-bottom: var(--crisp-space-0-5); }
|
|
131
144
|
& .crisp-recordpage-note-author { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
132
145
|
& .crisp-recordpage-note-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
|
|
133
146
|
& .crisp-recordpage-note-text { font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
|
|
147
|
+
|
|
134
148
|
& .crisp-recordpage-task { display: flex; align-items: flex-start; gap: var(--crisp-space-2); padding: var(--crisp-space-1-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
|
|
135
149
|
& .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); padding-top: 1px; }
|
|
136
150
|
& .crisp-recordpage-task[data-done] .crisp-recordpage-task-text { text-decoration: line-through; color: var(--crisp-fg-muted); }
|
|
151
|
+
|
|
137
152
|
& .crisp-recordpage-emptycard { display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-6) var(--crisp-space-3); border: 1px dashed var(--crisp-border-default); border-radius: var(--crisp-radius-xl); color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); text-align: center; }
|
|
138
153
|
& .crisp-recordpage-emptycard svg { color: var(--crisp-fg-muted); opacity: 0.7; }
|
|
154
|
+
|
|
139
155
|
& .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
|
|
156
|
+
|
|
140
157
|
& .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
|
|
141
158
|
& .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
|
|
142
159
|
& .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); }
|
package/dist/styles.css
CHANGED
|
@@ -2163,23 +2163,33 @@
|
|
|
2163
2163
|
& .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
2164
2164
|
& .crisp-recordpage-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
2165
2165
|
& .crisp-recordpage-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
|
|
2166
|
+
/* #457: an `editor: "select"` field's at-rest chip -- same shape as
|
|
2167
|
+
RecordsTable's own `.crisp-recordstable-status[-dot]` (already measured against Attio there), but its own classes: that pair is scoped under `.crisp-recordstable`'s own recipe root, so reusing the
|
|
2168
|
+
classnames verbatim here would resolve to nothing. */
|
|
2169
|
+
& .crisp-recordpage-status { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
|
|
2170
|
+
& .crisp-recordpage-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
|
|
2166
2171
|
& .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
|
|
2167
2172
|
& .crisp-recordpage-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
|
|
2168
2173
|
& .crisp-recordpage-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
2169
2174
|
& .crisp-recordpage-link:hover { text-decoration: underline; }
|
|
2170
2175
|
& .crisp-recordpage-emptystate { padding: var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
2176
|
+
|
|
2171
2177
|
& .crisp-recordpage-main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; padding: var(--crisp-space-3) var(--crisp-space-5); }
|
|
2178
|
+
|
|
2172
2179
|
& .crisp-recordpage-panel { padding-top: var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
2173
2180
|
& .crisp-recordpage-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); background: var(--crisp-bg-surface); overflow: hidden; }
|
|
2174
2181
|
& .crisp-recordpage-card-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.14px; color: var(--crisp-fg-primary); padding: var(--crisp-space-3) var(--crisp-space-3) var(--crisp-space-2); }
|
|
2175
2182
|
& .crisp-recordpage-hl-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); overflow: hidden; }
|
|
2176
|
-
/* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
|
|
2183
|
+
/* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
|
|
2184
|
+
column count, so the grid stays responsive (auto-fit) without per-cell
|
|
2185
|
+
nth-child border hacks that break when the column count changes. */
|
|
2177
2186
|
& .crisp-recordpage-hl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1px; background: var(--crisp-border-subtle); }
|
|
2178
2187
|
& .crisp-recordpage-hl-cell { position: relative; min-height: 84px; padding: var(--crisp-space-2-5) var(--crisp-space-3); background: var(--crisp-bg-surface); }
|
|
2179
2188
|
& .crisp-recordpage-hl-icon { position: absolute; top: var(--crisp-space-2-5); inset-inline-end: var(--crisp-space-3); color: var(--crisp-fg-muted); }
|
|
2180
2189
|
& .crisp-recordpage-hl-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); margin-bottom: var(--crisp-space-2-5); padding-inline-end: var(--crisp-space-4); }
|
|
2181
2190
|
& .crisp-recordpage-hl-value { display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
2182
2191
|
& .crisp-recordpage-hl-value--empty { color: var(--crisp-fg-muted); font-weight: var(--crisp-weight-regular); }
|
|
2192
|
+
|
|
2183
2193
|
& .crisp-recordpage-feed { display: flex; flex-direction: column; }
|
|
2184
2194
|
& .crisp-recordpage-feed-item { display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2) 0; }
|
|
2185
2195
|
& .crisp-recordpage-feed-dot { position: relative; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex-shrink: 0; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
|
|
@@ -2188,6 +2198,7 @@
|
|
|
2188
2198
|
& .crisp-recordpage-feed-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); margin-top: 2px; }
|
|
2189
2199
|
& .crisp-recordpage-feed-more { align-self: flex-start; margin-top: var(--crisp-space-1); border: none; background: none; padding: var(--crisp-space-1) 0; color: var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
2190
2200
|
& .crisp-recordpage-feed-more:hover { text-decoration: underline; }
|
|
2201
|
+
|
|
2191
2202
|
& .crisp-recordpage-section-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-1); }
|
|
2192
2203
|
& .crisp-recordpage-section-head .crisp-recordpage-section-title { padding: 0; }
|
|
2193
2204
|
& .crisp-recordpage-section-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
|
|
@@ -2195,6 +2206,7 @@
|
|
|
2195
2206
|
& .crisp-recordpage-addbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 26px; padding: 0 var(--crisp-space-1-5); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
2196
2207
|
& .crisp-recordpage-addbtn:hover { background: var(--crisp-bg-raised); }
|
|
2197
2208
|
& .crisp-recordpage-addbtn svg { flex-shrink: 0; }
|
|
2209
|
+
|
|
2198
2210
|
& .crisp-recordpage-composer { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding: var(--crisp-space-2); border: 1px solid var(--crisp-border-default); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); margin-bottom: var(--crisp-space-2); }
|
|
2199
2211
|
& .crisp-recordpage-composer textarea { width: 100%; min-height: 56px; resize: vertical; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
|
|
2200
2212
|
& .crisp-recordpage-composer textarea:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); border-radius: var(--crisp-radius-md); }
|
|
@@ -2204,18 +2216,23 @@
|
|
|
2204
2216
|
& .crisp-recordpage-btn:hover { background: var(--crisp-bg-raised); }
|
|
2205
2217
|
& .crisp-recordpage-btn--primary { box-shadow: none; background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); }
|
|
2206
2218
|
& .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
2219
|
+
|
|
2207
2220
|
& .crisp-recordpage-note { display: flex; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
|
|
2208
2221
|
& .crisp-recordpage-note-body { flex: 1; min-width: 0; }
|
|
2209
2222
|
& .crisp-recordpage-note-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-bottom: var(--crisp-space-0-5); }
|
|
2210
2223
|
& .crisp-recordpage-note-author { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
2211
2224
|
& .crisp-recordpage-note-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
|
|
2212
2225
|
& .crisp-recordpage-note-text { font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
|
|
2226
|
+
|
|
2213
2227
|
& .crisp-recordpage-task { display: flex; align-items: flex-start; gap: var(--crisp-space-2); padding: var(--crisp-space-1-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
|
|
2214
2228
|
& .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); padding-top: 1px; }
|
|
2215
2229
|
& .crisp-recordpage-task[data-done] .crisp-recordpage-task-text { text-decoration: line-through; color: var(--crisp-fg-muted); }
|
|
2230
|
+
|
|
2216
2231
|
& .crisp-recordpage-emptycard { display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-6) var(--crisp-space-3); border: 1px dashed var(--crisp-border-default); border-radius: var(--crisp-radius-xl); color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); text-align: center; }
|
|
2217
2232
|
& .crisp-recordpage-emptycard svg { color: var(--crisp-fg-muted); opacity: 0.7; }
|
|
2233
|
+
|
|
2218
2234
|
& .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
|
|
2235
|
+
|
|
2219
2236
|
& .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
|
|
2220
2237
|
& .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
|
|
2221
2238
|
& .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); }
|
package/dist/styles.layered.css
CHANGED
|
@@ -2164,23 +2164,33 @@
|
|
|
2164
2164
|
& .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
2165
2165
|
& .crisp-recordpage-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
2166
2166
|
& .crisp-recordpage-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
|
|
2167
|
+
/* #457: an `editor: "select"` field's at-rest chip -- same shape as
|
|
2168
|
+
RecordsTable's own `.crisp-recordstable-status[-dot]` (already measured against Attio there), but its own classes: that pair is scoped under `.crisp-recordstable`'s own recipe root, so reusing the
|
|
2169
|
+
classnames verbatim here would resolve to nothing. */
|
|
2170
|
+
& .crisp-recordpage-status { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
|
|
2171
|
+
& .crisp-recordpage-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
|
|
2167
2172
|
& .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
|
|
2168
2173
|
& .crisp-recordpage-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
|
|
2169
2174
|
& .crisp-recordpage-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
2170
2175
|
& .crisp-recordpage-link:hover { text-decoration: underline; }
|
|
2171
2176
|
& .crisp-recordpage-emptystate { padding: var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
2177
|
+
|
|
2172
2178
|
& .crisp-recordpage-main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; padding: var(--crisp-space-3) var(--crisp-space-5); }
|
|
2179
|
+
|
|
2173
2180
|
& .crisp-recordpage-panel { padding-top: var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
2174
2181
|
& .crisp-recordpage-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); background: var(--crisp-bg-surface); overflow: hidden; }
|
|
2175
2182
|
& .crisp-recordpage-card-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.14px; color: var(--crisp-fg-primary); padding: var(--crisp-space-3) var(--crisp-space-3) var(--crisp-space-2); }
|
|
2176
2183
|
& .crisp-recordpage-hl-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); overflow: hidden; }
|
|
2177
|
-
/* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
|
|
2184
|
+
/* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
|
|
2185
|
+
column count, so the grid stays responsive (auto-fit) without per-cell
|
|
2186
|
+
nth-child border hacks that break when the column count changes. */
|
|
2178
2187
|
& .crisp-recordpage-hl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1px; background: var(--crisp-border-subtle); }
|
|
2179
2188
|
& .crisp-recordpage-hl-cell { position: relative; min-height: 84px; padding: var(--crisp-space-2-5) var(--crisp-space-3); background: var(--crisp-bg-surface); }
|
|
2180
2189
|
& .crisp-recordpage-hl-icon { position: absolute; top: var(--crisp-space-2-5); inset-inline-end: var(--crisp-space-3); color: var(--crisp-fg-muted); }
|
|
2181
2190
|
& .crisp-recordpage-hl-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); margin-bottom: var(--crisp-space-2-5); padding-inline-end: var(--crisp-space-4); }
|
|
2182
2191
|
& .crisp-recordpage-hl-value { display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
2183
2192
|
& .crisp-recordpage-hl-value--empty { color: var(--crisp-fg-muted); font-weight: var(--crisp-weight-regular); }
|
|
2193
|
+
|
|
2184
2194
|
& .crisp-recordpage-feed { display: flex; flex-direction: column; }
|
|
2185
2195
|
& .crisp-recordpage-feed-item { display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2) 0; }
|
|
2186
2196
|
& .crisp-recordpage-feed-dot { position: relative; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex-shrink: 0; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
|
|
@@ -2189,6 +2199,7 @@
|
|
|
2189
2199
|
& .crisp-recordpage-feed-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); margin-top: 2px; }
|
|
2190
2200
|
& .crisp-recordpage-feed-more { align-self: flex-start; margin-top: var(--crisp-space-1); border: none; background: none; padding: var(--crisp-space-1) 0; color: var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
2191
2201
|
& .crisp-recordpage-feed-more:hover { text-decoration: underline; }
|
|
2202
|
+
|
|
2192
2203
|
& .crisp-recordpage-section-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-1); }
|
|
2193
2204
|
& .crisp-recordpage-section-head .crisp-recordpage-section-title { padding: 0; }
|
|
2194
2205
|
& .crisp-recordpage-section-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
|
|
@@ -2196,6 +2207,7 @@
|
|
|
2196
2207
|
& .crisp-recordpage-addbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 26px; padding: 0 var(--crisp-space-1-5); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
2197
2208
|
& .crisp-recordpage-addbtn:hover { background: var(--crisp-bg-raised); }
|
|
2198
2209
|
& .crisp-recordpage-addbtn svg { flex-shrink: 0; }
|
|
2210
|
+
|
|
2199
2211
|
& .crisp-recordpage-composer { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding: var(--crisp-space-2); border: 1px solid var(--crisp-border-default); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); margin-bottom: var(--crisp-space-2); }
|
|
2200
2212
|
& .crisp-recordpage-composer textarea { width: 100%; min-height: 56px; resize: vertical; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
|
|
2201
2213
|
& .crisp-recordpage-composer textarea:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); border-radius: var(--crisp-radius-md); }
|
|
@@ -2205,18 +2217,23 @@
|
|
|
2205
2217
|
& .crisp-recordpage-btn:hover { background: var(--crisp-bg-raised); }
|
|
2206
2218
|
& .crisp-recordpage-btn--primary { box-shadow: none; background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); }
|
|
2207
2219
|
& .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
2220
|
+
|
|
2208
2221
|
& .crisp-recordpage-note { display: flex; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
|
|
2209
2222
|
& .crisp-recordpage-note-body { flex: 1; min-width: 0; }
|
|
2210
2223
|
& .crisp-recordpage-note-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-bottom: var(--crisp-space-0-5); }
|
|
2211
2224
|
& .crisp-recordpage-note-author { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
|
|
2212
2225
|
& .crisp-recordpage-note-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
|
|
2213
2226
|
& .crisp-recordpage-note-text { font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
|
|
2227
|
+
|
|
2214
2228
|
& .crisp-recordpage-task { display: flex; align-items: flex-start; gap: var(--crisp-space-2); padding: var(--crisp-space-1-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
|
|
2215
2229
|
& .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); padding-top: 1px; }
|
|
2216
2230
|
& .crisp-recordpage-task[data-done] .crisp-recordpage-task-text { text-decoration: line-through; color: var(--crisp-fg-muted); }
|
|
2231
|
+
|
|
2217
2232
|
& .crisp-recordpage-emptycard { display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-6) var(--crisp-space-3); border: 1px dashed var(--crisp-border-default); border-radius: var(--crisp-radius-xl); color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); text-align: center; }
|
|
2218
2233
|
& .crisp-recordpage-emptycard svg { color: var(--crisp-fg-muted); opacity: 0.7; }
|
|
2234
|
+
|
|
2219
2235
|
& .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
|
|
2236
|
+
|
|
2220
2237
|
& .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
|
|
2221
2238
|
& .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
|
|
2222
2239
|
& .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); }
|
|
@@ -45,16 +45,17 @@ export type DataTableColumn<T> = {
|
|
|
45
45
|
* commits) — see `editor` for `select`/`switch`/`reference`. */
|
|
46
46
|
editable?: boolean;
|
|
47
47
|
/** The inline editor an `editable` column opens. Omitted: a plain field
|
|
48
|
-
* inferred from `type` (number/date/datetime/text)
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* the
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
48
|
+
* inferred from `type` (number/date/datetime/text). `"select"`/
|
|
49
|
+
* `"reference"` render crisp's own `Select` the same way — at rest the
|
|
50
|
+
* cell shows the column's `render`/formatted value like every other
|
|
51
|
+
* editable type (#453), and double-click (or Enter on the selected
|
|
52
|
+
* cell) opens the picker; choosing a value commits and closes it,
|
|
53
|
+
* Escape cancels. `select` reads `options`; `reference` reads
|
|
54
|
+
* `refOptions` and is search-enabled, the same searchable-`Select`
|
|
55
|
+
* shape as `RecordsTable`'s own `ReferenceCell`. `"switch"` is the one
|
|
56
|
+
* exception — ALWAYS its own live widget, no double-click gate, since
|
|
57
|
+
* flipping the toggle IS the commit gesture and there's no meaningfully
|
|
58
|
+
* different "rest" display for a boolean. */
|
|
58
59
|
editor?: "select" | "switch" | "reference";
|
|
59
60
|
/** Options for `editor: "select"` (value/label, an optional `color` a
|
|
60
61
|
* caller's own `render` can read) — same shape as RecordsTable's
|