refactor(ui): update styling for improved visual consistency across components

- Changed background classes to 'bg-material-ultra-thick' in various UI elements including media, buttons, and modals for a cohesive design.
- Standardized text color usage to 'text-text-secondary' and 'text-material-opaque' in entry items and notifications for better readability.
- Enhanced hover effects and transitions for buttons and interactive elements to align with design standards.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-04-24 20:34:04 +08:00
parent 9da9dec7ec
commit a525406600
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
48 changed files with 188 additions and 207 deletions

View File

@ -45,7 +45,7 @@ export const AnimatedCommandButton: FC<AnimatedCommandButtonProps & HTMLMotionPr
"rounded-md p-1.5 duration-200",
variant === "solid" || variant === "ghost"
? "border-accent/5 bg-accent/80 text-accent border backdrop-blur"
: "text-accent bg-slate-50/50 dark:bg-slate-900/50",
: "text-accent hover:bg-material-ultra-thick",
variant === "ghost" && "bg-theme-item-active hover:bg-theme-item-hover",
className,
)}

View File

@ -72,12 +72,12 @@
@apply bg-green-100 dark:bg-green-900;
&::before {
@apply bg-green-500;
@apply bg-green;
}
&::after {
content: " +";
@apply absolute left-0 text-green-500;
@apply text-green absolute left-0;
}
}
@ -90,7 +90,7 @@
&::after {
content: " -";
@apply absolute left-0 text-red-500;
@apply text-red absolute left-0;
}
}

View File

@ -15,6 +15,7 @@ interface CollapseProps {
defaultOpen?: boolean
collapseId?: string
onOpenChange?: (isOpened: boolean) => void
contentClassName?: string
}
export const CollapseGroup: Component<{
@ -91,7 +92,9 @@ export const CollapseControlled: Component<
</div>
)}
</div>
<CollapseContent isOpened={props.isOpened}>{props.children}</CollapseContent>
<CollapseContent isOpened={props.isOpened} className={props.contentClassName}>
{props.children}
</CollapseContent>
</div>
)
export const CollapseContent: Component<{

View File

@ -52,12 +52,13 @@ export const FABBase = typescriptHappyForwardRef(
"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",
"hover:border-border bg-background relative rounded-xl border border-transparent",
"group duration-200",
className,
)}
{...rest}
>
<div className="shadow-perfect border-border/50 pointer-events-none absolute inset-0 rounded-xl border shadow-xl duration-200 group-hover:opacity-0" />
{children}
</m.button>
)}

View File

@ -143,7 +143,9 @@ const TocHoverCard: React.FC<TocHoverCardProps> = ({
exit={{ opacity: 0, x: 110, transition: { duration: 0.1 } }}
transition={{ duration: 0.5, type: "spring" }}
className={cn(
"relative z-10 -mt-1 rounded-xl border bg-white px-3 py-1 text-xs drop-shadow-xl dark:bg-neutral-950",
"relative z-10 -mt-1 rounded-xl border",
"px-3 py-1 text-xs",
"bg-material-ultra-thick backdrop-blur-background shadow-context-menu",
"scrollbar-none max-h-[calc(100svh-4rem)] overflow-auto",
)}
>

View File

@ -6,7 +6,7 @@ export const BlockError = (props: { error: any; message: string }) => {
captureException(props.error)
}, [])
return (
<div className="center flex min-h-12 flex-col rounded bg-red-400 py-4 text-sm text-white dark:bg-red-800">
<div className="center bg-red flex min-h-12 flex-col rounded py-4 text-sm text-white">
{props.message}
<pre className="m-0 bg-transparent">{props.error?.message}</pre>

View File

@ -334,7 +334,7 @@ const FallbackMedia: FC<MediaProps> = ({ type, mediaContainerClassName, classNam
<div
className={cn(
"size-full",
"center rounded bg-zinc-100 dark:bg-neutral-900",
"center bg-material-ultra-thick rounded",
"not-prose @container !flex max-h-full flex-col space-y-1 p-4",
mediaContainerClassName,
)}

View File

@ -221,7 +221,7 @@ const ControlBar = memo(() => {
dragMomentum={false}
dragConstraints={{ current: document.documentElement }}
className={cn(
"absolute inset-x-2 bottom-2 h-8 rounded-2xl border bg-zinc-100/90 backdrop-blur-xl dark:border-transparent dark:bg-neutral-700/90",
"bg-material-thick backdrop-blur-background border-border absolute inset-x-2 bottom-2 h-8 rounded-2xl border",
"flex items-center gap-3 px-3",
"mx-auto max-w-[80vw]",
)}

View File

@ -15,11 +15,13 @@ export const VolumeSlider: FC<{
onVolumeChange?.(values[0]!)
}}
>
<Slider.Track className="relative w-1 grow rounded bg-zinc-500">
<Slider.Range className="absolute w-full rounded bg-black dark:bg-white" />
<Slider.Track className="relative w-1 grow rounded bg-white dark:bg-neutral-800">
<Slider.Range className="absolute w-full rounded bg-zinc-500/40 dark:bg-neutral-600" />
</Slider.Track>
{/* indicator */}
<Slider.Thumb
className="block size-3 rounded-full bg-black dark:bg-white"
className="block size-3 rounded-full bg-zinc-500 dark:bg-zinc-400"
aria-label="Volume"
/>
</Slider.Root>

View File

@ -17,14 +17,14 @@ export const PeekModalBaseButton = ({
<Tooltip>
<TooltipTrigger
type="button"
aria-label={label}
className={cn(
"no-drag-region center bg-background flex size-8 rounded-full p-1 shadow-sm ring-1 ring-zinc-200 dark:ring-neutral-800",
"no-drag-region center bg-background ring-border flex size-8 rounded-full p-1 shadow-sm ring-1 empty:hidden",
className,
)}
onClick={onClick}
>
{icon}
<span className="sr-only">{label}</span>
</TooltipTrigger>
<TooltipContent>{label}</TooltipContent>
</Tooltip>

View File

@ -0,0 +1,5 @@
import { createContext, useContext } from "react"
export const InPeekModal = createContext(false)
InPeekModal.displayName = "InPeekModal"
export const useInPeekModal = () => useContext(InPeekModal)

View File

@ -1,7 +1,7 @@
import { getStableRouterNavigate } from "@follow/components/atoms/route.js"
import { RootPortalProvider } from "@follow/components/ui/portal/provider.js"
import type { PropsWithChildren, ReactNode } from "react"
import { createContext, useContext, useState } from "react"
import { useState } from "react"
import { useTranslation } from "react-i18next"
import { m } from "~/components/common/Motion"
@ -9,9 +9,8 @@ import { m } from "~/components/common/Motion"
import { PeekModalBaseButton } from "../components/base"
import { FixedModalCloseButton } from "../components/close"
import { useCurrentModal, useModalStack } from "../stacked/hooks"
import { InPeekModal } from "./InPeekModal"
const InPeekModal = createContext(false)
export const useInPeekModal = () => useContext(InPeekModal)
export const PeekModal = (
props: PropsWithChildren<{
to?: string
@ -32,7 +31,7 @@ export const PeekModal = (
return (
<RootPortalProvider value={rootRef as HTMLElement}>
<div
className="scrollbar-none relative mx-auto mt-[10vh] max-w-full overflow-auto px-2 lg:max-w-[65rem] lg:p-0"
className="scrollbar-none relative mx-auto mt-[10vh] max-w-full overflow-hidden px-2 lg:max-w-[65rem] lg:p-0"
ref={setRootRef}
>
<m.div

View File

@ -29,7 +29,7 @@ export const ModalOverlay = forwardRef(
id="modal-overlay"
className={cn(
// NOTE: pointer-events-none is required, if remove this, when modal is closing, you can not click element behind the modal
"!pointer-events-none fixed inset-0 rounded-[var(--fo-window-radius)] bg-zinc-50/80 dark:bg-neutral-900/80",
"bg-material-ultra-thick !pointer-events-none fixed inset-0 rounded-[var(--fo-window-radius)]",
blur && "backdrop-blur-sm",
className,
)}

View File

@ -6,9 +6,9 @@ export const Paper: Component<{
}> = ({ children, className, as: As = "main" }) => (
<As
className={cn(
"relative bg-white md:col-start-1 lg:col-auto dark:bg-zinc-900",
"bg-background relative md:col-start-1 lg:col-auto",
"-m-4 p-[2rem_1rem] md:m-0 lg:p-[30px_45px]",
"rounded-[0_6px_6px_0] border-zinc-200/70 lg:border dark:border-neutral-800",
"border-border rounded-[0_6px_6px_0] lg:border",
"shadow-perfect perfect-sm",
"min-w-0",
"print:!border-none print:!bg-transparent print:!shadow-none",

View File

@ -31,8 +31,10 @@ const NormalDailyReportTitle: FC<DailyItemProps> = ({ day }) => {
return (
<button
type="button"
onClick={() => setOpenedSummary(day)}
className="border-b pb-6 last:border-b-0"
onClick={() => {
setOpenedSummary(day)
}}
className="border-b last:border-b-0"
>
<DailyReportTitle title={title} startDate={startDate} endDate={endDate} />
</button>

View File

@ -1,6 +1,5 @@
import { Spring } from "@follow/components/constants/spring.js"
import { EmptyIcon } from "@follow/components/icons/empty.jsx"
import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.jsx"
import { Card, CardContent } from "@follow/components/ui/card/index.jsx"
import { LoadingCircle } from "@follow/components/ui/loading/index.jsx"
import { RootPortal } from "@follow/components/ui/portal/index.js"
import { ScrollArea } from "@follow/components/ui/scroll-area/index.js"
@ -12,6 +11,7 @@ import {
} from "@follow/components/ui/tooltip/index.jsx"
import { nextFrame, stopPropagation } from "@follow/utils/dom"
import { cn, isBizId } from "@follow/utils/utils"
import { noop } from "foxact/noop"
import type { Components } from "hast-util-to-jsx-runtime"
import type { Variant } from "motion/react"
import { m, useAnimationControls } from "motion/react"
@ -21,7 +21,7 @@ import { Trans, useTranslation } from "react-i18next"
import { MenuItemText } from "~/atoms/context-menu"
import { useGeneralSettingSelector } from "~/atoms/settings/general"
import { Collapse } from "~/components/ui/collapse"
import { CollapseControlled } from "~/components/ui/collapse"
import { RelativeTime } from "~/components/ui/datetime"
import {
DropdownMenu,
@ -57,18 +57,34 @@ import { remarkSnowflakeId } from "./plugins/parse-snowflake"
import type { DailyItemProps, DailyView } from "./types"
import { useParseDailyDate } from "./useParseDailyDate"
export const DailyItem = ({ view, day, className }: DailyItemProps) => {
export const DailyItem = ({
view,
day,
className,
onClick,
isOpened,
}: DailyItemProps & {
isOpened: boolean
onClick: () => void
}) => {
const { title, startDate, endDate } = useParseDailyDate(day)
return (
<Collapse
<CollapseControlled
isOpened={isOpened}
onOpenChange={noop}
collapseId={`${day}`}
hideArrow
title={<DailyReportTitle title={title} startDate={startDate} endDate={endDate} />}
className={cn(className, "mx-auto w-full max-w-lg border-b pb-6 last:border-b-0")}
contentClassName="flex-1 flex flex-col"
title={
<button type="button" className="container" onClick={onClick}>
<DailyReportTitle title={title} startDate={startDate} endDate={endDate} />
</button>
}
className={cn(className, "mx-auto w-full max-w-lg border-b last:border-b-0")}
>
<DailyReportContent endDate={endDate} view={view} startDate={startDate} />
</Collapse>
</CollapseControlled>
)
}
@ -93,8 +109,9 @@ export const DailyReportTitle = ({
return (
<m.div
className="flex items-center justify-center gap-2 text-base"
className="flex items-center justify-center gap-2 pb-6 text-base"
layoutId={`daily-report-title-${title}`}
transition={Spring.presets.smooth}
>
<i className="i-mgc-ai-cute-re" />
<div className="font-medium">{t("ai_daily.title", { title })}</div>
@ -167,29 +184,30 @@ interface DailyReportContentProps {
endDate: number
}
export const DailyReportContent: Component<DailyReportContentProps> = ({
const DailyReportContent: Component<DailyReportContentProps> = ({
endDate,
startDate,
view,
className,
}) => {
const content = useQueryData({ endDate, startDate, view })
const RelatedEntryLink = useState(() => createRelatedEntryLink("modal"))[0]
return (
<Card className="border-none bg-transparent">
<CardContent className={cn("space-y-0 p-0", className)}>
<ScrollArea.ScrollArea mask={false} flex viewportClassName="h-[calc(100vh-176px)]">
<AutoResizeHeight spring>
{content.isLoading ? (
<LoadingCircle
size="large"
className="center flex h-[calc(100vh-176px)] text-center"
/>
) : (
!!content.data && (
<div className="relative h-0 flex-1 grow">
<div className="absolute inset-0 flex">
<ScrollArea.ScrollArea mask flex viewportClassName="grow" rootClassName="grow">
{content.isLoading ? (
<LoadingCircle size="large" className="center flex h-[calc(100vh-176px)] text-center" />
) : (
!!content.data && (
<m.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={Spring.smooth(0.35)}
className="overflow-hidden"
>
<Markdown
applyMiddleware={(pipeline) => {
pipeline.use(remarkSnowflakeId)
@ -205,12 +223,12 @@ export const DailyReportContent: Component<DailyReportContentProps> = ({
>
{content.data}
</Markdown>
)
)}
</AutoResizeHeight>
</m.div>
)
)}
</ScrollArea.ScrollArea>
</CardContent>
</Card>
</div>
</div>
)
}
@ -457,7 +475,7 @@ const EntryToastPreview = ({ entryId }: { entryId: string }) => {
}
const EntryModalPreview = ({ entryId }: { entryId: string }) => (
<Paper className="!p-0">
<Paper className="p-0 !pt-16 empty:hidden">
<EntryContent
className="h-auto [&_#entry-action-header-bar]:!bg-transparent"
entryId={entryId}
@ -503,7 +521,7 @@ const EntryMoreActions: FC<{ entryId: string }> = ({ entryId }) => {
<i className="i-mgc-more-1-cute-re" />
</DropdownMenuTrigger>
<RootPortal>
<DropdownMenuContent>
<DropdownMenuContent alignOffset={20} sideOffset={30}>
{availableActions.map((config) =>
config instanceof MenuItemText ? (
<CommandDropdownMenuItem

View File

@ -57,8 +57,9 @@ const SummaryDetailContent = () => {
const ctxValue = React.useContext(EntryContentPlaceholderContext)
const openedSummary = useAtomValue(ctxValue.openedSummary)
const setOpenedSummary = useSetAtom(ctxValue.openedSummary)
return (
<div className="relative">
<>
<div className="fade-in-0 absolute right-4 top-4 duration-200">
<PeekModalBaseButton
icon={<i className="i-mgc-close-cute-re" />}
@ -67,27 +68,35 @@ const SummaryDetailContent = () => {
/>
</div>
<CollapseGroup
defaultOpenId={`${openedSummary}`}
onOpenChange={(state) => {
const isAllClosed = Object.values(state).every((v) => !v)
if (isAllClosed) {
setOpenedSummary(null)
}
}}
>
<CollapseGroup defaultOpenId={`${openedSummary}`}>
<DailyItem
isOpened={openedSummary === DayOf.Today}
onClick={() => {
if (openedSummary === DayOf.Today) {
setOpenedSummary(null)
} else {
setOpenedSummary(DayOf.Today)
}
}}
day={DayOf.Today}
view={view as DailyView}
className={cn(openedSummary === DayOf.Today && "grow", "pt-6")}
/>
<DailyItem
isOpened={openedSummary === DayOf.Yesterday}
onClick={() => {
if (openedSummary === DayOf.Yesterday) {
setOpenedSummary(null)
} else {
setOpenedSummary(DayOf.Yesterday)
}
}}
day={DayOf.Yesterday}
view={view as DailyView}
className={cn(openedSummary === DayOf.Yesterday && "grow", "pt-6")}
/>
</CollapseGroup>
</div>
</>
)
}

View File

@ -14,20 +14,18 @@ export function ArticleItem({ entryId, entryPreview, translation }: UniversalIte
export function ArticleItemStateLess({ entry, feed }: EntryItemStatelessProps) {
return (
<div className="relative select-none rounded-md text-zinc-700 transition-colors dark:text-neutral-400">
<div className="bg-theme-background text-text relative select-none rounded-md transition-colors">
<div className="relative">
<div className="group relative flex py-4 pl-3 pr-2">
<FeedIcon className="mr-2 size-5 rounded-sm" feed={feed} fallback />
<div className="-mt-0.5 line-clamp-4 flex-1 text-sm leading-tight">
<div className="flex gap-1 text-[10px] font-bold text-zinc-400 dark:text-neutral-500">
<div className="text-text-secondary flex gap-1 text-[10px] font-bold">
<FeedTitle feed={feed} />
<span>·</span>
<span>{!!entry.publishedAt && <RelativeTime date={entry.publishedAt} />}</span>
</div>
<div className="relative my-1 break-words font-medium">{entry.title}</div>
<div className="mt-1.5 text-[13px] text-zinc-400 dark:text-neutral-500">
{entry.description}
</div>
<div className="text-text-secondary mt-1.5 text-[13px]">{entry.description}</div>
</div>
{entry.media?.[0] ? (
<Media
@ -56,12 +54,12 @@ export function ArticleItemStateLess({ entry, feed }: EntryItemStatelessProps) {
}
export const ArticleItemSkeleton = (
<div className="relative h-[120px] rounded-md text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative h-[120px] rounded-md">
<div className="relative">
<div className="group relative flex py-4 pl-3 pr-2">
<Skeleton className="mr-2 size-5 rounded-sm" />
<div className="-mt-0.5 flex-1 text-sm leading-tight">
<div className="flex gap-1 text-[10px] font-bold text-zinc-400 dark:text-neutral-500">
<div className="text-material-opaque flex gap-1 text-[10px] font-bold">
<Skeleton className="h-3 w-24" />
<span>·</span>
<Skeleton className="h-3 w-12 shrink-0" />
@ -70,7 +68,7 @@ export const ArticleItemSkeleton = (
<Skeleton className="h-3.5 w-full" />
<Skeleton className="mt-1 h-3.5 w-3/4" />
</div>
<div className="mt-1.5 text-[13px] text-zinc-400 dark:text-neutral-500">
<div className="text-material-opaque mt-1.5 text-[13px]">
<Skeleton className="h-3 w-full" />
<Skeleton className="mt-1 h-3 w-4/5" />
</div>

View File

@ -13,11 +13,11 @@ export function AudioItem({ entryId, entryPreview, translation }: UniversalItemP
export function AudioItemStateLess({ entry, feed }: EntryItemStatelessProps) {
return (
<div className="bg-theme-background relative mx-auto w-full max-w-lg select-none rounded-md text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative mx-auto w-full max-w-lg select-none rounded-md">
<div className="relative">
<div className="group relative flex py-4 pl-3 pr-2">
<div className="-mt-0.5 line-clamp-4 flex-1 text-sm leading-tight">
<div className="flex gap-1 text-[10px] font-bold text-zinc-400 dark:text-neutral-500">
<div className="text-material-opaque flex gap-1 text-[10px] font-bold">
<FeedTitle feed={feed} />
<span>·</span>
<span>{!!entry.publishedAt && <RelativeTime date={entry.publishedAt} />}</span>
@ -55,11 +55,11 @@ export function AudioItemStateLess({ entry, feed }: EntryItemStatelessProps) {
}
export const AudioItemSkeleton = (
<div className="bg-theme-background relative mx-auto w-full select-none rounded-md text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative mx-auto w-full select-none rounded-md">
<div className="relative">
<div className="group relative flex py-4 pl-3 pr-2">
<div className="-mt-0.5 line-clamp-4 flex-1 text-sm leading-tight">
<div className="flex gap-1 text-[10px] font-bold text-zinc-400 dark:text-neutral-500">
<div className="text-material-opaque flex gap-1 text-[10px] font-bold">
<Skeleton className="h-3 w-20" />
<span>·</span>
<Skeleton className="h-3 w-10" />

View File

@ -18,7 +18,7 @@ export function NotificationItemStateLess({ entry, feed }: EntryItemStatelessPro
<FeedIcon feed={feed} fallback />
<div className="-mt-0.5 line-clamp-4 flex-1 text-sm leading-tight">
<div className="flex gap-1 text-[10px] font-bold text-zinc-400 dark:text-neutral-500">
<div className="text-text-secondary flex gap-1 text-[10px] font-bold">
<FeedTitle feed={feed} />
<span>·</span>
<span>{!!entry.publishedAt && <RelativeTime date={entry.publishedAt} />}</span>
@ -35,7 +35,7 @@ export const NotificationItemSkeleton = (
<div className="group relative flex py-4 pl-3 pr-2">
<Skeleton className="mr-2 size-5 shrink-0 overflow-hidden rounded-sm" />
<div className="-mt-0.5 line-clamp-4 flex-1 text-sm leading-tight">
<div className="flex gap-1 text-[10px] font-bold text-zinc-400 dark:text-neutral-500">
<div className="text-material-opaque flex gap-1 text-[10px] font-bold">
<Skeleton className="h-3 w-32 truncate" />
<span>·</span>
<Skeleton className="h-3 w-12 shrink-0" />

View File

@ -10,7 +10,7 @@ import type { EntryItemStatelessProps } from "../types"
export function PictureItemStateLess({ entry, feed }: EntryItemStatelessProps) {
return (
<div className="bg-theme-background relative mx-auto max-w-md select-none rounded-md text-zinc-700 transition-colors dark:text-neutral-400">
<div className="bg-theme-background text-text relative mx-auto max-w-md select-none rounded-md transition-colors">
<div className="relative">
<div className="p-1.5">
<div className="relative flex gap-2 overflow-x-auto">
@ -49,7 +49,7 @@ export function PictureItemStateLess({ entry, feed }: EntryItemStatelessProps) {
<div className="mt-1 flex items-center gap-1 truncate text-[13px]">
<FeedIcon feed={feed} fallback className="size-4" />
<FeedTitle feed={feed} />
<span className="text-zinc-500">·</span>
<span className="text-material-opaque">·</span>
{!!entry.publishedAt && <RelativeTime date={entry.publishedAt} />}
</div>
</div>

View File

@ -178,7 +178,7 @@ MasonryItemFixedDimensionWrapper.whyDidYouRender = {
}
export const PictureItemSkeleton = (
<div className="bg-theme-background relative max-w-md rounded-md text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative max-w-md rounded-md">
<div className="relative">
<div className="p-1.5">
<div className="relative flex gap-2 overflow-x-auto">
@ -193,7 +193,7 @@ export const PictureItemSkeleton = (
<div className="mt-1 flex items-center gap-1 truncate text-[13px]">
<Skeleton className="mr-0.5 size-4" />
<Skeleton className="h-3 w-1/2" />
<span className="text-zinc-500">·</span>
<span className="text-material-opaque">·</span>
<Skeleton className="h-3 w-12" />
</div>
</div>

View File

@ -199,7 +199,7 @@ export function SocialMediaItemStateLess({ entry, feed }: EntryItemStatelessProp
}
export const SocialMediaItemSkeleton = (
<div className="bg-theme-background relative m-auto w-[645px] rounded-md text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative m-auto w-[645px] rounded-md">
<div className="relative">
<div className="group relative flex px-8 py-6">
<Skeleton className="mr-2 size-9" />
@ -207,7 +207,7 @@ export const SocialMediaItemSkeleton = (
<div className="-mt-0.5 line-clamp-5 flex-1 text-sm">
<div className="flex w-[calc(100%-10rem)] space-x-1">
<Skeleton className="h-4 w-16" />
<span className="text-zinc-500">·</span>
<span className="text-material-opaque">·</span>
<Skeleton className="h-4 w-12" />
</div>
<div className="relative mt-0.5 text-sm">

View File

@ -190,7 +190,7 @@ const PreviewVideoModalContent: ModalContentComponent<{
export function VideoItemStateLess({ entry, feed }: EntryItemStatelessProps) {
return (
<div className="bg-theme-background relative mx-auto w-full max-w-lg rounded-md text-zinc-700 transition-colors dark:text-neutral-400">
<div className="bg-theme-background text-text relative mx-auto w-full max-w-lg rounded-md transition-colors">
<div className="relative">
<div className="p-1.5">
<div className="w-full">
@ -221,10 +221,10 @@ export function VideoItemStateLess({ entry, feed }: EntryItemStatelessProps) {
<div className="relative mb-1 mt-1.5 truncate font-medium leading-none">
{entry.title}
</div>
<div className="mt-1 flex items-center gap-1 truncate text-[13px]">
<div className="text-text-secondary mt-1 flex items-center gap-1 truncate text-[13px]">
<FeedIcon feed={feed} fallback className="size-4" />
<FeedTitle feed={feed} />
<span className="text-zinc-500">·</span>
<span className="text-material-opaque">·</span>
{!!entry.publishedAt && <RelativeTime date={entry.publishedAt} />}
</div>
</div>
@ -235,7 +235,7 @@ export function VideoItemStateLess({ entry, feed }: EntryItemStatelessProps) {
}
export const VideoItemSkeleton = (
<div className="bg-theme-background relative mx-auto w-full max-w-lg rounded-md text-zinc-700 transition-colors dark:text-neutral-400">
<div className="relative mx-auto w-full max-w-lg rounded-md">
<div className="relative">
<div className="p-1.5">
<div className="w-full">
@ -250,7 +250,7 @@ export const VideoItemSkeleton = (
<div className="mt-1 flex items-center gap-1 truncate text-[13px]">
<Skeleton className="mr-0.5 size-4" />
<Skeleton className="h-3 w-1/2" />
<span className="text-zinc-500">·</span>
<span className="text-material-opaque">·</span>
<Skeleton className="h-3 w-12" />
</div>
</div>

View File

@ -139,7 +139,6 @@ export const EntryItemWrapper: FC<
<div
className={cn(
"hover:bg-theme-item-hover relative duration-200",
asRead ? "text-zinc-700 dark:text-neutral-400" : "text-zinc-900 dark:text-neutral-300",
views[view as FeedViewType]?.wideMode ? "rounded-md" : "px-2",
(isActive || isContextMenuOpen) && "!bg-theme-item-active",
itemClassName,

View File

@ -280,7 +280,7 @@ function AudioCover({
>
<button
type="button"
className="center bg-theme-background hover:bg-accent size-10 rounded-full opacity-95 hover:text-white hover:opacity-100"
className="center bg-material-opaque hover:bg-accent size-10 rounded-full opacity-95 hover:text-white hover:opacity-100"
>
<i
className={cn("size-6", {
@ -296,17 +296,17 @@ function AudioCover({
<div className="absolute bottom-0 w-full overflow-hidden rounded-b-sm text-center">
<div
className={cn(
"absolute left-0 top-0 size-full bg-white/50 opacity-0 duration-200 group-hover:opacity-100 dark:bg-neutral-900/70",
"bg-material-ultra-thick absolute left-0 top-0 size-full opacity-0 duration-200 group-hover:opacity-100",
isMobile && "opacity-100",
)}
/>
<div
className={cn(
"text-[13px] opacity-0 backdrop-blur-none duration-200 group-hover:opacity-100 group-hover:backdrop-blur-sm",
isMobile && "opacity-100 backdrop-blur-sm",
"group-hover:backdrop-blur-background text-[13px] opacity-0 backdrop-blur-none duration-200 group-hover:opacity-100",
isMobile && "backdrop-blur-background opacity-100",
)}
>
{formatEstimatedMins(estimatedMins)}
{formatEstimatedMins(10)}
</div>
</div>
)}

View File

@ -111,9 +111,9 @@ export function AISummary({ entryId }: { entryId: string }) {
>
{summary.isLoading ? (
<div className="space-y-2">
<div className="h-3 w-full rounded-lg bg-neutral-200/30 dark:bg-neutral-700/30" />
<div className="h-3 w-[92%] rounded-lg bg-neutral-200/30 dark:bg-neutral-700/30" />
<div className="h-3 w-[85%] rounded-lg bg-neutral-200/30 dark:bg-neutral-700/30" />
<div className="bg-material-ultra-thick h-3 w-full rounded-lg" />
<div className="bg-material-ultra-thick h-3 w-[92%] rounded-lg" />
<div className="bg-material-ultra-thick h-3 w-[85%] rounded-lg" />
</div>
) : (
<div className="animate-in fade-in-0 duration-500">{summary.data}</div>

View File

@ -11,11 +11,11 @@ export const EntryPlaceholderLogo = () => {
data-hide-in-print
onContextMenu={stopPropagation}
className={
"flex w-full min-w-0 flex-col items-center justify-center gap-1 px-12 pb-6 text-center text-lg font-medium text-zinc-400 duration-500"
"text-text-secondary flex w-full min-w-0 flex-col items-center justify-center gap-1 px-12 pb-6 text-center text-lg font-medium duration-500"
}
>
<Logo className="size-16 opacity-40 grayscale" />
<div className="line-clamp-3 w-[60ch] max-w-full">{title}</div>
<Logo className="[&_rect]:fill-text-secondary size-16 opacity-40" />
<div className="line-clamp-3 w-[60ch] max-w-full opacity-70">{title}</div>
</div>
)
}

View File

@ -84,7 +84,7 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => {
{/* Meta Information with improved layout */}
<div className="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm">
<div className="text-text-secondary [&>div:hover]:multi-['text-gray-700;dark:text-gray-300'] flex flex-wrap items-center gap-4 [&>div]:transition-colors">
<div className="text-text-secondary [&>div:hover]:multi-[text-text] flex flex-wrap items-center gap-4 [&>div]:transition-colors">
<div
className="flex items-center text-xs font-medium"
onClick={() =>

View File

@ -15,7 +15,7 @@ import {
} from "~/atoms/readability"
import { useUISettingKey } from "~/atoms/settings/ui"
import { ShadowDOM } from "~/components/common/ShadowDOM"
import { useInPeekModal } from "~/components/ui/modal/inspire/PeekModal"
import { useInPeekModal } from "~/components/ui/modal/inspire/InPeekModal"
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
import { useAuthQuery } from "~/hooks/common"
import { useFeedSafeUrl } from "~/hooks/common/useFeedSafeUrl"
@ -154,16 +154,7 @@ export const EntryContent: Component<EntryContentProps> = ({
<div className="@container relative flex size-full flex-col overflow-hidden print:size-auto print:overflow-visible">
<EntryTimelineSidebar entryId={entry.entries.id} />
<ScrollArea.ScrollArea
mask={false}
rootClassName={cn(
"h-0 min-w-0 grow overflow-y-auto print:h-auto print:overflow-visible",
className,
)}
scrollbarClassName="mr-[1.5px] print:hidden"
viewportClassName="p-5"
ref={scrollerRef}
>
<EntryScrollArea className={className} scrollerRef={scrollerRef}>
<div
style={stableRenderStyle}
className="animate-in fade-in slide-in-from-bottom-24 f-motion-reduce:fade-in-0 f-motion-reduce:slide-in-from-bottom-0 select-text duration-200 ease-in-out"
@ -238,9 +229,33 @@ export const EntryContent: Component<EntryContentProps> = ({
<SupportCreator entryId={entryId} />
</article>
</div>
</ScrollArea.ScrollArea>
</EntryScrollArea>
<SourceContentPanel src={safeUrl ?? "#"} />
</div>
</>
)
}
const EntryScrollArea: Component<{
scrollerRef: React.RefObject<HTMLDivElement>
}> = ({ children, className, scrollerRef }) => {
const isInPeekModal = useInPeekModal()
if (isInPeekModal) {
return <div className="p-5">{children}</div>
}
return (
<ScrollArea.ScrollArea
mask={false}
rootClassName={cn(
"h-0 min-w-0 grow overflow-y-auto print:h-auto print:overflow-visible",
className,
)}
scrollbarClassName="mr-[1.5px] print:hidden"
viewportClassName="p-5"
ref={scrollerRef}
>
{children}
</ScrollArea.ScrollArea>
)
}

View File

@ -45,14 +45,6 @@ export interface EntryContentClassNames {
header?: string
}
export const SummaryLoadingSkeleton = (
<div className="space-y-2">
<span className="block h-3 w-full animate-pulse rounded-xl bg-zinc-200 dark:bg-neutral-800" />
<span className="block h-3 w-full animate-pulse rounded-xl bg-zinc-200 dark:bg-neutral-800" />
<span className="block h-3 w-full animate-pulse rounded-xl bg-zinc-200 dark:bg-neutral-800" />
</div>
)
export const TitleMetaHandler: Component<{
entryId: string
}> = ({ entryId }) => {

View File

@ -58,7 +58,7 @@ const ShortcutModalContent = () => {
<div className="text-text-secondary mb-2 pl-4 text-xs font-medium capitalize">
{t(shortcutsType[type])}
</div>
<div className="rounded-md border text-[13px] text-zinc-600 dark:text-zinc-300">
<div className="text-text rounded-md border text-[13px]">
{Object.keys(shortcuts[type]).map((action, index) => (
<div
key={`${type}-${action}`}

View File

@ -414,8 +414,8 @@ const PlaybackRateSelector = () => {
type="button"
className={cn(
"center hover:bg-theme-item-hover rounded-md p-1 font-mono",
playbackRate === rate && "bg-theme-item-hover text-black dark:text-white",
playbackRate !== rate && "text-zinc-500",
playbackRate === rate && "bg-theme-item-hover text-text",
playbackRate !== rate && "text-text-secondary",
)}
onClick={() => AudioPlayer.setPlaybackRate(rate)}
>

View File

@ -20,7 +20,7 @@ export const TimeStamp = (props: { time: string }) => {
return (
<span
className="text-accent dark:text-theme-accent-500 cursor-pointer tabular-nums"
className="text-accent cursor-pointer tabular-nums"
onClick={() => {
AudioPlayer.mount({
type: "audio",

View File

@ -306,7 +306,7 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego
}}
className="center hover:bg-material-ultra-thick -ml-1 flex size-5 shrink-0 rounded-lg"
>
<i className="i-mgc-close-cute-re text-red-500 dark:text-red-400" />
<i className="i-mgc-close-cute-re text-red" />
</MotionButtonBase>
) : (
<div className="center mr-2 size-[16px]">
@ -435,7 +435,7 @@ const RenameCategoryForm: FC<{
/>
<MotionButtonBase
type="submit"
className="center hover:bg-material-ultra-thick -mr-1 flex size-5 shrink-0 rounded-lg text-green-600 dark:text-green-400"
className="center hover:bg-material-ultra-thick text-green -mr-1 flex size-5 shrink-0 rounded-lg"
>
<i className="i-mgc-check-filled size-3" />
</MotionButtonBase>

View File

@ -178,12 +178,7 @@ const FeedItemImpl = ({ view, feedId, className }: FeedItemProps) => {
}}
{...contextMenuProps}
>
<div
className={cn(
"flex min-w-0 items-center",
isFeed && feed.errorAt && "text-red-900 dark:text-red-500",
)}
>
<div className={cn("flex min-w-0 items-center", isFeed && feed.errorAt && "text-red")}>
<FeedIcon fallback feed={feed} size={16} />
<FeedTitle feed={feed} />
{isFeed && feed.errorAt && (

View File

@ -145,7 +145,7 @@ const SortButton = () => {
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.98, y: 10 }}
transition={{ type: "spring", duration: 0.3 }}
className="border-border bg-theme-background relative z-10 rounded-md border p-3 shadow-md dark:shadow-zinc-500/20"
className="border-border bg-theme-background shadow-context-menu relative z-10 rounded-md border p-3"
>
<HoverCard.Arrow className="fill-border -translate-x-4" />
<section className="w-[170px] text-center">

View File

@ -7,12 +7,7 @@ export const UnreadNumber = ({ unread, className }: { unread: number; className?
if (!unread) return null
return (
<div
className={cn(
"center text-[0.65rem] tabular-nums text-zinc-400 dark:text-neutral-500",
className,
)}
>
<div className={cn("center text-text-tertiary text-[0.65rem] tabular-nums", className)}>
{!showUnreadCount ? <i className="i-mgc-round-cute-fi text-[0.3rem]" /> : unread}
</div>
)

View File

@ -1,46 +0,0 @@
.mask-scroller {
mask:
linear-gradient(to right, white, transparent) 0 50% / 0.8rem 100% no-repeat,
linear-gradient(white, white) 50% 50% / 100% 100% no-repeat,
linear-gradient(to right, transparent, white) 100% 50% / 0.8rem 100% no-repeat;
mask-composite: exclude;
mask-size:
calc((var(--scroll-progress-left) / 100) * 0.8rem) 100%,
100% 100%,
calc((100 - (100 * (var(--scroll-progress-right) / 100))) * 1px) 100%;
}
@supports (animation-timeline: scroll()) {
.mask-scroller {
mask:
linear-gradient(to right, white, transparent) 0 50% / 0.8rem 100% no-repeat,
linear-gradient(white, white) 50% 50% / 100% 100% no-repeat,
linear-gradient(to right, transparent, white) 100% 50% / 1.6rem 100% no-repeat;
mask-composite: exclude;
animation:
mask-left both linear,
mask-right both linear;
animation-timeline: scroll(self);
animation-range:
0 50px,
calc(100% - 50px) 100%;
}
}
@keyframes mask-left {
100% {
mask-size:
0.8rem 100%,
100% 100%,
0.8rem 100%;
}
}
@keyframes mask-right {
100% {
mask-size:
0.8rem 100%,
100% 100%,
0 100%;
}
}

View File

@ -135,7 +135,7 @@ export function FeedColumn({ children, className }: PropsWithChildren<{ classNam
className={cn(
"relative flex h-full flex-col pt-2.5",
!feedColumnShow && ELECTRON_BUILD && "bg-zinc-200 dark:bg-neutral-800",
!feedColumnShow && ELECTRON_BUILD && "bg-material-opaque",
className,
)}
onClick={useCallback(() => navigateBackHome(), [navigateBackHome])}

View File

@ -60,7 +60,7 @@ export const UserAvatar = forwardRef<
}}
{...props}
className={cn(
"flex h-20 items-center justify-center gap-2 px-5 py-2 font-medium text-zinc-600 dark:text-zinc-300",
"text-text-secondary flex h-20 items-center justify-center gap-2 px-5 py-2 font-medium",
className,
)}
>

View File

@ -36,7 +36,7 @@ export const UserAvatar = ({ className }: { className?: string }) => {
return (
<div
className={cn(
"flex h-20 items-center justify-center gap-8 px-10 py-4 text-2xl font-medium text-zinc-600 dark:text-zinc-300",
"text-text-secondary flex h-20 items-center justify-center gap-8 px-10 py-4 text-2xl font-medium",
className,
)}
>

View File

@ -1,12 +1,8 @@
import { Spring } from "@follow/components/constants/spring"
import { cn } from "@follow/utils/utils"
import type { Spring } from "motion/react"
import { m } from "motion/react"
import { useEffect, useRef, useState } from "react"
const softSpringPreset: Spring = {
type: "spring",
}
interface AnimateChangeInHeightProps {
children: React.ReactNode
className?: string
@ -56,7 +52,7 @@ export const AutoResizeHeight: React.FC<AnimateChangeInHeightProps> = ({
transition={
spring
? {
...softSpringPreset,
...Spring.presets.softSpring,
duration,
}
: {

View File

@ -35,7 +35,7 @@ export const AvatarFallback = React.forwardRef<
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"bg-material-medium flex size-full items-center justify-center rounded-full text-xs",
"bg-material-opaque flex size-full items-center justify-center rounded-full text-xs",
className,
)}
{...props}

View File

@ -79,7 +79,7 @@ export const ActionButton = React.forwardRef<
className={cn(
"no-drag-region pointer-events-auto inline-flex items-center justify-center",
active && typeof icon !== "function" && "bg-zinc-500/15 hover:bg-zinc-500/20",
"hover:bg-material-ultra-thick data-[state=open]:bg-material-ultra-thick rounded-md duration-200",
"hover:bg-theme-item-hover data-[state=open]:bg-theme-item-active rounded-md duration-200",
"disabled:cursor-not-allowed disabled:opacity-50",
clickableDisabled && "cursor-not-allowed opacity-50",
actionButtonStyleVariant.size[size],

View File

@ -57,7 +57,8 @@ export const MagneticHoverEffect = <T extends React.ElementType = "div">({
className={cn(
"relative",
"inline-block transition-all duration-200 ease-out",
"before:backdrop-blur-background before:absolute before:-inset-x-2 before:inset-y-0 before:z-[-1] before:scale-0 before:rounded-xl before:bg-black/[0.03] 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 dark:before:bg-white/[0.10]",
"hover: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,
)}
>

View File

@ -89,7 +89,7 @@ export function FeedIcon({
}
className={cn(
"flex shrink-0 items-center justify-center rounded-sm",
"bg-[var(--fo-light-background)] text-white dark:bg-[var(--fo-dark-background)]",
"bg-material-ultra-thick text-white",
"mr-2",
className,
)}

View File

@ -1,10 +1,5 @@
import { cn } from "@follow/utils/utils"
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-gray-200 dark:bg-neutral-800", className)}
{...props}
/>
)
return <div className={cn("bg-material-opaque animate-pulse rounded-md", className)} {...props} />
}