1
1
import { styled } from "@storybook/theming" ;
2
- import React , { useCallback , useEffect , useMemo , useRef , useState } from "react" ;
2
+ import React , {
3
+ createContext ,
4
+ Dispatch ,
5
+ ReactNode ,
6
+ SetStateAction ,
7
+ useCallback ,
8
+ useContext ,
9
+ useEffect ,
10
+ useMemo ,
11
+ useRef ,
12
+ useState ,
13
+ } from "react" ;
3
14
4
15
const TRANSITION_DURATION_MS = 200 ;
5
16
@@ -18,23 +29,33 @@ const Content = styled.div({
18
29
width : "max-content" ,
19
30
} ) ;
20
31
32
+ const initialState = {
33
+ containerHeight : 0 ,
34
+ containerWidth : 0 ,
35
+ contentHeight : 0 ,
36
+ contentWidth : 0 ,
37
+ contentScale : 1 ,
38
+ translateX : 0 ,
39
+ translateY : 0 ,
40
+ multiplierX : 1 ,
41
+ multiplierY : 1 ,
42
+ zoomed : false ,
43
+ transition : "none" ,
44
+ transitionTimeout : 0 ,
45
+ } ;
46
+
47
+ type State = typeof initialState ;
48
+
49
+ export const ZoomContext = createContext < [ State , Dispatch < SetStateAction < State > > ] > ( null as any ) ;
50
+
51
+ export const ZoomProvider = ( { children } : { children : ReactNode } ) => {
52
+ return < ZoomContext . Provider value = { useState ( initialState ) } > { children } </ ZoomContext . Provider > ;
53
+ } ;
54
+
21
55
const clamp = ( value : number , min : number , max : number ) => Math . max ( min , Math . min ( max , value ) ) ;
22
56
23
57
export const useZoom = ( ) => {
24
- const [ state , setState ] = useState ( {
25
- containerHeight : 0 ,
26
- containerWidth : 0 ,
27
- contentHeight : 0 ,
28
- contentWidth : 0 ,
29
- contentScale : 1 ,
30
- translateX : 0 ,
31
- translateY : 0 ,
32
- multiplierX : 1 ,
33
- multiplierY : 1 ,
34
- zoomed : false ,
35
- transition : "none" ,
36
- transitionTimeout : 0 ,
37
- } ) ;
58
+ const [ state , setState ] = useContext ( ZoomContext ) ;
38
59
39
60
const containerRef = useRef < HTMLDivElement > ( null ) ;
40
61
const contentRef = useRef < HTMLDivElement > ( null ) ;
@@ -73,21 +94,25 @@ export const useZoom = () => {
73
94
translateX,
74
95
translateY,
75
96
} ) ) ;
76
- } , [ containerRef , contentRef ] ) ;
97
+ } , [ containerRef , contentRef , setState ] ) ;
77
98
78
99
const onMouseEvent = useCallback (
79
100
( e : MouseEvent ) => {
80
101
if ( ! containerRef . current ) return ;
81
- const { x, y } = containerRef . current . getBoundingClientRect ( ) ;
102
+ const { x, y, width , height } = containerRef . current . getBoundingClientRect ( ) ;
82
103
83
104
setState ( ( currentState ) => {
105
+ const { clientX : cx , clientY : cy , type : eventType } = e ;
106
+ const hovered = cx >= x && cx <= x + width && cy >= y && cy <= y + height ;
107
+ if ( ! hovered ) return currentState ;
108
+
84
109
const { containerHeight, containerWidth, contentHeight, contentWidth } = currentState ;
85
110
const ratioX = contentWidth < containerWidth ? contentWidth / contentHeight + 1 : 1 ;
86
111
const ratioY = contentHeight < containerHeight ? contentHeight / contentWidth + 1 : 1 ;
87
- const multiplierX = ( ( e . clientX - x ) / containerWidth ) * 2 * 1.2 * ratioX - 0.2 * ratioX ;
88
- const multiplierY = ( ( e . clientY - y ) / containerHeight ) * 2 * 1.2 * ratioY - 0.2 * ratioY ;
112
+ const multiplierX = ( ( cx - x ) / containerWidth ) * 2 * 1.2 * ratioX - 0.2 * ratioX ;
113
+ const multiplierY = ( ( cy - y ) / containerHeight ) * 2 * 1.2 * ratioY - 0.2 * ratioY ;
89
114
90
- const clicked = e . type === "click" && ( e as any ) . pointerType !== "touch" ;
115
+ const clicked = eventType === "click" && ( e as any ) . pointerType !== "touch" ;
91
116
const zoomed = clicked ? ! currentState . zoomed : currentState . zoomed ;
92
117
const update = {
93
118
...currentState ,
@@ -108,7 +133,7 @@ export const useZoom = () => {
108
133
} ;
109
134
} ) ;
110
135
} ,
111
- [ containerRef ]
136
+ [ containerRef , setState ]
112
137
) ;
113
138
114
139
const onToggleZoom = useCallback (
@@ -151,6 +176,7 @@ export const useZoom = () => {
151
176
const contentStyle = {
152
177
transform : `translate(${ translateX } px, ${ translateY } px) scale(${ scale } )` ,
153
178
transition : state . transition ,
179
+ ...( state . contentScale < 1 && { cursor : state . zoomed ? "zoom-out" : "zoom-in" } ) ,
154
180
} ;
155
181
156
182
return {
@@ -173,8 +199,8 @@ export const ZoomContainer = ({
173
199
children,
174
200
render,
175
201
} : {
176
- children ?: React . ReactNode ;
177
- render ?: ( props : ReturnType < typeof useZoom > [ "renderProps" ] ) => React . ReactChild ;
202
+ children ?: ReactNode ;
203
+ render ?: ( props : ReturnType < typeof useZoom > [ "renderProps" ] ) => ReactNode ;
178
204
} ) => {
179
205
const { containerProps, contentProps, renderProps } = useZoom ( ) ;
180
206
0 commit comments