@djangocfg/ui-tools 2.1.417 → 2.1.419

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 (335) hide show
  1. package/dist/audio-player/index.cjs +1 -2
  2. package/dist/audio-player/index.cjs.map +1 -1
  3. package/dist/audio-player/index.d.cts +3 -11
  4. package/dist/audio-player/index.d.ts +3 -11
  5. package/dist/audio-player/index.mjs +1 -2
  6. package/dist/audio-player/index.mjs.map +1 -1
  7. package/dist/file-icon/index.cjs +3 -3
  8. package/dist/file-icon/index.cjs.map +1 -1
  9. package/dist/file-icon/index.mjs +3 -3
  10. package/dist/file-icon/index.mjs.map +1 -1
  11. package/dist/tree/index.cjs +0 -3
  12. package/dist/tree/index.cjs.map +1 -1
  13. package/dist/tree/index.mjs +0 -3
  14. package/dist/tree/index.mjs.map +1 -1
  15. package/package.json +117 -36
  16. package/src/common/FloatingToolbar/actions/CopyAction.tsx +31 -0
  17. package/src/{components → common}/FloatingToolbar/actions/DownloadAction.tsx +15 -10
  18. package/src/common/FloatingToolbar/actions/ExpandAction.tsx +33 -0
  19. package/src/common/FloatingToolbar/actions/FullscreenAction.tsx +38 -0
  20. package/src/{components → common}/FloatingToolbar/index.tsx +39 -0
  21. package/src/lib/http.ts +64 -0
  22. package/src/tools/chat/index.ts +1 -1
  23. package/src/tools/chat/launcher/ChatFAB.tsx +66 -74
  24. package/src/tools/chat/launcher/header/ChatHeaderActionButton.tsx +2 -3
  25. package/src/tools/chat/lazy.tsx +1 -1
  26. package/src/tools/chat/messages/MessageBubble.tsx +1 -1
  27. package/src/tools/chat/messages/blocks/builtin.tsx +1 -1
  28. package/src/tools/chat/messages/blocks/renderers/CodeBlock.tsx +2 -2
  29. package/src/tools/chat/messages/blocks/renderers/JsonBlock.tsx +12 -1
  30. package/src/tools/data/DataGrid/lazy.tsx +1 -1
  31. package/src/tools/data/DataTable/lazy.tsx +1 -1
  32. package/src/tools/data/JsonTree/JsonViewer.tsx +720 -0
  33. package/src/tools/data/JsonTree/README.md +126 -73
  34. package/src/tools/data/JsonTree/index.tsx +3 -95
  35. package/src/tools/data/JsonTree/lazy.tsx +10 -50
  36. package/src/tools/data/JsonTree/types.ts +82 -63
  37. package/src/tools/data/Kanban/lazy.tsx +1 -1
  38. package/src/tools/data/Listbox/lazy.tsx +1 -1
  39. package/src/tools/data/Masonry/lazy.tsx +1 -1
  40. package/src/tools/data/Timeline/lazy.tsx +1 -1
  41. package/src/tools/data/Tree/components/TreeRow.tsx +0 -11
  42. package/src/tools/data/Tree/lazy.tsx +1 -1
  43. package/src/tools/dev/Map/lazy.tsx +1 -1
  44. package/src/tools/dev/Mermaid/Mermaid.client.tsx +2 -2
  45. package/src/tools/dev/Mermaid/lazy.tsx +1 -1
  46. package/src/tools/dev/api/ApiRefTable/ApiRefTable.tsx +65 -0
  47. package/src/tools/dev/api/ApiRefTable/README.md +31 -0
  48. package/src/tools/dev/api/ApiRefTable/components/Row.tsx +96 -0
  49. package/src/tools/dev/api/ApiRefTable/components/TypeDisplay.tsx +44 -0
  50. package/src/tools/dev/api/ApiRefTable/index.ts +6 -0
  51. package/src/tools/dev/api/ApiRefTable/lazy.tsx +21 -0
  52. package/src/tools/dev/api/ApiRefTable/types.ts +82 -0
  53. package/src/tools/dev/api/ApiRefTable/utils.ts +42 -0
  54. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/ApiIntroSection.tsx +1 -1
  55. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +1 -1
  56. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/index.tsx +1 -1
  57. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +7 -21
  58. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/RequestPanel.tsx +1 -1
  59. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/PrettyView.tsx +13 -19
  60. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/types.ts +1 -1
  61. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/lazy.tsx +1 -1
  62. package/src/tools/dev/api/RequestViewer/README.md +33 -0
  63. package/src/tools/dev/api/RequestViewer/RequestViewer.tsx +121 -0
  64. package/src/tools/dev/api/RequestViewer/components/BodyTab.tsx +44 -0
  65. package/src/tools/dev/api/RequestViewer/components/EmptyState.tsx +13 -0
  66. package/src/tools/dev/api/RequestViewer/components/HeadersTab.tsx +78 -0
  67. package/src/tools/dev/api/RequestViewer/components/TimingTab.tsx +113 -0
  68. package/src/tools/dev/api/RequestViewer/components/utils.ts +31 -0
  69. package/src/tools/dev/api/RequestViewer/index.ts +16 -0
  70. package/src/tools/dev/api/RequestViewer/lazy.tsx +30 -0
  71. package/src/tools/dev/api/RequestViewer/types.ts +81 -0
  72. package/src/tools/dev/code/DiffViewer/DiffViewer.tsx +144 -0
  73. package/src/tools/dev/code/DiffViewer/README.md +33 -0
  74. package/src/tools/dev/code/DiffViewer/components/CopyButton.tsx +49 -0
  75. package/src/tools/dev/code/DiffViewer/components/DiffLineContent.tsx +48 -0
  76. package/src/tools/dev/code/DiffViewer/components/SplitView.tsx +220 -0
  77. package/src/tools/dev/code/DiffViewer/components/UnifiedView.tsx +154 -0
  78. package/src/tools/dev/code/DiffViewer/hooks/useDiff.ts +47 -0
  79. package/src/tools/dev/code/DiffViewer/hooks/useHighlighter.ts +54 -0
  80. package/src/tools/dev/code/DiffViewer/index.ts +22 -0
  81. package/src/tools/dev/code/DiffViewer/lazy.tsx +22 -0
  82. package/src/tools/dev/code/DiffViewer/types.ts +109 -0
  83. package/src/tools/dev/code/DiffViewer/utils/computeDiff.ts +159 -0
  84. package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/CollapseToggle.tsx +1 -1
  85. package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/MarkdownMessage.tsx +1 -1
  86. package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/components.tsx +2 -2
  87. package/src/tools/dev/{PrettyCode → code/PrettyCode}/PrettyCode.client.tsx +2 -2
  88. package/src/tools/dev/{PrettyCode → code/PrettyCode}/lazy.tsx +1 -1
  89. package/src/tools/dev/ops/EnvTable/EnvTable.tsx +228 -0
  90. package/src/tools/dev/ops/EnvTable/README.md +29 -0
  91. package/src/tools/dev/ops/EnvTable/hooks/useEnvMask.ts +121 -0
  92. package/src/tools/dev/ops/EnvTable/index.ts +12 -0
  93. package/src/tools/dev/ops/EnvTable/lazy.tsx +21 -0
  94. package/src/tools/dev/ops/EnvTable/types.ts +76 -0
  95. package/src/tools/dev/ops/LogViewer/LogViewer.tsx +194 -0
  96. package/src/tools/dev/ops/LogViewer/README.md +30 -0
  97. package/src/tools/dev/ops/LogViewer/components/LogRow.tsx +151 -0
  98. package/src/tools/dev/ops/LogViewer/components/Toolbar.tsx +199 -0
  99. package/src/tools/dev/ops/LogViewer/hooks/useAutoScroll.ts +68 -0
  100. package/src/tools/dev/ops/LogViewer/hooks/useLogFilter.ts +58 -0
  101. package/src/tools/dev/ops/LogViewer/index.ts +18 -0
  102. package/src/tools/dev/ops/LogViewer/lazy.tsx +25 -0
  103. package/src/tools/dev/ops/LogViewer/types.ts +142 -0
  104. package/src/tools/dev/ops/LogViewer/utils/ansi.ts +231 -0
  105. package/src/tools/forms/CodeEditor/components/Editor.tsx +19 -0
  106. package/src/tools/forms/CodeEditor/hooks/useEditorTheme.ts +13 -73
  107. package/src/tools/forms/CodeEditor/lazy.tsx +1 -1
  108. package/src/tools/forms/CodeEditor/types/index.ts +7 -0
  109. package/src/tools/forms/FileUpload/lazy.tsx +1 -1
  110. package/src/tools/forms/JsonEditor/JsonEditor.tsx +115 -0
  111. package/src/tools/forms/JsonEditor/index.ts +1 -0
  112. package/src/tools/forms/JsonEditor/lazy.tsx +24 -0
  113. package/src/tools/forms/JsonForm/index.ts +1 -1
  114. package/src/tools/forms/JsonForm/lazy.tsx +1 -1
  115. package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +40 -0
  116. package/src/tools/forms/MarkdownEditor/lazy.tsx +1 -1
  117. package/src/tools/forms/MarkdownEditor/styles.css +174 -21
  118. package/src/tools/forms/NotionEditor/CustomKeymap.ts +48 -0
  119. package/src/tools/forms/NotionEditor/LinkDialog.tsx +133 -0
  120. package/src/tools/forms/NotionEditor/NotionEditor.tsx +304 -0
  121. package/src/tools/forms/NotionEditor/README.md +237 -0
  122. package/src/tools/forms/NotionEditor/SlashExtension.ts +32 -0
  123. package/src/tools/forms/NotionEditor/SlashList.tsx +136 -0
  124. package/src/tools/forms/NotionEditor/TaskItemView.tsx +41 -0
  125. package/src/tools/forms/NotionEditor/createSlashSuggestion.ts +121 -0
  126. package/src/tools/forms/NotionEditor/extensions.ts +105 -0
  127. package/src/tools/forms/NotionEditor/index.ts +1 -0
  128. package/src/tools/forms/NotionEditor/lazy.tsx +44 -0
  129. package/src/tools/forms/NotionEditor/slashItems.ts +159 -0
  130. package/src/tools/forms/NotionEditor/styles.css +478 -0
  131. package/src/tools/forms/NotionEditor/types.ts +28 -0
  132. package/src/tools/index.ts +153 -13
  133. package/src/tools/input/Combobox/lazy.tsx +1 -1
  134. package/src/tools/input/CronScheduler/components/CronPreview.README.md +28 -0
  135. package/src/tools/input/CronScheduler/components/CronPreview.tsx +136 -0
  136. package/src/tools/input/CronScheduler/components/index.ts +3 -0
  137. package/src/tools/input/CronScheduler/index.tsx +5 -1
  138. package/src/tools/input/CronScheduler/lazy.tsx +5 -1
  139. package/src/tools/input/CronScheduler/utils/cron-next-runs.ts +122 -0
  140. package/src/tools/input/CronScheduler/utils/index.ts +1 -0
  141. package/src/tools/input/Scroller/lazy.tsx +1 -1
  142. package/src/tools/input/Sortable/lazy.tsx +1 -1
  143. package/src/tools/input/SpeechRecognition/lazy.tsx +1 -1
  144. package/src/tools/input/SpeechRecognition/widgets/VoiceComposerSlot.tsx +41 -36
  145. package/src/tools/media/AudioPlayer/PlayerShell.tsx +3 -11
  146. package/src/tools/media/AudioPlayer/types.ts +4 -11
  147. package/src/tools/media/ImageViewer/components/ImageToolbar.tsx +58 -47
  148. package/src/tools/media/ImageViewer/components/ImageViewer.tsx +35 -19
  149. package/src/tools/media/ImageViewer/lazy.tsx +1 -1
  150. package/src/tools/media/ImageViewer/types.ts +4 -0
  151. package/src/tools/media/LottiePlayer/lazy.tsx +1 -1
  152. package/src/tools/media/VideoPlayer/VideoPlayer.tsx +47 -1
  153. package/src/tools/media/VideoPlayer/parts/fullscreen.tsx +21 -4
  154. package/src/tools/media/VideoPlayer/parts/pip.tsx +21 -4
  155. package/src/tools/media/VideoPlayer/parts/play-button.tsx +21 -4
  156. package/src/tools/media/VideoPlayer/parts/playback-rate.tsx +19 -3
  157. package/src/tools/media/VideoPlayer/parts/volume.tsx +237 -18
  158. package/src/tools/media/VideoPlayer/styles/video-player.css +87 -7
  159. package/src/tools/media/VideoPlayer/types.ts +4 -0
  160. package/src/tools/overlay/ResponsiveDialog/lazy.tsx +1 -1
  161. package/src/tools/overlay/ScrollSpy/lazy.tsx +1 -1
  162. package/src/tools/overlay/SelectionToolbar/lazy.tsx +1 -1
  163. package/src/tools/overlay/Tour/lazy.tsx +1 -1
  164. package/src/tools/visual/Marquee/lazy.tsx +1 -1
  165. package/src/tools/visual/QRCode/lazy.tsx +1 -1
  166. package/src/tools/visual/charts/ActivityGraph/ActivityGraph.tsx +195 -0
  167. package/src/tools/visual/charts/ActivityGraph/README.md +28 -0
  168. package/src/tools/visual/charts/ActivityGraph/index.ts +8 -0
  169. package/src/tools/visual/charts/ActivityGraph/lazy.tsx +21 -0
  170. package/src/tools/visual/charts/ActivityGraph/types.ts +59 -0
  171. package/src/tools/visual/charts/ActivityGraph/utils.ts +88 -0
  172. package/src/tools/visual/charts/CommitGraph/CommitGraph.tsx +80 -0
  173. package/src/tools/visual/charts/CommitGraph/README.md +28 -0
  174. package/src/tools/visual/charts/CommitGraph/components/CommitDetail.tsx +107 -0
  175. package/src/tools/visual/charts/CommitGraph/components/CommitRow.tsx +122 -0
  176. package/src/tools/visual/charts/CommitGraph/components/Rails.tsx +171 -0
  177. package/src/tools/visual/charts/CommitGraph/hooks/useGraphLayout.ts +169 -0
  178. package/src/tools/visual/charts/CommitGraph/hooks/useLaneColors.ts +45 -0
  179. package/src/tools/visual/charts/CommitGraph/index.ts +14 -0
  180. package/src/tools/visual/charts/CommitGraph/lazy.tsx +25 -0
  181. package/src/tools/visual/charts/CommitGraph/types.ts +85 -0
  182. package/src/tools/visual/charts/CommitGraph/utils.ts +53 -0
  183. package/src/tools/visual/{Gauge → charts/Gauge}/lazy.tsx +1 -1
  184. package/src/tools/visual/charts/Sparkline/README.md +29 -0
  185. package/src/tools/visual/charts/Sparkline/Sparkline.tsx +217 -0
  186. package/src/tools/visual/charts/Sparkline/index.ts +9 -0
  187. package/src/tools/visual/charts/Sparkline/lazy.tsx +26 -0
  188. package/src/tools/visual/charts/Sparkline/types.ts +58 -0
  189. package/src/tools/visual/design/ColorPalette/ColorPalette.tsx +129 -0
  190. package/src/tools/visual/design/ColorPalette/README.md +34 -0
  191. package/src/tools/visual/design/ColorPalette/components/Swatch.tsx +102 -0
  192. package/src/tools/visual/design/ColorPalette/hooks/useCopyToClipboard.ts +41 -0
  193. package/src/tools/visual/design/ColorPalette/index.ts +12 -0
  194. package/src/tools/visual/design/ColorPalette/lazy.tsx +21 -0
  195. package/src/tools/visual/design/ColorPalette/types.ts +63 -0
  196. package/src/tools/visual/design/ColorPalette/utils.ts +83 -0
  197. package/src/tools/visual/{ColorPicker → design/ColorPicker}/lazy.tsx +1 -1
  198. package/src/tools/visual/{FileIcon → design/FileIcon}/treeAdapter.tsx +1 -1
  199. package/src/tools/visual/{Fps → indicators/Fps}/lazy.tsx +1 -1
  200. package/src/tools/visual/{Rating → indicators/Rating}/lazy.tsx +1 -1
  201. package/src/tools/visual/indicators/StatusIndicator/README.md +28 -0
  202. package/src/tools/visual/indicators/StatusIndicator/StatusIndicator.tsx +83 -0
  203. package/src/tools/visual/indicators/StatusIndicator/index.ts +14 -0
  204. package/src/tools/visual/indicators/StatusIndicator/lazy.tsx +21 -0
  205. package/src/tools/visual/indicators/StatusIndicator/types.ts +133 -0
  206. package/src/components/FloatingToolbar/actions/CopyAction.tsx +0 -22
  207. package/src/components/FloatingToolbar/actions/ExpandAction.tsx +0 -25
  208. package/src/components/FloatingToolbar/actions/FullscreenAction.tsx +0 -30
  209. package/src/tools/data/JsonTree/components/JsonContent.tsx +0 -197
  210. package/src/tools/data/JsonTree/hooks/useJsonExpand.ts +0 -50
  211. /package/src/{components → common}/FloatingToolbar/FloatingToolbar.css +0 -0
  212. /package/src/{components → common}/FloatingToolbar/actions/index.ts +0 -0
  213. /package/src/{components → common}/FloatingToolbar/hooks/useElementCorner.ts +0 -0
  214. /package/src/{components → common}/FloatingToolbar/hooks/useScrollIsolation.ts +0 -0
  215. /package/src/{components → common}/index.ts +0 -0
  216. /package/src/{components → common}/lazy-wrapper.tsx +0 -0
  217. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/README.md +0 -0
  218. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/DocsView.tsx +0 -0
  219. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +0 -0
  220. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +0 -0
  221. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +0 -0
  222. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +0 -0
  223. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +0 -0
  224. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +0 -0
  225. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +0 -0
  226. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/index.tsx +0 -0
  227. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +0 -0
  228. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +0 -0
  229. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +0 -0
  230. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/index.tsx +0 -0
  231. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +0 -0
  232. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +0 -0
  233. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +0 -0
  234. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +0 -0
  235. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +0 -0
  236. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/defaults.ts +0 -0
  237. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/index.tsx +0 -0
  238. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/context.tsx +0 -0
  239. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +0 -0
  240. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/index.tsx +0 -0
  241. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/store/index.ts +0 -0
  242. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/store/selectors.ts +0 -0
  243. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/types.ts +0 -0
  244. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/SchemaCopyMenu.tsx +0 -0
  245. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/BrandHeader.tsx +0 -0
  246. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/CategoryBlock.tsx +0 -0
  247. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/EndpointRow.tsx +0 -0
  248. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SchemaSection.tsx +0 -0
  249. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SearchInput.tsx +0 -0
  250. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SidebarBody.tsx +0 -0
  251. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/Toolbar.tsx +0 -0
  252. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/buildVM.ts +0 -0
  253. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/index.tsx +0 -0
  254. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/types.ts +0 -0
  255. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/useDebouncedValue.ts +0 -0
  256. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/SlideInPlayground.tsx +0 -0
  257. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/TryItSheet.tsx +0 -0
  258. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/anchor.ts +0 -0
  259. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/grouping.ts +0 -0
  260. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/index.tsx +0 -0
  261. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/sidebarLabel.ts +0 -0
  262. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/index.ts +0 -0
  263. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/BodyFormEditor.tsx +0 -0
  264. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/EndpointDraftSync.tsx +0 -0
  265. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/EndpointResetButton.tsx +0 -0
  266. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/PreviewView.tsx +0 -0
  267. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/RawView.tsx +0 -0
  268. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/StatusBar.tsx +0 -0
  269. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/ViewTabs.tsx +0 -0
  270. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/detectContent.ts +0 -0
  271. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/index.tsx +0 -0
  272. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/useResponseView.ts +0 -0
  273. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/SendButton.tsx +0 -0
  274. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ui.tsx +0 -0
  275. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/constants.ts +0 -0
  276. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/context/PlaygroundContext.tsx +0 -0
  277. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/index.ts +0 -0
  278. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useDocsUrlSync.ts +0 -0
  279. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useEndpointDraft.ts +0 -0
  280. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useMobile.ts +0 -0
  281. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useOpenApiSchema.ts +0 -0
  282. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/index.tsx +0 -0
  283. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/types.ts +0 -0
  284. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/apiKeyManager.ts +0 -0
  285. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/codeSamples.ts +0 -0
  286. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/formatters.ts +0 -0
  287. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/index.ts +0 -0
  288. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/operationToHar.ts +0 -0
  289. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/sampler.ts +0 -0
  290. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/schemaExport.ts +0 -0
  291. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/scrollParent.ts +0 -0
  292. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/url.ts +0 -0
  293. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/versionManager.ts +0 -0
  294. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/ActionRow.tsx +0 -0
  295. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/ChatMessageRow.tsx +0 -0
  296. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/CodeBlock.tsx +0 -0
  297. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/README.md +0 -0
  298. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/index.ts +0 -0
  299. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/linkRules.ts +0 -0
  300. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/plainText.ts +0 -0
  301. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/sanitize.ts +0 -0
  302. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/types.ts +0 -0
  303. /package/src/tools/dev/{PrettyCode → code/PrettyCode}/README.md +0 -0
  304. /package/src/tools/dev/{PrettyCode → code/PrettyCode}/index.tsx +0 -0
  305. /package/src/tools/dev/{PrettyCode → code/PrettyCode}/registerPrismLanguages.ts +0 -0
  306. /package/src/tools/visual/{Gauge → charts/Gauge}/Gauge.tsx +0 -0
  307. /package/src/tools/visual/{Gauge → charts/Gauge}/index.ts +0 -0
  308. /package/src/tools/visual/{Gauge → charts/Gauge}/types.ts +0 -0
  309. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/ColorPicker.tsx +0 -0
  310. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/ColorPickerContext.tsx +0 -0
  311. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/ColorPickerStore.tsx +0 -0
  312. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/index.ts +0 -0
  313. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/index.ts +0 -0
  314. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/lib/color-utils.ts +0 -0
  315. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerAlphaSlider.tsx +0 -0
  316. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerArea.tsx +0 -0
  317. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerEyeDropper.tsx +0 -0
  318. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerFormatSelect.tsx +0 -0
  319. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerHueSlider.tsx +0 -0
  320. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerInput.tsx +0 -0
  321. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerSwatch.tsx +0 -0
  322. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/index.ts +0 -0
  323. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/types.ts +0 -0
  324. /package/src/tools/visual/{FileIcon → design/FileIcon}/FileIcon.tsx +0 -0
  325. /package/src/tools/visual/{FileIcon → design/FileIcon}/get-file-icon.ts +0 -0
  326. /package/src/tools/visual/{FileIcon → design/FileIcon}/icons/icon-data.ts +0 -0
  327. /package/src/tools/visual/{FileIcon → design/FileIcon}/index.ts +0 -0
  328. /package/src/tools/visual/{FileIcon → design/FileIcon}/loader.ts +0 -0
  329. /package/src/tools/visual/{FileIcon → design/FileIcon}/specialFolders.ts +0 -0
  330. /package/src/tools/visual/{Fps → indicators/Fps}/Fps.tsx +0 -0
  331. /package/src/tools/visual/{Fps → indicators/Fps}/index.ts +0 -0
  332. /package/src/tools/visual/{Fps → indicators/Fps}/types.ts +0 -0
  333. /package/src/tools/visual/{Rating → indicators/Rating}/Rating.tsx +0 -0
  334. /package/src/tools/visual/{Rating → indicators/Rating}/index.ts +0 -0
  335. /package/src/tools/visual/{Rating → indicators/Rating}/types.ts +0 -0
@@ -0,0 +1,121 @@
1
+ // Adapted from jalcoui (MIT) — github.com/jal-co/ui
2
+
3
+ 'use client';
4
+
5
+ import * as React from 'react';
6
+ import type { EnvVariable } from '../types';
7
+
8
+ const COPY_FEEDBACK_MS = 1500;
9
+
10
+ /**
11
+ * Mask the value for display. Reveals the first 4 chars only when the value
12
+ * is long enough — the raw `value` never leaks to the DOM in masked state.
13
+ */
14
+ export function maskValue(value: string): string {
15
+ if (value.length <= 4) return '••••••••';
16
+ return value.slice(0, 4) + '••••••••';
17
+ }
18
+
19
+ export interface UseEnvMaskOptions {
20
+ variables: EnvVariable[];
21
+ defaultRevealed?: boolean;
22
+ }
23
+
24
+ export interface UseEnvMaskReturn {
25
+ /** Set of indices currently revealed. */
26
+ revealed: Set<number>;
27
+ /** Whether every row is revealed. */
28
+ allRevealed: boolean;
29
+ /** Toggle a single row by index. */
30
+ toggleIndex: (index: number) => void;
31
+ /** Reveal-all / hide-all toggle. */
32
+ toggleAll: () => void;
33
+ /** Last-copied row index (or `'all'`) — null when feedback expired. */
34
+ copiedTarget: number | 'all' | null;
35
+ /** Copy a single value to clipboard, marking the row as copied. */
36
+ copyValue: (index: number, value: string) => void;
37
+ /** Copy every variable as a `.env`-style block. */
38
+ copyAllAsEnv: () => void;
39
+ }
40
+
41
+ /**
42
+ * Mask / reveal / copy state for {@link EnvTable}. The hook owns per-row
43
+ * reveal state and short-lived "copied!" feedback flags.
44
+ */
45
+ export function useEnvMask({
46
+ variables,
47
+ defaultRevealed = false,
48
+ }: UseEnvMaskOptions): UseEnvMaskReturn {
49
+ const [revealed, setRevealed] = React.useState<Set<number>>(() =>
50
+ defaultRevealed ? new Set(variables.map((_, i) => i)) : new Set(),
51
+ );
52
+ const [copiedTarget, setCopiedTarget] = React.useState<number | 'all' | null>(
53
+ null,
54
+ );
55
+ const timerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
56
+
57
+ React.useEffect(
58
+ () => () => {
59
+ if (timerRef.current) clearTimeout(timerRef.current);
60
+ },
61
+ [],
62
+ );
63
+
64
+ const flagCopied = React.useCallback((target: number | 'all') => {
65
+ setCopiedTarget(target);
66
+ if (timerRef.current) clearTimeout(timerRef.current);
67
+ timerRef.current = setTimeout(() => setCopiedTarget(null), COPY_FEEDBACK_MS);
68
+ }, []);
69
+
70
+ const allRevealed =
71
+ variables.length > 0 && revealed.size === variables.length;
72
+
73
+ const toggleIndex = React.useCallback((index: number) => {
74
+ setRevealed((prev) => {
75
+ const next = new Set(prev);
76
+ if (next.has(index)) next.delete(index);
77
+ else next.add(index);
78
+ return next;
79
+ });
80
+ }, []);
81
+
82
+ const toggleAll = React.useCallback(() => {
83
+ setRevealed((prev) =>
84
+ prev.size === variables.length
85
+ ? new Set()
86
+ : new Set(variables.map((_, i) => i)),
87
+ );
88
+ }, [variables]);
89
+
90
+ const copyValue = React.useCallback(
91
+ (index: number, value: string) => {
92
+ void navigator.clipboard
93
+ .writeText(value)
94
+ .then(() => flagCopied(index))
95
+ .catch(() => {
96
+ /* clipboard rejection — swallow, no feedback */
97
+ });
98
+ },
99
+ [flagCopied],
100
+ );
101
+
102
+ const copyAllAsEnv = React.useCallback(() => {
103
+ const text = variables.map((v) => `${v.key}=${v.value}`).join('\n');
104
+ void navigator.clipboard
105
+ .writeText(text)
106
+ .then(() => flagCopied('all'))
107
+ .catch(() => {
108
+ /* swallow */
109
+ });
110
+ }, [variables, flagCopied]);
111
+
112
+ return {
113
+ revealed,
114
+ allRevealed,
115
+ toggleIndex,
116
+ toggleAll,
117
+ copiedTarget,
118
+ copyValue,
119
+ copyAllAsEnv,
120
+ };
121
+ }
@@ -0,0 +1,12 @@
1
+ // Adapted from jalcoui (MIT) — github.com/jal-co/ui
2
+
3
+ export { EnvTable } from './EnvTable';
4
+ export { ENV_TONE_CLASSES, getEnvTone } from './types';
5
+ export { maskValue, useEnvMask } from './hooks/useEnvMask';
6
+ export type {
7
+ EnvVariable,
8
+ EnvTableProps,
9
+ EnvEnvironment,
10
+ EnvBadgeTone,
11
+ } from './types';
12
+ export type { UseEnvMaskOptions, UseEnvMaskReturn } from './hooks/useEnvMask';
@@ -0,0 +1,21 @@
1
+ // Adapted from jalcoui (MIT) — github.com/jal-co/ui
2
+
3
+ 'use client';
4
+
5
+ import { createLazyComponent } from '../../../../common/lazy-wrapper';
6
+ import type { EnvTableProps } from './types';
7
+
8
+ export const LazyEnvTable = createLazyComponent<EnvTableProps>(
9
+ () => import('./EnvTable').then((mod) => ({ default: mod.EnvTable })),
10
+ {
11
+ displayName: 'LazyEnvTable',
12
+ fallback: (
13
+ <div
14
+ data-slot="env-table-skeleton"
15
+ className="h-40 w-full animate-pulse rounded-xl bg-muted"
16
+ />
17
+ ),
18
+ },
19
+ );
20
+
21
+ export type { EnvTableProps } from './types';
@@ -0,0 +1,76 @@
1
+ // Adapted from jalcoui (MIT) — github.com/jal-co/ui
2
+
3
+ import type * as React from 'react';
4
+
5
+ /**
6
+ * Target environment for a variable. Renders as a colored badge next to the
7
+ * variable name. Custom string values fall back to the muted tone.
8
+ */
9
+ export type EnvEnvironment =
10
+ | 'production'
11
+ | 'preview'
12
+ | 'development'
13
+ | (string & {});
14
+
15
+ /**
16
+ * Tone mapping for environment badges. Resolves to semantic surface tokens —
17
+ * never raw color scales.
18
+ */
19
+ export type EnvBadgeTone = 'success' | 'warning' | 'info' | 'muted';
20
+
21
+ /**
22
+ * One environment variable row.
23
+ */
24
+ export interface EnvVariable {
25
+ /** Variable name (e.g. DATABASE_URL). */
26
+ key: string;
27
+ /** Variable value. Masked by default. */
28
+ value: string;
29
+ /** Target environment. When provided, renders as a badge. */
30
+ environment?: EnvEnvironment;
31
+ /** Optional description shown below the value. */
32
+ description?: string;
33
+ /** Mark the row as required — name renders in `text-destructive`. */
34
+ required?: boolean;
35
+ }
36
+
37
+ export interface EnvTableProps
38
+ extends Omit<React.ComponentProps<'div'>, 'children' | 'title'> {
39
+ /** Environment variables to display. */
40
+ variables: EnvVariable[];
41
+ /** Optional heading above the table. */
42
+ title?: string;
43
+ /** Start with all values revealed. Defaults to `false`. */
44
+ defaultRevealed?: boolean;
45
+ /** Empty-state text. Defaults to `"No environment variables."`. */
46
+ emptyLabel?: string;
47
+ }
48
+
49
+ /**
50
+ * Map environment string → semantic tone. Unknown strings fall through to
51
+ * `muted`.
52
+ */
53
+ export function getEnvTone(env: EnvEnvironment | undefined): EnvBadgeTone {
54
+ switch (env) {
55
+ case 'production':
56
+ return 'success';
57
+ case 'preview':
58
+ return 'info';
59
+ case 'development':
60
+ return 'warning';
61
+ default:
62
+ return 'muted';
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Tailwind classes per env badge tone. All resolve to semantic tokens; opacity-
68
+ * derived surfaces keep every preset safe even when `*-background` tokens are
69
+ * not overridden.
70
+ */
71
+ export const ENV_TONE_CLASSES: Record<EnvBadgeTone, string> = {
72
+ success: 'bg-success/15 text-success',
73
+ warning: 'bg-warning/15 text-warning',
74
+ info: 'bg-info/15 text-info',
75
+ muted: 'bg-muted text-muted-foreground',
76
+ };
@@ -0,0 +1,194 @@
1
+ // Adapted from jalcoui (MIT) — github.com/jal-co/ui
2
+ //
3
+ // Streaming log viewer with per-level filtering, search, ANSI rendering,
4
+ // and sticky-bottom auto-scroll. Virtualized via `react-virtuoso` (the
5
+ // same library `chat/messages/MessageList` and `data/DataGrid` build on
6
+ // — no new virt dep). Handles 10k+ entries without lag.
7
+ //
8
+ // Decomposition:
9
+ // - components/Toolbar header, level chips, search input
10
+ // - components/LogRow single row + ANSI render + search highlight
11
+ // - hooks/useLogFilter filter + search single-pass derivation
12
+ // - hooks/useAutoScroll sticky-bottom controller wrapping Virtuoso
13
+ // - utils/ansi SGR parser → semantic tone segments
14
+
15
+ 'use client';
16
+
17
+ import * as React from 'react';
18
+ import { Virtuoso } from 'react-virtuoso';
19
+ import { cn } from '@djangocfg/ui-core/lib';
20
+
21
+ import type { LogLevel, LogViewerProps } from './types';
22
+ import { useLogFilter } from './hooks/useLogFilter';
23
+ import { useAutoScroll } from './hooks/useAutoScroll';
24
+ import { Toolbar, ScrollToBottomPill } from './components/Toolbar';
25
+ import { LogRow } from './components/LogRow';
26
+
27
+ const DEFAULT_LEVELS: LogLevel[] = ['error', 'warn', 'info', 'debug', 'verbose'];
28
+
29
+ // Threshold above which Virtuoso wins outright. Below it, the plain
30
+ // `.map()` path is fine and keeps the DOM debuggable.
31
+ const VIRTUALIZE_THRESHOLD = 100;
32
+
33
+ export function LogViewer({
34
+ entries,
35
+ title = 'Logs',
36
+ maxHeight = 400,
37
+ lineNumbers = true,
38
+ timestamps = true,
39
+ autoScroll = true,
40
+ levels = DEFAULT_LEVELS,
41
+ ansi = true,
42
+ onClear,
43
+ noVirtualize = false,
44
+ className,
45
+ ...divProps
46
+ }: LogViewerProps) {
47
+ const [activeLevels, setActiveLevels] = React.useState<Set<LogLevel>>(
48
+ () => new Set(levels),
49
+ );
50
+ const [searchQuery, setSearchQuery] = React.useState('');
51
+ const [paused, setPaused] = React.useState(false);
52
+
53
+ // If the caller changes the available `levels` list (e.g. story
54
+ // toggles), reseed the active set. Compare by sorted join — a new
55
+ // array literal of the same levels shouldn't reset user toggles.
56
+ const levelsKey = React.useMemo(() => [...levels].sort().join(','), [levels]);
57
+ React.useEffect(() => {
58
+ setActiveLevels(new Set(levels));
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ }, [levelsKey]);
61
+
62
+ const toggleLevel = React.useCallback((level: LogLevel) => {
63
+ setActiveLevels((prev) => {
64
+ const next = new Set(prev);
65
+ if (next.has(level)) {
66
+ next.delete(level);
67
+ } else {
68
+ next.add(level);
69
+ }
70
+ return next;
71
+ });
72
+ }, []);
73
+
74
+ const { filtered, counts } = useLogFilter({
75
+ entries,
76
+ activeLevels,
77
+ searchQuery,
78
+ });
79
+
80
+ const { virtuosoRef, isAtBottom, onAtBottomChange, scrollToBottom } =
81
+ useAutoScroll(filtered.length, autoScroll && !paused);
82
+
83
+ // Line-number gutter width — based on total count, not filtered, so the
84
+ // gutter doesn't reflow when the user toggles a filter.
85
+ const lineNumberWidth = Math.max(String(entries.length).length, 3);
86
+
87
+ const shouldVirtualize = !noVirtualize && filtered.length > VIRTUALIZE_THRESHOLD;
88
+
89
+ const renderRow = React.useCallback(
90
+ (index: number, entry: (typeof filtered)[number]) => (
91
+ <LogRow
92
+ entry={entry}
93
+ index={index}
94
+ lineNumberWidth={lineNumberWidth}
95
+ showLineNumbers={lineNumbers}
96
+ showTimestamps={timestamps}
97
+ ansi={ansi}
98
+ searchQuery={searchQuery}
99
+ />
100
+ ),
101
+ [lineNumberWidth, lineNumbers, timestamps, ansi, searchQuery],
102
+ );
103
+
104
+ const isEmpty = filtered.length === 0;
105
+ const hasActiveFilter =
106
+ searchQuery.length > 0 || activeLevels.size < levels.length;
107
+
108
+ const resetFilters = React.useCallback(() => {
109
+ setSearchQuery('');
110
+ setActiveLevels(new Set(levels));
111
+ }, [levels]);
112
+
113
+ return (
114
+ <div
115
+ data-slot="log-viewer"
116
+ className={cn(
117
+ 'flex flex-col overflow-hidden rounded-xl border border-border/60 bg-card shadow-sm',
118
+ className,
119
+ )}
120
+ {...divProps}
121
+ >
122
+ <Toolbar
123
+ title={title}
124
+ totalCount={entries.length}
125
+ filteredCount={filtered.length}
126
+ levels={levels}
127
+ activeLevels={activeLevels}
128
+ toggleLevel={toggleLevel}
129
+ levelCounts={counts}
130
+ searchQuery={searchQuery}
131
+ setSearchQuery={setSearchQuery}
132
+ autoScrollEnabled={autoScroll}
133
+ paused={paused}
134
+ setPaused={setPaused}
135
+ onClear={onClear}
136
+ />
137
+
138
+ <div
139
+ className="font-mono text-xs leading-relaxed [scrollbar-width:thin]"
140
+ style={{ height: maxHeight }}
141
+ role="log"
142
+ aria-live="polite"
143
+ aria-label={title}
144
+ >
145
+ {isEmpty ? (
146
+ <div className="flex h-full flex-col items-center justify-center gap-1 text-sm text-muted-foreground">
147
+ <span>
148
+ {entries.length === 0
149
+ ? 'No log entries.'
150
+ : 'No matching log entries.'}
151
+ </span>
152
+ {hasActiveFilter && entries.length > 0 && (
153
+ <button
154
+ type="button"
155
+ onClick={resetFilters}
156
+ className="text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground"
157
+ >
158
+ Reset filters
159
+ </button>
160
+ )}
161
+ </div>
162
+ ) : shouldVirtualize ? (
163
+ <Virtuoso
164
+ ref={virtuosoRef}
165
+ data={filtered}
166
+ atBottomStateChange={onAtBottomChange}
167
+ atBottomThreshold={40}
168
+ followOutput={autoScroll && !paused ? 'auto' : false}
169
+ itemContent={renderRow}
170
+ increaseViewportBy={{ top: 200, bottom: 400 }}
171
+ style={{ height: '100%' }}
172
+ />
173
+ ) : (
174
+ <div className="h-full overflow-auto [scrollbar-width:thin]">
175
+ {filtered.map((entry, i) => (
176
+ <LogRow
177
+ key={i}
178
+ entry={entry}
179
+ index={i}
180
+ lineNumberWidth={lineNumberWidth}
181
+ showLineNumbers={lineNumbers}
182
+ showTimestamps={timestamps}
183
+ ansi={ansi}
184
+ searchQuery={searchQuery}
185
+ />
186
+ ))}
187
+ </div>
188
+ )}
189
+ </div>
190
+
191
+ <ScrollToBottomPill visible={!isAtBottom && !isEmpty} onClick={scrollToBottom} />
192
+ </div>
193
+ );
194
+ }
@@ -0,0 +1,30 @@
1
+ # LogViewer
2
+
3
+ Virtualized log stream viewer. Level filter, free-text search, ANSI escape parsing, copy. Tested on 10k+ entries. Level colors route to semantic tokens (`destructive` / `warning` / `info` / `debug` / `muted`).
4
+
5
+ ```tsx
6
+ import { LogViewer } from '@djangocfg/ui-tools/log-viewer';
7
+
8
+ <LogViewer
9
+ entries={[
10
+ { level: 'info', message: 'Server listening on :3000', timestamp: '2025-01-12T10:00:00Z' },
11
+ { level: 'error', message: '\x1b[31mDatabase connection failed\x1b[0m' },
12
+ ]}
13
+ />
14
+ ```
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Description |
19
+ |---|---|---|---|
20
+ | `entries` | `LogEntry[]` | — | Each entry: `{ level, message, timestamp? }`. `message` may contain ANSI escapes. |
21
+ | `defaultLevel` | `LogLevel \| 'all'` | `'all'` | Initial level filter. |
22
+ | `defaultSearch` | `string` | `''` | Initial search term. |
23
+ | `showTimestamps` | `boolean` | `true` | Render the timestamp column. |
24
+ | `maxHeight` | `number \| string` | `400` | Viewport height. |
25
+
26
+ Storybook: `apps/storybook/stories/ui-tools/dev/LogViewer.stories.tsx`
27
+
28
+ ---
29
+
30
+ Adapted from jalcoui (MIT).
@@ -0,0 +1,151 @@
1
+ // Adapted from jalcoui (MIT) — github.com/jal-co/ui
2
+
3
+ 'use client';
4
+
5
+ import * as React from 'react';
6
+ import { cn } from '@djangocfg/ui-core/lib';
7
+ import type { LogEntry } from '../types';
8
+ import { LEVEL_LABELS, getLevelToneClasses } from '../types';
9
+ import { parseAnsi, classesForSegment, stripAnsi } from '../utils/ansi';
10
+
11
+ function formatTimestamp(ts?: string): string {
12
+ const d = ts ? new Date(ts) : new Date();
13
+ const ms = d.getMilliseconds().toString().padStart(3, '0');
14
+ return `${d.toLocaleTimeString('en-US', {
15
+ hour12: false,
16
+ hour: '2-digit',
17
+ minute: '2-digit',
18
+ second: '2-digit',
19
+ })}.${ms}`;
20
+ }
21
+
22
+ /** Escape a string for use inside a `RegExp` constructor. */
23
+ function escapeRegex(value: string): string {
24
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
25
+ }
26
+
27
+ interface MessageBodyProps {
28
+ message: string;
29
+ ansi: boolean;
30
+ searchQuery: string;
31
+ }
32
+
33
+ /**
34
+ * Render the message body. The render is staged in two passes:
35
+ *
36
+ * 1. ANSI parse — produces styled segments. When `ansi` is `false` the
37
+ * raw message becomes one segment.
38
+ * 2. Search highlight — splits each segment's text on the search needle
39
+ * and wraps matches in `<mark>` while preserving the segment's tone
40
+ * classes.
41
+ *
42
+ * The two passes run independently so a needle that crosses an ANSI
43
+ * boundary still highlights inside each segment.
44
+ */
45
+ const MessageBody = React.memo(function MessageBody({
46
+ message,
47
+ ansi,
48
+ searchQuery,
49
+ }: MessageBodyProps) {
50
+ const segments = React.useMemo(
51
+ () => (ansi ? parseAnsi(message) : [{ text: ansi ? message : stripAnsi(message) }]),
52
+ [message, ansi],
53
+ );
54
+
55
+ const needle = searchQuery.trim();
56
+ if (!needle) {
57
+ return (
58
+ <>
59
+ {segments.map((seg, i) => (
60
+ <span key={i} className={classesForSegment(seg)}>
61
+ {seg.text}
62
+ </span>
63
+ ))}
64
+ </>
65
+ );
66
+ }
67
+
68
+ const re = new RegExp(`(${escapeRegex(needle)})`, 'gi');
69
+ return (
70
+ <>
71
+ {segments.map((seg, i) => {
72
+ const classes = classesForSegment(seg);
73
+ const parts = seg.text.split(re);
74
+ return (
75
+ <span key={i} className={classes}>
76
+ {parts.map((part, j) =>
77
+ part.toLowerCase() === needle.toLowerCase() ? (
78
+ <mark
79
+ key={j}
80
+ className="rounded-sm bg-warning/30 px-0.5 text-inherit"
81
+ >
82
+ {part}
83
+ </mark>
84
+ ) : (
85
+ <React.Fragment key={j}>{part}</React.Fragment>
86
+ ),
87
+ )}
88
+ </span>
89
+ );
90
+ })}
91
+ </>
92
+ );
93
+ });
94
+
95
+ export interface LogRowProps {
96
+ entry: LogEntry;
97
+ index: number;
98
+ /** Width of the line-number gutter in `ch` units (caller-computed
99
+ * once per render so every row aligns). */
100
+ lineNumberWidth: number;
101
+ showLineNumbers: boolean;
102
+ showTimestamps: boolean;
103
+ ansi: boolean;
104
+ searchQuery: string;
105
+ }
106
+
107
+ export const LogRow = React.memo(function LogRow({
108
+ entry,
109
+ index,
110
+ lineNumberWidth,
111
+ showLineNumbers,
112
+ showTimestamps,
113
+ ansi,
114
+ searchQuery,
115
+ }: LogRowProps) {
116
+ const tone = getLevelToneClasses(entry.level);
117
+
118
+ return (
119
+ <div className="flex gap-3 border-b border-border/20 px-3 py-1 transition-colors hover:bg-muted/30">
120
+ {showLineNumbers && (
121
+ <span
122
+ className="shrink-0 select-none text-right text-muted-foreground/50 tabular-nums"
123
+ style={{ width: `${lineNumberWidth}ch` }}
124
+ aria-hidden="true"
125
+ >
126
+ {index + 1}
127
+ </span>
128
+ )}
129
+ {showTimestamps && (
130
+ <span className="shrink-0 text-muted-foreground/70 tabular-nums">
131
+ {formatTimestamp(entry.timestamp)}
132
+ </span>
133
+ )}
134
+ <span
135
+ className={cn(
136
+ 'w-[3ch] shrink-0 text-right font-semibold tabular-nums',
137
+ tone.text,
138
+ )}
139
+ >
140
+ {LEVEL_LABELS[entry.level]}
141
+ </span>
142
+ <span className="min-w-0 flex-1 whitespace-pre-wrap break-words text-foreground/90">
143
+ <MessageBody
144
+ message={entry.message}
145
+ ansi={ansi}
146
+ searchQuery={searchQuery}
147
+ />
148
+ </span>
149
+ </div>
150
+ );
151
+ });