@djangocfg/ui-tools 2.1.110 → 2.1.112

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
@@ -4,10 +4,11 @@
4
4
  * ImageToolbar - Floating toolbar for image controls
5
5
  */
6
6
 
7
- import { useMemo, useCallback } from 'react';
7
+ import { useMemo } from 'react';
8
8
  import { ZoomIn, ZoomOut, RotateCw, FlipHorizontal, FlipVertical, Maximize2, Expand } from 'lucide-react';
9
9
  import { useControls } from 'react-zoom-pan-pinch';
10
10
  import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@djangocfg/ui-core/components';
11
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
11
12
  import { cn } from '@djangocfg/ui-core/lib';
12
13
  import { ZOOM_PRESETS } from '../utils';
13
14
  import type { ImageToolbarProps } from '../types';
@@ -25,8 +26,19 @@ export function ImageToolbar({
25
26
  onZoomPreset,
26
27
  onExpand,
27
28
  }: ImageToolbarProps) {
29
+ const t = useTypedT<I18nTranslations>();
28
30
  const { zoomIn, zoomOut, resetTransform } = useControls();
29
31
 
32
+ const labels = useMemo(() => ({
33
+ zoomIn: t('tools.image.zoomIn'),
34
+ zoomOut: t('tools.image.zoomOut'),
35
+ fitToView: t('tools.image.fitToView'),
36
+ flipHorizontal: t('tools.image.flipHorizontal'),
37
+ flipVertical: t('tools.image.flipVertical'),
38
+ rotate: t('tools.image.rotate'),
39
+ fullscreen: t('tools.image.fullscreen'),
40
+ }), [t]);
41
+
30
42
  const zoomLabel = useMemo(() => {
31
43
  const percent = Math.round(scale * 100);
32
44
  return `${percent}%`;
@@ -40,7 +52,7 @@ export function ImageToolbar({
40
52
  size="icon"
41
53
  className="h-7 w-7"
42
54
  onClick={() => zoomOut()}
43
- title="Zoom out"
55
+ title={labels.zoomOut}
44
56
  >
45
57
  <ZoomOut className="h-3.5 w-3.5" />
46
58
  </Button>
@@ -69,7 +81,7 @@ export function ImageToolbar({
69
81
  size="icon"
70
82
  className="h-7 w-7"
71
83
  onClick={() => zoomIn()}
72
- title="Zoom in"
84
+ title={labels.zoomIn}
73
85
  >
74
86
  <ZoomIn className="h-3.5 w-3.5" />
75
87
  </Button>
@@ -82,7 +94,7 @@ export function ImageToolbar({
82
94
  size="icon"
83
95
  className="h-7 w-7"
84
96
  onClick={() => resetTransform()}
85
- title="Fit to view"
97
+ title={labels.fitToView}
86
98
  >
87
99
  <Maximize2 className="h-3.5 w-3.5" />
88
100
  </Button>
@@ -95,7 +107,7 @@ export function ImageToolbar({
95
107
  size="icon"
96
108
  className={cn('h-7 w-7', transform.flipH && 'bg-accent')}
97
109
  onClick={onFlipH}
98
- title="Flip horizontal"
110
+ title={labels.flipHorizontal}
99
111
  >
100
112
  <FlipHorizontal className="h-3.5 w-3.5" />
101
113
  </Button>
@@ -105,7 +117,7 @@ export function ImageToolbar({
105
117
  size="icon"
106
118
  className={cn('h-7 w-7', transform.flipV && 'bg-accent')}
107
119
  onClick={onFlipV}
108
- title="Flip vertical"
120
+ title={labels.flipVertical}
109
121
  >
110
122
  <FlipVertical className="h-3.5 w-3.5" />
111
123
  </Button>
@@ -115,7 +127,7 @@ export function ImageToolbar({
115
127
  size="icon"
116
128
  className="h-7 w-7"
117
129
  onClick={onRotate}
118
- title="Rotate 90°"
130
+ title={labels.rotate}
119
131
  >
120
132
  <RotateCw className="h-3.5 w-3.5" />
121
133
  </Button>
@@ -134,7 +146,7 @@ export function ImageToolbar({
134
146
  size="icon"
135
147
  className="h-7 w-7"
136
148
  onClick={onExpand}
137
- title="Open in fullscreen"
149
+ title={labels.fullscreen}
138
150
  >
139
151
  <Expand className="h-3.5 w-3.5" />
140
152
  </Button>
@@ -12,10 +12,11 @@
12
12
  * - Keyboard shortcuts (+/-, 0, r)
13
13
  */
14
14
 
15
- import { useEffect, useState, useRef, useCallback } from 'react';
15
+ import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
16
16
  import { ImageIcon, AlertCircle } from 'lucide-react';
17
17
  import { TransformWrapper, TransformComponent, useControls } from 'react-zoom-pan-pinch';
18
18
  import { cn, Dialog, DialogContent, DialogTitle, Alert, AlertDescription } from '@djangocfg/ui-core';
19
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
19
20
 
20
21
  import { ImageToolbar } from './ImageToolbar';
21
22
  import { ImageInfo } from './ImageInfo';
@@ -27,12 +28,19 @@ import type { ImageViewerProps } from '../types';
27
28
  // =============================================================================
28
29
 
29
30
  export function ImageViewer({ file, content, src: directSrc, inDialog = false }: ImageViewerProps) {
31
+ const t = useTypedT<I18nTranslations>();
30
32
  const [scale, setScale] = useState(1);
31
33
  const [dialogOpen, setDialogOpen] = useState(false);
32
34
  const [loadError, setLoadError] = useState(false);
33
35
  const containerRef = useRef<HTMLDivElement>(null);
34
36
  const controlsRef = useRef<ReturnType<typeof useControls> | null>(null);
35
37
 
38
+ const labels = useMemo(() => ({
39
+ noImage: t('tools.image.noImage'),
40
+ failedToLoad: t('tools.image.failedToLoad'),
41
+ loading: t('ui.form.loading'),
42
+ }), [t]);
43
+
36
44
  // Loading state
37
45
  const {
38
46
  src,
@@ -113,7 +121,7 @@ export function ImageViewer({ file, content, src: directSrc, inDialog = false }:
113
121
  <AlertCircle className="w-12 h-12 text-destructive/70" />
114
122
  <Alert variant="destructive" className="max-w-md">
115
123
  <AlertCircle className="h-4 w-4" />
116
- <AlertDescription>{error || 'Failed to load image'}</AlertDescription>
124
+ <AlertDescription>{error || labels.failedToLoad}</AlertDescription>
117
125
  </Alert>
118
126
  </div>
119
127
  );
@@ -124,7 +132,7 @@ export function ImageViewer({ file, content, src: directSrc, inDialog = false }:
124
132
  return (
125
133
  <div className="flex-1 flex flex-col items-center justify-center gap-2 bg-muted/30">
126
134
  <ImageIcon className="w-12 h-12 text-muted-foreground/50" />
127
- <p className="text-sm text-muted-foreground">No image content</p>
135
+ <p className="text-sm text-muted-foreground">{labels.noImage}</p>
128
136
  </div>
129
137
  );
130
138
  }
@@ -147,7 +155,7 @@ export function ImageViewer({ file, content, src: directSrc, inDialog = false }:
147
155
  {useProgressiveLoading && !isFullyLoaded && (
148
156
  <div className="absolute top-3 left-3 z-10 px-2 py-1 bg-background/80 backdrop-blur-sm border rounded text-[10px] text-muted-foreground font-mono flex items-center gap-1.5">
149
157
  <div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
150
- Loading...
158
+ {labels.loading}
151
159
  </div>
152
160
  )}
153
161
 
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Lazy-loaded ImageViewer Component
5
+ *
6
+ * ImageViewer (~50KB) 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 { ImageViewer } from '@djangocfg/ui-tools/image-viewer'
11
+ */
12
+
13
+ import { createLazyComponent, LoadingFallback } from '../../components';
14
+ import type { ImageViewerProps } from './types';
15
+
16
+ // ============================================================================
17
+ // Re-export types
18
+ // ============================================================================
19
+
20
+ export type { ImageViewerProps };
21
+
22
+ // ============================================================================
23
+ // Lazy Component
24
+ // ============================================================================
25
+
26
+ /**
27
+ * LazyImageViewer - Lazy-loaded image viewer with zoom/pan/rotate
28
+ *
29
+ * Automatically shows loading state while ImageViewer loads (~50KB)
30
+ */
31
+ export const LazyImageViewer = createLazyComponent<ImageViewerProps>(
32
+ () => import('./components').then((mod) => ({ default: mod.ImageViewer })),
33
+ {
34
+ displayName: 'LazyImageViewer',
35
+ fallback: <LoadingFallback minHeight={200} text="Loading image viewer..." />,
36
+ }
37
+ );
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Lazy-loaded JsonForm Component
5
+ *
6
+ * Heavy JSON Schema Form (~300KB) 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 { JsonSchemaForm } from '@djangocfg/ui-tools/json-form'
11
+ */
12
+
13
+ import { createLazyComponent, CardLoadingFallback } from '../../components';
14
+ import type { JsonSchemaFormProps } from './types';
15
+
16
+ // ============================================================================
17
+ // Re-export types
18
+ // ============================================================================
19
+
20
+ export type { JsonSchemaFormProps };
21
+
22
+ // ============================================================================
23
+ // Lazy Component
24
+ // ============================================================================
25
+
26
+ /**
27
+ * LazyJsonSchemaForm - Lazy-loaded JSON Schema form generator
28
+ *
29
+ * Automatically shows loading state while RJSF loads (~300KB)
30
+ */
31
+ export const LazyJsonSchemaForm = createLazyComponent<JsonSchemaFormProps>(
32
+ () => import('./JsonSchemaForm').then((mod) => ({ default: mod.JsonSchemaForm })),
33
+ {
34
+ displayName: 'LazyJsonSchemaForm',
35
+ fallback: (
36
+ <CardLoadingFallback
37
+ title="Form"
38
+ description="Loading form schema..."
39
+ minHeight={200}
40
+ />
41
+ ),
42
+ }
43
+ );
@@ -3,6 +3,7 @@
3
3
  import React, { useCallback, useMemo, useRef } from 'react';
4
4
 
5
5
  import { Input } from '@djangocfg/ui-core/components';
6
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
6
7
  import { WidgetProps } from '@rjsf/utils';
7
8
 
8
9
  /**
@@ -26,6 +27,8 @@ export function ColorWidget(props: WidgetProps) {
26
27
  rawErrors,
27
28
  } = props;
28
29
 
30
+ const t = useTypedT<I18nTranslations>();
31
+ const pickColorLabel = useMemo(() => t('tools.color.pick'), [t]);
29
32
  const colorInputRef = useRef<HTMLInputElement>(null);
30
33
 
31
34
  // Determine format from options or auto-detect from value
@@ -185,7 +188,7 @@ export function ColorWidget(props: WidgetProps) {
185
188
  disabled={disabled || readonly}
186
189
  className="h-10 w-10 shrink-0 rounded-md border-2 border-input cursor-pointer hover:border-ring focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
187
190
  style={{ backgroundColor: previewColor }}
188
- aria-label="Pick color"
191
+ aria-label={pickColorLabel}
189
192
  />
190
193
  {/* Hidden native color input */}
191
194
  <input
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Lazy-loaded JsonTree Component
5
+ *
6
+ * JsonTree (~100KB) 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 JsonTree from '@djangocfg/ui-tools/json-tree'
11
+ */
12
+
13
+ import { createLazyComponent, LoadingFallback } from '../../components';
14
+ import type { JsonTreeConfig } from './index';
15
+ import type { CommonExternalProps } from 'react-json-tree';
16
+
17
+ // ============================================================================
18
+ // Types
19
+ // ============================================================================
20
+
21
+ export interface JsonTreeProps {
22
+ title?: string;
23
+ data: unknown;
24
+ config?: JsonTreeConfig;
25
+ jsonTreeProps?: Partial<CommonExternalProps>;
26
+ }
27
+
28
+ export type { JsonTreeConfig };
29
+
30
+ // ============================================================================
31
+ // Lazy Component
32
+ // ============================================================================
33
+
34
+ /**
35
+ * LazyJsonTree - Lazy-loaded JSON visualization tree
36
+ *
37
+ * Automatically shows loading state while JsonTree loads (~100KB)
38
+ */
39
+ export const LazyJsonTree = createLazyComponent<JsonTreeProps>(
40
+ () => import('./index'),
41
+ {
42
+ displayName: 'LazyJsonTree',
43
+ fallback: <LoadingFallback minHeight={100} text="Loading JSON viewer..." />,
44
+ }
45
+ );
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Lazy-loaded LottiePlayer Component
5
+ *
6
+ * Lottie library (~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 { LottiePlayer } from '@djangocfg/ui-tools/lottie'
11
+ */
12
+
13
+ import { createLazyComponent, LoadingFallback } from '../../components';
14
+ import type { LottiePlayerProps } from './types';
15
+
16
+ // ============================================================================
17
+ // Re-export types
18
+ // ============================================================================
19
+
20
+ export type {
21
+ LottiePlayerProps,
22
+ LottieSize,
23
+ LottieSpeed,
24
+ LottieDirection,
25
+ } from './types';
26
+
27
+ // ============================================================================
28
+ // Lottie Loading Fallback
29
+ // ============================================================================
30
+
31
+ function LottieLoadingFallback() {
32
+ return (
33
+ <div className="flex items-center justify-center p-8">
34
+ <div className="flex flex-col items-center gap-2">
35
+ <div className="h-8 w-8 animate-spin rounded-full border-4 border-muted border-t-primary" />
36
+ <span className="text-sm text-muted-foreground">Loading animation...</span>
37
+ </div>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ // ============================================================================
43
+ // Lazy Component
44
+ // ============================================================================
45
+
46
+ /**
47
+ * LazyLottiePlayer - Lazy-loaded Lottie animation player
48
+ *
49
+ * Automatically shows loading state while Lottie loads (~200KB)
50
+ */
51
+ export const LazyLottiePlayer = createLazyComponent<LottiePlayerProps>(
52
+ () => import('./LottiePlayer.client').then((mod) => ({ default: mod.LottiePlayer })),
53
+ {
54
+ displayName: 'LazyLottiePlayer',
55
+ fallback: <LottieLoadingFallback />,
56
+ }
57
+ );
@@ -0,0 +1,54 @@
1
+ 'use client'
2
+
3
+ import { useState, memo } from 'react'
4
+ import { createPortal } from 'react-dom'
5
+ import { useControl } from 'react-map-gl/maplibre'
6
+
7
+ import type { IControl, MapInstance } from 'react-map-gl/maplibre'
8
+ import type { CustomOverlayProps } from '../types'
9
+
10
+ class OverlayControl implements IControl {
11
+ _map: MapInstance | null = null
12
+ _container: HTMLElement | null = null
13
+ _redraw: () => void
14
+
15
+ constructor(redraw: () => void) {
16
+ this._redraw = redraw
17
+ }
18
+
19
+ onAdd(map: MapInstance): HTMLElement {
20
+ this._map = map
21
+ map.on('move', this._redraw)
22
+ this._container = document.createElement('div')
23
+ this._redraw()
24
+ return this._container
25
+ }
26
+
27
+ onRemove(): void {
28
+ this._container?.remove()
29
+ this._map?.off('move', this._redraw)
30
+ this._map = null
31
+ }
32
+
33
+ getElement(): HTMLElement | null {
34
+ return this._container
35
+ }
36
+ }
37
+
38
+ function CustomOverlayComponent({ children }: CustomOverlayProps) {
39
+ const [, setVersion] = useState(0)
40
+
41
+ const ctrl = useControl<OverlayControl>(() => {
42
+ const forceUpdate = () => setVersion((v) => v + 1)
43
+ return new OverlayControl(forceUpdate)
44
+ })
45
+
46
+ const element = ctrl.getElement()
47
+
48
+ if (!element) return null
49
+
50
+ // Children can use useMapContext() to access the map if needed
51
+ return createPortal(children, element)
52
+ }
53
+
54
+ export const CustomOverlay = memo(CustomOverlayComponent)
@@ -0,0 +1,36 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * DrawControl - Drawing tools for polygon, line, point creation
5
+ *
6
+ * Requires: @mapbox/mapbox-gl-draw
7
+ * CSS: @import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * import MapboxDraw from '@mapbox/mapbox-gl-draw'
12
+ * import { useControl } from '@carapis/map'
13
+ *
14
+ * function DrawControl(props) {
15
+ * useControl(
16
+ * () => new MapboxDraw(props),
17
+ * ({ map }) => {
18
+ * map.on('draw.create', props.onCreate)
19
+ * map.on('draw.update', props.onUpdate)
20
+ * map.on('draw.delete', props.onDelete)
21
+ * },
22
+ * ({ map }) => {
23
+ * map.off('draw.create', props.onCreate)
24
+ * map.off('draw.update', props.onUpdate)
25
+ * map.off('draw.delete', props.onDelete)
26
+ * },
27
+ * { position: props.position }
28
+ * )
29
+ * return null
30
+ * }
31
+ * ```
32
+ */
33
+
34
+ // Re-export for documentation purposes
35
+ // Actual implementation requires user to install @mapbox/mapbox-gl-draw
36
+ export { }
@@ -0,0 +1,70 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * GeocoderControl - Address search with autocomplete
5
+ *
6
+ * Requires: @maplibre/maplibre-gl-geocoder
7
+ * CSS: @import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css'
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * import { useState } from 'react'
12
+ * import { useControl, Marker } from '@carapis/map'
13
+ * import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder'
14
+ *
15
+ * const geocoderApi = {
16
+ * forwardGeocode: async (config) => {
17
+ * const features = []
18
+ * const response = await fetch(
19
+ * `https://nominatim.openstreetmap.org/search?q=${config.query}&format=geojson`
20
+ * )
21
+ * const geojson = await response.json()
22
+ * for (const feature of geojson.features) {
23
+ * const center = [
24
+ * feature.bbox[0] + (feature.bbox[2] - feature.bbox[0]) / 2,
25
+ * feature.bbox[1] + (feature.bbox[3] - feature.bbox[1]) / 2,
26
+ * ]
27
+ * features.push({
28
+ * type: 'Feature',
29
+ * geometry: { type: 'Point', coordinates: center },
30
+ * place_name: feature.properties.display_name,
31
+ * properties: feature.properties,
32
+ * text: feature.properties.display_name,
33
+ * center,
34
+ * })
35
+ * }
36
+ * return { features }
37
+ * },
38
+ * }
39
+ *
40
+ * function GeocoderControl({ position, onResult }) {
41
+ * const [marker, setMarker] = useState(null)
42
+ *
43
+ * useControl(
44
+ * ({ mapLib }) => {
45
+ * const ctrl = new MaplibreGeocoder(geocoderApi, {
46
+ * maplibregl: mapLib,
47
+ * marker: false,
48
+ * })
49
+ * ctrl.on('result', (evt) => {
50
+ * onResult?.(evt)
51
+ * const { result } = evt
52
+ * if (result?.center) {
53
+ * setMarker(
54
+ * <Marker longitude={result.center[0]} latitude={result.center[1]} />
55
+ * )
56
+ * }
57
+ * })
58
+ * return ctrl
59
+ * },
60
+ * { position }
61
+ * )
62
+ *
63
+ * return marker
64
+ * }
65
+ * ```
66
+ */
67
+
68
+ // Re-export for documentation purposes
69
+ // Actual implementation requires user to install @maplibre/maplibre-gl-geocoder
70
+ export { }