Skip to content

Commit 401a5d6

Browse files
committed
feat(Gallery): add Gallery and FilesGallery components
1 parent b855931 commit 401a5d6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1459
-11
lines changed

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313
/src/components/StoreBadge @NikitaCG
1414
/src/components/Stories @darkgenius
1515
/src/components/ConfirmDialog @kseniya57
16+
/src/components/Gallery @kseniya57
17+
/src/components/FilesGallery @kseniya57

src/components/FilePreview/FilePreview.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export interface FilePreviewProps extends QAProps {
5353

5454
onClick?: React.MouseEventHandler<HTMLDivElement>;
5555
actions?: FilePreviewActionProps[];
56+
hideName?: boolean;
5657
}
5758

5859
export function FilePreview({
@@ -63,6 +64,7 @@ export function FilePreview({
6364
description,
6465
onClick,
6566
actions,
67+
hideName,
6668
}: FilePreviewProps) {
6769
const id = useUniqId();
6870

@@ -125,9 +127,11 @@ export function FilePreview({
125127
<Icon className={cn('icon-svg')} data={FILE_ICON[type]} size={20} />
126128
</div>
127129
)}
128-
<Text className={cn('name')} color="secondary" ellipsis title={file.name}>
129-
{file.name}
130-
</Text>
130+
{!hideName && (
131+
<Text className={cn('name')} color="secondary" ellipsis title={file.name}>
132+
{file.name}
133+
</Text>
134+
)}
131135
{Boolean(description) && (
132136
<Text
133137
className={cn('description')}

src/components/FilePreview/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ A component for displaying the file.
1212
| className | `string` | | | Class name for the file container |
1313
| onClick | `function` | | | Click handler for the file container |
1414
| [actions](#actions) | `FilePreviewActionProps[]` | | `[]` | Click handler for the file container |
15+
| hideName | `Boolean` | | | Hide the file name |
1516

1617
#### Actions
1718

1819
For a file, you can prescribe actions that will be visible when you hover over it.
1920

20-
| Property | Type | Required | Default | Description |
21-
| ---------- | ------------------------------------------------------------------------------------ | -------- | ------- | ------------------------------ |
22-
| id | `String` | | | Action id |
23-
| icon | `String` | | | Action icon |
24-
| title | `String` | | | Action hint on hover |
25-
| onClick | `function` | | | Action click handler |
26-
| href | `String` | | | Action button href |
27-
| extraProps | `ButtonHTMLAttributes<HTMLButtonElement> \| AnchorHTMLAttributes<HTMLAnchorElement>` | | | Additional action button props |
21+
| Property | Type | Required | Default | Description |
22+
| ---------- | ----------------------------------------- | ---------------------------------------- | ------- | ------------------------------ |
23+
| id | `String` | | | Action id |
24+
| icon | `String` | | | Action icon |
25+
| title | `String` | | | Action hint on hover |
26+
| onClick | `function` | | | Action click handler |
27+
| href | `String` | | | Action button href |
28+
| extraProps | `ButtonHTMLAttributes<HTMLButtonElement>` | AnchorHTMLAttributes<HTMLAnchorElement>` | | Additional action button props |
2829

2930
```jsx
3031

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
@use '../variables';
2+
3+
$block: '.#{variables.$ns}files-gallery';
4+
5+
$filePreviewBlock: '.#{variables.$ns}file-preview';
6+
7+
#{$block} {
8+
&__active-item-info {
9+
align-self: center;
10+
}
11+
12+
&__file-preview {
13+
width: 100%;
14+
height: 100%;
15+
16+
#{$filePreviewBlock}__card {
17+
width: 100%;
18+
min-width: 100%;
19+
height: 100%;
20+
padding: 0;
21+
}
22+
23+
#{$filePreviewBlock}__image,
24+
#{$filePreviewBlock}__icon {
25+
width: 100%;
26+
height: 100%;
27+
}
28+
}
29+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react';
2+
3+
import {ChevronsCollapseUpRight, ChevronsExpandUpRight} from '@gravity-ui/icons';
4+
import {ActionTooltip, Button, Icon} from '@gravity-ui/uikit';
5+
6+
import {Gallery, GalleryProps} from '../Gallery';
7+
import {block} from '../utils/cn';
8+
9+
import {useFullScreen} from './hooks/useFullScreen';
10+
import {i18n} from './i18n';
11+
import {GalleryFileBase} from './types';
12+
import {renderActiveItemInfo} from './utils/renderActiveItemInfo';
13+
import {renderItemPreview} from './utils/renderItemPreview';
14+
15+
import './FilesGallery.scss';
16+
17+
export const cnFilesGallery = block('files-gallery');
18+
19+
export type FilesGalleryProps<GalleryFileType extends GalleryFileBase> = Omit<
20+
GalleryProps<GalleryFileType>,
21+
'fullScreen' | 'renderItemPreview' | 'renderActiveItemInfo'
22+
> & {};
23+
24+
export const FilesGallery = <GalleryFileType extends GalleryFileBase>({
25+
renderActions: providedRenderActions,
26+
activeItemInfoClassName,
27+
...galleryProps
28+
}: FilesGalleryProps<GalleryFileType>) => {
29+
const {fullScreen, handleSwitchFullScreenMode} = useFullScreen();
30+
31+
const renderActions = React.useCallback<
32+
NonNullable<GalleryProps<GalleryFileType>['renderActions']>
33+
>(
34+
(item) => {
35+
return (
36+
<React.Fragment>
37+
{providedRenderActions?.(item)}
38+
<ActionTooltip
39+
title={fullScreen ? i18n('exit-full-screen') : i18n('enter-full-screen')}
40+
hotkey="Shift+F"
41+
>
42+
<Button
43+
size="l"
44+
view="flat"
45+
extraProps={{
46+
'aria-label': fullScreen
47+
? i18n('exit-full-screen')
48+
: i18n('enter-full-screen'),
49+
}}
50+
onClick={handleSwitchFullScreenMode}
51+
>
52+
<Icon
53+
data={fullScreen ? ChevronsCollapseUpRight : ChevronsExpandUpRight}
54+
/>
55+
</Button>
56+
</ActionTooltip>
57+
</React.Fragment>
58+
);
59+
},
60+
[fullScreen, handleSwitchFullScreenMode, providedRenderActions],
61+
);
62+
63+
return (
64+
<Gallery<GalleryFileType>
65+
fullScreen={fullScreen}
66+
renderItemPreview={renderItemPreview}
67+
renderActions={renderActions}
68+
renderActiveItemInfo={renderActiveItemInfo}
69+
activeItemInfoClassName={cnFilesGallery('active-item-info', activeItemInfoClassName)}
70+
{...galleryProps}
71+
/>
72+
);
73+
};

src/components/FilesGallery/README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
## FilesGallery
2+
3+
The component for rendering file galleries.
4+
The component is responsible for the gallery navigation (keyboard arrows, body side click and header arrow click).
5+
You should provide the renderers for the body and file actions, for example the copy link action and the download action.
6+
7+
### PropTypes
8+
9+
| Property | Type | Required | Values | Default | Description |
10+
| :---------------------- | :------------------------------------------------- | :------- | :----- | :------ | :---------------------------------------------------- |
11+
| items | `(GalleryFileType extends GalleryFileBase)[]` | Yes | | | The gallery items list |
12+
| initialItemIndex | `Number` | | | 0 | The initial active item index |
13+
| open | `Boolean` | | | | The modal opened state |
14+
| onClose | `() => void` | Yes | | | The modal close handler |
15+
| renderActions | `(activeItem: GalleryFileType) => React.ReactNode` | | | | The gallery actions renderer, accepts the active item |
16+
| renderBody | `(activeItem: GalleryFileType) => React.ReactNode` | Yes | | | The gallery body renderer, accepts the active item |
17+
| modalClassName | `String` | | | | The modal class |
18+
| className | `String` | | | | The modal content class |
19+
| headerClassName | `String` | | | | The gallery header class |
20+
| activeItemInfoClassName | `String` | | | | The active item info class name |
21+
| footerClassName | `String` | | | | The gallery footer class |
22+
| bodyClassName | `String` | | | | The gallery body class |
23+
24+
### Examples
25+
26+
```tsx
27+
import {
28+
FilesGallery,
29+
FilesGalleryFallbackText,
30+
ImageFileView,
31+
VideoFileView,
32+
DocumentFileView,
33+
} from '@gravity-ui/components';
34+
35+
const FilesGalleryShowcase = () => {
36+
const [open, setOpen] = React.useState(false);
37+
38+
const container = usePortalContainer();
39+
40+
const handleClose = React.useCallback(() => {
41+
setOpen(false);
42+
}, []);
43+
44+
const handleOpen = React.useCallback(() => {
45+
setOpen(true);
46+
}, []);
47+
48+
const renderBody = React.useCallback((activeFile: GalleryFile) => {
49+
switch (activeFile.type) {
50+
case 'image': {
51+
return <ImageFileView src={activeFile.url} />;
52+
}
53+
case 'video': {
54+
return <VideoFileView src={activeFile.url} />;
55+
}
56+
case 'document': {
57+
return <DocumentFileView name={activeFile.data.name} src={activeFile.url} />;
58+
}
59+
case 'text': {
60+
return <Text variant="body-1">{activeFile.text}</Text>;
61+
}
62+
default: {
63+
return <FilesGalleryFallbackText />;
64+
}
65+
}
66+
}, []);
67+
68+
const renderActions = React.useCallback((activeFile: GalleryFile) => {
69+
return (
70+
<React.Fragment>
71+
<CopyToClipboard text={'url' in activeFile ? activeFile.url : activeFile.text}>
72+
{() => (
73+
<div>
74+
<ActionTooltip title="Copy link">
75+
<Button
76+
size="l"
77+
view="flat"
78+
extraProps={{
79+
'aria-label': 'Copy link',
80+
}}
81+
>
82+
<Icon data={Link} />
83+
</Button>
84+
</ActionTooltip>
85+
</div>
86+
)}
87+
</CopyToClipboard>
88+
{'url' in activeFile && (
89+
<ActionTooltip title="Download">
90+
<Button
91+
size="l"
92+
view="flat"
93+
extraProps={{
94+
'aria-label': 'download',
95+
}}
96+
onClick={(event) => event.stopPropagation()}
97+
href={`${activeFile.url}?inline=false`}
98+
target="_blank"
99+
rel="noreferrer"
100+
>
101+
<Icon data={ArrowDownToLine} />
102+
</Button>
103+
</ActionTooltip>
104+
)}
105+
</React.Fragment>
106+
);
107+
}, []);
108+
109+
return (
110+
<React.Fragment>
111+
<Button onClick={handleOpen} view="action" size="l">
112+
Open gallery
113+
</Button>
114+
<FilesGallery<GalleryFile>
115+
theme="dark"
116+
open={open}
117+
onClose={handleClose}
118+
container={container || undefined}
119+
items={files}
120+
renderBody={renderBody}
121+
renderActions={renderActions}
122+
/>
123+
</React.Fragment>
124+
);
125+
};
126+
```

0 commit comments

Comments
 (0)