@konce-pt/react 0.12.0 → 0.15.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 (378) hide show
  1. package/README.md +122 -64
  2. package/dist/accordion/accordion.d.ts +14 -6
  3. package/dist/accordion/accordion.js +14 -7
  4. package/dist/accordion/llms.txt +22 -10
  5. package/dist/alert/alert.d.ts +2 -2
  6. package/dist/alert/alert.js +2 -2
  7. package/dist/alert/llms.txt +10 -10
  8. package/dist/app-shell/app-shell.d.ts +14 -14
  9. package/dist/app-shell/app-shell.js +11 -11
  10. package/dist/app-shell/llms.txt +13 -13
  11. package/dist/auth/auth-types.d.ts +6 -6
  12. package/dist/auth/auth.d.ts +2 -2
  13. package/dist/auth/auth.js +7 -7
  14. package/dist/auth/llms.txt +9 -9
  15. package/dist/autocomplete/autocomplete.d.ts +19 -6
  16. package/dist/autocomplete/autocomplete.js +23 -10
  17. package/dist/autocomplete/llms.txt +16 -5
  18. package/dist/avatar/avatar.d.ts +6 -6
  19. package/dist/avatar/avatar.js +5 -5
  20. package/dist/avatar/llms.txt +10 -10
  21. package/dist/avatar-group/avatar-group.d.ts +4 -4
  22. package/dist/avatar-group/avatar-group.js +3 -3
  23. package/dist/avatar-group/llms.txt +8 -8
  24. package/dist/backdrop/backdrop.d.ts +7 -7
  25. package/dist/backdrop/backdrop.js +16 -16
  26. package/dist/backdrop/llms.txt +27 -27
  27. package/dist/badge/badge.d.ts +3 -3
  28. package/dist/badge/badge.js +1 -1
  29. package/dist/badge/llms.txt +11 -11
  30. package/dist/bottom-sheet/bottom-sheet.d.ts +4 -4
  31. package/dist/bottom-sheet/bottom-sheet.js +2 -2
  32. package/dist/bottom-sheet/llms.txt +9 -9
  33. package/dist/breadcrumb/breadcrumb.d.ts +2 -2
  34. package/dist/breadcrumb/breadcrumb.js +3 -3
  35. package/dist/breadcrumb/llms.txt +3 -3
  36. package/dist/button/button.d.ts +3 -3
  37. package/dist/button/button.js +8 -8
  38. package/dist/button/llms.txt +27 -19
  39. package/dist/button-group/button-group.d.ts +9 -9
  40. package/dist/button-group/button-group.js +30 -8
  41. package/dist/button-group/llms.txt +20 -14
  42. package/dist/card/card.d.ts +8 -8
  43. package/dist/card/card.js +6 -6
  44. package/dist/card/llms.txt +20 -20
  45. package/dist/carousel/carousel.d.ts +6 -6
  46. package/dist/carousel/carousel.js +4 -4
  47. package/dist/carousel/llms.txt +10 -10
  48. package/dist/charts/chart-legend.d.ts +3 -3
  49. package/dist/charts/chart-legend.js +1 -1
  50. package/dist/charts/chart-tooltip.d.ts +3 -3
  51. package/dist/charts/chart-tooltip.js +1 -1
  52. package/dist/charts/chart.d.ts +1 -1
  53. package/dist/charts/chart.js +8 -8
  54. package/dist/charts/llms.txt +6 -6
  55. package/dist/charts-entry/public-api.js +2 -2
  56. package/dist/chat/chat.d.ts +17 -10
  57. package/dist/chat/chat.js +42 -18
  58. package/dist/chat/llms.txt +32 -17
  59. package/dist/chat-composer/chat-composer.d.ts +17 -7
  60. package/dist/chat-composer/chat-composer.js +21 -8
  61. package/dist/chat-composer/llms.txt +20 -11
  62. package/dist/chat-dock/chat-dock.d.ts +11 -4
  63. package/dist/chat-dock/chat-dock.js +9 -6
  64. package/dist/chat-dock/llms.txt +15 -14
  65. package/dist/chat-entry/public-api.js +1 -1
  66. package/dist/chat-message/chat-message.d.ts +12 -5
  67. package/dist/chat-message/chat-message.js +17 -7
  68. package/dist/chat-message/llms.txt +16 -15
  69. package/dist/chat-typing/chat-typing.d.ts +2 -2
  70. package/dist/chat-typing/chat-typing.js +3 -3
  71. package/dist/chat-typing/llms.txt +6 -6
  72. package/dist/checkbox/checkbox.d.ts +13 -6
  73. package/dist/checkbox/checkbox.js +26 -7
  74. package/dist/checkbox/llms.txt +24 -12
  75. package/dist/chip/chip.d.ts +4 -4
  76. package/dist/chip/chip.js +2 -2
  77. package/dist/chip/llms.txt +11 -11
  78. package/dist/chips-input/chips-input.d.ts +4 -4
  79. package/dist/chips-input/chips-input.js +35 -9
  80. package/dist/chips-input/llms.txt +15 -8
  81. package/dist/clock/clock.d.ts +4 -4
  82. package/dist/clock/clock.js +7 -7
  83. package/dist/clock/llms.txt +8 -8
  84. package/dist/color-picker/color-picker.d.ts +6 -6
  85. package/dist/color-picker/color-picker.js +3 -3
  86. package/dist/color-picker/llms.txt +5 -5
  87. package/dist/confirm/confirm-container.d.ts +3 -3
  88. package/dist/confirm/confirm-container.js +5 -5
  89. package/dist/confirm/confirm-store.d.ts +4 -4
  90. package/dist/confirm/confirm-store.js +7 -7
  91. package/dist/confirm/llms.txt +6 -6
  92. package/dist/context-menu/context-menu.d.ts +1 -1
  93. package/dist/context-menu/context-menu.js +5 -5
  94. package/dist/context-menu/llms.txt +7 -7
  95. package/dist/data-table/data-table-types.d.ts +5 -5
  96. package/dist/data-table/data-table.d.ts +29 -8
  97. package/dist/data-table/data-table.js +340 -95
  98. package/dist/data-table/export-csv.d.ts +3 -3
  99. package/dist/data-table/export-csv.js +4 -4
  100. package/dist/data-table/llms.txt +127 -94
  101. package/dist/data-view/data-view.d.ts +13 -5
  102. package/dist/data-view/data-view.js +67 -6
  103. package/dist/data-view/llms.txt +29 -10
  104. package/dist/date-range/date-range.d.ts +9 -9
  105. package/dist/date-range/date-range.js +24 -24
  106. package/dist/date-range/llms.txt +20 -20
  107. package/dist/datepicker/datepicker.d.ts +10 -10
  108. package/dist/datepicker/datepicker.js +29 -29
  109. package/dist/datepicker/llms.txt +24 -24
  110. package/dist/dialog/dialog.d.ts +12 -7
  111. package/dist/dialog/dialog.js +4 -4
  112. package/dist/dialog/llms.txt +14 -11
  113. package/dist/divider/divider.d.ts +1 -1
  114. package/dist/divider/llms.txt +4 -4
  115. package/dist/drawer/drawer.d.ts +5 -5
  116. package/dist/drawer/drawer.js +2 -2
  117. package/dist/drawer/llms.txt +11 -11
  118. package/dist/empty/empty.d.ts +2 -2
  119. package/dist/empty/empty.js +1 -1
  120. package/dist/empty/llms.txt +9 -9
  121. package/dist/fab/fab.d.ts +1 -1
  122. package/dist/fab/fab.js +1 -1
  123. package/dist/fab/llms.txt +10 -6
  124. package/dist/fieldset/fieldset.d.ts +18 -4
  125. package/dist/fieldset/fieldset.js +9 -6
  126. package/dist/fieldset/llms.txt +26 -9
  127. package/dist/file-upload/file-upload.d.ts +3 -3
  128. package/dist/file-upload/file-upload.js +7 -7
  129. package/dist/file-upload/llms.txt +7 -7
  130. package/dist/form-field/form-field-context.d.ts +4 -4
  131. package/dist/form-field/form-field.d.ts +12 -4
  132. package/dist/form-field/form-field.js +26 -6
  133. package/dist/form-field/llms.txt +25 -11
  134. package/dist/galleria/galleria.d.ts +3 -3
  135. package/dist/galleria/galleria.js +5 -5
  136. package/dist/galleria/llms.txt +7 -7
  137. package/dist/grid/col.d.ts +4 -4
  138. package/dist/grid/col.js +2 -2
  139. package/dist/grid/flex.d.ts +3 -3
  140. package/dist/grid/flex.js +1 -1
  141. package/dist/grid/grid.d.ts +7 -7
  142. package/dist/grid/grid.js +3 -3
  143. package/dist/grid/layout.d.ts +2 -2
  144. package/dist/grid/layout.js +2 -2
  145. package/dist/grid/llms.txt +15 -15
  146. package/dist/grid-entry/public-api.js +1 -1
  147. package/dist/i18n/i18n-context.d.ts +3 -3
  148. package/dist/i18n/i18n-context.js +7 -7
  149. package/dist/icon/icon-registry.d.ts +3 -3
  150. package/dist/icon/icon-registry.js +3 -3
  151. package/dist/icon/icon.d.ts +4 -4
  152. package/dist/icon/icon.js +6 -6
  153. package/dist/icon/llms.txt +13 -13
  154. package/dist/icon-button/icon-button.d.ts +2 -2
  155. package/dist/icon-button/icon-button.js +1 -1
  156. package/dist/icon-button/llms.txt +9 -5
  157. package/dist/icons-entry/public-api.d.ts +2 -2
  158. package/dist/icons-entry/public-api.js +3 -3
  159. package/dist/image/image.js +1 -1
  160. package/dist/image/llms.txt +13 -13
  161. package/dist/input/input.d.ts +18 -5
  162. package/dist/input/input.js +21 -6
  163. package/dist/input/llms.txt +25 -11
  164. package/dist/input-mask/input-mask.d.ts +1 -1
  165. package/dist/input-mask/input-mask.js +2 -2
  166. package/dist/input-mask/llms.txt +5 -5
  167. package/dist/input-number/input-number.d.ts +9 -9
  168. package/dist/input-number/input-number.js +11 -11
  169. package/dist/input-number/llms.txt +14 -14
  170. package/dist/input-otp/input-otp.d.ts +6 -6
  171. package/dist/input-otp/input-otp.js +10 -10
  172. package/dist/input-otp/llms.txt +9 -9
  173. package/dist/internal/calendar-focus.d.ts +1 -1
  174. package/dist/internal/calendar-focus.js +3 -3
  175. package/dist/internal/cn.d.ts +1 -1
  176. package/dist/internal/mask.d.ts +2 -2
  177. package/dist/internal/mask.js +4 -4
  178. package/dist/internal/option-key.d.ts +2 -2
  179. package/dist/internal/option-key.js +2 -2
  180. package/dist/internal/option-nav.d.ts +4 -4
  181. package/dist/internal/option-nav.js +5 -5
  182. package/dist/internal/overlay-container.js +1 -1
  183. package/dist/internal/overlay.d.ts +3 -3
  184. package/dist/internal/overlay.js +8 -8
  185. package/dist/internal/password-strength.d.ts +2 -2
  186. package/dist/internal/password-strength.js +6 -6
  187. package/dist/internal/roving.d.ts +2 -2
  188. package/dist/internal/roving.js +3 -3
  189. package/dist/internal/scroll-lock.d.ts +1 -1
  190. package/dist/internal/scroll-lock.js +2 -2
  191. package/dist/internal/slot-children.d.ts +3 -3
  192. package/dist/internal/slot-children.js +4 -4
  193. package/dist/internal/slot.d.ts +3 -3
  194. package/dist/internal/slot.js +2 -2
  195. package/dist/internal/step.d.ts +3 -3
  196. package/dist/internal/step.js +4 -4
  197. package/dist/internal/tween.d.ts +5 -5
  198. package/dist/internal/tween.js +5 -5
  199. package/dist/internal/types.d.ts +2 -2
  200. package/dist/internal/use-breakpoint.d.ts +2 -2
  201. package/dist/internal/use-breakpoint.js +4 -4
  202. package/dist/internal/use-controllable-state.d.ts +1 -1
  203. package/dist/internal/use-controllable-state.js +3 -3
  204. package/dist/internal/use-id.d.ts +2 -2
  205. package/dist/internal/use-id.js +2 -2
  206. package/dist/internal/use-modal-focus.d.ts +1 -1
  207. package/dist/internal/use-modal-focus.js +3 -3
  208. package/dist/knob/knob.d.ts +4 -4
  209. package/dist/knob/knob.js +14 -10
  210. package/dist/knob/llms.txt +17 -12
  211. package/dist/listbox/listbox.d.ts +6 -6
  212. package/dist/listbox/listbox.js +9 -9
  213. package/dist/listbox/llms.txt +10 -10
  214. package/dist/loader/llms.txt +56 -0
  215. package/dist/loader/loader.d.ts +49 -0
  216. package/dist/loader/loader.js +68 -0
  217. package/dist/map/llms.txt +25 -22
  218. package/dist/map/map-create-dialog.d.ts +4 -4
  219. package/dist/map/map-create-dialog.js +10 -10
  220. package/dist/map/map-hours-field.d.ts +4 -4
  221. package/dist/map/map-hours-field.js +7 -7
  222. package/dist/map/map-image-field.d.ts +2 -2
  223. package/dist/map/map-image-field.js +4 -4
  224. package/dist/map/map-legend.d.ts +4 -4
  225. package/dist/map/map-legend.js +2 -2
  226. package/dist/map/map-poi-card.d.ts +3 -3
  227. package/dist/map/map-poi-card.js +9 -9
  228. package/dist/map/map-search.d.ts +17 -6
  229. package/dist/map/map-search.js +21 -18
  230. package/dist/map/map.d.ts +21 -14
  231. package/dist/map/map.js +30 -29
  232. package/dist/map/ports.d.ts +9 -9
  233. package/dist/map/ports.js +6 -6
  234. package/dist/map-entry/public-api.js +2 -2
  235. package/dist/megamenu/llms.txt +9 -9
  236. package/dist/megamenu/megamenu.d.ts +6 -6
  237. package/dist/megamenu/megamenu.js +10 -10
  238. package/dist/mention/llms.txt +15 -15
  239. package/dist/mention/mention.d.ts +5 -5
  240. package/dist/mention/mention.js +2 -2
  241. package/dist/mention/mentions.d.ts +1 -1
  242. package/dist/mention/mentions.js +1 -1
  243. package/dist/menu/llms.txt +12 -12
  244. package/dist/menu/menu-item.d.ts +4 -4
  245. package/dist/menu/menu-keys.d.ts +2 -2
  246. package/dist/menu/menu-keys.js +5 -5
  247. package/dist/menu/menu.d.ts +2 -2
  248. package/dist/menu/menu.js +6 -6
  249. package/dist/menubar/llms.txt +10 -10
  250. package/dist/menubar/menubar.d.ts +5 -5
  251. package/dist/menubar/menubar.js +10 -10
  252. package/dist/meter-group/llms.txt +12 -12
  253. package/dist/meter-group/meter-group.d.ts +2 -2
  254. package/dist/meter-group/meter-group.js +1 -1
  255. package/dist/motion/motion-context.d.ts +22 -0
  256. package/dist/motion/motion-context.js +28 -0
  257. package/dist/order-list/llms.txt +22 -10
  258. package/dist/order-list/order-list.d.ts +12 -4
  259. package/dist/order-list/order-list.js +104 -21
  260. package/dist/paginator/llms.txt +8 -8
  261. package/dist/paginator/paginator.js +1 -1
  262. package/dist/panel/llms.txt +25 -13
  263. package/dist/panel/panel.d.ts +11 -3
  264. package/dist/panel/panel.js +7 -4
  265. package/dist/password/llms.txt +16 -8
  266. package/dist/password/password.d.ts +2 -2
  267. package/dist/password/password.js +24 -3
  268. package/dist/peek/llms.txt +10 -10
  269. package/dist/peek/peek.d.ts +6 -6
  270. package/dist/peek/peek.js +7 -7
  271. package/dist/pick-list/llms.txt +33 -13
  272. package/dist/pick-list/pick-list.d.ts +12 -4
  273. package/dist/pick-list/pick-list.js +111 -23
  274. package/dist/popover/llms.txt +11 -11
  275. package/dist/popover/popover.d.ts +4 -4
  276. package/dist/popover/popover.js +5 -5
  277. package/dist/progress/llms.txt +17 -17
  278. package/dist/progress/progress.d.ts +7 -7
  279. package/dist/progress/progress.js +6 -6
  280. package/dist/public-api.d.ts +15 -3
  281. package/dist/public-api.js +13 -7
  282. package/dist/radio-group/llms.txt +13 -6
  283. package/dist/radio-group/radio-group.d.ts +11 -4
  284. package/dist/radio-group/radio-group.js +15 -7
  285. package/dist/rating/llms.txt +20 -13
  286. package/dist/rating/rating-pointer.d.ts +16 -0
  287. package/dist/rating/rating-pointer.js +26 -0
  288. package/dist/rating/rating.d.ts +6 -6
  289. package/dist/rating/rating.js +76 -12
  290. package/dist/rich-text/lists.d.ts +2 -2
  291. package/dist/rich-text/lists.js +7 -7
  292. package/dist/rich-text/llms.txt +26 -17
  293. package/dist/rich-text/rich-text.d.ts +12 -7
  294. package/dist/rich-text/rich-text.js +78 -54
  295. package/dist/rich-text/sanitize.d.ts +3 -3
  296. package/dist/rich-text/sanitize.js +12 -12
  297. package/dist/rich-text/tables.d.ts +3 -3
  298. package/dist/rich-text/tables.js +3 -3
  299. package/dist/roadmap/llms.txt +15 -15
  300. package/dist/roadmap/roadmap.d.ts +6 -6
  301. package/dist/roadmap/roadmap.js +19 -19
  302. package/dist/roadmap-entry/public-api.js +2 -2
  303. package/dist/scroll-top/llms.txt +6 -6
  304. package/dist/scroll-top/scroll-top.d.ts +1 -1
  305. package/dist/scroll-top/scroll-top.js +1 -1
  306. package/dist/select/llms.txt +19 -12
  307. package/dist/select/select.d.ts +8 -8
  308. package/dist/select/select.js +47 -21
  309. package/dist/shared/types.d.ts +5 -5
  310. package/dist/shared/types.js +1 -1
  311. package/dist/sidenav/llms.txt +9 -9
  312. package/dist/sidenav/sidenav.d.ts +1 -1
  313. package/dist/sidenav/sidenav.js +1 -1
  314. package/dist/skeleton/llms.txt +3 -3
  315. package/dist/skeleton/skeleton.d.ts +1 -1
  316. package/dist/skeleton/skeleton.js +1 -1
  317. package/dist/slider/llms.txt +32 -13
  318. package/dist/slider/slider-drag.d.ts +26 -0
  319. package/dist/slider/slider-drag.js +79 -0
  320. package/dist/slider/slider.d.ts +6 -5
  321. package/dist/slider/slider.js +27 -8
  322. package/dist/speech/llms.txt +97 -0
  323. package/dist/speech/speech-button.d.ts +44 -0
  324. package/dist/speech/speech-button.js +86 -0
  325. package/dist/speech/speech-wave.d.ts +19 -0
  326. package/dist/speech/speech-wave.js +16 -0
  327. package/dist/speech/use-speech-recognition.d.ts +81 -0
  328. package/dist/speech/use-speech-recognition.js +179 -0
  329. package/dist/speed-dial/llms.txt +14 -8
  330. package/dist/speed-dial/speed-dial.d.ts +1 -1
  331. package/dist/speed-dial/speed-dial.js +1 -1
  332. package/dist/spinner/llms.txt +7 -7
  333. package/dist/spinner/spinner.d.ts +1 -1
  334. package/dist/spinner/spinner.js +1 -1
  335. package/dist/split-button/llms.txt +21 -15
  336. package/dist/split-button/split-button.d.ts +2 -2
  337. package/dist/split-button/split-button.js +4 -4
  338. package/dist/splitter/llms.txt +11 -11
  339. package/dist/splitter/splitter.d.ts +3 -3
  340. package/dist/splitter/splitter.js +5 -5
  341. package/dist/stepper/llms.txt +33 -14
  342. package/dist/stepper/stepper.d.ts +6 -6
  343. package/dist/stepper/stepper.js +53 -10
  344. package/dist/switch/llms.txt +12 -5
  345. package/dist/switch/switch.d.ts +10 -3
  346. package/dist/switch/switch.js +12 -5
  347. package/dist/switch-group/llms.txt +9 -9
  348. package/dist/switch-group/switch-group.d.ts +4 -4
  349. package/dist/switch-group/switch-group.js +4 -4
  350. package/dist/tabs/llms.txt +10 -10
  351. package/dist/tabs/tabs.d.ts +5 -5
  352. package/dist/tabs/tabs.js +8 -8
  353. package/dist/textarea/llms.txt +23 -12
  354. package/dist/textarea/textarea.d.ts +19 -6
  355. package/dist/textarea/textarea.js +21 -7
  356. package/dist/timeline/llms.txt +4 -4
  357. package/dist/timeline/timeline.d.ts +1 -1
  358. package/dist/timeline/timeline.js +1 -1
  359. package/dist/toast/llms.txt +47 -17
  360. package/dist/toast/toast-container.d.ts +9 -3
  361. package/dist/toast/toast-container.js +17 -9
  362. package/dist/toast/toast-store.d.ts +40 -4
  363. package/dist/toast/toast-store.js +36 -1
  364. package/dist/toolbar/llms.txt +7 -7
  365. package/dist/toolbar/toolbar.d.ts +3 -3
  366. package/dist/toolbar/toolbar.js +2 -2
  367. package/dist/tooltip/llms.txt +10 -10
  368. package/dist/tooltip/tooltip.d.ts +3 -3
  369. package/dist/tooltip/tooltip.js +18 -18
  370. package/dist/tree/llms.txt +25 -12
  371. package/dist/tree/tree-keys.d.ts +5 -5
  372. package/dist/tree/tree-keys.js +7 -7
  373. package/dist/tree/tree.d.ts +14 -7
  374. package/dist/tree/tree.js +53 -18
  375. package/dist/update/llms.txt +54 -0
  376. package/dist/update/update.d.ts +49 -0
  377. package/dist/update/update.js +82 -0
  378. package/package.json +15 -13
@@ -6,13 +6,13 @@ Import: `import { KptChat } from '@konce-pt/react/chat';`
6
6
 
7
7
  ## DOM
8
8
  <div class="kpt-chat-host">
9
- <p class="kpt-chat__offline" role="status">…</p>
9
+ <p class="kpt-chat__offline" role="status">...</p>
10
10
  <div class="kpt-chat__body">
11
11
  <div class="kpt-chat__viewport" role="log" aria-live="polite">
12
12
  <p class="kpt-chat__date" role="separator">Today</p>
13
- <div class="kpt-chat-message-host">…</div>
14
- <p class="kpt-chat__unread" role="separator">…</p>
15
- <div class="kpt-chat-typing-host">…</div>
13
+ <div class="kpt-chat-message-host">...</div>
14
+ <p class="kpt-chat__unread" role="separator">...</p>
15
+ <div class="kpt-chat-typing-host">...</div>
16
16
  </div>
17
17
  <p class="kpt-chat__day" aria-hidden="true">Today</p>
18
18
  <button class="kpt-chat__jump">3 new messages</button>
@@ -20,31 +20,32 @@ Import: `import { KptChat } from '@konce-pt/react/chat';`
20
20
  </div>
21
21
 
22
22
  ## Props
23
- - `messages`: `KptChatMessage<T>[]` — **in chronological order**; the component does not sort
24
- - `currentUser`: string — the author id treated as "me"
23
+ - `messages`: `KptChatMessage<T>[]` - **in chronological order**; the component does not sort
24
+ - `currentUser`: string - the author id treated as "me"
25
25
  - `groupGapMs`: number (default `KPT_CHAT_GROUP_GAP_MS`, 5 min)
26
- - `unreadFrom`: string | null — the id of the first unread message
26
+ - `unreadFrom`: string | null - the id of the first unread message
27
27
  - `offline`: boolean, `typing`: `KptChatAuthor[]`, `autoScroll`: boolean (default true)
28
- - `actions`: boolean — reply and react buttons on hover; `emptyLabel`: string
29
- - `reactionOptions`: `readonly KptChatReactionOption[]` (default `KPT_CHAT_REACTIONS`) — passed to the bubbles
30
- - `onRetry` / `onReply` — `(message) => void`; `onReact` — `(event: KptChatReactEvent) => void`
31
- (`{ message, emoji }`); `onQuoted` — `(quote) => void`
32
- - `onLoadMore` — fired once when the viewport reaches the top, not on every scroll event
33
- - `renderMessage(message, row)` — the equivalent of `<ng-template let-message>`; it replaces the
28
+ - `actions`: boolean - reply and react buttons on hover; `emptyLabel`: string
29
+ - `reactionOptions`: `readonly KptChatReactionOption[]` (default `KPT_CHAT_REACTIONS`) - passed to the bubbles
30
+ - `enterMotion`: `'spring' | 'fade' | 'none'` (default from `KptMotionProvider`, else `spring`) - see "Motion"
31
+ - `onRetry` / `onReply` - `(message) => void`; `onReact` - `(event: KptChatReactEvent) => void`
32
+ (`{ message, emoji }`); `onQuoted` - `(quote) => void`
33
+ - `onLoadMore` - fired once when the viewport reaches the top, not on every scroll event
34
+ - `renderMessage(message, row)` - the equivalent of `<ng-template let-message>`; it replaces the
34
35
  bubble's text, and the bubble, its grouping and its meta row stay in place
35
36
  - `className`, `ref` and every other `<div>` prop go to the host
36
37
 
37
38
  ## The composer is a sibling, not a slot
38
39
  A read-only conversation (an archive, a preview) should not have to carry a text field it never
39
40
  shows, so `KptChatComposer` is placed next to the list rather than inside it. The parent is what
40
- gives the list its height — `.kpt-chat-host` is a column flexbox with `min-height: 0`.
41
+ gives the list its height - `.kpt-chat-host` is a column flexbox with `min-height: 0`.
41
42
 
42
43
  ## Transport stays in the application
43
44
  The component takes messages and only reports. Retrying a send is a status change the application
44
45
  makes; `mergeStatus()` from `@konce-pt/chat` is there for out-of-order acks.
45
46
 
46
47
  ## Bubble actions: reply and react
47
- `actions` puts two buttons beside the bubble. The arrow emits `onReply` with the message —
48
+ `actions` puts two buttons beside the bubble. The arrow emits `onReply` with the message -
48
49
  `replyRefFrom()` from `@konce-pt/chat` turns it into the `replyTo` the composer's quote bar wants.
49
50
  The smiley opens a picker of five reactions (`KPT_CHAT_REACTIONS`) and emits `onReact` with the
50
51
  chosen emoji; `toggleReaction(message.reactions, emoji)` is the arithmetic that puts it in the
@@ -57,6 +58,20 @@ re-render and resize) the component measures the separators, the unread divider
57
58
  notices, hands the rectangles to `chatDayIndicator()` from `@konce-pt/chat` and swaps the pill's
58
59
  text; a centred label driving into the pill's band puts the pill out, never the content.
59
60
 
61
+ ## Motion
62
+ `enterMotion` (`spring | fade | none`) on the chat, the bubble and the dock:
63
+ - a new message at the bottom - your own grows from the composer side, someone else's comes in from
64
+ the sender's side, sharpening out of a blur; the first load and older messages loaded at the top
65
+ (`loadMore`) do not move - they are not news;
66
+ - a new reaction pops in, a changed count rolls in like a drum, a new status icon turns into place
67
+ (only after the bubble's first render, so loaded history stays still);
68
+ - the dock: `spring` grows the window out of the button (a capsule the size of the FAB opens into
69
+ the panel), `fade` slides and fades it as before.
70
+
71
+ With no value it takes `enter` from `KptMotionProvider` (default `spring`); `enabled: false` and the system
72
+ "reduce motion" setting always win. The typing dots are a state indicator - with motion off they
73
+ pulse slower instead of stopping.
74
+
60
75
  ## Examples
61
76
  <KptChat messages={messages} currentUser="me" typing={typing} onRetry={resend} />
62
77
  <KptChat messages={messages} currentUser="me" offline unreadFrom="m-42" onLoadMore={loadOlder} />
@@ -81,7 +96,7 @@ read. Separators are `role="separator"` with a label. Auto-scroll uses `behavior
81
96
  The scrolling history (`role="log"`) is a Tab stop named "Conversation history" (`chat.history`), so the
82
97
  keyboard can scroll it even when no bubble holds a focusable element. The "new messages" button counts
83
98
  the messages that actually arrived (a batch of five gives "5"). The typing indicator sits inside the log
84
- but with `aria-live="off"`, so "X is typing…" is not announced like a message. Older messages loaded at
99
+ but with `aria-live="off"`, so "X is typing..." is not announced like a message. Older messages loaded at
85
100
  the top keep the view on the same message, also in Safari (no `overflow-anchor`). The composer reports
86
101
  typing on the first keystroke and then at most every 3 s (`KPT_CHAT_TYPING_THROTTLE_MS`). A custom
87
- reaction option may carry `label` — its accessible name.
102
+ reaction option may carry `label` - its accessible name.
@@ -1,7 +1,8 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
2
  import { type KptChatReplyRef } from '@konce-pt/chat';
3
3
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
- /** Ładunek wysyłki. Kompozytor niczego nie wysyła — pakuje dane i oddaje aplikacji. */
4
+ import { type KptSpeechColor } from '../speech/speech-button.tsx';
5
+ /** Ładunek wysyłki. Kompozytor niczego nie wysyła - pakuje dane i oddaje aplikacji. */
5
6
  export interface KptChatComposerSubmit {
6
7
  text: string;
7
8
  files: readonly File[];
@@ -14,31 +15,40 @@ export interface KptChatComposerProps extends Omit<ComponentPropsWithRef<'div'>,
14
15
  /** Podpowiedź w pustym polu; bez niej etykieta ze słownika. */
15
16
  placeholder?: string;
16
17
  disabled?: boolean;
17
- /** Brak połączenia — przycisk mówi o kolejkowaniu, nie o wysyłce. */
18
+ /** Brak połączenia - przycisk mówi o kolejkowaniu, nie o wysyłce. */
18
19
  offline?: boolean;
19
20
  allowAttachments?: boolean;
20
21
  /** Ile wierszy pole może urosnąć, zanim zacznie się przewijać. */
21
22
  maxRows?: number;
22
23
  enterToSend?: boolean;
24
+ /**
25
+ * Mikrofon do dyktowania (Web Speech API) między polem a przyciskiem wysyłki. Bez wsparcia
26
+ * przeglądarki się nie pokazuje. Wysyłka w trakcie dyktowania zatwierdza to, co widać w polu.
27
+ */
28
+ speech?: boolean;
29
+ /** Język dyktowania (BCP 47); bez niego z `KptI18nProvider`. */
30
+ speechLang?: string;
31
+ /** Kolor "stop" i fali w trakcie nasłuchu; domyślnie kolor motywu. */
32
+ speechColor?: KptSpeechColor;
23
33
  /** Wiadomość, na którą odpowiadamy. Po wysłaniu czyści się razem z treścią. */
24
34
  replyTo?: KptChatReplyRef | null;
25
35
  defaultReplyTo?: KptChatReplyRef | null;
26
36
  onReplyToChange?: (replyTo: KptChatReplyRef | null) => void;
27
37
  onSend?: (payload: KptChatComposerSubmit) => void;
28
38
  /**
29
- * Sygnał „piszę” dla drugiej strony: przy pierwszym znaku, a w trakcie pisania najwyżej raz na
30
- * `KPT_CHAT_TYPING_THROTTLE_MS` (3 s) — nie przy każdym znaku.
39
+ * Sygnał "piszę" dla drugiej strony: przy pierwszym znaku, a w trakcie pisania najwyżej raz na
40
+ * `KPT_CHAT_TYPING_THROTTLE_MS` (3 s) - nie przy każdym znaku.
31
41
  */
32
42
  onTyping?: () => void;
33
43
  }
34
44
  /**
35
- * Pole pisania wiadomości — autorosnące, z załącznikami i paskiem cytatu.
45
+ * Pole pisania wiadomości - autorosnące, z załącznikami i paskiem cytatu.
36
46
  *
37
47
  * `Enter` wysyła, `Shift+Enter` łamie linię (`enterToSend={false}` odwraca układ).
38
- * Przy `offline` przycisk zmienia etykietę na kolejkowanie — wiadomość dalej da się napisać
48
+ * Przy `offline` przycisk zmienia etykietę na kolejkowanie - wiadomość dalej da się napisać
39
49
  * i oddać aplikacji, bo to ona decyduje, co zrobić z kolejką.
40
50
  *
41
51
  * @example
42
52
  * <KptChatComposer allowAttachments onSend={(payload) => send(payload)} />
43
53
  */
44
- export declare function KptChatComposer({ value, defaultValue, onValueChange, placeholder, disabled, offline, allowAttachments, maxRows, enterToSend, replyTo, defaultReplyTo, onReplyToChange, onSend, onTyping, locale, dictionary, className, ...rest }: Readonly<KptChatComposerProps>): import("react").JSX.Element;
54
+ export declare function KptChatComposer({ value, defaultValue, onValueChange, placeholder, disabled, offline, allowAttachments, maxRows, enterToSend, speech, speechLang, speechColor, replyTo, defaultReplyTo, onReplyToChange, onSend, onTyping, locale, dictionary, className, ...rest }: Readonly<KptChatComposerProps>): import("react").JSX.Element;
@@ -7,17 +7,19 @@ import { useControllableState } from "../internal/use-controllable-state.js";
7
7
  import { useKptMessages } from "../i18n/i18n-context.js";
8
8
  import { KptIcon } from "../icon/icon.js";
9
9
  import { KptIconButton } from "../icon-button/icon-button.js";
10
+ import { KptSpeechButton } from "../speech/speech-button.js";
11
+ import { useKptSpeechRecognition } from "../speech/use-speech-recognition.js";
10
12
  /**
11
- * Pole pisania wiadomości — autorosnące, z załącznikami i paskiem cytatu.
13
+ * Pole pisania wiadomości - autorosnące, z załącznikami i paskiem cytatu.
12
14
  *
13
15
  * `Enter` wysyła, `Shift+Enter` łamie linię (`enterToSend={false}` odwraca układ).
14
- * Przy `offline` przycisk zmienia etykietę na kolejkowanie — wiadomość dalej da się napisać
16
+ * Przy `offline` przycisk zmienia etykietę na kolejkowanie - wiadomość dalej da się napisać
15
17
  * i oddać aplikacji, bo to ona decyduje, co zrobić z kolejką.
16
18
  *
17
19
  * @example
18
20
  * <KptChatComposer allowAttachments onSend={(payload) => send(payload)} />
19
21
  */
20
- export function KptChatComposer({ value, defaultValue = '', onValueChange, placeholder = '', disabled = false, offline = false, allowAttachments = false, maxRows = 6, enterToSend = true, replyTo, defaultReplyTo = null, onReplyToChange, onSend, onTyping, locale, dictionary, className, ...rest }) {
22
+ export function KptChatComposer({ value, defaultValue = '', onValueChange, placeholder = '', disabled = false, offline = false, allowAttachments = false, maxRows = 6, enterToSend = true, speech = false, speechLang, speechColor, replyTo, defaultReplyTo = null, onReplyToChange, onSend, onTyping, locale, dictionary, className, ...rest }) {
21
23
  const { t } = useKptMessages({ locale, dictionary });
22
24
  const [text, setText] = useControllableState({
23
25
  value,
@@ -32,9 +34,13 @@ export function KptChatComposer({ value, defaultValue = '', onValueChange, place
32
34
  const [files, setFiles] = useState([]);
33
35
  const fieldRef = useRef(null);
34
36
  const pickerRef = useRef(null);
35
- /** Kiedy ostatnio zgłoszono „piszę” — dławienie `onTyping`. */
37
+ // Dyktowanie: tekst zatwierdzony wchodzi przez `onChange` pola - rośnie ono i zgłasza "piszę"
38
+ // jak przy klawiaturze. Bez `speech` kontroler nie tworzy sesji.
39
+ const mic = useKptSpeechRecognition({ enabled: speech, target: fieldRef, lang: speechLang, disabled });
40
+ /** Kiedy ostatnio zgłoszono "piszę" - dławienie `onTyping`. */
36
41
  const lastTyping = useRef(0);
37
- const canSend = !disabled && (text.trim().length > 0 || files.length > 0);
42
+ // W trakcie dyktowania słowa mogą być jeszcze tylko tymczasowe - wysyłka i tak je zatwierdzi.
43
+ const canSend = !disabled && (text.trim().length > 0 || files.length > 0 || mic.listening);
38
44
  const sendLabel = offline ? t('chat.queue') : t('chat.send');
39
45
  /**
40
46
  * Wysokość liczona od treści. Zerowanie przed odczytem `scrollHeight` jest konieczne:
@@ -52,7 +58,14 @@ export function KptChatComposer({ value, defaultValue = '', onValueChange, place
52
58
  const submit = () => {
53
59
  if (!canSend)
54
60
  return;
55
- onSend?.({ text: text.trim(), files, replyTo: quote });
61
+ // Tekst tymczasowy dyktowania widać w polu, ale nie ma go w stanie - `flush()` zatwierdza go
62
+ // synchronicznie przez zdarzenie `input`, więc DOM ma go od razu, a stan dopiero po renderze.
63
+ // Dlatego treść do wysyłki czytamy z pola: wysyła się dokładnie to, co widać.
64
+ mic.flush();
65
+ const current = (fieldRef.current?.value ?? text).trim();
66
+ if (!current && !files.length)
67
+ return;
68
+ onSend?.({ text: current, files, replyTo: quote });
56
69
  setText('');
57
70
  setFiles([]);
58
71
  setQuote(null);
@@ -80,11 +93,11 @@ export function KptChatComposer({ value, defaultValue = '', onValueChange, place
80
93
  } }), _jsx(KptIconButton, { size: "sm", disabled: disabled, "aria-label": t('chat.attach'), onClick: () => pickerRef.current?.click(), children: _jsx(KptIcon, { name: "paperclip" }) })] })) : null, _jsx("textarea", { ref: fieldRef, className: "kpt-chat-composer__field", rows: 1, style: { '--_max-rows': maxRows }, value: text, disabled: disabled, placeholder: placeholder || t('chat.placeholder'), "aria-label": placeholder || t('chat.placeholder'), onChange: (event) => {
81
94
  setText(event.currentTarget.value);
82
95
  grow(event.currentTarget);
83
- // „Piszę” przy pierwszym znaku i potem najwyżej raz na KPT_CHAT_TYPING_THROTTLE_MS — nie przy każdym.
96
+ // "Piszę" przy pierwszym znaku i potem najwyżej raz na KPT_CHAT_TYPING_THROTTLE_MS - nie przy każdym.
84
97
  const now = Date.now();
85
98
  if (now - lastTyping.current >= KPT_CHAT_TYPING_THROTTLE_MS) {
86
99
  lastTyping.current = now;
87
100
  onTyping?.();
88
101
  }
89
- }, onKeyDown: onKeyDown }), _jsx(KptIconButton, { variant: "filled", disabled: !canSend, "aria-label": sendLabel, onClick: submit, children: _jsx(KptIcon, { name: offline ? 'clock' : 'send' }) })] })] }));
102
+ }, onKeyDown: onKeyDown }), speech ? (_jsx(KptSpeechButton, { listeningColor: speechColor, speech: mic, size: "sm", unsupported: "hide", disabled: disabled, hostProps: { className: 'kpt-chat-composer__speech' } })) : null, _jsx(KptIconButton, { variant: "filled", disabled: !canSend, "aria-label": sendLabel, onClick: submit, children: _jsx(KptIcon, { name: offline ? 'clock' : 'send' }) })] })] }));
90
103
  }
@@ -1,12 +1,12 @@
1
1
  # KptChatComposer (@konce-pt/react/chat)
2
2
 
3
- The message field — auto-growing, with attachments and a quote strip.
3
+ The message field - auto-growing, with attachments and a quote strip.
4
4
  Import: `import { KptChatComposer } from '@konce-pt/react/chat';`
5
5
 
6
6
  ## DOM
7
7
  <div class="kpt-chat-composer-host">
8
- <div class="kpt-chat-composer__reply">…the quoted message…</div>
9
- <ul class="kpt-chat-composer__files">…picked files…</ul>
8
+ <div class="kpt-chat-composer__reply">...the quoted message...</div>
9
+ <ul class="kpt-chat-composer__files">...picked files...</ul>
10
10
  <div class="kpt-chat-composer">
11
11
  <input type="file" class="kpt-chat-composer__picker" hidden />
12
12
  <!-- KptIconButton: attach -->
@@ -16,26 +16,26 @@ Import: `import { KptChatComposer } from '@konce-pt/react/chat';`
16
16
  </div>
17
17
 
18
18
  ## Props
19
- - `value` / `defaultValue` / `onValueChange` — the text
20
- - `replyTo` / `defaultReplyTo` / `onReplyToChange` — `KptChatReplyRef | null`
19
+ - `value` / `defaultValue` / `onValueChange` - the text
20
+ - `replyTo` / `defaultReplyTo` / `onReplyToChange` - `KptChatReplyRef | null`
21
21
  - `placeholder`, `disabled`, `offline`, `allowAttachments`
22
22
  - `maxRows`: number (default 6), `enterToSend`: boolean (default true)
23
- - `onSend(payload)` — `{ text, files, replyTo }`; the text is trimmed
24
- - `onTyping()` — every keystroke, for the "is typing" signal
23
+ - `onSend(payload)` - `{ text, files, replyTo }`; the text is trimmed
24
+ - `onTyping()` - every keystroke, for the "is typing" signal
25
25
  - `className`, `ref` and every other `<div>` prop go to the host
26
26
 
27
27
  ## Enter sends, Shift+Enter breaks the line
28
- `enterToSend={false}` swaps the two. A keystroke arriving mid-composition (an IME) never sends —
28
+ `enterToSend={false}` swaps the two. A keystroke arriving mid-composition (an IME) never sends -
29
29
  `isComposing` is checked, otherwise picking a candidate would fire the message.
30
30
 
31
31
  ## The height comes from the content
32
32
  The field grows with the text and then scrolls. The script sets `height` to `scrollHeight`, having
33
- zeroed it first — without that reset the field can only grow, because `scrollHeight` never drops
33
+ zeroed it first - without that reset the field can only grow, because `scrollHeight` never drops
34
34
  below the height already set. The ceiling is CSS: `--_max-rows`.
35
35
 
36
36
  ## Offline does not lock the field
37
37
  The button changes its label to queueing and the payload still reaches `onSend`. What to do with a
38
- queue is the application's decision — the component has no transport to hold it back with.
38
+ queue is the application's decision - the component has no transport to hold it back with.
39
39
 
40
40
  ## Examples
41
41
  <KptChatComposer allowAttachments onSend={send} />
@@ -51,6 +51,15 @@ The field `--kpt-color-surface-variant` + `--kpt-radius-lg`, the top edge `--kpt
51
51
  the quote strip `--kpt-color-primary`.
52
52
 
53
53
  ## Accessibility
54
- The textarea carries an `aria-label` equal to the placeholder — a field without a label is
54
+ The textarea carries an `aria-label` equal to the placeholder - a field without a label is
55
55
  invisible to a screen reader. Both icon buttons are labelled from the dictionary. It is not a
56
56
  form control: no `touched`/`invalid` state, and the value disappears on send.
57
+
58
+ ## Dictation
59
+ `speech`: boolean = false puts a microphone between the field and the send button (`KptSpeechButton`);
60
+ `speechLang` sets the language and `speechColor` the listening colour (theme colour by default). Words appear in the field live. Enter or "Send" during dictation
61
+ commits what is visible (`flush()`) and stops listening, so exactly the visible text is sent - the send
62
+ button is enabled while listening even if only interim words exist. Final text comes through the
63
+ field's `onChange`: the field grows and `onTyping` fires as with a keyboard.
64
+
65
+ <KptChatComposer speech onSend={({ text }) => askAgent(text)} />
@@ -1,5 +1,6 @@
1
1
  import { type ComponentPropsWithRef, type ReactNode } from 'react';
2
2
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
+ import { type KptEnterMotion } from '@konce-pt/motion';
3
4
  export type KptChatDockPosition = 'bottom-end' | 'bottom-start';
4
5
  export interface KptChatDockProps extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
5
6
  open?: boolean;
@@ -15,17 +16,23 @@ export interface KptChatDockProps extends Omit<ComponentPropsWithRef<'div'>, 'ch
15
16
  width?: string;
16
17
  /**
17
18
  * Odsunięcie od rogu ekranu (dowolna długość CSS), wspólne dla obu osi.
18
- * Domyślnie `--kpt-space-6`. Przydaje się, gdy w tym samym rogu stoi już inny widget —
19
+ * Domyślnie `--kpt-space-6`. Przydaje się, gdy w tym samym rogu stoi już inny widget -
19
20
  * `KptScrollTop` przykleja się dokładnie tam samo i bez tego oba się nakładają.
20
21
  */
21
22
  offset?: string;
22
23
  children?: ReactNode;
24
+ /**
25
+ * Ruch otwarcia: `spring` - okno wyrasta z przycisku (kapsuła rośnie w panel na sprężynie),
26
+ * `fade` - panel wjeżdża i się przenika, `none` - bez ruchu. Bez wartości bierze `enter`
27
+ * z `KptMotionProvider` (domyślnie `spring`); `enabled={false}` i systemowe "ogranicz ruch" wyłączają go zawsze.
28
+ */
29
+ enterMotion?: KptEnterMotion;
23
30
  }
24
31
  /**
25
- * Pływający czat w rogu ekranu — FAB z licznikiem nieprzeczytanych i panel z rozmową.
32
+ * Pływający czat w rogu ekranu - FAB z licznikiem nieprzeczytanych i panel z rozmową.
26
33
  *
27
34
  * Zawartość panelu jest dzieckiem, więc dock nie wie nic o wiadomościach: wkłada się do niego
28
- * `KptChat` i `KptChatComposer` (albo cokolwiek innego). Sam FAB to `KptFab` — dock go używa,
35
+ * `KptChat` i `KptChatComposer` (albo cokolwiek innego). Sam FAB to `KptFab` - dock go używa,
29
36
  * nie zastępuje.
30
37
  *
31
38
  * Poniżej `--kpt-breakpoint-sm` panel wchodzi na pełny ekran: 22,5 rem szerokości na telefonie
@@ -37,4 +44,4 @@ export interface KptChatDockProps extends Omit<ComponentPropsWithRef<'div'>, 'ch
37
44
  * <KptChatComposer onSend={send} />
38
45
  * </KptChatDock>
39
46
  */
40
- export declare function KptChatDock({ open, defaultOpen, onOpenChange, position, unread, header, icon, width, offset, locale, dictionary, className, children, ...rest }: Readonly<KptChatDockProps>): import("react").JSX.Element;
47
+ export declare function KptChatDock({ open, defaultOpen, onOpenChange, position, unread, header, icon, width, offset, enterMotion, locale, dictionary, className, children, ...rest }: Readonly<KptChatDockProps>): import("react").JSX.Element;
@@ -8,11 +8,13 @@ import { KptBadge } from "../badge/badge.js";
8
8
  import { KptFab } from "../fab/fab.js";
9
9
  import { KptIcon } from "../icon/icon.js";
10
10
  import { KptIconButton } from "../icon-button/icon-button.js";
11
+ import { resolveEnterMotion } from '@konce-pt/motion';
12
+ import { useKptMotion } from "../motion/motion-context.js";
11
13
  /**
12
- * Pływający czat w rogu ekranu — FAB z licznikiem nieprzeczytanych i panel z rozmową.
14
+ * Pływający czat w rogu ekranu - FAB z licznikiem nieprzeczytanych i panel z rozmową.
13
15
  *
14
16
  * Zawartość panelu jest dzieckiem, więc dock nie wie nic o wiadomościach: wkłada się do niego
15
- * `KptChat` i `KptChatComposer` (albo cokolwiek innego). Sam FAB to `KptFab` — dock go używa,
17
+ * `KptChat` i `KptChatComposer` (albo cokolwiek innego). Sam FAB to `KptFab` - dock go używa,
16
18
  * nie zastępuje.
17
19
  *
18
20
  * Poniżej `--kpt-breakpoint-sm` panel wchodzi na pełny ekran: 22,5 rem szerokości na telefonie
@@ -24,7 +26,8 @@ import { KptIconButton } from "../icon-button/icon-button.js";
24
26
  * <KptChatComposer onSend={send} />
25
27
  * </KptChatDock>
26
28
  */
27
- export function KptChatDock({ open, defaultOpen = false, onOpenChange, position = 'bottom-end', unread = 0, header = '', icon = 'message-circle', width = '22.5rem', offset = '', locale, dictionary, className, children, ...rest }) {
29
+ export function KptChatDock({ open, defaultOpen = false, onOpenChange, position = 'bottom-end', unread = 0, header = '', icon = 'message-circle', width = '22.5rem', offset = '', enterMotion, locale, dictionary, className, children, ...rest }) {
30
+ const motion = resolveEnterMotion(enterMotion, useKptMotion());
28
31
  const { t } = useKptMessages({ locale, dictionary });
29
32
  const [isOpen, setOpen] = useControllableState({
30
33
  value: open,
@@ -34,7 +37,7 @@ export function KptChatDock({ open, defaultOpen = false, onOpenChange, position
34
37
  const panelRef = useRef(null);
35
38
  const triggerRef = useRef(null);
36
39
  /*
37
- * Fokus ląduje na polu pisania, jeśli w panelu jest — po to się czat otwiera.
40
+ * Fokus ląduje na polu pisania, jeśli w panelu jest - po to się czat otwiera.
38
41
  * Bez pola bierze pierwszy element interaktywny, a w ostateczności sam panel.
39
42
  */
40
43
  useEffect(() => {
@@ -55,7 +58,7 @@ export function KptChatDock({ open, defaultOpen = false, onOpenChange, position
55
58
  if (!isOpen)
56
59
  return;
57
60
  setOpen(false);
58
- // Fokus wraca na FAB — po zamknięciu panelu nie może zostać na elemencie,
61
+ // Fokus wraca na FAB - po zamknięciu panelu nie może zostać na elemencie,
59
62
  // który przed chwilą zniknął z drzewa dostępności.
60
63
  triggerRef.current?.querySelector('button')?.focus();
61
64
  };
@@ -64,5 +67,5 @@ export function KptChatDock({ open, defaultOpen = false, onOpenChange, position
64
67
  close();
65
68
  };
66
69
  const fab = (_jsx(KptFab, { "aria-label": isOpen ? t('chat.close') : t('chat.open'), onClick: () => setOpen(!isOpen), children: _jsx(KptIcon, { name: isOpen ? 'x' : icon }) }));
67
- return (_jsxs("div", { className: cn('kpt-chat-dock-host', className), onKeyDown: onKeyDown, ...rest, children: [_jsxs("div", { ref: panelRef, className: cn('kpt-chat-dock__panel', isOpen && 'is-open'), "data-position": position, role: "dialog", tabIndex: -1, style: { '--_width': width, ...(offset ? { '--_offset': offset } : {}) }, "aria-label": header || t('chat.open'), "aria-hidden": isOpen ? undefined : true, inert: !isOpen, children: [_jsxs("header", { className: "kpt-chat-dock__header", children: [_jsx("span", { className: "kpt-chat-dock__title", children: header }), _jsx(KptIconButton, { size: "sm", "aria-label": t('chat.close'), onClick: close, children: _jsx(KptIcon, { name: "x" }) })] }), _jsx("div", { className: "kpt-chat-dock__content", children: children })] }), _jsx("span", { ref: triggerRef, className: "kpt-chat-dock__trigger", "data-position": position, style: offset ? { '--_offset': offset } : undefined, children: unread > 0 ? (_jsx(KptBadge, { value: unread, variant: "danger", size: "sm", overlay: true, "aria-label": t('chat.unreadBadge', { count: unread }), children: fab })) : (fab) })] }));
70
+ return (_jsxs("div", { className: cn('kpt-chat-dock-host', className), onKeyDown: onKeyDown, ...rest, children: [_jsxs("div", { ref: panelRef, className: cn('kpt-chat-dock__panel', isOpen && 'is-open'), "data-position": position, "data-motion": motion, role: "dialog", tabIndex: -1, style: { '--_width': width, ...(offset ? { '--_offset': offset } : {}) }, "aria-label": header || t('chat.open'), "aria-hidden": isOpen ? undefined : true, inert: !isOpen, children: [_jsxs("header", { className: "kpt-chat-dock__header", children: [_jsx("span", { className: "kpt-chat-dock__title", children: header }), _jsx(KptIconButton, { size: "sm", "aria-label": t('chat.close'), onClick: close, children: _jsx(KptIcon, { name: "x" }) })] }), _jsx("div", { className: "kpt-chat-dock__content", children: children })] }), _jsx("span", { ref: triggerRef, className: "kpt-chat-dock__trigger", "data-position": position, style: offset ? { '--_offset': offset } : undefined, children: unread > 0 ? (_jsx(KptBadge, { value: unread, variant: "danger", size: "sm", overlay: true, "aria-label": t('chat.unreadBadge', { count: unread }), children: fab })) : (fab) })] }));
68
71
  }
@@ -1,6 +1,6 @@
1
1
  # KptChatDock (@konce-pt/react/chat)
2
2
 
3
- A floating chat in the corner of the screen — a FAB with an unread badge plus a panel holding the
3
+ A floating chat in the corner of the screen - a FAB with an unread badge plus a panel holding the
4
4
  conversation.
5
5
  Import: `import { KptChatDock } from '@konce-pt/react/chat';`
6
6
 
@@ -11,31 +11,32 @@ Import: `import { KptChatDock } from '@konce-pt/react/chat';`
11
11
  <span class="kpt-chat-dock__title">Support</span>
12
12
  <!-- KptIconButton: close -->
13
13
  </header>
14
- <div class="kpt-chat-dock__content">…children…</div>
14
+ <div class="kpt-chat-dock__content">...children...</div>
15
15
  </div>
16
16
  <span class="kpt-chat-dock__trigger" data-position="bottom-end">
17
- <!-- KptBadge overlay → KptFab -->
17
+ <!-- KptBadge overlay -> KptFab -->
18
18
  </span>
19
19
  </div>
20
20
 
21
21
  ## Props
22
- - `open` / `defaultOpen` / `onOpenChange` — works controlled and uncontrolled
22
+ - `open` / `defaultOpen` / `onOpenChange` - works controlled and uncontrolled
23
23
  - `position`: `'bottom-end' | 'bottom-start'` (logical, so RTL flips the corner on its own)
24
- - `unread`: number — 0 hides the badge; `header`: string
25
- - `icon`: string (default `message-circle`) — the closed FAB; open always shows a cross
26
- - `width`: string (default `22.5rem`) — above `--kpt-breakpoint-sm`
27
- - `offset`: string — distance from the corner, both axes (default `--kpt-space-6`)
28
- - `children` — the panel content
24
+ - `unread`: number - 0 hides the badge; `header`: string
25
+ - `icon`: string (default `message-circle`) - the closed FAB; open always shows a cross
26
+ - `width`: string (default `22.5rem`) - above `--kpt-breakpoint-sm`
27
+ - `offset`: string - distance from the corner, both axes (default `--kpt-space-6`)
28
+ - `enterMotion`: `'spring' | 'fade' | 'none'` - `spring` grows the window out of the button, `fade` slides it in (default from `KptMotionProvider`, else `spring`)
29
+ - `children` - the panel content
29
30
  - `className`, `ref` and every other `<div>` prop go to the host
30
31
 
31
32
  ## The FAB is KptFab, not a copy of it
32
- The dock composes the existing components — `KptFab`, `KptBadge`, `KptIconButton` — so a change to
33
+ The dock composes the existing components - `KptFab`, `KptBadge`, `KptIconButton` - so a change to
33
34
  the FAB reaches the chat as well. The content is `children`, which means the dock knows nothing
34
35
  about messages: put `KptChat` and `KptChatComposer` inside, or anything else.
35
36
 
36
37
  ## Sharing the corner with KptScrollTop
37
38
  `KptScrollTop` pins itself to the same spot with the same `--kpt-z-overlay`, and it would swallow the
38
- chat FAB whole — only the unread badge would stick out. The stylesheet resolves it by itself: when a
39
+ chat FAB whole - only the unread badge would stick out. The stylesheet resolves it by itself: when a
39
40
  `bottom-end` dock is on the page, the scroll-top button steps **aside**, not up, because the
40
41
  conversation panel stands above the FAB. It applies to `bottom-end` only and measures from the
41
42
  default offset, so an application that sets `offset` positions the scroll-top button itself.
@@ -45,7 +46,7 @@ default offset, so an application that sets `offset` positions the scroll-top bu
45
46
  Dropped in the middle of an application's layout the dock takes no space in the flow.
46
47
 
47
48
  ## Closed is not removed
48
- The panel stays in the DOM with `visibility: hidden` and a delayed transition — a transition
49
+ The panel stays in the DOM with `visibility: hidden` and a delayed transition - a transition
49
50
  cannot animate out of `display: none`. It also carries `inert` and `aria-hidden` while closed, so
50
51
  a hidden panel never catches the tab order.
51
52
 
@@ -55,7 +56,7 @@ a hidden panel never catches the tab order.
55
56
  <KptChatComposer onSend={send} />
56
57
  </KptChatDock>
57
58
 
58
- <KptChatDock open={open} onOpenChange={setOpen} position="bottom-start" width="26rem">…</KptChatDock>
59
+ <KptChatDock open={open} onOpenChange={setOpen} position="bottom-start" width="26rem">...</KptChatDock>
59
60
 
60
61
  ## i18n
61
62
  `open`, `close`, `unreadBadge` (`{count}`) from the `chat` namespace.
@@ -65,7 +66,7 @@ The panel `--kpt-radius-lg` + `--kpt-elevation-3`, the corner offsets `--kpt-spa
65
66
  the stack `--kpt-z-overlay`.
66
67
 
67
68
  ## Accessibility
68
- Opening moves focus to the composer inside the panel — that is what the chat is opened for; with no
69
+ Opening moves focus to the composer inside the panel - that is what the chat is opened for; with no
69
70
  field it takes the first interactive element. `Escape` closes and returns focus to the FAB, because
70
71
  focus must not stay on an element that just left the accessibility tree. Below 640px the panel goes
71
72
  full-screen.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * @konce-pt/react/chat — rozmowa. Osobny subpath, bo dock i logika listy są ciężkie
2
+ * @konce-pt/react/chat - rozmowa. Osobny subpath, bo dock i logika listy są ciężkie
3
3
  * względem rdzenia biblioteki i nie każda aplikacja ich potrzebuje.
4
4
  */
5
5
  export { KptChat } from "../chat/chat.js";
@@ -1,7 +1,8 @@
1
1
  import { type ComponentPropsWithRef, type ReactNode } from 'react';
2
+ import { type KptEnterMotion } from '@konce-pt/motion';
2
3
  import type { KptChatAttachment, KptChatAuthor, KptChatGroupPosition, KptChatMessage as KptChatMessageModel, KptChatReaction, KptChatReactionOption, KptChatReplyRef, KptChatStatus } from '@konce-pt/chat';
3
4
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
- /** Wygląd bąbla. `system` nie rysuje bąbla w ogóle — to komunikat, nie wypowiedź. */
5
+ /** Wygląd bąbla. `system` nie rysuje bąbla w ogóle - to komunikat, nie wypowiedź. */
5
6
  export type KptChatMessageVariant = 'neutral' | 'primary' | 'solid' | 'ghost' | 'system';
6
7
  export interface KptChatMessageProps extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
7
8
  /** Cała wiadomość naraz. Pojedyncze propsy poniżej mają nad nią priorytet. */
@@ -29,15 +30,21 @@ export interface KptChatMessageProps extends Omit<ComponentPropsWithRef<'div'>,
29
30
  reactionOptions?: readonly KptChatReactionOption[];
30
31
  onRetry?: () => void;
31
32
  onReply?: () => void;
32
- /** Wybrana reakcja (emoji). Biblioteka niczego nie dopisuje do modelu — robi to aplikacja. */
33
+ /** Wybrana reakcja (emoji). Biblioteka niczego nie dopisuje do modelu - robi to aplikacja. */
33
34
  onReact?: (emoji: string) => void;
34
- /** Kliknięcie w cytat — aplikacja może przewinąć listę do oryginału. */
35
+ /** Kliknięcie w cytat - aplikacja może przewinąć listę do oryginału. */
35
36
  onQuoted?: (quote: KptChatReplyRef) => void;
36
37
  /** Treść bąbla. Wygrywa z `text`, więc bąbel przyjmuje cokolwiek. */
37
38
  children?: ReactNode;
39
+ /**
40
+ * Ruch pojawiania się (reakcja, zmiana statusu): `spring` - wyrasta z miejsca, z którego przychodzi, na sprężynie;
41
+ * `fade` - tylko się przenika; `none` - bez ruchu. Bez wartości bierze `enter` z `KptMotionProvider`
42
+ * (domyślnie `spring`); `enabled={false}` i systemowe "ogranicz ruch" wyłączają go zawsze.
43
+ */
44
+ enterMotion?: KptEnterMotion;
38
45
  }
39
46
  /**
40
- * Pojedyncza wiadomość czatu — bąbel z treścią, godziną, statusem wysyłki i opcjonalnym
47
+ * Pojedyncza wiadomość czatu - bąbel z treścią, godziną, statusem wysyłki i opcjonalnym
41
48
  * awatarem, cytatem, załącznikami i reakcjami.
42
49
  *
43
50
  * Działa na dwa sposoby: dostaje gotowy obiekt przez `message` (tak używa go `KptChat`)
@@ -50,4 +57,4 @@ export interface KptChatMessageProps extends Omit<ComponentPropsWithRef<'div'>,
50
57
  * <KptChatMessage message={msg} align="end" position="last" />
51
58
  * <KptChatMessage align="end" status="failed" onRetry={resend}>Nie poszło</KptChatMessage>
52
59
  */
53
- export declare function KptChatMessage({ message, author, text, time, attachments, reactions, replyTo, status, align, variant, position, system, showAuthor, showAvatar, showMeta, tail, actions, reactionOptions, onRetry, onReply, onReact, onQuoted, children, locale, dictionary, className, ...rest }: Readonly<KptChatMessageProps>): import("react").JSX.Element;
60
+ export declare function KptChatMessage({ message, author, text, time, attachments, reactions, replyTo, status, align, variant, position, system, showAuthor, showAvatar, showMeta, tail, actions, reactionOptions, enterMotion, ref, onRetry, onReply, onReact, onQuoted, children, locale, dictionary, className, ...rest }: Readonly<KptChatMessageProps>): import("react").JSX.Element;
@@ -1,12 +1,15 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
+ import { resolveEnterMotion } from '@konce-pt/motion';
4
5
  import { KPT_CHAT_REACTIONS, canRetry, toDate } from '@konce-pt/chat';
5
6
  import { cn } from "../internal/cn.js";
6
7
  import { useKptMessages } from "../i18n/i18n-context.js";
7
8
  import { KptAvatar } from "../avatar/avatar.js";
8
9
  import { KptIcon } from "../icon/icon.js";
9
10
  import { KptIconButton } from "../icon-button/icon-button.js";
11
+ import { useKptMotion } from "../motion/motion-context.js";
12
+ import { composeRefs } from "../internal/compose-refs.js";
10
13
  /** Ikona statusu wysyłki. `read` używa tej samej ikony co `delivered`, różni je kolor. */
11
14
  const STATUS_ICONS = {
12
15
  sending: 'clock',
@@ -16,7 +19,7 @@ const STATUS_ICONS = {
16
19
  failed: 'alert-triangle',
17
20
  };
18
21
  /**
19
- * Pojedyncza wiadomość czatu — bąbel z treścią, godziną, statusem wysyłki i opcjonalnym
22
+ * Pojedyncza wiadomość czatu - bąbel z treścią, godziną, statusem wysyłki i opcjonalnym
20
23
  * awatarem, cytatem, załącznikami i reakcjami.
21
24
  *
22
25
  * Działa na dwa sposoby: dostaje gotowy obiekt przez `message` (tak używa go `KptChat`)
@@ -29,14 +32,21 @@ const STATUS_ICONS = {
29
32
  * <KptChatMessage message={msg} align="end" position="last" />
30
33
  * <KptChatMessage align="end" status="failed" onRetry={resend}>Nie poszło</KptChatMessage>
31
34
  */
32
- export function KptChatMessage({ message = null, author = null, text = null, time = null, attachments = null, reactions = null, replyTo = null, status = null, align = 'start', variant = null, position = 'single', system = false, showAuthor = false, showAvatar = false, showMeta = true, tail = true, actions = false, reactionOptions = KPT_CHAT_REACTIONS, onRetry, onReply, onReact, onQuoted, children, locale, dictionary, className, ...rest }) {
35
+ export function KptChatMessage({ message = null, author = null, text = null, time = null, attachments = null, reactions = null, replyTo = null, status = null, align = 'start', variant = null, position = 'single', system = false, showAuthor = false, showAvatar = false, showMeta = true, tail = true, actions = false, reactionOptions = KPT_CHAT_REACTIONS, enterMotion, ref, onRetry, onReply, onReact, onQuoted, children, locale, dictionary, className, ...rest }) {
33
36
  const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
34
37
  const rowRef = useRef(null);
35
38
  /** Czy wybierak reakcji jest otwarty. */
36
39
  const [pickerOpen, setPickerOpen] = useState(false);
40
+ const motion = resolveEnterMotion(enterMotion, useKptMotion());
41
+ const hostRef = useRef(null);
42
+ // Reakcje i status wskakują dopiero po pierwszym renderze - wczytana historia się nie rusza.
43
+ // Klasa idzie wprost na element: osobny stan dałby drugi przebieg renderu tylko po to.
44
+ useEffect(() => {
45
+ hostRef.current?.classList.add('is-live');
46
+ }, []);
37
47
  // Kliknięcie obok zamyka wybierak. Sam `onBlur` nie wystarcza: w Safari kliknięcie w przycisk
38
48
  // nie przenosi na niego fokusu, więc wybierak zostawałby otwarty na zawsze. Faza przechwytywania
39
- // i `pointerdown` (nie `click`) — własne `onClick` opcji zdąży się wykonać przed zamknięciem.
49
+ // i `pointerdown` (nie `click`) - własne `onClick` opcji zdąży się wykonać przed zamknięciem.
40
50
  useEffect(() => {
41
51
  if (!pickerOpen)
42
52
  return;
@@ -76,7 +86,7 @@ export function KptChatMessage({ message = null, author = null, text = null, tim
76
86
  const statusIcon = resolvedStatus ? STATUS_ICONS[resolvedStatus] : null;
77
87
  const retryable = canRetry(resolvedStatus);
78
88
  /*
79
- * Etykieta dla czytnika ekranu. Bez niej z bąbla płynie sama treść — bez nadawcy,
89
+ * Etykieta dla czytnika ekranu. Bez niej z bąbla płynie sama treść - bez nadawcy,
80
90
  * godziny i informacji, że wiadomość się nie wysłała.
81
91
  */
82
92
  const ariaLabel = isSystem
@@ -85,9 +95,9 @@ export function KptChatMessage({ message = null, author = null, text = null, tim
85
95
  .filter(Boolean)
86
96
  .join(', ');
87
97
  if (isSystem) {
88
- return (_jsx("div", { className: cn('kpt-chat-message-host', className), role: "note", "aria-label": ariaLabel, ...rest, children: _jsx("p", { className: "kpt-chat-message__system", children: resolvedText }) }));
98
+ return (_jsx("div", { ref: composeRefs(hostRef, ref), className: cn('kpt-chat-message-host', className), role: "note", "aria-label": ariaLabel, "data-motion": motion, ...rest, children: _jsx("p", { className: "kpt-chat-message__system", children: resolvedText }) }));
89
99
  }
90
- return (_jsx("div", { className: cn('kpt-chat-message-host', className), role: "article", "aria-label": ariaLabel, ...rest, children: _jsxs("div", { ref: rowRef, className: "kpt-chat-message__row", "data-align": align, children: [align === 'start' ? (_jsx("span", { className: "kpt-chat-message__avatar", "aria-hidden": "true", children: showAvatar ? (_jsx(KptAvatar, { size: "sm", src: resolvedAuthor?.avatar ?? null, name: resolvedAuthor?.name ?? '' })) : null })) : null, _jsxs("div", { className: "kpt-chat-message__stack", children: [showAuthor && resolvedAuthor ? (_jsx("span", { className: "kpt-chat-message__author", children: resolvedAuthor.name })) : null, _jsxs("div", { className: "kpt-chat-message__bubble", "data-variant": resolvedVariant, "data-align": align, "data-position": position, "data-status": resolvedStatus ?? undefined, "data-tail": hasTail ? '' : undefined, children: [resolvedReplyTo ? (_jsxs("button", { type: "button", className: "kpt-chat-message__quote", onClick: () => onQuoted?.(resolvedReplyTo), children: [_jsx("span", { className: "kpt-chat-message__quote-author", children: resolvedReplyTo.author }), _jsx("span", { className: "kpt-chat-message__quote-text", children: resolvedReplyTo.excerpt })] })) : null, resolvedText ? _jsx("p", { className: "kpt-chat-message__text", children: resolvedText }) : null, children, resolvedAttachments.length ? (_jsx("ul", { className: "kpt-chat-message__files", children: resolvedAttachments.map((file) => (_jsxs("li", { className: "kpt-chat-message__file", children: [file.thumbnail ? (_jsx("img", { className: "kpt-chat-message__file-thumb", src: file.thumbnail, alt: file.name })) : (_jsx(KptIcon, { name: "paperclip" })), _jsx("a", { className: "kpt-chat-message__file-name", href: file.url, download: file.url ? file.name : undefined, children: file.name }), file.size ? (_jsx("span", { className: "kpt-chat-message__file-size", children: fileSize(file.size) })) : null] }, file.id))) })) : null, showMeta ? (_jsxs("span", { className: "kpt-chat-message__meta", children: [timeLabel ? (_jsx("time", { className: "kpt-chat-message__time", dateTime: sentAt?.toISOString(), children: timeLabel })) : null, statusIcon ? (_jsx(KptIcon, { className: "kpt-chat-message__status", name: statusIcon })) : null] })) : null, resolvedReactions.length ? (_jsx("ul", { className: "kpt-chat-message__reactions", "data-align": align, children: resolvedReactions.map((reaction) => (_jsxs("li", { className: "kpt-chat-message__reaction", "data-reacted": reaction.reacted ? '' : undefined, children: [_jsx("span", { "aria-hidden": "true", children: reaction.emoji }), _jsx("span", { className: "kpt-chat-message__reaction-count", children: reaction.count })] }, reaction.emoji))) })) : null] })] }), _jsx("span", { className: "kpt-chat-message__actions", "data-open": pickerOpen ? '' : undefined, children: retryable ? (_jsx(KptIconButton, { hostProps: { className: 'kpt-chat-message__retry' }, size: "sm", "aria-label": t('chat.retry'), onClick: () => onRetry?.(), children: _jsx(KptIcon, { name: "refresh" }) })) : actions ? (_jsxs(_Fragment, { children: [_jsx(KptIconButton, { size: "sm", "aria-label": t('chat.replyTo', { author: resolvedAuthor?.name ?? '' }), onClick: () => onReply?.(), children: _jsx(KptIcon, { name: "corner-up-left" }) }), _jsxs("span", { className: "kpt-chat-message__react", onKeyDown: (event) => {
100
+ return (_jsx("div", { ref: composeRefs(hostRef, ref), className: cn('kpt-chat-message-host', className), role: "article", "aria-label": ariaLabel, "data-motion": motion, ...rest, children: _jsxs("div", { ref: rowRef, className: "kpt-chat-message__row", "data-align": align, children: [align === 'start' ? (_jsx("span", { className: "kpt-chat-message__avatar", "aria-hidden": "true", children: showAvatar ? (_jsx(KptAvatar, { size: "sm", src: resolvedAuthor?.avatar ?? null, name: resolvedAuthor?.name ?? '' })) : null })) : null, _jsxs("div", { className: "kpt-chat-message__stack", children: [showAuthor && resolvedAuthor ? (_jsx("span", { className: "kpt-chat-message__author", children: resolvedAuthor.name })) : null, _jsxs("div", { className: "kpt-chat-message__bubble", "data-variant": resolvedVariant, "data-align": align, "data-position": position, "data-status": resolvedStatus ?? undefined, "data-tail": hasTail ? '' : undefined, children: [resolvedReplyTo ? (_jsxs("button", { type: "button", className: "kpt-chat-message__quote", onClick: () => onQuoted?.(resolvedReplyTo), children: [_jsx("span", { className: "kpt-chat-message__quote-author", children: resolvedReplyTo.author }), _jsx("span", { className: "kpt-chat-message__quote-text", children: resolvedReplyTo.excerpt })] })) : null, resolvedText ? _jsx("p", { className: "kpt-chat-message__text", children: resolvedText }) : null, children, resolvedAttachments.length ? (_jsx("ul", { className: "kpt-chat-message__files", children: resolvedAttachments.map((file) => (_jsxs("li", { className: "kpt-chat-message__file", children: [file.thumbnail ? (_jsx("img", { className: "kpt-chat-message__file-thumb", src: file.thumbnail, alt: file.name })) : (_jsx(KptIcon, { name: "paperclip" })), _jsx("a", { className: "kpt-chat-message__file-name", href: file.url, download: file.url ? file.name : undefined, children: file.name }), file.size ? (_jsx("span", { className: "kpt-chat-message__file-size", children: fileSize(file.size) })) : null] }, file.id))) })) : null, showMeta ? (_jsxs("span", { className: "kpt-chat-message__meta", children: [timeLabel ? (_jsx("time", { className: "kpt-chat-message__time", dateTime: sentAt?.toISOString(), children: timeLabel })) : null, statusIcon ? (_jsx(KptIcon, { className: "kpt-chat-message__status", name: statusIcon }, statusIcon)) : null] })) : null, resolvedReactions.length ? (_jsx("ul", { className: "kpt-chat-message__reactions", "data-align": align, children: resolvedReactions.map((reaction) => (_jsxs("li", { className: "kpt-chat-message__reaction", "data-reacted": reaction.reacted ? '' : undefined, children: [_jsx("span", { "aria-hidden": "true", children: reaction.emoji }), _jsx("span", { className: "kpt-chat-message__reaction-count", children: _jsx("span", { className: "kpt-chat-message__reaction-digit", children: reaction.count }, reaction.count) })] }, reaction.emoji))) })) : null] })] }), _jsx("span", { className: "kpt-chat-message__actions", "data-open": pickerOpen ? '' : undefined, children: retryable ? (_jsx(KptIconButton, { hostProps: { className: 'kpt-chat-message__retry' }, size: "sm", "aria-label": t('chat.retry'), onClick: () => onRetry?.(), children: _jsx(KptIcon, { name: "refresh" }) })) : actions ? (_jsxs(_Fragment, { children: [_jsx(KptIconButton, { size: "sm", "aria-label": t('chat.replyTo', { author: resolvedAuthor?.name ?? '' }), onClick: () => onReply?.(), children: _jsx(KptIcon, { name: "corner-up-left" }) }), _jsxs("span", { className: "kpt-chat-message__react", onKeyDown: (event) => {
91
101
  if (event.key === 'Escape')
92
102
  setPickerOpen(false);
93
103
  }, onBlur: (event) => {
@@ -103,7 +113,7 @@ export function KptChatMessage({ message = null, author = null, text = null, tim
103
113
  }, children: _jsx("span", { "aria-hidden": "true", children: option.emoji }) }, option.kind));
104
114
  }) })) : null] })] })) : null })] }) }));
105
115
  }
106
- /** Rozmiar załącznika w kB/MB — surowe bajty nic nie mówią przy pliku obok nazwy. */
116
+ /** Rozmiar załącznika w kB/MB - surowe bajty nic nie mówią przy pliku obok nazwy. */
107
117
  function fileSize(bytes) {
108
118
  if (bytes < 1024)
109
119
  return bytes + ' B';