ProComponents, templates & AI tooling
HeroUI
27.7k

Snippet

Snippet 从 HeroUI v2 到 v3 的迁移指南。

Snippet 组件在 HeroUI v3 中已移除。请使用原生 HTML 元素配合 Tailwind CSS,并通过 Clipboard API 自行实现复制能力。

主要变化

1. 组件移除

v2: 来自 @heroui/react<Snippet> 组件
v3: 原生 HTML 元素(<pre><code>)+ 手动实现复制逻辑

2. 功能对照

v2 的 Snippet 具备以下能力,需要在 v3 中分别替代:

v2 功能v3 替代说明
复制按钮Button + Clipboard API使用 navigator.clipboard.writeText()
复制提示Tooltip 组件使用 v3 的 Tooltip
符号前缀手动渲染将符号作为文本内容输出
多行支持数组映射对字符串数组做 map
变体(flatsolidborderedshadowTailwind 类使用背景 / 边框等工具类
颜色(defaultprimary 等)Tailwind 类使用颜色相关工具类
尺寸(smmdlgTailwind 字号使用 text-smtext-basetext-lg
圆角Tailwind 圆角使用 rounded-*

结构变化

在 v2 中,Snippet 是带内置复制能力的包装组件:

import { Snippet } from "@heroui/react";

export default function App() {
  return (
    <Snippet symbol="$">
      npm install @heroui/react
    </Snippet>
  );
}

在 v3 中,请使用原生 HTML 元素并手动实现复制:

import { Button, Tooltip } from "@heroui/react";
import { useState } from "react";

export default function App() {
  const [copied, setCopied] = useState(false);

  const handleCopy = async () => {
    await navigator.clipboard.writeText("npm install @heroui/react");
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <div className="flex items-center gap-2 rounded-lg bg-default-100 px-3 py-1.5">
      <pre className="text-sm font-mono">
        <span className="text-default-500">$ </span>
        npm install @heroui/react
      </pre>
      <Tooltip>
        <Button
          isIconOnly
          aria-label="Copy"
          size="sm"
          variant="ghost"
          onPress={handleCopy}
        >
          {copied ? "✓" : "📋"}
        </Button>
        <Tooltip.Content>{copied ? "Copied!" : "Copy to clipboard"}</Tooltip.Content>
      </Tooltip>
    </div>
  );
}

迁移示例

多行 Snippet

<Snippet symbol="$">
  {[
    "npm install @heroui/react",
    "yarn add @heroui/react",
    "pnpm add @heroui/react"
  ]}
</Snippet>
import { Button, Tooltip } from "@heroui/react";
import { useState } from "react";

function MultiLineSnippet() {
  const [copied, setCopied] = useState(false);
  const lines = [
    "npm install @heroui/react",
    "yarn add @heroui/react",
    "pnpm add @heroui/react"
  ];
  const codeString = lines.join("\n");

  const handleCopy = async () => {
    await navigator.clipboard.writeText(codeString);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <div className="flex items-start gap-2 rounded-lg bg-default-100 p-3">
      <div className="flex-1 space-y-1">
        {lines.map((line, index) => (
          <pre key={index} className="text-sm font-mono">
            <span className="text-default-500">$ </span>
            {line}
          </pre>
        ))}
      </div>
      <Tooltip>
        <Button
          isIconOnly
          aria-label="Copy"
          size="sm"
          variant="ghost"
          onPress={handleCopy}
        >
          {copied ? "✓" : "📋"}
        </Button>
        <Tooltip.Content>{copied ? "Copied!" : "Copy to clipboard"}</Tooltip.Content>
      </Tooltip>
    </div>
  );
}

样式选项

{/* With variants */}
<Snippet variant="bordered" color="primary">
  npm install @heroui/react
</Snippet>

{/* Without symbol */}
<Snippet hideSymbol>
  npm install @heroui/react
</Snippet>

{/* Without copy button */}
<Snippet hideCopyButton>
  npm install @heroui/react
</Snippet>
{/* With variants */}
<div className="flex items-center gap-2 rounded-lg border border-accent bg-transparent px-3 py-1.5">
  <pre className="text-sm font-mono text-accent">
    <span className="text-accent/60">$ </span>
    npm install @heroui/react
  </pre>
  {/* Copy button */}
</div>

{/* Without symbol */}
<div className="flex items-center gap-2 rounded-lg bg-default-100 px-3 py-1.5">
  <pre className="text-sm font-mono">
    npm install @heroui/react
  </pre>
  {/* Copy button */}
</div>

{/* Without copy button */}
<div className="rounded-lg bg-default-100 px-3 py-1.5">
  <pre className="text-sm font-mono">
    <span className="text-default-500">$ </span>
    npm install @heroui/react
  </pre>
</div>

创建可复用的 Snippet 组件(推荐)

Snippet 类需求很常见,下面是一个完整的可复用组件示例:

import { Snippet } from "@heroui/react";

<Snippet
  symbol="$"
  variant="bordered"
  color="primary"
  size="md"
>
  npm install @heroui/react
</Snippet>
import { Button, Tooltip } from "@heroui/react";
import { useState, ReactNode } from "react";
import { cn } from "@/lib/utils"; // or your cn utility

interface SnippetProps {
  children: string | string[];
  symbol?: string | ReactNode;
  variant?: "flat" | "solid" | "bordered" | "shadow";
  color?: "default" | "primary" | "secondary" | "success" | "warning" | "danger";
  size?: "sm" | "md" | "lg";
  radius?: "none" | "sm" | "md" | "lg" | "full";
  hideSymbol?: boolean;
  hideCopyButton?: boolean;
  disableCopy?: boolean;
  disableTooltip?: boolean;
  className?: string;
  codeString?: string;
  onCopy?: (value: string) => void;
}

const variantClasses = {
  flat: "bg-default-100",
  solid: "bg-default-200",
  bordered: "border border-default-200 bg-transparent",
  shadow: "bg-default-100 shadow-sm",
};

const colorClasses = {
  default: "text-default-foreground",
  primary: "text-accent",
  secondary: "text-default-600",
  success: "text-success",
  warning: "text-warning",
  danger: "text-danger",
};

const sizeClasses = {
  sm: "px-1.5 py-0.5 text-xs",
  md: "px-3 py-1.5 text-sm",
  lg: "px-4 py-2 text-base",
};

const radiusClasses = {
  none: "rounded-none",
  sm: "rounded-sm",
  md: "rounded-md",
  lg: "rounded-lg",
  full: "rounded-full",
};

export function Snippet({
  children,
  symbol = "$",
  variant = "flat",
  color = "default",
  size = "md",
  radius = "md",
  hideSymbol = false,
  hideCopyButton = false,
  disableCopy = false,
  disableTooltip = false,
  className,
  codeString,
  onCopy,
}: SnippetProps) {
  const [copied, setCopied] = useState(false);
  const isMultiLine = Array.isArray(children);
  const lines = isMultiLine ? children : [children];
  const textToCopy = codeString || (isMultiLine ? lines.join("\n") : String(children));

  const handleCopy = async () => {
    if (disableCopy) return;

    try {
      await navigator.clipboard.writeText(textToCopy);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
      onCopy?.(textToCopy);
    } catch (error) {
      console.error("Failed to copy:", error);
    }
  };

  const symbolElement = hideSymbol ? null : (
    <span className={cn("text-default-500", colorClasses[color], "opacity-60")}>
      {symbol}{typeof symbol === "string" ? " " : ""}
    </span>
  );

  const copyButton = hideCopyButton ? null : (
    <Tooltip isDisabled={disableTooltip || disableCopy}>
      <Button
        isIconOnly
        aria-label="Copy"
        size="sm"
        variant="ghost"
        onPress={handleCopy}
        isDisabled={disableCopy}
        className="shrink-0"
      >
        {copied ? (
          <span className="text-success">✓</span>
        ) : (
          <span>📋</span>
        )}
      </Button>
      <Tooltip.Content>{copied ? "Copied!" : "Copy to clipboard"}</Tooltip.Content>
    </Tooltip>
  );

  return (
    <div
      className={cn(
        "flex items-start gap-2 font-mono",
        variantClasses[variant],
        sizeClasses[size],
        radiusClasses[radius],
        className
      )}
    >
      <div className="flex-1 min-w-0">
        {isMultiLine ? (
          <div className="space-y-1">
            {lines.map((line, index) => (
              <pre key={index} className={cn("m-0", colorClasses[color])}>
                {symbolElement}
                {line}
              </pre>
            ))}
          </div>
        ) : (
          <pre className={cn("m-0", colorClasses[color])}>
            {symbolElement}
            {children}
          </pre>
        )}
      </div>
      {copyButton}
    </div>
  );
}

// Usage
<Snippet
  symbol="$"
  variant="bordered"
  color="primary"
  size="md"
>
  npm install @heroui/react
</Snippet>

总结

  1. 组件已移除:v3 不再提供 Snippet 组件。
  2. 导入调整:移除 import { Snippet } from "@heroui/react"
  3. 使用原生元素:改用原生 <pre><code> 等元素。
  4. 手动复制:使用 Clipboard API 实现复制。
  5. 样式:直接用 Tailwind CSS 类表达变体、颜色、尺寸。
  6. Tooltip:复制按钮的提示请使用 v3 Tooltip。
  7. Button:复制按钮请使用 v3 Button。

迁移步骤

  1. 移除导入:从 @heroui/react 的导入中删除 Snippet
  2. 替换组件:将所有 <Snippet> 替换为原生 HTML 结构。
  3. 实现复制:使用 navigator.clipboard.writeText() 等方法。
  4. 添加复制按钮:使用 v3 的 Button 与 Tooltip。
  5. 应用样式:用 Tailwind CSS 表达变体、颜色、尺寸。
  6. 处理多行:多行场景对数组做映射渲染。
  7. (可选) 在应用中封装可复用的 Snippet 组件。

Clipboard API 说明

Clipboard API 需要:

  • HTTPS(本地开发可使用 localhost)
  • 用户手势触发(不能自动调用)
  • 现代浏览器支持

如需兼容旧浏览器,可采用回退方案:

const handleCopy = async (text: string) => {
  try {
    await navigator.clipboard.writeText(text);
  } catch (error) {
    // 旧版浏览器回退
    const textArea = document.createElement("textarea");
    textArea.value = text;
    textArea.style.position = "fixed";
    textArea.style.opacity = "0";
    document.body.appendChild(textArea);
    textArea.select();
    document.execCommand("copy");
    document.body.removeChild(textArea);
  }
};

本页目录