@djangocfg/ui-tools 2.1.91

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 (174) hide show
  1. package/dist/LottiePlayer.client-LBEC2JKY.mjs +161 -0
  2. package/dist/LottiePlayer.client-LBEC2JKY.mjs.map +1 -0
  3. package/dist/LottiePlayer.client-WFMG2OOW.cjs +168 -0
  4. package/dist/LottiePlayer.client-WFMG2OOW.cjs.map +1 -0
  5. package/dist/Mermaid.client-4TU2TSH3.mjs +477 -0
  6. package/dist/Mermaid.client-4TU2TSH3.mjs.map +1 -0
  7. package/dist/Mermaid.client-SBYY364Q.cjs +483 -0
  8. package/dist/Mermaid.client-SBYY364Q.cjs.map +1 -0
  9. package/dist/PlaygroundLayout-3YVSAEAF.cjs +1003 -0
  10. package/dist/PlaygroundLayout-3YVSAEAF.cjs.map +1 -0
  11. package/dist/PlaygroundLayout-4DYBORAS.mjs +996 -0
  12. package/dist/PlaygroundLayout-4DYBORAS.mjs.map +1 -0
  13. package/dist/PrettyCode.client-LCBPPTIX.mjs +152 -0
  14. package/dist/PrettyCode.client-LCBPPTIX.mjs.map +1 -0
  15. package/dist/PrettyCode.client-PNPLXRH6.cjs +154 -0
  16. package/dist/PrettyCode.client-PNPLXRH6.cjs.map +1 -0
  17. package/dist/chunk-37ZI6VD4.mjs +12 -0
  18. package/dist/chunk-37ZI6VD4.mjs.map +1 -0
  19. package/dist/chunk-3HK2OE62.cjs +81 -0
  20. package/dist/chunk-3HK2OE62.cjs.map +1 -0
  21. package/dist/chunk-7DGDQVQW.cjs +591 -0
  22. package/dist/chunk-7DGDQVQW.cjs.map +1 -0
  23. package/dist/chunk-M6P2FU7L.mjs +572 -0
  24. package/dist/chunk-M6P2FU7L.mjs.map +1 -0
  25. package/dist/chunk-UQ3XI5MY.cjs +15 -0
  26. package/dist/chunk-UQ3XI5MY.cjs.map +1 -0
  27. package/dist/chunk-YFRNE2IR.mjs +79 -0
  28. package/dist/chunk-YFRNE2IR.mjs.map +1 -0
  29. package/dist/index.cjs +5042 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +1591 -0
  32. package/dist/index.d.ts +1591 -0
  33. package/dist/index.mjs +4941 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/package.json +86 -0
  36. package/src/components/markdown/MarkdownMessage.tsx +340 -0
  37. package/src/components/markdown/index.ts +5 -0
  38. package/src/index.ts +26 -0
  39. package/src/stores/index.ts +9 -0
  40. package/src/stores/mediaCache.ts +534 -0
  41. package/src/tools/AudioPlayer/README.md +206 -0
  42. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
  43. package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +280 -0
  44. package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
  45. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +149 -0
  46. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
  47. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
  48. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
  49. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
  50. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
  51. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
  52. package/src/tools/AudioPlayer/components/index.ts +22 -0
  53. package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +158 -0
  54. package/src/tools/AudioPlayer/context/index.ts +16 -0
  55. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  56. package/src/tools/AudioPlayer/hooks/index.ts +35 -0
  57. package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
  58. package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
  59. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +207 -0
  60. package/src/tools/AudioPlayer/index.ts +133 -0
  61. package/src/tools/AudioPlayer/types/effects.ts +73 -0
  62. package/src/tools/AudioPlayer/types/index.ts +27 -0
  63. package/src/tools/AudioPlayer/utils/debug.ts +14 -0
  64. package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
  65. package/src/tools/AudioPlayer/utils/index.ts +6 -0
  66. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
  67. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
  68. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
  69. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
  70. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
  71. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
  72. package/src/tools/ImageViewer/README.md +200 -0
  73. package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
  74. package/src/tools/ImageViewer/components/ImageToolbar.tsx +145 -0
  75. package/src/tools/ImageViewer/components/ImageViewer.tsx +241 -0
  76. package/src/tools/ImageViewer/components/index.ts +7 -0
  77. package/src/tools/ImageViewer/hooks/index.ts +9 -0
  78. package/src/tools/ImageViewer/hooks/useImageLoading.ts +204 -0
  79. package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
  80. package/src/tools/ImageViewer/index.ts +60 -0
  81. package/src/tools/ImageViewer/types.ts +81 -0
  82. package/src/tools/ImageViewer/utils/constants.ts +59 -0
  83. package/src/tools/ImageViewer/utils/debug.ts +14 -0
  84. package/src/tools/ImageViewer/utils/index.ts +17 -0
  85. package/src/tools/ImageViewer/utils/lqip.ts +47 -0
  86. package/src/tools/JsonForm/JsonSchemaForm.tsx +197 -0
  87. package/src/tools/JsonForm/examples/BotConfigExample.tsx +249 -0
  88. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +161 -0
  89. package/src/tools/JsonForm/index.ts +46 -0
  90. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +47 -0
  91. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +74 -0
  92. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +107 -0
  93. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +35 -0
  94. package/src/tools/JsonForm/templates/FieldTemplate.tsx +62 -0
  95. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +116 -0
  96. package/src/tools/JsonForm/templates/index.ts +12 -0
  97. package/src/tools/JsonForm/types.ts +83 -0
  98. package/src/tools/JsonForm/utils.ts +213 -0
  99. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +37 -0
  100. package/src/tools/JsonForm/widgets/ColorWidget.tsx +219 -0
  101. package/src/tools/JsonForm/widgets/NumberWidget.tsx +89 -0
  102. package/src/tools/JsonForm/widgets/SelectWidget.tsx +97 -0
  103. package/src/tools/JsonForm/widgets/SliderWidget.tsx +148 -0
  104. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +35 -0
  105. package/src/tools/JsonForm/widgets/TextWidget.tsx +96 -0
  106. package/src/tools/JsonForm/widgets/index.ts +14 -0
  107. package/src/tools/JsonTree/index.tsx +243 -0
  108. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +213 -0
  109. package/src/tools/LottiePlayer/index.tsx +56 -0
  110. package/src/tools/LottiePlayer/types.ts +108 -0
  111. package/src/tools/LottiePlayer/useLottie.ts +164 -0
  112. package/src/tools/Mermaid/Mermaid.client.tsx +82 -0
  113. package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +95 -0
  114. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +103 -0
  115. package/src/tools/Mermaid/hooks/index.ts +4 -0
  116. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +73 -0
  117. package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +46 -0
  118. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +226 -0
  119. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +29 -0
  120. package/src/tools/Mermaid/index.tsx +44 -0
  121. package/src/tools/Mermaid/utils/mermaid-helpers.ts +33 -0
  122. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +149 -0
  123. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +263 -0
  124. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +125 -0
  125. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +100 -0
  126. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +157 -0
  127. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  128. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +173 -0
  129. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +68 -0
  130. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  131. package/src/tools/OpenapiViewer/constants.ts +39 -0
  132. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +337 -0
  133. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  134. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  135. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +199 -0
  136. package/src/tools/OpenapiViewer/index.tsx +37 -0
  137. package/src/tools/OpenapiViewer/types.ts +151 -0
  138. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  139. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  140. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  141. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  142. package/src/tools/PrettyCode/PrettyCode.client.tsx +208 -0
  143. package/src/tools/PrettyCode/index.tsx +47 -0
  144. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
  145. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
  146. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
  147. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
  148. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
  149. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
  150. package/src/tools/VideoPlayer/README.md +264 -0
  151. package/src/tools/VideoPlayer/components/VideoControls.tsx +138 -0
  152. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +172 -0
  153. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
  154. package/src/tools/VideoPlayer/components/index.ts +14 -0
  155. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
  156. package/src/tools/VideoPlayer/context/index.ts +8 -0
  157. package/src/tools/VideoPlayer/hooks/index.ts +12 -0
  158. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +70 -0
  159. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +116 -0
  160. package/src/tools/VideoPlayer/index.ts +77 -0
  161. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +284 -0
  162. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +505 -0
  163. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +400 -0
  164. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  165. package/src/tools/VideoPlayer/types/index.ts +38 -0
  166. package/src/tools/VideoPlayer/types/player.ts +116 -0
  167. package/src/tools/VideoPlayer/types/provider.ts +93 -0
  168. package/src/tools/VideoPlayer/types/sources.ts +97 -0
  169. package/src/tools/VideoPlayer/utils/debug.ts +14 -0
  170. package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
  171. package/src/tools/VideoPlayer/utils/index.ts +12 -0
  172. package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
  173. package/src/tools/_shared.ts +29 -0
  174. package/src/tools/index.ts +172 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Hook for rendering Mermaid diagrams with debounce and validation
3
+ */
4
+
5
+ import mermaid from 'mermaid';
6
+ import { useEffect, useRef, useState } from 'react';
7
+
8
+ import { useMermaidCleanup } from './useMermaidCleanup';
9
+ import { useMermaidValidation } from './useMermaidValidation';
10
+
11
+ interface UseMermaidRendererProps {
12
+ chart: string;
13
+ theme: string;
14
+ isCompact?: boolean;
15
+ }
16
+
17
+ interface MermaidRenderResult {
18
+ mermaidRef: React.RefObject<HTMLDivElement>;
19
+ svgContent: string;
20
+ isVertical: boolean;
21
+ isRendering: boolean;
22
+ }
23
+
24
+ // Utility function to apply text colors to Mermaid SVG
25
+ const applyMermaidTextColors = (container: HTMLElement, textColor: string) => {
26
+ const svgElement = container.querySelector('svg');
27
+ if (svgElement) {
28
+ // SVG text elements use 'fill'
29
+ svgElement.querySelectorAll('text').forEach((el) => {
30
+ (el as SVGElement).style.fill = textColor;
31
+ });
32
+
33
+ // HTML elements inside foreignObject use 'color'
34
+ svgElement.querySelectorAll('.nodeLabel, .edgeLabel').forEach((el) => {
35
+ (el as HTMLElement).style.color = textColor;
36
+ });
37
+ }
38
+ };
39
+
40
+ // Detect if diagram is vertical (tall and narrow)
41
+ const isVerticalDiagram = (svgElement: SVGSVGElement): boolean => {
42
+ const viewBox = svgElement.getAttribute('viewBox');
43
+ if (viewBox) {
44
+ const [, , width, height] = viewBox.split(' ').map(Number);
45
+ return height > width * 1.5;
46
+ }
47
+ const bbox = svgElement.getBBox?.();
48
+ if (bbox) {
49
+ return bbox.height > bbox.width * 1.5;
50
+ }
51
+ return false;
52
+ };
53
+
54
+ export function useMermaidRenderer({ chart, theme, isCompact = false }: UseMermaidRendererProps): MermaidRenderResult {
55
+ const mermaidRef = useRef<HTMLDivElement>(null);
56
+ const renderTimerRef = useRef<NodeJS.Timeout | null>(null);
57
+ const [svgContent, setSvgContent] = useState<string>('');
58
+ const [isVertical, setIsVertical] = useState(false);
59
+ const [isRendering, setIsRendering] = useState(false);
60
+
61
+ const { isMermaidCodeComplete } = useMermaidValidation();
62
+ const { cleanupMermaidErrors } = useMermaidCleanup();
63
+
64
+ useEffect(() => {
65
+ // Get CSS variables for semantic colors
66
+ const getCSSVariable = (variable: string) => {
67
+ if (typeof document === 'undefined') return '';
68
+ const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
69
+ return value ? `hsl(${value})` : '';
70
+ };
71
+
72
+ const diagramFontSize = isCompact ? '12px' : '14px';
73
+
74
+ const themeVariables = theme === 'dark' ? {
75
+ primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
76
+ primaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
77
+ primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
78
+ secondaryColor: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',
79
+ secondaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
80
+ secondaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
81
+ tertiaryColor: getCSSVariable('--accent') || 'hsl(217.2 32.6% 20%)',
82
+ tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
83
+ tertiaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
84
+ mainBkg: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
85
+ textColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
86
+ nodeBorder: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
87
+ nodeTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
88
+ secondBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',
89
+ lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
90
+ edgeLabelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
91
+ clusterBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 12%)',
92
+ clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
93
+ background: getCSSVariable('--background') || 'hsl(222.2 84% 4.9%)',
94
+ labelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
95
+ labelTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
96
+ errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 62.8% 30.6%)',
97
+ errorTextColor: 'hsl(210 40% 98%)',
98
+ fontSize: diagramFontSize,
99
+ fontFamily: 'Inter, system-ui, sans-serif',
100
+ } : {
101
+ primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
102
+ primaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
103
+ primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
104
+ secondaryColor: getCSSVariable('--secondary') || 'hsl(210 40% 96.1%)',
105
+ secondaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
106
+ secondaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
107
+ tertiaryColor: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',
108
+ tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
109
+ tertiaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
110
+ mainBkg: getCSSVariable('--card') || 'hsl(0 0% 100%)',
111
+ textColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
112
+ nodeBorder: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
113
+ nodeTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
114
+ secondBkg: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',
115
+ lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
116
+ edgeLabelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',
117
+ clusterBkg: getCSSVariable('--accent') || 'hsl(210 40% 98%)',
118
+ clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
119
+ background: getCSSVariable('--background') || 'hsl(0 0% 100%)',
120
+ labelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',
121
+ labelTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
122
+ errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 84.2% 60.2%)',
123
+ errorTextColor: 'hsl(210 40% 98%)',
124
+ fontSize: diagramFontSize,
125
+ fontFamily: 'Inter, system-ui, sans-serif',
126
+ };
127
+
128
+ mermaid.initialize({
129
+ startOnLoad: false,
130
+ theme: 'base',
131
+ securityLevel: 'loose',
132
+ suppressErrorRendering: true, // Prevent mermaid from appending errors to body
133
+ fontFamily: 'Inter, system-ui, sans-serif',
134
+ flowchart: {
135
+ useMaxWidth: true,
136
+ htmlLabels: true,
137
+ curve: 'basis',
138
+ },
139
+ themeVariables,
140
+ });
141
+
142
+ const renderChart = async () => {
143
+ if (!mermaidRef.current || !chart) return;
144
+
145
+ // Validate code completeness
146
+ if (!isMermaidCodeComplete(chart)) {
147
+ setIsRendering(true);
148
+ return;
149
+ }
150
+
151
+ try {
152
+ setIsRendering(true);
153
+
154
+ // Clear container
155
+ if (mermaidRef.current) {
156
+ mermaidRef.current.innerHTML = '';
157
+ }
158
+
159
+ const id = `mermaid-${Math.random().toString(36).substring(2, 9)}`;
160
+ const { svg } = await mermaid.render(id, chart);
161
+
162
+ if (mermaidRef.current) {
163
+ const textColor = theme === 'dark'
164
+ ? getCSSVariable('--foreground') || 'hsl(0 0% 90%)'
165
+ : getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)';
166
+
167
+ const processedSvg = svg.replace(
168
+ /<svg /,
169
+ `<svg style="--mermaid-text-color: ${textColor};" `
170
+ );
171
+
172
+ mermaidRef.current.innerHTML = processedSvg;
173
+ setSvgContent(processedSvg);
174
+
175
+ applyMermaidTextColors(mermaidRef.current, textColor);
176
+
177
+ const svgElement = mermaidRef.current.querySelector('svg');
178
+ if (svgElement) {
179
+ svgElement.style.maxWidth = '100%';
180
+ svgElement.style.height = 'auto';
181
+ svgElement.style.display = 'block';
182
+ setIsVertical(isVerticalDiagram(svgElement));
183
+ }
184
+ }
185
+
186
+ setIsRendering(false);
187
+ } catch (error) {
188
+ console.error('Mermaid rendering error:', error);
189
+ setIsRendering(false);
190
+ cleanupMermaidErrors();
191
+
192
+ if (mermaidRef.current) {
193
+ mermaidRef.current.innerHTML = `
194
+ <div class="p-4 text-destructive bg-destructive/10 border border-destructive/20 rounded-sm">
195
+ <p class="font-semibold">Mermaid Diagram Error</p>
196
+ <p class="text-sm">${error instanceof Error ? error.message : 'Unknown error'}</p>
197
+ </div>
198
+ `;
199
+ }
200
+ }
201
+ };
202
+
203
+ // Clear previous timer
204
+ if (renderTimerRef.current) {
205
+ clearTimeout(renderTimerRef.current);
206
+ }
207
+
208
+ // Debounce: wait 500ms after last update
209
+ renderTimerRef.current = setTimeout(() => {
210
+ renderChart();
211
+ }, 500);
212
+
213
+ return () => {
214
+ if (renderTimerRef.current) {
215
+ clearTimeout(renderTimerRef.current);
216
+ }
217
+ };
218
+ }, [chart, theme, isCompact, isMermaidCodeComplete, cleanupMermaidErrors]);
219
+
220
+ return {
221
+ mermaidRef,
222
+ svgContent,
223
+ isVertical,
224
+ isRendering,
225
+ };
226
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Hook for validating Mermaid code completeness
3
+ */
4
+
5
+ export function useMermaidValidation() {
6
+ const isMermaidCodeComplete = (code: string): boolean => {
7
+ if (!code || code.trim().length === 0) return false;
8
+
9
+ const trimmed = code.trim();
10
+
11
+ // Check if code has basic structure
12
+ const lines = trimmed.split('\n');
13
+ if (lines.length < 2) return false; // Need at least diagram type + one element
14
+
15
+ // Check for common incomplete patterns
16
+ const lastLine = lines[lines.length - 1].trim();
17
+
18
+ // Incomplete if last line ends with arrow without destination
19
+ if (lastLine.match(/-->?\s*$/)) return false;
20
+ if (lastLine.match(/-->\|[^|]*\|\s*$/)) return false;
21
+
22
+ // Incomplete if last line ends with opening bracket/parenthesis
23
+ if (lastLine.match(/[\[({]\s*$/)) return false;
24
+
25
+ return true;
26
+ };
27
+
28
+ return { isMermaidCodeComplete };
29
+ }
@@ -0,0 +1,44 @@
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 React, { lazy, Suspense } from 'react';
11
+
12
+ // Lazy load the client component
13
+ const MermaidClient = lazy(() => import('./Mermaid.client'));
14
+
15
+ // Loading fallback component
16
+ const LoadingFallback = () => (
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
+ interface MermaidProps {
31
+ chart: string;
32
+ className?: string;
33
+ isCompact?: boolean;
34
+ }
35
+
36
+ const Mermaid: React.FC<MermaidProps> = (props) => {
37
+ return (
38
+ <Suspense fallback={<LoadingFallback />}>
39
+ <MermaidClient {...props} />
40
+ </Suspense>
41
+ );
42
+ };
43
+
44
+ export default Mermaid;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Helper utilities for Mermaid diagram rendering
3
+ */
4
+
5
+ // Utility function to apply text colors to Mermaid SVG
6
+ export const applyMermaidTextColors = (container: HTMLElement, textColor: string) => {
7
+ const svgElement = container.querySelector('svg');
8
+ if (svgElement) {
9
+ // SVG text elements use 'fill'
10
+ svgElement.querySelectorAll('text').forEach((el) => {
11
+ (el as SVGElement).style.fill = textColor;
12
+ });
13
+
14
+ // HTML elements inside foreignObject use 'color'
15
+ svgElement.querySelectorAll('.nodeLabel, .edgeLabel').forEach((el) => {
16
+ (el as HTMLElement).style.color = textColor;
17
+ });
18
+ }
19
+ };
20
+
21
+ // Detect if diagram is vertical (tall and narrow)
22
+ export const isVerticalDiagram = (svgElement: SVGSVGElement): boolean => {
23
+ const viewBox = svgElement.getAttribute('viewBox');
24
+ if (viewBox) {
25
+ const [, , width, height] = viewBox.split(' ').map(Number);
26
+ return height > width * 1.5;
27
+ }
28
+ const bbox = svgElement.getBBox?.();
29
+ if (bbox) {
30
+ return bbox.height > bbox.width * 1.5;
31
+ }
32
+ return false;
33
+ };
@@ -0,0 +1,149 @@
1
+ 'use client';
2
+
3
+ import { AlertCircle, ChevronDown, Code, Database, FileText } from 'lucide-react';
4
+ import React, { useMemo } from 'react';
5
+
6
+ import {
7
+ Badge, Card, CardContent, CardHeader, CardTitle, Collapsible, CollapsibleContent,
8
+ CollapsibleTrigger, CopyButton
9
+ } from '@djangocfg/ui-core/components';
10
+
11
+ import { usePlaygroundContext } from '../context/PlaygroundContext';
12
+ import { getMethodColor, getStatusColor } from '../utils';
13
+
14
+ export const EndpointInfo: React.FC = () => {
15
+ const { state } = usePlaygroundContext();
16
+ const { selectedEndpoint } = state;
17
+
18
+ // Memoize endpoint JSON for copy
19
+ const endpointJson = useMemo(() => {
20
+ if (!selectedEndpoint) return '';
21
+ return JSON.stringify({
22
+ name: selectedEndpoint.name,
23
+ method: selectedEndpoint.method,
24
+ path: selectedEndpoint.path,
25
+ description: selectedEndpoint.description,
26
+ parameters: selectedEndpoint.parameters,
27
+ requestBody: selectedEndpoint.requestBody,
28
+ responses: selectedEndpoint.responses
29
+ }, null, 2);
30
+ }, [selectedEndpoint]);
31
+
32
+ if (!selectedEndpoint) {
33
+ return null;
34
+ }
35
+
36
+ const getMethodBadges = (methods: string) => {
37
+ return methods.split(', ').map((method) => (
38
+ <Badge key={method} variant={getMethodColor(method) === 'success' ? 'default' : 'secondary'} className="text-xs">
39
+ {method}
40
+ </Badge>
41
+ ));
42
+ };
43
+
44
+ return (
45
+ <div className="space-y-4">
46
+ <div className="flex items-center justify-between">
47
+ <h2 className="text-lg font-semibold text-foreground">Selected Endpoint</h2>
48
+ <CopyButton value={endpointJson} variant="outline" size="sm">
49
+ Copy
50
+ </CopyButton>
51
+ </div>
52
+
53
+ <Card>
54
+ <CardHeader>
55
+ <CardTitle className="flex items-center justify-between text-sm text-foreground">
56
+ <div className="flex items-center space-x-2">
57
+ <Code className="h-4 w-4" />
58
+ <span className="font-medium">{selectedEndpoint.name}</span>
59
+ </div>
60
+ <div className="flex space-x-1">{getMethodBadges(selectedEndpoint.method)}</div>
61
+ </CardTitle>
62
+ </CardHeader>
63
+ <CardContent className="space-y-4">
64
+ <div>
65
+ <p className="text-xs font-mono text-muted-foreground break-all">
66
+ {selectedEndpoint.path}
67
+ </p>
68
+ <p className="text-xs text-muted-foreground mt-1">
69
+ {selectedEndpoint.description}
70
+ </p>
71
+ </div>
72
+
73
+ {/* Parameters */}
74
+ {selectedEndpoint.parameters && selectedEndpoint.parameters.length > 0 && (
75
+ <Collapsible>
76
+ <CollapsibleTrigger className="flex items-center space-x-2 text-sm font-medium text-foreground">
77
+ <Database className="h-4 w-4" />
78
+ <span>Parameters ({selectedEndpoint.parameters.length})</span>
79
+ <ChevronDown className="h-4 w-4" />
80
+ </CollapsibleTrigger>
81
+ <CollapsibleContent className="mt-2 space-y-2">
82
+ {selectedEndpoint.parameters.map((param, index) => (
83
+ <div key={index} className="flex items-center space-x-2 text-xs">
84
+ <Badge variant={param.required ? 'destructive' : 'secondary'} className="text-xs">
85
+ {param.required ? 'Required' : 'Optional'}
86
+ </Badge>
87
+ <span className="font-mono text-muted-foreground">
88
+ {param.name}: {param.type}
89
+ </span>
90
+ {param.description && (
91
+ <span className="text-muted-foreground">- {param.description}</span>
92
+ )}
93
+ </div>
94
+ ))}
95
+ </CollapsibleContent>
96
+ </Collapsible>
97
+ )}
98
+
99
+ {/* Request Body */}
100
+ {selectedEndpoint.requestBody && (
101
+ <Collapsible>
102
+ <CollapsibleTrigger className="flex items-center space-x-2 text-sm font-medium text-foreground">
103
+ <FileText className="h-4 w-4" />
104
+ <span>Request Body</span>
105
+ <ChevronDown className="h-4 w-4" />
106
+ </CollapsibleTrigger>
107
+ <CollapsibleContent className="mt-2">
108
+ <div className="text-xs text-muted-foreground">
109
+ <p>Type: {selectedEndpoint.requestBody.type}</p>
110
+ {selectedEndpoint.requestBody.description && (
111
+ <p className="mt-1">{selectedEndpoint.requestBody.description}</p>
112
+ )}
113
+ </div>
114
+ </CollapsibleContent>
115
+ </Collapsible>
116
+ )}
117
+
118
+ {/* Responses */}
119
+ {selectedEndpoint.responses && selectedEndpoint.responses.length > 0 && (
120
+ <Collapsible>
121
+ <CollapsibleTrigger className="flex items-center space-x-2 text-sm font-medium text-foreground">
122
+ <AlertCircle className="h-4 w-4" />
123
+ <span>Responses ({selectedEndpoint.responses.length})</span>
124
+ <ChevronDown className="h-4 w-4" />
125
+ </CollapsibleTrigger>
126
+ <CollapsibleContent className="mt-2 space-y-2">
127
+ {selectedEndpoint.responses.map((response, index) => {
128
+ const statusColor = getStatusColor(parseInt(response.code));
129
+ const badgeVariant = statusColor === 'success' ? 'default' :
130
+ statusColor === 'error' ? 'destructive' : 'secondary';
131
+ return (
132
+ <div key={index} className="flex items-center space-x-2 text-xs">
133
+ <Badge variant={badgeVariant} className="text-xs">
134
+ {response.code}
135
+ </Badge>
136
+ <span className="text-muted-foreground">
137
+ {response.description}
138
+ </span>
139
+ </div>
140
+ );
141
+ })}
142
+ </CollapsibleContent>
143
+ </Collapsible>
144
+ )}
145
+ </CardContent>
146
+ </Card>
147
+ </div>
148
+ );
149
+ };