@crisp-ui-kit/crisp 0.55.1 → 0.55.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 +89 -5
- package/dist/cjs/components/datatable/datatable.recipe.js +33 -0
- package/dist/datatable.css +31 -0
- package/dist/datatable.layered.css +31 -0
- package/dist/esm/components/datatable/datatable.js +90 -6
- package/dist/esm/components/datatable/datatable.recipe.js +33 -0
- package/dist/styles.css +31 -0
- package/dist/styles.layered.css +31 -0
- package/dist/types/components/datatable/datatable.d.ts +23 -3
- package/package.json +1 -1
|
@@ -210,6 +210,19 @@ function parsePhoneValue(v) {
|
|
|
210
210
|
return null;
|
|
211
211
|
return { iso2: s.slice(0, i), digits: s.slice(i + 1) };
|
|
212
212
|
}
|
|
213
|
+
// crisp/#816 -- a `multiValue` phone column's raw value is several
|
|
214
|
+
// `parsePhoneValue`-shaped entries joined with `;`. Empty entries (a blank
|
|
215
|
+
// trailing slot that was never filled) are dropped rather than rendered as
|
|
216
|
+
// a stray chip.
|
|
217
|
+
function parsePhoneValues(v) {
|
|
218
|
+
const s = typeof v === "string" ? v : "";
|
|
219
|
+
if (!s)
|
|
220
|
+
return [];
|
|
221
|
+
return s
|
|
222
|
+
.split(";")
|
|
223
|
+
.map((entry) => parsePhoneValue(entry))
|
|
224
|
+
.filter((p) => !!p?.digits);
|
|
225
|
+
}
|
|
213
226
|
// stable pastel tone per string value — colour-coded tags/statuses
|
|
214
227
|
const TAG_TONES = [
|
|
215
228
|
"brand",
|
|
@@ -430,6 +443,22 @@ function renderTyped(col, value, avatarTone, row, display, locale = "en-US", ope
|
|
|
430
443
|
return (0, jsx_runtime_1.jsx)(RatingStars, { value: n, max: col.maxRating ?? 5 });
|
|
431
444
|
}
|
|
432
445
|
case "phone": {
|
|
446
|
+
// crisp/#816, confirmed live -- a `multiValue` phone cell renders
|
|
447
|
+
// each value as its own real `tel:` link (Attio: a measured link
|
|
448
|
+
// blue, underlined, no flag/dial-code decoration, `4px` flex gap
|
|
449
|
+
// between values), unlike the single-value cell above/below (a plain
|
|
450
|
+
// flag+dial+digits span, never a link). The whole cell hard-clips
|
|
451
|
+
// when it overflows the column -- confirmed live, Attio does not
|
|
452
|
+
// ellipsis a multi-value phone cell.
|
|
453
|
+
if (col.multiValue) {
|
|
454
|
+
const values = parsePhoneValues(value);
|
|
455
|
+
if (!values.length)
|
|
456
|
+
return empty;
|
|
457
|
+
return ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-phone-multi", children: values.map((p) => {
|
|
458
|
+
const country = (0, countries_1.countryByIso2)(p.iso2);
|
|
459
|
+
return ((0, jsx_runtime_1.jsxs)("a", { className: "crisp-datatable-phone-link", href: `tel:+${country?.dialCode ?? ""}${p.digits}`, onClick: (ev) => ev.stopPropagation(), children: ["+", country?.dialCode ?? "?", p.digits] }, `${p.iso2}|${p.digits}`));
|
|
460
|
+
}) }));
|
|
461
|
+
}
|
|
433
462
|
const parsed = parsePhoneValue(value);
|
|
434
463
|
if (!parsed?.digits)
|
|
435
464
|
return empty;
|
|
@@ -446,6 +475,16 @@ function renderTyped(col, value, avatarTone, row, display, locale = "en-US", ope
|
|
|
446
475
|
// link's own navigation must not also fire the row's onRowClick.
|
|
447
476
|
onClick: (ev) => ev.stopPropagation(), children: text }));
|
|
448
477
|
}
|
|
478
|
+
case "date":
|
|
479
|
+
case "datetime": {
|
|
480
|
+
const text = display || formatCell(col.type, value, col.symbol, locale);
|
|
481
|
+
if (!text)
|
|
482
|
+
return empty;
|
|
483
|
+
// crisp#822 -- a column-level predicate, not `tones` (see its doc
|
|
484
|
+
// comment): the danger state is computed over the row, not read back
|
|
485
|
+
// off this formatted string.
|
|
486
|
+
return col.danger?.(value, row) ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-danger", children: text })) : (text);
|
|
487
|
+
}
|
|
449
488
|
default: {
|
|
450
489
|
const text = display || formatCell(col.type, value, col.symbol, locale);
|
|
451
490
|
const isNum = col.type === "number" || col.type === "currency";
|
|
@@ -619,6 +658,44 @@ function DataPhoneEditor({ col, value, isPending, onCommit, }) {
|
|
|
619
658
|
commit(iso2, digits);
|
|
620
659
|
} })] }));
|
|
621
660
|
}
|
|
661
|
+
// crisp/#816, confirmed live -- Attio's own multi-value Phone Number
|
|
662
|
+
// editor (`col.multiValue`): a stack of already-committed values (a
|
|
663
|
+
// drag grip, the value as a real `tel:` link, and a hover-revealed
|
|
664
|
+
// copy/delete pair -- measured `32px` row, `5px 8px` padding, `8px` gap,
|
|
665
|
+
// `16×16` icons) plus one ALWAYS-blank trailing "country + digits" row in
|
|
666
|
+
// `DataPhoneEditor`'s own shape. The blank row itself is the only
|
|
667
|
+
// affordance for adding another value -- there is no separate "+ Add"
|
|
668
|
+
// button; committing it (Enter/blur with digits) appends and clears back
|
|
669
|
+
// to blank so the next value can be typed immediately. Drag-to-reorder
|
|
670
|
+
// (the grip icon) was measured present but isn't wired here -- reordering
|
|
671
|
+
// a committed array is a distinct interaction from add/remove/copy and is
|
|
672
|
+
// left as a follow-up rather than a partial, non-functional drag handle.
|
|
673
|
+
function DataPhoneMultiEditor({ col, value, isPending, onCommit, }) {
|
|
674
|
+
const committed = parsePhoneValues(value);
|
|
675
|
+
const [draftIso2, setDraftIso2] = React.useState("US");
|
|
676
|
+
const [draftDigits, setDraftDigits] = React.useState("");
|
|
677
|
+
const countryOptions = countries_1.COUNTRIES.map((c) => ({
|
|
678
|
+
value: c.iso2,
|
|
679
|
+
label: `${(0, countries_1.flagEmoji)(c.iso2)} ${c.name} +${c.dialCode}`,
|
|
680
|
+
keywords: `${c.name} ${c.iso2} ${c.dialCode}`,
|
|
681
|
+
}));
|
|
682
|
+
const join = (list) => list.map((p) => `${p.iso2}|${p.digits}`).join(";");
|
|
683
|
+
const removeAt = (i) => onCommit(join(committed.filter((_, idx) => idx !== i)));
|
|
684
|
+
const commitDraft = () => {
|
|
685
|
+
if (!draftDigits)
|
|
686
|
+
return;
|
|
687
|
+
onCommit(join([...committed, { iso2: draftIso2, digits: draftDigits }]));
|
|
688
|
+
setDraftDigits("");
|
|
689
|
+
};
|
|
690
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-phone-multi-editor", children: [committed.map((p, i) => {
|
|
691
|
+
const country = (0, countries_1.countryByIso2)(p.iso2);
|
|
692
|
+
const tel = `+${country?.dialCode ?? "?"}${p.digits}`;
|
|
693
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-phone-multi-row", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 16, strokeWidth: 1.75, className: "crisp-datatable-phone-multi-grip", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("a", { className: "crisp-datatable-phone-link", href: `tel:${tel}`, onClick: (ev) => ev.stopPropagation(), children: tel }), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-phone-multi-actions", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-datatable-phone-multi-action", "aria-label": `Copy ${col.key} value`, onClick: () => navigator.clipboard?.writeText(tel), children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 16, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-datatable-phone-multi-action", "aria-label": `Remove ${col.key} value`, disabled: isPending, onClick: () => removeAt(i), children: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 16, strokeWidth: 1.75 }) })] })] }, `${p.iso2}|${p.digits}`));
|
|
694
|
+
}), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-phone-editor", children: [(0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", searchable: true, "aria-label": `Edit ${col.key} country`, value: draftIso2, disabled: isPending, options: countryOptions, onValueChange: setDraftIso2 }), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-phone-dial", "aria-hidden": "true", children: ["+", (0, countries_1.countryByIso2)(draftIso2)?.dialCode ?? "?"] }), (0, jsx_runtime_1.jsx)("input", { className: "crisp-datatable-edit", "aria-label": `Edit ${col.key} number (add another)`, value: draftDigits, disabled: isPending, onChange: (e) => setDraftDigits(e.currentTarget.value), onBlur: commitDraft, onKeyDown: (e) => {
|
|
695
|
+
if (e.key === "Enter")
|
|
696
|
+
commitDraft();
|
|
697
|
+
} })] })] }));
|
|
698
|
+
}
|
|
622
699
|
// crisp/#762, confirmed live -- Attio's own Timestamp editor is a calendar +
|
|
623
700
|
// time + preset picker, not the native `datetime-local` input `type:
|
|
624
701
|
// "datetime"` gets by default (that stays the DEFAULT, unchanged, for
|
|
@@ -1651,17 +1728,23 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
1651
1728
|
// value while showing the rejection's message beside the cell (reject).
|
|
1652
1729
|
// The revert needs no extra code: this component never mutates `data`
|
|
1653
1730
|
// itself, so once `editing` clears, the cell simply shows `data` again.
|
|
1654
|
-
|
|
1731
|
+
// crisp/#816 -- `keepEditing` lets a multi-value editor stay open across
|
|
1732
|
+
// a commit (adding/removing one value must not close the whole cell, the
|
|
1733
|
+
// way it does for every other, single-value editor). Default false
|
|
1734
|
+
// preserves every existing editor's own close-on-commit behaviour.
|
|
1735
|
+
function commitEdit(id, key, value, keepEditing = false) {
|
|
1655
1736
|
setEditError(null);
|
|
1656
1737
|
const result = onEdit?.(id, key, value);
|
|
1657
1738
|
if (result && typeof result.then === "function") {
|
|
1658
1739
|
setPending({ id, key });
|
|
1659
1740
|
result.then(() => {
|
|
1660
1741
|
setPending(null);
|
|
1661
|
-
|
|
1742
|
+
if (!keepEditing)
|
|
1743
|
+
setEditing(null);
|
|
1662
1744
|
}, (err) => {
|
|
1663
1745
|
setPending(null);
|
|
1664
|
-
|
|
1746
|
+
if (!keepEditing)
|
|
1747
|
+
setEditing(null);
|
|
1665
1748
|
setEditError({
|
|
1666
1749
|
id,
|
|
1667
1750
|
key,
|
|
@@ -1674,7 +1757,8 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
1674
1757
|
});
|
|
1675
1758
|
return;
|
|
1676
1759
|
}
|
|
1677
|
-
|
|
1760
|
+
if (!keepEditing)
|
|
1761
|
+
setEditing(null);
|
|
1678
1762
|
}
|
|
1679
1763
|
const reduceMotion = (0, core_1.usePrefersReducedMotion)();
|
|
1680
1764
|
// crisp/#577 -- keyboard nav to an off-screen (virtualized-out) cell
|
|
@@ -2213,7 +2297,7 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
2213
2297
|
// portaled popover, so a handler here can't
|
|
2214
2298
|
// rely on the key event ever bubbling through
|
|
2215
2299
|
// this element (#453).
|
|
2216
|
-
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) })) : col.editor === "phone" ? ((0, jsx_runtime_1.jsx)(DataPhoneEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v) })) : col.editor === "datetime" ? ((0, jsx_runtime_1.jsx)(DataDateTimeEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v) })) : ((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));
|
|
2300
|
+
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) })) : col.editor === "phone" && col.multiValue ? ((0, jsx_runtime_1.jsx)(DataPhoneMultiEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v, true) })) : col.editor === "phone" ? ((0, jsx_runtime_1.jsx)(DataPhoneEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v) })) : col.editor === "datetime" ? ((0, jsx_runtime_1.jsx)(DataDateTimeEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v) })) : ((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));
|
|
2217
2301
|
}
|
|
2218
2302
|
if (isEditing) {
|
|
2219
2303
|
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, ...rowHeaderProps, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-datatable-edit",
|
|
@@ -277,6 +277,10 @@ const config = (0, core_1.defineRecipe)({
|
|
|
277
277
|
& .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, ${tokens_1.contract.bgOrange} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgOrange} 82%, ${tokens_1.contract.fgPrimary}); }
|
|
278
278
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
279
279
|
& .crisp-datatable-muted { color: ${tokens_1.contract.fgMuted}; }
|
|
280
|
+
/* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
|
|
281
|
+
TasksTable's own overdue due-date (crisp#462), plain text colour, no
|
|
282
|
+
fill/weight change. */
|
|
283
|
+
& .crisp-datatable-danger { color: ${tokens_1.contract.fgDanger}; }
|
|
280
284
|
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
281
285
|
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
282
286
|
rest, underline on hover, own-colour text rather than a browser-blue
|
|
@@ -290,6 +294,35 @@ const config = (0, core_1.defineRecipe)({
|
|
|
290
294
|
& .crisp-datatable-phone-dial { color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); }
|
|
291
295
|
& .crisp-datatable-phone-editor { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["2"]}; }
|
|
292
296
|
& .crisp-datatable-phone-editor input { flex: 1; min-width: 0; }
|
|
297
|
+
/* multi-value phone (#816) -- confirmed live (a real custom "Multiple
|
|
298
|
+
values" Phone Number attribute, and the built-in People "Phone
|
|
299
|
+
numbers" field): the collapsed cell is a 4px-gap flex row of real
|
|
300
|
+
tel: links in a measured link blue (~rgb(59,108,232) pixel-sampled;
|
|
301
|
+
reused as the closest already-tokened blue, brand600, rather than
|
|
302
|
+
adding a third blue token for a delta this small), underlined,
|
|
303
|
+
UNLIKE the single-value cell above (plain text, no link, no
|
|
304
|
+
underline) -- multi-value is the one shape Attio makes into a real
|
|
305
|
+
link. The cell hard-clips on overflow (no ellipsis -- confirmed
|
|
306
|
+
live, a second value can end mid-digit with no "..."). */
|
|
307
|
+
& .crisp-datatable-phone-multi { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; overflow: hidden; }
|
|
308
|
+
& .crisp-datatable-phone-link { color: ${tokens_1.contract.bgBrandSolid}; text-decoration: underline; white-space: nowrap; }
|
|
309
|
+
/* multi-value editor (#816) -- confirmed live: a stack of committed-
|
|
310
|
+
value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
|
|
311
|
+
the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
|
|
312
|
+
DataPhoneEditor-shaped row -- that blank row is the only "add
|
|
313
|
+
another" affordance, there's no separate button for it. The
|
|
314
|
+
copy/delete pair reuses .crisp-datatable-entity-action's own
|
|
315
|
+
hover-reveal chrome (not independently re-measured as its own hit
|
|
316
|
+
target this pass, only the 16px icon glyphs were). */
|
|
317
|
+
& .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
|
|
318
|
+
& .crisp-datatable-phone-multi-row { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: ${tokens_1.contract.size.menuRow}; padding: 5px ${tokens_1.contract.space["2"]}; }
|
|
319
|
+
& .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
320
|
+
& .crisp-datatable-phone-multi-grip { color: ${tokens_1.contract.fgMuted}; flex-shrink: 0; cursor: grab; }
|
|
321
|
+
& .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
|
|
322
|
+
& .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
|
|
323
|
+
& .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
|
|
324
|
+
& .crisp-datatable-phone-multi-action { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: ${tokens_1.contract.radius.sm}; background: none; font-family: inherit; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 55%, transparent); cursor: pointer; flex-shrink: 0; }
|
|
325
|
+
& .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, transparent); color: ${tokens_1.contract.fgPrimary}; }
|
|
293
326
|
/* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
|
|
294
327
|
& .crisp-datatable-datetime-editor { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["2"]}; }
|
|
295
328
|
/* rating cell type/editor (#760) -- measured live: 16px stars on an
|
package/dist/datatable.css
CHANGED
|
@@ -208,6 +208,9 @@
|
|
|
208
208
|
& .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
|
|
209
209
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
210
210
|
& .crisp-datatable-muted { color: var(--crisp-fg-muted); }
|
|
211
|
+
/* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
|
|
212
|
+
TasksTable's own overdue due-date (crisp#462), plain text colour, no fill/weight change. */
|
|
213
|
+
& .crisp-datatable-danger { color: var(--crisp-fg-danger); }
|
|
211
214
|
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
212
215
|
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
213
216
|
rest, underline on hover, own-colour text rather than a browser-blue
|
|
@@ -221,6 +224,34 @@
|
|
|
221
224
|
& .crisp-datatable-phone-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
|
|
222
225
|
& .crisp-datatable-phone-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
223
226
|
& .crisp-datatable-phone-editor input { flex: 1; min-width: 0; }
|
|
227
|
+
/* multi-value phone (#816) -- confirmed live (a real custom "Multiple
|
|
228
|
+
values" Phone Number attribute, and the built-in People "Phone
|
|
229
|
+
numbers" field): the collapsed cell is a 4px-gap flex row of real
|
|
230
|
+
tel: links in a measured link blue (~rgb(59,108,232) pixel-sampled;
|
|
231
|
+
reused as the closest already-tokened blue, brand600, rather than
|
|
232
|
+
adding a third blue token for a delta this small), underlined,
|
|
233
|
+
UNLIKE the single-value cell above (plain text, no link, no
|
|
234
|
+
underline) -- multi-value is the one shape Attio makes into a real
|
|
235
|
+
link. The cell hard-clips on overflow (no ellipsis -- confirmed
|
|
236
|
+
live, a second value can end mid-digit with no "..."). */
|
|
237
|
+
& .crisp-datatable-phone-multi { display: inline-flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
|
|
238
|
+
& .crisp-datatable-phone-link { color: var(--crisp-bg-brand-solid); text-decoration: underline; white-space: nowrap; }
|
|
239
|
+
/* multi-value editor (#816) -- confirmed live: a stack of committed-
|
|
240
|
+
value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
|
|
241
|
+
the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
|
|
242
|
+
DataPhoneEditor-shaped row -- that blank row is the only "add
|
|
243
|
+
another" affordance, there's no separate button for it. The copy/delete pair reuses .crisp-datatable-entity-action's own
|
|
244
|
+
hover-reveal chrome (not independently re-measured as its own hit
|
|
245
|
+
target this pass, only the 16px icon glyphs were). */
|
|
246
|
+
& .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
|
|
247
|
+
& .crisp-datatable-phone-multi-row { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); padding: 5px var(--crisp-space-2); }
|
|
248
|
+
& .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
249
|
+
& .crisp-datatable-phone-multi-grip { color: var(--crisp-fg-muted); flex-shrink: 0; cursor: grab; }
|
|
250
|
+
& .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
|
|
251
|
+
& .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
|
|
252
|
+
& .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
|
|
253
|
+
& .crisp-datatable-phone-multi-action { 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; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
|
|
254
|
+
& .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
|
|
224
255
|
/* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
|
|
225
256
|
& .crisp-datatable-datetime-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
226
257
|
/* rating cell type/editor (#760) -- measured live: 16px stars on an
|
|
@@ -209,6 +209,9 @@
|
|
|
209
209
|
& .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
|
|
210
210
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
211
211
|
& .crisp-datatable-muted { color: var(--crisp-fg-muted); }
|
|
212
|
+
/* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
|
|
213
|
+
TasksTable's own overdue due-date (crisp#462), plain text colour, no fill/weight change. */
|
|
214
|
+
& .crisp-datatable-danger { color: var(--crisp-fg-danger); }
|
|
212
215
|
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
213
216
|
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
214
217
|
rest, underline on hover, own-colour text rather than a browser-blue
|
|
@@ -222,6 +225,34 @@
|
|
|
222
225
|
& .crisp-datatable-phone-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
|
|
223
226
|
& .crisp-datatable-phone-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
224
227
|
& .crisp-datatable-phone-editor input { flex: 1; min-width: 0; }
|
|
228
|
+
/* multi-value phone (#816) -- confirmed live (a real custom "Multiple
|
|
229
|
+
values" Phone Number attribute, and the built-in People "Phone
|
|
230
|
+
numbers" field): the collapsed cell is a 4px-gap flex row of real
|
|
231
|
+
tel: links in a measured link blue (~rgb(59,108,232) pixel-sampled;
|
|
232
|
+
reused as the closest already-tokened blue, brand600, rather than
|
|
233
|
+
adding a third blue token for a delta this small), underlined,
|
|
234
|
+
UNLIKE the single-value cell above (plain text, no link, no
|
|
235
|
+
underline) -- multi-value is the one shape Attio makes into a real
|
|
236
|
+
link. The cell hard-clips on overflow (no ellipsis -- confirmed
|
|
237
|
+
live, a second value can end mid-digit with no "..."). */
|
|
238
|
+
& .crisp-datatable-phone-multi { display: inline-flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
|
|
239
|
+
& .crisp-datatable-phone-link { color: var(--crisp-bg-brand-solid); text-decoration: underline; white-space: nowrap; }
|
|
240
|
+
/* multi-value editor (#816) -- confirmed live: a stack of committed-
|
|
241
|
+
value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
|
|
242
|
+
the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
|
|
243
|
+
DataPhoneEditor-shaped row -- that blank row is the only "add
|
|
244
|
+
another" affordance, there's no separate button for it. The copy/delete pair reuses .crisp-datatable-entity-action's own
|
|
245
|
+
hover-reveal chrome (not independently re-measured as its own hit
|
|
246
|
+
target this pass, only the 16px icon glyphs were). */
|
|
247
|
+
& .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
|
|
248
|
+
& .crisp-datatable-phone-multi-row { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); padding: 5px var(--crisp-space-2); }
|
|
249
|
+
& .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
250
|
+
& .crisp-datatable-phone-multi-grip { color: var(--crisp-fg-muted); flex-shrink: 0; cursor: grab; }
|
|
251
|
+
& .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
|
|
252
|
+
& .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
|
|
253
|
+
& .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
|
|
254
|
+
& .crisp-datatable-phone-multi-action { 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; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
|
|
255
|
+
& .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
|
|
225
256
|
/* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
|
|
226
257
|
& .crisp-datatable-datetime-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
227
258
|
/* rating cell type/editor (#760) -- measured live: 16px stars on an
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ArrowDown, ArrowUp, Check, ChevronsUpDown, CircleUserRound, ClipboardPaste, Copy, GripVertical, History, Lock, PanelRight, Plus, Search, SlidersHorizontal, X, } from "lucide-react";
|
|
2
|
+
import { ArrowDown, ArrowUp, Check, ChevronsUpDown, CircleUserRound, ClipboardPaste, Copy, GripVertical, History, Lock, PanelRight, Plus, Search, SlidersHorizontal, Trash2, X, } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cellDatePrecision, formatCellDate, isRtlElement, parseCellDate, useCrispMessage, useCrispMessages, usePrefersReducedMotion, } from "../../core/index.js";
|
|
5
5
|
// crisp/#601 supersession: a deliberate exception to "DS atoms stay
|
|
@@ -172,6 +172,19 @@ function parsePhoneValue(v) {
|
|
|
172
172
|
return null;
|
|
173
173
|
return { iso2: s.slice(0, i), digits: s.slice(i + 1) };
|
|
174
174
|
}
|
|
175
|
+
// crisp/#816 -- a `multiValue` phone column's raw value is several
|
|
176
|
+
// `parsePhoneValue`-shaped entries joined with `;`. Empty entries (a blank
|
|
177
|
+
// trailing slot that was never filled) are dropped rather than rendered as
|
|
178
|
+
// a stray chip.
|
|
179
|
+
function parsePhoneValues(v) {
|
|
180
|
+
const s = typeof v === "string" ? v : "";
|
|
181
|
+
if (!s)
|
|
182
|
+
return [];
|
|
183
|
+
return s
|
|
184
|
+
.split(";")
|
|
185
|
+
.map((entry) => parsePhoneValue(entry))
|
|
186
|
+
.filter((p) => !!p?.digits);
|
|
187
|
+
}
|
|
175
188
|
// stable pastel tone per string value — colour-coded tags/statuses
|
|
176
189
|
const TAG_TONES = [
|
|
177
190
|
"brand",
|
|
@@ -392,6 +405,22 @@ function renderTyped(col, value, avatarTone, row, display, locale = "en-US", ope
|
|
|
392
405
|
return _jsx(RatingStars, { value: n, max: col.maxRating ?? 5 });
|
|
393
406
|
}
|
|
394
407
|
case "phone": {
|
|
408
|
+
// crisp/#816, confirmed live -- a `multiValue` phone cell renders
|
|
409
|
+
// each value as its own real `tel:` link (Attio: a measured link
|
|
410
|
+
// blue, underlined, no flag/dial-code decoration, `4px` flex gap
|
|
411
|
+
// between values), unlike the single-value cell above/below (a plain
|
|
412
|
+
// flag+dial+digits span, never a link). The whole cell hard-clips
|
|
413
|
+
// when it overflows the column -- confirmed live, Attio does not
|
|
414
|
+
// ellipsis a multi-value phone cell.
|
|
415
|
+
if (col.multiValue) {
|
|
416
|
+
const values = parsePhoneValues(value);
|
|
417
|
+
if (!values.length)
|
|
418
|
+
return empty;
|
|
419
|
+
return (_jsx("span", { className: "crisp-datatable-phone-multi", children: values.map((p) => {
|
|
420
|
+
const country = countryByIso2(p.iso2);
|
|
421
|
+
return (_jsxs("a", { className: "crisp-datatable-phone-link", href: `tel:+${country?.dialCode ?? ""}${p.digits}`, onClick: (ev) => ev.stopPropagation(), children: ["+", country?.dialCode ?? "?", p.digits] }, `${p.iso2}|${p.digits}`));
|
|
422
|
+
}) }));
|
|
423
|
+
}
|
|
395
424
|
const parsed = parsePhoneValue(value);
|
|
396
425
|
if (!parsed?.digits)
|
|
397
426
|
return empty;
|
|
@@ -408,6 +437,16 @@ function renderTyped(col, value, avatarTone, row, display, locale = "en-US", ope
|
|
|
408
437
|
// link's own navigation must not also fire the row's onRowClick.
|
|
409
438
|
onClick: (ev) => ev.stopPropagation(), children: text }));
|
|
410
439
|
}
|
|
440
|
+
case "date":
|
|
441
|
+
case "datetime": {
|
|
442
|
+
const text = display || formatCell(col.type, value, col.symbol, locale);
|
|
443
|
+
if (!text)
|
|
444
|
+
return empty;
|
|
445
|
+
// crisp#822 -- a column-level predicate, not `tones` (see its doc
|
|
446
|
+
// comment): the danger state is computed over the row, not read back
|
|
447
|
+
// off this formatted string.
|
|
448
|
+
return col.danger?.(value, row) ? (_jsx("span", { className: "crisp-datatable-danger", children: text })) : (text);
|
|
449
|
+
}
|
|
411
450
|
default: {
|
|
412
451
|
const text = display || formatCell(col.type, value, col.symbol, locale);
|
|
413
452
|
const isNum = col.type === "number" || col.type === "currency";
|
|
@@ -581,6 +620,44 @@ function DataPhoneEditor({ col, value, isPending, onCommit, }) {
|
|
|
581
620
|
commit(iso2, digits);
|
|
582
621
|
} })] }));
|
|
583
622
|
}
|
|
623
|
+
// crisp/#816, confirmed live -- Attio's own multi-value Phone Number
|
|
624
|
+
// editor (`col.multiValue`): a stack of already-committed values (a
|
|
625
|
+
// drag grip, the value as a real `tel:` link, and a hover-revealed
|
|
626
|
+
// copy/delete pair -- measured `32px` row, `5px 8px` padding, `8px` gap,
|
|
627
|
+
// `16×16` icons) plus one ALWAYS-blank trailing "country + digits" row in
|
|
628
|
+
// `DataPhoneEditor`'s own shape. The blank row itself is the only
|
|
629
|
+
// affordance for adding another value -- there is no separate "+ Add"
|
|
630
|
+
// button; committing it (Enter/blur with digits) appends and clears back
|
|
631
|
+
// to blank so the next value can be typed immediately. Drag-to-reorder
|
|
632
|
+
// (the grip icon) was measured present but isn't wired here -- reordering
|
|
633
|
+
// a committed array is a distinct interaction from add/remove/copy and is
|
|
634
|
+
// left as a follow-up rather than a partial, non-functional drag handle.
|
|
635
|
+
function DataPhoneMultiEditor({ col, value, isPending, onCommit, }) {
|
|
636
|
+
const committed = parsePhoneValues(value);
|
|
637
|
+
const [draftIso2, setDraftIso2] = React.useState("US");
|
|
638
|
+
const [draftDigits, setDraftDigits] = React.useState("");
|
|
639
|
+
const countryOptions = COUNTRIES.map((c) => ({
|
|
640
|
+
value: c.iso2,
|
|
641
|
+
label: `${flagEmoji(c.iso2)} ${c.name} +${c.dialCode}`,
|
|
642
|
+
keywords: `${c.name} ${c.iso2} ${c.dialCode}`,
|
|
643
|
+
}));
|
|
644
|
+
const join = (list) => list.map((p) => `${p.iso2}|${p.digits}`).join(";");
|
|
645
|
+
const removeAt = (i) => onCommit(join(committed.filter((_, idx) => idx !== i)));
|
|
646
|
+
const commitDraft = () => {
|
|
647
|
+
if (!draftDigits)
|
|
648
|
+
return;
|
|
649
|
+
onCommit(join([...committed, { iso2: draftIso2, digits: draftDigits }]));
|
|
650
|
+
setDraftDigits("");
|
|
651
|
+
};
|
|
652
|
+
return (_jsxs("span", { className: "crisp-datatable-phone-multi-editor", children: [committed.map((p, i) => {
|
|
653
|
+
const country = countryByIso2(p.iso2);
|
|
654
|
+
const tel = `+${country?.dialCode ?? "?"}${p.digits}`;
|
|
655
|
+
return (_jsxs("span", { className: "crisp-datatable-phone-multi-row", children: [_jsx(GripVertical, { size: 16, strokeWidth: 1.75, className: "crisp-datatable-phone-multi-grip", "aria-hidden": "true" }), _jsx("a", { className: "crisp-datatable-phone-link", href: `tel:${tel}`, onClick: (ev) => ev.stopPropagation(), children: tel }), _jsxs("span", { className: "crisp-datatable-phone-multi-actions", children: [_jsx("button", { type: "button", className: "crisp-datatable-phone-multi-action", "aria-label": `Copy ${col.key} value`, onClick: () => navigator.clipboard?.writeText(tel), children: _jsx(Copy, { size: 16, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "crisp-datatable-phone-multi-action", "aria-label": `Remove ${col.key} value`, disabled: isPending, onClick: () => removeAt(i), children: _jsx(Trash2, { size: 16, strokeWidth: 1.75 }) })] })] }, `${p.iso2}|${p.digits}`));
|
|
656
|
+
}), _jsxs("span", { className: "crisp-datatable-phone-editor", children: [_jsx(Select, { variant: "ghost", searchable: true, "aria-label": `Edit ${col.key} country`, value: draftIso2, disabled: isPending, options: countryOptions, onValueChange: setDraftIso2 }), _jsxs("span", { className: "crisp-datatable-phone-dial", "aria-hidden": "true", children: ["+", countryByIso2(draftIso2)?.dialCode ?? "?"] }), _jsx("input", { className: "crisp-datatable-edit", "aria-label": `Edit ${col.key} number (add another)`, value: draftDigits, disabled: isPending, onChange: (e) => setDraftDigits(e.currentTarget.value), onBlur: commitDraft, onKeyDown: (e) => {
|
|
657
|
+
if (e.key === "Enter")
|
|
658
|
+
commitDraft();
|
|
659
|
+
} })] })] }));
|
|
660
|
+
}
|
|
584
661
|
// crisp/#762, confirmed live -- Attio's own Timestamp editor is a calendar +
|
|
585
662
|
// time + preset picker, not the native `datetime-local` input `type:
|
|
586
663
|
// "datetime"` gets by default (that stays the DEFAULT, unchanged, for
|
|
@@ -1613,17 +1690,23 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
1613
1690
|
// value while showing the rejection's message beside the cell (reject).
|
|
1614
1691
|
// The revert needs no extra code: this component never mutates `data`
|
|
1615
1692
|
// itself, so once `editing` clears, the cell simply shows `data` again.
|
|
1616
|
-
|
|
1693
|
+
// crisp/#816 -- `keepEditing` lets a multi-value editor stay open across
|
|
1694
|
+
// a commit (adding/removing one value must not close the whole cell, the
|
|
1695
|
+
// way it does for every other, single-value editor). Default false
|
|
1696
|
+
// preserves every existing editor's own close-on-commit behaviour.
|
|
1697
|
+
function commitEdit(id, key, value, keepEditing = false) {
|
|
1617
1698
|
setEditError(null);
|
|
1618
1699
|
const result = onEdit?.(id, key, value);
|
|
1619
1700
|
if (result && typeof result.then === "function") {
|
|
1620
1701
|
setPending({ id, key });
|
|
1621
1702
|
result.then(() => {
|
|
1622
1703
|
setPending(null);
|
|
1623
|
-
|
|
1704
|
+
if (!keepEditing)
|
|
1705
|
+
setEditing(null);
|
|
1624
1706
|
}, (err) => {
|
|
1625
1707
|
setPending(null);
|
|
1626
|
-
|
|
1708
|
+
if (!keepEditing)
|
|
1709
|
+
setEditing(null);
|
|
1627
1710
|
setEditError({
|
|
1628
1711
|
id,
|
|
1629
1712
|
key,
|
|
@@ -1636,7 +1719,8 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
1636
1719
|
});
|
|
1637
1720
|
return;
|
|
1638
1721
|
}
|
|
1639
|
-
|
|
1722
|
+
if (!keepEditing)
|
|
1723
|
+
setEditing(null);
|
|
1640
1724
|
}
|
|
1641
1725
|
const reduceMotion = usePrefersReducedMotion();
|
|
1642
1726
|
// crisp/#577 -- keyboard nav to an off-screen (virtualized-out) cell
|
|
@@ -2175,7 +2259,7 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
2175
2259
|
// portaled popover, so a handler here can't
|
|
2176
2260
|
// rely on the key event ever bubbling through
|
|
2177
2261
|
// this element (#453).
|
|
2178
|
-
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) })) : col.editor === "phone" ? (_jsx(DataPhoneEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v) })) : col.editor === "datetime" ? (_jsx(DataDateTimeEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v) })) : (_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));
|
|
2262
|
+
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) })) : col.editor === "phone" && col.multiValue ? (_jsx(DataPhoneMultiEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v, true) })) : col.editor === "phone" ? (_jsx(DataPhoneEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v) })) : col.editor === "datetime" ? (_jsx(DataDateTimeEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v) })) : (_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));
|
|
2179
2263
|
}
|
|
2180
2264
|
if (isEditing) {
|
|
2181
2265
|
return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, ...rowHeaderProps, onClick: (e) => e.stopPropagation(), children: [_jsx("input", { className: "crisp-datatable-edit",
|
|
@@ -274,6 +274,10 @@ const config = defineRecipe({
|
|
|
274
274
|
& .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, ${c.bgOrange} 16%, transparent); color: color-mix(in srgb, ${c.bgOrange} 82%, ${c.fgPrimary}); }
|
|
275
275
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
276
276
|
& .crisp-datatable-muted { color: ${c.fgMuted}; }
|
|
277
|
+
/* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
|
|
278
|
+
TasksTable's own overdue due-date (crisp#462), plain text colour, no
|
|
279
|
+
fill/weight change. */
|
|
280
|
+
& .crisp-datatable-danger { color: ${c.fgDanger}; }
|
|
277
281
|
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
278
282
|
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
279
283
|
rest, underline on hover, own-colour text rather than a browser-blue
|
|
@@ -287,6 +291,35 @@ const config = defineRecipe({
|
|
|
287
291
|
& .crisp-datatable-phone-dial { color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
|
|
288
292
|
& .crisp-datatable-phone-editor { display: flex; align-items: center; gap: ${c.space["1"]}; height: ${c.size.tableRow}; padding: 0 ${c.space["2"]}; }
|
|
289
293
|
& .crisp-datatable-phone-editor input { flex: 1; min-width: 0; }
|
|
294
|
+
/* multi-value phone (#816) -- confirmed live (a real custom "Multiple
|
|
295
|
+
values" Phone Number attribute, and the built-in People "Phone
|
|
296
|
+
numbers" field): the collapsed cell is a 4px-gap flex row of real
|
|
297
|
+
tel: links in a measured link blue (~rgb(59,108,232) pixel-sampled;
|
|
298
|
+
reused as the closest already-tokened blue, brand600, rather than
|
|
299
|
+
adding a third blue token for a delta this small), underlined,
|
|
300
|
+
UNLIKE the single-value cell above (plain text, no link, no
|
|
301
|
+
underline) -- multi-value is the one shape Attio makes into a real
|
|
302
|
+
link. The cell hard-clips on overflow (no ellipsis -- confirmed
|
|
303
|
+
live, a second value can end mid-digit with no "..."). */
|
|
304
|
+
& .crisp-datatable-phone-multi { display: inline-flex; align-items: center; gap: ${c.space["1"]}; overflow: hidden; }
|
|
305
|
+
& .crisp-datatable-phone-link { color: ${c.bgBrandSolid}; text-decoration: underline; white-space: nowrap; }
|
|
306
|
+
/* multi-value editor (#816) -- confirmed live: a stack of committed-
|
|
307
|
+
value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
|
|
308
|
+
the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
|
|
309
|
+
DataPhoneEditor-shaped row -- that blank row is the only "add
|
|
310
|
+
another" affordance, there's no separate button for it. The
|
|
311
|
+
copy/delete pair reuses .crisp-datatable-entity-action's own
|
|
312
|
+
hover-reveal chrome (not independently re-measured as its own hit
|
|
313
|
+
target this pass, only the 16px icon glyphs were). */
|
|
314
|
+
& .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
|
|
315
|
+
& .crisp-datatable-phone-multi-row { display: flex; align-items: center; gap: ${c.space["2"]}; height: ${c.size.menuRow}; padding: 5px ${c.space["2"]}; }
|
|
316
|
+
& .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
317
|
+
& .crisp-datatable-phone-multi-grip { color: ${c.fgMuted}; flex-shrink: 0; cursor: grab; }
|
|
318
|
+
& .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: ${c.space["1"]}; margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
|
|
319
|
+
& .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
|
|
320
|
+
& .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
|
|
321
|
+
& .crisp-datatable-phone-multi-action { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: ${c.radius.sm}; background: none; font-family: inherit; color: color-mix(in srgb, ${c.fgPrimary} 55%, transparent); cursor: pointer; flex-shrink: 0; }
|
|
322
|
+
& .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, ${c.fgPrimary} 4%, transparent); color: ${c.fgPrimary}; }
|
|
290
323
|
/* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
|
|
291
324
|
& .crisp-datatable-datetime-editor { display: flex; align-items: center; gap: ${c.space["1"]}; height: ${c.size.tableRow}; padding: 0 ${c.space["2"]}; }
|
|
292
325
|
/* rating cell type/editor (#760) -- measured live: 16px stars on an
|
package/dist/styles.css
CHANGED
|
@@ -807,6 +807,9 @@
|
|
|
807
807
|
& .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
|
|
808
808
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
809
809
|
& .crisp-datatable-muted { color: var(--crisp-fg-muted); }
|
|
810
|
+
/* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
|
|
811
|
+
TasksTable's own overdue due-date (crisp#462), plain text colour, no fill/weight change. */
|
|
812
|
+
& .crisp-datatable-danger { color: var(--crisp-fg-danger); }
|
|
810
813
|
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
811
814
|
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
812
815
|
rest, underline on hover, own-colour text rather than a browser-blue
|
|
@@ -820,6 +823,34 @@
|
|
|
820
823
|
& .crisp-datatable-phone-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
|
|
821
824
|
& .crisp-datatable-phone-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
822
825
|
& .crisp-datatable-phone-editor input { flex: 1; min-width: 0; }
|
|
826
|
+
/* multi-value phone (#816) -- confirmed live (a real custom "Multiple
|
|
827
|
+
values" Phone Number attribute, and the built-in People "Phone
|
|
828
|
+
numbers" field): the collapsed cell is a 4px-gap flex row of real
|
|
829
|
+
tel: links in a measured link blue (~rgb(59,108,232) pixel-sampled;
|
|
830
|
+
reused as the closest already-tokened blue, brand600, rather than
|
|
831
|
+
adding a third blue token for a delta this small), underlined,
|
|
832
|
+
UNLIKE the single-value cell above (plain text, no link, no
|
|
833
|
+
underline) -- multi-value is the one shape Attio makes into a real
|
|
834
|
+
link. The cell hard-clips on overflow (no ellipsis -- confirmed
|
|
835
|
+
live, a second value can end mid-digit with no "..."). */
|
|
836
|
+
& .crisp-datatable-phone-multi { display: inline-flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
|
|
837
|
+
& .crisp-datatable-phone-link { color: var(--crisp-bg-brand-solid); text-decoration: underline; white-space: nowrap; }
|
|
838
|
+
/* multi-value editor (#816) -- confirmed live: a stack of committed-
|
|
839
|
+
value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
|
|
840
|
+
the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
|
|
841
|
+
DataPhoneEditor-shaped row -- that blank row is the only "add
|
|
842
|
+
another" affordance, there's no separate button for it. The copy/delete pair reuses .crisp-datatable-entity-action's own
|
|
843
|
+
hover-reveal chrome (not independently re-measured as its own hit
|
|
844
|
+
target this pass, only the 16px icon glyphs were). */
|
|
845
|
+
& .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
|
|
846
|
+
& .crisp-datatable-phone-multi-row { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); padding: 5px var(--crisp-space-2); }
|
|
847
|
+
& .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
848
|
+
& .crisp-datatable-phone-multi-grip { color: var(--crisp-fg-muted); flex-shrink: 0; cursor: grab; }
|
|
849
|
+
& .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
|
|
850
|
+
& .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
|
|
851
|
+
& .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
|
|
852
|
+
& .crisp-datatable-phone-multi-action { 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; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
|
|
853
|
+
& .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
|
|
823
854
|
/* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
|
|
824
855
|
& .crisp-datatable-datetime-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
825
856
|
/* rating cell type/editor (#760) -- measured live: 16px stars on an
|
package/dist/styles.layered.css
CHANGED
|
@@ -808,6 +808,9 @@
|
|
|
808
808
|
& .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
|
|
809
809
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
810
810
|
& .crisp-datatable-muted { color: var(--crisp-fg-muted); }
|
|
811
|
+
/* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
|
|
812
|
+
TasksTable's own overdue due-date (crisp#462), plain text colour, no fill/weight change. */
|
|
813
|
+
& .crisp-datatable-danger { color: var(--crisp-fg-danger); }
|
|
811
814
|
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
812
815
|
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
813
816
|
rest, underline on hover, own-colour text rather than a browser-blue
|
|
@@ -821,6 +824,34 @@
|
|
|
821
824
|
& .crisp-datatable-phone-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
|
|
822
825
|
& .crisp-datatable-phone-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
823
826
|
& .crisp-datatable-phone-editor input { flex: 1; min-width: 0; }
|
|
827
|
+
/* multi-value phone (#816) -- confirmed live (a real custom "Multiple
|
|
828
|
+
values" Phone Number attribute, and the built-in People "Phone
|
|
829
|
+
numbers" field): the collapsed cell is a 4px-gap flex row of real
|
|
830
|
+
tel: links in a measured link blue (~rgb(59,108,232) pixel-sampled;
|
|
831
|
+
reused as the closest already-tokened blue, brand600, rather than
|
|
832
|
+
adding a third blue token for a delta this small), underlined,
|
|
833
|
+
UNLIKE the single-value cell above (plain text, no link, no
|
|
834
|
+
underline) -- multi-value is the one shape Attio makes into a real
|
|
835
|
+
link. The cell hard-clips on overflow (no ellipsis -- confirmed
|
|
836
|
+
live, a second value can end mid-digit with no "..."). */
|
|
837
|
+
& .crisp-datatable-phone-multi { display: inline-flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
|
|
838
|
+
& .crisp-datatable-phone-link { color: var(--crisp-bg-brand-solid); text-decoration: underline; white-space: nowrap; }
|
|
839
|
+
/* multi-value editor (#816) -- confirmed live: a stack of committed-
|
|
840
|
+
value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
|
|
841
|
+
the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
|
|
842
|
+
DataPhoneEditor-shaped row -- that blank row is the only "add
|
|
843
|
+
another" affordance, there's no separate button for it. The copy/delete pair reuses .crisp-datatable-entity-action's own
|
|
844
|
+
hover-reveal chrome (not independently re-measured as its own hit
|
|
845
|
+
target this pass, only the 16px icon glyphs were). */
|
|
846
|
+
& .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
|
|
847
|
+
& .crisp-datatable-phone-multi-row { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); padding: 5px var(--crisp-space-2); }
|
|
848
|
+
& .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
849
|
+
& .crisp-datatable-phone-multi-grip { color: var(--crisp-fg-muted); flex-shrink: 0; cursor: grab; }
|
|
850
|
+
& .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
|
|
851
|
+
& .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
|
|
852
|
+
& .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
|
|
853
|
+
& .crisp-datatable-phone-multi-action { 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; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
|
|
854
|
+
& .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
|
|
824
855
|
/* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
|
|
825
856
|
& .crisp-datatable-datetime-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
826
857
|
/* rating cell type/editor (#760) -- measured live: 16px stars on an
|
|
@@ -50,9 +50,9 @@ export type DataCellType = "text" | "entity" | "tags" | "status" | "date" | "dat
|
|
|
50
50
|
* country selection survives even for a national number that's still
|
|
51
51
|
* being typed (an E.164 string alone can't distinguish "no country
|
|
52
52
|
* chosen yet" from "the digits happen to start with a valid dial
|
|
53
|
-
* code"). See `editor: "phone"` for the editable picker.
|
|
54
|
-
* (Attio's own "Multiple values" constraint on this
|
|
55
|
-
*
|
|
53
|
+
* code"). See `editor: "phone"` for the editable picker. See `multiValue`
|
|
54
|
+
* (crisp#816) for Attio's own "Multiple values" constraint on this
|
|
55
|
+
* type. */
|
|
56
56
|
| "phone";
|
|
57
57
|
export type DataTableColumn<T> = {
|
|
58
58
|
key: string;
|
|
@@ -87,6 +87,26 @@ export type DataTableColumn<T> = {
|
|
|
87
87
|
resizable?: boolean;
|
|
88
88
|
/** Tone overrides for `status`/`tags` values (else a stable colour per value). */
|
|
89
89
|
tones?: Record<string, string>;
|
|
90
|
+
/** `type: "date"`/`"datetime"` only: render this cell in Attio's own
|
|
91
|
+
* measured overdue/danger red (`fgDanger`, `#eb3b3b` — the same token
|
|
92
|
+
* `TasksTable`'s own overdue due-date already uses, crisp#462) when the
|
|
93
|
+
* predicate returns true. A per-row PREDICATE rather than `tones`'s
|
|
94
|
+
* value-keyed lookup, because a date's danger state is computed over the
|
|
95
|
+
* WHOLE ROW (e.g. a precomputed "overdue" bucket) — `tones` can only key
|
|
96
|
+
* off the formatted string, which a date doesn't have a closed set of
|
|
97
|
+
* (crisp#822). */
|
|
98
|
+
danger?: (value: unknown, row: T) => boolean;
|
|
99
|
+
/** This column holds more than one value per row -- Attio's own
|
|
100
|
+
* "Multiple values" constraint (crisp#816, confirmed live on the Phone
|
|
101
|
+
* Number attribute type: Constraints → Multiple values, a per-attribute
|
|
102
|
+
* toggle, absent for e.g. Text). Built out for `type`/`editor: "phone"`
|
|
103
|
+
* only this pass; the raw value is that type's normal per-value string,
|
|
104
|
+
* several of them joined with `;` (`"MN|88112233;US|4155550123"`) rather
|
|
105
|
+
* than JSON -- the same "a real separator earns its keep" reasoning
|
|
106
|
+
* `phone`'s own single-value `"{iso2}|{digits}"` shape already uses.
|
|
107
|
+
* Structural flag lives on the column (type-agnostic, per the issue)
|
|
108
|
+
* even though only one type renders it today. */
|
|
109
|
+
multiValue?: boolean;
|
|
90
110
|
/** Enable click-to-sort on this column's header. */
|
|
91
111
|
sortable?: boolean;
|
|
92
112
|
/** Exclude this column from the search filter (default: included). */
|