Add modal

This commit is contained in:
Adrian Wozniak 2020-04-01 10:36:05 +02:00
parent 4be598b9ae
commit 5758c79f35
13 changed files with 221 additions and 57 deletions

View File

@ -0,0 +1,80 @@
.modal {
z-index: var(--modal);
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.modal > .clickableOverlay {
min-height: 100%;
background: rgba(9, 30, 66, 0.54);
}
.modal > .clickableOverlay.center {
display: flex;
justify-content: center;
align-items: center;
padding: 50px;
}
.modal > .clickableOverlay > .styledModal {
display: inline-block;
position: relative;
width: 100%;
background: #fff;
}
.modal > .clickableOverlay > .styledModal.center {
/*max-width: ${props => props.width}px;*/
vertical-align: middle;
border-radius: 3px;
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1);
}
.modal > .clickableOverlay > .styledModal.aside {
/*max-width: ${props => props.width}px;*/
min-height: 100vh;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
}
.modal > .clickableOverlay > .styledModal.aside > .styledIcon {
position: absolute;
font-size: 25px;
color: var(--textMedium);
transition: all 0.1s;
cursor: pointer;
user-select: none;
}
.modal > .clickableOverlay > .styledModal.aside > .styledIcon.modalVariantCenter {
top: 10px;
right: 12px;
padding: 3px 5px 0 5px;
border-radius: 4px;
}
.modal > .clickableOverlay > .styledModal.aside > .styledIcon.modalVariantCenter:hover {
background: var(--backgroundLight);
}
.modal > .clickableOverlay > .styledModal.aside > .styledIcon.modalVariantAside {
top: 10px;
right: -30px;
width: 50px;
height: 50px;
padding-top: 10px;
border-radius: 3px;
text-align: center;
background: #fff;
border: 1px solid var(--borderLightest);
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1);
}
.modal > .clickableOverlay > .styledModal.aside > .styledIcon.modalVariantAside:hover {
color: var(--primary);
}

View File

@ -9,4 +9,5 @@
@import "css/styledTooltip.css";
@import "css/styledAvatar.css";
@import "css/app.css";
@import "css/modal.css";
@import "css/project.css";

View File

@ -57,6 +57,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
crate::shared::update(&msg, model, orders);
match model.page {
Page::Project => project::update(msg, model, orders),
Page::EditIssue(_id) => project::update(msg, model, orders),
Page::ProjectSettings => project_settings::update(msg, model, orders),
Page::Login => login::update(msg, model, orders),
Page::Register => register::update(msg, model, orders),
@ -69,6 +70,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
fn view(model: &model::Model) -> Node<Msg> {
match model.page {
Page::Project => project::view(model),
Page::EditIssue(_id) => project::view(model),
Page::ProjectSettings => project_settings::view(model),
Page::Login => login::view(model),
Page::Register => register::view(model),
@ -82,6 +84,10 @@ fn routes(url: Url) -> Option<Msg> {
match url.path[0].as_ref() {
"board" => Some(Msg::ChangePage(model::Page::Project)),
"issues" => match url.path.get(1).as_ref().map(|s| s.parse::<i32>()) {
Some(Ok(id)) => Some(Msg::ChangePage(model::Page::EditIssue(id))),
_ => None,
},
"project-settings" => Some(Msg::ChangePage(model::Page::ProjectSettings)),
"login" => Some(Msg::ChangePage(model::Page::Login)),
"register" => Some(Msg::ChangePage(model::Page::Register)),

View File

@ -13,6 +13,7 @@ pub type ProjectId = i32;
#[serde(rename_all = "kebab-case")]
pub enum Page {
Project,
EditIssue(IssueId),
ProjectSettings,
Login,
Register,
@ -21,12 +22,12 @@ pub enum Page {
impl Page {
pub fn to_path(&self) -> String {
match self {
Page::Project => "/board",
Page::ProjectSettings => "/project-settings",
Page::Login => "/login",
Page::Register => "/register",
Page::Project => "/board".to_string(),
Page::EditIssue(id) => format!("/issues/{id}", id = id),
Page::ProjectSettings => "/project-settings".to_string(),
Page::Login => "/login".to_string(),
Page::Register => "/register".to_string(),
}
.to_string()
}
}

View File

@ -200,7 +200,7 @@ fn project_board_filters(model: &Model) -> Node<Msg> {
|| project_page.recently_updated_filter
|| !project_page.active_avatar_filters.is_empty()
{
true => button![
true => seed::button![
id!["clearAllFilters"],
"Clear all",
mouse_ev(Ev::Click, |_| Msg::ProjectClearFilters),
@ -360,9 +360,7 @@ fn project_issue(model: &Model, project: &FullProject, issue: &Issue) -> Node<Ms
div![
attrs![At::Class => "bottom"],
div![
// <IssueTypeIcon type={issue.type} />
div![attrs![At::Class => "issueTypeIcon"], issue_type_icon],
// <IssuePriorityIcon priority={issue.priority} top={-1} left={4} />
div![attrs![At::Class => "issuePriorityIcon"], priority_icon]
],
div![attrs![At::Class => "assignees"], avatars,],

View File

@ -5,6 +5,7 @@ use crate::model::{Icon, Model};
use crate::Msg;
pub mod aside;
pub mod modal;
pub mod navbar_left;
pub mod styled_avatar;
pub mod styled_button;

View File

@ -0,0 +1,68 @@
use seed::{prelude::*, *};
use crate::model::Icon;
use crate::shared::{styled_icon, ToNode};
use crate::Msg;
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
pub enum Variant {
Center,
Aside,
}
impl Variant {
pub fn to_class_name(&self) -> &str {
match self {
Variant::Center => "center",
Variant::Aside => "aside",
}
}
pub fn to_icon_class_name(&self) -> &str {
match self {
Variant::Center => "modalVariantCenter",
Variant::Aside => "modalVariantAside",
}
}
}
#[derive(Debug)]
pub struct Modal {
pub variant: Variant,
pub width: usize,
pub with_icon: bool,
pub children: Vec<Node<Msg>>,
}
impl ToNode for Modal {
fn into_node(self) -> Node<Msg> {
render(self)
}
}
pub fn render(values: Modal) -> Node<Msg> {
let Modal {
variant,
width,
with_icon,
children,
} = values;
let icon = if with_icon {
let mut styled_icon = styled_icon(Icon::Close);
styled_icon.add_class(variant.to_icon_class_name().to_string());
styled_icon
} else {
empty![]
};
div![
attrs![At::Class => "modal"],
div![
attrs![At::Class => format!("clickableOverlay {}", variant.to_class_name())],
div![
attrs![At::Class => format!("styledModal {}", variant.to_class_name())],
icon,
children
]
]
]
}

View File

@ -1,6 +1,6 @@
import styled from 'styled-components';
import { color, font, mixin } from 'shared/utils/styles';
import { color } from 'shared/utils/styles';
export const List = styled.div`
display: flex;
@ -17,7 +17,9 @@ export const Title = styled.div`
text-transform: uppercase;
color: ${color.textMedium};
font-size: 12.5px;
${mixin.truncateText}
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`;
export const IssuesCount = styled.span`

View File

@ -1,6 +1,6 @@
import styled from 'styled-components';
import { color, sizes, font, mixin, zIndexValues } from 'shared/utils/styles';
import { color, font, mixin, sizes, zIndexValues } from 'shared/utils/styles';
export const Sidebar = styled.div`
position: fixed;
@ -12,7 +12,9 @@ export const Sidebar = styled.div`
padding: 0 16px 24px;
background: ${color.backgroundLightest};
border-right: 1px solid ${color.borderLightest};
${mixin.scrollableY}
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
${mixin.customScrollbar()}
@media (max-width: 1100px) {
width: ${sizes.secondarySideBarWidth - 10}px;

View File

@ -92,7 +92,9 @@ export const TimeSection = styled.div`
width: 90px;
padding: 5px 0;
border-left: 1px solid ${color.borderLight};
${mixin.scrollableY}
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
`;
export const Time = styled.div`

View File

@ -1,6 +1,6 @@
import styled, { css } from 'styled-components';
import { color, mixin, zIndexValues } from 'shared/utils/styles';
import { color, zIndexValues } from 'shared/utils/styles';
import Icon from 'shared/components/Icon';
export const ScrollOverlay = styled.div`
@ -10,7 +10,9 @@ export const ScrollOverlay = styled.div`
left: 0;
height: 100%;
width: 100%;
${mixin.scrollableY}
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
`;
export const ClickableOverlay = styled.div`
@ -42,11 +44,11 @@ const modalStyles = {
max-width: ${props => props.width}px;
vertical-align: middle;
border-radius: 3px;
${mixin.boxShadowMedium}
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1);
`,
aside: css`
min-height: 100vh;
max-width: ${props => props.width}px;
min-height: 100vh;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
`,
};
@ -57,7 +59,7 @@ export const CloseIcon = styled(Icon)`
color: ${color.textMedium};
transition: all 0.1s;
cursor: pointer;
user-select: none;
user-select: none;
${props => closeIconStyles[props.variant]}
`;
@ -81,7 +83,7 @@ const closeIconStyles = {
text-align: center;
background: #fff;
border: 1px solid ${color.borderLightest};
${mixin.boxShadowMedium};
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1);;
&:hover {
color: ${color.primary};
}

View File

@ -1,34 +1,11 @@
import React, { Fragment, useState, useRef, useEffect, useCallback } from 'react';
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown';
import { ScrollOverlay, ClickableOverlay, StyledModal, CloseIcon } from './Styles';
const propTypes = {
className: PropTypes.string,
testid: PropTypes.string,
variant: PropTypes.oneOf(['center', 'aside']),
width: PropTypes.number,
withCloseIcon: PropTypes.bool,
isOpen: PropTypes.bool,
onClose: PropTypes.func,
renderLink: PropTypes.func,
renderContent: PropTypes.func.isRequired,
};
const defaultProps = {
className: undefined,
testid: 'modal',
variant: 'center',
width: 600,
withCloseIcon: true,
isOpen: undefined,
onClose: () => {},
renderLink: () => {},
};
import { ClickableOverlay, CloseIcon, ScrollOverlay, StyledModal } from './Styles';
const Modal = ({
className,
@ -76,26 +53,48 @@ const Modal = ({
<ScrollOverlay>
<ClickableOverlay variant={variant} ref={$clickableOverlayRef}>
<StyledModal
className={className}
variant={variant}
width={width}
data-testid={testid}
ref={$modalRef}
className={className}
variant={ variant }
width={ width }
data-testid={ testid }
ref={ $modalRef }
>
{withCloseIcon && <CloseIcon type="close" variant={variant} onClick={closeModal} />}
{renderContent({ close: closeModal })}
{ withCloseIcon && <CloseIcon type="close" variant={ variant } onClick={ closeModal }/> }
{ renderContent({ close: closeModal }) }
</StyledModal>
</ClickableOverlay>
</ScrollOverlay>,
$root,
)}
$root,
) }
</Fragment>
);
};
const $root = document.getElementById('root');
Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;
Modal.propTypes = {
className: PropTypes.string,
testid: PropTypes.string,
variant: PropTypes.oneOf([ 'center', 'aside' ]),
width: PropTypes.number,
withCloseIcon: PropTypes.bool,
isOpen: PropTypes.bool,
onClose: PropTypes.func,
renderLink: PropTypes.func,
renderContent: PropTypes.func.isRequired,
};
Modal.defaultProps = {
className: undefined,
testid: 'modal',
variant: 'center',
width: 600,
withCloseIcon: true,
isOpen: undefined,
onClose: () => {
},
renderLink: () => {
},
};
export default Modal;

View File

@ -1,6 +1,6 @@
import styled, { css } from 'styled-components';
import { color, font, mixin, zIndexValues } from 'shared/utils/styles';
import { color, mixin, zIndexValues } from 'shared/utils/styles';
import Icon from 'shared/components/Icon';
export const StyledSelect = styled.div`
@ -124,7 +124,9 @@ export const ClearIcon = styled(Icon)`
export const Options = styled.div`
max-height: 200px;
${mixin.scrollableY};
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;;
${mixin.customScrollbar()};
`;