"use client"; // @ts-nocheck - pre-existing legacy design token (bare 'colors'/'spacing' etc) issues in shared components. import Link from "next/link"; import { forwardRef, ButtonHTMLAttributes, AnchorHTMLAttributes } from "react"; import { clsx } from "clsx"; import { designSystem, colors, typography, spacing } from "@/lib/design-system"; import { Loader2 } from "lucide-react"; const { semanticColors } = designSystem; // colors/radius etc reserved for variants // ============================================================================= // Button Types and Interfaces // ============================================================================= export type ButtonVariant = | "primary" | "secondary" | "ghost" | "outline" | "danger" | "success" | "emerald" | "violet" | "amber" | "blue"; export type ButtonSize = "sm" | "md" | "lg" | "xl" | "icon"; export interface ButtonProps extends ButtonHTMLAttributes { variant?: ButtonVariant; size?: ButtonSize; loading?: boolean; fullWidth?: boolean; leftIcon?: React.ReactNode; rightIcon?: React.ReactNode; asLink?: boolean; href?: string; } // ============================================================================= // Style Mappings // ============================================================================= const variantStyles = { primary: { bg: colors.accentPrimary, text: colors.white, hoverBg: colors.accentPrimaryHover, border: "none", activeBg: colors.accentPrimary, }, secondary: { bg: colors.bgSurface, text: colors.textPrimary, hoverBg: colors.bgElevated, border: colors.borderMedium, hoverBorder: colors.borderMedium, activeBg: colors.bgElevated, }, ghost: { bg: "transparent", text: colors.textSecondary, hoverBg: colors.bgGlass, border: "none", hoverBorder: "none", hoverText: colors.textPrimary, activeBg: colors.bgGlass, }, outline: { bg: "transparent", text: colors.textPrimary, hoverBg: colors.bgGlass, border: colors.borderSubtle, hoverBorder: colors.borderMedium, activeBg: colors.bgGlass, }, danger: { bg: colors.accentRed, text: colors.white, hoverBg: "#F87171", // lighter red border: "none", activeBg: colors.accentRed, }, success: { bg: colors.accentEmerald, text: colors.white, hoverBg: "#10B981", // same or lighter border: "none", activeBg: colors.accentEmerald, }, // Accent variants (use design system tokens) emerald: { bg: colors.accentEmerald, text: colors.white, hoverBg: "#34D399", // lighter emerald border: "none", activeBg: colors.accentEmerald, }, violet: { bg: semanticColors.info, text: colors.white, hoverBg: colors.accentViolet, border: "none", activeBg: semanticColors.info, }, amber: { bg: colors.accentPrimary, text: colors.white, hoverBg: colors.accentPrimaryHover, border: "none", activeBg: colors.accentPrimary, }, blue: { bg: colors.accentBlue, text: colors.white, hoverBg: "#93C5FD", // lighter blue border: "none", activeBg: colors.accentBlue, }, } as const; const sizeStyles = { sm: { height: "32px", padding: "0 12px", fontSize: typography.captionSmall.size, fontWeight: typography.captionSmall.weight, gap: "4px", width: undefined, }, md: { height: "40px", padding: "0 16px", fontSize: typography.button.size, fontWeight: typography.button.weight, gap: spacing.button.gap, width: undefined, }, lg: { height: "44px", padding: "0 24px", fontSize: typography.buttonPrimary.size, fontWeight: typography.buttonPrimary.weight, gap: spacing.button.gap, width: undefined, }, xl: { height: "48px", padding: "0 32px", fontSize: "15px", fontWeight: "600" as const, gap: "10px", width: undefined, }, icon: { height: "40px", width: "40px", padding: "0", fontSize: "20px", gap: "0", }, } as const; // Helper to extract Link-compatible props safely function extractLinkProps(props: ButtonProps): Partial> { const { type: _type, form: _form, formAction: _formAction, formEncType: _formEncType, formMethod: _formMethod, formNoValidate: _formNoValidate, formTarget: _formTarget, name: _name, value: _value, autoFocus: _autoFocus, tabIndex: _tabIndex, ...safeProps } = props; return safeProps as Partial>; } // ============================================================================= // SharedButton Component // ============================================================================= export const SharedButton = forwardRef( ( { variant = "primary", size = "md", loading = false, fullWidth = false, leftIcon, rightIcon, asLink = false, href, disabled, className, children, ...props }, ref, ) => { const styles = variantStyles[variant]; const sizes = sizeStyles[size]; const baseClasses = clsx( "relative z-10 pointer-events-auto", "inline-flex items-center justify-center", "border", "font-sans", "transition-all", "duration-150", "focus:outline-none", "focus-visible:ring-2", "focus-visible:ring-[var(--accent-primary)]", "focus-visible:ring-offset-2", "focus-visible:ring-offset-[var(--bg-root)]", fullWidth && "w-full", className, ); const sizeClasses = clsx( `h-[${sizes.height}]`, sizes.width && `w-[${sizes.width}]`, sizes.padding && `px-[${sizes.padding.split(" ")[0].replace("px", "")}px]`, sizes.fontSize && `text-[${sizes.fontSize}]`, "fontWeight" in sizes && (sizes as { fontWeight?: string }).fontWeight && `font-[${(sizes as { fontWeight?: string }).fontWeight}]`, sizes.gap && `gap-[${sizes.gap}]`, ); const variantClasses = clsx( !styles.bg || styles.bg === "transparent" ? "" : `bg-[${styles.bg}]`, styles.text && `text-[${styles.text}]`, !styles.border || styles.border === "none" ? "border-transparent" : `border-[${styles.border}]`, // Hover states styles.hoverBg && `hover:bg-[${styles.hoverBg}]`, ("hoverBorder" in styles && (styles as { hoverBorder?: string }).hoverBorder) && `hover:border-[${(styles as { hoverBorder?: string }).hoverBorder}]`, ("hoverText" in styles && (styles as { hoverText?: string }).hoverText) && `hover:text-[${(styles as { hoverText?: string }).hoverText}]`, // Active states styles.activeBg && `active:bg-[${styles.activeBg}]`, // Disabled states "disabled:cursor-not-allowed", "disabled:opacity-40", "disabled:transform-none", ); const allClasses = clsx(baseClasses, sizeClasses, variantClasses); // Loading state if (loading) { return ( ); } // Render as Link if asLink or href is provided if (asLink || href) { const linkProps = asLink ? { href: href || "#" } : {}; const finalHref = href || "#"; // Filter out button-specific props that Link doesn't accept const { type: _type, onClick: buttonOnClick, ...restProps } = props; // Extract only Link-compatible props const linkCompatibleProps = extractLinkProps({ ...restProps } as ButtonProps); // Safe onClick handler with proper typing const handleClick = (e: React.MouseEvent) => { if (disabled) { e.preventDefault(); return; } buttonOnClick?.(e as unknown as React.MouseEvent); }; return ( } href={finalHref} className={clsx(allClasses, "cursor-pointer", disabled && "pointer-events-none opacity-40")} {...linkCompatibleProps} {...linkProps} onClick={handleClick} > {leftIcon && } {children} {rightIcon && } ); } // Render as button return ( ); }, ); SharedButton.displayName = "SharedButton"; // ============================================================================= // Convenience Button Components (matching existing patterns) // ============================================================================= // Button with consistent primary style export function BtnPrimary({ children, onClick, loading = false, className, ...props }: ButtonProps) { return ( {children} ); } // Button with secondary style export function BtnSecondary({ children, onClick, loading = false, className, ...props }: ButtonProps) { return ( {children} ); } // Ghost button export function BtnGhost({ children, onClick, className, ...props }: ButtonProps) { return ( {children} ); } // Action button (for teach page pattern) export function ActionButton({ children, primary = false, onClick, className, ...props }: ButtonProps & { primary?: boolean }) { return ( {children} ); }