@crisp-ui-kit/crisp 0.54.4 → 0.54.6
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/chip.css +1 -1
- package/dist/chip.layered.css +1 -1
- package/dist/cjs/components/chip/chip.recipe.js +12 -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 +13 -3
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +75 -2
- 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/comments/comments.js +13 -21
- package/dist/cjs/product/comments/comments.recipe.js +29 -14
- package/dist/cjs/product/recordstable/recordstable.js +12 -3
- package/dist/cjs/product/richtext/richtext.js +31 -4
- package/dist/comments.css +21 -15
- package/dist/comments.layered.css +21 -15
- package/dist/datatable.css +8 -4
- package/dist/datatable.layered.css +8 -4
- package/dist/esm/components/chip/chip.recipe.js +12 -0
- 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 +13 -3
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +76 -3
- 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/comments/comments.js +13 -21
- package/dist/esm/product/comments/comments.recipe.js +29 -14
- package/dist/esm/product/recordstable/recordstable.js +12 -3
- package/dist/esm/product/richtext/richtext.js +31 -4
- package/dist/styles.css +41 -20
- package/dist/styles.layered.css +41 -20
- package/dist/tooltip.css +7 -0
- package/dist/tooltip.d.ts +2 -2
- package/dist/tooltip.layered.css +7 -0
- package/dist/types/components/chip/chip.recipe.d.ts +1 -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 +32 -1
- 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/comments/comments.d.ts +1 -2
- package/dist/types/product/recordstable/recordstable.d.ts +12 -0
- package/package.json +1 -1
package/dist/authshell.css
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
.crisp-authshell {
|
|
2
2
|
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;
|
|
3
|
+
&[data-single-pane] { justify-content: center; }
|
|
3
4
|
& .crisp-authshell-progress { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: var(--crisp-border-default); }
|
|
4
5
|
& .crisp-authshell-progress-fill { height: 2px; background: var(--crisp-fg-primary); border-radius: 4px; transition: width .2s; }
|
|
5
6
|
& .crisp-authshell-logo { margin-top: var(--crisp-space-8); height: 24px; display: flex; align-items: center; }
|
|
6
7
|
& .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; }
|
|
8
|
+
&[data-single-pane] .crisp-authshell-card { height: auto; border: none; border-radius: 0; overflow: visible; }
|
|
7
9
|
& .crisp-authshell-pane { flex: 0 0 564px; padding: 72px; display: flex; flex-direction: column; box-sizing: border-box; }
|
|
10
|
+
&[data-single-pane] .crisp-authshell-pane { flex: 0 0 auto; padding: 0; }
|
|
8
11
|
& .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; }
|
|
9
12
|
& .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; }
|
|
10
13
|
& .crisp-authshell-body { width: 420px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--crisp-space-8); align-items: center; }
|
|
14
|
+
&[data-single-pane] .crisp-authshell-body { width: 360px; }
|
|
11
15
|
& .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; }
|
|
12
16
|
& .crisp-authshell-form { align-self: stretch; display: flex; flex-direction: column; gap: 28px; }
|
|
13
17
|
& .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); }
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
@layer crisp-components {
|
|
2
2
|
.crisp-authshell {
|
|
3
3
|
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;
|
|
4
|
+
&[data-single-pane] { justify-content: center; }
|
|
4
5
|
& .crisp-authshell-progress { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: var(--crisp-border-default); }
|
|
5
6
|
& .crisp-authshell-progress-fill { height: 2px; background: var(--crisp-fg-primary); border-radius: 4px; transition: width .2s; }
|
|
6
7
|
& .crisp-authshell-logo { margin-top: var(--crisp-space-8); height: 24px; display: flex; align-items: center; }
|
|
7
8
|
& .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; }
|
|
9
|
+
&[data-single-pane] .crisp-authshell-card { height: auto; border: none; border-radius: 0; overflow: visible; }
|
|
8
10
|
& .crisp-authshell-pane { flex: 0 0 564px; padding: 72px; display: flex; flex-direction: column; box-sizing: border-box; }
|
|
11
|
+
&[data-single-pane] .crisp-authshell-pane { flex: 0 0 auto; padding: 0; }
|
|
9
12
|
& .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; }
|
|
10
13
|
& .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; }
|
|
11
14
|
& .crisp-authshell-body { width: 420px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--crisp-space-8); align-items: center; }
|
|
15
|
+
&[data-single-pane] .crisp-authshell-body { width: 360px; }
|
|
12
16
|
& .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; }
|
|
13
17
|
& .crisp-authshell-form { align-self: stretch; display: flex; flex-direction: column; gap: 28px; }
|
|
14
18
|
& .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); }
|
package/dist/chip.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
.crisp-chip {
|
|
2
|
-
display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
|
|
2
|
+
display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
|
|
3
3
|
}
|
|
4
4
|
.crisp-chip--truncate_true {
|
|
5
5
|
& .crisp-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
|
package/dist/chip.layered.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
@layer crisp-components {
|
|
2
2
|
.crisp-chip {
|
|
3
|
-
display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
|
|
3
|
+
display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
|
|
4
4
|
}
|
|
5
5
|
.crisp-chip--truncate_true {
|
|
6
6
|
& .crisp-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
|
|
@@ -30,6 +30,18 @@ const spec = (0, core_1.defineSpec)({
|
|
|
30
30
|
// -- `inline-flex` items don't by default -- which `truncate` below
|
|
31
31
|
// depends on and any consumer constraining a Chip's width already needs.
|
|
32
32
|
minWidth: 0,
|
|
33
|
+
// Caps the chip at its containing block's width (#668) -- `min-width: 0`
|
|
34
|
+
// alone only lets it shrink inside a flex/grid row with `flex-shrink`;
|
|
35
|
+
// it does nothing in a plain block or table-cell ancestor (GRC's own
|
|
36
|
+
// `DataTable` cells, a render-uniform `<td>`, not a flex row), where the
|
|
37
|
+
// chip just grew to content width and overflowed instead. Checked
|
|
38
|
+
// Attio's own actor chip for a value to match instead of guessing one:
|
|
39
|
+
// `max-width: none` there too -- it has no pixel cap either, and stays
|
|
40
|
+
// contained only because its own ancestor chain is a real flex row the
|
|
41
|
+
// whole way down, which `Chip` alone can't assume for every consumer.
|
|
42
|
+
// `100%` is the honest equivalent: never wider than the container,
|
|
43
|
+
// whatever kind of container it is.
|
|
44
|
+
maxWidth: "100%",
|
|
33
45
|
paddingInline: tokens_1.contract.space["1"],
|
|
34
46
|
borderRadius: tokens_1.contract.radius.chip,
|
|
35
47
|
fontSize: tokens_1.contract.text.lg,
|
|
@@ -63,8 +63,16 @@ function isLabelTruncated(el) {
|
|
|
63
63
|
* viewport change, header text change) via `ResizeObserver`; SSR-safe the
|
|
64
64
|
* same way `MiddleTruncate` is -- the DOM is only touched inside an effect,
|
|
65
65
|
* so the server (and the first client paint) render the closed tooltip.
|
|
66
|
+
*
|
|
67
|
+
* #580: a `referencedType` column appends that type's icon + name after the
|
|
68
|
+
* label (Attio's own reference-column tooltip, e.g. "Primary location" +
|
|
69
|
+
* Country's icon + "Country") and uses `Tooltip`'s `"reference"` chrome
|
|
70
|
+
* variant -- Attio's second, structurally distinct tooltip (same
|
|
71
|
+
* background/colour/radius/padding/font as the plain one, a different
|
|
72
|
+
* `box-shadow`, crisp#563). Every other column stays exactly as #534/#558
|
|
73
|
+
* shipped: label-only, the `"plain"` variant.
|
|
66
74
|
*/
|
|
67
|
-
function ThLabel({ header }) {
|
|
75
|
+
function ThLabel({ header, referencedType, }) {
|
|
68
76
|
const ref = React.useRef(null);
|
|
69
77
|
const [overflowing, setOverflowing] = React.useState(false);
|
|
70
78
|
// 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.
|
|
@@ -93,7 +101,9 @@ function ThLabel({ header }) {
|
|
|
93
101
|
// actually show. `open={false}` when it fits keeps the Tooltip mounted
|
|
94
102
|
// (asChild costs no extra DOM either way) but pins it permanently closed,
|
|
95
103
|
// so hovering a non-overflowing header still opens nothing.
|
|
96
|
-
|
|
104
|
+
const ReferenceIcon = referencedType?.icon;
|
|
105
|
+
const content = referencedType ? ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tooltip-reference-row", children: [header, ReferenceIcon && (0, jsx_runtime_1.jsx)(ReferenceIcon, { size: 16, strokeWidth: 1.75 }), referencedType.label] })) : (header);
|
|
106
|
+
return ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { content: content, asChild: true, variant: referencedType ? "reference" : "plain", open: overflowing ? undefined : false, children: (0, jsx_runtime_1.jsx)("span", { ref: ref, className: "crisp-datatable-th-label", children: header }) }));
|
|
97
107
|
}
|
|
98
108
|
/** A `date` input needs `yyyy-mm-dd`; cells may hold a Date, an ISO string or
|
|
99
109
|
* a display string. Returns "" when it cannot be parsed, so the field opens
|
|
@@ -289,6 +299,16 @@ function renderTyped(col, value, avatarTone, row, display, locale = "en-US") {
|
|
|
289
299
|
// translation) can never silently repaint the chip
|
|
290
300
|
return ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-tag", "data-tone": col.tones?.[s] ?? toneFor(s), children: display || s }));
|
|
291
301
|
}
|
|
302
|
+
case "url":
|
|
303
|
+
case "email": {
|
|
304
|
+
const text = display || asText(value);
|
|
305
|
+
if (!text)
|
|
306
|
+
return empty;
|
|
307
|
+
return ((0, jsx_runtime_1.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,
|
|
308
|
+
// Same reasoning as `entity`'s `onEntityClick` link above: the
|
|
309
|
+
// link's own navigation must not also fire the row's onRowClick.
|
|
310
|
+
onClick: (ev) => ev.stopPropagation(), children: text }));
|
|
311
|
+
}
|
|
292
312
|
default: {
|
|
293
313
|
const text = display || formatCell(col.type, value, col.symbol, locale);
|
|
294
314
|
const isNum = col.type === "number" || col.type === "currency";
|
|
@@ -424,7 +444,8 @@ function DataReferenceEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
|
424
444
|
keywords: query,
|
|
425
445
|
});
|
|
426
446
|
}
|
|
427
|
-
|
|
447
|
+
const isActor = col.editor === "actor";
|
|
448
|
+
return ((0, jsx_runtime_1.jsx)("span", { ref: ref, style: { display: "contents" }, children: (0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", searchable: true, searchPlaceholder: col.refSearchPlaceholder ?? (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) => {
|
|
428
449
|
if (v === CREATE_REFERENCE_VALUE)
|
|
429
450
|
col.onCreateReference?.(query);
|
|
430
451
|
else
|
|
@@ -1199,7 +1220,7 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
1199
1220
|
? "ascending"
|
|
1200
1221
|
: "descending"
|
|
1201
1222
|
: "none";
|
|
1202
|
-
const inner = ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-th-inner", children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 16, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)(ThLabel, { header: col.header }), col.sortable ? (0, jsx_runtime_1.jsx)(ChevronFor, { colKey: col.key }) : null] }));
|
|
1223
|
+
const inner = ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-th-inner", children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 16, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)(ThLabel, { header: col.header, referencedType: col.referencedType }), col.sortable ? (0, jsx_runtime_1.jsx)(ChevronFor, { colKey: col.key }) : null] }));
|
|
1203
1224
|
return ((0, jsx_runtime_1.jsxs)(table_1.TableHead, { align: alignFor(col), "aria-sort": ariaSort, "aria-colindex": colAriaIndex(i), ...dataStickyCol(i), children: [renaming === col.key ? ((0, jsx_runtime_1.jsx)("input", {
|
|
1204
1225
|
// biome-ignore lint/a11y/noAutofocus: the rename item opens this field
|
|
1205
1226
|
autoFocus: true, className: "crisp-datatable-th-rename", defaultValue: typeof col.header === "string" ? col.header : "", "aria-label": renameColumnLabel, onBlur: (e) => {
|
|
@@ -1280,7 +1301,9 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
1280
1301
|
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-edit-picker", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { "aria-label": `Edit ${col.key}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commitEdit(row.id, col.key, String(checked)) }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
1281
1302
|
}
|
|
1282
1303
|
if (isEditing &&
|
|
1283
|
-
(col.editor === "select" ||
|
|
1304
|
+
(col.editor === "select" ||
|
|
1305
|
+
col.editor === "reference" ||
|
|
1306
|
+
col.editor === "actor")) {
|
|
1284
1307
|
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined,
|
|
1285
1308
|
// Same as the plain text editor's own cell:
|
|
1286
1309
|
// opening/using the picker must not also fire
|
|
@@ -144,6 +144,12 @@ const config = (0, core_1.defineRecipe)({
|
|
|
144
144
|
& .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}); }
|
|
145
145
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
146
146
|
& .crisp-datatable-muted { color: ${tokens_1.contract.fgMuted}; }
|
|
147
|
+
/* url/email cell types (#654) -- same measured values as RecordsTable's
|
|
148
|
+
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
149
|
+
rest, underline on hover, own-colour text rather than a browser-blue
|
|
150
|
+
link. */
|
|
151
|
+
& .crisp-datatable-link { color: ${tokens_1.contract.fgPrimary}; text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
|
|
152
|
+
& .crisp-datatable-link:hover { text-decoration: underline; }
|
|
147
153
|
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
148
154
|
a leading 20px grip column, a 16px grip icon, and the row's own
|
|
149
155
|
hairline doubling as the drop indicator (it tints toward the brand
|
|
@@ -42,9 +42,19 @@ function initials(name) {
|
|
|
42
42
|
* Composes `Chip`'s `truncate` CSS directly rather than its string-only
|
|
43
43
|
* auto-wrap (children here is an avatar plus a name, not a single string),
|
|
44
44
|
* so the name gets its own `.crisp-chip-label` span while the avatar stays
|
|
45
|
-
* fixed-size.
|
|
45
|
+
* fixed-size. `trailing` (#683) is the same idea for a consumer's own
|
|
46
|
+
* icon/badge after the name — inside the chip's own flex row rather than a
|
|
47
|
+
* second one a consumer would otherwise wrap this in, which needs its own
|
|
48
|
+
* `min-width: 0` to keep shrinking correctly and, missing that, overflows
|
|
49
|
+
* its container the same way #668 did before `Chip` itself was fixed.
|
|
46
50
|
*/
|
|
47
|
-
function PersonChip({ name, avatar, muted, className, ...props }) {
|
|
48
|
-
return ((0, jsx_runtime_1.jsxs)(chip_1.Chip, { tone: "neutral", truncate: true, className: className, ...props, children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "circle", tone: avatar ? "neutral" : hashTone(name),
|
|
51
|
+
function PersonChip({ name, avatar, muted, trailing, className, ...props }) {
|
|
52
|
+
return ((0, jsx_runtime_1.jsxs)(chip_1.Chip, { tone: "neutral", truncate: true, className: className, ...props, children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "circle", tone: avatar ? "neutral" : hashTone(name),
|
|
53
|
+
// Explicit rather than relying on the flex default (a fixed-size,
|
|
54
|
+
// non-wrapping element's own min-content floor already resists
|
|
55
|
+
// shrinking in every browser tested) — #668 asked "does the avatar
|
|
56
|
+
// stay" as its own question, so it gets its own undeniable answer
|
|
57
|
+
// rather than an implicit one.
|
|
58
|
+
style: { flexShrink: 0 }, children: avatar ? (0, jsx_runtime_1.jsx)("img", { src: avatar, alt: "" }) : initials(name) }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-chip-label", style: muted ? { color: tokens_1.contract.fgNeutralMuted } : undefined, children: name }), trailing != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-personchip-trailing", style: { flexShrink: 0 }, children: trailing }))] }));
|
|
49
59
|
}
|
|
50
60
|
PersonChip.displayName = "PersonChip";
|
|
@@ -177,7 +177,7 @@ function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
177
177
|
* this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
|
|
178
178
|
* Created by), which one shared list can't reproduce.
|
|
179
179
|
*/
|
|
180
|
-
function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }) {
|
|
180
|
+
function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, viewColumns: viewColumnsProp, shownKeys: shownKeysProp, defaultShownKeys, onShownKeysChange, viewSettingsLabel: viewSettingsLabelProp, reorderAttributeLabel: reorderAttributeLabelProp, hideAttributeLabel, addAttributeToViewLabel: addAttributeToViewLabelProp, searchAttributesPlaceholder: searchAttributesPlaceholderProp, ...rest }) {
|
|
181
181
|
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
182
182
|
// CrispMessagesProvider's translation of the English default below, or
|
|
183
183
|
// that default itself with no provider — unchanged from before this
|
|
@@ -200,6 +200,10 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
200
200
|
const deleteFilterLabel = (0, core_1.useCrispMessage)(deleteFilterLabelProp, "Delete filter");
|
|
201
201
|
const ascendingLabel = (0, core_1.useCrispMessage)(ascendingLabelProp, "Ascending");
|
|
202
202
|
const descendingLabel = (0, core_1.useCrispMessage)(descendingLabelProp, "Descending");
|
|
203
|
+
const viewSettingsLabel = (0, core_1.useCrispMessage)(viewSettingsLabelProp, "View settings");
|
|
204
|
+
const reorderAttributeLabel = (0, core_1.useCrispMessage)(reorderAttributeLabelProp, "Reorder attribute");
|
|
205
|
+
const addAttributeToViewLabel = (0, core_1.useCrispMessage)(addAttributeToViewLabelProp, "Add attribute to view");
|
|
206
|
+
const searchAttributesPlaceholder = (0, core_1.useCrispMessage)(searchAttributesPlaceholderProp, "Search attributes…");
|
|
203
207
|
// OP_LABELS' own words ("contains", "is", "is any of", …) have no prop of
|
|
204
208
|
// their own — a whole lookup table, not one string — so they translate
|
|
205
209
|
// straight from the provider's dictionary at the two spots they render
|
|
@@ -211,10 +215,17 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
211
215
|
// gets `columns` on the other, unchanged from before either prop existed.
|
|
212
216
|
const sortColumns = sortColumnsProp ?? columns ?? [];
|
|
213
217
|
const filterColumns = filterColumnsProp ?? columns ?? [];
|
|
218
|
+
const viewColumns = viewColumnsProp ?? columns ?? [];
|
|
214
219
|
const [sortsState, setSortsState] = React.useState(defaultSorts ?? []);
|
|
215
220
|
const [filtersState, setFiltersState] = React.useState(defaultFilters ?? []);
|
|
221
|
+
// #590: "everything shown" unless told otherwise -- lazy initializer so
|
|
222
|
+
// this only reads `viewColumns` once, at mount, the same way `defaultSorts`/
|
|
223
|
+
// `defaultFilters` above seed once and are never re-derived from a prop
|
|
224
|
+
// that may keep changing identity across renders.
|
|
225
|
+
const [shownKeysState, setShownKeysState] = React.useState(() => defaultShownKeys ?? viewColumns.map((c) => c.key));
|
|
216
226
|
const sorts = sortsProp ?? sortsState;
|
|
217
227
|
const filters = filtersProp ?? filtersState;
|
|
228
|
+
const shownKeys = shownKeysProp ?? shownKeysState;
|
|
218
229
|
const putSorts = (next) => {
|
|
219
230
|
if (sortsProp === undefined)
|
|
220
231
|
setSortsState(next);
|
|
@@ -225,11 +236,28 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
225
236
|
setFiltersState(next);
|
|
226
237
|
onFiltersChange?.(next);
|
|
227
238
|
};
|
|
239
|
+
const putShownKeys = (next) => {
|
|
240
|
+
if (shownKeysProp === undefined)
|
|
241
|
+
setShownKeysState(next);
|
|
242
|
+
onShownKeysChange?.(next);
|
|
243
|
+
};
|
|
228
244
|
const [openMenu, setOpenMenu] = React.useState(null);
|
|
229
245
|
const [dragSort, setDragSort] = React.useState(null);
|
|
230
246
|
const [filterQuery, setFilterQuery] = React.useState("");
|
|
231
247
|
// filters the "isanyof" checkbox list, same lifetime as filterQuery above
|
|
232
248
|
const [anyOfQuery, setAnyOfQuery] = React.useState("");
|
|
249
|
+
const [dragAttr, setDragAttr] = React.useState(null);
|
|
250
|
+
// #590: the popover has two screens -- the draggable shown-attribute list
|
|
251
|
+
// (default) and, behind "Add attribute to view", a search list of the
|
|
252
|
+
// rest. Measured live, 2026-09-25: clicking "View settings" opens a
|
|
253
|
+
// `role="dialog"` of dnd-kit `aria-roledescription="sortable"` rows --
|
|
254
|
+
// grip, type icon, label -- with no search box, ending in a static
|
|
255
|
+
// "Add attribute to view" row; each row also carries a "View attribute
|
|
256
|
+
// menu" kebab whose own contents weren't explored (undefined scope, not
|
|
257
|
+
// built here -- hiding an attribute is a trailing control instead, see
|
|
258
|
+
// the hide button below).
|
|
259
|
+
const [addingAttribute, setAddingAttribute] = React.useState(false);
|
|
260
|
+
const [addAttrQuery, setAddAttrQuery] = React.useState("");
|
|
233
261
|
// close any open menu on outside click (anything not inside a menu or its
|
|
234
262
|
// trigger) or Escape
|
|
235
263
|
React.useEffect(() => {
|
|
@@ -292,6 +320,27 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
292
320
|
putFilters(filters.filter((c) => c.id !== id));
|
|
293
321
|
setOpenMenu(null);
|
|
294
322
|
};
|
|
323
|
+
// #590: shown attributes, in `shownKeys`' order -- a key with no matching
|
|
324
|
+
// `viewColumns` entry (stale state after a column is removed upstream)
|
|
325
|
+
// drops out rather than rendering a blank row.
|
|
326
|
+
const shownAttrs = shownKeys
|
|
327
|
+
.map((k) => viewColumns.find((c) => c.key === k))
|
|
328
|
+
.filter((c) => c != null);
|
|
329
|
+
const hiddenAttrs = viewColumns.filter((c) => !shownKeys.includes(c.key));
|
|
330
|
+
const moveAttr = (from, to) => {
|
|
331
|
+
if (from === to)
|
|
332
|
+
return;
|
|
333
|
+
const next = [...shownKeys];
|
|
334
|
+
const [moved] = next.splice(from, 1);
|
|
335
|
+
next.splice(to, 0, moved);
|
|
336
|
+
putShownKeys(next);
|
|
337
|
+
};
|
|
338
|
+
const hideAttr = (key) => putShownKeys(shownKeys.filter((k) => k !== key));
|
|
339
|
+
const showAttr = (key) => {
|
|
340
|
+
putShownKeys([...shownKeys, key]);
|
|
341
|
+
setAddingAttribute(false);
|
|
342
|
+
setAddAttrQuery("");
|
|
343
|
+
};
|
|
295
344
|
// #594: the filter chip's kebab is a real role="menu" (crisp/#188's shared
|
|
296
345
|
// model — arrows, Home/End, typeahead, Escape), not a hand-rolled div like
|
|
297
346
|
// the sort/condition popovers above (those are role="dialog", which makes
|
|
@@ -381,7 +430,31 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
381
430
|
});
|
|
382
431
|
}, children: v }, v));
|
|
383
432
|
})] }))] }))] }, cond.id));
|
|
384
|
-
}), (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-
|
|
433
|
+
}), (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-tool crisp-tabletoolbar-tool--ghost", onClick: () => {
|
|
434
|
+
if (openMenu === "viewsettings") {
|
|
435
|
+
setOpenMenu(null);
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
setAddingAttribute(false);
|
|
439
|
+
setAddAttrQuery("");
|
|
440
|
+
setOpenMenu("viewsettings");
|
|
441
|
+
}
|
|
442
|
+
}, "aria-haspopup": "dialog", "aria-expanded": openMenu === "viewsettings", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), viewSettingsLabel] }), openMenu === "viewsettings" &&
|
|
443
|
+
(addingAttribute ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": addAttributeToViewLabel, children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: searchAttributesPlaceholder, "aria-label": searchAttributesPlaceholder,
|
|
444
|
+
// biome-ignore lint/a11y/noAutofocus: focus the search box on open, same as Filter's field picker
|
|
445
|
+
autoFocus: true, value: addAttrQuery, onChange: (e) => setAddAttrQuery(e.target.value) }), hiddenAttrs
|
|
446
|
+
.filter((col) => col.label
|
|
447
|
+
.toLowerCase()
|
|
448
|
+
.includes(addAttrQuery.trim().toLowerCase()))
|
|
449
|
+
.map((col) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => showAttr(col.key), children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)("span", { style: { width: 15 } })), col.label] }, col.key)))] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": viewSettingsLabel, children: [shownAttrs.map((col, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
450
|
+
if (dragAttr !== null)
|
|
451
|
+
e.preventDefault();
|
|
452
|
+
}, onDrop: () => {
|
|
453
|
+
if (dragAttr !== null)
|
|
454
|
+
moveAttr(dragAttr, i);
|
|
455
|
+
setDragAttr(null);
|
|
456
|
+
}, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderAttributeLabel, draggable: true, onDragStart: () => setDragAttr(i), onDragEnd: () => setDragAttr(null), children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-sort-optlabel crisp-tabletoolbar-sort-field", children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : null, col.label] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => hideAttr(col.key), "aria-label": hideAttributeLabel?.(col.label) ??
|
|
457
|
+
`${crispMessages.Hide ?? "Hide"} ${col.label} ${crispMessages["from view"] ?? "from view"}`, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, col.key))), (0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-sort-add", onClick: () => setAddingAttribute(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), addAttributeToViewLabel] }) })] })))] }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-spacer" }), count != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-count", children: count })), children] }) }));
|
|
385
458
|
}
|
|
386
459
|
/**
|
|
387
460
|
* The Sort/Filter chip's own chrome (`.crisp-tabletoolbar-tool`), standalone
|
|
@@ -68,7 +68,7 @@ function mergeRefs(...refs) {
|
|
|
68
68
|
* carry them for the same reason it can't carry `aria-describedby` (below):
|
|
69
69
|
* a screen reader reads state off the focused element, not an ancestor.
|
|
70
70
|
*/
|
|
71
|
-
exports.Tooltip = React.forwardRef(({ content, children, side = "top", delay = 350, open: openProp, defaultOpen, onOpenChange, asChild = false, "aria-describedby": restDescribedBy,
|
|
71
|
+
exports.Tooltip = React.forwardRef(({ content, children, side = "top", delay = 350, open: openProp, defaultOpen, onOpenChange, asChild = false, variant = "plain", "aria-describedby": restDescribedBy,
|
|
72
72
|
// Not forwarded, even though a composer's other rest props are
|
|
73
73
|
// (below): `style` sizes a VISUAL box, and Tooltip's wrapper has none
|
|
74
74
|
// of its own -- the box that matters is whatever the consumer's own
|
|
@@ -152,7 +152,7 @@ style: _style, ...rest }, forwardedRef) => {
|
|
|
152
152
|
};
|
|
153
153
|
const portal = open &&
|
|
154
154
|
pos != null &&
|
|
155
|
-
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { id: tipId, role: "tooltip", className: (0, tooltip_recipe_1.tooltipRecipe)(), style: {
|
|
155
|
+
(0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { id: tipId, role: "tooltip", className: (0, tooltip_recipe_1.tooltipRecipe)({ variant }), style: {
|
|
156
156
|
left: pos.left,
|
|
157
157
|
top: pos.top,
|
|
158
158
|
transform: pos.below
|
|
@@ -14,6 +14,16 @@ const tokens_1 = require("../../tokens");
|
|
|
14
14
|
// (#101112, the same "dark pill" ink NavItem's own text uses) and
|
|
15
15
|
// line100's light hex (#eeeff1) — both theme-constant, same as before,
|
|
16
16
|
// just the wrong pair.
|
|
17
|
+
//
|
|
18
|
+
// crisp#563: this `boxShadow` is Attio's PLAIN tooltip (a truncated header
|
|
19
|
+
// label, measured on "Twitter follower count") — still an uncorroborated
|
|
20
|
+
// single sample, flagged for its own re-measurement pass, not touched here.
|
|
21
|
+
// Attio has a SECOND, visually similar but structurally different tooltip
|
|
22
|
+
// for a reference-column header (icon + the referenced type's name after
|
|
23
|
+
// the label, crisp#580) with its own measured shadow — an inset ring plus
|
|
24
|
+
// two offset shadows, `variant: "reference"` below. Background, text
|
|
25
|
+
// colour, radius, padding and font are identical between the two measured
|
|
26
|
+
// samples; only the shadow differs.
|
|
17
27
|
const spec = (0, core_1.defineSpec)({
|
|
18
28
|
name: "tooltip",
|
|
19
29
|
base: {
|
|
@@ -38,7 +48,34 @@ const spec = (0, core_1.defineSpec)({
|
|
|
38
48
|
// An at-rule whose body is declarations rather than rules, so a part
|
|
39
49
|
// expresses it directly: the entry state the overlay animates from.
|
|
40
50
|
{ selector: "@starting-style", decls: { opacity: 0 } },
|
|
51
|
+
// #580: the icon + referenced-type-name row a `variant="reference"`
|
|
52
|
+
// consumer (DataTable/RecordsTable's `ThLabel`) composes into `content`.
|
|
53
|
+
// Lives here, not in the caller's inline style, so it reads its gap from
|
|
54
|
+
// the token contract like every other measured value on this surface —
|
|
55
|
+
// `content` is portaled straight into this bubble regardless of which
|
|
56
|
+
// component built the JSX, so a nested selector reaches it the same way
|
|
57
|
+
// it reaches anything else rendered inside `.crisp-tooltip`.
|
|
58
|
+
{
|
|
59
|
+
selector: "& .crisp-tooltip-reference-row",
|
|
60
|
+
decls: {
|
|
61
|
+
display: "inline-flex",
|
|
62
|
+
alignItems: "center",
|
|
63
|
+
gap: tokens_1.contract.space["1"],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
41
66
|
],
|
|
67
|
+
variants: {
|
|
68
|
+
variant: {
|
|
69
|
+
// Inherits `base`'s boxShadow untouched -- crisp#563 updates that
|
|
70
|
+
// single value when it re-measures the plain tooltip, and this
|
|
71
|
+
// variant picks the correction up for free.
|
|
72
|
+
plain: {},
|
|
73
|
+
reference: {
|
|
74
|
+
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",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
defaultVariants: { variant: "plain" },
|
|
42
79
|
});
|
|
43
80
|
exports.spec = spec;
|
|
44
81
|
const config = (0, core_1.specToRecipe)(spec);
|
|
@@ -16,6 +16,6 @@ function AuthShell({ progress, logo, title, children, preview, footer, className
|
|
|
16
16
|
const pct = hasProgress
|
|
17
17
|
? Math.round(Math.max(0, Math.min(1, progress)) * 100)
|
|
18
18
|
: 0;
|
|
19
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, authshell_recipe_1.authshellRecipe)({ className }), ...rest, children: [hasProgress && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-progress", role: "progressbar", "aria-valuenow": pct, "aria-valuemin": 0, "aria-valuemax": 100, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-progress-fill", style: { width: `${pct}%` } }) })), logo != null && (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-logo", children: logo }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-authshell-card", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-pane", children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-authshell-body", children: [title != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-title", children: title })), (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-form", children: children })] }) }), preview != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-preview", children: preview }))] }), footer != null && (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-footer", children: footer })] }));
|
|
19
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, authshell_recipe_1.authshellRecipe)({ className }), "data-single-pane": preview == null ? "" : undefined, ...rest, children: [hasProgress && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-progress", role: "progressbar", "aria-valuenow": pct, "aria-valuemin": 0, "aria-valuemax": 100, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-progress-fill", style: { width: `${pct}%` } }) })), logo != null && (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-logo", children: logo }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-authshell-card", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-pane", children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-authshell-body", children: [title != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-title", children: title })), (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-form", children: children })] }) }), preview != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-preview", children: preview }))] }), footer != null && (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-footer", children: footer })] }));
|
|
20
20
|
}
|
|
21
21
|
AuthShell.displayName = "AuthShell";
|
|
@@ -10,25 +10,43 @@ const tokens_1 = require("../../tokens");
|
|
|
10
10
|
// from the live app, EXCEPT the 1200px preview-hiding breakpoint (called out
|
|
11
11
|
// at the container query below) — the pane drops out in JS somewhere between a
|
|
12
12
|
// measured 954px and 1393px viewport width, so 1200px is chosen, not measured.
|
|
13
|
+
//
|
|
14
|
+
// crisp#653: that two-pane card's own 692px height, border and 420px pane
|
|
15
|
+
// width were measured for a SPECIFIC screen with a real product-preview
|
|
16
|
+
// pane -- omitting `preview` (`data-single-pane`, set by the component) used
|
|
17
|
+
// to keep all three anyway, leaving a bordered box with a huge empty area
|
|
18
|
+
// under short content. Attio's own single-pane screen (`app.attio.com/login`,
|
|
19
|
+
// reachable in a fresh logged-out browser context -- no session needed) has
|
|
20
|
+
// NO card at all: every ancestor of its "Sign in" heading measured
|
|
21
|
+
// `border: 0px none`, `box-shadow: none`, `background: transparent`, a
|
|
22
|
+
// 360-364px content column (not 420px), sized to its own content and
|
|
23
|
+
// roughly centred in the viewport rather than pinned near the top.
|
|
24
|
+
// `data-single-pane` now drops the card chrome and narrows the body to
|
|
25
|
+
// match; the two-pane card is unchanged.
|
|
13
26
|
const config = (0, core_1.defineRecipe)({
|
|
14
27
|
name: "authshell",
|
|
15
28
|
base: `min-height: 100%; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; position: relative;
|
|
16
29
|
background: ${tokens_1.contract.bgSurface}; font-family: ${tokens_1.contract.font.sans}; font-size: ${tokens_1.contract.text.lg}; line-height: 20px; color: ${tokens_1.contract.fgPrimary};
|
|
17
30
|
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
18
31
|
|
|
32
|
+
&[data-single-pane] { justify-content: center; }
|
|
33
|
+
|
|
19
34
|
& .crisp-authshell-progress { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: ${tokens_1.contract.borderDefault}; }
|
|
20
35
|
& .crisp-authshell-progress-fill { height: 2px; background: ${tokens_1.contract.fgPrimary}; border-radius: 4px; transition: width .2s; }
|
|
21
36
|
|
|
22
37
|
& .crisp-authshell-logo { margin-top: ${tokens_1.contract.space["8"]}; height: 24px; display: flex; align-items: center; }
|
|
23
38
|
|
|
24
39
|
& .crisp-authshell-card { margin-top: ${tokens_1.contract.space["6"]}; display: flex; height: 692px; border-radius: 20px; border: 1px solid ${tokens_1.contract.borderSubtle}; overflow: hidden; }
|
|
40
|
+
&[data-single-pane] .crisp-authshell-card { height: auto; border: none; border-radius: 0; overflow: visible; }
|
|
25
41
|
|
|
26
42
|
& .crisp-authshell-pane { flex: 0 0 564px; padding: 72px; display: flex; flex-direction: column; box-sizing: border-box; }
|
|
43
|
+
&[data-single-pane] .crisp-authshell-pane { flex: 0 0 auto; padding: 0; }
|
|
27
44
|
|
|
28
45
|
& .crisp-authshell-preview { flex: 0 0 564px; padding: 96px 64px; background: ${tokens_1.contract.bgSubtle}; border-inline-start: 1px solid color-mix(in srgb, ${tokens_1.contract.fgPrimary} 5%, transparent); position: relative; overflow: hidden; box-sizing: border-box; }
|
|
29
46
|
& .crisp-authshell-preview::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 120px; background: linear-gradient(color-mix(in srgb, ${tokens_1.contract.bgSubtle} 0%, transparent) 0%, ${tokens_1.contract.bgSubtle} 100%); pointer-events: none; }
|
|
30
47
|
|
|
31
48
|
& .crisp-authshell-body { width: 420px; margin: 0 auto; display: flex; flex-direction: column; gap: ${tokens_1.contract.space["8"]}; align-items: center; }
|
|
49
|
+
&[data-single-pane] .crisp-authshell-body { width: 360px; }
|
|
32
50
|
|
|
33
51
|
& .crisp-authshell-title { font-size: 20px; line-height: 24px; font-weight: ${tokens_1.contract.weight.semibold}; letter-spacing: -0.4px; color: ${tokens_1.contract.fgPrimary}; align-self: stretch; }
|
|
34
52
|
|
|
@@ -46,13 +46,6 @@ const menu_1 = require("../../components/menu");
|
|
|
46
46
|
const popover_1 = require("../../components/popover");
|
|
47
47
|
const core_1 = require("../../core");
|
|
48
48
|
const comments_recipe_1 = require("./comments.recipe");
|
|
49
|
-
/** A single literal JSX occurrence for the reply glyph, used at two sizes --
|
|
50
|
-
* so the RTL-mirroring sweep (which counts the flip-marker attribute against
|
|
51
|
-
* source-text occurrences, not runtime renders) sees one mark for one
|
|
52
|
-
* direction-bearing icon, not two. */
|
|
53
|
-
function ReplyGlyph({ size }) {
|
|
54
|
-
return ((0, jsx_runtime_1.jsx)(lucide_react_1.CornerUpLeft, { "data-rtl-flip": "", size: size, strokeWidth: 2, "aria-hidden": "true" }));
|
|
55
|
-
}
|
|
56
49
|
const DEFAULT_EMOJI = [
|
|
57
50
|
"👍",
|
|
58
51
|
"👎",
|
|
@@ -159,7 +152,7 @@ function serializeComposerField(field) {
|
|
|
159
152
|
* disabled until the field holds text. Uncontrolled — reads its own DOM on
|
|
160
153
|
* submit, the same shape `RichTextEditor` uses for its body.
|
|
161
154
|
*/
|
|
162
|
-
const Composer = React.forwardRef(function Composer({ placeholder, postLabel, mentionTrigger, renderMentionPicker,
|
|
155
|
+
const Composer = React.forwardRef(function Composer({ placeholder, postLabel, mentionTrigger, renderMentionPicker, mentionToolLabel, onSubmit, }, ref) {
|
|
163
156
|
const fieldRef = React.useRef(null);
|
|
164
157
|
const [empty, setEmpty] = React.useState(true);
|
|
165
158
|
const [mention, setMention] = React.useState(null);
|
|
@@ -295,15 +288,15 @@ const Composer = React.forwardRef(function Composer({ placeholder, postLabel, me
|
|
|
295
288
|
e.preventDefault();
|
|
296
289
|
submit();
|
|
297
290
|
}
|
|
298
|
-
} }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-composer-foot", children: [(0, jsx_runtime_1.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
291
|
+
} }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-composer-foot", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-composer-tools", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-tool", "aria-label": mentionToolLabel, onClick: () => {
|
|
292
|
+
const field = fieldRef.current;
|
|
293
|
+
if (!field)
|
|
294
|
+
return;
|
|
295
|
+
field.focus();
|
|
296
|
+
document.execCommand("insertText", false, mentionTrigger);
|
|
297
|
+
syncEmpty();
|
|
298
|
+
updateMention();
|
|
299
|
+
}, children: "@" }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-post", disabled: empty, onClick: submit, children: postLabel })] }), mention &&
|
|
307
300
|
renderMentionPicker?.({
|
|
308
301
|
query: mention.query,
|
|
309
302
|
anchor: mention.anchor,
|
|
@@ -312,7 +305,7 @@ const Composer = React.forwardRef(function Composer({ placeholder, postLabel, me
|
|
|
312
305
|
})] }));
|
|
313
306
|
});
|
|
314
307
|
function CommentRow({ comment, replyLabel, reactLabel, resolveLabel, moreLabel, copyLinkLabel, deleteLabel, addReactionLabel, reactionSearchLabel, emojiOptions, resolvable, resolved, resolvedLabel, onReply, onReact, onResolve, onCopyLink, onDeleteRequest, }) {
|
|
315
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row", children: [(0, jsx_runtime_1.jsx)(AuthorAvatar, { author: comment.author }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-main", children: [resolvedLabel != null && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-resolved-label", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 2.5, "aria-hidden": "true" }), resolvedLabel] })), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-comments-author", children: comment.author.name }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-comments-time", children: comment.time })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-body-text", children: comment.body }), comment.reactions && comment.reactions.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-reactions", children: [comment.reactions.map((r) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-comments-reaction", "data-reacted": r.reactedByMe ? "" : undefined, onClick: () => onReact(r.emoji), children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: r.emoji }), r.count] }, r.emoji))), (0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "start", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-reaction-add", "aria-label": addReactionLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SmilePlus, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: (0, jsx_runtime_1.jsx)(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: onReact }) })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-actions", children: [onReply && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": replyLabel, onClick: onReply, children: (0, jsx_runtime_1.jsx)(
|
|
308
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row", children: [(0, jsx_runtime_1.jsx)(AuthorAvatar, { author: comment.author }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-main", children: [resolvedLabel != null && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-resolved-label", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 2.5, "aria-hidden": "true" }), resolvedLabel] })), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-comments-author", children: comment.author.name }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-comments-time", children: comment.time })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-body-text", children: comment.body }), comment.reactions && comment.reactions.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-reactions", children: [comment.reactions.map((r) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-comments-reaction", "data-reacted": r.reactedByMe ? "" : undefined, onClick: () => onReact(r.emoji), children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: r.emoji }), r.count] }, r.emoji))), (0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "start", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-reaction-add", "aria-label": addReactionLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SmilePlus, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: (0, jsx_runtime_1.jsx)(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: onReact }) })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-actions", children: [onReply && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": replyLabel, onClick: onReply, children: (0, jsx_runtime_1.jsx)(lucide_react_1.CornerUpLeft, { "data-rtl-flip": "", size: 16, strokeWidth: 2, "aria-hidden": "true" }) })), (0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": reactLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SmilePlus, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: (0, jsx_runtime_1.jsx)(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: onReact }) }), resolvable && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": resolveLabel, "aria-pressed": !!resolved, "data-active": resolved ? "" : undefined, onClick: onResolve, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": moreLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: onCopyLink, children: copyLinkLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { danger: true, onSelect: onDeleteRequest, children: deleteLabel })] })] })] }));
|
|
316
309
|
}
|
|
317
310
|
/**
|
|
318
311
|
* Per-record comment threads (crisp#625) — measured live on app.attio.com's
|
|
@@ -323,7 +316,7 @@ function CommentRow({ comment, replyLabel, reactLabel, resolveLabel, moreLabel,
|
|
|
323
316
|
* separate row affordance (P-051) — deliberately NOT part of this
|
|
324
317
|
* component, since it lives in a table cell this component doesn't own.
|
|
325
318
|
*/
|
|
326
|
-
function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved = false, onShowResolvedChange, onlyMine = false, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions = DEFAULT_EMOJI, mentionTrigger = "@", renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp,
|
|
319
|
+
function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved = false, onShowResolvedChange, onlyMine = false, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions = DEFAULT_EMOJI, mentionTrigger = "@", renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp, composerMentionToolLabel: composerMentionToolLabelProp, className, ...rest }) {
|
|
327
320
|
const closeLabel = (0, core_1.useCrispMessage)(closeLabelProp, "Close");
|
|
328
321
|
const backLabel = (0, core_1.useCrispMessage)(backLabelProp, "Back");
|
|
329
322
|
const title = (0, core_1.useCrispMessage)(titleProp, "All comments");
|
|
@@ -349,7 +342,6 @@ function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResol
|
|
|
349
342
|
const deleteConfirmBody = (0, core_1.useCrispMessage)(deleteConfirmBodyProp, "You're about to delete this comment. Once deleted it cannot be restored.");
|
|
350
343
|
const deleteConfirmActionLabel = (0, core_1.useCrispMessage)(deleteConfirmActionLabelProp, "Delete");
|
|
351
344
|
const cancelLabel = (0, core_1.useCrispMessage)(cancelLabelProp, "Cancel");
|
|
352
|
-
const composerReplyToolLabel = (0, core_1.useCrispMessage)(composerReplyToolLabelProp, "Reply");
|
|
353
345
|
const composerMentionToolLabel = (0, core_1.useCrispMessage)(composerMentionToolLabelProp, "Mention someone");
|
|
354
346
|
const [internalActiveId, setInternalActiveId] = React.useState(null);
|
|
355
347
|
const activeThreadId = activeThreadIdProp ?? internalActiveId;
|
|
@@ -394,7 +386,7 @@ function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResol
|
|
|
394
386
|
? resolvedByYouLabel
|
|
395
387
|
: (resolvedByLabel?.(t.resolvedBy.name) ??
|
|
396
388
|
`Resolved by ${t.resolvedBy.name}`)
|
|
397
|
-
: undefined, onReply: () => setActiveThreadId(t.id), onReact: (emoji) => onReact(t.id, t.root.id, emoji), onResolve: onResolve ? () => onResolve(t.id) : undefined, onCopyLink: () => copyLink(t.id, t.root.id), onDeleteRequest: () => setDeleteTarget({ threadId: t.id, commentId: t.root.id }) }, t.id)))) }), (0, jsx_runtime_1.jsx)(Composer, { placeholder: activeThread ? replyPlaceholder : composerPlaceholder, postLabel: activeThread ? replyPostLabel : postLabel, mentionTrigger: mentionTrigger, renderMentionPicker: renderMentionPicker,
|
|
389
|
+
: undefined, onReply: () => setActiveThreadId(t.id), onReact: (emoji) => onReact(t.id, t.root.id, emoji), onResolve: onResolve ? () => onResolve(t.id) : undefined, onCopyLink: () => copyLink(t.id, t.root.id), onDeleteRequest: () => setDeleteTarget({ threadId: t.id, commentId: t.root.id }) }, t.id)))) }), (0, jsx_runtime_1.jsx)(Composer, { placeholder: activeThread ? replyPlaceholder : composerPlaceholder, postLabel: activeThread ? replyPostLabel : postLabel, mentionTrigger: mentionTrigger, renderMentionPicker: renderMentionPicker, mentionToolLabel: composerMentionToolLabel, onSubmit: (text) => onPost(text, activeThread ? activeThread.id : null) }, activeThread ? activeThread.id : "root"), (0, jsx_runtime_1.jsxs)(dialog_1.Dialog, { open: deleteTarget != null, onOpenChange: (open) => {
|
|
398
390
|
if (!open)
|
|
399
391
|
setDeleteTarget(null);
|
|
400
392
|
}, title: deleteConfirmTitle, children: [(0, jsx_runtime_1.jsx)(dialog_1.DialogBody, { children: deleteConfirmBody }), (0, jsx_runtime_1.jsxs)(dialog_1.DialogFooter, { children: [(0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "neutral", size: "md", onClick: () => setDeleteTarget(null), children: cancelLabel }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "danger", size: "md", onClick: () => {
|