@crisp-ui-kit/crisp 0.54.5 → 0.54.7
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/authshell.css +4 -0
- package/dist/authshell.layered.css +4 -0
- package/dist/cjs/components/datatable/datatable.js +28 -5
- package/dist/cjs/components/datatable/datatable.recipe.js +6 -0
- package/dist/cjs/components/personchip/personchip.js +7 -3
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +10 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +9 -3
- package/dist/cjs/components/tooltip/tooltip.js +2 -2
- package/dist/cjs/components/tooltip/tooltip.recipe.js +37 -0
- package/dist/cjs/product/authshell/authshell.js +1 -1
- package/dist/cjs/product/authshell/authshell.recipe.js +18 -0
- package/dist/cjs/product/recordstable/recordstable.js +12 -3
- package/dist/cjs/product/richtext/richtext.js +31 -4
- package/dist/datatable.css +8 -4
- package/dist/datatable.layered.css +8 -4
- package/dist/esm/components/datatable/datatable.js +28 -5
- package/dist/esm/components/datatable/datatable.recipe.js +6 -0
- package/dist/esm/components/personchip/personchip.js +7 -3
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +10 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +10 -4
- package/dist/esm/components/tooltip/tooltip.js +2 -2
- package/dist/esm/components/tooltip/tooltip.recipe.js +37 -0
- package/dist/esm/product/authshell/authshell.js +1 -1
- package/dist/esm/product/authshell/authshell.recipe.js +18 -0
- package/dist/esm/product/recordstable/recordstable.js +12 -3
- package/dist/esm/product/richtext/richtext.js +31 -4
- package/dist/recordstable.css +2 -1
- package/dist/recordstable.layered.css +2 -1
- package/dist/styles.css +23 -6
- package/dist/styles.layered.css +23 -6
- package/dist/tabletoolbar.css +2 -1
- package/dist/tabletoolbar.layered.css +2 -1
- package/dist/tooltip.css +7 -0
- package/dist/tooltip.d.ts +2 -2
- package/dist/tooltip.layered.css +7 -0
- package/dist/types/components/datatable/datatable.d.ts +50 -11
- package/dist/types/components/personchip/personchip.d.ts +14 -2
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +4 -2
- package/dist/types/components/tooltip/tooltip.d.ts +16 -0
- package/dist/types/components/tooltip/tooltip.recipe.d.ts +30 -2
- package/dist/types/product/authshell/authshell.d.ts +4 -1
- package/dist/types/product/recordstable/recordstable.d.ts +12 -0
- package/package.json +1 -1
|
@@ -26,8 +26,16 @@ export function isLabelTruncated(el) {
|
|
|
26
26
|
* viewport change, header text change) via `ResizeObserver`; SSR-safe the
|
|
27
27
|
* same way `MiddleTruncate` is -- the DOM is only touched inside an effect,
|
|
28
28
|
* so the server (and the first client paint) render the closed tooltip.
|
|
29
|
+
*
|
|
30
|
+
* #580: a `referencedType` column appends that type's icon + name after the
|
|
31
|
+
* label (Attio's own reference-column tooltip, e.g. "Primary location" +
|
|
32
|
+
* Country's icon + "Country") and uses `Tooltip`'s `"reference"` chrome
|
|
33
|
+
* variant -- Attio's second, structurally distinct tooltip (same
|
|
34
|
+
* background/colour/radius/padding/font as the plain one, a different
|
|
35
|
+
* `box-shadow`, crisp#563). Every other column stays exactly as #534/#558
|
|
36
|
+
* shipped: label-only, the `"plain"` variant.
|
|
29
37
|
*/
|
|
30
|
-
function ThLabel({ header }) {
|
|
38
|
+
function ThLabel({ header, referencedType, }) {
|
|
31
39
|
const ref = React.useRef(null);
|
|
32
40
|
const [overflowing, setOverflowing] = React.useState(false);
|
|
33
41
|
// biome-ignore lint/correctness/useExhaustiveDependencies: `header` isn't read in the body, but a rename (col.header changing with the column's own width unchanged) needs a re-measure a ResizeObserver on `el` won't fire for -- same reasoning as Tabs' own re-measure effect.
|
|
@@ -56,7 +64,9 @@ function ThLabel({ header }) {
|
|
|
56
64
|
// actually show. `open={false}` when it fits keeps the Tooltip mounted
|
|
57
65
|
// (asChild costs no extra DOM either way) but pins it permanently closed,
|
|
58
66
|
// so hovering a non-overflowing header still opens nothing.
|
|
59
|
-
|
|
67
|
+
const ReferenceIcon = referencedType?.icon;
|
|
68
|
+
const content = referencedType ? (_jsxs("span", { className: "crisp-tooltip-reference-row", children: [header, ReferenceIcon && _jsx(ReferenceIcon, { size: 16, strokeWidth: 1.75 }), referencedType.label] })) : (header);
|
|
69
|
+
return (_jsx(Tooltip, { content: content, asChild: true, variant: referencedType ? "reference" : "plain", open: overflowing ? undefined : false, children: _jsx("span", { ref: ref, className: "crisp-datatable-th-label", children: header }) }));
|
|
60
70
|
}
|
|
61
71
|
/** A `date` input needs `yyyy-mm-dd`; cells may hold a Date, an ISO string or
|
|
62
72
|
* a display string. Returns "" when it cannot be parsed, so the field opens
|
|
@@ -252,6 +262,16 @@ function renderTyped(col, value, avatarTone, row, display, locale = "en-US") {
|
|
|
252
262
|
// translation) can never silently repaint the chip
|
|
253
263
|
return (_jsx("span", { className: "crisp-datatable-tag", "data-tone": col.tones?.[s] ?? toneFor(s), children: display || s }));
|
|
254
264
|
}
|
|
265
|
+
case "url":
|
|
266
|
+
case "email": {
|
|
267
|
+
const text = display || asText(value);
|
|
268
|
+
if (!text)
|
|
269
|
+
return empty;
|
|
270
|
+
return (_jsx("a", { className: "crisp-datatable-link", href: col.type === "url" ? `https://${text}` : `mailto:${text}`, target: col.type === "url" ? "_blank" : undefined, rel: col.type === "url" ? "noreferrer" : undefined,
|
|
271
|
+
// Same reasoning as `entity`'s `onEntityClick` link above: the
|
|
272
|
+
// link's own navigation must not also fire the row's onRowClick.
|
|
273
|
+
onClick: (ev) => ev.stopPropagation(), children: text }));
|
|
274
|
+
}
|
|
255
275
|
default: {
|
|
256
276
|
const text = display || formatCell(col.type, value, col.symbol, locale);
|
|
257
277
|
const isNum = col.type === "number" || col.type === "currency";
|
|
@@ -387,7 +407,8 @@ function DataReferenceEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
|
387
407
|
keywords: query,
|
|
388
408
|
});
|
|
389
409
|
}
|
|
390
|
-
|
|
410
|
+
const isActor = col.editor === "actor";
|
|
411
|
+
return (_jsx("span", { ref: ref, style: { display: "contents" }, children: _jsx(Select, { variant: "ghost", searchable: true, searchPlaceholder: col.refSearchPlaceholder ?? (isActor ? "Search people…" : undefined), emptyLabel: col.refEmptyLabel ?? (isActor ? "No users" : undefined), onSearchChange: setQuery, "aria-label": `Edit ${col.key}`, value: asText(value) || undefined, disabled: isPending, options: options, onValueChange: (v) => {
|
|
391
412
|
if (v === CREATE_REFERENCE_VALUE)
|
|
392
413
|
col.onCreateReference?.(query);
|
|
393
414
|
else
|
|
@@ -1162,7 +1183,7 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
1162
1183
|
? "ascending"
|
|
1163
1184
|
: "descending"
|
|
1164
1185
|
: "none";
|
|
1165
|
-
const inner = (_jsxs("span", { className: "crisp-datatable-th-inner", children: [Icon ? _jsx(Icon, { size: 16, strokeWidth: 1.75 }) : null, _jsx(ThLabel, { header: col.header }), col.sortable ? _jsx(ChevronFor, { colKey: col.key }) : null] }));
|
|
1186
|
+
const inner = (_jsxs("span", { className: "crisp-datatable-th-inner", children: [Icon ? _jsx(Icon, { size: 16, strokeWidth: 1.75 }) : null, _jsx(ThLabel, { header: col.header, referencedType: col.referencedType }), col.sortable ? _jsx(ChevronFor, { colKey: col.key }) : null] }));
|
|
1166
1187
|
return (_jsxs(TableHead, { align: alignFor(col), "aria-sort": ariaSort, "aria-colindex": colAriaIndex(i), ...dataStickyCol(i), children: [renaming === col.key ? (_jsx("input", {
|
|
1167
1188
|
// biome-ignore lint/a11y/noAutofocus: the rename item opens this field
|
|
1168
1189
|
autoFocus: true, className: "crisp-datatable-th-rename", defaultValue: typeof col.header === "string" ? col.header : "", "aria-label": renameColumnLabel, onBlur: (e) => {
|
|
@@ -1243,7 +1264,9 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
1243
1264
|
return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...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));
|
|
1244
1265
|
}
|
|
1245
1266
|
if (isEditing &&
|
|
1246
|
-
(col.editor === "select" ||
|
|
1267
|
+
(col.editor === "select" ||
|
|
1268
|
+
col.editor === "reference" ||
|
|
1269
|
+
col.editor === "actor")) {
|
|
1247
1270
|
return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined,
|
|
1248
1271
|
// Same as the plain text editor's own cell:
|
|
1249
1272
|
// opening/using the picker must not also fire
|
|
@@ -141,6 +141,12 @@ const config = defineRecipe({
|
|
|
141
141
|
& .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}); }
|
|
142
142
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
143
143
|
& .crisp-datatable-muted { color: ${c.fgMuted}; }
|
|
144
|
+
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
145
|
+
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
146
|
+
rest, underline on hover, own-colour text rather than a browser-blue
|
|
147
|
+
link. */
|
|
148
|
+
& .crisp-datatable-link { color: ${c.fgPrimary}; text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
149
|
+
& .crisp-datatable-link:hover { text-decoration: underline; }
|
|
144
150
|
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
145
151
|
a leading 20px grip column, a 16px grip icon, and the row's own
|
|
146
152
|
hairline doubling as the drop indicator (it tints toward the brand
|
|
@@ -39,15 +39,19 @@ function initials(name) {
|
|
|
39
39
|
* Composes `Chip`'s `truncate` CSS directly rather than its string-only
|
|
40
40
|
* auto-wrap (children here is an avatar plus a name, not a single string),
|
|
41
41
|
* so the name gets its own `.crisp-chip-label` span while the avatar stays
|
|
42
|
-
* fixed-size.
|
|
42
|
+
* fixed-size. `trailing` (#683) is the same idea for a consumer's own
|
|
43
|
+
* icon/badge after the name — inside the chip's own flex row rather than a
|
|
44
|
+
* second one a consumer would otherwise wrap this in, which needs its own
|
|
45
|
+
* `min-width: 0` to keep shrinking correctly and, missing that, overflows
|
|
46
|
+
* its container the same way #668 did before `Chip` itself was fixed.
|
|
43
47
|
*/
|
|
44
|
-
export function PersonChip({ name, avatar, muted, className, ...props }) {
|
|
48
|
+
export function PersonChip({ name, avatar, muted, trailing, className, ...props }) {
|
|
45
49
|
return (_jsxs(Chip, { tone: "neutral", truncate: true, className: className, ...props, children: [_jsx(Avatar, { size: "sm", shape: "circle", tone: avatar ? "neutral" : hashTone(name),
|
|
46
50
|
// Explicit rather than relying on the flex default (a fixed-size,
|
|
47
51
|
// non-wrapping element's own min-content floor already resists
|
|
48
52
|
// shrinking in every browser tested) — #668 asked "does the avatar
|
|
49
53
|
// stay" as its own question, so it gets its own undeniable answer
|
|
50
54
|
// rather than an implicit one.
|
|
51
|
-
style: { flexShrink: 0 }, children: avatar ? _jsx("img", { src: avatar, alt: "" }) : initials(name) }), _jsx("span", { className: "crisp-chip-label", style: muted ? { color: c.fgNeutralMuted } : undefined, children: name })] }));
|
|
55
|
+
style: { flexShrink: 0 }, children: avatar ? _jsx("img", { src: avatar, alt: "" }) : initials(name) }), _jsx("span", { className: "crisp-chip-label", style: muted ? { color: c.fgNeutralMuted } : undefined, children: name }), trailing != null && (_jsx("span", { className: "crisp-personchip-trailing", style: { flexShrink: 0 }, children: trailing }))] }));
|
|
52
56
|
}
|
|
53
57
|
PersonChip.displayName = "PersonChip";
|
|
@@ -14,6 +14,14 @@ import { contract as c, elevation as e } from "../../tokens/index.js";
|
|
|
14
14
|
// block, and those depend on following `.menu` in source order. Keeping the
|
|
15
15
|
// split preserves that cascade exactly, which is what lets the generated CSS
|
|
16
16
|
// stay byte-for-byte identical to what shipped before the extraction.
|
|
17
|
+
//
|
|
18
|
+
// `.menu--op` (#594 follow-up) is the filter condition's own operator list —
|
|
19
|
+
// measured live against Attio's own popover at 135px (rounds to 134px), not
|
|
20
|
+
// the default 232px every other menu here uses. `.menu-item` also picked up
|
|
21
|
+
// `line-height: 20px` and `font-weight: medium` from the same measurement
|
|
22
|
+
// (its 8 rows read 14px/20px/500) — shared by every menu that renders one,
|
|
23
|
+
// not just the operator list, since crisp's menu-item typography was
|
|
24
|
+
// previously unset rather than wrong.
|
|
17
25
|
export const toolbarChromeHead = (p) => ` & .${CLASS_PREFIX}${p}-toolbar { display: flex; align-items: center; gap: ${c.space["2"]}; padding: 0 ${c.space["3"]} ${c.space["2"]}; }
|
|
18
26
|
& .${CLASS_PREFIX}${p}-toolbar[data-inline] { padding-bottom: 0; }
|
|
19
27
|
& .${CLASS_PREFIX}${p}-search { display: flex; align-items: center; gap: ${c.space["1.5"]}; height: ${c.size.row}; padding: 0 ${c.space["2.5"]}; border: none; border-radius: ${c.radius.md}; background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px ${c.borderSubtle}; color: ${c.fgMuted}; }
|
|
@@ -52,7 +60,7 @@ export const toolbarChromeBody = (p) => ` & .${CLASS_PREFIX}${p}-menu-head {
|
|
|
52
60
|
& .${CLASS_PREFIX}${p}-sort-learn { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; font-size: ${c.text.lg}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); text-decoration: none; }
|
|
53
61
|
& .${CLASS_PREFIX}${p}-sort-learn svg { flex-shrink: 0; }
|
|
54
62
|
& .${CLASS_PREFIX}${p}-sort-learn:hover { color: ${c.fgPrimary}; }
|
|
55
|
-
& .${CLASS_PREFIX}${p}-menu-item { display: flex; align-items: center; gap: ${c.space["1.5"]}; width: 100%; height: ${c.size.menuRow}; margin: 0; padding: 0 ${c.space["2"]}; border: none; background: none; border-radius: ${c.radius.md}; font-family: inherit; font-size: ${c.text.lg}; color: ${c.fgPrimary}; text-align: left; cursor: pointer; }
|
|
63
|
+
& .${CLASS_PREFIX}${p}-menu-item { display: flex; align-items: center; gap: ${c.space["1.5"]}; width: 100%; height: ${c.size.menuRow}; margin: 0; padding: 0 ${c.space["2"]}; border: none; background: none; border-radius: ${c.radius.md}; font-family: inherit; font-size: ${c.text.lg}; line-height: 20px; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; text-align: left; cursor: pointer; }
|
|
56
64
|
& .${CLASS_PREFIX}${p}-menu-item:hover { background: ${c.bgRaised}; }
|
|
57
65
|
& .${CLASS_PREFIX}${p}-menu-item:disabled { opacity: 0.4; cursor: default; }
|
|
58
66
|
& .${CLASS_PREFIX}${p}-menu-item:disabled:hover { background: none; }
|
|
@@ -65,6 +73,7 @@ export const toolbarChromeBody = (p) => ` & .${CLASS_PREFIX}${p}-menu-head {
|
|
|
65
73
|
& .${CLASS_PREFIX}${p}-menu--right { left: auto; right: 0; }
|
|
66
74
|
& .${CLASS_PREFIX}${p}-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
|
|
67
75
|
& .${CLASS_PREFIX}${p}-menu--hdr { min-width: 200px; }
|
|
76
|
+
& .${CLASS_PREFIX}${p}-menu--op { min-width: 134px; }
|
|
68
77
|
& .${CLASS_PREFIX}${p}-menu-item--danger { color: ${c.bgDanger}; }
|
|
69
78
|
& .${CLASS_PREFIX}${p}-menu-item--danger svg { color: ${c.bgDanger}; }
|
|
70
79
|
& .${CLASS_PREFIX}${p}-menu-item--danger:hover { background: color-mix(in srgb, ${c.bgDanger} 10%, transparent); }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ArrowDownWideNarrow, ArrowUpDown, ArrowUpNarrowWide, Check, CircleHelp, GripVertical, ListFilter, MoreVertical, Plus, SlidersHorizontal, X, } from "lucide-react";
|
|
2
|
+
import { ArrowDownWideNarrow, ArrowUpDown, ArrowUpNarrowWide, Check, CircleHelp, GripVertical, ListFilter, MoreVertical, Plus, SlidersHorizontal, Trash2, X, } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { isInsideLaterFloatingLayer, useCrispMessage, useCrispMessages, useFocusAfterRemoval, } from "../../core/index.js";
|
|
5
5
|
import { MenuCheckboxItem, useMenuKeyboard } from "../menu/index.js";
|
|
@@ -13,8 +13,14 @@ const OP_LABELS = {
|
|
|
13
13
|
is: "is",
|
|
14
14
|
isnot: "is not",
|
|
15
15
|
isanyof: "is any of",
|
|
16
|
-
empty
|
|
17
|
-
|
|
16
|
+
// Attio's own text says "empty"/"not empty", not "is empty"/"is not empty"
|
|
17
|
+
// -- #619's own issue body quotes it verbatim this way, and #593's number/
|
|
18
|
+
// date operator sets ("is / is not / less than / greater than / empty /
|
|
19
|
+
// not empty") repeat it. #619 ported the 3 new ops but left these two
|
|
20
|
+
// untouched from the pre-existing 5-op set, which had drifted; caught in
|
|
21
|
+
// review.
|
|
22
|
+
empty: "empty",
|
|
23
|
+
notempty: "not empty",
|
|
18
24
|
};
|
|
19
25
|
// #619: order and label set match Attio's own text-filter operator picker
|
|
20
26
|
// verbatim (measured live, per RecordsTable's own #578 port of the same
|
|
@@ -368,7 +374,7 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
368
374
|
const hasValue = (!opNeedsValue(cond.op) || cond.value.trim()) &&
|
|
369
375
|
(!opNeedsValues(cond.op) || (cond.values?.length ?? 0) > 0);
|
|
370
376
|
return (_jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("span", { className: "crisp-tabletoolbar-fchip", children: [_jsxs("button", { type: "button", className: "crisp-tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [_jsx("span", { className: "crisp-tabletoolbar-fchip-field", children: col?.label }), hasValue ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "crisp-tabletoolbar-fchip-op", children: crispMessages[OP_LABELS[cond.op]] ??
|
|
371
|
-
OP_LABELS[cond.op] }), opNeedsValue(cond.op) && (_jsx("span", { className: "crisp-tabletoolbar-fchip-val", children: cond.value })), cond.op === "isanyof" && cond.values && (_jsx("span", { className: "crisp-tabletoolbar-fchip-val", children: isAnyOfSummary(cond.values) }))] })) : (_jsx("span", { className: "crisp-tabletoolbar-fchip-op", children: selectConditionLabel }))] }), _jsx("button", { type: "button", className: "crisp-tabletoolbar-fchip-x", "data-menu-trigger": true, "aria-haspopup": "menu", "aria-expanded": kebabOpen, "aria-label": `${col?.label} ${filterOptionsLabel}`, onClick: () => setOpenMenu(kebabOpen ? null : `fkebab:${cond.id}`), children: _jsx(MoreVertical, { size: 13, strokeWidth: 2 }) })] }), kebabOpen && (_jsx("div", { ref: kebabMenuRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-menu--right crisp-tabletoolbar-menu--hdr", role: "menu", "aria-label": `${col?.label} ${filterOptionsLabel}`, onKeyDown: onKebabKeyDown, children:
|
|
377
|
+
OP_LABELS[cond.op] }), opNeedsValue(cond.op) && (_jsx("span", { className: "crisp-tabletoolbar-fchip-val", children: cond.value })), cond.op === "isanyof" && cond.values && (_jsx("span", { className: "crisp-tabletoolbar-fchip-val", children: isAnyOfSummary(cond.values) }))] })) : (_jsx("span", { className: "crisp-tabletoolbar-fchip-op", children: selectConditionLabel }))] }), _jsx("button", { type: "button", className: "crisp-tabletoolbar-fchip-x", "data-menu-trigger": true, "aria-haspopup": "menu", "aria-expanded": kebabOpen, "aria-label": `${col?.label} ${filterOptionsLabel}`, onClick: () => setOpenMenu(kebabOpen ? null : `fkebab:${cond.id}`), children: _jsx(MoreVertical, { size: 13, strokeWidth: 2 }) })] }), kebabOpen && (_jsx("div", { ref: kebabMenuRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-menu--right crisp-tabletoolbar-menu--hdr", role: "menu", "aria-label": `${col?.label} ${filterOptionsLabel}`, onKeyDown: onKebabKeyDown, children: _jsxs("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item crisp-tabletoolbar-menu-item--danger", onClick: () => removeFilter(cond.id), children: [_jsx(Trash2, { size: 16, strokeWidth: 1.75 }), deleteFilterLabel] }) })), open && (_jsxs("div", { className: "crisp-tabletoolbar-menu crisp-tabletoolbar-menu--op", role: "dialog", "aria-label": `${col?.label} ${crispMessages.condition ?? "condition"}`, children: [_jsxs("div", { className: "crisp-tabletoolbar-menu-head", children: [col?.label, " ", crispMessages.condition ?? "condition"] }), opsFor(col).map((op) => (_jsxs("button", { type: "button", className: "crisp-tabletoolbar-menu-item", "data-on": cond.op === op ? "" : undefined, onClick: () => patchFilter(cond.id, { op }), children: [crispMessages[OP_LABELS[op]] ?? OP_LABELS[op], cond.op === op ? (_jsx(Check, { className: "crisp-tabletoolbar-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, op))), opNeedsValue(cond.op) && (_jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter value`,
|
|
372
378
|
// biome-ignore lint/a11y/noAutofocus: focus the value field
|
|
373
379
|
autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) })), cond.op === "isanyof" && col?.values && (_jsxs(_Fragment, { children: [_jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter values`,
|
|
374
380
|
// biome-ignore lint/a11y/noAutofocus: focus the search field
|
|
@@ -32,7 +32,7 @@ function mergeRefs(...refs) {
|
|
|
32
32
|
* carry them for the same reason it can't carry `aria-describedby` (below):
|
|
33
33
|
* a screen reader reads state off the focused element, not an ancestor.
|
|
34
34
|
*/
|
|
35
|
-
export const Tooltip = React.forwardRef(({ content, children, side = "top", delay = 350, open: openProp, defaultOpen, onOpenChange, asChild = false, "aria-describedby": restDescribedBy,
|
|
35
|
+
export const Tooltip = React.forwardRef(({ content, children, side = "top", delay = 350, open: openProp, defaultOpen, onOpenChange, asChild = false, variant = "plain", "aria-describedby": restDescribedBy,
|
|
36
36
|
// Not forwarded, even though a composer's other rest props are
|
|
37
37
|
// (below): `style` sizes a VISUAL box, and Tooltip's wrapper has none
|
|
38
38
|
// of its own -- the box that matters is whatever the consumer's own
|
|
@@ -116,7 +116,7 @@ style: _style, ...rest }, forwardedRef) => {
|
|
|
116
116
|
};
|
|
117
117
|
const portal = open &&
|
|
118
118
|
pos != null &&
|
|
119
|
-
createPortal(_jsx("div", { id: tipId, role: "tooltip", className: tooltipRecipe(), style: {
|
|
119
|
+
createPortal(_jsx("div", { id: tipId, role: "tooltip", className: tooltipRecipe({ variant }), style: {
|
|
120
120
|
left: pos.left,
|
|
121
121
|
top: pos.top,
|
|
122
122
|
transform: pos.below
|
|
@@ -11,6 +11,16 @@ import { contract as c } from "../../tokens/index.js";
|
|
|
11
11
|
// (#101112, the same "dark pill" ink NavItem's own text uses) and
|
|
12
12
|
// line100's light hex (#eeeff1) — both theme-constant, same as before,
|
|
13
13
|
// just the wrong pair.
|
|
14
|
+
//
|
|
15
|
+
// crisp#563: this `boxShadow` is Attio's PLAIN tooltip (a truncated header
|
|
16
|
+
// label, measured on "Twitter follower count") — still an uncorroborated
|
|
17
|
+
// single sample, flagged for its own re-measurement pass, not touched here.
|
|
18
|
+
// Attio has a SECOND, visually similar but structurally different tooltip
|
|
19
|
+
// for a reference-column header (icon + the referenced type's name after
|
|
20
|
+
// the label, crisp#580) with its own measured shadow — an inset ring plus
|
|
21
|
+
// two offset shadows, `variant: "reference"` below. Background, text
|
|
22
|
+
// colour, radius, padding and font are identical between the two measured
|
|
23
|
+
// samples; only the shadow differs.
|
|
14
24
|
const spec = defineSpec({
|
|
15
25
|
name: "tooltip",
|
|
16
26
|
base: {
|
|
@@ -35,7 +45,34 @@ const spec = defineSpec({
|
|
|
35
45
|
// An at-rule whose body is declarations rather than rules, so a part
|
|
36
46
|
// expresses it directly: the entry state the overlay animates from.
|
|
37
47
|
{ selector: "@starting-style", decls: { opacity: 0 } },
|
|
48
|
+
// #580: the icon + referenced-type-name row a `variant="reference"`
|
|
49
|
+
// consumer (DataTable/RecordsTable's `ThLabel`) composes into `content`.
|
|
50
|
+
// Lives here, not in the caller's inline style, so it reads its gap from
|
|
51
|
+
// the token contract like every other measured value on this surface —
|
|
52
|
+
// `content` is portaled straight into this bubble regardless of which
|
|
53
|
+
// component built the JSX, so a nested selector reaches it the same way
|
|
54
|
+
// it reaches anything else rendered inside `.crisp-tooltip`.
|
|
55
|
+
{
|
|
56
|
+
selector: "& .crisp-tooltip-reference-row",
|
|
57
|
+
decls: {
|
|
58
|
+
display: "inline-flex",
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
gap: c.space["1"],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
38
63
|
],
|
|
64
|
+
variants: {
|
|
65
|
+
variant: {
|
|
66
|
+
// Inherits `base`'s boxShadow untouched -- crisp#563 updates that
|
|
67
|
+
// single value when it re-measures the plain tooltip, and this
|
|
68
|
+
// variant picks the correction up for free.
|
|
69
|
+
plain: {},
|
|
70
|
+
reference: {
|
|
71
|
+
boxShadow: "rgba(0,0,0,0) 0 0 0 1px inset, rgba(28,40,64,0.18) 0 0 2px 0, rgba(0,0,0,0.04) 0 1px 3px 0",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
defaultVariants: { variant: "plain" },
|
|
39
76
|
});
|
|
40
77
|
const config = specToRecipe(spec);
|
|
41
78
|
export const tooltipRecipe = resolveRecipe(config);
|
|
@@ -13,6 +13,6 @@ export function AuthShell({ progress, logo, title, children, preview, footer, cl
|
|
|
13
13
|
const pct = hasProgress
|
|
14
14
|
? Math.round(Math.max(0, Math.min(1, progress)) * 100)
|
|
15
15
|
: 0;
|
|
16
|
-
return (_jsxs("div", { className: authshellRecipe({ className }), ...rest, children: [hasProgress && (_jsx("div", { className: "crisp-authshell-progress", role: "progressbar", "aria-valuenow": pct, "aria-valuemin": 0, "aria-valuemax": 100, children: _jsx("div", { className: "crisp-authshell-progress-fill", style: { width: `${pct}%` } }) })), logo != null && _jsx("div", { className: "crisp-authshell-logo", children: logo }), _jsxs("div", { className: "crisp-authshell-card", children: [_jsx("div", { className: "crisp-authshell-pane", children: _jsxs("div", { className: "crisp-authshell-body", children: [title != null && (_jsx("div", { className: "crisp-authshell-title", children: title })), _jsx("div", { className: "crisp-authshell-form", children: children })] }) }), preview != null && (_jsx("div", { className: "crisp-authshell-preview", children: preview }))] }), footer != null && _jsx("div", { className: "crisp-authshell-footer", children: footer })] }));
|
|
16
|
+
return (_jsxs("div", { className: authshellRecipe({ className }), "data-single-pane": preview == null ? "" : undefined, ...rest, children: [hasProgress && (_jsx("div", { className: "crisp-authshell-progress", role: "progressbar", "aria-valuenow": pct, "aria-valuemin": 0, "aria-valuemax": 100, children: _jsx("div", { className: "crisp-authshell-progress-fill", style: { width: `${pct}%` } }) })), logo != null && _jsx("div", { className: "crisp-authshell-logo", children: logo }), _jsxs("div", { className: "crisp-authshell-card", children: [_jsx("div", { className: "crisp-authshell-pane", children: _jsxs("div", { className: "crisp-authshell-body", children: [title != null && (_jsx("div", { className: "crisp-authshell-title", children: title })), _jsx("div", { className: "crisp-authshell-form", children: children })] }) }), preview != null && (_jsx("div", { className: "crisp-authshell-preview", children: preview }))] }), footer != null && _jsx("div", { className: "crisp-authshell-footer", children: footer })] }));
|
|
17
17
|
}
|
|
18
18
|
AuthShell.displayName = "AuthShell";
|
|
@@ -7,25 +7,43 @@ import { contract as c } from "../../tokens/index.js";
|
|
|
7
7
|
// from the live app, EXCEPT the 1200px preview-hiding breakpoint (called out
|
|
8
8
|
// at the container query below) — the pane drops out in JS somewhere between a
|
|
9
9
|
// measured 954px and 1393px viewport width, so 1200px is chosen, not measured.
|
|
10
|
+
//
|
|
11
|
+
// crisp#653: that two-pane card's own 692px height, border and 420px pane
|
|
12
|
+
// width were measured for a SPECIFIC screen with a real product-preview
|
|
13
|
+
// pane -- omitting `preview` (`data-single-pane`, set by the component) used
|
|
14
|
+
// to keep all three anyway, leaving a bordered box with a huge empty area
|
|
15
|
+
// under short content. Attio's own single-pane screen (`app.attio.com/login`,
|
|
16
|
+
// reachable in a fresh logged-out browser context -- no session needed) has
|
|
17
|
+
// NO card at all: every ancestor of its "Sign in" heading measured
|
|
18
|
+
// `border: 0px none`, `box-shadow: none`, `background: transparent`, a
|
|
19
|
+
// 360-364px content column (not 420px), sized to its own content and
|
|
20
|
+
// roughly centred in the viewport rather than pinned near the top.
|
|
21
|
+
// `data-single-pane` now drops the card chrome and narrows the body to
|
|
22
|
+
// match; the two-pane card is unchanged.
|
|
10
23
|
const config = defineRecipe({
|
|
11
24
|
name: "authshell",
|
|
12
25
|
base: `min-height: 100%; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; position: relative;
|
|
13
26
|
background: ${c.bgSurface}; font-family: ${c.font.sans}; font-size: ${c.text.lg}; line-height: 20px; color: ${c.fgPrimary};
|
|
14
27
|
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
15
28
|
|
|
29
|
+
&[data-single-pane] { justify-content: center; }
|
|
30
|
+
|
|
16
31
|
& .crisp-authshell-progress { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: ${c.borderDefault}; }
|
|
17
32
|
& .crisp-authshell-progress-fill { height: 2px; background: ${c.fgPrimary}; border-radius: 4px; transition: width .2s; }
|
|
18
33
|
|
|
19
34
|
& .crisp-authshell-logo { margin-top: ${c.space["8"]}; height: 24px; display: flex; align-items: center; }
|
|
20
35
|
|
|
21
36
|
& .crisp-authshell-card { margin-top: ${c.space["6"]}; display: flex; height: 692px; border-radius: 20px; border: 1px solid ${c.borderSubtle}; overflow: hidden; }
|
|
37
|
+
&[data-single-pane] .crisp-authshell-card { height: auto; border: none; border-radius: 0; overflow: visible; }
|
|
22
38
|
|
|
23
39
|
& .crisp-authshell-pane { flex: 0 0 564px; padding: 72px; display: flex; flex-direction: column; box-sizing: border-box; }
|
|
40
|
+
&[data-single-pane] .crisp-authshell-pane { flex: 0 0 auto; padding: 0; }
|
|
24
41
|
|
|
25
42
|
& .crisp-authshell-preview { flex: 0 0 564px; padding: 96px 64px; background: ${c.bgSubtle}; border-inline-start: 1px solid color-mix(in srgb, ${c.fgPrimary} 5%, transparent); position: relative; overflow: hidden; box-sizing: border-box; }
|
|
26
43
|
& .crisp-authshell-preview::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 120px; background: linear-gradient(color-mix(in srgb, ${c.bgSubtle} 0%, transparent) 0%, ${c.bgSubtle} 100%); pointer-events: none; }
|
|
27
44
|
|
|
28
45
|
& .crisp-authshell-body { width: 420px; margin: 0 auto; display: flex; flex-direction: column; gap: ${c.space["8"]}; align-items: center; }
|
|
46
|
+
&[data-single-pane] .crisp-authshell-body { width: 360px; }
|
|
29
47
|
|
|
30
48
|
& .crisp-authshell-title { font-size: 20px; line-height: 24px; font-weight: ${c.weight.semibold}; letter-spacing: -0.4px; color: ${c.fgPrimary}; align-self: stretch; }
|
|
31
49
|
|
|
@@ -37,8 +37,15 @@ export function isLabelTruncated(el) {
|
|
|
37
37
|
* viewport change, label change) via `ResizeObserver`; SSR-safe the same way
|
|
38
38
|
* `MiddleTruncate` is -- the DOM is only touched inside an effect, so the
|
|
39
39
|
* server (and the first client paint) renders the plain span.
|
|
40
|
+
*
|
|
41
|
+
* #580: a `referencedType` column (Attio's own `type: "reference"`) appends
|
|
42
|
+
* that type's icon + name after the label and switches to `Tooltip`'s
|
|
43
|
+
* `"reference"` chrome variant (Attio's second, structurally distinct
|
|
44
|
+
* tooltip — same background/colour/radius/padding/font as the plain one, a
|
|
45
|
+
* different `box-shadow`, crisp#563). Every other column stays label-only,
|
|
46
|
+
* the `"plain"` variant #534/#558 shipped.
|
|
40
47
|
*/
|
|
41
|
-
function ThLabel({ label }) {
|
|
48
|
+
function ThLabel({ label, referencedType, }) {
|
|
42
49
|
const ref = React.useRef(null);
|
|
43
50
|
const [overflowing, setOverflowing] = React.useState(false);
|
|
44
51
|
// biome-ignore lint/correctness/useExhaustiveDependencies: `label` isn't read in the body, but a rename (col.label changing with the column's own width unchanged) needs a re-measure a ResizeObserver on `el` won't fire for -- same reasoning as Tabs' own re-measure effect.
|
|
@@ -67,7 +74,9 @@ function ThLabel({ label }) {
|
|
|
67
74
|
// actually show. `open={false}` when it fits keeps the Tooltip mounted
|
|
68
75
|
// (asChild costs no extra DOM either way) but pins it permanently closed,
|
|
69
76
|
// so hovering a non-overflowing header still opens nothing.
|
|
70
|
-
|
|
77
|
+
const ReferenceIcon = referencedType?.icon;
|
|
78
|
+
const content = referencedType ? (_jsxs("span", { className: "crisp-tooltip-reference-row", children: [label, ReferenceIcon && _jsx(ReferenceIcon, { size: 16, strokeWidth: 1.75 }), referencedType.label] })) : (label);
|
|
79
|
+
return (_jsx(Tooltip, { content: content, asChild: true, variant: referencedType ? "reference" : "plain", open: overflowing ? undefined : false, children: _jsx("span", { ref: ref, className: "crisp-recordstable-th-label", children: label }) }));
|
|
71
80
|
}
|
|
72
81
|
const EDITABLE = new Set(["text", "number", "url", "currency"]);
|
|
73
82
|
// types whose cell is an always-interactive picker (not double-click-to-edit)
|
|
@@ -1173,7 +1182,7 @@ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, ro
|
|
|
1173
1182
|
setDragCol(col.key);
|
|
1174
1183
|
e.dataTransfer.effectAllowed = "move";
|
|
1175
1184
|
}
|
|
1176
|
-
: undefined, onDragEnd: () => setDragCol(null), onClick: (e) => openColumnMenu(col.key, e.currentTarget), children: [Icon ? _jsx(Icon, { size: 16, strokeWidth: 1.75 }) : null, _jsx(ThLabel, { label: col.label }), _jsx("span", { className: "crisp-recordstable-sort", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: _jsx("path", { d: "m4 6 4 4 4-4" }) }) })] })), openMenu === `col:${col.key}` &&
|
|
1185
|
+
: undefined, onDragEnd: () => setDragCol(null), onClick: (e) => openColumnMenu(col.key, e.currentTarget), children: [Icon ? _jsx(Icon, { size: 16, strokeWidth: 1.75 }) : null, _jsx(ThLabel, { label: col.label, referencedType: col.referencedType }), _jsx("span", { className: "crisp-recordstable-sort", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: _jsx("path", { d: "m4 6 4 4 4-4" }) }) })] })), openMenu === `col:${col.key}` &&
|
|
1177
1186
|
rootRef.current &&
|
|
1178
1187
|
createPortal(_jsxs("div", { ref: menuRef, className: "crisp-recordstable-menu crisp-recordstable-menu--fixed crisp-recordstable-menu--hdr", role: "menu", "aria-label": `${col.label} column`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: [_jsxs("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "asc"), children: [_jsx(ArrowUp, { size: 15, strokeWidth: 1.75 }), t.sortAscending] }), _jsxs("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "desc"), children: [_jsx(ArrowDown, { size: 15, strokeWidth: 1.75 }), t.sortDescending] }), _jsxs("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i <= 1, onClick: () => moveColumn(col.key, -1), children: [_jsx(ArrowLeft, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }), t.moveLeft] }), _jsxs("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i === 0 || i >= cols.length - 1, onClick: () => moveColumn(col.key, 1), children: [_jsx(ArrowRight, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }), t.moveRight] }), _jsx("div", { className: "crisp-recordstable-menu-sep" }), _jsxs("button", { type: "button", className: "crisp-recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => {
|
|
1179
1188
|
setRenamingCol(col.key);
|
|
@@ -123,6 +123,17 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
123
123
|
// The Range covering a live "/query" (trigger included), mirroring the
|
|
124
124
|
// mention primitive below — only set in "slash" mode.
|
|
125
125
|
const slashRangeRef = React.useRef(null);
|
|
126
|
+
// #672: the selection the user actually had when they opened the toolbar's
|
|
127
|
+
// "Body ▾" menu — only set in "toolbar" mode. That menu's own search
|
|
128
|
+
// input steals DOM focus from the body the moment it opens, so by the
|
|
129
|
+
// time an item is picked, `window.getSelection()` no longer points at
|
|
130
|
+
// the block the user meant; without this, `run()`'s `body.focus()`
|
|
131
|
+
// resets the caret to the start of the document (the FIRST block)
|
|
132
|
+
// instead, and every conversion landed there regardless of what was
|
|
133
|
+
// selected. Cloned, since the live Range would otherwise track DOM
|
|
134
|
+
// mutations (and the selection moving to the search input) between now
|
|
135
|
+
// and when it's restored.
|
|
136
|
+
const toolbarSelectionRef = React.useRef(null);
|
|
126
137
|
const blockSearchRef = React.useRef(null);
|
|
127
138
|
const [mention, setMention] = React.useState(null);
|
|
128
139
|
// The Range covering the live "@query" text (trigger included) — kept
|
|
@@ -228,6 +239,7 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
228
239
|
}, [readOnly]);
|
|
229
240
|
const closeBlockMenu = React.useCallback(() => {
|
|
230
241
|
slashRangeRef.current = null;
|
|
242
|
+
toolbarSelectionRef.current = null;
|
|
231
243
|
setBlockMenu(null);
|
|
232
244
|
}, []);
|
|
233
245
|
/** Mirrors the mention-trigger detection below: only handles the caret
|
|
@@ -406,6 +418,14 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
406
418
|
sel?.removeAllRanges();
|
|
407
419
|
sel?.addRange(range);
|
|
408
420
|
}
|
|
421
|
+
else if (blockMenu?.mode === "toolbar" && toolbarSelectionRef.current) {
|
|
422
|
+
// #672: no trigger text to delete here (nothing was typed to open
|
|
423
|
+
// this menu) -- just put the selection back where the user actually
|
|
424
|
+
// had it, before `applyBlockItem` -> `run()` refocuses the body.
|
|
425
|
+
const sel = window.getSelection();
|
|
426
|
+
sel?.removeAllRanges();
|
|
427
|
+
sel?.addRange(toolbarSelectionRef.current);
|
|
428
|
+
}
|
|
409
429
|
applyBlockItem(item.id);
|
|
410
430
|
closeBlockMenu();
|
|
411
431
|
};
|
|
@@ -428,14 +448,21 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
428
448
|
const toggleBlockMenuFromToolbar = () => {
|
|
429
449
|
if (!bar)
|
|
430
450
|
return;
|
|
431
|
-
setBlockMenu((cur) =>
|
|
432
|
-
|
|
433
|
-
|
|
451
|
+
setBlockMenu((cur) => {
|
|
452
|
+
if (cur?.mode === "toolbar")
|
|
453
|
+
return null;
|
|
454
|
+
// #672: capture the live selection NOW, before the menu's own search
|
|
455
|
+
// input (focused by the effect below) steals it away.
|
|
456
|
+
const sel = window.getSelection();
|
|
457
|
+
toolbarSelectionRef.current =
|
|
458
|
+
sel && sel.rangeCount > 0 ? sel.getRangeAt(0).cloneRange() : null;
|
|
459
|
+
return {
|
|
434
460
|
anchor: { x: bar.x, y: bar.y },
|
|
435
461
|
query: "",
|
|
436
462
|
highlighted: 0,
|
|
437
463
|
mode: "toolbar",
|
|
438
|
-
}
|
|
464
|
+
};
|
|
465
|
+
});
|
|
439
466
|
};
|
|
440
467
|
// biome-ignore lint/correctness/useExhaustiveDependencies: syncEmpty only reads/writes stable refs, same as run()'s own call to it just below
|
|
441
468
|
const insertMention = React.useCallback((item) => {
|
package/dist/recordstable.css
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
& .crisp-recordstable-sort-learn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-lg); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); text-decoration: none; }
|
|
44
44
|
& .crisp-recordstable-sort-learn svg { flex-shrink: 0; }
|
|
45
45
|
& .crisp-recordstable-sort-learn:hover { color: var(--crisp-fg-primary); }
|
|
46
|
-
& .crisp-recordstable-menu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); text-align: left; cursor: pointer; }
|
|
46
|
+
& .crisp-recordstable-menu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); text-align: left; cursor: pointer; }
|
|
47
47
|
& .crisp-recordstable-menu-item:hover { background: var(--crisp-bg-raised); }
|
|
48
48
|
& .crisp-recordstable-menu-item:disabled { opacity: 0.4; cursor: default; }
|
|
49
49
|
& .crisp-recordstable-menu-item:disabled:hover { background: none; }
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
& .crisp-recordstable-menu--right { left: auto; right: 0; }
|
|
57
57
|
& .crisp-recordstable-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
|
|
58
58
|
& .crisp-recordstable-menu--hdr { min-width: 200px; }
|
|
59
|
+
& .crisp-recordstable-menu--op { min-width: 134px; }
|
|
59
60
|
& .crisp-recordstable-menu-item--danger { color: var(--crisp-bg-danger); }
|
|
60
61
|
& .crisp-recordstable-menu-item--danger svg { color: var(--crisp-bg-danger); }
|
|
61
62
|
& .crisp-recordstable-menu-item--danger:hover { background: color-mix(in srgb, var(--crisp-bg-danger) 10%, transparent); }
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
& .crisp-recordstable-sort-learn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-lg); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); text-decoration: none; }
|
|
45
45
|
& .crisp-recordstable-sort-learn svg { flex-shrink: 0; }
|
|
46
46
|
& .crisp-recordstable-sort-learn:hover { color: var(--crisp-fg-primary); }
|
|
47
|
-
& .crisp-recordstable-menu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); text-align: left; cursor: pointer; }
|
|
47
|
+
& .crisp-recordstable-menu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); text-align: left; cursor: pointer; }
|
|
48
48
|
& .crisp-recordstable-menu-item:hover { background: var(--crisp-bg-raised); }
|
|
49
49
|
& .crisp-recordstable-menu-item:disabled { opacity: 0.4; cursor: default; }
|
|
50
50
|
& .crisp-recordstable-menu-item:disabled:hover { background: none; }
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
& .crisp-recordstable-menu--right { left: auto; right: 0; }
|
|
58
58
|
& .crisp-recordstable-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
|
|
59
59
|
& .crisp-recordstable-menu--hdr { min-width: 200px; }
|
|
60
|
+
& .crisp-recordstable-menu--op { min-width: 134px; }
|
|
60
61
|
& .crisp-recordstable-menu-item--danger { color: var(--crisp-bg-danger); }
|
|
61
62
|
& .crisp-recordstable-menu-item--danger svg { color: var(--crisp-bg-danger); }
|
|
62
63
|
& .crisp-recordstable-menu-item--danger:hover { background: color-mix(in srgb, var(--crisp-bg-danger) 10%, transparent); }
|
package/dist/styles.css
CHANGED
|
@@ -679,10 +679,14 @@
|
|
|
679
679
|
& .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)); }
|
|
680
680
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
681
681
|
& .crisp-datatable-muted { color: var(--crisp-fg-muted); }
|
|
682
|
-
/*
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
682
|
+
/* url/email cell types (#654) -- same measured values as RecordsTable's own .crisp-recordstable-link, which this mirrors: no underline at rest, underline on hover, own-colour text rather than a browser-blue link. */
|
|
683
|
+
& .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
684
|
+
& .crisp-datatable-link:hover { text-decoration: underline; }
|
|
685
|
+
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures a leading 20px grip column, a 16px grip icon, and the row's own
|
|
686
|
+
hairline doubling as the drop indicator (it tints toward the brand
|
|
687
|
+
colour as the active drop target). The affordance + geometry are
|
|
688
|
+
measured; the drag MECHANISM is crisp's existing house idiom -- HTML5 draggable + dataTransfer, the same one column reorder already uses (RecordsTable's reorderColumn) -- not a proprietary
|
|
689
|
+
pointer-events implementation. */
|
|
686
690
|
& .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
|
|
687
691
|
& .crisp-datatable-grip { display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%; margin: 0; border: none; border-radius: 0; background: none; font-family: inherit; color: var(--crisp-fg-muted); cursor: grab; }
|
|
688
692
|
& .crisp-datatable-grip:active { cursor: grabbing; }
|
|
@@ -1742,7 +1746,7 @@
|
|
|
1742
1746
|
& .crisp-tabletoolbar-sort-learn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-lg); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); text-decoration: none; }
|
|
1743
1747
|
& .crisp-tabletoolbar-sort-learn svg { flex-shrink: 0; }
|
|
1744
1748
|
& .crisp-tabletoolbar-sort-learn:hover { color: var(--crisp-fg-primary); }
|
|
1745
|
-
& .crisp-tabletoolbar-menu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); text-align: left; cursor: pointer; }
|
|
1749
|
+
& .crisp-tabletoolbar-menu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); text-align: left; cursor: pointer; }
|
|
1746
1750
|
& .crisp-tabletoolbar-menu-item:hover { background: var(--crisp-bg-raised); }
|
|
1747
1751
|
& .crisp-tabletoolbar-menu-item:disabled { opacity: 0.4; cursor: default; }
|
|
1748
1752
|
& .crisp-tabletoolbar-menu-item:disabled:hover { background: none; }
|
|
@@ -1755,6 +1759,7 @@
|
|
|
1755
1759
|
& .crisp-tabletoolbar-menu--right { left: auto; right: 0; }
|
|
1756
1760
|
& .crisp-tabletoolbar-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
|
|
1757
1761
|
& .crisp-tabletoolbar-menu--hdr { min-width: 200px; }
|
|
1762
|
+
& .crisp-tabletoolbar-menu--op { min-width: 134px; }
|
|
1758
1763
|
& .crisp-tabletoolbar-menu-item--danger { color: var(--crisp-bg-danger); }
|
|
1759
1764
|
& .crisp-tabletoolbar-menu-item--danger svg { color: var(--crisp-bg-danger); }
|
|
1760
1765
|
& .crisp-tabletoolbar-menu-item--danger:hover { background: color-mix(in srgb, var(--crisp-bg-danger) 10%, transparent); }
|
|
@@ -1892,6 +1897,13 @@
|
|
|
1892
1897
|
.crisp-tooltip {
|
|
1893
1898
|
position: fixed; z-index: 70; box-sizing: border-box; pointer-events: none; max-width: 260px; padding: var(--crisp-space-1-5) var(--crisp-space-2); border-radius: var(--crisp-radius-md); background: #101112; color: #eeeff1; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); line-height: 1.45; box-shadow: 0 4px 14px -2px rgba(28,40,64,0.28), 0 0 0 1px rgba(255,255,255,0.04); opacity: 1; transition: opacity 0.12s ease;
|
|
1894
1899
|
@starting-style { opacity: 0; }
|
|
1900
|
+
& .crisp-tooltip-reference-row { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
|
|
1901
|
+
}
|
|
1902
|
+
.crisp-tooltip--variant_plain {
|
|
1903
|
+
|
|
1904
|
+
}
|
|
1905
|
+
.crisp-tooltip--variant_reference {
|
|
1906
|
+
box-shadow: rgba(0,0,0,0) 0 0 0 1px inset, rgba(28,40,64,0.18) 0 0 2px 0, rgba(0,0,0,0.04) 0 1px 3px 0;
|
|
1895
1907
|
}
|
|
1896
1908
|
|
|
1897
1909
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -1966,14 +1978,18 @@
|
|
|
1966
1978
|
|
|
1967
1979
|
.crisp-authshell {
|
|
1968
1980
|
min-height: 100%; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; position: relative; background: var(--crisp-bg-surface); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
1981
|
+
&[data-single-pane] { justify-content: center; }
|
|
1969
1982
|
& .crisp-authshell-progress { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: var(--crisp-border-default); }
|
|
1970
1983
|
& .crisp-authshell-progress-fill { height: 2px; background: var(--crisp-fg-primary); border-radius: 4px; transition: width .2s; }
|
|
1971
1984
|
& .crisp-authshell-logo { margin-top: var(--crisp-space-8); height: 24px; display: flex; align-items: center; }
|
|
1972
1985
|
& .crisp-authshell-card { margin-top: var(--crisp-space-6); display: flex; height: 692px; border-radius: 20px; border: 1px solid var(--crisp-border-subtle); overflow: hidden; }
|
|
1986
|
+
&[data-single-pane] .crisp-authshell-card { height: auto; border: none; border-radius: 0; overflow: visible; }
|
|
1973
1987
|
& .crisp-authshell-pane { flex: 0 0 564px; padding: 72px; display: flex; flex-direction: column; box-sizing: border-box; }
|
|
1988
|
+
&[data-single-pane] .crisp-authshell-pane { flex: 0 0 auto; padding: 0; }
|
|
1974
1989
|
& .crisp-authshell-preview { flex: 0 0 564px; padding: 96px 64px; background: var(--crisp-bg-subtle); border-inline-start: 1px solid color-mix(in srgb, var(--crisp-fg-primary) 5%, transparent); position: relative; overflow: hidden; box-sizing: border-box; }
|
|
1975
1990
|
& .crisp-authshell-preview::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 120px; background: linear-gradient(color-mix(in srgb, var(--crisp-bg-subtle) 0%, transparent) 0%, var(--crisp-bg-subtle) 100%); pointer-events: none; }
|
|
1976
1991
|
& .crisp-authshell-body { width: 420px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--crisp-space-8); align-items: center; }
|
|
1992
|
+
&[data-single-pane] .crisp-authshell-body { width: 360px; }
|
|
1977
1993
|
& .crisp-authshell-title { font-size: 20px; line-height: 24px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.4px; color: var(--crisp-fg-primary); align-self: stretch; }
|
|
1978
1994
|
& .crisp-authshell-form { align-self: stretch; display: flex; flex-direction: column; gap: 28px; }
|
|
1979
1995
|
& .crisp-authshell-footer { margin-top: var(--crisp-space-6); display: flex; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-sm); line-height: 16px; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
|
|
@@ -2738,7 +2754,7 @@
|
|
|
2738
2754
|
& .crisp-recordstable-sort-learn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-lg); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); text-decoration: none; }
|
|
2739
2755
|
& .crisp-recordstable-sort-learn svg { flex-shrink: 0; }
|
|
2740
2756
|
& .crisp-recordstable-sort-learn:hover { color: var(--crisp-fg-primary); }
|
|
2741
|
-
& .crisp-recordstable-menu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); text-align: left; cursor: pointer; }
|
|
2757
|
+
& .crisp-recordstable-menu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); text-align: left; cursor: pointer; }
|
|
2742
2758
|
& .crisp-recordstable-menu-item:hover { background: var(--crisp-bg-raised); }
|
|
2743
2759
|
& .crisp-recordstable-menu-item:disabled { opacity: 0.4; cursor: default; }
|
|
2744
2760
|
& .crisp-recordstable-menu-item:disabled:hover { background: none; }
|
|
@@ -2751,6 +2767,7 @@
|
|
|
2751
2767
|
& .crisp-recordstable-menu--right { left: auto; right: 0; }
|
|
2752
2768
|
& .crisp-recordstable-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
|
|
2753
2769
|
& .crisp-recordstable-menu--hdr { min-width: 200px; }
|
|
2770
|
+
& .crisp-recordstable-menu--op { min-width: 134px; }
|
|
2754
2771
|
& .crisp-recordstable-menu-item--danger { color: var(--crisp-bg-danger); }
|
|
2755
2772
|
& .crisp-recordstable-menu-item--danger svg { color: var(--crisp-bg-danger); }
|
|
2756
2773
|
& .crisp-recordstable-menu-item--danger:hover { background: color-mix(in srgb, var(--crisp-bg-danger) 10%, transparent); }
|