@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,108 @@
1
+ /**
2
+ * LottiePlayer Types
3
+ *
4
+ * Type definitions for the Lottie animation player component
5
+ */
6
+
7
+ export type LottieSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
8
+
9
+ export type LottieSpeed = 0.5 | 1 | 1.5 | 2;
10
+
11
+ export type LottieDirection = 1 | -1;
12
+
13
+ export interface LottiePlayerProps {
14
+ /**
15
+ * Animation data (JSON object) or URL to load from
16
+ */
17
+ src: string | object;
18
+
19
+ /**
20
+ * Size preset for the player
21
+ * @default 'md'
22
+ */
23
+ size?: LottieSize;
24
+
25
+ /**
26
+ * Custom width (overrides size preset)
27
+ */
28
+ width?: number | string;
29
+
30
+ /**
31
+ * Custom height (overrides size preset)
32
+ */
33
+ height?: number | string;
34
+
35
+ /**
36
+ * Autoplay animation
37
+ * @default true
38
+ */
39
+ autoplay?: boolean;
40
+
41
+ /**
42
+ * Loop animation
43
+ * @default true
44
+ */
45
+ loop?: boolean | number;
46
+
47
+ /**
48
+ * Playback speed
49
+ * @default 1
50
+ */
51
+ speed?: LottieSpeed;
52
+
53
+ /**
54
+ * Direction (1 = forward, -1 = reverse)
55
+ * @default 1
56
+ */
57
+ direction?: LottieDirection;
58
+
59
+ /**
60
+ * Show playback controls
61
+ * @default false
62
+ */
63
+ controls?: boolean;
64
+
65
+ /**
66
+ * Background color
67
+ */
68
+ background?: string;
69
+
70
+ /**
71
+ * CSS class name
72
+ */
73
+ className?: string;
74
+
75
+ /**
76
+ * Show loading state
77
+ * @default true
78
+ */
79
+ showLoading?: boolean;
80
+
81
+ /**
82
+ * Callback when animation completes
83
+ */
84
+ onComplete?: () => void;
85
+
86
+ /**
87
+ * Callback when animation loads
88
+ */
89
+ onLoad?: () => void;
90
+
91
+ /**
92
+ * Callback on error
93
+ */
94
+ onError?: (error: Error) => void;
95
+ }
96
+
97
+ export interface LottieAnimationData {
98
+ v: string;
99
+ fr: number;
100
+ ip: number;
101
+ op: number;
102
+ w: number;
103
+ h: number;
104
+ nm: string;
105
+ ddd: number;
106
+ assets: any[];
107
+ layers: any[];
108
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * useLottie Hook
3
+ *
4
+ * Hook for loading and managing Lottie animation data
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import { useEffect, useRef, useState } from 'react';
10
+
11
+ import { LottieAnimationData } from './types';
12
+
13
+ export interface UseLottieOptions {
14
+ /**
15
+ * Animation data (JSON object) or URL to load from
16
+ */
17
+ src: string | object;
18
+
19
+ /**
20
+ * Enable caching of loaded animations
21
+ * @default true
22
+ */
23
+ cache?: boolean;
24
+ }
25
+
26
+ export interface UseLottieReturn {
27
+ /**
28
+ * Loaded animation data
29
+ */
30
+ animationData: object | null;
31
+
32
+ /**
33
+ * Loading state
34
+ */
35
+ isLoading: boolean;
36
+
37
+ /**
38
+ * Error state
39
+ */
40
+ error: Error | null;
41
+
42
+ /**
43
+ * Retry loading the animation
44
+ */
45
+ retry: () => void;
46
+ }
47
+
48
+ // Simple in-memory cache for loaded animations
49
+ const animationCache = new Map<string, object>();
50
+
51
+ /**
52
+ * Hook for loading Lottie animations from URLs or objects
53
+ *
54
+ * Features:
55
+ * - Loads animations from URLs or accepts animation objects directly
56
+ * - Caching support to prevent re-fetching the same animation
57
+ * - Error handling with retry capability
58
+ * - Loading states
59
+ *
60
+ * Usage:
61
+ * ```tsx
62
+ * const { animationData, isLoading, error, retry } = useLottie({
63
+ * src: 'https://example.com/animation.json'
64
+ * });
65
+ *
66
+ * if (isLoading) return <div>Loading...</div>;
67
+ * if (error) return <div>Error: {error.message} <button onClick={retry}>Retry</button></div>;
68
+ * if (!animationData) return null;
69
+ *
70
+ * return <LottiePlayer animationData={animationData} />;
71
+ * ```
72
+ */
73
+ export function useLottie(options: UseLottieOptions): UseLottieReturn {
74
+ const { src, cache = true } = options;
75
+
76
+ const [animationData, setAnimationData] = useState<object | null>(null);
77
+ const [isLoading, setIsLoading] = useState(false);
78
+ const [error, setError] = useState<Error | null>(null);
79
+ const [retryCount, setRetryCount] = useState(0);
80
+
81
+ // Track if component is mounted to prevent state updates on unmounted component
82
+ const isMountedRef = useRef(true);
83
+
84
+ useEffect(() => {
85
+ isMountedRef.current = true;
86
+ return () => {
87
+ isMountedRef.current = false;
88
+ };
89
+ }, []);
90
+
91
+ useEffect(() => {
92
+ // If src is already an object, use it directly
93
+ if (typeof src === 'object' && src !== null) {
94
+ setAnimationData(src);
95
+ setIsLoading(false);
96
+ setError(null);
97
+ return;
98
+ }
99
+
100
+ // If src is a string (URL), fetch it
101
+ if (typeof src === 'string') {
102
+ const loadAnimation = async () => {
103
+ // Check cache first
104
+ if (cache && animationCache.has(src)) {
105
+ if (isMountedRef.current) {
106
+ setAnimationData(animationCache.get(src)!);
107
+ setIsLoading(false);
108
+ setError(null);
109
+ }
110
+ return;
111
+ }
112
+
113
+ // Load from URL
114
+ if (isMountedRef.current) {
115
+ setIsLoading(true);
116
+ setError(null);
117
+ }
118
+
119
+ try {
120
+ const response = await fetch(src);
121
+
122
+ if (!response.ok) {
123
+ throw new Error(`Failed to load animation: ${response.status} ${response.statusText}`);
124
+ }
125
+
126
+ const data = await response.json();
127
+
128
+ // Validate that it's a valid Lottie animation
129
+ if (!data || typeof data !== 'object' || !data.v || !data.layers) {
130
+ throw new Error('Invalid Lottie animation data');
131
+ }
132
+
133
+ // Cache the loaded animation
134
+ if (cache) {
135
+ animationCache.set(src, data);
136
+ }
137
+
138
+ if (isMountedRef.current) {
139
+ setAnimationData(data);
140
+ setIsLoading(false);
141
+ }
142
+ } catch (err) {
143
+ if (isMountedRef.current) {
144
+ setError(err instanceof Error ? err : new Error('Failed to load animation'));
145
+ setIsLoading(false);
146
+ }
147
+ }
148
+ };
149
+
150
+ loadAnimation();
151
+ }
152
+ }, [src, cache, retryCount]);
153
+
154
+ const retry = () => {
155
+ setRetryCount((prev) => prev + 1);
156
+ };
157
+
158
+ return {
159
+ animationData,
160
+ isLoading,
161
+ error,
162
+ retry,
163
+ };
164
+ }
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
6
+ import { MermaidFullscreenModal } from './components/MermaidFullscreenModal';
7
+ import { useMermaidFullscreen } from './hooks/useMermaidFullscreen';
8
+ import { useMermaidRenderer } from './hooks/useMermaidRenderer';
9
+
10
+ interface MermaidProps {
11
+ chart: string;
12
+ className?: string;
13
+ isCompact?: boolean;
14
+ }
15
+
16
+ const Mermaid: React.FC<MermaidProps> = ({ chart, className = '', isCompact = false }) => {
17
+ const theme = useResolvedTheme();
18
+
19
+ // Rendering logic
20
+ const { mermaidRef, svgContent, isVertical, isRendering } = useMermaidRenderer({
21
+ chart,
22
+ theme,
23
+ isCompact,
24
+ });
25
+
26
+ // Fullscreen modal logic
27
+ const {
28
+ isFullscreen,
29
+ fullscreenRef,
30
+ openFullscreen,
31
+ closeFullscreen,
32
+ handleBackdropClick,
33
+ } = useMermaidFullscreen();
34
+
35
+ const handleClick = () => {
36
+ if (svgContent) {
37
+ openFullscreen();
38
+ }
39
+ };
40
+
41
+ return (
42
+ <>
43
+ <div
44
+ className={`relative bg-card rounded-sm border border-border overflow-hidden cursor-pointer hover:shadow-sm transition-shadow ${className}`}
45
+ onClick={handleClick}
46
+ >
47
+ <div className="p-4 border-b border-border bg-muted/50">
48
+ <h6 className="text-sm font-semibold text-foreground">Diagram</h6>
49
+ <p className="text-xs text-muted-foreground mt-1">Click to view fullscreen</p>
50
+ </div>
51
+ <div className="relative p-4 overflow-hidden">
52
+ <div
53
+ ref={mermaidRef}
54
+ className="flex justify-center items-center min-h-[200px]"
55
+ style={{ isolation: 'isolate' }}
56
+ />
57
+ {isRendering && (
58
+ <div className="absolute inset-0 flex items-center justify-center bg-background/50 backdrop-blur-sm">
59
+ <div className="flex flex-col items-center gap-2">
60
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
61
+ <p className="text-xs text-muted-foreground">Rendering diagram...</p>
62
+ </div>
63
+ </div>
64
+ )}
65
+ </div>
66
+ </div>
67
+
68
+ <MermaidFullscreenModal
69
+ isOpen={isFullscreen}
70
+ svgContent={svgContent}
71
+ isVertical={isVertical}
72
+ theme={theme}
73
+ chart={chart}
74
+ fullscreenRef={fullscreenRef}
75
+ onClose={closeFullscreen}
76
+ onBackdropClick={handleBackdropClick}
77
+ />
78
+ </>
79
+ );
80
+ };
81
+
82
+ export default Mermaid;
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+
5
+ interface MermaidCodeViewerProps {
6
+ chart: string;
7
+ renderPreview: () => React.ReactNode;
8
+ }
9
+
10
+ export const MermaidCodeViewer: React.FC<MermaidCodeViewerProps> = ({
11
+ chart,
12
+ renderPreview,
13
+ }) => {
14
+ const [activeTab, setActiveTab] = useState<'preview' | 'code'>('preview');
15
+ const [copied, setCopied] = useState(false);
16
+
17
+ const handleCopy = async () => {
18
+ await navigator.clipboard.writeText(chart);
19
+ setCopied(true);
20
+ setTimeout(() => setCopied(false), 2000);
21
+ };
22
+
23
+ return (
24
+ <div className="flex flex-col h-full">
25
+ {/* Tabs */}
26
+ <div className="flex items-center justify-between border-b border-border px-4">
27
+ <div className="flex">
28
+ <button
29
+ onClick={() => setActiveTab('preview')}
30
+ className={`px-4 py-3 text-sm font-medium transition-colors relative ${
31
+ activeTab === 'preview'
32
+ ? 'text-foreground'
33
+ : 'text-muted-foreground hover:text-foreground'
34
+ }`}
35
+ >
36
+ Preview
37
+ {activeTab === 'preview' && (
38
+ <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
39
+ )}
40
+ </button>
41
+ <button
42
+ onClick={() => setActiveTab('code')}
43
+ className={`px-4 py-3 text-sm font-medium transition-colors relative ${
44
+ activeTab === 'code'
45
+ ? 'text-foreground'
46
+ : 'text-muted-foreground hover:text-foreground'
47
+ }`}
48
+ >
49
+ Code
50
+ {activeTab === 'code' && (
51
+ <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
52
+ )}
53
+ </button>
54
+ </div>
55
+
56
+ {/* Copy button - show only on Code tab */}
57
+ {activeTab === 'code' && (
58
+ <button
59
+ onClick={handleCopy}
60
+ className="flex items-center gap-2 px-3 py-1.5 text-xs font-medium bg-primary/10 hover:bg-primary/20 text-primary rounded transition-colors"
61
+ >
62
+ {copied ? (
63
+ <>
64
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
65
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
66
+ </svg>
67
+ Copied!
68
+ </>
69
+ ) : (
70
+ <>
71
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
72
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
73
+ </svg>
74
+ Copy
75
+ </>
76
+ )}
77
+ </button>
78
+ )}
79
+ </div>
80
+
81
+ {/* Content */}
82
+ <div className="flex-1 overflow-auto">
83
+ {activeTab === 'preview' ? (
84
+ <div className="p-6 flex items-center justify-center min-h-full">
85
+ {renderPreview()}
86
+ </div>
87
+ ) : (
88
+ <pre className="p-6 text-sm font-mono text-foreground bg-muted/30 h-full overflow-auto">
89
+ <code>{chart}</code>
90
+ </pre>
91
+ )}
92
+ </div>
93
+ </div>
94
+ );
95
+ };
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+
6
+ import { applyMermaidTextColors } from '../utils/mermaid-helpers';
7
+ import { MermaidCodeViewer } from './MermaidCodeViewer';
8
+
9
+ interface MermaidFullscreenModalProps {
10
+ isOpen: boolean;
11
+ svgContent: string;
12
+ isVertical: boolean;
13
+ theme: string;
14
+ chart: string;
15
+ fullscreenRef: React.RefObject<HTMLDivElement>;
16
+ onClose: () => void;
17
+ onBackdropClick: (e: React.MouseEvent) => void;
18
+ }
19
+
20
+ export const MermaidFullscreenModal: React.FC<MermaidFullscreenModalProps> = ({
21
+ isOpen,
22
+ svgContent,
23
+ isVertical,
24
+ theme,
25
+ chart,
26
+ fullscreenRef,
27
+ onClose,
28
+ onBackdropClick,
29
+ }) => {
30
+ // Apply text colors to fullscreen modal after render
31
+ useEffect(() => {
32
+ if (isOpen && fullscreenRef.current) {
33
+ const getCSSVariable = (variable: string) => {
34
+ if (typeof document === 'undefined') return '';
35
+ const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
36
+ return value ? `hsl(${value})` : '';
37
+ };
38
+
39
+ const textColor = theme === 'dark'
40
+ ? getCSSVariable('--foreground') || 'hsl(0 0% 90%)'
41
+ : getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)';
42
+
43
+ applyMermaidTextColors(fullscreenRef.current, textColor);
44
+
45
+ // Make SVG responsive
46
+ const svgElement = fullscreenRef.current.querySelector('svg');
47
+ if (svgElement) {
48
+ svgElement.style.display = 'block';
49
+ svgElement.style.height = 'auto';
50
+ svgElement.style.maxWidth = '100%';
51
+
52
+ // For vertical diagrams, limit width
53
+ if (isVertical) {
54
+ svgElement.style.maxWidth = '600px';
55
+ svgElement.style.margin = '0 auto';
56
+ }
57
+ }
58
+ }
59
+ }, [isOpen, theme, isVertical, fullscreenRef]);
60
+
61
+ if (!isOpen || typeof document === 'undefined') return null;
62
+
63
+ return createPortal(
64
+ <div
65
+ className="fixed inset-0 z-9999 flex items-center justify-center p-4"
66
+ style={{ backgroundColor: 'rgb(0 0 0 / 0.75)' }}
67
+ onClick={onBackdropClick}
68
+ >
69
+ <div className={`relative bg-card rounded-sm shadow-xl max-h-[95vh] flex flex-col border border-border ${
70
+ isVertical
71
+ ? 'w-auto min-w-[400px] max-w-[600px]'
72
+ : 'min-w-[600px] max-w-[90vw] w-auto'
73
+ }`}>
74
+ {/* Header */}
75
+ <div className="flex items-center justify-between py-4 px-6 border-b border-border">
76
+ <h3 className="text-sm font-medium text-foreground py-0 my-0">Diagram</h3>
77
+ <button
78
+ onClick={onClose}
79
+ className="text-muted-foreground hover:text-foreground transition-colors"
80
+ >
81
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
82
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
83
+ </svg>
84
+ </button>
85
+ </div>
86
+
87
+ {/* Content - Code Viewer with tabs */}
88
+ <div className="flex-1 overflow-hidden">
89
+ <MermaidCodeViewer
90
+ chart={chart}
91
+ renderPreview={() => (
92
+ <div
93
+ ref={fullscreenRef}
94
+ dangerouslySetInnerHTML={{ __html: svgContent }}
95
+ />
96
+ )}
97
+ />
98
+ </div>
99
+ </div>
100
+ </div>,
101
+ document.body
102
+ );
103
+ };
@@ -0,0 +1,4 @@
1
+ export { useMermaidRenderer } from './useMermaidRenderer';
2
+ export { useMermaidValidation } from './useMermaidValidation';
3
+ export { useMermaidCleanup } from './useMermaidCleanup';
4
+ export { useMermaidFullscreen } from './useMermaidFullscreen';
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Hook for cleaning up orphaned Mermaid DOM nodes
3
+ */
4
+
5
+ import { useCallback, useEffect } from 'react';
6
+
7
+ export function useMermaidCleanup() {
8
+ const cleanupMermaidErrors = useCallback(() => {
9
+ if (typeof document === 'undefined') return;
10
+
11
+ // Remove all orphaned mermaid elements from body
12
+ // Mermaid can append: SVGs, divs with errors, text nodes
13
+
14
+ // 1. Remove elements with mermaid-* IDs directly in body
15
+ document.querySelectorAll('[id^="mermaid-"]').forEach((node) => {
16
+ if (node.parentNode === document.body) {
17
+ node.remove();
18
+ }
19
+ });
20
+
21
+ // 2. Remove elements with d prefix (mermaid diagram IDs) directly in body
22
+ document.querySelectorAll('[id^="d"]').forEach((node) => {
23
+ if (node.parentNode === document.body && node.id.match(/^d\d+$/)) {
24
+ node.remove();
25
+ }
26
+ });
27
+
28
+ // 3. Remove orphaned SVG elements that mermaid creates in body
29
+ document.querySelectorAll('body > svg').forEach((node) => {
30
+ // Check if it's a mermaid SVG (has mermaid classes or aria-roledescription)
31
+ if (node.getAttribute('aria-roledescription') ||
32
+ node.classList.contains('mermaid') ||
33
+ node.querySelector('.mermaid') ||
34
+ node.id?.includes('mermaid')) {
35
+ node.remove();
36
+ }
37
+ });
38
+
39
+ // 4. Remove any orphaned error divs with "Syntax error" text
40
+ document.querySelectorAll('body > div').forEach((node) => {
41
+ const text = node.textContent || '';
42
+ if (text.includes('Syntax error in text') ||
43
+ text.includes('mermaid version') ||
44
+ node.id?.startsWith('mermaid-') ||
45
+ node.id?.startsWith('d') && node.id.match(/^d\d+$/)) {
46
+ node.remove();
47
+ }
48
+ });
49
+
50
+ // 5. Remove orphaned pre elements with error info
51
+ document.querySelectorAll('body > pre').forEach((node) => {
52
+ const text = node.textContent || '';
53
+ if (text.includes('Syntax error') || text.includes('mermaid')) {
54
+ node.remove();
55
+ }
56
+ });
57
+ }, []);
58
+
59
+ // Cleanup on unmount
60
+ useEffect(() => {
61
+ return () => {
62
+ cleanupMermaidErrors();
63
+ };
64
+ }, [cleanupMermaidErrors]);
65
+
66
+ // Also run cleanup periodically to catch any stray elements
67
+ useEffect(() => {
68
+ const interval = setInterval(cleanupMermaidErrors, 1000);
69
+ return () => clearInterval(interval);
70
+ }, [cleanupMermaidErrors]);
71
+
72
+ return { cleanupMermaidErrors };
73
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Hook for managing Mermaid fullscreen modal
3
+ */
4
+
5
+ import { useEffect, useRef, useState } from 'react';
6
+
7
+ export function useMermaidFullscreen() {
8
+ const [isFullscreen, setIsFullscreen] = useState(false);
9
+ const fullscreenRef = useRef<HTMLDivElement>(null);
10
+
11
+ const openFullscreen = () => setIsFullscreen(true);
12
+ const closeFullscreen = () => setIsFullscreen(false);
13
+
14
+ const handleBackdropClick = (e: React.MouseEvent) => {
15
+ if (e.target === e.currentTarget) {
16
+ closeFullscreen();
17
+ }
18
+ };
19
+
20
+ // Handle ESC key
21
+ useEffect(() => {
22
+ const handleEscKey = (event: KeyboardEvent) => {
23
+ if (event.key === 'Escape' && isFullscreen) {
24
+ closeFullscreen();
25
+ }
26
+ };
27
+
28
+ if (isFullscreen) {
29
+ document.addEventListener('keydown', handleEscKey);
30
+ document.body.style.overflow = 'hidden';
31
+ }
32
+
33
+ return () => {
34
+ document.removeEventListener('keydown', handleEscKey);
35
+ document.body.style.overflow = 'unset';
36
+ };
37
+ }, [isFullscreen]);
38
+
39
+ return {
40
+ isFullscreen,
41
+ fullscreenRef,
42
+ openFullscreen,
43
+ closeFullscreen,
44
+ handleBackdropClick,
45
+ };
46
+ }