@crisp-ui-kit/crisp 0.5.0 → 0.7.1

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 (472) hide show
  1. package/README.md +14 -0
  2. package/dist/accordion.css +9 -1
  3. package/dist/appshell.css +12 -4
  4. package/dist/authshell.css +57 -0
  5. package/dist/authshell.d.ts +7 -0
  6. package/dist/avatargroup.css +2 -2
  7. package/dist/badge.css +4 -4
  8. package/dist/barchart.css +13 -5
  9. package/dist/board.css +9 -1
  10. package/dist/bulkbar.css +11 -3
  11. package/dist/button.css +15 -1
  12. package/dist/button.d.ts +2 -1
  13. package/dist/card.css +8 -0
  14. package/dist/checkbox.css +17 -1
  15. package/dist/cjs/components/accordion/accordion.js +69 -0
  16. package/dist/cjs/components/accordion/accordion.recipe.js +26 -0
  17. package/dist/cjs/components/accordion/index.js +7 -0
  18. package/dist/cjs/components/avatar/avatar.js +44 -0
  19. package/dist/cjs/components/avatar/avatar.recipe.js +42 -0
  20. package/dist/cjs/components/avatar/index.js +7 -0
  21. package/dist/cjs/components/avatargroup/avatargroup.js +52 -0
  22. package/dist/cjs/components/avatargroup/avatargroup.recipe.js +18 -0
  23. package/dist/cjs/components/avatargroup/index.js +7 -0
  24. package/dist/cjs/components/badge/badge.js +44 -0
  25. package/dist/cjs/components/badge/badge.recipe.js +68 -0
  26. package/dist/cjs/components/badge/index.js +7 -0
  27. package/dist/cjs/components/barchart/barchart.js +45 -0
  28. package/dist/cjs/components/barchart/barchart.recipe.js +34 -0
  29. package/dist/cjs/components/barchart/index.js +7 -0
  30. package/dist/cjs/components/breadcrumb/breadcrumb.js +53 -0
  31. package/dist/cjs/components/breadcrumb/breadcrumb.recipe.js +21 -0
  32. package/dist/cjs/components/breadcrumb/index.js +7 -0
  33. package/dist/cjs/components/bulkbar/bulkbar.js +53 -0
  34. package/dist/cjs/components/bulkbar/bulkbar.recipe.js +31 -0
  35. package/dist/cjs/components/bulkbar/index.js +8 -0
  36. package/dist/cjs/components/button/button.js +62 -0
  37. package/dist/cjs/components/button/button.recipe.js +58 -0
  38. package/dist/cjs/components/button/index.js +7 -0
  39. package/dist/cjs/components/callout/callout.js +25 -0
  40. package/dist/cjs/components/callout/callout.recipe.js +29 -0
  41. package/dist/cjs/components/callout/index.js +7 -0
  42. package/dist/cjs/components/card/card.js +54 -0
  43. package/dist/cjs/components/card/card.recipe.js +31 -0
  44. package/dist/cjs/components/card/index.js +10 -0
  45. package/dist/cjs/components/checkbox/checkbox.js +56 -0
  46. package/dist/cjs/components/checkbox/checkbox.recipe.js +34 -0
  47. package/dist/cjs/components/checkbox/index.js +7 -0
  48. package/dist/cjs/components/columncalc/columncalc.js +139 -0
  49. package/dist/cjs/components/columncalc/columncalc.recipe.js +33 -0
  50. package/dist/cjs/components/columncalc/index.js +10 -0
  51. package/dist/cjs/components/contextmenu/contextmenu.js +78 -0
  52. package/dist/cjs/components/contextmenu/contextmenu.recipe.js +24 -0
  53. package/dist/cjs/components/contextmenu/index.js +7 -0
  54. package/dist/cjs/components/datatable/datatable.js +508 -0
  55. package/dist/cjs/components/datatable/datatable.recipe.js +113 -0
  56. package/dist/cjs/components/datatable/index.js +7 -0
  57. package/dist/cjs/components/datepicker/datepicker.js +153 -0
  58. package/dist/cjs/components/datepicker/datepicker.recipe.js +190 -0
  59. package/dist/cjs/components/datepicker/index.js +19 -0
  60. package/dist/cjs/components/datepickerfield/datepickerfield.js +85 -0
  61. package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +44 -0
  62. package/dist/cjs/components/datepickerfield/index.js +7 -0
  63. package/dist/cjs/components/dialog/dialog.js +86 -0
  64. package/dist/cjs/components/dialog/dialog.recipe.js +35 -0
  65. package/dist/cjs/components/dialog/index.js +9 -0
  66. package/dist/cjs/components/divider/divider.js +10 -0
  67. package/dist/cjs/components/divider/divider.recipe.js +17 -0
  68. package/dist/cjs/components/divider/index.js +7 -0
  69. package/dist/cjs/components/donutchart/donutchart.js +41 -0
  70. package/dist/cjs/components/donutchart/donutchart.recipe.js +30 -0
  71. package/dist/cjs/components/donutchart/index.js +7 -0
  72. package/dist/cjs/components/drawer/drawer.js +80 -0
  73. package/dist/cjs/components/drawer/drawer.recipe.js +30 -0
  74. package/dist/cjs/components/drawer/index.js +9 -0
  75. package/dist/cjs/components/emptystate/emptystate.js +10 -0
  76. package/dist/cjs/components/emptystate/emptystate.recipe.js +34 -0
  77. package/dist/cjs/components/emptystate/index.js +7 -0
  78. package/dist/cjs/components/formfield/formfield.js +50 -0
  79. package/dist/cjs/components/formfield/formfield.recipe.js +22 -0
  80. package/dist/cjs/components/formfield/index.js +7 -0
  81. package/dist/cjs/components/input/index.js +7 -0
  82. package/dist/cjs/components/input/input.js +47 -0
  83. package/dist/cjs/components/input/input.recipe.js +59 -0
  84. package/dist/cjs/components/kbd/index.js +7 -0
  85. package/dist/cjs/components/kbd/kbd.js +44 -0
  86. package/dist/cjs/components/kbd/kbd.recipe.js +23 -0
  87. package/dist/cjs/components/linechart/index.js +7 -0
  88. package/dist/cjs/components/linechart/linechart.js +57 -0
  89. package/dist/cjs/components/linechart/linechart.recipe.js +35 -0
  90. package/dist/cjs/components/menu/index.js +14 -0
  91. package/dist/cjs/components/menu/menu-keyboard.js +117 -0
  92. package/dist/cjs/components/menu/menu.js +144 -0
  93. package/dist/cjs/components/menu/menu.recipe.js +54 -0
  94. package/dist/cjs/components/multiselect/index.js +7 -0
  95. package/dist/cjs/components/multiselect/multiselect.js +161 -0
  96. package/dist/cjs/components/multiselect/multiselect.recipe.js +71 -0
  97. package/dist/cjs/components/popover/index.js +7 -0
  98. package/dist/cjs/components/popover/popover.js +106 -0
  99. package/dist/cjs/components/popover/popover.recipe.js +21 -0
  100. package/dist/cjs/components/progress/index.js +7 -0
  101. package/dist/cjs/components/progress/progress.js +26 -0
  102. package/dist/cjs/components/progress/progress.recipe.js +20 -0
  103. package/dist/cjs/components/radiogroup/index.js +8 -0
  104. package/dist/cjs/components/radiogroup/radiogroup.js +83 -0
  105. package/dist/cjs/components/radiogroup/radiogroup.recipe.js +40 -0
  106. package/dist/cjs/components/resizablepanel/index.js +12 -0
  107. package/dist/cjs/components/resizablepanel/resizablepanel.js +176 -0
  108. package/dist/cjs/components/resizablepanel/resizablepanel.recipe.js +69 -0
  109. package/dist/cjs/components/segmented/index.js +7 -0
  110. package/dist/cjs/components/segmented/segmented.js +95 -0
  111. package/dist/cjs/components/segmented/segmented.recipe.js +23 -0
  112. package/dist/cjs/components/select/index.js +7 -0
  113. package/dist/cjs/components/select/select.js +180 -0
  114. package/dist/cjs/components/select/select.recipe.js +79 -0
  115. package/dist/cjs/components/skeleton/index.js +7 -0
  116. package/dist/cjs/components/skeleton/skeleton.js +13 -0
  117. package/dist/cjs/components/skeleton/skeleton.recipe.js +17 -0
  118. package/dist/cjs/components/spinner/index.js +7 -0
  119. package/dist/cjs/components/spinner/spinner.js +15 -0
  120. package/dist/cjs/components/spinner/spinner.recipe.js +16 -0
  121. package/dist/cjs/components/stagebar/index.js +7 -0
  122. package/dist/cjs/components/stagebar/stagebar.js +26 -0
  123. package/dist/cjs/components/stagebar/stagebar.recipe.js +17 -0
  124. package/dist/cjs/components/statcard/index.js +7 -0
  125. package/dist/cjs/components/statcard/statcard.js +15 -0
  126. package/dist/cjs/components/statcard/statcard.recipe.js +37 -0
  127. package/dist/cjs/components/switch/index.js +7 -0
  128. package/dist/cjs/components/switch/switch.js +58 -0
  129. package/dist/cjs/components/switch/switch.recipe.js +29 -0
  130. package/dist/cjs/components/table/index.js +14 -0
  131. package/dist/cjs/components/table/table.js +66 -0
  132. package/dist/cjs/components/table/table.recipe.js +43 -0
  133. package/dist/cjs/components/tabletoolbar/index.js +20 -0
  134. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +84 -0
  135. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +237 -0
  136. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +26 -0
  137. package/dist/cjs/components/tabs/index.js +7 -0
  138. package/dist/cjs/components/tabs/tabs.js +106 -0
  139. package/dist/cjs/components/tabs/tabs.recipe.js +36 -0
  140. package/dist/cjs/components/textarea/index.js +7 -0
  141. package/dist/cjs/components/textarea/textarea.js +56 -0
  142. package/dist/cjs/components/textarea/textarea.recipe.js +46 -0
  143. package/dist/cjs/components/toast/index.js +8 -0
  144. package/dist/cjs/components/toast/toast.js +95 -0
  145. package/dist/cjs/components/toast/toast.recipe.js +31 -0
  146. package/dist/cjs/components/tooltip/index.js +7 -0
  147. package/dist/cjs/components/tooltip/tooltip.js +95 -0
  148. package/dist/cjs/components/tooltip/tooltip.recipe.js +16 -0
  149. package/dist/cjs/components/wheelpicker/index.js +7 -0
  150. package/dist/cjs/components/wheelpicker/wheelpicker.js +134 -0
  151. package/dist/cjs/components/wheelpicker/wheelpicker.recipe.js +95 -0
  152. package/dist/cjs/core/cx.js +8 -0
  153. package/dist/cjs/core/define.js +6 -0
  154. package/dist/cjs/core/index.js +11 -0
  155. package/dist/cjs/core/primitive.js +49 -0
  156. package/dist/cjs/core/resolve.js +44 -0
  157. package/dist/cjs/core/types.js +2 -0
  158. package/dist/cjs/index.js +92 -0
  159. package/dist/cjs/package.json +3 -0
  160. package/dist/cjs/product/appshell/appshell.js +104 -0
  161. package/dist/cjs/product/appshell/appshell.recipe.js +30 -0
  162. package/dist/cjs/product/appshell/index.js +7 -0
  163. package/dist/cjs/product/authshell/authshell.js +21 -0
  164. package/dist/cjs/product/authshell/authshell.recipe.js +67 -0
  165. package/dist/cjs/product/authshell/index.js +7 -0
  166. package/dist/cjs/product/board/board.js +78 -0
  167. package/dist/cjs/product/board/board.recipe.js +31 -0
  168. package/dist/cjs/product/board/index.js +7 -0
  169. package/dist/cjs/product/command/command.js +238 -0
  170. package/dist/cjs/product/command/command.recipe.js +47 -0
  171. package/dist/cjs/product/command/index.js +8 -0
  172. package/dist/cjs/product/entitycard/entitycard.js +14 -0
  173. package/dist/cjs/product/entitycard/entitycard.recipe.js +32 -0
  174. package/dist/cjs/product/entitycard/index.js +7 -0
  175. package/dist/cjs/product/flowcanvas/flowcanvas.js +121 -0
  176. package/dist/cjs/product/flowcanvas/flowcanvas.recipe.js +28 -0
  177. package/dist/cjs/product/flowcanvas/index.js +7 -0
  178. package/dist/cjs/product/inbox/inbox.js +16 -0
  179. package/dist/cjs/product/inbox/inbox.recipe.js +46 -0
  180. package/dist/cjs/product/inbox/index.js +7 -0
  181. package/dist/cjs/product/listpage/index.js +8 -0
  182. package/dist/cjs/product/listpage/listpage.js +25 -0
  183. package/dist/cjs/product/listpage/listpage.recipe.js +46 -0
  184. package/dist/cjs/product/navitem/index.js +5 -0
  185. package/dist/cjs/product/navitem/navitem.js +44 -0
  186. package/dist/cjs/product/navitem/navitem.recipe.js +36 -0
  187. package/dist/cjs/product/onboarding/index.js +7 -0
  188. package/dist/cjs/product/onboarding/onboarding.js +17 -0
  189. package/dist/cjs/product/onboarding/onboarding.recipe.js +44 -0
  190. package/dist/cjs/product/recordpage/index.js +7 -0
  191. package/dist/cjs/product/recordpage/recordpage.js +409 -0
  192. package/dist/cjs/product/recordpage/recordpage.recipe.js +162 -0
  193. package/dist/cjs/product/recordpanel/index.js +7 -0
  194. package/dist/cjs/product/recordpanel/recordpanel.js +83 -0
  195. package/dist/cjs/product/recordpanel/recordpanel.recipe.js +51 -0
  196. package/dist/cjs/product/recordstable/company-logos.js +29 -0
  197. package/dist/cjs/product/recordstable/index.js +10 -0
  198. package/dist/cjs/product/recordstable/recordstable.js +1092 -0
  199. package/dist/cjs/product/recordstable/recordstable.recipe.js +152 -0
  200. package/dist/cjs/product/recordstable/recordstable.tag.js +21 -0
  201. package/dist/cjs/product/richtext/index.js +7 -0
  202. package/dist/cjs/product/richtext/richtext.js +147 -0
  203. package/dist/cjs/product/richtext/richtext.recipe.js +43 -0
  204. package/dist/cjs/product/settingssection/index.js +7 -0
  205. package/dist/cjs/product/settingssection/settingssection.js +14 -0
  206. package/dist/cjs/product/settingssection/settingssection.recipe.js +23 -0
  207. package/dist/cjs/product/settingsshell/index.js +7 -0
  208. package/dist/cjs/product/settingsshell/settingsshell.js +59 -0
  209. package/dist/cjs/product/settingsshell/settingsshell.recipe.js +46 -0
  210. package/dist/cjs/product/sidebar/index.js +13 -0
  211. package/dist/cjs/product/sidebar/sidebar.js +148 -0
  212. package/dist/cjs/product/sidebar/sidebar.recipe.js +46 -0
  213. package/dist/cjs/product/tasklist/index.js +7 -0
  214. package/dist/cjs/product/tasklist/tasklist.js +19 -0
  215. package/dist/cjs/product/tasklist/tasklist.recipe.js +38 -0
  216. package/dist/cjs/product/trialbanner/index.js +7 -0
  217. package/dist/cjs/product/trialbanner/trialbanner.js +13 -0
  218. package/dist/cjs/product/trialbanner/trialbanner.recipe.js +32 -0
  219. package/dist/cjs/product/viewtoggle/index.js +7 -0
  220. package/dist/cjs/product/viewtoggle/viewtoggle.js +55 -0
  221. package/dist/cjs/product/viewtoggle/viewtoggle.recipe.js +27 -0
  222. package/dist/cjs/tokens/contract.js +21 -0
  223. package/dist/cjs/tokens/elevation.js +47 -0
  224. package/dist/cjs/tokens/index.js +18 -0
  225. package/dist/cjs/tokens/palette.js +63 -0
  226. package/dist/cjs/tokens/scale.js +70 -0
  227. package/dist/cjs/tokens/semantic.js +57 -0
  228. package/dist/columncalc.css +26 -0
  229. package/dist/columncalc.d.ts +7 -0
  230. package/dist/command.css +34 -1
  231. package/dist/contextmenu.css +10 -2
  232. package/dist/datatable.css +60 -14
  233. package/dist/datepicker.css +175 -19
  234. package/dist/datepicker.d.ts +2 -2
  235. package/dist/datepickerfield.css +27 -0
  236. package/dist/datepickerfield.d.ts +7 -0
  237. package/dist/dialog.css +10 -2
  238. package/dist/donutchart.css +8 -0
  239. package/dist/drawer.css +13 -5
  240. package/dist/entitycard.css +10 -2
  241. package/dist/esm/components/accordion/accordion.js +17 -16
  242. package/dist/esm/components/accordion/accordion.recipe.js +1 -1
  243. package/dist/esm/components/avatargroup/avatargroup.js +2 -2
  244. package/dist/esm/components/avatargroup/avatargroup.recipe.js +2 -2
  245. package/dist/esm/components/badge/badge.recipe.js +4 -4
  246. package/dist/esm/components/barchart/barchart.js +15 -2
  247. package/dist/esm/components/barchart/barchart.recipe.js +10 -5
  248. package/dist/esm/components/breadcrumb/breadcrumb.js +2 -2
  249. package/dist/esm/components/bulkbar/bulkbar.js +2 -2
  250. package/dist/esm/components/bulkbar/bulkbar.recipe.js +4 -4
  251. package/dist/esm/components/button/button.js +13 -2
  252. package/dist/esm/components/button/button.recipe.js +20 -4
  253. package/dist/esm/components/callout/callout.js +2 -2
  254. package/dist/esm/components/checkbox/checkbox.recipe.js +9 -1
  255. package/dist/esm/components/columncalc/columncalc.js +100 -0
  256. package/dist/esm/components/columncalc/columncalc.recipe.js +30 -0
  257. package/dist/esm/components/columncalc/index.js +1 -0
  258. package/dist/esm/components/contextmenu/contextmenu.recipe.js +4 -4
  259. package/dist/esm/components/datatable/datatable.js +276 -49
  260. package/dist/esm/components/datatable/datatable.recipe.js +54 -16
  261. package/dist/esm/components/datepicker/datepicker.js +104 -53
  262. package/dist/esm/components/datepicker/datepicker.recipe.js +180 -25
  263. package/dist/esm/components/datepicker/index.js +1 -1
  264. package/dist/esm/components/datepickerfield/datepickerfield.js +49 -0
  265. package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +41 -0
  266. package/dist/esm/components/datepickerfield/index.js +2 -0
  267. package/dist/esm/components/dialog/dialog.js +2 -2
  268. package/dist/esm/components/dialog/dialog.recipe.js +4 -4
  269. package/dist/esm/components/divider/divider.js +2 -2
  270. package/dist/esm/components/donutchart/donutchart.js +2 -2
  271. package/dist/esm/components/drawer/drawer.js +2 -2
  272. package/dist/esm/components/drawer/drawer.recipe.js +6 -6
  273. package/dist/esm/components/emptystate/emptystate.js +2 -2
  274. package/dist/esm/components/formfield/formfield.js +2 -2
  275. package/dist/esm/components/input/input.js +6 -3
  276. package/dist/esm/components/input/input.recipe.js +38 -5
  277. package/dist/esm/components/linechart/linechart.js +15 -2
  278. package/dist/esm/components/linechart/linechart.recipe.js +10 -5
  279. package/dist/esm/components/menu/index.js +1 -0
  280. package/dist/esm/components/menu/menu-keyboard.js +80 -0
  281. package/dist/esm/components/menu/menu.js +27 -60
  282. package/dist/esm/components/menu/menu.recipe.js +5 -5
  283. package/dist/esm/components/multiselect/multiselect.js +10 -3
  284. package/dist/esm/components/multiselect/multiselect.recipe.js +8 -8
  285. package/dist/esm/components/popover/popover.js +2 -2
  286. package/dist/esm/components/popover/popover.recipe.js +2 -2
  287. package/dist/esm/components/progress/progress.js +3 -3
  288. package/dist/esm/components/radiogroup/radiogroup.js +2 -2
  289. package/dist/esm/components/radiogroup/radiogroup.recipe.js +1 -1
  290. package/dist/esm/components/resizablepanel/index.js +2 -0
  291. package/dist/esm/components/resizablepanel/resizablepanel.js +135 -0
  292. package/dist/esm/components/resizablepanel/resizablepanel.recipe.js +66 -0
  293. package/dist/esm/components/segmented/segmented.js +2 -2
  294. package/dist/esm/components/select/select.js +2 -2
  295. package/dist/esm/components/select/select.recipe.js +6 -6
  296. package/dist/esm/components/skeleton/skeleton.js +5 -2
  297. package/dist/esm/components/spinner/spinner.js +3 -2
  298. package/dist/esm/components/stagebar/stagebar.js +2 -2
  299. package/dist/esm/components/statcard/statcard.js +2 -2
  300. package/dist/esm/components/table/table.recipe.js +6 -6
  301. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +3 -3
  302. package/dist/esm/components/tabletoolbar/tabletoolbar.js +21 -13
  303. package/dist/esm/components/tabs/tabs.js +2 -2
  304. package/dist/esm/components/tabs/tabs.recipe.js +4 -4
  305. package/dist/esm/components/textarea/textarea.js +5 -5
  306. package/dist/esm/components/textarea/textarea.recipe.js +16 -4
  307. package/dist/esm/components/toast/toast.js +4 -4
  308. package/dist/esm/components/toast/toast.recipe.js +2 -2
  309. package/dist/esm/components/tooltip/tooltip.js +21 -7
  310. package/dist/esm/components/wheelpicker/index.js +2 -0
  311. package/dist/esm/components/wheelpicker/wheelpicker.js +98 -0
  312. package/dist/esm/components/wheelpicker/wheelpicker.recipe.js +92 -0
  313. package/dist/esm/index.js +5 -0
  314. package/dist/esm/product/appshell/appshell.js +33 -9
  315. package/dist/esm/product/appshell/appshell.recipe.js +4 -4
  316. package/dist/esm/product/authshell/authshell.js +18 -0
  317. package/dist/esm/product/authshell/authshell.recipe.js +64 -0
  318. package/dist/esm/product/authshell/index.js +2 -0
  319. package/dist/esm/product/board/board.js +2 -2
  320. package/dist/esm/product/board/board.recipe.js +1 -1
  321. package/dist/esm/product/command/command.js +19 -6
  322. package/dist/esm/product/command/command.recipe.js +27 -2
  323. package/dist/esm/product/entitycard/entitycard.js +2 -2
  324. package/dist/esm/product/entitycard/entitycard.recipe.js +2 -2
  325. package/dist/esm/product/flowcanvas/flowcanvas.js +4 -4
  326. package/dist/esm/product/inbox/inbox.recipe.js +1 -1
  327. package/dist/esm/product/listpage/listpage.recipe.js +3 -3
  328. package/dist/esm/product/navitem/navitem.recipe.js +13 -1
  329. package/dist/esm/product/onboarding/onboarding.js +2 -2
  330. package/dist/esm/product/onboarding/onboarding.recipe.js +2 -2
  331. package/dist/esm/product/recordpage/recordpage.js +133 -47
  332. package/dist/esm/product/recordpage/recordpage.recipe.js +19 -17
  333. package/dist/esm/product/recordpanel/recordpanel.js +4 -4
  334. package/dist/esm/product/recordpanel/recordpanel.recipe.js +12 -16
  335. package/dist/esm/product/recordstable/recordstable.js +157 -66
  336. package/dist/esm/product/recordstable/recordstable.recipe.js +23 -23
  337. package/dist/esm/product/recordstable/recordstable.tag.js +18 -0
  338. package/dist/esm/product/richtext/richtext.js +3 -3
  339. package/dist/esm/product/richtext/richtext.recipe.js +3 -3
  340. package/dist/esm/product/settingssection/settingssection.js +2 -2
  341. package/dist/esm/product/settingsshell/settingsshell.js +2 -2
  342. package/dist/esm/product/settingsshell/settingsshell.recipe.js +7 -2
  343. package/dist/esm/product/sidebar/sidebar.js +2 -2
  344. package/dist/esm/product/sidebar/sidebar.recipe.js +27 -2
  345. package/dist/esm/product/tasklist/tasklist.js +4 -4
  346. package/dist/esm/product/trialbanner/trialbanner.js +2 -2
  347. package/dist/esm/product/trialbanner/trialbanner.recipe.js +2 -2
  348. package/dist/esm/product/viewtoggle/viewtoggle.js +10 -2
  349. package/dist/esm/product/viewtoggle/viewtoggle.recipe.js +1 -1
  350. package/dist/esm/tokens/elevation.js +44 -0
  351. package/dist/esm/tokens/index.js +1 -0
  352. package/dist/esm/tokens/palette.js +30 -0
  353. package/dist/esm/tokens/scale.js +7 -0
  354. package/dist/esm/tokens/semantic.js +24 -0
  355. package/dist/inbox.css +9 -1
  356. package/dist/input.css +32 -4
  357. package/dist/linechart.css +5 -5
  358. package/dist/listpage.css +11 -3
  359. package/dist/menu.css +11 -3
  360. package/dist/multiselect.css +14 -6
  361. package/dist/native/tokens.d.ts +16 -0
  362. package/dist/native/tokens.js +221 -0
  363. package/dist/navitem.css +21 -1
  364. package/dist/onboarding.css +10 -2
  365. package/dist/popover.css +8 -0
  366. package/dist/progress.css +8 -0
  367. package/dist/radiogroup.css +9 -1
  368. package/dist/recordpage.css +22 -12
  369. package/dist/recordpanel.css +25 -15
  370. package/dist/recordstable.css +37 -23
  371. package/dist/resizablepanel.css +53 -0
  372. package/dist/resizablepanel.d.ts +7 -0
  373. package/dist/richtext.css +11 -3
  374. package/dist/segmented.css +8 -0
  375. package/dist/select.css +12 -4
  376. package/dist/settingsshell.css +15 -2
  377. package/dist/sidebar.css +34 -1
  378. package/dist/skeleton.css +8 -0
  379. package/dist/spinner.css +8 -0
  380. package/dist/stagebar.css +8 -0
  381. package/dist/styles.css +2061 -1111
  382. package/dist/switch.css +8 -0
  383. package/dist/table.css +14 -6
  384. package/dist/tabletoolbar.css +8 -0
  385. package/dist/tabs.css +12 -4
  386. package/dist/tasklist.css +8 -0
  387. package/dist/textarea.css +23 -3
  388. package/dist/toast.css +10 -2
  389. package/dist/tokens.css +29 -0
  390. package/dist/tokens.json +217 -0
  391. package/dist/tooltip.css +8 -0
  392. package/dist/trialbanner.css +8 -0
  393. package/dist/types/components/accordion/accordion.d.ts +10 -8
  394. package/dist/types/components/avatargroup/avatargroup.d.ts +2 -2
  395. package/dist/types/components/barchart/barchart.d.ts +4 -2
  396. package/dist/types/components/breadcrumb/breadcrumb.d.ts +2 -2
  397. package/dist/types/components/bulkbar/bulkbar.d.ts +4 -2
  398. package/dist/types/components/button/button.d.ts +8 -0
  399. package/dist/types/components/button/button.recipe.d.ts +12 -0
  400. package/dist/types/components/callout/callout.d.ts +2 -2
  401. package/dist/types/components/columncalc/columncalc.d.ts +49 -0
  402. package/dist/types/components/columncalc/columncalc.recipe.d.ts +3 -0
  403. package/dist/types/components/columncalc/index.d.ts +1 -0
  404. package/dist/types/components/datatable/datatable.d.ts +58 -3
  405. package/dist/types/components/datepicker/datepicker.d.ts +41 -18
  406. package/dist/types/components/datepicker/datepicker.recipe.d.ts +25 -2
  407. package/dist/types/components/datepicker/index.d.ts +1 -1
  408. package/dist/types/components/datepickerfield/datepickerfield.d.ts +35 -0
  409. package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +5 -0
  410. package/dist/types/components/datepickerfield/index.d.ts +2 -0
  411. package/dist/types/components/dialog/dialog.d.ts +3 -1
  412. package/dist/types/components/divider/divider.d.ts +2 -2
  413. package/dist/types/components/donutchart/donutchart.d.ts +2 -2
  414. package/dist/types/components/drawer/drawer.d.ts +3 -1
  415. package/dist/types/components/emptystate/emptystate.d.ts +2 -2
  416. package/dist/types/components/formfield/formfield.d.ts +7 -4
  417. package/dist/types/components/input/input.d.ts +8 -2
  418. package/dist/types/components/linechart/linechart.d.ts +4 -2
  419. package/dist/types/components/menu/index.d.ts +1 -0
  420. package/dist/types/components/menu/menu-keyboard.d.ts +33 -0
  421. package/dist/types/components/menu/menu.d.ts +7 -1
  422. package/dist/types/components/multiselect/multiselect.d.ts +8 -4
  423. package/dist/types/components/popover/popover.d.ts +4 -1
  424. package/dist/types/components/progress/progress.d.ts +2 -2
  425. package/dist/types/components/radiogroup/radiogroup.d.ts +2 -2
  426. package/dist/types/components/resizablepanel/index.d.ts +2 -0
  427. package/dist/types/components/resizablepanel/resizablepanel.d.ts +75 -0
  428. package/dist/types/components/resizablepanel/resizablepanel.recipe.d.ts +3 -0
  429. package/dist/types/components/segmented/segmented.d.ts +2 -2
  430. package/dist/types/components/select/select.d.ts +2 -2
  431. package/dist/types/components/skeleton/skeleton.d.ts +2 -2
  432. package/dist/types/components/spinner/spinner.d.ts +2 -2
  433. package/dist/types/components/stagebar/stagebar.d.ts +2 -2
  434. package/dist/types/components/statcard/statcard.d.ts +2 -2
  435. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +28 -2
  436. package/dist/types/components/tabs/tabs.d.ts +2 -2
  437. package/dist/types/components/toast/toast.d.ts +7 -1
  438. package/dist/types/components/tooltip/tooltip.d.ts +8 -1
  439. package/dist/types/components/wheelpicker/index.d.ts +2 -0
  440. package/dist/types/components/wheelpicker/wheelpicker.d.ts +37 -0
  441. package/dist/types/components/wheelpicker/wheelpicker.recipe.d.ts +26 -0
  442. package/dist/types/index.d.ts +5 -0
  443. package/dist/types/product/appshell/appshell.d.ts +16 -2
  444. package/dist/types/product/authshell/authshell.d.ts +28 -0
  445. package/dist/types/product/authshell/authshell.recipe.d.ts +3 -0
  446. package/dist/types/product/authshell/index.d.ts +2 -0
  447. package/dist/types/product/board/board.d.ts +2 -2
  448. package/dist/types/product/command/command.d.ts +25 -1
  449. package/dist/types/product/entitycard/entitycard.d.ts +2 -2
  450. package/dist/types/product/flowcanvas/flowcanvas.d.ts +9 -1
  451. package/dist/types/product/onboarding/onboarding.d.ts +7 -1
  452. package/dist/types/product/recordpage/recordpage.d.ts +65 -1
  453. package/dist/types/product/recordpanel/recordpanel.d.ts +8 -2
  454. package/dist/types/product/recordstable/recordstable.d.ts +90 -1
  455. package/dist/types/product/recordstable/recordstable.tag.d.ts +10 -0
  456. package/dist/types/product/richtext/richtext.d.ts +5 -1
  457. package/dist/types/product/settingssection/settingssection.d.ts +2 -2
  458. package/dist/types/product/settingsshell/settingsshell.d.ts +6 -2
  459. package/dist/types/product/sidebar/sidebar.d.ts +8 -0
  460. package/dist/types/product/tasklist/tasklist.d.ts +5 -1
  461. package/dist/types/product/trialbanner/trialbanner.d.ts +2 -2
  462. package/dist/types/product/viewtoggle/viewtoggle.d.ts +11 -5
  463. package/dist/types/tokens/contract.d.ts +14 -3
  464. package/dist/types/tokens/elevation.d.ts +44 -0
  465. package/dist/types/tokens/index.d.ts +1 -0
  466. package/dist/types/tokens/palette.d.ts +40 -0
  467. package/dist/types/tokens/scale.d.ts +7 -0
  468. package/dist/types/tokens/semantic.d.ts +19 -0
  469. package/dist/viewtoggle.css +9 -1
  470. package/dist/wheelpicker.css +68 -0
  471. package/dist/wheelpicker.d.ts +7 -0
  472. package/package.json +160 -68
@@ -0,0 +1,1092 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.defaultRows = exports.defaultColumns = exports.recordsTableLabels = void 0;
37
+ exports.tagTone = tagTone;
38
+ exports.RecordsTable = RecordsTable;
39
+ const jsx_runtime_1 = require("react/jsx-runtime");
40
+ const lucide_react_1 = require("lucide-react");
41
+ const React = __importStar(require("react"));
42
+ const react_dom_1 = require("react-dom");
43
+ const avatar_1 = require("../../components/avatar");
44
+ const bulkbar_1 = require("../../components/bulkbar");
45
+ const button_1 = require("../../components/button");
46
+ const checkbox_1 = require("../../components/checkbox");
47
+ const datepickerfield_1 = require("../../components/datepickerfield");
48
+ const dialog_1 = require("../../components/dialog");
49
+ const emptystate_1 = require("../../components/emptystate");
50
+ const input_1 = require("../../components/input");
51
+ const menu_1 = require("../../components/menu");
52
+ const multiselect_1 = require("../../components/multiselect");
53
+ const select_1 = require("../../components/select");
54
+ const switch_1 = require("../../components/switch");
55
+ const company_logos_1 = require("./company-logos");
56
+ const recordstable_recipe_1 = require("./recordstable.recipe");
57
+ const EDITABLE = new Set(["text", "number", "url", "currency"]);
58
+ // types whose cell is an always-interactive picker (not double-click-to-edit)
59
+ const PICKER = new Set(["status", "reference", "date", "tags"]);
60
+ // a `render` column owns its content fully — never editable, never a picker
61
+ const canEditCol = (col) => EDITABLE.has(col.type ?? "text") && !col.render;
62
+ const isPickerCol = (col) => PICKER.has(col.type ?? "text") && !col.render;
63
+ // deterministic pastel colour per tag value (a select option keeps its colour)
64
+ const TAG_TONES = [
65
+ "brand",
66
+ "success",
67
+ "warning",
68
+ "danger",
69
+ "purple",
70
+ "cyan",
71
+ "orange",
72
+ ];
73
+ function tagTone(t) {
74
+ let h = 0;
75
+ for (let i = 0; i < t.length; i++)
76
+ h = (h * 31 + t.charCodeAt(i)) >>> 0;
77
+ return TAG_TONES[h % TAG_TONES.length];
78
+ }
79
+ // deterministic per-name colour for `avatarTone="hashed"` — a distinct
80
+ // palette from TAG_TONES because it must stay within the tones the `Avatar`
81
+ // DS component itself supports (no "success"/"warning"/"danger").
82
+ const AVATAR_TONES = ["brand", "orange", "cyan", "purple", "green"];
83
+ function avatarToneFor(name) {
84
+ let h = 0;
85
+ for (let i = 0; i < name.length; i++)
86
+ h = (h * 31 + name.charCodeAt(i)) >>> 0;
87
+ return AVATAR_TONES[h % AVATAR_TONES.length];
88
+ }
89
+ exports.recordsTableLabels = {
90
+ selectAll: "Select all",
91
+ sort: "Sort",
92
+ sortField: "Sort field",
93
+ sortDirection: "Sort direction",
94
+ removeSort: "Remove sort",
95
+ reorderSort: "Reorder sort",
96
+ addSort: "Add sort",
97
+ ascending: "Ascending",
98
+ descending: "Descending",
99
+ noSorts: "No sorts applied to this view.",
100
+ learnSorting: "Learn about sorting",
101
+ filter: "Filter",
102
+ addFilter: "Add filter",
103
+ filterByField: "Filter by field",
104
+ filterByPlaceholder: "Filter by…",
105
+ filterValuePlaceholder: "Value…",
106
+ selectCondition: "Select condition",
107
+ clearFilter: "Clear filter",
108
+ addColumn: "Add column",
109
+ columnLabel: "Column label",
110
+ editColumnLabel: "Edit column label",
111
+ hideFromView: "Hide from view",
112
+ moveLeft: "Move left",
113
+ moveRight: "Move right",
114
+ sortAscending: "Sort ascending",
115
+ sortDescending: "Sort descending",
116
+ newRecord: "New record",
117
+ createRecord: "Create record",
118
+ createMore: "Create more",
119
+ newButton: "New",
120
+ more: "More",
121
+ moreActions: "More actions",
122
+ recordActions: "Record actions",
123
+ copy: "Copy",
124
+ duplicate: "Duplicate",
125
+ addToList: "Add to list",
126
+ inAList: "In a list",
127
+ delete: "Delete",
128
+ sendEmail: "Send email",
129
+ exportCsv: "Export CSV",
130
+ runWorkflow: "Run workflow",
131
+ calculate: "Calculate",
132
+ emptyPlaceholder: "Empty",
133
+ setRecordPlaceholder: "Set a record…",
134
+ noRecords: "No records yet",
135
+ untitled: "Untitled",
136
+ agg: {
137
+ none: "None",
138
+ count: "Count all",
139
+ filled: "Not empty",
140
+ sum: "Sum",
141
+ avg: "Average",
142
+ min: "Min",
143
+ max: "Max",
144
+ },
145
+ op: {
146
+ contains: "contains",
147
+ is: "is",
148
+ isnot: "is not",
149
+ empty: "is empty",
150
+ notempty: "is not empty",
151
+ },
152
+ newRecordTitle: (entity) => `New ${entity}`,
153
+ setFieldPlaceholder: (field) => `Set ${field}…`,
154
+ newColumnLabel: (index) => `Field ${index}`,
155
+ sortedBy: (first, extra) => `Sorted by ${first}${extra ? ` +${extra}` : ""}`,
156
+ filterCount: (active) => (active ? `Filter · ${active}` : "Filter"),
157
+ recordCount: (n) => `${n} record${n === 1 ? "" : "s"}`,
158
+ conditionTitle: (field) => `${field} condition`,
159
+ };
160
+ // currency is numeric data too — it gets the same numeric aggregations as
161
+ // `number` (only `number` auto-defaults to Sum on mount; currency starts at
162
+ // "none" like every other type and is picked explicitly from this menu)
163
+ const aggOptions = (col) => col.type === "number" || col.type === "currency"
164
+ ? ["sum", "avg", "min", "max", "count", "filled", "none"]
165
+ : ["count", "filled", "none"];
166
+ const compact = new Intl.NumberFormat("en", {
167
+ notation: "compact",
168
+ maximumFractionDigits: 1,
169
+ });
170
+ const CHECK_W = 40;
171
+ const ADDCOL_W = 44;
172
+ const colWidth = (col) => col.width ?? (col.type === "entity" ? 240 : 180);
173
+ const OP_ORDER = ["contains", "is", "isnot", "empty", "notempty"];
174
+ const opNeedsValue = (op) => op === "contains" || op === "is" || op === "isnot";
175
+ function matchesFilter(col, row, cond) {
176
+ if (cond.op === "empty")
177
+ return !isFilled(col, row);
178
+ if (cond.op === "notempty")
179
+ return isFilled(col, row);
180
+ // filter always reads the raw underlying value, never `format`'s display
181
+ // string — a formatted "$1,200" must still match a filter value of "1200"
182
+ const t = rawText(col, row).toLowerCase();
183
+ const v = cond.value.trim().toLowerCase();
184
+ if (!v)
185
+ return true;
186
+ if (cond.op === "contains")
187
+ return t.includes(v);
188
+ if (cond.op === "is")
189
+ return t === v;
190
+ return t !== v; // isnot
191
+ }
192
+ // the raw value stringified, ignoring `format`/`render` entirely — the basis
193
+ // for filtering and the fallback for `cellText`'s copy path
194
+ function rawText(col, row) {
195
+ const v = row[col.key];
196
+ if (v == null)
197
+ return "";
198
+ if (col.type === "entity")
199
+ return v.name ?? "";
200
+ if (Array.isArray(v))
201
+ return v.join(", ");
202
+ return String(v);
203
+ }
204
+ // what ⌘C / row-copy / CSV export puts on the clipboard for this cell: a
205
+ // `render` column has no serializable node, so this never touches `render` —
206
+ // it uses `format`'s display string when the column has one, else the raw
207
+ // value stringified exactly like a plain column.
208
+ function cellText(col, row) {
209
+ if (col.format)
210
+ return col.format(row[col.key], row);
211
+ return rawText(col, row);
212
+ }
213
+ function isFilled(col, row) {
214
+ const v = row[col.key];
215
+ if (v == null || v === "")
216
+ return false;
217
+ if (Array.isArray(v))
218
+ return v.length > 0;
219
+ if (col.type === "entity")
220
+ return Boolean(v.name);
221
+ return true;
222
+ }
223
+ function summarize(col, agg, rows) {
224
+ if (agg === "none")
225
+ return null;
226
+ if (agg === "count")
227
+ return { label: "count", value: String(rows.length) };
228
+ if (agg === "filled") {
229
+ const n = rows.filter((r) => isFilled(col, r)).length;
230
+ return { label: "filled", value: String(n) };
231
+ }
232
+ const nums = rows
233
+ .map((r) => r[col.key])
234
+ .filter((v) => typeof v === "number");
235
+ if (!nums.length)
236
+ return { label: agg, value: "—" };
237
+ let n;
238
+ if (agg === "sum")
239
+ n = nums.reduce((a, b) => a + b, 0);
240
+ else if (agg === "avg")
241
+ n = nums.reduce((a, b) => a + b, 0) / nums.length;
242
+ else if (agg === "min")
243
+ n = Math.min(...nums);
244
+ else
245
+ n = Math.max(...nums);
246
+ return { label: agg, value: compact.format(n) };
247
+ }
248
+ function EntityCell({ value, pinned, onOpenPage, avatarTone = "brand", labels, }) {
249
+ // a real favicon/logo when we have one; fall back to the letter tile if it
250
+ // 404s or the row has no avatar
251
+ const [imgFailed, setImgFailed] = React.useState(false);
252
+ const showImg = Boolean(value.avatar) && !imgFailed;
253
+ return ((0, jsx_runtime_1.jsxs)("span", { className: "recordstable-entity", children: [showImg ? ((0, jsx_runtime_1.jsx)("img", { className: "recordstable-fav", src: value.avatar, alt: "", onError: () => setImgFailed(true) })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: avatarTone === "hashed" ? avatarToneFor(value.name) : "brand", children: value.name.charAt(0) })), onOpenPage ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordstable-entity-name recordstable-entity-name--link", onClick: (e) => {
254
+ e.stopPropagation();
255
+ onOpenPage();
256
+ }, children: value.name })) : ((0, jsx_runtime_1.jsx)("span", { className: "recordstable-entity-name", children: value.name })), pinned ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Star, { className: "recordstable-pin", size: 13, strokeWidth: 2, "aria-label": labels.inAList })) : null] }));
257
+ }
258
+ function CellView({ column, row, pinned, onOpenPage, avatarTone = "brand", labels, }) {
259
+ const value = row[column.key];
260
+ // `render` fully owns the cell's content — no empty-dash fallback, no
261
+ // type renderer; it's the only thing drawn here (see the doc comment on
262
+ // `Column.render`).
263
+ if (column.render)
264
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: column.render(value, row) });
265
+ if (value == null ||
266
+ value === "" ||
267
+ (Array.isArray(value) && !value.length)) {
268
+ return (0, jsx_runtime_1.jsx)("span", { className: "recordstable-muted", children: "\u2014" });
269
+ }
270
+ // `format` keeps the type renderer's chrome below and only replaces the
271
+ // text it displays.
272
+ const display = column.format?.(value, row);
273
+ switch (column.type) {
274
+ case "entity":
275
+ return ((0, jsx_runtime_1.jsx)(EntityCell, { labels: labels, value: value, pinned: pinned, onOpenPage: onOpenPage, avatarTone: avatarTone }));
276
+ case "tags":
277
+ return ((0, jsx_runtime_1.jsx)("span", { className: "recordstable-cell recordstable-cell--tags", children: value.map((t) => ((0, jsx_runtime_1.jsx)("span", { className: "recordstable-tag", "data-tone": tagTone(t), children: t }, t))) }));
278
+ case "number":
279
+ return ((0, jsx_runtime_1.jsx)("span", { className: "recordstable-cell recordstable-cell--num", children: display ??
280
+ (typeof value === "number" ? value.toLocaleString() : value) }));
281
+ case "url":
282
+ return ((0, jsx_runtime_1.jsx)("a", { className: "recordstable-link", href: `https://${value}`, target: "_blank", rel: "noreferrer", onClick: (e) => e.stopPropagation(), children: display ?? value }));
283
+ case "date":
284
+ return (0, jsx_runtime_1.jsx)("span", { className: "recordstable-muted", children: display ?? value });
285
+ case "currency": {
286
+ const n = typeof value === "number" ? value : Number(value);
287
+ return ((0, jsx_runtime_1.jsx)("span", { className: "recordstable-cell recordstable-cell--num", children: display ??
288
+ `${column.symbol ?? "$"}${Number.isFinite(n) ? n.toLocaleString() : value}` }));
289
+ }
290
+ case "status": {
291
+ const opt = column.options?.find((o) => o.value === value);
292
+ if (!opt)
293
+ return (0, jsx_runtime_1.jsx)("span", { className: "recordstable-muted", children: "\u2014" });
294
+ return ((0, jsx_runtime_1.jsxs)("span", { className: "recordstable-status", children: [(0, jsx_runtime_1.jsx)("span", { className: "recordstable-status-dot", style: { background: opt.color } }), display ?? opt.label] }));
295
+ }
296
+ case "reference": {
297
+ const opt = column.refOptions?.find((o) => o.value === value);
298
+ const label = display ?? opt?.label ?? String(value);
299
+ return ((0, jsx_runtime_1.jsxs)("span", { className: "recordstable-entity", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: avatarTone === "hashed" ? avatarToneFor(label) : "brand", children: label.charAt(0) }), (0, jsx_runtime_1.jsx)("span", { className: "recordstable-entity-name", children: label })] }));
300
+ }
301
+ default:
302
+ return ((0, jsx_runtime_1.jsx)("span", { className: "recordstable-entity-name", children: display ?? value }));
303
+ }
304
+ }
305
+ // Always-interactive cell editors for the picker column types (status / date /
306
+ // reference). Each is a ghost crisp control that reads the cell value and
307
+ // commits the new one — matching Attio's inline field editors.
308
+ function StatusCell({ column, row, onChange, }) {
309
+ return ((0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", "aria-label": column.label, placeholder: "\u2014", value: row[column.key] ?? undefined, onValueChange: onChange, options: column.options ?? [] }));
310
+ }
311
+ function ReferenceCell({ column, row, onChange, labels, }) {
312
+ return ((0, jsx_runtime_1.jsx)(select_1.Select, { variant: "ghost", searchable: true, searchPlaceholder: "Search records\u2026", "aria-label": column.label, placeholder: labels.setRecordPlaceholder, value: row[column.key] ?? undefined, onValueChange: onChange, options: (column.refOptions ?? []).map((o) => ({
313
+ value: o.value,
314
+ label: o.label,
315
+ })) }));
316
+ }
317
+ function TagsCell({ column, row, options, onChange, labels, }) {
318
+ const value = row[column.key] ?? [];
319
+ return ((0, jsx_runtime_1.jsx)(multiselect_1.MultiSelect, { options: options, value: value, onValueChange: onChange, placeholder: labels.emptyPlaceholder, searchPlaceholder: "Search options\u2026", "aria-label": column.label }));
320
+ }
321
+ function DateCell({ column, row, onChange, }) {
322
+ const raw = row[column.key];
323
+ const value = raw ? new Date(raw) : null;
324
+ return ((0, jsx_runtime_1.jsx)(datepickerfield_1.DatePickerField, { variant: "ghost", "aria-label": column.label, value: value, onChange: (d) => onChange(d.toISOString().slice(0, 10)) }));
325
+ }
326
+ /**
327
+ * The "New <object>" create modal — measured from Attio's create-record dialog:
328
+ * a stacked label + "Set …" input per column, a "Create more" toggle, and a
329
+ * primary "Create record" action (⌘↵). Building the row mirrors the inline
330
+ * editor's per-type coercion so a created record reads back like an edited one.
331
+ */
332
+ function CreateRecordDialog({ open, onOpenChange, columns, entityLabel, onCreate, labels, }) {
333
+ const [values, setValues] = React.useState({});
334
+ const [createMore, setCreateMore] = React.useState(false);
335
+ const firstRef = React.useRef(null);
336
+ // reset the form each time the modal (re)opens
337
+ React.useEffect(() => {
338
+ if (open)
339
+ setValues({});
340
+ }, [open]);
341
+ const set = (key, v) => setValues((prev) => ({ ...prev, [key]: v }));
342
+ const buildRow = () => {
343
+ const row = {
344
+ id: `new-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
345
+ };
346
+ for (const col of columns) {
347
+ const raw = (values[col.key] ?? "").trim();
348
+ if (col.type === "entity") {
349
+ row[col.key] = { name: raw || labels.untitled };
350
+ }
351
+ else if (!raw) {
352
+ // leave empty fields unset so the cell renders its "—" placeholder
353
+ }
354
+ else if (col.type === "tags") {
355
+ row[col.key] = raw
356
+ .split(",")
357
+ .map((t) => t.trim())
358
+ .filter(Boolean);
359
+ }
360
+ else if (col.type === "number") {
361
+ row[col.key] = Number(raw.replace(/[^0-9.-]/g, "")) || null;
362
+ }
363
+ else {
364
+ row[col.key] = raw;
365
+ }
366
+ }
367
+ return row;
368
+ };
369
+ const submit = () => {
370
+ onCreate(buildRow());
371
+ if (createMore) {
372
+ setValues({});
373
+ firstRef.current?.focus();
374
+ }
375
+ else {
376
+ onOpenChange(false);
377
+ }
378
+ };
379
+ const entityCol = columns.find((c) => c.type === "entity");
380
+ const EntityIcon = entityCol?.icon;
381
+ return ((0, jsx_runtime_1.jsxs)(dialog_1.Dialog, { open: open, onOpenChange: onOpenChange, title: labels.newRecordTitle(entityLabel), icon: EntityIcon ? (0, jsx_runtime_1.jsx)(EntityIcon, { size: 16, strokeWidth: 1.75 }) : undefined, width: 720, children: [(0, jsx_runtime_1.jsx)(dialog_1.DialogBody, { children: columns.map((col, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "dialog-field", children: [(0, jsx_runtime_1.jsx)("label", { className: "dialog-field-label", htmlFor: `create-${col.key}`, children: col.label }), (0, jsx_runtime_1.jsx)(input_1.Input, { id: `create-${col.key}`, ref: i === 0 ? firstRef : undefined, value: values[col.key] ?? "", onChange: (e) => set(col.key, e.currentTarget.value), placeholder: labels.setFieldPlaceholder(col.label), onKeyDown: (e) => {
382
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey))
383
+ submit();
384
+ } })] }, col.key))) }), (0, jsx_runtime_1.jsxs)(dialog_1.DialogFooter, { children: [(0, jsx_runtime_1.jsxs)("span", { className: "dialog-more", children: [labels.createMore, (0, jsx_runtime_1.jsx)(switch_1.Switch, { checked: createMore, onCheckedChange: setCreateMore, "aria-label": labels.createMore })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "dialog-submit", onClick: submit, children: [labels.createRecord, (0, jsx_runtime_1.jsx)("span", { className: "dialog-submit-key", children: "\u2318\u21B5" })] })] })] }));
385
+ }
386
+ /**
387
+ * Attio-style record grid — fully featured. Toolbar (search / sort status /
388
+ * count / New), sticky checkbox + entity columns, 36px rows, typed cells,
389
+ * click-to-sort headers, column resize by dragging a header edge, inline cell
390
+ * editing (double-click a text/number/url/date cell), add-row and add-column,
391
+ * and a floating bulk-action bar (delete / deselect) when rows are selected.
392
+ * Selection can be controlled via `selectedIds`/`onSelectionChange`.
393
+ */
394
+ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, rows: rowsProp = exports.defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", className, }) {
395
+ const [cols, setCols] = React.useState(columnsProp);
396
+ const [data, setData] = React.useState(rowsProp);
397
+ // Attio's multi-criteria sort: an ordered list of {field, direction} rows.
398
+ const [sorts, setSorts] = React.useState([]);
399
+ const [dragSort, setDragSort] = React.useState(null);
400
+ const [editing, setEditing] = React.useState(null);
401
+ const [renamingCol, setRenamingCol] = React.useState(null);
402
+ const [creating, setCreating] = React.useState(false);
403
+ // Attio-style filter: an ordered list of conditions, each shown as a toolbar
404
+ // chip with an editable value (operator is "contains")
405
+ const [filterConds, setFilterConds] = React.useState([]);
406
+ const [filterQuery, setFilterQuery] = React.useState("");
407
+ // one popover open at a time: "sort" | "filter" | "more" | `sum:<key>`
408
+ const t = React.useMemo(() => ({
409
+ ...exports.recordsTableLabels,
410
+ ...labels,
411
+ agg: { ...exports.recordsTableLabels.agg, ...labels?.agg },
412
+ op: { ...exports.recordsTableLabels.op, ...labels?.op },
413
+ }), [labels]);
414
+ const [openMenu, setOpenMenu] = React.useState(null);
415
+ // the element that opened the current menu, so Esc/select can hand focus back
416
+ const menuReturn = React.useRef(null);
417
+ const menuRef = React.useRef(null);
418
+ const ctxRef = React.useRef(null);
419
+ const ctxReturn = React.useRef(null);
420
+ // viewport-anchored position for menus that live inside the scroll container
421
+ // (summary footer, column headers) — rendered fixed to escape the overflow clip
422
+ const [menuPos, setMenuPos] = React.useState(null);
423
+ const [pinned, setPinned] = React.useState(() => new Set());
424
+ const [scrolledX, setScrolledX] = React.useState(false);
425
+ const idCounter = React.useRef(0);
426
+ // fixed menus (summary / header) portal here — outside the sticky cells'
427
+ // stacking contexts, but still inside .recordstable so the styles apply
428
+ const rootRef = React.useRef(null);
429
+ const scrollRef = React.useRef(null);
430
+ // spreadsheet-style cell selection (click a cell, arrow-key to navigate)
431
+ const [selCell, setSelCell] = React.useState(null);
432
+ // shift-click range selection — anchor row + whether shift was held on click
433
+ const selAnchor = React.useRef(null);
434
+ const shiftHeld = React.useRef(false);
435
+ // right-click row context menu (fixed at the cursor)
436
+ const [ctxMenu, setCtxMenu] = React.useState(null);
437
+ // column drag-to-reorder — key of the header currently being dragged
438
+ const [dragCol, setDragCol] = React.useState(null);
439
+ // Let a plain vertical wheel scroll the (wide) table horizontally when it has
440
+ // no vertical overflow of its own — otherwise the wheel just scrolls the page
441
+ // and the extra columns feel unreachable. Non-passive so we can preventDefault.
442
+ React.useEffect(() => {
443
+ const el = scrollRef.current;
444
+ if (!el)
445
+ return;
446
+ const onWheel = (e) => {
447
+ // don't hijack the wheel when it's over an open cell popover (its own list
448
+ // scrolls) — only map vertical wheel to horizontal for the table itself
449
+ if (e.target?.closest?.(".select-listbox, .multiselect-listbox, .datepicker-cal, .recordstable-menu"))
450
+ return;
451
+ const canH = el.scrollWidth > el.clientWidth;
452
+ const canV = el.scrollHeight > el.clientHeight;
453
+ if (canH && !canV && e.deltaX === 0 && e.deltaY !== 0) {
454
+ el.scrollLeft += e.deltaY;
455
+ e.preventDefault();
456
+ }
457
+ };
458
+ el.addEventListener("wheel", onWheel, { passive: false });
459
+ return () => el.removeEventListener("wheel", onWheel);
460
+ }, []);
461
+ // close any open menu on outside click (anything not inside a menu or its
462
+ // trigger) or Escape
463
+ React.useEffect(() => {
464
+ if (!openMenu)
465
+ return;
466
+ const onDown = (e) => {
467
+ const t = e.target;
468
+ if (!t.closest(".recordstable-menu, [data-menu-trigger]"))
469
+ setOpenMenu(null);
470
+ };
471
+ const onKey = (e) => e.key === "Escape" && setOpenMenu(null);
472
+ document.addEventListener("mousedown", onDown);
473
+ document.addEventListener("keydown", onKey);
474
+ return () => {
475
+ document.removeEventListener("mousedown", onDown);
476
+ document.removeEventListener("keydown", onKey);
477
+ };
478
+ }, [openMenu]);
479
+ // dismiss the right-click row context menu on any outside click / Escape
480
+ React.useEffect(() => {
481
+ if (!ctxMenu)
482
+ return;
483
+ const onDown = (e) => {
484
+ if (!e.target.closest(".recordstable-ctx"))
485
+ setCtxMenu(null);
486
+ };
487
+ const onKey = (e) => e.key === "Escape" && setCtxMenu(null);
488
+ document.addEventListener("mousedown", onDown);
489
+ document.addEventListener("keydown", onKey);
490
+ return () => {
491
+ document.removeEventListener("mousedown", onDown);
492
+ document.removeEventListener("keydown", onKey);
493
+ };
494
+ }, [ctxMenu]);
495
+ const activeFilters = filterConds.length;
496
+ const addFilter = (key) => {
497
+ idCounter.current += 1;
498
+ const id = `filter-${idCounter.current}`;
499
+ setFilterConds((prev) => [...prev, { id, key, op: "contains", value: "" }]);
500
+ setFilterQuery("");
501
+ setOpenMenu(`fcond:${id}`); // open its condition editor right away
502
+ };
503
+ const patchFilter = (id, patch) => setFilterConds((prev) => prev.map((c) => (c.id === id ? { ...c, ...patch } : c)));
504
+ const removeFilter = (id) => {
505
+ setFilterConds((prev) => prev.filter((c) => c.id !== id));
506
+ setOpenMenu(null);
507
+ };
508
+ const [agg, setAgg] = React.useState(() => {
509
+ const m = {};
510
+ for (const col of columnsProp) {
511
+ m[col.key] =
512
+ col.type === "number"
513
+ ? "sum"
514
+ : col.type === "entity"
515
+ ? "count"
516
+ : "none";
517
+ }
518
+ return m;
519
+ });
520
+ // selection — controlled if `selectedIds` is provided, else internal
521
+ const [internalSel, setInternalSel] = React.useState(() => new Set());
522
+ const selected = React.useMemo(() => (selectedIds ? new Set(selectedIds) : internalSel), [selectedIds, internalSel]);
523
+ const setSelected = React.useCallback((next) => {
524
+ onSelectionChange?.([...next]);
525
+ if (!selectedIds)
526
+ setInternalSel(next);
527
+ }, [onSelectionChange, selectedIds]);
528
+ const filtered = React.useMemo(() => {
529
+ const active = filterConds.filter((c) => !opNeedsValue(c.op) || c.value.trim());
530
+ if (!active.length)
531
+ return data;
532
+ return data.filter((row) => active.every((cond) => {
533
+ const col = cols.find((c) => c.key === cond.key);
534
+ return col ? matchesFilter(col, row, cond) : true;
535
+ }));
536
+ }, [data, cols, filterConds]);
537
+ const visibleRows = React.useMemo(() => {
538
+ if (sorts.length === 0)
539
+ return filtered;
540
+ const getters = sorts.map((s) => {
541
+ const col = cols.find((c) => c.key === s.key);
542
+ return (r) => {
543
+ const v = r[s.key];
544
+ if (col?.type === "entity")
545
+ return v?.name ?? "";
546
+ if (Array.isArray(v))
547
+ return v[0] ?? "";
548
+ return v ?? "";
549
+ };
550
+ });
551
+ return [...filtered].sort((a, b) => {
552
+ for (let i = 0; i < sorts.length; i++) {
553
+ const get = getters[i];
554
+ const av = get(a);
555
+ const bv = get(b);
556
+ const cmp = typeof av === "number" && typeof bv === "number"
557
+ ? av - bv
558
+ : String(av).localeCompare(String(bv));
559
+ if (cmp !== 0)
560
+ return sorts[i].dir === "asc" ? cmp : -cmp;
561
+ }
562
+ return 0;
563
+ });
564
+ }, [filtered, cols, sorts]);
565
+ // arrow-key navigation over the selected cell (spreadsheet-style)
566
+ const onGridKeyDown = (e) => {
567
+ if (!selCell || editing)
568
+ return;
569
+ // ⌘C / Ctrl+C — copy the selected cell's rendered text to the clipboard
570
+ if ((e.metaKey || e.ctrlKey) && (e.key === "c" || e.key === "C")) {
571
+ const row = visibleRows.find((rw) => rw.id === selCell.id);
572
+ const col = cols.find((cc) => cc.key === selCell.key);
573
+ if (row && col)
574
+ navigator.clipboard?.writeText(cellText(col, row));
575
+ e.preventDefault();
576
+ return;
577
+ }
578
+ const r = visibleRows.findIndex((row) => row.id === selCell.id);
579
+ const c = cols.findIndex((col) => col.key === selCell.key);
580
+ if (r < 0 || c < 0)
581
+ return;
582
+ let nr = r;
583
+ let nc = c;
584
+ if (e.key === "ArrowUp")
585
+ nr = Math.max(0, r - 1);
586
+ else if (e.key === "ArrowDown")
587
+ nr = Math.min(visibleRows.length - 1, r + 1);
588
+ else if (e.key === "ArrowLeft")
589
+ nc = Math.max(0, c - 1);
590
+ else if (e.key === "ArrowRight")
591
+ nc = Math.min(cols.length - 1, c + 1);
592
+ else if (e.key === "Escape") {
593
+ setSelCell(null);
594
+ return;
595
+ }
596
+ else if (e.key === "Enter") {
597
+ if (canEditCol(cols[c])) {
598
+ e.preventDefault();
599
+ setEditing({ id: selCell.id, key: selCell.key });
600
+ }
601
+ return;
602
+ }
603
+ else
604
+ return;
605
+ e.preventDefault();
606
+ setSelCell({ id: visibleRows[nr].id, key: cols[nc].key });
607
+ };
608
+ const allSelected = visibleRows.length > 0 && visibleRows.every((r) => selected.has(r.id));
609
+ const someSelected = selected.size > 0 && !allSelected;
610
+ const toggleAll = () => setSelected(allSelected ? new Set() : new Set(visibleRows.map((r) => r.id)));
611
+ const toggleRow = (id) => {
612
+ const next = new Set(selected);
613
+ // shift-click → select the contiguous range from the anchor to this row
614
+ if (shiftHeld.current && selAnchor.current) {
615
+ const a = visibleRows.findIndex((r) => r.id === selAnchor.current);
616
+ const b = visibleRows.findIndex((r) => r.id === id);
617
+ if (a >= 0 && b >= 0) {
618
+ const [lo, hi] = a < b ? [a, b] : [b, a];
619
+ for (let i = lo; i <= hi; i++)
620
+ next.add(visibleRows[i].id);
621
+ shiftHeld.current = false;
622
+ setSelected(next);
623
+ return;
624
+ }
625
+ }
626
+ if (next.has(id))
627
+ next.delete(id);
628
+ else
629
+ next.add(id);
630
+ selAnchor.current = id;
631
+ shiftHeld.current = false;
632
+ setSelected(next);
633
+ };
634
+ const addSort = () => {
635
+ const used = new Set(sorts.map((s) => s.key));
636
+ const col = cols.find((c) => !used.has(c.key));
637
+ if (col)
638
+ setSorts([...sorts, { key: col.key, dir: "asc" }]);
639
+ };
640
+ const moveSort = (from, to) => setSorts((prev) => {
641
+ if (from === to)
642
+ return prev;
643
+ const next = [...prev];
644
+ const [moved] = next.splice(from, 1);
645
+ next.splice(to, 0, moved);
646
+ return next;
647
+ });
648
+ const setSortField = (i, key) => setSorts(sorts.map((s, j) => (j === i ? { ...s, key } : s)));
649
+ const setSortDir = (i, dir) => setSorts(sorts.map((s, j) => (j === i ? { ...s, dir } : s)));
650
+ const removeSort = (i) => setSorts(sorts.filter((_, j) => j !== i));
651
+ const commitEdit = (id, key, raw) => {
652
+ const col = cols.find((c) => c.key === key);
653
+ const numeric = col?.type === "number" || col?.type === "currency";
654
+ const coerce = (r) => {
655
+ if (numeric)
656
+ return raw === "" ? null : Number(raw.replace(/[^0-9.-]/g, ""));
657
+ if (col?.type === "tags")
658
+ return raw
659
+ .split(",")
660
+ .map((t) => t.trim())
661
+ .filter(Boolean);
662
+ if (col?.type === "entity")
663
+ return { ...r[key], name: raw };
664
+ return raw;
665
+ };
666
+ setData((prev) => prev.map((r) => (r.id === id ? { ...r, [key]: coerce(r) } : r)));
667
+ setEditing(null);
668
+ };
669
+ // the option set for a `tags` column — the union of every value in that column,
670
+ // so the tag editor offers the existing options (each keeps a stable colour).
671
+ const tagOptionsFor = React.useCallback((key) => {
672
+ const set = new Set();
673
+ for (const r of data) {
674
+ const v = r[key];
675
+ if (Array.isArray(v))
676
+ for (const t of v)
677
+ set.add(t);
678
+ }
679
+ return [...set].map((t) => ({ value: t, label: t, tone: tagTone(t) }));
680
+ }, [data]);
681
+ // set any cell value directly (used by the status / date / reference pickers)
682
+ // biome-ignore lint/suspicious/noExplicitAny: cell values are column-type dependent
683
+ const setCell = (id, key, value) => {
684
+ setData((prev) => prev.map((r) => (r.id === id ? { ...r, [key]: value } : r)));
685
+ };
686
+ const addRow = () => {
687
+ idCounter.current += 1;
688
+ const id = `new-${idCounter.current}`;
689
+ const entityKey = cols.find((c) => c.type === "entity")?.key;
690
+ const row = { id };
691
+ if (entityKey)
692
+ row[entityKey] = { name: t.untitled };
693
+ setData((prev) => [...prev, row]);
694
+ if (entityKey)
695
+ setEditing(null);
696
+ };
697
+ const sortBy = (key, dir) => {
698
+ setSorts([{ key, dir }]);
699
+ setOpenMenu(null);
700
+ };
701
+ const hideColumn = (key) => {
702
+ setCols((prev) => prev.filter((c) => c.key !== key));
703
+ setOpenMenu(null);
704
+ };
705
+ // Move a column one slot left/right. The first (entity) column is sticky and
706
+ // stays pinned, so reordering is clamped to index >= 1.
707
+ const moveColumn = (key, dir) => {
708
+ setCols((prev) => {
709
+ const i = prev.findIndex((c) => c.key === key);
710
+ const j = i + dir;
711
+ if (i < 1 || j < 1 || j >= prev.length)
712
+ return prev;
713
+ const next = [...prev];
714
+ [next[i], next[j]] = [next[j], next[i]];
715
+ return next;
716
+ });
717
+ setOpenMenu(null);
718
+ };
719
+ // drop the dragged column INTO `toKey`'s slot (drag-to-reorder) — the slot the
720
+ // dragover indicator highlights, and the same result the header menu's "Move
721
+ // left"/"Move right" gives for a neighbouring column. The sticky entity
722
+ // column (index 0) stays pinned — never reorder into slot 0.
723
+ const reorderColumn = (fromKey, toKey) => {
724
+ setDragCol(null);
725
+ if (fromKey === toKey)
726
+ return;
727
+ setCols((prev) => {
728
+ const from = prev.findIndex((c) => c.key === fromKey);
729
+ const to = prev.findIndex((c) => c.key === toKey);
730
+ if (from < 1 || to < 1)
731
+ return prev;
732
+ const next = [...prev];
733
+ const [moved] = next.splice(from, 1);
734
+ // insert at the TARGET'S ORIGINAL index, which is the slot the dragover
735
+ // indicator highlighted. Re-finding the target after the splice shifts
736
+ // every rightward drag one slot left, so an adjacent one does nothing.
737
+ next.splice(to, 0, moved);
738
+ return next;
739
+ });
740
+ };
741
+ const renameColumn = (key, label) => {
742
+ const name = label.trim();
743
+ if (name)
744
+ setCols((prev) => prev.map((c) => (c.key === key ? { ...c, label: name } : c)));
745
+ setRenamingCol(null);
746
+ };
747
+ // the two `role="menu"` popovers driven by `openMenu`; the sort and filter
748
+ // popovers are dialogs and keep their own focus handling
749
+ const roleMenuOpen = !!openMenu && (openMenu.startsWith("col:") || openMenu.startsWith("sum:"));
750
+ const closeRoleMenu = React.useCallback(() => {
751
+ setOpenMenu(null);
752
+ menuReturn.current?.focus();
753
+ }, []);
754
+ const { onKeyDown: onMenuKeyDown } = (0, menu_1.useMenuKeyboard)({
755
+ open: roleMenuOpen,
756
+ close: closeRoleMenu,
757
+ dismiss: () => setOpenMenu(null),
758
+ contentRef: menuRef,
759
+ });
760
+ const closeCtxMenu = React.useCallback(() => {
761
+ setCtxMenu(null);
762
+ ctxReturn.current?.focus();
763
+ }, []);
764
+ const { onKeyDown: onCtxKeyDown } = (0, menu_1.useMenuKeyboard)({
765
+ open: !!ctxMenu,
766
+ close: closeCtxMenu,
767
+ dismiss: () => setCtxMenu(null),
768
+ contentRef: ctxRef,
769
+ });
770
+ const openColumnMenu = (key, el) => {
771
+ if (openMenu === `col:${key}`) {
772
+ setOpenMenu(null);
773
+ return;
774
+ }
775
+ const r = el.getBoundingClientRect();
776
+ setMenuPos({ top: r.bottom + 4, left: r.left });
777
+ menuReturn.current = el;
778
+ setOpenMenu(`col:${key}`);
779
+ };
780
+ const addColumn = () => {
781
+ idCounter.current += 1;
782
+ const key = `field-${idCounter.current}`;
783
+ setCols((prev) => [
784
+ ...prev,
785
+ { key, label: t.newColumnLabel(prev.length), type: "text", width: 160 },
786
+ ]);
787
+ };
788
+ const deleteSelected = () => {
789
+ setData((prev) => prev.filter((r) => !selected.has(r.id)));
790
+ setSelected(new Set());
791
+ };
792
+ const exportSelected = () => {
793
+ const picked = data.filter((r) => selected.has(r.id));
794
+ const esc = (s) => `"${s.replace(/"/g, '""')}"`;
795
+ const csv = [
796
+ cols.map((c) => esc(c.label)).join(","),
797
+ ...picked.map((r) => cols.map((c) => esc(cellText(c, r))).join(",")),
798
+ ].join("\n");
799
+ const url = URL.createObjectURL(new Blob([csv], { type: "text/csv;charset=utf-8" }));
800
+ const a = document.createElement("a");
801
+ a.href = url;
802
+ a.download = "records.csv";
803
+ a.click();
804
+ URL.revokeObjectURL(url);
805
+ setOpenMenu(null);
806
+ };
807
+ const entityKey = cols.find((c) => c.type === "entity")?.key;
808
+ // "Add to list" → pin the selected rows (shows a ★ on the entity)
809
+ const addToList = () => {
810
+ setPinned((prev) => new Set([...prev, ...selected]));
811
+ setSelected(new Set());
812
+ };
813
+ // "Send email" → open the mail client addressed to the selected records
814
+ const sendEmail = () => {
815
+ const names = data
816
+ .filter((r) => selected.has(r.id))
817
+ .map((r) => entityKey ? cellText({ key: entityKey, label: "" }, r) : r.id);
818
+ window.open(`mailto:?subject=${encodeURIComponent(names.join(", "))}`);
819
+ };
820
+ // "Run workflow" → a demo automation: duplicate the selected records
821
+ const runWorkflow = () => {
822
+ setData((prev) => {
823
+ const copies = prev
824
+ .filter((r) => selected.has(r.id))
825
+ .map((r) => {
826
+ idCounter.current += 1;
827
+ return { ...r, id: `${r.id}-copy-${idCounter.current}` };
828
+ });
829
+ return [...prev, ...copies];
830
+ });
831
+ setSelected(new Set());
832
+ };
833
+ // single-row actions used by the right-click context menu
834
+ const duplicateRow = (id) => {
835
+ setData((prev) => {
836
+ const i = prev.findIndex((r) => r.id === id);
837
+ if (i < 0)
838
+ return prev;
839
+ idCounter.current += 1;
840
+ const copy = { ...prev[i], id: `${id}-copy-${idCounter.current}` };
841
+ const next = [...prev];
842
+ next.splice(i + 1, 0, copy);
843
+ return next;
844
+ });
845
+ setCtxMenu(null);
846
+ };
847
+ const deleteRow = (id) => {
848
+ setData((prev) => prev.filter((r) => r.id !== id));
849
+ if (selected.has(id)) {
850
+ const next = new Set(selected);
851
+ next.delete(id);
852
+ setSelected(next);
853
+ }
854
+ setCtxMenu(null);
855
+ };
856
+ const copyRow = (id) => {
857
+ const row = data.find((r) => r.id === id);
858
+ const col = cols.find((c) => c.type === "entity");
859
+ if (row && col)
860
+ navigator.clipboard?.writeText(cellText(col, row));
861
+ setCtxMenu(null);
862
+ };
863
+ const pinRow = (id) => {
864
+ setPinned((prev) => new Set([...prev, id]));
865
+ setCtxMenu(null);
866
+ };
867
+ const setAggValue = (key, value) => {
868
+ setAgg((prev) => ({ ...prev, [key]: value }));
869
+ setOpenMenu(null);
870
+ };
871
+ // column resize via pointer drag on the header edge
872
+ const resizeCol = (key, startX, startW) => {
873
+ // guard the whole document during the drag: kill text selection and force a
874
+ // resize cursor so the gesture doesn't select cells or flicker (was buggy)
875
+ const prevSelect = document.body.style.userSelect;
876
+ const prevCursor = document.body.style.cursor;
877
+ document.body.style.userSelect = "none";
878
+ document.body.style.cursor = "col-resize";
879
+ const onMove = (e) => {
880
+ const w = Math.round(Math.max(72, startW + (e.clientX - startX)));
881
+ setCols((prev) => prev.map((c) => (c.key === key ? { ...c, width: w } : c)));
882
+ };
883
+ const onUp = () => {
884
+ document.body.style.userSelect = prevSelect;
885
+ document.body.style.cursor = prevCursor;
886
+ window.removeEventListener("pointermove", onMove);
887
+ window.removeEventListener("pointerup", onUp);
888
+ };
889
+ window.addEventListener("pointermove", onMove);
890
+ window.addEventListener("pointerup", onUp);
891
+ };
892
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordstable_recipe_1.recordstableRecipe)({ className }), ref: rootRef, children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordstable-toolbar", children: [(0, jsx_runtime_1.jsxs)("span", { className: "recordstable-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `recordstable-tool${sorts.length ? " recordstable-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "sort" ? null : "sort"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "sort", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sorts.length
893
+ ? t.sortedBy(cols.find((c) => c.key === sorts[0].key)?.label ?? "", sorts.length - 1)
894
+ : t.sort] }), openMenu === "sort" && ((0, jsx_runtime_1.jsxs)("div", { className: "recordstable-menu recordstable-sortpop", role: "dialog", "aria-label": t.sort, children: [sorts.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "recordstable-sort-empty", children: t.noSorts })) : (sorts.map((s, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "recordstable-sort-row", onDragOver: (e) => {
895
+ if (dragSort !== null)
896
+ e.preventDefault();
897
+ }, onDrop: () => {
898
+ if (dragSort !== null)
899
+ moveSort(dragSort, i);
900
+ setDragSort(null);
901
+ }, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordstable-sort-grip", "aria-label": t.reorderSort, draggable: true, onDragStart: () => setDragSort(i), onDragEnd: () => setDragSort(null), children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)(select_1.Select, { className: "recordstable-sort-field", searchable: true, value: s.key, onValueChange: (k) => setSortField(i, k), options: cols.map((c) => ({
902
+ value: c.key,
903
+ label: c.icon ? ((0, jsx_runtime_1.jsxs)("span", { className: "recordstable-sort-optlabel", children: [(0, jsx_runtime_1.jsx)(c.icon, { size: 15, strokeWidth: 1.75 }), c.label] })) : (c.label),
904
+ keywords: c.label,
905
+ })), "aria-label": t.sortField }), (0, jsx_runtime_1.jsx)(select_1.Select, { className: "recordstable-sort-dir", value: s.dir, onValueChange: (d) => setSortDir(i, d), options: [
906
+ {
907
+ value: "asc",
908
+ label: ((0, jsx_runtime_1.jsxs)("span", { className: "recordstable-sort-optlabel", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpNarrowWide, { size: 15, strokeWidth: 1.75 }), t.ascending] })),
909
+ keywords: t.ascending,
910
+ },
911
+ {
912
+ value: "desc",
913
+ label: ((0, jsx_runtime_1.jsxs)("span", { className: "recordstable-sort-optlabel", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDownWideNarrow, { size: 15, strokeWidth: 1.75 }), t.descending] })),
914
+ keywords: t.descending,
915
+ },
916
+ ], "aria-label": t.sortDirection }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordstable-sort-remove", onClick: () => removeSort(i), "aria-label": t.removeSort, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), (0, jsx_runtime_1.jsx)("div", { className: "recordstable-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-sort-add", onClick: addSort, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), t.addSort] }) }), (0, jsx_runtime_1.jsx)("div", { className: "recordstable-sort-foot", children: (0, jsx_runtime_1.jsxs)("a", { className: "recordstable-sort-learn", href: "https://attio.com/help", target: "_blank", rel: "noreferrer", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CircleHelp, { size: 14, strokeWidth: 1.75 }), t.learnSorting] }) })] }))] }), (0, jsx_runtime_1.jsxs)("span", { className: "recordstable-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `recordstable-tool recordstable-tool--ghost${activeFilters ? " recordstable-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "filter" ? null : "filter"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "filter", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), t.filterCount(activeFilters)] }), openMenu === "filter" && ((0, jsx_runtime_1.jsxs)("div", { className: "recordstable-menu", role: "dialog", "aria-label": t.addFilter, children: [(0, jsx_runtime_1.jsx)("input", { className: "recordstable-menu-input", placeholder: t.filterByPlaceholder, "aria-label": t.filterByField,
917
+ // biome-ignore lint/a11y/noAutofocus: focus the picker on open
918
+ autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) }), cols
919
+ .filter((col) => col.label
920
+ .toLowerCase()
921
+ .includes(filterQuery.trim().toLowerCase()))
922
+ .map((col) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", onClick: () => addFilter(col.key), children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)("span", { style: { width: 15 } })), col.label] }, col.key)))] }))] }), filterConds.map((cond) => {
923
+ const col = cols.find((c) => c.key === cond.key);
924
+ const open = openMenu === `fcond:${cond.id}`;
925
+ const hasValue = !opNeedsValue(cond.op) || cond.value.trim();
926
+ return ((0, jsx_runtime_1.jsxs)("span", { className: "recordstable-toolgroup", children: [(0, jsx_runtime_1.jsxs)("span", { className: "recordstable-fchip", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", "aria-expanded": open, onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [(0, jsx_runtime_1.jsx)("span", { className: "recordstable-fchip-field", children: col?.label }), hasValue ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "recordstable-fchip-op", children: t.op[cond.op] }), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("span", { className: "recordstable-fchip-val", children: cond.value }))] })) : ((0, jsx_runtime_1.jsx)("span", { className: "recordstable-fchip-op", children: t.selectCondition }))] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordstable-fchip-x", "aria-label": `Remove ${col?.label} filter`, onClick: () => removeFilter(cond.id), children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13, strokeWidth: 2 }) })] }), open && ((0, jsx_runtime_1.jsxs)("div", { className: "recordstable-menu", role: "dialog", "aria-label": `${col?.label} condition`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "recordstable-menu-head", children: [col?.label, " condition"] }), OP_ORDER.map((op) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", "data-on": cond.op === op ? "" : undefined, onClick: () => patchFilter(cond.id, { op }), children: [t.op[op], cond.op === op ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "recordstable-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, op))), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("input", { className: "recordstable-menu-input", placeholder: t.filterValuePlaceholder, "aria-label": `${col?.label} filter value`,
927
+ // biome-ignore lint/a11y/noAutofocus: focus the value field
928
+ autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) }))] }))] }, cond.id));
929
+ }), (0, jsx_runtime_1.jsx)("span", { className: "recordstable-spacer" }), (0, jsx_runtime_1.jsx)("span", { className: "recordstable-count", children: t.recordCount(visibleRows.length) }), showNew && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-new", onClick: () => setCreating(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 2, "aria-hidden": "true" }), t.newButton] }))] }), (0, jsx_runtime_1.jsxs)("div", { ref: scrollRef, className: "recordstable-scroll", tabIndex: -1, "data-scrolled": scrolledX ? "" : undefined, onKeyDown: onGridKeyDown, onScroll: (e) => {
930
+ const left = e.currentTarget.scrollLeft > 0;
931
+ setScrolledX((prev) => (prev === left ? prev : left));
932
+ }, children: [(0, jsx_runtime_1.jsxs)("table", { className: "recordstable-table", style: {
933
+ // fill the container; the trailing add-column absorbs the slack (a
934
+ // flexible filler). minWidth keeps real column widths and drives the
935
+ // horizontal scroll once the columns outgrow the container.
936
+ width: "100%",
937
+ minWidth: CHECK_W +
938
+ ADDCOL_W +
939
+ cols.reduce((sum, c) => sum + colWidth(c), 0),
940
+ }, children: [(0, jsx_runtime_1.jsxs)("colgroup", { children: [(0, jsx_runtime_1.jsx)("col", { style: { width: CHECK_W } }), cols.map((col) => ((0, jsx_runtime_1.jsx)("col", { style: { width: colWidth(col) } }, col.key))), (0, jsx_runtime_1.jsx)("col", {})] }), (0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { className: "recordstable-th recordstable-col-check", children: (0, jsx_runtime_1.jsx)("span", { className: "recordstable-check-inner", children: (0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { "aria-label": t.selectAll, checked: allSelected, indeterminate: someSelected, onChange: toggleAll }) }) }), cols.map((col, i) => {
941
+ const Icon = col.icon;
942
+ const sc = sorts.find((s) => s.key === col.key);
943
+ return ((0, jsx_runtime_1.jsxs)("th", { className: `recordstable-th${i === 0 ? " recordstable-col-entity" : ""}`, "data-sorted": sc?.dir, "data-drop": dragCol && dragCol !== col.key && i >= 1 ? "" : undefined, onDragOver: dragCol && i >= 1 ? (e) => e.preventDefault() : undefined, onDrop: dragCol && i >= 1
944
+ ? (e) => {
945
+ e.preventDefault();
946
+ reorderColumn(dragCol, col.key);
947
+ }
948
+ : undefined, children: [renamingCol === col.key ? ((0, jsx_runtime_1.jsx)("input", { className: "recordstable-th-rename",
949
+ // biome-ignore lint/a11y/noAutofocus: focus the rename field the moment it opens
950
+ autoFocus: true, defaultValue: col.label, "aria-label": t.columnLabel, onKeyDown: (e) => {
951
+ if (e.key === "Enter")
952
+ renameColumn(col.key, e.currentTarget.value);
953
+ else if (e.key === "Escape")
954
+ setRenamingCol(null);
955
+ }, onBlur: (e) => renameColumn(col.key, e.currentTarget.value) })) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-th-inner", "data-menu-trigger": true, "aria-haspopup": "menu", draggable: i >= 1, onDragStart: i >= 1
956
+ ? (e) => {
957
+ setDragCol(col.key);
958
+ e.dataTransfer.effectAllowed = "move";
959
+ }
960
+ : undefined, onDragEnd: () => setDragCol(null), onClick: (e) => openColumnMenu(col.key, e.currentTarget), children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 14, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)("span", { className: "recordstable-th-label", children: col.label }), (0, jsx_runtime_1.jsx)("span", { className: "recordstable-sort", children: (0, jsx_runtime_1.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("path", { d: "m4 6 4 4 4-4" }) }) })] })), openMenu === `col:${col.key}` &&
961
+ rootRef.current &&
962
+ (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { ref: menuRef, className: "recordstable-menu recordstable-menu--fixed recordstable-menu--hdr", role: "menu", "aria-label": `${col.label} column`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "asc"), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUp, { size: 15, strokeWidth: 1.75 }), t.sortAscending] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "desc"), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDown, { size: 15, strokeWidth: 1.75 }), t.sortDescending] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i <= 1, onClick: () => moveColumn(col.key, -1), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowLeft, { size: 15, strokeWidth: 1.75 }), t.moveLeft] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i === 0 || i >= cols.length - 1, onClick: () => moveColumn(col.key, 1), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { size: 15, strokeWidth: 1.75 }), t.moveRight] }), (0, jsx_runtime_1.jsx)("div", { className: "recordstable-menu-sep" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => {
963
+ setRenamingCol(col.key);
964
+ setOpenMenu(null);
965
+ }, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }), t.editColumnLabel] }), cols.length > 1 && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => hideColumn(col.key), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.EyeOff, { size: 15, strokeWidth: 1.75 }), t.hideFromView] }))] }), rootRef.current), (0, jsx_runtime_1.jsx)("span", { className: "recordstable-resize", "aria-hidden": "true", onPointerDown: (e) => {
966
+ e.preventDefault();
967
+ e.stopPropagation();
968
+ const th = e.currentTarget
969
+ .parentElement;
970
+ resizeCol(col.key, e.clientX, th ? th.getBoundingClientRect().width : 180);
971
+ }, onClick: (e) => e.stopPropagation() })] }, col.key));
972
+ }), (0, jsx_runtime_1.jsx)("th", { className: "recordstable-th recordstable-addcol", children: allowAddColumn && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: addColumn, "aria-label": t.addColumn, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }) })) })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: visibleRows.map((row) => ((0, jsx_runtime_1.jsxs)("tr", { "data-selected": selected.has(row.id) ? "" : undefined, onContextMenu: (e) => {
973
+ e.preventDefault();
974
+ ctxReturn.current = document.activeElement;
975
+ setCtxMenu({ x: e.clientX, y: e.clientY, id: row.id });
976
+ }, children: [(0, jsx_runtime_1.jsx)("td", { className: "recordstable-td recordstable-col-check", children: (0, jsx_runtime_1.jsx)("span", { className: "recordstable-check-inner", children: (0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { "aria-label": `Select ${row.id}`, checked: selected.has(row.id), onClick: (e) => {
977
+ shiftHeld.current = e.shiftKey;
978
+ }, onChange: () => toggleRow(row.id) }) }) }), cols.map((col, i) => {
979
+ const canEdit = canEditCol(col);
980
+ const isPicker = isPickerCol(col);
981
+ const isEditing = editing?.id === row.id && editing?.key === col.key;
982
+ return ((0, jsx_runtime_1.jsxs)("td", { className: `recordstable-td${i === 0 ? " recordstable-col-entity" : ""}${canEdit ? " recordstable-td--editable" : ""}${isPicker ? " recordstable-td--picker" : ""}`, "data-enriched": col.enriched ? "" : undefined, "data-selected": selCell?.id === row.id && selCell?.key === col.key
983
+ ? ""
984
+ : undefined, onClick: () => {
985
+ if (isPicker)
986
+ return;
987
+ setSelCell({ id: row.id, key: col.key });
988
+ scrollRef.current?.focus({ preventScroll: true });
989
+ }, onDoubleClick: canEdit
990
+ ? () => setEditing({ id: row.id, key: col.key })
991
+ : undefined, children: [isPicker ? (col.type === "status" ? ((0, jsx_runtime_1.jsx)(StatusCell, { column: col, row: row, onChange: (v) => setCell(row.id, col.key, v) })) : col.type === "reference" ? ((0, jsx_runtime_1.jsx)(ReferenceCell, { labels: t, column: col, row: row, onChange: (v) => setCell(row.id, col.key, v) })) : col.type === "tags" ? ((0, jsx_runtime_1.jsx)(TagsCell, { labels: t, column: col, row: row, options: tagOptionsFor(col.key), onChange: (v) => setCell(row.id, col.key, v) })) : ((0, jsx_runtime_1.jsx)(DateCell, { column: col, row: row, onChange: (v) => setCell(row.id, col.key, v) }))) : isEditing ? ((0, jsx_runtime_1.jsx)("input", { className: `recordstable-edit${col.type === "number" ? " recordstable-edit--num" : ""}`,
992
+ // biome-ignore lint/a11y/noAutofocus: focusing the just-opened editor is the expected behavior
993
+ autoFocus: true, defaultValue: cellText(col, row), onBlur: (e) => commitEdit(row.id, col.key, e.target.value), onKeyDown: (e) => {
994
+ if (e.key === "Enter")
995
+ commitEdit(row.id, col.key, e.target.value);
996
+ if (e.key === "Escape")
997
+ setEditing(null);
998
+ } })) : ((0, jsx_runtime_1.jsx)(CellView, { labels: t, column: col, row: row, pinned: i === 0 && pinned.has(row.id), onOpenPage: i === 0 && onOpenRecordPage
999
+ ? () => onOpenRecordPage(row)
1000
+ : undefined, avatarTone: avatarTone })), i === 0 && onOpenRecord && !isEditing && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordstable-open", "aria-label": `Open ${cellText(col, row)}`, onClick: (e) => {
1001
+ e.stopPropagation();
1002
+ onOpenRecord(row);
1003
+ }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpRight, { size: 15, strokeWidth: 1.75 }) }))] }, col.key));
1004
+ }), (0, jsx_runtime_1.jsx)("td", { className: "recordstable-td recordstable-addcol" })] }, row.id))) }), (0, jsx_runtime_1.jsx)("tfoot", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { className: "recordstable-sum recordstable-col-check" }), cols.map((col, i) => {
1005
+ const a = agg[col.key] ?? "none";
1006
+ const res = summarize(col, a, visibleRows);
1007
+ const sumOpen = openMenu === `sum:${col.key}`;
1008
+ return ((0, jsx_runtime_1.jsx)("td", { className: `recordstable-sum${i === 0 ? " recordstable-col-entity" : ""}`, children: (0, jsx_runtime_1.jsxs)("span", { className: "recordstable-toolgroup recordstable-sumgroup", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordstable-sum-btn", "data-empty": res ? undefined : "", "data-menu-trigger": true, "aria-haspopup": "menu", onClick: (e) => {
1009
+ if (sumOpen) {
1010
+ setOpenMenu(null);
1011
+ return;
1012
+ }
1013
+ const r = e.currentTarget.getBoundingClientRect();
1014
+ setMenuPos({
1015
+ bottom: window.innerHeight - r.top + 4,
1016
+ right: window.innerWidth - r.right,
1017
+ });
1018
+ menuReturn.current = e.currentTarget;
1019
+ setOpenMenu(`sum:${col.key}`);
1020
+ }, children: res ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "recordstable-sum-label", children: t.agg[a] }), (0, jsx_runtime_1.jsx)("span", { className: "recordstable-sum-value", children: res.value })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "recordstable-sum-label", children: t.calculate })) }), sumOpen &&
1021
+ rootRef.current &&
1022
+ (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { ref: menuRef, className: "recordstable-menu recordstable-menu--fixed recordstable-menu--up", role: "menu", "aria-label": `${col.label} summary`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: aggOptions(col).map((opt) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitemradio", tabIndex: -1, "aria-checked": a === opt, "data-on": a === opt ? "" : undefined, onClick: () => setAggValue(col.key, opt), children: [t.agg[opt], a === opt ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "recordstable-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, opt))) }), rootRef.current)] }) }, col.key));
1023
+ }), (0, jsx_runtime_1.jsx)("td", { className: "recordstable-sum recordstable-addcol" })] }) })] }), visibleRows.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "recordstable-empty", children: (0, jsx_runtime_1.jsx)(emptystate_1.EmptyState, { variant: "page", icon: activeFilters ? ((0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Inbox, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" })), title: activeFilters ? "No records match that filter" : t.noRecords, description: activeFilters
1024
+ ? "None of your records match the filters that are set."
1025
+ : `Create your first ${cols.find((c) => c.type === "entity")?.label?.toLowerCase() ?? "record"} to get started.`, action: activeFilters ? ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: () => setFilterConds([]), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 15, strokeWidth: 1.75 }), t.clearFilter] })) : ((0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "sm", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), t.newRecord] })) }) })) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-addrow", onClick: addRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), t.newRecord] }))] }), selected.size > 0 && ((0, jsx_runtime_1.jsxs)(bulkbar_1.BulkBar, { count: selected.size, countLabel: "Selected", onClear: () => setSelected(new Set()), children: [(0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.ListPlus, { size: 15, strokeWidth: 1.75 }), onClick: addToList, children: t.addToList }), (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 15, strokeWidth: 1.75 }), onClick: sendEmail, children: t.sendEmail }), (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Workflow, { size: 15, strokeWidth: 1.75 }), onClick: runWorkflow, children: t.runWorkflow }), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.moreActions, trigger: (0, jsx_runtime_1.jsx)(bulkbar_1.BulkBarButton, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 15, strokeWidth: 1.75 }), children: t.more }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Download, { size: 15, strokeWidth: 1.75 }), onSelect: exportSelected, children: t.exportCsv }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, onSelect: deleteSelected, children: t.delete })] })] })), (0, jsx_runtime_1.jsx)(CreateRecordDialog, { labels: t, open: creating, onOpenChange: setCreating, columns: cols, entityLabel: cols.find((c) => c.type === "entity")?.label ?? "record", onCreate: (row) => setData((prev) => [...prev, row]) }), ctxMenu &&
1026
+ (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { ref: ctxRef, className: "recordstable-menu recordstable-ctx", role: "menu", "aria-label": t.recordActions, style: { position: "fixed", top: ctxMenu.y, left: ctxMenu.x }, onKeyDown: onCtxKeyDown, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => copyRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), t.copy] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => duplicateRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CopyPlus, { size: 15, strokeWidth: 1.75 }), t.duplicate] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => pinRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), t.addToList] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item recordstable-menu-item--danger", role: "menuitem", tabIndex: -1, onClick: () => deleteRow(ctxMenu.id), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), t.delete] })] }), rootRef.current ?? document.body)] }));
1027
+ }
1028
+ exports.defaultColumns = [
1029
+ { key: "company", label: "Company", type: "entity", icon: lucide_react_1.Building2 },
1030
+ { key: "domain", label: "Domain", type: "url", icon: lucide_react_1.Globe },
1031
+ {
1032
+ key: "categories",
1033
+ label: "Categories",
1034
+ type: "tags",
1035
+ icon: lucide_react_1.Tag,
1036
+ width: 240,
1037
+ },
1038
+ {
1039
+ key: "employees",
1040
+ label: "Employees",
1041
+ type: "number",
1042
+ icon: lucide_react_1.Users,
1043
+ width: 120,
1044
+ },
1045
+ { key: "location", label: "Location", type: "text", icon: lucide_react_1.MapPin },
1046
+ { key: "stage", label: "Stage", type: "tags", icon: lucide_react_1.TrendingUp, width: 150 },
1047
+ { key: "ceo", label: "CEO", type: "text", icon: lucide_react_1.Briefcase, width: 170 },
1048
+ { key: "twitter", label: "Twitter", type: "url", icon: lucide_react_1.AtSign, width: 150 },
1049
+ {
1050
+ key: "followers",
1051
+ label: "Followers",
1052
+ type: "number",
1053
+ icon: lucide_react_1.Users,
1054
+ width: 120,
1055
+ },
1056
+ {
1057
+ key: "linkedin",
1058
+ label: "LinkedIn",
1059
+ type: "url",
1060
+ icon: lucide_react_1.Globe,
1061
+ width: 180,
1062
+ },
1063
+ {
1064
+ key: "founded",
1065
+ label: "Founded",
1066
+ type: "date",
1067
+ icon: lucide_react_1.Calendar,
1068
+ width: 110,
1069
+ },
1070
+ { key: "arr", label: "Est. ARR", type: "number", icon: lucide_react_1.Hash, width: 130 },
1071
+ ];
1072
+ // biome-ignore format: one row per line keeps the wide demo dataset readable
1073
+ const RAW_ROWS = [
1074
+ { id: "google", company: { name: "Google" }, domain: "google.com", categories: ["Search", "Ads", "Cloud", "AI", "Android", "Maps", "Hardware", "Workspace", "Chrome"], employees: 182000, location: "Mountain View, CA", stage: ["Public"], ceo: "Sundar Pichai", twitter: "twitter.com/google", followers: 29800000, linkedin: "linkedin.com/company/google", founded: "1998", arr: 307000000000 },
1075
+ { id: "stripe", company: { name: "Stripe" }, domain: "stripe.com", categories: ["Payments", "Fintech"], employees: 8000, location: "San Francisco, CA", stage: ["Series I"], ceo: "Patrick Collison", twitter: "twitter.com/stripe", followers: 1200000, linkedin: "linkedin.com/company/stripe", founded: "2010", arr: 16000000000 },
1076
+ { id: "figma", company: { name: "Figma" }, domain: "figma.com", categories: ["Design", "SaaS"], employees: 1300, location: "San Francisco, CA", stage: ["Series E"], ceo: "Dylan Field", twitter: "twitter.com/figma", followers: 540000, linkedin: "linkedin.com/company/figma", founded: "2012", arr: 600000000 },
1077
+ { id: "notion", company: { name: "Notion" }, domain: "notion.so", categories: ["Productivity", "SaaS"], employees: 800, location: "San Francisco, CA", stage: ["Series C"], ceo: "Ivan Zhao", twitter: "twitter.com/notionhq", followers: 480000, linkedin: "linkedin.com/company/notionhq", founded: "2013", arr: 400000000 },
1078
+ { id: "linear", company: { name: "Linear" }, domain: "linear.app", categories: ["Dev tools"], employees: 60, location: "Remote", stage: ["Series B"], ceo: "Karri Saarinen", twitter: "twitter.com/linear", followers: 120000, linkedin: "linkedin.com/company/linear", founded: "2019", arr: 30000000 },
1079
+ { id: "vercel", company: { name: "Vercel" }, domain: "vercel.com", categories: ["Dev tools", "Cloud"], employees: 500, location: "San Francisco, CA", stage: ["Series E"], ceo: "Guillermo Rauch", twitter: "twitter.com/vercel", followers: 310000, linkedin: "linkedin.com/company/vercel", founded: "2015", arr: 100000000 },
1080
+ { id: "openai", company: { name: "OpenAI" }, domain: "openai.com", categories: ["AI", "Research"], employees: 3500, location: "San Francisco, CA", stage: ["Late stage"], ceo: "Sam Altman", twitter: "twitter.com/openai", followers: 4200000, linkedin: "linkedin.com/company/openai", founded: "2015", arr: 3400000000 },
1081
+ { id: "anthropic", company: { name: "Anthropic" }, domain: "anthropic.com", categories: ["AI", "Research"], employees: 1000, location: "San Francisco, CA", stage: ["Series E"], ceo: "Dario Amodei", twitter: "twitter.com/anthropicai", followers: 520000, linkedin: "linkedin.com/company/anthropic", founded: "2021", arr: 1000000000 },
1082
+ ];
1083
+ // Attach a real favicon per company (derived from the domain); the entity cell
1084
+ // falls back to a letter tile if it fails to load.
1085
+ exports.defaultRows = RAW_ROWS.map((r) => ({
1086
+ ...r,
1087
+ company: {
1088
+ ...r.company,
1089
+ avatar: company_logos_1.companyLogos[r.domain] ??
1090
+ `https://www.google.com/s2/favicons?domain=${r.domain}&sz=64`,
1091
+ },
1092
+ }));