Merge pull request #1730 from coralproject/timestamp

[next] Timestamp - Reader can see relative time of when that comment was posted
This commit is contained in:
Belén Curcio
2018-07-16 13:39:39 -03:00
committed by GitHub
76 changed files with 1143 additions and 262 deletions
+2 -1
View File
@@ -180,7 +180,7 @@ module.exports = {
// All available locales can be loadable on demand.
// To restrict available locales set:
// availableLocales: ["en-US"]
// availableLocales: ["en-US"],
},
},
],
@@ -247,6 +247,7 @@ module.exports = {
options: {
modules: true,
importLoaders: 1,
localIdentName: "[name]-[local]-[hash:base64:5]",
},
},
{
+12 -16
View File
@@ -9152,14 +9152,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -9174,20 +9172,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -9304,8 +9299,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@@ -9317,7 +9311,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -9332,7 +9325,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -9444,8 +9436,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -9578,7 +9569,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -18851,6 +18841,12 @@
"react-is": "^16.4.1"
}
},
"react-timeago": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/react-timeago/-/react-timeago-4.1.9.tgz",
"integrity": "sha512-MKucv9nU65BOPqIrClAFxqvpGCC4RdRpqp0P1YIb7C3yT6TQVdcoOlr0k4TDHvLQhbkwd3nbTxiDQMa3iDlZxg==",
"dev": true
},
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+1
View File
@@ -136,6 +136,7 @@
"react-relay": "github:coralproject/patched#react-relay",
"react-responsive": "^4.1.0",
"react-test-renderer": "^16.4.1",
"react-timeago": "^4.1.9",
"recompose": "^0.27.1",
"relay-compiler": "github:coralproject/patched#relay-compiler",
"relay-compiler-language-typescript": "github:coralproject/patched#relay-compiler-language-typescript",
@@ -1,7 +1,9 @@
import { LocalizationProvider } from "fluent-react/compat";
import { MessageContext } from "fluent/compat";
import React, { StatelessComponent } from "react";
import { Formatter } from "react-timeago";
import { Environment } from "relay-runtime";
import { UIContext } from "talk-ui/components";
export interface TalkContext {
// relayEnvironment for our relay framework.
@@ -9,6 +11,9 @@ export interface TalkContext {
// localMessages for our i18n framework.
localeMessages: MessageContext[];
// formatter for timeago.
timeagoFormatter?: Formatter;
}
const { Provider, Consumer } = React.createContext<TalkContext>({} as any);
@@ -27,7 +32,9 @@ export const TalkContextProvider: StatelessComponent<{
}> = ({ value, children }) => (
<Provider value={value}>
<LocalizationProvider messages={value.localeMessages}>
{children}
<UIContext.Provider value={{ timeagoFormatter: value.timeagoFormatter }}>
{children}
</UIContext.Provider>
</LocalizationProvider>
</Provider>
);
@@ -1,4 +1,7 @@
import { Localized } from "fluent-react/compat";
import { noop } from "lodash";
import React from "react";
import { Formatter } from "react-timeago";
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import { generateMessages, LocalesData, negotiateLanguages } from "../i18n";
@@ -16,6 +19,25 @@ interface CreateContextArguments {
init?: ((context: TalkContext) => void | Promise<void>);
}
/**
* timeagoFormatter integrates timeago into our translation
* framework. It gets injected into the UIContext.
*/
export const timeagoFormatter: Formatter = (value, unit, suffix) => {
// We use 'in' instead of 'from now' for language consistency
const ourSuffix = suffix === "from now" ? "in" : suffix;
return (
<Localized
id="framework-timeago"
$value={value}
$unit={unit}
$suffix={ourSuffix}
>
<span>now</span>
</Localized>
);
};
/**
* `createContext` manages the dependencies of our framework
* and returns a `TalkContext` that can be passed to the
@@ -46,6 +68,7 @@ export default async function createContext({
const context = {
relayEnvironment,
localeMessages,
timeagoFormatter,
};
// Run custom initializations.
+14
View File
@@ -0,0 +1,14 @@
/* Here we add global stylings for body and document */
:global {
body {
margin: "0";
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
}
}
.root {
}
@@ -0,0 +1,22 @@
import { shallow } from "enzyme";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import App from "./App";
it("renders correctly", () => {
const props: PropTypesOf<typeof App> = {
asset: {},
};
const wrapper = shallow(<App {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders correctly when asset is null", () => {
const props: PropTypesOf<typeof App> = {
asset: null,
};
const wrapper = shallow(<App {...props} />);
expect(wrapper).toMatchSnapshot();
});
+3 -1
View File
@@ -5,6 +5,8 @@ import { Flex } from "talk-ui/components";
import StreamContainer from "../containers/StreamContainer";
import * as styles from "./App.css";
export interface AppProps {
asset: {} | null;
}
@@ -12,7 +14,7 @@ export interface AppProps {
const App: StatelessComponent<AppProps> = props => {
if (props.asset) {
return (
<Flex justifyContent="center">
<Flex justifyContent="center" className={styles.root}>
<StreamContainer asset={props.asset} />
</Flex>
);
@@ -1,10 +0,0 @@
.root {
}
.gutterBottom {
margin-bottom: calc(1px * var(--spacing-unit));
}
.topBar {
margin-bottom: calc(0.5px * var(--spacing-unit));
}
@@ -1,26 +1,17 @@
import { shallow } from "enzyme";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import Comment from "./Comment";
it("renders username and body", () => {
const props = {
const props: PropTypesOf<typeof Comment> = {
author: {
username: "Marvin",
},
body: "Woof",
};
const wrapper = shallow(<Comment {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with gutterBottom", () => {
const props = {
author: {
username: "Marvin",
},
body: "Woof",
gutterBottom: true,
createdAt: "1995-12-17T03:24:00.000Z",
};
const wrapper = shallow(<Comment {...props} />);
expect(wrapper).toMatchSnapshot();
@@ -1,30 +1,27 @@
import cn from "classnames";
import React from "react";
import { StatelessComponent } from "react";
import { Typography } from "talk-ui/components";
import * as styles from "./Comment.css";
import Timestamp from "./Timestamp";
import TopBar from "./TopBar";
import Username from "./Username";
export interface CommentProps {
className?: string;
author: {
username: string;
} | null;
body: string | null;
gutterBottom?: boolean;
createdAt: string;
}
const Comment: StatelessComponent<CommentProps> = props => {
const rootClassName = cn(styles.root, props.className, {
[styles.gutterBottom]: props.gutterBottom,
});
return (
<div className={rootClassName} role="article">
<div className={styles.topBar}>
<div role="article">
<TopBar>
{props.author && <Username>{props.author.username}</Username>}
</div>
<Timestamp>{props.createdAt}</Timestamp>
</TopBar>
<Typography>{props.body}</Typography>
</div>
);
@@ -0,0 +1,3 @@
.root {
composes: timestamp from "talk-ui/shared/typography.css";
}
@@ -0,0 +1,14 @@
import { shallow } from "enzyme";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import Timestamp from "./Timestamp";
it("renders correctly", () => {
const props: PropTypesOf<typeof Timestamp> = {
children: "1995-12-17T03:24:00.000Z",
};
const wrapper = shallow(<Timestamp {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -0,0 +1,16 @@
import React from "react";
import { StatelessComponent } from "react";
import { RelativeTime } from "talk-ui/components";
import * as styles from "./Timestamp.css";
export interface TimestampProps {
children: string;
}
const Timestamp: StatelessComponent<TimestampProps> = props => (
<RelativeTime className={styles.root} date={props.children} />
);
export default Timestamp;
@@ -0,0 +1,3 @@
.root {
margin-bottom: calc(0.5 * var(--spacing-unit));
}
@@ -0,0 +1,45 @@
import React from "react";
import TestRenderer from "react-test-renderer";
import { PropTypesOf } from "talk-framework/types";
import { UIContext, UIContextProps } from "talk-ui/components";
import TopBar from "./TopBar";
it("renders correctly on small screens", () => {
const props: PropTypesOf<typeof TopBar> = {
children: <div>Hello World</div>,
};
const context: UIContextProps = {
mediaQueryValues: {
width: 320,
},
};
const testRenderer = TestRenderer.create(
<UIContext.Provider value={context}>
<TopBar {...props} />
</UIContext.Provider>
);
expect(testRenderer.toJSON()).toMatchSnapshot();
});
it("renders correctly on big screens", () => {
const props: PropTypesOf<typeof TopBar> = {
children: <div>Hello World</div>,
};
const context: UIContextProps = {
mediaQueryValues: {
width: 1600,
},
};
const testRenderer = TestRenderer.create(
<UIContext.Provider value={context}>
<TopBar {...props} />
</UIContext.Provider>
);
expect(testRenderer.toJSON()).toMatchSnapshot();
});
@@ -0,0 +1,32 @@
import cn from "classnames";
import React from "react";
import { StatelessComponent } from "react";
import { Flex, MatchMedia } from "talk-ui/components";
import * as styles from "./TopBar.css";
export interface TopBarProps {
className?: string;
children: React.ReactNode;
}
const TopBar: StatelessComponent<TopBarProps> = props => {
const rootClassName = cn(styles.root, props.className);
return (
<MatchMedia minWidth="xs">
{matches => (
<Flex
className={rootClassName}
alignItems="baseline"
direction={matches ? "row" : "column"}
itemGutter={matches ? true : "half"}
>
{props.children}
</Flex>
)}
</MatchMedia>
);
};
export default TopBar;
@@ -0,0 +1,3 @@
.root {
line-height: 1;
}
@@ -0,0 +1,45 @@
import React from "react";
import TestRenderer from "react-test-renderer";
import { PropTypesOf } from "talk-framework/types";
import { UIContext, UIContextProps } from "talk-ui/components";
import Username from "./Username";
it("renders correctly on small screens", () => {
const props: PropTypesOf<typeof Username> = {
children: "Marvin",
};
const context: UIContextProps = {
mediaQueryValues: {
width: 320,
},
};
const testRenderer = TestRenderer.create(
<UIContext.Provider value={context}>
<Username {...props} />
</UIContext.Provider>
);
expect(testRenderer.toJSON()).toMatchSnapshot();
});
it("renders correctly on big screens", () => {
const props: PropTypesOf<typeof Username> = {
children: "Marvin",
};
const context: UIContextProps = {
mediaQueryValues: {
width: 1600,
},
};
const testRenderer = TestRenderer.create(
<UIContext.Provider value={context}>
<Username {...props} />
</UIContext.Provider>
);
expect(testRenderer.toJSON()).toMatchSnapshot();
});
@@ -0,0 +1,28 @@
import React from "react";
import { StatelessComponent } from "react";
import { MatchMedia, Typography } from "talk-ui/components";
import * as styles from "./Username.css";
export interface UsernameProps {
children: string;
}
const Username: StatelessComponent<UsernameProps> = props => {
return (
<MatchMedia minWidth="xs">
{matches => (
<Typography
variant={matches ? "heading2" : "heading3"}
className={styles.root}
component="span"
>
{props.children}
</Typography>
)}
</MatchMedia>
);
};
export default Username;
@@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders username and body 1`] = `
<div
role="article"
>
<TopBar>
<Username>
Marvin
</Username>
<Timestamp>
1995-12-17T03:24:00.000Z
</Timestamp>
</TopBar>
<withPropsOnChange(Typography)>
Woof
</withPropsOnChange(Typography)>
</div>
`;
@@ -0,0 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<withPropsOnChange(RelativeTime)
className="Timestamp-root"
date="1995-12-17T03:24:00.000Z"
/>
`;
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly on big screens 1`] = `
<div
className="Flex-root TopBar-root Flex-itemGutter Flex-alignBaseline Flex-directionRow"
>
<div>
Hello World
</div>
</div>
`;
exports[`renders correctly on small screens 1`] = `
<div
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<div>
Hello World
</div>
</div>
`;
@@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly on big screens 1`] = `
<span
className="Typography-root Typography-heading2 Username-root"
>
Marvin
</span>
`;
exports[`renders correctly on small screens 1`] = `
<span
className="Typography-root Typography-heading3 Username-root"
>
Marvin
</span>
`;
@@ -0,0 +1 @@
export { default, default as Comment, CommentProps } from "./Comment";
+1 -1
View File
@@ -1,6 +1,6 @@
.root {
border-left: 3px solid;
padding-left: calc(1px * var(--spacing-unit));
padding-left: var(--spacing-unit);
}
.level0 {
@@ -0,0 +1,14 @@
import { shallow } from "enzyme";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import Indent from "./Indent";
it("renders correctly", () => {
const props: PropTypesOf<typeof Indent> = {
children: <div>Hello World</div>,
};
const wrapper = shallow(<Indent {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -5,7 +5,7 @@
height: 100px;
width: 100%;
box-sizing: border-box;
margin-bottom: calc(1px * var(--spacing-unit));
margin-bottom: var(--spacing-unit);
}
.postButtonContainer {
@@ -3,10 +3,12 @@ import { noop } from "lodash";
import React from "react";
import sinon, { SinonSpy } from "sinon";
import ReplyList, { ReplyListProps } from "./ReplyList";
import { PropTypesOf } from "talk-framework/types";
import ReplyList from "./ReplyList";
it("renders correctly", () => {
const props: ReplyListProps = {
const props: PropTypesOf<typeof ReplyList> = {
commentID: "comment-id",
comments: [{ id: "comment-1" }, { id: "comment-2" }],
onShowAll: noop,
@@ -18,7 +20,7 @@ it("renders correctly", () => {
});
describe("when there is more", () => {
const props: ReplyListProps = {
const props: PropTypesOf<typeof ReplyList> = {
commentID: "comment-id",
comments: [{ id: "comment-1" }, { id: "comment-2" }],
onShowAll: sinon.spy(),
@@ -2,7 +2,7 @@ import { Localized } from "fluent-react/compat";
import * as React from "react";
import { StatelessComponent } from "react";
import { Button } from "talk-ui/components";
import { Button, Flex } from "talk-ui/components";
import CommentContainer from "../containers/CommentContainer";
import Indent from "./Indent";
@@ -18,9 +18,14 @@ export interface ReplyListProps {
const ReplyList: StatelessComponent<ReplyListProps> = props => {
return (
<Indent>
<div id={`talk-comments-replyList-log--${props.commentID}`} role="log">
<Flex
direction="column"
id={`talk-comments-replyList-log--${props.commentID}`}
role="log"
itemGutter
>
{props.comments.map(comment => (
<CommentContainer key={comment.id} data={comment} gutterBottom />
<CommentContainer key={comment.id} data={comment} />
))}
{props.hasMore && (
<Localized id="comments-replyList-showAll">
@@ -37,7 +42,7 @@ const ReplyList: StatelessComponent<ReplyListProps> = props => {
</Button>
</Localized>
)}
</div>
</Flex>
</Indent>
);
};
@@ -1,12 +1,14 @@
import { shallow } from "enzyme";
import { noop } from "lodash";
import React from "react";
import sinon from "sinon";
import sinon, { SinonSpy } from "sinon";
import Stream, { StreamProps } from "./Stream";
import { PropTypesOf } from "talk-framework/types";
import Stream from "./Stream";
it("renders correctly", () => {
const props: StreamProps = {
const props: PropTypesOf<typeof Stream> = {
assetID: "asset-id",
isClosed: false,
comments: [{ id: "comment-1" }, { id: "comment-2" }],
@@ -19,7 +21,7 @@ it("renders correctly", () => {
});
describe("when there is more", () => {
const props = {
const props: PropTypesOf<typeof Stream> = {
assetID: "asset-id",
isClosed: false,
comments: [{ id: "comment-1" }, { id: "comment-2" }],
@@ -35,7 +37,7 @@ describe("when there is more", () => {
it("calls onLoadMore", () => {
wrapper.find("#talk-comments-stream-loadMore").simulate("click");
expect(props.onLoadMore.calledOnce).toBe(true);
expect((props.onLoadMore as SinonSpy).calledOnce).toBe(true);
});
const wrapperDisabledButton = shallow(<Stream {...props} disableLoadMore />);
+12 -6
View File
@@ -2,7 +2,7 @@ import { Localized } from "fluent-react/compat";
import * as React from "react";
import { StatelessComponent } from "react";
import { Button } from "talk-ui/components";
import { Button, Flex } from "talk-ui/components";
import CommentContainer from "../containers/CommentContainer";
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
@@ -24,12 +24,18 @@ const Stream: StatelessComponent<StreamProps> = props => {
<div className={styles.root}>
<Logo gutterBottom />
<PostCommentFormContainer assetID={props.assetID} />
<div id="talk-comments-stream-log" role="log" aria-live="polite">
<Flex
direction="column"
id="talk-comments-stream-log"
role="log"
aria-live="polite"
itemGutter
>
{props.comments.map(comment => (
<div key={comment.id}>
<CommentContainer data={comment} gutterBottom />
<Flex direction="column" key={comment.id} itemGutter>
<CommentContainer data={comment} />
<ReplyListContainer comment={comment} />
</div>
</Flex>
))}
{props.hasMore && (
<Localized id="comments-stream-loadMore">
@@ -46,7 +52,7 @@ const Stream: StatelessComponent<StreamProps> = props => {
</Button>
</Localized>
)}
</div>
</Flex>
</div>
);
};
@@ -1,3 +0,0 @@
.root {
composes: heading4 from "talk-ui/shared/typography.css";
}
@@ -1,12 +0,0 @@
import { shallow } from "enzyme";
import React from "react";
import Username from "./Username";
it("renders correctly", () => {
const props = {
children: "Marvin",
};
const wrapper = shallow(<Username {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -1,14 +0,0 @@
import React from "react";
import { StatelessComponent } from "react";
import * as styles from "./Username.css";
export interface CommentProps {
children: string;
}
const Username: StatelessComponent<CommentProps> = props => {
return <span className={styles.root}>{props.children}</span>;
};
export default Username;
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<Flex
className="App-root"
justifyContent="center"
>
<Relay(StreamContainer)
asset={Object {}}
/>
</Flex>
`;
exports[`renders correctly when asset is null 1`] = `
<div>
Asset not found
</div>
`;
@@ -1,37 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders username and body 1`] = `
<div
className="Comment-root"
role="article"
>
<div
className="Comment-topBar"
>
<Username>
Marvin
</Username>
</div>
<withPropsOnChange(Typography)>
Woof
</withPropsOnChange(Typography)>
</div>
`;
exports[`renders with gutterBottom 1`] = `
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
>
<Username>
Marvin
</Username>
</div>
<withPropsOnChange(Typography)>
Woof
</withPropsOnChange(Typography)>
</div>
`;
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<div
className="Indent-root Indent-level0"
>
<div>
Hello World
</div>
</div>
`;
@@ -2,8 +2,10 @@
exports[`renders correctly 1`] = `
<Indent>
<div
<Flex
direction="column"
id="talk-comments-replyList-log--comment-id"
itemGutter={true}
role="log"
>
<Relay(CommentContainer)
@@ -12,7 +14,6 @@ exports[`renders correctly 1`] = `
"id": "comment-1",
}
}
gutterBottom={true}
key="comment-1"
/>
<Relay(CommentContainer)
@@ -21,17 +22,18 @@ exports[`renders correctly 1`] = `
"id": "comment-2",
}
}
gutterBottom={true}
key="comment-2"
/>
</div>
</Flex>
</Indent>
`;
exports[`when there is more renders a load more button 1`] = `
<Indent>
<div
<Flex
direction="column"
id="talk-comments-replyList-log--comment-id"
itemGutter={true}
role="log"
>
<Relay(CommentContainer)
@@ -40,7 +42,6 @@ exports[`when there is more renders a load more button 1`] = `
"id": "comment-1",
}
}
gutterBottom={true}
key="comment-1"
/>
<Relay(CommentContainer)
@@ -49,7 +50,6 @@ exports[`when there is more renders a load more button 1`] = `
"id": "comment-2",
}
}
gutterBottom={true}
key="comment-2"
/>
<Localized
@@ -67,6 +67,6 @@ exports[`when there is more renders a load more button 1`] = `
Show All Replies
</withPropsOnChange(Button)>
</Localized>
</div>
</Flex>
</Indent>
`;
@@ -10,12 +10,16 @@ exports[`renders correctly 1`] = `
<withContext(createMutationContainer(PostCommentFormContainer))
assetID="asset-id"
/>
<div
<Flex
aria-live="polite"
direction="column"
id="talk-comments-stream-log"
itemGutter={true}
role="log"
>
<div
<Flex
direction="column"
itemGutter={true}
key="comment-1"
>
<Relay(CommentContainer)
@@ -24,7 +28,6 @@ exports[`renders correctly 1`] = `
"id": "comment-1",
}
}
gutterBottom={true}
/>
<Relay(ReplyListContainer)
comment={
@@ -33,8 +36,10 @@ exports[`renders correctly 1`] = `
}
}
/>
</div>
<div
</Flex>
<Flex
direction="column"
itemGutter={true}
key="comment-2"
>
<Relay(CommentContainer)
@@ -43,7 +48,6 @@ exports[`renders correctly 1`] = `
"id": "comment-2",
}
}
gutterBottom={true}
/>
<Relay(ReplyListContainer)
comment={
@@ -52,8 +56,8 @@ exports[`renders correctly 1`] = `
}
}
/>
</div>
</div>
</Flex>
</Flex>
</div>
`;
@@ -67,12 +71,16 @@ exports[`when there is more disables load more button 1`] = `
<withContext(createMutationContainer(PostCommentFormContainer))
assetID="asset-id"
/>
<div
<Flex
aria-live="polite"
direction="column"
id="talk-comments-stream-log"
itemGutter={true}
role="log"
>
<div
<Flex
direction="column"
itemGutter={true}
key="comment-1"
>
<Relay(CommentContainer)
@@ -81,7 +89,6 @@ exports[`when there is more disables load more button 1`] = `
"id": "comment-1",
}
}
gutterBottom={true}
/>
<Relay(ReplyListContainer)
comment={
@@ -90,8 +97,10 @@ exports[`when there is more disables load more button 1`] = `
}
}
/>
</div>
<div
</Flex>
<Flex
direction="column"
itemGutter={true}
key="comment-2"
>
<Relay(CommentContainer)
@@ -100,7 +109,6 @@ exports[`when there is more disables load more button 1`] = `
"id": "comment-2",
}
}
gutterBottom={true}
/>
<Relay(ReplyListContainer)
comment={
@@ -109,7 +117,7 @@ exports[`when there is more disables load more button 1`] = `
}
}
/>
</div>
</Flex>
<Localized
id="comments-stream-loadMore"
>
@@ -125,7 +133,7 @@ exports[`when there is more disables load more button 1`] = `
Load More
</withPropsOnChange(Button)>
</Localized>
</div>
</Flex>
</div>
`;
@@ -139,12 +147,16 @@ exports[`when there is more renders a load more button 1`] = `
<withContext(createMutationContainer(PostCommentFormContainer))
assetID="asset-id"
/>
<div
<Flex
aria-live="polite"
direction="column"
id="talk-comments-stream-log"
itemGutter={true}
role="log"
>
<div
<Flex
direction="column"
itemGutter={true}
key="comment-1"
>
<Relay(CommentContainer)
@@ -153,7 +165,6 @@ exports[`when there is more renders a load more button 1`] = `
"id": "comment-1",
}
}
gutterBottom={true}
/>
<Relay(ReplyListContainer)
comment={
@@ -162,8 +173,10 @@ exports[`when there is more renders a load more button 1`] = `
}
}
/>
</div>
<div
</Flex>
<Flex
direction="column"
itemGutter={true}
key="comment-2"
>
<Relay(CommentContainer)
@@ -172,7 +185,6 @@ exports[`when there is more renders a load more button 1`] = `
"id": "comment-2",
}
}
gutterBottom={true}
/>
<Relay(ReplyListContainer)
comment={
@@ -181,7 +193,7 @@ exports[`when there is more renders a load more button 1`] = `
}
}
/>
</div>
</Flex>
<Localized
id="comments-stream-loadMore"
>
@@ -197,6 +209,6 @@ exports[`when there is more renders a load more button 1`] = `
Load More
</withPropsOnChange(Button)>
</Localized>
</div>
</Flex>
</div>
`;
@@ -0,0 +1,16 @@
import { shallow } from "enzyme";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import { AppContainer } from "./AppContainer";
it("renders correctly", () => {
const props: PropTypesOf<typeof AppContainer> = {
data: {
asset: {},
},
};
const wrapper = shallow(<AppContainer {...props} />);
expect(wrapper).toMatchSnapshot();
});
@@ -11,7 +11,7 @@ interface InnerProps {
data: Data;
}
const AppContainer: StatelessComponent<InnerProps> = props => {
export const AppContainer: StatelessComponent<InnerProps> = props => {
return <App {...props.data} />;
};
@@ -1,15 +1,18 @@
import { shallow } from "enzyme";
import React from "react";
import { PropTypesOf } from "talk-framework/types";
import { CommentContainer } from "./CommentContainer";
it("renders username and body", () => {
const props = {
const props: PropTypesOf<typeof CommentContainer> = {
data: {
author: {
username: "Marvin",
},
body: "Woof",
createdAt: "1995-12-17T03:24:00.000Z",
},
};
@@ -16,6 +16,7 @@ graphql`
username
}
body
createdAt
}
`;
@@ -8,7 +8,7 @@ import ReplyList from "../components/ReplyList";
import { ReplyListContainer } from "./ReplyListContainer";
it("renders correctly", () => {
const props: any = {
const props: PropTypesOf<typeof ReplyListContainer> = {
comment: {
id: "comment-id",
replies: {
@@ -18,14 +18,14 @@ it("renders correctly", () => {
relay: {
hasMore: noop,
isLoading: noop,
},
} as any,
};
const wrapper = shallow(<ReplyListContainer {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders correctly when replies are null", () => {
const props: any = {
const props: PropTypesOf<typeof ReplyListContainer> = {
comment: {
id: "comment-id",
replies: null,
@@ -33,7 +33,7 @@ it("renders correctly when replies are null", () => {
relay: {
hasMore: noop,
isLoading: noop,
},
} as any,
};
const wrapper = shallow(<ReplyListContainer {...props} />);
expect(wrapper).toMatchSnapshot();
@@ -22,7 +22,10 @@ export class ReplyListContainer extends React.Component<InnerProps> {
};
public render() {
if (this.props.comment.replies === null) {
if (
this.props.comment.replies === null ||
this.props.comment.replies.edges.length === 0
) {
return null;
}
const comments = this.props.comment.replies.edges.map(edge => edge.node);
@@ -1,9 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<span
className="Username-root"
>
Marvin
</span>
<App
asset={Object {}}
/>
`;
@@ -8,5 +8,6 @@ exports[`renders username and body 1`] = `
}
}
body="Woof"
createdAt="1995-12-17T03:24:00.000Z"
/>
`;
@@ -2,7 +2,7 @@
exports[`loads more comments 1`] = `
<div
className="Flex-root Flex-justifyCenter"
className="Flex-root App-root Flex-justifyCenter"
>
<div
className="Stream-root"
@@ -40,22 +40,31 @@ exports[`loads more comments 1`] = `
</form>
<div
aria-live="polite"
className="Flex-root Flex-itemGutter Flex-directionColumn"
id="talk-comments-stream-log"
role="log"
>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Markus
</span>
<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>
<p
className="Typography-root Typography-body1"
@@ -64,19 +73,27 @@ exports[`loads more comments 1`] = `
</p>
</div>
</div>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Lukas
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
<p
className="Typography-root Typography-body1"
@@ -85,19 +102,27 @@ exports[`loads more comments 1`] = `
</p>
</div>
</div>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Isabelle
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:14:00.000Z"
title="2018-07-06T18:14:00.000Z"
>
2018-07-06T18:14:00.000Z
</time>
</div>
<p
className="Typography-root Typography-body1"
@@ -113,7 +138,7 @@ exports[`loads more comments 1`] = `
exports[`renders comment stream 1`] = `
<div
className="Flex-root Flex-justifyCenter"
className="Flex-root App-root Flex-justifyCenter"
>
<div
className="Stream-root"
@@ -151,22 +176,31 @@ exports[`renders comment stream 1`] = `
</form>
<div
aria-live="polite"
className="Flex-root Flex-itemGutter Flex-directionColumn"
id="talk-comments-stream-log"
role="log"
>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Markus
</span>
<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>
<p
className="Typography-root Typography-body1"
@@ -175,19 +209,27 @@ exports[`renders comment stream 1`] = `
</p>
</div>
</div>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Lukas
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
<p
className="Typography-root Typography-body1"
@@ -2,7 +2,7 @@
exports[`renders comment stream 1`] = `
<div
className="Flex-root Flex-justifyCenter"
className="Flex-root App-root Flex-justifyCenter"
>
<div
className="Stream-root"
@@ -40,22 +40,31 @@ exports[`renders comment stream 1`] = `
</form>
<div
aria-live="polite"
className="Flex-root Flex-itemGutter Flex-directionColumn"
id="talk-comments-stream-log"
role="log"
>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Markus
</span>
<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>
<p
className="Typography-root Typography-body1"
@@ -64,19 +73,27 @@ exports[`renders comment stream 1`] = `
</p>
</div>
</div>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Markus
</span>
<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>
<p
className="Typography-root Typography-body1"
@@ -88,21 +105,28 @@ exports[`renders comment stream 1`] = `
className="Indent-root Indent-level0"
>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
id="talk-comments-replyList-log--comment-with-replies"
role="log"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Markus
</span>
<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>
<p
className="Typography-root Typography-body1"
@@ -111,17 +135,23 @@ exports[`renders comment stream 1`] = `
</p>
</div>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Lukas
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
<p
className="Typography-root Typography-body1"
@@ -2,7 +2,7 @@
exports[`renders comment stream 1`] = `
<div
className="Flex-root Flex-justifyCenter"
className="Flex-root App-root Flex-justifyCenter"
>
<div
className="Stream-root"
@@ -40,22 +40,31 @@ exports[`renders comment stream 1`] = `
</form>
<div
aria-live="polite"
className="Flex-root Flex-itemGutter Flex-directionColumn"
id="talk-comments-stream-log"
role="log"
>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Markus
</span>
<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>
<p
className="Typography-root Typography-body1"
@@ -64,19 +73,27 @@ exports[`renders comment stream 1`] = `
</p>
</div>
</div>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Lukas
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
<p
className="Typography-root Typography-body1"
@@ -2,7 +2,7 @@
exports[`renders comment stream 1`] = `
<div
className="Flex-root Flex-justifyCenter"
className="Flex-root App-root Flex-justifyCenter"
>
<div
className="Stream-root"
@@ -40,22 +40,31 @@ exports[`renders comment stream 1`] = `
</form>
<div
aria-live="polite"
className="Flex-root Flex-itemGutter Flex-directionColumn"
id="talk-comments-stream-log"
role="log"
>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Markus
</span>
<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>
<p
className="Typography-root Typography-body1"
@@ -67,21 +76,28 @@ exports[`renders comment stream 1`] = `
className="Indent-root Indent-level0"
>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
id="talk-comments-replyList-log--comment-0"
role="log"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Lukas
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
<p
className="Typography-root Typography-body1"
@@ -111,7 +127,7 @@ exports[`renders comment stream 1`] = `
exports[`show all replies 1`] = `
<div
className="Flex-root Flex-justifyCenter"
className="Flex-root App-root Flex-justifyCenter"
>
<div
className="Stream-root"
@@ -149,22 +165,31 @@ exports[`show all replies 1`] = `
</form>
<div
aria-live="polite"
className="Flex-root Flex-itemGutter Flex-directionColumn"
id="talk-comments-stream-log"
role="log"
>
<div>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Markus
</span>
<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>
<p
className="Typography-root Typography-body1"
@@ -176,21 +201,28 @@ exports[`show all replies 1`] = `
className="Indent-root Indent-level0"
>
<div
className="Flex-root Flex-itemGutter Flex-directionColumn"
id="talk-comments-replyList-log--comment-0"
role="log"
>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Lukas
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:20:00.000Z"
title="2018-07-06T18:20:00.000Z"
>
2018-07-06T18:20:00.000Z
</time>
</div>
<p
className="Typography-root Typography-body1"
@@ -199,17 +231,23 @@ exports[`show all replies 1`] = `
</p>
</div>
<div
className="Comment-root Comment-gutterBottom"
role="article"
>
<div
className="Comment-topBar"
className="Flex-root TopBar-root Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
>
<span
className="Username-root"
className="Typography-root Typography-heading3 Username-root"
>
Isabelle
</span>
<time
className="Timestamp-root RelativeTime-root"
dateTime="2018-07-06T18:14:00.000Z"
title="2018-07-06T18:14:00.000Z"
>
2018-07-06T18:14:00.000Z
</time>
</div>
<p
className="Typography-root Typography-body1"
+4 -4
View File
@@ -18,19 +18,19 @@ export const comments = [
id: "comment-0",
author: users[0],
body: "Joining Too",
createdAt: "2018-07-06T18:24:00",
createdAt: "2018-07-06T18:24:00.000Z",
},
{
id: "comment-1",
author: users[1],
body: "What's up?",
createdAt: "2018-07-06T18:20:00",
createdAt: "2018-07-06T18:20:00.000Z",
},
{
id: "comment-2",
author: users[2],
body: "Hey!",
createdAt: "2018-07-06T18:14:00",
createdAt: "2018-07-06T18:14:00.000Z",
},
];
@@ -54,7 +54,7 @@ export const commentWithReplies = {
id: "comment-with-replies",
author: users[0],
body: "I like yoghurt",
createdAt: "2018-07-06T18:24:00",
createdAt: "2018-07-06T18:24:00.000Z",
replies: {
edges: [
{ node: comments[0], cursor: comments[0].createdAt },
@@ -2,6 +2,54 @@
display: flex;
}
.halfItemGutter {
& > * {
margin: 0 calc(0.5 * var(--spacing-unit)) 0 0;
}
&.directionRowReverse {
& > * {
margin: 0 0 0 calc(0.5 * var(--spacing-unit));
}
}
&.directionColumn {
& > * {
margin: 0 0 calc(0.5 * var(--spacing-unit)) 0;
}
}
&.directionColumnReverese {
& > * {
margin: calc(0.5 * var(--spacing-unit)) 0 0 0;
}
}
& > *:last-child {
margin: 0;
}
}
.itemGutter {
& > * {
margin: 0 var(--spacing-unit) 0 0;
}
&.directionRowReverse {
& > * {
margin: 0 0 0 var(--spacing-unit);
}
}
&.directionColumn {
& > * {
margin: 0 0 var(--spacing-unit) 0;
}
}
&.directionColumnReverese {
& > * {
margin: var(--spacing-unit) 0 0 0;
}
}
& > *:last-child {
margin: 0;
}
}
.justifyFlexStart {
justify-content: flex-start;
}
+17 -3
View File
@@ -7,6 +7,8 @@ import { pascalCase } from "talk-common/utils";
import * as styles from "./Flex.css";
interface InnerProps {
id?: string;
role?: string;
justifyContent?:
| "flex-start"
| "flex-end"
@@ -16,12 +18,24 @@ interface InnerProps {
| "space-evenly";
alignItems?: "flex-start" | "flex-end" | "center" | "baseline" | "stretch";
direction?: "row" | "column" | "row-reverse" | "column-reverse";
itemGutter?: boolean | "half";
className?: string;
}
const Flex: StatelessComponent<InnerProps> = props => {
const { justifyContent, alignItems, direction, ...rest } = props;
const {
className,
justifyContent,
alignItems,
direction,
itemGutter,
...rest
} = props;
const classObject: Record<string, boolean> = {};
const classObject: Record<string, boolean> = {
[styles.itemGutter]: itemGutter === true,
[styles.halfItemGutter]: itemGutter === "half",
};
if (justifyContent) {
classObject[(styles as any)[`justify${pascalCase(justifyContent)}`]] = true;
@@ -35,7 +49,7 @@ const Flex: StatelessComponent<InnerProps> = props => {
classObject[(styles as any)[`direction${pascalCase(direction)}`]] = true;
}
const classNames: string = cn(styles.root, classObject);
const classNames: string = cn(styles.root, className, classObject);
return <div className={classNames} {...rest} />;
};
@@ -3,7 +3,7 @@ import React from "react";
import { PropTypesOf } from "talk-ui/types";
import MatchMedia from "./MatchMedia";
import { MatchMedia } from "./MatchMedia";
it("renders correctly", () => {
const props: PropTypesOf<typeof MatchMedia> = {
@@ -1,8 +1,9 @@
import React from "react";
import { ReactNode, StatelessComponent } from "react";
import Responsive from "react-responsive";
import Responsive, { MediaQueryMatchers } from "react-responsive";
import theme from "../../theme/variables";
import UIContext from "../UIContext";
type Breakpoints = keyof typeof theme.breakpoints;
@@ -20,9 +21,10 @@ interface InnerProps {
print?: boolean;
screen?: boolean;
speech?: boolean;
values?: Partial<MediaQueryMatchers>;
}
const MatchMedia: StatelessComponent<InnerProps> = props => {
export const MatchMedia: StatelessComponent<InnerProps> = props => {
const { speech, minWidth, maxWidth, ...rest } = props;
const mapped = {
// TODO: Temporarily map newer speech to older aural type until
@@ -34,4 +36,12 @@ const MatchMedia: StatelessComponent<InnerProps> = props => {
return <Responsive {...rest} {...mapped} />;
};
export default MatchMedia;
const MatchMediaWithContext: StatelessComponent<InnerProps> = props => (
<UIContext.Consumer>
{({ mediaQueryValues }) => (
<MatchMedia {...props} values={mediaQueryValues} />
)}
</UIContext.Consumer>
);
export default MatchMediaWithContext;
@@ -0,0 +1,4 @@
.root {
composes: body1 from "talk-ui/shared/typography.css";
background-color: transparent;
}
@@ -0,0 +1,17 @@
---
name: RelativeTime
menu: UI Kit
---
import { Playground } from 'docz'
import RelativeTime from './RelativeTime'
# RelativeTime
Renders relative time until given `date`.
## Basic usage
<Playground>
<RelativeTime date="2018-07-04T12:14"
formatter={(value, unit, suffix, timestamp) => "".concat(value, " ", unit, " ", suffix)} />
</Playground>
@@ -0,0 +1,42 @@
import React from "react";
import { create } from "react-test-renderer";
import { PropTypesOf } from "talk-ui/types";
import UIContext from "../UIContext";
import RelativeTime from "./RelativeTime";
it("uses default formatter", () => {
const props = {
date: "2018-12-17T03:24:00.000Z",
};
const tree = create(<RelativeTime {...props} />).toJSON();
expect(tree).toMatchSnapshot();
});
it("uses formatter from context", () => {
const context: any = {
timeagoFormatter: () => "My Context Formatter",
};
const props: PropTypesOf<typeof RelativeTime> = {
date: "2018-12-17T03:24:00.000Z",
};
const tree = create(
<UIContext.Provider value={context}>
<RelativeTime {...props} />
</UIContext.Provider>
).toJSON();
expect(tree).toMatchSnapshot();
});
it("uses formatter from props", () => {
const props = {
date: "2018-12-17T03:24:00.000Z",
formatter: () => "My Props Formatter",
};
const tree = create(<RelativeTime {...props} />).toJSON();
expect(tree).toMatchSnapshot();
});
@@ -0,0 +1,42 @@
import cn from "classnames";
import React from "react";
import TimeAgo, { Formatter } from "react-timeago";
import { UIContext } from "talk-ui/components";
import { withStyles } from "talk-ui/hocs";
import { PropTypesOf } from "talk-ui/types";
import * as styles from "./RelativeTime.css";
interface InnerProps {
date: string;
live?: boolean;
classes: typeof styles;
className?: string;
formatter?: Formatter;
}
const defaultFormatter: Formatter = (value, unit, suffix, timestamp: string) =>
new Date(timestamp).toISOString();
class RelativeTime extends React.Component<InnerProps> {
public render() {
const { date, classes, live, className, formatter } = this.props;
return (
<UIContext.Consumer>
{({ timeagoFormatter }) => (
<TimeAgo
date={date}
className={cn(className, classes.root)}
live={live}
formatter={timeagoFormatter || formatter || defaultFormatter}
/>
)}
</UIContext.Consumer>
);
}
}
const enhanced = withStyles(styles)(RelativeTime);
export type RelativeTimeProps = PropTypesOf<typeof enhanced>;
export default enhanced;
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`uses default formatter 1`] = `
<time
className="RelativeTime-root"
dateTime="2018-12-17T03:24:00.000Z"
title="2018-12-17T03:24:00.000Z"
>
2018-12-17T03:24:00.000Z
</time>
`;
exports[`uses formatter from context 1`] = `
<time
className="RelativeTime-root"
dateTime="2018-12-17T03:24:00.000Z"
title="2018-12-17T03:24:00.000Z"
>
My Context Formatter
</time>
`;
exports[`uses formatter from props 1`] = `
<time
className="RelativeTime-root"
dateTime="2018-12-17T03:24:00.000Z"
title="2018-12-17T03:24:00.000Z"
>
My Context Formatter
</time>
`;
@@ -0,0 +1,2 @@
export * from "./RelativeTime";
export { default } from "./RelativeTime";
@@ -43,6 +43,10 @@
composes: overline from "talk-ui/shared/typography.css";
}
.timestamp {
composes: timestamp from "talk-ui/shared/typography.css";
}
.alignLeft {
text-align: left;
}
@@ -70,7 +74,7 @@
}
.paragraph {
margin-bottom: calc(1px * var(--spacing-unit));
margin-bottom: var(--spacing-unit);
}
.colorInherit {
@@ -16,7 +16,8 @@ type Variant =
| "subtitle2"
| "body1"
| "body2"
| "button";
| "button"
| "timestamp";
// Based on Typography Component of Material UI.
// https://github.com/mui-org/material-ui/blob/303199d39b42a321d28347d8440d69166f872f27/packages/material-ui/src/Typography/Typography.js
@@ -99,6 +100,8 @@ const Typography: StatelessComponent<InnerProps> = props => {
{
[classes.colorPrimary]: color === "primary",
[classes.colorSecondary]: color === "secondary",
[classes.colorError]: color === "error",
[classes.colorTextSecondary]: color === "textSecondary",
[classes.noWrap]: noWrap,
[classes.gutterBottom]: gutterBottom,
[classes.paragraph]: paragraph,
@@ -129,6 +132,7 @@ Typography.defaultProps = {
subtitle2: "h3",
body1: "p",
body2: "aside",
timestamp: "span",
},
noWrap: false,
paragraph: false,
@@ -0,0 +1,12 @@
import React from "react";
import { MediaQueryMatchers } from "react-responsive";
import { Formatter } from "react-timeago";
export interface UIContextProps {
timeagoFormatter?: Formatter | null;
mediaQueryValues?: Partial<MediaQueryMatchers>;
}
const UIContext = React.createContext<UIContextProps>({} as any);
export default UIContext;
@@ -0,0 +1 @@
export { default, UIContextProps } from "./UIContext";
+2
View File
@@ -1,5 +1,7 @@
export { default as BaseButton } from "./BaseButton";
export { default as Button } from "./Button";
export { default as Typography } from "./Typography";
export { default as RelativeTime } from "./RelativeTime";
export { default as UIContext, UIContextProps } from "./UIContext";
export { default as Flex } from "./Flex";
export { default as MatchMedia } from "./MatchMedia";
+9
View File
@@ -61,3 +61,12 @@
.overline {
}
.timestamp {
color: var(--palette-text-secondary);
font-family: "Source Sans Pro";
font-weight: var(--font-weight-medium);
font-size: 14px;
line-height: calc(18em / 16);
letter-spacing: calc(0.2em / 16);
}
+2 -2
View File
@@ -6,11 +6,11 @@
*/
:root {
--spacing-unit: var(--spacing-unit-small);
--spacing-unit: calc(1px * var(--spacing-unit-small));
}
@media (min-width: $breakpoints-xs) {
:root {
--spacing-unit: var(--spacing-unit-large);
--spacing-unit: calc(1px * var(--spacing-unit-large));
}
}
@@ -2,6 +2,8 @@ import Context from "talk-server/graph/tenant/context";
import { Comment, ConnectionInput } from "talk-server/models/comment";
export default {
createdAt: async (comment: Comment, _: any, ctx: Context) =>
comment.created_at,
author: async (comment: Comment, _: any, ctx: Context) =>
ctx.loaders.Users.user.load(comment.author_id),
replies: async (comment: Comment, input: ConnectionInput, ctx: Context) =>
@@ -202,6 +202,11 @@ type Comment {
"""
body: String
"""
createdAt is the date in which the comment was created.
"""
createdAt: Time
"""
author is the User that authored the Comment.
"""
+38
View File
@@ -5,3 +5,41 @@
## Validation
framework-validation-required = Dies ist ein Pflichtpfeld.
framework-timeago =
{ $suffix ->
[ago] vor
[in] in
}
{ $value }
{ $unit ->
[second] { $value ->
[1] Sekunde
*[other] Sekunden
}
[minuto] { $value ->
[1] Minute
*[other] Minuten
}
[hour] { $value ->
[0] Stunde
*[other] Stunden
}
[day] { $value ->
[1] Tag
*[other] Tage
}
[week] { $value ->
[1] Woche
*[other] Wochen
}
[month] { $value ->
[1] Monat
*[other] Monate
}
[year] { $value ->
[1] Jahr
*[other] Jahre
}
*[other] unknown unit
}
+45
View File
@@ -5,3 +5,48 @@
## Validation
framework-validation-required = This field is required.
framework-timeago-time =
{ $value }
{ $unit ->
[second] { $value ->
[1] second
*[other] seconds
}
[minute] { $value ->
[1] minute
*[other] minutes
}
[hour] { $value ->
[0] hour
*[other] hours
}
[day] { $value ->
[1] day
*[other] days
}
[week] { $value ->
[1] week
*[other] weeks
}
[month] { $value ->
[1] month
*[other] months
}
[year] { $value ->
[1] year
*[other] years
}
*[other] unknown unit
}
framework-timeago =
{ $value ->
[0] now
*[other]
{ $suffix ->
[ago] {framework-timeago-time} ago
[in] in {framework-timeago-time}
*[other] unknown suffix
}
}
+42
View File
@@ -2,3 +2,45 @@
### All keys must start with `framework` because this file is shared
### among different targets.
framework-timeago =
{ $value ->
[0] ahora
*[other]
{ $suffix ->
[ago] hace
[in] en
*[other] unknown suffix
}
{ $value }
{ $unit ->
[second] { $value ->
[1] segundo
*[other] segundos
}
[minute] { $value ->
[1] minuto
*[other] minutos
}
[hour] { $value ->
[0] hora
*[other] horas
}
[day] { $value ->
[1] dia
*[other] dias
}
[week] { $value ->
[1] semana
*[other] semanas
}
[month] { $value ->
[1] mes
*[other] meses
}
[year] { $value ->
[1] año
*[other] años
}
*[other] unknown unit
}
}
+55
View File
@@ -0,0 +1,55 @@
declare module "react-timeago" {
import React from "react";
export type Formatter = (
value: number,
unit: "second" | "minute" | "hour" | "day" | "week" | "month" | "year",
suffix: "ago" | "from now",
epochMiliseconds: string
) => string | React.ReactElement<any>;
export interface LocaleDefinition {
prefixAgo?: string;
prefixFromNow?: string;
suffixAgo?: string;
suffixFromNow?: string;
second?: string;
seconds?: string;
minute?: string;
minutes?: string;
hour?: string;
hours?: string;
day?: string;
days?: string;
week?: string;
weeks?: string;
month?: string;
months?: string;
year?: string;
years?: string;
wordSeparator?: string;
numbers?: number[];
}
export interface TimeAgoProps {
date: string;
live?: boolean;
className: string;
formatter?: Formatter;
}
const TimeAgo: React.ComponentType<TimeAgoProps>;
export default TimeAgo;
}
declare module "react-timeago/lib/formatters/buildFormatter" {
import { Formatter, LocaleDefinition } from "react-timeago";
function buildFormatter(localeInput: LocaleDefinition): Formatter;
export default buildFormatter;
}
declare module "react-timeago/lib/language-strings/*" {
import { LocaleDefinition } from "react-timeago";
const localeStrings: LocaleDefinition;
export default localeStrings;
}