diff --git a/web/src/App/BaseStyles.ts b/web/src/App/BaseStyles.ts
index 1febcbf..e96a068 100644
--- a/web/src/App/BaseStyles.ts
+++ b/web/src/App/BaseStyles.ts
@@ -70,7 +70,7 @@ export default createGlobalStyle`
outline: none;
}
&:disabled {
- opacity: 1;
+ opacity: 0.5;
}
}
[role="button"], button, input, textarea {
diff --git a/web/src/App/ThemeStyles.ts b/web/src/App/ThemeStyles.ts
new file mode 100644
index 0000000..1d46126
--- /dev/null
+++ b/web/src/App/ThemeStyles.ts
@@ -0,0 +1,47 @@
+import { createGlobalStyle, DefaultTheme } from 'styled-components';
+
+const theme: DefaultTheme = {
+ borderRadius: {
+ primary: '3px',
+ alternate: '6px',
+ },
+ colors: {
+ primary: '115, 103, 240',
+ secondary: '216, 93, 216',
+ alternate: '65, 69, 97',
+ success: '40, 199, 111',
+ danger: '234, 84, 85',
+ warning: '255, 159, 67',
+ dark: '30, 30, 30',
+ text: {
+ primary: '194, 198, 220',
+ secondary: '255, 255, 255',
+ },
+ bg: {
+ primary: '16, 22, 58',
+ secondary: '38, 44, 73',
+ },
+ },
+};
+
+export { theme };
+
+export default createGlobalStyle`
+ :root {
+ --color-text: #c2c6dc;
+ --color-text-hover: #fff;
+ --color-primary: rgba(115, 103, 240);
+ --color-button-text: #c2c6dc;
+ --color-button-text-hover: #fff;
+ --color-button-background: rgba(115, 103, 240);
+
+ --color-background: #262c49;
+ --color-background-dark: #10163a;
+
+ --color-input-text: #c2c6dc;
+ --color-input-text-focus: #fff;
+
+ --color-icon: #c2c6dc;
+ --color-active-icon: rgba(115, 103, 240);
+ }
+`;
diff --git a/web/src/App/index.tsx b/web/src/App/index.tsx
index 557faf8..e34af90 100644
--- a/web/src/App/index.tsx
+++ b/web/src/App/index.tsx
@@ -2,9 +2,10 @@ import React, { useState, useEffect } from 'react';
import jwtDecode from 'jwt-decode';
import { createBrowserHistory } from 'history';
import { setAccessToken } from 'shared/utils/accessToken';
-import styled from 'styled-components';
+import styled, { ThemeProvider } from 'styled-components';
import NormalizeStyles from './NormalizeStyles';
import BaseStyles from './BaseStyles';
+import ThemeStyles, { theme } from './ThemeStyles';
import Routes from './Routes';
import { UserIDContext } from './context';
import Navbar from './Navbar';
@@ -39,20 +40,23 @@ const App = () => {
return (
<>
-
-
-
-
- {loading ? (
- loading
- ) : (
- <>
-
-
- >
- )}
-
-
+
+
+
+
+
+
+ {loading ? (
+ loading
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+
>
);
diff --git a/web/src/Projects/Project/index.tsx b/web/src/Projects/Project/index.tsx
index 368311a..4996054 100644
--- a/web/src/Projects/Project/index.tsx
+++ b/web/src/Projects/Project/index.tsx
@@ -229,14 +229,14 @@ const ProjectAction = styled.div`
display: flex;
align-items: center;
font-size: 15px;
- color: #c2c6dc;
+ color: var(--color-text);
&:not(:last-child) {
margin-right: 16px;
}
&:hover {
- color: ${mixin.lighten('#c2c6dc', 0.25)};
+ color: var(--color-text-hover);
}
`;
@@ -490,15 +490,15 @@ const Project = () => {
);
}}
>
-
+
Labels
-
+
Fields
-
+
Rules
@@ -588,8 +588,8 @@ const Project = () => {
setQuickCardEditor(initialQuickCardEditorState)}
- onEditCard={(_listId: string, cardId: string, cardName: string) => {
- updateTaskName({ variables: { taskID: cardId, name: cardName } });
+ onEditCard={(_taskGroupID: string, taskID: string, cardName: string) => {
+ updateTaskName({ variables: { taskID, name: cardName } });
}}
onOpenMembersPopup={($targetRef, task) => {
showPopup(
diff --git a/web/src/index.tsx b/web/src/index.tsx
index f72da2f..b05a5fb 100644
--- a/web/src/index.tsx
+++ b/web/src/index.tsx
@@ -1,20 +1,22 @@
import React from 'react';
-
import ReactDOM from 'react-dom';
+import axios from 'axios';
+import createAuthRefreshInterceptor from 'axios-auth-refresh';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { ApolloLink, Observable, fromPromise } from 'apollo-link';
-
import { getAccessToken, getNewToken, setAccessToken } from 'shared/utils/accessToken';
-import axios from 'axios';
-import createAuthRefreshInterceptor from 'axios-auth-refresh';
-
import App from './App';
-// Function that will be called to refresh authorization
+// https://able.bio/AnasT/apollo-graphql-async-access-token-refresh--470t1c8
+
+let forward$;
+let isRefreshing = false;
+let pendingRequests: any = [];
+
const refreshAuthLogic = (failedRequest: any) =>
axios.post('http://localhost:3333/auth/refresh_token', {}, { withCredentials: true }).then(tokenRefreshResponse => {
setAccessToken(tokenRefreshResponse.data.accessToken);
@@ -24,12 +26,6 @@ const refreshAuthLogic = (failedRequest: any) =>
createAuthRefreshInterceptor(axios, refreshAuthLogic);
-// https://able.bio/AnasT/apollo-graphql-async-access-token-refresh--470t1c8
-
-let forward$;
-let isRefreshing = false;
-let pendingRequests: any = [];
-
const resolvePendingRequests = () => {
pendingRequests.map((callback: any) => callback());
pendingRequests = [];
diff --git a/web/src/shared/components/AddList/Styles.ts b/web/src/shared/components/AddList/Styles.ts
index 563d258..09b50fc 100644
--- a/web/src/shared/components/AddList/Styles.ts
+++ b/web/src/shared/components/AddList/Styles.ts
@@ -1,6 +1,7 @@
import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea/lib';
import { mixin } from 'shared/utils/styles';
+import Button from 'shared/components/Button';
export const Container = styled.div``;
@@ -90,22 +91,10 @@ export const ListAddControls = styled.div`
margin: 4px 0 0;
`;
-export const AddListButton = styled.button`
- box-shadow: none;
- border: none;
- color: #c2c6dc;
+export const AddListButton = styled(Button)`
float: left;
- margin: 0 4px 0 0;
- cursor: pointer;
- display: inline-block;
- font-weight: 400;
- line-height: 20px;
padding: 6px 12px;
- text-align: center;
border-radius: 3px;
- font-size: 14px;
-
- background: rgb(115, 103, 240);
`;
export const CancelAdd = styled.div`
diff --git a/web/src/shared/components/AddList/index.tsx b/web/src/shared/components/AddList/index.tsx
index c597941..e9d9272 100644
--- a/web/src/shared/components/AddList/index.tsx
+++ b/web/src/shared/components/AddList/index.tsx
@@ -50,6 +50,7 @@ const NameEditor: React.FC = ({ onSave, onCancel }) => {
{
onSave(listName);
setListName('');
diff --git a/web/src/shared/components/Button/Button.stories.tsx b/web/src/shared/components/Button/Button.stories.tsx
new file mode 100644
index 0000000..d7edc81
--- /dev/null
+++ b/web/src/shared/components/Button/Button.stories.tsx
@@ -0,0 +1,138 @@
+import React from 'react';
+import { action } from '@storybook/addon-actions';
+import BaseStyles from 'App/BaseStyles';
+import NormalizeStyles from 'App/NormalizeStyles';
+import { theme } from 'App/ThemeStyles';
+import styled, { ThemeProvider } from 'styled-components';
+import Button from '.';
+
+export default {
+ component: Button,
+ title: 'Button',
+ parameters: {
+ backgrounds: [
+ { name: 'gray', value: '#f8f8f8', default: true },
+ { name: 'white', value: '#ffffff' },
+ ],
+ },
+};
+
+const ButtonRow = styled.div`
+ display: flex;
+ align-items: center;
+ justify-items: center;
+ margin: 25px;
+ width: 100%;
+ & > button {
+ margin-right: 1.5rem;
+ }
+`;
+
+export const Default = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/web/src/shared/components/Button/index.tsx b/web/src/shared/components/Button/index.tsx
new file mode 100644
index 0000000..5e47954
--- /dev/null
+++ b/web/src/shared/components/Button/index.tsx
@@ -0,0 +1,171 @@
+import React from 'react';
+import styled, { css } from 'styled-components/macro';
+
+const Text = styled.span<{ fontSize: string }>`
+ position: relative;
+ display: inline-block;
+ transition: all 0.2s ease;
+ font-size: ${props => props.fontSize};
+ color: rgba(${props => props.theme.colors.text.secondary});
+`;
+
+const Base = styled.button<{ color: string; disabled: boolean }>`
+ transition: all 0.2s ease;
+ position: relative;
+ border: none;
+ cursor: pointer;
+ padding: 0.75rem 2rem;
+ border-radius: ${props => props.theme.borderRadius.alternate};
+
+ ${props =>
+ props.disabled &&
+ css`
+ opacity: 0.5;
+ cursor: default;
+ pointer-events: none;
+ `}
+`;
+
+const Filled = styled(Base)`
+ background: rgba(${props => props.theme.colors[props.color]});
+ &:hover {
+ box-shadow: 0 8px 25px -8px rgba(${props => props.theme.colors[props.color]});
+ }
+`;
+const Outline = styled(Base)`
+ border: 1px solid rgba(${props => props.theme.colors[props.color]});
+ background: transparent;
+ & ${Text} {
+ color: rgba(${props => props.theme.colors[props.color]});
+ }
+
+ &:hover {
+ background: rgba(${props => props.theme.colors[props.color]}, 0.08);
+ }
+`;
+
+const Flat = styled(Base)`
+ background: transparent;
+ &:hover {
+ background: rgba(${props => props.theme.colors[props.color]}, 0.2);
+ }
+`;
+
+const LineX = styled.span<{ color: string }>`
+ transition: all 0.2s ease;
+ position: absolute;
+ height: 2px;
+ width: 0;
+ top: auto;
+ bottom: -2px;
+ left: 50%;
+ transform: translate(-50%);
+ background: rgba(${props => props.theme.colors[props.color]}, 1);
+`;
+
+const LineDown = styled(Base)`
+ background: transparent;
+ border-radius: 0;
+ border-width: 0;
+ border-style: solid;
+ border-bottom-width: 2px;
+ border-color: rgba(${props => props.theme.colors[props.color]}, 0.2);
+
+ &:hover ${LineX} {
+ width: 100%;
+ }
+ &:hover ${Text} {
+ transform: translateY(2px);
+ }
+`;
+
+const Gradient = styled(Base)`
+ background: linear-gradient(
+ 30deg,
+ rgba(${props => props.theme.colors[props.color]}, 1),
+ rgba(${props => props.theme.colors[props.color]}, 0.5)
+ );
+ text-shadow: 1px 2px 4px rgba(0, 0, 0, 0.3);
+ &:hover {
+ transform: translateY(-2px);
+ }
+`;
+
+const Relief = styled(Base)`
+ background: rgba(${props => props.theme.colors[props.color]}, 1);
+ -webkit-box-shadow: 0 -3px 0 0 rgba(0, 0, 0, 0.2) inset;
+ box-shadow: inset 0 -3px 0 0 rgba(0, 0, 0, 0.2);
+
+ &:active {
+ transform: translateY(3px);
+ box-shadow: none !important;
+ }
+`;
+
+type ButtonProps = {
+ fontSize?: string;
+ variant?: 'filled' | 'outline' | 'flat' | 'lineDown' | 'gradient' | 'relief';
+ color?: 'primary' | 'danger' | 'success' | 'warning' | 'dark';
+ disabled?: boolean;
+ className?: string;
+ onClick?: () => void;
+};
+
+const Button: React.FC = ({
+ disabled = false,
+ fontSize = '14px',
+ color = 'primary',
+ variant = 'filled',
+ onClick,
+ className,
+ children,
+}) => {
+ const handleClick = () => {
+ if (onClick) {
+ onClick();
+ }
+ };
+ switch (variant) {
+ case 'filled':
+ return (
+
+ {children}
+
+ );
+ case 'outline':
+ return (
+
+ {children}
+
+ );
+ case 'flat':
+ return (
+
+ {children}
+
+ );
+ case 'lineDown':
+ return (
+
+ {children}
+
+
+ );
+ case 'gradient':
+ return (
+
+ {children}
+
+ );
+ case 'relief':
+ return (
+
+ {children}
+
+ );
+ default:
+ throw new Error('not a valid variant');
+ }
+};
+
+export default Button;
diff --git a/web/src/shared/components/Card/Card.stories.tsx b/web/src/shared/components/Card/Card.stories.tsx
index d499ec0..9bebbc0 100644
--- a/web/src/shared/components/Card/Card.stories.tsx
+++ b/web/src/shared/components/Card/Card.stories.tsx
@@ -107,9 +107,68 @@ export const Everything = () => {
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(null);
+ return (
+
+ );
+};
+
+export const Editable = () => {
+ const $ref = useRef(null);
+ return (
+
+ );
+};
diff --git a/web/src/shared/components/Card/Styles.ts b/web/src/shared/components/Card/Styles.ts
index 14f4cd7..5ca43fc 100644
--- a/web/src/shared/components/Card/Styles.ts
+++ b/web/src/shared/components/Card/Styles.ts
@@ -1,10 +1,34 @@
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 { 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: 16px;
+ line-height: 20px;
+ color: var(--color-input-text-focus);
+ &:focus {
+ border: none;
+ outline: none;
+ }
+`;
+
export const ListCardBadges = styled.div`
float: left;
display: flex;
@@ -17,6 +41,7 @@ export const ListCardBadge = styled.div`
display: flex;
align-items: center;
margin: 0 6px 4px 0;
+ font-size: 12px;
max-width: 100%;
min-height: 20px;
overflow: hidden;
@@ -32,6 +57,7 @@ export const DescriptionBadge = styled(ListCardBadge)`
`;
export const DueDateCardBadge = styled(ListCardBadge)<{ isPastDue: boolean }>`
+ font-size: 12px;
${props =>
props.isPastDue &&
css`
@@ -49,16 +75,16 @@ export const ListCardBadgeText = styled.span`
white-space: nowrap;
`;
-export const ListCardContainer = styled.div<{ isActive: boolean }>`
+export const ListCardContainer = styled.div<{ isActive: boolean; editable: boolean }>`
max-width: 256px;
margin-bottom: 8px;
- background-color: #fff;
border-radius: 3px;
${mixin.boxShadowCard}
cursor: pointer !important;
position: relative;
- background-color: ${props => (props.isActive ? mixin.darken('#262c49', 0.1) : mixin.lighten('#262c49', 0.05))};
+ background-color: ${props =>
+ props.isActive && !props.editable ? mixin.darken('#262c49', 0.1) : 'var(--color-background)'};
`;
export const ListCardInnerContainer = styled.div`
@@ -113,18 +139,16 @@ export const ListCardOperation = styled.span`
`;
export const CardTitle = styled.span`
- font-family: 'Droid Sans';
clear: both;
display: block;
margin: 0 0 4px;
overflow: hidden;
text-decoration: none;
word-wrap: break-word;
- color: #c2c6dc;
+ color: var(--color-text);
`;
export const CardMembers = styled.div`
float: right;
- margin: 0 -2px 0 0;
+ margin: 0 -2px 4px 0;
`;
-
diff --git a/web/src/shared/components/Card/index.tsx b/web/src/shared/components/Card/index.tsx
index d8c6c22..88a3ddc 100644
--- a/web/src/shared/components/Card/index.tsx
+++ b/web/src/shared/components/Card/index.tsx
@@ -1,11 +1,10 @@
-import React, { useState, useRef } from 'react';
-import { DraggableProvidedDraggableProps } from 'react-beautiful-dnd';
-import PropTypes from 'prop-types';
+import React, { useState, useRef, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import Member from 'shared/components/Member';
+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,
DescriptionBadge,
DueDateCardBadge,
ListCardBadges,
@@ -21,7 +20,6 @@ import {
CardTitle,
CardMembers,
} from './Styles';
-import TaskAssignee from 'shared/components/TaskAssignee';
type DueDate = {
isPastDue: boolean;
@@ -35,11 +33,11 @@ type Checklist = {
type Props = {
title: string;
- description: string;
taskID: string;
taskGroupID: string;
- onContextMenu: (e: ContextMenuEvent) => void;
- onClick: (e: React.MouseEvent) => void;
+ onContextMenu?: (e: ContextMenuEvent) => void;
+ onClick?: (e: React.MouseEvent) => void;
+ description?: null | string;
dueDate?: DueDate;
checklists?: Checklist;
labels?: Array;
@@ -47,6 +45,9 @@ type Props = {
wrapperProps?: any;
members?: Array | null;
onCardMemberClick?: OnCardMemberClick;
+ editable?: boolean;
+ onEditCard?: (taskGroupID: string, taskID: string, cardName: string) => void;
+ onCardTitleChange?: (name: string) => void;
};
const Card = React.forwardRef(
@@ -65,20 +66,47 @@ const Card = React.forwardRef(
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 (typeof $innerCardRef.current !== 'undefined') {
const pos = $innerCardRef.current.getBoundingClientRect();
- onContextMenu({
- top: pos.top,
- left: pos.left,
- taskGroupID,
- taskID,
- });
+ if (onContextMenu) {
+ onContextMenu({
+ top: pos.top,
+ left: pos.left,
+ taskGroupID,
+ taskID,
+ });
+ }
}
};
const onTaskContext = (e: React.MouseEvent) => {
@@ -96,9 +124,14 @@ const Card = React.forwardRef(
onMouseEnter={() => setActive(true)}
onMouseLeave={() => setActive(false)}
ref={$cardRef}
- onClick={onClick}
+ onClick={e => {
+ if (onClick) {
+ onClick(e);
+ }
+ }}
onContextMenu={onTaskContext}
isActive={isActive}
+ editable={editable}
{...wrapperProps}
>
@@ -116,7 +149,24 @@ const Card = React.forwardRef(
))}
- {title}
+ {editable ? (
+ {
+ setCardTitle(e.currentTarget.value);
+ if (onCardTitleChange) {
+ onCardTitleChange(e.currentTarget.value);
+ }
+ }}
+ onClick={e => {
+ e.stopPropagation();
+ }}
+ onKeyDown={handleKeyDown}
+ value={currentCardTitle}
+ ref={$editorRef}
+ />
+ ) : (
+ {title}
+ )}
{watched && (
diff --git a/web/src/shared/components/CardComposer/Styles.ts b/web/src/shared/components/CardComposer/Styles.ts
index 56745b8..4ffd6da 100644
--- a/web/src/shared/components/CardComposer/Styles.ts
+++ b/web/src/shared/components/CardComposer/Styles.ts
@@ -1,4 +1,5 @@
import styled from 'styled-components';
+import Button from 'shared/components/Button';
import TextareaAutosize from 'react-autosize-textarea';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { mixin } from 'shared/utils/styles';
@@ -15,53 +16,6 @@ export const CardComposerWrapper = styled.div<{ isOpen: boolean }>`
flex-direction: column;
`;
-export const ListCard = styled.div`
- background-color: ${props => mixin.lighten('#262c49', 0.05)};
- border-radius: 3px;
- ${mixin.boxShadowCard}
- cursor: pointer;
- display: block;
- margin-bottom: 8px;
- max-width: 300px;
- min-height: 20px;
- position: relative;
- text-decoration: none;
- z-index: 0;
-`;
-
-export const ListCardDetails = styled.div`
- overflow: hidden;
- padding: 6px 8px 2px;
- position: relative;
- z-index: 10;
-`;
-
-export const ListCardLabels = styled.div``;
-
-export const ListCardEditor = styled(TextareaAutosize)`
- font-family: 'Droid Sans';
- 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: 20px;
-
- color: #c2c6dc;
- l &:focus {
- background-color: ${props => mixin.lighten('#262c49', 0.05)};
- }
-`;
-
export const ComposerControls = styled.div``;
export const ComposerControlsSaveSection = styled.div`
@@ -73,18 +27,9 @@ export const ComposerControlsSaveSection = styled.div`
export const ComposerControlsActionsSection = styled.div`
float: right;
`;
-
-export const AddCardButton = styled.button`
- background: rgb(115, 103, 240);
- box-shadow: none;
- border: none;
- color: #c2c6dc;
- cursor: pointer;
- display: inline-block;
- font-weight: 400;
- line-height: 20px;
+export const AddCardButton = styled(Button)`
margin-right: 4px;
padding: 6px 12px;
- text-align: center;
border-radius: 3px;
`;
+
diff --git a/web/src/shared/components/CardComposer/index.tsx b/web/src/shared/components/CardComposer/index.tsx
index faffeef..5303e2f 100644
--- a/web/src/shared/components/CardComposer/index.tsx
+++ b/web/src/shared/components/CardComposer/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types';
import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
@@ -8,13 +8,11 @@ import {
CardComposerWrapper,
CancelIcon,
AddCardButton,
- ListCard,
- ListCardDetails,
- ListCardEditor,
ComposerControls,
ComposerControlsSaveSection,
ComposerControlsActionsSection,
} from './Styles';
+import Card from '../Card';
type Props = {
isOpen: boolean;
@@ -24,43 +22,36 @@ type Props = {
const CardComposer = ({ isOpen, onCreateCard, onClose }: Props) => {
const [cardName, setCardName] = useState('');
- const $cardEditor: any = useRef(null);
- const handleCreateCard = () => {
- onCreateCard(cardName);
- setCardName('');
- if ($cardEditor && $cardEditor.current) {
- $cardEditor.current.focus();
- }
- };
- const onKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === 'Enter') {
- e.preventDefault();
- handleCreateCard();
- }
- };
+ const $cardRef = useRef(null);
+ useOnOutsideClick($cardRef, true, onClose, null);
useOnEscapeKeyDown(isOpen, onClose);
- useOnOutsideClick($cardEditor, true, () => onClose(), null);
- useEffect(() => {
- $cardEditor.current.focus();
- }, []);
return (
-
-
- {
- setCardName(e.currentTarget.value);
- }}
- value={cardName}
- placeholder="Enter a title for this card..."
- />
-
-
+ {
+ onCreateCard(name);
+ setCardName('');
+ }}
+ onCardTitleChange={name => {
+ setCardName(name);
+ }}
+ />
- Add Card
+ {
+ onCreateCard(cardName);
+ setCardName('');
+ }}
+ >
+ Add Card
+
diff --git a/web/src/shared/components/Input/Input.stories.tsx b/web/src/shared/components/Input/Input.stories.tsx
new file mode 100644
index 0000000..32cd832
--- /dev/null
+++ b/web/src/shared/components/Input/Input.stories.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import BaseStyles from 'App/BaseStyles';
+import NormalizeStyles from 'App/NormalizeStyles';
+import { theme } from 'App/ThemeStyles';
+import styled, { ThemeProvider } from 'styled-components';
+
+import Input from '.';
+import { User } from 'shared/icons';
+
+export default {
+ component: Input,
+ title: 'Input',
+ parameters: {
+ backgrounds: [
+ { name: 'white', value: '#ffffff', default: true },
+ { name: 'gray', value: '#f8f8f8' },
+ ],
+ },
+};
+
+const Wrapper = styled.div`
+ background: rgba(${props => props.theme.colors.bg.primary});
+ padding: 45px;
+ margin: 25px;
+ display: flex;
+ flex-direction: column;
+`;
+
+export const Default = () => {
+ return (
+ <>
+
+
+
+
+
+
+ } width="100%" placeholder="Placeholder" />
+
+
+ >
+ );
+};
diff --git a/web/src/shared/components/Input/index.tsx b/web/src/shared/components/Input/index.tsx
new file mode 100644
index 0000000..d596752
--- /dev/null
+++ b/web/src/shared/components/Input/index.tsx
@@ -0,0 +1,91 @@
+import React from 'react';
+import styled from 'styled-components/macro';
+
+const InputWrapper = styled.div<{ width: string }>`
+ position: relative;
+ width: ${props => props.width};
+ display: flex;
+ align-items: flex-start;
+ flex-direction: column;
+ position: relative;
+ justify-content: center;
+
+ margin-bottom: 2.2rem;
+ margin-top: 17px;
+`;
+
+const InputLabel = styled.span<{ width: string }>`
+ width: ${props => props.width};
+ padding: 0.7rem !important;
+ color: #c2c6dc;
+ left: 0;
+ top: 0;
+ transition: all 0.2s ease;
+ position: absolute;
+ border-radius: 5px;
+ overflow: hidden;
+ font-size: 0.85rem;
+ cursor: text;
+ font-size: 12px;
+ user-select: none;
+ pointer-events: none;
+}
+`;
+
+const InputInput = styled.input<{ hasIcon: boolean; width: string; focusBg: string; borderColor: string }>`
+ width: ${props => props.width};
+ font-size: 14px;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-color: ${props => props.borderColor};
+ background: #262c49;
+ box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.15);
+ ${props => (props.hasIcon ? 'padding: 0.7rem 1rem 0.7rem 3rem;' : 'padding: 0.7rem;')}
+ line-height: 16px;
+ color: #c2c6dc;
+ position: relative;
+ border-radius: 5px;
+ transition: all 0.3s ease;
+ &:focus {
+ box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15);
+ border: 1px solid rgba(115, 103, 240);
+ background: ${props => props.focusBg};
+ }
+ &:focus ~ ${InputLabel} {
+ color: rgba(115, 103, 240);
+ transform: translate(-3px, -90%);
+ }
+`;
+
+const Icon = styled.div`
+ display: flex;
+ left: 16px;
+ position: absolute;
+`;
+
+type InputProps = {
+ variant?: 'normal' | 'alternate';
+ label?: string;
+ width?: string;
+ placeholder?: string;
+ icon?: JSX.Element;
+};
+
+const Input: React.FC = ({ width = 'auto', variant = 'normal', label, placeholder, icon }) => {
+ const borderColor = variant === 'normal' ? 'rgba(0, 0, 0, 0.2)' : '#414561';
+ const focusBg = variant === 'normal' ? 'rgba(38, 44, 73, )' : 'rgba(16, 22, 58, 1)';
+ return (
+
+
+ {label && {label}}
+ {icon && icon}
+
+ );
+};
+
+export default Input;
diff --git a/web/src/shared/components/Lists/Lists.stories.tsx b/web/src/shared/components/Lists/Lists.stories.tsx
index 7878d21..bdcdf47 100644
--- a/web/src/shared/components/Lists/Lists.stories.tsx
+++ b/web/src/shared/components/Lists/Lists.stories.tsx
@@ -65,7 +65,6 @@ const initialListsData = {
export const Default = () => {
const [listsData, setListsData] = useState(initialListsData);
const onCardDrop = (droppedTask: Task) => {
- console.log(droppedTask);
const newState = {
...listsData,
tasks: {
@@ -85,84 +84,6 @@ export const Default = () => {
};
setListsData(newState);
};
- return ;
-};
-
-const createColumn = (id: any, name: any, position: any) => {
- return {
- taskGroupID: id,
- name,
- position,
- tasks: [],
- };
-};
-
-const initialListsDataLarge = {
- columns: {
- 'column-1': createColumn('column-1', 'General', 1),
- 'column-2': createColumn('column-2', 'General', 2),
- 'column-3': createColumn('column-3', 'General', 3),
- 'column-4': createColumn('column-4', 'General', 4),
- 'column-5': createColumn('column-5', 'General', 5),
- 'column-6': createColumn('column-6', 'General', 6),
- 'column-7': createColumn('column-7', 'General', 7),
- 'column-8': createColumn('column-8', 'General', 8),
- 'column-9': createColumn('column-9', 'General', 9),
- },
- tasks: {
- 'task-1': {
- taskID: 'task-1',
- taskGroup: { taskGroupID: 'column-1' },
- name: 'Create roadmap',
- position: 2,
- labels: [],
- },
- 'task-2': {
- taskID: 'task-2',
- taskGroup: { taskGroupID: 'column-1' },
- position: 1,
- name: 'Create authentication',
- labels: [],
- },
- 'task-3': {
- taskID: 'task-3',
- taskGroup: { taskGroupID: 'column-1' },
- position: 3,
- name: 'Create login',
- labels: [],
- },
- 'task-4': {
- taskID: 'task-4',
- taskGroup: { taskGroupID: 'column-1' },
- position: 4,
- name: 'Create plugins',
- labels: [],
- },
- },
-};
-
-export const ListsWithManyList = () => {
- const [listsData, setListsData] = useState(initialListsDataLarge);
- const onCardDrop = (droppedTask: any) => {
- const newState = {
- ...listsData,
- tasks: {
- ...listsData.tasks,
- [droppedTask.id]: droppedTask,
- },
- };
- setListsData(newState);
- };
- const onListDrop = (droppedColumn: any) => {
- const newState = {
- ...listsData,
- columns: {
- ...listsData.columns,
- [droppedColumn.id]: droppedColumn,
- },
- };
- setListsData(newState);
- };
return (
= [
export const Default = () => {
const $cardRef: any = createRef();
+ const task: Task = {
+ id: 'task',
+ name: 'Hello, world!',
+ position: 1,
+ labels: labelData,
+ taskGroup: {
+ id: '1',
+ },
+ };
const [isEditorOpen, setEditorOpen] = useState(false);
const [top, setTop] = useState(0);
const [left, setLeft] = useState(0);
@@ -44,15 +53,7 @@ export const Default = () => {
<>
{isEditorOpen && (
setEditorOpen(false)}
onEditCard={action('edit card')}
onOpenLabelsPopup={action('open popup')}
@@ -76,7 +77,7 @@ export const Default = () => {
taskGroupID="1"
description="hello!"
ref={$cardRef}
- title="Hello, world"
+ title={task.name}
onClick={action('on click')}
onContextMenu={e => {
setTop(e.top);
diff --git a/web/src/shared/components/QuickCardEditor/Styles.ts b/web/src/shared/components/QuickCardEditor/Styles.ts
index d0422df..22a7c62 100644
--- a/web/src/shared/components/QuickCardEditor/Styles.ts
+++ b/web/src/shared/components/QuickCardEditor/Styles.ts
@@ -21,53 +21,6 @@ export const Container = styled.div<{ top: number; left: number }>`
left: ${props => props.left}px;
`;
-export const Editor = styled.div`
- background-color: ${props => mixin.lighten('#262c49', 0.05)};
- border-radius: 3px;
- box-shadow: 0 1px 0 rgba(9, 30, 66, 0.25);
- color: #c2c6dc;
- padding: 6px 8px 2px;
- cursor: default;
- display: block;
- margin-bottom: 8px;
- max-width: 300px;
- min-height: 20px;
- position: relative;
- text-decoration: none;
- z-index: 1;
-`;
-
-export const EditorDetails = styled.div`
- overflow: hidden;
- padding: 0;
- position: relative;
- z-index: 10;
-`;
-
-export const EditorTextarea = styled(TextareaAutosize)`
- font-family: 'Droid Sans';
- 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: 16px;
- line-height: 20px;
- color: #fff;
- &:focus {
- border: none;
- outline: none;
- }
-`;
-
export const SaveButton = styled.button`
cursor: pointer;
background: rgb(115, 103, 240);
@@ -125,25 +78,3 @@ export const CloseButton = styled.div`
z-index: 40;
cursor: pointer;
`;
-
-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};
-`;
diff --git a/web/src/shared/components/QuickCardEditor/index.tsx b/web/src/shared/components/QuickCardEditor/index.tsx
index f1a2e17..529e021 100644
--- a/web/src/shared/components/QuickCardEditor/index.tsx
+++ b/web/src/shared/components/QuickCardEditor/index.tsx
@@ -1,20 +1,8 @@
-import React, { useRef, useState, useEffect } from 'react';
+import React, { useRef, useState } from 'react';
import Cross from 'shared/icons/Cross';
import styled from 'styled-components';
-import Member from 'shared/components/Member';
-import {
- Wrapper,
- Container,
- Editor,
- EditorDetails,
- EditorTextarea,
- EditorButtons,
- SaveButton,
- EditorButton,
- CloseButton,
- ListCardLabels,
- ListCardLabel,
-} from './Styles';
+import { Wrapper, Container, EditorButtons, SaveButton, EditorButton, CloseButton } from './Styles';
+import Card from '../Card';
export const CardMembers = styled.div`
position: absolute;
@@ -46,61 +34,34 @@ const QuickCardEditor = ({
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();
- }, []);
const handleCloseEditor = (e: any) => {
e.stopPropagation();
onCloseEditor();
};
- const handleKeyDown = (e: any) => {
- if (e.key === 'Enter') {
- e.preventDefault();
- onEditCard(task.taskGroup.id, task.id, currentCardTitle);
- onCloseEditor();
- }
- };
-
return (
-
-
- {task.labels &&
- task.labels.map(label => (
-
- {label.projectLabel.name}
-
- ))}
-
-
- setCardTitle(e.currentTarget.value)}
- onClick={e => {
- e.stopPropagation();
- }}
- onKeyDown={handleKeyDown}
- value={currentCardTitle}
- ref={$editorRef}
- />
-
- {task.assigned &&
- task.assigned.map(member => (
-
- ))}
-
-
-
- onEditCard(task.taskGroup.id, task.id, currentCardTitle)}>Save
+ {
+ onEditCard(taskGroupID, taskID, name);
+ onCloseEditor();
+ }}
+ members={task.assigned}
+ taskID={task.id}
+ taskGroupID={task.taskGroup.id}
+ labels={task.labels.map(l => l.projectLabel)}
+ />
+ onEditCard(task.taskGroup.id, task.id, currentCardTitle)}>Save
= ({ label }) => {
- return (
-
-
- {label}
-
- );
-};
+import Input from 'shared/components/Input';
+import Button from 'shared/components/Button';
const ProfileContainer = styled.div`
display: flex;
@@ -103,30 +44,13 @@ const ActionButtons = styled.div`
flex-wrap: wrap;
align-items: center;
`;
-const UploadButton = styled.div`
+const UploadButton = styled(Button)`
margin-right: 1rem;
- padding: 0.75rem 2rem;
- border: 0;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- color: #fff;
display: inline-block;
- background: rgba(115, 103, 240);
`;
-const RemoveButton = styled.button`
+const RemoveButton = styled(Button)`
display: inline-block;
- border: 1px solid rgba(234, 84, 85, 1);
- background: transparent;
- color: rgba(234, 84, 85, 1);
- padding: 0.75rem 2rem;
-
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
`;
const ImgLabel = styled.p`
@@ -164,7 +88,9 @@ const AvatarSettings: React.FC = ({ profile, onProfileAvata
onProfileAvatarChange()}>Upload photo
- onProfileAvatarRemove()}>Remove
+ onProfileAvatarRemove()}>
+ Remove
+
Allowed JPG, GIF or PNG. Max size of 800kB
@@ -241,6 +167,7 @@ const TabNavLine = styled.span<{ top: number }>`
`;
const TabContentWrapper = styled.div`
+ margin-left: 1rem;
position: relative;
display: block;
overflow: hidden;
@@ -254,7 +181,6 @@ const TabContent = styled.div`
padding: 0;
padding: 1.5rem;
background-color: #10163a;
- margin-left: 1rem !important;
border-radius: 0.5rem;
`;
@@ -294,17 +220,9 @@ const SettingActions = styled.div`
justify-content: flex-end;
`;
-const SaveButton = styled.div`
+const SaveButton = styled(Button)`
margin-right: 1rem;
- padding: 0.75rem 2rem;
- border: 0;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- color: #fff;
display: inline-block;
- background: rgba(115, 103, 240);
`;
type SettingsProps = {
@@ -345,11 +263,11 @@ const Settings: React.FC = ({ onProfileAvatarRemove, onProfileAva
onProfileAvatarChange={onProfileAvatarChange}
profile={profile}
/>
-
-
-
-
-
+
+
+
+
+
Save Change
diff --git a/web/src/shared/components/TaskAssignee/index.tsx b/web/src/shared/components/TaskAssignee/index.tsx
index 7a37329..2c82d77 100644
--- a/web/src/shared/components/TaskAssignee/index.tsx
+++ b/web/src/shared/components/TaskAssignee/index.tsx
@@ -16,10 +16,11 @@ export const Wrapper = styled.div<{ size: number | string; bgColor: string | nul
align-items: center;
justify-content: center;
color: #fff;
- font-weight: 700;
background: ${props => (props.backgroundURL ? `url(${props.backgroundURL})` : props.bgColor)};
background-position: center;
background-size: contain;
+ font-size: 14px;
+ font-weight: 400;
`;
type TaskAssigneeProps = {
diff --git a/web/src/shared/components/TaskDetails/Styles.ts b/web/src/shared/components/TaskDetails/Styles.ts
index 75ca0f3..bb3ffe5 100644
--- a/web/src/shared/components/TaskDetails/Styles.ts
+++ b/web/src/shared/components/TaskDetails/Styles.ts
@@ -1,6 +1,7 @@
import styled from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea/lib';
import { mixin } from 'shared/utils/styles';
+import Button from 'shared/components/Button';
export const TaskHeader = styled.div`
padding: 21px 30px 0px;
@@ -159,23 +160,13 @@ export const TaskDetailsMarkdown = styled.div`
export const TaskDetailsControls = styled.div`
clear: both;
margin-top: 8px;
+ display: flex;
`;
-export const ConfirmSave = styled.div`
- background-color: #5aac44;
- box-shadow: none;
- border: none;
- color: #fff;
- float: left;
- margin: 0 4px 0 0;
- cursor: pointer;
- display: inline-block;
- font-weight: 400;
- line-height: 20px;
+export const ConfirmSave = styled(Button)`
padding: 6px 12px;
- text-align: center;
border-radius: 3px;
- font-size: 14px;
+ margin-right: 6px;
`;
export const CancelEdit = styled.div`
diff --git a/web/src/shared/components/TaskDetails/index.tsx b/web/src/shared/components/TaskDetails/index.tsx
index 41d7dfa..250d752 100644
--- a/web/src/shared/components/TaskDetails/index.tsx
+++ b/web/src/shared/components/TaskDetails/index.tsx
@@ -88,7 +88,9 @@ const DetailsEditor: React.FC = ({
onChange={(e: React.ChangeEvent) => setDescription(e.currentTarget.value)}
/>
- Save
+
+ Save
+
diff --git a/web/src/shared/components/Textarea/index.tsx b/web/src/shared/components/Textarea/index.tsx
new file mode 100644
index 0000000..eb5f7ce
--- /dev/null
+++ b/web/src/shared/components/Textarea/index.tsx
@@ -0,0 +1,31 @@
+import styled from 'styled-components/macro';
+import TextareaAutosize from 'react-autosize-textarea';
+
+const Textarea = styled(TextareaAutosize)`
+ border: none;
+ resize: none;
+ overflow: hidden;
+ overflow-wrap: break-word;
+ background: transparent;
+ border-radius: 3px;
+ box-shadow: none;
+ margin: -4px 0;
+
+ letter-spacing: normal;
+ word-spacing: normal;
+ text-transform: none;
+ text-indent: 0px;
+ text-shadow: none;
+ flex-direction: column;
+ text-align: start;
+
+ color: #c2c6dc;
+ font-weight: 600;
+ font-size: 20px;
+ padding: 3px 10px 3px 8px;
+ &:focus {
+ box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
+ }
+`;
+
+export default Textarea;
diff --git a/web/src/shared/components/TopNavbar/Styles.ts b/web/src/shared/components/TopNavbar/Styles.ts
index bb9af72..8f4a999 100644
--- a/web/src/shared/components/TopNavbar/Styles.ts
+++ b/web/src/shared/components/TopNavbar/Styles.ts
@@ -1,6 +1,7 @@
import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea';
import { mixin } from 'shared/utils/styles';
+import Button from 'shared/components/Button';
export const NavbarWrapper = styled.div`
width: 100%;
@@ -103,7 +104,7 @@ export const ProjectTabs = styled.div`
export const ProjectTab = styled.span<{ active?: boolean }>`
font-size: 80%;
- color: #c2c6dc;
+ color: rgba(${props => props.theme.colors.text.primary});
font-size: 15px;
cursor: pointer;
display: flex;
@@ -122,27 +123,25 @@ export const ProjectTab = styled.span<{ active?: boolean }>`
${props =>
props.active
? css`
- box-shadow: inset 0 -2px #d85dd8;
- color: #d85dd8;
+ box-shadow: inset 0 -2px rgba(${props.theme.colors.secondary});
+ color: rgba(${props.theme.colors.secondary});
`
: css`
&:hover {
- box-shadow: inset 0 -2px #cbd4db;
- color: ${mixin.lighten('#c2c6dc', 0.25)};
+ box-shadow: inset 0 -2px rgba(${props.theme.colors.text.secondary});
+ color: rgba(${props.theme.colors.text.secondary});
}
`}
`;
export const ProjectName = styled.h1`
- color: #c2c6dc;
+ color: rgba(${props => props.theme.colors.text.primary});
font-weight: 600;
font-size: 20px;
padding: 3px 10px 3px 8px;
- font-family: 'Droid Sans';
margin: -4px 0;
`;
export const ProjectNameTextarea = styled(TextareaAutosize)`
- font-family: 'Droid Sans';
border: none;
resize: none;
overflow: hidden;
@@ -211,28 +210,7 @@ export const ProjectSettingsButton = styled.button`
}
`;
-export const InviteButton = 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;
-
+export const InviteButton = styled(Button)`
margin: 0 0 0 8px;
-
- border-radius: 3px;
- border-width: 1px;
- border-style: solid;
- border-color: transparent;
- border-image: initial;
- border-color: #414561;
-
- &:hover {
- background: rgb(115, 103, 240);
- }
+ padding: 6px 12px;
`;
diff --git a/web/src/shared/components/TopNavbar/index.tsx b/web/src/shared/components/TopNavbar/index.tsx
index bdc0531..7390be3 100644
--- a/web/src/shared/components/TopNavbar/index.tsx
+++ b/web/src/shared/components/TopNavbar/index.tsx
@@ -176,7 +176,7 @@ const NavBar: React.FC = ({
{projectMembers.map(member => (
))}
- Invite
+ Invite
)}
diff --git a/web/src/styled.d.ts b/web/src/styled.d.ts
new file mode 100644
index 0000000..6012716
--- /dev/null
+++ b/web/src/styled.d.ts
@@ -0,0 +1,30 @@
+// import original module declarations
+import 'styled-components';
+
+// and extend them!
+declare module 'styled-components' {
+ export interface DefaultTheme {
+ borderRadius: {
+ primary: string;
+ alternate: string;
+ };
+ colors: {
+ [key: string]: any;
+ primary: string;
+ secondary: string;
+ success: string;
+ danger: string;
+ warning: string;
+ dark: string;
+ alternate: string;
+ text: {
+ primary: string;
+ secondary: string;
+ };
+ bg: {
+ primary: string;
+ secondary: string;
+ };
+ };
+ }
+}