unnamed-ui
按钮

Block Button

Button component with multiple variants, colors, and states

Button 组件

配置按钮属性,预览即时更新

按钮配置

预览

"use client";

import React, { useState } from "react";
import { Button } from "@/components/composed/block-button/block-button";
import {
  Send,
  Download,
  ArrowRight,
  ArrowLeft,
  Save,
  Trash2,
  Plus,
  RefreshCw,
  Settings,
  Eye,
  EyeOff,
  Home,
  User,
  Star,
  Heart,
  Mail,
  Search,
  Bell,
  PlusCircle,
  MinusCircle,
  Check,
  X,
} from "lucide-react";

// 可选图标列表
const ICONS = {
  none: null,
  Send,
  Download,
  ArrowRight,
  ArrowLeft,
  Save,
  Trash2,
  Plus,
  RefreshCw,
  Settings,
  Eye,
  EyeOff,
  Home,
  User,
  Star,
  Heart,
  Mail,
  Search,
  Bell,
  PlusCircle,
  MinusCircle,
  Check,
  X,
};

const VARIANTS = [
  { value: "solid", label: "实心" },
  { value: "text", label: "文字" },
  { value: "outline", label: "边框" },
  { value: "link", label: "链接" },
];

const COLORS = [
  { value: "primary", label: "主色" },
  { value: "secondary", label: "次要" },
  { value: "danger", label: "危险" },
];

const SIZES = [
  { value: "sm", label: "小" },
  { value: "md", label: "中" },
  { value: "lg", label: "大" },
];

const ICON_OPTIONS = Object.keys(ICONS).map((key) => ({
  value: key,
  label: key.charAt(0).toUpperCase() + key.slice(1).replace(/([A-Z])/g, " $1"),
}));

export function ButtonDemo() {
  // 按钮配置状态
  const [config, setConfig] = useState({
    variant: "solid",
    color: "primary",
    size: "md",
    disabled: false,
    loading: false,
    block: false,
    icon: "none",
    iconRight: "none",
    text: "按钮",
  });

  const updateConfig = (key: string, value: unknown) => {
    setConfig((prev) => ({ ...prev, [key]: value }));
  };

  const SelectedIcon = ICONS[config.icon as keyof typeof ICONS];
  const SelectedIconRight = ICONS[config.iconRight as keyof typeof ICONS];

  // 生成代码
  const generateCode = () => {
    const props = [];
    if (config.variant !== "solid") props.push(`variant="${config.variant}"`);
    if (config.color !== "primary") props.push(`color="${config.color}"`);
    if (config.size !== "md") props.push(`size="${config.size}"`);
    if (config.disabled) props.push("disabled");
    if (config.loading) props.push("loading");
    if (config.block) props.push("block");
    if (config.icon !== "none") props.push(`icon={${config.icon}}`);
    if (config.iconRight !== "none")
      props.push(`iconRight={${config.iconRight}}`);

    const propsString = props.length > 0 ? "\n  " + props.join("\n  ") : "";
    return `<Button${propsString}>\n  ${config.text}\n</Button>`;
  };

  const [showCode, setShowCode] = useState(false);

  return (
    <div className="min-h-screen bg-gray-50">
      <div className="max-w-7xl mx-auto p-6">
        {/* 标题 */}
        <div className="mb-8">
          <h1 className="text-2xl font-bold text-gray-900 mb-2">Button 组件</h1>
          <p className="text-gray-600">配置按钮属性,预览即时更新</p>
        </div>

        <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
          {/* 配置面板 */}
          <div className="lg:col-span-1 bg-white rounded-xl shadow-sm border border-gray-200 p-6">
            <h2 className="text-lg font-semibold text-gray-900 mb-6">
              按钮配置
            </h2>

            {/* 变体类型 */}
            <div className="mb-6">
              <label className="block text-sm font-medium text-gray-700 mb-2">
                按钮类型
              </label>
              <div className="grid grid-cols-2 gap-2">
                {VARIANTS.map((v) => (
                  <button
                    key={v.value}
                    onClick={() => updateConfig("variant", v.value)}
                    className={`px-3 py-2 text-sm rounded-md border transition-all ${
                      config.variant === v.value
                        ? "border-blue-500 bg-blue-50 text-blue-700 font-medium"
                        : "border-gray-300 text-gray-700 hover:bg-gray-50"
                    }`}
                  >
                    {v.label}
                  </button>
                ))}
              </div>
            </div>

            {/* 颜色 */}
            <div className="mb-6">
              <label className="block text-sm font-medium text-gray-700 mb-2">
                颜色
              </label>
              <div className="flex gap-2">
                {COLORS.map((c) => (
                  <button
                    key={c.value}
                    onClick={() => updateConfig("color", c.value)}
                    className={`flex-1 px-3 py-2 text-sm rounded-md border transition-all ${
                      config.color === c.value
                        ? "border-blue-500 bg-blue-50 text-blue-700 font-medium"
                        : "border-gray-300 text-gray-700 hover:bg-gray-50"
                    }`}
                  >
                    {c.label}
                  </button>
                ))}
              </div>
            </div>

            {/* 尺寸 */}
            <div className="mb-6">
              <label className="block text-sm font-medium text-gray-700 mb-2">
                尺寸
              </label>
              <div className="flex gap-2">
                {SIZES.map((s) => (
                  <button
                    key={s.value}
                    onClick={() => updateConfig("size", s.value)}
                    className={`flex-1 px-3 py-2 text-sm rounded-md border transition-all ${
                      config.size === s.value
                        ? "border-blue-500 bg-blue-50 text-blue-700 font-medium"
                        : "border-gray-300 text-gray-700 hover:bg-gray-50"
                    }`}
                  >
                    {s.label}
                  </button>
                ))}
              </div>
            </div>

            {/* 图标配置 */}
            <div className="mb-6">
              <label className="block text-sm font-medium text-gray-700 mb-2">
                左侧图标
              </label>
              <select
                value={config.icon}
                onChange={(e) => updateConfig("icon", e.target.value)}
                className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white"
              >
                {ICON_OPTIONS.map((opt) => (
                  <option key={opt.value} value={opt.value}>
                    {opt.label}
                  </option>
                ))}
              </select>
            </div>

            <div className="mb-6">
              <label className="block text-sm font-medium text-gray-700 mb-2">
                右侧图标
              </label>
              <select
                value={config.iconRight}
                onChange={(e) => updateConfig("iconRight", e.target.value)}
                className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white"
              >
                {ICON_OPTIONS.map((opt) => (
                  <option key={opt.value} value={opt.value}>
                    {opt.label}
                  </option>
                ))}
              </select>
            </div>

            {/* 按钮文字 */}
            <div className="mb-6">
              <label className="block text-sm font-medium text-gray-700 mb-2">
                按钮文字
              </label>
              <input
                type="text"
                value={config.text}
                onChange={(e) => updateConfig("text", e.target.value)}
                className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                placeholder="输入按钮文字"
              />
            </div>

            {/* 开关选项 */}
            <div className="mb-2">
              <label className="block text-sm font-medium text-gray-700 mb-3">
                选项
              </label>
              <div className="space-y-3">
                <label className="flex items-center gap-3 cursor-pointer">
                  <input
                    type="checkbox"
                    checked={config.disabled}
                    onChange={(e) => updateConfig("disabled", e.target.checked)}
                    className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
                  />
                  <span className="text-sm text-gray-700">禁用</span>
                </label>
                <label className="flex items-center gap-3 cursor-pointer">
                  <input
                    type="checkbox"
                    checked={config.loading}
                    onChange={(e) => updateConfig("loading", e.target.checked)}
                    className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
                  />
                  <span className="text-sm text-gray-700">加载中</span>
                </label>
                <label className="flex items-center gap-3 cursor-pointer">
                  <input
                    type="checkbox"
                    checked={config.block}
                    onChange={(e) => updateConfig("block", e.target.checked)}
                    className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
                  />
                  <span className="text-sm text-gray-700">全宽</span>
                </label>
              </div>
            </div>
          </div>

          {/* 预览区域 */}
          <div className="lg:col-span-2 space-y-6">
            {/* 主要预览 */}
            <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8">
              <h2 className="text-lg font-semibold text-gray-900 mb-6">预览</h2>

              <div
                className={`flex ${
                  config.block ? "flex-col" : "flex-row"
                } items-center justify-center gap-6 min-h-[140px]`}
              >
                <Button
                  variant={
                    config.variant as "solid" | "text" | "outline" | "link"
                  }
                  color={config.color as "primary" | "secondary" | "danger"}
                  size={config.size as "sm" | "md" | "lg"}
                  disabled={config.disabled}
                  loading={config.loading}
                  block={config.block}
                  icon={
                    config.icon !== "none"
                      ? (SelectedIcon as React.ComponentType<
                          React.SVGProps<SVGSVGElement>
                        >)
                      : undefined
                  }
                  iconRight={
                    config.iconRight !== "none"
                      ? (SelectedIconRight as React.ComponentType<
                          React.SVGProps<SVGSVGElement>
                        >)
                      : undefined
                  }
                >
                  {config.text}
                </Button>
              </div>
            </div>

            {/* 代码展示 */}
            <div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
              <button
                onClick={() => setShowCode(!showCode)}
                className="w-full px-6 py-4 flex items-center justify-between bg-gray-50 hover:bg-gray-100 transition-colors"
              >
                <span className="text-sm font-medium text-gray-700">代码</span>
                <span className="text-sm text-gray-500">
                  {showCode ? "收起" : "展开"}
                </span>
              </button>

              {showCode && (
                <pre className="p-6 text-sm overflow-x-auto bg-gray-900 text-gray-100">
                  <code>{generateCode()}</code>
                </pre>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

Button 组件支持多种类型、颜色和状态,适用于各种交互场景。

概述

  • 四种按钮类型:solid(实心)、text(文字)、outline(边框)、link(链接)
  • 三种颜色:primary(主色)、secondary(次要色)、danger(危险色)
  • 三种尺寸:sm(小)、md(中)、lg(大)
  • 六种状态:default、hover、pressed、disabled、loading、progress
  • 图标支持:支持前置图标和后置图标
  • 进度条:支持进度状态显示
  • CSS Token:使用 CSS 变量实现主题化

快速开始

import { Button } from "@/registry/wuhan/blocks/block-button/block-button";

export function Example() {
  return (
    <Button variant="solid" color="primary">
      按钮文字
    </Button>
  );
}

特性

  • 多种变体:满足不同视觉权重需求
  • 完整状态:覆盖所有交互状态
  • 图标支持:灵活的图标配置
  • 进度显示:支持上传/下载等场景
  • 主题化:基于 CSS 变量,易于定制

安装

pnpm dlx shadcn@latest add http://localhost:3000/r/wuhan/block-button.json

代码演示

按钮类型

按钮支持四种类型:实心(solid)、文字(text)、边框(outline)和链接(link)。

"use client";

import { Button } from "@/components/composed/block-button/block-button";

export function ButtonVariantsDemo() {
  return (
    <div className="flex items-center gap-3">
      <Button variant="solid" color="primary">
        实心按钮
      </Button>
      <Button variant="text" color="primary">
        文字按钮
      </Button>
      <Button variant="outline" color="primary">
        边框按钮
      </Button>
      <Button variant="link" color="primary">
        链接按钮
      </Button>
    </div>
  );
}

按钮颜色

按钮支持三种颜色:主色(primary)、次要色(secondary)和危险色(danger)。

"use client";

import { Button } from "@/components/composed/block-button/block-button";

export function ButtonColorsDemo() {
  return (
    <div className="flex items-center gap-3">
      <Button variant="solid" color="primary">
        主色
      </Button>
      <Button variant="solid" color="secondary">
        次要色
      </Button>
      <Button variant="solid" color="danger">
        危险
      </Button>
    </div>
  );
}

按钮尺寸

按钮支持三种尺寸:小(sm)、中(md)、大(lg)。

"use client";

import { Button } from "@/components/composed/block-button/block-button";

export function ButtonSizesDemo() {
  return (
    <div className="flex items-center gap-3">
      <Button size="sm" color="primary">
        小按钮
      </Button>
      <Button size="md" color="primary">
        中按钮
      </Button>
      <Button size="lg" color="primary">
        大按钮
      </Button>
    </div>
  );
}

带图标按钮

支持在按钮前后添加图标。

"use client";

import { Button } from "@/components/composed/block-button/block-button";
import { ArrowRight, Download, Send } from "lucide-react";

export function ButtonWithIconDemo() {
  return (
    <div className="flex items-center gap-3">
      <Button
        variant="solid"
        color="primary"
        icon={<Send className="size-4" />}
      >
        发送
      </Button>
      <Button
        variant="outline"
        color="primary"
        icon={<Download className="size-4" />}
      >
        下载
      </Button>
      <Button
        variant="text"
        color="primary"
        iconRight={<ArrowRight className="size-4" />}
      >
        下一步
      </Button>
    </div>
  );
}

纯图标按钮

children 为空时,只传图标,自动移除间距,适合纯图标按钮场景。

纯图标按钮(不同尺寸)

纯图标按钮(不同变体)

纯图标按钮(不同颜色)

纯图标按钮(禁用状态)

"use client";

import { Button } from "@/components/composed/block-button/block-button";
import { Download, Heart, Search, Settings, Star, Trash2 } from "lucide-react";

export function ButtonIconOnlyDemo() {
  return (
    <div className="space-y-6">
      <div className="space-y-4">
        <h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
          纯图标按钮(不同尺寸)
        </h3>
        <div className="flex items-center gap-3">
          <Button
            variant="solid"
            color="primary"
            size="sm"
            icon={<Search className="size-4" />}
          />
          <Button
            variant="solid"
            color="primary"
            size="md"
            icon={<Heart className="size-4" />}
          />
          <Button
            variant="solid"
            color="primary"
            size="lg"
            icon={<Star className="size-5" />}
          />
        </div>
      </div>

      <div className="space-y-4">
        <h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
          纯图标按钮(不同变体)
        </h3>
        <div className="flex items-center gap-3">
          <Button
            variant="solid"
            color="primary"
            icon={<Download className="size-4" />}
          />
          <Button
            variant="outline"
            color="primary"
            icon={<Download className="size-4" />}
          />
          <Button
            variant="text"
            color="primary"
            icon={<Download className="size-4" />}
          />
        </div>
      </div>

      <div className="space-y-4">
        <h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
          纯图标按钮(不同颜色)
        </h3>
        <div className="flex items-center gap-3">
          <Button
            variant="solid"
            color="primary"
            icon={<Settings className="size-4" />}
          />
          <Button
            variant="solid"
            color="secondary"
            icon={<Settings className="size-4" />}
          />
          <Button
            variant="solid"
            color="danger"
            icon={<Trash2 className="size-4" />}
          />
        </div>
      </div>

      <div className="space-y-4">
        <h3 className="text-sm font-medium text-[var(--Text-text-primary)]">
          纯图标按钮(禁用状态)
        </h3>
        <div className="flex items-center gap-3">
          <Button
            variant="solid"
            color="primary"
            icon={<Settings className="size-4" />}
            disabled
          />
          <Button
            variant="outline"
            color="primary"
            icon={<Settings className="size-4" />}
            disabled
          />
          <Button
            variant="text"
            color="primary"
            icon={<Settings className="size-4" />}
            disabled
          />
        </div>
      </div>
    </div>
  );
}

加载状态

通过 loading 属性显示加载状态。

"use client";

import { Button } from "@/components/composed/block-button/block-button";
import { useState } from "react";

export function ButtonLoadingDemo() {
  const [loading, setLoading] = useState(false);

  const handleClick = () => {
    setLoading(true);
    setTimeout(() => setLoading(false), 2000);
  };

  return (
    <div className="flex items-center gap-3">
      <Button
        variant="solid"
        color="primary"
        loading={loading}
        onClick={handleClick}
      >
        {loading ? "加载中..." : "点击加载"}
      </Button>
    </div>
  );
}

进度状态

通过 progressprogressValue 显示进度状态,适用于上传、下载等场景。

"use client";

import { Button } from "@/components/composed/block-button/block-button";
import { useState } from "react";

export function ButtonProgressDemo() {
  const [progress, setProgress] = useState(0);

  const handleClick = () => {
    setProgress(0);
    const interval = setInterval(() => {
      setProgress((prev) => {
        if (prev >= 100) {
          clearInterval(interval);
          return 100;
        }
        return prev + 10;
      });
    }, 200);
  };

  return (
    <div className="flex items-center gap-3">
      <Button
        variant="outline"
        color="primary"
        progress={progress < 100}
        progressValue={progress}
        onClick={handleClick}
      >
        {progress === 0
          ? "开始上传"
          : progress < 100
            ? `上传中 ${progress}%`
            : "完成"}
      </Button>
    </div>
  );
}

禁用状态

"use client";

import { Button } from "@/components/composed/block-button/block-button";

export function ButtonDisabledDemo() {
  return (
    <div className="flex items-center gap-3">
      <Button variant="solid" color="primary" disabled>
        禁用实心
      </Button>
      <Button variant="outline" color="primary" disabled>
        禁用边框
      </Button>
      <Button variant="text" color="primary" disabled>
        禁用文字
      </Button>
    </div>
  );
}

全宽按钮

通过 block 属性使按钮占满父容器宽度。

"use client";

import { Button } from "@/components/composed/block-button/block-button";

export function ButtonBlockDemo() {
  return (
    <div className="space-y-3">
      <Button variant="solid" color="primary" block>
        全宽按钮
      </Button>
    </div>
  );
}

API

Button Props

参数说明类型默认值
variant按钮类型'solid' | 'text' | 'outline' | 'link''solid'
color按钮颜色'primary' | 'secondary' | 'danger''primary'
size按钮尺寸'sm' | 'md' | 'lg''md'
loading是否加载中booleanfalse
disabled是否禁用booleanfalse
progress是否显示进度booleanfalse
progressValue进度值 0-100number-
icon前置图标React.ComponentType<SVGProps<SVGSVGElement>>-
iconRight后置图标React.ComponentType<SVGProps<SVGSVGElement>>-
block是否全宽booleanfalse
asChild作为子组件渲染booleanfalse
children按钮内容React.ReactNode-
onClick点击事件() => void-

设计规范

尺寸规范

尺寸高度水平内边距字号行高
sm28px (h-7)12px (px-3)12px (text-xs)16px
md32px (h-8)16px (px-4)14px (text-sm)20px
lg40px (h-10)24px (px-6)16px (text-base)24px

基础样式

变体圆角间距字体
solid/text/outline8px (radius-md)8px (gap-md)font-family-cn
link-4px (gap-xs)font-family-cn

颜色规范

Primary 主色

状态SolidTextOutlineLink
defaultbg-brand, text-inversebg-container, border-brand, text-brandbg-container, text-brandbg-transparent, text-brand
hoverbg-brand-hover, text-inversebg-brand-light, text-brandbg-container, text-brand-hoverbg-transparent, text-brand-hover
pressedbg-brand-active, text-inversebg-brand-light-active, text-brandbg-container, text-brand-activebg-transparent, text-brand-active
disabledopacity-50bg-container-disable, text-disablebg-container-disable, text-disablebg-transparent, text-disable
loadingopacity-50bg-container-disable, text-disablebg-container-disable, text-disablebg-transparent, text-disable
progressbg-brand, text-inverse带 border-brandbg-container, text-brandbg-transparent, text-brand

Secondary 次要色

状态SolidTextOutlineLink
defaultbg-secondary, text-inversebg-container, border-secondary, text-secondarybg-container, text-secondarybg-transparent, text-secondary
hoverbg-secondary-hover, text-inversebg-secondary-light, text-secondary-hoverbg-container, text-secondary-hoverbg-transparent, text-secondary-hover
pressedbg-secondary-active, text-inversebg-secondary-light-active, text-secondary-activebg-container, text-secondary-activebg-transparent, text-secondary-active
disabledopacity-50bg-container-disable, text-disablebg-container-disable, text-disablebg-transparent, text-disable
loadingopacity-50bg-container-disable, text-disablebg-container-disable, text-disablebg-transparent, text-disable
progressbg-secondary, text-inverse带 border-secondarybg-container, text-secondarybg-transparent, text-secondary

Danger 危险色

状态SolidTextOutlineLink
defaultbg-error, text-inversebg-container, border-error, text-errorbg-container, text-errorbg-transparent, text-error
hoverbg-error-hover, text-inversebg-error-light, text-error-hoverbg-container, text-error-hoverbg-transparent, text-error-hover
pressedbg-error-active, text-inversebg-error-light-active, text-error-activebg-container, text-error-activebg-transparent, text-error-active
disabledopacity-50bg-container-disable, text-disablebg-container-disable, text-disablebg-transparent, text-disable
loadingopacity-50bg-container-disable, text-disablebg-container-disable, text-disablebg-transparent, text-disable
progressbg-error, text-inverse带 border-errorbg-container, text-errorbg-transparent, text-error

注意事项

  1. CSS 变量:组件使用 CSS 变量实现主题化,需要在全局样式中定义对应的变量
  2. 加载状态:加载状态时会禁用按钮并显示加载图标
  3. 进度状态:进度状态和加载状态互斥,不能同时使用
  4. 禁用状态:禁用状态会覆盖加载和进度状态

交互规范

  • 实心按钮:用于主要操作,视觉权重最高
  • 文字/边框按钮:用于次要操作,视觉权重较低
  • 危险按钮:用于删除、移除等危险操作
  • 尺寸选择:小按钮用于紧凑场景,大按钮用于主要操作
  • 加载状态:加载状态时禁止重复点击
  • 危险操作:建议使用危险颜色
  • 主要操作:使用实心主色按钮