@crisp-ui-kit/crisp 0.54.3 → 0.54.4
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/board.css +13 -1
- package/dist/board.layered.css +13 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +41 -4
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/product/board/board.js +60 -2
- package/dist/cjs/product/board/board.recipe.js +10 -0
- package/dist/cjs/product/comments/comments.js +426 -0
- package/dist/cjs/product/comments/comments.recipe.js +126 -0
- package/dist/cjs/product/comments/index.js +8 -0
- package/dist/cjs/product/listpage/listpage.recipe.js +10 -1
- package/dist/cjs/product/objectsettings/index.js +16 -0
- package/dist/cjs/product/objectsettings/objectsettings.js +365 -0
- package/dist/cjs/product/objectsettings/objectsettings.recipe.js +65 -0
- package/dist/cjs/product/taskstable/taskstable.recipe.js +10 -1
- package/dist/comments.css +80 -0
- package/dist/comments.d.ts +7 -0
- package/dist/comments.layered.css +82 -0
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +43 -6
- package/dist/esm/index.js +2 -0
- package/dist/esm/product/board/board.js +61 -3
- package/dist/esm/product/board/board.recipe.js +10 -0
- package/dist/esm/product/comments/comments.js +388 -0
- package/dist/esm/product/comments/comments.recipe.js +123 -0
- package/dist/esm/product/comments/index.js +2 -0
- package/dist/esm/product/listpage/listpage.recipe.js +10 -1
- package/dist/esm/product/objectsettings/index.js +2 -0
- package/dist/esm/product/objectsettings/objectsettings.js +322 -0
- package/dist/esm/product/objectsettings/objectsettings.recipe.js +62 -0
- package/dist/esm/product/taskstable/taskstable.recipe.js +10 -1
- package/dist/listpage.css +8 -1
- package/dist/listpage.layered.css +8 -1
- package/dist/objectsettings.css +49 -0
- package/dist/objectsettings.d.ts +7 -0
- package/dist/objectsettings.layered.css +51 -0
- package/dist/styles.css +160 -3
- package/dist/styles.layered.css +160 -3
- package/dist/taskstable.css +8 -1
- package/dist/taskstable.layered.css +8 -1
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +8 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/product/board/board.d.ts +20 -1
- package/dist/types/product/comments/comments.d.ts +192 -0
- package/dist/types/product/comments/comments.recipe.d.ts +3 -0
- package/dist/types/product/comments/index.d.ts +2 -0
- package/dist/types/product/objectsettings/index.d.ts +2 -0
- package/dist/types/product/objectsettings/objectsettings.d.ts +261 -0
- package/dist/types/product/objectsettings/objectsettings.recipe.d.ts +3 -0
- package/package.json +11 -1
|
@@ -0,0 +1,365 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ObjectSettings = exports.ATTRIBUTE_AI_AUTOFILL_OPTIONS = exports.ATTRIBUTE_TYPE_OPTIONS = void 0;
|
|
37
|
+
exports.ObjectsList = ObjectsList;
|
|
38
|
+
exports.ObjectDetailPage = ObjectDetailPage;
|
|
39
|
+
exports.ObjectGeneralSection = ObjectGeneralSection;
|
|
40
|
+
exports.ObjectAppearanceSection = ObjectAppearanceSection;
|
|
41
|
+
exports.AttributesTable = AttributesTable;
|
|
42
|
+
exports.AttributeTypeSelect = AttributeTypeSelect;
|
|
43
|
+
exports.CreateAttributeDialog = CreateAttributeDialog;
|
|
44
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
45
|
+
const lucide_react_1 = require("lucide-react");
|
|
46
|
+
const React = __importStar(require("react"));
|
|
47
|
+
const badge_1 = require("../../components/badge");
|
|
48
|
+
const datatable_1 = require("../../components/datatable");
|
|
49
|
+
const formfield_1 = require("../../components/formfield");
|
|
50
|
+
const input_1 = require("../../components/input");
|
|
51
|
+
const menu_1 = require("../../components/menu");
|
|
52
|
+
const select_1 = require("../../components/select");
|
|
53
|
+
const switch_1 = require("../../components/switch");
|
|
54
|
+
const tabs_1 = require("../../components/tabs");
|
|
55
|
+
const textarea_1 = require("../../components/textarea");
|
|
56
|
+
const core_1 = require("../../core");
|
|
57
|
+
const formdialog_1 = require("../formdialog");
|
|
58
|
+
const settingssection_1 = require("../settingssection");
|
|
59
|
+
const objectsettings_recipe_1 = require("./objectsettings.recipe");
|
|
60
|
+
/**
|
|
61
|
+
* Settings → Objects (crisp#637, P-122): H1 + subtitle, a search+Filter row,
|
|
62
|
+
* and a DataTable of every object (icon+name, type, record count,
|
|
63
|
+
* attribute count, a per-row "…" menu). Measured live against
|
|
64
|
+
* app.attio.com/<workspace>/settings/data/objects — the plan-gate banner
|
|
65
|
+
* above the table and the "5/3" object-count pill in the "Object" header
|
|
66
|
+
* (both billing-tier chrome) are skipped by decision, not oversight.
|
|
67
|
+
*/
|
|
68
|
+
function ObjectsList({ objects, onOpenObject, onEditObject, onCopyObjectId, onCreateObject, title: titleProp, subtitle: subtitleProp, newObjectLabel: newObjectLabelProp, searchPlaceholder: searchPlaceholderProp, filterLabel: filterLabelProp, objectColumnLabel: objectColumnLabelProp, typeColumnLabel: typeColumnLabelProp, recordsColumnLabel: recordsColumnLabelProp, attributesColumnLabel: attributesColumnLabelProp, noRecordsLabel: noRecordsLabelProp, editLabel: editLabelProp, copyIdLabel: copyIdLabelProp, rowMenuLabel, emptyLabel, className, ...rest }) {
|
|
69
|
+
const title = (0, core_1.useCrispMessage)(titleProp, "Objects");
|
|
70
|
+
const subtitle = (0, core_1.useCrispMessage)(subtitleProp, "Modify and add objects in your workspace");
|
|
71
|
+
const newObjectLabel = (0, core_1.useCrispMessage)(newObjectLabelProp, "New custom object");
|
|
72
|
+
const searchPlaceholder = (0, core_1.useCrispMessage)(searchPlaceholderProp, "Search objects");
|
|
73
|
+
const filterLabel = (0, core_1.useCrispMessage)(filterLabelProp, "Filter");
|
|
74
|
+
const objectColumnLabel = (0, core_1.useCrispMessage)(objectColumnLabelProp, "Object");
|
|
75
|
+
const typeColumnLabel = (0, core_1.useCrispMessage)(typeColumnLabelProp, "Type");
|
|
76
|
+
const recordsColumnLabel = (0, core_1.useCrispMessage)(recordsColumnLabelProp, "Records");
|
|
77
|
+
const attributesColumnLabel = (0, core_1.useCrispMessage)(attributesColumnLabelProp, "Attributes");
|
|
78
|
+
const noRecordsLabel = (0, core_1.useCrispMessage)(noRecordsLabelProp, "No records");
|
|
79
|
+
const editLabel = (0, core_1.useCrispMessage)(editLabelProp, "Edit");
|
|
80
|
+
const copyIdLabel = (0, core_1.useCrispMessage)(copyIdLabelProp, "Copy ID");
|
|
81
|
+
const [query, setQuery] = React.useState("");
|
|
82
|
+
const filtered = React.useMemo(() => {
|
|
83
|
+
if (!query.trim())
|
|
84
|
+
return objects;
|
|
85
|
+
const q = query.trim().toLowerCase();
|
|
86
|
+
return objects.filter((o) => String(o.name ?? "")
|
|
87
|
+
.toLowerCase()
|
|
88
|
+
.includes(q));
|
|
89
|
+
}, [objects, query]);
|
|
90
|
+
const columns = React.useMemo(() => [
|
|
91
|
+
{
|
|
92
|
+
key: "name",
|
|
93
|
+
header: objectColumnLabel,
|
|
94
|
+
render: (_v, row) => ((0, jsx_runtime_1.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-icon", style: { background: row.iconColor ?? "currentColor" }, children: row.icon }), row.name] })),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: "typeLabel",
|
|
98
|
+
header: typeColumnLabel,
|
|
99
|
+
render: (_v, row) => ((0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-typebadge", children: row.typeLabel })),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
key: "recordCount",
|
|
103
|
+
header: recordsColumnLabel,
|
|
104
|
+
align: "left",
|
|
105
|
+
render: (_v, row) => row.recordCount ? (row.recordCount) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-muted", children: noRecordsLabel })),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
key: "attributeCount",
|
|
109
|
+
header: attributesColumnLabel,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
key: "actions",
|
|
113
|
+
header: "",
|
|
114
|
+
align: "right",
|
|
115
|
+
render: (_v, row) => ((0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": rowMenuLabel?.(row) ?? `${row.name}`, style: {
|
|
116
|
+
display: "inline-flex",
|
|
117
|
+
border: "none",
|
|
118
|
+
background: "none",
|
|
119
|
+
cursor: "pointer",
|
|
120
|
+
color: "inherit",
|
|
121
|
+
padding: 4,
|
|
122
|
+
}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 14 }), onSelect: () => onEditObject?.(row.id), children: editLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onCopyObjectId?.(row.id), children: copyIdLabel })] })),
|
|
123
|
+
},
|
|
124
|
+
], [
|
|
125
|
+
objectColumnLabel,
|
|
126
|
+
typeColumnLabel,
|
|
127
|
+
recordsColumnLabel,
|
|
128
|
+
attributesColumnLabel,
|
|
129
|
+
noRecordsLabel,
|
|
130
|
+
editLabel,
|
|
131
|
+
copyIdLabel,
|
|
132
|
+
onEditObject,
|
|
133
|
+
onCopyObjectId,
|
|
134
|
+
rowMenuLabel,
|
|
135
|
+
]);
|
|
136
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, objectsettings_recipe_1.objectsettingsRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-page", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-head", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-heading", children: [(0, jsx_runtime_1.jsx)("h1", { className: "crisp-objectsettings-title", children: title }), (0, jsx_runtime_1.jsx)("p", { className: "crisp-objectsettings-subtitle", children: subtitle })] }), onCreateObject && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-objectsettings-filterbtn", onClick: onCreateObject, children: newObjectLabel }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-toolbar", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-objectsettings-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("input", { type: "search", placeholder: searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": searchPlaceholder })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-objectsettings-filterbtn", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14 }), filterLabel] })] }), (0, jsx_runtime_1.jsx)(datatable_1.DataTable, { columns: columns, data: filtered, onRowClick: onOpenObject ? (row) => onOpenObject(row.id) : undefined, empty: emptyLabel })] }) }));
|
|
137
|
+
}
|
|
138
|
+
ObjectsList.displayName = "ObjectsList";
|
|
139
|
+
/**
|
|
140
|
+
* Settings → Objects → an object (crisp#637, P-123, verified live against
|
|
141
|
+
* .../objects/companies/general): 48px icon tile + name + type badge, a
|
|
142
|
+
* description line, then a Configuration/Attributes tab bar (Attio's own
|
|
143
|
+
* tab bar also carries Permissions/Templates/Notifications/Imports — out of
|
|
144
|
+
* scope here, see #613/#638; only the two rows this task covers are built).
|
|
145
|
+
* Tab content is a render prop, the same shape crisp's own `Tabs` already
|
|
146
|
+
* takes, so a consumer composes `ObjectGeneralSection`/
|
|
147
|
+
* `ObjectAppearanceSection` and `AttributesTable` underneath.
|
|
148
|
+
*/
|
|
149
|
+
function ObjectDetailPage({ icon, iconColor, name, typeLabel, description, tab, defaultTab = "configuration", onTabChange, attributeCount, configurationLabel: configurationLabelProp, attributesLabel: attributesLabelProp, children, }) {
|
|
150
|
+
const configurationLabel = (0, core_1.useCrispMessage)(configurationLabelProp, "Configuration");
|
|
151
|
+
const attributesLabel = (0, core_1.useCrispMessage)(attributesLabelProp, "Attributes");
|
|
152
|
+
const [internalTab, setInternalTab] = React.useState(defaultTab);
|
|
153
|
+
const active = tab ?? internalTab;
|
|
154
|
+
const setTab = (v) => {
|
|
155
|
+
const next = v;
|
|
156
|
+
if (tab === undefined)
|
|
157
|
+
setInternalTab(next);
|
|
158
|
+
onTabChange?.(next);
|
|
159
|
+
};
|
|
160
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, objectsettings_recipe_1.objectsettingsRecipe)(), children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-page", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-objectsettings-head", children: (0, jsx_runtime_1.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: 12 }, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-icon crisp-objectsettings-icon--lg", style: { background: iconColor ?? "currentColor" }, children: icon }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-heading", children: [(0, jsx_runtime_1.jsxs)("h1", { className: "crisp-objectsettings-title", children: [name, typeLabel != null && ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "neutral", size: "small", children: typeLabel }))] }), description != null && ((0, jsx_runtime_1.jsx)("p", { className: "crisp-objectsettings-subtitle", children: description }))] })] }) }), (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { value: active, onValueChange: setTab, tabs: [
|
|
161
|
+
{ value: "configuration", label: configurationLabel },
|
|
162
|
+
{
|
|
163
|
+
value: "attributes",
|
|
164
|
+
label: attributesLabel,
|
|
165
|
+
badge: attributeCount,
|
|
166
|
+
},
|
|
167
|
+
], children: (v) => children(v) })] }) }));
|
|
168
|
+
}
|
|
169
|
+
ObjectDetailPage.displayName = "ObjectDetailPage";
|
|
170
|
+
/** The Configuration tab's "General" section (crisp#637, P-123): plural/
|
|
171
|
+
* singular noun inputs side by side, then a slug input Attio locks for
|
|
172
|
+
* system objects. Composes `SettingsSection` + `FormField`, no new chrome. */
|
|
173
|
+
function ObjectGeneralSection({ pluralNoun, onPluralNounChange, singularNoun, onSingularNounChange, slug, slugEditable = false, onSlugChange, titleLabel: titleLabelProp, descriptionLabel: descriptionLabelProp, pluralLabel: pluralLabelProp, singularLabel: singularLabelProp, slugLabel: slugLabelProp, slugHelpLabel: slugHelpLabelProp, }) {
|
|
174
|
+
const titleLabel = (0, core_1.useCrispMessage)(titleLabelProp, "General");
|
|
175
|
+
const descriptionLabel = (0, core_1.useCrispMessage)(descriptionLabelProp, "Set words to describe a single and multiple objects of this type");
|
|
176
|
+
const pluralLabel = (0, core_1.useCrispMessage)(pluralLabelProp, "Plural noun");
|
|
177
|
+
const singularLabel = (0, core_1.useCrispMessage)(singularLabelProp, "Singular noun");
|
|
178
|
+
const slugLabel = (0, core_1.useCrispMessage)(slugLabelProp, "Slug");
|
|
179
|
+
const slugHelpLabel = (0, core_1.useCrispMessage)(slugHelpLabelProp, "You can't change the slug of a system object");
|
|
180
|
+
const pluralId = React.useId();
|
|
181
|
+
const singularId = React.useId();
|
|
182
|
+
const slugId = React.useId();
|
|
183
|
+
return ((0, jsx_runtime_1.jsxs)(settingssection_1.SettingsSection, { title: titleLabel, description: descriptionLabel, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-fieldrow", children: [(0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: pluralLabel, htmlFor: pluralId, children: (0, jsx_runtime_1.jsx)(input_1.Input, { id: pluralId, value: pluralNoun, onChange: (e) => onPluralNounChange?.(e.target.value) }) }), (0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: singularLabel, htmlFor: singularId, children: (0, jsx_runtime_1.jsx)(input_1.Input, { id: singularId, value: singularNoun, onChange: (e) => onSingularNounChange?.(e.target.value) }) })] }), (0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: slugLabel, htmlFor: slugId, description: !slugEditable ? slugHelpLabel : undefined, children: (0, jsx_runtime_1.jsx)(input_1.Input, { id: slugId, value: slug, disabled: !slugEditable, onChange: (e) => onSlugChange?.(e.target.value) }) })] }));
|
|
184
|
+
}
|
|
185
|
+
ObjectGeneralSection.displayName = "ObjectGeneralSection";
|
|
186
|
+
/** The Configuration tab's "Appearance" section (crisp#637, P-123): a single
|
|
187
|
+
* "Record page layout" row with a "Configure record page" button — the
|
|
188
|
+
* target flow itself (crisp's own record-page-layout editor) is a separate,
|
|
189
|
+
* unbuilt surface; this only renders the row and its click. */
|
|
190
|
+
function ObjectAppearanceSection({ onConfigureRecordPage, titleLabel: titleLabelProp, rowLabel: rowLabelProp, rowDescriptionLabel: rowDescriptionLabelProp, actionLabel: actionLabelProp, }) {
|
|
191
|
+
const titleLabel = (0, core_1.useCrispMessage)(titleLabelProp, "Appearance");
|
|
192
|
+
const rowLabel = (0, core_1.useCrispMessage)(rowLabelProp, "Record page layout");
|
|
193
|
+
const rowDescriptionLabel = (0, core_1.useCrispMessage)(rowDescriptionLabelProp, "Configure tabs, widgets, and layout for this object's record pages");
|
|
194
|
+
const actionLabel = (0, core_1.useCrispMessage)(actionLabelProp, "Configure record page");
|
|
195
|
+
return ((0, jsx_runtime_1.jsx)(settingssection_1.SettingsSection, { title: titleLabel, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-fieldrow", style: { alignItems: "center" }, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { style: { fontWeight: 500 }, children: rowLabel }), (0, jsx_runtime_1.jsx)("p", { className: "crisp-objectsettings-subtitle", children: rowDescriptionLabel })] }), onConfigureRecordPage && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-objectsettings-filterbtn", style: { justifySelf: "end" }, onClick: onConfigureRecordPage, children: actionLabel }))] }) }));
|
|
196
|
+
}
|
|
197
|
+
ObjectAppearanceSection.displayName = "ObjectAppearanceSection";
|
|
198
|
+
/**
|
|
199
|
+
* The Attributes tab's table (crisp#637, P-124, verified live): H2 +
|
|
200
|
+
* "Create attribute", a search+Filter row, then a DataTable of every
|
|
201
|
+
* attribute — drag handle (via `DataTable`'s own `onRowsReorder`) + type
|
|
202
|
+
* icon + name, Data type, a constraint label, and System (grey)/Enriched
|
|
203
|
+
* (purple) property badges. Row height is DataTable's own 36px; Attio's
|
|
204
|
+
* measures 40px here — a real, small divergence left to DataTable's owner
|
|
205
|
+
* rather than patched locally (see the PR).
|
|
206
|
+
*/
|
|
207
|
+
function AttributesTable({ attributes, onOpenAttribute, onEditAttribute, onArchiveAttribute, onReorder, onCreateAttribute, title: titleProp, subtitle: subtitleProp, createLabel: createLabelProp, searchPlaceholder: searchPlaceholderProp, filterLabel: filterLabelProp, nameColumnLabel: nameColumnLabelProp, dataTypeColumnLabel: dataTypeColumnLabelProp, constraintsColumnLabel: constraintsColumnLabelProp, propertiesColumnLabel: propertiesColumnLabelProp, systemLabel: systemLabelProp, enrichedLabel: enrichedLabelProp, editLabel: editLabelProp, archiveLabel: archiveLabelProp, rowActions, emptyLabel, }) {
|
|
208
|
+
const title = (0, core_1.useCrispMessage)(titleProp, "Attributes");
|
|
209
|
+
const subtitle = (0, core_1.useCrispMessage)(subtitleProp, "Modify and add object attributes");
|
|
210
|
+
const createLabel = (0, core_1.useCrispMessage)(createLabelProp, "Create attribute");
|
|
211
|
+
const searchPlaceholder = (0, core_1.useCrispMessage)(searchPlaceholderProp, "Search attributes");
|
|
212
|
+
const filterLabel = (0, core_1.useCrispMessage)(filterLabelProp, "Filter");
|
|
213
|
+
const nameColumnLabel = (0, core_1.useCrispMessage)(nameColumnLabelProp, "Name");
|
|
214
|
+
const dataTypeColumnLabel = (0, core_1.useCrispMessage)(dataTypeColumnLabelProp, "Data type");
|
|
215
|
+
const constraintsColumnLabel = (0, core_1.useCrispMessage)(constraintsColumnLabelProp, "Constraints");
|
|
216
|
+
const propertiesColumnLabel = (0, core_1.useCrispMessage)(propertiesColumnLabelProp, "Properties");
|
|
217
|
+
const systemLabel = (0, core_1.useCrispMessage)(systemLabelProp, "System");
|
|
218
|
+
const enrichedLabel = (0, core_1.useCrispMessage)(enrichedLabelProp, "Enriched");
|
|
219
|
+
const editLabel = (0, core_1.useCrispMessage)(editLabelProp, "Edit");
|
|
220
|
+
const archiveLabel = (0, core_1.useCrispMessage)(archiveLabelProp, "Archive");
|
|
221
|
+
const [query, setQuery] = React.useState("");
|
|
222
|
+
const filtered = React.useMemo(() => {
|
|
223
|
+
if (!query.trim())
|
|
224
|
+
return attributes;
|
|
225
|
+
const q = query.trim().toLowerCase();
|
|
226
|
+
return attributes.filter((a) => String(a.name ?? "")
|
|
227
|
+
.toLowerCase()
|
|
228
|
+
.includes(q));
|
|
229
|
+
}, [attributes, query]);
|
|
230
|
+
const columns = React.useMemo(() => [
|
|
231
|
+
{
|
|
232
|
+
key: "name",
|
|
233
|
+
header: nameColumnLabel,
|
|
234
|
+
render: (_v, row) => ((0, jsx_runtime_1.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [(0, jsx_runtime_1.jsx)("span", { style: {
|
|
235
|
+
display: "inline-flex",
|
|
236
|
+
color: "var(--crisp-fg-muted, inherit)",
|
|
237
|
+
}, children: row.icon }), row.name] })),
|
|
238
|
+
},
|
|
239
|
+
{ key: "dataType", header: dataTypeColumnLabel },
|
|
240
|
+
{
|
|
241
|
+
key: "constraint",
|
|
242
|
+
header: constraintsColumnLabel,
|
|
243
|
+
render: (_v, row) => row.constraint ?? null,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
key: "properties",
|
|
247
|
+
header: propertiesColumnLabel,
|
|
248
|
+
render: (_v, row) => ((0, jsx_runtime_1.jsxs)("span", { style: { display: "flex", gap: 6 }, children: [row.system && ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "neutral", size: "small", children: systemLabel })), row.enriched && ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "purple", size: "small", children: enrichedLabel }))] })),
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
key: "actions",
|
|
252
|
+
header: "",
|
|
253
|
+
align: "right",
|
|
254
|
+
render: (_v, row) => ((0, jsx_runtime_1.jsx)(menu_1.Menu, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": `${row.name}`, style: {
|
|
255
|
+
display: "inline-flex",
|
|
256
|
+
border: "none",
|
|
257
|
+
background: "none",
|
|
258
|
+
cursor: "pointer",
|
|
259
|
+
color: "inherit",
|
|
260
|
+
padding: 4,
|
|
261
|
+
}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16 }) }), children: rowActions ? (rowActions(row)) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onEditAttribute?.(row.id), children: editLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { danger: true, onSelect: () => onArchiveAttribute?.(row.id), children: archiveLabel })] })) })),
|
|
262
|
+
},
|
|
263
|
+
], [
|
|
264
|
+
nameColumnLabel,
|
|
265
|
+
dataTypeColumnLabel,
|
|
266
|
+
constraintsColumnLabel,
|
|
267
|
+
propertiesColumnLabel,
|
|
268
|
+
systemLabel,
|
|
269
|
+
enrichedLabel,
|
|
270
|
+
editLabel,
|
|
271
|
+
archiveLabel,
|
|
272
|
+
onEditAttribute,
|
|
273
|
+
onArchiveAttribute,
|
|
274
|
+
rowActions,
|
|
275
|
+
]);
|
|
276
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, objectsettings_recipe_1.objectsettingsRecipe)(), children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-page", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-head", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-heading", children: [(0, jsx_runtime_1.jsx)("h2", { className: "crisp-objectsettings-title", style: { fontSize: 16, lineHeight: "20px" }, children: title }), (0, jsx_runtime_1.jsx)("p", { className: "crisp-objectsettings-subtitle", children: subtitle })] }), onCreateAttribute && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-objectsettings-filterbtn", onClick: onCreateAttribute, children: createLabel }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-toolbar", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-objectsettings-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("input", { type: "search", placeholder: searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": searchPlaceholder })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-objectsettings-filterbtn", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14 }), filterLabel] })] }), (0, jsx_runtime_1.jsx)(datatable_1.DataTable, { columns: columns, data: filtered, onRowClick: onOpenAttribute ? (row) => onOpenAttribute(row.id) : undefined, onRowsReorder: onReorder, empty: emptyLabel })] }) }));
|
|
277
|
+
}
|
|
278
|
+
AttributesTable.displayName = "AttributesTable";
|
|
279
|
+
/** Attio's own "Type" group, verbatim (crisp#637, P-126) — icons are
|
|
280
|
+
* crisp's Lucide choices, not Attio's proprietary set (golden rule 4). */
|
|
281
|
+
exports.ATTRIBUTE_TYPE_OPTIONS = [
|
|
282
|
+
{ value: "checkbox", label: "Checkbox", icon: lucide_react_1.CheckSquare },
|
|
283
|
+
{ value: "currency", label: "Currency", icon: lucide_react_1.DollarSign },
|
|
284
|
+
{ value: "date", label: "Date", icon: lucide_react_1.Calendar },
|
|
285
|
+
{ value: "location", label: "Location", icon: lucide_react_1.MapPin },
|
|
286
|
+
{ value: "multiselect", label: "Multi-select", icon: lucide_react_1.ListChecks },
|
|
287
|
+
{ value: "number", label: "Number", icon: lucide_react_1.Hash },
|
|
288
|
+
{ value: "phone", label: "Phone Number", icon: lucide_react_1.Phone },
|
|
289
|
+
{ value: "rating", label: "Rating", icon: lucide_react_1.Star },
|
|
290
|
+
{ value: "record", label: "Record", icon: lucide_react_1.FileText },
|
|
291
|
+
{ value: "relationship", label: "Relationship", icon: lucide_react_1.Link2, badge: "Pro" },
|
|
292
|
+
{ value: "select", label: "Select", icon: lucide_react_1.ListChecks },
|
|
293
|
+
{ value: "status", label: "Status", icon: lucide_react_1.Flag },
|
|
294
|
+
{ value: "text", label: "Text", icon: lucide_react_1.Type },
|
|
295
|
+
{ value: "timestamp", label: "Timestamp", icon: lucide_react_1.Clock },
|
|
296
|
+
{ value: "user", label: "User", icon: lucide_react_1.User },
|
|
297
|
+
];
|
|
298
|
+
/** Attio's own "AI Autofill" group, verbatim (crisp#637, P-126) — opt in via
|
|
299
|
+
* `AttributeTypeSelect`'s `aiAutofillOptions`; omitted by default, since a
|
|
300
|
+
* consumer with no autofill backend of its own (GRC) has nothing to wire
|
|
301
|
+
* these to. */
|
|
302
|
+
exports.ATTRIBUTE_AI_AUTOFILL_OPTIONS = [
|
|
303
|
+
{ value: "classify-record", label: "Classify record", icon: lucide_react_1.Sparkles },
|
|
304
|
+
{ value: "prompt-completion", label: "Prompt completion", icon: lucide_react_1.Sparkles },
|
|
305
|
+
{ value: "summarize-record", label: "Summarize record", icon: lucide_react_1.Sparkles },
|
|
306
|
+
{ value: "web-agent", label: "Web agent", icon: lucide_react_1.Sparkles },
|
|
307
|
+
];
|
|
308
|
+
function toSelectOption(o, group) {
|
|
309
|
+
const Icon = o.icon;
|
|
310
|
+
return {
|
|
311
|
+
value: o.value,
|
|
312
|
+
keywords: o.label,
|
|
313
|
+
group,
|
|
314
|
+
label: ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-objectsettings-typeoption", children: [(0, jsx_runtime_1.jsx)(Icon, { size: 14 }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-typeoption-label", children: o.label }), o.badge != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-typeoption-badge", children: o.badge }))] })),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/** The "Attribute Type" dropdown (crisp#637, P-126, verified live): a
|
|
318
|
+
* searchable, grouped `Select` — reused as-is, not a new picker primitive.
|
|
319
|
+
* Grouping and the per-item trailing "Pro" badge are composed through
|
|
320
|
+
* `Select`'s existing `group` field and rich `label` ReactNode. */
|
|
321
|
+
function AttributeTypeSelect({ value, onValueChange, options = exports.ATTRIBUTE_TYPE_OPTIONS, aiAutofillOptions, aiAutofillGroupLabel: aiAutofillGroupLabelProp, typeGroupLabel: typeGroupLabelProp, searchPlaceholder: searchPlaceholderProp, id, }) {
|
|
322
|
+
const aiAutofillGroupLabel = (0, core_1.useCrispMessage)(aiAutofillGroupLabelProp, "AI Autofill");
|
|
323
|
+
const typeGroupLabel = (0, core_1.useCrispMessage)(typeGroupLabelProp, "Type");
|
|
324
|
+
const searchPlaceholder = (0, core_1.useCrispMessage)(searchPlaceholderProp, "Search attribute types...");
|
|
325
|
+
const selectOptions = React.useMemo(() => {
|
|
326
|
+
const ai = (aiAutofillOptions ?? []).map((o) => toSelectOption(o, aiAutofillGroupLabel));
|
|
327
|
+
const type = options.map((o) => toSelectOption(o, typeGroupLabel));
|
|
328
|
+
return [...ai, ...type];
|
|
329
|
+
}, [aiAutofillOptions, options, aiAutofillGroupLabel, typeGroupLabel]);
|
|
330
|
+
return ((0, jsx_runtime_1.jsx)(select_1.Select, { id: id, options: selectOptions, value: value, onValueChange: onValueChange, searchable: true, searchPlaceholder: searchPlaceholder, width: "full" }));
|
|
331
|
+
}
|
|
332
|
+
AttributeTypeSelect.displayName = "AttributeTypeSelect";
|
|
333
|
+
/**
|
|
334
|
+
* "Create attribute" (crisp#637, P-125, verified live): Attribute Type
|
|
335
|
+
* select (defaults to Text) + Name (autofocused) + Description (optional)
|
|
336
|
+
* + an AI-autofill switch row, footer Cancel/Create. Built on `FormDialog`
|
|
337
|
+
* in its plain (non-`createMode`) shape — Attio's own footer here has a
|
|
338
|
+
* real Cancel button, unlike the record-create dialog `createMode` models
|
|
339
|
+
* (crisp#548), so `createMode` would be the wrong fit even though the
|
|
340
|
+
* button carries the same ⌘↵ hint; that hint is rendered locally instead.
|
|
341
|
+
*/
|
|
342
|
+
function CreateAttributeDialog({ type, onTypeChange, typeOptions, aiAutofillTypeOptions, name, onNameChange, description, onDescriptionChange, aiAutofillEnabled = false, onAiAutofillChange, onSubmit, onClose, busy, error, title: titleProp, attributeTypeLabel: attributeTypeLabelProp, nameLabel: nameLabelProp, descriptionLabel: descriptionLabelProp, descriptionPlaceholder: descriptionPlaceholderProp, aiAutofillLabel: aiAutofillLabelProp, setUpAiAutofillLabel: setUpAiAutofillLabelProp, createLabel: createLabelProp, }) {
|
|
343
|
+
const title = (0, core_1.useCrispMessage)(titleProp, "Create attribute");
|
|
344
|
+
const attributeTypeLabel = (0, core_1.useCrispMessage)(attributeTypeLabelProp, "Attribute Type");
|
|
345
|
+
const nameLabel = (0, core_1.useCrispMessage)(nameLabelProp, "Name");
|
|
346
|
+
const descriptionLabel = (0, core_1.useCrispMessage)(descriptionLabelProp, "Description");
|
|
347
|
+
const descriptionPlaceholder = (0, core_1.useCrispMessage)(descriptionPlaceholderProp, "Add a description for this attribute");
|
|
348
|
+
const aiAutofillLabel = (0, core_1.useCrispMessage)(aiAutofillLabelProp, "AI autofill");
|
|
349
|
+
const setUpAiAutofillLabel = (0, core_1.useCrispMessage)(setUpAiAutofillLabelProp, "Set up AI autofill");
|
|
350
|
+
const createLabel = (0, core_1.useCrispMessage)(createLabelProp, "Create attribute");
|
|
351
|
+
const switchId = React.useId();
|
|
352
|
+
const nameId = React.useId();
|
|
353
|
+
return ((0, jsx_runtime_1.jsxs)(formdialog_1.FormDialog, { title: title, submitLabel: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [createLabel, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-objectsettings-kbd", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("kbd", { children: "\u2318" }), (0, jsx_runtime_1.jsx)("kbd", { children: "\u21B5" })] })] }), onSubmit: onSubmit, onClose: onClose, busy: busy, error: error, size: 520, className: (0, objectsettings_recipe_1.objectsettingsRecipe)(), children: [(0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: attributeTypeLabel, children: (0, jsx_runtime_1.jsx)(AttributeTypeSelect, { value: type, onValueChange: onTypeChange, options: typeOptions, aiAutofillOptions: aiAutofillTypeOptions }) }), (0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: nameLabel, htmlFor: nameId, children: (0, jsx_runtime_1.jsx)(input_1.Input, { id: nameId, autoFocus: true, value: name, onChange: (e) => onNameChange(e.target.value) }) }), (0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: descriptionLabel, optional: true, children: (0, jsx_runtime_1.jsx)(textarea_1.Textarea, { placeholder: descriptionPlaceholder, value: description, onChange: (e) => onDescriptionChange(e.target.value) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-aiautofill", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-aiautofill-label", children: aiAutofillLabel }), (0, jsx_runtime_1.jsxs)("label", { className: "crisp-objectsettings-aiautofill-row", htmlFor: switchId, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14 }), (0, jsx_runtime_1.jsx)("span", { children: setUpAiAutofillLabel }), (0, jsx_runtime_1.jsx)(switch_1.Switch, { id: switchId, checked: aiAutofillEnabled, onCheckedChange: onAiAutofillChange })] })] })] }));
|
|
354
|
+
}
|
|
355
|
+
CreateAttributeDialog.displayName = "CreateAttributeDialog";
|
|
356
|
+
/**
|
|
357
|
+
* This folder holds several components (`ObjectsList`, `ObjectDetailPage`,
|
|
358
|
+
* `AttributesTable`, `CreateAttributeDialog`, …), not one named after the
|
|
359
|
+
* slug — unlike every other product dir, whose docs `<ManualInstallation>`
|
|
360
|
+
* resolves an import binding from the folder slug ("avatargroup" →
|
|
361
|
+
* `AvatarGroup`) by reading this file's exports. This alias exists only so
|
|
362
|
+
* that resolver finds a match for "objectsettings"; consumers should import
|
|
363
|
+
* the real names above, never this one.
|
|
364
|
+
*/
|
|
365
|
+
exports.ObjectSettings = ObjectsList;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.objectsettingsRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// Measured live against app.attio.com/<workspace>/settings/data/objects and
|
|
7
|
+
// .../objects/companies/{general,attributes} (crisp#637, P-122/P-123/P-124/
|
|
8
|
+
// P-125/P-126). Values with no matching scale token are one-off literals,
|
|
9
|
+
// same precedent as authshell's own 20px title (crisp#637 golden rule 3):
|
|
10
|
+
// the 24/600/28/-0.48 page title, the 12px/16px badge dot sizes, and the
|
|
11
|
+
// 30% squircle radius on the object-icon tile.
|
|
12
|
+
const config = (0, core_1.defineRecipe)({
|
|
13
|
+
name: "objectsettings",
|
|
14
|
+
base: `& .crisp-objectsettings-page { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["6"]}; }
|
|
15
|
+
& .crisp-objectsettings-head { display: flex; align-items: flex-start; justify-content: space-between; gap: ${tokens_1.contract.space["4"]}; }
|
|
16
|
+
& .crisp-objectsettings-heading { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["0.5"]}; min-width: 0; }
|
|
17
|
+
& .crisp-objectsettings-title { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; font-size: 24px; line-height: 28px; font-weight: ${tokens_1.contract.weight.semibold}; letter-spacing: -0.48px; color: ${tokens_1.contract.fgPrimary}; margin: 0; }
|
|
18
|
+
& .crisp-objectsettings-subtitle { font-size: ${tokens_1.contract.text.lg}; line-height: 20px; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; margin: 0; }
|
|
19
|
+
|
|
20
|
+
& .crisp-objectsettings-toolbar { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; }
|
|
21
|
+
& .crisp-objectsettings-search { display: flex; align-items: center; flex: 1; gap: ${tokens_1.contract.space["1.5"]}; height: ${tokens_1.contract.size.menuRow}; padding: 0 ${tokens_1.contract.space["2.5"]}; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderDefault}; color: ${tokens_1.contract.fgMuted}; transition: box-shadow .15s; }
|
|
22
|
+
& .crisp-objectsettings-search:focus-within { box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; }
|
|
23
|
+
& .crisp-objectsettings-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgPrimary}; }
|
|
24
|
+
& .crisp-objectsettings-search input::placeholder { color: ${tokens_1.contract.fgMuted}; }
|
|
25
|
+
& .crisp-objectsettings-filterbtn { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; height: ${tokens_1.contract.size.menuRow}; padding: 0 ${tokens_1.contract.space["2.5"]} 0 ${tokens_1.contract.space["2"]}; border: none; border-radius: ${tokens_1.contract.radius.lg}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderDefault}; color: ${tokens_1.contract.fgPrimary}; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; cursor: pointer; white-space: nowrap; }
|
|
26
|
+
& .crisp-objectsettings-filterbtn:hover { background: ${tokens_1.contract.bgRaised}; }
|
|
27
|
+
|
|
28
|
+
/* Object / attribute icon tile — Attio's own squircle (measured
|
|
29
|
+
border-radius: 30%, not a scale token: it's a proportional radius
|
|
30
|
+
tied to the tile's own size, unlike every other radius crisp uses,
|
|
31
|
+
which is a fixed px). */
|
|
32
|
+
& .crisp-objectsettings-icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 16px; height: 16px; border-radius: 30%; color: ${tokens_1.contract.fgOnBrand}; }
|
|
33
|
+
& .crisp-objectsettings-icon--lg { width: 48px; height: 48px; border-radius: 16px; }
|
|
34
|
+
|
|
35
|
+
/* "Standard"/"Custom" type badge and the muted "No records" cell —
|
|
36
|
+
plain text, not a Badge pill (measured: transparent background). */
|
|
37
|
+
& .crisp-objectsettings-typebadge { font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgNeutralSoft}; }
|
|
38
|
+
& .crisp-objectsettings-muted { color: ${tokens_1.contract.fgMuted}; }
|
|
39
|
+
|
|
40
|
+
/* Attribute-type option row (icon + label + optional trailing badge),
|
|
41
|
+
composed as a Select option's \`label\` ReactNode. */
|
|
42
|
+
& .crisp-objectsettings-typeoption { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; width: 100%; min-width: 0; }
|
|
43
|
+
& .crisp-objectsettings-typeoption-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
44
|
+
& .crisp-objectsettings-typeoption-badge { flex-shrink: 0; font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgBrandSoft}; }
|
|
45
|
+
|
|
46
|
+
/* Create-attribute dialog's AI-autofill row and the submit button's
|
|
47
|
+
⌘↵ hint (measured off the same dialog, same shape as Dialog's own
|
|
48
|
+
.crisp-dialog-submit-key, kept local rather than reaching into
|
|
49
|
+
another component's scoped classes). */
|
|
50
|
+
& .crisp-objectsettings-aiautofill { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["1.5"]}; }
|
|
51
|
+
& .crisp-objectsettings-aiautofill-label { font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; }
|
|
52
|
+
& .crisp-objectsettings-aiautofill-row { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: ${tokens_1.contract.size.menuRow}; padding: 0 ${tokens_1.contract.space["2.5"]}; border-radius: ${tokens_1.contract.radius.md}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderDefault}; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgPrimary}; cursor: pointer; }
|
|
53
|
+
& .crisp-objectsettings-aiautofill-row > svg { flex-shrink: 0; color: ${tokens_1.contract.fgMuted}; }
|
|
54
|
+
& .crisp-objectsettings-aiautofill-row > span { flex: 1; }
|
|
55
|
+
& .crisp-objectsettings-kbd { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; margin-inline-start: ${tokens_1.contract.space["1"]}; }
|
|
56
|
+
& .crisp-objectsettings-kbd kbd { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0 3px; border-radius: 4px; background: transparent; color: inherit; font-family: inherit; font-style: normal; font-size: 10px; }
|
|
57
|
+
|
|
58
|
+
& .crisp-objectsettings-fieldrow { display: grid; grid-template-columns: 1fr 1fr; gap: ${tokens_1.contract.space["4"]}; }
|
|
59
|
+
`,
|
|
60
|
+
variants: {},
|
|
61
|
+
defaultVariants: {},
|
|
62
|
+
compoundVariants: [],
|
|
63
|
+
});
|
|
64
|
+
exports.objectsettingsRecipe = (0, core_1.resolveRecipe)(config);
|
|
65
|
+
exports.default = config;
|
|
@@ -26,7 +26,16 @@ const config = (0, core_1.defineRecipe)({
|
|
|
26
26
|
& .crisp-taskstable-colhead-cell { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; min-width: 0; }
|
|
27
27
|
|
|
28
28
|
& .crisp-taskstable-row { display: flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; }
|
|
29
|
-
|
|
29
|
+
/* #642: measured live -- title, due date and assignee name are ALL
|
|
30
|
+
14px/500 on Attio's own Tasks page. Only -title/-due set their own
|
|
31
|
+
font-size (and only -title its own weight), so an unstyled consumer
|
|
32
|
+
slot (a plain PersonChip/RecordChip name span with no font-size of
|
|
33
|
+
its own) fell through to the browser/body default, 16px/400. Set
|
|
34
|
+
the ambient font here so it cascades to any slot content that
|
|
35
|
+
doesn't override it -- -title/-due's own declarations still apply,
|
|
36
|
+
unchanged, they're just now redundant with this baseline rather
|
|
37
|
+
than the only source of it. */
|
|
38
|
+
& .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; }
|
|
30
39
|
|
|
31
40
|
& .crisp-taskstable-check { appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: ${tokens_1.contract.radius.full}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; }
|
|
32
41
|
& .crisp-taskstable-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 30%, transparent); }
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.crisp-comments {
|
|
2
|
+
display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
3
|
+
& * { box-sizing: border-box; }
|
|
4
|
+
& .crisp-comments-head { display: flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; height: 49px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); }
|
|
5
|
+
& .crisp-comments-headbtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
|
|
6
|
+
& .crisp-comments-headbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
7
|
+
& .crisp-comments-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
8
|
+
& .crisp-comments-title { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
9
|
+
& .crisp-comments-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
10
|
+
& .crisp-comments-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
11
|
+
& .crisp-comments-body--pad-top { padding-top: var(--crisp-space-3); }
|
|
12
|
+
/* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line. */
|
|
13
|
+
& .crisp-comments-row { position: relative; display: flex; gap: var(--crisp-space-1-5); }
|
|
14
|
+
& .crisp-comments-row-main { flex: 1; min-width: 0; }
|
|
15
|
+
& .crisp-comments-row-head { display: flex; align-items: baseline; gap: var(--crisp-space-1-5); height: 20px; }
|
|
16
|
+
& .crisp-comments-author { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
|
|
17
|
+
& .crisp-comments-time { font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
|
|
18
|
+
& .crisp-comments-resolved-label { display: flex; align-items: center; gap: var(--crisp-space-1); margin-bottom: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
|
|
19
|
+
& .crisp-comments-body-text { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
|
|
20
|
+
/* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
|
|
21
|
+
& .crisp-comments-row-actions { position: absolute; top: 0; inset-inline-end: 0; display: flex; opacity: 0; pointer-events: none; background: var(--crisp-bg-surface); }
|
|
22
|
+
& .crisp-comments-row:hover .crisp-comments-row-actions, & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
|
|
23
|
+
& .crisp-comments-action { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
|
|
24
|
+
& .crisp-comments-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
25
|
+
& .crisp-comments-action[data-active] { color: var(--crisp-bg-brand-solid); }
|
|
26
|
+
/* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill, padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
|
|
27
|
+
& .crisp-comments-reactions { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-1-5); }
|
|
28
|
+
& .crisp-comments-reaction { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 28px; padding: 4px 8px 4px 6px; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: 12px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; }
|
|
29
|
+
& .crisp-comments-reaction[data-reacted] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
|
|
30
|
+
& .crisp-comments-reaction-add { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: var(--crisp-radius-md); background: transparent; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; opacity: 0; transition: opacity .12s, background-color .12s; }
|
|
31
|
+
& .crisp-comments-row:hover .crisp-comments-reaction-add { opacity: 1; }
|
|
32
|
+
& .crisp-comments-reaction-add:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
33
|
+
/* Reply-in-thread view -- an indented block of replies below the root. */
|
|
34
|
+
& .crisp-comments-replies { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); padding-inline-start: var(--crisp-space-4); border-inline-start: 1px solid var(--crisp-border-subtle); }
|
|
35
|
+
& .crisp-comments-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--crisp-space-1); padding: var(--crisp-space-6) var(--crisp-space-4); text-align: center; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-size: var(--crisp-text-lg); }
|
|
36
|
+
/* Composer -- measured: 14px/20px body text, 16px gap above the reply/mention icon row, Comment button 84x32 padding 6px 10px radius 9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4 disabled / 1 enabled (no color/background change between the two). */
|
|
37
|
+
& .crisp-comments-composer { flex-shrink: 0; padding: var(--crisp-space-3) var(--crisp-space-4) var(--crisp-space-4); border-top: 1px solid var(--crisp-border-subtle); }
|
|
38
|
+
& .crisp-comments-field { min-height: 20px; max-height: 120px; overflow-y: auto; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); outline: none; white-space: pre-wrap; word-break: break-word; }
|
|
39
|
+
& .crisp-comments-field:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: 2px; }
|
|
40
|
+
& .crisp-comments-field:empty::before { content: attr(data-placeholder); color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
|
|
41
|
+
& .crisp-comments-composer-foot { display: flex; align-items: center; justify-content: space-between; margin-top: var(--crisp-space-4); }
|
|
42
|
+
& .crisp-comments-composer-tools { display: flex; align-items: center; gap: var(--crisp-space-1); }
|
|
43
|
+
& .crisp-comments-tool { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); font-family: inherit; cursor: pointer; }
|
|
44
|
+
& .crisp-comments-tool:hover { color: var(--crisp-fg-primary); }
|
|
45
|
+
/* Same fill/shadow as Button's own intent="primary" (crisp/button.recipe.ts) --
|
|
46
|
+
confirmed byte-identical against the live "Comment" button's computed box-shadow, so this reuses that literal rather than re-deriving it. 32px/radius.lg/6px-10px padding has no matching Button `size` yet (24/28/40 are the three that exist), so this stays hand-rolled instead of stretching one of those. */
|
|
47
|
+
& .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-brand-solid); box-shadow: 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); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-on-brand); cursor: pointer; opacity: 1; transition: opacity .12s, background-color .12s; }
|
|
48
|
+
& .crisp-comments-post:hover:not(:disabled) { background: var(--crisp-bg-brand-solid-pressed); }
|
|
49
|
+
& .crisp-comments-post:disabled { opacity: .4; cursor: default; }
|
|
50
|
+
/* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding, elevation.floating -- a plain reuse of crisp's own Menu, so no rules
|
|
51
|
+
live here; kept as a comment for the record. */
|
|
52
|
+
|
|
53
|
+
/* An inserted "@mention" chip in the composer -- same shape as
|
|
54
|
+
RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
|
|
55
|
+
& .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
|
|
56
|
+
|
|
57
|
+
/* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact chrome wasn't independently pixel-measured this pass (screenshot
|
|
58
|
+
capture broke mid-session), so this reuses Menu's already-measured floating-popover shell rather than a new value. */
|
|
59
|
+
& .crisp-comments-reactionpicker { width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
|
|
60
|
+
& .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
|
|
61
|
+
& .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
|
|
62
|
+
& .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
|
|
63
|
+
& .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
|
|
64
|
+
& .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
|
|
65
|
+
/* Row badge (CommentCountBadge) -- lives in its own class family so the grid lanes can drop it into a cell without pulling in the panel's
|
|
66
|
+
CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
|
|
67
|
+
icon + 12px count text, both at 55% ink, replacing the dot. */
|
|
68
|
+
& .crisp-comments-badge { display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
|
|
69
|
+
& .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
|
|
70
|
+
& .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
|
|
71
|
+
[dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@media (prefers-reduced-motion: reduce) {
|
|
75
|
+
.crisp-comments, .crisp-comments * {
|
|
76
|
+
animation-duration: 0.01ms !important;
|
|
77
|
+
animation-iteration-count: 1 !important;
|
|
78
|
+
transition-duration: 0.01ms !important;
|
|
79
|
+
}
|
|
80
|
+
}
|