@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.
- package/dist/audio-player/index.cjs +1 -2
- package/dist/audio-player/index.cjs.map +1 -1
- package/dist/audio-player/index.d.cts +3 -11
- package/dist/audio-player/index.d.ts +3 -11
- package/dist/audio-player/index.mjs +1 -2
- package/dist/audio-player/index.mjs.map +1 -1
- package/dist/file-icon/index.cjs +3 -3
- package/dist/file-icon/index.cjs.map +1 -1
- package/dist/file-icon/index.mjs +3 -3
- package/dist/file-icon/index.mjs.map +1 -1
- package/dist/tree/index.cjs +0 -3
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.mjs +0 -3
- package/dist/tree/index.mjs.map +1 -1
- package/package.json +117 -36
- package/src/common/FloatingToolbar/actions/CopyAction.tsx +31 -0
- package/src/{components → common}/FloatingToolbar/actions/DownloadAction.tsx +15 -10
- package/src/common/FloatingToolbar/actions/ExpandAction.tsx +33 -0
- package/src/common/FloatingToolbar/actions/FullscreenAction.tsx +38 -0
- package/src/{components → common}/FloatingToolbar/index.tsx +39 -0
- package/src/lib/http.ts +64 -0
- package/src/tools/chat/index.ts +1 -1
- package/src/tools/chat/launcher/ChatFAB.tsx +66 -74
- package/src/tools/chat/launcher/header/ChatHeaderActionButton.tsx +2 -3
- package/src/tools/chat/lazy.tsx +1 -1
- package/src/tools/chat/messages/MessageBubble.tsx +1 -1
- package/src/tools/chat/messages/blocks/builtin.tsx +1 -1
- package/src/tools/chat/messages/blocks/renderers/CodeBlock.tsx +2 -2
- package/src/tools/chat/messages/blocks/renderers/JsonBlock.tsx +12 -1
- package/src/tools/data/DataGrid/lazy.tsx +1 -1
- package/src/tools/data/DataTable/lazy.tsx +1 -1
- package/src/tools/data/JsonTree/JsonViewer.tsx +720 -0
- package/src/tools/data/JsonTree/README.md +126 -73
- package/src/tools/data/JsonTree/index.tsx +3 -95
- package/src/tools/data/JsonTree/lazy.tsx +10 -50
- package/src/tools/data/JsonTree/types.ts +82 -63
- package/src/tools/data/Kanban/lazy.tsx +1 -1
- package/src/tools/data/Listbox/lazy.tsx +1 -1
- package/src/tools/data/Masonry/lazy.tsx +1 -1
- package/src/tools/data/Timeline/lazy.tsx +1 -1
- package/src/tools/data/Tree/components/TreeRow.tsx +0 -11
- package/src/tools/data/Tree/lazy.tsx +1 -1
- package/src/tools/dev/Map/lazy.tsx +1 -1
- package/src/tools/dev/Mermaid/Mermaid.client.tsx +2 -2
- package/src/tools/dev/Mermaid/lazy.tsx +1 -1
- package/src/tools/dev/api/ApiRefTable/ApiRefTable.tsx +65 -0
- package/src/tools/dev/api/ApiRefTable/README.md +31 -0
- package/src/tools/dev/api/ApiRefTable/components/Row.tsx +96 -0
- package/src/tools/dev/api/ApiRefTable/components/TypeDisplay.tsx +44 -0
- package/src/tools/dev/api/ApiRefTable/index.ts +6 -0
- package/src/tools/dev/api/ApiRefTable/lazy.tsx +21 -0
- package/src/tools/dev/api/ApiRefTable/types.ts +82 -0
- package/src/tools/dev/api/ApiRefTable/utils.ts +42 -0
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/ApiIntroSection.tsx +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/index.tsx +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +7 -21
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/RequestPanel.tsx +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/PrettyView.tsx +13 -19
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/types.ts +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/lazy.tsx +1 -1
- package/src/tools/dev/api/RequestViewer/README.md +33 -0
- package/src/tools/dev/api/RequestViewer/RequestViewer.tsx +121 -0
- package/src/tools/dev/api/RequestViewer/components/BodyTab.tsx +44 -0
- package/src/tools/dev/api/RequestViewer/components/EmptyState.tsx +13 -0
- package/src/tools/dev/api/RequestViewer/components/HeadersTab.tsx +78 -0
- package/src/tools/dev/api/RequestViewer/components/TimingTab.tsx +113 -0
- package/src/tools/dev/api/RequestViewer/components/utils.ts +31 -0
- package/src/tools/dev/api/RequestViewer/index.ts +16 -0
- package/src/tools/dev/api/RequestViewer/lazy.tsx +30 -0
- package/src/tools/dev/api/RequestViewer/types.ts +81 -0
- package/src/tools/dev/code/DiffViewer/DiffViewer.tsx +144 -0
- package/src/tools/dev/code/DiffViewer/README.md +33 -0
- package/src/tools/dev/code/DiffViewer/components/CopyButton.tsx +49 -0
- package/src/tools/dev/code/DiffViewer/components/DiffLineContent.tsx +48 -0
- package/src/tools/dev/code/DiffViewer/components/SplitView.tsx +220 -0
- package/src/tools/dev/code/DiffViewer/components/UnifiedView.tsx +154 -0
- package/src/tools/dev/code/DiffViewer/hooks/useDiff.ts +47 -0
- package/src/tools/dev/code/DiffViewer/hooks/useHighlighter.ts +54 -0
- package/src/tools/dev/code/DiffViewer/index.ts +22 -0
- package/src/tools/dev/code/DiffViewer/lazy.tsx +22 -0
- package/src/tools/dev/code/DiffViewer/types.ts +109 -0
- package/src/tools/dev/code/DiffViewer/utils/computeDiff.ts +159 -0
- package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/CollapseToggle.tsx +1 -1
- package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/MarkdownMessage.tsx +1 -1
- package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/components.tsx +2 -2
- package/src/tools/dev/{PrettyCode → code/PrettyCode}/PrettyCode.client.tsx +2 -2
- package/src/tools/dev/{PrettyCode → code/PrettyCode}/lazy.tsx +1 -1
- package/src/tools/dev/ops/EnvTable/EnvTable.tsx +228 -0
- package/src/tools/dev/ops/EnvTable/README.md +29 -0
- package/src/tools/dev/ops/EnvTable/hooks/useEnvMask.ts +121 -0
- package/src/tools/dev/ops/EnvTable/index.ts +12 -0
- package/src/tools/dev/ops/EnvTable/lazy.tsx +21 -0
- package/src/tools/dev/ops/EnvTable/types.ts +76 -0
- package/src/tools/dev/ops/LogViewer/LogViewer.tsx +194 -0
- package/src/tools/dev/ops/LogViewer/README.md +30 -0
- package/src/tools/dev/ops/LogViewer/components/LogRow.tsx +151 -0
- package/src/tools/dev/ops/LogViewer/components/Toolbar.tsx +199 -0
- package/src/tools/dev/ops/LogViewer/hooks/useAutoScroll.ts +68 -0
- package/src/tools/dev/ops/LogViewer/hooks/useLogFilter.ts +58 -0
- package/src/tools/dev/ops/LogViewer/index.ts +18 -0
- package/src/tools/dev/ops/LogViewer/lazy.tsx +25 -0
- package/src/tools/dev/ops/LogViewer/types.ts +142 -0
- package/src/tools/dev/ops/LogViewer/utils/ansi.ts +231 -0
- package/src/tools/forms/CodeEditor/components/Editor.tsx +19 -0
- package/src/tools/forms/CodeEditor/hooks/useEditorTheme.ts +13 -73
- package/src/tools/forms/CodeEditor/lazy.tsx +1 -1
- package/src/tools/forms/CodeEditor/types/index.ts +7 -0
- package/src/tools/forms/FileUpload/lazy.tsx +1 -1
- package/src/tools/forms/JsonEditor/JsonEditor.tsx +115 -0
- package/src/tools/forms/JsonEditor/index.ts +1 -0
- package/src/tools/forms/JsonEditor/lazy.tsx +24 -0
- package/src/tools/forms/JsonForm/index.ts +1 -1
- package/src/tools/forms/JsonForm/lazy.tsx +1 -1
- package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +40 -0
- package/src/tools/forms/MarkdownEditor/lazy.tsx +1 -1
- package/src/tools/forms/MarkdownEditor/styles.css +174 -21
- package/src/tools/forms/NotionEditor/CustomKeymap.ts +48 -0
- package/src/tools/forms/NotionEditor/LinkDialog.tsx +133 -0
- package/src/tools/forms/NotionEditor/NotionEditor.tsx +304 -0
- package/src/tools/forms/NotionEditor/README.md +237 -0
- package/src/tools/forms/NotionEditor/SlashExtension.ts +32 -0
- package/src/tools/forms/NotionEditor/SlashList.tsx +136 -0
- package/src/tools/forms/NotionEditor/TaskItemView.tsx +41 -0
- package/src/tools/forms/NotionEditor/createSlashSuggestion.ts +121 -0
- package/src/tools/forms/NotionEditor/extensions.ts +105 -0
- package/src/tools/forms/NotionEditor/index.ts +1 -0
- package/src/tools/forms/NotionEditor/lazy.tsx +44 -0
- package/src/tools/forms/NotionEditor/slashItems.ts +159 -0
- package/src/tools/forms/NotionEditor/styles.css +478 -0
- package/src/tools/forms/NotionEditor/types.ts +28 -0
- package/src/tools/index.ts +153 -13
- package/src/tools/input/Combobox/lazy.tsx +1 -1
- package/src/tools/input/CronScheduler/components/CronPreview.README.md +28 -0
- package/src/tools/input/CronScheduler/components/CronPreview.tsx +136 -0
- package/src/tools/input/CronScheduler/components/index.ts +3 -0
- package/src/tools/input/CronScheduler/index.tsx +5 -1
- package/src/tools/input/CronScheduler/lazy.tsx +5 -1
- package/src/tools/input/CronScheduler/utils/cron-next-runs.ts +122 -0
- package/src/tools/input/CronScheduler/utils/index.ts +1 -0
- package/src/tools/input/Scroller/lazy.tsx +1 -1
- package/src/tools/input/Sortable/lazy.tsx +1 -1
- package/src/tools/input/SpeechRecognition/lazy.tsx +1 -1
- package/src/tools/input/SpeechRecognition/widgets/VoiceComposerSlot.tsx +41 -36
- package/src/tools/media/AudioPlayer/PlayerShell.tsx +3 -11
- package/src/tools/media/AudioPlayer/types.ts +4 -11
- package/src/tools/media/ImageViewer/components/ImageToolbar.tsx +58 -47
- package/src/tools/media/ImageViewer/components/ImageViewer.tsx +35 -19
- package/src/tools/media/ImageViewer/lazy.tsx +1 -1
- package/src/tools/media/ImageViewer/types.ts +4 -0
- package/src/tools/media/LottiePlayer/lazy.tsx +1 -1
- package/src/tools/media/VideoPlayer/VideoPlayer.tsx +47 -1
- package/src/tools/media/VideoPlayer/parts/fullscreen.tsx +21 -4
- package/src/tools/media/VideoPlayer/parts/pip.tsx +21 -4
- package/src/tools/media/VideoPlayer/parts/play-button.tsx +21 -4
- package/src/tools/media/VideoPlayer/parts/playback-rate.tsx +19 -3
- package/src/tools/media/VideoPlayer/parts/volume.tsx +237 -18
- package/src/tools/media/VideoPlayer/styles/video-player.css +87 -7
- package/src/tools/media/VideoPlayer/types.ts +4 -0
- package/src/tools/overlay/ResponsiveDialog/lazy.tsx +1 -1
- package/src/tools/overlay/ScrollSpy/lazy.tsx +1 -1
- package/src/tools/overlay/SelectionToolbar/lazy.tsx +1 -1
- package/src/tools/overlay/Tour/lazy.tsx +1 -1
- package/src/tools/visual/Marquee/lazy.tsx +1 -1
- package/src/tools/visual/QRCode/lazy.tsx +1 -1
- package/src/tools/visual/charts/ActivityGraph/ActivityGraph.tsx +195 -0
- package/src/tools/visual/charts/ActivityGraph/README.md +28 -0
- package/src/tools/visual/charts/ActivityGraph/index.ts +8 -0
- package/src/tools/visual/charts/ActivityGraph/lazy.tsx +21 -0
- package/src/tools/visual/charts/ActivityGraph/types.ts +59 -0
- package/src/tools/visual/charts/ActivityGraph/utils.ts +88 -0
- package/src/tools/visual/charts/CommitGraph/CommitGraph.tsx +80 -0
- package/src/tools/visual/charts/CommitGraph/README.md +28 -0
- package/src/tools/visual/charts/CommitGraph/components/CommitDetail.tsx +107 -0
- package/src/tools/visual/charts/CommitGraph/components/CommitRow.tsx +122 -0
- package/src/tools/visual/charts/CommitGraph/components/Rails.tsx +171 -0
- package/src/tools/visual/charts/CommitGraph/hooks/useGraphLayout.ts +169 -0
- package/src/tools/visual/charts/CommitGraph/hooks/useLaneColors.ts +45 -0
- package/src/tools/visual/charts/CommitGraph/index.ts +14 -0
- package/src/tools/visual/charts/CommitGraph/lazy.tsx +25 -0
- package/src/tools/visual/charts/CommitGraph/types.ts +85 -0
- package/src/tools/visual/charts/CommitGraph/utils.ts +53 -0
- package/src/tools/visual/{Gauge → charts/Gauge}/lazy.tsx +1 -1
- package/src/tools/visual/charts/Sparkline/README.md +29 -0
- package/src/tools/visual/charts/Sparkline/Sparkline.tsx +217 -0
- package/src/tools/visual/charts/Sparkline/index.ts +9 -0
- package/src/tools/visual/charts/Sparkline/lazy.tsx +26 -0
- package/src/tools/visual/charts/Sparkline/types.ts +58 -0
- package/src/tools/visual/design/ColorPalette/ColorPalette.tsx +129 -0
- package/src/tools/visual/design/ColorPalette/README.md +34 -0
- package/src/tools/visual/design/ColorPalette/components/Swatch.tsx +102 -0
- package/src/tools/visual/design/ColorPalette/hooks/useCopyToClipboard.ts +41 -0
- package/src/tools/visual/design/ColorPalette/index.ts +12 -0
- package/src/tools/visual/design/ColorPalette/lazy.tsx +21 -0
- package/src/tools/visual/design/ColorPalette/types.ts +63 -0
- package/src/tools/visual/design/ColorPalette/utils.ts +83 -0
- package/src/tools/visual/{ColorPicker → design/ColorPicker}/lazy.tsx +1 -1
- package/src/tools/visual/{FileIcon → design/FileIcon}/treeAdapter.tsx +1 -1
- package/src/tools/visual/{Fps → indicators/Fps}/lazy.tsx +1 -1
- package/src/tools/visual/{Rating → indicators/Rating}/lazy.tsx +1 -1
- package/src/tools/visual/indicators/StatusIndicator/README.md +28 -0
- package/src/tools/visual/indicators/StatusIndicator/StatusIndicator.tsx +83 -0
- package/src/tools/visual/indicators/StatusIndicator/index.ts +14 -0
- package/src/tools/visual/indicators/StatusIndicator/lazy.tsx +21 -0
- package/src/tools/visual/indicators/StatusIndicator/types.ts +133 -0
- package/src/components/FloatingToolbar/actions/CopyAction.tsx +0 -22
- package/src/components/FloatingToolbar/actions/ExpandAction.tsx +0 -25
- package/src/components/FloatingToolbar/actions/FullscreenAction.tsx +0 -30
- package/src/tools/data/JsonTree/components/JsonContent.tsx +0 -197
- package/src/tools/data/JsonTree/hooks/useJsonExpand.ts +0 -50
- /package/src/{components → common}/FloatingToolbar/FloatingToolbar.css +0 -0
- /package/src/{components → common}/FloatingToolbar/actions/index.ts +0 -0
- /package/src/{components → common}/FloatingToolbar/hooks/useElementCorner.ts +0 -0
- /package/src/{components → common}/FloatingToolbar/hooks/useScrollIsolation.ts +0 -0
- /package/src/{components → common}/index.ts +0 -0
- /package/src/{components → common}/lazy-wrapper.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/README.md +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/DocsView.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/defaults.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/context.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/store/index.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/store/selectors.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/types.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/SchemaCopyMenu.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/BrandHeader.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/CategoryBlock.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/EndpointRow.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SchemaSection.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SearchInput.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SidebarBody.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/Toolbar.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/buildVM.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/types.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/useDebouncedValue.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/SlideInPlayground.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/TryItSheet.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/anchor.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/grouping.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/sidebarLabel.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/index.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/BodyFormEditor.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/EndpointDraftSync.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/EndpointResetButton.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/PreviewView.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/RawView.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/StatusBar.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/ViewTabs.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/detectContent.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/useResponseView.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/SendButton.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ui.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/constants.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/context/PlaygroundContext.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/index.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useDocsUrlSync.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useEndpointDraft.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useMobile.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useOpenApiSchema.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/types.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/apiKeyManager.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/codeSamples.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/formatters.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/index.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/operationToHar.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/sampler.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/schemaExport.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/scrollParent.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/url.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/versionManager.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/ActionRow.tsx +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/ChatMessageRow.tsx +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/CodeBlock.tsx +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/README.md +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/index.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/linkRules.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/plainText.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/sanitize.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/types.ts +0 -0
- /package/src/tools/dev/{PrettyCode → code/PrettyCode}/README.md +0 -0
- /package/src/tools/dev/{PrettyCode → code/PrettyCode}/index.tsx +0 -0
- /package/src/tools/dev/{PrettyCode → code/PrettyCode}/registerPrismLanguages.ts +0 -0
- /package/src/tools/visual/{Gauge → charts/Gauge}/Gauge.tsx +0 -0
- /package/src/tools/visual/{Gauge → charts/Gauge}/index.ts +0 -0
- /package/src/tools/visual/{Gauge → charts/Gauge}/types.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/ColorPicker.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/ColorPickerContext.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/ColorPickerStore.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/index.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/index.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/lib/color-utils.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerAlphaSlider.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerArea.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerEyeDropper.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerFormatSelect.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerHueSlider.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerInput.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerSwatch.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/index.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/types.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/FileIcon.tsx +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/get-file-icon.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/icons/icon-data.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/index.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/loader.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/specialFolders.ts +0 -0
- /package/src/tools/visual/{Fps → indicators/Fps}/Fps.tsx +0 -0
- /package/src/tools/visual/{Fps → indicators/Fps}/index.ts +0 -0
- /package/src/tools/visual/{Fps → indicators/Fps}/types.ts +0 -0
- /package/src/tools/visual/{Rating → indicators/Rating}/Rating.tsx +0 -0
- /package/src/tools/visual/{Rating → indicators/Rating}/index.ts +0 -0
- /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
|
-
<
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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-
|
|
400
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
103
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
135
|
+
</TbIconButton>
|
|
113
136
|
|
|
114
137
|
<div className="w-px h-4 bg-white/20 mx-1" />
|
|
115
138
|
|
|
116
139
|
{/* Transform controls */}
|
|
117
|
-
<
|
|
118
|
-
|
|
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
|
-
|
|
143
|
+
className={transform.flipH ? 'bg-white/20 text-white' : undefined}
|
|
122
144
|
>
|
|
123
145
|
<FlipHorizontal className="h-3.5 w-3.5" />
|
|
124
|
-
</
|
|
146
|
+
</TbIconButton>
|
|
125
147
|
|
|
126
|
-
<
|
|
127
|
-
|
|
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
|
-
|
|
151
|
+
className={transform.flipV ? 'bg-white/20 text-white' : undefined}
|
|
131
152
|
>
|
|
132
153
|
<FlipVertical className="h-3.5 w-3.5" />
|
|
133
|
-
</
|
|
154
|
+
</TbIconButton>
|
|
134
155
|
|
|
135
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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 '../../../
|
|
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 '../../../
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|