feature: add projects & quick card members button
This commit is contained in:
@ -128,28 +128,3 @@ export const CardMembers = styled.div`
|
||||
margin: 0 -2px 0 0;
|
||||
`;
|
||||
|
||||
export const CardMember = styled.div<{ bgColor: string; ref: any }>`
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
float: right;
|
||||
margin: 0 0 4px 4px;
|
||||
|
||||
background-color: ${props => props.bgColor};
|
||||
color: #fff;
|
||||
border-radius: 25em;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
z-index: 0;
|
||||
`;
|
||||
|
||||
export const CardMemberInitials = styled.div`
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react';
|
||||
import { DraggableProvidedDraggableProps } from 'react-beautiful-dnd';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import Member from 'shared/components/Member';
|
||||
import { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faClock, faCheckSquare, faEye } from '@fortawesome/free-regular-svg-icons';
|
||||
import {
|
||||
@ -19,8 +20,6 @@ import {
|
||||
ListCardOperation,
|
||||
CardTitle,
|
||||
CardMembers,
|
||||
CardMember,
|
||||
CardMemberInitials,
|
||||
} from './Styles';
|
||||
|
||||
type DueDate = {
|
||||
@ -33,31 +32,6 @@ type Checklist = {
|
||||
total: number;
|
||||
};
|
||||
|
||||
type MemberProps = {
|
||||
onCardMemberClick?: OnCardMemberClick;
|
||||
taskID: string;
|
||||
member: TaskUser;
|
||||
};
|
||||
|
||||
const Member: React.FC<MemberProps> = ({ onCardMemberClick, taskID, member }) => {
|
||||
const $targetRef = useRef<HTMLDivElement>();
|
||||
return (
|
||||
<CardMember
|
||||
ref={$targetRef}
|
||||
onClick={e => {
|
||||
if (onCardMemberClick) {
|
||||
e.stopPropagation();
|
||||
onCardMemberClick($targetRef, taskID, member.id);
|
||||
}
|
||||
}}
|
||||
key={member.id}
|
||||
bgColor={member.profileIcon.bgColor ?? '#7367F0'}
|
||||
>
|
||||
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
|
||||
</CardMember>
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
description: string;
|
||||
|
@ -15,7 +15,7 @@ import { Container, BoardWrapper } from './Styles';
|
||||
|
||||
interface SimpleProps {
|
||||
taskGroups: Array<TaskGroup>;
|
||||
onTaskDrop: (task: Task) => void;
|
||||
onTaskDrop: (task: Task, previousTaskGroupID: string) => void;
|
||||
onTaskGroupDrop: (taskGroup: TaskGroup) => void;
|
||||
|
||||
onTaskClick: (task: Task) => void;
|
||||
@ -110,7 +110,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
||||
id: destination.droppableId,
|
||||
},
|
||||
};
|
||||
onTaskDrop(newTask);
|
||||
onTaskDrop(newTask, droppedTask.taskGroup.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
55
web/src/shared/components/Member/index.tsx
Normal file
55
web/src/shared/components/Member/index.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import React, { useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const CardMember = styled.div<{ bgColor: string; ref: any }>`
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
float: right;
|
||||
margin: 0 0 4px 4px;
|
||||
|
||||
background-color: ${props => props.bgColor};
|
||||
color: #fff;
|
||||
border-radius: 25em;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
z-index: 0;
|
||||
`;
|
||||
|
||||
const CardMemberInitials = styled.div`
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
type MemberProps = {
|
||||
onCardMemberClick?: OnCardMemberClick;
|
||||
taskID: string;
|
||||
member: TaskUser;
|
||||
};
|
||||
|
||||
const Member: React.FC<MemberProps> = ({ onCardMemberClick, taskID, member }) => {
|
||||
const $targetRef = useRef<HTMLDivElement>();
|
||||
return (
|
||||
<CardMember
|
||||
ref={$targetRef}
|
||||
onClick={e => {
|
||||
if (onCardMemberClick) {
|
||||
e.stopPropagation();
|
||||
onCardMemberClick($targetRef, taskID, member.id);
|
||||
}
|
||||
}}
|
||||
key={member.id}
|
||||
bgColor={member.profileIcon.bgColor ?? '#7367F0'}
|
||||
>
|
||||
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
|
||||
</CardMember>
|
||||
);
|
||||
};
|
||||
|
||||
export default Member;
|
32
web/src/shared/components/NewProject/NewProject.stories.tsx
Normal file
32
web/src/shared/components/NewProject/NewProject.stories.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { useState, useRef, createRef } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import styled from 'styled-components';
|
||||
import NormalizeStyles from 'App/NormalizeStyles';
|
||||
import BaseStyles from 'App/BaseStyles';
|
||||
|
||||
import NewProject from '.';
|
||||
|
||||
export default {
|
||||
component: NewProject,
|
||||
title: 'NewProject',
|
||||
parameters: {
|
||||
backgrounds: [
|
||||
{ name: 'white', value: '#ffffff', default: true },
|
||||
{ name: 'gray', value: '#f8f8f8' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<>
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
<NewProject
|
||||
onCreateProject={action('create project')}
|
||||
teams={[{ name: 'General', id: 'general', createdAt: '' }]}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
280
web/src/shared/components/NewProject/index.tsx
Normal file
280
web/src/shared/components/NewProject/index.tsx
Normal file
@ -0,0 +1,280 @@
|
||||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
import Select from 'react-select';
|
||||
import { ArrowLeft, Cross } from 'shared/icons';
|
||||
|
||||
const Overlay = styled.div`
|
||||
z-index: 10000;
|
||||
background: #262c49;
|
||||
bottom: 0;
|
||||
color: #fff;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
const Header = styled.div`
|
||||
height: 64px;
|
||||
padding: 0 24px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
justify-content: space-between;
|
||||
transition: box-shadow 250ms;
|
||||
`;
|
||||
|
||||
const HeaderLeft = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
`;
|
||||
const HeaderRight = styled.div`
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 32px 0;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const ContainerContent = styled.div`
|
||||
width: 520px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const Title = styled.h1`
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: #c2c6dc;
|
||||
margin-bottom: 25px;
|
||||
`;
|
||||
|
||||
const ProjectName = styled.input`
|
||||
margin: 0 0 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 12px;
|
||||
background: #262c49;
|
||||
outline: none;
|
||||
color: #c2c6dc;
|
||||
|
||||
border-radius: 3px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-image: initial;
|
||||
border-color: #414561;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
|
||||
&:focus {
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||
}
|
||||
`;
|
||||
const ProjectNameLabel = styled.label`
|
||||
color: #c2c6dc;
|
||||
font-size: 12px;
|
||||
margin-bottom: 4px;
|
||||
`;
|
||||
const ProjectInfo = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const ProjectField = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 15px;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
const ProjectTeamField = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
const colourStyles = {
|
||||
control: (styles: any, data: any) => {
|
||||
return {
|
||||
...styles,
|
||||
backgroundColor: data.isMenuOpen ? mixin.darken('#262c49', 0.15) : '#262c49',
|
||||
boxShadow: data.menuIsOpen ? 'rgb(115, 103, 240) 0px 0px 0px 1px' : 'none',
|
||||
borderRadius: '3px',
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'solid',
|
||||
borderImage: 'initial',
|
||||
borderColor: '#414561',
|
||||
':hover': {
|
||||
boxShadow: 'rgb(115, 103, 240) 0px 0px 0px 1px',
|
||||
borderRadius: '3px',
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'solid',
|
||||
borderImage: 'initial',
|
||||
borderColor: '#414561',
|
||||
},
|
||||
':active': {
|
||||
boxShadow: 'rgb(115, 103, 240) 0px 0px 0px 1px',
|
||||
borderRadius: '3px',
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'solid',
|
||||
borderImage: 'initial',
|
||||
borderColor: 'rgb(115, 103, 240)',
|
||||
},
|
||||
};
|
||||
},
|
||||
menu: (styles: any) => {
|
||||
return {
|
||||
...styles,
|
||||
backgroundColor: mixin.darken('#262c49', 0.15),
|
||||
};
|
||||
},
|
||||
dropdownIndicator: (styles: any) => ({ ...styles, color: '#c2c6dc', ':hover': { color: '#c2c6dc' } }),
|
||||
indicatorSeparator: (styles: any) => ({ ...styles, color: '#c2c6dc' }),
|
||||
option: (styles: any, { data, isDisabled, isFocused, isSelected }: any) => {
|
||||
return {
|
||||
...styles,
|
||||
backgroundColor: isDisabled
|
||||
? null
|
||||
: isSelected
|
||||
? mixin.darken('#262c49', 0.25)
|
||||
: isFocused
|
||||
? mixin.darken('#262c49', 0.15)
|
||||
: null,
|
||||
color: isDisabled ? '#ccc' : isSelected ? '#fff' : '#c2c6dc',
|
||||
cursor: isDisabled ? 'not-allowed' : 'default',
|
||||
':active': {
|
||||
...styles[':active'],
|
||||
backgroundColor: !isDisabled && (isSelected ? mixin.darken('#262c49', 0.25) : '#fff'),
|
||||
},
|
||||
':hover': {
|
||||
...styles[':hover'],
|
||||
backgroundColor: !isDisabled && (isSelected ? 'rgb(115, 103, 240)' : 'rgb(115, 103, 240)'),
|
||||
},
|
||||
};
|
||||
},
|
||||
placeholder: (styles: any) => ({ ...styles, color: '#c2c6dc' }),
|
||||
clearIndicator: (styles: any) => ({ ...styles, color: '#c2c6dc', ':hover': { color: '#c2c6dc' } }),
|
||||
input: (styles: any) => ({
|
||||
...styles,
|
||||
color: '#fff',
|
||||
}),
|
||||
singleValue: (styles: any) => {
|
||||
return {
|
||||
...styles,
|
||||
color: '#fff',
|
||||
};
|
||||
},
|
||||
};
|
||||
const CreateButton = styled.button`
|
||||
outline: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
line-height: 20px;
|
||||
padding: 6px 12px;
|
||||
background-color: none;
|
||||
text-align: center;
|
||||
color: #c2c6dc;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
|
||||
border-radius: 3px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-image: initial;
|
||||
border-color: #414561;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: rgb(115, 103, 240);
|
||||
border-color: rgb(115, 103, 240);
|
||||
}
|
||||
`;
|
||||
type NewProjectProps = {
|
||||
teams: Array<Team>;
|
||||
onClose: () => void;
|
||||
onCreateProject: (projectName: string, teamID: string) => void;
|
||||
};
|
||||
|
||||
const NewProject: React.FC<NewProjectProps> = ({ teams, onClose, onCreateProject }) => {
|
||||
const [projectName, setProjectName] = useState('');
|
||||
const [team, setTeam] = useState<null | string>(null);
|
||||
const options = teams.map(t => ({ label: t.name, value: t.id }));
|
||||
return (
|
||||
<Overlay>
|
||||
<Content>
|
||||
<Header>
|
||||
<HeaderLeft
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<ArrowLeft color="#c2c6dc" />
|
||||
</HeaderLeft>
|
||||
<HeaderRight
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<Cross color="#c2c6dc" />
|
||||
</HeaderRight>
|
||||
</Header>
|
||||
<Container>
|
||||
<ContainerContent>
|
||||
<Title>Add project details</Title>
|
||||
<ProjectInfo>
|
||||
<ProjectField>
|
||||
<ProjectNameLabel>Project name</ProjectNameLabel>
|
||||
<ProjectName
|
||||
value={projectName}
|
||||
onChange={(e: any) => {
|
||||
setProjectName(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</ProjectField>
|
||||
<ProjectTeamField>
|
||||
<ProjectNameLabel>Team</ProjectNameLabel>
|
||||
<Select
|
||||
onChange={(e: any) => {
|
||||
setTeam(e.value);
|
||||
}}
|
||||
value={options.filter(d => d.value === team)}
|
||||
styles={colourStyles}
|
||||
classNamePrefix="teamSelect"
|
||||
options={options}
|
||||
/>
|
||||
</ProjectTeamField>
|
||||
</ProjectInfo>
|
||||
<CreateButton
|
||||
onClick={() => {
|
||||
if (team && projectName !== '') {
|
||||
onCreateProject(projectName, team);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Create project
|
||||
</CreateButton>
|
||||
</ContainerContent>
|
||||
</Container>
|
||||
</Content>
|
||||
</Overlay>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewProject;
|
@ -1,12 +1,12 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
|
||||
export const Container = styled.div<{ invert: boolean; top: number; left: number; ref: any }>`
|
||||
export const Container = styled.div<{ invert: boolean; top: number; left: number; ref: any; width: number | string }>`
|
||||
left: ${props => props.left}px;
|
||||
top: ${props => props.top}px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 316px;
|
||||
width: ${props => props.width}px;
|
||||
padding-top: 10px;
|
||||
height: auto;
|
||||
z-index: 40000;
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
} from './Styles';
|
||||
|
||||
type PopupContextState = {
|
||||
show: (target: RefObject<HTMLElement>, content: JSX.Element) => void;
|
||||
show: (target: RefObject<HTMLElement>, content: JSX.Element, width?: string | number) => void;
|
||||
setTab: (newTab: number) => void;
|
||||
getCurrentTab: () => number;
|
||||
hide: () => void;
|
||||
@ -23,7 +23,7 @@ type PopupContextState = {
|
||||
|
||||
type PopupProps = {
|
||||
title: string | null;
|
||||
onClose: () => void;
|
||||
onClose?: () => void;
|
||||
tab: number;
|
||||
};
|
||||
|
||||
@ -32,17 +32,23 @@ type PopupContainerProps = {
|
||||
left: number;
|
||||
invert: boolean;
|
||||
onClose: () => void;
|
||||
width?: string | number;
|
||||
};
|
||||
|
||||
const PopupContainer: React.FC<PopupContainerProps> = ({ top, left, onClose, children, invert }) => {
|
||||
const PopupContainer: React.FC<PopupContainerProps> = ({ width, top, left, onClose, children, invert }) => {
|
||||
const $containerRef = useRef();
|
||||
useOnOutsideClick($containerRef, true, onClose, null);
|
||||
return (
|
||||
<Container left={left} top={top} ref={$containerRef} invert={invert}>
|
||||
<Container width={width ?? 316} left={left} top={top} ref={$containerRef} invert={invert}>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
PopupContainer.defaultProps = {
|
||||
width: 316,
|
||||
};
|
||||
|
||||
const PopupContext = createContext<PopupContextState>({
|
||||
show: () => {},
|
||||
setTab: () => {},
|
||||
@ -63,6 +69,7 @@ type PopupState = {
|
||||
currentTab: number;
|
||||
previousTab: number;
|
||||
content: JSX.Element | null;
|
||||
width?: string | number;
|
||||
};
|
||||
|
||||
const { Provider, Consumer } = PopupContext;
|
||||
@ -81,7 +88,7 @@ const defaultState = {
|
||||
|
||||
export const PopupProvider: React.FC = ({ children }) => {
|
||||
const [currentState, setState] = useState<PopupState>(defaultState);
|
||||
const show = (target: RefObject<HTMLElement>, content: JSX.Element) => {
|
||||
const show = (target: RefObject<HTMLElement>, content: JSX.Element, width?: number | string) => {
|
||||
if (target && target.current) {
|
||||
const bounds = target.current.getBoundingClientRect();
|
||||
if (bounds.left + 304 + 30 > window.innerWidth) {
|
||||
@ -93,6 +100,7 @@ export const PopupProvider: React.FC = ({ children }) => {
|
||||
currentTab: 0,
|
||||
previousTab: 0,
|
||||
content,
|
||||
width: width ?? 316,
|
||||
});
|
||||
} else {
|
||||
setState({
|
||||
@ -103,6 +111,7 @@ export const PopupProvider: React.FC = ({ children }) => {
|
||||
currentTab: 0,
|
||||
previousTab: 0,
|
||||
content,
|
||||
width: width ?? 316,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -144,6 +153,7 @@ export const PopupProvider: React.FC = ({ children }) => {
|
||||
top={currentState.top}
|
||||
left={currentState.left}
|
||||
onClose={() => setState(defaultState)}
|
||||
width={currentState.width ?? 316}
|
||||
>
|
||||
{currentState.content}
|
||||
<ContainerDiamond invert={currentState.invert} />
|
||||
@ -162,14 +172,15 @@ type Props = {
|
||||
onClose: () => void;
|
||||
onPrevious?: () => void | null;
|
||||
noHeader?: boolean | null;
|
||||
width?: string | number;
|
||||
};
|
||||
|
||||
const PopupMenu: React.FC<Props> = ({ title, top, left, onClose, noHeader, children, onPrevious }) => {
|
||||
const PopupMenu: React.FC<Props> = ({ width, title, top, left, onClose, noHeader, children, onPrevious }) => {
|
||||
const $containerRef = useRef();
|
||||
useOnOutsideClick($containerRef, true, onClose, null);
|
||||
|
||||
return (
|
||||
<Container invert={false} left={left} top={top} ref={$containerRef}>
|
||||
<Container width={width ?? 316} invert={false} left={left} top={top} ref={$containerRef}>
|
||||
<Wrapper>
|
||||
{onPrevious && (
|
||||
<PreviousButton onClick={onPrevious}>
|
||||
@ -217,9 +228,11 @@ export const Popup: React.FC<PopupProps> = ({ title, onClose, tab, children }) =
|
||||
<HeaderTitle>{title}</HeaderTitle>
|
||||
</Header>
|
||||
)}
|
||||
<CloseButton onClick={() => onClose()}>
|
||||
<Cross color="#c2c6dc" />
|
||||
</CloseButton>
|
||||
{onClose && (
|
||||
<CloseButton onClick={() => onClose()}>
|
||||
<Cross color="#c2c6dc" />
|
||||
</CloseButton>
|
||||
)}
|
||||
<Content>{children}</Content>
|
||||
</Wrapper>
|
||||
</>
|
||||
|
@ -1,6 +1,12 @@
|
||||
import styled from 'styled-components';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
|
||||
export const AddProjectLabel = styled.span`
|
||||
padding-top: 4px;
|
||||
font-size: 14px;
|
||||
color: #c2c6dc;
|
||||
`;
|
||||
|
||||
export const ProjectContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -20,8 +26,7 @@ export const TeamTitle = styled.span`
|
||||
|
||||
export const ProjectWrapper = styled.div<{ color: string }>`
|
||||
display: flex;
|
||||
padding: 15px 25px;
|
||||
border-radius: 20px;
|
||||
padding: 15px 25px; border-radius: 20px;
|
||||
${mixin.boxShadowCard}
|
||||
background: ${props => mixin.darken(props.color, 0.35)};
|
||||
color: #fff;
|
||||
@ -31,8 +36,30 @@ export const ProjectWrapper = styled.div<{ color: string }>`
|
||||
height: 100px;
|
||||
transition: transform 0.25s ease;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
`;
|
||||
|
||||
export const AddProjectWrapper = styled.div`
|
||||
display: flex;
|
||||
padding: 15px 25px;
|
||||
border-radius: 20px;
|
||||
${mixin.boxShadowCard}
|
||||
border: 1px dashed;
|
||||
border-color: #c2c6dc;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
margin: 0 10px;
|
||||
width: 240px;
|
||||
flex-direction: column;
|
||||
height: 100px;
|
||||
transition: transform 0.25s ease;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
`;
|
||||
|
@ -1,7 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ProjectWrapper, ProjectContent, ProjectTitle, TeamTitle } from './Styles';
|
||||
import { Plus } from 'shared/icons';
|
||||
import { AddProjectWrapper, AddProjectLabel, ProjectWrapper, ProjectContent, ProjectTitle, TeamTitle } from './Styles';
|
||||
|
||||
type AddProjectItemProps = {
|
||||
onAddProject: () => void;
|
||||
};
|
||||
export const AddProjectItem: React.FC<AddProjectItemProps> = ({ onAddProject }) => {
|
||||
return (
|
||||
<AddProjectWrapper
|
||||
onClick={() => {
|
||||
onAddProject();
|
||||
}}
|
||||
>
|
||||
<Plus size={20} color="#c2c6dc" />
|
||||
<AddProjectLabel>New Project</AddProjectLabel>
|
||||
</AddProjectWrapper>
|
||||
);
|
||||
};
|
||||
type Props = {
|
||||
project: Project;
|
||||
};
|
||||
|
51
web/src/shared/components/ProjectSettings/index.tsx
Normal file
51
web/src/shared/components/ProjectSettings/index.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ListActionsWrapper = styled.ul`
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
export const ListActionItemWrapper = styled.li`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
`;
|
||||
export const ListActionItem = styled.span`
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #c2c6dc;
|
||||
font-weight: 400;
|
||||
padding: 6px 12px;
|
||||
position: relative;
|
||||
margin: 0 -12px;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
background: rgb(115, 103, 240);
|
||||
}
|
||||
`;
|
||||
|
||||
export const ListSeparator = styled.hr`
|
||||
background-color: #414561;
|
||||
border: 0;
|
||||
height: 1px;
|
||||
margin: 8px 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type Props = {};
|
||||
const ProjectSettings: React.FC<Props> = () => {
|
||||
return (
|
||||
<>
|
||||
<ListActionsWrapper>
|
||||
<ListActionItemWrapper onClick={() => {}}>
|
||||
<ListActionItem>Delete Project</ListActionItem>
|
||||
</ListActionItemWrapper>
|
||||
</ListActionsWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProjectSettings;
|
@ -56,6 +56,7 @@ export const Default = () => {
|
||||
onCloseEditor={() => setEditorOpen(false)}
|
||||
onEditCard={action('edit card')}
|
||||
onOpenLabelsPopup={action('open popup')}
|
||||
onOpenMembersPopup={action('open popup')}
|
||||
onArchiveCard={action('archive card')}
|
||||
top={top}
|
||||
left={left}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import Cross from 'shared/icons/Cross';
|
||||
import styled from 'styled-components';
|
||||
import Member from 'shared/components/Member';
|
||||
import {
|
||||
Wrapper,
|
||||
Container,
|
||||
@ -14,20 +16,39 @@ import {
|
||||
ListCardLabel,
|
||||
} from './Styles';
|
||||
|
||||
export const CardMembers = styled.div`
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
task: Task;
|
||||
onCloseEditor: () => void;
|
||||
onEditCard: (taskGroupID: string, taskID: string, cardName: string) => void;
|
||||
onOpenLabelsPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
||||
onOpenMembersPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
||||
onArchiveCard: (taskGroupID: string, taskID: string) => void;
|
||||
onCardMemberClick?: OnCardMemberClick;
|
||||
top: number;
|
||||
left: number;
|
||||
};
|
||||
|
||||
const QuickCardEditor = ({ task, onCloseEditor, onOpenLabelsPopup, onArchiveCard, onEditCard, top, left }: Props) => {
|
||||
const QuickCardEditor = ({
|
||||
task,
|
||||
onCloseEditor,
|
||||
onOpenLabelsPopup,
|
||||
onOpenMembersPopup,
|
||||
onCardMemberClick,
|
||||
onArchiveCard,
|
||||
onEditCard,
|
||||
top,
|
||||
left,
|
||||
}: Props) => {
|
||||
const [currentCardTitle, setCardTitle] = useState(task.name);
|
||||
const $editorRef: any = useRef();
|
||||
const $labelsRef: any = useRef();
|
||||
const $membersRef: any = useRef();
|
||||
useEffect(() => {
|
||||
$editorRef.current.focus();
|
||||
$editorRef.current.select();
|
||||
@ -71,10 +92,25 @@ const QuickCardEditor = ({ task, onCloseEditor, onOpenLabelsPopup, onArchiveCard
|
||||
value={currentCardTitle}
|
||||
ref={$editorRef}
|
||||
/>
|
||||
<CardMembers>
|
||||
{task.assigned &&
|
||||
task.assigned.map(member => (
|
||||
<Member key={member.id} taskID={task.id} member={member} onCardMemberClick={onCardMemberClick} />
|
||||
))}
|
||||
</CardMembers>
|
||||
</EditorDetails>
|
||||
</Editor>
|
||||
<SaveButton onClick={e => onEditCard(task.taskGroup.id, task.id, currentCardTitle)}>Save</SaveButton>
|
||||
<EditorButtons>
|
||||
<EditorButton
|
||||
ref={$membersRef}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onOpenMembersPopup($membersRef, task);
|
||||
}}
|
||||
>
|
||||
Edit Assigned
|
||||
</EditorButton>
|
||||
<EditorButton
|
||||
ref={$labelsRef}
|
||||
onClick={e => {
|
||||
|
@ -44,6 +44,7 @@ export const Default = () => {
|
||||
lastName="Knott"
|
||||
initials="JK"
|
||||
onNotificationClick={action('notifications click')}
|
||||
onOpenSettings={action('open settings')}
|
||||
onProfileClick={onClick}
|
||||
/>
|
||||
{menu.isOpen && (
|
||||
|
@ -32,9 +32,14 @@ import MiniProfile from 'shared/components/MiniProfile';
|
||||
type ProjectHeadingProps = {
|
||||
projectName: string;
|
||||
onSaveProjectName?: (projectName: string) => void;
|
||||
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
|
||||
};
|
||||
|
||||
const ProjectHeading: React.FC<ProjectHeadingProps> = ({ projectName: initialProjectName, onSaveProjectName }) => {
|
||||
const ProjectHeading: React.FC<ProjectHeadingProps> = ({
|
||||
projectName: initialProjectName,
|
||||
onSaveProjectName,
|
||||
onOpenSettings,
|
||||
}) => {
|
||||
const [isEditProjectName, setEditProjectName] = useState(false);
|
||||
const [projectName, setProjectName] = useState(initialProjectName);
|
||||
const $projectName = useRef<HTMLTextAreaElement>(null);
|
||||
@ -66,6 +71,7 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({ projectName: initialPro
|
||||
}
|
||||
};
|
||||
|
||||
const $settings = useRef<HTMLButtonElement>(null);
|
||||
return (
|
||||
<>
|
||||
<Separator>»</Separator>
|
||||
@ -87,7 +93,12 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({ projectName: initialPro
|
||||
{projectName}
|
||||
</ProjectName>
|
||||
)}
|
||||
<ProjectSettingsButton>
|
||||
<ProjectSettingsButton
|
||||
onClick={() => {
|
||||
onOpenSettings($settings);
|
||||
}}
|
||||
ref={$settings}
|
||||
>
|
||||
<AngleDown color="#c2c6dc" />
|
||||
</ProjectSettingsButton>
|
||||
<ProjectSettingsButton>
|
||||
@ -103,6 +114,7 @@ type NavBarProps = {
|
||||
onSaveProjectName?: (projectName: string) => void;
|
||||
onNotificationClick: () => void;
|
||||
bgColor: string;
|
||||
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
initials: string;
|
||||
@ -119,6 +131,7 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
initials,
|
||||
bgColor,
|
||||
projectMembers,
|
||||
onOpenSettings,
|
||||
}) => {
|
||||
const $profileRef: any = useRef(null);
|
||||
const handleProfileClick = () => {
|
||||
@ -147,7 +160,13 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
<ProjectActions>
|
||||
<ProjectMeta>
|
||||
<ProjectSwitcher>Projects</ProjectSwitcher>
|
||||
{projectName && <ProjectHeading projectName={projectName} onSaveProjectName={onSaveProjectName} />}
|
||||
{projectName && (
|
||||
<ProjectHeading
|
||||
onOpenSettings={onOpenSettings}
|
||||
projectName={projectName}
|
||||
onSaveProjectName={onSaveProjectName}
|
||||
/>
|
||||
)}
|
||||
</ProjectMeta>
|
||||
{projectName && (
|
||||
<ProjectTabs>
|
||||
|
Reference in New Issue
Block a user