@djangocfg/ui-tools 2.1.407 → 2.1.409

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 (297) hide show
  1. package/README.md +9 -10
  2. package/dist/file-icon/index.cjs +449 -61
  3. package/dist/file-icon/index.cjs.map +1 -1
  4. package/dist/file-icon/index.d.cts +56 -18
  5. package/dist/file-icon/index.d.ts +56 -18
  6. package/dist/file-icon/index.mjs +448 -62
  7. package/dist/file-icon/index.mjs.map +1 -1
  8. package/dist/tree/index.cjs +49 -22
  9. package/dist/tree/index.cjs.map +1 -1
  10. package/dist/tree/index.d.cts +9 -3
  11. package/dist/tree/index.d.ts +9 -3
  12. package/dist/tree/index.mjs +49 -22
  13. package/dist/tree/index.mjs.map +1 -1
  14. package/dist/{types-B_zhyAqR.d.cts → types-eEu8SeiQ.d.cts} +4 -0
  15. package/dist/{types-B_zhyAqR.d.ts → types-eEu8SeiQ.d.ts} +4 -0
  16. package/package.json +8 -13
  17. package/src/components/FloatingToolbar/index.tsx +37 -3
  18. package/src/lib/page-snapshot/__tests__/capture-integration.test.ts +85 -0
  19. package/src/lib/page-snapshot/__tests__/engine.test.ts +36 -0
  20. package/src/lib/page-snapshot/__tests__/redaction-integration.test.ts +99 -0
  21. package/src/lib/page-snapshot/__tests__/tokens.test.ts +17 -0
  22. package/src/lib/page-snapshot/capture/__tests__/budget.test.ts +49 -0
  23. package/src/lib/page-snapshot/capture/__tests__/chrome-filter.test.ts +47 -0
  24. package/src/lib/page-snapshot/capture/__tests__/fold.test.ts +66 -0
  25. package/src/lib/page-snapshot/capture/__tests__/scope.test.ts +74 -0
  26. package/src/lib/page-snapshot/capture/__tests__/walk.test.ts +129 -0
  27. package/src/lib/page-snapshot/capture/accessible-name.ts +73 -0
  28. package/src/lib/page-snapshot/capture/budget.ts +95 -0
  29. package/src/lib/page-snapshot/capture/chrome-filter.ts +81 -0
  30. package/src/lib/page-snapshot/capture/classify.ts +111 -0
  31. package/src/lib/page-snapshot/capture/dom-utils.ts +111 -0
  32. package/src/lib/page-snapshot/capture/fold.ts +96 -0
  33. package/src/lib/page-snapshot/capture/scope.ts +169 -0
  34. package/src/lib/page-snapshot/capture/walk.ts +250 -0
  35. package/src/lib/page-snapshot/cst/__tests__/serialize.test.ts +50 -0
  36. package/src/lib/page-snapshot/cst/directives.ts +47 -0
  37. package/src/lib/page-snapshot/cst/payload.ts +50 -0
  38. package/src/lib/page-snapshot/cst/serialize.ts +84 -0
  39. package/src/lib/page-snapshot/cst/types.ts +115 -0
  40. package/src/lib/page-snapshot/engine.ts +176 -0
  41. package/src/lib/page-snapshot/index.ts +93 -0
  42. package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +72 -0
  43. package/src/lib/page-snapshot/react/PageSnapshotPreview.tsx +78 -0
  44. package/src/lib/page-snapshot/react/__tests__/PageSnapshotChip.test.tsx +54 -0
  45. package/src/lib/page-snapshot/react/__tests__/provider.test.tsx +103 -0
  46. package/src/lib/page-snapshot/react/__tests__/use-page-snapshot-toggle.test.tsx +62 -0
  47. package/src/lib/page-snapshot/react/provider.tsx +162 -0
  48. package/src/lib/page-snapshot/react/use-page-snapshot-toggle.ts +47 -0
  49. package/src/lib/page-snapshot/react/use-page-snapshot.ts +67 -0
  50. package/src/lib/page-snapshot/redaction/__tests__/audit.test.ts +25 -0
  51. package/src/lib/page-snapshot/redaction/__tests__/heuristics.test.ts +73 -0
  52. package/src/lib/page-snapshot/redaction/__tests__/luhn.test.ts +26 -0
  53. package/src/lib/page-snapshot/redaction/__tests__/patterns.test.ts +60 -0
  54. package/src/lib/page-snapshot/redaction/audit.ts +58 -0
  55. package/src/lib/page-snapshot/redaction/heuristics.ts +75 -0
  56. package/src/lib/page-snapshot/redaction/index.ts +75 -0
  57. package/src/lib/page-snapshot/redaction/luhn.ts +25 -0
  58. package/src/lib/page-snapshot/redaction/patterns.ts +111 -0
  59. package/src/lib/page-snapshot/refs/__tests__/registry.test.ts +24 -0
  60. package/src/lib/page-snapshot/refs/registry.ts +46 -0
  61. package/src/lib/page-snapshot/staleness/__tests__/hash.test.ts +34 -0
  62. package/src/lib/page-snapshot/staleness/hash.ts +20 -0
  63. package/src/lib/page-snapshot/tokens.ts +15 -0
  64. package/src/tools/AudioPlayer/context/PlayerProvider.tsx +13 -14
  65. package/src/tools/AudioPlayer/hooks/useAudioElementEvents.ts +55 -6
  66. package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
  67. package/src/tools/Chat/README.md +277 -39
  68. package/src/tools/Chat/composer/Composer.tsx +471 -0
  69. package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
  70. package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
  71. package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
  72. package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
  73. package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
  74. package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
  75. package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
  76. package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
  77. package/src/tools/Chat/composer/index.ts +45 -0
  78. package/src/tools/Chat/composer/size-context.tsx +26 -0
  79. package/src/tools/Chat/composer/types.ts +143 -0
  80. package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
  81. package/src/tools/Chat/context/ChatProvider.tsx +54 -3
  82. package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
  83. package/src/tools/Chat/core/index.ts +23 -1
  84. package/src/tools/Chat/core/markdown.ts +1 -1
  85. package/src/tools/Chat/core/metadata.ts +47 -0
  86. package/src/tools/Chat/core/payload-dispatch.ts +1 -1
  87. package/src/tools/Chat/core/transport/http.ts +71 -32
  88. package/src/tools/Chat/core/transport/sse.ts +18 -10
  89. package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
  90. package/src/tools/Chat/highlight/README.md +103 -0
  91. package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
  92. package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
  93. package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
  94. package/src/tools/Chat/highlight/index.ts +21 -0
  95. package/src/tools/Chat/highlight/resolveRef.ts +42 -0
  96. package/src/tools/Chat/highlight/types.ts +49 -0
  97. package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
  98. package/src/tools/Chat/hooks/index.ts +0 -5
  99. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
  100. package/src/tools/Chat/hooks/useChat.ts +47 -14
  101. package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
  102. package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
  103. package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
  104. package/src/tools/Chat/index.ts +25 -219
  105. package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
  106. package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
  107. package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
  108. package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
  109. package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
  110. package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
  111. package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
  112. package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
  113. package/src/tools/Chat/launcher/header/index.ts +26 -0
  114. package/src/tools/Chat/launcher/index.ts +3 -10
  115. package/src/tools/Chat/lazy.tsx +38 -284
  116. package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
  117. package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
  118. package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
  119. package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
  120. package/src/tools/Chat/messages/blocks/index.ts +12 -0
  121. package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
  122. package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
  123. package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
  124. package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
  125. package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
  126. package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
  127. package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
  128. package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
  129. package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
  130. package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
  131. package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
  132. package/src/tools/Chat/{components → messages}/index.ts +11 -5
  133. package/src/tools/Chat/public.ts +212 -0
  134. package/src/tools/Chat/shell/ChatRoot.tsx +345 -0
  135. package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
  136. package/src/tools/Chat/shell/index.ts +15 -0
  137. package/src/tools/Chat/types/block.ts +120 -0
  138. package/src/tools/Chat/types/config.ts +0 -5
  139. package/src/tools/Chat/types/index.ts +17 -0
  140. package/src/tools/Chat/types/message.ts +3 -0
  141. package/src/tools/Chat/utils/index.ts +4 -0
  142. package/src/tools/CodeEditor/README.md +4 -6
  143. package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
  144. package/src/tools/CodeEditor/components/Editor.tsx +96 -44
  145. package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
  146. package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
  147. package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
  148. package/src/tools/CodeEditor/lazy.tsx +6 -0
  149. package/src/tools/CodeEditor/lib/index.ts +1 -1
  150. package/src/tools/CodeEditor/lib/themes.ts +3 -39
  151. package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
  152. package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
  153. package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
  154. package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
  155. package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
  156. package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
  157. package/src/tools/CronScheduler/index.tsx +1 -1
  158. package/src/tools/CronScheduler/types/index.ts +8 -3
  159. package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
  160. package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
  161. package/src/tools/FileIcon/FileIcon.tsx +24 -39
  162. package/src/tools/FileIcon/get-file-icon.ts +73 -0
  163. package/src/tools/FileIcon/icons/icon-data.ts +399 -0
  164. package/src/tools/FileIcon/index.ts +4 -0
  165. package/src/tools/FileIcon/loader.ts +17 -35
  166. package/src/tools/FileIcon/specialFolders.ts +18 -0
  167. package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
  168. package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
  169. package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
  170. package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
  171. package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
  172. package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
  173. package/src/tools/ImageViewer/components/ImageViewer.tsx +96 -24
  174. package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
  175. package/src/tools/ImageViewer/utils/constants.ts +3 -0
  176. package/src/tools/ImageViewer/utils/index.ts +1 -0
  177. package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
  178. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
  179. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
  180. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
  181. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
  182. package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
  183. package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
  184. package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
  185. package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
  186. package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
  187. package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
  188. package/src/tools/JsonForm/widgets/index.ts +1 -0
  189. package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
  190. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
  191. package/src/tools/LottiePlayer/index.tsx +14 -4
  192. package/src/tools/LottiePlayer/lazy.tsx +11 -3
  193. package/src/tools/LottiePlayer/types.ts +31 -1
  194. package/src/tools/LottiePlayer/useLottie.ts +32 -9
  195. package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
  196. package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
  197. package/src/tools/Map/components/MapCluster.tsx +28 -21
  198. package/src/tools/Map/components/MapContainer.tsx +11 -4
  199. package/src/tools/Map/components/MapLegend.tsx +46 -15
  200. package/src/tools/Map/components/MapMarker.tsx +31 -2
  201. package/src/tools/Map/hooks/useMapEvents.ts +64 -105
  202. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
  203. package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
  204. package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
  205. package/src/tools/MarkdownEditor/lazy.tsx +32 -7
  206. package/src/tools/MarkdownEditor/styles.css +13 -0
  207. package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
  208. package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
  209. package/src/tools/MarkdownMessage/components.tsx +22 -9
  210. package/src/tools/MarkdownMessage/types.ts +24 -1
  211. package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
  212. package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
  213. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
  214. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
  215. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
  216. package/src/tools/Mermaid/index.tsx +6 -0
  217. package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
  218. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
  219. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
  220. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
  221. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
  222. package/src/tools/OpenapiViewer/constants.ts +3 -0
  223. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
  224. package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
  225. package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
  226. package/src/tools/PrettyCode/lazy.tsx +1 -1
  227. package/src/tools/SpeechRecognition/README.md +1 -1
  228. package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
  229. package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
  230. package/src/tools/SpeechRecognition/components/index.ts +2 -0
  231. package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
  232. package/src/tools/SpeechRecognition/core/language.ts +23 -6
  233. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
  234. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
  235. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
  236. package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
  237. package/src/tools/Tree/README.md +4 -8
  238. package/src/tools/Tree/TreeRoot.tsx +22 -10
  239. package/src/tools/Tree/components/TreeContent.tsx +24 -4
  240. package/src/tools/Tree/components/TreeLabel.tsx +8 -2
  241. package/src/tools/Tree/components/TreeRow.tsx +16 -6
  242. package/src/tools/Tree/data/flatten.ts +10 -4
  243. package/src/tools/Tree/types.ts +4 -0
  244. package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
  245. package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
  246. package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
  247. package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
  248. package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
  249. package/src/tools/Uploader/utils/formatters.ts +8 -3
  250. package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +1 -0
  251. package/src/tools/VideoPlayer/canvas/{jsx.d.ts → jsx-augmentation.ts} +12 -19
  252. package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +1 -0
  253. package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +1 -0
  254. package/src/tools/VideoPlayer/parts/fullscreen.tsx +1 -1
  255. package/src/tools/VideoPlayer/parts/pip.tsx +1 -1
  256. package/src/tools/VideoPlayer/parts/playback-rate.tsx +1 -1
  257. package/src/tools/VideoPlayer/parts/seek-bar.tsx +2 -2
  258. package/src/tools/VideoPlayer/parts/volume.tsx +2 -2
  259. package/src/tools/index.ts +2 -1
  260. package/src/tools/Chat/components/AudioToggle.tsx +0 -78
  261. package/src/tools/Chat/components/ChatRoot.tsx +0 -305
  262. package/src/tools/Chat/components/Composer.tsx +0 -216
  263. package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
  264. package/src/tools/Chat/types.ts +0 -9
  265. package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
  266. package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
  267. package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
  268. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
  269. package/src/tools/Tour/README.md +0 -373
  270. package/src/tools/Tour/components/Tour.tsx +0 -12
  271. package/src/tools/Tour/components/TourContent.tsx +0 -171
  272. package/src/tools/Tour/components/TourNavigation.tsx +0 -77
  273. package/src/tools/Tour/components/TourProgress.tsx +0 -88
  274. package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
  275. package/src/tools/Tour/components/index.ts +0 -5
  276. package/src/tools/Tour/context/TourContext.ts +0 -19
  277. package/src/tools/Tour/context/TourProvider.tsx +0 -292
  278. package/src/tools/Tour/context/index.ts +0 -2
  279. package/src/tools/Tour/hooks/index.ts +0 -3
  280. package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
  281. package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
  282. package/src/tools/Tour/hooks/useTour.ts +0 -42
  283. package/src/tools/Tour/index.ts +0 -38
  284. package/src/tools/Tour/types/index.ts +0 -224
  285. package/src/tools/Tour/utils/dom.ts +0 -98
  286. package/src/tools/Tour/utils/index.ts +0 -3
  287. package/src/tools/Tour/utils/logger.ts +0 -3
  288. package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
  289. /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
  290. /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
  291. /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
  292. /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
  293. /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
  294. /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
  295. /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
  296. /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
  297. /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+
3
+ import { cn } from '@djangocfg/ui-core/lib';
4
+
5
+ import type { ComposerAction, ComposerSize } from './types';
6
+
7
+ /** Per-size geometry for action-bar buttons + their icons.
8
+ * ChatGPT/Gemini-style — fully round, slightly larger glyphs. */
9
+ export const BUTTON_SIZE: Record<ComposerSize, { button: string; icon: string }> = {
10
+ sm: { button: 'h-7 w-7 rounded-full', icon: '[&_svg]:size-4' },
11
+ md: { button: 'h-9 w-9 rounded-full', icon: '[&_svg]:size-[1.125rem]' },
12
+ lg: { button: 'h-11 w-11 rounded-full', icon: '[&_svg]:size-5' },
13
+ };
14
+
15
+ const VARIANT_CLASSES: Record<NonNullable<ComposerAction['variant']>, string> = {
16
+ ghost: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
17
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
18
+ primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
19
+ // The send button is the composer's primary CTA — brand `primary`
20
+ // token, cohesive with accent surfaces (e.g. the user bubble).
21
+ // Disabled stays a dimmed `primary` (not neutral grey) so the button
22
+ // is still recognisable as send against the composer surface.
23
+ send: 'bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-primary/30 disabled:text-primary-foreground/60',
24
+ };
25
+
26
+ export interface ComposerButtonProps {
27
+ action: ComposerAction;
28
+ size: ComposerSize;
29
+ }
30
+
31
+ /**
32
+ * Renders a single declarative `ComposerAction` as an icon button.
33
+ * Centralises size, variant, aria-label, and `aria-pressed` so hosts
34
+ * cannot misalign or under-label an action.
35
+ */
36
+ export function ComposerButton({ action, size }: ComposerButtonProps) {
37
+ const variant = action.variant ?? 'ghost';
38
+ const sz = BUTTON_SIZE[size];
39
+ const isToggle = action.pressed !== undefined;
40
+ return (
41
+ <button
42
+ type="button"
43
+ onClick={action.onClick}
44
+ disabled={action.disabled}
45
+ aria-label={action.label}
46
+ title={action.label}
47
+ aria-pressed={isToggle ? action.pressed : undefined}
48
+ className={cn(
49
+ 'inline-flex shrink-0 cursor-pointer items-center justify-center',
50
+ 'transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
51
+ 'disabled:pointer-events-none [&_svg]:shrink-0',
52
+ // The send variant carries its own muted disabled fill, so it
53
+ // skips the generic opacity dim that the other variants use.
54
+ variant === 'send' ? 'disabled:cursor-default' : 'disabled:opacity-50',
55
+ sz.button,
56
+ sz.icon,
57
+ VARIANT_CLASSES[variant],
58
+ isToggle && action.pressed && variant === 'ghost' && 'bg-accent text-accent-foreground',
59
+ )}
60
+ >
61
+ {action.icon}
62
+ </button>
63
+ );
64
+ }
@@ -0,0 +1,90 @@
1
+ 'use client';
2
+
3
+ import { isValidElement } from 'react';
4
+
5
+ import { cn } from '@djangocfg/ui-core/lib';
6
+
7
+ import type { ComposerFooterProps } from './types';
8
+
9
+ /** Counter starts announcing once the draft passes this fraction of the cap. */
10
+ const COUNTER_THRESHOLD = 0.8;
11
+
12
+ const TEXT_SIZE = {
13
+ sm: 'text-[11px]',
14
+ md: 'text-xs',
15
+ lg: 'text-xs',
16
+ } as const;
17
+
18
+ function shortcutHint(submitOn: 'enter' | 'cmd+enter'): string {
19
+ return submitOn === 'cmd+enter'
20
+ ? '⌘⏎ to send'
21
+ : '⏎ to send · ⇧⏎ for newline';
22
+ }
23
+
24
+ /**
25
+ * The quiet strip below the input surface. Three zones: start (auto
26
+ * shortcut hint), center (host extras), end (auto char counter). Renders
27
+ * as a plain `div` unless it carries interactive controls.
28
+ */
29
+ export function ComposerFooter({
30
+ start,
31
+ center,
32
+ end,
33
+ showCounter = true,
34
+ // Off by default — ChatGPT / Gemini don't surface a keyboard hint.
35
+ // Hosts can opt back in with `showHint`.
36
+ showHint = false,
37
+ value = '',
38
+ maxLength,
39
+ submitOn = 'enter',
40
+ size = 'md',
41
+ className,
42
+ }: ComposerFooterProps) {
43
+ const nearLimit =
44
+ maxLength !== undefined && value.length >= maxLength * COUNTER_THRESHOLD;
45
+ const counterNode =
46
+ showCounter && maxLength !== undefined && nearLimit ? (
47
+ <span
48
+ aria-live="polite"
49
+ className={cn(
50
+ 'tabular-nums',
51
+ value.length >= maxLength ? 'text-destructive' : 'text-muted-foreground',
52
+ )}
53
+ >
54
+ {value.length} / {maxLength}
55
+ </span>
56
+ ) : null;
57
+
58
+ const hintNode =
59
+ showHint && !start ? (
60
+ <span aria-hidden className="text-muted-foreground/70">
61
+ {shortcutHint(submitOn)}
62
+ </span>
63
+ ) : null;
64
+
65
+ const startNode = start ?? hintNode;
66
+ const endNode = end ?? counterNode;
67
+
68
+ // Nothing to show — render nothing rather than an empty strip.
69
+ if (!startNode && !center && !endNode) return null;
70
+
71
+ // A toolbar role is only correct when the strip carries host-provided
72
+ // interactive controls. The auto hint/counter alone stay a plain div.
73
+ const hasInteractive = isValidElement(center) || isValidElement(start) || isValidElement(end);
74
+
75
+ return (
76
+ <div
77
+ role={hasInteractive ? 'toolbar' : undefined}
78
+ aria-label={hasInteractive ? 'Composer options' : undefined}
79
+ className={cn(
80
+ 'flex min-h-7 items-center justify-between gap-2 px-1 pt-1',
81
+ TEXT_SIZE[size],
82
+ className,
83
+ )}
84
+ >
85
+ <div className="flex min-w-0 items-center gap-2">{startNode}</div>
86
+ {center ? <div className="flex items-center gap-2">{center}</div> : null}
87
+ <div className="flex items-center gap-2">{endNode}</div>
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+
3
+ import { Plus } from 'lucide-react';
4
+
5
+ import { MenuBuilder, type MenuItem } from '@djangocfg/ui-core/components';
6
+ import { cn } from '@djangocfg/ui-core/lib';
7
+
8
+ import { BUTTON_SIZE } from './ComposerButton';
9
+ import { useResolvedComposerSize } from './size-context';
10
+ import type { ComposerSize } from './types';
11
+
12
+ export interface ComposerMenuButtonProps {
13
+ /** Declarative menu tree (see `MenuBuilder`). */
14
+ items: MenuItem[];
15
+ /** Omit to inherit the composer's size. */
16
+ size?: ComposerSize;
17
+ disabled?: boolean;
18
+ /** Accessible label for the trigger. */
19
+ label?: string;
20
+ /** Trigger icon. Defaults to a `+`. */
21
+ icon?: React.ReactNode;
22
+ }
23
+
24
+ /**
25
+ * The composer's leading menu button — a `+` trigger that opens a
26
+ * declarative `MenuBuilder` (ChatGPT-style: attach, tools, more).
27
+ *
28
+ * Drop it into `composerSlots.inlineStart` so it sits at the start of
29
+ * the action bar. Size inherits from the composer unless set explicitly.
30
+ */
31
+ export function ComposerMenuButton({
32
+ items,
33
+ size,
34
+ disabled,
35
+ label = 'Add files and more',
36
+ icon,
37
+ }: ComposerMenuButtonProps) {
38
+ const sz = BUTTON_SIZE[useResolvedComposerSize(size)];
39
+
40
+ return (
41
+ <MenuBuilder items={items} side="top" align="start">
42
+ <button
43
+ type="button"
44
+ disabled={disabled}
45
+ aria-label={label}
46
+ title={label}
47
+ className={cn(
48
+ 'inline-flex shrink-0 cursor-pointer items-center justify-center',
49
+ 'text-muted-foreground transition-colors',
50
+ 'hover:bg-accent hover:text-accent-foreground',
51
+ 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
52
+ 'disabled:pointer-events-none disabled:opacity-50 [&_svg]:shrink-0',
53
+ 'data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
54
+ sz.button,
55
+ sz.icon,
56
+ )}
57
+ >
58
+ {icon ?? <Plus aria-hidden />}
59
+ </button>
60
+ </MenuBuilder>
61
+ );
62
+ }
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+
3
+ import { useMemo } from 'react';
4
+ import { ChevronDown } from 'lucide-react';
5
+
6
+ import { MenuBuilder, type MenuItem } from '@djangocfg/ui-core/components';
7
+ import { cn } from '@djangocfg/ui-core/lib';
8
+
9
+ import { useResolvedComposerSize } from './size-context';
10
+ import type { ComposerSize } from './types';
11
+
12
+ /** One selectable model. */
13
+ export interface ComposerModelOption {
14
+ /** Stable id — also the radio value. */
15
+ id: string;
16
+ /** Pill text + menu row label. */
17
+ label: string;
18
+ /** Secondary muted line in the menu. */
19
+ description?: string;
20
+ disabled?: boolean;
21
+ }
22
+
23
+ export interface ComposerModelPickerProps {
24
+ /** Selected model id. */
25
+ value: string;
26
+ options: ComposerModelOption[];
27
+ onChange: (id: string) => void;
28
+ /** Omit to inherit the composer's size. */
29
+ size?: ComposerSize;
30
+ disabled?: boolean;
31
+ /** Accessible label for the trigger. */
32
+ label?: string;
33
+ className?: string;
34
+ }
35
+
36
+ /** Per-size geometry for the trigger pill. */
37
+ const PILL_SIZE: Record<ComposerSize, { pill: string; text: string }> = {
38
+ sm: { pill: 'h-7 gap-1 px-2', text: 'text-xs' },
39
+ md: { pill: 'h-9 gap-1 px-2.5', text: 'text-sm' },
40
+ lg: { pill: 'h-11 gap-1.5 px-3', text: 'text-sm' },
41
+ };
42
+
43
+ /**
44
+ * A "Flash-Lite ▾" pill that opens a radio-group model picker — Gemini's
45
+ * model switcher. Drop it into `composerSlots.inlineEnd` (trailing
46
+ * cluster, before mic/send).
47
+ */
48
+ export function ComposerModelPicker({
49
+ value,
50
+ options,
51
+ onChange,
52
+ size,
53
+ disabled,
54
+ label = 'Select model',
55
+ className,
56
+ }: ComposerModelPickerProps) {
57
+ const sz = PILL_SIZE[useResolvedComposerSize(size)];
58
+ const current = options.find((o) => o.id === value);
59
+
60
+ const items: MenuItem[] = useMemo(
61
+ () => [
62
+ {
63
+ kind: 'radio-group',
64
+ id: 'model',
65
+ value,
66
+ onValueChange: onChange,
67
+ options: options.map((o) => ({
68
+ id: o.id,
69
+ value: o.id,
70
+ label: o.label,
71
+ description: o.description,
72
+ disabled: o.disabled,
73
+ })),
74
+ },
75
+ ],
76
+ [value, options, onChange],
77
+ );
78
+
79
+ return (
80
+ <MenuBuilder items={items} side="top" align="end">
81
+ <button
82
+ type="button"
83
+ disabled={disabled}
84
+ aria-label={label}
85
+ title={label}
86
+ className={cn(
87
+ 'inline-flex shrink-0 cursor-pointer items-center rounded-full',
88
+ 'text-muted-foreground transition-colors',
89
+ 'hover:bg-accent hover:text-accent-foreground',
90
+ 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
91
+ 'disabled:pointer-events-none disabled:opacity-50',
92
+ 'data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
93
+ '[&_svg]:size-4 [&_svg]:shrink-0',
94
+ sz.pill,
95
+ sz.text,
96
+ className,
97
+ )}
98
+ >
99
+ <span className="font-medium">{current?.label ?? value}</span>
100
+ <ChevronDown aria-hidden className="opacity-70" />
101
+ </button>
102
+ </MenuBuilder>
103
+ );
104
+ }
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useMemo, useRef } from 'react';
4
+
5
+ import {
6
+ LazyMarkdownEditor as MarkdownEditor,
7
+ type MarkdownEditorHandle,
8
+ type MentionConfig,
9
+ } from '@djangocfg/ui-tools/markdown-editor';
10
+
11
+ import { useRegisterComposer } from '../hooks/useAutoFocusOnStreamEnd';
12
+ import type { ComposerSize, ComposerTextareaProps } from './types';
13
+
14
+ /** Single-row baseline height per composer size (px). */
15
+ const MIN_HEIGHT: Record<ComposerSize, number> = {
16
+ sm: 20,
17
+ md: 24,
18
+ lg: 28,
19
+ };
20
+
21
+ export interface ComposerRichTextareaProps extends ComposerTextareaProps {
22
+ /** `@`-mention autocomplete config. Omit for a plain rich textarea. */
23
+ mentions?: MentionConfig;
24
+ }
25
+
26
+ /**
27
+ * Drop-in TipTap-backed textarea for the composer's Tier B `Textarea`
28
+ * slot. Pre-wired with chat defaults — `unstyled` (the composer surface
29
+ * draws the frame), no toolbar, size-matched single-row height, and
30
+ * Enter-to-send. The host passes only `mentions` if it needs `@`
31
+ * autocomplete; everything else flows from `ComposerTextareaProps`.
32
+ *
33
+ * @example
34
+ * <Composer
35
+ * slots={{ Textarea: (p) => <ComposerRichTextarea {...p} mentions={cfg} /> }}
36
+ * />
37
+ */
38
+ export function ComposerRichTextarea({
39
+ composer,
40
+ placeholder,
41
+ disabled,
42
+ size,
43
+ className,
44
+ mentions,
45
+ }: ComposerRichTextareaProps) {
46
+ // Tiptap captures the mentions object once on first render — keep its
47
+ // identity stable so the suggestion plugin stays wired.
48
+ const stableMentions = useMemo(() => mentions, [mentions]);
49
+
50
+ // The TipTap editor has no `<textarea>`, so the composer's default
51
+ // `focus` handle (which targets `textareaRef`) is a no-op here.
52
+ // Register the editor's real imperative handle with the chat context
53
+ // so `useAutoFocusOnStreamEnd` (refocus after the assistant reply)
54
+ // and the voice slot actually drive *this* editor.
55
+ const editorRef = useRef<MarkdownEditorHandle>(null);
56
+ const focus = useCallback(() => editorRef.current?.focus(), []);
57
+ const moveCursorToEnd = useCallback(
58
+ () => editorRef.current?.moveCursorToEnd(),
59
+ [],
60
+ );
61
+ const getValue = useCallback(() => composer.value, [composer.value]);
62
+ useRegisterComposer({
63
+ focus,
64
+ moveCursorToEnd,
65
+ getValue,
66
+ setValue: composer.setValue,
67
+ });
68
+
69
+ return (
70
+ <div className={className ?? 'min-w-0 flex-1'}>
71
+ <MarkdownEditor
72
+ ref={editorRef}
73
+ value={composer.value}
74
+ onChange={composer.setValue}
75
+ mentions={stableMentions}
76
+ placeholder={placeholder}
77
+ minHeight={MIN_HEIGHT[size]}
78
+ showToolbar={false}
79
+ unstyled
80
+ disabled={disabled}
81
+ onSubmit={() => {
82
+ if (composer.canSubmit) void composer.submit();
83
+ return true;
84
+ }}
85
+ />
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+ import { X } from 'lucide-react';
5
+
6
+ import { cn } from '@djangocfg/ui-core/lib';
7
+
8
+ import { useResolvedComposerSize } from './size-context';
9
+ import type { ComposerSize } from './types';
10
+
11
+ export interface ComposerToolPillProps {
12
+ /** Leading icon — a lucide-react element. */
13
+ icon?: ReactNode;
14
+ /** Pill text. */
15
+ label: ReactNode;
16
+ /** Accent fill when the tool/mode is active. */
17
+ active?: boolean;
18
+ /** Clicking the pill body (toggles the tool). */
19
+ onClick?: () => void;
20
+ /** When set, renders a trailing `×` that calls this instead. */
21
+ onRemove?: () => void;
22
+ disabled?: boolean;
23
+ /** Omit to inherit the composer's size. */
24
+ size?: ComposerSize;
25
+ className?: string;
26
+ }
27
+
28
+ /** Per-size geometry — round capsule heights matching the action bar. */
29
+ const PILL_SIZE: Record<ComposerSize, { pill: string; text: string; icon: string }> = {
30
+ sm: { pill: 'h-7 gap-1 px-2', text: 'text-xs', icon: '[&_svg]:size-3.5' },
31
+ md: { pill: 'h-9 gap-1.5 px-3', text: 'text-sm', icon: '[&_svg]:size-4' },
32
+ lg: { pill: 'h-11 gap-2 px-3.5', text: 'text-sm', icon: '[&_svg]:size-[1.125rem]' },
33
+ };
34
+
35
+ /**
36
+ * A rounded action-bar capsule for a selected tool or mode — Gemini's
37
+ * "🎨 Images" pill. Drop it into `composerSlots.inlineStart` (leading
38
+ * cluster). When `onRemove` is set the pill shows a trailing `×`.
39
+ */
40
+ export function ComposerToolPill({
41
+ icon,
42
+ label,
43
+ active = false,
44
+ onClick,
45
+ onRemove,
46
+ disabled,
47
+ size,
48
+ className,
49
+ }: ComposerToolPillProps) {
50
+ const sz = PILL_SIZE[useResolvedComposerSize(size)];
51
+ return (
52
+ <div
53
+ className={cn(
54
+ 'inline-flex shrink-0 items-center rounded-full border transition-colors',
55
+ sz.pill,
56
+ sz.text,
57
+ sz.icon,
58
+ active
59
+ ? 'border-transparent bg-accent text-accent-foreground'
60
+ : 'border-border bg-transparent text-muted-foreground',
61
+ disabled && 'pointer-events-none opacity-50',
62
+ className,
63
+ )}
64
+ >
65
+ <button
66
+ type="button"
67
+ onClick={onClick}
68
+ disabled={disabled}
69
+ aria-pressed={onClick ? active : undefined}
70
+ className={cn(
71
+ 'inline-flex items-center gap-1.5 outline-none',
72
+ onClick && !disabled && 'cursor-pointer hover:text-foreground',
73
+ )}
74
+ >
75
+ {icon ? <span className="flex shrink-0 [&_svg]:shrink-0">{icon}</span> : null}
76
+ <span className="font-medium">{label}</span>
77
+ </button>
78
+ {onRemove ? (
79
+ <button
80
+ type="button"
81
+ onClick={onRemove}
82
+ disabled={disabled}
83
+ aria-label="Remove"
84
+ title="Remove"
85
+ className={cn(
86
+ '-mr-1 ml-0.5 inline-flex size-5 cursor-pointer items-center justify-center',
87
+ 'rounded-full transition-colors hover:bg-foreground/10 [&_svg]:size-3.5',
88
+ )}
89
+ >
90
+ <X aria-hidden />
91
+ </button>
92
+ ) : null}
93
+ </div>
94
+ );
95
+ }
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+
3
+ /** Composer — the `<Composer>` shell + its slot kit. */
4
+
5
+ export {
6
+ Composer,
7
+ type ComposerProps,
8
+ type ComposerSize,
9
+ type ComposerAppearance,
10
+ } from './Composer';
11
+ export { ComposerButton, BUTTON_SIZE, type ComposerButtonProps } from './ComposerButton';
12
+ export { ComposerMenuButton, type ComposerMenuButtonProps } from './ComposerMenuButton';
13
+ export { ComposerRichTextarea, type ComposerRichTextareaProps } from './ComposerRichTextarea';
14
+ export { ComposerActionBar } from './ComposerActionBar';
15
+ export { ComposerToolPill, type ComposerToolPillProps } from './ComposerToolPill';
16
+ export {
17
+ ComposerModelPicker,
18
+ type ComposerModelPickerProps,
19
+ type ComposerModelOption,
20
+ } from './ComposerModelPicker';
21
+ export {
22
+ ComposerBanner,
23
+ type ComposerBannerProps,
24
+ type ComposerBannerAction,
25
+ } from './ComposerBanner';
26
+ export { ComposerFooter } from './ComposerFooter';
27
+ export { useResolvedComposerSize } from './size-context';
28
+ export {
29
+ useComposerActions,
30
+ type UseComposerActionsParams,
31
+ type ComposerActionClusters,
32
+ } from './useComposerActions';
33
+ export type {
34
+ ComposerAction,
35
+ ComposerActionVisibility,
36
+ ComposerLayout,
37
+ ComposerSlots,
38
+ ComposerSlotComponents,
39
+ ComposerSlotProps,
40
+ ComposerFooterProps,
41
+ SendButtonProps,
42
+ AttachButtonProps,
43
+ ComposerTextareaProps,
44
+ ActionBarProps,
45
+ } from './types';
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext } from 'react';
4
+
5
+ import type { ComposerSize } from './types';
6
+
7
+ /**
8
+ * Propagates the composer's `size` to slot components placed in
9
+ * `composerSlots.inlineStart` / `inlineEnd` (`ComposerMenuButton`,
10
+ * `ComposerModelPicker`, `ComposerToolPill`). Without this a host has
11
+ * to repeat `size` on every slotted control to keep the action bar
12
+ * visually aligned; with it, an explicit `size` prop on the slot
13
+ * component still wins, but omitting it inherits the composer's size.
14
+ */
15
+ const ComposerSizeContext = createContext<ComposerSize | null>(null);
16
+
17
+ export const ComposerSizeProvider = ComposerSizeContext.Provider;
18
+
19
+ /**
20
+ * Resolve the effective size for a slotted control: an explicit prop
21
+ * always wins; otherwise inherit from the composer; final fallback `md`.
22
+ */
23
+ export function useResolvedComposerSize(explicit?: ComposerSize): ComposerSize {
24
+ const inherited = useContext(ComposerSizeContext);
25
+ return explicit ?? inherited ?? 'md';
26
+ }