@crisp-ui-kit/crisp 0.57.1 → 0.57.2

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 (252) hide show
  1. package/dist/authshell.css +9 -4
  2. package/dist/authshell.layered.css +9 -4
  3. package/dist/badge.css +3 -3
  4. package/dist/badge.layered.css +3 -3
  5. package/dist/board.css +28 -8
  6. package/dist/board.layered.css +28 -8
  7. package/dist/bottomsheet.css +26 -0
  8. package/dist/bottomsheet.d.ts +7 -0
  9. package/dist/bottomsheet.layered.css +28 -0
  10. package/dist/button.css +7 -6
  11. package/dist/button.layered.css +7 -6
  12. package/dist/checkbox.css +7 -3
  13. package/dist/checkbox.layered.css +7 -3
  14. package/dist/chip.css +15 -0
  15. package/dist/chip.d.ts +3 -1
  16. package/dist/chip.layered.css +15 -0
  17. package/dist/chipgroup.css +5 -0
  18. package/dist/chipgroup.d.ts +7 -0
  19. package/dist/chipgroup.layered.css +7 -0
  20. package/dist/cjs/components/badge/badge.recipe.js +22 -8
  21. package/dist/cjs/components/bottomsheet/bottomsheet.js +90 -0
  22. package/dist/cjs/components/bottomsheet/bottomsheet.recipe.js +77 -0
  23. package/dist/cjs/components/bottomsheet/index.js +9 -0
  24. package/dist/cjs/components/button/button.recipe.js +39 -15
  25. package/dist/cjs/components/checkbox/checkbox.recipe.js +61 -9
  26. package/dist/cjs/components/chip/chip.js +14 -2
  27. package/dist/cjs/components/chip/chip.recipe.js +69 -1
  28. package/dist/cjs/components/chipgroup/chipgroup.js +51 -0
  29. package/dist/cjs/components/chipgroup/chipgroup.recipe.js +36 -0
  30. package/dist/cjs/components/chipgroup/index.js +7 -0
  31. package/dist/cjs/components/datatable/datatable.js +5 -1
  32. package/dist/cjs/components/datatable/datatable.recipe.js +11 -2
  33. package/dist/cjs/components/dialog/dialog.recipe.js +21 -10
  34. package/dist/cjs/components/emptystate/emptystate.recipe.js +7 -2
  35. package/dist/cjs/components/fileupload/fileupload.js +21 -4
  36. package/dist/cjs/components/input/index.js +2 -1
  37. package/dist/cjs/components/input/input.js +31 -1
  38. package/dist/cjs/components/input/input.recipe.js +16 -9
  39. package/dist/cjs/components/menu/menu.recipe.js +21 -7
  40. package/dist/cjs/components/menusheet/index.js +9 -0
  41. package/dist/cjs/components/menusheet/menusheet.js +133 -0
  42. package/dist/cjs/components/menusheet/menusheet.recipe.js +103 -0
  43. package/dist/cjs/components/multiselect/multiselect.js +1 -1
  44. package/dist/cjs/components/multiselect/multiselect.recipe.js +33 -9
  45. package/dist/cjs/components/select/select.js +1 -1
  46. package/dist/cjs/components/select/select.recipe.js +38 -12
  47. package/dist/cjs/components/snackbar/index.js +8 -0
  48. package/dist/cjs/components/snackbar/snackbar.js +121 -0
  49. package/dist/cjs/components/snackbar/snackbar.recipe.js +71 -0
  50. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  51. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +37 -12
  52. package/dist/cjs/components/tabs/tabs.js +10 -4
  53. package/dist/cjs/components/tabs/tabs.recipe.js +23 -6
  54. package/dist/cjs/components/textarea/textarea.recipe.js +11 -8
  55. package/dist/cjs/index.js +7 -0
  56. package/dist/cjs/product/authshell/authshell.recipe.js +5 -1
  57. package/dist/cjs/product/command/command.recipe.js +4 -1
  58. package/dist/cjs/product/comments/comments.js +7 -2
  59. package/dist/cjs/product/comments/comments.recipe.js +25 -7
  60. package/dist/cjs/product/memberssettings/index.js +6 -0
  61. package/dist/cjs/product/memberssettings/memberssettings.js +99 -0
  62. package/dist/cjs/product/memberssettings/memberssettings.recipe.js +83 -0
  63. package/dist/cjs/product/notificationssettings/index.js +5 -0
  64. package/dist/cjs/product/notificationssettings/notificationssettings.js +19 -0
  65. package/dist/cjs/product/notificationssettings/notificationssettings.recipe.js +44 -0
  66. package/dist/cjs/product/recordpage/recordpage.js +57 -6
  67. package/dist/cjs/product/recordpage/recordpage.recipe.js +43 -6
  68. package/dist/cjs/product/workspacegeneralsettings/index.js +5 -0
  69. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.js +21 -0
  70. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +51 -0
  71. package/dist/cjs/tokens/contract.js +1 -0
  72. package/dist/cjs/tokens/elevation.js +8 -5
  73. package/dist/cjs/tokens/palette.js +28 -8
  74. package/dist/cjs/tokens/scale.js +44 -2
  75. package/dist/cjs/tokens/semantic.js +2 -0
  76. package/dist/columncalc.css +15 -7
  77. package/dist/columncalc.layered.css +15 -7
  78. package/dist/command.css +23 -19
  79. package/dist/command.layered.css +23 -19
  80. package/dist/comments.css +28 -19
  81. package/dist/comments.d.ts +2 -2
  82. package/dist/comments.layered.css +28 -19
  83. package/dist/datatable.css +161 -67
  84. package/dist/datatable.layered.css +161 -67
  85. package/dist/datepicker.css +13 -41
  86. package/dist/datepicker.layered.css +13 -41
  87. package/dist/datepickerfield.css +4 -1
  88. package/dist/datepickerfield.layered.css +4 -1
  89. package/dist/dialog.css +12 -7
  90. package/dist/dialog.layered.css +12 -7
  91. package/dist/emptystate.css +5 -3
  92. package/dist/emptystate.layered.css +5 -3
  93. package/dist/esm/components/badge/badge.recipe.js +22 -8
  94. package/dist/esm/components/bottomsheet/bottomsheet.js +52 -0
  95. package/dist/esm/components/bottomsheet/bottomsheet.recipe.js +74 -0
  96. package/dist/esm/components/bottomsheet/index.js +2 -0
  97. package/dist/esm/components/button/button.recipe.js +40 -16
  98. package/dist/esm/components/checkbox/checkbox.recipe.js +61 -9
  99. package/dist/esm/components/chip/chip.js +14 -2
  100. package/dist/esm/components/chip/chip.recipe.js +69 -1
  101. package/dist/esm/components/chipgroup/chipgroup.js +15 -0
  102. package/dist/esm/components/chipgroup/chipgroup.recipe.js +33 -0
  103. package/dist/esm/components/chipgroup/index.js +2 -0
  104. package/dist/esm/components/datatable/datatable.js +5 -1
  105. package/dist/esm/components/datatable/datatable.recipe.js +11 -2
  106. package/dist/esm/components/dialog/dialog.recipe.js +22 -11
  107. package/dist/esm/components/emptystate/emptystate.recipe.js +7 -2
  108. package/dist/esm/components/fileupload/fileupload.js +20 -5
  109. package/dist/esm/components/input/index.js +1 -1
  110. package/dist/esm/components/input/input.js +30 -0
  111. package/dist/esm/components/input/input.recipe.js +17 -10
  112. package/dist/esm/components/menu/menu.recipe.js +21 -7
  113. package/dist/esm/components/menusheet/index.js +2 -0
  114. package/dist/esm/components/menusheet/menusheet.js +95 -0
  115. package/dist/esm/components/menusheet/menusheet.recipe.js +100 -0
  116. package/dist/esm/components/multiselect/multiselect.js +1 -1
  117. package/dist/esm/components/multiselect/multiselect.recipe.js +33 -9
  118. package/dist/esm/components/select/select.js +1 -1
  119. package/dist/esm/components/select/select.recipe.js +38 -12
  120. package/dist/esm/components/snackbar/index.js +2 -0
  121. package/dist/esm/components/snackbar/snackbar.js +84 -0
  122. package/dist/esm/components/snackbar/snackbar.recipe.js +68 -0
  123. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  124. package/dist/esm/components/tabletoolbar/tabletoolbar.js +37 -12
  125. package/dist/esm/components/tabs/tabs.js +10 -4
  126. package/dist/esm/components/tabs/tabs.recipe.js +23 -6
  127. package/dist/esm/components/textarea/textarea.recipe.js +12 -9
  128. package/dist/esm/index.js +7 -0
  129. package/dist/esm/product/authshell/authshell.recipe.js +5 -1
  130. package/dist/esm/product/command/command.recipe.js +4 -1
  131. package/dist/esm/product/comments/comments.js +7 -2
  132. package/dist/esm/product/comments/comments.recipe.js +25 -7
  133. package/dist/esm/product/memberssettings/index.js +1 -0
  134. package/dist/esm/product/memberssettings/memberssettings.js +62 -0
  135. package/dist/esm/product/memberssettings/memberssettings.recipe.js +80 -0
  136. package/dist/esm/product/notificationssettings/index.js +1 -0
  137. package/dist/esm/product/notificationssettings/notificationssettings.js +16 -0
  138. package/dist/esm/product/notificationssettings/notificationssettings.recipe.js +41 -0
  139. package/dist/esm/product/recordpage/recordpage.js +58 -7
  140. package/dist/esm/product/recordpage/recordpage.recipe.js +43 -6
  141. package/dist/esm/product/workspacegeneralsettings/index.js +1 -0
  142. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.js +18 -0
  143. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +48 -0
  144. package/dist/esm/tokens/contract.js +2 -1
  145. package/dist/esm/tokens/elevation.js +8 -5
  146. package/dist/esm/tokens/palette.js +28 -8
  147. package/dist/esm/tokens/scale.js +43 -1
  148. package/dist/esm/tokens/semantic.js +2 -0
  149. package/dist/fileupload.css +3 -1
  150. package/dist/fileupload.layered.css +3 -1
  151. package/dist/input.css +6 -4
  152. package/dist/input.layered.css +6 -4
  153. package/dist/listpage.css +3 -1
  154. package/dist/listpage.layered.css +3 -1
  155. package/dist/memberssettings.css +67 -0
  156. package/dist/memberssettings.d.ts +7 -0
  157. package/dist/memberssettings.layered.css +69 -0
  158. package/dist/menu.css +2 -2
  159. package/dist/menu.layered.css +2 -2
  160. package/dist/menusheet.css +36 -0
  161. package/dist/menusheet.d.ts +7 -0
  162. package/dist/menusheet.layered.css +38 -0
  163. package/dist/multiselect.css +13 -6
  164. package/dist/multiselect.layered.css +13 -6
  165. package/dist/native/tokens.d.ts +2 -1
  166. package/dist/native/tokens.js +20 -3
  167. package/dist/notificationssettings.css +22 -0
  168. package/dist/notificationssettings.d.ts +7 -0
  169. package/dist/notificationssettings.layered.css +24 -0
  170. package/dist/objectsettings.css +4 -4
  171. package/dist/objectsettings.layered.css +4 -4
  172. package/dist/recordpage.css +67 -23
  173. package/dist/recordpage.layered.css +67 -23
  174. package/dist/recordpanel.css +35 -16
  175. package/dist/recordpanel.layered.css +35 -16
  176. package/dist/resizablepanel.css +8 -5
  177. package/dist/resizablepanel.layered.css +8 -5
  178. package/dist/richtext.css +24 -8
  179. package/dist/richtext.layered.css +24 -8
  180. package/dist/savedviews.css +9 -2
  181. package/dist/savedviews.layered.css +9 -2
  182. package/dist/select.css +25 -5
  183. package/dist/select.layered.css +25 -5
  184. package/dist/settingsshell.css +2 -3
  185. package/dist/settingsshell.layered.css +2 -3
  186. package/dist/slider.css +6 -27
  187. package/dist/slider.layered.css +6 -27
  188. package/dist/snackbar.css +24 -0
  189. package/dist/snackbar.d.ts +7 -0
  190. package/dist/snackbar.layered.css +26 -0
  191. package/dist/statcard.css +3 -1
  192. package/dist/statcard.layered.css +3 -1
  193. package/dist/styles.css +859 -345
  194. package/dist/styles.layered.css +859 -345
  195. package/dist/table.css +28 -10
  196. package/dist/table.layered.css +28 -10
  197. package/dist/tabletoolbar.css +35 -24
  198. package/dist/tabletoolbar.layered.css +35 -24
  199. package/dist/tabs.css +18 -4
  200. package/dist/tabs.layered.css +18 -4
  201. package/dist/textarea.css +2 -6
  202. package/dist/textarea.layered.css +2 -6
  203. package/dist/themepicker.css +2 -1
  204. package/dist/themepicker.layered.css +2 -1
  205. package/dist/tokens.css +13 -2
  206. package/dist/tokens.json +20 -3
  207. package/dist/types/components/bottomsheet/bottomsheet.d.ts +35 -0
  208. package/dist/types/components/bottomsheet/bottomsheet.recipe.d.ts +3 -0
  209. package/dist/types/components/bottomsheet/index.d.ts +2 -0
  210. package/dist/types/components/button/button.recipe.d.ts +11 -6
  211. package/dist/types/components/checkbox/checkbox.recipe.d.ts +28 -5
  212. package/dist/types/components/chip/chip.d.ts +22 -0
  213. package/dist/types/components/chip/chip.recipe.d.ts +48 -0
  214. package/dist/types/components/chipgroup/chipgroup.d.ts +14 -0
  215. package/dist/types/components/chipgroup/chipgroup.recipe.d.ts +5 -0
  216. package/dist/types/components/chipgroup/index.d.ts +2 -0
  217. package/dist/types/components/datatable/datatable.d.ts +19 -0
  218. package/dist/types/components/fileupload/fileupload.d.ts +26 -0
  219. package/dist/types/components/input/index.d.ts +1 -1
  220. package/dist/types/components/input/input.d.ts +72 -0
  221. package/dist/types/components/menusheet/index.d.ts +2 -0
  222. package/dist/types/components/menusheet/menusheet.d.ts +42 -0
  223. package/dist/types/components/menusheet/menusheet.recipe.d.ts +3 -0
  224. package/dist/types/components/snackbar/index.d.ts +2 -0
  225. package/dist/types/components/snackbar/snackbar.d.ts +36 -0
  226. package/dist/types/components/snackbar/snackbar.recipe.d.ts +5 -0
  227. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +28 -1
  228. package/dist/types/components/tabs/tabs.d.ts +11 -1
  229. package/dist/types/index.d.ts +7 -0
  230. package/dist/types/product/comments/comments.d.ts +9 -3
  231. package/dist/types/product/comments/comments.recipe.d.ts +12 -2
  232. package/dist/types/product/memberssettings/index.d.ts +1 -0
  233. package/dist/types/product/memberssettings/memberssettings.d.ts +85 -0
  234. package/dist/types/product/memberssettings/memberssettings.recipe.d.ts +3 -0
  235. package/dist/types/product/notificationssettings/index.d.ts +1 -0
  236. package/dist/types/product/notificationssettings/notificationssettings.d.ts +37 -0
  237. package/dist/types/product/notificationssettings/notificationssettings.recipe.d.ts +3 -0
  238. package/dist/types/product/recordpage/recordpage.d.ts +53 -2
  239. package/dist/types/product/workspacegeneralsettings/index.d.ts +1 -0
  240. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.d.ts +48 -0
  241. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.recipe.d.ts +3 -0
  242. package/dist/types/tokens/contract.d.ts +4 -1
  243. package/dist/types/tokens/elevation.d.ts +8 -5
  244. package/dist/types/tokens/palette.d.ts +9 -1
  245. package/dist/types/tokens/scale.d.ts +11 -1
  246. package/dist/types/tokens/semantic.d.ts +2 -0
  247. package/dist/wheelpicker.css +4 -2
  248. package/dist/wheelpicker.layered.css +4 -2
  249. package/dist/workspacegeneralsettings.css +30 -0
  250. package/dist/workspacegeneralsettings.d.ts +7 -0
  251. package/dist/workspacegeneralsettings.layered.css +32 -0
  252. package/package.json +36 -1
@@ -311,7 +311,14 @@ function NotesSection({ notes, onAdd, labels, }) {
311
311
  return ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: labels.tabs.notes, count: notes.length, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-addbtn", onClick: () => setOpen(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), labels.newNote] }) }), open && ((0, jsx_runtime_1.jsx)(Composer, { labels: labels, multiline: true, placeholder: labels.notePlaceholder, submitLabel: labels.saveNote, onCancel: () => setOpen(false), onSubmit: (t) => {
312
312
  onAdd(t);
313
313
  setOpen(false);
314
- } })), notes.length === 0 && !open ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-emptystate", children: labels.noNotesYet })) : (notes.map((n) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-note", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "circle", tone: "neutral", children: "Y" }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-note-body", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-note-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-note-author", children: labels.you }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-note-time", children: n.time })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-note-text", children: n.text })] })] }, n.id))))] }));
314
+ } })), notes.length === 0 && !open ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-emptystate", children: labels.noNotesYet })) : (
315
+ // #871: one line, not a two-row card -- measured live: avatar,
316
+ // then the note's own title and body run together inline, then a
317
+ // relative time at the far end of the SAME row. No author name at
318
+ // all (Attio's real preview never shows one here, only the note's
319
+ // own content) -- `labels.you` stays defined for a consumer that
320
+ // still wants it elsewhere, just unused in this exact spot now.
321
+ notes.map((n) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-note", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-note-left", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "circle", tone: "neutral", children: "Y" }), n.title && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-note-title", children: n.title })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-note-text", children: n.text })] }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-note-time", children: n.time })] }, n.id))))] }));
315
322
  }
316
323
  function TasksSection({ tasks, onAdd, onToggle, labels, }) {
317
324
  const [open, setOpen] = React.useState(false);
@@ -344,13 +351,51 @@ function EmptyCard({ icon, children, }) {
344
351
  * `RecordPage`'s own root does: `<div className={recordpageRecipe()}>`
345
352
  * (also exported from this package).
346
353
  */
347
- function RecordDetails({ record, columns, sections, onEdit, labels, className, ...rest }) {
354
+ function RecordDetails({ record, columns, sections, onEdit, labels, className, resizable = false, width: controlledWidth, defaultWidth = DETAILS_DEFAULT_W, onWidthChange, minWidth = DETAILS_MIN_W, maxWidth = DETAILS_MAX_W, ...rest }) {
348
355
  const t = useRecordPageLabels(labels);
349
356
  const { editingKey, pendingKey, error, openEditing, closeEditing, commit } = (0, editable_field_1.useEditableField)(onEdit);
350
357
  const [detailsOpen, setDetailsOpen] = React.useState(true);
351
358
  const [searchOpen, setSearchOpen] = React.useState(false);
352
359
  const [fieldQuery, setFieldQuery] = React.useState("");
353
360
  const [showAllValues, setShowAllValues] = React.useState(false);
361
+ // #930: controlled/uncontrolled width, same split as RecordPanel's own
362
+ // resizable width -- only read when `resizable`, same reasoning as
363
+ // RecordPanel's own (a non-resizing consumer has no business carrying
364
+ // width state at all).
365
+ const [internalWidth, setInternalWidth] = React.useState(defaultWidth);
366
+ const width = controlledWidth ?? internalWidth;
367
+ const setWidth = (next) => {
368
+ const clamped = Math.max(minWidth, Math.min(maxWidth, next));
369
+ if (controlledWidth === undefined)
370
+ setInternalWidth(clamped);
371
+ onWidthChange?.(clamped);
372
+ };
373
+ const [detailsResizing, setDetailsResizing] = React.useState(false);
374
+ const detailsDragRef = React.useRef(null);
375
+ const onDetailsResizeDown = (e) => {
376
+ // This column is a leading (left-docked) rail, the mirror image of
377
+ // RecordPanel's own trailing (right-docked) one -- a rightward pull
378
+ // grows it in LTR, the same direction RecordPage's own internal aside
379
+ // resize already uses (#212/#255).
380
+ detailsDragRef.current = {
381
+ x: e.clientX,
382
+ w: width,
383
+ dir: (0, core_1.isRtlElement)(e.currentTarget) ? -1 : 1,
384
+ };
385
+ setDetailsResizing(true);
386
+ e.target.setPointerCapture(e.pointerId);
387
+ };
388
+ const onDetailsResizeMove = (e) => {
389
+ const r = detailsDragRef.current;
390
+ if (!r)
391
+ return;
392
+ setWidth(r.w + r.dir * (e.clientX - r.x));
393
+ };
394
+ const onDetailsResizeUp = (e) => {
395
+ detailsDragRef.current = null;
396
+ setDetailsResizing(false);
397
+ e.target.releasePointerCapture(e.pointerId);
398
+ };
354
399
  const fields = columns.filter((col) => col.type !== "entity");
355
400
  const q = fieldQuery.trim().toLowerCase();
356
401
  const matches = (col) => !q || col.label.toLowerCase().includes(q);
@@ -387,7 +432,7 @@ function RecordDetails({ record, columns, sections, onEdit, labels, className, .
387
432
  groups = [{ title: undefined, fields: shown }];
388
433
  }
389
434
  const totalShown = groups.reduce((n, g) => n + g.fields.length, 0);
390
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, core_1.cx)("crisp-recordpage-section", className), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-detailhead", children: [searchOpen ? ((0, jsx_runtime_1.jsx)("input", {
435
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, core_1.cx)("crisp-recordpage-section", className), "data-resizable": resizable ? "" : undefined, style: resizable ? { width } : undefined, ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-detailhead", children: [searchOpen ? ((0, jsx_runtime_1.jsx)("input", {
391
436
  // biome-ignore lint/a11y/noAutofocus: focusing the just-opened field search is expected
392
437
  autoFocus: true, type: "text", className: "crisp-recordpage-fieldsearch", placeholder: t.searchPlaceholder, "aria-label": t.searchFields, value: fieldQuery, onChange: (e) => setFieldQuery(e.target.value), onKeyDown: (e) => {
393
438
  if (e.key === "Escape") {
@@ -461,7 +506,13 @@ function RecordDetails({ record, columns, sections, onEdit, labels, className, .
461
506
  setSearchOpen(next);
462
507
  if (!next)
463
508
  setFieldQuery("");
464
- }, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-noresults", children: t.noFieldsMatch(fieldQuery) })))] }));
509
+ }, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-noresults", children: t.noFieldsMatch(fieldQuery) }))), resizable && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-detailresize", "data-active": detailsResizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(width), "aria-valuemin": minWidth, "aria-valuemax": maxWidth, tabIndex: 0, onPointerDown: onDetailsResizeDown, onPointerMove: onDetailsResizeMove, onPointerUp: onDetailsResizeUp, onKeyDown: (e) => {
510
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
511
+ return;
512
+ const rtl = (0, core_1.isRtlElement)(e.currentTarget);
513
+ const grow = e.key === (rtl ? "ArrowLeft" : "ArrowRight");
514
+ setWidth(grow ? width + 16 : width - 16);
515
+ } }))] }));
465
516
  }
466
517
  RecordDetails.displayName = "RecordDetails";
467
518
  /**
@@ -472,7 +523,7 @@ RecordDetails.displayName = "RecordDetails";
472
523
  * Emails sections (Notes & Tasks are live — add and check things off). Drop it
473
524
  * into the app's main content area.
474
525
  */
475
- function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, quickActions: quickActionsProp, comments: commentsProp, onPostComment, currentUser, onShare, ...rest }) {
526
+ function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, onClose, closeLabel = "Close", onPrevious, previousRecordLabel = "Previous record", onNext, nextRecordLabel = "Next record", counterLabel, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, quickActions: quickActionsProp, comments: commentsProp, onPostComment, currentUser, onShare, ...rest }) {
476
527
  const t = useRecordPageLabels(labels);
477
528
  const [favorite, setFavorite] = React.useState(false);
478
529
  // Notes/Tasks are controlled when the caller supplies `notes`/`tasks` (even
@@ -728,7 +779,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
728
779
  const effectivePanel = tabsProp
729
780
  ? (active) => tabsProp.find((tb) => tb.id === active)?.render()
730
781
  : builtinPanel;
731
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-topbar", children: [(0, jsx_runtime_1.jsxs)("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-name", children: name })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-actions", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", onClick: onShare, children: t.share }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: () => setCommentsOpen(true), "aria-label": t.comments, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MessageSquare, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Merge, { size: 15, strokeWidth: 1.75 }), children: t.mergeRecords }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Settings, { size: 15, strokeWidth: 1.75 }), children: t.configurePage }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyPageUrl }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyRecordId }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, children: t.deleteRecord })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-body", children: [(0, jsx_runtime_1.jsxs)("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? ((0, jsx_runtime_1.jsx)("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-entity-name", children: name }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-quickactions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 16, strokeWidth: 1.75 }), t.addToList] }), quickActions.map((a, i) => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": a.label, onClick: a.onSelect, children: (0, jsx_runtime_1.jsx)(a.icon, { size: 15, strokeWidth: 1.75 }) }, i)))] }), (0, jsx_runtime_1.jsx)(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-section", children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.lists, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-addbtn", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": DETAILS_MIN_W, "aria-valuemax": DETAILS_MAX_W, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
782
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-topbar", "data-peek": onClose ? "" : undefined, children: [onClose ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-peekbar", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-peek-iconbtn", "aria-label": closeLabel, onClick: onClose, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, strokeWidth: 1.75 }) }), (onPrevious || onNext) && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-peek-pager", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": previousRecordLabel, disabled: !onPrevious, onClick: onPrevious, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronUp, { size: 16, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": nextRecordLabel, disabled: !onNext, onClick: onNext, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: 16, strokeWidth: 1.75 }) })] })), counterLabel != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-peek-counter", children: counterLabel }))] })) : ((0, jsx_runtime_1.jsxs)("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-name", children: name })] })), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-actions", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", onClick: onShare, children: t.share }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: () => setCommentsOpen(true), "aria-label": t.comments, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MessageSquare, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Merge, { size: 15, strokeWidth: 1.75 }), children: t.mergeRecords }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Settings, { size: 15, strokeWidth: 1.75 }), children: t.configurePage }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyPageUrl }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyRecordId }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, children: t.deleteRecord })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-body", children: [(0, jsx_runtime_1.jsxs)("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? ((0, jsx_runtime_1.jsx)("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-entity-name", children: name }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-quickactions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 16, strokeWidth: 1.75 }), t.addToList] }), quickActions.map((a, i) => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": a.label, onClick: a.onSelect, children: (0, jsx_runtime_1.jsx)(a.icon, { size: 15, strokeWidth: 1.75 }) }, i)))] }), (0, jsx_runtime_1.jsx)(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-section", children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.lists, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-addbtn", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": DETAILS_MIN_W, "aria-valuemax": DETAILS_MAX_W, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
732
783
  if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
733
784
  return;
734
785
  // The pointer drag learned its direction in #212; this did not.
@@ -26,6 +26,21 @@ const config = (0, core_1.defineRecipe)({
26
26
  & * { box-sizing: border-box; }
27
27
 
28
28
  & .crisp-recordpage-topbar { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: 44px; flex-shrink: 0; padding: 0 ${tokens_1.contract.space["3"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; }
29
+ /* #624: expanding a peek to its full record keeps a hybrid header
30
+ (Close/Previous/Next/counter) instead of reverting to the plain
31
+ breadcrumb above -- measured live on a real expanded record: 49px
32
+ tall (not 44px), 8px/10px padding, same borderSubtle hairline. Same
33
+ tokens as product/recordpanel's own already-shipped toolbar, not
34
+ new values -- this is the same row, reused in a second component. */
35
+ & .crisp-recordpage-topbar[data-peek] { height: 49px; padding: ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["2.5"]}; }
36
+ & .crisp-recordpage-peekbar { display: flex; align-items: center; }
37
+ & .crisp-recordpage-peek-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: ${tokens_1.contract.radius.md}; background: transparent; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s; }
38
+ & .crisp-recordpage-peek-iconbtn:hover { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
39
+ & .crisp-recordpage-peek-pager { display: flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; margin-inline-start: ${tokens_1.contract.space["3"]}; }
40
+ & .crisp-recordpage-peek-navbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgSurface}; box-shadow: ${tokens_1.elevation.field}; color: ${tokens_1.contract.fgPrimary}; font-family: inherit; cursor: pointer; }
41
+ & .crisp-recordpage-peek-navbtn:hover:not(:disabled) { background: ${tokens_1.contract.bgRaised}; }
42
+ & .crisp-recordpage-peek-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
43
+ & .crisp-recordpage-peek-counter { margin-inline-start: ${tokens_1.contract.space["3"]}; font-size: 12px; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
29
44
  & .crisp-recordpage-crumbs { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; min-width: 0; }
30
45
  & .crisp-recordpage-crumb { display: inline-flex; align-items: center; height: 26px; padding: 0 ${tokens_1.contract.space["1.5"]}; margin: 0 calc(-1 * ${tokens_1.contract.space["1.5"]}) 0 0; border: none; border-radius: ${tokens_1.contract.radius.md}; background: none; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; cursor: pointer; }
31
46
  & .crisp-recordpage-crumb:hover { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
@@ -45,6 +60,17 @@ const config = (0, core_1.defineRecipe)({
45
60
  & .crisp-recordpage-resizer::before { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -4px; width: 9px; }
46
61
  & .crisp-recordpage-resizer::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -1px; width: 2px; background: transparent; transition: background-color .12s ease; }
47
62
  & .crisp-recordpage-resizer:hover::after, & .crisp-recordpage-resizer[data-active]::after { background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 55%, transparent); }
63
+ /* #930: RecordDetails' own standalone resize handle -- same visible
64
+ hit-slop/line-highlight shape as .crisp-recordpage-resizer just above,
65
+ just absolutely positioned at the trailing edge instead of a 0-width
66
+ flex sibling, since RecordDetails always returns a single root (the
67
+ same internal-strip placement RecordPanel's own resize handle uses,
68
+ mirrored to the opposite edge -- this column is leading, RecordPanel
69
+ is trailing). */
70
+ & .crisp-recordpage-section[data-resizable] { position: relative; }
71
+ & .crisp-recordpage-detailresize { position: absolute; top: 0; bottom: 0; inset-inline-end: -4px; width: 9px; cursor: col-resize; z-index: 5; touch-action: none; }
72
+ & .crisp-recordpage-detailresize::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-end: 3px; width: 2px; background: transparent; transition: background-color .12s ease; }
73
+ & .crisp-recordpage-detailresize:hover::after, & .crisp-recordpage-detailresize[data-active]::after { background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 55%, transparent); }
48
74
  & .crisp-recordpage-entity { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; padding: ${tokens_1.contract.space["1"]} ${tokens_1.contract.space["1"]} ${tokens_1.contract.space["2"]}; }
49
75
  & .crisp-recordpage-entity-logo { width: 32px; height: 32px; border-radius: 30%; flex-shrink: 0; object-fit: cover; background: ${tokens_1.contract.bgRaised}; }
50
76
  & .crisp-recordpage-entity-name { flex: 0 1 auto; min-width: 0; font-size: ${tokens_1.contract.text.xl}; font-weight: ${tokens_1.contract.weight.semibold}; letter-spacing: -0.3px; color: ${tokens_1.contract.fgPrimary}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -178,12 +204,23 @@ const config = (0, core_1.defineRecipe)({
178
204
  & .crisp-recordpage-btn--primary { box-shadow: none; background: ${tokens_1.contract.bgBrandSolid}; color: ${tokens_1.contract.fgOnBrand}; }
179
205
  & .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
180
206
 
181
- & .crisp-recordpage-note { display: flex; gap: ${tokens_1.contract.space["2.5"]}; padding: ${tokens_1.contract.space["2.5"]} 0; border-top: 1px solid ${tokens_1.contract.borderSubtle}; }
182
- & .crisp-recordpage-note-body { flex: 1; min-width: 0; }
183
- & .crisp-recordpage-note-head { display: flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; margin-bottom: ${tokens_1.contract.space["0.5"]}; }
184
- & .crisp-recordpage-note-author { font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; }
185
- & .crisp-recordpage-note-time { font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.fgMuted}; }
186
- & .crisp-recordpage-note-text { font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgPrimary}; white-space: pre-wrap; word-break: break-word; }
207
+ /* #871: one bordered card per note (border-radius.xl, borderSubtle),
208
+ not a flush divider-list row -- measured live on a real populated
209
+ note: the whole row is flex space-between, avatar+title+body on the
210
+ left (its own flex, gap.2), the relative time alone on the right.
211
+ The real DOM nests an inner clickable button (padding 6/8, radius.md)
212
+ inside an outer card (padding 4px, radius.xl) -- collapsed into this
213
+ one non-interactive div's own combined padding (2.5/3) since crisp's
214
+ NotesSection has no click-to-open behaviour to justify the extra
215
+ layer. The 6px gap between stacked cards is NOT independently
216
+ measured (this workspace only ever had one real note to check) --
217
+ a judgement call off the existing space scale, not a live value. */
218
+ & .crisp-recordpage-note { display: flex; align-items: center; justify-content: space-between; gap: ${tokens_1.contract.space["3"]}; padding: ${tokens_1.contract.space["2.5"]} ${tokens_1.contract.space["3"]}; border: 1px solid ${tokens_1.contract.borderSubtle}; border-radius: ${tokens_1.contract.radius.xl}; }
219
+ & .crisp-recordpage-note + .crisp-recordpage-note { margin-top: ${tokens_1.contract.space["1.5"]}; }
220
+ & .crisp-recordpage-note-left { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; min-width: 0; overflow: hidden; }
221
+ & .crisp-recordpage-note-title { flex-shrink: 0; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; }
222
+ & .crisp-recordpage-note-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); }
223
+ & .crisp-recordpage-note-time { flex-shrink: 0; font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 50%, transparent); }
187
224
 
188
225
  & .crisp-recordpage-task { display: flex; align-items: flex-start; gap: ${tokens_1.contract.space["2"]}; padding: ${tokens_1.contract.space["1.5"]} 0; border-top: 1px solid ${tokens_1.contract.borderSubtle}; }
189
226
  & .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgPrimary}; padding-top: 1px; }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkspaceGeneralSettings = void 0;
4
+ var workspacegeneralsettings_1 = require("./workspacegeneralsettings");
5
+ Object.defineProperty(exports, "WorkspaceGeneralSettings", { enumerable: true, get: function () { return workspacegeneralsettings_1.WorkspaceGeneralSettings; } });
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkspaceGeneralSettings = WorkspaceGeneralSettings;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const lucide_react_1 = require("lucide-react");
6
+ const avatar_1 = require("../../components/avatar");
7
+ const button_1 = require("../../components/button");
8
+ const formfield_1 = require("../../components/formfield");
9
+ const input_1 = require("../../components/input");
10
+ const table_1 = require("../../components/table");
11
+ const textlink_1 = require("../../components/textlink");
12
+ const workspacegeneralsettings_recipe_1 = require("./workspacegeneralsettings.recipe");
13
+ /**
14
+ * Attio's Settings → Workspace → General page (crisp#638, P-128), measured
15
+ * live: a logo + Name/Slug section, an "Export workspace data" table, and a
16
+ * "Danger zone" delete-workspace row.
17
+ */
18
+ function WorkspaceGeneralSettings({ logoSrc, workspaceInitial, onUploadLogo, name, onNameChange, slug, onCopySlug, exports: exportRows, onStartExport, onDeleteWorkspace, className, title = "General", description = "Change the settings for your current workspace.", learnMoreLabel = "Learn more", learnMoreHref = "#", logoHelpText = "We only support PNGs, JPEGs and GIFs under 10MB", uploadLogoLabel = "Upload logo", nameFieldLabel = "Name", slugFieldLabel = "Slug", copySlugLabel = "Copy slug", exportSectionTitle = "Export workspace data", exportSectionDescription = "Exports are in CSV format and can be downloaded within 7 days", startExportLabel = "Start new export", typeColumnLabel = "Type", dateColumnLabel = "Date", noExportsLabel = "No exports yet", dangerSectionTitle = "Danger zone", deleteWorkspaceTitle = "Delete workspace", deleteWorkspaceWarning = "Once deleted, your workspace cannot be recovered", deleteWorkspaceLabel = "Delete workspace", ...rest }) {
19
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, workspacegeneralsettings_recipe_1.workspacegeneralsettingsRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-page", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-heading", children: [(0, jsx_runtime_1.jsx)("h1", { className: "crisp-workspacegeneralsettings-title", children: title }), (0, jsx_runtime_1.jsxs)("p", { className: "crisp-workspacegeneralsettings-desc", children: [description, " ", (0, jsx_runtime_1.jsx)(textlink_1.TextLink, { href: learnMoreHref, children: learnMoreLabel })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-section", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-logorow", children: [logoSrc ? ((0, jsx_runtime_1.jsx)("img", { src: logoSrc, alt: "", width: 48, height: 48, style: { borderRadius: "30%", objectFit: "cover" } })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "xl", shape: "rounded", tone: "brand", children: workspaceInitial })), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-logotext", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-workspacegeneralsettings-logohelp", children: logoHelpText }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", onClick: onUploadLogo, children: uploadLogoLabel })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-fieldrow", children: [(0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: nameFieldLabel, children: (0, jsx_runtime_1.jsx)(input_1.Input, { value: name, onChange: (e) => onNameChange(e.target.value) }) }), (0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: slugFieldLabel, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-slugfield", children: [(0, jsx_runtime_1.jsx)(input_1.Input, { value: slug, readOnly: true }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-workspacegeneralsettings-copybtn", "aria-label": copySlugLabel, onClick: onCopySlug, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 16 }) })] }) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-section", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-sectionhead", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-sectionhead-text", children: [(0, jsx_runtime_1.jsx)("h2", { className: "crisp-workspacegeneralsettings-sectiontitle", children: exportSectionTitle }), (0, jsx_runtime_1.jsx)("p", { className: "crisp-workspacegeneralsettings-desc", children: exportSectionDescription })] }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", onClick: onStartExport, children: startExportLabel })] }), (0, jsx_runtime_1.jsxs)(table_1.Table, { children: [(0, jsx_runtime_1.jsx)(table_1.TableHeader, { children: (0, jsx_runtime_1.jsxs)(table_1.TableRow, { children: [(0, jsx_runtime_1.jsx)(table_1.TableHead, { children: typeColumnLabel }), (0, jsx_runtime_1.jsx)(table_1.TableHead, { children: dateColumnLabel })] }) }), (0, jsx_runtime_1.jsx)(table_1.TableBody, { children: exportRows.length === 0 ? ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { colSpan: 2, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-workspacegeneralsettings-emptyrow", children: noExportsLabel }) }) })) : (exportRows.map((row) => ((0, jsx_runtime_1.jsxs)(table_1.TableRow, { children: [(0, jsx_runtime_1.jsx)(table_1.TableCell, { children: row.type }), (0, jsx_runtime_1.jsx)(table_1.TableCell, { children: row.date })] }, row.id)))) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-section", children: [(0, jsx_runtime_1.jsx)("h2", { className: "crisp-workspacegeneralsettings-sectiontitle", children: dangerSectionTitle }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-dangerrow", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-workspacegeneralsettings-dangertext", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-workspacegeneralsettings-rowtitle", children: deleteWorkspaceTitle }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-workspacegeneralsettings-dangerwarning", children: deleteWorkspaceWarning })] }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "danger", size: "sm", onClick: onDeleteWorkspace, children: deleteWorkspaceLabel })] })] })] }) }));
20
+ }
21
+ WorkspaceGeneralSettings.displayName = "WorkspaceGeneralSettings";
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.workspacegeneralsettingsRecipe = void 0;
4
+ const core_1 = require("../../core");
5
+ const tokens_1 = require("../../tokens");
6
+ // Measured live against app.attio.com/<workspace>/settings/workspace
7
+ // (crisp#638, P-128). Logo tile + Name/Slug fields side by side, an
8
+ // "Export workspace data" section (header row + Type/Date table + empty
9
+ // state), and a "Danger zone" section (a red-tinted bordered row).
10
+ const config = (0, core_1.defineRecipe)({
11
+ name: "workspacegeneralsettings",
12
+ base: `& .crisp-workspacegeneralsettings-page { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["6"]}; font-family: ${tokens_1.contract.font.sans}; }
13
+ & .crisp-workspacegeneralsettings-heading { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["0.5"]}; }
14
+ & .crisp-workspacegeneralsettings-title { font-size: 24px; line-height: 28px; font-weight: ${tokens_1.contract.weight.semibold}; letter-spacing: -0.48px; color: ${tokens_1.contract.fgPrimary}; margin: 0; }
15
+ & .crisp-workspacegeneralsettings-desc { font-size: ${tokens_1.contract.text.lg}; line-height: 20px; color: ${tokens_1.contract.fgMuted}; margin: 0; }
16
+
17
+ & .crisp-workspacegeneralsettings-section { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["4"]}; }
18
+ & .crisp-workspacegeneralsettings-sectiontitle { font-size: 16px; line-height: 20px; font-weight: ${tokens_1.contract.weight.semibold}; color: ${tokens_1.contract.fgPrimary}; margin: 0; }
19
+ & .crisp-workspacegeneralsettings-sectionhead { display: flex; align-items: flex-start; justify-content: space-between; gap: ${tokens_1.contract.space["4"]}; }
20
+ & .crisp-workspacegeneralsettings-sectionhead-text { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["1"]}; }
21
+
22
+ /* Logo row: a tile + helper text + "Upload logo" button. */
23
+ & .crisp-workspacegeneralsettings-logorow { display: flex; align-items: center; gap: ${tokens_1.contract.space["4"]}; }
24
+ & .crisp-workspacegeneralsettings-logohelp { font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.fgMuted}; }
25
+ & .crisp-workspacegeneralsettings-logotext { display: flex; flex-direction: column; align-items: flex-start; gap: ${tokens_1.contract.space["1"]}; }
26
+
27
+ /* Name + Slug sit side by side, each ~half the content width. */
28
+ & .crisp-workspacegeneralsettings-fieldrow { display: flex; gap: ${tokens_1.contract.space["4"]}; }
29
+ & .crisp-workspacegeneralsettings-fieldrow > * { flex: 1; min-width: 0; }
30
+ & .crisp-workspacegeneralsettings-slugfield { display: flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; }
31
+ & .crisp-workspacegeneralsettings-slugfield > :first-child { flex: 1; min-width: 0; }
32
+ & .crisp-workspacegeneralsettings-copybtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 32px; height: 32px; border: none; border-radius: ${tokens_1.contract.radius.md}; background: none; font-family: inherit; color: ${tokens_1.contract.fgMuted}; cursor: pointer; }
33
+ & .crisp-workspacegeneralsettings-copybtn:hover { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
34
+
35
+ /* Export-data empty state -- measured: 14/500/63%-opacity, centred in a
36
+ 40px row, no border of its own (the Table wrapper already draws it). */
37
+ & .crisp-workspacegeneralsettings-emptyrow { padding: ${tokens_1.contract.space["2.5"]} ${tokens_1.contract.space["4"]}; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); text-align: center; }
38
+
39
+ /* Danger zone: a red-tinted bordered row, distinct from every other
40
+ card on the page. */
41
+ & .crisp-workspacegeneralsettings-dangerrow { display: flex; align-items: center; justify-content: space-between; gap: ${tokens_1.contract.space["6"]}; padding: ${tokens_1.contract.space["3"]} ${tokens_1.contract.space["4"]}; border: 1px solid color-mix(in srgb, ${tokens_1.contract.bgDanger} 30%, transparent); border-radius: 12px; background: color-mix(in srgb, ${tokens_1.contract.bgDanger} 6%, transparent); }
42
+ & .crisp-workspacegeneralsettings-dangertext { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["1"]}; }
43
+ & .crisp-workspacegeneralsettings-rowtitle { font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; }
44
+ & .crisp-workspacegeneralsettings-dangerwarning { font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); }
45
+ `,
46
+ variants: {},
47
+ defaultVariants: {},
48
+ compoundVariants: [],
49
+ });
50
+ exports.workspacegeneralsettingsRecipe = (0, core_1.resolveRecipe)(config);
51
+ exports.default = config;
@@ -19,4 +19,5 @@ exports.contract = Object.assign(colors, {
19
19
  font: mapScale("font", scale_1.font),
20
20
  text: mapScale("text", scale_1.text),
21
21
  weight: mapScale("weight", scale_1.weight),
22
+ tracking: mapScale("tracking", scale_1.tracking),
22
23
  });
@@ -15,9 +15,11 @@ const contract_1 = require("./contract");
15
15
  */
16
16
  exports.elevation = {
17
17
  /**
18
- * Tier 1 — anything floating and *attached*: menus, popovers, dropdowns,
18
+ * Tier 1 — anything floating and *attached*: popovers, dropdowns,
19
19
  * hover-preview cards, the floating sidebar, the record peek, drawers, the
20
20
  * bulk bar. Pairs with a 10–12px radius and, for panels, an 8px gutter.
21
+ * NOT `Menu` (your-moon/crisp#934) -- that moved to
22
+ * `selectFloating`, seed-design's own flat shadow, below.
21
23
  */
22
24
  floating: `inset 0 0 0 1px ${contract_1.contract.ringPopover}, 0 0 0 1px ${contract_1.contract.shadowPopover1}, 0 4px 8px -4px ${contract_1.contract.shadowPopover2}, 0 4px 12px -2px ${contract_1.contract.shadowPopover3}`,
23
25
  /**
@@ -60,10 +62,11 @@ exports.elevation = {
60
62
  */
61
63
  fieldNoRing: `0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04)`,
62
64
  /**
63
- * Select's own listbox shadow (crisp/#867) — seed-design's `$shadow.s3`, a
64
- * single layer, NOT `floating` above. This component follows seed, not
65
- * Attio, for the same reason `DatePicker` follows seed's calendar
66
- * structure: an explicit owner request to match seed-design exactly.
65
+ * Seed-design's own `$shadow.s3`, a single flat layer, NOT `floating`
66
+ * above. Originally Select's own listbox shadow (crisp/#867); also `Menu`'s
67
+ * (your-moon/crisp#934) -- the raw seed dump confirms Menu
68
+ * is the one component seed's own spec gives a shadow to at all, and it's
69
+ * this exact value. Despite the name, not exclusive to Select.
67
70
  */
68
71
  selectFloating: `0 4px 16px ${contract_1.contract.shadowSelect}`,
69
72
  };
@@ -66,14 +66,34 @@ exports.palette = {
66
66
  ringBrandSoft: { light: "#d6e5ff", dark: "#212224" },
67
67
  tileActive: { light: "#ffffff", dark: "#101112" },
68
68
  tileActiveShadow: { light: "rgba(0,0,0,0.04)", dark: "rgba(0,0,0,0)" },
69
- // Dialog's page-dimming backdrop (crisp#677) — measured live behind the
70
- // Create-attribute FormDialog, both themes: `rgba(0,0,0,.25)` light,
71
- // `rgba(0,0,0,.55)` dark. `Dialog`'s own backdrop had shipped fully
72
- // `transparent` (AUTHORING's Lesson 14 measured the command palette has
73
- // NO dark backdrop and that got generalized to every `Dialog`, which this
74
- // corrects — the palette is a different surface from a plain content
75
- // dialog and really does dim the page).
76
- bgScrim: { light: "rgba(0,0,0,0.25)", dark: "rgba(0,0,0,0.55)" },
69
+ // Dialog's page-dimming backdrop (crisp#677, Attio's own measured value:
70
+ // `rgba(0,0,0,.25)` light, `rgba(0,0,0,.55)` dark — `Dialog`'s own backdrop
71
+ // had shipped fully `transparent`, AUTHORING's Lesson 14 measured the
72
+ // command palette has NO dark backdrop and that got generalized to every
73
+ // `Dialog`, which this corrected).
74
+ //
75
+ // your-moon/crisp#934: light theme moved to seed-design's own scrim
76
+ // (`#00000074` ≈ 45% black, `bg.overlay`) — the owner's flat-dialog
77
+ // directive names "seed's own scrim" specifically, now that elevation is
78
+ // conveyed by the scrim alone (no more frosted-glass shadow/blur). Dark
79
+ // theme's `.55` is UNCHANGED: seed's own token system has no separate
80
+ // dark-theme scrim value to adopt, and `.55` wasn't found wrong -- only
81
+ // light had a sourced replacement value to move to.
82
+ bgScrim: { light: "rgba(0,0,0,0.45)", dark: "rgba(0,0,0,0.55)" },
83
+ // seed-design's `$color.bg.overlay` (BottomSheet/MenuSheet's own backdrop,
84
+ // crisp#911) — kept as its OWN token rather than folded into `bgScrim`
85
+ // above, even though #934 (landed after this one was added) moved
86
+ // `bgScrim`'s LIGHT value to the same `#00000074`: `bgOverlayStrong` is
87
+ // seed's literally theme-CONSTANT value (`color.yaml` gives the same hex
88
+ // for both `theme-light` and `theme-dark`), while `bgScrim` still differs
89
+ // by theme (`.45`/`.55`) -- the two tokens express different design
90
+ // decisions that currently coincide in one theme only.
91
+ bgOverlayStrong: { light: "#00000074", dark: "#00000074" },
92
+ // seed-design's `$color.stroke.neutral-muted` (MenuSheet group divider,
93
+ // crisp#911) -- a faint alpha hairline between grouped rows, theme-flipping
94
+ // (`color.yaml`: `static-black-alpha-300` light / `static-white-alpha-100`
95
+ // dark).
96
+ strokeGroupDivider: { light: "#00000010", dark: "#ffffff17" },
77
97
  // The discard-confirm's description line (crisp#677, `Dialog`'s new
78
98
  // `variant="alert"`) — measured live, both themes, over `popoverBg`: a
79
99
  // theme-relative alpha rather than a flat gray, so it stays legible if
@@ -9,7 +9,7 @@
9
9
  // Recipes MUST reference these through the contract (`c.space[...]`), never a
10
10
  // literal px — this file is the only exception.
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.weight = exports.text = exports.font = exports.size = exports.radius = exports.space = void 0;
12
+ exports.tracking = exports.weight = exports.text = exports.font = exports.size = exports.radius = exports.space = void 0;
13
13
  // Spacing: gaps, padding, margins. Keys follow the Tailwind step convention
14
14
  // (0.5 = half a 4px step = 2px) so the numbers stay legible to consumers.
15
15
  exports.space = {
@@ -30,13 +30,22 @@ exports.space = {
30
30
  // (inputs, rows, tiles); full is the pill.
31
31
  exports.radius = {
32
32
  xs: "4px", // seed-design's $radius.r1 — small badge / checkbox box corner
33
- chip: "7px", // tag / chip corner — the single most used radius
33
+ // your-moon/crisp#919: was `7px` (Attio's own most-measured
34
+ // radius, 70 occurrences) -- now a pill, matching seed-design's own
35
+ // `chip.yaml` (`$radius.full` at every size, no size-based radius step at
36
+ // all, unlike Button/Badge). Same numeric value as `full` below, kept as
37
+ // its own named token because every one of this token's ~9 consumers
38
+ // (Chip, DataTable/RecordPage/RecordsTable/MultiSelect's own tag/mention
39
+ // rules, TableToolbar/DatePickerField's date-preset chips) reasons about
40
+ // "the chip corner" as its own concept, not literally "a generic pill".
41
+ chip: "999px",
34
42
  sm: "6px", // chips, badges
35
43
  md: "8px", // inputs, search box
36
44
  lg: "9px", // rows, tiles, list items
37
45
  xl: "12px", // overlay items (command rows)
38
46
  "2xl": "16px", // overlay panels (command surface)
39
47
  "3xl": "20px", // seed-design's $radius.r5 — Select's listbox corner (crisp/#867)
48
+ "4xl": "24px", // seed-design's $radius.r6 — BottomSheet's top corner radius (crisp/#911)
40
49
  full: "999px", // pills
41
50
  };
42
51
  // Component sizing — recurring measured dimensions that are neither spacing nor
@@ -88,3 +97,36 @@ exports.weight = {
88
97
  semibold: "600",
89
98
  bold: "700", // seed-design's font-weight-bold, for the seed-styled calendar
90
99
  };
100
+ // Size-based letter-spacing ramp for DISPLAY/HEADING type (18px and up —
101
+ // `text.*` above tops out at 16px and stays untracked). crisp's five
102
+ // already-shipped headings tighten as they grow — authshell 20px/-0.4px (2%
103
+ // of size), objectsettings 24px/-0.48px (2%), richtext 32px/-0.6px (1.875%)
104
+ // — all Attio-measured (Inter) and left exactly as they are; this token
105
+ // never touches them, or the button (-0.07px) / table-cell (-0.14px)
106
+ // tracking, which are body sizes, not display type. (statcard's 28px/-0.4px,
107
+ // 1.43%, is a looser outlier — it sets tabular numerals, not prose — and is
108
+ // also left alone.)
109
+ //
110
+ // crisp#909: the study at docs/study/korean/ observed that Naver and Kakao
111
+ // both tighten tracking as type gets bigger, and crisp's OWN large type
112
+ // (EmptyState's 20px title, Dialog's 18px alert title, and any future
113
+ // heading) had no tracking at all — it read as default, not set. The study
114
+ // supplied the PATTERN only, never a number: Naver/Kakao's own values are
115
+ // Korean-specific and were not copied. The numbers below come from rendering
116
+ // real Mongolian Cyrillic ("Эрсдэлийн бүртгэл", "Хяналтын самбар") in VK Sans
117
+ // Display — crisp's own font override, see `font.sans` above — at every
118
+ // display size crisp uses today (18/20/24/26/28/32px), screenshotting six
119
+ // candidates per size (0%, 1%, 1.5%, 2%, 2.5%, 3% of the font-size) and
120
+ // checking for glyph collision. None collided even at 3%, so the pre-existing
121
+ // 2%-of-size rule the five Attio headings already happen to follow was kept
122
+ // as the ramp, rounded to the nearest 0.01px — it reads as deliberately set
123
+ // with headroom to spare, and keeps new display type visually consistent
124
+ // with the Attio-measured headings already shipped.
125
+ exports.tracking = {
126
+ "18": "-0.36px",
127
+ "20": "-0.4px",
128
+ "24": "-0.48px",
129
+ "26": "-0.52px",
130
+ "28": "-0.56px",
131
+ "32": "-0.64px",
132
+ };
@@ -41,6 +41,8 @@ exports.semantic = {
41
41
  bgTileActive: "tileActive",
42
42
  shadowTileActive: "tileActiveShadow",
43
43
  bgScrim: "bgScrim",
44
+ bgOverlayStrong: "bgOverlayStrong",
45
+ strokeGroupDivider: "strokeGroupDivider",
44
46
  fgAlertDescription: "fgAlertDescription",
45
47
  bgOverlayFrost: "overlayFrost",
46
48
  shadowOverlay1: "shadowOverlay1",
@@ -1,30 +1,38 @@
1
1
  .crisp-columncalc {
2
- display: flex; align-items: stretch; width: 100%; height: 100%; font-family: var(--crisp-font-sans); /* Menu wraps its trigger in a flex div that sizes to content, which would shrink the button back to its text. Make the wrapper fill too, so the whole cell stays clickable. */
2
+ display: flex; align-items: stretch; width: 100%; height: 100%; font-family: var(--crisp-font-sans); /* Menu wraps its trigger in a flex div that sizes to content, which would
3
+ shrink the button back to its text. Make the wrapper fill too, so the
4
+ whole cell stays clickable. */
3
5
  & .crisp-menu { width: 100%; height: 100%; }
4
- /* No radius: the trigger fills the cell, so rounding its own corners leaves the cell's background showing through at the four corners as
6
+ /* No radius: the trigger fills the cell, so rounding its own corners
7
+ leaves the cell's background showing through at the four corners as
5
8
  soon as the hover tint paints. Everything else that fills a grid cell
6
9
  agrees -- recordstable-sum-btn, the same summary trigger, declares no
7
10
  radius, and datatable-grip and the embedded pickers zero theirs. An 8px
8
11
  radius survived the chip-to-full-cell conversion and shipped rounded
9
- until #282, so the spec check now guards it. */
10
- /* The hit target is the WHOLE summary cell -- measured 246x36 with
12
+ until #282, so the spec check now guards it. */ /* The hit target is the WHOLE summary cell -- measured 246x36 with
11
13
  padding 0 12px, not a content-sized chip. Clicking anywhere in the cell
12
14
  opens the aggregation menu.
13
15
  No `min-height` (crisp#834, found reproducing the footer-row-38px-
14
- not-36px bug in an isolated browser test, not table.recipe.ts's own border/box-sizing, the hypothesis the issue itself flagged as unconfirmed): the table cell's OWN `height: tableRow` (36px,
16
+ not-36px bug in an isolated browser test, not table.recipe.ts's own
17
+ border/box-sizing, the hypothesis the issue itself flagged as
18
+ unconfirmed): the table cell's OWN `height: tableRow` (36px,
15
19
  border-box) leaves 34px of content box once its 2px of top+bottom
16
20
  border are counted, and `min-height: tableRow` here demanded 36px
17
21
  of CONTENT height regardless -- overflowing the cell by 2px, which
18
22
  auto-height table layout resolves by growing the whole row to fit,
19
23
  landing at exactly the reported 38. `height: 100%` alone already
20
- fills whatever the cell's real content box is (the doc comment above this rule's own stated intent), with nothing left demanding more. */
24
+ fills whatever the cell's real content box is (the doc comment above
25
+ this rule's own stated intent), with nothing left demanding more. */
21
26
  & .crisp-columncalc-trigger { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s, color .12s; }
22
27
  & .crisp-columncalc-trigger:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
23
28
  & .crisp-columncalc-trigger svg { flex-shrink: 0; opacity: 0.7; }
24
29
  & .crisp-columncalc-trigger[data-align="right"] { justify-content: flex-end; }
25
30
  & .crisp-columncalc-placeholder { overflow: hidden; text-overflow: ellipsis; }
26
31
  & .crisp-columncalc-value { color: var(--crisp-fg-primary); font-variant-numeric: tabular-nums; }
27
- /* crisp#837: re-measured live against Attio's real footer calc label -- 14px (was 13px, `c.text.md`, fixed on the shared trigger rule above) and rgba(0,0,0,.63), the same color-mix(fgPrimary, 63%) figure this file's own -op label already should have used instead of the flatter
32
+ /* crisp#837: re-measured live against Attio's real footer calc label --
33
+ 14px (was 13px, `c.text.md`, fixed on the shared trigger rule above)
34
+ and rgba(0,0,0,.63), the same color-mix(fgPrimary, 63%) figure this
35
+ file's own -op label already should have used instead of the flatter
28
36
  `fgMuted` token (a close but not measured match). */
29
37
  & .crisp-columncalc-op { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
30
38
  & .crisp-columncalc-section { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
@@ -1,31 +1,39 @@
1
1
  @layer crisp-components {
2
2
  .crisp-columncalc {
3
- display: flex; align-items: stretch; width: 100%; height: 100%; font-family: var(--crisp-font-sans); /* Menu wraps its trigger in a flex div that sizes to content, which would shrink the button back to its text. Make the wrapper fill too, so the whole cell stays clickable. */
3
+ display: flex; align-items: stretch; width: 100%; height: 100%; font-family: var(--crisp-font-sans); /* Menu wraps its trigger in a flex div that sizes to content, which would
4
+ shrink the button back to its text. Make the wrapper fill too, so the
5
+ whole cell stays clickable. */
4
6
  & .crisp-menu { width: 100%; height: 100%; }
5
- /* No radius: the trigger fills the cell, so rounding its own corners leaves the cell's background showing through at the four corners as
7
+ /* No radius: the trigger fills the cell, so rounding its own corners
8
+ leaves the cell's background showing through at the four corners as
6
9
  soon as the hover tint paints. Everything else that fills a grid cell
7
10
  agrees -- recordstable-sum-btn, the same summary trigger, declares no
8
11
  radius, and datatable-grip and the embedded pickers zero theirs. An 8px
9
12
  radius survived the chip-to-full-cell conversion and shipped rounded
10
- until #282, so the spec check now guards it. */
11
- /* The hit target is the WHOLE summary cell -- measured 246x36 with
13
+ until #282, so the spec check now guards it. */ /* The hit target is the WHOLE summary cell -- measured 246x36 with
12
14
  padding 0 12px, not a content-sized chip. Clicking anywhere in the cell
13
15
  opens the aggregation menu.
14
16
  No `min-height` (crisp#834, found reproducing the footer-row-38px-
15
- not-36px bug in an isolated browser test, not table.recipe.ts's own border/box-sizing, the hypothesis the issue itself flagged as unconfirmed): the table cell's OWN `height: tableRow` (36px,
17
+ not-36px bug in an isolated browser test, not table.recipe.ts's own
18
+ border/box-sizing, the hypothesis the issue itself flagged as
19
+ unconfirmed): the table cell's OWN `height: tableRow` (36px,
16
20
  border-box) leaves 34px of content box once its 2px of top+bottom
17
21
  border are counted, and `min-height: tableRow` here demanded 36px
18
22
  of CONTENT height regardless -- overflowing the cell by 2px, which
19
23
  auto-height table layout resolves by growing the whole row to fit,
20
24
  landing at exactly the reported 38. `height: 100%` alone already
21
- fills whatever the cell's real content box is (the doc comment above this rule's own stated intent), with nothing left demanding more. */
25
+ fills whatever the cell's real content box is (the doc comment above
26
+ this rule's own stated intent), with nothing left demanding more. */
22
27
  & .crisp-columncalc-trigger { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s, color .12s; }
23
28
  & .crisp-columncalc-trigger:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
24
29
  & .crisp-columncalc-trigger svg { flex-shrink: 0; opacity: 0.7; }
25
30
  & .crisp-columncalc-trigger[data-align="right"] { justify-content: flex-end; }
26
31
  & .crisp-columncalc-placeholder { overflow: hidden; text-overflow: ellipsis; }
27
32
  & .crisp-columncalc-value { color: var(--crisp-fg-primary); font-variant-numeric: tabular-nums; }
28
- /* crisp#837: re-measured live against Attio's real footer calc label -- 14px (was 13px, `c.text.md`, fixed on the shared trigger rule above) and rgba(0,0,0,.63), the same color-mix(fgPrimary, 63%) figure this file's own -op label already should have used instead of the flatter
33
+ /* crisp#837: re-measured live against Attio's real footer calc label --
34
+ 14px (was 13px, `c.text.md`, fixed on the shared trigger rule above)
35
+ and rgba(0,0,0,.63), the same color-mix(fgPrimary, 63%) figure this
36
+ file's own -op label already should have used instead of the flatter
29
37
  `fgMuted` token (a close but not measured match). */
30
38
  & .crisp-columncalc-op { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
31
39
  & .crisp-columncalc-section { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }