diff --git a/salesflow-saas/frontend/src/components/ui/badge.tsx b/salesflow-saas/frontend/src/components/ui/badge.tsx new file mode 100644 index 00000000..3835526c --- /dev/null +++ b/salesflow-saas/frontend/src/components/ui/badge.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { type ReactNode } from 'react'; +import { motion } from 'framer-motion'; +import { clsx } from 'clsx'; + +type BadgeVariant = 'success' | 'warning' | 'danger' | 'info' | 'neutral' | 'live'; + +interface BadgeProps { + variant?: BadgeVariant; + dot?: boolean; + children: ReactNode; + className?: string; +} + +const variantStyles: Record = { + success: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/30', + warning: 'bg-amber-500/15 text-amber-400 border-amber-500/30', + danger: 'bg-red-500/15 text-red-400 border-red-500/30', + info: 'bg-blue-500/15 text-blue-400 border-blue-500/30', + neutral: 'bg-slate-500/15 text-slate-400 border-slate-500/30', + live: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/30', +}; + +const dotColors: Record = { + success: 'bg-emerald-400', + warning: 'bg-amber-400', + danger: 'bg-red-400', + info: 'bg-blue-400', + neutral: 'bg-slate-400', + live: 'bg-emerald-400', +}; + +function Badge({ variant = 'neutral', dot = false, children, className }: BadgeProps) { + return ( + + {dot && ( + + {variant === 'live' && ( + + )} + + + )} + {children} + + ); +} + +export { Badge }; +export type { BadgeProps, BadgeVariant }; diff --git a/salesflow-saas/frontend/src/components/ui/button.tsx b/salesflow-saas/frontend/src/components/ui/button.tsx new file mode 100644 index 00000000..ba7861b5 --- /dev/null +++ b/salesflow-saas/frontend/src/components/ui/button.tsx @@ -0,0 +1,118 @@ +'use client'; + +import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'; +import { motion, type HTMLMotionProps } from 'framer-motion'; +import { clsx } from 'clsx'; +import { Loader2 } from 'lucide-react'; + +type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'gold'; +type ButtonSize = 'sm' | 'md' | 'lg'; + +interface ButtonProps + extends Omit, 'children' | 'disabled'>, + Pick, 'disabled' | 'type' | 'form'> { + variant?: ButtonVariant; + size?: ButtonSize; + loading?: boolean; + icon?: ReactNode; + iconPosition?: 'start' | 'end'; + fullWidth?: boolean; + children: ReactNode; +} + +const variantStyles: Record = { + primary: clsx( + 'bg-gradient-to-l from-teal-500 to-emerald-600 text-white', + 'hover:shadow-[0_0_20px_rgba(20,184,166,0.4)]', + 'active:from-teal-600 active:to-emerald-700', + 'disabled:from-slate-600 disabled:to-slate-700 disabled:text-slate-400', + ), + secondary: clsx( + 'border border-teal-500/50 text-teal-400 bg-transparent', + 'hover:bg-teal-500/10 hover:border-teal-400', + 'hover:shadow-[0_0_15px_rgba(20,184,166,0.2)]', + 'active:bg-teal-500/20', + 'disabled:border-slate-600 disabled:text-slate-500', + ), + ghost: clsx( + 'text-slate-300 bg-transparent', + 'hover:bg-white/5 hover:text-white', + 'active:bg-white/10', + 'disabled:text-slate-600', + ), + danger: clsx( + 'bg-gradient-to-l from-red-500 to-rose-600 text-white', + 'hover:shadow-[0_0_20px_rgba(239,68,68,0.4)]', + 'active:from-red-600 active:to-rose-700', + 'disabled:from-slate-600 disabled:to-slate-700 disabled:text-slate-400', + ), + gold: clsx( + 'bg-gradient-to-l from-amber-400 to-yellow-500 text-slate-900 font-semibold', + 'hover:shadow-[0_0_20px_rgba(251,191,36,0.4)]', + 'active:from-amber-500 active:to-yellow-600', + 'disabled:from-slate-600 disabled:to-slate-700 disabled:text-slate-400', + ), +}; + +const sizeStyles: Record = { + sm: 'h-8 text-sm ps-3 pe-3 gap-1.5 rounded-md', + md: 'h-10 text-base ps-5 pe-5 gap-2 rounded-lg', + lg: 'h-12 text-lg ps-7 pe-7 gap-2.5 rounded-xl', +}; + +const DealixButton = forwardRef( + ( + { + variant = 'primary', + size = 'md', + loading = false, + icon, + iconPosition = 'start', + fullWidth = false, + disabled, + children, + className, + ...props + }, + ref, + ) => { + const isDisabled = disabled || loading; + + return ( + + {loading ? ( + + ) : ( + icon && iconPosition === 'start' && {icon} + )} + {children} + {!loading && icon && iconPosition === 'end' && ( + {icon} + )} + + ); + }, +); + +DealixButton.displayName = 'DealixButton'; + +export { DealixButton as Button }; +export type { ButtonProps, ButtonVariant, ButtonSize }; diff --git a/salesflow-saas/frontend/src/components/ui/card.tsx b/salesflow-saas/frontend/src/components/ui/card.tsx new file mode 100644 index 00000000..0e9df010 --- /dev/null +++ b/salesflow-saas/frontend/src/components/ui/card.tsx @@ -0,0 +1,130 @@ +'use client'; + +import { forwardRef, type ReactNode, type HTMLAttributes } from 'react'; +import { motion, type HTMLMotionProps } from 'framer-motion'; +import { clsx } from 'clsx'; + +type CardVariant = 'default' | 'gradient' | 'elevated' | 'feature'; + +interface CardProps extends Omit, 'children'> { + variant?: CardVariant; + header?: ReactNode; + footer?: ReactNode; + badge?: ReactNode; + noPadding?: boolean; + children: ReactNode; +} + +const variantStyles: Record = { + default: clsx( + 'bg-white/5 backdrop-blur-xl', + 'border border-white/10', + ), + gradient: clsx( + 'bg-gradient-to-bl from-teal-500/10 via-slate-900/80 to-slate-900/90', + 'backdrop-blur-xl border border-teal-500/20', + ), + elevated: clsx( + 'bg-slate-800/80 backdrop-blur-xl', + 'border border-white/10', + 'shadow-xl shadow-black/20', + ), + feature: clsx( + 'bg-gradient-to-bl from-teal-500/15 via-emerald-500/5 to-transparent', + 'backdrop-blur-xl border border-teal-400/20', + ), +}; + +const Card = forwardRef( + ( + { + variant = 'default', + header, + footer, + badge, + noPadding = false, + children, + className, + ...props + }, + ref, + ) => { + return ( + + {badge && ( +
{badge}
+ )} + + {header && ( +
+ {header} +
+ )} + +
{children}
+ + {footer && ( +
+ {footer} +
+ )} +
+ ); + }, +); + +Card.displayName = 'Card'; + +interface CardTitleProps extends HTMLAttributes { + children: ReactNode; +} + +function CardTitle({ children, className, ...props }: CardTitleProps) { + return ( +

+ {children} +

+ ); +} + +function CardDescription({ children, className, ...props }: HTMLAttributes) { + return ( +

+ {children} +

+ ); +} + +export { Card, CardTitle, CardDescription }; +export type { CardProps, CardVariant };