Skip to content

Commit 156b01e

Browse files
committed
feat(Gallery): add a hook for opening the gallery from the custom content
1 parent 6639b0c commit 156b01e

19 files changed

+488
-0
lines changed

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/hooks/index.ts

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 './useFilesGalleryFromContent';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
## useFilesGalleryFromContent
2+
3+
The hook for opening the gallery from html content with images and videos
4+
5+
### PropTypes
6+
7+
_GalleryContextProvider_:
8+
9+
| Property | Type | Required | Values | Default | Description |
10+
| :----------- | :---------------------------- | :------- | :----- | :------ | :------------------ |
11+
| theme | `ThemeProviderProps['theme']` | | | `dark` | The gallery theme |
12+
| className | `String` | | | | The modal class |
13+
| container | `HTMLElement` | | | | The modal container |
14+
| emptyMessage | `String` | | | No data | No data message |
15+
16+
_useFilesGalleryFromContent_
17+
18+
| Property | Type | Required | Values | Default | Description |
19+
| :---------- | :----------------------------------- | :------- | :----- | :------ | :-------------------------------------------------------------------------------------------------------------------------- |
20+
| customFiles | `(GalleryItem & { url?: string })[]` | | | | The additional files list (pass the url to be able to exclude the items from content if they are found in the custom files) |
21+
22+
_useFilesGalleryFromContent returns function with args_:
23+
24+
| Property | Type | Required | Values | Default | Description |
25+
| :------- | :--------------------------------- | :------- | :----- | :------ | :-------------- |
26+
| event | `React.MouseEvent<HTMLDivElement>` | Yes | | | The click event |
27+
28+
### Usage
29+
30+
First you should wrap your content into the GalleryContextProvider to be able to use the hook
31+
32+
```tsx
33+
import {GalleryContextProvider} from '@gravity-ui/components';
34+
35+
<GalleryContextProvider theme="dark" emptyMessage="Seems like your gallery is empty!">
36+
children
37+
</GalleryContextProvider>;
38+
```
39+
40+
Then use the hook inside your component
41+
42+
```tsx
43+
import {useFilesGalleryFromContent, getGalleryItemImage} from '@gravity-ui/components';
44+
45+
const customImages = [
46+
'https://i.pinimg.com/originals/d8/bd/b4/d8bdb45a931b4265bec8e8d3f15021bf.jpg',
47+
'https://i.pinimg.com/originals/c2/31/a0/c231a069c5e24099723564dae736f438.jpg',
48+
];
49+
50+
const customFiles = customImages.map((image) => ({
51+
url: image,
52+
...getGalleryItemImage({src: image, name: image}),
53+
}));
54+
55+
const openFilesGalleryFromContent = useFilesGalleryFromContent(customFiles);
56+
57+
<div>
58+
<img
59+
src="https://santreyd.ru/upload/iblock/acc/accd0c751590e792f7e43a05f22472f9.jpg"
60+
alt="Corgi"
61+
/>
62+
<a href="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4">
63+
My video
64+
</a>
65+
</div>;
66+
```
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {Text} from '@gravity-ui/uikit';
2+
3+
import {GalleryContextProvider} from '../../useGallery';
4+
import {useFilesGalleryFromContent} from '../useFilesGalleryFromContent';
5+
6+
const UseFilesGalleryFromContentExample = () => {
7+
const openFilesGalleryFromContent = useFilesGalleryFromContent();
8+
9+
return (
10+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
11+
<div onClick={openFilesGalleryFromContent}>
12+
<Text variant="subheader-3" as={'h2' as const}>
13+
Click image or video link to open the gallery
14+
</Text>
15+
<a href="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4">
16+
My video
17+
</a>
18+
<br />
19+
<img
20+
src="https://santreyd.ru/upload/iblock/acc/accd0c751590e792f7e43a05f22472f9.jpg"
21+
alt="Corgi"
22+
/>
23+
</div>
24+
);
25+
};
26+
27+
export const UseFilesGalleryFromContentShowcase = () => {
28+
return (
29+
<GalleryContextProvider>
30+
<UseFilesGalleryFromContentExample />
31+
</GalleryContextProvider>
32+
);
33+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type {Meta} from '@storybook/react';
2+
3+
import {UseFilesGalleryFromContentShowcase} from './UseFilesGalleryFromContentShowcase';
4+
5+
export default {
6+
title: 'Hooks/useFilesGalleryFromContent',
7+
parameters: {
8+
a11y: {
9+
element: '#storybook-root',
10+
config: {
11+
rules: [
12+
{
13+
id: 'color-contrast',
14+
enabled: false,
15+
},
16+
],
17+
},
18+
},
19+
},
20+
} as Meta;
21+
22+
export const Showcase = UseFilesGalleryFromContentShowcase;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const supportedImageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'bmp'];
2+
3+
export const supportedVideoExtensions = ['mp4', 'webm', 'ogg'];
4+
5+
export const supportedExtensions = [...supportedImageExtensions, ...supportedVideoExtensions];
6+
7+
export const extensionRegex = /\w+?$/;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './useFilesGalleryFromContent';
2+
export * from './types';
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {GalleryItemProps} from '../../components';
2+
3+
export type GalleryItemPropsWithUrl = GalleryItemProps & {
4+
// pass the url to be able to exclude the items from content if they are found in the custom files
5+
url?: string;
6+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import * as React from 'react';
2+
3+
import {getGalleryItemImage, getGalleryItemVideo} from '../../components';
4+
import {useGallery} from '../useGallery';
5+
6+
import {extensionRegex, supportedExtensions, supportedVideoExtensions} from './constants';
7+
import {GalleryItemPropsWithUrl} from './types';
8+
9+
export function useFilesGalleryFromContent(customFiles?: GalleryItemPropsWithUrl[]) {
10+
const openFilesGallery = useGallery();
11+
12+
return React.useCallback(
13+
(event: React.MouseEvent<HTMLDivElement>) => {
14+
if (event.target instanceof HTMLElement) {
15+
let fileLink = '';
16+
17+
if (event.target.tagName === 'IMG' && !event.target.closest('a')) {
18+
fileLink = event.target.getAttribute('src') ?? '';
19+
} else if (event.target.tagName === 'A') {
20+
fileLink = event.target.getAttribute('href') ?? '';
21+
}
22+
23+
if (!fileLink) {
24+
return;
25+
}
26+
27+
const filesFromContent = [
28+
...(event.currentTarget?.querySelectorAll('img,a') ?? []),
29+
].reduce<GalleryItemPropsWithUrl[]>((result, element) => {
30+
const isImage = element.tagName === 'IMG';
31+
const link = isImage
32+
? element.getAttribute('src')
33+
: element.getAttribute('href');
34+
35+
if (link && !customFiles?.some((item) => item.url === link)) {
36+
const extension = link.match(extensionRegex)?.[0] || '';
37+
38+
if (isImage || supportedExtensions.includes(extension)) {
39+
const name =
40+
(isImage
41+
? element.getAttribute('alt')
42+
: element.getAttribute('title')) || '';
43+
44+
result.push({
45+
...(supportedVideoExtensions.includes(extension)
46+
? getGalleryItemVideo({src: link, name: name})
47+
: getGalleryItemImage({src: link, name: name})),
48+
url: link,
49+
});
50+
}
51+
}
52+
53+
return result;
54+
}, []);
55+
56+
const files = [...(customFiles ?? []), ...filesFromContent];
57+
58+
const initialItemIndex = files.findIndex((item) => item.url === fileLink);
59+
60+
if (initialItemIndex !== -1) {
61+
event.preventDefault();
62+
openFilesGallery(files, initialItemIndex);
63+
}
64+
}
65+
},
66+
[customFiles, openFilesGallery],
67+
);
68+
}
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 {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+
});

0 commit comments

Comments
 (0)