@djangocfg/ui-tools 2.1.110 → 2.1.111

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 (159) hide show
  1. package/README.md +242 -49
  2. package/dist/JsonSchemaForm-65NLLK56.mjs +4 -0
  3. package/dist/JsonSchemaForm-65NLLK56.mjs.map +1 -0
  4. package/dist/JsonSchemaForm-PY6DH3HE.cjs +13 -0
  5. package/dist/JsonSchemaForm-PY6DH3HE.cjs.map +1 -0
  6. package/dist/JsonTree-6RYAOPSS.mjs +4 -0
  7. package/dist/JsonTree-6RYAOPSS.mjs.map +1 -0
  8. package/dist/JsonTree-7OH6CIHT.cjs +10 -0
  9. package/dist/JsonTree-7OH6CIHT.cjs.map +1 -0
  10. package/dist/MapContainer-GXQLP5WY.mjs +214 -0
  11. package/dist/MapContainer-GXQLP5WY.mjs.map +1 -0
  12. package/dist/MapContainer-RYG4HPH4.cjs +221 -0
  13. package/dist/MapContainer-RYG4HPH4.cjs.map +1 -0
  14. package/dist/{Mermaid.client-4OCKJ6QD.mjs → Mermaid.client-OKACITCW.mjs} +16 -7
  15. package/dist/Mermaid.client-OKACITCW.mjs.map +1 -0
  16. package/dist/{Mermaid.client-ZP6OE46Z.cjs → Mermaid.client-PNXEC6YL.cjs} +16 -7
  17. package/dist/Mermaid.client-PNXEC6YL.cjs.map +1 -0
  18. package/dist/{PlaygroundLayout-XXVBU4WZ.cjs → PlaygroundLayout-SYMEAG3J.cjs} +25 -24
  19. package/dist/PlaygroundLayout-SYMEAG3J.cjs.map +1 -0
  20. package/dist/{PlaygroundLayout-LMQTVXSP.mjs → PlaygroundLayout-UQRBU5RH.mjs} +4 -3
  21. package/dist/PlaygroundLayout-UQRBU5RH.mjs.map +1 -0
  22. package/dist/{PrettyCode.client-2CLSV2VD.cjs → PrettyCode.client-DANYYQYO.cjs} +11 -4
  23. package/dist/PrettyCode.client-DANYYQYO.cjs.map +1 -0
  24. package/dist/{PrettyCode.client-Y2BVON7R.mjs → PrettyCode.client-RS5ZTNBT.mjs} +11 -4
  25. package/dist/PrettyCode.client-RS5ZTNBT.mjs.map +1 -0
  26. package/dist/chunk-2DSR7V2L.mjs +561 -0
  27. package/dist/chunk-2DSR7V2L.mjs.map +1 -0
  28. package/dist/chunk-47T5ECYV.cjs +1357 -0
  29. package/dist/chunk-47T5ECYV.cjs.map +1 -0
  30. package/dist/chunk-5QT3QYFZ.cjs +189 -0
  31. package/dist/chunk-5QT3QYFZ.cjs.map +1 -0
  32. package/dist/chunk-7IIRYG4S.mjs +1057 -0
  33. package/dist/chunk-7IIRYG4S.mjs.map +1 -0
  34. package/dist/{chunk-FB5QBSI3.cjs → chunk-DI3HUXHK.cjs} +15 -195
  35. package/dist/chunk-DI3HUXHK.cjs.map +1 -0
  36. package/dist/chunk-EVGWYASL.cjs +1528 -0
  37. package/dist/chunk-EVGWYASL.cjs.map +1 -0
  38. package/dist/chunk-F2N7P5XU.cjs +30 -0
  39. package/dist/chunk-F2N7P5XU.cjs.map +1 -0
  40. package/dist/{chunk-L6UHASYQ.mjs → chunk-G6PRZP5I.mjs} +7 -186
  41. package/dist/chunk-G6PRZP5I.mjs.map +1 -0
  42. package/dist/chunk-JWB2EWQO.mjs +5 -0
  43. package/dist/chunk-JWB2EWQO.mjs.map +1 -0
  44. package/dist/chunk-LTJX2JXE.mjs +338 -0
  45. package/dist/chunk-LTJX2JXE.mjs.map +1 -0
  46. package/dist/chunk-OVNC4KW6.mjs +1494 -0
  47. package/dist/chunk-OVNC4KW6.mjs.map +1 -0
  48. package/dist/chunk-PNZSJN6T.cjs +1086 -0
  49. package/dist/chunk-PNZSJN6T.cjs.map +1 -0
  50. package/dist/chunk-TEFRA7GW.cjs +565 -0
  51. package/dist/chunk-TEFRA7GW.cjs.map +1 -0
  52. package/dist/chunk-UOMPPIED.mjs +1343 -0
  53. package/dist/chunk-UOMPPIED.mjs.map +1 -0
  54. package/dist/chunk-W6YHQI4F.mjs +187 -0
  55. package/dist/chunk-W6YHQI4F.mjs.map +1 -0
  56. package/dist/chunk-XTBRWVIV.cjs +346 -0
  57. package/dist/chunk-XTBRWVIV.cjs.map +1 -0
  58. package/dist/components-C7ZL7OMY.mjs +5 -0
  59. package/dist/components-C7ZL7OMY.mjs.map +1 -0
  60. package/dist/components-CJ2IB65O.cjs +27 -0
  61. package/dist/components-CJ2IB65O.cjs.map +1 -0
  62. package/dist/components-EASJYK45.mjs +6 -0
  63. package/dist/components-EASJYK45.mjs.map +1 -0
  64. package/dist/components-LDRFDV4A.cjs +22 -0
  65. package/dist/components-LDRFDV4A.cjs.map +1 -0
  66. package/dist/components-VZKUTDJK.mjs +5 -0
  67. package/dist/components-VZKUTDJK.mjs.map +1 -0
  68. package/dist/components-Y64GTIMQ.cjs +42 -0
  69. package/dist/components-Y64GTIMQ.cjs.map +1 -0
  70. package/dist/index.cjs +701 -4813
  71. package/dist/index.cjs.map +1 -1
  72. package/dist/index.d.cts +1274 -1026
  73. package/dist/index.d.ts +1274 -1026
  74. package/dist/index.mjs +358 -4730
  75. package/dist/index.mjs.map +1 -1
  76. package/package.json +27 -4
  77. package/src/components/index.ts +17 -0
  78. package/src/components/lazy-wrapper.tsx +281 -0
  79. package/src/index.ts +92 -7
  80. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +14 -5
  81. package/src/tools/AudioPlayer/lazy.tsx +85 -0
  82. package/src/tools/Gallery/components/Gallery.tsx +182 -0
  83. package/src/tools/Gallery/components/GalleryCarousel.tsx +251 -0
  84. package/src/tools/Gallery/components/GalleryCompact.tsx +173 -0
  85. package/src/tools/Gallery/components/GalleryGrid.tsx +493 -0
  86. package/src/tools/Gallery/components/GalleryImage.tsx +66 -0
  87. package/src/tools/Gallery/components/GalleryLightbox.tsx +331 -0
  88. package/src/tools/Gallery/components/GalleryMedia.tsx +66 -0
  89. package/src/tools/Gallery/components/GalleryThumbnails.tsx +173 -0
  90. package/src/tools/Gallery/components/GalleryThumbnailsVirtual.tsx +138 -0
  91. package/src/tools/Gallery/components/GalleryVideo.tsx +222 -0
  92. package/src/tools/Gallery/components/index.ts +13 -0
  93. package/src/tools/Gallery/hooks/index.ts +23 -0
  94. package/src/tools/Gallery/hooks/useGallery.ts +137 -0
  95. package/src/tools/Gallery/hooks/useImageDimensions.ts +223 -0
  96. package/src/tools/Gallery/hooks/usePinchZoom.ts +234 -0
  97. package/src/tools/Gallery/hooks/usePreloadImages.ts +71 -0
  98. package/src/tools/Gallery/hooks/useSwipe.ts +86 -0
  99. package/src/tools/Gallery/hooks/useVirtualList.ts +129 -0
  100. package/src/tools/Gallery/hooks/useZoom.ts +316 -0
  101. package/src/tools/Gallery/index.ts +66 -0
  102. package/src/tools/Gallery/types.ts +183 -0
  103. package/src/tools/Gallery/utils/imageAnalysis.ts +52 -0
  104. package/src/tools/Gallery/utils/index.ts +11 -0
  105. package/src/tools/ImageViewer/components/ImageToolbar.tsx +20 -8
  106. package/src/tools/ImageViewer/components/ImageViewer.tsx +12 -4
  107. package/src/tools/ImageViewer/lazy.tsx +37 -0
  108. package/src/tools/JsonForm/lazy.tsx +43 -0
  109. package/src/tools/JsonForm/widgets/ColorWidget.tsx +4 -1
  110. package/src/tools/JsonTree/lazy.tsx +45 -0
  111. package/src/tools/LottiePlayer/lazy.tsx +57 -0
  112. package/src/tools/Map/components/CustomOverlay.tsx +54 -0
  113. package/src/tools/Map/components/DrawControl.tsx +36 -0
  114. package/src/tools/Map/components/GeocoderControl.tsx +70 -0
  115. package/src/tools/Map/components/LayerSwitcher.tsx +225 -0
  116. package/src/tools/Map/components/MapCluster.tsx +273 -0
  117. package/src/tools/Map/components/MapContainer.tsx +191 -0
  118. package/src/tools/Map/components/MapControls.tsx +44 -0
  119. package/src/tools/Map/components/MapLegend.tsx +161 -0
  120. package/src/tools/Map/components/MapMarker.tsx +102 -0
  121. package/src/tools/Map/components/MapPopup.tsx +46 -0
  122. package/src/tools/Map/components/MapSource.tsx +30 -0
  123. package/src/tools/Map/components/index.ts +20 -0
  124. package/src/tools/Map/context/MapContext.tsx +89 -0
  125. package/src/tools/Map/context/index.ts +2 -0
  126. package/src/tools/Map/hooks/index.ts +9 -0
  127. package/src/tools/Map/hooks/useMap.ts +11 -0
  128. package/src/tools/Map/hooks/useMapControl.ts +99 -0
  129. package/src/tools/Map/hooks/useMapEvents.ts +147 -0
  130. package/src/tools/Map/hooks/useMapLayers.ts +83 -0
  131. package/src/tools/Map/hooks/useMapViewport.ts +62 -0
  132. package/src/tools/Map/hooks/useMarkers.ts +85 -0
  133. package/src/tools/Map/index.ts +116 -0
  134. package/src/tools/Map/layers/cluster.ts +94 -0
  135. package/src/tools/Map/layers/index.ts +15 -0
  136. package/src/tools/Map/layers/line.ts +93 -0
  137. package/src/tools/Map/layers/point.ts +61 -0
  138. package/src/tools/Map/layers/polygon.ts +73 -0
  139. package/src/tools/Map/lazy.tsx +56 -0
  140. package/src/tools/Map/styles/index.ts +15 -0
  141. package/src/tools/Map/types.ts +259 -0
  142. package/src/tools/Map/utils/geo.ts +88 -0
  143. package/src/tools/Map/utils/index.ts +16 -0
  144. package/src/tools/Map/utils/transform.ts +107 -0
  145. package/src/tools/Mermaid/Mermaid.client.tsx +12 -4
  146. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +6 -2
  147. package/src/tools/Mermaid/lazy.tsx +46 -0
  148. package/src/tools/OpenapiViewer/lazy.tsx +72 -0
  149. package/src/tools/PrettyCode/PrettyCode.client.tsx +10 -3
  150. package/src/tools/PrettyCode/lazy.tsx +64 -0
  151. package/src/tools/VideoPlayer/lazy.tsx +63 -0
  152. package/dist/Mermaid.client-4OCKJ6QD.mjs.map +0 -1
  153. package/dist/Mermaid.client-ZP6OE46Z.cjs.map +0 -1
  154. package/dist/PlaygroundLayout-LMQTVXSP.mjs.map +0 -1
  155. package/dist/PlaygroundLayout-XXVBU4WZ.cjs.map +0 -1
  156. package/dist/PrettyCode.client-2CLSV2VD.cjs.map +0 -1
  157. package/dist/PrettyCode.client-Y2BVON7R.mjs.map +0 -1
  158. package/dist/chunk-FB5QBSI3.cjs.map +0 -1
  159. package/dist/chunk-L6UHASYQ.mjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.110",
3
+ "version": "2.1.111",
4
4
  "description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
5
5
  "keywords": [
6
6
  "ui-tools",
@@ -10,7 +10,11 @@
10
10
  "audio-player",
11
11
  "json-form",
12
12
  "mermaid",
13
- "code-highlight"
13
+ "code-highlight",
14
+ "gallery",
15
+ "lightbox",
16
+ "map",
17
+ "maplibre"
14
18
  ],
15
19
  "author": {
16
20
  "name": "DjangoCFG",
@@ -32,6 +36,16 @@
32
36
  "types": "./dist/index.d.ts",
33
37
  "import": "./dist/index.mjs",
34
38
  "require": "./dist/index.cjs"
39
+ },
40
+ "./gallery": {
41
+ "types": "./src/tools/Gallery/index.ts",
42
+ "import": "./src/tools/Gallery/index.ts",
43
+ "require": "./src/tools/Gallery/index.ts"
44
+ },
45
+ "./map": {
46
+ "types": "./src/tools/Map/index.ts",
47
+ "import": "./src/tools/Map/index.ts",
48
+ "require": "./src/tools/Map/index.ts"
35
49
  }
36
50
  },
37
51
  "files": [
@@ -47,7 +61,8 @@
47
61
  "check": "tsc --noEmit"
48
62
  },
49
63
  "peerDependencies": {
50
- "@djangocfg/ui-core": "^2.1.110",
64
+ "@djangocfg/i18n": "^2.1.111",
65
+ "@djangocfg/ui-core": "^2.1.111",
51
66
  "lucide-react": "^0.545.0",
52
67
  "react": "^19.0.0",
53
68
  "react-dom": "^19.0.0",
@@ -61,19 +76,27 @@
61
76
  "@rjsf/validator-ajv8": "^6.1.2",
62
77
  "@vidstack/react": "next",
63
78
  "@wavesurfer/react": "^1.0.12",
79
+ "maplibre-gl": "^4.7.1",
64
80
  "media-icons": "next",
65
81
  "mermaid": "^11.12.0",
66
82
  "prism-react-renderer": "^2.4.1",
67
83
  "react-json-tree": "^0.20.0",
68
84
  "react-lottie-player": "^2.1.0",
85
+ "react-map-gl": "^8.1.0",
69
86
  "react-markdown": "10.1.0",
70
87
  "react-zoom-pan-pinch": "^3.7.0",
71
88
  "remark-gfm": "4.0.1",
72
89
  "vidstack": "next",
73
90
  "wavesurfer.js": "^7.12.1"
74
91
  },
92
+ "optionalDependencies": {
93
+ "@mapbox/mapbox-gl-draw": "^1.4.3",
94
+ "@maplibre/maplibre-gl-geocoder": "^1.7.0"
95
+ },
75
96
  "devDependencies": {
76
- "@djangocfg/typescript-config": "^2.1.110",
97
+ "@djangocfg/i18n": "^2.1.111",
98
+ "@djangocfg/typescript-config": "^2.1.111",
99
+ "@types/mapbox__mapbox-gl-draw": "^1.4.8",
77
100
  "@types/node": "^24.7.2",
78
101
  "@types/react": "^19.1.0",
79
102
  "@types/react-dom": "^19.1.0",
@@ -0,0 +1,17 @@
1
+ // Lazy loading utilities
2
+ export {
3
+ LazyWrapper,
4
+ LoadingFallback,
5
+ CardLoadingFallback,
6
+ MapLoadingFallback,
7
+ Spinner,
8
+ createLazyComponent,
9
+ } from './lazy-wrapper';
10
+ export type {
11
+ LazyWrapperProps,
12
+ LoadingFallbackProps,
13
+ CreateLazyComponentOptions,
14
+ } from './lazy-wrapper';
15
+
16
+ // Markdown
17
+ export * from './markdown';
@@ -0,0 +1,281 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Suspense, type ReactNode, type ComponentType } from 'react';
5
+ import { cn } from '@djangocfg/ui-core/lib';
6
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
7
+
8
+ // ============================================================================
9
+ // Loading Fallback Components
10
+ // ============================================================================
11
+
12
+ export interface LoadingFallbackProps {
13
+ /** Minimum height of the loading container */
14
+ minHeight?: string | number;
15
+ /** Show loading text */
16
+ showText?: boolean;
17
+ /** Custom loading text */
18
+ text?: string;
19
+ /** Additional CSS classes */
20
+ className?: string;
21
+ }
22
+
23
+ /**
24
+ * Spinner - Simple spinning loader
25
+ */
26
+ export function Spinner({ className }: { className?: string }) {
27
+ const t = useTypedT<I18nTranslations>();
28
+ const loadingLabel = t('ui.states.loading');
29
+
30
+ return (
31
+ <div
32
+ className={cn(
33
+ 'inline-block h-8 w-8 animate-spin rounded-full',
34
+ 'border-4 border-solid border-current border-r-transparent',
35
+ 'align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]',
36
+ className
37
+ )}
38
+ role="status"
39
+ aria-label={loadingLabel}
40
+ />
41
+ );
42
+ }
43
+
44
+ /**
45
+ * LoadingFallback - Default loading state for lazy components
46
+ */
47
+ export function LoadingFallback({
48
+ minHeight = 200,
49
+ showText = true,
50
+ text,
51
+ className,
52
+ }: LoadingFallbackProps) {
53
+ const t = useTypedT<I18nTranslations>();
54
+ const loadingText = text ?? t('ui.form.loading');
55
+ const height = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
56
+
57
+ return (
58
+ <div
59
+ className={cn(
60
+ 'flex items-center justify-center bg-muted/30 rounded-lg',
61
+ className
62
+ )}
63
+ style={{ minHeight: height }}
64
+ >
65
+ <div className="text-center">
66
+ <Spinner className="text-primary" />
67
+ {showText && (
68
+ <p className="mt-3 text-sm text-muted-foreground">{loadingText}</p>
69
+ )}
70
+ </div>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ /**
76
+ * CardLoadingFallback - Loading state styled as a card
77
+ */
78
+ export function CardLoadingFallback({
79
+ title,
80
+ description,
81
+ minHeight = 200,
82
+ className,
83
+ }: {
84
+ title?: string;
85
+ description?: string;
86
+ minHeight?: string | number;
87
+ className?: string;
88
+ }) {
89
+ const t = useTypedT<I18nTranslations>();
90
+ const cardTitle = title ?? t('ui.states.loading');
91
+ const height = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
92
+
93
+ return (
94
+ <div
95
+ className={cn(
96
+ 'relative bg-card rounded-lg border border-border overflow-hidden',
97
+ className
98
+ )}
99
+ >
100
+ <div className="p-4 border-b border-border bg-muted/50">
101
+ <h6 className="text-sm font-semibold text-foreground">{cardTitle}</h6>
102
+ {description && (
103
+ <p className="text-xs text-muted-foreground mt-1">{description}</p>
104
+ )}
105
+ </div>
106
+ <div className="p-4">
107
+ <div
108
+ className="flex justify-center items-center"
109
+ style={{ minHeight: height }}
110
+ >
111
+ <Spinner className="text-primary" />
112
+ </div>
113
+ </div>
114
+ </div>
115
+ );
116
+ }
117
+
118
+ /**
119
+ * MapLoadingFallback - Loading state for map components
120
+ */
121
+ export function MapLoadingFallback({
122
+ minHeight = 400,
123
+ className,
124
+ }: {
125
+ minHeight?: string | number;
126
+ className?: string;
127
+ }) {
128
+ const t = useTypedT<I18nTranslations>();
129
+ const loadingText = t('ui.form.loading');
130
+ const height = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
131
+
132
+ return (
133
+ <div
134
+ className={cn(
135
+ 'relative bg-muted/50 rounded-lg overflow-hidden',
136
+ 'flex items-center justify-center',
137
+ className
138
+ )}
139
+ style={{ minHeight: height }}
140
+ >
141
+ <div className="text-center">
142
+ <div className="relative">
143
+ <Spinner className="text-primary h-10 w-10" />
144
+ <div className="absolute inset-0 flex items-center justify-center">
145
+ <svg
146
+ className="h-5 w-5 text-muted-foreground"
147
+ fill="none"
148
+ viewBox="0 0 24 24"
149
+ stroke="currentColor"
150
+ >
151
+ <path
152
+ strokeLinecap="round"
153
+ strokeLinejoin="round"
154
+ strokeWidth={2}
155
+ d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
156
+ />
157
+ <path
158
+ strokeLinecap="round"
159
+ strokeLinejoin="round"
160
+ strokeWidth={2}
161
+ d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
162
+ />
163
+ </svg>
164
+ </div>
165
+ </div>
166
+ <p className="mt-3 text-sm text-muted-foreground">{loadingText}</p>
167
+ </div>
168
+ </div>
169
+ );
170
+ }
171
+
172
+ // ============================================================================
173
+ // Lazy Wrapper
174
+ // ============================================================================
175
+
176
+ export interface LazyWrapperProps {
177
+ children: ReactNode;
178
+ /** Custom fallback component */
179
+ fallback?: ReactNode;
180
+ /** Use card-style fallback */
181
+ card?: boolean;
182
+ /** Card title (when card=true) */
183
+ cardTitle?: string;
184
+ /** Card description (when card=true) */
185
+ cardDescription?: string;
186
+ /** Minimum height for fallback */
187
+ minHeight?: string | number;
188
+ /** Additional CSS classes for fallback */
189
+ className?: string;
190
+ }
191
+
192
+ /**
193
+ * LazyWrapper - Suspense wrapper with configurable fallbacks
194
+ *
195
+ * @example
196
+ * // Basic usage
197
+ * <LazyWrapper>
198
+ * <LazyComponent />
199
+ * </LazyWrapper>
200
+ *
201
+ * @example
202
+ * // Card style fallback
203
+ * <LazyWrapper card cardTitle="Diagram" minHeight={300}>
204
+ * <Mermaid chart={...} />
205
+ * </LazyWrapper>
206
+ *
207
+ * @example
208
+ * // Custom fallback
209
+ * <LazyWrapper fallback={<MyCustomLoader />}>
210
+ * <HeavyComponent />
211
+ * </LazyWrapper>
212
+ */
213
+ export function LazyWrapper({
214
+ children,
215
+ fallback,
216
+ card = false,
217
+ cardTitle,
218
+ cardDescription,
219
+ minHeight = 200,
220
+ className,
221
+ }: LazyWrapperProps) {
222
+ const defaultFallback = card ? (
223
+ <CardLoadingFallback
224
+ title={cardTitle}
225
+ description={cardDescription}
226
+ minHeight={minHeight}
227
+ className={className}
228
+ />
229
+ ) : (
230
+ <LoadingFallback minHeight={minHeight} className={className} />
231
+ );
232
+
233
+ return <Suspense fallback={fallback ?? defaultFallback}>{children}</Suspense>;
234
+ }
235
+
236
+ // ============================================================================
237
+ // Lazy Component Factory
238
+ // ============================================================================
239
+
240
+ export interface CreateLazyComponentOptions<P> {
241
+ /** Loading fallback component */
242
+ fallback?: ReactNode | ((props: P) => ReactNode);
243
+ /** Display name for the component */
244
+ displayName?: string;
245
+ }
246
+
247
+ /**
248
+ * createLazyComponent - Factory for creating lazy-loaded components
249
+ *
250
+ * @example
251
+ * const LazyMermaid = createLazyComponent(
252
+ * () => import('./Mermaid.client'),
253
+ * {
254
+ * displayName: 'Mermaid',
255
+ * fallback: <CardLoadingFallback title="Diagram" />,
256
+ * }
257
+ * );
258
+ */
259
+ export function createLazyComponent<P extends object>(
260
+ loader: () => Promise<{ default: ComponentType<P> }>,
261
+ options: CreateLazyComponentOptions<P> = {}
262
+ ): ComponentType<P> {
263
+ const LazyComponent = React.lazy(loader);
264
+
265
+ const WrappedComponent = (props: P) => {
266
+ const fallback =
267
+ typeof options.fallback === 'function'
268
+ ? options.fallback(props)
269
+ : options.fallback ?? <LoadingFallback />;
270
+
271
+ return (
272
+ <Suspense fallback={fallback}>
273
+ <LazyComponent {...props} />
274
+ </Suspense>
275
+ );
276
+ };
277
+
278
+ WrappedComponent.displayName = options.displayName ?? 'LazyComponent';
279
+
280
+ return WrappedComponent;
281
+ }
package/src/index.ts CHANGED
@@ -5,24 +5,109 @@
5
5
  * Works in any React environment: Next.js, Vite, Wails, CRA
6
6
  *
7
7
  * Bundle sizes:
8
+ * - Map: ~800KB (MapLibre GL maps)
8
9
  * - Mermaid: ~800KB (diagram rendering)
9
10
  * - PrettyCode: ~500KB (code syntax highlighting)
10
- * - JsonTree: ~100KB (JSON visualization)
11
- * - LottiePlayer: ~200KB (Lottie animation player)
12
- * - JsonForm: ~300KB (JSON Schema form generator)
13
11
  * - OpenapiViewer: ~400KB (OpenAPI schema viewer & playground)
14
- * - VideoPlayer: ~150KB (Professional video player with Vidstack)
12
+ * - JsonForm: ~300KB (JSON Schema form generator)
13
+ * - LottiePlayer: ~200KB (Lottie animation player)
15
14
  * - AudioPlayer: ~200KB (Audio player with WaveSurfer.js)
15
+ * - VideoPlayer: ~150KB (Professional video player with Vidstack)
16
+ * - JsonTree: ~100KB (JSON visualization)
17
+ * - Gallery: ~50KB (Image/video gallery)
16
18
  * - ImageViewer: ~50KB (Image viewer with zoom/pan/rotate)
19
+ *
20
+ * For tree-shaking, use subpath imports:
21
+ * - import { Gallery } from '@djangocfg/ui-tools/gallery'
22
+ * - import { MapContainer } from '@djangocfg/ui-tools/map'
17
23
  */
18
24
 
19
25
  'use client';
20
26
 
21
- // Re-export everything from tools
27
+ // ============================================================================
28
+ // Lazy Loading Utilities
29
+ // ============================================================================
30
+
31
+ export {
32
+ // Components
33
+ LazyWrapper,
34
+ LoadingFallback,
35
+ CardLoadingFallback,
36
+ MapLoadingFallback,
37
+ Spinner,
38
+ // Factory
39
+ createLazyComponent,
40
+ } from './components';
41
+ export type {
42
+ LazyWrapperProps,
43
+ LoadingFallbackProps,
44
+ CreateLazyComponentOptions,
45
+ } from './components';
46
+
47
+ // ============================================================================
48
+ // Lazy Components (heavy tools, loaded on demand)
49
+ // ============================================================================
50
+
51
+ // Map (~800KB)
52
+ export {
53
+ LazyMapContainer,
54
+ LazyMapView,
55
+ } from './tools/Map/lazy';
56
+ export type {
57
+ MapContainerProps,
58
+ MapViewport,
59
+ MapStyleKey,
60
+ MarkerData,
61
+ } from './tools/Map/lazy';
62
+
63
+ // Mermaid (~800KB)
64
+ export { LazyMermaid } from './tools/Mermaid/lazy';
65
+ export type { MermaidProps } from './tools/Mermaid/lazy';
66
+
67
+ // PrettyCode (~500KB)
68
+ export { LazyPrettyCode } from './tools/PrettyCode/lazy';
69
+ export type { PrettyCodeProps } from './tools/PrettyCode/lazy';
70
+
71
+ // OpenapiViewer (~400KB)
72
+ export { LazyOpenapiViewer } from './tools/OpenapiViewer/lazy';
73
+ export type { PlaygroundConfig, SchemaSource, PlaygroundProps } from './tools/OpenapiViewer/lazy';
74
+
75
+ // JsonForm (~300KB)
76
+ export { LazyJsonSchemaForm } from './tools/JsonForm/lazy';
77
+
78
+ // LottiePlayer (~200KB)
79
+ export { LazyLottiePlayer } from './tools/LottiePlayer/lazy';
80
+
81
+ // AudioPlayer (~200KB)
82
+ export {
83
+ LazyHybridAudioPlayer,
84
+ LazyHybridSimplePlayer,
85
+ } from './tools/AudioPlayer/lazy';
86
+
87
+ // VideoPlayer (~150KB)
88
+ export { LazyVideoPlayer } from './tools/VideoPlayer/lazy';
89
+
90
+ // JsonTree (~100KB)
91
+ export { LazyJsonTree } from './tools/JsonTree/lazy';
92
+ export type { JsonTreeProps } from './tools/JsonTree/lazy';
93
+
94
+ // ImageViewer (~50KB)
95
+ export { LazyImageViewer } from './tools/ImageViewer/lazy';
96
+
97
+ // ============================================================================
98
+ // Tools (from ./tools/index.ts) - Direct imports (no lazy loading)
99
+ // ============================================================================
100
+
22
101
  export * from './tools';
23
102
 
24
- // Re-export components (markdown, etc.)
103
+ // ============================================================================
104
+ // Components (Markdown, etc.)
105
+ // ============================================================================
106
+
25
107
  export * from './components/markdown';
26
108
 
27
- // Re-export stores
109
+ // ============================================================================
110
+ // Stores
111
+ // ============================================================================
112
+
28
113
  export * from './stores/mediaCache';
@@ -15,7 +15,7 @@
15
15
  * - Timer display
16
16
  */
17
17
 
18
- import { memo } from 'react';
18
+ import { memo, useMemo } from 'react';
19
19
  import {
20
20
  Play,
21
21
  Pause,
@@ -27,6 +27,7 @@ import {
27
27
  Loader2,
28
28
  Repeat,
29
29
  } from 'lucide-react';
30
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
30
31
  import { Button, Slider, cn } from '../../_shared';
31
32
  import { useHybridAudioContext } from '../context/HybridAudioProvider';
32
33
  import { HybridWaveform } from './HybridWaveform';
@@ -72,8 +73,16 @@ export const HybridAudioPlayer = memo(function HybridAudioPlayer({
72
73
  className,
73
74
  style,
74
75
  }: HybridAudioPlayerProps) {
76
+ const t = useTypedT<I18nTranslations>();
75
77
  const { state, controls } = useHybridAudioContext();
76
78
 
79
+ const labels = useMemo(() => ({
80
+ restart: t('tools.audio.restart'),
81
+ back: t('tools.audio.back'),
82
+ forward: t('tools.audio.forward'),
83
+ volume: t('tools.audio.volume'),
84
+ }), [t]);
85
+
77
86
  const isLoading = !state.isReady;
78
87
 
79
88
  const handleVolumeChange = (value: number[]) => {
@@ -119,7 +128,7 @@ export const HybridAudioPlayer = memo(function HybridAudioPlayer({
119
128
  className="h-9 w-9"
120
129
  onClick={controls.restart}
121
130
  disabled={!state.isReady}
122
- title="Restart"
131
+ title={labels.restart}
123
132
  >
124
133
  <RotateCcw className="h-4 w-4" />
125
134
  </Button>
@@ -131,7 +140,7 @@ export const HybridAudioPlayer = memo(function HybridAudioPlayer({
131
140
  className="h-9 w-9"
132
141
  onClick={() => controls.skip(-5)}
133
142
  disabled={!state.isReady}
134
- title="Back 5 seconds"
143
+ title={labels.back}
135
144
  >
136
145
  <SkipBack className="h-4 w-4" />
137
146
  </Button>
@@ -161,7 +170,7 @@ export const HybridAudioPlayer = memo(function HybridAudioPlayer({
161
170
  className="h-9 w-9"
162
171
  onClick={() => controls.skip(5)}
163
172
  disabled={!state.isReady}
164
- title="Forward 5 seconds"
173
+ title={labels.forward}
165
174
  >
166
175
  <SkipForward className="h-4 w-4" />
167
176
  </Button>
@@ -189,7 +198,7 @@ export const HybridAudioPlayer = memo(function HybridAudioPlayer({
189
198
  step={1}
190
199
  onValueChange={handleVolumeChange}
191
200
  className="w-20"
192
- aria-label="Volume"
201
+ aria-label={labels.volume}
193
202
  />
194
203
  </>
195
204
  )}
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Lazy-loaded AudioPlayer Components
5
+ *
6
+ * Heavy WaveSurfer.js (~200KB) is loaded only when component is rendered.
7
+ * Use this for automatic code-splitting with Suspense fallback.
8
+ *
9
+ * For direct imports without lazy loading, use:
10
+ * import { HybridAudioPlayer } from '@djangocfg/ui-tools/audio'
11
+ */
12
+
13
+ import { createLazyComponent, LoadingFallback } from '../../components';
14
+ import type {
15
+ HybridAudioPlayerProps,
16
+ HybridSimplePlayerProps,
17
+ } from './components';
18
+
19
+ // ============================================================================
20
+ // Re-export types
21
+ // ============================================================================
22
+
23
+ export type { HybridAudioPlayerProps, HybridSimplePlayerProps };
24
+
25
+ // ============================================================================
26
+ // Audio Loading Fallback
27
+ // ============================================================================
28
+
29
+ function AudioLoadingFallback() {
30
+ return (
31
+ <div className="flex items-center justify-center p-6 bg-muted/30 rounded-lg">
32
+ <div className="flex flex-col items-center gap-2">
33
+ <div className="relative">
34
+ <div className="h-10 w-10 animate-spin rounded-full border-4 border-muted border-t-primary" />
35
+ <div className="absolute inset-0 flex items-center justify-center">
36
+ <svg
37
+ className="h-5 w-5 text-muted-foreground"
38
+ fill="none"
39
+ viewBox="0 0 24 24"
40
+ stroke="currentColor"
41
+ >
42
+ <path
43
+ strokeLinecap="round"
44
+ strokeLinejoin="round"
45
+ strokeWidth={2}
46
+ d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"
47
+ />
48
+ </svg>
49
+ </div>
50
+ </div>
51
+ <span className="text-sm text-muted-foreground">Loading audio player...</span>
52
+ </div>
53
+ </div>
54
+ );
55
+ }
56
+
57
+ // ============================================================================
58
+ // Lazy Components
59
+ // ============================================================================
60
+
61
+ /**
62
+ * LazyHybridAudioPlayer - Lazy-loaded full-featured audio player
63
+ *
64
+ * Automatically shows loading state while WaveSurfer loads (~200KB)
65
+ */
66
+ export const LazyHybridAudioPlayer = createLazyComponent<HybridAudioPlayerProps>(
67
+ () => import('./components').then((mod) => ({ default: mod.HybridAudioPlayer })),
68
+ {
69
+ displayName: 'LazyHybridAudioPlayer',
70
+ fallback: <AudioLoadingFallback />,
71
+ }
72
+ );
73
+
74
+ /**
75
+ * LazyHybridSimplePlayer - Lazy-loaded simple audio player
76
+ *
77
+ * Automatically shows loading state while WaveSurfer loads (~200KB)
78
+ */
79
+ export const LazyHybridSimplePlayer = createLazyComponent<HybridSimplePlayerProps>(
80
+ () => import('./components').then((mod) => ({ default: mod.HybridSimplePlayer })),
81
+ {
82
+ displayName: 'LazyHybridSimplePlayer',
83
+ fallback: <AudioLoadingFallback />,
84
+ }
85
+ );