19 lines
4.8 KiB
JSON
19 lines
4.8 KiB
JSON
{
|
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
"name": "ripple-button",
|
|
"type": "registry:ui",
|
|
"title": "Ripple Button",
|
|
"description": "A clickable button featuring a ripple animation effect on click.",
|
|
"dependencies": [
|
|
"motion",
|
|
"class-variance-authority"
|
|
],
|
|
"files": [
|
|
{
|
|
"path": "registry/buttons/ripple/index.tsx",
|
|
"content": "'use client';\n\nimport * as React from 'react';\nimport { type HTMLMotionProps, motion, type Transition } from 'motion/react';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst buttonVariants = cva(\n \"relative overflow-hidden cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_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\",\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',\n outline:\n 'border bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost:\n 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n },\n size: {\n default: 'h-10 px-4 py-2 has-[>svg]:px-3',\n sm: 'h-9 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',\n lg: 'h-11 px-8 has-[>svg]:px-6',\n icon: 'size-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\nconst rippleVariants = cva('absolute rounded-full size-5 pointer-events-none', {\n variants: {\n variant: {\n default: 'bg-primary-foreground',\n destructive: 'bg-destructive',\n outline: 'bg-input',\n secondary: 'bg-secondary',\n ghost: 'bg-accent',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\ntype Ripple = {\n id: number;\n x: number;\n y: number;\n};\n\ntype RippleButtonProps = HTMLMotionProps<'button'> & {\n children: React.ReactNode;\n rippleClassName?: string;\n scale?: number;\n transition?: Transition;\n} & VariantProps<typeof buttonVariants>;\n\nfunction RippleButton({\n ref,\n children,\n onClick,\n className,\n rippleClassName,\n variant,\n size,\n scale = 10,\n transition = { duration: 0.6, ease: 'easeOut' },\n ...props\n}: RippleButtonProps) {\n const [ripples, setRipples] = React.useState<Ripple[]>([]);\n const buttonRef = React.useRef<HTMLButtonElement>(null);\n React.useImperativeHandle(ref, () => buttonRef.current as HTMLButtonElement);\n\n const createRipple = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n const button = buttonRef.current;\n if (!button) return;\n\n const rect = button.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n\n const newRipple: Ripple = {\n id: Date.now(),\n x,\n y,\n };\n\n setRipples((prev) => [...prev, newRipple]);\n\n setTimeout(() => {\n setRipples((prev) => prev.filter((r) => r.id !== newRipple.id));\n }, 600);\n },\n [],\n );\n\n const handleClick = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n createRipple(event);\n if (onClick) {\n onClick(event);\n }\n },\n [createRipple, onClick],\n );\n\n return (\n <motion.button\n ref={buttonRef}\n data-slot=\"ripple-button\"\n onClick={handleClick}\n whileTap={{ scale: 0.95 }}\n whileHover={{ scale: 1.05 }}\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n >\n {children}\n {ripples.map((ripple) => (\n <motion.span\n key={ripple.id}\n initial={{ scale: 0, opacity: 0.5 }}\n animate={{ scale, opacity: 0 }}\n transition={transition}\n className={cn(\n rippleVariants({ variant, className: rippleClassName }),\n )}\n style={{\n top: ripple.y - 10,\n left: ripple.x - 10,\n }}\n />\n ))}\n </motion.button>\n );\n}\n\nexport { RippleButton, type RippleButtonProps };\n",
|
|
"type": "registry:ui",
|
|
"target": "components/animate-ui/buttons/ripple.tsx"
|
|
}
|
|
]
|
|
} |