@crisp-ui-kit/crisp 0.57.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/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/input/input.js +2 -2
- package/dist/cjs/components/input/input.recipe.js +8 -0
- package/dist/cjs/components/multiselect/multiselect.js +1 -1
- package/dist/cjs/components/multiselect/multiselect.recipe.js +12 -3
- package/dist/cjs/components/progress/progress.js +21 -4
- package/dist/cjs/components/progress/progress.recipe.js +30 -0
- package/dist/cjs/components/select/select.js +2 -2
- package/dist/cjs/components/select/select.recipe.js +32 -5
- 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.recipe.js +6 -3
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/product/recordpage/recordpage.js +46 -5
- package/dist/cjs/tokens/palette.js +79 -25
- package/dist/cjs/tokens/scale.js +1 -0
- package/dist/cjs/tokens/semantic.js +24 -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/input/input.js +3 -3
- package/dist/esm/components/input/input.recipe.js +8 -0
- package/dist/esm/components/multiselect/multiselect.js +1 -1
- package/dist/esm/components/multiselect/multiselect.recipe.js +12 -3
- package/dist/esm/components/progress/progress.js +22 -5
- package/dist/esm/components/progress/progress.recipe.js +30 -0
- package/dist/esm/components/select/select.js +2 -2
- package/dist/esm/components/select/select.recipe.js +32 -5
- 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.recipe.js +6 -3
- package/dist/esm/index.js +1 -0
- package/dist/esm/product/recordpage/recordpage.js +47 -6
- package/dist/esm/tokens/palette.js +79 -25
- package/dist/esm/tokens/scale.js +1 -0
- package/dist/esm/tokens/semantic.js +24 -0
- package/dist/input.css +7 -0
- package/dist/input.layered.css +7 -0
- package/dist/multiselect.css +4 -0
- package/dist/multiselect.d.ts +2 -2
- package/dist/multiselect.layered.css +4 -0
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +33 -22
- package/dist/progress.css +3 -0
- package/dist/progress.layered.css +3 -0
- package/dist/select.css +22 -2
- package/dist/select.d.ts +2 -2
- package/dist/select.layered.css +22 -2
- package/dist/styles.css +71 -23
- package/dist/styles.layered.css +71 -23
- 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 +2 -2
- package/dist/textlink.layered.css +2 -2
- package/dist/tokens.css +18 -11
- package/dist/tokens.json +33 -22
- 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/input/input.d.ts +12 -0
- package/dist/types/components/multiselect/multiselect.d.ts +4 -3
- package/dist/types/components/multiselect/multiselect.recipe.d.ts +3 -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/select.d.ts +6 -5
- package/dist/types/components/select/select.recipe.d.ts +3 -0
- package/dist/types/components/textarea/textarea.d.ts +16 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/product/recordpage/recordpage.d.ts +37 -1
- package/dist/types/tokens/contract.d.ts +4 -1
- package/dist/types/tokens/palette.d.ts +19 -11
- package/dist/types/tokens/scale.d.ts +1 -0
- package/dist/types/tokens/semantic.d.ts +15 -0
- package/package.json +6 -1
|
@@ -21,11 +21,14 @@ 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}; }`,
|
|
29
32
|
},
|
|
30
33
|
// crisp#874: an explicit, OPTIONAL size override -- no `defaultVariants`
|
|
31
34
|
// entry below, so omitting `size` emits no `--size_*` class at all and
|
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,9 +73,16 @@ 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
88
|
// Tooltip copy re-measured live for #865 (was "Log a note"/"Create a
|
|
@@ -88,6 +97,8 @@ exports.recordPageLabels = {
|
|
|
88
97
|
composeEmail: "Compose email",
|
|
89
98
|
askAttio: "Ask AI",
|
|
90
99
|
addToList: "Add to list",
|
|
100
|
+
share: "Share",
|
|
101
|
+
comments: "Comments",
|
|
91
102
|
noLists: "This record has not been added to any lists.",
|
|
92
103
|
highlights: "Highlights",
|
|
93
104
|
recordDetails: "Record Details",
|
|
@@ -461,7 +472,7 @@ RecordDetails.displayName = "RecordDetails";
|
|
|
461
472
|
* Emails sections (Notes & Tasks are live — add and check things off). Drop it
|
|
462
473
|
* into the app's main content area.
|
|
463
474
|
*/
|
|
464
|
-
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, ...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 }) {
|
|
465
476
|
const t = useRecordPageLabels(labels);
|
|
466
477
|
const [favorite, setFavorite] = React.useState(false);
|
|
467
478
|
// Notes/Tasks are controlled when the caller supplies `notes`/`tasks` (even
|
|
@@ -473,6 +484,11 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
473
484
|
const [internalTasks, setInternalTasks] = React.useState([]);
|
|
474
485
|
const notes = notesProp ?? internalNotes;
|
|
475
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" };
|
|
476
492
|
const [showAllActivity, setShowAllActivity] = React.useState(false);
|
|
477
493
|
// Drag-resizable left details column.
|
|
478
494
|
const [detailsW, setDetailsW] = React.useState(DETAILS_DEFAULT_W);
|
|
@@ -631,6 +647,31 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
631
647
|
}
|
|
632
648
|
setInternalTasks((n) => n.map((x) => (x.id === id ? { ...x, done: !x.done } : x)));
|
|
633
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
|
+
};
|
|
634
675
|
// A small, plausible activity feed derived from the record's own fields.
|
|
635
676
|
const domain = byKey.get("domain");
|
|
636
677
|
const cats = byKey.get("categories");
|
|
@@ -687,7 +728,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
687
728
|
const effectivePanel = tabsProp
|
|
688
729
|
? (active) => tabsProp.find((tb) => tb.id === active)?.render()
|
|
689
730
|
: builtinPanel;
|
|
690
|
-
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) => {
|
|
691
732
|
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
|
|
692
733
|
return;
|
|
693
734
|
// The pointer drag learned its direction in #212; this did not.
|
|
@@ -699,6 +740,6 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
699
740
|
setDetailsW((w) => grow
|
|
700
741
|
? Math.min(DETAILS_MAX_W, w + 16)
|
|
701
742
|
: Math.max(DETAILS_MIN_W, w - 16));
|
|
702
|
-
} }), (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 }) })] }));
|
|
703
744
|
}
|
|
704
745
|
RecordPage.displayName = "RecordPage";
|
|
@@ -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
|
|
@@ -84,25 +92,54 @@ exports.palette = {
|
|
|
84
92
|
shadowPopover2: { light: "rgba(28,40,64,0.12)", dark: "rgba(0,0,0,0.24)" },
|
|
85
93
|
shadowPopover3: { light: "rgba(24,41,75,0.16)", dark: "rgba(0,0,0,0.32)" },
|
|
86
94
|
gray400: { light: "#71717a", dark: "#9a9aa2" },
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
// the
|
|
104
|
-
//
|
|
105
|
-
|
|
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" },
|
|
106
143
|
// seed-design's field focus ring (stroke-neutral-contrast = gray-1000),
|
|
107
144
|
// measured from @seed-design/css. A high-contrast neutral rather than a brand
|
|
108
145
|
// hue: the ring reads as "this field has focus", not as a state colour.
|
|
@@ -122,8 +159,25 @@ exports.palette = {
|
|
|
122
159
|
neutralSubtle: { light: "#868b94", dark: "#b0b3ba" },
|
|
123
160
|
neutralMuted: { light: "#555d6d", dark: "#dcdee3" },
|
|
124
161
|
focusRing: { light: "#1e82eb", dark: "#5e98fe" },
|
|
125
|
-
|
|
126
|
-
|
|
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" },
|
|
127
181
|
orange600: { light: "#f97514", dark: "#f97514" },
|
|
128
182
|
cyan600: { light: "#00b9eb", dark: "#00b9eb" },
|
|
129
183
|
purple600: { light: "#9b69ff", dark: "#9b69ff" },
|
package/dist/cjs/tokens/scale.js
CHANGED
|
@@ -29,6 +29,7 @@ exports.space = {
|
|
|
29
29
|
// Corner radius. sm/md/lg are the three used on stacked surfaces
|
|
30
30
|
// (inputs, rows, tiles); full is the pill.
|
|
31
31
|
exports.radius = {
|
|
32
|
+
xs: "4px", // seed-design's $radius.r1 — small badge / checkbox box corner
|
|
32
33
|
chip: "7px", // tag / chip corner — the single most used radius
|
|
33
34
|
sm: "6px", // chips, badges
|
|
34
35
|
md: "8px", // inputs, search box
|
|
@@ -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",
|
|
@@ -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" },
|
|
@@ -126,8 +126,14 @@ const spec = defineSpec({
|
|
|
126
126
|
{
|
|
127
127
|
variant: "solid",
|
|
128
128
|
tone: "brand",
|
|
129
|
+
// your-moon/crisp#897: `bgBrandContrast`, not `bgBrandSolid` — white
|
|
130
|
+
// text on the exact accent blue (#0298d9) only clears 3.23:1;
|
|
131
|
+
// `bgBrandContrast` is the darker sibling built to clear 4.5:1+ with
|
|
132
|
+
// white, so `fgOnBrand` (white) stays right rather than `fgOnTone`
|
|
133
|
+
// (ink read off-brand next to solid `success`/`warning`/`danger`,
|
|
134
|
+
// which really do need it).
|
|
129
135
|
decls: {
|
|
130
|
-
background: c.
|
|
136
|
+
background: c.bgBrandContrast,
|
|
131
137
|
color: c.fgOnBrand,
|
|
132
138
|
boxShadow: "none",
|
|
133
139
|
},
|
|
@@ -140,7 +146,15 @@ const spec = defineSpec({
|
|
|
140
146
|
{
|
|
141
147
|
variant: "solid",
|
|
142
148
|
tone: "warning",
|
|
143
|
-
|
|
149
|
+
// seed-identity migration (your-moon/crisp#890): `bgWarningSolid`, not
|
|
150
|
+
// `bgWarning` — seed's own solid warning fill is a distinct, paler
|
|
151
|
+
// yellow from the accent every other tone's solid fill reuses
|
|
152
|
+
// directly (see tokens/palette.ts's `amberSolid` comment).
|
|
153
|
+
decls: {
|
|
154
|
+
background: c.bgWarningSolid,
|
|
155
|
+
color: c.fgOnTone,
|
|
156
|
+
boxShadow: "none",
|
|
157
|
+
},
|
|
144
158
|
},
|
|
145
159
|
{
|
|
146
160
|
variant: "solid",
|
|
@@ -27,7 +27,13 @@ const spec = defineSpec({
|
|
|
27
27
|
intent: {
|
|
28
28
|
primary: {
|
|
29
29
|
decls: {
|
|
30
|
-
|
|
30
|
+
// your-moon/crisp#897: `bgBrandContrast`, not `bgBrandSolid` —
|
|
31
|
+
// white text on the exact accent blue (#0298d9) only clears
|
|
32
|
+
// 3.23:1; `bgBrandContrast` is the darker sibling built to clear
|
|
33
|
+
// white at 4.5:1+, keeping `fgOnBrand` (white) rather than
|
|
34
|
+
// reaching for `fgOnTone` (ink read off-brand — seed's own solid
|
|
35
|
+
// buttons use white).
|
|
36
|
+
background: c.bgBrandContrast,
|
|
31
37
|
color: c.fgOnBrand,
|
|
32
38
|
// crisp#494: the inset ring is a black tint, not white -- live-
|
|
33
39
|
// measured on Attio's own "New Company" button as
|
|
@@ -36,12 +42,16 @@ const spec = defineSpec({
|
|
|
36
42
|
// exactly.
|
|
37
43
|
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08)",
|
|
38
44
|
},
|
|
45
|
+
// your-moon/crisp#897: a brightness filter, not a second background
|
|
46
|
+
// swap — matches `danger`'s own pattern below. `bgBrandSolidPressed`
|
|
47
|
+
// remains the accent's own hover-darken for the many OTHER
|
|
48
|
+
// consumers that pair `bgBrandSolid` with white already (out of
|
|
49
|
+
// scope for this fix; tracked separately), so reusing it here would
|
|
50
|
+
// have made primary's rest-to-hover jump inconsistent with its own
|
|
51
|
+
// rest fill's new, darker starting point.
|
|
39
52
|
parts: [
|
|
40
|
-
{ selector: "&:hover", decls: {
|
|
41
|
-
{
|
|
42
|
-
selector: "&:active",
|
|
43
|
-
decls: { background: c.bgBrandSolidPressed },
|
|
44
|
-
},
|
|
53
|
+
{ selector: "&:hover", decls: { filter: "brightness(0.96)" } },
|
|
54
|
+
{ selector: "&:active", decls: { filter: "brightness(0.92)" } },
|
|
45
55
|
],
|
|
46
56
|
},
|
|
47
57
|
neutral: {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { inputRecipe } from "./input.recipe.js";
|
|
4
|
-
export const Input = React.forwardRef(({ size, invalid, className, style, ...props }, ref) => {
|
|
4
|
+
export const Input = React.forwardRef(({ size, invalid, className, style, suffix, ...props }, ref) => {
|
|
5
5
|
// The wrapper exists only to host the ::after focus ring — an <input> is a
|
|
6
6
|
// replaced element and cannot have one. Everything the consumer passes
|
|
7
7
|
// still lands on the <input> itself, so `id`, `name`, `onChange`, form
|
|
8
8
|
// association and every native attribute behave exactly as before.
|
|
9
|
-
return (
|
|
9
|
+
return (_jsxs("span", { className: inputRecipe({ size, invalid, className }), style: style, "data-readonly": props.readOnly ? "" : undefined, children: [_jsx("input", { ref: ref, className: "crisp-input-value", "aria-invalid": invalid === true || undefined, ...props }), suffix != null && _jsx("span", { className: "crisp-input-suffix", children: suffix })] }));
|
|
10
10
|
});
|
|
11
11
|
Input.displayName = "Input";
|
|
@@ -38,6 +38,14 @@ const config = defineRecipe({
|
|
|
38
38
|
& .crisp-input-value::placeholder { color: ${c.fgMuted}; }
|
|
39
39
|
& .crisp-input-value:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
40
40
|
&:has(.crisp-input-value:disabled) { opacity: 1; }
|
|
41
|
+
|
|
42
|
+
/* crisp#634: measured live on Attio's own Settings > Profile read-only
|
|
43
|
+
primary-email field -- a tinted field, not just muted text, driven off
|
|
44
|
+
the native \`readOnly\` attribute. \`ringField\` (rgba(0,0,0,.06)) is an
|
|
45
|
+
exact match for the measured ring; the 2%-fgPrimary tint reuses the
|
|
46
|
+
same color-mix idiom every other soft crisp surface already uses. */
|
|
47
|
+
&[data-readonly] { background: color-mix(in srgb, ${c.fgPrimary} 2%, transparent); box-shadow: inset 0 0 0 1px ${c.ringField}; }
|
|
48
|
+
& .crisp-input-suffix { flex-shrink: 0; margin-inline-start: ${c.space["1"]}; }
|
|
41
49
|
`,
|
|
42
50
|
variants: {
|
|
43
51
|
size: {
|
|
@@ -35,7 +35,7 @@ export function MultiSelect({ options, value: valueProp, defaultValue, onValueCh
|
|
|
35
35
|
// The id belongs on the trigger, not the wrapper: `<label for>` only names a
|
|
36
36
|
// LABELABLE element, so an id left on the root div meant a FormField label
|
|
37
37
|
// above this control named nothing (#247). Select already did this.
|
|
38
|
-
id, placeholder: placeholderProp, searchable = true, searchPlaceholder: searchPlaceholderProp, emptyLabel: emptyLabelProp, disabled, readOnly, invalid, size = "
|
|
38
|
+
id, placeholder: placeholderProp, searchable = true, searchPlaceholder: searchPlaceholderProp, emptyLabel: emptyLabelProp, disabled, readOnly, invalid, size = "small", variant = "default", className, "aria-label": ariaLabel, ...rest }) {
|
|
39
39
|
// crisp/#471: same resolution order as Select's own.
|
|
40
40
|
const placeholder = useCrispMessage(placeholderProp, "Select…");
|
|
41
41
|
const searchPlaceholder = useCrispMessage(searchPlaceholderProp, "Search…");
|
|
@@ -96,9 +96,18 @@ const config = defineRecipe({
|
|
|
96
96
|
.crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, ${c.bgOrange} 16%, transparent); color: color-mix(in srgb, ${c.bgOrange} 82%, ${c.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${c.bgOrange} 30%, transparent); }
|
|
97
97
|
`,
|
|
98
98
|
variants: {
|
|
99
|
-
//
|
|
100
|
-
//
|
|
99
|
+
// Owner's follow-up after #867/#879/#882 (crisp/#889): SIZE is Attio's
|
|
100
|
+
// again, LOOK stays seed's -- see Select's own recipe comment for the
|
|
101
|
+
// full reasoning (measured live in GRC's "New employee" dialog: a 40px
|
|
102
|
+
// trigger next to a 32px `Input` visibly mismatched). `small` is the new
|
|
103
|
+
// default: `c.size.menuRow` (32px) and the original, pre-#882
|
|
104
|
+
// Attio-measured asymmetric padding for a chip-wrapping trigger.
|
|
105
|
+
// `medium`/`large` (seed's own 40/52px) stay, opt-in.
|
|
101
106
|
size: {
|
|
107
|
+
small: `
|
|
108
|
+
& .crisp-multiselect-trigger { min-height: ${c.size.menuRow}; border-radius: ${c.radius.md}; gap: ${c.space["1"]}; padding: ${c.space["0.5"]} ${c.space["1.5"]} ${c.space["0.5"]} ${c.space["2"]}; }
|
|
109
|
+
& .crisp-multiselect-chevron { width: ${c.size.icon}; height: ${c.size.icon}; }
|
|
110
|
+
`,
|
|
102
111
|
medium: `
|
|
103
112
|
& .crisp-multiselect-trigger { min-height: ${c.size.selectTriggerMd}; border-radius: ${c.radius.md}; gap: ${c.space["2"]}; padding: ${c.space["1"]} ${c.space["3.5"]}; }
|
|
104
113
|
& .crisp-multiselect-chevron { width: ${c.size.icon}; height: ${c.size.icon}; }
|
|
@@ -131,7 +140,7 @@ const config = defineRecipe({
|
|
|
131
140
|
`,
|
|
132
141
|
},
|
|
133
142
|
},
|
|
134
|
-
defaultVariants: { size: "
|
|
143
|
+
defaultVariants: { size: "small", invalid: false, variant: "default" },
|
|
135
144
|
compoundVariants: [],
|
|
136
145
|
});
|
|
137
146
|
export const multiselectRecipe = Object.assign(resolveRecipe(config), {
|