@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,74 @@
1
+ "use client"
2
+
3
+ import { Plus } from 'lucide-react';
4
+ import React from 'react';
5
+
6
+ import { Button } from '@djangocfg/ui-core/components';
7
+ import { ArrayFieldTemplateProps } from '@rjsf/utils';
8
+
9
+ /**
10
+ * Array field template for JSON Schema Form
11
+ * Renders array items with add/remove controls
12
+ *
13
+ * NOTE: In RJSF v6, `items` is an array of ReactElement (already rendered),
14
+ * NOT an array of objects with {children, hasRemove, etc.}.
15
+ * The actual rendering of each item (including remove buttons) is handled
16
+ * by ArrayFieldItemTemplate.
17
+ */
18
+ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
19
+ const {
20
+ title,
21
+ items,
22
+ canAdd,
23
+ onAddClick,
24
+ required,
25
+ } = props;
26
+
27
+ return (
28
+ <div className="space-y-4">
29
+ {title && (
30
+ <div className="flex items-center justify-between">
31
+ <h3 className="text-lg font-semibold">
32
+ {title}
33
+ {required && <span className="text-destructive ml-1">*</span>}
34
+ </h3>
35
+ {canAdd && (
36
+ <Button
37
+ type="button"
38
+ variant="outline"
39
+ size="sm"
40
+ onClick={onAddClick}
41
+ className="gap-2"
42
+ >
43
+ <Plus className="h-4 w-4" />
44
+ Add Item
45
+ </Button>
46
+ )}
47
+ </div>
48
+ )}
49
+
50
+ {/* In RJSF v6, items is already an array of ReactElements */}
51
+ <div className="space-y-3">
52
+ {items}
53
+ </div>
54
+
55
+ {items.length === 0 && (
56
+ <div className="text-center py-8 text-muted-foreground border-2 border-dashed rounded-md">
57
+ No items added yet.
58
+ {canAdd && (
59
+ <Button
60
+ type="button"
61
+ variant="ghost"
62
+ size="sm"
63
+ onClick={onAddClick}
64
+ className="mt-2 gap-2"
65
+ >
66
+ <Plus className="h-4 w-4" />
67
+ Add First Item
68
+ </Button>
69
+ )}
70
+ </div>
71
+ )}
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,107 @@
1
+ "use client"
2
+
3
+ import React, { ChangeEvent, FocusEvent, useCallback, useMemo } from 'react';
4
+
5
+ import { Input } from '@djangocfg/ui-core/components';
6
+ import { getInputProps, WidgetProps } from '@rjsf/utils';
7
+
8
+ /**
9
+ * Base input template for JSON Schema Form
10
+ *
11
+ * This template is CRITICAL for rendering primitive types (string, number)
12
+ * inside arrays. Without it, array items with primitive types will render
13
+ * as empty containers.
14
+ *
15
+ * RJSF uses this template for:
16
+ * - Array items with primitive types (e.g., array of strings)
17
+ * - Fields without explicit widget mapping
18
+ * - All text-based widgets internally
19
+ *
20
+ * @see https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-templates/#baseinputtemplate
21
+ */
22
+ export function BaseInputTemplate(props: WidgetProps) {
23
+ const {
24
+ id,
25
+ type,
26
+ value,
27
+ readonly,
28
+ disabled,
29
+ autofocus,
30
+ onBlur,
31
+ onFocus,
32
+ onChange,
33
+ options,
34
+ schema,
35
+ rawErrors,
36
+ placeholder,
37
+ } = props;
38
+
39
+ // Get input props from RJSF utils (handles step, min, max, etc.)
40
+ const inputProps = useMemo(() => {
41
+ return getInputProps(schema, type, options);
42
+ }, [schema, type, options]);
43
+
44
+ // Safely convert value to string for input
45
+ const safeValue = useMemo(() => {
46
+ if (value === null || value === undefined) return '';
47
+ return String(value);
48
+ }, [value]);
49
+
50
+ const hasError = useMemo(() => {
51
+ return rawErrors && rawErrors.length > 0;
52
+ }, [rawErrors]);
53
+
54
+ // Handle text change - transform empty strings based on schema type
55
+ const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
56
+ const val = event.target.value;
57
+
58
+ // Empty value handling
59
+ if (val === '') {
60
+ onChange(options?.emptyValue ?? '');
61
+ return;
62
+ }
63
+
64
+ // Number type conversion
65
+ if (inputProps.type === 'number' || schema.type === 'number' || schema.type === 'integer') {
66
+ const num = Number(val);
67
+ onChange(isNaN(num) ? val : num);
68
+ return;
69
+ }
70
+
71
+ onChange(val);
72
+ }, [onChange, inputProps.type, schema.type, options?.emptyValue]);
73
+
74
+ const handleBlur = useCallback((event: FocusEvent<HTMLInputElement>) => {
75
+ onBlur(id, event.target.value);
76
+ }, [id, onBlur]);
77
+
78
+ const handleFocus = useCallback((event: FocusEvent<HTMLInputElement>) => {
79
+ onFocus(id, event.target.value);
80
+ }, [id, onFocus]);
81
+
82
+ // Determine input type
83
+ const inputType = useMemo(() => {
84
+ if (inputProps.type) return inputProps.type;
85
+ if (schema.type === 'number' || schema.type === 'integer') return 'number';
86
+ return 'text';
87
+ }, [inputProps.type, schema.type]);
88
+
89
+ return (
90
+ <Input
91
+ id={id}
92
+ type={inputType}
93
+ value={safeValue}
94
+ disabled={disabled}
95
+ readOnly={readonly}
96
+ autoFocus={autofocus}
97
+ onChange={handleChange}
98
+ onBlur={handleBlur}
99
+ onFocus={handleFocus}
100
+ placeholder={placeholder}
101
+ className={hasError ? 'border-destructive' : ''}
102
+ step={inputProps.step}
103
+ min={inputProps.min}
104
+ max={inputProps.max}
105
+ />
106
+ );
107
+ }
@@ -0,0 +1,35 @@
1
+ "use client"
2
+
3
+ import { AlertCircle } from 'lucide-react';
4
+ import React from 'react';
5
+
6
+ import { Alert, AlertDescription, AlertTitle } from '@djangocfg/ui-core/components';
7
+ import { ErrorListProps } from '@rjsf/utils';
8
+
9
+ /**
10
+ * Error list template for JSON Schema Form
11
+ * Displays validation errors at the top of the form
12
+ */
13
+ export function ErrorListTemplate(props: ErrorListProps) {
14
+ const { errors } = props;
15
+
16
+ if (!errors || errors.length === 0) {
17
+ return null;
18
+ }
19
+
20
+ return (
21
+ <Alert variant="destructive" className="mb-6">
22
+ <AlertCircle className="h-4 w-4" />
23
+ <AlertTitle>Validation Errors</AlertTitle>
24
+ <AlertDescription>
25
+ <ul className="list-disc list-inside space-y-1 mt-2">
26
+ {errors.map((error, index) => (
27
+ <li key={index} className="text-sm">
28
+ {error.stack}
29
+ </li>
30
+ ))}
31
+ </ul>
32
+ </AlertDescription>
33
+ </Alert>
34
+ );
35
+ }
@@ -0,0 +1,62 @@
1
+ "use client"
2
+
3
+ import React from 'react';
4
+
5
+ import { Label } from '@djangocfg/ui-core/components';
6
+ import { cn } from '@djangocfg/ui-core/lib';
7
+ import { FieldTemplateProps } from '@rjsf/utils';
8
+
9
+ /**
10
+ * Field template for JSON Schema Form
11
+ * Controls the layout and styling of individual form fields
12
+ */
13
+ export function FieldTemplate(props: FieldTemplateProps) {
14
+ const {
15
+ id,
16
+ classNames,
17
+ style,
18
+ label,
19
+ help,
20
+ required,
21
+ description,
22
+ errors,
23
+ children,
24
+ displayLabel,
25
+ hidden,
26
+ rawErrors,
27
+ } = props;
28
+
29
+ if (hidden) {
30
+ return <div className="hidden">{children}</div>;
31
+ }
32
+
33
+ const hasError = rawErrors && rawErrors.length > 0;
34
+
35
+ return (
36
+ <div
37
+ className={cn('space-y-2', classNames)}
38
+ style={style}
39
+ >
40
+ {displayLabel && label && (
41
+ <Label htmlFor={id} className={cn(hasError && 'text-destructive')}>
42
+ {label}
43
+ {required && <span className="text-destructive ml-1">*</span>}
44
+ </Label>
45
+ )}
46
+
47
+ {description && (
48
+ <div className="text-sm text-muted-foreground">{description}</div>
49
+ )}
50
+
51
+ <div>{children}</div>
52
+
53
+ {errors && (
54
+ <div className="text-sm text-destructive">{errors}</div>
55
+ )}
56
+
57
+ {help && (
58
+ <div className="text-sm text-muted-foreground">{help}</div>
59
+ )}
60
+ </div>
61
+ );
62
+ }
@@ -0,0 +1,116 @@
1
+ "use client"
2
+
3
+ import { ChevronDown } from 'lucide-react';
4
+ import React, { useState } from 'react';
5
+
6
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@djangocfg/ui-core/components';
7
+ import { cn } from '@djangocfg/ui-core/lib';
8
+ import { ObjectFieldTemplateProps } from '@rjsf/utils';
9
+
10
+ /**
11
+ * Object field template for JSON Schema Form
12
+ *
13
+ * Supports:
14
+ * - Collapsible groups via ui:collapsible option
15
+ * - Grid layout via ui:grid option
16
+ * - Custom styling via ui:className
17
+ *
18
+ * Usage in uiSchema:
19
+ * ```json
20
+ * {
21
+ * "colors": {
22
+ * "ui:collapsible": true,
23
+ * "ui:collapsed": false,
24
+ * "ui:grid": 2
25
+ * }
26
+ * }
27
+ * ```
28
+ */
29
+ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
30
+ const {
31
+ title,
32
+ description,
33
+ properties,
34
+ required,
35
+ uiSchema,
36
+ } = props;
37
+
38
+ // UI options
39
+ const isCollapsible = uiSchema?.['ui:collapsible'] === true;
40
+ const defaultCollapsed = uiSchema?.['ui:collapsed'] === true;
41
+ const gridCols = uiSchema?.['ui:grid'] as number | undefined;
42
+ const className = uiSchema?.['ui:className'] as string | undefined;
43
+
44
+ // Collapsible state
45
+ const [isOpen, setIsOpen] = useState(!defaultCollapsed);
46
+
47
+ // Check if this is root object (no title usually means root)
48
+ const isRoot = !title;
49
+
50
+ // Grid class based on columns
51
+ const gridClass = gridCols
52
+ ? `grid gap-4 grid-cols-${gridCols}`
53
+ : 'space-y-4';
54
+
55
+ // Content wrapper
56
+ const content = (
57
+ <div className={cn(gridClass, className)}>
58
+ {properties.map((element) => (
59
+ <div key={element.name} className="property-wrapper">
60
+ {element.content}
61
+ </div>
62
+ ))}
63
+ </div>
64
+ );
65
+
66
+ // Root object - no wrapper
67
+ if (isRoot) {
68
+ return <div className="space-y-6">{content}</div>;
69
+ }
70
+
71
+ // Collapsible group
72
+ if (isCollapsible) {
73
+ return (
74
+ <Collapsible open={isOpen} onOpenChange={setIsOpen} className="space-y-2">
75
+ <CollapsibleTrigger className="flex w-full items-center justify-between rounded-lg border bg-muted/50 px-4 py-3 text-left hover:bg-muted transition-colors">
76
+ <div>
77
+ <h3 className="text-sm font-semibold">
78
+ {title}
79
+ {required && <span className="text-destructive ml-1">*</span>}
80
+ </h3>
81
+ {description && (
82
+ <p className="text-xs text-muted-foreground mt-0.5">{description}</p>
83
+ )}
84
+ </div>
85
+ <ChevronDown
86
+ className={cn(
87
+ 'h-4 w-4 text-muted-foreground transition-transform duration-200',
88
+ isOpen && 'rotate-180'
89
+ )}
90
+ />
91
+ </CollapsibleTrigger>
92
+ <CollapsibleContent className="pl-1 pr-1 pt-2">
93
+ {content}
94
+ </CollapsibleContent>
95
+ </Collapsible>
96
+ );
97
+ }
98
+
99
+ // Regular group with title
100
+ return (
101
+ <div className="space-y-4">
102
+ {title && (
103
+ <div className="border-b pb-2">
104
+ <h3 className="text-sm font-semibold">
105
+ {title}
106
+ {required && <span className="text-destructive ml-1">*</span>}
107
+ </h3>
108
+ {description && (
109
+ <p className="text-xs text-muted-foreground mt-1">{description}</p>
110
+ )}
111
+ </div>
112
+ )}
113
+ {content}
114
+ </div>
115
+ );
116
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Custom templates for JSON Schema Form
3
+ *
4
+ * Templates control the layout and structure of form sections
5
+ */
6
+
7
+ export { FieldTemplate } from './FieldTemplate';
8
+ export { ObjectFieldTemplate } from './ObjectFieldTemplate';
9
+ export { ArrayFieldTemplate } from './ArrayFieldTemplate';
10
+ export { ArrayFieldItemTemplate } from './ArrayFieldItemTemplate';
11
+ export { ErrorListTemplate } from './ErrorListTemplate';
12
+ export { BaseInputTemplate } from './BaseInputTemplate';
@@ -0,0 +1,83 @@
1
+ import type { RJSFSchema, UiSchema } from '@rjsf/utils';
2
+ import type { IChangeEvent, FormProps } from '@rjsf/core';
3
+
4
+ /**
5
+ * JSON Schema Form props interface
6
+ */
7
+ export interface JsonSchemaFormProps<T = any> extends Partial<FormProps<T>> {
8
+ /** JSON Schema that defines the form structure */
9
+ schema: RJSFSchema;
10
+
11
+ /** UI Schema for customizing the form appearance */
12
+ uiSchema?: UiSchema;
13
+
14
+ /** Initial form data */
15
+ formData?: T;
16
+
17
+ /** Callback when form is submitted */
18
+ onSubmit?: (data: IChangeEvent<T>) => void;
19
+
20
+ /** Callback when form data changes */
21
+ onChange?: (data: IChangeEvent<T>) => void;
22
+
23
+ /** Callback when form has validation errors */
24
+ onError?: (errors: any[]) => void;
25
+
26
+ /** Whether to show error list at the top of form */
27
+ showErrorList?: false | 'top' | 'bottom';
28
+
29
+ /** Whether to live validate on change */
30
+ liveValidate?: boolean;
31
+
32
+ /** Whether the form is disabled */
33
+ disabled?: boolean;
34
+
35
+ /** Whether the form is read-only */
36
+ readonly?: boolean;
37
+
38
+ /** Custom CSS class name */
39
+ className?: string;
40
+
41
+ /** Whether to show submit button */
42
+ showSubmitButton?: boolean;
43
+
44
+ /** Submit button text */
45
+ submitButtonText?: string;
46
+ }
47
+
48
+ /**
49
+ * Field template props
50
+ */
51
+ export interface FieldTemplateProps {
52
+ id: string;
53
+ label: string;
54
+ required: boolean;
55
+ description?: string;
56
+ errors?: any;
57
+ help?: string;
58
+ displayLabel?: boolean;
59
+ children: React.ReactNode;
60
+ classNames?: string;
61
+ rawErrors?: string[];
62
+ schema: RJSFSchema;
63
+ }
64
+
65
+ /**
66
+ * Widget props base interface
67
+ */
68
+ export interface BaseWidgetProps {
69
+ id: string;
70
+ value: any;
71
+ required: boolean;
72
+ disabled: boolean;
73
+ readonly: boolean;
74
+ autofocus: boolean;
75
+ onChange: (value: any) => void;
76
+ onBlur: (id: string, value: any) => void;
77
+ onFocus: (id: string, value: any) => void;
78
+ options: any;
79
+ schema: RJSFSchema;
80
+ label: string;
81
+ placeholder?: string;
82
+ rawErrors?: string[];
83
+ }
@@ -0,0 +1,213 @@
1
+ import consola from 'consola';
2
+
3
+ import { RJSFSchema } from '@rjsf/utils';
4
+
5
+ /**
6
+ * Utility functions for JSON Schema Form
7
+ */
8
+
9
+ /**
10
+ * Safely validates and normalizes JSON Schema
11
+ * Ensures schema is valid before rendering
12
+ */
13
+ export function validateSchema(schema: any): RJSFSchema | null {
14
+ if (!schema || typeof schema !== 'object') {
15
+ if (process.env.NODE_ENV === 'development') {
16
+ consola.error('[JsonSchemaForm] Invalid schema: must be an object', schema);
17
+ }
18
+ return null;
19
+ }
20
+
21
+ // Basic schema validation - more permissive
22
+ // Schema is valid if it has type OR properties OR $ref OR $schema
23
+ const hasValidStructure =
24
+ schema.type ||
25
+ schema.properties ||
26
+ schema.$ref ||
27
+ schema.$schema;
28
+
29
+ if (!hasValidStructure) {
30
+ if (process.env.NODE_ENV === 'development') {
31
+ consola.error('[JsonSchemaForm] Invalid schema: missing type, properties, $ref, or $schema', schema);
32
+ }
33
+ return null;
34
+ }
35
+
36
+ if (process.env.NODE_ENV === 'development') {
37
+ consola.success('[JsonSchemaForm] Schema validated successfully:', {
38
+ type: schema.type,
39
+ title: schema.title,
40
+ hasProperties: !!schema.properties,
41
+ hasRequired: !!schema.required,
42
+ });
43
+ }
44
+
45
+ return schema as RJSFSchema;
46
+ }
47
+
48
+ /**
49
+ * Safely normalizes form data
50
+ * Removes undefined values and ensures data structure matches schema
51
+ */
52
+ export function normalizeFormData<T = any>(
53
+ formData: any,
54
+ schema: RJSFSchema
55
+ ): T {
56
+ if (formData === null || formData === undefined) {
57
+ return (schema.type === 'object' ? {} : schema.type === 'array' ? [] : null) as T;
58
+ }
59
+
60
+ // Deep clone to avoid mutations
61
+ const normalized = JSON.parse(JSON.stringify(formData));
62
+
63
+ // Remove undefined values recursively
64
+ return removeUndefined(normalized) as T;
65
+ }
66
+
67
+ /**
68
+ * Recursively removes undefined values from an object
69
+ */
70
+ function removeUndefined(obj: any): any {
71
+ if (obj === null || obj === undefined) {
72
+ return obj;
73
+ }
74
+
75
+ if (Array.isArray(obj)) {
76
+ return obj.map(removeUndefined).filter((item) => item !== undefined);
77
+ }
78
+
79
+ if (typeof obj === 'object') {
80
+ const cleaned: any = {};
81
+ for (const key in obj) {
82
+ if (obj[key] !== undefined) {
83
+ cleaned[key] = removeUndefined(obj[key]);
84
+ }
85
+ }
86
+ return cleaned;
87
+ }
88
+
89
+ return obj;
90
+ }
91
+
92
+ /**
93
+ * Merges schema defaults with form data
94
+ */
95
+ export function mergeDefaults(
96
+ formData: any,
97
+ schema: RJSFSchema
98
+ ): any {
99
+ if (!schema) return formData;
100
+
101
+ const result = { ...formData };
102
+
103
+ if (schema.type === 'object' && schema.properties) {
104
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
105
+ const prop = propSchema as RJSFSchema;
106
+
107
+ // Apply default if field is missing
108
+ if (result[key] === undefined && prop.default !== undefined) {
109
+ result[key] = prop.default;
110
+ }
111
+
112
+ // Recursively merge nested objects
113
+ if (prop.type === 'object' && result[key]) {
114
+ result[key] = mergeDefaults(result[key], prop);
115
+ }
116
+ }
117
+ }
118
+
119
+ return result;
120
+ }
121
+
122
+ /**
123
+ * Safely parses JSON string with error handling
124
+ */
125
+ export function safeJsonParse<T = any>(
126
+ jsonString: string,
127
+ fallback: T
128
+ ): T {
129
+ try {
130
+ return JSON.parse(jsonString);
131
+ } catch (error) {
132
+ consola.error('[JsonSchemaForm] JSON parse error:', error);
133
+ return fallback;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Safely stringifies object to JSON
139
+ */
140
+ export function safeJsonStringify(
141
+ obj: any,
142
+ pretty: boolean = true
143
+ ): string {
144
+ try {
145
+ return JSON.stringify(obj, null, pretty ? 2 : 0);
146
+ } catch (error) {
147
+ consola.error('[JsonSchemaForm] JSON stringify error:', error);
148
+ return '{}';
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Checks if schema has required fields
154
+ */
155
+ export function hasRequiredFields(schema: RJSFSchema): boolean {
156
+ return Array.isArray(schema.required) && schema.required.length > 0;
157
+ }
158
+
159
+ /**
160
+ * Gets all required field paths from schema
161
+ */
162
+ export function getRequiredFields(
163
+ schema: RJSFSchema,
164
+ prefix: string = ''
165
+ ): string[] {
166
+ const required: string[] = [];
167
+
168
+ if (schema.required && Array.isArray(schema.required)) {
169
+ required.push(...schema.required.map((field) =>
170
+ prefix ? `${prefix}.${field}` : field
171
+ ));
172
+ }
173
+
174
+ if (schema.type === 'object' && schema.properties) {
175
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
176
+ const prop = propSchema as RJSFSchema;
177
+ const fieldPath = prefix ? `${prefix}.${key}` : key;
178
+ required.push(...getRequiredFields(prop, fieldPath));
179
+ }
180
+ }
181
+
182
+ return required;
183
+ }
184
+
185
+ /**
186
+ * Validates form data against required fields
187
+ */
188
+ export function validateRequiredFields(
189
+ formData: any,
190
+ schema: RJSFSchema
191
+ ): { valid: boolean; missing: string[] } {
192
+ const requiredFields = getRequiredFields(schema);
193
+ const missing: string[] = [];
194
+
195
+ for (const field of requiredFields) {
196
+ const value = getNestedValue(formData, field);
197
+ if (value === undefined || value === null || value === '') {
198
+ missing.push(field);
199
+ }
200
+ }
201
+
202
+ return {
203
+ valid: missing.length === 0,
204
+ missing,
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Gets nested value from object by path
210
+ */
211
+ function getNestedValue(obj: any, path: string): any {
212
+ return path.split('.').reduce((current, key) => current?.[key], obj);
213
+ }