Skip to content

Commit

Permalink
[CORL 1010] Collapse comment threads (#2976)
Browse files Browse the repository at this point in the history
* toggle open and closed comment + replies

* show username and date for collapsed comment

* toggle comments and replies open and closed

* keep collapsed state of comments in reply list

* style collapsed comment toggle

* add css class overrides

* style answered comments when collapsed

* adjust spacing on collapse button:

* add hover styles

* add aria labels and update snapshots
  • Loading branch information
tessalt authored and nick-funk committed Jun 12, 2020
1 parent 4eaf0ec commit e543a8a
Show file tree
Hide file tree
Showing 43 changed files with 8,588 additions and 6,500 deletions.
10 changes: 10 additions & 0 deletions src/core/client/stream/classes.ts
Expand Up @@ -279,6 +279,16 @@ const CLASSES = {
*/
reacted: "coral coral-reacted",

/**
* collapseToggle is the button to collapse and expand the display of a comment.
*/
collapseToggle: {
$root: "coral coral-comment-collapse-toggle",
icon: "coral coral-comment-collapse-toggle-icon",
indent: "coral coral-comment-collapse-toggle-indent",
collapsed: "coral coral-comment-collapse-toggle-collapsed",
},

/**
* topBar is the uppper bar of the comment.
*/
Expand Down
16 changes: 16 additions & 0 deletions src/core/client/stream/tabs/Comments/Comment/AnsweredTag.css
@@ -0,0 +1,16 @@
.answeredTag {
color: #03ab61;
background: #e6faf1;

border-color: #03ab61;
border-style: solid;
border-radius: 2px;
border-width: 1px;

text-transform: uppercase;
}

.tagIcon {
margin-right: var(--v2-spacing-1);
color: var(--v2-colors-green-500);
}
27 changes: 27 additions & 0 deletions src/core/client/stream/tabs/Comments/Comment/AnsweredTag.tsx
@@ -0,0 +1,27 @@
import { Localized } from "@fluent/react/compat";
import React, { FunctionComponent } from "react";

import { Flex, Icon, Tag } from "coral-ui/components/v2";

import styles from "./AnsweredTag.css";

interface Props {
collapsed?: boolean;
}

const AnsweredTag: FunctionComponent<Props> = ({ collapsed }) => {
return collapsed ? (
<Icon className={styles.tagIcon}>check</Icon>
) : (
<Tag variant="regular" color="primary" className={styles.answeredTag}>
<Flex alignItems="center">
<Icon size="xs" className={styles.tagIcon}>
check
</Icon>
<Localized id="qa-answered-tag">answered</Localized>
</Flex>
</Tag>
);
};

export default AnsweredTag;
@@ -0,0 +1,20 @@
import { FunctionComponent, ReactElement, useCallback, useState } from "react";

interface InjectedCollapsableCommentProps {
collapsed: boolean;
toggleCollapsed: () => void;
}

interface Props {
children(props: InjectedCollapsableCommentProps): ReactElement;
}

const CollapsableComment: FunctionComponent<Props> = ({ children }) => {
const [collapsed, setCollapsed] = useState<boolean>(false);
const toggleCollapsed = useCallback(() => {
setCollapsed(!collapsed);
}, [collapsed]);
return children({ collapsed, toggleCollapsed });
};

export default CollapsableComment;
1 change: 1 addition & 0 deletions src/core/client/stream/tabs/Comments/Comment/Comment.css
@@ -1,6 +1,7 @@
$commentTimestampColor: var(--v2-colors-grey-500);

.root {
flex: 1;
}

.highlight {
Expand Down
3 changes: 2 additions & 1 deletion src/core/client/stream/tabs/Comments/Comment/Comment.tsx
Expand Up @@ -23,6 +23,7 @@ export interface CommentProps {
highlight?: boolean;
parentAuthorName?: string | null;
userTags?: React.ReactNode;
collapsed?: boolean;
}

const Comment: FunctionComponent<CommentProps> = (props) => {
Expand Down Expand Up @@ -57,12 +58,12 @@ const Comment: FunctionComponent<CommentProps> = (props) => {
</TopBarLeft>
{props.topBarRight && <div>{props.topBarRight}</div>}
</Flex>

{props.parentAuthorName && (
<div className={styles.subBar}>
<InReplyTo username={props.parentAuthorName} />
</div>
)}

<HorizontalGutter size="oneAndAHalf">
<HTMLContent className={CLASSES.comment.content}>
{props.body || ""}
Expand Down
Expand Up @@ -6,10 +6,10 @@ $commenterActionEditColorActive: var(--v2-colors-stream-blue-300);
}

.answeredTag {
color: #03AB61;
background: #E6FAF1;
color: #03ab61;
background: #e6faf1;

border-color: #03AB61;
border-color: #03ab61;
border-style: solid;
border-radius: 2px;
border-width: 1px;
Expand Down Expand Up @@ -44,3 +44,7 @@ $commenterActionEditColorActive: var(--v2-colors-stream-blue-300);
.actionButton {
padding: var(--v2-spacing-1);
}

.staticUsername {
color: var(--v2-colors-mono-100);
}
Expand Up @@ -66,6 +66,8 @@ function createDefaultProps(add: DeepPartial<Props> = {}): Props {
localReply: false,
disableReplies: false,
onRemoveAnswered: undefined,
collapsed: undefined,
toggleCollapsed: noop as any,
},
add
);
Expand Down
61 changes: 36 additions & 25 deletions src/core/client/stream/tabs/Comments/Comment/CommentContainer.tsx
Expand Up @@ -23,23 +23,19 @@ import {
withShowAuthPopupMutation,
} from "coral-stream/mutations";
import { Ability, can } from "coral-stream/permissions";
import {
Button,
Flex,
HorizontalGutter,
Icon,
Tag,
} from "coral-ui/components/v2";
import { Button, Flex, HorizontalGutter, Icon } from "coral-ui/components/v2";

import { CommentContainer_comment as CommentData } from "coral-stream/__generated__/CommentContainer_comment.graphql";
import { CommentContainer_settings as SettingsData } from "coral-stream/__generated__/CommentContainer_settings.graphql";
import { CommentContainer_story as StoryData } from "coral-stream/__generated__/CommentContainer_story.graphql";
import { CommentContainer_viewer as ViewerData } from "coral-stream/__generated__/CommentContainer_viewer.graphql";

import { isPublished } from "../helpers";
import AnsweredTag from "./AnsweredTag";
import UserBadgesContainer from "./AuthorBadgesContainer";
import ButtonsBar from "./ButtonsBar";
import EditCommentFormContainer from "./EditCommentForm";
import FeaturedTag from "./FeaturedTag";
import IndentedComment from "./IndentedComment";
import CaretContainer, {
RejectedTombstoneContainer,
Expand All @@ -50,7 +46,7 @@ import ReplyButton from "./ReplyButton";
import ReplyCommentFormContainer from "./ReplyCommentForm";
import ReportFlowContainer, { ReportButton } from "./ReportFlow";
import ShowConversationLink from "./ShowConversationLink";
import { UsernameWithPopoverContainer } from "./Username";
import { UsernameContainer, UsernameWithPopoverContainer } from "./Username";
import UserTagsContainer from "./UserTagsContainer";

import styles from "./CommentContainer.css";
Expand Down Expand Up @@ -80,6 +76,8 @@ interface Props {
hideReportButton?: boolean;
hideModerationCarat?: boolean;
onRemoveAnswered?: () => void;
collapsed?: boolean;
toggleCollapsed?: () => void;
}

interface State {
Expand Down Expand Up @@ -219,6 +217,7 @@ export class CommentContainer extends Component<Props, State> {
viewer,
className,
hideAnsweredTag,
collapsed,
} = this.props;
const { showReplyDialog, showEditDialog, editable } = this.state;
const hasFeaturedTag = Boolean(
Expand Down Expand Up @@ -252,25 +251,10 @@ export class CommentContainer extends Component<Props, State> {
const commentTags = (
<>
{hasFeaturedTag && !isQA && (
<Tag
className={CLASSES.comment.topBar.featuredTag}
color="streamBlue"
variant="pill"
>
<Localized id="comments-featuredTag">
<span>Featured</span>
</Localized>
</Tag>
<FeaturedTag collapsed={this.props.collapsed} />
)}
{hasAnsweredTag && isQA && (
<Tag variant="regular" color="primary" className={styles.answeredTag}>
<Flex alignItems="center">
<Icon size="xs" className={styles.tagIcon}>
check
</Icon>
<Localized id="qa-answered-tag">answered</Localized>
</Flex>
</Tag>
<AnsweredTag collapsed={this.props.collapsed} />
)}
</>
);
Expand Down Expand Up @@ -326,16 +310,41 @@ export class CommentContainer extends Component<Props, State> {
{!comment.deleted && (
<IndentedComment
indentLevel={indentLevel}
collapsed={collapsed}
body={comment.body}
createdAt={comment.createdAt}
blur={comment.pending || false}
showEditedMarker={comment.editing.edited}
highlight={highlight}
toggleCollapsed={this.props.toggleCollapsed}
parentAuthorName={
comment.parent &&
comment.parent.author &&
comment.parent.author.username
}
staticUsername={
comment.author && (
<>
<UsernameContainer
className={cn(
styles.staticUsername,
CLASSES.comment.topBar.username
)}
comment={comment}
/>
<UserTagsContainer
className={CLASSES.comment.topBar.userTag}
story={story}
comment={comment}
settings={settings}
/>
<UserBadgesContainer
className={CLASSES.comment.topBar.userBadge}
comment={comment}
/>
</>
)
}
username={
comment.author && (
<>
Expand All @@ -357,6 +366,7 @@ export class CommentContainer extends Component<Props, State> {
</>
)
}
staticTopBarRight={commentTags}
topBarRight={
<Flex alignItems="center" itemGutter>
{commentTags}
Expand Down Expand Up @@ -581,6 +591,7 @@ const enhanced = withContext(({ eventEmitter }) => ({ eventEmitter }))(
...AuthorBadgesContainer_comment
...UserTagsContainer_comment
...UsernameWithPopoverContainer_comment
...UsernameContainer_comment
}
`,
settings: graphql`
Expand Down
27 changes: 27 additions & 0 deletions src/core/client/stream/tabs/Comments/Comment/CommentToggle.css
@@ -0,0 +1,27 @@
.root {
padding: var(--v2-spacing-2);
width: 100%;
background: var(--v2-colors-grey-100);

&.mouseHover,
&:hover {
background: var(--v2-colors-grey-200);
}
}

.icon {
color: var(--v2-colors-grey-500);
}

.timestamp {
font-family: var(--v2-font-family-primary);
font-style: normal;
font-weight: var(--v2-font-weight-primary-semi-bold);
font-size: var(--v2-font-size-2);
line-height: var(--v2-line-height-3);
color: var(--v2-colors-grey-500);
}

.inner {
flex: 1;
}
67 changes: 67 additions & 0 deletions src/core/client/stream/tabs/Comments/Comment/CommentToggle.tsx
@@ -0,0 +1,67 @@
import cn from "classnames";
import React, { FunctionComponent } from "react";

import CLASSES from "coral-stream/classes";
import { BaseButton, Flex, Icon, RelativeTime } from "coral-ui/components/v2";

import EditedMarker from "./EditedMarker";
import TopBarLeft from "./TopBarLeft";

import styles from "./CommentToggle.css";

export interface Props {
className?: string;
username: React.ReactNode;
body: string | null;
createdAt: string;
topBarRight?: React.ReactNode;
footer?: React.ReactNode;
showEditedMarker?: boolean;
highlight?: boolean;
parentAuthorName?: string | null;
userTags?: React.ReactNode;
collapsed?: boolean;
toggleCollapsed?: () => void;
}

const CommentToggle: FunctionComponent<Props> = (props) => {
return (
<BaseButton
onClick={props.toggleCollapsed}
className={cn(styles.root, CLASSES.comment.collapseToggle.$root)}
>
<Flex alignItems="center" spacing={1}>
<Icon className={cn(styles.icon, CLASSES.comment.collapseToggle.icon)}>
add
</Icon>
<Flex
direction="row"
justifyContent="space-between"
className={cn(styles.inner, CLASSES.comment.topBar.$root)}
>
<TopBarLeft>
<Flex direction="row" alignItems="center" itemGutter="half">
{props.username && props.username}
{props.userTags}
</Flex>
<Flex direction="row" alignItems="baseline" itemGutter>
<RelativeTime
className={cn(
styles.timestamp,
CLASSES.comment.topBar.timestamp
)}
date={props.createdAt}
/>
{props.showEditedMarker && (
<EditedMarker className={CLASSES.comment.topBar.edited} />
)}
</Flex>
</TopBarLeft>
{props.topBarRight && <div>{props.topBarRight}</div>}
</Flex>
</Flex>
</BaseButton>
);
};

export default CommentToggle;
3 changes: 3 additions & 0 deletions src/core/client/stream/tabs/Comments/Comment/FeaturedTag.css
@@ -0,0 +1,3 @@
.root {
color: var(--v2-colors-stream-blue-500);
}

0 comments on commit e543a8a

Please sign in to comment.