Skip to content

Commit 60609ea

Browse files
ui(animation): smooth exit animation for image details panel (#978)
Co-authored-by: Rahul Harpal <51887323+rahulharpal1603@users.noreply.github.com>
1 parent f5294a6 commit 60609ea

1 file changed

Lines changed: 117 additions & 108 deletions

File tree

Lines changed: 117 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { motion, AnimatePresence } from 'framer-motion';
23
import { open } from '@tauri-apps/plugin-shell';
34
import {
45
X,
@@ -49,131 +50,139 @@ export const MediaInfoPanel: React.FC<MediaInfoPanelProps> = ({
4950
const handleLocationClick = async () => {
5051
if (currentImage?.metadata?.latitude && currentImage?.metadata?.longitude) {
5152
const { latitude, longitude } = currentImage.metadata;
52-
const url = `https://maps.google.com/?q=${latitude},${longitude}`;
5353
try {
54-
await open(url);
54+
await open(`https://maps.google.com/?q=${latitude},${longitude}`);
5555
} catch (error) {
5656
console.error('Failed to open map URL:', error);
5757
}
5858
}
5959
};
6060

61-
if (!show) return null;
62-
6361
return (
64-
<div className="animate-in slide-in-from-left absolute top-10 left-6 z-50 w-[350px] rounded-xl border border-white/10 bg-black/60 p-6 shadow-xl backdrop-blur-lg transition-all duration-300">
65-
<div className="mb-4 flex items-center justify-between border-b border-white/10 pb-3">
66-
<h3 className="text-xl font-medium text-white">Image Details</h3>
67-
<button
68-
onClick={onClose}
69-
className="text-white/70 hover:text-white"
70-
aria-label="Close info panel"
62+
<AnimatePresence>
63+
{show && (
64+
<motion.div
65+
initial={{ opacity: 0, x: -20 }}
66+
animate={{ opacity: 1, x: 0 }}
67+
exit={{ opacity: 0, x: -20 }}
68+
transition={{ type: 'spring', stiffness: 260, damping: 25 }}
69+
className="absolute top-10 left-6 z-50 w-[350px] rounded-xl border border-white/10 bg-black/60 p-6 shadow-xl backdrop-blur-lg"
7170
>
72-
<X className="h-5 w-5" />
73-
</button>
74-
</div>
75-
76-
<div className="space-y-4 text-sm">
77-
<div className="flex items-start gap-3">
78-
<div className="rounded-lg bg-white/10 p-2">
79-
<ImageLucide className="h-5 w-5 text-blue-400" />
80-
</div>
81-
<div className="min-w-0 flex-1">
82-
<p className="text-xs text-white/50">Name</p>
83-
<p
84-
className="truncate font-medium text-white"
85-
title={getImageName()}
71+
<div className="mb-4 flex items-center justify-between border-b border-white/10 pb-3">
72+
<h3 className="text-xl font-medium text-white">Image Details</h3>
73+
<button
74+
onClick={onClose}
75+
className="text-white/70 hover:text-white"
76+
aria-label="Close info panel"
8677
>
87-
{getImageName()}
88-
</p>
78+
<X className="h-5 w-5" />
79+
</button>
8980
</div>
90-
</div>
9181

92-
<div className="flex items-start gap-3">
93-
<div className="rounded-lg bg-white/10 p-2">
94-
<Calendar className="h-5 w-5 text-emerald-400" />
95-
</div>
96-
<div>
97-
<p className="text-xs text-white/50">Date</p>
98-
<p className="font-medium text-white">{getFormattedDate()}</p>
99-
</div>
100-
</div>
82+
<div className="space-y-4 text-sm">
83+
<div className="flex items-start gap-3">
84+
<div className="rounded-lg bg-white/10 p-2">
85+
<ImageLucide className="h-5 w-5 text-blue-400" />
86+
</div>
87+
<div className="min-w-0 flex-1">
88+
<p className="text-xs text-white/50">Name</p>
89+
<p
90+
className="truncate font-medium text-white"
91+
title={getImageName()}
92+
>
93+
{getImageName()}
94+
</p>
95+
</div>
96+
</div>
10197

102-
<div className="flex items-start gap-3">
103-
<div className="rounded-lg bg-white/10 p-2">
104-
<MapPin className="h-5 w-5 text-red-400" />
105-
</div>
106-
<div className="min-w-0 flex-1">
107-
<p className="text-xs text-white/50">Location</p>
108-
{currentImage?.metadata?.latitude &&
109-
currentImage?.metadata?.longitude ? (
110-
<button
111-
type="button"
112-
onClick={handleLocationClick}
113-
className="flex w-full cursor-pointer items-center truncate text-left font-medium text-white hover:underline"
114-
title={`Lat: ${currentImage.metadata.latitude}, Lon: ${currentImage.metadata.longitude}`}
115-
>
116-
{`Lat: ${currentImage.metadata.latitude.toFixed(4)}, Lon: ${currentImage.metadata.longitude.toFixed(4)}`}
117-
<SquareArrowOutUpRight className="ml-1 h-[14px] w-[14px]" />
118-
</button>
119-
) : (
120-
<p className="font-medium text-white">Location not available</p>
121-
)}
122-
</div>
123-
</div>
98+
<div className="flex items-start gap-3">
99+
<div className="rounded-lg bg-white/10 p-2">
100+
<Calendar className="h-5 w-5 text-emerald-400" />
101+
</div>
102+
<div>
103+
<p className="text-xs text-white/50">Date</p>
104+
<p className="font-medium text-white">{getFormattedDate()}</p>
105+
</div>
106+
</div>
124107

125-
<div className="flex items-start gap-3">
126-
<div className="rounded-lg bg-white/10 p-2">
127-
<Tag className="h-5 w-5 text-purple-400" />
128-
</div>
129-
<div className="flex-1">
130-
<p className="mb-1 text-xs text-white/50">Tags</p>
131-
{currentImage?.tags && currentImage.tags.length > 0 ? (
132-
<div className="flex flex-wrap gap-2">
133-
{currentImage.tags.map((tag, i) => (
134-
<span
135-
key={i}
136-
className="rounded-full border border-blue-500/30 bg-blue-500/20 px-2 py-1 text-xs text-blue-300"
108+
<div className="flex items-start gap-3">
109+
<div className="rounded-lg bg-white/10 p-2">
110+
<MapPin className="h-5 w-5 text-red-400" />
111+
</div>
112+
<div className="min-w-0 flex-1">
113+
<p className="text-xs text-white/50">Location</p>
114+
{currentImage?.metadata?.latitude &&
115+
currentImage?.metadata?.longitude ? (
116+
<button
117+
type="button"
118+
onClick={handleLocationClick}
119+
className="flex w-full items-center truncate text-left font-medium text-white hover:underline"
137120
>
138-
{tag}
139-
</span>
140-
))}
121+
{`Lat: ${currentImage.metadata.latitude.toFixed(4)}, Lon: ${currentImage.metadata.longitude.toFixed(4)}`}
122+
<SquareArrowOutUpRight className="ml-1 h-[14px] w-[14px]" />
123+
</button>
124+
) : (
125+
<p className="font-medium text-white">
126+
Location not available
127+
</p>
128+
)}
141129
</div>
142-
) : (
143-
<p className="text-white/60">No tags available</p>
144-
)}
145-
</div>
146-
</div>
130+
</div>
147131

148-
<div className="flex items-start gap-3">
149-
<div className="rounded-lg bg-white/10 p-2">
150-
<Info className="h-5 w-5 text-amber-400" />
151-
</div>
152-
<div>
153-
<p className="text-xs text-white/50">Position</p>
154-
<p className="font-medium text-white">
155-
{currentIndex + 1} of {totalImages}
156-
</p>
157-
</div>
158-
</div>
132+
<div className="flex items-start gap-3">
133+
<div className="rounded-lg bg-white/10 p-2">
134+
<Tag className="h-5 w-5 text-purple-400" />
135+
</div>
136+
<div className="flex-1">
137+
<p className="mb-1 text-xs text-white/50">Tags</p>
138+
{currentImage?.tags?.length ? (
139+
<div className="flex flex-wrap gap-2">
140+
{currentImage.tags.map((tag, i) => (
141+
<span
142+
key={i}
143+
className="rounded-full border border-blue-500/30 bg-blue-500/20 px-2 py-1 text-xs text-blue-300"
144+
>
145+
{tag}
146+
</span>
147+
))}
148+
</div>
149+
) : (
150+
<p className="text-white/60">No tags available</p>
151+
)}
152+
</div>
153+
</div>
159154

160-
<div className="mt-4 border-t border-white/10 pt-3">
161-
<button
162-
className="w-full cursor-pointer rounded-lg bg-white/10 py-2 text-white transition-colors hover:bg-white/20"
163-
onClick={async () => {
164-
if (currentImage?.path) {
165-
try {
166-
await open(currentImage.path);
167-
} catch (error) {
168-
console.error('Failed to open file:', error);
169-
}
170-
}
171-
}}
172-
>
173-
Open Original File
174-
</button>
175-
</div>
176-
</div>
177-
</div>
155+
<div className="flex items-start gap-3">
156+
<div className="rounded-lg bg-white/10 p-2">
157+
<Info className="h-5 w-5 text-amber-400" />
158+
</div>
159+
<div>
160+
<p className="text-xs text-white/50">Position</p>
161+
<p className="font-medium text-white">
162+
{currentIndex + 1} of {totalImages}
163+
</p>
164+
</div>
165+
</div>
166+
167+
<div className="mt-4 border-t border-white/10 pt-3">
168+
<button
169+
className="w-full rounded-lg bg-white/10 py-2 text-white hover:bg-white/20"
170+
onClick={async () => {
171+
if (currentImage?.path) {
172+
try {
173+
await open(currentImage.path);
174+
} catch (error) {
175+
console.error('Failed to open file:', error);
176+
}
177+
}
178+
}}
179+
>
180+
Open Original File
181+
</button>
182+
</div>
183+
</div>
184+
</motion.div>
185+
)}
186+
</AnimatePresence>
178187
);
179188
};

0 commit comments

Comments
 (0)