novita-anysite / src /components /version-dropdown.tsx
viktor
feat. improve ui
869a182
raw
history blame
7.58 kB
"use client"
import { useState, useEffect, useRef } from "react"
import { ChevronDown, Trash2 } from "lucide-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator
} from "@/components/ui/dropdown-menu"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Version } from "@/lib/types"
export function VersionDropdown({
onVersionSelect,
currentVersion,
onClearAll
}: {
onVersionSelect: (version: Version) => void,
currentVersion?: string,
onClearAll?: () => void
}) {
const [versions, setVersions] = useState<Version[]>([]);
const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
const [showClearConfirm, setShowClearConfirm] = useState(false);
const [userSelected, setUserSelected] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
// Update current version ID when prop changes
useEffect(() => {
if (currentVersion) {
setCurrentVersionId(currentVersion);
setUserSelected(true);
}
}, [currentVersion]);
// Load versions from localStorage
const loadVersions = () => {
const storedVersions = localStorage.getItem('novita-versions');
if (storedVersions) {
const parsedVersions = JSON.parse(storedVersions) as Version[];
// Sort versions by creation time, newest first
const sortedVersions = parsedVersions.sort((a, b) => b.createdAt - a.createdAt);
setVersions(sortedVersions);
// Only set the current version to the newest one if there's no current version
// We don't want to override a user selection
if (!currentVersionId && sortedVersions.length > 0 && !userSelected) {
setCurrentVersionId(sortedVersions[0].id);
}
} else {
// Initialize with empty version list
setVersions([]);
}
};
// Load versions from localStorage on mount and when localStorage changes
useEffect(() => {
loadVersions();
// Add event listener for storage changes
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'novita-versions') {
loadVersions();
}
};
window.addEventListener('storage', handleStorageChange);
// Also check for updates periodically since the storage event
// may not fire if the change is made in the same window
const interval = setInterval(loadVersions, 1000);
return () => {
window.removeEventListener('storage', handleStorageChange);
clearInterval(interval);
};
}, [currentVersionId, userSelected]);
// Handle click outside to close dropdown
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
}
}, [isOpen]);
// Handle version selection
const handleVersionSelect = (version: Version) => {
setCurrentVersionId(version.id);
setUserSelected(true);
setIsOpen(false);
onVersionSelect(version);
};
// Clear all versions
const clearAllVersions = () => {
setVersions([]);
setCurrentVersionId(null);
setUserSelected(false);
localStorage.setItem('novita-versions', JSON.stringify([]));
setShowClearConfirm(false);
// Call the onClearAll callback if provided
if (onClearAll) {
onClearAll();
}
};
// Get version name based on index
const getVersionName = (index: number) => {
return `v${versions.length - 1 - index}`;
};
// Find the currently selected version's index
const getCurrentVersionIndex = () => {
if (!currentVersionId) return 0;
const index = versions.findIndex(v => v.id === currentVersionId);
return index !== -1 ? index : 0;
};
if (versions.length === 0) {
return null;
}
return (
<>
<div className="relative inline-block text-left ml-2" ref={dropdownRef}>
<div className="flex items-center gap-2">
<span className="text-l text-white">current version</span>
<div
onClick={() => setIsOpen(!isOpen)}
className={`
cursor-pointer flex items-center justify-between gap-2
text-white/90 hover:text-white
px-3 py-1.5 rounded-md
border border-novita-gray/30 hover:border-novita-gray/60
bg-novita-gray/5 hover:bg-novita-gray/20
transition-all duration-200 ease-in-out
${isOpen ? 'border-novita-gray/60 bg-novita-gray/20' : ''}
`}
>
<span className="text-sm">{versions.length > 0 ? getVersionName(getCurrentVersionIndex()) : "v0"}</span>
<ChevronDown className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'transform rotate-180' : ''}`} />
</div>
</div>
{isOpen && (
<div className="absolute z-50 mt-1 min-w-[160px] w-max right-0 origin-top-right rounded-md bg-novita-dark border border-novita-gray/20 shadow-lg">
<div className="py-1">
{versions.map((version, index) => (
<div
key={version.id}
onClick={() => handleVersionSelect(version)}
className={`
px-4 py-2 text-xs cursor-pointer hover:bg-novita-gray/20
${currentVersionId === version.id ? 'text-white bg-novita-gray/40' : 'text-white/70'}
`}
>
{getVersionName(index)} {index === 0 ? "(Newest)" : ""}
</div>
))}
{versions.length > 0 && (
<>
<div className="h-px bg-novita-gray/30 my-1"></div>
<div
className="px-4 py-2 text-xs cursor-pointer text-red-400 hover:bg-red-500/20 hover:text-red-300 flex items-center"
onClick={() => setShowClearConfirm(true)}
>
<Trash2 className="h-3.5 w-3.5 mr-2" />
Clear All
</div>
</>
)}
</div>
</div>
)}
</div>
<AlertDialog open={showClearConfirm} onOpenChange={setShowClearConfirm}>
<AlertDialogContent className="bg-novita-dark border-novita-gray/30 text-white">
<AlertDialogHeader>
<AlertDialogTitle>Clear All Versions</AlertDialogTitle>
<AlertDialogDescription className="text-novita-gray/70">
This will delete all saved versions.
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="bg-novita-gray/20 border-novita-gray/30 text-white hover:bg-novita-gray/30">
Cancel
</AlertDialogCancel>
<AlertDialogAction
className="bg-red-500 text-white hover:bg-red-600"
onClick={clearAllVersions}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}