{"version":3,"file":"Button.mjs","sources":["../../../../src/lib/ui/inputs/Button.tsx"],"sourcesContent":["/**\n * @file Button Component\n * @description Versatile button component with multiple variants, sizes, and states\n * Uses theme tokens for consistent styling and accessibility\n */\n\nimport React, {\n  type ButtonHTMLAttributes,\n  type CSSProperties,\n  forwardRef,\n  memo,\n  useMemo,\n} from 'react';\nimport { colorTokens, tokens } from '@/lib/theme';\nimport { Spinner } from '@/lib/ui';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Button variant options\n */\nexport type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive';\n\n/**\n * Button size options\n */\nexport type ButtonSize = 'sm' | 'md' | 'lg';\n\n/**\n * Button props\n */\nexport interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'style'> {\n  /** Button variant */\n  variant?: ButtonVariant;\n\n  /** Button size */\n  size?: ButtonSize;\n\n  /** Full width button */\n  fullWidth?: boolean;\n\n  /** Loading state */\n  isLoading?: boolean;\n\n  /** Left icon */\n  leftIcon?: React.ReactNode;\n\n  /** Right icon */\n  rightIcon?: React.ReactNode;\n\n  /** Custom styles (merged with base styles) */\n  style?: CSSProperties;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Size configurations\n */\nconst sizeConfig: Record<\n  ButtonSize,\n  {\n    padding: string;\n    fontSize: string;\n    minHeight: string;\n    iconSize: number;\n    gap: string;\n  }\n> = {\n  sm: {\n    padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n    fontSize: tokens.fontSize.sm,\n    minHeight: '2rem',\n    iconSize: 14,\n    gap: tokens.spacing.xs,\n  },\n  md: {\n    padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,\n    fontSize: tokens.fontSize.sm,\n    minHeight: '2.5rem',\n    iconSize: 16,\n    gap: tokens.spacing.sm,\n  },\n  lg: {\n    padding: `${tokens.spacing.sm} ${tokens.spacing.lg}`,\n    fontSize: tokens.fontSize.base,\n    minHeight: '3rem',\n    iconSize: 20,\n    gap: tokens.spacing.sm,\n  },\n};\n\n/**\n * Variant styles\n */\nconst variantStyles: Record<\n  ButtonVariant,\n  {\n    background: string;\n    color: string;\n    border: string;\n    hoverBackground: string;\n    hoverBorder: string;\n    activeBackground: string;\n  }\n> = {\n  primary: {\n    background: colorTokens.primary.default,\n    color: colorTokens.neutral.white,\n    border: 'transparent',\n    hoverBackground: colorTokens.primary.hover,\n    hoverBorder: 'transparent',\n    activeBackground: colorTokens.primary.active,\n  },\n  secondary: {\n    background: colorTokens.secondary.light,\n    color: colorTokens.text.primary,\n    border: 'transparent',\n    hoverBackground: colorTokens.secondary.default,\n    hoverBorder: 'transparent',\n    activeBackground: colorTokens.secondary.hover,\n  },\n  outline: {\n    background: 'transparent',\n    color: colorTokens.text.primary,\n    border: colorTokens.border.emphasis,\n    hoverBackground: colorTokens.interactive.hover,\n    hoverBorder: colorTokens.border.emphasis,\n    activeBackground: colorTokens.interactive.active,\n  },\n  ghost: {\n    background: 'transparent',\n    color: colorTokens.text.secondary,\n    border: 'transparent',\n    hoverBackground: colorTokens.interactive.hover,\n    hoverBorder: 'transparent',\n    activeBackground: colorTokens.interactive.active,\n  },\n  destructive: {\n    background: colorTokens.error.default,\n    color: colorTokens.neutral.white,\n    border: 'transparent',\n    hoverBackground: colorTokens.error.hover,\n    hoverBorder: 'transparent',\n    activeBackground: colorTokens.error.hover,\n  },\n};\n\n// Static base styles\nconst baseButtonStyle: CSSProperties = {\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  fontWeight: tokens.fontWeight.medium,\n  borderRadius: tokens.radius.md,\n  borderWidth: '1px',\n  borderStyle: 'solid',\n  cursor: 'pointer',\n  transition: 'all 150ms ease-in-out',\n  textDecoration: 'none',\n  lineHeight: 1.5,\n  whiteSpace: 'nowrap',\n  userSelect: 'none',\n};\n\nconst disabledStyle: CSSProperties = {\n  opacity: 0.5,\n  cursor: 'not-allowed',\n  pointerEvents: 'none',\n};\n\nconst iconContainerStyle: CSSProperties = {\n  display: 'inline-flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  flexShrink: 0,\n};\n\nconst loadingContainerStyle: CSSProperties = {\n  position: 'relative',\n};\n\nconst loadingContentStyle: CSSProperties = {\n  visibility: 'hidden',\n};\n\nconst loadingSpinnerStyle: CSSProperties = {\n  position: 'absolute',\n  top: '50%',\n  left: '50%',\n  transform: 'translate(-50%, -50%)',\n};\n\n// Focus visible styles injected once\nconst buttonFocusStyles = `\n  button:focus {\n    outline: none;\n  }\n\n  button:focus-visible {\n    outline: 2px solid ${colorTokens.border.focus};\n    outline-offset: 2px;\n  }\n`;\n\n// Inject focus styles once at module load (SSR-safe)\nif (typeof document !== 'undefined') {\n  const styleId = 'button-focus-styles';\n  if (!document.getElementById(styleId)) {\n    const styleElement = document.createElement('style');\n    styleElement.id = styleId;\n    styleElement.textContent = buttonFocusStyles;\n    document.head.appendChild(styleElement);\n  }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Button component with forwardRef for ref forwarding\n */\nexport const Button = memo(\n  forwardRef<HTMLButtonElement, ButtonProps>(\n    (\n      {\n        variant = 'primary',\n        size = 'md',\n        fullWidth = false,\n        isLoading = false,\n        leftIcon,\n        rightIcon,\n        disabled,\n        children,\n        className,\n        style,\n        type = 'button',\n        'aria-label': ariaLabel,\n        ...props\n      },\n      ref\n    ): React.ReactElement => {\n      const isDisabled = (disabled ?? false) || isLoading;\n      const sizeProps = sizeConfig[size];\n      const variantProps = variantStyles[variant];\n\n      // Memoize computed button style\n      const buttonStyle = useMemo((): CSSProperties => {\n        return {\n          ...baseButtonStyle,\n          padding: sizeProps.padding,\n          fontSize: sizeProps.fontSize,\n          minHeight: sizeProps.minHeight,\n          gap: sizeProps.gap,\n          backgroundColor: variantProps.background,\n          color: variantProps.color,\n          borderColor: variantProps.border,\n          width: fullWidth ? '100%' : undefined,\n          ...(isDisabled ? disabledStyle : {}),\n          ...style,\n        };\n      }, [sizeProps, variantProps, fullWidth, isDisabled, style]);\n\n      // Determine spinner variant based on button variant\n      const spinnerVariant =\n        variant === 'primary' || variant === 'destructive' ? 'white' : 'primary';\n      const spinnerSize = size === 'lg' ? 'md' : 'sm';\n\n      // Render icon with proper sizing\n      const renderIcon = (\n        icon: React.ReactNode,\n        position: 'left' | 'right'\n      ): React.ReactElement | null => {\n        if (icon === null || icon === undefined) return null;\n\n        return (\n          <span\n            style={{\n              ...iconContainerStyle,\n              marginRight:\n                position === 'left' && children !== null && children !== undefined\n                  ? sizeProps.gap\n                  : undefined,\n              marginLeft:\n                position === 'right' && children !== null && children !== undefined\n                  ? sizeProps.gap\n                  : undefined,\n            }}\n            aria-hidden=\"true\"\n          >\n            {icon}\n          </span>\n        );\n      };\n\n      // Content rendering\n      const content = (\n        <>\n          {leftIcon !== null && leftIcon !== undefined && renderIcon(leftIcon, 'left')}\n          {children}\n          {rightIcon !== null && rightIcon !== undefined && renderIcon(rightIcon, 'right')}\n        </>\n      );\n\n      return (\n        <button\n          ref={ref}\n          type={type}\n          className={className}\n          style={buttonStyle}\n          disabled={isDisabled}\n          aria-disabled={isDisabled}\n          aria-busy={isLoading}\n          aria-label={(() => {\n            if (ariaLabel !== undefined && ariaLabel !== '') return ariaLabel;\n            if (isLoading) return 'Loading...';\n            return undefined;\n          })()}\n          {...props}\n        >\n          {isLoading ? (\n            <span style={loadingContainerStyle}>\n              <span style={loadingContentStyle}>{content}</span>\n              <span style={loadingSpinnerStyle}>\n                <Spinner size={spinnerSize} variant={spinnerVariant} label=\"Loading\" />\n              </span>\n            </span>\n          ) : (\n            content\n          )}\n        </button>\n      );\n    }\n  )\n);\n\nButton.displayName = 'Button';\n\n// ============================================================================\n// Additional Button Variants\n// ============================================================================\n\n/**\n * Icon-only button component\n */\nexport interface IconButtonProps extends Omit<ButtonProps, 'leftIcon' | 'rightIcon' | 'children'> {\n  /** Icon to display */\n  icon: React.ReactNode;\n\n  /** Accessible label (required for icon-only buttons) */\n  'aria-label': string;\n}\n\nexport const IconButton = memo(\n  forwardRef<HTMLButtonElement, IconButtonProps>(\n    ({ icon, size = 'md', style, ...props }, ref): React.ReactElement => {\n      // Square padding for icon buttons\n      const iconButtonStyle: CSSProperties = {\n        ...style,\n        padding: size === 'sm' ? tokens.spacing.xs : tokens.spacing.sm,\n        aspectRatio: '1',\n      };\n\n      return (\n        <Button ref={ref} size={size} style={iconButtonStyle} {...props}>\n          <span style={iconContainerStyle} aria-hidden=\"true\">\n            {icon}\n          </span>\n        </Button>\n      );\n    }\n  )\n);\n\nIconButton.displayName = 'IconButton';\n\n/**\n * Button group component for grouping related buttons\n */\nexport interface ButtonGroupProps {\n  /** Button group children */\n  children: React.ReactNode;\n\n  /** Direction of button group */\n  direction?: 'horizontal' | 'vertical';\n\n  /** Gap between buttons */\n  gap?: 'sm' | 'md' | 'lg';\n\n  /** Additional class name */\n  className?: string;\n\n  /** Custom styles */\n  style?: CSSProperties;\n}\n\nconst gapMap: Record<'sm' | 'md' | 'lg', string> = {\n  sm: tokens.spacing.xs,\n  md: tokens.spacing.sm,\n  lg: tokens.spacing.md,\n};\n\nexport const ButtonGroup = memo(\n  ({\n    children,\n    direction = 'horizontal',\n    gap = 'sm',\n    className,\n    style,\n  }: ButtonGroupProps): React.ReactElement => {\n    const groupStyle = useMemo(\n      (): CSSProperties => ({\n        display: 'flex',\n        flexDirection: direction === 'vertical' ? 'column' : 'row',\n        gap: gapMap[gap],\n        ...style,\n      }),\n      [direction, gap, style]\n    );\n\n    return (\n      <div className={className} style={groupStyle} role=\"group\">\n        {children}\n      </div>\n    );\n  }\n);\n\nButtonGroup.displayName = 'ButtonGroup';\n"],"names":["sizeConfig","tokens","variantStyles","colorTokens","baseButtonStyle","disabledStyle","iconContainerStyle","loadingContainerStyle","loadingContentStyle","loadingSpinnerStyle","buttonFocusStyles","styleId","styleElement","Button","memo","forwardRef","variant","size","fullWidth","isLoading","leftIcon","rightIcon","disabled","children","className","style","type","ariaLabel","props","ref","isDisabled","sizeProps","variantProps","buttonStyle","useMemo","spinnerVariant","spinnerSize","renderIcon","icon","position","jsx","content","jsxs","Fragment","Spinner","IconButton","iconButtonStyle","gapMap","ButtonGroup","direction","gap","groupStyle"],"mappings":";;;;AA+DA,MAAMA,IASF;AAAA,EACF,IAAI;AAAA,IACF,SAAS,GAAGC,EAAO,QAAQ,EAAE,IAAIA,EAAO,QAAQ,EAAE;AAAA,IAClD,UAAUA,EAAO,SAAS;AAAA,IAC1B,WAAW;AAAA,IACX,UAAU;AAAA,IACV,KAAKA,EAAO,QAAQ;AAAA,EAAA;AAAA,EAEtB,IAAI;AAAA,IACF,SAAS,GAAGA,EAAO,QAAQ,EAAE,IAAIA,EAAO,QAAQ,EAAE;AAAA,IAClD,UAAUA,EAAO,SAAS;AAAA,IAC1B,WAAW;AAAA,IACX,UAAU;AAAA,IACV,KAAKA,EAAO,QAAQ;AAAA,EAAA;AAAA,EAEtB,IAAI;AAAA,IACF,SAAS,GAAGA,EAAO,QAAQ,EAAE,IAAIA,EAAO,QAAQ,EAAE;AAAA,IAClD,UAAUA,EAAO,SAAS;AAAA,IAC1B,WAAW;AAAA,IACX,UAAU;AAAA,IACV,KAAKA,EAAO,QAAQ;AAAA,EAAA;AAExB,GAKMC,IAUF;AAAA,EACF,SAAS;AAAA,IACP,YAAYC,EAAY,QAAQ;AAAA,IAChC,OAAOA,EAAY,QAAQ;AAAA,IAC3B,QAAQ;AAAA,IACR,iBAAiBA,EAAY,QAAQ;AAAA,IACrC,aAAa;AAAA,IACb,kBAAkBA,EAAY,QAAQ;AAAA,EAAA;AAAA,EAExC,WAAW;AAAA,IACT,YAAYA,EAAY,UAAU;AAAA,IAClC,OAAOA,EAAY,KAAK;AAAA,IACxB,QAAQ;AAAA,IACR,iBAAiBA,EAAY,UAAU;AAAA,IACvC,aAAa;AAAA,IACb,kBAAkBA,EAAY,UAAU;AAAA,EAAA;AAAA,EAE1C,SAAS;AAAA,IACP,YAAY;AAAA,IACZ,OAAOA,EAAY,KAAK;AAAA,IACxB,QAAQA,EAAY,OAAO;AAAA,IAC3B,iBAAiBA,EAAY,YAAY;AAAA,IACzC,aAAaA,EAAY,OAAO;AAAA,IAChC,kBAAkBA,EAAY,YAAY;AAAA,EAAA;AAAA,EAE5C,OAAO;AAAA,IACL,YAAY;AAAA,IACZ,OAAOA,EAAY,KAAK;AAAA,IACxB,QAAQ;AAAA,IACR,iBAAiBA,EAAY,YAAY;AAAA,IACzC,aAAa;AAAA,IACb,kBAAkBA,EAAY,YAAY;AAAA,EAAA;AAAA,EAE5C,aAAa;AAAA,IACX,YAAYA,EAAY,MAAM;AAAA,IAC9B,OAAOA,EAAY,QAAQ;AAAA,IAC3B,QAAQ;AAAA,IACR,iBAAiBA,EAAY,MAAM;AAAA,IACnC,aAAa;AAAA,IACb,kBAAkBA,EAAY,MAAM;AAAA,EAAA;AAExC,GAGMC,IAAiC;AAAA,EACrC,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,YAAYH,EAAO,WAAW;AAAA,EAC9B,cAAcA,EAAO,OAAO;AAAA,EAC5B,aAAa;AAAA,EACb,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AACd,GAEMI,IAA+B;AAAA,EACnC,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,eAAe;AACjB,GAEMC,IAAoC;AAAA,EACxC,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,YAAY;AACd,GAEMC,IAAuC;AAAA,EAC3C,UAAU;AACZ,GAEMC,IAAqC;AAAA,EACzC,YAAY;AACd,GAEMC,IAAqC;AAAA,EACzC,UAAU;AAAA,EACV,KAAK;AAAA,EACL,MAAM;AAAA,EACN,WAAW;AACb,GAGMC,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAMDP,EAAY,OAAO,KAAK;AAAA;AAAA;AAAA;AAMjD,IAAI,OAAO,WAAa,KAAa;AACnC,QAAMQ,IAAU;AAChB,MAAI,CAAC,SAAS,eAAeA,CAAO,GAAG;AACrC,UAAMC,IAAe,SAAS,cAAc,OAAO;AACnD,IAAAA,EAAa,KAAKD,GAClBC,EAAa,cAAcF,GAC3B,SAAS,KAAK,YAAYE,CAAY;AAAA,EACxC;AACF;AASO,MAAMC,IAASC;AAAA,EACpBC;AAAA,IACE,CACE;AAAA,MACE,SAAAC,IAAU;AAAA,MACV,MAAAC,IAAO;AAAA,MACP,WAAAC,IAAY;AAAA,MACZ,WAAAC,IAAY;AAAA,MACZ,UAAAC;AAAA,MACA,WAAAC;AAAA,MACA,UAAAC;AAAA,MACA,UAAAC;AAAA,MACA,WAAAC;AAAA,MACA,OAAAC;AAAA,MACA,MAAAC,IAAO;AAAA,MACP,cAAcC;AAAA,MACd,GAAGC;AAAA,IAAA,GAELC,MACuB;AACvB,YAAMC,KAAcR,KAAY,OAAUH,GACpCY,IAAY/B,EAAWiB,CAAI,GAC3Be,IAAe9B,EAAcc,CAAO,GAGpCiB,IAAcC,EAAQ,OACnB;AAAA,QACL,GAAG9B;AAAA,QACH,SAAS2B,EAAU;AAAA,QACnB,UAAUA,EAAU;AAAA,QACpB,WAAWA,EAAU;AAAA,QACrB,KAAKA,EAAU;AAAA,QACf,iBAAiBC,EAAa;AAAA,QAC9B,OAAOA,EAAa;AAAA,QACpB,aAAaA,EAAa;AAAA,QAC1B,OAAOd,IAAY,SAAS;AAAA,QAC5B,GAAIY,IAAazB,IAAgB,CAAA;AAAA,QACjC,GAAGoB;AAAA,MAAA,IAEJ,CAACM,GAAWC,GAAcd,GAAWY,GAAYL,CAAK,CAAC,GAGpDU,IACJnB,MAAY,aAAaA,MAAY,gBAAgB,UAAU,WAC3DoB,IAAcnB,MAAS,OAAO,OAAO,MAGrCoB,IAAa,CACjBC,GACAC,MAEID,KAAS,OAAmC,OAG9C,gBAAAE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,OAAO;AAAA,YACL,GAAGlC;AAAA,YACH,aACEiC,MAAa,UAAUhB,MAAa,QAAQA,MAAa,SACrDQ,EAAU,MACV;AAAA,YACN,YACEQ,MAAa,WAAWhB,MAAa,QAAQA,MAAa,SACtDQ,EAAU,MACV;AAAA,UAAA;AAAA,UAER,eAAY;AAAA,UAEX,UAAAO;AAAA,QAAA;AAAA,MAAA,GAMDG,IACJ,gBAAAC,EAAAC,GAAA,EACG,UAAA;AAAA,QAAAvB,KAAa,QAAkCiB,EAAWjB,GAAU,MAAM;AAAA,QAC1EG;AAAA,QACAF,KAAc,QAAmCgB,EAAWhB,GAAW,OAAO;AAAA,MAAA,GACjF;AAGF,aACE,gBAAAmB;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAAX;AAAA,UACA,MAAAH;AAAA,UACA,WAAAF;AAAA,UACA,OAAOS;AAAA,UACP,UAAUH;AAAA,UACV,iBAAeA;AAAA,UACf,aAAWX;AAAA,UACX,eAAa,MAAM;AACjB,gBAAIQ,MAAc,UAAaA,MAAc,GAAI,QAAOA;AACxD,gBAAIR,EAAW,QAAO;AAAA,UAExB,GAAA;AAAA,UACC,GAAGS;AAAA,UAEH,UAAAT,IACC,gBAAAuB,EAAC,QAAA,EAAK,OAAOnC,GACX,UAAA;AAAA,YAAA,gBAAAiC,EAAC,QAAA,EAAK,OAAOhC,GAAsB,UAAAiC,GAAQ;AAAA,YAC3C,gBAAAD,EAAC,QAAA,EAAK,OAAO/B,GACX,UAAA,gBAAA+B,EAACI,GAAA,EAAQ,MAAMR,GAAa,SAASD,GAAgB,OAAM,UAAA,CAAU,EAAA,CACvE;AAAA,UAAA,EAAA,CACF,IAEAM;AAAA,QAAA;AAAA,MAAA;AAAA,IAIR;AAAA,EAAA;AAEJ;AAEA5B,EAAO,cAAc;AAiBd,MAAMgC,IAAa/B;AAAA,EACxBC;AAAA,IACE,CAAC,EAAE,MAAAuB,GAAM,MAAArB,IAAO,MAAM,OAAAQ,GAAO,GAAGG,EAAA,GAASC,MAA4B;AAEnE,YAAMiB,IAAiC;AAAA,QACrC,GAAGrB;AAAA,QACH,SAASR,MAAS,OAAOhB,EAAO,QAAQ,KAAKA,EAAO,QAAQ;AAAA,QAC5D,aAAa;AAAA,MAAA;AAGf,aACE,gBAAAuC,EAAC3B,GAAA,EAAO,KAAAgB,GAAU,MAAAZ,GAAY,OAAO6B,GAAkB,GAAGlB,GACxD,UAAA,gBAAAY,EAAC,UAAK,OAAOlC,GAAoB,eAAY,QAC1C,aACH,GACF;AAAA,IAEJ;AAAA,EAAA;AAEJ;AAEAuC,EAAW,cAAc;AAsBzB,MAAME,IAA6C;AAAA,EACjD,IAAI9C,EAAO,QAAQ;AAAA,EACnB,IAAIA,EAAO,QAAQ;AAAA,EACnB,IAAIA,EAAO,QAAQ;AACrB,GAEa+C,IAAclC;AAAA,EACzB,CAAC;AAAA,IACC,UAAAS;AAAA,IACA,WAAA0B,IAAY;AAAA,IACZ,KAAAC,IAAM;AAAA,IACN,WAAA1B;AAAA,IACA,OAAAC;AAAA,EAAA,MAC0C;AAC1C,UAAM0B,IAAajB;AAAA,MACjB,OAAsB;AAAA,QACpB,SAAS;AAAA,QACT,eAAee,MAAc,aAAa,WAAW;AAAA,QACrD,KAAKF,EAAOG,CAAG;AAAA,QACf,GAAGzB;AAAA,MAAA;AAAA,MAEL,CAACwB,GAAWC,GAAKzB,CAAK;AAAA,IAAA;AAGxB,6BACG,OAAA,EAAI,WAAAD,GAAsB,OAAO2B,GAAY,MAAK,SAChD,UAAA5B,GACH;AAAA,EAEJ;AACF;AAEAyB,EAAY,cAAc;"}