@djangocfg/ui-tools 2.1.120 → 2.1.121
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/package.json +6 -6
- package/src/tools/Gallery/Gallery.story.tsx +17 -11
- package/src/tools/Gallery/components/compact/GalleryCompact.tsx +34 -4
- package/src/tools/Gallery/components/compact/index.ts +1 -1
- package/src/tools/Gallery/components/index.ts +1 -1
- package/src/tools/Gallery/components/media/GalleryImage.tsx +2 -2
- package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +3 -0
- package/src/tools/Gallery/index.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-tools",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.121",
|
|
4
4
|
"description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-tools",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"check": "tsc --noEmit"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@djangocfg/i18n": "^2.1.
|
|
66
|
-
"@djangocfg/ui-core": "^2.1.
|
|
65
|
+
"@djangocfg/i18n": "^2.1.121",
|
|
66
|
+
"@djangocfg/ui-core": "^2.1.121",
|
|
67
67
|
"lucide-react": "^0.545.0",
|
|
68
68
|
"react": "^19.0.0",
|
|
69
69
|
"react-dom": "^19.0.0",
|
|
@@ -95,10 +95,10 @@
|
|
|
95
95
|
"@maplibre/maplibre-gl-geocoder": "^1.7.0"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
|
-
"@djangocfg/i18n": "^2.1.
|
|
98
|
+
"@djangocfg/i18n": "^2.1.121",
|
|
99
99
|
"@djangocfg/playground": "workspace:*",
|
|
100
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
101
|
-
"@djangocfg/ui-core": "^2.1.
|
|
100
|
+
"@djangocfg/typescript-config": "^2.1.121",
|
|
101
|
+
"@djangocfg/ui-core": "^2.1.121",
|
|
102
102
|
"@types/mapbox__mapbox-gl-draw": "^1.4.8",
|
|
103
103
|
"@types/node": "^24.7.2",
|
|
104
104
|
"@types/react": "^19.1.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineStory, useBoolean, useSelect, useNumber } from '@djangocfg/playground';
|
|
2
|
-
import { Gallery, GalleryCompact, type GalleryMediaItem } from './index';
|
|
2
|
+
import { Gallery, GalleryCompact, type GalleryMediaItem, type GalleryAspectRatio } from './index';
|
|
3
3
|
|
|
4
4
|
// Sample images for testing
|
|
5
5
|
const SAMPLE_IMAGES: GalleryMediaItem[] = [
|
|
@@ -185,6 +185,13 @@ export const Interactive = () => {
|
|
|
185
185
|
description: 'Gallery preview display mode',
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
const [aspectRatio] = useSelect('aspectRatio', {
|
|
189
|
+
options: ['auto', '16/9', '4/3', '3/2', '1/1'] as const,
|
|
190
|
+
defaultValue: '4/3' as GalleryAspectRatio,
|
|
191
|
+
label: 'Aspect Ratio',
|
|
192
|
+
description: 'Image container aspect ratio',
|
|
193
|
+
});
|
|
194
|
+
|
|
188
195
|
return (
|
|
189
196
|
<div className="space-y-8">
|
|
190
197
|
{/* GalleryCompact */}
|
|
@@ -192,16 +199,15 @@ export const Interactive = () => {
|
|
|
192
199
|
<h3 className="text-lg font-semibold mb-4">GalleryCompact</h3>
|
|
193
200
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
194
201
|
<div className="rounded-xl border border-border overflow-hidden">
|
|
195
|
-
<
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
</div>
|
|
202
|
+
<GalleryCompact
|
|
203
|
+
images={SAMPLE_IMAGES}
|
|
204
|
+
aspectRatio={aspectRatio}
|
|
205
|
+
showDots={showDots}
|
|
206
|
+
showArrows={showArrows}
|
|
207
|
+
enableZoom={enableZoom}
|
|
208
|
+
showCounter={showCounter}
|
|
209
|
+
maxDots={maxDots}
|
|
210
|
+
/>
|
|
205
211
|
<div className="p-4">
|
|
206
212
|
<h3 className="font-medium">Interactive Card</h3>
|
|
207
213
|
<p className="text-sm text-muted-foreground">Use controls on the right →</p>
|
|
@@ -12,9 +12,14 @@ import { ChevronLeft, ChevronRight, ImageOff } from 'lucide-react'
|
|
|
12
12
|
import { GalleryMedia } from '../media'
|
|
13
13
|
import type { GalleryMediaItem } from '../../types'
|
|
14
14
|
|
|
15
|
+
/** Preset aspect ratios for common use cases */
|
|
16
|
+
export type GalleryAspectRatio = '16/9' | '4/3' | '3/2' | '1/1' | 'auto'
|
|
17
|
+
|
|
15
18
|
export interface GalleryCompactProps {
|
|
16
19
|
/** Array of images to display */
|
|
17
20
|
images: GalleryMediaItem[]
|
|
21
|
+
/** Aspect ratio preset or 'auto' to fill parent (default: 'auto') */
|
|
22
|
+
aspectRatio?: GalleryAspectRatio
|
|
18
23
|
/** Show dots indicator (default: true) */
|
|
19
24
|
showDots?: boolean
|
|
20
25
|
/** Max dots to show (default: 5) */
|
|
@@ -43,8 +48,18 @@ export interface GalleryCompactProps {
|
|
|
43
48
|
* - Fills parent container
|
|
44
49
|
* - Stops event propagation on navigation
|
|
45
50
|
*/
|
|
51
|
+
/** Map aspect ratio presets to CSS values */
|
|
52
|
+
const aspectRatioMap: Record<GalleryAspectRatio, string | undefined> = {
|
|
53
|
+
'16/9': '16 / 9',
|
|
54
|
+
'4/3': '4 / 3',
|
|
55
|
+
'3/2': '3 / 2',
|
|
56
|
+
'1/1': '1 / 1',
|
|
57
|
+
'auto': undefined,
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
export const GalleryCompact = memo(function GalleryCompact({
|
|
47
61
|
images,
|
|
62
|
+
aspectRatio = 'auto',
|
|
48
63
|
showDots = true,
|
|
49
64
|
maxDots = 5,
|
|
50
65
|
showCounter = false,
|
|
@@ -59,12 +74,20 @@ export const GalleryCompact = memo(function GalleryCompact({
|
|
|
59
74
|
// Track if component is mounted (client-side) to avoid hydration mismatches
|
|
60
75
|
const [isMounted, setIsMounted] = useState(false)
|
|
61
76
|
|
|
77
|
+
// Compute aspect ratio style - if set, we control height via aspect-ratio, otherwise fill parent
|
|
78
|
+
const aspectRatioStyle = aspectRatioMap[aspectRatio]
|
|
79
|
+
const containerStyle = aspectRatioStyle ? { aspectRatio: aspectRatioStyle } : undefined
|
|
80
|
+
const hasFixedAspect = aspectRatio !== 'auto'
|
|
81
|
+
const containerClass = hasFixedAspect ? 'relative w-full' : 'relative w-full h-full'
|
|
82
|
+
|
|
62
83
|
useEffect(() => {
|
|
63
84
|
setIsMounted(true)
|
|
64
85
|
}, [])
|
|
65
86
|
|
|
66
87
|
const total = images.length
|
|
67
88
|
const hasMultiple = total > 1
|
|
89
|
+
// Stable key for carousel reset on images change
|
|
90
|
+
const carouselKey = images[0]?.id ?? 'empty'
|
|
68
91
|
|
|
69
92
|
// Determine which images should be loaded (current + neighbors for smooth transition)
|
|
70
93
|
const loadedIndices = useMemo(() => {
|
|
@@ -144,7 +167,10 @@ export const GalleryCompact = memo(function GalleryCompact({
|
|
|
144
167
|
// Empty state
|
|
145
168
|
if (total === 0) {
|
|
146
169
|
return (
|
|
147
|
-
<div
|
|
170
|
+
<div
|
|
171
|
+
className={cn(containerClass, 'bg-muted flex items-center justify-center', className)}
|
|
172
|
+
style={containerStyle}
|
|
173
|
+
>
|
|
148
174
|
<ImageOff className="w-8 h-8 text-muted-foreground/50" />
|
|
149
175
|
</div>
|
|
150
176
|
)
|
|
@@ -154,7 +180,8 @@ export const GalleryCompact = memo(function GalleryCompact({
|
|
|
154
180
|
if (!hasMultiple) {
|
|
155
181
|
return (
|
|
156
182
|
<div
|
|
157
|
-
className={cn('
|
|
183
|
+
className={cn(containerClass, 'overflow-hidden group', className)}
|
|
184
|
+
style={containerStyle}
|
|
158
185
|
onClick={onClick}
|
|
159
186
|
>
|
|
160
187
|
<div className={cn(
|
|
@@ -180,7 +207,8 @@ export const GalleryCompact = memo(function GalleryCompact({
|
|
|
180
207
|
if (!isMounted) {
|
|
181
208
|
return (
|
|
182
209
|
<div
|
|
183
|
-
className={cn('
|
|
210
|
+
className={cn(containerClass, 'overflow-hidden', className)}
|
|
211
|
+
style={containerStyle}
|
|
184
212
|
onClick={onClick}
|
|
185
213
|
>
|
|
186
214
|
<GalleryMedia
|
|
@@ -228,12 +256,14 @@ export const GalleryCompact = memo(function GalleryCompact({
|
|
|
228
256
|
|
|
229
257
|
return (
|
|
230
258
|
<div
|
|
231
|
-
className={cn('
|
|
259
|
+
className={cn(containerClass, 'group/gallery', className)}
|
|
260
|
+
style={containerStyle}
|
|
232
261
|
onClick={handleClick}
|
|
233
262
|
onMouseEnter={() => setIsHovered(true)}
|
|
234
263
|
onMouseLeave={() => setIsHovered(false)}
|
|
235
264
|
>
|
|
236
265
|
<Carousel
|
|
266
|
+
key={carouselKey}
|
|
237
267
|
setApi={setApi}
|
|
238
268
|
opts={{
|
|
239
269
|
loop: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { GalleryCompact, type GalleryCompactProps } from './GalleryCompact'
|
|
1
|
+
export { GalleryCompact, type GalleryCompactProps, type GalleryAspectRatio } from './GalleryCompact'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export { Gallery } from './Gallery'
|
|
3
3
|
|
|
4
4
|
// Compact mode (for cards)
|
|
5
|
-
export { GalleryCompact, type GalleryCompactProps } from './compact'
|
|
5
|
+
export { GalleryCompact, type GalleryCompactProps, type GalleryAspectRatio } from './compact'
|
|
6
6
|
|
|
7
7
|
// Preview modes
|
|
8
8
|
export { GalleryCarousel } from './preview'
|
|
@@ -43,7 +43,7 @@ export const GalleryImage = memo(function GalleryImage({
|
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
45
|
<div
|
|
46
|
-
className={cn('relative overflow-hidden', className)}
|
|
46
|
+
className={cn('relative w-full h-full overflow-hidden', className)}
|
|
47
47
|
onClick={onClick}
|
|
48
48
|
>
|
|
49
49
|
{/* Loading skeleton */}
|
|
@@ -56,7 +56,7 @@ export const GalleryImage = memo(function GalleryImage({
|
|
|
56
56
|
<img
|
|
57
57
|
src={image.src}
|
|
58
58
|
alt={image.alt || 'Gallery image'}
|
|
59
|
-
className="w-full h-full object-cover animate-in fade-in-0 duration-300"
|
|
59
|
+
className="absolute inset-0 w-full h-full object-cover animate-in fade-in-0 duration-300"
|
|
60
60
|
loading={priority ? 'eager' : 'lazy'}
|
|
61
61
|
decoding="async"
|
|
62
62
|
/>
|
|
@@ -67,6 +67,8 @@ export const GalleryCarousel = memo(function GalleryCarousel({
|
|
|
67
67
|
const [api, setApi] = React.useState<CarouselApi>()
|
|
68
68
|
// Track if component is mounted (client-side) to avoid hydration mismatches
|
|
69
69
|
const [isMounted, setIsMounted] = useState(false)
|
|
70
|
+
// Stable key for carousel reset on images change
|
|
71
|
+
const carouselKey = images[0]?.id ?? 'empty'
|
|
70
72
|
|
|
71
73
|
useEffect(() => {
|
|
72
74
|
setIsMounted(true)
|
|
@@ -246,6 +248,7 @@ export const GalleryCarousel = memo(function GalleryCarousel({
|
|
|
246
248
|
return (
|
|
247
249
|
<div className={cn('space-y-3', className)}>
|
|
248
250
|
<Carousel
|
|
251
|
+
key={carouselKey}
|
|
249
252
|
setApi={setApi}
|
|
250
253
|
opts={{
|
|
251
254
|
loop: true,
|
|
@@ -10,7 +10,7 @@ export {
|
|
|
10
10
|
GalleryThumbnailsVirtual,
|
|
11
11
|
GalleryLightbox,
|
|
12
12
|
} from './components'
|
|
13
|
-
export type { GalleryCompactProps, GalleryGridProps, GalleryGridLayout } from './components'
|
|
13
|
+
export type { GalleryCompactProps, GalleryAspectRatio, GalleryGridProps, GalleryGridLayout } from './components'
|
|
14
14
|
|
|
15
15
|
// Hooks
|
|
16
16
|
export {
|