patternfly_yew/components/
about.rs

1//! About modal
2use crate::prelude::{use_backdrop, use_random_id, Button, ButtonVariant, Icon};
3use yew::prelude::*;
4use yew_hooks::{use_click_away, use_event_with_window};
5
6/// Properties for [`About`]
7#[derive(Clone, PartialEq, Properties)]
8pub struct AboutModalProperties {
9    /// Required Attributes
10    pub brand_image_src: AttrValue,
11    pub brand_image_alt: AttrValue,
12    pub children: Html,
13
14    /// Optional Attributes
15    #[prop_or(AttrValue::from("About Dialog"))]
16    pub aria_label: AttrValue,
17    #[prop_or_default]
18    pub background_image_src: AttrValue,
19    #[prop_or_default]
20    pub class: Classes,
21    #[prop_or(AttrValue::from("Close dialog"))]
22    pub close_button_aria_label: AttrValue,
23    #[prop_or_default]
24    pub product_name: AttrValue,
25    #[prop_or_default]
26    pub trademark: AttrValue,
27    #[prop_or_default]
28    pub onclose: Option<Callback<()>>,
29
30    /// Additional attributes not included in PF React
31    /// Disable closing the modal when the escape key is pressed
32    #[prop_or_default]
33    pub disable_close_escape: bool,
34    /// Disable closing the modal when the user clicks outside the modal
35    #[prop_or_default]
36    pub disable_close_click_outside: bool,
37    /// Id of the outermost element
38    #[prop_or_default]
39    pub id: Option<AttrValue>,
40}
41
42/// About modal component
43///
44/// > An **about modal** displays information about an application like product version number(s), as well as any appropriate legal text.
45///
46/// See: <https://www.patternfly.org/components/about-modal>
47///
48/// For a complete example, see the PatternFly Yew quickstart.
49///
50/// ## Properties
51///
52/// Defined by [`AboutModalProperties`].
53///
54/// ## Contexts
55///
56/// If the modal dialog is wrapped by a [`crate::prelude::BackdropViewer`] component and no
57/// `onclose` callback is set, then it will automatically close the backdrop when the modal dialog
58/// gets closed.
59///
60#[function_component(AboutModal)]
61pub fn about_modal(props: &AboutModalProperties) -> Html {
62    let backdrop = use_backdrop();
63
64    let onclose = use_memo(
65        (props.onclose.clone(), backdrop.clone()),
66        |(onclose, backdrop)| {
67            let onclose = onclose.clone();
68            let backdrop = backdrop.clone();
69            Callback::from(move |()| {
70                if let Some(onclose) = &onclose {
71                    onclose.emit(());
72                } else if let Some(backdrop) = &backdrop {
73                    backdrop.close();
74                }
75            })
76        },
77    );
78
79    // escape key
80    {
81        let disabled = props.disable_close_escape;
82        let onclose = onclose.clone();
83        use_event_with_window("keydown", move |e: KeyboardEvent| {
84            if !disabled && e.key() == "Escape" {
85                onclose.emit(());
86            }
87        });
88    }
89
90    // outside click
91    let node_ref = use_node_ref();
92
93    {
94        let disabled = props.disable_close_click_outside;
95        let onclose = onclose.clone();
96        use_click_away(node_ref.clone(), move |_: Event| {
97            if !disabled {
98                onclose.emit(());
99            }
100        });
101    }
102
103    let style = if props.background_image_src.is_empty() {
104        None
105    } else {
106        Some(format!(
107            "--pf-v5-c-about-modal-box--BackgroundImage: url({});",
108            &props.background_image_src
109        ))
110    };
111
112    let header_id = use_random_id();
113
114    let (aria_labelledby, aria_label, header) = if props.product_name.is_empty() {
115        // if we don't have a product name, use the aria label
116        (None::<String>, Some(props.aria_label.clone()), html!())
117    } else {
118        // if we have a product name, use it for the header, set the header id and ignore the label
119        (
120            Some((*header_id).to_string()),
121            None,
122            html!(
123                <div class="pf-v5-c-about-modal-box__header">
124                    <h1 class="pf-v5-c-title pf-m-4xl" id={*header_id}>{ props.product_name.clone() }</h1>
125                </div>
126            ),
127        )
128    };
129
130    html!(
131        <div
132            id={props.id.clone()}
133            class={classes!("pf-v5-c-about-modal-box", props.class.clone())}
134            {style}
135            role="dialog"
136            aria-modal="true"
137            aria-labelledby={aria_labelledby}
138            aria-label={aria_label}
139            ref={node_ref}
140        >
141            if !props.brand_image_src.is_empty() {
142                <div class="pf-v5-c-about-modal-box__brand">
143                    <img
144                      class="pf-v5-c-about-modal-box__brand-image"
145                      src={props.brand_image_src.clone()}
146                      alt={props.brand_image_alt.clone()}
147                    />
148                </div>
149            }
150
151            <div class="pf-v5-c-about-modal-box__close">
152                <Button
153                    variant={ButtonVariant::Plain}
154                    aria_label={props.close_button_aria_label.clone()}
155                    onclick={onclose.reform(|_|())}
156                >
157                    { Icon::Times }
158                </Button>
159            </div>
160
161            { header }
162
163            <div class="pf-v5-c-about-modal-box__content">
164                <div class="pf-v5-c-about-modal-box__body">
165                    { props.children.clone() }
166                </div>
167                if !props.trademark.is_empty() {
168                    <p class="pf-v5-c-about-modal-box__strapline">{ props.trademark.clone() }</p>
169                }
170            </div>
171        </div>
172    )
173}