@djangocfg/ui-tools 2.1.407 → 2.1.409
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/README.md +9 -10
- package/dist/file-icon/index.cjs +449 -61
- package/dist/file-icon/index.cjs.map +1 -1
- package/dist/file-icon/index.d.cts +56 -18
- package/dist/file-icon/index.d.ts +56 -18
- package/dist/file-icon/index.mjs +448 -62
- package/dist/file-icon/index.mjs.map +1 -1
- package/dist/tree/index.cjs +49 -22
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.d.cts +9 -3
- package/dist/tree/index.d.ts +9 -3
- package/dist/tree/index.mjs +49 -22
- package/dist/tree/index.mjs.map +1 -1
- package/dist/{types-B_zhyAqR.d.cts → types-eEu8SeiQ.d.cts} +4 -0
- package/dist/{types-B_zhyAqR.d.ts → types-eEu8SeiQ.d.ts} +4 -0
- package/package.json +8 -13
- package/src/components/FloatingToolbar/index.tsx +37 -3
- package/src/lib/page-snapshot/__tests__/capture-integration.test.ts +85 -0
- package/src/lib/page-snapshot/__tests__/engine.test.ts +36 -0
- package/src/lib/page-snapshot/__tests__/redaction-integration.test.ts +99 -0
- package/src/lib/page-snapshot/__tests__/tokens.test.ts +17 -0
- package/src/lib/page-snapshot/capture/__tests__/budget.test.ts +49 -0
- package/src/lib/page-snapshot/capture/__tests__/chrome-filter.test.ts +47 -0
- package/src/lib/page-snapshot/capture/__tests__/fold.test.ts +66 -0
- package/src/lib/page-snapshot/capture/__tests__/scope.test.ts +74 -0
- package/src/lib/page-snapshot/capture/__tests__/walk.test.ts +129 -0
- package/src/lib/page-snapshot/capture/accessible-name.ts +73 -0
- package/src/lib/page-snapshot/capture/budget.ts +95 -0
- package/src/lib/page-snapshot/capture/chrome-filter.ts +81 -0
- package/src/lib/page-snapshot/capture/classify.ts +111 -0
- package/src/lib/page-snapshot/capture/dom-utils.ts +111 -0
- package/src/lib/page-snapshot/capture/fold.ts +96 -0
- package/src/lib/page-snapshot/capture/scope.ts +169 -0
- package/src/lib/page-snapshot/capture/walk.ts +250 -0
- package/src/lib/page-snapshot/cst/__tests__/serialize.test.ts +50 -0
- package/src/lib/page-snapshot/cst/directives.ts +47 -0
- package/src/lib/page-snapshot/cst/payload.ts +50 -0
- package/src/lib/page-snapshot/cst/serialize.ts +84 -0
- package/src/lib/page-snapshot/cst/types.ts +115 -0
- package/src/lib/page-snapshot/engine.ts +176 -0
- package/src/lib/page-snapshot/index.ts +93 -0
- package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +72 -0
- package/src/lib/page-snapshot/react/PageSnapshotPreview.tsx +78 -0
- package/src/lib/page-snapshot/react/__tests__/PageSnapshotChip.test.tsx +54 -0
- package/src/lib/page-snapshot/react/__tests__/provider.test.tsx +103 -0
- package/src/lib/page-snapshot/react/__tests__/use-page-snapshot-toggle.test.tsx +62 -0
- package/src/lib/page-snapshot/react/provider.tsx +162 -0
- package/src/lib/page-snapshot/react/use-page-snapshot-toggle.ts +47 -0
- package/src/lib/page-snapshot/react/use-page-snapshot.ts +67 -0
- package/src/lib/page-snapshot/redaction/__tests__/audit.test.ts +25 -0
- package/src/lib/page-snapshot/redaction/__tests__/heuristics.test.ts +73 -0
- package/src/lib/page-snapshot/redaction/__tests__/luhn.test.ts +26 -0
- package/src/lib/page-snapshot/redaction/__tests__/patterns.test.ts +60 -0
- package/src/lib/page-snapshot/redaction/audit.ts +58 -0
- package/src/lib/page-snapshot/redaction/heuristics.ts +75 -0
- package/src/lib/page-snapshot/redaction/index.ts +75 -0
- package/src/lib/page-snapshot/redaction/luhn.ts +25 -0
- package/src/lib/page-snapshot/redaction/patterns.ts +111 -0
- package/src/lib/page-snapshot/refs/__tests__/registry.test.ts +24 -0
- package/src/lib/page-snapshot/refs/registry.ts +46 -0
- package/src/lib/page-snapshot/staleness/__tests__/hash.test.ts +34 -0
- package/src/lib/page-snapshot/staleness/hash.ts +20 -0
- package/src/lib/page-snapshot/tokens.ts +15 -0
- package/src/tools/AudioPlayer/context/PlayerProvider.tsx +13 -14
- package/src/tools/AudioPlayer/hooks/useAudioElementEvents.ts +55 -6
- package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
- package/src/tools/Chat/README.md +277 -39
- package/src/tools/Chat/composer/Composer.tsx +471 -0
- package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
- package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
- package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
- package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
- package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
- package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
- package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
- package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
- package/src/tools/Chat/composer/index.ts +45 -0
- package/src/tools/Chat/composer/size-context.tsx +26 -0
- package/src/tools/Chat/composer/types.ts +143 -0
- package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
- package/src/tools/Chat/context/ChatProvider.tsx +54 -3
- package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
- package/src/tools/Chat/core/index.ts +23 -1
- package/src/tools/Chat/core/markdown.ts +1 -1
- package/src/tools/Chat/core/metadata.ts +47 -0
- package/src/tools/Chat/core/payload-dispatch.ts +1 -1
- package/src/tools/Chat/core/transport/http.ts +71 -32
- package/src/tools/Chat/core/transport/sse.ts +18 -10
- package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
- package/src/tools/Chat/highlight/README.md +103 -0
- package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
- package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
- package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
- package/src/tools/Chat/highlight/index.ts +21 -0
- package/src/tools/Chat/highlight/resolveRef.ts +42 -0
- package/src/tools/Chat/highlight/types.ts +49 -0
- package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
- package/src/tools/Chat/hooks/index.ts +0 -5
- package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
- package/src/tools/Chat/hooks/useChat.ts +47 -14
- package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
- package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
- package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
- package/src/tools/Chat/index.ts +25 -219
- package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
- package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
- package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
- package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
- package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
- package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
- package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
- package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
- package/src/tools/Chat/launcher/header/index.ts +26 -0
- package/src/tools/Chat/launcher/index.ts +3 -10
- package/src/tools/Chat/lazy.tsx +38 -284
- package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
- package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
- package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
- package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
- package/src/tools/Chat/messages/blocks/index.ts +12 -0
- package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
- package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
- package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
- package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
- package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
- package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
- package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
- package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
- package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
- package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
- package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
- package/src/tools/Chat/{components → messages}/index.ts +11 -5
- package/src/tools/Chat/public.ts +212 -0
- package/src/tools/Chat/shell/ChatRoot.tsx +345 -0
- package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
- package/src/tools/Chat/shell/index.ts +15 -0
- package/src/tools/Chat/types/block.ts +120 -0
- package/src/tools/Chat/types/config.ts +0 -5
- package/src/tools/Chat/types/index.ts +17 -0
- package/src/tools/Chat/types/message.ts +3 -0
- package/src/tools/Chat/utils/index.ts +4 -0
- package/src/tools/CodeEditor/README.md +4 -6
- package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
- package/src/tools/CodeEditor/components/Editor.tsx +96 -44
- package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
- package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
- package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
- package/src/tools/CodeEditor/lazy.tsx +6 -0
- package/src/tools/CodeEditor/lib/index.ts +1 -1
- package/src/tools/CodeEditor/lib/themes.ts +3 -39
- package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
- package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
- package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
- package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
- package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
- package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
- package/src/tools/CronScheduler/index.tsx +1 -1
- package/src/tools/CronScheduler/types/index.ts +8 -3
- package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
- package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
- package/src/tools/FileIcon/FileIcon.tsx +24 -39
- package/src/tools/FileIcon/get-file-icon.ts +73 -0
- package/src/tools/FileIcon/icons/icon-data.ts +399 -0
- package/src/tools/FileIcon/index.ts +4 -0
- package/src/tools/FileIcon/loader.ts +17 -35
- package/src/tools/FileIcon/specialFolders.ts +18 -0
- package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
- package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
- package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
- package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
- package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
- package/src/tools/ImageViewer/components/ImageViewer.tsx +96 -24
- package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
- package/src/tools/ImageViewer/utils/constants.ts +3 -0
- package/src/tools/ImageViewer/utils/index.ts +1 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
- package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
- package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
- package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
- package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
- package/src/tools/JsonForm/widgets/index.ts +1 -0
- package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
- package/src/tools/LottiePlayer/index.tsx +14 -4
- package/src/tools/LottiePlayer/lazy.tsx +11 -3
- package/src/tools/LottiePlayer/types.ts +31 -1
- package/src/tools/LottiePlayer/useLottie.ts +32 -9
- package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
- package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
- package/src/tools/Map/components/MapCluster.tsx +28 -21
- package/src/tools/Map/components/MapContainer.tsx +11 -4
- package/src/tools/Map/components/MapLegend.tsx +46 -15
- package/src/tools/Map/components/MapMarker.tsx +31 -2
- package/src/tools/Map/hooks/useMapEvents.ts +64 -105
- package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
- package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
- package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
- package/src/tools/MarkdownEditor/lazy.tsx +32 -7
- package/src/tools/MarkdownEditor/styles.css +13 -0
- package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
- package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
- package/src/tools/MarkdownMessage/components.tsx +22 -9
- package/src/tools/MarkdownMessage/types.ts +24 -1
- package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
- package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
- package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
- package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
- package/src/tools/Mermaid/index.tsx +6 -0
- package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
- package/src/tools/OpenapiViewer/constants.ts +3 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
- package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
- package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
- package/src/tools/PrettyCode/lazy.tsx +1 -1
- package/src/tools/SpeechRecognition/README.md +1 -1
- package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
- package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
- package/src/tools/SpeechRecognition/components/index.ts +2 -0
- package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
- package/src/tools/SpeechRecognition/core/language.ts +23 -6
- package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
- package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
- package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
- package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
- package/src/tools/Tree/README.md +4 -8
- package/src/tools/Tree/TreeRoot.tsx +22 -10
- package/src/tools/Tree/components/TreeContent.tsx +24 -4
- package/src/tools/Tree/components/TreeLabel.tsx +8 -2
- package/src/tools/Tree/components/TreeRow.tsx +16 -6
- package/src/tools/Tree/data/flatten.ts +10 -4
- package/src/tools/Tree/types.ts +4 -0
- package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
- package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
- package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
- package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
- package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
- package/src/tools/Uploader/utils/formatters.ts +8 -3
- package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +1 -0
- package/src/tools/VideoPlayer/canvas/{jsx.d.ts → jsx-augmentation.ts} +12 -19
- package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +1 -0
- package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +1 -0
- package/src/tools/VideoPlayer/parts/fullscreen.tsx +1 -1
- package/src/tools/VideoPlayer/parts/pip.tsx +1 -1
- package/src/tools/VideoPlayer/parts/playback-rate.tsx +1 -1
- package/src/tools/VideoPlayer/parts/seek-bar.tsx +2 -2
- package/src/tools/VideoPlayer/parts/volume.tsx +2 -2
- package/src/tools/index.ts +2 -1
- package/src/tools/Chat/components/AudioToggle.tsx +0 -78
- package/src/tools/Chat/components/ChatRoot.tsx +0 -305
- package/src/tools/Chat/components/Composer.tsx +0 -216
- package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
- package/src/tools/Chat/types.ts +0 -9
- package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
- package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
- package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
- package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
- package/src/tools/Tour/README.md +0 -373
- package/src/tools/Tour/components/Tour.tsx +0 -12
- package/src/tools/Tour/components/TourContent.tsx +0 -171
- package/src/tools/Tour/components/TourNavigation.tsx +0 -77
- package/src/tools/Tour/components/TourProgress.tsx +0 -88
- package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
- package/src/tools/Tour/components/index.ts +0 -5
- package/src/tools/Tour/context/TourContext.ts +0 -19
- package/src/tools/Tour/context/TourProvider.tsx +0 -292
- package/src/tools/Tour/context/index.ts +0 -2
- package/src/tools/Tour/hooks/index.ts +0 -3
- package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
- package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
- package/src/tools/Tour/hooks/useTour.ts +0 -42
- package/src/tools/Tour/index.ts +0 -38
- package/src/tools/Tour/types/index.ts +0 -224
- package/src/tools/Tour/utils/dom.ts +0 -98
- package/src/tools/Tour/utils/index.ts +0 -3
- package/src/tools/Tour/utils/logger.ts +0 -3
- package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
- /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
- /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
- /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
- /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
|
@@ -3,21 +3,24 @@
|
|
|
3
3
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
4
4
|
|
|
5
5
|
import { Input } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
7
|
import { useAppT } from '@djangocfg/i18n';
|
|
7
8
|
import { WidgetProps } from '@rjsf/utils';
|
|
8
9
|
|
|
10
|
+
import { useWidgetEnv } from './_useWidgetEnv';
|
|
11
|
+
|
|
9
12
|
/**
|
|
10
|
-
* Color input widget for JSON Schema Form
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* Color input widget for JSON Schema Form.
|
|
14
|
+
*
|
|
15
|
+
* Supports HSL format (h s% l%) and HEX format. Click on the color box
|
|
16
|
+
* to open the native color picker directly. Honors `density` and
|
|
17
|
+
* `ui:disabledWhen` via `useWidgetEnv`.
|
|
13
18
|
*/
|
|
14
19
|
export function ColorWidget(props: WidgetProps) {
|
|
15
20
|
const {
|
|
16
21
|
id,
|
|
17
22
|
placeholder,
|
|
18
23
|
required,
|
|
19
|
-
disabled,
|
|
20
|
-
readonly,
|
|
21
24
|
autofocus,
|
|
22
25
|
value,
|
|
23
26
|
onChange,
|
|
@@ -26,6 +29,7 @@ export function ColorWidget(props: WidgetProps) {
|
|
|
26
29
|
options,
|
|
27
30
|
rawErrors,
|
|
28
31
|
} = props;
|
|
32
|
+
const { disabled, compact, tooltipText } = useWidgetEnv(props);
|
|
29
33
|
|
|
30
34
|
const t = useAppT();
|
|
31
35
|
const pickColorLabel = useMemo(() => t('tools.color.pick'), [t]);
|
|
@@ -173,10 +177,10 @@ export function ColorWidget(props: WidgetProps) {
|
|
|
173
177
|
|
|
174
178
|
// Click on color box opens native color picker
|
|
175
179
|
const handleColorBoxClick = useCallback(() => {
|
|
176
|
-
if (!disabled
|
|
180
|
+
if (!disabled) {
|
|
177
181
|
colorInputRef.current?.click();
|
|
178
182
|
}
|
|
179
|
-
}, [disabled
|
|
183
|
+
}, [disabled]);
|
|
180
184
|
|
|
181
185
|
return (
|
|
182
186
|
<div className="flex items-center gap-2">
|
|
@@ -185,8 +189,11 @@ export function ColorWidget(props: WidgetProps) {
|
|
|
185
189
|
<button
|
|
186
190
|
type="button"
|
|
187
191
|
onClick={handleColorBoxClick}
|
|
188
|
-
disabled={disabled
|
|
189
|
-
className=
|
|
192
|
+
disabled={disabled}
|
|
193
|
+
className={cn(
|
|
194
|
+
'shrink-0 rounded-md border-2 border-input cursor-pointer hover:border-ring focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
195
|
+
compact ? 'h-7 w-7' : 'h-10 w-10',
|
|
196
|
+
)}
|
|
190
197
|
style={{ backgroundColor: previewColor }}
|
|
191
198
|
aria-label={pickColorLabel}
|
|
192
199
|
/>
|
|
@@ -197,7 +204,7 @@ export function ColorWidget(props: WidgetProps) {
|
|
|
197
204
|
value={hexValue}
|
|
198
205
|
onChange={handleColorPickerChange}
|
|
199
206
|
className="absolute inset-0 opacity-0 w-full h-full cursor-pointer"
|
|
200
|
-
disabled={disabled
|
|
207
|
+
disabled={disabled}
|
|
201
208
|
tabIndex={-1}
|
|
202
209
|
/>
|
|
203
210
|
</div>
|
|
@@ -208,14 +215,15 @@ export function ColorWidget(props: WidgetProps) {
|
|
|
208
215
|
type="text"
|
|
209
216
|
placeholder={placeholder || (format === 'hsl' ? '217 91% 60%' : '#3b82f6')}
|
|
210
217
|
disabled={disabled}
|
|
211
|
-
readOnly={readonly}
|
|
212
218
|
autoFocus={autofocus}
|
|
213
219
|
value={safeValue}
|
|
214
220
|
required={required}
|
|
215
221
|
onChange={handleChange}
|
|
216
222
|
onBlur={handleBlur}
|
|
217
223
|
onFocus={handleFocus}
|
|
218
|
-
|
|
224
|
+
title={tooltipText}
|
|
225
|
+
aria-invalid={hasError || undefined}
|
|
226
|
+
className={cn('flex-1 font-mono text-sm', hasError && 'border-destructive', compact && 'h-7 text-xs')}
|
|
219
227
|
/>
|
|
220
228
|
</div>
|
|
221
229
|
);
|
|
@@ -3,19 +3,21 @@
|
|
|
3
3
|
import React, { useCallback, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { Input } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
7
|
import { WidgetProps } from '@rjsf/utils';
|
|
7
8
|
|
|
9
|
+
import { useWidgetEnv } from './_useWidgetEnv';
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
|
-
* Number input widget for JSON Schema Form
|
|
10
|
-
* Handles integer and number fields
|
|
12
|
+
* Number input widget for JSON Schema Form.
|
|
13
|
+
* Handles `integer` and `number` fields. Honors `density` and
|
|
14
|
+
* `ui:disabledWhen` via `useWidgetEnv`.
|
|
11
15
|
*/
|
|
12
16
|
export function NumberWidget(props: WidgetProps) {
|
|
13
17
|
const {
|
|
14
18
|
id,
|
|
15
19
|
placeholder,
|
|
16
20
|
required,
|
|
17
|
-
disabled,
|
|
18
|
-
readonly,
|
|
19
21
|
autofocus,
|
|
20
22
|
value,
|
|
21
23
|
onChange,
|
|
@@ -25,11 +27,12 @@ export function NumberWidget(props: WidgetProps) {
|
|
|
25
27
|
schema,
|
|
26
28
|
rawErrors,
|
|
27
29
|
} = props;
|
|
30
|
+
const { disabled, compact, tooltipText } = useWidgetEnv(props);
|
|
28
31
|
|
|
29
32
|
// Memoize widget configuration
|
|
30
33
|
const config = useMemo(() => ({
|
|
31
34
|
isInteger: schema.type === 'integer',
|
|
32
|
-
step: schema.type === 'integer' ?
|
|
35
|
+
step: schema.multipleOf ?? (schema.type === 'integer' ? 1 : 'any'),
|
|
33
36
|
min: schema.minimum,
|
|
34
37
|
max: schema.maximum,
|
|
35
38
|
emptyValue: options?.emptyValue,
|
|
@@ -39,11 +42,12 @@ export function NumberWidget(props: WidgetProps) {
|
|
|
39
42
|
const safeValue = useMemo(() => {
|
|
40
43
|
if (value === null || value === undefined || value === '') return '';
|
|
41
44
|
if (typeof value === 'number' && !isNaN(value)) return value;
|
|
42
|
-
|
|
45
|
+
const coerced = Number(value);
|
|
46
|
+
return isNaN(coerced) ? '' : coerced;
|
|
43
47
|
}, [value]);
|
|
44
48
|
|
|
45
49
|
const hasError = useMemo(() => {
|
|
46
|
-
return rawErrors && rawErrors.length > 0;
|
|
50
|
+
return Boolean(rawErrors && rawErrors.length > 0);
|
|
47
51
|
}, [rawErrors]);
|
|
48
52
|
|
|
49
53
|
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
@@ -73,7 +77,6 @@ export function NumberWidget(props: WidgetProps) {
|
|
|
73
77
|
type="number"
|
|
74
78
|
placeholder={placeholder}
|
|
75
79
|
disabled={disabled}
|
|
76
|
-
readOnly={readonly}
|
|
77
80
|
autoFocus={autofocus}
|
|
78
81
|
value={safeValue}
|
|
79
82
|
required={required}
|
|
@@ -83,7 +86,9 @@ export function NumberWidget(props: WidgetProps) {
|
|
|
83
86
|
step={config.step}
|
|
84
87
|
min={config.min}
|
|
85
88
|
max={config.max}
|
|
86
|
-
|
|
89
|
+
title={tooltipText}
|
|
90
|
+
aria-invalid={hasError || undefined}
|
|
91
|
+
className={cn(hasError && 'border-destructive', compact && 'h-7 text-xs')}
|
|
87
92
|
/>
|
|
88
93
|
);
|
|
89
94
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Label, RadioGroup, RadioGroupItem } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
7
|
+
import { WidgetProps } from '@rjsf/utils';
|
|
8
|
+
|
|
9
|
+
import { useWidgetEnv } from './_useWidgetEnv';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Radio group widget for JSON Schema Form.
|
|
13
|
+
*
|
|
14
|
+
* Renders `enum` fields as a vertical list of radio buttons — an
|
|
15
|
+
* alternative to `SelectWidget` for small option sets. Opt in via
|
|
16
|
+
* `uiSchema`:
|
|
17
|
+
*
|
|
18
|
+
* { plan: { 'ui:widget': 'radio' } }
|
|
19
|
+
*
|
|
20
|
+
* Set `ui:options.inline = true` to lay the options out in a row.
|
|
21
|
+
* Honors `density` and `ui:disabledWhen` via `useWidgetEnv`.
|
|
22
|
+
*/
|
|
23
|
+
export function RadioWidget(props: WidgetProps) {
|
|
24
|
+
const { id, value, options, onChange, onBlur, onFocus } = props;
|
|
25
|
+
const { disabled, compact, tooltipText } = useWidgetEnv(props);
|
|
26
|
+
|
|
27
|
+
const enumOptions = useMemo(() => {
|
|
28
|
+
const opts = options?.enumOptions;
|
|
29
|
+
if (!Array.isArray(opts)) return [];
|
|
30
|
+
return opts.filter((opt) => opt && opt.value !== undefined);
|
|
31
|
+
}, [options]);
|
|
32
|
+
|
|
33
|
+
const inline = options?.inline === true;
|
|
34
|
+
|
|
35
|
+
const safeValue = useMemo(() => {
|
|
36
|
+
if (value === null || value === undefined) return '';
|
|
37
|
+
return String(value);
|
|
38
|
+
}, [value]);
|
|
39
|
+
|
|
40
|
+
const handleChange = useCallback((next: string) => {
|
|
41
|
+
onChange(next);
|
|
42
|
+
onBlur(id, next);
|
|
43
|
+
}, [onChange, onBlur, id]);
|
|
44
|
+
|
|
45
|
+
if (enumOptions.length === 0) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<RadioGroup
|
|
51
|
+
value={safeValue}
|
|
52
|
+
onValueChange={handleChange}
|
|
53
|
+
disabled={disabled}
|
|
54
|
+
onFocus={() => onFocus(id, value)}
|
|
55
|
+
title={tooltipText}
|
|
56
|
+
className={cn(
|
|
57
|
+
inline ? 'flex flex-row flex-wrap gap-x-4 gap-y-2' : 'grid gap-2',
|
|
58
|
+
compact && 'gap-1.5',
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
{enumOptions.map((option: { value: unknown; label?: string }) => {
|
|
62
|
+
const optValue = String(option.value);
|
|
63
|
+
const optId = `${id}-${optValue}`;
|
|
64
|
+
return (
|
|
65
|
+
<div key={optValue || '__empty__'} className="flex items-center gap-2">
|
|
66
|
+
<RadioGroupItem value={optValue} id={optId} disabled={disabled} />
|
|
67
|
+
<Label
|
|
68
|
+
htmlFor={optId}
|
|
69
|
+
className={cn('cursor-pointer font-normal', compact && 'text-xs')}
|
|
70
|
+
>
|
|
71
|
+
{option.label || optValue}
|
|
72
|
+
</Label>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
})}
|
|
76
|
+
</RadioGroup>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -66,6 +66,7 @@ export function SelectWidget(props: WidgetProps) {
|
|
|
66
66
|
disabled={disabled}
|
|
67
67
|
readOnly={disabled}
|
|
68
68
|
title={tooltipText}
|
|
69
|
+
aria-invalid={hasError || undefined}
|
|
69
70
|
className={`flex h-10 w-full rounded-md border ${
|
|
70
71
|
hasError ? 'border-destructive' : 'border-input'
|
|
71
72
|
} bg-transparent px-3 py-2 text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm`}
|
|
@@ -54,21 +54,24 @@ export function SliderWidget(props: WidgetProps) {
|
|
|
54
54
|
return { min, max, step, unit, showInput };
|
|
55
55
|
}, [schema, options]);
|
|
56
56
|
|
|
57
|
-
// Parse value - handle string values like "0.5rem"
|
|
57
|
+
// Parse value - handle string values like "0.5rem". The result is
|
|
58
|
+
// clamped to [min, max] so an out-of-range value never pushes the
|
|
59
|
+
// Radix thumb off the track.
|
|
58
60
|
const numericValue = useMemo(() => {
|
|
61
|
+
const clamp = (n: number) => Math.min(config.max, Math.max(config.min, n));
|
|
59
62
|
if (value === null || value === undefined || value === '') {
|
|
60
63
|
return config.min;
|
|
61
64
|
}
|
|
62
65
|
if (typeof value === 'number') {
|
|
63
|
-
return value;
|
|
66
|
+
return isNaN(value) ? config.min : clamp(value);
|
|
64
67
|
}
|
|
65
68
|
if (typeof value === 'string') {
|
|
66
69
|
// Extract number from string like "0.5rem"
|
|
67
70
|
const parsed = parseFloat(value);
|
|
68
|
-
return isNaN(parsed) ? config.min : parsed;
|
|
71
|
+
return isNaN(parsed) ? config.min : clamp(parsed);
|
|
69
72
|
}
|
|
70
73
|
return config.min;
|
|
71
|
-
}, [value, config.min]);
|
|
74
|
+
}, [value, config.min, config.max]);
|
|
72
75
|
|
|
73
76
|
const hasError = useMemo(() => {
|
|
74
77
|
return rawErrors && rawErrors.length > 0;
|
|
@@ -2,20 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useCallback, useMemo } from 'react';
|
|
4
4
|
|
|
5
|
-
import { Input } from '@djangocfg/ui-core/components';
|
|
5
|
+
import { Input, Textarea } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
7
|
import { WidgetProps } from '@rjsf/utils';
|
|
7
8
|
|
|
9
|
+
import { useWidgetEnv } from './_useWidgetEnv';
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
|
-
* Text input widget for JSON Schema Form
|
|
10
|
-
*
|
|
12
|
+
* Text input widget for JSON Schema Form.
|
|
13
|
+
*
|
|
14
|
+
* Handles `string` fields. Switches to a multiline `Textarea` when
|
|
15
|
+
* `options.widget === 'textarea'` (see `TextareaWidget`). Maps the
|
|
16
|
+
* schema `format` to a native input type (`email`, `url`, `tel`,
|
|
17
|
+
* `password`, `date`, `datetime-local`, `time`). Honors `density`
|
|
18
|
+
* and `ui:disabledWhen` via `useWidgetEnv`.
|
|
11
19
|
*/
|
|
20
|
+
|
|
21
|
+
/** Maps JSON Schema `format` values to native `<input type>` values. */
|
|
22
|
+
const FORMAT_INPUT_TYPE: Record<string, string> = {
|
|
23
|
+
email: 'email',
|
|
24
|
+
uri: 'url',
|
|
25
|
+
url: 'url',
|
|
26
|
+
tel: 'tel',
|
|
27
|
+
phone: 'tel',
|
|
28
|
+
password: 'password',
|
|
29
|
+
date: 'date',
|
|
30
|
+
'date-time': 'datetime-local',
|
|
31
|
+
datetime: 'datetime-local',
|
|
32
|
+
time: 'time',
|
|
33
|
+
};
|
|
34
|
+
|
|
12
35
|
export function TextWidget(props: WidgetProps) {
|
|
13
36
|
const {
|
|
14
37
|
id,
|
|
15
38
|
placeholder,
|
|
16
39
|
required,
|
|
17
|
-
disabled,
|
|
18
|
-
readonly,
|
|
19
40
|
autofocus,
|
|
20
41
|
value,
|
|
21
42
|
onChange,
|
|
@@ -25,13 +46,16 @@ export function TextWidget(props: WidgetProps) {
|
|
|
25
46
|
schema,
|
|
26
47
|
rawErrors,
|
|
27
48
|
} = props;
|
|
49
|
+
const { disabled, compact, tooltipText } = useWidgetEnv(props);
|
|
28
50
|
|
|
29
51
|
// Memoize widget configuration
|
|
30
52
|
const config = useMemo(() => ({
|
|
31
53
|
isTextarea: options?.widget === 'textarea',
|
|
32
|
-
rows: options?.rows
|
|
54
|
+
rows: typeof options?.rows === 'number' ? options.rows : 3,
|
|
33
55
|
emptyValue: options?.emptyValue,
|
|
34
|
-
|
|
56
|
+
inputType:
|
|
57
|
+
(typeof schema.format === 'string' && FORMAT_INPUT_TYPE[schema.format]) || 'text',
|
|
58
|
+
}), [options, schema.format]);
|
|
35
59
|
|
|
36
60
|
// Ensure value is always a string
|
|
37
61
|
const safeValue = useMemo(() => {
|
|
@@ -40,7 +64,7 @@ export function TextWidget(props: WidgetProps) {
|
|
|
40
64
|
}, [value]);
|
|
41
65
|
|
|
42
66
|
const hasError = useMemo(() => {
|
|
43
|
-
return rawErrors && rawErrors.length > 0;
|
|
67
|
+
return Boolean(rawErrors && rawErrors.length > 0);
|
|
44
68
|
}, [rawErrors]);
|
|
45
69
|
|
|
46
70
|
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
@@ -58,14 +82,10 @@ export function TextWidget(props: WidgetProps) {
|
|
|
58
82
|
|
|
59
83
|
if (config.isTextarea) {
|
|
60
84
|
return (
|
|
61
|
-
<
|
|
85
|
+
<Textarea
|
|
62
86
|
id={id}
|
|
63
|
-
className={`flex min-h-[80px] w-full rounded-md border ${
|
|
64
|
-
hasError ? 'border-destructive' : 'border-input'
|
|
65
|
-
} bg-transparent px-3 py-2 text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm`}
|
|
66
87
|
placeholder={placeholder}
|
|
67
|
-
disabled={disabled
|
|
68
|
-
readOnly={readonly}
|
|
88
|
+
disabled={disabled}
|
|
69
89
|
autoFocus={autofocus}
|
|
70
90
|
value={safeValue}
|
|
71
91
|
required={required}
|
|
@@ -73,6 +93,9 @@ export function TextWidget(props: WidgetProps) {
|
|
|
73
93
|
onBlur={handleBlur}
|
|
74
94
|
onFocus={handleFocus}
|
|
75
95
|
rows={config.rows}
|
|
96
|
+
title={tooltipText}
|
|
97
|
+
aria-invalid={hasError || undefined}
|
|
98
|
+
className={cn(hasError && 'border-destructive', compact && 'text-xs')}
|
|
76
99
|
/>
|
|
77
100
|
);
|
|
78
101
|
}
|
|
@@ -80,17 +103,18 @@ export function TextWidget(props: WidgetProps) {
|
|
|
80
103
|
return (
|
|
81
104
|
<Input
|
|
82
105
|
id={id}
|
|
83
|
-
type=
|
|
106
|
+
type={config.inputType}
|
|
84
107
|
placeholder={placeholder}
|
|
85
108
|
disabled={disabled}
|
|
86
|
-
readOnly={readonly}
|
|
87
109
|
autoFocus={autofocus}
|
|
88
110
|
value={safeValue}
|
|
89
111
|
required={required}
|
|
90
112
|
onChange={handleChange}
|
|
91
113
|
onBlur={handleBlur}
|
|
92
114
|
onFocus={handleFocus}
|
|
93
|
-
|
|
115
|
+
title={tooltipText}
|
|
116
|
+
aria-invalid={hasError || undefined}
|
|
117
|
+
className={cn(hasError && 'border-destructive', compact && 'h-7 text-xs')}
|
|
94
118
|
/>
|
|
95
119
|
);
|
|
96
120
|
}
|
|
@@ -10,6 +10,7 @@ export { TextareaWidget } from './TextareaWidget';
|
|
|
10
10
|
export { NumberWidget } from './NumberWidget';
|
|
11
11
|
export { CheckboxWidget } from './CheckboxWidget';
|
|
12
12
|
export { SelectWidget } from './SelectWidget';
|
|
13
|
+
export { RadioWidget } from './RadioWidget';
|
|
13
14
|
export { SwitchWidget } from './SwitchWidget';
|
|
14
15
|
export { ColorWidget } from './ColorWidget';
|
|
15
16
|
export { SliderWidget } from './SliderWidget';
|
|
@@ -2,33 +2,78 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { memo } from 'react';
|
|
4
4
|
import { CommonExternalProps, JSONTree } from 'react-json-tree';
|
|
5
|
+
import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
|
|
5
6
|
|
|
6
|
-
// JSON inspector
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
7
|
+
// JSON inspector keeps its own syntax-highlight contrast model rather
|
|
8
|
+
// than reusing semantic UI tokens — same convention as Chrome DevTools,
|
|
9
|
+
// Insomnia and Bruno. It still follows the host light/dark theme, but
|
|
10
|
+
// each mode gets a hand-tuned base16 palette so keys/values stay
|
|
11
|
+
// high-contrast instead of flattening into pastels.
|
|
12
|
+
interface JsonTreePalette {
|
|
13
|
+
surface: string;
|
|
14
|
+
fg: string; // base05 — default text
|
|
15
|
+
title: string; // base06 — title
|
|
16
|
+
muted: string; // base04 — collection counts, meta
|
|
17
|
+
string: string; // base0A — strings + link color
|
|
18
|
+
theme: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DARK_PALETTE: JsonTreePalette = {
|
|
22
|
+
surface: '#0d1117',
|
|
23
|
+
fg: '#e5e7eb',
|
|
24
|
+
title: '#f3f4f6',
|
|
25
|
+
muted: '#9ca3af',
|
|
26
|
+
string: '#eab308',
|
|
27
|
+
theme: {
|
|
28
|
+
scheme: 'djangocfg-dark',
|
|
29
|
+
base00: 'transparent',
|
|
30
|
+
base01: '#1a1a1a',
|
|
31
|
+
base02: '#2a2a2a',
|
|
32
|
+
base03: '#6b7280',
|
|
33
|
+
base04: '#9ca3af',
|
|
34
|
+
base05: '#e5e7eb',
|
|
35
|
+
base06: '#f3f4f6',
|
|
36
|
+
base07: '#ffffff',
|
|
37
|
+
base08: '#ef4444', // null, undefined
|
|
38
|
+
base09: '#f97316', // numbers
|
|
39
|
+
base0A: '#eab308', // strings
|
|
40
|
+
base0B: '#22c55e', // booleans
|
|
41
|
+
base0C: '#06b6d4', // dates, regex
|
|
42
|
+
base0D: '#3b82f6', // keys
|
|
43
|
+
base0E: '#a855f7', // functions
|
|
44
|
+
base0F: '#f43f5e', // deprecations
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const LIGHT_PALETTE: JsonTreePalette = {
|
|
49
|
+
surface: '#ffffff',
|
|
50
|
+
fg: '#1f2937',
|
|
51
|
+
title: '#111827',
|
|
52
|
+
muted: '#6b7280',
|
|
53
|
+
string: '#b45309',
|
|
54
|
+
theme: {
|
|
55
|
+
scheme: 'djangocfg-light',
|
|
56
|
+
base00: 'transparent',
|
|
57
|
+
base01: '#f3f4f6',
|
|
58
|
+
base02: '#e5e7eb',
|
|
59
|
+
base03: '#9ca3af',
|
|
60
|
+
base04: '#6b7280',
|
|
61
|
+
base05: '#1f2937',
|
|
62
|
+
base06: '#111827',
|
|
63
|
+
base07: '#000000',
|
|
64
|
+
base08: '#dc2626', // null, undefined
|
|
65
|
+
base09: '#c2410c', // numbers
|
|
66
|
+
base0A: '#b45309', // strings
|
|
67
|
+
base0B: '#15803d', // booleans
|
|
68
|
+
base0C: '#0e7490', // dates, regex
|
|
69
|
+
base0D: '#1d4ed8', // keys
|
|
70
|
+
base0E: '#7e22ce', // functions
|
|
71
|
+
base0F: '#be123c', // deprecations
|
|
72
|
+
},
|
|
30
73
|
};
|
|
31
74
|
|
|
75
|
+
const TRUNCATION_SUFFIX = '…';
|
|
76
|
+
|
|
32
77
|
interface JsonContentProps {
|
|
33
78
|
data: unknown;
|
|
34
79
|
renderKey: number;
|
|
@@ -49,6 +94,7 @@ interface JsonContentProps {
|
|
|
49
94
|
*
|
|
50
95
|
* Memoised: re-renders only when any prop changes. `data` is compared
|
|
51
96
|
* by reference — the parent should not mutate the object in place.
|
|
97
|
+
* Light/dark palette follows the host theme via `useResolvedTheme`.
|
|
52
98
|
*/
|
|
53
99
|
const JsonContent = memo(({
|
|
54
100
|
data,
|
|
@@ -64,52 +110,81 @@ const JsonContent = memo(({
|
|
|
64
110
|
shouldExpandNodeInitially,
|
|
65
111
|
jsonTreeProps = {},
|
|
66
112
|
}: JsonContentProps) => {
|
|
113
|
+
const resolvedTheme = useResolvedTheme();
|
|
114
|
+
const palette = resolvedTheme === 'dark' ? DARK_PALETTE : LIGHT_PALETTE;
|
|
115
|
+
|
|
67
116
|
const getItemString = showCollectionInfo
|
|
68
117
|
? (nodeType: string, nodeData: unknown) => {
|
|
69
118
|
if (nodeType === 'Array') {
|
|
70
119
|
const length = Array.isArray(nodeData) ? nodeData.length : 0;
|
|
71
|
-
return length > 0 ? <span className="text-xs" style={{ color:
|
|
120
|
+
return length > 0 ? <span className="text-xs" style={{ color: palette.muted }}>({length})</span> : null;
|
|
72
121
|
}
|
|
73
122
|
if (nodeType === 'Object') {
|
|
74
123
|
const keys = nodeData && typeof nodeData === 'object' ? Object.keys(nodeData) : [];
|
|
75
|
-
return keys.length > 0 ? <span className="text-xs" style={{ color:
|
|
124
|
+
return keys.length > 0 ? <span className="text-xs" style={{ color: palette.muted }}>{`{${keys.length}}`}</span> : null;
|
|
76
125
|
}
|
|
77
126
|
return null;
|
|
78
127
|
}
|
|
79
128
|
: () => null;
|
|
80
129
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
130
|
+
const isUrl = (value: unknown): value is string =>
|
|
131
|
+
typeof value === 'string' && (value.startsWith('http://') || value.startsWith('https://'));
|
|
132
|
+
|
|
133
|
+
// Truncation lives in valueRenderer (not postprocessValue) so the full
|
|
134
|
+
// string stays intact for copy/download and is still reachable via the
|
|
135
|
+
// `title` tooltip. URLs render as clickable links.
|
|
136
|
+
const valueRenderer = (displayValue: unknown, rawValue: unknown) => {
|
|
137
|
+
if (isUrl(rawValue)) {
|
|
138
|
+
const truncated =
|
|
139
|
+
rawValue.length > maxStringLength
|
|
140
|
+
? rawValue.slice(0, maxStringLength) + TRUNCATION_SUFFIX
|
|
141
|
+
: rawValue;
|
|
142
|
+
return (
|
|
143
|
+
<a
|
|
144
|
+
href={rawValue}
|
|
145
|
+
target="_blank"
|
|
146
|
+
rel="noopener noreferrer"
|
|
147
|
+
title={rawValue}
|
|
148
|
+
style={{ color: palette.string, textDecoration: 'underline' }}
|
|
149
|
+
onClick={(e) => e.stopPropagation()}
|
|
150
|
+
>
|
|
151
|
+
{`"${truncated}"`}
|
|
152
|
+
</a>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (typeof rawValue === 'string' && rawValue.length > maxStringLength) {
|
|
156
|
+
return (
|
|
157
|
+
<span title={rawValue}>
|
|
158
|
+
{`"${rawValue.slice(0, maxStringLength)}${TRUNCATION_SUFFIX}"`}
|
|
159
|
+
</span>
|
|
160
|
+
);
|
|
84
161
|
}
|
|
85
|
-
return
|
|
162
|
+
return displayValue as React.ReactNode;
|
|
86
163
|
};
|
|
87
164
|
|
|
88
|
-
const isCustomNode = (value: unknown) =>
|
|
89
|
-
typeof value === 'string' && (value.startsWith('http://') || value.startsWith('https://'));
|
|
90
|
-
|
|
91
165
|
return (
|
|
92
166
|
<div
|
|
93
|
-
|
|
167
|
+
role="region"
|
|
168
|
+
aria-label={title ? `JSON: ${title}` : 'JSON data'}
|
|
169
|
+
className={`overflow-auto h-full rounded-md border border-border/60 ${padding}`}
|
|
94
170
|
style={{
|
|
95
|
-
backgroundColor:
|
|
96
|
-
color:
|
|
171
|
+
backgroundColor: palette.surface,
|
|
172
|
+
color: palette.fg,
|
|
97
173
|
...(fontSize ? { fontSize } : null),
|
|
98
174
|
}}
|
|
99
175
|
>
|
|
100
176
|
{showTitle && title && (
|
|
101
|
-
<h6 className="text-sm font-semibold mb-2" style={{ color:
|
|
177
|
+
<h6 className="text-sm font-semibold mb-2" style={{ color: palette.title }}>{title}</h6>
|
|
102
178
|
)}
|
|
103
179
|
<JSONTree
|
|
104
180
|
key={renderKey}
|
|
105
181
|
data={data}
|
|
106
|
-
theme={
|
|
182
|
+
theme={palette.theme}
|
|
107
183
|
invertTheme={false}
|
|
108
184
|
hideRoot={true}
|
|
109
185
|
shouldExpandNodeInitially={shouldExpandNodeInitially}
|
|
110
186
|
getItemString={getItemString}
|
|
111
|
-
|
|
112
|
-
isCustomNode={isCustomNode}
|
|
187
|
+
valueRenderer={valueRenderer}
|
|
113
188
|
collectionLimit={collectionLimit}
|
|
114
189
|
sortObjectKeys={!preserveKeyOrder}
|
|
115
190
|
{...jsonTreeProps}
|