WHAT - 通过 shadcn 组件源码学习 React

一、button

import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
    "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
    {
        variants: {
            variant: {
                default:
                    "bg-primary text-primary-foreground hover:bg-primary/90",
                destructive:
                    "bg-destructive text-destructive-foreground hover:bg-destructive/90",
                outline:
                    "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
                secondary:
                    "bg-secondary text-secondary-foreground hover:bg-secondary/80",
                disabled:
                    "disabled-foreground bg-disabled text-disabled-foreground",
                ghost: "hover:bg-accent focus-visible:ring-0 focus-visible:ring-offset-0",
                link: "hover:text-primary underline-offset-4",
                icon: "border border-input",
            },
            size: {
                default: "h-8 px-5 py-1.5",
                sm: "h-9 rounded-md px-3",
                lg: "h-11 rounded-md px-8",
                icon: "h-6 w-6",
                iconSm: "h-8 w-8",
                ssm: "h-6",
            },
        },
        defaultVariants: {
            variant: "default",
            size: "default",
        },
    },
)

export interface ButtonProps
    extends React.ButtonHTMLAttributes<HTMLButtonElement>,
        VariantProps<typeof buttonVariants> {
    asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
    ({ className, variant, size, asChild = false, ...props }, ref) => {
        const Comp = asChild ? Slot : "button"
        return (
            <Comp
                className={cn(buttonVariants({ variant, size, className }))}
                ref={ref}
                {...props}
            />
        )
    },
)
Button.displayName = "Button"

export { Button, buttonVariants }

这段代码定义了一个可变样式的按钮组件Button,使用了多个工具和库。我们将逐步解释各部分代码的作用。

1. 导入部分

import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
  • React: 导入React库。
  • Slot: 从@radix-ui/react-slot库中导入Slot组件,用于支持“asChild”属性。
  • cvaVariantProps: 从class-variance-authority库中导入,用于定义可变样式。
  • cn: 从项目中的utils工具库导入cn函数,用于合并CSS类名。

2. 定义按钮的样式变体

const buttonVariants = cva(
    "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
    {
        variants: {
            variant: {
                default: "bg-primary text-primary-foreground hover:bg-primary/90",
                destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
                outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
                secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
                disabled: "disabled-foreground bg-disabled text-disabled-foreground",
                ghost: "hover:bg-accent focus-visible:ring-0 focus-visible:ring-offset-0",
                link: "hover:text-primary underline-offset-4",
                icon: "border border-input",
            },
            size: {
                default: "h-8 px-5 py-1.5",
                sm: "h-9 rounded-md px-3",
                lg: "h-11 rounded-md px-8",
                icon: "h-6 w-6",
                iconSm: "h-8 w-8",
                ssm: "h-6",
            },
        },
        defaultVariants: {
            variant: "default",
            size: "default",
        },
    },
)
  • buttonVariants: 使用cva函数定义了按钮的样式变体。这个对象包含了两个主要部分:
    • variants: 定义了不同的变体选项,如variantsize,每个选项又包含不同的具体样式。
    • defaultVariants: 定义了默认的变体值。

当然,让我们逐一解释variants对象中的每个属性及其对应的CSS属性值。

1. variant

variant属性定义了按钮的多种视觉风格,每种风格都对应一组CSS类名。

  • default

    bg-primary {
      background-color: var(--primary-color);
    }
    text-primary-foreground {
      color: var(--primary-foreground-color);
    }
    hover:bg-primary/90 {
      background-color: var(--primary-color);
      opacity: 0.9;
    }
    
  • destructive

    bg-destructive {
      background-color: var(--destructive-color);
    }
    text-destructive-foreground {
      color: var(--destructive-foreground-color);
    }
    hover:bg-destructive/90 {
      background-color: var(--destructive-color);
      opacity: 0.9;
    }
    
  • outline

    border border-input {
      border: 1px solid var(--input-border-color);
    }
    bg-background {
      background-color: var(--background-color);
    }
    hover:bg-accent {
      background-color: var(--accent-color);
    }
    hover:text-accent-foreground {
      color: var(--accent-foreground-color);
    }
    
  • secondary

    bg-secondary {
      background-color: var(--secondary-color);
    }
    text-secondary-foreground {
      color: var(--secondary-foreground-color);
    }
    hover:bg-secondary/80 {
      background-color: var(--secondary-color);
      opacity: 0.8;
    }
    
  • disabled

    disabled-foreground {
      color: var(--disabled-foreground-color);
    }
    bg-disabled {
      background-color: var(--disabled-color);
    }
    text-disabled-foreground {
      color: var(--disabled-foreground-color);
    }
    
  • ghost

    hover:bg-accent {
      background-color: var(--accent-color);
    }
    focus-visible:ring-0 {
      outline: none;
      box-shadow: none;
    }
    focus-visible:ring-offset-0 {
      box-shadow: none;
    }
    
  • link

    hover:text-primary {
      color: var(--primary-color);
    }
    underline-offset-4 {
      text-underline-offset: 4px;
    }
    
  • icon

    border border-input {
      border: 1px solid var(--input-border-color);
    }
    

2. size

size属性定义了按钮的不同尺寸,每个尺寸都对应一组CSS类名。

  • default

    h-8 {
      height: 2rem; /* 32px */
    }
    px-5 {
      padding-left: 1.25rem; /* 20px */
      padding-right: 1.25rem; /* 20px */
    }
    py-1.5 {
      padding-top: 0.375rem; /* 6px */
      padding-bottom: 0.375rem; /* 6px */
    }
    
  • sm

    h-9 {
      height: 2.25rem; /* 36px */
    }
    rounded-md {
      border-radius: 0.375rem; /* 6px */
    }
    px-3 {
      padding-left: 0.75rem; /* 12px */
      padding-right: 0.75rem; /* 12px */
    }
    
  • lg

    h-11 {
      height: 2.75rem; /* 44px */
    }
    rounded-md {
      border-radius: 0.375rem; /* 6px */
    }
    px-8 {
      padding-left: 2rem; /* 32px */
      padding-right: 2rem; /* 32px */
    }
    
  • icon

    h-6 {
      height: 1.5rem; /* 24px */
    }
    w-6 {
      width: 1.5rem; /* 24px */
    }
    
  • iconSm

    h-8 {
      height: 2rem; /* 32px */
    }
    w-8 {
      width: 2rem; /* 32px */
    }
    
  • ssm

    h-6 {
      height: 1.5rem; /* 24px */
    }
    

总结

这些variants属性提供了丰富的样式变体,使得按钮组件可以根据不同的需求应用不同的外观和尺寸,通过简单的属性传递实现了多样化的视觉效果。

3. 定义按钮的属性类型

export interface ButtonProps
    extends React.ButtonHTMLAttributes<HTMLButtonElement>,
        VariantProps<typeof buttonVariants> {
    asChild?: boolean
}
  • ButtonProps: 定义了按钮组件的属性接口,扩展了React.ButtonHTMLAttributesVariantProps。另外,还增加了asChild属性,用于指定是否将按钮作为子组件渲染。

4. 定义按钮组件

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
    ({ className, variant, size, asChild = false, ...props }, ref) => {
        const Comp = asChild ? Slot : "button"
        return (
            <Comp
                className={cn(buttonVariants({ variant, size, className }))}
                ref={ref}
                {...props}
            />
        )
    },
)
Button.displayName = "Button"
  • Button: 使用React.forwardRef定义一个带有转发引用(ref)的按钮组件。
    • Comp: 根据asChild属性,动态决定使用Slot组件还是button元素。
    • cn(buttonVariants({ variant, size, className })): 使用cn函数合并传入的className和通过buttonVariants生成的变体样式。

5. 导出组件和样式变体

export { Button, buttonVariants }
  • ButtonbuttonVariants均被导出,允许在其他模块中使用。

总结

这个组件使用了class-variance-authority库来管理按钮的样式变体,通过React的forwardRef和条件渲染实现了灵活的按钮组件。这样,开发者可以通过简单的属性传递来改变按钮的外观和行为。

相关推荐

  1. WHAT - 通过 shadcn 学习 React

    2024-07-22 13:44:04       14 阅读
  2. WHAT - 介绍一个不太一样的 UI shadcn/ui

    2024-07-22 13:44:04       16 阅读
  3. React如何通信

    2024-07-22 13:44:04       33 阅读
  4. ReactReact 之间如何通信

    2024-07-22 13:44:04       41 阅读
  5. WHAT - React 学习系列(二)

    2024-07-22 13:44:04       25 阅读
  6. WHAT - React 学习系列(一)

    2024-07-22 13:44:04       20 阅读
  7. React间的通信

    2024-07-22 13:44:04       51 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-22 13:44:04       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-22 13:44:04       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-22 13:44:04       45 阅读
  4. Python语言-面向对象

    2024-07-22 13:44:04       55 阅读

热门阅读

  1. 探索 PHP 与 JD 详情 API 接口的连接奥秘

    2024-07-22 13:44:04       20 阅读
  2. 多个返回值QT/C++

    2024-07-22 13:44:04       17 阅读
  3. C# --- .Net Framework中的Binding Redirect

    2024-07-22 13:44:04       19 阅读
  4. 深入语音识别:贝叶斯准则的细致解析

    2024-07-22 13:44:04       18 阅读
  5. 从统计学、到机器学习和ChatGPT

    2024-07-22 13:44:04       18 阅读
  6. MobaXterm远程工具

    2024-07-22 13:44:04       21 阅读
  7. 【TORCH】获取第一个batch数值的几种方法

    2024-07-22 13:44:04       19 阅读
  8. [Python]使用pyttsx3将文字转语音

    2024-07-22 13:44:04       14 阅读
  9. 【QT】线程控制和同步

    2024-07-22 13:44:04       15 阅读
  10. [基础算法理论] --- 双指针

    2024-07-22 13:44:04       18 阅读
  11. PHP银行卡实名认证接口对接、银行卡识别

    2024-07-22 13:44:04       17 阅读