@djangocfg/ui-nextjs 1.4.45

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/package.json +110 -0
  4. package/src/animations/AnimatedBackground.tsx +645 -0
  5. package/src/animations/index.ts +2 -0
  6. package/src/blocks/ArticleCard.tsx +94 -0
  7. package/src/blocks/ArticleList.tsx +95 -0
  8. package/src/blocks/CTASection.tsx +136 -0
  9. package/src/blocks/FeatureSection.tsx +104 -0
  10. package/src/blocks/Hero.tsx +102 -0
  11. package/src/blocks/NewsletterSection.tsx +119 -0
  12. package/src/blocks/StatsSection.tsx +103 -0
  13. package/src/blocks/SuperHero.tsx +328 -0
  14. package/src/blocks/TestimonialSection.tsx +122 -0
  15. package/src/blocks/index.ts +9 -0
  16. package/src/components/README.md +2018 -0
  17. package/src/components/breadcrumb-navigation.tsx +127 -0
  18. package/src/components/breadcrumb.tsx +132 -0
  19. package/src/components/button-download.tsx +275 -0
  20. package/src/components/dropdown-menu.tsx +219 -0
  21. package/src/components/index.ts +86 -0
  22. package/src/components/markdown/MarkdownMessage.tsx +338 -0
  23. package/src/components/markdown/index.ts +5 -0
  24. package/src/components/menubar.tsx +274 -0
  25. package/src/components/multi-select-pro/async.tsx +608 -0
  26. package/src/components/multi-select-pro/helpers.tsx +84 -0
  27. package/src/components/multi-select-pro/index.tsx +622 -0
  28. package/src/components/navigation-menu.tsx +153 -0
  29. package/src/components/pagination-static.tsx +348 -0
  30. package/src/components/pagination.tsx +138 -0
  31. package/src/components/phone-input.tsx +276 -0
  32. package/src/components/sidebar.tsx +866 -0
  33. package/src/components/sonner.tsx +31 -0
  34. package/src/components/ssr-pagination.tsx +237 -0
  35. package/src/hooks/index.ts +19 -0
  36. package/src/hooks/useCfgRouter.ts +153 -0
  37. package/src/hooks/useLocalStorage.ts +221 -0
  38. package/src/hooks/useQueryParams.ts +73 -0
  39. package/src/hooks/useSessionStorage.ts +188 -0
  40. package/src/hooks/useTheme.ts +57 -0
  41. package/src/index.ts +24 -0
  42. package/src/lib/index.ts +2 -0
  43. package/src/styles/index.css +2 -0
  44. package/src/theme/ForceTheme.tsx +115 -0
  45. package/src/theme/ThemeProvider.tsx +82 -0
  46. package/src/theme/ThemeToggle.tsx +52 -0
  47. package/src/theme/index.ts +3 -0
  48. package/src/tools/JsonForm/JsonSchemaForm.tsx +199 -0
  49. package/src/tools/JsonForm/examples/BotConfigExample.tsx +245 -0
  50. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +157 -0
  51. package/src/tools/JsonForm/index.ts +46 -0
  52. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +46 -0
  53. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +73 -0
  54. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +106 -0
  55. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +34 -0
  56. package/src/tools/JsonForm/templates/FieldTemplate.tsx +61 -0
  57. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +43 -0
  58. package/src/tools/JsonForm/templates/index.ts +12 -0
  59. package/src/tools/JsonForm/types.ts +83 -0
  60. package/src/tools/JsonForm/utils.ts +212 -0
  61. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +36 -0
  62. package/src/tools/JsonForm/widgets/NumberWidget.tsx +88 -0
  63. package/src/tools/JsonForm/widgets/SelectWidget.tsx +100 -0
  64. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +34 -0
  65. package/src/tools/JsonForm/widgets/TextWidget.tsx +95 -0
  66. package/src/tools/JsonForm/widgets/index.ts +12 -0
  67. package/src/tools/JsonTree/index.tsx +252 -0
  68. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +212 -0
  69. package/src/tools/LottiePlayer/index.tsx +54 -0
  70. package/src/tools/LottiePlayer/types.ts +108 -0
  71. package/src/tools/LottiePlayer/useLottie.ts +163 -0
  72. package/src/tools/Mermaid/Mermaid.client.tsx +341 -0
  73. package/src/tools/Mermaid/index.tsx +40 -0
  74. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +144 -0
  75. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +255 -0
  76. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +123 -0
  77. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +98 -0
  78. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +164 -0
  79. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  80. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +169 -0
  81. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +64 -0
  82. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  83. package/src/tools/OpenapiViewer/constants.ts +39 -0
  84. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +338 -0
  85. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  86. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  87. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +203 -0
  88. package/src/tools/OpenapiViewer/index.tsx +36 -0
  89. package/src/tools/OpenapiViewer/types.ts +152 -0
  90. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  91. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  92. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  93. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  94. package/src/tools/PrettyCode/PrettyCode.client.tsx +217 -0
  95. package/src/tools/PrettyCode/index.tsx +43 -0
  96. package/src/tools/VideoPlayer/README.md +239 -0
  97. package/src/tools/VideoPlayer/VideoControls.tsx +138 -0
  98. package/src/tools/VideoPlayer/VideoPlayer.tsx +230 -0
  99. package/src/tools/VideoPlayer/index.ts +9 -0
  100. package/src/tools/VideoPlayer/types.ts +62 -0
  101. package/src/tools/index.ts +43 -0
@@ -0,0 +1,73 @@
1
+ "use client"
2
+
3
+ import React from 'react';
4
+ import { ArrayFieldTemplateProps } from '@rjsf/utils';
5
+ import { Button } from '@djangocfg/ui-core/components';
6
+ import { Plus } from 'lucide-react';
7
+
8
+ /**
9
+ * Array field template for JSON Schema Form
10
+ * Renders array items with add/remove controls
11
+ *
12
+ * NOTE: In RJSF v6, `items` is an array of ReactElement (already rendered),
13
+ * NOT an array of objects with {children, hasRemove, etc.}.
14
+ * The actual rendering of each item (including remove buttons) is handled
15
+ * by ArrayFieldItemTemplate.
16
+ */
17
+ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
18
+ const {
19
+ title,
20
+ items,
21
+ canAdd,
22
+ onAddClick,
23
+ required,
24
+ } = props;
25
+
26
+ return (
27
+ <div className="space-y-4">
28
+ {title && (
29
+ <div className="flex items-center justify-between">
30
+ <h3 className="text-lg font-semibold">
31
+ {title}
32
+ {required && <span className="text-destructive ml-1">*</span>}
33
+ </h3>
34
+ {canAdd && (
35
+ <Button
36
+ type="button"
37
+ variant="outline"
38
+ size="sm"
39
+ onClick={onAddClick}
40
+ className="gap-2"
41
+ >
42
+ <Plus className="h-4 w-4" />
43
+ Add Item
44
+ </Button>
45
+ )}
46
+ </div>
47
+ )}
48
+
49
+ {/* In RJSF v6, items is already an array of ReactElements */}
50
+ <div className="space-y-3">
51
+ {items}
52
+ </div>
53
+
54
+ {items.length === 0 && (
55
+ <div className="text-center py-8 text-muted-foreground border-2 border-dashed rounded-md">
56
+ No items added yet.
57
+ {canAdd && (
58
+ <Button
59
+ type="button"
60
+ variant="ghost"
61
+ size="sm"
62
+ onClick={onAddClick}
63
+ className="mt-2 gap-2"
64
+ >
65
+ <Plus className="h-4 w-4" />
66
+ Add First Item
67
+ </Button>
68
+ )}
69
+ </div>
70
+ )}
71
+ </div>
72
+ );
73
+ }
@@ -0,0 +1,106 @@
1
+ "use client"
2
+
3
+ import React, { useCallback, useMemo, ChangeEvent, FocusEvent } from 'react';
4
+ import { getInputProps, WidgetProps } from '@rjsf/utils';
5
+ import { Input } from '@djangocfg/ui-core/components';
6
+
7
+ /**
8
+ * Base input template for JSON Schema Form
9
+ *
10
+ * This template is CRITICAL for rendering primitive types (string, number)
11
+ * inside arrays. Without it, array items with primitive types will render
12
+ * as empty containers.
13
+ *
14
+ * RJSF uses this template for:
15
+ * - Array items with primitive types (e.g., array of strings)
16
+ * - Fields without explicit widget mapping
17
+ * - All text-based widgets internally
18
+ *
19
+ * @see https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-templates/#baseinputtemplate
20
+ */
21
+ export function BaseInputTemplate(props: WidgetProps) {
22
+ const {
23
+ id,
24
+ type,
25
+ value,
26
+ readonly,
27
+ disabled,
28
+ autofocus,
29
+ onBlur,
30
+ onFocus,
31
+ onChange,
32
+ options,
33
+ schema,
34
+ rawErrors,
35
+ placeholder,
36
+ } = props;
37
+
38
+ // Get input props from RJSF utils (handles step, min, max, etc.)
39
+ const inputProps = useMemo(() => {
40
+ return getInputProps(schema, type, options);
41
+ }, [schema, type, options]);
42
+
43
+ // Safely convert value to string for input
44
+ const safeValue = useMemo(() => {
45
+ if (value === null || value === undefined) return '';
46
+ return String(value);
47
+ }, [value]);
48
+
49
+ const hasError = useMemo(() => {
50
+ return rawErrors && rawErrors.length > 0;
51
+ }, [rawErrors]);
52
+
53
+ // Handle text change - transform empty strings based on schema type
54
+ const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
55
+ const val = event.target.value;
56
+
57
+ // Empty value handling
58
+ if (val === '') {
59
+ onChange(options?.emptyValue ?? '');
60
+ return;
61
+ }
62
+
63
+ // Number type conversion
64
+ if (inputProps.type === 'number' || schema.type === 'number' || schema.type === 'integer') {
65
+ const num = Number(val);
66
+ onChange(isNaN(num) ? val : num);
67
+ return;
68
+ }
69
+
70
+ onChange(val);
71
+ }, [onChange, inputProps.type, schema.type, options?.emptyValue]);
72
+
73
+ const handleBlur = useCallback((event: FocusEvent<HTMLInputElement>) => {
74
+ onBlur(id, event.target.value);
75
+ }, [id, onBlur]);
76
+
77
+ const handleFocus = useCallback((event: FocusEvent<HTMLInputElement>) => {
78
+ onFocus(id, event.target.value);
79
+ }, [id, onFocus]);
80
+
81
+ // Determine input type
82
+ const inputType = useMemo(() => {
83
+ if (inputProps.type) return inputProps.type;
84
+ if (schema.type === 'number' || schema.type === 'integer') return 'number';
85
+ return 'text';
86
+ }, [inputProps.type, schema.type]);
87
+
88
+ return (
89
+ <Input
90
+ id={id}
91
+ type={inputType}
92
+ value={safeValue}
93
+ disabled={disabled}
94
+ readOnly={readonly}
95
+ autoFocus={autofocus}
96
+ onChange={handleChange}
97
+ onBlur={handleBlur}
98
+ onFocus={handleFocus}
99
+ placeholder={placeholder}
100
+ className={hasError ? 'border-destructive' : ''}
101
+ step={inputProps.step}
102
+ min={inputProps.min}
103
+ max={inputProps.max}
104
+ />
105
+ );
106
+ }
@@ -0,0 +1,34 @@
1
+ "use client"
2
+
3
+ import React from 'react';
4
+ import { ErrorListProps } from '@rjsf/utils';
5
+ import { Alert, AlertDescription, AlertTitle } from '@djangocfg/ui-core/components';
6
+ import { AlertCircle } from 'lucide-react';
7
+
8
+ /**
9
+ * Error list template for JSON Schema Form
10
+ * Displays validation errors at the top of the form
11
+ */
12
+ export function ErrorListTemplate(props: ErrorListProps) {
13
+ const { errors } = props;
14
+
15
+ if (!errors || errors.length === 0) {
16
+ return null;
17
+ }
18
+
19
+ return (
20
+ <Alert variant="destructive" className="mb-6">
21
+ <AlertCircle className="h-4 w-4" />
22
+ <AlertTitle>Validation Errors</AlertTitle>
23
+ <AlertDescription>
24
+ <ul className="list-disc list-inside space-y-1 mt-2">
25
+ {errors.map((error, index) => (
26
+ <li key={index} className="text-sm">
27
+ {error.stack}
28
+ </li>
29
+ ))}
30
+ </ul>
31
+ </AlertDescription>
32
+ </Alert>
33
+ );
34
+ }
@@ -0,0 +1,61 @@
1
+ "use client"
2
+
3
+ import React from 'react';
4
+ import { FieldTemplateProps } from '@rjsf/utils';
5
+ import { Label } from '@djangocfg/ui-core/components';
6
+ import { cn } from '@djangocfg/ui-core/lib';
7
+
8
+ /**
9
+ * Field template for JSON Schema Form
10
+ * Controls the layout and styling of individual form fields
11
+ */
12
+ export function FieldTemplate(props: FieldTemplateProps) {
13
+ const {
14
+ id,
15
+ classNames,
16
+ style,
17
+ label,
18
+ help,
19
+ required,
20
+ description,
21
+ errors,
22
+ children,
23
+ displayLabel,
24
+ hidden,
25
+ rawErrors,
26
+ } = props;
27
+
28
+ if (hidden) {
29
+ return <div className="hidden">{children}</div>;
30
+ }
31
+
32
+ const hasError = rawErrors && rawErrors.length > 0;
33
+
34
+ return (
35
+ <div
36
+ className={cn('space-y-2', classNames)}
37
+ style={style}
38
+ >
39
+ {displayLabel && label && (
40
+ <Label htmlFor={id} className={cn(hasError && 'text-destructive')}>
41
+ {label}
42
+ {required && <span className="text-destructive ml-1">*</span>}
43
+ </Label>
44
+ )}
45
+
46
+ {description && (
47
+ <div className="text-sm text-muted-foreground">{description}</div>
48
+ )}
49
+
50
+ <div>{children}</div>
51
+
52
+ {errors && (
53
+ <div className="text-sm text-destructive">{errors}</div>
54
+ )}
55
+
56
+ {help && (
57
+ <div className="text-sm text-muted-foreground">{help}</div>
58
+ )}
59
+ </div>
60
+ );
61
+ }
@@ -0,0 +1,43 @@
1
+ "use client"
2
+
3
+ import React from 'react';
4
+ import { ObjectFieldTemplateProps } from '@rjsf/utils';
5
+ import { cn } from '@djangocfg/ui-core/lib';
6
+
7
+ /**
8
+ * Object field template for JSON Schema Form
9
+ * Renders nested object properties in a structured layout
10
+ */
11
+ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
12
+ const {
13
+ title,
14
+ description,
15
+ properties,
16
+ required,
17
+ uiSchema,
18
+ } = props;
19
+
20
+ return (
21
+ <div className="space-y-6">
22
+ {title && (
23
+ <div>
24
+ <h3 className="text-lg font-semibold">
25
+ {title}
26
+ {required && <span className="text-destructive ml-1">*</span>}
27
+ </h3>
28
+ {description && (
29
+ <p className="text-sm text-muted-foreground mt-1">{description}</p>
30
+ )}
31
+ </div>
32
+ )}
33
+
34
+ <div className={cn('space-y-4', uiSchema?.['ui:className'])}>
35
+ {properties.map((element) => (
36
+ <div key={element.name} className="property-wrapper">
37
+ {element.content}
38
+ </div>
39
+ ))}
40
+ </div>
41
+ </div>
42
+ );
43
+ }
@@ -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,212 @@
1
+ import { RJSFSchema } from '@rjsf/utils';
2
+ import consola from 'consola';
3
+
4
+ /**
5
+ * Utility functions for JSON Schema Form
6
+ */
7
+
8
+ /**
9
+ * Safely validates and normalizes JSON Schema
10
+ * Ensures schema is valid before rendering
11
+ */
12
+ export function validateSchema(schema: any): RJSFSchema | null {
13
+ if (!schema || typeof schema !== 'object') {
14
+ if (process.env.NODE_ENV === 'development') {
15
+ consola.error('[JsonSchemaForm] Invalid schema: must be an object', schema);
16
+ }
17
+ return null;
18
+ }
19
+
20
+ // Basic schema validation - more permissive
21
+ // Schema is valid if it has type OR properties OR $ref OR $schema
22
+ const hasValidStructure =
23
+ schema.type ||
24
+ schema.properties ||
25
+ schema.$ref ||
26
+ schema.$schema;
27
+
28
+ if (!hasValidStructure) {
29
+ if (process.env.NODE_ENV === 'development') {
30
+ consola.error('[JsonSchemaForm] Invalid schema: missing type, properties, $ref, or $schema', schema);
31
+ }
32
+ return null;
33
+ }
34
+
35
+ if (process.env.NODE_ENV === 'development') {
36
+ consola.success('[JsonSchemaForm] Schema validated successfully:', {
37
+ type: schema.type,
38
+ title: schema.title,
39
+ hasProperties: !!schema.properties,
40
+ hasRequired: !!schema.required,
41
+ });
42
+ }
43
+
44
+ return schema as RJSFSchema;
45
+ }
46
+
47
+ /**
48
+ * Safely normalizes form data
49
+ * Removes undefined values and ensures data structure matches schema
50
+ */
51
+ export function normalizeFormData<T = any>(
52
+ formData: any,
53
+ schema: RJSFSchema
54
+ ): T {
55
+ if (formData === null || formData === undefined) {
56
+ return (schema.type === 'object' ? {} : schema.type === 'array' ? [] : null) as T;
57
+ }
58
+
59
+ // Deep clone to avoid mutations
60
+ const normalized = JSON.parse(JSON.stringify(formData));
61
+
62
+ // Remove undefined values recursively
63
+ return removeUndefined(normalized) as T;
64
+ }
65
+
66
+ /**
67
+ * Recursively removes undefined values from an object
68
+ */
69
+ function removeUndefined(obj: any): any {
70
+ if (obj === null || obj === undefined) {
71
+ return obj;
72
+ }
73
+
74
+ if (Array.isArray(obj)) {
75
+ return obj.map(removeUndefined).filter((item) => item !== undefined);
76
+ }
77
+
78
+ if (typeof obj === 'object') {
79
+ const cleaned: any = {};
80
+ for (const key in obj) {
81
+ if (obj[key] !== undefined) {
82
+ cleaned[key] = removeUndefined(obj[key]);
83
+ }
84
+ }
85
+ return cleaned;
86
+ }
87
+
88
+ return obj;
89
+ }
90
+
91
+ /**
92
+ * Merges schema defaults with form data
93
+ */
94
+ export function mergeDefaults(
95
+ formData: any,
96
+ schema: RJSFSchema
97
+ ): any {
98
+ if (!schema) return formData;
99
+
100
+ const result = { ...formData };
101
+
102
+ if (schema.type === 'object' && schema.properties) {
103
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
104
+ const prop = propSchema as RJSFSchema;
105
+
106
+ // Apply default if field is missing
107
+ if (result[key] === undefined && prop.default !== undefined) {
108
+ result[key] = prop.default;
109
+ }
110
+
111
+ // Recursively merge nested objects
112
+ if (prop.type === 'object' && result[key]) {
113
+ result[key] = mergeDefaults(result[key], prop);
114
+ }
115
+ }
116
+ }
117
+
118
+ return result;
119
+ }
120
+
121
+ /**
122
+ * Safely parses JSON string with error handling
123
+ */
124
+ export function safeJsonParse<T = any>(
125
+ jsonString: string,
126
+ fallback: T
127
+ ): T {
128
+ try {
129
+ return JSON.parse(jsonString);
130
+ } catch (error) {
131
+ consola.error('[JsonSchemaForm] JSON parse error:', error);
132
+ return fallback;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Safely stringifies object to JSON
138
+ */
139
+ export function safeJsonStringify(
140
+ obj: any,
141
+ pretty: boolean = true
142
+ ): string {
143
+ try {
144
+ return JSON.stringify(obj, null, pretty ? 2 : 0);
145
+ } catch (error) {
146
+ consola.error('[JsonSchemaForm] JSON stringify error:', error);
147
+ return '{}';
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Checks if schema has required fields
153
+ */
154
+ export function hasRequiredFields(schema: RJSFSchema): boolean {
155
+ return Array.isArray(schema.required) && schema.required.length > 0;
156
+ }
157
+
158
+ /**
159
+ * Gets all required field paths from schema
160
+ */
161
+ export function getRequiredFields(
162
+ schema: RJSFSchema,
163
+ prefix: string = ''
164
+ ): string[] {
165
+ const required: string[] = [];
166
+
167
+ if (schema.required && Array.isArray(schema.required)) {
168
+ required.push(...schema.required.map((field) =>
169
+ prefix ? `${prefix}.${field}` : field
170
+ ));
171
+ }
172
+
173
+ if (schema.type === 'object' && schema.properties) {
174
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
175
+ const prop = propSchema as RJSFSchema;
176
+ const fieldPath = prefix ? `${prefix}.${key}` : key;
177
+ required.push(...getRequiredFields(prop, fieldPath));
178
+ }
179
+ }
180
+
181
+ return required;
182
+ }
183
+
184
+ /**
185
+ * Validates form data against required fields
186
+ */
187
+ export function validateRequiredFields(
188
+ formData: any,
189
+ schema: RJSFSchema
190
+ ): { valid: boolean; missing: string[] } {
191
+ const requiredFields = getRequiredFields(schema);
192
+ const missing: string[] = [];
193
+
194
+ for (const field of requiredFields) {
195
+ const value = getNestedValue(formData, field);
196
+ if (value === undefined || value === null || value === '') {
197
+ missing.push(field);
198
+ }
199
+ }
200
+
201
+ return {
202
+ valid: missing.length === 0,
203
+ missing,
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Gets nested value from object by path
209
+ */
210
+ function getNestedValue(obj: any, path: string): any {
211
+ return path.split('.').reduce((current, key) => current?.[key], obj);
212
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { WidgetProps } from '@rjsf/utils';
3
+ import { Checkbox } from '@djangocfg/ui-core/components';
4
+
5
+ /**
6
+ * Checkbox widget for JSON Schema Form
7
+ * Handles boolean fields
8
+ */
9
+ export function CheckboxWidget(props: WidgetProps) {
10
+ const {
11
+ id,
12
+ value,
13
+ disabled,
14
+ readonly,
15
+ autofocus,
16
+ onChange,
17
+ onBlur,
18
+ onFocus,
19
+ } = props;
20
+
21
+ const handleChange = (checked: boolean) => {
22
+ onChange(checked);
23
+ };
24
+
25
+ return (
26
+ <Checkbox
27
+ id={id}
28
+ checked={value || false}
29
+ disabled={disabled || readonly}
30
+ autoFocus={autofocus}
31
+ onCheckedChange={handleChange}
32
+ onBlur={() => onBlur(id, value)}
33
+ onFocus={() => onFocus(id, value)}
34
+ />
35
+ );
36
+ }