View Magnifier
bucharitesh/view-magnifierFreeComponent
A view magnifier component for Next.js apps with next-themes and Tailwind CSS, supporting system, light, and dark modes.
Install it
npx shadcn@latest add https://bucharitesh.in/r/view-magnifier.jsonSource
1'use client';23import { cn } from '@/lib/utils';4import { animate, motion, useMotionValue, useTransform } from 'motion/react';5import type React from 'react';6import { useCallback, useEffect, useRef, useState } from 'react';78interface ViewMagnifierProps {9 children: React.ReactNode;10 className?: string;11 maxScale?: number;12 onScaleChange?: (isActive: boolean) => void;13 onMaxScaleReached?: (isAtMax: boolean) => void;14}1516const ViewMagnifier: React.FC<ViewMagnifierProps> = ({17 className,18 children,19 maxScale = 1.8,20 onScaleChange,21 onMaxScaleReached,22 ...props23}) => {24 const [isMouseDown, setIsMouseDown] = useState<boolean>(false);25 const [zoomLevel, setZoomLevel] = useState<number>(100);26 const [isAtMaxScale, setIsAtMaxScale] = useState<boolean>(false);27 const containerRef = useRef<HTMLDivElement>(null);28 const startX = useRef<number>(0);29 const initialScale = useRef<number>(1);30 const scale = useMotionValue(1);31 const opacity = useTransform(scale, [1, maxScale], [0, 1]);32 const containerScale = useTransform(scale, [1, maxScale], [1, 1.6]);3334 // Monitor scale changes for max scale callback35 useEffect(() => {36 const unsubscribe = scale.on('change', (latestScale) => {37 const newIsAtMaxScale = Math.abs(latestScale - maxScale) < 0.01;38 if (newIsAtMaxScale !== isAtMaxScale) {39 setIsAtMaxScale(newIsAtMaxScale);40 onMaxScaleReached?.(newIsAtMaxScale);41 }42 });4344 return () => unsubscribe();45 }, [scale, maxScale, isAtMaxScale, onMaxScaleReached]);4647 const handleZoomAnimation = useCallback(48 (targetScale: number) => {49 animate(scale, targetScale, {50 type: 'spring',51 stiffness: 400,52 damping: 30,53 onUpdate: (latest) => setZoomLevel(Math.round(latest * 100)),54 });55 },56 [scale]57 );5859 const handlePointerDown = useCallback(60 (e: React.PointerEvent<HTMLButtonElement>): void => {61 setIsMouseDown(true);62 startX.current = e.clientX;63 initialScale.current = scale.get();64 e.currentTarget.setPointerCapture(e.pointerId);65 onScaleChange?.(true);66 },67 [scale, onScaleChange]68 );6970 const handlePointerUp = useCallback(71 (e: React.PointerEvent<HTMLButtonElement>): void => {72 if (isMouseDown) {73 setIsMouseDown(false);74 handleZoomAnimation(1);75 e.currentTarget.releasePointerCapture(e.pointerId);76 onScaleChange?.(false);77 }78 },79 [isMouseDown, handleZoomAnimation, onScaleChange]80 );8182// … truncated
What it pulls in
npm packages
Files it writes
More from bucharitesh
All 26 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Bookbook | bucharitesh | Components | Free | 1 dep · 2 files | |
| Game of Lifegame-of-life | bucharitesh | Components | Free | no deps | |
| Magical Mousemagical-mouse | bucharitesh | Components | Free | 1 dep | |
| Metallic Buttonmetallic-button | bucharitesh | Components | Free | 2 deps | |
| Modern Progressmodern-progress | bucharitesh | Components | Free | 1 dep | |
| Pixel Iconpixel-icon | bucharitesh | Components | Free | no deps | |
| Split Text Effectsplit-text-effect | bucharitesh | Components | Free | 1 dep | |
| Text Highlighttext-highlight | bucharitesh | Components | Free | 1 dep |
