@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.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/package.json +110 -0
- package/src/animations/AnimatedBackground.tsx +645 -0
- package/src/animations/index.ts +2 -0
- package/src/blocks/ArticleCard.tsx +94 -0
- package/src/blocks/ArticleList.tsx +95 -0
- package/src/blocks/CTASection.tsx +136 -0
- package/src/blocks/FeatureSection.tsx +104 -0
- package/src/blocks/Hero.tsx +102 -0
- package/src/blocks/NewsletterSection.tsx +119 -0
- package/src/blocks/StatsSection.tsx +103 -0
- package/src/blocks/SuperHero.tsx +328 -0
- package/src/blocks/TestimonialSection.tsx +122 -0
- package/src/blocks/index.ts +9 -0
- package/src/components/README.md +2018 -0
- package/src/components/breadcrumb-navigation.tsx +127 -0
- package/src/components/breadcrumb.tsx +132 -0
- package/src/components/button-download.tsx +275 -0
- package/src/components/dropdown-menu.tsx +219 -0
- package/src/components/index.ts +86 -0
- package/src/components/markdown/MarkdownMessage.tsx +338 -0
- package/src/components/markdown/index.ts +5 -0
- package/src/components/menubar.tsx +274 -0
- package/src/components/multi-select-pro/async.tsx +608 -0
- package/src/components/multi-select-pro/helpers.tsx +84 -0
- package/src/components/multi-select-pro/index.tsx +622 -0
- package/src/components/navigation-menu.tsx +153 -0
- package/src/components/pagination-static.tsx +348 -0
- package/src/components/pagination.tsx +138 -0
- package/src/components/phone-input.tsx +276 -0
- package/src/components/sidebar.tsx +866 -0
- package/src/components/sonner.tsx +31 -0
- package/src/components/ssr-pagination.tsx +237 -0
- package/src/hooks/index.ts +19 -0
- package/src/hooks/useCfgRouter.ts +153 -0
- package/src/hooks/useLocalStorage.ts +221 -0
- package/src/hooks/useQueryParams.ts +73 -0
- package/src/hooks/useSessionStorage.ts +188 -0
- package/src/hooks/useTheme.ts +57 -0
- package/src/index.ts +24 -0
- package/src/lib/index.ts +2 -0
- package/src/styles/index.css +2 -0
- package/src/theme/ForceTheme.tsx +115 -0
- package/src/theme/ThemeProvider.tsx +82 -0
- package/src/theme/ThemeToggle.tsx +52 -0
- package/src/theme/index.ts +3 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +199 -0
- package/src/tools/JsonForm/examples/BotConfigExample.tsx +245 -0
- package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +157 -0
- package/src/tools/JsonForm/index.ts +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +73 -0
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +106 -0
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +34 -0
- package/src/tools/JsonForm/templates/FieldTemplate.tsx +61 -0
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +43 -0
- package/src/tools/JsonForm/templates/index.ts +12 -0
- package/src/tools/JsonForm/types.ts +83 -0
- package/src/tools/JsonForm/utils.ts +212 -0
- package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +36 -0
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +88 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +100 -0
- package/src/tools/JsonForm/widgets/SwitchWidget.tsx +34 -0
- package/src/tools/JsonForm/widgets/TextWidget.tsx +95 -0
- package/src/tools/JsonForm/widgets/index.ts +12 -0
- package/src/tools/JsonTree/index.tsx +252 -0
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +212 -0
- package/src/tools/LottiePlayer/index.tsx +54 -0
- package/src/tools/LottiePlayer/types.ts +108 -0
- package/src/tools/LottiePlayer/useLottie.ts +163 -0
- package/src/tools/Mermaid/Mermaid.client.tsx +341 -0
- package/src/tools/Mermaid/index.tsx +40 -0
- package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +144 -0
- package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +255 -0
- package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +123 -0
- package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +98 -0
- package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +164 -0
- package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
- package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +169 -0
- package/src/tools/OpenapiViewer/components/VersionSelector.tsx +64 -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 +338 -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 +203 -0
- package/src/tools/OpenapiViewer/index.tsx +36 -0
- package/src/tools/OpenapiViewer/types.ts +152 -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 +217 -0
- package/src/tools/PrettyCode/index.tsx +43 -0
- package/src/tools/VideoPlayer/README.md +239 -0
- package/src/tools/VideoPlayer/VideoControls.tsx +138 -0
- package/src/tools/VideoPlayer/VideoPlayer.tsx +230 -0
- package/src/tools/VideoPlayer/index.ts +9 -0
- package/src/tools/VideoPlayer/types.ts +62 -0
- 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
|
+
}
|