Skip to content

Commit 02eb18c

Browse files
committed
update
1 parent 076f11a commit 02eb18c

File tree

6 files changed

+254
-122
lines changed

6 files changed

+254
-122
lines changed

src/components/ClickToZoom.astro

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
---
2+
import ClickToZoomCustom from "./ClickToZoomCustom.tsx"
3+
24
export type Props = {
35
src: string
46
alt?: string
57
style?: string
68
caption?: string
79
}
810
const { src, alt, style, caption } = Astro.props as Props
9-
10-
/**
11-
* This component adds the classNames required by the scripts/click-to-zoom.ts script.
12-
*/
1311
---
1412

1513
<div class="click-to-zoom-container">
16-
<img src={src} alt={alt} style={style} class="click-to-zoom" data-caption={caption} />
17-
{caption && <p class="click-to-zoom-caption">{caption}</p>}
14+
<ClickToZoomCustom src={src} alt={alt} style={style} caption={caption} client:only="react" />
1815
</div>
1916

2017
<style>
@@ -24,59 +21,4 @@ const { src, alt, style, caption } = Astro.props as Props
2421
align-items: center;
2522
margin-bottom: 2rem;
2623
}
27-
.click-to-zoom {
28-
margin-top: 1em;
29-
max-height: 50vh;
30-
cursor: zoom-in;
31-
display: block;
32-
margin-left: auto;
33-
margin-right: auto;
34-
}
35-
.click-to-zoom-caption {
36-
font-size: 0.8rem;
37-
text-align: center;
38-
margin-top: 0.5rem;
39-
color: var(--color-text-secondary);
40-
}
41-
</style>
42-
<style is:global>
43-
#expanded-image-preview {
44-
max-height: 90vh;
45-
max-width: 95vw;
46-
}
47-
#expanded-image-wrapper {
48-
width: 100vw;
49-
height: 100vh;
50-
position: fixed;
51-
top: 0;
52-
left: 0;
53-
background: rgba(0, 0, 0, 0.6);
54-
backdrop-filter: blur(5px);
55-
-webkit-backdrop-filter: blur(5px);
56-
z-index: 100;
57-
flex-direction: column;
58-
justify-content: center;
59-
align-content: center;
60-
align-items: center;
61-
margin: 0;
62-
cursor: zoom-out;
63-
display: none;
64-
}
65-
@media (min-width: 50em) {
66-
#expanded-image-wrapper {
67-
display: flex;
68-
}
69-
}
70-
71-
.expanded-image-caption {
72-
background-color: rgba(0, 0, 0, 0.7);
73-
color: white;
74-
padding: 8px 15px;
75-
border-radius: 5px;
76-
font-size: 0.9rem;
77-
text-align: center;
78-
max-width: 80%;
79-
z-index: 101;
80-
margin-top: 1rem;
81-
}
8224
</style>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/* Thumbnail image styles */
2+
.thumbnailImage {
3+
margin-top: 1em;
4+
max-height: 50vh;
5+
cursor: zoom-in;
6+
display: block;
7+
margin-left: auto;
8+
margin-right: auto;
9+
transition: opacity 0.2s ease;
10+
max-width: none !important; /* Override global article * styles */
11+
}
12+
13+
.thumbnailImage:hover {
14+
opacity: 0.9;
15+
}
16+
17+
.thumbnailImage:focus {
18+
outline: 2px solid var(--blue-500);
19+
outline-offset: 2px;
20+
}
21+
22+
/* Caption styles */
23+
.caption {
24+
font-size: 0.8rem;
25+
text-align: center;
26+
margin-top: 0.5rem;
27+
color: var(--color-text-secondary);
28+
margin-bottom: 0;
29+
}
30+
31+
/* Modal backdrop */
32+
.modalBackdrop {
33+
position: fixed;
34+
top: 0;
35+
left: 0;
36+
width: 100vw;
37+
height: 100vh;
38+
background: rgba(0, 0, 0, 0.8);
39+
backdrop-filter: blur(5px);
40+
-webkit-backdrop-filter: blur(5px);
41+
z-index: 1000;
42+
display: flex;
43+
flex-direction: column;
44+
justify-content: flex-start;
45+
align-items: center;
46+
padding: 20px;
47+
box-sizing: border-box;
48+
cursor: zoom-out;
49+
overflow: auto;
50+
}
51+
52+
/* Expanded image styles */
53+
.expandedImage {
54+
max-height: 90vh;
55+
max-width: 90vw;
56+
object-fit: contain;
57+
cursor: zoom-in;
58+
transition: transform 0.3s ease;
59+
user-select: none;
60+
-webkit-user-select: none;
61+
border-radius: 4px;
62+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
63+
margin-top: auto;
64+
margin-bottom: auto;
65+
transform-origin: center center;
66+
}
67+
68+
/* Perfect zoom level - not too much, not too little */
69+
.expandedImage.zoomed {
70+
transform: scale(1.3);
71+
cursor: zoom-out;
72+
}
73+
74+
/* Expanded caption */
75+
.expandedCaption {
76+
background-color: rgba(0, 0, 0, 0.7);
77+
color: white;
78+
padding: 8px 15px;
79+
border-radius: 5px;
80+
font-size: 0.9rem;
81+
text-align: center;
82+
max-width: 80%;
83+
margin-top: 1rem;
84+
margin-bottom: 0;
85+
}
86+
87+
/* Responsive adjustments */
88+
@media (max-width: 768px) {
89+
.modalBackdrop {
90+
padding: 10px;
91+
}
92+
93+
.expandedImage {
94+
max-height: 85vh;
95+
max-width: 95vw;
96+
}
97+
98+
.expandedImage.zoomed {
99+
transform: scale(1.2);
100+
}
101+
102+
.expandedCaption {
103+
max-width: 90%;
104+
font-size: 0.8rem;
105+
}
106+
}
107+
108+
/* Smooth animations */
109+
@media (prefers-reduced-motion: no-preference) {
110+
.thumbnailImage {
111+
transition:
112+
opacity 0.2s ease,
113+
transform 0.1s ease;
114+
}
115+
116+
.thumbnailImage:active {
117+
transform: scale(0.98);
118+
}
119+
120+
.expandedImage {
121+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
122+
}
123+
}
124+
125+
/* Respect reduced motion preference */
126+
@media (prefers-reduced-motion: reduce) {
127+
.thumbnailImage,
128+
.expandedImage {
129+
transition: none;
130+
}
131+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import React, { useState, useEffect, useRef } from "react"
2+
import { createPortal } from "react-dom"
3+
import styles from "./ClickToZoomCustom.module.css"
4+
5+
interface ClickToZoomCustomProps {
6+
src: string
7+
alt?: string
8+
style?: string | React.CSSProperties
9+
caption?: string
10+
}
11+
12+
interface ModalState {
13+
isOpen: boolean
14+
isZoomed: boolean
15+
}
16+
17+
export default function ClickToZoomCustom({ src, alt, style, caption }: ClickToZoomCustomProps) {
18+
const [modalState, setModalState] = useState<ModalState>({ isOpen: false, isZoomed: false })
19+
const imageRef = useRef<HTMLImageElement>(null)
20+
21+
// Parse style prop if it's a string
22+
const parseStyle = (styleValue: string | React.CSSProperties | undefined): React.CSSProperties => {
23+
if (!styleValue) return {}
24+
if (typeof styleValue === "object") return styleValue
25+
26+
// Parse CSS string to object
27+
const styleObj: React.CSSProperties = {}
28+
styleValue.split(";").forEach((rule) => {
29+
const [property, value] = rule.split(":").map((s) => s.trim())
30+
if (property && value) {
31+
// Convert kebab-case to camelCase
32+
const camelProperty = property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
33+
styleObj[camelProperty as keyof React.CSSProperties] = value as any
34+
}
35+
})
36+
return styleObj
37+
}
38+
39+
// Handle keyboard events
40+
useEffect(() => {
41+
const handleKeyDown = (event: KeyboardEvent) => {
42+
if (event.key === "Escape" && modalState.isOpen) {
43+
closeModal()
44+
}
45+
}
46+
47+
if (modalState.isOpen) {
48+
document.addEventListener("keydown", handleKeyDown)
49+
// Prevent body scroll when modal is open
50+
document.body.style.overflow = "hidden"
51+
}
52+
53+
return () => {
54+
document.removeEventListener("keydown", handleKeyDown)
55+
document.body.style.overflow = "unset"
56+
}
57+
}, [modalState.isOpen])
58+
59+
const openModal = () => {
60+
setModalState({ isOpen: true, isZoomed: false })
61+
}
62+
63+
const closeModal = () => {
64+
setModalState({ isOpen: false, isZoomed: false })
65+
}
66+
67+
const toggleZoom = (event: React.MouseEvent) => {
68+
event.stopPropagation()
69+
setModalState((prev) => ({ ...prev, isZoomed: !prev.isZoomed }))
70+
}
71+
72+
const handleBackdropClick = (event: React.MouseEvent) => {
73+
if (event.target === event.currentTarget) {
74+
closeModal()
75+
}
76+
}
77+
78+
const Modal = () => (
79+
<div
80+
className={styles.modalBackdrop}
81+
onClick={handleBackdropClick}
82+
role="dialog"
83+
aria-modal="true"
84+
aria-labelledby="expanded-image"
85+
>
86+
<img
87+
id="expanded-image"
88+
ref={imageRef}
89+
src={src}
90+
alt={alt}
91+
className={`${styles.expandedImage} ${modalState.isZoomed ? styles.zoomed : ""}`}
92+
onClick={toggleZoom}
93+
style={{
94+
cursor: modalState.isZoomed ? "zoom-out" : "zoom-in",
95+
}}
96+
/>
97+
{caption && <p className={styles.expandedCaption}>{caption}</p>}
98+
</div>
99+
)
100+
101+
return (
102+
<>
103+
<img
104+
src={src}
105+
alt={alt}
106+
style={parseStyle(style)}
107+
className={styles.thumbnailImage}
108+
onClick={openModal}
109+
onKeyDown={(e) => e.key === "Enter" && openModal()}
110+
role="button"
111+
tabIndex={0}
112+
aria-label={`Click to zoom image: ${alt || "diagram"}`}
113+
/>
114+
{caption && <p className={styles.caption}>{caption}</p>}
115+
116+
{modalState.isOpen && typeof document !== "undefined" && createPortal(<Modal />, document.body)}
117+
</>
118+
)
119+
}

src/content/ccip/concepts/cross-chain-token/svm/integration-guide.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ The following diagram visualizes the complete decision process based on your min
4848
<ClickToZoom
4949
src="/images/ccip/cct/cct-svm-diagrams/solana_cct_flowchart.svg"
5050
alt="CCIP Cross-Chain Token integration decision flowchart for SVM chains, showing paths based on mint authority control and deployment approaches"
51-
style="display: block; margin: 1rem auto; max-height: 100vh; width: auto;"
51+
style="display: block; margin: 1rem auto; max-height: 70vh; width: auto;"
5252
/>
5353

5454
## Mint Authority Assessment

0 commit comments

Comments
 (0)