@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.
Files changed (78) 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 +12 -5
  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/Gallery.tsx +3 -3
  39. package/src/tools/Gallery/components/compact/GalleryCompact.tsx +363 -0
  40. package/src/tools/Gallery/components/compact/index.ts +1 -0
  41. package/src/tools/Gallery/components/index.ts +17 -12
  42. package/src/tools/Gallery/components/{GalleryLightbox.tsx → lightbox/GalleryLightbox.tsx} +6 -6
  43. package/src/tools/Gallery/components/lightbox/index.ts +1 -0
  44. package/src/tools/Gallery/components/{GalleryImage.tsx → media/GalleryImage.tsx} +1 -1
  45. package/src/tools/Gallery/components/{GalleryMedia.tsx → media/GalleryMedia.tsx} +1 -1
  46. package/src/tools/Gallery/components/{GalleryVideo.tsx → media/GalleryVideo.tsx} +1 -1
  47. package/src/tools/Gallery/components/media/index.ts +3 -0
  48. package/src/tools/Gallery/components/{GalleryCarousel.tsx → preview/GalleryCarousel.tsx} +114 -6
  49. package/src/tools/Gallery/components/{GalleryGrid.tsx → preview/GalleryGrid.tsx} +1 -1
  50. package/src/tools/Gallery/components/preview/index.ts +2 -0
  51. package/src/tools/Gallery/components/{GalleryThumbnails.tsx → thumbnails/GalleryThumbnails.tsx} +1 -1
  52. package/src/tools/Gallery/components/{GalleryThumbnailsVirtual.tsx → thumbnails/GalleryThumbnailsVirtual.tsx} +2 -2
  53. package/src/tools/Gallery/components/thumbnails/index.ts +2 -0
  54. package/src/tools/JsonForm/JsonForm.story.tsx +134 -0
  55. package/src/tools/JsonTree/JsonTree.story.tsx +140 -0
  56. package/src/tools/JsonTree/index.tsx +1 -1
  57. package/src/tools/LottiePlayer/LottiePlayer.story.tsx +95 -0
  58. package/src/tools/Map/Map.story.tsx +300 -0
  59. package/src/tools/Mermaid/Mermaid.story.tsx +131 -0
  60. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +2 -5
  61. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +7 -1
  62. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +4 -2
  63. package/src/tools/Mermaid/index.tsx +1 -1
  64. package/src/tools/PrettyCode/PrettyCode.story.tsx +116 -0
  65. package/src/tools/PrettyCode/index.tsx +1 -1
  66. package/src/tools/VideoPlayer/VideoPlayer.story.tsx +87 -0
  67. package/src/tools/VideoPlayer/utils/resolvers.ts +2 -2
  68. package/dist/JsonTree-6RYAOPSS.mjs +0 -4
  69. package/dist/JsonTree-7OH6CIHT.cjs +0 -10
  70. package/dist/Mermaid.client-OKACITCW.mjs.map +0 -1
  71. package/dist/Mermaid.client-PNXEC6YL.cjs.map +0 -1
  72. package/dist/chunk-47T5ECYV.cjs.map +0 -1
  73. package/dist/chunk-5QT3QYFZ.cjs.map +0 -1
  74. package/dist/chunk-DI3HUXHK.cjs.map +0 -1
  75. package/dist/chunk-G6PRZP5I.mjs.map +0 -1
  76. package/dist/chunk-UOMPPIED.mjs.map +0 -1
  77. package/dist/chunk-W6YHQI4F.mjs.map +0 -1
  78. 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 '../types'
7
+ import type { GalleryMediaItem } from '../../types'
8
8
 
9
9
  export interface GalleryVideoProps {
10
10
  /** Video data */
@@ -0,0 +1,3 @@
1
+ export { GalleryImage } from './GalleryImage'
2
+ export { GalleryVideo } from './GalleryVideo'
3
+ export { GalleryMedia, type GalleryMediaProps } from './GalleryMedia'
@@ -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 './GalleryMedia'
16
- import { GalleryThumbnails } from './GalleryThumbnails'
17
- import type { GalleryMediaItem } from '../types'
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="relative bg-muted cursor-pointer"
260
+ className={cn('relative bg-muted', enableLightbox && 'cursor-pointer')}
154
261
  style={{ aspectRatio }}
155
- onClick={enableLightbox ? onLightboxOpen : undefined}
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 '../types'
6
+ import type { GalleryMediaItem } from '../../types'
7
7
 
8
8
  export type GalleryGridLayout =
9
9
  | 'auto'
@@ -0,0 +1,2 @@
1
+ export { GalleryCarousel } from './GalleryCarousel'
2
+ export { GalleryGrid, type GalleryGridProps, type GalleryGridLayout } from './GalleryGrid'
@@ -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 '../types'
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 '../hooks/useVirtualList'
6
- import type { GalleryThumbnailsProps } from '../types'
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,2 @@
1
+ export { GalleryThumbnails } from './GalleryThumbnails'
2
+ export { GalleryThumbnailsVirtual } from './GalleryThumbnailsVirtual'
@@ -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
+ );
@@ -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
+ );