Skip to content

Commit c103155

Browse files
committed
feat: add Gallery component
1 parent e0e1a56 commit c103155

59 files changed

Lines changed: 2285 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './useGallery';
2+
export * from './useGallery.props';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './useGalleryItem';
2+
export * from './useGalleryItem.props';
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { useGalleryItemProps } from '@primereact/types/shared/gallery';
2+
3+
export const defaultItemProps: useGalleryItemProps = {
4+
normalScale: 1,
5+
zoomedScale: 3
6+
};

packages/headless/src/gallery/item/useGalleryItem.tsx

Lines changed: 522 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { useGalleryProps } from '@primereact/types/shared/gallery';
2+
3+
export const defaultProps: useGalleryProps = {
4+
activeIndex: 0,
5+
onActiveIndexChange: undefined
6+
};

packages/headless/src/gallery/useGallery.test.ts

Whitespace-only changes.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { withHeadless } from '@primereact/core/headless';
2+
import { Carousel } from 'primereact/carousel';
3+
import * as React from 'react';
4+
import { defaultProps } from './useGallery.props';
5+
6+
export const useGallery = withHeadless({
7+
name: 'useGallery',
8+
defaultProps,
9+
setup: ({ props, elementRef }) => {
10+
const { activeIndex: activeIndexProp = 0, onActiveIndexChange = undefined } = props;
11+
const contentRef = React.useRef<HTMLDivElement>(null);
12+
const itemsRef = React.useRef<React.RefObject<HTMLDivElement>[]>([]);
13+
const toolbarRef = React.useRef<HTMLDivElement>(null);
14+
const thumbnailRef = React.useRef<React.RefObject<typeof Carousel>>(null);
15+
const prevRef = React.useRef<HTMLButtonElement>(null);
16+
const nextRef = React.useRef<HTMLButtonElement>(null);
17+
const [, forceUpdate] = React.useState(0);
18+
const [activeIndex, setActiveIndex] = React.useState(activeIndexProp);
19+
20+
const [isFullscreen, setIsFullscreen] = React.useState(false);
21+
22+
React.useEffect(() => {
23+
setActiveIndex(activeIndexProp);
24+
}, [activeIndexProp]);
25+
26+
const registerItem = (ref: HTMLDivElement | null): number => {
27+
if (!ref) return -1;
28+
29+
const existingIndex = itemsRef.current.findIndex((item) => item.current === ref);
30+
31+
if (existingIndex === -1) {
32+
itemsRef.current.push({ current: ref });
33+
forceUpdate((x) => x + 1);
34+
35+
return itemsRef.current.length - 1;
36+
}
37+
38+
return existingIndex;
39+
};
40+
41+
const handleNext = () => {
42+
const newIndex = (activeIndex + 1) % itemsRef.current.length;
43+
44+
setActiveIndex(newIndex);
45+
onActiveIndexChange?.({ originalEvent: undefined as unknown as React.SyntheticEvent, value: newIndex });
46+
};
47+
48+
const handlePrev = () => {
49+
const newIndex = (activeIndex - 1 + itemsRef.current.length) % itemsRef.current.length;
50+
51+
setActiveIndex(newIndex);
52+
onActiveIndexChange?.({ originalEvent: undefined as unknown as React.SyntheticEvent, value: newIndex });
53+
};
54+
55+
const createCustomEvent = (action: string) => () => {
56+
const activeItem = itemsRef.current[activeIndex]?.current;
57+
58+
if (activeItem) {
59+
const event = new CustomEvent('gallery-' + action, {
60+
detail: { action: action }
61+
});
62+
63+
activeItem.dispatchEvent(event);
64+
}
65+
};
66+
67+
const toggleFullScreen = () => {
68+
if (!elementRef.current) return;
69+
70+
if (!isFullscreen) {
71+
Object.assign(elementRef.current.style, {
72+
position: 'fixed',
73+
top: '0',
74+
left: '0',
75+
width: '100dvw',
76+
height: '100dvh',
77+
zIndex: '9999'
78+
});
79+
80+
document.body.style.overflow = 'hidden';
81+
82+
setIsFullscreen(true);
83+
} else {
84+
Object.assign(elementRef.current.style, {
85+
position: 'relative',
86+
top: '',
87+
left: '',
88+
width: '',
89+
height: '',
90+
zIndex: ''
91+
});
92+
93+
document.body.style.overflow = 'auto';
94+
95+
setIsFullscreen(false);
96+
}
97+
98+
setTimeout(() => {
99+
window.dispatchEvent(new Event('resize'));
100+
}, 100);
101+
};
102+
103+
const actions = {
104+
zoomIn: createCustomEvent('zoom-in'),
105+
zoomOut: createCustomEvent('zoom-out'),
106+
rotateLeft: createCustomEvent('rotate-left'),
107+
rotateRight: createCustomEvent('rotate-right'),
108+
flipX: createCustomEvent('flip-x'),
109+
flipY: createCustomEvent('flip-y'),
110+
download: createCustomEvent('download'),
111+
next: handleNext,
112+
prev: handlePrev,
113+
toggleFullScreen: toggleFullScreen
114+
};
115+
116+
const handleClickAction = React.useCallback(
117+
(action?: string) => {
118+
if (action && actions[action as keyof typeof actions]) {
119+
actions[action as keyof typeof actions]();
120+
}
121+
},
122+
[actions]
123+
);
124+
125+
const state = {
126+
isFullscreen,
127+
activeIndex
128+
};
129+
130+
return {
131+
state,
132+
registerItem,
133+
handleNext,
134+
handlePrev,
135+
createCustomEvent,
136+
toggleFullScreen,
137+
handleClickAction,
138+
actions,
139+
contentRef,
140+
toolbarRef,
141+
thumbnailRef,
142+
prevRef,
143+
nextRef
144+
};
145+
}
146+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { createOptionalContext } from '@primereact/core/utils';
2+
import type { GalleryInstance } from '@primereact/types/shared/gallery';
3+
4+
export const [GalleryProvider, useGalleryContext] = createOptionalContext<GalleryInstance>();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as HeadlessGallery from '@primereact/headless/gallery';
2+
import type { GalleryProps } from '@primereact/types/shared/gallery';
3+
4+
export const defaultProps: GalleryProps = {
5+
...HeadlessGallery.defaultProps,
6+
as: 'div'
7+
};

packages/primereact/src/gallery/Gallery.test.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)