fix: prevent guest link icon from re-animating on WebSocket updates

The external guest link icon was fading in/out on every WebSocket update,
creating a distracting visual effect. Now the fade-in animation only plays
when the URL first becomes available (initial load or when saving a new URL),
rather than re-triggering on every state update.

- Add fadeIn keyframe animation to Tailwind config
- Track animation state with shouldAnimateIcon signal
- Only animate on transition from no URL to having a URL
- Clear animation flag after 200ms to prevent re-triggering
This commit is contained in:
rcourtman 2025-10-24 09:27:06 +00:00
parent f20eccee86
commit 1ae4f5bfd8
2 changed files with 28 additions and 2 deletions

View file

@ -61,6 +61,7 @@ export function GuestRow(props: GuestRowProps) {
const isEditingUrl = createMemo(() => currentlyEditingGuestId() === guestId());
const [customUrl, setCustomUrl] = createSignal<string | undefined>(props.customUrl);
const [shouldAnimateIcon, setShouldAnimateIcon] = createSignal(false);
const [drawerOpen, setDrawerOpen] = createSignal(drawerState.get(initialGuestId) ?? false);
const editingUrlValue = createMemo(() => {
editingValuesVersion(); // Subscribe to changes
@ -82,7 +83,17 @@ export function GuestRow(props: GuestRowProps) {
createEffect(() => {
// Don't update customUrl from props if this guest is currently being edited
if (currentlyEditingGuestId() !== guestId()) {
setCustomUrl(props.customUrl);
const prevUrl = customUrl();
const newUrl = props.customUrl;
// Only animate when URL transitions from empty to having a value
if (!prevUrl && newUrl) {
setShouldAnimateIcon(true);
// Remove animation class after it completes
setTimeout(() => setShouldAnimateIcon(false), 200);
}
setCustomUrl(newUrl);
}
});
@ -250,6 +261,14 @@ export function GuestRow(props: GuestRowProps) {
try {
await GuestMetadataAPI.updateMetadata(guestId(), { customUrl: newUrl });
// Animate if transitioning from no URL to having a URL
const hadUrl = !!customUrl();
if (!hadUrl && newUrl) {
setShouldAnimateIcon(true);
setTimeout(() => setShouldAnimateIcon(false), 200);
}
setCustomUrl(newUrl || undefined);
// Notify parent to update metadata
@ -430,7 +449,7 @@ export function GuestRow(props: GuestRowProps) {
href={customUrl()}
target="_blank"
rel="noopener noreferrer"
class="flex-shrink-0 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 transition-colors"
class={`flex-shrink-0 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 transition-colors ${shouldAnimateIcon() ? 'animate-fadeIn' : ''}`}
title="Open in new tab"
onClick={(event) => event.stopPropagation()}
>

View file

@ -13,6 +13,13 @@ export default {
},
animation: {
'spin-slow': 'spin 2s linear infinite',
'fadeIn': 'fadeIn 0.2s ease-in',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
}
}
},
},