@djangocfg/ui-tools 2.1.119 → 2.1.120

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 (63) hide show
  1. package/dist/JsonTree-G2TPWQ4C.mjs +4 -0
  2. package/dist/{JsonTree-6RYAOPSS.mjs.map → JsonTree-G2TPWQ4C.mjs.map} +1 -1
  3. package/dist/JsonTree-TWXUBBIG.cjs +10 -0
  4. package/dist/{JsonTree-7OH6CIHT.cjs.map → JsonTree-TWXUBBIG.cjs.map} +1 -1
  5. package/dist/{Mermaid.client-PNXEC6YL.cjs → Mermaid.client-AF4WOQZR.cjs} +9 -11
  6. package/dist/Mermaid.client-AF4WOQZR.cjs.map +1 -0
  7. package/dist/{Mermaid.client-OKACITCW.mjs → Mermaid.client-W4QXJX7Q.mjs} +9 -11
  8. package/dist/Mermaid.client-W4QXJX7Q.mjs.map +1 -0
  9. package/dist/{PlaygroundLayout-SYMEAG3J.cjs → PlaygroundLayout-RZMJWH3Y.cjs} +25 -25
  10. package/dist/{PlaygroundLayout-SYMEAG3J.cjs.map → PlaygroundLayout-RZMJWH3Y.cjs.map} +1 -1
  11. package/dist/{PlaygroundLayout-UQRBU5RH.mjs → PlaygroundLayout-UQABCZ6K.mjs} +4 -4
  12. package/dist/{PlaygroundLayout-UQRBU5RH.mjs.map → PlaygroundLayout-UQABCZ6K.mjs.map} +1 -1
  13. package/dist/{chunk-UOMPPIED.mjs → chunk-4G4UGMOP.mjs} +3 -3
  14. package/dist/chunk-4G4UGMOP.mjs.map +1 -0
  15. package/dist/{chunk-47T5ECYV.cjs → chunk-CY3CQS26.cjs} +3 -3
  16. package/dist/chunk-CY3CQS26.cjs.map +1 -0
  17. package/dist/{chunk-5QT3QYFZ.cjs → chunk-EGYUND4E.cjs} +2 -2
  18. package/dist/chunk-EGYUND4E.cjs.map +1 -0
  19. package/dist/{chunk-DI3HUXHK.cjs → chunk-OYLQZT62.cjs} +2 -2
  20. package/dist/chunk-OYLQZT62.cjs.map +1 -0
  21. package/dist/{chunk-W6YHQI4F.mjs → chunk-OYYCGIBF.mjs} +2 -2
  22. package/dist/chunk-OYYCGIBF.mjs.map +1 -0
  23. package/dist/{chunk-G6PRZP5I.mjs → chunk-XZZ22EHP.mjs} +2 -2
  24. package/dist/chunk-XZZ22EHP.mjs.map +1 -0
  25. package/dist/{components-EASJYK45.mjs → components-4YSJ5ALL.mjs} +3 -3
  26. package/dist/{components-CJ2IB65O.cjs.map → components-4YSJ5ALL.mjs.map} +1 -1
  27. package/dist/{components-CJ2IB65O.cjs → components-BSTP3VLD.cjs} +7 -7
  28. package/dist/{components-EASJYK45.mjs.map → components-BSTP3VLD.cjs.map} +1 -1
  29. package/dist/index.cjs +27 -27
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.mjs +10 -10
  34. package/dist/index.mjs.map +1 -1
  35. package/package.json +9 -12
  36. package/src/tools/AudioPlayer/AudioPlayer.story.tsx +102 -0
  37. package/src/tools/Gallery/Gallery.story.tsx +231 -0
  38. package/src/tools/Gallery/components/compact/GalleryCompact.tsx +72 -7
  39. package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +83 -1
  40. package/src/tools/JsonForm/JsonForm.story.tsx +134 -0
  41. package/src/tools/JsonTree/JsonTree.story.tsx +140 -0
  42. package/src/tools/JsonTree/index.tsx +1 -1
  43. package/src/tools/LottiePlayer/LottiePlayer.story.tsx +95 -0
  44. package/src/tools/Map/Map.story.tsx +300 -0
  45. package/src/tools/Mermaid/Mermaid.story.tsx +131 -0
  46. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +2 -5
  47. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +7 -1
  48. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +4 -2
  49. package/src/tools/Mermaid/index.tsx +1 -1
  50. package/src/tools/PrettyCode/PrettyCode.story.tsx +116 -0
  51. package/src/tools/PrettyCode/index.tsx +1 -1
  52. package/src/tools/VideoPlayer/VideoPlayer.story.tsx +87 -0
  53. package/src/tools/VideoPlayer/utils/resolvers.ts +2 -2
  54. package/dist/JsonTree-6RYAOPSS.mjs +0 -4
  55. package/dist/JsonTree-7OH6CIHT.cjs +0 -10
  56. package/dist/Mermaid.client-OKACITCW.mjs.map +0 -1
  57. package/dist/Mermaid.client-PNXEC6YL.cjs.map +0 -1
  58. package/dist/chunk-47T5ECYV.cjs.map +0 -1
  59. package/dist/chunk-5QT3QYFZ.cjs.map +0 -1
  60. package/dist/chunk-DI3HUXHK.cjs.map +0 -1
  61. package/dist/chunk-G6PRZP5I.mjs.map +0 -1
  62. package/dist/chunk-UOMPPIED.mjs.map +0 -1
  63. package/dist/chunk-W6YHQI4F.mjs.map +0 -1
@@ -0,0 +1,140 @@
1
+ import { defineStory, useBoolean, useSelect, useNumber } from '@djangocfg/playground';
2
+ import JsonTree from './index';
3
+
4
+ export default defineStory({
5
+ title: 'Tools/Json Tree',
6
+ component: JsonTree,
7
+ description: 'Interactive JSON tree viewer with expand/collapse.',
8
+ });
9
+
10
+ const sampleData = {
11
+ user: {
12
+ id: 'usr_123',
13
+ name: 'John Doe',
14
+ email: 'john@example.com',
15
+ roles: ['admin', 'user'],
16
+ settings: {
17
+ theme: 'dark',
18
+ notifications: true,
19
+ language: 'en',
20
+ },
21
+ },
22
+ metadata: {
23
+ createdAt: '2024-01-15T10:30:00Z',
24
+ updatedAt: '2024-01-20T14:45:00Z',
25
+ version: 2,
26
+ },
27
+ };
28
+
29
+ const apiResponse = {
30
+ status: 200,
31
+ data: {
32
+ vehicles: [
33
+ { id: 1, make: 'BMW', model: 'X5', year: 2023, price: 65000 },
34
+ { id: 2, make: 'Mercedes', model: 'GLE', year: 2022, price: 72000 },
35
+ { id: 3, make: 'Audi', model: 'Q7', year: 2023, price: 68000 },
36
+ ],
37
+ pagination: {
38
+ page: 1,
39
+ perPage: 10,
40
+ total: 156,
41
+ hasMore: true,
42
+ },
43
+ },
44
+ meta: {
45
+ requestId: 'req_abc123',
46
+ duration: 45,
47
+ },
48
+ };
49
+
50
+ const nestedData = {
51
+ level1: {
52
+ level2: {
53
+ level3: {
54
+ level4: {
55
+ level5: {
56
+ value: 'deeply nested',
57
+ },
58
+ },
59
+ },
60
+ },
61
+ },
62
+ };
63
+
64
+ const DATA_SAMPLES = {
65
+ user: sampleData,
66
+ api: apiResponse,
67
+ nested: nestedData,
68
+ };
69
+
70
+ export const Interactive = () => {
71
+ const [dataSource] = useSelect('dataSource', {
72
+ options: ['user', 'api', 'nested'] as const,
73
+ defaultValue: 'api',
74
+ label: 'Data Source',
75
+ description: 'Select JSON data to display',
76
+ });
77
+
78
+ const [showExpandControls] = useBoolean('showExpandControls', {
79
+ defaultValue: true,
80
+ label: 'Expand Controls',
81
+ description: 'Show expand/collapse all buttons',
82
+ });
83
+
84
+ const [showActionButtons] = useBoolean('showActionButtons', {
85
+ defaultValue: true,
86
+ label: 'Action Buttons',
87
+ description: 'Show copy/download buttons',
88
+ });
89
+
90
+ const [maxAutoExpandDepth] = useNumber('maxAutoExpandDepth', {
91
+ defaultValue: 2,
92
+ min: 0,
93
+ max: 10,
94
+ label: 'Auto Expand Depth',
95
+ description: 'Maximum depth to expand automatically',
96
+ });
97
+
98
+ return (
99
+ <div className="max-w-2xl h-96">
100
+ <JsonTree
101
+ data={DATA_SAMPLES[dataSource]}
102
+ config={{
103
+ maxAutoExpandDepth,
104
+ showExpandControls,
105
+ showActionButtons,
106
+ }}
107
+ />
108
+ </div>
109
+ );
110
+ };
111
+
112
+ export const Default = () => (
113
+ <div className="max-w-2xl">
114
+ <JsonTree data={sampleData} />
115
+ </div>
116
+ );
117
+
118
+ export const APIResponse = () => (
119
+ <div className="max-w-2xl">
120
+ <JsonTree data={apiResponse} />
121
+ </div>
122
+ );
123
+
124
+ export const DeepNesting = () => (
125
+ <div className="max-w-2xl">
126
+ <JsonTree data={nestedData} />
127
+ </div>
128
+ );
129
+
130
+ export const CollapsedByDefault = () => (
131
+ <div className="max-w-2xl h-96">
132
+ <JsonTree data={apiResponse} config={{ maxAutoExpandDepth: 0 }} />
133
+ </div>
134
+ );
135
+
136
+ export const WithMaxDepth = () => (
137
+ <div className="max-w-2xl h-96">
138
+ <JsonTree data={apiResponse} config={{ maxAutoExpandDepth: 2 }} />
139
+ </div>
140
+ );
@@ -31,7 +31,7 @@ export interface JsonTreeConfig {
31
31
  preserveKeyOrder?: boolean;
32
32
  }
33
33
 
34
- interface JsonTreeComponentProps {
34
+ export interface JsonTreeComponentProps {
35
35
  title?: string;
36
36
  data: unknown;
37
37
  config?: JsonTreeConfig;
@@ -0,0 +1,95 @@
1
+ import { defineStory, useSelect, useBoolean } from '@djangocfg/playground';
2
+ import { LottiePlayer } from './index';
3
+
4
+ export default defineStory({
5
+ title: 'Tools/Lottie Player',
6
+ component: LottiePlayer,
7
+ description: 'Lottie animation player for JSON animations.',
8
+ });
9
+
10
+ // Public Lottie animation URLs
11
+ const ANIMATIONS = {
12
+ loading: 'https://assets2.lottiefiles.com/packages/lf20_usmfx6bp.json',
13
+ success: 'https://assets4.lottiefiles.com/packages/lf20_jbrw3hcz.json',
14
+ rocket: 'https://assets3.lottiefiles.com/packages/lf20_l3qxn9jy.json',
15
+ heart: 'https://assets7.lottiefiles.com/packages/lf20_3vbOcw.json',
16
+ };
17
+
18
+ export const Interactive = () => {
19
+ const [animation] = useSelect('animation', {
20
+ options: ['loading', 'success', 'rocket', 'heart'] as const,
21
+ defaultValue: 'rocket',
22
+ label: 'Animation',
23
+ description: 'Select Lottie animation',
24
+ });
25
+
26
+ const [loop] = useBoolean('loop', {
27
+ defaultValue: true,
28
+ label: 'Loop',
29
+ description: 'Loop animation playback',
30
+ });
31
+
32
+ const [autoplay] = useBoolean('autoplay', {
33
+ defaultValue: true,
34
+ label: 'Autoplay',
35
+ description: 'Auto-start animation',
36
+ });
37
+
38
+ const [controls] = useBoolean('controls', {
39
+ defaultValue: false,
40
+ label: 'Show Controls',
41
+ description: 'Display playback controls',
42
+ });
43
+
44
+ return (
45
+ <div className="w-64 h-64">
46
+ <LottiePlayer
47
+ src={ANIMATIONS[animation]}
48
+ loop={loop}
49
+ autoplay={autoplay}
50
+ controls={controls}
51
+ />
52
+ </div>
53
+ );
54
+ };
55
+
56
+ export const Loading = () => (
57
+ <div className="w-48 h-48">
58
+ <LottiePlayer src={ANIMATIONS.loading} loop autoplay />
59
+ </div>
60
+ );
61
+
62
+ export const Success = () => (
63
+ <div className="w-48 h-48">
64
+ <LottiePlayer src={ANIMATIONS.success} autoplay />
65
+ </div>
66
+ );
67
+
68
+ export const Rocket = () => (
69
+ <div className="w-64 h-64">
70
+ <LottiePlayer src={ANIMATIONS.rocket} loop autoplay />
71
+ </div>
72
+ );
73
+
74
+ export const Heart = () => (
75
+ <div className="w-32 h-32">
76
+ <LottiePlayer src={ANIMATIONS.heart} loop autoplay />
77
+ </div>
78
+ );
79
+
80
+ export const WithControls = () => (
81
+ <div className="w-64 h-64">
82
+ <LottiePlayer
83
+ src={ANIMATIONS.rocket}
84
+ loop
85
+ autoplay
86
+ controls
87
+ />
88
+ </div>
89
+ );
90
+
91
+ export const Paused = () => (
92
+ <div className="w-48 h-48">
93
+ <LottiePlayer src={ANIMATIONS.loading} loop />
94
+ </div>
95
+ );
@@ -0,0 +1,300 @@
1
+ import { useState } from 'react';
2
+ import { defineStory, useSelect, useNumber, useBoolean } from '@djangocfg/playground';
3
+ import { MapContainer, MapMarker, MapPopup, MapProvider, MapView, useMapControl } from './index';
4
+
5
+ export default defineStory({
6
+ title: 'Tools/Map',
7
+ component: MapContainer,
8
+ description: 'Interactive map component using MapLibre GL.',
9
+ });
10
+
11
+ // Sample property locations
12
+ const PROPERTIES = {
13
+ dubai: {
14
+ id: 'dubai-marina',
15
+ latitude: 25.0805,
16
+ longitude: 55.1403,
17
+ name: 'Dubai Marina Tower',
18
+ address: 'Dubai Marina, Dubai, UAE',
19
+ price: '$2,500,000',
20
+ },
21
+ london: {
22
+ id: 'london-chelsea',
23
+ latitude: 51.4875,
24
+ longitude: -0.1687,
25
+ name: 'Chelsea Townhouse',
26
+ address: 'Chelsea, London, UK',
27
+ price: '£3,200,000',
28
+ },
29
+ nyc: {
30
+ id: 'nyc-manhattan',
31
+ latitude: 40.7580,
32
+ longitude: -73.9855,
33
+ name: 'Manhattan Penthouse',
34
+ address: 'Midtown, New York, USA',
35
+ price: '$5,800,000',
36
+ },
37
+ paris: {
38
+ id: 'paris-marais',
39
+ latitude: 48.8566,
40
+ longitude: 2.3522,
41
+ name: 'Le Marais Apartment',
42
+ address: 'Le Marais, Paris, France',
43
+ price: '€1,900,000',
44
+ },
45
+ tokyo: {
46
+ id: 'tokyo-shibuya',
47
+ latitude: 35.6595,
48
+ longitude: 139.7004,
49
+ name: 'Shibuya Residence',
50
+ address: 'Shibuya, Tokyo, Japan',
51
+ price: '¥180,000,000',
52
+ },
53
+ };
54
+
55
+ export const Interactive = () => {
56
+ const [property] = useSelect('property', {
57
+ options: ['dubai', 'london', 'nyc', 'paris', 'tokyo'] as const,
58
+ defaultValue: 'dubai',
59
+ label: 'Property',
60
+ description: 'Select property location',
61
+ });
62
+
63
+ const [mapStyle] = useSelect('mapStyle', {
64
+ options: ['light', 'dark', 'streets', 'satellite'] as const,
65
+ defaultValue: 'light',
66
+ label: 'Map Style',
67
+ description: 'Map visual style',
68
+ });
69
+
70
+ const [zoom] = useNumber('zoom', {
71
+ defaultValue: 14,
72
+ min: 1,
73
+ max: 18,
74
+ label: 'Zoom Level',
75
+ description: 'Map zoom level',
76
+ });
77
+
78
+ const [showMarker] = useBoolean('showMarker', {
79
+ defaultValue: true,
80
+ label: 'Show Marker',
81
+ description: 'Display property marker',
82
+ });
83
+
84
+ const [showResetButton] = useBoolean('showResetButton', {
85
+ defaultValue: true,
86
+ label: 'Reset Button',
87
+ description: 'Show reset view button',
88
+ });
89
+
90
+ const loc = PROPERTIES[property];
91
+ const googleMapsUrl = `https://www.google.com/maps?q=${loc.latitude},${loc.longitude}`;
92
+
93
+ return (
94
+ <div className="space-y-4">
95
+ {/* Property info */}
96
+ <div className="p-4 rounded-lg border border-border bg-card">
97
+ <h3 className="font-semibold text-foreground">{loc.name}</h3>
98
+ <p className="text-sm text-muted-foreground">{loc.address}</p>
99
+ <p className="text-lg font-bold text-primary mt-2">{loc.price}</p>
100
+ </div>
101
+
102
+ {/* Map */}
103
+ <div className="h-80 rounded-xl overflow-hidden border border-border" key={`${property}-${zoom}-${mapStyle}`}>
104
+ <MapContainer
105
+ initialViewport={{
106
+ latitude: loc.latitude,
107
+ longitude: loc.longitude,
108
+ zoom,
109
+ }}
110
+ mapStyle={mapStyle}
111
+ openInMapsUrl={googleMapsUrl}
112
+ showResetButton={showResetButton}
113
+ autoResetDelay={5000}
114
+ >
115
+ {showMarker && (
116
+ <MapMarker marker={loc} color="#10b981" size={32} />
117
+ )}
118
+ </MapContainer>
119
+ </div>
120
+ </div>
121
+ );
122
+ };
123
+
124
+ export const PropertyCard = () => {
125
+ const loc = PROPERTIES.dubai;
126
+ const googleMapsUrl = `https://www.google.com/maps?q=${loc.latitude},${loc.longitude}`;
127
+
128
+ return (
129
+ <div className="max-w-md rounded-xl border border-border bg-card overflow-hidden">
130
+ {/* Map */}
131
+ <div className="aspect-[2/1]">
132
+ <MapContainer
133
+ initialViewport={{
134
+ latitude: loc.latitude,
135
+ longitude: loc.longitude,
136
+ zoom: 14,
137
+ }}
138
+ mapStyle="light"
139
+ attributionControl={false}
140
+ openInMapsUrl={googleMapsUrl}
141
+ autoResetDelay={5000}
142
+ >
143
+ <MapMarker marker={loc} color="#10b981" size={32} />
144
+ </MapContainer>
145
+ </div>
146
+
147
+ {/* Property info */}
148
+ <div className="p-4">
149
+ <h3 className="font-semibold text-foreground">{loc.name}</h3>
150
+ <p className="text-sm text-muted-foreground">{loc.address}</p>
151
+ <p className="text-lg font-bold text-primary mt-2">{loc.price}</p>
152
+ </div>
153
+ </div>
154
+ );
155
+ };
156
+
157
+ export const DarkStyle = () => {
158
+ const loc = PROPERTIES.tokyo;
159
+
160
+ return (
161
+ <div className="h-96 rounded-xl overflow-hidden border border-border">
162
+ <MapContainer
163
+ initialViewport={{
164
+ latitude: loc.latitude,
165
+ longitude: loc.longitude,
166
+ zoom: 13,
167
+ }}
168
+ mapStyle="dark"
169
+ >
170
+ <MapMarker marker={loc} color="#f97316" size={28} />
171
+ </MapContainer>
172
+ </div>
173
+ );
174
+ };
175
+
176
+ export const MultipleMarkers = () => (
177
+ <div className="h-96 rounded-xl overflow-hidden border border-border">
178
+ <MapContainer
179
+ initialViewport={{
180
+ latitude: 40,
181
+ longitude: 0,
182
+ zoom: 2,
183
+ }}
184
+ mapStyle="light"
185
+ showResetButton
186
+ >
187
+ {Object.values(PROPERTIES).map((prop) => (
188
+ <MapMarker key={prop.id} marker={prop} color="#3b82f6" size={24} />
189
+ ))}
190
+ </MapContainer>
191
+ </div>
192
+ );
193
+
194
+ export const WithAutoReset = () => {
195
+ const loc = PROPERTIES.london;
196
+
197
+ return (
198
+ <div className="space-y-2">
199
+ <p className="text-sm text-muted-foreground">
200
+ Pan/zoom the map — it will reset after 3 seconds of inactivity
201
+ </p>
202
+ <div className="h-80 rounded-xl overflow-hidden border border-border">
203
+ <MapContainer
204
+ initialViewport={{
205
+ latitude: loc.latitude,
206
+ longitude: loc.longitude,
207
+ zoom: 15,
208
+ }}
209
+ mapStyle="streets"
210
+ autoResetDelay={3000}
211
+ showResetButton
212
+ >
213
+ <MapMarker marker={loc} color="#ec4899" size={32} />
214
+ </MapContainer>
215
+ </div>
216
+ </div>
217
+ );
218
+ };
219
+
220
+ type PropertyKey = keyof typeof PROPERTIES;
221
+
222
+ // Inner component that uses useMapControl (must be inside MapProvider)
223
+ function MapWithPopupInner({
224
+ selectedId,
225
+ setSelectedId,
226
+ }: {
227
+ selectedId: PropertyKey | null;
228
+ setSelectedId: (id: PropertyKey | null) => void;
229
+ }) {
230
+ const { flyTo } = useMapControl();
231
+ const selectedProperty = selectedId ? PROPERTIES[selectedId] : null;
232
+
233
+ const handleMarkerClick = (key: PropertyKey) => {
234
+ const prop = PROPERTIES[key];
235
+ setSelectedId(key);
236
+ // Fly to the selected marker with animation
237
+ flyTo([prop.longitude, prop.latitude], 12, { duration: 1500 });
238
+ };
239
+
240
+ return (
241
+ <MapView mapStyle="light" showResetButton>
242
+ {(Object.entries(PROPERTIES) as [PropertyKey, typeof PROPERTIES[PropertyKey]][]).map(([key, prop]) => (
243
+ <MapMarker
244
+ key={prop.id}
245
+ marker={prop}
246
+ color={selectedId === key ? '#10b981' : '#3b82f6'}
247
+ size={selectedId === key ? 36 : 28}
248
+ onClick={() => handleMarkerClick(key)}
249
+ />
250
+ ))}
251
+
252
+ {selectedProperty && (
253
+ <MapPopup
254
+ longitude={selectedProperty.longitude}
255
+ latitude={selectedProperty.latitude}
256
+ onClose={() => setSelectedId(null)}
257
+ anchor="bottom"
258
+ offset={20}
259
+ >
260
+ <div className="p-3 min-w-48">
261
+ <h3 className="font-semibold text-foreground text-sm">{selectedProperty.name}</h3>
262
+ <p className="text-xs text-muted-foreground mt-1">{selectedProperty.address}</p>
263
+ <p className="text-base font-bold text-primary mt-2">{selectedProperty.price}</p>
264
+ <a
265
+ href={`https://www.google.com/maps?q=${selectedProperty.latitude},${selectedProperty.longitude}`}
266
+ target="_blank"
267
+ rel="noopener noreferrer"
268
+ className="mt-3 block w-full text-center text-xs px-3 py-1.5 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
269
+ >
270
+ View on Google Maps
271
+ </a>
272
+ </div>
273
+ </MapPopup>
274
+ )}
275
+ </MapView>
276
+ );
277
+ }
278
+
279
+ export const WithPopup = () => {
280
+ const [selectedId, setSelectedId] = useState<PropertyKey | null>(null);
281
+
282
+ return (
283
+ <div className="space-y-2">
284
+ <p className="text-sm text-muted-foreground">
285
+ Click on markers to fly to property and see details
286
+ </p>
287
+ <div className="h-96 rounded-xl overflow-hidden border border-border">
288
+ <MapProvider
289
+ initialViewport={{
290
+ latitude: 40,
291
+ longitude: 0,
292
+ zoom: 2,
293
+ }}
294
+ >
295
+ <MapWithPopupInner selectedId={selectedId} setSelectedId={setSelectedId} />
296
+ </MapProvider>
297
+ </div>
298
+ </div>
299
+ );
300
+ };
@@ -0,0 +1,131 @@
1
+ import { defineStory, useSelect, useBoolean } from '@djangocfg/playground';
2
+ import Mermaid from './index';
3
+
4
+ export default defineStory({
5
+ title: 'Tools/Mermaid',
6
+ component: Mermaid,
7
+ description: 'Mermaid diagram renderer for flowcharts, sequences, and more.',
8
+ });
9
+
10
+ const DIAGRAMS = {
11
+ flowchart: `flowchart TD
12
+ A[Start] --> B{Is it working?}
13
+ B -->|Yes| C[Great!]
14
+ B -->|No| D[Debug]
15
+ D --> B
16
+ C --> E[End]`,
17
+ sequence: `sequenceDiagram
18
+ participant U as User
19
+ participant F as Frontend
20
+ participant A as API
21
+ participant D as Database
22
+
23
+ U->>F: Click login
24
+ F->>A: POST /auth/login
25
+ A->>D: Query user
26
+ D-->>A: User data
27
+ A-->>F: JWT token
28
+ F-->>U: Redirect to dashboard`,
29
+ class: `classDiagram
30
+ class Vehicle {
31
+ +String id
32
+ +String make
33
+ +String model
34
+ +int year
35
+ +float price
36
+ +getFullName()
37
+ }
38
+ class Car {
39
+ +int doors
40
+ +String fuelType
41
+ }
42
+ class Motorcycle {
43
+ +String engineType
44
+ }
45
+ Vehicle <|-- Car
46
+ Vehicle <|-- Motorcycle`,
47
+ er: `erDiagram
48
+ USER ||--o{ ORDER : places
49
+ ORDER ||--|{ LINE_ITEM : contains
50
+ PRODUCT ||--o{ LINE_ITEM : "ordered in"
51
+ USER {
52
+ string id PK
53
+ string email
54
+ string name
55
+ }
56
+ ORDER {
57
+ string id PK
58
+ date created_at
59
+ string status
60
+ }
61
+ PRODUCT {
62
+ string id PK
63
+ string name
64
+ float price
65
+ }`,
66
+ gantt: `gantt
67
+ title Project Timeline
68
+ dateFormat YYYY-MM-DD
69
+ section Design
70
+ Research :a1, 2024-01-01, 7d
71
+ Wireframes :a2, after a1, 5d
72
+ UI Design :a3, after a2, 10d
73
+ section Development
74
+ Frontend :b1, after a3, 14d
75
+ Backend :b2, after a3, 14d
76
+ Integration :b3, after b1, 7d
77
+ section Testing
78
+ QA Testing :c1, after b3, 7d
79
+ Bug Fixes :c2, after c1, 5d`,
80
+ };
81
+
82
+ export const Interactive = () => {
83
+ const [diagramType] = useSelect('diagramType', {
84
+ options: ['flowchart', 'sequence', 'class', 'er', 'gantt'] as const,
85
+ defaultValue: 'flowchart',
86
+ label: 'Diagram Type',
87
+ description: 'Select Mermaid diagram type',
88
+ });
89
+
90
+ const [isCompact] = useBoolean('isCompact', {
91
+ defaultValue: false,
92
+ label: 'Compact Mode',
93
+ description: 'Use smaller font size',
94
+ });
95
+
96
+ return (
97
+ <div className="max-w-4xl">
98
+ <Mermaid chart={DIAGRAMS[diagramType]} isCompact={isCompact} />
99
+ </div>
100
+ );
101
+ };
102
+
103
+ export const Flowchart = () => (
104
+ <div className="max-w-2xl">
105
+ <Mermaid chart={DIAGRAMS.flowchart} />
106
+ </div>
107
+ );
108
+
109
+ export const SequenceDiagram = () => (
110
+ <div className="max-w-3xl">
111
+ <Mermaid chart={DIAGRAMS.sequence} />
112
+ </div>
113
+ );
114
+
115
+ export const ClassDiagram = () => (
116
+ <div className="max-w-2xl">
117
+ <Mermaid chart={DIAGRAMS.class} />
118
+ </div>
119
+ );
120
+
121
+ export const ERDiagram = () => (
122
+ <div className="max-w-3xl">
123
+ <Mermaid chart={DIAGRAMS.er} />
124
+ </div>
125
+ );
126
+
127
+ export const GanttChart = () => (
128
+ <div className="max-w-4xl">
129
+ <Mermaid chart={DIAGRAMS.gantt} />
130
+ </div>
131
+ );
@@ -63,11 +63,8 @@ export function useMermaidCleanup() {
63
63
  };
64
64
  }, [cleanupMermaidErrors]);
65
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]);
66
+ // Removed periodic cleanup - it causes unnecessary re-renders
67
+ // Cleanup only happens on unmount now
71
68
 
72
69
  return { cleanupMermaidErrors };
73
70
  }
@@ -66,7 +66,13 @@ export function useMermaidRenderer({ chart, theme, isCompact = false }: UseMerma
66
66
  const getCSSVariable = (variable: string) => {
67
67
  if (typeof document === 'undefined') return '';
68
68
  const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
69
- return value ? `hsl(${value})` : '';
69
+ if (!value) return '';
70
+ // If value is already a complete color (hex, rgb, hsl with parentheses), return as-is
71
+ if (value.startsWith('#') || value.startsWith('rgb') || value.startsWith('hsl(')) {
72
+ return value;
73
+ }
74
+ // Otherwise assume it's HSL components and wrap in hsl()
75
+ return `hsl(${value})`;
70
76
  };
71
77
 
72
78
  const diagramFontSize = isCompact ? '12px' : '14px';