131 lines
5.1 KiB
TypeScript
131 lines
5.1 KiB
TypeScript
import { useEffect, useRef, useState } from 'react'
|
|
|
|
export type GallerySize = 'small' | 'medium' | 'large'
|
|
|
|
export const gallerySizeClasses: Record<GallerySize, string> = {
|
|
small: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7',
|
|
medium: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5',
|
|
large: 'grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3',
|
|
}
|
|
|
|
function GridSmallIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
|
|
<rect x="2" y="2" width="4" height="4" rx="0.5" />
|
|
<rect x="8" y="2" width="4" height="4" rx="0.5" />
|
|
<rect x="14" y="2" width="4" height="4" rx="0.5" />
|
|
<rect x="20" y="2" width="2" height="4" rx="0.5" />
|
|
<rect x="2" y="8" width="4" height="4" rx="0.5" />
|
|
<rect x="8" y="8" width="4" height="4" rx="0.5" />
|
|
<rect x="14" y="8" width="4" height="4" rx="0.5" />
|
|
<rect x="20" y="8" width="2" height="4" rx="0.5" />
|
|
<rect x="2" y="14" width="4" height="4" rx="0.5" />
|
|
<rect x="8" y="14" width="4" height="4" rx="0.5" />
|
|
<rect x="14" y="14" width="4" height="4" rx="0.5" />
|
|
<rect x="20" y="14" width="2" height="4" rx="0.5" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function GridMediumIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
|
|
<rect x="2" y="2" width="6" height="6" rx="1" />
|
|
<rect x="10" y="2" width="6" height="6" rx="1" />
|
|
<rect x="18" y="2" width="4" height="6" rx="1" />
|
|
<rect x="2" y="10" width="6" height="6" rx="1" />
|
|
<rect x="10" y="10" width="6" height="6" rx="1" />
|
|
<rect x="18" y="10" width="4" height="6" rx="1" />
|
|
<rect x="2" y="18" width="6" height="4" rx="1" />
|
|
<rect x="10" y="18" width="6" height="4" rx="1" />
|
|
<rect x="18" y="18" width="4" height="4" rx="1" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function GridLargeIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
|
|
<rect x="2" y="2" width="9" height="9" rx="1.5" />
|
|
<rect x="13" y="2" width="9" height="9" rx="1.5" />
|
|
<rect x="2" y="13" width="9" height="9" rx="1.5" />
|
|
<rect x="13" y="13" width="9" height="9" rx="1.5" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
export function GenSpaceGallerySizeMenu({
|
|
gallerySize,
|
|
onGallerySizeChange,
|
|
}: {
|
|
gallerySize: GallerySize
|
|
onGallerySizeChange: (size: GallerySize) => void
|
|
}) {
|
|
const [open, setOpen] = useState(false)
|
|
const menuRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
setOpen(false)
|
|
}
|
|
}
|
|
if (open) {
|
|
document.addEventListener('mousedown', handleClickOutside)
|
|
}
|
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
}, [open])
|
|
|
|
const CurrentIcon = gallerySize === 'small'
|
|
? GridSmallIcon
|
|
: gallerySize === 'medium'
|
|
? GridMediumIcon
|
|
: GridLargeIcon
|
|
|
|
return (
|
|
<div ref={menuRef} className="relative">
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(value => !value)}
|
|
aria-label={`Gallery size: ${gallerySize}`}
|
|
aria-haspopup="menu"
|
|
aria-expanded={open}
|
|
className={`p-2 rounded-md transition-colors ${
|
|
open ? 'bg-zinc-800 text-white' : 'text-zinc-400 hover:text-white hover:bg-zinc-800'
|
|
}`}
|
|
>
|
|
<CurrentIcon className="h-4 w-4" />
|
|
</button>
|
|
|
|
{open && (
|
|
<div className="absolute top-full mt-2 right-0 bg-zinc-800 border border-zinc-700 rounded-md p-2 min-w-[160px] shadow-xl z-50">
|
|
{([
|
|
{ value: 'small' as const, label: 'Small', icon: GridSmallIcon },
|
|
{ value: 'medium' as const, label: 'Medium', icon: GridMediumIcon },
|
|
{ value: 'large' as const, label: 'Large', icon: GridLargeIcon },
|
|
]).map(option => (
|
|
<button
|
|
key={option.value}
|
|
type="button"
|
|
onClick={() => { onGallerySizeChange(option.value); setOpen(false) }}
|
|
className={`w-full flex items-center justify-between px-2 py-2.5 rounded-md transition-colors text-left ${gallerySize === option.value ? 'bg-white/20 hover:bg-white/25' : 'hover:bg-zinc-700'}`}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<option.icon className={`h-4 w-4 ${gallerySize === option.value ? 'text-white' : 'text-zinc-500'}`} />
|
|
<span className={`text-sm ${gallerySize === option.value ? 'text-white font-medium' : 'text-zinc-400'}`}>
|
|
{option.label}
|
|
</span>
|
|
</div>
|
|
{gallerySize === option.value && (
|
|
<svg className="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|