@djangocfg/ui-tools 2.1.118 → 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.
- package/dist/JsonTree-G2TPWQ4C.mjs +4 -0
- package/dist/{JsonTree-6RYAOPSS.mjs.map → JsonTree-G2TPWQ4C.mjs.map} +1 -1
- package/dist/JsonTree-TWXUBBIG.cjs +10 -0
- package/dist/{JsonTree-7OH6CIHT.cjs.map → JsonTree-TWXUBBIG.cjs.map} +1 -1
- package/dist/{Mermaid.client-PNXEC6YL.cjs → Mermaid.client-AF4WOQZR.cjs} +9 -11
- package/dist/Mermaid.client-AF4WOQZR.cjs.map +1 -0
- package/dist/{Mermaid.client-OKACITCW.mjs → Mermaid.client-W4QXJX7Q.mjs} +9 -11
- package/dist/Mermaid.client-W4QXJX7Q.mjs.map +1 -0
- package/dist/{PlaygroundLayout-SYMEAG3J.cjs → PlaygroundLayout-RZMJWH3Y.cjs} +25 -25
- package/dist/{PlaygroundLayout-SYMEAG3J.cjs.map → PlaygroundLayout-RZMJWH3Y.cjs.map} +1 -1
- package/dist/{PlaygroundLayout-UQRBU5RH.mjs → PlaygroundLayout-UQABCZ6K.mjs} +4 -4
- package/dist/{PlaygroundLayout-UQRBU5RH.mjs.map → PlaygroundLayout-UQABCZ6K.mjs.map} +1 -1
- package/dist/{chunk-UOMPPIED.mjs → chunk-4G4UGMOP.mjs} +3 -3
- package/dist/chunk-4G4UGMOP.mjs.map +1 -0
- package/dist/{chunk-47T5ECYV.cjs → chunk-CY3CQS26.cjs} +3 -3
- package/dist/chunk-CY3CQS26.cjs.map +1 -0
- package/dist/{chunk-5QT3QYFZ.cjs → chunk-EGYUND4E.cjs} +2 -2
- package/dist/chunk-EGYUND4E.cjs.map +1 -0
- package/dist/{chunk-DI3HUXHK.cjs → chunk-OYLQZT62.cjs} +2 -2
- package/dist/chunk-OYLQZT62.cjs.map +1 -0
- package/dist/{chunk-W6YHQI4F.mjs → chunk-OYYCGIBF.mjs} +2 -2
- package/dist/chunk-OYYCGIBF.mjs.map +1 -0
- package/dist/{chunk-G6PRZP5I.mjs → chunk-XZZ22EHP.mjs} +2 -2
- package/dist/chunk-XZZ22EHP.mjs.map +1 -0
- package/dist/{components-EASJYK45.mjs → components-4YSJ5ALL.mjs} +3 -3
- package/dist/{components-CJ2IB65O.cjs.map → components-4YSJ5ALL.mjs.map} +1 -1
- package/dist/{components-CJ2IB65O.cjs → components-BSTP3VLD.cjs} +7 -7
- package/dist/{components-EASJYK45.mjs.map → components-BSTP3VLD.cjs.map} +1 -1
- package/dist/index.cjs +27 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +10 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -5
- package/src/tools/AudioPlayer/AudioPlayer.story.tsx +102 -0
- package/src/tools/Gallery/Gallery.story.tsx +231 -0
- package/src/tools/Gallery/components/Gallery.tsx +3 -3
- package/src/tools/Gallery/components/compact/GalleryCompact.tsx +363 -0
- package/src/tools/Gallery/components/compact/index.ts +1 -0
- package/src/tools/Gallery/components/index.ts +17 -12
- package/src/tools/Gallery/components/{GalleryLightbox.tsx → lightbox/GalleryLightbox.tsx} +6 -6
- package/src/tools/Gallery/components/lightbox/index.ts +1 -0
- package/src/tools/Gallery/components/{GalleryImage.tsx → media/GalleryImage.tsx} +1 -1
- package/src/tools/Gallery/components/{GalleryMedia.tsx → media/GalleryMedia.tsx} +1 -1
- package/src/tools/Gallery/components/{GalleryVideo.tsx → media/GalleryVideo.tsx} +1 -1
- package/src/tools/Gallery/components/media/index.ts +3 -0
- package/src/tools/Gallery/components/{GalleryCarousel.tsx → preview/GalleryCarousel.tsx} +114 -6
- package/src/tools/Gallery/components/{GalleryGrid.tsx → preview/GalleryGrid.tsx} +1 -1
- package/src/tools/Gallery/components/preview/index.ts +2 -0
- package/src/tools/Gallery/components/{GalleryThumbnails.tsx → thumbnails/GalleryThumbnails.tsx} +1 -1
- package/src/tools/Gallery/components/{GalleryThumbnailsVirtual.tsx → thumbnails/GalleryThumbnailsVirtual.tsx} +2 -2
- package/src/tools/Gallery/components/thumbnails/index.ts +2 -0
- package/src/tools/JsonForm/JsonForm.story.tsx +134 -0
- package/src/tools/JsonTree/JsonTree.story.tsx +140 -0
- package/src/tools/JsonTree/index.tsx +1 -1
- package/src/tools/LottiePlayer/LottiePlayer.story.tsx +95 -0
- package/src/tools/Map/Map.story.tsx +300 -0
- package/src/tools/Mermaid/Mermaid.story.tsx +131 -0
- package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +2 -5
- package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +7 -1
- package/src/tools/Mermaid/hooks/useMermaidValidation.ts +4 -2
- package/src/tools/Mermaid/index.tsx +1 -1
- package/src/tools/PrettyCode/PrettyCode.story.tsx +116 -0
- package/src/tools/PrettyCode/index.tsx +1 -1
- package/src/tools/VideoPlayer/VideoPlayer.story.tsx +87 -0
- package/src/tools/VideoPlayer/utils/resolvers.ts +2 -2
- package/dist/JsonTree-6RYAOPSS.mjs +0 -4
- package/dist/JsonTree-7OH6CIHT.cjs +0 -10
- package/dist/Mermaid.client-OKACITCW.mjs.map +0 -1
- package/dist/Mermaid.client-PNXEC6YL.cjs.map +0 -1
- package/dist/chunk-47T5ECYV.cjs.map +0 -1
- package/dist/chunk-5QT3QYFZ.cjs.map +0 -1
- package/dist/chunk-DI3HUXHK.cjs.map +0 -1
- package/dist/chunk-G6PRZP5I.mjs.map +0 -1
- package/dist/chunk-UOMPPIED.mjs.map +0 -1
- package/dist/chunk-W6YHQI4F.mjs.map +0 -1
- package/src/tools/Gallery/components/GalleryCompact.tsx +0 -173
|
@@ -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
|
-
//
|
|
67
|
-
|
|
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
|
-
|
|
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';
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Hook for validating Mermaid code completeness
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
|
|
5
7
|
export function useMermaidValidation() {
|
|
6
|
-
const isMermaidCodeComplete = (code: string): boolean => {
|
|
8
|
+
const isMermaidCodeComplete = useCallback((code: string): boolean => {
|
|
7
9
|
if (!code || code.trim().length === 0) return false;
|
|
8
10
|
|
|
9
11
|
const trimmed = code.trim();
|
|
@@ -23,7 +25,7 @@ export function useMermaidValidation() {
|
|
|
23
25
|
if (lastLine.match(/[\[({]\s*$/)) return false;
|
|
24
26
|
|
|
25
27
|
return true;
|
|
26
|
-
};
|
|
28
|
+
}, []);
|
|
27
29
|
|
|
28
30
|
return { isMermaidCodeComplete };
|
|
29
31
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { defineStory, useSelect, useBoolean } from '@djangocfg/playground';
|
|
2
|
+
import PrettyCode from './index';
|
|
3
|
+
|
|
4
|
+
export default defineStory({
|
|
5
|
+
title: 'Tools/Pretty Code',
|
|
6
|
+
component: PrettyCode,
|
|
7
|
+
description: 'Syntax highlighted code block with copy button.',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const CODE_SAMPLES = {
|
|
11
|
+
javascript: `function fibonacci(n) {
|
|
12
|
+
if (n <= 1) return n;
|
|
13
|
+
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Calculate first 10 fibonacci numbers
|
|
17
|
+
const results = Array.from({ length: 10 }, (_, i) => fibonacci(i));
|
|
18
|
+
console.log(results);`,
|
|
19
|
+
typescript: `interface User {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
email: string;
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function fetchUser(id: string): Promise<User> {
|
|
27
|
+
const response = await fetch(\`/api/users/\${id}\`);
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error('User not found');
|
|
30
|
+
}
|
|
31
|
+
return response.json();
|
|
32
|
+
}`,
|
|
33
|
+
python: `from dataclasses import dataclass
|
|
34
|
+
from typing import List, Optional
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Vehicle:
|
|
38
|
+
id: str
|
|
39
|
+
make: str
|
|
40
|
+
model: str
|
|
41
|
+
year: int
|
|
42
|
+
price: Optional[float] = None
|
|
43
|
+
|
|
44
|
+
def filter_vehicles(
|
|
45
|
+
vehicles: List[Vehicle],
|
|
46
|
+
min_year: int = 2020
|
|
47
|
+
) -> List[Vehicle]:
|
|
48
|
+
return [v for v in vehicles if v.year >= min_year]`,
|
|
49
|
+
css: `.card {
|
|
50
|
+
@apply rounded-xl border border-border bg-card;
|
|
51
|
+
@apply p-4 shadow-sm transition-shadow;
|
|
52
|
+
|
|
53
|
+
&:hover {
|
|
54
|
+
@apply shadow-md;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.card-title {
|
|
58
|
+
@apply text-lg font-semibold text-foreground;
|
|
59
|
+
}
|
|
60
|
+
}`,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Interactive = () => {
|
|
64
|
+
const [language] = useSelect('language', {
|
|
65
|
+
options: ['javascript', 'typescript', 'python', 'css'] as const,
|
|
66
|
+
defaultValue: 'typescript',
|
|
67
|
+
label: 'Language',
|
|
68
|
+
description: 'Programming language for syntax highlighting',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const [mode] = useSelect('mode', {
|
|
72
|
+
options: ['dark', 'light'] as const,
|
|
73
|
+
defaultValue: 'dark',
|
|
74
|
+
label: 'Theme',
|
|
75
|
+
description: 'Color theme for code',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const [isCompact] = useBoolean('isCompact', {
|
|
79
|
+
defaultValue: false,
|
|
80
|
+
label: 'Compact',
|
|
81
|
+
description: 'Use compact styling',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<PrettyCode
|
|
86
|
+
data={CODE_SAMPLES[language]}
|
|
87
|
+
language={language}
|
|
88
|
+
mode={mode}
|
|
89
|
+
isCompact={isCompact}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const JavaScript = () => (
|
|
95
|
+
<PrettyCode data={CODE_SAMPLES.javascript} language="javascript" />
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
export const TypeScript = () => (
|
|
99
|
+
<PrettyCode data={CODE_SAMPLES.typescript} language="typescript" />
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
export const Python = () => (
|
|
103
|
+
<PrettyCode data={CODE_SAMPLES.python} language="python" />
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export const CSS = () => (
|
|
107
|
+
<PrettyCode data={CODE_SAMPLES.css} language="css" />
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
export const LightMode = () => (
|
|
111
|
+
<PrettyCode data={CODE_SAMPLES.typescript} language="typescript" mode="light" />
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
export const Compact = () => (
|
|
115
|
+
<PrettyCode data={CODE_SAMPLES.javascript} language="javascript" isCompact />
|
|
116
|
+
);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defineStory, useBoolean, useSelect } from '@djangocfg/playground';
|
|
2
|
+
import { VideoPlayer } from './index';
|
|
3
|
+
import type { VideoSourceUnion } from './types';
|
|
4
|
+
|
|
5
|
+
export default defineStory({
|
|
6
|
+
title: 'Tools/Video Player',
|
|
7
|
+
component: VideoPlayer,
|
|
8
|
+
description: 'Video player with HLS support, quality selection, and custom controls.',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const VIDEO_SOURCES: Record<string, VideoSourceUnion> = {
|
|
12
|
+
mp4: {
|
|
13
|
+
type: 'url',
|
|
14
|
+
url: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
|
15
|
+
poster: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg',
|
|
16
|
+
title: 'Big Buck Bunny',
|
|
17
|
+
},
|
|
18
|
+
hls: {
|
|
19
|
+
type: 'hls',
|
|
20
|
+
url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
|
|
21
|
+
title: 'HLS Stream',
|
|
22
|
+
},
|
|
23
|
+
elephants: {
|
|
24
|
+
type: 'url',
|
|
25
|
+
url: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
|
|
26
|
+
poster: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ElephantsDream.jpg',
|
|
27
|
+
title: "Elephant's Dream",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Interactive = () => {
|
|
32
|
+
const [sourceKey] = useSelect('source', {
|
|
33
|
+
options: ['mp4', 'hls', 'elephants'] as const,
|
|
34
|
+
defaultValue: 'mp4',
|
|
35
|
+
label: 'Video Source',
|
|
36
|
+
description: 'Select video source type',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const [autoplay] = useBoolean('autoplay', {
|
|
40
|
+
defaultValue: false,
|
|
41
|
+
label: 'Autoplay',
|
|
42
|
+
description: 'Auto-start video playback',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const [muted] = useBoolean('muted', {
|
|
46
|
+
defaultValue: false,
|
|
47
|
+
label: 'Muted',
|
|
48
|
+
description: 'Mute video audio',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const [loop] = useBoolean('loop', {
|
|
52
|
+
defaultValue: false,
|
|
53
|
+
label: 'Loop',
|
|
54
|
+
description: 'Loop video playback',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const source = VIDEO_SOURCES[sourceKey];
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="max-w-3xl" key={sourceKey}>
|
|
61
|
+
<VideoPlayer
|
|
62
|
+
source={source}
|
|
63
|
+
autoPlay={autoplay}
|
|
64
|
+
muted={muted}
|
|
65
|
+
loop={loop}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const MP4 = () => (
|
|
72
|
+
<div className="max-w-3xl">
|
|
73
|
+
<VideoPlayer source={VIDEO_SOURCES.mp4} />
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
export const HLS = () => (
|
|
78
|
+
<div className="max-w-3xl">
|
|
79
|
+
<VideoPlayer source={VIDEO_SOURCES.hls} />
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
export const WithPoster = () => (
|
|
84
|
+
<div className="max-w-3xl">
|
|
85
|
+
<VideoPlayer source={VIDEO_SOURCES.elephants} />
|
|
86
|
+
</div>
|
|
87
|
+
);
|