Add modal
This commit is contained in:
parent
4be598b9ae
commit
5758c79f35
80
jirs-client/js/css/modal.css
Normal file
80
jirs-client/js/css/modal.css
Normal 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);
|
||||
}
|
@ -9,4 +9,5 @@
|
||||
@import "css/styledTooltip.css";
|
||||
@import "css/styledAvatar.css";
|
||||
@import "css/app.css";
|
||||
@import "css/modal.css";
|
||||
@import "css/project.css";
|
||||
|
@ -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)),
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,],
|
||||
|
@ -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;
|
||||
|
68
jirs-client/src/shared/modal.rs
Normal file
68
jirs-client/src/shared/modal.rs
Normal 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
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
@ -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`
|
||||
|
6
react-client/src/Project/Sidebar/Styles.js
vendored
6
react-client/src/Project/Sidebar/Styles.js
vendored
@ -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;
|
||||
|
@ -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`
|
||||
|
@ -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};
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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()};
|
||||
`;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user