Skip to main content

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    /// Cause component to consume the available height of its container.
70    #[prop_or_default]
71    pub full_height: bool,
72    /// Use plain styling. This removes border and background.
73    #[prop_or_default]
74    pub plain: bool,
75    /// Flag indicating if the card is expanded. Shows expandable content when `true`.
76    #[prop_or_default]
77    pub expanded: bool,
78    /// Add additional styles to the Card.
79    #[prop_or_default]
80    pub style: Option<AttrValue>,
81
82    /// OUIA Component id
83    #[prop_or_default]
84    pub ouia_id: Option<String>,
85    /// OUIA Component Type
86    #[prop_or(OUIA.component_type())]
87    pub ouia_type: OuiaComponentType,
88    /// OUIA Component Safe
89    #[prop_or(OuiaSafe::TRUE)]
90    pub ouia_safe: OuiaSafe,
91}
92
93#[derive(Debug, Clone, PartialEq)]
94struct CardContext {
95    card_id: AttrValue,
96    expanded: bool,
97    clickable: bool,
98    selectable: bool,
99    disabled: bool,
100}
101
102/// Card component
103///
104/// > 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.
105///
106/// See: <https://www.patternfly.org/components/card>
107///
108/// ## Properties
109///
110/// Defined by [`CardProperties`].
111///
112/// ## Children
113///
114/// Cards can have any number of [`CardBody`] or [`CardDivider`] children.
115///
116/// ## Example
117///
118/// ```
119/// use yew::prelude::*;
120/// use patternfly_yew::prelude::*;
121///
122/// #[function_component(Example)]
123/// fn example() -> Html {
124///   html!(
125///     <Card>
126///       <CardTitle>{"The heading"}</CardTitle>
127///       <CardBody>
128///         { "Foo" }
129///       </CardBody>
130///       <CardFooter>{"The footer"}</CardFooter>
131///     </Card>
132///   )
133/// }
134/// ```
135#[function_component(Card)]
136pub fn card(props: &CardProperties) -> Html {
137    let ouia_id = use_memo(props.ouia_id.clone(), |id| {
138        id.clone().unwrap_or(OUIA.generated_id())
139    });
140    let mut class = classes!("pf-v6-c-card");
141
142    if props.size == CardSize::Compact {
143        class.push("pf-m-compact");
144    }
145    if props.size == CardSize::Large {
146        class.push("pf-m-display-lg");
147    }
148    if props.disabled {
149        class.push("pf-m-disabled");
150    }
151    if props.expanded {
152        class.push("pf-m-expanded");
153    }
154    if props.flat {
155        class.push("pf-m-flat");
156    }
157    if props.selectable {
158        class.push("pf-m-selectable")
159    }
160    if props.selected {
161        class.push("pf-m-selected")
162    }
163    if props.full_height {
164        class.push("pf-m-full-height");
165    }
166    if props.plain {
167        class.push("pf-m-plain");
168    }
169    if props.selectable && props.clickable {
170        class.push("pf-m-selectable");
171        class.push("pf-m-clickable");
172        if props.selected {
173            class.push("pf-m-current");
174        }
175    } else if props.selectable {
176        class.push("pf-m-selectable");
177        if props.selected {
178            class.push("pf-m-selected");
179        }
180    } else if props.clickable {
181        class.push("pf-m-clickable");
182        if props.selected {
183            class.push("pf-m-selected");
184        }
185    }
186    class.extend(props.class.clone());
187
188    let context = CardContext {
189        card_id: props.id.clone(),
190        expanded: props.expanded,
191        clickable: props.clickable,
192        selectable: props.selectable,
193        disabled: props.disabled,
194    };
195
196    html! (
197        <ContextProvider<CardContext> {context}>
198            <@{props.component.clone()}
199                id={props.id.clone()}
200                {class}
201                style={props.style.clone()}
202                data-ouia-component-id={(*ouia_id).clone()}
203                data-ouia-component-type={props.ouia_type}
204                data-ouia-safe={props.ouia_safe}
205            >
206                {props.children.clone()}
207            </@>
208        </ContextProvider<CardContext>>
209    )
210}
211
212/// Specialized card divider component
213///
214/// This component is normally used as part of a list of card bodies.
215///
216/// ## Properties
217///
218/// This component does not have properties.
219#[function_component(CardDivider)]
220pub fn card_divider() -> Html {
221    html!(<Divider r#type={DividerType::Hr} />)
222}