{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "button", "type": "registry:ui", "title": "Button", "description": "A button component that can be used to trigger an action.", "dependencies": [ "tailwindcss" ], "registryDependencies": [ "@tra-kit/loading-spinner" ], "files": [ { "path": "registry/tra-kit/components/button.tsx", "content": "import { cva } from 'class-variance-authority';\r\nimport React, { useEffect, useRef } from 'react';\r\nimport type { VariantProps } from 'class-variance-authority';\r\n\r\nimport LoadingSpinner from '@/components/ui/loading-spinner';\r\nimport { cn } from '@/lib/utils';\r\n\r\nexport const buttonVariants = cva(\r\n `btn-ripple active:scale-[0.97] ring-offset-background focus-visible:ring-ring relative inline-flex select-none flex-wrap items-center justify-center overflow-hidden whitespace-nowrap text-sm font-medium\r\n transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 \r\n disabled:pointer-events-none `,\r\n {\r\n variants: {\r\n variant: {\r\n solid:\r\n 'bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-button-disabled disabled:text-button-disabled-text',\r\n outlined:\r\n 'border-primary text-primary-foreground hover:bg-primary/10 disabled:border-button-disabled-text disabled:text-button-disabled-text border-2 bg-transparent',\r\n ghost:\r\n 'text-primary hover:text-primary/90 disabled:text-button-disabled-text bg-transparent',\r\n },\r\n color: {\r\n primary: 'bg-primary text-primary-foreground',\r\n secondary: 'bg-secondary text-secondary-foreground',\r\n tertiary: 'bg-tertiary text-tertiary-foreground',\r\n error: 'bg-error text-error-foreground',\r\n },\r\n size: {\r\n default: 'h-14 px-7',\r\n sm: 'h-13 rounded-lg px-6',\r\n lg: 'h-15 rounded-lg px-8',\r\n icon: 'size-10 min-h-10 min-w-10 rounded-full',\r\n },\r\n rounded: {\r\n default: 'rounded-lg',\r\n lg: 'rounded-full',\r\n },\r\n },\r\n compoundVariants: [\r\n {\r\n variant: 'outlined',\r\n color: 'primary',\r\n className: 'border-primary text-primary hover:bg-primary/10 bg-transparent',\r\n },\r\n {\r\n variant: 'outlined',\r\n color: 'secondary',\r\n className: 'border-secondary text-secondary hover:bg-secondary/10 bg-transparent',\r\n },\r\n {\r\n variant: 'outlined',\r\n color: 'tertiary',\r\n className: 'border-tertiary text-tertiary hover:bg-tertiary/10 bg-transparent',\r\n },\r\n {\r\n variant: 'outlined',\r\n color: 'error',\r\n className: 'border-error text-error hover:bg-error/10 hover:text-error/80 bg-transparent',\r\n },\r\n {\r\n variant: 'solid',\r\n color: 'primary',\r\n className: 'bg-primary text-primary-foreground hover:bg-primary/90',\r\n },\r\n {\r\n variant: 'solid',\r\n color: 'secondary',\r\n className: 'bg-secondary text-secondary-foreground hover:bg-secondary/90',\r\n },\r\n {\r\n variant: 'solid',\r\n color: 'tertiary',\r\n className: 'bg-tertiary text-tertiary-foreground hover:bg-tertiary/90',\r\n },\r\n {\r\n variant: 'solid',\r\n color: 'error',\r\n className: 'bg-error text-error-foreground hover:bg-error/80',\r\n },\r\n {\r\n variant: 'ghost',\r\n color: 'primary',\r\n className: 'text-primary hover:text-primary/80 bg-transparent',\r\n },\r\n {\r\n variant: 'ghost',\r\n color: 'secondary',\r\n className: 'text-secondary hover:text-secondary/80 bg-transparent',\r\n },\r\n {\r\n variant: 'ghost',\r\n color: 'tertiary',\r\n className: 'text-tertiary hover:text-tertiary/80 bg-transparent',\r\n },\r\n {\r\n variant: 'ghost',\r\n color: 'error',\r\n className: 'text-error hover:text-error/80 bg-transparent',\r\n },\r\n ],\r\n defaultVariants: {\r\n variant: 'solid',\r\n size: 'default',\r\n color: 'primary',\r\n rounded: 'default',\r\n },\r\n },\r\n);\r\n\r\nconst spinnerVariants = cva('border-t-button-disabled text-button-disabled-text mr-2 border-2', {\r\n variants: {\r\n size: {\r\n default: 'size-4 max-h-4 min-h-4 min-w-4 max-w-4',\r\n sm: 'size-3 max-h-3 min-h-3 min-w-3 max-w-3',\r\n lg: 'size-5 max-h-5 min-h-5 min-w-5 max-w-5',\r\n icon: 'size-3 max-h-3 min-h-3 min-w-3 max-w-3',\r\n },\r\n },\r\n defaultVariants: {\r\n size: 'default',\r\n },\r\n});\r\n\r\nexport interface IButton\r\n extends\r\n Omit, 'color'>,\r\n VariantProps {\r\n asChild?: React.ElementType;\r\n children: React.ReactNode;\r\n className?: string;\r\n color?: 'primary' | 'secondary' | 'tertiary';\r\n disabled?: boolean;\r\n disableEffect?: boolean;\r\n effectColor?: string;\r\n effectOpacity?: string;\r\n loading?: boolean;\r\n loadingSpinnerClassname?: string;\r\n loadingText?: string;\r\n rounded?: 'default' | 'lg';\r\n size?: 'default' | 'sm' | 'lg' | 'icon';\r\n variant?: 'solid' | 'outlined' | 'ghost';\r\n}\r\n\r\nconst Button = React.forwardRef(\r\n (\r\n {\r\n asChild,\r\n children,\r\n className,\r\n color,\r\n disableEffect = false,\r\n disabled,\r\n effectColor = '#212129',\r\n effectOpacity = '0.3',\r\n loading = false,\r\n loadingSpinnerClassname,\r\n loadingText = 'Sending...',\r\n rounded = 'default',\r\n size,\r\n variant,\r\n type,\r\n ...props\r\n },\r\n ref,\r\n ) => {\r\n const Comp = asChild ?? 'button';\r\n const buttonRef = useRef(null);\r\n\r\n useEffect(() => {\r\n const btn = buttonRef.current;\r\n const handleClick = (event: MouseEvent) => {\r\n const { pageX, pageY, currentTarget } = event;\r\n if (currentTarget instanceof HTMLElement) {\r\n const x = ((pageX - currentTarget.offsetLeft) * 100) / currentTarget.offsetWidth;\r\n const y = ((pageY - currentTarget.offsetTop) * 100) / currentTarget.offsetHeight;\r\n const ripple = document.createElement('span');\r\n const uniqueClassName = `ripple-effect-${Math.random().toString(36).substr(2, 9)}`;\r\n ripple.classList.add('ripple-effect', uniqueClassName);\r\n ripple.style.left = `${x}%`;\r\n ripple.style.top = `${y}%`;\r\n\r\n const style = document.createElement('style');\r\n style.innerHTML = `\r\n .${uniqueClassName} {\r\n position: absolute;\r\n transform: translate(-50%, -50%);\r\n border-radius: 50%;\r\n width: 5px;\r\n background-color: ${effectColor};\r\n height: 5px;\r\n pointer-events: none;\r\n z-index: 5;\r\n animation: ripple 0.8s linear infinite;\r\n }\r\n @keyframes ripple {\r\n 0% {\r\n width: 0;\r\n height: 0;\r\n opacity: ${effectOpacity};\r\n }\r\n 100% {\r\n width: 500px;\r\n height: 500px;\r\n opacity: 0;\r\n }\r\n }\r\n `;\r\n document.head.appendChild(style);\r\n\r\n btn!.appendChild(ripple);\r\n setTimeout(() => {\r\n ripple.remove();\r\n style.remove();\r\n }, 800);\r\n }\r\n };\r\n\r\n if (btn && !disableEffect) {\r\n btn.addEventListener('click', handleClick);\r\n }\r\n\r\n return () => {\r\n if (btn) {\r\n btn.removeEventListener('click', handleClick);\r\n }\r\n };\r\n }, [effectColor, effectOpacity, disableEffect]);\r\n\r\n return (\r\n \r\n {loading && (\r\n \r\n )}\r\n <>\r\n {loading && size !== 'icon' ? loadingText : loading && size === 'icon' ? null : children}\r\n \r\n \r\n );\r\n },\r\n);\r\nButton.displayName = 'Button';\r\n\r\nexport default Button;\r\n", "type": "registry:ui" } ] }