@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
@@ -6,6 +6,7 @@ import { AlertCircle, Loader2, Mic } from 'lucide-react';
6
6
 
7
7
  import { useCountdownFromSeconds, useNotificationSounds } from '@djangocfg/ui-core/hooks';
8
8
  import { cn } from '@djangocfg/ui-core/lib';
9
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@djangocfg/ui-core/components';
9
10
  import { useActiveComposer } from '@djangocfg/ui-tools/composer-registry';
10
11
  import { useSpeechRecognition } from '../hooks/useSpeechRecognition';
11
12
  import { useVoiceSupport } from '../hooks/useVoiceSupport';
@@ -364,44 +365,48 @@ export function VoiceComposerSlot({
364
365
  Failed
365
366
  </span>
366
367
  ) : null}
367
- <button
368
- type="button"
369
- onClick={toggle}
370
- aria-pressed={slotState === 'listening'}
371
- aria-label={ariaLabel}
372
- title={tooltip}
373
- data-state={slotState}
374
- className={cn(
375
- 'relative inline-flex items-center justify-center rounded-full transition-all duration-200',
376
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
377
- SIZE_CLS[size],
378
- slotState === 'listening' &&
379
- 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
380
- slotState === 'processing' &&
381
- 'bg-primary/10 text-primary hover:bg-primary/15',
382
- slotState === 'error' &&
383
- 'bg-destructive/10 text-destructive hover:bg-destructive/15',
384
- slotState === 'idle' &&
385
- 'text-muted-foreground hover:bg-muted hover:text-foreground',
386
- className,
387
- )}
388
- >
389
- {/* Recording feedback — pulsing circle overlay driven by the
390
- live mic level. Hidden in every non-listening state. */}
391
- <RecordingPulse active={slotState === 'listening'} level={rec.level} />
392
- {slotState === 'processing' ? (
393
- <Loader2 className="animate-spin" />
394
- ) : slotState === 'error' ? (
395
- <AlertCircle />
396
- ) : (
397
- <Mic
368
+ <Tooltip>
369
+ <TooltipTrigger asChild>
370
+ <button
371
+ type="button"
372
+ onClick={toggle}
373
+ aria-pressed={slotState === 'listening'}
374
+ aria-label={ariaLabel}
375
+ data-state={slotState}
398
376
  className={cn(
399
- 'transition-transform duration-200',
400
- slotState === 'listening' && 'scale-110',
377
+ 'relative inline-flex items-center justify-center rounded-full transition-all duration-200',
378
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
379
+ SIZE_CLS[size],
380
+ slotState === 'listening' &&
381
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
382
+ slotState === 'processing' &&
383
+ 'bg-primary/10 text-primary hover:bg-primary/15',
384
+ slotState === 'error' &&
385
+ 'bg-destructive/10 text-destructive hover:bg-destructive/15',
386
+ slotState === 'idle' &&
387
+ 'text-muted-foreground hover:bg-muted hover:text-foreground',
388
+ className,
401
389
  )}
402
- />
403
- )}
404
- </button>
390
+ >
391
+ {/* Recording feedback — pulsing circle overlay driven by the
392
+ live mic level. Hidden in every non-listening state. */}
393
+ <RecordingPulse active={slotState === 'listening'} level={rec.level} />
394
+ {slotState === 'processing' ? (
395
+ <Loader2 className="animate-spin" />
396
+ ) : slotState === 'error' ? (
397
+ <AlertCircle />
398
+ ) : (
399
+ <Mic
400
+ className={cn(
401
+ 'transition-transform duration-200',
402
+ slotState === 'listening' && 'scale-110',
403
+ )}
404
+ />
405
+ )}
406
+ </button>
407
+ </TooltipTrigger>
408
+ <TooltipContent side="top">{tooltip}</TooltipContent>
409
+ </Tooltip>
405
410
  </span>
406
411
  );
407
412
  }
@@ -99,19 +99,11 @@ export function PlayerShell({
99
99
  container.setAttribute('tabindex', '0');
100
100
  }, [container]);
101
101
 
102
- // `autoFocus` opts the player into pulling keyboard focus on mount
103
- // (and whenever the container ref is established). Once focused,
104
- // the hotkey scope (Space=play/pause, ←→=seek, ↑↓=volume, M=mute)
105
- // is immediately live.
106
- //
107
- // Deferred via a 0-timeout so the tree row's native focus event
108
- // (fired by the click that triggered the mount) lands first; we
109
- // then steal focus here. rAF was racy — the row's focus event can
110
- // fire AFTER the next animation frame.
102
+ // Declarative autoFocus: focus the container once the DOM node is ready.
103
+ // Parents that want a *fresh* focus per source remount us via `key={src}`.
111
104
  useEffect(() => {
112
105
  if (!autoFocus || !container) return;
113
- const id = setTimeout(() => container.focus(), 0);
114
- return () => clearTimeout(id);
106
+ container.focus({ preventScroll: true });
115
107
  }, [autoFocus, container]);
116
108
 
117
109
  return (
@@ -51,17 +51,10 @@ export type PlayerProps = {
51
51
 
52
52
  ariaLabel?: string;
53
53
  enableKeyboardShortcuts?: boolean;
54
- /**
55
- * Move keyboard focus into the player container on mount. Activates
56
- * the hotkey scope (Space=play/pause, ←→=seek, ↑↓=volume, M=mute)
57
- * without the user having to click the player first.
58
- *
59
- * Useful when the player mounts as the result of an explicit user
60
- * action — e.g. a file picker selecting an audio file — so keyboard
61
- * control is immediately live.
62
- *
63
- * @default false
64
- */
54
+
55
+ /** Focus the player container on mount so its keyboard scope is active
56
+ * immediately. Pair with `key={src}` upstream when the parent wants a
57
+ * fresh focus on every source change (file-browser inspector pattern). */
65
58
  autoFocus?: boolean;
66
59
 
67
60
  // When the user clicks on the waveform while paused, also start playback.
@@ -4,10 +4,18 @@
4
4
  * ImageToolbar - Floating toolbar for image controls
5
5
  */
6
6
 
7
- import { useMemo } from 'react';
7
+ import { useMemo, type ReactNode } from 'react';
8
8
  import { ZoomIn, ZoomOut, RotateCw, FlipHorizontal, FlipVertical, Maximize2, Expand } from 'lucide-react';
9
9
  import { useControls } from 'react-zoom-pan-pinch';
10
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@djangocfg/ui-core/components';
10
+ import {
11
+ DropdownMenu,
12
+ DropdownMenuContent,
13
+ DropdownMenuItem,
14
+ DropdownMenuTrigger,
15
+ Tooltip,
16
+ TooltipContent,
17
+ TooltipTrigger,
18
+ } from '@djangocfg/ui-core/components';
11
19
  import { useAppT } from '@djangocfg/i18n';
12
20
  import { cn } from '@djangocfg/ui-core/lib';
13
21
  import { ZOOM_PRESETS } from '../utils';
@@ -29,6 +37,36 @@ const TB_BUTTON =
29
37
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70 ' +
30
38
  'disabled:opacity-40 disabled:pointer-events-none';
31
39
 
40
+ // Small helper so each icon button shares the same Tooltip wrapper +
41
+ // accessibility shape (visible tooltip on hover/focus, aria-label for AT).
42
+ function TbIconButton({
43
+ label,
44
+ onClick,
45
+ className,
46
+ children,
47
+ }: {
48
+ label: string;
49
+ onClick: () => void;
50
+ className?: string;
51
+ children: ReactNode;
52
+ }) {
53
+ return (
54
+ <Tooltip>
55
+ <TooltipTrigger asChild>
56
+ <button
57
+ type="button"
58
+ aria-label={label}
59
+ onClick={onClick}
60
+ className={cn(TB_BUTTON, 'h-7 w-7', className)}
61
+ >
62
+ {children}
63
+ </button>
64
+ </TooltipTrigger>
65
+ <TooltipContent side="top">{label}</TooltipContent>
66
+ </Tooltip>
67
+ );
68
+ }
69
+
32
70
  export function ImageToolbar({
33
71
  scale,
34
72
  transform,
@@ -59,14 +97,9 @@ export function ImageToolbar({
59
97
  return (
60
98
  <div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-0.5 bg-black/60 backdrop-blur-sm border border-white/10 rounded-lg p-1 shadow-lg">
61
99
  {/* Zoom controls */}
62
- <button
63
- type="button"
64
- className={cn(TB_BUTTON, 'h-7 w-7')}
65
- onClick={() => zoomOut()}
66
- title={labels.zoomOut}
67
- >
100
+ <TbIconButton label={labels.zoomOut} onClick={() => zoomOut()}>
68
101
  <ZoomOut className="h-3.5 w-3.5" />
69
- </button>
102
+ </TbIconButton>
70
103
 
71
104
  <DropdownMenu>
72
105
  <DropdownMenuTrigger asChild>
@@ -90,56 +123,39 @@ export function ImageToolbar({
90
123
  </DropdownMenuContent>
91
124
  </DropdownMenu>
92
125
 
93
- <button
94
- type="button"
95
- className={cn(TB_BUTTON, 'h-7 w-7')}
96
- onClick={() => zoomIn()}
97
- title={labels.zoomIn}
98
- >
126
+ <TbIconButton label={labels.zoomIn} onClick={() => zoomIn()}>
99
127
  <ZoomIn className="h-3.5 w-3.5" />
100
- </button>
128
+ </TbIconButton>
101
129
 
102
130
  <div className="w-px h-4 bg-white/20 mx-1" />
103
131
 
104
132
  {/* Fit to view */}
105
- <button
106
- type="button"
107
- className={cn(TB_BUTTON, 'h-7 w-7')}
108
- onClick={() => resetTransform()}
109
- title={labels.fitToView}
110
- >
133
+ <TbIconButton label={labels.fitToView} onClick={() => resetTransform()}>
111
134
  <Maximize2 className="h-3.5 w-3.5" />
112
- </button>
135
+ </TbIconButton>
113
136
 
114
137
  <div className="w-px h-4 bg-white/20 mx-1" />
115
138
 
116
139
  {/* Transform controls */}
117
- <button
118
- type="button"
119
- className={cn(TB_BUTTON, 'h-7 w-7', transform.flipH && 'bg-white/20 text-white')}
140
+ <TbIconButton
141
+ label={labels.flipHorizontal}
120
142
  onClick={onFlipH}
121
- title={labels.flipHorizontal}
143
+ className={transform.flipH ? 'bg-white/20 text-white' : undefined}
122
144
  >
123
145
  <FlipHorizontal className="h-3.5 w-3.5" />
124
- </button>
146
+ </TbIconButton>
125
147
 
126
- <button
127
- type="button"
128
- className={cn(TB_BUTTON, 'h-7 w-7', transform.flipV && 'bg-white/20 text-white')}
148
+ <TbIconButton
149
+ label={labels.flipVertical}
129
150
  onClick={onFlipV}
130
- title={labels.flipVertical}
151
+ className={transform.flipV ? 'bg-white/20 text-white' : undefined}
131
152
  >
132
153
  <FlipVertical className="h-3.5 w-3.5" />
133
- </button>
154
+ </TbIconButton>
134
155
 
135
- <button
136
- type="button"
137
- className={cn(TB_BUTTON, 'h-7 w-7')}
138
- onClick={onRotate}
139
- title={labels.rotate}
140
- >
156
+ <TbIconButton label={labels.rotate} onClick={onRotate}>
141
157
  <RotateCw className="h-3.5 w-3.5" />
142
- </button>
158
+ </TbIconButton>
143
159
 
144
160
  {transform.rotation !== 0 && (
145
161
  <span className="text-[10px] text-white/60 font-mono pl-1">
@@ -150,14 +166,9 @@ export function ImageToolbar({
150
166
  {onExpand && (
151
167
  <>
152
168
  <div className="w-px h-4 bg-white/20 mx-1" />
153
- <button
154
- type="button"
155
- className={cn(TB_BUTTON, 'h-7 w-7')}
156
- onClick={onExpand}
157
- title={labels.fullscreen}
158
- >
169
+ <TbIconButton label={labels.fullscreen} onClick={onExpand}>
159
170
  <Expand className="h-3.5 w-3.5" />
160
- </button>
171
+ </TbIconButton>
161
172
  </>
162
173
  )}
163
174
  </div>
@@ -20,7 +20,7 @@ import {
20
20
  TransformComponent,
21
21
  type ReactZoomPanPinchRef,
22
22
  } from 'react-zoom-pan-pinch';
23
- import { cn, Dialog, DialogContent, DialogTitle, Alert, AlertDescription } from '@djangocfg/ui-core';
23
+ import { cn, Dialog, DialogContent, DialogTitle, Alert, AlertDescription, Tooltip, TooltipContent, TooltipTrigger } from '@djangocfg/ui-core';
24
24
  import { useAppT } from '@djangocfg/i18n';
25
25
  import { useHotkey } from '@djangocfg/ui-core/hooks';
26
26
 
@@ -38,6 +38,7 @@ export function ImageViewer({
38
38
  images,
39
39
  initialIndex = 0,
40
40
  inDialog = false,
41
+ autoFocus = false,
41
42
  }: ImageViewerProps) {
42
43
  const t = useAppT();
43
44
 
@@ -134,6 +135,13 @@ export function ImageViewer({
134
135
  return true;
135
136
  }, []);
136
137
 
138
+ // Declarative autoFocus: focus the container once on mount. Pair with
139
+ // `key={src}` upstream for per-source focus reset.
140
+ useEffect(() => {
141
+ if (!autoFocus) return;
142
+ containerRef.current?.focus({ preventScroll: true });
143
+ }, [autoFocus]);
144
+
137
145
  // Keyboard: zoom / rotate / pan (only when container focused)
138
146
  useEffect(() => {
139
147
  const handleKeyDown = (e: KeyboardEvent) => {
@@ -305,24 +313,32 @@ export function ImageViewer({
305
313
  {/* Gallery navigation */}
306
314
  {hasMultiple && (
307
315
  <>
308
- <button
309
- type="button"
310
- onClick={prev}
311
- aria-label={labels.prev}
312
- title={labels.prev}
313
- className="absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
314
- >
315
- <ChevronLeft className="h-5 w-5" />
316
- </button>
317
- <button
318
- type="button"
319
- onClick={next}
320
- aria-label={labels.next}
321
- title={labels.next}
322
- className="absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
323
- >
324
- <ChevronRight className="h-5 w-5" />
325
- </button>
316
+ <Tooltip>
317
+ <TooltipTrigger asChild>
318
+ <button
319
+ type="button"
320
+ onClick={prev}
321
+ aria-label={labels.prev}
322
+ className="absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
323
+ >
324
+ <ChevronLeft className="h-5 w-5" />
325
+ </button>
326
+ </TooltipTrigger>
327
+ <TooltipContent side="right">{labels.prev}</TooltipContent>
328
+ </Tooltip>
329
+ <Tooltip>
330
+ <TooltipTrigger asChild>
331
+ <button
332
+ type="button"
333
+ onClick={next}
334
+ aria-label={labels.next}
335
+ className="absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
336
+ >
337
+ <ChevronRight className="h-5 w-5" />
338
+ </button>
339
+ </TooltipTrigger>
340
+ <TooltipContent side="left">{labels.next}</TooltipContent>
341
+ </Tooltip>
326
342
  <div className="absolute top-3 left-1/2 -translate-x-1/2 z-10 bg-black/60 backdrop-blur-sm border border-white/10 text-white/80 text-xs font-mono px-2 py-0.5 rounded-full pointer-events-none">
327
343
  {currentIndex + 1} / {images.length}
328
344
  </div>
@@ -10,7 +10,7 @@
10
10
  * import { ImageViewer } from '@djangocfg/ui-tools/image-viewer'
11
11
  */
12
12
 
13
- import { createLazyComponent, LoadingFallback } from '../../../components';
13
+ import { createLazyComponent, LoadingFallback } from '../../../common';
14
14
  import type { ImageViewerProps } from './types';
15
15
 
16
16
  // ============================================================================
@@ -43,6 +43,10 @@ export interface ImageViewerProps {
43
43
  initialIndex?: number;
44
44
  /** Hide expand button when already in dialog */
45
45
  inDialog?: boolean;
46
+ /** Focus the viewer container on mount so its keyboard scope is active
47
+ * immediately (zoom/rotate/gallery hotkeys). Pair with `key={src}`
48
+ * upstream when the parent wants a fresh focus per source change. */
49
+ autoFocus?: boolean;
46
50
  }
47
51
 
48
52
  export interface ImageToolbarProps {
@@ -10,7 +10,7 @@
10
10
  * import { LottiePlayer } from '@djangocfg/ui-tools/lottie'
11
11
  */
12
12
 
13
- import { createLazyComponent } from '../../../components';
13
+ import { createLazyComponent } from '../../../common';
14
14
  import type { LottiePlayerProps } from './types';
15
15
 
16
16
  // ============================================================================
@@ -12,7 +12,7 @@
12
12
  * iframe sources where the embed renders its own UI).
13
13
  */
14
14
 
15
- import { useMemo, type CSSProperties } from 'react';
15
+ import { useCallback, useEffect, useMemo, useRef, type CSSProperties } from 'react';
16
16
  import { MediaController } from 'media-chrome/react';
17
17
  import { cn } from '@djangocfg/ui-core/lib';
18
18
  import './styles/video-player.css';
@@ -42,6 +42,7 @@ export function VideoPlayer({
42
42
  aspectRatio = 16 / 9,
43
43
  className,
44
44
  children,
45
+ autoFocus = false,
45
46
  ...settings
46
47
  }: VideoPlayerProps) {
47
48
  const normalized = useMemo(
@@ -50,22 +51,67 @@ export function VideoPlayer({
50
51
  );
51
52
 
52
53
  const isIframe = normalized.type === 'iframe';
54
+ const isYouTube = normalized.type === 'youtube';
53
55
  // For iframe embeds media-chrome cannot drive the inner player — hide the
54
56
  // control bar to avoid a non-functional UI.
55
57
  const showControls = controls && !isIframe;
56
58
 
59
+ // MediaController is a custom element; without `tabindex` it cannot take
60
+ // focus, so its built-in keyboard shortcuts (space/arrows/f) never fire.
61
+ // We type the ref through the element interface (HTMLElement methods are
62
+ // all we use) — media-chrome's full MediaController type pulls private
63
+ // fields we don't need to see.
64
+ const controllerRef = useRef<HTMLElement | null>(null);
65
+
66
+ // Click-to-play overlay for YouTube — the underlying YT iframe is made
67
+ // `pointer-events: none` via CSS to suppress YouTube's own hover-state /
68
+ // UI (title bar, pause-screen, branding tray; ToS forbids hiding them,
69
+ // but we can make them inert). This overlay restores click-to-toggle
70
+ // by dispatching media-chrome request events the controller listens for.
71
+ const onYouTubeShieldClick = useCallback(() => {
72
+ const el = controllerRef.current;
73
+ if (!el) return;
74
+ const paused = el.hasAttribute('mediapaused');
75
+ const eventName = paused ? 'mediaplayrequest' : 'mediapauserequest';
76
+ el.dispatchEvent(new CustomEvent(eventName, { bubbles: true, composed: true }));
77
+ }, []);
78
+
79
+ useEffect(() => {
80
+ if (!autoFocus) return;
81
+ const el = controllerRef.current;
82
+ if (!el) return;
83
+ if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0');
84
+ el.focus({ preventScroll: true });
85
+ }, [autoFocus]);
86
+
57
87
  return (
58
88
  <MediaController
89
+ ref={(el) => {
90
+ controllerRef.current = el as unknown as HTMLElement | null;
91
+ }}
59
92
  // Fade controls + scrim after 2.5s of inactivity while playing;
60
93
  // they reappear on mousemove / pause / focus (media-chrome built-in).
61
94
  autohide="2.5"
62
95
  className={cn(
63
96
  'video-player relative block w-full overflow-hidden rounded-lg bg-black',
97
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50',
64
98
  className,
65
99
  )}
66
100
  style={aspectRatioStyle(aspectRatio)}
67
101
  >
68
102
  <CanvasDispatcher source={normalized} {...settings} />
103
+ {isYouTube && (
104
+ // Transparent click-shield over the YT iframe. The iframe itself
105
+ // is pointer-events:none (see video-player.css); this div absorbs
106
+ // pointer events and forwards a play/pause request to media-chrome,
107
+ // so users still get click-to-toggle without ever interacting with
108
+ // YouTube's own UI.
109
+ <div
110
+ className="vp-yt-click-shield"
111
+ onClick={onYouTubeShieldClick}
112
+ aria-hidden="true"
113
+ />
114
+ )}
69
115
  {children ??
70
116
  (showControls && (
71
117
  <ControlsBar>
@@ -2,12 +2,20 @@
2
2
 
3
3
  import { MediaFullscreenButton } from 'media-chrome/react';
4
4
  import { cn } from '@djangocfg/ui-core/lib';
5
- import type { ComponentProps } from 'react';
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipTrigger,
9
+ } from '@djangocfg/ui-core/components';
10
+ import type { ComponentProps, ReactNode } from 'react';
6
11
 
7
- export type FullscreenProps = ComponentProps<typeof MediaFullscreenButton>;
12
+ export type FullscreenProps = ComponentProps<typeof MediaFullscreenButton> & {
13
+ /** Tooltip copy. Defaults to `"Fullscreen"`. Pass `false` to opt out. */
14
+ readonly label?: ReactNode | false;
15
+ };
8
16
 
9
- export function Fullscreen({ className, ...props }: FullscreenProps) {
10
- return (
17
+ export function Fullscreen({ className, label, ...props }: FullscreenProps) {
18
+ const button = (
11
19
  <MediaFullscreenButton
12
20
  {...props}
13
21
  className={cn(
@@ -16,4 +24,13 @@ export function Fullscreen({ className, ...props }: FullscreenProps) {
16
24
  )}
17
25
  />
18
26
  );
27
+
28
+ if (label === false) return button;
29
+
30
+ return (
31
+ <Tooltip>
32
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
33
+ <TooltipContent side="top">{label ?? 'Fullscreen'}</TooltipContent>
34
+ </Tooltip>
35
+ );
19
36
  }
@@ -2,12 +2,20 @@
2
2
 
3
3
  import { MediaPipButton } from 'media-chrome/react';
4
4
  import { cn } from '@djangocfg/ui-core/lib';
5
- import type { ComponentProps } from 'react';
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipTrigger,
9
+ } from '@djangocfg/ui-core/components';
10
+ import type { ComponentProps, ReactNode } from 'react';
6
11
 
7
- export type PipProps = ComponentProps<typeof MediaPipButton>;
12
+ export type PipProps = ComponentProps<typeof MediaPipButton> & {
13
+ /** Tooltip copy. Defaults to `"Picture in picture"`. Pass `false` to opt out. */
14
+ readonly label?: ReactNode | false;
15
+ };
8
16
 
9
- export function Pip({ className, ...props }: PipProps) {
10
- return (
17
+ export function Pip({ className, label, ...props }: PipProps) {
18
+ const button = (
11
19
  <MediaPipButton
12
20
  {...props}
13
21
  className={cn(
@@ -16,4 +24,13 @@ export function Pip({ className, ...props }: PipProps) {
16
24
  )}
17
25
  />
18
26
  );
27
+
28
+ if (label === false) return button;
29
+
30
+ return (
31
+ <Tooltip>
32
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
33
+ <TooltipContent side="top">{label ?? 'Picture in picture'}</TooltipContent>
34
+ </Tooltip>
35
+ );
19
36
  }
@@ -2,12 +2,20 @@
2
2
 
3
3
  import { MediaPlayButton } from 'media-chrome/react';
4
4
  import { cn } from '@djangocfg/ui-core/lib';
5
- import type { ComponentProps } from 'react';
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipTrigger,
9
+ } from '@djangocfg/ui-core/components';
10
+ import type { ComponentProps, ReactNode } from 'react';
6
11
 
7
- export type PlayButtonProps = ComponentProps<typeof MediaPlayButton>;
12
+ export type PlayButtonProps = ComponentProps<typeof MediaPlayButton> & {
13
+ /** Tooltip copy. Defaults to `"Play / Pause"`. Pass `false` to opt out. */
14
+ readonly label?: ReactNode | false;
15
+ };
8
16
 
9
- export function PlayButton({ className, ...props }: PlayButtonProps) {
10
- return (
17
+ export function PlayButton({ className, label, ...props }: PlayButtonProps) {
18
+ const button = (
11
19
  <MediaPlayButton
12
20
  {...props}
13
21
  className={cn(
@@ -16,4 +24,13 @@ export function PlayButton({ className, ...props }: PlayButtonProps) {
16
24
  )}
17
25
  />
18
26
  );
27
+
28
+ if (label === false) return button;
29
+
30
+ return (
31
+ <Tooltip>
32
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
33
+ <TooltipContent side="top">{label ?? 'Play / Pause'}</TooltipContent>
34
+ </Tooltip>
35
+ );
19
36
  }
@@ -2,7 +2,12 @@
2
2
 
3
3
  import { MediaPlaybackRateButton } from 'media-chrome/react';
4
4
  import { cn } from '@djangocfg/ui-core/lib';
5
- import type { ComponentProps } from 'react';
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipTrigger,
9
+ } from '@djangocfg/ui-core/components';
10
+ import type { ComponentProps, ReactNode } from 'react';
6
11
 
7
12
  export type PlaybackRateProps = Omit<
8
13
  ComponentProps<typeof MediaPlaybackRateButton>,
@@ -10,13 +15,15 @@ export type PlaybackRateProps = Omit<
10
15
  > & {
11
16
  /** Space-separated list, e.g. `'0.5 1 1.5 2'`, or an array of numbers. */
12
17
  readonly rates?: string | readonly number[];
18
+ /** Tooltip copy. Defaults to `"Playback speed"`. Pass `false` to opt out. */
19
+ readonly label?: ReactNode | false;
13
20
  };
14
21
 
15
22
  const DEFAULT_RATES: readonly number[] = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
16
23
 
17
- export function PlaybackRate({ className, rates, ...props }: PlaybackRateProps) {
24
+ export function PlaybackRate({ className, rates, label, ...props }: PlaybackRateProps) {
18
25
  const value = rates ?? DEFAULT_RATES;
19
- return (
26
+ const button = (
20
27
  <MediaPlaybackRateButton
21
28
  {...props}
22
29
  // media-chrome's setter accepts `string | ArrayLike<number>` — the
@@ -28,4 +35,13 @@ export function PlaybackRate({ className, rates, ...props }: PlaybackRateProps)
28
35
  )}
29
36
  />
30
37
  );
38
+
39
+ if (label === false) return button;
40
+
41
+ return (
42
+ <Tooltip>
43
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
44
+ <TooltipContent side="top">{label ?? 'Playback speed'}</TooltipContent>
45
+ </Tooltip>
46
+ );
31
47
  }