Context Lens

Superposition en vignette qui assombrit le canevas en dehors d'une region de focus circulaire. Installez Context Lens depuis le registre VLLNT UI avec la CLI shadcn.

Signaler un bug

Superposition en vignette qui assombrit le canevas en dehors d'une region de focus circulaire. Fait partie de la famille utility dans VLLNT UI, il est publie comme entree de registre lisible par machine — copiez la source directement dans votre application avec la CLI shadcn et possedez-la, sans dependance d'execution a une bibliotheque de composants.

Apercu

Basculez entre clair et sombre pour inspecter l'apercu Storybook integre.

Installation

Ajoutez Context Lens a votre projet avec la CLI shadcn. La source arrive dans votre code, prete a etre adaptee :

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/context-lens.json

Source

"use client"; import { type ComponentPropsWithoutRef, useId } from "react"; import { cn } from "../../lib/utils"; /** * Focus point — pixel coordinates relative to the canvas viewport. * * @public */ export type ContextLensFocus = { /** Center X of the lens. */ cx: number; /** Center Y of the lens. */ cy: number; /** Inner radius — fully transparent inside this circle. */ inner: number; /** Outer radius — full dim beyond this circle. */ outer: number; }; /** * Localizable strings. * * @public */ export type ContextLensLabels = { /** Aria-label for the lens layer. Defaults to `"Context lens"`. */ region?: string; }; const DEFAULT_LABELS = { region: "Context lens", } as const satisfies Required<ContextLensLabels>; /** * Props for {@link ContextLens}. * * @public */ export type ContextLensProps = { /** Lens geometry. When `null`, no lens renders (full surface unobscured). */ focus: ContextLensFocus | null; /** Localizable strings. */ labels?: ContextLensLabels; /** Dim opacity outside the outer ring (0..1). Defaults to `0.55`. */ opacity?: number; } & ComponentPropsWithoutRef<"svg">; const clamp01 = (value: number): number => { if (value < 0) { return 0; } if (value > 1) { return 1; } return value; }; const safeRadius = (value: number): number => (value < 0 ? 0 : value); /** * Vignette overlay that dims the canvas outside a circular focus * region. Use to draw the eye to a selection, an active run, or any * single object the user must attend to. Pure presentation; the host * computes the focus center + radii from the active selection's * bounding box. * * The lens is `pointer-events: none` — host gestures pass through. * * @example * ```tsx * <div className="relative h-screen w-screen"> * <Canvas /> * <ContextLens focus={{ cx: 480, cy: 320, inner: 90, outer: 180 }} /> * </div> * ``` * * @public */ export const ContextLens = ({ ref, ...props }: ContextLensProps & { ref?: React.Ref<SVGSVGElement> }) => { const { className, focus, labels, opacity = 0.55, ...rest } = props; const maskId = useId(); if (!focus) { return null; } const resolvedLabels = { ...DEFAULT_LABELS, ...labels }; const inner = safeRadius(focus.inner); const outer = safeRadius(Math.max(focus.outer, inner)); const fillOpacity = clamp01(opacity); return ( <svg aria-hidden="true" aria-label={resolvedLabels.region} className={cn( "pointer-events-none absolute inset-0 z-20 h-full w-full", className, )} data-context-lens ref={ref} {...rest} > <defs> <radialGradient cx={focus.cx} cy={focus.cy} data-context-lens-gradient gradientUnits="userSpaceOnUse" id={maskId} r={outer === 0 ? 1 : outer} > <stop offset="0%" stopColor="black" stopOpacity={0} /> <stop offset={outer === 0 ? "100%" : `${(inner / outer) * 100}%`} stopColor="black" stopOpacity={0} /> <stop offset="100%" stopColor="black" stopOpacity={1} /> </radialGradient> </defs> <rect data-context-lens-dim fill="black" fillOpacity={fillOpacity} height="100%" mask={`url(#${maskId}-mask)`} width="100%" x={0} y={0} /> <mask id={`${maskId}-mask`}> <rect fill="white" height="100%" width="100%" x={0} y={0} /> <circle cx={focus.cx} cy={focus.cy} fill={`url(#${maskId})`} r={outer} /> </mask> </svg> ); }; ContextLens.displayName = "ContextLens";

Stories

Explorez chaque variante et etat dans le Storybook interactif :

Apercu

Basculez entre clair et sombre pour inspecter l'apercu Storybook integre.

Dependances

  • @vllnt/ui@^0.3.0