mirror of
https://github.com/wassname/talk.git
synced 2026-07-05 00:18:23 +08:00
[CORL-260] Bring back sorting (#2186)
* feat: sort stream * feat: add FieldSet component to ui * feat: make accessible and add feature test * test: fix snapshots
This commit is contained in:
+4
-3
@@ -3,6 +3,7 @@ import React, { StatelessComponent } from "react";
|
||||
|
||||
import { DURATION_UNIT, DurationField } from "talk-framework/components";
|
||||
import {
|
||||
FieldSet,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
@@ -27,7 +28,7 @@ interface Props {
|
||||
const ClosingCommentStreamsConfig: StatelessComponent<Props> = ({
|
||||
disabled,
|
||||
}) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
|
||||
<Localized id="configure-general-closingCommentStreams-title">
|
||||
<Header container="legend">Closing Comment Streams</Header>
|
||||
</Localized>
|
||||
@@ -40,13 +41,13 @@ const ClosingCommentStreamsConfig: StatelessComponent<Props> = ({
|
||||
story’s publication
|
||||
</Typography>
|
||||
</Localized>
|
||||
<FormField container="fieldset">
|
||||
<FormField container={<FieldSet />}>
|
||||
<Localized id="configure-general-closingCommentStreams-closeCommentsAutomatically">
|
||||
<InputLabel container="legend">Close Comments Automatically</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField name="autoCloseStream" disabled={disabled} />
|
||||
</FormField>
|
||||
<FormField container="fieldset">
|
||||
<FormField container={<FieldSet />}>
|
||||
<Localized id="configure-general-closingCommentStreams-closeCommentsAfter">
|
||||
<InputLabel container="legend">Close Comments After</InputLabel>
|
||||
</Localized>
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import {
|
||||
validateWholeNumberGreaterThanOrEqual,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
FieldSet,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
@@ -38,7 +39,7 @@ const CommentEditingConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<FormField container={<FieldSet />}>
|
||||
<Localized id="configure-general-commentEditing-commentEditTimeFrame">
|
||||
<InputLabel container="legend">Comment Edit Timeframe</InputLabel>
|
||||
</Localized>
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ import {
|
||||
validateWholeNumberGreaterThan,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
FieldSet,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
@@ -38,7 +39,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const CommentLengthConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
|
||||
<Localized id="configure-general-commentLength-title">
|
||||
<Header container="legend">Comment Length</Header>
|
||||
</Localized>
|
||||
|
||||
+3
-2
@@ -4,6 +4,7 @@ import { Field } from "react-final-form";
|
||||
|
||||
import { ExternalLink } from "talk-framework/lib/i18n/components";
|
||||
import {
|
||||
FieldSet,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
@@ -21,12 +22,12 @@ interface Props {
|
||||
}
|
||||
|
||||
const GuidelinesConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
|
||||
<Localized id="configure-general-guidelines-title">
|
||||
<Header container="legend">Community Guidelines Summary</Header>
|
||||
</Localized>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<FormField container={<FieldSet />}>
|
||||
<Localized id="configure-general-guidelines-showCommunityGuidelines">
|
||||
<InputLabel container="legend">
|
||||
Show Community Guidelines Summary
|
||||
|
||||
+3
-2
@@ -10,6 +10,7 @@ import {
|
||||
Validator,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
FieldSet,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
@@ -38,7 +39,7 @@ const AkismetConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
return "";
|
||||
};
|
||||
return (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
|
||||
<Localized id="configure-moderation-akismet-title">
|
||||
<Header container="legend">Akismet Spam Detection Filter</Header>
|
||||
</Localized>
|
||||
@@ -57,7 +58,7 @@ const AkismetConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<FormField container={<FieldSet />}>
|
||||
<Localized id="configure-moderation-akismet-filter">
|
||||
<InputLabel container="legend">Spam Detection Filter</InputLabel>
|
||||
</Localized>
|
||||
|
||||
+4
-3
@@ -14,6 +14,7 @@ import {
|
||||
Validator,
|
||||
} from "talk-framework/lib/validation";
|
||||
import {
|
||||
FieldSet,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputDescription,
|
||||
@@ -49,7 +50,7 @@ const PerspectiveConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
return "";
|
||||
};
|
||||
return (
|
||||
<HorizontalGutter size="oneAndAHalf" container="fieldset">
|
||||
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
|
||||
<Localized id="configure-moderation-perspective-title">
|
||||
<Header container="legend">Perspective Toxic Comment Filter</Header>
|
||||
</Localized>
|
||||
@@ -66,7 +67,7 @@ const PerspectiveConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<FormField container={<FieldSet />}>
|
||||
<Localized id="configure-moderation-perspective-filter">
|
||||
<InputLabel container="legend">Spam Detection Filter</InputLabel>
|
||||
</Localized>
|
||||
@@ -126,7 +127,7 @@ const PerspectiveConfig: StatelessComponent<Props> = ({ disabled }) => {
|
||||
</Field>
|
||||
</FormField>
|
||||
|
||||
<FormField container="fieldset">
|
||||
<FormField container={<FieldSet />}>
|
||||
<Localized id="configure-moderation-perspective-allowStoreCommentData">
|
||||
<InputLabel container="legend">
|
||||
Allow Google to Store Comment Data
|
||||
|
||||
@@ -108,7 +108,7 @@ exports[`renders configure general 1`] = `
|
||||
data-testid="configure-generalContainer"
|
||||
>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
className="FieldSet-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
|
||||
@@ -116,7 +116,7 @@ exports[`renders configure general 1`] = `
|
||||
Community Guidelines Summary
|
||||
</legend>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="FieldSet-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
@@ -208,7 +208,7 @@ Markdown can be found
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
className="FieldSet-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
|
||||
@@ -358,7 +358,7 @@ Edited comments are marked as (Edited) on the comment stream and the
|
||||
moderation panel.
|
||||
</p>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="FieldSet-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
@@ -417,7 +417,7 @@ moderation panel.
|
||||
</select>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="SelectField-iconWrapper"
|
||||
className="SelectField-afterWrapper"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -431,7 +431,7 @@ moderation panel.
|
||||
</fieldset>
|
||||
</div>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
className="FieldSet-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
|
||||
@@ -444,7 +444,7 @@ moderation panel.
|
||||
Set comment streams to close after a defined period of time after a story’s publication
|
||||
</p>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="FieldSet-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
@@ -499,7 +499,7 @@ moderation panel.
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="FieldSet-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
@@ -558,7 +558,7 @@ moderation panel.
|
||||
</select>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="SelectField-iconWrapper"
|
||||
className="SelectField-afterWrapper"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
|
||||
@@ -108,7 +108,7 @@ exports[`renders configure moderation 1`] = `
|
||||
data-testid="configure-moderationContainer"
|
||||
>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
className="FieldSet-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
|
||||
@@ -131,7 +131,7 @@ the
|
||||
. If approved by a moderator, the comment will be published.
|
||||
</p>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="FieldSet-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
@@ -225,7 +225,7 @@ comment is toxic, according to Perspective API. By default the treshold is set t
|
||||
</div>
|
||||
</div>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="FieldSet-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
@@ -370,7 +370,7 @@ improve the API over time
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
className="FieldSet-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
|
||||
@@ -397,7 +397,7 @@ are placed in the
|
||||
the comment will be published.
|
||||
</p>
|
||||
<fieldset
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="FieldSet-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<legend
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
|
||||
@@ -19,6 +19,7 @@ exports[`init local state 1`] = `
|
||||
\\"network\\": {
|
||||
\\"__ref\\": \\"client:root.local.network\\"
|
||||
},
|
||||
\\"defaultStreamOrderBy\\": \\"CREATED_AT_DESC\\",
|
||||
\\"authPopup\\": {
|
||||
\\"__ref\\": \\"client:root.local.authPopup\\"
|
||||
},
|
||||
|
||||
@@ -39,6 +39,8 @@ export default async function initLocalState(
|
||||
if (query.commentID) {
|
||||
localRecord.setValue(query.commentID, "commentID");
|
||||
}
|
||||
// Set sort
|
||||
localRecord.setValue("CREATED_AT_DESC", "defaultStreamOrderBy");
|
||||
|
||||
// Create authPopup Record
|
||||
const authPopupRecord = createAndRetain(
|
||||
@@ -54,5 +56,8 @@ export default async function initLocalState(
|
||||
|
||||
// Set active tab
|
||||
localRecord.setValue("COMMENTS", "activeTab");
|
||||
|
||||
// Set sort
|
||||
localRecord.setValue("CREATED_AT_DESC", "defaultStreamOrderBy");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ type Local {
|
||||
storyID: String
|
||||
storyURL: String
|
||||
commentID: String
|
||||
defaultStreamOrderBy: COMMENT_SORT!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Environment, RecordSource } from "relay-runtime";
|
||||
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay";
|
||||
import { createRelayEnvironment } from "talk-framework/testHelpers";
|
||||
|
||||
import { commit } from "./SetStreamOrderByMutation";
|
||||
|
||||
let environment: Environment;
|
||||
const source: RecordSource = new RecordSource();
|
||||
|
||||
beforeAll(() => {
|
||||
environment = createRelayEnvironment({
|
||||
source,
|
||||
});
|
||||
});
|
||||
|
||||
it("Sets streamOrderBy", () => {
|
||||
const orderBy = "CREATED_AT_ASC";
|
||||
commit(environment, { orderBy });
|
||||
expect(source.get(LOCAL_ID)!.streamOrderBy).toEqual(orderBy);
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { commitLocalUpdate, Environment } from "relay-runtime";
|
||||
|
||||
import { createMutationContainer, LOCAL_ID } from "talk-framework/lib/relay";
|
||||
|
||||
export interface SetStreamOrderByInput {
|
||||
orderBy:
|
||||
| "CREATED_AT_ASC"
|
||||
| "CREATED_AT_DESC"
|
||||
| "REPLIES_DESC"
|
||||
| "RESPECT_DESC"
|
||||
| "%future added value";
|
||||
}
|
||||
|
||||
export type SetStreamOrderByMutation = (
|
||||
input: SetStreamOrderByInput
|
||||
) => Promise<void>;
|
||||
|
||||
export async function commit(
|
||||
environment: Environment,
|
||||
input: SetStreamOrderByInput
|
||||
) {
|
||||
return commitLocalUpdate(environment, store => {
|
||||
const record = store.get(LOCAL_ID)!;
|
||||
record.setValue(input.orderBy, "streamOrderBy");
|
||||
});
|
||||
}
|
||||
|
||||
export const withSetStreamOrderByMutation = createMutationContainer(
|
||||
"setStreamOrderBy",
|
||||
commit
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
.root {
|
||||
border: 0;
|
||||
border-top: 1px solid var(--palette-divider);
|
||||
margin: var(--spacing-unit) 0;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import * as styles from "./Divider.css";
|
||||
|
||||
const Divider: StatelessComponent = () => <hr className={styles.root} />;
|
||||
export default Divider;
|
||||
@@ -0,0 +1,8 @@
|
||||
.mobileSelect {
|
||||
font-size: 0;
|
||||
padding: 11px;
|
||||
width: 0px;
|
||||
}
|
||||
.mobileAfterWrapper {
|
||||
right: 5px;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { LocalizationProvider } from "fluent-react/compat";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { UIContext, UIContextProps } from "talk-ui/components";
|
||||
|
||||
import SortMenu from "./SortMenu";
|
||||
|
||||
it("renders correctly on small screens", () => {
|
||||
const props: PropTypesOf<typeof SortMenu> = {
|
||||
orderBy: "CREATED_AT_ASC",
|
||||
onChange: noop,
|
||||
};
|
||||
|
||||
const context: UIContextProps = {
|
||||
mediaQueryValues: {
|
||||
width: 320,
|
||||
},
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<LocalizationProvider bundles={[]}>
|
||||
<UIContext.Provider value={context}>
|
||||
<SortMenu {...props} />
|
||||
</UIContext.Provider>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly on big screens", () => {
|
||||
const props: PropTypesOf<typeof SortMenu> = {
|
||||
orderBy: "CREATED_AT_ASC",
|
||||
onChange: noop,
|
||||
};
|
||||
|
||||
const context: UIContextProps = {
|
||||
mediaQueryValues: {
|
||||
width: 1600,
|
||||
},
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<LocalizationProvider bundles={[]}>
|
||||
<UIContext.Provider value={context}>
|
||||
<SortMenu {...props} />
|
||||
</UIContext.Provider>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import {
|
||||
Flex,
|
||||
Icon,
|
||||
MatchMedia,
|
||||
Option,
|
||||
SelectField,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Divider from "./Divider";
|
||||
import * as styles from "./SortMenu.css";
|
||||
|
||||
interface Props {
|
||||
orderBy:
|
||||
| "CREATED_AT_ASC"
|
||||
| "CREATED_AT_DESC"
|
||||
| "REPLIES_DESC"
|
||||
| "RESPECT_DESC"
|
||||
| "%future added value";
|
||||
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
}
|
||||
|
||||
const SortMenu: StatelessComponent<Props> = props => (
|
||||
<MatchMedia ltWidth="sm">
|
||||
{matches => (
|
||||
<div>
|
||||
<Flex justifyContent="flex-end" alignItems="center" itemGutter>
|
||||
{!matches && (
|
||||
<Localized id="comments-sortMenu-sortBy">
|
||||
<Typography
|
||||
variant="bodyCopyBold"
|
||||
container={<label htmlFor="talk-comments-sortMenu" />}
|
||||
>
|
||||
Sort By
|
||||
</Typography>
|
||||
</Localized>
|
||||
)}
|
||||
<SelectField
|
||||
id="talk-comments-sortMenu"
|
||||
value={props.orderBy}
|
||||
onChange={props.onChange}
|
||||
afterWrapper={(matches && <Icon>sort</Icon>) || undefined}
|
||||
classes={{
|
||||
select: (matches && styles.mobileSelect) || undefined,
|
||||
afterWrapper: (matches && styles.mobileAfterWrapper) || undefined,
|
||||
}}
|
||||
>
|
||||
<Localized id="comments-sortMenu-newest">
|
||||
<Option value="CREATED_AT_DESC">Newest</Option>
|
||||
</Localized>
|
||||
<Localized id="comments-sortMenu-oldest">
|
||||
<Option value="CREATED_AT_ASC">Oldest</Option>
|
||||
</Localized>
|
||||
<Localized id="comments-sortMenu-mostReplies">
|
||||
<Option value="REPLIES_DESC">Most Replies</Option>
|
||||
</Localized>
|
||||
<Localized id="comments-sortMenu-mostReactions">
|
||||
<Option value="RESPECT_DESC">Most Reactions</Option>
|
||||
</Localized>
|
||||
</SelectField>
|
||||
</Flex>
|
||||
<Divider />
|
||||
</div>
|
||||
)}
|
||||
</MatchMedia>
|
||||
);
|
||||
export default SortMenu;
|
||||
@@ -27,6 +27,8 @@ it("renders correctly", () => {
|
||||
disableLoadMore: false,
|
||||
hasMore: false,
|
||||
me: null,
|
||||
orderBy: "CREATED_AT_ASC",
|
||||
onChangeOrderBy: noop,
|
||||
};
|
||||
const wrapper = shallow(<StreamN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
@@ -50,6 +52,8 @@ describe("when use is logged in", () => {
|
||||
label: "Respect",
|
||||
},
|
||||
},
|
||||
orderBy: "CREATED_AT_ASC",
|
||||
onChangeOrderBy: noop,
|
||||
};
|
||||
const wrapper = shallow(<StreamN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
@@ -73,6 +77,8 @@ describe("when there is more", () => {
|
||||
disableLoadMore: false,
|
||||
hasMore: true,
|
||||
me: null,
|
||||
orderBy: "CREATED_AT_ASC",
|
||||
onChangeOrderBy: noop,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<StreamN {...props} />);
|
||||
|
||||
@@ -4,12 +4,13 @@ import { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import UserBoxContainer from "talk-stream/containers/UserBoxContainer";
|
||||
import { Button, HorizontalGutter } from "talk-ui/components";
|
||||
import { Button, HorizontalGutter, Spinner } from "talk-ui/components";
|
||||
|
||||
import CommentContainer from "../containers/CommentContainer";
|
||||
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
|
||||
import ReplyListContainer from "../containers/ReplyListContainer";
|
||||
import PostCommentFormFake from "./PostCommentFormFake";
|
||||
import SortMenu from "./SortMenu";
|
||||
|
||||
import styles from "./Stream.css";
|
||||
|
||||
@@ -34,6 +35,9 @@ export interface StreamProps {
|
||||
PropTypesOf<typeof CommentContainer>["me"] &
|
||||
PropTypesOf<typeof ReplyListContainer>["me"]
|
||||
| null;
|
||||
orderBy: PropTypesOf<typeof SortMenu>["orderBy"];
|
||||
onChangeOrderBy: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
refetching?: boolean;
|
||||
}
|
||||
|
||||
const Stream: StatelessComponent<StreamProps> = props => {
|
||||
@@ -47,43 +51,49 @@ const Stream: StatelessComponent<StreamProps> = props => {
|
||||
<PostCommentFormFake />
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter
|
||||
id="talk-comments-stream-log"
|
||||
data-testid="comments-stream-log"
|
||||
role="log"
|
||||
aria-live="polite"
|
||||
>
|
||||
{props.comments.map(comment => (
|
||||
<HorizontalGutter key={comment.id}>
|
||||
<CommentContainer
|
||||
me={props.me}
|
||||
settings={props.settings}
|
||||
comment={comment}
|
||||
story={props.story}
|
||||
/>
|
||||
<ReplyListContainer
|
||||
settings={props.settings}
|
||||
me={props.me}
|
||||
comment={comment}
|
||||
story={props.story}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
))}
|
||||
{props.hasMore && (
|
||||
<Localized id="comments-stream-loadMore">
|
||||
<Button
|
||||
id={"talk-comments-stream-loadMore"}
|
||||
onClick={props.onLoadMore}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
disabled={props.disableLoadMore}
|
||||
aria-controls="talk-comments-stream-log"
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
</Localized>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
{props.comments.length > 0 && (
|
||||
<SortMenu orderBy={props.orderBy} onChange={props.onChangeOrderBy} />
|
||||
)}
|
||||
{props.refetching && <Spinner />}
|
||||
{!props.refetching && (
|
||||
<HorizontalGutter
|
||||
id="talk-comments-stream-log"
|
||||
data-testid="comments-stream-log"
|
||||
role="log"
|
||||
aria-live="polite"
|
||||
>
|
||||
{props.comments.map(comment => (
|
||||
<HorizontalGutter key={comment.id}>
|
||||
<CommentContainer
|
||||
me={props.me}
|
||||
settings={props.settings}
|
||||
comment={comment}
|
||||
story={props.story}
|
||||
/>
|
||||
<ReplyListContainer
|
||||
settings={props.settings}
|
||||
me={props.me}
|
||||
comment={comment}
|
||||
story={props.story}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
))}
|
||||
{props.hasMore && (
|
||||
<Localized id="comments-stream-loadMore">
|
||||
<Button
|
||||
id={"talk-comments-stream-loadMore"}
|
||||
onClick={props.onLoadMore}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
disabled={props.disableLoadMore}
|
||||
aria-controls="talk-comments-stream-log"
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
</Localized>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly on big screens 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-justifyFlexEnd Flex-alignCenter"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
|
||||
htmlFor="talk-comments-sortMenu"
|
||||
>
|
||||
Sort By
|
||||
</label>
|
||||
<span
|
||||
className="SelectField-root"
|
||||
>
|
||||
<select
|
||||
className="SelectField-select undefined"
|
||||
id="talk-comments-sortMenu"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
value="CREATED_AT_ASC"
|
||||
>
|
||||
<option
|
||||
value="CREATED_AT_DESC"
|
||||
>
|
||||
Newest
|
||||
</option>
|
||||
<option
|
||||
value="CREATED_AT_ASC"
|
||||
>
|
||||
Oldest
|
||||
</option>
|
||||
<option
|
||||
value="REPLIES_DESC"
|
||||
>
|
||||
Most Replies
|
||||
</option>
|
||||
<option
|
||||
value="RESPECT_DESC"
|
||||
>
|
||||
Most Reactions
|
||||
</option>
|
||||
</select>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="SelectField-afterWrapper undefined"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
expand_more
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<hr
|
||||
className="Divider-root"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders correctly on small screens 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-justifyFlexEnd Flex-alignCenter"
|
||||
>
|
||||
<span
|
||||
className="SelectField-root"
|
||||
>
|
||||
<select
|
||||
className="SelectField-select SortMenu-mobileSelect"
|
||||
id="talk-comments-sortMenu"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
value="CREATED_AT_ASC"
|
||||
>
|
||||
<option
|
||||
value="CREATED_AT_DESC"
|
||||
>
|
||||
Newest
|
||||
</option>
|
||||
<option
|
||||
value="CREATED_AT_ASC"
|
||||
>
|
||||
Oldest
|
||||
</option>
|
||||
<option
|
||||
value="REPLIES_DESC"
|
||||
>
|
||||
Most Replies
|
||||
</option>
|
||||
<option
|
||||
value="RESPECT_DESC"
|
||||
>
|
||||
Most Reactions
|
||||
</option>
|
||||
</select>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="SelectField-afterWrapper SortMenu-mobileAfterWrapper"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
sort
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<hr
|
||||
className="Divider-root"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -21,6 +21,10 @@ exports[`renders correctly 1`] = `
|
||||
/>
|
||||
<PostCommentFormFake />
|
||||
</ForwardRef(forwardRef)>
|
||||
<SortMenu
|
||||
onChange={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
/>
|
||||
<ForwardRef(forwardRef)
|
||||
aria-live="polite"
|
||||
data-testid="comments-stream-log"
|
||||
@@ -148,6 +152,10 @@ exports[`when there is more disables load more button 1`] = `
|
||||
/>
|
||||
<PostCommentFormFake />
|
||||
</ForwardRef(forwardRef)>
|
||||
<SortMenu
|
||||
onChange={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
/>
|
||||
<ForwardRef(forwardRef)
|
||||
aria-live="polite"
|
||||
data-testid="comments-stream-log"
|
||||
@@ -289,6 +297,10 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
/>
|
||||
<PostCommentFormFake />
|
||||
</ForwardRef(forwardRef)>
|
||||
<SortMenu
|
||||
onChange={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
/>
|
||||
<ForwardRef(forwardRef)
|
||||
aria-live="polite"
|
||||
data-testid="comments-stream-log"
|
||||
@@ -432,6 +444,10 @@ exports[`when use is logged in renders correctly 1`] = `
|
||||
storyID="story-id"
|
||||
/>
|
||||
</ForwardRef(forwardRef)>
|
||||
<SortMenu
|
||||
onChange={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
/>
|
||||
<ForwardRef(forwardRef)
|
||||
aria-live="polite"
|
||||
data-testid="comments-stream-log"
|
||||
|
||||
@@ -31,6 +31,7 @@ it("renders correctly", () => {
|
||||
hasMore: noop,
|
||||
isLoading: noop,
|
||||
} as any,
|
||||
defaultOrderBy: "CREATED_AT_ASC",
|
||||
};
|
||||
const wrapper = shallow(<StreamContainerN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
@@ -58,6 +59,7 @@ describe("when has more comments", () => {
|
||||
isLoading: () => false,
|
||||
loadMore: (_: any, callback: () => void) => (finishLoading = callback),
|
||||
} as any,
|
||||
defaultOrderBy: "CREATED_AT_ASC",
|
||||
};
|
||||
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { graphql, RelayPaginationProp } from "react-relay";
|
||||
|
||||
import { withPaginationContainer } from "talk-framework/lib/relay";
|
||||
@@ -18,6 +18,7 @@ interface InnerProps {
|
||||
settings: SettingsData;
|
||||
me: MeData | null;
|
||||
relay: RelayPaginationProp;
|
||||
defaultOrderBy: COMMENT_SORT;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
@@ -32,6 +33,27 @@ graphql`
|
||||
export class StreamContainer extends React.Component<InnerProps> {
|
||||
public state = {
|
||||
disableLoadMore: false,
|
||||
refetching: false,
|
||||
};
|
||||
private orderBy = this.props.defaultOrderBy;
|
||||
|
||||
private handleOnChangeOrderBy = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
this.orderBy = e.target.value as COMMENT_SORT;
|
||||
this.setState({ refetching: true });
|
||||
this.props.relay.refetchConnection(
|
||||
5,
|
||||
err => {
|
||||
if (err) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
this.setState({ refetching: false });
|
||||
},
|
||||
{
|
||||
orderBy: e.target.value as COMMENT_SORT,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
public render() {
|
||||
@@ -45,6 +67,9 @@ export class StreamContainer extends React.Component<InnerProps> {
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
disableLoadMore={this.state.disableLoadMore}
|
||||
me={this.props.me}
|
||||
orderBy={this.orderBy}
|
||||
onChangeOrderBy={this.handleOnChangeOrderBy}
|
||||
refetching={this.state.refetching}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+12
@@ -14,7 +14,10 @@ exports[`renders correctly 1`] = `
|
||||
}
|
||||
disableLoadMore={false}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
@@ -61,7 +64,10 @@ exports[`when has more comments renders hasMore 1`] = `
|
||||
disableLoadMore={false}
|
||||
hasMore={true}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
@@ -108,7 +114,10 @@ exports[`when has more comments when loading more disables load more button 1`]
|
||||
disableLoadMore={true}
|
||||
hasMore={true}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
@@ -155,7 +164,10 @@ exports[`when has more comments when loading more enable load more button after
|
||||
disableLoadMore={false}
|
||||
hasMore={true}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
|
||||
@@ -11,7 +11,7 @@ it("renders stream container", () => {
|
||||
error: null,
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(React.createElement(() => render(data)));
|
||||
renderer.render(React.createElement(() => render(data, "CREATED_AT_ASC")));
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ it("renders loading", () => {
|
||||
error: null,
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(React.createElement(() => render(data)));
|
||||
renderer.render(React.createElement(() => render(data, "CREATED_AT_ASC")));
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -31,6 +31,6 @@ it("renders error", () => {
|
||||
error: new Error("error"),
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(React.createElement(() => render(data)));
|
||||
renderer.render(React.createElement(() => render(data, "CREATED_AT_ASC")));
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -11,20 +11,20 @@ import { StreamQueryLocal as Local } from "talk-stream/__generated__/StreamQuery
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
import StreamContainer from "../containers/StreamContainer";
|
||||
|
||||
interface InnerProps {
|
||||
interface Props {
|
||||
local: Local;
|
||||
}
|
||||
|
||||
export const render = ({
|
||||
error,
|
||||
props,
|
||||
}: ReadyState<QueryTypes["response"]>) => {
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
export const render = (
|
||||
data: ReadyState<QueryTypes["response"]>,
|
||||
defaultStreamOrderBy: Props["local"]["defaultStreamOrderBy"]
|
||||
) => {
|
||||
if (data.error) {
|
||||
return <div>{data.error.message}</div>;
|
||||
}
|
||||
|
||||
if (props) {
|
||||
if (!props.story) {
|
||||
if (data.props) {
|
||||
if (!data.props.story) {
|
||||
return (
|
||||
<Localized id="comments-streamQuery-storyNotFound">
|
||||
<div>Story not found</div>
|
||||
@@ -33,9 +33,10 @@ export const render = ({
|
||||
}
|
||||
return (
|
||||
<StreamContainer
|
||||
settings={props.settings}
|
||||
me={props.me}
|
||||
story={props.story}
|
||||
settings={data.props.settings}
|
||||
me={data.props.me}
|
||||
story={data.props.story}
|
||||
defaultOrderBy={defaultStreamOrderBy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -47,36 +48,45 @@ export const render = ({
|
||||
);
|
||||
};
|
||||
|
||||
const StreamQuery: StatelessComponent<InnerProps> = ({
|
||||
local: { storyID, storyURL },
|
||||
}) => (
|
||||
<QueryRenderer<QueryTypes>
|
||||
query={graphql`
|
||||
query StreamQuery($storyID: ID, $storyURL: String) {
|
||||
me {
|
||||
...StreamContainer_me
|
||||
const StreamQuery: StatelessComponent<Props> = props => {
|
||||
const {
|
||||
local: { storyID, storyURL, defaultStreamOrderBy },
|
||||
} = props;
|
||||
return (
|
||||
<QueryRenderer<QueryTypes>
|
||||
query={graphql`
|
||||
query StreamQuery(
|
||||
$storyID: ID
|
||||
$storyURL: String
|
||||
$streamOrderBy: COMMENT_SORT
|
||||
) {
|
||||
me {
|
||||
...StreamContainer_me
|
||||
}
|
||||
story(id: $storyID, url: $storyURL) {
|
||||
...StreamContainer_story @arguments(orderBy: $streamOrderBy)
|
||||
}
|
||||
settings {
|
||||
...StreamContainer_settings
|
||||
}
|
||||
}
|
||||
story(id: $storyID, url: $storyURL) {
|
||||
...StreamContainer_story
|
||||
}
|
||||
settings {
|
||||
...StreamContainer_settings
|
||||
}
|
||||
}
|
||||
`}
|
||||
variables={{
|
||||
storyID,
|
||||
storyURL,
|
||||
}}
|
||||
render={render}
|
||||
/>
|
||||
);
|
||||
`}
|
||||
variables={{
|
||||
storyID,
|
||||
storyURL,
|
||||
streamOrderBy: defaultStreamOrderBy,
|
||||
}}
|
||||
render={data => render(data, props.local.defaultStreamOrderBy)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const enhanced = withLocalStateContainer(
|
||||
graphql`
|
||||
fragment StreamQueryLocal on Local {
|
||||
storyID
|
||||
storyURL
|
||||
defaultStreamOrderBy
|
||||
}
|
||||
`
|
||||
)(StreamQuery);
|
||||
|
||||
@@ -16,6 +16,7 @@ exports[`renders loading 1`] = `
|
||||
|
||||
exports[`renders stream container 1`] = `
|
||||
<Relay(StreamContainer)
|
||||
defaultOrderBy="CREATED_AT_ASC"
|
||||
story={Object {}}
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -171,6 +171,65 @@ exports[`renders app with comment stream 1`] = `
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-justifyFlexEnd Flex-alignCenter"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
|
||||
htmlFor="talk-comments-sortMenu"
|
||||
>
|
||||
Sort By
|
||||
</label>
|
||||
<span
|
||||
className="SelectField-root"
|
||||
>
|
||||
<select
|
||||
className="SelectField-select undefined"
|
||||
id="talk-comments-sortMenu"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
value="CREATED_AT_DESC"
|
||||
>
|
||||
<option
|
||||
value="CREATED_AT_DESC"
|
||||
>
|
||||
Newest
|
||||
</option>
|
||||
<option
|
||||
value="CREATED_AT_ASC"
|
||||
>
|
||||
Oldest
|
||||
</option>
|
||||
<option
|
||||
value="REPLIES_DESC"
|
||||
>
|
||||
Most Replies
|
||||
</option>
|
||||
<option
|
||||
value="RESPECT_DESC"
|
||||
>
|
||||
Most Reactions
|
||||
</option>
|
||||
</select>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="SelectField-afterWrapper undefined"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
expand_more
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<hr
|
||||
className="Divider-root"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders app with comment stream 1`] = `
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
data-testid="comments-stream-log"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
data-testid="comment-comment-2"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="Indent-root"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
className="Comment-root"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-directionRow"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
|
||||
>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Hey!",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifySpaceBetween"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
id="comments-commentContainer-replyButton-comment-2"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Reply
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
className="Popover-root"
|
||||
>
|
||||
<button
|
||||
aria-controls="permalink-popover-comment-2"
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Share
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
aria-labelledby="permalink-popover-comment-2-ariainfo"
|
||||
id="permalink-popover-comment-2"
|
||||
role="popup"
|
||||
>
|
||||
<div
|
||||
className="AriaInfo-root"
|
||||
id="permalink-popover-comment-2-ariainfo"
|
||||
>
|
||||
A dialog showing a permalink to the comment
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Respect
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Report
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
data-testid="comment-comment-3"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="Indent-root"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
className="Comment-root"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-directionRow"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
|
||||
>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Comment Body 3",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifySpaceBetween"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
id="comments-commentContainer-replyButton-comment-3"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Reply
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
className="Popover-root"
|
||||
>
|
||||
<button
|
||||
aria-controls="permalink-popover-comment-3"
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Share
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
aria-labelledby="permalink-popover-comment-3-ariainfo"
|
||||
id="permalink-popover-comment-3"
|
||||
role="popup"
|
||||
>
|
||||
<div
|
||||
className="AriaInfo-root"
|
||||
id="permalink-popover-comment-3-ariainfo"
|
||||
>
|
||||
A dialog showing a permalink to the comment
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Respect
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Report
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -8,6 +8,7 @@ export default function create(params: CreateParams) {
|
||||
localRecord.setValue("COMMENTS", "activeTab");
|
||||
localRecord.setValue(false, "loggedIn");
|
||||
localRecord.setValue("jti", "accessTokenJTI");
|
||||
localRecord.setValue("CREATED_AT_DESC", "defaultStreamOrderBy");
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import {
|
||||
createSinonStub,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import { settings, stories } from "../fixtures";
|
||||
import create from "./create";
|
||||
|
||||
const createTestRenderer = async (resolver: any = {}) => {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
...resolver.Query,
|
||||
settings: sinon.stub().returns(settings),
|
||||
},
|
||||
};
|
||||
const { testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(stories[0].id, "storyID");
|
||||
},
|
||||
});
|
||||
return {
|
||||
testRenderer,
|
||||
};
|
||||
};
|
||||
|
||||
it("renders app with comment stream", async () => {
|
||||
const commentsQueryStub = createSinonStub(
|
||||
s =>
|
||||
s.onFirstCall().callsFake((input: any) => {
|
||||
expect(input).toEqual({ first: 5, orderBy: "CREATED_AT_DESC" });
|
||||
return stories[0].comments;
|
||||
}),
|
||||
s =>
|
||||
s.onSecondCall().callsFake((input: any) => {
|
||||
expect(input).toEqual({
|
||||
after: null,
|
||||
first: 5,
|
||||
orderBy: "CREATED_AT_ASC",
|
||||
});
|
||||
return stories[1].comments;
|
||||
})
|
||||
);
|
||||
const storyQueryStub = createSinonStub(s =>
|
||||
s.callsFake((_: any, input: any) => {
|
||||
expect(input.id).toEqual("story-1");
|
||||
expect(input.url).toBeFalsy();
|
||||
return {
|
||||
...stories[0],
|
||||
comments: commentsQueryStub,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
Query: {
|
||||
story: storyQueryStub,
|
||||
},
|
||||
});
|
||||
|
||||
let streamLog = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("comments-stream-log")
|
||||
);
|
||||
const selectField = within(testRenderer.root).getByLabelText("Sort By");
|
||||
const oldestOption = within(selectField).getByText("Oldest");
|
||||
|
||||
selectField.props.onChange({
|
||||
target: { value: oldestOption.props.value.toString() },
|
||||
});
|
||||
|
||||
streamLog = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("comments-stream-log")
|
||||
);
|
||||
expect(within(streamLog).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
.root {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: AriaInfo
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from "docz";
|
||||
|
||||
# FieldSet
|
||||
|
||||
Simple `fieldset` with removed styling for accessibility purposes.
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import FieldSet from "./FieldSet";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof FieldSet> = {
|
||||
children: "content",
|
||||
};
|
||||
const renderer = TestRenderer.create(<FieldSet {...props} />);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import cn from "classnames";
|
||||
import React, { AllHTMLAttributes, Ref, StatelessComponent } from "react";
|
||||
|
||||
import { withForwardRef, withStyles } from "talk-ui/hocs";
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import styles from "./FieldSet.css";
|
||||
|
||||
interface InnerProps extends AllHTMLAttributes<HTMLElement> {
|
||||
/**
|
||||
* This prop can be used to add custom classnames.
|
||||
* It is handled by the `withStyles `HOC.
|
||||
*/
|
||||
classes: typeof styles;
|
||||
/** Internal: Forwarded Ref */
|
||||
forwardRef?: Ref<HTMLFieldSetElement>;
|
||||
}
|
||||
|
||||
const FieldSet: StatelessComponent<InnerProps> = props => {
|
||||
const { className, classes, forwardRef: ref, ...rest } = props;
|
||||
const rootClassName = cn(classes.root, className);
|
||||
return <fieldset className={rootClassName} {...rest} ref={ref} />;
|
||||
};
|
||||
const enhanced = withForwardRef(withStyles(styles)(FieldSet));
|
||||
export type FieldSetProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<fieldset
|
||||
className="FieldSet-root"
|
||||
>
|
||||
content
|
||||
</fieldset>
|
||||
`;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./FieldSet";
|
||||
@@ -3,7 +3,7 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
.afterWrapper {
|
||||
position: absolute;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
@@ -60,6 +60,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.iconWrapperDisabled {
|
||||
.afterWrapperDisabled {
|
||||
color: var(--palette-text-secondary);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ export interface SelectFieldProps {
|
||||
onFocus: EventHandler<FocusEvent<HTMLSelectElement>>;
|
||||
onBlur: EventHandler<FocusEvent<HTMLSelectElement>>;
|
||||
keyboardFocus: boolean;
|
||||
|
||||
afterWrapper?: React.ReactElement<any>;
|
||||
}
|
||||
|
||||
const SelectField: StatelessComponent<SelectFieldProps> = props => {
|
||||
@@ -48,6 +50,7 @@ const SelectField: StatelessComponent<SelectFieldProps> = props => {
|
||||
keyboardFocus,
|
||||
children,
|
||||
disabled,
|
||||
afterWrapper,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@@ -56,8 +59,8 @@ const SelectField: StatelessComponent<SelectFieldProps> = props => {
|
||||
[classes.keyboardFocus]: keyboardFocus,
|
||||
});
|
||||
|
||||
const iconWrapperClassName = cn(classes.iconWrapper, {
|
||||
[classes.iconWrapperDisabled]: disabled,
|
||||
const afterWrapperClassName = cn(classes.afterWrapper, {
|
||||
[classes.afterWrapperDisabled]: disabled,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -65,12 +68,16 @@ const SelectField: StatelessComponent<SelectFieldProps> = props => {
|
||||
<select className={selectClassName} disabled={disabled} {...rest}>
|
||||
{children}
|
||||
</select>
|
||||
<span className={iconWrapperClassName} aria-hidden>
|
||||
<Icon>expand_more</Icon>
|
||||
<span className={afterWrapperClassName} aria-hidden>
|
||||
{afterWrapper}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
SelectField.defaultProps = {
|
||||
afterWrapper: <Icon>expand_more</Icon>,
|
||||
};
|
||||
|
||||
const enhanced = withStyles(styles)(withKeyboardFocus(SelectField));
|
||||
export default enhanced;
|
||||
|
||||
@@ -17,7 +17,7 @@ exports[`renders correctly 1`] = `
|
||||
/>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="SelectField-iconWrapper SelectField-iconWrapperDisabled"
|
||||
className="SelectField-afterWrapper SelectField-afterWrapperDisabled"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
|
||||
@@ -17,6 +17,7 @@ export { default as Popup } from "./Popup";
|
||||
export { default as FormField } from "./FormField";
|
||||
export { default as InputDescription } from "./InputDescription";
|
||||
export { default as Spinner } from "./Spinner";
|
||||
export { default as FieldSet } from "./FieldSet";
|
||||
export { default as HorizontalGutter } from "./HorizontalGutter";
|
||||
export { default as Icon } from "./Icon";
|
||||
export { default as AriaInfo } from "./AriaInfo";
|
||||
|
||||
@@ -85,6 +85,12 @@ comments-replyTo = Replying to: <username></username>
|
||||
comments-reportButton-report = Report
|
||||
comments-reportButton-reported = Reported
|
||||
|
||||
comments-sortMenu-sortBy = Sort By
|
||||
comments-sortMenu-newest = Newest
|
||||
comments-sortMenu-oldest = Oldest
|
||||
comments-sortMenu-mostReplies = Most Replies
|
||||
comments-sortMenu-mostReactions = Most Reactions
|
||||
|
||||
## Profile Tab
|
||||
profile-historyComment-viewConversation = View Conversation
|
||||
profile-historyComment-replies = Replies {$replyCount}
|
||||
|
||||
Reference in New Issue
Block a user