1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! Select control

use crate::prelude::*;
use yew::prelude::*;

/// Properties for [`SimpleSelect`].
#[derive(PartialEq, Properties)]
pub struct SimpleSelectProperties<T>
where
    T: Clone + Eq + SelectItemRenderer,
{
    #[prop_or_default]
    pub placeholder: Option<String>,

    #[prop_or_default]
    pub entries: Vec<T>,

    #[prop_or_default]
    pub selected: Option<T>,

    #[prop_or_default]
    pub onselect: Callback<T>,
}

/// Render an item for the [`SimpleSelect`] component.
pub trait SelectItemRenderer {
    type Item;

    fn label(&self) -> String;
}

impl<T> SelectItemRenderer for T
where
    T: std::fmt::Display,
{
    type Item = T;

    fn label(&self) -> String {
        self.to_string()
    }
}

/// A simple select component.
///
/// > A *select* list enables users to select one or more items from a list. Use a select list when options are dynamic or variable.
///
/// See: <https://www.patternfly.org/components/menus/select>
///
#[function_component(SimpleSelect)]
pub fn simple_select<T>(props: &SimpleSelectProperties<T>) -> Html
where
    T: Clone + Eq + SelectItemRenderer + 'static,
{
    let text = props
        .selected
        .as_ref()
        .map(|s| s.label())
        .or_else(|| props.placeholder.clone());

    html!(
        <Dropdown
            text={text.clone()}
        >
            { for props.entries.iter().map(|entry| {
                html_nested!(
                    <Raw>
                        <SimpleSelectItem<T>
                            entry={entry.clone()}
                            selected={props.selected.as_ref() == Some(entry)}
                            onselect={props.onselect.clone()}
                        />
                    </Raw>
                )
            }) }
        </Dropdown>
    )
}

#[derive(PartialEq, Properties)]
struct SimpleSelectItemProperties<T>
where
    T: Eq + SelectItemRenderer + 'static,
{
    entry: T,
    selected: bool,
    onselect: Callback<T>,
}

/// An item of the [`SimpleSelect`] component.
#[function_component(SimpleSelectItem)]
fn simple_select_item<T>(props: &SimpleSelectItemProperties<T>) -> Html
where
    T: Clone + Eq + SelectItemRenderer + 'static,
{
    let onclick = use_callback(
        (props.entry.clone(), props.onselect.clone()),
        |_, (entry, onselect)| {
            log::info!("Emit: {}", entry.label());
            onselect.emit(entry.clone());
        },
    );

    html!(
        <MenuAction
            {onclick}
            selected={props.selected}
        >
            { props.entry.label() }
        </MenuAction>)
}