feat: add comments badge to task card
This commit is contained in:
@@ -1,18 +1,27 @@
|
||||
import styled, { css, keyframes } from 'styled-components';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
import TextareaAutosize from 'react-autosize-textarea';
|
||||
import { CheckCircle, CheckSquareOutline, Clock } from 'shared/icons';
|
||||
import { CheckCircle, CheckSquareOutline, Clock, Bubble } from 'shared/icons';
|
||||
import TaskAssignee from 'shared/components/TaskAssignee';
|
||||
|
||||
export const CardMember = styled(TaskAssignee)<{ zIndex: number }>`
|
||||
box-shadow: 0 0 0 2px ${props => props.theme.colors.bg.secondary},
|
||||
inset 0 0 0 1px ${props => mixin.rgba(props.theme.colors.bg.secondary, 0.07)};
|
||||
z-index: ${props => props.zIndex};
|
||||
box-shadow: 0 0 0 2px ${(props) => props.theme.colors.bg.secondary},
|
||||
inset 0 0 0 1px ${(props) => mixin.rgba(props.theme.colors.bg.secondary, 0.07)};
|
||||
z-index: ${(props) => props.zIndex};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const CommentsIcon = styled(Bubble)<{ color: 'success' | 'normal' }>`
|
||||
${(props) =>
|
||||
props.color === 'success' &&
|
||||
css`
|
||||
fill: ${props.theme.colors.success};
|
||||
stroke: ${props.theme.colors.success};
|
||||
`}
|
||||
`;
|
||||
|
||||
export const ChecklistIcon = styled(CheckSquareOutline)<{ color: 'success' | 'normal' }>`
|
||||
${props =>
|
||||
${(props) =>
|
||||
props.color === 'success' &&
|
||||
css`
|
||||
fill: ${props.theme.colors.success};
|
||||
@@ -21,7 +30,7 @@ export const ChecklistIcon = styled(CheckSquareOutline)<{ color: 'success' | 'no
|
||||
`;
|
||||
|
||||
export const ClockIcon = styled(Clock)<{ color: string }>`
|
||||
fill: ${props => props.color};
|
||||
fill: ${(props) => props.color};
|
||||
`;
|
||||
|
||||
export const EditorTextarea = styled(TextareaAutosize)`
|
||||
@@ -40,7 +49,7 @@ export const EditorTextarea = styled(TextareaAutosize)`
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
&:focus {
|
||||
border: none;
|
||||
outline: none;
|
||||
@@ -54,6 +63,22 @@ export const ListCardBadges = styled.div`
|
||||
margin-left: -2px;
|
||||
`;
|
||||
|
||||
export const CommentsBadge = styled.div`
|
||||
color: #5e6c84;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 6px 4px 0;
|
||||
font-size: 12px;
|
||||
max-width: 100%;
|
||||
min-height: 20px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding: 2px;
|
||||
text-decoration: none;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
`;
|
||||
|
||||
export const ListCardBadge = styled.div`
|
||||
color: #5e6c84;
|
||||
display: flex;
|
||||
@@ -76,7 +101,7 @@ export const DescriptionBadge = styled(ListCardBadge)`
|
||||
|
||||
export const DueDateCardBadge = styled(ListCardBadge)<{ isPastDue: boolean }>`
|
||||
font-size: 12px;
|
||||
${props =>
|
||||
${(props) =>
|
||||
props.isPastDue &&
|
||||
css`
|
||||
padding-left: 4px;
|
||||
@@ -91,7 +116,7 @@ export const ListCardBadgeText = styled.span<{ color?: 'success' | 'normal' }>`
|
||||
padding: 0 4px 0 6px;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
${props => props.color === 'success' && `color: ${props.theme.colors.success};`}
|
||||
${(props) => props.color === 'success' && `color: ${props.theme.colors.success};`}
|
||||
`;
|
||||
|
||||
export const ListCardContainer = styled.div<{ isActive: boolean; editable: boolean }>`
|
||||
@@ -102,7 +127,7 @@ export const ListCardContainer = styled.div<{ isActive: boolean; editable: boole
|
||||
cursor: pointer !important;
|
||||
position: relative;
|
||||
|
||||
background-color: ${props =>
|
||||
background-color: ${(props) =>
|
||||
props.isActive && !props.editable
|
||||
? mixin.darken(props.theme.colors.bg.secondary, 0.1)
|
||||
: `${props.theme.colors.bg.secondary}`};
|
||||
@@ -119,7 +144,7 @@ export const ListCardDetails = styled.div<{ complete: boolean }>`
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
${props => props.complete && 'opacity: 0.6;'}
|
||||
${(props) => props.complete && 'opacity: 0.6;'}
|
||||
`;
|
||||
|
||||
const labelVariantExpandAnimation = keyframes`
|
||||
@@ -157,7 +182,7 @@ export const ListCardLabelsWrapper = styled.div`
|
||||
`;
|
||||
|
||||
export const ListCardLabel = styled.span<{ variant: 'small' | 'large' }>`
|
||||
${props =>
|
||||
${(props) =>
|
||||
props.variant === 'small'
|
||||
? css`
|
||||
height: 8px;
|
||||
@@ -183,14 +208,14 @@ export const ListCardLabel = styled.span<{ variant: 'small' | 'large' }>`
|
||||
color: #fff;
|
||||
display: flex;
|
||||
position: relative;
|
||||
background-color: ${props => props.color};
|
||||
background-color: ${(props) => props.color};
|
||||
`;
|
||||
|
||||
export const ListCardLabels = styled.div<{ toggleLabels: boolean; toggleDirection: 'expand' | 'shrink' }>`
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
${props =>
|
||||
${(props) =>
|
||||
props.toggleLabels &&
|
||||
props.toggleDirection === 'expand' &&
|
||||
css`
|
||||
@@ -201,7 +226,7 @@ export const ListCardLabels = styled.div<{ toggleLabels: boolean; toggleDirectio
|
||||
animation: ${labelTextVariantExpandAnimation} 0.45s ease-out;
|
||||
}
|
||||
`}
|
||||
${props =>
|
||||
${(props) =>
|
||||
props.toggleLabels &&
|
||||
props.toggleDirection === 'shrink' &&
|
||||
css`
|
||||
@@ -225,7 +250,7 @@ export const ListCardOperation = styled.span`
|
||||
top: 2px;
|
||||
z-index: 100;
|
||||
&:hover {
|
||||
background-color: ${props => mixin.darken(props.theme.colors.bg.secondary, 0.25)};
|
||||
background-color: ${(props) => mixin.darken(props.theme.colors.bg.secondary, 0.25)};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -234,7 +259,7 @@ export const CardTitle = styled.div`
|
||||
margin: 0 0 4px;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
display: block;
|
||||
align-items: center;
|
||||
`;
|
||||
@@ -251,7 +276,7 @@ export const CardMembers = styled.div`
|
||||
`;
|
||||
|
||||
export const CompleteIcon = styled(CheckCircle)`
|
||||
fill: ${props => props.theme.colors.success};
|
||||
fill: ${(props) => props.theme.colors.success};
|
||||
margin-right: 4px;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: -2px;
|
||||
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
CardTitle,
|
||||
CardMembers,
|
||||
CardTitleText,
|
||||
CommentsIcon,
|
||||
CommentsBadge,
|
||||
} from './Styles';
|
||||
|
||||
type DueDate = {
|
||||
@@ -47,6 +49,7 @@ type Props = {
|
||||
dueDate?: DueDate;
|
||||
checklists?: Checklist | null;
|
||||
labels?: Array<ProjectLabel>;
|
||||
comments?: { unread: boolean; total: number } | null;
|
||||
watched?: boolean;
|
||||
wrapperProps?: any;
|
||||
members?: Array<TaskUser> | null;
|
||||
@@ -72,6 +75,7 @@ const Card = React.forwardRef(
|
||||
taskGroupID,
|
||||
complete,
|
||||
toggleLabels = false,
|
||||
comments,
|
||||
toggleDirection = 'shrink',
|
||||
setToggleLabels,
|
||||
onClick,
|
||||
@@ -138,7 +142,7 @@ const Card = React.forwardRef(
|
||||
onMouseEnter={() => setActive(true)}
|
||||
onMouseLeave={() => setActive(false)}
|
||||
ref={$cardRef}
|
||||
onClick={e => {
|
||||
onClick={(e) => {
|
||||
if (onClick) {
|
||||
onClick(e);
|
||||
}
|
||||
@@ -151,7 +155,7 @@ const Card = React.forwardRef(
|
||||
<ListCardInnerContainer ref={$innerCardRef}>
|
||||
{!isPublic && isActive && !editable && (
|
||||
<ListCardOperation
|
||||
onClick={e => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onContextMenu) {
|
||||
onContextMenu($innerCardRef, taskID, taskGroupID);
|
||||
@@ -167,7 +171,7 @@ const Card = React.forwardRef(
|
||||
<ListCardLabels
|
||||
toggleLabels={toggleLabels}
|
||||
toggleDirection={toggleDirection}
|
||||
onClick={e => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onCardLabelClick) {
|
||||
onCardLabelClick();
|
||||
@@ -177,7 +181,7 @@ const Card = React.forwardRef(
|
||||
{labels
|
||||
.slice()
|
||||
.sort((a, b) => a.labelColor.position - b.labelColor.position)
|
||||
.map(label => (
|
||||
.map((label) => (
|
||||
<ListCardLabel
|
||||
onAnimationEnd={() => {
|
||||
if (setToggleLabels) {
|
||||
@@ -198,13 +202,13 @@ const Card = React.forwardRef(
|
||||
<EditorContent>
|
||||
{complete && <CompleteIcon width={16} height={16} />}
|
||||
<EditorTextarea
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setCardTitle(e.currentTarget.value);
|
||||
if (onCardTitleChange) {
|
||||
onCardTitleChange(e.currentTarget.value);
|
||||
}
|
||||
}}
|
||||
onClick={e => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
@@ -235,6 +239,12 @@ const Card = React.forwardRef(
|
||||
<List width={8} height={8} />
|
||||
</DescriptionBadge>
|
||||
)}
|
||||
{comments && (
|
||||
<CommentsBadge>
|
||||
<CommentsIcon color={comments.unread ? 'success' : 'normal'} width={8} height={8} />
|
||||
<ListCardBadgeText color={comments.unread ? 'success' : 'normal'}>{comments.total}</ListCardBadgeText>
|
||||
</CommentsBadge>
|
||||
)}
|
||||
{checklists && (
|
||||
<ListCardBadge>
|
||||
<ChecklistIcon
|
||||
@@ -256,7 +266,7 @@ const Card = React.forwardRef(
|
||||
size={28}
|
||||
zIndex={members.length - idx}
|
||||
member={member}
|
||||
onMemberProfile={$target => {
|
||||
onMemberProfile={($target) => {
|
||||
if (onCardMemberClick) {
|
||||
onCardMemberClick($target, taskID, member.id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user