mirror of
https://github.com/cloud-shuttle/leptos-shadcn-ui.git
synced 2025-12-22 22:00:00 +00:00
- 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!
642 lines
19 KiB
Markdown
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*
|