feat: add discover back to top fab
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
c038c791af
commit
a97e60cd00
|
|
@ -0,0 +1,107 @@
|
|||
import { m } from "@renderer/components/common/Motion"
|
||||
import { useTypeScriptHappyCallback } from "@renderer/hooks/common/useTypescriptHappyCallback"
|
||||
import { jotaiStore } from "@renderer/lib/jotai"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import clsx from "clsx"
|
||||
import { typescriptHappyForwardRef } from "foxact/typescript-happy-forward-ref"
|
||||
import type { HTMLMotionProps } from "framer-motion"
|
||||
import { AnimatePresence } from "framer-motion"
|
||||
import { atom, useAtomValue } from "jotai"
|
||||
import type React from "react"
|
||||
import type { JSX, PropsWithChildren, ReactNode } from "react"
|
||||
import { useId } from "react"
|
||||
|
||||
import { RootPortal } from "../portal"
|
||||
|
||||
const fabContainerElementAtom = atom(null as HTMLDivElement | null)
|
||||
|
||||
export interface FABConfig {
|
||||
id: string
|
||||
icon: JSX.Element
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export const FABBase = typescriptHappyForwardRef(
|
||||
(
|
||||
props: PropsWithChildren<
|
||||
{
|
||||
id: string
|
||||
show?: boolean
|
||||
children: JSX.Element
|
||||
} & HTMLMotionProps<"button">
|
||||
>,
|
||||
ref: React.ForwardedRef<HTMLButtonElement>,
|
||||
) => {
|
||||
const { children, show = true, ...extra } = props
|
||||
const { className, ...rest } = extra
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
<m.button
|
||||
type="button"
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0, opacity: 0 }}
|
||||
ref={ref}
|
||||
aria-label="Floating action button"
|
||||
className={cn(
|
||||
"mt-2 flex items-center justify-center",
|
||||
"size-9 text-lg md:text-base",
|
||||
"outline-accent hover:opacity-100 focus:opacity-100 focus:outline-none",
|
||||
"rounded-xl border border-zinc-400/20 backdrop-blur-lg dark:border-zinc-500/30 dark:text-zinc-200",
|
||||
"bg-zinc-50/80 shadow-lg dark:bg-neutral-900/80",
|
||||
"transition-all duration-500 ease-in-out",
|
||||
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</m.button>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export const FABPortable = typescriptHappyForwardRef(
|
||||
(
|
||||
props: {
|
||||
children: React.JSX.Element
|
||||
onClick: () => void
|
||||
show?: boolean
|
||||
},
|
||||
ref: React.ForwardedRef<HTMLButtonElement>,
|
||||
) => {
|
||||
const { onClick, children, show = true } = props
|
||||
const id = useId()
|
||||
const portalElement = useAtomValue(fabContainerElementAtom)
|
||||
|
||||
if (!portalElement) return null
|
||||
|
||||
return (
|
||||
<RootPortal to={portalElement}>
|
||||
<FABBase ref={ref} id={id} show={show} onClick={onClick}>
|
||||
{children}
|
||||
</FABBase>
|
||||
</RootPortal>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export const FABContainer = (props: { children?: ReactNode }) => {
|
||||
return (
|
||||
<div
|
||||
ref={useTypeScriptHappyCallback((ref) => jotaiStore.set(fabContainerElementAtom, ref), [])}
|
||||
data-testid="fab-container"
|
||||
data-hide-print
|
||||
className={clsx(
|
||||
"fixed bottom-[calc(2rem+env(safe-area-inset-bottom))] left-[calc(100vw-3rem-1rem)] z-[9] flex flex-col",
|
||||
"transition-transform duration-300 ease-in-out",
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./FABContainer"
|
||||
|
|
@ -91,7 +91,9 @@ export const FeedForm: Component<{
|
|||
<div className="center grow flex-col gap-3">
|
||||
<i className="i-mgc-close-cute-re size-7 text-red-500" />
|
||||
<p>Error in fetching feed.</p>
|
||||
<p className="break-all px-8 text-center">{getFetchErrorMessage(feedQuery.error)}</p>
|
||||
<p className="cursor-text select-text break-all px-8 text-center">
|
||||
{getFetchErrorMessage(feedQuery.error)}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export function Component() {
|
|||
const [search, setSearch] = useSearchParams()
|
||||
const { t } = useTranslation()
|
||||
useSubViewTitle("Discover")
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-2xl font-bold">{t("words.discover")}</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { getReadonlyRoute } from "@renderer/atoms/route"
|
||||
import { getSidebarActiveView, setSidebarActiveView } from "@renderer/atoms/sidebar"
|
||||
import { MotionButtonBase } from "@renderer/components/ui/button"
|
||||
import { FABContainer, FABPortable } from "@renderer/components/ui/fab"
|
||||
import { ScrollArea } from "@renderer/components/ui/scroll-area"
|
||||
import { springScrollTo } from "@renderer/lib/scroller"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { Outlet, useNavigate } from "react-router-dom"
|
||||
|
|
@ -20,13 +22,14 @@ export function Component() {
|
|||
const $scroll = scrollRef
|
||||
|
||||
if (!$scroll) return
|
||||
$scroll.scrollTop = 0
|
||||
|
||||
$scroll.onscroll = () => {
|
||||
springScrollTo(0, $scroll)
|
||||
const handler = () => {
|
||||
setIsTitleSticky($scroll.scrollTop > 120)
|
||||
}
|
||||
$scroll.addEventListener("scroll", handler)
|
||||
return () => {
|
||||
$scroll.onscroll = null
|
||||
$scroll.removeEventListener("scroll", handler)
|
||||
}
|
||||
}, [scrollRef, title])
|
||||
|
||||
|
|
@ -65,6 +68,23 @@ export function Component() {
|
|||
>
|
||||
<Outlet />
|
||||
</ScrollArea.ScrollArea>
|
||||
|
||||
<FABContainer>
|
||||
<BackToTopFAB show={isTitleSticky} scrollRef={scrollRef} />
|
||||
</FABContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const BackToTopFAB = ({ show, scrollRef }: { show: boolean; scrollRef: any }) => {
|
||||
return (
|
||||
<FABPortable
|
||||
onClick={() => {
|
||||
springScrollTo(0, scrollRef)
|
||||
}}
|
||||
show={show}
|
||||
>
|
||||
<i className="i-mingcute-arow-to-up-line" />
|
||||
</FABPortable>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue