@djangocfg/ui-tools 2.1.91
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/LottiePlayer.client-LBEC2JKY.mjs +161 -0
- package/dist/LottiePlayer.client-LBEC2JKY.mjs.map +1 -0
- package/dist/LottiePlayer.client-WFMG2OOW.cjs +168 -0
- package/dist/LottiePlayer.client-WFMG2OOW.cjs.map +1 -0
- package/dist/Mermaid.client-4TU2TSH3.mjs +477 -0
- package/dist/Mermaid.client-4TU2TSH3.mjs.map +1 -0
- package/dist/Mermaid.client-SBYY364Q.cjs +483 -0
- package/dist/Mermaid.client-SBYY364Q.cjs.map +1 -0
- package/dist/PlaygroundLayout-3YVSAEAF.cjs +1003 -0
- package/dist/PlaygroundLayout-3YVSAEAF.cjs.map +1 -0
- package/dist/PlaygroundLayout-4DYBORAS.mjs +996 -0
- package/dist/PlaygroundLayout-4DYBORAS.mjs.map +1 -0
- package/dist/PrettyCode.client-LCBPPTIX.mjs +152 -0
- package/dist/PrettyCode.client-LCBPPTIX.mjs.map +1 -0
- package/dist/PrettyCode.client-PNPLXRH6.cjs +154 -0
- package/dist/PrettyCode.client-PNPLXRH6.cjs.map +1 -0
- package/dist/chunk-37ZI6VD4.mjs +12 -0
- package/dist/chunk-37ZI6VD4.mjs.map +1 -0
- package/dist/chunk-3HK2OE62.cjs +81 -0
- package/dist/chunk-3HK2OE62.cjs.map +1 -0
- package/dist/chunk-7DGDQVQW.cjs +591 -0
- package/dist/chunk-7DGDQVQW.cjs.map +1 -0
- package/dist/chunk-M6P2FU7L.mjs +572 -0
- package/dist/chunk-M6P2FU7L.mjs.map +1 -0
- package/dist/chunk-UQ3XI5MY.cjs +15 -0
- package/dist/chunk-UQ3XI5MY.cjs.map +1 -0
- package/dist/chunk-YFRNE2IR.mjs +79 -0
- package/dist/chunk-YFRNE2IR.mjs.map +1 -0
- package/dist/index.cjs +5042 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1591 -0
- package/dist/index.d.ts +1591 -0
- package/dist/index.mjs +4941 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +86 -0
- package/src/components/markdown/MarkdownMessage.tsx +340 -0
- package/src/components/markdown/index.ts +5 -0
- package/src/index.ts +26 -0
- package/src/stores/index.ts +9 -0
- package/src/stores/mediaCache.ts +534 -0
- package/src/tools/AudioPlayer/README.md +206 -0
- package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
- package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +280 -0
- package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +149 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
- package/src/tools/AudioPlayer/components/index.ts +22 -0
- package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +158 -0
- package/src/tools/AudioPlayer/context/index.ts +16 -0
- package/src/tools/AudioPlayer/effects/index.ts +412 -0
- package/src/tools/AudioPlayer/hooks/index.ts +35 -0
- package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
- package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
- package/src/tools/AudioPlayer/hooks/useVisualization.tsx +207 -0
- package/src/tools/AudioPlayer/index.ts +133 -0
- package/src/tools/AudioPlayer/types/effects.ts +73 -0
- package/src/tools/AudioPlayer/types/index.ts +27 -0
- package/src/tools/AudioPlayer/utils/debug.ts +14 -0
- package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
- package/src/tools/AudioPlayer/utils/index.ts +6 -0
- package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
- package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
- package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
- package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
- package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
- package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
- package/src/tools/ImageViewer/README.md +200 -0
- package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +145 -0
- package/src/tools/ImageViewer/components/ImageViewer.tsx +241 -0
- package/src/tools/ImageViewer/components/index.ts +7 -0
- package/src/tools/ImageViewer/hooks/index.ts +9 -0
- package/src/tools/ImageViewer/hooks/useImageLoading.ts +204 -0
- package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
- package/src/tools/ImageViewer/index.ts +60 -0
- package/src/tools/ImageViewer/types.ts +81 -0
- package/src/tools/ImageViewer/utils/constants.ts +59 -0
- package/src/tools/ImageViewer/utils/debug.ts +14 -0
- package/src/tools/ImageViewer/utils/index.ts +17 -0
- package/src/tools/ImageViewer/utils/lqip.ts +47 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +197 -0
- package/src/tools/JsonForm/examples/BotConfigExample.tsx +249 -0
- package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +161 -0
- package/src/tools/JsonForm/index.ts +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +47 -0
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +74 -0
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +107 -0
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +35 -0
- package/src/tools/JsonForm/templates/FieldTemplate.tsx +62 -0
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +116 -0
- package/src/tools/JsonForm/templates/index.ts +12 -0
- package/src/tools/JsonForm/types.ts +83 -0
- package/src/tools/JsonForm/utils.ts +213 -0
- package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +37 -0
- package/src/tools/JsonForm/widgets/ColorWidget.tsx +219 -0
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +89 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +97 -0
- package/src/tools/JsonForm/widgets/SliderWidget.tsx +148 -0
- package/src/tools/JsonForm/widgets/SwitchWidget.tsx +35 -0
- package/src/tools/JsonForm/widgets/TextWidget.tsx +96 -0
- package/src/tools/JsonForm/widgets/index.ts +14 -0
- package/src/tools/JsonTree/index.tsx +243 -0
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +213 -0
- package/src/tools/LottiePlayer/index.tsx +56 -0
- package/src/tools/LottiePlayer/types.ts +108 -0
- package/src/tools/LottiePlayer/useLottie.ts +164 -0
- package/src/tools/Mermaid/Mermaid.client.tsx +82 -0
- package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +95 -0
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +103 -0
- package/src/tools/Mermaid/hooks/index.ts +4 -0
- package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +73 -0
- package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +46 -0
- package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +226 -0
- package/src/tools/Mermaid/hooks/useMermaidValidation.ts +29 -0
- package/src/tools/Mermaid/index.tsx +44 -0
- package/src/tools/Mermaid/utils/mermaid-helpers.ts +33 -0
- package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +149 -0
- package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +263 -0
- package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +125 -0
- package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +100 -0
- package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +157 -0
- package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
- package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +173 -0
- package/src/tools/OpenapiViewer/components/VersionSelector.tsx +68 -0
- package/src/tools/OpenapiViewer/components/index.ts +14 -0
- package/src/tools/OpenapiViewer/constants.ts +39 -0
- package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +337 -0
- package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
- package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +199 -0
- package/src/tools/OpenapiViewer/index.tsx +37 -0
- package/src/tools/OpenapiViewer/types.ts +151 -0
- package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
- package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
- package/src/tools/OpenapiViewer/utils/index.ts +9 -0
- package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +208 -0
- package/src/tools/PrettyCode/index.tsx +47 -0
- package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
- package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
- package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
- package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
- package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
- package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
- package/src/tools/VideoPlayer/README.md +264 -0
- package/src/tools/VideoPlayer/components/VideoControls.tsx +138 -0
- package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +172 -0
- package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
- package/src/tools/VideoPlayer/components/index.ts +14 -0
- package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
- package/src/tools/VideoPlayer/context/index.ts +8 -0
- package/src/tools/VideoPlayer/hooks/index.ts +12 -0
- package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +70 -0
- package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +116 -0
- package/src/tools/VideoPlayer/index.ts +77 -0
- package/src/tools/VideoPlayer/providers/NativeProvider.tsx +284 -0
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +505 -0
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +400 -0
- package/src/tools/VideoPlayer/providers/index.ts +8 -0
- package/src/tools/VideoPlayer/types/index.ts +38 -0
- package/src/tools/VideoPlayer/types/player.ts +116 -0
- package/src/tools/VideoPlayer/types/provider.ts +93 -0
- package/src/tools/VideoPlayer/types/sources.ts +97 -0
- package/src/tools/VideoPlayer/utils/debug.ts +14 -0
- package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
- package/src/tools/VideoPlayer/utils/index.ts +12 -0
- package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
- package/src/tools/_shared.ts +29 -0
- package/src/tools/index.ts +172 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Checkbox } from '@djangocfg/ui-core/components';
|
|
4
|
+
import { WidgetProps } from '@rjsf/utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Checkbox widget for JSON Schema Form
|
|
8
|
+
* Handles boolean fields
|
|
9
|
+
*/
|
|
10
|
+
export function CheckboxWidget(props: WidgetProps) {
|
|
11
|
+
const {
|
|
12
|
+
id,
|
|
13
|
+
value,
|
|
14
|
+
disabled,
|
|
15
|
+
readonly,
|
|
16
|
+
autofocus,
|
|
17
|
+
onChange,
|
|
18
|
+
onBlur,
|
|
19
|
+
onFocus,
|
|
20
|
+
} = props;
|
|
21
|
+
|
|
22
|
+
const handleChange = (checked: boolean) => {
|
|
23
|
+
onChange(checked);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Checkbox
|
|
28
|
+
id={id}
|
|
29
|
+
checked={value || false}
|
|
30
|
+
disabled={disabled || readonly}
|
|
31
|
+
autoFocus={autofocus}
|
|
32
|
+
onCheckedChange={handleChange}
|
|
33
|
+
onBlur={() => onBlur(id, value)}
|
|
34
|
+
onFocus={() => onFocus(id, value)}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useMemo, useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Input } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { WidgetProps } from '@rjsf/utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Color input widget for JSON Schema Form
|
|
10
|
+
* Supports HSL format (h s% l%) and HEX format
|
|
11
|
+
* Click on color box to open native color picker directly
|
|
12
|
+
*/
|
|
13
|
+
export function ColorWidget(props: WidgetProps) {
|
|
14
|
+
const {
|
|
15
|
+
id,
|
|
16
|
+
placeholder,
|
|
17
|
+
required,
|
|
18
|
+
disabled,
|
|
19
|
+
readonly,
|
|
20
|
+
autofocus,
|
|
21
|
+
value,
|
|
22
|
+
onChange,
|
|
23
|
+
onBlur,
|
|
24
|
+
onFocus,
|
|
25
|
+
options,
|
|
26
|
+
rawErrors,
|
|
27
|
+
} = props;
|
|
28
|
+
|
|
29
|
+
const colorInputRef = useRef<HTMLInputElement>(null);
|
|
30
|
+
|
|
31
|
+
// Determine format from options or auto-detect from value
|
|
32
|
+
const format = useMemo(() => {
|
|
33
|
+
if (options?.format) return options.format as 'hsl' | 'hex';
|
|
34
|
+
if (typeof value === 'string' && value.startsWith('#')) return 'hex';
|
|
35
|
+
return 'hsl';
|
|
36
|
+
}, [options?.format, value]);
|
|
37
|
+
|
|
38
|
+
// Ensure value is always a string
|
|
39
|
+
const safeValue = useMemo(() => {
|
|
40
|
+
if (value === null || value === undefined) return '';
|
|
41
|
+
return String(value);
|
|
42
|
+
}, [value]);
|
|
43
|
+
|
|
44
|
+
// Convert HSL string (like "217 91% 60%") to CSS hsl() value
|
|
45
|
+
const hslToCss = useCallback((hslValue: string): string => {
|
|
46
|
+
if (!hslValue) return 'transparent';
|
|
47
|
+
if (hslValue.startsWith('#')) return hslValue;
|
|
48
|
+
if (hslValue.startsWith('hsl')) return hslValue;
|
|
49
|
+
// Format: "h s% l%" -> "hsl(h, s%, l%)"
|
|
50
|
+
const parts = hslValue.split(' ');
|
|
51
|
+
if (parts.length === 3) {
|
|
52
|
+
return `hsl(${parts[0]}, ${parts[1]}, ${parts[2]})`;
|
|
53
|
+
}
|
|
54
|
+
return 'transparent';
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
// Convert CSS color to preview color
|
|
58
|
+
const previewColor = useMemo(() => {
|
|
59
|
+
return hslToCss(safeValue);
|
|
60
|
+
}, [safeValue, hslToCss]);
|
|
61
|
+
|
|
62
|
+
// Convert hex to HSL string for internal storage
|
|
63
|
+
const hexToHsl = useCallback((hex: string): string => {
|
|
64
|
+
// Remove # if present
|
|
65
|
+
hex = hex.replace('#', '');
|
|
66
|
+
|
|
67
|
+
// Parse hex values
|
|
68
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
69
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
70
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
71
|
+
|
|
72
|
+
const max = Math.max(r, g, b);
|
|
73
|
+
const min = Math.min(r, g, b);
|
|
74
|
+
let h = 0;
|
|
75
|
+
let s = 0;
|
|
76
|
+
const l = (max + min) / 2;
|
|
77
|
+
|
|
78
|
+
if (max !== min) {
|
|
79
|
+
const d = max - min;
|
|
80
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
81
|
+
|
|
82
|
+
switch (max) {
|
|
83
|
+
case r:
|
|
84
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
85
|
+
break;
|
|
86
|
+
case g:
|
|
87
|
+
h = ((b - r) / d + 2) / 6;
|
|
88
|
+
break;
|
|
89
|
+
case b:
|
|
90
|
+
h = ((r - g) / d + 4) / 6;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
// Convert HSL to hex
|
|
99
|
+
const hslToHex = useCallback((hslValue: string): string => {
|
|
100
|
+
if (!hslValue || hslValue.startsWith('#')) return hslValue || '#000000';
|
|
101
|
+
|
|
102
|
+
const parts = hslValue.split(' ');
|
|
103
|
+
if (parts.length !== 3) return '#000000';
|
|
104
|
+
|
|
105
|
+
const h = parseInt(parts[0]) / 360;
|
|
106
|
+
const s = parseInt(parts[1].replace('%', '')) / 100;
|
|
107
|
+
const l = parseInt(parts[2].replace('%', '')) / 100;
|
|
108
|
+
|
|
109
|
+
const hue2rgb = (p: number, q: number, t: number) => {
|
|
110
|
+
if (t < 0) t += 1;
|
|
111
|
+
if (t > 1) t -= 1;
|
|
112
|
+
if (t < 1/6) return p + (q - p) * 6 * t;
|
|
113
|
+
if (t < 1/2) return q;
|
|
114
|
+
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
|
115
|
+
return p;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
let r, g, b;
|
|
119
|
+
if (s === 0) {
|
|
120
|
+
r = g = b = l;
|
|
121
|
+
} else {
|
|
122
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
123
|
+
const p = 2 * l - q;
|
|
124
|
+
r = hue2rgb(p, q, h + 1/3);
|
|
125
|
+
g = hue2rgb(p, q, h);
|
|
126
|
+
b = hue2rgb(p, q, h - 1/3);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const toHex = (x: number) => {
|
|
130
|
+
const hex = Math.round(x * 255).toString(16);
|
|
131
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
const hasError = useMemo(() => {
|
|
138
|
+
return rawErrors && rawErrors.length > 0;
|
|
139
|
+
}, [rawErrors]);
|
|
140
|
+
|
|
141
|
+
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
142
|
+
const newValue = event.target.value;
|
|
143
|
+
onChange(newValue);
|
|
144
|
+
}, [onChange]);
|
|
145
|
+
|
|
146
|
+
const handleColorPickerChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
147
|
+
const hexValue = event.target.value;
|
|
148
|
+
if (format === 'hsl') {
|
|
149
|
+
onChange(hexToHsl(hexValue));
|
|
150
|
+
} else {
|
|
151
|
+
onChange(hexValue);
|
|
152
|
+
}
|
|
153
|
+
}, [onChange, format, hexToHsl]);
|
|
154
|
+
|
|
155
|
+
const handleBlur = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
|
|
156
|
+
onBlur(id, event.target.value);
|
|
157
|
+
}, [id, onBlur]);
|
|
158
|
+
|
|
159
|
+
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
|
|
160
|
+
onFocus(id, event.target.value);
|
|
161
|
+
}, [id, onFocus]);
|
|
162
|
+
|
|
163
|
+
// Get hex value for native color picker
|
|
164
|
+
const hexValue = useMemo(() => {
|
|
165
|
+
if (format === 'hex' || safeValue.startsWith('#')) {
|
|
166
|
+
return safeValue || '#000000';
|
|
167
|
+
}
|
|
168
|
+
return hslToHex(safeValue);
|
|
169
|
+
}, [safeValue, format, hslToHex]);
|
|
170
|
+
|
|
171
|
+
// Click on color box opens native color picker
|
|
172
|
+
const handleColorBoxClick = useCallback(() => {
|
|
173
|
+
if (!disabled && !readonly) {
|
|
174
|
+
colorInputRef.current?.click();
|
|
175
|
+
}
|
|
176
|
+
}, [disabled, readonly]);
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<div className="flex items-center gap-2">
|
|
180
|
+
{/* Color preview box - clicking opens native picker */}
|
|
181
|
+
<div className="relative">
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
onClick={handleColorBoxClick}
|
|
185
|
+
disabled={disabled || readonly}
|
|
186
|
+
className="h-10 w-10 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"
|
|
187
|
+
style={{ backgroundColor: previewColor }}
|
|
188
|
+
aria-label="Pick color"
|
|
189
|
+
/>
|
|
190
|
+
{/* Hidden native color input */}
|
|
191
|
+
<input
|
|
192
|
+
ref={colorInputRef}
|
|
193
|
+
type="color"
|
|
194
|
+
value={hexValue}
|
|
195
|
+
onChange={handleColorPickerChange}
|
|
196
|
+
className="absolute inset-0 opacity-0 w-full h-full cursor-pointer"
|
|
197
|
+
disabled={disabled || readonly}
|
|
198
|
+
tabIndex={-1}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
{/* Text input for manual HSL/HEX entry */}
|
|
203
|
+
<Input
|
|
204
|
+
id={id}
|
|
205
|
+
type="text"
|
|
206
|
+
placeholder={placeholder || (format === 'hsl' ? '217 91% 60%' : '#3b82f6')}
|
|
207
|
+
disabled={disabled}
|
|
208
|
+
readOnly={readonly}
|
|
209
|
+
autoFocus={autofocus}
|
|
210
|
+
value={safeValue}
|
|
211
|
+
required={required}
|
|
212
|
+
onChange={handleChange}
|
|
213
|
+
onBlur={handleBlur}
|
|
214
|
+
onFocus={handleFocus}
|
|
215
|
+
className={`flex-1 font-mono text-sm ${hasError ? 'border-destructive' : ''}`}
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Input } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { WidgetProps } from '@rjsf/utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Number input widget for JSON Schema Form
|
|
10
|
+
* Handles integer and number fields
|
|
11
|
+
*/
|
|
12
|
+
export function NumberWidget(props: WidgetProps) {
|
|
13
|
+
const {
|
|
14
|
+
id,
|
|
15
|
+
placeholder,
|
|
16
|
+
required,
|
|
17
|
+
disabled,
|
|
18
|
+
readonly,
|
|
19
|
+
autofocus,
|
|
20
|
+
value,
|
|
21
|
+
onChange,
|
|
22
|
+
onBlur,
|
|
23
|
+
onFocus,
|
|
24
|
+
options,
|
|
25
|
+
schema,
|
|
26
|
+
rawErrors,
|
|
27
|
+
} = props;
|
|
28
|
+
|
|
29
|
+
// Memoize widget configuration
|
|
30
|
+
const config = useMemo(() => ({
|
|
31
|
+
isInteger: schema.type === 'integer',
|
|
32
|
+
step: schema.type === 'integer' ? '1' : 'any',
|
|
33
|
+
min: schema.minimum,
|
|
34
|
+
max: schema.maximum,
|
|
35
|
+
emptyValue: options?.emptyValue,
|
|
36
|
+
}), [schema, options]);
|
|
37
|
+
|
|
38
|
+
// Ensure value is valid number or empty string
|
|
39
|
+
const safeValue = useMemo(() => {
|
|
40
|
+
if (value === null || value === undefined || value === '') return '';
|
|
41
|
+
if (typeof value === 'number' && !isNaN(value)) return value;
|
|
42
|
+
return '';
|
|
43
|
+
}, [value]);
|
|
44
|
+
|
|
45
|
+
const hasError = useMemo(() => {
|
|
46
|
+
return rawErrors && rawErrors.length > 0;
|
|
47
|
+
}, [rawErrors]);
|
|
48
|
+
|
|
49
|
+
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
50
|
+
const newValue = event.target.value;
|
|
51
|
+
if (newValue === '') {
|
|
52
|
+
onChange(config.emptyValue);
|
|
53
|
+
} else {
|
|
54
|
+
const parsedValue = config.isInteger
|
|
55
|
+
? parseInt(newValue, 10)
|
|
56
|
+
: parseFloat(newValue);
|
|
57
|
+
|
|
58
|
+
onChange(isNaN(parsedValue) ? config.emptyValue : parsedValue);
|
|
59
|
+
}
|
|
60
|
+
}, [onChange, config]);
|
|
61
|
+
|
|
62
|
+
const handleBlur = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
|
|
63
|
+
onBlur(id, event.target.value);
|
|
64
|
+
}, [id, onBlur]);
|
|
65
|
+
|
|
66
|
+
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
|
|
67
|
+
onFocus(id, event.target.value);
|
|
68
|
+
}, [id, onFocus]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Input
|
|
72
|
+
id={id}
|
|
73
|
+
type="number"
|
|
74
|
+
placeholder={placeholder}
|
|
75
|
+
disabled={disabled}
|
|
76
|
+
readOnly={readonly}
|
|
77
|
+
autoFocus={autofocus}
|
|
78
|
+
value={safeValue}
|
|
79
|
+
required={required}
|
|
80
|
+
onChange={handleChange}
|
|
81
|
+
onBlur={handleBlur}
|
|
82
|
+
onFocus={handleFocus}
|
|
83
|
+
step={config.step}
|
|
84
|
+
min={config.min}
|
|
85
|
+
max={config.max}
|
|
86
|
+
className={hasError ? 'border-destructive' : ''}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Select, SelectContent, SelectItem, SelectTrigger, SelectValue
|
|
7
|
+
} from '@djangocfg/ui-core/components';
|
|
8
|
+
import { WidgetProps } from '@rjsf/utils';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Select dropdown widget for JSON Schema Form
|
|
12
|
+
* Handles enum fields
|
|
13
|
+
*/
|
|
14
|
+
export function SelectWidget(props: WidgetProps) {
|
|
15
|
+
const {
|
|
16
|
+
id,
|
|
17
|
+
options,
|
|
18
|
+
value,
|
|
19
|
+
required,
|
|
20
|
+
disabled,
|
|
21
|
+
readonly,
|
|
22
|
+
autofocus,
|
|
23
|
+
onChange,
|
|
24
|
+
onBlur,
|
|
25
|
+
onFocus,
|
|
26
|
+
placeholder,
|
|
27
|
+
rawErrors,
|
|
28
|
+
} = props;
|
|
29
|
+
|
|
30
|
+
// Safely extract and validate enum options
|
|
31
|
+
const enumOptions = useMemo(() => {
|
|
32
|
+
const opts = options?.enumOptions;
|
|
33
|
+
if (!Array.isArray(opts)) return [];
|
|
34
|
+
return opts.filter(opt => opt && (opt.value !== undefined));
|
|
35
|
+
}, [options]);
|
|
36
|
+
|
|
37
|
+
const hasError = useMemo(() => {
|
|
38
|
+
return rawErrors && rawErrors.length > 0;
|
|
39
|
+
}, [rawErrors]);
|
|
40
|
+
|
|
41
|
+
// Ensure value is always a string
|
|
42
|
+
const safeValue = useMemo(() => {
|
|
43
|
+
if (value === null || value === undefined) return '';
|
|
44
|
+
return String(value);
|
|
45
|
+
}, [value]);
|
|
46
|
+
|
|
47
|
+
const handleChange = useCallback((newValue: string) => {
|
|
48
|
+
onChange(newValue);
|
|
49
|
+
onBlur(id, newValue);
|
|
50
|
+
}, [onChange, onBlur, id]);
|
|
51
|
+
|
|
52
|
+
// If no enum options, render a text input instead
|
|
53
|
+
// This handles cases like anyOf/oneOf where RJSF might incorrectly use SelectWidget
|
|
54
|
+
if (enumOptions.length === 0) {
|
|
55
|
+
return (
|
|
56
|
+
<input
|
|
57
|
+
id={id}
|
|
58
|
+
type="text"
|
|
59
|
+
value={safeValue}
|
|
60
|
+
onChange={(e) => onChange(e.target.value)}
|
|
61
|
+
onBlur={(e) => onBlur(id, e.target.value)}
|
|
62
|
+
onFocus={(e) => onFocus(id, e.target.value)}
|
|
63
|
+
disabled={disabled || readonly}
|
|
64
|
+
readOnly={readonly}
|
|
65
|
+
className={`flex h-10 w-full rounded-md border ${
|
|
66
|
+
hasError ? 'border-destructive' : 'border-input'
|
|
67
|
+
} 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`}
|
|
68
|
+
placeholder={placeholder}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Select
|
|
75
|
+
value={safeValue}
|
|
76
|
+
onValueChange={handleChange}
|
|
77
|
+
disabled={disabled || readonly}
|
|
78
|
+
required={required}
|
|
79
|
+
>
|
|
80
|
+
<SelectTrigger
|
|
81
|
+
id={id}
|
|
82
|
+
className={hasError ? 'border-destructive' : ''}
|
|
83
|
+
autoFocus={autofocus}
|
|
84
|
+
onFocus={() => onFocus(id, value)}
|
|
85
|
+
>
|
|
86
|
+
<SelectValue placeholder={placeholder || 'Select an option'} />
|
|
87
|
+
</SelectTrigger>
|
|
88
|
+
<SelectContent>
|
|
89
|
+
{enumOptions.map((option: any) => (
|
|
90
|
+
<SelectItem key={String(option.value)} value={String(option.value)}>
|
|
91
|
+
{option.label || String(option.value)}
|
|
92
|
+
</SelectItem>
|
|
93
|
+
))}
|
|
94
|
+
</SelectContent>
|
|
95
|
+
</Select>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Input, Slider } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
7
|
+
import { WidgetProps } from '@rjsf/utils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Slider widget for JSON Schema Form
|
|
11
|
+
*
|
|
12
|
+
* Supports:
|
|
13
|
+
* - number/integer types
|
|
14
|
+
* - min/max from schema
|
|
15
|
+
* - step from schema or options
|
|
16
|
+
* - unit suffix (e.g., "rem", "px", "%")
|
|
17
|
+
* - optional text input for precise values
|
|
18
|
+
*
|
|
19
|
+
* Usage in uiSchema:
|
|
20
|
+
* ```json
|
|
21
|
+
* {
|
|
22
|
+
* "radius": {
|
|
23
|
+
* "ui:widget": "slider",
|
|
24
|
+
* "ui:options": {
|
|
25
|
+
* "unit": "rem",
|
|
26
|
+
* "showInput": true,
|
|
27
|
+
* "step": 0.125
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function SliderWidget(props: WidgetProps) {
|
|
34
|
+
const {
|
|
35
|
+
id,
|
|
36
|
+
disabled,
|
|
37
|
+
readonly,
|
|
38
|
+
value,
|
|
39
|
+
onChange,
|
|
40
|
+
schema,
|
|
41
|
+
options,
|
|
42
|
+
rawErrors,
|
|
43
|
+
} = props;
|
|
44
|
+
|
|
45
|
+
// Extract configuration
|
|
46
|
+
const config = useMemo(() => {
|
|
47
|
+
const min = schema.minimum ?? options?.min ?? 0;
|
|
48
|
+
const max = schema.maximum ?? options?.max ?? 100;
|
|
49
|
+
const step = options?.step ?? (schema.type === 'integer' ? 1 : 0.1);
|
|
50
|
+
const unit = options?.unit as string | undefined;
|
|
51
|
+
const showInput = options?.showInput !== false; // default true
|
|
52
|
+
|
|
53
|
+
return { min, max, step, unit, showInput };
|
|
54
|
+
}, [schema, options]);
|
|
55
|
+
|
|
56
|
+
// Parse value - handle string values like "0.5rem"
|
|
57
|
+
const numericValue = useMemo(() => {
|
|
58
|
+
if (value === null || value === undefined || value === '') {
|
|
59
|
+
return config.min;
|
|
60
|
+
}
|
|
61
|
+
if (typeof value === 'number') {
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
if (typeof value === 'string') {
|
|
65
|
+
// Extract number from string like "0.5rem"
|
|
66
|
+
const parsed = parseFloat(value);
|
|
67
|
+
return isNaN(parsed) ? config.min : parsed;
|
|
68
|
+
}
|
|
69
|
+
return config.min;
|
|
70
|
+
}, [value, config.min]);
|
|
71
|
+
|
|
72
|
+
const hasError = useMemo(() => {
|
|
73
|
+
return rawErrors && rawErrors.length > 0;
|
|
74
|
+
}, [rawErrors]);
|
|
75
|
+
|
|
76
|
+
// Handle slider change
|
|
77
|
+
const handleSliderChange = useCallback((values: number[]) => {
|
|
78
|
+
const newValue = values[0];
|
|
79
|
+
if (config.unit) {
|
|
80
|
+
onChange(`${newValue}${config.unit}`);
|
|
81
|
+
} else {
|
|
82
|
+
onChange(newValue);
|
|
83
|
+
}
|
|
84
|
+
}, [onChange, config.unit]);
|
|
85
|
+
|
|
86
|
+
// Handle text input change
|
|
87
|
+
const handleInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
88
|
+
const inputValue = event.target.value;
|
|
89
|
+
|
|
90
|
+
// If has unit, just store raw value with unit
|
|
91
|
+
if (config.unit) {
|
|
92
|
+
// Remove unit if user typed it, then add it back
|
|
93
|
+
const cleanValue = inputValue.replace(config.unit, '').trim();
|
|
94
|
+
const parsed = parseFloat(cleanValue);
|
|
95
|
+
if (!isNaN(parsed)) {
|
|
96
|
+
onChange(`${parsed}${config.unit}`);
|
|
97
|
+
} else if (inputValue === '') {
|
|
98
|
+
onChange(`${config.min}${config.unit}`);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
const parsed = parseFloat(inputValue);
|
|
102
|
+
onChange(isNaN(parsed) ? config.min : parsed);
|
|
103
|
+
}
|
|
104
|
+
}, [onChange, config.unit, config.min]);
|
|
105
|
+
|
|
106
|
+
// Display value with unit
|
|
107
|
+
const displayValue = useMemo(() => {
|
|
108
|
+
if (config.unit) {
|
|
109
|
+
return `${numericValue}${config.unit}`;
|
|
110
|
+
}
|
|
111
|
+
return String(numericValue);
|
|
112
|
+
}, [numericValue, config.unit]);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className={cn('flex items-center gap-3', hasError && 'text-destructive')}>
|
|
116
|
+
{/* Slider */}
|
|
117
|
+
<Slider
|
|
118
|
+
id={id}
|
|
119
|
+
disabled={disabled || readonly}
|
|
120
|
+
value={[numericValue]}
|
|
121
|
+
onValueChange={handleSliderChange}
|
|
122
|
+
min={config.min}
|
|
123
|
+
max={config.max}
|
|
124
|
+
step={config.step}
|
|
125
|
+
className="flex-1"
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
{/* Value input or display */}
|
|
129
|
+
{config.showInput ? (
|
|
130
|
+
<Input
|
|
131
|
+
type="text"
|
|
132
|
+
value={displayValue}
|
|
133
|
+
onChange={handleInputChange}
|
|
134
|
+
disabled={disabled}
|
|
135
|
+
readOnly={readonly}
|
|
136
|
+
className={cn(
|
|
137
|
+
'w-20 text-center font-mono text-sm',
|
|
138
|
+
hasError && 'border-destructive'
|
|
139
|
+
)}
|
|
140
|
+
/>
|
|
141
|
+
) : (
|
|
142
|
+
<span className="w-16 text-right font-mono text-sm text-muted-foreground">
|
|
143
|
+
{displayValue}
|
|
144
|
+
</span>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Switch } from '@djangocfg/ui-core/components';
|
|
4
|
+
import { WidgetProps } from '@rjsf/utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Switch toggle widget for JSON Schema Form
|
|
8
|
+
* Alternative boolean input
|
|
9
|
+
*/
|
|
10
|
+
export function SwitchWidget(props: WidgetProps) {
|
|
11
|
+
const {
|
|
12
|
+
id,
|
|
13
|
+
value,
|
|
14
|
+
disabled,
|
|
15
|
+
readonly,
|
|
16
|
+
onChange,
|
|
17
|
+
onBlur,
|
|
18
|
+
onFocus,
|
|
19
|
+
} = props;
|
|
20
|
+
|
|
21
|
+
const handleChange = (checked: boolean) => {
|
|
22
|
+
onChange(checked);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Switch
|
|
27
|
+
id={id}
|
|
28
|
+
checked={value || false}
|
|
29
|
+
disabled={disabled || readonly}
|
|
30
|
+
onCheckedChange={handleChange}
|
|
31
|
+
onBlur={() => onBlur(id, value)}
|
|
32
|
+
onFocus={() => onFocus(id, value)}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|