patternfly_yew/components/card/header/
selectable.rs

1use super::*;
2
3/// Selectable actions for a card. Note the hints about `clickable` and `selectable` in the containing [`Card`].
4#[derive(Debug, Clone, PartialEq)]
5pub enum CardSelectableActionsVariant {
6    /// The entire card is clickable. Performs an action on click.
7    /// Clicking the card will highlight the card.
8    /// If you only want a single card out of a selection to be highlighted,
9    /// then make sure that the `name` field of the [`CardHeaderSelectableActionsObjectBase`]
10    /// has the same value in all [`Card`]s between which you want to differentiate.
11    /// Requires setting `clickable` to `true` and `selectable` to `false` in the containing [`Card`].
12    Click {
13        onclick: Option<Callback<MouseEvent>>,
14    },
15    /// Uses radio selection for selecting the card.
16    ///
17    /// Requires setting `clickable` to `true` in the containing [`Card`].
18    /// To make sure that only a single [`Card`] out of a group can be selected at once,
19    /// make sure that the `name` field of the [`CardHeaderSelectableActionsObjectBase`]
20    /// has the same value in all [`Card`]s between which you want to differentiate.
21    /// If `clickable` is `false` in the containing [`Card`], then clicking anywhere within the card will toggle the state of the radio button.
22    /// If `clickable is `true` in the containing` [`Card`], then only clicking the radio button itself will toggle the state of the radio button (to allow having other clickable content within the card such as links).
23    SingleSelect { onchange: Option<Callback<()>> },
24    /// Checkbox selection for selecting any amount of cards.
25    /// If `clickable` is `false` in the containing [`Card`], then clicking anywhere within the card will toggle the state of the checkbox.
26    /// If `clickable is `true` in the containing` [`Card`], then only clicking the radio button itself will toggle the state of the checkbox (to allow having other clickable content within the card such as links).
27    MultiSelect {
28        onchange: Callback<CheckboxState>,
29        checked: CheckboxState,
30    },
31}
32
33/// Interactions with a card through clicking on it.
34#[derive(Debug, Clone, PartialEq, Properties)]
35pub struct CardSelectableActionsObjectProperties {
36    /// The actual action.
37    pub action: CardSelectableActionsVariant,
38    /// Meta information common to any kind of action.
39    #[prop_or_default]
40    pub base: CardSelectableActionsObjectBase,
41}
42
43/// Metadata for a selectable action.
44#[derive(Debug, Clone, PartialEq, Properties, Default)]
45pub struct CardSelectableActionsObjectBase {
46    /// Remove the offset of the position of the actions to the header content.
47    /// This looks better if using large card titles or tall header images, for example.
48    #[prop_or_default]
49    pub has_no_offset: bool,
50    /// Additional classes to the selectable actions object.
51    #[prop_or_default]
52    pub class: Classes,
53    /// HTML id
54    #[prop_or_default]
55    pub id: Option<AttrValue>,
56    /// The name of the action. Use this field to group action across multiple cards.
57    /// This is useful for single selections to describe which cards can be selected from.
58    #[prop_or_default]
59    pub name: Option<AttrValue>,
60}
61
62#[function_component(CardSelectableActionsObject)]
63pub fn selectable_actions_object(props: &CardSelectableActionsObjectProperties) -> Html {
64    type Variant = CardSelectableActionsVariant;
65    match &props.action {
66        Variant::SingleSelect { onchange } => html! {
67            <SingleSelectActionRadio base={props.base.clone()} onchange={onchange.clone()} />
68        },
69        Variant::MultiSelect { onchange, checked } => html! {
70            <MultiSelectActionCheckbox base={props.base.clone()} onchange={onchange.clone()} checked={*checked} />
71        },
72        Variant::Click { onclick } => html! {
73            <ClickableInput base={props.base.clone()} onclick={onclick.clone()} />
74        },
75    }
76}
77
78#[derive(Debug, Clone, PartialEq, Properties)]
79struct SingleSelectActionRadioProperties {
80    base: CardSelectableActionsObjectBase,
81    onchange: Option<Callback<()>>,
82}
83
84struct CommonProps {
85    id: Option<String>,
86    name: Option<AttrValue>,
87    disabled: bool,
88    base_class: &'static str,
89}
90
91fn get_common_props(base: &CardSelectableActionsObjectBase, context: &CardContext) -> CommonProps {
92    CommonProps {
93        base_class: "pf-m-standalone",
94        id: base.id.as_ref().map(|s| s.to_string()),
95        name: base.name.as_ref().cloned(),
96        disabled: context.disabled,
97    }
98}
99
100#[function_component(SingleSelectActionRadio)]
101fn single_select_action_radio(props: &SingleSelectActionRadioProperties) -> Html {
102    let context: CardContext = use_context().expect("Couldn't find card context");
103    let onchange = {
104        let onchange = props.onchange.clone();
105        Callback::from(move |_| {
106            if let Some(f) = onchange.clone() {
107                f.emit(())
108            }
109        })
110    };
111    let common = get_common_props(&props.base, &context);
112    html! {
113        <Radio
114            class={common.base_class}
115            id={common.id}
116            name={common.name}
117            disabled={common.disabled}
118            {onchange}
119            force_label=true
120        />
121    }
122}
123
124#[derive(Debug, Clone, PartialEq, Properties)]
125struct MultiSelectActionCheckboxProperties {
126    base: CardSelectableActionsObjectBase,
127    onchange: Callback<CheckboxState>,
128    checked: CheckboxState,
129}
130
131#[function_component(MultiSelectActionCheckbox)]
132fn multi_select_action_checkbox(props: &MultiSelectActionCheckboxProperties) -> Html {
133    let context: CardContext = use_context().expect("Couldn't find card context");
134    let common = get_common_props(&props.base, &context);
135    html! {
136        <Checkbox
137            class={common.base_class}
138            id={common.id}
139            name={common.name}
140            disabled={common.disabled}
141            onchange={props.onchange.clone()}
142            checked={props.checked}
143            label={html!()}
144        />
145    }
146}
147
148#[derive(Debug, Clone, PartialEq, Properties)]
149struct ClickableInputProperties {
150    base: CardSelectableActionsObjectBase,
151    onclick: Option<Callback<MouseEvent>>,
152}
153
154#[function_component(ClickableInput)]
155fn clickable_input_action_radio(props: &ClickableInputProperties) -> Html {
156    let context: CardContext = use_context().expect("Couldn't find card context");
157    if context.selectable {
158        log::warn!("Using a click action for an entire tile in a selectable card doesn't work. Set `selectable` to `false` in card `{}`", context.card_id);
159    }
160    let common = get_common_props(&props.base, &context);
161    html! {
162        <Radio
163            class={common.base_class}
164            id={common.id}
165            name={common.name}
166            disabled={common.disabled}
167            input_class="pf-v5-screen-reader"
168            input_onclick={props.onclick.clone()}
169            force_label=true
170        />
171    }
172}