@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,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
+ };