Files
leptos-shadcn-ui/docs/remediation/components/card/REMEDIATION_PLAN.md
Peter Hanssens ec459d142c 🔧 Complete refactoring implementation for v0.9.0
- All 9 components fully refactored with modular architecture
- 45+ test modules created and organized
- File size compliance achieved (99% reduction)
- Enterprise-grade code quality implemented
- All compilation issues resolved

Ready for v0.9.0 release publication!
2025-09-22 13:06:22 +10:00

19 KiB

🔧 Card Component Remediation Plan

Priority 1: Enhanced Card Component - Immediate Action Required

🚨 Current Issues Summary

The Card component has a good foundation but needs enhancement for production use:

  • Basic structure exists and is well-implemented
  • CSS class constants properly defined
  • ⚠️ ~40% test coverage (estimated)
  • Missing accessibility features (ARIA attributes, semantic structure)
  • No interactive card functionality
  • Missing card variants (destructive, warning, success)
  • No comprehensive integration tests

🎯 Remediation Strategy

Phase 1: Accessibility & Semantic Structure (Week 1)

Day 1-2: Add ARIA Attributes and Semantic Structure

Current Problem: Card lacks proper ARIA attributes and semantic HTML structure

Target Implementation:

// Enhanced Card with ARIA attributes
#[component]
pub fn Card(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] id: MaybeProp<String>,
    #[prop(into, optional)] style: Signal<Style>,
    #[prop(into, optional)] variant: MaybeProp<CardVariant>,
    #[prop(into, optional)] interactive: Signal<bool>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    let computed_class = Signal::derive(move || {
        let base_class = CARD_CLASS;
        let variant_class = match variant.get() {
            Some(CardVariant::Destructive) => " border-destructive bg-destructive/5",
            Some(CardVariant::Warning) => " border-warning bg-warning/5",
            Some(CardVariant::Success) => " border-success bg-success/5",
            _ => "",
        };
        let interactive_class = if interactive.get() { " cursor-pointer hover:shadow-md transition-shadow" } else { "" };
        let custom_class = class.get().unwrap_or_default();
        format!("{}{}{} {}", base_class, variant_class, interactive_class, custom_class)
    });

    view! {
        <article
            class=computed_class
            id=move || id.get().unwrap_or_default()
            style=move || style.get().to_string()
            role="article"
            tabindex=move || if interactive.get() { "0" } else { "-1" }
        >
            {children.map(|c| c())}
        </article>
    }
}

Day 3-4: Implement Card Variants

Current Problem: No card variants for different states

Target Implementation:

// Card variant enum
#[derive(Clone, Debug, PartialEq)]
pub enum CardVariant {
    Default,
    Destructive,
    Warning,
    Success,
}

// Enhanced CardTitle with semantic structure
#[component]
pub fn CardTitle(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] id: MaybeProp<String>,
    #[prop(into, optional)] style: Signal<Style>,
    #[prop(into, optional)] level: MaybeProp<u8>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    let computed_class = Signal::derive(move || {
        format!("{} {}", CARD_TITLE_CLASS, class.get().unwrap_or_default())
    });

    let heading_level = level.get().unwrap_or(2).clamp(1, 6);

    view! {
        <h{heading_level}
            class=computed_class
            id=move || id.get().unwrap_or_default()
            style=move || style.get().to_string()
        >
            {children.map(|c| c())}
        </h{heading_level}>
    }
}

Day 5: Implement Interactive Card Functionality

Current Problem: No interactive card functionality

Target Implementation:

// Enhanced Card with click handling
#[component]
pub fn InteractiveCard(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] id: MaybeProp<String>,
    #[prop(into, optional)] style: Signal<Style>,
    #[prop(into, optional)] variant: MaybeProp<CardVariant>,
    #[prop(into, optional)] on_click: Option<Callback<()>>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    let (is_hovered, set_is_hovered) = signal(false);
    let (is_focused, set_is_focused) = signal(false);
    
    let computed_class = Signal::derive(move || {
        let base_class = CARD_CLASS;
        let variant_class = match variant.get() {
            Some(CardVariant::Destructive) => " border-destructive bg-destructive/5",
            Some(CardVariant::Warning) => " border-warning bg-warning/5",
            Some(CardVariant::Success) => " border-success bg-success/5",
            _ => "",
        };
        let interactive_class = " cursor-pointer hover:shadow-md transition-shadow focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2";
        let custom_class = class.get().unwrap_or_default();
        format!("{}{}{} {}", base_class, variant_class, interactive_class, custom_class)
    });

    let handle_click = move |_| {
        if let Some(callback) = &on_click {
            callback.run(());
        }
    };

    let handle_keydown = move |event: KeyboardEvent| {
        if event.key() == "Enter" || event.key() == " " {
            event.prevent_default();
            handle_click(());
        }
    };

    view! {
        <article
            class=computed_class
            id=move || id.get().unwrap_or_default()
            style=move || style.get().to_string()
            role="button"
            tabindex="0"
            on:click=handle_click
            on:keydown=handle_keydown
            on:mouseenter=move |_| set_is_hovered.set(true)
            on:mouseleave=move |_| set_is_hovered.set(false)
            on:focus=move |_| set_is_focused.set(true)
            on:blur=move |_| set_is_focused.set(false)
        >
            {children.map(|c| c())}
        </article>
    }
}

Phase 2: Comprehensive Testing (Week 2)

Day 1-2: Component Rendering Tests

File: packages/leptos/card/src/tests/rendering_tests.rs

#[cfg(test)]
mod rendering_tests {
    use super::*;
    use leptos::prelude::*;

    #[test]
    fn test_card_renders_without_errors() {
        let card = view! {
            <Card>
                <CardHeader>
                    <CardTitle>"Test Card"</CardTitle>
                </CardHeader>
                <CardContent>
                    <p>"Card content"</p>
                </CardContent>
            </Card>
        };
        
        // Verify the view renders without errors
        let _view = card.into_view();
        // If we get here without panicking, the view was created successfully
    }

    #[test]
    fn test_card_with_all_sections() {
        let card = view! {
            <Card>
                <CardHeader>
                    <CardTitle>"Complete Card"</CardTitle>
                    <CardDescription>"Card description"</CardDescription>
                </CardHeader>
                <CardContent>
                    <p>"Card content goes here"</p>
                </CardContent>
                <CardFooter>
                    <Button>"Action"</Button>
                </CardFooter>
            </Card>
        };
        
        // Verify the view renders without errors
        let _view = card.into_view();
        // If we get here without panicking, the view was created successfully
    }

    #[test]
    fn test_card_variants() {
        let variants = vec![
            CardVariant::Default,
            CardVariant::Destructive,
            CardVariant::Warning,
            CardVariant::Success,
        ];
        
        for variant in variants {
            let card = view! {
                <Card variant=variant>
                    <CardHeader>
                        <CardTitle>"Variant Card"</CardTitle>
                    </CardHeader>
                </Card>
            };
            
            // Verify the view renders without errors
            let _view = card.into_view();
            // If we get here without panicking, the view was created successfully
        }
    }
}

Day 3-4: Accessibility Tests

File: packages/leptos/card/src/tests/accessibility_tests.rs

#[cfg(test)]
mod accessibility_tests {
    use super::*;

    #[test]
    fn test_card_aria_attributes() {
        let card = view! {
            <Card interactive=Signal::derive(|| true)>
                <CardHeader>
                    <CardTitle>"Interactive Card"</CardTitle>
                </CardHeader>
            </Card>
        };
        
        // Verify the view renders without errors
        let _view = card.into_view();
        // If we get here without panicking, the view was created successfully
    }

    #[test]
    fn test_card_title_semantic_structure() {
        let title = view! {
            <CardTitle level=2>"Semantic Title"</CardTitle>
        };
        
        // Verify the view renders without errors
        let _view = title.into_view();
        // If we get here without panicking, the view was created successfully
    }

    #[test]
    fn test_card_description_accessibility() {
        let description = view! {
            <CardDescription>"Accessible description"</CardDescription>
        };
        
        // Verify the view renders without errors
        let _view = description.into_view();
        // If we get here without panicking, the view was created successfully
    }
}

Day 5: Interactive Card Tests

File: packages/leptos/card/src/tests/interactive_tests.rs

#[cfg(test)]
mod interactive_tests {
    use super::*;

    #[test]
    fn test_interactive_card_behavior() {
        let (clicked, set_clicked) = signal(false);
        
        let on_click = Callback::new(move |_| {
            set_clicked.set(true);
        });
        
        let card = view! {
            <InteractiveCard on_click=on_click>
                <CardHeader>
                    <CardTitle>"Clickable Card"</CardTitle>
                </CardHeader>
            </InteractiveCard>
        };
        
        // Verify the view renders without errors
        let _view = card.into_view();
        // If we get here without panicking, the view was created successfully
    }

    #[test]
    fn test_card_keyboard_navigation() {
        let card = view! {
            <InteractiveCard>
                <CardHeader>
                    <CardTitle>"Keyboard Card"</CardTitle>
                </CardHeader>
            </InteractiveCard>
        };
        
        // Verify the view renders without errors
        let _view = card.into_view();
        // If we get here without panicking, the view was created successfully
    }

    #[test]
    fn test_card_focus_management() {
        let card = view! {
            <InteractiveCard>
                <CardHeader>
                    <CardTitle>"Focusable Card"</CardTitle>
                </CardHeader>
            </InteractiveCard>
        };
        
        // Verify the view renders without errors
        let _view = card.into_view();
        // If we get here without panicking, the view was created successfully
    }
}

Phase 3: Integration Testing (Week 3)

Day 1-2: Card with Form Integration Tests

File: packages/leptos/card/src/tests/integration_tests.rs

#[cfg(test)]
mod integration_tests {
    use super::*;

    #[test]
    fn test_card_with_form_elements() {
        let card = view! {
            <Card>
                <CardHeader>
                    <CardTitle>"Form Card"</CardTitle>
                </CardHeader>
                <CardContent>
                    <Form>
                        <FormField name="email">
                            <FormLabel for_field="email">"Email"</FormLabel>
                            <Input id="email" type="email" />
                        </FormField>
                    </Form>
                </CardContent>
                <CardFooter>
                    <Button type="submit">"Submit"</Button>
                </CardFooter>
            </Card>
        };
        
        // Verify the view renders without errors
        let _view = card.into_view();
        // If we get here without panicking, the view was created successfully
    }

    #[test]
    fn test_card_with_button_actions() {
        let card = view! {
            <Card>
                <CardHeader>
                    <CardTitle>"Action Card"</CardTitle>
                </CardHeader>
                <CardContent>
                    <p>"Card with action buttons"</p>
                </CardContent>
                <CardFooter>
                    <Button variant=ButtonVariant::Primary>"Primary"</Button>
                    <Button variant=ButtonVariant::Secondary>"Secondary"</Button>
                </CardFooter>
            </Card>
        };
        
        // Verify the view renders without errors
        let _view = card.into_view();
        // If we get here without panicking, the view was created successfully
    }

    #[test]
    fn test_card_with_dialog_integration() {
        let card = view! {
            <Card>
                <CardHeader>
                    <CardTitle>"Dialog Card"</CardTitle>
                </CardHeader>
                <CardContent>
                    <Dialog>
                        <DialogTrigger>"Open Dialog"</DialogTrigger>
                        <DialogContent>
                            <DialogTitle>"Card Dialog"</DialogTitle>
                        </DialogContent>
                    </Dialog>
                </CardContent>
            </Card>
        };
        
        // Verify the view renders without errors
        let _view = card.into_view();
        // If we get here without panicking, the view was created successfully
    }
}

Day 3-4: Performance Tests

File: packages/leptos/card/src/tests/performance_tests.rs

#[cfg(test)]
mod performance_tests {
    use super::*;
    use std::time::Instant;

    #[test]
    fn test_card_render_performance() {
        let start = Instant::now();
        let card = view! {
            <Card>
                <CardHeader>
                    <CardTitle>"Performance Test"</CardTitle>
                </CardHeader>
                <CardContent>
                    <p>"Content"</p>
                </CardContent>
            </Card>
        };
        let _rendered = card.into_view();
        let duration = start.elapsed();
        
        assert!(duration.as_millis() < 2, "Card render time should be < 2ms");
    }

    #[test]
    fn test_card_variant_performance() {
        let start = Instant::now();
        let variants = vec![
            CardVariant::Default,
            CardVariant::Destructive,
            CardVariant::Warning,
            CardVariant::Success,
        ];
        
        for variant in variants {
            let card = view! {
                <Card variant=variant>
                    <CardHeader>
                        <CardTitle>"Variant Test"</CardTitle>
                    </CardHeader>
                </Card>
            };
            let _rendered = card.into_view();
        }
        let duration = start.elapsed();
        
        assert!(duration.as_millis() < 5, "Card variant rendering should be < 5ms");
    }
}

Day 5: Error Handling Tests

File: packages/leptos/card/src/tests/error_handling_tests.rs

#[cfg(test)]
mod error_handling_tests {
    use super::*;

    #[test]
    fn test_card_graceful_error_handling() {
        let card = view! {
            <Card>
                <CardHeader>
                    <CardTitle>"Error Test"</CardTitle>
                </CardHeader>
                <CardContent>
                    <p>"Content with potential errors"</p>
                </CardContent>
            </Card>
        };
        
        // Should not panic even with potential errors
        let _view = card.into_view();
        assert!(true, "Card should handle errors gracefully");
    }

    #[test]
    fn test_card_missing_children_handling() {
        let card = view! {
            <Card>
                // No children provided
            </Card>
        };
        
        // Should not panic with missing children
        let _view = card.into_view();
        assert!(true, "Card should handle missing children gracefully");
    }
}

📋 Implementation Checklist

Week 1: Accessibility & Interactive Features

  • Day 1: Add ARIA attributes to Card component
  • Day 2: Implement semantic HTML structure
  • Day 3: Add card variants (destructive, warning, success)
  • Day 4: Implement interactive card functionality
  • Day 5: Add keyboard navigation and focus management

Week 2: Comprehensive Testing

  • Day 1: Implement component rendering tests
  • Day 2: Add accessibility tests
  • Day 3: Create interactive card tests
  • Day 4: Add integration tests
  • Day 5: Implement performance tests

Week 3: Integration & Error Handling

  • Day 1: Card with form integration tests
  • Day 2: Card with button integration tests
  • Day 3: Performance benchmarking
  • Day 4: Error handling tests
  • Day 5: Final validation and documentation

Week 4: Validation & Documentation

  • Day 1: Run full test suite
  • Day 2: Performance benchmarking
  • Day 3: Accessibility testing
  • Day 4: Update documentation
  • Day 5: Final validation and sign-off

🎯 Success Metrics

Week 1 Targets

  • ARIA attributes implemented
  • Card variants working
  • Interactive functionality functional
  • Semantic structure enhanced

Week 2 Targets

  • 90% test coverage achieved
  • All test categories implemented
  • Accessibility tests passing
  • Interactive tests working

Week 3 Targets

  • Card integration working
  • Performance benchmarks established
  • Error handling implemented
  • Production ready

Week 4 Targets

  • 100% test coverage
  • AAA accessibility compliance
  • Performance targets met
  • Documentation complete

🚨 Risk Mitigation

Medium-Risk Areas

  1. Interactive Cards: Click handling and keyboard navigation
  2. Card Variants: CSS class management and theming
  3. Performance: Render time with multiple variants
  4. Integration: Card with other components

Mitigation Strategies

  1. Incremental Implementation: Small, testable changes
  2. Comprehensive Testing: Test-driven development
  3. Code Reviews: Peer review for all changes
  4. Rollback Plans: Git branches for each phase

🔧 Tools & Dependencies

Required Dependencies

[dependencies]
leptos = "0.8"
leptos_style = "0.1"

Testing Tools

  • Unit Testing: Rust native testing
  • WASM Testing: wasm-bindgen-test
  • Accessibility Testing: Manual testing + automated checks
  • Performance Testing: Custom benchmarking

🚀 Next Steps

  1. Start immediately with ARIA attributes
  2. Implement card variants as priority
  3. Add interactive functionality for accessibility
  4. Create comprehensive tests for all functionality
  5. Validate production readiness before completion

This remediation plan will transform the Card component from a basic, static card into a production-ready, fully-accessible, interactive card system within 4 weeks.


Remediation plan created: September 20, 2025
Target completion: October 18, 2025