@crisp-ui-kit/crisp 0.56.0 → 0.57.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/accentpicker.css +5 -0
- package/dist/accentpicker.d.ts +7 -0
- package/dist/accentpicker.layered.css +7 -0
- package/dist/avatar.css +1 -1
- package/dist/avatar.layered.css +1 -1
- package/dist/badge.css +2 -2
- package/dist/badge.layered.css +2 -2
- package/dist/button.css +3 -3
- package/dist/button.layered.css +3 -3
- package/dist/callout.css +6 -0
- package/dist/callout.d.ts +2 -2
- package/dist/callout.layered.css +6 -0
- package/dist/cjs/components/accentpicker/accentpicker.js +66 -0
- package/dist/cjs/components/accentpicker/accentpicker.recipe.js +25 -0
- package/dist/cjs/components/accentpicker/index.js +7 -0
- package/dist/cjs/components/avatar/avatar.recipe.js +4 -1
- package/dist/cjs/components/badge/badge.recipe.js +16 -2
- package/dist/cjs/components/button/button.recipe.js +16 -6
- package/dist/cjs/components/callout/callout.js +2 -2
- package/dist/cjs/components/callout/callout.recipe.js +15 -2
- package/dist/cjs/components/input/input.js +2 -2
- package/dist/cjs/components/input/input.recipe.js +14 -0
- package/dist/cjs/components/menu/menu.js +2 -2
- package/dist/cjs/components/menu/menu.recipe.js +8 -0
- package/dist/cjs/components/multiselect/multiselect.js +23 -10
- package/dist/cjs/components/multiselect/multiselect.recipe.js +104 -65
- package/dist/cjs/components/progress/progress.js +21 -4
- package/dist/cjs/components/progress/progress.recipe.js +30 -0
- package/dist/cjs/components/select/listbox-keyboard.js +7 -1
- package/dist/cjs/components/select/select.js +24 -11
- package/dist/cjs/components/select/select.recipe.js +177 -78
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +3 -3
- package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +26 -1
- package/dist/cjs/components/tabs/tabs.js +63 -4
- package/dist/cjs/components/tabs/tabs.recipe.js +13 -0
- package/dist/cjs/components/text/text.recipe.js +8 -7
- package/dist/cjs/components/textarea/textarea.js +3 -2
- package/dist/cjs/components/textarea/textarea.recipe.js +2 -1
- package/dist/cjs/components/textlink/textlink.js +2 -2
- package/dist/cjs/components/textlink/textlink.recipe.js +24 -3
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/product/recordpage/recordpage.js +61 -7
- package/dist/cjs/tokens/elevation.js +7 -0
- package/dist/cjs/tokens/palette.js +120 -25
- package/dist/cjs/tokens/scale.js +9 -0
- package/dist/cjs/tokens/semantic.js +42 -0
- package/dist/esm/components/accentpicker/accentpicker.js +30 -0
- package/dist/esm/components/accentpicker/accentpicker.recipe.js +22 -0
- package/dist/esm/components/accentpicker/index.js +2 -0
- package/dist/esm/components/avatar/avatar.recipe.js +4 -1
- package/dist/esm/components/badge/badge.recipe.js +16 -2
- package/dist/esm/components/button/button.recipe.js +16 -6
- package/dist/esm/components/callout/callout.js +2 -2
- package/dist/esm/components/callout/callout.recipe.js +15 -2
- package/dist/esm/components/input/input.js +3 -3
- package/dist/esm/components/input/input.recipe.js +14 -0
- package/dist/esm/components/menu/menu.js +2 -2
- package/dist/esm/components/menu/menu.recipe.js +8 -0
- package/dist/esm/components/multiselect/multiselect.js +23 -10
- package/dist/esm/components/multiselect/multiselect.recipe.js +104 -65
- package/dist/esm/components/progress/progress.js +22 -5
- package/dist/esm/components/progress/progress.recipe.js +30 -0
- package/dist/esm/components/select/listbox-keyboard.js +7 -1
- package/dist/esm/components/select/select.js +24 -11
- package/dist/esm/components/select/select.recipe.js +177 -78
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +3 -3
- package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +26 -1
- package/dist/esm/components/tabs/tabs.js +63 -4
- package/dist/esm/components/tabs/tabs.recipe.js +13 -0
- package/dist/esm/components/text/text.recipe.js +8 -7
- package/dist/esm/components/textarea/textarea.js +3 -2
- package/dist/esm/components/textarea/textarea.recipe.js +2 -1
- package/dist/esm/components/textlink/textlink.js +2 -2
- package/dist/esm/components/textlink/textlink.recipe.js +24 -3
- package/dist/esm/index.js +1 -0
- package/dist/esm/product/recordpage/recordpage.js +62 -8
- package/dist/esm/tokens/elevation.js +7 -0
- package/dist/esm/tokens/palette.js +120 -25
- package/dist/esm/tokens/scale.js +9 -0
- package/dist/esm/tokens/semantic.js +42 -0
- package/dist/input.css +11 -0
- package/dist/input.d.ts +2 -2
- package/dist/input.layered.css +11 -0
- package/dist/menu.css +8 -0
- package/dist/menu.layered.css +8 -0
- package/dist/multiselect.css +57 -29
- package/dist/multiselect.d.ts +3 -1
- package/dist/multiselect.layered.css +57 -29
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +82 -28
- package/dist/progress.css +3 -0
- package/dist/progress.layered.css +3 -0
- package/dist/select.css +100 -42
- package/dist/select.d.ts +3 -2
- package/dist/select.layered.css +100 -42
- package/dist/styles.css +268 -93
- package/dist/styles.layered.css +268 -93
- package/dist/tabletoolbar.css +8 -1
- package/dist/tabletoolbar.layered.css +8 -1
- package/dist/tabs.css +6 -0
- package/dist/tabs.layered.css +6 -0
- package/dist/text.css +1 -1
- package/dist/text.layered.css +1 -1
- package/dist/textarea.css +2 -1
- package/dist/textarea.layered.css +2 -1
- package/dist/textlink.css +11 -2
- package/dist/textlink.d.ts +2 -1
- package/dist/textlink.layered.css +11 -2
- package/dist/tokens.css +43 -11
- package/dist/tokens.json +82 -28
- package/dist/types/components/accentpicker/accentpicker.d.ts +23 -0
- package/dist/types/components/accentpicker/accentpicker.recipe.d.ts +3 -0
- package/dist/types/components/accentpicker/index.d.ts +2 -0
- package/dist/types/components/button/button.recipe.d.ts +2 -2
- package/dist/types/components/callout/callout.d.ts +9 -1
- package/dist/types/components/callout/callout.recipe.d.ts +12 -2
- package/dist/types/components/input/input.d.ts +14 -0
- package/dist/types/components/input/input.recipe.d.ts +3 -0
- package/dist/types/components/menu/menu.d.ts +7 -1
- package/dist/types/components/multiselect/multiselect.d.ts +25 -6
- package/dist/types/components/multiselect/multiselect.recipe.d.ts +27 -0
- package/dist/types/components/progress/progress.d.ts +16 -4
- package/dist/types/components/progress/progress.recipe.d.ts +22 -0
- package/dist/types/components/select/listbox-keyboard.d.ts +10 -1
- package/dist/types/components/select/select.d.ts +29 -6
- package/dist/types/components/select/select.recipe.d.ts +25 -10
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +14 -0
- package/dist/types/components/tabs/tabs.d.ts +9 -1
- package/dist/types/components/textarea/textarea.d.ts +16 -0
- package/dist/types/components/textlink/textlink.d.ts +8 -1
- package/dist/types/components/textlink/textlink.recipe.d.ts +10 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/product/recordpage/recordpage.d.ts +61 -1
- package/dist/types/tokens/contract.d.ts +16 -3
- package/dist/types/tokens/elevation.d.ts +7 -0
- package/dist/types/tokens/palette.d.ts +55 -11
- package/dist/types/tokens/scale.d.ts +6 -0
- package/dist/types/tokens/semantic.d.ts +33 -0
- package/package.json +6 -1
|
@@ -21,11 +21,32 @@ const config = (0, core_1.defineRecipe)({
|
|
|
21
21
|
variants: {
|
|
22
22
|
tone: {
|
|
23
23
|
// The bold, always-brand-coloured trailing link a Card's own `action`
|
|
24
|
-
// slot uses ("View all →", "All registers →").
|
|
25
|
-
|
|
24
|
+
// slot uses ("View all →", "All registers →"). your-moon/crisp#897:
|
|
25
|
+
// `fgBrandContrast`, not `bgBrandSolid` (#0298d9) directly -- as TEXT
|
|
26
|
+
// on a white page the accent itself only clears 3.23:1; the darker
|
|
27
|
+
// contrast-safe sibling clears 4.5:1+.
|
|
28
|
+
brand: `font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgBrandContrast};`,
|
|
26
29
|
// A quieter in-context reference (a sample record under a finding)
|
|
27
30
|
// that only goes brand on hover.
|
|
28
|
-
muted: `font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.regular}; color: ${tokens_1.contract.fgMuted}; &:hover { color: ${tokens_1.contract.
|
|
31
|
+
muted: `font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.regular}; color: ${tokens_1.contract.fgMuted}; &:hover { color: ${tokens_1.contract.fgBrandContrast}; }`,
|
|
32
|
+
},
|
|
33
|
+
// crisp#874: an explicit, OPTIONAL size override -- no `defaultVariants`
|
|
34
|
+
// entry below, so omitting `size` emits no `--size_*` class at all and
|
|
35
|
+
// each tone's own hard-coded font-size (above) stands completely
|
|
36
|
+
// unchanged. Only when a caller passes `size` explicitly does this
|
|
37
|
+
// override it: `size`'s CSS block is emitted AFTER `tone`'s in the
|
|
38
|
+
// generated stylesheet (`generate-recipes.mjs` walks `variants` in
|
|
39
|
+
// object-key order), so at equal specificity it wins the cascade.
|
|
40
|
+
// Measured live (a running GRC server, `getComputedStyle`):
|
|
41
|
+
// internal/grc's two primary-column record links
|
|
42
|
+
// (routes/lists/Lists.tsx:51, routes/registers/Registers.tsx:63) are
|
|
43
|
+
// 14px/500 ambient, inherited from DataTable's own
|
|
44
|
+
// `.crisp-datatable-entity-name` -- `size="lg"` matches that context
|
|
45
|
+
// without changing `brand`'s weight/colour.
|
|
46
|
+
size: {
|
|
47
|
+
sm: `font-size: ${tokens_1.contract.text.sm};`,
|
|
48
|
+
md: `font-size: ${tokens_1.contract.text.md};`,
|
|
49
|
+
lg: `font-size: ${tokens_1.contract.text.lg};`,
|
|
29
50
|
},
|
|
30
51
|
},
|
|
31
52
|
defaultVariants: { tone: "muted" },
|
package/dist/cjs/index.js
CHANGED
|
@@ -18,6 +18,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
// primitives (components/)
|
|
21
|
+
__exportStar(require("./components/accentpicker"), exports);
|
|
21
22
|
__exportStar(require("./components/accordion"), exports);
|
|
22
23
|
__exportStar(require("./components/avatar"), exports);
|
|
23
24
|
__exportStar(require("./components/avatargroup"), exports);
|
|
@@ -41,12 +41,14 @@ const lucide_react_1 = require("lucide-react");
|
|
|
41
41
|
const React = __importStar(require("react"));
|
|
42
42
|
const avatar_1 = require("../../components/avatar");
|
|
43
43
|
const checkbox_1 = require("../../components/checkbox");
|
|
44
|
+
const drawer_1 = require("../../components/drawer");
|
|
44
45
|
const menu_1 = require("../../components/menu");
|
|
45
46
|
const select_1 = require("../../components/select");
|
|
46
47
|
const spinner_1 = require("../../components/spinner");
|
|
47
48
|
const switch_1 = require("../../components/switch");
|
|
48
49
|
const tabs_1 = require("../../components/tabs");
|
|
49
50
|
const core_1 = require("../../core");
|
|
51
|
+
const comments_1 = require("../comments");
|
|
50
52
|
const column_1 = require("./column");
|
|
51
53
|
const editable_field_1 = require("./editable-field");
|
|
52
54
|
const recordpage_recipe_1 = require("./recordpage.recipe");
|
|
@@ -71,19 +73,32 @@ exports.recordPageLabels = {
|
|
|
71
73
|
breadcrumb: "Breadcrumb",
|
|
72
74
|
editRecord: "Edit record",
|
|
73
75
|
recordActions: "Record actions",
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
// US spelling, matching Attio's own live text exactly (#621) — this string
|
|
77
|
+
// is shared with the entity header's favourite-star aria-label, so both
|
|
78
|
+
// read "favorites" now, not just the menu.
|
|
79
|
+
addToFavourites: "Add to favorites",
|
|
80
|
+
removeFromFavourites: "Remove from favorites",
|
|
76
81
|
copyRecordLink: "Copy record link",
|
|
82
|
+
configurePage: "Configure page",
|
|
83
|
+
copyPageUrl: "Copy page URL",
|
|
84
|
+
copyRecordId: "Copy record ID",
|
|
85
|
+
mergeRecords: "Merge records",
|
|
77
86
|
deleteRecord: "Delete record",
|
|
78
87
|
linkRecords: "Link records",
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
// Tooltip copy re-measured live for #865 (was "Log a note"/"Create a
|
|
89
|
+
// task" — the field names stay, only the visible text changes to match
|
|
90
|
+
// Attio's own React-tooltip strings on these two icon buttons exactly).
|
|
91
|
+
logANote: "New note",
|
|
92
|
+
createATask: "New task",
|
|
93
|
+
runWorkflow: "Run workflow",
|
|
81
94
|
fullScreen: "Full screen",
|
|
82
95
|
exitFullScreen: "Exit full screen",
|
|
83
96
|
resizeDetails: "Resize details column",
|
|
84
97
|
composeEmail: "Compose email",
|
|
85
98
|
askAttio: "Ask AI",
|
|
86
99
|
addToList: "Add to list",
|
|
100
|
+
share: "Share",
|
|
101
|
+
comments: "Comments",
|
|
87
102
|
noLists: "This record has not been added to any lists.",
|
|
88
103
|
highlights: "Highlights",
|
|
89
104
|
recordDetails: "Record Details",
|
|
@@ -457,7 +472,7 @@ RecordDetails.displayName = "RecordDetails";
|
|
|
457
472
|
* Emails sections (Notes & Tasks are live — add and check things off). Drop it
|
|
458
473
|
* into the app's main content area.
|
|
459
474
|
*/
|
|
460
|
-
function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, ...rest }) {
|
|
475
|
+
function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, quickActions: quickActionsProp, comments: commentsProp, onPostComment, currentUser, onShare, ...rest }) {
|
|
461
476
|
const t = useRecordPageLabels(labels);
|
|
462
477
|
const [favorite, setFavorite] = React.useState(false);
|
|
463
478
|
// Notes/Tasks are controlled when the caller supplies `notes`/`tasks` (even
|
|
@@ -469,6 +484,11 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
469
484
|
const [internalTasks, setInternalTasks] = React.useState([]);
|
|
470
485
|
const notes = notesProp ?? internalNotes;
|
|
471
486
|
const tasks = tasksProp ?? internalTasks;
|
|
487
|
+
// Comments follow the same controlled/self-contained duality (#864).
|
|
488
|
+
const [internalComments, setInternalComments] = React.useState([]);
|
|
489
|
+
const comments = commentsProp ?? internalComments;
|
|
490
|
+
const [commentsOpen, setCommentsOpen] = React.useState(false);
|
|
491
|
+
const me = currentUser ?? { id: "me", name: "You" };
|
|
472
492
|
const [showAllActivity, setShowAllActivity] = React.useState(false);
|
|
473
493
|
// Drag-resizable left details column.
|
|
474
494
|
const [detailsW, setDetailsW] = React.useState(DETAILS_DEFAULT_W);
|
|
@@ -566,6 +586,15 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
566
586
|
},
|
|
567
587
|
];
|
|
568
588
|
const highlights = highlightsProp === undefined ? defaultHighlights : highlightsProp;
|
|
589
|
+
// #865: the built-in CRM icon set for the quick-action row, used when the
|
|
590
|
+
// caller omits `quickActions` -- same "caller's own list, not a merge"
|
|
591
|
+
// choice `tabs` (#379) and `highlights` (#528) already make.
|
|
592
|
+
const defaultQuickActions = [
|
|
593
|
+
{ label: t.logANote, icon: lucide_react_1.FileText },
|
|
594
|
+
{ label: t.runWorkflow, icon: lucide_react_1.Workflow },
|
|
595
|
+
{ label: t.createATask, icon: lucide_react_1.SquareCheck },
|
|
596
|
+
];
|
|
597
|
+
const quickActions = quickActionsProp ?? defaultQuickActions;
|
|
569
598
|
const ti = { size: 15, strokeWidth: 1.75 };
|
|
570
599
|
const builtinTabs = [
|
|
571
600
|
{ value: "overview", label: t.tabs.overview, icon: (0, jsx_runtime_1.jsx)(lucide_react_1.LayoutGrid, { ...ti }) },
|
|
@@ -618,6 +647,31 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
618
647
|
}
|
|
619
648
|
setInternalTasks((n) => n.map((x) => (x.id === id ? { ...x, done: !x.done } : x)));
|
|
620
649
|
};
|
|
650
|
+
const postComment = (text, threadId) => {
|
|
651
|
+
if (commentsProp) {
|
|
652
|
+
onPostComment?.(text, threadId);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (threadId == null) {
|
|
656
|
+
setInternalComments((prev) => [
|
|
657
|
+
{
|
|
658
|
+
id: nextId(),
|
|
659
|
+
root: { id: nextId(), author: me, time: t.justNow, body: text },
|
|
660
|
+
},
|
|
661
|
+
...prev,
|
|
662
|
+
]);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
setInternalComments((prev) => prev.map((th) => th.id === threadId
|
|
666
|
+
? {
|
|
667
|
+
...th,
|
|
668
|
+
replies: [
|
|
669
|
+
...(th.replies ?? []),
|
|
670
|
+
{ id: nextId(), author: me, time: t.justNow, body: text },
|
|
671
|
+
],
|
|
672
|
+
}
|
|
673
|
+
: th));
|
|
674
|
+
};
|
|
621
675
|
// A small, plausible activity feed derived from the record's own fields.
|
|
622
676
|
const domain = byKey.get("domain");
|
|
623
677
|
const cats = byKey.get("categories");
|
|
@@ -674,7 +728,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
674
728
|
const effectivePanel = tabsProp
|
|
675
729
|
? (active) => tabsProp.find((tb) => tb.id === active)?.render()
|
|
676
730
|
: builtinPanel;
|
|
677
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-topbar", children: [(0, jsx_runtime_1.jsxs)("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-name", children: name })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-actions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.
|
|
731
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-topbar", children: [(0, jsx_runtime_1.jsxs)("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-name", children: name })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-actions", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", onClick: onShare, children: t.share }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: () => setCommentsOpen(true), "aria-label": t.comments, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MessageSquare, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Merge, { size: 15, strokeWidth: 1.75 }), children: t.mergeRecords }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Settings, { size: 15, strokeWidth: 1.75 }), children: t.configurePage }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyPageUrl }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyRecordId }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, children: t.deleteRecord })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-body", children: [(0, jsx_runtime_1.jsxs)("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? ((0, jsx_runtime_1.jsx)("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-entity-name", children: name }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-quickactions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 16, strokeWidth: 1.75 }), t.addToList] }), quickActions.map((a, i) => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": a.label, onClick: a.onSelect, children: (0, jsx_runtime_1.jsx)(a.icon, { size: 15, strokeWidth: 1.75 }) }, i)))] }), (0, jsx_runtime_1.jsx)(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-section", children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.lists, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-addbtn", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": DETAILS_MIN_W, "aria-valuemax": DETAILS_MAX_W, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
|
|
678
732
|
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
|
|
679
733
|
return;
|
|
680
734
|
// The pointer drag learned its direction in #212; this did not.
|
|
@@ -686,6 +740,6 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
686
740
|
setDetailsW((w) => grow
|
|
687
741
|
? Math.min(DETAILS_MAX_W, w + 16)
|
|
688
742
|
: Math.max(DETAILS_MIN_W, w - 16));
|
|
689
|
-
} }), (0, jsx_runtime_1.jsx)("main", { className: "crisp-recordpage-main", children: (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: effectiveTabs, defaultValue: tabsProp ? defaultTab : "overview", "aria-label": `${name} record`, children: effectivePanel }) })] })] }));
|
|
743
|
+
} }), (0, jsx_runtime_1.jsx)("main", { className: "crisp-recordpage-main", children: (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: effectiveTabs, defaultValue: tabsProp ? defaultTab : "overview", "aria-label": `${name} record`, children: effectivePanel }) })] }), (0, jsx_runtime_1.jsx)(drawer_1.Drawer, { open: commentsOpen, onOpenChange: setCommentsOpen, "aria-label": t.comments, style: { width: 320 }, children: (0, jsx_runtime_1.jsx)(comments_1.CommentsPanel, { threads: comments, currentUserId: me.id, onPost: postComment, onClose: () => setCommentsOpen(false), role: undefined }) })] }));
|
|
690
744
|
}
|
|
691
745
|
RecordPage.displayName = "RecordPage";
|
|
@@ -59,4 +59,11 @@ exports.elevation = {
|
|
|
59
59
|
* nobody measured for them.
|
|
60
60
|
*/
|
|
61
61
|
fieldNoRing: `0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04)`,
|
|
62
|
+
/**
|
|
63
|
+
* Select's own listbox shadow (crisp/#867) — seed-design's `$shadow.s3`, a
|
|
64
|
+
* single layer, NOT `floating` above. This component follows seed, not
|
|
65
|
+
* Attio, for the same reason `DatePicker` follows seed's calendar
|
|
66
|
+
* structure: an explicit owner request to match seed-design exactly.
|
|
67
|
+
*/
|
|
68
|
+
selectFloating: `0 4px 16px ${contract_1.contract.shadowSelect}`,
|
|
62
69
|
};
|
|
@@ -8,9 +8,14 @@ exports.palette = {
|
|
|
8
8
|
// on a bright tone fill. Same value as gray900's light side (the measured
|
|
9
9
|
// near-black), named separately because its job is not "primary foreground".
|
|
10
10
|
ink: { light: "#101112", dark: "#101112" },
|
|
11
|
-
|
|
11
|
+
// seed-identity migration (your-moon/crisp#890): seed's bg.layer-default-
|
|
12
|
+
// pressed / gray-100. Light side only — dark is untouched, unmeasured
|
|
13
|
+
// Attio territory this migration doesn't scope into.
|
|
14
|
+
gray50: { light: "#f7f8f9", dark: "#101112" },
|
|
12
15
|
gray900: { light: "#101112", dark: "#f5f5f5" },
|
|
13
|
-
|
|
16
|
+
// seed-identity migration (your-moon/crisp#890): seed's stroke.neutral-
|
|
17
|
+
// weak / gray-400. Light side only, same scoping as `gray50` above.
|
|
18
|
+
line200: { light: "#dcdee3", dark: "#212224" },
|
|
14
19
|
line100: { light: "#eeeff1", dark: "#1c1d1f" },
|
|
15
20
|
// ImportDialog's dropzone border — measured live from Attio's Import CSV
|
|
16
21
|
// flow (`rgb(230,231,234)`), distinct from `line200`/`line100` (#e4e4e7 /
|
|
@@ -22,8 +27,11 @@ exports.palette = {
|
|
|
22
27
|
line150: { light: "#e6e7ea", dark: "#212224" },
|
|
23
28
|
// The filter chip's field/value segments while the condition is
|
|
24
29
|
// incomplete (no value typed yet) — measured live on Attio's own filter
|
|
25
|
-
// chip (`rgb(246,247,247)`),
|
|
26
|
-
//
|
|
30
|
+
// chip (`rgb(246,247,247)`), close to but not equal to `gray50`
|
|
31
|
+
// (`#f7f8f9` since the seed-identity migration, your-moon/crisp#890 — the
|
|
32
|
+
// two used to differ the other direction, gray25 lighter; the relative
|
|
33
|
+
// order isn't the point, staying off `gray50` while remaining a
|
|
34
|
+
// near-neighbour is). The chip's operator/kebab segments stay `gray00` in the same
|
|
27
35
|
// state; only the two text-content segments carry this muted fill. Dark
|
|
28
36
|
// value not independently re-measured (Attio's dark theme is off-limits to
|
|
29
37
|
// toggle) — reuses `gray50`'s dark value, the nearest existing "muted
|
|
@@ -47,6 +55,15 @@ exports.palette = {
|
|
|
47
55
|
// both themes (Switch's own spec), so this reuses ringField's dark value
|
|
48
56
|
// rather than inventing a third "field-adjacent ring, dark" colour.
|
|
49
57
|
ringSwitch: { light: "#d9dade", dark: "#212224" },
|
|
58
|
+
// Callout's bordered/banner look (crisp#869) — measured live on Attio's
|
|
59
|
+
// Objects settings page (the "Create custom objects... Upgrade" Pro-plan
|
|
60
|
+
// banner): `border: 1px solid rgb(214,229,255)`, an opaque colour, not a
|
|
61
|
+
// composited tint of `bgBrandSolid` (checked: no single color-mix
|
|
62
|
+
// percentage matches all three channels at once). Dark value not
|
|
63
|
+
// independently re-measured (Attio's own theme toggle is off-limits) —
|
|
64
|
+
// reuses `ringField`/`ringSwitch`'s existing dark ring (#212224), the same
|
|
65
|
+
// "edge visible against a dark surface" role those already establish.
|
|
66
|
+
ringBrandSoft: { light: "#d6e5ff", dark: "#212224" },
|
|
50
67
|
tileActive: { light: "#ffffff", dark: "#101112" },
|
|
51
68
|
tileActiveShadow: { light: "rgba(0,0,0,0.04)", dark: "rgba(0,0,0,0)" },
|
|
52
69
|
// Dialog's page-dimming backdrop (crisp#677) — measured live behind the
|
|
@@ -75,25 +92,54 @@ exports.palette = {
|
|
|
75
92
|
shadowPopover2: { light: "rgba(28,40,64,0.12)", dark: "rgba(0,0,0,0.24)" },
|
|
76
93
|
shadowPopover3: { light: "rgba(24,41,75,0.16)", dark: "rgba(0,0,0,0.32)" },
|
|
77
94
|
gray400: { light: "#71717a", dark: "#9a9aa2" },
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
// the
|
|
95
|
-
//
|
|
96
|
-
|
|
95
|
+
// seed-identity migration (your-moon/crisp#890, "Attio structure, seed
|
|
96
|
+
// look"): the owner's TechPartners primary (#0298D9, sourced from the
|
|
97
|
+
// official logo SVG + the live site's own compiled CSS — see
|
|
98
|
+
// ~/grc-parity/seed-identity-spec.md §1) replaces the old measured-from-
|
|
99
|
+
// Attio brand blue, built as seed's own carrot-scale RELATIVE lightness
|
|
100
|
+
// shape around that exact hex at step 600 ("Method A" in the spec — the
|
|
101
|
+
// verified logo hex stays literally in the scale, at the step that's
|
|
102
|
+
// actually rendered, rather than a mathematically tidier neighbour).
|
|
103
|
+
// Does not flip with theme, matching this token's own prior precedent.
|
|
104
|
+
brand600: { light: "#0298d9", dark: "#0298d9" },
|
|
105
|
+
// Method A's raw carrot-shape output (L 37.1%) — the hover/pressed-darken
|
|
106
|
+
// step of the ACCENT colour above, same role brand700 has always had.
|
|
107
|
+
// Not tuned for any particular contrast target; see `brandContrast` below
|
|
108
|
+
// for the token that carries that job.
|
|
109
|
+
brand700: { light: "#0283bb", dark: "#0283bb" },
|
|
110
|
+
// your-moon/crisp#897: #0298d9 (the verified brand hex, kept exactly as
|
|
111
|
+
// the ACCENT everywhere else — focus rings, selected states, icons,
|
|
112
|
+
// tints) only clears 3.23:1 against white, short of the 4.5:1 a SOLID
|
|
113
|
+
// fill carrying white text needs (Button primary, Badge solid+brand,
|
|
114
|
+
// Avatar's brand tone), or brand-coloured TEXT sitting directly on a white
|
|
115
|
+
// page (TextLink/Text's brand tone). This is that darker, AA-safe step —
|
|
116
|
+
// same hue (198°), L dropped to 34% — clearing 4.88:1 with white. An
|
|
117
|
+
// earlier round of this fix used dark ink text instead of finding this
|
|
118
|
+
// value; that read off-brand (seed's own solid buttons use white) and got
|
|
119
|
+
// reverted in favour of this.
|
|
120
|
+
brandContrast: { light: "#0278ac", dark: "#0278ac" },
|
|
121
|
+
// seed's red-700 (fg.critical) — the accent used for plain danger TEXT
|
|
122
|
+
// (an overdue due-date, a destructive menu label) and for tinting
|
|
123
|
+
// (Chip/Badge's soft-danger `color-mix()` inputs). NOT used for a solid
|
|
124
|
+
// fill paired with white text — see `red700` below for why that split
|
|
125
|
+
// matters here specifically.
|
|
126
|
+
red600: { light: "#fa342c", dark: "#ff5a5f" },
|
|
127
|
+
// seed's red-800 (bg.critical-solid-**pressed**), not red-700
|
|
128
|
+
// (bg.critical-solid-enabled, `red600` above) — deliberately the darker
|
|
129
|
+
// of the two. `red700` is crisp's SOLID destructive fill (Button danger,
|
|
130
|
+
// Badge solid+danger), which pairs with white text and, on Button,
|
|
131
|
+
// DARKENS further on hover/active via a brightness filter (not a second
|
|
132
|
+
// token, per this token's own original comment below this migration note
|
|
133
|
+
// — that invariant is why a dark-enough red matters at all). Seed's own
|
|
134
|
+
// red-700 only clears 3.76:1 with white (measured) — short of AA even
|
|
135
|
+
// before any hover darkening — so reusing crisp's existing "needs to
|
|
136
|
+
// survive getting darker" role means red-800 (5.68:1 at rest, improving
|
|
137
|
+
// through hover/active) is the correct step, not seed's literal
|
|
138
|
+
// "enabled" one. `red500` below is pure text again (no fill/contrast
|
|
139
|
+
// constraint), so it stays on the
|
|
140
|
+
// lighter red-700, matching seed exactly.
|
|
141
|
+
red700: { light: "#ca1d13", dark: "#ba2525" },
|
|
142
|
+
red500: { light: "#fa342c", dark: "#ff5a5f" },
|
|
97
143
|
// seed-design's field focus ring (stroke-neutral-contrast = gray-1000),
|
|
98
144
|
// measured from @seed-design/css. A high-contrast neutral rather than a brand
|
|
99
145
|
// hue: the ring reads as "this field has focus", not as a state colour.
|
|
@@ -113,8 +159,25 @@ exports.palette = {
|
|
|
113
159
|
neutralSubtle: { light: "#868b94", dark: "#b0b3ba" },
|
|
114
160
|
neutralMuted: { light: "#555d6d", dark: "#dcdee3" },
|
|
115
161
|
focusRing: { light: "#1e82eb", dark: "#5e98fe" },
|
|
116
|
-
|
|
117
|
-
|
|
162
|
+
// seed's fg.positive / bg.positive-solid (green-700).
|
|
163
|
+
green600: { light: "#079171", dark: "#2ea968" },
|
|
164
|
+
// seed's fg.warning / stroke.warning-solid (yellow-700) — the ACCENT used
|
|
165
|
+
// for tinting (Badge/Chip's `soft()`/`ring()` colour-mix formulas), not
|
|
166
|
+
// seed's actual solid-fill colour, which is a much paler yellow at a
|
|
167
|
+
// different scale step — see `amberSolid` below for why the two need to
|
|
168
|
+
// stay separate tokens.
|
|
169
|
+
amber600: { light: "#9b7821", dark: "#e0982a" },
|
|
170
|
+
// seed's bg.warning-solid (yellow-300) — genuinely NOT amber600 tinted
|
|
171
|
+
// lighter: seed's warning-solid is its own pale scale step, distinct from
|
|
172
|
+
// the darker accent every other tone's solid fill derives from (see the
|
|
173
|
+
// spec's Badge section). Badge's `variant="solid" tone="warning"` uses
|
|
174
|
+
// this directly, unmixed, the same way `red700`'s solid destructive fill
|
|
175
|
+
// is used directly rather than computed from a tint. Dark side is NOT
|
|
176
|
+
// independently sourced (this is a new token; no prior Attio measurement
|
|
177
|
+
// exists to preserve) — reuses amber600's own dark value as the nearest
|
|
178
|
+
// existing "warning, dark theme" tone, same convention this file uses
|
|
179
|
+
// throughout for an unmeasured dark side (ringSwitch, line150, gray25).
|
|
180
|
+
amberSolid: { light: "#fbdc65", dark: "#e0982a" },
|
|
118
181
|
orange600: { light: "#f97514", dark: "#f97514" },
|
|
119
182
|
cyan600: { light: "#00b9eb", dark: "#00b9eb" },
|
|
120
183
|
purple600: { light: "#9b69ff", dark: "#9b69ff" },
|
|
@@ -162,4 +225,36 @@ exports.palette = {
|
|
|
162
225
|
purpleTint: { light: "#f5f0ff", dark: "#2f1e5a" },
|
|
163
226
|
softPurpleText: { light: "#6238b5", dark: "#d8c4ff" },
|
|
164
227
|
purpleRing: { light: "#e8ddfe", dark: "#45297d" },
|
|
228
|
+
// Select rebuilt to seed-design parity (crisp/#867), NOT Attio -- a
|
|
229
|
+
// deliberate one-component exception, the same way DatePicker already
|
|
230
|
+
// follows seed's calendar structure. Values below are resolved directly
|
|
231
|
+
// from `@seed-design/rootage`'s color.yaml (gray palette), not measured
|
|
232
|
+
// live, since there is no Attio reference for this component anymore.
|
|
233
|
+
// gray-00 / gray-200 (bg.layer-floating) -- the listbox surface. Distinct
|
|
234
|
+
// from `popoverBg` (crisp's Attio-measured popover, #fff/#17181a): the dark
|
|
235
|
+
// value differs (#1d2025), so this is its own token, not a reuse.
|
|
236
|
+
selectListboxBg: { light: "#ffffff", dark: "#1d2025" },
|
|
237
|
+
// gray-400 (stroke.neutral-weak) -- the trigger's real 1px border at rest.
|
|
238
|
+
selectTriggerStroke: { light: "#dcdee3", dark: "#393d46" },
|
|
239
|
+
// static-black/white-alpha (bg.transparent-pressed) -- the trigger/item
|
|
240
|
+
// press tint. Deliberately alpha, not a gray, so it reads the same over
|
|
241
|
+
// either surface colour.
|
|
242
|
+
selectPressedTint: { light: "#00000007", dark: "#ffffff0d" },
|
|
243
|
+
// gray-200 / gray-300 (bg.disabled) -- the trigger's disabled AND readonly
|
|
244
|
+
// fill. Distinct from `neutralWeak` (light matches, dark is #1d2025 not
|
|
245
|
+
// #2b2e35), so its own token rather than a reuse.
|
|
246
|
+
selectDisabledBg: { light: "#f3f4f5", dark: "#2b2e35" },
|
|
247
|
+
// red-700 (stroke.critical-solid) -- the trigger's invalid border. Distinct
|
|
248
|
+
// from crisp's own `bgDangerSolid`/`red700` (#ba2525, an Attio red): seed's
|
|
249
|
+
// red is a different hue entirely, and this component follows seed.
|
|
250
|
+
selectInvalidStroke: { light: "#fa342c", dark: "#ff6e60" },
|
|
251
|
+
// static-black/white-alpha (stroke.neutral-muted) -- the listbox's own
|
|
252
|
+
// divider slot (defined in select.yaml; not yet exposed on any consumer).
|
|
253
|
+
selectDivider: { light: "#00000010", dark: "#ffffff17" },
|
|
254
|
+
// gray-600 (fg.placeholder) -- distinct from `neutralSubtle` (its light/dark
|
|
255
|
+
// pair is the same two hexes, swapped), so its own token, not a reuse.
|
|
256
|
+
selectPlaceholder: { light: "#b0b3ba", dark: "#868b94" },
|
|
257
|
+
// s3 (shadow.yaml) -- the listbox's single-layer floating shadow, in place
|
|
258
|
+
// of crisp's own multi-layer `elevation.floating` for this component only.
|
|
259
|
+
selectShadow: { light: "#0000001f", dark: "#000000cc" },
|
|
165
260
|
};
|
package/dist/cjs/tokens/scale.js
CHANGED
|
@@ -20,6 +20,7 @@ exports.space = {
|
|
|
20
20
|
"2": "8px",
|
|
21
21
|
"2.5": "10px",
|
|
22
22
|
"3": "12px",
|
|
23
|
+
"3.5": "14px", // seed-design's $dimension.x3_5 — Select's medium trigger paddingX (crisp/#867)
|
|
23
24
|
"4": "16px",
|
|
24
25
|
"5": "20px",
|
|
25
26
|
"6": "24px",
|
|
@@ -28,12 +29,14 @@ exports.space = {
|
|
|
28
29
|
// Corner radius. sm/md/lg are the three used on stacked surfaces
|
|
29
30
|
// (inputs, rows, tiles); full is the pill.
|
|
30
31
|
exports.radius = {
|
|
32
|
+
xs: "4px", // seed-design's $radius.r1 — small badge / checkbox box corner
|
|
31
33
|
chip: "7px", // tag / chip corner — the single most used radius
|
|
32
34
|
sm: "6px", // chips, badges
|
|
33
35
|
md: "8px", // inputs, search box
|
|
34
36
|
lg: "9px", // rows, tiles, list items
|
|
35
37
|
xl: "12px", // overlay items (command rows)
|
|
36
38
|
"2xl": "16px", // overlay panels (command surface)
|
|
39
|
+
"3xl": "20px", // seed-design's $radius.r5 — Select's listbox corner (crisp/#867)
|
|
37
40
|
full: "999px", // pills
|
|
38
41
|
};
|
|
39
42
|
// Component sizing — recurring measured dimensions that are neither spacing nor
|
|
@@ -52,6 +55,12 @@ exports.size = {
|
|
|
52
55
|
// whole cells and a column gap would cut it.
|
|
53
56
|
calendarCell: "34px", // calendar day pitch (32px day + 2px gap)
|
|
54
57
|
calendarPill: "32px", // the day highlight itself
|
|
58
|
+
// seed-design's Select, resolved from select-trigger.yaml/select-item.yaml
|
|
59
|
+
// (crisp/#867) — this component follows seed, not Attio, so these sit
|
|
60
|
+
// alongside the measured scale rather than folding into it.
|
|
61
|
+
selectTriggerMd: "40px", // $dimension.x10 — the medium (default) trigger height
|
|
62
|
+
selectTriggerLg: "52px", // $dimension.x13 — the large trigger height
|
|
63
|
+
selectIconLg: "20px", // $dimension.x5 — large trigger's chevron
|
|
55
64
|
};
|
|
56
65
|
// Typography — the measured UI runs entirely on Inter. Sizes measured in the sidebar
|
|
57
66
|
// collapse onto 11/12/13/14/16 (lg = 14px is the default body/label size);
|
|
@@ -4,6 +4,17 @@ exports.semantic = void 0;
|
|
|
4
4
|
exports.semantic = {
|
|
5
5
|
bgBrandSolid: "brand600",
|
|
6
6
|
bgBrandSolidPressed: "brand700",
|
|
7
|
+
/** your-moon/crisp#897: the AA-safe darker sibling of `bgBrandSolid` — a
|
|
8
|
+
* SOLID fill carrying white text (Button primary, Badge solid+brand,
|
|
9
|
+
* Avatar's brand tone). `bgBrandSolid` itself stays the exact brand
|
|
10
|
+
* accent (focus rings, selected states, icons, tints), unpaired with
|
|
11
|
+
* white text anywhere that matters for contrast. */
|
|
12
|
+
bgBrandContrast: "brandContrast",
|
|
13
|
+
/** your-moon/crisp#897: brand-coloured TEXT sitting on a white page
|
|
14
|
+
* (TextLink/Text's `brand` tone) — same value as `bgBrandContrast`,
|
|
15
|
+
* named separately because one's a background and one's a foreground,
|
|
16
|
+
* matching every other fg/bg pair in this file. */
|
|
17
|
+
fgBrandContrast: "brandContrast",
|
|
7
18
|
fgOnBrand: "white",
|
|
8
19
|
// text on a solid success/warning/danger fill. Those tone tokens BRIGHTEN in
|
|
9
20
|
// dark (they are tuned for tinted backgrounds and coloured text), so white on
|
|
@@ -41,6 +52,15 @@ exports.semantic = {
|
|
|
41
52
|
shadowPopover2: "shadowPopover2",
|
|
42
53
|
shadowPopover3: "shadowPopover3",
|
|
43
54
|
fgPrimary: "gray900",
|
|
55
|
+
// seed-identity migration (your-moon/crisp#890): NOT touched here, on
|
|
56
|
+
// purpose. seed's closest analog (fg.neutral-subtle, #868b94) is lighter
|
|
57
|
+
// than crisp's current #71717a, and Badge's neutral-SOLID variant reuses
|
|
58
|
+
// this exact token as a BACKGROUND paired with white text
|
|
59
|
+
// (`badge.recipe.ts`'s compound variant) — #868b94 drops that pairing to
|
|
60
|
+
// 3.42:1, below AA. Retargeting `fgMuted` and fixing that one background
|
|
61
|
+
// choice belong together in Badge's own upcoming look PR, not split
|
|
62
|
+
// across two PRs where the token change alone would ship a known
|
|
63
|
+
// contrast regression.
|
|
44
64
|
fgMuted: "gray400",
|
|
45
65
|
bgDanger: "red600",
|
|
46
66
|
/** Fill for a SOLID destructive control that carries white text. */
|
|
@@ -66,6 +86,10 @@ exports.semantic = {
|
|
|
66
86
|
strokeFocusRing: "focusRing",
|
|
67
87
|
bgSuccess: "green600",
|
|
68
88
|
bgWarning: "amber600",
|
|
89
|
+
/** A SOLID warning fill (Badge's `variant="solid" tone="warning"`) — seed's
|
|
90
|
+
* own warning-solid is a distinct, paler yellow, not `bgWarning` tinted
|
|
91
|
+
* lighter. See `amberSolid` in palette.ts. */
|
|
92
|
+
bgWarningSolid: "amberSolid",
|
|
69
93
|
bgOrange: "orange600",
|
|
70
94
|
bgCyan: "cyan600",
|
|
71
95
|
bgPurple: "purple600",
|
|
@@ -85,4 +109,22 @@ exports.semantic = {
|
|
|
85
109
|
bgPurpleSoft: "purpleTint",
|
|
86
110
|
fgPurpleSoft: "softPurpleText",
|
|
87
111
|
ringPurpleSoft: "purpleRing",
|
|
112
|
+
/** Callout's bordered/banner look (crisp#869) — an opaque measured ring,
|
|
113
|
+
* not a `color-mix` tint of `bgBrandSolid` (see palette.ts's own comment). */
|
|
114
|
+
ringBrandSoft: "ringBrandSoft",
|
|
115
|
+
/** Select rebuilt to seed-design parity (crisp/#867) — see palette.ts's
|
|
116
|
+
* comment above `selectListboxBg` for why these are their own tokens
|
|
117
|
+
* rather than a reuse of an Attio-measured one. */
|
|
118
|
+
/** Seed's neutral ink (gray-1000) used as TEXT/icon colour — same palette
|
|
119
|
+
* entry as `strokeFieldFocus` (a ring), aliased separately because the
|
|
120
|
+
* role here is content colour, not a focus ring. */
|
|
121
|
+
fgSelectNeutral: "fieldFocus",
|
|
122
|
+
bgSelectListbox: "selectListboxBg",
|
|
123
|
+
strokeSelectTrigger: "selectTriggerStroke",
|
|
124
|
+
bgSelectPressed: "selectPressedTint",
|
|
125
|
+
bgSelectDisabled: "selectDisabledBg",
|
|
126
|
+
strokeSelectInvalid: "selectInvalidStroke",
|
|
127
|
+
strokeSelectDivider: "selectDivider",
|
|
128
|
+
fgSelectPlaceholder: "selectPlaceholder",
|
|
129
|
+
shadowSelect: "selectShadow",
|
|
88
130
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useRadioGroupKeyboard } from "../radiogroup/index.js";
|
|
4
|
+
import { accentpickerRecipe } from "./accentpicker.recipe.js";
|
|
5
|
+
/**
|
|
6
|
+
* A row of selectable colour swatches — Attio's own accent-colour picker
|
|
7
|
+
* (Settings > Appearance). Not a fixed palette: pass the colours (and the
|
|
8
|
+
* accent isn't crisp's concern to theme the app with — this only picks one).
|
|
9
|
+
* Same roving-focus/arrow-key model every other crisp radio-like control
|
|
10
|
+
* shares (`useRadioGroupKeyboard`, also used by `RadioGroup` and
|
|
11
|
+
* `Segmented`).
|
|
12
|
+
*/
|
|
13
|
+
export function AccentPicker({ colors, value, defaultValue, onValueChange, className, onKeyDown: onKeyDownProp, ...rest }) {
|
|
14
|
+
const [internal, setInternal] = React.useState(defaultValue);
|
|
15
|
+
const selected = value ?? internal;
|
|
16
|
+
const select = (v) => {
|
|
17
|
+
if (value === undefined)
|
|
18
|
+
setInternal(v);
|
|
19
|
+
onValueChange?.(v);
|
|
20
|
+
};
|
|
21
|
+
const { onKeyDown, groupRef } = useRadioGroupKeyboard({ select });
|
|
22
|
+
return (_jsx("div", { ref: groupRef, role: "radiogroup", className: accentpickerRecipe({ className }), ...rest, onKeyDown: (e) => {
|
|
23
|
+
onKeyDown(e);
|
|
24
|
+
onKeyDownProp?.(e);
|
|
25
|
+
}, children: colors.map((color) => {
|
|
26
|
+
const checked = selected === color;
|
|
27
|
+
return (_jsx("button", { type: "button", role: "radio", "aria-checked": checked, "aria-label": color, "data-value": color, tabIndex: checked ? 0 : -1, className: "crisp-accentpicker-swatch", style: { borderColor: checked ? color : "transparent" }, onClick: () => select(color), children: _jsx("svg", { width: "22", height: "22", viewBox: "0 0 22 22", fill: "none", "aria-hidden": "true", children: _jsx("circle", { cx: "11", cy: "11", r: "9", fill: color }) }) }, color));
|
|
28
|
+
}) }));
|
|
29
|
+
}
|
|
30
|
+
AccentPicker.displayName = "AccentPicker";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
// A row of selectable colour swatches — measured live on Attio's Appearance
|
|
4
|
+
// settings page (crisp#635): `role="radiogroup"`, `display: flex; gap: 8px`.
|
|
5
|
+
// Each swatch is a 24x24 circular button, `border: 1px solid transparent`
|
|
6
|
+
// at rest; the CHECKED swatch's border switches to its own colour (not a
|
|
7
|
+
// fixed brand ring) — measured directly on the DOM (`border: 1px solid
|
|
8
|
+
// rgb(38,109,240)` on the checked blue swatch, `1px solid rgba(0,0,0,0)` on
|
|
9
|
+
// every unchecked one). That per-swatch colour is data, not a class, so it's
|
|
10
|
+
// set inline by the component, not here. Inside sits a 22x22 SVG with one
|
|
11
|
+
// solid r=9 (18px) circle -- no separate ring/dot layer, contrary to an
|
|
12
|
+
// earlier, less precise read of this same control.
|
|
13
|
+
const config = defineRecipe({
|
|
14
|
+
name: "accentpicker",
|
|
15
|
+
base: `display: flex; gap: ${c.space["2"]};
|
|
16
|
+
& .crisp-accentpicker-swatch { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
|
|
17
|
+
width: 24px; height: 24px; padding: 0; margin: 0; border-radius: ${c.radius.full}; border: 1px solid transparent;
|
|
18
|
+
background: none; font-family: inherit; cursor: pointer; }
|
|
19
|
+
& .crisp-accentpicker-swatch:focus-visible { outline: 2px solid ${c.bgBrandSolid}; outline-offset: 2px; }`,
|
|
20
|
+
});
|
|
21
|
+
export const accentpickerRecipe = resolveRecipe(config);
|
|
22
|
+
export default config;
|
|
@@ -36,7 +36,10 @@ const spec = defineSpec({
|
|
|
36
36
|
color: c.fgNeutralMuted,
|
|
37
37
|
boxShadow: `inset 0 0 0 1px ${c.borderDefault}`,
|
|
38
38
|
},
|
|
39
|
-
|
|
39
|
+
// your-moon/crisp#897: `bgBrandContrast`, the darker AA-safe sibling
|
|
40
|
+
// of the accent blue (`bgBrandSolid`, #0298d9, only 3.23:1 with
|
|
41
|
+
// white) -- keeps white text, matching every other tone here.
|
|
42
|
+
brand: { background: c.bgBrandContrast, color: "#fff" },
|
|
40
43
|
orange: { background: c.bgOrange, color: "#fff" },
|
|
41
44
|
cyan: { background: c.bgCyan, color: "#fff" },
|
|
42
45
|
purple: { background: c.bgPurple, color: "#fff" },
|