[CORL-916] conversation view (#2850)

* show comment counts for stories in story table

* remove debug code

* add modal to view comment

* load converasation view into modal

* style modal header

* style conversation view modal

* style conversation thread in conversation modal

* styles conversation view comments

* show replies to replies on click

* update snaps

* update snap

* pass settings prop through to comment content, set default replies to 5
This commit is contained in:
Tessa Thornton
2020-03-26 13:47:16 -04:00
committed by GitHub
parent 8fd266deeb
commit 58f2f71743
53 changed files with 1509 additions and 278 deletions
@@ -0,0 +1,21 @@
$moderateCardUsernameBackgroundHover: var(--v2-colors-grey-200);
.root {
margin-right: var(--mini-unit);
padding: var(--spacing-1);
margin-left: calc(-1 * var(--v2-spacing-1));
line-height: var(--v2-line-height-reset);
&:hover {
background-color: $moderateCardUsernameBackgroundHover;
border-radius: var(--v2-round-corners);
border-style: none;
}
&:active {
background-color: $moderateCardUsernameBackgroundHover;
border-radius: var(--v2-round-corners);
border-style: none;
}
}
@@ -0,0 +1,27 @@
import cn from "classnames";
import React, { FunctionComponent } from "react";
import { BaseButton } from "coral-ui/components/v2";
import Username from "./Username";
import styles from "./UsernameButton.css";
interface Props {
username: string;
className?: string;
onClick: () => void;
}
const UsernameButton: FunctionComponent<Props> = ({
username,
onClick,
className,
}) => {
return (
<BaseButton onClick={onClick} className={cn(styles.root, className)}>
<Username>{username}</Username>
</BaseButton>
);
};
export default UsernameButton;
@@ -0,0 +1,4 @@
export { default as CommentContent } from "./CommentContent";
export { default as InReplyTo } from "./InReplyTo";
export { default as Username } from "./Username";
export { default as UsernameButton } from "./UsernameButton";
@@ -0,0 +1,38 @@
import React, { FunctionComponent } from "react";
import { Modal } from "coral-ui/components";
import ConversationModalQuery from "./ConversationModalQuery";
interface ConversationModalProps {
open: boolean;
onClose: () => void;
commentID?: string;
onUsernameClicked: (userID: string) => void;
}
const ConversationModal: FunctionComponent<ConversationModalProps> = ({
open,
onClose,
commentID,
onUsernameClicked,
}) => {
if (!commentID) {
return null;
}
return (
<Modal open={open} onClose={onClose}>
{({ firstFocusableRef, lastFocusableRef }) => (
<ConversationModalQuery
commentID={commentID}
onClose={onClose}
firstFocusableRef={firstFocusableRef}
lastFocusableRef={lastFocusableRef}
onUsernameClicked={onUsernameClicked}
/>
)}
</Modal>
);
};
export default ConversationModal;
@@ -0,0 +1,37 @@
$conversationModalHighlightBackground: var(--v2-colors-teal-100);
$conversationModalCommentText: var(--v2-colors-mono-500);
.root {
flex: 1;
}
.line {
border-color: var(--v2-colors-grey-400);
}
.adornments {
margin-right: var(--v2-spacing-2);
}
.circle {
margin-top: var(--v2-spacing-2);
margin-bottom: var(--v2-spacing-2);
}
.highlightedCircle {
margin-top: var(--v2-spacing-1);
}
.highlighted {
background-color: $conversationModalHighlightBackground;
padding: var(--v2-spacing-2);
padding-top: var(--v2-spacing-1);
margin-bottom: var(--v2-spacing-2);
}
.commentText {
color: $conversationModalCommentText;
}
.showReplies {
padding-left: var(--v2-spacing-2);
}
@@ -0,0 +1,160 @@
import { Localized } from "@fluent/react/compat";
import cn from "classnames";
import React, { FunctionComponent, useCallback, useState } from "react";
import { graphql, withFragmentContainer } from "coral-framework/lib/relay";
import {
Button,
Flex,
HorizontalGutter,
Timestamp,
} from "coral-ui/components/v2";
import ConversationModalRepliesQuery from "./ConversationModalRepliesQuery";
import { ConversationModalCommentContainer_comment } from "coral-admin/__generated__/ConversationModalCommentContainer_comment.graphql";
import { ConversationModalCommentContainer_settings } from "coral-admin/__generated__/ConversationModalCommentContainer_settings.graphql";
import { CommentContent, InReplyTo, UsernameButton } from "../Comment";
import { Circle, Line } from "../Timeline";
import styles from "./ConversationModalCommentContainer.css";
interface Props {
comment: ConversationModalCommentContainer_comment;
isHighlighted: boolean;
onUsernameClick: (id?: string) => void;
isParent?: boolean;
isReply?: boolean;
settings: ConversationModalCommentContainer_settings;
}
const ConversationModalCommentContainer: FunctionComponent<Props> = ({
comment,
isHighlighted,
isParent,
isReply,
onUsernameClick,
settings,
}) => {
const commentAuthorClick = useCallback(() => {
if (comment.author) {
onUsernameClick(comment.author.id);
}
}, [onUsernameClick, comment.author]);
const commentParentAuthorClick = useCallback(() => {
if (comment.parent && comment.parent.author) {
onUsernameClick(comment.parent.author.id);
}
}, [onUsernameClick, comment.parent]);
const [showReplies, setShowReplies] = useState(false);
const onShowReplies = useCallback(() => {
setShowReplies(true);
}, []);
return (
<HorizontalGutter>
<Flex>
<Flex
direction="column"
alignItems="center"
className={cn(styles.adornments, {
[styles.highlightedCircle]: isHighlighted,
})}
>
{!isReply && <Circle className={styles.circle} />}
{(isParent || isReply) && <Line className={styles.line} />}
</Flex>
<HorizontalGutter
spacing={1}
className={cn(styles.root, {
[styles.highlighted]: isHighlighted,
})}
>
<div>
<Flex alignItems="center">
{comment.author && comment.author.username && (
<UsernameButton
onClick={commentAuthorClick}
username={comment.author.username}
/>
)}
<Timestamp>{comment.createdAt}</Timestamp>
</Flex>
{comment.parent &&
comment.parent.author &&
comment.parent.author.username && (
<InReplyTo onUsernameClick={commentParentAuthorClick}>
{comment.parent.author.username}
</InReplyTo>
)}
</div>
<div>
{comment.body && (
<CommentContent className={styles.commentText} phrases={settings}>
{comment.body}
</CommentContent>
)}
</div>
</HorizontalGutter>
</Flex>
{isReply && comment.replyCount > 0 && (
<div className={styles.showReplies}>
{!showReplies && (
<Localized id="conversation-modal-reply-show-replies">
<Button
color="mono"
variant="outline"
onClick={onShowReplies}
fullWidth
>
Show replies
</Button>
</Localized>
)}
{showReplies && (
<ConversationModalRepliesQuery
onUsernameClicked={onUsernameClick}
commentID={comment.id}
/>
)}
</div>
)}
</HorizontalGutter>
);
};
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment ConversationModalCommentContainer_settings on Settings {
locale
wordList {
banned
suspect
}
multisite
featureFlags
...MarkersContainer_settings
}
`,
comment: graphql`
fragment ConversationModalCommentContainer_comment on Comment {
id
body
createdAt
author {
username
id
}
replyCount
parent {
author {
username
id
}
}
}
`,
})(ConversationModalCommentContainer);
export default enhanced;
@@ -0,0 +1,10 @@
.root {
}
.topCircle {
margin-right: var(--v2-spacing-2);
}
.bottomCircleContainer {
width: 8px;
padding-top: var(--v2-spacing-1);
}
@@ -0,0 +1,159 @@
import { Localized } from "@fluent/react/compat";
import React, { FunctionComponent } from "react";
import { graphql, RelayPaginationProp } from "react-relay";
import {
useLoadMore,
withPaginationContainer,
} from "coral-framework/lib/relay";
import {
Button,
Counter,
Flex,
HorizontalGutter,
} from "coral-ui/components/v2";
import { ConversationModalContainer_comment } from "coral-admin/__generated__/ConversationModalContainer_comment.graphql";
import { ConversationModalContainer_settings } from "coral-admin/__generated__/ConversationModalContainer_settings.graphql";
import { ConversationModalContainerPaginationQueryVariables } from "coral-admin/__generated__/ConversationModalContainerPaginationQuery.graphql";
import { Circle } from "../Timeline";
import ConversationModalComment from "./ConversationModalCommentContainer";
import styles from "./ConversationModalContainer.css";
interface Props {
relay: RelayPaginationProp;
comment: ConversationModalContainer_comment;
settings: ConversationModalContainer_settings;
onClose: () => void;
onUsernameClicked: (id?: string) => void;
}
const ConversationModalContainer: FunctionComponent<Props> = ({
comment,
relay,
settings,
onUsernameClicked,
}) => {
const [loadMore] = useLoadMore(relay, 5);
const parents = comment.parents.edges.map(edge => edge.node);
return (
<HorizontalGutter className={styles.root}>
{comment.parentCount > parents.length && (
<div>
<Flex alignItems="center">
<Circle hollow={true} className={styles.topCircle} />
<Button underline variant="text" onClick={loadMore}>
<Localized id="conversation-modal-show-more-parents">
<span>Show more of this conversation</span>
</Localized>
<Counter>{comment.parentCount}</Counter>
</Button>
</Flex>
<Flex
direction="column"
alignItems="center"
className={styles.bottomCircleContainer}
>
<Circle color="light" size="small" />
</Flex>
</div>
)}
{parents.map(parent => (
<ConversationModalComment
key={parent.id}
isParent={true}
comment={parent}
onUsernameClick={onUsernameClicked}
settings={settings}
isHighlighted={false}
/>
))}
<ConversationModalComment
comment={comment}
settings={settings}
onUsernameClick={onUsernameClicked}
isHighlighted={true}
/>
</HorizontalGutter>
);
};
// TODO: (cvle) If this could be autogenerated.
type FragmentVariables = ConversationModalContainerPaginationQueryVariables;
const enhanced = withPaginationContainer<
Props,
ConversationModalContainerPaginationQueryVariables,
FragmentVariables
>(
{
settings: graphql`
fragment ConversationModalContainer_settings on Settings {
...ConversationModalCommentContainer_settings
}
`,
comment: graphql`
fragment ConversationModalContainer_comment on Comment
@argumentDefinitions(
count: { type: "Int!", defaultValue: 1 }
cursor: { type: "Cursor" }
) {
id
...ConversationModalCommentContainer_comment
rootParent {
id
}
parents(last: $count, before: $cursor)
@connection(key: "ConversationModal_parents") {
edges {
node {
id
...ConversationModalCommentContainer_comment
}
}
}
parentCount
}
`,
},
{
direction: "backward",
getConnectionFromProps(props) {
return props.comment && props.comment.parents;
},
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
getFragmentVariables(prevVars, totalCount) {
return {
...prevVars,
count: totalCount,
};
},
getVariables(props, { count, cursor }) {
return {
count,
cursor,
// commentID isn't specified as an @argument for the fragment, but it should be a
// variable available for the fragment under the query root.
commentID: props.comment.id,
};
},
query: graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query ConversationModalContainerPaginationQuery(
$count: Int!
$cursor: Cursor
$commentID: ID!
) {
comment(id: $commentID) {
...ConversationModalContainer_comment
@arguments(count: $count, cursor: $cursor)
}
}
`,
}
)(ConversationModalContainer);
export default enhanced;
@@ -0,0 +1,17 @@
$modal-header-text: var(--v2-colors-mono-900);
.title {
font-size: var(--v2-font-size-4);
font-family: var(--v2-font-family-primary);
font-weight: var(--v2-font-weight-primary-regular);
line-height: var(--v2-line-height-title);
color: $modal-header-text;
margin: 0;
}
.conversationTitle {
font-weight: var(--v2-font-weight-primary-semi-bold);
text-transform: uppercase;
display: block;
margin-bottom: var(--v2-spacing-2);
}
@@ -0,0 +1,65 @@
import { Localized } from "@fluent/react/compat";
import { useRouter } from "found";
import React, { FunctionComponent, RefObject, useCallback } from "react";
import { getModerationLink } from "coral-framework/helpers";
import { graphql, withFragmentContainer } from "coral-framework/lib/relay";
import { Button, HorizontalGutter, ModalHeader } from "coral-ui/components/v2";
import { ConversationModalHeaderContainer_comment } from "coral-admin/__generated__/ConversationModalHeaderContainer_comment.graphql";
import styles from "./ConversationModalHeaderContainer.css";
interface Props {
comment: ConversationModalHeaderContainer_comment;
onClose: () => void;
focusableRef: RefObject<any>;
}
const ConversationModalHeaderContainer: FunctionComponent<Props> = ({
comment,
onClose,
focusableRef,
}) => {
const { router } = useRouter();
const onModerate = useCallback(() => {
const link = getModerationLink({ storyID: comment.story.id });
router.push(link);
}, [comment.id]);
return (
<ModalHeader onClose={onClose} focusableRef={focusableRef}>
<HorizontalGutter spacing={3}>
<h1 className={styles.title}>
<Localized id="conversation-modal-header-title">
<span className={styles.conversationTitle}>Conversation on:</span>
</Localized>
{comment.story.metadata
? comment.story.metadata.title
: comment.story.url}
</h1>
<Localized id="conversation-modal-header-moderate-link">
<Button variant="outline" color="mono" uppercase onClick={onModerate}>
Moderate story
</Button>
</Localized>
</HorizontalGutter>
</ModalHeader>
);
};
const enhanced = withFragmentContainer<Props>({
comment: graphql`
fragment ConversationModalHeaderContainer_comment on Comment {
story {
id
metadata {
title
}
url
}
id
}
`,
})(ConversationModalHeaderContainer);
export default enhanced;
@@ -0,0 +1,8 @@
.root {
padding: 0;
width: 635px;
}
.body {
padding: var(--v2-spacing-4);
}
@@ -0,0 +1,99 @@
import { Localized } from "@fluent/react/compat";
import React, { FunctionComponent } from "react";
import {
graphql,
QueryRenderData,
QueryRenderer,
} from "coral-framework/lib/relay";
import { CallOut, Card, Spinner } from "coral-ui/components/v2";
import { ConversationModalQuery as QueryTypes } from "coral-admin/__generated__/ConversationModalQuery.graphql";
import ConversationModalContainer from "./ConversationModalContainer";
import ConversationModalHeaderContainer from "./ConversationModalHeaderContainer";
import ConversationModalRepliesContainer from "./ConversationModalRepliesContainer";
import styles from "./ConversationModalQuery.css";
interface Props {
commentID: string;
onClose: () => void;
firstFocusableRef: React.RefObject<any>;
onUsernameClicked: (userID: string) => void;
lastFocusableRef: React.RefObject<any>;
}
const ConversationModalQuery: FunctionComponent<Props> = ({
commentID,
onClose,
lastFocusableRef,
onUsernameClicked,
}) => {
return (
<QueryRenderer<QueryTypes>
query={graphql`
query ConversationModalQuery($commentID: ID!) {
settings {
...ConversationModalContainer_settings
...ConversationModalRepliesContainer_settings
}
comment(id: $commentID) {
...ConversationModalContainer_comment
...ConversationModalRepliesContainer_comment
...ConversationModalHeaderContainer_comment
}
}
`}
variables={{ commentID }}
cacheConfig={{ force: true }}
render={({ props }: QueryRenderData<QueryTypes>) => {
if (!props) {
return (
<div>
<Spinner />
</div>
);
}
if (!props.comment) {
return (
<div>
<CallOut>
<Localized id="conversation-modal-comment-not-found">
Comment not found.
</Localized>
</CallOut>
</div>
);
}
return (
<Card className={styles.root}>
<ConversationModalHeaderContainer
onClose={onClose}
comment={props.comment}
focusableRef={lastFocusableRef}
/>
<div className={styles.body}>
<ConversationModalContainer
onClose={onClose}
settings={props.settings}
comment={props.comment}
onUsernameClicked={onUsernameClicked}
/>
<ConversationModalRepliesContainer
onClose={onClose}
comment={props.comment}
settings={props.settings}
onUsernameClicked={onUsernameClicked}
/>
</div>
</Card>
);
}}
/>
);
};
export default ConversationModalQuery;
@@ -0,0 +1,7 @@
.comment {
padding-left: calc(var(--v2-spacing-4));
}
.footer {
padding-left: calc(var(--v2-spacing-4));
}
@@ -0,0 +1,152 @@
import { Localized } from "@fluent/react/compat";
import React, { FunctionComponent, useCallback, useState } from "react";
import { graphql, RelayPaginationProp } from "react-relay";
import {
useLoadMore,
withPaginationContainer,
} from "coral-framework/lib/relay";
import { Omit } from "coral-framework/types";
import { Button, HorizontalGutter } from "coral-ui/components/v2";
import { ConversationModalRepliesContainer_comment } from "coral-admin/__generated__/ConversationModalRepliesContainer_comment.graphql";
import { ConversationModalRepliesContainer_settings } from "coral-admin/__generated__/ConversationModalRepliesContainer_settings.graphql";
import { ConversationModalRepliesContainerPaginationQueryVariables } from "coral-admin/__generated__/ConversationModalRepliesContainerPaginationQuery.graphql";
import ConversationModalCommentContainer from "./ConversationModalCommentContainer";
import styles from "./ConversationModalRepliesContainer.css";
interface Props {
relay: RelayPaginationProp;
comment: ConversationModalRepliesContainer_comment;
settings: ConversationModalRepliesContainer_settings;
onClose: () => void;
onUsernameClicked: (id?: string) => void;
}
const ConversationModalRepliesContainer: FunctionComponent<Props> = ({
comment,
relay,
settings,
onUsernameClicked,
}) => {
const [loadMore] = useLoadMore(relay, 5);
const replies = comment.replies.edges.map(edge => edge.node);
const [showReplies, setShowReplies] = useState(false);
const onShowReplies = useCallback(() => {
setShowReplies(true);
}, []);
return (
<HorizontalGutter>
{showReplies &&
replies.map(reply => (
<div key={reply.id} className={styles.comment}>
<ConversationModalCommentContainer
key={reply.id}
settings={settings}
comment={reply}
isHighlighted={false}
isReply={true}
onUsernameClick={onUsernameClicked}
/>
</div>
))}
<div className={styles.footer}>
{!showReplies && comment.replyCount > 0 && (
<Localized id="conversation-modal-replies-show">
<Button variant="outline" fullWidth onClick={onShowReplies}>
Show replies
</Button>
</Localized>
)}
{showReplies &&
comment.replyCount > replies.length &&
replies.length > 0 && (
<Localized id="conversation-modal-replies-show-more">
<Button variant="outline" fullWidth onClick={loadMore}>
Show more replies
</Button>
</Localized>
)}
</div>
</HorizontalGutter>
);
};
// TODO: (cvle) If this could be autogenerated.
type FragmentVariables = Omit<
ConversationModalRepliesContainerPaginationQueryVariables,
"commentID"
>;
const enhanced = withPaginationContainer<
Props,
ConversationModalRepliesContainerPaginationQueryVariables,
FragmentVariables
>(
{
settings: graphql`
fragment ConversationModalRepliesContainer_settings on Settings {
...ConversationModalCommentContainer_settings
}
`,
comment: graphql`
fragment ConversationModalRepliesContainer_comment on Comment
@argumentDefinitions(
count: { type: "Int!", defaultValue: 5 }
cursor: { type: "Cursor" }
orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC }
) {
id
replies(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "ConversationModalReplies_replies") {
edges {
node {
id
...ConversationModalCommentContainer_comment
}
}
}
replyCount
}
`,
},
{
direction: "forward",
getConnectionFromProps(props) {
return props.comment && props.comment.replies;
},
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
getFragmentVariables(prevVars, totalCount) {
return {
...prevVars,
count: totalCount,
};
},
getVariables(props, { count, cursor }, fragmentVariables) {
return {
count,
cursor,
orderBy: fragmentVariables.orderBy,
commentID: props.comment.id,
};
},
query: graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query ConversationModalRepliesContainerPaginationQuery(
$count: Int!
$cursor: Cursor
$orderBy: COMMENT_SORT!
$commentID: ID!
) {
comment(id: $commentID) {
...ConversationModalRepliesContainer_comment
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
}
}
`,
}
)(ConversationModalRepliesContainer);
export default enhanced;
@@ -0,0 +1,87 @@
import { Localized } from "@fluent/react/compat";
import React, { FunctionComponent } from "react";
import {
graphql,
QueryRenderData,
QueryRenderer,
} from "coral-framework/lib/relay";
import { CallOut, Spinner } from "coral-ui/components/v2";
import { ConversationModalRepliesQuery as QueryTypes } from "coral-admin/__generated__/ConversationModalRepliesQuery.graphql";
import ConversationModalCommentContainer from "./ConversationModalCommentContainer";
interface Props {
commentID: string;
onUsernameClicked: (userID: string) => void;
}
const ConversationModalRepliesQuery: FunctionComponent<Props> = ({
commentID,
onUsernameClicked,
}) => {
return (
<QueryRenderer<QueryTypes>
query={graphql`
query ConversationModalRepliesQuery($commentID: ID!) {
settings {
...ConversationModalCommentContainer_settings
}
comment(id: $commentID) {
replies {
edges {
node {
id
...ConversationModalCommentContainer_comment
}
}
}
}
}
`}
variables={{ commentID }}
cacheConfig={{ force: true }}
render={({ props }: QueryRenderData<QueryTypes>) => {
if (!props) {
return (
<div>
<Spinner />
</div>
);
}
if (!props.comment) {
return (
<div>
<CallOut>
<Localized id="conversation-modal-comment-not-found">
Comment not found.
</Localized>
</CallOut>
</div>
);
}
const replies = props.comment.replies.edges.map(edge => edge.node);
return (
<div>
{replies.map(reply => (
<div key={reply.id}>
<ConversationModalCommentContainer
key={reply.id}
comment={reply}
settings={props.settings}
isHighlighted={false}
isReply={true}
onUsernameClick={onUsernameClicked}
/>
</div>
))}
</div>
);
}}
/>
);
};
export default ConversationModalRepliesQuery;
@@ -0,0 +1 @@
export { default } from "./ConversationModal";
@@ -6,7 +6,7 @@ import { HorizontalGutter, Timestamp } from "coral-ui/components/v2";
import { CommentRevisionContainer_comment as CommentData } from "coral-admin/__generated__/CommentRevisionContainer_comment.graphql";
import { CommentRevisionContainer_settings as SettingsData } from "coral-admin/__generated__/CommentRevisionContainer_settings.graphql";
import CommentContent from "./CommentContent";
import { CommentContent } from "../Comment";
interface Props {
comment: CommentData;
@@ -15,26 +15,6 @@ $moderateCardLinkTextColor: var(--v2-colors-teal-700);
margin-bottom: var(--v2-spacing-4);
}
.usernameButton {
margin-right: var(--mini-unit);
padding: var(--spacing-1);
margin-left: calc(-1 * var(--v2-spacing-1));
line-height: var(--v2-line-height-reset);
&:hover {
background-color: $moderateCardUsernameBackgroundHover;
border-radius: var(--v2-round-corners);
border-style: none;
}
&:active {
background-color: $moderateCardUsernameBackgroundHover;
border-radius: var(--v2-round-corners);
border-style: none;
}
}
.inReplyTo {
}
@@ -35,6 +35,7 @@ const baseProps: PropTypesOf<typeof ModerateCardN> = {
onBan: noop,
onUsernameClick: noop,
onFocusOrClick: noop,
onConversationClick: noop,
showStory: false,
moderatedBy: null,
};
@@ -13,7 +13,8 @@ import { HOTKEYS } from "coral-admin/constants";
import { GetPhrasesRegExpOptions } from "coral-admin/helpers";
import { PropTypesOf } from "coral-framework/types";
import {
BaseButton,
Button,
ButtonIcon,
Card,
Flex,
HorizontalGutter,
@@ -22,14 +23,12 @@ import {
Timestamp,
} from "coral-ui/components/v2";
import { CommentContent, InReplyTo, UsernameButton } from "../Comment";
import ApproveButton from "./ApproveButton";
import CommentAuthorContainer from "./CommentAuthorContainer";
import CommentContent from "./CommentContent";
import FeatureButton from "./FeatureButton";
import InReplyTo from "./InReplyTo";
import MarkersContainer from "./MarkersContainer";
import RejectButton from "./RejectButton";
import Username from "./Username";
import styles from "./ModerateCard.css";
@@ -59,6 +58,7 @@ interface Props {
onReject: () => void;
onFeature: () => void;
onUsernameClick: (id?: string) => void;
onConversationClick: (id?: string) => void;
onFocusOrClick: () => void;
mini?: boolean;
hideUsername?: boolean;
@@ -102,6 +102,7 @@ const ModerateCard: FunctionComponent<Props> = ({
moderatedBy,
selected,
onFocusOrClick,
onConversationClick,
mini = false,
hideUsername = false,
deleted = false,
@@ -165,6 +166,9 @@ const ModerateCard: FunctionComponent<Props> = ({
onUsernameClick(inReplyTo.id);
}
}, [onUsernameClick, inReplyTo]);
const viewConversationClick = useCallback(() => {
onConversationClick();
}, [onConversationClick]);
return (
<Card
className={cn(
@@ -190,12 +194,10 @@ const ModerateCard: FunctionComponent<Props> = ({
<Flex alignItems="center">
{!hideUsername && username && (
<>
<BaseButton
<UsernameButton
username={username}
onClick={commentAuthorClick}
className={styles.usernameButton}
>
<Username>{username}</Username>
</BaseButton>
/>
<CommentAuthorContainer comment={comment} />
</>
)}
@@ -224,15 +226,12 @@ const ModerateCard: FunctionComponent<Props> = ({
{commentBody}
</CommentContent>
<div className={styles.viewContext}>
<Localized id="moderate-comment-viewContext">
<TextLink
className={styles.link}
href={viewContextHref}
target="_blank"
>
View Context
</TextLink>
</Localized>
<Button iconLeft variant="text" onClick={viewConversationClick}>
<ButtonIcon>question_answer</ButtonIcon>
<Localized id="moderate-comment-viewConversation">
<span>View conversation</span>
</Localized>
</Button>
</div>
<div
className={cn(styles.separator, {
@@ -46,6 +46,7 @@ interface Props {
mini?: boolean;
hideUsername?: boolean;
onUsernameClicked?: (userID: string) => void;
onConversationClicked?: (commentID: string) => void;
onSetSelected?: () => void;
selected?: boolean;
selectPrev?: () => void;
@@ -86,6 +87,7 @@ const ModerateCardContainer: FunctionComponent<Props> = ({
selectPrev,
selectNext,
onUsernameClicked: usernameClicked,
onConversationClicked: conversationClicked,
onSetSelected: setSelected,
banUser,
loadNext,
@@ -160,6 +162,16 @@ const ModerateCardContainer: FunctionComponent<Props> = ({
[usernameClicked, comment]
);
const onConversationClicked = useCallback(
(id?: string) => {
if (!conversationClicked) {
return;
}
conversationClicked(id || comment.id);
},
[conversationClicked, comment]
);
const handleModerateStory = useCallback(
(e: React.MouseEvent) => {
router.push(getModerationLink({ storyID: comment.story.id }));
@@ -227,6 +239,7 @@ const ModerateCardContainer: FunctionComponent<Props> = ({
onReject={handleReject}
onFeature={onFeature}
onUsernameClick={onUsernameClicked}
onConversationClick={onConversationClicked}
selected={selected}
selectPrev={selectPrev}
selectNext={selectNext}
@@ -19,14 +19,10 @@ exports[`renders approved correctly 1`] = `
alignItems="center"
>
<React.Fragment>
<ForwardRef(forwardRef)
className="ModerateCard-usernameButton"
<UsernameButton
onClick={[Function]}
>
<Username>
Theon
</Username>
</ForwardRef(forwardRef)>
username="Theon"
/>
<Relay(CommentAuthorContainer)
comment={Object {}}
/>
@@ -67,17 +63,22 @@ exports[`renders approved correctly 1`] = `
<div
className="ModerateCard-viewContext"
>
<Localized
id="moderate-comment-viewContext"
<ForwardRef(forwardRef)
iconLeft={true}
onClick={[Function]}
variant="text"
>
<withPropsOnChange(TextLinkProps)
className="ModerateCard-link"
href="http://localhost/comment"
target="_blank"
<ForwardRef(forwardRef)>
question_answer
</ForwardRef(forwardRef)>
<Localized
id="moderate-comment-viewConversation"
>
View Context
</withPropsOnChange(TextLinkProps)>
</Localized>
<span>
View conversation
</span>
</Localized>
</ForwardRef(forwardRef)>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -152,14 +153,10 @@ exports[`renders correctly 1`] = `
alignItems="center"
>
<React.Fragment>
<ForwardRef(forwardRef)
className="ModerateCard-usernameButton"
<UsernameButton
onClick={[Function]}
>
<Username>
Theon
</Username>
</ForwardRef(forwardRef)>
username="Theon"
/>
<Relay(CommentAuthorContainer)
comment={Object {}}
/>
@@ -200,17 +197,22 @@ exports[`renders correctly 1`] = `
<div
className="ModerateCard-viewContext"
>
<Localized
id="moderate-comment-viewContext"
<ForwardRef(forwardRef)
iconLeft={true}
onClick={[Function]}
variant="text"
>
<withPropsOnChange(TextLinkProps)
className="ModerateCard-link"
href="http://localhost/comment"
target="_blank"
<ForwardRef(forwardRef)>
question_answer
</ForwardRef(forwardRef)>
<Localized
id="moderate-comment-viewConversation"
>
View Context
</withPropsOnChange(TextLinkProps)>
</Localized>
<span>
View conversation
</span>
</Localized>
</ForwardRef(forwardRef)>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -285,14 +287,10 @@ exports[`renders dangling correctly 1`] = `
alignItems="center"
>
<React.Fragment>
<ForwardRef(forwardRef)
className="ModerateCard-usernameButton"
<UsernameButton
onClick={[Function]}
>
<Username>
Theon
</Username>
</ForwardRef(forwardRef)>
username="Theon"
/>
<Relay(CommentAuthorContainer)
comment={Object {}}
/>
@@ -333,17 +331,22 @@ exports[`renders dangling correctly 1`] = `
<div
className="ModerateCard-viewContext"
>
<Localized
id="moderate-comment-viewContext"
<ForwardRef(forwardRef)
iconLeft={true}
onClick={[Function]}
variant="text"
>
<withPropsOnChange(TextLinkProps)
className="ModerateCard-link"
href="http://localhost/comment"
target="_blank"
<ForwardRef(forwardRef)>
question_answer
</ForwardRef(forwardRef)>
<Localized
id="moderate-comment-viewConversation"
>
View Context
</withPropsOnChange(TextLinkProps)>
</Localized>
<span>
View conversation
</span>
</Localized>
</ForwardRef(forwardRef)>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -418,14 +421,10 @@ exports[`renders rejected correctly 1`] = `
alignItems="center"
>
<React.Fragment>
<ForwardRef(forwardRef)
className="ModerateCard-usernameButton"
<UsernameButton
onClick={[Function]}
>
<Username>
Theon
</Username>
</ForwardRef(forwardRef)>
username="Theon"
/>
<Relay(CommentAuthorContainer)
comment={Object {}}
/>
@@ -466,17 +465,22 @@ exports[`renders rejected correctly 1`] = `
<div
className="ModerateCard-viewContext"
>
<Localized
id="moderate-comment-viewContext"
<ForwardRef(forwardRef)
iconLeft={true}
onClick={[Function]}
variant="text"
>
<withPropsOnChange(TextLinkProps)
className="ModerateCard-link"
href="http://localhost/comment"
target="_blank"
<ForwardRef(forwardRef)>
question_answer
</ForwardRef(forwardRef)>
<Localized
id="moderate-comment-viewConversation"
>
View Context
</withPropsOnChange(TextLinkProps)>
</Localized>
<span>
View conversation
</span>
</Localized>
</ForwardRef(forwardRef)>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -551,14 +555,10 @@ exports[`renders reply correctly 1`] = `
alignItems="center"
>
<React.Fragment>
<ForwardRef(forwardRef)
className="ModerateCard-usernameButton"
<UsernameButton
onClick={[Function]}
>
<Username>
Theon
</Username>
</ForwardRef(forwardRef)>
username="Theon"
/>
<Relay(CommentAuthorContainer)
comment={Object {}}
/>
@@ -608,17 +608,22 @@ exports[`renders reply correctly 1`] = `
<div
className="ModerateCard-viewContext"
>
<Localized
id="moderate-comment-viewContext"
<ForwardRef(forwardRef)
iconLeft={true}
onClick={[Function]}
variant="text"
>
<withPropsOnChange(TextLinkProps)
className="ModerateCard-link"
href="http://localhost/comment"
target="_blank"
<ForwardRef(forwardRef)>
question_answer
</ForwardRef(forwardRef)>
<Localized
id="moderate-comment-viewConversation"
>
View Context
</withPropsOnChange(TextLinkProps)>
</Localized>
<span>
View conversation
</span>
</Localized>
</ForwardRef(forwardRef)>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -693,14 +698,10 @@ exports[`renders story info 1`] = `
alignItems="center"
>
<React.Fragment>
<ForwardRef(forwardRef)
className="ModerateCard-usernameButton"
<UsernameButton
onClick={[Function]}
>
<Username>
Theon
</Username>
</ForwardRef(forwardRef)>
username="Theon"
/>
<Relay(CommentAuthorContainer)
comment={Object {}}
/>
@@ -741,17 +742,22 @@ exports[`renders story info 1`] = `
<div
className="ModerateCard-viewContext"
>
<Localized
id="moderate-comment-viewContext"
<ForwardRef(forwardRef)
iconLeft={true}
onClick={[Function]}
variant="text"
>
<withPropsOnChange(TextLinkProps)
className="ModerateCard-link"
href="http://localhost/comment"
target="_blank"
<ForwardRef(forwardRef)>
question_answer
</ForwardRef(forwardRef)>
<Localized
id="moderate-comment-viewConversation"
>
View Context
</withPropsOnChange(TextLinkProps)>
</Localized>
<span>
View conversation
</span>
</Localized>
</ForwardRef(forwardRef)>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -864,14 +870,10 @@ exports[`renders tombstoned when comment is deleted 1`] = `
alignItems="center"
>
<React.Fragment>
<ForwardRef(forwardRef)
className="ModerateCard-usernameButton"
<UsernameButton
onClick={[Function]}
>
<Username>
Theon
</Username>
</ForwardRef(forwardRef)>
username="Theon"
/>
<Relay(CommentAuthorContainer)
comment={Object {}}
/>
@@ -920,17 +922,22 @@ exports[`renders tombstoned when comment is deleted 1`] = `
<div
className="ModerateCard-viewContext"
>
<Localized
id="moderate-comment-viewContext"
<ForwardRef(forwardRef)
iconLeft={true}
onClick={[Function]}
variant="text"
>
<withPropsOnChange(TextLinkProps)
className="ModerateCard-link"
href="http://localhost/comment"
target="_blank"
<ForwardRef(forwardRef)>
question_answer
</ForwardRef(forwardRef)>
<Localized
id="moderate-comment-viewConversation"
>
View Context
</withPropsOnChange(TextLinkProps)>
</Localized>
<span>
View conversation
</span>
</Localized>
</ForwardRef(forwardRef)>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -0,0 +1,29 @@
.sizeReg {
width: 8px;
height: 8px;
}
.sizeSmall {
width: 4px;
height: 4px;
}
.colorReg {
color: var(--v2-colors-grey-500);
}
.colorLight {
color: var(--v2-colors-grey-400);
}
.root {
position: relative;
}
.circle {
fill: currentColor;
stroke: currentColor;
position: absolute;
left: 0;
top: 0;
}
@@ -0,0 +1,42 @@
import cn from "classnames";
import React, { FunctionComponent } from "react";
import styles from "./Circle.css";
export interface CircleProps {
className?: string;
hollow?: boolean;
size?: "small" | "regular";
color?: "light" | "regular";
}
const Circle: FunctionComponent<CircleProps> = props => {
return (
<div
className={cn(styles.root, props.className, {
[styles.sizeReg]: props.size === "regular",
[styles.sizeSmall]: props.size === "small",
[styles.colorReg]: props.color === "regular",
[styles.colorLight]: props.color === "light",
})}
>
<svg
className={cn(styles.circle, {})}
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
{props.hollow && (
<circle cx="50%" cy="50%" r="45" strokeWidth="10" fill="none" />
)}
{!props.hollow && <circle cx="50%" cy="50%" r="50" />}
</svg>
</div>
);
};
Circle.defaultProps = {
size: "regular",
color: "regular",
};
export default Circle;
@@ -0,0 +1,9 @@
.root {
border-left: 1px solid var(--palette-grey-main);
border-right: 1px solid var(--palette-grey-main);
flex: 1;
}
.dotted {
border-left-style: dotted;
}
@@ -0,0 +1,16 @@
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { PropTypesOf } from "coral-framework/types";
import Line from "./Line";
it("renders correctly", () => {
const props: PropTypesOf<typeof Line> = {
className: "root",
dotted: true,
};
const renderer = createRenderer();
renderer.render(<Line {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -0,0 +1,23 @@
import cn from "classnames";
import React, { FunctionComponent } from "react";
import styles from "./Line.css";
interface LineProps {
className?: string;
dotted?: boolean;
}
const Line: FunctionComponent<LineProps> = props => {
return (
<div
className={cn(styles.root, props.className, {
[styles.dotted]: props.dotted,
})}
>
{props.children}
</div>
);
};
export default Line;
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<div
className="Line-root root Line-dotted"
/>
`;
@@ -0,0 +1,2 @@
export { default as Circle } from "./Circle";
export { default as Line } from "./Line";
@@ -2,6 +2,7 @@ import { Localized } from "@fluent/react/compat";
import React, { FunctionComponent, useCallback, useState } from "react";
import AutoLoadMore from "coral-admin/components/AutoLoadMore";
import ConversationModal from "coral-admin/components/ConversationModal";
import ModerateCardContainer from "coral-admin/components/ModerateCard";
import UserHistoryDrawer from "coral-admin/components/UserHistoryDrawer";
import { HOTKEYS } from "coral-admin/constants";
@@ -46,6 +47,10 @@ const Queue: FunctionComponent<Props> = ({
const [userDrawerId, setUserDrawerID] = useState("");
const [selectedComment, setSelectedComment] = useState<number | null>(0);
const [singleView, setSingleView] = useState(false);
const [conversationModalVisible, setConversationModalVisible] = useState(
false
);
const [conversationCommentID, setConversationCommentID] = useState("");
const toggleView = useCallback(() => {
if (!singleView) {
@@ -94,6 +99,23 @@ const Queue: FunctionComponent<Props> = ({
setUserDrawerID("");
}, []);
const onShowConversationModal = useCallback((commentID: string) => {
setConversationCommentID(commentID);
setConversationModalVisible(true);
}, []);
const onHideConversationModal = useCallback(() => {
setConversationModalVisible(false);
setConversationCommentID("");
}, []);
const onUsernameClickedFromModal = useCallback((userID: string) => {
setUserDrawerID(userID);
setUserDrawerVisible(true);
setConversationModalVisible(false);
setConversationCommentID("");
}, []);
return (
<HorizontalGutter className={styles.root} size="double">
{Boolean(viewNewCount && viewNewCount > 0) && (
@@ -119,6 +141,7 @@ const Queue: FunctionComponent<Props> = ({
danglingLogic={danglingLogic}
showStoryInfo={Boolean(allStories)}
onUsernameClicked={onShowUserDrawer}
onConversationClicked={onShowConversationModal}
onSetSelected={() => setSelectedComment(i)}
selected={selectedComment === i}
selectPrev={selectPrev}
@@ -141,6 +164,12 @@ const Queue: FunctionComponent<Props> = ({
onClose={onHideUserDrawer}
userID={userDrawerId}
/>
<ConversationModal
onUsernameClicked={onUsernameClickedFromModal}
open={conversationModalVisible}
onClose={onHideConversationModal}
commentID={conversationCommentID}
/>
</HorizontalGutter>
);
};
@@ -24,6 +24,12 @@ exports[`renders correctly with load more 1`] = `
open={false}
userID=""
/>
<ConversationModal
commentID=""
onClose={[Function]}
onUsernameClicked={[Function]}
open={false}
/>
</ForwardRef(forwardRef)>
`;
@@ -43,5 +49,11 @@ exports[`renders correctly without load more 1`] = `
open={false}
userID=""
/>
<ConversationModal
commentID=""
onClose={[Function]}
onUsernameClicked={[Function]}
open={false}
/>
</ForwardRef(forwardRef)>
`;
@@ -34,7 +34,7 @@ exports[`approves comment in reported queue: dangling 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -98,19 +98,28 @@ exports[`approves comment in reported queue: dangling 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/0"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -291,7 +300,7 @@ exports[`rejects comment in reported queue: dangling 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -355,19 +364,28 @@ exports[`rejects comment in reported queue: dangling 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/0"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -545,7 +563,7 @@ exports[`renders reported queue with comments 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -609,19 +627,28 @@ exports[`renders reported queue with comments 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/0"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -786,7 +813,7 @@ exports[`renders reported queue with comments 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -850,19 +877,28 @@ exports[`renders reported queue with comments 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/1"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -1043,7 +1079,7 @@ exports[`renders reported queue with comments 2`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -1107,19 +1143,28 @@ exports[`renders reported queue with comments 2`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/0"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -1284,7 +1329,7 @@ exports[`renders reported queue with comments 2`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -1348,19 +1393,28 @@ exports[`renders reported queue with comments 2`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/1"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -1531,7 +1585,7 @@ exports[`renders reported queue with comments and load more 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -1595,19 +1649,28 @@ exports[`renders reported queue with comments and load more 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/2"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -34,7 +34,7 @@ exports[`approves comment in rejected queue: dangling 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -98,19 +98,28 @@ exports[`approves comment in rejected queue: dangling 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/0"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -288,7 +297,7 @@ exports[`renders rejected queue with comments 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -352,19 +361,28 @@ exports[`renders rejected queue with comments 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/0"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -529,7 +547,7 @@ exports[`renders rejected queue with comments 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -593,19 +611,28 @@ exports[`renders rejected queue with comments 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/1"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -776,7 +803,7 @@ exports[`renders rejected queue with comments and load more 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -840,19 +867,28 @@ exports[`renders rejected queue with comments and load more 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/2"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -21,7 +21,7 @@ exports[`approves single comment 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -85,19 +85,28 @@ exports[`approves single comment 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/0"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -235,7 +244,7 @@ exports[`rejects single comment 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -299,19 +308,28 @@ exports[`rejects single comment 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/0"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -484,7 +502,7 @@ exports[`renders single comment view 1`] = `
className="Box-root Flex-root Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root ModerateCard-usernameButton"
className="BaseButton-root UsernameButton-root"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
@@ -548,19 +566,28 @@ exports[`renders single comment view 1`] = `
<div
className="ModerateCard-viewContext"
>
<a
className="TextLink-root ModerateCard-link"
href="http://localhost/comment/0"
target="_blank"
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantText Button-uppercase Button-iconLeft"
data-color="regular"
data-variant="text"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
View Context
<i
aria-hidden="true"
className="Icon-root Icon-xs TextLink-icon"
className="Icon-root Icon-sm ButtonIcon-root"
>
open_in_new
question_answer
</i>
</a>
<span>
View Conversation
</span>
</button>
</div>
<div
className="ModerateCard-separator ModerateCard-ruledSeparator"
@@ -490,3 +490,7 @@ $button-text-dark-foreground-active: var(--v2-colors-blue-700);
.linkButton {
text-decoration: none;
}
.underline {
text-decoration: underline;
}
@@ -55,6 +55,9 @@ interface Props extends Omit<BaseButtonProps, "ref"> {
/** Internal: Forwarded Ref */
forwardRef?: Ref<HTMLButtonElement>;
/* if true adds a link-style underline */
underline?: boolean;
}
export class Button extends React.Component<Props> {
@@ -82,6 +85,7 @@ export class Button extends React.Component<Props> {
iconRight,
adornmentLeft,
adornmentRight,
underline,
to,
...rest
} = this.props;
@@ -108,6 +112,7 @@ export class Button extends React.Component<Props> {
[classes.iconRight]: iconRight,
[classes.adornmentLeft]: adornmentLeft,
[classes.adornmentRight]: adornmentRight,
[classes.underline]: underline,
},
className
);
@@ -0,0 +1,6 @@
$modal-header-background: var(--v2-colors-white-500);
.root {
background-color: $modal-header-background;
padding: var(--v2-spacing-4);
}
@@ -0,0 +1,27 @@
import React, { FunctionComponent, RefObject } from "react";
import { CardCloseButton, Flex } from "coral-ui/components/v2";
import styles from "./ModalHeader.css";
interface Props {
onClose: () => void;
focusableRef: RefObject<any>;
}
const ModalHeader: FunctionComponent<Props> = ({
onClose,
focusableRef,
children,
}) => {
return (
<div className={styles.root}>
<Flex justifyContent="space-between" alignItems="flex-start">
{children}
<CardCloseButton onClick={onClose} ref={focusableRef} />
</Flex>
</div>
);
};
export default ModalHeader;
@@ -1 +1,2 @@
export { default, ModalProps } from "./Modal";
export { default as ModalHeader } from "./ModalHeader";
+1 -1
View File
@@ -39,7 +39,7 @@ export { default as Label } from "./Label";
export { ListGroup, ListGroupRow } from "./ListGroup";
export { Marker, Count as MarkerCount } from "./Marker";
export { default as Message, MessageIcon } from "./Message";
export { default as Modal, ModalProps } from "./Modal";
export { default as Modal, ModalProps, ModalHeader } from "./Modal";
export { default as PasswordField } from "./PasswordField";
export { default as Popover } from "./Popover";
export { default as RadioButton } from "./RadioButton";
+1
View File
@@ -716,6 +716,7 @@ moderate-emptyQueue-approved = There are no approved comments.
moderate-comment-edited = (edited)
moderate-comment-inReplyTo = Reply to <Username></Username>
moderate-comment-viewContext = View Context
moderate-comment-viewConversation = View Conversation
moderate-comment-rejectButton =
.aria-label = Reject
moderate-comment-approveButton =