patternfly_yew/components/
modal.rs1use crate::ouia;
3use crate::prelude::use_backdrop;
4use crate::prelude::wrap::wrapper_div_with_attributes;
5use crate::utils::{Ouia, OuiaComponentType, OuiaSafe};
6use yew::prelude::*;
7use yew::virtual_dom::ApplyAttributeAs;
8use yew_hooks::{use_click_away, use_event_with_window};
9
10const OUIA: Ouia = ouia!("ModalContent");
11
12#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
13pub enum ModalVariant {
14 #[default]
15 None,
16 Small,
17 Medium,
18 Large,
19}
20
21impl ModalVariant {
22 pub fn as_classes(&self) -> Classes {
23 match self {
24 ModalVariant::None => classes!(),
25 ModalVariant::Small => classes!("pf-m-sm"),
26 ModalVariant::Medium => classes!("pf-m-md"),
27 ModalVariant::Large => classes!("pf-m-lg"),
28 }
29 }
30}
31
32#[derive(Clone, PartialEq, Properties)]
34pub struct ModalProperties {
35 #[prop_or_default]
36 pub title: String,
37 #[prop_or_default]
38 pub description: String,
39 #[prop_or_default]
40 pub variant: ModalVariant,
41 #[prop_or_default]
42 pub children: Children,
43 #[prop_or_default]
44 pub footer: Option<Html>,
45
46 #[prop_or_default]
47 pub onclose: Option<Callback<()>>,
48
49 #[prop_or(true)]
51 pub show_close: bool,
52
53 #[prop_or_default]
55 pub disable_close_escape: bool,
56 #[prop_or_default]
58 pub disable_close_click_outside: bool,
59
60 #[prop_or_default]
62 pub ouia_id: Option<String>,
63 #[prop_or(OUIA.component_type())]
65 pub ouia_type: OuiaComponentType,
66 #[prop_or(OuiaSafe::TRUE)]
68 pub ouia_safe: OuiaSafe,
69}
70
71#[function_component(Modal)]
88pub fn modal(props: &ModalProperties) -> Html {
89 let ouia_id = use_memo(props.ouia_id.clone(), |id| {
90 id.clone().unwrap_or(OUIA.generated_id())
91 });
92 let mut classes = props.variant.as_classes();
93 classes.push("pf-v5-c-modal-box");
94
95 let backdrop = use_backdrop();
96
97 let onclose = use_memo((props.onclose.clone(), backdrop), |(onclose, backdrop)| {
98 let onclose = onclose.clone();
99 let backdrop = backdrop.clone();
100 Callback::from(move |()| {
101 if let Some(onclose) = &onclose {
102 onclose.emit(());
103 } else if let Some(backdrop) = &backdrop {
104 backdrop.close();
105 }
106 })
107 });
108
109 {
111 let disabled = props.disable_close_escape;
112 let onclose = onclose.clone();
113 use_event_with_window("keydown", move |e: KeyboardEvent| {
114 if !disabled && e.key() == "Escape" {
115 onclose.emit(());
116 }
117 });
118 }
119
120 let node_ref = use_node_ref();
123
124 {
125 let disabled = props.disable_close_click_outside;
126 let onclose = onclose.clone();
127 use_click_away(node_ref.clone(), move |_: Event| {
128 if !disabled {
129 onclose.emit(());
130 }
131 });
132 }
133
134 html! (
135 <div
136 class={classes}
137 role="dialog"
138 aria-modal="true"
139 aria-labelledby="modal-title"
140 aria-describedby="modal-description"
141 ref={node_ref}
142 data-ouia-component-id={(*ouia_id).clone()}
143 data-ouia-component-type={props.ouia_type}
144 data-ouia-safe={props.ouia_safe}
145 >
146 if props.show_close {
147 <div class="pf-v5-c-modal-box__close">
148 <button
149 class="pf-v5-c-button pf-m-plain"
150 type="button"
151 aria-label="Close dialog"
152 onclick={onclose.reform(|_|())}
153 >
154 <i class="fas fa-times" aria-hidden="true"></i>
155 </button>
156 </div>
157 }
158
159 <header class="pf-v5-c-modal-box__header">
160 <h1
161 class="pf-v5-c-modal-box__title"
162 id="modal-title-modal-with-form"
163 >{ &props.title }</h1>
164 </header>
165
166
167 if !&props.description.is_empty() {
168 <div class="pf-v5-c-modal-box__body">
169 <p>{ &props.description }</p>
170 </div>
171 }
172
173 { for props.children.iter().map(|c|{
174 wrapper_div_with_attributes(c,
175 &[
176 ("class", "pf-v5-c-modal-box__body", ApplyAttributeAs::Attribute),
177 ("id", "modal-description", ApplyAttributeAs::Attribute)
178 ])
179 }) }
180
181 if let Some(footer) = &props.footer {
182 <footer class="pf-v5-c-modal-box__footer">
183 { footer.clone() }
184 </footer>
185 }
186 </div>
187 )
188}