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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
use derive_more::Display;
use yew::prelude::*;
use yewtil::NeqAssign;

#[derive(Clone, Debug, Properties, PartialEq)]
pub struct FieldProps {
    #[prop_or_default]
    pub children: Children,
    #[prop_or_default]
    pub classes: Option<String>,
    /// A text label for the field.
    #[prop_or_default]
    pub label: Option<String>,
    /// Extra classes for the label container.
    #[prop_or_default]
    pub label_classes: Option<String>,
    /// A help message for the field.
    #[prop_or_default]
    pub help: Option<String>,
    /// Extra classes for the help message container.
    #[prop_or_default]
    pub help_classes: Option<String>,
    /// Has icons on the left of the field's controls.
    #[prop_or_default]
    pub icons_left: bool,
    /// Has icons on the right of the field's controls.
    #[prop_or_default]
    pub icons_right: bool,
    /// Allow addons to the field's controls.
    #[prop_or_default]
    pub addons: bool,
    /// Alignment for the field's addons.
    #[prop_or_default]
    pub addons_align: Option<AddonsAlign>,
    /// All controls in this field should be grouped.
    #[prop_or_default]
    pub grouped: bool,
    /// Alignment for grouped controls.
    #[prop_or_default]
    pub grouped_align: Option<GroupedAlign>,
    /// Allow the grouped controls to span multiple lines.
    #[prop_or_default]
    pub multiline: bool,
}

/// A container for form controls
pub struct Field {
    props: FieldProps,
}

impl Component for Field {
    type Message = ();
    type Properties = FieldProps;

    fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
        Self { props }
    }

    fn update(&mut self, _: Self::Message) -> ShouldRender {
        false
    }

    fn change(&mut self, props: Self::Properties) -> ShouldRender {
        self.props.neq_assign(props)
    }

    fn view(&self) -> Html {
        let mut classes = Classes::from("field");
        if let Some(extra) = &self.props.classes {
            classes = classes.extend(extra);
        }
        if self.props.icons_left {
            classes.push("has-icons-left");
        }
        if self.props.icons_right {
            classes.push("has-icons-right");
        }
        if self.props.addons {
            classes.push("has-addons");
        }
        if let Some(align) = &self.props.addons_align {
            classes.push(&align.to_string());
        }
        if self.props.grouped {
            classes.push("is-grouped");
        }
        if let Some(align) = &self.props.grouped_align {
            classes.push(&align.to_string());
        }
        if self.props.multiline {
            classes.push("is-grouped-multiline");
        }

        // Build the label if label content is provided.
        let label = match &self.props.label {
            Some(label_content) => match &self.props.label_classes {
                Some(label_classes_str) => {
                    let mut label_classes = Classes::from(label_classes_str);
                    label_classes.push("label");
                    html! {<label class=label_classes>{label_content.clone()}</label>}
                }
                None => html! {<label class="label">{label_content.clone()}</label>},
            },
            None => html! {},
        };

        // Build the help label if present.
        let help = match &self.props.help {
            Some(help_content) => match &self.props.help_classes {
                Some(help_classes_str) => {
                    let mut help_classes = Classes::from(help_classes_str);
                    help_classes.push("help");
                    html! {<label class=help_classes>{help_content.clone()}</label>}
                }
                None => html! {<label class="help">{help_content.clone()}</label>},
            },
            None => html! {},
        };

        html! {
            <div class=classes>
                {label}
                {self.props.children.clone()}
                {help}
            </div>
        }
    }
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

#[derive(Clone, Debug, Properties, PartialEq)]
pub struct FieldHorizontalProps {
    #[prop_or_default]
    pub children: Children,
    #[prop_or_default]
    pub classes: Option<String>,
    /// The text label for the field.
    #[prop_or_default]
    pub label: String,
    /// The size of the contents for the field.
    #[prop_or_default]
    pub label_size: Option<LabelSize>,
}

/// A field wrapper to create horizontal fields.
///
/// https://bulma.io/documentation/form/general/#horizontal-form
pub struct FieldHorizontal {
    props: FieldHorizontalProps,
}

impl Component for FieldHorizontal {
    type Message = ();
    type Properties = FieldHorizontalProps;

    fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
        Self { props }
    }

    fn update(&mut self, _: Self::Message) -> ShouldRender {
        false
    }

    fn change(&mut self, props: Self::Properties) -> ShouldRender {
        self.props.neq_assign(props)
    }

    fn view(&self) -> Html {
        // field classes
        let mut classes = Classes::from("field is-horizontal");
        if let Some(extra) = &self.props.classes {
            classes = classes.extend(extra);
        }

        // label classes
        let mut labelclasses = Classes::from("field-label");
        if let Some(size) = &self.props.label_size {
            labelclasses.push(&size.to_string());
        }

        html! {
            <div class=classes>
                <div class=labelclasses>
                    <label class="label">{self.props.label.clone()}</label>
                </div>
                <div class="field-body">
                    {self.props.children.clone()}
                </div>
            </div>
        }
    }
}

/// The two alignment options available for field addons.
///
/// https://bulma.io/documentation/form/general/
#[derive(Clone, Debug, Display, PartialEq)]
#[display(fmt = "has-addons-{}")]
pub enum AddonsAlign {
    #[display(fmt = "centered")]
    Centered,
    #[display(fmt = "right")]
    Right,
}

/// The two alignment options available for grouped field controls.
///
/// https://bulma.io/documentation/form/general/
#[derive(Clone, Debug, Display, PartialEq)]
#[display(fmt = "is-grouped-{}")]
pub enum GroupedAlign {
    #[display(fmt = "centered")]
    Centered,
    #[display(fmt = "right")]
    Right,
}

/// The three sizes available for horizontal field labels.
///
/// https://bulma.io/documentation/form/general/#horizontal-form
#[derive(Clone, Debug, Display, PartialEq)]
#[display(fmt = "is-{}")]
pub enum LabelSize {
    #[display(fmt = "small")]
    Small,
    #[display(fmt = "medium")]
    Medium,
    #[display(fmt = "large")]
    Large,
}