新增首页灵感推荐瀑布流 #196
@@ -0,0 +1,81 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
// TODO: 后续改为由服务器下发灵感资源;届时保留此组件的展示与预览交互,移除本地目录扫描。
|
||||
const INSPIRATION_IMAGES = Object.entries(
|
||||
import.meta.glob('./assets/inspiration/*.{webp,png,jpg,jpeg}', {
|
||||
eager: true,
|
||||
import: 'default',
|
||||
query: '?url',
|
||||
}),
|
||||
)
|
||||
.sort(([left], [right]) =>
|
||||
left.localeCompare(right, undefined, { numeric: true }),
|
||||
)
|
||||
.map(([, image]) => image as string);
|
||||
|
||||
export default function InspirationGallery() {
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedImage) {
|
||||
return;
|
||||
}
|
||||
const previousOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
setSelectedImage(null);
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.body.style.overflow = previousOverflow;
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [selectedImage]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="columns-3 gap-3 max-[760px]:columns-2 max-[460px]:columns-1">
|
||||
{INSPIRATION_IMAGES.map((image, index) => (
|
||||
<button
|
||||
className="mb-3 block w-full cursor-zoom-in break-inside-avoid overflow-hidden rounded-lg border-0 bg-transparent p-0 shadow-(--platform-panel-shadow) transition duration-200 hover:-translate-y-0.5 hover:shadow-(--platform-profile-action-shadow) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--platform-warm-text)"
|
||||
type="button"
|
||||
key={image}
|
||||
aria-label={`查看灵感图片 ${index + 1}`}
|
||||
onClick={() => setSelectedImage(image)}
|
||||
>
|
||||
<img
|
||||
className="block h-auto w-full"
|
||||
src={image}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{selectedImage
|
||||
? createPortal(
|
||||
<div
|
||||
className="fixed inset-0 z-[1000] grid cursor-zoom-out place-items-center bg-black/80 p-6"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="查看灵感图片"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
>
|
||||
<img
|
||||
className="block max-h-[92vh] max-w-[92vw] cursor-default object-contain shadow-2xl"
|
||||
src={selectedImage}
|
||||
alt="放大的灵感图片"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
/>
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 180 KiB |