Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ba46584
Implement image zoom component with drag functionality
MiguelOrtizUp Mar 30, 2026
4456620
Add ImageMagnifier component for image zooming
MiguelOrtizUp Mar 30, 2026
c4aa78b
Create README for imageZoom component
MiguelOrtizUp Mar 30, 2026
134ca59
Add metadata for image-zoom component
MiguelOrtizUp Mar 30, 2026
7867099
Initialize package.json for image-zoom component
MiguelOrtizUp Mar 30, 2026
5e6866a
Add retoolCustomComponentLibraryConfig to package.json
MiguelOrtizUp Mar 30, 2026
68e8fa1
Update custom-component-support dependency to latest
MiguelOrtizUp Mar 30, 2026
825e1fd
Update package.json for my-react-app configuration
MiguelOrtizUp Mar 30, 2026
934ce4f
Implement ZoomImage component with drag functionality
MiguelOrtizUp Mar 30, 2026
df4323a
Add ImageMagnifier component for image zooming
MiguelOrtizUp Mar 30, 2026
424cec0
Delete components/image-zoom/components directory
MiguelOrtizUp Mar 30, 2026
d0e4b06
Delete components/image-zoom/index
MiguelOrtizUp Mar 30, 2026
bcaac96
Rename components/image-zoom/README.md to components/image-zoom/sourc…
MiguelOrtizUp Mar 30, 2026
9bebf47
Rename components/image-zoom/metadata.json to components/image-zoom/s…
MiguelOrtizUp Mar 30, 2026
ab17b4c
Add README.md for image zoom component
MiguelOrtizUp Mar 30, 2026
07c5964
Add metadata.json for image zoom component
MiguelOrtizUp Mar 30, 2026
e1f6ce2
Add files via upload
MiguelOrtizUp Mar 30, 2026
58866f0
Rename Gemini_Generated_Image_ol3ah2ol3ah2ol3a.png to cover.png
MiguelOrtizUp Mar 30, 2026
3b036c1
Update tags in image-zoom metadata
MiguelOrtizUp Mar 30, 2026
43e2135
Initial plan
Copilot Apr 1, 2026
9ea26ac
Merge pull request #1 from MiguelOrtizUp/copilot/build-interactive-pi…
MiguelOrtizUp Apr 1, 2026
97c0970
Revert "[WIP] Add interactive pipeline network builder canvas with AI…
MiguelOrtizUp Apr 1, 2026
b0e37ec
Merge pull request #2 from MiguelOrtizUp/revert-1-copilot/build-inter…
MiguelOrtizUp Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions components/image-zoom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# imageZoom

A custom [Retool](https://retool.com/) component that adds image zoom and magnification capabilities to your Retool applications.

## Features

- **Normal mode** — Click to zoom in/out on the image; drag to pan while zoomed in.
- **Glass mode** — A circular magnifier lens follows the cursor over the image.

## Retool State Properties

| Property | Type | Default | Description |
|---|---|---|---|
| `imageUrl` | `string` | Sample flower image | URL of the image to display |
| `imageAlt` | `string` | `""` | Alt text for the image |
| `zoomMode` | `string` | `"normal"` | Zoom mode: `normal`, `glass`, `self`, or `side` |
| `zoomLevel` | `number` | `3` | Magnification multiplier |

## Zoom Modes

### `normal`
Click anywhere on the image to zoom in to the configured `zoomLevel`. Click again to zoom back out. While zoomed in, drag the image to pan.

### `glass`
A circular magnifier lens (180×180 px by default) follows the mouse cursor and shows the area under it at `zoomLevel` magnification.

1 change: 1 addition & 0 deletions components/image-zoom/cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions components/image-zoom/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"id": "image-zoom",
"title": "Image Zoom",
"author": "@MiguelOrtiz",
"shortDescription": "A component that adds image zoom and magnification capabilities to your Retool applications.",
"tags": ["Custom", "React", "UI Components"]
}
42 changes: 42 additions & 0 deletions components/image-zoom/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "my-react-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@tryretool/custom-component-support": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"dev": "npx retool-ccl dev",
"deploy": "npx retool-ccl deploy",
"test": "vitest"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"postcss-modules": "^6.0.0",
"prettier": "^3.0.3",
"typescript": "^5.4.0",
"vitest": "^4.0.17"
}
}
189 changes: 189 additions & 0 deletions components/image-zoom/source/components/imageMagnifier.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import React, { useRef, useState } from 'react'

const ImageMagnifier = ({
src,
className = '',
width,
height,
alt,
mode = 'glass',
magnifierHeight = 150,
magnifierWidth = 150,
zoomLevel = 3
}) => {
const [showMagnifier, setShowMagnifier] = useState(false)
const [[imgWidth, imgHeight], setSize] = useState([0, 0])
const [[x, y], setXY] = useState([0, 0])

const mouseEnter = (e) => {
const el = e.currentTarget

const { width, height } = el.getBoundingClientRect()
setSize([width, height])
setShowMagnifier(true)
}

const mouseLeave = (e) => {
e.preventDefault()
setShowMagnifier(false)
}

const mouseMove = (e) => {
const el = e.currentTarget
const { top, left } = el.getBoundingClientRect()

const x = e.pageX - left - window.scrollX
const y = e.pageY - top - window.scrollY

setXY([x, y])
}

return (
<div
style={{
position: 'relative',
display: 'inline-flex',
height: '100%',
cursor:
mode === 'glass'
? 'none'
: mode === 'self' || mode === 'side'
? 'crosshair'
: 'default'
}}
>
<div
style={{
position: 'relative',
display: 'inline-block',
height: '100%'
}}
>
<img
src={src}
className={className}
width={width}
height={height}
alt={alt}
onMouseEnter={(e) => mouseEnter(e)}
onMouseLeave={(e) => mouseLeave(e)}
onMouseMove={(e) => mouseMove(e)}
/>
{mode === 'glass' ? (
<>
<div
style={{
width: '100%',
height: '100%',
backgroundColor: showMagnifier ? '#00000022' : 'transparent',
transition: 'all 500ms',
position: 'absolute',
top: 0,
left: 0,
pointerEvents: 'none'
}}
/>
<div
style={{
display: showMagnifier ? '' : 'none',
position: 'absolute',
pointerEvents: 'none',
height: `${magnifierHeight}px`,
width: `${magnifierWidth}px`,
opacity: '1',
border: '1px solid lightgrey',
backgroundColor: 'white',
borderRadius: '999px',
backgroundImage: `url('${src}')`,
backgroundRepeat: 'no-repeat',
top: `${y - magnifierHeight / 2}px`,
left: `${x - magnifierWidth / 2}px`,
backgroundSize: `${imgWidth * zoomLevel}px ${imgHeight * zoomLevel}px`,
backgroundPositionX: `${-x * zoomLevel + magnifierWidth / 2}px`,
backgroundPositionY: `${-y * zoomLevel + magnifierHeight / 2}px`
}}
/>
</>
) : mode === 'side' ? (
<>
<div
style={{
width: '100%',
height: '100%',
backgroundColor: showMagnifier ? '#00000033' : 'transparent',
transition: 'all 500ms',
position: 'absolute',
top: 0,
left: 0,
pointerEvents: 'none'
}}
/>
<div
style={{
display: showMagnifier ? '' : 'none',
position: 'absolute',
pointerEvents: 'none',
height: `${magnifierHeight}px`,
width: `${magnifierWidth}px`,
opacity: '1',
border: '1px solid lightgrey',
backgroundColor: 'white',
borderRadius: '5px',
backgroundImage: `url('${src}')`,
backgroundRepeat: 'no-repeat',
top: `${y - magnifierHeight / 2}px`,
left: `${x - magnifierWidth / 2}px`,
backgroundSize: `${imgWidth * 1}px ${imgHeight * 1}px`,
backgroundPositionX: `${-x * 1 + magnifierWidth / 2}px`,
backgroundPositionY: `${-y * 1 + magnifierHeight / 2}px`
}}
/>
</>
) : mode === 'self' ? (
<div
style={{
opacity: showMagnifier ? 1 : 0,
transition: 'opacity 300ms',
transitionDelay: '200ms',
position: 'absolute',
width: '100%',
height: '100%',
top: 0,
left: 0,
pointerEvents: 'none',
border: '1px solid lightgrey',
backgroundColor: 'white',
borderRadius: '5px',
backgroundImage: `url('${src}')`,
backgroundRepeat: 'no-repeat',
backgroundSize: `${imgWidth * zoomLevel}px ${imgHeight * zoomLevel}px`,
backgroundPositionX: `${-x * zoomLevel + x}px`,
backgroundPositionY: `${-y * zoomLevel + y}px`
}}
/>
) : null}
</div>
{mode === 'side' ? (
<div
style={{
display: showMagnifier ? '' : 'none',
pointerEvents: 'none',
width: `400px`,
height: `400px`,
opacity: '1',
border: '1px solid lightgrey',
backgroundColor: 'white',
borderRadius: '10px',
backgroundImage: `url('${src}')`,
backgroundRepeat: 'no-repeat',
backgroundSize: `${imgWidth * zoomLevel}px ${imgHeight * zoomLevel}px`,
backgroundPositionX: `${-x * zoomLevel + 200}px`,
backgroundPositionY: `${-y * zoomLevel + 200}px`
}}
/>
) : null}
</div>
)
}

export default ImageMagnifier
Loading