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

🎨 Dialog Component Design

Component: Dialog | Priority: HIGH | Status: ⚠️ Needs Major Work

📋 Component Overview

The Dialog component is a critical modal component but currently has significant issues with test coverage and implementation completeness. This is a Priority 1 component requiring immediate attention.

Current Status

  • ⚠️ 2 test files with basic implementations
  • ⚠️ ~20% test coverage (estimated)
  • ⚠️ Missing accessibility tests
  • ⚠️ Missing keyboard navigation tests
  • ⚠️ Missing modal behavior tests
  • Basic structure exists
  • Context system implemented

🎯 Design Specifications

Visual Design

/* Dialog Overlay */
.dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  background-color: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(4px);
  animation: fade-in 0.2s ease-out;
}

/* Dialog Content */
.dialog-content {
  position: fixed;
  left: 50%;
  top: 50%;
  z-index: 50;
  display: grid;
  width: 100%;
  max-width: 32rem;
  gap: 1rem;
  border: 1px solid hsl(var(--border));
  background-color: hsl(var(--background));
  padding: 1.5rem;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  transform: translate(-50%, -50%);
  animation: slide-in 0.2s ease-out;
}

/* Dialog Header */
.dialog-header {
  display: flex;
  flex-direction: column;
  space-y: 1.5rem;
  text-align: center;
}

/* Dialog Footer */
.dialog-footer {
  display: flex;
  flex-direction: column-reverse;
  gap: 0.5rem;
}

/* Responsive Design */
@media (min-width: 640px) {
  .dialog-content {
    max-width: 28rem;
  }
  
  .dialog-footer {
    flex-direction: row;
    justify-content: flex-end;
  }
}

Animation Keyframes

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes slide-in {
  from {
    opacity: 0;
    transform: translate(-50%, -48%) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
}

🔧 API Design

Dialog Root Component

#[component]
pub fn Dialog(
    #[prop(into, optional)] open: Signal<bool>,
    #[prop(into, optional)] on_open_change: Option<Callback<bool>>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView

Dialog Trigger Component

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

Dialog Content Component

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

Dialog Header Component

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

Dialog Title Component

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

Dialog Description Component

#[component]
pub fn DialogDescription(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView
#[component]
pub fn DialogFooter(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView

Dialog Close Component

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

Accessibility Features

ARIA Attributes

  • role="dialog" on dialog content
  • aria-modal="true" for modal behavior
  • aria-labelledby linking to dialog title
  • aria-describedby linking to dialog description
  • aria-hidden="true" on background content

Keyboard Navigation

  • Escape: Close dialog
  • Tab: Focus management within dialog
  • Shift+Tab: Reverse focus management
  • Enter/Space: Activate focused element

Focus Management

  • Focus trap within dialog
  • Focus restoration after closing
  • Initial focus on first focusable element
  • Focus on trigger after closing

Screen Reader Support

  • Dialog title announced when opened
  • Dialog description provided
  • State changes announced
  • Focus management communicated

🧪 Testing Requirements

Current Test Status

  • ⚠️ 2 test files with basic implementations
  • ⚠️ Missing modal behavior tests
  • ⚠️ Missing accessibility tests
  • ⚠️ Missing keyboard navigation tests
  • ⚠️ Missing integration tests

Required Test Categories

1. Basic Rendering Tests

#[test]
fn test_dialog_renders_without_errors() {
    let dialog = Dialog::new(DialogProps {
        open: Signal::derive(|| false),
        ..Default::default()
    });
    
    let rendered = dialog.render();
    assert!(rendered.contains("dialog"));
}

#[test]
fn test_dialog_content_renders() {
    let content = DialogContent::new(DialogContentProps {
        children: Some(Children::new()),
        ..Default::default()
    });
    
    let rendered = content.render();
    assert!(rendered.contains("dialog-content"));
}

2. Modal Behavior Tests

#[test]
fn test_dialog_opens_and_closes() {
    let (open, set_open) = signal(false);
    
    // Test opening
    set_open.set(true);
    assert!(open.get(), "Dialog should be open");
    
    // Test closing
    set_open.set(false);
    assert!(!open.get(), "Dialog should be closed");
}

#[test]
fn test_dialog_overlay_blocks_interaction() {
    let dialog = Dialog::new(DialogProps {
        open: Signal::derive(|| true),
        ..Default::default()
    });
    
    // Test that overlay prevents background interaction
    let rendered = dialog.render();
    assert!(rendered.contains("dialog-overlay"));
}

3. Accessibility Tests

#[test]
fn test_dialog_aria_attributes() {
    let dialog = Dialog::new(DialogProps {
        open: Signal::derive(|| true),
        ..Default::default()
    });
    
    let rendered = dialog.render();
    assert!(rendered.contains("role=\"dialog\""));
    assert!(rendered.contains("aria-modal=\"true\""));
}

#[test]
fn test_dialog_focus_management() {
    let dialog = Dialog::new(DialogProps {
        open: Signal::derive(|| true),
        ..Default::default()
    });
    
    // Test focus trap
    let rendered = dialog.render();
    assert!(rendered.contains("tabindex"));
}

4. Keyboard Navigation Tests

#[test]
fn test_dialog_escape_key() {
    let (open, set_open) = signal(true);
    let dialog = Dialog::new(DialogProps {
        open: Signal::derive(move || open.get()),
        on_open_change: Some(Callback::new(move |new_open| {
            set_open.set(new_open);
        })),
        ..Default::default()
    });
    
    // Simulate escape key
    // Test that dialog closes
    assert!(!open.get(), "Dialog should close on escape key");
}

5. Integration Tests

#[test]
fn test_dialog_with_form() {
    let (open, set_open) = signal(true);
    let form_submitted = signal(false);
    
    let dialog = Dialog::new(DialogProps {
        open: Signal::derive(move || open.get()),
        children: Some(Children::new()),
        ..Default::default()
    });
    
    // Test form submission within dialog
    form_submitted.set(true);
    assert!(form_submitted.get(), "Form should submit within dialog");
}

🚀 Performance Considerations

Bundle Size

  • Base component: ~3KB
  • With animations: ~5KB
  • WASM optimized: ~4KB

Render Performance

  • Initial render: <2ms
  • Open/close animation: <200ms
  • Memory usage: <2KB per instance

Optimization Strategies

  • Lazy rendering of dialog content
  • CSS animations for smooth transitions
  • Event delegation for keyboard handling
  • Minimal re-renders on state changes

🔄 State Management

Context System

#[derive(Clone, Copy)]
pub struct DialogContextValue {
    pub open: Signal<bool>,
    pub set_open: Callback<bool>,
}

// Provide context in Dialog root
provide_context(DialogContextValue {
    open: open_state,
    set_open,
});

// Consume context in child components
let ctx = expect_context::<DialogContextValue>();

Signal Integration

// Dialog state management
let (open, set_open) = signal(false);

view! {
    <Dialog open=Signal::derive(move || open.get())>
        <DialogTrigger on:click=move |_| set_open.set(true)>
            "Open Dialog"
        </DialogTrigger>
        <DialogContent>
            <DialogHeader>
                <DialogTitle>"Dialog Title"</DialogTitle>
            </DialogHeader>
            <DialogFooter>
                <DialogClose on:click=move |_| set_open.set(false)>
                    "Close"
                </DialogClose>
            </DialogFooter>
        </DialogContent>
    </Dialog>
}

📱 Responsive Design

Breakpoint Behavior

  • Mobile: Full width, full height
  • Tablet: Centered with margins
  • Desktop: Fixed width, centered

Touch Targets

  • Minimum size: 44px × 44px for interactive elements
  • Touch-friendly spacing
  • Swipe gestures for mobile

🎨 Theming Support

CSS Custom Properties

:root {
  --dialog-overlay: rgba(0, 0, 0, 0.5);
  --dialog-background: hsl(var(--background));
  --dialog-border: hsl(var(--border));
  --dialog-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}

Dark Mode Support

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

🔧 Implementation Notes

Current Implementation

  • File: packages/leptos/dialog/src/default.rs (219 lines)
  • Status: ⚠️ Basic implementation, needs enhancement
  • Tests: packages/leptos/dialog/src/tests.rs (353 lines)

Required Implementation

  1. Add comprehensive accessibility features
  2. Implement keyboard navigation
  3. Add focus management
  4. Create modal behavior tests
  5. Add integration tests

📋 Action Items

Immediate (Week 1) - Priority 1

  • Add accessibility features (ARIA attributes, focus management)
  • Implement keyboard navigation (Escape, Tab, Enter)
  • Create modal behavior tests
  • Add focus trap functionality

Short-term (Week 2-3)

  • Implement comprehensive test suite
  • Add integration tests
  • Create performance benchmarks
  • Add animation support

Long-term (Week 4+)

  • Advanced modal features
  • Custom animations
  • Performance optimizations
  • Advanced theming

🎯 Success Metrics

Quality Metrics

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

Functionality Metrics

  • Modal behavior: 100% working
  • Accessibility compliance: WCAG 2.1 AA
  • Keyboard navigation: 100% functional
  • Focus management: 100% working

🚨 Critical Issues

Must Fix Immediately

  1. Missing accessibility features - ARIA attributes, focus management
  2. No keyboard navigation - Escape key, Tab management
  3. Missing modal behavior tests - Core functionality not tested
  4. No focus trap - Essential for modal behavior

Impact Assessment

  • High impact on user experience
  • Critical for accessibility compliance
  • Essential for modal functionality
  • Required for production readiness

This Dialog component is critical and requires immediate attention to bring it to production-ready status. It serves as the foundation for all modal interactions in the library.


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