Improve edit
This commit is contained in:
parent
c3c8d1b66e
commit
a97a96e9ac
@ -8,6 +8,32 @@
|
||||
padding-right: 50px;
|
||||
}
|
||||
|
||||
.issueDetails > .content > .left > .styledTextArea {
|
||||
margin: 18px 0 0 -8px;
|
||||
height: 44px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.issueDetails > .content > .left > .styledTextArea > textarea {
|
||||
padding: 7px 7px 8px;
|
||||
line-height: 1.28;
|
||||
resize: none;
|
||||
transition: background 0.1s;
|
||||
font-size: 24px;
|
||||
font-family: var(--font-medium);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.issueDetails > .content > .left > .styledTextArea > textarea:not(:focus) {
|
||||
background: #fff;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 0 0 1px transparent;
|
||||
}
|
||||
|
||||
.issueDetails > .content > .left > .styledTextArea > textarea:hover:not(:focus) {
|
||||
background: var(--backgroundLight);
|
||||
}
|
||||
|
||||
.issueDetails > .content > .right {
|
||||
width: 35%;
|
||||
padding-top: 5px;
|
||||
|
@ -63,31 +63,3 @@
|
||||
.styledField > * {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.styledTextArea {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.styledTextArea > textarea {
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
padding: 8px 12px 9px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--borderLightest);
|
||||
color: var(--textDarkest);
|
||||
background: var(--backgroundLightest);
|
||||
font-family: var(--font-regular);
|
||||
font-weight: normal;
|
||||
font-size: 15px
|
||||
}
|
||||
|
||||
.styledTextArea > textarea:focus {
|
||||
background: #fff;
|
||||
border: 1px solid var(--borderInputFocus);
|
||||
box-shadow: 0 0 0 1px var(--borderInputFocus);
|
||||
}
|
||||
|
||||
.styledTextArea > textarea.invalid:focus {
|
||||
border: 1px solid var(--danger);
|
||||
}
|
||||
|
27
jirs-client/js/css/styledTextArea.css
Normal file
27
jirs-client/js/css/styledTextArea.css
Normal file
@ -0,0 +1,27 @@
|
||||
.styledTextArea {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.styledTextArea > textarea {
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
padding: 8px 12px 9px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--borderLightest);
|
||||
color: var(--textDarkest);
|
||||
background: var(--backgroundLightest);
|
||||
font-family: var(--font-regular);
|
||||
font-weight: normal;
|
||||
font-size: 15px
|
||||
}
|
||||
|
||||
.styledTextArea > textarea:focus {
|
||||
background: #fff;
|
||||
border: 1px solid var(--borderInputFocus);
|
||||
box-shadow: 0 0 0 1px var(--borderInputFocus);
|
||||
}
|
||||
|
||||
.styledTextArea > textarea.invalid:focus {
|
||||
border: 1px solid var(--danger);
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
@import "./css/styledButton.css";
|
||||
@import "./css/styledInput.css";
|
||||
@import "./css/styledModal.css";
|
||||
@import "./css/styledTextArea.css";
|
||||
@import "./css/styledForm.css";
|
||||
@import "./css/app.css";
|
||||
@import "./css/issue.css";
|
||||
|
@ -28,6 +28,15 @@ pub type AvatarFilterActive = bool;
|
||||
pub enum FieldId {
|
||||
// edit issue
|
||||
IssueTypeEditModalTop,
|
||||
TitleIssueEditModal,
|
||||
DescriptionIssueEditModal,
|
||||
StatusIssueEditModal,
|
||||
AssigneesIssueEditModal,
|
||||
ReporterIssueEditModal,
|
||||
PriorityIssueEditModal,
|
||||
EstimateIssueEditModal,
|
||||
TimeSpendIssueEditModal,
|
||||
TimeRemainingIssueEditModal,
|
||||
// project boards
|
||||
TextFilterBoard,
|
||||
//
|
||||
@ -53,6 +62,15 @@ impl std::fmt::Display for FieldId {
|
||||
FieldId::ReporterAddIssueModal => f.write_str("reporterAddIssueModal"),
|
||||
FieldId::AssigneesAddIssueModal => f.write_str("assigneesAddIssueModal"),
|
||||
FieldId::IssuePriorityAddIssueModal => f.write_str("issuePriorityAddIssueModal"),
|
||||
FieldId::TitleIssueEditModal => f.write_str("titleIssueEditModal"),
|
||||
FieldId::DescriptionIssueEditModal => f.write_str("descriptionIssueEditModal"),
|
||||
FieldId::StatusIssueEditModal => f.write_str("statusIssueEditModal"),
|
||||
FieldId::AssigneesIssueEditModal => f.write_str("assigneesIssueEditModal"),
|
||||
FieldId::ReporterIssueEditModal => f.write_str("reporterIssueEditModal"),
|
||||
FieldId::PriorityIssueEditModal => f.write_str("priorityIssueEditModal"),
|
||||
FieldId::EstimateIssueEditModal => f.write_str("estimateIssueEditModal"),
|
||||
FieldId::TimeSpendIssueEditModal => f.write_str("timeSpendIssueEditModal"),
|
||||
FieldId::TimeRemainingIssueEditModal => f.write_str("timeRemainingIssueEditModal"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,45 @@
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use jirs_data::{Issue, IssueType};
|
||||
use jirs_data::{Issue, IssuePriority, IssueStatus, IssueType, ToVec, User};
|
||||
|
||||
use crate::model::{EditIssueModal, ModalType, Model};
|
||||
use crate::shared::styled_avatar::StyledAvatar;
|
||||
use crate::shared::styled_button::StyledButton;
|
||||
use crate::shared::styled_field::StyledField;
|
||||
use crate::shared::styled_icon::{Icon, StyledIcon};
|
||||
use crate::shared::styled_select::StyledSelect;
|
||||
use crate::shared::styled_select::{SelectOption, StyledSelect};
|
||||
use crate::shared::styled_textarea::StyledTextarea;
|
||||
use crate::shared::ToNode;
|
||||
use crate::{FieldChange, FieldId, IssueId, Msg};
|
||||
|
||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
let modal: &mut EditIssueModal = match model.modals.get_mut(0) {
|
||||
Some(ModalType::EditIssue(_issue_id, modal)) => modal,
|
||||
_ => return,
|
||||
};
|
||||
modal.top_type_state.update(msg, orders);
|
||||
modal.status_state.update(msg, orders);
|
||||
modal.reporter_state.update(msg, orders);
|
||||
modal.assignees_state.update(msg, orders);
|
||||
modal.priority_state.update(msg, orders);
|
||||
}
|
||||
|
||||
pub fn view(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg> {
|
||||
let issue_id = issue.id;
|
||||
|
||||
let issue_type_select = StyledSelect::build(FieldId::IssueTypeEditModalTop)
|
||||
.dropdown_width(150)
|
||||
.name("type")
|
||||
.text_filter(modal.top_select_filter.as_str())
|
||||
.opened(modal.top_select_opened)
|
||||
.text_filter(modal.top_type_state.text_filter.as_str())
|
||||
.opened(modal.top_type_state.opened)
|
||||
.valid(true)
|
||||
.options(vec![
|
||||
IssueTypeOption(issue_id, IssueType::Story),
|
||||
IssueTypeOption(issue_id, IssueType::Task),
|
||||
IssueTypeOption(issue_id, IssueType::Bug),
|
||||
])
|
||||
.selected(vec![IssueTypeOption(issue_id, modal.value.clone())])
|
||||
.options(
|
||||
IssueType::ordered()
|
||||
.into_iter()
|
||||
.map(|t| IssueTypeTopOption(issue_id, t))
|
||||
.collect(),
|
||||
)
|
||||
.selected(vec![IssueTypeTopOption(issue_id, modal.value.clone())])
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
@ -75,6 +91,36 @@ pub fn view(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg>
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
// left
|
||||
let title = StyledTextarea::build()
|
||||
.value(issue.title.as_str())
|
||||
.add_class("textarea")
|
||||
.max_height(48)
|
||||
.height(0)
|
||||
.build(FieldId::TitleIssueEditModal)
|
||||
.into_node();
|
||||
|
||||
// right
|
||||
let status = StyledSelect::build(FieldId::StatusIssueEditModal)
|
||||
.name("status")
|
||||
.opened(modal.status_state.opened)
|
||||
.text_filter(modal.status_state.text_filter.as_str())
|
||||
.options(
|
||||
IssueStatus::ordered()
|
||||
.into_iter()
|
||||
.map(|opt| IssueStatusOption(issue_id, opt))
|
||||
.collect(),
|
||||
)
|
||||
.selected(vec![IssueStatusOption(issue_id, issue.status.clone())])
|
||||
.valid(true)
|
||||
.build()
|
||||
.into_node();
|
||||
// let status_field = StyledField::build()
|
||||
// .input(status)
|
||||
// .label("Status")
|
||||
// .build()
|
||||
// .into_node();
|
||||
|
||||
div![
|
||||
attrs![At::Class => "issueDetails"],
|
||||
div![
|
||||
@ -91,19 +137,19 @@ pub fn view(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg>
|
||||
attrs![At::Class => "content"],
|
||||
div![
|
||||
attrs![At::Class => "left"],
|
||||
div![attrs![At::Class => "title"]],
|
||||
title,
|
||||
div![attrs![At::Class => "description"]],
|
||||
div![attrs![At::Class => "comments"]],
|
||||
],
|
||||
div![attrs![At::Class => "right"]],
|
||||
div![attrs![At::Class => "right"], status],
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(PartialOrd, PartialEq, Debug)]
|
||||
pub struct IssueTypeOption(pub IssueId, pub IssueType);
|
||||
pub struct IssueTypeTopOption(pub IssueId, pub IssueType);
|
||||
|
||||
impl crate::shared::styled_select::SelectOption for IssueTypeOption {
|
||||
impl SelectOption for IssueTypeTopOption {
|
||||
fn into_option(self) -> Node<Msg> {
|
||||
let name = self.1.to_label().to_owned();
|
||||
|
||||
@ -113,9 +159,9 @@ impl crate::shared::styled_select::SelectOption for IssueTypeOption {
|
||||
.into_node();
|
||||
|
||||
div![
|
||||
attrs![At::Class => "type"],
|
||||
attrs![At::Class => "optionItem"],
|
||||
icon,
|
||||
div![attrs![At::Class => "typeLabel"], name]
|
||||
div![attrs![At::Class => "optionLabel typeLabel"], name]
|
||||
]
|
||||
}
|
||||
|
||||
@ -142,3 +188,173 @@ impl crate::shared::styled_select::SelectOption for IssueTypeOption {
|
||||
self.1.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
/////
|
||||
#[derive(PartialOrd, PartialEq, Debug)]
|
||||
pub struct IssueStatusOption(pub IssueId, pub IssueStatus);
|
||||
|
||||
impl SelectOption for IssueStatusOption {
|
||||
fn into_option(self) -> Node<Msg> {
|
||||
let name = self.1.to_label().to_owned();
|
||||
|
||||
div![
|
||||
attrs![At::Class => "type optionItem"],
|
||||
div![attrs![At::Class => "typeLabel optionLabel"], name]
|
||||
]
|
||||
}
|
||||
|
||||
fn into_value(self) -> Node<Msg> {
|
||||
let name = self.1.to_label().to_owned();
|
||||
|
||||
div![
|
||||
attrs![At::Class => "selectItem"],
|
||||
div![attrs![At::Class => "selectItemLabel"], name]
|
||||
]
|
||||
}
|
||||
|
||||
fn match_text_filter(&self, text_filter: &str) -> bool {
|
||||
format!("{}", self.0)
|
||||
.to_lowercase()
|
||||
.contains(&text_filter.to_lowercase())
|
||||
}
|
||||
|
||||
fn to_value(&self) -> u32 {
|
||||
self.1.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialOrd, PartialEq, Debug)]
|
||||
pub struct IssueTypeOption(pub IssueType);
|
||||
|
||||
impl SelectOption for IssueTypeOption {
|
||||
fn into_option(self) -> Node<Msg> {
|
||||
let name = self.0.to_label().to_owned();
|
||||
|
||||
let icon = StyledIcon::build(self.0.into())
|
||||
.add_class("issueTypeIcon")
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
div![
|
||||
attrs![At::Class => "type optionItem"],
|
||||
icon,
|
||||
div![attrs![At::Class => "typeLabel optionLabel"], name]
|
||||
]
|
||||
}
|
||||
|
||||
fn into_value(self) -> Node<Msg> {
|
||||
let name = self.0.to_label().to_owned();
|
||||
|
||||
let type_icon = StyledIcon::build(self.0.into()).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 {
|
||||
self.0
|
||||
.to_string()
|
||||
.to_lowercase()
|
||||
.contains(&text_filter.to_lowercase())
|
||||
}
|
||||
|
||||
fn to_value(&self) -> u32 {
|
||||
self.0.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct IssuePriorityOption(IssuePriority);
|
||||
|
||||
impl SelectOption for IssuePriorityOption {
|
||||
fn into_option(self) -> Node<Msg> {
|
||||
let name = format!("{}", self.0);
|
||||
|
||||
let icon = StyledIcon::build(self.0.into())
|
||||
.add_class("issuePriorityIcon")
|
||||
.size(18)
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
div![
|
||||
attrs![At::Class => format!("priority optionItem {}", name)],
|
||||
icon,
|
||||
div![attrs![At::Class => "priorityLabel optionLabel"], name]
|
||||
]
|
||||
}
|
||||
|
||||
fn into_value(self) -> Node<Msg> {
|
||||
let name = format!("{}", self.0);
|
||||
|
||||
let type_icon = StyledIcon::build(self.0.into()).build().into_node();
|
||||
|
||||
div![
|
||||
attrs![At::Class => format!("selectItem priority {}", name)],
|
||||
type_icon,
|
||||
div![attrs![At::Class => "selectItemLabel"], name]
|
||||
]
|
||||
}
|
||||
|
||||
fn match_text_filter(&self, text_filter: &str) -> bool {
|
||||
self.0
|
||||
.to_string()
|
||||
.to_lowercase()
|
||||
.contains(&text_filter.to_lowercase())
|
||||
}
|
||||
|
||||
fn to_value(&self) -> u32 {
|
||||
self.0.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct UserOption<'opt>(pub &'opt User);
|
||||
|
||||
impl<'opt> UserOption<'opt> {
|
||||
fn avatar_node(&self) -> Node<Msg> {
|
||||
let user = self.0;
|
||||
StyledAvatar::build()
|
||||
.avatar_url(user.avatar_url.as_ref().cloned().unwrap_or_default())
|
||||
.size(20)
|
||||
.name(user.name.as_str())
|
||||
.build()
|
||||
.into_node()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'opt> SelectOption for UserOption<'opt> {
|
||||
fn into_option(self) -> Node<Msg> {
|
||||
let user = self.0;
|
||||
|
||||
let styled_avatar = self.avatar_node();
|
||||
|
||||
div![
|
||||
attrs![At::Class => "user optionItem"],
|
||||
styled_avatar,
|
||||
div![attrs![At::Class => "typeLabel optionLabel"], user.name]
|
||||
]
|
||||
}
|
||||
|
||||
fn into_value(self) -> Node<Msg> {
|
||||
let user = self.0;
|
||||
|
||||
let styled_avatar = self.avatar_node();
|
||||
|
||||
div![
|
||||
attrs![At::Class => "selectItem"],
|
||||
styled_avatar,
|
||||
div![attrs![At::Class => "selectItemLabel"], user.name]
|
||||
]
|
||||
}
|
||||
|
||||
fn match_text_filter(&self, text_filter: &str) -> bool {
|
||||
self.0.name.contains(text_filter)
|
||||
}
|
||||
|
||||
fn to_value(&self) -> u32 {
|
||||
self.0.id as u32
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use jirs_data::{Issue, IssueType, UpdateIssuePayload};
|
||||
use crate::api::send_ws_msg;
|
||||
use crate::model::{AddIssueModal, EditIssueModal, ModalType, Page};
|
||||
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
||||
use crate::shared::styled_select::StyledSelectChange;
|
||||
use crate::shared::styled_select::{StyledSelectChange, StyledSelectState};
|
||||
use crate::shared::{find_issue, ToNode};
|
||||
use crate::{model, FieldChange, FieldId, Msg};
|
||||
|
||||
@ -43,10 +43,13 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
|
||||
*issue_id,
|
||||
EditIssueModal {
|
||||
id: *issue_id,
|
||||
top_select_opened: false,
|
||||
top_select_filter: "".to_string(),
|
||||
value,
|
||||
link_copied: false,
|
||||
top_type_state: StyledSelectState::new(FieldId::IssueTypeEditModalTop),
|
||||
status_state: StyledSelectState::new(FieldId::StatusIssueEditModal),
|
||||
reporter_state: StyledSelectState::new(FieldId::ReporterIssueEditModal),
|
||||
assignees_state: StyledSelectState::new(FieldId::AssigneesIssueEditModal),
|
||||
priority_state: StyledSelectState::new(FieldId::PriorityIssueEditModal),
|
||||
},
|
||||
));
|
||||
}
|
||||
@ -59,13 +62,13 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
|
||||
Msg::StyledSelectChanged(FieldId::IssueTypeEditModalTop, change) => {
|
||||
match (change, model.modals.last_mut()) {
|
||||
(StyledSelectChange::Text(ref text), Some(ModalType::EditIssue(_, modal))) => {
|
||||
modal.top_select_filter = text.clone();
|
||||
modal.top_type_state.text_filter = text.clone();
|
||||
}
|
||||
(
|
||||
StyledSelectChange::DropDownVisibility(flag),
|
||||
Some(ModalType::EditIssue(_, modal)),
|
||||
) => {
|
||||
modal.top_select_opened = *flag;
|
||||
modal.top_type_state.opened = *flag;
|
||||
}
|
||||
(
|
||||
StyledSelectChange::Changed(value),
|
||||
@ -107,6 +110,7 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
|
||||
_ => (),
|
||||
}
|
||||
add_issue::update(msg, model, orders);
|
||||
issue_details::update(msg, model, orders);
|
||||
}
|
||||
|
||||
pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
|
@ -20,10 +20,13 @@ pub enum ModalType {
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||
pub struct EditIssueModal {
|
||||
pub id: i32,
|
||||
pub top_select_opened: bool,
|
||||
pub top_select_filter: String,
|
||||
pub value: IssueType,
|
||||
pub link_copied: bool,
|
||||
pub top_type_state: StyledSelectState,
|
||||
pub status_state: StyledSelectState,
|
||||
pub reporter_state: StyledSelectState,
|
||||
pub assignees_state: StyledSelectState,
|
||||
pub priority_state: StyledSelectState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||
|
@ -59,7 +59,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
||||
})
|
||||
.last();
|
||||
if let Some(m) = modal {
|
||||
m.top_select_filter = text;
|
||||
m.top_type_state.text_filter = text;
|
||||
}
|
||||
}
|
||||
Msg::InputChanged(FieldId::TextFilterBoard, text) => {
|
||||
|
@ -66,6 +66,7 @@ pub fn render(values: StyledField) -> Node<Msg> {
|
||||
Some(s) => div![attrs![At::Class => "styledTip"], s],
|
||||
_ => empty![],
|
||||
};
|
||||
|
||||
div![
|
||||
attrs![At::Class => "styledField"],
|
||||
seed::label![attrs![At::Class => "styledLabel"], label],
|
||||
|
@ -7,6 +7,9 @@ use crate::{FieldId, Msg};
|
||||
pub struct StyledTextarea {
|
||||
id: FieldId,
|
||||
height: usize,
|
||||
max_height: usize,
|
||||
value: String,
|
||||
class_list: Vec<String>,
|
||||
}
|
||||
|
||||
impl ToNode for StyledTextarea {
|
||||
@ -24,19 +27,51 @@ impl StyledTextarea {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StyledTextareaBuilder {
|
||||
height: Option<usize>,
|
||||
max_height: Option<usize>,
|
||||
on_change: Option<EventHandler<Msg>>,
|
||||
value: String,
|
||||
class_list: Vec<String>,
|
||||
}
|
||||
|
||||
impl StyledTextareaBuilder {
|
||||
#[inline]
|
||||
pub fn height(mut self, height: usize) -> Self {
|
||||
self.height = Some(height);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_height(mut self, height: usize) -> Self {
|
||||
self.max_height = Some(height);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn value<S>(mut self, value: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.value = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_class<S>(mut self, value: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.class_list.push(value.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build(self, id: FieldId) -> StyledTextarea {
|
||||
StyledTextarea {
|
||||
id,
|
||||
value: self.value,
|
||||
height: self.height.unwrap_or(110),
|
||||
class_list: self.class_list,
|
||||
max_height: self.max_height.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,9 +90,20 @@ const ADDITIONAL_HEIGHT: f64 = PADDING_TOP_BOTTOM + BORDER_TOP_BOTTOM;
|
||||
// * 17 is padding top + bottom
|
||||
// * 2 is border top + bottom
|
||||
pub fn render(values: StyledTextarea) -> Node<Msg> {
|
||||
let StyledTextarea { id, height } = values;
|
||||
let StyledTextarea {
|
||||
id,
|
||||
height,
|
||||
max_height,
|
||||
value,
|
||||
mut class_list,
|
||||
} = values;
|
||||
let mut style_list = vec![];
|
||||
if height > 0 {
|
||||
style_list.push(format!("min-height: {}px", height));
|
||||
}
|
||||
if max_height > 0 {
|
||||
style_list.push(format!("max-height: {}px", max_height));
|
||||
}
|
||||
|
||||
let mut handlers = vec![];
|
||||
|
||||
@ -88,15 +134,18 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
||||
let text_input_handler = input_ev(Ev::KeyUp, move |value| Msg::InputChanged(id, value));
|
||||
handlers.push(text_input_handler);
|
||||
|
||||
class_list.push("textAreaInput".to_string());
|
||||
|
||||
div![
|
||||
attrs![At::Class => "styledTextArea"],
|
||||
div![attrs![At::Class => "textAreaHeading"]],
|
||||
textarea![
|
||||
attrs![
|
||||
At::Class => "textAreaInput";
|
||||
At::Class => class_list.join(" ");
|
||||
At::ContentEditable => "true";
|
||||
At::Style => style_list.join(";");
|
||||
],
|
||||
value,
|
||||
handlers,
|
||||
]
|
||||
]
|
||||
|
@ -1,5 +1,3 @@
|
||||
use seed::*;
|
||||
|
||||
use jirs_data::*;
|
||||
|
||||
use crate::api::send_ws_msg;
|
||||
|
@ -98,6 +98,17 @@ impl Default for IssueStatus {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u32> for IssueStatus {
|
||||
fn into(self) -> u32 {
|
||||
match self {
|
||||
IssueStatus::Backlog => 0,
|
||||
IssueStatus::Selected => 1,
|
||||
IssueStatus::InProgress => 2,
|
||||
IssueStatus::Done => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IssueStatus {
|
||||
type Err = String;
|
||||
|
||||
|
@ -17,9 +17,10 @@ export const TitleTextarea = styled(Textarea)`
|
||||
box-shadow: 0 0 0 1px transparent;
|
||||
transition: background 0.1s;
|
||||
font-size: 24px
|
||||
${ font.medium };font-weight: normal;
|
||||
${font.medium};
|
||||
font-weight: normal;
|
||||
&:hover:not(:focus) {
|
||||
background: ${ color.backgroundLight };
|
||||
background: ${color.backgroundLight};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
Loading…
Reference in New Issue
Block a user