@djangocfg/ui-tools 2.1.109 → 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.
- package/README.md +242 -49
- package/dist/JsonSchemaForm-65NLLK56.mjs +4 -0
- package/dist/JsonSchemaForm-65NLLK56.mjs.map +1 -0
- package/dist/JsonSchemaForm-PY6DH3HE.cjs +13 -0
- package/dist/JsonSchemaForm-PY6DH3HE.cjs.map +1 -0
- package/dist/JsonTree-6RYAOPSS.mjs +4 -0
- package/dist/JsonTree-6RYAOPSS.mjs.map +1 -0
- package/dist/JsonTree-7OH6CIHT.cjs +10 -0
- package/dist/JsonTree-7OH6CIHT.cjs.map +1 -0
- package/dist/MapContainer-GXQLP5WY.mjs +214 -0
- package/dist/MapContainer-GXQLP5WY.mjs.map +1 -0
- package/dist/MapContainer-RYG4HPH4.cjs +221 -0
- package/dist/MapContainer-RYG4HPH4.cjs.map +1 -0
- package/dist/{Mermaid.client-4OCKJ6QD.mjs → Mermaid.client-OKACITCW.mjs} +16 -7
- package/dist/Mermaid.client-OKACITCW.mjs.map +1 -0
- package/dist/{Mermaid.client-ZP6OE46Z.cjs → Mermaid.client-PNXEC6YL.cjs} +16 -7
- package/dist/Mermaid.client-PNXEC6YL.cjs.map +1 -0
- package/dist/{PlaygroundLayout-XXVBU4WZ.cjs → PlaygroundLayout-SYMEAG3J.cjs} +25 -24
- package/dist/PlaygroundLayout-SYMEAG3J.cjs.map +1 -0
- package/dist/{PlaygroundLayout-LMQTVXSP.mjs → PlaygroundLayout-UQRBU5RH.mjs} +4 -3
- package/dist/PlaygroundLayout-UQRBU5RH.mjs.map +1 -0
- package/dist/{PrettyCode.client-2CLSV2VD.cjs → PrettyCode.client-DANYYQYO.cjs} +11 -4
- package/dist/PrettyCode.client-DANYYQYO.cjs.map +1 -0
- package/dist/{PrettyCode.client-Y2BVON7R.mjs → PrettyCode.client-RS5ZTNBT.mjs} +11 -4
- package/dist/PrettyCode.client-RS5ZTNBT.mjs.map +1 -0
- package/dist/chunk-2DSR7V2L.mjs +561 -0
- package/dist/chunk-2DSR7V2L.mjs.map +1 -0
- package/dist/chunk-47T5ECYV.cjs +1357 -0
- package/dist/chunk-47T5ECYV.cjs.map +1 -0
- package/dist/chunk-5QT3QYFZ.cjs +189 -0
- package/dist/chunk-5QT3QYFZ.cjs.map +1 -0
- package/dist/chunk-7IIRYG4S.mjs +1057 -0
- package/dist/chunk-7IIRYG4S.mjs.map +1 -0
- package/dist/{chunk-FB5QBSI3.cjs → chunk-DI3HUXHK.cjs} +15 -195
- package/dist/chunk-DI3HUXHK.cjs.map +1 -0
- package/dist/chunk-EVGWYASL.cjs +1528 -0
- package/dist/chunk-EVGWYASL.cjs.map +1 -0
- package/dist/chunk-F2N7P5XU.cjs +30 -0
- package/dist/chunk-F2N7P5XU.cjs.map +1 -0
- package/dist/{chunk-L6UHASYQ.mjs → chunk-G6PRZP5I.mjs} +7 -186
- package/dist/chunk-G6PRZP5I.mjs.map +1 -0
- package/dist/chunk-JWB2EWQO.mjs +5 -0
- package/dist/chunk-JWB2EWQO.mjs.map +1 -0
- package/dist/chunk-LTJX2JXE.mjs +338 -0
- package/dist/chunk-LTJX2JXE.mjs.map +1 -0
- package/dist/chunk-OVNC4KW6.mjs +1494 -0
- package/dist/chunk-OVNC4KW6.mjs.map +1 -0
- package/dist/chunk-PNZSJN6T.cjs +1086 -0
- package/dist/chunk-PNZSJN6T.cjs.map +1 -0
- package/dist/chunk-TEFRA7GW.cjs +565 -0
- package/dist/chunk-TEFRA7GW.cjs.map +1 -0
- package/dist/chunk-UOMPPIED.mjs +1343 -0
- package/dist/chunk-UOMPPIED.mjs.map +1 -0
- package/dist/chunk-W6YHQI4F.mjs +187 -0
- package/dist/chunk-W6YHQI4F.mjs.map +1 -0
- package/dist/chunk-XTBRWVIV.cjs +346 -0
- package/dist/chunk-XTBRWVIV.cjs.map +1 -0
- package/dist/components-C7ZL7OMY.mjs +5 -0
- package/dist/components-C7ZL7OMY.mjs.map +1 -0
- package/dist/components-CJ2IB65O.cjs +27 -0
- package/dist/components-CJ2IB65O.cjs.map +1 -0
- package/dist/components-EASJYK45.mjs +6 -0
- package/dist/components-EASJYK45.mjs.map +1 -0
- package/dist/components-LDRFDV4A.cjs +22 -0
- package/dist/components-LDRFDV4A.cjs.map +1 -0
- package/dist/components-VZKUTDJK.mjs +5 -0
- package/dist/components-VZKUTDJK.mjs.map +1 -0
- package/dist/components-Y64GTIMQ.cjs +42 -0
- package/dist/components-Y64GTIMQ.cjs.map +1 -0
- package/dist/index.cjs +701 -4813
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1274 -1026
- package/dist/index.d.ts +1274 -1026
- package/dist/index.mjs +358 -4730
- package/dist/index.mjs.map +1 -1
- package/package.json +27 -4
- package/src/components/index.ts +17 -0
- package/src/components/lazy-wrapper.tsx +281 -0
- package/src/index.ts +92 -7
- package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +14 -5
- package/src/tools/AudioPlayer/lazy.tsx +85 -0
- package/src/tools/Gallery/components/Gallery.tsx +182 -0
- package/src/tools/Gallery/components/GalleryCarousel.tsx +251 -0
- package/src/tools/Gallery/components/GalleryCompact.tsx +173 -0
- package/src/tools/Gallery/components/GalleryGrid.tsx +493 -0
- package/src/tools/Gallery/components/GalleryImage.tsx +66 -0
- package/src/tools/Gallery/components/GalleryLightbox.tsx +331 -0
- package/src/tools/Gallery/components/GalleryMedia.tsx +66 -0
- package/src/tools/Gallery/components/GalleryThumbnails.tsx +173 -0
- package/src/tools/Gallery/components/GalleryThumbnailsVirtual.tsx +138 -0
- package/src/tools/Gallery/components/GalleryVideo.tsx +222 -0
- package/src/tools/Gallery/components/index.ts +13 -0
- package/src/tools/Gallery/hooks/index.ts +23 -0
- package/src/tools/Gallery/hooks/useGallery.ts +137 -0
- package/src/tools/Gallery/hooks/useImageDimensions.ts +223 -0
- package/src/tools/Gallery/hooks/usePinchZoom.ts +234 -0
- package/src/tools/Gallery/hooks/usePreloadImages.ts +71 -0
- package/src/tools/Gallery/hooks/useSwipe.ts +86 -0
- package/src/tools/Gallery/hooks/useVirtualList.ts +129 -0
- package/src/tools/Gallery/hooks/useZoom.ts +316 -0
- package/src/tools/Gallery/index.ts +66 -0
- package/src/tools/Gallery/types.ts +183 -0
- package/src/tools/Gallery/utils/imageAnalysis.ts +52 -0
- package/src/tools/Gallery/utils/index.ts +11 -0
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +20 -8
- package/src/tools/ImageViewer/components/ImageViewer.tsx +12 -4
- package/src/tools/ImageViewer/lazy.tsx +37 -0
- package/src/tools/JsonForm/lazy.tsx +43 -0
- package/src/tools/JsonForm/widgets/ColorWidget.tsx +4 -1
- package/src/tools/JsonTree/lazy.tsx +45 -0
- package/src/tools/LottiePlayer/lazy.tsx +57 -0
- package/src/tools/Map/components/CustomOverlay.tsx +54 -0
- package/src/tools/Map/components/DrawControl.tsx +36 -0
- package/src/tools/Map/components/GeocoderControl.tsx +70 -0
- package/src/tools/Map/components/LayerSwitcher.tsx +225 -0
- package/src/tools/Map/components/MapCluster.tsx +273 -0
- package/src/tools/Map/components/MapContainer.tsx +191 -0
- package/src/tools/Map/components/MapControls.tsx +44 -0
- package/src/tools/Map/components/MapLegend.tsx +161 -0
- package/src/tools/Map/components/MapMarker.tsx +102 -0
- package/src/tools/Map/components/MapPopup.tsx +46 -0
- package/src/tools/Map/components/MapSource.tsx +30 -0
- package/src/tools/Map/components/index.ts +20 -0
- package/src/tools/Map/context/MapContext.tsx +89 -0
- package/src/tools/Map/context/index.ts +2 -0
- package/src/tools/Map/hooks/index.ts +9 -0
- package/src/tools/Map/hooks/useMap.ts +11 -0
- package/src/tools/Map/hooks/useMapControl.ts +99 -0
- package/src/tools/Map/hooks/useMapEvents.ts +147 -0
- package/src/tools/Map/hooks/useMapLayers.ts +83 -0
- package/src/tools/Map/hooks/useMapViewport.ts +62 -0
- package/src/tools/Map/hooks/useMarkers.ts +85 -0
- package/src/tools/Map/index.ts +116 -0
- package/src/tools/Map/layers/cluster.ts +94 -0
- package/src/tools/Map/layers/index.ts +15 -0
- package/src/tools/Map/layers/line.ts +93 -0
- package/src/tools/Map/layers/point.ts +61 -0
- package/src/tools/Map/layers/polygon.ts +73 -0
- package/src/tools/Map/lazy.tsx +56 -0
- package/src/tools/Map/styles/index.ts +15 -0
- package/src/tools/Map/types.ts +259 -0
- package/src/tools/Map/utils/geo.ts +88 -0
- package/src/tools/Map/utils/index.ts +16 -0
- package/src/tools/Map/utils/transform.ts +107 -0
- package/src/tools/Mermaid/Mermaid.client.tsx +12 -4
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +6 -2
- package/src/tools/Mermaid/lazy.tsx +46 -0
- package/src/tools/OpenapiViewer/lazy.tsx +72 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +10 -3
- package/src/tools/PrettyCode/lazy.tsx +64 -0
- package/src/tools/VideoPlayer/lazy.tsx +63 -0
- package/dist/Mermaid.client-4OCKJ6QD.mjs.map +0 -1
- package/dist/Mermaid.client-ZP6OE46Z.cjs.map +0 -1
- package/dist/PlaygroundLayout-LMQTVXSP.mjs.map +0 -1
- package/dist/PlaygroundLayout-XXVBU4WZ.cjs.map +0 -1
- package/dist/PrettyCode.client-2CLSV2VD.cjs.map +0 -1
- package/dist/PrettyCode.client-Y2BVON7R.mjs.map +0 -1
- package/dist/chunk-FB5QBSI3.cjs.map +0 -1
- 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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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 ||
|
|
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">
|
|
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
|
-
|
|
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=
|
|
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 { }
|