Add select tip. Add action buttons. Fix select styles

This commit is contained in:
Adrian Woźniak 2020-04-04 19:13:42 +02:00
parent 8950277149
commit 72c894fa77
8 changed files with 176 additions and 75 deletions

View File

@ -10,3 +10,32 @@
padding-bottom: 15px; padding-bottom: 15px;
font-size: 21px; 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;
}

View File

@ -46,6 +46,18 @@
padding: 5px 5px 5px 10px; 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 { .styledSelect > .dropDown {
z-index: var(--dropdown); z-index: var(--dropdown);
position: absolute; position: absolute;
@ -53,7 +65,7 @@
left: 0; left: 0;
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;
background: #fff; 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%; width: 100%;
} }

View File

@ -1,3 +1,7 @@
use seed::{prelude::*, *};
use jirs_data::IssueType;
use crate::model::{AddIssueModal, Model}; use crate::model::{AddIssueModal, Model};
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_form::StyledForm; 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::styled_select::StyledSelect;
use crate::shared::ToNode; use crate::shared::ToNode;
use crate::{FieldId, Msg}; use crate::{FieldId, Msg};
use jirs_data::IssueType;
use seed::{prelude::*, *};
pub fn view(_model: &Model, modal: &AddIssueModal) -> Node<Msg> { pub fn view(_model: &Model, modal: &AddIssueModal) -> Node<Msg> {
let select_type = StyledSelect::build(FieldId::IssueTypeAddIssueModal) let select_type = StyledSelect::build(FieldId::IssueTypeAddIssueModal)
@ -16,6 +18,7 @@ pub fn view(_model: &Model, modal: &AddIssueModal) -> Node<Msg> {
.text_filter(modal.type_select_filter.as_str()) .text_filter(modal.type_select_filter.as_str())
.opened(modal.type_select_opened) .opened(modal.type_select_opened)
.valid(true) .valid(true)
.tip("Start typing to get a list of possible matches.")
.options(vec![ .options(vec![
IssueTypeOption(IssueType::Story), IssueTypeOption(IssueType::Story),
IssueTypeOption(IssueType::Task), IssueTypeOption(IssueType::Task),
@ -24,9 +27,25 @@ pub fn view(_model: &Model, modal: &AddIssueModal) -> Node<Msg> {
.selected(vec![IssueTypeOption(modal.issue_type.clone())]) .selected(vec![IssueTypeOption(modal.issue_type.clone())])
.build() .build()
.into_node(); .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() let form = StyledForm::build()
.heading("Create issue") .heading("Create issue")
.add_field(select_type) .add_field(select_type)
.add_field(actions)
.build() .build()
.into_node(); .into_node();
@ -61,14 +80,12 @@ impl crate::shared::styled_select::SelectOption for IssueTypeOption {
let name = self.0.to_label().to_owned(); let name = self.0.to_label().to_owned();
let type_icon = StyledIcon::build(self.0.into()).build().into_node(); 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] div![
// StyledButton::build() attrs![At::Class => "selectItem"],
// .secondary() type_icon,
// .children(vec![span![format!("{}", name)]]) div![attrs![At::Class => "selectItemLabel"], name]
// .icon(StyledIcon::build(self.0.into()).build()) ]
// .build()
// .into_node()
} }
fn match_text_filter(&self, text_filter: &str) -> bool { fn match_text_filter(&self, text_filter: &str) -> bool {

View File

@ -182,28 +182,19 @@ fn project_board_filters(model: &Model) -> Node<Msg> {
let project_page = &model.project_page; let project_page = &model.project_page;
let only_my = StyledButton { let only_my = StyledButton::build()
variant: ButtonVariant::Empty, .empty()
.active(model.project_page.only_my_filter)
disabled: false, .text("Only My Issues")
active: model.project_page.only_my_filter, .on_click(mouse_ev(Ev::Click, |_| Msg::ProjectToggleOnlyMy))
text: Some("Only My Issues".to_string()), .build()
icon: None,
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ProjectToggleOnlyMy)),
children: vec![],
}
.into_node(); .into_node();
let recently_updated = StyledButton { let recently_updated = StyledButton::build()
variant: ButtonVariant::Empty, .empty()
.text("Recently Updated")
disabled: false, .on_click(mouse_ev(Ev::Click, |_| Msg::ProjectToggleRecentlyUpdated))
active: model.project_page.recently_updated_filter, .build()
text: Some("Recently Updated".to_string()),
icon: None,
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ProjectToggleRecentlyUpdated)),
children: vec![],
}
.into_node(); .into_node();
let clear_all = match project_page.only_my_filter let clear_all = match project_page.only_my_filter

View File

@ -27,13 +27,14 @@ impl ToString for Variant {
#[derive(Default)] #[derive(Default)]
pub struct StyledButtonBuilder { pub struct StyledButtonBuilder {
pub variant: Option<Variant>, variant: Option<Variant>,
pub disabled: Option<bool>, disabled: Option<bool>,
pub active: Option<bool>, active: Option<bool>,
pub text: Option<Option<String>>, text: Option<Option<String>>,
pub icon: Option<Option<Node<Msg>>>, icon: Option<Option<Node<Msg>>>,
pub on_click: Option<Option<EventHandler<Msg>>>, on_click: Option<Option<EventHandler<Msg>>>,
pub children: Option<Vec<Node<Msg>>>, children: Option<Vec<Node<Msg>>>,
class_list: Vec<String>,
} }
impl StyledButtonBuilder { impl StyledButtonBuilder {
@ -67,13 +68,16 @@ impl StyledButtonBuilder {
// self // self
// } // }
// pub fn active(mut self, value: bool) -> Self { pub fn active(mut self, value: bool) -> Self {
// self.active = Some(value); self.active = Some(value);
// self self
// } }
pub fn text(mut self, value: String) -> Self { pub fn text<S>(mut self, value: S) -> Self
self.text = Some(Some(value)); where
S: Into<String>,
{
self.text = Some(Some(value.into()));
self self
} }
@ -95,6 +99,14 @@ impl StyledButtonBuilder {
self self
} }
pub fn add_class<S>(mut self, name: S) -> Self
where
S: Into<String>,
{
self.class_list.push(name.into());
self
}
pub fn build(self) -> StyledButton { pub fn build(self) -> StyledButton {
StyledButton { StyledButton {
variant: self.variant.unwrap_or_else(|| Variant::Primary), variant: self.variant.unwrap_or_else(|| Variant::Primary),
@ -104,18 +116,20 @@ impl StyledButtonBuilder {
icon: self.icon.unwrap_or_else(|| None), icon: self.icon.unwrap_or_else(|| None),
on_click: self.on_click.unwrap_or_else(|| None), on_click: self.on_click.unwrap_or_else(|| None),
children: self.children.unwrap_or_default(), children: self.children.unwrap_or_default(),
class_list: self.class_list,
} }
} }
} }
pub struct StyledButton { pub struct StyledButton {
pub variant: Variant, variant: Variant,
pub disabled: bool, disabled: bool,
pub active: bool, active: bool,
pub text: Option<String>, text: Option<String>,
pub icon: Option<Node<Msg>>, icon: Option<Node<Msg>>,
pub on_click: Option<EventHandler<Msg>>, on_click: Option<EventHandler<Msg>>,
pub children: Vec<Node<Msg>>, children: Vec<Node<Msg>>,
class_list: Vec<String>,
} }
impl StyledButton { impl StyledButton {
@ -139,8 +153,10 @@ pub fn render(values: StyledButton) -> Node<Msg> {
icon, icon,
on_click, on_click,
children, children,
mut class_list,
} = values; } = 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() { if children.is_empty() && text.is_none() {
class_list.push("iconOnly".to_string()); class_list.push("iconOnly".to_string());
} }

View File

@ -1,6 +1,7 @@
use seed::{prelude::*, *};
use crate::shared::ToNode; use crate::shared::ToNode;
use crate::Msg; use crate::Msg;
use seed::{prelude::*, *};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct StyledForm { pub struct StyledForm {
@ -54,7 +55,7 @@ pub fn render(values: StyledForm) -> Node<Msg> {
attrs![At::Class => "styledForm"], attrs![At::Class => "styledForm"],
div![ div![
attrs![At::Class => "formElement"], attrs![At::Class => "formElement"],
div![attrs![At::Class => "heading"], heading], div![attrs![At::Class => "formHeading"], heading],
fields fields
], ],
] ]

View File

@ -131,13 +131,19 @@ impl StyledIconBuilder {
self self
} }
pub fn add_class(mut self, name: String) -> Self { pub fn add_class<S>(mut self, name: S) -> Self
self.class_list.push(name); where
S: Into<String>,
{
self.class_list.push(name.into());
self self
} }
pub fn add_style(mut self, name: String) -> Self { pub fn add_style<S>(mut self, name: S) -> Self
self.style_list.push(name); where
S: Into<String>,
{
self.style_list.push(name.into());
self self
} }

View File

@ -59,6 +59,7 @@ where
selected: Vec<Child>, selected: Vec<Child>,
text_filter: String, text_filter: String,
opened: bool, opened: bool,
tip: Option<String>,
} }
impl<Child> ToNode for StyledSelect<Child> impl<Child> ToNode for StyledSelect<Child>
@ -88,6 +89,7 @@ where
selected: None, selected: None,
text_filter: None, text_filter: None,
opened: None, opened: None,
tip: None,
} }
} }
} }
@ -109,6 +111,7 @@ where
selected: Option<Vec<Child>>, selected: Option<Vec<Child>>,
text_filter: Option<String>, text_filter: Option<String>,
opened: Option<bool>, opened: Option<bool>,
tip: Option<String>,
} }
impl<Child> StyledSelectBuilder<Child> impl<Child> StyledSelectBuilder<Child>
@ -129,6 +132,7 @@ where
selected: self.selected.unwrap_or_default(), selected: self.selected.unwrap_or_default(),
text_filter: self.text_filter.unwrap_or_default(), text_filter: self.text_filter.unwrap_or_default(),
opened: self.opened.unwrap_or_default(), opened: self.opened.unwrap_or_default(),
tip: self.tip,
} }
} }
@ -173,6 +177,14 @@ where
self self
} }
pub fn tip<S>(mut self, tip: S) -> Self
where
S: Into<String>,
{
self.tip = Some(tip.into());
self
}
pub fn normal(mut self) -> Self { pub fn normal(mut self) -> Self {
self.variant = Some(Variant::Normal); self.variant = Some(Variant::Normal);
self self
@ -190,12 +202,13 @@ where
name, name,
placeholder: _, placeholder: _,
valid, valid,
is_multi: _, is_multi,
allow_clear, allow_clear,
options, options,
selected, selected,
text_filter, text_filter,
opened, opened,
tip,
} = values; } = values;
let on_text = input_ev(Ev::KeyUp, |value| { let on_text = input_ev(Ev::KeyUp, |value| {
@ -211,13 +224,21 @@ where
}); });
let dropdown_style = dropdown_width let dropdown_style = dropdown_width
.map(|n| format!("width: {}px", n)) .map(|n| format!("width: {}px;", n))
.unwrap_or_default(); .unwrap_or_else(|| format!("width: 100%;"));
let mut select_class = vec!["styledSelect".to_string(), format!("{}", variant)]; let mut select_class = vec!["styledSelect".to_string(), format!("{}", variant)];
if !valid { if !valid {
select_class.push("invalid".to_string()); 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<Node<Msg>> = options let children: Vec<Node<Msg>> = options
.into_iter() .into_iter()
.filter(|o| !selected.contains(&o) && o.match_text_filter(text_filter.as_str())) .filter(|o| !selected.contains(&o) && o.match_text_filter(text_filter.as_str()))
@ -237,6 +258,10 @@ where
.collect(); .collect();
let value = selected.into_iter().map(|m| render_value(m.into_value())); 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 { let text_input = match opened {
true => seed::input![ true => seed::input![
@ -263,12 +288,14 @@ where
_ => seed::div![attrs![ At::Class => "options" ], children], _ => seed::div![attrs![ At::Class => "options" ], children],
}; };
div![
seed::div![ seed::div![
attrs![At::Class => select_class.join(" ")], attrs![At::Class => select_class.join(" ")],
div![ div![
attrs![At::Class => format!("valueContainer {}", variant)], attrs![At::Class => format!("valueContainer {}", variant)],
visibility_handler, visibility_handler,
value, value,
chevron_down,
], ],
div![ div![
attrs![At::Class => "dropDown", At::Style => dropdown_style], attrs![At::Class => "dropDown", At::Style => dropdown_style],
@ -276,6 +303,8 @@ where
clear_icon, clear_icon,
option_list option_list
] ]
],
tip_node,
] ]
} }