fix: improve dropdown menu focus management and simplify rendering

- Introduced `useSetGlobalFocusableScope` hook to manage focus scope when the dropdown menu opens and closes.
- Removed the `Focusable` wrapper from the dropdown menu content to streamline rendering.
- Updated the subscription column to utilize `useGlobalFocusableScopeSelector` for better focus state management.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-05-27 22:54:35 +08:00
parent 119f6b5383
commit f31d43da08
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
2 changed files with 30 additions and 19 deletions

View File

@ -1,3 +1,4 @@
import { useSetGlobalFocusableScope } from "@follow/components/common/Focusable/hooks.js"
import { Divider } from "@follow/components/ui/divider/Divider.js"
import { Kbd } from "@follow/components/ui/kbd/Kbd.js"
import { RootPortal } from "@follow/components/ui/portal/index.js"
@ -6,15 +7,21 @@ import { cn } from "@follow/utils/utils"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import * as React from "react"
import { Focusable } from "~/components/common/Focusable"
import { HotkeyScope } from "~/constants"
const DropdownMenu: typeof DropdownMenuPrimitive.Root = (props) => {
const setGlobalFocusableScope = useSetGlobalFocusableScope()
return (
<DropdownMenuPrimitive.Root
{...props}
onOpenChange={useTypeScriptHappyCallback(
(open) => {
if (open) {
setGlobalFocusableScope(HotkeyScope.DropdownMenu, "append")
} else {
setGlobalFocusableScope(HotkeyScope.DropdownMenu, "remove")
}
props.onOpenChange?.(open)
},
[props.onOpenChange],
@ -91,18 +98,16 @@ const DropdownMenuContent = ({
}) => {
return (
<RootPortal>
<Focusable scope={HotkeyScope.DropdownMenu} className="contents">
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"bg-material-medium backdrop-blur-background text-text shadow-context-menu z-[60] min-w-32 overflow-hidden rounded-[6px] border p-1",
"motion-scale-in-75 motion-duration-150 text-body lg:animate-none",
className,
)}
{...props}
/>
</Focusable>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"bg-material-medium backdrop-blur-background text-text shadow-context-menu z-[60] min-w-32 overflow-hidden rounded-[6px] border p-1",
"motion-scale-in-75 motion-duration-150 text-body lg:animate-none",
className,
)}
{...props}
/>
</RootPortal>
)
}

View File

@ -1,4 +1,4 @@
import { useGlobalFocusableScope } from "@follow/components/common/Focusable/hooks.js"
import { useGlobalFocusableScopeSelector } from "@follow/components/common/Focusable/hooks.js"
import { ActionButton } from "@follow/components/ui/button/index.js"
import { RootPortal } from "@follow/components/ui/portal/index.js"
import { Routes } from "@follow/constants"
@ -243,9 +243,15 @@ const CommandsHandler = ({
setActive: (args: string | ((prev: string | undefined, index: number) => string)) => void
timelineList: string[]
}) => {
const activeScope = useGlobalFocusableScope()
const when =
activeScope.or(HotkeyScope.SubscriptionList, HotkeyScope.Timeline) || activeScope.size === 0
const when = useGlobalFocusableScopeSelector(
// eslint-disable-next-line @eslint-react/hooks-extra/no-unnecessary-use-callback
useCallback(
(activeScope) =>
activeScope.or(HotkeyScope.SubscriptionList, HotkeyScope.Timeline) ||
activeScope.size === 0,
[],
),
)
useCommandBinding({
commandId: COMMAND_ID.subscription.switchTabToNext,
@ -261,13 +267,13 @@ const CommandsHandler = ({
return EventBus.subscribe(COMMAND_ID.subscription.switchTabToNext, () => {
setActive((_, i) => timelineList[(i + 1) % timelineList.length]!)
})
}, [activeScope, setActive, timelineList])
}, [setActive, timelineList])
useEffect(() => {
return EventBus.subscribe(COMMAND_ID.subscription.switchTabToPrevious, () => {
setActive((_, i) => timelineList[(i - 1 + timelineList.length) % timelineList.length]!)
})
}, [activeScope, setActive, timelineList])
}, [setActive, timelineList])
return null
}