@adea-ai/ui 0.61.0 → 0.62.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 (334) hide show
  1. package/README.md +44 -6
  2. package/dist/LICENSE +201 -0
  3. package/dist/NOTICE +50 -0
  4. package/dist/components/composites/account-menu/index.js +2 -0
  5. package/dist/components/composites/list-row/index.js +2 -0
  6. package/dist/components/composites/settings/index.js +2 -0
  7. package/dist/components/composites/stat/index.js +2 -0
  8. package/dist/components/composites/update-dialog/index.js +2 -0
  9. package/dist/components/composites/workspace-mark/index.js +2 -0
  10. package/dist/components/conversation/index.js +6 -0
  11. package/dist/components/layout/app-shell/index.js +2 -0
  12. package/dist/components/layout/page/index.js +2 -0
  13. package/dist/components/layout/panel/index.js +2 -0
  14. package/dist/components/layout/side-rail/index.js +3 -0
  15. package/dist/components/layout/sidebar-nav/index.js +2 -0
  16. package/dist/components/layout/status-bar/index.js +2 -0
  17. package/dist/components/layout/top-bar/index.js +2 -0
  18. package/dist/components/motion/index.js +2 -2
  19. package/dist/components/theme/index.js +5 -0
  20. package/dist/components/ui/accordion/index.js +2 -0
  21. package/dist/components/ui/alert/index.js +2 -0
  22. package/dist/components/ui/alert-dialog/index.js +2 -0
  23. package/dist/components/ui/aspect-ratio/index.js +2 -0
  24. package/dist/components/ui/avatar/index.js +2 -0
  25. package/dist/components/ui/badge/index.js +2 -0
  26. package/dist/components/ui/board/index.js +2 -0
  27. package/dist/components/ui/breadcrumb/index.js +2 -0
  28. package/dist/components/ui/button/index.js +2 -0
  29. package/dist/components/ui/button-group/index.js +2 -0
  30. package/dist/components/ui/calendar/index.js +2 -0
  31. package/dist/components/ui/card/index.js +2 -0
  32. package/dist/components/ui/carousel/index.js +2 -0
  33. package/dist/components/ui/chart/index.js +2 -0
  34. package/dist/components/ui/checkbox/index.js +2 -0
  35. package/dist/components/ui/code-block/index.js +2 -0
  36. package/dist/components/ui/collapsible/index.js +2 -0
  37. package/dist/components/ui/combobox/index.js +2 -0
  38. package/dist/components/ui/command/index.js +2 -0
  39. package/dist/components/ui/context-menu/index.js +2 -0
  40. package/dist/components/ui/detail-panel/index.js +2 -0
  41. package/dist/components/ui/dialog/index.js +2 -0
  42. package/dist/components/ui/diff-block/index.js +2 -0
  43. package/dist/components/ui/drawer/index.js +2 -0
  44. package/dist/components/ui/dropdown-menu/index.js +2 -0
  45. package/dist/components/ui/empty/index.js +2 -0
  46. package/dist/components/ui/entity-icon/index.js +2 -0
  47. package/dist/components/ui/field/index.js +2 -0
  48. package/dist/components/ui/hover-card/index.js +2 -0
  49. package/dist/components/ui/input/index.js +2 -0
  50. package/dist/components/ui/input-group/index.js +2 -0
  51. package/dist/components/ui/input-otp/index.js +2 -0
  52. package/dist/components/ui/item/index.js +2 -0
  53. package/dist/components/ui/kbd/index.js +2 -0
  54. package/dist/components/ui/label/index.js +2 -0
  55. package/dist/components/ui/menubar/index.js +2 -0
  56. package/dist/components/ui/modal-dialog/index.js +2 -0
  57. package/dist/components/ui/navigation-menu/index.js +2 -0
  58. package/dist/components/ui/pagination/index.js +2 -0
  59. package/dist/components/ui/popover/index.js +2 -0
  60. package/dist/components/ui/progress/index.js +2 -0
  61. package/dist/components/ui/radio-group/index.js +2 -0
  62. package/dist/components/ui/resizable/index.js +2 -0
  63. package/dist/components/ui/scroll-area/index.js +2 -0
  64. package/dist/components/ui/select/index.js +2 -0
  65. package/dist/components/ui/separator/index.js +2 -0
  66. package/dist/components/ui/sheet/index.js +2 -0
  67. package/dist/components/ui/skeleton/index.js +2 -0
  68. package/dist/components/ui/slider/index.js +2 -0
  69. package/dist/components/ui/spinner/index.js +2 -0
  70. package/dist/components/ui/status-chip/index.js +2 -0
  71. package/dist/components/ui/switch/index.js +2 -0
  72. package/dist/components/ui/table/index.js +2 -0
  73. package/dist/components/ui/tabs/index.js +2 -0
  74. package/dist/components/ui/textarea/index.js +2 -0
  75. package/dist/components/ui/toast/index.js +2 -0
  76. package/dist/components/ui/toggle/index.js +2 -0
  77. package/dist/components/ui/toggle-group/index.js +2 -0
  78. package/dist/components/ui/tooltip/index.js +2 -0
  79. package/dist/index.d.ts +2 -4
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +1 -3
  82. package/dist/r/accordion.json +3 -1
  83. package/dist/r/account-menu.json +2 -1
  84. package/dist/r/alert-dialog.json +3 -1
  85. package/dist/r/alert.json +3 -1
  86. package/dist/r/app-shell.json +3 -1
  87. package/dist/r/aspect-ratio.json +3 -1
  88. package/dist/r/avatar.json +3 -1
  89. package/dist/r/badge.json +3 -1
  90. package/dist/r/board.json +3 -1
  91. package/dist/r/breadcrumb.json +3 -1
  92. package/dist/r/button-group.json +2 -1
  93. package/dist/r/button.json +3 -1
  94. package/dist/r/calendar.json +2 -1
  95. package/dist/r/card.json +3 -1
  96. package/dist/r/carousel.json +2 -1
  97. package/dist/r/chart.json +3 -1
  98. package/dist/r/checkbox.json +3 -1
  99. package/dist/r/code-block.json +2 -1
  100. package/dist/r/collapsible.json +3 -1
  101. package/dist/r/combobox.json +3 -1
  102. package/dist/r/command.json +3 -1
  103. package/dist/r/context-menu.json +3 -1
  104. package/dist/r/conversation.json +2 -1
  105. package/dist/r/detail-panel.json +2 -1
  106. package/dist/r/dialog.json +3 -1
  107. package/dist/r/diff-block.json +3 -1
  108. package/dist/r/drawer.json +3 -1
  109. package/dist/r/dropdown-menu.json +3 -1
  110. package/dist/r/empty.json +3 -1
  111. package/dist/r/entity-icon.json +3 -1
  112. package/dist/r/field.json +3 -1
  113. package/dist/r/hover-card.json +3 -1
  114. package/dist/r/input-group.json +3 -1
  115. package/dist/r/input-otp.json +3 -1
  116. package/dist/r/input.json +3 -1
  117. package/dist/r/item.json +3 -1
  118. package/dist/r/kbd.json +3 -1
  119. package/dist/r/label.json +3 -1
  120. package/dist/r/lib.json +50 -0
  121. package/dist/r/list-row.json +3 -1
  122. package/dist/r/menubar.json +3 -1
  123. package/dist/r/modal-dialog.json +2 -1
  124. package/dist/r/motion.json +22 -0
  125. package/dist/r/navigation-menu.json +3 -1
  126. package/dist/r/page.json +3 -1
  127. package/dist/r/pagination.json +3 -1
  128. package/dist/r/panel.json +3 -1
  129. package/dist/r/popover.json +3 -1
  130. package/dist/r/progress.json +3 -1
  131. package/dist/r/radio-group.json +3 -1
  132. package/dist/r/registry.json +320 -73
  133. package/dist/r/resizable.json +3 -1
  134. package/dist/r/scroll-area.json +3 -1
  135. package/dist/r/select.json +3 -1
  136. package/dist/r/separator.json +3 -1
  137. package/dist/r/settings.json +3 -1
  138. package/dist/r/sheet.json +2 -1
  139. package/dist/r/side-rail.json +3 -1
  140. package/dist/r/sidebar-nav.json +3 -1
  141. package/dist/r/skeleton.json +3 -1
  142. package/dist/r/slider.json +3 -1
  143. package/dist/r/spinner.json +3 -1
  144. package/dist/r/src/components/composites/account-menu/account-menu.tsx +150 -0
  145. package/dist/r/src/components/composites/account-menu/index.ts +1 -0
  146. package/dist/r/src/components/composites/list-row/index.ts +1 -0
  147. package/dist/r/src/components/composites/list-row/list-row.tsx +116 -0
  148. package/dist/r/src/components/composites/settings/index.ts +1 -0
  149. package/dist/r/src/components/composites/settings/settings.tsx +142 -0
  150. package/dist/r/src/components/composites/stat/index.ts +9 -0
  151. package/dist/r/src/components/composites/stat/stat.tsx +155 -0
  152. package/dist/r/src/components/composites/update-dialog/index.ts +7 -0
  153. package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +529 -0
  154. package/dist/r/src/components/composites/workspace-mark/index.ts +1 -0
  155. package/dist/r/src/components/composites/workspace-mark/workspace-mark.tsx +72 -0
  156. package/dist/r/src/components/conversation/conversation-avatar.tsx +60 -0
  157. package/dist/r/src/components/conversation/conversation-surface.tsx +107 -0
  158. package/dist/r/src/components/conversation/index.ts +25 -0
  159. package/dist/r/src/components/conversation/message-composer.tsx +239 -0
  160. package/dist/r/src/components/conversation/message-row.tsx +387 -0
  161. package/dist/r/src/components/conversation/thread-panel.tsx +141 -0
  162. package/dist/r/src/components/layout/app-shell/app-shell.tsx +92 -0
  163. package/dist/r/src/components/layout/app-shell/index.ts +1 -0
  164. package/dist/r/src/components/layout/page/index.ts +9 -0
  165. package/dist/r/src/components/layout/page/page.tsx +122 -0
  166. package/dist/r/src/components/layout/panel/index.ts +12 -0
  167. package/dist/r/src/components/layout/panel/panel.tsx +166 -0
  168. package/dist/r/src/components/layout/side-rail/index.ts +12 -0
  169. package/dist/r/src/components/layout/side-rail/side-rail-context.ts +42 -0
  170. package/dist/r/src/components/layout/side-rail/side-rail.tsx +405 -0
  171. package/dist/r/src/components/layout/sidebar-nav/index.ts +14 -0
  172. package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +244 -0
  173. package/dist/r/src/components/layout/status-bar/index.ts +1 -0
  174. package/dist/r/src/components/layout/status-bar/status-bar.tsx +73 -0
  175. package/dist/r/src/components/layout/top-bar/index.ts +10 -0
  176. package/dist/r/src/components/layout/top-bar/top-bar.tsx +166 -0
  177. package/dist/r/src/components/motion/index.ts +2 -0
  178. package/dist/r/src/components/motion/presence.tsx +32 -0
  179. package/dist/r/src/components/theme/appearance-panel.tsx +179 -0
  180. package/dist/r/src/components/theme/index.ts +22 -0
  181. package/dist/r/src/components/theme/theme-picker.tsx +174 -0
  182. package/dist/r/src/components/theme/theme-provider.tsx +309 -0
  183. package/dist/r/src/components/theme/theme-toggle.tsx +64 -0
  184. package/dist/r/src/components/ui/accordion/accordion.tsx +66 -0
  185. package/dist/r/src/components/ui/accordion/index.ts +1 -0
  186. package/dist/r/src/components/ui/alert/alert.tsx +95 -0
  187. package/dist/r/src/components/ui/alert/index.ts +1 -0
  188. package/dist/r/src/components/ui/alert-dialog/alert-dialog.tsx +176 -0
  189. package/dist/r/src/components/ui/alert-dialog/index.ts +13 -0
  190. package/dist/r/src/components/ui/aspect-ratio/aspect-ratio.tsx +38 -0
  191. package/dist/r/src/components/ui/aspect-ratio/index.ts +1 -0
  192. package/dist/r/src/components/ui/avatar/avatar.tsx +114 -0
  193. package/dist/r/src/components/ui/avatar/index.ts +9 -0
  194. package/dist/r/src/components/ui/badge/badge.tsx +59 -0
  195. package/dist/r/src/components/ui/badge/index.ts +1 -0
  196. package/dist/r/src/components/ui/board/board.tsx +254 -0
  197. package/dist/r/src/components/ui/board/index.ts +10 -0
  198. package/dist/r/src/components/ui/breadcrumb/breadcrumb.tsx +117 -0
  199. package/dist/r/src/components/ui/breadcrumb/index.ts +9 -0
  200. package/dist/r/src/components/ui/button/button.tsx +89 -0
  201. package/dist/r/src/components/ui/button/index.ts +1 -0
  202. package/dist/r/src/components/ui/button-group/button-group.tsx +92 -0
  203. package/dist/r/src/components/ui/button-group/index.ts +6 -0
  204. package/dist/r/src/components/ui/calendar/calendar.tsx +277 -0
  205. package/dist/r/src/components/ui/calendar/index.ts +1 -0
  206. package/dist/r/src/components/ui/card/card.tsx +82 -0
  207. package/dist/r/src/components/ui/card/index.ts +10 -0
  208. package/dist/r/src/components/ui/carousel/carousel.tsx +209 -0
  209. package/dist/r/src/components/ui/carousel/index.ts +1 -0
  210. package/dist/r/src/components/ui/chart/chart.tsx +413 -0
  211. package/dist/r/src/components/ui/chart/index.ts +17 -0
  212. package/dist/r/src/components/ui/checkbox/checkbox.tsx +99 -0
  213. package/dist/r/src/components/ui/checkbox/index.ts +1 -0
  214. package/dist/r/src/components/ui/code-block/code-block.tsx +238 -0
  215. package/dist/r/src/components/ui/code-block/index.ts +1 -0
  216. package/dist/r/src/components/ui/collapsible/collapsible.tsx +54 -0
  217. package/dist/r/src/components/ui/collapsible/index.ts +1 -0
  218. package/dist/r/src/components/ui/combobox/combobox.tsx +142 -0
  219. package/dist/r/src/components/ui/combobox/index.ts +10 -0
  220. package/dist/r/src/components/ui/command/command.tsx +214 -0
  221. package/dist/r/src/components/ui/command/index.ts +12 -0
  222. package/dist/r/src/components/ui/context-menu/context-menu.tsx +163 -0
  223. package/dist/r/src/components/ui/context-menu/index.ts +15 -0
  224. package/dist/r/src/components/ui/detail-panel/detail-panel.tsx +161 -0
  225. package/dist/r/src/components/ui/detail-panel/index.ts +9 -0
  226. package/dist/r/src/components/ui/dialog/dialog.tsx +138 -0
  227. package/dist/r/src/components/ui/dialog/index.ts +14 -0
  228. package/dist/r/src/components/ui/diff-block/diff-block.tsx +286 -0
  229. package/dist/r/src/components/ui/diff-block/index.ts +11 -0
  230. package/dist/r/src/components/ui/drawer/drawer.tsx +144 -0
  231. package/dist/r/src/components/ui/drawer/index.ts +13 -0
  232. package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +203 -0
  233. package/dist/r/src/components/ui/dropdown-menu/index.ts +18 -0
  234. package/dist/r/src/components/ui/empty/empty.tsx +104 -0
  235. package/dist/r/src/components/ui/empty/index.ts +10 -0
  236. package/dist/r/src/components/ui/entity-icon/entity-icon.tsx +154 -0
  237. package/dist/r/src/components/ui/entity-icon/index.ts +1 -0
  238. package/dist/r/src/components/ui/field/field.tsx +131 -0
  239. package/dist/r/src/components/ui/field/index.ts +11 -0
  240. package/dist/r/src/components/ui/hover-card/hover-card.tsx +48 -0
  241. package/dist/r/src/components/ui/hover-card/index.ts +1 -0
  242. package/dist/r/src/components/ui/input/index.ts +1 -0
  243. package/dist/r/src/components/ui/input/input.tsx +41 -0
  244. package/dist/r/src/components/ui/input-group/index.ts +8 -0
  245. package/dist/r/src/components/ui/input-group/input-group.tsx +111 -0
  246. package/dist/r/src/components/ui/input-otp/index.ts +1 -0
  247. package/dist/r/src/components/ui/input-otp/input-otp.tsx +108 -0
  248. package/dist/r/src/components/ui/item/index.ts +10 -0
  249. package/dist/r/src/components/ui/item/item.tsx +146 -0
  250. package/dist/r/src/components/ui/kbd/index.ts +1 -0
  251. package/dist/r/src/components/ui/kbd/kbd.tsx +42 -0
  252. package/dist/r/src/components/ui/label/index.ts +1 -0
  253. package/dist/r/src/components/ui/label/label.tsx +36 -0
  254. package/dist/r/src/components/ui/menubar/index.ts +16 -0
  255. package/dist/r/src/components/ui/menubar/menubar.tsx +190 -0
  256. package/dist/r/src/components/ui/modal-dialog/index.ts +1 -0
  257. package/dist/r/src/components/ui/modal-dialog/modal-dialog.tsx +95 -0
  258. package/dist/r/src/components/ui/navigation-menu/index.ts +13 -0
  259. package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +229 -0
  260. package/dist/r/src/components/ui/pagination/index.ts +8 -0
  261. package/dist/r/src/components/ui/pagination/pagination.tsx +144 -0
  262. package/dist/r/src/components/ui/popover/index.ts +10 -0
  263. package/dist/r/src/components/ui/popover/popover.tsx +81 -0
  264. package/dist/r/src/components/ui/progress/index.ts +1 -0
  265. package/dist/r/src/components/ui/progress/progress.tsx +67 -0
  266. package/dist/r/src/components/ui/radio-group/index.ts +1 -0
  267. package/dist/r/src/components/ui/radio-group/radio-group.tsx +78 -0
  268. package/dist/r/src/components/ui/resizable/index.ts +6 -0
  269. package/dist/r/src/components/ui/resizable/resizable.tsx +74 -0
  270. package/dist/r/src/components/ui/scroll-area/index.ts +1 -0
  271. package/dist/r/src/components/ui/scroll-area/scroll-area.tsx +66 -0
  272. package/dist/r/src/components/ui/select/index.ts +11 -0
  273. package/dist/r/src/components/ui/select/select.tsx +188 -0
  274. package/dist/r/src/components/ui/separator/index.ts +1 -0
  275. package/dist/r/src/components/ui/separator/separator.tsx +40 -0
  276. package/dist/r/src/components/ui/sheet/index.ts +13 -0
  277. package/dist/r/src/components/ui/sheet/sheet.tsx +132 -0
  278. package/dist/r/src/components/ui/skeleton/index.ts +1 -0
  279. package/dist/r/src/components/ui/skeleton/skeleton.tsx +28 -0
  280. package/dist/r/src/components/ui/slider/index.ts +1 -0
  281. package/dist/r/src/components/ui/slider/slider.tsx +81 -0
  282. package/dist/r/src/components/ui/spinner/index.ts +1 -0
  283. package/dist/r/src/components/ui/spinner/spinner.tsx +58 -0
  284. package/dist/r/src/components/ui/status-chip/index.ts +8 -0
  285. package/dist/r/src/components/ui/status-chip/status-chip.tsx +142 -0
  286. package/dist/r/src/components/ui/switch/index.ts +1 -0
  287. package/dist/r/src/components/ui/switch/switch.tsx +100 -0
  288. package/dist/r/src/components/ui/table/index.ts +11 -0
  289. package/dist/r/src/components/ui/table/table.tsx +125 -0
  290. package/dist/r/src/components/ui/tabs/index.ts +8 -0
  291. package/dist/r/src/components/ui/tabs/tabs.tsx +77 -0
  292. package/dist/r/src/components/ui/textarea/index.ts +1 -0
  293. package/dist/r/src/components/ui/textarea/textarea.tsx +36 -0
  294. package/dist/r/src/components/ui/toast/index.ts +1 -0
  295. package/dist/r/src/components/ui/toast/toast.tsx +220 -0
  296. package/dist/r/src/components/ui/toggle/index.ts +1 -0
  297. package/dist/r/src/components/ui/toggle/toggle.tsx +60 -0
  298. package/dist/r/src/components/ui/toggle-group/index.ts +6 -0
  299. package/dist/r/src/components/ui/toggle-group/toggle-group.tsx +66 -0
  300. package/dist/r/src/components/ui/tooltip/index.ts +9 -0
  301. package/dist/r/src/components/ui/tooltip/tooltip.tsx +102 -0
  302. package/dist/r/src/lib/keyed-rows.ts +68 -0
  303. package/dist/r/src/lib/overlay.ts +70 -0
  304. package/dist/r/src/lib/themes.ts +490 -0
  305. package/dist/r/src/lib/tokens.ts +688 -0
  306. package/dist/r/src/lib/utils.ts +76 -0
  307. package/dist/r/src/lib/variants.ts +62 -0
  308. package/dist/r/src/lib/version-notes.ts +65 -0
  309. package/dist/r/src/styles/base.css +644 -0
  310. package/dist/r/src/styles/fonts.css +26 -0
  311. package/dist/r/src/styles/globals.css +18 -0
  312. package/dist/r/src/styles/theme.css +864 -0
  313. package/dist/r/stat.json +3 -1
  314. package/dist/r/status-bar.json +3 -1
  315. package/dist/r/status-chip.json +2 -1
  316. package/dist/r/switch.json +3 -1
  317. package/dist/r/table.json +3 -1
  318. package/dist/r/tabs.json +3 -1
  319. package/dist/r/textarea.json +3 -1
  320. package/dist/r/theming.json +42 -0
  321. package/dist/r/toast.json +3 -1
  322. package/dist/r/toggle-group.json +2 -1
  323. package/dist/r/toggle.json +3 -1
  324. package/dist/r/tooltip.json +3 -1
  325. package/dist/r/top-bar.json +3 -1
  326. package/dist/r/update-dialog.json +2 -1
  327. package/dist/r/workspace-mark.json +2 -1
  328. package/package.json +10 -9
  329. package/registry.json +320 -73
  330. package/src/index.ts +2 -4
  331. package/dist/components/ui/diff-block/diff-block.test.d.ts +0 -2
  332. package/dist/components/ui/diff-block/diff-block.test.d.ts.map +0 -1
  333. package/dist/lib/keyed-rows.test.d.ts +0 -2
  334. package/dist/lib/keyed-rows.test.d.ts.map +0 -1
@@ -0,0 +1,72 @@
1
+ import type { ComponentProps, JSX } from 'solid-js'
2
+ import { Show, splitProps } from 'solid-js'
3
+ import { cn } from '../../../lib/utils'
4
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip/tooltip'
5
+
6
+ /**
7
+ * WorkspaceMark.
8
+ *
9
+ * The mark for one workspace in a rail: an initial or a glyph, a ring while it is
10
+ * active, and a badge for something unread. It is deliberately not an `Avatar` —
11
+ * a workspace is not a person, it does not need an image or initials derived from a
12
+ * name, and a rail of them needs an *active* treatment an avatar does not have.
13
+ *
14
+ * The mark is square with a rounded corner rather than round, which is what
15
+ * distinguishes a workspace from a person at a glance in a rail that holds both.
16
+ */
17
+ export type WorkspaceMarkProps = ComponentProps<'button'> & {
18
+ /** The workspace's name. Used for the label, the initial, and the tooltip. */
19
+ name: string
20
+ /** Draw the active ring. */
21
+ active?: boolean
22
+ /** A count or a dot, drawn at the corner. */
23
+ badge?: JSX.Element
24
+ /** Replace the initial with a glyph. */
25
+ children?: JSX.Element
26
+ }
27
+
28
+ export function WorkspaceMark(props: WorkspaceMarkProps) {
29
+ const [local, rest] = splitProps(props, ['class', 'name', 'active', 'badge', 'children'])
30
+
31
+ const mark = (
32
+ <button
33
+ type="button"
34
+ aria-label={local.name}
35
+ aria-current={local.active ? 'true' : undefined}
36
+ data-active={local.active ? '' : undefined}
37
+ class={cn(
38
+ 'relative flex size-9 shrink-0 items-center justify-center rounded-lg border text-sm font-semibold',
39
+ 'transition-[color,background-color,border-color,transform] ease-out outline-none select-none',
40
+ 'hover:scale-105 active:scale-95',
41
+ 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
42
+ 'disabled:pointer-events-none disabled:opacity-50',
43
+ local.active
44
+ ? 'border-primary bg-primary-subtle text-primary'
45
+ : 'border-border bg-card text-muted-foreground hover:text-foreground',
46
+ local.class
47
+ )}
48
+ {...rest}
49
+ >
50
+ <Show
51
+ when={local.children}
52
+ fallback={<span aria-hidden="true">{local.name.charAt(0).toUpperCase()}</span>}
53
+ >
54
+ {local.children}
55
+ </Show>
56
+ <Show when={local.badge}>
57
+ <span class="absolute -end-0.5 -top-0.5 flex items-center justify-center">
58
+ {local.badge}
59
+ </span>
60
+ </Show>
61
+ </button>
62
+ )
63
+
64
+ return (
65
+ <Tooltip placement="right">
66
+ <TooltipTrigger as="span" class="contents">
67
+ {mark}
68
+ </TooltipTrigger>
69
+ <TooltipContent>{local.name}</TooltipContent>
70
+ </Tooltip>
71
+ )
72
+ }
@@ -0,0 +1,60 @@
1
+ import type { ComponentProps } from 'solid-js'
2
+ import { Show, splitProps } from 'solid-js'
3
+ import { Dynamic } from 'solid-js/web'
4
+ import { Bot, Sparkles, User } from 'lucide-solid'
5
+ import { cn } from '../../lib/utils'
6
+
7
+ /**
8
+ * ConversationAvatar.
9
+ *
10
+ * The speaker's mark in a transcript. Three kinds, and the distinction matters
11
+ * visually: a person, an agent, and the system itself are different *voices*, and a
12
+ * transcript where all three look alike is one the reader has to decode line by
13
+ * line.
14
+ *
15
+ * The avatar is decorative — the speaker's name is in the message's accessible name
16
+ * — so it is `aria-hidden` and carries no text of its own. A caller with a real
17
+ * image passes it as children.
18
+ */
19
+ export type ConversationAvatarKind = 'user' | 'agent' | 'system'
20
+
21
+ export type ConversationAvatarProps = ComponentProps<'span'> & {
22
+ kind: ConversationAvatarKind
23
+ /** Draw the mark larger, for the sender's own messages. */
24
+ size?: 'md' | 'lg'
25
+ }
26
+
27
+ const GLYPHS = { user: User, agent: Bot, system: Sparkles } as const
28
+
29
+ export function ConversationAvatar(props: ConversationAvatarProps) {
30
+ const [local, rest] = splitProps(props, ['class', 'kind', 'size', 'children'])
31
+
32
+ return (
33
+ <span
34
+ data-slot="conversation-avatar"
35
+ aria-hidden="true"
36
+ class={cn(
37
+ 'grid shrink-0 place-items-center rounded-full border border-border',
38
+ local.size === 'lg' ? 'size-8' : 'size-7',
39
+ {
40
+ 'bg-primary-subtle text-primary': local.kind === 'user',
41
+ 'bg-card text-foreground': local.kind === 'agent',
42
+ 'bg-muted text-muted-foreground': local.kind === 'system',
43
+ },
44
+ local.class
45
+ )}
46
+ {...rest}
47
+ >
48
+ <Show
49
+ when={local.children}
50
+ fallback={
51
+ <span class="grid size-4 place-items-center [&_svg]:size-4">
52
+ <Dynamic component={GLYPHS[local.kind]} />
53
+ </span>
54
+ }
55
+ >
56
+ {local.children}
57
+ </Show>
58
+ </span>
59
+ )
60
+ }
@@ -0,0 +1,107 @@
1
+ import { ArrowDown } from 'lucide-solid'
2
+ import type { ComponentProps, JSX } from 'solid-js'
3
+ import { Show, createSignal, onCleanup, splitProps } from 'solid-js'
4
+ import { cn } from '../../lib/utils'
5
+ import { Button } from '../ui/button/button'
6
+
7
+ /**
8
+ * ConversationSurface.
9
+ *
10
+ * The scrolling transcript. Its job is small and it is the difference between a
11
+ * transcript that stays readable as it grows and one that fights the reader:
12
+ *
13
+ * - **Stick to the bottom.** New messages scroll into view only when the reader is
14
+ * already at the bottom. Someone reading back through history must not be yanked
15
+ * to the end by an arriving message, and that is the single most common way a
16
+ * chat surface is made unusable.
17
+ * - **Offer a way back.** Once the reader is not at the bottom, a control appears
18
+ * that returns them and says how much they have missed.
19
+ * - **`overscroll-contain`.** The transcript scrolls to its end and stops, rather
20
+ * than dragging the pane behind it — which in a desktop shell means the whole
21
+ * window moves.
22
+ */
23
+ export type ConversationSurfaceProps = ComponentProps<'div'> & {
24
+ /** How far from the bottom still counts as "at the bottom", in px. */
25
+ threshold?: number
26
+ /** Rendered above the transcript when there is nothing in it. */
27
+ empty?: JSX.Element
28
+ /** A header that scrolls with the transcript, e.g. a day divider or a banner. */
29
+ header?: JSX.Element
30
+ }
31
+
32
+ /** How far the reader is from the end of the transcript, in px. */
33
+ function distanceFromBottom(element: HTMLDivElement): number {
34
+ return element.scrollHeight - element.scrollTop - element.clientHeight
35
+ }
36
+
37
+ export function ConversationSurface(props: ConversationSurfaceProps) {
38
+ const [local, rest] = splitProps(props, ['class', 'threshold', 'empty', 'header', 'children'])
39
+
40
+ const [atBottom, setAtBottom] = createSignal(true)
41
+ const [missed, setMissed] = createSignal(0)
42
+ let scroller: HTMLDivElement | undefined
43
+
44
+ const onScroll = () => {
45
+ if (!scroller) return
46
+ const distance = distanceFromBottom(scroller)
47
+ const threshold = local.threshold ?? 80
48
+ setAtBottom(distance <= threshold)
49
+ setMissed(distance <= threshold ? 0 : Math.round(distance / 96))
50
+ }
51
+
52
+ const jumpToLatest = () => {
53
+ scroller?.scrollTo({ top: scroller.scrollHeight, behavior: 'smooth' })
54
+ setAtBottom(true)
55
+ setMissed(0)
56
+ }
57
+
58
+ // An arriving message while the reader is at the bottom should follow it. While
59
+ // they are not, it must not.
60
+ const observer = () => {
61
+ if (!scroller) return
62
+ const mutation = new MutationObserver(() => {
63
+ if (atBottom()) scroller?.scrollTo({ top: scroller.scrollHeight })
64
+ else onScroll()
65
+ })
66
+ mutation.observe(scroller, { childList: true, subtree: true })
67
+ onCleanup(() => mutation.disconnect())
68
+ }
69
+
70
+ return (
71
+ <div
72
+ data-slot="conversation-surface"
73
+ class={cn('relative flex min-h-0 flex-1 flex-col', local.class)}
74
+ >
75
+ <div
76
+ ref={(element) => {
77
+ scroller = element
78
+ observer()
79
+ }}
80
+ onScroll={onScroll}
81
+ class="min-h-0 flex-1 overflow-y-auto overscroll-contain"
82
+ {...rest}
83
+ >
84
+ <Show when={local.header}>{local.header}</Show>
85
+ <Show when={local.children} fallback={<div class="p-4">{local.empty}</div>}>
86
+ {local.children}
87
+ </Show>
88
+ </div>
89
+
90
+ <Show when={!atBottom()}>
91
+ <div class="pointer-events-none absolute inset-x-0 bottom-3 flex justify-center">
92
+ <Button
93
+ size="sm"
94
+ variant="secondary"
95
+ class="pointer-events-auto gap-1.5 shadow-md"
96
+ onClick={jumpToLatest}
97
+ >
98
+ <ArrowDown />
99
+ <Show when={missed() > 0} fallback={<>Jump to latest</>}>
100
+ {missed()} new
101
+ </Show>
102
+ </Button>
103
+ </div>
104
+ </Show>
105
+ </div>
106
+ )
107
+ }
@@ -0,0 +1,25 @@
1
+ export {
2
+ ConversationAvatar,
3
+ type ConversationAvatarKind,
4
+ type ConversationAvatarProps,
5
+ } from './conversation-avatar'
6
+ export { ConversationSurface, type ConversationSurfaceProps } from './conversation-surface'
7
+ export {
8
+ ComposerAttachmentButton,
9
+ ComposerHint,
10
+ MessageComposer,
11
+ type MessageComposerProps,
12
+ } from './message-composer'
13
+ export {
14
+ MessageBody,
15
+ MessageDayDivider,
16
+ MessageGroup,
17
+ MessageRow,
18
+ type MessageRowProps,
19
+ } from './message-row'
20
+ export {
21
+ AttachmentCard,
22
+ ThreadPanel,
23
+ type AttachmentCardProps,
24
+ type ThreadPanelProps,
25
+ } from './thread-panel'
@@ -0,0 +1,239 @@
1
+ import { CornerDownLeft, Paperclip, Send, X } from 'lucide-solid'
2
+ import type { ComponentProps, JSX } from 'solid-js'
3
+ import { Show, createSignal, splitProps } from 'solid-js'
4
+ import { cn } from '../../lib/utils'
5
+ import { Button } from '../ui/button/button'
6
+ import { Kbd } from '../ui/kbd/kbd'
7
+ import { Spinner } from '../ui/spinner/spinner'
8
+ import { Textarea } from '../ui/textarea/textarea'
9
+
10
+ /**
11
+ * MessageComposer.
12
+ *
13
+ * The field a message is written in. It is a form, and it behaves like one: Enter
14
+ * sends, Shift+Enter breaks the line, Escape clears the error.
15
+ *
16
+ * ## What it does not know
17
+ *
18
+ * No mention model, no attachment model, no dictation, no idempotency keys. The
19
+ * first version of this composer was written against adea's agent and artifact
20
+ * types, which meant it could only ever be used by adea. Here the attachment control
21
+ * is a *slot*, the mention menu is a slot, and a caller that has an agent model
22
+ * renders one; a caller that does not gets a composer without it.
23
+ *
24
+ * ## The three things it does own
25
+ *
26
+ * **Enter sends, Shift+Enter breaks the line.** That is the convention every chat
27
+ * application shares, and getting it wrong is the single most irritating thing a
28
+ * composer can do. `⌘`/`Ctrl`+Enter is accepted too, because someone arriving from an
29
+ * editor will try it.
30
+ *
31
+ * **The send button is disabled when there is nothing to send.** A control that
32
+ * accepts a press and does nothing is worse than one that says it cannot.
33
+ *
34
+ * **The status region is `aria-live`.** "Sending…" and a failure both land there, so
35
+ * a screen reader hears the outcome without the composer stealing focus.
36
+ */
37
+ export type MessageComposerProps = Omit<ComponentProps<'form'>, 'onSubmit'> & {
38
+ /** The draft. Controlled: the caller owns the text, because it usually persists. */
39
+ value: string
40
+ onValueChange: (value: string) => void
41
+ /** Called on send. A rejection is surfaced as an error and the draft is kept. */
42
+ onSubmit: () => void | Promise<void>
43
+ placeholder?: string
44
+ /** Disable the whole composer, e.g. while the channel is read-only. */
45
+ disabled?: boolean
46
+ /** Label the send action for a context other than a message, e.g. a reply. */
47
+ submitLabel?: string
48
+ /** Show the keyboard hint under the field. */
49
+ showHint?: boolean
50
+ /** A leading control group, e.g. an attach button. */
51
+ leading?: JSX.Element
52
+ /** A trailing group before the send button, e.g. a dictation toggle. */
53
+ trailing?: JSX.Element
54
+ /** A menu rendered above the field, e.g. mention suggestions. */
55
+ menu?: JSX.Element
56
+ /** The reply target, drawn as a strip above the field. */
57
+ replyTo?: { label: string; onDismiss: () => void }
58
+ }
59
+
60
+ export function MessageComposer(props: MessageComposerProps) {
61
+ const [local, rest] = splitProps(props, [
62
+ 'class',
63
+ 'value',
64
+ 'onValueChange',
65
+ 'onSubmit',
66
+ 'placeholder',
67
+ 'disabled',
68
+ 'submitLabel',
69
+ 'showHint',
70
+ 'leading',
71
+ 'trailing',
72
+ 'menu',
73
+ 'replyTo',
74
+ ])
75
+
76
+ const [sending, setSending] = createSignal(false)
77
+ const [error, setError] = createSignal<string | null>(null)
78
+
79
+ const canSend = () => local.value.trim().length > 0 && !local.disabled && !sending()
80
+
81
+ const send = async () => {
82
+ if (!canSend()) return
83
+ setSending(true)
84
+ setError(null)
85
+ try {
86
+ await local.onSubmit()
87
+ } catch {
88
+ // The draft is deliberately left in place. A send that fails and clears the
89
+ // field loses the user's work, which is the worst outcome available here.
90
+ setError('Message not sent. Your draft is still here; retry when the connection recovers.')
91
+ } finally {
92
+ setSending(false)
93
+ }
94
+ }
95
+
96
+ const onKeyDown: JSX.EventHandlerUnion<HTMLTextAreaElement, KeyboardEvent> = (event) => {
97
+ if (event.key === 'Escape' && error()) {
98
+ setError(null)
99
+ return
100
+ }
101
+ if (event.key !== 'Enter') return
102
+ // Shift breaks the line; a bare Enter, or a modified one, sends.
103
+ if (event.shiftKey && !event.metaKey && !event.ctrlKey) return
104
+ event.preventDefault()
105
+ void send()
106
+ }
107
+
108
+ return (
109
+ <form
110
+ data-slot="message-composer"
111
+ class={cn('flex flex-col gap-2', local.class)}
112
+ onSubmit={(event) => {
113
+ event.preventDefault()
114
+ void send()
115
+ }}
116
+ {...rest}
117
+ >
118
+ <Show when={local.menu}>
119
+ <div>{local.menu}</div>
120
+ </Show>
121
+
122
+ <Show when={local.replyTo}>
123
+ {(reply) => (
124
+ <div class="bg-muted text-muted-foreground flex items-center gap-2 rounded-md px-2.5 py-1.5 text-xs">
125
+ <span class="min-w-0 flex-1 truncate">Replying to {reply().label}</span>
126
+ <Button
127
+ type="button"
128
+ variant="ghost"
129
+ size="icon-2xs"
130
+ aria-label="Cancel reply"
131
+ onClick={() => reply().onDismiss()}
132
+ >
133
+ <X />
134
+ </Button>
135
+ </div>
136
+ )}
137
+ </Show>
138
+
139
+ <div
140
+ class={cn(
141
+ 'border-input bg-card flex flex-col gap-1 rounded-lg border p-2',
142
+ 'transition-[color,box-shadow,border-color] ease-out',
143
+ 'focus-within:border-ring focus-within:ring-3 focus-within:ring-primary-subtle',
144
+ local.disabled && 'opacity-50'
145
+ )}
146
+ >
147
+ <Textarea
148
+ value={local.value}
149
+ onInput={(event) => local.onValueChange(event.currentTarget.value)}
150
+ onKeyDown={onKeyDown}
151
+ placeholder={local.placeholder ?? 'Write a message…'}
152
+ disabled={local.disabled}
153
+ rows={1}
154
+ aria-label={local.submitLabel ? `${local.submitLabel} message` : 'Message'}
155
+ class="min-h-9 resize-none border-0 bg-transparent p-0 text-sm shadow-none focus-visible:ring-0"
156
+ />
157
+
158
+ <div class="flex items-center gap-1">
159
+ <Show when={local.leading}>{local.leading}</Show>
160
+ <div class="ms-auto flex items-center gap-1">
161
+ <Show when={local.trailing}>{local.trailing}</Show>
162
+ <Button
163
+ type="submit"
164
+ size="icon-sm"
165
+ disabled={!canSend()}
166
+ aria-label={sending() ? 'Sending message' : 'Send message'}
167
+ >
168
+ <Show when={sending()} fallback={<Send />}>
169
+ <Spinner size="sm" label={false} class="text-primary-foreground" />
170
+ </Show>
171
+ </Button>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <Show when={local.showHint}>
177
+ <p class="text-muted-foreground flex items-center gap-1.5 text-2xs">
178
+ <Kbd>Enter</Kbd> to send
179
+ <span aria-hidden="true">·</span>
180
+ <Kbd>Shift</Kbd> <Kbd>Enter</Kbd> for a new line
181
+ </p>
182
+ </Show>
183
+
184
+ {/* A live region, so the outcome is announced without moving focus out of the
185
+ field the user is typing in. */}
186
+ <div aria-live="polite" class="min-h-0">
187
+ <Show when={error()}>
188
+ {(message) => <p class="text-destructive text-xs">{message()}</p>}
189
+ </Show>
190
+ </div>
191
+ </form>
192
+ )
193
+ }
194
+
195
+ /**
196
+ * The attachment control.
197
+ *
198
+ * A slot rather than a built-in, because what can be attached is the application's
199
+ * business — but the *button* is shared, so it looks and behaves the same in both.
200
+ * The count is announced, since a paperclip alone does not say whether anything is
201
+ * attached.
202
+ */
203
+ export function ComposerAttachmentButton(
204
+ props: ComponentProps<typeof Button> & { count?: number; open?: boolean }
205
+ ) {
206
+ const [local, rest] = splitProps(props, ['count', 'open', 'children'])
207
+
208
+ return (
209
+ <Button
210
+ type="button"
211
+ variant="ghost"
212
+ size="icon-sm"
213
+ aria-label={local.count ? `${local.count} attached, add an attachment` : 'Add an attachment'}
214
+ aria-expanded={local.open}
215
+ class="text-muted-foreground"
216
+ {...rest}
217
+ >
218
+ {local.children ?? <Paperclip />}
219
+ </Button>
220
+ )
221
+ }
222
+
223
+ /** The hint a composer shows when the field is empty, as a one-line prompt. */
224
+ export function ComposerHint(props: ComponentProps<'p'>) {
225
+ const [local, rest] = splitProps(props, ['class', 'children'])
226
+ return (
227
+ <p
228
+ class={cn('text-muted-foreground flex items-center gap-1.5 text-2xs', local.class)}
229
+ {...rest}
230
+ >
231
+ {local.children ?? (
232
+ <>
233
+ <CornerDownLeft aria-hidden="true" class="size-3" />
234
+ <Kbd>Enter</Kbd> to send
235
+ </>
236
+ )}
237
+ </p>
238
+ )
239
+ }