Skip to content

Commit 69c4f98

Browse files
authored
feat(Gallery): add a hook for opening the gallery (#275)
1 parent 08c403a commit 69c4f98

14 files changed

+487
-28
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
/src/components/Stories @darkgenius
1515
/src/components/ConfirmDialog @kseniya57
1616
/src/components/Gallery @kseniya57
17+
/src/hooks/useGallery @kseniya57

src/components/Gallery/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The children of the Gallery should be an array of [GalleryItem with the required
1212
| open | `Boolean` | | | | The modal opened state |
1313
| onOpenChange | `(open: boolean) => void` | | | | The modal toggle handler |
1414
| className | `String` | | | | The modal class |
15+
| container | `HTMLElement` | | | | The modal container |
1516
| emptyMessage | `String` | | | No data | No data message |
1617

1718
### GalleryItem

src/components/Gallery/__stories__/Gallery.stories.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ const ImagesGalleryTemplate: StoryFn<GalleryProps> = () => {
6161
</Button>
6262
<Gallery open={open} onOpenChange={handleToggle}>
6363
{images.map((image, index) => (
64-
<GalleryItem key={index} {...getGalleryItemImage({src: image, name: image})} />
64+
<GalleryItem
65+
key={index}
66+
{...getGalleryItemImage({src: image.url, name: image.name})}
67+
/>
6568
))}
6669
</Gallery>
6770
</React.Fragment>

src/components/Gallery/__stories__/mockData.ts

Lines changed: 125 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,129 @@
11
export const images = [
2-
'https://i.pinimg.com/originals/d8/bd/b4/d8bdb45a931b4265bec8e8d3f15021bf.jpg',
3-
'https://i.pinimg.com/originals/c2/31/a0/c231a069c5e24099723564dae736f438.jpg',
4-
'https://cs4.pikabu.ru/post_img/big/2015/02/27/6/1425024947_2006737473.jpeg',
5-
'https://i.pinimg.com/originals/ef/7b/97/ef7b9724ad06cd6dfce92193e95a5caa.jpg',
6-
'https://avatars.mds.yandex.net/i?id=ea31df78678a1b3f4f1fb7199090831d_l-5235412-images-thumbs&n=13',
7-
'https://i.ytimg.com/vi/WA63GQpLzjA/maxresdefault.jpg',
8-
'https://i.pinimg.com/originals/02/eb/fd/02ebfd63d5435ec87c7413b8b2428214.jpg',
9-
'https://mir-s3-cdn-cf.behance.net/project_modules/max_3840/2b800731080995.5640a39521da5.jpg',
10-
'https://pic.rutubelist.ru/video/7a/1b/7a1b88f88ff7a470ea6f8131d51c2c5c.jpg',
11-
'https://i.pinimg.com/originals/4b/c7/ed/4bc7ed612f2080303644deb0f857b70f.jpg',
12-
'https://img1.reactor.cc/pics/post/нейроарт-нейронные-сети-красивые-картинки-art-7821877.png',
13-
'https://steamuserimages-a.akamaihd.net/ugc/841461304090603934/D3243F5856FEAE2052FC7CDB748B5BB65E6B247A/?imw=512&amp;imh=306&amp;ima=fit&amp;impolicy=Letterbox&amp;imcolor=%23000000&amp;letterbox=true',
14-
'https://celes.club/uploads/posts/2022-06/1654752045_50-celes-club-p-multyashnii-kosmos-oboi-krasivie-53.jpg',
15-
// duplicate the list to show the previews scroll
16-
'https://i.pinimg.com/originals/d8/bd/b4/d8bdb45a931b4265bec8e8d3f15021bf.jpg',
17-
'https://i.pinimg.com/originals/c2/31/a0/c231a069c5e24099723564dae736f438.jpg',
18-
'https://cs4.pikabu.ru/post_img/big/2015/02/27/6/1425024947_2006737473.jpeg',
19-
'https://i.pinimg.com/originals/ef/7b/97/ef7b9724ad06cd6dfce92193e95a5caa.jpg',
20-
'https://avatars.mds.yandex.net/i?id=ea31df78678a1b3f4f1fb7199090831d_l-5235412-images-thumbs&n=13',
21-
'https://i.ytimg.com/vi/WA63GQpLzjA/maxresdefault.jpg',
22-
'https://i.pinimg.com/originals/02/eb/fd/02ebfd63d5435ec87c7413b8b2428214.jpg',
23-
'https://mir-s3-cdn-cf.behance.net/project_modules/max_3840/2b800731080995.5640a39521da5.jpg',
24-
'https://pic.rutubelist.ru/video/7a/1b/7a1b88f88ff7a470ea6f8131d51c2c5c.jpg',
25-
'https://i.pinimg.com/originals/4b/c7/ed/4bc7ed612f2080303644deb0f857b70f.jpg',
26-
'https://img1.reactor.cc/pics/post/нейроарт-нейронные-сети-красивые-картинки-art-7821877.png',
27-
'https://steamuserimages-a.akamaihd.net/ugc/841461304090603934/D3243F5856FEAE2052FC7CDB748B5BB65E6B247A/?imw=512&amp;imh=306&amp;ima=fit&amp;impolicy=Letterbox&amp;imcolor=%23000000&amp;letterbox=true',
28-
'https://celes.club/uploads/posts/2022-06/1654752045_50-celes-club-p-multyashnii-kosmos-oboi-krasivie-53.jpg',
2+
{
3+
name: 'Resting',
4+
url: 'https://i.pinimg.com/originals/e7/44/1a/e7441aebde7c4d5a5afc476d5fa87082.jpg',
5+
},
6+
{
7+
name: 'Woke up',
8+
url: 'https://i.pinimg.com/736x/46/81/f0/4681f0072c3b5b8a96fdf78c4e12037c.jpg',
9+
},
10+
{
11+
name: 'Sleeping',
12+
url: 'https://i.pinimg.com/736x/ae/2a/a3/ae2aa360b9c18d0be08ef7279ab81638.jpg',
13+
},
14+
{
15+
name: 'Walking',
16+
url: 'https://i.pinimg.com/originals/42/b4/eb/42b4ebb23e452387a84c3ad02295003f.jpg',
17+
},
18+
{
19+
name: 'Driving',
20+
url: 'https://i.pinimg.com/736x/3a/e0/af/3ae0af7c666a284847d682156b8a6496.jpg',
21+
},
22+
{
23+
name: 'Sea',
24+
url: 'https://i.pinimg.com/736x/30/91/28/3091283eeff577749c5e3c81a9af7ce1.jpg',
25+
},
26+
{
27+
name: 'Running',
28+
url: 'https://i.pinimg.com/736x/61/ce/22/61ce22263ae275efa9b9c7b02b3dea61.jpg',
29+
},
30+
{
31+
name: "Didn't understand",
32+
url: 'https://i.pinimg.com/736x/72/70/a6/7270a6d918468fcce5c297afc281bf35.jpg',
33+
},
34+
{
35+
name: 'Hidden',
36+
url: 'https://i.pinimg.com/originals/cc/8d/cf/cc8dcfb1f6bcb54a2836019558027ef7.jpg',
37+
},
38+
{
39+
name: 'Garden',
40+
url: 'https://i.pinimg.com/736x/cd/74/75/cd7475dd2fa19d339e147b627896c5d4.jpg',
41+
},
42+
{
43+
name: 'Flower',
44+
url: 'https://i.pinimg.com/736x/aa/21/dd/aa21ddd2b3de5298953ca0762838951e.jpg',
45+
},
46+
{
47+
name: 'Forest',
48+
url: 'https://i.pinimg.com/originals/9c/3a/16/9c3a161254af0369c2a32bae240da2ff.jpg',
49+
},
50+
{
51+
name: 'Upset',
52+
url: 'https://i.pinimg.com/originals/d0/48/41/d048413c59db4dd7d1e332da991ce347.jpg',
53+
},
54+
{
55+
name: 'Thinking',
56+
url: 'https://i.pinimg.com/736x/a9/4a/4b/a94a4b6810787f415166bc9d0bef71eb.jpg',
57+
},
58+
{
59+
name: 'Field',
60+
url: 'https://i.pinimg.com/736x/9c/b2/d1/9cb2d19f5cf6bdb53b7c1f42f480db3f.jpg',
61+
},
62+
{
63+
name: 'Sitting',
64+
url: 'https://i.pinimg.com/736x/34/94/5e/34945e08218f073f4f1d3b147108b06a.jpg',
65+
},
66+
{
67+
name: 'Doggy',
68+
url: 'https://i.pinimg.com/originals/a6/30/93/a6309348c1dab1899d022b8759dc659b.jpg',
69+
},
70+
{
71+
name: 'Eating',
72+
url: 'https://i.pinimg.com/736x/69/e0/aa/69e0aac5a17f8790da23dcc379e6fd05.jpg',
73+
},
74+
{
75+
name: 'Gift',
76+
url: 'https://i.pinimg.com/736x/e3/f2/b4/e3f2b4f87a0ae9744a0b14c81710463c.jpg',
77+
},
78+
{name: 'Happy', url: 'https://i.pinimg.com/736x/f0/d4/01/f0d4011c75b92f165dbab83c8654ebf1.jpg'},
79+
{
80+
name: 'Morning',
81+
url: 'https://i.pinimg.com/736x/69/51/5a/69515a0da57d33b5df80d5af0c14ef7e.jpg',
82+
},
83+
{
84+
name: 'Waiting',
85+
url: 'https://i.pinimg.com/736x/07/b6/f5/07b6f527be3860832e55e6ec041766b6.jpg',
86+
},
87+
{
88+
name: 'Cool',
89+
url: 'https://i.pinimg.com/originals/32/a3/30/32a33050f32ba74c2b66456d458cb792.jpg',
90+
},
91+
{
92+
name: 'Travelling',
93+
url: 'https://i.pinimg.com/originals/07/73/12/077312668712ae5aba52766b5b3e2b8e.jpg',
94+
},
95+
{
96+
name: 'Fashion',
97+
url: 'https://i.pinimg.com/736x/5d/15/35/5d1535227d2eb616b21113d69e5cf49b.jpg',
98+
},
99+
{
100+
name: 'Washed',
101+
url: 'https://i.pinimg.com/736x/12/e5/5a/12e55a138ef6345d359d9f6cf0715a5d.jpg',
102+
},
103+
{
104+
name: 'Sleep soon',
105+
url: 'https://i.pinimg.com/736x/90/da/f6/90daf6e058bc377b1a3c1cf53d63bde7.jpg',
106+
},
107+
{
108+
name: 'Friends',
109+
url: 'https://i.pinimg.com/originals/0f/e4/66/0fe4667784f51ac62a7fbb8c84b16f2f.jpg',
110+
},
111+
{
112+
name: 'The gentleman',
113+
url: 'https://i.pinimg.com/736x/ed/11/1e/ed111e9003c2d972b7e09816deef26de.jpg',
114+
},
115+
{
116+
name: 'Basket',
117+
url: 'https://i.pinimg.com/736x/cf/4e/ac/cf4eac1954a1c1b4c9d43dbb8f8752a8.jpg',
118+
},
119+
{
120+
name: 'Winter',
121+
url: 'https://i.pinimg.com/originals/ee/50/90/ee509026a73aae62ef3ffd1860f2db5f.jpg',
122+
},
123+
{
124+
name: 'Flowers',
125+
url: 'https://i.pinimg.com/originals/d8/a0/53/d8a0536b3855ee1cdeaaf1eb3bb71aed.jpg',
126+
},
29127
];
30128

31129
export type GalleryFile =

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useGallery';

src/hooks/useGallery/AsyncGallery.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as React from 'react';
2+
3+
export const AsyncGallery = React.lazy(() =>
4+
import('../../components/Gallery/Gallery').then((module) => ({default: module.Gallery})),
5+
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as React from 'react';
2+
3+
import type {GalleryItemProps} from '../../components';
4+
5+
export type GalleryContextType = {
6+
openGallery: (items: GalleryItemProps[], initialFileIndex?: number) => void;
7+
};
8+
9+
export const GalleryContext = React.createContext<GalleryContextType>({
10+
openGallery: () => {},
11+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import * as React from 'react';
2+
3+
import {ThemeProvider} from '@gravity-ui/uikit';
4+
import type {ThemeProviderProps} from '@gravity-ui/uikit';
5+
6+
import {GalleryItem} from '../../components';
7+
import type {GalleryItemProps, GalleryProps} from '../../components';
8+
9+
import {AsyncGallery} from './AsyncGallery';
10+
import {GalleryContext} from './GalleryContext';
11+
import type {GalleryContextType} from './GalleryContext';
12+
13+
export type GalleryProviderProps = React.PropsWithChildren<
14+
Pick<GalleryProps, 'container' | 'className' | 'emptyMessage'>
15+
> &
16+
Pick<ThemeProviderProps, 'theme'>;
17+
18+
export const GalleryProvider = ({
19+
children,
20+
container,
21+
emptyMessage,
22+
theme,
23+
className,
24+
}: GalleryProviderProps) => {
25+
const [isOpen, setIsOpen] = React.useState(false);
26+
const [{items, initialItemIndex}, setGalleryProps] = React.useState<{
27+
items: GalleryItemProps[];
28+
initialItemIndex: number;
29+
}>({items: [], initialItemIndex: 0});
30+
31+
const contextValue = React.useMemo<GalleryContextType>(
32+
() => ({
33+
openGallery: (items, initialFileIndex) => {
34+
setGalleryProps({
35+
items,
36+
initialItemIndex: initialFileIndex ?? 0,
37+
});
38+
setIsOpen(true);
39+
},
40+
}),
41+
[],
42+
);
43+
44+
return (
45+
<GalleryContext.Provider value={contextValue}>
46+
{children}
47+
{isOpen && (
48+
<React.Suspense fallback={null}>
49+
<ThemeProvider theme={theme}>
50+
<AsyncGallery
51+
open={isOpen}
52+
onOpenChange={setIsOpen}
53+
container={container}
54+
className={className}
55+
emptyMessage={emptyMessage}
56+
initialItemIndex={initialItemIndex}
57+
>
58+
{items.map((file, index) => (
59+
<GalleryItem key={index} {...file} />
60+
))}
61+
</AsyncGallery>
62+
</ThemeProvider>
63+
</React.Suspense>
64+
)}
65+
</GalleryContext.Provider>
66+
);
67+
};

src/hooks/useGallery/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
## useGallery
2+
3+
The hook for opening the gallery
4+
5+
### Usage
6+
7+
First you should wrap your content into the GalleryProvider to be able to use the hook
8+
9+
```tsx
10+
import {GalleryProvider} from '@gravity-ui/components';
11+
12+
<GalleryProvider theme="dark" emptyMessage="Seems like your gallery is empty!">
13+
children
14+
</GalleryProvider>;
15+
```
16+
17+
Then use the hook inside your custom hooks or components
18+
19+
```tsx
20+
import {useGallery, getGalleryItemImage} from '@gravity-ui/components';
21+
22+
const {openGallery} = useGallery();
23+
24+
const images = [
25+
'https://i.pinimg.com/originals/d8/bd/b4/d8bdb45a931b4265bec8e8d3f15021bf.jpg',
26+
'https://i.pinimg.com/originals/c2/31/a0/c231a069c5e24099723564dae736f438.jpg',
27+
];
28+
29+
openGallery(
30+
images.map((image) => getGalleryItemImage({src: image, name: image})),
31+
2,
32+
);
33+
```
34+
35+
### Properties
36+
37+
_GalleryProvider_:
38+
39+
| Property | Type | Required | Default | Description |
40+
| :----------- | :---------------------------- | :------- | :-------- | :------------------ |
41+
| theme | `ThemeProviderProps['theme']` | | | The gallery theme |
42+
| className | `String` | | | The modal class |
43+
| container | `HTMLElement` | | | The modal container |
44+
| emptyMessage | `String` | | "No data" | No data message |
45+
46+
_useGallery hook returns_:
47+
48+
| Property | Type | Description |
49+
| :---------- | :--------------------------------------------------------- | :----------------------------------- |
50+
| openGallery | `(items: GalleryItem[], initialItemIndex: number) => void` | The function for opening the gallery |
51+
52+
_openGallery function args_:
53+
54+
| Property | Type | Required | Default | Description |
55+
| :--------------- | :-------------- | :------- | :------ | :--------------------- |
56+
| items | `GalleryItem[]` | Yes | | The gallery items |
57+
| initialItemIndex | `number` | | 0 | The initial item index |
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {FilePreview, Text} from '@gravity-ui/uikit';
2+
3+
import {getGalleryItemImage} from '../../../components';
4+
import {GalleryProvider} from '../GalleryProvider';
5+
import {useGallery} from '../useGallery';
6+
7+
import {images} from './mockData';
8+
9+
const UseGalleryExample = () => {
10+
const {openGallery} = useGallery();
11+
12+
const handleOpen = (index: number) => {
13+
openGallery(
14+
images.map((image) => getGalleryItemImage({src: image.url, name: image.name})),
15+
index,
16+
);
17+
};
18+
19+
return (
20+
<div>
21+
<Text variant="subheader-3" as={'h2' as const}>
22+
Click an item to open the gallery
23+
</Text>
24+
<div
25+
style={{
26+
display: 'grid',
27+
justifyItems: 'center',
28+
alignItems: 'center',
29+
gridTemplateColumns: 'repeat(6, 1fr)',
30+
gridGap: 8,
31+
width: 800,
32+
}}
33+
>
34+
{images.map((image, index) => (
35+
<FilePreview
36+
onClick={() => handleOpen(index)}
37+
key={index}
38+
imageSrc={image.url}
39+
file={{type: 'image/jpeg', name: image.name} as File}
40+
/>
41+
))}
42+
</div>
43+
</div>
44+
);
45+
};
46+
47+
export const UseGalleryShowcase = () => {
48+
return (
49+
<GalleryProvider>
50+
<UseGalleryExample />
51+
</GalleryProvider>
52+
);
53+
};

0 commit comments

Comments
 (0)