From 1621520cc23d8f107105e4f5083f2c79f4356d79 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Wed, 10 Jul 2024 11:21:17 +0300 Subject: [PATCH 01/11] feat(ImageBasePositionedComponent): add subcomponent to positioning component in Image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавил сабкомпонент ImageBasePositionedComponent для абсолютного позиционирования компонентов в компоненте Image. - Добавил тесты для компонента - Добавил стори для компонента - Добавил использование компонента в документацию --- packages/vkui/src/components/Image/Image.tsx | 4 + packages/vkui/src/components/Image/Readme.md | 83 ++++++++++++++++++ .../src/components/ImageBase/ImageBase.tsx | 10 ++- .../ImageBasePositionedComponent.module.css | 9 ++ .../ImageBasePositionedComponent.stories.tsx | 72 ++++++++++++++++ .../ImageBasePositionedComponent.test.tsx | 71 +++++++++++++++ .../ImageBasePositionedComponent.tsx | 86 +++++++++++++++++++ .../vkui/src/components/ImageBase/context.ts | 3 + .../vkui/src/components/ImageBase/types.ts | 1 + packages/vkui/src/styles/constants.css | 1 + 10 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 packages/vkui/src/components/ImageBase/ImageBasePositionedComponent/ImageBasePositionedComponent.module.css create mode 100644 packages/vkui/src/components/ImageBase/ImageBasePositionedComponent/ImageBasePositionedComponent.stories.tsx create mode 100644 packages/vkui/src/components/ImageBase/ImageBasePositionedComponent/ImageBasePositionedComponent.test.tsx create mode 100644 packages/vkui/src/components/ImageBase/ImageBasePositionedComponent/ImageBasePositionedComponent.tsx diff --git a/packages/vkui/src/components/Image/Image.tsx b/packages/vkui/src/components/Image/Image.tsx index 6b0c911d70..f29512725a 100644 --- a/packages/vkui/src/components/Image/Image.tsx +++ b/packages/vkui/src/components/Image/Image.tsx @@ -75,6 +75,7 @@ const getBorderRadiusBySize = ( export const Image: React.FC & { Badge: typeof ImageBadge; Overlay: typeof ImageBase.Overlay; + PositionedComponent: typeof ImageBase.PositionedComponent; } = ({ size = IMAGE_DEFAULT_SIZE, borderRadius: borderRadiusProp = 'm', @@ -106,3 +107,6 @@ Image.Badge.displayName = 'Image.Badge'; Image.Overlay = ImageBase.Overlay; Image.Overlay.displayName = 'Image.Overlay'; + +Image.PositionedComponent = ImageBase.PositionedComponent; +Image.PositionedComponent.displayName = 'Image.PositionedComponent'; diff --git a/packages/vkui/src/components/Image/Readme.md b/packages/vkui/src/components/Image/Readme.md index cefdf68b50..394f9aa4d1 100644 --- a/packages/vkui/src/components/Image/Readme.md +++ b/packages/vkui/src/components/Image/Readme.md @@ -55,6 +55,53 @@ const OthersFeatures = () => { ); }; +const WithPositionedComponents = () => { + const [showContextMenu, setShowContextMenu] = useState(true); + const [contextMenuOpened, setContextMenuOpened] = useState(false); + const [contextMenuVisibility, setContextMenuVisibility] = useState('on-image-hover'); + + return ( + C позиционированными компонентами}> + + + setShowContextMenu(e.target.checked)} + > + Показать контекстное меню + + + + ; + visibility?: 'always' | 'on-hover'; } export const ImageBaseFloatElement = ({ position, visibility = 'always', style, - containerRef, className, horizontalIndent, verticalIndent, @@ -116,15 +110,15 @@ export const ImageBaseFloatElement = ({ useIsomorphicLayoutEffect( function resetHidden() { - setHidden(visibility === 'on-image-hover'); + setHidden(visibility === 'on-hover'); }, [visibility], ); React.useEffect( function updateHiddenSubscribe() { - const wrapper = (containerRef && containerRef.current) || imageWrapperRef.current; - if (wrapper && visibility === 'on-image-hover') { + const wrapper = imageWrapperRef.current; + if (wrapper && visibility === 'on-hover') { const onMouseOver = () => setHidden(false); const onMouseOut = () => setHidden(true); @@ -138,7 +132,7 @@ export const ImageBaseFloatElement = ({ } return; }, - [visibility, imageWrapperRef, containerRef], + [visibility, imageWrapperRef], ); const positionStyle = React.useMemo( From 1a15e9ad089b06df05d64ee4c202efe8979b150e Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Tue, 13 Aug 2024 11:19:55 +0300 Subject: [PATCH 10/11] fix(ImageBaseFloatElement): fix tests --- .../ImageBaseFloatElement.test.tsx | 42 +------------------ 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.test.tsx b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.test.tsx index 99d056226f..21a4696927 100644 --- a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.test.tsx +++ b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.test.tsx @@ -54,7 +54,7 @@ describe(ImageBaseFloatElement, () => { Приложение шторм онлайн { expect(screen.getByTestId('component')).toHaveClass(styles['FloatElement--hidden']); }); - it('check visibility on image hover without use Image', async () => { - const containerRef: React.RefObject = { - current: null, - }; - render( - <> -
- -
- , - ); - - expect(screen.getByTestId('component')).toHaveClass(styles['FloatElement--hidden']); - - fireEvent( - containerRef.current!, - new MouseEvent('mouseover', { - bubbles: true, - cancelable: true, - }), - ); - - expect(screen.getByTestId('component')).not.toHaveClass(styles['FloatElement--hidden']); - - fireEvent( - containerRef.current!, - new MouseEvent('mouseout', { - bubbles: true, - cancelable: true, - }), - ); - - expect(screen.getByTestId('component')).toHaveClass(styles['FloatElement--hidden']); - }); - const placementFixtures = Object.entries({ 'top-start': styles['FloatElement--position-topStart'], 'top': styles['FloatElement--position-top'], From 57a775313c36a8ba0850f13d883fb621b04b52c9 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Tue, 27 Aug 2024 11:00:09 +0300 Subject: [PATCH 11/11] fix(ImageBaseFloatElement): rename props vertical and horizontal indent to block and inline --- packages/vkui/src/components/Image/Readme.md | 4 +- .../ImageBaseFloatElement.module.css | 36 +++---- .../ImageBaseFloatElement.stories.tsx | 4 +- .../ImageBaseFloatElement.test.tsx | 54 +++++------ .../ImageBaseFloatElement.tsx | 97 +++++++++---------- .../ImageBaseFloatElement/helpers.ts | 2 +- 6 files changed, 93 insertions(+), 104 deletions(-) diff --git a/packages/vkui/src/components/Image/Readme.md b/packages/vkui/src/components/Image/Readme.md index 62315cae7e..3ff2576508 100644 --- a/packages/vkui/src/components/Image/Readme.md +++ b/packages/vkui/src/components/Image/Readme.md @@ -112,8 +112,8 @@ const WithFloatElements = () => { {showContextMenu && ( diff --git a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.module.css b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.module.css index ecb48bfaf3..4a937ca00d 100644 --- a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.module.css +++ b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.module.css @@ -11,75 +11,75 @@ --vkui_internal--FloatElement_vertical_indent: 0; } -.FloatElement--horizontalIndent-2xs { +.FloatElement--inlineIndent-2xs { --vkui_internal--FloatElement_horizontal_indent: var(--vkui--spacing_size_2xs); } -.FloatElement--horizontalIndent-xs { +.FloatElement--inlineIndent-xs { --vkui_internal--FloatElement_horizontal_indent: var(--vkui--spacing_size_xs); } -.FloatElement--horizontalIndent-s { +.FloatElement--inlineIndent-s { --vkui_internal--FloatElement_horizontal_indent: var(--vkui--spacing_size_s); } -.FloatElement--horizontalIndent-m { +.FloatElement--inlineIndent-m { --vkui_internal--FloatElement_horizontal_indent: var(--vkui--spacing_size_m); } -.FloatElement--horizontalIndent-l { +.FloatElement--inlineIndent-l { --vkui_internal--FloatElement_horizontal_indent: var(--vkui--spacing_size_l); } -.FloatElement--horizontalIndent-xl { +.FloatElement--inlineIndent-xl { --vkui_internal--FloatElement_horizontal_indent: var(--vkui--spacing_size_xl); } -.FloatElement--horizontalIndent-2xl { +.FloatElement--inlineIndent-2xl { --vkui_internal--FloatElement_horizontal_indent: var(--vkui--spacing_size_2xl); } -.FloatElement--horizontalIndent-3xl { +.FloatElement--inlineIndent-3xl { --vkui_internal--FloatElement_horizontal_indent: var(--vkui--spacing_size_3xl); } -.FloatElement--horizontalIndent-4xl { +.FloatElement--inlineIndent-4xl { --vkui_internal--FloatElement_horizontal_indent: var(--vkui--spacing_size_4xl); } -.FloatElement--verticalIndent-2xs { +.FloatElement--blockIndent-2xs { --vkui_internal--FloatElement_vertical_indent: var(--vkui--spacing_size_2xs); } -.FloatElement--verticalIndent-xs { +.FloatElement--blockIndent-xs { --vkui_internal--FloatElement_vertical_indent: var(--vkui--spacing_size_xs); } -.FloatElement--verticalIndent-s { +.FloatElement--blockIndent-s { --vkui_internal--FloatElement_vertical_indent: var(--vkui--spacing_size_s); } -.FloatElement--verticalIndent-m { +.FloatElement--blockIndent-m { --vkui_internal--FloatElement_vertical_indent: var(--vkui--spacing_size_m); } -.FloatElement--verticalIndent-l { +.FloatElement--blockIndent-l { --vkui_internal--FloatElement_vertical_indent: var(--vkui--spacing_size_l); } -.FloatElement--verticalIndent-xl { +.FloatElement--blockIndent-xl { --vkui_internal--FloatElement_vertical_indent: var(--vkui--spacing_size_xl); } -.FloatElement--verticalIndent-2xl { +.FloatElement--blockIndent-2xl { --vkui_internal--FloatElement_vertical_indent: var(--vkui--spacing_size_2xl); } -.FloatElement--verticalIndent-3xl { +.FloatElement--blockIndent-3xl { --vkui_internal--FloatElement_vertical_indent: var(--vkui--spacing_size_3xl); } -.FloatElement--verticalIndent-4xl { +.FloatElement--blockIndent-4xl { --vkui_internal--FloatElement_vertical_indent: var(--vkui--spacing_size_4xl); } diff --git a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.stories.tsx b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.stories.tsx index 3c3f637703..a7cce7fea4 100644 --- a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.stories.tsx +++ b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.stories.tsx @@ -1,4 +1,4 @@ -import { Meta, StoryObj } from '@storybook/react'; +import { type Meta, type StoryObj } from '@storybook/react'; import { Icon16MoreHorizontal, Icon28AddOutline, Icon28DeleteOutline } from '@vkontakte/icons'; import { CanvasFullLayout, DisableCartesianParam } from '../../../storybook/constants'; import { getAvatarUrl } from '../../../testing/mock'; @@ -6,7 +6,7 @@ import { Button } from '../../Button/Button'; import { CellButton } from '../../CellButton/CellButton'; import { Popover } from '../../Popover/Popover'; import { ImageBase } from '../ImageBase'; -import { ImageBaseFloatElement, ImageBaseFloatElementProps } from './ImageBaseFloatElement'; +import { ImageBaseFloatElement, type ImageBaseFloatElementProps } from './ImageBaseFloatElement'; const ContextMenu = () => { return ( diff --git a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.test.tsx b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.test.tsx index 21a4696927..940958ab00 100644 --- a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.test.tsx +++ b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.test.tsx @@ -2,8 +2,8 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { baselineComponent } from '../../../testing/utils'; import { Image } from '../../Image/Image'; import { - FloatElementIndentation, - FloatElementPlacement, + type FloatElementIndentation, + type FloatElementPlacement, ImageBaseFloatElement, } from './ImageBaseFloatElement'; import styles from './ImageBaseFloatElement.module.css'; @@ -110,15 +110,15 @@ describe(ImageBaseFloatElement, () => { }); const horizontalIndentationFixtures = Object.entries({ - '2xs': styles['FloatElement--horizontalIndent-2xs'], - 'xs': styles['FloatElement--horizontalIndent-xs'], - 's': styles['FloatElement--horizontalIndent-s'], - 'm': styles['FloatElement--horizontalIndent-m'], - 'l': styles['FloatElement--horizontalIndent-l'], - 'xl': styles['FloatElement--horizontalIndent-xl'], - '2xl': styles['FloatElement--horizontalIndent-2xl'], - '3xl': styles['FloatElement--horizontalIndent-3xl'], - '4xl': styles['FloatElement--horizontalIndent-4xl'], + '2xs': styles['FloatElement--inlineIndent-2xs'], + 'xs': styles['FloatElement--inlineIndent-xs'], + 's': styles['FloatElement--inlineIndent-s'], + 'm': styles['FloatElement--inlineIndent-m'], + 'l': styles['FloatElement--inlineIndent-l'], + 'xl': styles['FloatElement--inlineIndent-xl'], + '2xl': styles['FloatElement--inlineIndent-2xl'], + '3xl': styles['FloatElement--inlineIndent-3xl'], + '4xl': styles['FloatElement--inlineIndent-4xl'], }).map(([indent, className]) => ({ indent: indent as Exclude, className, @@ -128,22 +128,22 @@ describe(ImageBaseFloatElement, () => { 'should have horizontal indentation className %j', ({ indent, className }) => { render( - , + , ); expect(screen.getByTestId('component')).toHaveClass(className); }, ); const verticalIndentationFixtures = Object.entries({ - '2xs': styles['FloatElement--verticalIndent-2xs'], - 'xs': styles['FloatElement--verticalIndent-xs'], - 's': styles['FloatElement--verticalIndent-s'], - 'm': styles['FloatElement--verticalIndent-m'], - 'l': styles['FloatElement--verticalIndent-l'], - 'xl': styles['FloatElement--verticalIndent-xl'], - '2xl': styles['FloatElement--verticalIndent-2xl'], - '3xl': styles['FloatElement--verticalIndent-3xl'], - '4xl': styles['FloatElement--verticalIndent-4xl'], + '2xs': styles['FloatElement--blockIndent-2xs'], + 'xs': styles['FloatElement--blockIndent-xs'], + 's': styles['FloatElement--blockIndent-s'], + 'm': styles['FloatElement--blockIndent-m'], + 'l': styles['FloatElement--blockIndent-l'], + 'xl': styles['FloatElement--blockIndent-xl'], + '2xl': styles['FloatElement--blockIndent-2xl'], + '3xl': styles['FloatElement--blockIndent-3xl'], + '4xl': styles['FloatElement--blockIndent-4xl'], }).map(([indent, className]) => ({ indent: indent as Exclude, className, @@ -152,9 +152,7 @@ describe(ImageBaseFloatElement, () => { it.each(verticalIndentationFixtures)( 'should have vertical indentation className %j', ({ indent, className }) => { - render( - , - ); + render(); expect(screen.getByTestId('component')).toHaveClass(className); }, ); @@ -164,8 +162,8 @@ describe(ImageBaseFloatElement, () => { , ); expect( @@ -186,8 +184,8 @@ describe(ImageBaseFloatElement, () => { , ); expect( diff --git a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.tsx b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.tsx index 2aab878ec0..0334ab90d0 100644 --- a/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.tsx +++ b/packages/vkui/src/components/ImageBase/ImageBaseFloatElement/ImageBaseFloatElement.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; import { useIsomorphicLayoutEffect } from '../../../lib/useIsomorphicLayoutEffect'; -import { HTMLAttributesWithRootRef } from '../../../types'; +import { type HTMLAttributesWithRootRef } from '../../../types'; import { RootComponent } from '../../RootComponent/RootComponent'; import { ImageBaseContext } from '../context'; import { resolveIndent } from './helpers'; @@ -50,28 +50,28 @@ const positionPlacementClassNames = { 'middle-end': styles['FloatElement--position-middleEnd'], }; -const horizontalIndentClassNames = { - '2xs': styles['FloatElement--horizontalIndent-2xs'], - 'xs': styles['FloatElement--horizontalIndent-xs'], - 's': styles['FloatElement--horizontalIndent-s'], - 'm': styles['FloatElement--horizontalIndent-m'], - 'l': styles['FloatElement--horizontalIndent-l'], - 'xl': styles['FloatElement--horizontalIndent-xl'], - '2xl': styles['FloatElement--horizontalIndent-2xl'], - '3xl': styles['FloatElement--horizontalIndent-3xl'], - '4xl': styles['FloatElement--horizontalIndent-4xl'], +const inlineIndentClassNames = { + '2xs': styles['FloatElement--inlineIndent-2xs'], + 'xs': styles['FloatElement--inlineIndent-xs'], + 's': styles['FloatElement--inlineIndent-s'], + 'm': styles['FloatElement--inlineIndent-m'], + 'l': styles['FloatElement--inlineIndent-l'], + 'xl': styles['FloatElement--inlineIndent-xl'], + '2xl': styles['FloatElement--inlineIndent-2xl'], + '3xl': styles['FloatElement--inlineIndent-3xl'], + '4xl': styles['FloatElement--inlineIndent-4xl'], }; -const verticalIndentClassNames = { - '2xs': styles['FloatElement--verticalIndent-2xs'], - 'xs': styles['FloatElement--verticalIndent-xs'], - 's': styles['FloatElement--verticalIndent-s'], - 'm': styles['FloatElement--verticalIndent-m'], - 'l': styles['FloatElement--verticalIndent-l'], - 'xl': styles['FloatElement--verticalIndent-xl'], - '2xl': styles['FloatElement--verticalIndent-2xl'], - '3xl': styles['FloatElement--verticalIndent-3xl'], - '4xl': styles['FloatElement--verticalIndent-4xl'], +const blockIndentClassNames = { + '2xs': styles['FloatElement--blockIndent-2xs'], + 'xs': styles['FloatElement--blockIndent-xs'], + 's': styles['FloatElement--blockIndent-s'], + 'm': styles['FloatElement--blockIndent-m'], + 'l': styles['FloatElement--blockIndent-l'], + 'xl': styles['FloatElement--blockIndent-xl'], + '2xl': styles['FloatElement--blockIndent-2xl'], + '3xl': styles['FloatElement--blockIndent-3xl'], + '4xl': styles['FloatElement--blockIndent-4xl'], }; export interface ImageBaseFloatElementProps extends HTMLAttributesWithRootRef { @@ -82,11 +82,11 @@ export interface ImageBaseFloatElementProps extends HTMLAttributesWithRootRef { const [hidden, setHidden] = React.useState(visibility !== 'always'); @@ -148,30 +148,21 @@ export const ImageBaseFloatElement = ({ [position], ); - const [ - horizontalIndentStyle, - verticalIndentStyle, - horizontalIndentClassName, - verticalIndentClassName, - ] = React.useMemo(() => { - const [horizontalIndentStyle, horizontalIndentClassName] = resolveIndent( - horizontalIndent, - '--vkui_internal--FloatElement_horizontal_indent', - horizontalIndentClassNames, - ); - const [verticalIndentStyle, verticalIndentClassName] = resolveIndent( - verticalIndent, - '--vkui_internal--FloatElement_vertical_indent', - verticalIndentClassNames, - ); - - return [ - horizontalIndentStyle, - verticalIndentStyle, - horizontalIndentClassName, - verticalIndentClassName, - ]; - }, [horizontalIndent, verticalIndent]); + const [inlineIndentStyle, blockIndentStyle, inlineIndentClassName, blockIndentClassName] = + React.useMemo(() => { + const [inlineIndentStyle, inlineIndentClassName] = resolveIndent( + inlineIndent, + '--vkui_internal--FloatElement_horizontal_indent', + inlineIndentClassNames, + ); + const [blockIndentStyle, blockIndentClassName] = resolveIndent( + blockIndent, + '--vkui_internal--FloatElement_vertical_indent', + blockIndentClassNames, + ); + + return [inlineIndentStyle, blockIndentStyle, inlineIndentClassName, blockIndentClassName]; + }, [inlineIndent, blockIndent]); return (