171 lines
7.0 KiB
TypeScript
171 lines
7.0 KiB
TypeScript
|
|
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"
|
||
|
|
|
||
|
|
// Loading 图标组件
|
||
|
|
export const LoadingIcon = ({ className }: { className?: string }) => (
|
||
|
|
<i className={cn("iconfont icon-loading animate-spin", className)} />
|
||
|
|
)
|
||
|
|
|
||
|
|
const buttonVariants = cva(
|
||
|
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap transition-all disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer",
|
||
|
|
{
|
||
|
|
variants: {
|
||
|
|
variant: {
|
||
|
|
primary:
|
||
|
|
"bg-primary-gradient-normal disabled:bg-none disabled:bg-primary-variant-disabled disabled:text-txt-primary-disabled disabled:backdrop-blur-lg text-txt-primary-specialmap-normal shadow-xs hover:bg-primary-gradient-hover active:bg-primary-gradient-press",
|
||
|
|
secondary: "border border-solid border-primary-variant-normal text-primary-variant-normal disabled:border-primary-variant-disabled disabled:text-txt-primary-disabled hover:border-primary-variant-hover hover:text-primary-variant-hover active:border-primary-variant-press active:text-primary-variant-press",
|
||
|
|
tertiary: "bg-surface-element-normal text-txt-primary-normal disabled:bg-surface-element-disabled disabled:text-txt-primary-disabled hover:bg-surface-element-hover active:bg-surface-element-press",
|
||
|
|
tertiaryDark: "bg-surface-element-dark-normal text-txt-primary-normal disabled:bg-surface-element-dark-disabled disabled:text-txt-primary-disabled hover:bg-surface-element-dark-hover active:bg-surface-element-dark-press",
|
||
|
|
destructive: "bg-important-normal text-txt-primary-normal disabled:bg-important-disabled disabled:text-txt-primary-disabled disabled:backdrop-blur-lg hover:bg-important-hover active:bg-important-press",
|
||
|
|
ghost: "bg-transparent text-primary-variant-normal disabled:text-txt-primary-disabled hover:bg-surface-element-hover hover:backdrop-blur-[16px] active:bg-surface-element-press",
|
||
|
|
vip: "bg-gradient-secondary-normal text-black"
|
||
|
|
},
|
||
|
|
size: {
|
||
|
|
large: "min-w-[128px] h-12 rounded-full px-8 has-[>svg]:px-6 txt-label-l",
|
||
|
|
small: "min-w-[80px] h-8 rounded-full px-4 has-[>svg]:px-2 txt-label-s",
|
||
|
|
icon: "size-9",
|
||
|
|
},
|
||
|
|
block: {
|
||
|
|
true: "w-full",
|
||
|
|
false: "",
|
||
|
|
},
|
||
|
|
loading: {
|
||
|
|
true: "cursor-not-allowed backdrop-blur backdrop-filter",
|
||
|
|
false: "",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
defaultVariants: {
|
||
|
|
variant: "primary",
|
||
|
|
size: "large",
|
||
|
|
block: false,
|
||
|
|
loading: false,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
const iconButtonVariants = cva(
|
||
|
|
"inline-flex items-center justify-center transition-all disabled:pointer-events-none [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none rounded-full cursor-pointer",
|
||
|
|
{
|
||
|
|
variants: {
|
||
|
|
variant: {
|
||
|
|
default: "bg-primary-gradient-normal text-txt-primary-normal disabled:bg-primary-gradient-disabled disabled:bg-none disabled:text-txt-primary-disabled hover:bg-primary-gradient-hover active:bg-primary-gradient-press",
|
||
|
|
primary: "bg-primary-normal text-txt-primary-normal disabled:bg-primary-disabled disabled:text-txt-primary-disabled hover:bg-primary-hover active:bg-primary-press",
|
||
|
|
tertiary: "bg-surface-element-normal text-txt-primary-normal disabled:bg-surface-element-disabled disabled:text-txt-primary-disabled hover:bg-surface-element-hover active:bg-surface-element-press",
|
||
|
|
tertiaryDark: "bg-surface-element-dark-normal text-txt-primary-normal disabled:bg-surface-element-dark-disabled disabled:text-txt-primary-specialmap-disable hover:bg-surface-element-dark-hover active:bg-surface-element-dark-press",
|
||
|
|
contrast: "bg-surface-element-light-normal text-txt-primary-normal disabled:bg-surface-element-light-disabled disabled:text-txt-primary-disabled hover:bg-surface-element-light-hover active:bg-surface-element-light-press",
|
||
|
|
ghost: "bg-transparent text-txt-primary-normal disabled:text-txt-primary-disabled hover:bg-surface-element-hover hover:backdrop-blur-[16px] active:bg-surface-element-press",
|
||
|
|
destructive: "bg-important-normal text-txt-primary-normal disabled:bg-important-disabled disabled:text-txt-primary-disabled disabled:backdrop-blur-lg hover:bg-important-hover active:bg-important-press",
|
||
|
|
},
|
||
|
|
size: {
|
||
|
|
large: "size-12 [&_svg:not([class*='size-'])]:size-6 [&_i]:!text-[24px]",
|
||
|
|
small: "size-8 [&_svg:not([class*='size-'])]:size-4 [&_i]:!text-[16px]",
|
||
|
|
xs: "size-6 [&_svg:not([class*='size-'])]:size-3 [&_i]:!text-[12px]",
|
||
|
|
mini: "size-4 [&_svg:not([class*='size-'])]:size-2 [&_i]:!text-[8px]",
|
||
|
|
},
|
||
|
|
loading: {
|
||
|
|
true: "cursor-not-allowed backdrop-blur backdrop-filter",
|
||
|
|
false: "",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
defaultVariants: {
|
||
|
|
variant: "default",
|
||
|
|
size: "large",
|
||
|
|
loading: false,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
function Button({
|
||
|
|
className,
|
||
|
|
variant,
|
||
|
|
size,
|
||
|
|
block,
|
||
|
|
loading = false,
|
||
|
|
children,
|
||
|
|
asChild = false,
|
||
|
|
disabled,
|
||
|
|
...props
|
||
|
|
}: React.ComponentProps<"button"> &
|
||
|
|
VariantProps<typeof buttonVariants> & {
|
||
|
|
asChild?: boolean,
|
||
|
|
block?: boolean,
|
||
|
|
loading?: boolean
|
||
|
|
}) {
|
||
|
|
const Comp = asChild ? Slot : "button"
|
||
|
|
|
||
|
|
// 当 loading 为 true 时,自动禁用按钮
|
||
|
|
const isDisabled = disabled || loading
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Comp
|
||
|
|
data-slot="button"
|
||
|
|
className={cn(buttonVariants({ variant, size, className, block, loading }))}
|
||
|
|
disabled={isDisabled}
|
||
|
|
{...props}
|
||
|
|
>
|
||
|
|
{loading && (
|
||
|
|
<LoadingIcon
|
||
|
|
className={cn(
|
||
|
|
size === "large" ? "!text-[24px]" : "!text-[16px]",
|
||
|
|
"shrink-0"
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
{/* 当 loading 时,如果有 children 则显示,否则只显示 loading 图标 */}
|
||
|
|
{!loading ? children : null}
|
||
|
|
</Comp>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
function IconButton({
|
||
|
|
className,
|
||
|
|
variant,
|
||
|
|
iconfont,
|
||
|
|
size,
|
||
|
|
loading = false,
|
||
|
|
children,
|
||
|
|
asChild = false,
|
||
|
|
disabled,
|
||
|
|
...props
|
||
|
|
}: React.ComponentProps<"button"> &
|
||
|
|
VariantProps<typeof iconButtonVariants> & {
|
||
|
|
iconfont?: string,
|
||
|
|
asChild?: boolean,
|
||
|
|
loading?: boolean
|
||
|
|
}) {
|
||
|
|
const Comp = asChild ? Slot : "button"
|
||
|
|
|
||
|
|
// 当 loading 为 true 时,自动禁用按钮
|
||
|
|
const isDisabled = disabled || loading
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Comp
|
||
|
|
data-slot="icon-button"
|
||
|
|
className={cn(iconButtonVariants({ variant, size, className, loading }))}
|
||
|
|
disabled={isDisabled}
|
||
|
|
type="button"
|
||
|
|
{...props}
|
||
|
|
>
|
||
|
|
{loading ? (
|
||
|
|
<LoadingIcon
|
||
|
|
className={cn(
|
||
|
|
size === "large" ? "!text-[24px]" :
|
||
|
|
size === "small" ? "!text-[16px]" :
|
||
|
|
size === "xs" ? "!text-[12px]" : "!text-[8px]"
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
{iconfont && <i className={cn("iconfont", iconfont)} />}
|
||
|
|
{children}
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</Comp>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export { Button, IconButton, buttonVariants, iconButtonVariants }
|