feat: add visual style picker and contextual previews
This commit is contained in:
75
src/components/HoverImagePreview.tsx
Normal file
75
src/components/HoverImagePreview.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import type { PointerEvent } from 'react';
|
||||
|
||||
type PreviewState = {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
};
|
||||
|
||||
function parseRatio(aspectRatio?: string) {
|
||||
if (!aspectRatio || aspectRatio === 'long') return aspectRatio === 'long' ? 1 / 3 : 1;
|
||||
const [w, h] = aspectRatio.split(':').map(Number);
|
||||
return w && h ? w / h : 1;
|
||||
}
|
||||
|
||||
function nextPreviewState(event: PointerEvent<HTMLElement>, aspectRatio?: string): PreviewState {
|
||||
const gap = 18;
|
||||
const margin = 12;
|
||||
const ratio = parseRatio(aspectRatio);
|
||||
const width = Math.min(620, Math.max(280, window.innerWidth * 0.42));
|
||||
const height = Math.min(window.innerHeight * 0.82, width / ratio);
|
||||
let left = event.clientX + gap;
|
||||
let top = event.clientY + gap;
|
||||
|
||||
if (left + width > window.innerWidth - margin) {
|
||||
left = event.clientX - width - gap;
|
||||
}
|
||||
if (top + height > window.innerHeight - margin) {
|
||||
top = window.innerHeight - height - margin;
|
||||
}
|
||||
return {
|
||||
left: Math.max(margin, left),
|
||||
top: Math.max(margin, top),
|
||||
width,
|
||||
};
|
||||
}
|
||||
|
||||
export function HoverImagePreview({
|
||||
src,
|
||||
alt,
|
||||
imageClassName,
|
||||
aspectRatio,
|
||||
}: {
|
||||
src: string;
|
||||
alt: string;
|
||||
imageClassName?: string;
|
||||
aspectRatio?: string;
|
||||
}) {
|
||||
const [preview, setPreview] = useState<PreviewState | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className={imageClassName}
|
||||
onPointerMove={event => {
|
||||
if (event.pointerType === 'touch') return;
|
||||
setPreview(nextPreviewState(event, aspectRatio));
|
||||
}}
|
||||
onPointerLeave={() => setPreview(null)}
|
||||
/>
|
||||
{preview && (
|
||||
<div
|
||||
className="pointer-events-none fixed z-[90] rounded-[8px] bg-white p-2 shadow-2xl ring-1 ring-white/20"
|
||||
style={{ left: preview.left, top: preview.top, width: preview.width }}
|
||||
>
|
||||
<img src={src} alt="" className="max-h-[82vh] w-full object-contain" style={{ aspectRatio: parseRatio(aspectRatio) }} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user