AniBTUI
✦ 组件@anibt/hover-cardregistry JSON

Hover Card

指针停在链接上时弹出的预览卡,比如字幕组或番剧的简介。只是预览,不是唯一入口。

本集由 @喵萌奶茶屋 翻译、校对、压制。

安装

npx shadcn@latest add @anibt/hover-card

第一次用之前:在 components.json 里登记 @anibt,再单独运行一次 npx shadcn@latest add @anibt/theme(见快速开始)。只有单独安装主题时,CLI 才会把 shadcn 的标准颜色换成白樱 / 夜樱;随组件带进来的主题只补上 AniBT 自己的变量和配方。

手动安装
  1. 1. 先装主题组件只读 @anibt/theme 的 token 和配方,没有主题就没有样子。
    npx shadcn@latest add @anibt/theme
  2. 2. 安装依赖
    npm install radix-ui
  3. 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离触发器的距离