Skip to content

Commit

Permalink
feat(Gallery): add Gallery and FilesGallery components
Browse files Browse the repository at this point in the history
  • Loading branch information
kseniyakuzina committed Dec 19, 2024
1 parent b855931 commit 8c07955
Show file tree
Hide file tree
Showing 42 changed files with 1,461 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
/src/components/StoreBadge @NikitaCG
/src/components/Stories @darkgenius
/src/components/ConfirmDialog @kseniya57
/src/components/Gallery @kseniya57
/src/components/FilesGallery @kseniya57
10 changes: 7 additions & 3 deletions src/components/FilePreview/FilePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface FilePreviewProps extends QAProps {

onClick?: React.MouseEventHandler<HTMLDivElement>;
actions?: FilePreviewActionProps[];
hideName?: boolean;
}

export function FilePreview({
Expand All @@ -63,6 +64,7 @@ export function FilePreview({
description,
onClick,
actions,
hideName,
}: FilePreviewProps) {
const id = useUniqId();

Expand Down Expand Up @@ -125,9 +127,11 @@ export function FilePreview({
<Icon className={cn('icon-svg')} data={FILE_ICON[type]} size={20} />
</div>
)}
<Text className={cn('name')} color="secondary" ellipsis title={file.name}>
{file.name}
</Text>
{!hideName && (
<Text className={cn('name')} color="secondary" ellipsis title={file.name}>
{file.name}
</Text>
)}
{Boolean(description) && (
<Text
className={cn('description')}
Expand Down
17 changes: 9 additions & 8 deletions src/components/FilePreview/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@ A component for displaying the file.
| className | `string` | | | Class name for the file container |
| onClick | `function` | | | Click handler for the file container |
| [actions](#actions) | `FilePreviewActionProps[]` | | `[]` | Click handler for the file container |
| hideName | `Boolean` | | | Hide the file name |

#### Actions

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

| Property | Type | Required | Default | Description |
| ---------- | ------------------------------------------------------------------------------------ | -------- | ------- | ------------------------------ |
| id | `String` | | | Action id |
| icon | `String` | | | Action icon |
| title | `String` | | | Action hint on hover |
| onClick | `function` | | | Action click handler |
| href | `String` | | | Action button href |
| extraProps | `ButtonHTMLAttributes<HTMLButtonElement> \| AnchorHTMLAttributes<HTMLAnchorElement>` | | | Additional action button props |
| Property | Type | Required | Default | Description |
| ---------- | ----------------------------------------- | ---------------------------------------- | ------- | ------------------------------ |
| id | `String` | | | Action id |
| icon | `String` | | | Action icon |
| title | `String` | | | Action hint on hover |
| onClick | `function` | | | Action click handler |
| href | `String` | | | Action button href |
| extraProps | `ButtonHTMLAttributes<HTMLButtonElement>` | AnchorHTMLAttributes<HTMLAnchorElement>` | | Additional action button props |

```jsx

Expand Down
31 changes: 31 additions & 0 deletions src/components/FilesGallery/FilesGallery.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@use '../variables';

$block: '.#{variables.$ns}files-gallery';

$filePreviewBlock: '.#{variables.$ns}file-preview';

#{$block} {
&__active-item-info {
align-self: center;
}

&__file-preview {
&[class] {
width: 100%;
height: 100%;
}

#{$filePreviewBlock}__card {
width: 100%;
min-width: 100%;
height: 100%;
padding: 0;
}

#{$filePreviewBlock}__image,
#{$filePreviewBlock}__icon {
width: 100%;
height: 100%;
}
}
}
73 changes: 73 additions & 0 deletions src/components/FilesGallery/FilesGallery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';

import {ChevronsCollapseUpRight, ChevronsExpandUpRight} from '@gravity-ui/icons';
import {ActionTooltip, Button, Icon} from '@gravity-ui/uikit';

import {Gallery, GalleryProps} from '../Gallery';
import {block} from '../utils/cn';

import {useFullScreen} from './hooks/useFullScreen';
import {i18n} from './i18n';
import {GalleryFileBase} from './types';
import {renderActiveItemInfo} from './utils/renderActiveItemInfo';
import {renderItemPreview} from './utils/renderItemPreview';

import './FilesGallery.scss';

export const cnFilesGallery = block('files-gallery');

export type FilesGalleryProps<GalleryFileType extends GalleryFileBase> = Omit<
GalleryProps<GalleryFileType>,
'fullScreen' | 'renderItemPreview' | 'renderActiveItemInfo'
> & {};

export const FilesGallery = <GalleryFileType extends GalleryFileBase>({
renderActions: providedRenderActions,
activeItemInfoClassName,
...galleryProps
}: FilesGalleryProps<GalleryFileType>) => {
const {fullScreen, handleSwitchFullScreenMode} = useFullScreen();

const renderActions = React.useCallback<
NonNullable<GalleryProps<GalleryFileType>['renderActions']>
>(
(item) => {
return (
<React.Fragment>
{providedRenderActions?.(item)}
<ActionTooltip
title={fullScreen ? i18n('exit-full-screen') : i18n('enter-full-screen')}
hotkey="Shift+F"
>
<Button
size="l"
view="flat"
extraProps={{
'aria-label': fullScreen
? i18n('exit-full-screen')
: i18n('enter-full-screen'),
}}
onClick={handleSwitchFullScreenMode}
>
<Icon
data={fullScreen ? ChevronsCollapseUpRight : ChevronsExpandUpRight}
/>
</Button>
</ActionTooltip>
</React.Fragment>
);
},
[fullScreen, handleSwitchFullScreenMode, providedRenderActions],
);

return (
<Gallery<GalleryFileType>
fullScreen={fullScreen}
renderItemPreview={renderItemPreview}
renderActions={renderActions}
renderActiveItemInfo={renderActiveItemInfo}
activeItemInfoClassName={cnFilesGallery('active-item-info', activeItemInfoClassName)}
{...galleryProps}
/>
);
};
126 changes: 126 additions & 0 deletions src/components/FilesGallery/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
## FilesGallery

The component for rendering file galleries.
The component is responsible for the gallery navigation (keyboard arrows, body side click and header arrow click).
You should provide the renderers for the body and file actions, for example the copy link action and the download action.

### PropTypes

| Property | Type | Required | Values | Default | Description |
| :---------------------- | :------------------------------------------------- | :------- | :----- | :------ | :---------------------------------------------------- |
| items | `(GalleryFileType extends GalleryFileBase)[]` | Yes | | | The gallery items list |
| initialItemIndex | `Number` | | | 0 | The initial active item index |
| open | `Boolean` | | | | The modal opened state |
| onClose | `() => void` | Yes | | | The modal close handler |
| renderActions | `(activeItem: GalleryFileType) => React.ReactNode` | | | | The gallery actions renderer, accepts the active item |
| renderBody | `(activeItem: GalleryFileType) => React.ReactNode` | Yes | | | The gallery body renderer, accepts the active item |
| modalClassName | `String` | | | | The modal class |
| className | `String` | | | | The modal content class |
| headerClassName | `String` | | | | The gallery header class |
| activeItemInfoClassName | `String` | | | | The active item info class name |
| footerClassName | `String` | | | | The gallery footer class |
| bodyClassName | `String` | | | | The gallery body class |

### Examples

```tsx
import {
FilesGallery,
FilesGalleryFallbackText,
ImageFileView,
VideoFileView,
DocumentFileView,
} from '@gravity-ui/components';

const FilesGalleryShowcase = () => {
const [open, setOpen] = React.useState(false);

const container = usePortalContainer();

const handleClose = React.useCallback(() => {
setOpen(false);
}, []);

const handleOpen = React.useCallback(() => {
setOpen(true);
}, []);

const renderBody = React.useCallback((activeFile: GalleryFile) => {
switch (activeFile.type) {
case 'image': {
return <ImageFileView src={activeFile.url} />;
}
case 'video': {
return <VideoFileView src={activeFile.url} />;
}
case 'document': {
return <DocumentFileView name={activeFile.data.name} src={activeFile.url} />;
}
case 'text': {
return <Text variant="body-1">{activeFile.text}</Text>;
}
default: {
return <FilesGalleryFallbackText />;
}
}
}, []);

const renderActions = React.useCallback((activeFile: GalleryFile) => {
return (
<React.Fragment>
<CopyToClipboard text={'url' in activeFile ? activeFile.url : activeFile.text}>
{() => (
<div>
<ActionTooltip title="Copy link">
<Button
size="l"
view="flat"
extraProps={{
'aria-label': 'Copy link',
}}
>
<Icon data={Link} />
</Button>
</ActionTooltip>
</div>
)}
</CopyToClipboard>
{'url' in activeFile && (
<ActionTooltip title="Download">
<Button
size="l"
view="flat"
extraProps={{
'aria-label': 'download',
}}
onClick={(event) => event.stopPropagation()}
href={`${activeFile.url}?inline=false`}
target="_blank"
rel="noreferrer"
>
<Icon data={ArrowDownToLine} />
</Button>
</ActionTooltip>
)}
</React.Fragment>
);
}, []);

return (
<React.Fragment>
<Button onClick={handleOpen} view="action" size="l">
Open gallery
</Button>
<FilesGallery<GalleryFile>
theme="dark"
open={open}
onClose={handleClose}
container={container || undefined}
items={files}
renderBody={renderBody}
renderActions={renderActions}
/>
</React.Fragment>
);
};
```
Loading

0 comments on commit 8c07955

Please sign in to comment.