Skip to content

Commit

Permalink
feat: add discover back to top fab
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Sep 12, 2024
1 parent c038c79 commit a97e60c
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 4 deletions.
107 changes: 107 additions & 0 deletions src/renderer/src/components/ui/fab/FABContainer.tsx
Original file line number Diff line number Diff line change
@@ -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>
)
}
1 change: 1 addition & 0 deletions src/renderer/src/components/ui/fab/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./FABContainer"
4 changes: 3 additions & 1 deletion src/renderer/src/modules/discover/feed-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
26 changes: 23 additions & 3 deletions src/renderer/src/pages/(main)/(layer)/(subview)/layout.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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])

Expand Down Expand Up @@ -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>
)
}

0 comments on commit a97e60c

Please sign in to comment.