Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ImageBasePositionedComponent): add subcomponent to positioning component in Image #7166

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1621520
feat(ImageBasePositionedComponent): add subcomponent to positioning c…
EldarMuhamethanov Jul 10, 2024
9844cbd
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Jul 10, 2024
2bdca6d
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Jul 16, 2024
07b86ea
feat(ImageBasePositionedComponent): add placement to position component
EldarMuhamethanov Jul 16, 2024
029571d
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Jul 16, 2024
5aba85c
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Jul 17, 2024
0b0237f
fix(ImageBasePositionedComponent): add horizontal and vertical indent…
EldarMuhamethanov Jul 17, 2024
82269e2
feat(ImageBasePositionedComponent): add 2xs and 4xl size of indent
EldarMuhamethanov Jul 17, 2024
a23447e
fix(ImageBasePositionedComponent): rewrite calculate indent logic
EldarMuhamethanov Jul 21, 2024
f03330d
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Jul 22, 2024
47a41a9
fix(ImageBaseFloatElement): rename component
EldarMuhamethanov Jul 22, 2024
ebf6926
fix(ImageBaseFloatElement): fix test
EldarMuhamethanov Jul 22, 2024
de6a7e0
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Aug 7, 2024
2f3ec3c
fix: rename PositionedComponent to FloatElement
EldarMuhamethanov Aug 8, 2024
3cba881
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Aug 12, 2024
9cee169
fix(ImageBaseFloatElement): remove containerRef props and rename 'on-…
EldarMuhamethanov Aug 13, 2024
1a15e9a
fix(ImageBaseFloatElement): fix tests
EldarMuhamethanov Aug 13, 2024
0ce252b
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Aug 27, 2024
57a7753
fix(ImageBaseFloatElement): rename props vertical and horizontal inde…
EldarMuhamethanov Aug 27, 2024
cc7d574
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Sep 13, 2024
e7e5822
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Sep 16, 2024
537bc0e
Merge branch 'master' into e.muhamethanov/6924/image-overlay-components
EldarMuhamethanov Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/vkui/src/components/Image/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const getBorderRadiusBySizeInPx = (
export const Image: React.FC<ImageProps> & {
Badge: typeof ImageBadge;
Overlay: typeof ImageBase.Overlay;
PositionedComponent: typeof ImageBase.PositionedComponent;
} = ({
size = IMAGE_DEFAULT_SIZE,
borderRadius = 'm',
Expand Down Expand Up @@ -171,3 +172,6 @@ Image.Badge.displayName = 'Image.Badge';

Image.Overlay = ImageBase.Overlay;
Image.Overlay.displayName = 'Image.Overlay';

Image.PositionedComponent = ImageBase.PositionedComponent;
Image.PositionedComponent.displayName = 'Image.PositionedComponent';
82 changes: 82 additions & 0 deletions packages/vkui/src/components/Image/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,52 @@ const OthersFeatures = () => {
);
};

const WithPositionedComponents = () => {
const [showContextMenu, setShowContextMenu] = useState(true);
const [contextMenuOpened, setContextMenuOpened] = useState(false);
const [contextMenuVisibility, setContextMenuVisibility] = useState('on-image-hover');

return (
<Group header={<Header mode="secondary">C позиционированными компонентами</Header>}>
<FormLayoutGroup mode="horizontal">
<FormItem top="Контекстное меню">
<Checkbox
checked={showContextMenu}
onChange={(e) => setShowContextMenu(e.target.checked)}
>
Показать контекстное меню
</Checkbox>
</FormItem>
<FormItem top="Контекстное меню">
<Select
options={[
{ label: 'Всегда', value: 'always' },
{ label: 'При наведении на картинку', value: 'on-image-hover' },
]}
value={contextMenuVisibility}
disabled={!showContextMenu}
onChange={(e) => setContextMenuVisibility(e.target.value)}
/>
</FormItem>
</FormLayoutGroup>
<Flex margin="auto" gap={'m'}>
<Image size={96} src={getAvatarUrl('app_shorm_online')} alt="Приложение шторм онлайн">
{showContextMenu && (
<Image.PositionedComponent
position="top-end"
horizontalIndent="l"
verticalIndent="l"
visibility={contextMenuOpened ? 'always' : contextMenuVisibility}
>
<ContextMenu onShownChange={setContextMenuOpened} />
</Image.PositionedComponent>
)}
</Image>
</Flex>
</Group>
);
};

const Example = () => {
return (
<View activePanel="avatar">
Expand All @@ -64,6 +110,8 @@ const Example = () => {
<Default />

<OthersFeatures />

<WithPositionedComponents />
</Panel>
</View>
);
Expand Down Expand Up @@ -182,5 +230,39 @@ const ImagePropsForm = ({ onBorderRadiusChange, onBadgeChange, onOverlayChange }
);
};

const ContextMenu = ({ onShownChange }) => {
return (
<Popover
noStyling
trigger="click"
role="dialog"
onShownChange={onShownChange}
content={({ onClose }) => (
<div
style={{
backgroundColor: 'var(--vkui--color_background_modal_inverse)',
borderRadius: 8,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.3)',
}}
>
<CellButton role="menuitem" before={<Icon28AddOutline />} onClick={onClose}>
Добавить
</CellButton>
<CellButton
role="menuitem"
before={<Icon28DeleteOutline />}
mode="danger"
onClick={onClose}
>
Удалить
</CellButton>
</div>
)}
>
<Button mode="primary" after={<Icon16MoreHorizontal />}></Button>
</Popover>
);
};

<Example />;
```
20 changes: 19 additions & 1 deletion packages/vkui/src/components/ImageBase/ImageBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import type { AnchorHTMLAttributesOnly, HasRef, HasRootRef, LiteralUnion } from
import { Clickable } from '../Clickable/Clickable';
import { ImageBaseBadge, type ImageBaseBadgeProps } from './ImageBaseBadge/ImageBaseBadge';
import { ImageBaseOverlay, type ImageBaseOverlayProps } from './ImageBaseOverlay/ImageBaseOverlay';
import {
ImageBasePositionedComponent,
type ImageBasePositionedComponentProps,
type PositionedComponentIndentation,
type PositionedComponentPlacement,
type PositionedComponentPosition,
} from './ImageBasePositionedComponent/ImageBasePositionedComponent';
import { ImageBaseContext } from './context';
import type { ImageBaseContextProps, ImageBaseExpectedIconProps, ImageBaseSize } from './types';
import { validateFallbackIcon, validateSize } from './validators';
Expand All @@ -18,6 +25,10 @@ export type {
ImageBaseBadgeProps,
ImageBaseOverlayProps,
ImageBaseContextProps,
ImageBasePositionedComponentProps,
PositionedComponentPlacement,
PositionedComponentPosition,
PositionedComponentIndentation,
};

export {
Expand Down Expand Up @@ -104,6 +115,7 @@ const getObjectFitClassName = (objectFit: React.CSSProperties['objectFit']) => {
export const ImageBase: React.FC<ImageBaseProps> & {
Badge: typeof ImageBaseBadge;
Overlay: typeof ImageBaseOverlay;
PositionedComponent: typeof ImageBasePositionedComponent;
} = ({
alt,
crossOrigin,
Expand All @@ -129,9 +141,11 @@ export const ImageBase: React.FC<ImageBaseProps> & {
onError,
withTransparentBackground,
objectFit = 'cover',
getRootRef,
...restProps
}: ImageBaseProps) => {
const size = sizeProp ?? minOr([widthSize, heightSize], defaultSize);
const wrapperRef = useExternRef(getRootRef);

const width = widthSize ?? size;
const height = heightSize ?? size;
Expand Down Expand Up @@ -185,14 +199,15 @@ export const ImageBase: React.FC<ImageBaseProps> & {
);

return (
<ImageBaseContext.Provider value={{ size }}>
<ImageBaseContext.Provider value={{ size, ref: wrapperRef }}>
<Clickable
style={{ width, height, ...style }}
baseClassName={classNames(
styles['ImageBase'],
loaded && styles['ImageBase--loaded'],
withTransparentBackground && styles['ImageBase--transparent-background'],
)}
getRootRef={wrapperRef}
{...restProps}
>
{hasSrc && (
Expand Down Expand Up @@ -230,3 +245,6 @@ ImageBase.Badge.displayName = 'ImageBase.Badge';

ImageBase.Overlay = ImageBaseOverlay;
ImageBase.Overlay.displayName = 'ImageBase.Overlay';

ImageBase.PositionedComponent = ImageBasePositionedComponent;
ImageBase.PositionedComponent.displayName = 'ImageBase.PositionedComponent';
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
.PositionedComponent {
position: absolute;
z-index: var(--vkui_internal--z_index_image_base_positioned_element);
transition: opacity 0.3s ease-in-out;
inset-inline-start: var(--vkui_internal--PositionedComponent_inset_inline_start, initial);
inset-inline-end: var(--vkui_internal--PositionedComponent_inset_inline_end, initial);
inset-block-start: var(--vkui_internal--PositionedComponent_inset_block_start, initial);
inset-block-end: var(--vkui_internal--PositionedComponent_inset_block_end, initial);

--vkui_internal--PositionedComponent_horizontal_indent: 0;
--vkui_internal--PositionedComponent_vertical_indent: 0;
}

.PositionedComponent--horizontalIndent-2xs {
--vkui_internal--PositionedComponent_horizontal_indent: var(--vkui--spacing_size_2xs);
}

.PositionedComponent--horizontalIndent-xs {
--vkui_internal--PositionedComponent_horizontal_indent: var(--vkui--spacing_size_xs);
}

.PositionedComponent--horizontalIndent-s {
--vkui_internal--PositionedComponent_horizontal_indent: var(--vkui--spacing_size_s);
}

.PositionedComponent--horizontalIndent-m {
--vkui_internal--PositionedComponent_horizontal_indent: var(--vkui--spacing_size_m);
}

.PositionedComponent--horizontalIndent-l {
--vkui_internal--PositionedComponent_horizontal_indent: var(--vkui--spacing_size_l);
}

.PositionedComponent--horizontalIndent-xl {
--vkui_internal--PositionedComponent_horizontal_indent: var(--vkui--spacing_size_xl);
}

.PositionedComponent--horizontalIndent-2xl {
--vkui_internal--PositionedComponent_horizontal_indent: var(--vkui--spacing_size_2xl);
}

.PositionedComponent--horizontalIndent-3xl {
--vkui_internal--PositionedComponent_horizontal_indent: var(--vkui--spacing_size_3xl);
}

.PositionedComponent--horizontalIndent-4xl {
--vkui_internal--PositionedComponent_horizontal_indent: var(--vkui--spacing_size_4xl);
}

.PositionedComponent--verticalIndent-2xs {
--vkui_internal--PositionedComponent_vertical_indent: var(--vkui--spacing_size_2xs);
}

.PositionedComponent--verticalIndent-xs {
--vkui_internal--PositionedComponent_vertical_indent: var(--vkui--spacing_size_xs);
}

.PositionedComponent--verticalIndent-s {
--vkui_internal--PositionedComponent_vertical_indent: var(--vkui--spacing_size_s);
}

.PositionedComponent--verticalIndent-m {
--vkui_internal--PositionedComponent_vertical_indent: var(--vkui--spacing_size_m);
}

.PositionedComponent--verticalIndent-l {
--vkui_internal--PositionedComponent_vertical_indent: var(--vkui--spacing_size_l);
}

.PositionedComponent--verticalIndent-xl {
--vkui_internal--PositionedComponent_vertical_indent: var(--vkui--spacing_size_xl);
}

.PositionedComponent--verticalIndent-2xl {
--vkui_internal--PositionedComponent_vertical_indent: var(--vkui--spacing_size_2xl);
}

.PositionedComponent--verticalIndent-3xl {
--vkui_internal--PositionedComponent_vertical_indent: var(--vkui--spacing_size_3xl);
}

.PositionedComponent--verticalIndent-4xl {
--vkui_internal--PositionedComponent_vertical_indent: var(--vkui--spacing_size_4xl);
}

.PositionedComponent--hidden {
opacity: 0;
}

.PositionedComponent--position-topStart {
inset-inline-start: var(--vkui_internal--PositionedComponent_horizontal_indent);
inset-block-start: var(--vkui_internal--PositionedComponent_vertical_indent);
}

.PositionedComponent--position-top {
inset-inline-start: 50%;
inset-block-start: var(--vkui_internal--PositionedComponent_vertical_indent);
transform: translateX(-50%);
}

.PositionedComponent--position-topEnd {
inset-inline-end: var(--vkui_internal--PositionedComponent_horizontal_indent);
inset-block-start: var(--vkui_internal--PositionedComponent_vertical_indent);
}

.PositionedComponent--position-bottomStart {
inset-inline-start: var(--vkui_internal--PositionedComponent_horizontal_indent);
inset-block-end: var(--vkui_internal--PositionedComponent_vertical_indent);
}

.PositionedComponent--position-bottom {
inset-inline-start: 50%;
inset-block-end: var(--vkui_internal--PositionedComponent_vertical_indent);
transform: translateX(-50%);
}

.PositionedComponent--position-bottomEnd {
inset-block-end: var(--vkui_internal--PositionedComponent_vertical_indent);
inset-inline-end: var(--vkui_internal--PositionedComponent_horizontal_indent);
}

.PositionedComponent--position-middleStart {
inset-inline-start: var(--vkui_internal--PositionedComponent_horizontal_indent);
inset-block-start: 50%;
transform: translateY(-50%);
}

.PositionedComponent--position-middle {
inset-inline-start: 50%;
inset-block-start: 50%;
transform: translate(-50%, -50%);
}

.PositionedComponent--position-middleEnd {
inset-inline-end: var(--vkui_internal--PositionedComponent_horizontal_indent);
inset-block-start: 50%;
transform: translateY(-50%);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Meta, StoryObj } from '@storybook/react';
import { Icon16MoreHorizontal, Icon28AddOutline, Icon28DeleteOutline } from '@vkontakte/icons';
import { CanvasFullLayout, DisableCartesianParam } from '../../../storybook/constants';
import { getAvatarUrl } from '../../../testing/mock';
import { Button } from '../../Button/Button';
import { CellButton } from '../../CellButton/CellButton';
import { Popover } from '../../Popover/Popover';
import { ImageBase } from '../ImageBase';
import {
ImageBasePositionedComponent,
ImageBasePositionedComponentProps,
} from './ImageBasePositionedComponent';

const ContextMenu = () => {
return (
<Popover
noStyling
trigger="click"
role="dialog"
content={({ onClose }) => (
<div
style={{
backgroundColor: 'var(--vkui--color_background_modal_inverse)',
borderRadius: 8,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.3)',
}}
>
<CellButton role="menuitem" before={<Icon28AddOutline />} onClick={onClose}>
Добавить
</CellButton>
<CellButton
role="menuitem"
before={<Icon28DeleteOutline />}
mode="danger"
onClick={onClose}
>
Удалить
</CellButton>
</div>
)}
>
<Button mode="primary" after={<Icon16MoreHorizontal />}></Button>
</Popover>
);
};

const story: Meta<ImageBasePositionedComponentProps> = {
title: 'Blocks/ImageBasePositionedComponent',
component: ImageBasePositionedComponent,
parameters: { ...CanvasFullLayout, ...DisableCartesianParam },
decorators: [
(Component) => (
<ImageBase size={96} src={getAvatarUrl('app_shorm_online')} alt="Приложение шторм онлайн">
<Component />
</ImageBase>
),
],
};

export default story;

type Story = StoryObj<ImageBasePositionedComponentProps>;

export const Playground: Story = {
args: {
position: {
insetInlineEnd: '5%',
insetBlockStart: '5%',
},
children: <ContextMenu />,
},
};
Loading