arch: move web folder into api & move api to top level

This commit is contained in:
Jordan Knott
2020-07-04 18:08:37 -05:00
parent eaffaa70df
commit e5d5e6da01
354 changed files with 20 additions and 1557 deletions

View File

@ -0,0 +1,174 @@
import React, { useRef } from 'react';
import { action } from '@storybook/addon-actions';
import LabelColors from 'shared/constants/labelColors';
import Card from '.';
export default {
component: Card,
title: 'Card',
parameters: {
backgrounds: [
{ name: 'gray', value: '#f8f8f8', default: true },
{ name: 'white', value: '#ffffff' },
],
},
};
const labelData: Array<ProjectLabel> = [
{
id: 'development',
name: 'Development',
createdDate: new Date().toString(),
labelColor: {
id: '1',
colorHex: LabelColors.BLUE,
name: 'blue',
position: 1,
},
},
];
export const Default = () => {
const $ref = useRef<HTMLDivElement>(null);
return (
<Card
taskID="1"
taskGroupID="1"
description=""
ref={$ref}
title="Hello, world"
onClick={action('on click')}
onContextMenu={action('on context click')}
/>
);
};
export const Labels = () => {
const $ref = useRef<HTMLDivElement>(null);
return (
<Card
taskID="1"
taskGroupID="1"
description=""
ref={$ref}
title="Hello, world"
labels={labelData}
onClick={action('on click')}
onContextMenu={action('on context click')}
/>
);
};
export const Badges = () => {
const $ref = useRef<HTMLDivElement>(null);
return (
<Card
taskID="1"
taskGroupID="1"
description="hello!"
ref={$ref}
title="Hello, world"
onClick={action('on click')}
onContextMenu={action('on context click')}
watched
checklists={{ complete: 1, total: 4 }}
dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }}
/>
);
};
export const PastDue = () => {
const $ref = useRef<HTMLDivElement>(null);
return (
<Card
taskID="1"
taskGroupID="1"
description="hello!"
ref={$ref}
title="Hello, world"
onClick={action('on click')}
onContextMenu={action('on context click')}
watched
checklists={{ complete: 1, total: 4 }}
dueDate={{ isPastDue: true, formattedDate: 'Oct 26, 2020' }}
/>
);
};
export const Everything = () => {
const $ref = useRef<HTMLDivElement>(null);
return (
<Card
taskID="1"
taskGroupID="1"
description="hello!"
ref={$ref}
title="Hello, world"
onClick={action('on click')}
onContextMenu={action('on context click')}
watched
members={[
{
id: '1',
fullName: 'Jordan Knott',
profileIcon: {
bgColor: '#0079bf',
initials: 'JK',
url: null,
},
},
]}
labels={labelData}
checklists={{ complete: 1, total: 4 }}
dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }}
/>
);
};
export const Members = () => {
const $ref = useRef<HTMLDivElement>(null);
return (
<Card
description={null}
taskID="1"
taskGroupID="1"
ref={$ref}
title="Hello, world"
onClick={action('on click')}
onContextMenu={action('on context click')}
members={[
{
id: '1',
fullName: 'Jordan Knott',
profileIcon: {
bgColor: '#0079bf',
initials: 'JK',
url: null,
},
},
]}
labels={[]}
/>
);
};
export const Editable = () => {
const $ref = useRef<HTMLDivElement>(null);
return (
<Card
taskID="1"
taskGroupID="1"
description="hello!"
ref={$ref}
title="Hello, world"
onClick={action('on click')}
onContextMenu={action('on context click')}
watched
labels={labelData}
checklists={{ complete: 1, total: 4 }}
dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }}
editable
onEditCard={action('edit card')}
/>
);
};

View File

@ -0,0 +1,171 @@
import styled, { css } from 'styled-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { mixin } from 'shared/utils/styles';
import TextareaAutosize from 'react-autosize-textarea';
import { CheckCircle } from 'shared/icons';
import { RefObject } from 'react';
export const ClockIcon = styled(FontAwesomeIcon)``;
export const EditorTextarea = styled(TextareaAutosize)`
overflow: hidden;
overflow-wrap: break-word;
resize: none;
height: 54px;
width: 100%;
background: none;
border: none;
box-shadow: none;
margin-bottom: 4px;
max-height: 162px;
min-height: 54px;
padding: 0;
font-size: 14px;
line-height: 16px;
color: rgba(${props => props.theme.colors.text.primary});
&:focus {
border: none;
outline: none;
}
`;
export const ListCardBadges = styled.div`
float: left;
display: flex;
max-width: 100%;
margin-left: -2px;
`;
export const ListCardBadge = styled.div`
color: #5e6c84;
display: flex;
align-items: center;
margin: 0 6px 4px 0;
font-size: 12px;
max-width: 100%;
min-height: 20px;
overflow: hidden;
position: relative;
padding: 2px;
text-decoration: none;
text-overflow: ellipsis;
vertical-align: top;
`;
export const DescriptionBadge = styled(ListCardBadge)`
padding-right: 6px;
`;
export const DueDateCardBadge = styled(ListCardBadge)<{ isPastDue: boolean }>`
font-size: 12px;
${props =>
props.isPastDue &&
css`
padding-left: 4px;
background-color: #ec9488;
border-radius: 3px;
color: #fff;
`}
`;
export const ListCardBadgeText = styled.span`
font-size: 12px;
padding: 0 4px 0 6px;
vertical-align: top;
white-space: nowrap;
`;
export const ListCardContainer = styled.div<{ isActive: boolean; editable: boolean }>`
max-width: 256px;
margin-bottom: 8px;
border-radius: 3px;
${mixin.boxShadowCard}
cursor: pointer !important;
position: relative;
background-color: ${props =>
props.isActive && !props.editable ? mixin.darken('#262c49', 0.1) : `rgba(${props.theme.colors.bg.secondary})`};
`;
export const ListCardInnerContainer = styled.div`
width: 100%;
height: 100%;
`;
export const ListCardDetails = styled.div<{ complete: boolean }>`
overflow: hidden;
padding: 6px 8px 2px;
position: relative;
z-index: 10;
${props => props.complete && 'opacity: 0.6;'}
`;
export const ListCardLabels = styled.div`
overflow: auto;
position: relative;
`;
export const ListCardLabel = styled.span`
height: 16px;
line-height: 16px;
padding: 0 8px;
max-width: 198px;
float: left;
font-size: 12px;
font-weight: 700;
margin: 0 4px 4px 0;
width: auto;
border-radius: 4px;
color: #fff;
display: block;
position: relative;
background-color: ${props => props.color};
`;
export const ListCardOperation = styled.span`
display: flex;
align-content: center;
justify-content: center;
border-radius: 3px;
opacity: 0.8;
padding: 6px;
position: absolute;
right: 2px;
top: 2px;
z-index: 10;
&:hover {
background-color: ${props => mixin.darken('#262c49', 0.45)};
}
`;
export const CardTitle = styled.span`
clear: both;
margin: 0 0 4px;
overflow: hidden;
text-decoration: none;
word-wrap: break-word;
line-height: 16px;
font-size: 14px;
color: rgba(${props => props.theme.colors.text.primary});
display: flex;
align-items: center;
`;
export const CardMembers = styled.div`
float: right;
margin: 0 -2px 4px 0;
`;
export const CompleteIcon = styled(CheckCircle)`
fill: rgba(${props => props.theme.colors.success});
margin-right: 4px;
flex-shrink: 0;
`;
export const EditorContent = styled.div`
display: flex;
`;

View File

@ -0,0 +1,220 @@
import React, { useState, useRef, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import TaskAssignee from 'shared/components/TaskAssignee';
import { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons';
import { faClock, faCheckSquare, faEye } from '@fortawesome/free-regular-svg-icons';
import {
EditorTextarea,
EditorContent,
CompleteIcon,
DescriptionBadge,
DueDateCardBadge,
ListCardBadges,
ListCardBadge,
ListCardBadgeText,
ListCardContainer,
ListCardInnerContainer,
ListCardDetails,
ClockIcon,
ListCardLabels,
ListCardLabel,
ListCardOperation,
CardTitle,
CardMembers,
} from './Styles';
type DueDate = {
isPastDue: boolean;
formattedDate: string;
};
type Checklist = {
complete: number;
total: number;
};
type Props = {
title: string;
taskID: string;
taskGroupID: string;
complete?: boolean;
onContextMenu?: ($target: React.RefObject<HTMLElement>, taskID: string, taskGroupID: string) => void;
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
description?: null | string;
dueDate?: DueDate;
checklists?: Checklist | null;
labels?: Array<ProjectLabel>;
watched?: boolean;
wrapperProps?: any;
members?: Array<TaskUser> | null;
onCardMemberClick?: OnCardMemberClick;
editable?: boolean;
onEditCard?: (taskGroupID: string, taskID: string, cardName: string) => void;
onCardTitleChange?: (name: string) => void;
};
const Card = React.forwardRef(
(
{
wrapperProps,
onContextMenu,
taskID,
taskGroupID,
complete,
onClick,
labels,
title,
dueDate,
description,
checklists,
watched,
members,
onCardMemberClick,
editable,
onEditCard,
onCardTitleChange,
}: Props,
$cardRef: any,
) => {
const [currentCardTitle, setCardTitle] = useState(title);
const $editorRef: any = useRef();
useEffect(() => {
setCardTitle(title);
}, [title]);
useEffect(() => {
if ($editorRef && $editorRef.current) {
$editorRef.current.focus();
$editorRef.current.select();
}
}, []);
const handleKeyDown = (e: any) => {
if (e.key === 'Enter') {
e.preventDefault();
if (onEditCard) {
onEditCard(taskGroupID, taskID, currentCardTitle);
}
}
};
const [isActive, setActive] = useState(false);
const $innerCardRef: any = useRef(null);
const onOpenComposer = () => {
if (onContextMenu) {
onContextMenu($innerCardRef, taskID, taskGroupID);
}
};
const onTaskContext = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
onOpenComposer();
};
const onOperationClick = (e: React.MouseEvent<HTMLOrSVGElement>) => {
e.preventDefault();
e.stopPropagation();
onOpenComposer();
};
return (
<ListCardContainer
onMouseEnter={() => setActive(true)}
onMouseLeave={() => setActive(false)}
ref={$cardRef}
onClick={e => {
if (onClick) {
onClick(e);
}
}}
onContextMenu={onTaskContext}
isActive={isActive}
editable={editable}
{...wrapperProps}
>
<ListCardInnerContainer ref={$innerCardRef}>
{isActive && (
<ListCardOperation>
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
</ListCardOperation>
)}
<ListCardDetails complete={complete ?? false}>
<ListCardLabels>
{labels &&
labels.map(label => (
<ListCardLabel color={label.labelColor.colorHex} key={label.id}>
{label.name}
</ListCardLabel>
))}
</ListCardLabels>
{editable ? (
<EditorContent>
{complete && <CompleteIcon width={16} height={16} />}
<EditorTextarea
onChange={e => {
setCardTitle(e.currentTarget.value);
if (onCardTitleChange) {
onCardTitleChange(e.currentTarget.value);
}
}}
onClick={e => {
e.stopPropagation();
}}
onKeyDown={handleKeyDown}
value={currentCardTitle}
ref={$editorRef}
/>
</EditorContent>
) : (
<CardTitle>
{complete && <CompleteIcon width={16} height={16} />}
{title}
</CardTitle>
)}
<ListCardBadges>
{watched && (
<ListCardBadge>
<FontAwesomeIcon color="#6b778c" icon={faEye} size="xs" />
</ListCardBadge>
)}
{dueDate && (
<DueDateCardBadge isPastDue={dueDate.isPastDue}>
<ClockIcon color={dueDate.isPastDue ? '#fff' : '#6b778c'} icon={faClock} size="xs" />
<ListCardBadgeText>{dueDate.formattedDate}</ListCardBadgeText>
</DueDateCardBadge>
)}
{description && (
<DescriptionBadge>
<FontAwesomeIcon color="#6b778c" icon={faList} size="xs" />
</DescriptionBadge>
)}
{checklists && (
<ListCardBadge>
<FontAwesomeIcon color="#6b778c" icon={faCheckSquare} size="xs" />
<ListCardBadgeText>{`${checklists.complete}/${checklists.total}`}</ListCardBadgeText>
</ListCardBadge>
)}
</ListCardBadges>
<CardMembers>
{members &&
members.map(member => (
<TaskAssignee
key={member.id}
size={28}
member={member}
onMemberProfile={$target => {
if (onCardMemberClick) {
onCardMemberClick($target, taskID, member.id);
}
}}
/>
))}
</CardMembers>
</ListCardDetails>
</ListCardInnerContainer>
</ListCardContainer>
);
},
);
Card.displayName = 'Card';
export default Card;