This commit is contained in:
Chi Vinh Le
2018-09-06 00:06:28 +02:00
parent eb74162eef
commit 31bc5fa913
21 changed files with 415 additions and 89 deletions
@@ -1,5 +1,11 @@
.root {
}
.footer {
.topBar {
margin-bottom: calc(0.5 * var(--spacing-unit));
}
.footer:not(:empty) {
margin-top: var(--spacing-unit);
}
.reply {
margin-top: var(--spacing-unit);
}
@@ -1,8 +1,8 @@
import React from "react";
import { StatelessComponent } from "react";
import * as styles from "./Comment.css";
import React, { ReactElement, StatelessComponent } from "react";
import PermalinkButtonContainer from "../../containers/PermalinkButtonContainer";
import { Flex } from "talk-ui/components";
import * as styles from "./Comment.css";
import HTMLContent from "./HTMLContent";
import Timestamp from "./Timestamp";
import TopBar from "./TopBar";
@@ -16,20 +16,21 @@ export interface CommentProps {
} | null;
body: string | null;
createdAt: string;
footer?: ReactElement<any> | Array<ReactElement<any>>;
}
const Comment: StatelessComponent<CommentProps> = props => {
return (
<div role="article" className={styles.root}>
<TopBar>
<TopBar className={styles.topBar}>
{props.author &&
props.author.username && <Username>{props.author.username}</Username>}
<Timestamp>{props.createdAt}</Timestamp>
</TopBar>
<HTMLContent>{props.body || ""}</HTMLContent>
<div className={styles.footer}>
<PermalinkButtonContainer commentID={props.id} />
</div>
<Flex className={styles.footer} direction="row" itemGutter="half">
{props.footer}
</Flex>
</div>
);
};
@@ -0,0 +1,27 @@
import { Localized } from "fluent-react/compat";
import React, { EventHandler, MouseEvent, StatelessComponent } from "react";
import { Button, ButtonIcon, MatchMedia } from "talk-ui/components";
interface Props {
onClick?: EventHandler<MouseEvent<HTMLButtonElement>>;
active?: boolean;
}
const ReplyButton: StatelessComponent<Props> = props => (
<Button
onClick={props.onClick}
variant="ghost"
size="small"
active={props.active}
>
<MatchMedia gtWidth="xs">
<ButtonIcon>reply</ButtonIcon>
</MatchMedia>
<Localized id="comments-replyButton-reply">
<span>Reply</span>
</Localized>
</Button>
);
export default ReplyButton;
@@ -1,3 +0,0 @@
.root {
margin-bottom: calc(0.5 * var(--spacing-unit));
}
@@ -4,15 +4,13 @@ import { StatelessComponent } from "react";
import { Flex, MatchMedia } from "talk-ui/components";
import * as styles from "./TopBar.css";
export interface TopBarProps {
className?: string;
children: React.ReactNode;
}
const TopBar: StatelessComponent<TopBarProps> = props => {
const rootClassName = cn(styles.root, props.className);
const rootClassName = cn(props.className);
return (
<MatchMedia gtWidth="xs">
{matches => (
@@ -8,7 +8,8 @@ import CommentContainer from "../containers/CommentContainer";
import * as styles from "./PermalinkView.css";
export interface PermalinkViewProps {
comment: PropTypesOf<typeof CommentContainer>["data"] | null;
asset: PropTypesOf<typeof CommentContainer>["asset"];
comment: PropTypesOf<typeof CommentContainer>["comment"] | null;
showAllCommentsHref: string | null;
onShowAllComments: (e: MouseEvent<any>) => void;
}
@@ -16,6 +17,7 @@ export interface PermalinkViewProps {
const PermalinkView: StatelessComponent<PermalinkViewProps> = ({
showAllCommentsHref,
comment,
asset,
onShowAllComments,
}) => {
return (
@@ -42,7 +44,7 @@ const PermalinkView: StatelessComponent<PermalinkViewProps> = ({
<Typography>Comment not found</Typography>
</Localized>
)}
{comment && <CommentContainer data={comment} />}
{comment && <CommentContainer comment={comment} asset={asset} />}
</div>
);
};
@@ -1,17 +1,6 @@
.root {
}
.textarea {
composes: bodyCopy from "talk-ui/shared/typography.css";
display: block;
height: 150px;
width: 100%;
box-sizing: border-box;
padding: var(--spacing-unit);
border-radius: var(--round-corners);
resize: vertical;
}
.poweredBy {
margin-top: calc(-0.5 * var(--spacing-unit));
}
@@ -0,0 +1,99 @@
import { FormState } from "final-form";
import { Localized } from "fluent-react/compat";
import React, { EventHandler, MouseEvent, StatelessComponent } from "react";
import { Field, Form, FormSpy } from "react-final-form";
import { OnSubmit } from "talk-framework/lib/form";
import { required } from "talk-framework/lib/validation";
import {
AriaInfo,
Button,
Flex,
HorizontalGutter,
Typography,
} from "talk-ui/components";
import RTE from "./RTE";
interface FormProps {
body: string;
}
export interface ReplyCommentFormProps {
className?: string;
onSubmit: OnSubmit<FormProps>;
onCancel?: EventHandler<MouseEvent<any>>;
onChange?: (state: FormState) => void;
initialValues?: FormProps;
}
const ReplyCommentForm: StatelessComponent<ReplyCommentFormProps> = props => (
<Form onSubmit={props.onSubmit} initialValues={props.initialValues}>
{({ handleSubmit, submitting }) => (
<form
className={props.className}
autoComplete="off"
onSubmit={handleSubmit}
id="comments-replyCommentForm-form"
>
<FormSpy onChange={props.onChange} />
<HorizontalGutter>
<Field name="body" validate={required}>
{({ input, meta }) => (
<div>
<Localized id="comments-replyCommentForm-rteLabel">
<AriaInfo
component="label"
htmlFor="comments-replyCommentForm-field"
>
Write a reply
</AriaInfo>
</Localized>
<Localized
id="comments-replyCommentForm-rte"
attrs={{ placeholder: true }}
>
<RTE
inputId="comments-replyCommentForm-field"
onChange={({ html }) => input.onChange(html)}
value={input.value}
placeholder="Write a reply"
/>
</Localized>
{meta.touched &&
(meta.error || meta.submitError) && (
<Typography align="right" color="error" gutterBottom>
{meta.error || meta.submitError}
</Typography>
)}
</div>
)}
</Field>
<Flex direction="row" justifyContent="flex-end" itemGutter="half">
<Localized id="comments-replyCommentForm-cancel">
<Button
variant="outlined"
disabled={submitting}
onClick={props.onCancel}
>
Cancel
</Button>
</Localized>
<Localized id="comments-replyCommentForm-submit">
<Button
color="primary"
variant="filled"
disabled={submitting}
type="submit"
>
Submit
</Button>
</Localized>
</Flex>
</HorizontalGutter>
</form>
)}
</Form>
);
export default ReplyCommentForm;
@@ -9,9 +9,12 @@ import CommentContainer from "../containers/CommentContainer";
import Indent from "./Indent";
export interface ReplyListProps {
commentID: string;
asset: PropTypesOf<typeof CommentContainer>["asset"];
comment: {
id: string;
};
comments: ReadonlyArray<
{ id: string } & PropTypesOf<typeof CommentContainer>["data"]
{ id: string } & PropTypesOf<typeof CommentContainer>["comment"]
>;
onShowAll: () => void;
hasMore: boolean;
@@ -22,17 +25,21 @@ const ReplyList: StatelessComponent<ReplyListProps> = props => {
return (
<Indent>
<HorizontalGutter
id={`talk-comments-replyList-log--${props.commentID}`}
id={`talk-comments-replyList-log--${props.comment.id}`}
role="log"
>
{props.comments.map(comment => (
<CommentContainer key={comment.id} data={comment} />
<CommentContainer
key={comment.id}
comment={comment}
asset={props.asset}
/>
))}
{props.hasMore && (
<Localized id="comments-replyList-showAll">
<Button
id={`talk-comments-replyList-showAll--${props.commentID}`}
aria-controls={`talk-comments-replyList-log--${props.commentID}`}
id={`talk-comments-replyList-showAll--${props.comment.id}`}
aria-controls={`talk-comments-replyList-log--${props.comment.id}`}
onClick={props.onShowAll}
disabled={props.disableShowAll}
variant="outlined"
+9 -6
View File
@@ -13,10 +13,13 @@ import PostCommentFormFake from "./PostCommentFormFake";
import * as styles from "./Stream.css";
export interface StreamProps {
assetID: string;
isClosed?: boolean;
asset: {
id: string;
isClosed?: boolean;
} & PropTypesOf<typeof CommentContainer>["asset"] &
PropTypesOf<typeof ReplyListContainer>["asset"];
comments: ReadonlyArray<
{ id: string } & PropTypesOf<typeof CommentContainer>["data"] &
{ id: string } & PropTypesOf<typeof CommentContainer>["comment"] &
PropTypesOf<typeof ReplyListContainer>["comment"]
>;
onLoadMore?: () => void;
@@ -31,7 +34,7 @@ const Stream: StatelessComponent<StreamProps> = props => {
<HorizontalGutter size="half">
<UserBoxContainer user={props.user} />
{props.user ? (
<PostCommentFormContainer assetID={props.assetID} />
<PostCommentFormContainer assetID={props.asset.id} />
) : (
<PostCommentFormFake />
)}
@@ -43,8 +46,8 @@ const Stream: StatelessComponent<StreamProps> = props => {
>
{props.comments.map(comment => (
<HorizontalGutter key={comment.id}>
<CommentContainer data={comment} />
<ReplyListContainer comment={comment} />
<CommentContainer comment={comment} asset={props.asset} />
<ReplyListContainer comment={comment} asset={props.asset} />
</HorizontalGutter>
))}
{props.hasMore && (