wui
组件

闪光按钮 Shiny Button

带有流动扫光效果的填充按钮。

基础示例

Loading…

安装组件:

pnpm dlx wui@latest add @wui/shiny-button
components/ui/shiny-button.tsx
"use client"

import * as React from "react"
import { motion, useReducedMotion, type HTMLMotionProps } from "motion/react"

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

export interface ShinyButtonProps extends HTMLMotionProps<"button"> {
  /** Seconds for one full light sweep across the button. @default 3 */
  speed?: number
  /** Pause between sweeps, in seconds. @default 1.2 */
  gap?: number
  // Narrows motion's children type (which allows MotionValue) back to ReactNode.
  children?: React.ReactNode
}

/**
 * A "fancy" template component: a filled button with a light sweep that
 * continuously glides across it. Effect components like this are opt-in and are
 * allowed to be more opinionated than the core UI — but they still gate motion
 * on `prefers-reduced-motion`. Copy it as a starting point for your own effects.
 */
function ShinyButton({
  className,
  speed = 3,
  gap = 1.2,
  children,
  ...props
}: ShinyButtonProps) {
  const reduceMotion = useReducedMotion()

  return (
    <motion.button
      data-slot="shiny-button"
      whileTap={reduceMotion ? undefined : { scale: 0.97 }}
      className={cn(
        "relative inline-flex h-10 shrink-0 items-center justify-center gap-2 overflow-hidden whitespace-nowrap rounded-lg border border-primary/20 bg-primary px-6 text-sm font-medium text-primary-foreground shadow-sm outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
        className
      )}
      {...props}
    >
      <span className="relative z-10 inline-flex items-center gap-2">
        {children}
      </span>
      {reduceMotion ? null : (
        <motion.span
          aria-hidden
          data-slot="shiny-button-sheen"
          className="pointer-events-none absolute inset-y-0 w-1/2"
          style={{
            background:
              "linear-gradient(120deg, transparent 20%, var(--shine) 50%, transparent 80%)",
          }}
          initial={{ x: "-150%" }}
          animate={{ x: "300%" }}
          transition={{
            duration: speed,
            ease: "linear",
            repeat: Infinity,
            repeatDelay: gap,
          }}
        />
      )}
    </motion.button>
  )
}

export { ShinyButton }

组件作用

ShinyButtonFancy 系列的首个组件。该系列包含偏展示性的效果组件,类似 CodePen 中的创意效果;它们与核心 UI 共存,并通过相同的 wui add 流程按需拉取。

效果组件比核心基础组件更有视觉倾向,也更醒目,因此应克制、有目的地使用:

  • 适合用于首屏行动按钮、定价页的“升级”按钮或空状态等需要单一视觉焦点的位置。
  • 不要在整个表单或工具栏中铺满闪光按钮;持续动画会与内容争夺注意力并造成视觉疲劳。
  • 与所有 wui 动画一样,当用户偏好减少动态效果时,扫光会自动禁用,按钮仍保持为普通的实色按钮。

组件属性

ShinyButton 继承 motion.button 的全部属性,并额外提供:

PropTypeDefaultDescription
speednumber3Seconds for one full light sweep across the button.
gapnumber1.2Pause between sweeps, in seconds.
disabledboolean

事件

ShinyButton 会像 Button 一样,将 onClickonFocus 等原生按钮事件透传给底层元素。

扩展使用

组件源码位于你的项目中,可以自由调整外观:修改 speedgap 控制扫光的速度与密度, 将 bg-primary 表面替换为渐变,或修改 linear-gradient 实现彩色高光。

添加自己的效果组件

新效果组件遵循标准的组件规范,额外要求仅为在 registry.json 中添加 "categories": ["fancy"],并将页面放到组件侧边栏的 Fancy 分组下。 完整清单见 docs/COMPONENT-SPEC.md 第 9 节。