@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
|
@@ -4,7 +4,7 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react'
|
|
|
4
4
|
import { cn } from '@djangocfg/ui-core/lib'
|
|
5
5
|
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n'
|
|
6
6
|
import { Play, Pause, Volume2, VolumeX, Maximize, AlertCircle } from 'lucide-react'
|
|
7
|
-
import type { GalleryMediaItem } from '
|
|
7
|
+
import type { GalleryMediaItem } from '../../types'
|
|
8
8
|
|
|
9
9
|
export interface GalleryVideoProps {
|
|
10
10
|
/** Video data */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { memo, useCallback, useEffect, useMemo } from 'react'
|
|
4
|
+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
5
5
|
import { cn } from '@djangocfg/ui-core/lib'
|
|
6
6
|
import {
|
|
7
7
|
Carousel,
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
type CarouselApi,
|
|
13
13
|
} from '@djangocfg/ui-core/components'
|
|
14
14
|
import { ZoomIn } from 'lucide-react'
|
|
15
|
-
import { GalleryMedia } from '
|
|
16
|
-
import { GalleryThumbnails } from '
|
|
17
|
-
import type { GalleryMediaItem } from '
|
|
15
|
+
import { GalleryMedia } from '../media'
|
|
16
|
+
import { GalleryThumbnails } from '../thumbnails'
|
|
17
|
+
import type { GalleryMediaItem } from '../../types'
|
|
18
18
|
|
|
19
19
|
export interface GalleryCarouselProps {
|
|
20
20
|
images: GalleryMediaItem[]
|
|
@@ -65,6 +65,12 @@ export const GalleryCarousel = memo(function GalleryCarousel({
|
|
|
65
65
|
className,
|
|
66
66
|
}: GalleryCarouselProps) {
|
|
67
67
|
const [api, setApi] = React.useState<CarouselApi>()
|
|
68
|
+
// Track if component is mounted (client-side) to avoid hydration mismatches
|
|
69
|
+
const [isMounted, setIsMounted] = useState(false)
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
setIsMounted(true)
|
|
73
|
+
}, [])
|
|
68
74
|
|
|
69
75
|
// Notify parent when API is ready
|
|
70
76
|
useEffect(() => {
|
|
@@ -136,6 +142,107 @@ export const GalleryCarousel = memo(function GalleryCarousel({
|
|
|
136
142
|
[api]
|
|
137
143
|
)
|
|
138
144
|
|
|
145
|
+
// Track pointer down position to distinguish click from drag
|
|
146
|
+
const pointerStartRef = useRef<{ x: number; y: number; time: number } | null>(null)
|
|
147
|
+
const CLICK_THRESHOLD = 10 // pixels
|
|
148
|
+
const CLICK_TIME_THRESHOLD = 300 // ms
|
|
149
|
+
|
|
150
|
+
const handlePointerDown = useCallback((e: React.PointerEvent) => {
|
|
151
|
+
pointerStartRef.current = { x: e.clientX, y: e.clientY, time: Date.now() }
|
|
152
|
+
}, [])
|
|
153
|
+
|
|
154
|
+
const handlePointerUp = useCallback((e: React.PointerEvent) => {
|
|
155
|
+
if (!pointerStartRef.current || !enableLightbox || !onLightboxOpen) return
|
|
156
|
+
|
|
157
|
+
const { x, y, time } = pointerStartRef.current
|
|
158
|
+
const dx = Math.abs(e.clientX - x)
|
|
159
|
+
const dy = Math.abs(e.clientY - y)
|
|
160
|
+
const dt = Date.now() - time
|
|
161
|
+
|
|
162
|
+
// Only trigger click if pointer didn't move much and was quick
|
|
163
|
+
if (dx < CLICK_THRESHOLD && dy < CLICK_THRESHOLD && dt < CLICK_TIME_THRESHOLD) {
|
|
164
|
+
onLightboxOpen()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pointerStartRef.current = null
|
|
168
|
+
}, [enableLightbox, onLightboxOpen])
|
|
169
|
+
|
|
170
|
+
// SSR fallback - render first image only to avoid hydration mismatch
|
|
171
|
+
// The carousel requires DOM access and will cause issues during SSR
|
|
172
|
+
if (!isMounted) {
|
|
173
|
+
return (
|
|
174
|
+
<div className={cn('space-y-3', className)}>
|
|
175
|
+
<div className="relative rounded-xl overflow-hidden">
|
|
176
|
+
<div
|
|
177
|
+
className="relative bg-muted"
|
|
178
|
+
style={{ aspectRatio }}
|
|
179
|
+
>
|
|
180
|
+
<GalleryMedia
|
|
181
|
+
media={images[0]}
|
|
182
|
+
className="w-full h-full"
|
|
183
|
+
priority
|
|
184
|
+
/>
|
|
185
|
+
{/* Dark overlay */}
|
|
186
|
+
<div className="absolute inset-0 bg-gradient-to-t from-black/40 via-transparent to-black/20 pointer-events-none" />
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
{/* Counter */}
|
|
190
|
+
{showCounter && hasMultiple && (
|
|
191
|
+
<div
|
|
192
|
+
className={cn(
|
|
193
|
+
'absolute bottom-3 right-3 z-10',
|
|
194
|
+
'bg-black/60 backdrop-blur-sm text-white px-3 py-1.5 rounded-full',
|
|
195
|
+
'text-sm font-medium'
|
|
196
|
+
)}
|
|
197
|
+
>
|
|
198
|
+
1 / {total}
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
|
|
202
|
+
{/* Mobile dots */}
|
|
203
|
+
{hasMultiple && (
|
|
204
|
+
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-2 md:hidden z-10">
|
|
205
|
+
{mobileDots.map((index) => (
|
|
206
|
+
<div
|
|
207
|
+
key={index}
|
|
208
|
+
className={cn(
|
|
209
|
+
'rounded-full',
|
|
210
|
+
index === 0
|
|
211
|
+
? 'w-2.5 h-2.5 bg-white shadow-[0_0_4px_rgba(255,255,255,0.8)]'
|
|
212
|
+
: 'w-2 h-2 bg-white/40'
|
|
213
|
+
)}
|
|
214
|
+
/>
|
|
215
|
+
))}
|
|
216
|
+
{remainingCount > 0 && (
|
|
217
|
+
<span className="text-white/70 text-xs ml-1">+{remainingCount}</span>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
{/* Thumbnails placeholder */}
|
|
224
|
+
{showThumbnails && hasMultiple && (
|
|
225
|
+
<div className="hidden md:flex gap-2">
|
|
226
|
+
{images.slice(0, 8).map((image, index) => (
|
|
227
|
+
<div
|
|
228
|
+
key={image.id}
|
|
229
|
+
className={cn(
|
|
230
|
+
'w-16 h-16 rounded-lg overflow-hidden bg-muted flex-shrink-0',
|
|
231
|
+
index === 0 && 'ring-2 ring-primary'
|
|
232
|
+
)}
|
|
233
|
+
>
|
|
234
|
+
<GalleryMedia
|
|
235
|
+
media={image}
|
|
236
|
+
className="w-full h-full"
|
|
237
|
+
/>
|
|
238
|
+
</div>
|
|
239
|
+
))}
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
139
246
|
return (
|
|
140
247
|
<div className={cn('space-y-3', className)}>
|
|
141
248
|
<Carousel
|
|
@@ -150,9 +257,10 @@ export const GalleryCarousel = memo(function GalleryCarousel({
|
|
|
150
257
|
{images.map((image, index) => (
|
|
151
258
|
<CarouselItem key={image.id} className="pl-0">
|
|
152
259
|
<div
|
|
153
|
-
className=
|
|
260
|
+
className={cn('relative bg-muted', enableLightbox && 'cursor-pointer')}
|
|
154
261
|
style={{ aspectRatio }}
|
|
155
|
-
|
|
262
|
+
onPointerDown={enableLightbox ? handlePointerDown : undefined}
|
|
263
|
+
onPointerUp={enableLightbox ? handlePointerUp : undefined}
|
|
156
264
|
>
|
|
157
265
|
<GalleryMedia
|
|
158
266
|
media={image}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { memo, useCallback, useMemo, useState } from 'react'
|
|
4
4
|
import { cn } from '@djangocfg/ui-core/lib'
|
|
5
5
|
import { Play } from 'lucide-react'
|
|
6
|
-
import type { GalleryMediaItem } from '
|
|
6
|
+
import type { GalleryMediaItem } from '../../types'
|
|
7
7
|
|
|
8
8
|
export type GalleryGridLayout =
|
|
9
9
|
| 'auto'
|
package/src/tools/Gallery/components/{GalleryThumbnails.tsx → thumbnails/GalleryThumbnails.tsx}
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { memo, useRef, useEffect, useCallback, Suspense, lazy } from 'react'
|
|
4
4
|
import { cn } from '@djangocfg/ui-core/lib'
|
|
5
|
-
import type { GalleryThumbnailsProps } from '
|
|
5
|
+
import type { GalleryThumbnailsProps } from '../../types'
|
|
6
6
|
|
|
7
7
|
// Lazy load virtualized thumbnails - only for 50+ images
|
|
8
8
|
const GalleryThumbnailsVirtual = lazy(() =>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { memo, useCallback, useEffect } from 'react'
|
|
4
4
|
import { cn } from '@djangocfg/ui-core/lib'
|
|
5
|
-
import { useVirtualList } from '
|
|
6
|
-
import type { GalleryThumbnailsProps } from '
|
|
5
|
+
import { useVirtualList } from '../../hooks/useVirtualList'
|
|
6
|
+
import type { GalleryThumbnailsProps } from '../../types'
|
|
7
7
|
|
|
8
8
|
const SIZES = {
|
|
9
9
|
sm: { width: 56, height: 40 },
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { defineStory, useSelect, useBoolean } from '@djangocfg/playground';
|
|
2
|
+
import { JsonSchemaForm } from './index';
|
|
3
|
+
|
|
4
|
+
export default defineStory({
|
|
5
|
+
title: 'Tools/Json Schema Form',
|
|
6
|
+
component: JsonSchemaForm,
|
|
7
|
+
description: 'Dynamic form generator from JSON Schema using react-jsonschema-form.',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const SCHEMAS = {
|
|
11
|
+
simple: {
|
|
12
|
+
schema: {
|
|
13
|
+
type: 'object' as const,
|
|
14
|
+
required: ['name', 'email'],
|
|
15
|
+
properties: {
|
|
16
|
+
name: { type: 'string' as const, title: 'Name' },
|
|
17
|
+
email: { type: 'string' as const, title: 'Email', format: 'email' },
|
|
18
|
+
age: { type: 'integer' as const, title: 'Age', minimum: 0, maximum: 120 },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
uiSchema: {},
|
|
22
|
+
},
|
|
23
|
+
vehicle: {
|
|
24
|
+
schema: {
|
|
25
|
+
type: 'object' as const,
|
|
26
|
+
required: ['make', 'model', 'year'],
|
|
27
|
+
properties: {
|
|
28
|
+
make: {
|
|
29
|
+
type: 'string' as const,
|
|
30
|
+
title: 'Make',
|
|
31
|
+
enum: ['BMW', 'Mercedes', 'Audi', 'Porsche', 'Tesla'],
|
|
32
|
+
},
|
|
33
|
+
model: { type: 'string' as const, title: 'Model' },
|
|
34
|
+
year: { type: 'integer' as const, title: 'Year', minimum: 1990, maximum: 2025 },
|
|
35
|
+
price: { type: 'number' as const, title: 'Price ($)' },
|
|
36
|
+
features: {
|
|
37
|
+
type: 'array' as const,
|
|
38
|
+
title: 'Features',
|
|
39
|
+
items: {
|
|
40
|
+
type: 'string' as const,
|
|
41
|
+
enum: ['Leather', 'Navigation', 'Sunroof', 'Heated Seats', 'Parking Sensors'],
|
|
42
|
+
},
|
|
43
|
+
uniqueItems: true,
|
|
44
|
+
},
|
|
45
|
+
description: { type: 'string' as const, title: 'Description' },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
uiSchema: {
|
|
49
|
+
description: { 'ui:widget': 'textarea' },
|
|
50
|
+
features: { 'ui:widget': 'checkboxes' },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
contact: {
|
|
54
|
+
schema: {
|
|
55
|
+
type: 'object' as const,
|
|
56
|
+
required: ['firstName', 'lastName', 'email'],
|
|
57
|
+
properties: {
|
|
58
|
+
firstName: { type: 'string' as const, title: 'First Name' },
|
|
59
|
+
lastName: { type: 'string' as const, title: 'Last Name' },
|
|
60
|
+
email: { type: 'string' as const, title: 'Email', format: 'email' },
|
|
61
|
+
phone: { type: 'string' as const, title: 'Phone' },
|
|
62
|
+
message: { type: 'string' as const, title: 'Message' },
|
|
63
|
+
subscribe: { type: 'boolean' as const, title: 'Subscribe to newsletter' },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
uiSchema: {
|
|
67
|
+
message: { 'ui:widget': 'textarea' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const Interactive = () => {
|
|
73
|
+
const [schemaType] = useSelect('schemaType', {
|
|
74
|
+
options: ['simple', 'vehicle', 'contact'] as const,
|
|
75
|
+
defaultValue: 'vehicle',
|
|
76
|
+
label: 'Schema Type',
|
|
77
|
+
description: 'Select form schema',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const [liveValidate] = useBoolean('liveValidate', {
|
|
81
|
+
defaultValue: false,
|
|
82
|
+
label: 'Live Validate',
|
|
83
|
+
description: 'Validate on every change',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const config = SCHEMAS[schemaType];
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="max-w-lg">
|
|
90
|
+
<JsonSchemaForm
|
|
91
|
+
schema={config.schema}
|
|
92
|
+
uiSchema={config.uiSchema}
|
|
93
|
+
liveValidate={liveValidate}
|
|
94
|
+
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const SimpleForm = () => (
|
|
101
|
+
<div className="max-w-md">
|
|
102
|
+
<JsonSchemaForm
|
|
103
|
+
schema={SCHEMAS.simple.schema}
|
|
104
|
+
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
export const VehicleForm = () => (
|
|
110
|
+
<div className="max-w-lg">
|
|
111
|
+
<JsonSchemaForm
|
|
112
|
+
schema={SCHEMAS.vehicle.schema}
|
|
113
|
+
uiSchema={SCHEMAS.vehicle.uiSchema}
|
|
114
|
+
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
export const WithDefaultValues = () => (
|
|
120
|
+
<div className="max-w-lg">
|
|
121
|
+
<JsonSchemaForm
|
|
122
|
+
schema={SCHEMAS.vehicle.schema}
|
|
123
|
+
uiSchema={SCHEMAS.vehicle.uiSchema}
|
|
124
|
+
formData={{
|
|
125
|
+
make: 'BMW',
|
|
126
|
+
model: 'X5',
|
|
127
|
+
year: 2023,
|
|
128
|
+
price: 65000,
|
|
129
|
+
features: ['Leather', 'Navigation'],
|
|
130
|
+
}}
|
|
131
|
+
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
@@ -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
|
+
);
|
|
@@ -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
|
+
);
|