Implemented kanban board page with lists of issues

This commit is contained in:
ireic
2019-12-12 17:26:57 +01:00
parent 3143f66a0f
commit 73b4ff97b2
73 changed files with 1343 additions and 561 deletions

View File

@@ -7,8 +7,7 @@ export const Image = styled.div`
width: ${props => props.size}px;
height: ${props => props.size}px;
border-radius: 100%;
background-image: url('${props => props.avatarUrl}');
${mixin.backgroundImage}
${props => mixin.backgroundImage(props.avatarUrl)}
`;
export const Letter = styled.div`

View File

@@ -14,7 +14,7 @@ const defaultProps = {
className: undefined,
avatarUrl: null,
name: '',
size: 24,
size: 32,
};
const colors = [
@@ -30,12 +30,12 @@ const colors = [
const getColorFromName = name => colors[name.toLocaleLowerCase().charCodeAt(0) % colors.length];
const Avatar = ({ className, avatarUrl, name, size }) => {
const Avatar = ({ className, avatarUrl, name, size, ...otherProps }) => {
if (avatarUrl) {
return <Image className={className} size={size} avatarUrl={avatarUrl} />;
return <Image className={className} size={size} avatarUrl={avatarUrl} {...otherProps} />;
}
return (
<Letter className={className} size={size} color={getColorFromName(name)}>
<Letter className={className} size={size} color={getColorFromName(name)} {...otherProps}>
<span>{name.charAt(0)}</span>
</Letter>
);

View File

@@ -4,83 +4,79 @@ import Spinner from 'shared/components/Spinner';
import { color, font, mixin } from 'shared/utils/styles';
export const StyledButton = styled.button`
display: inline-block;
height: 36px;
line-height: 34px;
padding: 0 18px;
vertical-align: middle;
display: inline-flex;
align-items: center;
justify-content: center;
height: 32px;
line-height: 1;
padding: 0 ${props => (props.iconOnly ? 9 : 12)}px;
white-space: nowrap;
text-align: center;
border-radius: 4px;
border-radius: 3px;
transition: all 0.1s;
appearance: none !important;
appearance: none;
${mixin.clickable}
${font.bold}
${font.size(14)}
${props => (props.hollow ? hollowStyles : filledStyles)}
${font.size(14.5)}
${props => buttonColors[props.color]}
&:disabled {
opacity: 0.6;
cursor: default;
}
i {
position: relative;
top: -1px;
right: 4px;
margin-right: 7px;
display: inline-block;
vertical-align: middle;
line-height: 1;
font-size: 16px;
}
${props => (props.iconOnly ? iconOnlyStyles : '')}
`;
const filledStyles = props => css`
color: #fff;
background: ${color[props.color]};
border: 1px solid ${color[props.color]};
${!props.disabled &&
css`
&:hover,
&:focus {
background: ${mixin.darken(color[props.color], 0.15)};
border: 1px solid ${mixin.darken(color[props.color], 0.15)};
}
&:active {
background: ${mixin.lighten(color[props.color], 0.1)};
border: 1px solid ${mixin.lighten(color[props.color], 0.1)};
}
`}
`;
const hollowStyles = props => css`
color: ${color.textMediumBlue};
background: #fff;
border: 1px solid ${color.borderBlue};
${!props.disabled &&
css`
&:hover,
&:focus {
border: 1px solid ${mixin.darken(color.borderBlue, 0.15)};
}
&:active {
border: 1px solid ${color.borderBlue};
}
`}
`;
const iconOnlyStyles = css`
padding: 0 12px;
i {
right: 0;
margin-right: 0;
margin-right: ${props => (props.iconOnly ? 0 : 7)}px;
}
`;
const secondaryAndEmptyShared = css`
color: ${color.textDark};
${font.regular}
&:not(:disabled) {
&:hover {
background: ${color.backgroundLight};
}
&:active {
color: ${color.primary};
background: ${mixin.rgba(color.primary, 0.15)};
}
${props =>
props.isActive &&
`
color: ${color.primary};
background: ${mixin.rgba(color.primary, 0.15)} !important;
`}
}
`;
const buttonColors = {
primary: css`
color: #fff;
background: ${color.primary};
${font.medium}
&:not(:disabled) {
&:hover {
background: ${mixin.lighten(color.primary, 0.15)};
}
&:active {
background: ${mixin.darken(color.primary, 0.1)};
}
${props =>
props.isActive &&
`
background: ${mixin.darken(color.primary, 0.1)} !important;
`}
}
`,
secondary: css`
background: ${color.secondary};
${secondaryAndEmptyShared};
`,
empty: css`
background: #fff;
${secondaryAndEmptyShared};
`,
};
export const StyledSpinner = styled(Spinner)`
position: relative;
right: 8px;
display: inline-block;
vertical-align: middle;
line-height: 1;
top: 1px;
margin-right: ${props => (props.iconOnly ? 0 : 7)}px;
`;

View File

@@ -8,9 +8,7 @@ import { StyledButton, StyledSpinner } from './Styles';
const propTypes = {
className: PropTypes.string,
children: PropTypes.node,
type: PropTypes.string,
hollow: PropTypes.bool,
color: PropTypes.oneOf(['primary', 'success', 'danger']),
color: PropTypes.oneOf(['primary', 'secondary', 'empty']),
icon: PropTypes.string,
iconSize: PropTypes.number,
disabled: PropTypes.bool,
@@ -21,11 +19,9 @@ const propTypes = {
const defaultProps = {
className: undefined,
children: undefined,
type: 'button',
hollow: false,
color: 'primary',
color: 'secondary',
icon: undefined,
iconSize: undefined,
iconSize: 18,
disabled: false,
working: false,
onClick: () => {},
@@ -33,7 +29,7 @@ const defaultProps = {
const Button = ({
children,
hollow,
color: propsColor,
icon,
iconSize,
disabled,
@@ -43,21 +39,31 @@ const Button = ({
}) => (
<StyledButton
{...buttonProps}
hollow={hollow}
onClick={() => {
if (!disabled && !working) {
onClick();
}
}}
color={propsColor}
disabled={disabled || working}
working={working}
iconOnly={!children}
>
{working && <StyledSpinner size={26} color={hollow ? color.textMediumBlue : '#fff'} />}
{!working && icon && (
<Icon type={icon} size={iconSize} color={hollow ? color.textMediumBlue : '#fff'} />
{working && (
<StyledSpinner
iconOnly={!children}
size={26}
color={propsColor === 'primary' ? '#fff' : color.textDark}
/>
)}
{children}
{!working && icon && (
<Icon
type={icon}
size={iconSize}
color={propsColor === 'primary' ? '#fff' : color.textDark}
/>
)}
<div>{children}</div>
</StyledButton>
);

View File

@@ -72,7 +72,7 @@ const ConfirmModal = ({
{confirmInput && (
<>
<InputLabel>{`Type ${confirmInput} below to confirm.`}</InputLabel>
<StyledInput onChange={(event, value) => handleConfirmInputChange(value)} />
<StyledInput onChange={handleConfirmInputChange} />
<br />
</>
)}

View File

@@ -12,7 +12,7 @@ export const Dropdown = styled.div`
top: 130%;
right: 0;
width: 270px;
border-radius: 4px;
border-radius: 3px;
background: #fff;
${mixin.boxShadowBorderMedium}
${props => (props.withTime ? withTimeStyles : '')}
@@ -76,7 +76,7 @@ export const Day = styled.div`
width: 14.28%;
height: 30px;
line-height: 30px;
border-radius: 4px;
border-radius: 3px;
${font.size(15)}
${props => (!props.isFiller ? hoverStyles : '')}
${props => (props.isToday ? font.bold : '')}

View File

@@ -4,42 +4,30 @@ import PropTypes from 'prop-types';
import StyledIcon from './Styles';
const codes = {
[`check-circle`]: '\\e86c',
[`check-fat`]: '\\f00c',
[`arrow-left`]: '\\e900',
[`arrow-right`]: '\\e912',
[`upload-thin`]: '\\e91f',
[`bell`]: '\\e901',
[`calendar`]: '\\e903',
[`check`]: '\\e904',
[`chevron-down`]: '\\e905',
[`chevron-left`]: '\\e906',
[`chevron-right`]: '\\e907',
[`chevron-up`]: '\\e908',
[`clock`]: '\\e909',
[`download`]: '\\e90a',
[`plus`]: '\\e90c',
[`refresh`]: '\\e90d',
[`search`]: '\\e90e',
[`upload`]: '\\e90f',
[`close`]: '\\e910',
[`archive`]: '\\e915',
[`briefcase`]: '\\e916',
[`settings`]: '\\e902',
[`email`]: '\\e914',
[`lock`]: '\\e913',
[`dashboard`]: '\\e917',
[`alert`]: '\\e911',
[`edit`]: '\\e918',
[`delete`]: '\\e919',
[`sort`]: '\\f0dc',
[`sort-up`]: '\\f0d8',
[`sort-down`]: '\\f0d7',
[`euro`]: '\\f153',
[`folder-plus`]: '\\e921',
[`folder-minus`]: '\\e920',
[`file`]: '\\e90b',
[`file-text`]: '\\e924',
[`bug`]: '\\e90f',
[`stopwatch`]: '\\e914',
[`task`]: '\\e910',
[`story`]: '\\e911',
[`arrow-down`]: '\\e90a',
[`arrow-left-circle`]: '\\e917',
[`arrow-up`]: '\\e90b',
[`chevron-down`]: '\\e900',
[`chevron-left`]: '\\e901',
[`chevron-right`]: '\\e902',
[`chevron-up`]: '\\e903',
[`board`]: '\\e904',
[`help`]: '\\e905',
[`link`]: '\\e90c',
[`menu`]: '\\e916',
[`more`]: '\\e90e',
[`attach`]: '\\e90d',
[`plus`]: '\\e906',
[`search`]: '\\e907',
[`issues`]: '\\e908',
[`settings`]: '\\e909',
[`close`]: '\\e913',
[`help-filled`]: '\\e912',
[`feedback`]: '\\e915',
};
const propTypes = {

View File

@@ -5,29 +5,29 @@ import { color, font } from 'shared/utils/styles';
export default styled.div`
position: relative;
display: inline-block;
height: 40px;
height: 32px;
width: 100%;
input {
height: 100%;
width: 100%;
padding: 0 15px;
border-radius: 4px;
border: 1px solid ${color.borderLight};
box-shadow: inset 0 0 1px 0 rgba(0, 0, 0, 0.03);
background: #fff;
padding: 0 7px;
border-radius: 3px;
border: 1px solid ${color.borderLightest};
background: ${color.backgroundLightest};
${font.regular}
${font.size(14)}
${font.size(15)}
&:focus {
border: 1px solid ${color.borderMedium};
background: #fff;
border: 1px solid ${color.borderInputFocus};
box-shadow: 0 0 0 1px ${color.borderInputFocus};
}
${props => (props.icon ? 'padding-left: 40px;' : '')}
${props => (props.icon ? 'padding-left: 32px;' : '')}
${props => (props.invalid ? `&, &:focus { border: 1px solid ${color.danger}; }` : '')}
}
i {
position: absolute;
top: 12px;
left: 14px;
font-size: 16px;
top: 8px;
left: 8px;
pointer-events: none;
color: ${color.textMedium};
}

View File

@@ -25,12 +25,12 @@ const defaultProps = {
const Input = forwardRef(({ icon, className, invalid, filter, onChange, ...inputProps }, ref) => {
const handleChange = event => {
if (!filter || filter.test(event.target.value)) {
onChange(event, event.target.value);
onChange(event.target.value, event);
}
};
return (
<StyledInput className={className} icon={icon} invalid={invalid}>
{icon && <Icon type={icon} />}
{icon && <Icon type={icon} size={15} />}
<input {...inputProps} onChange={handleChange} ref={ref} />
</StyledInput>
);

View File

@@ -3,17 +3,17 @@ import PropTypes from 'prop-types';
const propTypes = {
className: PropTypes.string,
width: PropTypes.number,
size: PropTypes.number,
};
const defaultProps = {
className: undefined,
width: 28,
size: 28,
};
const Logo = ({ className, width }) => (
const Logo = ({ className, size }) => (
<span className={className}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75.76 75.76" width={width}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75.76 75.76" width={size}>
<defs>
<linearGradient
id="linear-gradient"

View File

@@ -15,7 +15,7 @@ export const ScrollOverlay = styled.div`
export const ClickableOverlay = styled.div`
min-height: 100%;
background: ${mixin.rgba(color.textLightBlue, 0.7)};
background: ${mixin.rgba(color.textLight, 0.7)};
${props => clickOverlayStyles[props.variant]}
`;

View File

@@ -0,0 +1,44 @@
import styled from 'styled-components';
import { Icon } from 'shared/components';
import { color, font, mixin } from 'shared/utils/styles';
import imageBackground from './assets/background-forest.jpg';
export const ErrorPage = styled.div`
padding: 64px;
`;
export const ErrorPageInner = styled.div`
margin: 0 auto;
max-width: 1440px;
padding: 200px 0;
${mixin.backgroundImage(imageBackground)}
@media (max-height: 680px) {
padding: 140px 0;
}
`;
export const ErrorBox = styled.div`
position: relative;
margin: 0 auto;
max-width: 480px;
padding: 32px;
border-radius: 3px;
border: 1px solid ${color.borderLight};
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
background: rgba(255, 255, 255, 0.9);
`;
export const StyledIcon = styled(Icon)`
position: absolute;
top: 32px;
left: 32px;
font-size: 30px;
color: ${color.primary};
`;
export const Title = styled.h1`
margin-bottom: 16px;
padding-left: 42px;
${font.size(29)}
`;

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { ErrorPage, ErrorPageInner, ErrorBox, StyledIcon, Title } from './Styles';
const PageError = () => (
<ErrorPage>
<ErrorPageInner>
<ErrorBox>
<StyledIcon type="bug" />
<Title>Theres been a glitch</Title>
<p>
{'Were not quite sure what went wrong. Please contact us or try looking on our '}
<a href="https://support.atlassian.com/jira-software-cloud/">Help Center</a>
{' if you need a hand.'}
</p>
</ErrorBox>
</ErrorPageInner>
</ErrorPage>
);
export default PageError;

View File

@@ -2,6 +2,6 @@ import styled from 'styled-components';
export default styled.div`
width: 100%;
padding: 100px;
padding-top: 200px;
text-align: center;
`;

View File

@@ -0,0 +1,120 @@
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
className: PropTypes.string,
size: PropTypes.number,
};
const defaultProps = {
className: undefined,
size: 40,
};
const Logo = ({ className, size }) => (
<span className={className}>
<svg
width={size}
height={size}
style={{ borderRadius: 3 }}
viewBox="0 0 128 128"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<rect id="path-1" x="0" y="0" width="128" height="128" />
</defs>
<g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="project_avatar_settings">
<g>
<mask id="mask-2" fill="white">
<use xlinkHref="#path-1" />
</mask>
<use id="Rectangle" fill="#FF5630" xlinkHref="#path-1" />
<g id="Settings" fillRule="nonzero">
<g transform="translate(20.000000, 17.000000)">
<path
d="M74.578,84.289 L72.42,84.289 C70.625,84.289 69.157,82.821 69.157,81.026 L69.157,16.537 C69.157,14.742 70.625,13.274 72.42,13.274 L74.578,13.274 C76.373,13.274 77.841,14.742 77.841,16.537 L77.841,81.026 C77.842,82.82 76.373,84.289 74.578,84.289 Z"
id="Shape"
fill="#2A5083"
/>
<path
d="M14.252,84.289 L12.094,84.289 C10.299,84.289 8.831,82.821 8.831,81.026 L8.831,16.537 C8.831,14.742 10.299,13.274 12.094,13.274 L14.252,13.274 C16.047,13.274 17.515,14.742 17.515,16.537 L17.515,81.026 C17.515,82.82 16.047,84.289 14.252,84.289 Z"
id="Shape"
fill="#2A5083"
/>
<rect
id="Rectangle-path"
fill="#153A56"
x="8.83"
y="51.311"
width="8.685"
height="7.763"
/>
<path
d="M13.173,53.776 L13.173,53.776 C6.342,53.776 0.753,48.187 0.753,41.356 L0.753,41.356 C0.753,34.525 6.342,28.936 13.173,28.936 L13.173,28.936 C20.004,28.936 25.593,34.525 25.593,41.356 L25.593,41.356 C25.593,48.187 20.004,53.776 13.173,53.776 Z"
id="Shape"
fill="#FFFFFF"
/>
<path
d="M18.021,43.881 L8.324,43.881 C7.453,43.881 6.741,43.169 6.741,42.298 L6.741,41.25 C6.741,40.379 7.453,39.667 8.324,39.667 L18.021,39.667 C18.892,39.667 19.604,40.379 19.604,41.25 L19.604,42.297 C19.605,43.168 18.892,43.881 18.021,43.881 Z"
id="Shape"
fill="#2A5083"
opacity="0.2"
/>
<rect
id="Rectangle-path"
fill="#153A56"
x="69.157"
y="68.307"
width="8.685"
height="7.763"
/>
<path
d="M73.499,70.773 L73.499,70.773 C66.668,70.773 61.079,65.184 61.079,58.353 L61.079,58.353 C61.079,51.522 66.668,45.933 73.499,45.933 L73.499,45.933 C80.33,45.933 85.919,51.522 85.919,58.353 L85.919,58.353 C85.919,65.183 80.33,70.773 73.499,70.773 Z"
id="Shape"
fill="#FFFFFF"
/>
<path
d="M78.348,60.877 L68.651,60.877 C67.78,60.877 67.068,60.165 67.068,59.294 L67.068,58.247 C67.068,57.376 67.781,56.664 68.651,56.664 L78.348,56.664 C79.219,56.664 79.931,57.377 79.931,58.247 L79.931,59.294 C79.931,60.165 79.219,60.877 78.348,60.877 Z"
id="Shape"
fill="#2A5083"
opacity="0.2"
/>
<path
d="M44.415,84.289 L42.257,84.289 C40.462,84.289 38.994,82.821 38.994,81.026 L38.994,16.537 C38.994,14.742 40.462,13.274 42.257,13.274 L44.415,13.274 C46.21,13.274 47.678,14.742 47.678,16.537 L47.678,81.026 C47.678,82.82 46.21,84.289 44.415,84.289 Z"
id="Shape"
fill="#2A5083"
/>
<rect
id="Rectangle-path"
fill="#153A56"
x="38.974"
y="23.055"
width="8.685"
height="7.763"
/>
<path
d="M43.316,25.521 L43.316,25.521 C36.485,25.521 30.896,19.932 30.896,13.101 L30.896,13.101 C30.896,6.27 36.485,0.681 43.316,0.681 L43.316,0.681 C50.147,0.681 55.736,6.27 55.736,13.101 L55.736,13.101 C55.736,19.932 50.147,25.521 43.316,25.521 Z"
id="Shape"
fill="#FFFFFF"
/>
<path
d="M48.165,15.626 L38.468,15.626 C37.597,15.626 36.885,14.914 36.885,14.043 L36.885,12.996 C36.885,12.125 37.597,11.413 38.468,11.413 L48.165,11.413 C49.036,11.413 49.748,12.125 49.748,12.996 L49.748,14.043 C49.748,14.913 49.036,15.626 48.165,15.626 Z"
id="Shape"
fill="#2A5083"
opacity="0.2"
/>
</g>
</g>
</g>
</g>
</g>
</svg>
</span>
);
Logo.propTypes = propTypes;
Logo.defaultProps = defaultProps;
export default Logo;

View File

@@ -64,11 +64,11 @@ const SelectDropdown = ({
};
const handleInputKeyDown = event => {
if (event.keyCode === KeyCodes.escape) {
if (event.keyCode === KeyCodes.ESCAPE) {
handleInputEscapeKeyDown(event);
} else if (event.keyCode === KeyCodes.enter) {
} else if (event.keyCode === KeyCodes.ENTER) {
handleInputEnterKeyDown(event);
} else if (event.keyCode === KeyCodes.arrowDown || event.keyCode === KeyCodes.arrowUp) {
} else if (event.keyCode === KeyCodes.ARROW_DOWN || event.keyCode === KeyCodes.ARROW_UP) {
handleInputArrowUpOrDownKeyDown(event);
}
};
@@ -101,7 +101,7 @@ const SelectDropdown = ({
const $optionsHeight = $options.getBoundingClientRect().height;
const $activeHeight = $active.getBoundingClientRect().height;
if (event.keyCode === KeyCodes.arrowDown) {
if (event.keyCode === KeyCodes.ARROW_DOWN) {
if ($options.lastElementChild === $active) {
$active.classList.remove(activeOptionClass);
$options.firstElementChild.classList.add(activeOptionClass);
@@ -113,7 +113,7 @@ const SelectDropdown = ({
$options.scrollTop += $activeHeight;
}
}
} else if (event.keyCode === KeyCodes.arrowUp) {
} else if (event.keyCode === KeyCodes.ARROW_UP) {
if ($options.firstElementChild === $active) {
$active.classList.remove(activeOptionClass);
$options.lastElementChild.classList.add(activeOptionClass);

View File

@@ -6,7 +6,7 @@ import Icon from 'shared/components/Icon';
export const StyledSelect = styled.div`
position: relative;
width: 100%;
border-radius: 4px;
border-radius: 3px;
border: 1px solid ${color.borderLight};
background: #fff;
${font.size(14)}
@@ -41,7 +41,7 @@ export const ChevronIcon = styled(Icon)`
export const Placeholder = styled.div`
padding: 11px 0 0 15px;
color: ${color.textLightBlue};
color: ${color.textLight};
`;
export const ValueSingle = styled.div`

View File

@@ -93,10 +93,10 @@ const Select = ({
const handleFocusedSelectKeydown = event => {
if (isDropdownOpen) return;
if (event.keyCode === KeyCodes.enter) {
if (event.keyCode === KeyCodes.ENTER) {
event.preventDefault();
}
if (event.keyCode !== KeyCodes.escape && event.keyCode !== KeyCodes.tab && !event.shiftKey) {
if (event.keyCode !== KeyCodes.ESCAPE && event.keyCode !== KeyCodes.TAB && !event.shiftKey) {
setDropdownOpen(true);
}
};

View File

@@ -6,17 +6,18 @@ export default styled.div`
display: inline-block;
width: 100%;
textarea {
width: 100%;
padding: 13px 15px 14px;
border-radius: 4px;
border: 1px solid ${color.borderLight};
box-shadow: inset 0 0 1px 0 rgba(0, 0, 0, 0.03);
background: #fff;
overflow-y: hidden;
width: 100%;
padding: 6px 7px 7px;
border-radius: 3px;
border: 1px solid ${color.borderLightest};
background: ${color.backgroundLightest};
${font.regular}
${font.size(14)}
${font.size(15)}
&:focus {
border: 1px solid ${color.borderMedium};
background: #fff;
border: 1px solid ${color.borderInputFocus};
box-shadow: 0 0 0 1px ${color.borderInputFocus};
}
${props => (props.invalid ? `&, &:focus { border: 1px solid ${color.danger}; }` : '')}
}

View File

@@ -24,7 +24,7 @@ const Textarea = forwardRef(({ className, invalid, onChange, ...textareaProps },
<StyledTextarea className={className} invalid={invalid}>
<TextareaAutoSize
{...textareaProps}
onChange={event => onChange(event, event.target.value)}
onChange={event => onChange(event.target.value, event)}
ref={ref}
/>
</StyledTextarea>

View File

@@ -6,7 +6,9 @@ export { default as Icon } from './Icon';
export { default as Input } from './Input';
export { default as Logo } from './Logo';
export { default as Modal } from './Modal';
export { default as PageError } from './PageError';
export { default as PageLoader } from './PageLoader';
export { default as ProjectAvatar } from './ProjectAvatar';
export { default as Select } from './Select';
export { default as Spinner } from './Spinner';
export { default as Textarea } from './Textarea';