diff --git a/src/hooks/useActionBarHeightVar.ts b/src/hooks/useActionBarHeightVar.ts new file mode 100644 index 0000000..564e82c --- /dev/null +++ b/src/hooks/useActionBarHeightVar.ts @@ -0,0 +1,38 @@ +import { useLayoutEffect, type RefObject } from 'react'; + +/** + * 将悬浮操作条的实时高度同步到根元素 CSS 变量,供页面底部留白使用。 + * active 为 false 或元素未挂载时清除变量。 + */ +export function useActionBarHeightVar( + ref: RefObject, + cssVar: string, + active: boolean +) { + useLayoutEffect(() => { + if (typeof window === 'undefined') return; + + const actionsEl = active ? ref.current : null; + if (!actionsEl) { + document.documentElement.style.removeProperty(cssVar); + return; + } + + const updateHeight = () => { + const height = actionsEl.getBoundingClientRect().height; + document.documentElement.style.setProperty(cssVar, `${height}px`); + }; + + updateHeight(); + window.addEventListener('resize', updateHeight); + + const ro = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(updateHeight); + ro?.observe(actionsEl); + + return () => { + ro?.disconnect(); + window.removeEventListener('resize', updateHeight); + document.documentElement.style.removeProperty(cssVar); + }; + }, [ref, cssVar, active]); +} diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index 5b4c498..0595b96 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -15,6 +15,7 @@ import { animate } from 'motion/mini'; import type { AnimationPlaybackControlsWithThen } from 'motion-dom'; import { useInterval } from '@/hooks/useInterval'; import { useHeaderRefresh } from '@/hooks/useHeaderRefresh'; +import { useActionBarHeightVar } from '@/hooks/useActionBarHeightVar'; import { usePageTransitionLayer } from '@/components/common/PageTransitionLayer'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; @@ -492,32 +493,11 @@ export function AuthFilesPage() { [filter, navigate] ); - useLayoutEffect(() => { - if (typeof window === 'undefined') return; - - const actionsEl = floatingBatchActionsRef.current; - if (!actionsEl) { - document.documentElement.style.removeProperty('--auth-files-action-bar-height'); - return; - } - - const updatePadding = () => { - const height = actionsEl.getBoundingClientRect().height; - document.documentElement.style.setProperty('--auth-files-action-bar-height', `${height}px`); - }; - - updatePadding(); - window.addEventListener('resize', updatePadding); - - const ro = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(updatePadding); - ro?.observe(actionsEl); - - return () => { - ro?.disconnect(); - window.removeEventListener('resize', updatePadding); - document.documentElement.style.removeProperty('--auth-files-action-bar-height'); - }; - }, [batchActionBarVisible, selectionCount]); + useActionBarHeightVar( + floatingBatchActionsRef, + '--auth-files-action-bar-height', + batchActionBarVisible + ); useEffect(() => { selectionCountRef.current = selectionCount; diff --git a/src/pages/ConfigPage.tsx b/src/pages/ConfigPage.tsx index deb8e70..d59a5ac 100644 --- a/src/pages/ConfigPage.tsx +++ b/src/pages/ConfigPage.tsx @@ -3,7 +3,6 @@ import { lazy, useCallback, useEffect, - useLayoutEffect, useMemo, useRef, useState, @@ -25,6 +24,7 @@ import { import { VisualConfigEditor } from '@/components/config/VisualConfigEditor'; import { DiffModal } from '@/components/config/DiffModal'; import { useMediaQuery } from '@/hooks/useMediaQuery'; +import { useActionBarHeightVar } from '@/hooks/useActionBarHeightVar'; import { useUnsavedChangesGuard } from '@/hooks/useUnsavedChangesGuard'; import { useVisualConfig } from '@/hooks/useVisualConfig'; import { useNotificationStore, useAuthStore, useThemeStore, useConfigStore } from '@/stores'; @@ -427,29 +427,7 @@ export function ConfigPage() { }, [lastSearchedQuery, performSearch]); // Keep bottom floating actions from covering page content by syncing its height to a CSS variable. - useLayoutEffect(() => { - if (typeof window === 'undefined' || !shouldRenderFloatingActions) return; - - const actionsEl = floatingActionsRef.current; - if (!actionsEl) return; - - const updatePadding = () => { - const height = actionsEl.getBoundingClientRect().height; - document.documentElement.style.setProperty('--config-action-bar-height', `${height}px`); - }; - - updatePadding(); - window.addEventListener('resize', updatePadding); - - const ro = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(updatePadding); - ro?.observe(actionsEl); - - return () => { - ro?.disconnect(); - window.removeEventListener('resize', updatePadding); - document.documentElement.style.removeProperty('--config-action-bar-height'); - }; - }, [shouldRenderFloatingActions]); + useActionBarHeightVar(floatingActionsRef, '--config-action-bar-height', shouldRenderFloatingActions); // Status text const getStatusText = () => {