@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
|
@@ -1,32 +1,251 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
forwardRef,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ButtonHTMLAttributes,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { Volume2, VolumeX, Volume1 } from 'lucide-react';
|
|
4
13
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
5
|
-
import
|
|
14
|
+
import {
|
|
15
|
+
Popover,
|
|
16
|
+
PopoverContent,
|
|
17
|
+
PopoverTrigger,
|
|
18
|
+
Slider,
|
|
19
|
+
Tooltip,
|
|
20
|
+
TooltipContent,
|
|
21
|
+
TooltipTrigger,
|
|
22
|
+
} from '@djangocfg/ui-core/components';
|
|
6
23
|
|
|
7
24
|
export interface VolumeProps {
|
|
8
25
|
readonly className?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Reserved for backwards compatibility. The control is always a single
|
|
28
|
+
* icon button that opens a popover with a vertical slider on hover/focus,
|
|
29
|
+
* so this flag is now a no-op. Kept so existing consumers
|
|
30
|
+
* (`<Volume iconOnly />`) don't break.
|
|
31
|
+
*/
|
|
9
32
|
readonly iconOnly?: boolean;
|
|
10
|
-
|
|
11
|
-
readonly
|
|
33
|
+
/** Mute-button tooltip copy. Defaults to `"Volume"`. Pass `false` to opt out. */
|
|
34
|
+
readonly muteLabel?: ReactNode | false;
|
|
12
35
|
}
|
|
13
36
|
|
|
14
|
-
|
|
37
|
+
// Hide the popover slider on iOS Safari — `video.volume` is read-only
|
|
38
|
+
// there (controlled by hardware buttons), so a JS slider does nothing.
|
|
39
|
+
// The trigger still toggles mute, which iOS *does* honour.
|
|
40
|
+
function isIosSafari(): boolean {
|
|
41
|
+
if (typeof navigator === 'undefined') return false;
|
|
42
|
+
const ua = navigator.userAgent;
|
|
43
|
+
const iOS =
|
|
44
|
+
/iPad|iPhone|iPod/.test(ua) ||
|
|
45
|
+
(navigator.platform === 'MacIntel' &&
|
|
46
|
+
(navigator as { maxTouchPoints?: number }).maxTouchPoints! > 1);
|
|
47
|
+
return iOS;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// media-chrome request event names. Dispatched on any descendant of
|
|
51
|
+
// `<media-controller>`; the controller catches them and updates its
|
|
52
|
+
// internal store, which then propagates state back via attributes.
|
|
53
|
+
const MEDIA_VOLUME_REQUEST = 'mediavolumerequest';
|
|
54
|
+
const MEDIA_MUTE_REQUEST = 'mediamuterequest';
|
|
55
|
+
const MEDIA_UNMUTE_REQUEST = 'mediaunmuterequest';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Click-style trigger that visually matches the rest of the VideoPlayer
|
|
59
|
+
* control bar. Mirrors `.video-player__control` styling from
|
|
60
|
+
* `styles/video-player.css` so it slots in next to `<MediaPlayButton>` &
|
|
61
|
+
* friends without standing out.
|
|
62
|
+
*/
|
|
63
|
+
const TriggerButton = forwardRef<
|
|
64
|
+
HTMLButtonElement,
|
|
65
|
+
ButtonHTMLAttributes<HTMLButtonElement>
|
|
66
|
+
>(function TriggerButton({ className, children, ...rest }, ref) {
|
|
15
67
|
return (
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{!iconOnly && (
|
|
25
|
-
<MediaVolumeRange
|
|
26
|
-
{...rangeProps}
|
|
27
|
-
className={cn('h-8 w-20', rangeProps?.className)}
|
|
28
|
-
/>
|
|
68
|
+
<button
|
|
69
|
+
ref={ref}
|
|
70
|
+
type="button"
|
|
71
|
+
className={cn(
|
|
72
|
+
'video-player__control',
|
|
73
|
+
'grid h-8 w-8 place-items-center rounded-md',
|
|
74
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60',
|
|
75
|
+
className,
|
|
29
76
|
)}
|
|
77
|
+
{...rest}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</button>
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* VideoPlayer volume control.
|
|
86
|
+
*
|
|
87
|
+
* - **Click** the trigger → mute / unmute toggle (dispatches a
|
|
88
|
+
* `mediamuterequest` / `mediaunmuterequest` so media-chrome's store
|
|
89
|
+
* stays the single source of truth — no parallel state machine).
|
|
90
|
+
* - **Hover / keyboard focus** → opens a popover containing a vertical
|
|
91
|
+
* `<Slider>` (0..100). Drag = volume change via `mediavolumerequest`.
|
|
92
|
+
* - Icon reflects level (mute / low / high) and mirrors `media.muted`.
|
|
93
|
+
*
|
|
94
|
+
* State is read directly off the underlying `HTMLMediaElement` (located
|
|
95
|
+
* via the closest `<media-controller>`'s `.media` accessor); we never
|
|
96
|
+
* shadow media-chrome's store.
|
|
97
|
+
*/
|
|
98
|
+
export function Volume({ className, muteLabel }: VolumeProps) {
|
|
99
|
+
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
100
|
+
const [volume, setVolume] = useState(1);
|
|
101
|
+
const [muted, setMuted] = useState(false);
|
|
102
|
+
const [open, setOpen] = useState(false);
|
|
103
|
+
const [iosSafari] = useState(isIosSafari);
|
|
104
|
+
|
|
105
|
+
// Resolve & track the underlying HTMLMediaElement so we can read live
|
|
106
|
+
// volume / muted. media-chrome fires `mediaelementchange` on the
|
|
107
|
+
// controller whenever the slotted media element swaps (e.g. source
|
|
108
|
+
// change), so we re-bind listeners on each swap.
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const trigger = triggerRef.current;
|
|
111
|
+
if (!trigger) return;
|
|
112
|
+
const controller = trigger.closest('media-controller') as
|
|
113
|
+
| (HTMLElement & { media?: HTMLMediaElement | null })
|
|
114
|
+
| null;
|
|
115
|
+
if (!controller) return;
|
|
116
|
+
|
|
117
|
+
let media: HTMLMediaElement | null = controller.media ?? null;
|
|
118
|
+
const sync = () => {
|
|
119
|
+
if (!media) return;
|
|
120
|
+
setVolume(media.volume);
|
|
121
|
+
setMuted(media.muted);
|
|
122
|
+
};
|
|
123
|
+
const bind = (el: HTMLMediaElement | null) => {
|
|
124
|
+
if (!el) return;
|
|
125
|
+
el.addEventListener('volumechange', sync);
|
|
126
|
+
sync();
|
|
127
|
+
};
|
|
128
|
+
const unbind = (el: HTMLMediaElement | null) => {
|
|
129
|
+
if (!el) return;
|
|
130
|
+
el.removeEventListener('volumechange', sync);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
bind(media);
|
|
134
|
+
|
|
135
|
+
const onMediaChange = (e: Event) => {
|
|
136
|
+
unbind(media);
|
|
137
|
+
const detail = (e as CustomEvent).detail as HTMLMediaElement | null;
|
|
138
|
+
media = detail ?? controller.media ?? null;
|
|
139
|
+
bind(media);
|
|
140
|
+
};
|
|
141
|
+
controller.addEventListener('mediaelementchange', onMediaChange);
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
unbind(media);
|
|
145
|
+
controller.removeEventListener('mediaelementchange', onMediaChange);
|
|
146
|
+
};
|
|
147
|
+
}, []);
|
|
148
|
+
|
|
149
|
+
const dispatch = useCallback((name: string, detail?: number) => {
|
|
150
|
+
const trigger = triggerRef.current;
|
|
151
|
+
if (!trigger) return;
|
|
152
|
+
trigger.dispatchEvent(
|
|
153
|
+
new CustomEvent(name, { detail, bubbles: true, composed: true }),
|
|
154
|
+
);
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
const toggleMute = useCallback(() => {
|
|
158
|
+
dispatch(muted ? MEDIA_UNMUTE_REQUEST : MEDIA_MUTE_REQUEST);
|
|
159
|
+
}, [dispatch, muted]);
|
|
160
|
+
|
|
161
|
+
const onSlider = useCallback(
|
|
162
|
+
(value: number) => {
|
|
163
|
+
dispatch(MEDIA_VOLUME_REQUEST, value);
|
|
164
|
+
// If the user nudges the slider above zero, treat that as an
|
|
165
|
+
// implicit unmute — matches what the native HTML5 volume slider does.
|
|
166
|
+
if (value > 0 && muted) dispatch(MEDIA_UNMUTE_REQUEST);
|
|
167
|
+
},
|
|
168
|
+
[dispatch, muted],
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const effectiveVolume = muted ? 0 : volume;
|
|
172
|
+
const Icon =
|
|
173
|
+
muted || volume === 0 ? VolumeX : volume < 0.5 ? Volume1 : Volume2;
|
|
174
|
+
|
|
175
|
+
const trigger = (
|
|
176
|
+
<TriggerButton
|
|
177
|
+
ref={triggerRef}
|
|
178
|
+
aria-label={muted ? 'Unmute' : 'Mute'}
|
|
179
|
+
aria-pressed={muted}
|
|
180
|
+
onClick={toggleMute}
|
|
181
|
+
>
|
|
182
|
+
<Icon className="h-4 w-4" strokeWidth={1.75} />
|
|
183
|
+
</TriggerButton>
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// On iOS Safari volume is hardware-only — skip the popover entirely
|
|
187
|
+
// and let the trigger work as a plain mute/unmute button.
|
|
188
|
+
if (iosSafari) {
|
|
189
|
+
if (muteLabel === false) return <div className={cn('flex', className)}>{trigger}</div>;
|
|
190
|
+
return (
|
|
191
|
+
<div className={cn('flex items-center', className)}>
|
|
192
|
+
<Tooltip>
|
|
193
|
+
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
|
|
194
|
+
<TooltipContent side="top">{muteLabel ?? 'Mute / unmute'}</TooltipContent>
|
|
195
|
+
</Tooltip>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div
|
|
202
|
+
className={cn('flex items-center', className)}
|
|
203
|
+
// Hover-driven open: opening on pointerenter of the wrapper (not
|
|
204
|
+
// just the button) gives the user a forgiving hit-area when their
|
|
205
|
+
// cursor drifts between trigger and popover. The popover itself
|
|
206
|
+
// also catches pointerenter via Radix portal — so once it's open,
|
|
207
|
+
// moving into it keeps it open.
|
|
208
|
+
onPointerEnter={() => setOpen(true)}
|
|
209
|
+
onPointerLeave={() => setOpen(false)}
|
|
210
|
+
onFocus={() => setOpen(true)}
|
|
211
|
+
onBlur={(e) => {
|
|
212
|
+
// Don't close when focus moves between the trigger and the
|
|
213
|
+
// popover content (both live in this wrapper / its portal).
|
|
214
|
+
if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {
|
|
215
|
+
setOpen(false);
|
|
216
|
+
}
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
220
|
+
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
|
|
221
|
+
<PopoverContent
|
|
222
|
+
side="top"
|
|
223
|
+
align="center"
|
|
224
|
+
sideOffset={8}
|
|
225
|
+
// Keep focus on the trigger so click-to-mute still works while
|
|
226
|
+
// the popover is visible; the slider remains keyboard-reachable
|
|
227
|
+
// via Tab once a user explicitly tabs into it.
|
|
228
|
+
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
229
|
+
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
230
|
+
onPointerEnter={() => setOpen(true)}
|
|
231
|
+
onPointerLeave={() => setOpen(false)}
|
|
232
|
+
className="flex w-12 flex-col items-center gap-2 p-3"
|
|
233
|
+
>
|
|
234
|
+
<span className="w-full text-center text-[10px] tabular-nums text-muted-foreground">
|
|
235
|
+
{Math.round(effectiveVolume * 100)}
|
|
236
|
+
</span>
|
|
237
|
+
<Slider
|
|
238
|
+
orientation="vertical"
|
|
239
|
+
min={0}
|
|
240
|
+
max={1}
|
|
241
|
+
step={0.01}
|
|
242
|
+
value={[effectiveVolume]}
|
|
243
|
+
onValueChange={([v]) => onSlider(v)}
|
|
244
|
+
aria-label="Volume"
|
|
245
|
+
className="h-24"
|
|
246
|
+
/>
|
|
247
|
+
</PopoverContent>
|
|
248
|
+
</Popover>
|
|
30
249
|
</div>
|
|
31
250
|
);
|
|
32
251
|
}
|
|
@@ -42,11 +42,6 @@ media-controller {
|
|
|
42
42
|
--media-range-thumb-opacity: 0;
|
|
43
43
|
--media-range-thumb-transition: opacity 0.15s ease;
|
|
44
44
|
|
|
45
|
-
/* Tooltip */
|
|
46
|
-
--media-tooltip-background: var(--popover);
|
|
47
|
-
--media-tooltip-color: var(--popover-foreground);
|
|
48
|
-
--media-tooltip-border-radius: 6px;
|
|
49
|
-
|
|
50
45
|
/* Font */
|
|
51
46
|
--media-font-family: inherit;
|
|
52
47
|
--media-font-size: 12px;
|
|
@@ -87,6 +82,29 @@ media-control-bar {
|
|
|
87
82
|
background: transparent;
|
|
88
83
|
}
|
|
89
84
|
|
|
85
|
+
/*
|
|
86
|
+
* Suppress media-chrome's built-in tooltips. They are sticky elements
|
|
87
|
+
* slotted under each control button's shadow root (exposed via
|
|
88
|
+
* `part="tooltip"`) and paint a permanent "Play" / "Mute" / "Enter
|
|
89
|
+
* fullscreen" label below the bar. We wrap each control with our own
|
|
90
|
+
* Radix-backed <Tooltip> from @djangocfg/ui-core, which fires only on
|
|
91
|
+
* hover/focus and matches the rest of the platform. Reach into the
|
|
92
|
+
* shadow DOM via ::part(tooltip) — a global `media-tooltip` selector
|
|
93
|
+
* cannot pierce the boundary.
|
|
94
|
+
*/
|
|
95
|
+
media-play-button::part(tooltip),
|
|
96
|
+
media-mute-button::part(tooltip),
|
|
97
|
+
media-fullscreen-button::part(tooltip),
|
|
98
|
+
media-pip-button::part(tooltip),
|
|
99
|
+
media-playback-rate-button::part(tooltip),
|
|
100
|
+
media-captions-button::part(tooltip),
|
|
101
|
+
media-airplay-button::part(tooltip),
|
|
102
|
+
media-cast-button::part(tooltip),
|
|
103
|
+
media-seek-forward-button::part(tooltip),
|
|
104
|
+
media-seek-backward-button::part(tooltip) {
|
|
105
|
+
display: none !important;
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
/*
|
|
91
109
|
* Auto-hide. media-chrome flags inactivity by setting `userinactive` on
|
|
92
110
|
* the <media-controller>; it only does so while playback is running, and
|
|
@@ -120,17 +138,79 @@ media-time-range {
|
|
|
120
138
|
--media-preview-background: var(--popover);
|
|
121
139
|
--media-preview-border-radius: 6px;
|
|
122
140
|
--media-text-color: var(--popover-foreground);
|
|
141
|
+
|
|
142
|
+
/* Kill media-chrome's default blue focus rectangle on the range track.
|
|
143
|
+
media-chrome paints a `box-shadow: inset 0 0 0 2px rgb(27 127 204 / .9)`
|
|
144
|
+
via `--media-focus-box-shadow` whenever the inner `<input type=range>`
|
|
145
|
+
matches `:focus-visible`, which wraps the entire seek-bar in a big
|
|
146
|
+
blue rectangle on click/Tab. We opt out of the track-wide ring and
|
|
147
|
+
surface focus on the thumb instead (see :focus-visible below). */
|
|
148
|
+
--media-focus-box-shadow: none;
|
|
123
149
|
}
|
|
124
150
|
|
|
125
|
-
/* Reveal the accent thumb only while hovering / scrubbing the rail
|
|
126
|
-
|
|
151
|
+
/* Reveal the accent thumb only while hovering / scrubbing the rail, OR
|
|
152
|
+
when the rail is keyboard-focused so a11y keyboard users still see
|
|
153
|
+
where focus lives. */
|
|
154
|
+
media-time-range:hover,
|
|
155
|
+
media-time-range:focus-within {
|
|
127
156
|
--media-range-thumb-opacity: 1;
|
|
128
157
|
}
|
|
129
158
|
|
|
159
|
+
/* Keyboard focus indicator: a ring around the thumb (not the track).
|
|
160
|
+
Uses the platform `--ring` semantic token so it matches every other
|
|
161
|
+
focusable surface. */
|
|
162
|
+
media-time-range:focus-within {
|
|
163
|
+
--media-range-thumb-box-shadow: 0 0 0 2px var(--ring);
|
|
164
|
+
}
|
|
165
|
+
|
|
130
166
|
media-time-range::part(preview-box) {
|
|
131
167
|
display: none;
|
|
132
168
|
}
|
|
133
169
|
|
|
170
|
+
/*
|
|
171
|
+
* YouTube iframe bleed-through suppression.
|
|
172
|
+
*
|
|
173
|
+
* YouTube's IFrame embed ToS requires showing some chrome that cannot be
|
|
174
|
+
* hidden via player params: the title bar at the top, the large pause
|
|
175
|
+
* button on the pause screen, and the bottom branding/share tray on
|
|
176
|
+
* hover/pause. We can't remove them, but we CAN make them non-interactive
|
|
177
|
+
* — so the user never triggers YouTube's hover-state, never gets a
|
|
178
|
+
* "More videos" overlay, and clicks never reach the YT iframe.
|
|
179
|
+
*
|
|
180
|
+
* The <youtube-video> custom element renders an inner <iframe>; disable
|
|
181
|
+
* pointer events on it (and on the wrapper itself, just in case the
|
|
182
|
+
* element later swaps layout). The companion `.vp-yt-click-shield` div
|
|
183
|
+
* inside <media-controller> absorbs clicks and converts them into
|
|
184
|
+
* media-chrome play/pause requests — so click-to-toggle still works.
|
|
185
|
+
*
|
|
186
|
+
* Our control bar lives above the shield and re-enables pointer events
|
|
187
|
+
* for the slot, so the controls stay clickable.
|
|
188
|
+
*/
|
|
189
|
+
youtube-video,
|
|
190
|
+
youtube-video iframe {
|
|
191
|
+
pointer-events: none;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.vp-yt-click-shield {
|
|
195
|
+
position: absolute;
|
|
196
|
+
inset: 0;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
background: transparent;
|
|
199
|
+
pointer-events: auto;
|
|
200
|
+
/* Sit above the iframe but below the slotted control bar. */
|
|
201
|
+
z-index: 1;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* media-chrome slots its control surfaces directly under
|
|
205
|
+
<media-controller>; raise them above the click-shield and restore
|
|
206
|
+
pointer events so buttons remain interactive. */
|
|
207
|
+
media-controller > media-control-bar,
|
|
208
|
+
media-controller > [slot='centered-chrome'],
|
|
209
|
+
media-controller > [slot='top-chrome'] {
|
|
210
|
+
z-index: 2;
|
|
211
|
+
pointer-events: auto;
|
|
212
|
+
}
|
|
213
|
+
|
|
134
214
|
media-time-range::part(current-box) {
|
|
135
215
|
padding: 2px 6px;
|
|
136
216
|
border-radius: 6px;
|
|
@@ -79,4 +79,8 @@ export interface VideoPlayerProps extends VideoPlayerSettings {
|
|
|
79
79
|
readonly className?: string;
|
|
80
80
|
/** Custom children replace the default control bar entirely. */
|
|
81
81
|
readonly children?: ReactNode;
|
|
82
|
+
/** Focus the player container on mount so media-chrome keyboard shortcuts
|
|
83
|
+
* (space=play/pause, f=fullscreen, arrows=seek/volume) are active
|
|
84
|
+
* immediately. Pair with `key={src}` upstream for per-source focus reset. */
|
|
85
|
+
readonly autoFocus?: boolean;
|
|
82
86
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* import { LazyResponsiveDialog, LazyResponsiveDialogContent } from '@djangocfg/ui-tools/responsive-dialog';
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { createLazyComponent, LoadingFallback } from '../../../
|
|
13
|
+
import { createLazyComponent, LoadingFallback } from '../../../common/lazy-wrapper';
|
|
14
14
|
import type {
|
|
15
15
|
ResponsiveDialogProps,
|
|
16
16
|
ResponsiveDialogContentProps,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Lazy-loaded ScrollSpy Component
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { createLazyComponent, LoadingFallback } from '../../../
|
|
7
|
+
import { createLazyComponent, LoadingFallback } from '../../../common/lazy-wrapper';
|
|
8
8
|
import type {
|
|
9
9
|
ScrollSpyProps,
|
|
10
10
|
ScrollSpyNavProps,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Lazy-loaded SelectionToolbar Component
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { createLazyComponent, LoadingFallback } from '../../../
|
|
7
|
+
import { createLazyComponent, LoadingFallback } from '../../../common/lazy-wrapper';
|
|
8
8
|
import type {
|
|
9
9
|
SelectionToolbarProps,
|
|
10
10
|
SelectionToolbarContentProps,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { createLazyComponent } from '../../../
|
|
3
|
+
import { createLazyComponent } from '../../../common/lazy-wrapper';
|
|
4
4
|
import type { MarqueeProps } from './types';
|
|
5
5
|
|
|
6
6
|
export const LazyMarquee = createLazyComponent<MarqueeProps>(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { createLazyComponent } from '../../../
|
|
3
|
+
import { createLazyComponent } from '../../../common/lazy-wrapper';
|
|
4
4
|
import type { QRCodeProps } from './types';
|
|
5
5
|
|
|
6
6
|
export const LazyQRCode = createLazyComponent<QRCodeProps>(
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
|
+
//
|
|
3
|
+
// GitHub-style activity heatmap that visualizes daily counts as a
|
|
4
|
+
// color-intensity grid. Auto-sizes blocks to fit the container by default.
|
|
5
|
+
//
|
|
6
|
+
// Port notes:
|
|
7
|
+
// - Cell colors resolve through `useThemeColor('primary')` + `alpha(...)`,
|
|
8
|
+
// not raw `bg-emerald-*` (CONTRACT §2, AUDIT §1). The five emerald
|
|
9
|
+
// stops in the original collapse to five opacity stops on the active
|
|
10
|
+
// theme's `primary` token, which means the heatmap follows the theme
|
|
11
|
+
// preset automatically.
|
|
12
|
+
// - Intensity bucketing routed through `getIntensity` from
|
|
13
|
+
// `@djangocfg/ui-core/lib` (CONTRACT shared util added in Phase 0).
|
|
14
|
+
// - Container sizing routed through `useResizeObserver` from
|
|
15
|
+
// `@djangocfg/ui-core/hooks` (shared RO from Phase 0), replacing the
|
|
16
|
+
// local `ResizeObserver` instance in the source.
|
|
17
|
+
// - Tooltips: the original wrapped every cell in its own Radix
|
|
18
|
+
// `Tooltip.Provider`. Mounting a provider per cell violates CONTRACT
|
|
19
|
+
// §5 ("no nested providers") and explodes to 365 portals on a default
|
|
20
|
+
// 52-week graph. The port uses the native `title` attribute on each
|
|
21
|
+
// cell instead — accessible, no provider dependency, no portal.
|
|
22
|
+
|
|
23
|
+
'use client';
|
|
24
|
+
|
|
25
|
+
import * as React from 'react';
|
|
26
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
27
|
+
import { getIntensity } from '@djangocfg/ui-core/lib';
|
|
28
|
+
import { useResizeObserver } from '@djangocfg/ui-core/hooks';
|
|
29
|
+
import { useThemeColor, alpha } from '@djangocfg/ui-core/styles/palette';
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
DAY_LABEL_INDICES,
|
|
33
|
+
DAY_LABEL_WIDTH,
|
|
34
|
+
DAY_LABELS,
|
|
35
|
+
DEFAULT_INTENSITY_OPACITY,
|
|
36
|
+
DEFAULT_INTENSITY_THRESHOLDS,
|
|
37
|
+
GAP,
|
|
38
|
+
MONTH_LABEL_HEIGHT,
|
|
39
|
+
type ActivityGraphProps,
|
|
40
|
+
} from './types';
|
|
41
|
+
import { buildWeeks, formatDate, getMonthLabels } from './utils';
|
|
42
|
+
|
|
43
|
+
export function ActivityGraph({
|
|
44
|
+
data,
|
|
45
|
+
intensityOpacity = DEFAULT_INTENSITY_OPACITY,
|
|
46
|
+
blockSize: fixedBlockSize,
|
|
47
|
+
blockRadius = 2,
|
|
48
|
+
weeks: weekCount = 52,
|
|
49
|
+
className,
|
|
50
|
+
...props
|
|
51
|
+
}: ActivityGraphProps) {
|
|
52
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
53
|
+
const { width: containerWidth } = useResizeObserver(containerRef);
|
|
54
|
+
|
|
55
|
+
const isAutoFit = fixedBlockSize == null;
|
|
56
|
+
|
|
57
|
+
// Auto-fit cell size based on container width. Falls back to a small
|
|
58
|
+
// default until the first measurement lands (consistent with original).
|
|
59
|
+
const autoSize = React.useMemo(() => {
|
|
60
|
+
if (!isAutoFit || containerWidth <= 0) return null;
|
|
61
|
+
const available = containerWidth - DAY_LABEL_WIDTH;
|
|
62
|
+
const size = (available - GAP * (weekCount - 1)) / weekCount;
|
|
63
|
+
return Math.max(4, Math.floor(size));
|
|
64
|
+
}, [isAutoFit, containerWidth, weekCount]);
|
|
65
|
+
|
|
66
|
+
const blockSize = fixedBlockSize ?? autoSize ?? 10;
|
|
67
|
+
const showGraph = !isAutoFit || autoSize != null;
|
|
68
|
+
|
|
69
|
+
// Resolve the theme color once; map each intensity bucket to an
|
|
70
|
+
// opacity stop applied to that color (see CONTRACT §3 — never raw
|
|
71
|
+
// Tailwind class for parametric colors).
|
|
72
|
+
const primary = useThemeColor('primary');
|
|
73
|
+
const intensityColors = React.useMemo(
|
|
74
|
+
() => intensityOpacity.map((op) => alpha(primary, op)),
|
|
75
|
+
[primary, intensityOpacity],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const weeks = React.useMemo(() => buildWeeks(data, weekCount), [data, weekCount]);
|
|
79
|
+
const maxCount = React.useMemo(
|
|
80
|
+
() => data.reduce((m, d) => (d.count > m ? d.count : m), 0),
|
|
81
|
+
[data],
|
|
82
|
+
);
|
|
83
|
+
const monthLabels = React.useMemo(
|
|
84
|
+
() => getMonthLabels(weeks, blockSize),
|
|
85
|
+
[weeks, blockSize],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const gridWidth = weeks.length * (blockSize + GAP) - GAP;
|
|
89
|
+
const gridHeight = 7 * (blockSize + GAP) - GAP;
|
|
90
|
+
const totalWidth = DAY_LABEL_WIDTH + gridWidth;
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div
|
|
94
|
+
ref={containerRef}
|
|
95
|
+
className={cn(isAutoFit ? 'w-full' : 'overflow-x-auto', className)}
|
|
96
|
+
role="img"
|
|
97
|
+
aria-label="Activity graph"
|
|
98
|
+
data-slot="activity-graph"
|
|
99
|
+
{...props}
|
|
100
|
+
>
|
|
101
|
+
{showGraph && (
|
|
102
|
+
<div
|
|
103
|
+
className="flex flex-col gap-2"
|
|
104
|
+
style={{ minWidth: isAutoFit ? undefined : totalWidth }}
|
|
105
|
+
>
|
|
106
|
+
<div
|
|
107
|
+
className="relative"
|
|
108
|
+
style={{
|
|
109
|
+
width: isAutoFit ? '100%' : totalWidth,
|
|
110
|
+
height: MONTH_LABEL_HEIGHT + gridHeight,
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{monthLabels.map((m, i) => (
|
|
114
|
+
<span
|
|
115
|
+
key={`${m.label}-${i}`}
|
|
116
|
+
className="absolute text-[10px] leading-none text-muted-foreground"
|
|
117
|
+
style={{ left: DAY_LABEL_WIDTH + m.offset, top: 0 }}
|
|
118
|
+
>
|
|
119
|
+
{m.label}
|
|
120
|
+
</span>
|
|
121
|
+
))}
|
|
122
|
+
|
|
123
|
+
{DAY_LABELS.map((label, i) => (
|
|
124
|
+
<span
|
|
125
|
+
key={label}
|
|
126
|
+
className="absolute text-[10px] leading-none text-muted-foreground"
|
|
127
|
+
style={{
|
|
128
|
+
left: 0,
|
|
129
|
+
top:
|
|
130
|
+
MONTH_LABEL_HEIGHT +
|
|
131
|
+
DAY_LABEL_INDICES[i] * (blockSize + GAP) +
|
|
132
|
+
blockSize / 2 -
|
|
133
|
+
4,
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
{label}
|
|
137
|
+
</span>
|
|
138
|
+
))}
|
|
139
|
+
|
|
140
|
+
<div
|
|
141
|
+
className="absolute"
|
|
142
|
+
style={{
|
|
143
|
+
left: DAY_LABEL_WIDTH,
|
|
144
|
+
top: MONTH_LABEL_HEIGHT,
|
|
145
|
+
display: 'flex',
|
|
146
|
+
gap: GAP,
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
{weeks.map((week, wi) => (
|
|
150
|
+
<div key={wi} style={{ display: 'flex', flexDirection: 'column', gap: GAP }}>
|
|
151
|
+
{week.map((day, di) => {
|
|
152
|
+
const ratio = maxCount > 0 ? day.count / maxCount : 0;
|
|
153
|
+
const intensity = getIntensity(ratio, [...DEFAULT_INTENSITY_THRESHOLDS]);
|
|
154
|
+
const title = `${day.count} contribution${day.count === 1 ? '' : 's'} — ${formatDate(day.date)}`;
|
|
155
|
+
return (
|
|
156
|
+
<div
|
|
157
|
+
key={di}
|
|
158
|
+
data-slot="activity-graph-cell"
|
|
159
|
+
data-intensity={intensity}
|
|
160
|
+
className="transition-colors"
|
|
161
|
+
title={title}
|
|
162
|
+
style={{
|
|
163
|
+
width: blockSize,
|
|
164
|
+
height: blockSize,
|
|
165
|
+
borderRadius: blockRadius,
|
|
166
|
+
backgroundColor: intensityColors[intensity],
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</div>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div className="flex items-center gap-1.5 self-end text-[10px] text-muted-foreground">
|
|
177
|
+
<span>Less</span>
|
|
178
|
+
{intensityColors.map((color, i) => (
|
|
179
|
+
<div
|
|
180
|
+
key={i}
|
|
181
|
+
style={{
|
|
182
|
+
width: blockSize,
|
|
183
|
+
height: blockSize,
|
|
184
|
+
borderRadius: blockRadius,
|
|
185
|
+
backgroundColor: color,
|
|
186
|
+
}}
|
|
187
|
+
/>
|
|
188
|
+
))}
|
|
189
|
+
<span>More</span>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# ActivityGraph
|
|
2
|
+
|
|
3
|
+
GitHub-style contribution heatmap. Maps daily counts to opacity stops of the active theme `primary` color via `useThemeColor` + `useResizeObserver`.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { ActivityGraph } from '@djangocfg/ui-tools/activity-graph';
|
|
7
|
+
|
|
8
|
+
<ActivityGraph
|
|
9
|
+
data={[{ date: '2025-01-12', count: 4 }, { date: '2025-01-13', count: 0 }]}
|
|
10
|
+
weeks={52}
|
|
11
|
+
/>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Props
|
|
15
|
+
|
|
16
|
+
| Prop | Type | Default | Description |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| `data` | `ActivityEntry[]` | — | Daily entries `{ date, count }`. Duplicate dates are summed. |
|
|
19
|
+
| `weeks` | `number` | `52` | Trailing week count. |
|
|
20
|
+
| `blockSize` | `number` | auto | Cell size in px. When omitted, auto-fits the container. |
|
|
21
|
+
| `blockRadius` | `number` | `2` | Cell border-radius in px. |
|
|
22
|
+
| `intensityOpacity` | `[number,number,number,number,number]` | `[0.08, 0.18, 0.35, 0.6, 0.95]` | Opacity stops applied to the theme `primary` color (intensity 0–4). |
|
|
23
|
+
|
|
24
|
+
Storybook: `apps/storybook/stories/ui-tools/visual/ActivityGraph.stories.tsx`
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
Adapted from jalcoui (MIT).
|