diff --git a/src/frontend/src/features/rooms/livekit/components/controls/ReactionsToggle.tsx b/src/frontend/src/features/rooms/livekit/components/controls/ReactionsToggle.tsx index cb6669d6..90097fab 100644 --- a/src/frontend/src/features/rooms/livekit/components/controls/ReactionsToggle.tsx +++ b/src/frontend/src/features/rooms/livekit/components/controls/ReactionsToggle.tsx @@ -1,9 +1,9 @@ import { useTranslation } from 'react-i18next' import { RiEmotionLine } from '@remixicon/react' -import { useState, useRef } from 'react' +import { useState, useRef, useEffect } from 'react' import { css } from '@/styled-system/css' import { useRoomContext } from '@livekit/components-react' -import { Menu, ToggleButton, Button } from '@/primitives' +import { ToggleButton, Button } from '@/primitives' import { NotificationType } from '@/features/notifications/NotificationType' import { NotificationPayload } from '@/features/notifications/NotificationPayload' import { @@ -27,6 +27,8 @@ export const ReactionsToggle = () => { const instanceIdRef = useRef(0) const room = useRoomContext() + const [isVisible, setIsVisible] = useState(false) + const sendReaction = async (emoji: string) => { const encoder = new TextEncoder() const payload: NotificationPayload = { @@ -53,42 +55,133 @@ export const ReactionsToggle = () => { }, ANIMATION_DURATION) } + // Custom animation implementation for the emoji toolbar + // Could not use a menu and its animation, because a menu would make the toolbar inaccessible by keyboard + // animation isn't perfect + const [isRendered, setIsRendered] = useState(isVisible) + const [opacity, setOpacity] = useState(isVisible ? 1 : 0) + + useEffect(() => { + if (isVisible) { + // Show: first render, then animate in + setIsRendered(true) + // Need to delay setting opacity to ensure CSS transition works + // (using requestAnimationFrame to ensure DOM has updated) + requestAnimationFrame(() => { + requestAnimationFrame(() => { + setOpacity(1) + }) + }) + } else if (isRendered) { + // Hide: first animate out, then unrender + setOpacity(0) + + // Wait for animation to complete before removing from DOM + const timer = setTimeout(() => { + setIsRendered(false) + }, 200) // Match this to your animation duration + return () => clearTimeout(timer) + } + }, [isVisible, isRendered]) + return ( <> -