@crisp-ui-kit/crisp 0.53.5 → 0.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activityfeed.css +12 -0
- package/dist/activityfeed.d.ts +7 -0
- package/dist/activityfeed.layered.css +14 -0
- package/dist/badge.css +3 -0
- package/dist/badge.d.ts +2 -2
- package/dist/badge.layered.css +3 -0
- package/dist/cjs/components/badge/badge.recipe.js +10 -0
- package/dist/cjs/components/bulkbar/bulkbar.js +5 -1
- package/dist/cjs/components/fileupload/fileupload.js +19 -3
- package/dist/cjs/components/formfield/formfield.js +8 -1
- package/dist/cjs/components/heatmapchart/heatmapchart.js +14 -2
- package/dist/cjs/components/skiplink/index.js +17 -0
- package/dist/cjs/components/skiplink/skiplink.js +16 -0
- package/dist/cjs/components/skiplink/skiplink.recipe.js +33 -0
- package/dist/cjs/components/textlink/index.js +7 -0
- package/dist/cjs/components/textlink/textlink.js +16 -0
- package/dist/cjs/components/textlink/textlink.recipe.js +35 -0
- package/dist/cjs/components/visuallyhidden/index.js +17 -0
- package/dist/cjs/components/visuallyhidden/visuallyhidden.js +16 -0
- package/dist/cjs/components/visuallyhidden/visuallyhidden.recipe.js +20 -0
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/product/activityfeed/activityfeed.js +38 -0
- package/dist/cjs/product/activityfeed/activityfeed.recipe.js +35 -0
- package/dist/cjs/product/activityfeed/index.js +10 -0
- package/dist/cjs/product/board/board.js +11 -2
- package/dist/cjs/product/command/command.js +13 -1
- package/dist/cjs/product/flowcanvas/flowcanvas.js +9 -2
- package/dist/cjs/product/headerbar/headerbar.js +15 -0
- package/dist/cjs/product/headerbar/headerbar.recipe.js +35 -0
- package/dist/cjs/product/headerbar/index.js +17 -0
- package/dist/cjs/product/inbox/inbox.js +13 -1
- package/dist/cjs/product/onboarding/onboarding.js +22 -1
- package/dist/cjs/product/recordpage/index.js +2 -1
- package/dist/cjs/product/recordpage/recordpage.js +197 -123
- package/dist/cjs/product/recordpage/recordpage.recipe.js +5 -0
- package/dist/cjs/product/richtext/richtext.js +43 -0
- package/dist/cjs/product/tasklist/tasklist.js +8 -1
- package/dist/cjs/tokens/palette.js +14 -0
- package/dist/cjs/tokens/semantic.js +5 -0
- package/dist/esm/components/badge/badge.recipe.js +10 -0
- package/dist/esm/components/bulkbar/bulkbar.js +5 -1
- package/dist/esm/components/fileupload/fileupload.js +20 -4
- package/dist/esm/components/formfield/formfield.js +8 -1
- package/dist/esm/components/heatmapchart/heatmapchart.js +14 -2
- package/dist/esm/components/skiplink/index.js +1 -0
- package/dist/esm/components/skiplink/skiplink.js +13 -0
- package/dist/esm/components/skiplink/skiplink.recipe.js +30 -0
- package/dist/esm/components/textlink/index.js +2 -0
- package/dist/esm/components/textlink/textlink.js +13 -0
- package/dist/esm/components/textlink/textlink.recipe.js +32 -0
- package/dist/esm/components/visuallyhidden/index.js +1 -0
- package/dist/esm/components/visuallyhidden/visuallyhidden.js +13 -0
- package/dist/esm/components/visuallyhidden/visuallyhidden.recipe.js +17 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/product/activityfeed/activityfeed.js +32 -0
- package/dist/esm/product/activityfeed/activityfeed.recipe.js +32 -0
- package/dist/esm/product/activityfeed/index.js +2 -0
- package/dist/esm/product/board/board.js +12 -3
- package/dist/esm/product/command/command.js +14 -2
- package/dist/esm/product/flowcanvas/flowcanvas.js +9 -2
- package/dist/esm/product/headerbar/headerbar.js +12 -0
- package/dist/esm/product/headerbar/headerbar.recipe.js +32 -0
- package/dist/esm/product/headerbar/index.js +1 -0
- package/dist/esm/product/inbox/inbox.js +13 -1
- package/dist/esm/product/onboarding/onboarding.js +22 -1
- package/dist/esm/product/recordpage/index.js +1 -1
- package/dist/esm/product/recordpage/recordpage.js +197 -124
- package/dist/esm/product/recordpage/recordpage.recipe.js +5 -0
- package/dist/esm/product/richtext/richtext.js +43 -0
- package/dist/esm/product/tasklist/tasklist.js +8 -1
- package/dist/esm/tokens/palette.js +14 -0
- package/dist/esm/tokens/semantic.js +5 -0
- package/dist/headerbar.css +7 -0
- package/dist/headerbar.d.ts +7 -0
- package/dist/headerbar.layered.css +9 -0
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +16 -4
- package/dist/recordpage.css +17 -31
- package/dist/recordpage.layered.css +17 -31
- package/dist/skiplink.css +5 -0
- package/dist/skiplink.d.ts +7 -0
- package/dist/skiplink.layered.css +7 -0
- package/dist/styles.css +69 -31
- package/dist/styles.layered.css +69 -31
- package/dist/textlink.css +11 -0
- package/dist/textlink.d.ts +7 -0
- package/dist/textlink.layered.css +13 -0
- package/dist/tokens.css +6 -0
- package/dist/tokens.json +16 -4
- package/dist/types/components/badge/badge.d.ts +2 -0
- package/dist/types/components/badge/badge.recipe.d.ts +8 -0
- package/dist/types/components/bulkbar/bulkbar.d.ts +1 -1
- package/dist/types/components/heatmapchart/heatmapchart.d.ts +1 -1
- package/dist/types/components/skiplink/index.d.ts +1 -0
- package/dist/types/components/skiplink/skiplink.d.ts +18 -0
- package/dist/types/components/skiplink/skiplink.recipe.d.ts +3 -0
- package/dist/types/components/textlink/index.d.ts +2 -0
- package/dist/types/components/textlink/textlink.d.ts +19 -0
- package/dist/types/components/textlink/textlink.recipe.d.ts +13 -0
- package/dist/types/components/visuallyhidden/index.d.ts +1 -0
- package/dist/types/components/visuallyhidden/visuallyhidden.d.ts +15 -0
- package/dist/types/components/visuallyhidden/visuallyhidden.recipe.d.ts +3 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/product/activityfeed/activityfeed.d.ts +57 -0
- package/dist/types/product/activityfeed/activityfeed.recipe.d.ts +3 -0
- package/dist/types/product/activityfeed/index.d.ts +2 -0
- package/dist/types/product/board/board.d.ts +1 -1
- package/dist/types/product/command/command.d.ts +1 -1
- package/dist/types/product/flowcanvas/flowcanvas.d.ts +1 -1
- package/dist/types/product/headerbar/headerbar.d.ts +27 -0
- package/dist/types/product/headerbar/headerbar.recipe.d.ts +3 -0
- package/dist/types/product/headerbar/index.d.ts +1 -0
- package/dist/types/product/inbox/inbox.d.ts +1 -1
- package/dist/types/product/onboarding/onboarding.d.ts +1 -1
- package/dist/types/product/recordpage/index.d.ts +1 -1
- package/dist/types/product/recordpage/recordpage.d.ts +90 -1
- package/dist/types/product/tasklist/tasklist.d.ts +1 -1
- package/dist/types/tokens/contract.d.ts +3 -0
- package/dist/types/tokens/palette.d.ts +12 -0
- package/dist/types/tokens/semantic.d.ts +5 -0
- package/dist/visuallyhidden.css +3 -0
- package/dist/visuallyhidden.d.ts +7 -0
- package/dist/visuallyhidden.layered.css +5 -0
- package/package.json +26 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HeaderBar = HeaderBar;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const headerbar_recipe_1 = require("./headerbar.recipe");
|
|
6
|
+
/**
|
|
7
|
+
* A page-level header row: an optional leading slot, a flexible title/
|
|
8
|
+
* breadcrumb region, and a trailing action cluster, on the measured 48px
|
|
9
|
+
* bar Attio uses for both its page-header and its view-bar row. See the
|
|
10
|
+
* recipe's own doc comment for the live measurement (crisp#462).
|
|
11
|
+
*/
|
|
12
|
+
function HeaderBar({ leading, title, trailing, bordered = true, className, ...rest }) {
|
|
13
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, headerbar_recipe_1.headerbarRecipe)({ className }), "data-bordered": bordered ? "" : undefined, ...rest, children: [leading ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-headerbar-leading", children: leading })) : null, title ? (0, jsx_runtime_1.jsx)("div", { className: "crisp-headerbar-title", children: title }) : null, trailing ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-headerbar-trailing", children: trailing })) : null] }));
|
|
14
|
+
}
|
|
15
|
+
HeaderBar.displayName = "HeaderBar";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.headerbarRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// A page-level header row — measured live at app.attio.com/techpartners/companies
|
|
7
|
+
// against two stacked bars: the top utility bar (breadcrumb + trailing icon
|
|
8
|
+
// actions, e.g. search/notifications) and the view bar directly under it
|
|
9
|
+
// ("All Companies" view name + tabs + "New Company"). Both share the same
|
|
10
|
+
// 48px content height and `0 ${space3}` padding; only the outer (first) bar
|
|
11
|
+
// carries the divider -- measured `border-bottom: 1px solid rgb(238,239,241)`
|
|
12
|
+
// (`borderSubtle`/`line100`, exact match) on the utility bar, no border of
|
|
13
|
+
// its own on the view bar directly beneath it. `bordered` (default true)
|
|
14
|
+
// lets a consumer that stacks two HeaderBars turn it off on the inner one,
|
|
15
|
+
// the way GRC's own AppLayout UtilityBar + Page viewBar do today with two
|
|
16
|
+
// hand-rolled, drifting copies of the same 49px bar (crisp#462 GRC audit,
|
|
17
|
+
// crisp#527).
|
|
18
|
+
//
|
|
19
|
+
// No bell, no breadcrumb, no search box baked in -- those are the `leading`/
|
|
20
|
+
// `title`/`trailing` slot content a consumer supplies. `size.header` (48px)
|
|
21
|
+
// is reused rather than a new token, since it's the same "workspace-switcher
|
|
22
|
+
// header height" measured elsewhere in the sidebar.
|
|
23
|
+
const config = (0, core_1.defineRecipe)({
|
|
24
|
+
name: "headerbar",
|
|
25
|
+
base: `box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; gap: ${tokens_1.contract.space["2"]}; height: ${tokens_1.contract.size.header}; flex-shrink: 0; padding: 0 ${tokens_1.contract.space["3"]}; font-family: ${tokens_1.contract.font.sans}; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
26
|
+
&[data-bordered] { border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; }
|
|
27
|
+
& .crisp-headerbar-leading { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; flex-shrink: 0; min-width: 0; }
|
|
28
|
+
& .crisp-headerbar-title { flex: 1; min-width: 0; display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; overflow: hidden; }
|
|
29
|
+
& .crisp-headerbar-trailing { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; flex-shrink: 0; }`,
|
|
30
|
+
variants: {},
|
|
31
|
+
defaultVariants: {},
|
|
32
|
+
compoundVariants: [],
|
|
33
|
+
});
|
|
34
|
+
exports.headerbarRecipe = (0, core_1.resolveRecipe)(config);
|
|
35
|
+
exports.default = config;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./headerbar"), exports);
|
|
@@ -38,6 +38,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
38
38
|
const lucide_react_1 = require("lucide-react");
|
|
39
39
|
const React = __importStar(require("react"));
|
|
40
40
|
const tabs_1 = require("../../components/tabs");
|
|
41
|
+
const core_1 = require("../../core");
|
|
41
42
|
const inbox_recipe_1 = require("./inbox.recipe");
|
|
42
43
|
/**
|
|
43
44
|
* A notifications inbox panel — measured from a notification flyout. A
|
|
@@ -45,8 +46,19 @@ const inbox_recipe_1 = require("./inbox.recipe");
|
|
|
45
46
|
* (avatar + message + time + unread dot) or a centred empty state. Put it inside
|
|
46
47
|
* a Popover for the flyout.
|
|
47
48
|
*/
|
|
48
|
-
function Inbox({ notifications = [], title
|
|
49
|
+
function Inbox({ notifications = [], title: titleProp, tabs, activeTab, onTabChange, emptyTitle: emptyTitleProp, emptySubtitle: emptySubtitleProp, emptyAction, tabsLabel: tabsLabelProp, className, ...rest }) {
|
|
49
50
|
const listRef = React.useRef(null);
|
|
51
|
+
// crisp/#471: `title`/`emptyTitle`/`emptySubtitle` are typed
|
|
52
|
+
// `React.ReactNode`, so they resolve by hand rather than through
|
|
53
|
+
// `useCrispMessage`, which is string-only — same reasoning as DataTable's
|
|
54
|
+
// own `empty`.
|
|
55
|
+
const crispMessages = (0, core_1.useCrispMessages)();
|
|
56
|
+
const title = titleProp ?? crispMessages.Notifications ?? "Notifications";
|
|
57
|
+
const emptyTitle = emptyTitleProp ?? crispMessages["No notifications"] ?? "No notifications";
|
|
58
|
+
const emptySubtitle = emptySubtitleProp ??
|
|
59
|
+
crispMessages["Notifications such as mentions and important updates will be displayed here."] ??
|
|
60
|
+
"Notifications such as mentions and important updates will be displayed here.";
|
|
61
|
+
const tabsLabel = (0, core_1.useCrispMessage)(tabsLabelProp, "Notification views");
|
|
50
62
|
// crisp's shared tablist model, the one `Tabs` runs (#195). Hand-rolling a
|
|
51
63
|
// second one is how Inbox ended up with no arrow keys, no roving tabindex
|
|
52
64
|
// and no name while `Tabs` had all three.
|
|
@@ -4,6 +4,7 @@ exports.onboardingLabels = void 0;
|
|
|
4
4
|
exports.OnboardingChecklist = OnboardingChecklist;
|
|
5
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
6
|
const lucide_react_1 = require("lucide-react");
|
|
7
|
+
const core_1 = require("../../core");
|
|
7
8
|
const onboarding_recipe_1 = require("./onboarding.recipe");
|
|
8
9
|
/**
|
|
9
10
|
* The English defaults for the two pieces of copy that are formatters rather
|
|
@@ -19,9 +20,29 @@ exports.onboardingLabels = {
|
|
|
19
20
|
* started" widget. Title + optional menu/minimize, a segmented progress bar,
|
|
20
21
|
* and step rows (completed rows strike through with a brand check).
|
|
21
22
|
*/
|
|
22
|
-
function OnboardingChecklist({ title
|
|
23
|
+
function OnboardingChecklist({ title: titleProp, steps, encouragement: encouragementProp, onStepClick, onMenu, onMinimize, className, optionsLabel: optionsLabelProp, minimizeLabel: minimizeLabelProp, progressLabel: progressLabelProp, progressText: progressTextProp, completedStepLabel: completedStepLabelProp, ...rest }) {
|
|
23
24
|
const total = steps.length;
|
|
24
25
|
const done = steps.filter((s) => s.done).length;
|
|
26
|
+
// crisp/#471: same resolution order as every other component's own —
|
|
27
|
+
// explicit prop, then a CrispMessagesProvider's translation of the English
|
|
28
|
+
// default, then that default itself. `title`/`encouragement` are typed
|
|
29
|
+
// `React.ReactNode` (a caller can pass a translated element), so they
|
|
30
|
+
// resolve by hand rather than through `useCrispMessage`, which is
|
|
31
|
+
// string-only — same reasoning as DataTable's own `empty`.
|
|
32
|
+
const crispMessages = (0, core_1.useCrispMessages)();
|
|
33
|
+
const title = titleProp ?? crispMessages["Getting started"] ?? "Getting started";
|
|
34
|
+
const encouragement = encouragementProp ?? crispMessages["Keep it up!"] ?? "Keep it up!";
|
|
35
|
+
const optionsLabel = (0, core_1.useCrispMessage)(optionsLabelProp, "Checklist options");
|
|
36
|
+
const minimizeLabel = (0, core_1.useCrispMessage)(minimizeLabelProp, "Minimize");
|
|
37
|
+
const progressLabel = (0, core_1.useCrispMessage)(progressLabelProp, "Onboarding progress");
|
|
38
|
+
// `progressText`/`completedStepLabel` are formatters, not strings (the
|
|
39
|
+
// count moves within the sentence between languages, #216) — each of
|
|
40
|
+
// their own static fragments gets its own lookup, same as
|
|
41
|
+
// TableToolbar's "Sorted by" (crisp/#217).
|
|
42
|
+
const progressText = progressTextProp ??
|
|
43
|
+
((doneCount, totalCount) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("b", { children: [doneCount, "/", totalCount] }), " ", crispMessages["steps completed"] ?? "steps completed"] })));
|
|
44
|
+
const completedStepLabel = completedStepLabelProp ??
|
|
45
|
+
((label) => `${String(label)} ${crispMessages["— completed"] ?? "— completed"}`);
|
|
25
46
|
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, onboarding_recipe_1.onboardingRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-onboarding-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-onboarding-title", children: title }), onMenu && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-onboarding-iconbtn", "aria-label": optionsLabel, onClick: onMenu, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) })), onMinimize && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-onboarding-iconbtn", "aria-label": minimizeLabel, onClick: onMinimize, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 16, strokeWidth: 1.75 }) }))] }), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-onboarding-sub", children: [progressText(done, total), encouragement ? (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [" \u00B7 ", encouragement] }) : null] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-onboarding-progress", role: "progressbar", "aria-valuenow": done, "aria-valuemin": 0, "aria-valuemax": total, "aria-label": progressLabel, children: Array.from({ length: total }, (_, i) => ((0, jsx_runtime_1.jsx)("span", { className: "crisp-onboarding-seg", "data-done": i < done ? "" : undefined }, i))) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-onboarding-steps", children: steps.map((s) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-onboarding-step", "data-done": s.done ? "" : undefined, "aria-label": s.done ? completedStepLabel(s.label) : undefined, disabled: s.done, onClick: s.done ? undefined : () => onStepClick?.(s.id), children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-onboarding-step-icon", children: s.icon }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-onboarding-step-label", children: s.label }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-onboarding-step-check", children: s.done && (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 3 }) })] }, s.id))) })] }));
|
|
26
47
|
}
|
|
27
48
|
OnboardingChecklist.displayName = "OnboardingChecklist";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.recordpageRecipe = exports.recordPageLabels = exports.RecordPage = void 0;
|
|
3
|
+
exports.recordpageRecipe = exports.recordPageLabels = exports.RecordPage = exports.RecordDetails = void 0;
|
|
4
4
|
var recordpage_1 = require("./recordpage");
|
|
5
|
+
Object.defineProperty(exports, "RecordDetails", { enumerable: true, get: function () { return recordpage_1.RecordDetails; } });
|
|
5
6
|
Object.defineProperty(exports, "RecordPage", { enumerable: true, get: function () { return recordpage_1.RecordPage; } });
|
|
6
7
|
Object.defineProperty(exports, "recordPageLabels", { enumerable: true, get: function () { return recordpage_1.recordPageLabels; } });
|
|
7
8
|
var recordpage_recipe_1 = require("./recordpage.recipe");
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.recordPageLabels = void 0;
|
|
37
|
+
exports.RecordDetails = RecordDetails;
|
|
37
38
|
exports.RecordPage = RecordPage;
|
|
38
39
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
39
40
|
const lucide_react_1 = require("lucide-react");
|
|
@@ -124,7 +125,42 @@ exports.recordPageLabels = {
|
|
|
124
125
|
taggedCategories: (count) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["Tagged ", (0, jsx_runtime_1.jsx)("b", { children: count }), " categories"] })),
|
|
125
126
|
recordCreated: (name) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("b", { children: name }), " record was created"] })),
|
|
126
127
|
noEmailsWith: (name) => (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["No emails logged with ", name, " yet."] }),
|
|
128
|
+
noFieldsMatch: (query) => (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["No fields match \u201C", query, "\u201D."] }),
|
|
127
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* crisp/#471: a `CrispMessagesProvider`'s `componentDefaults.recordPage` sits
|
|
132
|
+
* BETWEEN the built-in English defaults and an instance's own explicit
|
|
133
|
+
* `labels` prop -- a provider translates every `RecordPage`/`RecordDetails`
|
|
134
|
+
* on the page at once, and a caller who still passes `labels` for one
|
|
135
|
+
* instance keeps overriding it there. Not `useCrispMessage`: several of
|
|
136
|
+
* these entries (`domainSetTo`, `noFieldsMatch`, …) are formatter FUNCTIONS,
|
|
137
|
+
* which a flat string dictionary can't hold. Shared by `RecordPage` and
|
|
138
|
+
* `RecordDetails` (#528) -- both merge the same three layers the same way,
|
|
139
|
+
* so this is the one place that merge is written, not two copies of it.
|
|
140
|
+
*/
|
|
141
|
+
function useRecordPageLabels(labels) {
|
|
142
|
+
const providerLabels = (0, core_1.useCrispComponentDefaults)("recordPage");
|
|
143
|
+
return React.useMemo(() => ({
|
|
144
|
+
...exports.recordPageLabels,
|
|
145
|
+
...providerLabels,
|
|
146
|
+
...labels,
|
|
147
|
+
tabs: {
|
|
148
|
+
...exports.recordPageLabels.tabs,
|
|
149
|
+
...providerLabels?.tabs,
|
|
150
|
+
...labels?.tabs,
|
|
151
|
+
},
|
|
152
|
+
highlightLabels: {
|
|
153
|
+
...exports.recordPageLabels.highlightLabels,
|
|
154
|
+
...providerLabels?.highlightLabels,
|
|
155
|
+
...labels?.highlightLabels,
|
|
156
|
+
},
|
|
157
|
+
highlightEmpty: {
|
|
158
|
+
...exports.recordPageLabels.highlightEmpty,
|
|
159
|
+
...providerLabels?.highlightEmpty,
|
|
160
|
+
...labels?.highlightEmpty,
|
|
161
|
+
},
|
|
162
|
+
}), [labels, providerLabels]);
|
|
163
|
+
}
|
|
128
164
|
/** `Select` owns its own closed-by-default `open` state with no prop or
|
|
129
165
|
* imperative way to start it open -- the only thing that opens it is a real
|
|
130
166
|
* click on its trigger button. Mounting one on click (rather than rendering
|
|
@@ -180,6 +216,16 @@ function TagsValue({ values }) {
|
|
|
180
216
|
}
|
|
181
217
|
function FieldValue({ col, row, labels, }) {
|
|
182
218
|
const v = row[col.key];
|
|
219
|
+
// `render` (#528) -- the same full-custom-content escape hatch
|
|
220
|
+
// `RecordsTable`'s own columns already have (recordstable.tsx's own doc:
|
|
221
|
+
// "an escape hatch for a display crisp can't express via type"), reused
|
|
222
|
+
// here so a value this component has no built-in shape for (a reference
|
|
223
|
+
// field's link to another record, say) doesn't need a new `type`/`editor`
|
|
224
|
+
// of its own. Checked before the empty check -- like `RecordsTable`'s own
|
|
225
|
+
// `render`, it owns the cell's content fully, including what "empty"
|
|
226
|
+
// looks like for that column.
|
|
227
|
+
if (col.render)
|
|
228
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: col.render(v, row) });
|
|
183
229
|
if (valueEmpty(v))
|
|
184
230
|
return (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-empty", children: labels.setAValue });
|
|
185
231
|
// `editor: "select"` at rest (#457) -- checked ahead of `col.type` since
|
|
@@ -247,6 +293,147 @@ function TasksSection({ tasks, onAdd, onToggle, labels, }) {
|
|
|
247
293
|
function EmptyCard({ icon, children, }) {
|
|
248
294
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-emptycard", children: [icon, children] }));
|
|
249
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* The Details column's own field list, on its own (#528) — `RecordPage`'s
|
|
298
|
+
* "Record Details" collapsible + searchable field list, the one piece a
|
|
299
|
+
* consumer with a fixed-width rail of its own (no resize handle, no entity
|
|
300
|
+
* header, no CRM Lists panel — GRC's own persistent 320px column, grc#66)
|
|
301
|
+
* actually needs, rather than rebuilding this same click-to-edit/search/
|
|
302
|
+
* `sections` machinery by hand or pulling in the whole page's chrome for a
|
|
303
|
+
* rail that isn't one. `RecordPage` composes this internally for its own
|
|
304
|
+
* resizable aside (same component either way, not a second copy) — this is
|
|
305
|
+
* that same block, exactly, just not wrapped in `RecordPage`'s own topbar/
|
|
306
|
+
* tabs/resizer.
|
|
307
|
+
*
|
|
308
|
+
* Not wrapped in `recordpageRecipe` itself: doing so would duplicate the
|
|
309
|
+
* recipe's own `display:flex;height:100%` base rule as a nested child
|
|
310
|
+
* inside `RecordPage`'s aside (which already carries that class from
|
|
311
|
+
* `RecordPage`'s own root), breaking that layout. A standalone caller
|
|
312
|
+
* needs exactly one `.crisp-recordpage`-scoped ancestor for this
|
|
313
|
+
* component's classes to resolve — wrap it in one, the same way
|
|
314
|
+
* `RecordPage`'s own root does: `<div className={recordpageRecipe()}>`
|
|
315
|
+
* (also exported from this package).
|
|
316
|
+
*/
|
|
317
|
+
function RecordDetails({ record, columns, sections, onEdit, labels, className, ...rest }) {
|
|
318
|
+
const t = useRecordPageLabels(labels);
|
|
319
|
+
const { editingKey, pendingKey, error, openEditing, closeEditing, commit } = (0, recordstable_1.useEditableField)(onEdit);
|
|
320
|
+
const [detailsOpen, setDetailsOpen] = React.useState(true);
|
|
321
|
+
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
322
|
+
const [fieldQuery, setFieldQuery] = React.useState("");
|
|
323
|
+
const [showAllValues, setShowAllValues] = React.useState(false);
|
|
324
|
+
const fields = columns.filter((col) => col.type !== "entity");
|
|
325
|
+
const q = fieldQuery.trim().toLowerCase();
|
|
326
|
+
const matches = (col) => !q || col.label.toLowerCase().includes(q);
|
|
327
|
+
// #528: `sections` groups fields under headings, in the caller's own
|
|
328
|
+
// order, and always shows every match — no 5-field reveal (see
|
|
329
|
+
// `RecordPageSection`'s own doc). Omitted, this stays the flat,
|
|
330
|
+
// Attio-measured list with its 5-field "View all values" reveal,
|
|
331
|
+
// unchanged from before #528.
|
|
332
|
+
const FIELD_LIMIT = 5;
|
|
333
|
+
let canRevealFields = false;
|
|
334
|
+
let groups;
|
|
335
|
+
if (sections) {
|
|
336
|
+
const byKey = new Map(fields.map((col) => [col.key, col]));
|
|
337
|
+
groups = sections
|
|
338
|
+
.map((s) => ({
|
|
339
|
+
title: s.title,
|
|
340
|
+
fields: s.fields
|
|
341
|
+
.map((key) => byKey.get(key))
|
|
342
|
+
.filter((col) => col !== undefined)
|
|
343
|
+
.filter(matches),
|
|
344
|
+
}))
|
|
345
|
+
// A section left with no matching fields (a search that matches
|
|
346
|
+
// nothing in it, or a `fields` key naming no real column) doesn't
|
|
347
|
+
// render at all -- heading included, not an empty group under a
|
|
348
|
+
// heading with nothing below it.
|
|
349
|
+
.filter((g) => g.fields.length > 0);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const matched = q ? fields.filter(matches) : fields;
|
|
353
|
+
canRevealFields = !q && matched.length > FIELD_LIMIT;
|
|
354
|
+
const shown = canRevealFields && !showAllValues
|
|
355
|
+
? matched.slice(0, FIELD_LIMIT)
|
|
356
|
+
: matched;
|
|
357
|
+
groups = [{ title: undefined, fields: shown }];
|
|
358
|
+
}
|
|
359
|
+
const totalShown = groups.reduce((n, g) => n + g.fields.length, 0);
|
|
360
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, core_1.cx)("crisp-recordpage-section", className), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-detailhead", children: [searchOpen ? ((0, jsx_runtime_1.jsx)("input", {
|
|
361
|
+
// biome-ignore lint/a11y/noAutofocus: focusing the just-opened field search is expected
|
|
362
|
+
autoFocus: true, type: "text", className: "crisp-recordpage-fieldsearch", placeholder: t.searchPlaceholder, "aria-label": t.searchFields, value: fieldQuery, onChange: (e) => setFieldQuery(e.target.value), onKeyDown: (e) => {
|
|
363
|
+
if (e.key === "Escape") {
|
|
364
|
+
setSearchOpen(false);
|
|
365
|
+
setFieldQuery("");
|
|
366
|
+
}
|
|
367
|
+
} })) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-collapse", "data-collapsed": detailsOpen ? undefined : "", "aria-expanded": detailsOpen, onClick: () => setDetailsOpen((o) => !o), children: [t.recordDetails, (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-recordpage-chev", size: 12, strokeWidth: 2.5 })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-detailsearch", "data-active": searchOpen ? "" : undefined, "aria-label": searchOpen ? t.closeSearch : t.searchFields, "aria-pressed": searchOpen, onClick: () => {
|
|
368
|
+
setSearchOpen((s) => !s);
|
|
369
|
+
setFieldQuery("");
|
|
370
|
+
if (!searchOpen)
|
|
371
|
+
setDetailsOpen(true);
|
|
372
|
+
}, children: searchOpen ? ((0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 14, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 14, strokeWidth: 1.75 })) })] }), detailsOpen &&
|
|
373
|
+
(totalShown > 0 ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [groups.map((g, i) => ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [sections && g.title != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-section-title crisp-recordpage-fieldsection-title", children: g.title })), g.fields.map((col) => {
|
|
374
|
+
// #431: a field only actually opens when there's an
|
|
375
|
+
// `onEdit` to commit through -- `editable` with nothing
|
|
376
|
+
// to call would be a dead click.
|
|
377
|
+
const canEdit = Boolean(col.editable && onEdit);
|
|
378
|
+
const isPending = pendingKey === col.key;
|
|
379
|
+
const fieldError = error?.key === col.key ? error.message : null;
|
|
380
|
+
const label = ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-label", children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : null, (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-field-label-text", children: col.label })] }));
|
|
381
|
+
// `switch` is always its own live widget -- no
|
|
382
|
+
// click-to-open, no separate edit/view mode: flipping the
|
|
383
|
+
// toggle IS the commit gesture, and there's no
|
|
384
|
+
// meaningfully different "rest" display for a boolean
|
|
385
|
+
// (same exception `DataTable`'s own `editor` makes, #453).
|
|
386
|
+
if (canEdit && col.editor === "switch") {
|
|
387
|
+
const value = record[col.key];
|
|
388
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-edit-picker", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { "aria-label": `Edit ${col.label}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commit(record.id, col.key, String(checked)) }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-recordpage-edit-pending" }))] }), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
389
|
+
}
|
|
390
|
+
// `select` opens crisp's own `Select` on a SINGLE click
|
|
391
|
+
// (#457, corrects #431/#385's original always-live copy
|
|
392
|
+
// of the same regression #453 fixed on `DataTable`) --
|
|
393
|
+
// like every other editable type here, at rest it shows
|
|
394
|
+
// its value (see `FieldValue`'s own `col.editor ===
|
|
395
|
+
// "select"` case) via the generic click-to-open branch
|
|
396
|
+
// below, and only mounts `Select` once `editingKey`
|
|
397
|
+
// matches. `DataTable`'s own double-click gate doesn't
|
|
398
|
+
// apply: `RecordPage`'s plain text/date fields already
|
|
399
|
+
// open on a single click (measured live for #431), so
|
|
400
|
+
// `select` matches that, not `DataTable`.
|
|
401
|
+
if (canEdit &&
|
|
402
|
+
editingKey === col.key &&
|
|
403
|
+
col.editor === "select") {
|
|
404
|
+
const value = record[col.key];
|
|
405
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.jsx)(RecordPageSelectEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commit(record.id, col.key, v), onCancel: closeEditing }), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
406
|
+
}
|
|
407
|
+
if (canEdit && editingKey === col.key) {
|
|
408
|
+
const value = record[col.key];
|
|
409
|
+
const editor = (0, recordstable_1.editableInputType)(col.type);
|
|
410
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-recordpage-edit",
|
|
411
|
+
// biome-ignore lint/a11y/noAutofocus: focus the field the moment it opens
|
|
412
|
+
autoFocus: true, ...editor, disabled: isPending, defaultValue: value == null ? "" : String(value), "aria-label": `Edit ${col.label}`, onKeyDown: (e) => {
|
|
413
|
+
if (isPending)
|
|
414
|
+
return;
|
|
415
|
+
if (e.key === "Enter")
|
|
416
|
+
commit(record.id, col.key, e.currentTarget.value);
|
|
417
|
+
else if (e.key === "Escape")
|
|
418
|
+
closeEditing();
|
|
419
|
+
}, onBlur: (e) => {
|
|
420
|
+
if (isPending)
|
|
421
|
+
return;
|
|
422
|
+
commit(record.id, col.key, e.currentTarget.value);
|
|
423
|
+
} }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-recordpage-edit-pending" })), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
424
|
+
}
|
|
425
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", "data-editable": canEdit ? "" : undefined, children: [canEdit ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-edit-trigger", onClick: () => openEditing(col.key), children: (0, jsx_runtime_1.jsx)(FieldValue, { col: col, row: record, labels: t }) })) : ((0, jsx_runtime_1.jsx)(FieldValue, { col: col, row: record, labels: t })), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
426
|
+
})] }, g.title != null ? String(g.title) : `section-${i}`))), canRevealFields && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-viewall crisp-recordpage-viewall--section", onClick: () => {
|
|
427
|
+
// Revealing all fields also opens the (focused) field
|
|
428
|
+
// search; collapsing closes it.
|
|
429
|
+
const next = !showAllValues;
|
|
430
|
+
setShowAllValues(next);
|
|
431
|
+
setSearchOpen(next);
|
|
432
|
+
if (!next)
|
|
433
|
+
setFieldQuery("");
|
|
434
|
+
}, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-noresults", children: t.noFieldsMatch(fieldQuery) })))] }));
|
|
435
|
+
}
|
|
436
|
+
RecordDetails.displayName = "RecordDetails";
|
|
250
437
|
/**
|
|
251
438
|
* The full-page company record view — the page that opens when you click into a
|
|
252
439
|
* record (as opposed to the peek [RecordPanel]). A top breadcrumb bar with
|
|
@@ -255,36 +442,8 @@ function EmptyCard({ icon, children, }) {
|
|
|
255
442
|
* Emails sections (Notes & Tasks are live — add and check things off). Drop it
|
|
256
443
|
* into the app's main content area.
|
|
257
444
|
*/
|
|
258
|
-
function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, ...rest }) {
|
|
259
|
-
const
|
|
260
|
-
// crisp/#471: a CrispMessagesProvider's `componentDefaults.recordPage` sits
|
|
261
|
-
// BETWEEN the built-in English defaults and this instance's own explicit
|
|
262
|
-
// `labels` prop — a provider translates every RecordPage on the page at
|
|
263
|
-
// once, and a caller who still passes `labels` for one instance keeps
|
|
264
|
-
// overriding it there, same as before this existed. Not `useCrispMessage`:
|
|
265
|
-
// several of these entries (`domainSetTo`, `sortedBy`, …) are formatter
|
|
266
|
-
// FUNCTIONS, which a flat string dictionary can't hold.
|
|
267
|
-
const providerLabels = (0, core_1.useCrispComponentDefaults)("recordPage");
|
|
268
|
-
const t = React.useMemo(() => ({
|
|
269
|
-
...exports.recordPageLabels,
|
|
270
|
-
...providerLabels,
|
|
271
|
-
...labels,
|
|
272
|
-
tabs: {
|
|
273
|
-
...exports.recordPageLabels.tabs,
|
|
274
|
-
...providerLabels?.tabs,
|
|
275
|
-
...labels?.tabs,
|
|
276
|
-
},
|
|
277
|
-
highlightLabels: {
|
|
278
|
-
...exports.recordPageLabels.highlightLabels,
|
|
279
|
-
...providerLabels?.highlightLabels,
|
|
280
|
-
...labels?.highlightLabels,
|
|
281
|
-
},
|
|
282
|
-
highlightEmpty: {
|
|
283
|
-
...exports.recordPageLabels.highlightEmpty,
|
|
284
|
-
...providerLabels?.highlightEmpty,
|
|
285
|
-
...labels?.highlightEmpty,
|
|
286
|
-
},
|
|
287
|
-
}), [labels, providerLabels]);
|
|
445
|
+
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 }) {
|
|
446
|
+
const t = useRecordPageLabels(labels);
|
|
288
447
|
const [favorite, setFavorite] = React.useState(false);
|
|
289
448
|
// Notes/Tasks are controlled when the caller supplies `notes`/`tasks` (even
|
|
290
449
|
// `[]`) -- otherwise RecordPage keeps its own self-contained state, as
|
|
@@ -296,11 +455,6 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
296
455
|
const notes = notesProp ?? internalNotes;
|
|
297
456
|
const tasks = tasksProp ?? internalTasks;
|
|
298
457
|
const [showAllActivity, setShowAllActivity] = React.useState(false);
|
|
299
|
-
// Collapsible + searchable Record Details.
|
|
300
|
-
const [detailsOpen, setDetailsOpen] = React.useState(true);
|
|
301
|
-
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
302
|
-
const [fieldQuery, setFieldQuery] = React.useState("");
|
|
303
|
-
const [showAllValues, setShowAllValues] = React.useState(false);
|
|
304
458
|
// Drag-resizable left details column.
|
|
305
459
|
const [detailsW, setDetailsW] = React.useState(320);
|
|
306
460
|
const [resizing, setResizing] = React.useState(false);
|
|
@@ -337,17 +491,6 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
337
491
|
? record[entityCol.key]
|
|
338
492
|
: undefined;
|
|
339
493
|
const name = entity?.name ?? "Record";
|
|
340
|
-
const fields = columns.filter((col) => col.type !== "entity");
|
|
341
|
-
const q = fieldQuery.trim().toLowerCase();
|
|
342
|
-
const matchedFields = q
|
|
343
|
-
? fields.filter((col) => col.label.toLowerCase().includes(q))
|
|
344
|
-
: fields;
|
|
345
|
-
// Shows the first 5 fields; "View all values" reveals the rest.
|
|
346
|
-
const FIELD_LIMIT = 5;
|
|
347
|
-
const canRevealFields = !q && matchedFields.length > FIELD_LIMIT;
|
|
348
|
-
const shownFields = canRevealFields && !showAllValues
|
|
349
|
-
? matchedFields.slice(0, FIELD_LIMIT)
|
|
350
|
-
: matchedFields;
|
|
351
494
|
const byKey = React.useMemo(() => new Map(columns.map((col) => [col.key, col])), [columns]);
|
|
352
495
|
// The Overview "Highlights" — a fixed set of enrichment stats (measured
|
|
353
496
|
// from the live app), a couple derived from the record, the rest empty.
|
|
@@ -365,7 +508,11 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
365
508
|
: n >= 1000
|
|
366
509
|
? "1K–10K"
|
|
367
510
|
: "<1K";
|
|
368
|
-
|
|
511
|
+
// #528: the built-in CRM Highlights set, used when the caller omits
|
|
512
|
+
// `highlights` entirely -- a caller passing `highlights` (an array, or
|
|
513
|
+
// `false` to omit the section) replaces this wholesale, the same
|
|
514
|
+
// "caller's own list, not a merge" choice `tabs` already makes (#379).
|
|
515
|
+
const defaultHighlights = [
|
|
369
516
|
{
|
|
370
517
|
label: t.highlightLabels.connectionStrength,
|
|
371
518
|
icon: lucide_react_1.Zap,
|
|
@@ -403,6 +550,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
403
550
|
empty: t.highlightEmpty.employeeRange,
|
|
404
551
|
},
|
|
405
552
|
];
|
|
553
|
+
const highlights = highlightsProp === undefined ? defaultHighlights : highlightsProp;
|
|
406
554
|
const ti = { size: 15, strokeWidth: 1.75 };
|
|
407
555
|
const builtinTabs = [
|
|
408
556
|
{ value: "overview", label: t.tabs.overview, icon: (0, jsx_runtime_1.jsx)(lucide_react_1.LayoutGrid, { ...ti }) },
|
|
@@ -485,7 +633,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
485
633
|
const builtinPanel = (active) => {
|
|
486
634
|
switch (active) {
|
|
487
635
|
case "overview":
|
|
488
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-panel", children: [(0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-section-title", children: t.highlights }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-card", children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-grid", children: highlights.map((h) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-hl-cell", children: [(0, jsx_runtime_1.jsx)(h.icon, { className: "crisp-recordpage-hl-icon", size: 15, strokeWidth: 1.75 }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-label", children: h.label }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-value", children: h.value ?? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-hl-value--empty", children: h.empty })) })] }, h.label))) }) })] }), activityFeed, (0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.emails, count: 0 }), emailsEmpty] }), (0, jsx_runtime_1.jsx)(NotesSection, { notes: notes, onAdd: addNote, labels: t }), (0, jsx_runtime_1.jsx)(TasksSection, { tasks: tasks, onAdd: addTask, onToggle: toggleTask, labels: t })] }));
|
|
636
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-panel", children: [highlights !== false && ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-section-title", children: t.highlights }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-card", children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-grid", children: highlights.map((h) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-hl-cell", children: [h.icon && ((0, jsx_runtime_1.jsx)(h.icon, { className: "crisp-recordpage-hl-icon", size: 15, strokeWidth: 1.75 })), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-label", children: h.label }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-value", children: h.value ?? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-hl-value--empty", children: h.empty })) })] }, h.label))) }) })] })), activityFeed, (0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.emails, count: 0 }), emailsEmpty] }), (0, jsx_runtime_1.jsx)(NotesSection, { notes: notes, onAdd: addNote, labels: t }), (0, jsx_runtime_1.jsx)(TasksSection, { tasks: tasks, onAdd: addTask, onToggle: toggleTask, labels: t })] }));
|
|
489
637
|
case "activity":
|
|
490
638
|
return (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel", children: activityFeed });
|
|
491
639
|
case "notes":
|
|
@@ -511,81 +659,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
511
659
|
const effectivePanel = tabsProp
|
|
512
660
|
? (active) => tabsProp.find((tb) => tb.id === active)?.render()
|
|
513
661
|
: builtinPanel;
|
|
514
|
-
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.Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), (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, shortcut: "\u232B", 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.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.copyRecordLink, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.logANote, children: (0, jsx_runtime_1.jsx)(lucide_react_1.FileText, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.linkRecords, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Share2, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.createATask, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-section", children: [(0, jsx_runtime_1.jsxs)("
|
|
515
|
-
// biome-ignore lint/a11y/noAutofocus: focusing the just-opened field search is expected
|
|
516
|
-
autoFocus: true, type: "text", className: "crisp-recordpage-fieldsearch", placeholder: t.searchPlaceholder, "aria-label": t.searchFields, value: fieldQuery, onChange: (e) => setFieldQuery(e.target.value), onKeyDown: (e) => {
|
|
517
|
-
if (e.key === "Escape") {
|
|
518
|
-
setSearchOpen(false);
|
|
519
|
-
setFieldQuery("");
|
|
520
|
-
}
|
|
521
|
-
} })) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-collapse", "data-collapsed": detailsOpen ? undefined : "", "aria-expanded": detailsOpen, onClick: () => setDetailsOpen((o) => !o), children: [t.recordDetails, (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-recordpage-chev", size: 12, strokeWidth: 2.5 })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-detailsearch", "data-active": searchOpen ? "" : undefined, "aria-label": searchOpen ? t.closeSearch : t.searchFields, "aria-pressed": searchOpen, onClick: () => {
|
|
522
|
-
setSearchOpen((s) => !s);
|
|
523
|
-
setFieldQuery("");
|
|
524
|
-
if (!searchOpen)
|
|
525
|
-
setDetailsOpen(true);
|
|
526
|
-
}, children: searchOpen ? ((0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 14, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 14, strokeWidth: 1.75 })) })] }), detailsOpen &&
|
|
527
|
-
(shownFields.length > 0 ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [shownFields.map((col) => {
|
|
528
|
-
// #431: a field only actually opens when there's an
|
|
529
|
-
// `onEdit` to commit through -- `editable` with nothing
|
|
530
|
-
// to call would be a dead click.
|
|
531
|
-
const canEdit = Boolean(col.editable && onEdit);
|
|
532
|
-
const isPending = pendingKey === col.key;
|
|
533
|
-
const fieldError = error?.key === col.key ? error.message : null;
|
|
534
|
-
const label = ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-label", children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : null, (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-field-label-text", children: col.label })] }));
|
|
535
|
-
// `switch` is always its own live widget -- no
|
|
536
|
-
// click-to-open, no separate edit/view mode: flipping the
|
|
537
|
-
// toggle IS the commit gesture, and there's no
|
|
538
|
-
// meaningfully different "rest" display for a boolean
|
|
539
|
-
// (same exception `DataTable`'s own `editor` makes, #453).
|
|
540
|
-
if (canEdit && col.editor === "switch") {
|
|
541
|
-
const value = record[col.key];
|
|
542
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-edit-picker", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { "aria-label": `Edit ${col.label}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commit(record.id, col.key, String(checked)) }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-recordpage-edit-pending" }))] }), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
543
|
-
}
|
|
544
|
-
// `select` opens crisp's own `Select` on a SINGLE click
|
|
545
|
-
// (#457, corrects #431/#385's original always-live
|
|
546
|
-
// copy of the same regression #453 fixed on `DataTable`)
|
|
547
|
-
// -- like every other editable type here, at rest it
|
|
548
|
-
// shows its value (see `FieldValue`'s own `col.editor
|
|
549
|
-
// === "select"` case) via the generic click-to-open
|
|
550
|
-
// branch below, and only mounts `Select` once
|
|
551
|
-
// `editingKey` matches. `DataTable`'s own double-click
|
|
552
|
-
// gate doesn't apply: `RecordPage`'s plain text/date
|
|
553
|
-
// fields already open on a single click (measured live
|
|
554
|
-
// for #431), so `select` matches that, not `DataTable`.
|
|
555
|
-
if (canEdit &&
|
|
556
|
-
editingKey === col.key &&
|
|
557
|
-
col.editor === "select") {
|
|
558
|
-
const value = record[col.key];
|
|
559
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.jsx)(RecordPageSelectEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commit(record.id, col.key, v), onCancel: closeEditing }), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
560
|
-
}
|
|
561
|
-
if (canEdit && editingKey === col.key) {
|
|
562
|
-
const value = record[col.key];
|
|
563
|
-
const editor = (0, recordstable_1.editableInputType)(col.type);
|
|
564
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-recordpage-edit",
|
|
565
|
-
// biome-ignore lint/a11y/noAutofocus: focus the field the moment it opens
|
|
566
|
-
autoFocus: true, ...editor, disabled: isPending, defaultValue: value == null ? "" : String(value), "aria-label": `Edit ${col.label}`, onKeyDown: (e) => {
|
|
567
|
-
if (isPending)
|
|
568
|
-
return;
|
|
569
|
-
if (e.key === "Enter")
|
|
570
|
-
commit(record.id, col.key, e.currentTarget.value);
|
|
571
|
-
else if (e.key === "Escape")
|
|
572
|
-
closeEditing();
|
|
573
|
-
}, onBlur: (e) => {
|
|
574
|
-
if (isPending)
|
|
575
|
-
return;
|
|
576
|
-
commit(record.id, col.key, e.currentTarget.value);
|
|
577
|
-
} }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-recordpage-edit-pending" })), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
578
|
-
}
|
|
579
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-field", children: [label, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-field-value", "data-editable": canEdit ? "" : undefined, children: [canEdit ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-edit-trigger", onClick: () => openEditing(col.key), children: (0, jsx_runtime_1.jsx)(FieldValue, { col: col, row: record, labels: t }) })) : ((0, jsx_runtime_1.jsx)(FieldValue, { col: col, row: record, labels: t })), fieldError && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
580
|
-
}), canRevealFields && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-viewall crisp-recordpage-viewall--section", onClick: () => {
|
|
581
|
-
// Revealing all fields also opens the (focused)
|
|
582
|
-
// field search; collapsing closes it.
|
|
583
|
-
const next = !showAllValues;
|
|
584
|
-
setShowAllValues(next);
|
|
585
|
-
setSearchOpen(next);
|
|
586
|
-
if (!next)
|
|
587
|
-
setFieldQuery("");
|
|
588
|
-
}, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-noresults", children: ["No fields match \u201C", fieldQuery, "\u201D."] })))] }), (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": 300, "aria-valuemax": 560, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
|
|
662
|
+
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.Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), (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, shortcut: "\u232B", 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.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.copyRecordLink, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.logANote, children: (0, jsx_runtime_1.jsx)(lucide_react_1.FileText, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.linkRecords, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Share2, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.createATask, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), (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": 300, "aria-valuemax": 560, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
|
|
589
663
|
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
|
|
590
664
|
return;
|
|
591
665
|
// The pointer drag learned its direction in #212; this did not.
|
|
@@ -66,6 +66,11 @@ const config = (0, core_1.defineRecipe)({
|
|
|
66
66
|
& .crisp-recordpage-detailsearch { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: ${tokens_1.contract.size.chip}; flex-shrink: 0; padding: 0; margin: 0; border: none; border-radius: ${tokens_1.contract.radius.sm}; background: none; color: ${tokens_1.contract.fgMuted}; font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
|
|
67
67
|
& .crisp-recordpage-detailsearch:hover, & .crisp-recordpage-detailsearch[data-active] { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
|
|
68
68
|
& .crisp-recordpage-noresults { padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["1"]}; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; }
|
|
69
|
+
/* A caller-supplied \`sections\` grouping (#528) is not an Attio-measured
|
|
70
|
+
shape (its own Details column is flat) -- this heading reuses
|
|
71
|
+
.crisp-recordpage-section-title's own type treatment verbatim, just
|
|
72
|
+
spaced from the group above it. */
|
|
73
|
+
& .crisp-recordpage-fieldsection-title { margin-top: ${tokens_1.contract.space["3"]}; }
|
|
69
74
|
& .crisp-recordpage-morevalues { display: inline-flex; align-items: center; padding: 0; margin: 0; border: none; background: none; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgMuted}; cursor: pointer; }
|
|
70
75
|
& .crisp-recordpage-morevalues:hover { color: ${tokens_1.contract.fgPrimary}; }
|
|
71
76
|
& .crisp-recordpage-viewall { flex-basis: 100%; margin-top: ${tokens_1.contract.space["0.5"]}; border: none; background: none; padding: 0; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 55%, transparent); font-family: inherit; font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.regular}; cursor: pointer; text-align: start; }
|