Hover Card
指针停在链接上时弹出的预览卡,比如字幕组或番剧的简介。只是预览,不是唯一入口。
本集由 @喵萌奶茶屋 翻译、校对、压制。
hover-card-demo.tsx
"use client"
import { CalendarDaysIcon } from "lucide-react"
import { StatusBadge } from "@/components/ui/badge"
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
export default function HoverCardDemo() {
return (
<p className="text-[14px] text-anibt-ink2">
本集由{" "}
<HoverCard>
<HoverCardTrigger asChild>
<a href="#mmnc" className="font-semibold text-accent-foreground underline-offset-4 hover:underline">
@喵萌奶茶屋
</a>
</HoverCardTrigger>
<HoverCardContent className="w-72" align="start">
<div className="flex gap-3">
<span className="anibt-avatar anibt-rim size-11 text-[18px]" aria-hidden="true">
喵
</span>
<div className="flex min-w-0 flex-1 flex-col gap-1">
<div className="flex items-center gap-2">
<span className="anibt-ell text-[14px] font-bold text-foreground">喵萌奶茶屋</span>
<StatusBadge tone="on">已认证</StatusBadge>
</div>
<p className="text-[12px] leading-normal text-muted-foreground">
简繁内封、1080p 为主的老牌字幕组,长期搬运季番。
</p>
</div>
</div>
<div className="anibt-stitch-over mt-3 flex items-center gap-4 pt-3 text-[12px] text-muted-foreground">
<span>
发布 <b className="anibt-num font-semibold text-foreground">2,431</b>
</span>
<span>
成员 <b className="anibt-num font-semibold text-foreground">18</b>
</span>
<span className="ml-auto inline-flex items-center gap-1">
<CalendarDaysIcon className="size-3.5" aria-hidden="true" />
<span className="anibt-num">2014</span> 年加入
</span>
</div>
</HoverCardContent>
</HoverCard>{" "}
翻译、校对、压制。
</p>
)
}安装
npx shadcn@latest add @anibt/hover-card第一次用之前:在 components.json 里登记 @anibt,再单独运行一次 npx shadcn@latest add @anibt/theme(见快速开始)。只有单独安装主题时,CLI 才会把 shadcn 的标准颜色换成白樱 / 夜樱;随组件带进来的主题只补上 AniBT 自己的变量和配方。
手动安装
- 1. 先装主题组件只读
@anibt/theme的 token 和配方,没有主题就没有样子。npx shadcn@latest add @anibt/theme - 2. 安装依赖
npm install radix-ui - 3. 把下面的文件放进项目components/ui/hover-card.tsx
"use client" import * as React from "react" import { HoverCard as HoverCardPrimitive } from "radix-ui" import { cn } from "@/lib/utils" /** * HoverCard — AniBT 「贴纸手账」: a preview card for a sighted pointer, opened * by resting on a link (a fansub group's name, a show's title). * * The floating tier (`anibt-pop`), 256 wide with 16px of padding. It is a * preview, never the only way to reach what it shows: the trigger is a real * link, and touch and keyboard readers follow it instead. * * Export names and props are shadcn's, so this file can replace * `components/ui/hover-card.tsx`. */ type RenderCompat = { /** Base UI's `asChild`: render this element instead, with the part's props. */ render?: React.ReactElement } /** Maps Base UI's `render` onto Radix's `asChild`. */ function renderAsChild<P extends { asChild?: boolean; children?: React.ReactNode }>({ render, ...props }: P & RenderCompat): P { if (!render) return props as P const children = props.children === undefined ? render : React.cloneElement(render, undefined, props.children) return { ...props, asChild: true, children } as P } function HoverCard({ openDelay = 300, closeDelay = 150, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Root>) { return <HoverCardPrimitive.Root data-slot="hover-card" openDelay={openDelay} closeDelay={closeDelay} {...props} /> } function HoverCardTrigger(props: React.ComponentProps<typeof HoverCardPrimitive.Trigger> & RenderCompat) { return <HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...renderAsChild(props)} /> } function HoverCardContent({ className, align = "center", sideOffset = 6, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Content>) { return ( <HoverCardPrimitive.Portal data-slot="hover-card-portal"> <HoverCardPrimitive.Content data-slot="hover-card-content" align={align} sideOffset={sideOffset} className={cn( "anibt-pop z-50 w-64 max-w-[calc(100vw-1rem)] origin-(--radix-hover-card-content-transform-origin) p-4 text-[13px] duration-150 outline-hidden", "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1", className )} {...props} /> </HoverCardPrimitive.Portal> ) } export { HoverCard, HoverCardTrigger, HoverCardContent }
用法
components/group-link.tsx
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
<HoverCard>
<HoverCardTrigger asChild>
<a href="/group/mmnc">@喵萌奶茶屋</a>
</HoverCardTrigger>
<HoverCardContent>{/* 头像、简介、数字 */}</HoverCardContent>
</HoverCard>什么时候用
- 触发器必须是真的链接:触屏和键盘用户直接点进去看完整页面,悬停卡只给有指针的人省一次跳转。
- 停 300ms 打开、离开 150ms 关闭,划过页面时不会闪。用
openDelay/closeDelay调整。 - 卡片是浮层(
anibt-pop),宽 256、内边 16。例子里的头像是anibt-avatar anibt-rim的模切贴纸方块,数字用圆体,底部用缝线隔开。
属性
HoverCard
openDelaynumber默认 300停留多久打开(毫秒)
closeDelaynumber默认 150离开多久关闭(毫秒)
HoverCardContent
align"start" | "center" | "end"默认 "center"与触发器的对齐方式
sideOffsetnumber默认 6离触发器的距离
