diff --git a/jirs-client/src/components/styled_checkbox.rs b/jirs-client/src/components/styled_checkbox.rs index 9a6dce2b..05400f31 100644 --- a/jirs-client/src/components/styled_checkbox.rs +++ b/jirs-client/src/components/styled_checkbox.rs @@ -81,7 +81,7 @@ impl<'l> ChildBuilder<'l> { } } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct StyledCheckbox<'l, Options> where Options: Iterator>, @@ -90,6 +90,18 @@ where pub class_list: &'l str, } +impl<'l, Options> Default for StyledCheckbox<'l, Options> +where + Options: Iterator>, +{ + fn default() -> Self { + Self { + options: None, + class_list: "", + } + } +} + impl<'l, Options> StyledCheckbox<'l, Options> where Options: Iterator>, diff --git a/jirs-client/src/components/styled_editor.rs b/jirs-client/src/components/styled_editor.rs index 3be6acaa..ef0cda40 100644 --- a/jirs-client/src/components/styled_editor.rs +++ b/jirs-client/src/components/styled_editor.rs @@ -49,89 +49,81 @@ impl<'l> Default for StyledEditor<'l> { } } -impl<'l> ToNode for StyledEditor<'l> { +impl<'l> StyledEditor<'l> { #[inline] - fn into_node(self) -> Node { - render(self) + pub fn render(self) -> Node { + let StyledEditor { + id, + initial_text, + text: _, + html, + mode, + update_event, + } = self; + + let id = id.expect("Styled Editor requires ID"); + let on_editor_clicked = click_handler(id.clone(), Mode::Editor); + let on_view_clicked = click_handler(id.clone(), Mode::View); + + let editor_id = format!("editor-{}", id); + let view_id = format!("view-{}", id); + let name = format!("styled-editor-{}", id); + + let text_area = StyledTextarea { + id: Some(id), + height: 40, + max_height: 0, + value: initial_text, + class_list: "", + update_event, + placeholder: "", + disable_auto_resize: false, + } + .into_node(); + + div![ + C!["styledEditor"], + label![ + C![ + "navbar viewTab activeTab", + IF![mode == Mode::View => "activeTab"] + ], + attrs![At::For => view_id.as_str()], + "View", + on_view_clicked + ], + label![ + C!["navbar editorTab", IF![mode == Mode::Editor => "activeTab"]], + attrs![At::For => editor_id.as_str()], + "Editor", + on_editor_clicked + ], + seed::input![ + id![editor_id.as_str()], + C!["editorRadio"], + attrs![At::Type => "radio"; At::Name => name.as_str(); At::Checked => true], + ], + text_area, + seed::input![ + id![view_id.as_str()], + C!["viewRadio"], + attrs![ At::Type => "radio"; At::Name => name.as_str();], + IF![mode == Mode::View => attrs![At::Checked => true]] + ], + div![ + C!["view"], + IF![mode == Mode::Editor => empty![]], + IF![mode == Mode::View => raw![html]], + ], + ] } } -#[inline] -pub fn render(values: StyledEditor) -> Node { - let StyledEditor { - id, - initial_text, - text: _, - html, - mode, - update_event, - } = values; - - let id = id.expect("Styled Editor requires ID"); - let on_editor_clicked = click_handler(id.clone(), Mode::Editor); - let on_view_clicked = click_handler(id.clone(), Mode::View); - - let editor_id = format!("editor-{}", id); - let view_id = format!("view-{}", id); - let name = format!("styled-editor-{}", id); - - let text_area = StyledTextarea { - id: Some(id), - height: 40, - max_height: 0, - value: initial_text, - class_list: "", - update_event, - placeholder: "", - disable_auto_resize: false, +impl<'l> ToNode for StyledEditor<'l> { + #[inline] + fn into_node(self) -> Node { + self.render() } - .into_node(); - - let (editor_radio_node, view_radio_node) = ( - seed::input![ - id![editor_id.as_str()], - C!["editorRadio"], - attrs![At::Type => "radio"; At::Name => name.as_str(); At::Checked => true], - ], - seed::input![ - id![view_id.as_str()], - C!["viewRadio"], - attrs![ At::Type => "radio"; At::Name => name.as_str();], - IF![mode == Mode::View => attrs![At::Checked => true]] - ], - ); - - div![ - C!["styledEditor"], - label![ - if mode == Mode::View { - C!["navbar viewTab activeTab"] - } else { - C!["navbar viewTab"] - }, - attrs![At::For => view_id.as_str()], - "View", - on_view_clicked - ], - label![ - if mode == Mode::Editor { - C!["navbar editorTab activeTab"] - } else { - C!["navbar editorTab"] - }, - attrs![At::For => editor_id.as_str()], - "Editor", - on_editor_clicked - ], - editor_radio_node, - text_area, - view_radio_node, - div![ - C!["view"], - IF![mode == Mode::Editor => empty![]], - IF![mode == Mode::View => raw![html]], - ], - ] } #[inline] diff --git a/jirs-client/src/components/styled_field.rs b/jirs-client/src/components/styled_field.rs index 9935749e..940ac038 100644 --- a/jirs-client/src/components/styled_field.rs +++ b/jirs-client/src/components/styled_field.rs @@ -23,25 +23,27 @@ impl<'l> Default for StyledField<'l> { } } -impl<'l> ToNode for StyledField<'l> { - fn into_node(self) -> Node { - render(self) +impl<'l> StyledField<'l> { + pub fn render(self) -> Node { + let StyledField { + label, + tip, + input, + class_list, + } = self; + let tip_node = tip.map_or_else(|| empty![], |s| div![C!["styledTip"], s]); + + div![ + C!["styledField", class_list], + seed::label![C!["styledLabel"], label], + input, + tip_node, + ] } } -pub fn render(values: StyledField) -> Node { - let StyledField { - label, - tip, - input, - class_list, - } = values; - let tip_node = tip.map(|s| div![C!["styledTip"], s]).unwrap_or(empty![]); - - div![ - C!["styledField", class_list], - seed::label![C!["styledLabel"], label], - input, - tip_node, - ] +impl<'l> ToNode for StyledField<'l> { + fn into_node(self) -> Node { + self.render() + } } diff --git a/jirs-client/src/components/styled_form.rs b/jirs-client/src/components/styled_form.rs index ed9f8c4d..4e45dd2b 100644 --- a/jirs-client/src/components/styled_form.rs +++ b/jirs-client/src/components/styled_form.rs @@ -11,27 +11,29 @@ pub struct StyledForm<'l> { pub on_submit: Option>, } -impl<'l> ToNode for StyledForm<'l> { +impl<'l> StyledForm<'l> { #[inline] - fn into_node(self) -> Node { - render(self) + pub fn render(self) -> Node { + let StyledForm { + heading, + fields, + on_submit, + } = self; + let handlers = match on_submit { + Some(handler) => vec![handler], + _ => vec![], + }; + seed::form![ + handlers, + C!["styledForm"], + div![C!["formElement"], div![C!["formHeading"], heading], fields], + ] } } -#[inline] -pub fn render(values: StyledForm) -> Node { - let StyledForm { - heading, - fields, - on_submit, - } = values; - let handlers = match on_submit { - Some(handler) => vec![handler], - _ => vec![], - }; - seed::form![ - handlers, - C!["styledForm"], - div![C!["formElement"], div![C!["formHeading"], heading], fields], - ] +impl<'l> ToNode for StyledForm<'l> { + #[inline] + fn into_node(self) -> Node { + self.render() + } } diff --git a/jirs-client/src/components/styled_icon.rs b/jirs-client/src/components/styled_icon.rs index 59b1377a..f5c396dc 100644 --- a/jirs-client/src/components/styled_icon.rs +++ b/jirs-client/src/components/styled_icon.rs @@ -271,53 +271,55 @@ impl<'l> Default for StyledIcon<'l> { impl<'l> ToNode for StyledIcon<'l> { fn into_node(self) -> Node { - render(self) + self.render() } } -pub fn render(values: StyledIcon) -> Node { - let StyledIcon { - icon, - size, - color, - class_list, - style_list, - on_click, - } = values; +impl<'l> StyledIcon<'l> { + pub fn render(self) -> Node { + let StyledIcon { + icon, + size, + color, + class_list, + style_list, + on_click, + } = self; - let styles: Vec = vec![ - size.map(|s| { - let font_size = format!("font-size: {}", s); - attrs![At::Style => font_size] - }), - icon.to_color().map(|s| { - let color = format!("color: {}", s); - attrs![At::Style => color] - }), - color.map(|s| attrs![At::Style => format!("color: var(--{})", s)]), - ] - .into_iter() - .flatten() - .collect(); + let styles: Vec = vec![ + size.map(|s| { + let font_size = format!("font-size: {}", s); + attrs![At::Style => font_size] + }), + icon.to_color().map(|s| { + let color = format!("color: {}", s); + attrs![At::Style => color] + }), + color.map(|s| attrs![At::Style => format!("color: var(--{})", s)]), + ] + .into_iter() + .flatten() + .collect(); - let style_list = style_list.into_iter().fold("".to_string(), |mut mem, s| { - match s { - Cow::Borrowed(s) => { - mem.push_str(s); + let style_list = style_list.into_iter().fold("".to_string(), |mut mem, s| { + match s { + Cow::Borrowed(s) => { + mem.push_str(s); + } + Cow::Owned(owned) => { + mem.push_str(owned.as_str()); + } } - Cow::Owned(owned) => { - mem.push_str(owned.as_str()); - } - } - mem.push(';'); - mem - }); + mem.push(';'); + mem + }); - i![ - C!["styledIcon", class_list, icon.to_str()], - styles, - attrs![ At::Style => style_list ], - on_click, - "" - ] + i![ + C!["styledIcon", class_list, icon.to_str()], + styles, + attrs![ At::Style => style_list ], + on_click, + "" + ] + } } diff --git a/jirs-client/src/components/styled_image_input.rs b/jirs-client/src/components/styled_image_input.rs index 1973e16b..7bb0ce9b 100644 --- a/jirs-client/src/components/styled_image_input.rs +++ b/jirs-client/src/components/styled_image_input.rs @@ -39,46 +39,48 @@ pub struct StyledImageInput<'l> { impl<'l> ToNode for StyledImageInput<'l> { fn into_node(self) -> Node { - render(self) + self.render() } } -fn render(values: StyledImageInput) -> Node { - let StyledImageInput { - id, - class_list, - url, - } = values; +impl<'l> StyledImageInput<'l> { + pub fn render(self) -> Node { + let StyledImageInput { + id, + class_list, + url, + } = self; - let field_id = id.clone(); - let on_change = ev(Ev::Change, move |ev| { - let target = ev.target().unwrap(); - let input = seed::to_input(&target); - let v = input - .files() - .map(|list| { - let mut v = vec![]; - for i in 0..list.length() { - v.push(list.get(i).unwrap()); - } - v - }) - .unwrap_or_default(); - Msg::FileInputChanged(field_id, v) - }); - let input_id = id.to_string(); + let field_id = id.clone(); + let on_change = ev(Ev::Change, move |ev| { + let target = ev.target().unwrap(); + let input = seed::to_input(&target); + let v = input + .files() + .map(|list| { + let mut v = vec![]; + for i in 0..list.length() { + v.push(list.get(i).unwrap()); + } + v + }) + .unwrap_or_default(); + Msg::FileInputChanged(field_id, v) + }); + let input_id = id.to_string(); - div![ - C!["styledImageInput", class_list], - label![ - C!["label"], - attrs![At::For => input_id], - img![C!["mask"], attrs![At::Src => url.unwrap_or_default()], " "] - ], - input![ - C!["input"], - attrs![At::Type => "file", At::Id => input_id], - on_change + div![ + C!["styledImageInput", class_list], + label![ + C!["label"], + attrs![At::For => input_id], + img![C!["mask"], attrs![At::Src => url.unwrap_or_default()], " "] + ], + input![ + C!["input"], + attrs![At::Type => "file", At::Id => input_id], + on_change + ] ] - ] + } } diff --git a/jirs-client/src/components/styled_input.rs b/jirs-client/src/components/styled_input.rs index 27323e3b..743ca5c9 100644 --- a/jirs-client/src/components/styled_input.rs +++ b/jirs-client/src/components/styled_input.rs @@ -163,62 +163,64 @@ impl<'l, 'm: 'l> StyledInput<'l, 'm> { impl<'l, 'm: 'l> ToNode for StyledInput<'l, 'm> { #[inline] fn into_node(self) -> Node { - render(self) + self.render() } } -pub fn render(values: StyledInput) -> Node { - let StyledInput { - id, - icon, - valid, - value, - input_type, - input_class_list, - wrapper_class_list, - variant, - auto_focus, - input_handlers, - } = values; - let id = id.expect("Input id is required"); - - let icon_node = icon - .map(|icon| StyledIcon::from(icon).into_node()) - .unwrap_or(Node::Empty); - - let on_change = { - let field_id = id.clone(); - ev(Ev::Change, move |event| { - event.stop_propagation(); - let target = event.target().unwrap(); - Msg::StrInputChanged(field_id, seed::to_input(&target).value()) - }) - }; - - div![ - C![ - "styledInput", - format!("{}", id), - variant.to_str(), - wrapper_class_list - ], - IF![!valid => C!["invalid"]], - icon_node, - seed::input![ - C![ - "inputElement", - variant.to_str(), - input_class_list, - icon.as_ref().map(|_| "withIcon").unwrap_or_default() - ], - attrs![ - "id" => format!("{}", id), - "value" => value, - "type" => input_type.unwrap_or("text"), - ], - IF![auto_focus => attrs![At::AutoFocus => true]], - on_change, +impl<'l, 'm: 'l> StyledInput<'l, 'm> { + pub fn render(self) -> Node { + let StyledInput { + id, + icon, + valid, + value, + input_type, + input_class_list, + wrapper_class_list, + variant, + auto_focus, input_handlers, - ], - ] + } = self; + let id = id.expect("Input id is required"); + + let icon_node = icon + .map(|icon| StyledIcon::from(icon).render()) + .unwrap_or(Node::Empty); + + let on_change = { + let field_id = id.clone(); + ev(Ev::Change, move |event| { + event.stop_propagation(); + let target = event.target().unwrap(); + Msg::StrInputChanged(field_id, seed::to_input(&target).value()) + }) + }; + + div![ + C![ + "styledInput", + format!("{}", id), + variant.to_str(), + wrapper_class_list + ], + IF![!valid => C!["invalid"]], + icon_node, + seed::input![ + C![ + "inputElement", + variant.to_str(), + input_class_list, + icon.as_ref().map(|_| "withIcon").unwrap_or_default() + ], + attrs![ + "id" => format!("{}", id), + "value" => value, + "type" => input_type.unwrap_or("text"), + ], + IF![auto_focus => attrs![At::AutoFocus => true]], + on_change, + input_handlers, + ], + ] + } } diff --git a/jirs-client/src/components/styled_link.rs b/jirs-client/src/components/styled_link.rs index c083d682..98aa5782 100644 --- a/jirs-client/src/components/styled_link.rs +++ b/jirs-client/src/components/styled_link.rs @@ -14,36 +14,38 @@ pub struct StyledLink<'l> { impl<'l> ToNode for StyledLink<'l> { fn into_node(self) -> Node { - render(self) + self.render() } } -pub fn render(values: StyledLink) -> Node { - let StyledLink { - children, - class_list, - href, - } = values; +impl<'l> StyledLink<'l> { + pub fn render(self) -> Node { + let StyledLink { + children, + class_list, + href, + } = self; - let on_click = { - let href = href.to_string(); - mouse_ev("click", move |ev| { - if href.starts_with('/') { - ev.prevent_default(); - ev.stop_propagation(); - if let Ok(url) = seed::Url::from_str(href.as_str()) { - url.go_and_push(); + let on_click = { + let href = href.to_string(); + mouse_ev("click", move |ev| { + if href.starts_with('/') { + ev.prevent_default(); + ev.stop_propagation(); + if let Ok(url) = seed::Url::from_str(href.as_str()) { + url.go_and_push(); + } } - } - None as Option - }) - }; + None as Option + }) + }; - a![ - C!["styledLink", class_list], - attrs![ At::Href => href, ], - on_click, - children, - ] + a![ + C!["styledLink", class_list], + attrs![ At::Href => href, ], + on_click, + children, + ] + } } diff --git a/jirs-client/src/components/styled_modal.rs b/jirs-client/src/components/styled_modal.rs index ae338fa5..3ac5e0c4 100644 --- a/jirs-client/src/components/styled_modal.rs +++ b/jirs-client/src/components/styled_modal.rs @@ -63,60 +63,55 @@ impl<'l> StyledModal<'l> { impl<'l> ToNode for StyledModal<'l> { fn into_node(self) -> Node { - render(self) + self.render() } } -#[inline] -pub fn render(values: StyledModal) -> Node { - let StyledModal { - variant, - width, - with_icon, - children, - class_list, - } = values; +impl<'l> StyledModal<'l> { + #[inline] + pub fn render(self) -> Node { + let StyledModal { + variant, + width, + with_icon, + children, + class_list, + } = self; - let icon = if with_icon { - StyledIcon { - icon: Icon::Close, - class_list: variant.to_icon_class_name(), - ..Default::default() - } - .into_node() - } else { - empty![] - }; + let close_handler = mouse_ev(Ev::Click, |ev| { + ev.stop_propagation(); + ev.prevent_default(); + Msg::ModalDropped + }); + let body_handler = mouse_ev(Ev::Click, |ev| { + ev.stop_propagation(); + ev.prevent_default(); + None as Option + }); - let close_handler = mouse_ev(Ev::Click, |ev| { - ev.stop_propagation(); - ev.prevent_default(); - Msg::ModalDropped - }); - let body_handler = mouse_ev(Ev::Click, |ev| { - ev.stop_propagation(); - ev.prevent_default(); - None as Option - }); - - let clickable_class = format!("clickableOverlay {}", variant.to_class_name()); - let styled_modal_style = match width { - Some(0) => "".to_string(), - Some(n) => format!("max-width: {width}px", width = n), - _ => format!("max-width: {width}px", width = 130), - }; - div![ - C!["modal"], + let styled_modal_style = match width { + Some(0) => "".to_string(), + Some(n) => format!("max-width: {width}px", width = n), + _ => format!("max-width: {width}px", width = 130), + }; div![ - C![clickable_class], - close_handler, + C!["modal"], div![ - C![class_list, "styledModal", variant.to_class_name()], - attrs![At::Style => styled_modal_style], - body_handler, - icon, - children + C!["clickableOverlay", variant.to_class_name()], + close_handler, + div![ + C![class_list, "styledModal", variant.to_class_name()], + attrs![At::Style => styled_modal_style], + body_handler, + IF![with_icon => StyledIcon { + icon: Icon::Close, + class_list: variant.to_icon_class_name(), + ..Default::default() + } + .render()], + children + ] ] ] - ] + } } diff --git a/jirs-client/src/components/styled_select.rs b/jirs-client/src/components/styled_select.rs index 71b9cfc6..f3dca173 100644 --- a/jirs-client/src/components/styled_select.rs +++ b/jirs-client/src/components/styled_select.rs @@ -246,30 +246,6 @@ where vec![] }; - let option_list = match (opened, children.is_empty()) { - (false, _) => empty![], - (_, true) => seed::div![C!["noOptions"], "No results"], - _ => seed::div![C!["options"], children], - }; - - let value: Vec> = if is_multi { - let add_icon = StyledIcon::from(Icon::Plus).into_node(); - let mut children: Vec> = selected - .into_iter() - .map(|m| into_multi_value(m, id.clone())) - .collect(); - - if !children.is_empty() { - children.push(div![C!["addMore"], add_icon, "Add more"]); - } else { - children.push(div![C!["placeholder"], "Select"]); - } - - vec![div![C!["valueMulti"], children]] - } else { - selected.into_iter().map(|m| m.render_value()).collect() - }; - seed::div![ C!["styledSelect", variant.to_str(), IF![!valid => "invalid"]], attrs![At::Style => dropdown_style.as_str()], @@ -280,7 +256,21 @@ where div![ C!["valueContainer", variant.to_str()], on_handler, - value, + match is_multi { + true => vec![div![ + C!["valueMulti"], + selected + .into_iter() + .map(|m| into_multi_value(m, id.clone())) + .collect::>>(), + IF![children.is_empty() => div![C!["placeholder"], "Select"]], + IF![!children.is_empty() => div![C!["addMore"], StyledIcon::from(Icon::Plus).render(), "Add more"]], + ]], + false => selected + .into_iter() + .map(|m| m.render_value()) + .collect::>>(), + }, action_icon, ], div![ @@ -296,7 +286,11 @@ where ], on_text, ]], - option_list + match (opened, children.is_empty()) { + (false, _) => empty![], + (_, true) => seed::div![C!["noOptions"], "No results"], + _ => seed::div![C!["options"], children], + } ] ] }