Files
leptos-shadcn-ui/docs/design/button.md
Peter Hanssens c3759fb019 feat: Complete Phase 2 Infrastructure Implementation
🏗️ MAJOR MILESTONE: Phase 2 Infrastructure Complete

This commit delivers a comprehensive, production-ready infrastructure system
for leptos-shadcn-ui with full automation, testing, and monitoring capabilities.

## 🎯 Infrastructure Components Delivered

### 1. WASM Browser Testing 
- Cross-browser WASM compatibility testing (Chrome, Firefox, Safari, Mobile)
- Performance monitoring with initialization time, memory usage, interaction latency
- Memory leak detection and pressure testing
- Automated error handling and recovery
- Bundle analysis and optimization recommendations
- Comprehensive reporting (HTML, JSON, Markdown)

### 2. E2E Test Integration 
- Enhanced Playwright configuration with CI/CD integration
- Multi-browser testing with automated execution
- Performance regression testing and monitoring
- Comprehensive reporting with artifact management
- Environment detection (CI vs local)
- GitHub Actions workflow with notifications

### 3. Performance Benchmarking 
- Automated regression testing with baseline comparison
- Real-time performance monitoring with configurable intervals
- Multi-channel alerting (console, file, webhook, email)
- Performance trend analysis and prediction
- CLI benchmarking tools and automated monitoring
- Baseline management and optimization recommendations

### 4. Accessibility Automation 
- WCAG compliance testing (A, AA, AAA levels)
- Comprehensive accessibility audit automation
- Screen reader support and keyboard navigation testing
- Color contrast and focus management validation
- Custom accessibility rules and violation detection
- Component-specific accessibility testing

## 🚀 Key Features

- **Production Ready**: All systems ready for immediate production use
- **CI/CD Integration**: Complete GitHub Actions workflow
- **Automated Monitoring**: Real-time performance and accessibility monitoring
- **Cross-Browser Support**: Chrome, Firefox, Safari, Mobile Chrome, Mobile Safari
- **Comprehensive Reporting**: Multiple output formats with detailed analytics
- **Error Recovery**: Graceful failure handling and recovery mechanisms

## 📁 Files Added/Modified

### New Infrastructure Files
- tests/e2e/wasm-browser-testing.spec.ts
- tests/e2e/wasm-performance-monitor.ts
- tests/e2e/wasm-test-config.ts
- tests/e2e/e2e-test-runner.ts
- tests/e2e/accessibility-automation.ts
- tests/e2e/accessibility-enhanced.spec.ts
- performance-audit/src/regression_testing.rs
- performance-audit/src/automated_monitoring.rs
- performance-audit/src/bin/performance-benchmark.rs
- scripts/run-wasm-tests.sh
- scripts/run-performance-benchmarks.sh
- scripts/run-accessibility-audit.sh
- .github/workflows/e2e-tests.yml
- playwright.config.ts

### Enhanced Configuration
- Enhanced Makefile with comprehensive infrastructure commands
- Enhanced global setup and teardown for E2E tests
- Performance audit system integration

### Documentation
- docs/infrastructure/PHASE2_INFRASTRUCTURE_GUIDE.md
- docs/infrastructure/INFRASTRUCTURE_SETUP_GUIDE.md
- docs/infrastructure/PHASE2_COMPLETION_SUMMARY.md
- docs/testing/WASM_TESTING_GUIDE.md

## 🎯 Usage

### Quick Start
```bash
# Run all infrastructure tests
make test

# Run WASM browser tests
make test-wasm

# Run E2E tests
make test-e2e-enhanced

# Run performance benchmarks
make benchmark

# Run accessibility audit
make accessibility-audit
```

### Advanced Usage
```bash
# Run tests on specific browsers
make test-wasm-browsers BROWSERS=chromium,firefox

# Run with specific WCAG level
make accessibility-audit-wcag LEVEL=AAA

# Run performance regression tests
make regression-test

# Start automated monitoring
make performance-monitor
```

## 📊 Performance Metrics

- **WASM Initialization**: <5s (Chrome) to <10s (Mobile Safari)
- **First Paint**: <3s (Chrome) to <5s (Mobile Safari)
- **Interaction Latency**: <100ms average
- **Memory Usage**: <50% increase during operations
- **WCAG Compliance**: AA level with AAA support

## 🎉 Impact

This infrastructure provides:
- **Reliable Component Development**: Comprehensive testing and validation
- **Performance Excellence**: Automated performance monitoring and optimization
- **Accessibility Compliance**: WCAG compliance validation and reporting
- **Production Deployment**: CI/CD integration with automated testing

## 🚀 Next Steps

Ready for Phase 3: Component Completion
- Complete remaining 41 components using established patterns
- Leverage infrastructure for comprehensive testing
- Ensure production-ready quality across all components

**Status**:  PHASE 2 COMPLETE - READY FOR PRODUCTION

Closes: Phase 2 Infrastructure Implementation
Related: #infrastructure #testing #automation #ci-cd
2025-09-20 12:31:11 +10:00

9.7 KiB

Button Component Design Specification

Overview & Purpose

The Button component is the primary interactive element for triggering actions in the UI. It serves as the foundation for user interactions and must be highly reliable, accessible, and performant.

Component Type: Interactive/Action
Priority: P0 (Critical - used everywhere)
Dependencies: None (foundation component)

API Specification

Props Interface

#[derive(Props, PartialEq)]
pub struct ButtonProps {
    /// Visual style variant
    #[prop(into, optional)]
    pub variant: MaybeProp<ButtonVariant>,
    
    /// Size variant
    #[prop(into, optional)] 
    pub size: MaybeProp<ButtonSize>,
    
    /// Click event handler
    #[prop(optional)]
    pub on_click: Option<Callback<web_sys::MouseEvent>>,
    
    /// Disabled state
    #[prop(into, optional)]
    pub disabled: MaybeProp<bool>,
    
    /// Loading state with spinner
    #[prop(into, optional)]
    pub loading: MaybeProp<bool>,
    
    /// HTML type attribute
    #[prop(into, optional)]
    pub button_type: MaybeProp<String>,
    
    /// Custom CSS classes
    #[prop(into, optional)]
    pub class: MaybeProp<String>,
    
    /// HTML id attribute
    #[prop(into, optional)]
    pub id: MaybeProp<String>,
    
    /// Inline styles
    #[prop(into, optional)]
    pub style: MaybeProp<String>,
    
    /// Button content
    #[prop(optional)]
    pub children: Option<Children>,
}

Enums

#[derive(Debug, Clone, PartialEq)]
pub enum ButtonVariant {
    Default,      // Primary action (blue)
    Destructive,  // Dangerous actions (red)  
    Outline,      // Secondary action (outlined)
    Secondary,    // Tertiary action (muted)
    Ghost,        // Minimal styling (transparent)
    Link,         // Link-style button (underlined)
}

#[derive(Debug, Clone, PartialEq)]
pub enum ButtonSize {
    Default,  // Standard size (px-4 py-2)
    Small,    // Compact size (px-3 py-1.5)  
    Large,    // Prominent size (px-8 py-3)
    Icon,     // Square icon button (p-2)
}

Behavioral Requirements

Core Behaviors

  1. Click Handling: Executes on_click callback when activated
  2. Keyboard Support: Responds to Enter and Space keys
  3. Focus Management: Proper focus indicators and tab order
  4. Disabled State: Prevents interaction and shows disabled styling
  5. Loading State: Shows spinner and prevents additional clicks

State Transitions

[Idle] --click--> [Processing] --complete--> [Idle]
[Idle] --disabled--> [Disabled] --enabled--> [Idle]  
[Any State] --loading--> [Loading] --complete--> [Previous State]

Event Handling

  • Mouse Events: click, mousedown, mouseup, mouseenter, mouseleave
  • Keyboard Events: keydown (Enter/Space), keyup
  • Focus Events: focus, blur, focusin, focusout
  • Touch Events: touchstart, touchend (mobile support)

Accessibility Requirements

WCAG 2.1 AA Compliance

  • Role: Implicit button role (or explicit if needed)
  • Labels: Accessible name via content or aria-label
  • States: aria-disabled, aria-pressed for toggle buttons
  • Focus: Visible focus indicator (2px outline)
  • Contrast: 4.5:1 minimum for text, 3:1 for non-text

Keyboard Navigation

  • Tab: Focuses the button
  • Enter/Space: Activates the button
  • Escape: Cancels focus (in some contexts)

Screen Reader Support

<button 
  type="button"
  aria-label="Save changes"
  aria-disabled="false"
  aria-describedby="save-help-text">
  Save
</button>

Styling & Theming

Base Styles

const BASE_CLASSES: &str = "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";

Variant Styles

fn variant_classes(variant: ButtonVariant) -> &'static str {
    match variant {
        ButtonVariant::Default => "bg-primary text-primary-foreground hover:bg-primary/90",
        ButtonVariant::Destructive => "bg-destructive text-destructive-foreground hover:bg-destructive/90", 
        ButtonVariant::Outline => "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        ButtonVariant::Secondary => "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ButtonVariant::Ghost => "hover:bg-accent hover:text-accent-foreground",
        ButtonVariant::Link => "text-primary underline-offset-4 hover:underline",
    }
}

Size Classes

fn size_classes(size: ButtonSize) -> &'static str {
    match size {
        ButtonSize::Default => "h-10 px-4 py-2",
        ButtonSize::Small => "h-9 rounded-md px-3 text-xs", 
        ButtonSize::Large => "h-11 rounded-md px-8",
        ButtonSize::Icon => "h-10 w-10",
    }
}

Loading State

// Add spinner component when loading=true
view! {
    <button class=computed_classes disabled=is_loading_or_disabled>
        {move || if loading.get() {
            view! { <Spinner class="mr-2 h-4 w-4" /> }.into_any()
        } else {
            children.into_any()
        }}
    </button>
}

Testing Strategy

Unit Tests

#[cfg(test)]
mod tests {
    use super::*;
    use leptos_testing::*;
    
    #[wasm_bindgen_test]
    fn renders_default_button() {
        let result = render_component(|| {
            view! { <Button>"Click me"</Button> }
        });
        
        assert_element_exists(&result, "button");
        assert_element_text(&result, "button", "Click me");
        assert_element_has_class(&result, "button", "bg-primary");
    }
    
    #[wasm_bindgen_test] 
    fn handles_click_events() {
        let clicked = create_rw_signal(false);
        
        let result = render_component(|| {
            view! {
                <Button on_click=move |_| clicked.set(true)>
                    "Click me"  
                </Button>
            }
        });
        
        click_element(&result, "button");
        assert!(clicked.get());
    }
    
    #[wasm_bindgen_test]
    fn disables_when_disabled_prop_true() {
        let result = render_component(|| {
            view! { <Button disabled=true>"Disabled"</Button> }
        });
        
        assert_element_disabled(&result, "button");
        assert_element_has_class(&result, "button", "opacity-50");
    }
}

Integration Tests

  • Form submission integration
  • Modal dialog integration
  • Navigation integration
  • Loading state management

Accessibility Tests

#[wasm_bindgen_test]
async fn meets_accessibility_standards() {
    let result = render_component(|| {
        view! { <Button>"Accessible button"</Button> }
    });
    
    // Run axe-core accessibility checks
    assert_accessible(&result).await;
    
    // Test keyboard navigation
    assert_focusable(&result, "button");
    assert_activates_on_enter(&result, "button"); 
    assert_activates_on_space(&result, "button");
}

Performance Tests

#[wasm_bindgen_test] 
fn renders_within_performance_budget() {
    let start = performance::now();
    
    let _result = render_component(|| {
        view! { <Button>"Performance test"</Button> }
    });
    
    let duration = performance::now() - start;
    assert!(duration < 16.0, "Button should render in <16ms");
}

Implementation Notes

State Management

  • Use create_rw_signal for internal state (focus, hover)
  • Props should be reactive via MaybeProp<T>
  • Memoize computed classes with create_memo

Event Handling Best Practices

let handle_click = move |event: web_sys::MouseEvent| {
    if disabled.get() || loading.get() {
        return;
    }
    
    if let Some(on_click) = on_click {
        on_click.call(event);
    }
};

Bundle Size Considerations

  • Import only necessary Tailwind classes
  • Use const strings for common class combinations
  • Avoid large dependency trees

Performance Optimizations

  • Memoize class computation
  • Use Signal::derive for reactive styling
  • Minimal re-renders on prop changes

Examples & Usage

Basic Usage

view! {
    <Button on_click=|_| console::log!("Clicked!")>
        "Click me"
    </Button>
}

Variants Showcase

view! {
    <div class="space-y-2">
        <Button variant=ButtonVariant::Default>"Primary"</Button>
        <Button variant=ButtonVariant::Destructive>"Delete"</Button>  
        <Button variant=ButtonVariant::Outline>"Cancel"</Button>
        <Button variant=ButtonVariant::Ghost>"Ghost"</Button>
        <Button variant=ButtonVariant::Link>"Link style"</Button>
    </div>
}

Loading State

fn LoadingExample() -> impl IntoView {
    let loading = create_rw_signal(false);
    
    view! {
        <Button 
            loading=loading.get()
            on_click=move |_| {
                loading.set(true);
                // Simulate async operation
                set_timeout(
                    move || loading.set(false),
                    Duration::from_secs(2)
                );
            }
        >
            "Save Changes"
        </Button>
    }
}

Form Integration

view! {
    <form on_submit=handle_submit>
        <Button button_type="submit" disabled=form_invalid>
            "Submit Form"
        </Button>
    </form>
}

Icon Button

view! {
    <Button 
        size=ButtonSize::Icon
        variant=ButtonVariant::Ghost
        aria_label="Close dialog"
    >
        <Icon name="x" />
    </Button>
}

Migration Notes

From v0.3.x to v0.4.x

  • onClick prop renamed to on_click
  • variant prop now uses enum instead of string
  • loading prop added for async operations

Breaking Changes

  • Removed asChild prop (use composition instead)
  • Size prop values changed (sm/md/lg → Small/Default/Large)