AniBTUI
✦ 组件@anibt/tooltipregistry JSON

Tooltip

深色提示气泡(夜樱里是浅色),带同色的小尾巴,给只有图标的按钮报名字。

安装

npx shadcn@latest add @anibt/tooltip

第一次用之前:在 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/tooltip.tsx
    "use client"
    
    import * as React from "react"
    import { Tooltip as TooltipPrimitive } from "radix-ui"
    
    import { cn } from "@/lib/utils"
    
    /**
     * Tooltip — the AniBT 「贴纸手账」 hint bubble.
     *
     * A's tip is the one dark thing on a light page (and the one light thing in
     * 夜樱): `--anibt-tip` under `--anibt-tip-ink`, 12/16, radius 6, the sticker
     * shadow, and a tail in its own colour pointing at the control. It names an
     * icon-only control or says one short fact; anything longer belongs in a
     * popover.
     *
     * Radix underneath, so hover, keyboard focus, Escape and the grace area all
     * behave. Compatibility with shadcn:
     * - `Tooltip` works without a `TooltipProvider` (it brings its own); put one
     *   provider around a list so its tooltips share one delay.
     * - `TooltipTrigger` takes Base UI's `render={<button …/>}` as well as
     *   `asChild`, and `TooltipProvider` / `Tooltip` take Base UI's `delay` as well
     *   as Radix's `delayDuration`, so the `base-*` sidebar keeps working.
     */
    
    const TooltipScope = React.createContext(false)
    
    type DelayProps = {
      /** Radix: ms before the tip opens. Default 0. */
      delayDuration?: number
      /** Base UI's name for `delayDuration`. */
      delay?: number
    }
    
    function TooltipProvider({
      delayDuration,
      delay,
      children,
      ...props
    }: Omit<React.ComponentProps<typeof TooltipPrimitive.Provider>, "delayDuration"> & DelayProps) {
      return (
        <TooltipScope.Provider value={true}>
          <TooltipPrimitive.Provider
            data-slot="tooltip-provider"
            delayDuration={delayDuration ?? delay ?? 0}
            {...props}
          >
            {children}
          </TooltipPrimitive.Provider>
        </TooltipScope.Provider>
      )
    }
    
    function Tooltip({
      delayDuration,
      delay,
      ...props
    }: Omit<React.ComponentProps<typeof TooltipPrimitive.Root>, "delayDuration"> & DelayProps) {
      const shared = React.useContext(TooltipScope)
      const root = (
        <TooltipPrimitive.Root data-slot="tooltip" delayDuration={delayDuration ?? delay} {...props} />
      )
      // A tooltip inside a provider shares its delay; a lone one brings its own.
      return shared ? root : <TooltipProvider>{root}</TooltipProvider>
    }
    
    type TooltipTriggerProps = React.ComponentProps<typeof TooltipPrimitive.Trigger> & {
      /**
       * Base UI's form of `asChild`: the element to render as the trigger. It
       * takes the trigger's props; `children` go inside it.
       */
      render?: React.ReactElement
    }
    
    function TooltipTrigger({ render, asChild, children, ...props }: TooltipTriggerProps) {
      if (render) {
        const child = children !== undefined ? React.cloneElement(render, undefined, children) : render
        return (
          <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...{ asChild: true }} {...props}>
            {child}
          </TooltipPrimitive.Trigger>
        )
      }
      return (
        <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...{ asChild: asChild }} {...props}>
          {children}
        </TooltipPrimitive.Trigger>
      )
    }
    
    function TooltipContent({
      className,
      sideOffset = 6,
      children,
      ...props
    }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
      return (
        <TooltipPrimitive.Portal>
          <TooltipPrimitive.Content
            data-slot="tooltip-content"
            sideOffset={sideOffset}
            className={cn(
              "anibt-tip z-50 inline-flex w-fit max-w-[min(20rem,calc(100vw-2rem))] origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 text-balance",
              "animate-in fade-in-0 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
              // A key cap inside the tip (`⌘K`) takes the tip's ink, not a field's.
              "**:data-[slot=kbd]:h-4 **:data-[slot=kbd]:min-w-4 **:data-[slot=kbd]:bg-current/15 **:data-[slot=kbd]:text-current **:data-[slot=kbd]:shadow-none",
              className
            )}
            {...props}
          >
            {children}
            {/* A's tail: the tip's own colour, pointing at the control. */}
            <TooltipPrimitive.Arrow className="fill-anibt-tip" width={10} height={5} />
          </TooltipPrimitive.Content>
        </TooltipPrimitive.Portal>
      )
    }
    
    export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
    export type { TooltipTriggerProps }

用法

用法
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"

<Tooltip>
  <TooltipTrigger asChild>
    <Button variant="secondary" size="icon" aria-label="复制磁力链接">
      <MagnetIcon />
    </Button>
  </TooltipTrigger>
  <TooltipContent>复制磁力链接</TooltipContent>
</Tooltip>

提示只说一句短话:给图标按钮报名字,或者补一个事实。更长的内容放进弹层。按钮自己也要有 aria-label——提示不是唯一的名字来源。

Tooltip 不需要外层的 TooltipProvider 也能用(它会自己带一个);一整个列表的提示共用一个 provider,延迟才一致。

方向与键帽

side、align、sideOffset 都是 Radix 的。提示里的键帽(data-slot="kbd")会自动换成气泡的颜色。

与 shadcn 的兼容

  • TooltipTrigger 同时接受 Radix 的 asChild 和 Base UI 的 render={<button … />},base-* 风格的 sidebar 不用改。
  • TooltipProvider / Tooltip 同时接受 delayDuration(Radix)和 delay(Base UI)。

属性

TooltipContent
side"top" | "right" | "bottom" | "left"默认 "top"气泡在哪一边
sideOffsetnumber默认 6离控件的距离
align"start" | "center" | "end"默认 "center"沿边对齐
TooltipTrigger
asChildboolean默认 false把触发器交给唯一的子元素
renderReactElement默认 —Base UI 的写法:渲染成这个元素
TooltipProvider · Tooltip
delayDurationnumber默认 0多久后打开(毫秒)
delaynumber默认 —Base UI 对 delayDuration 的叫法