Files
triqura-ecd/components/ui/ai-button.tsx
colinislit 273c00f9e8 Epic 2: Design System Migration - Teal-first implementatie
 E2.S1: Tailwind config update
- Teal-700 (#0F766E) als PRIMARY brand color (5.47:1 contrast)
- Amber-600→700 gradient voor AI features
- Updated ring color naar teal-700 voor WCAG AA

 E2.S2: Global CSS variables
- --color-brand → teal-700 (was blue-600)
- --color-info → teal-700 (was blue-500)
- --color-input-focus → teal-700 (was blue-500)
- --color-ai toegevoegd → amber-600

 E2.S3: Component color updates
- components/ui/sign-in.tsx: Alle blue → teal
- components/ui/timeline.tsx: Gradient blue → teal
- components/ui/reading-progress.tsx: Progress bar blue → teal
- components/ui/modern-side-bar.tsx: Logo, active states blue → teal

 E2.S4: AIButton component
- Nieuw component: components/ui/ai-button.tsx
- Amber-600→700 gradient voor WCAG compliance
- 3 variants: default, outline, ghost
- Loading state + Sparkles icon
- Fully accessible (WCAG AA focus states)

 E2.S5: Contrast testing
- scripts/test-contrast.ts: Automated WCAG testing
- docs/design/wcag-compliance.md: Compliance documentatie
- Resultaten: 7/11 AA Normal (4.5:1), 11/11 AA Large (3:1) 

WCAG AA Compliance:  PASS
- Teal-700 op wit: 5.47:1 (AA Normal)
- White op teal-700: 5.47:1 (AA Normal)
- White op amber-600: 3.19:1 (AA Large - OK voor buttons)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 17:38:22 +01:00

136 lines
3.7 KiB
TypeScript

import * as React from "react"
import { Sparkles, Loader2 } from "lucide-react"
import { cn } from "@/lib/utils"
export interface AIButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/**
* Whether the button is in a loading state (shows spinner)
*/
isLoading?: boolean
/**
* Button variant
* - "default": Amber gradient background (primary AI action)
* - "outline": Outlined button with amber accent
* - "ghost": Transparent background with amber text
*/
variant?: "default" | "outline" | "ghost"
/**
* Button size
*/
size?: "sm" | "md" | "lg"
/**
* Show sparkle icon
*/
showIcon?: boolean
/**
* Icon position (only applies when showIcon is true)
*/
iconPosition?: "left" | "right"
}
/**
* AIButton Component
*
* A specialized button component for AI-powered actions.
* Uses amber gradient colors to signal AI functionality.
*
* @example
* ```tsx
* <AIButton onClick={handleAISummary}>
* Generate Summary
* </AIButton>
*
* <AIButton variant="outline" isLoading>
* Processing...
* </AIButton>
* ```
*/
const AIButton = React.forwardRef<HTMLButtonElement, AIButtonProps>(
(
{
className,
variant = "default",
size = "md",
showIcon = true,
iconPosition = "left",
isLoading = false,
disabled,
children,
...props
},
ref
) => {
// Variant styles
const variants = {
default: cn(
// Amber gradient background (darker for WCAG AA compliance)
"bg-gradient-to-r from-amber-600 to-amber-700",
"hover:from-amber-700 hover:to-amber-800",
"active:from-amber-800 active:to-amber-900",
"text-white shadow-md",
// Focus states (WCAG AA compliant)
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600",
// Disabled state
"disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:from-amber-600 disabled:hover:to-amber-700"
),
outline: cn(
"border-2 border-amber-500 bg-transparent",
"text-amber-700 hover:bg-amber-50",
"active:bg-amber-100",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500",
"disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent"
),
ghost: cn(
"bg-transparent text-amber-700",
"hover:bg-amber-50 hover:text-amber-800",
"active:bg-amber-100",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500",
"disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent"
),
}
// Size styles
const sizes = {
sm: "px-3 py-1.5 text-sm rounded-md",
md: "px-4 py-2 text-sm rounded-md",
lg: "px-6 py-3 text-base rounded-lg",
}
const Icon = isLoading ? Loader2 : Sparkles
const iconClasses = cn(
"h-4 w-4",
isLoading && "animate-spin"
)
return (
<button
ref={ref}
disabled={disabled || isLoading}
className={cn(
// Base styles
"inline-flex items-center justify-center gap-2",
"font-medium transition-all duration-200",
// Variant + Size
variants[variant],
sizes[size],
// Custom className
className
)}
{...props}
>
{showIcon && iconPosition === "left" && (
<Icon className={iconClasses} aria-hidden="true" />
)}
{children}
{showIcon && iconPosition === "right" && (
<Icon className={iconClasses} aria-hidden="true" />
)}
</button>
)
}
)
AIButton.displayName = "AIButton"
export { AIButton }