feat(ssr): optimize entries styles

This commit is contained in:
DIYgod
2025-05-07 17:03:12 +08:00
parent efc2e9713b
commit df1ed5ba92
9 changed files with 68 additions and 161 deletions
+16 -37
View File
@@ -1,11 +1,9 @@
import type { EntriesPreview } from "@client/query/entries"
import type { Feed } from "@client/query/feed"
import { TitleMarquee } from "@follow/components/ui/marquee/index.jsx"
import { cn } from "@follow/utils/utils"
import dayjs from "dayjs"
import type { FC } from "react"
import { TeleportalTakeOff } from "../layout/main/teleportal"
import { FeedIcon } from "../ui/feed-icon"
import { LazyImage } from "../ui/image"
@@ -14,41 +12,22 @@ export const GridList: FC<{
feed?: Feed
}> = ({ entries, feed }) => {
return (
<TeleportalTakeOff>
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 lg:px-3">
{entries.map((entry) => (
<Wrapper key={entry.id} href={entry.url || entry.authorUrl || ""}>
<div className="relative -mx-1.5 -mt-1.5">
<LazyImage
src={entry.media?.[0]!.url}
className="aspect-video h-auto w-full shrink-0 rounded-md object-cover"
/>
</div>
<GridItemFooter feed={feed} entryId={entry.id} entryPreview={entry} />
</Wrapper>
))}
</div>
</TeleportalTakeOff>
)
}
const Wrapper: FC<{
children: React.ReactNode
href: string
wrapperClassName?: string
}> = ({ children, href, wrapperClassName }) => {
return (
<a
href={href}
rel="noopener noreferrer"
target="_blank"
className={cn(
"hover:bg-material-medium overflow-hidden rounded-md p-1.5 duration-200",
wrapperClassName,
)}
>
{children}
</a>
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 lg:px-3">
{entries.map((entry) => (
<div
className="hover:bg-material-medium overflow-hidden rounded-md p-1.5 duration-200"
key={entry.id}
>
<div className="relative -mx-1.5 -mt-1.5">
<LazyImage
src={entry.media?.[0]!.url}
className="aspect-video h-auto w-full shrink-0 rounded-md object-cover"
/>
</div>
<GridItemFooter feed={feed} entryId={entry.id} entryPreview={entry} />
</div>
))}
</div>
)
}
+2 -2
View File
@@ -50,7 +50,7 @@ const NormalList: FC<{
return (
<>
{entries?.map((entry) => (
<a className="relative" href={entry.url || void 0} target="_blank" key={entry.id}>
<div className="relative cursor-default" key={entry.id}>
<NormalListItem
withDetails
entryId={entry.id}
@@ -61,7 +61,7 @@ const NormalList: FC<{
feedId: feed?.feed.id || entry.feeds?.id!,
}}
/>
</a>
</div>
))}
</>
)
+20 -35
View File
@@ -23,57 +23,42 @@ function NormalListItemImpl({
const displayTime = entry.entries.publishedAt
return (
<MagneticHoverEffect className={"group relative flex gap-2 py-4 pl-3 pr-2"}>
<MagneticHoverEffect className={"group relative mx-auto flex max-w-3xl gap-2 py-4 pl-3 pr-2"}>
<FeedIcon feed={feed} fallback entry={entry.entries} />
<div className={"-mt-0.5 flex-1 text-sm leading-tight"}>
<div
className={cn(
"flex gap-1 text-[10px] font-bold",
"text-text-secondary",
entry.collections && "text-zinc-600 dark:text-zinc-500",
)}
>
<div className={"-mt-0.5 flex-1 text-base leading-tight"}>
<div className={cn("flex gap-1 text-xs font-bold", "text-text-secondary")}>
<EllipsisHorizontalTextWithTooltip className="truncate">
{feed?.title}
</EllipsisHorizontalTextWithTooltip>
<span>·</span>
<span className="shrink-0">{!!displayTime && <RelativeTime date={displayTime} />}</span>
</div>
<div
className={cn(
"text-text relative my-0.5 line-clamp-2 break-words",
!!entry.collections && "pr-5",
entry.entries.title ? withDetails && "font-medium" : "text-[13px]",
)}
>
<div className={cn("text-text relative my-0.5 line-clamp-1 break-words font-medium")}>
{entry.entries.title}
{/* {!!entry.collections && <StarIcon className="absolute right-0 top-0" />} */}
</div>
{withDetails && (
<div className="flex gap-2">
<div className={cn("grow text-[13px]", "text-text-secondary line-clamp-5")}>
<div className={cn("grow text-sm", "text-text-secondary line-clamp-3")}>
{entry.entries.description}
</div>
{entry.entries.media?.[0] && (
<div className="relative size-24 shrink-0 overflow-hidden rounded">
<LazyImage
proxy={{
width: 160,
height: 160,
}}
className="overflow-hidden rounded"
src={entry.entries.media[0].url}
height={entry.entries.media[0].height}
width={entry.entries.media[0].width}
blurhash={entry.entries.media[0].blurhash}
/>
</div>
)}
</div>
)}
</div>
{entry.entries.media?.[0] && (
<div className="relative size-24 shrink-0 overflow-hidden rounded">
<LazyImage
proxy={{
width: 160,
height: 160,
}}
className="overflow-hidden rounded-lg"
src={entry.entries.media[0].url}
height={entry.entries.media[0].height}
width={entry.entries.media[0].width}
blurhash={entry.entries.media[0].blurhash}
/>
</div>
)}
</MagneticHoverEffect>
)
}
+26 -29
View File
@@ -1,4 +1,3 @@
import { TeleportalTakeOff } from "@client/components/layout/main/teleportal"
import { LazyImage } from "@client/components/ui/image"
import { getPreferredTitle } from "@client/lib/helper"
import type { EntriesPreview } from "@client/query/entries"
@@ -75,7 +74,7 @@ const breakpoints = {
// 48rem => 48 * 16= 768
768: 3,
// 72rem => 72 * 16= 1152
1152: 4,
976: 4,
// 80rem => 80 * 16= 1280
1280: 5,
1536: 6,
@@ -173,33 +172,31 @@ export const PictureList: FC<{
return (
<PhotoProvider>
<TeleportalTakeOff>
<div className="relative flex min-w-0 px-2 lg:px-6">
<MemoedDangerousHTMLStyle>{inlineStyle}</MemoedDangerousHTMLStyle>
<div className="flex w-full flex-wrap" ref={containerRef}>
{isInitLayout && (
<MasonryItemWidthContext value={currentItemWidth}>
{/* eslint-disable-next-line @eslint-react/no-context-provider */}
<MasonryItemsAspectRatioContext.Provider value={masonryItemsRadio}>
<MasonryItemsAspectRatioSetterContext value={setMasonryItemsRadio}>
<div className="relative w-full">
<Masonry
items={items}
columnGutter={yGutter}
columnWidth={currentItemWidth}
columnCount={currentColumn}
overscanBy={2}
render={render}
itemKey={itemKey}
/>
</div>
</MasonryItemsAspectRatioSetterContext>
</MasonryItemsAspectRatioContext.Provider>
</MasonryItemWidthContext>
)}
</div>
<div className="relative flex min-w-0 px-2 lg:px-6">
<MemoedDangerousHTMLStyle>{inlineStyle}</MemoedDangerousHTMLStyle>
<div className="flex w-full flex-wrap" ref={containerRef}>
{isInitLayout && (
<MasonryItemWidthContext value={currentItemWidth}>
{/* eslint-disable-next-line @eslint-react/no-context-provider */}
<MasonryItemsAspectRatioContext.Provider value={masonryItemsRadio}>
<MasonryItemsAspectRatioSetterContext value={setMasonryItemsRadio}>
<div className="relative w-full">
<Masonry
items={items}
columnGutter={yGutter}
columnWidth={currentItemWidth}
columnCount={currentColumn}
overscanBy={2}
render={render}
itemKey={itemKey}
/>
</div>
</MasonryItemsAspectRatioSetterContext>
</MasonryItemsAspectRatioContext.Provider>
</MasonryItemWidthContext>
)}
</div>
</TeleportalTakeOff>
</div>
</PhotoProvider>
)
}
@@ -226,7 +223,7 @@ const render: React.ComponentType<
ratio={data.height && data.width ? data.height / data.width : undefined}
>
<div
className="overflow-hidden rounded-md"
className="size-full overflow-hidden rounded-md"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
@@ -1,18 +0,0 @@
import { atom, useAtomValue, useSetAtom } from "jotai"
import type { FC, PropsWithChildren } from "react"
import { createPortal } from "react-dom"
const portalAtoms = atom(null as HTMLElement | null)
export const TeleportalDestination = () => {
const setPortal = useSetAtom(portalAtoms)
return <div className="flex flex-col" ref={setPortal} />
}
export const TeleportalTakeOff: FC<PropsWithChildren> = ({ children }) => {
const portal = useAtomValue(portalAtoms)
if (!portal) return null
return createPortal(children, portal)
}
+1 -1
View File
@@ -49,7 +49,7 @@ export const LazyImage = ({
src={nextSrc}
height={height}
width={width}
className={cn("size-full", className)}
className={cn("size-full object-cover", className)}
onClick={onClick}
tabIndex={1}
/>
@@ -100,7 +100,7 @@ export function Component() {
</Button>
</div>
</div>
<div className={cn("w-full pb-12 pt-8", "flex max-w-3xl flex-col gap-2")}>
<div className={cn("w-full pb-12 pt-8", "flex flex-col gap-2")}>
{entries.isLoading && !entries.data ? (
<LoadingCircle size="large" className="center mt-12" />
) : (
@@ -1,5 +1,4 @@
import { Header } from "@client/components/layout/header"
import { TeleportalDestination } from "@client/components/layout/main/teleportal"
import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.jsx"
import { PoweredByFooter } from "@follow/components/common/PoweredByFooter.jsx"
import { Outlet } from "react-router"
@@ -16,7 +15,6 @@ export const Component = () => {
<main className="relative mx-auto w-full max-w-[var(--container-max-width)] flex-1">
<Outlet />
</main>
<TeleportalDestination />
<PoweredByFooter />
</div>
)
@@ -1,6 +1,6 @@
import { cn } from "@follow/utils/utils"
import * as React from "react"
import { useCallback, useImperativeHandle, useRef } from "react"
import { useImperativeHandle, useRef } from "react"
type MagneticHoverEffectProps<T extends React.ElementType> = {
as?: T
@@ -22,47 +22,13 @@ export const MagneticHoverEffect = <T extends React.ElementType = "div">({
return itemRef.current as unknown as T
})
const handleMouseEnter = (e: React.MouseEvent<HTMLElement>) => {
if (!itemRef.current) return
const rect = itemRef.current.getBoundingClientRect()
const x = ((e.clientX - rect.left) / rect.width) * 100
const y = ((e.clientY - rect.top) / rect.height) * 100
itemRef.current.style.transition = "transform 0.2s cubic-bezier(0.33, 1, 0.68, 1)"
itemRef.current.style.setProperty("--origin-x", `${x}%`)
itemRef.current.style.setProperty("--origin-y", `${y}%`)
}
const handleMouseLeave = () => {
if (!itemRef.current) return
itemRef.current.style.transform = "translate(0px, 0px)"
itemRef.current.style.transition = "transform 0.4s cubic-bezier(0.33, 1, 0.68, 1)"
}
const handleMouseMove = useCallback((e: React.MouseEvent<HTMLElement>) => {
if (!itemRef.current) return
const rect = itemRef.current.getBoundingClientRect()
const centerX = rect.left + rect.width / 2
const centerY = rect.top + rect.height / 2
const distanceX = (e.clientX - centerX) * 0.05
const distanceY = (e.clientY - centerY) * 0.05
itemRef.current.style.transform = `translate(${distanceX}px, ${distanceY}px)`
}, [])
return (
<Component
ref={itemRef as any}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseMove={handleMouseMove}
{...rest}
className={cn(
"relative",
"inline-block transition-transform duration-200 ease-out will-change-transform",
"inline-block duration-200 ease-out",
"hover:before:bg-material-ultra-thick",
"before:backdrop-blur-background before:absolute before:-inset-x-2 before:inset-y-0 before:z-[-1] before:scale-0 before:rounded-xl before:opacity-0 before:transition-all before:duration-200 before:[transform-origin:var(--origin-x)_var(--origin-y)] hover:before:scale-100 hover:before:opacity-100",
rest.className,