@crisp-ui-kit/crisp 0.54.0 → 0.54.1
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/activityfeed.css +12 -0
- package/dist/activityfeed.d.ts +7 -0
- package/dist/activityfeed.layered.css +14 -0
- package/dist/badge.css +3 -0
- package/dist/badge.d.ts +2 -2
- package/dist/badge.layered.css +3 -0
- package/dist/cjs/components/badge/badge.recipe.js +10 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/product/activityfeed/activityfeed.js +38 -0
- package/dist/cjs/product/activityfeed/activityfeed.recipe.js +35 -0
- package/dist/cjs/product/activityfeed/index.js +10 -0
- package/dist/cjs/product/richtext/richtext.js +43 -0
- package/dist/cjs/tokens/palette.js +14 -0
- package/dist/cjs/tokens/semantic.js +5 -0
- package/dist/esm/components/badge/badge.recipe.js +10 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/product/activityfeed/activityfeed.js +32 -0
- package/dist/esm/product/activityfeed/activityfeed.recipe.js +32 -0
- package/dist/esm/product/activityfeed/index.js +2 -0
- package/dist/esm/product/richtext/richtext.js +43 -0
- package/dist/esm/tokens/palette.js +14 -0
- package/dist/esm/tokens/semantic.js +5 -0
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +16 -4
- package/dist/styles.css +22 -0
- package/dist/styles.layered.css +22 -0
- package/dist/tokens.css +6 -0
- package/dist/tokens.json +16 -4
- package/dist/types/components/badge/badge.d.ts +2 -0
- package/dist/types/components/badge/badge.recipe.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/product/activityfeed/activityfeed.d.ts +57 -0
- package/dist/types/product/activityfeed/activityfeed.recipe.d.ts +3 -0
- package/dist/types/product/activityfeed/index.d.ts +2 -0
- package/dist/types/tokens/contract.d.ts +3 -0
- package/dist/types/tokens/palette.d.ts +12 -0
- package/dist/types/tokens/semantic.d.ts +5 -0
- package/package.json +6 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
.crisp-activityfeed {
|
|
2
|
+
box-sizing: border-box; max-width: 767px; margin-inline: auto; padding: var(--crisp-space-6); display: flex; flex-direction: column; gap: var(--crisp-space-4);
|
|
3
|
+
& .crisp-activityfeed-row { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding-bottom: var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
|
|
4
|
+
& .crisp-activityfeed-row:last-of-type { border-bottom: none; padding-bottom: 0; }
|
|
5
|
+
& .crisp-activityfeed-row-head { display: flex; align-items: center; gap: var(--crisp-space-2); }
|
|
6
|
+
& .crisp-activityfeed-row-spacer { flex: 1; }
|
|
7
|
+
& .crisp-activityfeed-row-when { font-size: var(--crisp-text-xs); color: var(--crisp-fg-muted); white-space: nowrap; }
|
|
8
|
+
& .crisp-activityfeed-diff { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--crisp-space-1); }
|
|
9
|
+
& .crisp-activityfeed-diff-item { padding-inline-start: var(--crisp-space-8); font-size: var(--crisp-text-md); }
|
|
10
|
+
& .crisp-activityfeed-diff-field { color: var(--crisp-fg-muted); }
|
|
11
|
+
& .crisp-activityfeed-diff-before { color: var(--crisp-fg-muted); text-decoration: line-through; }
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@layer crisp-components {
|
|
2
|
+
.crisp-activityfeed {
|
|
3
|
+
box-sizing: border-box; max-width: 767px; margin-inline: auto; padding: var(--crisp-space-6); display: flex; flex-direction: column; gap: var(--crisp-space-4);
|
|
4
|
+
& .crisp-activityfeed-row { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding-bottom: var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
|
|
5
|
+
& .crisp-activityfeed-row:last-of-type { border-bottom: none; padding-bottom: 0; }
|
|
6
|
+
& .crisp-activityfeed-row-head { display: flex; align-items: center; gap: var(--crisp-space-2); }
|
|
7
|
+
& .crisp-activityfeed-row-spacer { flex: 1; }
|
|
8
|
+
& .crisp-activityfeed-row-when { font-size: var(--crisp-text-xs); color: var(--crisp-fg-muted); white-space: nowrap; }
|
|
9
|
+
& .crisp-activityfeed-diff { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--crisp-space-1); }
|
|
10
|
+
& .crisp-activityfeed-diff-item { padding-inline-start: var(--crisp-space-8); font-size: var(--crisp-text-md); }
|
|
11
|
+
& .crisp-activityfeed-diff-field { color: var(--crisp-fg-muted); }
|
|
12
|
+
& .crisp-activityfeed-diff-before { color: var(--crisp-fg-muted); text-decoration: line-through; }
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/badge.css
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
.crisp-badge--tone_danger {
|
|
18
18
|
background: color-mix(in srgb, var(--crisp-bg-danger) 14%, transparent); color: var(--crisp-fg-danger-soft); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent);
|
|
19
19
|
}
|
|
20
|
+
.crisp-badge--tone_purple {
|
|
21
|
+
background: var(--crisp-bg-purple-soft); color: var(--crisp-fg-purple-soft); box-shadow: inset 0 0 0 1px var(--crisp-ring-purple-soft);
|
|
22
|
+
}
|
|
20
23
|
.crisp-badge--size_small {
|
|
21
24
|
height: 16px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-xs); border-radius: var(--crisp-radius-sm);
|
|
22
25
|
}
|
package/dist/badge.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { RecipeFn } from "../core/types";
|
|
2
2
|
|
|
3
3
|
export interface BadgeVariants {
|
|
4
|
-
tone?: "neutral" | "brand" | "success" | "warning" | "danger";
|
|
4
|
+
tone?: "neutral" | "brand" | "success" | "warning" | "danger" | "purple";
|
|
5
5
|
size?: "small" | "medium" | "large";
|
|
6
6
|
variant?: "soft" | "solid";
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export declare const badgeRecipe: RecipeFn<{ tone: { neutral: string; brand: string; success: string; warning: string; danger: string }; size: { small: string; medium: string; large: string }; variant: { soft: string; solid: string } }>;
|
|
9
|
+
export declare const badgeRecipe: RecipeFn<{ tone: { neutral: string; brand: string; success: string; warning: string; danger: string; purple: string }; size: { small: string; medium: string; large: string }; variant: { soft: string; solid: string } }>;
|
package/dist/badge.layered.css
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
.crisp-badge--tone_danger {
|
|
19
19
|
background: color-mix(in srgb, var(--crisp-bg-danger) 14%, transparent); color: var(--crisp-fg-danger-soft); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent);
|
|
20
20
|
}
|
|
21
|
+
.crisp-badge--tone_purple {
|
|
22
|
+
background: var(--crisp-bg-purple-soft); color: var(--crisp-fg-purple-soft); box-shadow: inset 0 0 0 1px var(--crisp-ring-purple-soft);
|
|
23
|
+
}
|
|
21
24
|
.crisp-badge--size_small {
|
|
22
25
|
height: 16px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-xs); border-radius: var(--crisp-radius-sm);
|
|
23
26
|
}
|
|
@@ -63,6 +63,16 @@ const spec = (0, core_1.defineSpec)({
|
|
|
63
63
|
color: tokens_1.contract.fgDangerSoft,
|
|
64
64
|
boxShadow: ring(tokens_1.contract.bgDanger, 30),
|
|
65
65
|
},
|
|
66
|
+
// crisp#506: a distinct purple used for role-style labels (Attio's own
|
|
67
|
+
// "Admin" role pill), not a status/severity colour -- the other five
|
|
68
|
+
// tones don't cover this hue. Unlike those, these are the measured
|
|
69
|
+
// *solid* values directly (not a color-mix tint of a base accent):
|
|
70
|
+
// Attio's own pill background reads a flat, opaque rgb(), no alpha.
|
|
71
|
+
purple: {
|
|
72
|
+
background: tokens_1.contract.bgPurpleSoft,
|
|
73
|
+
color: tokens_1.contract.fgPurpleSoft,
|
|
74
|
+
boxShadow: `inset 0 0 0 1px ${tokens_1.contract.ringPurpleSoft}`,
|
|
75
|
+
},
|
|
66
76
|
},
|
|
67
77
|
size: {
|
|
68
78
|
small: {
|
package/dist/cjs/index.js
CHANGED
|
@@ -81,6 +81,7 @@ __exportStar(require("./core"), exports);
|
|
|
81
81
|
// product screens (Sidebar, RecordsTable, RecordPage, Inbox, …). Use them as-is
|
|
82
82
|
// or as composition references for your own data; `components/` above holds
|
|
83
83
|
// the generic primitives (Table, DataTable, Button, …) to build your own UI from.
|
|
84
|
+
__exportStar(require("./product/activityfeed"), exports);
|
|
84
85
|
__exportStar(require("./product/appshell"), exports);
|
|
85
86
|
__exportStar(require("./product/authshell"), exports);
|
|
86
87
|
__exportStar(require("./product/board"), exports);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ActivityFeed = ActivityFeed;
|
|
4
|
+
exports.ActivityFeedRow = ActivityFeedRow;
|
|
5
|
+
exports.ActivityFeedDiffItem = ActivityFeedDiffItem;
|
|
6
|
+
exports.ActivityFeedDiffList = ActivityFeedDiffList;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const activityfeed_recipe_1 = require("./activityfeed.recipe");
|
|
9
|
+
/**
|
|
10
|
+
* A record's own event timeline — a vertically stacked, bordered list of
|
|
11
|
+
* `ActivityFeedRow`s, capped at a readable content width. Built for
|
|
12
|
+
* internal/grc's per-record "Activity" tab (crisp#462 SHELL/forms gap): it
|
|
13
|
+
* replaces a hand-rolled `<div className="mx-auto max-w-3xl space-y-4
|
|
14
|
+
* p-6">`. No live Attio re-measurement behind its own chrome this pass (the
|
|
15
|
+
* CDP bridge was unreachable when this was built) — see the recipe's own
|
|
16
|
+
* doc comment for which already-measured crisp values it reuses instead of
|
|
17
|
+
* a new guess.
|
|
18
|
+
*/
|
|
19
|
+
function ActivityFeed({ className, children, ...rest }) {
|
|
20
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, activityfeed_recipe_1.activityfeedRecipe)({ className }), ...rest, children: children }));
|
|
21
|
+
}
|
|
22
|
+
ActivityFeed.displayName = "ActivityFeed";
|
|
23
|
+
/** One event row: actor + action + timestamp, with an optional diff list
|
|
24
|
+
* underneath. The last row in an `ActivityFeed` draws no divider. */
|
|
25
|
+
function ActivityFeedRow({ actor, action, when, children, }) {
|
|
26
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-activityfeed-row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-activityfeed-row-head", children: [actor, action, (0, jsx_runtime_1.jsx)("span", { className: "crisp-activityfeed-row-spacer" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-activityfeed-row-when", children: when })] }), children] }));
|
|
27
|
+
}
|
|
28
|
+
ActivityFeedRow.displayName = "ActivityFeedRow";
|
|
29
|
+
/** One changed-field line under an `ActivityFeedRow` — "Field: before →
|
|
30
|
+
* after", or "Field: set to after" when there is no known prior value. */
|
|
31
|
+
function ActivityFeedDiffItem({ field, before, after, }) {
|
|
32
|
+
return ((0, jsx_runtime_1.jsxs)("li", { className: "crisp-activityfeed-diff-item", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-activityfeed-diff-field", children: field }), " ", before !== undefined ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-activityfeed-diff-before", children: before }), " ", (0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "\u2192" }), " ", (0, jsx_runtime_1.jsx)("span", { children: after })] })) : ((0, jsx_runtime_1.jsx)("span", { children: after }))] }));
|
|
33
|
+
}
|
|
34
|
+
ActivityFeedDiffItem.displayName = "ActivityFeedDiffItem";
|
|
35
|
+
function ActivityFeedDiffList({ children, }) {
|
|
36
|
+
return (0, jsx_runtime_1.jsx)("ul", { className: "crisp-activityfeed-diff", children: children });
|
|
37
|
+
}
|
|
38
|
+
ActivityFeedDiffList.displayName = "ActivityFeedDiffList";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.activityfeedRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// A record's own event timeline (crisp#462 gap: internal/grc's ActivityTab
|
|
7
|
+
// hand-rolled this as a bare `<div className="mx-auto max-w-3xl space-y-4
|
|
8
|
+
// p-6">` list of bordered rows). No live Attio re-measurement behind these
|
|
9
|
+
// values this pass -- the CDP bridge to the owner's Vivaldi was unreachable
|
|
10
|
+
// when this was built (documented in the component's own doc comment) --
|
|
11
|
+
// so this reuses already-measured crisp values throughout rather than a new
|
|
12
|
+
// guess: `space["6"]` (24px) padding is RecordPanel's own content padding,
|
|
13
|
+
// the row's bottom border reuses `borderSubtle` (the same divider every
|
|
14
|
+
// other bordered row in this codebase draws with), and the 767px content
|
|
15
|
+
// cap matches SettingsSection's own measured content width.
|
|
16
|
+
const config = (0, core_1.defineRecipe)({
|
|
17
|
+
name: "activityfeed",
|
|
18
|
+
base: `box-sizing: border-box; max-width: 767px; margin-inline: auto; padding: ${tokens_1.contract.space["6"]};
|
|
19
|
+
display: flex; flex-direction: column; gap: ${tokens_1.contract.space["4"]};
|
|
20
|
+
& .crisp-activityfeed-row { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["2"]}; padding-bottom: ${tokens_1.contract.space["4"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; }
|
|
21
|
+
& .crisp-activityfeed-row:last-of-type { border-bottom: none; padding-bottom: 0; }
|
|
22
|
+
& .crisp-activityfeed-row-head { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; }
|
|
23
|
+
& .crisp-activityfeed-row-spacer { flex: 1; }
|
|
24
|
+
& .crisp-activityfeed-row-when { font-size: ${tokens_1.contract.text.xs}; color: ${tokens_1.contract.fgMuted}; white-space: nowrap; }
|
|
25
|
+
& .crisp-activityfeed-diff { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: ${tokens_1.contract.space["1"]}; }
|
|
26
|
+
& .crisp-activityfeed-diff-item { padding-inline-start: ${tokens_1.contract.space["8"]}; font-size: ${tokens_1.contract.text.md}; }
|
|
27
|
+
& .crisp-activityfeed-diff-field { color: ${tokens_1.contract.fgMuted}; }
|
|
28
|
+
& .crisp-activityfeed-diff-before { color: ${tokens_1.contract.fgMuted}; text-decoration: line-through; }
|
|
29
|
+
`,
|
|
30
|
+
variants: {},
|
|
31
|
+
defaultVariants: {},
|
|
32
|
+
compoundVariants: [],
|
|
33
|
+
});
|
|
34
|
+
exports.activityfeedRecipe = (0, core_1.resolveRecipe)(config);
|
|
35
|
+
exports.default = config;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.activityfeedRecipe = exports.ActivityFeedRow = exports.ActivityFeedDiffList = exports.ActivityFeedDiffItem = exports.ActivityFeed = void 0;
|
|
4
|
+
var activityfeed_1 = require("./activityfeed");
|
|
5
|
+
Object.defineProperty(exports, "ActivityFeed", { enumerable: true, get: function () { return activityfeed_1.ActivityFeed; } });
|
|
6
|
+
Object.defineProperty(exports, "ActivityFeedDiffItem", { enumerable: true, get: function () { return activityfeed_1.ActivityFeedDiffItem; } });
|
|
7
|
+
Object.defineProperty(exports, "ActivityFeedDiffList", { enumerable: true, get: function () { return activityfeed_1.ActivityFeedDiffList; } });
|
|
8
|
+
Object.defineProperty(exports, "ActivityFeedRow", { enumerable: true, get: function () { return activityfeed_1.ActivityFeedRow; } });
|
|
9
|
+
var activityfeed_recipe_1 = require("./activityfeed.recipe");
|
|
10
|
+
Object.defineProperty(exports, "activityfeedRecipe", { enumerable: true, get: function () { return activityfeed_recipe_1.activityfeedRecipe; } });
|
|
@@ -183,6 +183,14 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
|
|
|
183
183
|
// (documented React behaviour), so a plain onFocus/onBlur pair on this
|
|
184
184
|
// same wrapper sees the portal's contents correctly.
|
|
185
185
|
const mentionPickerHasFocusRef = React.useRef(false);
|
|
186
|
+
// A caret position `insertMention` just established, to be re-asserted
|
|
187
|
+
// the next time `body` regains focus (#514 fixed the picker stealing
|
|
188
|
+
// focus on OPEN; this is the same "caret resets to offset 0" mechanism
|
|
189
|
+
// on SELECT — see insertMention's own comment for why one synchronous
|
|
190
|
+
// `sel.addRange` isn't enough). One-shot: consumed and cleared by the
|
|
191
|
+
// very next focus or input, whichever comes first, so a stale pointer
|
|
192
|
+
// can never later hijack an unrelated focus.
|
|
193
|
+
const pendingCaretRef = React.useRef(null);
|
|
186
194
|
const syncEmpty = (el) => {
|
|
187
195
|
if (el)
|
|
188
196
|
el.toggleAttribute("data-empty", (el.textContent ?? "") === "");
|
|
@@ -494,6 +502,20 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
|
|
|
494
502
|
after.collapse(true);
|
|
495
503
|
sel.removeAllRanges();
|
|
496
504
|
sel.addRange(after);
|
|
505
|
+
// `select`ing a mention is usually the LAST thing the consumer's
|
|
506
|
+
// picker does before it unmounts (ActorPicker-shaped consumers call
|
|
507
|
+
// `close` right after `select`, same as this file's own doc example).
|
|
508
|
+
// A picker built on a dialog primitive (crisp's own `Command`
|
|
509
|
+
// included) commonly restores focus to whatever had it before the
|
|
510
|
+
// dialog opened as part of ITS OWN unmount cleanup — a plain
|
|
511
|
+
// `.focus()` with no Range of its own, which collapses a
|
|
512
|
+
// contentEditable's caret to offset 0. That cleanup runs after this
|
|
513
|
+
// function returns (the picker only unmounts once `closeMention`
|
|
514
|
+
// below flips `mention` to null and React commits it), so the
|
|
515
|
+
// Selection set two lines up can already be correct here and still
|
|
516
|
+
// get clobbered a tick later. Re-assert it the next time `body`
|
|
517
|
+
// actually regains focus, whenever that turns out to be.
|
|
518
|
+
pendingCaretRef.current = { node: space, offset: 1 };
|
|
497
519
|
closeMention();
|
|
498
520
|
syncEmpty(bodyRef.current);
|
|
499
521
|
emit();
|
|
@@ -558,10 +580,31 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
|
|
|
558
580
|
} })), (0, jsx_runtime_1.jsx)("div", { ref: bodyRef, className: "crisp-rte-body", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-multiline": "true", "aria-label": bodyLabel, "aria-readonly": readOnly || undefined, "data-placeholder": placeholder, onInput: readOnly
|
|
559
581
|
? undefined
|
|
560
582
|
: () => {
|
|
583
|
+
// A real keystroke landing here means `body` already has
|
|
584
|
+
// focus on its own terms — any caret a prior mention
|
|
585
|
+
// selection was waiting to reassert (see insertMention) is
|
|
586
|
+
// moot now and must not later jump the caret backward on
|
|
587
|
+
// some unrelated future focus.
|
|
588
|
+
pendingCaretRef.current = null;
|
|
561
589
|
syncEmpty(bodyRef.current);
|
|
562
590
|
updateSlashTrigger();
|
|
563
591
|
updateMention();
|
|
564
592
|
emit();
|
|
593
|
+
}, onFocus: readOnly
|
|
594
|
+
? undefined
|
|
595
|
+
: () => {
|
|
596
|
+
const pending = pendingCaretRef.current;
|
|
597
|
+
pendingCaretRef.current = null;
|
|
598
|
+
if (!pending?.node.isConnected)
|
|
599
|
+
return;
|
|
600
|
+
const sel = window.getSelection();
|
|
601
|
+
if (!sel)
|
|
602
|
+
return;
|
|
603
|
+
const range = document.createRange();
|
|
604
|
+
range.setStart(pending.node, pending.offset);
|
|
605
|
+
range.collapse(true);
|
|
606
|
+
sel.removeAllRanges();
|
|
607
|
+
sel.addRange(range);
|
|
565
608
|
}, onMouseUp: readOnly
|
|
566
609
|
? undefined
|
|
567
610
|
: () => {
|
|
@@ -100,4 +100,18 @@ exports.palette = {
|
|
|
100
100
|
// as-is per build/spec-check.mjs's own note), this token exists only for
|
|
101
101
|
// Badge's soft-neutral text, so darkening it doesn't move any other surface.
|
|
102
102
|
softNeutralText: { light: "#686870", dark: "#9a9aa2" },
|
|
103
|
+
// Badge's purple/role-pill tone (crisp#506) -- measured live off Attio's
|
|
104
|
+
// own role pill ("Admin", app.attio.com/<ws>/settings/workspace/members):
|
|
105
|
+
// background rgb(245,240,255), text rgb(98,56,181). The ring re-uses a
|
|
106
|
+
// second, independent measurement from the same session (a company's
|
|
107
|
+
// Categories tag pill sharing the identical background, "Broadcasting",
|
|
108
|
+
// app.attio.com/<ws>/companies) at rgb(232,221,254) -- same hue family,
|
|
109
|
+
// cross-validated rather than guessed. Dark mode was NOT measured (no
|
|
110
|
+
// live Attio session was reachable when this tone was added) -- these
|
|
111
|
+
// three light values are reused as-is for dark rather than an invented
|
|
112
|
+
// "brightened" guess (Lesson: don't add unmeasured "robustness"). Needs a
|
|
113
|
+
// real dark-mode measurement before this note can be removed.
|
|
114
|
+
purpleTint: { light: "#f5f0ff", dark: "#f5f0ff" },
|
|
115
|
+
softPurpleText: { light: "#6238b5", dark: "#6238b5" },
|
|
116
|
+
purpleRing: { light: "#e8ddfe", dark: "#e8ddfe" },
|
|
103
117
|
};
|
|
@@ -63,4 +63,9 @@ exports.semantic = {
|
|
|
63
63
|
fgWarningSoft: "softWarningText",
|
|
64
64
|
fgDangerSoft: "softDangerText",
|
|
65
65
|
fgNeutralSoft: "softNeutralText",
|
|
66
|
+
/** Badge's purple/role-pill tone (crisp#506) — see palette.ts's comment
|
|
67
|
+
* on `purpleTint`/`softPurpleText`/`purpleRing` for the measurement. */
|
|
68
|
+
bgPurpleSoft: "purpleTint",
|
|
69
|
+
fgPurpleSoft: "softPurpleText",
|
|
70
|
+
ringPurpleSoft: "purpleRing",
|
|
66
71
|
};
|
|
@@ -60,6 +60,16 @@ const spec = defineSpec({
|
|
|
60
60
|
color: c.fgDangerSoft,
|
|
61
61
|
boxShadow: ring(c.bgDanger, 30),
|
|
62
62
|
},
|
|
63
|
+
// crisp#506: a distinct purple used for role-style labels (Attio's own
|
|
64
|
+
// "Admin" role pill), not a status/severity colour -- the other five
|
|
65
|
+
// tones don't cover this hue. Unlike those, these are the measured
|
|
66
|
+
// *solid* values directly (not a color-mix tint of a base accent):
|
|
67
|
+
// Attio's own pill background reads a flat, opaque rgb(), no alpha.
|
|
68
|
+
purple: {
|
|
69
|
+
background: c.bgPurpleSoft,
|
|
70
|
+
color: c.fgPurpleSoft,
|
|
71
|
+
boxShadow: `inset 0 0 0 1px ${c.ringPurpleSoft}`,
|
|
72
|
+
},
|
|
63
73
|
},
|
|
64
74
|
size: {
|
|
65
75
|
small: {
|
package/dist/esm/index.js
CHANGED
|
@@ -65,6 +65,7 @@ export * from "./core/index.js";
|
|
|
65
65
|
// product screens (Sidebar, RecordsTable, RecordPage, Inbox, …). Use them as-is
|
|
66
66
|
// or as composition references for your own data; `components/` above holds
|
|
67
67
|
// the generic primitives (Table, DataTable, Button, …) to build your own UI from.
|
|
68
|
+
export * from "./product/activityfeed/index.js";
|
|
68
69
|
export * from "./product/appshell/index.js";
|
|
69
70
|
export * from "./product/authshell/index.js";
|
|
70
71
|
export * from "./product/board/index.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { activityfeedRecipe } from "./activityfeed.recipe.js";
|
|
3
|
+
/**
|
|
4
|
+
* A record's own event timeline — a vertically stacked, bordered list of
|
|
5
|
+
* `ActivityFeedRow`s, capped at a readable content width. Built for
|
|
6
|
+
* internal/grc's per-record "Activity" tab (crisp#462 SHELL/forms gap): it
|
|
7
|
+
* replaces a hand-rolled `<div className="mx-auto max-w-3xl space-y-4
|
|
8
|
+
* p-6">`. No live Attio re-measurement behind its own chrome this pass (the
|
|
9
|
+
* CDP bridge was unreachable when this was built) — see the recipe's own
|
|
10
|
+
* doc comment for which already-measured crisp values it reuses instead of
|
|
11
|
+
* a new guess.
|
|
12
|
+
*/
|
|
13
|
+
export function ActivityFeed({ className, children, ...rest }) {
|
|
14
|
+
return (_jsx("div", { className: activityfeedRecipe({ className }), ...rest, children: children }));
|
|
15
|
+
}
|
|
16
|
+
ActivityFeed.displayName = "ActivityFeed";
|
|
17
|
+
/** One event row: actor + action + timestamp, with an optional diff list
|
|
18
|
+
* underneath. The last row in an `ActivityFeed` draws no divider. */
|
|
19
|
+
export function ActivityFeedRow({ actor, action, when, children, }) {
|
|
20
|
+
return (_jsxs("div", { className: "crisp-activityfeed-row", children: [_jsxs("div", { className: "crisp-activityfeed-row-head", children: [actor, action, _jsx("span", { className: "crisp-activityfeed-row-spacer" }), _jsx("span", { className: "crisp-activityfeed-row-when", children: when })] }), children] }));
|
|
21
|
+
}
|
|
22
|
+
ActivityFeedRow.displayName = "ActivityFeedRow";
|
|
23
|
+
/** One changed-field line under an `ActivityFeedRow` — "Field: before →
|
|
24
|
+
* after", or "Field: set to after" when there is no known prior value. */
|
|
25
|
+
export function ActivityFeedDiffItem({ field, before, after, }) {
|
|
26
|
+
return (_jsxs("li", { className: "crisp-activityfeed-diff-item", children: [_jsx("span", { className: "crisp-activityfeed-diff-field", children: field }), " ", before !== undefined ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "crisp-activityfeed-diff-before", children: before }), " ", _jsx("span", { "aria-hidden": "true", children: "\u2192" }), " ", _jsx("span", { children: after })] })) : (_jsx("span", { children: after }))] }));
|
|
27
|
+
}
|
|
28
|
+
ActivityFeedDiffItem.displayName = "ActivityFeedDiffItem";
|
|
29
|
+
export function ActivityFeedDiffList({ children, }) {
|
|
30
|
+
return _jsx("ul", { className: "crisp-activityfeed-diff", children: children });
|
|
31
|
+
}
|
|
32
|
+
ActivityFeedDiffList.displayName = "ActivityFeedDiffList";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
// A record's own event timeline (crisp#462 gap: internal/grc's ActivityTab
|
|
4
|
+
// hand-rolled this as a bare `<div className="mx-auto max-w-3xl space-y-4
|
|
5
|
+
// p-6">` list of bordered rows). No live Attio re-measurement behind these
|
|
6
|
+
// values this pass -- the CDP bridge to the owner's Vivaldi was unreachable
|
|
7
|
+
// when this was built (documented in the component's own doc comment) --
|
|
8
|
+
// so this reuses already-measured crisp values throughout rather than a new
|
|
9
|
+
// guess: `space["6"]` (24px) padding is RecordPanel's own content padding,
|
|
10
|
+
// the row's bottom border reuses `borderSubtle` (the same divider every
|
|
11
|
+
// other bordered row in this codebase draws with), and the 767px content
|
|
12
|
+
// cap matches SettingsSection's own measured content width.
|
|
13
|
+
const config = defineRecipe({
|
|
14
|
+
name: "activityfeed",
|
|
15
|
+
base: `box-sizing: border-box; max-width: 767px; margin-inline: auto; padding: ${c.space["6"]};
|
|
16
|
+
display: flex; flex-direction: column; gap: ${c.space["4"]};
|
|
17
|
+
& .crisp-activityfeed-row { display: flex; flex-direction: column; gap: ${c.space["2"]}; padding-bottom: ${c.space["4"]}; border-bottom: 1px solid ${c.borderSubtle}; }
|
|
18
|
+
& .crisp-activityfeed-row:last-of-type { border-bottom: none; padding-bottom: 0; }
|
|
19
|
+
& .crisp-activityfeed-row-head { display: flex; align-items: center; gap: ${c.space["2"]}; }
|
|
20
|
+
& .crisp-activityfeed-row-spacer { flex: 1; }
|
|
21
|
+
& .crisp-activityfeed-row-when { font-size: ${c.text.xs}; color: ${c.fgMuted}; white-space: nowrap; }
|
|
22
|
+
& .crisp-activityfeed-diff { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: ${c.space["1"]}; }
|
|
23
|
+
& .crisp-activityfeed-diff-item { padding-inline-start: ${c.space["8"]}; font-size: ${c.text.md}; }
|
|
24
|
+
& .crisp-activityfeed-diff-field { color: ${c.fgMuted}; }
|
|
25
|
+
& .crisp-activityfeed-diff-before { color: ${c.fgMuted}; text-decoration: line-through; }
|
|
26
|
+
`,
|
|
27
|
+
variants: {},
|
|
28
|
+
defaultVariants: {},
|
|
29
|
+
compoundVariants: [],
|
|
30
|
+
});
|
|
31
|
+
export const activityfeedRecipe = resolveRecipe(config);
|
|
32
|
+
export default config;
|
|
@@ -147,6 +147,14 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
147
147
|
// (documented React behaviour), so a plain onFocus/onBlur pair on this
|
|
148
148
|
// same wrapper sees the portal's contents correctly.
|
|
149
149
|
const mentionPickerHasFocusRef = React.useRef(false);
|
|
150
|
+
// A caret position `insertMention` just established, to be re-asserted
|
|
151
|
+
// the next time `body` regains focus (#514 fixed the picker stealing
|
|
152
|
+
// focus on OPEN; this is the same "caret resets to offset 0" mechanism
|
|
153
|
+
// on SELECT — see insertMention's own comment for why one synchronous
|
|
154
|
+
// `sel.addRange` isn't enough). One-shot: consumed and cleared by the
|
|
155
|
+
// very next focus or input, whichever comes first, so a stale pointer
|
|
156
|
+
// can never later hijack an unrelated focus.
|
|
157
|
+
const pendingCaretRef = React.useRef(null);
|
|
150
158
|
const syncEmpty = (el) => {
|
|
151
159
|
if (el)
|
|
152
160
|
el.toggleAttribute("data-empty", (el.textContent ?? "") === "");
|
|
@@ -458,6 +466,20 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
458
466
|
after.collapse(true);
|
|
459
467
|
sel.removeAllRanges();
|
|
460
468
|
sel.addRange(after);
|
|
469
|
+
// `select`ing a mention is usually the LAST thing the consumer's
|
|
470
|
+
// picker does before it unmounts (ActorPicker-shaped consumers call
|
|
471
|
+
// `close` right after `select`, same as this file's own doc example).
|
|
472
|
+
// A picker built on a dialog primitive (crisp's own `Command`
|
|
473
|
+
// included) commonly restores focus to whatever had it before the
|
|
474
|
+
// dialog opened as part of ITS OWN unmount cleanup — a plain
|
|
475
|
+
// `.focus()` with no Range of its own, which collapses a
|
|
476
|
+
// contentEditable's caret to offset 0. That cleanup runs after this
|
|
477
|
+
// function returns (the picker only unmounts once `closeMention`
|
|
478
|
+
// below flips `mention` to null and React commits it), so the
|
|
479
|
+
// Selection set two lines up can already be correct here and still
|
|
480
|
+
// get clobbered a tick later. Re-assert it the next time `body`
|
|
481
|
+
// actually regains focus, whenever that turns out to be.
|
|
482
|
+
pendingCaretRef.current = { node: space, offset: 1 };
|
|
461
483
|
closeMention();
|
|
462
484
|
syncEmpty(bodyRef.current);
|
|
463
485
|
emit();
|
|
@@ -522,10 +544,31 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
522
544
|
} })), _jsx("div", { ref: bodyRef, className: "crisp-rte-body", contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", tabIndex: readOnly ? -1 : 0, "aria-multiline": "true", "aria-label": bodyLabel, "aria-readonly": readOnly || undefined, "data-placeholder": placeholder, onInput: readOnly
|
|
523
545
|
? undefined
|
|
524
546
|
: () => {
|
|
547
|
+
// A real keystroke landing here means `body` already has
|
|
548
|
+
// focus on its own terms — any caret a prior mention
|
|
549
|
+
// selection was waiting to reassert (see insertMention) is
|
|
550
|
+
// moot now and must not later jump the caret backward on
|
|
551
|
+
// some unrelated future focus.
|
|
552
|
+
pendingCaretRef.current = null;
|
|
525
553
|
syncEmpty(bodyRef.current);
|
|
526
554
|
updateSlashTrigger();
|
|
527
555
|
updateMention();
|
|
528
556
|
emit();
|
|
557
|
+
}, onFocus: readOnly
|
|
558
|
+
? undefined
|
|
559
|
+
: () => {
|
|
560
|
+
const pending = pendingCaretRef.current;
|
|
561
|
+
pendingCaretRef.current = null;
|
|
562
|
+
if (!pending?.node.isConnected)
|
|
563
|
+
return;
|
|
564
|
+
const sel = window.getSelection();
|
|
565
|
+
if (!sel)
|
|
566
|
+
return;
|
|
567
|
+
const range = document.createRange();
|
|
568
|
+
range.setStart(pending.node, pending.offset);
|
|
569
|
+
range.collapse(true);
|
|
570
|
+
sel.removeAllRanges();
|
|
571
|
+
sel.addRange(range);
|
|
529
572
|
}, onMouseUp: readOnly
|
|
530
573
|
? undefined
|
|
531
574
|
: () => {
|
|
@@ -97,4 +97,18 @@ export const palette = {
|
|
|
97
97
|
// as-is per build/spec-check.mjs's own note), this token exists only for
|
|
98
98
|
// Badge's soft-neutral text, so darkening it doesn't move any other surface.
|
|
99
99
|
softNeutralText: { light: "#686870", dark: "#9a9aa2" },
|
|
100
|
+
// Badge's purple/role-pill tone (crisp#506) -- measured live off Attio's
|
|
101
|
+
// own role pill ("Admin", app.attio.com/<ws>/settings/workspace/members):
|
|
102
|
+
// background rgb(245,240,255), text rgb(98,56,181). The ring re-uses a
|
|
103
|
+
// second, independent measurement from the same session (a company's
|
|
104
|
+
// Categories tag pill sharing the identical background, "Broadcasting",
|
|
105
|
+
// app.attio.com/<ws>/companies) at rgb(232,221,254) -- same hue family,
|
|
106
|
+
// cross-validated rather than guessed. Dark mode was NOT measured (no
|
|
107
|
+
// live Attio session was reachable when this tone was added) -- these
|
|
108
|
+
// three light values are reused as-is for dark rather than an invented
|
|
109
|
+
// "brightened" guess (Lesson: don't add unmeasured "robustness"). Needs a
|
|
110
|
+
// real dark-mode measurement before this note can be removed.
|
|
111
|
+
purpleTint: { light: "#f5f0ff", dark: "#f5f0ff" },
|
|
112
|
+
softPurpleText: { light: "#6238b5", dark: "#6238b5" },
|
|
113
|
+
purpleRing: { light: "#e8ddfe", dark: "#e8ddfe" },
|
|
100
114
|
};
|
|
@@ -60,4 +60,9 @@ export const semantic = {
|
|
|
60
60
|
fgWarningSoft: "softWarningText",
|
|
61
61
|
fgDangerSoft: "softDangerText",
|
|
62
62
|
fgNeutralSoft: "softNeutralText",
|
|
63
|
+
/** Badge's purple/role-pill tone (crisp#506) — see palette.ts's comment
|
|
64
|
+
* on `purpleTint`/`softPurpleText`/`purpleRing` for the measurement. */
|
|
65
|
+
bgPurpleSoft: "purpleTint",
|
|
66
|
+
fgPurpleSoft: "softPurpleText",
|
|
67
|
+
ringPurpleSoft: "purpleRing",
|
|
63
68
|
};
|
package/dist/native/tokens.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// The same tokens the CSS custom properties carry, resolved per theme, with
|
|
3
3
|
// dimensions as numbers for React Native and Lynx style objects.
|
|
4
4
|
export type Theme = "light" | "dark";
|
|
5
|
-
export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "borderDefault" | "borderSubtle" | "ringNeutral" | "ringField" | "ringSwitch" | "bgTileActive" | "shadowTileActive" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft", string>;
|
|
5
|
+
export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "borderDefault" | "borderSubtle" | "ringNeutral" | "ringField" | "ringSwitch" | "bgTileActive" | "shadowTileActive" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft" | "bgPurpleSoft" | "fgPurpleSoft" | "ringPurpleSoft", string>;
|
|
6
6
|
export declare const tokens: {
|
|
7
7
|
color: Record<Theme, ColorTokens>;
|
|
8
8
|
palette: Record<Theme, Record<string, string>>;
|
package/dist/native/tokens.js
CHANGED
|
@@ -49,7 +49,10 @@ export const tokens = {
|
|
|
49
49
|
"fgSuccessSoft": "#186b3d",
|
|
50
50
|
"fgWarningSoft": "#80520d",
|
|
51
51
|
"fgDangerSoft": "#a33539",
|
|
52
|
-
"fgNeutralSoft": "#686870"
|
|
52
|
+
"fgNeutralSoft": "#686870",
|
|
53
|
+
"bgPurpleSoft": "#f5f0ff",
|
|
54
|
+
"fgPurpleSoft": "#6238b5",
|
|
55
|
+
"ringPurpleSoft": "#e8ddfe"
|
|
53
56
|
},
|
|
54
57
|
"dark": {
|
|
55
58
|
"bgBrandSolid": "#266df0",
|
|
@@ -97,7 +100,10 @@ export const tokens = {
|
|
|
97
100
|
"fgSuccessSoft": "#2ea968",
|
|
98
101
|
"fgWarningSoft": "#e0982a",
|
|
99
102
|
"fgDangerSoft": "#ff5a5f",
|
|
100
|
-
"fgNeutralSoft": "#9a9aa2"
|
|
103
|
+
"fgNeutralSoft": "#9a9aa2",
|
|
104
|
+
"bgPurpleSoft": "#f5f0ff",
|
|
105
|
+
"fgPurpleSoft": "#6238b5",
|
|
106
|
+
"ringPurpleSoft": "#e8ddfe"
|
|
101
107
|
}
|
|
102
108
|
},
|
|
103
109
|
"palette": {
|
|
@@ -146,7 +152,10 @@ export const tokens = {
|
|
|
146
152
|
"softSuccessText": "#186b3d",
|
|
147
153
|
"softWarningText": "#80520d",
|
|
148
154
|
"softDangerText": "#a33539",
|
|
149
|
-
"softNeutralText": "#686870"
|
|
155
|
+
"softNeutralText": "#686870",
|
|
156
|
+
"purpleTint": "#f5f0ff",
|
|
157
|
+
"softPurpleText": "#6238b5",
|
|
158
|
+
"purpleRing": "#e8ddfe"
|
|
150
159
|
},
|
|
151
160
|
"dark": {
|
|
152
161
|
"gray00": "#0a0a0a",
|
|
@@ -193,7 +202,10 @@ export const tokens = {
|
|
|
193
202
|
"softSuccessText": "#2ea968",
|
|
194
203
|
"softWarningText": "#e0982a",
|
|
195
204
|
"softDangerText": "#ff5a5f",
|
|
196
|
-
"softNeutralText": "#9a9aa2"
|
|
205
|
+
"softNeutralText": "#9a9aa2",
|
|
206
|
+
"purpleTint": "#f5f0ff",
|
|
207
|
+
"softPurpleText": "#6238b5",
|
|
208
|
+
"purpleRing": "#e8ddfe"
|
|
197
209
|
}
|
|
198
210
|
},
|
|
199
211
|
"space": {
|
package/dist/styles.css
CHANGED
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
--crisp-fg-warning-soft: #80520d;
|
|
46
46
|
--crisp-fg-danger-soft: #a33539;
|
|
47
47
|
--crisp-fg-neutral-soft: #686870;
|
|
48
|
+
--crisp-bg-purple-soft: #f5f0ff;
|
|
49
|
+
--crisp-fg-purple-soft: #6238b5;
|
|
50
|
+
--crisp-ring-purple-soft: #e8ddfe;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
[data-theme="dark"] {
|
|
@@ -94,6 +97,9 @@
|
|
|
94
97
|
--crisp-fg-warning-soft: #e0982a;
|
|
95
98
|
--crisp-fg-danger-soft: #ff5a5f;
|
|
96
99
|
--crisp-fg-neutral-soft: #9a9aa2;
|
|
100
|
+
--crisp-bg-purple-soft: #f5f0ff;
|
|
101
|
+
--crisp-fg-purple-soft: #6238b5;
|
|
102
|
+
--crisp-ring-purple-soft: #e8ddfe;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
:root {
|
|
@@ -224,6 +230,9 @@
|
|
|
224
230
|
.crisp-badge--tone_danger {
|
|
225
231
|
background: color-mix(in srgb, var(--crisp-bg-danger) 14%, transparent); color: var(--crisp-fg-danger-soft); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent);
|
|
226
232
|
}
|
|
233
|
+
.crisp-badge--tone_purple {
|
|
234
|
+
background: var(--crisp-bg-purple-soft); color: var(--crisp-fg-purple-soft); box-shadow: inset 0 0 0 1px var(--crisp-ring-purple-soft);
|
|
235
|
+
}
|
|
227
236
|
.crisp-badge--size_small {
|
|
228
237
|
height: 16px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-xs); border-radius: var(--crisp-radius-sm);
|
|
229
238
|
}
|
|
@@ -1869,6 +1878,19 @@
|
|
|
1869
1878
|
& .crisp-wheelpicker-item { height: 44px; min-height: 44px; font-size: 26px; line-height: 35px; }
|
|
1870
1879
|
}
|
|
1871
1880
|
|
|
1881
|
+
.crisp-activityfeed {
|
|
1882
|
+
box-sizing: border-box; max-width: 767px; margin-inline: auto; padding: var(--crisp-space-6); display: flex; flex-direction: column; gap: var(--crisp-space-4);
|
|
1883
|
+
& .crisp-activityfeed-row { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding-bottom: var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
|
|
1884
|
+
& .crisp-activityfeed-row:last-of-type { border-bottom: none; padding-bottom: 0; }
|
|
1885
|
+
& .crisp-activityfeed-row-head { display: flex; align-items: center; gap: var(--crisp-space-2); }
|
|
1886
|
+
& .crisp-activityfeed-row-spacer { flex: 1; }
|
|
1887
|
+
& .crisp-activityfeed-row-when { font-size: var(--crisp-text-xs); color: var(--crisp-fg-muted); white-space: nowrap; }
|
|
1888
|
+
& .crisp-activityfeed-diff { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--crisp-space-1); }
|
|
1889
|
+
& .crisp-activityfeed-diff-item { padding-inline-start: var(--crisp-space-8); font-size: var(--crisp-text-md); }
|
|
1890
|
+
& .crisp-activityfeed-diff-field { color: var(--crisp-fg-muted); }
|
|
1891
|
+
& .crisp-activityfeed-diff-before { color: var(--crisp-fg-muted); text-decoration: line-through; }
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1872
1894
|
.crisp-appshell {
|
|
1873
1895
|
display: flex; height: 100%; width: 100%; overflow: hidden; background: var(--crisp-bg-surface); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
1874
1896
|
& .crisp-appshell-side { position: relative; flex-shrink: 0; overflow: hidden; }
|
package/dist/styles.layered.css
CHANGED
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
--crisp-fg-warning-soft: #80520d;
|
|
46
46
|
--crisp-fg-danger-soft: #a33539;
|
|
47
47
|
--crisp-fg-neutral-soft: #686870;
|
|
48
|
+
--crisp-bg-purple-soft: #f5f0ff;
|
|
49
|
+
--crisp-fg-purple-soft: #6238b5;
|
|
50
|
+
--crisp-ring-purple-soft: #e8ddfe;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
[data-theme="dark"] {
|
|
@@ -94,6 +97,9 @@
|
|
|
94
97
|
--crisp-fg-warning-soft: #e0982a;
|
|
95
98
|
--crisp-fg-danger-soft: #ff5a5f;
|
|
96
99
|
--crisp-fg-neutral-soft: #9a9aa2;
|
|
100
|
+
--crisp-bg-purple-soft: #f5f0ff;
|
|
101
|
+
--crisp-fg-purple-soft: #6238b5;
|
|
102
|
+
--crisp-ring-purple-soft: #e8ddfe;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
:root {
|
|
@@ -225,6 +231,9 @@
|
|
|
225
231
|
.crisp-badge--tone_danger {
|
|
226
232
|
background: color-mix(in srgb, var(--crisp-bg-danger) 14%, transparent); color: var(--crisp-fg-danger-soft); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent);
|
|
227
233
|
}
|
|
234
|
+
.crisp-badge--tone_purple {
|
|
235
|
+
background: var(--crisp-bg-purple-soft); color: var(--crisp-fg-purple-soft); box-shadow: inset 0 0 0 1px var(--crisp-ring-purple-soft);
|
|
236
|
+
}
|
|
228
237
|
.crisp-badge--size_small {
|
|
229
238
|
height: 16px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-xs); border-radius: var(--crisp-radius-sm);
|
|
230
239
|
}
|
|
@@ -1870,6 +1879,19 @@
|
|
|
1870
1879
|
& .crisp-wheelpicker-item { height: 44px; min-height: 44px; font-size: 26px; line-height: 35px; }
|
|
1871
1880
|
}
|
|
1872
1881
|
|
|
1882
|
+
.crisp-activityfeed {
|
|
1883
|
+
box-sizing: border-box; max-width: 767px; margin-inline: auto; padding: var(--crisp-space-6); display: flex; flex-direction: column; gap: var(--crisp-space-4);
|
|
1884
|
+
& .crisp-activityfeed-row { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding-bottom: var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
|
|
1885
|
+
& .crisp-activityfeed-row:last-of-type { border-bottom: none; padding-bottom: 0; }
|
|
1886
|
+
& .crisp-activityfeed-row-head { display: flex; align-items: center; gap: var(--crisp-space-2); }
|
|
1887
|
+
& .crisp-activityfeed-row-spacer { flex: 1; }
|
|
1888
|
+
& .crisp-activityfeed-row-when { font-size: var(--crisp-text-xs); color: var(--crisp-fg-muted); white-space: nowrap; }
|
|
1889
|
+
& .crisp-activityfeed-diff { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--crisp-space-1); }
|
|
1890
|
+
& .crisp-activityfeed-diff-item { padding-inline-start: var(--crisp-space-8); font-size: var(--crisp-text-md); }
|
|
1891
|
+
& .crisp-activityfeed-diff-field { color: var(--crisp-fg-muted); }
|
|
1892
|
+
& .crisp-activityfeed-diff-before { color: var(--crisp-fg-muted); text-decoration: line-through; }
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1873
1895
|
.crisp-appshell {
|
|
1874
1896
|
display: flex; height: 100%; width: 100%; overflow: hidden; background: var(--crisp-bg-surface); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
1875
1897
|
& .crisp-appshell-side { position: relative; flex-shrink: 0; overflow: hidden; }
|
package/dist/tokens.css
CHANGED
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
--crisp-fg-warning-soft: #80520d;
|
|
46
46
|
--crisp-fg-danger-soft: #a33539;
|
|
47
47
|
--crisp-fg-neutral-soft: #686870;
|
|
48
|
+
--crisp-bg-purple-soft: #f5f0ff;
|
|
49
|
+
--crisp-fg-purple-soft: #6238b5;
|
|
50
|
+
--crisp-ring-purple-soft: #e8ddfe;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
[data-theme="dark"] {
|
|
@@ -94,6 +97,9 @@
|
|
|
94
97
|
--crisp-fg-warning-soft: #e0982a;
|
|
95
98
|
--crisp-fg-danger-soft: #ff5a5f;
|
|
96
99
|
--crisp-fg-neutral-soft: #9a9aa2;
|
|
100
|
+
--crisp-bg-purple-soft: #f5f0ff;
|
|
101
|
+
--crisp-fg-purple-soft: #6238b5;
|
|
102
|
+
--crisp-ring-purple-soft: #e8ddfe;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
:root {
|
package/dist/tokens.json
CHANGED
|
@@ -46,7 +46,10 @@
|
|
|
46
46
|
"fgSuccessSoft": "#186b3d",
|
|
47
47
|
"fgWarningSoft": "#80520d",
|
|
48
48
|
"fgDangerSoft": "#a33539",
|
|
49
|
-
"fgNeutralSoft": "#686870"
|
|
49
|
+
"fgNeutralSoft": "#686870",
|
|
50
|
+
"bgPurpleSoft": "#f5f0ff",
|
|
51
|
+
"fgPurpleSoft": "#6238b5",
|
|
52
|
+
"ringPurpleSoft": "#e8ddfe"
|
|
50
53
|
},
|
|
51
54
|
"dark": {
|
|
52
55
|
"bgBrandSolid": "#266df0",
|
|
@@ -94,7 +97,10 @@
|
|
|
94
97
|
"fgSuccessSoft": "#2ea968",
|
|
95
98
|
"fgWarningSoft": "#e0982a",
|
|
96
99
|
"fgDangerSoft": "#ff5a5f",
|
|
97
|
-
"fgNeutralSoft": "#9a9aa2"
|
|
100
|
+
"fgNeutralSoft": "#9a9aa2",
|
|
101
|
+
"bgPurpleSoft": "#f5f0ff",
|
|
102
|
+
"fgPurpleSoft": "#6238b5",
|
|
103
|
+
"ringPurpleSoft": "#e8ddfe"
|
|
98
104
|
}
|
|
99
105
|
},
|
|
100
106
|
"palette": {
|
|
@@ -143,7 +149,10 @@
|
|
|
143
149
|
"softSuccessText": "#186b3d",
|
|
144
150
|
"softWarningText": "#80520d",
|
|
145
151
|
"softDangerText": "#a33539",
|
|
146
|
-
"softNeutralText": "#686870"
|
|
152
|
+
"softNeutralText": "#686870",
|
|
153
|
+
"purpleTint": "#f5f0ff",
|
|
154
|
+
"softPurpleText": "#6238b5",
|
|
155
|
+
"purpleRing": "#e8ddfe"
|
|
147
156
|
},
|
|
148
157
|
"dark": {
|
|
149
158
|
"gray00": "#0a0a0a",
|
|
@@ -190,7 +199,10 @@
|
|
|
190
199
|
"softSuccessText": "#2ea968",
|
|
191
200
|
"softWarningText": "#e0982a",
|
|
192
201
|
"softDangerText": "#ff5a5f",
|
|
193
|
-
"softNeutralText": "#9a9aa2"
|
|
202
|
+
"softNeutralText": "#9a9aa2",
|
|
203
|
+
"purpleTint": "#f5f0ff",
|
|
204
|
+
"softPurpleText": "#6238b5",
|
|
205
|
+
"purpleRing": "#e8ddfe"
|
|
194
206
|
}
|
|
195
207
|
},
|
|
196
208
|
"space": {
|
|
@@ -13,6 +13,7 @@ export declare const Badge: React.ForwardRefExoticComponent<Omit<React.DetailedH
|
|
|
13
13
|
readonly success: string;
|
|
14
14
|
readonly warning: string;
|
|
15
15
|
readonly danger: string;
|
|
16
|
+
readonly purple: string;
|
|
16
17
|
};
|
|
17
18
|
readonly size: {
|
|
18
19
|
readonly small: string;
|
|
@@ -31,6 +32,7 @@ export declare const Badge: React.ForwardRefExoticComponent<Omit<React.DetailedH
|
|
|
31
32
|
readonly success: string;
|
|
32
33
|
readonly warning: string;
|
|
33
34
|
readonly danger: string;
|
|
35
|
+
readonly purple: string;
|
|
34
36
|
};
|
|
35
37
|
readonly size: {
|
|
36
38
|
readonly small: string;
|
|
@@ -48,6 +48,11 @@ declare const spec: {
|
|
|
48
48
|
color: `var(--${string})`;
|
|
49
49
|
boxShadow: string;
|
|
50
50
|
};
|
|
51
|
+
readonly purple: {
|
|
52
|
+
readonly background: `var(--${string})`;
|
|
53
|
+
readonly color: `var(--${string})`;
|
|
54
|
+
readonly boxShadow: `inset 0 0 0 1px var(--${string})`;
|
|
55
|
+
};
|
|
51
56
|
};
|
|
52
57
|
readonly size: {
|
|
53
58
|
readonly small: {
|
|
@@ -128,6 +133,7 @@ declare const config: import("../..").RecipeConfig<{
|
|
|
128
133
|
readonly success: string;
|
|
129
134
|
readonly warning: string;
|
|
130
135
|
readonly danger: string;
|
|
136
|
+
readonly purple: string;
|
|
131
137
|
};
|
|
132
138
|
readonly size: {
|
|
133
139
|
readonly small: string;
|
|
@@ -146,6 +152,7 @@ export declare const badgeRecipe: import("../..").RecipeFn<{
|
|
|
146
152
|
readonly success: string;
|
|
147
153
|
readonly warning: string;
|
|
148
154
|
readonly danger: string;
|
|
155
|
+
readonly purple: string;
|
|
149
156
|
};
|
|
150
157
|
readonly size: {
|
|
151
158
|
readonly small: string;
|
|
@@ -164,6 +171,7 @@ export declare const badgeRecipe: import("../..").RecipeFn<{
|
|
|
164
171
|
readonly success: string;
|
|
165
172
|
readonly warning: string;
|
|
166
173
|
readonly danger: string;
|
|
174
|
+
readonly purple: string;
|
|
167
175
|
};
|
|
168
176
|
readonly size: {
|
|
169
177
|
readonly small: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export * from "./components/tooltip";
|
|
|
57
57
|
export * from "./components/visuallyhidden";
|
|
58
58
|
export * from "./components/wheelpicker";
|
|
59
59
|
export * from "./core";
|
|
60
|
+
export * from "./product/activityfeed";
|
|
60
61
|
export * from "./product/appshell";
|
|
61
62
|
export * from "./product/authshell";
|
|
62
63
|
export * from "./product/board";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
export type ActivityFeedProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
3
|
+
className?: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* A record's own event timeline — a vertically stacked, bordered list of
|
|
7
|
+
* `ActivityFeedRow`s, capped at a readable content width. Built for
|
|
8
|
+
* internal/grc's per-record "Activity" tab (crisp#462 SHELL/forms gap): it
|
|
9
|
+
* replaces a hand-rolled `<div className="mx-auto max-w-3xl space-y-4
|
|
10
|
+
* p-6">`. No live Attio re-measurement behind its own chrome this pass (the
|
|
11
|
+
* CDP bridge was unreachable when this was built) — see the recipe's own
|
|
12
|
+
* doc comment for which already-measured crisp values it reuses instead of
|
|
13
|
+
* a new guess.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ActivityFeed({ className, children, ...rest }: ActivityFeedProps): React.JSX.Element;
|
|
16
|
+
export declare namespace ActivityFeed {
|
|
17
|
+
var displayName: string;
|
|
18
|
+
}
|
|
19
|
+
export type ActivityFeedRowProps = {
|
|
20
|
+
/** Who did it — a chip, an avatar+name, whatever the consumer's identity
|
|
21
|
+
* treatment is. crisp renders no actor chip of its own. */
|
|
22
|
+
actor: React.ReactNode;
|
|
23
|
+
/** What happened — a tag/badge naming the action. */
|
|
24
|
+
action: React.ReactNode;
|
|
25
|
+
/** When — already formatted for display. */
|
|
26
|
+
when: React.ReactNode;
|
|
27
|
+
/** An optional field-level diff list under the row (see
|
|
28
|
+
* `ActivityFeedDiffItem`). */
|
|
29
|
+
children?: React.ReactNode;
|
|
30
|
+
};
|
|
31
|
+
/** One event row: actor + action + timestamp, with an optional diff list
|
|
32
|
+
* underneath. The last row in an `ActivityFeed` draws no divider. */
|
|
33
|
+
export declare function ActivityFeedRow({ actor, action, when, children, }: ActivityFeedRowProps): React.JSX.Element;
|
|
34
|
+
export declare namespace ActivityFeedRow {
|
|
35
|
+
var displayName: string;
|
|
36
|
+
}
|
|
37
|
+
export type ActivityFeedDiffItemProps = {
|
|
38
|
+
/** The changed field's label. */
|
|
39
|
+
field: React.ReactNode;
|
|
40
|
+
/** The prior value, already formatted — omit for a "set to" (no known
|
|
41
|
+
* prior value) row. */
|
|
42
|
+
before?: React.ReactNode;
|
|
43
|
+
/** The new value, already formatted. */
|
|
44
|
+
after: React.ReactNode;
|
|
45
|
+
};
|
|
46
|
+
/** One changed-field line under an `ActivityFeedRow` — "Field: before →
|
|
47
|
+
* after", or "Field: set to after" when there is no known prior value. */
|
|
48
|
+
export declare function ActivityFeedDiffItem({ field, before, after, }: ActivityFeedDiffItemProps): React.JSX.Element;
|
|
49
|
+
export declare namespace ActivityFeedDiffItem {
|
|
50
|
+
var displayName: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function ActivityFeedDiffList({ children, }: {
|
|
53
|
+
children: React.ReactNode;
|
|
54
|
+
}): React.JSX.Element;
|
|
55
|
+
export declare namespace ActivityFeedDiffList {
|
|
56
|
+
var displayName: string;
|
|
57
|
+
}
|
|
@@ -47,6 +47,9 @@ export declare const contract: MapLeafNodes<{
|
|
|
47
47
|
fgWarningSoft: "softWarningText";
|
|
48
48
|
fgDangerSoft: "softDangerText";
|
|
49
49
|
fgNeutralSoft: "softNeutralText";
|
|
50
|
+
bgPurpleSoft: "purpleTint";
|
|
51
|
+
fgPurpleSoft: "softPurpleText";
|
|
52
|
+
ringPurpleSoft: "purpleRing";
|
|
50
53
|
}, `var(--${string})`> & {
|
|
51
54
|
space: Record<"0.5" | "1" | "1.5" | "2" | "2.5" | "3" | "4" | "5" | "6" | "8" | "px", `var(--${string})`>;
|
|
52
55
|
radius: Record<"2xl" | "chip" | "full" | "lg" | "md" | "sm" | "xl", `var(--${string})`>;
|
|
@@ -179,5 +179,17 @@ export declare const palette: {
|
|
|
179
179
|
readonly light: "#686870";
|
|
180
180
|
readonly dark: "#9a9aa2";
|
|
181
181
|
};
|
|
182
|
+
readonly purpleTint: {
|
|
183
|
+
readonly light: "#f5f0ff";
|
|
184
|
+
readonly dark: "#f5f0ff";
|
|
185
|
+
};
|
|
186
|
+
readonly softPurpleText: {
|
|
187
|
+
readonly light: "#6238b5";
|
|
188
|
+
readonly dark: "#6238b5";
|
|
189
|
+
};
|
|
190
|
+
readonly purpleRing: {
|
|
191
|
+
readonly light: "#e8ddfe";
|
|
192
|
+
readonly dark: "#e8ddfe";
|
|
193
|
+
};
|
|
182
194
|
};
|
|
183
195
|
export type PaletteKey = keyof typeof palette;
|
|
@@ -55,5 +55,10 @@ export declare const semantic: {
|
|
|
55
55
|
fgWarningSoft: "softWarningText";
|
|
56
56
|
fgDangerSoft: "softDangerText";
|
|
57
57
|
fgNeutralSoft: "softNeutralText";
|
|
58
|
+
/** Badge's purple/role-pill tone (crisp#506) — see palette.ts's comment
|
|
59
|
+
* on `purpleTint`/`softPurpleText`/`purpleRing` for the measurement. */
|
|
60
|
+
bgPurpleSoft: "purpleTint";
|
|
61
|
+
fgPurpleSoft: "softPurpleText";
|
|
62
|
+
ringPurpleSoft: "purpleRing";
|
|
58
63
|
};
|
|
59
64
|
export type Tokens = typeof semantic;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crisp-ui-kit/crisp",
|
|
3
|
-
"version": "0.54.
|
|
3
|
+
"version": "0.54.1",
|
|
4
4
|
"description": "A lean React design system, measured pixel-for-pixel from Attio.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -323,6 +323,11 @@
|
|
|
323
323
|
"import": "./dist/esm/components/wheelpicker/index.js",
|
|
324
324
|
"require": "./dist/cjs/components/wheelpicker/index.js"
|
|
325
325
|
},
|
|
326
|
+
"./activityfeed": {
|
|
327
|
+
"types": "./dist/types/product/activityfeed/index.d.ts",
|
|
328
|
+
"import": "./dist/esm/product/activityfeed/index.js",
|
|
329
|
+
"require": "./dist/cjs/product/activityfeed/index.js"
|
|
330
|
+
},
|
|
326
331
|
"./appshell": {
|
|
327
332
|
"types": "./dist/types/product/appshell/index.d.ts",
|
|
328
333
|
"import": "./dist/esm/product/appshell/index.js",
|