1+ import { useGlobalFocusableScopeSelector } from "@follow/components/common/Focusable/hooks.js"
12import { PassviseFragment } from "@follow/components/common/Fragment.js"
2- import { useMobile } from "@follow/components/hooks/useMobile .js"
3+ import { Spring } from "@follow/components/constants/spring .js"
34import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.js"
45import { Skeleton } from "@follow/components/ui/skeleton/index.jsx"
56import { FeedViewType } from "@follow/constants"
@@ -10,10 +11,12 @@ import { getImageProxyUrl } from "@follow/utils/img-proxy"
1011import { LRUCache } from "@follow/utils/lru-cache"
1112import { cn } from "@follow/utils/utils"
1213import { atom } from "jotai"
13- import { useLayoutEffect , useMemo , useRef , useState } from "react"
14+ import { AnimatePresence , m } from "motion/react"
15+ import { useEffect , useLayoutEffect , useMemo , useRef , useState } from "react"
1416import { useTranslation } from "react-i18next"
1517
1618import { useGeneralSettingKey } from "~/atoms/settings/general"
19+ import { FocusablePresets } from "~/components/common/Focusable"
1720import { RelativeTime } from "~/components/ui/datetime"
1821import { HTML } from "~/components/ui/markdown/HTML"
1922import { usePreviewMedia } from "~/components/ui/media/hooks"
@@ -67,14 +70,33 @@ export const SocialMediaItem: EntryListItemFC = ({ entryId, translation }) => {
6770 const ref = useRef < HTMLDivElement > ( null )
6871 const [ showAction , setShowAction ] = useState ( false )
6972
70- const isMobile = useMobile ( )
7173 const handleMouseEnter = useMemo ( ( ) => {
7274 return ( ) => setShowAction ( true )
7375 } , [ ] )
7476 const handleMouseLeave = useMemo ( ( ) => {
75- return ( ) => setShowAction ( false )
77+ return ( e : React . MouseEvent ) => {
78+ // If the mouse is over the action bar, don't hide the action bar
79+ const relatedTarget = e . relatedTarget as Element
80+ const currentTarget = e . currentTarget as Element
81+
82+ if ( relatedTarget && currentTarget . contains ( relatedTarget ) ) {
83+ return
84+ }
85+ setShowAction ( false )
86+ }
7687 } , [ ] )
7788
89+ const isDropdownMenuOpen = useGlobalFocusableScopeSelector (
90+ FocusablePresets . isNotFloatingLayerScope ,
91+ )
92+
93+ useEffect ( ( ) => {
94+ // Hide the action bar when dropdown menu is open and click outside
95+ if ( isDropdownMenuOpen ) {
96+ setShowAction ( false )
97+ }
98+ } , [ isDropdownMenuOpen ] )
99+
78100 useLayoutEffect ( ( ) => {
79101 if ( ref . current ) {
80102 jotaiStore . set ( socialMediaContentWidthAtom , ref . current . offsetWidth )
@@ -149,7 +171,7 @@ export const SocialMediaItem: EntryListItemFC = ({ entryId, translation }) => {
149171 < SocialMediaGallery entryId = { entryId } />
150172 </ div >
151173
152- { showAction && ! isMobile && < ActionBar entryId = { entryId } /> }
174+ < AnimatePresence > { showAction && < ActionBar entryId = { entryId } /> } </ AnimatePresence >
153175 </ div >
154176 )
155177}
@@ -165,12 +187,18 @@ const ActionBar = ({ entryId }: { entryId: string }) => {
165187 if ( entryActions . length === 0 ) return null
166188
167189 return (
168- < div className = "absolute right-1 top-0 -translate-y-1/2 rounded-lg border border-gray-200 bg-white/90 p-1 shadow-sm backdrop-blur-sm dark:border-neutral-900 dark:bg-neutral-900" >
190+ < m . div
191+ initial = { { opacity : 0 , scale : 0.9 , y : "-1/2" } }
192+ animate = { { opacity : 1 , scale : 1 , y : "-1/2" } }
193+ exit = { { opacity : 0 , scale : 0.9 , y : "-1/2" } }
194+ transition = { Spring . presets . smooth }
195+ className = "absolute right-1 top-0 -translate-y-1/2 rounded-lg border border-gray-200 bg-white/90 p-1 shadow-sm backdrop-blur-sm dark:border-neutral-900 dark:bg-neutral-900"
196+ >
169197 < div className = "flex items-center gap-1" >
170198 < EntryHeaderActions entryId = { entryId } view = { FeedViewType . SocialMedia } />
171199 < MoreActions entryId = { entryId } view = { FeedViewType . SocialMedia } />
172200 </ div >
173- </ div >
201+ </ m . div >
174202 )
175203}
176204
0 commit comments