@crisp-ui-kit/crisp 0.49.0 → 0.51.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.
Files changed (104) hide show
  1. package/dist/cjs/components/composewindow/composewindow.js +144 -0
  2. package/dist/cjs/components/composewindow/composewindow.recipe.js +45 -0
  3. package/dist/cjs/components/composewindow/index.js +8 -0
  4. package/dist/cjs/components/datatable/datatable.js +224 -43
  5. package/dist/cjs/components/datatable/datatable.recipe.js +38 -4
  6. package/dist/cjs/components/datatable/index.js +6 -0
  7. package/dist/cjs/components/datepickerfield/datepickerfield.js +25 -15
  8. package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +25 -8
  9. package/dist/cjs/components/dialog/dialog.recipe.js +9 -1
  10. package/dist/cjs/components/menu/menu.js +4 -2
  11. package/dist/cjs/components/multiselect/multiselect.recipe.js +14 -8
  12. package/dist/cjs/components/select/select.js +2 -1
  13. package/dist/cjs/components/table/table.recipe.js +12 -3
  14. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +2 -1
  15. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +39 -8
  16. package/dist/cjs/core/cell-date.js +11 -0
  17. package/dist/cjs/core/index.js +2 -1
  18. package/dist/cjs/index.js +1 -0
  19. package/dist/cjs/product/recordpage/recordpage.js +88 -10
  20. package/dist/cjs/product/recordpage/recordpage.recipe.js +30 -0
  21. package/dist/cjs/product/recordpanel/recordpanel.js +54 -4
  22. package/dist/cjs/product/recordpanel/recordpanel.recipe.js +29 -0
  23. package/dist/cjs/product/recordstable/editable-field.js +110 -0
  24. package/dist/cjs/product/recordstable/index.js +4 -1
  25. package/dist/cjs/product/recordstable/recordstable.js +67 -11
  26. package/dist/cjs/product/recordstable/recordstable.recipe.js +9 -1
  27. package/dist/cjs/product/settingsshell/settingsshell.recipe.js +13 -4
  28. package/dist/cjs/product/sidebar/sidebar.js +15 -3
  29. package/dist/cjs/product/sidebar/sidebar.recipe.js +21 -2
  30. package/dist/composewindow.css +24 -0
  31. package/dist/composewindow.d.ts +7 -0
  32. package/dist/composewindow.layered.css +26 -0
  33. package/dist/datatable.css +34 -15
  34. package/dist/datatable.layered.css +34 -15
  35. package/dist/datepickerfield.css +9 -4
  36. package/dist/datepickerfield.layered.css +9 -4
  37. package/dist/dialog.css +2 -1
  38. package/dist/dialog.layered.css +2 -1
  39. package/dist/esm/components/composewindow/composewindow.js +107 -0
  40. package/dist/esm/components/composewindow/composewindow.recipe.js +42 -0
  41. package/dist/esm/components/composewindow/index.js +2 -0
  42. package/dist/esm/components/datatable/datatable.js +226 -45
  43. package/dist/esm/components/datatable/datatable.recipe.js +38 -4
  44. package/dist/esm/components/datatable/index.js +6 -0
  45. package/dist/esm/components/datepickerfield/datepickerfield.js +26 -16
  46. package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +25 -8
  47. package/dist/esm/components/dialog/dialog.recipe.js +9 -1
  48. package/dist/esm/components/menu/menu.js +4 -2
  49. package/dist/esm/components/multiselect/multiselect.recipe.js +14 -8
  50. package/dist/esm/components/select/select.js +2 -1
  51. package/dist/esm/components/table/table.recipe.js +12 -3
  52. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +2 -1
  53. package/dist/esm/components/tabletoolbar/tabletoolbar.js +38 -8
  54. package/dist/esm/core/cell-date.js +10 -0
  55. package/dist/esm/core/index.js +1 -1
  56. package/dist/esm/index.js +1 -0
  57. package/dist/esm/product/recordpage/recordpage.js +89 -11
  58. package/dist/esm/product/recordpage/recordpage.recipe.js +30 -0
  59. package/dist/esm/product/recordpanel/recordpanel.js +55 -5
  60. package/dist/esm/product/recordpanel/recordpanel.recipe.js +29 -0
  61. package/dist/esm/product/recordstable/editable-field.js +73 -0
  62. package/dist/esm/product/recordstable/index.js +1 -0
  63. package/dist/esm/product/recordstable/recordstable.js +69 -13
  64. package/dist/esm/product/recordstable/recordstable.recipe.js +9 -1
  65. package/dist/esm/product/settingsshell/settingsshell.recipe.js +13 -4
  66. package/dist/esm/product/sidebar/sidebar.js +15 -3
  67. package/dist/esm/product/sidebar/sidebar.recipe.js +21 -2
  68. package/dist/multiselect.css +8 -8
  69. package/dist/multiselect.layered.css +8 -8
  70. package/dist/recordpage.css +25 -13
  71. package/dist/recordpage.layered.css +25 -13
  72. package/dist/recordpanel.css +22 -5
  73. package/dist/recordpanel.layered.css +22 -5
  74. package/dist/recordstable.css +10 -2
  75. package/dist/recordstable.layered.css +10 -2
  76. package/dist/settingsshell.css +2 -2
  77. package/dist/settingsshell.layered.css +2 -2
  78. package/dist/sidebar.css +7 -2
  79. package/dist/sidebar.layered.css +7 -2
  80. package/dist/styles.css +150 -55
  81. package/dist/styles.layered.css +150 -55
  82. package/dist/table.css +4 -2
  83. package/dist/table.layered.css +4 -2
  84. package/dist/tabletoolbar.css +2 -1
  85. package/dist/tabletoolbar.layered.css +2 -1
  86. package/dist/types/components/composewindow/composewindow.d.ts +80 -0
  87. package/dist/types/components/composewindow/composewindow.recipe.d.ts +3 -0
  88. package/dist/types/components/composewindow/index.d.ts +2 -0
  89. package/dist/types/components/datatable/datatable.d.ts +58 -4
  90. package/dist/types/components/datatable/index.d.ts +1 -1
  91. package/dist/types/components/datepickerfield/datepickerfield.d.ts +18 -4
  92. package/dist/types/components/menu/menu.d.ts +10 -1
  93. package/dist/types/components/select/select.d.ts +6 -1
  94. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +62 -5
  95. package/dist/types/core/cell-date.d.ts +17 -0
  96. package/dist/types/core/index.d.ts +1 -1
  97. package/dist/types/index.d.ts +1 -0
  98. package/dist/types/product/recordpage/recordpage.d.ts +59 -1
  99. package/dist/types/product/recordpanel/recordpanel.d.ts +11 -1
  100. package/dist/types/product/recordstable/editable-field.d.ts +45 -0
  101. package/dist/types/product/recordstable/index.d.ts +1 -0
  102. package/dist/types/product/recordstable/recordstable.d.ts +29 -0
  103. package/dist/types/product/sidebar/sidebar.d.ts +60 -4
  104. package/package.json +6 -1
@@ -1,14 +1,23 @@
1
1
  import { defineRecipe, resolveRecipe, rtlFlipCss } from "../../core/index.js";
2
2
  import { contract as c } from "../../tokens/index.js";
3
- // The Settings layout — measured from a Settings screen: a 256px left nav with a
3
+ // The Settings layout — measured from a Settings screen: a 275px left nav with a
4
4
  // back header, a search field, and grouped nav items (active row raised), beside
5
- // a scrolling content pane. Sits inside whatever height its container gives it.
5
+ // a scrolling content pane. Fills whatever height its container gives it, the
6
+ // way Attio's settings nav runs the container's full height (#435) — the root
7
+ // used to carry only `min-height: 0` (lets it *shrink* inside a flex ancestor)
8
+ // with no `height` of its own, so it sized to its content instead of filling,
9
+ // and the nav's own border-inline-end stopped where the nav content ended
10
+ // rather than running the full height. A consumer (GRC) had to paper over it
11
+ // with an external `h-full` class.
12
+ // 275px, not the 256px this recipe used to claim (#410) — the same width
13
+ // AppShell's own Attio-measured `defaultWidth` already uses for the main
14
+ // sidebar pane; re-measured live at app.attio.com/<workspace>/settings/account.
6
15
  const config = defineRecipe({
7
16
  name: "settingsshell",
8
- base: `display: flex; min-height: 0; box-sizing: border-box; font-family: ${c.font.sans}; color: ${c.fgPrimary};
17
+ base: `display: flex; height: 100%; min-height: 0; box-sizing: border-box; font-family: ${c.font.sans}; color: ${c.fgPrimary};
9
18
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
10
19
 
11
- & .crisp-settingsshell-nav { display: flex; flex-direction: column; width: 256px; flex-shrink: 0; min-height: 0; padding: ${c.space["3"]}; border-inline-end: 1px solid ${c.borderSubtle}; }
20
+ & .crisp-settingsshell-nav { display: flex; flex-direction: column; width: 275px; flex-shrink: 0; min-height: 0; padding: ${c.space["3"]}; border-inline-end: 1px solid ${c.borderSubtle}; }
12
21
  & .crisp-settingsshell-back { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; height: 28px; padding: 0 ${c.space["1"]}; margin: 0 0 ${c.space["2"]}; border: none; background: none; font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.semibold}; color: ${c.fgPrimary}; cursor: pointer; border-radius: ${c.radius.md}; }
13
22
  & .crisp-settingsshell-back svg { color: ${c.fgMuted}; }
14
23
  & .crisp-settingsshell-back:hover svg { color: ${c.fgPrimary}; }
@@ -4,10 +4,20 @@ import * as React from "react";
4
4
  import { Avatar } from "../../components/avatar/index.js";
5
5
  import { Badge } from "../../components/badge/index.js";
6
6
  import { Button } from "../../components/button/index.js";
7
+ import { Menu } from "../../components/menu/index.js";
7
8
  import { Primitive } from "../../core/index.js";
8
9
  import { Command } from "../command/index.js";
9
10
  import { NavItem } from "../navitem/index.js";
10
11
  import { sidebarRecipe } from "./sidebar.recipe.js";
12
+ /** A plain `{ gettingStartedLabel, inviteLabel, trialText, trialActionLabel }`
13
+ * object (not a React element) — the shape `footer` renders as Attio's fixed
14
+ * onboarding/invite/trial copy, as opposed to a caller-supplied override. */
15
+ function isSidebarFooterCopy(value) {
16
+ return (typeof value === "object" &&
17
+ value !== null &&
18
+ !React.isValidElement(value) &&
19
+ "gettingStartedLabel" in value);
20
+ }
11
21
  const IconHome = ({ className }) => (_jsx(House, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
12
22
  const IconTasks = ({ className }) => (_jsx(SquareCheck, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
13
23
  const IconNotes = ({ className }) => (_jsx(FileText, { className: className, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }));
@@ -81,7 +91,7 @@ export const ATTIO_SIDEBAR_FOOTER = {
81
91
  function SectionHeader({ label, open, onToggle, }) {
82
92
  return (_jsxs("button", { type: "button", className: "crisp-sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [_jsx(IconChevronMini, {}), _jsx("span", { children: label })] }));
83
93
  }
84
- export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = ATTIO_SIDEBAR_WORKSPACE, items = ATTIO_SIDEBAR_ITEMS, recordsSection = ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = ATTIO_SIDEBAR_FOOTER, commandItems, commandOpen: commandOpenProp, defaultCommandOpen, onCommandOpenChange, commandShortcut = true, "aria-label": ariaLabel = "Main", ...props }, ref) => {
94
+ export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = ATTIO_SIDEBAR_WORKSPACE, workspaceMenu, items = ATTIO_SIDEBAR_ITEMS, recordsSection = ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = ATTIO_SIDEBAR_FOOTER, commandItems, commandOpen: commandOpenProp, defaultCommandOpen, onCommandOpenChange, commandShortcut = true, "aria-label": ariaLabel = "Main", ...props }, ref) => {
85
95
  // Seeded once, on mount, from whatever groups start collapsed — not
86
96
  // reactive to `groups` changing later, matching every other piece of
87
97
  // this component's state (recordsSection/listsSection/chatsSection have
@@ -127,13 +137,15 @@ export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse,
127
137
  return next;
128
138
  });
129
139
  }, []);
130
- return (_jsxs(Primitive.nav, { ref: ref, asChild: asChild, "aria-label": ariaLabel, className: sidebarRecipe({ className }), ...props, children: [_jsxs("div", { className: "crisp-sidebar-ws", children: [_jsx(Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), _jsx("span", { className: "crisp-sidebar-ws-name", children: workspace.name }), _jsx(IconChevronDown, {}), _jsx(Button, { intent: "ghost", size: "sm", shape: "icon", className: "crisp-sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: _jsx(IconCollapsePanel, {}) })] }), _jsxs("div", { className: "crisp-sidebar-searchrow", children: [_jsxs("button", { type: "button", className: "crisp-sidebar-search", onClick: () => setCommandOpen(true), children: [_jsx(IconSearch, {}), _jsx("span", { className: "crisp-sidebar-search-label", children: searchLabel }), _jsx("span", { className: "crisp-sidebar-search-kbd", children: "\u2318K" })] }), _jsx(Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: _jsx(IconInbox, {}) })] }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsx("div", { className: "crisp-sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing }) => (_jsx(NavItem, { href: "#", active: active, icon: _jsx(ItemIcon, {}), trailing: trailing, children: label }, label))) }), groups ? (
140
+ const workspaceTrigger = (_jsxs("button", { type: "button", className: "crisp-sidebar-ws", children: [_jsx(Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), _jsx("span", { className: "crisp-sidebar-ws-name", children: workspace.name }), _jsx(IconChevronDown, {})] }));
141
+ return (_jsxs(Primitive.nav, { ref: ref, asChild: asChild, "aria-label": ariaLabel, className: sidebarRecipe({ className }), ...props, children: [_jsxs("div", { className: "crisp-sidebar-ws-row", children: [workspaceMenu ? (_jsx(Menu, { trigger: workspaceTrigger, className: "crisp-sidebar-ws-menuroot", contentClassName: "crisp-sidebar-ws-menu-content", children: workspaceMenu })) : (workspaceTrigger), _jsx(Button, { intent: "ghost", size: "sm", shape: "icon", className: "crisp-sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: _jsx(IconCollapsePanel, {}) })] }), _jsxs("div", { className: "crisp-sidebar-searchrow", children: [_jsxs("button", { type: "button", className: "crisp-sidebar-search", onClick: () => setCommandOpen(true), children: [_jsx(IconSearch, {}), _jsx("span", { className: "crisp-sidebar-search-label", children: searchLabel }), _jsx("span", { className: "crisp-sidebar-search-kbd", children: "\u2318K" })] }), _jsx(Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: _jsx(IconInbox, {}) })] }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsx("div", { className: "crisp-sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect }) => (_jsx(NavItem, { href: href ?? "#", active: active, icon: _jsx(ItemIcon, {}), trailing: trailing, onClick: onSelect, children: label }, href ?? label))) }), groups ? (
131
142
  // #325: navigation as data, reusing the exact section markup/
132
143
  // recipe above — SectionHeader + .crisp-sidebar-section — instead
133
144
  // of the three fixed Attio sections.
134
145
  groups.map((group) => {
135
146
  const groupKey = group.key ?? group.label;
136
147
  return (_jsxs(React.Fragment, { children: [_jsx(SectionHeader, { label: group.label, open: !collapsedSections.has(groupKey), onToggle: () => toggleSection(groupKey) }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has(groupKey) ? "" : undefined, children: group.items.map((item) => (_jsx(NavItem, { href: item.href ?? "#", active: item.active, icon: item.icon ? _jsx(item.icon, {}) : undefined, trailing: item.count, onClick: item.onSelect, children: item.label }, item.href ?? item.label))) })] }, groupKey));
137
- })) : (_jsxs(_Fragment, { children: [_jsx(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => (_jsx(NavItem, { href: "#", icon: _jsx(Avatar, { size: "sm", shape: "rounded", tone: tone, children: _jsx(Glyph, {}) }), children: label }, label))) }), _jsx(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: _jsx("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), _jsx(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon }) => (_jsx(NavItem, { href: "#", icon: _jsx(ItemIcon, {}), children: label }, label))) })] })), _jsxs("div", { className: "crisp-sidebar-footer", children: [_jsx(Button, { intent: "neutral", size: "sm", className: "crisp-sidebar-pill", children: footer.gettingStartedLabel }), _jsx(NavItem, { href: "#", icon: _jsx(IconInvite, {}), children: footer.inviteLabel }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsxs("div", { className: "crisp-sidebar-trial", children: [_jsx("span", { children: footer.trialText }), _jsx(Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] }), _jsx(Command, { open: commandOpen, onOpenChange: setCommandOpen, items: commandItems })] }));
148
+ })) : (_jsxs(_Fragment, { children: [_jsx(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => (_jsx(NavItem, { href: "#", icon: _jsx(Avatar, { size: "sm", shape: "rounded", tone: tone, children: _jsx(Glyph, {}) }), children: label }, label))) }), _jsx(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: _jsx("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), _jsx(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect, }) => (_jsx(NavItem, { href: href ?? "#", active: active, icon: _jsx(ItemIcon, {}), trailing: trailing, onClick: onSelect, children: label }, href ?? label))) })] })), footer !== false &&
149
+ (isSidebarFooterCopy(footer) ? (_jsxs("div", { className: "crisp-sidebar-footer", children: [_jsx(Button, { intent: "neutral", size: "sm", className: "crisp-sidebar-pill", children: footer.gettingStartedLabel }), _jsx(NavItem, { href: "#", icon: _jsx(IconInvite, {}), children: footer.inviteLabel }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsxs("div", { className: "crisp-sidebar-trial", children: [_jsx("span", { children: footer.trialText }), _jsx(Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] })) : (_jsx("div", { className: "crisp-sidebar-footer", children: footer }))), _jsx(Command, { open: commandOpen, onOpenChange: setCommandOpen, items: commandItems })] }));
138
150
  });
139
151
  Sidebar.displayName = "Sidebar";
@@ -1,5 +1,12 @@
1
1
  import { defineRecipe, resolveRecipe, rtlFlipCss } from "../../core/index.js";
2
2
  import { contract as c, elevation as e } from "../../tokens/index.js";
3
+ // The workspace switcher's menu (#415) is 280px wide on app.attio.com --
4
+ // measured live via getComputedStyle on its [role="menu"], not derived from
5
+ // the trigger's own width (which reads 295-296px including its collapse
6
+ // button) or from Menu's generic 200px content-sized min-width. Everything
7
+ // else about the popover -- 4px padding, 12px radius, 32px/8px-radius items,
8
+ // 14px/500 labels, the layered shadow -- already matches Menu's own measured
9
+ // chrome, confirmed against this specific menu rather than assumed from it.
3
10
  const config = defineRecipe({
4
11
  name: "sidebar",
5
12
  base: `box-sizing: border-box; display: flex; flex-direction: column; width: 275px; height: 100%; background: ${c.bgSidebar}
@@ -12,10 +19,12 @@ const config = defineRecipe({
12
19
  & .crisp-sidebar-section-header { display: flex; align-items: center; gap: ${c.space["1"]}; width: 100%; margin: 0; border: none; background: none; padding: ${c.space["1.5"]} ${c.space["2"]} ${c.space["0.5"]}; font-family: ${c.font.sans}; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; cursor: pointer; }
13
20
  & .crisp-sidebar-section-header svg { flex-shrink: 0; transition: transform .15s ease; }
14
21
  & .crisp-sidebar-section-header[data-collapsed] svg { transform: rotate(-90deg); }
15
- & .crisp-sidebar-ws { display: flex; align-items: center; gap: ${c.space["2"]}; height: ${c.size.header}; padding: 0 ${c.space["2"]}; border-radius: ${c.radius.lg}; cursor: pointer; transition: background-color 0.16s; }
22
+ & .crisp-sidebar-ws-row { display: flex; align-items: center; gap: ${c.space["2"]}; height: ${c.size.header}; padding: 0 ${c.space["2"]}; }
23
+ & .crisp-sidebar-ws-menuroot { display: flex; flex: 1; min-width: 0; }
24
+ & .crisp-sidebar-ws { box-sizing: border-box; display: flex; align-items: center; gap: ${c.space["2"]}; flex: 1; min-width: 0; height: 100%; margin: 0; padding: 0; border: none; background: none; border-radius: ${c.radius.lg}; font-family: inherit; font-size: inherit; color: inherit; text-align: start; cursor: pointer; transition: background-color 0.16s; }
16
25
  & .crisp-sidebar-ws:hover { background: color-mix(in srgb, ${c.fgPrimary} 5%, transparent); }
17
26
  & .crisp-sidebar-ws-name { font-size: ${c.text.lg}; font-weight: ${c.weight.semibold}; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
18
- & .crisp-sidebar-ws-collapse { margin-inline-start: auto; }
27
+ & .crisp-sidebar-ws-collapse { flex-shrink: 0; }
19
28
  & .crisp-sidebar-searchrow { display: flex; align-items: center; gap: ${c.space["1.5"]}; margin-top: ${c.space["2.5"]}; padding-inline-end: ${c.space["2"]}; }
20
29
  & .crisp-sidebar-footer { margin-top: auto; display: flex; flex-direction: column; gap: ${c.space["0.5"]}; }
21
30
  & .crisp-sidebar-trial { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["2"]}; padding: ${c.space["1.5"]} ${c.space["2"]}; font-size: ${c.text.sm}; color: ${c.fgMuted}; }
@@ -29,6 +38,16 @@ const config = defineRecipe({
29
38
  & .crisp-sidebar-newlist:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
30
39
 
31
40
  ${rtlFlipCss}
41
+ `,
42
+ // Menu's own floating content is portaled to document.body (#357), so a
43
+ // `&`-nested rule here -- which compiles to a real descendant combinator
44
+ // scoped to .crisp-sidebar -- matches nothing once the popover leaves the
45
+ // sidebar's DOM subtree. Same fix Menu's own recipe already uses: the
46
+ // content's override lives in globalCss instead, keyed off the
47
+ // \`contentClassName\` Menu now hands the workspace switcher's portaled
48
+ // surface (#415).
49
+ globalCss: `
50
+ .crisp-sidebar-ws-menu-content { width: 280px; }
32
51
  `,
33
52
  variants: {},
34
53
  defaultVariants: {},
@@ -32,14 +32,14 @@
32
32
  .crisp-multiselect-option[aria-selected="true"] .crisp-multiselect-check { background: var(--crisp-bg-brand-solid); box-shadow: none; color: #fff; }
33
33
  .crisp-multiselect-empty { padding: var(--crisp-space-3) var(--crisp-space-2); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
34
34
 
35
- .crisp-recordstable-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; overflow: hidden; text-overflow: ellipsis; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
36
- .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
37
- .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); }
38
- .crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-warning) 82%, var(--crisp-fg-primary)); }
39
- .crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 15%, transparent); color: color-mix(in srgb, var(--crisp-bg-danger) 80%, var(--crisp-fg-primary)); }
40
- .crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); }
41
- .crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); }
42
- .crisp-recordstable-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)); }
35
+ .crisp-recordstable-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; overflow: hidden; text-overflow: ellipsis; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
36
+ .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
37
+ .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-success) 30%, transparent); }
38
+ .crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-warning) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-warning) 30%, transparent); }
39
+ .crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 15%, transparent); color: color-mix(in srgb, var(--crisp-bg-danger) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent); }
40
+ .crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-purple) 30%, transparent); }
41
+ .crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-cyan) 30%, transparent); }
42
+ .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
43
43
 
44
44
  .crisp-multiselect {
45
45
  position: relative; display: inline-flex; width: 100%; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
@@ -33,14 +33,14 @@
33
33
  .crisp-multiselect-option[aria-selected="true"] .crisp-multiselect-check { background: var(--crisp-bg-brand-solid); box-shadow: none; color: #fff; }
34
34
  .crisp-multiselect-empty { padding: var(--crisp-space-3) var(--crisp-space-2); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
35
35
 
36
- .crisp-recordstable-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; overflow: hidden; text-overflow: ellipsis; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
37
- .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
38
- .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); }
39
- .crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-warning) 82%, var(--crisp-fg-primary)); }
40
- .crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 15%, transparent); color: color-mix(in srgb, var(--crisp-bg-danger) 80%, var(--crisp-fg-primary)); }
41
- .crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); }
42
- .crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); }
43
- .crisp-recordstable-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)); }
36
+ .crisp-recordstable-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; overflow: hidden; text-overflow: ellipsis; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
37
+ .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
38
+ .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-success) 30%, transparent); }
39
+ .crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-warning) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-warning) 30%, transparent); }
40
+ .crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 15%, transparent); color: color-mix(in srgb, var(--crisp-bg-danger) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent); }
41
+ .crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-purple) 30%, transparent); }
42
+ .crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-cyan) 30%, transparent); }
43
+ .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
44
44
 
45
45
  .crisp-multiselect {
46
46
  position: relative; display: inline-flex; width: 100%; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
@@ -60,27 +60,46 @@
60
60
  & .crisp-recordpage-field-label svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
61
61
  & .crisp-recordpage-field-label-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
62
62
  & .crisp-recordpage-field-value { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); padding-top: 2px; line-height: 20px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
63
+ & .crisp-recordpage-field-value[data-editable] { cursor: pointer; }
64
+ /* the click-to-edit trigger is a real <button> (keyboard-operable),
65
+ reset back to plain inline text and made a flex-wrap container of its
66
+ own -- FieldValue can render several chips (tags), which still need
67
+ to wrap the way they did before this had a button around them at
68
+ all. It wraps only FieldValue, never the error slot beside it (see
69
+ the component's own comment) -- NOT `display: contents`, which drops a button's accessible semantics in some engines. */
70
+ & .crisp-recordpage-edit-trigger { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); margin: 0; padding: 0; border: none; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer; }
63
71
  & .crisp-recordpage-empty { color: var(--crisp-fg-muted); }
72
+
73
+ /* #431: measured live on app.attio.com's record page Details column -- clicking a field's value opens a plain, borderless field exactly
74
+ filling the value's own box (no border, no background, no box-shadow), same font as the value it replaces (hence `font: inherit`, not a restated size/weight). A field row's own hover tint
75
+ above already carries the "this is interactive" affordance; the
76
+ pending/error treatment isn't Attio-measured -- it reuses the same tokens DataTable's identical states already use (opacity 0.5,
77
+ `bgDanger`), per #385/#430. */
78
+ & .crisp-recordpage-edit { display: block; width: 100%; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; box-shadow: none; outline: none; font: inherit; color: inherit; }
79
+ /* not Attio-measured (its own field shows no focus ring either, caret
80
+ only) -- kept for real keyboard users, the same brand ring every
81
+ other crisp field already shows on focus (DataTable's identical .crisp-datatable-edit:focus, e.g.). */
82
+ & .crisp-recordpage-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
83
+ & .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
84
+ & .crisp-recordpage-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
85
+ & .crisp-recordpage-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
86
+ & .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
87
+ & .crisp-recordpage-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
64
88
  & .crisp-recordpage-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
65
89
  & .crisp-recordpage-link:hover { text-decoration: underline; }
66
90
  & .crisp-recordpage-emptystate { padding: var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
67
-
68
91
  & .crisp-recordpage-main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; padding: var(--crisp-space-3) var(--crisp-space-5); }
69
-
70
92
  & .crisp-recordpage-panel { padding-top: var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
71
93
  & .crisp-recordpage-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); background: var(--crisp-bg-surface); overflow: hidden; }
72
94
  & .crisp-recordpage-card-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.14px; color: var(--crisp-fg-primary); padding: var(--crisp-space-3) var(--crisp-space-3) var(--crisp-space-2); }
73
95
  & .crisp-recordpage-hl-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); overflow: hidden; }
74
- /* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
75
- column count, so the grid stays responsive (auto-fit) without per-cell
76
- nth-child border hacks that break when the column count changes. */
96
+ /* gap-as-gridlines: a 1px gap over the border colour draws dividers at any column count, so the grid stays responsive (auto-fit) without per-cell nth-child border hacks that break when the column count changes. */
77
97
  & .crisp-recordpage-hl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1px; background: var(--crisp-border-subtle); }
78
98
  & .crisp-recordpage-hl-cell { position: relative; min-height: 84px; padding: var(--crisp-space-2-5) var(--crisp-space-3); background: var(--crisp-bg-surface); }
79
99
  & .crisp-recordpage-hl-icon { position: absolute; top: var(--crisp-space-2-5); inset-inline-end: var(--crisp-space-3); color: var(--crisp-fg-muted); }
80
100
  & .crisp-recordpage-hl-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); margin-bottom: var(--crisp-space-2-5); padding-inline-end: var(--crisp-space-4); }
81
101
  & .crisp-recordpage-hl-value { display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
82
102
  & .crisp-recordpage-hl-value--empty { color: var(--crisp-fg-muted); font-weight: var(--crisp-weight-regular); }
83
-
84
103
  & .crisp-recordpage-feed { display: flex; flex-direction: column; }
85
104
  & .crisp-recordpage-feed-item { display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2) 0; }
86
105
  & .crisp-recordpage-feed-dot { position: relative; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex-shrink: 0; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
@@ -89,7 +108,6 @@
89
108
  & .crisp-recordpage-feed-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); margin-top: 2px; }
90
109
  & .crisp-recordpage-feed-more { align-self: flex-start; margin-top: var(--crisp-space-1); border: none; background: none; padding: var(--crisp-space-1) 0; color: var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
91
110
  & .crisp-recordpage-feed-more:hover { text-decoration: underline; }
92
-
93
111
  & .crisp-recordpage-section-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-1); }
94
112
  & .crisp-recordpage-section-head .crisp-recordpage-section-title { padding: 0; }
95
113
  & .crisp-recordpage-section-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
@@ -97,7 +115,6 @@
97
115
  & .crisp-recordpage-addbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 26px; padding: 0 var(--crisp-space-1-5); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
98
116
  & .crisp-recordpage-addbtn:hover { background: var(--crisp-bg-raised); }
99
117
  & .crisp-recordpage-addbtn svg { flex-shrink: 0; }
100
-
101
118
  & .crisp-recordpage-composer { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding: var(--crisp-space-2); border: 1px solid var(--crisp-border-default); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); margin-bottom: var(--crisp-space-2); }
102
119
  & .crisp-recordpage-composer textarea { width: 100%; min-height: 56px; resize: vertical; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
103
120
  & .crisp-recordpage-composer textarea:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); border-radius: var(--crisp-radius-md); }
@@ -107,23 +124,18 @@
107
124
  & .crisp-recordpage-btn:hover { background: var(--crisp-bg-raised); }
108
125
  & .crisp-recordpage-btn--primary { box-shadow: none; background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); }
109
126
  & .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
110
-
111
127
  & .crisp-recordpage-note { display: flex; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
112
128
  & .crisp-recordpage-note-body { flex: 1; min-width: 0; }
113
129
  & .crisp-recordpage-note-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-bottom: var(--crisp-space-0-5); }
114
130
  & .crisp-recordpage-note-author { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
115
131
  & .crisp-recordpage-note-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
116
132
  & .crisp-recordpage-note-text { font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
117
-
118
133
  & .crisp-recordpage-task { display: flex; align-items: flex-start; gap: var(--crisp-space-2); padding: var(--crisp-space-1-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
119
134
  & .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); padding-top: 1px; }
120
135
  & .crisp-recordpage-task[data-done] .crisp-recordpage-task-text { text-decoration: line-through; color: var(--crisp-fg-muted); }
121
-
122
136
  & .crisp-recordpage-emptycard { display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-6) var(--crisp-space-3); border: 1px dashed var(--crisp-border-default); border-radius: var(--crisp-radius-xl); color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); text-align: center; }
123
137
  & .crisp-recordpage-emptycard svg { color: var(--crisp-fg-muted); opacity: 0.7; }
124
-
125
138
  & .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
126
-
127
139
  & .crisp-recordstable-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); }
128
140
  & .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
129
141
  & .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); }
@@ -61,27 +61,46 @@
61
61
  & .crisp-recordpage-field-label svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
62
62
  & .crisp-recordpage-field-label-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
63
63
  & .crisp-recordpage-field-value { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); padding-top: 2px; line-height: 20px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
64
+ & .crisp-recordpage-field-value[data-editable] { cursor: pointer; }
65
+ /* the click-to-edit trigger is a real <button> (keyboard-operable),
66
+ reset back to plain inline text and made a flex-wrap container of its
67
+ own -- FieldValue can render several chips (tags), which still need
68
+ to wrap the way they did before this had a button around them at
69
+ all. It wraps only FieldValue, never the error slot beside it (see
70
+ the component's own comment) -- NOT `display: contents`, which drops a button's accessible semantics in some engines. */
71
+ & .crisp-recordpage-edit-trigger { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); margin: 0; padding: 0; border: none; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer; }
64
72
  & .crisp-recordpage-empty { color: var(--crisp-fg-muted); }
73
+
74
+ /* #431: measured live on app.attio.com's record page Details column -- clicking a field's value opens a plain, borderless field exactly
75
+ filling the value's own box (no border, no background, no box-shadow), same font as the value it replaces (hence `font: inherit`, not a restated size/weight). A field row's own hover tint
76
+ above already carries the "this is interactive" affordance; the
77
+ pending/error treatment isn't Attio-measured -- it reuses the same tokens DataTable's identical states already use (opacity 0.5,
78
+ `bgDanger`), per #385/#430. */
79
+ & .crisp-recordpage-edit { display: block; width: 100%; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; box-shadow: none; outline: none; font: inherit; color: inherit; }
80
+ /* not Attio-measured (its own field shows no focus ring either, caret
81
+ only) -- kept for real keyboard users, the same brand ring every
82
+ other crisp field already shows on focus (DataTable's identical .crisp-datatable-edit:focus, e.g.). */
83
+ & .crisp-recordpage-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
84
+ & .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
85
+ & .crisp-recordpage-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
86
+ & .crisp-recordpage-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
87
+ & .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
88
+ & .crisp-recordpage-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
65
89
  & .crisp-recordpage-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
66
90
  & .crisp-recordpage-link:hover { text-decoration: underline; }
67
91
  & .crisp-recordpage-emptystate { padding: var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
68
-
69
92
  & .crisp-recordpage-main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; padding: var(--crisp-space-3) var(--crisp-space-5); }
70
-
71
93
  & .crisp-recordpage-panel { padding-top: var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
72
94
  & .crisp-recordpage-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); background: var(--crisp-bg-surface); overflow: hidden; }
73
95
  & .crisp-recordpage-card-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.14px; color: var(--crisp-fg-primary); padding: var(--crisp-space-3) var(--crisp-space-3) var(--crisp-space-2); }
74
96
  & .crisp-recordpage-hl-card { border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-2xl); overflow: hidden; }
75
- /* gap-as-gridlines: a 1px gap over the border colour draws dividers at any
76
- column count, so the grid stays responsive (auto-fit) without per-cell
77
- nth-child border hacks that break when the column count changes. */
97
+ /* gap-as-gridlines: a 1px gap over the border colour draws dividers at any column count, so the grid stays responsive (auto-fit) without per-cell nth-child border hacks that break when the column count changes. */
78
98
  & .crisp-recordpage-hl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1px; background: var(--crisp-border-subtle); }
79
99
  & .crisp-recordpage-hl-cell { position: relative; min-height: 84px; padding: var(--crisp-space-2-5) var(--crisp-space-3); background: var(--crisp-bg-surface); }
80
100
  & .crisp-recordpage-hl-icon { position: absolute; top: var(--crisp-space-2-5); inset-inline-end: var(--crisp-space-3); color: var(--crisp-fg-muted); }
81
101
  & .crisp-recordpage-hl-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); margin-bottom: var(--crisp-space-2-5); padding-inline-end: var(--crisp-space-4); }
82
102
  & .crisp-recordpage-hl-value { display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
83
103
  & .crisp-recordpage-hl-value--empty { color: var(--crisp-fg-muted); font-weight: var(--crisp-weight-regular); }
84
-
85
104
  & .crisp-recordpage-feed { display: flex; flex-direction: column; }
86
105
  & .crisp-recordpage-feed-item { display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2) 0; }
87
106
  & .crisp-recordpage-feed-dot { position: relative; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex-shrink: 0; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
@@ -90,7 +109,6 @@
90
109
  & .crisp-recordpage-feed-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); margin-top: 2px; }
91
110
  & .crisp-recordpage-feed-more { align-self: flex-start; margin-top: var(--crisp-space-1); border: none; background: none; padding: var(--crisp-space-1) 0; color: var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
92
111
  & .crisp-recordpage-feed-more:hover { text-decoration: underline; }
93
-
94
112
  & .crisp-recordpage-section-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-1); }
95
113
  & .crisp-recordpage-section-head .crisp-recordpage-section-title { padding: 0; }
96
114
  & .crisp-recordpage-section-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
@@ -98,7 +116,6 @@
98
116
  & .crisp-recordpage-addbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 26px; padding: 0 var(--crisp-space-1-5); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
99
117
  & .crisp-recordpage-addbtn:hover { background: var(--crisp-bg-raised); }
100
118
  & .crisp-recordpage-addbtn svg { flex-shrink: 0; }
101
-
102
119
  & .crisp-recordpage-composer { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding: var(--crisp-space-2); border: 1px solid var(--crisp-border-default); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); margin-bottom: var(--crisp-space-2); }
103
120
  & .crisp-recordpage-composer textarea { width: 100%; min-height: 56px; resize: vertical; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
104
121
  & .crisp-recordpage-composer textarea:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); border-radius: var(--crisp-radius-md); }
@@ -108,23 +125,18 @@
108
125
  & .crisp-recordpage-btn:hover { background: var(--crisp-bg-raised); }
109
126
  & .crisp-recordpage-btn--primary { box-shadow: none; background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); }
110
127
  & .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
111
-
112
128
  & .crisp-recordpage-note { display: flex; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
113
129
  & .crisp-recordpage-note-body { flex: 1; min-width: 0; }
114
130
  & .crisp-recordpage-note-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-bottom: var(--crisp-space-0-5); }
115
131
  & .crisp-recordpage-note-author { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
116
132
  & .crisp-recordpage-note-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
117
133
  & .crisp-recordpage-note-text { font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
118
-
119
134
  & .crisp-recordpage-task { display: flex; align-items: flex-start; gap: var(--crisp-space-2); padding: var(--crisp-space-1-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
120
135
  & .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); padding-top: 1px; }
121
136
  & .crisp-recordpage-task[data-done] .crisp-recordpage-task-text { text-decoration: line-through; color: var(--crisp-fg-muted); }
122
-
123
137
  & .crisp-recordpage-emptycard { display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-6) var(--crisp-space-3); border: 1px dashed var(--crisp-border-default); border-radius: var(--crisp-radius-xl); color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); text-align: center; }
124
138
  & .crisp-recordpage-emptycard svg { color: var(--crisp-fg-muted); opacity: 0.7; }
125
-
126
139
  & .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
127
-
128
140
  & .crisp-recordstable-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); }
129
141
  & .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
130
142
  & .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); }
@@ -14,7 +14,26 @@
14
14
  & .crisp-recordpanel-field-label { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 132px; flex-shrink: 0; padding-top: 3px; color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); }
15
15
  & .crisp-recordpanel-field-label svg { flex-shrink: 0; }
16
16
  & .crisp-recordpanel-field-value { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); padding-top: 3px; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
17
+ & .crisp-recordpanel-field-value[data-editable] { cursor: pointer; }
18
+ /* the click-to-edit trigger is a real <button> (keyboard-operable), reset back to plain inline text and made a flex-wrap container of its own -- FieldValue can render several chips (tags), which still need to wrap the way they did before this had a button around them at all. It wraps only FieldValue, never the error slot beside it (see the component's own comment) -- NOT `display: contents`, which
19
+ drops a button's accessible semantics in some engines. */
20
+ & .crisp-recordpanel-edit-trigger { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); margin: 0; padding: 0; border: none; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer; }
17
21
  & .crisp-recordpanel-empty { color: var(--crisp-fg-muted); }
22
+ /* #431: measured live on app.attio.com's record side panel -- clicking a
23
+ field's value opens a plain, borderless field exactly filling the value's own box (no border, no background, no box-shadow), same font
24
+ as the value it replaces (hence `font: inherit`, not a restated
25
+ size/weight). A field row's own hover tint above already carries the "this is interactive" affordance; the pending/error treatment isn't
26
+ Attio-measured -- it reuses the same tokens DataTable's identical states already use (opacity 0.5, `bgDanger`), per #385/#430. */
27
+ & .crisp-recordpanel-edit { display: block; width: 100%; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; box-shadow: none; outline: none; font: inherit; color: inherit; }
28
+ /* not Attio-measured (its own field shows no focus ring either, caret only) -- kept for real keyboard users, the same brand ring every other crisp field already shows on focus (DataTable's identical
29
+ .crisp-datatable-edit:focus, e.g.). */
30
+ & .crisp-recordpanel-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
31
+ & .crisp-recordpanel-edit:disabled { opacity: 0.5; cursor: not-allowed; }
32
+ & .crisp-recordpanel-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
33
+ & .crisp-recordpanel-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
34
+ & .crisp-recordpanel-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
35
+ & .crisp-recordpanel-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
36
+
18
37
  & .crisp-recordstable-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); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
19
38
  & .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
20
39
  & .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-success) 30%, transparent); }
@@ -25,11 +44,9 @@
25
44
  & .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
26
45
  & .crisp-recordpanel-link { color: var(--crisp-bg-brand-solid); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
27
46
  & .crisp-recordpanel-link:hover { text-decoration: underline; }
28
- /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
29
- same horizontal inset (space.3), the same hairline border token
30
- (borderSubtle, flipped to border-top since this sits at the opposite
31
- edge), and the header/body gap (space.2) for its own vertical padding
32
- and for spacing between whatever it's given. */
47
+
48
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the
49
+ header's own measured values rather than inventing new ones -- the same horizontal inset (space.3), the same hairline border token (borderSubtle, flipped to border-top since this sits at the opposite edge), and the header/body gap (space.2) for its own vertical padding and for spacing between whatever it's given. */
33
50
  & .crisp-recordpanel-footer { flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); border-top: 1px solid var(--crisp-border-subtle); }
34
51
  }
35
52
 
@@ -15,7 +15,26 @@
15
15
  & .crisp-recordpanel-field-label { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 132px; flex-shrink: 0; padding-top: 3px; color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); }
16
16
  & .crisp-recordpanel-field-label svg { flex-shrink: 0; }
17
17
  & .crisp-recordpanel-field-value { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); padding-top: 3px; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
18
+ & .crisp-recordpanel-field-value[data-editable] { cursor: pointer; }
19
+ /* the click-to-edit trigger is a real <button> (keyboard-operable), reset back to plain inline text and made a flex-wrap container of its own -- FieldValue can render several chips (tags), which still need to wrap the way they did before this had a button around them at all. It wraps only FieldValue, never the error slot beside it (see the component's own comment) -- NOT `display: contents`, which
20
+ drops a button's accessible semantics in some engines. */
21
+ & .crisp-recordpanel-edit-trigger { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); margin: 0; padding: 0; border: none; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer; }
18
22
  & .crisp-recordpanel-empty { color: var(--crisp-fg-muted); }
23
+ /* #431: measured live on app.attio.com's record side panel -- clicking a
24
+ field's value opens a plain, borderless field exactly filling the value's own box (no border, no background, no box-shadow), same font
25
+ as the value it replaces (hence `font: inherit`, not a restated
26
+ size/weight). A field row's own hover tint above already carries the "this is interactive" affordance; the pending/error treatment isn't
27
+ Attio-measured -- it reuses the same tokens DataTable's identical states already use (opacity 0.5, `bgDanger`), per #385/#430. */
28
+ & .crisp-recordpanel-edit { display: block; width: 100%; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; box-shadow: none; outline: none; font: inherit; color: inherit; }
29
+ /* not Attio-measured (its own field shows no focus ring either, caret only) -- kept for real keyboard users, the same brand ring every other crisp field already shows on focus (DataTable's identical
30
+ .crisp-datatable-edit:focus, e.g.). */
31
+ & .crisp-recordpanel-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
32
+ & .crisp-recordpanel-edit:disabled { opacity: 0.5; cursor: not-allowed; }
33
+ & .crisp-recordpanel-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
34
+ & .crisp-recordpanel-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
35
+ & .crisp-recordpanel-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
36
+ & .crisp-recordpanel-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; }
37
+
19
38
  & .crisp-recordstable-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); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
20
39
  & .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
21
40
  & .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-success) 30%, transparent); }
@@ -26,11 +45,9 @@
26
45
  & .crisp-recordstable-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)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
27
46
  & .crisp-recordpanel-link { color: var(--crisp-bg-brand-solid); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
28
47
  & .crisp-recordpanel-link:hover { text-decoration: underline; }
29
- /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
30
- same horizontal inset (space.3), the same hairline border token
31
- (borderSubtle, flipped to border-top since this sits at the opposite
32
- edge), and the header/body gap (space.2) for its own vertical padding
33
- and for spacing between whatever it's given. */
48
+
49
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the
50
+ header's own measured values rather than inventing new ones -- the same horizontal inset (space.3), the same hairline border token (borderSubtle, flipped to border-top since this sits at the opposite edge), and the header/body gap (space.2) for its own vertical padding and for spacing between whatever it's given. */
34
51
  & .crisp-recordpanel-footer { flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); border-top: 1px solid var(--crisp-border-subtle); }
35
52
  }
36
53
 
@@ -3,11 +3,12 @@
3
3
  & * { box-sizing: border-box; }
4
4
  /* toolbar */
5
5
  & .crisp-recordstable-toolbar { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: 0 var(--crisp-space-3) var(--crisp-space-2); }
6
+ & .crisp-recordstable-toolbar[data-inline] { padding-bottom: 0; }
6
7
  & .crisp-recordstable-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); padding: 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
7
8
  & .crisp-recordstable-search svg { flex-shrink: 0; }
8
9
  & .crisp-recordstable-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
9
10
  & .crisp-recordstable-search input::placeholder { color: var(--crisp-fg-muted); }
10
- & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; transition: background-color .12s; }
11
+ & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
11
12
  & .crisp-recordstable-tool:hover { background: var(--crisp-bg-raised); }
12
13
  & .crisp-recordstable-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
13
14
  & .crisp-recordstable-tool--active { background: var(--crisp-bg-raised); }
@@ -111,6 +112,13 @@
111
112
  & .crisp-recordstable-td--picker .crisp-multiselect-chips .crisp-recordstable-tag { flex-shrink: 0; max-width: none; }
112
113
  & .crisp-recordstable-td--picker .crisp-multiselect-chevron { display: none; }
113
114
  & .crisp-recordstable-td--picker .crisp-multiselect-placeholder { color: var(--crisp-fg-muted); }
115
+ /* reference picker: a leading 16px avatar per option, plus a trailing
116
+ "Create X" row when the consumer supplies one (#449) -- measured live
117
+ against Attio's own picker (16px circular avatar, 16px plain icon for the create row, both sitting in the option's existing 8px gap, no new
118
+ gap value invented). */
119
+ & .crisp-recordstable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
120
+ & .crisp-recordstable-refoption-avatar { flex-shrink: 0; }
121
+ & .crisp-recordstable-refoption--create { color: var(--crisp-fg-muted); }
114
122
  & .crisp-recordstable-status { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
115
123
  & .crisp-recordstable-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
116
124
  & .crisp-recordstable-cell { display: flex; align-items: center; gap: var(--crisp-space-2); height: 100%; min-width: 0; }
@@ -176,7 +184,7 @@
176
184
  & .crisp-recordstable-empty { display: flex; align-items: center; justify-content: center; min-height: 320px; padding: var(--crisp-space-8) var(--crisp-space-4); }
177
185
 
178
186
  /* summary footer — per-column aggregation, sticky to the bottom */
179
- & .crisp-recordstable-sum { position: sticky; bottom: 0; z-index: 2; height: var(--crisp-size-tableRow); padding: 0; border-top: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); background: var(--crisp-bg-surface); }
187
+ & .crisp-recordstable-sum { position: sticky; bottom: 0; z-index: 2; height: var(--crisp-size-tableRow); padding: 0; border-top: 1px solid var(--crisp-border-subtle); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); background: var(--crisp-bg-surface); }
180
188
  & tfoot .crisp-recordstable-col-check, & tfoot .crisp-recordstable-col-entity { position: sticky; bottom: 0; z-index: 3; }
181
189
  & .crisp-recordstable-sum:last-child { border-inline-end: none; }
182
190
  & .crisp-recordstable-sum-btn { display: flex; align-items: center; justify-content: flex-end; gap: var(--crisp-space-1-5); width: 100%; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); cursor: pointer; white-space: nowrap; overflow: hidden; }
@@ -4,11 +4,12 @@
4
4
  & * { box-sizing: border-box; }
5
5
  /* toolbar */
6
6
  & .crisp-recordstable-toolbar { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: 0 var(--crisp-space-3) var(--crisp-space-2); }
7
+ & .crisp-recordstable-toolbar[data-inline] { padding-bottom: 0; }
7
8
  & .crisp-recordstable-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); padding: 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
8
9
  & .crisp-recordstable-search svg { flex-shrink: 0; }
9
10
  & .crisp-recordstable-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
10
11
  & .crisp-recordstable-search input::placeholder { color: var(--crisp-fg-muted); }
11
- & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; transition: background-color .12s; }
12
+ & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
12
13
  & .crisp-recordstable-tool:hover { background: var(--crisp-bg-raised); }
13
14
  & .crisp-recordstable-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
14
15
  & .crisp-recordstable-tool--active { background: var(--crisp-bg-raised); }
@@ -112,6 +113,13 @@
112
113
  & .crisp-recordstable-td--picker .crisp-multiselect-chips .crisp-recordstable-tag { flex-shrink: 0; max-width: none; }
113
114
  & .crisp-recordstable-td--picker .crisp-multiselect-chevron { display: none; }
114
115
  & .crisp-recordstable-td--picker .crisp-multiselect-placeholder { color: var(--crisp-fg-muted); }
116
+ /* reference picker: a leading 16px avatar per option, plus a trailing
117
+ "Create X" row when the consumer supplies one (#449) -- measured live
118
+ against Attio's own picker (16px circular avatar, 16px plain icon for the create row, both sitting in the option's existing 8px gap, no new
119
+ gap value invented). */
120
+ & .crisp-recordstable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
121
+ & .crisp-recordstable-refoption-avatar { flex-shrink: 0; }
122
+ & .crisp-recordstable-refoption--create { color: var(--crisp-fg-muted); }
115
123
  & .crisp-recordstable-status { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
116
124
  & .crisp-recordstable-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
117
125
  & .crisp-recordstable-cell { display: flex; align-items: center; gap: var(--crisp-space-2); height: 100%; min-width: 0; }
@@ -177,7 +185,7 @@
177
185
  & .crisp-recordstable-empty { display: flex; align-items: center; justify-content: center; min-height: 320px; padding: var(--crisp-space-8) var(--crisp-space-4); }
178
186
 
179
187
  /* summary footer — per-column aggregation, sticky to the bottom */
180
- & .crisp-recordstable-sum { position: sticky; bottom: 0; z-index: 2; height: var(--crisp-size-tableRow); padding: 0; border-top: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); background: var(--crisp-bg-surface); }
188
+ & .crisp-recordstable-sum { position: sticky; bottom: 0; z-index: 2; height: var(--crisp-size-tableRow); padding: 0; border-top: 1px solid var(--crisp-border-subtle); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); background: var(--crisp-bg-surface); }
181
189
  & tfoot .crisp-recordstable-col-check, & tfoot .crisp-recordstable-col-entity { position: sticky; bottom: 0; z-index: 3; }
182
190
  & .crisp-recordstable-sum:last-child { border-inline-end: none; }
183
191
  & .crisp-recordstable-sum-btn { display: flex; align-items: center; justify-content: flex-end; gap: var(--crisp-space-1-5); width: 100%; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); cursor: pointer; white-space: nowrap; overflow: hidden; }
@@ -1,6 +1,6 @@
1
1
  .crisp-settingsshell {
2
- display: flex; min-height: 0; box-sizing: border-box; font-family: var(--crisp-font-sans); color: var(--crisp-fg-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
3
- & .crisp-settingsshell-nav { display: flex; flex-direction: column; width: 256px; flex-shrink: 0; min-height: 0; padding: var(--crisp-space-3); border-inline-end: 1px solid var(--crisp-border-subtle); }
2
+ display: flex; height: 100%; min-height: 0; box-sizing: border-box; font-family: var(--crisp-font-sans); color: var(--crisp-fg-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
3
+ & .crisp-settingsshell-nav { display: flex; flex-direction: column; width: 275px; flex-shrink: 0; min-height: 0; padding: var(--crisp-space-3); border-inline-end: 1px solid var(--crisp-border-subtle); }
4
4
  & .crisp-settingsshell-back { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 28px; padding: 0 var(--crisp-space-1); margin: 0 0 var(--crisp-space-2); border: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); cursor: pointer; border-radius: var(--crisp-radius-md); }
5
5
  & .crisp-settingsshell-back svg { color: var(--crisp-fg-muted); }
6
6
  & .crisp-settingsshell-back:hover svg { color: var(--crisp-fg-primary); }