mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
feat: support go to top button
This commit is contained in:
parent
dfabc1bc42
commit
303f7b136a
@ -59,3 +59,8 @@ export const currentSectionAtom = atom((get) => {
|
||||
})
|
||||
|
||||
export type Update<T> = T | ((prev: T) => T)
|
||||
|
||||
export const goToTopAtom = atom({
|
||||
ok: false,
|
||||
fn: undefined as (() => void) | undefined,
|
||||
})
|
||||
|
@ -1,8 +1,10 @@
|
||||
import type { UseOverlayScrollbarsParams } from "overlayscrollbars-react"
|
||||
import { useOverlayScrollbars } from "overlayscrollbars-react"
|
||||
import type { HTMLProps, PropsWithChildren } from "react"
|
||||
import { useEffect, useMemo, useRef } from "react"
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react"
|
||||
import { defu } from "defu"
|
||||
import { useSetAtom } from "jotai"
|
||||
import { goToTopAtom } from "~/atoms"
|
||||
|
||||
type Props = HTMLProps<HTMLDivElement> & UseOverlayScrollbarsParams
|
||||
const defaultScrollbarParams: UseOverlayScrollbarsParams = {
|
||||
@ -41,3 +43,63 @@ export function OverlayScrollbar({ children, options, events, defer, ...props }:
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function GlobalOverlayScrollbar({ children, ...props }: PropsWithChildren<HTMLProps<HTMLDivElement>>) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const lastScroll = useRef(0)
|
||||
const timer = useRef<any>()
|
||||
const setGoToTop = useSetAtom(goToTopAtom)
|
||||
const onScroll = useCallback((e: Event) => {
|
||||
const now = Date.now()
|
||||
if (now - lastScroll.current > 50) {
|
||||
lastScroll.current = now
|
||||
clearTimeout(timer.current)
|
||||
timer.current = setTimeout(
|
||||
() => {
|
||||
const el = e.target as HTMLElement
|
||||
setGoToTop({
|
||||
ok: el.scrollTop > 100,
|
||||
fn: () => el.scrollTo({ top: 0, behavior: "smooth" }),
|
||||
})
|
||||
},
|
||||
500,
|
||||
)
|
||||
}
|
||||
}, [setGoToTop])
|
||||
const [initialize] = useOverlayScrollbars({
|
||||
options: {
|
||||
scrollbars: {
|
||||
autoHide: "scroll",
|
||||
},
|
||||
},
|
||||
events: {
|
||||
scroll: (_, e) => onScroll(e),
|
||||
},
|
||||
defer: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
initialize({
|
||||
target: ref.current!,
|
||||
cancel: {
|
||||
nativeScrollbarsOverlaid: true,
|
||||
},
|
||||
})
|
||||
}, [initialize])
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current
|
||||
if (el) {
|
||||
ref.current?.addEventListener("scroll", onScroll)
|
||||
return () => {
|
||||
el?.removeEventListener("scroll", onScroll)
|
||||
}
|
||||
}
|
||||
}, [onScroll])
|
||||
|
||||
return (
|
||||
<div ref={ref} {...props}>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import type { SourceID } from "@shared/types"
|
||||
import { Homepage, Version } from "@shared/consts"
|
||||
import logo from "~/assets/icon.svg"
|
||||
import { useDark } from "~/hooks/useDark"
|
||||
import { currentSectionAtom, refetchSourcesAtom } from "~/atoms"
|
||||
import { currentSectionAtom, goToTopAtom, refetchSourcesAtom } from "~/atoms"
|
||||
|
||||
function ThemeToggle() {
|
||||
const { toggleDark } = useDark()
|
||||
@ -21,6 +21,18 @@ function ThemeToggle() {
|
||||
)
|
||||
}
|
||||
|
||||
function GoTop() {
|
||||
const { ok, fn: goToTop } = useAtomValue(goToTopAtom)
|
||||
return (
|
||||
ok && (
|
||||
<button
|
||||
type="button"
|
||||
className="i-ph:arrow-fat-up-duotone btn-pure"
|
||||
onClick={goToTop}
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
export function GithubIcon() {
|
||||
return <a className="i-ph-github-logo-duotone inline-block btn-pure" href={Homepage} />
|
||||
}
|
||||
@ -70,6 +82,7 @@ export function Header() {
|
||||
</a>
|
||||
</span>
|
||||
<span className="flex gap-2 items-center text-xl text-primary-600 dark:text-primary ">
|
||||
<GoTop />
|
||||
<RefreshButton />
|
||||
<ThemeToggle />
|
||||
<GithubIcon />
|
||||
|
@ -1,14 +1,14 @@
|
||||
import "~/styles/globals.css"
|
||||
import "virtual:uno.css"
|
||||
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"
|
||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools"
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
|
||||
import "~/styles/globals.css"
|
||||
import "virtual:uno.css"
|
||||
import type { QueryClient } from "@tanstack/react-query"
|
||||
import { Author, Homepage } from "@shared/consts"
|
||||
import clsx from "clsx"
|
||||
import { Header } from "~/components/header"
|
||||
import { useOnReload } from "~/hooks/useOnReload"
|
||||
import { OverlayScrollbar } from "~/components/common/overlay-scrollbar"
|
||||
import { GlobalOverlayScrollbar } from "~/components/common/overlay-scrollbar"
|
||||
|
||||
export const Route = createRootRouteWithContext<{
|
||||
queryClient: QueryClient
|
||||
@ -28,8 +28,8 @@ function RootComponent() {
|
||||
useOnReload()
|
||||
return (
|
||||
<>
|
||||
<OverlayScrollbar className={clsx([
|
||||
"h-full overflow-x-auto relative px-4",
|
||||
<GlobalOverlayScrollbar className={clsx([
|
||||
"h-full overflow-x-auto px-4",
|
||||
"md:(px-10)",
|
||||
"lg:(px-24)",
|
||||
])}
|
||||
@ -59,7 +59,7 @@ function RootComponent() {
|
||||
</a>
|
||||
</span>
|
||||
</footer>
|
||||
</OverlayScrollbar>
|
||||
</GlobalOverlayScrollbar>
|
||||
{import.meta.env.DEV && (
|
||||
<>
|
||||
<ReactQueryDevtools buttonPosition="bottom-left" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user