@@ -3,16 +3,18 @@ import { createPortal } from 'react-dom';
33import Mermaid from '@theme/Mermaid' ;
44import styles from './styles.module.css' ;
55
6- const ZoomableMermaid = ( { children, title } ) => {
6+ const ZoomableMermaid = ( { children, title, defaultZoom = 1.2 } ) => {
77 const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
88 const [ isHovered , setIsHovered ] = useState ( false ) ;
9+ const [ zoomLevel , setZoomLevel ] = useState ( defaultZoom ) ; // Use defaultZoom prop
910 const modalRef = useRef ( null ) ;
1011 const containerRef = useRef ( null ) ;
1112
1213 const openModal = useCallback ( ( ) => {
1314 setIsModalOpen ( true ) ;
15+ setZoomLevel ( defaultZoom ) ; // Reset to default zoom when opening
1416 document . body . style . overflow = 'hidden' ;
15- } , [ ] ) ;
17+ } , [ defaultZoom ] ) ;
1618
1719 const closeModal = useCallback ( ( ) => {
1820 setIsModalOpen ( false ) ;
@@ -23,6 +25,18 @@ const ZoomableMermaid = ({ children, title }) => {
2325 }
2426 } , [ ] ) ;
2527
28+ const zoomIn = useCallback ( ( ) => {
29+ setZoomLevel ( prev => Math . min ( prev + 0.2 , 3.0 ) ) ; // Max 300%
30+ } , [ ] ) ;
31+
32+ const zoomOut = useCallback ( ( ) => {
33+ setZoomLevel ( prev => Math . max ( prev - 0.2 , 0.5 ) ) ; // Min 50%
34+ } , [ ] ) ;
35+
36+ const resetZoom = useCallback ( ( ) => {
37+ setZoomLevel ( defaultZoom ) ; // Reset to custom default instead of hardcoded 1.2
38+ } , [ defaultZoom ] ) ;
39+
2640 useEffect ( ( ) => {
2741 const handleEscape = ( e ) => {
2842 if ( e . key === 'Escape' && isModalOpen ) {
@@ -36,9 +50,25 @@ const ZoomableMermaid = ({ children, title }) => {
3650 }
3751 } ;
3852
53+ const handleKeydown = ( e ) => {
54+ if ( ! isModalOpen ) return ;
55+
56+ if ( e . key === '=' || e . key === '+' ) {
57+ e . preventDefault ( ) ;
58+ zoomIn ( ) ;
59+ } else if ( e . key === '-' ) {
60+ e . preventDefault ( ) ;
61+ zoomOut ( ) ;
62+ } else if ( e . key === '0' ) {
63+ e . preventDefault ( ) ;
64+ resetZoom ( ) ;
65+ }
66+ } ;
67+
3968 if ( isModalOpen ) {
4069 document . addEventListener ( 'keydown' , handleEscape ) ;
4170 document . addEventListener ( 'mousedown' , handleClickOutside ) ;
71+ document . addEventListener ( 'keydown' , handleKeydown ) ;
4272
4373 // Focus the modal content when opened
4474 setTimeout ( ( ) => {
@@ -51,8 +81,9 @@ const ZoomableMermaid = ({ children, title }) => {
5181 return ( ) => {
5282 document . removeEventListener ( 'keydown' , handleEscape ) ;
5383 document . removeEventListener ( 'mousedown' , handleClickOutside ) ;
84+ document . removeEventListener ( 'keydown' , handleKeydown ) ;
5485 } ;
55- } , [ isModalOpen , closeModal ] ) ;
86+ } , [ isModalOpen , closeModal , zoomIn , zoomOut , resetZoom ] ) ;
5687
5788 // Cleanup on unmount
5889 useEffect ( ( ) => {
@@ -77,7 +108,7 @@ const ZoomableMermaid = ({ children, title }) => {
77108 aria-describedby = "modal-description"
78109 >
79110 < div
80- className = { styles . modalContent }
111+ className = { styles . modalContent }
81112 ref = { modalRef }
82113 tabIndex = { - 1 }
83114 >
@@ -87,24 +118,76 @@ const ZoomableMermaid = ({ children, title }) => {
87118 { title }
88119 </ h3 >
89120 ) }
90- < button
91- className = { styles . closeButton }
92- onClick = { closeModal }
93- aria-label = "关闭放大视图"
94- type = "button"
95- >
96- < svg width = "24" height = "24" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" >
97- < line x1 = "18" y1 = "6" x2 = "6" y2 = "18" />
98- < line x1 = "6" y1 = "6" x2 = "18" y2 = "18" />
99- </ svg >
100- </ button >
121+ < div className = { styles . modalControls } >
122+ < span className = { styles . zoomIndicator } >
123+ { Math . round ( zoomLevel * 100 ) } %
124+ </ span >
125+ < button
126+ className = { styles . zoomButton }
127+ onClick = { zoomOut }
128+ disabled = { zoomLevel <= 0.5 }
129+ aria-label = "缩小图表"
130+ type = "button"
131+ title = "缩小 (快捷键: -)"
132+ >
133+ < svg width = "18" height = "18" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" >
134+ < circle cx = "11" cy = "11" r = "8" />
135+ < path d = "M8 11h6" />
136+ < path d = "m21 21-4.35-4.35" />
137+ </ svg >
138+ </ button >
139+ < button
140+ className = { styles . resetButton }
141+ onClick = { resetZoom }
142+ aria-label = { `重置到默认缩放 ${ Math . round ( defaultZoom * 100 ) } %` }
143+ type = "button"
144+ title = { `重置到默认缩放 ${ Math . round ( defaultZoom * 100 ) } % (快捷键: 0)` }
145+ >
146+ < svg width = "18" height = "18" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" >
147+ < path d = "M3 3l18 18" />
148+ < path d = "m19 4-7 7-7-7" />
149+ < path d = "m5 20 7-7 7 7" />
150+ </ svg >
151+ </ button >
152+ < button
153+ className = { styles . zoomButton }
154+ onClick = { zoomIn }
155+ disabled = { zoomLevel >= 3.0 }
156+ aria-label = "放大图表"
157+ type = "button"
158+ title = "放大 (快捷键: +)"
159+ >
160+ < svg width = "18" height = "18" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" >
161+ < circle cx = "11" cy = "11" r = "8" />
162+ < path d = "M8 11h6" />
163+ < path d = "M11 8v6" />
164+ < path d = "m21 21-4.35-4.35" />
165+ </ svg >
166+ </ button >
167+ < button
168+ className = { styles . closeButton }
169+ onClick = { closeModal }
170+ aria-label = "关闭放大视图"
171+ type = "button"
172+ >
173+ < svg width = "18" height = "18" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" >
174+ < line x1 = "18" y1 = "6" x2 = "6" y2 = "18" />
175+ < line x1 = "6" y1 = "6" x2 = "18" y2 = "18" />
176+ </ svg >
177+ </ button >
178+ </ div >
101179 </ div >
102180 < div
103181 className = { styles . modalBody }
104182 id = "modal-description"
105183 aria-label = "放大的 Mermaid 图表"
106184 >
107- < Mermaid value = { children } />
185+ < div
186+ className = { styles . diagramContainer }
187+ style = { { transform : `scale(${ zoomLevel } )` , transformOrigin : 'center' } }
188+ >
189+ < Mermaid value = { children } />
190+ </ div >
108191 </ div >
109192 </ div >
110193 </ div >
0 commit comments