chore: add theme colors, merge Card components, create single Button component

This commit is contained in:
Jordan Knott 2020-06-14 19:50:35 -05:00
parent 6267a37b6e
commit a12e9c1e50
30 changed files with 840 additions and 525 deletions

View File

@ -70,7 +70,7 @@ export default createGlobalStyle`
outline: none; outline: none;
} }
&:disabled { &:disabled {
opacity: 1; opacity: 0.5;
} }
} }
[role="button"], button, input, textarea { [role="button"], button, input, textarea {

View File

@ -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);
}
`;

View File

@ -2,9 +2,10 @@ import React, { useState, useEffect } from 'react';
import jwtDecode from 'jwt-decode'; import jwtDecode from 'jwt-decode';
import { createBrowserHistory } from 'history'; import { createBrowserHistory } from 'history';
import { setAccessToken } from 'shared/utils/accessToken'; import { setAccessToken } from 'shared/utils/accessToken';
import styled from 'styled-components'; import styled, { ThemeProvider } from 'styled-components';
import NormalizeStyles from './NormalizeStyles'; import NormalizeStyles from './NormalizeStyles';
import BaseStyles from './BaseStyles'; import BaseStyles from './BaseStyles';
import ThemeStyles, { theme } from './ThemeStyles';
import Routes from './Routes'; import Routes from './Routes';
import { UserIDContext } from './context'; import { UserIDContext } from './context';
import Navbar from './Navbar'; import Navbar from './Navbar';
@ -39,20 +40,23 @@ const App = () => {
return ( return (
<> <>
<UserIDContext.Provider value={{ userID, setUserID }}> <UserIDContext.Provider value={{ userID, setUserID }}>
<PopupProvider> <ThemeProvider theme={theme}>
<NormalizeStyles /> <PopupProvider>
<BaseStyles /> <NormalizeStyles />
<Router history={history}> <BaseStyles />
{loading ? ( <ThemeStyles />
<div>loading</div> <Router history={history}>
) : ( {loading ? (
<> <div>loading</div>
<Navbar /> ) : (
<Routes history={history} /> <>
</> <Navbar />
)} <Routes history={history} />
</Router> </>
</PopupProvider> )}
</Router>
</PopupProvider>
</ThemeProvider>
</UserIDContext.Provider> </UserIDContext.Provider>
</> </>
); );

View File

@ -229,14 +229,14 @@ const ProjectAction = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 15px; font-size: 15px;
color: #c2c6dc; color: var(--color-text);
&:not(:last-child) { &:not(:last-child) {
margin-right: 16px; margin-right: 16px;
} }
&:hover { &:hover {
color: ${mixin.lighten('#c2c6dc', 0.25)}; color: var(--color-text-hover);
} }
`; `;
@ -490,15 +490,15 @@ const Project = () => {
); );
}} }}
> >
<Tags size={13} color="#c2c6dc" /> <Tags size={13} color="var(--color-icon)" />
<ProjectActionText>Labels</ProjectActionText> <ProjectActionText>Labels</ProjectActionText>
</ProjectAction> </ProjectAction>
<ProjectAction> <ProjectAction>
<ToggleOn size={13} color="#c2c6dc" /> <ToggleOn size={13} color="var(--color-icon)" />
<ProjectActionText>Fields</ProjectActionText> <ProjectActionText>Fields</ProjectActionText>
</ProjectAction> </ProjectAction>
<ProjectAction> <ProjectAction>
<Bolt size={13} color="#c2c6dc" /> <Bolt size={13} color="var(--color-icon)" />
<ProjectActionText>Rules</ProjectActionText> <ProjectActionText>Rules</ProjectActionText>
</ProjectAction> </ProjectAction>
</ProjectActions> </ProjectActions>
@ -588,8 +588,8 @@ const Project = () => {
<QuickCardEditor <QuickCardEditor
task={currentQuickTask} task={currentQuickTask}
onCloseEditor={() => setQuickCardEditor(initialQuickCardEditorState)} onCloseEditor={() => setQuickCardEditor(initialQuickCardEditorState)}
onEditCard={(_listId: string, cardId: string, cardName: string) => { onEditCard={(_taskGroupID: string, taskID: string, cardName: string) => {
updateTaskName({ variables: { taskID: cardId, name: cardName } }); updateTaskName({ variables: { taskID, name: cardName } });
}} }}
onOpenMembersPopup={($targetRef, task) => { onOpenMembersPopup={($targetRef, task) => {
showPopup( showPopup(

View File

@ -1,20 +1,22 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import axios from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
import { ApolloProvider } from '@apollo/react-hooks'; import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client'; import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory'; import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http'; import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error'; import { onError } from 'apollo-link-error';
import { ApolloLink, Observable, fromPromise } from 'apollo-link'; import { ApolloLink, Observable, fromPromise } from 'apollo-link';
import { getAccessToken, getNewToken, setAccessToken } from 'shared/utils/accessToken'; import { getAccessToken, getNewToken, setAccessToken } from 'shared/utils/accessToken';
import axios from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
import App from './App'; 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) => const refreshAuthLogic = (failedRequest: any) =>
axios.post('http://localhost:3333/auth/refresh_token', {}, { withCredentials: true }).then(tokenRefreshResponse => { axios.post('http://localhost:3333/auth/refresh_token', {}, { withCredentials: true }).then(tokenRefreshResponse => {
setAccessToken(tokenRefreshResponse.data.accessToken); setAccessToken(tokenRefreshResponse.data.accessToken);
@ -24,12 +26,6 @@ const refreshAuthLogic = (failedRequest: any) =>
createAuthRefreshInterceptor(axios, refreshAuthLogic); createAuthRefreshInterceptor(axios, refreshAuthLogic);
// https://able.bio/AnasT/apollo-graphql-async-access-token-refresh--470t1c8
let forward$;
let isRefreshing = false;
let pendingRequests: any = [];
const resolvePendingRequests = () => { const resolvePendingRequests = () => {
pendingRequests.map((callback: any) => callback()); pendingRequests.map((callback: any) => callback());
pendingRequests = []; pendingRequests = [];

View File

@ -1,6 +1,7 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea/lib'; import TextareaAutosize from 'react-autosize-textarea/lib';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
import Button from 'shared/components/Button';
export const Container = styled.div``; export const Container = styled.div``;
@ -90,22 +91,10 @@ export const ListAddControls = styled.div`
margin: 4px 0 0; margin: 4px 0 0;
`; `;
export const AddListButton = styled.button` export const AddListButton = styled(Button)`
box-shadow: none;
border: none;
color: #c2c6dc;
float: left; float: left;
margin: 0 4px 0 0;
cursor: pointer;
display: inline-block;
font-weight: 400;
line-height: 20px;
padding: 6px 12px; padding: 6px 12px;
text-align: center;
border-radius: 3px; border-radius: 3px;
font-size: 14px;
background: rgb(115, 103, 240);
`; `;
export const CancelAdd = styled.div` export const CancelAdd = styled.div`

View File

@ -50,6 +50,7 @@ const NameEditor: React.FC<NameEditorProps> = ({ onSave, onCancel }) => {
</ListNameEditorWrapper> </ListNameEditorWrapper>
<ListAddControls> <ListAddControls>
<AddListButton <AddListButton
variant="relief"
onClick={() => { onClick={() => {
onSave(listName); onSave(listName);
setListName(''); setListName('');

View File

@ -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 (
<>
<BaseStyles />
<NormalizeStyles />
<ThemeProvider theme={theme}>
<ButtonRow>
<Button>Primary</Button>
<Button color="success">Success</Button>
<Button color="danger">Danger</Button>
<Button color="warning">Warning</Button>
<Button color="dark">Dark</Button>
<Button disabled>Disabled</Button>
</ButtonRow>
<ButtonRow>
<Button variant="outline">Primary</Button>
<Button variant="outline" color="success">
Success
</Button>
<Button variant="outline" color="danger">
Danger
</Button>
<Button variant="outline" color="warning">
Warning
</Button>
<Button variant="outline" color="dark">
Dark
</Button>
<Button variant="outline" disabled>
Disabled
</Button>
</ButtonRow>
<ButtonRow>
<Button variant="flat">Primary</Button>
<Button variant="flat" color="success">
Success
</Button>
<Button variant="flat" color="danger">
Danger
</Button>
<Button variant="flat" color="warning">
Warning
</Button>
<Button variant="flat" color="dark">
Dark
</Button>
<Button variant="flat" disabled>
Disabled
</Button>
</ButtonRow>
<ButtonRow>
<Button variant="lineDown">Primary</Button>
<Button variant="lineDown" color="success">
Success
</Button>
<Button variant="lineDown" color="danger">
Danger
</Button>
<Button variant="lineDown" color="warning">
Warning
</Button>
<Button variant="lineDown" color="dark">
Dark
</Button>
<Button variant="lineDown" disabled>
Disabled
</Button>
</ButtonRow>
<ButtonRow>
<Button variant="gradient">Primary</Button>
<Button variant="gradient" color="success">
Success
</Button>
<Button variant="gradient" color="danger">
Danger
</Button>
<Button variant="gradient" color="warning">
Warning
</Button>
<Button variant="gradient" color="dark">
Dark
</Button>
<Button variant="gradient" disabled>
Disabled
</Button>
</ButtonRow>
<ButtonRow>
<Button variant="relief">Primary</Button>
<Button variant="relief" color="success">
Success
</Button>
<Button variant="relief" color="danger">
Danger
</Button>
<Button variant="relief" color="warning">
Warning
</Button>
<Button variant="relief" color="dark">
Dark
</Button>
<Button variant="relief" disabled>
Disabled
</Button>
</ButtonRow>
</ThemeProvider>
</>
);
};

View File

@ -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<ButtonProps> = ({
disabled = false,
fontSize = '14px',
color = 'primary',
variant = 'filled',
onClick,
className,
children,
}) => {
const handleClick = () => {
if (onClick) {
onClick();
}
};
switch (variant) {
case 'filled':
return (
<Filled onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Filled>
);
case 'outline':
return (
<Outline onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Outline>
);
case 'flat':
return (
<Flat onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Flat>
);
case 'lineDown':
return (
<LineDown onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
<LineX color={color} />
</LineDown>
);
case 'gradient':
return (
<Gradient onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Gradient>
);
case 'relief':
return (
<Relief onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Relief>
);
default:
throw new Error('not a valid variant');
}
};
export default Button;

View File

@ -107,9 +107,68 @@ export const Everything = () => {
onClick={action('on click')} onClick={action('on click')}
onContextMenu={action('on context click')} onContextMenu={action('on context click')}
watched watched
members={[
{
id: '1',
fullName: 'Jordan Knott',
profileIcon: {
bgColor: '#0079bf',
initials: 'JK',
url: null,
},
},
]}
labels={labelData} labels={labelData}
checklists={{ complete: 1, total: 4 }} checklists={{ complete: 1, total: 4 }}
dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }} 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

@ -1,10 +1,34 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
import TextareaAutosize from 'react-autosize-textarea';
import { RefObject } from 'react'; import { RefObject } from 'react';
export const ClockIcon = styled(FontAwesomeIcon)``; 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` export const ListCardBadges = styled.div`
float: left; float: left;
display: flex; display: flex;
@ -17,6 +41,7 @@ export const ListCardBadge = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
margin: 0 6px 4px 0; margin: 0 6px 4px 0;
font-size: 12px;
max-width: 100%; max-width: 100%;
min-height: 20px; min-height: 20px;
overflow: hidden; overflow: hidden;
@ -32,6 +57,7 @@ export const DescriptionBadge = styled(ListCardBadge)`
`; `;
export const DueDateCardBadge = styled(ListCardBadge)<{ isPastDue: boolean }>` export const DueDateCardBadge = styled(ListCardBadge)<{ isPastDue: boolean }>`
font-size: 12px;
${props => ${props =>
props.isPastDue && props.isPastDue &&
css` css`
@ -49,16 +75,16 @@ export const ListCardBadgeText = styled.span`
white-space: nowrap; white-space: nowrap;
`; `;
export const ListCardContainer = styled.div<{ isActive: boolean }>` export const ListCardContainer = styled.div<{ isActive: boolean; editable: boolean }>`
max-width: 256px; max-width: 256px;
margin-bottom: 8px; margin-bottom: 8px;
background-color: #fff;
border-radius: 3px; border-radius: 3px;
${mixin.boxShadowCard} ${mixin.boxShadowCard}
cursor: pointer !important; cursor: pointer !important;
position: relative; 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` export const ListCardInnerContainer = styled.div`
@ -113,18 +139,16 @@ export const ListCardOperation = styled.span`
`; `;
export const CardTitle = styled.span` export const CardTitle = styled.span`
font-family: 'Droid Sans';
clear: both; clear: both;
display: block; display: block;
margin: 0 0 4px; margin: 0 0 4px;
overflow: hidden; overflow: hidden;
text-decoration: none; text-decoration: none;
word-wrap: break-word; word-wrap: break-word;
color: #c2c6dc; color: var(--color-text);
`; `;
export const CardMembers = styled.div` export const CardMembers = styled.div`
float: right; float: right;
margin: 0 -2px 0 0; margin: 0 -2px 4px 0;
`; `;

View File

@ -1,11 +1,10 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { DraggableProvidedDraggableProps } from 'react-beautiful-dnd';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 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 { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons';
import { faClock, faCheckSquare, faEye } from '@fortawesome/free-regular-svg-icons'; import { faClock, faCheckSquare, faEye } from '@fortawesome/free-regular-svg-icons';
import { import {
EditorTextarea,
DescriptionBadge, DescriptionBadge,
DueDateCardBadge, DueDateCardBadge,
ListCardBadges, ListCardBadges,
@ -21,7 +20,6 @@ import {
CardTitle, CardTitle,
CardMembers, CardMembers,
} from './Styles'; } from './Styles';
import TaskAssignee from 'shared/components/TaskAssignee';
type DueDate = { type DueDate = {
isPastDue: boolean; isPastDue: boolean;
@ -35,11 +33,11 @@ type Checklist = {
type Props = { type Props = {
title: string; title: string;
description: string;
taskID: string; taskID: string;
taskGroupID: string; taskGroupID: string;
onContextMenu: (e: ContextMenuEvent) => void; onContextMenu?: (e: ContextMenuEvent) => void;
onClick: (e: React.MouseEvent<HTMLDivElement>) => void; onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
description?: null | string;
dueDate?: DueDate; dueDate?: DueDate;
checklists?: Checklist; checklists?: Checklist;
labels?: Array<ProjectLabel>; labels?: Array<ProjectLabel>;
@ -47,6 +45,9 @@ type Props = {
wrapperProps?: any; wrapperProps?: any;
members?: Array<TaskUser> | null; members?: Array<TaskUser> | null;
onCardMemberClick?: OnCardMemberClick; onCardMemberClick?: OnCardMemberClick;
editable?: boolean;
onEditCard?: (taskGroupID: string, taskID: string, cardName: string) => void;
onCardTitleChange?: (name: string) => void;
}; };
const Card = React.forwardRef( const Card = React.forwardRef(
@ -65,20 +66,47 @@ const Card = React.forwardRef(
watched, watched,
members, members,
onCardMemberClick, onCardMemberClick,
editable,
onEditCard,
onCardTitleChange,
}: Props, }: Props,
$cardRef: any, $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 [isActive, setActive] = useState(false);
const $innerCardRef: any = useRef(null); const $innerCardRef: any = useRef(null);
const onOpenComposer = () => { const onOpenComposer = () => {
if (typeof $innerCardRef.current !== 'undefined') { if (typeof $innerCardRef.current !== 'undefined') {
const pos = $innerCardRef.current.getBoundingClientRect(); const pos = $innerCardRef.current.getBoundingClientRect();
onContextMenu({ if (onContextMenu) {
top: pos.top, onContextMenu({
left: pos.left, top: pos.top,
taskGroupID, left: pos.left,
taskID, taskGroupID,
}); taskID,
});
}
} }
}; };
const onTaskContext = (e: React.MouseEvent) => { const onTaskContext = (e: React.MouseEvent) => {
@ -96,9 +124,14 @@ const Card = React.forwardRef(
onMouseEnter={() => setActive(true)} onMouseEnter={() => setActive(true)}
onMouseLeave={() => setActive(false)} onMouseLeave={() => setActive(false)}
ref={$cardRef} ref={$cardRef}
onClick={onClick} onClick={e => {
if (onClick) {
onClick(e);
}
}}
onContextMenu={onTaskContext} onContextMenu={onTaskContext}
isActive={isActive} isActive={isActive}
editable={editable}
{...wrapperProps} {...wrapperProps}
> >
<ListCardInnerContainer ref={$innerCardRef}> <ListCardInnerContainer ref={$innerCardRef}>
@ -116,7 +149,24 @@ const Card = React.forwardRef(
</ListCardLabel> </ListCardLabel>
))} ))}
</ListCardLabels> </ListCardLabels>
<CardTitle>{title}</CardTitle> {editable ? (
<EditorTextarea
onChange={e => {
setCardTitle(e.currentTarget.value);
if (onCardTitleChange) {
onCardTitleChange(e.currentTarget.value);
}
}}
onClick={e => {
e.stopPropagation();
}}
onKeyDown={handleKeyDown}
value={currentCardTitle}
ref={$editorRef}
/>
) : (
<CardTitle>{title}</CardTitle>
)}
<ListCardBadges> <ListCardBadges>
{watched && ( {watched && (
<ListCardBadge> <ListCardBadge>

View File

@ -1,4 +1,5 @@
import styled from 'styled-components'; import styled from 'styled-components';
import Button from 'shared/components/Button';
import TextareaAutosize from 'react-autosize-textarea'; import TextareaAutosize from 'react-autosize-textarea';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
@ -15,53 +16,6 @@ export const CardComposerWrapper = styled.div<{ isOpen: boolean }>`
flex-direction: column; 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 ComposerControls = styled.div``;
export const ComposerControlsSaveSection = styled.div` export const ComposerControlsSaveSection = styled.div`
@ -73,18 +27,9 @@ export const ComposerControlsSaveSection = styled.div`
export const ComposerControlsActionsSection = styled.div` export const ComposerControlsActionsSection = styled.div`
float: right; float: right;
`; `;
export const AddCardButton = styled(Button)`
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;
margin-right: 4px; margin-right: 4px;
padding: 6px 12px; padding: 6px 12px;
text-align: center;
border-radius: 3px; border-radius: 3px;
`; `;

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown'; import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown';
import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { faTimes } from '@fortawesome/free-solid-svg-icons';
@ -8,13 +8,11 @@ import {
CardComposerWrapper, CardComposerWrapper,
CancelIcon, CancelIcon,
AddCardButton, AddCardButton,
ListCard,
ListCardDetails,
ListCardEditor,
ComposerControls, ComposerControls,
ComposerControlsSaveSection, ComposerControlsSaveSection,
ComposerControlsActionsSection, ComposerControlsActionsSection,
} from './Styles'; } from './Styles';
import Card from '../Card';
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -24,43 +22,36 @@ type Props = {
const CardComposer = ({ isOpen, onCreateCard, onClose }: Props) => { const CardComposer = ({ isOpen, onCreateCard, onClose }: Props) => {
const [cardName, setCardName] = useState(''); const [cardName, setCardName] = useState('');
const $cardEditor: any = useRef(null); const $cardRef = useRef<HTMLDivElement>(null);
const handleCreateCard = () => { useOnOutsideClick($cardRef, true, onClose, null);
onCreateCard(cardName);
setCardName('');
if ($cardEditor && $cardEditor.current) {
$cardEditor.current.focus();
}
};
const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
handleCreateCard();
}
};
useOnEscapeKeyDown(isOpen, onClose); useOnEscapeKeyDown(isOpen, onClose);
useOnOutsideClick($cardEditor, true, () => onClose(), null);
useEffect(() => {
$cardEditor.current.focus();
}, []);
return ( return (
<CardComposerWrapper isOpen={isOpen}> <CardComposerWrapper isOpen={isOpen}>
<ListCard> <Card
<ListCardDetails> title={cardName}
<ListCardEditor ref={$cardRef}
onKeyDown={onKeyDown} taskID=""
ref={$cardEditor} taskGroupID=""
onChange={e => { editable
setCardName(e.currentTarget.value); onEditCard={(_taskGroupID, _taskID, name) => {
}} onCreateCard(name);
value={cardName} setCardName('');
placeholder="Enter a title for this card..." }}
/> onCardTitleChange={name => {
</ListCardDetails> setCardName(name);
</ListCard> }}
/>
<ComposerControls> <ComposerControls>
<ComposerControlsSaveSection> <ComposerControlsSaveSection>
<AddCardButton onClick={handleCreateCard}>Add Card</AddCardButton> <AddCardButton
variant="relief"
onClick={() => {
onCreateCard(cardName);
setCardName('');
}}
>
Add Card
</AddCardButton>
<CancelIcon onClick={onClose} icon={faTimes} color="#42526e" /> <CancelIcon onClick={onClose} icon={faTimes} color="#42526e" />
</ComposerControlsSaveSection> </ComposerControlsSaveSection>
<ComposerControlsActionsSection /> <ComposerControlsActionsSection />

View File

@ -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 (
<>
<NormalizeStyles />
<BaseStyles />
<ThemeProvider theme={theme}>
<Wrapper>
<Input label="Label placeholder" />
<Input width="100%" placeholder="Placeholder" />
<Input icon={<User size={20} />} width="100%" placeholder="Placeholder" />
</Wrapper>
</ThemeProvider>
</>
);
};

View File

@ -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<InputProps> = ({ 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 (
<InputWrapper width={width}>
<InputInput
hasIcon={typeof icon !== 'undefined'}
width={width}
placeholder={placeholder}
focusBg={focusBg}
borderColor={borderColor}
/>
{label && <InputLabel width={width}>{label}</InputLabel>}
<Icon>{icon && icon}</Icon>
</InputWrapper>
);
};
export default Input;

View File

@ -65,7 +65,6 @@ const initialListsData = {
export const Default = () => { export const Default = () => {
const [listsData, setListsData] = useState(initialListsData); const [listsData, setListsData] = useState(initialListsData);
const onCardDrop = (droppedTask: Task) => { const onCardDrop = (droppedTask: Task) => {
console.log(droppedTask);
const newState = { const newState = {
...listsData, ...listsData,
tasks: { tasks: {
@ -85,84 +84,6 @@ export const Default = () => {
}; };
setListsData(newState); setListsData(newState);
}; };
return <span />;
};
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 ( return (
<Lists <Lists
taskGroups={[]} taskGroups={[]}

View File

@ -74,11 +74,11 @@ export const LoginButton = styled.input`
padding: 0.75rem 2rem; padding: 0.75rem 2rem;
font-size: 1rem; font-size: 1rem;
border-radius: 6px; border-radius: 6px;
background: rgb(115, 103, 240); background: var(--color-button-background);
outline: none; outline: none;
border: none; border: none;
cursor: pointer; cursor: pointer;
color: #fff; color: var(--color-button-text-hover);
&:disabled { &:disabled {
opacity: 0.5; opacity: 0.5;
cursor: default; cursor: default;
@ -98,7 +98,7 @@ export const RegisterButton = styled.button`
border: 1px solid rgb(115, 103, 240); border: 1px solid rgb(115, 103, 240);
background: transparent; background: transparent;
font-size: 1rem; font-size: 1rem;
color: rgba(115, 103, 240); color: var(--color-primary);
cursor: pointer; cursor: pointer;
`; `;

View File

@ -25,6 +25,7 @@ const CardMemberInitials = styled.div`
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 14px; font-size: 14px;
font-weight: 400;
`; `;
type MemberProps = { type MemberProps = {

View File

@ -37,6 +37,15 @@ const labelData: Array<TaskLabel> = [
export const Default = () => { export const Default = () => {
const $cardRef: any = createRef(); 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 [isEditorOpen, setEditorOpen] = useState(false);
const [top, setTop] = useState(0); const [top, setTop] = useState(0);
const [left, setLeft] = useState(0); const [left, setLeft] = useState(0);
@ -44,15 +53,7 @@ export const Default = () => {
<> <>
{isEditorOpen && ( {isEditorOpen && (
<QuickCardEditor <QuickCardEditor
task={{ task={task}
id: 'task',
name: 'General',
taskGroup: {
id: '1',
},
position: 1,
labels: labelData,
}}
onCloseEditor={() => setEditorOpen(false)} onCloseEditor={() => setEditorOpen(false)}
onEditCard={action('edit card')} onEditCard={action('edit card')}
onOpenLabelsPopup={action('open popup')} onOpenLabelsPopup={action('open popup')}
@ -76,7 +77,7 @@ export const Default = () => {
taskGroupID="1" taskGroupID="1"
description="hello!" description="hello!"
ref={$cardRef} ref={$cardRef}
title="Hello, world" title={task.name}
onClick={action('on click')} onClick={action('on click')}
onContextMenu={e => { onContextMenu={e => {
setTop(e.top); setTop(e.top);

View File

@ -21,53 +21,6 @@ export const Container = styled.div<{ top: number; left: number }>`
left: ${props => props.left}px; 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` export const SaveButton = styled.button`
cursor: pointer; cursor: pointer;
background: rgb(115, 103, 240); background: rgb(115, 103, 240);
@ -125,25 +78,3 @@ export const CloseButton = styled.div`
z-index: 40; z-index: 40;
cursor: pointer; 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};
`;

View File

@ -1,20 +1,8 @@
import React, { useRef, useState, useEffect } from 'react'; import React, { useRef, useState } from 'react';
import Cross from 'shared/icons/Cross'; import Cross from 'shared/icons/Cross';
import styled from 'styled-components'; import styled from 'styled-components';
import Member from 'shared/components/Member'; import { Wrapper, Container, EditorButtons, SaveButton, EditorButton, CloseButton } from './Styles';
import { import Card from '../Card';
Wrapper,
Container,
Editor,
EditorDetails,
EditorTextarea,
EditorButtons,
SaveButton,
EditorButton,
CloseButton,
ListCardLabels,
ListCardLabel,
} from './Styles';
export const CardMembers = styled.div` export const CardMembers = styled.div`
position: absolute; position: absolute;
@ -46,61 +34,34 @@ const QuickCardEditor = ({
left, left,
}: Props) => { }: Props) => {
const [currentCardTitle, setCardTitle] = useState(task.name); const [currentCardTitle, setCardTitle] = useState(task.name);
const $editorRef: any = useRef();
const $labelsRef: any = useRef(); const $labelsRef: any = useRef();
const $membersRef: any = useRef(); const $membersRef: any = useRef();
useEffect(() => {
$editorRef.current.focus();
$editorRef.current.select();
}, []);
const handleCloseEditor = (e: any) => { const handleCloseEditor = (e: any) => {
e.stopPropagation(); e.stopPropagation();
onCloseEditor(); onCloseEditor();
}; };
const handleKeyDown = (e: any) => {
if (e.key === 'Enter') {
e.preventDefault();
onEditCard(task.taskGroup.id, task.id, currentCardTitle);
onCloseEditor();
}
};
return ( return (
<Wrapper onClick={handleCloseEditor} open> <Wrapper onClick={handleCloseEditor} open>
<CloseButton onClick={handleCloseEditor}> <CloseButton onClick={handleCloseEditor}>
<Cross size={16} color="#000" /> <Cross size={16} color="#000" />
</CloseButton> </CloseButton>
<Container left={left} top={top}> <Container left={left} top={top}>
<Editor> <Card
<ListCardLabels> editable
{task.labels && onCardMemberClick={onCardMemberClick}
task.labels.map(label => ( title={currentCardTitle}
<ListCardLabel color={label.projectLabel.labelColor.colorHex} key={label.id}> onEditCard={(taskGroupID, taskID, name) => {
{label.projectLabel.name} onEditCard(taskGroupID, taskID, name);
</ListCardLabel> onCloseEditor();
))} }}
</ListCardLabels> members={task.assigned}
<EditorDetails> taskID={task.id}
<EditorTextarea taskGroupID={task.taskGroup.id}
onChange={e => setCardTitle(e.currentTarget.value)} labels={task.labels.map(l => l.projectLabel)}
onClick={e => { />
e.stopPropagation(); <SaveButton onClick={() => onEditCard(task.taskGroup.id, task.id, currentCardTitle)}>Save</SaveButton>
}}
onKeyDown={handleKeyDown}
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> <EditorButtons>
<EditorButton <EditorButton
ref={$membersRef} ref={$membersRef}

View File

@ -1,67 +1,8 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { User } from 'shared/icons'; import { User } from 'shared/icons';
import Input from 'shared/components/Input';
const TextFieldWrapper = styled.div` import Button from 'shared/components/Button';
display: flex;
align-items: flex-start;
flex-direction: column;
position: relative;
justify-content: center;
margin-bottom: 2.2rem;
margin-top: 17px;
`;
const TextFieldLabel = styled.span`
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;
width: 100%;
font-size: 12px;
user-select: none;
pointer-events: none;
}
`;
const TextFieldInput = styled.input`
font-size: 12px;
border: 1px solid rgba(0, 0, 0, 0.2);
background: #262c49;
padding: 0.7rem !important;
color: #c2c6dc;
position: relative;
border-radius: 5px;
transition: all 0.3s ease;
width: 100%;
&:focus {
box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15);
border: 1px solid rgba(115, 103, 240);
}
&:focus ~ ${TextFieldLabel} {
color: rgba(115, 103, 240);
transform: translate(-3px, -90%);
}
`;
type TextFieldProps = {
label: string;
};
const TextField: React.FC<TextFieldProps> = ({ label }) => {
return (
<TextFieldWrapper>
<TextFieldInput />
<TextFieldLabel>{label}</TextFieldLabel>
</TextFieldWrapper>
);
};
const ProfileContainer = styled.div` const ProfileContainer = styled.div`
display: flex; display: flex;
@ -103,30 +44,13 @@ const ActionButtons = styled.div`
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
`; `;
const UploadButton = styled.div` const UploadButton = styled(Button)`
margin-right: 1rem; margin-right: 1rem;
padding: 0.75rem 2rem;
border: 0;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
color: #fff;
display: inline-block; display: inline-block;
background: rgba(115, 103, 240);
`; `;
const RemoveButton = styled.button` const RemoveButton = styled(Button)`
display: inline-block; 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` const ImgLabel = styled.p`
@ -164,7 +88,9 @@ const AvatarSettings: React.FC<AvatarSettingsProps> = ({ profile, onProfileAvata
</AvatarContainer> </AvatarContainer>
<ActionButtons> <ActionButtons>
<UploadButton onClick={() => onProfileAvatarChange()}>Upload photo</UploadButton> <UploadButton onClick={() => onProfileAvatarChange()}>Upload photo</UploadButton>
<RemoveButton onClick={() => onProfileAvatarRemove()}>Remove</RemoveButton> <RemoveButton variant="outline" color="danger" onClick={() => onProfileAvatarRemove()}>
Remove
</RemoveButton>
<ImgLabel>Allowed JPG, GIF or PNG. Max size of 800kB</ImgLabel> <ImgLabel>Allowed JPG, GIF or PNG. Max size of 800kB</ImgLabel>
</ActionButtons> </ActionButtons>
</ProfileContainer> </ProfileContainer>
@ -241,6 +167,7 @@ const TabNavLine = styled.span<{ top: number }>`
`; `;
const TabContentWrapper = styled.div` const TabContentWrapper = styled.div`
margin-left: 1rem;
position: relative; position: relative;
display: block; display: block;
overflow: hidden; overflow: hidden;
@ -254,7 +181,6 @@ const TabContent = styled.div`
padding: 0; padding: 0;
padding: 1.5rem; padding: 1.5rem;
background-color: #10163a; background-color: #10163a;
margin-left: 1rem !important;
border-radius: 0.5rem; border-radius: 0.5rem;
`; `;
@ -294,17 +220,9 @@ const SettingActions = styled.div`
justify-content: flex-end; justify-content: flex-end;
`; `;
const SaveButton = styled.div` const SaveButton = styled(Button)`
margin-right: 1rem; margin-right: 1rem;
padding: 0.75rem 2rem;
border: 0;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
color: #fff;
display: inline-block; display: inline-block;
background: rgba(115, 103, 240);
`; `;
type SettingsProps = { type SettingsProps = {
@ -345,11 +263,11 @@ const Settings: React.FC<SettingsProps> = ({ onProfileAvatarRemove, onProfileAva
onProfileAvatarChange={onProfileAvatarChange} onProfileAvatarChange={onProfileAvatarChange}
profile={profile} profile={profile}
/> />
<TextField label="Name" /> <Input width="100%" label="Name" />
<TextField label="Initials " /> <Input width="100%" label="Initials " />
<TextField label="Username " /> <Input width="100%" label="Username " />
<TextField label="Email" /> <Input width="100%" label="Email" />
<TextField label="Bio" /> <Input width="100%" label="Bio" />
<SettingActions> <SettingActions>
<SaveButton>Save Change</SaveButton> <SaveButton>Save Change</SaveButton>
</SettingActions> </SettingActions>

View File

@ -16,10 +16,11 @@ export const Wrapper = styled.div<{ size: number | string; bgColor: string | nul
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: #fff; color: #fff;
font-weight: 700;
background: ${props => (props.backgroundURL ? `url(${props.backgroundURL})` : props.bgColor)}; background: ${props => (props.backgroundURL ? `url(${props.backgroundURL})` : props.bgColor)};
background-position: center; background-position: center;
background-size: contain; background-size: contain;
font-size: 14px;
font-weight: 400;
`; `;
type TaskAssigneeProps = { type TaskAssigneeProps = {

View File

@ -1,6 +1,7 @@
import styled from 'styled-components'; import styled from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea/lib'; import TextareaAutosize from 'react-autosize-textarea/lib';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
import Button from 'shared/components/Button';
export const TaskHeader = styled.div` export const TaskHeader = styled.div`
padding: 21px 30px 0px; padding: 21px 30px 0px;
@ -159,23 +160,13 @@ export const TaskDetailsMarkdown = styled.div`
export const TaskDetailsControls = styled.div` export const TaskDetailsControls = styled.div`
clear: both; clear: both;
margin-top: 8px; margin-top: 8px;
display: flex;
`; `;
export const ConfirmSave = styled.div` export const ConfirmSave = styled(Button)`
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;
padding: 6px 12px; padding: 6px 12px;
text-align: center;
border-radius: 3px; border-radius: 3px;
font-size: 14px; margin-right: 6px;
`; `;
export const CancelEdit = styled.div` export const CancelEdit = styled.div`

View File

@ -88,7 +88,9 @@ const DetailsEditor: React.FC<DetailsEditorProps> = ({
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.currentTarget.value)} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.currentTarget.value)}
/> />
<TaskDetailsControls> <TaskDetailsControls>
<ConfirmSave onClick={handleOutsideClick}>Save</ConfirmSave> <ConfirmSave variant="relief" onClick={handleOutsideClick}>
Save
</ConfirmSave>
<CancelEdit onClick={onCancel}> <CancelEdit onClick={onCancel}>
<Plus size={16} color="#c2c6dc" /> <Plus size={16} color="#c2c6dc" />
</CancelEdit> </CancelEdit>

View File

@ -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;

View File

@ -1,6 +1,7 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea'; import TextareaAutosize from 'react-autosize-textarea';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
import Button from 'shared/components/Button';
export const NavbarWrapper = styled.div` export const NavbarWrapper = styled.div`
width: 100%; width: 100%;
@ -103,7 +104,7 @@ export const ProjectTabs = styled.div`
export const ProjectTab = styled.span<{ active?: boolean }>` export const ProjectTab = styled.span<{ active?: boolean }>`
font-size: 80%; font-size: 80%;
color: #c2c6dc; color: rgba(${props => props.theme.colors.text.primary});
font-size: 15px; font-size: 15px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@ -122,27 +123,25 @@ export const ProjectTab = styled.span<{ active?: boolean }>`
${props => ${props =>
props.active props.active
? css` ? css`
box-shadow: inset 0 -2px #d85dd8; box-shadow: inset 0 -2px rgba(${props.theme.colors.secondary});
color: #d85dd8; color: rgba(${props.theme.colors.secondary});
` `
: css` : css`
&:hover { &:hover {
box-shadow: inset 0 -2px #cbd4db; box-shadow: inset 0 -2px rgba(${props.theme.colors.text.secondary});
color: ${mixin.lighten('#c2c6dc', 0.25)}; color: rgba(${props.theme.colors.text.secondary});
} }
`} `}
`; `;
export const ProjectName = styled.h1` export const ProjectName = styled.h1`
color: #c2c6dc; color: rgba(${props => props.theme.colors.text.primary});
font-weight: 600; font-weight: 600;
font-size: 20px; font-size: 20px;
padding: 3px 10px 3px 8px; padding: 3px 10px 3px 8px;
font-family: 'Droid Sans';
margin: -4px 0; margin: -4px 0;
`; `;
export const ProjectNameTextarea = styled(TextareaAutosize)` export const ProjectNameTextarea = styled(TextareaAutosize)`
font-family: 'Droid Sans';
border: none; border: none;
resize: none; resize: none;
overflow: hidden; overflow: hidden;
@ -211,28 +210,7 @@ export const ProjectSettingsButton = styled.button`
} }
`; `;
export const InviteButton = 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;
margin: 0 0 0 8px; margin: 0 0 0 8px;
padding: 6px 12px;
border-radius: 3px;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image: initial;
border-color: #414561;
&:hover {
background: rgb(115, 103, 240);
}
`; `;

View File

@ -176,7 +176,7 @@ const NavBar: React.FC<NavBarProps> = ({
{projectMembers.map(member => ( {projectMembers.map(member => (
<TaskAssignee key={member.id} size={28} member={member} onMemberProfile={onMemberProfile} /> <TaskAssignee key={member.id} size={28} member={member} onMemberProfile={onMemberProfile} />
))} ))}
<InviteButton>Invite</InviteButton> <InviteButton variant="outline">Invite</InviteButton>
</ProjectMembers> </ProjectMembers>
)} )}
<NotificationContainer onClick={onNotificationClick}> <NotificationContainer onClick={onNotificationClick}>

30
web/src/styled.d.ts vendored Normal file
View File

@ -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;
};
};
}
}