Skip to content

Commit b4ecdee

Browse files
madoctozombieJ
andauthored
feat: add previous and next toggle to toolbar (#320)
* 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: 二货机器人 <[email protected]>
1 parent 292424f commit b4ecdee

File tree

4 files changed

+270
-160
lines changed

4 files changed

+270
-160
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ type TransformAction =
172172
```typescript
173173
{
174174
icons: {
175+
prevIcon?: React.ReactNode;
176+
nextIcon?: React.ReactNode;
175177
flipYIcon: React.ReactNode;
176178
flipXIcon: React.ReactNode;
177179
rotateLeftIcon: React.ReactNode;
@@ -180,6 +182,7 @@ type TransformAction =
180182
zoomInIcon: React.ReactNode;
181183
};
182184
actions: {
185+
onActive?: (offset: number) => void;
183186
onFlipY: () => void;
184187
onFlipX: () => void;
185188
onRotateLeft: () => void;

src/Operations.tsx

Lines changed: 119 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@ import type { PreviewProps, ToolbarRenderInfoType } from './Preview';
99
import { PreviewGroupContext } from './context';
1010
import type { TransformType } from './hooks/useImageTransform';
1111

12+
type OperationType =
13+
| 'prev'
14+
| 'next'
15+
| 'flipY'
16+
| 'flipX'
17+
| 'rotateLeft'
18+
| 'rotateRight'
19+
| 'zoomOut'
20+
| 'zoomIn';
21+
22+
interface RenderOperationParams {
23+
icon: React.ReactNode;
24+
type: OperationType;
25+
disabled?: boolean;
26+
onClick: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
27+
}
28+
1229
interface OperationsProps
1330
extends Pick<
1431
PreviewProps,
@@ -30,8 +47,7 @@ interface OperationsProps
3047
scale: number;
3148
minScale: number;
3249
maxScale: number;
33-
onSwitchLeft: React.MouseEventHandler<HTMLDivElement>;
34-
onSwitchRight: React.MouseEventHandler<HTMLDivElement>;
50+
onActive: (offset: number) => void;
3551
onZoomIn: () => void;
3652
onZoomOut: () => void;
3753
onRotateRight: () => void;
@@ -65,8 +81,7 @@ const Operations: React.FC<OperationsProps> = props => {
6581
minScale,
6682
maxScale,
6783
closeIcon,
68-
onSwitchLeft,
69-
onSwitchRight,
84+
onActive,
7085
onClose,
7186
onZoomIn,
7287
onZoomOut,
@@ -99,55 +114,96 @@ const Operations: React.FC<OperationsProps> = props => {
99114
};
100115
}, [visible]);
101116

102-
const tools = [
103-
{
104-
icon: flipY,
105-
onClick: onFlipY,
106-
type: 'flipY',
107-
},
108-
{
109-
icon: flipX,
110-
onClick: onFlipX,
111-
type: 'flipX',
112-
},
113-
{
114-
icon: rotateLeft,
115-
onClick: onRotateLeft,
116-
type: 'rotateLeft',
117-
},
118-
{
119-
icon: rotateRight,
120-
onClick: onRotateRight,
121-
type: 'rotateRight',
122-
},
123-
{
124-
icon: zoomOut,
125-
onClick: onZoomOut,
126-
type: 'zoomOut',
127-
disabled: scale <= minScale,
128-
},
129-
{
130-
icon: zoomIn,
131-
onClick: onZoomIn,
132-
type: 'zoomIn',
133-
disabled: scale === maxScale,
117+
const handleActive = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, offset: number) => {
118+
e.preventDefault();
119+
e.stopPropagation();
120+
121+
onActive(offset);
122+
};
123+
124+
const renderOperation = React.useCallback(
125+
({ type, disabled, onClick, icon }: RenderOperationParams) => {
126+
return (
127+
<div
128+
key={type}
129+
className={classnames(toolClassName, `${prefixCls}-operations-operation-${type}`, {
130+
[`${prefixCls}-operations-operation-disabled`]: !!disabled,
131+
})}
132+
onClick={onClick}
133+
>
134+
{icon}
135+
</div>
136+
);
134137
},
135-
];
136-
137-
const toolsNode = tools.map(({ icon, onClick, type, disabled }) => (
138-
<div
139-
className={classnames(toolClassName, {
140-
[`${prefixCls}-operations-operation-${type}`]: true,
141-
[`${prefixCls}-operations-operation-disabled`]: !!disabled,
142-
})}
143-
onClick={onClick}
144-
key={type}
145-
>
146-
{icon}
147-
</div>
148-
));
138+
[toolClassName, prefixCls],
139+
);
140+
141+
const switchPrevNode = showSwitch
142+
? renderOperation({
143+
icon: left,
144+
onClick: e => handleActive(e, -1),
145+
type: 'prev',
146+
disabled: current === 0,
147+
})
148+
: undefined;
149+
150+
const switchNextNode = showSwitch
151+
? renderOperation({
152+
icon: right,
153+
onClick: e => handleActive(e, 1),
154+
type: 'next',
155+
disabled: current === count - 1,
156+
})
157+
: undefined;
158+
159+
const flipYNode = renderOperation({
160+
icon: flipY,
161+
onClick: onFlipY,
162+
type: 'flipY',
163+
});
164+
165+
const flipXNode = renderOperation({
166+
icon: flipX,
167+
onClick: onFlipX,
168+
type: 'flipX',
169+
});
170+
171+
const rotateLeftNode = renderOperation({
172+
icon: rotateLeft,
173+
onClick: onRotateLeft,
174+
type: 'rotateLeft',
175+
});
149176

150-
const toolbarNode = <div className={`${prefixCls}-operations`}>{toolsNode}</div>;
177+
const rotateRightNode = renderOperation({
178+
icon: rotateRight,
179+
onClick: onRotateRight,
180+
type: 'rotateRight',
181+
});
182+
183+
const zoomOutNode = renderOperation({
184+
icon: zoomOut,
185+
onClick: onZoomOut,
186+
type: 'zoomOut',
187+
disabled: scale <= minScale,
188+
});
189+
190+
const zoomInNode = renderOperation({
191+
icon: zoomIn,
192+
onClick: onZoomIn,
193+
type: 'zoomIn',
194+
disabled: scale === maxScale,
195+
});
196+
197+
const toolbarNode = (
198+
<div className={`${prefixCls}-operations`}>
199+
{flipYNode}
200+
{flipXNode}
201+
{rotateLeftNode}
202+
{rotateRightNode}
203+
{zoomOutNode}
204+
{zoomInNode}
205+
</div>
206+
);
151207

152208
return (
153209
<CSSMotion visible={visible} motionName={maskTransitionName}>
@@ -172,15 +228,15 @@ const Operations: React.FC<OperationsProps> = props => {
172228
className={classnames(`${prefixCls}-switch-left`, {
173229
[`${prefixCls}-switch-left-disabled`]: current === 0,
174230
})}
175-
onClick={onSwitchLeft}
231+
onClick={e => handleActive(e, -1)}
176232
>
177233
{left}
178234
</div>
179235
<div
180236
className={classnames(`${prefixCls}-switch-right`, {
181237
[`${prefixCls}-switch-right-disabled`]: current === count - 1,
182238
})}
183-
onClick={onSwitchRight}
239+
onClick={e => handleActive(e, 1)}
184240
>
185241
{right}
186242
</div>
@@ -197,22 +253,25 @@ const Operations: React.FC<OperationsProps> = props => {
197253
{toolbarRender
198254
? toolbarRender(toolbarNode, {
199255
icons: {
200-
flipYIcon: toolsNode[0],
201-
flipXIcon: toolsNode[1],
202-
rotateLeftIcon: toolsNode[2],
203-
rotateRightIcon: toolsNode[3],
204-
zoomOutIcon: toolsNode[4],
205-
zoomInIcon: toolsNode[5],
256+
prevIcon: switchPrevNode,
257+
nextIcon: switchNextNode,
258+
flipYIcon: flipYNode,
259+
flipXIcon: flipXNode,
260+
rotateLeftIcon: rotateLeftNode,
261+
rotateRightIcon: rotateRightNode,
262+
zoomOutIcon: zoomOutNode,
263+
zoomInIcon: zoomInNode,
206264
},
207265
actions: {
266+
onActive,
208267
onFlipY,
209268
onFlipX,
210269
onRotateLeft,
211270
onRotateRight,
212271
onZoomOut,
213272
onZoomIn,
214273
onReset,
215-
onClose
274+
onClose,
216275
},
217276
transform,
218277
...(groupContext ? { current, total: count } : {}),

src/Preview.tsx

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { BASE_SCALE_RATIO } from './previewConfig';
1616

1717
export type ToolbarRenderInfoType = {
1818
icons: {
19+
prevIcon?: React.ReactNode;
20+
nextIcon?: React.ReactNode;
1921
flipYIcon: React.ReactNode;
2022
flipXIcon: React.ReactNode;
2123
rotateLeftIcon: React.ReactNode;
@@ -24,6 +26,7 @@ export type ToolbarRenderInfoType = {
2426
zoomInIcon: React.ReactNode;
2527
};
2628
actions: {
29+
onActive?: (offset: number) => void;
2730
onFlipY: () => void;
2831
onFlipX: () => void;
2932
onRotateLeft: () => void;
@@ -207,33 +210,25 @@ const Preview: React.FC<PreviewProps> = props => {
207210
resetTransform('reset');
208211
};
209212

210-
const onSwitchLeft = (event?: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
211-
event?.preventDefault();
212-
event?.stopPropagation();
213-
if (current > 0) {
214-
setEnableTransition(false);
215-
resetTransform('prev');
216-
onChange?.(current - 1, current);
217-
}
218-
};
213+
const onActive = (offset: number) => {
214+
const position = current + offset;
219215

220-
const onSwitchRight = (event?: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
221-
event?.preventDefault();
222-
event?.stopPropagation();
223-
if (current < count - 1) {
224-
setEnableTransition(false);
225-
resetTransform('next');
226-
onChange?.(current + 1, current);
216+
if (!Number.isInteger(position) || position < 0 || position > count - 1) {
217+
return;
227218
}
228-
};
219+
220+
setEnableTransition(false);
221+
resetTransform(offset < 0 ? 'prev' : 'next');
222+
onChange?.(position, current);
223+
}
229224

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

233228
if (event.keyCode === KeyCode.LEFT) {
234-
onSwitchLeft();
229+
onActive(-1);
235230
} else if (event.keyCode === KeyCode.RIGHT) {
236-
onSwitchRight();
231+
onActive(1);
237232
}
238233
};
239234

@@ -269,9 +264,8 @@ const Preview: React.FC<PreviewProps> = props => {
269264
className={`${prefixCls}-img`}
270265
alt={alt}
271266
style={{
272-
transform: `translate3d(${transform.x}px, ${transform.y}px, 0) scale3d(${
273-
transform.flipX ? '-' : ''
274-
}${scale}, ${transform.flipY ? '-' : ''}${scale}, 1) rotate(${rotate}deg)`,
267+
transform: `translate3d(${transform.x}px, ${transform.y}px, 0) scale3d(${transform.flipX ? '-' : ''
268+
}${scale}, ${transform.flipY ? '-' : ''}${scale}, 1) rotate(${rotate}deg)`,
275269
transitionDuration: (!enableTransition || isTouching) && '0s',
276270
}}
277271
fallback={fallback}
@@ -334,8 +328,7 @@ const Preview: React.FC<PreviewProps> = props => {
334328
minScale={minScale}
335329
maxScale={maxScale}
336330
toolbarRender={toolbarRender}
337-
onSwitchLeft={onSwitchLeft}
338-
onSwitchRight={onSwitchRight}
331+
onActive={onActive}
339332
onZoomIn={onZoomIn}
340333
onZoomOut={onZoomOut}
341334
onRotateRight={onRotateRight}

0 commit comments

Comments
 (0)