@crisp-ui-kit/crisp 0.57.1 → 0.57.3
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/authshell.css +9 -4
- package/dist/authshell.layered.css +9 -4
- package/dist/badge.css +3 -3
- package/dist/badge.layered.css +3 -3
- package/dist/board.css +28 -8
- package/dist/board.layered.css +28 -8
- package/dist/bottomsheet.css +26 -0
- package/dist/bottomsheet.d.ts +7 -0
- package/dist/bottomsheet.layered.css +28 -0
- package/dist/button.css +10 -6
- package/dist/button.d.ts +2 -2
- package/dist/button.layered.css +10 -6
- package/dist/checkbox.css +7 -3
- package/dist/checkbox.layered.css +7 -3
- package/dist/chip.css +16 -1
- package/dist/chip.d.ts +3 -1
- package/dist/chip.layered.css +16 -1
- package/dist/chipgroup.css +5 -0
- package/dist/chipgroup.d.ts +7 -0
- package/dist/chipgroup.layered.css +7 -0
- package/dist/cjs/components/badge/badge.recipe.js +22 -8
- package/dist/cjs/components/bottomsheet/bottomsheet.js +90 -0
- package/dist/cjs/components/bottomsheet/bottomsheet.recipe.js +77 -0
- package/dist/cjs/components/bottomsheet/index.js +9 -0
- package/dist/cjs/components/breadcrumb/breadcrumb.js +1 -1
- package/dist/cjs/components/button/button.recipe.js +67 -15
- package/dist/cjs/components/checkbox/checkbox.recipe.js +61 -9
- package/dist/cjs/components/chip/chip.js +14 -2
- package/dist/cjs/components/chip/chip.recipe.js +72 -2
- package/dist/cjs/components/chipgroup/chipgroup.js +51 -0
- package/dist/cjs/components/chipgroup/chipgroup.recipe.js +36 -0
- package/dist/cjs/components/chipgroup/index.js +7 -0
- package/dist/cjs/components/datatable/datatable.js +5 -1
- package/dist/cjs/components/datatable/datatable.recipe.js +21 -6
- package/dist/cjs/components/datepickerfield/datepickerfield.js +2 -2
- package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +16 -3
- package/dist/cjs/components/dialog/dialog.recipe.js +21 -10
- package/dist/cjs/components/emptystate/emptystate.recipe.js +7 -2
- package/dist/cjs/components/fileupload/fileupload.js +21 -4
- package/dist/cjs/components/input/index.js +2 -1
- package/dist/cjs/components/input/input.js +31 -1
- package/dist/cjs/components/input/input.recipe.js +16 -9
- package/dist/cjs/components/menu/menu.js +18 -2
- package/dist/cjs/components/menu/menu.recipe.js +21 -7
- package/dist/cjs/components/menusheet/index.js +9 -0
- package/dist/cjs/components/menusheet/menusheet.js +133 -0
- package/dist/cjs/components/menusheet/menusheet.recipe.js +103 -0
- package/dist/cjs/components/multiselect/multiselect.js +1 -1
- package/dist/cjs/components/multiselect/multiselect.recipe.js +33 -9
- package/dist/cjs/components/select/select.js +1 -1
- package/dist/cjs/components/select/select.recipe.js +38 -12
- package/dist/cjs/components/snackbar/index.js +8 -0
- package/dist/cjs/components/snackbar/snackbar.js +121 -0
- package/dist/cjs/components/snackbar/snackbar.recipe.js +71 -0
- package/dist/cjs/components/table/table.recipe.js +7 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +37 -12
- package/dist/cjs/components/tabs/tabs.js +10 -4
- package/dist/cjs/components/tabs/tabs.recipe.js +23 -6
- package/dist/cjs/components/textarea/textarea.recipe.js +11 -8
- package/dist/cjs/core/floating-portal.js +7 -4
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/product/authshell/authshell.recipe.js +5 -1
- package/dist/cjs/product/command/command.recipe.js +4 -1
- package/dist/cjs/product/comments/comments.js +7 -2
- package/dist/cjs/product/comments/comments.recipe.js +25 -7
- package/dist/cjs/product/inbox/inbox.js +11 -3
- package/dist/cjs/product/memberssettings/index.js +6 -0
- package/dist/cjs/product/memberssettings/memberssettings.js +99 -0
- package/dist/cjs/product/memberssettings/memberssettings.recipe.js +83 -0
- package/dist/cjs/product/notificationssettings/index.js +5 -0
- package/dist/cjs/product/notificationssettings/notificationssettings.js +19 -0
- package/dist/cjs/product/notificationssettings/notificationssettings.recipe.js +44 -0
- package/dist/cjs/product/recordpage/index.js +2 -1
- package/dist/cjs/product/recordpage/recordpage.js +106 -9
- package/dist/cjs/product/recordpage/recordpage.recipe.js +68 -6
- package/dist/cjs/product/workspacegeneralsettings/index.js +5 -0
- package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.js +21 -0
- package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +51 -0
- package/dist/cjs/tokens/contract.js +1 -0
- package/dist/cjs/tokens/elevation.js +8 -5
- package/dist/cjs/tokens/palette.js +28 -8
- package/dist/cjs/tokens/scale.js +44 -2
- package/dist/cjs/tokens/semantic.js +2 -0
- package/dist/columncalc.css +15 -7
- package/dist/columncalc.layered.css +15 -7
- package/dist/command.css +23 -19
- package/dist/command.layered.css +23 -19
- package/dist/comments.css +28 -19
- package/dist/comments.d.ts +2 -2
- package/dist/comments.layered.css +28 -19
- package/dist/datatable.css +171 -71
- package/dist/datatable.layered.css +171 -71
- package/dist/datepicker.css +13 -41
- package/dist/datepicker.layered.css +13 -41
- package/dist/datepickerfield.css +12 -3
- package/dist/datepickerfield.d.ts +2 -2
- package/dist/datepickerfield.layered.css +12 -3
- package/dist/dialog.css +12 -7
- package/dist/dialog.layered.css +12 -7
- package/dist/emptystate.css +5 -3
- package/dist/emptystate.layered.css +5 -3
- package/dist/esm/components/badge/badge.recipe.js +22 -8
- package/dist/esm/components/bottomsheet/bottomsheet.js +52 -0
- package/dist/esm/components/bottomsheet/bottomsheet.recipe.js +74 -0
- package/dist/esm/components/bottomsheet/index.js +2 -0
- package/dist/esm/components/breadcrumb/breadcrumb.js +1 -1
- package/dist/esm/components/button/button.recipe.js +68 -16
- package/dist/esm/components/checkbox/checkbox.recipe.js +61 -9
- package/dist/esm/components/chip/chip.js +14 -2
- package/dist/esm/components/chip/chip.recipe.js +72 -2
- package/dist/esm/components/chipgroup/chipgroup.js +15 -0
- package/dist/esm/components/chipgroup/chipgroup.recipe.js +33 -0
- package/dist/esm/components/chipgroup/index.js +2 -0
- package/dist/esm/components/datatable/datatable.js +5 -1
- package/dist/esm/components/datatable/datatable.recipe.js +21 -6
- package/dist/esm/components/datepickerfield/datepickerfield.js +2 -2
- package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +16 -3
- package/dist/esm/components/dialog/dialog.recipe.js +22 -11
- package/dist/esm/components/emptystate/emptystate.recipe.js +7 -2
- package/dist/esm/components/fileupload/fileupload.js +20 -5
- package/dist/esm/components/input/index.js +1 -1
- package/dist/esm/components/input/input.js +30 -0
- package/dist/esm/components/input/input.recipe.js +17 -10
- package/dist/esm/components/menu/menu.js +18 -2
- package/dist/esm/components/menu/menu.recipe.js +21 -7
- package/dist/esm/components/menusheet/index.js +2 -0
- package/dist/esm/components/menusheet/menusheet.js +95 -0
- package/dist/esm/components/menusheet/menusheet.recipe.js +100 -0
- package/dist/esm/components/multiselect/multiselect.js +1 -1
- package/dist/esm/components/multiselect/multiselect.recipe.js +33 -9
- package/dist/esm/components/select/select.js +1 -1
- package/dist/esm/components/select/select.recipe.js +38 -12
- package/dist/esm/components/snackbar/index.js +2 -0
- package/dist/esm/components/snackbar/snackbar.js +84 -0
- package/dist/esm/components/snackbar/snackbar.recipe.js +68 -0
- package/dist/esm/components/table/table.recipe.js +7 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +37 -12
- package/dist/esm/components/tabs/tabs.js +10 -4
- package/dist/esm/components/tabs/tabs.recipe.js +23 -6
- package/dist/esm/components/textarea/textarea.recipe.js +12 -9
- package/dist/esm/core/floating-portal.js +7 -4
- package/dist/esm/index.js +7 -0
- package/dist/esm/product/authshell/authshell.recipe.js +5 -1
- package/dist/esm/product/command/command.recipe.js +4 -1
- package/dist/esm/product/comments/comments.js +7 -2
- package/dist/esm/product/comments/comments.recipe.js +25 -7
- package/dist/esm/product/inbox/inbox.js +11 -3
- package/dist/esm/product/memberssettings/index.js +1 -0
- package/dist/esm/product/memberssettings/memberssettings.js +62 -0
- package/dist/esm/product/memberssettings/memberssettings.recipe.js +80 -0
- package/dist/esm/product/notificationssettings/index.js +1 -0
- package/dist/esm/product/notificationssettings/notificationssettings.js +16 -0
- package/dist/esm/product/notificationssettings/notificationssettings.recipe.js +41 -0
- package/dist/esm/product/recordpage/index.js +1 -1
- package/dist/esm/product/recordpage/recordpage.js +106 -10
- package/dist/esm/product/recordpage/recordpage.recipe.js +68 -6
- package/dist/esm/product/workspacegeneralsettings/index.js +1 -0
- package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.js +18 -0
- package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +48 -0
- package/dist/esm/tokens/contract.js +2 -1
- package/dist/esm/tokens/elevation.js +8 -5
- package/dist/esm/tokens/palette.js +28 -8
- package/dist/esm/tokens/scale.js +43 -1
- package/dist/esm/tokens/semantic.js +2 -0
- package/dist/fileupload.css +3 -1
- package/dist/fileupload.layered.css +3 -1
- package/dist/input.css +6 -4
- package/dist/input.layered.css +6 -4
- package/dist/listpage.css +3 -1
- package/dist/listpage.layered.css +3 -1
- package/dist/memberssettings.css +67 -0
- package/dist/memberssettings.d.ts +7 -0
- package/dist/memberssettings.layered.css +69 -0
- package/dist/menu.css +2 -2
- package/dist/menu.layered.css +2 -2
- package/dist/menusheet.css +36 -0
- package/dist/menusheet.d.ts +7 -0
- package/dist/menusheet.layered.css +38 -0
- package/dist/multiselect.css +13 -6
- package/dist/multiselect.layered.css +13 -6
- package/dist/native/tokens.d.ts +2 -1
- package/dist/native/tokens.js +20 -3
- package/dist/notificationssettings.css +22 -0
- package/dist/notificationssettings.d.ts +7 -0
- package/dist/notificationssettings.layered.css +24 -0
- package/dist/objectsettings.css +4 -4
- package/dist/objectsettings.layered.css +4 -4
- package/dist/recordpage.css +90 -23
- package/dist/recordpage.layered.css +90 -23
- package/dist/recordpanel.css +35 -16
- package/dist/recordpanel.layered.css +35 -16
- package/dist/resizablepanel.css +8 -5
- package/dist/resizablepanel.layered.css +8 -5
- package/dist/richtext.css +24 -8
- package/dist/richtext.layered.css +24 -8
- package/dist/savedviews.css +9 -2
- package/dist/savedviews.layered.css +9 -2
- package/dist/select.css +25 -5
- package/dist/select.layered.css +25 -5
- package/dist/settingsshell.css +2 -3
- package/dist/settingsshell.layered.css +2 -3
- package/dist/slider.css +6 -27
- package/dist/slider.layered.css +6 -27
- package/dist/snackbar.css +24 -0
- package/dist/snackbar.d.ts +7 -0
- package/dist/snackbar.layered.css +26 -0
- package/dist/statcard.css +3 -1
- package/dist/statcard.layered.css +3 -1
- package/dist/styles.css +911 -354
- package/dist/styles.layered.css +911 -354
- package/dist/table.css +35 -12
- package/dist/table.layered.css +35 -12
- package/dist/tabletoolbar.css +35 -24
- package/dist/tabletoolbar.layered.css +35 -24
- package/dist/tabs.css +18 -4
- package/dist/tabs.layered.css +18 -4
- package/dist/textarea.css +2 -6
- package/dist/textarea.layered.css +2 -6
- package/dist/themepicker.css +2 -1
- package/dist/themepicker.layered.css +2 -1
- package/dist/tokens.css +13 -2
- package/dist/tokens.json +20 -3
- package/dist/types/components/bottomsheet/bottomsheet.d.ts +35 -0
- package/dist/types/components/bottomsheet/bottomsheet.recipe.d.ts +3 -0
- package/dist/types/components/bottomsheet/index.d.ts +2 -0
- package/dist/types/components/breadcrumb/breadcrumb.d.ts +9 -1
- package/dist/types/components/button/button.d.ts +2 -0
- package/dist/types/components/button/button.recipe.d.ts +24 -6
- package/dist/types/components/checkbox/checkbox.recipe.d.ts +28 -5
- package/dist/types/components/chip/chip.d.ts +22 -0
- package/dist/types/components/chip/chip.recipe.d.ts +49 -1
- package/dist/types/components/chipgroup/chipgroup.d.ts +14 -0
- package/dist/types/components/chipgroup/chipgroup.recipe.d.ts +5 -0
- package/dist/types/components/chipgroup/index.d.ts +2 -0
- package/dist/types/components/datatable/datatable.d.ts +19 -0
- package/dist/types/components/datepickerfield/datepickerfield.d.ts +4 -1
- package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +18 -3
- package/dist/types/components/fileupload/fileupload.d.ts +26 -0
- package/dist/types/components/input/index.d.ts +1 -1
- package/dist/types/components/input/input.d.ts +72 -0
- package/dist/types/components/menusheet/index.d.ts +2 -0
- package/dist/types/components/menusheet/menusheet.d.ts +42 -0
- package/dist/types/components/menusheet/menusheet.recipe.d.ts +3 -0
- package/dist/types/components/snackbar/index.d.ts +2 -0
- package/dist/types/components/snackbar/snackbar.d.ts +36 -0
- package/dist/types/components/snackbar/snackbar.recipe.d.ts +5 -0
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +28 -1
- package/dist/types/components/tabs/tabs.d.ts +11 -1
- package/dist/types/core/floating-portal.d.ts +6 -5
- package/dist/types/index.d.ts +7 -0
- package/dist/types/product/comments/comments.d.ts +9 -3
- package/dist/types/product/comments/comments.recipe.d.ts +12 -2
- package/dist/types/product/inbox/inbox.d.ts +13 -1
- package/dist/types/product/memberssettings/index.d.ts +1 -0
- package/dist/types/product/memberssettings/memberssettings.d.ts +85 -0
- package/dist/types/product/memberssettings/memberssettings.recipe.d.ts +3 -0
- package/dist/types/product/notificationssettings/index.d.ts +1 -0
- package/dist/types/product/notificationssettings/notificationssettings.d.ts +37 -0
- package/dist/types/product/notificationssettings/notificationssettings.recipe.d.ts +3 -0
- package/dist/types/product/recordpage/index.d.ts +1 -1
- package/dist/types/product/recordpage/recordpage.d.ts +99 -2
- package/dist/types/product/workspacegeneralsettings/index.d.ts +1 -0
- package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.d.ts +48 -0
- package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.recipe.d.ts +3 -0
- package/dist/types/tokens/contract.d.ts +4 -1
- package/dist/types/tokens/elevation.d.ts +8 -5
- package/dist/types/tokens/palette.d.ts +9 -1
- package/dist/types/tokens/scale.d.ts +11 -1
- package/dist/types/tokens/semantic.d.ts +2 -0
- package/dist/wheelpicker.css +4 -2
- package/dist/wheelpicker.layered.css +4 -2
- package/dist/workspacegeneralsettings.css +30 -0
- package/dist/workspacegeneralsettings.d.ts +7 -0
- package/dist/workspacegeneralsettings.layered.css +32 -0
- package/package.json +36 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Activity as ActivityIcon, CalendarClock, ChevronDown, CircleDollarSign, Copy, DollarSign, FileText, Folder, Inbox, Landmark, LayoutGrid, Mail, Maximize2, Merge, MessageSquare, Minimize2, MoreHorizontal, Pencil, Phone, Plus, Search, Settings, Sparkles, SquareCheck, Star, Trash2, Users, UsersRound, Workflow, X, Zap, } from "lucide-react";
|
|
2
|
+
import { Activity as ActivityIcon, CalendarClock, ChevronDown, ChevronUp, CircleDollarSign, Copy, DollarSign, FileText, Folder, Inbox, Landmark, LayoutGrid, ListFilter, Mail, Maximize2, Merge, MessageSquare, Minimize2, MoreHorizontal, Pencil, Phone, Plus, Search, Settings, Sparkles, SquareCheck, Star, Trash2, Users, UsersRound, Workflow, X, Zap, } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Avatar } from "../../components/avatar/index.js";
|
|
5
|
+
import { Button } from "../../components/button/index.js";
|
|
5
6
|
import { Checkbox } from "../../components/checkbox/index.js";
|
|
6
7
|
import { Drawer } from "../../components/drawer/index.js";
|
|
7
8
|
import { Menu, MenuItem, MenuSeparator } from "../../components/menu/index.js";
|
|
@@ -62,6 +63,22 @@ export const recordPageLabels = {
|
|
|
62
63
|
share: "Share",
|
|
63
64
|
comments: "Comments",
|
|
64
65
|
noLists: "This record has not been added to any lists.",
|
|
66
|
+
filters: "Filters",
|
|
67
|
+
addMeeting: "Add meeting",
|
|
68
|
+
noFiles: "No files",
|
|
69
|
+
filesHint: "Drag a file here or choose one from your computer.",
|
|
70
|
+
newFolder: "New folder",
|
|
71
|
+
uploadFile: "Upload file",
|
|
72
|
+
fileMoreActions: "More file actions",
|
|
73
|
+
noDealsFound: "No Deals found",
|
|
74
|
+
noDealsHint: "There are no related records for this relationship.",
|
|
75
|
+
newDeal: "New Deal",
|
|
76
|
+
noEmails: "No emails",
|
|
77
|
+
noEmailsHint: "This record doesn't have any emails, or they may be hidden due to permissions.",
|
|
78
|
+
configureMailbox: "Configure mailbox",
|
|
79
|
+
upgradeToViewCalls: "Upgrade to view calls",
|
|
80
|
+
callsUpgradeHint: "No call recordings yet! Upgrade to Pro to start recording calls.",
|
|
81
|
+
upgrade: "Upgrade",
|
|
65
82
|
highlights: "Highlights",
|
|
66
83
|
recordDetails: "Record Details",
|
|
67
84
|
searchFields: "Search fields",
|
|
@@ -273,7 +290,14 @@ function NotesSection({ notes, onAdd, labels, }) {
|
|
|
273
290
|
return (_jsxs("section", { children: [_jsx(SectionHead, { title: labels.tabs.notes, count: notes.length, action: _jsxs("button", { type: "button", className: "crisp-recordpage-addbtn", onClick: () => setOpen(true), children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), labels.newNote] }) }), open && (_jsx(Composer, { labels: labels, multiline: true, placeholder: labels.notePlaceholder, submitLabel: labels.saveNote, onCancel: () => setOpen(false), onSubmit: (t) => {
|
|
274
291
|
onAdd(t);
|
|
275
292
|
setOpen(false);
|
|
276
|
-
} })), notes.length === 0 && !open ? (_jsx("div", { className: "crisp-recordpage-emptystate", children: labels.noNotesYet })) : (
|
|
293
|
+
} })), notes.length === 0 && !open ? (_jsx("div", { className: "crisp-recordpage-emptystate", children: labels.noNotesYet })) : (
|
|
294
|
+
// #871: one line, not a two-row card -- measured live: avatar,
|
|
295
|
+
// then the note's own title and body run together inline, then a
|
|
296
|
+
// relative time at the far end of the SAME row. No author name at
|
|
297
|
+
// all (Attio's real preview never shows one here, only the note's
|
|
298
|
+
// own content) -- `labels.you` stays defined for a consumer that
|
|
299
|
+
// still wants it elsewhere, just unused in this exact spot now.
|
|
300
|
+
notes.map((n) => (_jsxs("div", { className: "crisp-recordpage-note", children: [_jsxs("span", { className: "crisp-recordpage-note-left", children: [_jsx(Avatar, { size: "sm", shape: "circle", tone: "neutral", children: "Y" }), n.title && (_jsx("span", { className: "crisp-recordpage-note-title", children: n.title })), _jsx("span", { className: "crisp-recordpage-note-text", children: n.text })] }), _jsx("span", { className: "crisp-recordpage-note-time", children: n.time })] }, n.id))))] }));
|
|
277
301
|
}
|
|
278
302
|
function TasksSection({ tasks, onAdd, onToggle, labels, }) {
|
|
279
303
|
const [open, setOpen] = React.useState(false);
|
|
@@ -306,13 +330,51 @@ function EmptyCard({ icon, children, }) {
|
|
|
306
330
|
* `RecordPage`'s own root does: `<div className={recordpageRecipe()}>`
|
|
307
331
|
* (also exported from this package).
|
|
308
332
|
*/
|
|
309
|
-
export function RecordDetails({ record, columns, sections, onEdit, labels, className, ...rest }) {
|
|
333
|
+
export function RecordDetails({ record, columns, sections, onEdit, labels, className, resizable = false, width: controlledWidth, defaultWidth = DETAILS_DEFAULT_W, onWidthChange, minWidth = DETAILS_MIN_W, maxWidth = DETAILS_MAX_W, ...rest }) {
|
|
310
334
|
const t = useRecordPageLabels(labels);
|
|
311
335
|
const { editingKey, pendingKey, error, openEditing, closeEditing, commit } = useEditableField(onEdit);
|
|
312
336
|
const [detailsOpen, setDetailsOpen] = React.useState(true);
|
|
313
337
|
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
314
338
|
const [fieldQuery, setFieldQuery] = React.useState("");
|
|
315
339
|
const [showAllValues, setShowAllValues] = React.useState(false);
|
|
340
|
+
// #930: controlled/uncontrolled width, same split as RecordPanel's own
|
|
341
|
+
// resizable width -- only read when `resizable`, same reasoning as
|
|
342
|
+
// RecordPanel's own (a non-resizing consumer has no business carrying
|
|
343
|
+
// width state at all).
|
|
344
|
+
const [internalWidth, setInternalWidth] = React.useState(defaultWidth);
|
|
345
|
+
const width = controlledWidth ?? internalWidth;
|
|
346
|
+
const setWidth = (next) => {
|
|
347
|
+
const clamped = Math.max(minWidth, Math.min(maxWidth, next));
|
|
348
|
+
if (controlledWidth === undefined)
|
|
349
|
+
setInternalWidth(clamped);
|
|
350
|
+
onWidthChange?.(clamped);
|
|
351
|
+
};
|
|
352
|
+
const [detailsResizing, setDetailsResizing] = React.useState(false);
|
|
353
|
+
const detailsDragRef = React.useRef(null);
|
|
354
|
+
const onDetailsResizeDown = (e) => {
|
|
355
|
+
// This column is a leading (left-docked) rail, the mirror image of
|
|
356
|
+
// RecordPanel's own trailing (right-docked) one -- a rightward pull
|
|
357
|
+
// grows it in LTR, the same direction RecordPage's own internal aside
|
|
358
|
+
// resize already uses (#212/#255).
|
|
359
|
+
detailsDragRef.current = {
|
|
360
|
+
x: e.clientX,
|
|
361
|
+
w: width,
|
|
362
|
+
dir: isRtlElement(e.currentTarget) ? -1 : 1,
|
|
363
|
+
};
|
|
364
|
+
setDetailsResizing(true);
|
|
365
|
+
e.target.setPointerCapture(e.pointerId);
|
|
366
|
+
};
|
|
367
|
+
const onDetailsResizeMove = (e) => {
|
|
368
|
+
const r = detailsDragRef.current;
|
|
369
|
+
if (!r)
|
|
370
|
+
return;
|
|
371
|
+
setWidth(r.w + r.dir * (e.clientX - r.x));
|
|
372
|
+
};
|
|
373
|
+
const onDetailsResizeUp = (e) => {
|
|
374
|
+
detailsDragRef.current = null;
|
|
375
|
+
setDetailsResizing(false);
|
|
376
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
377
|
+
};
|
|
316
378
|
const fields = columns.filter((col) => col.type !== "entity");
|
|
317
379
|
const q = fieldQuery.trim().toLowerCase();
|
|
318
380
|
const matches = (col) => !q || col.label.toLowerCase().includes(q);
|
|
@@ -349,7 +411,7 @@ export function RecordDetails({ record, columns, sections, onEdit, labels, class
|
|
|
349
411
|
groups = [{ title: undefined, fields: shown }];
|
|
350
412
|
}
|
|
351
413
|
const totalShown = groups.reduce((n, g) => n + g.fields.length, 0);
|
|
352
|
-
return (_jsxs("div", { className: cx("crisp-recordpage-section", className), ...rest, children: [_jsxs("div", { className: "crisp-recordpage-detailhead", children: [searchOpen ? (_jsx("input", {
|
|
414
|
+
return (_jsxs("div", { className: cx("crisp-recordpage-section", className), "data-resizable": resizable ? "" : undefined, style: resizable ? { width } : undefined, ...rest, children: [_jsxs("div", { className: "crisp-recordpage-detailhead", children: [searchOpen ? (_jsx("input", {
|
|
353
415
|
// biome-ignore lint/a11y/noAutofocus: focusing the just-opened field search is expected
|
|
354
416
|
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) => {
|
|
355
417
|
if (e.key === "Escape") {
|
|
@@ -423,9 +485,37 @@ export function RecordDetails({ record, columns, sections, onEdit, labels, class
|
|
|
423
485
|
setSearchOpen(next);
|
|
424
486
|
if (!next)
|
|
425
487
|
setFieldQuery("");
|
|
426
|
-
}, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : (_jsx("div", { className: "crisp-recordpage-noresults", children: t.noFieldsMatch(fieldQuery) })))
|
|
488
|
+
}, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : (_jsx("div", { className: "crisp-recordpage-noresults", children: t.noFieldsMatch(fieldQuery) }))), resizable && (_jsx("div", { className: "crisp-recordpage-detailresize", "data-active": detailsResizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(width), "aria-valuemin": minWidth, "aria-valuemax": maxWidth, tabIndex: 0, onPointerDown: onDetailsResizeDown, onPointerMove: onDetailsResizeMove, onPointerUp: onDetailsResizeUp, onKeyDown: (e) => {
|
|
489
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
|
|
490
|
+
return;
|
|
491
|
+
const rtl = isRtlElement(e.currentTarget);
|
|
492
|
+
const grow = e.key === (rtl ? "ArrowLeft" : "ArrowRight");
|
|
493
|
+
setWidth(grow ? width + 16 : width - 16);
|
|
494
|
+
} }))] }));
|
|
427
495
|
}
|
|
428
496
|
RecordDetails.displayName = "RecordDetails";
|
|
497
|
+
/**
|
|
498
|
+
* The main/tab-content column's own box, on its own (#750) — the same
|
|
499
|
+
* "standalone piece" `RecordDetails` already is for the Details column,
|
|
500
|
+
* mirrored to the other side of the layout. `RecordPage` composes this
|
|
501
|
+
* internally for its own tabbed body (below) — this exact box, not a second
|
|
502
|
+
* copy — so a consumer building a custom record page (its own tab set, its
|
|
503
|
+
* own menu, `RecordDetails` for the left column, but not the full
|
|
504
|
+
* `RecordPage`, which hardcodes the CRM tab set) gets the same
|
|
505
|
+
* Attio-measured inset: `flex: 1; min-width: 0; display: flex;
|
|
506
|
+
* flex-direction: column; min-height: 0; overflow-y: auto; padding: 12px
|
|
507
|
+
* 20px` (`recordpage.recipe.ts`'s own `.crisp-recordpage-main`) — without
|
|
508
|
+
* reaching for that internal class name directly, the only option before
|
|
509
|
+
* this existed.
|
|
510
|
+
*
|
|
511
|
+
* Needs the same single `.crisp-recordpage`-scoped ancestor `RecordDetails`
|
|
512
|
+
* already documents — wrap both in `recordpageRecipe()`, the way
|
|
513
|
+
* `RecordPage`'s own root does.
|
|
514
|
+
*/
|
|
515
|
+
export function RecordPageMain({ className, children, ...rest }) {
|
|
516
|
+
return (_jsx("main", { className: cx("crisp-recordpage-main", className), ...rest, children: children }));
|
|
517
|
+
}
|
|
518
|
+
RecordPageMain.displayName = "RecordPageMain";
|
|
429
519
|
/**
|
|
430
520
|
* The full-page company record view — the page that opens when you click into a
|
|
431
521
|
* record (as opposed to the peek [RecordPanel]). A top breadcrumb bar with
|
|
@@ -434,7 +524,7 @@ RecordDetails.displayName = "RecordDetails";
|
|
|
434
524
|
* Emails sections (Notes & Tasks are live — add and check things off). Drop it
|
|
435
525
|
* into the app's main content area.
|
|
436
526
|
*/
|
|
437
|
-
export function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, quickActions: quickActionsProp, comments: commentsProp, onPostComment, currentUser, onShare, ...rest }) {
|
|
527
|
+
export function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, onClose, closeLabel = "Close", onPrevious, previousRecordLabel = "Previous record", onNext, nextRecordLabel = "Next record", counterLabel, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, quickActions: quickActionsProp, comments: commentsProp, onPostComment, currentUser, onShare, ...rest }) {
|
|
438
528
|
const t = useRecordPageLabels(labels);
|
|
439
529
|
const [favorite, setFavorite] = React.useState(false);
|
|
440
530
|
// Notes/Tasks are controlled when the caller supplies `notes`/`tasks` (even
|
|
@@ -666,13 +756,19 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
|
|
|
666
756
|
case "overview":
|
|
667
757
|
return (_jsxs("div", { className: "crisp-recordpage-panel", children: [highlights !== false && (_jsxs("section", { children: [_jsx("div", { className: "crisp-recordpage-section-title", children: t.highlights }), _jsx("div", { className: "crisp-recordpage-hl-card", children: _jsx("div", { className: "crisp-recordpage-hl-grid", children: highlights.map((h) => (_jsxs("div", { className: "crisp-recordpage-hl-cell", children: [h.icon && (_jsx(h.icon, { className: "crisp-recordpage-hl-icon", size: 15, strokeWidth: 1.75 })), _jsx("div", { className: "crisp-recordpage-hl-label", children: h.label }), _jsx("div", { className: "crisp-recordpage-hl-value", children: h.value ?? (_jsx("span", { className: "crisp-recordpage-hl-value--empty", children: h.empty })) })] }, h.label))) }) })] })), activityFeed, _jsxs("section", { children: [_jsx(SectionHead, { title: t.tabs.emails, count: 0 }), emailsEmpty] }), _jsx(NotesSection, { notes: notes, onAdd: addNote, labels: t }), _jsx(TasksSection, { tasks: tasks, onAdd: addTask, onToggle: toggleTask, labels: t })] }));
|
|
668
758
|
case "activity":
|
|
669
|
-
return _jsx("div", { className: "crisp-recordpage-panel", children:
|
|
759
|
+
return (_jsx("div", { className: "crisp-recordpage-panel", children: _jsxs("section", { children: [_jsx(SectionHead, { title: t.tabs.activity, action: _jsxs("span", { className: "crisp-recordpage-tabtoolbar", children: [_jsxs("button", { type: "button", className: "crisp-recordpage-action", children: [_jsx(ListFilter, { size: 14, strokeWidth: 1.75 }), t.filters] }), _jsxs("button", { type: "button", className: "crisp-recordpage-action", children: [_jsx(Plus, { size: 14, strokeWidth: 1.75 }), t.addMeeting] })] }) }), _jsx("div", { className: "crisp-recordpage-feed", children: activity.map((a) => (_jsxs("div", { className: "crisp-recordpage-feed-item", children: [_jsx("span", { className: "crisp-recordpage-feed-dot", children: _jsx(ActivityIcon, { size: 13, strokeWidth: 2 }) }), _jsxs("div", { className: "crisp-recordpage-feed-body", children: [a.text, _jsx("div", { className: "crisp-recordpage-feed-time", children: a.time })] })] }, a.id))) })] }) }));
|
|
760
|
+
case "files":
|
|
761
|
+
return (_jsx("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: _jsxs("div", { className: "crisp-recordpage-illustrated", children: [_jsx("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: _jsx(Folder, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-recordpage-illustrated-title", children: t.noFiles }), _jsx("div", { className: "crisp-recordpage-illustrated-desc", children: t.filesHint }), _jsxs("div", { className: "crisp-recordpage-illustrated-actions", children: [_jsxs("button", { type: "button", className: "crisp-recordpage-action", children: [_jsx(Plus, { size: 14, strokeWidth: 1.75 }), t.newFolder] }), _jsxs("button", { type: "button", className: "crisp-recordpage-action", children: [_jsx(Plus, { size: 14, strokeWidth: 1.75 }), t.uploadFile] }), _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.fileMoreActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) })] })] }) }));
|
|
762
|
+
case "deals":
|
|
763
|
+
return (_jsx("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: _jsxs("div", { className: "crisp-recordpage-illustrated", children: [_jsx("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: _jsx(Merge, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-recordpage-illustrated-title", children: t.noDealsFound }), _jsx("div", { className: "crisp-recordpage-illustrated-desc", children: t.noDealsHint }), _jsx("div", { className: "crisp-recordpage-illustrated-actions", children: _jsxs(Button, { intent: "primary", size: "md", children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), t.newDeal] }) })] }) }));
|
|
670
764
|
case "notes":
|
|
671
765
|
return (_jsx("div", { className: "crisp-recordpage-panel", children: _jsx(NotesSection, { notes: notes, onAdd: addNote, labels: t }) }));
|
|
672
766
|
case "tasks":
|
|
673
767
|
return (_jsx("div", { className: "crisp-recordpage-panel", children: _jsx(TasksSection, { tasks: tasks, onAdd: addTask, onToggle: toggleTask, labels: t }) }));
|
|
674
768
|
case "emails":
|
|
675
|
-
return _jsx("div", { className: "crisp-recordpage-panel", children:
|
|
769
|
+
return (_jsxs("div", { className: "crisp-recordpage-panel", children: [_jsx(SectionHead, { title: t.tabs.emails }), _jsx("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: _jsxs("div", { className: "crisp-recordpage-illustrated", children: [_jsx("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: _jsx(Mail, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-recordpage-illustrated-title", children: t.noEmails }), _jsx("div", { className: "crisp-recordpage-illustrated-desc", children: t.noEmailsHint }), _jsx("div", { className: "crisp-recordpage-illustrated-actions", children: _jsx(Button, { intent: "primary", size: "md", children: t.configureMailbox }) })] }) })] }));
|
|
770
|
+
case "calls":
|
|
771
|
+
return (_jsx("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: _jsxs("div", { className: "crisp-recordpage-illustrated", children: [_jsx("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: _jsx(Phone, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-recordpage-illustrated-title crisp-recordpage-illustrated-title--lg", children: t.upgradeToViewCalls }), _jsx("div", { className: "crisp-recordpage-illustrated-desc", children: t.callsUpgradeHint }), _jsx("div", { className: "crisp-recordpage-illustrated-actions", children: _jsxs(Button, { intent: "primary", size: "md", children: [_jsx(Zap, { size: 15, strokeWidth: 1.75 }), t.upgrade] }) })] }) }));
|
|
676
772
|
default:
|
|
677
773
|
return (_jsx("div", { className: "crisp-recordpage-panel", children: _jsx(EmptyCard, { icon: _jsx(Folder, { size: 22, strokeWidth: 1.5 }), children: t.nothingHereYet }) }));
|
|
678
774
|
}
|
|
@@ -690,7 +786,7 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
|
|
|
690
786
|
const effectivePanel = tabsProp
|
|
691
787
|
? (active) => tabsProp.find((tb) => tb.id === active)?.render()
|
|
692
788
|
: builtinPanel;
|
|
693
|
-
return (_jsxs("div", { className: recordpageRecipe({ className }), ...rest, children: [_jsxs("div", { className: "crisp-recordpage-topbar", children: [_jsxs("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [_jsx("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), _jsx("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), _jsx("span", { className: "crisp-recordpage-crumb-name", children: name })] }), _jsxs("div", { className: "crisp-recordpage-actions", children: [_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", onClick: onShare, children: t.share }), _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: () => setCommentsOpen(true), "aria-label": t.comments, children: _jsx(MessageSquare, { size: 15, strokeWidth: 1.75 }) }), _jsxs("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [_jsx(Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && (_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? (_jsx(Minimize2, { size: 15, strokeWidth: 1.75 })) : (_jsx(Maximize2, { size: 15, strokeWidth: 1.75 })) })), _jsxs(Menu, { align: "end", "aria-label": t.recordActions, trigger: _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [_jsx(MenuItem, { icon: _jsx(Merge, { size: 15, strokeWidth: 1.75 }), children: t.mergeRecords }), _jsx(MenuItem, { icon: _jsx(Settings, { size: 15, strokeWidth: 1.75 }), children: t.configurePage }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyPageUrl }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyRecordId }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, children: t.deleteRecord })] })] })] }), _jsxs("div", { className: "crisp-recordpage-body", children: [_jsxs("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [_jsxs("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? (_jsx("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : (_jsx(Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), _jsx("span", { className: "crisp-recordpage-entity-name", children: name }), _jsx("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: _jsx(Pencil, { size: 15, strokeWidth: 1.75 }) }), _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: _jsx(Star, { size: 15, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "crisp-recordpage-quickactions", children: [_jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), _jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Plus, { size: 16, strokeWidth: 1.75 }), t.addToList] }), quickActions.map((a, i) => (_jsx("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": a.label, onClick: a.onSelect, children: _jsx(a.icon, { size: 15, strokeWidth: 1.75 }) }, i)))] }), _jsx(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), _jsxs("div", { className: "crisp-recordpage-section", children: [_jsx(SectionHead, { title: t.tabs.lists, action: _jsxs("button", { type: "button", className: "crisp-recordpage-addbtn", children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), _jsx("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), _jsx("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": DETAILS_MIN_W, "aria-valuemax": DETAILS_MAX_W, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
|
|
789
|
+
return (_jsxs("div", { className: recordpageRecipe({ className }), ...rest, children: [_jsxs("div", { className: "crisp-recordpage-topbar", "data-peek": onClose ? "" : undefined, children: [onClose ? (_jsxs("div", { className: "crisp-recordpage-peekbar", children: [_jsx("button", { type: "button", className: "crisp-recordpage-peek-iconbtn", "aria-label": closeLabel, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.75 }) }), (onPrevious || onNext) && (_jsxs("span", { className: "crisp-recordpage-peek-pager", children: [_jsx("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": previousRecordLabel, disabled: !onPrevious, onClick: onPrevious, children: _jsx(ChevronUp, { size: 16, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": nextRecordLabel, disabled: !onNext, onClick: onNext, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75 }) })] })), counterLabel != null && (_jsx("span", { className: "crisp-recordpage-peek-counter", children: counterLabel }))] })) : (_jsxs("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [_jsx("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), _jsx("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), _jsx("span", { className: "crisp-recordpage-crumb-name", children: name })] })), _jsxs("div", { className: "crisp-recordpage-actions", children: [_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", onClick: onShare, children: t.share }), _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: () => setCommentsOpen(true), "aria-label": t.comments, children: _jsx(MessageSquare, { size: 15, strokeWidth: 1.75 }) }), _jsxs("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [_jsx(Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && (_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? (_jsx(Minimize2, { size: 15, strokeWidth: 1.75 })) : (_jsx(Maximize2, { size: 15, strokeWidth: 1.75 })) })), _jsxs(Menu, { align: "end", "aria-label": t.recordActions, trigger: _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [_jsx(MenuItem, { icon: _jsx(Merge, { size: 15, strokeWidth: 1.75 }), children: t.mergeRecords }), _jsx(MenuItem, { icon: _jsx(Settings, { size: 15, strokeWidth: 1.75 }), children: t.configurePage }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyPageUrl }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyRecordId }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, children: t.deleteRecord })] })] })] }), _jsxs("div", { className: "crisp-recordpage-body", children: [_jsxs("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [_jsxs("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? (_jsx("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : (_jsx(Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), _jsx("span", { className: "crisp-recordpage-entity-name", children: name }), _jsx("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: _jsx(Pencil, { size: 15, strokeWidth: 1.75 }) }), _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: _jsx(Star, { size: 15, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "crisp-recordpage-quickactions", children: [_jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), _jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Plus, { size: 16, strokeWidth: 1.75 }), t.addToList] }), quickActions.map((a, i) => (_jsx("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": a.label, onClick: a.onSelect, children: _jsx(a.icon, { size: 15, strokeWidth: 1.75 }) }, i)))] }), _jsx(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), _jsxs("div", { className: "crisp-recordpage-section", children: [_jsx(SectionHead, { title: t.tabs.lists, action: _jsxs("button", { type: "button", className: "crisp-recordpage-addbtn", children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), _jsx("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), _jsx("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": DETAILS_MIN_W, "aria-valuemax": DETAILS_MAX_W, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
|
|
694
790
|
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
|
|
695
791
|
return;
|
|
696
792
|
// The pointer drag learned its direction in #212; this did not.
|
|
@@ -702,6 +798,6 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
|
|
|
702
798
|
setDetailsW((w) => grow
|
|
703
799
|
? Math.min(DETAILS_MAX_W, w + 16)
|
|
704
800
|
: Math.max(DETAILS_MIN_W, w - 16));
|
|
705
|
-
} }), _jsx(
|
|
801
|
+
} }), _jsx(RecordPageMain, { children: _jsx(Tabs, { tabs: effectiveTabs, defaultValue: tabsProp ? defaultTab : "overview", "aria-label": `${name} record`, children: effectivePanel }) })] }), _jsx(Drawer, { open: commentsOpen, onOpenChange: setCommentsOpen, "aria-label": t.comments, style: { width: 320 }, children: _jsx(CommentsPanel, { threads: comments, currentUserId: me.id, onPost: postComment, onClose: () => setCommentsOpen(false), role: undefined }) })] }));
|
|
706
802
|
}
|
|
707
803
|
RecordPage.displayName = "RecordPage";
|
|
@@ -23,6 +23,21 @@ const config = defineRecipe({
|
|
|
23
23
|
& * { box-sizing: border-box; }
|
|
24
24
|
|
|
25
25
|
& .crisp-recordpage-topbar { display: flex; align-items: center; gap: ${c.space["2"]}; height: 44px; flex-shrink: 0; padding: 0 ${c.space["3"]}; border-bottom: 1px solid ${c.borderSubtle}; }
|
|
26
|
+
/* #624: expanding a peek to its full record keeps a hybrid header
|
|
27
|
+
(Close/Previous/Next/counter) instead of reverting to the plain
|
|
28
|
+
breadcrumb above -- measured live on a real expanded record: 49px
|
|
29
|
+
tall (not 44px), 8px/10px padding, same borderSubtle hairline. Same
|
|
30
|
+
tokens as product/recordpanel's own already-shipped toolbar, not
|
|
31
|
+
new values -- this is the same row, reused in a second component. */
|
|
32
|
+
& .crisp-recordpage-topbar[data-peek] { height: 49px; padding: ${c.space["2"]} ${c.space["2.5"]}; }
|
|
33
|
+
& .crisp-recordpage-peekbar { display: flex; align-items: center; }
|
|
34
|
+
& .crisp-recordpage-peek-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: ${c.radius.md}; background: transparent; color: color-mix(in srgb, ${c.fgPrimary} 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s; }
|
|
35
|
+
& .crisp-recordpage-peek-iconbtn:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
|
|
36
|
+
& .crisp-recordpage-peek-pager { display: flex; align-items: center; gap: ${c.space["1.5"]}; margin-inline-start: ${c.space["3"]}; }
|
|
37
|
+
& .crisp-recordpage-peek-navbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: ${c.radius.md}; background: ${c.bgSurface}; box-shadow: ${e.field}; color: ${c.fgPrimary}; font-family: inherit; cursor: pointer; }
|
|
38
|
+
& .crisp-recordpage-peek-navbtn:hover:not(:disabled) { background: ${c.bgRaised}; }
|
|
39
|
+
& .crisp-recordpage-peek-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
40
|
+
& .crisp-recordpage-peek-counter { margin-inline-start: ${c.space["3"]}; font-size: 12px; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
26
41
|
& .crisp-recordpage-crumbs { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; min-width: 0; }
|
|
27
42
|
& .crisp-recordpage-crumb { display: inline-flex; align-items: center; height: 26px; padding: 0 ${c.space["1.5"]}; margin: 0 calc(-1 * ${c.space["1.5"]}) 0 0; border: none; border-radius: ${c.radius.md}; background: none; font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; cursor: pointer; }
|
|
28
43
|
& .crisp-recordpage-crumb:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
|
|
@@ -42,6 +57,17 @@ const config = defineRecipe({
|
|
|
42
57
|
& .crisp-recordpage-resizer::before { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -4px; width: 9px; }
|
|
43
58
|
& .crisp-recordpage-resizer::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -1px; width: 2px; background: transparent; transition: background-color .12s ease; }
|
|
44
59
|
& .crisp-recordpage-resizer:hover::after, & .crisp-recordpage-resizer[data-active]::after { background: color-mix(in srgb, ${c.bgBrandSolid} 55%, transparent); }
|
|
60
|
+
/* #930: RecordDetails' own standalone resize handle -- same visible
|
|
61
|
+
hit-slop/line-highlight shape as .crisp-recordpage-resizer just above,
|
|
62
|
+
just absolutely positioned at the trailing edge instead of a 0-width
|
|
63
|
+
flex sibling, since RecordDetails always returns a single root (the
|
|
64
|
+
same internal-strip placement RecordPanel's own resize handle uses,
|
|
65
|
+
mirrored to the opposite edge -- this column is leading, RecordPanel
|
|
66
|
+
is trailing). */
|
|
67
|
+
& .crisp-recordpage-section[data-resizable] { position: relative; }
|
|
68
|
+
& .crisp-recordpage-detailresize { position: absolute; top: 0; bottom: 0; inset-inline-end: -4px; width: 9px; cursor: col-resize; z-index: 5; touch-action: none; }
|
|
69
|
+
& .crisp-recordpage-detailresize::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-end: 3px; width: 2px; background: transparent; transition: background-color .12s ease; }
|
|
70
|
+
& .crisp-recordpage-detailresize:hover::after, & .crisp-recordpage-detailresize[data-active]::after { background: color-mix(in srgb, ${c.bgBrandSolid} 55%, transparent); }
|
|
45
71
|
& .crisp-recordpage-entity { display: flex; align-items: center; gap: ${c.space["2"]}; padding: ${c.space["1"]} ${c.space["1"]} ${c.space["2"]}; }
|
|
46
72
|
& .crisp-recordpage-entity-logo { width: 32px; height: 32px; border-radius: 30%; flex-shrink: 0; object-fit: cover; background: ${c.bgRaised}; }
|
|
47
73
|
& .crisp-recordpage-entity-name { flex: 0 1 auto; min-width: 0; font-size: ${c.text.xl}; font-weight: ${c.weight.semibold}; letter-spacing: -0.3px; color: ${c.fgPrimary}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -175,12 +201,23 @@ const config = defineRecipe({
|
|
|
175
201
|
& .crisp-recordpage-btn--primary { box-shadow: none; background: ${c.bgBrandSolid}; color: ${c.fgOnBrand}; }
|
|
176
202
|
& .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
177
203
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
204
|
+
/* #871: one bordered card per note (border-radius.xl, borderSubtle),
|
|
205
|
+
not a flush divider-list row -- measured live on a real populated
|
|
206
|
+
note: the whole row is flex space-between, avatar+title+body on the
|
|
207
|
+
left (its own flex, gap.2), the relative time alone on the right.
|
|
208
|
+
The real DOM nests an inner clickable button (padding 6/8, radius.md)
|
|
209
|
+
inside an outer card (padding 4px, radius.xl) -- collapsed into this
|
|
210
|
+
one non-interactive div's own combined padding (2.5/3) since crisp's
|
|
211
|
+
NotesSection has no click-to-open behaviour to justify the extra
|
|
212
|
+
layer. The 6px gap between stacked cards is NOT independently
|
|
213
|
+
measured (this workspace only ever had one real note to check) --
|
|
214
|
+
a judgement call off the existing space scale, not a live value. */
|
|
215
|
+
& .crisp-recordpage-note { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["3"]}; padding: ${c.space["2.5"]} ${c.space["3"]}; border: 1px solid ${c.borderSubtle}; border-radius: ${c.radius.xl}; }
|
|
216
|
+
& .crisp-recordpage-note + .crisp-recordpage-note { margin-top: ${c.space["1.5"]}; }
|
|
217
|
+
& .crisp-recordpage-note-left { display: flex; align-items: center; gap: ${c.space["2"]}; min-width: 0; overflow: hidden; }
|
|
218
|
+
& .crisp-recordpage-note-title { flex-shrink: 0; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; }
|
|
219
|
+
& .crisp-recordpage-note-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
|
|
220
|
+
& .crisp-recordpage-note-time { flex-shrink: 0; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 50%, transparent); }
|
|
184
221
|
|
|
185
222
|
& .crisp-recordpage-task { display: flex; align-items: flex-start; gap: ${c.space["2"]}; padding: ${c.space["1.5"]} 0; border-top: 1px solid ${c.borderSubtle}; }
|
|
186
223
|
& .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: ${c.text.lg}; color: ${c.fgPrimary}; padding-top: 1px; }
|
|
@@ -189,6 +226,31 @@ const config = defineRecipe({
|
|
|
189
226
|
& .crisp-recordpage-emptycard { display: flex; flex-direction: column; align-items: center; gap: ${c.space["1.5"]}; padding: ${c.space["6"]} ${c.space["3"]}; border: 1px dashed ${c.borderDefault}; border-radius: ${c.radius.xl}; color: ${c.fgMuted}; font-size: ${c.text.md}; text-align: center; }
|
|
190
227
|
& .crisp-recordpage-emptycard svg { color: ${c.fgMuted}; opacity: 0.7; }
|
|
191
228
|
|
|
229
|
+
/* #622: the Activity tab's own toolbar -- "Filters" + "+ Add meeting",
|
|
230
|
+
both the same measured button chrome as the topbar's own action
|
|
231
|
+
buttons (28px, e.field shadow, 14/500 label). */
|
|
232
|
+
& .crisp-recordpage-tabtoolbar { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; }
|
|
233
|
+
|
|
234
|
+
/* #622: Files tab's empty state -- measured live on Attio's own record
|
|
235
|
+
page (a company with no files uploaded). The 138x138 dotted-grid
|
|
236
|
+
backdrop is the same measured tile EmptyState's records variant uses
|
|
237
|
+
(crisp/#603), duplicated here rather than composed through that
|
|
238
|
+
component because this surface's title (16/600) and body (14/500,
|
|
239
|
+
63%-mixed fgPrimary, not the flat fgMuted EmptyState uses) measure to
|
|
240
|
+
their own values, matching neither of that component's existing
|
|
241
|
+
variants. */
|
|
242
|
+
& .crisp-recordpage-panel--center { flex: 1; align-items: center; justify-content: center; }
|
|
243
|
+
& .crisp-recordpage-illustrated { display: flex; flex-direction: column; align-items: center; gap: ${c.space["1.5"]}; text-align: center; }
|
|
244
|
+
& .crisp-recordpage-illustrated-icon { display: flex; align-items: center; justify-content: center; width: 138px; height: 138px; margin-bottom: ${c.space["2"]}; color: color-mix(in srgb, ${c.fgPrimary} 22%, transparent); background: radial-gradient(circle, color-mix(in srgb, ${c.fgPrimary} 12%, transparent) 1.2px, transparent 1.2px) 0 0 / 16px 16px; }
|
|
245
|
+
& .crisp-recordpage-illustrated-title { font-size: ${c.text.xl}; font-weight: ${c.weight.semibold}; color: ${c.fgPrimary}; }
|
|
246
|
+
/* #988: the Calls tab's own gated state measures a larger 20/24 title
|
|
247
|
+
than Files/Deals' 16/600 -- Attio's own "empty view" pattern (the
|
|
248
|
+
same 20/600 EmptyState's page/records variants already carry, not a
|
|
249
|
+
one-off). */
|
|
250
|
+
& .crisp-recordpage-illustrated-title--lg { font-size: 20px; line-height: 24px; }
|
|
251
|
+
& .crisp-recordpage-illustrated-desc { max-width: 320px; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
|
|
252
|
+
& .crisp-recordpage-illustrated-actions { display: inline-flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: ${c.space["1.5"]}; margin-top: ${c.space["2"]}; }
|
|
253
|
+
|
|
192
254
|
& .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
|
|
193
255
|
|
|
194
256
|
& .crisp-recordstable-tag { display: inline-flex; align-items: center; height: ${c.size.chip}; padding: 0 ${c.space["1"]}; border-radius: ${c.radius.chip}; font-size: ${c.text.lg}; line-height: 20px; font-weight: ${c.weight.medium}; white-space: nowrap; background: color-mix(in srgb, ${c.fgPrimary} 6%, transparent); color: color-mix(in srgb, ${c.fgPrimary} 82%, transparent); }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WorkspaceGeneralSettings, } from "./workspacegeneralsettings.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Copy } from "lucide-react";
|
|
3
|
+
import { Avatar } from "../../components/avatar/index.js";
|
|
4
|
+
import { Button } from "../../components/button/index.js";
|
|
5
|
+
import { FormField } from "../../components/formfield/index.js";
|
|
6
|
+
import { Input } from "../../components/input/index.js";
|
|
7
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../components/table/index.js";
|
|
8
|
+
import { TextLink } from "../../components/textlink/index.js";
|
|
9
|
+
import { workspacegeneralsettingsRecipe } from "./workspacegeneralsettings.recipe.js";
|
|
10
|
+
/**
|
|
11
|
+
* Attio's Settings → Workspace → General page (crisp#638, P-128), measured
|
|
12
|
+
* live: a logo + Name/Slug section, an "Export workspace data" table, and a
|
|
13
|
+
* "Danger zone" delete-workspace row.
|
|
14
|
+
*/
|
|
15
|
+
export function WorkspaceGeneralSettings({ logoSrc, workspaceInitial, onUploadLogo, name, onNameChange, slug, onCopySlug, exports: exportRows, onStartExport, onDeleteWorkspace, className, title = "General", description = "Change the settings for your current workspace.", learnMoreLabel = "Learn more", learnMoreHref = "#", logoHelpText = "We only support PNGs, JPEGs and GIFs under 10MB", uploadLogoLabel = "Upload logo", nameFieldLabel = "Name", slugFieldLabel = "Slug", copySlugLabel = "Copy slug", exportSectionTitle = "Export workspace data", exportSectionDescription = "Exports are in CSV format and can be downloaded within 7 days", startExportLabel = "Start new export", typeColumnLabel = "Type", dateColumnLabel = "Date", noExportsLabel = "No exports yet", dangerSectionTitle = "Danger zone", deleteWorkspaceTitle = "Delete workspace", deleteWorkspaceWarning = "Once deleted, your workspace cannot be recovered", deleteWorkspaceLabel = "Delete workspace", ...rest }) {
|
|
16
|
+
return (_jsx("div", { className: workspacegeneralsettingsRecipe({ className }), ...rest, children: _jsxs("div", { className: "crisp-workspacegeneralsettings-page", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-heading", children: [_jsx("h1", { className: "crisp-workspacegeneralsettings-title", children: title }), _jsxs("p", { className: "crisp-workspacegeneralsettings-desc", children: [description, " ", _jsx(TextLink, { href: learnMoreHref, children: learnMoreLabel })] })] }), _jsxs("div", { className: "crisp-workspacegeneralsettings-section", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-logorow", children: [logoSrc ? (_jsx("img", { src: logoSrc, alt: "", width: 48, height: 48, style: { borderRadius: "30%", objectFit: "cover" } })) : (_jsx(Avatar, { size: "xl", shape: "rounded", tone: "brand", children: workspaceInitial })), _jsxs("div", { className: "crisp-workspacegeneralsettings-logotext", children: [_jsx("span", { className: "crisp-workspacegeneralsettings-logohelp", children: logoHelpText }), _jsx(Button, { intent: "neutral", size: "sm", onClick: onUploadLogo, children: uploadLogoLabel })] })] }), _jsxs("div", { className: "crisp-workspacegeneralsettings-fieldrow", children: [_jsx(FormField, { label: nameFieldLabel, children: _jsx(Input, { value: name, onChange: (e) => onNameChange(e.target.value) }) }), _jsx(FormField, { label: slugFieldLabel, children: _jsxs("div", { className: "crisp-workspacegeneralsettings-slugfield", children: [_jsx(Input, { value: slug, readOnly: true }), _jsx("button", { type: "button", className: "crisp-workspacegeneralsettings-copybtn", "aria-label": copySlugLabel, onClick: onCopySlug, children: _jsx(Copy, { size: 16 }) })] }) })] })] }), _jsxs("div", { className: "crisp-workspacegeneralsettings-section", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-sectionhead", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-sectionhead-text", children: [_jsx("h2", { className: "crisp-workspacegeneralsettings-sectiontitle", children: exportSectionTitle }), _jsx("p", { className: "crisp-workspacegeneralsettings-desc", children: exportSectionDescription })] }), _jsx(Button, { intent: "neutral", size: "sm", onClick: onStartExport, children: startExportLabel })] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: typeColumnLabel }), _jsx(TableHead, { children: dateColumnLabel })] }) }), _jsx(TableBody, { children: exportRows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, children: _jsx("div", { className: "crisp-workspacegeneralsettings-emptyrow", children: noExportsLabel }) }) })) : (exportRows.map((row) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: row.type }), _jsx(TableCell, { children: row.date })] }, row.id)))) })] })] }), _jsxs("div", { className: "crisp-workspacegeneralsettings-section", children: [_jsx("h2", { className: "crisp-workspacegeneralsettings-sectiontitle", children: dangerSectionTitle }), _jsxs("div", { className: "crisp-workspacegeneralsettings-dangerrow", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-dangertext", children: [_jsx("span", { className: "crisp-workspacegeneralsettings-rowtitle", children: deleteWorkspaceTitle }), _jsx("span", { className: "crisp-workspacegeneralsettings-dangerwarning", children: deleteWorkspaceWarning })] }), _jsx(Button, { intent: "danger", size: "sm", onClick: onDeleteWorkspace, children: deleteWorkspaceLabel })] })] })] }) }));
|
|
17
|
+
}
|
|
18
|
+
WorkspaceGeneralSettings.displayName = "WorkspaceGeneralSettings";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
// Measured live against app.attio.com/<workspace>/settings/workspace
|
|
4
|
+
// (crisp#638, P-128). Logo tile + Name/Slug fields side by side, an
|
|
5
|
+
// "Export workspace data" section (header row + Type/Date table + empty
|
|
6
|
+
// state), and a "Danger zone" section (a red-tinted bordered row).
|
|
7
|
+
const config = defineRecipe({
|
|
8
|
+
name: "workspacegeneralsettings",
|
|
9
|
+
base: `& .crisp-workspacegeneralsettings-page { display: flex; flex-direction: column; gap: ${c.space["6"]}; font-family: ${c.font.sans}; }
|
|
10
|
+
& .crisp-workspacegeneralsettings-heading { display: flex; flex-direction: column; gap: ${c.space["0.5"]}; }
|
|
11
|
+
& .crisp-workspacegeneralsettings-title { font-size: 24px; line-height: 28px; font-weight: ${c.weight.semibold}; letter-spacing: -0.48px; color: ${c.fgPrimary}; margin: 0; }
|
|
12
|
+
& .crisp-workspacegeneralsettings-desc { font-size: ${c.text.lg}; line-height: 20px; color: ${c.fgMuted}; margin: 0; }
|
|
13
|
+
|
|
14
|
+
& .crisp-workspacegeneralsettings-section { display: flex; flex-direction: column; gap: ${c.space["4"]}; }
|
|
15
|
+
& .crisp-workspacegeneralsettings-sectiontitle { font-size: 16px; line-height: 20px; font-weight: ${c.weight.semibold}; color: ${c.fgPrimary}; margin: 0; }
|
|
16
|
+
& .crisp-workspacegeneralsettings-sectionhead { display: flex; align-items: flex-start; justify-content: space-between; gap: ${c.space["4"]}; }
|
|
17
|
+
& .crisp-workspacegeneralsettings-sectionhead-text { display: flex; flex-direction: column; gap: ${c.space["1"]}; }
|
|
18
|
+
|
|
19
|
+
/* Logo row: a tile + helper text + "Upload logo" button. */
|
|
20
|
+
& .crisp-workspacegeneralsettings-logorow { display: flex; align-items: center; gap: ${c.space["4"]}; }
|
|
21
|
+
& .crisp-workspacegeneralsettings-logohelp { font-size: ${c.text.sm}; color: ${c.fgMuted}; }
|
|
22
|
+
& .crisp-workspacegeneralsettings-logotext { display: flex; flex-direction: column; align-items: flex-start; gap: ${c.space["1"]}; }
|
|
23
|
+
|
|
24
|
+
/* Name + Slug sit side by side, each ~half the content width. */
|
|
25
|
+
& .crisp-workspacegeneralsettings-fieldrow { display: flex; gap: ${c.space["4"]}; }
|
|
26
|
+
& .crisp-workspacegeneralsettings-fieldrow > * { flex: 1; min-width: 0; }
|
|
27
|
+
& .crisp-workspacegeneralsettings-slugfield { display: flex; align-items: center; gap: ${c.space["1.5"]}; }
|
|
28
|
+
& .crisp-workspacegeneralsettings-slugfield > :first-child { flex: 1; min-width: 0; }
|
|
29
|
+
& .crisp-workspacegeneralsettings-copybtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 32px; height: 32px; border: none; border-radius: ${c.radius.md}; background: none; font-family: inherit; color: ${c.fgMuted}; cursor: pointer; }
|
|
30
|
+
& .crisp-workspacegeneralsettings-copybtn:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
|
|
31
|
+
|
|
32
|
+
/* Export-data empty state -- measured: 14/500/63%-opacity, centred in a
|
|
33
|
+
40px row, no border of its own (the Table wrapper already draws it). */
|
|
34
|
+
& .crisp-workspacegeneralsettings-emptyrow { padding: ${c.space["2.5"]} ${c.space["4"]}; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); text-align: center; }
|
|
35
|
+
|
|
36
|
+
/* Danger zone: a red-tinted bordered row, distinct from every other
|
|
37
|
+
card on the page. */
|
|
38
|
+
& .crisp-workspacegeneralsettings-dangerrow { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["6"]}; padding: ${c.space["3"]} ${c.space["4"]}; border: 1px solid color-mix(in srgb, ${c.bgDanger} 30%, transparent); border-radius: 12px; background: color-mix(in srgb, ${c.bgDanger} 6%, transparent); }
|
|
39
|
+
& .crisp-workspacegeneralsettings-dangertext { display: flex; flex-direction: column; gap: ${c.space["1"]}; }
|
|
40
|
+
& .crisp-workspacegeneralsettings-rowtitle { font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; }
|
|
41
|
+
& .crisp-workspacegeneralsettings-dangerwarning { font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
|
|
42
|
+
`,
|
|
43
|
+
variants: {},
|
|
44
|
+
defaultVariants: {},
|
|
45
|
+
compoundVariants: [],
|
|
46
|
+
});
|
|
47
|
+
export const workspacegeneralsettingsRecipe = resolveRecipe(config);
|
|
48
|
+
export default config;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VAR_PREFIX } from "../core/namespace.js";
|
|
2
|
-
import { font as fontScale, radius as radiusScale, size as sizeScale, space as spaceScale, text as textScale, weight as weightScale, } from "./scale.js";
|
|
2
|
+
import { font as fontScale, radius as radiusScale, size as sizeScale, space as spaceScale, text as textScale, tracking as trackingScale, weight as weightScale, } from "./scale.js";
|
|
3
3
|
import { semantic } from "./semantic.js";
|
|
4
4
|
const kebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
5
5
|
const toVar = (key) => `var(--${VAR_PREFIX}${kebab(key)})`;
|
|
@@ -16,4 +16,5 @@ export const contract = Object.assign(colors, {
|
|
|
16
16
|
font: mapScale("font", fontScale),
|
|
17
17
|
text: mapScale("text", textScale),
|
|
18
18
|
weight: mapScale("weight", weightScale),
|
|
19
|
+
tracking: mapScale("tracking", trackingScale),
|
|
19
20
|
});
|
|
@@ -12,9 +12,11 @@ import { contract as c } from "./contract.js";
|
|
|
12
12
|
*/
|
|
13
13
|
export const elevation = {
|
|
14
14
|
/**
|
|
15
|
-
* Tier 1 — anything floating and *attached*:
|
|
15
|
+
* Tier 1 — anything floating and *attached*: popovers, dropdowns,
|
|
16
16
|
* hover-preview cards, the floating sidebar, the record peek, drawers, the
|
|
17
17
|
* bulk bar. Pairs with a 10–12px radius and, for panels, an 8px gutter.
|
|
18
|
+
* NOT `Menu` (your-moon/crisp#934) -- that moved to
|
|
19
|
+
* `selectFloating`, seed-design's own flat shadow, below.
|
|
18
20
|
*/
|
|
19
21
|
floating: `inset 0 0 0 1px ${c.ringPopover}, 0 0 0 1px ${c.shadowPopover1}, 0 4px 8px -4px ${c.shadowPopover2}, 0 4px 12px -2px ${c.shadowPopover3}`,
|
|
20
22
|
/**
|
|
@@ -57,10 +59,11 @@ export const elevation = {
|
|
|
57
59
|
*/
|
|
58
60
|
fieldNoRing: `0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04)`,
|
|
59
61
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* Seed-design's own `$shadow.s3`, a single flat layer, NOT `floating`
|
|
63
|
+
* above. Originally Select's own listbox shadow (crisp/#867); also `Menu`'s
|
|
64
|
+
* (your-moon/crisp#934) -- the raw seed dump confirms Menu
|
|
65
|
+
* is the one component seed's own spec gives a shadow to at all, and it's
|
|
66
|
+
* this exact value. Despite the name, not exclusive to Select.
|
|
64
67
|
*/
|
|
65
68
|
selectFloating: `0 4px 16px ${c.shadowSelect}`,
|
|
66
69
|
};
|
|
@@ -63,14 +63,34 @@ export const palette = {
|
|
|
63
63
|
ringBrandSoft: { light: "#d6e5ff", dark: "#212224" },
|
|
64
64
|
tileActive: { light: "#ffffff", dark: "#101112" },
|
|
65
65
|
tileActiveShadow: { light: "rgba(0,0,0,0.04)", dark: "rgba(0,0,0,0)" },
|
|
66
|
-
// Dialog's page-dimming backdrop (crisp#677
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
66
|
+
// Dialog's page-dimming backdrop (crisp#677, Attio's own measured value:
|
|
67
|
+
// `rgba(0,0,0,.25)` light, `rgba(0,0,0,.55)` dark — `Dialog`'s own backdrop
|
|
68
|
+
// had shipped fully `transparent`, AUTHORING's Lesson 14 measured the
|
|
69
|
+
// command palette has NO dark backdrop and that got generalized to every
|
|
70
|
+
// `Dialog`, which this corrected).
|
|
71
|
+
//
|
|
72
|
+
// your-moon/crisp#934: light theme moved to seed-design's own scrim
|
|
73
|
+
// (`#00000074` ≈ 45% black, `bg.overlay`) — the owner's flat-dialog
|
|
74
|
+
// directive names "seed's own scrim" specifically, now that elevation is
|
|
75
|
+
// conveyed by the scrim alone (no more frosted-glass shadow/blur). Dark
|
|
76
|
+
// theme's `.55` is UNCHANGED: seed's own token system has no separate
|
|
77
|
+
// dark-theme scrim value to adopt, and `.55` wasn't found wrong -- only
|
|
78
|
+
// light had a sourced replacement value to move to.
|
|
79
|
+
bgScrim: { light: "rgba(0,0,0,0.45)", dark: "rgba(0,0,0,0.55)" },
|
|
80
|
+
// seed-design's `$color.bg.overlay` (BottomSheet/MenuSheet's own backdrop,
|
|
81
|
+
// crisp#911) — kept as its OWN token rather than folded into `bgScrim`
|
|
82
|
+
// above, even though #934 (landed after this one was added) moved
|
|
83
|
+
// `bgScrim`'s LIGHT value to the same `#00000074`: `bgOverlayStrong` is
|
|
84
|
+
// seed's literally theme-CONSTANT value (`color.yaml` gives the same hex
|
|
85
|
+
// for both `theme-light` and `theme-dark`), while `bgScrim` still differs
|
|
86
|
+
// by theme (`.45`/`.55`) -- the two tokens express different design
|
|
87
|
+
// decisions that currently coincide in one theme only.
|
|
88
|
+
bgOverlayStrong: { light: "#00000074", dark: "#00000074" },
|
|
89
|
+
// seed-design's `$color.stroke.neutral-muted` (MenuSheet group divider,
|
|
90
|
+
// crisp#911) -- a faint alpha hairline between grouped rows, theme-flipping
|
|
91
|
+
// (`color.yaml`: `static-black-alpha-300` light / `static-white-alpha-100`
|
|
92
|
+
// dark).
|
|
93
|
+
strokeGroupDivider: { light: "#00000010", dark: "#ffffff17" },
|
|
74
94
|
// The discard-confirm's description line (crisp#677, `Dialog`'s new
|
|
75
95
|
// `variant="alert"`) — measured live, both themes, over `popoverBg`: a
|
|
76
96
|
// theme-relative alpha rather than a flat gray, so it stays legible if
|
package/dist/esm/tokens/scale.js
CHANGED
|
@@ -27,13 +27,22 @@ export const space = {
|
|
|
27
27
|
// (inputs, rows, tiles); full is the pill.
|
|
28
28
|
export const radius = {
|
|
29
29
|
xs: "4px", // seed-design's $radius.r1 — small badge / checkbox box corner
|
|
30
|
-
|
|
30
|
+
// your-moon/crisp#919: was `7px` (Attio's own most-measured
|
|
31
|
+
// radius, 70 occurrences) -- now a pill, matching seed-design's own
|
|
32
|
+
// `chip.yaml` (`$radius.full` at every size, no size-based radius step at
|
|
33
|
+
// all, unlike Button/Badge). Same numeric value as `full` below, kept as
|
|
34
|
+
// its own named token because every one of this token's ~9 consumers
|
|
35
|
+
// (Chip, DataTable/RecordPage/RecordsTable/MultiSelect's own tag/mention
|
|
36
|
+
// rules, TableToolbar/DatePickerField's date-preset chips) reasons about
|
|
37
|
+
// "the chip corner" as its own concept, not literally "a generic pill".
|
|
38
|
+
chip: "999px",
|
|
31
39
|
sm: "6px", // chips, badges
|
|
32
40
|
md: "8px", // inputs, search box
|
|
33
41
|
lg: "9px", // rows, tiles, list items
|
|
34
42
|
xl: "12px", // overlay items (command rows)
|
|
35
43
|
"2xl": "16px", // overlay panels (command surface)
|
|
36
44
|
"3xl": "20px", // seed-design's $radius.r5 — Select's listbox corner (crisp/#867)
|
|
45
|
+
"4xl": "24px", // seed-design's $radius.r6 — BottomSheet's top corner radius (crisp/#911)
|
|
37
46
|
full: "999px", // pills
|
|
38
47
|
};
|
|
39
48
|
// Component sizing — recurring measured dimensions that are neither spacing nor
|
|
@@ -85,3 +94,36 @@ export const weight = {
|
|
|
85
94
|
semibold: "600",
|
|
86
95
|
bold: "700", // seed-design's font-weight-bold, for the seed-styled calendar
|
|
87
96
|
};
|
|
97
|
+
// Size-based letter-spacing ramp for DISPLAY/HEADING type (18px and up —
|
|
98
|
+
// `text.*` above tops out at 16px and stays untracked). crisp's five
|
|
99
|
+
// already-shipped headings tighten as they grow — authshell 20px/-0.4px (2%
|
|
100
|
+
// of size), objectsettings 24px/-0.48px (2%), richtext 32px/-0.6px (1.875%)
|
|
101
|
+
// — all Attio-measured (Inter) and left exactly as they are; this token
|
|
102
|
+
// never touches them, or the button (-0.07px) / table-cell (-0.14px)
|
|
103
|
+
// tracking, which are body sizes, not display type. (statcard's 28px/-0.4px,
|
|
104
|
+
// 1.43%, is a looser outlier — it sets tabular numerals, not prose — and is
|
|
105
|
+
// also left alone.)
|
|
106
|
+
//
|
|
107
|
+
// crisp#909: the study at docs/study/korean/ observed that Naver and Kakao
|
|
108
|
+
// both tighten tracking as type gets bigger, and crisp's OWN large type
|
|
109
|
+
// (EmptyState's 20px title, Dialog's 18px alert title, and any future
|
|
110
|
+
// heading) had no tracking at all — it read as default, not set. The study
|
|
111
|
+
// supplied the PATTERN only, never a number: Naver/Kakao's own values are
|
|
112
|
+
// Korean-specific and were not copied. The numbers below come from rendering
|
|
113
|
+
// real Mongolian Cyrillic ("Эрсдэлийн бүртгэл", "Хяналтын самбар") in VK Sans
|
|
114
|
+
// Display — crisp's own font override, see `font.sans` above — at every
|
|
115
|
+
// display size crisp uses today (18/20/24/26/28/32px), screenshotting six
|
|
116
|
+
// candidates per size (0%, 1%, 1.5%, 2%, 2.5%, 3% of the font-size) and
|
|
117
|
+
// checking for glyph collision. None collided even at 3%, so the pre-existing
|
|
118
|
+
// 2%-of-size rule the five Attio headings already happen to follow was kept
|
|
119
|
+
// as the ramp, rounded to the nearest 0.01px — it reads as deliberately set
|
|
120
|
+
// with headroom to spare, and keeps new display type visually consistent
|
|
121
|
+
// with the Attio-measured headings already shipped.
|
|
122
|
+
export const tracking = {
|
|
123
|
+
"18": "-0.36px",
|
|
124
|
+
"20": "-0.4px",
|
|
125
|
+
"24": "-0.48px",
|
|
126
|
+
"26": "-0.52px",
|
|
127
|
+
"28": "-0.56px",
|
|
128
|
+
"32": "-0.64px",
|
|
129
|
+
};
|
|
@@ -38,6 +38,8 @@ export const semantic = {
|
|
|
38
38
|
bgTileActive: "tileActive",
|
|
39
39
|
shadowTileActive: "tileActiveShadow",
|
|
40
40
|
bgScrim: "bgScrim",
|
|
41
|
+
bgOverlayStrong: "bgOverlayStrong",
|
|
42
|
+
strokeGroupDivider: "strokeGroupDivider",
|
|
41
43
|
fgAlertDescription: "fgAlertDescription",
|
|
42
44
|
bgOverlayFrost: "overlayFrost",
|
|
43
45
|
shadowOverlay1: "shadowOverlay1",
|
package/dist/fileupload.css
CHANGED
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
& .crisp-fileupload-remove:hover { color: var(--crisp-fg-primary); }
|
|
26
26
|
& .crisp-fileupload-remove:focus-visible { outline-color: var(--crisp-stroke-focus-ring); }
|
|
27
27
|
& .crisp-fileupload-errors { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); margin: 0; padding: 0; list-style: none; }
|
|
28
|
-
/* out of flow while empty, so the always-mounted live region costs no
|
|
28
|
+
/* out of flow while empty, so the always-mounted live region costs no
|
|
29
|
+
layout: display:none would take it out of the accessibility tree, which
|
|
30
|
+
is the one thing it exists to be in */
|
|
29
31
|
& .crisp-fileupload-errors:empty { position: absolute; }
|
|
30
32
|
& .crisp-fileupload-error { color: var(--crisp-bg-danger); font-size: var(--crisp-text-md); line-height: 18px; }
|
|
31
33
|
}
|