feat(entry-content): introduce navigation hints constants and refactor hint logic
- Added constants for navigation hints, including icons and text, to improve maintainability and clarity. - Refactored the EntryNavigationHandler and useEntryNavigationHints hook to utilize these constants, enhancing the user experience with clearer guidance. - Updated styling for hint buttons to improve visual consistency across components. These changes collectively streamline the navigation hint implementation, providing a more intuitive interaction model for users. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
92dbd65690
commit
60ea682fba
|
|
@ -36,6 +36,7 @@ import { useCommandHotkey } from "~/modules/command/hooks/use-register-hotkey"
|
|||
import { useWheelGestureClose } from "~/modules/entry-column/hooks/useWheelGestureClose"
|
||||
|
||||
import { ApplyEntryActions } from "../../ApplyEntryActions"
|
||||
import { NAVIGATION_HINTS_ICONS, NAVIGATION_HINTS_TEXT } from "../../constants/navigation-hints"
|
||||
import { useEntryContent } from "../../hooks"
|
||||
import { useEntryNavigationHints } from "../../hooks/useEntryNavigationHints"
|
||||
import { AIEntryHeader } from "../entry-header"
|
||||
|
|
@ -278,7 +279,6 @@ const EntryNavigationHandler = ({ entryId }: { entryId: string }) => {
|
|||
} = useEntryNavigationHints({
|
||||
enabled: when && !!entryId,
|
||||
entryId,
|
||||
scrollThreshold: 100,
|
||||
})
|
||||
|
||||
const isZenMode = useIsZenMode()
|
||||
|
|
@ -323,7 +323,7 @@ const EntryNavigationHandler = ({ entryId }: { entryId: string }) => {
|
|||
className={clsx(
|
||||
"group pointer-events-auto flex items-center gap-2",
|
||||
"rounded-full border px-3.5 py-2",
|
||||
"border-border/40 bg-material-ultra-thin/70 shadow-[0_1px_2px_rgba(0,0,0,0.06),0_8px_24px_rgba(0,0,0,0.08)]",
|
||||
"border-border/40 bg-material-ultra-thick shadow-[0_1px_2px_rgba(0,0,0,0.06),0_8px_24px_rgba(0,0,0,0.08)]",
|
||||
"hover:bg-material-thin/70 hover:border-border/60 active:scale-[0.98]",
|
||||
"backdrop-blur-background",
|
||||
)}
|
||||
|
|
@ -339,24 +339,24 @@ const EntryNavigationHandler = ({ entryId }: { entryId: string }) => {
|
|||
{/* First entry hint */}
|
||||
{showFirstEntryHint &&
|
||||
renderHintButton(
|
||||
tw`i-mgc-up-cute-re`,
|
||||
"Scroll up or click left-top back button to exit",
|
||||
NAVIGATION_HINTS_ICONS.ARROW_UP,
|
||||
NAVIGATION_HINTS_TEXT.SCROLL_UP_EXIT,
|
||||
"top",
|
||||
)}
|
||||
|
||||
{/* Scroll threshold hint or wheel gesture hint */}
|
||||
{(showScrollThresholdHint || showScrollHint) &&
|
||||
renderHintButton(
|
||||
tw`i-mingcute-arrow-left-up-line`,
|
||||
"Scroll up or click left-top back button to exit",
|
||||
NAVIGATION_HINTS_ICONS.ARROW_LEFT_UP,
|
||||
NAVIGATION_HINTS_TEXT.SCROLL_UP_EXIT,
|
||||
"top",
|
||||
)}
|
||||
|
||||
{/* Bottom hint */}
|
||||
{showBottomHint &&
|
||||
renderHintButton(
|
||||
tw`i-mingcute-arrow-to-down-line`,
|
||||
"Press ESC or click left-top back button to exit",
|
||||
NAVIGATION_HINTS_ICONS.ARROW_TO_DOWN,
|
||||
NAVIGATION_HINTS_TEXT.ESC_EXIT,
|
||||
"bottom",
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import { WrappedElementProvider } from "~/providers/wrapped-element-provider"
|
|||
|
||||
import { AISummary } from "../../AISummary"
|
||||
import { ApplyEntryActions } from "../../ApplyEntryActions"
|
||||
import { NAVIGATION_HINTS_ICONS, NAVIGATION_HINTS_TEXT } from "../../constants/navigation-hints"
|
||||
import { useEntryContent, useEntryMediaInfo } from "../../hooks"
|
||||
import { useEntryNavigationHints } from "../../hooks/useEntryNavigationHints"
|
||||
import { EntryHeader } from "../entry-header"
|
||||
|
|
@ -330,7 +331,6 @@ const EntryNavigationHandler = ({ entryId }: { entryId: string }) => {
|
|||
const { showFirstEntryHint, showScrollHint, showBottomHint } = useEntryNavigationHints({
|
||||
enabled: when && !!entryId,
|
||||
entryId,
|
||||
scrollThreshold: 100,
|
||||
})
|
||||
|
||||
// Render hint button with different states
|
||||
|
|
@ -367,26 +367,22 @@ const EntryNavigationHandler = ({ entryId }: { entryId: string }) => {
|
|||
{/* First entry hint */}
|
||||
{showFirstEntryHint &&
|
||||
renderHintButton(
|
||||
"i-mgc-arrow-up-cute-re",
|
||||
"Scroll up or click left-top back button to exit",
|
||||
NAVIGATION_HINTS_ICONS.ARROW_UP,
|
||||
NAVIGATION_HINTS_TEXT.SCROLL_UP_EXIT,
|
||||
"top",
|
||||
)}
|
||||
|
||||
{/* Scroll threshold hint */}
|
||||
{showScrollHint &&
|
||||
renderHintButton(
|
||||
"i-mgc-arrow-up-cute-re",
|
||||
"Scroll up or click left-top back button to exit",
|
||||
NAVIGATION_HINTS_ICONS.ARROW_UP,
|
||||
NAVIGATION_HINTS_TEXT.SCROLL_UP_EXIT,
|
||||
"top",
|
||||
)}
|
||||
|
||||
{/* Bottom hint */}
|
||||
{showBottomHint &&
|
||||
renderHintButton(
|
||||
"i-mgc-close-cute-re",
|
||||
"Press ESC or click left-top back button to exit",
|
||||
"bottom",
|
||||
)}
|
||||
renderHintButton(NAVIGATION_HINTS_ICONS.CLOSE, NAVIGATION_HINTS_TEXT.ESC_EXIT, "bottom")}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export const EntryScrollingAndNavigationHandler = ({
|
|||
"pointer-events-none absolute !right-1/2 z-40 !translate-x-1/2",
|
||||
"bottom-12",
|
||||
"backdrop-blur-background rounded-full border px-3.5 py-2",
|
||||
"border-border/40 bg-material-ultra-thin/70 shadow-[0_1px_2px_rgba(0,0,0,0.06),0_8px_24px_rgba(0,0,0,0.08)]",
|
||||
"border-border/40 bg-material-ultra-thick shadow-[0_1px_2px_rgba(0,0,0,0.06),0_8px_24px_rgba(0,0,0,0.08)]",
|
||||
"hover:bg-material-thin/70 hover:border-border/60 active:scale-[0.98]",
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Constants for entry navigation hints behavior
|
||||
*/
|
||||
export const NAVIGATION_HINTS_CONSTANTS = {
|
||||
/** Default scroll threshold to trigger scroll hint (px) */
|
||||
DEFAULT_SCROLL_THRESHOLD: 100,
|
||||
|
||||
/** Delay before showing first entry hint (ms) */
|
||||
FIRST_HINT_DELAY: 500,
|
||||
|
||||
/** Duration to show hints before auto-hiding (ms) */
|
||||
HINT_DISPLAY_DURATION: 3000,
|
||||
|
||||
/** Distance from bottom to trigger bottom hint (px) */
|
||||
BOTTOM_THRESHOLD: 50,
|
||||
|
||||
/** Distance from bottom to hide bottom hint when scrolling up (px) */
|
||||
BOTTOM_HIDE_THRESHOLD: 100,
|
||||
|
||||
/** Throttle interval for scroll handler (ms) */
|
||||
SCROLL_THROTTLE_INTERVAL: 100,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Text constants for navigation hints
|
||||
*/
|
||||
export const NAVIGATION_HINTS_TEXT = {
|
||||
SCROLL_UP_EXIT: "Scroll up or click left-top back button to exit",
|
||||
ESC_EXIT: "Press ESC or click left-top back button to exit",
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Icon constants for navigation hints
|
||||
*/
|
||||
export const NAVIGATION_HINTS_ICONS = {
|
||||
ARROW_UP: "i-mgc-up-cute-re",
|
||||
ARROW_LEFT_UP: "i-mingcute-arrow-left-up-line",
|
||||
ARROW_TO_DOWN: "i-mingcute-arrow-to-down-line",
|
||||
CLOSE: "i-mgc-close-cute-re",
|
||||
} as const
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
import { useScrollViewElement } from "@follow/components/ui/scroll-area/hooks.js"
|
||||
import { throttle } from "es-toolkit"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { startTransition, useEffect, useRef, useState } from "react"
|
||||
import { useEventCallback } from "usehooks-ts"
|
||||
|
||||
import { NAVIGATION_HINTS_CONSTANTS } from "../constants/navigation-hints"
|
||||
|
||||
interface UseEntryNavigationHintsOptions {
|
||||
/** Whether hints are enabled */
|
||||
enabled: boolean
|
||||
/** Entry ID to track changes */
|
||||
entryId?: string
|
||||
/** Scroll threshold to show hint (default: 100px) */
|
||||
/** Scroll threshold to show hint */
|
||||
scrollThreshold?: number
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ interface UseEntryNavigationHintsReturn {
|
|||
export const useEntryNavigationHints = ({
|
||||
enabled,
|
||||
entryId,
|
||||
scrollThreshold = 100,
|
||||
scrollThreshold = NAVIGATION_HINTS_CONSTANTS.DEFAULT_SCROLL_THRESHOLD,
|
||||
}: UseEntryNavigationHintsOptions): UseEntryNavigationHintsReturn => {
|
||||
const $scrollElement = useScrollViewElement()
|
||||
|
||||
|
|
@ -45,6 +47,8 @@ export const useEntryNavigationHints = ({
|
|||
const firstHintTimerRef = useRef<ReturnType<typeof setTimeout>>(void 0)
|
||||
const scrollHintTimerRef = useRef<ReturnType<typeof setTimeout>>(void 0)
|
||||
const bottomHintTimerRef = useRef<ReturnType<typeof setTimeout>>(void 0)
|
||||
const lastScrollTopRef = useRef(0)
|
||||
const scrollDirectionRef = useRef<"up" | "down" | "none">("none")
|
||||
|
||||
// Reset hints when entry changes
|
||||
useEffect(() => {
|
||||
|
|
@ -53,30 +57,38 @@ export const useEntryNavigationHints = ({
|
|||
hasShownFirstHintRef.current = false
|
||||
hasShownScrollHintRef.current = false
|
||||
hasShownBottomHintRef.current = false
|
||||
lastScrollTopRef.current = 0
|
||||
scrollDirectionRef.current = "none"
|
||||
|
||||
// Clear existing timers
|
||||
if (firstHintTimerRef.current) clearTimeout(firstHintTimerRef.current)
|
||||
if (scrollHintTimerRef.current) clearTimeout(scrollHintTimerRef.current)
|
||||
if (bottomHintTimerRef.current) clearTimeout(bottomHintTimerRef.current)
|
||||
|
||||
// Reset all hint states
|
||||
setShowFirstEntryHint(false)
|
||||
setShowScrollHint(false)
|
||||
setShowBottomHint(false)
|
||||
// Reset all hint states with low priority
|
||||
startTransition(() => {
|
||||
setShowFirstEntryHint(false)
|
||||
setShowScrollHint(false)
|
||||
setShowBottomHint(false)
|
||||
})
|
||||
|
||||
if (enabled) {
|
||||
// Show first entry hint after a brief delay
|
||||
firstHintTimerRef.current = setTimeout(() => {
|
||||
if (!hasShownFirstHintRef.current) {
|
||||
setShowFirstEntryHint(true)
|
||||
startTransition(() => {
|
||||
setShowFirstEntryHint(true)
|
||||
})
|
||||
hasShownFirstHintRef.current = true
|
||||
|
||||
// Hide after 3 seconds
|
||||
// Hide after configured duration
|
||||
firstHintTimerRef.current = setTimeout(() => {
|
||||
setShowFirstEntryHint(false)
|
||||
}, 3000)
|
||||
startTransition(() => {
|
||||
setShowFirstEntryHint(false)
|
||||
})
|
||||
}, NAVIGATION_HINTS_CONSTANTS.HINT_DISPLAY_DURATION)
|
||||
}
|
||||
}, 500) // Small delay to allow content to load
|
||||
}, NAVIGATION_HINTS_CONSTANTS.FIRST_HINT_DELAY) // Small delay to allow content to load
|
||||
}
|
||||
}
|
||||
}, [entryId, enabled])
|
||||
|
|
@ -91,35 +103,74 @@ export const useEntryNavigationHints = ({
|
|||
const { clientHeight } = $scrollElement
|
||||
const scrollBottom = scrollHeight - clientHeight - scrollTop
|
||||
|
||||
// Check if scrolled past threshold
|
||||
if (scrollTop > scrollThreshold && !hasShownScrollHintRef.current) {
|
||||
// Detect scroll direction
|
||||
const lastScrollTop = lastScrollTopRef.current
|
||||
if (scrollTop > lastScrollTop) {
|
||||
scrollDirectionRef.current = "down"
|
||||
} else if (scrollTop < lastScrollTop) {
|
||||
scrollDirectionRef.current = "up"
|
||||
}
|
||||
lastScrollTopRef.current = scrollTop
|
||||
|
||||
// Check if scrolled past threshold and scrolling up
|
||||
if (
|
||||
scrollTop > scrollThreshold &&
|
||||
!hasShownScrollHintRef.current &&
|
||||
scrollDirectionRef.current === "up"
|
||||
) {
|
||||
hasShownScrollHintRef.current = true
|
||||
setShowScrollHint(true)
|
||||
startTransition(() => {
|
||||
setShowScrollHint(true)
|
||||
})
|
||||
|
||||
// Clear previous timer
|
||||
if (scrollHintTimerRef.current) clearTimeout(scrollHintTimerRef.current)
|
||||
|
||||
// Hide after 3 seconds
|
||||
// Hide after configured duration
|
||||
scrollHintTimerRef.current = setTimeout(() => {
|
||||
setShowScrollHint(false)
|
||||
}, 3000)
|
||||
startTransition(() => {
|
||||
setShowScrollHint(false)
|
||||
})
|
||||
}, NAVIGATION_HINTS_CONSTANTS.HINT_DISPLAY_DURATION)
|
||||
}
|
||||
|
||||
// Check if at bottom (within 50px threshold)
|
||||
if (scrollBottom <= 50 && !hasShownBottomHintRef.current) {
|
||||
// Check if at bottom (within configured threshold)
|
||||
if (
|
||||
scrollBottom <= NAVIGATION_HINTS_CONSTANTS.BOTTOM_THRESHOLD &&
|
||||
!hasShownBottomHintRef.current
|
||||
) {
|
||||
hasShownBottomHintRef.current = true
|
||||
setShowBottomHint(true)
|
||||
startTransition(() => {
|
||||
setShowBottomHint(true)
|
||||
})
|
||||
|
||||
// Clear previous timer
|
||||
if (bottomHintTimerRef.current) clearTimeout(bottomHintTimerRef.current)
|
||||
|
||||
// Hide after 3 seconds
|
||||
// Hide after configured duration
|
||||
bottomHintTimerRef.current = setTimeout(() => {
|
||||
setShowBottomHint(false)
|
||||
startTransition(() => {
|
||||
setShowBottomHint(false)
|
||||
})
|
||||
hasShownBottomHintRef.current = false
|
||||
}, 3000)
|
||||
}, NAVIGATION_HINTS_CONSTANTS.HINT_DISPLAY_DURATION)
|
||||
}
|
||||
}, 100),
|
||||
|
||||
// Hide bottom hint if user scrolls up from bottom
|
||||
if (
|
||||
scrollBottom > NAVIGATION_HINTS_CONSTANTS.BOTTOM_HIDE_THRESHOLD &&
|
||||
hasShownBottomHintRef.current &&
|
||||
scrollDirectionRef.current === "up"
|
||||
) {
|
||||
// Clear timer if exists
|
||||
if (bottomHintTimerRef.current) clearTimeout(bottomHintTimerRef.current)
|
||||
|
||||
startTransition(() => {
|
||||
setShowBottomHint(false)
|
||||
})
|
||||
hasShownBottomHintRef.current = false
|
||||
}
|
||||
}, NAVIGATION_HINTS_CONSTANTS.SCROLL_THROTTLE_INTERVAL),
|
||||
)
|
||||
|
||||
// Attach scroll listener
|
||||
|
|
|
|||
Loading…
Reference in New Issue