chore: add theme colors, merge Card components, create single Button component
This commit is contained in:
parent
6267a37b6e
commit
a12e9c1e50
@ -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 {
|
||||||
|
47
web/src/App/ThemeStyles.ts
Normal file
47
web/src/App/ThemeStyles.ts
Normal 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);
|
||||||
|
}
|
||||||
|
`;
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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(
|
||||||
|
@ -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 = [];
|
||||||
|
@ -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`
|
||||||
|
@ -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('');
|
||||||
|
138
web/src/shared/components/Button/Button.stories.tsx
Normal file
138
web/src/shared/components/Button/Button.stories.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
171
web/src/shared/components/Button/index.tsx
Normal file
171
web/src/shared/components/Button/index.tsx
Normal 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;
|
@ -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')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -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;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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 />
|
||||||
|
43
web/src/shared/components/Input/Input.stories.tsx
Normal file
43
web/src/shared/components/Input/Input.stories.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
91
web/src/shared/components/Input/index.tsx
Normal file
91
web/src/shared/components/Input/index.tsx
Normal 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;
|
@ -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={[]}
|
||||||
|
@ -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;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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 = {
|
||||||
|
@ -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);
|
||||||
|
@ -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};
|
|
||||||
`;
|
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
|
@ -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 = {
|
||||||
|
@ -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`
|
||||||
|
@ -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>
|
||||||
|
31
web/src/shared/components/Textarea/index.tsx
Normal file
31
web/src/shared/components/Textarea/index.tsx
Normal 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;
|
@ -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);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
@ -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
30
web/src/styled.d.ts
vendored
Normal 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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user