Add modals on top other modal

This commit is contained in:
Adrian Woźniak 2020-04-03 16:15:56 +02:00
parent 9980b75ab5
commit f2b5753f83
12 changed files with 189 additions and 24 deletions

View File

@ -76,3 +76,7 @@
.modal > .clickableOverlay > .styledModal.aside > .styledIcon.modalVariantAside:hover { .modal > .clickableOverlay > .styledModal.aside > .styledIcon.modalVariantAside:hover {
color: var(--primary); color: var(--primary);
} }
.styledModal.confirmModal {
padding: 35px 40px 40px;
}

View File

@ -11,7 +11,7 @@
@import "css/styledSelect.css"; @import "css/styledSelect.css";
@import "css/styledButton.css"; @import "css/styledButton.css";
@import "css/styledInput.css"; @import "css/styledInput.css";
@import "css/styledModal.css";
@import "css/app.css"; @import "css/app.css";
@import "css/modal.css";
@import "css/issue.css"; @import "css/issue.css";
@import "css/project.css"; @import "css/project.css";

View File

@ -3,7 +3,7 @@ use seed::{prelude::*, *};
use jirs_data::IssueStatus; use jirs_data::IssueStatus;
use crate::model::Page; use crate::model::{ModalType, Page};
use crate::shared::styled_select::StyledSelectChange; use crate::shared::styled_select::StyledSelectChange;
mod api; mod api;
@ -23,6 +23,12 @@ pub type AvatarFilterActive = bool;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum FieldId { pub enum FieldId {
IssueTypeEditModalTop, IssueTypeEditModalTop,
CopyButtonLabel,
}
#[derive(Clone, Debug)]
pub enum FieldChange {
LinkCopied(FieldId, bool),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -52,7 +58,9 @@ pub enum Msg {
IssueUpdateResult(FetchObject<String>), IssueUpdateResult(FetchObject<String>),
// modals // modals
PopModal, ModalOpened(ModalType),
ModalDropped,
ModalChanged(FieldChange),
} }
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) { fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {

View File

@ -0,0 +1,16 @@
use seed::{prelude::*, *};
use crate::shared::styled_modal::StyledModal;
use crate::shared::ToNode;
use crate::{model, Msg};
pub fn view(model: &model::Model) -> Node<Msg> {
let handle_issue_delete = mouse_ev(Ev::Click, |_| Msg::NoOp);
StyledModal::build()
.title("Are you sure you want to delete this issue?")
.message("Once you delete, it's gone for good.")
.confirm_text("Delete issue")
.on_confirm(handle_issue_delete)
.build()
.into_node()
}

View File

@ -2,12 +2,12 @@ use seed::{prelude::*, *};
use jirs_data::{Issue, IssueType}; use jirs_data::{Issue, IssueType};
use crate::model::{EditIssueModal, Model}; use crate::model::{EditIssueModal, ModalType, Model};
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_icon::{Icon, StyledIcon}; use crate::shared::styled_icon::{Icon, StyledIcon};
use crate::shared::styled_select::{StyledSelect, Variant as SelectVariant}; use crate::shared::styled_select::{StyledSelect, Variant as SelectVariant};
use crate::shared::ToNode; use crate::shared::ToNode;
use crate::{FieldId, IssueId, Msg}; use crate::{FieldChange, FieldId, IssueId, Msg};
#[derive(PartialOrd, PartialEq, Debug)] #[derive(PartialOrd, PartialEq, Debug)]
struct IssueTypeOption(IssueId, IssueType); struct IssueTypeOption(IssueId, IssueType);
@ -92,10 +92,13 @@ pub fn view(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg>
el.set_selection_range(0, 9999).unwrap(); el.set_selection_range(0, 9999).unwrap();
seed::html_document().exec_command("copy").unwrap(); seed::html_document().exec_command("copy").unwrap();
seed::body().remove_child(&el).unwrap(); seed::body().remove_child(&el).unwrap();
Msg::NoOp Msg::ModalChanged(FieldChange::LinkCopied(FieldId::CopyButtonLabel, true))
});
let close_handler = mouse_ev(Ev::Click, |_| Msg::ModalDropped);
let delete_confirmation_handler = mouse_ev(Ev::Click, move |_| {
Msg::ModalOpened(ModalType::DeleteIssueConfirm(issue_id))
}); });
let close_handler = mouse_ev(Ev::Click, |_| Msg::PopModal);
let copy_button = StyledButton::build() let copy_button = StyledButton::build()
.empty() .empty()
.icon(Icon::Link) .icon(Icon::Link)
@ -110,6 +113,7 @@ pub fn view(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg>
let delete_button = StyledButton::build() let delete_button = StyledButton::build()
.empty() .empty()
.icon(Icon::Trash.into_styled_builder().size(19).build()) .icon(Icon::Trash.into_styled_builder().size(19).build())
.on_click(delete_confirmation_handler)
.build() .build()
.into_node(); .into_node();
let close_button = StyledButton::build() let close_button = StyledButton::build()

View File

@ -7,16 +7,29 @@ use crate::model::{EditIssueModal, ModalType, Page};
use crate::shared::modal::{Modal, Variant as ModalVariant}; use crate::shared::modal::{Modal, Variant as ModalVariant};
use crate::shared::styled_select::StyledSelectChange; use crate::shared::styled_select::StyledSelectChange;
use crate::shared::{find_issue, ToNode}; use crate::shared::{find_issue, ToNode};
use crate::{model, FieldId, Msg}; use crate::{model, FieldChange, FieldId, Msg};
mod confirm_delete_issue;
mod issue_details; mod issue_details;
pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) { pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
match msg { match msg {
Msg::PopModal => match model.modals.pop() { Msg::ModalDropped => match model.modals.pop() {
_ => (), _ => (),
}, },
Msg::ModalChanged(FieldChange::LinkCopied(FieldId::CopyButtonLabel, true)) => {
for modal in model.modals.iter_mut() {
if let ModalType::EditIssue(_, edit) = modal {
edit.link_copied = true;
}
}
}
Msg::ModalOpened(modal_type) => {
model.modals.push(modal_type.clone());
}
Msg::ChangePage(Page::EditIssue(issue_id)) => { Msg::ChangePage(Page::EditIssue(issue_id)) => {
let value = find_issue(model, *issue_id) let value = find_issue(model, *issue_id)
.map(|issue| issue.issue_type.clone()) .map(|issue| issue.issue_type.clone())
@ -113,6 +126,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
empty![] empty![]
} }
} }
ModalType::DeleteIssueConfirm(_id) => confirm_delete_issue::view(model),
_ => empty![], _ => empty![],
}) })
.collect(); .collect();

View File

@ -25,7 +25,6 @@ pub struct EditIssueModal {
} }
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum Page { pub enum Page {
Project, Project,
EditIssue(IssueId), EditIssue(IssueId),

View File

@ -13,6 +13,7 @@ pub mod styled_avatar;
pub mod styled_button; pub mod styled_button;
pub mod styled_icon; pub mod styled_icon;
pub mod styled_input; pub mod styled_input;
pub mod styled_modal;
pub mod styled_select; pub mod styled_select;
pub mod styled_tooltip; pub mod styled_tooltip;

View File

@ -58,7 +58,7 @@ pub fn render(values: Modal) -> Node<Msg> {
empty![] empty![]
}; };
let close_handler = mouse_ev(Ev::Click, |_| Msg::PopModal); let close_handler = mouse_ev(Ev::Click, |_| Msg::ModalDropped);
let body_handler = mouse_ev(Ev::Click, |ev| { let body_handler = mouse_ev(Ev::Click, |ev| {
ev.stop_propagation(); ev.stop_propagation();
Msg::NoOp Msg::NoOp

View File

@ -0,0 +1,119 @@
use seed::EventHandler;
use seed::{prelude::*, *};
use crate::shared::ToNode;
use crate::Msg;
const TITLE: &str = "Warning";
const MESSAGE: &str = "Are you sure you want to continue with this action?";
const CONFIRM_TEXT: &str = "Confirm";
const CANCEL_TEXT: &str = "Cancel";
#[derive(Debug)]
pub enum Variant {
Primary,
}
#[derive(Debug)]
pub struct StyledModal {
pub variant: Variant,
pub title: String,
pub message: String,
pub confirm_text: String,
pub cancel_text: String,
pub on_confirm: Option<EventHandler<Msg>>,
}
impl StyledModal {
pub fn build() -> StyledModalBuilder {
StyledModalBuilder::default()
}
}
impl ToNode for StyledModal {
fn into_node(self) -> Node<Msg> {
render(self)
}
}
#[derive(Default)]
pub struct StyledModalBuilder {
variant: Option<Variant>,
title: Option<String>,
message: Option<String>,
confirm_text: Option<String>,
cancel_text: Option<String>,
on_confirm: Option<Option<EventHandler<Msg>>>,
}
impl StyledModalBuilder {
pub fn variant(mut self, variant: Variant) -> Self {
self.variant = Some(variant);
self
}
pub fn title<S>(mut self, title: S) -> Self
where
S: Into<String>,
{
self.title = Some(title.into());
self
}
pub fn message<S>(mut self, message: S) -> Self
where
S: Into<String>,
{
self.message = Some(message.into());
self
}
pub fn confirm_text<S>(mut self, confirm_text: S) -> Self
where
S: Into<String>,
{
self.confirm_text = Some(confirm_text.into());
self
}
pub fn cancel_text<S>(mut self, cancel_text: S) -> Self
where
S: Into<String>,
{
self.cancel_text = Some(cancel_text.into());
self
}
pub fn on_confirm(mut self, on_confirm: EventHandler<Msg>) -> Self {
self.on_confirm = Some(Some(on_confirm));
self
}
pub fn build(self) -> StyledModal {
StyledModal {
variant: self.variant.unwrap_or_else(|| Variant::Primary),
title: self.title.unwrap_or_else(|| TITLE.to_string()),
message: self.message.unwrap_or_else(|| MESSAGE.to_string()),
confirm_text: self
.confirm_text
.unwrap_or_else(|| CONFIRM_TEXT.to_string()),
cancel_text: self.cancel_text.unwrap_or_else(|| CANCEL_TEXT.to_string()),
on_confirm: None,
}
}
}
pub fn render(values: StyledModal) -> Node<Msg> {
let StyledModal {
variant,
title,
message,
confirm_text,
cancel_text,
on_confirm,
} = values;
div![
attrs![At::Class => "modal"],
div![attrs![At::Class => "styledModal"]]
]
}

View File

@ -1,8 +1,8 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { font } from 'shared/utils/styles'; import { font } from '../../../shared/utils/styles';
import Modal from 'shared/components/Modal'; import Modal from '../../../shared/components/Modal';
import Button from 'shared/components/Button'; import Button from '../../../shared/components/Button';
export const StyledConfirmModal = styled(Modal)` export const StyledConfirmModal = styled(Modal)`
padding: 35px 40px 40px; padding: 35px 40px 40px;

View File

@ -1,19 +1,19 @@
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'; import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import useOnOutsideClick from 'shared/hooks/onOutsideClick'; import useOnOutsideClick from '../../../shared/hooks/onOutsideClick';
import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown'; import useOnEscapeKeyDown from '../../../shared/hooks/onEscapeKeyDown';
import { ClickableOverlay, CloseIcon, ScrollOverlay, StyledModal } from './Styles'; import { ClickableOverlay, CloseIcon, ScrollOverlay, StyledModal } from './Styles';
const Modal = ({ const Modal = ({
className, className,
testid, testid,
variant, variant,
width, width,
withCloseIcon, withCloseIcon,
isOpen: propsIsOpen, isOpen: propsIsOpen,
onClose: tellParentToClose, onClose: tellParentToClose,
renderLink, renderLink,
renderContent, renderContent,