@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,341 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import mermaid from 'mermaid';
|
|
4
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
6
|
+
import { useTheme } from '../../hooks/useTheme';
|
|
7
|
+
|
|
8
|
+
interface MermaidProps {
|
|
9
|
+
chart: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Utility function to apply text colors to Mermaid SVG
|
|
14
|
+
const applyMermaidTextColors = (container: HTMLElement, textColor: string) => {
|
|
15
|
+
const svgElement = container.querySelector('svg');
|
|
16
|
+
if (svgElement) {
|
|
17
|
+
// SVG text elements use 'fill'
|
|
18
|
+
svgElement.querySelectorAll('text').forEach((el) => {
|
|
19
|
+
(el as SVGElement).style.fill = textColor;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// HTML elements inside foreignObject use 'color'
|
|
23
|
+
svgElement.querySelectorAll('.nodeLabel, .edgeLabel').forEach((el) => {
|
|
24
|
+
(el as HTMLElement).style.color = textColor;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Detect if diagram is vertical (tall and narrow)
|
|
30
|
+
const isVerticalDiagram = (svgElement: SVGSVGElement): boolean => {
|
|
31
|
+
const viewBox = svgElement.getAttribute('viewBox');
|
|
32
|
+
if (viewBox) {
|
|
33
|
+
const [, , width, height] = viewBox.split(' ').map(Number);
|
|
34
|
+
// Consider vertical if height is more than 1.5x the width
|
|
35
|
+
return height > width * 1.5;
|
|
36
|
+
}
|
|
37
|
+
// Fallback to computed dimensions
|
|
38
|
+
const bbox = svgElement.getBBox?.();
|
|
39
|
+
if (bbox) {
|
|
40
|
+
return bbox.height > bbox.width * 1.5;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const Mermaid: React.FC<MermaidProps> = ({ chart, className = '' }) => {
|
|
46
|
+
const mermaidRef = useRef<HTMLDivElement>(null);
|
|
47
|
+
const fullscreenRef = useRef<HTMLDivElement>(null);
|
|
48
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
49
|
+
const [svgContent, setSvgContent] = useState<string>('');
|
|
50
|
+
const [isVertical, setIsVertical] = useState(false);
|
|
51
|
+
const theme = useTheme();
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
// Get CSS variables for semantic colors
|
|
55
|
+
const getCSSVariable = (variable: string) => {
|
|
56
|
+
if (typeof document === 'undefined') return '';
|
|
57
|
+
const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
|
|
58
|
+
return value ? `hsl(${value})` : '';
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Note: In Mermaid v11+, mermaidAPI.reset() no longer exists
|
|
62
|
+
// Re-initialization happens automatically via mermaid.initialize()
|
|
63
|
+
|
|
64
|
+
const themeVariables = theme === 'dark' ? {
|
|
65
|
+
// Dark theme - vibrant and clear
|
|
66
|
+
primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
67
|
+
primaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
68
|
+
primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
69
|
+
|
|
70
|
+
secondaryColor: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',
|
|
71
|
+
secondaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
72
|
+
secondaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
|
|
73
|
+
|
|
74
|
+
tertiaryColor: getCSSVariable('--accent') || 'hsl(217.2 32.6% 20%)',
|
|
75
|
+
tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
76
|
+
tertiaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
|
|
77
|
+
|
|
78
|
+
// Main elements - darker with good contrast
|
|
79
|
+
mainBkg: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
|
|
80
|
+
textColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
81
|
+
nodeBorder: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
|
|
82
|
+
nodeTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
83
|
+
|
|
84
|
+
// Alternative backgrounds
|
|
85
|
+
secondBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',
|
|
86
|
+
|
|
87
|
+
// Lines and edges - lighter for visibility
|
|
88
|
+
lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
89
|
+
edgeLabelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
|
|
90
|
+
|
|
91
|
+
// Clusters
|
|
92
|
+
clusterBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 12%)',
|
|
93
|
+
clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
94
|
+
|
|
95
|
+
// Background
|
|
96
|
+
background: getCSSVariable('--background') || 'hsl(222.2 84% 4.9%)',
|
|
97
|
+
|
|
98
|
+
// Labels
|
|
99
|
+
labelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
|
|
100
|
+
labelTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
101
|
+
|
|
102
|
+
// Special states
|
|
103
|
+
errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 62.8% 30.6%)',
|
|
104
|
+
errorTextColor: 'hsl(210 40% 98%)',
|
|
105
|
+
|
|
106
|
+
fontSize: '14px',
|
|
107
|
+
fontFamily: 'Inter, system-ui, sans-serif',
|
|
108
|
+
} : {
|
|
109
|
+
// Light theme - clean and professional
|
|
110
|
+
primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
111
|
+
primaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
112
|
+
primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
113
|
+
|
|
114
|
+
secondaryColor: getCSSVariable('--secondary') || 'hsl(210 40% 96.1%)',
|
|
115
|
+
secondaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
116
|
+
secondaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
|
|
117
|
+
|
|
118
|
+
tertiaryColor: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',
|
|
119
|
+
tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
120
|
+
tertiaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
|
|
121
|
+
|
|
122
|
+
// Main elements - white with good contrast
|
|
123
|
+
mainBkg: getCSSVariable('--card') || 'hsl(0 0% 100%)',
|
|
124
|
+
textColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
125
|
+
nodeBorder: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
|
|
126
|
+
nodeTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
127
|
+
|
|
128
|
+
// Alternative backgrounds
|
|
129
|
+
secondBkg: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',
|
|
130
|
+
|
|
131
|
+
// Lines and edges - vibrant primary color
|
|
132
|
+
lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
133
|
+
edgeLabelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',
|
|
134
|
+
|
|
135
|
+
// Clusters - subtle background
|
|
136
|
+
clusterBkg: getCSSVariable('--accent') || 'hsl(210 40% 98%)',
|
|
137
|
+
clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
138
|
+
|
|
139
|
+
// Background
|
|
140
|
+
background: getCSSVariable('--background') || 'hsl(0 0% 100%)',
|
|
141
|
+
|
|
142
|
+
// Labels
|
|
143
|
+
labelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',
|
|
144
|
+
labelTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
145
|
+
|
|
146
|
+
// Special states
|
|
147
|
+
errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 84.2% 60.2%)',
|
|
148
|
+
errorTextColor: 'hsl(210 40% 98%)',
|
|
149
|
+
|
|
150
|
+
fontSize: '14px',
|
|
151
|
+
fontFamily: 'Inter, system-ui, sans-serif',
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Initialize mermaid with dynamic theme configuration
|
|
155
|
+
mermaid.initialize({
|
|
156
|
+
startOnLoad: false,
|
|
157
|
+
theme: 'base', // Use 'base' theme for better custom variable support
|
|
158
|
+
securityLevel: 'loose',
|
|
159
|
+
fontFamily: 'Inter, system-ui, sans-serif',
|
|
160
|
+
flowchart: {
|
|
161
|
+
useMaxWidth: true,
|
|
162
|
+
htmlLabels: true,
|
|
163
|
+
curve: 'basis',
|
|
164
|
+
},
|
|
165
|
+
themeVariables,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Render the chart
|
|
169
|
+
// Mermaid v11+ requires unique IDs - use random suffix to avoid conflicts
|
|
170
|
+
const renderChart = async () => {
|
|
171
|
+
if (!mermaidRef.current || !chart) return;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const id = `mermaid-${Math.random().toString(36).substring(2, 9)}`;
|
|
175
|
+
const { svg } = await mermaid.render(id, chart);
|
|
176
|
+
|
|
177
|
+
if (mermaidRef.current) {
|
|
178
|
+
// Post-process SVG to force correct text colors
|
|
179
|
+
const textColor = theme === 'dark'
|
|
180
|
+
? getCSSVariable('--foreground') || 'hsl(0 0% 90%)'
|
|
181
|
+
: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)';
|
|
182
|
+
|
|
183
|
+
// Add inline style to override any conflicting styles
|
|
184
|
+
const processedSvg = svg.replace(
|
|
185
|
+
/<svg /,
|
|
186
|
+
`<svg style="--mermaid-text-color: ${textColor};" `
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
mermaidRef.current.innerHTML = processedSvg;
|
|
190
|
+
setSvgContent(processedSvg);
|
|
191
|
+
|
|
192
|
+
// Apply text colors and responsive styles using utility function
|
|
193
|
+
applyMermaidTextColors(mermaidRef.current, textColor);
|
|
194
|
+
|
|
195
|
+
// Make inline SVG responsive and detect orientation
|
|
196
|
+
const svgElement = mermaidRef.current.querySelector('svg');
|
|
197
|
+
if (svgElement) {
|
|
198
|
+
svgElement.style.maxWidth = '100%';
|
|
199
|
+
svgElement.style.height = 'auto';
|
|
200
|
+
svgElement.style.display = 'block';
|
|
201
|
+
|
|
202
|
+
// Detect if diagram is vertical
|
|
203
|
+
setIsVertical(isVerticalDiagram(svgElement));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error('Mermaid rendering error:', error);
|
|
208
|
+
if (mermaidRef.current) {
|
|
209
|
+
mermaidRef.current.innerHTML = `
|
|
210
|
+
<div class="p-4 text-destructive bg-destructive/10 border border-destructive/20 rounded-sm">
|
|
211
|
+
<p class="font-semibold">Mermaid Diagram Error</p>
|
|
212
|
+
<p class="text-sm">${error instanceof Error ? error.message : 'Unknown error'}</p>
|
|
213
|
+
</div>
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
renderChart();
|
|
220
|
+
}, [chart, theme]);
|
|
221
|
+
|
|
222
|
+
const handleClick = () => {
|
|
223
|
+
if (svgContent) {
|
|
224
|
+
setIsFullscreen(true);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const handleClose = () => {
|
|
229
|
+
setIsFullscreen(false);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const handleBackdropClick = (e: React.MouseEvent) => {
|
|
233
|
+
if (e.target === e.currentTarget) {
|
|
234
|
+
handleClose();
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Handle ESC key
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
const handleEscKey = (event: KeyboardEvent) => {
|
|
241
|
+
if (event.key === 'Escape' && isFullscreen) {
|
|
242
|
+
handleClose();
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
if (isFullscreen) {
|
|
247
|
+
document.addEventListener('keydown', handleEscKey);
|
|
248
|
+
document.body.style.overflow = 'hidden'; // Prevent background scroll
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return () => {
|
|
252
|
+
document.removeEventListener('keydown', handleEscKey);
|
|
253
|
+
document.body.style.overflow = 'unset';
|
|
254
|
+
};
|
|
255
|
+
}, [isFullscreen]);
|
|
256
|
+
|
|
257
|
+
// Apply text colors to fullscreen modal after render
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
if (isFullscreen && fullscreenRef.current) {
|
|
260
|
+
const getCSSVariable = (variable: string) => {
|
|
261
|
+
if (typeof document === 'undefined') return '';
|
|
262
|
+
const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
|
|
263
|
+
return value ? `hsl(${value})` : '';
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const textColor = theme === 'dark'
|
|
267
|
+
? getCSSVariable('--foreground') || 'hsl(0 0% 90%)'
|
|
268
|
+
: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)';
|
|
269
|
+
|
|
270
|
+
applyMermaidTextColors(fullscreenRef.current, textColor);
|
|
271
|
+
|
|
272
|
+
// Make SVG responsive
|
|
273
|
+
const svgElement = fullscreenRef.current.querySelector('svg');
|
|
274
|
+
if (svgElement) {
|
|
275
|
+
svgElement.style.display = 'block';
|
|
276
|
+
svgElement.style.height = 'auto';
|
|
277
|
+
svgElement.style.maxWidth = '100%';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}, [isFullscreen, theme]);
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<>
|
|
284
|
+
<div
|
|
285
|
+
className={`relative bg-card rounded-sm border border-border overflow-hidden cursor-pointer hover:shadow-sm transition-shadow ${className}`}
|
|
286
|
+
onClick={handleClick}
|
|
287
|
+
>
|
|
288
|
+
<div className="p-4 border-b border-border bg-muted/50">
|
|
289
|
+
<h6 className="text-sm font-semibold text-foreground">Diagram</h6>
|
|
290
|
+
<p className="text-xs text-muted-foreground mt-1">Click to view fullscreen</p>
|
|
291
|
+
</div>
|
|
292
|
+
<div className="p-4">
|
|
293
|
+
<div
|
|
294
|
+
ref={mermaidRef}
|
|
295
|
+
className="flex justify-center items-center min-h-[200px]"
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
{/* Fullscreen Modal - rendered in portal */}
|
|
301
|
+
{isFullscreen && typeof document !== 'undefined' && createPortal(
|
|
302
|
+
<div
|
|
303
|
+
className="fixed inset-0 z-9999 flex items-center justify-center p-4"
|
|
304
|
+
style={{ backgroundColor: 'rgb(0 0 0 / 0.75)' }}
|
|
305
|
+
onClick={handleBackdropClick}
|
|
306
|
+
>
|
|
307
|
+
<div className={`relative bg-card rounded-sm shadow-xl max-h-[95vh] flex flex-col border border-border ${
|
|
308
|
+
isVertical
|
|
309
|
+
? 'w-auto max-w-[500px]'
|
|
310
|
+
: 'max-w-[95vw] w-full h-full'
|
|
311
|
+
}`}>
|
|
312
|
+
{/* Header */}
|
|
313
|
+
<div className="flex items-center justify-between py-4 px-6 border-b border-border">
|
|
314
|
+
<h3 className="text-sm font-medium text-foreground py-0 my-0">Diagram</h3>
|
|
315
|
+
<button
|
|
316
|
+
onClick={handleClose}
|
|
317
|
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
318
|
+
>
|
|
319
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
320
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
321
|
+
</svg>
|
|
322
|
+
</button>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
{/* Content with scroll */}
|
|
326
|
+
<div className="flex-1 overflow-auto p-6">
|
|
327
|
+
<div
|
|
328
|
+
ref={fullscreenRef}
|
|
329
|
+
className="min-h-full flex items-start justify-center"
|
|
330
|
+
dangerouslySetInnerHTML={{ __html: svgContent }}
|
|
331
|
+
/>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
</div>,
|
|
335
|
+
document.body
|
|
336
|
+
)}
|
|
337
|
+
</>
|
|
338
|
+
);
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export default Mermaid;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mermaid Component - Dynamic Import Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Lazy loads the heavy Mermaid library (~800KB) only when needed.
|
|
5
|
+
* This significantly reduces the initial bundle size.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import dynamic from 'next/dynamic';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
|
|
13
|
+
// Dynamic import with loading state
|
|
14
|
+
const MermaidClient = dynamic(() => import('./Mermaid.client'), {
|
|
15
|
+
ssr: false,
|
|
16
|
+
loading: () => (
|
|
17
|
+
<div className="relative bg-card rounded-sm border border-border overflow-hidden">
|
|
18
|
+
<div className="p-4 border-b border-border bg-muted/50">
|
|
19
|
+
<h6 className="text-sm font-semibold text-foreground">Diagram</h6>
|
|
20
|
+
<p className="text-xs text-muted-foreground mt-1">Loading...</p>
|
|
21
|
+
</div>
|
|
22
|
+
<div className="p-4">
|
|
23
|
+
<div className="flex justify-center items-center min-h-[200px]">
|
|
24
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
interface MermaidProps {
|
|
32
|
+
chart: string;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const Mermaid: React.FC<MermaidProps> = (props) => {
|
|
37
|
+
return <MermaidClient {...props} />;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default Mermaid;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, Collapsible, CollapsibleContent, CollapsibleTrigger } from '@djangocfg/ui-core/components';
|
|
5
|
+
import { ChevronDown, Code, Database, FileText, AlertCircle, Copy } from 'lucide-react';
|
|
6
|
+
import { usePlaygroundContext } from '../context/PlaygroundContext';
|
|
7
|
+
import { getMethodColor, getStatusColor } from '../utils';
|
|
8
|
+
|
|
9
|
+
export const EndpointInfo: React.FC = () => {
|
|
10
|
+
const { state, copyToClipboard } = usePlaygroundContext();
|
|
11
|
+
const { selectedEndpoint } = state;
|
|
12
|
+
|
|
13
|
+
if (!selectedEndpoint) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const getMethodBadges = (methods: string) => {
|
|
18
|
+
return methods.split(', ').map((method) => (
|
|
19
|
+
<Badge key={method} variant={getMethodColor(method) === 'success' ? 'default' : 'secondary'} className="text-xs">
|
|
20
|
+
{method}
|
|
21
|
+
</Badge>
|
|
22
|
+
));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleCopyEndpoint = () => {
|
|
26
|
+
const endpointDetails = {
|
|
27
|
+
name: selectedEndpoint.name,
|
|
28
|
+
method: selectedEndpoint.method,
|
|
29
|
+
path: selectedEndpoint.path,
|
|
30
|
+
description: selectedEndpoint.description,
|
|
31
|
+
parameters: selectedEndpoint.parameters,
|
|
32
|
+
requestBody: selectedEndpoint.requestBody,
|
|
33
|
+
responses: selectedEndpoint.responses
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
copyToClipboard(JSON.stringify(endpointDetails, null, 2));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="space-y-4">
|
|
41
|
+
<div className="flex items-center justify-between">
|
|
42
|
+
<h2 className="text-lg font-semibold text-foreground">Selected Endpoint</h2>
|
|
43
|
+
<Button variant="outline" size="sm" onClick={handleCopyEndpoint}>
|
|
44
|
+
<Copy className="h-4 w-4" />
|
|
45
|
+
Copy
|
|
46
|
+
</Button>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<Card>
|
|
50
|
+
<CardHeader>
|
|
51
|
+
<CardTitle className="flex items-center justify-between text-sm text-foreground">
|
|
52
|
+
<div className="flex items-center space-x-2">
|
|
53
|
+
<Code className="h-4 w-4" />
|
|
54
|
+
<span className="font-medium">{selectedEndpoint.name}</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="flex space-x-1">{getMethodBadges(selectedEndpoint.method)}</div>
|
|
57
|
+
</CardTitle>
|
|
58
|
+
</CardHeader>
|
|
59
|
+
<CardContent className="space-y-4">
|
|
60
|
+
<div>
|
|
61
|
+
<p className="text-xs font-mono text-muted-foreground break-all">
|
|
62
|
+
{selectedEndpoint.path}
|
|
63
|
+
</p>
|
|
64
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
65
|
+
{selectedEndpoint.description}
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{/* Parameters */}
|
|
70
|
+
{selectedEndpoint.parameters && selectedEndpoint.parameters.length > 0 && (
|
|
71
|
+
<Collapsible>
|
|
72
|
+
<CollapsibleTrigger className="flex items-center space-x-2 text-sm font-medium text-foreground">
|
|
73
|
+
<Database className="h-4 w-4" />
|
|
74
|
+
<span>Parameters ({selectedEndpoint.parameters.length})</span>
|
|
75
|
+
<ChevronDown className="h-4 w-4" />
|
|
76
|
+
</CollapsibleTrigger>
|
|
77
|
+
<CollapsibleContent className="mt-2 space-y-2">
|
|
78
|
+
{selectedEndpoint.parameters.map((param, index) => (
|
|
79
|
+
<div key={index} className="flex items-center space-x-2 text-xs">
|
|
80
|
+
<Badge variant={param.required ? 'destructive' : 'secondary'} className="text-xs">
|
|
81
|
+
{param.required ? 'Required' : 'Optional'}
|
|
82
|
+
</Badge>
|
|
83
|
+
<span className="font-mono text-muted-foreground">
|
|
84
|
+
{param.name}: {param.type}
|
|
85
|
+
</span>
|
|
86
|
+
{param.description && (
|
|
87
|
+
<span className="text-muted-foreground">- {param.description}</span>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
))}
|
|
91
|
+
</CollapsibleContent>
|
|
92
|
+
</Collapsible>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
{/* Request Body */}
|
|
96
|
+
{selectedEndpoint.requestBody && (
|
|
97
|
+
<Collapsible>
|
|
98
|
+
<CollapsibleTrigger className="flex items-center space-x-2 text-sm font-medium text-foreground">
|
|
99
|
+
<FileText className="h-4 w-4" />
|
|
100
|
+
<span>Request Body</span>
|
|
101
|
+
<ChevronDown className="h-4 w-4" />
|
|
102
|
+
</CollapsibleTrigger>
|
|
103
|
+
<CollapsibleContent className="mt-2">
|
|
104
|
+
<div className="text-xs text-muted-foreground">
|
|
105
|
+
<p>Type: {selectedEndpoint.requestBody.type}</p>
|
|
106
|
+
{selectedEndpoint.requestBody.description && (
|
|
107
|
+
<p className="mt-1">{selectedEndpoint.requestBody.description}</p>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
</CollapsibleContent>
|
|
111
|
+
</Collapsible>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{/* Responses */}
|
|
115
|
+
{selectedEndpoint.responses && selectedEndpoint.responses.length > 0 && (
|
|
116
|
+
<Collapsible>
|
|
117
|
+
<CollapsibleTrigger className="flex items-center space-x-2 text-sm font-medium text-foreground">
|
|
118
|
+
<AlertCircle className="h-4 w-4" />
|
|
119
|
+
<span>Responses ({selectedEndpoint.responses.length})</span>
|
|
120
|
+
<ChevronDown className="h-4 w-4" />
|
|
121
|
+
</CollapsibleTrigger>
|
|
122
|
+
<CollapsibleContent className="mt-2 space-y-2">
|
|
123
|
+
{selectedEndpoint.responses.map((response, index) => (
|
|
124
|
+
<div key={index} className="flex items-center space-x-2 text-xs">
|
|
125
|
+
<Badge
|
|
126
|
+
variant={getStatusColor(parseInt(response.code)) === 'success' ? 'default' :
|
|
127
|
+
getStatusColor(parseInt(response.code)) === 'error' ? 'destructive' : 'secondary'}
|
|
128
|
+
className="text-xs"
|
|
129
|
+
>
|
|
130
|
+
{response.code}
|
|
131
|
+
</Badge>
|
|
132
|
+
<span className="text-muted-foreground">
|
|
133
|
+
{response.description}
|
|
134
|
+
</span>
|
|
135
|
+
</div>
|
|
136
|
+
))}
|
|
137
|
+
</CollapsibleContent>
|
|
138
|
+
</Collapsible>
|
|
139
|
+
)}
|
|
140
|
+
</CardContent>
|
|
141
|
+
</Card>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
};
|