feat: Add smooth transitions and animations to UI

- Add fade-in/fade-out transition to command palette (200ms)
- Add scale animation to command palette on open/close
- Add smooth popup animation to context menu (150ms)
- Add visual feedback for file selection with ring effect
- Add hover scale effect to file grid items
- Add transition-all to list view items for consistent behavior
- Zero JavaScript overhead, pure CSS transitions
- All animations under 200ms for instant feel
This commit is contained in:
ZacharyZcR
2025-11-09 06:48:31 +08:00
parent a2761c1ebf
commit f311a7a5ad
3 changed files with 32 additions and 11 deletions

View File

@@ -226,13 +226,17 @@ export function CommandPalette({
return ( return (
<div <div
className={cn( className={cn(
"fixed inset-0 z-50 flex items-center justify-center bg-black/30", "fixed inset-0 z-50 flex items-center justify-center bg-black/30 transition-opacity duration-200",
!isOpen && "hidden", !isOpen && "opacity-0 pointer-events-none",
)} )}
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
> >
<Command <Command
className="w-3/4 max-w-2xl max-h-[60vh] rounded-lg border-2 border-dark-border shadow-md flex flex-col" className={cn(
"w-3/4 max-w-2xl max-h-[60vh] rounded-lg border-2 border-dark-border shadow-md flex flex-col",
"transition-all duration-200 ease-out",
!isOpen && "scale-95 opacity-0",
)}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<CommandInput <CommandInput

View File

@@ -102,9 +102,15 @@ export function FileManagerContextMenu({
}: ContextMenuProps) { }: ContextMenuProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [menuPosition, setMenuPosition] = useState({ x, y }); const [menuPosition, setMenuPosition] = useState({ x, y });
const [isMounted, setIsMounted] = useState(false);
useEffect(() => { useEffect(() => {
if (!isVisible) return; if (!isVisible) {
setIsMounted(false);
return;
}
setIsMounted(true);
const adjustPosition = () => { const adjustPosition = () => {
const menuWidth = 200; const menuWidth = 200;
@@ -183,8 +189,6 @@ export function FileManagerContextMenu({
}; };
}, [isVisible, x, y, onClose]); }, [isVisible, x, y, onClose]);
if (!isVisible) return null;
const isFileContext = files.length > 0; const isFileContext = files.length > 0;
const isSingleFile = files.length === 1; const isSingleFile = files.length === 1;
const isMultipleFiles = files.length > 1; const isMultipleFiles = files.length > 1;
@@ -440,13 +444,24 @@ export function FileManagerContextMenu({
); );
}; };
if (!isVisible && !isMounted) return null;
return ( return (
<> <>
<div className="fixed inset-0 z-[99990]" /> <div
className={cn(
"fixed inset-0 z-[99990] transition-opacity duration-150",
!isMounted && "opacity-0"
)}
/>
<div <div
data-context-menu data-context-menu
className="fixed bg-dark-bg border border-dark-border rounded-lg shadow-xl min-w-[180px] max-w-[250px] z-[99995] overflow-hidden" className={cn(
"fixed bg-dark-bg border border-dark-border rounded-lg shadow-xl min-w-[180px] max-w-[250px] z-[99995] overflow-hidden",
"transition-all duration-150 ease-out origin-top-left",
isMounted ? "opacity-100 scale-100" : "opacity-0 scale-95"
)}
style={{ style={{
left: menuPosition.x, left: menuPosition.x,
top: menuPosition.y, top: menuPosition.y,

View File

@@ -1049,8 +1049,9 @@ export function FileManagerGrid({
draggable={true} draggable={true}
className={cn( className={cn(
"group p-3 rounded-lg cursor-pointer", "group p-3 rounded-lg cursor-pointer",
"hover:bg-accent hover:text-accent-foreground border-2 border-transparent", "transition-all duration-150 ease-out",
isSelected && "bg-primary/20 border-primary", "hover:bg-accent hover:text-accent-foreground hover:scale-[1.02] border-2 border-transparent",
isSelected && "bg-primary/20 border-primary ring-2 ring-primary/20",
dragState.target?.path === file.path && dragState.target?.path === file.path &&
"bg-muted border-primary border-dashed relative z-10", "bg-muted border-primary border-dashed relative z-10",
dragState.files.some((f) => f.path === file.path) && dragState.files.some((f) => f.path === file.path) &&
@@ -1138,8 +1139,9 @@ export function FileManagerGrid({
draggable={true} draggable={true}
className={cn( className={cn(
"flex items-center gap-3 p-2 rounded cursor-pointer", "flex items-center gap-3 p-2 rounded cursor-pointer",
"transition-all duration-150 ease-out",
"hover:bg-accent hover:text-accent-foreground", "hover:bg-accent hover:text-accent-foreground",
isSelected && "bg-primary/20", isSelected && "bg-primary/20 ring-2 ring-primary/20",
dragState.target?.path === file.path && dragState.target?.path === file.path &&
"bg-muted border-primary border-dashed relative z-10", "bg-muted border-primary border-dashed relative z-10",
dragState.files.some((f) => f.path === file.path) && dragState.files.some((f) => f.path === file.path) &&