@djangocfg/ui-tools 2.1.418 → 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/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/package.json +93 -28
- 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/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/hooks/useEditorTheme.ts +13 -73
- package/src/tools/forms/CodeEditor/lazy.tsx +1 -1
- 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/lazy.tsx +1 -1
- package/src/tools/forms/NotionEditor/README.md +237 -0
- package/src/tools/forms/NotionEditor/lazy.tsx +1 -1
- 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/ImageViewer/components/ImageToolbar.tsx +58 -47
- package/src/tools/media/ImageViewer/components/ImageViewer.tsx +27 -19
- package/src/tools/media/ImageViewer/lazy.tsx +1 -1
- package/src/tools/media/LottiePlayer/lazy.tsx +1 -1
- package/src/tools/media/VideoPlayer/VideoPlayer.tsx +28 -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/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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Sparkline
|
|
2
|
+
|
|
3
|
+
Compact inline trend chart (line / area / bar). Resolves stroke + fill via `useThemeColor(color)` — semantic tokens only, no raw scales.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Sparkline } from '@djangocfg/ui-tools/sparkline';
|
|
7
|
+
|
|
8
|
+
<Sparkline data={[3, 5, 4, 7, 9, 8, 12]} variant="area" color="success" />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Default | Description |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `data` | `SparklineDatum[]` | — | `number` or `{ value, label? }`. Empty array renders nothing. |
|
|
16
|
+
| `variant` | `'line' \| 'area' \| 'bar'` | `'line'` | Visual style. |
|
|
17
|
+
| `color` | `'primary' \| 'success' \| 'warning' \| 'destructive' \| 'info'` | `'primary'` | Semantic theme color. |
|
|
18
|
+
| `width` | `number` | `120` | SVG width. |
|
|
19
|
+
| `height` | `number` | `32` | SVG height. |
|
|
20
|
+
| `strokeWidth` | `number` | `1.5` | Line stroke (ignored for `bar`). |
|
|
21
|
+
| `showEndpoint` | `boolean` | `true` | Filled dot at latest point. |
|
|
22
|
+
| `showBaseline` | `boolean` | `false` | Dashed average line. |
|
|
23
|
+
| `ariaLabel` | `string` | — | Accessible label. |
|
|
24
|
+
|
|
25
|
+
Storybook: `apps/storybook/stories/ui-tools/visual/Sparkline.stories.tsx`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
Adapted from jalcoui (MIT).
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
|
+
//
|
|
3
|
+
// Generic inline SVG sparkline. Pure SVG, no charting deps. Color resolves
|
|
4
|
+
// through the theme palette (semantic token → hex), so the stroke and area
|
|
5
|
+
// fill follow the active preset.
|
|
6
|
+
//
|
|
7
|
+
// Requires <UiProviders> mounted by the host app (no nested providers here).
|
|
8
|
+
|
|
9
|
+
'use client';
|
|
10
|
+
|
|
11
|
+
import * as React from 'react';
|
|
12
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
13
|
+
import { useThemeColor, alpha } from '@djangocfg/ui-core/styles/palette';
|
|
14
|
+
import type { SparklineDatum, SparklineProps } from './types';
|
|
15
|
+
|
|
16
|
+
function toValue(d: SparklineDatum): number {
|
|
17
|
+
return typeof d === 'number' ? d : d.value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Stats {
|
|
21
|
+
values: number[];
|
|
22
|
+
min: number;
|
|
23
|
+
max: number;
|
|
24
|
+
range: number;
|
|
25
|
+
avg: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function computeStats(data: SparklineDatum[]): Stats {
|
|
29
|
+
const values = data.map(toValue);
|
|
30
|
+
const max = Math.max(...values, 1);
|
|
31
|
+
const min = Math.min(...values);
|
|
32
|
+
const range = max - min || 1;
|
|
33
|
+
const total = values.reduce((s, v) => s + v, 0);
|
|
34
|
+
const avg = values.length > 0 ? total / values.length : 0;
|
|
35
|
+
return { values, min, max, range, avg };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildLinePath(
|
|
39
|
+
values: number[],
|
|
40
|
+
width: number,
|
|
41
|
+
height: number,
|
|
42
|
+
padding: number,
|
|
43
|
+
min: number,
|
|
44
|
+
range: number,
|
|
45
|
+
): string {
|
|
46
|
+
if (values.length === 0) return '';
|
|
47
|
+
const drawHeight = height - padding * 2;
|
|
48
|
+
const drawWidth = width - padding * 2;
|
|
49
|
+
const denom = values.length - 1 || 1;
|
|
50
|
+
|
|
51
|
+
return values
|
|
52
|
+
.map((v, i) => {
|
|
53
|
+
const x = padding + (i / denom) * drawWidth;
|
|
54
|
+
const y = padding + drawHeight - ((v - min) / range) * drawHeight;
|
|
55
|
+
return `${i === 0 ? 'M' : 'L'} ${x.toFixed(2)} ${y.toFixed(2)}`;
|
|
56
|
+
})
|
|
57
|
+
.join(' ');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildAreaPath(
|
|
61
|
+
linePath: string,
|
|
62
|
+
width: number,
|
|
63
|
+
height: number,
|
|
64
|
+
padding: number,
|
|
65
|
+
): string {
|
|
66
|
+
if (!linePath) return '';
|
|
67
|
+
const lastX = width - padding;
|
|
68
|
+
const firstX = padding;
|
|
69
|
+
const bottom = height - padding;
|
|
70
|
+
return `${linePath} L ${lastX.toFixed(2)} ${bottom.toFixed(2)} L ${firstX.toFixed(2)} ${bottom.toFixed(2)} Z`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getEndpoint(
|
|
74
|
+
values: number[],
|
|
75
|
+
width: number,
|
|
76
|
+
height: number,
|
|
77
|
+
padding: number,
|
|
78
|
+
min: number,
|
|
79
|
+
range: number,
|
|
80
|
+
): { x: number; y: number } | null {
|
|
81
|
+
if (values.length === 0) return null;
|
|
82
|
+
const drawHeight = height - padding * 2;
|
|
83
|
+
const lastV = values[values.length - 1];
|
|
84
|
+
return {
|
|
85
|
+
x: width - padding,
|
|
86
|
+
y: padding + drawHeight - ((lastV - min) / range) * drawHeight,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getBaselineY(
|
|
91
|
+
avg: number,
|
|
92
|
+
height: number,
|
|
93
|
+
padding: number,
|
|
94
|
+
min: number,
|
|
95
|
+
range: number,
|
|
96
|
+
): number {
|
|
97
|
+
const drawHeight = height - padding * 2;
|
|
98
|
+
return padding + drawHeight - ((avg - min) / range) * drawHeight;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function Sparkline({
|
|
102
|
+
data,
|
|
103
|
+
variant = 'line',
|
|
104
|
+
color = 'primary',
|
|
105
|
+
width = 120,
|
|
106
|
+
height = 32,
|
|
107
|
+
strokeWidth = 1.5,
|
|
108
|
+
showEndpoint = true,
|
|
109
|
+
showBaseline = false,
|
|
110
|
+
ariaLabel,
|
|
111
|
+
className,
|
|
112
|
+
...rest
|
|
113
|
+
}: SparklineProps) {
|
|
114
|
+
const stroke = useThemeColor(color);
|
|
115
|
+
const fill = alpha(stroke, 0.15);
|
|
116
|
+
const baselineStroke = alpha(stroke, 0.4);
|
|
117
|
+
|
|
118
|
+
if (data.length === 0) return null;
|
|
119
|
+
|
|
120
|
+
const stats = computeStats(data);
|
|
121
|
+
const padding = variant === 'bar' ? 2 : 2 + strokeWidth;
|
|
122
|
+
const linePath =
|
|
123
|
+
variant !== 'bar'
|
|
124
|
+
? buildLinePath(stats.values, width, height, padding, stats.min, stats.range)
|
|
125
|
+
: '';
|
|
126
|
+
const areaPath = variant === 'area' ? buildAreaPath(linePath, width, height, padding) : '';
|
|
127
|
+
const endpoint =
|
|
128
|
+
showEndpoint && variant !== 'bar'
|
|
129
|
+
? getEndpoint(stats.values, width, height, padding, stats.min, stats.range)
|
|
130
|
+
: null;
|
|
131
|
+
const baselineY = showBaseline
|
|
132
|
+
? getBaselineY(stats.avg, height, padding, stats.min, stats.range)
|
|
133
|
+
: null;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div
|
|
137
|
+
data-slot="sparkline"
|
|
138
|
+
data-variant={variant}
|
|
139
|
+
data-color={color}
|
|
140
|
+
className={cn('inline-flex items-center', className)}
|
|
141
|
+
{...rest}
|
|
142
|
+
>
|
|
143
|
+
<svg
|
|
144
|
+
viewBox={`0 0 ${width} ${height}`}
|
|
145
|
+
width={width}
|
|
146
|
+
height={height}
|
|
147
|
+
fill="none"
|
|
148
|
+
role="img"
|
|
149
|
+
aria-label={ariaLabel}
|
|
150
|
+
aria-hidden={ariaLabel ? undefined : true}
|
|
151
|
+
className="block"
|
|
152
|
+
>
|
|
153
|
+
{baselineY != null && (
|
|
154
|
+
<line
|
|
155
|
+
x1={padding}
|
|
156
|
+
y1={baselineY}
|
|
157
|
+
x2={width - padding}
|
|
158
|
+
y2={baselineY}
|
|
159
|
+
stroke={baselineStroke}
|
|
160
|
+
strokeWidth={0.75}
|
|
161
|
+
strokeDasharray="3 3"
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{variant === 'bar' && (() => {
|
|
166
|
+
const drawHeight = height - padding * 2;
|
|
167
|
+
const drawWidth = width - padding * 2;
|
|
168
|
+
const gap = 1;
|
|
169
|
+
const barWidth = Math.max(
|
|
170
|
+
1,
|
|
171
|
+
(drawWidth - gap * (stats.values.length - 1)) / stats.values.length,
|
|
172
|
+
);
|
|
173
|
+
const denom = stats.values.length - 1 || 1;
|
|
174
|
+
return stats.values.map((v, i) => {
|
|
175
|
+
const barHeight = Math.max(1, ((v - stats.min) / stats.range) * drawHeight);
|
|
176
|
+
const x = padding + i * (barWidth + gap);
|
|
177
|
+
const y = padding + drawHeight - barHeight;
|
|
178
|
+
return (
|
|
179
|
+
<rect
|
|
180
|
+
key={i}
|
|
181
|
+
x={x}
|
|
182
|
+
y={y}
|
|
183
|
+
width={barWidth}
|
|
184
|
+
height={barHeight}
|
|
185
|
+
rx={Math.min(barWidth / 2, 1)}
|
|
186
|
+
fill={stroke}
|
|
187
|
+
opacity={0.7 + 0.3 * (i / denom)}
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
})()}
|
|
192
|
+
|
|
193
|
+
{variant === 'area' && areaPath && <path d={areaPath} fill={fill} />}
|
|
194
|
+
|
|
195
|
+
{variant !== 'bar' && linePath && (
|
|
196
|
+
<path
|
|
197
|
+
d={linePath}
|
|
198
|
+
stroke={stroke}
|
|
199
|
+
strokeWidth={strokeWidth}
|
|
200
|
+
strokeLinecap="round"
|
|
201
|
+
strokeLinejoin="round"
|
|
202
|
+
fill="none"
|
|
203
|
+
/>
|
|
204
|
+
)}
|
|
205
|
+
|
|
206
|
+
{endpoint && (
|
|
207
|
+
<circle
|
|
208
|
+
cx={endpoint.x}
|
|
209
|
+
cy={endpoint.y}
|
|
210
|
+
r={strokeWidth + 0.5}
|
|
211
|
+
fill={stroke}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
214
|
+
</svg>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import { createLazyComponent } from '../../../../common/lazy-wrapper';
|
|
6
|
+
import type { SparklineProps } from './types';
|
|
7
|
+
|
|
8
|
+
export const LazySparkline = createLazyComponent<SparklineProps>(
|
|
9
|
+
() => import('./Sparkline').then((mod) => ({ default: mod.Sparkline })),
|
|
10
|
+
{
|
|
11
|
+
displayName: 'LazySparkline',
|
|
12
|
+
fallback: (
|
|
13
|
+
<span
|
|
14
|
+
data-slot="sparkline-skeleton"
|
|
15
|
+
className="inline-block h-8 w-[120px] animate-pulse rounded bg-muted"
|
|
16
|
+
/>
|
|
17
|
+
),
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export type {
|
|
22
|
+
SparklineProps,
|
|
23
|
+
SparklineColor,
|
|
24
|
+
SparklineVariant,
|
|
25
|
+
SparklineDatum,
|
|
26
|
+
} from './types';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
|
+
|
|
3
|
+
import type * as React from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Semantic color token name used for the sparkline stroke / area fill.
|
|
7
|
+
*
|
|
8
|
+
* Resolves at runtime via `useThemeColor(color)` from
|
|
9
|
+
* `@djangocfg/ui-core/styles/palette`, so it follows the active theme preset
|
|
10
|
+
* — never reference raw Tailwind scales (`emerald-500`, `sky-400`, …).
|
|
11
|
+
*/
|
|
12
|
+
export type SparklineColor =
|
|
13
|
+
| 'primary'
|
|
14
|
+
| 'success'
|
|
15
|
+
| 'warning'
|
|
16
|
+
| 'destructive'
|
|
17
|
+
| 'info';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sparkline chart variant.
|
|
21
|
+
*
|
|
22
|
+
* - `line` — just the stroke.
|
|
23
|
+
* - `area` — stroke + alpha-filled area underneath.
|
|
24
|
+
* - `bar` — discrete bars per data point.
|
|
25
|
+
*/
|
|
26
|
+
export type SparklineVariant = 'line' | 'area' | 'bar';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A datum is either a bare number or an `{ value, label? }` object. The
|
|
30
|
+
* optional `label` is used for `aria-label` enrichment only — it never renders
|
|
31
|
+
* inside the SVG.
|
|
32
|
+
*/
|
|
33
|
+
export type SparklineDatum = number | { value: number; label?: string };
|
|
34
|
+
|
|
35
|
+
export interface SparklineProps
|
|
36
|
+
extends Omit<React.ComponentProps<'div'>, 'children'> {
|
|
37
|
+
/** Data points. Empty array renders nothing. */
|
|
38
|
+
data: SparklineDatum[];
|
|
39
|
+
/** Sparkline chart variant. @default 'line' */
|
|
40
|
+
variant?: SparklineVariant;
|
|
41
|
+
/**
|
|
42
|
+
* Semantic color token. Resolved via `useThemeColor()` — respects the
|
|
43
|
+
* active theme preset. @default 'primary'
|
|
44
|
+
*/
|
|
45
|
+
color?: SparklineColor;
|
|
46
|
+
/** SVG width in pixels. @default 120 */
|
|
47
|
+
width?: number;
|
|
48
|
+
/** SVG height in pixels. @default 32 */
|
|
49
|
+
height?: number;
|
|
50
|
+
/** Line stroke width (ignored for `bar`). @default 1.5 */
|
|
51
|
+
strokeWidth?: number;
|
|
52
|
+
/** Show a filled dot at the latest data point. @default true */
|
|
53
|
+
showEndpoint?: boolean;
|
|
54
|
+
/** Show a faint dashed line at the average value. @default false */
|
|
55
|
+
showBaseline?: boolean;
|
|
56
|
+
/** Optional accessible label for the chart. */
|
|
57
|
+
ariaLabel?: string;
|
|
58
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
|
+
//
|
|
3
|
+
// Grid of color swatches with click-to-copy in hex / rgb / hsl.
|
|
4
|
+
// Pulls colors from one of three sources via `mode`:
|
|
5
|
+
// - 'theme' → useThemePalette() (semantic tokens of the active theme)
|
|
6
|
+
// - 'preset' → useStylePresets() (status preset families)
|
|
7
|
+
// - 'custom' → caller-provided `colors` array
|
|
8
|
+
//
|
|
9
|
+
// Chrome (frame, value label) uses semantic Tailwind tokens; the swatch
|
|
10
|
+
// background uses inline `style={{ backgroundColor: hex }}` so it stays
|
|
11
|
+
// honest regardless of the active theme. See CONTRACT.md §2/§3.
|
|
12
|
+
|
|
13
|
+
'use client';
|
|
14
|
+
|
|
15
|
+
import * as React from 'react';
|
|
16
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
17
|
+
import { useThemePalette, useStylePresets } from '@djangocfg/ui-core/styles/palette';
|
|
18
|
+
import { Swatch } from './components/Swatch';
|
|
19
|
+
import type { ColorEntry, ColorPaletteMode, ColorPaletteProps } from './types';
|
|
20
|
+
|
|
21
|
+
function resolveMode(
|
|
22
|
+
mode: ColorPaletteMode | undefined,
|
|
23
|
+
colors: ColorEntry[] | undefined,
|
|
24
|
+
): ColorPaletteMode {
|
|
25
|
+
if (mode) return mode;
|
|
26
|
+
return colors && colors.length > 0 ? 'custom' : 'theme';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build the swatch list for `mode === 'theme'` — semantic tokens of the
|
|
31
|
+
* current theme. Includes the foreground/background pair plus brand and
|
|
32
|
+
* status families so a docs page can show "what does this preset look
|
|
33
|
+
* like" at a glance.
|
|
34
|
+
*/
|
|
35
|
+
function useThemeEntries(): ColorEntry[] {
|
|
36
|
+
const palette = useThemePalette();
|
|
37
|
+
return React.useMemo<ColorEntry[]>(
|
|
38
|
+
() => [
|
|
39
|
+
{ name: 'Background', value: palette.background },
|
|
40
|
+
{ name: 'Foreground', value: palette.foreground },
|
|
41
|
+
{ name: 'Card', value: palette.card },
|
|
42
|
+
{ name: 'Muted', value: palette.muted },
|
|
43
|
+
{ name: 'Muted Foreground', value: palette.mutedForeground },
|
|
44
|
+
{ name: 'Border', value: palette.border },
|
|
45
|
+
{ name: 'Primary', value: palette.primary },
|
|
46
|
+
{ name: 'Secondary', value: palette.secondary },
|
|
47
|
+
{ name: 'Accent', value: palette.accent },
|
|
48
|
+
{ name: 'Destructive', value: palette.destructive },
|
|
49
|
+
{ name: 'Success', value: palette.success },
|
|
50
|
+
{ name: 'Warning', value: palette.warning },
|
|
51
|
+
{ name: 'Info', value: palette.info },
|
|
52
|
+
],
|
|
53
|
+
[palette],
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build the swatch list for `mode === 'preset'` — the status / brand
|
|
59
|
+
* families exposed by `useStylePresets()`. Renders the `fill` color
|
|
60
|
+
* for each preset; consumers needing stroke/foreground should query
|
|
61
|
+
* the preset hooks directly.
|
|
62
|
+
*/
|
|
63
|
+
function usePresetEntries(): ColorEntry[] {
|
|
64
|
+
const presets = useStylePresets();
|
|
65
|
+
return React.useMemo<ColorEntry[]>(
|
|
66
|
+
() => [
|
|
67
|
+
{ name: 'Primary', value: presets.primary.fill },
|
|
68
|
+
{ name: 'Success', value: presets.success.fill },
|
|
69
|
+
{ name: 'Warning', value: presets.warning.fill },
|
|
70
|
+
{ name: 'Danger', value: presets.danger.fill },
|
|
71
|
+
{ name: 'Info', value: presets.info.fill },
|
|
72
|
+
{ name: 'Chart 3', value: presets.chart3.fill },
|
|
73
|
+
{ name: 'Chart 4', value: presets.chart4.fill },
|
|
74
|
+
{ name: 'Chart 5', value: presets.chart5.fill },
|
|
75
|
+
],
|
|
76
|
+
[presets],
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function ColorPalette({
|
|
81
|
+
mode,
|
|
82
|
+
colors,
|
|
83
|
+
columns = 4,
|
|
84
|
+
copyFormat = 'hex',
|
|
85
|
+
showName = true,
|
|
86
|
+
showValue = true,
|
|
87
|
+
className,
|
|
88
|
+
style,
|
|
89
|
+
...rest
|
|
90
|
+
}: ColorPaletteProps) {
|
|
91
|
+
// Always call both hooks so React keeps a stable order regardless of `mode`.
|
|
92
|
+
const themeEntries = useThemeEntries();
|
|
93
|
+
const presetEntries = usePresetEntries();
|
|
94
|
+
|
|
95
|
+
const resolvedMode = resolveMode(mode, colors);
|
|
96
|
+
const entries = React.useMemo<ColorEntry[]>(() => {
|
|
97
|
+
if (resolvedMode === 'theme') return themeEntries;
|
|
98
|
+
if (resolvedMode === 'preset') return presetEntries;
|
|
99
|
+
return colors ?? [];
|
|
100
|
+
}, [resolvedMode, themeEntries, presetEntries, colors]);
|
|
101
|
+
|
|
102
|
+
const gridStyle = React.useMemo<React.CSSProperties>(
|
|
103
|
+
() => ({
|
|
104
|
+
...style,
|
|
105
|
+
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
|
106
|
+
}),
|
|
107
|
+
[style, columns],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div
|
|
112
|
+
data-slot="color-palette"
|
|
113
|
+
data-mode={resolvedMode}
|
|
114
|
+
className={cn('grid gap-3', className)}
|
|
115
|
+
style={gridStyle}
|
|
116
|
+
{...rest}
|
|
117
|
+
>
|
|
118
|
+
{entries.map((color) => (
|
|
119
|
+
<Swatch
|
|
120
|
+
key={`${color.name}-${color.value}`}
|
|
121
|
+
color={color}
|
|
122
|
+
copyFormat={copyFormat}
|
|
123
|
+
showName={showName}
|
|
124
|
+
showValue={showValue}
|
|
125
|
+
/>
|
|
126
|
+
))}
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# ColorPalette
|
|
2
|
+
|
|
3
|
+
Grid of color swatches with copy-on-click. Source can be the active theme, a status preset family, or a custom list.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { ColorPalette } from '@djangocfg/ui-tools/color-palette';
|
|
7
|
+
|
|
8
|
+
// Render the current theme tokens
|
|
9
|
+
<ColorPalette mode="theme" columns={4} />
|
|
10
|
+
|
|
11
|
+
// Custom swatches
|
|
12
|
+
<ColorPalette
|
|
13
|
+
mode="custom"
|
|
14
|
+
colors={[{ name: 'Brand', value: '#0989aa' }]}
|
|
15
|
+
copyFormat="hex"
|
|
16
|
+
/>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Default | Description |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| `mode` | `'theme' \| 'preset' \| 'custom'` | `'custom'` if `colors` provided, else `'theme'` | Where swatches come from. |
|
|
24
|
+
| `colors` | `ColorEntry[]` | — | Required when `mode === 'custom'`. |
|
|
25
|
+
| `columns` | `number` | `4` | Grid columns. |
|
|
26
|
+
| `copyFormat` | `'hex' \| 'rgb' \| 'hsl'` | `'hex'` | Format of the copied + rendered value. |
|
|
27
|
+
| `showName` | `boolean` | `true` | Show the color label. |
|
|
28
|
+
| `showValue` | `boolean` | `true` | Show the color value string. |
|
|
29
|
+
|
|
30
|
+
Storybook: `apps/storybook/stories/ui-tools/visual/ColorPalette.stories.tsx`
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
Adapted from jalcoui (MIT).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { Check, Copy } from 'lucide-react';
|
|
7
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
8
|
+
import { useCopyToClipboard } from '../hooks/useCopyToClipboard';
|
|
9
|
+
import { formatColor, getReadableContrast } from '../utils';
|
|
10
|
+
import type { ColorEntry, ColorFormat } from '../types';
|
|
11
|
+
|
|
12
|
+
export interface SwatchProps {
|
|
13
|
+
color: ColorEntry;
|
|
14
|
+
copyFormat: ColorFormat;
|
|
15
|
+
showName: boolean;
|
|
16
|
+
showValue: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Single color swatch — colored block on top, optional `name` +
|
|
21
|
+
* formatted `value` row below. Click anywhere copies the value in
|
|
22
|
+
* the configured {@link ColorFormat}.
|
|
23
|
+
*
|
|
24
|
+
* Background uses inline `style={{ backgroundColor }}` (hex) so the
|
|
25
|
+
* color is rendered verbatim — not via Tailwind's color tokens.
|
|
26
|
+
* Chrome (border, value text) flows through semantic tokens so the
|
|
27
|
+
* frame still respects the active theme preset.
|
|
28
|
+
*/
|
|
29
|
+
export function Swatch({ color, copyFormat, showName, showValue }: SwatchProps) {
|
|
30
|
+
const { copied, copy } = useCopyToClipboard();
|
|
31
|
+
const formattedValue = React.useMemo(
|
|
32
|
+
() => formatColor(color.value, copyFormat),
|
|
33
|
+
[color.value, copyFormat],
|
|
34
|
+
);
|
|
35
|
+
const contrast = React.useMemo(
|
|
36
|
+
() => getReadableContrast(color.value),
|
|
37
|
+
[color.value],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const handleCopy = React.useCallback(() => {
|
|
41
|
+
void copy(formattedValue);
|
|
42
|
+
}, [copy, formattedValue]);
|
|
43
|
+
|
|
44
|
+
const iconColor = contrast === 'light' ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.85)';
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={handleCopy}
|
|
50
|
+
data-slot="color-swatch"
|
|
51
|
+
data-copied={copied || undefined}
|
|
52
|
+
aria-label={`Copy ${color.name}: ${formattedValue}`}
|
|
53
|
+
title={color.description ?? `${color.name} — ${formattedValue}`}
|
|
54
|
+
className={cn(
|
|
55
|
+
'group relative flex flex-col overflow-hidden rounded-lg border border-border/60 bg-card text-left shadow-xs outline-none transition-colors',
|
|
56
|
+
'hover:border-border focus-visible:ring-[3px] focus-visible:ring-ring/50',
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
className="relative flex h-20 items-center justify-center sm:h-24"
|
|
61
|
+
style={{ backgroundColor: color.value }}
|
|
62
|
+
>
|
|
63
|
+
{/* Copy / Check icon — hex color resolved in JS, theme tokens skipped on purpose
|
|
64
|
+
because the swatch background is the user-supplied hex, not a token. */}
|
|
65
|
+
<span
|
|
66
|
+
className={cn(
|
|
67
|
+
'inline-flex items-center justify-center rounded-full p-1.5 transition-opacity',
|
|
68
|
+
copied ? 'opacity-100' : 'opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100',
|
|
69
|
+
)}
|
|
70
|
+
style={{ color: iconColor }}
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
>
|
|
73
|
+
{copied ? <Check className="size-4" /> : <Copy className="size-4" />}
|
|
74
|
+
</span>
|
|
75
|
+
|
|
76
|
+
{copied && (
|
|
77
|
+
<span
|
|
78
|
+
data-slot="color-swatch-toast"
|
|
79
|
+
className="absolute bottom-1.5 right-1.5 rounded-md bg-card/90 px-1.5 py-0.5 text-[10px] font-medium text-foreground shadow-sm backdrop-blur"
|
|
80
|
+
>
|
|
81
|
+
Copied!
|
|
82
|
+
</span>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{(showName || showValue) && (
|
|
87
|
+
<div className="flex flex-col gap-0.5 bg-card px-3 py-2">
|
|
88
|
+
{showName && (
|
|
89
|
+
<span className="truncate text-xs font-medium text-foreground">
|
|
90
|
+
{color.name}
|
|
91
|
+
</span>
|
|
92
|
+
)}
|
|
93
|
+
{showValue && (
|
|
94
|
+
<span className="truncate font-mono text-[11px] text-muted-foreground">
|
|
95
|
+
{formattedValue}
|
|
96
|
+
</span>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</button>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Copy a string to the clipboard and flash a `copied` flag for
|
|
9
|
+
* `resetMs` (default 1500ms). Safe to call on browsers without
|
|
10
|
+
* `navigator.clipboard` — returns silently.
|
|
11
|
+
*/
|
|
12
|
+
export function useCopyToClipboard(resetMs = 1500): {
|
|
13
|
+
copied: boolean;
|
|
14
|
+
copy: (text: string) => Promise<void>;
|
|
15
|
+
} {
|
|
16
|
+
const [copied, setCopied] = React.useState(false);
|
|
17
|
+
const timerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
18
|
+
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
return () => {
|
|
21
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
22
|
+
};
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
const copy = React.useCallback(
|
|
26
|
+
async (text: string) => {
|
|
27
|
+
try {
|
|
28
|
+
if (typeof navigator === 'undefined' || !navigator.clipboard) return;
|
|
29
|
+
await navigator.clipboard.writeText(text);
|
|
30
|
+
setCopied(true);
|
|
31
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
32
|
+
timerRef.current = setTimeout(() => setCopied(false), resetMs);
|
|
33
|
+
} catch {
|
|
34
|
+
// Clipboard API not available / permission denied — ignore.
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[resetMs],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return { copied, copy };
|
|
41
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
|
+
|
|
3
|
+
export { ColorPalette } from './ColorPalette';
|
|
4
|
+
export { Swatch as ColorPaletteSwatch } from './components/Swatch';
|
|
5
|
+
export { useCopyToClipboard } from './hooks/useCopyToClipboard';
|
|
6
|
+
export { formatColor, getReadableContrast } from './utils';
|
|
7
|
+
export type {
|
|
8
|
+
ColorEntry,
|
|
9
|
+
ColorFormat,
|
|
10
|
+
ColorPaletteMode,
|
|
11
|
+
ColorPaletteProps,
|
|
12
|
+
} from './types';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import { createLazyComponent } from '../../../../common/lazy-wrapper';
|
|
6
|
+
import type { ColorPaletteProps } from './types';
|
|
7
|
+
|
|
8
|
+
export const LazyColorPalette = createLazyComponent<ColorPaletteProps>(
|
|
9
|
+
() => import('./ColorPalette').then((m) => ({ default: m.ColorPalette })),
|
|
10
|
+
{
|
|
11
|
+
displayName: 'LazyColorPalette',
|
|
12
|
+
fallback: (
|
|
13
|
+
<div
|
|
14
|
+
data-slot="color-palette-skeleton"
|
|
15
|
+
className="grid h-32 animate-pulse gap-3 rounded-lg bg-muted"
|
|
16
|
+
/>
|
|
17
|
+
),
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export type { ColorPaletteProps } from './types';
|