@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.
Files changed (174) hide show
  1. package/dist/LottiePlayer.client-LBEC2JKY.mjs +161 -0
  2. package/dist/LottiePlayer.client-LBEC2JKY.mjs.map +1 -0
  3. package/dist/LottiePlayer.client-WFMG2OOW.cjs +168 -0
  4. package/dist/LottiePlayer.client-WFMG2OOW.cjs.map +1 -0
  5. package/dist/Mermaid.client-4TU2TSH3.mjs +477 -0
  6. package/dist/Mermaid.client-4TU2TSH3.mjs.map +1 -0
  7. package/dist/Mermaid.client-SBYY364Q.cjs +483 -0
  8. package/dist/Mermaid.client-SBYY364Q.cjs.map +1 -0
  9. package/dist/PlaygroundLayout-3YVSAEAF.cjs +1003 -0
  10. package/dist/PlaygroundLayout-3YVSAEAF.cjs.map +1 -0
  11. package/dist/PlaygroundLayout-4DYBORAS.mjs +996 -0
  12. package/dist/PlaygroundLayout-4DYBORAS.mjs.map +1 -0
  13. package/dist/PrettyCode.client-LCBPPTIX.mjs +152 -0
  14. package/dist/PrettyCode.client-LCBPPTIX.mjs.map +1 -0
  15. package/dist/PrettyCode.client-PNPLXRH6.cjs +154 -0
  16. package/dist/PrettyCode.client-PNPLXRH6.cjs.map +1 -0
  17. package/dist/chunk-37ZI6VD4.mjs +12 -0
  18. package/dist/chunk-37ZI6VD4.mjs.map +1 -0
  19. package/dist/chunk-3HK2OE62.cjs +81 -0
  20. package/dist/chunk-3HK2OE62.cjs.map +1 -0
  21. package/dist/chunk-7DGDQVQW.cjs +591 -0
  22. package/dist/chunk-7DGDQVQW.cjs.map +1 -0
  23. package/dist/chunk-M6P2FU7L.mjs +572 -0
  24. package/dist/chunk-M6P2FU7L.mjs.map +1 -0
  25. package/dist/chunk-UQ3XI5MY.cjs +15 -0
  26. package/dist/chunk-UQ3XI5MY.cjs.map +1 -0
  27. package/dist/chunk-YFRNE2IR.mjs +79 -0
  28. package/dist/chunk-YFRNE2IR.mjs.map +1 -0
  29. package/dist/index.cjs +5042 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +1591 -0
  32. package/dist/index.d.ts +1591 -0
  33. package/dist/index.mjs +4941 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/package.json +86 -0
  36. package/src/components/markdown/MarkdownMessage.tsx +340 -0
  37. package/src/components/markdown/index.ts +5 -0
  38. package/src/index.ts +26 -0
  39. package/src/stores/index.ts +9 -0
  40. package/src/stores/mediaCache.ts +534 -0
  41. package/src/tools/AudioPlayer/README.md +206 -0
  42. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
  43. package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +280 -0
  44. package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
  45. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +149 -0
  46. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
  47. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
  48. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
  49. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
  50. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
  51. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
  52. package/src/tools/AudioPlayer/components/index.ts +22 -0
  53. package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +158 -0
  54. package/src/tools/AudioPlayer/context/index.ts +16 -0
  55. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  56. package/src/tools/AudioPlayer/hooks/index.ts +35 -0
  57. package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
  58. package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
  59. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +207 -0
  60. package/src/tools/AudioPlayer/index.ts +133 -0
  61. package/src/tools/AudioPlayer/types/effects.ts +73 -0
  62. package/src/tools/AudioPlayer/types/index.ts +27 -0
  63. package/src/tools/AudioPlayer/utils/debug.ts +14 -0
  64. package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
  65. package/src/tools/AudioPlayer/utils/index.ts +6 -0
  66. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
  67. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
  68. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
  69. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
  70. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
  71. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
  72. package/src/tools/ImageViewer/README.md +200 -0
  73. package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
  74. package/src/tools/ImageViewer/components/ImageToolbar.tsx +145 -0
  75. package/src/tools/ImageViewer/components/ImageViewer.tsx +241 -0
  76. package/src/tools/ImageViewer/components/index.ts +7 -0
  77. package/src/tools/ImageViewer/hooks/index.ts +9 -0
  78. package/src/tools/ImageViewer/hooks/useImageLoading.ts +204 -0
  79. package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
  80. package/src/tools/ImageViewer/index.ts +60 -0
  81. package/src/tools/ImageViewer/types.ts +81 -0
  82. package/src/tools/ImageViewer/utils/constants.ts +59 -0
  83. package/src/tools/ImageViewer/utils/debug.ts +14 -0
  84. package/src/tools/ImageViewer/utils/index.ts +17 -0
  85. package/src/tools/ImageViewer/utils/lqip.ts +47 -0
  86. package/src/tools/JsonForm/JsonSchemaForm.tsx +197 -0
  87. package/src/tools/JsonForm/examples/BotConfigExample.tsx +249 -0
  88. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +161 -0
  89. package/src/tools/JsonForm/index.ts +46 -0
  90. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +47 -0
  91. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +74 -0
  92. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +107 -0
  93. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +35 -0
  94. package/src/tools/JsonForm/templates/FieldTemplate.tsx +62 -0
  95. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +116 -0
  96. package/src/tools/JsonForm/templates/index.ts +12 -0
  97. package/src/tools/JsonForm/types.ts +83 -0
  98. package/src/tools/JsonForm/utils.ts +213 -0
  99. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +37 -0
  100. package/src/tools/JsonForm/widgets/ColorWidget.tsx +219 -0
  101. package/src/tools/JsonForm/widgets/NumberWidget.tsx +89 -0
  102. package/src/tools/JsonForm/widgets/SelectWidget.tsx +97 -0
  103. package/src/tools/JsonForm/widgets/SliderWidget.tsx +148 -0
  104. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +35 -0
  105. package/src/tools/JsonForm/widgets/TextWidget.tsx +96 -0
  106. package/src/tools/JsonForm/widgets/index.ts +14 -0
  107. package/src/tools/JsonTree/index.tsx +243 -0
  108. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +213 -0
  109. package/src/tools/LottiePlayer/index.tsx +56 -0
  110. package/src/tools/LottiePlayer/types.ts +108 -0
  111. package/src/tools/LottiePlayer/useLottie.ts +164 -0
  112. package/src/tools/Mermaid/Mermaid.client.tsx +82 -0
  113. package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +95 -0
  114. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +103 -0
  115. package/src/tools/Mermaid/hooks/index.ts +4 -0
  116. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +73 -0
  117. package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +46 -0
  118. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +226 -0
  119. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +29 -0
  120. package/src/tools/Mermaid/index.tsx +44 -0
  121. package/src/tools/Mermaid/utils/mermaid-helpers.ts +33 -0
  122. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +149 -0
  123. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +263 -0
  124. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +125 -0
  125. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +100 -0
  126. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +157 -0
  127. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  128. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +173 -0
  129. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +68 -0
  130. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  131. package/src/tools/OpenapiViewer/constants.ts +39 -0
  132. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +337 -0
  133. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  134. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  135. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +199 -0
  136. package/src/tools/OpenapiViewer/index.tsx +37 -0
  137. package/src/tools/OpenapiViewer/types.ts +151 -0
  138. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  139. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  140. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  141. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  142. package/src/tools/PrettyCode/PrettyCode.client.tsx +208 -0
  143. package/src/tools/PrettyCode/index.tsx +47 -0
  144. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
  145. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
  146. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
  147. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
  148. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
  149. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
  150. package/src/tools/VideoPlayer/README.md +264 -0
  151. package/src/tools/VideoPlayer/components/VideoControls.tsx +138 -0
  152. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +172 -0
  153. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
  154. package/src/tools/VideoPlayer/components/index.ts +14 -0
  155. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
  156. package/src/tools/VideoPlayer/context/index.ts +8 -0
  157. package/src/tools/VideoPlayer/hooks/index.ts +12 -0
  158. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +70 -0
  159. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +116 -0
  160. package/src/tools/VideoPlayer/index.ts +77 -0
  161. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +284 -0
  162. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +505 -0
  163. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +400 -0
  164. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  165. package/src/tools/VideoPlayer/types/index.ts +38 -0
  166. package/src/tools/VideoPlayer/types/player.ts +116 -0
  167. package/src/tools/VideoPlayer/types/provider.ts +93 -0
  168. package/src/tools/VideoPlayer/types/sources.ts +97 -0
  169. package/src/tools/VideoPlayer/utils/debug.ts +14 -0
  170. package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
  171. package/src/tools/VideoPlayer/utils/index.ts +12 -0
  172. package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
  173. package/src/tools/_shared.ts +29 -0
  174. 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
+ }