Files
leptos-shadcn-ui/docs/remediation/components/card/DESIGN.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

12 KiB
Raw Blame History

🎨 Card Component Design

Component: Card | Priority: HIGH | Status: ⚠️ Needs Enhancement

📋 Component Overview

The Card component is a fundamental layout component with a good foundation but needs enhancement for accessibility and comprehensive testing. This is a Priority 1 component requiring attention.

Current Status

  • Basic structure exists and is well-implemented
  • CSS class constants properly defined
  • Component composition working correctly
  • ⚠️ ~40% test coverage (estimated)
  • ⚠️ Missing accessibility features
  • ⚠️ Missing comprehensive integration tests
  • ⚠️ Missing interactive card tests

🎯 Design Specifications

Visual Design

/* Card Container */
.card {
  border-radius: 0.5rem;
  border: 1px solid hsl(var(--border));
  background-color: hsl(var(--card));
  color: hsl(var(--card-foreground));
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}

/* Card Header */
.card-header {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  padding: 1.5rem;
}

/* Card Title */
.card-title {
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.025em;
}

/* Card Description */
.card-description {
  font-size: 0.875rem;
  color: hsl(var(--muted-foreground));
}

/* Card Content */
.card-content {
  padding: 1.5rem;
  padding-top: 0;
}

/* Card Footer */
.card-footer {
  display: flex;
  align-items: center;
  padding: 1.5rem;
  padding-top: 0;
}

/* Interactive Card States */
.card:hover {
  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
}

.card:focus-within {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

/* Card Variants */
.card-destructive {
  border-color: hsl(var(--destructive));
  background-color: hsl(var(--destructive) / 0.05);
}

.card-warning {
  border-color: hsl(var(--warning));
  background-color: hsl(var(--warning) / 0.05);
}

.card-success {
  border-color: hsl(var(--success));
  background-color: hsl(var(--success) / 0.05);
}

Animation Keyframes

@keyframes card-fade-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes card-scale-hover {
  from {
    transform: scale(1);
  }
  to {
    transform: scale(1.02);
  }
}

.card {
  animation: card-fade-in 0.2s ease-out;
}

.card:hover {
  animation: card-scale-hover 0.2s ease-out;
}

🔧 API Design

Card Root Component

#[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

Card Header Component

#[component]
pub fn CardHeader(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] id: MaybeProp<String>,
    #[prop(into, optional)] style: Signal<Style>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView

Card Title Component

#[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>, // h1, h2, h3, etc.
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView

Card Description Component

#[component]
pub fn CardDescription(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] id: MaybeProp<String>,
    #[prop(into, optional)] style: Signal<Style>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView

Card Content Component

#[component]
pub fn CardContent(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] id: MaybeProp<String>,
    #[prop(into, optional)] style: Signal<Style>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView
#[component]
pub fn CardFooter(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] id: MaybeProp<String>,
    #[prop(into, optional)] style: Signal<Style>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView

Accessibility Features

ARIA Attributes

  • role="article" on card element for semantic meaning
  • aria-labelledby linking to card title
  • aria-describedby linking to card description
  • tabindex="0" for interactive cards
  • aria-expanded for collapsible cards

Keyboard Navigation

  • Tab: Navigate to interactive cards
  • Enter/Space: Activate interactive cards
  • Arrow Keys: Navigate between card sections (if applicable)

Focus Management

  • Visible focus indicators for interactive cards
  • Focus trapping within modal cards
  • Focus restoration after card interactions

Screen Reader Support

  • Card title announced when focused
  • Card description provided as context
  • Interactive state communicated
  • Content structure clearly defined

🧪 Testing Requirements

Current Test Status

  • Basic CSS class tests implemented
  • Styling consistency tests working
  • ⚠️ Missing component rendering tests
  • ⚠️ Missing accessibility tests
  • ⚠️ Missing integration tests
  • ⚠️ Missing interactive card tests

Required Test Categories

1. Component Rendering Tests

#[test]
fn test_card_renders_without_errors() {
    let card = Card::new(CardProps {
        children: Some(Children::new()),
        ..Default::default()
    });
    
    let rendered = card.render();
    assert!(rendered.contains("card"));
}

#[test]
fn test_card_with_all_sections() {
    let card = Card::new(CardProps {
        children: Some(Children::new()),
        ..Default::default()
    });
    
    let rendered = card.render();
    assert!(rendered.contains("card-header"));
    assert!(rendered.contains("card-content"));
    assert!(rendered.contains("card-footer"));
}

2. Accessibility Tests

#[test]
fn test_card_aria_attributes() {
    let card = Card::new(CardProps {
        interactive: Signal::derive(|| true),
        ..Default::default()
    });
    
    let rendered = card.render();
    assert!(rendered.contains("role=\"article\""));
    assert!(rendered.contains("tabindex=\"0\""));
}

#[test]
fn test_card_title_semantic_structure() {
    let title = CardTitle::new(CardTitleProps {
        level: Some(2),
        ..Default::default()
    });
    
    let rendered = title.render();
    assert!(rendered.contains("<h2"));
}

3. Interactive Card Tests

#[test]
fn test_interactive_card_behavior() {
    let (clicked, set_clicked) = signal(false);
    
    let card = Card::new(CardProps {
        interactive: Signal::derive(|| true),
        on_click: Some(Callback::new(move |_| {
            set_clicked.set(true);
        })),
        ..Default::default()
    });
    
    // Simulate click
    card.props.on_click.unwrap().run(());
    assert!(clicked.get(), "Card should be clickable");
}

4. Integration Tests

#[test]
fn test_card_with_form_elements() {
    let card = Card::new(CardProps {
        children: Some(Children::new()),
        ..Default::default()
    });
    
    let rendered = card.render();
    // Test that card integrates with form elements
    assert!(rendered.contains("card"));
}

#[test]
fn test_card_with_button_actions() {
    let card = Card::new(CardProps {
        children: Some(Children::new()),
        ..Default::default()
    });
    
    let rendered = card.render();
    // Test that card integrates with buttons
    assert!(rendered.contains("card"));
}

🚀 Performance Considerations

Bundle Size

  • Base component: ~3KB
  • With variants: ~4KB
  • WASM optimized: ~3.5KB

Render Performance

  • Initial render: <2ms
  • Hover effects: <1ms
  • Content updates: <1ms
  • Memory usage: <2KB per instance

Optimization Strategies

  • Lazy loading for card content
  • Efficient class computation
  • Minimal re-renders on state changes
  • Optimized CSS class merging

🔄 State Management

Card State

#[derive(Clone, Debug)]
pub struct CardState {
    pub is_interactive: bool,
    pub is_hovered: bool,
    pub is_focused: bool,
    pub variant: CardVariant,
}

impl CardState {
    pub fn new() -> Self {
        Self {
            is_interactive: false,
            is_hovered: false,
            is_focused: false,
            variant: CardVariant::Default,
        }
    }
    
    pub fn set_interactive(&mut self, interactive: bool) {
        self.is_interactive = interactive;
    }
    
    pub fn set_hovered(&mut self, hovered: bool) {
        self.is_hovered = hovered;
    }
}

Signal Integration

// Card state management
let (card_state, set_card_state) = signal(CardState::new());

view! {
    <Card 
        interactive=move || card_state.get().is_interactive
        on_click=move |_| {
            set_card_state.update(|state| {
                state.set_interactive(true);
            });
        }
    >
        <CardHeader>
            <CardTitle level=2>"Interactive Card"</CardTitle>
            <CardDescription>"Click to interact"</CardDescription>
        </CardHeader>
        <CardContent>
            <p>"Card content goes here"</p>
        </CardContent>
        <CardFooter>
            <Button>"Action"</Button>
        </CardFooter>
    </Card>
}

📱 Responsive Design

Breakpoint Behavior

  • Mobile: Full width, stacked layout
  • Tablet: Flexible width, proper spacing
  • Desktop: Fixed width, hover effects

Touch Targets

  • Minimum size: 44px × 44px for interactive elements
  • Touch-friendly spacing
  • Large tap targets for mobile

🎨 Theming Support

CSS Custom Properties

:root {
  --card: 0 0% 100%;
  --card-foreground: 222.2 84% 4.9%;
  --card-border: 214.3 31.8% 91.4%;
  --card-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}

Dark Mode Support

  • Automatic theme switching
  • High contrast mode support
  • Custom theme overrides

🔧 Implementation Notes

Current Implementation

  • File: packages/leptos/card/src/default.rs (142 lines)
  • Status: Good foundation, needs enhancement
  • Tests: packages/leptos/card/src/tests.rs (140 lines)

Required Implementation

  1. Add accessibility features (ARIA attributes, semantic structure)
  2. Implement interactive card functionality
  3. Add card variants (destructive, warning, success)
  4. Create comprehensive test suite
  5. Add performance optimizations

📋 Action Items

Immediate (Week 1) - Priority 1

  • Add accessibility features (ARIA attributes, semantic structure)
  • Implement interactive card functionality
  • Add card variants (destructive, warning, success)
  • Create component rendering tests

Short-term (Week 2-3)

  • Implement comprehensive test suite
  • Add integration tests
  • Create performance benchmarks
  • Add card state management

Long-term (Week 4+)

  • Advanced card features
  • Custom card layouts
  • Performance optimizations
  • Advanced theming

🎯 Success Metrics

Quality Metrics

  • Test coverage: 90%+ (current: ~40%)
  • Accessibility score: AAA compliance
  • Performance: <2ms render time
  • Bundle size: <4KB optimized

Functionality Metrics

  • Interactive cards: 100% working
  • Accessibility compliance: WCAG 2.1 AA
  • Card variants: 100% functional
  • Integration: 100% working

🚨 Critical Issues

Must Fix Immediately

  1. Missing accessibility features - ARIA attributes, semantic structure
  2. No interactive card tests - Core functionality not tested
  3. Missing card variants - Limited styling options
  4. No integration tests - Card with other components not tested

Impact Assessment

  • Medium impact on user experience
  • Important for layout functionality
  • Essential for accessibility compliance
  • Required for production readiness

This Card component has a good foundation but needs enhancement to bring it to production-ready status. It serves as a fundamental building block for layouts and content organization.


Design document created: September 20, 2025
Last updated: September 20, 2025