@crisp-ui-kit/crisp 0.57.1 → 0.57.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) 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 +10 -6
  11. package/dist/button.d.ts +2 -2
  12. package/dist/button.layered.css +10 -6
  13. package/dist/checkbox.css +7 -3
  14. package/dist/checkbox.layered.css +7 -3
  15. package/dist/chip.css +16 -1
  16. package/dist/chip.d.ts +3 -1
  17. package/dist/chip.layered.css +16 -1
  18. package/dist/chipgroup.css +5 -0
  19. package/dist/chipgroup.d.ts +7 -0
  20. package/dist/chipgroup.layered.css +7 -0
  21. package/dist/cjs/components/badge/badge.recipe.js +22 -8
  22. package/dist/cjs/components/bottomsheet/bottomsheet.js +90 -0
  23. package/dist/cjs/components/bottomsheet/bottomsheet.recipe.js +77 -0
  24. package/dist/cjs/components/bottomsheet/index.js +9 -0
  25. package/dist/cjs/components/breadcrumb/breadcrumb.js +1 -1
  26. package/dist/cjs/components/button/button.recipe.js +67 -15
  27. package/dist/cjs/components/checkbox/checkbox.recipe.js +61 -9
  28. package/dist/cjs/components/chip/chip.js +14 -2
  29. package/dist/cjs/components/chip/chip.recipe.js +72 -2
  30. package/dist/cjs/components/chipgroup/chipgroup.js +51 -0
  31. package/dist/cjs/components/chipgroup/chipgroup.recipe.js +36 -0
  32. package/dist/cjs/components/chipgroup/index.js +7 -0
  33. package/dist/cjs/components/datatable/datatable.js +5 -1
  34. package/dist/cjs/components/datatable/datatable.recipe.js +21 -6
  35. package/dist/cjs/components/datepickerfield/datepickerfield.js +2 -2
  36. package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +16 -3
  37. package/dist/cjs/components/dialog/dialog.recipe.js +21 -10
  38. package/dist/cjs/components/emptystate/emptystate.recipe.js +7 -2
  39. package/dist/cjs/components/fileupload/fileupload.js +21 -4
  40. package/dist/cjs/components/input/index.js +2 -1
  41. package/dist/cjs/components/input/input.js +31 -1
  42. package/dist/cjs/components/input/input.recipe.js +16 -9
  43. package/dist/cjs/components/menu/menu.js +18 -2
  44. package/dist/cjs/components/menu/menu.recipe.js +21 -7
  45. package/dist/cjs/components/menusheet/index.js +9 -0
  46. package/dist/cjs/components/menusheet/menusheet.js +133 -0
  47. package/dist/cjs/components/menusheet/menusheet.recipe.js +103 -0
  48. package/dist/cjs/components/multiselect/multiselect.js +1 -1
  49. package/dist/cjs/components/multiselect/multiselect.recipe.js +33 -9
  50. package/dist/cjs/components/select/select.js +1 -1
  51. package/dist/cjs/components/select/select.recipe.js +38 -12
  52. package/dist/cjs/components/snackbar/index.js +8 -0
  53. package/dist/cjs/components/snackbar/snackbar.js +121 -0
  54. package/dist/cjs/components/snackbar/snackbar.recipe.js +71 -0
  55. package/dist/cjs/components/table/table.recipe.js +7 -1
  56. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  57. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +37 -12
  58. package/dist/cjs/components/tabs/tabs.js +10 -4
  59. package/dist/cjs/components/tabs/tabs.recipe.js +23 -6
  60. package/dist/cjs/components/textarea/textarea.recipe.js +11 -8
  61. package/dist/cjs/core/floating-portal.js +7 -4
  62. package/dist/cjs/index.js +7 -0
  63. package/dist/cjs/product/authshell/authshell.recipe.js +5 -1
  64. package/dist/cjs/product/command/command.recipe.js +4 -1
  65. package/dist/cjs/product/comments/comments.js +7 -2
  66. package/dist/cjs/product/comments/comments.recipe.js +25 -7
  67. package/dist/cjs/product/inbox/inbox.js +11 -3
  68. package/dist/cjs/product/memberssettings/index.js +6 -0
  69. package/dist/cjs/product/memberssettings/memberssettings.js +99 -0
  70. package/dist/cjs/product/memberssettings/memberssettings.recipe.js +83 -0
  71. package/dist/cjs/product/notificationssettings/index.js +5 -0
  72. package/dist/cjs/product/notificationssettings/notificationssettings.js +19 -0
  73. package/dist/cjs/product/notificationssettings/notificationssettings.recipe.js +44 -0
  74. package/dist/cjs/product/recordpage/index.js +2 -1
  75. package/dist/cjs/product/recordpage/recordpage.js +106 -9
  76. package/dist/cjs/product/recordpage/recordpage.recipe.js +68 -6
  77. package/dist/cjs/product/workspacegeneralsettings/index.js +5 -0
  78. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.js +21 -0
  79. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +51 -0
  80. package/dist/cjs/tokens/contract.js +1 -0
  81. package/dist/cjs/tokens/elevation.js +8 -5
  82. package/dist/cjs/tokens/palette.js +28 -8
  83. package/dist/cjs/tokens/scale.js +44 -2
  84. package/dist/cjs/tokens/semantic.js +2 -0
  85. package/dist/columncalc.css +15 -7
  86. package/dist/columncalc.layered.css +15 -7
  87. package/dist/command.css +23 -19
  88. package/dist/command.layered.css +23 -19
  89. package/dist/comments.css +28 -19
  90. package/dist/comments.d.ts +2 -2
  91. package/dist/comments.layered.css +28 -19
  92. package/dist/datatable.css +171 -71
  93. package/dist/datatable.layered.css +171 -71
  94. package/dist/datepicker.css +13 -41
  95. package/dist/datepicker.layered.css +13 -41
  96. package/dist/datepickerfield.css +12 -3
  97. package/dist/datepickerfield.d.ts +2 -2
  98. package/dist/datepickerfield.layered.css +12 -3
  99. package/dist/dialog.css +12 -7
  100. package/dist/dialog.layered.css +12 -7
  101. package/dist/emptystate.css +5 -3
  102. package/dist/emptystate.layered.css +5 -3
  103. package/dist/esm/components/badge/badge.recipe.js +22 -8
  104. package/dist/esm/components/bottomsheet/bottomsheet.js +52 -0
  105. package/dist/esm/components/bottomsheet/bottomsheet.recipe.js +74 -0
  106. package/dist/esm/components/bottomsheet/index.js +2 -0
  107. package/dist/esm/components/breadcrumb/breadcrumb.js +1 -1
  108. package/dist/esm/components/button/button.recipe.js +68 -16
  109. package/dist/esm/components/checkbox/checkbox.recipe.js +61 -9
  110. package/dist/esm/components/chip/chip.js +14 -2
  111. package/dist/esm/components/chip/chip.recipe.js +72 -2
  112. package/dist/esm/components/chipgroup/chipgroup.js +15 -0
  113. package/dist/esm/components/chipgroup/chipgroup.recipe.js +33 -0
  114. package/dist/esm/components/chipgroup/index.js +2 -0
  115. package/dist/esm/components/datatable/datatable.js +5 -1
  116. package/dist/esm/components/datatable/datatable.recipe.js +21 -6
  117. package/dist/esm/components/datepickerfield/datepickerfield.js +2 -2
  118. package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +16 -3
  119. package/dist/esm/components/dialog/dialog.recipe.js +22 -11
  120. package/dist/esm/components/emptystate/emptystate.recipe.js +7 -2
  121. package/dist/esm/components/fileupload/fileupload.js +20 -5
  122. package/dist/esm/components/input/index.js +1 -1
  123. package/dist/esm/components/input/input.js +30 -0
  124. package/dist/esm/components/input/input.recipe.js +17 -10
  125. package/dist/esm/components/menu/menu.js +18 -2
  126. package/dist/esm/components/menu/menu.recipe.js +21 -7
  127. package/dist/esm/components/menusheet/index.js +2 -0
  128. package/dist/esm/components/menusheet/menusheet.js +95 -0
  129. package/dist/esm/components/menusheet/menusheet.recipe.js +100 -0
  130. package/dist/esm/components/multiselect/multiselect.js +1 -1
  131. package/dist/esm/components/multiselect/multiselect.recipe.js +33 -9
  132. package/dist/esm/components/select/select.js +1 -1
  133. package/dist/esm/components/select/select.recipe.js +38 -12
  134. package/dist/esm/components/snackbar/index.js +2 -0
  135. package/dist/esm/components/snackbar/snackbar.js +84 -0
  136. package/dist/esm/components/snackbar/snackbar.recipe.js +68 -0
  137. package/dist/esm/components/table/table.recipe.js +7 -1
  138. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  139. package/dist/esm/components/tabletoolbar/tabletoolbar.js +37 -12
  140. package/dist/esm/components/tabs/tabs.js +10 -4
  141. package/dist/esm/components/tabs/tabs.recipe.js +23 -6
  142. package/dist/esm/components/textarea/textarea.recipe.js +12 -9
  143. package/dist/esm/core/floating-portal.js +7 -4
  144. package/dist/esm/index.js +7 -0
  145. package/dist/esm/product/authshell/authshell.recipe.js +5 -1
  146. package/dist/esm/product/command/command.recipe.js +4 -1
  147. package/dist/esm/product/comments/comments.js +7 -2
  148. package/dist/esm/product/comments/comments.recipe.js +25 -7
  149. package/dist/esm/product/inbox/inbox.js +11 -3
  150. package/dist/esm/product/memberssettings/index.js +1 -0
  151. package/dist/esm/product/memberssettings/memberssettings.js +62 -0
  152. package/dist/esm/product/memberssettings/memberssettings.recipe.js +80 -0
  153. package/dist/esm/product/notificationssettings/index.js +1 -0
  154. package/dist/esm/product/notificationssettings/notificationssettings.js +16 -0
  155. package/dist/esm/product/notificationssettings/notificationssettings.recipe.js +41 -0
  156. package/dist/esm/product/recordpage/index.js +1 -1
  157. package/dist/esm/product/recordpage/recordpage.js +106 -10
  158. package/dist/esm/product/recordpage/recordpage.recipe.js +68 -6
  159. package/dist/esm/product/workspacegeneralsettings/index.js +1 -0
  160. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.js +18 -0
  161. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +48 -0
  162. package/dist/esm/tokens/contract.js +2 -1
  163. package/dist/esm/tokens/elevation.js +8 -5
  164. package/dist/esm/tokens/palette.js +28 -8
  165. package/dist/esm/tokens/scale.js +43 -1
  166. package/dist/esm/tokens/semantic.js +2 -0
  167. package/dist/fileupload.css +3 -1
  168. package/dist/fileupload.layered.css +3 -1
  169. package/dist/input.css +6 -4
  170. package/dist/input.layered.css +6 -4
  171. package/dist/listpage.css +3 -1
  172. package/dist/listpage.layered.css +3 -1
  173. package/dist/memberssettings.css +67 -0
  174. package/dist/memberssettings.d.ts +7 -0
  175. package/dist/memberssettings.layered.css +69 -0
  176. package/dist/menu.css +2 -2
  177. package/dist/menu.layered.css +2 -2
  178. package/dist/menusheet.css +36 -0
  179. package/dist/menusheet.d.ts +7 -0
  180. package/dist/menusheet.layered.css +38 -0
  181. package/dist/multiselect.css +13 -6
  182. package/dist/multiselect.layered.css +13 -6
  183. package/dist/native/tokens.d.ts +2 -1
  184. package/dist/native/tokens.js +20 -3
  185. package/dist/notificationssettings.css +22 -0
  186. package/dist/notificationssettings.d.ts +7 -0
  187. package/dist/notificationssettings.layered.css +24 -0
  188. package/dist/objectsettings.css +4 -4
  189. package/dist/objectsettings.layered.css +4 -4
  190. package/dist/recordpage.css +90 -23
  191. package/dist/recordpage.layered.css +90 -23
  192. package/dist/recordpanel.css +35 -16
  193. package/dist/recordpanel.layered.css +35 -16
  194. package/dist/resizablepanel.css +8 -5
  195. package/dist/resizablepanel.layered.css +8 -5
  196. package/dist/richtext.css +24 -8
  197. package/dist/richtext.layered.css +24 -8
  198. package/dist/savedviews.css +9 -2
  199. package/dist/savedviews.layered.css +9 -2
  200. package/dist/select.css +25 -5
  201. package/dist/select.layered.css +25 -5
  202. package/dist/settingsshell.css +2 -3
  203. package/dist/settingsshell.layered.css +2 -3
  204. package/dist/slider.css +6 -27
  205. package/dist/slider.layered.css +6 -27
  206. package/dist/snackbar.css +24 -0
  207. package/dist/snackbar.d.ts +7 -0
  208. package/dist/snackbar.layered.css +26 -0
  209. package/dist/statcard.css +3 -1
  210. package/dist/statcard.layered.css +3 -1
  211. package/dist/styles.css +911 -354
  212. package/dist/styles.layered.css +911 -354
  213. package/dist/table.css +35 -12
  214. package/dist/table.layered.css +35 -12
  215. package/dist/tabletoolbar.css +35 -24
  216. package/dist/tabletoolbar.layered.css +35 -24
  217. package/dist/tabs.css +18 -4
  218. package/dist/tabs.layered.css +18 -4
  219. package/dist/textarea.css +2 -6
  220. package/dist/textarea.layered.css +2 -6
  221. package/dist/themepicker.css +2 -1
  222. package/dist/themepicker.layered.css +2 -1
  223. package/dist/tokens.css +13 -2
  224. package/dist/tokens.json +20 -3
  225. package/dist/types/components/bottomsheet/bottomsheet.d.ts +35 -0
  226. package/dist/types/components/bottomsheet/bottomsheet.recipe.d.ts +3 -0
  227. package/dist/types/components/bottomsheet/index.d.ts +2 -0
  228. package/dist/types/components/breadcrumb/breadcrumb.d.ts +9 -1
  229. package/dist/types/components/button/button.d.ts +2 -0
  230. package/dist/types/components/button/button.recipe.d.ts +24 -6
  231. package/dist/types/components/checkbox/checkbox.recipe.d.ts +28 -5
  232. package/dist/types/components/chip/chip.d.ts +22 -0
  233. package/dist/types/components/chip/chip.recipe.d.ts +49 -1
  234. package/dist/types/components/chipgroup/chipgroup.d.ts +14 -0
  235. package/dist/types/components/chipgroup/chipgroup.recipe.d.ts +5 -0
  236. package/dist/types/components/chipgroup/index.d.ts +2 -0
  237. package/dist/types/components/datatable/datatable.d.ts +19 -0
  238. package/dist/types/components/datepickerfield/datepickerfield.d.ts +4 -1
  239. package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +18 -3
  240. package/dist/types/components/fileupload/fileupload.d.ts +26 -0
  241. package/dist/types/components/input/index.d.ts +1 -1
  242. package/dist/types/components/input/input.d.ts +72 -0
  243. package/dist/types/components/menusheet/index.d.ts +2 -0
  244. package/dist/types/components/menusheet/menusheet.d.ts +42 -0
  245. package/dist/types/components/menusheet/menusheet.recipe.d.ts +3 -0
  246. package/dist/types/components/snackbar/index.d.ts +2 -0
  247. package/dist/types/components/snackbar/snackbar.d.ts +36 -0
  248. package/dist/types/components/snackbar/snackbar.recipe.d.ts +5 -0
  249. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +28 -1
  250. package/dist/types/components/tabs/tabs.d.ts +11 -1
  251. package/dist/types/core/floating-portal.d.ts +6 -5
  252. package/dist/types/index.d.ts +7 -0
  253. package/dist/types/product/comments/comments.d.ts +9 -3
  254. package/dist/types/product/comments/comments.recipe.d.ts +12 -2
  255. package/dist/types/product/inbox/inbox.d.ts +13 -1
  256. package/dist/types/product/memberssettings/index.d.ts +1 -0
  257. package/dist/types/product/memberssettings/memberssettings.d.ts +85 -0
  258. package/dist/types/product/memberssettings/memberssettings.recipe.d.ts +3 -0
  259. package/dist/types/product/notificationssettings/index.d.ts +1 -0
  260. package/dist/types/product/notificationssettings/notificationssettings.d.ts +37 -0
  261. package/dist/types/product/notificationssettings/notificationssettings.recipe.d.ts +3 -0
  262. package/dist/types/product/recordpage/index.d.ts +1 -1
  263. package/dist/types/product/recordpage/recordpage.d.ts +99 -2
  264. package/dist/types/product/workspacegeneralsettings/index.d.ts +1 -0
  265. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.d.ts +48 -0
  266. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.recipe.d.ts +3 -0
  267. package/dist/types/tokens/contract.d.ts +4 -1
  268. package/dist/types/tokens/elevation.d.ts +8 -5
  269. package/dist/types/tokens/palette.d.ts +9 -1
  270. package/dist/types/tokens/scale.d.ts +11 -1
  271. package/dist/types/tokens/semantic.d.ts +2 -0
  272. package/dist/wheelpicker.css +4 -2
  273. package/dist/wheelpicker.layered.css +4 -2
  274. package/dist/workspacegeneralsettings.css +30 -0
  275. package/dist/workspacegeneralsettings.d.ts +7 -0
  276. package/dist/workspacegeneralsettings.layered.css +32 -0
  277. package/package.json +36 -1
@@ -1,8 +1,12 @@
1
1
  @layer crisp-components {
2
2
  .crisp-datatable {
3
- display: flex; flex-direction: column; gap: var(--crisp-space-2-5); font-family: var(--crisp-font-sans); /* The toolbar carried no horizontal inset of its own, so its content (search box, sort/filter chips) sat flush at the table's outer edge
4
- (x=0) while the header row's own th/td padding (table.recipe.ts, #465) starts every column's text at 12px -- one row down, the SAME
5
- grid's own header cell already begins at that inset. c.space["3"] here just matches the row directly below it, measured live against Attio (the sort/filter row's search chip lines up with the first
3
+ display: flex; flex-direction: column; gap: var(--crisp-space-2-5); font-family: var(--crisp-font-sans); /* The toolbar carried no horizontal inset of its own, so its content
4
+ (search box, sort/filter chips) sat flush at the table's outer edge
5
+ (x=0) while the header row's own th/td padding (table.recipe.ts,
6
+ #465) starts every column's text at 12px -- one row down, the SAME
7
+ grid's own header cell already begins at that inset. c.space["3"]
8
+ here just matches the row directly below it, measured live against
9
+ Attio (the sort/filter row's search chip lines up with the first
6
10
  column header's text, not the grid's raw edge). */
7
11
  & .crisp-datatable-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); min-height: 49px; border-top: 1px solid var(--crisp-border-subtle); padding: 0 var(--crisp-space-3); }
8
12
  & .crisp-datatable-search { position: relative; flex: 1; max-width: 280px; display: inline-flex; align-items: center; }
@@ -12,10 +16,7 @@
12
16
  & .crisp-datatable-search input:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
13
17
  & .crisp-datatable-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
14
18
  /* announced, never drawn */
15
- & .crisp-datatable-live {
16
- position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
17
- overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
18
- }
19
+ & .crisp-datatable-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
19
20
  /* crisp/#645: visually-hidden-but-focusable, not visually-hidden-and-
20
21
  inert like .crisp-datatable-live above -- same clip-rect shape,
21
22
  this one just needs to actually hold DOM focus so a native
@@ -23,7 +24,8 @@
23
24
  in datatable.tsx). resize: none keeps a stray native textarea handle
24
25
  from ever being drawable, even though it's clipped to 1px anyway. */
25
26
  & .crisp-datatable-clipboard-proxy { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; resize: none; }
26
- /* single active cell (crisp/#645 follow-up -- this had no styling of its own at all before, a gap against Golden Rule 0: RecordsTable's own
27
+ /* single active cell (crisp/#645 follow-up -- this had no styling of its
28
+ own at all before, a gap against Golden Rule 0: RecordsTable's own
27
29
  measured value, .crisp-recordstable-td[data-selected], brought
28
30
  across unchanged now that a visible anchor actually matters for the
29
31
  Shift+Click range below). */
@@ -32,57 +34,91 @@
32
34
  tint formula already used twice below (tbody tr[data-selected] td)
33
35
  for the same reason a selected group of rows gets one: a selected
34
36
  GROUP of cells is the same kind of state, just cell-scoped instead of
35
- row-scoped. Not independently re-measured against Attio's own range highlight (out of scope this slice -- flagged on crisp#645). */
37
+ row-scoped. Not independently re-measured against Attio's own range
38
+ highlight (out of scope this slice -- flagged on crisp#645). */
36
39
  & td[data-in-range] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
37
- /* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. `& .crisp-table th`, not bare `& th` (found while re-checking #691's
40
+ /* The header cell has no padding of its own -- the button owns the
41
+ full cell so the whole header is clickable (its own inset is 12px).
42
+ Mirrors the summary-row fix.
43
+ `& .crisp-table th`, not bare `& th` (found while re-checking #691's
38
44
  own fix live): DataTable's root nests `Table`'s own wrapper, whose
39
45
  `table.recipe.ts` carries `& th, & td { padding: 0 12px }` at the
40
46
  SAME specificity (.crisp-table th vs .crisp-datatable th, both one
41
47
  class + one element) -- a bare `& th` here only won by source
42
- order, which flipped the moment table.recipe.ts's own CSS file happened to land after datatable's in the build (alphabetical:
48
+ order, which flipped the moment table.recipe.ts's own CSS file
49
+ happened to land after datatable's in the build (alphabetical:
43
50
  datatable < table), silently reinstating 12px on every header cell
44
- in the whole product and making every interactive column's button double it to 24px. Scoping through `.crisp-table` adds a second class to the selector (0,2,1 beats 0,1,1) so this can never lose a specificity tie to source order again. `thead th`, not a bare `th` (crisp#829): #779 made the primary column's BODY cell a real `<th scope="row">` for a11y, and a bare
51
+ in the whole product and making every interactive column's button
52
+ double it to 24px. Scoping through `.crisp-table` adds a second
53
+ class to the selector (0,2,1 beats 0,1,1) so this can never lose a
54
+ specificity tie to source order again.
55
+ `thead th`, not a bare `th` (crisp#829): #779 made the primary
56
+ column's BODY cell a real `<th scope="row">` for a11y, and a bare
45
57
  `th` here matches that one too, zeroing an inset nothing else
46
58
  supplies -- the primary column's text sat flush left, one column's
47
59
  cell no longer matching its own header or any sibling `<td>`. Only
48
- the header row's own `<th>` needs zero padding (its sort BUTTON owns the inset); the body row's `<th scope="row">` has no button,
60
+ the header row's own `<th>` needs zero padding (its sort BUTTON
61
+ owns the inset); the body row's `<th scope="row">` has no button,
49
62
  and needs the SAME base 12px (table.recipe.ts, #465) every other
50
63
  cell already gets. */
51
64
  & .crisp-table thead th { padding: 0; }
52
- /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger): Menu wraps its trigger in a flex div that sizes to its content, so a long header grew past the column instead of filling it -- with nothing left to squeeze, the label's own ellipsis CSS (below) had no properly
53
- -sized box to truncate against, and the th's own overflow hard-clipped it with no "...". Fill the wrapper, scoped to th so Menu's default
65
+ /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger):
66
+ Menu wraps its trigger in a flex div that sizes to its content, so a
67
+ long header grew past the column instead of filling it -- with nothing
68
+ left to squeeze, the label's own ellipsis CSS (below) had no properly
69
+ -sized box to truncate against, and the th's own overflow hard-clipped
70
+ it with no "...". Fill the wrapper, scoped to th so Menu's default
54
71
  (correct everywhere else) is untouched. */
55
72
  & th .crisp-menu { width: 100%; height: 100%; }
56
73
  & .crisp-datatable-th-inner { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; }
57
74
  & .crisp-datatable-th-inner > svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
58
75
  /* crisp/#605 follow-up: a header with neither a Menu nor a sort button
59
- (the primary/entity column's now-inert path, or any column with no columnMenu/sortable at all) renders .crisp-datatable-th-inner as a DIRECT child of th instead of nested inside a button -- and loses the button's own 12px inset, drifting left of the td's 12px base inset. Measured live on Attio's own Companies grid: the header label
60
- and the cell's own text land at the exact same x (335px from the viewport, both 40px from the column's left edge) via two different
76
+ (the primary/entity column's now-inert path, or any column with no
77
+ columnMenu/sortable at all) renders .crisp-datatable-th-inner as a
78
+ DIRECT child of th instead of nested inside a button -- and loses
79
+ the button's own 12px inset, drifting left of the td's 12px base
80
+ inset. Measured live on Attio's own Companies grid: the header label
81
+ and the cell's own text land at the exact same x (335px from the
82
+ viewport, both 40px from the column's left edge) via two different
61
83
  padding+icon+gap combinations -- Attio does not leave the inert
62
84
  primary header flush left either. The `>` combinator targets only
63
- the button-less case: Menu's own wrapper (`.crisp-menu`) and the plain sort `button` both put >=1 more level between `th` and this span, so their own 12px (already on the button) isn't doubled. */
85
+ the button-less case: Menu's own wrapper (`.crisp-menu`) and the
86
+ plain sort `button` both put >=1 more level between `th` and this
87
+ span, so their own 12px (already on the button) isn't doubled. */
64
88
  & th > .crisp-datatable-th-inner { box-sizing: border-box; padding: 0 var(--crisp-space-3); }
65
- /* The summary cell has NO padding of its own -- the calculation trigger
89
+ /* The summary cell has NO padding of its own -- the calculation trigger
66
90
  owns the whole 246x37 area so a click anywhere in the cell opens the
67
91
  menu. Zero the cell's inset and let ColumnCalc supply the 12px. */
68
92
  & tfoot td { padding: 0; }
69
- /* Group heading row -- a view is grouped under a subtle label with the bucket's count beside it. */
70
- & .crisp-datatable-viewsettings { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s, color .12s; }
93
+ /* Group heading row -- a view is grouped under a subtle label with the
94
+ bucket's count beside it. */
95
+ & .crisp-datatable-viewsettings { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s, color .12s; }
71
96
  & .crisp-datatable-viewsettings:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
72
97
  & .crisp-datatable-group-label { 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); }
73
- & .crisp-datatable-group { height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-3); background: var(--crisp-bg-subtle); border-bottom: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
98
+ & .crisp-datatable-group { height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-3); background: var(--crisp-bg-subtle); border-bottom: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
74
99
  & .crisp-datatable-group-count { margin-inline-start: var(--crisp-space-1-5); color: var(--crisp-fg-muted); font-variant-numeric: tabular-nums; }
75
100
  & .crisp-datatable-th-rename { width: 100%; min-width: 0; height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-1); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); outline: none; }
76
101
  & .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
77
- & .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
78
- /* #465: 12px per the comment above (and Attio's measured header cell padding: 0 12px) -- was still c.space["2"] (8px), drifted from its own documented value. */
102
+ /* #719: overflow: clip, not hidden, on every truncation rule below --
103
+ hidden still honours a programmatic scrollLeft assignment (found via
104
+ a screenshot tool that pans anything with scrollWidth > clientWidth),
105
+ blanking or fragmenting the visible text even though real user
106
+ scrolling was already blocked. clip renders the ellipsis identically
107
+ and leaves no scroll position to move. */
108
+ & .crisp-datatable-th-label { overflow: clip; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
109
+ /* #465: 12px per the comment above (and Attio's measured header cell
110
+ padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
111
+ own documented value. */
79
112
  & .crisp-datatable-sort { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
80
113
  /* A right-aligned column's header keeps its field-icon + label packed at
81
114
  the START, matching its own values only in that the VALUES right-align
82
115
  -- Attio's own right-aligned header (measured live: "Twitter follower
83
- count") keeps the icon+label left and only the trailing column-menu icon sits pinned to the right edge, independent of the label's width
84
- (#406, reversing #393's own justify-content: flex-end here). The chevron's own margin-inline-start: auto below already pins IT to the
85
- end of a flex-start row with no override needed -- that's what pins the trailing icon on Attio too. */
116
+ count") keeps the icon+label left and only the trailing column-menu
117
+ icon sits pinned to the right edge, independent of the label's width
118
+ (#406, reversing #393's own justify-content: flex-end here). The
119
+ chevron's own margin-inline-start: auto below already pins IT to the
120
+ end of a flex-start row with no override needed -- that's what pins
121
+ the trailing icon on Attio too. */
86
122
  & .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); opacity: 0; transition: opacity .12s; }
87
123
  & .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
88
124
  & .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
@@ -90,17 +126,23 @@
90
126
  & th[data-sticky], & td[data-sticky] { position: sticky; z-index: 1; background: var(--crisp-bg-surface); }
91
127
  /* `thead th[data-sticky]`, not a bare `th[data-sticky]` (crisp#848, same
92
128
  bare-`th` family as #829/#833/#835): this z-index:3 was written for
93
- the HEADER's own sticky corner cell (sticky column x sticky header), meant to win over everything else. Unscoped, it also matched the primary column's BODY cell -- a real `<th scope="row" data-sticky>`
94
- since #779 -- giving every scrolled body row's own sticky cell a HIGHER z-index than the sticky FOOTER's (2), so it painted over the
129
+ the HEADER's own sticky corner cell (sticky column x sticky header),
130
+ meant to win over everything else. Unscoped, it also matched the
131
+ primary column's BODY cell -- a real `<th scope="row" data-sticky>`
132
+ since #779 -- giving every scrolled body row's own sticky cell a
133
+ HIGHER z-index than the sticky FOOTER's (2), so it painted over the
95
134
  footer instead of the footer showing through. Confirmed live on a
96
- real bounded-height, 25-row table (GRC's Risk register): scrolled partway, `elementFromPoint` at the footer's own on-screen cell
135
+ real bounded-height, 25-row table (GRC's Risk register): scrolled
136
+ partway, `elementFromPoint` at the footer's own on-screen cell
97
137
  resolved to a body row's `<th>`, not the footer's `<td>`, and that
98
138
  `<th>`'s computed z-index read 3. Scoped to `thead`, a body row's
99
139
  own sticky `<th>` now falls back to the base rule above (z-index 1,
100
- correct -- the same as a plain sticky `<td>`), and the footer's own explicit z-index below is unaffected either way. */
140
+ correct -- the same as a plain sticky `<td>`), and the footer's own
141
+ explicit z-index below is unaffected either way. */
101
142
  & thead th[data-sticky] { z-index: 3; }
102
143
  /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
103
- `tfoot td { z-index: 2 }`), which `td[data-sticky]`'s own higher-specificity `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
144
+ `tfoot td { z-index: 2 }`), which `td[data-sticky]`'s own higher-specificity
145
+ `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
104
146
  & tfoot td[data-sticky] { z-index: 2; }
105
147
  & tbody tr[data-selected] td[data-sticky] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
106
148
  & tbody tr:hover td[data-sticky] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
@@ -120,7 +162,8 @@
120
162
  /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own
121
163
  already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%,
122
164
  composited OVER whatever row-state background the cell already has
123
- (hover, selected), which is why it's an ::after overlay and not a plain background. */
165
+ (hover, selected), which is why it's an ::after overlay and not a
166
+ plain background. */
124
167
  & td[data-enriched]::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: color-mix(in srgb, var(--crisp-bg-purple) 4%, transparent); }
125
168
  & .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; vertical-align: middle; }
126
169
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
@@ -131,40 +174,56 @@
131
174
  & .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
132
175
  & .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
133
176
  /* The rejection's message, beside the cell's own content -- a sibling in
134
- the TableCell, never nested inside a column's own render output, so a hover-only wrapper (a Tooltip, say) around a custom render can't hide
177
+ the TableCell, never nested inside a column's own render output, so a
178
+ hover-only wrapper (a Tooltip, say) around a custom render can't hide
135
179
  it. Block-level: the row grows to fit it rather than floating over
136
180
  the row below, so it can never end up clipped by the scroll
137
- container's overflow the way a position:fixed escape would need guarding against. */
181
+ container's overflow the way a position:fixed escape would need
182
+ guarding against. */
138
183
  & .crisp-datatable-edit-error { display: block; padding: var(--crisp-space-0-5) 0 0; font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
139
- /* editor: "select"/"switch" -- always live, no separate edit mode, so the cell IS the picker: same chromeless-fill treatment as RecordsTable's own --picker cells (measured there already; DataTable
184
+ /* editor: "select"/"switch" -- always live, no separate edit mode, so
185
+ the cell IS the picker: same chromeless-fill treatment as
186
+ RecordsTable's own --picker cells (measured there already; DataTable
140
187
  shares RecordsTable's grid, Golden Rule 0). */
141
188
  & .crisp-datatable-td--picker { padding: 0; }
142
189
  & .crisp-datatable-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
143
190
  & .crisp-datatable-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; height: 100%; min-height: 0; border-radius: 0; box-shadow: none; padding: 0; }
144
191
  & .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
145
- /* reference picker: a leading 16px avatar per option, plus a trailing "Create X" row when the consumer supplies one (#449) -- same measured values as RecordsTable's own .crisp-recordstable-refoption*. */
146
- & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
192
+ /* reference picker: a leading 16px avatar per option, plus a trailing
193
+ "Create X" row when the consumer supplies one (#449) -- same measured
194
+ values as RecordsTable's own .crisp-recordstable-refoption*. */
195
+ & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: clip; text-overflow: ellipsis; }
147
196
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
148
197
  & .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
149
198
  & .crisp-datatable-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; color: var(--crisp-fg-muted); font-size: var(--crisp-text-lg); }
150
199
  /* #826 -- the full-body loading state (a filter/sort re-query, or a
151
- fresh page boot), measured live on Attio's Companies grid on both triggers: icon+text centred in the row-body area, 10px gap, 14px/500 weight/20px line-height, both icon and text in the SAME muted rgba(0,0,0,0.63) -- the same color-mix figure this file already uses for other muted text (e.g. -phone-dial). Distinct from the cold in-place row load (blank, no indicator) and the warm-navigation per-cell shimmer (#599) -- neither of those swaps the whole body. */
200
+ fresh page boot), measured live on Attio's Companies grid on both
201
+ triggers: icon+text centred in the row-body area, 10px gap, 14px/500
202
+ weight/20px line-height, both icon and text in the SAME muted
203
+ rgba(0,0,0,0.63) -- the same color-mix figure this file already uses
204
+ for other muted text (e.g. -phone-dial). Distinct from the cold
205
+ in-place row load (blank, no indicator) and the warm-navigation
206
+ per-cell shimmer (#599) -- neither of those swaps the whole body. */
152
207
  & .crisp-datatable-loading { display: flex; align-items: center; justify-content: center; gap: 10px; padding: var(--crisp-space-8) var(--crisp-space-3); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
153
208
  & .crisp-datatable-loading .crisp-spinner { border-top-color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
154
- /* #579: click-to-expand overlay for a truncated cell -- a read-only, full-value card, settled live as click (not Attio's usual
155
- hover-expand) and measured position: absolute, unlike this file's floating chrome (Menu/Select popovers) which portals and escapes the scroll container. Deliberately NOT the edit-error pattern above (grow the row, block-level, never clipped): a virtualized grid assumes a fixed row height for its scroll math (owned by the grid core, not this cell), so growing a row here would fight it. Anchored to the SAME column width the cell itself renders at (measured: Attio's own overlay came out only slightly wider than the
156
- triggering cell's clientWidth -- the overshoot is this padding), wrapping onto as many lines as the value needs; it CAN be clipped by `.crisp-table-scroll`'s own `overflow: auto` if it grows past the
209
+ /* #579: click-to-expand overlay for a truncated cell -- a read-only,
210
+ full-value card, settled live as click (not Attio's usual
211
+ hover-expand) and measured position: absolute, unlike this file's
212
+ floating chrome (Menu/Select popovers) which portals and escapes the
213
+ scroll container. Deliberately NOT the edit-error pattern above
214
+ (grow the row, block-level, never clipped): a virtualized grid
215
+ assumes a fixed row height for its scroll math (owned by the grid
216
+ core, not this cell), so growing a row here would fight it. Anchored
217
+ to the SAME column width the cell itself renders at (measured:
218
+ Attio's own overlay came out only slightly wider than the
219
+ triggering cell's clientWidth -- the overshoot is this padding),
220
+ wrapping onto as many lines as the value needs; it CAN be clipped by
221
+ `.crisp-table-scroll`'s own `overflow: auto` if it grows past the
157
222
  visible viewport, same as the edit-error comment above already
158
223
  notes for any position:absolute escape -- unmeasured against Attio
159
224
  for this exact case, flagged rather than guessed. */
160
225
  & td[data-cell-expanded] { overflow: visible; z-index: 7; }
161
- & .crisp-datatable-cell-overlay {
162
- position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box;
163
- padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface);
164
- border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px;
165
- font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
166
- color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default;
167
- }
226
+ & .crisp-datatable-cell-overlay { position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box; padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface); border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default; }
168
227
  /* fixed layout so column widths (and resize) are exact — like RecordsTable */
169
228
  & table { table-layout: fixed; }
170
229
  /* read-only/computed column marker (#596) -- sits just inside the
@@ -176,42 +235,70 @@
176
235
  & .crisp-datatable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
177
236
  & .crisp-datatable-resize::after { content: ""; position: absolute; top: 0; inset-inline-start: 3px; width: 2px; height: 100%; background: var(--crisp-bg-brand-solid); opacity: 0; transition: opacity .12s; }
178
237
  & .crisp-datatable-resize:hover::after, & .crisp-datatable-resize[data-resizing]::after { opacity: 1; }
179
- /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's own grid (the issue's own comment): the dragged header keeps full
238
+ /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's
239
+ own grid (the issue's own comment): the dragged header keeps full
180
240
  opacity and the plain `pointer` cursor throughout (Attio draws no
181
241
  ghost and no "grabbing" cursor of its own), so this adds nothing to
182
242
  `[data-dragging]` itself -- the ONLY new paint is the drop-indicator
183
- line, a 3px bar in the same measured brand blue the resize handle's own hover state (above) already uses, positioned on whichever edge of the hovered header the pointer is closer to. */
243
+ line, a 3px bar in the same measured brand blue the resize handle's
244
+ own hover state (above) already uses, positioned on whichever edge of
245
+ the hovered header the pointer is closer to. */
184
246
  & th[data-dragging] { cursor: pointer; }
185
247
  & th[data-drop-before]::before, & th[data-drop-after]::after { content: ""; position: absolute; top: 0; width: 3px; height: 100%; background: var(--crisp-bg-brand-solid); z-index: 6; pointer-events: none; }
186
248
  & th[data-drop-before]::before { inset-inline-start: -1.5px; }
187
249
  & th[data-drop-after]::after { inset-inline-end: -1.5px; }
188
250
  /* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
189
251
  & .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
190
- /* crisp/#601 supersession follow-up -- the same flex-row shape .crisp-datatable-entity gives its own avatar+name+actions, but structural only (no entity-specific colour/weight): lets onOpenComments/onOpenPreview compose alongside ANY column's own
252
+ /* crisp/#601 supersession follow-up -- the same flex-row shape
253
+ .crisp-datatable-entity gives its own avatar+name+actions, but
254
+ structural only (no entity-specific colour/weight): lets
255
+ onOpenComments/onOpenPreview compose alongside ANY column's own
191
256
  content (a custom render, or a non-entity built-in type), not just
192
257
  type: "entity"'s own inline rendering. */
193
258
  & .crisp-datatable-cell-actions-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; width: 100%; }
194
- /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text, plain hover vs rest) that Attio's underline is unconditional, not a
259
+ /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text,
260
+ plain hover vs rest) that Attio's underline is unconditional, not a
195
261
  hover reveal -- identical at rest and on hover: text-decoration-color
196
- is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text colour. The previous :hover-gated, full-contrast rule here was itself unmeasured and wrong in two ways at once. */ /* crisp#851 -- Attio's own entity/record names are measured at
197
- font-weight 500, distinct from a plain-text primary column's 400 (table.recipe.ts's own `tbody th` default). This was never its own
262
+ is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text
263
+ colour. The previous :hover-gated, full-contrast rule here was itself
264
+ unmeasured and wrong in two ways at once. */ /* crisp#851 -- Attio's own entity/record names are measured at
265
+ font-weight 500, distinct from a plain-text primary column's 400
266
+ (table.recipe.ts's own `tbody th` default). This was never its own
198
267
  explicit style -- it silently rode on a since-fixed header-rule leak
199
268
  (#833/#835/#848) that happened to apply the same 500 to every
200
269
  primary-column body cell regardless of type. Made explicit here so
201
270
  fixing that leak doesn't also (wrongly) drop entity names to 400. */
202
- & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--crisp-weight-medium); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); }
203
- & button.crisp-datatable-entity-name--link { margin: 0; padding: 0; border: none; background: none; font: inherit; color: inherit; letter-spacing: inherit; text-align: start; cursor: pointer; max-width: 100%; }
204
- /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary column, revealed on ROW hover (the issue's own title says "row
271
+ & .crisp-datatable-entity-name { overflow: clip; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--crisp-weight-medium); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); }
272
+ /* #893: shared by the button (onEntityClick, a different record) and
273
+ the anchor (href, the row's own record) -- was button-only, which
274
+ silently left a real <a> unstyled the day #893 added one. */
275
+ & .crisp-datatable-entity-name--link { margin: 0; padding: 0; border: none; background: none; font: inherit; color: inherit; letter-spacing: inherit; text-align: start; cursor: pointer; max-width: 100%; }
276
+ /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary
277
+ column, revealed on ROW hover (the issue's own title says "row
205
278
  hover", and a live check confirmed hovering well past the name --
206
- not just the icons' own hit target -- still reveals them) or keyboard focus. Measured live: border-radius 6px, icon colour rgba(0,0,0,0.55) at rest, darkening to the full text colour with a rgba(0,0,0,0.04) background on the icon's OWN hover -- both
279
+ not just the icons' own hit target -- still reveals them) or
280
+ keyboard focus. Measured live: border-radius 6px, icon colour
281
+ rgba(0,0,0,0.55) at rest, darkening to the full text colour with a
282
+ rgba(0,0,0,0.04) background on the icon's OWN hover -- both
207
283
  expressed as color-mix against fgPrimary so they stay correct in
208
284
  dark mode instead of a literal black that would invert. */
209
285
  & .crisp-datatable-entity-actions { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
210
286
  & tr:hover .crisp-datatable-entity-actions, & .crisp-datatable-entity-actions:focus-within { opacity: 1; }
211
287
  & .crisp-datatable-entity-action { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
212
288
  & .crisp-datatable-entity-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
213
- & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: var(--crisp-radius-sm); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
214
- & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); } & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); } & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); } & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); } & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); } & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
289
+ /* #892: the default shape's radius was a stray literal (radius.sm, 6px)
290
+ -- Attio's own real company/object avatar measures 30%, which is
291
+ Avatar's own already-shipped shape="rounded" value, not a new number.
292
+ shape="circle" reuses Avatar's own shape="circle" value (radius.full)
293
+ the same way, measured live on a real Attio person avatar. */
294
+ & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: 30%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
295
+ & .crisp-datatable-avatar[data-shape="circle"] { border-radius: var(--crisp-radius-full); }
296
+ & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
297
+ & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
298
+ & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
299
+ & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
300
+ & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
301
+ & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
215
302
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
216
303
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
217
304
  & .crisp-datatable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
@@ -224,13 +311,19 @@
224
311
  & .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
225
312
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
226
313
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
227
- /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as TasksTable's own overdue due-date (crisp#462), plain text colour, no
314
+ /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
315
+ TasksTable's own overdue due-date (crisp#462), plain text colour, no
228
316
  fill/weight change. */
229
317
  & .crisp-datatable-danger { color: var(--crisp-fg-danger); }
230
- /* url/email cell types (#654) -- same measured values as RecordsTable's own .crisp-recordstable-link, which this mirrors: no underline at rest, underline on hover, own-colour text rather than a browser-blue link. */
231
- & .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
318
+ /* url/email cell types (#654) -- same measured values as RecordsTable's
319
+ own .crisp-recordstable-link, which this mirrors: no underline at
320
+ rest, underline on hover, own-colour text rather than a browser-blue
321
+ link. */
322
+ & .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: clip; text-overflow: ellipsis; }
232
323
  & .crisp-datatable-link:hover { text-decoration: underline; }
233
- /* phone cell type/editor (#765) -- measured live: the dial-code prefix is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text value already measured elsewhere in this file. */
324
+ /* phone cell type/editor (#765) -- measured live: the dial-code prefix
325
+ is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text
326
+ value already measured elsewhere in this file. */
234
327
  & .crisp-datatable-phone { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
235
328
  & .crisp-datatable-phone-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
236
329
  & .crisp-datatable-phone-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
@@ -260,7 +353,8 @@
260
353
  value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
261
354
  the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
262
355
  DataPhoneEditor-shaped row -- that blank row is the only "add
263
- another" affordance, there's no separate button for it. The copy/delete pair reuses .crisp-datatable-entity-action's own
356
+ another" affordance, there's no separate button for it. The
357
+ copy/delete pair reuses .crisp-datatable-entity-action's own
264
358
  hover-reveal chrome (not independently re-measured as its own hit
265
359
  target this pass, only the 16px icon glyphs were). */
266
360
  & .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
@@ -268,8 +362,7 @@
268
362
  & .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
269
363
  & .crisp-datatable-phone-multi-grip { color: var(--crisp-fg-muted); flex-shrink: 0; cursor: grab; }
270
364
  & .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
271
- & .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
272
- & .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
365
+ & .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions, & .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
273
366
  & .crisp-datatable-phone-multi-action { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
274
367
  & .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
275
368
  /* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
@@ -278,7 +371,10 @@
278
371
  18px pitch (a 2px gap), an empty star at rgba(0,0,0,0.1), a
279
372
  committed/filled star at brand600 (--crisp-bg-brand-solid, an exact
280
373
  match to the measured #266df0), and a distinct hover-preview colour
281
- -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed (#1f5fd6); kept as its own literal rather than forced onto that token, since the two were measured stable and different. The read-only svg and the editable button's own inner svg both carry the
374
+ -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed
375
+ (#1f5fd6); kept as its own literal rather than forced onto that token,
376
+ since the two were measured stable and different. The read-only svg
377
+ and the editable button's own inner svg both carry the
282
378
  crisp-datatable-rating-star class on the svg itself (not the
283
379
  button), so one set of fill rules covers both -- the button only
284
380
  adds its own reset + cursor via a separate -btn class. */
@@ -289,9 +385,13 @@
289
385
  & .crisp-datatable-rating-star[data-hovering] path { fill: #215bc4; }
290
386
  & .crisp-datatable-rating-star-btn { display: inline-flex; padding: 0; margin: 0; border: none; background: none; font-family: inherit; cursor: pointer; }
291
387
  /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
292
- a leading 20px grip column, a 16px grip icon, and the row's own hairline doubling as the drop indicator (it tints toward the brand colour as the active drop target). The affordance + geometry are measured; the drag MECHANISM is crisp's existing house idiom -- HTML5
388
+ a leading 20px grip column, a 16px grip icon, and the row's own
389
+ hairline doubling as the drop indicator (it tints toward the brand
390
+ colour as the active drop target). The affordance + geometry are
391
+ measured; the drag MECHANISM is crisp's existing house idiom -- HTML5
293
392
  draggable + dataTransfer, the same one column reorder already uses
294
- (RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
393
+ (RecordsTable's reorderColumn) -- not a proprietary
394
+ pointer-events implementation. */
295
395
  & .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
296
396
  & .crisp-datatable-grip { display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%; margin: 0; border: none; border-radius: 0; background: none; font-family: inherit; color: var(--crisp-fg-muted); cursor: grab; }
297
397
  & .crisp-datatable-grip:active { cursor: grabbing; }
@@ -16,36 +16,22 @@
16
16
  measurement -- accessibility wins over the seed reference.
17
17
  neutralMuted clears it, 6.62:1 light / 14.71:1 dark (dark was
18
18
  already fine at 9.43:1). */
19
- & .crisp-datepicker-weekday {
20
- display: flex; align-items: center; justify-content: center;
21
- height: var(--crisp-size-calendarCell); color: var(--crisp-fg-neutral-muted);
22
- font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium);
23
- }
24
-
19
+ & .crisp-datepicker-weekday { display: flex; align-items: center; justify-content: center; height: var(--crisp-size-calendarCell); color: var(--crisp-fg-neutral-muted); font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium); }
25
20
  & .crisp-datepicker-months { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; min-width: 0; }
26
21
  & .crisp-datepicker-months[data-wheel-open] { visibility: hidden; }
27
22
  & .crisp-datepicker-month { min-width: 0; }
28
- & .crisp-datepicker-month-label {
29
- display: flex; align-items: center; height: 24px;
30
- color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px;
31
- font-weight: var(--crisp-weight-medium);
32
- }
33
-
23
+ & .crisp-datepicker-month-label { display: flex; align-items: center; height: 24px; color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); }
34
24
  & .crisp-datepicker-grid { display: grid; min-width: 0; }
35
25
  & .crisp-datepicker-week-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
36
-
37
- & .crisp-datepicker-cell {
38
- position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell);
39
- isolation: isolate;
40
- }
26
+ & .crisp-datepicker-cell { position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell); isolation: isolate; }
41
27
  & .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
42
-
43
28
  /* The bridge joining days inside a completed range.
44
29
  The endpoints do NOT carry data-in-range -- only the interior days do --
45
30
  so these are three independent rules, not a base plus two modifiers. A
46
31
  modifier that sets only inset-inline-start never generates a pseudo
47
32
  element on an endpoint, which leaves a half-cell of white between the
48
- endpoint's pill and the run of grey beside it. Each rule declares its own content. */
33
+ endpoint's pill and the run of grey beside it. Each rule declares its
34
+ own content. */
49
35
  & .crisp-datepicker-cell[data-range-complete][data-in-range]::before { content: ""; position: absolute; z-index: -1; inset-inline: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
50
36
  & .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 50%; inset-inline-end: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
51
37
  & .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 0; inset-inline-end: 50%; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
@@ -61,33 +47,19 @@
61
47
  & .crisp-datepicker-date:disabled { color: var(--crisp-fg-disabled); cursor: not-allowed; }
62
48
  & .crisp-datepicker-date:focus-visible::after { content: ""; position: absolute; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); transform: translateX(-50%); border-radius: var(--crisp-radius-md); outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
63
49
  & .crisp-datepicker-date-content { position: relative; z-index: 1; display: flex; width: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box; flex-direction: column; align-items: center; gap: 3px; padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); font-variant-numeric: tabular-nums; }
64
- /* The numeral is its own 42px flex box so it centres on the 42px pill. Without this the content column top-aligns the glyph and the pill reads as offset — the cell is 48px, the pill 42px inset 3px, and only a 42px-tall numeral box shares the pill's centre line. */
65
- & .crisp-datepicker-date-content > [data-date-picker-day] {
66
- display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0;
67
- align-items: center; justify-content: center;
68
- }
69
-
50
+ /* The numeral is its own 42px flex box so it centres on the 42px pill.
51
+ Without this the content column top-aligns the glyph and the pill reads
52
+ as offset — the cell is 48px, the pill 42px inset 3px, and only a
53
+ 42px-tall numeral box shares the pill's centre line. */
54
+ & .crisp-datepicker-date-content > [data-date-picker-day] { display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0; align-items: center; justify-content: center; }
70
55
  /* the year/month wheel, laid over the grid it replaces */
71
- & .crisp-datepicker-wheel-container {
72
- position: absolute; z-index: 1; inset-inline: 0;
73
- top: var(--crisp-size-menuRow); bottom: 0;
74
- display: flex; align-items: center; overflow: hidden;
75
- background: var(--crisp-bg-surface);
76
- }
56
+ & .crisp-datepicker-wheel-container { position: absolute; z-index: 1; inset-inline: 0; top: var(--crisp-size-menuRow); bottom: 0; display: flex; align-items: center; overflow: hidden; background: var(--crisp-bg-surface); }
77
57
  & .crisp-datepicker-wheel { width: 100%; }
78
-
79
- & .crisp-datepicker-continuous-scroll {
80
- height: 100%; min-height: 0; overflow-y: auto;
81
- overscroll-behavior: contain; scrollbar-width: none;
82
- }
58
+ & .crisp-datepicker-continuous-scroll { height: 100%; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none; }
83
59
  & .crisp-datepicker-continuous-scroll::-webkit-scrollbar { display: none; }
84
60
  & .crisp-datepicker-continuous-content { display: flex; flex-direction: column; }
85
-
86
61
  /* announced, never drawn */
87
- & .crisp-datepicker-live-region {
88
- position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
89
- overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
90
- }
62
+ & .crisp-datepicker-live-region { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
91
63
  }
92
64
  .crisp-datepicker--visibleRange_month {
93
65