mirror of
https://github.com/wassname/talk.git
synced 2026-07-27 11:28:12 +08:00
[CORL-161, CORL-240] Message Box (#2222)
* feat: Message Box * test: do all the testing stuff * fix: removed old messageBox * Update src/core/client/stream/tabs/configure/components/MessageBoxConfig.tsx Co-Authored-By: wyattjoh <wyattjoh@gmail.com> * fix: addressed space error
This commit is contained in:
+2
-2
@@ -2,6 +2,7 @@ import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent, Suspense } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { MarkdownEditor } from "talk-framework/components/loadables";
|
||||
import {
|
||||
HorizontalGutter,
|
||||
Spinner,
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
} from "talk-ui/components";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
import LazyMarkdown from "./LazyMarkdown";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
@@ -39,7 +39,7 @@ const ClosedStreamMessageConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LazyMarkdown
|
||||
<MarkdownEditor
|
||||
id="configure-general-closedStreamMessage-content"
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
|
||||
+3
-2
@@ -14,8 +14,9 @@ import {
|
||||
} from "talk-ui/components";
|
||||
|
||||
import OnOffField from "talk-admin/routes/configure/components/OnOffField";
|
||||
import { MarkdownEditor } from "talk-framework/components/loadables";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
import LazyMarkdown from "./LazyMarkdown";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
@@ -60,7 +61,7 @@ const GuidelinesConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LazyMarkdown
|
||||
<MarkdownEditor
|
||||
id="configure-general-guidelines-content"
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
|
||||
+3
-2
@@ -13,8 +13,9 @@ import {
|
||||
} from "talk-ui/components";
|
||||
|
||||
import OnOffField from "talk-admin/routes/configure/components/OnOffField";
|
||||
import { MarkdownEditor } from "talk-framework/components/loadables";
|
||||
|
||||
import Header from "../../../components/Header";
|
||||
import LazyMarkdown from "./LazyMarkdown";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
@@ -73,7 +74,7 @@ const SitewideCommentingConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LazyMarkdown
|
||||
<MarkdownEditor
|
||||
id="configure-general-sitewideCommenting-message"
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
|
||||
+1
-1
@@ -3,10 +3,10 @@ import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { GeneralConfigRouteContainerQueryResponse } from "talk-admin/__generated__/GeneralConfigRouteContainerQuery.graphql";
|
||||
import { loadMarkdownEditor } from "talk-framework/components/loadables";
|
||||
import { withRouteConfig } from "talk-framework/lib/router";
|
||||
import { Delay, Spinner } from "talk-ui/components";
|
||||
|
||||
import { loadMarkdownEditor } from "../components/LazyMarkdown";
|
||||
import GeneralConfigContainer from "./GeneralConfigContainer";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { merge } from "lodash";
|
||||
|
||||
export const settings = {
|
||||
id: "settings",
|
||||
moderation: "POST",
|
||||
premodLinksEnable: false,
|
||||
wordList: {
|
||||
suspect: ["idiot", "stupid"],
|
||||
banned: ["fuck"],
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
export function loadMarkdownEditor() {
|
||||
return import("talk-framework/components/loadables/MarkdownEditor" /* webpackChunkName: "markdownEditor" */);
|
||||
return import("./MarkdownEditor" /* webpackChunkName: "markdownEditor" */);
|
||||
}
|
||||
|
||||
export default React.lazy(loadMarkdownEditor);
|
||||
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
default as MarkdownEditor,
|
||||
loadMarkdownEditor,
|
||||
} from "./MarkdownEditor/LazyMarkdownEditory";
|
||||
@@ -0,0 +1,10 @@
|
||||
.root {
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: calc(0.5 * var(--spacing-unit) + 1px) var(--spacing-unit);
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
background-color: var(--palette-text-primary);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import MessageBox from "./MessageBox";
|
||||
import MessageBoxIcon from "./MessageBoxIcon";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof MessageBox> = {
|
||||
className: "custom",
|
||||
children: "Hello World",
|
||||
};
|
||||
const renderer = TestRenderer.create(<MessageBox {...props} />);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders icon", () => {
|
||||
const renderer = TestRenderer.create(
|
||||
<MessageBox>
|
||||
<MessageBoxIcon>alert</MessageBoxIcon>Alert MessageBox
|
||||
</MessageBox>
|
||||
);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import cn from "classnames";
|
||||
import React, { ReactNode, StatelessComponent } from "react";
|
||||
import { withStyles } from "talk-ui/hocs";
|
||||
|
||||
import styles from "./MessageBox.css";
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* The content of the component.
|
||||
*/
|
||||
children: ReactNode;
|
||||
/**
|
||||
* Convenient prop to override the root styling.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Override or extend the styles applied to the component.
|
||||
*/
|
||||
classes: typeof styles;
|
||||
}
|
||||
|
||||
const MessageBox: StatelessComponent<Props> = props => {
|
||||
const { className, classes, children, ...rest } = props;
|
||||
|
||||
const rootClassName = cn(classes.root, className);
|
||||
|
||||
return (
|
||||
<div className={rootClassName} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const enhanced = withStyles(styles)(MessageBox);
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,4 @@
|
||||
.root {
|
||||
composes: root from "talk-stream/shared/htmlContent.css";
|
||||
color: var(--palette-text-light);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import cn from "classnames";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Markdown } from "talk-framework/components";
|
||||
import { withStyles } from "talk-ui/hocs";
|
||||
|
||||
import styles from "./MessageBoxContent.css";
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* The content of the component.
|
||||
*/
|
||||
children: string;
|
||||
/**
|
||||
* Convenient prop to override the root styling.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Override or extend the styles applied to the component.
|
||||
*/
|
||||
classes: typeof styles;
|
||||
}
|
||||
|
||||
const MessageBox: StatelessComponent<Props> = props => {
|
||||
const { className, classes, children, ...rest } = props;
|
||||
|
||||
const rootClassName = cn(classes.root, className);
|
||||
|
||||
return (
|
||||
<Markdown className={rootClassName} {...rest}>
|
||||
{children}
|
||||
</Markdown>
|
||||
);
|
||||
};
|
||||
|
||||
const enhanced = withStyles(styles)(MessageBox);
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,9 @@
|
||||
.root {
|
||||
align-self: flex-start;
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
color: var(--palette-text-light);
|
||||
&:first-child {
|
||||
margin-right: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import cn from "classnames";
|
||||
import React, { HTMLAttributes, Ref, StatelessComponent } from "react";
|
||||
|
||||
import Icon, { IconProps } from "talk-ui/components/Icon";
|
||||
import { withForwardRef, withStyles } from "talk-ui/hocs";
|
||||
import { Omit } from "talk-ui/types";
|
||||
|
||||
import styles from "./MessageBoxIcon.css";
|
||||
|
||||
interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, "color"> {
|
||||
/**
|
||||
* This prop can be used to add custom classnames.
|
||||
* It is handled by the `withStyles `HOC.
|
||||
*/
|
||||
classes: typeof styles & IconProps["classes"];
|
||||
|
||||
size?: IconProps["size"];
|
||||
|
||||
/** The name of the icon to render */
|
||||
children: string;
|
||||
|
||||
/** Internal: Forwarded Ref */
|
||||
forwardRef?: Ref<HTMLSpanElement>;
|
||||
}
|
||||
|
||||
export const MessageBoxIcon: StatelessComponent<Props> = props => {
|
||||
const { classes, className, forwardRef, ...rest } = props;
|
||||
const rootClassName = cn(classes.root, className);
|
||||
return <Icon className={rootClassName} {...rest} ref={forwardRef} />;
|
||||
};
|
||||
|
||||
MessageBoxIcon.defaultProps = {
|
||||
size: "md",
|
||||
};
|
||||
|
||||
const enhanced = withForwardRef(withStyles(styles)(MessageBoxIcon));
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,23 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="MessageBox-root custom"
|
||||
>
|
||||
Hello World
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders icon 1`] = `
|
||||
<div
|
||||
className="MessageBox-root"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md MessageBoxIcon-root"
|
||||
>
|
||||
alert
|
||||
</span>
|
||||
Alert MessageBox
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default, default as MessageBox } from "./MessageBox";
|
||||
export { default as MessageBoxIcon } from "./MessageBoxIcon";
|
||||
export { default as MessageBoxContent } from "./MessageBoxContent";
|
||||
@@ -1,4 +1,5 @@
|
||||
.root {
|
||||
.messageBox {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.poweredBy {
|
||||
|
||||
@@ -8,18 +8,19 @@ import ValidationMessage from "talk-admin/routes/configure/components/Validation
|
||||
import { OnSubmit } from "talk-framework/lib/form";
|
||||
import { AriaInfo, Button, Flex, HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import MessageBoxContainer from "../containers/MessageBoxContainer";
|
||||
import PostCommentSubmitStatusContainer from "../containers/PostCommentSubmitStatusContainer";
|
||||
import RemainingCharactersContainer from "../containers/RemainingCharactersContainer";
|
||||
import { cleanupRTEEmptyHTML, getCommentBodyValidators } from "../helpers";
|
||||
import RTE from "./RTE";
|
||||
|
||||
import PostCommentSubmitStatusContainer from "../containers/PostCommentSubmitStatusContainer";
|
||||
import styles from "./PostCommentForm.css";
|
||||
|
||||
interface FormProps {
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface PostCommentFormProps {
|
||||
interface Props {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
onChange?: (state: FormState, form: FormApi) => void;
|
||||
initialValues?: FormProps;
|
||||
@@ -28,103 +29,110 @@ export interface PostCommentFormProps {
|
||||
disabled?: boolean;
|
||||
disabledMessage?: React.ReactNode;
|
||||
submitStatus: PropTypesOf<PostCommentSubmitStatusContainer>["status"];
|
||||
showMessageBox?: boolean;
|
||||
story: PropTypesOf<typeof MessageBoxContainer>["story"];
|
||||
}
|
||||
|
||||
const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
|
||||
<Form onSubmit={props.onSubmit} initialValues={props.initialValues}>
|
||||
{({ handleSubmit, submitting, submitError, form }) => (
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={handleSubmit}
|
||||
className={styles.root}
|
||||
id="comments-postCommentForm-form"
|
||||
>
|
||||
<FormSpy
|
||||
onChange={state => props.onChange && props.onChange(state, form)}
|
||||
/>
|
||||
<HorizontalGutter>
|
||||
<Field
|
||||
name="body"
|
||||
validate={getCommentBodyValidators(props.min, props.max)}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<HorizontalGutter size="half">
|
||||
<Localized id="comments-postCommentForm-rteLabel">
|
||||
<AriaInfo
|
||||
component="label"
|
||||
htmlFor="comments-postCommentForm-field"
|
||||
const PostCommentForm: StatelessComponent<Props> = props => (
|
||||
<div>
|
||||
{props.showMessageBox && (
|
||||
<MessageBoxContainer story={props.story} className={styles.messageBox} />
|
||||
)}
|
||||
<Form onSubmit={props.onSubmit} initialValues={props.initialValues}>
|
||||
{({ handleSubmit, submitting, submitError, form }) => (
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={handleSubmit}
|
||||
id="comments-postCommentForm-form"
|
||||
>
|
||||
<FormSpy
|
||||
onChange={state => props.onChange && props.onChange(state, form)}
|
||||
/>
|
||||
<HorizontalGutter>
|
||||
<Field
|
||||
name="body"
|
||||
validate={getCommentBodyValidators(props.min, props.max)}
|
||||
>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<HorizontalGutter size="half">
|
||||
<Localized id="comments-postCommentForm-rteLabel">
|
||||
<AriaInfo
|
||||
component="label"
|
||||
htmlFor="comments-postCommentForm-field"
|
||||
>
|
||||
Post a comment
|
||||
</AriaInfo>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="comments-postCommentForm-rte"
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
Post a comment
|
||||
</AriaInfo>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="comments-postCommentForm-rte"
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
<RTE
|
||||
inputId="comments-postCommentForm-field"
|
||||
onChange={({ html }) =>
|
||||
input.onChange(cleanupRTEEmptyHTML(html))
|
||||
}
|
||||
value={input.value}
|
||||
placeholder="Post a comment"
|
||||
disabled={submitting || props.disabled}
|
||||
/>
|
||||
</Localized>
|
||||
{props.disabled ? (
|
||||
<>
|
||||
{props.disabledMessage && (
|
||||
<ValidationMessage fullWidth>
|
||||
{props.disabledMessage}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{meta.touched &&
|
||||
(meta.error ||
|
||||
(meta.submitError && !meta.dirtySinceLastSubmit)) && (
|
||||
<RTE
|
||||
inputId="comments-postCommentForm-field"
|
||||
onChange={({ html }) =>
|
||||
input.onChange(cleanupRTEEmptyHTML(html))
|
||||
}
|
||||
value={input.value}
|
||||
placeholder="Post a comment"
|
||||
disabled={submitting || props.disabled}
|
||||
/>
|
||||
</Localized>
|
||||
{props.disabled ? (
|
||||
<>
|
||||
{props.disabledMessage && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
{props.disabledMessage}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{submitError && (
|
||||
<ValidationMessage fullWidth>
|
||||
{submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
<PostCommentSubmitStatusContainer
|
||||
status={props.submitStatus}
|
||||
/>
|
||||
{props.max && (
|
||||
<RemainingCharactersContainer
|
||||
value={input.value}
|
||||
max={props.max}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{meta.touched &&
|
||||
(meta.error ||
|
||||
(meta.submitError &&
|
||||
!meta.dirtySinceLastSubmit)) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{submitError && (
|
||||
<ValidationMessage fullWidth>
|
||||
{submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
<PostCommentSubmitStatusContainer
|
||||
status={props.submitStatus}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
<Flex direction="column" alignItems="flex-end">
|
||||
<Localized id="comments-postCommentForm-submit">
|
||||
<Button
|
||||
color="primary"
|
||||
variant="filled"
|
||||
disabled={submitting || !input.value || props.disabled}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Localized>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
{props.max && (
|
||||
<RemainingCharactersContainer
|
||||
value={input.value}
|
||||
max={props.max}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
<Flex direction="column" alignItems="flex-end">
|
||||
<Localized id="comments-postCommentForm-submit">
|
||||
<Button
|
||||
color="primary"
|
||||
variant="filled"
|
||||
disabled={submitting || !input.value || props.disabled}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Localized>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PostCommentForm;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.messageBox {
|
||||
margin-bottom: -1px;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { CallOut } from "talk-ui/components";
|
||||
|
||||
import MessageBoxContainer from "../containers/MessageBoxContainer";
|
||||
|
||||
import styles from "./PostCommentFormClosed.css";
|
||||
|
||||
interface Props {
|
||||
showMessageBox?: boolean;
|
||||
story: PropTypesOf<typeof MessageBoxContainer>["story"];
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
const PostCommentFormClosed: StatelessComponent<Props> = props => (
|
||||
<div>
|
||||
{props.showMessageBox && (
|
||||
<MessageBoxContainer story={props.story} className={styles.messageBox} />
|
||||
)}
|
||||
<CallOut fullWidth>{props.children}</CallOut>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PostCommentFormClosed;
|
||||
-3
@@ -1,7 +1,4 @@
|
||||
.root {
|
||||
}
|
||||
|
||||
.sitewide {
|
||||
background-color: var(--palette-grey-dark);
|
||||
border-color: var(--palette-grey-dark);
|
||||
color: var(--palette-text-light);
|
||||
@@ -0,0 +1,24 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { CallOut, HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import MessageBoxContainer from "../containers/MessageBoxContainer";
|
||||
|
||||
import styles from "./PostCommentFormClosedSitewide.css";
|
||||
|
||||
interface Props {
|
||||
showMessageBox?: boolean;
|
||||
story: PropTypesOf<typeof MessageBoxContainer>["story"];
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
const PostCommentFormClosedSitewide: StatelessComponent<Props> = props => (
|
||||
<HorizontalGutter size="double">
|
||||
<CallOut fullWidth className={styles.root}>
|
||||
{props.children}
|
||||
</CallOut>
|
||||
{props.showMessageBox && <MessageBoxContainer story={props.story} />}
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default PostCommentFormClosedSitewide;
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import { createRenderer } from "react-test-renderer/shallow";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import PostCommentFormCollapsed from "./PostCommentFormCollapsed";
|
||||
|
||||
it("renders correctly when comments are closed sitewide", () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormCollapsed> = {
|
||||
closedSitewide: true,
|
||||
closedMessage: "closed site-wide",
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<PostCommentFormCollapsed {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly when story is closed", () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormCollapsed> = {
|
||||
closedSitewide: false,
|
||||
closedMessage: "closed story",
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<PostCommentFormCollapsed {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import cn from "classnames";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { CallOut } from "talk-ui/components";
|
||||
|
||||
import styles from "./PostCommentFormCollapsed.css";
|
||||
|
||||
interface Props {
|
||||
closedSitewide?: boolean;
|
||||
closedMessage?: React.ReactNode;
|
||||
}
|
||||
const PostCommentFormCollapsed: StatelessComponent<Props> = props => (
|
||||
<CallOut
|
||||
fullWidth
|
||||
className={cn(styles.root, { [styles.sitewide]: props.closedSitewide })}
|
||||
>
|
||||
{props.closedMessage}
|
||||
</CallOut>
|
||||
);
|
||||
|
||||
export default PostCommentFormCollapsed;
|
||||
@@ -1,6 +1,10 @@
|
||||
.root {
|
||||
}
|
||||
|
||||
.messageBox {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
composes: bodyCopy from "talk-ui/shared/typography.css";
|
||||
display: block;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import React from "react";
|
||||
import { createRenderer } from "react-test-renderer/shallow";
|
||||
import { removeFragmentRefs } from "talk-framework/testHelpers";
|
||||
|
||||
import PostCommentFormFake from "./PostCommentFormFake";
|
||||
|
||||
const PostCommentFormFakeN = removeFragmentRefs(PostCommentFormFake);
|
||||
|
||||
it("renders correctly", () => {
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<PostCommentFormFake />);
|
||||
renderer.render(<PostCommentFormFakeN story={{}} showMessageBox />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -1,28 +1,46 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { Button, HorizontalGutter } from "talk-ui/components";
|
||||
|
||||
import MessageBoxContainer from "../containers/MessageBoxContainer";
|
||||
import RTE from "./RTE";
|
||||
|
||||
import styles from "./PostCommentFormFake.css";
|
||||
|
||||
const PostCommentFormFake: StatelessComponent = props => (
|
||||
<HorizontalGutter className={styles.root}>
|
||||
<div aria-hidden="true">
|
||||
<Localized
|
||||
id="comments-postCommentFormFake-rte"
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
<RTE placeholder="Post a comment" disabled />
|
||||
interface Props {
|
||||
showMessageBox?: boolean;
|
||||
story: PropTypesOf<typeof MessageBoxContainer>["story"];
|
||||
}
|
||||
|
||||
const PostCommentFormFake: StatelessComponent<Props> = props => (
|
||||
<div>
|
||||
{props.showMessageBox && (
|
||||
<MessageBoxContainer story={props.story} className={styles.messageBox} />
|
||||
)}
|
||||
<HorizontalGutter className={styles.root}>
|
||||
<div aria-hidden="true">
|
||||
<Localized
|
||||
id="comments-postCommentFormFake-rte"
|
||||
attrs={{ placeholder: true }}
|
||||
>
|
||||
<RTE placeholder="Post a comment" disabled />
|
||||
</Localized>
|
||||
</div>
|
||||
<Localized id="comments-postCommentFormFake-signInAndJoin">
|
||||
<Button
|
||||
color="primary"
|
||||
variant="filled"
|
||||
disabled
|
||||
type="submit"
|
||||
fullWidth
|
||||
>
|
||||
Sign in and join the conversation
|
||||
</Button>
|
||||
</Localized>
|
||||
</div>
|
||||
<Localized id="comments-postCommentFormFake-signInAndJoin">
|
||||
<Button color="primary" variant="filled" disabled type="submit" fullWidth>
|
||||
Sign in and join the conversation
|
||||
</Button>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PostCommentFormFake;
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly when comments are closed sitewide 1`] = `
|
||||
<withPropsOnChange(CallOut)
|
||||
className="PostCommentFormCollapsed-root PostCommentFormCollapsed-sitewide"
|
||||
fullWidth={true}
|
||||
>
|
||||
closed site-wide
|
||||
</withPropsOnChange(CallOut)>
|
||||
`;
|
||||
|
||||
exports[`renders correctly when story is closed 1`] = `
|
||||
<withPropsOnChange(CallOut)
|
||||
className="PostCommentFormCollapsed-root"
|
||||
fullWidth={true}
|
||||
>
|
||||
closed story
|
||||
</withPropsOnChange(CallOut)>
|
||||
`;
|
||||
+35
-29
@@ -1,38 +1,44 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<ForwardRef(forwardRef)
|
||||
className="PostCommentFormFake-root"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
<div>
|
||||
<Relay(MessageBoxContainer)
|
||||
className="PostCommentFormFake-messageBox"
|
||||
story={Object {}}
|
||||
/>
|
||||
<ForwardRef(forwardRef)
|
||||
className="PostCommentFormFake-root"
|
||||
>
|
||||
<Localized
|
||||
attrs={
|
||||
Object {
|
||||
"placeholder": true,
|
||||
<div
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Localized
|
||||
attrs={
|
||||
Object {
|
||||
"placeholder": true,
|
||||
}
|
||||
}
|
||||
}
|
||||
id="comments-postCommentFormFake-rte"
|
||||
id="comments-postCommentFormFake-rte"
|
||||
>
|
||||
<RTE
|
||||
disabled={true}
|
||||
placeholder="Post a comment"
|
||||
/>
|
||||
</Localized>
|
||||
</div>
|
||||
<Localized
|
||||
id="comments-postCommentFormFake-signInAndJoin"
|
||||
>
|
||||
<RTE
|
||||
<ForwardRef(forwardRef)
|
||||
color="primary"
|
||||
disabled={true}
|
||||
placeholder="Post a comment"
|
||||
/>
|
||||
fullWidth={true}
|
||||
type="submit"
|
||||
variant="filled"
|
||||
>
|
||||
Sign in and join the conversation
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
</div>
|
||||
<Localized
|
||||
id="comments-postCommentFormFake-signInAndJoin"
|
||||
>
|
||||
<ForwardRef(forwardRef)
|
||||
color="primary"
|
||||
disabled={true}
|
||||
fullWidth={true}
|
||||
type="submit"
|
||||
variant="filled"
|
||||
>
|
||||
Sign in and join the conversation
|
||||
</ForwardRef(forwardRef)>
|
||||
</Localized>
|
||||
</ForwardRef(forwardRef)>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import { MessageBoxContainer_story as StoryData } from "talk-stream/__generated__/MessageBoxContainer_story.graphql";
|
||||
import {
|
||||
MessageBox,
|
||||
MessageBoxContent,
|
||||
MessageBoxIcon,
|
||||
} from "talk-stream/components/MessageBox";
|
||||
|
||||
interface Props {
|
||||
story: StoryData;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const MessageBoxContainer: StatelessComponent<Props> = ({
|
||||
story,
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<MessageBox className={className}>
|
||||
{story.settings.messageBox.icon && (
|
||||
<MessageBoxIcon>{story.settings.messageBox.icon}</MessageBoxIcon>
|
||||
)}
|
||||
<MessageBoxContent>
|
||||
{story.settings.messageBox.content || ""}
|
||||
</MessageBoxContent>
|
||||
</MessageBox>
|
||||
);
|
||||
};
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
story: graphql`
|
||||
fragment MessageBoxContainer_story on Story {
|
||||
settings {
|
||||
messageBox {
|
||||
content
|
||||
icon
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(MessageBoxContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -11,7 +11,7 @@ interface Props {
|
||||
commentID: string;
|
||||
}
|
||||
|
||||
export const PermalinkButtonContainerProps: StatelessComponent<Props> = ({
|
||||
export const PermalinkButtonContainer: StatelessComponent<Props> = ({
|
||||
story,
|
||||
commentID,
|
||||
}) => {
|
||||
@@ -29,6 +29,6 @@ const enhanced = withFragmentContainer<Props>({
|
||||
url
|
||||
}
|
||||
`,
|
||||
})(PermalinkButtonContainerProps);
|
||||
})(PermalinkButtonContainer);
|
||||
|
||||
export default enhanced;
|
||||
|
||||
@@ -27,6 +27,11 @@ function createDefaultProps(add: DeepPartial<Props> = {}): Props {
|
||||
story: {
|
||||
id: "story-id",
|
||||
isClosed: false,
|
||||
settings: {
|
||||
messageBox: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
sessionStorage: createPromisifiedStorage(),
|
||||
settings: {
|
||||
|
||||
@@ -21,10 +21,9 @@ import {
|
||||
withCreateCommentMutation,
|
||||
} from "talk-stream/mutations";
|
||||
|
||||
import PostCommentForm, {
|
||||
PostCommentFormProps,
|
||||
} from "../components/PostCommentForm";
|
||||
import PostCommentFormCollapsed from "../components/PostCommentFormCollapsed";
|
||||
import PostCommentForm from "../components/PostCommentForm";
|
||||
import PostCommentFormClosed from "../components/PostCommentFormClosed";
|
||||
import PostCommentFormClosedSitewide from "../components/PostCommentFormClosedSitewide";
|
||||
import PostCommentFormFake from "../components/PostCommentFormFake";
|
||||
import {
|
||||
getSubmitStatus,
|
||||
@@ -42,7 +41,7 @@ interface Props {
|
||||
}
|
||||
|
||||
interface State {
|
||||
initialValues?: PostCommentFormProps["initialValues"];
|
||||
initialValues?: PropTypesOf<typeof PostCommentForm>["initialValues"];
|
||||
initialized: boolean;
|
||||
keepFormWhenClosed: boolean;
|
||||
submitStatus: SubmitStatus | null;
|
||||
@@ -79,10 +78,9 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
private handleOnSubmit: PostCommentFormProps["onSubmit"] = async (
|
||||
input,
|
||||
form
|
||||
) => {
|
||||
private handleOnSubmit: PropTypesOf<
|
||||
typeof PostCommentForm
|
||||
>["onSubmit"] = async (input, form) => {
|
||||
try {
|
||||
const submitStatus = getSubmitStatus(
|
||||
await this.props.createComment({
|
||||
@@ -107,7 +105,10 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
return;
|
||||
};
|
||||
|
||||
private handleOnChange: PostCommentFormProps["onChange"] = (state, form) => {
|
||||
private handleOnChange: PropTypesOf<typeof PostCommentForm>["onChange"] = (
|
||||
state,
|
||||
form
|
||||
) => {
|
||||
if (this.state.submitStatus && state.dirty) {
|
||||
this.setState({ submitStatus: null });
|
||||
}
|
||||
@@ -126,27 +127,39 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
if (!this.state.initialized) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
!this.state.keepFormWhenClosed &&
|
||||
(this.props.settings.disableCommenting.enabled ||
|
||||
this.props.story.isClosed)
|
||||
) {
|
||||
if (!this.state.keepFormWhenClosed) {
|
||||
if (this.props.settings.disableCommenting.enabled) {
|
||||
return (
|
||||
<PostCommentFormClosedSitewide
|
||||
story={this.props.story}
|
||||
showMessageBox={this.props.story.settings.messageBox.enabled}
|
||||
>
|
||||
{this.props.settings.disableCommenting.message}
|
||||
</PostCommentFormClosedSitewide>
|
||||
);
|
||||
}
|
||||
if (this.props.story.isClosed) {
|
||||
return (
|
||||
<PostCommentFormClosed
|
||||
story={this.props.story}
|
||||
showMessageBox={this.props.story.settings.messageBox.enabled}
|
||||
>
|
||||
{this.props.settings.closeCommenting.message}
|
||||
</PostCommentFormClosed>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!this.props.local.loggedIn) {
|
||||
return (
|
||||
<PostCommentFormCollapsed
|
||||
closedSitewide={this.props.settings.disableCommenting.enabled}
|
||||
closedMessage={
|
||||
(this.props.settings.disableCommenting.enabled &&
|
||||
this.props.settings.disableCommenting.message) ||
|
||||
this.props.settings.closeCommenting.message
|
||||
}
|
||||
<PostCommentFormFake
|
||||
story={this.props.story}
|
||||
showMessageBox={this.props.story.settings.messageBox.enabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!this.props.local.loggedIn) {
|
||||
return <PostCommentFormFake />;
|
||||
}
|
||||
return (
|
||||
<PostCommentForm
|
||||
story={this.props.story}
|
||||
onSubmit={this.handleOnSubmit}
|
||||
onChange={this.handleOnChange}
|
||||
initialValues={this.state.initialValues}
|
||||
@@ -170,6 +183,7 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
this.props.settings.closeCommenting.message
|
||||
}
|
||||
submitStatus={this.state.submitStatus}
|
||||
showMessageBox={this.props.story.settings.messageBox.enabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -208,6 +222,12 @@ const enhanced = withContext(({ sessionStorage }) => ({
|
||||
fragment PostCommentFormContainer_story on Story {
|
||||
id
|
||||
isClosed
|
||||
...MessageBoxContainer_story
|
||||
settings {
|
||||
messageBox {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(PostCommentFormContainer)
|
||||
|
||||
+7
-7
@@ -15,7 +15,7 @@ exports[`hide reply button 1`] = `
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<ButtonsBar>
|
||||
<Relay(PermalinkButtonContainerProps)
|
||||
<Relay(PermalinkButtonContainer)
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
@@ -109,7 +109,7 @@ exports[`renders body only 1`] = `
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<Relay(PermalinkButtonContainerProps)
|
||||
<Relay(PermalinkButtonContainer)
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
@@ -203,7 +203,7 @@ exports[`renders disabled reply when commenting has been disabled 1`] = `
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<Relay(PermalinkButtonContainerProps)
|
||||
<Relay(PermalinkButtonContainer)
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
@@ -297,7 +297,7 @@ exports[`renders disabled reply when story is closed 1`] = `
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<Relay(PermalinkButtonContainerProps)
|
||||
<Relay(PermalinkButtonContainer)
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
@@ -391,7 +391,7 @@ exports[`renders in reply to 1`] = `
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<Relay(PermalinkButtonContainerProps)
|
||||
<Relay(PermalinkButtonContainer)
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
@@ -493,7 +493,7 @@ exports[`renders username and body 1`] = `
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<Relay(PermalinkButtonContainerProps)
|
||||
<Relay(PermalinkButtonContainer)
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
@@ -587,7 +587,7 @@ exports[`shows conversation link 1`] = `
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<Relay(PermalinkButtonContainerProps)
|
||||
<Relay(PermalinkButtonContainer)
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
|
||||
+80
-8
@@ -8,15 +8,39 @@ exports[`renders correctly 1`] = `
|
||||
min={3}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
showMessageBox={false}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
"settings": Object {
|
||||
"messageBox": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
submitStatus={null}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders when commenting has been disabled (collapsing) 1`] = `
|
||||
<PostCommentFormCollapsed
|
||||
closedMessage="commenting disabled"
|
||||
closedSitewide={true}
|
||||
/>
|
||||
<PostCommentFormClosedSitewide
|
||||
showMessageBox={false}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
"settings": Object {
|
||||
"messageBox": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
commenting disabled
|
||||
</PostCommentFormClosedSitewide>
|
||||
`;
|
||||
|
||||
exports[`renders when commenting has been disabled (non-collapsing) 1`] = `
|
||||
@@ -27,15 +51,39 @@ exports[`renders when commenting has been disabled (non-collapsing) 1`] = `
|
||||
min={3}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
showMessageBox={false}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
"settings": Object {
|
||||
"messageBox": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
submitStatus={null}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders when story has been closed (collapsing) 1`] = `
|
||||
<PostCommentFormCollapsed
|
||||
closedMessage="story closed"
|
||||
closedSitewide={false}
|
||||
/>
|
||||
<PostCommentFormClosed
|
||||
showMessageBox={false}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": true,
|
||||
"settings": Object {
|
||||
"messageBox": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
story closed
|
||||
</PostCommentFormClosed>
|
||||
`;
|
||||
|
||||
exports[`renders when story has been closed (non-collapsing) 1`] = `
|
||||
@@ -46,6 +94,18 @@ exports[`renders when story has been closed (non-collapsing) 1`] = `
|
||||
min={3}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
showMessageBox={false}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": true,
|
||||
"settings": Object {
|
||||
"messageBox": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
submitStatus={null}
|
||||
/>
|
||||
`;
|
||||
@@ -63,6 +123,18 @@ exports[`renders with initialValues 1`] = `
|
||||
min={3}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
showMessageBox={false}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
"settings": Object {
|
||||
"messageBox": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
submitStatus={null}
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import MessageBoxConfigContainer from "../containers/MessageBoxConfigContainer";
|
||||
import PremodConfigContainer from "../containers/PremodConfigContainer";
|
||||
import PremodLinksConfigContainer from "../containers/PremodLinksConfigContainer";
|
||||
|
||||
@@ -21,7 +22,8 @@ import styles from "./ConfigureCommentStream.css";
|
||||
interface Props {
|
||||
onSubmit: (settings: any, form: FormApi) => void;
|
||||
storySettings: PropTypesOf<typeof PremodConfigContainer>["storySettings"] &
|
||||
PropTypesOf<typeof PremodLinksConfigContainer>["storySettings"];
|
||||
PropTypesOf<typeof PremodLinksConfigContainer>["storySettings"] &
|
||||
PropTypesOf<typeof MessageBoxConfigContainer>["storySettings"];
|
||||
}
|
||||
|
||||
const ConfigureCommentStream: StatelessComponent<Props> = ({
|
||||
@@ -66,6 +68,11 @@ const ConfigureCommentStream: StatelessComponent<Props> = ({
|
||||
storySettings={storySettings}
|
||||
disabled={submitting}
|
||||
/>
|
||||
<MessageBoxConfigContainer
|
||||
onInitValues={onInitValues}
|
||||
storySettings={storySettings}
|
||||
disabled={submitting}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
</form>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.preview {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent, Suspense } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { MarkdownEditor } from "talk-framework/components/loadables";
|
||||
import { parseBool } from "talk-framework/lib/form";
|
||||
import {
|
||||
MessageBox,
|
||||
MessageBoxContent,
|
||||
MessageBoxIcon,
|
||||
} from "talk-stream/components/MessageBox";
|
||||
import {
|
||||
HorizontalGutter,
|
||||
Icon,
|
||||
Spinner,
|
||||
TileOption,
|
||||
TileSelector,
|
||||
Typography,
|
||||
ValidationMessage,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import ToggleConfig from "./ToggleConfig";
|
||||
import WidthLimitedDescription from "./WidthLimitedDescription";
|
||||
|
||||
import styles from "./MessageBoxConfig.css";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const MessageBoxConfig: StatelessComponent<Props> = ({ disabled }) => (
|
||||
<Field name="messageBox.enabled" type="checkbox" parse={parseBool}>
|
||||
{({ input }) => (
|
||||
<ToggleConfig
|
||||
id={input.name}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
onFocus={input.onFocus}
|
||||
onBlur={input.onBlur}
|
||||
checked={input.checked}
|
||||
disabled={disabled}
|
||||
title={
|
||||
<Localized id="configure-messageBox-title">
|
||||
<span>Enable Message Box for this Stream</span>
|
||||
</Localized>
|
||||
}
|
||||
>
|
||||
<HorizontalGutter size="oneAndAHalf">
|
||||
<Localized id="configure-messageBox-description">
|
||||
<WidthLimitedDescription>
|
||||
Add a message to the top of the comment box for your readers. Use
|
||||
this to pose a topic, ask a question or make announcements
|
||||
relating to this story.
|
||||
</WidthLimitedDescription>
|
||||
</Localized>
|
||||
{input.checked && (
|
||||
<Field name="messageBox.icon">
|
||||
{({ input: iconInput }) => (
|
||||
<Field name="messageBox.content">
|
||||
{({ input: contentInput, meta }) => (
|
||||
<>
|
||||
<HorizontalGutter size="half" container="section">
|
||||
<Localized id="configure-messageBox-preview">
|
||||
<Typography
|
||||
variant="bodyCopyBold"
|
||||
container="h1"
|
||||
className={styles.preview}
|
||||
>
|
||||
PREVIEW
|
||||
</Typography>
|
||||
</Localized>
|
||||
<MessageBox>
|
||||
{iconInput.value && (
|
||||
<MessageBoxIcon>{iconInput.value}</MessageBoxIcon>
|
||||
)}
|
||||
{/* Using a zero width join character to ensure that the space is used */}
|
||||
<MessageBoxContent>
|
||||
{contentInput.value || " "}
|
||||
</MessageBoxContent>
|
||||
</MessageBox>
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter size="half" container="fieldset">
|
||||
<Localized id="configure-messageBox-selectAnIcon">
|
||||
<Typography variant="bodyCopyBold" container="legend">
|
||||
Select an Icon
|
||||
</Typography>
|
||||
</Localized>
|
||||
<TileSelector
|
||||
id="configure-messageBox-icon"
|
||||
name={iconInput.name}
|
||||
onChange={iconInput.onChange}
|
||||
value={iconInput.value}
|
||||
>
|
||||
<TileOption value="question_answer">
|
||||
<Icon size="md">question_answer</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="today">
|
||||
<Icon size="md">today</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="help_outline">
|
||||
<Icon size="md">help_outline</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="warning">
|
||||
<Icon size="md">warning</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="chat_bubble_outline">
|
||||
<Icon size="md">chat_bubble_outline</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="">No Icon</TileOption>
|
||||
</TileSelector>
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter size="half" container="section">
|
||||
<div>
|
||||
<Localized id="configure-messageBox-writeAMessage">
|
||||
<Typography
|
||||
variant="bodyCopyBold"
|
||||
container={
|
||||
<label htmlFor="configure-messageBox-content" />
|
||||
}
|
||||
>
|
||||
Write a Message
|
||||
</Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<MarkdownEditor
|
||||
id="configure-messageBox-content"
|
||||
name={contentInput.name}
|
||||
onChange={contentInput.onChange}
|
||||
value={contentInput.value}
|
||||
/>
|
||||
</Suspense>
|
||||
{meta.touched &&
|
||||
(meta.error || meta.submitError) && (
|
||||
<ValidationMessage>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
)}
|
||||
</Field>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
</ToggleConfig>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
|
||||
export default MessageBoxConfig;
|
||||
@@ -50,6 +50,7 @@ const enhanced = withFragmentContainer<Props>({
|
||||
settings {
|
||||
...PremodConfigContainer_storySettings
|
||||
...PremodLinksConfigContainer_storySettings
|
||||
...MessageBoxConfigContainer_storySettings
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { MessageBoxConfigContainer_storySettings as StorySettingsData } from "talk-stream/__generated__/MessageBoxConfigContainer_storySettings.graphql";
|
||||
|
||||
import MessageBoxConfig from "../components/MessageBoxConfig";
|
||||
|
||||
interface Props {
|
||||
storySettings: StorySettingsData;
|
||||
onInitValues: (values: StorySettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class MessageBoxConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.storySettings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <MessageBoxConfig disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
storySettings: graphql`
|
||||
fragment MessageBoxConfigContainer_storySettings on StorySettings {
|
||||
messageBox {
|
||||
enabled
|
||||
content
|
||||
icon
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(MessageBoxConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
+85
-83
@@ -52,102 +52,104 @@ exports[`renders comment stream with community guidelines 1`] = `
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder "
|
||||
className=""
|
||||
>
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder "
|
||||
>
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
}
|
||||
}
|
||||
}
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
|
||||
>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
>
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
>
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Sign in and Join the Conversation
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Sign in and Join the Conversation
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,512 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders message box when commenting disabled 1`] = `
|
||||
<section
|
||||
aria-labelledby="tab-COMMENTS"
|
||||
className="App-tabContent"
|
||||
data-testid="current-tab-pane"
|
||||
id="tabPane-COMMENTS"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="CallOut-root CallOut-colorRegular CallOut-fullWidth PostCommentFormClosedSitewide-root"
|
||||
>
|
||||
Commenting disabled
|
||||
</div>
|
||||
<div
|
||||
className="MessageBox-root"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md MessageBoxIcon-root"
|
||||
>
|
||||
chat
|
||||
</span>
|
||||
<div
|
||||
className="Markdown-root MessageBoxContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p><strong>What do you think</strong>?</p>
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
data-testid="comments-stream-log"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`renders message box when logged in 1`] = `
|
||||
<section
|
||||
aria-labelledby="tab-COMMENTS"
|
||||
className="App-tabContent"
|
||||
data-testid="current-tab-pane"
|
||||
id="tabPane-COMMENTS"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Flex-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-flex Flex-halfItemGutter Flex-wrap"
|
||||
>
|
||||
<div
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Signed in as
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
.
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Not you?
|
||||
</span>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="MessageBox-root PostCommentForm-messageBox"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md MessageBoxIcon-root"
|
||||
>
|
||||
chat
|
||||
</span>
|
||||
<div
|
||||
className="Markdown-root MessageBoxContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p><strong>What do you think</strong>?</p>
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
autoComplete="off"
|
||||
id="comments-postCommentForm-form"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="AriaInfo-root"
|
||||
htmlFor="comments-postCommentForm-field"
|
||||
>
|
||||
Post a comment
|
||||
</label>
|
||||
<div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder"
|
||||
>
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content"
|
||||
contentEditable={true}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
}
|
||||
}
|
||||
disabled={false}
|
||||
id="comments-postCommentForm-field"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarBottom Toolbar-toolbar"
|
||||
>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-alignFlexEnd Flex-directionColumn"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
data-testid="comments-stream-log"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`renders message box when not logged in 1`] = `
|
||||
<section
|
||||
aria-labelledby="tab-COMMENTS"
|
||||
className="App-tabContent"
|
||||
data-testid="current-tab-pane"
|
||||
id="tabPane-COMMENTS"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="MessageBox-root PostCommentFormFake-messageBox"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md MessageBoxIcon-root"
|
||||
>
|
||||
chat
|
||||
</span>
|
||||
<div
|
||||
className="Markdown-root MessageBoxContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p><strong>What do you think</strong>?</p>
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder "
|
||||
>
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
}
|
||||
}
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
|
||||
>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Sign in and Join the Conversation
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
data-testid="comments-stream-log"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`renders message box when story isClosed 1`] = `
|
||||
<section
|
||||
aria-labelledby="tab-COMMENTS"
|
||||
className="App-tabContent"
|
||||
data-testid="current-tab-pane"
|
||||
id="tabPane-COMMENTS"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="MessageBox-root PostCommentFormClosed-messageBox"
|
||||
>
|
||||
<div
|
||||
className="Markdown-root MessageBoxContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p><strong>What do you think</strong>?</p>
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="CallOut-root CallOut-colorRegular CallOut-fullWidth"
|
||||
>
|
||||
Story is closed
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
data-testid="comments-stream-log"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
@@ -70,102 +70,104 @@ exports[`renders comment stream 1`] = `
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder "
|
||||
className=""
|
||||
>
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder "
|
||||
>
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
}
|
||||
}
|
||||
}
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
|
||||
>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
>
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
>
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Sign in and Join the Conversation
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Sign in and Join the Conversation
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { merge } from "lodash";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { waitForElement, within } from "talk-framework/testHelpers";
|
||||
|
||||
import { settings, storyWithNoComments, users } from "../fixtures";
|
||||
import create from "./create";
|
||||
|
||||
async function createTestRenderer(
|
||||
data: {
|
||||
story?: any;
|
||||
settings?: any;
|
||||
loggedIn?: boolean;
|
||||
} = {},
|
||||
options: { muteNetworkErrors?: boolean } = {}
|
||||
) {
|
||||
const resolvers = {
|
||||
Query: {
|
||||
settings: sinon.stub().returns(merge({}, settings, data.settings)),
|
||||
me: sinon.stub().returns((data.loggedIn && users[0]) || null),
|
||||
story: sinon.stub().callsFake((_: any, variables: any) => {
|
||||
expectAndFail(variables.id).toBe(storyWithNoComments.id);
|
||||
return merge({}, storyWithNoComments, data.story);
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(storyWithNoComments.id, "storyID");
|
||||
localRecord.setValue(Boolean(data.loggedIn), "loggedIn");
|
||||
},
|
||||
});
|
||||
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("comments-stream-log")
|
||||
);
|
||||
|
||||
const tabPane = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("current-tab-pane")
|
||||
);
|
||||
|
||||
return {
|
||||
testRenderer,
|
||||
context,
|
||||
tabPane,
|
||||
};
|
||||
}
|
||||
|
||||
it("renders message box when not logged in", async () => {
|
||||
const { tabPane } = await createTestRenderer({
|
||||
story: {
|
||||
settings: {
|
||||
messageBox: {
|
||||
enabled: true,
|
||||
icon: "chat",
|
||||
content: "**What do you think**?",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(within(tabPane).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders message box when logged in", async () => {
|
||||
const { tabPane } = await createTestRenderer({
|
||||
story: {
|
||||
settings: {
|
||||
messageBox: {
|
||||
enabled: true,
|
||||
icon: "chat",
|
||||
content: "**What do you think**?",
|
||||
},
|
||||
},
|
||||
},
|
||||
loggedIn: true,
|
||||
});
|
||||
expect(within(tabPane).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders message box when commenting disabled", async () => {
|
||||
const { tabPane } = await createTestRenderer({
|
||||
story: {
|
||||
settings: {
|
||||
messageBox: {
|
||||
enabled: true,
|
||||
icon: "chat",
|
||||
content: "**What do you think**?",
|
||||
},
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
message: "Commenting disabled",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(within(tabPane).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders message box when story isClosed", async () => {
|
||||
const { tabPane } = await createTestRenderer({
|
||||
story: {
|
||||
isClosed: true,
|
||||
settings: {
|
||||
messageBox: {
|
||||
enabled: true,
|
||||
icon: null,
|
||||
content: "**What do you think**?",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(within(tabPane).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -162,6 +162,53 @@ exports[`renders configure 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="CheckBox-root"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="CheckBox-input"
|
||||
disabled={false}
|
||||
id="messageBox.enabled"
|
||||
name="messageBox.enabled"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
className="CheckBox-label"
|
||||
htmlFor="messageBox.enabled"
|
||||
>
|
||||
<span
|
||||
className="CheckBox-labelSpan"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
<span>
|
||||
Enable Message Box for this Stream
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="ToggleConfig-details"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<p
|
||||
className="Typography-root Typography-detail Typography-colorTextSecondary WidthLimitedDescription-root"
|
||||
>
|
||||
Add a message to the top of the comment box for your readers. Use this to pose a topic,
|
||||
ask a question or make announcements relating to this story.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -128,3 +128,59 @@ it("change premod links", async () => {
|
||||
// Should have successfully sent with server.
|
||||
expect(updateStorySettingsStub.called).toBe(true);
|
||||
});
|
||||
|
||||
it("change message box", async () => {
|
||||
let storyRecord = cloneDeep(stories[0]);
|
||||
const updateStorySettingsStub = sinon
|
||||
.stub()
|
||||
.callsFake((_: any, data: any) => {
|
||||
expectAndFail(data.input.settings.messageBox).toEqual({
|
||||
enabled: true,
|
||||
content: "*What do you think?*",
|
||||
icon: "question_answer",
|
||||
});
|
||||
storyRecord = merge(storyRecord, { settings: data.input.settings });
|
||||
return {
|
||||
story: storyRecord,
|
||||
clientMutationId: data.input.clientMutationId,
|
||||
};
|
||||
});
|
||||
const { form, applyButton } = await createTestRenderer({
|
||||
Mutation: {
|
||||
updateStorySettings: updateStorySettingsStub,
|
||||
},
|
||||
});
|
||||
|
||||
const enableField = within(form).getByLabelText(
|
||||
"Enable Message Box for this Stream"
|
||||
);
|
||||
|
||||
expect(applyButton.props.disabled).toBe(true);
|
||||
// Let's enable premod.
|
||||
enableField.props.onChange({});
|
||||
expect(applyButton.props.disabled).toBe(false);
|
||||
|
||||
// Select icon
|
||||
within(form)
|
||||
.getByLabelText("question_answer")
|
||||
.props.onChange({ target: { value: "question_answer" } });
|
||||
|
||||
// Change content.
|
||||
(await waitForElement(() =>
|
||||
within(form).getByLabelText("Write a Message")
|
||||
)).props.onChange("*What do you think?*");
|
||||
|
||||
// Send form
|
||||
form.props.onSubmit();
|
||||
|
||||
expect(applyButton.props.disabled).toBe(true);
|
||||
expect(enableField.props.disabled).toBe(true);
|
||||
|
||||
// Wait for submission to be finished
|
||||
await wait(() => {
|
||||
expect(enableField.props.disabled).toBe(false);
|
||||
});
|
||||
|
||||
// Should have successfully sent with server.
|
||||
expect(updateStorySettingsStub.called).toBe(true);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
|
||||
export const settings = {
|
||||
id: "settings",
|
||||
moderation: "POST",
|
||||
premodLinksEnable: false,
|
||||
communityGuidelines: {
|
||||
enabled: false,
|
||||
content: "",
|
||||
@@ -295,6 +297,9 @@ export const baseStory = {
|
||||
settings: {
|
||||
moderation: "POST",
|
||||
premodLinksEnable: false,
|
||||
messageBox: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -329,6 +334,18 @@ export const stories = denormalizeStories([
|
||||
},
|
||||
]);
|
||||
|
||||
export const storyWithNoComments = denormalizeStory({
|
||||
...baseStory,
|
||||
id: "story-with-no-comments",
|
||||
url: "http://localhost/stories/story-with-no-comments",
|
||||
comments: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const storyWithReplies = denormalizeStory({
|
||||
...baseStory,
|
||||
id: "story-with-replies",
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
color: var(--palette-grey-main);
|
||||
}
|
||||
|
||||
.colorDark {
|
||||
background-color: var(--palette-text-primary);
|
||||
border-width: 0px;
|
||||
color: var(--palette-text-light);
|
||||
}
|
||||
|
||||
.colorError {
|
||||
background-color: var(--palette-error-light);
|
||||
border-color: var(--palette-error-darkest);
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface MessageProps {
|
||||
/*
|
||||
* Name of color, "grey" stays by default - common gray one
|
||||
*/
|
||||
color?: "error" | "grey" | "primary";
|
||||
color?: "error" | "grey" | "primary" | "dark";
|
||||
}
|
||||
|
||||
const Message: StatelessComponent<MessageProps> = props => {
|
||||
@@ -36,6 +36,7 @@ const Message: StatelessComponent<MessageProps> = props => {
|
||||
[classes.colorGrey]: color === "grey",
|
||||
[classes.colorError]: color === "error",
|
||||
[classes.colorPrimary]: color === "primary",
|
||||
[classes.colorDark]: color === "dark",
|
||||
[classes.fullWidth]: fullWidth,
|
||||
},
|
||||
className
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.root {
|
||||
align-self: flex-start;
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
&:first-child {
|
||||
margin-right: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
.input {
|
||||
cursor: pointer;
|
||||
position: absolute; /* take it out of document flow */
|
||||
opacity: 0; /* hide it */
|
||||
}
|
||||
|
||||
.label {
|
||||
composes: bodyCopy from "talk-ui/shared/typography.css";
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border: 1px solid var(--palette-grey-dark);
|
||||
border-radius: 2px;
|
||||
color: var(--palette-grey-dark);
|
||||
padding: calc(0.5 * var(--spacing-unit));
|
||||
line-height: 1;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.checked {
|
||||
border: 1px solid var(--palette-text-primary);
|
||||
background: var(--palette-text-primary);
|
||||
color: var(--palette-text-light);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import cn from "classnames";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import styles from "./TileOption.css";
|
||||
import { SelectorChildProps } from "./TileSelector";
|
||||
|
||||
interface Props extends SelectorChildProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const TileOption: StatelessComponent<Props> = ({
|
||||
id,
|
||||
name,
|
||||
onChange,
|
||||
className,
|
||||
value,
|
||||
checked,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<input
|
||||
id={id}
|
||||
name={name}
|
||||
type="radio"
|
||||
className={styles.input}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
checked={checked}
|
||||
/>
|
||||
<label
|
||||
htmlFor={id}
|
||||
className={cn(styles.label, { [styles.checked]: checked })}
|
||||
>
|
||||
{children}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TileOption;
|
||||
@@ -0,0 +1,2 @@
|
||||
.root {
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: TileSelector
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from "docz";
|
||||
import Container from "react-with-state-props";
|
||||
|
||||
import TileSelector from "./TileSelector";
|
||||
import TileOption from "./TileOption";
|
||||
import HorizontalGutter from "../HorizontalGutter";
|
||||
import Icon from "../Icon";
|
||||
|
||||
# TileSelector
|
||||
|
||||
## Basic Use
|
||||
|
||||
<Playground>
|
||||
<Container
|
||||
state={{ value: null }}
|
||||
render={props => (
|
||||
<TileSelector
|
||||
id="icon"
|
||||
name="icon"
|
||||
onChange={val => props.setValue(val)}
|
||||
value={props.value}
|
||||
>
|
||||
<TileOption value="question_answer">
|
||||
<Icon size="md">question_answer</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="today">
|
||||
<Icon size="md">today</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="help_outline">
|
||||
<Icon size="md">help_outline</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="warning">
|
||||
<Icon size="md">warning</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="chat_bubble_outline">
|
||||
<Icon size="md">chat_bubble_outline</Icon>
|
||||
</TileOption>
|
||||
<TileOption value="">No Icon</TileOption>
|
||||
</TileSelector>
|
||||
)}
|
||||
/>
|
||||
</Playground>
|
||||
@@ -0,0 +1,52 @@
|
||||
import cn from "classnames";
|
||||
import React, { ReactElement, StatelessComponent, useCallback } from "react";
|
||||
|
||||
import { Flex } from "talk-ui/components";
|
||||
|
||||
import styles from "./TileSelector.css";
|
||||
|
||||
export interface SelectorChildProps {
|
||||
id?: string;
|
||||
name?: string;
|
||||
value?: any;
|
||||
checked?: boolean;
|
||||
onChange?: React.EventHandler<React.ChangeEvent<HTMLInputElement>>;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
name: string;
|
||||
onChange?: (value: any) => void;
|
||||
value?: string;
|
||||
className?: string;
|
||||
children: Array<ReactElement<SelectorChildProps, any>>;
|
||||
}
|
||||
|
||||
const TileSelector: StatelessComponent<Props> = props => {
|
||||
const { id, name, value, className, children, onChange } = props;
|
||||
const onItemChange = useCallback(
|
||||
(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChange && onChange(evt.target.value || null),
|
||||
[onChange]
|
||||
);
|
||||
return (
|
||||
<Flex
|
||||
className={cn(className, styles.root)}
|
||||
itemGutter="half"
|
||||
alignItems="stretch"
|
||||
>
|
||||
{React.Children.map(
|
||||
children,
|
||||
(c: ReactElement<SelectorChildProps, any>, i) =>
|
||||
React.cloneElement(c, {
|
||||
id: `${id}-${i}`,
|
||||
name,
|
||||
checked: c.props.value === value,
|
||||
onChange: onItemChange,
|
||||
})
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default TileSelector;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default, default as TileSelector } from "./TileSelector";
|
||||
export { default as Option } from "./TileOption";
|
||||
@@ -47,3 +47,4 @@ export { default as Counter } from "./Counter";
|
||||
export { Marker, Count as MarkerCount } from "./Marker";
|
||||
export { default as Card } from "./Card";
|
||||
export { default as PasswordField } from "./PasswordField";
|
||||
export { TileSelector, Option as TileOption } from "./TileSelector";
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
font-family: var(--font-family-sans-serif);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: calc(16rem / var(--rem-base));
|
||||
line-height: calc(16em / 16);
|
||||
line-height: calc(18em / 16);
|
||||
letter-spacing: calc(-0.1em / 16);
|
||||
}
|
||||
|
||||
|
||||
@@ -846,7 +846,7 @@ type ReactionConfiguration {
|
||||
|
||||
type CommunityGuidelines {
|
||||
"""
|
||||
enable set to true will show the guidelines above the question box.
|
||||
enable set to true will show the guidelines above the message box.
|
||||
"""
|
||||
enabled: Boolean!
|
||||
|
||||
@@ -2275,7 +2275,7 @@ input SettingsKarmaInput {
|
||||
|
||||
input SettingsCommunityGuidelinesInput {
|
||||
"""
|
||||
enable set to true will show the guidelines above the question box.
|
||||
enable set to true will show the guidelines above the message box.
|
||||
"""
|
||||
enabled: Boolean
|
||||
|
||||
|
||||
@@ -76,11 +76,6 @@ export interface Story extends TenantResource {
|
||||
* createdAt is the date that the Story was added to the Talk database.
|
||||
*/
|
||||
createdAt: Date;
|
||||
|
||||
/**
|
||||
* premodLinksEnable will put all comments that contain links into premod.
|
||||
*/
|
||||
premodLinksEnable?: boolean;
|
||||
}
|
||||
|
||||
export async function createStoryIndexes(mongo: Db) {
|
||||
|
||||
@@ -145,3 +145,12 @@ configure-premod-description =
|
||||
configure-premodLink-title = Pre-Moderate Comments Containing Links
|
||||
configure-premodLink-description =
|
||||
Moderators must approve any comment that contains a link before it is published to this stream.
|
||||
|
||||
configure-messageBox-title = Enable Message Box for this Stream
|
||||
configure-messageBox-description =
|
||||
Add a message to the top of the comment box for your readers. Use this to pose a topic,
|
||||
ask a question or make announcements relating to this story.
|
||||
configure-messageBox-preview = Preview
|
||||
configure-messageBox-selectAnIcon = Select an Icon
|
||||
configure-messageBox-noIcon = No Icon
|
||||
configure-messageBox-writeAMessage = Write a Message
|
||||
|
||||
Reference in New Issue
Block a user