collapsible.rs
1 use crate::use_unique_id; 2 use dioxus::html::GlobalAttributesExtension; 3 use dioxus::prelude::*; 4 use dioxus_primitives::collapsible::{ 5 Collapsible as PrimitiveCollapsible, CollapsibleContent as PrimitiveCollapsibleContent, 6 CollapsibleTrigger as PrimitiveCollapsibleTrigger, 7 }; 8 use lucide_dioxus::ChevronDown; 9 10 /// Props for the Collapsible component 11 #[derive(Props, Clone, PartialEq)] 12 pub struct CollapsibleProps { 13 /// Optional additional classes for the collapsible 14 #[props(default)] 15 pub class: Option<String>, 16 17 /// Optional ID for the collapsible element 18 #[props(default)] 19 pub id: Option<String>, 20 21 /// Child elements 22 pub children: Element, 23 } 24 25 /// Styled wrapper for the Collapsible component 26 #[component] 27 pub fn Collapsible(props: CollapsibleProps) -> Element { 28 // Generate unique ID if not provided 29 let collapsible_id = use_unique_id(); 30 let id_value = use_memo(move || { 31 props 32 .id 33 .clone() 34 .unwrap_or_else(|| collapsible_id.peek().clone()) 35 }); 36 37 let collapsible_classes = vec![ 38 // Base classes - clean container styling 39 "w-full border border-border rounded-lg", 40 // Additional classes passed by the user 41 props.class.as_deref().unwrap_or(""), 42 ] 43 .into_iter() 44 .filter(|s| !s.is_empty()) 45 .collect::<Vec<_>>() 46 .join(" "); 47 48 rsx! { 49 PrimitiveCollapsible { 50 id: id_value.peek().clone(), 51 class: collapsible_classes, 52 {props.children} 53 } 54 } 55 } 56 57 /// Props for the CollapsibleTrigger component 58 #[derive(Props, Clone, PartialEq)] 59 pub struct CollapsibleTriggerProps { 60 /// Optional additional classes for the trigger 61 #[props(default)] 62 pub class: Option<String>, 63 64 /// Optional ID for the trigger element 65 #[props(default)] 66 pub id: Option<String>, 67 68 /// Child elements 69 pub children: Element, 70 } 71 72 /// Styled wrapper for the CollapsibleTrigger component 73 #[component] 74 pub fn CollapsibleTrigger(props: CollapsibleTriggerProps) -> Element { 75 // Generate unique ID if not provided 76 let trigger_id = use_unique_id(); 77 let id_value = use_memo(move || { 78 props 79 .id 80 .clone() 81 .unwrap_or_else(|| trigger_id.peek().clone()) 82 }); 83 84 let trigger_classes = vec![ 85 // Base classes 86 "flex w-full items-center justify-between py-4 px-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", 87 88 // Additional classes passed by the user 89 props.class.as_deref().unwrap_or(""), 90 ] 91 .into_iter() 92 .filter(|s| !s.is_empty()) 93 .collect::<Vec<_>>() 94 .join(" "); 95 96 rsx! { 97 PrimitiveCollapsibleTrigger { 98 id: id_value.peek().clone(), 99 class: trigger_classes, 100 101 {props.children} 102 103 // Chevron icon 104 ChevronDown { 105 class: "h-4 w-4 shrink-0 transition-transform duration-200 data-[state=open]:rotate-180" 106 } 107 } 108 } 109 } 110 111 /// Props for the CollapsibleContent component 112 #[derive(Props, Clone, PartialEq)] 113 pub struct CollapsibleContentProps { 114 /// Optional additional classes for the content 115 #[props(default)] 116 pub class: Option<String>, 117 118 /// Optional ID for the content element 119 #[props(default)] 120 pub id: Option<String>, 121 122 /// Child elements 123 pub children: Element, 124 } 125 126 /// Styled wrapper for the CollapsibleContent component 127 #[component] 128 pub fn CollapsibleContent(props: CollapsibleContentProps) -> Element { 129 // Generate unique ID if not provided 130 let content_id = use_unique_id(); 131 let id_value = use_memo(move || { 132 props 133 .id 134 .clone() 135 .unwrap_or_else(|| content_id.peek().clone()) 136 }); 137 138 let content_classes = vec![ 139 // Base classes - shadcn ui inspired content styling 140 "overflow-hidden text-sm px-4 data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down", 141 142 // Additional classes passed by the user 143 props.class.as_deref().unwrap_or(""), 144 ] 145 .into_iter() 146 .filter(|s| !s.is_empty()) 147 .collect::<Vec<_>>() 148 .join(" "); 149 150 rsx! { 151 PrimitiveCollapsibleContent { 152 id: id_value.peek().clone(), 153 class: content_classes, 154 div { 155 class: "pb-4 pt-0", 156 {props.children} 157 } 158 } 159 } 160 }