perf: use focusable selector hooks across components
- Replaced instances of `useGlobalFocusableScope` with `useGlobalFocusableScopeSelector` for improved focus state management. - Updated various components including FeedColumn, Subview, EntryColumn, and FeedItem to utilize the new selector for better performance and clarity. - Added a new `viewportClassName` property to the SettingPageConfig interface for enhanced configuration options. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
f31d43da08
commit
e374dd6473
|
|
@ -1,6 +1,6 @@
|
|||
import type { DragEndEvent } from "@dnd-kit/core"
|
||||
import { DndContext, PointerSensor, pointerWithin, useSensor, useSensors } from "@dnd-kit/core"
|
||||
import { useGlobalFocusableScope } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { useGlobalFocusableScopeSelector } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { PanelSplitter } from "@follow/components/ui/divider/index.js"
|
||||
import { Kbd } from "@follow/components/ui/kbd/Kbd.js"
|
||||
import { RootPortal } from "@follow/components/ui/portal/index.jsx"
|
||||
|
|
@ -238,11 +238,14 @@ const FeedResponsiveResizerContainer = ({
|
|||
}
|
||||
}, [feedColumnShow])
|
||||
|
||||
const activeScopes = useGlobalFocusableScope()
|
||||
const when = useGlobalFocusableScopeSelector(
|
||||
// eslint-disable-next-line @eslint-react/hooks-extra/no-unnecessary-use-callback
|
||||
React.useCallback((activeScope) => !activeScope.or(...FloatingLayerScope), []),
|
||||
)
|
||||
|
||||
useCommandBinding({
|
||||
commandId: COMMAND_ID.layout.toggleSubscriptionColumn,
|
||||
when: !activeScopes.or(...FloatingLayerScope),
|
||||
when,
|
||||
})
|
||||
|
||||
const [delayShowSplitter, setDelayShowSplitter] = useState(feedColumnShow)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { getReadonlyRoute } from "@follow/components/atoms/route.js"
|
||||
import { useGlobalFocusableScope } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { useGlobalFocusableHasScope } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { MotionButtonBase } from "@follow/components/ui/button/index.js"
|
||||
import { ScrollArea } from "@follow/components/ui/scroll-area/index.js"
|
||||
import { Routes } from "@follow/constants"
|
||||
|
|
@ -71,9 +71,9 @@ function SubviewLayoutInner() {
|
|||
navigate(-1)
|
||||
}
|
||||
}
|
||||
const activeScope = useGlobalFocusableScope()
|
||||
|
||||
useHotkeys("Escape", backHandler, {
|
||||
enabled: activeScope.has(HotkeyScope.SubLayer),
|
||||
enabled: useGlobalFocusableHasScope(HotkeyScope.SubLayer),
|
||||
})
|
||||
return (
|
||||
<div className="relative flex size-full">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
useFocusActions,
|
||||
useGlobalFocusableScope,
|
||||
useGlobalFocusableScopeSelector,
|
||||
} from "@follow/components/common/Focusable/hooks.js"
|
||||
import { useScrollViewElement } from "@follow/components/ui/scroll-area/hooks.js"
|
||||
import { useRefValue } from "@follow/hooks"
|
||||
|
|
@ -24,9 +24,7 @@ export const EntryColumnShortcutHandler: FC<{
|
|||
}> = memo(({ data, refetch, handleScrollTo }) => {
|
||||
const dataRef = useRefValue(data!)
|
||||
|
||||
const activeScope = useGlobalFocusableScope()
|
||||
|
||||
const when = FocusablePresets.isTimeline(activeScope)
|
||||
const when = useGlobalFocusableScopeSelector(FocusablePresets.isTimeline)
|
||||
|
||||
useCommandBinding({
|
||||
commandId: COMMAND_ID.timeline.switchToNext,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useGlobalFocusableScope } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { useGlobalFocusableScopeSelector } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { ActionButton, Button } from "@follow/components/ui/button/index.js"
|
||||
import { Kbd, KbdCombined } from "@follow/components/ui/kbd/Kbd.js"
|
||||
import { useCountdown } from "@follow/hooks"
|
||||
import { EventBus } from "@follow/utils/event-bus"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import type { FC, ReactNode } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useHotkeys } from "react-hotkeys-hook"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
|
@ -33,10 +33,17 @@ export const MarkAllReadButton = ({
|
|||
const { t } = useTranslation()
|
||||
const { t: commonT } = useTranslation("common")
|
||||
|
||||
const activeScope = useGlobalFocusableScope()
|
||||
// const activeScope = useGlobalFocusableScope()
|
||||
const when = useGlobalFocusableScopeSelector(
|
||||
// eslint-disable-next-line @eslint-react/hooks-extra/no-unnecessary-use-callback
|
||||
useCallback(
|
||||
(activeScope) => activeScope.or(HotkeyScope.Timeline, HotkeyScope.SubscriptionList),
|
||||
[],
|
||||
),
|
||||
)
|
||||
useCommandBinding({
|
||||
commandId: COMMAND_ID.subscription.markAllAsRead,
|
||||
when: activeScope.or(HotkeyScope.Timeline, HotkeyScope.SubscriptionList),
|
||||
when,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useGlobalFocusableScope } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { useGlobalFocusableScopeSelector } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { useMobile } from "@follow/components/hooks/useMobile.js"
|
||||
import type { FeedViewType } from "@follow/constants"
|
||||
import { views } from "@follow/constants"
|
||||
|
|
@ -51,8 +51,8 @@ export const EntryItemWrapper: FC<
|
|||
({ entryId }) => entryId === entry.entries.id,
|
||||
[entry.entries.id],
|
||||
)
|
||||
const scope = useGlobalFocusableScope()
|
||||
useContextMenuActionShortCutTrigger(actionConfigs, isActive && FocusablePresets.isTimeline(scope))
|
||||
const when = useGlobalFocusableScopeSelector(FocusablePresets.isTimeline)
|
||||
useContextMenuActionShortCutTrigger(actionConfigs, isActive && when)
|
||||
|
||||
const asRead = useEntryIsRead(entry)
|
||||
const hoverMarkUnread = useGeneralSettingKey("hoverMarkUnread")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { IconTransition } from "~/components/ux/transition/icon"
|
|||
export const EnhancedSettingsIndicator = () => {
|
||||
const enhancedSettings = useGeneralSettingKey("enhancedSettings")
|
||||
const { t } = useTranslation("settings")
|
||||
|
||||
if (!enhancedSettings) return null
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export interface SettingPageConfig {
|
|||
ctx: SettingPageContext,
|
||||
serverConfigs?: ServerConfigs | null,
|
||||
) => [boolean, DisableWhy]
|
||||
viewportClassName?: string
|
||||
}
|
||||
export const defineSettingPageData = (config: SettingPageConfig) => ({
|
||||
...config,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useGlobalFocusableScope } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { useGlobalFocusableScopeSelector } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { useMobile } from "@follow/components/hooks/useMobile.js"
|
||||
import { OouiUserAnonymous } from "@follow/components/icons/OouiUserAnonymous.jsx"
|
||||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
|
|
@ -120,9 +120,9 @@ const FeedItemImpl = ({ view, feedId, className, isPreview }: FeedItemProps) =>
|
|||
view,
|
||||
})
|
||||
|
||||
const scope = useGlobalFocusableScope()
|
||||
const when = useGlobalFocusableScopeSelector(FocusablePresets.isSubscriptionList)
|
||||
|
||||
const whenTrigger = FocusablePresets.isSubscriptionList(scope) && isActive
|
||||
const whenTrigger = when && isActive
|
||||
useContextMenuActionShortCutTrigger(items, whenTrigger)
|
||||
|
||||
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false)
|
||||
|
|
@ -260,8 +260,8 @@ const ListItemImpl: Component<ListItemProps> = ({
|
|||
const isActive = useRouteParamsSelector((routerParams) => routerParams.listId === listId)
|
||||
const items = useListActions({ listId, view })
|
||||
|
||||
const scope = useGlobalFocusableScope()
|
||||
useContextMenuActionShortCutTrigger(items, FocusablePresets.isSubscriptionList(scope) && isActive)
|
||||
const when = useGlobalFocusableScopeSelector(FocusablePresets.isSubscriptionList)
|
||||
useContextMenuActionShortCutTrigger(items, when && isActive)
|
||||
|
||||
const listUnread = useUnreadByListId(listId)
|
||||
|
||||
|
|
@ -369,8 +369,8 @@ const InboxItemImpl: Component<InboxItemProps> = ({ view, inboxId, className, ic
|
|||
const isActive = useRouteParamsSelector((routerParams) => routerParams.inboxId === inboxId)
|
||||
const { items } = useInboxActions({ inboxId })
|
||||
|
||||
const scope = useGlobalFocusableScope()
|
||||
useContextMenuActionShortCutTrigger(items, FocusablePresets.isSubscriptionList(scope) && isActive)
|
||||
const when = useGlobalFocusableScopeSelector(FocusablePresets.isSubscriptionList)
|
||||
useContextMenuActionShortCutTrigger(items, when && isActive)
|
||||
|
||||
const inboxUnread = useUnreadById(inboxId)
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ export const useFocusableContainerRef = () => {
|
|||
return use(FocusableContainerRefContext)
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance issue, use `useGlobalFocusableScopeSelector` instead
|
||||
* @deprecated use `useGlobalFocusableScopeSelector` instead
|
||||
*/
|
||||
export const useGlobalFocusableScope = () => {
|
||||
return useAtomValue(use(GlobalFocusableContext))
|
||||
}
|
||||
|
|
@ -35,7 +39,9 @@ export const useGlobalFocusableScope = () => {
|
|||
export const useGlobalFocusableHasScope = (scope: string) => {
|
||||
return useGlobalFocusableScopeSelector(useCallback((v) => v.has(scope), [scope]))
|
||||
}
|
||||
export const useGlobalFocusableScopeSelector = (selector: (scope: Set<string>) => boolean) => {
|
||||
export const useGlobalFocusableScopeSelector = (
|
||||
selector: (scope: EnhanceSet<string>) => boolean,
|
||||
) => {
|
||||
const ctx = use(GlobalFocusableContext)
|
||||
|
||||
return useAtomValue(useMemo(() => selectAtom(ctx, selector), [ctx, selector]))
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ function Checkbox({ className, onCheckedChange, ...props }: CheckboxProps) {
|
|||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.2,
|
||||
delay: 0.2,
|
||||
},
|
||||
},
|
||||
unchecked: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue