Create standalone browser port of LTX Athena with authenticated native API transport
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user