@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
@@ -1,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Activity as ActivityIcon, CalendarClock, ChevronDown, CircleDollarSign, Copy, DollarSign, FileText, Folder, Inbox, Landmark, LayoutGrid, Mail, Maximize2, Merge, MessageSquare, Minimize2, MoreHorizontal, Pencil, Phone, Plus, Search, Settings, Sparkles, SquareCheck, Star, Trash2, Users, UsersRound, Workflow, X, Zap, } from "lucide-react";
2
+ import { Activity as ActivityIcon, CalendarClock, ChevronDown, ChevronUp, CircleDollarSign, Copy, DollarSign, FileText, Folder, Inbox, Landmark, LayoutGrid, Mail, Maximize2, Merge, MessageSquare, Minimize2, MoreHorizontal, Pencil, Phone, Plus, Search, Settings, Sparkles, SquareCheck, Star, Trash2, Users, UsersRound, Workflow, X, Zap, } from "lucide-react";
3
3
  import * as React from "react";
4
4
  import { Avatar } from "../../components/avatar/index.js";
5
5
  import { Checkbox } from "../../components/checkbox/index.js";
@@ -273,7 +273,14 @@ function NotesSection({ notes, onAdd, labels, }) {
273
273
  return (_jsxs("section", { children: [_jsx(SectionHead, { title: labels.tabs.notes, count: notes.length, action: _jsxs("button", { type: "button", className: "crisp-recordpage-addbtn", onClick: () => setOpen(true), children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), labels.newNote] }) }), open && (_jsx(Composer, { labels: labels, multiline: true, placeholder: labels.notePlaceholder, submitLabel: labels.saveNote, onCancel: () => setOpen(false), onSubmit: (t) => {
274
274
  onAdd(t);
275
275
  setOpen(false);
276
- } })), notes.length === 0 && !open ? (_jsx("div", { className: "crisp-recordpage-emptystate", children: labels.noNotesYet })) : (notes.map((n) => (_jsxs("div", { className: "crisp-recordpage-note", children: [_jsx(Avatar, { size: "sm", shape: "circle", tone: "neutral", children: "Y" }), _jsxs("div", { className: "crisp-recordpage-note-body", children: [_jsxs("div", { className: "crisp-recordpage-note-head", children: [_jsx("span", { className: "crisp-recordpage-note-author", children: labels.you }), _jsx("span", { className: "crisp-recordpage-note-time", children: n.time })] }), _jsx("div", { className: "crisp-recordpage-note-text", children: n.text })] })] }, n.id))))] }));
276
+ } })), notes.length === 0 && !open ? (_jsx("div", { className: "crisp-recordpage-emptystate", children: labels.noNotesYet })) : (
277
+ // #871: one line, not a two-row card -- measured live: avatar,
278
+ // then the note's own title and body run together inline, then a
279
+ // relative time at the far end of the SAME row. No author name at
280
+ // all (Attio's real preview never shows one here, only the note's
281
+ // own content) -- `labels.you` stays defined for a consumer that
282
+ // still wants it elsewhere, just unused in this exact spot now.
283
+ notes.map((n) => (_jsxs("div", { className: "crisp-recordpage-note", children: [_jsxs("span", { className: "crisp-recordpage-note-left", children: [_jsx(Avatar, { size: "sm", shape: "circle", tone: "neutral", children: "Y" }), n.title && (_jsx("span", { className: "crisp-recordpage-note-title", children: n.title })), _jsx("span", { className: "crisp-recordpage-note-text", children: n.text })] }), _jsx("span", { className: "crisp-recordpage-note-time", children: n.time })] }, n.id))))] }));
277
284
  }
278
285
  function TasksSection({ tasks, onAdd, onToggle, labels, }) {
279
286
  const [open, setOpen] = React.useState(false);
@@ -306,13 +313,51 @@ function EmptyCard({ icon, children, }) {
306
313
  * `RecordPage`'s own root does: `<div className={recordpageRecipe()}>`
307
314
  * (also exported from this package).
308
315
  */
309
- export function RecordDetails({ record, columns, sections, onEdit, labels, className, ...rest }) {
316
+ export function RecordDetails({ record, columns, sections, onEdit, labels, className, resizable = false, width: controlledWidth, defaultWidth = DETAILS_DEFAULT_W, onWidthChange, minWidth = DETAILS_MIN_W, maxWidth = DETAILS_MAX_W, ...rest }) {
310
317
  const t = useRecordPageLabels(labels);
311
318
  const { editingKey, pendingKey, error, openEditing, closeEditing, commit } = useEditableField(onEdit);
312
319
  const [detailsOpen, setDetailsOpen] = React.useState(true);
313
320
  const [searchOpen, setSearchOpen] = React.useState(false);
314
321
  const [fieldQuery, setFieldQuery] = React.useState("");
315
322
  const [showAllValues, setShowAllValues] = React.useState(false);
323
+ // #930: controlled/uncontrolled width, same split as RecordPanel's own
324
+ // resizable width -- only read when `resizable`, same reasoning as
325
+ // RecordPanel's own (a non-resizing consumer has no business carrying
326
+ // width state at all).
327
+ const [internalWidth, setInternalWidth] = React.useState(defaultWidth);
328
+ const width = controlledWidth ?? internalWidth;
329
+ const setWidth = (next) => {
330
+ const clamped = Math.max(minWidth, Math.min(maxWidth, next));
331
+ if (controlledWidth === undefined)
332
+ setInternalWidth(clamped);
333
+ onWidthChange?.(clamped);
334
+ };
335
+ const [detailsResizing, setDetailsResizing] = React.useState(false);
336
+ const detailsDragRef = React.useRef(null);
337
+ const onDetailsResizeDown = (e) => {
338
+ // This column is a leading (left-docked) rail, the mirror image of
339
+ // RecordPanel's own trailing (right-docked) one -- a rightward pull
340
+ // grows it in LTR, the same direction RecordPage's own internal aside
341
+ // resize already uses (#212/#255).
342
+ detailsDragRef.current = {
343
+ x: e.clientX,
344
+ w: width,
345
+ dir: isRtlElement(e.currentTarget) ? -1 : 1,
346
+ };
347
+ setDetailsResizing(true);
348
+ e.target.setPointerCapture(e.pointerId);
349
+ };
350
+ const onDetailsResizeMove = (e) => {
351
+ const r = detailsDragRef.current;
352
+ if (!r)
353
+ return;
354
+ setWidth(r.w + r.dir * (e.clientX - r.x));
355
+ };
356
+ const onDetailsResizeUp = (e) => {
357
+ detailsDragRef.current = null;
358
+ setDetailsResizing(false);
359
+ e.target.releasePointerCapture(e.pointerId);
360
+ };
316
361
  const fields = columns.filter((col) => col.type !== "entity");
317
362
  const q = fieldQuery.trim().toLowerCase();
318
363
  const matches = (col) => !q || col.label.toLowerCase().includes(q);
@@ -349,7 +394,7 @@ export function RecordDetails({ record, columns, sections, onEdit, labels, class
349
394
  groups = [{ title: undefined, fields: shown }];
350
395
  }
351
396
  const totalShown = groups.reduce((n, g) => n + g.fields.length, 0);
352
- return (_jsxs("div", { className: cx("crisp-recordpage-section", className), ...rest, children: [_jsxs("div", { className: "crisp-recordpage-detailhead", children: [searchOpen ? (_jsx("input", {
397
+ return (_jsxs("div", { className: cx("crisp-recordpage-section", className), "data-resizable": resizable ? "" : undefined, style: resizable ? { width } : undefined, ...rest, children: [_jsxs("div", { className: "crisp-recordpage-detailhead", children: [searchOpen ? (_jsx("input", {
353
398
  // biome-ignore lint/a11y/noAutofocus: focusing the just-opened field search is expected
354
399
  autoFocus: true, type: "text", className: "crisp-recordpage-fieldsearch", placeholder: t.searchPlaceholder, "aria-label": t.searchFields, value: fieldQuery, onChange: (e) => setFieldQuery(e.target.value), onKeyDown: (e) => {
355
400
  if (e.key === "Escape") {
@@ -423,7 +468,13 @@ export function RecordDetails({ record, columns, sections, onEdit, labels, class
423
468
  setSearchOpen(next);
424
469
  if (!next)
425
470
  setFieldQuery("");
426
- }, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : (_jsx("div", { className: "crisp-recordpage-noresults", children: t.noFieldsMatch(fieldQuery) })))] }));
471
+ }, children: showAllValues ? t.viewLess : t.viewAllValues }))] })) : (_jsx("div", { className: "crisp-recordpage-noresults", children: t.noFieldsMatch(fieldQuery) }))), resizable && (_jsx("div", { className: "crisp-recordpage-detailresize", "data-active": detailsResizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(width), "aria-valuemin": minWidth, "aria-valuemax": maxWidth, tabIndex: 0, onPointerDown: onDetailsResizeDown, onPointerMove: onDetailsResizeMove, onPointerUp: onDetailsResizeUp, onKeyDown: (e) => {
472
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
473
+ return;
474
+ const rtl = isRtlElement(e.currentTarget);
475
+ const grow = e.key === (rtl ? "ArrowLeft" : "ArrowRight");
476
+ setWidth(grow ? width + 16 : width - 16);
477
+ } }))] }));
427
478
  }
428
479
  RecordDetails.displayName = "RecordDetails";
429
480
  /**
@@ -434,7 +485,7 @@ RecordDetails.displayName = "RecordDetails";
434
485
  * Emails sections (Notes & Tasks are live — add and check things off). Drop it
435
486
  * into the app's main content area.
436
487
  */
437
- export function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, quickActions: quickActionsProp, comments: commentsProp, onPostComment, currentUser, onShare, ...rest }) {
488
+ export function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, onClose, closeLabel = "Close", onPrevious, previousRecordLabel = "Previous record", onNext, nextRecordLabel = "Next record", counterLabel, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, quickActions: quickActionsProp, comments: commentsProp, onPostComment, currentUser, onShare, ...rest }) {
438
489
  const t = useRecordPageLabels(labels);
439
490
  const [favorite, setFavorite] = React.useState(false);
440
491
  // Notes/Tasks are controlled when the caller supplies `notes`/`tasks` (even
@@ -690,7 +741,7 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
690
741
  const effectivePanel = tabsProp
691
742
  ? (active) => tabsProp.find((tb) => tb.id === active)?.render()
692
743
  : builtinPanel;
693
- return (_jsxs("div", { className: recordpageRecipe({ className }), ...rest, children: [_jsxs("div", { className: "crisp-recordpage-topbar", children: [_jsxs("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [_jsx("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), _jsx("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), _jsx("span", { className: "crisp-recordpage-crumb-name", children: name })] }), _jsxs("div", { className: "crisp-recordpage-actions", children: [_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", onClick: onShare, children: t.share }), _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: () => setCommentsOpen(true), "aria-label": t.comments, children: _jsx(MessageSquare, { size: 15, strokeWidth: 1.75 }) }), _jsxs("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [_jsx(Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && (_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? (_jsx(Minimize2, { size: 15, strokeWidth: 1.75 })) : (_jsx(Maximize2, { size: 15, strokeWidth: 1.75 })) })), _jsxs(Menu, { align: "end", "aria-label": t.recordActions, trigger: _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [_jsx(MenuItem, { icon: _jsx(Merge, { size: 15, strokeWidth: 1.75 }), children: t.mergeRecords }), _jsx(MenuItem, { icon: _jsx(Settings, { size: 15, strokeWidth: 1.75 }), children: t.configurePage }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyPageUrl }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyRecordId }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, children: t.deleteRecord })] })] })] }), _jsxs("div", { className: "crisp-recordpage-body", children: [_jsxs("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [_jsxs("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? (_jsx("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : (_jsx(Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), _jsx("span", { className: "crisp-recordpage-entity-name", children: name }), _jsx("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: _jsx(Pencil, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: _jsx(Star, { size: 15, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "crisp-recordpage-quickactions", children: [_jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), _jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Plus, { size: 16, strokeWidth: 1.75 }), t.addToList] }), quickActions.map((a, i) => (_jsx("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": a.label, onClick: a.onSelect, children: _jsx(a.icon, { size: 15, strokeWidth: 1.75 }) }, i)))] }), _jsx(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), _jsxs("div", { className: "crisp-recordpage-section", children: [_jsx(SectionHead, { title: t.tabs.lists, action: _jsxs("button", { type: "button", className: "crisp-recordpage-addbtn", children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), _jsx("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), _jsx("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": DETAILS_MIN_W, "aria-valuemax": DETAILS_MAX_W, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
744
+ return (_jsxs("div", { className: recordpageRecipe({ className }), ...rest, children: [_jsxs("div", { className: "crisp-recordpage-topbar", "data-peek": onClose ? "" : undefined, children: [onClose ? (_jsxs("div", { className: "crisp-recordpage-peekbar", children: [_jsx("button", { type: "button", className: "crisp-recordpage-peek-iconbtn", "aria-label": closeLabel, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.75 }) }), (onPrevious || onNext) && (_jsxs("span", { className: "crisp-recordpage-peek-pager", children: [_jsx("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": previousRecordLabel, disabled: !onPrevious, onClick: onPrevious, children: _jsx(ChevronUp, { size: 16, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": nextRecordLabel, disabled: !onNext, onClick: onNext, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75 }) })] })), counterLabel != null && (_jsx("span", { className: "crisp-recordpage-peek-counter", children: counterLabel }))] })) : (_jsxs("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [_jsx("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), _jsx("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), _jsx("span", { className: "crisp-recordpage-crumb-name", children: name })] })), _jsxs("div", { className: "crisp-recordpage-actions", children: [_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", onClick: onShare, children: t.share }), _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: () => setCommentsOpen(true), "aria-label": t.comments, children: _jsx(MessageSquare, { size: 15, strokeWidth: 1.75 }) }), _jsxs("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [_jsx(Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && (_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? (_jsx(Minimize2, { size: 15, strokeWidth: 1.75 })) : (_jsx(Maximize2, { size: 15, strokeWidth: 1.75 })) })), _jsxs(Menu, { align: "end", "aria-label": t.recordActions, trigger: _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [_jsx(MenuItem, { icon: _jsx(Merge, { size: 15, strokeWidth: 1.75 }), children: t.mergeRecords }), _jsx(MenuItem, { icon: _jsx(Settings, { size: 15, strokeWidth: 1.75 }), children: t.configurePage }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyPageUrl }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyRecordId }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, children: t.deleteRecord })] })] })] }), _jsxs("div", { className: "crisp-recordpage-body", children: [_jsxs("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [_jsxs("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? (_jsx("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : (_jsx(Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), _jsx("span", { className: "crisp-recordpage-entity-name", children: name }), _jsx("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: _jsx(Pencil, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: _jsx(Star, { size: 15, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "crisp-recordpage-quickactions", children: [_jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), _jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Plus, { size: 16, strokeWidth: 1.75 }), t.addToList] }), quickActions.map((a, i) => (_jsx("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": a.label, onClick: a.onSelect, children: _jsx(a.icon, { size: 15, strokeWidth: 1.75 }) }, i)))] }), _jsx(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), _jsxs("div", { className: "crisp-recordpage-section", children: [_jsx(SectionHead, { title: t.tabs.lists, action: _jsxs("button", { type: "button", className: "crisp-recordpage-addbtn", children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), _jsx("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), _jsx("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": DETAILS_MIN_W, "aria-valuemax": DETAILS_MAX_W, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
694
745
  if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
695
746
  return;
696
747
  // The pointer drag learned its direction in #212; this did not.
@@ -23,6 +23,21 @@ const config = defineRecipe({
23
23
  & * { box-sizing: border-box; }
24
24
 
25
25
  & .crisp-recordpage-topbar { display: flex; align-items: center; gap: ${c.space["2"]}; height: 44px; flex-shrink: 0; padding: 0 ${c.space["3"]}; border-bottom: 1px solid ${c.borderSubtle}; }
26
+ /* #624: expanding a peek to its full record keeps a hybrid header
27
+ (Close/Previous/Next/counter) instead of reverting to the plain
28
+ breadcrumb above -- measured live on a real expanded record: 49px
29
+ tall (not 44px), 8px/10px padding, same borderSubtle hairline. Same
30
+ tokens as product/recordpanel's own already-shipped toolbar, not
31
+ new values -- this is the same row, reused in a second component. */
32
+ & .crisp-recordpage-topbar[data-peek] { height: 49px; padding: ${c.space["2"]} ${c.space["2.5"]}; }
33
+ & .crisp-recordpage-peekbar { display: flex; align-items: center; }
34
+ & .crisp-recordpage-peek-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: ${c.radius.md}; background: transparent; color: color-mix(in srgb, ${c.fgPrimary} 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s; }
35
+ & .crisp-recordpage-peek-iconbtn:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
36
+ & .crisp-recordpage-peek-pager { display: flex; align-items: center; gap: ${c.space["1.5"]}; margin-inline-start: ${c.space["3"]}; }
37
+ & .crisp-recordpage-peek-navbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: ${c.radius.md}; background: ${c.bgSurface}; box-shadow: ${e.field}; color: ${c.fgPrimary}; font-family: inherit; cursor: pointer; }
38
+ & .crisp-recordpage-peek-navbtn:hover:not(:disabled) { background: ${c.bgRaised}; }
39
+ & .crisp-recordpage-peek-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
40
+ & .crisp-recordpage-peek-counter { margin-inline-start: ${c.space["3"]}; font-size: 12px; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
26
41
  & .crisp-recordpage-crumbs { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; min-width: 0; }
27
42
  & .crisp-recordpage-crumb { display: inline-flex; align-items: center; height: 26px; padding: 0 ${c.space["1.5"]}; margin: 0 calc(-1 * ${c.space["1.5"]}) 0 0; border: none; border-radius: ${c.radius.md}; background: none; font-family: inherit; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; cursor: pointer; }
28
43
  & .crisp-recordpage-crumb:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
@@ -42,6 +57,17 @@ const config = defineRecipe({
42
57
  & .crisp-recordpage-resizer::before { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -4px; width: 9px; }
43
58
  & .crisp-recordpage-resizer::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -1px; width: 2px; background: transparent; transition: background-color .12s ease; }
44
59
  & .crisp-recordpage-resizer:hover::after, & .crisp-recordpage-resizer[data-active]::after { background: color-mix(in srgb, ${c.bgBrandSolid} 55%, transparent); }
60
+ /* #930: RecordDetails' own standalone resize handle -- same visible
61
+ hit-slop/line-highlight shape as .crisp-recordpage-resizer just above,
62
+ just absolutely positioned at the trailing edge instead of a 0-width
63
+ flex sibling, since RecordDetails always returns a single root (the
64
+ same internal-strip placement RecordPanel's own resize handle uses,
65
+ mirrored to the opposite edge -- this column is leading, RecordPanel
66
+ is trailing). */
67
+ & .crisp-recordpage-section[data-resizable] { position: relative; }
68
+ & .crisp-recordpage-detailresize { position: absolute; top: 0; bottom: 0; inset-inline-end: -4px; width: 9px; cursor: col-resize; z-index: 5; touch-action: none; }
69
+ & .crisp-recordpage-detailresize::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-end: 3px; width: 2px; background: transparent; transition: background-color .12s ease; }
70
+ & .crisp-recordpage-detailresize:hover::after, & .crisp-recordpage-detailresize[data-active]::after { background: color-mix(in srgb, ${c.bgBrandSolid} 55%, transparent); }
45
71
  & .crisp-recordpage-entity { display: flex; align-items: center; gap: ${c.space["2"]}; padding: ${c.space["1"]} ${c.space["1"]} ${c.space["2"]}; }
46
72
  & .crisp-recordpage-entity-logo { width: 32px; height: 32px; border-radius: 30%; flex-shrink: 0; object-fit: cover; background: ${c.bgRaised}; }
47
73
  & .crisp-recordpage-entity-name { flex: 0 1 auto; min-width: 0; font-size: ${c.text.xl}; font-weight: ${c.weight.semibold}; letter-spacing: -0.3px; color: ${c.fgPrimary}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -175,12 +201,23 @@ const config = defineRecipe({
175
201
  & .crisp-recordpage-btn--primary { box-shadow: none; background: ${c.bgBrandSolid}; color: ${c.fgOnBrand}; }
176
202
  & .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
177
203
 
178
- & .crisp-recordpage-note { display: flex; gap: ${c.space["2.5"]}; padding: ${c.space["2.5"]} 0; border-top: 1px solid ${c.borderSubtle}; }
179
- & .crisp-recordpage-note-body { flex: 1; min-width: 0; }
180
- & .crisp-recordpage-note-head { display: flex; align-items: center; gap: ${c.space["1.5"]}; margin-bottom: ${c.space["0.5"]}; }
181
- & .crisp-recordpage-note-author { font-size: ${c.text.md}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; }
182
- & .crisp-recordpage-note-time { font-size: ${c.text.sm}; color: ${c.fgMuted}; }
183
- & .crisp-recordpage-note-text { font-size: ${c.text.lg}; color: ${c.fgPrimary}; white-space: pre-wrap; word-break: break-word; }
204
+ /* #871: one bordered card per note (border-radius.xl, borderSubtle),
205
+ not a flush divider-list row -- measured live on a real populated
206
+ note: the whole row is flex space-between, avatar+title+body on the
207
+ left (its own flex, gap.2), the relative time alone on the right.
208
+ The real DOM nests an inner clickable button (padding 6/8, radius.md)
209
+ inside an outer card (padding 4px, radius.xl) -- collapsed into this
210
+ one non-interactive div's own combined padding (2.5/3) since crisp's
211
+ NotesSection has no click-to-open behaviour to justify the extra
212
+ layer. The 6px gap between stacked cards is NOT independently
213
+ measured (this workspace only ever had one real note to check) --
214
+ a judgement call off the existing space scale, not a live value. */
215
+ & .crisp-recordpage-note { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["3"]}; padding: ${c.space["2.5"]} ${c.space["3"]}; border: 1px solid ${c.borderSubtle}; border-radius: ${c.radius.xl}; }
216
+ & .crisp-recordpage-note + .crisp-recordpage-note { margin-top: ${c.space["1.5"]}; }
217
+ & .crisp-recordpage-note-left { display: flex; align-items: center; gap: ${c.space["2"]}; min-width: 0; overflow: hidden; }
218
+ & .crisp-recordpage-note-title { flex-shrink: 0; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; }
219
+ & .crisp-recordpage-note-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
220
+ & .crisp-recordpage-note-time { flex-shrink: 0; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 50%, transparent); }
184
221
 
185
222
  & .crisp-recordpage-task { display: flex; align-items: flex-start; gap: ${c.space["2"]}; padding: ${c.space["1.5"]} 0; border-top: 1px solid ${c.borderSubtle}; }
186
223
  & .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: ${c.text.lg}; color: ${c.fgPrimary}; padding-top: 1px; }
@@ -0,0 +1 @@
1
+ export { WorkspaceGeneralSettings, } from "./workspacegeneralsettings.js";
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Copy } from "lucide-react";
3
+ import { Avatar } from "../../components/avatar/index.js";
4
+ import { Button } from "../../components/button/index.js";
5
+ import { FormField } from "../../components/formfield/index.js";
6
+ import { Input } from "../../components/input/index.js";
7
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../components/table/index.js";
8
+ import { TextLink } from "../../components/textlink/index.js";
9
+ import { workspacegeneralsettingsRecipe } from "./workspacegeneralsettings.recipe.js";
10
+ /**
11
+ * Attio's Settings → Workspace → General page (crisp#638, P-128), measured
12
+ * live: a logo + Name/Slug section, an "Export workspace data" table, and a
13
+ * "Danger zone" delete-workspace row.
14
+ */
15
+ export function WorkspaceGeneralSettings({ logoSrc, workspaceInitial, onUploadLogo, name, onNameChange, slug, onCopySlug, exports: exportRows, onStartExport, onDeleteWorkspace, className, title = "General", description = "Change the settings for your current workspace.", learnMoreLabel = "Learn more", learnMoreHref = "#", logoHelpText = "We only support PNGs, JPEGs and GIFs under 10MB", uploadLogoLabel = "Upload logo", nameFieldLabel = "Name", slugFieldLabel = "Slug", copySlugLabel = "Copy slug", exportSectionTitle = "Export workspace data", exportSectionDescription = "Exports are in CSV format and can be downloaded within 7 days", startExportLabel = "Start new export", typeColumnLabel = "Type", dateColumnLabel = "Date", noExportsLabel = "No exports yet", dangerSectionTitle = "Danger zone", deleteWorkspaceTitle = "Delete workspace", deleteWorkspaceWarning = "Once deleted, your workspace cannot be recovered", deleteWorkspaceLabel = "Delete workspace", ...rest }) {
16
+ return (_jsx("div", { className: workspacegeneralsettingsRecipe({ className }), ...rest, children: _jsxs("div", { className: "crisp-workspacegeneralsettings-page", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-heading", children: [_jsx("h1", { className: "crisp-workspacegeneralsettings-title", children: title }), _jsxs("p", { className: "crisp-workspacegeneralsettings-desc", children: [description, " ", _jsx(TextLink, { href: learnMoreHref, children: learnMoreLabel })] })] }), _jsxs("div", { className: "crisp-workspacegeneralsettings-section", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-logorow", children: [logoSrc ? (_jsx("img", { src: logoSrc, alt: "", width: 48, height: 48, style: { borderRadius: "30%", objectFit: "cover" } })) : (_jsx(Avatar, { size: "xl", shape: "rounded", tone: "brand", children: workspaceInitial })), _jsxs("div", { className: "crisp-workspacegeneralsettings-logotext", children: [_jsx("span", { className: "crisp-workspacegeneralsettings-logohelp", children: logoHelpText }), _jsx(Button, { intent: "neutral", size: "sm", onClick: onUploadLogo, children: uploadLogoLabel })] })] }), _jsxs("div", { className: "crisp-workspacegeneralsettings-fieldrow", children: [_jsx(FormField, { label: nameFieldLabel, children: _jsx(Input, { value: name, onChange: (e) => onNameChange(e.target.value) }) }), _jsx(FormField, { label: slugFieldLabel, children: _jsxs("div", { className: "crisp-workspacegeneralsettings-slugfield", children: [_jsx(Input, { value: slug, readOnly: true }), _jsx("button", { type: "button", className: "crisp-workspacegeneralsettings-copybtn", "aria-label": copySlugLabel, onClick: onCopySlug, children: _jsx(Copy, { size: 16 }) })] }) })] })] }), _jsxs("div", { className: "crisp-workspacegeneralsettings-section", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-sectionhead", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-sectionhead-text", children: [_jsx("h2", { className: "crisp-workspacegeneralsettings-sectiontitle", children: exportSectionTitle }), _jsx("p", { className: "crisp-workspacegeneralsettings-desc", children: exportSectionDescription })] }), _jsx(Button, { intent: "neutral", size: "sm", onClick: onStartExport, children: startExportLabel })] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: typeColumnLabel }), _jsx(TableHead, { children: dateColumnLabel })] }) }), _jsx(TableBody, { children: exportRows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, children: _jsx("div", { className: "crisp-workspacegeneralsettings-emptyrow", children: noExportsLabel }) }) })) : (exportRows.map((row) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: row.type }), _jsx(TableCell, { children: row.date })] }, row.id)))) })] })] }), _jsxs("div", { className: "crisp-workspacegeneralsettings-section", children: [_jsx("h2", { className: "crisp-workspacegeneralsettings-sectiontitle", children: dangerSectionTitle }), _jsxs("div", { className: "crisp-workspacegeneralsettings-dangerrow", children: [_jsxs("div", { className: "crisp-workspacegeneralsettings-dangertext", children: [_jsx("span", { className: "crisp-workspacegeneralsettings-rowtitle", children: deleteWorkspaceTitle }), _jsx("span", { className: "crisp-workspacegeneralsettings-dangerwarning", children: deleteWorkspaceWarning })] }), _jsx(Button, { intent: "danger", size: "sm", onClick: onDeleteWorkspace, children: deleteWorkspaceLabel })] })] })] }) }));
17
+ }
18
+ WorkspaceGeneralSettings.displayName = "WorkspaceGeneralSettings";
@@ -0,0 +1,48 @@
1
+ import { defineRecipe, resolveRecipe } from "../../core/index.js";
2
+ import { contract as c } from "../../tokens/index.js";
3
+ // Measured live against app.attio.com/<workspace>/settings/workspace
4
+ // (crisp#638, P-128). Logo tile + Name/Slug fields side by side, an
5
+ // "Export workspace data" section (header row + Type/Date table + empty
6
+ // state), and a "Danger zone" section (a red-tinted bordered row).
7
+ const config = defineRecipe({
8
+ name: "workspacegeneralsettings",
9
+ base: `& .crisp-workspacegeneralsettings-page { display: flex; flex-direction: column; gap: ${c.space["6"]}; font-family: ${c.font.sans}; }
10
+ & .crisp-workspacegeneralsettings-heading { display: flex; flex-direction: column; gap: ${c.space["0.5"]}; }
11
+ & .crisp-workspacegeneralsettings-title { font-size: 24px; line-height: 28px; font-weight: ${c.weight.semibold}; letter-spacing: -0.48px; color: ${c.fgPrimary}; margin: 0; }
12
+ & .crisp-workspacegeneralsettings-desc { font-size: ${c.text.lg}; line-height: 20px; color: ${c.fgMuted}; margin: 0; }
13
+
14
+ & .crisp-workspacegeneralsettings-section { display: flex; flex-direction: column; gap: ${c.space["4"]}; }
15
+ & .crisp-workspacegeneralsettings-sectiontitle { font-size: 16px; line-height: 20px; font-weight: ${c.weight.semibold}; color: ${c.fgPrimary}; margin: 0; }
16
+ & .crisp-workspacegeneralsettings-sectionhead { display: flex; align-items: flex-start; justify-content: space-between; gap: ${c.space["4"]}; }
17
+ & .crisp-workspacegeneralsettings-sectionhead-text { display: flex; flex-direction: column; gap: ${c.space["1"]}; }
18
+
19
+ /* Logo row: a tile + helper text + "Upload logo" button. */
20
+ & .crisp-workspacegeneralsettings-logorow { display: flex; align-items: center; gap: ${c.space["4"]}; }
21
+ & .crisp-workspacegeneralsettings-logohelp { font-size: ${c.text.sm}; color: ${c.fgMuted}; }
22
+ & .crisp-workspacegeneralsettings-logotext { display: flex; flex-direction: column; align-items: flex-start; gap: ${c.space["1"]}; }
23
+
24
+ /* Name + Slug sit side by side, each ~half the content width. */
25
+ & .crisp-workspacegeneralsettings-fieldrow { display: flex; gap: ${c.space["4"]}; }
26
+ & .crisp-workspacegeneralsettings-fieldrow > * { flex: 1; min-width: 0; }
27
+ & .crisp-workspacegeneralsettings-slugfield { display: flex; align-items: center; gap: ${c.space["1.5"]}; }
28
+ & .crisp-workspacegeneralsettings-slugfield > :first-child { flex: 1; min-width: 0; }
29
+ & .crisp-workspacegeneralsettings-copybtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 32px; height: 32px; border: none; border-radius: ${c.radius.md}; background: none; font-family: inherit; color: ${c.fgMuted}; cursor: pointer; }
30
+ & .crisp-workspacegeneralsettings-copybtn:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
31
+
32
+ /* Export-data empty state -- measured: 14/500/63%-opacity, centred in a
33
+ 40px row, no border of its own (the Table wrapper already draws it). */
34
+ & .crisp-workspacegeneralsettings-emptyrow { padding: ${c.space["2.5"]} ${c.space["4"]}; font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); text-align: center; }
35
+
36
+ /* Danger zone: a red-tinted bordered row, distinct from every other
37
+ card on the page. */
38
+ & .crisp-workspacegeneralsettings-dangerrow { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["6"]}; padding: ${c.space["3"]} ${c.space["4"]}; border: 1px solid color-mix(in srgb, ${c.bgDanger} 30%, transparent); border-radius: 12px; background: color-mix(in srgb, ${c.bgDanger} 6%, transparent); }
39
+ & .crisp-workspacegeneralsettings-dangertext { display: flex; flex-direction: column; gap: ${c.space["1"]}; }
40
+ & .crisp-workspacegeneralsettings-rowtitle { font-size: ${c.text.lg}; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; }
41
+ & .crisp-workspacegeneralsettings-dangerwarning { font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
42
+ `,
43
+ variants: {},
44
+ defaultVariants: {},
45
+ compoundVariants: [],
46
+ });
47
+ export const workspacegeneralsettingsRecipe = resolveRecipe(config);
48
+ export default config;
@@ -1,5 +1,5 @@
1
1
  import { VAR_PREFIX } from "../core/namespace.js";
2
- import { font as fontScale, radius as radiusScale, size as sizeScale, space as spaceScale, text as textScale, weight as weightScale, } from "./scale.js";
2
+ import { font as fontScale, radius as radiusScale, size as sizeScale, space as spaceScale, text as textScale, tracking as trackingScale, weight as weightScale, } from "./scale.js";
3
3
  import { semantic } from "./semantic.js";
4
4
  const kebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
5
5
  const toVar = (key) => `var(--${VAR_PREFIX}${kebab(key)})`;
@@ -16,4 +16,5 @@ export const contract = Object.assign(colors, {
16
16
  font: mapScale("font", fontScale),
17
17
  text: mapScale("text", textScale),
18
18
  weight: mapScale("weight", weightScale),
19
+ tracking: mapScale("tracking", trackingScale),
19
20
  });
@@ -12,9 +12,11 @@ import { contract as c } from "./contract.js";
12
12
  */
13
13
  export const elevation = {
14
14
  /**
15
- * Tier 1 — anything floating and *attached*: menus, popovers, dropdowns,
15
+ * Tier 1 — anything floating and *attached*: popovers, dropdowns,
16
16
  * hover-preview cards, the floating sidebar, the record peek, drawers, the
17
17
  * bulk bar. Pairs with a 10–12px radius and, for panels, an 8px gutter.
18
+ * NOT `Menu` (your-moon/crisp#934) -- that moved to
19
+ * `selectFloating`, seed-design's own flat shadow, below.
18
20
  */
19
21
  floating: `inset 0 0 0 1px ${c.ringPopover}, 0 0 0 1px ${c.shadowPopover1}, 0 4px 8px -4px ${c.shadowPopover2}, 0 4px 12px -2px ${c.shadowPopover3}`,
20
22
  /**
@@ -57,10 +59,11 @@ export const elevation = {
57
59
  */
58
60
  fieldNoRing: `0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04)`,
59
61
  /**
60
- * Select's own listbox shadow (crisp/#867) — seed-design's `$shadow.s3`, a
61
- * single layer, NOT `floating` above. This component follows seed, not
62
- * Attio, for the same reason `DatePicker` follows seed's calendar
63
- * structure: an explicit owner request to match seed-design exactly.
62
+ * Seed-design's own `$shadow.s3`, a single flat layer, NOT `floating`
63
+ * above. Originally Select's own listbox shadow (crisp/#867); also `Menu`'s
64
+ * (your-moon/crisp#934) -- the raw seed dump confirms Menu
65
+ * is the one component seed's own spec gives a shadow to at all, and it's
66
+ * this exact value. Despite the name, not exclusive to Select.
64
67
  */
65
68
  selectFloating: `0 4px 16px ${c.shadowSelect}`,
66
69
  };
@@ -63,14 +63,34 @@ export const palette = {
63
63
  ringBrandSoft: { light: "#d6e5ff", dark: "#212224" },
64
64
  tileActive: { light: "#ffffff", dark: "#101112" },
65
65
  tileActiveShadow: { light: "rgba(0,0,0,0.04)", dark: "rgba(0,0,0,0)" },
66
- // Dialog's page-dimming backdrop (crisp#677) — measured live behind the
67
- // Create-attribute FormDialog, both themes: `rgba(0,0,0,.25)` light,
68
- // `rgba(0,0,0,.55)` dark. `Dialog`'s own backdrop had shipped fully
69
- // `transparent` (AUTHORING's Lesson 14 measured the command palette has
70
- // NO dark backdrop and that got generalized to every `Dialog`, which this
71
- // corrects — the palette is a different surface from a plain content
72
- // dialog and really does dim the page).
73
- bgScrim: { light: "rgba(0,0,0,0.25)", dark: "rgba(0,0,0,0.55)" },
66
+ // Dialog's page-dimming backdrop (crisp#677, Attio's own measured value:
67
+ // `rgba(0,0,0,.25)` light, `rgba(0,0,0,.55)` dark — `Dialog`'s own backdrop
68
+ // had shipped fully `transparent`, AUTHORING's Lesson 14 measured the
69
+ // command palette has NO dark backdrop and that got generalized to every
70
+ // `Dialog`, which this corrected).
71
+ //
72
+ // your-moon/crisp#934: light theme moved to seed-design's own scrim
73
+ // (`#00000074` ≈ 45% black, `bg.overlay`) — the owner's flat-dialog
74
+ // directive names "seed's own scrim" specifically, now that elevation is
75
+ // conveyed by the scrim alone (no more frosted-glass shadow/blur). Dark
76
+ // theme's `.55` is UNCHANGED: seed's own token system has no separate
77
+ // dark-theme scrim value to adopt, and `.55` wasn't found wrong -- only
78
+ // light had a sourced replacement value to move to.
79
+ bgScrim: { light: "rgba(0,0,0,0.45)", dark: "rgba(0,0,0,0.55)" },
80
+ // seed-design's `$color.bg.overlay` (BottomSheet/MenuSheet's own backdrop,
81
+ // crisp#911) — kept as its OWN token rather than folded into `bgScrim`
82
+ // above, even though #934 (landed after this one was added) moved
83
+ // `bgScrim`'s LIGHT value to the same `#00000074`: `bgOverlayStrong` is
84
+ // seed's literally theme-CONSTANT value (`color.yaml` gives the same hex
85
+ // for both `theme-light` and `theme-dark`), while `bgScrim` still differs
86
+ // by theme (`.45`/`.55`) -- the two tokens express different design
87
+ // decisions that currently coincide in one theme only.
88
+ bgOverlayStrong: { light: "#00000074", dark: "#00000074" },
89
+ // seed-design's `$color.stroke.neutral-muted` (MenuSheet group divider,
90
+ // crisp#911) -- a faint alpha hairline between grouped rows, theme-flipping
91
+ // (`color.yaml`: `static-black-alpha-300` light / `static-white-alpha-100`
92
+ // dark).
93
+ strokeGroupDivider: { light: "#00000010", dark: "#ffffff17" },
74
94
  // The discard-confirm's description line (crisp#677, `Dialog`'s new
75
95
  // `variant="alert"`) — measured live, both themes, over `popoverBg`: a
76
96
  // theme-relative alpha rather than a flat gray, so it stays legible if
@@ -27,13 +27,22 @@ export const space = {
27
27
  // (inputs, rows, tiles); full is the pill.
28
28
  export const radius = {
29
29
  xs: "4px", // seed-design's $radius.r1 — small badge / checkbox box corner
30
- chip: "7px", // tag / chip corner — the single most used radius
30
+ // your-moon/crisp#919: was `7px` (Attio's own most-measured
31
+ // radius, 70 occurrences) -- now a pill, matching seed-design's own
32
+ // `chip.yaml` (`$radius.full` at every size, no size-based radius step at
33
+ // all, unlike Button/Badge). Same numeric value as `full` below, kept as
34
+ // its own named token because every one of this token's ~9 consumers
35
+ // (Chip, DataTable/RecordPage/RecordsTable/MultiSelect's own tag/mention
36
+ // rules, TableToolbar/DatePickerField's date-preset chips) reasons about
37
+ // "the chip corner" as its own concept, not literally "a generic pill".
38
+ chip: "999px",
31
39
  sm: "6px", // chips, badges
32
40
  md: "8px", // inputs, search box
33
41
  lg: "9px", // rows, tiles, list items
34
42
  xl: "12px", // overlay items (command rows)
35
43
  "2xl": "16px", // overlay panels (command surface)
36
44
  "3xl": "20px", // seed-design's $radius.r5 — Select's listbox corner (crisp/#867)
45
+ "4xl": "24px", // seed-design's $radius.r6 — BottomSheet's top corner radius (crisp/#911)
37
46
  full: "999px", // pills
38
47
  };
39
48
  // Component sizing — recurring measured dimensions that are neither spacing nor
@@ -85,3 +94,36 @@ export const weight = {
85
94
  semibold: "600",
86
95
  bold: "700", // seed-design's font-weight-bold, for the seed-styled calendar
87
96
  };
97
+ // Size-based letter-spacing ramp for DISPLAY/HEADING type (18px and up —
98
+ // `text.*` above tops out at 16px and stays untracked). crisp's five
99
+ // already-shipped headings tighten as they grow — authshell 20px/-0.4px (2%
100
+ // of size), objectsettings 24px/-0.48px (2%), richtext 32px/-0.6px (1.875%)
101
+ // — all Attio-measured (Inter) and left exactly as they are; this token
102
+ // never touches them, or the button (-0.07px) / table-cell (-0.14px)
103
+ // tracking, which are body sizes, not display type. (statcard's 28px/-0.4px,
104
+ // 1.43%, is a looser outlier — it sets tabular numerals, not prose — and is
105
+ // also left alone.)
106
+ //
107
+ // crisp#909: the study at docs/study/korean/ observed that Naver and Kakao
108
+ // both tighten tracking as type gets bigger, and crisp's OWN large type
109
+ // (EmptyState's 20px title, Dialog's 18px alert title, and any future
110
+ // heading) had no tracking at all — it read as default, not set. The study
111
+ // supplied the PATTERN only, never a number: Naver/Kakao's own values are
112
+ // Korean-specific and were not copied. The numbers below come from rendering
113
+ // real Mongolian Cyrillic ("Эрсдэлийн бүртгэл", "Хяналтын самбар") in VK Sans
114
+ // Display — crisp's own font override, see `font.sans` above — at every
115
+ // display size crisp uses today (18/20/24/26/28/32px), screenshotting six
116
+ // candidates per size (0%, 1%, 1.5%, 2%, 2.5%, 3% of the font-size) and
117
+ // checking for glyph collision. None collided even at 3%, so the pre-existing
118
+ // 2%-of-size rule the five Attio headings already happen to follow was kept
119
+ // as the ramp, rounded to the nearest 0.01px — it reads as deliberately set
120
+ // with headroom to spare, and keeps new display type visually consistent
121
+ // with the Attio-measured headings already shipped.
122
+ export const tracking = {
123
+ "18": "-0.36px",
124
+ "20": "-0.4px",
125
+ "24": "-0.48px",
126
+ "26": "-0.52px",
127
+ "28": "-0.56px",
128
+ "32": "-0.64px",
129
+ };
@@ -38,6 +38,8 @@ export const semantic = {
38
38
  bgTileActive: "tileActive",
39
39
  shadowTileActive: "tileActiveShadow",
40
40
  bgScrim: "bgScrim",
41
+ bgOverlayStrong: "bgOverlayStrong",
42
+ strokeGroupDivider: "strokeGroupDivider",
41
43
  fgAlertDescription: "fgAlertDescription",
42
44
  bgOverlayFrost: "overlayFrost",
43
45
  shadowOverlay1: "shadowOverlay1",
@@ -25,7 +25,9 @@
25
25
  & .crisp-fileupload-remove:hover { color: var(--crisp-fg-primary); }
26
26
  & .crisp-fileupload-remove:focus-visible { outline-color: var(--crisp-stroke-focus-ring); }
27
27
  & .crisp-fileupload-errors { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); margin: 0; padding: 0; list-style: none; }
28
- /* out of flow while empty, so the always-mounted live region costs no layout: display:none would take it out of the accessibility tree, which is the one thing it exists to be in */
28
+ /* out of flow while empty, so the always-mounted live region costs no
29
+ layout: display:none would take it out of the accessibility tree, which
30
+ is the one thing it exists to be in */
29
31
  & .crisp-fileupload-errors:empty { position: absolute; }
30
32
  & .crisp-fileupload-error { color: var(--crisp-bg-danger); font-size: var(--crisp-text-md); line-height: 18px; }
31
33
  }
@@ -26,7 +26,9 @@
26
26
  & .crisp-fileupload-remove:hover { color: var(--crisp-fg-primary); }
27
27
  & .crisp-fileupload-remove:focus-visible { outline-color: var(--crisp-stroke-focus-ring); }
28
28
  & .crisp-fileupload-errors { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); margin: 0; padding: 0; list-style: none; }
29
- /* out of flow while empty, so the always-mounted live region costs no layout: display:none would take it out of the accessibility tree, which is the one thing it exists to be in */
29
+ /* out of flow while empty, so the always-mounted live region costs no
30
+ layout: display:none would take it out of the accessibility tree, which
31
+ is the one thing it exists to be in */
30
32
  & .crisp-fileupload-errors:empty { position: absolute; }
31
33
  & .crisp-fileupload-error { color: var(--crisp-bg-danger); font-size: var(--crisp-text-md); line-height: 18px; }
32
34
  }
package/dist/input.css CHANGED
@@ -1,5 +1,5 @@
1
1
  .crisp-input {
2
- position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
2
+ position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); border: 1px solid var(--crisp-border-default); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
3
3
  &::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
4
4
  &:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
5
5
  & .crisp-input-value { box-sizing: border-box; flex: 1 1 auto; min-width: 0; width: 100%; align-self: stretch; margin: 0; border: none; background: none; outline: none; font: inherit; color: var(--crisp-fg-primary); }
@@ -10,8 +10,10 @@
10
10
  primary-email field -- a tinted field, not just muted text, driven off
11
11
  the native `readOnly` attribute. `ringField` (rgba(0,0,0,.06)) is an
12
12
  exact match for the measured ring; the 2%-fgPrimary tint reuses the
13
- same color-mix idiom every other soft crisp surface already uses. */
14
- &[data-readonly] { background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); }
13
+ same color-mix idiom every other soft crisp surface already uses.
14
+ `border-color: transparent` neutralises #910's new real rest border
15
+ so this measured ring isn't drawn twice. */
16
+ &[data-readonly] { background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); border-color: transparent; box-shadow: inset 0 0 0 1px var(--crisp-ring-field); }
15
17
  & .crisp-input-suffix { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); }
16
18
  }
17
19
  .crisp-input--size_sm {
@@ -23,7 +25,7 @@
23
25
  & .crisp-input-value { padding-inline: var(--crisp-space-2-5); }
24
26
  }
25
27
  .crisp-input--size_large {
26
- height: 36px; border-radius: 10px;
28
+ height: 36px; border-radius: var(--crisp-radius-xl);
27
29
  & .crisp-input-value { padding-inline: var(--crisp-space-3); }
28
30
  }
29
31
  .crisp-input--invalid_true {
@@ -1,6 +1,6 @@
1
1
  @layer crisp-components {
2
2
  .crisp-input {
3
- position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
3
+ position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); border: 1px solid var(--crisp-border-default); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
4
4
  &::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
5
5
  &:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
6
6
  & .crisp-input-value { box-sizing: border-box; flex: 1 1 auto; min-width: 0; width: 100%; align-self: stretch; margin: 0; border: none; background: none; outline: none; font: inherit; color: var(--crisp-fg-primary); }
@@ -11,8 +11,10 @@
11
11
  primary-email field -- a tinted field, not just muted text, driven off
12
12
  the native `readOnly` attribute. `ringField` (rgba(0,0,0,.06)) is an
13
13
  exact match for the measured ring; the 2%-fgPrimary tint reuses the
14
- same color-mix idiom every other soft crisp surface already uses. */
15
- &[data-readonly] { background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); }
14
+ same color-mix idiom every other soft crisp surface already uses.
15
+ `border-color: transparent` neutralises #910's new real rest border
16
+ so this measured ring isn't drawn twice. */
17
+ &[data-readonly] { background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); border-color: transparent; box-shadow: inset 0 0 0 1px var(--crisp-ring-field); }
16
18
  & .crisp-input-suffix { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); }
17
19
  }
18
20
  .crisp-input--size_sm {
@@ -24,7 +26,7 @@
24
26
  & .crisp-input-value { padding-inline: var(--crisp-space-2-5); }
25
27
  }
26
28
  .crisp-input--size_large {
27
- height: 36px; border-radius: 10px;
29
+ height: 36px; border-radius: var(--crisp-radius-xl);
28
30
  & .crisp-input-value { padding-inline: var(--crisp-space-3); }
29
31
  }
30
32
  .crisp-input--invalid_true {
package/dist/listpage.css CHANGED
@@ -5,7 +5,9 @@
5
5
  & .crisp-listpage-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); white-space: nowrap; }
6
6
  /* #641: trailing (View settings / New) has to reach the toolbar's own
7
7
  inline-end edge on its own -- the only thing pushing it there used
8
- to be .crisp-listpage-count's margin-inline-start: auto, which a consumer with no count badge (TasksTable, measured live: trailing sat right after leading, nowhere near the row's own right edge)
8
+ to be .crisp-listpage-count's margin-inline-start: auto, which a
9
+ consumer with no count badge (TasksTable, measured live: trailing
10
+ sat right after leading, nowhere near the row's own right edge)
9
11
  never renders. A bare margin-inline-start: auto here is safely
10
12
  overridden by the more specific rule right below whenever count IS
11
13
  present, so the two cases don't fight -- count's own auto margin
@@ -6,7 +6,9 @@
6
6
  & .crisp-listpage-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); white-space: nowrap; }
7
7
  /* #641: trailing (View settings / New) has to reach the toolbar's own
8
8
  inline-end edge on its own -- the only thing pushing it there used
9
- to be .crisp-listpage-count's margin-inline-start: auto, which a consumer with no count badge (TasksTable, measured live: trailing sat right after leading, nowhere near the row's own right edge)
9
+ to be .crisp-listpage-count's margin-inline-start: auto, which a
10
+ consumer with no count badge (TasksTable, measured live: trailing
11
+ sat right after leading, nowhere near the row's own right edge)
10
12
  never renders. A bare margin-inline-start: auto here is safely
11
13
  overridden by the more specific rule right below whenever count IS
12
14
  present, so the two cases don't fight -- count's own auto margin