patternfly_yew/components/card/
mod.rs

1use crate::prelude::{Divider, DividerType, OuiaComponentType};
2use crate::utils::{Ouia, OuiaSafe};
3use yew::prelude::*;
4
5const OUIA: Ouia = ouia!("Card");
6
7mod actions;
8mod body;
9mod expandable_content;
10mod footer;
11mod header;
12mod selectable_actions;
13mod title;
14
15use crate::ouia;
16pub use actions::*;
17pub use body::*;
18pub use expandable_content::*;
19pub use footer::*;
20pub use header::*;
21pub use selectable_actions::*;
22pub use title::*;
23
24/// The size of a [`Card`].
25#[derive(Debug, Clone, Copy, Default, PartialEq)]
26pub enum CardSize {
27    #[default]
28    Default,
29    Compact,
30    Large,
31}
32
33/// Properties for [`Card`]
34#[derive(Clone, PartialEq, Properties)]
35pub struct CardProperties {
36    /// Content rendered inside the Card.
37    #[prop_or_default]
38    pub children: Html,
39    /// ID of the card. Also passed back in the CardHeader onexpand callback.
40    #[prop_or_default]
41    pub id: AttrValue,
42    /// Additional classes added to the card.
43    #[prop_or_default]
44    pub class: Classes,
45    /// Sets the base component to render. Defaults to "div".
46    #[prop_or(String::from("div"))]
47    pub component: String,
48    /// The size of the Card. View [`CardSize`] for more info.
49    #[prop_or_default]
50    pub size: CardSize,
51    /// Modifies the card to include selectable styling. Check [`CardSelectableActionsVariant`] for more info.
52    #[prop_or_default]
53    pub selectable: bool,
54    /// Styles the card as selected.
55    #[prop_or_default]
56    pub selected: bool,
57    /// Modifies the card to include clickable styling.
58    /// If `selectable` is also true, then this allows clicking things within the card (such as links and buttons).
59    /// If `selectable` is false, then you can supply a [`CardSelectableActionsVariant::Click`] to
60    /// perform an action if any part of the card is clicked.
61    #[prop_or_default]
62    pub clickable: bool,
63    /// Modifies a clickable or selectable card to have disabled styling.
64    #[prop_or_default]
65    pub disabled: bool,
66    /// Use flat styling.
67    #[prop_or_default]
68    pub flat: bool,
69    /// Modifies the card to include rounded styling.
70    #[prop_or_default]
71    pub rounded: bool,
72    /// Cause component to consume the available height of its container.
73    #[prop_or_default]
74    pub full_height: bool,
75    /// Use plain styling. This removes border and background.
76    #[prop_or_default]
77    pub plain: bool,
78    /// Flag indicating if the card is expanded. Shows expandable content when `true`.
79    #[prop_or_default]
80    pub expanded: bool,
81    /// Add additional styles to the Card.
82    #[prop_or_default]
83    pub style: Option<AttrValue>,
84
85    /// OUIA Component id
86    #[prop_or_default]
87    pub ouia_id: Option<String>,
88    /// OUIA Component Type
89    #[prop_or(OUIA.component_type())]
90    pub ouia_type: OuiaComponentType,
91    /// OUIA Component Safe
92    #[prop_or(OuiaSafe::TRUE)]
93    pub ouia_safe: OuiaSafe,
94}
95
96#[derive(Debug, Clone, PartialEq)]
97struct CardContext {
98    card_id: AttrValue,
99    expanded: bool,
100    clickable: bool,
101    selectable: bool,
102    disabled: bool,
103}
104
105/// Card component
106///
107/// > A **card** is a square or rectangular container that can contain any kind of content. Cards symbolize units of information, and each one acts as an entry point for users to access more details. For example, in dashboards and catalog views, cards function as a preview of a detailed page. Cards may also be used in data displays like card views, or for positioning content on a page.
108///
109/// See: <https://www.patternfly.org/components/card>
110///
111/// ## Properties
112///
113/// Defined by [`CardProperties`].
114///
115/// ## Children
116///
117/// Cards can have any number of [`CardBody`] or [`CardDivider`] children.
118///
119/// ## Example
120///
121/// ```
122/// use yew::prelude::*;
123/// use patternfly_yew::prelude::*;
124///
125/// #[function_component(Example)]
126/// fn example() -> Html {
127///   html!(
128///     <Card>
129///       <CardTitle>{"The heading"}</CardTitle>
130///       <CardBody>
131///         { "Foo" }
132///       </CardBody>
133///       <CardFooter>{"The footer"}</CardFooter>
134///     </Card>
135///   )
136/// }
137/// ```
138#[function_component(Card)]
139pub fn card(props: &CardProperties) -> Html {
140    let ouia_id = use_memo(props.ouia_id.clone(), |id| {
141        id.clone().unwrap_or(OUIA.generated_id())
142    });
143    let mut class = classes!("pf-v5-c-card");
144
145    if props.size == CardSize::Compact {
146        class.push("pf-m-compact");
147    }
148    if props.size == CardSize::Large {
149        class.push("pf-m-display-lg");
150    }
151    if props.disabled {
152        class.push("pf-m-disabled");
153    }
154    if props.expanded {
155        class.push("pf-m-expanded");
156    }
157    if props.flat {
158        class.push("pf-m-flat");
159    }
160    if props.selectable {
161        class.push("pf-m-selectable")
162    }
163    if props.selected {
164        class.push("pf-m-selected")
165    }
166    if props.full_height {
167        class.push("pf-m-full-height");
168    }
169    if props.rounded {
170        class.push("pf-m-rounded");
171    }
172    if props.plain {
173        class.push("pf-m-plain");
174    }
175    if props.selectable && props.clickable {
176        class.push("pf-m-selectable");
177        class.push("pf-m-clickable");
178        if props.selected {
179            class.push("pf-m-current");
180        }
181    } else if props.selectable {
182        class.push("pf-m-selectable");
183        if props.selected {
184            class.push("pf-m-selected");
185        }
186    } else if props.clickable {
187        class.push("pf-m-clickable");
188        if props.selected {
189            class.push("pf-m-selected");
190        }
191    }
192    class.extend(props.class.clone());
193
194    let context = CardContext {
195        card_id: props.id.clone(),
196        expanded: props.expanded,
197        clickable: props.clickable,
198        selectable: props.selectable,
199        disabled: props.disabled,
200    };
201
202    html! (
203        <ContextProvider<CardContext> {context}>
204            <@{props.component.clone()}
205                id={props.id.clone()}
206                {class}
207                style={props.style.clone()}
208                data-ouia-component-id={(*ouia_id).clone()}
209                data-ouia-component-type={props.ouia_type}
210                data-ouia-safe={props.ouia_safe}
211            >
212                {props.children.clone()}
213            </@>
214        </ContextProvider<CardContext>>
215    )
216}
217
218/// Specialized card divider component
219///
220/// This component is normally used as part of a list of card bodies.
221///
222/// ## Properties
223///
224/// This component does not have properties.
225#[function_component(CardDivider)]
226pub fn card_divider() -> Html {
227    html!(<Divider r#type={DividerType::Hr} />)
228}