Skip to content

Commit

Permalink
feat: add previous and next toggle to toolbar (#320)
Browse files Browse the repository at this point in the history
* feat: add left and right switches to toolbar

* test: update case

* fix: improve

* test: update test

* test: update test

* test: update test

---------

Co-authored-by: 二货机器人 <smith3816@gmail.com>
  • Loading branch information
madocto and zombieJ committed Jun 11, 2024
1 parent 292424f commit b4ecdee
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 160 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ type TransformAction =
```typescript
{
icons: {
prevIcon?: React.ReactNode;
nextIcon?: React.ReactNode;
flipYIcon: React.ReactNode;
flipXIcon: React.ReactNode;
rotateLeftIcon: React.ReactNode;
Expand All @@ -180,6 +182,7 @@ type TransformAction =
zoomInIcon: React.ReactNode;
};
actions: {
onActive?: (offset: number) => void;
onFlipY: () => void;
onFlipX: () => void;
onRotateLeft: () => void;
Expand Down
179 changes: 119 additions & 60 deletions src/Operations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ import type { PreviewProps, ToolbarRenderInfoType } from './Preview';
import { PreviewGroupContext } from './context';
import type { TransformType } from './hooks/useImageTransform';

type OperationType =
| 'prev'
| 'next'
| 'flipY'
| 'flipX'
| 'rotateLeft'
| 'rotateRight'
| 'zoomOut'
| 'zoomIn';

interface RenderOperationParams {
icon: React.ReactNode;
type: OperationType;
disabled?: boolean;
onClick: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
}

interface OperationsProps
extends Pick<
PreviewProps,
Expand All @@ -30,8 +47,7 @@ interface OperationsProps
scale: number;
minScale: number;
maxScale: number;
onSwitchLeft: React.MouseEventHandler<HTMLDivElement>;
onSwitchRight: React.MouseEventHandler<HTMLDivElement>;
onActive: (offset: number) => void;
onZoomIn: () => void;
onZoomOut: () => void;
onRotateRight: () => void;
Expand Down Expand Up @@ -65,8 +81,7 @@ const Operations: React.FC<OperationsProps> = props => {
minScale,
maxScale,
closeIcon,
onSwitchLeft,
onSwitchRight,
onActive,
onClose,
onZoomIn,
onZoomOut,
Expand Down Expand Up @@ -99,55 +114,96 @@ const Operations: React.FC<OperationsProps> = props => {
};
}, [visible]);

const tools = [
{
icon: flipY,
onClick: onFlipY,
type: 'flipY',
},
{
icon: flipX,
onClick: onFlipX,
type: 'flipX',
},
{
icon: rotateLeft,
onClick: onRotateLeft,
type: 'rotateLeft',
},
{
icon: rotateRight,
onClick: onRotateRight,
type: 'rotateRight',
},
{
icon: zoomOut,
onClick: onZoomOut,
type: 'zoomOut',
disabled: scale <= minScale,
},
{
icon: zoomIn,
onClick: onZoomIn,
type: 'zoomIn',
disabled: scale === maxScale,
const handleActive = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, offset: number) => {
e.preventDefault();
e.stopPropagation();

onActive(offset);
};

const renderOperation = React.useCallback(
({ type, disabled, onClick, icon }: RenderOperationParams) => {
return (
<div
key={type}
className={classnames(toolClassName, `${prefixCls}-operations-operation-${type}`, {
[`${prefixCls}-operations-operation-disabled`]: !!disabled,
})}
onClick={onClick}
>
{icon}
</div>
);
},
];

const toolsNode = tools.map(({ icon, onClick, type, disabled }) => (
<div
className={classnames(toolClassName, {
[`${prefixCls}-operations-operation-${type}`]: true,
[`${prefixCls}-operations-operation-disabled`]: !!disabled,
})}
onClick={onClick}
key={type}
>
{icon}
</div>
));
[toolClassName, prefixCls],
);

const switchPrevNode = showSwitch
? renderOperation({
icon: left,
onClick: e => handleActive(e, -1),
type: 'prev',
disabled: current === 0,
})
: undefined;

const switchNextNode = showSwitch
? renderOperation({
icon: right,
onClick: e => handleActive(e, 1),
type: 'next',
disabled: current === count - 1,
})
: undefined;

const flipYNode = renderOperation({
icon: flipY,
onClick: onFlipY,
type: 'flipY',
});

const flipXNode = renderOperation({
icon: flipX,
onClick: onFlipX,
type: 'flipX',
});

const rotateLeftNode = renderOperation({
icon: rotateLeft,
onClick: onRotateLeft,
type: 'rotateLeft',
});

const toolbarNode = <div className={`${prefixCls}-operations`}>{toolsNode}</div>;
const rotateRightNode = renderOperation({
icon: rotateRight,
onClick: onRotateRight,
type: 'rotateRight',
});

const zoomOutNode = renderOperation({
icon: zoomOut,
onClick: onZoomOut,
type: 'zoomOut',
disabled: scale <= minScale,
});

const zoomInNode = renderOperation({
icon: zoomIn,
onClick: onZoomIn,
type: 'zoomIn',
disabled: scale === maxScale,
});

const toolbarNode = (
<div className={`${prefixCls}-operations`}>
{flipYNode}
{flipXNode}
{rotateLeftNode}
{rotateRightNode}
{zoomOutNode}
{zoomInNode}
</div>
);

return (
<CSSMotion visible={visible} motionName={maskTransitionName}>
Expand All @@ -172,15 +228,15 @@ const Operations: React.FC<OperationsProps> = props => {
className={classnames(`${prefixCls}-switch-left`, {
[`${prefixCls}-switch-left-disabled`]: current === 0,
})}
onClick={onSwitchLeft}
onClick={e => handleActive(e, -1)}
>
{left}
</div>
<div
className={classnames(`${prefixCls}-switch-right`, {
[`${prefixCls}-switch-right-disabled`]: current === count - 1,
})}
onClick={onSwitchRight}
onClick={e => handleActive(e, 1)}
>
{right}
</div>
Expand All @@ -197,22 +253,25 @@ const Operations: React.FC<OperationsProps> = props => {
{toolbarRender
? toolbarRender(toolbarNode, {
icons: {
flipYIcon: toolsNode[0],
flipXIcon: toolsNode[1],
rotateLeftIcon: toolsNode[2],
rotateRightIcon: toolsNode[3],
zoomOutIcon: toolsNode[4],
zoomInIcon: toolsNode[5],
prevIcon: switchPrevNode,
nextIcon: switchNextNode,
flipYIcon: flipYNode,
flipXIcon: flipXNode,
rotateLeftIcon: rotateLeftNode,
rotateRightIcon: rotateRightNode,
zoomOutIcon: zoomOutNode,
zoomInIcon: zoomInNode,
},
actions: {
onActive,
onFlipY,
onFlipX,
onRotateLeft,
onRotateRight,
onZoomOut,
onZoomIn,
onReset,
onClose
onClose,
},
transform,
...(groupContext ? { current, total: count } : {}),
Expand Down
41 changes: 17 additions & 24 deletions src/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { BASE_SCALE_RATIO } from './previewConfig';

export type ToolbarRenderInfoType = {
icons: {
prevIcon?: React.ReactNode;
nextIcon?: React.ReactNode;
flipYIcon: React.ReactNode;
flipXIcon: React.ReactNode;
rotateLeftIcon: React.ReactNode;
Expand All @@ -24,6 +26,7 @@ export type ToolbarRenderInfoType = {
zoomInIcon: React.ReactNode;
};
actions: {
onActive?: (offset: number) => void;
onFlipY: () => void;
onFlipX: () => void;
onRotateLeft: () => void;
Expand Down Expand Up @@ -207,33 +210,25 @@ const Preview: React.FC<PreviewProps> = props => {
resetTransform('reset');
};

const onSwitchLeft = (event?: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event?.preventDefault();
event?.stopPropagation();
if (current > 0) {
setEnableTransition(false);
resetTransform('prev');
onChange?.(current - 1, current);
}
};
const onActive = (offset: number) => {
const position = current + offset;

const onSwitchRight = (event?: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event?.preventDefault();
event?.stopPropagation();
if (current < count - 1) {
setEnableTransition(false);
resetTransform('next');
onChange?.(current + 1, current);
if (!Number.isInteger(position) || position < 0 || position > count - 1) {
return;
}
};

setEnableTransition(false);
resetTransform(offset < 0 ? 'prev' : 'next');
onChange?.(position, current);
}

const onKeyDown = (event: KeyboardEvent) => {
if (!visible || !showLeftOrRightSwitches) return;

if (event.keyCode === KeyCode.LEFT) {
onSwitchLeft();
onActive(-1);
} else if (event.keyCode === KeyCode.RIGHT) {
onSwitchRight();
onActive(1);
}
};

Expand Down Expand Up @@ -269,9 +264,8 @@ const Preview: React.FC<PreviewProps> = props => {
className={`${prefixCls}-img`}
alt={alt}
style={{
transform: `translate3d(${transform.x}px, ${transform.y}px, 0) scale3d(${
transform.flipX ? '-' : ''
}${scale}, ${transform.flipY ? '-' : ''}${scale}, 1) rotate(${rotate}deg)`,
transform: `translate3d(${transform.x}px, ${transform.y}px, 0) scale3d(${transform.flipX ? '-' : ''
}${scale}, ${transform.flipY ? '-' : ''}${scale}, 1) rotate(${rotate}deg)`,
transitionDuration: (!enableTransition || isTouching) && '0s',
}}
fallback={fallback}
Expand Down Expand Up @@ -334,8 +328,7 @@ const Preview: React.FC<PreviewProps> = props => {
minScale={minScale}
maxScale={maxScale}
toolbarRender={toolbarRender}
onSwitchLeft={onSwitchLeft}
onSwitchRight={onSwitchRight}
onActive={onActive}
onZoomIn={onZoomIn}
onZoomOut={onZoomOut}
onRotateRight={onRotateRight}
Expand Down
Loading

0 comments on commit b4ecdee

Please sign in to comment.