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

642 lines
19 KiB
Markdown

# 🔧 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:**
```rust
// 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:**
```rust
// 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:**
```rust
// 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`
```rust
#[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`
```rust
#[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`
```rust
#[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`
```rust
#[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`
```rust
#[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`
```rust
#[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**
```toml
[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*