@crisp-ui-kit/crisp 0.50.0 → 0.52.0
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/cjs/components/composewindow/composewindow.js +144 -0
- package/dist/cjs/components/composewindow/composewindow.recipe.js +45 -0
- package/dist/cjs/components/composewindow/index.js +8 -0
- package/dist/cjs/components/datatable/datatable.js +224 -43
- package/dist/cjs/components/datatable/datatable.recipe.js +37 -3
- package/dist/cjs/components/datatable/index.js +6 -0
- package/dist/cjs/components/datepickerfield/datepickerfield.js +25 -15
- package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +25 -8
- package/dist/cjs/components/dialog/dialog.recipe.js +9 -1
- package/dist/cjs/components/menu/menu.js +4 -2
- package/dist/cjs/components/multiselect/multiselect.recipe.js +14 -8
- package/dist/cjs/components/select/select.js +2 -1
- package/dist/cjs/components/table/table.recipe.js +12 -3
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +1 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +18 -7
- package/dist/cjs/core/caret-rect.js +73 -0
- package/dist/cjs/core/cell-date.js +11 -0
- package/dist/cjs/core/index.js +8 -1
- package/dist/cjs/core/mention.js +32 -0
- package/dist/cjs/core/use-mention-trigger.js +124 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/product/recordpage/recordpage.js +88 -10
- package/dist/cjs/product/recordpage/recordpage.recipe.js +30 -0
- package/dist/cjs/product/recordpanel/recordpanel.js +11 -29
- package/dist/cjs/product/recordpanel/recordpanel.recipe.js +55 -17
- package/dist/cjs/product/recordstable/editable-field.js +113 -0
- package/dist/cjs/product/recordstable/index.js +4 -1
- package/dist/cjs/product/recordstable/recordstable.js +67 -11
- package/dist/cjs/product/recordstable/recordstable.recipe.js +9 -1
- package/dist/cjs/product/recordstable/recordstable.tag.js +8 -3
- package/dist/cjs/product/richtext/index.js +2 -1
- package/dist/cjs/product/richtext/richtext.js +440 -23
- package/dist/cjs/product/richtext/richtext.recipe.js +59 -9
- package/dist/cjs/product/settingsshell/settingsshell.recipe.js +13 -4
- package/dist/cjs/product/sidebar/sidebar.js +15 -3
- package/dist/cjs/product/sidebar/sidebar.recipe.js +21 -2
- package/dist/composewindow.css +24 -0
- package/dist/composewindow.d.ts +7 -0
- package/dist/composewindow.layered.css +26 -0
- package/dist/datatable.css +33 -14
- package/dist/datatable.layered.css +33 -14
- package/dist/datepickerfield.css +9 -4
- package/dist/datepickerfield.layered.css +9 -4
- package/dist/dialog.css +2 -1
- package/dist/dialog.layered.css +2 -1
- package/dist/esm/components/composewindow/composewindow.js +107 -0
- package/dist/esm/components/composewindow/composewindow.recipe.js +42 -0
- package/dist/esm/components/composewindow/index.js +2 -0
- package/dist/esm/components/datatable/datatable.js +226 -45
- package/dist/esm/components/datatable/datatable.recipe.js +37 -3
- package/dist/esm/components/datatable/index.js +6 -0
- package/dist/esm/components/datepickerfield/datepickerfield.js +26 -16
- package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +25 -8
- package/dist/esm/components/dialog/dialog.recipe.js +9 -1
- package/dist/esm/components/menu/menu.js +4 -2
- package/dist/esm/components/multiselect/multiselect.recipe.js +14 -8
- package/dist/esm/components/select/select.js +2 -1
- package/dist/esm/components/table/table.recipe.js +12 -3
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +1 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +18 -7
- package/dist/esm/core/caret-rect.js +70 -0
- package/dist/esm/core/cell-date.js +10 -0
- package/dist/esm/core/index.js +4 -1
- package/dist/esm/core/mention.js +29 -0
- package/dist/esm/core/use-mention-trigger.js +88 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/product/recordpage/recordpage.js +89 -11
- package/dist/esm/product/recordpage/recordpage.recipe.js +30 -0
- package/dist/esm/product/recordpanel/index.js +1 -1
- package/dist/esm/product/recordpanel/recordpanel.js +13 -31
- package/dist/esm/product/recordpanel/recordpanel.recipe.js +56 -18
- package/dist/esm/product/recordstable/editable-field.js +76 -0
- package/dist/esm/product/recordstable/index.js +1 -0
- package/dist/esm/product/recordstable/recordstable.js +69 -13
- package/dist/esm/product/recordstable/recordstable.recipe.js +9 -1
- package/dist/esm/product/recordstable/recordstable.tag.js +8 -3
- package/dist/esm/product/richtext/index.js +1 -1
- package/dist/esm/product/richtext/richtext.js +439 -22
- package/dist/esm/product/richtext/richtext.recipe.js +60 -10
- package/dist/esm/product/settingsshell/settingsshell.recipe.js +13 -4
- package/dist/esm/product/sidebar/sidebar.js +15 -3
- package/dist/esm/product/sidebar/sidebar.recipe.js +21 -2
- package/dist/multiselect.css +8 -8
- package/dist/multiselect.layered.css +8 -8
- package/dist/recordpage.css +25 -13
- package/dist/recordpage.layered.css +25 -13
- package/dist/recordpanel.css +43 -20
- package/dist/recordpanel.layered.css +43 -20
- package/dist/recordstable.css +9 -2
- package/dist/recordstable.layered.css +9 -2
- package/dist/richtext.css +31 -5
- package/dist/richtext.layered.css +31 -5
- package/dist/settingsshell.css +2 -2
- package/dist/settingsshell.layered.css +2 -2
- package/dist/sidebar.css +7 -2
- package/dist/sidebar.layered.css +7 -2
- package/dist/styles.css +199 -74
- package/dist/styles.layered.css +199 -74
- package/dist/table.css +4 -2
- package/dist/table.layered.css +4 -2
- package/dist/tabletoolbar.css +1 -1
- package/dist/tabletoolbar.layered.css +1 -1
- package/dist/types/components/composewindow/composewindow.d.ts +80 -0
- package/dist/types/components/composewindow/composewindow.recipe.d.ts +3 -0
- package/dist/types/components/composewindow/index.d.ts +2 -0
- package/dist/types/components/datatable/datatable.d.ts +58 -4
- package/dist/types/components/datatable/index.d.ts +1 -1
- package/dist/types/components/datepickerfield/datepickerfield.d.ts +18 -4
- package/dist/types/components/menu/menu.d.ts +10 -1
- package/dist/types/components/select/select.d.ts +6 -1
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +29 -5
- package/dist/types/core/caret-rect.d.ts +16 -0
- package/dist/types/core/cell-date.d.ts +17 -0
- package/dist/types/core/index.d.ts +4 -1
- package/dist/types/core/mention.d.ts +27 -0
- package/dist/types/core/use-mention-trigger.d.ts +41 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/product/recordpage/recordpage.d.ts +59 -1
- package/dist/types/product/recordpanel/index.d.ts +1 -1
- package/dist/types/product/recordpanel/recordpanel.d.ts +81 -17
- package/dist/types/product/recordstable/editable-field.d.ts +48 -0
- package/dist/types/product/recordstable/index.d.ts +1 -0
- package/dist/types/product/recordstable/recordstable.d.ts +29 -0
- package/dist/types/product/recordstable/recordstable.tag.d.ts +8 -3
- package/dist/types/product/richtext/index.d.ts +1 -1
- package/dist/types/product/richtext/richtext.d.ts +168 -7
- package/dist/types/product/sidebar/sidebar.d.ts +60 -4
- package/package.json +6 -1
|
@@ -3,15 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.settingsshellRecipe = void 0;
|
|
4
4
|
const core_1 = require("../../core");
|
|
5
5
|
const tokens_1 = require("../../tokens");
|
|
6
|
-
// The Settings layout — measured from a Settings screen: a
|
|
6
|
+
// The Settings layout — measured from a Settings screen: a 275px left nav with a
|
|
7
7
|
// back header, a search field, and grouped nav items (active row raised), beside
|
|
8
|
-
// a scrolling content pane.
|
|
8
|
+
// a scrolling content pane. Fills whatever height its container gives it, the
|
|
9
|
+
// way Attio's settings nav runs the container's full height (#435) — the root
|
|
10
|
+
// used to carry only `min-height: 0` (lets it *shrink* inside a flex ancestor)
|
|
11
|
+
// with no `height` of its own, so it sized to its content instead of filling,
|
|
12
|
+
// and the nav's own border-inline-end stopped where the nav content ended
|
|
13
|
+
// rather than running the full height. A consumer (GRC) had to paper over it
|
|
14
|
+
// with an external `h-full` class.
|
|
15
|
+
// 275px, not the 256px this recipe used to claim (#410) — the same width
|
|
16
|
+
// AppShell's own Attio-measured `defaultWidth` already uses for the main
|
|
17
|
+
// sidebar pane; re-measured live at app.attio.com/<workspace>/settings/account.
|
|
9
18
|
const config = (0, core_1.defineRecipe)({
|
|
10
19
|
name: "settingsshell",
|
|
11
|
-
base: `display: flex; min-height: 0; box-sizing: border-box; font-family: ${tokens_1.contract.font.sans}; color: ${tokens_1.contract.fgPrimary};
|
|
20
|
+
base: `display: flex; height: 100%; min-height: 0; box-sizing: border-box; font-family: ${tokens_1.contract.font.sans}; color: ${tokens_1.contract.fgPrimary};
|
|
12
21
|
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
13
22
|
|
|
14
|
-
& .crisp-settingsshell-nav { display: flex; flex-direction: column; width:
|
|
23
|
+
& .crisp-settingsshell-nav { display: flex; flex-direction: column; width: 275px; flex-shrink: 0; min-height: 0; padding: ${tokens_1.contract.space["3"]}; border-inline-end: 1px solid ${tokens_1.contract.borderSubtle}; }
|
|
15
24
|
& .crisp-settingsshell-back { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; height: 28px; padding: 0 ${tokens_1.contract.space["1"]}; margin: 0 0 ${tokens_1.contract.space["2"]}; border: none; background: none; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.semibold}; color: ${tokens_1.contract.fgPrimary}; cursor: pointer; border-radius: ${tokens_1.contract.radius.md}; }
|
|
16
25
|
& .crisp-settingsshell-back svg { color: ${tokens_1.contract.fgMuted}; }
|
|
17
26
|
& .crisp-settingsshell-back:hover svg { color: ${tokens_1.contract.fgPrimary}; }
|
|
@@ -40,10 +40,20 @@ const React = __importStar(require("react"));
|
|
|
40
40
|
const avatar_1 = require("../../components/avatar");
|
|
41
41
|
const badge_1 = require("../../components/badge");
|
|
42
42
|
const button_1 = require("../../components/button");
|
|
43
|
+
const menu_1 = require("../../components/menu");
|
|
43
44
|
const core_1 = require("../../core");
|
|
44
45
|
const command_1 = require("../command");
|
|
45
46
|
const navitem_1 = require("../navitem");
|
|
46
47
|
const sidebar_recipe_1 = require("./sidebar.recipe");
|
|
48
|
+
/** A plain `{ gettingStartedLabel, inviteLabel, trialText, trialActionLabel }`
|
|
49
|
+
* object (not a React element) — the shape `footer` renders as Attio's fixed
|
|
50
|
+
* onboarding/invite/trial copy, as opposed to a caller-supplied override. */
|
|
51
|
+
function isSidebarFooterCopy(value) {
|
|
52
|
+
return (typeof value === "object" &&
|
|
53
|
+
value !== null &&
|
|
54
|
+
!React.isValidElement(value) &&
|
|
55
|
+
"gettingStartedLabel" in value);
|
|
56
|
+
}
|
|
47
57
|
const IconHome = ({ className }) => ((0, jsx_runtime_1.jsx)(lucide_react_1.House, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
|
|
48
58
|
const IconTasks = ({ className }) => ((0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
|
|
49
59
|
const IconNotes = ({ className }) => ((0, jsx_runtime_1.jsx)(lucide_react_1.FileText, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
|
|
@@ -117,7 +127,7 @@ exports.ATTIO_SIDEBAR_FOOTER = {
|
|
|
117
127
|
function SectionHeader({ label, open, onToggle, }) {
|
|
118
128
|
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [(0, jsx_runtime_1.jsx)(IconChevronMini, {}), (0, jsx_runtime_1.jsx)("span", { children: label })] }));
|
|
119
129
|
}
|
|
120
|
-
exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = exports.ATTIO_SIDEBAR_WORKSPACE, items = exports.ATTIO_SIDEBAR_ITEMS, recordsSection = exports.ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = exports.ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = exports.ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = exports.ATTIO_SIDEBAR_FOOTER, commandItems, commandOpen: commandOpenProp, defaultCommandOpen, onCommandOpenChange, commandShortcut = true, "aria-label": ariaLabel = "Main", ...props }, ref) => {
|
|
130
|
+
exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = exports.ATTIO_SIDEBAR_WORKSPACE, workspaceMenu, items = exports.ATTIO_SIDEBAR_ITEMS, recordsSection = exports.ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = exports.ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = exports.ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = exports.ATTIO_SIDEBAR_FOOTER, commandItems, commandOpen: commandOpenProp, defaultCommandOpen, onCommandOpenChange, commandShortcut = true, "aria-label": ariaLabel = "Main", ...props }, ref) => {
|
|
121
131
|
// Seeded once, on mount, from whatever groups start collapsed — not
|
|
122
132
|
// reactive to `groups` changing later, matching every other piece of
|
|
123
133
|
// this component's state (recordsSection/listsSection/chatsSection have
|
|
@@ -163,13 +173,15 @@ exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, coll
|
|
|
163
173
|
return next;
|
|
164
174
|
});
|
|
165
175
|
}, []);
|
|
166
|
-
|
|
176
|
+
const workspaceTrigger = ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-ws", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-ws-name", children: workspace.name }), (0, jsx_runtime_1.jsx)(IconChevronDown, {})] }));
|
|
177
|
+
return ((0, jsx_runtime_1.jsxs)(core_1.Primitive.nav, { ref: ref, asChild: asChild, "aria-label": ariaLabel, className: (0, sidebar_recipe_1.sidebarRecipe)({ className }), ...props, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-ws-row", children: [workspaceMenu ? ((0, jsx_runtime_1.jsx)(menu_1.Menu, { trigger: workspaceTrigger, className: "crisp-sidebar-ws-menuroot", contentClassName: "crisp-sidebar-ws-menu-content", children: workspaceMenu })) : (workspaceTrigger), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "ghost", size: "sm", shape: "icon", className: "crisp-sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: (0, jsx_runtime_1.jsx)(IconCollapsePanel, {}) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-searchrow", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-search", onClick: () => setCommandOpen(true), children: [(0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-search-label", children: searchLabel }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-search-kbd", children: "\u2318K" })] }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: (0, jsx_runtime_1.jsx)(IconInbox, {}) })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-divider" }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: href ?? "#", active: active, icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), trailing: trailing, onClick: onSelect, children: label }, href ?? label))) }), groups ? (
|
|
167
178
|
// #325: navigation as data, reusing the exact section markup/
|
|
168
179
|
// recipe above — SectionHeader + .crisp-sidebar-section — instead
|
|
169
180
|
// of the three fixed Attio sections.
|
|
170
181
|
groups.map((group) => {
|
|
171
182
|
const groupKey = group.key ?? group.label;
|
|
172
183
|
return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: group.label, open: !collapsedSections.has(groupKey), onToggle: () => toggleSection(groupKey) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has(groupKey) ? "" : undefined, children: group.items.map((item) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: item.href ?? "#", active: item.active, icon: item.icon ? (0, jsx_runtime_1.jsx)(item.icon, {}) : undefined, trailing: item.count, onClick: item.onSelect, children: item.label }, item.href ?? item.label))) })] }, groupKey));
|
|
173
|
-
})) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: tone, children: (0, jsx_runtime_1.jsx)(Glyph, {}) }), children: label }, label))) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon
|
|
184
|
+
})) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: tone, children: (0, jsx_runtime_1.jsx)(Glyph, {}) }), children: label }, label))) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect, }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: href ?? "#", active: active, icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), trailing: trailing, onClick: onSelect, children: label }, href ?? label))) })] })), footer !== false &&
|
|
185
|
+
(isSidebarFooterCopy(footer) ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-footer", children: [(0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", className: "crisp-sidebar-pill", children: footer.gettingStartedLabel }), (0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(IconInvite, {}), children: footer.inviteLabel }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-divider" }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-trial", children: [(0, jsx_runtime_1.jsx)("span", { children: footer.trialText }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] })) : ((0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-footer", children: footer }))), (0, jsx_runtime_1.jsx)(command_1.Command, { open: commandOpen, onOpenChange: setCommandOpen, items: commandItems })] }));
|
|
174
186
|
});
|
|
175
187
|
exports.Sidebar.displayName = "Sidebar";
|
|
@@ -3,6 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.sidebarRecipe = void 0;
|
|
4
4
|
const core_1 = require("../../core");
|
|
5
5
|
const tokens_1 = require("../../tokens");
|
|
6
|
+
// The workspace switcher's menu (#415) is 280px wide on app.attio.com --
|
|
7
|
+
// measured live via getComputedStyle on its [role="menu"], not derived from
|
|
8
|
+
// the trigger's own width (which reads 295-296px including its collapse
|
|
9
|
+
// button) or from Menu's generic 200px content-sized min-width. Everything
|
|
10
|
+
// else about the popover -- 4px padding, 12px radius, 32px/8px-radius items,
|
|
11
|
+
// 14px/500 labels, the layered shadow -- already matches Menu's own measured
|
|
12
|
+
// chrome, confirmed against this specific menu rather than assumed from it.
|
|
6
13
|
const config = (0, core_1.defineRecipe)({
|
|
7
14
|
name: "sidebar",
|
|
8
15
|
base: `box-sizing: border-box; display: flex; flex-direction: column; width: 275px; height: 100%; background: ${tokens_1.contract.bgSidebar}
|
|
@@ -15,10 +22,12 @@ const config = (0, core_1.defineRecipe)({
|
|
|
15
22
|
& .crisp-sidebar-section-header { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; width: 100%; margin: 0; border: none; background: none; padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["0.5"]}; font-family: ${tokens_1.contract.font.sans}; font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; cursor: pointer; }
|
|
16
23
|
& .crisp-sidebar-section-header svg { flex-shrink: 0; transition: transform .15s ease; }
|
|
17
24
|
& .crisp-sidebar-section-header[data-collapsed] svg { transform: rotate(-90deg); }
|
|
18
|
-
& .crisp-sidebar-ws { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: ${tokens_1.contract.size.header}; padding: 0 ${tokens_1.contract.space["2"]};
|
|
25
|
+
& .crisp-sidebar-ws-row { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: ${tokens_1.contract.size.header}; padding: 0 ${tokens_1.contract.space["2"]}; }
|
|
26
|
+
& .crisp-sidebar-ws-menuroot { display: flex; flex: 1; min-width: 0; }
|
|
27
|
+
& .crisp-sidebar-ws { box-sizing: border-box; display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; flex: 1; min-width: 0; height: 100%; margin: 0; padding: 0; border: none; background: none; border-radius: ${tokens_1.contract.radius.lg}; font-family: inherit; font-size: inherit; color: inherit; text-align: start; cursor: pointer; transition: background-color 0.16s; }
|
|
19
28
|
& .crisp-sidebar-ws:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 5%, transparent); }
|
|
20
29
|
& .crisp-sidebar-ws-name { font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.semibold}; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
21
|
-
& .crisp-sidebar-ws-collapse {
|
|
30
|
+
& .crisp-sidebar-ws-collapse { flex-shrink: 0; }
|
|
22
31
|
& .crisp-sidebar-searchrow { display: flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; margin-top: ${tokens_1.contract.space["2.5"]}; padding-inline-end: ${tokens_1.contract.space["2"]}; }
|
|
23
32
|
& .crisp-sidebar-footer { margin-top: auto; display: flex; flex-direction: column; gap: ${tokens_1.contract.space["0.5"]}; }
|
|
24
33
|
& .crisp-sidebar-trial { display: flex; align-items: center; justify-content: space-between; gap: ${tokens_1.contract.space["2"]}; padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]}; font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.fgMuted}; }
|
|
@@ -32,6 +41,16 @@ const config = (0, core_1.defineRecipe)({
|
|
|
32
41
|
& .crisp-sidebar-newlist:hover { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
|
|
33
42
|
|
|
34
43
|
${core_1.rtlFlipCss}
|
|
44
|
+
`,
|
|
45
|
+
// Menu's own floating content is portaled to document.body (#357), so a
|
|
46
|
+
// `&`-nested rule here -- which compiles to a real descendant combinator
|
|
47
|
+
// scoped to .crisp-sidebar -- matches nothing once the popover leaves the
|
|
48
|
+
// sidebar's DOM subtree. Same fix Menu's own recipe already uses: the
|
|
49
|
+
// content's override lives in globalCss instead, keyed off the
|
|
50
|
+
// \`contentClassName\` Menu now hands the workspace switcher's portaled
|
|
51
|
+
// surface (#415).
|
|
52
|
+
globalCss: `
|
|
53
|
+
.crisp-sidebar-ws-menu-content { width: 280px; }
|
|
35
54
|
`,
|
|
36
55
|
variants: {},
|
|
37
56
|
defaultVariants: {},
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.crisp-composewindow {
|
|
2
|
+
display: contents; font-family: var(--crisp-font-sans);
|
|
3
|
+
& .crisp-composewindow-backdrop { position: fixed; inset: 0; z-index: 60; background: transparent; pointer-events: none; }
|
|
4
|
+
& .crisp-composewindow-positioner { position: fixed; inset: 0; z-index: 61; display: flex; align-items: flex-start; justify-content: center; padding: 15vh var(--crisp-space-4) var(--crisp-space-4); pointer-events: none; }
|
|
5
|
+
& .crisp-composewindow-content { pointer-events: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 90vw; max-height: 74vh; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-2xl); background: color-mix(in srgb, var(--crisp-bg-overlay-frost) 60%, transparent); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); box-shadow: 0 8px 28px -6px var(--crisp-shadow-overlay1), 0 18px 88px -4px var(--crisp-shadow-overlay2), 0 0 1px var(--crisp-shadow-overlay3); opacity: 1; transform: scale(1); transition: opacity 0.2s ease, transform 0.2s ease; @starting-style { opacity: 0; transform: scale(0.97); } }
|
|
6
|
+
& .crisp-composewindow-content[data-minimized] { visibility: hidden; opacity: 0; }
|
|
7
|
+
& .crisp-composewindow-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); overflow: hidden; }
|
|
8
|
+
& .crisp-composewindow-header { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-2); flex-shrink: 0; height: 52px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
|
|
9
|
+
& .crisp-composewindow-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
10
|
+
& .crisp-composewindow-actions { display: flex; align-items: center; gap: var(--crisp-space-0-5); flex-shrink: 0; }
|
|
11
|
+
& .crisp-composewindow-action-btn { 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: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
|
|
12
|
+
& .crisp-composewindow-action-btn:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
|
|
13
|
+
& .crisp-composewindow-action-btn[data-state="on"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: var(--crisp-bg-brand-solid); }
|
|
14
|
+
& .crisp-composewindow-body { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; padding: var(--crisp-space-5) var(--crisp-space-4); }
|
|
15
|
+
& .crisp-composewindow-content-body { display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@media (prefers-reduced-motion: reduce) {
|
|
19
|
+
.crisp-composewindow, .crisp-composewindow * {
|
|
20
|
+
animation-duration: 0.01ms !important;
|
|
21
|
+
animation-iteration-count: 1 !important;
|
|
22
|
+
transition-duration: 0.01ms !important;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@layer crisp-components {
|
|
2
|
+
.crisp-composewindow {
|
|
3
|
+
display: contents; font-family: var(--crisp-font-sans);
|
|
4
|
+
& .crisp-composewindow-backdrop { position: fixed; inset: 0; z-index: 60; background: transparent; pointer-events: none; }
|
|
5
|
+
& .crisp-composewindow-positioner { position: fixed; inset: 0; z-index: 61; display: flex; align-items: flex-start; justify-content: center; padding: 15vh var(--crisp-space-4) var(--crisp-space-4); pointer-events: none; }
|
|
6
|
+
& .crisp-composewindow-content { pointer-events: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 90vw; max-height: 74vh; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-2xl); background: color-mix(in srgb, var(--crisp-bg-overlay-frost) 60%, transparent); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); box-shadow: 0 8px 28px -6px var(--crisp-shadow-overlay1), 0 18px 88px -4px var(--crisp-shadow-overlay2), 0 0 1px var(--crisp-shadow-overlay3); opacity: 1; transform: scale(1); transition: opacity 0.2s ease, transform 0.2s ease; @starting-style { opacity: 0; transform: scale(0.97); } }
|
|
7
|
+
& .crisp-composewindow-content[data-minimized] { visibility: hidden; opacity: 0; }
|
|
8
|
+
& .crisp-composewindow-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); overflow: hidden; }
|
|
9
|
+
& .crisp-composewindow-header { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-2); flex-shrink: 0; height: 52px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
|
|
10
|
+
& .crisp-composewindow-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
11
|
+
& .crisp-composewindow-actions { display: flex; align-items: center; gap: var(--crisp-space-0-5); flex-shrink: 0; }
|
|
12
|
+
& .crisp-composewindow-action-btn { 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: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
|
|
13
|
+
& .crisp-composewindow-action-btn:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
|
|
14
|
+
& .crisp-composewindow-action-btn[data-state="on"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: var(--crisp-bg-brand-solid); }
|
|
15
|
+
& .crisp-composewindow-body { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; padding: var(--crisp-space-5) var(--crisp-space-4); }
|
|
16
|
+
& .crisp-composewindow-content-body { display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@media (prefers-reduced-motion: reduce) {
|
|
20
|
+
.crisp-composewindow, .crisp-composewindow * {
|
|
21
|
+
animation-duration: 0.01ms !important;
|
|
22
|
+
animation-iteration-count: 1 !important;
|
|
23
|
+
transition-duration: 0.01ms !important;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/datatable.css
CHANGED
|
@@ -32,10 +32,13 @@
|
|
|
32
32
|
& .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
|
|
33
33
|
& .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
34
34
|
& .crisp-datatable-sort { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
/* A right-aligned column's header keeps its field-icon + label packed at
|
|
36
|
+
the START, matching its own values only in that the VALUES right-align
|
|
37
|
+
-- Attio's own right-aligned header (measured live: "Twitter follower
|
|
38
|
+
count") keeps the icon+label left and only the trailing column-menu icon sits pinned to the right edge, independent of the label's width
|
|
39
|
+
(#406, reversing #393's own justify-content: flex-end here). The chevron's own margin-inline-start: auto below already pins IT to the
|
|
40
|
+
end of a flex-start row with no override needed -- that's what pins the trailing icon on Attio too. */
|
|
37
41
|
& .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); opacity: 0; transition: opacity .12s; }
|
|
38
|
-
& th[data-align="right"] .crisp-datatable-chevron { margin-inline-start: 0; order: -1; }
|
|
39
42
|
& .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
|
|
40
43
|
& .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
|
|
41
44
|
/* sticky first column(s) + horizontal scroll shadow — RecordsTable's feel */
|
|
@@ -58,6 +61,28 @@
|
|
|
58
61
|
& .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
|
|
59
62
|
& .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; vertical-align: middle; }
|
|
60
63
|
& .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
|
|
64
|
+
/* An async onEdit's in-flight state (#385) -- the field/picker dims and
|
|
65
|
+
stops accepting input rather than disappearing, so the just-typed or
|
|
66
|
+
just-chosen value stays visible while its commit is pending. Same
|
|
67
|
+
treatment Input already gives its own disabled state. */
|
|
68
|
+
& .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
69
|
+
& .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
|
|
70
|
+
/* The rejection's message, beside the cell's own content -- a sibling in
|
|
71
|
+
the TableCell, never nested inside a column's own render output, so a hover-only wrapper (a Tooltip, say) around a custom render can't hide
|
|
72
|
+
it. Block-level: the row grows to fit it rather than floating over
|
|
73
|
+
the row below, so it can never end up clipped by the scroll
|
|
74
|
+
container's overflow the way a position:fixed escape would need guarding against. */
|
|
75
|
+
& .crisp-datatable-edit-error { display: block; padding: var(--crisp-space-0-5) 0 0; font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
|
|
76
|
+
/* editor: "select"/"switch" -- always live, no separate edit mode, so the cell IS the picker: same chromeless-fill treatment as RecordsTable's own --picker cells (measured there already; DataTable
|
|
77
|
+
shares RecordsTable's grid, Golden Rule 0). */
|
|
78
|
+
& .crisp-datatable-td--picker { padding: 0; }
|
|
79
|
+
& .crisp-datatable-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
80
|
+
& .crisp-datatable-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; height: 100%; min-height: 0; border-radius: 0; box-shadow: none; padding: 0; }
|
|
81
|
+
& .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
|
|
82
|
+
/* reference picker: a leading 16px avatar per option, plus a trailing "Create X" row when the consumer supplies one (#449) -- same measured values as RecordsTable's own .crisp-recordstable-refoption*. */
|
|
83
|
+
& .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
84
|
+
& .crisp-datatable-refoption-avatar { flex-shrink: 0; }
|
|
85
|
+
& .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
|
|
61
86
|
& .crisp-datatable-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; color: var(--crisp-fg-muted); font-size: var(--crisp-text-lg); }
|
|
62
87
|
/* fixed layout so column widths (and resize) are exact — like RecordsTable */
|
|
63
88
|
& table { table-layout: fixed; }
|
|
@@ -69,12 +94,7 @@
|
|
|
69
94
|
& .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
|
|
70
95
|
& .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
71
96
|
& .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: var(--crisp-radius-sm); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
|
|
72
|
-
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
|
|
73
|
-
& .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
|
|
74
|
-
& .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
|
|
75
|
-
& .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
|
|
76
|
-
& .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
|
|
77
|
-
& .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
|
|
97
|
+
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); } & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); } & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); } & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); } & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); } & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
|
|
78
98
|
& .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
|
|
79
99
|
& .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
|
|
80
100
|
& .crisp-datatable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
|
|
@@ -87,11 +107,10 @@
|
|
|
87
107
|
& .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
|
|
88
108
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
89
109
|
& .crisp-datatable-muted { color: var(--crisp-fg-muted); }
|
|
90
|
-
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
91
|
-
hairline doubling as the drop indicator (it tints toward the brand
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
pointer-events implementation. */
|
|
110
|
+
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
111
|
+
a leading 20px grip column, a 16px grip icon, and the row's own hairline doubling as the drop indicator (it tints toward the brand colour as the active drop target). The affordance + geometry are measured; the drag MECHANISM is crisp's existing house idiom -- HTML5
|
|
112
|
+
draggable + dataTransfer, the same one column reorder already uses
|
|
113
|
+
(RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
|
|
95
114
|
& .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
|
|
96
115
|
& .crisp-datatable-grip { display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%; margin: 0; border: none; border-radius: 0; background: none; font-family: inherit; color: var(--crisp-fg-muted); cursor: grab; }
|
|
97
116
|
& .crisp-datatable-grip:active { cursor: grabbing; }
|
|
@@ -33,10 +33,13 @@
|
|
|
33
33
|
& .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
|
|
34
34
|
& .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
35
35
|
& .crisp-datatable-sort { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/* A right-aligned column's header keeps its field-icon + label packed at
|
|
37
|
+
the START, matching its own values only in that the VALUES right-align
|
|
38
|
+
-- Attio's own right-aligned header (measured live: "Twitter follower
|
|
39
|
+
count") keeps the icon+label left and only the trailing column-menu icon sits pinned to the right edge, independent of the label's width
|
|
40
|
+
(#406, reversing #393's own justify-content: flex-end here). The chevron's own margin-inline-start: auto below already pins IT to the
|
|
41
|
+
end of a flex-start row with no override needed -- that's what pins the trailing icon on Attio too. */
|
|
38
42
|
& .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); opacity: 0; transition: opacity .12s; }
|
|
39
|
-
& th[data-align="right"] .crisp-datatable-chevron { margin-inline-start: 0; order: -1; }
|
|
40
43
|
& .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
|
|
41
44
|
& .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
|
|
42
45
|
/* sticky first column(s) + horizontal scroll shadow — RecordsTable's feel */
|
|
@@ -59,6 +62,28 @@
|
|
|
59
62
|
& .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
|
|
60
63
|
& .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; vertical-align: middle; }
|
|
61
64
|
& .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
|
|
65
|
+
/* An async onEdit's in-flight state (#385) -- the field/picker dims and
|
|
66
|
+
stops accepting input rather than disappearing, so the just-typed or
|
|
67
|
+
just-chosen value stays visible while its commit is pending. Same
|
|
68
|
+
treatment Input already gives its own disabled state. */
|
|
69
|
+
& .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
70
|
+
& .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
|
|
71
|
+
/* The rejection's message, beside the cell's own content -- a sibling in
|
|
72
|
+
the TableCell, never nested inside a column's own render output, so a hover-only wrapper (a Tooltip, say) around a custom render can't hide
|
|
73
|
+
it. Block-level: the row grows to fit it rather than floating over
|
|
74
|
+
the row below, so it can never end up clipped by the scroll
|
|
75
|
+
container's overflow the way a position:fixed escape would need guarding against. */
|
|
76
|
+
& .crisp-datatable-edit-error { display: block; padding: var(--crisp-space-0-5) 0 0; font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
|
|
77
|
+
/* editor: "select"/"switch" -- always live, no separate edit mode, so the cell IS the picker: same chromeless-fill treatment as RecordsTable's own --picker cells (measured there already; DataTable
|
|
78
|
+
shares RecordsTable's grid, Golden Rule 0). */
|
|
79
|
+
& .crisp-datatable-td--picker { padding: 0; }
|
|
80
|
+
& .crisp-datatable-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
|
|
81
|
+
& .crisp-datatable-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; height: 100%; min-height: 0; border-radius: 0; box-shadow: none; padding: 0; }
|
|
82
|
+
& .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
|
|
83
|
+
/* reference picker: a leading 16px avatar per option, plus a trailing "Create X" row when the consumer supplies one (#449) -- same measured values as RecordsTable's own .crisp-recordstable-refoption*. */
|
|
84
|
+
& .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
85
|
+
& .crisp-datatable-refoption-avatar { flex-shrink: 0; }
|
|
86
|
+
& .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
|
|
62
87
|
& .crisp-datatable-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; color: var(--crisp-fg-muted); font-size: var(--crisp-text-lg); }
|
|
63
88
|
/* fixed layout so column widths (and resize) are exact — like RecordsTable */
|
|
64
89
|
& table { table-layout: fixed; }
|
|
@@ -70,12 +95,7 @@
|
|
|
70
95
|
& .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
|
|
71
96
|
& .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
72
97
|
& .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: var(--crisp-radius-sm); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
|
|
73
|
-
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
|
|
74
|
-
& .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
|
|
75
|
-
& .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
|
|
76
|
-
& .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
|
|
77
|
-
& .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
|
|
78
|
-
& .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
|
|
98
|
+
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); } & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); } & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); } & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); } & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); } & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
|
|
79
99
|
& .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
|
|
80
100
|
& .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
|
|
81
101
|
& .crisp-datatable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
|
|
@@ -88,11 +108,10 @@
|
|
|
88
108
|
& .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
|
|
89
109
|
& .crisp-datatable-num { font-variant-numeric: tabular-nums; }
|
|
90
110
|
& .crisp-datatable-muted { color: var(--crisp-fg-muted); }
|
|
91
|
-
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
92
|
-
hairline doubling as the drop indicator (it tints toward the brand
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
pointer-events implementation. */
|
|
111
|
+
/* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
|
|
112
|
+
a leading 20px grip column, a 16px grip icon, and the row's own hairline doubling as the drop indicator (it tints toward the brand colour as the active drop target). The affordance + geometry are measured; the drag MECHANISM is crisp's existing house idiom -- HTML5
|
|
113
|
+
draggable + dataTransfer, the same one column reorder already uses
|
|
114
|
+
(RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
|
|
96
115
|
& .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
|
|
97
116
|
& .crisp-datatable-grip { display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%; margin: 0; border: none; border-radius: 0; background: none; font-family: inherit; color: var(--crisp-fg-muted); cursor: grab; }
|
|
98
117
|
& .crisp-datatable-grip:active { cursor: grabbing; }
|
package/dist/datepickerfield.css
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
.crisp-datepickerfield {
|
|
2
2
|
display: inline-flex; font-family: var(--crisp-font-sans);
|
|
3
|
-
& .crisp-datepickerfield-
|
|
4
|
-
& .crisp-datepickerfield-
|
|
5
|
-
& .crisp-datepickerfield-
|
|
6
|
-
& .crisp-datepickerfield-trigger
|
|
3
|
+
& .crisp-datepickerfield-box { display: inline-flex; align-items: center; box-sizing: border-box; height: 32px; margin: 0; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); }
|
|
4
|
+
& .crisp-datepickerfield-box[data-ghost] { background: transparent; box-shadow: none; }
|
|
5
|
+
& .crisp-datepickerfield-box[data-ghost]:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
|
|
6
|
+
& .crisp-datepickerfield-trigger { display: inline-flex; align-items: center; gap: var(--crisp-space-2); height: 100%; padding: 0 var(--crisp-space-2-5); margin: 0; border: none; background: none; color: inherit; font-family: inherit; font-size: inherit; white-space: nowrap; cursor: pointer; }
|
|
7
|
+
& .crisp-datepickerfield-box[data-placeholder] .crisp-datepickerfield-trigger { color: var(--crisp-fg-muted); }
|
|
7
8
|
& .crisp-datepickerfield-trigger svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
9
|
+
/* Reuses TableToolbar's measured filter-chip remove button
|
|
10
|
+
(tabletoolbar.chrome.ts's .crisp-tabletoolbar-fchip-x) -- the same small inline "clear this value" affordance, not a freshly-measured one: no live Attio composition for a DatePickerField-equivalent clear was reachable this round (#394). */
|
|
11
|
+
& .crisp-datepickerfield-clear { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-inline-end: var(--crisp-space-1); padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
|
|
12
|
+
& .crisp-datepickerfield-clear:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); color: var(--crisp-fg-primary); }
|
|
8
13
|
& .crisp-datepickerfield-panel { padding: var(--crisp-space-2); width: 300px; }
|
|
9
14
|
& .crisp-datepickerfield-presets { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-2); }
|
|
10
15
|
& .crisp-datepickerfield-preset { height: var(--crisp-size-chip); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-chip); background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-sm); cursor: pointer; }
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
@layer crisp-components {
|
|
2
2
|
.crisp-datepickerfield {
|
|
3
3
|
display: inline-flex; font-family: var(--crisp-font-sans);
|
|
4
|
-
& .crisp-datepickerfield-
|
|
5
|
-
& .crisp-datepickerfield-
|
|
6
|
-
& .crisp-datepickerfield-
|
|
7
|
-
& .crisp-datepickerfield-trigger
|
|
4
|
+
& .crisp-datepickerfield-box { display: inline-flex; align-items: center; box-sizing: border-box; height: 32px; margin: 0; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); }
|
|
5
|
+
& .crisp-datepickerfield-box[data-ghost] { background: transparent; box-shadow: none; }
|
|
6
|
+
& .crisp-datepickerfield-box[data-ghost]:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
|
|
7
|
+
& .crisp-datepickerfield-trigger { display: inline-flex; align-items: center; gap: var(--crisp-space-2); height: 100%; padding: 0 var(--crisp-space-2-5); margin: 0; border: none; background: none; color: inherit; font-family: inherit; font-size: inherit; white-space: nowrap; cursor: pointer; }
|
|
8
|
+
& .crisp-datepickerfield-box[data-placeholder] .crisp-datepickerfield-trigger { color: var(--crisp-fg-muted); }
|
|
8
9
|
& .crisp-datepickerfield-trigger svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
|
|
10
|
+
/* Reuses TableToolbar's measured filter-chip remove button
|
|
11
|
+
(tabletoolbar.chrome.ts's .crisp-tabletoolbar-fchip-x) -- the same small inline "clear this value" affordance, not a freshly-measured one: no live Attio composition for a DatePickerField-equivalent clear was reachable this round (#394). */
|
|
12
|
+
& .crisp-datepickerfield-clear { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-inline-end: var(--crisp-space-1); padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
|
|
13
|
+
& .crisp-datepickerfield-clear:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); color: var(--crisp-fg-primary); }
|
|
9
14
|
& .crisp-datepickerfield-panel { padding: var(--crisp-space-2); width: 300px; }
|
|
10
15
|
& .crisp-datepickerfield-presets { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-2); }
|
|
11
16
|
& .crisp-datepickerfield-preset { height: var(--crisp-size-chip); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-chip); background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-sm); cursor: pointer; }
|
package/dist/dialog.css
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
& .crisp-dialog-field-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
|
|
16
16
|
& .crisp-dialog-more { display: inline-flex; align-items: center; gap: var(--crisp-space-2); margin-inline-end: auto; border: none; background: none; padding: 0; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); cursor: pointer; }
|
|
17
17
|
& .crisp-dialog-submit { display: inline-flex; align-items: center; gap: var(--crisp-space-2); height: 28px; padding: 0 var(--crisp-space-1) 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
18
|
-
& .crisp-dialog-submit-key { display: inline-flex; align-items: center;
|
|
18
|
+
& .crisp-dialog-submit-key { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
|
|
19
|
+
& .crisp-dialog-submit-key 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: var(--crisp-fg-on-brand); font-family: inherit; font-style: normal; font-size: 10px; }
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
@media (prefers-reduced-motion: reduce) {
|
package/dist/dialog.layered.css
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
& .crisp-dialog-field-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
|
|
17
17
|
& .crisp-dialog-more { display: inline-flex; align-items: center; gap: var(--crisp-space-2); margin-inline-end: auto; border: none; background: none; padding: 0; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); cursor: pointer; }
|
|
18
18
|
& .crisp-dialog-submit { display: inline-flex; align-items: center; gap: var(--crisp-space-2); height: 28px; padding: 0 var(--crisp-space-1) 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
|
|
19
|
-
& .crisp-dialog-submit-key { display: inline-flex; align-items: center;
|
|
19
|
+
& .crisp-dialog-submit-key { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
|
|
20
|
+
& .crisp-dialog-submit-key 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: var(--crisp-fg-on-brand); font-family: inherit; font-style: normal; font-size: 10px; }
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as dialog from "@zag-js/dialog";
|
|
3
|
+
import { normalizeProps, Portal, useMachine } from "@zag-js/react";
|
|
4
|
+
import { Minus, Pin, X } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { cx, withDescribedBy } from "../../core/index.js";
|
|
7
|
+
import { composewindowRecipe } from "./composewindow.recipe.js";
|
|
8
|
+
// A globally unique id per mounted instance — see command.tsx/dialog.tsx for
|
|
9
|
+
// the rationale (the machine defers its dismissal listeners a frame, so a
|
|
10
|
+
// tree-relative id could let a stale registration resolve to a new instance).
|
|
11
|
+
let instanceCounter = 0;
|
|
12
|
+
function useComposeWindowInstanceId() {
|
|
13
|
+
const idRef = React.useRef(null);
|
|
14
|
+
if (idRef.current == null) {
|
|
15
|
+
instanceCounter += 1;
|
|
16
|
+
idRef.current = `crisp-composewindow-${instanceCounter}`;
|
|
17
|
+
}
|
|
18
|
+
return idRef.current;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A floating, minimizable, pinnable window — Attio's own note "compose
|
|
22
|
+
* window" chrome (measured live, read-only, 2026-09-24; see HANDOFF), not a
|
|
23
|
+
* new visual recipe: it reuses `Dialog`'s exact measured frosted-glass panel
|
|
24
|
+
* (`bgOverlayFrost` 60% + `blur(15px)`, `elevation.modal`'s 3-layer shadow,
|
|
25
|
+
* 16px outer radius) — those numbers matched Attio's compose window exactly
|
|
26
|
+
* when re-checked against it, panel-for-panel. What's actually different
|
|
27
|
+
* from `Dialog` is the header (a breadcrumb plus minimize/pin/close, not a
|
|
28
|
+
* title plus close) and two extra pieces of state Dialog has no concept of.
|
|
29
|
+
*
|
|
30
|
+
* `minimized` is measured, not guessed — the FIRST version of this file built
|
|
31
|
+
* an invented "minimized bar" (a Gmail-style dock, since a bar was never
|
|
32
|
+
* actually exercised against Attio). Re-measured properly: minimizing an
|
|
33
|
+
* existing note does not produce any bar, tray, dock, or badge anywhere on
|
|
34
|
+
* screen. Confirmed four independent ways, both with synthetic and real
|
|
35
|
+
* CDP-dispatched trusted clicks — the element count under the note's own
|
|
36
|
+
* subtree drops by ~110 nodes, `[role="dialog"]` stops matching it, its
|
|
37
|
+
* `data-hidden="true"` ancestor computes `visibility: hidden; opacity: 0`,
|
|
38
|
+
* and the URL drops the `?id=…&modal=note` query exactly like Close does —
|
|
39
|
+
* but the subtree is NOT removed: `document.querySelectorAll('[aria-label=
|
|
40
|
+
* "Minimize"]')` still finds it afterward, `visibility: hidden` (not
|
|
41
|
+
* `display: none`) preserves layout, and nothing in localStorage or the DOM
|
|
42
|
+
* suggests state was discarded. So: minimized hides the panel — mounted,
|
|
43
|
+
* `visibility: hidden; opacity: 0` — rather than unmounting or closing it,
|
|
44
|
+
* and crisp renders no restore affordance of its own (Attio has none
|
|
45
|
+
* either); reopening is the consumer's own UI, driven by `minimized`/
|
|
46
|
+
* `onMinimizedChange` the same way `open` already works.
|
|
47
|
+
*
|
|
48
|
+
* "Pinned" is implemented as the one directly-supported behavior that
|
|
49
|
+
* matches its name (Zag's `closeOnInteractOutside`, off while pinned) —
|
|
50
|
+
* Attio's own multi-instance stacking/docking when pinned was not exercised,
|
|
51
|
+
* so nothing here repositions the window when pinned.
|
|
52
|
+
*
|
|
53
|
+
* Not modal: Attio's own compose window dims nothing behind it (`Dialog`'s
|
|
54
|
+
* backdrop is `background: transparent` too — the blur alone does the
|
|
55
|
+
* separation), and the page behind stays visibly interactive in the
|
|
56
|
+
* measured screenshot. `trapFocus` follows the same call — a picture-in-
|
|
57
|
+
* picture window, not a modal, so Tab is not caught inside it.
|
|
58
|
+
*/
|
|
59
|
+
export function ComposeWindow({ open, onOpenChange, breadcrumb, minimized: minimizedProp, defaultMinimized, onMinimizedChange, pinned: pinnedProp, defaultPinned, onPinnedChange, width = 968, "aria-label": ariaLabel, minimizeLabel = "Minimize", pinLabel = "Pin to screen", closeLabel = "Close", children, className, style, "aria-describedby": describedBy, ...rest }) {
|
|
60
|
+
const id = useComposeWindowInstanceId();
|
|
61
|
+
const [internalMinimized, setInternalMinimized] = React.useState(defaultMinimized ?? false);
|
|
62
|
+
const minimized = minimizedProp ?? internalMinimized;
|
|
63
|
+
const setMinimized = React.useCallback((next) => {
|
|
64
|
+
if (minimizedProp === undefined)
|
|
65
|
+
setInternalMinimized(next);
|
|
66
|
+
onMinimizedChange?.(next);
|
|
67
|
+
}, [minimizedProp, onMinimizedChange]);
|
|
68
|
+
const [internalPinned, setInternalPinned] = React.useState(defaultPinned ?? false);
|
|
69
|
+
const pinned = pinnedProp ?? internalPinned;
|
|
70
|
+
const setPinned = React.useCallback((next) => {
|
|
71
|
+
if (pinnedProp === undefined)
|
|
72
|
+
setInternalPinned(next);
|
|
73
|
+
onPinnedChange?.(next);
|
|
74
|
+
}, [pinnedProp, onPinnedChange]);
|
|
75
|
+
const service = useMachine(dialog.machine, {
|
|
76
|
+
id,
|
|
77
|
+
// Always `open` while the consumer says so — NOT `open && !minimized`.
|
|
78
|
+
// Zag's own "closed" state sets `hidden` on the content element, which
|
|
79
|
+
// the browser's UA stylesheet turns into `display: none` — the exact
|
|
80
|
+
// thing minimized must NOT be (Attio's own measured minimized subtree
|
|
81
|
+
// keeps `display: flex`, only `visibility`/`opacity` change). Disabling
|
|
82
|
+
// `closeOnEscape`/`closeOnInteractOutside` below gets the same "stand
|
|
83
|
+
// down while minimized" result without that side effect.
|
|
84
|
+
open,
|
|
85
|
+
onOpenChange(details) {
|
|
86
|
+
// Zag only ever asks to close (Escape / outside pointerdown) — it
|
|
87
|
+
// never asks to open on its own, so this is always a close request.
|
|
88
|
+
if (!details.open)
|
|
89
|
+
onOpenChange(false);
|
|
90
|
+
},
|
|
91
|
+
modal: false,
|
|
92
|
+
trapFocus: false,
|
|
93
|
+
closeOnInteractOutside: !pinned && !minimized,
|
|
94
|
+
closeOnEscape: !minimized,
|
|
95
|
+
"aria-label": ariaLabel,
|
|
96
|
+
});
|
|
97
|
+
const api = dialog.connect(service, normalizeProps);
|
|
98
|
+
if (!open)
|
|
99
|
+
return null;
|
|
100
|
+
return (_jsx(Portal, { children: _jsxs("div", { className: composewindowRecipe(), children: [_jsx("div", { ...api.getBackdropProps(), className: "crisp-composewindow-backdrop" }), _jsx("div", { ...api.getPositionerProps(), className: "crisp-composewindow-positioner", children: _jsx("div", { ...rest, ...withDescribedBy(api.getContentProps(), describedBy), className: cx("crisp-composewindow-content", className), "data-crisp-composewindow": id, "data-minimized": minimized || undefined, style: { ...style, maxWidth: `min(${width}px, 90vw)` }, children: _jsxs("div", { className: "crisp-composewindow-card", children: [_jsxs("div", { className: "crisp-composewindow-header", children: [_jsx("span", { ...api.getTitleProps(), className: "crisp-composewindow-breadcrumb", children: breadcrumb }), _jsxs("div", { className: "crisp-composewindow-actions", children: [_jsx("button", { type: "button", className: "crisp-composewindow-action-btn", "aria-label": minimizeLabel, onClick: () => setMinimized(true), children: _jsx(Minus, { size: 14, strokeWidth: 2, "aria-hidden": "true" }) }), _jsx("button", { type: "button", className: "crisp-composewindow-action-btn", "aria-label": pinLabel, "aria-pressed": pinned, "data-state": pinned ? "on" : "off", onClick: () => setPinned(!pinned), children: _jsx(Pin, { size: 14, strokeWidth: 2, "aria-hidden": "true" }) }), _jsx("button", { type: "button", ...api.getCloseTriggerProps(), className: "crisp-composewindow-action-btn", "aria-label": closeLabel, children: _jsx(X, { size: 14, strokeWidth: 2, "aria-hidden": "true" }) })] })] }), _jsx("div", { className: "crisp-composewindow-body", children: children })] }) }) })] }) }));
|
|
101
|
+
}
|
|
102
|
+
ComposeWindow.displayName = "ComposeWindow";
|
|
103
|
+
export function ComposeWindowBody({ children, className, }) {
|
|
104
|
+
return (_jsx("div", { className: className
|
|
105
|
+
? `crisp-composewindow-content-body ${className}`
|
|
106
|
+
: "crisp-composewindow-content-body", children: children }));
|
|
107
|
+
}
|