@crisp-ui-kit/crisp 0.2.0 → 0.7.0

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