@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,88 @@
1
+ "use client"
2
+
3
+ import React, { useMemo, useCallback } from 'react';
4
+ import { WidgetProps } from '@rjsf/utils';
5
+ import { Input } from '@djangocfg/ui-core/components';
6
+
7
+ /**
8
+ * Number input widget for JSON Schema Form
9
+ * Handles integer and number fields
10
+ */
11
+ export function NumberWidget(props: WidgetProps) {
12
+ const {
13
+ id,
14
+ placeholder,
15
+ required,
16
+ disabled,
17
+ readonly,
18
+ autofocus,
19
+ value,
20
+ onChange,
21
+ onBlur,
22
+ onFocus,
23
+ options,
24
+ schema,
25
+ rawErrors,
26
+ } = props;
27
+
28
+ // Memoize widget configuration
29
+ const config = useMemo(() => ({
30
+ isInteger: schema.type === 'integer',
31
+ step: schema.type === 'integer' ? '1' : 'any',
32
+ min: schema.minimum,
33
+ max: schema.maximum,
34
+ emptyValue: options?.emptyValue,
35
+ }), [schema, options]);
36
+
37
+ // Ensure value is valid number or empty string
38
+ const safeValue = useMemo(() => {
39
+ if (value === null || value === undefined || value === '') return '';
40
+ if (typeof value === 'number' && !isNaN(value)) return value;
41
+ return '';
42
+ }, [value]);
43
+
44
+ const hasError = useMemo(() => {
45
+ return rawErrors && rawErrors.length > 0;
46
+ }, [rawErrors]);
47
+
48
+ const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
49
+ const newValue = event.target.value;
50
+ if (newValue === '') {
51
+ onChange(config.emptyValue);
52
+ } else {
53
+ const parsedValue = config.isInteger
54
+ ? parseInt(newValue, 10)
55
+ : parseFloat(newValue);
56
+
57
+ onChange(isNaN(parsedValue) ? config.emptyValue : parsedValue);
58
+ }
59
+ }, [onChange, config]);
60
+
61
+ const handleBlur = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
62
+ onBlur(id, event.target.value);
63
+ }, [id, onBlur]);
64
+
65
+ const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
66
+ onFocus(id, event.target.value);
67
+ }, [id, onFocus]);
68
+
69
+ return (
70
+ <Input
71
+ id={id}
72
+ type="number"
73
+ placeholder={placeholder}
74
+ disabled={disabled}
75
+ readOnly={readonly}
76
+ autoFocus={autofocus}
77
+ value={safeValue}
78
+ required={required}
79
+ onChange={handleChange}
80
+ onBlur={handleBlur}
81
+ onFocus={handleFocus}
82
+ step={config.step}
83
+ min={config.min}
84
+ max={config.max}
85
+ className={hasError ? 'border-destructive' : ''}
86
+ />
87
+ );
88
+ }
@@ -0,0 +1,100 @@
1
+ "use client"
2
+
3
+ import React, { useMemo, useCallback } from 'react';
4
+ import { WidgetProps } from '@rjsf/utils';
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectItem,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from '@djangocfg/ui-core/components';
12
+
13
+ /**
14
+ * Select dropdown widget for JSON Schema Form
15
+ * Handles enum fields
16
+ */
17
+ export function SelectWidget(props: WidgetProps) {
18
+ const {
19
+ id,
20
+ options,
21
+ value,
22
+ required,
23
+ disabled,
24
+ readonly,
25
+ autofocus,
26
+ onChange,
27
+ onBlur,
28
+ onFocus,
29
+ placeholder,
30
+ rawErrors,
31
+ } = props;
32
+
33
+ // Safely extract and validate enum options
34
+ const enumOptions = useMemo(() => {
35
+ const opts = options?.enumOptions;
36
+ if (!Array.isArray(opts)) return [];
37
+ return opts.filter(opt => opt && (opt.value !== undefined));
38
+ }, [options]);
39
+
40
+ const hasError = useMemo(() => {
41
+ return rawErrors && rawErrors.length > 0;
42
+ }, [rawErrors]);
43
+
44
+ // Ensure value is always a string
45
+ const safeValue = useMemo(() => {
46
+ if (value === null || value === undefined) return '';
47
+ return String(value);
48
+ }, [value]);
49
+
50
+ const handleChange = useCallback((newValue: string) => {
51
+ onChange(newValue);
52
+ onBlur(id, newValue);
53
+ }, [onChange, onBlur, id]);
54
+
55
+ // If no enum options, render a text input instead
56
+ // This handles cases like anyOf/oneOf where RJSF might incorrectly use SelectWidget
57
+ if (enumOptions.length === 0) {
58
+ return (
59
+ <input
60
+ id={id}
61
+ type="text"
62
+ value={safeValue}
63
+ onChange={(e) => onChange(e.target.value)}
64
+ onBlur={(e) => onBlur(id, e.target.value)}
65
+ onFocus={(e) => onFocus(id, e.target.value)}
66
+ disabled={disabled || readonly}
67
+ readOnly={readonly}
68
+ className={`flex h-10 w-full rounded-md border ${
69
+ hasError ? 'border-destructive' : 'border-input'
70
+ } 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`}
71
+ placeholder={placeholder}
72
+ />
73
+ );
74
+ }
75
+
76
+ return (
77
+ <Select
78
+ value={safeValue}
79
+ onValueChange={handleChange}
80
+ disabled={disabled || readonly}
81
+ required={required}
82
+ >
83
+ <SelectTrigger
84
+ id={id}
85
+ className={hasError ? 'border-destructive' : ''}
86
+ autoFocus={autofocus}
87
+ onFocus={() => onFocus(id, value)}
88
+ >
89
+ <SelectValue placeholder={placeholder || 'Select an option'} />
90
+ </SelectTrigger>
91
+ <SelectContent>
92
+ {enumOptions.map((option: any) => (
93
+ <SelectItem key={String(option.value)} value={String(option.value)}>
94
+ {option.label || String(option.value)}
95
+ </SelectItem>
96
+ ))}
97
+ </SelectContent>
98
+ </Select>
99
+ );
100
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { WidgetProps } from '@rjsf/utils';
3
+ import { Switch } from '@djangocfg/ui-core/components';
4
+
5
+ /**
6
+ * Switch toggle widget for JSON Schema Form
7
+ * Alternative boolean input
8
+ */
9
+ export function SwitchWidget(props: WidgetProps) {
10
+ const {
11
+ id,
12
+ value,
13
+ disabled,
14
+ readonly,
15
+ onChange,
16
+ onBlur,
17
+ onFocus,
18
+ } = props;
19
+
20
+ const handleChange = (checked: boolean) => {
21
+ onChange(checked);
22
+ };
23
+
24
+ return (
25
+ <Switch
26
+ id={id}
27
+ checked={value || false}
28
+ disabled={disabled || readonly}
29
+ onCheckedChange={handleChange}
30
+ onBlur={() => onBlur(id, value)}
31
+ onFocus={() => onFocus(id, value)}
32
+ />
33
+ );
34
+ }
@@ -0,0 +1,95 @@
1
+ "use client"
2
+
3
+ import React, { useMemo, useCallback } from 'react';
4
+ import { WidgetProps } from '@rjsf/utils';
5
+ import { Input } from '@djangocfg/ui-core/components';
6
+
7
+ /**
8
+ * Text input widget for JSON Schema Form
9
+ * Handles string fields with optional textarea for multiline
10
+ */
11
+ export function TextWidget(props: WidgetProps) {
12
+ const {
13
+ id,
14
+ placeholder,
15
+ required,
16
+ disabled,
17
+ readonly,
18
+ autofocus,
19
+ value,
20
+ onChange,
21
+ onBlur,
22
+ onFocus,
23
+ options,
24
+ schema,
25
+ rawErrors,
26
+ } = props;
27
+
28
+ // Memoize widget configuration
29
+ const config = useMemo(() => ({
30
+ isTextarea: options?.widget === 'textarea',
31
+ rows: options?.rows || 3,
32
+ emptyValue: options?.emptyValue,
33
+ }), [options]);
34
+
35
+ // Ensure value is always a string
36
+ const safeValue = useMemo(() => {
37
+ if (value === null || value === undefined) return '';
38
+ return String(value);
39
+ }, [value]);
40
+
41
+ const hasError = useMemo(() => {
42
+ return rawErrors && rawErrors.length > 0;
43
+ }, [rawErrors]);
44
+
45
+ const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
46
+ const newValue = event.target.value;
47
+ onChange(newValue === '' ? config.emptyValue : newValue);
48
+ }, [onChange, config.emptyValue]);
49
+
50
+ const handleBlur = useCallback((event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
51
+ onBlur(id, event.target.value);
52
+ }, [id, onBlur]);
53
+
54
+ const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
55
+ onFocus(id, event.target.value);
56
+ }, [id, onFocus]);
57
+
58
+ if (config.isTextarea) {
59
+ return (
60
+ <textarea
61
+ id={id}
62
+ className={`flex min-h-[80px] w-full rounded-md border ${
63
+ hasError ? 'border-destructive' : 'border-input'
64
+ } 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`}
65
+ placeholder={placeholder}
66
+ disabled={disabled || readonly}
67
+ readOnly={readonly}
68
+ autoFocus={autofocus}
69
+ value={safeValue}
70
+ required={required}
71
+ onChange={handleChange}
72
+ onBlur={handleBlur}
73
+ onFocus={handleFocus}
74
+ rows={config.rows}
75
+ />
76
+ );
77
+ }
78
+
79
+ return (
80
+ <Input
81
+ id={id}
82
+ type="text"
83
+ placeholder={placeholder}
84
+ disabled={disabled}
85
+ readOnly={readonly}
86
+ autoFocus={autofocus}
87
+ value={safeValue}
88
+ required={required}
89
+ onChange={handleChange}
90
+ onBlur={handleBlur}
91
+ onFocus={handleFocus}
92
+ className={hasError ? 'border-destructive' : ''}
93
+ />
94
+ );
95
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Custom widgets for JSON Schema Form
3
+ *
4
+ * Each widget is a React component that renders a specific form input type
5
+ * using components from @djangocfg/ui
6
+ */
7
+
8
+ export { TextWidget } from './TextWidget';
9
+ export { NumberWidget } from './NumberWidget';
10
+ export { CheckboxWidget } from './CheckboxWidget';
11
+ export { SelectWidget } from './SelectWidget';
12
+ export { SwitchWidget } from './SwitchWidget';
@@ -0,0 +1,252 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { CommonExternalProps, JSONTree } from 'react-json-tree';
5
+ import { ChevronDown, ChevronUp, Copy, Download } from 'lucide-react';
6
+ import { Button } from '@djangocfg/ui-core/components';
7
+ import { useCopy } from '@djangocfg/ui-core/hooks';
8
+
9
+ export type { Language } from 'prism-react-renderer';
10
+
11
+ export interface JsonTreeConfig {
12
+ /** Maximum depth to expand automatically (default: 2) */
13
+ maxAutoExpandDepth?: number;
14
+ /** Maximum items in array to auto-expand (default: 10) */
15
+ maxAutoExpandArrayItems?: number;
16
+ /** Maximum object keys to auto-expand (default: 5) */
17
+ maxAutoExpandObjectKeys?: number;
18
+ /** Maximum string length before truncation (default: 200) */
19
+ maxStringLength?: number;
20
+ /** Collection limit for performance (default: 50) */
21
+ collectionLimit?: number;
22
+ /** Whether to show collection info (array length, object keys count) */
23
+ showCollectionInfo?: boolean;
24
+ /** Whether to show expand/collapse all buttons */
25
+ showExpandControls?: boolean;
26
+ /** Whether to show copy/download buttons */
27
+ showActionButtons?: boolean;
28
+ /** Custom CSS classes for the container */
29
+ className?: string;
30
+ /** Whether to preserve object key order (default: true) */
31
+ preserveKeyOrder?: boolean;
32
+ }
33
+
34
+ interface JsonTreeComponentProps {
35
+ title?: string;
36
+ data: unknown;
37
+ config?: JsonTreeConfig;
38
+ /** Override for react-json-tree props */
39
+ jsonTreeProps?: Partial<CommonExternalProps>;
40
+ }
41
+
42
+ const JsonTreeComponent = ({ title, data, config = {}, jsonTreeProps = {} }: JsonTreeComponentProps) => {
43
+ // State for expand/collapse all
44
+ const [expandAll, setExpandAll] = useState<boolean | null>(null);
45
+ const [renderKey, setRenderKey] = useState(0);
46
+
47
+ // Copy hook
48
+ const { copyToClipboard } = useCopy({
49
+ successMessage: "JSON copied to clipboard",
50
+ errorMessage: "Failed to copy JSON"
51
+ });
52
+
53
+ // Default configuration
54
+ const {
55
+ maxAutoExpandDepth = 2,
56
+ maxAutoExpandArrayItems = 10,
57
+ maxAutoExpandObjectKeys = 5,
58
+ maxStringLength = 200,
59
+ collectionLimit = 50,
60
+ showCollectionInfo = true,
61
+ showExpandControls = true,
62
+ showActionButtons = true,
63
+ className = '',
64
+ preserveKeyOrder = true,
65
+ } = config;
66
+
67
+ // JSON Tree theme optimized for dark theme
68
+ const jsonTreeTheme = {
69
+ scheme: 'djangocfg-dark',
70
+ base00: 'transparent', // Background
71
+ base01: '#1a1a1a', // Lighter background
72
+ base02: '#2a2a2a', // Selection background
73
+ base03: '#6b7280', // Comments, invisibles
74
+ base04: '#9ca3af', // Dark foreground
75
+ base05: '#e5e7eb', // Default foreground
76
+ base06: '#f3f4f6', // Light foreground
77
+ base07: '#ffffff', // Lightest foreground
78
+ base08: '#ef4444', // Red - for null, undefined
79
+ base09: '#f97316', // Orange - for numbers
80
+ base0A: '#eab308', // Yellow - for strings
81
+ base0B: '#22c55e', // Green - for booleans (true)
82
+ base0C: '#06b6d4', // Cyan - for dates, regex
83
+ base0D: '#3b82f6', // Blue - for keys
84
+ base0E: '#a855f7', // Purple - for functions
85
+ base0F: '#f43f5e', // Pink - for deprecations
86
+ };
87
+
88
+ // Smart expansion logic
89
+ const shouldExpandNodeInitially = (keyPath: readonly (string | number)[], nodeData: unknown, level: number) => {
90
+ // If user explicitly clicked "Expand All", expand everything
91
+ if (expandAll === true) return true;
92
+
93
+ // If user explicitly clicked "Collapse All", collapse everything
94
+ if (expandAll === false) return false;
95
+
96
+ // Default auto-expansion (expandAll === null)
97
+ // Always expand up to maxAutoExpandDepth
98
+ if (level <= maxAutoExpandDepth) return true;
99
+
100
+ // For arrays, expand if they have less than maxAutoExpandArrayItems
101
+ if (Array.isArray(nodeData) && nodeData.length <= maxAutoExpandArrayItems) return true;
102
+
103
+ // For objects, expand if they have less than maxAutoExpandObjectKeys
104
+ if (nodeData && typeof nodeData === 'object' && !Array.isArray(nodeData)) {
105
+ const keys = Object.keys(nodeData);
106
+ return keys.length <= maxAutoExpandObjectKeys;
107
+ }
108
+
109
+ return false;
110
+ };
111
+
112
+ // Collection info display
113
+ const getItemString = showCollectionInfo
114
+ ? (nodeType: string, nodeData: unknown) => {
115
+ if (nodeType === 'Array') {
116
+ const length = Array.isArray(nodeData) ? nodeData.length : 0;
117
+ return length > 0 ? <span className="text-muted-foreground text-sm">({length} items)</span> : null;
118
+ }
119
+ if (nodeType === 'Object') {
120
+ const keys = nodeData && typeof nodeData === 'object' ? Object.keys(nodeData) : [];
121
+ return keys.length > 0 ? <span className="text-muted-foreground text-sm">({keys.length} keys)</span> : null;
122
+ }
123
+ return null;
124
+ }
125
+ : () => null;
126
+
127
+ // Value processing for better display
128
+ const postprocessValue = (value: unknown) => {
129
+ // Truncate very long strings
130
+ if (typeof value === 'string' && value.length > maxStringLength) {
131
+ return value.substring(0, maxStringLength) + '... (truncated)';
132
+ }
133
+ return value;
134
+ };
135
+
136
+ // Custom node detection for special formatting
137
+ const isCustomNode = (value: unknown) => {
138
+ // Mark URLs as custom nodes for special styling
139
+ if (typeof value === 'string' && (value.startsWith('http://') || value.startsWith('https://'))) {
140
+ return true;
141
+ }
142
+ return false;
143
+ };
144
+
145
+ // Action handlers
146
+ const handleCopy = () => {
147
+ const jsonString = JSON.stringify(data, null, 2);
148
+ copyToClipboard(jsonString);
149
+ };
150
+
151
+ const handleDownload = () => {
152
+ const jsonString = JSON.stringify(data, null, 2);
153
+ const blob = new Blob([jsonString], { type: 'application/json' });
154
+ const url = URL.createObjectURL(blob);
155
+ const a = document.createElement('a');
156
+ a.href = url;
157
+ a.download = 'data.json';
158
+ document.body.appendChild(a);
159
+ a.click();
160
+ document.body.removeChild(a);
161
+ URL.revokeObjectURL(url);
162
+ };
163
+
164
+ return (
165
+ <div className={`relative border border-border rounded-sm h-full overflow-hidden ${className}`}>
166
+ {/* Header with title and controls */}
167
+ {(title || showExpandControls || showActionButtons) && (
168
+ <div className="p-4 border-b border-border bg-muted/50 rounded-t-sm">
169
+ <div className="flex items-center justify-between">
170
+ {title && (
171
+ <h6 className="text-lg font-semibold text-foreground">{title}</h6>
172
+ )}
173
+
174
+ {(showExpandControls || showActionButtons) && (
175
+ <div className="flex items-center space-x-2">
176
+ {/* Expand/Collapse Controls */}
177
+ {showExpandControls && (
178
+ <Button
179
+ variant={expandAll === true ? "default" : "outline"}
180
+ size="sm"
181
+ onClick={() => {
182
+ const newState = expandAll === true ? false : true;
183
+ setExpandAll(newState);
184
+ setRenderKey(prev => prev + 1);
185
+ }}
186
+ className="h-8 px-2"
187
+ >
188
+ {expandAll === true ? (
189
+ <>
190
+ <ChevronUp className="h-3 w-3" />
191
+ <span className="ml-1 text-xs">Collapse All</span>
192
+ </>
193
+ ) : (
194
+ <>
195
+ <ChevronDown className="h-3 w-3" />
196
+ <span className="ml-1 text-xs">Expand All</span>
197
+ </>
198
+ )}
199
+ </Button>
200
+ )}
201
+
202
+ {/* Action Buttons */}
203
+ {showActionButtons && (
204
+ <>
205
+ <Button
206
+ variant="outline"
207
+ size="sm"
208
+ onClick={handleCopy}
209
+ className="h-8 px-2"
210
+ >
211
+ <Copy className="h-3 w-3" />
212
+ <span className="ml-1 text-xs hidden sm:inline">Copy</span>
213
+ </Button>
214
+ <Button
215
+ variant="outline"
216
+ size="sm"
217
+ onClick={handleDownload}
218
+ className="h-8 px-2"
219
+ >
220
+ <Download className="h-3 w-3" />
221
+ <span className="ml-1 text-xs hidden sm:inline">Download</span>
222
+ </Button>
223
+ </>
224
+ )}
225
+ </div>
226
+ )}
227
+ </div>
228
+ </div>
229
+ )}
230
+
231
+ {/* JSON Tree Content */}
232
+ <div className="h-full overflow-auto p-4">
233
+ <JSONTree
234
+ key={renderKey} // Force re-render when expand/collapse state changes
235
+ data={data}
236
+ theme={jsonTreeTheme}
237
+ invertTheme={false}
238
+ hideRoot={true}
239
+ shouldExpandNodeInitially={shouldExpandNodeInitially}
240
+ getItemString={getItemString}
241
+ postprocessValue={postprocessValue}
242
+ isCustomNode={isCustomNode}
243
+ collectionLimit={collectionLimit}
244
+ sortObjectKeys={!preserveKeyOrder}
245
+ {...jsonTreeProps}
246
+ />
247
+ </div>
248
+ </div>
249
+ );
250
+ };
251
+
252
+ export default JsonTreeComponent;