diff --git a/src/components/Gallery/Gallery.tsx b/src/components/Gallery/Gallery.tsx
index dd660bb3..2abd3484 100644
--- a/src/components/Gallery/Gallery.tsx
+++ b/src/components/Gallery/Gallery.tsx
@@ -10,6 +10,7 @@ import {NavigationButton} from './components/NavigationButton/NavigationButton';
import {BODY_CONTENT_CLASS_NAME, cnGallery} from './constants';
import {GalleryContextProvider} from './contexts/GalleryContext';
import {useFullScreen} from './hooks/useFullScreen';
+import {useImageRotationState} from './hooks/useImageRotationState';
import {useMobileGestures} from './hooks/useMobileGestures/useMobileGestures';
import type {UseNavigationProps} from './hooks/useNavigation';
import {useNavigation} from './hooks/useNavigation';
@@ -70,6 +71,12 @@ export const Gallery = ({
const {fullScreen, setFullScreen} = useFullScreen();
+ const {rotation, rotateLeft, rotateRight, resetRotation} = useImageRotationState();
+
+ React.useEffect(() => {
+ resetRotation();
+ }, [activeItemIndex, resetRotation]);
+
const handleBackClick = React.useCallback(() => {
onOpenChange?.(false);
}, [onOpenChange]);
@@ -131,80 +138,88 @@ export const Gallery = ({
overflow: mode === 'default' ? 'auto' : 'hidden',
}}
>
-
-
-
-
- {!items.length && (
-
- {emptyMessage ?? t('no-items')}
-
- )}
-
+
+
+
+ {!items.length && (
+
+ {emptyMessage ?? t('no-items')}
+
+ )}
{activeItem?.view}
-
- {showNavigationButtons && (
-
-
-
-
- )}
+ {showNavigationButtons && (
+
+
+
+
+ )}
+
+ {showFooter && (
+
+ {withNavigation && (
+
+ {items.map((item, index) => {
+ const handleClick = () => {
+ setActiveItemIndex(index);
+ };
+
+ const selected = activeItemIndex === index;
+
+ return (
+
+ );
+ })}
+
+ )}
+
+ )}
- {showFooter && (
-
- {withNavigation && (
-
- {items.map((item, index) => {
- const handleClick = () => {
- setActiveItemIndex(index);
- };
-
- const selected = activeItemIndex === index;
-
- return (
-
- );
- })}
-
- )}
-
- )}
-
+
);
};
diff --git a/src/components/Gallery/__stories__/Gallery.stories.tsx b/src/components/Gallery/__stories__/Gallery.stories.tsx
index 0c5e94ee..6ab9f158 100644
--- a/src/components/Gallery/__stories__/Gallery.stories.tsx
+++ b/src/components/Gallery/__stories__/Gallery.stories.tsx
@@ -23,6 +23,8 @@ import {
getGalleryItemDocument,
getGalleryItemDownloadAction,
getGalleryItemImage,
+ getGalleryItemRotateLeftAction,
+ getGalleryItemRotateRightAction,
getGalleryItemVideo,
} from '../';
import type {GalleryProps} from '../';
@@ -373,3 +375,37 @@ const SmallImagesTemplate: StoryFn
= () => {
};
export const SmallImages = SmallImagesTemplate.bind({});
+
+const RotationGalleryTemplate: StoryFn = () => {
+ const [open, setOpen] = React.useState(false);
+
+ const handleToggle = React.useCallback(() => {
+ setOpen(false);
+ }, []);
+
+ const handleOpen = React.useCallback(() => {
+ setOpen(true);
+ }, []);
+
+ return (
+
+
+
+ {images.map((image, index) => (
+
+ ))}
+
+
+ );
+};
+
+export const RotationGallery = RotationGalleryTemplate.bind({});
diff --git a/src/components/Gallery/components/views/ImageView/ImageView.tsx b/src/components/Gallery/components/views/ImageView/ImageView.tsx
index 966ce776..056a4d02 100644
--- a/src/components/Gallery/components/views/ImageView/ImageView.tsx
+++ b/src/components/Gallery/components/views/ImageView/ImageView.tsx
@@ -4,6 +4,7 @@ import {Spin, useMobile} from '@gravity-ui/uikit';
import {block} from '../../../../utils/cn';
import {useGalleryContext} from '../../../contexts/GalleryContext';
+import {useImageRotation} from '../../../hooks/useImageRotation';
import {useImageZoom} from '../../../hooks/useImageZoom';
import {GalleryFallbackText} from '../../FallbackText';
@@ -27,10 +28,16 @@ export const ImageView = ({className, src, alt = ''}: ImageViewProps) => {
const {imageHandlers, setImageSize, setContainerSize, resetZoom, imageStyles, isZooming} =
useImageZoom({onTap});
+ const {imageRotationStyles, rotation, setContainerDims} = useImageRotation();
+
React.useEffect(() => {
onViewInteractionChange(isZooming);
}, [isZooming, onViewInteractionChange]);
+ React.useEffect(() => {
+ resetZoom();
+ }, [src, rotation, resetZoom]);
+
const handleLoad = React.useCallback(() => {
setStatus('complete');
if (imageRef.current) {
@@ -45,7 +52,6 @@ export const ImageView = ({className, src, alt = ''}: ImageViewProps) => {
setStatus('error');
}, []);
- // Track container dimensions and handle resize
React.useEffect(() => {
if (!containerRef.current) return undefined;
@@ -56,9 +62,9 @@ export const ImageView = ({className, src, alt = ''}: ImageViewProps) => {
height: containerRef.current.clientHeight,
};
- // Only update if dimensions are valid
if (size.width > 0 && size.height > 0) {
setContainerSize(size);
+ setContainerDims(size);
}
}
};
@@ -80,16 +86,16 @@ export const ImageView = ({className, src, alt = ''}: ImageViewProps) => {
clearTimeout(timeoutId);
window.removeEventListener('resize', updateSize);
};
- }, [setContainerSize]);
-
- React.useEffect(() => {
- resetZoom();
- }, [src, resetZoom]);
+ }, [setContainerSize, setContainerDims]);
if (status === 'error') {
return ;
}
+ const mergedTransform = [imageStyles.transform, imageRotationStyles.transform]
+ .filter(Boolean)
+ .join(' ');
+
return (
{status === 'loading' && }
@@ -101,7 +107,11 @@ export const ImageView = ({className, src, alt = ''}: ImageViewProps) => {
{...imageHandlers}
onLoad={handleLoad}
onError={handleError}
- style={imageStyles}
+ style={{
+ ...imageStyles,
+ ...imageRotationStyles,
+ transform: mergedTransform || undefined,
+ }}
/>
);
diff --git a/src/components/Gallery/contexts/GalleryContext/GalleryContext.tsx b/src/components/Gallery/contexts/GalleryContext/GalleryContext.tsx
index 55f3824e..05e08241 100644
--- a/src/components/Gallery/contexts/GalleryContext/GalleryContext.tsx
+++ b/src/components/Gallery/contexts/GalleryContext/GalleryContext.tsx
@@ -8,28 +8,49 @@ export type GalleryContextValue = {
onTap: React.TouchEventHandler;
/** Callback to notify Gallery about view interaction state changes. */
onViewInteractionChange: (isInteracting: boolean) => void;
+ /** Current image rotation in degrees. */
+ rotation: number;
+ /** Rotate the active image counter-clockwise by one step. */
+ rotateLeft: () => void;
+ /** Rotate the active image clockwise by one step. */
+ rotateRight: () => void;
};
const GalleryContext = React.createContext({
onTap: () => {},
onViewInteractionChange: () => {},
+ rotation: 0,
+ rotateLeft: () => {},
+ rotateRight: () => {},
});
export const GalleryContextProvider: React.FunctionComponent<
React.PropsWithChildren
-> = function GalleryContextProvider({children, onViewInteractionChange, onTap}) {
+> = function GalleryContextProvider({
+ children,
+ onViewInteractionChange,
+ onTap,
+ rotation,
+ rotateLeft,
+ rotateRight,
+}) {
const value: GalleryContextValue = React.useMemo(
() => ({
onTap,
onViewInteractionChange,
+ rotation,
+ rotateLeft,
+ rotateRight,
}),
- [onTap, onViewInteractionChange],
+ [onTap, onViewInteractionChange, rotation, rotateLeft, rotateRight],
);
return {children};
};
/**
* Context for communication between Gallery and its child views.
- * Provides callbacks for view interaction events.
+ * Provides callbacks for view interaction events and image rotation state.
+ *
+ * @returns Current GalleryContext value.
*/
export const useGalleryContext = () => React.useContext(GalleryContext);
diff --git a/src/components/Gallery/hooks/useImageRotation/__tests__/useImageRotation.test.tsx b/src/components/Gallery/hooks/useImageRotation/__tests__/useImageRotation.test.tsx
new file mode 100644
index 00000000..0f88eb2a
--- /dev/null
+++ b/src/components/Gallery/hooks/useImageRotation/__tests__/useImageRotation.test.tsx
@@ -0,0 +1,160 @@
+import {act, renderHook} from '@testing-library/react';
+
+import type {GalleryContextValue} from '../../../contexts/GalleryContext';
+import {GalleryContextProvider} from '../../../contexts/GalleryContext';
+import {useImageRotation} from '../useImageRotation';
+
+const renderUseImageRotation = (rotation: number) => {
+ const contextValue: GalleryContextValue = {
+ onTap: () => {},
+ onViewInteractionChange: () => {},
+ rotation,
+ rotateLeft: () => {},
+ rotateRight: () => {},
+ };
+
+ return renderHook(() => useImageRotation(), {
+ wrapper: ({children}) => (
+ {children}
+ ),
+ });
+};
+
+describe('useImageRotation', () => {
+ it('returns empty styles when container has not been measured', () => {
+ const {result} = renderUseImageRotation(90);
+
+ expect(result.current.imageRotationStyles).toEqual({});
+ });
+
+ it('returns empty styles when container width is 0', () => {
+ const {result} = renderUseImageRotation(90);
+
+ act(() => {
+ result.current.setContainerDims({width: 0, height: 500});
+ });
+
+ expect(result.current.imageRotationStyles).toEqual({});
+ });
+
+ it('returns empty styles when container height is 0', () => {
+ const {result} = renderUseImageRotation(90);
+
+ act(() => {
+ result.current.setContainerDims({width: 500, height: 0});
+ });
+
+ expect(result.current.imageRotationStyles).toEqual({});
+ });
+
+ it('produces no transform at rotation 0 once dimensions are set', () => {
+ const {result} = renderUseImageRotation(0);
+
+ act(() => {
+ result.current.setContainerDims({width: 800, height: 600});
+ });
+
+ expect(result.current.imageRotationStyles).toEqual({});
+ });
+
+ it('applies rotate transform for non-horizontal angles', () => {
+ const {result} = renderUseImageRotation(180);
+
+ act(() => {
+ result.current.setContainerDims({width: 800, height: 600});
+ });
+
+ expect(result.current.imageRotationStyles).toEqual({transform: 'rotate(180deg)'});
+ });
+
+ it('swaps max-width / max-height for 90° rotation', () => {
+ const {result} = renderUseImageRotation(90);
+
+ act(() => {
+ result.current.setContainerDims({width: 800, height: 600});
+ });
+
+ expect(result.current.imageRotationStyles).toEqual({
+ transform: 'rotate(90deg)',
+ maxWidth: 600,
+ maxHeight: 800,
+ });
+ });
+
+ it('swaps dimensions for 270° rotation', () => {
+ const {result} = renderUseImageRotation(270);
+
+ act(() => {
+ result.current.setContainerDims({width: 800, height: 600});
+ });
+
+ expect(result.current.imageRotationStyles).toEqual({
+ transform: 'rotate(270deg)',
+ maxWidth: 600,
+ maxHeight: 800,
+ });
+ });
+
+ it('normalizes negative rotations to detect horizontal angles', () => {
+ const {result} = renderUseImageRotation(-90);
+
+ act(() => {
+ result.current.setContainerDims({width: 800, height: 600});
+ });
+
+ expect(result.current.imageRotationStyles).toEqual({
+ transform: 'rotate(-90deg)',
+ maxWidth: 600,
+ maxHeight: 800,
+ });
+ });
+
+ it('handles rotation values greater than 360°', () => {
+ const {result} = renderUseImageRotation(450); // 450 % 360 === 90
+
+ act(() => {
+ result.current.setContainerDims({width: 800, height: 600});
+ });
+
+ expect(result.current.imageRotationStyles).toEqual({
+ transform: 'rotate(450deg)',
+ maxWidth: 600,
+ maxHeight: 800,
+ });
+ });
+
+ it('does not swap dimensions for 180° rotation', () => {
+ const {result} = renderUseImageRotation(180);
+
+ act(() => {
+ result.current.setContainerDims({width: 800, height: 600});
+ });
+
+ expect(result.current.imageRotationStyles.maxWidth).toBeUndefined();
+ expect(result.current.imageRotationStyles.maxHeight).toBeUndefined();
+ });
+
+ it('exposes rotation actions from context', () => {
+ const rotateLeft = jest.fn();
+ const rotateRight = jest.fn();
+ const contextValue: GalleryContextValue = {
+ onTap: () => {},
+ onViewInteractionChange: () => {},
+ rotation: 0,
+ rotateLeft,
+ rotateRight,
+ };
+
+ const {result} = renderHook(() => useImageRotation(), {
+ wrapper: ({children}) => (
+ {children}
+ ),
+ });
+
+ result.current.rotateLeft();
+ result.current.rotateRight();
+
+ expect(rotateLeft).toHaveBeenCalledTimes(1);
+ expect(rotateRight).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/components/Gallery/hooks/useImageRotation/constants.ts b/src/components/Gallery/hooks/useImageRotation/constants.ts
new file mode 100644
index 00000000..d683ebdd
--- /dev/null
+++ b/src/components/Gallery/hooks/useImageRotation/constants.ts
@@ -0,0 +1,3 @@
+export const ROTATION_STEP = 90; // degrees per rotate-left / rotate-right action
+export const FULL_ROTATION = 360; // degrees in a full rotation
+export const INITIAL_ROTATION = 0; // degrees at the start
diff --git a/src/components/Gallery/hooks/useImageRotation/index.ts b/src/components/Gallery/hooks/useImageRotation/index.ts
new file mode 100644
index 00000000..d065970d
--- /dev/null
+++ b/src/components/Gallery/hooks/useImageRotation/index.ts
@@ -0,0 +1 @@
+export * from './useImageRotation';
diff --git a/src/components/Gallery/hooks/useImageRotation/useImageRotation.ts b/src/components/Gallery/hooks/useImageRotation/useImageRotation.ts
new file mode 100644
index 00000000..97545ab2
--- /dev/null
+++ b/src/components/Gallery/hooks/useImageRotation/useImageRotation.ts
@@ -0,0 +1,48 @@
+import * as React from 'react';
+
+import {useGalleryContext} from '../../contexts/GalleryContext';
+
+import {FULL_ROTATION, ROTATION_STEP} from './constants';
+
+export type UseImageRotationReturn = {
+ /**
+ * Styles for the `
` element: the `rotate` transform part plus
+ * swapped max-width/max-height constraints for 90°/270° rotations.
+ * Returns an empty object until the container has been measured.
+ */
+ imageRotationStyles: React.CSSProperties;
+ rotation: number;
+ rotateLeft: () => void;
+ rotateRight: () => void;
+ setContainerDims: React.Dispatch>;
+};
+
+/**
+ * Hook for reading image rotation state from GalleryContext.
+ *
+ * @returns Rotation state, actions, computed styles, and a setter for container dimensions.
+ */
+export function useImageRotation(): UseImageRotationReturn {
+ const {rotation, rotateLeft, rotateRight} = useGalleryContext();
+ const [containerDims, setContainerDims] = React.useState({width: 0, height: 0});
+
+ const normalizedRotation = ((rotation % FULL_ROTATION) + FULL_ROTATION) % FULL_ROTATION;
+ const isHorizontalRotation =
+ normalizedRotation === ROTATION_STEP ||
+ normalizedRotation === FULL_ROTATION - ROTATION_STEP;
+
+ const imageRotationStyles = React.useMemo(() => {
+ if (containerDims.width <= 0 || containerDims.height <= 0) {
+ return {};
+ }
+
+ return {
+ ...(rotation ? {transform: `rotate(${rotation}deg)`} : {}),
+ ...(isHorizontalRotation
+ ? {maxWidth: containerDims.height, maxHeight: containerDims.width}
+ : {}),
+ };
+ }, [rotation, isHorizontalRotation, containerDims]);
+
+ return {imageRotationStyles, rotation, rotateLeft, rotateRight, setContainerDims};
+}
diff --git a/src/components/Gallery/hooks/useImageRotationState/__tests__/useImageRotationState.test.ts b/src/components/Gallery/hooks/useImageRotationState/__tests__/useImageRotationState.test.ts
new file mode 100644
index 00000000..61134065
--- /dev/null
+++ b/src/components/Gallery/hooks/useImageRotationState/__tests__/useImageRotationState.test.ts
@@ -0,0 +1,71 @@
+import {act, renderHook} from '@testing-library/react';
+
+import {INITIAL_ROTATION, ROTATION_STEP} from '../../useImageRotation/constants';
+import {useImageRotationState} from '../useImageRotationState';
+
+describe('useImageRotationState', () => {
+ it('starts at 0 by default', () => {
+ const {result} = renderHook(() => useImageRotationState());
+
+ expect(result.current.rotation).toBe(0);
+ });
+
+ it('increments rotation by ROTATION_STEP on rotateRight', () => {
+ const {result} = renderHook(() => useImageRotationState());
+
+ act(() => {
+ result.current.rotateRight();
+ });
+
+ expect(result.current.rotation).toBe(ROTATION_STEP);
+ });
+
+ it('decrements rotation by ROTATION_STEP on rotateLeft', () => {
+ const {result} = renderHook(() => useImageRotationState());
+
+ act(() => {
+ result.current.rotateLeft();
+ });
+
+ expect(result.current.rotation).toBe(-ROTATION_STEP);
+ });
+
+ it('accumulates rotation across multiple calls', () => {
+ const {result} = renderHook(() => useImageRotationState());
+
+ act(() => {
+ result.current.rotateRight();
+ result.current.rotateRight();
+ result.current.rotateRight();
+ });
+
+ expect(result.current.rotation).toBe(ROTATION_STEP * 3);
+ });
+
+ it('resetRotation returns to initial rotation', () => {
+ const {result} = renderHook(() => useImageRotationState());
+
+ act(() => {
+ result.current.rotateRight();
+ result.current.rotateRight();
+ });
+ expect(result.current.rotation).toBe(INITIAL_ROTATION + ROTATION_STEP * 2);
+
+ act(() => {
+ result.current.resetRotation();
+ });
+ expect(result.current.rotation).toBe(INITIAL_ROTATION);
+ });
+
+ it('keeps stable rotateLeft/rotateRight identities when step is unchanged', () => {
+ const {result, rerender} = renderHook(() => useImageRotationState());
+
+ const firstRotateLeft = result.current.rotateLeft;
+ const firstRotateRight = result.current.rotateRight;
+
+ rerender();
+
+ expect(result.current.rotateLeft).toBe(firstRotateLeft);
+ expect(result.current.rotateRight).toBe(firstRotateRight);
+ });
+});
diff --git a/src/components/Gallery/hooks/useImageRotationState/index.ts b/src/components/Gallery/hooks/useImageRotationState/index.ts
new file mode 100644
index 00000000..89c2b553
--- /dev/null
+++ b/src/components/Gallery/hooks/useImageRotationState/index.ts
@@ -0,0 +1 @@
+export * from './useImageRotationState';
diff --git a/src/components/Gallery/hooks/useImageRotationState/useImageRotationState.ts b/src/components/Gallery/hooks/useImageRotationState/useImageRotationState.ts
new file mode 100644
index 00000000..d866d351
--- /dev/null
+++ b/src/components/Gallery/hooks/useImageRotationState/useImageRotationState.ts
@@ -0,0 +1,25 @@
+import * as React from 'react';
+
+import {INITIAL_ROTATION, ROTATION_STEP} from '../useImageRotation/constants';
+
+export type UseImageRotationStateReturn = {
+ rotation: number;
+ rotateLeft: VoidFunction;
+ rotateRight: VoidFunction;
+ resetRotation: VoidFunction;
+};
+
+/**
+ * Hook that owns image rotation state and exposes rotation actions.
+ *
+ * @returns Current rotation and actions to rotate left/right or reset.
+ */
+export function useImageRotationState(): UseImageRotationStateReturn {
+ const [rotation, setRotation] = React.useState(INITIAL_ROTATION);
+
+ const rotateLeft = React.useCallback(() => setRotation((r) => r - ROTATION_STEP), []);
+ const rotateRight = React.useCallback(() => setRotation((r) => r + ROTATION_STEP), []);
+ const resetRotation = React.useCallback(() => setRotation(INITIAL_ROTATION), []);
+
+ return {rotation, rotateLeft, rotateRight, resetRotation};
+}
diff --git a/src/components/Gallery/i18n/en.json b/src/components/Gallery/i18n/en.json
index 79dd567f..57b97fe2 100644
--- a/src/components/Gallery/i18n/en.json
+++ b/src/components/Gallery/i18n/en.json
@@ -3,5 +3,7 @@
"no-items": "No data",
"back": "Back",
"copy-link": "Copy link",
- "download": "Download"
+ "download": "Download",
+ "rotate-left": "Rotate left",
+ "rotate-right": "Rotate right"
}
diff --git a/src/components/Gallery/i18n/ru.json b/src/components/Gallery/i18n/ru.json
index 8675512a..bd4df96a 100644
--- a/src/components/Gallery/i18n/ru.json
+++ b/src/components/Gallery/i18n/ru.json
@@ -3,5 +3,7 @@
"no-items": "Нет данных",
"back": "Назад",
"copy-link": "Скопировать ссылку",
- "download": "Скачать"
+ "download": "Скачать",
+ "rotate-left": "Повернуть влево",
+ "rotate-right": "Повернуть вправо"
}
diff --git a/src/components/Gallery/index.ts b/src/components/Gallery/index.ts
index 9bda4b1b..892839a6 100644
--- a/src/components/Gallery/index.ts
+++ b/src/components/Gallery/index.ts
@@ -6,9 +6,22 @@ export {
useImageZoom as useGalleryImageZoom,
type UseImageZoomProps as UseGalleryImageZoomProps,
} from './hooks/useImageZoom';
+export {
+ useImageRotation as useGalleryImageRotation,
+ type UseImageRotationReturn as UseGalleryImageRotationReturn,
+} from './hooks/useImageRotation';
+export {
+ useImageRotationState as useGalleryImageRotationState,
+ type UseImageRotationStateReturn as UseGalleryImageRotationStateReturn,
+} from './hooks/useImageRotationState';
export {type GalleryContextValue, useGalleryContext} from './contexts/GalleryContext';
export {getGalleryItemVideo} from './utils/getGalleryItemVideo';
-export {getGalleryItemImage} from './utils/getGalleryItemImage';
+export {
+ getGalleryItemImage,
+ type GetDefaultGalleryItemImageArgs,
+} from './utils/getGalleryItemImage';
export {getGalleryItemDocument} from './utils/getGalleryItemDocument';
export {getGalleryItemDownloadAction} from './utils/getGalleryItemDownloadAction';
export {getGalleryItemCopyLinkAction} from './utils/getGalleryItemCopyLinkAction';
+export {getGalleryItemRotateLeftAction} from './utils/getGalleryItemRotateLeftAction';
+export {getGalleryItemRotateRightAction} from './utils/getGalleryItemRotateRightAction';
diff --git a/src/components/Gallery/utils/getGalleryItemRotateLeftAction.tsx b/src/components/Gallery/utils/getGalleryItemRotateLeftAction.tsx
new file mode 100644
index 00000000..787b6818
--- /dev/null
+++ b/src/components/Gallery/utils/getGalleryItemRotateLeftAction.tsx
@@ -0,0 +1,45 @@
+import * as React from 'react';
+
+import {ArrowRotateLeft} from '@gravity-ui/icons';
+import {ActionTooltip, Button, Icon} from '@gravity-ui/uikit';
+import type {ButtonProps} from '@gravity-ui/uikit';
+
+import type {GalleryItemAction} from '../GalleryItem';
+import {useGalleryContext} from '../contexts/GalleryContext';
+import type {TProps} from '../i18n';
+
+type GetGalleryItemRotateLeftActionArgs = {
+ /** Custom icon for the rotate-left button. Defaults to ArrowRotateLeft. */
+ icon?: React.ReactNode;
+};
+
+type RotateLeftButtonProps = {
+ buttonProps: ButtonProps;
+ icon?: React.ReactNode;
+ title: string;
+};
+
+const RotateLeftButton = ({buttonProps, icon, title}: RotateLeftButtonProps) => {
+ const {rotateLeft} = useGalleryContext();
+ return (
+
+
+
+ );
+};
+
+export function getGalleryItemRotateLeftAction({
+ icon,
+}: GetGalleryItemRotateLeftActionArgs = {}): GalleryItemAction {
+ return {
+ id: 'rotate-left',
+ title: 'rotate-left',
+ __titleT: ({t}) => t('rotate-left'),
+ icon: icon ?? ,
+ __renderT: (buttonProps: ButtonProps, {t}: TProps) => (
+
+ ),
+ };
+}
diff --git a/src/components/Gallery/utils/getGalleryItemRotateRightAction.tsx b/src/components/Gallery/utils/getGalleryItemRotateRightAction.tsx
new file mode 100644
index 00000000..4e2fbc8f
--- /dev/null
+++ b/src/components/Gallery/utils/getGalleryItemRotateRightAction.tsx
@@ -0,0 +1,45 @@
+import * as React from 'react';
+
+import {ArrowRotateRight} from '@gravity-ui/icons';
+import {ActionTooltip, Button, Icon} from '@gravity-ui/uikit';
+import type {ButtonProps} from '@gravity-ui/uikit';
+
+import type {GalleryItemAction} from '../GalleryItem';
+import {useGalleryContext} from '../contexts/GalleryContext';
+import type {TProps} from '../i18n';
+
+type GetGalleryItemRotateRightActionArgs = {
+ /** Custom icon for the rotate-right button. Defaults to ArrowRotateRight. */
+ icon?: React.ReactNode;
+};
+
+type RotateRightButtonProps = {
+ buttonProps: ButtonProps;
+ icon?: React.ReactNode;
+ title: string;
+};
+
+const RotateRightButton = ({buttonProps, icon, title}: RotateRightButtonProps) => {
+ const {rotateRight} = useGalleryContext();
+ return (
+
+
+
+ );
+};
+
+export function getGalleryItemRotateRightAction({
+ icon,
+}: GetGalleryItemRotateRightActionArgs = {}): GalleryItemAction {
+ return {
+ id: 'rotate-right',
+ title: 'rotate-right',
+ __titleT: ({t}) => t('rotate-right'),
+ icon: icon ?? ,
+ __renderT: (buttonProps: ButtonProps, {t}: TProps) => (
+
+ ),
+ };
+}