From b87b3de2dc5de55fdcecac060070b48c298314ef Mon Sep 17 00:00:00 2001 From: leutinatasya Date: Thu, 19 Dec 2024 11:32:00 +0300 Subject: [PATCH] feat(FilePreview): support mobile menu --- src/components/FilePreview/FilePreview.scss | 42 ++---- src/components/FilePreview/FilePreview.tsx | 27 ++-- .../FilePreview/FilePreviewAction.tsx | 48 ------- .../FilePreviewActions.scss | 61 +++++++++ .../FilePreviewActions/FilePreviewActions.tsx | 129 ++++++++++++++++++ .../MobileImagePreview/MobileImagePreview.tsx | 14 +- src/components/FilePreview/types.ts | 19 +++ 7 files changed, 242 insertions(+), 98 deletions(-) delete mode 100644 src/components/FilePreview/FilePreviewAction.tsx create mode 100644 src/components/FilePreview/FilePreviewActions/FilePreviewActions.scss create mode 100644 src/components/FilePreview/FilePreviewActions/FilePreviewActions.tsx diff --git a/src/components/FilePreview/FilePreview.scss b/src/components/FilePreview/FilePreview.scss index 10c9a9e7c..c7a5ecdef 100644 --- a/src/components/FilePreview/FilePreview.scss +++ b/src/components/FilePreview/FilePreview.scss @@ -3,40 +3,24 @@ $block: '.#{variables.$ns}file-preview'; -$smallRoundedButtonSize: 24px; - #{$block} { - --_-box-shadow: none; - --_-border-radius: 4px; - --_-color-base-background: transparent; + --_--box-shadow: none; + --_--border-radius: 4px; + --_--color-base-background: transparent; position: relative; width: 120px; - &:hover, - &:focus-within { - #{$block}__actions:not(#{$block}__actions_hide) { - opacity: 1; - } - - --_-color-base-background: var(--g-color-base-simple-hover, rgba(0, 0, 0, 0.05)); - } - - &__actions { - position: absolute; - inset-block-start: -1 * math.div($smallRoundedButtonSize, 2); - inset-inline-end: -1 * math.div($smallRoundedButtonSize, 2); - z-index: 1; - - display: flex; - gap: 4px; - - opacity: 0; + &:not(#{$block}_mobile):focus-within, + &:not(#{$block}_mobile):hover { + --_--color-base-background: var(--g-color-base-simple-hover); } &:hover { - --_-color-base-background: var(--g-color-base-simple-hover); + #{$block}__actions-panel { + opacity: 1; + } } &__card { @@ -48,7 +32,7 @@ $smallRoundedButtonSize: 24px; outline: none; box-shadow: var(--gc-card-box-shadow); - border-radius: var(--_-border-radius); + border-radius: var(--_--border-radius); padding: 4px 10px; &_clickable { @@ -56,18 +40,18 @@ $smallRoundedButtonSize: 24px; } &_hoverable { - background-color: var(--_-color-base-background); + background-color: var(--_--color-base-background); } &::after { position: absolute; inset: 0; - border-radius: var(--_-border-radius); + border-radius: var(--_--border-radius); pointer-events: none; } &:hover { - --_-box-shadow: 0px 3px 10px var(--g-color-sfx-shadow); + --_--box-shadow: 0px 3px 10px var(--g-color-sfx-shadow); } &:focus::after { diff --git a/src/components/FilePreview/FilePreview.tsx b/src/components/FilePreview/FilePreview.tsx index 563387533..16fa9f5c7 100644 --- a/src/components/FilePreview/FilePreview.tsx +++ b/src/components/FilePreview/FilePreview.tsx @@ -12,7 +12,7 @@ import { Filmstrip as VideoIcon, } from '@gravity-ui/icons'; -import {useActionHandlers, useUniqId} from '../../hooks'; +import {useActionHandlers} from '../../hooks'; import {useBoolean} from '../../hooks/private'; import {Icon} from '../Icon'; import type {IconData} from '../Icon'; @@ -21,10 +21,9 @@ import {useMobile} from '../mobile'; import type {QAProps} from '../types'; import {block} from '../utils/cn'; -import {FilePreviewAction} from './FilePreviewAction'; -import type {FilePreviewActionProps} from './FilePreviewAction'; +import {FilePreviewActions} from './FilePreviewActions/FilePreviewActions'; import {MobileImagePreview} from './MobileImagePreview/MobileImagePreview'; -import type {FileType} from './types'; +import type {FilePreviewActionProps, FileType} from './types'; import {getFileType} from './utils'; import './FilePreview.scss'; @@ -63,8 +62,6 @@ export function FilePreview({ onClick, actions, }: FilePreviewProps) { - const id = useUniqId(); - const [previewSrc, setPreviewSrc] = React.useState(imageSrc); const [isPreviewSheetVisible, showPreviewSheet, closePreviewSheet] = useBoolean(false); const mobile = useMobile(); @@ -92,7 +89,6 @@ export function FilePreview({ const withActions = Boolean(actions?.length); const isPreviewString = typeof previewSrc === 'string'; - const hideActions = isPreviewString && mobile; const handleClick: React.MouseEventHandler = React.useCallback( (e) => { @@ -140,17 +136,12 @@ export function FilePreview({ )} - {actions?.length ? ( -
- {actions.map((action, index) => ( - - ))} -
- ) : null} + + ; - extraProps?: - | React.ButtonHTMLAttributes - | React.AnchorHTMLAttributes; - tooltipExtraProps?: Omit; -} - -export function FilePreviewAction({ - id, - icon, - title, - href, - disabled, - onClick, - extraProps, - tooltipExtraProps, -}: FilePreviewActionProps) { - return ( - - - - ); -} - -FilePreviewAction.displayName = 'FilePreviewAction'; diff --git a/src/components/FilePreview/FilePreviewActions/FilePreviewActions.scss b/src/components/FilePreview/FilePreviewActions/FilePreviewActions.scss new file mode 100644 index 000000000..9e1d0eba2 --- /dev/null +++ b/src/components/FilePreview/FilePreviewActions/FilePreviewActions.scss @@ -0,0 +1,61 @@ +@use 'sass:math'; +@use '../../variables'; +@use '../../../../styles/mixins'; + +$block: '.#{variables.$ns}file-preview-actions'; + +#{$block} { + --_--mobile-icon-size: 18px; + --_--mobile-actions-menu-size: 28px; + --_--small-rounded-button-size: 24px; + + &__round-actions, + &__mobile-actions-menu { + position: absolute; + z-index: 1; + display: flex; + gap: var(--g-spacing-1); + } + + &__mobile-actions-menu { + height: var(--_--mobile-actions-menu-size); + width: var(--_--mobile-actions-menu-size); + } + + &__round-actions { + inset-block-start: -1 * math.div(var(--_--small-rounded-button-size), 2); + inset-inline-end: -1 * math.div(var(--_--small-rounded-button-size), 2); + opacity: 0; + } + + &__round-button { + width: var(--_--small-rounded-button-size); + height: var(--_--small-rounded-button-size); + justify-content: center; + align-items: center; + } + + &__icon { + display: flex; + } + + &__mobile-list-item { + display: flex; + gap: var(--g-spacing-2); + align-items: center; + padding: 0 var(--g-spacing-2); + + & > svg { + width: var(--_--mobile-icon-size); + height: var(--_--mobile-icon-size); + } + } + + &__mobile-sheet { + --g-sheet-content-padding: var(--g-spacing-1) var(--g-spacing-2); + } + + &__mobile-button-icon { + vertical-align: sub; + } +} diff --git a/src/components/FilePreview/FilePreviewActions/FilePreviewActions.tsx b/src/components/FilePreview/FilePreviewActions/FilePreviewActions.tsx new file mode 100644 index 000000000..c10c279e0 --- /dev/null +++ b/src/components/FilePreview/FilePreviewActions/FilePreviewActions.tsx @@ -0,0 +1,129 @@ +import React from 'react'; + +import {EllipsisVertical} from '@gravity-ui/icons'; + +import {useUniqId} from '../../../hooks/useUniqId'; +import {ActionTooltip} from '../../ActionTooltip'; +import {Button} from '../../Button'; +import {List} from '../../List'; +import type {ListProps} from '../../List'; +import {Sheet} from '../../Sheet'; +import {Text} from '../../Text'; +import {useMobile} from '../../mobile'; +import {block} from '../../utils/cn'; +import type {FilePreviewActionProps} from '../types'; + +import './FilePreviewActions.scss'; + +const cn = block('file-preview-actions'); + +export interface FilePreviewActionsProps { + actions?: FilePreviewActionProps[]; + hoverabelPanelClassName: string; + fileName: string; +} + +const renderListItem = (item: FilePreviewActionProps) => { + return ( +
+ {item.icon} + + {item.title} + +
+ ); +}; + +export const FilePreviewActions = ({ + actions, + fileName, + hoverabelPanelClassName, +}: FilePreviewActionsProps) => { + const id = useUniqId(); + const mobile = useMobile(); + + const [showMobileMenu, setShowMobileMenu] = React.useState(false); + + const handleMobileMenuClose = React.useCallback(() => { + setShowMobileMenu(false); + }, []); + + const handleItemClick = React.useCallback< + NonNullable['onItemClick']> + >((item, _, __, event) => { + if (event) { + item.onClick?.(event); + } + setShowMobileMenu(false); + }, []); + + if (!actions?.length) { + return null; + } + + const handleMobileButtonClick = () => { + setShowMobileMenu(true); + }; + + if (mobile) { + return ( + + + + + + + ); + } + + return ( +
+ {actions.map( + ({title, icon, onClick, href, disabled, extraProps, tooltipExtraProps}, index) => ( + + + + ), + )} +
+ ); +}; diff --git a/src/components/FilePreview/MobileImagePreview/MobileImagePreview.tsx b/src/components/FilePreview/MobileImagePreview/MobileImagePreview.tsx index 76c837bf3..f1e126f00 100644 --- a/src/components/FilePreview/MobileImagePreview/MobileImagePreview.tsx +++ b/src/components/FilePreview/MobileImagePreview/MobileImagePreview.tsx @@ -2,12 +2,13 @@ import React from 'react'; import {ArrowLeft as ArrowLeftIcon} from '@gravity-ui/icons'; +import {useUniqId} from '../../../hooks/useUniqId'; import {Button} from '../../Button'; import {Icon} from '../../Icon'; import {Sheet} from '../../Sheet'; import {block} from '../../utils/cn'; -import type {FilePreviewActionProps} from '../FilePreviewAction'; import i18n from '../i18n'; +import type {FilePreviewActionProps} from '../types'; import './MobileImagePreview.scss'; @@ -28,6 +29,8 @@ export function MobileImagePreview({ actions, fileName, }: FilePreviewProps) { + const id = useUniqId(); + const [showError, setShowError] = React.useState(false); const showSheet = Boolean(previewSrc && visible); @@ -52,8 +55,13 @@ export function MobileImagePreview({
- {actions?.map((action) => ( - ))} diff --git a/src/components/FilePreview/types.ts b/src/components/FilePreview/types.ts index d92b862a4..8c078e21e 100644 --- a/src/components/FilePreview/types.ts +++ b/src/components/FilePreview/types.ts @@ -1,3 +1,5 @@ +import type {ActionTooltipProps} from '../ActionTooltip'; + export const FILE_TYPES = [ 'default', 'image', @@ -11,3 +13,20 @@ export const FILE_TYPES = [ ] as const; export type FileType = (typeof FILE_TYPES)[number]; + +export interface FilePreviewActionProps { + id?: string; + icon: React.ReactNode; + title: string; + href?: string; + disabled?: boolean; + onClick?: ( + event: + | React.MouseEvent + | React.KeyboardEvent, + ) => void; + extraProps?: + | React.ButtonHTMLAttributes + | React.AnchorHTMLAttributes; + tooltipExtraProps?: Omit; +}