feat: support go to top button

This commit is contained in:
Ou 2024-10-13 01:32:32 +08:00
parent dfabc1bc42
commit 303f7b136a
4 changed files with 88 additions and 8 deletions

View File

@ -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,
})

View File

@ -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>
)
}

View File

@ -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 />

View File

@ -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" />