react19【系列实用教程】实用组件封装

必要的全局样式

src/index.css

c 复制代码
@import "tailwindcss";
/* 当根元素有 class="dark" 时 → 激活 dark: 样式,用于在页面上切换主题为暗黑模式 */
@custom-variant dark (&:where(.dark, .dark *));

@theme {
  /* 主配色 */
  --color-apple-light: #f5f5f7;
  --color-apple-dark: #1d1d1f;
  /* 标准色 */
  --color-apple-black: #000000;
  --color-apple-white: #ffffff;
  /* 品牌配色 */
  --color-apple-blue: #0071e3;
  --color-apple-red: #b64400;
  /* 灰阶系统(Gray Scale) */
  --color-apple-gray-100: #f5f5f7;
  --color-apple-gray-200: #d2d2d7;
  --color-apple-gray-300: #86868b;
  --color-apple-gray-800: #424245;
  --color-apple-gray-900: #1d1d1f;
  /* 字体 */
  --font-sans: "Inter", "Roboto", "Open Sans", "Helvetica Neue", sans-serif;
  --font-display: "Poppins", "Inter", "sans-serif";
  --font-text: "Source Sans Pro", "Open Sans", "sans-serif";
  /* 文字颜色*/
  --color-apple-text-light: #1d1d1f;
  --color-apple-text-dark: #f5f5f7;
  /* 阴影 */
  --shadow-apple-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
  --shadow-apple-md: 0 4px 12px rgba(0, 0, 0, 0.08);
  --shadow-apple-lg: 0 8px 24px rgba(0, 0, 0, 0.06);
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0.01em;
  color: var(--color-apple-text-light);
  background-color: var(--color-apple-light);
}

按钮

src/components/Button.tsx

c 复制代码
export interface ButtonProps {
  title?: React.ReactNode;
  variant?: "primary" | "outline";
  disabled?: boolean;
  onClick?: () => void;
}

const Button = ({
  title,
  variant = "primary",
  disabled = false,
  onClick,
}: ButtonProps) => {
  let className = `px-5 py-2 rounded-md border
                   transition-colors duration-200
                   inline-flex items-center justify-center gap-2`;
  if (variant === "primary") {
    className +=
      " bg-apple-blue text-white border-apple-blue hover:bg-apple-blue/90";
  } else if (variant === "outline") {
    className +=
      " bg-transparent text-apple-blue border-apple-blue hover:bg-apple-blue hover:text-white";
  }

  if (disabled) {
    className += " opacity-50 cursor-not-allowed pointer-events-none";
  }

  return (
    <button className={className} disabled={disabled} onClick={onClick}>
      {title}
    </button>
  );
};

export default Button;

使用

c 复制代码
<Button title="购买" variant="outline" />

图标按钮

src/components/IconButton.tsx

c 复制代码
import Button, { type ButtonProps } from "./Button.tsx";

export interface IconButtonProps extends ButtonProps {
  icon: React.ReactNode;
  ioconPosition?: "left" | "right";
}

const IconButton = ({
  icon,
  ioconPosition = "left",
  title,
  ...rest
}: IconButtonProps) => {
  return (
    <Button
      title={
        <span className="flex items-center gap-2">
          {ioconPosition === "left" && icon}
          <span>{title}</span>
          {ioconPosition === "right" && icon}
        </span>
      }
      {...rest}
    />
  );
};

export default IconButton;

使用

c 复制代码
import IconButton from "./IconButton.tsx";
import { MdOutlineNavigateNext } from "react-icons/md";
c 复制代码
          <IconButton
            icon={<MdOutlineNavigateNext />}
            ioconPosition="right"
            title="进一步了解"
            variant="primary"
          />
相关推荐
IT_陈寒1 小时前
React的useEffect依赖数组把我坑惨了,原来这样写才靠谱
前端·人工智能·后端
恋猫de小郭1 小时前
Flutter hit,一个可以灵活控制溢出点击的第三方包
android·前端·flutter
我是苏苏1 小时前
C#基础:使用System.Speech.Synthesis离线播放/朗读文字内容
开发语言·c#
古韵1 小时前
小程序上传进度条,还要自己监听 onProgressUpdate 吗?
前端·javascript·uni-app
古韵1 小时前
axios 已经跑得好好的,还要不要上 alova?
前端·javascript·axios
噗噗122 小时前
企业微信侧边栏应用与前端 JS-SDK 的深度整合指南
前端·javascript·企业微信
Patrick在香港2 小时前
Python依赖管理从踩坑到选型:pip/poetry/uv四种方案全面实测
开发语言·python·数据分析·scikit-learn·ai编程·pip·uv
我命由我123452 小时前
Kotlin 面向对象 - 枚举排序
android·java·开发语言·java-ee·kotlin·android studio·android-studio
程序喵大人2 小时前
【C++进阶】STL容器与迭代器 - 10 把容器、迭代器和数据流串成一个小程序
开发语言·c++·容器·小程序·迭代器·stl