leptos_shadcn_dialog/
default.rs1use leptos::{ev::MouseEvent, prelude::*};
2use leptos_node_ref::AnyNodeRef;
3use leptos_struct_component::{StructComponent, struct_component};
4use leptos_style::Style;
5
6#[component]
8pub fn Dialog(
9 #[prop(into, optional)] open: Signal<bool>,
10 #[prop(into, optional)] on_open_change: Option<Callback<bool>>,
11 #[prop(optional)] children: Option<Children>,
12) -> impl IntoView {
13 let internal_open = RwSignal::new(false);
14
15 let open_state = Signal::derive(move || {
16 if open.get() != internal_open.get() {
17 open.get()
18 } else {
19 internal_open.get()
20 }
21 });
22
23 let set_open = Callback::new(move |new_open: bool| {
24 internal_open.set(new_open);
25 if let Some(callback) = &on_open_change {
26 callback.run(new_open);
27 }
28 });
29
30 provide_context(DialogContextValue {
31 open: open_state,
32 set_open,
33 });
34
35 view! {
36 <div>
37 {children.map(|c| c())}
38 </div>
39 }
40}
41
42#[derive(Clone, Copy)]
43pub struct DialogContextValue {
44 pub open: Signal<bool>,
45 pub set_open: Callback<bool>,
46}
47
48#[derive(Clone, StructComponent)]
50#[struct_component(tag = "button")]
51pub struct DialogTriggerChildProps {
52 pub node_ref: AnyNodeRef,
53 pub class: Signal<String>,
54 pub id: MaybeProp<String>,
55 pub style: Signal<Style>,
56 pub disabled: Signal<bool>,
57 pub r#type: MaybeProp<String>,
58 pub onclick: Option<Callback<MouseEvent>>,
59}
60
61#[component]
62pub fn DialogTrigger(
63 #[prop(into, optional)] class: MaybeProp<String>,
64 #[prop(into, optional)] id: MaybeProp<String>,
65 #[prop(into, optional)] style: Signal<Style>,
66 #[prop(into, optional)] node_ref: AnyNodeRef,
67 #[prop(into, optional)] as_child: Option<Callback<DialogTriggerChildProps, AnyView>>,
68 #[prop(optional)] children: Option<Children>,
69) -> impl IntoView {
70 let ctx = expect_context::<DialogContextValue>();
71
72 let trigger_class = Signal::derive(move || {
73 format!("{}", class.get().unwrap_or_default())
74 });
75
76 let handle_click = Callback::new(move |_: MouseEvent| {
77 ctx.set_open.run(true);
78 });
79
80 let child_props = DialogTriggerChildProps {
81 node_ref,
82 class: trigger_class,
83 id,
84 style,
85 disabled: Signal::derive(|| false),
86 r#type: "button".to_string().into(),
87 onclick: Some(handle_click),
88 };
89
90 if let Some(as_child) = as_child.as_ref() {
91 as_child.run(child_props)
92 } else {
93 child_props.render(children)
94 }
95}
96
97#[component]
99pub fn DialogContent(
100 #[prop(into, optional)] class: MaybeProp<String>,
101 #[prop(into, optional)] style: Signal<Style>,
102 #[prop(optional)] children: Option<Children>,
103) -> impl IntoView {
104 let ctx = expect_context::<DialogContextValue>();
105
106 let content_class = Signal::derive(move || {
107 format!("fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg {}", class.get().unwrap_or_default())
108 });
109
110 if ctx.open.get() {
111 view! {
112 <div
113 class="fixed inset-0 z-50"
114 on:click=move |_| ctx.set_open.run(false)
115 >
116 <div
117 class={content_class}
118 style={move || style.get().to_string()}
119 on:click=|e: MouseEvent| e.stop_propagation()
120 >
121 {children.map(|c| c())}
122 </div>
123 </div>
124 }.into_any()
125 } else {
126 view! { <div></div> }.into_any()
127 }
128}
129
130#[component]
132pub fn DialogHeader(
133 #[prop(into, optional)] class: MaybeProp<String>,
134 #[prop(optional)] children: Option<Children>,
135) -> impl IntoView {
136 let header_class = Signal::derive(move || {
137 format!("flex flex-col space-y-1.5 text-center sm:text-left {}", class.get().unwrap_or_default())
138 });
139
140 view! {
141 <div class={header_class}>
142 {children.map(|c| c())}
143 </div>
144 }
145}
146
147#[component]
149pub fn DialogTitle(
150 #[prop(into, optional)] class: MaybeProp<String>,
151 #[prop(optional)] children: Option<Children>,
152) -> impl IntoView {
153 let title_class = Signal::derive(move || {
154 format!("text-lg font-semibold leading-none tracking-tight {}", class.get().unwrap_or_default())
155 });
156
157 view! {
158 <h2 class={title_class}>
159 {children.map(|c| c())}
160 </h2>
161 }
162}
163
164#[component]
166pub fn DialogDescription(
167 #[prop(into, optional)] class: MaybeProp<String>,
168 #[prop(optional)] children: Option<Children>,
169) -> impl IntoView {
170 let description_class = Signal::derive(move || {
171 format!("text-sm text-muted-foreground {}", class.get().unwrap_or_default())
172 });
173
174 view! {
175 <p class={description_class}>
176 {children.map(|c| c())}
177 </p>
178 }
179}
180
181#[component]
183pub fn DialogFooter(
184 #[prop(into, optional)] class: MaybeProp<String>,
185 #[prop(optional)] children: Option<Children>,
186) -> impl IntoView {
187 let footer_class = Signal::derive(move || {
188 format!("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 {}", class.get().unwrap_or_default())
189 });
190
191 view! {
192 <div class={footer_class}>
193 {children.map(|c| c())}
194 </div>
195 }
196}
197
198#[component]
200pub fn DialogClose(
201 #[prop(into, optional)] class: MaybeProp<String>,
202 #[prop(optional)] children: Option<Children>,
203) -> impl IntoView {
204 let ctx = expect_context::<DialogContextValue>();
205
206 let close_class = Signal::derive(move || {
207 format!("{}", class.get().unwrap_or_default())
208 });
209
210 view! {
211 <button
212 class={close_class}
213 on:click=move |_| ctx.set_open.run(false)
214 >
215 {children.map(|c| c())}
216 </button>
217 }
218}