From 72c894fa773f7221d9ce4717b7099d0e10312718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?= Date: Sat, 4 Apr 2020 19:13:42 +0200 Subject: [PATCH] Add select tip. Add action buttons. Fix select styles --- jirs-client/js/css/styledForm.css | 29 ++++++++++++ jirs-client/js/css/styledSelect.css | 14 +++++- jirs-client/src/modal/add_issue.rs | 37 +++++++++++----- jirs-client/src/project.rs | 35 ++++++--------- jirs-client/src/shared/styled_button.rs | 58 +++++++++++++++--------- jirs-client/src/shared/styled_form.rs | 5 ++- jirs-client/src/shared/styled_icon.rs | 14 ++++-- jirs-client/src/shared/styled_select.rs | 59 ++++++++++++++++++------- 8 files changed, 176 insertions(+), 75 deletions(-) diff --git a/jirs-client/js/css/styledForm.css b/jirs-client/js/css/styledForm.css index 5039bb68..b75dd442 100644 --- a/jirs-client/js/css/styledForm.css +++ b/jirs-client/js/css/styledForm.css @@ -10,3 +10,32 @@ padding-bottom: 15px; font-size: 21px; } + +.styledForm > .formElement .selectItem { + display: flex; + align-items: center; + margin-right: 15px; +} + +.styledForm > .formElement .selectItem.withBottomMargin { + margin-bottom: 5px; +} + +.styledForm > .formElement .selectItem > .selectItemLabel { + padding: 0 3px 0 6px; +} + +.styledForm > .formElement .divider { + margin-top: 22px; + border-top: 1px solid var(--borderLightest); +} + +.styledForm > .formElement > .actions { + display: flex; + justify-content: flex-end; + padding-top: 30px; +} + +.styledForm > .formElement > .actions > .actionButton { + margin-left: 10px; +} diff --git a/jirs-client/js/css/styledSelect.css b/jirs-client/js/css/styledSelect.css index ee98b364..b519018e 100644 --- a/jirs-client/js/css/styledSelect.css +++ b/jirs-client/js/css/styledSelect.css @@ -46,6 +46,18 @@ padding: 5px 5px 5px 10px; } +.styledSelect > .valueContainer > .chevronIcon { + margin-left: auto; + font-size: 18px; + color: var(--textMedium); +} + +.styledSelectTip { + padding-top: 6px; + color: var(--textMedium); + font-size: 12.5px; +} + .styledSelect > .dropDown { z-index: var(--dropdown); position: absolute; @@ -53,7 +65,7 @@ left: 0; border-radius: 0 0 4px 4px; background: #fff; - box-shadow: rgba(9, 30, 66, 0.25) 0px 4px 8px -2px, rgba(9, 30, 66, 0.31) 0px 0px 1px; + box-shadow: rgba(9, 30, 66, 0.25) 0 4px 8px -2px, rgba(9, 30, 66, 0.31) 0 0 1px; width: 100%; } diff --git a/jirs-client/src/modal/add_issue.rs b/jirs-client/src/modal/add_issue.rs index 4171c6bf..60bd6f38 100644 --- a/jirs-client/src/modal/add_issue.rs +++ b/jirs-client/src/modal/add_issue.rs @@ -1,3 +1,7 @@ +use seed::{prelude::*, *}; + +use jirs_data::IssueType; + use crate::model::{AddIssueModal, Model}; use crate::shared::styled_button::StyledButton; use crate::shared::styled_form::StyledForm; @@ -6,8 +10,6 @@ use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant}; use crate::shared::styled_select::StyledSelect; use crate::shared::ToNode; use crate::{FieldId, Msg}; -use jirs_data::IssueType; -use seed::{prelude::*, *}; pub fn view(_model: &Model, modal: &AddIssueModal) -> Node { let select_type = StyledSelect::build(FieldId::IssueTypeAddIssueModal) @@ -16,6 +18,7 @@ pub fn view(_model: &Model, modal: &AddIssueModal) -> Node { .text_filter(modal.type_select_filter.as_str()) .opened(modal.type_select_opened) .valid(true) + .tip("Start typing to get a list of possible matches.") .options(vec![ IssueTypeOption(IssueType::Story), IssueTypeOption(IssueType::Task), @@ -24,9 +27,25 @@ pub fn view(_model: &Model, modal: &AddIssueModal) -> Node { .selected(vec![IssueTypeOption(modal.issue_type.clone())]) .build() .into_node(); + + let submit = StyledButton::build() + .primary() + .text("Create Issue") + .add_class("action") + .build() + .into_node(); + let cancel = StyledButton::build() + .empty() + .add_class("action") + .text("Cancel") + .build() + .into_node(); + let actions = div![attrs![At::Class => "actions"], submit, cancel]; + let form = StyledForm::build() .heading("Create issue") .add_field(select_type) + .add_field(actions) .build() .into_node(); @@ -61,14 +80,12 @@ impl crate::shared::styled_select::SelectOption for IssueTypeOption { let name = self.0.to_label().to_owned(); let type_icon = StyledIcon::build(self.0.into()).build().into_node(); - let chevron_icon = StyledIcon::build(Icon::ChevronDown).build().into_node(); - div![attrs![At::Class => "option"], type_icon, name, chevron_icon] - // StyledButton::build() - // .secondary() - // .children(vec![span![format!("{}", name)]]) - // .icon(StyledIcon::build(self.0.into()).build()) - // .build() - // .into_node() + + div![ + attrs![At::Class => "selectItem"], + type_icon, + div![attrs![At::Class => "selectItemLabel"], name] + ] } fn match_text_filter(&self, text_filter: &str) -> bool { diff --git a/jirs-client/src/project.rs b/jirs-client/src/project.rs index 6a6971ec..3edfead5 100644 --- a/jirs-client/src/project.rs +++ b/jirs-client/src/project.rs @@ -182,29 +182,20 @@ fn project_board_filters(model: &Model) -> Node { let project_page = &model.project_page; - let only_my = StyledButton { - variant: ButtonVariant::Empty, + let only_my = StyledButton::build() + .empty() + .active(model.project_page.only_my_filter) + .text("Only My Issues") + .on_click(mouse_ev(Ev::Click, |_| Msg::ProjectToggleOnlyMy)) + .build() + .into_node(); - disabled: false, - active: model.project_page.only_my_filter, - text: Some("Only My Issues".to_string()), - icon: None, - on_click: Some(mouse_ev(Ev::Click, |_| Msg::ProjectToggleOnlyMy)), - children: vec![], - } - .into_node(); - - let recently_updated = StyledButton { - variant: ButtonVariant::Empty, - - disabled: false, - active: model.project_page.recently_updated_filter, - text: Some("Recently Updated".to_string()), - icon: None, - on_click: Some(mouse_ev(Ev::Click, |_| Msg::ProjectToggleRecentlyUpdated)), - children: vec![], - } - .into_node(); + let recently_updated = StyledButton::build() + .empty() + .text("Recently Updated") + .on_click(mouse_ev(Ev::Click, |_| Msg::ProjectToggleRecentlyUpdated)) + .build() + .into_node(); let clear_all = match project_page.only_my_filter || project_page.recently_updated_filter diff --git a/jirs-client/src/shared/styled_button.rs b/jirs-client/src/shared/styled_button.rs index 98987505..9844cabc 100644 --- a/jirs-client/src/shared/styled_button.rs +++ b/jirs-client/src/shared/styled_button.rs @@ -27,13 +27,14 @@ impl ToString for Variant { #[derive(Default)] pub struct StyledButtonBuilder { - pub variant: Option, - pub disabled: Option, - pub active: Option, - pub text: Option>, - pub icon: Option>>, - pub on_click: Option>>, - pub children: Option>>, + variant: Option, + disabled: Option, + active: Option, + text: Option>, + icon: Option>>, + on_click: Option>>, + children: Option>>, + class_list: Vec, } impl StyledButtonBuilder { @@ -67,13 +68,16 @@ impl StyledButtonBuilder { // self // } - // pub fn active(mut self, value: bool) -> Self { - // self.active = Some(value); - // self - // } + pub fn active(mut self, value: bool) -> Self { + self.active = Some(value); + self + } - pub fn text(mut self, value: String) -> Self { - self.text = Some(Some(value)); + pub fn text(mut self, value: S) -> Self + where + S: Into, + { + self.text = Some(Some(value.into())); self } @@ -95,6 +99,14 @@ impl StyledButtonBuilder { self } + pub fn add_class(mut self, name: S) -> Self + where + S: Into, + { + self.class_list.push(name.into()); + self + } + pub fn build(self) -> StyledButton { StyledButton { variant: self.variant.unwrap_or_else(|| Variant::Primary), @@ -104,18 +116,20 @@ impl StyledButtonBuilder { icon: self.icon.unwrap_or_else(|| None), on_click: self.on_click.unwrap_or_else(|| None), children: self.children.unwrap_or_default(), + class_list: self.class_list, } } } pub struct StyledButton { - pub variant: Variant, - pub disabled: bool, - pub active: bool, - pub text: Option, - pub icon: Option>, - pub on_click: Option>, - pub children: Vec>, + variant: Variant, + disabled: bool, + active: bool, + text: Option, + icon: Option>, + on_click: Option>, + children: Vec>, + class_list: Vec, } impl StyledButton { @@ -139,8 +153,10 @@ pub fn render(values: StyledButton) -> Node { icon, on_click, children, + mut class_list, } = values; - let mut class_list = vec!["styledButton".to_string(), variant.to_string()]; + class_list.push("styledButton".to_string()); + class_list.push(variant.to_string()); if children.is_empty() && text.is_none() { class_list.push("iconOnly".to_string()); } diff --git a/jirs-client/src/shared/styled_form.rs b/jirs-client/src/shared/styled_form.rs index a51abdf3..4cd32e30 100644 --- a/jirs-client/src/shared/styled_form.rs +++ b/jirs-client/src/shared/styled_form.rs @@ -1,6 +1,7 @@ +use seed::{prelude::*, *}; + use crate::shared::ToNode; use crate::Msg; -use seed::{prelude::*, *}; #[derive(Debug, Clone)] pub struct StyledForm { @@ -54,7 +55,7 @@ pub fn render(values: StyledForm) -> Node { attrs![At::Class => "styledForm"], div![ attrs![At::Class => "formElement"], - div![attrs![At::Class => "heading"], heading], + div![attrs![At::Class => "formHeading"], heading], fields ], ] diff --git a/jirs-client/src/shared/styled_icon.rs b/jirs-client/src/shared/styled_icon.rs index 3a9774e5..6ce5f391 100644 --- a/jirs-client/src/shared/styled_icon.rs +++ b/jirs-client/src/shared/styled_icon.rs @@ -131,13 +131,19 @@ impl StyledIconBuilder { self } - pub fn add_class(mut self, name: String) -> Self { - self.class_list.push(name); + pub fn add_class(mut self, name: S) -> Self + where + S: Into, + { + self.class_list.push(name.into()); self } - pub fn add_style(mut self, name: String) -> Self { - self.style_list.push(name); + pub fn add_style(mut self, name: S) -> Self + where + S: Into, + { + self.style_list.push(name.into()); self } diff --git a/jirs-client/src/shared/styled_select.rs b/jirs-client/src/shared/styled_select.rs index 0b92c7a1..67c2675b 100644 --- a/jirs-client/src/shared/styled_select.rs +++ b/jirs-client/src/shared/styled_select.rs @@ -59,6 +59,7 @@ where selected: Vec, text_filter: String, opened: bool, + tip: Option, } impl ToNode for StyledSelect @@ -88,6 +89,7 @@ where selected: None, text_filter: None, opened: None, + tip: None, } } } @@ -109,6 +111,7 @@ where selected: Option>, text_filter: Option, opened: Option, + tip: Option, } impl StyledSelectBuilder @@ -129,6 +132,7 @@ where selected: self.selected.unwrap_or_default(), text_filter: self.text_filter.unwrap_or_default(), opened: self.opened.unwrap_or_default(), + tip: self.tip, } } @@ -173,6 +177,14 @@ where self } + pub fn tip(mut self, tip: S) -> Self + where + S: Into, + { + self.tip = Some(tip.into()); + self + } + pub fn normal(mut self) -> Self { self.variant = Some(Variant::Normal); self @@ -190,12 +202,13 @@ where name, placeholder: _, valid, - is_multi: _, + is_multi, allow_clear, options, selected, text_filter, opened, + tip, } = values; let on_text = input_ev(Ev::KeyUp, |value| { @@ -211,13 +224,21 @@ where }); let dropdown_style = dropdown_width - .map(|n| format!("width: {}px", n)) - .unwrap_or_default(); + .map(|n| format!("width: {}px;", n)) + .unwrap_or_else(|| format!("width: 100%;")); let mut select_class = vec!["styledSelect".to_string(), format!("{}", variant)]; if !valid { select_class.push("invalid".to_string()); } + let chevron_down = match (selected.is_empty() || !is_multi) && variant != Variant::Empty { + true => StyledIcon::build(Icon::ChevronDown) + .add_class("chevronIcon") + .build() + .into_node(), + _ => empty![], + }; + let children: Vec> = options .into_iter() .filter(|o| !selected.contains(&o) && o.match_text_filter(text_filter.as_str())) @@ -237,6 +258,10 @@ where .collect(); let value = selected.into_iter().map(|m| render_value(m.into_value())); + let tip_node = match tip { + Some(s) => div![attrs![At::Class => "styledSelectTip"], s], + _ => empty![], + }; let text_input = match opened { true => seed::input![ @@ -263,19 +288,23 @@ where _ => seed::div![attrs![ At::Class => "options" ], children], }; - seed::div![ - attrs![At::Class => select_class.join(" ")], - div![ - attrs![At::Class => format!("valueContainer {}", variant)], - visibility_handler, - value, + div![ + seed::div![ + attrs![At::Class => select_class.join(" ")], + div![ + attrs![At::Class => format!("valueContainer {}", variant)], + visibility_handler, + value, + chevron_down, + ], + div![ + attrs![At::Class => "dropDown", At::Style => dropdown_style], + text_input, + clear_icon, + option_list + ] ], - div![ - attrs![At::Class => "dropDown", At::Style => dropdown_style], - text_input, - clear_icon, - option_list - ] + tip_node, ] }