Skip to main content

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
71                base={props.base.clone()}
72                onchange={onchange.clone()}
73                checked={*checked}
74            />
75        },
76        Variant::Click { onclick } => html! {
77            <ClickableInput base={props.base.clone()} onclick={onclick.clone()} />
78        },
79    }
80}
81
82#[derive(Debug, Clone, PartialEq, Properties)]
83struct SingleSelectActionRadioProperties {
84    base: CardSelectableActionsObjectBase,
85    onchange: Option<Callback<()>>,
86}
87
88struct CommonProps {
89    id: Option<String>,
90    name: Option<AttrValue>,
91    disabled: bool,
92    base_class: &'static str,
93}
94
95fn get_common_props(base: &CardSelectableActionsObjectBase, context: &CardContext) -> CommonProps {
96    CommonProps {
97        base_class: "pf-m-standalone",
98        id: base.id.as_ref().map(|s| s.to_string()),
99        name: base.name.as_ref().cloned(),
100        disabled: context.disabled,
101    }
102}
103
104#[function_component(SingleSelectActionRadio)]
105fn single_select_action_radio(props: &SingleSelectActionRadioProperties) -> Html {
106    let context: CardContext = use_context().expect("Couldn't find card context");
107    let onchange = {
108        let onchange = props.onchange.clone();
109        Callback::from(move |_| {
110            if let Some(f) = onchange.clone() {
111                f.emit(())
112            }
113        })
114    };
115    let common = get_common_props(&props.base, &context);
116    html! {
117        <Radio
118            class={common.base_class}
119            id={common.id}
120            name={common.name}
121            disabled={common.disabled}
122            {onchange}
123            force_label=true
124        />
125    }
126}
127
128#[derive(Debug, Clone, PartialEq, Properties)]
129struct MultiSelectActionCheckboxProperties {
130    base: CardSelectableActionsObjectBase,
131    onchange: Callback<CheckboxState>,
132    checked: CheckboxState,
133}
134
135#[function_component(MultiSelectActionCheckbox)]
136fn multi_select_action_checkbox(props: &MultiSelectActionCheckboxProperties) -> Html {
137    let context: CardContext = use_context().expect("Couldn't find card context");
138    let common = get_common_props(&props.base, &context);
139    html! {
140        <Checkbox
141            class={common.base_class}
142            id={common.id}
143            name={common.name}
144            disabled={common.disabled}
145            onchange={props.onchange.clone()}
146            checked={props.checked}
147            label={html!()}
148        />
149    }
150}
151
152#[derive(Debug, Clone, PartialEq, Properties)]
153struct ClickableInputProperties {
154    base: CardSelectableActionsObjectBase,
155    onclick: Option<Callback<MouseEvent>>,
156}
157
158#[function_component(ClickableInput)]
159fn clickable_input_action_radio(props: &ClickableInputProperties) -> Html {
160    let context: CardContext = use_context().expect("Couldn't find card context");
161    if context.selectable {
162        log::warn!(
163            "Using a click action for an entire tile in a selectable card doesn't work. Set `selectable` to `false` in card `{}`",
164            context.card_id
165        );
166    }
167    let common = get_common_props(&props.base, &context);
168    html! {
169        <Radio
170            class={common.base_class}
171            id={common.id}
172            name={common.name}
173            disabled={common.disabled}
174            input_class="pf-v6-screen-reader"
175            input_onclick={props.onclick.clone()}
176            force_label=true
177        />
178    }
179}