# 🎨 **Button Component Design** ## **Overview** Design for the Button component that provides interactive buttons with variants, sizes, and accessibility features. ## **Core Component** ### **Button Component** ```rust #[component] pub fn Button( #[prop(into, optional)] variant: Option, #[prop(into, optional)] size: Option, #[prop(into, optional)] disabled: Option>, #[prop(into, optional)] loading: Option>, #[prop(into, optional)] class: Option, #[prop(into, optional)] id: Option, #[prop(into, optional)] on_click: Option>, #[prop(into, optional)] children: Option, ) -> impl IntoView { let variant = variant.unwrap_or_default(); let size = size.unwrap_or_default(); let button_class = move || { let mut classes = vec!["inline-flex", "items-center", "justify-center", "whitespace-nowrap", "rounded-md", "text-sm", "font-medium", "ring-offset-background", "transition-colors", "focus-visible:outline-none", "focus-visible:ring-2", "focus-visible:ring-ring", "focus-visible:ring-offset-2", "disabled:pointer-events-none", "disabled:opacity-50"]; // Add variant classes classes.extend(variant.classes()); // Add size classes classes.extend(size.classes()); if let Some(custom_class) = class.as_ref() { classes.push(custom_class); } classes.join(" ") }; let handle_click = move |_| { if let Some(on_click) = on_click.as_ref() { on_click.call(()); } }; let is_disabled = disabled.map(|d| d.get()).unwrap_or(false); let is_loading = loading.map(|l| l.get()).unwrap_or(false); view! { } } ``` ## **Supporting Types** ### **ButtonVariant** ```rust #[derive(Debug, Clone, PartialEq)] pub enum ButtonVariant { Default, Destructive, Outline, Secondary, Ghost, Link, } impl Default for ButtonVariant { fn default() -> Self { Self::Default } } impl ButtonVariant { pub fn classes(&self) -> Vec<&'static str> { match self { ButtonVariant::Default => vec!["bg-primary", "text-primary-foreground", "hover:bg-primary/90"], ButtonVariant::Destructive => vec!["bg-destructive", "text-destructive-foreground", "hover:bg-destructive/90"], ButtonVariant::Outline => vec!["border", "border-input", "bg-background", "hover:bg-accent", "hover:text-accent-foreground"], ButtonVariant::Secondary => vec!["bg-secondary", "text-secondary-foreground", "hover:bg-secondary/80"], ButtonVariant::Ghost => vec!["hover:bg-accent", "hover:text-accent-foreground"], ButtonVariant::Link => vec!["text-primary", "underline-offset-4", "hover:underline"], } } } ``` ### **ButtonSize** ```rust #[derive(Debug, Clone, PartialEq)] pub enum ButtonSize { Default, Sm, Lg, Icon, } impl Default for ButtonSize { fn default() -> Self { Self::Default } } impl ButtonSize { pub fn classes(&self) -> Vec<&'static str> { match self { ButtonSize::Default => vec!["h-10", "px-4", "py-2"], ButtonSize::Sm => vec!["h-9", "rounded-md", "px-3"], ButtonSize::Lg => vec!["h-11", "rounded-md", "px-8"], ButtonSize::Icon => vec!["h-10", "w-10"], } } } ``` ## **Enhanced Button Features** ### **Button with Loading State** ```rust #[component] pub fn LoadingButton( #[prop(into, optional)] variant: Option, #[prop(into, optional)] size: Option, #[prop(into, optional)] class: Option, #[prop(into, optional)] on_click: Option>, #[prop(into, optional)] children: Option, ) -> impl IntoView { let (is_loading, set_is_loading) = signal(false); let handle_click = move |_| { if !is_loading.get() { set_is_loading.set(true); if let Some(on_click) = on_click.as_ref() { on_click.call(()); } // Simulate async operation set_timeout(move || { set_is_loading.set(false); }, 2000); } }; view! { } } ``` ### **Button with Icon** ```rust #[component] pub fn IconButton( #[prop(into, optional)] variant: Option, #[prop(into, optional)] class: Option, #[prop(into, optional)] on_click: Option>, #[prop(into, optional)] icon: Option, ) -> impl IntoView { view! { } } ``` ### **Button Group** ```rust #[component] pub fn ButtonGroup( #[prop(into, optional)] class: Option, #[prop(into, optional)] children: Option, ) -> impl IntoView { let group_class = move || { let mut classes = vec!["inline-flex", "items-center", "justify-center", "rounded-md", "text-sm", "font-medium", "ring-offset-background", "transition-colors", "focus-visible:outline-none", "focus-visible:ring-2", "focus-visible:ring-ring", "focus-visible:ring-offset-2"]; if let Some(custom_class) = class.as_ref() { classes.push(custom_class); } classes.join(" ") }; view! {
{children}
} } ``` ## **Usage Examples** ### **Basic Button** ```rust view! { } ``` ### **Button with Variants** ```rust view! {
} ``` ### **Button with Sizes** ```rust view! {
} ``` ### **Button with Loading State** ```rust let (is_loading, set_is_loading) = signal(false); let handle_async_click = move |_| { set_is_loading.set(true); // Simulate async operation set_timeout(move || { set_is_loading.set(false); }, 2000); }; view! { } ``` ### **Button with Custom Styling** ```rust view! { } ``` ### **Button Group** ```rust view! { } ``` ## **Accessibility Features** ### **Keyboard Navigation** - **Tab**: Focus management - **Enter/Space**: Activate button - **Escape**: Cancel action (if applicable) ### **ARIA Attributes** - `role="button"`: Button role - `aria-disabled`: Disabled state - `aria-pressed`: Toggle state (if applicable) - `aria-label`: Accessible label ### **Screen Reader Support** - Proper labeling - State announcements - Focus management ### **Visual Indicators** - Focus rings - Hover states - Disabled states - Loading indicators --- **File Size**: 299 lines **Priority**: 🟢 **P2 - WORKING** **Dependencies**: leptos