From e909d21039bc8aa0c07fda48e91c0f18fe091cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Fri, 7 Sep 2018 10:23:29 -0300 Subject: [PATCH 01/53] Adding Message Matching the exact values --- .../client/ui/components/Message/Message.css | 29 +++++++++ .../client/ui/components/Message/Message.mdx | 27 ++++++++ .../ui/components/Message/Message.spec.tsx | 15 +++++ .../client/ui/components/Message/Message.tsx | 61 +++++++++++++++++++ .../client/ui/components/Message/index.ts | 1 + src/core/client/ui/components/index.ts | 1 + 6 files changed, 134 insertions(+) create mode 100644 src/core/client/ui/components/Message/Message.css create mode 100644 src/core/client/ui/components/Message/Message.mdx create mode 100644 src/core/client/ui/components/Message/Message.spec.tsx create mode 100644 src/core/client/ui/components/Message/Message.tsx create mode 100644 src/core/client/ui/components/Message/index.ts diff --git a/src/core/client/ui/components/Message/Message.css b/src/core/client/ui/components/Message/Message.css new file mode 100644 index 000000000..86c7f0129 --- /dev/null +++ b/src/core/client/ui/components/Message/Message.css @@ -0,0 +1,29 @@ +.root { + composes: alertMessage from "talk-ui/shared/typography.css"; + position: relative; + display: inline-flex; + justify-content: flex-start; + align-items: center; + padding: calc(0.8 * var(--spacing-unit)) var(--spacing-unit); + box-sizing: border-box; + border-radius: var(--round-corners); + border-width: 1px; + border-left-width: calc(0.8 * var(--spacing-unit)); + border-style: solid; +} + +.color { + background-color: var(--palette-common-white); + border-color: var(--palette-grey-main); + color: var(--palette-grey-main); +} + +.fullWidth { + display: flex; + width: 100%; +} + +.icon { + margin-right: calc(0.8 * var(--spacing-unit)); + color: var(--palette-grey-main); +} diff --git a/src/core/client/ui/components/Message/Message.mdx b/src/core/client/ui/components/Message/Message.mdx new file mode 100644 index 000000000..9a876d76c --- /dev/null +++ b/src/core/client/ui/components/Message/Message.mdx @@ -0,0 +1,27 @@ +--- +name: Message +menu: UI Kit +--- + +import { Playground } from 'docz' +import Message from './Message' +import HorizontalGutter from '../HorizontalGutter' + +# Message + +## Basic Use + + + This is a message + Contrary to popular belief, Lorem Ipsum is not simply random text. + + + +## Usage with icon + + + Edit: 1 min 23 secs Remaining + + + + diff --git a/src/core/client/ui/components/Message/Message.spec.tsx b/src/core/client/ui/components/Message/Message.spec.tsx new file mode 100644 index 000000000..d1335f8d9 --- /dev/null +++ b/src/core/client/ui/components/Message/Message.spec.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import TestRenderer from "react-test-renderer"; + +import { PropTypesOf } from "talk-ui/types"; + +import Message from "./Message"; + +it("renders correctly", () => { + const props: PropTypesOf = { + className: "custom", + children: "Hello World", + }; + const renderer = TestRenderer.create(); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/Message/Message.tsx b/src/core/client/ui/components/Message/Message.tsx new file mode 100644 index 000000000..19bb8ece5 --- /dev/null +++ b/src/core/client/ui/components/Message/Message.tsx @@ -0,0 +1,61 @@ +import cn from "classnames"; +import React, { ReactNode, StatelessComponent } from "react"; + +import { withStyles } from "talk-ui/hocs"; + +import Icon from "../Icon"; +import * as styles from "./Message.css"; + +export interface MessageProps { + /** + * 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; + /* + * If set renders a full width message + */ + fullWidth?: boolean; + /* + * Name of the icon, render if provided + */ + icon?: string; +} + +const Message: StatelessComponent = props => { + const { className, classes, fullWidth, children, icon, ...rest } = props; + + const rootClassName = cn( + classes.root, + classes.color, + { + [classes.fullWidth]: fullWidth, + }, + className + ); + + return ( +
+ {icon && ( + + {icon} + + )} + {children} +
+ ); +}; + +Message.defaultProps = { + fullWidth: false, +}; + +const enhanced = withStyles(styles)(Message); +export default enhanced; diff --git a/src/core/client/ui/components/Message/index.ts b/src/core/client/ui/components/Message/index.ts new file mode 100644 index 000000000..a6d22dd86 --- /dev/null +++ b/src/core/client/ui/components/Message/index.ts @@ -0,0 +1 @@ +export { default } from "./Message"; diff --git a/src/core/client/ui/components/index.ts b/src/core/client/ui/components/index.ts index f701660dd..61970ed25 100644 --- a/src/core/client/ui/components/index.ts +++ b/src/core/client/ui/components/index.ts @@ -20,3 +20,4 @@ export { default as Spinner } from "./Spinner"; export { default as HorizontalGutter } from "./HorizontalGutter"; export { default as Icon } from "./Icon"; export { default as AriaInfo } from "./AriaInfo"; +export { default as Message } from "./Message"; From 994b0f7fb3502752be97df6058a02df9ae15c09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Fri, 7 Sep 2018 10:56:03 -0300 Subject: [PATCH 02/53] Updated API --- .../client/ui/components/Message/Message.css | 11 ++++-- .../client/ui/components/Message/Message.tsx | 18 ++++++++-- .../ValidationMessage/ValidationMessage.css | 27 --------------- .../ValidationMessage/ValidationMessage.mdx | 2 +- .../ValidationMessage/ValidationMessage.tsx | 34 ++++--------------- 5 files changed, 31 insertions(+), 61 deletions(-) delete mode 100644 src/core/client/ui/components/ValidationMessage/ValidationMessage.css diff --git a/src/core/client/ui/components/Message/Message.css b/src/core/client/ui/components/Message/Message.css index 86c7f0129..267de3201 100644 --- a/src/core/client/ui/components/Message/Message.css +++ b/src/core/client/ui/components/Message/Message.css @@ -8,16 +8,22 @@ box-sizing: border-box; border-radius: var(--round-corners); border-width: 1px; - border-left-width: calc(0.8 * var(--spacing-unit)); border-style: solid; + border-left-width: calc(0.8 * var(--spacing-unit)); } -.color { +.colorInfo { background-color: var(--palette-common-white); border-color: var(--palette-grey-main); color: var(--palette-grey-main); } +.colorValidation { + background-color: var(--palette-error-light); + border-color: var(--palette-error-darkest); + color: var(--palette-common-white); +} + .fullWidth { display: flex; width: 100%; @@ -25,5 +31,4 @@ .icon { margin-right: calc(0.8 * var(--spacing-unit)); - color: var(--palette-grey-main); } diff --git a/src/core/client/ui/components/Message/Message.tsx b/src/core/client/ui/components/Message/Message.tsx index 19bb8ece5..9eb678f4f 100644 --- a/src/core/client/ui/components/Message/Message.tsx +++ b/src/core/client/ui/components/Message/Message.tsx @@ -27,15 +27,28 @@ export interface MessageProps { * Name of the icon, render if provided */ icon?: string; + /* + * Name of color, "info" stays by default - common gray one + */ + color?: "validation" | "info"; } const Message: StatelessComponent = props => { - const { className, classes, fullWidth, children, icon, ...rest } = props; + const { + className, + classes, + fullWidth, + children, + icon, + color, + ...rest + } = props; const rootClassName = cn( classes.root, - classes.color, { + [classes.colorInfo]: color === "info", + [classes.colorValidation]: color === "validation", [classes.fullWidth]: fullWidth, }, className @@ -54,6 +67,7 @@ const Message: StatelessComponent = props => { }; Message.defaultProps = { + color: "info", fullWidth: false, }; diff --git a/src/core/client/ui/components/ValidationMessage/ValidationMessage.css b/src/core/client/ui/components/ValidationMessage/ValidationMessage.css deleted file mode 100644 index b3d6a6290..000000000 --- a/src/core/client/ui/components/ValidationMessage/ValidationMessage.css +++ /dev/null @@ -1,27 +0,0 @@ -.root { - composes: alertMessage from "talk-ui/shared/typography.css"; - position: relative; - display: inline-flex; - justify-content: flex-start; - align-items: center; - padding: calc(0.5 * var(--spacing-unit)) var(--spacing-unit); - box-sizing: border-box; - border-radius: var(--round-corners); - border-left-width: calc(0.5 * var(--spacing-unit)); - border-left-style: solid; -} - -.colorError { - background-color: var(--palette-error-light); - border-color: var(--palette-error-darkest); - color: var(--palette-common-white); -} - -.fullWidth { - display: flex; - width: 100%; -} - -.icon { - margin-right: var(--spacing-unit); -} diff --git a/src/core/client/ui/components/ValidationMessage/ValidationMessage.mdx b/src/core/client/ui/components/ValidationMessage/ValidationMessage.mdx index c8e7b11ad..28bbec5a6 100644 --- a/src/core/client/ui/components/ValidationMessage/ValidationMessage.mdx +++ b/src/core/client/ui/components/ValidationMessage/ValidationMessage.mdx @@ -4,7 +4,7 @@ menu: UI Kit --- import { Playground } from 'docz' -import ValidationMessage from './ValidationMessage.tsx' +import ValidationMessage from './ValidationMessage' import HorizontalGutter from '../HorizontalGutter' # ValidationMessage diff --git a/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx b/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx index 0cc781560..c7abbb8cc 100644 --- a/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx +++ b/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx @@ -1,10 +1,5 @@ -import cn from "classnames"; import React, { ReactNode, StatelessComponent } from "react"; - -import { withStyles } from "talk-ui/hocs"; - -import Icon from "../Icon"; -import * as styles from "./ValidationMessage.css"; +import Message from "../Message"; export interface ValidationMessageProps { /** @@ -16,34 +11,18 @@ export interface ValidationMessageProps { */ className?: string; /** - * Override or extend the styles applied to the component. + * If set renders a full width message */ - classes: typeof styles; - /* - * If set renders a full width message - */ fullWidth?: boolean; } const ValidationMessage: StatelessComponent = props => { - const { className, classes, fullWidth, children, ...rest } = props; - - const rootClassName = cn( - classes.root, - classes.colorError, - { - [classes.fullWidth]: fullWidth, - }, - className - ); + const { className, fullWidth, children, ...rest } = props; return ( -
- - warning - + {children} -
+
); }; @@ -51,5 +30,4 @@ ValidationMessage.defaultProps = { fullWidth: false, }; -const enhanced = withStyles(styles)(ValidationMessage); -export default enhanced; +export default ValidationMessage; From b37ec2b944e16aa4b58634866d4f579a070d550e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Fri, 7 Sep 2018 12:54:22 -0300 Subject: [PATCH 03/53] Supporting icons with ValidationMessages --- .../ValidationMessage/ValidationMessage.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx b/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx index c7abbb8cc..34782a29d 100644 --- a/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx +++ b/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx @@ -14,13 +14,22 @@ export interface ValidationMessageProps { * If set renders a full width message */ fullWidth?: boolean; + /* + * Name of the icon, if not provided it will default to warning icon + */ + icon?: string; } const ValidationMessage: StatelessComponent = props => { - const { className, fullWidth, children, ...rest } = props; + const { className, fullWidth, children, icon, ...rest } = props; return ( - + {children} ); From dce2177dcd99d4eebb58d18b58fdfa6da519a7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Fri, 7 Sep 2018 13:03:56 -0300 Subject: [PATCH 04/53] Updated snapshots --- .../test/__snapshots__/signIn.spec.tsx.snap | 24 +-- .../test/__snapshots__/signUp.spec.tsx.snap | 164 +++++++++--------- .../__snapshots__/Message.spec.tsx.snap | 9 + 3 files changed, 103 insertions(+), 94 deletions(-) create mode 100644 src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap diff --git a/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap b/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap index cb01d2c1b..770f604f0 100644 --- a/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap +++ b/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap @@ -34,11 +34,11 @@ exports[`accepts correct password 1`] = ` value="" />
@@ -180,11 +180,11 @@ exports[`accepts valid email 1`] = ` value="" />
@@ -290,11 +290,11 @@ exports[`checks for invalid email 1`] = ` value="invalid-email" />
@@ -321,11 +321,11 @@ exports[`checks for invalid email 1`] = ` value="" />
@@ -546,11 +546,11 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
@@ -577,11 +577,11 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
diff --git a/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap b/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap index 6293da7d8..b382a8713 100644 --- a/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap +++ b/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap @@ -34,11 +34,11 @@ exports[`accepts correct password 1`] = ` value="" />
@@ -70,11 +70,11 @@ exports[`accepts correct password 1`] = ` value="" />
@@ -124,11 +124,11 @@ exports[`accepts correct password 1`] = ` value="" />
@@ -215,11 +215,11 @@ exports[`accepts correct password confirmation 1`] = ` value="" />
@@ -251,11 +251,11 @@ exports[`accepts correct password confirmation 1`] = ` value="" />
@@ -287,11 +287,11 @@ exports[`accepts correct password confirmation 1`] = ` value="" />
@@ -318,11 +318,11 @@ exports[`accepts correct password confirmation 1`] = ` value="testtest" />
@@ -432,11 +432,11 @@ exports[`accepts valid email 1`] = ` value="" />
@@ -468,11 +468,11 @@ exports[`accepts valid email 1`] = ` value="" />
@@ -499,11 +499,11 @@ exports[`accepts valid email 1`] = ` value="" />
@@ -590,11 +590,11 @@ exports[`accepts valid username 1`] = ` value="" />
@@ -649,11 +649,11 @@ exports[`accepts valid username 1`] = ` value="" />
@@ -680,11 +680,11 @@ exports[`accepts valid username 1`] = ` value="" />
@@ -771,11 +771,11 @@ exports[`checks for invalid characters in username 1`] = ` value="" />
@@ -807,11 +807,11 @@ exports[`checks for invalid characters in username 1`] = ` value="$%$§$%$§%" />
@@ -843,11 +843,11 @@ exports[`checks for invalid characters in username 1`] = ` value="" />
@@ -874,11 +874,11 @@ exports[`checks for invalid characters in username 1`] = ` value="" />
@@ -965,11 +965,11 @@ exports[`checks for invalid email 1`] = ` value="invalid-email" />
@@ -1001,11 +1001,11 @@ exports[`checks for invalid email 1`] = ` value="" />
@@ -1037,11 +1037,11 @@ exports[`checks for invalid email 1`] = ` value="" />
@@ -1068,11 +1068,11 @@ exports[`checks for invalid email 1`] = ` value="" />
@@ -1159,11 +1159,11 @@ exports[`checks for too long username 1`] = ` value="" />
@@ -1195,11 +1195,11 @@ exports[`checks for too long username 1`] = ` value="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" />
@@ -1231,11 +1231,11 @@ exports[`checks for too long username 1`] = ` value="" />
@@ -1262,11 +1262,11 @@ exports[`checks for too long username 1`] = ` value="" />
@@ -1353,11 +1353,11 @@ exports[`checks for too short password 1`] = ` value="" />
@@ -1389,11 +1389,11 @@ exports[`checks for too short password 1`] = ` value="" />
@@ -1425,11 +1425,11 @@ exports[`checks for too short password 1`] = ` value="pass" />
@@ -1456,11 +1456,11 @@ exports[`checks for too short password 1`] = ` value="" />
@@ -1547,11 +1547,11 @@ exports[`checks for too short username 1`] = ` value="" />
@@ -1583,11 +1583,11 @@ exports[`checks for too short username 1`] = ` value="u" />
@@ -1619,11 +1619,11 @@ exports[`checks for too short username 1`] = ` value="" />
@@ -1650,11 +1650,11 @@ exports[`checks for too short username 1`] = ` value="" />
@@ -1741,11 +1741,11 @@ exports[`checks for wrong password confirmation 1`] = ` value="" />
@@ -1777,11 +1777,11 @@ exports[`checks for wrong password confirmation 1`] = ` value="" />
@@ -1813,11 +1813,11 @@ exports[`checks for wrong password confirmation 1`] = ` value="" />
@@ -1844,11 +1844,11 @@ exports[`checks for wrong password confirmation 1`] = ` value="not-matching" />
@@ -2077,11 +2077,11 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
@@ -2113,11 +2113,11 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
@@ -2149,11 +2149,11 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
@@ -2180,11 +2180,11 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
diff --git a/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap b/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap new file mode 100644 index 000000000..0b0f3046d --- /dev/null +++ b/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+ Hello World +
+`; From 71c5c7c64fb242269f601e5817e7b0da90565d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Mon, 10 Sep 2018 11:13:13 -0300 Subject: [PATCH 05/53] Basic Tab usage --- src/core/client/ui/components/Tabs/Tab.css | 42 ++++++++++++ src/core/client/ui/components/Tabs/Tab.tsx | 43 ++++++++++++ src/core/client/ui/components/Tabs/TabBar.css | 13 ++++ src/core/client/ui/components/Tabs/TabBar.mdx | 52 +++++++++++++++ src/core/client/ui/components/Tabs/TabBar.tsx | 66 +++++++++++++++++++ .../client/ui/components/Tabs/TabContent.tsx | 0 .../client/ui/components/Tabs/TabPane.tsx | 0 src/core/client/ui/components/Tabs/index.ts | 1 + 8 files changed, 217 insertions(+) create mode 100644 src/core/client/ui/components/Tabs/Tab.css create mode 100644 src/core/client/ui/components/Tabs/Tab.tsx create mode 100644 src/core/client/ui/components/Tabs/TabBar.css create mode 100644 src/core/client/ui/components/Tabs/TabBar.mdx create mode 100644 src/core/client/ui/components/Tabs/TabBar.tsx create mode 100644 src/core/client/ui/components/Tabs/TabContent.tsx create mode 100644 src/core/client/ui/components/Tabs/TabPane.tsx create mode 100644 src/core/client/ui/components/Tabs/index.ts diff --git a/src/core/client/ui/components/Tabs/Tab.css b/src/core/client/ui/components/Tabs/Tab.css new file mode 100644 index 000000000..1a25fcc1c --- /dev/null +++ b/src/core/client/ui/components/Tabs/Tab.css @@ -0,0 +1,42 @@ +.root { + box-sizing: border-box; + list-style: none; + padding: var(--spacing-unit); + display: inline-flex; + margin-bottom: -1px; +} + +.root.primary { + background: #f2f2f2; + color: #787d80; + border: 1px solid #979797; + border-left-width: 0; + padding: calc(0.5 * var(--spacing-unit)) calc(var(--spacing-unit) * 2); + + &:first-child { + border-left-width: 1px; + border-top-left-radius: var(--round-corners); + } + + &:last-child { + border-top-right-radius: var(--round-corners); + } + + &.active { + background-color: var(--palette-common-white); + color: var(--palette-common-black); + border-bottom-color: white; + border-top-width: 5px; + border-top-color: #3498db; + } +} + +.secondary { + border-bottom: 1px solid #e0e0e0; + padding: calc(0.5 * var(--spacing-unit)) calc(var(--spacing-unit) * 2); + + &.active { + font-weight: bold; + border-bottom: 3px solid #0277bd; + } +} diff --git a/src/core/client/ui/components/Tabs/Tab.tsx b/src/core/client/ui/components/Tabs/Tab.tsx new file mode 100644 index 000000000..32509b75f --- /dev/null +++ b/src/core/client/ui/components/Tabs/Tab.tsx @@ -0,0 +1,43 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; +import { withStyles } from "talk-ui/hocs"; +import * as styles from "./Tab.css"; + +export interface TabBarProps { + /** + * Convenient prop to override the root styling. + */ + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes: typeof styles; + + tabId: number; + active: boolean; + color: string; + onTabClick?: () => void; +} + +const TabBar: StatelessComponent = props => { + const { className, classes, children, tabId, active, color } = props; + + const rootClassName = cn( + classes.root, + { + [classes.primary]: color === "primary", + [classes.secondary]: color === "secondary", + [classes.active]: active, + }, + className + ); + + return ( +
  • + {children} +
  • + ); +}; + +const enhanced = withStyles(styles)(TabBar); +export default enhanced; diff --git a/src/core/client/ui/components/Tabs/TabBar.css b/src/core/client/ui/components/Tabs/TabBar.css new file mode 100644 index 000000000..8d076ee0a --- /dev/null +++ b/src/core/client/ui/components/Tabs/TabBar.css @@ -0,0 +1,13 @@ +.root { + display: flex; + padding: 0; + margin: 0; +} + +.primary { + border-bottom: 1px solid #787d80; +} + +.secondary { + border-bottom: 1px solid #e0e0e0; +} diff --git a/src/core/client/ui/components/Tabs/TabBar.mdx b/src/core/client/ui/components/Tabs/TabBar.mdx new file mode 100644 index 000000000..5b7e47727 --- /dev/null +++ b/src/core/client/ui/components/Tabs/TabBar.mdx @@ -0,0 +1,52 @@ +--- +name: TabBar +menu: UI Kit +--- + +import { Playground, PropsTable } from 'docz' +import HorizontalGutter from '../HorizontalGutter' + + +import TabBar from './TabBar' +import Tab from './Tab' + + +# TabBar + +## Basic Use + + + + + Tab one + Active Tab + Tab Three + + + + Tab one + Active Tab + Tab Three + + + + Tab one + Active Tab + Tab Three + + + + + +## Secondary Color + + + + + Tab one + Active Tab + Tab Three + + + + diff --git a/src/core/client/ui/components/Tabs/TabBar.tsx b/src/core/client/ui/components/Tabs/TabBar.tsx new file mode 100644 index 000000000..6aef2e6a8 --- /dev/null +++ b/src/core/client/ui/components/Tabs/TabBar.tsx @@ -0,0 +1,66 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; +import { withStyles } from "talk-ui/hocs"; +import * as styles from "./TabBar.css"; + +export interface TabBarProps { + /** + * Convenient prop to override the root styling. + */ + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes: typeof styles; + + color: "primary" | "secondary"; + activeId?: string; + defaultActiveId?: string; + onChange?: (activeId: string) => void; + onTabClick?: () => void; +} + +const TabBar: StatelessComponent = props => { + const { + className, + classes, + children, + onTabClick, + activeId, + color, + defaultActiveId, + } = props; + + const rootClassName = cn( + classes.root, + [ + { + [classes.primary]: color === "primary", + [classes.secondary]: color === "secondary", + }, + ], + className + ); + + const tabs = React.Children.toArray(children).map( + (child: React.ReactElement, index: number) => + React.cloneElement(child, { + tabId: index, + active: + defaultActiveId && !activeId + ? child.props.tabId === defaultActiveId + : child.props.tabId === activeId, + color, + onTabClick, + }) + ); + + return
      {tabs}
    ; +}; + +TabBar.defaultProps = { + color: "primary", +}; + +const enhanced = withStyles(styles)(TabBar); +export default enhanced; diff --git a/src/core/client/ui/components/Tabs/TabContent.tsx b/src/core/client/ui/components/Tabs/TabContent.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/client/ui/components/Tabs/TabPane.tsx b/src/core/client/ui/components/Tabs/TabPane.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/client/ui/components/Tabs/index.ts b/src/core/client/ui/components/Tabs/index.ts new file mode 100644 index 000000000..58ecee89f --- /dev/null +++ b/src/core/client/ui/components/Tabs/index.ts @@ -0,0 +1 @@ +export { default } from "./TabBar"; From 96515eac0cd16e8caf4592cff8c75f7d86d94583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Mon, 10 Sep 2018 11:24:17 -0300 Subject: [PATCH 06/53] Added Color palette --- src/core/client/ui/components/Tabs/Tab.css | 19 ++++++++++++------- src/core/client/ui/components/Tabs/TabBar.css | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/core/client/ui/components/Tabs/Tab.css b/src/core/client/ui/components/Tabs/Tab.css index 1a25fcc1c..9650b8a95 100644 --- a/src/core/client/ui/components/Tabs/Tab.css +++ b/src/core/client/ui/components/Tabs/Tab.css @@ -4,12 +4,18 @@ padding: var(--spacing-unit); display: inline-flex; margin-bottom: -1px; + font-weight: var(--font-weight-regular); + font-family: var(--font-family-serif); + + &:hover { + cursor: pointer; + } } .root.primary { background: #f2f2f2; - color: #787d80; - border: 1px solid #979797; + color: var(--palette-grey-main); + border: 1px solid var(--palette-grey-main); border-left-width: 0; padding: calc(0.5 * var(--spacing-unit)) calc(var(--spacing-unit) * 2); @@ -25,18 +31,17 @@ &.active { background-color: var(--palette-common-white); color: var(--palette-common-black); - border-bottom-color: white; + border-bottom-color: var(--palette-common-white); border-top-width: 5px; - border-top-color: #3498db; + border-top-color: var(--palette-primary-main); } } .secondary { - border-bottom: 1px solid #e0e0e0; padding: calc(0.5 * var(--spacing-unit)) calc(var(--spacing-unit) * 2); &.active { - font-weight: bold; - border-bottom: 3px solid #0277bd; + font-weight: var(--font-weight-medium); + border-bottom: 3px solid var(--palette-primary-main); } } diff --git a/src/core/client/ui/components/Tabs/TabBar.css b/src/core/client/ui/components/Tabs/TabBar.css index 8d076ee0a..cb75fbb27 100644 --- a/src/core/client/ui/components/Tabs/TabBar.css +++ b/src/core/client/ui/components/Tabs/TabBar.css @@ -5,7 +5,7 @@ } .primary { - border-bottom: 1px solid #787d80; + border-bottom: 1px solid var(--palette-grey-main); } .secondary { From cb3a6b053383e3f8e6a376160a27020f564e5dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Mon, 10 Sep 2018 11:35:56 -0300 Subject: [PATCH 07/53] Adding TabContent, TabPane --- src/core/client/ui/components/Tabs/Tab.mdx | 52 +++++++++++++++++++ src/core/client/ui/components/Tabs/Tab.tsx | 22 ++++++-- src/core/client/ui/components/Tabs/TabBar.tsx | 6 ++- .../client/ui/components/Tabs/TabContent.tsx | 8 +++ .../client/ui/components/Tabs/TabPane.css | 0 .../client/ui/components/Tabs/TabPane.tsx | 40 ++++++++++++++ 6 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 src/core/client/ui/components/Tabs/Tab.mdx create mode 100644 src/core/client/ui/components/Tabs/TabPane.css diff --git a/src/core/client/ui/components/Tabs/Tab.mdx b/src/core/client/ui/components/Tabs/Tab.mdx new file mode 100644 index 000000000..add6f01cf --- /dev/null +++ b/src/core/client/ui/components/Tabs/Tab.mdx @@ -0,0 +1,52 @@ +--- +name: Tab +menu: UI Kit +--- + +import { Playground, PropsTable } from 'docz' +import HorizontalGutter from '../HorizontalGutter' + + +import TabBar from './TabBar' +import Tab from './Tab' + + +# Tab + +## Basic Use + + + + + Tab one + Active Tab + Tab Three + + + + Tab one + Active Tab + Tab Three + + + + Tab one + Active Tab + Tab Three + + + + + +## Secondary Color + + + + + Tab one + Active Tab + Tab Three + + + + diff --git a/src/core/client/ui/components/Tabs/Tab.tsx b/src/core/client/ui/components/Tabs/Tab.tsx index 32509b75f..f1f8e245a 100644 --- a/src/core/client/ui/components/Tabs/Tab.tsx +++ b/src/core/client/ui/components/Tabs/Tab.tsx @@ -13,14 +13,22 @@ export interface TabBarProps { */ classes: typeof styles; - tabId: number; + tabId: string; active: boolean; color: string; onTabClick?: () => void; } const TabBar: StatelessComponent = props => { - const { className, classes, children, tabId, active, color } = props; + const { + className, + classes, + children, + tabId, + active, + color, + onTabClick, + } = props; const rootClassName = cn( classes.root, @@ -33,7 +41,15 @@ const TabBar: StatelessComponent = props => { ); return ( -
  • +
  • ); diff --git a/src/core/client/ui/components/Tabs/TabBar.tsx b/src/core/client/ui/components/Tabs/TabBar.tsx index 6aef2e6a8..8bb3d18f1 100644 --- a/src/core/client/ui/components/Tabs/TabBar.tsx +++ b/src/core/client/ui/components/Tabs/TabBar.tsx @@ -55,7 +55,11 @@ const TabBar: StatelessComponent = props => { }) ); - return
      {tabs}
    ; + return ( +
      + {tabs} +
    + ); }; TabBar.defaultProps = { diff --git a/src/core/client/ui/components/Tabs/TabContent.tsx b/src/core/client/ui/components/Tabs/TabContent.tsx index e69de29bb..50653284d 100644 --- a/src/core/client/ui/components/Tabs/TabContent.tsx +++ b/src/core/client/ui/components/Tabs/TabContent.tsx @@ -0,0 +1,8 @@ +import React, { StatelessComponent } from "react"; + +const TabContent: StatelessComponent<{}> = props => { + const { children } = props; + return <>{children}; +}; + +export default TabContent; diff --git a/src/core/client/ui/components/Tabs/TabPane.css b/src/core/client/ui/components/Tabs/TabPane.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/client/ui/components/Tabs/TabPane.tsx b/src/core/client/ui/components/Tabs/TabPane.tsx index e69de29bb..e24560f33 100644 --- a/src/core/client/ui/components/Tabs/TabPane.tsx +++ b/src/core/client/ui/components/Tabs/TabPane.tsx @@ -0,0 +1,40 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; +import { withStyles } from "talk-ui/hocs"; +import * as styles from "./TabPane.css"; + +export interface TabBarProps { + /** + * Convenient prop to override the root styling. + */ + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes: typeof styles; + + tabId: string; + hidden: boolean; +} + +const TabContent: StatelessComponent = props => { + const { className, classes, children, tabId, hidden } = props; + + const rootClassName = cn(classes.root, className); + + return ( + + ); +}; + +const enhanced = withStyles(styles)(TabContent); +export default enhanced; From e9ab3d1d7f93187b6b7a0a52ef93cb9f8988df79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Mon, 10 Sep 2018 12:27:00 -0300 Subject: [PATCH 08/53] Working tab example --- src/core/client/ui/components/Tabs/Tab.mdx | 52 -------------- src/core/client/ui/components/Tabs/Tab.tsx | 68 +++++++++---------- src/core/client/ui/components/Tabs/TabBar.mdx | 1 - src/core/client/ui/components/Tabs/TabBar.tsx | 7 +- .../client/ui/components/Tabs/TabContent.tsx | 22 +++++- src/core/client/ui/components/Tabs/index.ts | 5 +- src/core/client/ui/components/index.ts | 1 + src/docs/tabs.mdx | 25 +++++++ 8 files changed, 88 insertions(+), 93 deletions(-) delete mode 100644 src/core/client/ui/components/Tabs/Tab.mdx create mode 100644 src/docs/tabs.mdx diff --git a/src/core/client/ui/components/Tabs/Tab.mdx b/src/core/client/ui/components/Tabs/Tab.mdx deleted file mode 100644 index add6f01cf..000000000 --- a/src/core/client/ui/components/Tabs/Tab.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: Tab -menu: UI Kit ---- - -import { Playground, PropsTable } from 'docz' -import HorizontalGutter from '../HorizontalGutter' - - -import TabBar from './TabBar' -import Tab from './Tab' - - -# Tab - -## Basic Use - - - - - Tab one - Active Tab - Tab Three - - - - Tab one - Active Tab - Tab Three - - - - Tab one - Active Tab - Tab Three - - - - - -## Secondary Color - - - - - Tab one - Active Tab - Tab Three - - - - diff --git a/src/core/client/ui/components/Tabs/Tab.tsx b/src/core/client/ui/components/Tabs/Tab.tsx index f1f8e245a..70f2a5f5d 100644 --- a/src/core/client/ui/components/Tabs/Tab.tsx +++ b/src/core/client/ui/components/Tabs/Tab.tsx @@ -16,44 +16,44 @@ export interface TabBarProps { tabId: string; active: boolean; color: string; - onTabClick?: () => void; + onTabClick?: (tabId: string) => void; } -const TabBar: StatelessComponent = props => { - const { - className, - classes, - children, - tabId, - active, - color, - onTabClick, - } = props; +class TabBar extends React.Component { + public handleTabClick = () => { + if (this.props.onTabClick) { + this.props.onTabClick(this.props.tabId); + } + }; - const rootClassName = cn( - classes.root, - { - [classes.primary]: color === "primary", - [classes.secondary]: color === "secondary", - [classes.active]: active, - }, - className - ); + public render() { + const { className, classes, children, tabId, active, color } = this.props; - return ( - - ); -}; + const rootClassName = cn( + classes.root, + { + [classes.primary]: color === "primary", + [classes.secondary]: color === "secondary", + [classes.active]: active, + }, + className + ); + + return ( + + ); + } +} const enhanced = withStyles(styles)(TabBar); export default enhanced; diff --git a/src/core/client/ui/components/Tabs/TabBar.mdx b/src/core/client/ui/components/Tabs/TabBar.mdx index 5b7e47727..847efe843 100644 --- a/src/core/client/ui/components/Tabs/TabBar.mdx +++ b/src/core/client/ui/components/Tabs/TabBar.mdx @@ -10,7 +10,6 @@ import HorizontalGutter from '../HorizontalGutter' import TabBar from './TabBar' import Tab from './Tab' - # TabBar ## Basic Use diff --git a/src/core/client/ui/components/Tabs/TabBar.tsx b/src/core/client/ui/components/Tabs/TabBar.tsx index 8bb3d18f1..e3f947d47 100644 --- a/src/core/client/ui/components/Tabs/TabBar.tsx +++ b/src/core/client/ui/components/Tabs/TabBar.tsx @@ -14,10 +14,12 @@ export interface TabBarProps { classes: typeof styles; color: "primary" | "secondary"; + activeId?: string; defaultActiveId?: string; + onChange?: (activeId: string) => void; - onTabClick?: () => void; + onTabClick?: (tabId: string) => void; } const TabBar: StatelessComponent = props => { @@ -44,8 +46,9 @@ const TabBar: StatelessComponent = props => { const tabs = React.Children.toArray(children).map( (child: React.ReactElement, index: number) => + console.log(child) || React.cloneElement(child, { - tabId: index, + index, active: defaultActiveId && !activeId ? child.props.tabId === defaultActiveId diff --git a/src/core/client/ui/components/Tabs/TabContent.tsx b/src/core/client/ui/components/Tabs/TabContent.tsx index 50653284d..27f37930c 100644 --- a/src/core/client/ui/components/Tabs/TabContent.tsx +++ b/src/core/client/ui/components/Tabs/TabContent.tsx @@ -1,8 +1,24 @@ import React, { StatelessComponent } from "react"; -const TabContent: StatelessComponent<{}> = props => { - const { children } = props; - return <>{children}; +export interface TabContentProps { + activeTab?: string; +} + +const TabContent: StatelessComponent = props => { + const { children, activeTab } = props; + return ( + <> + {React.Children.toArray(children) + .filter( + (child: React.ReactElement) => child.props.tabId === activeTab + ) + .map((child: React.ReactElement, i) => + React.cloneElement(child, { + tabId: child.props.tabId !== undefined ? child.props.tabId : i, + }) + )} + + ); }; export default TabContent; diff --git a/src/core/client/ui/components/Tabs/index.ts b/src/core/client/ui/components/Tabs/index.ts index 58ecee89f..f8818e9ea 100644 --- a/src/core/client/ui/components/Tabs/index.ts +++ b/src/core/client/ui/components/Tabs/index.ts @@ -1 +1,4 @@ -export { default } from "./TabBar"; +export { default as TabBar } from "./TabBar"; +export { default as Tab } from "./Tab"; +export { default as TabPane } from "./TabPane"; +export { default as TabContent } from "./TabContent"; diff --git a/src/core/client/ui/components/index.ts b/src/core/client/ui/components/index.ts index f701660dd..28a3f5c36 100644 --- a/src/core/client/ui/components/index.ts +++ b/src/core/client/ui/components/index.ts @@ -20,3 +20,4 @@ export { default as Spinner } from "./Spinner"; export { default as HorizontalGutter } from "./HorizontalGutter"; export { default as Icon } from "./Icon"; export { default as AriaInfo } from "./AriaInfo"; +export { Tab, TabBar, TabContent, TabPane } from "./Tabs"; diff --git a/src/docs/tabs.mdx b/src/docs/tabs.mdx new file mode 100644 index 000000000..63efef0e9 --- /dev/null +++ b/src/docs/tabs.mdx @@ -0,0 +1,25 @@ +--- +name: Tabs +route: '/Tabs' +--- + +# Tabs + +### Examples + +import { Playground, PropsTable } from 'docz' +import { Flex, TabBar, Tab, TabContent, TabPane } from '../core/client/ui/components' +import Container from "react-with-state-props" + +## Simple Form + + ( + props.setActiveId(id)}> + One + Two + Three + + )}/> + From 3b01af66b346f68b7aa40816665daef208f2a877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Mon, 10 Sep 2018 12:36:13 -0300 Subject: [PATCH 09/53] Two working examples, primary and secondary --- src/core/client/ui/components/Tabs/TabBar.tsx | 9 +++--- .../client/ui/components/Tabs/TabContent.tsx | 2 +- .../client/ui/components/Tabs/TabPane.tsx | 23 ++++---------- src/docs/tabs.mdx | 31 +++++++++++++++++-- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/core/client/ui/components/Tabs/TabBar.tsx b/src/core/client/ui/components/Tabs/TabBar.tsx index e3f947d47..faef9f09e 100644 --- a/src/core/client/ui/components/Tabs/TabBar.tsx +++ b/src/core/client/ui/components/Tabs/TabBar.tsx @@ -15,7 +15,7 @@ export interface TabBarProps { color: "primary" | "secondary"; - activeId?: string; + activeTab?: string; defaultActiveId?: string; onChange?: (activeId: string) => void; @@ -28,7 +28,7 @@ const TabBar: StatelessComponent = props => { classes, children, onTabClick, - activeId, + activeTab, color, defaultActiveId, } = props; @@ -46,13 +46,12 @@ const TabBar: StatelessComponent = props => { const tabs = React.Children.toArray(children).map( (child: React.ReactElement, index: number) => - console.log(child) || React.cloneElement(child, { index, active: - defaultActiveId && !activeId + defaultActiveId && !activeTab ? child.props.tabId === defaultActiveId - : child.props.tabId === activeId, + : child.props.tabId === activeTab, color, onTabClick, }) diff --git a/src/core/client/ui/components/Tabs/TabContent.tsx b/src/core/client/ui/components/Tabs/TabContent.tsx index 27f37930c..4ce407db7 100644 --- a/src/core/client/ui/components/Tabs/TabContent.tsx +++ b/src/core/client/ui/components/Tabs/TabContent.tsx @@ -14,7 +14,7 @@ const TabContent: StatelessComponent = props => { ) .map((child: React.ReactElement, i) => React.cloneElement(child, { - tabId: child.props.tabId !== undefined ? child.props.tabId : i, + tabId: child.props.tabId ? child.props.tabId : i, }) )} diff --git a/src/core/client/ui/components/Tabs/TabPane.tsx b/src/core/client/ui/components/Tabs/TabPane.tsx index e24560f33..485eb5e53 100644 --- a/src/core/client/ui/components/Tabs/TabPane.tsx +++ b/src/core/client/ui/components/Tabs/TabPane.tsx @@ -1,7 +1,4 @@ -import cn from "classnames"; import React, { StatelessComponent } from "react"; -import { withStyles } from "talk-ui/hocs"; -import * as styles from "./TabPane.css"; export interface TabBarProps { /** @@ -9,32 +6,24 @@ export interface TabBarProps { */ className?: string; /** - * Override or extend the styles applied to the component. + * Name of the tab */ - classes: typeof styles; - tabId: string; - hidden: boolean; } -const TabContent: StatelessComponent = props => { - const { className, classes, children, tabId, hidden } = props; - - const rootClassName = cn(classes.root, className); - +const TabPane: StatelessComponent = props => { + const { className, children, tabId } = props; return ( ); }; -const enhanced = withStyles(styles)(TabContent); -export default enhanced; +export default TabPane; diff --git a/src/docs/tabs.mdx b/src/docs/tabs.mdx index 63efef0e9..1fc0f50bc 100644 --- a/src/docs/tabs.mdx +++ b/src/docs/tabs.mdx @@ -11,15 +11,42 @@ import { Playground, PropsTable } from 'docz' import { Flex, TabBar, Tab, TabContent, TabPane } from '../core/client/ui/components' import Container from "react-with-state-props" -## Simple Form +## Primary Tabs ( - props.setActiveId(id)}> +
    + props.setActiveId(id)}> One Two Three + + Hola One + Hola Two + Hola Three + +
    + )}/> +
    + +## Secondary Tabs + + ( +
    + props.setActiveId(id)}> + One + Two + Three + + + Hola One + Hola Two + Hola Three + +
    )}/>
    From a8b24187cb2bb2c45ed2e02846f7f5573384b60b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Mon, 10 Sep 2018 12:55:31 -0300 Subject: [PATCH 10/53] change --- src/core/client/ui/components/Tabs/Tab.mdx | 25 +++++++++++++++++++ src/core/client/ui/components/Tabs/TabBar.mdx | 8 +++--- src/core/client/ui/components/Tabs/TabBar.tsx | 8 +++--- .../client/ui/components/Tabs/TabContent.mdx | 23 +++++++++++++++++ .../client/ui/components/Tabs/TabPane.mdx | 23 +++++++++++++++++ 5 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 src/core/client/ui/components/Tabs/Tab.mdx create mode 100644 src/core/client/ui/components/Tabs/TabContent.mdx create mode 100644 src/core/client/ui/components/Tabs/TabPane.mdx diff --git a/src/core/client/ui/components/Tabs/Tab.mdx b/src/core/client/ui/components/Tabs/Tab.mdx new file mode 100644 index 000000000..365259456 --- /dev/null +++ b/src/core/client/ui/components/Tabs/Tab.mdx @@ -0,0 +1,25 @@ +--- +name: TabBar +menu: UI Kit +--- + +import { Playground, PropsTable } from 'docz' +import HorizontalGutter from '../HorizontalGutter' + + +import TabBar from './TabBar' +import Tab from './Tab' + +# Tab +`Tab` component is to be used within the `TabBar` component. This renders a Tab inside the Tab Bar.s + +## Basic Usage + + + + Tab one + Active Tab + Tab Three + + + diff --git a/src/core/client/ui/components/Tabs/TabBar.mdx b/src/core/client/ui/components/Tabs/TabBar.mdx index 847efe843..14fc68a3b 100644 --- a/src/core/client/ui/components/Tabs/TabBar.mdx +++ b/src/core/client/ui/components/Tabs/TabBar.mdx @@ -16,19 +16,19 @@ import Tab from './Tab' - + Tab one Active Tab Tab Three - + Tab one Active Tab Tab Three - + Tab one Active Tab Tab Three @@ -41,7 +41,7 @@ import Tab from './Tab' - + Tab one Active Tab Tab Three diff --git a/src/core/client/ui/components/Tabs/TabBar.tsx b/src/core/client/ui/components/Tabs/TabBar.tsx index faef9f09e..ab9c6212d 100644 --- a/src/core/client/ui/components/Tabs/TabBar.tsx +++ b/src/core/client/ui/components/Tabs/TabBar.tsx @@ -16,7 +16,7 @@ export interface TabBarProps { color: "primary" | "secondary"; activeTab?: string; - defaultActiveId?: string; + defaultActiveTab?: string; onChange?: (activeId: string) => void; onTabClick?: (tabId: string) => void; @@ -30,7 +30,7 @@ const TabBar: StatelessComponent = props => { onTabClick, activeTab, color, - defaultActiveId, + defaultActiveTab, } = props; const rootClassName = cn( @@ -49,8 +49,8 @@ const TabBar: StatelessComponent = props => { React.cloneElement(child, { index, active: - defaultActiveId && !activeTab - ? child.props.tabId === defaultActiveId + defaultActiveTab && !activeTab + ? child.props.tabId === defaultActiveTab : child.props.tabId === activeTab, color, onTabClick, diff --git a/src/core/client/ui/components/Tabs/TabContent.mdx b/src/core/client/ui/components/Tabs/TabContent.mdx new file mode 100644 index 000000000..1646887a5 --- /dev/null +++ b/src/core/client/ui/components/Tabs/TabContent.mdx @@ -0,0 +1,23 @@ +--- +name: TabContent +menu: UI Kit +--- + +import { Playground, PropsTable } from 'docz' +import HorizontalGutter from '../HorizontalGutter' + +import TabPane from './TabPane' +import TabContent from './TabContent' + +# TabContent + +## Basic Use + + + + Hola One + Hola Two + Hola Three + + + diff --git a/src/core/client/ui/components/Tabs/TabPane.mdx b/src/core/client/ui/components/Tabs/TabPane.mdx new file mode 100644 index 000000000..36cb915f0 --- /dev/null +++ b/src/core/client/ui/components/Tabs/TabPane.mdx @@ -0,0 +1,23 @@ +--- +name: TabPane +menu: UI Kit +--- + +import { Playground, PropsTable } from 'docz' +import HorizontalGutter from '../HorizontalGutter' + +import TabPane from './TabPane' +import TabContent from './TabContent' + +# TabPane + +## Basic Use + + + + Hola One + Hola Two + Hola Three + + + From b608e8a52131bad1bc3a8fe1aedb7c907798d600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Mon, 10 Sep 2018 13:22:57 -0300 Subject: [PATCH 11/53] Adding descriptions --- src/core/client/ui/components/Tabs/Tab.mdx | 2 +- src/core/client/ui/components/Tabs/Tab.tsx | 15 +++++++++++++-- src/core/client/ui/components/Tabs/TabBar.tsx | 16 ++++++++++++---- .../client/ui/components/Tabs/TabContent.tsx | 3 +++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/core/client/ui/components/Tabs/Tab.mdx b/src/core/client/ui/components/Tabs/Tab.mdx index 365259456..f81399cfc 100644 --- a/src/core/client/ui/components/Tabs/Tab.mdx +++ b/src/core/client/ui/components/Tabs/Tab.mdx @@ -1,5 +1,5 @@ --- -name: TabBar +name: Tab menu: UI Kit --- diff --git a/src/core/client/ui/components/Tabs/Tab.tsx b/src/core/client/ui/components/Tabs/Tab.tsx index 70f2a5f5d..87b4ba236 100644 --- a/src/core/client/ui/components/Tabs/Tab.tsx +++ b/src/core/client/ui/components/Tabs/Tab.tsx @@ -12,10 +12,21 @@ export interface TabBarProps { * Override or extend the styles applied to the component. */ classes: typeof styles; - + /** + * The id/name of the tab + */ tabId: string; + /** + * Active status + */ active: boolean; - color: string; + /** + * Color style variant + */ + color: "primary" | "secondary"; + /** + * Action taken on tab click + */ onTabClick?: (tabId: string) => void; } diff --git a/src/core/client/ui/components/Tabs/TabBar.tsx b/src/core/client/ui/components/Tabs/TabBar.tsx index ab9c6212d..46427f3cd 100644 --- a/src/core/client/ui/components/Tabs/TabBar.tsx +++ b/src/core/client/ui/components/Tabs/TabBar.tsx @@ -12,13 +12,21 @@ export interface TabBarProps { * Override or extend the styles applied to the component. */ classes: typeof styles; - + /** + * Color style variant + */ color: "primary" | "secondary"; - + /** + * Active tab id/name + */ activeTab?: string; + /** + * Default active tab id/name + */ defaultActiveTab?: string; - - onChange?: (activeId: string) => void; + /** + * Action taken on tab click + */ onTabClick?: (tabId: string) => void; } diff --git a/src/core/client/ui/components/Tabs/TabContent.tsx b/src/core/client/ui/components/Tabs/TabContent.tsx index 4ce407db7..da68f05a9 100644 --- a/src/core/client/ui/components/Tabs/TabContent.tsx +++ b/src/core/client/ui/components/Tabs/TabContent.tsx @@ -1,6 +1,9 @@ import React, { StatelessComponent } from "react"; export interface TabContentProps { + /** + * Active tab id/name + */ activeTab?: string; } From a36d944e4ad52b797fce415848ff5426780db084 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 11 Sep 2018 00:15:27 +0200 Subject: [PATCH 12/53] Implement edit --- .../framework/lib/bootstrap/createContext.tsx | 11 +- .../stream/components/Comment/Comment.css | 3 - .../stream/components/Comment/Comment.tsx | 23 ++- .../{TopBar.spec.tsx => TopBarLeft.spec.tsx} | 10 +- .../Comment/{TopBar.tsx => TopBarLeft.tsx} | 6 +- .../client/stream/components/Comment/index.ts | 3 + .../stream/components/EditCommentForm.tsx | 156 ++++++++++++++++++ .../stream/containers/CommentContainer.tsx | 76 ++++++++- .../containers/EditCommentFormContainer.tsx | 139 ++++++++++++++++ .../stream/mutations/CreateCommentMutation.ts | 3 + .../stream/mutations/EditCommentMutation.ts | 56 +++++++ src/core/client/stream/mutations/index.ts | 5 + .../components/RelativeTime/RelativeTime.css | 2 - src/core/common/utils/index.ts | 1 + src/core/common/utils/isBeforeDate.spec.ts | 0 src/core/common/utils/isBeforeDate.ts | 3 + src/locales/de/framework.ftl | 2 +- src/locales/en-US/framework.ftl | 13 +- src/locales/en-US/stream.ftl | 13 +- src/locales/es/framework.ftl | 4 +- src/types/react-timeago.d.ts | 1 + 21 files changed, 494 insertions(+), 36 deletions(-) rename src/core/client/stream/components/Comment/{TopBar.spec.tsx => TopBarLeft.spec.tsx} (81%) rename src/core/client/stream/components/Comment/{TopBar.tsx => TopBarLeft.tsx} (81%) create mode 100644 src/core/client/stream/components/EditCommentForm.tsx create mode 100644 src/core/client/stream/containers/EditCommentFormContainer.tsx create mode 100644 src/core/client/stream/mutations/EditCommentMutation.ts create mode 100644 src/core/common/utils/isBeforeDate.spec.ts create mode 100644 src/core/common/utils/isBeforeDate.ts diff --git a/src/core/client/framework/lib/bootstrap/createContext.tsx b/src/core/client/framework/lib/bootstrap/createContext.tsx index 839cee453..d11b3b83c 100644 --- a/src/core/client/framework/lib/bootstrap/createContext.tsx +++ b/src/core/client/framework/lib/bootstrap/createContext.tsx @@ -47,7 +47,16 @@ interface CreateContextArguments { */ export const timeagoFormatter: Formatter = (value, unit, suffix) => { // We use 'in' instead of 'from now' for language consistency - const ourSuffix = suffix === "from now" ? "in" : suffix; + const ourSuffix = suffix === "from now" ? "noSuffix" : suffix; + + if (unit === "second" && suffix === "ago") { + return ( + + Just now + + ); + } + return ( | Array>; footer?: ReactElement | Array>; } const Comment: StatelessComponent = props => { return (
    - - {props.author && - props.author.username && {props.author.username}} - {props.createdAt} - + + + {props.author && + props.author.username && ( + {props.author.username} + )} + {props.createdAt} + +
    {props.topBarRight}
    +
    {props.body || ""} {props.footer} diff --git a/src/core/client/stream/components/Comment/TopBar.spec.tsx b/src/core/client/stream/components/Comment/TopBarLeft.spec.tsx similarity index 81% rename from src/core/client/stream/components/Comment/TopBar.spec.tsx rename to src/core/client/stream/components/Comment/TopBarLeft.spec.tsx index 5ee691f70..8769bb3dd 100644 --- a/src/core/client/stream/components/Comment/TopBar.spec.tsx +++ b/src/core/client/stream/components/Comment/TopBarLeft.spec.tsx @@ -4,10 +4,10 @@ import TestRenderer from "react-test-renderer"; import { PropTypesOf } from "talk-framework/types"; import { UIContext, UIContextProps } from "talk-ui/components"; -import TopBar from "./TopBar"; +import TopBarLeft from "./TopBarLeft"; it("renders correctly on small screens", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { children:
    Hello World
    , }; @@ -19,14 +19,14 @@ it("renders correctly on small screens", () => { const testRenderer = TestRenderer.create( - + ); expect(testRenderer.toJSON()).toMatchSnapshot(); }); it("renders correctly on big screens", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { children:
    Hello World
    , }; @@ -38,7 +38,7 @@ it("renders correctly on big screens", () => { const testRenderer = TestRenderer.create( - + ); expect(testRenderer.toJSON()).toMatchSnapshot(); diff --git a/src/core/client/stream/components/Comment/TopBar.tsx b/src/core/client/stream/components/Comment/TopBarLeft.tsx similarity index 81% rename from src/core/client/stream/components/Comment/TopBar.tsx rename to src/core/client/stream/components/Comment/TopBarLeft.tsx index eed8aea32..5f3862020 100644 --- a/src/core/client/stream/components/Comment/TopBar.tsx +++ b/src/core/client/stream/components/Comment/TopBarLeft.tsx @@ -4,12 +4,12 @@ import { StatelessComponent } from "react"; import { Flex, MatchMedia } from "talk-ui/components"; -export interface TopBarProps { +export interface TopBarLeftProps { className?: string; children: React.ReactNode; } -const TopBar: StatelessComponent = props => { +const TopBarLeft: StatelessComponent = props => { const rootClassName = cn(props.className); return ( @@ -27,4 +27,4 @@ const TopBar: StatelessComponent = props => { ); }; -export default TopBar; +export default TopBarLeft; diff --git a/src/core/client/stream/components/Comment/index.ts b/src/core/client/stream/components/Comment/index.ts index abb1d79ae..56c2da651 100644 --- a/src/core/client/stream/components/Comment/index.ts +++ b/src/core/client/stream/components/Comment/index.ts @@ -1 +1,4 @@ export { default, default as IndentedComment } from "./IndentedComment"; +export { default as TopBarLeft } from "./TopBarLeft"; +export { default as Username } from "./Username"; +export { default as Timestamp } from "./Timestamp"; diff --git a/src/core/client/stream/components/EditCommentForm.tsx b/src/core/client/stream/components/EditCommentForm.tsx new file mode 100644 index 000000000..84c753b1b --- /dev/null +++ b/src/core/client/stream/components/EditCommentForm.tsx @@ -0,0 +1,156 @@ +import { CoralRTE } from "@coralproject/rte"; +import { Localized } from "fluent-react/compat"; +import React, { + EventHandler, + MouseEvent, + Ref, + StatelessComponent, +} from "react"; +import { Field, Form } from "react-final-form"; + +import { OnSubmit } from "talk-framework/lib/form"; +import { required } from "talk-framework/lib/validation"; +import { + AriaInfo, + Button, + Flex, + HorizontalGutter, + RelativeTime, + Typography, + ValidationMessage, +} from "talk-ui/components"; + +import { Timestamp, TopBarLeft, Username } from "./Comment"; +import RTE from "./RTE"; + +interface FormProps { + body: string; +} + +export interface EditCommentFormProps { + id: string; + className?: string; + author: { + username: string | null; + } | null; + createdAt: string; + editableUntil: string; + onSubmit: OnSubmit; + onCancel?: EventHandler>; + onClose?: EventHandler>; + initialValues?: FormProps; + rteRef?: Ref; + expired?: boolean; +} + +const EditCommentForm: StatelessComponent = props => { + const inputID = `comments-editCommentForm-rte-${props.id}`; + return ( +
    + {({ handleSubmit, submitting, hasValidationErrors, pristine }) => ( + + +
    + + {props.author && + props.author.username && ( + {props.author.username} + )} + {props.createdAt} + +
    + + {({ input, meta }) => ( +
    + + + Edit comment + + + + input.onChange(html)} + value={input.value} + placeholder="Edit comment" + forwardRef={props.rteRef} + disabled={submitting || props.expired} + /> + + {meta.touched && + (meta.error || meta.submitError) && ( + + {meta.error || meta.submitError} + + )} +
    + )} +
    + {props.expired ? ( + + + Edit time has expired. You can no longer edit this comment. + Why not post another one? + + + ) : ( + + } + > + {"Edit: remaining"} + + + )} + + {props.expired ? ( + + + + ) : ( + <> + + + + + + + + )} + +
    +
    + )} + + ); +}; + +export default EditCommentForm; diff --git a/src/core/client/stream/containers/CommentContainer.tsx b/src/core/client/stream/containers/CommentContainer.tsx index 21b4b767e..c3368b9d0 100644 --- a/src/core/client/stream/containers/CommentContainer.tsx +++ b/src/core/client/stream/containers/CommentContainer.tsx @@ -1,6 +1,8 @@ +import { Localized } from "fluent-react/compat"; import React, { Component } from "react"; import { graphql } from "react-relay"; +import { isBeforeDate } from "talk-common/utils"; import withFragmentContainer from "talk-framework/lib/relay/withFragmentContainer"; import { PropTypesOf } from "talk-framework/types"; import { CommentContainer_asset as AssetData } from "talk-stream/__generated__/CommentContainer_asset.graphql"; @@ -11,10 +13,12 @@ import { withShowAuthPopupMutation, } from "talk-stream/mutations"; +import { Button } from "talk-ui/components"; import Comment from "../components/Comment"; import ReplyButton from "../components/Comment/ReplyButton"; -import ReplyCommentFormContainer from ".//ReplyCommentFormContainer"; +import EditCommentFormContainer from "./EditCommentFormContainer"; import PermalinkButtonContainer from "./PermalinkButtonContainer"; +import ReplyCommentFormContainer from "./ReplyCommentFormContainer"; interface InnerProps { me: MeData | null; @@ -26,13 +30,28 @@ interface InnerProps { interface State { showReplyDialog: boolean; + showEditDialog: boolean; + editable: boolean; } export class CommentContainer extends Component { + private uneditableTimer: any; + public state = { showReplyDialog: false, + showEditDialog: false, + editable: isBeforeDate(this.props.comment.editing.editableUntil), }; + constructor(props: InnerProps) { + super(props); + this.uneditableTimer = this.updateWhenNotEditable(); + } + + public componentWillUnmount() { + clearTimeout(this.uneditableTimer); + } + private openReplyDialog = () => { if (this.props.me) { this.setState(state => ({ @@ -43,20 +62,68 @@ export class CommentContainer extends Component { } }; + private openEditDialog = () => { + if (this.props.me) { + this.setState(state => ({ + showEditDialog: true, + })); + } else { + this.props.showAuthPopup({ view: "SIGN_IN" }); + } + }; + + private closeEditDialog = () => { + this.setState(state => ({ + showEditDialog: false, + })); + }; + private closeReplyDialog = () => { this.setState(state => ({ showReplyDialog: false, })); }; + private updateWhenNotEditable() { + const ms = + new Date(this.props.comment.editing.editableUntil).getTime() - Date.now(); + if (ms > 0) { + return setTimeout(() => this.setState({ editable: false }), ms); + } + return; + } + public render() { const { comment, asset, ...rest } = this.props; - const { showReplyDialog } = this.state; + const { showReplyDialog, showEditDialog, editable } = this.state; + if (showEditDialog) { + return ( + + ); + } return ( <> + + + )) || + undefined + } footer={ <> void; + asset: AssetData; + autofocus: boolean; +} + +interface State { + initialValues?: EditCommentFormProps["initialValues"]; + initialized: boolean; + expired: boolean; +} + +export class EditCommentFormContainer extends Component { + private expiredTimer: any; + + public state: State = { + initialized: false, + expired: !isBeforeDate(this.props.comment.editing.editableUntil), + }; + + constructor(props: InnerProps) { + super(props); + this.expiredTimer = this.updateWhenExpired(); + } + + public componentWillUnmount() { + clearTimeout(this.expiredTimer); + } + + private updateWhenExpired() { + const ms = + new Date(this.props.comment.editing.editableUntil).getTime() - Date.now(); + if (ms > 0) { + return setTimeout(() => this.setState({ expired: true }), ms); + } + return; + } + + private handleRTERef = (rte: CoralRTE | null) => { + if (rte && this.props.autofocus) { + rte.focus(); + } + }; + + private handleOnCancelOrClose = () => { + if (this.props.onClose) { + this.props.onClose(); + } + }; + + private handleOnSubmit: EditCommentFormProps["onSubmit"] = async ( + input, + form + ) => { + try { + await this.props.editComment({ + assetID: this.props.asset.id, + commentID: this.props.comment.id, + body: input.body, + }); + if (this.props.onClose) { + this.props.onClose(); + } + } catch (error) { + if (error instanceof BadUserInputError) { + return error.invalidArgsLocalized; + } + // tslint:disable-next-line:no-console + console.error(error); + } + return undefined; + }; + + public render() { + return ( + + ); + } +} +const enhanced = withContext(({ sessionStorage, browserInfo }) => ({ + // Disable autofocus on ios and enable for the rest. + autofocus: !browserInfo.ios, +}))( + withEditCommentMutation( + withFragmentContainer({ + asset: graphql` + fragment EditCommentFormContainer_asset on Asset { + id + } + `, + comment: graphql` + fragment EditCommentFormContainer_comment on Comment { + id + body + createdAt + author { + username + } + editing { + editableUntil + } + } + `, + })(EditCommentFormContainer) + ) +); +export type PostCommentFormContainerProps = PropTypesOf; +export default enhanced; diff --git a/src/core/client/stream/mutations/CreateCommentMutation.ts b/src/core/client/stream/mutations/CreateCommentMutation.ts index a293a58e7..958ad9acc 100644 --- a/src/core/client/stream/mutations/CreateCommentMutation.ts +++ b/src/core/client/stream/mutations/CreateCommentMutation.ts @@ -92,6 +92,9 @@ function commit( username: me.username, }, body: input.body, + editing: { + editableUntil: new Date(Date.now() + 10000), + }, }, }, clientMutationId: (clientMutationId++).toString(), diff --git a/src/core/client/stream/mutations/EditCommentMutation.ts b/src/core/client/stream/mutations/EditCommentMutation.ts new file mode 100644 index 000000000..a3d456ed2 --- /dev/null +++ b/src/core/client/stream/mutations/EditCommentMutation.ts @@ -0,0 +1,56 @@ +import { graphql } from "react-relay"; +import { Environment } from "relay-runtime"; + +import { + commitMutationPromiseNormalized, + createMutationContainer, +} from "talk-framework/lib/relay"; +import { Omit } from "talk-framework/types"; + +import { EditCommentMutation as MutationTypes } from "talk-stream/__generated__/EditCommentMutation.graphql"; + +export type EditCommentInput = Omit< + MutationTypes["variables"]["input"], + "clientMutationId" +>; + +const mutation = graphql` + mutation EditCommentMutation($input: EditCommentInput!) { + editComment(input: $input) { + comment { + body + } + clientMutationId + } + } +`; + +let clientMutationId = 0; + +function commit(environment: Environment, input: EditCommentInput) { + return commitMutationPromiseNormalized(environment, { + mutation, + variables: { + input: { + ...input, + clientMutationId: clientMutationId.toString(), + }, + }, + optimisticResponse: { + editComment: { + id: input.commentID, + body: input.body, + clientMutationId: (clientMutationId++).toString(), + }, + } as any, // TODO: (cvle) generated types should contain one for the optimistic response. + }); +} + +export const withEditCommentMutation = createMutationContainer( + "editComment", + commit +); + +export type EditCommentMutation = ( + input: EditCommentInput +) => Promise; diff --git a/src/core/client/stream/mutations/index.ts b/src/core/client/stream/mutations/index.ts index 324fddbdc..6c920dfea 100644 --- a/src/core/client/stream/mutations/index.ts +++ b/src/core/client/stream/mutations/index.ts @@ -3,6 +3,11 @@ export { CreateCommentMutation, CreateCommentInput, } from "./CreateCommentMutation"; +export { + withEditCommentMutation, + EditCommentMutation, + EditCommentInput, +} from "./EditCommentMutation"; export { withSetNetworkStatusMutation, SetNetworkStatusMutation, diff --git a/src/core/client/ui/components/RelativeTime/RelativeTime.css b/src/core/client/ui/components/RelativeTime/RelativeTime.css index e0450679f..c3a2af639 100644 --- a/src/core/client/ui/components/RelativeTime/RelativeTime.css +++ b/src/core/client/ui/components/RelativeTime/RelativeTime.css @@ -1,4 +1,2 @@ .root { - composes: bodyCopy from "talk-ui/shared/typography.css"; - background-color: transparent; } diff --git a/src/core/common/utils/index.ts b/src/core/common/utils/index.ts index 28435047e..4d6df9fc5 100644 --- a/src/core/common/utils/index.ts +++ b/src/core/common/utils/index.ts @@ -2,3 +2,4 @@ export { default as timeout } from "./timeout"; export { default as animationFrame } from "./animationFrame"; export { default as pascalCase } from "./pascalCase"; export { default as oncePerFrame } from "./oncePerFrame"; +export { default as isBeforeDate } from "./isBeforeDate"; diff --git a/src/core/common/utils/isBeforeDate.spec.ts b/src/core/common/utils/isBeforeDate.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/common/utils/isBeforeDate.ts b/src/core/common/utils/isBeforeDate.ts new file mode 100644 index 000000000..24d2a33e9 --- /dev/null +++ b/src/core/common/utils/isBeforeDate.ts @@ -0,0 +1,3 @@ +export default function isBeforeDate(date: string | number | Date) { + return new Date() < new Date(date); +} diff --git a/src/locales/de/framework.ftl b/src/locales/de/framework.ftl index 55dca1c8c..7ea668be3 100644 --- a/src/locales/de/framework.ftl +++ b/src/locales/de/framework.ftl @@ -6,10 +6,10 @@ framework-validation-required = Dies ist ein Pflichtpfeld. +framework-timeago-just-now = Gerade eben framework-timeago = { $suffix -> [ago] vor - [in] in } { $value } { $unit -> diff --git a/src/locales/en-US/framework.ftl b/src/locales/en-US/framework.ftl index f03b2a50a..89eee969b 100644 --- a/src/locales/en-US/framework.ftl +++ b/src/locales/en-US/framework.ftl @@ -13,6 +13,8 @@ framework-validation-invalidEmail = Please enter a valid email address. framework-validation-passwordTooShort = Password must contain at least {$minLength} characters. framework-validation-passwordsDoNotMatch = Passwords do not match. Try again. +framework-timeago-just-now = Just now + framework-timeago-time = { $value } { $unit -> @@ -48,12 +50,7 @@ framework-timeago-time = } framework-timeago = - { $value -> - [0] now - *[other] - { $suffix -> - [ago] {framework-timeago-time} ago - [in] in {framework-timeago-time} - *[other] unknown suffix - } + { $suffix -> + [ago] {framework-timeago-time} ago + [noSuffix] {framework-timeago-time} } diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index 29aac39c9..683ea792e 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -53,4 +53,15 @@ comments-replyCommentForm-submit = Submit comments-replyCommentForm-cancel = Cancel comments-replyCommentForm-rteLabel = Write a reply comments-replyCommentForm-rte = - .placeholder = { comments-postCommentForm-rteLabel } + .placeholder = { comments-replyCommentForm-rteLabel } + +comments-commentContainer-editButton = Edit + +comments-editCommentForm-saveChanges = Save Changes +comments-editCommentForm-cancel = Cancel +comments-editCommentForm-close = Close +comments-editCommentForm-rteLabel = Edit comment +comments-editCommentForm-rte = + .placeholder = { comments-editCommentForm-rteLabel } +comments-editCommentForm-editRemainingTime = Edit: remaining +comments-editCommentForm-editTimeExpired = Edit time has expired. You can no longer edit this comment. Why not post another one? diff --git a/src/locales/es/framework.ftl b/src/locales/es/framework.ftl index a383c75bb..86b5e6cb4 100644 --- a/src/locales/es/framework.ftl +++ b/src/locales/es/framework.ftl @@ -2,14 +2,12 @@ ### All keys must start with `framework` because this file is shared ### among different targets. +framework-timeago-just-now = Hace poco framework-timeago = { $value -> - [0] ahora *[other] { $suffix -> [ago] hace - [in] en - *[other] unknown suffix } { $value } { $unit -> diff --git a/src/types/react-timeago.d.ts b/src/types/react-timeago.d.ts index 431bbf88e..02161745c 100644 --- a/src/types/react-timeago.d.ts +++ b/src/types/react-timeago.d.ts @@ -36,6 +36,7 @@ declare module "react-timeago" { live?: boolean; className: string; formatter?: Formatter; + minPeriod?: number; } const TimeAgo: React.ComponentType; From b5db80b1e1cbee6989682ee0cb305fd0bc9cb470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Tue, 11 Sep 2018 09:47:59 -0300 Subject: [PATCH 13/53] Updated colors --- src/core/client/ui/components/Message/Message.css | 4 ++-- src/core/client/ui/components/Message/Message.tsx | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/client/ui/components/Message/Message.css b/src/core/client/ui/components/Message/Message.css index 267de3201..57dbe34bf 100644 --- a/src/core/client/ui/components/Message/Message.css +++ b/src/core/client/ui/components/Message/Message.css @@ -12,13 +12,13 @@ border-left-width: calc(0.8 * var(--spacing-unit)); } -.colorInfo { +.colorGrey { background-color: var(--palette-common-white); border-color: var(--palette-grey-main); color: var(--palette-grey-main); } -.colorValidation { +.colorError { background-color: var(--palette-error-light); border-color: var(--palette-error-darkest); color: var(--palette-common-white); diff --git a/src/core/client/ui/components/Message/Message.tsx b/src/core/client/ui/components/Message/Message.tsx index 9eb678f4f..258174288 100644 --- a/src/core/client/ui/components/Message/Message.tsx +++ b/src/core/client/ui/components/Message/Message.tsx @@ -28,9 +28,9 @@ export interface MessageProps { */ icon?: string; /* - * Name of color, "info" stays by default - common gray one + * Name of color, "grey" stays by default - common gray one */ - color?: "validation" | "info"; + color?: "error" | "grey"; } const Message: StatelessComponent = props => { @@ -47,8 +47,8 @@ const Message: StatelessComponent = props => { const rootClassName = cn( classes.root, { - [classes.colorInfo]: color === "info", - [classes.colorValidation]: color === "validation", + [classes.colorGrey]: color === "grey", + [classes.colorError]: color === "error", [classes.fullWidth]: fullWidth, }, className @@ -67,7 +67,7 @@ const Message: StatelessComponent = props => { }; Message.defaultProps = { - color: "info", + color: "grey", fullWidth: false, }; From a35ee7909837d708e3b7edd6afe7fb55dd272365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Tue, 11 Sep 2018 09:57:55 -0300 Subject: [PATCH 14/53] Adding MessageIcon --- .../client/ui/components/Message/Message.mdx | 3 +- .../client/ui/components/Message/Message.tsx | 19 +--------- .../ui/components/Message/MessageIcon.css | 5 +++ .../ui/components/Message/MessageIcon.tsx | 36 +++++++++++++++++++ 4 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 src/core/client/ui/components/Message/MessageIcon.css create mode 100644 src/core/client/ui/components/Message/MessageIcon.tsx diff --git a/src/core/client/ui/components/Message/Message.mdx b/src/core/client/ui/components/Message/Message.mdx index 9a876d76c..828d18036 100644 --- a/src/core/client/ui/components/Message/Message.mdx +++ b/src/core/client/ui/components/Message/Message.mdx @@ -5,6 +5,7 @@ menu: UI Kit import { Playground } from 'docz' import Message from './Message' +import MessageIcon from './MessageIcon' import HorizontalGutter from '../HorizontalGutter' # Message @@ -20,7 +21,7 @@ import HorizontalGutter from '../HorizontalGutter' ## Usage with icon - Edit: 1 min 23 secs Remaining + alarmEdit: 1 min 23 secs Remaining diff --git a/src/core/client/ui/components/Message/Message.tsx b/src/core/client/ui/components/Message/Message.tsx index 258174288..c3f4ea2e8 100644 --- a/src/core/client/ui/components/Message/Message.tsx +++ b/src/core/client/ui/components/Message/Message.tsx @@ -24,25 +24,13 @@ export interface MessageProps { */ fullWidth?: boolean; /* - * Name of the icon, render if provided - */ - icon?: string; - /* * Name of color, "grey" stays by default - common gray one */ color?: "error" | "grey"; } const Message: StatelessComponent = props => { - const { - className, - classes, - fullWidth, - children, - icon, - color, - ...rest - } = props; + const { className, classes, fullWidth, children, color, ...rest } = props; const rootClassName = cn( classes.root, @@ -56,11 +44,6 @@ const Message: StatelessComponent = props => { return (
    - {icon && ( - - {icon} - - )} {children}
    ); diff --git a/src/core/client/ui/components/Message/MessageIcon.css b/src/core/client/ui/components/Message/MessageIcon.css new file mode 100644 index 000000000..df730b2de --- /dev/null +++ b/src/core/client/ui/components/Message/MessageIcon.css @@ -0,0 +1,5 @@ +.root { + &:first-child { + margin-right: calc(0.5 * var(--spacing-unit)); + } +} diff --git a/src/core/client/ui/components/Message/MessageIcon.tsx b/src/core/client/ui/components/Message/MessageIcon.tsx new file mode 100644 index 000000000..cd4d0ad13 --- /dev/null +++ b/src/core/client/ui/components/Message/MessageIcon.tsx @@ -0,0 +1,36 @@ +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 * as styles from "./MessageIcon.css"; + +interface InnerProps extends HTMLAttributes { + /** + * 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; +} + +export const MessageIcon: StatelessComponent = props => { + const { classes, className, forwardRef, ...rest } = props; + const rootClassName = cn(classes.root, className); + return ; +}; + +MessageIcon.defaultProps = { + size: "sm", +}; + +const enhanced = withForwardRef(withStyles(styles)(MessageIcon)); +export default enhanced; From 91d421ad46a215be976a0a0fd24924e56873721e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Tue, 11 Sep 2018 10:01:14 -0300 Subject: [PATCH 15/53] Updated tests --- .../ui/components/Message/Message.spec.tsx | 14 ++++++++++++++ .../Message/__snapshots__/Message.spec.tsx.snap | 16 +++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/core/client/ui/components/Message/Message.spec.tsx b/src/core/client/ui/components/Message/Message.spec.tsx index d1335f8d9..6f7bfc8cc 100644 --- a/src/core/client/ui/components/Message/Message.spec.tsx +++ b/src/core/client/ui/components/Message/Message.spec.tsx @@ -4,6 +4,7 @@ import TestRenderer from "react-test-renderer"; import { PropTypesOf } from "talk-ui/types"; import Message from "./Message"; +import MessageIcon from "./MessageIcon"; it("renders correctly", () => { const props: PropTypesOf = { @@ -13,3 +14,16 @@ it("renders correctly", () => { const renderer = TestRenderer.create(); expect(renderer.toJSON()).toMatchSnapshot(); }); + +it("renders icon", () => { + const props: PropTypesOf = { + className: "custom", + }; + + const renderer = TestRenderer.create( + + alertAlert Message + + ); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap b/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap index 0b0f3046d..ae81503dc 100644 --- a/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap +++ b/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap @@ -2,8 +2,22 @@ exports[`renders correctly 1`] = `
    Hello World
    `; + +exports[`renders icon 1`] = ` +
    + + Alert Message +
    +`; From f7fa1dae23e12d20f902a1ff23c2eed16f14bb7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Tue, 11 Sep 2018 10:17:46 -0300 Subject: [PATCH 16/53] Updated tests --- .../test/__snapshots__/signIn.spec.tsx.snap | 54 +-- .../test/__snapshots__/signUp.spec.tsx.snap | 369 ++++-------------- .../ValidationMessage.spec.tsx.snap | 9 +- 3 files changed, 96 insertions(+), 336 deletions(-) diff --git a/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap b/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap index 770f604f0..b4db66fc7 100644 --- a/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap +++ b/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap @@ -34,14 +34,9 @@ exports[`accepts correct password 1`] = ` value="" />
    - This field is required. @@ -180,14 +175,9 @@ exports[`accepts valid email 1`] = ` value="" />
    - This field is required. @@ -290,14 +280,9 @@ exports[`checks for invalid email 1`] = ` value="invalid-email" />
    - Please enter a valid email address. @@ -321,14 +306,9 @@ exports[`checks for invalid email 1`] = ` value="" />
    - This field is required. @@ -546,14 +526,9 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    - This field is required. @@ -577,14 +552,9 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    - This field is required. diff --git a/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap b/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap index b382a8713..e18788d70 100644 --- a/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap +++ b/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap @@ -34,14 +34,9 @@ exports[`accepts correct password 1`] = ` value="" />
    - This field is required. @@ -70,14 +65,9 @@ exports[`accepts correct password 1`] = ` value="" />
    - This field is required. @@ -124,14 +114,9 @@ exports[`accepts correct password 1`] = ` value="" />
    - This field is required. @@ -215,14 +200,9 @@ exports[`accepts correct password confirmation 1`] = ` value="" />
    - This field is required. @@ -251,14 +231,9 @@ exports[`accepts correct password confirmation 1`] = ` value="" />
    - This field is required. @@ -287,14 +262,9 @@ exports[`accepts correct password confirmation 1`] = ` value="" />
    - This field is required. @@ -318,14 +288,9 @@ exports[`accepts correct password confirmation 1`] = ` value="testtest" />
    - Passwords do not match. Try again. @@ -432,14 +397,9 @@ exports[`accepts valid email 1`] = ` value="" />
    - This field is required. @@ -468,14 +428,9 @@ exports[`accepts valid email 1`] = ` value="" />
    - This field is required. @@ -499,14 +454,9 @@ exports[`accepts valid email 1`] = ` value="" />
    - This field is required. @@ -590,14 +540,9 @@ exports[`accepts valid username 1`] = ` value="" />
    - This field is required. @@ -649,14 +594,9 @@ exports[`accepts valid username 1`] = ` value="" />
    - This field is required. @@ -680,14 +620,9 @@ exports[`accepts valid username 1`] = ` value="" />
    - This field is required. @@ -771,14 +706,9 @@ exports[`checks for invalid characters in username 1`] = ` value="" />
    - This field is required. @@ -807,14 +737,9 @@ exports[`checks for invalid characters in username 1`] = ` value="$%$§$%$§%" />
    - Invalid characters. Try again. @@ -843,14 +768,9 @@ exports[`checks for invalid characters in username 1`] = ` value="" />
    - This field is required. @@ -874,14 +794,9 @@ exports[`checks for invalid characters in username 1`] = ` value="" />
    - This field is required. @@ -965,14 +880,9 @@ exports[`checks for invalid email 1`] = ` value="invalid-email" />
    - Please enter a valid email address. @@ -1001,14 +911,9 @@ exports[`checks for invalid email 1`] = ` value="" />
    - This field is required. @@ -1037,14 +942,9 @@ exports[`checks for invalid email 1`] = ` value="" />
    - This field is required. @@ -1068,14 +968,9 @@ exports[`checks for invalid email 1`] = ` value="" />
    - This field is required. @@ -1159,14 +1054,9 @@ exports[`checks for too long username 1`] = ` value="" />
    - This field is required. @@ -1195,14 +1085,9 @@ exports[`checks for too long username 1`] = ` value="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" />
    - Usernames cannot be longer than ⁨20⁩ characters. @@ -1231,14 +1116,9 @@ exports[`checks for too long username 1`] = ` value="" />
    - This field is required. @@ -1262,14 +1142,9 @@ exports[`checks for too long username 1`] = ` value="" />
    - This field is required. @@ -1353,14 +1228,9 @@ exports[`checks for too short password 1`] = ` value="" />
    - This field is required. @@ -1389,14 +1259,9 @@ exports[`checks for too short password 1`] = ` value="" />
    - This field is required. @@ -1425,14 +1290,9 @@ exports[`checks for too short password 1`] = ` value="pass" />
    - Password must contain at least ⁨8⁩ characters. @@ -1456,14 +1316,9 @@ exports[`checks for too short password 1`] = ` value="" />
    - This field is required. @@ -1547,14 +1402,9 @@ exports[`checks for too short username 1`] = ` value="" />
    - This field is required. @@ -1583,14 +1433,9 @@ exports[`checks for too short username 1`] = ` value="u" />
    - Username must contain at least ⁨3⁩ characters. @@ -1619,14 +1464,9 @@ exports[`checks for too short username 1`] = ` value="" />
    - This field is required. @@ -1650,14 +1490,9 @@ exports[`checks for too short username 1`] = ` value="" />
    - This field is required. @@ -1741,14 +1576,9 @@ exports[`checks for wrong password confirmation 1`] = ` value="" />
    - This field is required. @@ -1777,14 +1607,9 @@ exports[`checks for wrong password confirmation 1`] = ` value="" />
    - This field is required. @@ -1813,14 +1638,9 @@ exports[`checks for wrong password confirmation 1`] = ` value="" />
    - This field is required. @@ -1844,14 +1664,9 @@ exports[`checks for wrong password confirmation 1`] = ` value="not-matching" />
    - Passwords do not match. Try again. @@ -2077,14 +1892,9 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    - This field is required. @@ -2113,14 +1923,9 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    - This field is required. @@ -2149,14 +1954,9 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    - This field is required. @@ -2180,14 +1980,9 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    - This field is required. diff --git a/src/core/client/ui/components/ValidationMessage/__snapshots__/ValidationMessage.spec.tsx.snap b/src/core/client/ui/components/ValidationMessage/__snapshots__/ValidationMessage.spec.tsx.snap index 7f03e37e0..da5a985c1 100644 --- a/src/core/client/ui/components/ValidationMessage/__snapshots__/ValidationMessage.spec.tsx.snap +++ b/src/core/client/ui/components/ValidationMessage/__snapshots__/ValidationMessage.spec.tsx.snap @@ -2,14 +2,9 @@ exports[`renders correctly 1`] = `
    - Hello World
    `; From 4e63858c66293ebf7ad2213eed750defe09456ee Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 11 Sep 2018 17:25:40 +0200 Subject: [PATCH 17/53] Fix lint and test --- .../components/Comment/Comment.spec.tsx | 1 - .../stream/components/Comment/Comment.tsx | 2 +- .../Comment/IndentedComment.spec.tsx | 1 - .../__snapshots__/Comment.spec.tsx.snap | 20 +- ...spec.tsx.snap => TopBarLeft.spec.tsx.snap} | 0 .../containers/CommentContainer.spec.tsx | 6 + .../stream/containers/CommentContainer.tsx | 8 +- .../CommentContainer.spec.tsx.snap | 6 - .../test/__snapshots__/loadMore.spec.tsx.snap | 140 ++++---- .../__snapshots__/permalinkView.spec.tsx.snap | 56 ++-- ...permalinkViewCommentNotFound.spec.tsx.snap | 28 +- .../__snapshots__/postComment.spec.tsx.snap | 239 ++++++++------ .../__snapshots__/postReply.spec.tsx.snap | 301 +++++++++++------- .../__snapshots__/renderReplies.spec.tsx.snap | 112 ++++--- .../__snapshots__/renderStream.spec.tsx.snap | 56 ++-- .../showAllReplies.spec.tsx.snap | 140 ++++---- src/core/client/stream/test/fixtures.ts | 12 + .../client/stream/test/postComment.spec.tsx | 3 + .../client/stream/test/postReply.spec.tsx | 3 + 19 files changed, 667 insertions(+), 467 deletions(-) rename src/core/client/stream/components/Comment/__snapshots__/{TopBar.spec.tsx.snap => TopBarLeft.spec.tsx.snap} (100%) diff --git a/src/core/client/stream/components/Comment/Comment.spec.tsx b/src/core/client/stream/components/Comment/Comment.spec.tsx index 8b409adeb..cae068012 100644 --- a/src/core/client/stream/components/Comment/Comment.spec.tsx +++ b/src/core/client/stream/components/Comment/Comment.spec.tsx @@ -7,7 +7,6 @@ import Comment from "./Comment"; it("renders username and body", () => { const props: PropTypesOf = { - id: "comment-id", author: { username: "Marvin", }, diff --git a/src/core/client/stream/components/Comment/Comment.tsx b/src/core/client/stream/components/Comment/Comment.tsx index 37c6bb2ad..02dd50bfd 100644 --- a/src/core/client/stream/components/Comment/Comment.tsx +++ b/src/core/client/stream/components/Comment/Comment.tsx @@ -34,7 +34,7 @@ const Comment: StatelessComponent = props => { )} {props.createdAt} -
    {props.topBarRight}
    + {props.topBarRight &&
    {props.topBarRight}
    } {props.body || ""} diff --git a/src/core/client/stream/components/Comment/IndentedComment.spec.tsx b/src/core/client/stream/components/Comment/IndentedComment.spec.tsx index c86a66ef2..be489a804 100644 --- a/src/core/client/stream/components/Comment/IndentedComment.spec.tsx +++ b/src/core/client/stream/components/Comment/IndentedComment.spec.tsx @@ -8,7 +8,6 @@ import IndentedComment from "./IndentedComment"; it("renders correctly", () => { const props: PropTypesOf = { indentLevel: 1, - id: "comment-id", author: { username: "Marvin", }, diff --git a/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap b/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap index 246444f0d..cf9aa58ab 100644 --- a/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap +++ b/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap @@ -5,16 +5,20 @@ exports[`renders username and body 1`] = ` className="Comment-root" role="article" > - - - Marvin - - - 1995-12-17T03:24:00.000Z - - + + + Marvin + + + 1995-12-17T03:24:00.000Z + + + Woof diff --git a/src/core/client/stream/components/Comment/__snapshots__/TopBar.spec.tsx.snap b/src/core/client/stream/components/Comment/__snapshots__/TopBarLeft.spec.tsx.snap similarity index 100% rename from src/core/client/stream/components/Comment/__snapshots__/TopBar.spec.tsx.snap rename to src/core/client/stream/components/Comment/__snapshots__/TopBarLeft.spec.tsx.snap diff --git a/src/core/client/stream/containers/CommentContainer.spec.tsx b/src/core/client/stream/containers/CommentContainer.spec.tsx index d43c241af..91f2acafb 100644 --- a/src/core/client/stream/containers/CommentContainer.spec.tsx +++ b/src/core/client/stream/containers/CommentContainer.spec.tsx @@ -23,6 +23,9 @@ it("renders username and body", () => { }, body: "Woof", createdAt: "1995-12-17T03:24:00.000Z", + editing: { + editableUntil: "1995-12-17T03:24:30.000Z", + }, }, indentLevel: 1, showAuthPopup: noop as any, @@ -45,6 +48,9 @@ it("renders body only", () => { }, body: "Woof", createdAt: "1995-12-17T03:24:00.000Z", + editing: { + editableUntil: "1995-12-17T03:24:30.000Z", + }, }, indentLevel: 1, showAuthPopup: noop as any, diff --git a/src/core/client/stream/containers/CommentContainer.tsx b/src/core/client/stream/containers/CommentContainer.tsx index c3368b9d0..9d6ae37f6 100644 --- a/src/core/client/stream/containers/CommentContainer.tsx +++ b/src/core/client/stream/containers/CommentContainer.tsx @@ -94,7 +94,7 @@ export class CommentContainer extends Component { } public render() { - const { comment, asset, ...rest } = this.props; + const { comment, asset, indentLevel } = this.props; const { showReplyDialog, showEditDialog, editable } = this.state; if (showEditDialog) { return ( @@ -108,8 +108,10 @@ export class CommentContainer extends Component { return ( <> diff --git a/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap b/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap index 4883c80e1..9ad26125f 100644 --- a/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap +++ b/src/core/client/stream/containers/__snapshots__/CommentContainer.spec.tsx.snap @@ -22,10 +22,7 @@ exports[`renders body only 1`] = ` /> } - id="comment-id" indentLevel={1} - me={null} - showAuthPopup={[Function]} /> `; @@ -52,10 +49,7 @@ exports[`renders username and body 1`] = ` /> } - id="comment-id" indentLevel={1} - me={null} - showAuthPopup={[Function]} /> `; diff --git a/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap b/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap index 2b6612877..27964d7c7 100644 --- a/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap +++ b/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap @@ -153,20 +153,24 @@ exports[`loads more comments 1`] = ` role="article" >
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Isabelle - - + + Isabelle + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Markus - - + + Markus + + +
    - - Markus - - + + Markus + + +
    - - Markus - - + + Markus + + +
    +
    + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Markus - - + + Markus + + +
    +
    + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Markus - - + + Markus + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Markus - - + + Markus + + +
    - - Lukas - - + + Lukas + + +
    - - Isabelle - - + + Isabelle + + +
    { author: users[0], body: "Hello world! (from server)", createdAt: "2018-07-06T18:24:00.000Z", + editing: { + editableUntil: "2018-07-06T18:24:30.000Z", + }, }, }, clientMutationId: "0", diff --git a/src/core/client/stream/test/postReply.spec.tsx b/src/core/client/stream/test/postReply.spec.tsx index 3c64bc098..27a91b267 100644 --- a/src/core/client/stream/test/postReply.spec.tsx +++ b/src/core/client/stream/test/postReply.spec.tsx @@ -45,6 +45,9 @@ beforeEach(() => { edges: [], pageInfo: { endCursor: null, hasNextPage: false }, }, + editing: { + editableUntil: "2018-07-06T18:24:30.000Z", + }, }, }, clientMutationId: "0", From 170fb47daea9f4556b561b50c862e51fafc1fef0 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 11 Sep 2018 17:32:46 +0200 Subject: [PATCH 18/53] More unit tests --- .../client/stream/components/Comment/Comment.spec.tsx | 2 ++ src/core/client/stream/components/Comment/Comment.tsx | 4 ++-- .../Comment/__snapshots__/Comment.spec.tsx.snap | 7 ++++++- .../Comment/__snapshots__/IndentedComment.spec.tsx.snap | 1 - src/core/common/utils/isBeforeDate.spec.ts | 9 +++++++++ 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/core/client/stream/components/Comment/Comment.spec.tsx b/src/core/client/stream/components/Comment/Comment.spec.tsx index cae068012..f8d14f6eb 100644 --- a/src/core/client/stream/components/Comment/Comment.spec.tsx +++ b/src/core/client/stream/components/Comment/Comment.spec.tsx @@ -12,6 +12,8 @@ it("renders username and body", () => { }, body: "Woof", createdAt: "1995-12-17T03:24:00.000Z", + topBarRight: "topBarRight", + footer: "footer", }; const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); diff --git a/src/core/client/stream/components/Comment/Comment.tsx b/src/core/client/stream/components/Comment/Comment.tsx index 02dd50bfd..9a03094cc 100644 --- a/src/core/client/stream/components/Comment/Comment.tsx +++ b/src/core/client/stream/components/Comment/Comment.tsx @@ -15,8 +15,8 @@ export interface CommentProps { } | null; body: string | null; createdAt: string; - topBarRight?: ReactElement | Array>; - footer?: ReactElement | Array>; + topBarRight?: React.ReactNode; + footer?: React.ReactNode; } const Comment: StatelessComponent = props => { diff --git a/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap b/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap index cf9aa58ab..7d13e23ea 100644 --- a/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap +++ b/src/core/client/stream/components/Comment/__snapshots__/Comment.spec.tsx.snap @@ -18,6 +18,9 @@ exports[`renders username and body 1`] = ` 1995-12-17T03:24:00.000Z +
    + topBarRight +
    Woof @@ -26,6 +29,8 @@ exports[`renders username and body 1`] = ` className="Comment-footer" direction="row" itemGutter="half" - /> + > + footer +
    `; diff --git a/src/core/client/stream/components/Comment/__snapshots__/IndentedComment.spec.tsx.snap b/src/core/client/stream/components/Comment/__snapshots__/IndentedComment.spec.tsx.snap index ae2d56dee..d264e4050 100644 --- a/src/core/client/stream/components/Comment/__snapshots__/IndentedComment.spec.tsx.snap +++ b/src/core/client/stream/components/Comment/__snapshots__/IndentedComment.spec.tsx.snap @@ -12,7 +12,6 @@ exports[`renders correctly 1`] = ` } body="Woof" createdAt="1995-12-17T03:24:00.000Z" - id="comment-id" /> `; diff --git a/src/core/common/utils/isBeforeDate.spec.ts b/src/core/common/utils/isBeforeDate.spec.ts index e69de29bb..4359ca55a 100644 --- a/src/core/common/utils/isBeforeDate.spec.ts +++ b/src/core/common/utils/isBeforeDate.spec.ts @@ -0,0 +1,9 @@ +import timekeeper from "timekeeper"; +import isBeforeDate from "./isBeforeDate"; + +it("works correctly", () => { + timekeeper.freeze(new Date("2018-07-06T18:24:00.000Z")); + expect(isBeforeDate(new Date("2018-07-06T18:24:30.000Z"))).toBe(true); + expect(isBeforeDate(new Date("2018-07-06T18:23:30.000Z"))).toBe(false); + timekeeper.reset(); +}); From 7b221c40a0f91016f22e1261861094addae51e4d Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 11 Sep 2018 18:03:54 +0200 Subject: [PATCH 19/53] Add integration test --- .../stream/components/Comment/Comment.tsx | 2 +- .../stream/containers/CommentContainer.tsx | 1 + .../__snapshots__/editComment.spec.tsx.snap | 1459 +++++++++++++++++ .../client/stream/test/editComment.spec.tsx | 90 + 4 files changed, 1551 insertions(+), 1 deletion(-) create mode 100644 src/core/client/stream/test/__snapshots__/editComment.spec.tsx.snap create mode 100644 src/core/client/stream/test/editComment.spec.tsx diff --git a/src/core/client/stream/components/Comment/Comment.tsx b/src/core/client/stream/components/Comment/Comment.tsx index 9a03094cc..68fa6dab4 100644 --- a/src/core/client/stream/components/Comment/Comment.tsx +++ b/src/core/client/stream/components/Comment/Comment.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, StatelessComponent } from "react"; +import React, { StatelessComponent } from "react"; import { Flex } from "talk-ui/components"; diff --git a/src/core/client/stream/containers/CommentContainer.tsx b/src/core/client/stream/containers/CommentContainer.tsx index 9d6ae37f6..df61c3f9e 100644 --- a/src/core/client/stream/containers/CommentContainer.tsx +++ b/src/core/client/stream/containers/CommentContainer.tsx @@ -116,6 +116,7 @@ export class CommentContainer extends Component { (editable && ( +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + +
    + +
    +
    +
    +
    +
    +
    + + Powered by + + ⁨The Coral Project⁩ + + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + Markus + + +
    +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + + + Edit: + + remaining + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + + Lukas + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +`; + +exports[`edit a comment: optimistic response 1`] = ` +
    +
    +
    +
    +
    +
    + Signed in as + + Markus + + . +
    +
    + + Not you?  + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + +
    + +
    +
    +
    +
    +
    +
    + + Powered by + + ⁨The Coral Project⁩ + + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + Markus + + +
    +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + + + Edit: + + remaining + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + + Lukas + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +`; + +exports[`edit a comment: render stream 1`] = ` +
    +
    +
    +
    +
    +
    + Signed in as + + Markus + + . +
    +
    + + Not you?  + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + +
    + +
    +
    +
    +
    +
    +
    + + Powered by + + ⁨The Coral Project⁩ + + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + Markus + + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + Lukas + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +`; + +exports[`edit a comment: server response 1`] = ` +
    +
    +
    +
    +
    +
    + Signed in as + + Markus + + . +
    +
    + + Not you?  + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + +
    + +
    +
    +
    +
    +
    +
    + + Powered by + + ⁨The Coral Project⁩ + + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + Markus + + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + Lukas + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +`; diff --git a/src/core/client/stream/test/editComment.spec.tsx b/src/core/client/stream/test/editComment.spec.tsx new file mode 100644 index 000000000..b1d0a67e8 --- /dev/null +++ b/src/core/client/stream/test/editComment.spec.tsx @@ -0,0 +1,90 @@ +import { ReactTestRenderer } from "react-test-renderer"; +import timekeeper from "timekeeper"; + +import { timeout } from "talk-common/utils"; +import { createSinonStub } from "talk-framework/testHelpers"; + +import create from "./create"; +import { assets, users } from "./fixtures"; + +let testRenderer: ReactTestRenderer; +beforeEach(() => { + const resolvers = { + Query: { + asset: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0]) + ), + me: createSinonStub( + s => s.throws(), + s => s.withArgs(undefined, { clientAuthRevision: 0 }).returns(users[0]) + ), + }, + Mutation: { + editComment: createSinonStub( + s => s.throws(), + s => + s + .withArgs(undefined, { + input: { + assetID: assets[0].id, + commentID: assets[0].comments.edges[0].node.id, + body: "Edited!", + clientMutationId: "0", + }, + }) + .returns({ + // TODO: add a type assertion here to ensure that if the type changes, that the test will fail + comment: { + id: assets[0].comments.edges[0].node.id, + body: "Edited! (from server)", + }, + clientMutationId: "0", + }) + ), + }, + }; + + timekeeper.freeze(assets[0].comments.edges[0].node.createdAt); + ({ testRenderer } = create({ + // Set this to true, to see graphql responses. + logNetwork: false, + resolvers, + initLocalState: localRecord => { + localRecord.setValue(assets[0].id, "assetID"); + }, + })); +}); + +afterAll(() => { + timekeeper.reset(); +}); + +it("edit a comment", async () => { + // Wait for loading. + await timeout(); + expect(testRenderer.toJSON()).toMatchSnapshot("render stream"); + + // Open edit form. + testRenderer.root + .findByProps({ id: "comments-commentContainer-editButton-comment-0" }) + .props.onClick(); + expect(testRenderer.toJSON()).toMatchSnapshot("edit form"); + + testRenderer.root + .findByProps({ inputId: "comments-editCommentForm-rte-comment-0" }) + .props.onChange({ html: "Edited!" }); + + testRenderer.root + .findByProps({ id: "comments-editCommentForm-form-comment-0" }) + .props.onSubmit(); + + // Test optimistic response. + expect(testRenderer.toJSON()).toMatchSnapshot("optimistic response"); + + // Wait for loading. + await timeout(); + + // Test after server response. + expect(testRenderer.toJSON()).toMatchSnapshot("server response"); +}); From 5a8ce7655cb191316965d2c0b04630dfd35651e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Tue, 11 Sep 2018 13:09:49 -0300 Subject: [PATCH 20/53] Updated tests --- .../test/__snapshots__/signIn.spec.tsx.snap | 54 ++- .../test/__snapshots__/signUp.spec.tsx.snap | 369 ++++++++++++++---- .../ui/components/Message/Message.spec.tsx | 6 +- .../client/ui/components/Message/Message.tsx | 3 - .../__snapshots__/Message.spec.tsx.snap | 2 +- .../ValidationMessage/ValidationMessage.tsx | 9 +- .../ValidationMessage.spec.tsx.snap | 9 +- 7 files changed, 341 insertions(+), 111 deletions(-) diff --git a/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap b/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap index b4db66fc7..c21b65546 100644 --- a/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap +++ b/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap @@ -34,9 +34,14 @@ exports[`accepts correct password 1`] = ` value="" />
    + This field is required. @@ -175,9 +180,14 @@ exports[`accepts valid email 1`] = ` value="" />
    + This field is required. @@ -280,9 +290,14 @@ exports[`checks for invalid email 1`] = ` value="invalid-email" />
    + Please enter a valid email address. @@ -306,9 +321,14 @@ exports[`checks for invalid email 1`] = ` value="" />
    + This field is required. @@ -526,9 +546,14 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    + This field is required. @@ -552,9 +577,14 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    + This field is required. diff --git a/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap b/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap index e18788d70..88ec82da6 100644 --- a/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap +++ b/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap @@ -34,9 +34,14 @@ exports[`accepts correct password 1`] = ` value="" />
    + This field is required. @@ -65,9 +70,14 @@ exports[`accepts correct password 1`] = ` value="" />
    + This field is required. @@ -114,9 +124,14 @@ exports[`accepts correct password 1`] = ` value="" />
    + This field is required. @@ -200,9 +215,14 @@ exports[`accepts correct password confirmation 1`] = ` value="" />
    + This field is required. @@ -231,9 +251,14 @@ exports[`accepts correct password confirmation 1`] = ` value="" />
    + This field is required. @@ -262,9 +287,14 @@ exports[`accepts correct password confirmation 1`] = ` value="" />
    + This field is required. @@ -288,9 +318,14 @@ exports[`accepts correct password confirmation 1`] = ` value="testtest" />
    + Passwords do not match. Try again. @@ -397,9 +432,14 @@ exports[`accepts valid email 1`] = ` value="" />
    + This field is required. @@ -428,9 +468,14 @@ exports[`accepts valid email 1`] = ` value="" />
    + This field is required. @@ -454,9 +499,14 @@ exports[`accepts valid email 1`] = ` value="" />
    + This field is required. @@ -540,9 +590,14 @@ exports[`accepts valid username 1`] = ` value="" />
    + This field is required. @@ -594,9 +649,14 @@ exports[`accepts valid username 1`] = ` value="" />
    + This field is required. @@ -620,9 +680,14 @@ exports[`accepts valid username 1`] = ` value="" />
    + This field is required. @@ -706,9 +771,14 @@ exports[`checks for invalid characters in username 1`] = ` value="" />
    + This field is required. @@ -737,9 +807,14 @@ exports[`checks for invalid characters in username 1`] = ` value="$%$§$%$§%" />
    + Invalid characters. Try again. @@ -768,9 +843,14 @@ exports[`checks for invalid characters in username 1`] = ` value="" />
    + This field is required. @@ -794,9 +874,14 @@ exports[`checks for invalid characters in username 1`] = ` value="" />
    + This field is required. @@ -880,9 +965,14 @@ exports[`checks for invalid email 1`] = ` value="invalid-email" />
    + Please enter a valid email address. @@ -911,9 +1001,14 @@ exports[`checks for invalid email 1`] = ` value="" />
    + This field is required. @@ -942,9 +1037,14 @@ exports[`checks for invalid email 1`] = ` value="" />
    + This field is required. @@ -968,9 +1068,14 @@ exports[`checks for invalid email 1`] = ` value="" />
    + This field is required. @@ -1054,9 +1159,14 @@ exports[`checks for too long username 1`] = ` value="" />
    + This field is required. @@ -1085,9 +1195,14 @@ exports[`checks for too long username 1`] = ` value="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" />
    + Usernames cannot be longer than ⁨20⁩ characters. @@ -1116,9 +1231,14 @@ exports[`checks for too long username 1`] = ` value="" />
    + This field is required. @@ -1142,9 +1262,14 @@ exports[`checks for too long username 1`] = ` value="" />
    + This field is required. @@ -1228,9 +1353,14 @@ exports[`checks for too short password 1`] = ` value="" />
    + This field is required. @@ -1259,9 +1389,14 @@ exports[`checks for too short password 1`] = ` value="" />
    + This field is required. @@ -1290,9 +1425,14 @@ exports[`checks for too short password 1`] = ` value="pass" />
    + Password must contain at least ⁨8⁩ characters. @@ -1316,9 +1456,14 @@ exports[`checks for too short password 1`] = ` value="" />
    + This field is required. @@ -1402,9 +1547,14 @@ exports[`checks for too short username 1`] = ` value="" />
    + This field is required. @@ -1433,9 +1583,14 @@ exports[`checks for too short username 1`] = ` value="u" />
    + Username must contain at least ⁨3⁩ characters. @@ -1464,9 +1619,14 @@ exports[`checks for too short username 1`] = ` value="" />
    + This field is required. @@ -1490,9 +1650,14 @@ exports[`checks for too short username 1`] = ` value="" />
    + This field is required. @@ -1576,9 +1741,14 @@ exports[`checks for wrong password confirmation 1`] = ` value="" />
    + This field is required. @@ -1607,9 +1777,14 @@ exports[`checks for wrong password confirmation 1`] = ` value="" />
    + This field is required. @@ -1638,9 +1813,14 @@ exports[`checks for wrong password confirmation 1`] = ` value="" />
    + This field is required. @@ -1664,9 +1844,14 @@ exports[`checks for wrong password confirmation 1`] = ` value="not-matching" />
    + Passwords do not match. Try again. @@ -1892,9 +2077,14 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    + This field is required. @@ -1923,9 +2113,14 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    + This field is required. @@ -1954,9 +2149,14 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    + This field is required. @@ -1980,9 +2180,14 @@ exports[`shows error when submitting empty form 1`] = ` value="" />
    + This field is required. diff --git a/src/core/client/ui/components/Message/Message.spec.tsx b/src/core/client/ui/components/Message/Message.spec.tsx index 6f7bfc8cc..ac69cea85 100644 --- a/src/core/client/ui/components/Message/Message.spec.tsx +++ b/src/core/client/ui/components/Message/Message.spec.tsx @@ -16,12 +16,8 @@ it("renders correctly", () => { }); it("renders icon", () => { - const props: PropTypesOf = { - className: "custom", - }; - const renderer = TestRenderer.create( - + alertAlert Message ); diff --git a/src/core/client/ui/components/Message/Message.tsx b/src/core/client/ui/components/Message/Message.tsx index c3f4ea2e8..7547d8442 100644 --- a/src/core/client/ui/components/Message/Message.tsx +++ b/src/core/client/ui/components/Message/Message.tsx @@ -1,9 +1,6 @@ import cn from "classnames"; import React, { ReactNode, StatelessComponent } from "react"; - import { withStyles } from "talk-ui/hocs"; - -import Icon from "../Icon"; import * as styles from "./Message.css"; export interface MessageProps { diff --git a/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap b/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap index ae81503dc..38381f0c6 100644 --- a/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap +++ b/src/core/client/ui/components/Message/__snapshots__/Message.spec.tsx.snap @@ -10,7 +10,7 @@ exports[`renders correctly 1`] = ` exports[`renders icon 1`] = `
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + +
    + +
    +
    +
    +
    +
    +
    + + Powered by + + ⁨The Coral Project⁩ + + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + Markus + + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + Lukas + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +`; + exports[`edit a comment: edit form 1`] = `
    `; + +exports[`shows expiry message: edit form closed 1`] = ` +
    +
    +
    +
    +
    +
    + Signed in as + + Markus + + . +
    +
    + + Not you?  + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + +
    + +
    +
    +
    +
    +
    +
    + + Powered by + + ⁨The Coral Project⁩ + + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + Markus + + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + Lukas + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +`; + +exports[`shows expiry message: edit time expired 1`] = ` +
    +
    +
    +
    +
    +
    + Signed in as + + Markus + + . +
    +
    + + Not you?  + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + +
    + +
    +
    +
    +
    +
    +
    + + Powered by + + ⁨The Coral Project⁩ + + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + Markus + + +
    +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + + Edit time has expired. You can no longer edit this comment. Why not post another one? +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + Lukas + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +`; diff --git a/src/core/client/stream/test/__snapshots__/postComment.spec.tsx.snap b/src/core/client/stream/test/__snapshots__/postComment.spec.tsx.snap index 15fe54400..0c0129082 100644 --- a/src/core/client/stream/test/__snapshots__/postComment.spec.tsx.snap +++ b/src/core/client/stream/test/__snapshots__/postComment.spec.tsx.snap @@ -207,6 +207,7 @@ exports[`post a comment: optimistic response 1`] = `
    Powered by Lukas - + +
    Powered by Lukas - + +
    Powered by Markus - + +
    Powered by Markus - + +
    + ( + + Edited + + ) +
    +
    Powered by Markus - + +
    Powered by Lukas - + +
    Markus - + +
    Lukas - + +
    Isabelle - + +
    Markus - + +
    Lukas - + +
    Markus - + +
    Markus - + +
    Markus - + +
    Powered by Markus - + +
    Lukas - + +
    Powered by Markus - + +
    Markus - + +
    Lukas - + +
    Powered by Markus - + +
    Lukas - + +
    Powered by Markus - + +
    Lukas - + +
    Powered by Markus - + +
    Markus - + +
    Powered by Markus - + +
    Markus - + +
    Lukas - + +
    Powered by Markus - + +
    Lukas - + +
    Markus - + +
    Markus - + +
    Markus - + +
    Lukas - + +
    Markus - + +
    Lukas - + +
    Markus - + +
    Lukas - + +
    Markus - + +
    Lukas - + +
    Isabelle - + +
    { body: "Hello world! (from server)", createdAt: "2018-07-06T18:24:00.000Z", editing: { + edited: false, editableUntil: "2018-07-06T18:24:30.000Z", }, }, diff --git a/src/core/client/stream/test/postReply.spec.tsx b/src/core/client/stream/test/postReply.spec.tsx index 27a91b267..e8d43bdc5 100644 --- a/src/core/client/stream/test/postReply.spec.tsx +++ b/src/core/client/stream/test/postReply.spec.tsx @@ -46,6 +46,7 @@ beforeEach(() => { pageInfo: { endCursor: null, hasNextPage: false }, }, editing: { + edited: false, editableUntil: "2018-07-06T18:24:30.000Z", }, }, diff --git a/src/core/client/ui/components/FormField/__snapshots__/FormField.spec.tsx.snap b/src/core/client/ui/components/FormField/__snapshots__/FormField.spec.tsx.snap index 874adc015..011020568 100644 --- a/src/core/client/ui/components/FormField/__snapshots__/FormField.spec.tsx.snap +++ b/src/core/client/ui/components/FormField/__snapshots__/FormField.spec.tsx.snap @@ -18,7 +18,7 @@ exports[`works with multiple form components 1`] = ` Username

    A unique identifier displayed on your comments. You may use “_” and “.”

    diff --git a/src/core/client/ui/components/InputDescription/InputDescription.tsx b/src/core/client/ui/components/InputDescription/InputDescription.tsx index d36d5fe7a..ae84ed141 100644 --- a/src/core/client/ui/components/InputDescription/InputDescription.tsx +++ b/src/core/client/ui/components/InputDescription/InputDescription.tsx @@ -11,7 +11,7 @@ const InputDescription: StatelessComponent = props => { const { className, children, ...rest } = props; return ( Form Components should go here

    diff --git a/src/core/client/ui/components/Typography/Typography.css b/src/core/client/ui/components/Typography/Typography.css index a2bd115fe..be3e0954e 100644 --- a/src/core/client/ui/components/Typography/Typography.css +++ b/src/core/client/ui/components/Typography/Typography.css @@ -40,8 +40,8 @@ composes: inputLabel from "talk-ui/shared/typography.css"; } -.inputDescription { - composes: inputDescription from "talk-ui/shared/typography.css"; +.detail { + composes: detail from "talk-ui/shared/typography.css"; } .timestamp { diff --git a/src/core/client/ui/components/Typography/Typography.tsx b/src/core/client/ui/components/Typography/Typography.tsx index 2c4a20d94..b8f6d2396 100644 --- a/src/core/client/ui/components/Typography/Typography.tsx +++ b/src/core/client/ui/components/Typography/Typography.tsx @@ -15,7 +15,7 @@ type Variant = | "bodyCopy" | "bodyCopyBold" | "inputLabel" - | "inputDescription" + | "detail" | "timestamp"; // Based on Typography Component of Material UI. @@ -146,7 +146,7 @@ Typography.defaultProps = { bodyCopyBold: "p", timestamp: "span", inputLabel: "label", - inputDescription: "p", + detail: "p", }, noWrap: false, paragraph: false, diff --git a/src/core/client/ui/shared/typography.css b/src/core/client/ui/shared/typography.css index cb785f8fc..3012a3617 100644 --- a/src/core/client/ui/shared/typography.css +++ b/src/core/client/ui/shared/typography.css @@ -155,7 +155,7 @@ color: var(--palette-text-primary); } -.inputDescription { +.detail { font-size: calc(14rem / var(--rem-base)); font-weight: var(--font-weight-regular); font-family: var(--font-family-sans-serif); diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index 683ea792e..d939806a7 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -65,3 +65,4 @@ comments-editCommentForm-rte = .placeholder = { comments-editCommentForm-rteLabel } comments-editCommentForm-editRemainingTime = Edit: remaining comments-editCommentForm-editTimeExpired = Edit time has expired. You can no longer edit this comment. Why not post another one? +comments-editedMarker-edited = Edited From 2292e5d53f1ba6da890190e3e2cbfb5923a53989 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 12 Sep 2018 17:07:36 +0200 Subject: [PATCH 30/53] Fix integration test --- src/core/client/stream/test/editComment.spec.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/client/stream/test/editComment.spec.tsx b/src/core/client/stream/test/editComment.spec.tsx index d141201ea..de22f2384 100644 --- a/src/core/client/stream/test/editComment.spec.tsx +++ b/src/core/client/stream/test/editComment.spec.tsx @@ -25,7 +25,6 @@ function createTestRenderer() { s .withArgs(undefined, { input: { - assetID: assets[0].id, commentID: assets[0].comments.edges[0].node.id, body: "Edited!", clientMutationId: "0", From 8b09b52be8ee7204aac20997b7cc611c683d2b59 Mon Sep 17 00:00:00 2001 From: Kiwi Date: Wed, 12 Sep 2018 18:04:54 +0200 Subject: [PATCH 31/53] [next] Start a clean session when user logs in / out (#1853) * Clear user session after login / logout * Filename cases * Improve type checking * Apply suggestions --- src/core/client/auth/index.tsx | 20 +- src/core/client/auth/test/create.tsx | 5 +- src/core/client/framework/helpers/getMe.ts | 7 +- .../framework/lib/bootstrap/TalkContext.tsx | 7 + .../framework/lib/bootstrap/createContext.tsx | 147 ---------- .../framework/lib/bootstrap/createManaged.tsx | 252 ++++++++++++++++++ .../client/framework/lib/bootstrap/index.ts | 8 +- .../lib/relay/commitLocalUpdatePromisified.ts | 18 ++ src/core/client/framework/lib/relay/index.ts | 3 + .../mutations/SetAuthTokenMutation.spec.ts | 32 ++- .../mutations/SetAuthTokenMutation.ts | 28 +- src/core/client/stream/index.tsx | 44 ++- .../listeners/OnPostMessageAuthError.tsx | 28 ++ ....ts => OnPostMessageSetAuthToken.spec.tsx} | 17 +- .../listeners/OnPostMessageSetAuthToken.ts | 31 +++ ...tID.spec.ts => OnPymSetCommentID.spec.tsx} | 16 +- .../stream/listeners/OnPymSetCommentID.ts | 39 +++ src/core/client/stream/listeners/index.ts | 8 +- .../listeners/onPostMessageAuthError.ts | 11 - .../listeners/onPostMessageSetAuthToken.ts | 11 - .../stream/listeners/onPymSetCommentID.ts | 19 -- .../__snapshots__/initLocalState.spec.ts.snap | 1 - .../client/stream/local/initLocalState.ts | 3 - src/core/client/stream/local/local.graphql | 4 - .../stream/queries/PermalinkViewQuery.tsx | 15 +- .../client/stream/queries/StreamQuery.tsx | 11 +- src/core/client/stream/test/create.tsx | 5 +- .../client/stream/test/postComment.spec.tsx | 10 +- .../client/stream/test/postReply.spec.tsx | 10 +- .../server/graph/tenant/schema/schema.graphql | 6 +- 30 files changed, 485 insertions(+), 331 deletions(-) delete mode 100644 src/core/client/framework/lib/bootstrap/createContext.tsx create mode 100644 src/core/client/framework/lib/bootstrap/createManaged.tsx create mode 100644 src/core/client/framework/lib/relay/commitLocalUpdatePromisified.ts create mode 100644 src/core/client/stream/listeners/OnPostMessageAuthError.tsx rename src/core/client/stream/listeners/{onPostMessageSetAuthToken.spec.ts => OnPostMessageSetAuthToken.spec.tsx} (57%) create mode 100644 src/core/client/stream/listeners/OnPostMessageSetAuthToken.ts rename src/core/client/stream/listeners/{onPymSetCommentID.spec.ts => OnPymSetCommentID.spec.tsx} (77%) create mode 100644 src/core/client/stream/listeners/OnPymSetCommentID.ts delete mode 100644 src/core/client/stream/listeners/onPostMessageAuthError.ts delete mode 100644 src/core/client/stream/listeners/onPostMessageSetAuthToken.ts delete mode 100644 src/core/client/stream/listeners/onPymSetCommentID.ts diff --git a/src/core/client/auth/index.tsx b/src/core/client/auth/index.tsx index b30487719..d6057f5a3 100644 --- a/src/core/client/auth/index.tsx +++ b/src/core/client/auth/index.tsx @@ -2,11 +2,7 @@ import React from "react"; import { StatelessComponent } from "react"; import ReactDOM from "react-dom"; -import { - createContext, - TalkContext, - TalkContextProvider, -} from "talk-framework/lib/bootstrap"; +import { createManaged } from "talk-framework/lib/bootstrap"; import AppContainer from "./containers/AppContainer"; import resizePopup from "./dom/resizePopup"; @@ -32,23 +28,17 @@ function pollPopupHeight(interval: number = 100) { }, interval); } -// This is called when the context is first initialized. -async function init({ relayEnvironment }: TalkContext) { - await initLocalState(relayEnvironment); -} - async function main() { - // Bootstrap our context. - const context = await createContext({ - init, + const ManagedTalkContextProvider = await createManaged({ + initLocalState, localesData, userLocales: navigator.languages, }); const Index: StatelessComponent = () => ( - + - + ); ReactDOM.render(, document.getElementById("app")); diff --git a/src/core/client/auth/test/create.tsx b/src/core/client/auth/test/create.tsx index a0b9d9da9..fc03cd354 100644 --- a/src/core/client/auth/test/create.tsx +++ b/src/core/client/auth/test/create.tsx @@ -1,4 +1,6 @@ +import { EventEmitter2 } from "eventemitter2"; import { IResolvers } from "graphql-tools"; +import { noop } from "lodash"; import React from "react"; import TestRenderer from "react-test-renderer"; import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime"; @@ -29,7 +31,6 @@ export default function create(params: CreateParams) { logNetwork: params.logNetwork, resolvers: params.resolvers, initLocalState: (localRecord, source, env) => { - localRecord.setValue(0, "authRevision"); if (params.initLocalState) { params.initLocalState(localRecord, source, env); } @@ -45,6 +46,8 @@ export default function create(params: CreateParams) { postMessage: new PostMessageService(), browserInfo: { ios: false }, uuidGenerator: createUUIDGenerator(), + eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }), + clearSession: noop, }; const testRenderer = TestRenderer.create( diff --git a/src/core/client/framework/helpers/getMe.ts b/src/core/client/framework/helpers/getMe.ts index fb5bafcf9..d989b5be2 100644 --- a/src/core/client/framework/helpers/getMe.ts +++ b/src/core/client/framework/helpers/getMe.ts @@ -3,12 +3,9 @@ import { Environment, ROOT_ID } from "relay-runtime"; export default function getMe(environment: Environment) { const source = environment.getStore().getSource(); const root = source.get(ROOT_ID)!; - const meKey = Object.keys(root) - .reverse() - .find(s => s.startsWith("me("))!; - if (!root[meKey]) { + if (!root.me) { return null; } - const meID = root[meKey].__ref; + const meID = root.me.__ref; return source.get(meID)!; } diff --git a/src/core/client/framework/lib/bootstrap/TalkContext.tsx b/src/core/client/framework/lib/bootstrap/TalkContext.tsx index d855a884e..ece409674 100644 --- a/src/core/client/framework/lib/bootstrap/TalkContext.tsx +++ b/src/core/client/framework/lib/bootstrap/TalkContext.tsx @@ -1,3 +1,4 @@ +import { EventEmitter2 } from "eventemitter2"; import { LocalizationProvider } from "fluent-react/compat"; import { FluentBundle } from "fluent/compat"; import { Child as PymChild } from "pym.js"; @@ -52,6 +53,12 @@ export interface TalkContext { /** Generates uuids. */ uuidGenerator: () => string; + + /** A event emitter */ + eventEmitter: EventEmitter2; + + /** Clear session data. */ + clearSession: () => void; } const { Provider, Consumer } = React.createContext({} as any); diff --git a/src/core/client/framework/lib/bootstrap/createContext.tsx b/src/core/client/framework/lib/bootstrap/createContext.tsx deleted file mode 100644 index 839cee453..000000000 --- a/src/core/client/framework/lib/bootstrap/createContext.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { EventEmitter2 } from "eventemitter2"; -import { Localized } from "fluent-react/compat"; -import { noop } from "lodash"; -import { Child as PymChild } from "pym.js"; -import React from "react"; -import { Formatter } from "react-timeago"; -import { Environment, Network, RecordSource, Store } from "relay-runtime"; -import uuid from "uuid/v4"; - -import { getBrowserInfo } from "talk-framework/lib/browserInfo"; -import { LOCAL_ID } from "talk-framework/lib/relay"; -import { - createLocalStorage, - createPromisifiedStorage, - createPymStorage, - createSessionStorage, -} from "talk-framework/lib/storage"; - -import { RestClient } from "talk-framework/lib/rest"; -import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside"; - -import { generateBundles, LocalesData, negotiateLanguages } from "../i18n"; -import { createFetch, TokenGetter } from "../network"; -import { PostMessageService } from "../postMessage"; -import { TalkContext } from "./TalkContext"; - -interface CreateContextArguments { - /** Locales that the user accepts, usually `navigator.languages`. */ - userLocales: ReadonlyArray; - - /** Locales data that is returned by our `locales-loader`. */ - localesData: LocalesData; - - /** Init will be called after the context has been created. */ - init?: ((context: TalkContext) => void | Promise); - - /** A pym child that interacts with the pym parent. */ - pym?: PymChild; - - /** Supports emitting and listening to events. */ - eventEmitter?: EventEmitter2; -} - -/** - * 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 ( - - now - - ); -}; - -function areWeInIframe() { - try { - return window.self !== window.top; - } catch (e) { - return true; - } -} - -/** - * `createContext` manages the dependencies of our framework - * and returns a `TalkContext` that can be passed to the - * `TalkContextProvider`. - */ -export default async function createContext({ - init = noop, - userLocales, - localesData, - pym, - eventEmitter = new EventEmitter2({ wildcard: true }), -}: CreateContextArguments): Promise { - const inIframe = areWeInIframe(); - // Initialize Relay. - const source = new RecordSource(); - const tokenGetter: TokenGetter = () => { - const localState = source.get(LOCAL_ID); - if (localState) { - return localState.authToken || ""; - } - return ""; - }; - const relayEnvironment = new Environment({ - network: Network.create(createFetch(tokenGetter)), - store: new Store(source), - }); - - // Listen for outside clicks. - let registerClickFarAway: ClickFarAwayRegister | undefined; - if (pym) { - registerClickFarAway = cb => { - pym.onMessage("click", cb); - // Return unlisten callback. - return () => { - const index = pym.messageHandlers.click.indexOf(cb); - if (index > -1) { - pym.messageHandlers.click.splice(index, 1); - } - }; - }; - } - - // Initialize i18n. - const locales = negotiateLanguages(userLocales, localesData); - - if (process.env.NODE_ENV !== "production") { - // tslint:disable:next-line: no-console - console.log(`Negotiated locales ${JSON.stringify(locales)}`); - } - - const localeBundles = await generateBundles(locales, localesData); - - // Assemble context. - const context = { - relayEnvironment, - localeBundles, - timeagoFormatter, - pym, - eventEmitter, - registerClickFarAway, - rest: new RestClient("/api", tokenGetter), - postMessage: new PostMessageService(), - localStorage: - (pym && inIframe && createPymStorage(pym, "localStorage")) || - createPromisifiedStorage(createLocalStorage()), - sessionStorage: - (pym && inIframe && createPymStorage(pym, "sessionStorage")) || - createPromisifiedStorage(createSessionStorage()), - browserInfo: getBrowserInfo(), - uuidGenerator: uuid, - }; - - // Run custom initializations. - await init(context); - - return context; -} diff --git a/src/core/client/framework/lib/bootstrap/createManaged.tsx b/src/core/client/framework/lib/bootstrap/createManaged.tsx new file mode 100644 index 000000000..f2d86e7c8 --- /dev/null +++ b/src/core/client/framework/lib/bootstrap/createManaged.tsx @@ -0,0 +1,252 @@ +import { EventEmitter2 } from "eventemitter2"; +import { Localized } from "fluent-react/compat"; +import { noop } from "lodash"; +import { Child as PymChild } from "pym.js"; +import React, { Component, ComponentType } from "react"; +import { Formatter } from "react-timeago"; +import { Environment, Network, RecordSource, Store } from "relay-runtime"; +import uuid from "uuid/v4"; + +import { getBrowserInfo } from "talk-framework/lib/browserInfo"; +import { LOCAL_ID } from "talk-framework/lib/relay"; +import { + createLocalStorage, + createPromisifiedStorage, + createPymStorage, + createSessionStorage, + PromisifiedStorage, +} from "talk-framework/lib/storage"; + +import { RestClient } from "talk-framework/lib/rest"; +import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside"; + +import { generateBundles, LocalesData, negotiateLanguages } from "../i18n"; +import { createFetch, TokenGetter } from "../network"; +import { PostMessageService } from "../postMessage"; +import { TalkContext, TalkContextProvider } from "./TalkContext"; + +export type InitLocalState = (( + environment: Environment, + context: TalkContext +) => void | Promise); + +interface CreateContextArguments { + /** Locales that the user accepts, usually `navigator.languages`. */ + userLocales: ReadonlyArray; + + /** Locales data that is returned by our `locales-loader`. */ + localesData: LocalesData; + + /** Init will be called after the context has been created. */ + initLocalState?: InitLocalState; + + /** A pym child that interacts with the pym parent. */ + pym?: PymChild; + + /** Supports emitting and listening to events. */ + eventEmitter?: EventEmitter2; +} + +/** + * 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 ( + + now + + ); +}; + +/** + * Returns true if we are in an iframe. + */ +function areWeInIframe() { + try { + return window.self !== window.top; + } catch (e) { + return true; + } +} + +function createRelayEnvironment() { + // Initialize Relay. + const source = new RecordSource(); + const tokenGetter: TokenGetter = () => { + const localState = source.get(LOCAL_ID); + if (localState) { + return localState.authToken || ""; + } + return ""; + }; + const environment = new Environment({ + network: Network.create(createFetch(tokenGetter)), + store: new Store(source), + }); + return { environment, tokenGetter }; +} + +function createRestAPI(tokenGetter: (() => string)) { + return new RestClient("/api", tokenGetter); +} + +/** + * Returns a managed TalkContextProvider, that includes given context + * and handles context changes, e.g. when a user session changes. + */ +function createMangedTalkContextProvider( + context: TalkContext, + initLocalState: InitLocalState +) { + const ManagedTalkContextProvider = class extends Component< + {}, + { context: TalkContext } + > { + constructor(props: {}) { + super(props); + this.state = { + context: { + ...context, + clearSession: this.clearSession, + }, + }; + } + + // This is called every time a user session starts or ends. + private clearSession = async () => { + // Clear session storage. + this.state.context.sessionStorage.clear(); + + // Create a new context with a new Relay Environment. + const { + environment: newEnvironment, + tokenGetter: newTokenGetter, + } = createRelayEnvironment(); + + const newContext = { + ...this.state.context, + relayEnvironment: newEnvironment, + rest: createRestAPI(newTokenGetter), + }; + + // Initialize local state. + await initLocalState(newContext.relayEnvironment, newContext); + + // Propagate new context. + this.setState({ + context: newContext, + }); + }; + + public render() { + return ( + + {this.props.children} + + ); + } + }; + + return ManagedTalkContextProvider; +} + +/** + * resolveLocalStorage decides which local storage to use in the context + */ +function resolveLocalStorage(pym?: PymChild): PromisifiedStorage { + if (pym && areWeInIframe()) { + // Use local storage over pym when we have pym and are in an iframe. + return createPymStorage(pym, "localStorage"); + } + // Use promisified, prefixed local storage. + return createPromisifiedStorage(createLocalStorage()); +} + +/** + * resolveSessionStorage decides which session storage to use in the context + */ +function resolveSessionStorage(pym?: PymChild): PromisifiedStorage { + if (pym && areWeInIframe()) { + // Use session storage over pym when we have pym and are in an iframe. + return createPymStorage(pym, "sessionStorage"); + } + // Use promisified, prefixed session storage. + return createPromisifiedStorage(createSessionStorage()); +} + +/** + * `createManaged` establishes the dependencies of our framework + * and returns a `ManagedTalkContextProvider` that provides the context + * to the rest of the application. + */ +export default async function createManaged({ + initLocalState = noop, + userLocales, + localesData, + pym, + eventEmitter = new EventEmitter2({ wildcard: true, maxListeners: 20 }), +}: CreateContextArguments): Promise { + // Listen for outside clicks. + let registerClickFarAway: ClickFarAwayRegister | undefined; + if (pym) { + registerClickFarAway = cb => { + pym.onMessage("click", cb); + // Return unlisten callback. + return () => { + const index = pym.messageHandlers.click.indexOf(cb); + if (index > -1) { + pym.messageHandlers.click.splice(index, 1); + } + }; + }; + } + + // Initialize i18n. + const locales = negotiateLanguages(userLocales, localesData); + + if (process.env.NODE_ENV !== "production") { + // tslint:disable:next-line: no-console + console.log(`Negotiated locales ${JSON.stringify(locales)}`); + } + + const localeBundles = await generateBundles(locales, localesData); + + const localStorage = resolveLocalStorage(pym); + const sessionStorage = resolveSessionStorage(pym); + + const { environment, tokenGetter } = createRelayEnvironment(); + + // Assemble context. + const context: TalkContext = { + relayEnvironment: environment, + localeBundles, + timeagoFormatter, + pym, + eventEmitter, + registerClickFarAway, + rest: createRestAPI(tokenGetter), + postMessage: new PostMessageService(), + localStorage, + sessionStorage, + browserInfo: getBrowserInfo(), + uuidGenerator: uuid, + // Noop, this is later replaced by the + // managed TalkContextProvider. + clearSession: noop, + }; + + // Initialize local state. + await initLocalState(context.relayEnvironment, context); + + // Returns a managed TalkContextProvider, that includes the above + // context and handles context changes, e.g. when a user session changes. + return createMangedTalkContextProvider(context, initLocalState); +} diff --git a/src/core/client/framework/lib/bootstrap/index.ts b/src/core/client/framework/lib/bootstrap/index.ts index a6bd57eaa..0ef2ee79b 100644 --- a/src/core/client/framework/lib/bootstrap/index.ts +++ b/src/core/client/framework/lib/bootstrap/index.ts @@ -1,3 +1,7 @@ -export * from "./TalkContext"; -export { default as createContext } from "./createContext"; +export { + TalkContext, + TalkContextConsumer, + TalkContextProvider, +} from "./TalkContext"; +export { default as createManaged } from "./createManaged"; export { default as withContext } from "./withContext"; diff --git a/src/core/client/framework/lib/relay/commitLocalUpdatePromisified.ts b/src/core/client/framework/lib/relay/commitLocalUpdatePromisified.ts new file mode 100644 index 000000000..92d341cef --- /dev/null +++ b/src/core/client/framework/lib/relay/commitLocalUpdatePromisified.ts @@ -0,0 +1,18 @@ +import { + commitLocalUpdate, + Environment, + RecordSourceProxy, +} from "relay-runtime"; + +export default function commitLocalUpdatePromisified( + environment: Environment, + updater: (store: RecordSourceProxy) => Promise +) { + return new Promise((resolve, reject) => { + commitLocalUpdate(environment, store => { + updater(store) + .then(() => resolve()) + .catch(err => reject(err)); + }); + }); +} diff --git a/src/core/client/framework/lib/relay/index.ts b/src/core/client/framework/lib/relay/index.ts index 43dca5178..31bae3d63 100644 --- a/src/core/client/framework/lib/relay/index.ts +++ b/src/core/client/framework/lib/relay/index.ts @@ -13,3 +13,6 @@ export { commitMutationPromiseNormalized, } from "./commitMutationPromise"; export { graphql } from "react-relay"; +export { + default as commitLocalUpdatePromisified, +} from "./commitLocalUpdatePromisified"; diff --git a/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts b/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts index 887e6fd84..158dd0854 100644 --- a/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts +++ b/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts @@ -1,7 +1,9 @@ import { Environment, RecordSource } from "relay-runtime"; +import sinon from "sinon"; +import { TalkContext } from "talk-framework/lib/bootstrap"; import { LOCAL_ID } from "talk-framework/lib/relay"; -import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createPromisifiedStorage } from "talk-framework/lib/storage"; import { createRelayEnvironment } from "talk-framework/testHelpers"; import { commit } from "./SetAuthTokenMutation"; @@ -15,21 +17,29 @@ beforeAll(() => { }); }); -it("Sets auth token to localStorage", () => { - const context = { - localStorage: createInMemoryStorage(), +it("Sets auth token to localStorage", async () => { + const clearSessionStub = sinon.stub(); + const context: Partial = { + localStorage: createPromisifiedStorage(), + clearSession: clearSessionStub, }; const authToken = "auth token"; - commit(environment, { authToken }, context as any); + await commit(environment, { authToken }, context as any); expect(source.get(LOCAL_ID)!.authToken).toEqual(authToken); - expect(context.localStorage.getItem("authToken")).toEqual(authToken); + await expect(context.localStorage!.getItem("authToken")).resolves.toEqual( + authToken + ); + expect(clearSessionStub.calledOnce).toBe(true); }); -it("Removes auth token from localStorage", () => { - const context = { - localStorage: createInMemoryStorage(), +it("Removes auth token from localStorage", async () => { + const clearSessionStub = sinon.stub(); + const context: Partial = { + localStorage: createPromisifiedStorage(), + clearSession: clearSessionStub, }; localStorage.setItem("authToken", "tmp"); - commit(environment, { authToken: null }, context as any); - expect(context.localStorage.getItem("authToken")).toBeNull(); + await commit(environment, { authToken: null }, context as any); + await expect(context.localStorage!.getItem("authToken")).resolves.toBeNull(); + expect(clearSessionStub.calledOnce).toBe(true); }); diff --git a/src/core/client/framework/mutations/SetAuthTokenMutation.ts b/src/core/client/framework/mutations/SetAuthTokenMutation.ts index 90e520155..f0da6df92 100644 --- a/src/core/client/framework/mutations/SetAuthTokenMutation.ts +++ b/src/core/client/framework/mutations/SetAuthTokenMutation.ts @@ -1,7 +1,10 @@ -import { commitLocalUpdate, Environment } from "relay-runtime"; +import { Environment } from "relay-runtime"; import { TalkContext } from "talk-framework/lib/bootstrap"; -import { createMutationContainer } from "talk-framework/lib/relay"; +import { + commitLocalUpdatePromisified, + createMutationContainer, +} from "talk-framework/lib/relay"; import { LOCAL_ID } from "talk-framework/lib/relay/withLocalStateContainer"; export interface SetAuthTokenInput { @@ -13,27 +16,18 @@ export type SetAuthTokenMutation = (input: SetAuthTokenInput) => Promise; export async function commit( environment: Environment, input: SetAuthTokenInput, - { localStorage }: TalkContext + { localStorage, clearSession }: TalkContext ) { - return commitLocalUpdate(environment, store => { + return await commitLocalUpdatePromisified(environment, async store => { const record = store.get(LOCAL_ID)!; record.setValue(input.authToken, "authToken"); if (input.authToken) { - localStorage.setItem("authToken", input.authToken); + await localStorage.setItem("authToken", input.authToken); } else { - localStorage.removeItem("authToken"); + await localStorage.removeItem("authToken"); } - // Increment auth revision to indicate a change in auth state. - record.setValue(record.getValue("authRevision") + 1, "authRevision"); - - // Force gc to trigger. - environment - .retain({ - dataID: "tmp", - node: { selections: [] }, - variables: {}, - }) - .dispose(); + // Clear current session, as we are starting a new one. + clearSession(); }); } diff --git a/src/core/client/stream/index.tsx b/src/core/client/stream/index.tsx index 31fade961..7dde43c00 100644 --- a/src/core/client/stream/index.tsx +++ b/src/core/client/stream/index.tsx @@ -3,46 +3,40 @@ import React from "react"; import { StatelessComponent } from "react"; import ReactDOM from "react-dom"; -import { - createContext, - TalkContext, - TalkContextProvider, -} from "talk-framework/lib/bootstrap"; +import { createManaged } from "talk-framework/lib/bootstrap"; import AppContainer from "./containers/AppContainer"; import { - onPostMessageAuthError, - onPostMessageSetAuthToken, - onPymSetCommentID, + OnPostMessageAuthError, + OnPostMessageSetAuthToken, + OnPymSetCommentID, } from "./listeners"; import { initLocalState } from "./local"; import localesData from "./locales"; -const listeners = [ - onPymSetCommentID, - onPostMessageSetAuthToken, - onPostMessageAuthError, -]; - -// This is called when the context is first initialized. -async function init(context: TalkContext) { - await initLocalState(context.relayEnvironment, context); - listeners.forEach(f => f(context)); -} +const listeners = ( + <> + + + + +); async function main() { - // Bootstrap our context. - const context = await createContext({ - init, + const ManagedTalkContextProvider = await createManaged({ + initLocalState, localesData, userLocales: navigator.languages, pym: new PymChild({ polling: 100 }), }); const Index: StatelessComponent = () => ( - - - + + <> + {listeners} + + + ); ReactDOM.render(, document.getElementById("app")); diff --git a/src/core/client/stream/listeners/OnPostMessageAuthError.tsx b/src/core/client/stream/listeners/OnPostMessageAuthError.tsx new file mode 100644 index 000000000..3bb851965 --- /dev/null +++ b/src/core/client/stream/listeners/OnPostMessageAuthError.tsx @@ -0,0 +1,28 @@ +import { Component } from "react"; + +import { withContext } from "talk-framework/lib/bootstrap"; +import { PostMessageService } from "talk-framework/lib/postMessage"; + +interface Props { + postMessage: PostMessageService; +} + +class OnPostMessageAuthError extends Component { + constructor(props: Props) { + super(props); + // Auth popup will use this to send back errors during login. + props.postMessage!.on("authError", error => { + // tslint:disable-next-line:no-console + console.error(error); + }); + } + + public render() { + return null; + } +} + +const enhanced = withContext(({ postMessage }) => ({ postMessage }))( + OnPostMessageAuthError +); +export default enhanced; diff --git a/src/core/client/stream/listeners/onPostMessageSetAuthToken.spec.ts b/src/core/client/stream/listeners/OnPostMessageSetAuthToken.spec.tsx similarity index 57% rename from src/core/client/stream/listeners/onPostMessageSetAuthToken.spec.ts rename to src/core/client/stream/listeners/OnPostMessageSetAuthToken.spec.tsx index c35823c8a..da2eb5816 100644 --- a/src/core/client/stream/listeners/onPostMessageSetAuthToken.spec.ts +++ b/src/core/client/stream/listeners/OnPostMessageSetAuthToken.spec.tsx @@ -1,10 +1,14 @@ +import { shallow } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; import { Environment, RecordSource } from "relay-runtime"; +import { TalkContext } from "talk-framework/lib/bootstrap"; import { LOCAL_ID } from "talk-framework/lib/relay"; -import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createPromisifiedStorage } from "talk-framework/lib/storage"; import { createRelayEnvironment } from "talk-framework/testHelpers"; -import onPostMessageSetAuthToken from "./onPostMessageSetAuthToken"; +import { OnPostMessageSetAuthToken } from "./OnPostMessageSetAuthToken"; let relayEnvironment: Environment; const source: RecordSource = new RecordSource(); @@ -17,16 +21,17 @@ beforeAll(() => { it("Sets auth token", () => { const token = "auth-token"; - const context = { + const context: Partial = { postMessage: { on: (name: string, cb: (token: string) => void) => { expect(name).toBe("setAuthToken"); cb(token); }, - }, + } as any, relayEnvironment, - localStorage: createInMemoryStorage(), + localStorage: createPromisifiedStorage(), + clearSession: noop, }; - onPostMessageSetAuthToken(context as any); + shallow(); expect(source.get(LOCAL_ID)!.authToken).toEqual(token); }); diff --git a/src/core/client/stream/listeners/OnPostMessageSetAuthToken.ts b/src/core/client/stream/listeners/OnPostMessageSetAuthToken.ts new file mode 100644 index 000000000..553e4fa5b --- /dev/null +++ b/src/core/client/stream/listeners/OnPostMessageSetAuthToken.ts @@ -0,0 +1,31 @@ +import { Component } from "react"; + +import { TalkContext, withContext } from "talk-framework/lib/bootstrap"; +import { commit as setAuthToken } from "talk-framework/mutations/SetAuthTokenMutation"; + +interface Props { + context: TalkContext; +} + +export class OnPostMessageSetAuthToken extends Component { + constructor(props: Props) { + super(props); + // Auth popup will use this to handle a successful login. + props.context.postMessage!.on("setAuthToken", (authToken: string) => { + setAuthToken( + this.props.context.relayEnvironment, + { authToken }, + this.props.context + ); + }); + } + + public render() { + return null; + } +} + +const enhanced = withContext(context => ({ context }))( + OnPostMessageSetAuthToken +); +export default enhanced; diff --git a/src/core/client/stream/listeners/onPymSetCommentID.spec.ts b/src/core/client/stream/listeners/OnPymSetCommentID.spec.tsx similarity index 77% rename from src/core/client/stream/listeners/onPymSetCommentID.spec.ts rename to src/core/client/stream/listeners/OnPymSetCommentID.spec.tsx index 6b5e67ea2..20132c1f7 100644 --- a/src/core/client/stream/listeners/onPymSetCommentID.spec.ts +++ b/src/core/client/stream/listeners/OnPymSetCommentID.spec.tsx @@ -1,9 +1,11 @@ +import { shallow } from "enzyme"; +import React from "react"; import { Environment, RecordSource } from "relay-runtime"; import { LOCAL_ID } from "talk-framework/lib/relay"; import { createRelayEnvironment } from "talk-framework/testHelpers"; -import onPymSetCommentID from "./onPymSetCommentID"; +import { OnPymSetCommentID } from "./OnPymSetCommentID"; let relayEnvironment: Environment; const source: RecordSource = new RecordSource(); @@ -16,30 +18,30 @@ beforeAll(() => { it("Sets comment id", () => { const id = "comment1-id"; - const context = { + const props = { pym: { onMessage: (eventName: string, cb: (id: string) => void) => { expect(eventName).toBe("setCommentID"); cb(id); }, - }, + } as any, relayEnvironment, }; - onPymSetCommentID(context as any); + shallow(); expect(source.get(LOCAL_ID)!.commentID).toEqual(id); }); it("Sets comment id to null when empty", () => { const id = ""; - const context = { + const props = { pym: { onMessage: (eventName: string, cb: (data: string) => void) => { expect(eventName).toBe("setCommentID"); cb(id); }, - }, + } as any, relayEnvironment, }; - onPymSetCommentID(context as any); + shallow(); expect(source.get(LOCAL_ID)!.commentID).toEqual(null); }); diff --git a/src/core/client/stream/listeners/OnPymSetCommentID.ts b/src/core/client/stream/listeners/OnPymSetCommentID.ts new file mode 100644 index 000000000..f9ca0c6f5 --- /dev/null +++ b/src/core/client/stream/listeners/OnPymSetCommentID.ts @@ -0,0 +1,39 @@ +import { Child } from "pym.js"; +import { Component } from "react"; +import { commitLocalUpdate } from "react-relay"; +import { Environment } from "relay-runtime"; + +import { withContext } from "talk-framework/lib/bootstrap"; +import { LOCAL_ID } from "talk-framework/lib/relay"; + +interface Props { + relayEnvironment: Environment; + pym: Child; +} + +export class OnPymSetCommentID extends Component { + constructor(props: Props) { + super(props); + + // Sets comment id through pym. + props.pym!.onMessage("setCommentID", raw => { + commitLocalUpdate(this.props.relayEnvironment, s => { + const id = raw || null; + if (s.get(LOCAL_ID)!.getValue("commentID") !== id) { + s.get(LOCAL_ID)!.setValue(id, "commentID"); + } + }); + }); + } + + public render() { + return null; + } +} + +const enhanced = withContext(({ relayEnvironment, pym }) => ({ + relayEnvironment, + pym, +}))(OnPymSetCommentID); + +export default enhanced; diff --git a/src/core/client/stream/listeners/index.ts b/src/core/client/stream/listeners/index.ts index 7901544e0..1c315d81f 100644 --- a/src/core/client/stream/listeners/index.ts +++ b/src/core/client/stream/listeners/index.ts @@ -1,5 +1,5 @@ -export { default as onPymSetCommentID } from "./onPymSetCommentID"; +export { default as OnPymSetCommentID } from "./OnPymSetCommentID"; export { - default as onPostMessageSetAuthToken, -} from "./onPostMessageSetAuthToken"; -export { default as onPostMessageAuthError } from "./onPostMessageAuthError"; + default as OnPostMessageSetAuthToken, +} from "./OnPostMessageSetAuthToken"; +export { default as OnPostMessageAuthError } from "./OnPostMessageAuthError"; diff --git a/src/core/client/stream/listeners/onPostMessageAuthError.ts b/src/core/client/stream/listeners/onPostMessageAuthError.ts deleted file mode 100644 index 668a4ba1f..000000000 --- a/src/core/client/stream/listeners/onPostMessageAuthError.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TalkContext } from "talk-framework/lib/bootstrap"; - -export default function onPostMessageSetAuthToken({ - postMessage, -}: TalkContext) { - // Auth popup will use this to send back errors during login. - postMessage!.on("authError", error => { - // tslint:disable-next-line:no-console - console.error(error); - }); -} diff --git a/src/core/client/stream/listeners/onPostMessageSetAuthToken.ts b/src/core/client/stream/listeners/onPostMessageSetAuthToken.ts deleted file mode 100644 index 767af51f7..000000000 --- a/src/core/client/stream/listeners/onPostMessageSetAuthToken.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TalkContext } from "talk-framework/lib/bootstrap"; - -import { commit as setAuthToken } from "talk-framework/mutations/SetAuthTokenMutation"; - -export default function onPostMessageSetAuthToken(ctx: TalkContext) { - const { relayEnvironment, postMessage } = ctx; - // Auth popup will use this to handle a successful login. - postMessage!.on("setAuthToken", (authToken: string) => { - setAuthToken(relayEnvironment, { authToken }, ctx); - }); -} diff --git a/src/core/client/stream/listeners/onPymSetCommentID.ts b/src/core/client/stream/listeners/onPymSetCommentID.ts deleted file mode 100644 index 4b5547173..000000000 --- a/src/core/client/stream/listeners/onPymSetCommentID.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { commitLocalUpdate } from "react-relay"; - -import { TalkContext } from "talk-framework/lib/bootstrap"; -import { LOCAL_ID } from "talk-framework/lib/relay"; - -export default function onPymSetCommentID({ - relayEnvironment, - pym, -}: TalkContext) { - // Sets comment id through pym. - pym!.onMessage("setCommentID", raw => { - commitLocalUpdate(relayEnvironment, s => { - const id = raw || null; - if (s.get(LOCAL_ID)!.getValue("commentID") !== id) { - s.get(LOCAL_ID)!.setValue(id, "commentID"); - } - }); - }); -} diff --git a/src/core/client/stream/local/__snapshots__/initLocalState.spec.ts.snap b/src/core/client/stream/local/__snapshots__/initLocalState.spec.ts.snap index b95cc19ce..c8dda11c7 100644 --- a/src/core/client/stream/local/__snapshots__/initLocalState.spec.ts.snap +++ b/src/core/client/stream/local/__snapshots__/initLocalState.spec.ts.snap @@ -13,7 +13,6 @@ exports[`init local state 1`] = ` \\"__id\\": \\"client:root.local\\", \\"__typename\\": \\"Local\\", \\"authToken\\": \\"\\", - \\"authRevision\\": 0, \\"network\\": { \\"__ref\\": \\"client:root.local.network\\" }, diff --git a/src/core/client/stream/local/initLocalState.ts b/src/core/client/stream/local/initLocalState.ts index 2d953e6ae..5b127755d 100644 --- a/src/core/client/stream/local/initLocalState.ts +++ b/src/core/client/stream/local/initLocalState.ts @@ -35,9 +35,6 @@ export default async function initLocalState( // Set auth token localRecord.setValue(authToken || "", "authToken"); - // Set initial auth revision, this is increment whenenver auth state might have changed. - localRecord.setValue(0, "authRevision"); - // Parse query params const query = qs.parse(location.search); diff --git a/src/core/client/stream/local/local.graphql b/src/core/client/stream/local/local.graphql index 9e04c476a..37615ec9d 100644 --- a/src/core/client/stream/local/local.graphql +++ b/src/core/client/stream/local/local.graphql @@ -26,10 +26,6 @@ type Local { commentID: String authPopup: AuthPopup! authToken: String - # Used to invalidate the `me` endpoint. - # This is incremented whenever the auth status - # might have changed. - authRevision: Int! } extend type Query { diff --git a/src/core/client/stream/queries/PermalinkViewQuery.tsx b/src/core/client/stream/queries/PermalinkViewQuery.tsx index fc098dafe..4d1ac4611 100644 --- a/src/core/client/stream/queries/PermalinkViewQuery.tsx +++ b/src/core/client/stream/queries/PermalinkViewQuery.tsx @@ -45,19 +45,12 @@ export const render = ({ }; const PermalinkViewQuery: StatelessComponent = ({ - local: { commentID, assetID, authRevision }, + local: { commentID, assetID }, }) => ( query={graphql` - query PermalinkViewQuery( - $commentID: ID! - $assetID: ID! - $authRevision: Int! - ) { - # authRevision is increment every time auth state has changed. - # This is basically a cache invalidation and causes relay - # to automatically update this query. - me(clientAuthRevision: $authRevision) { + query PermalinkViewQuery($commentID: ID!, $assetID: ID!) { + me { ...PermalinkViewContainer_me } asset(id: $assetID) { @@ -71,7 +64,6 @@ const PermalinkViewQuery: StatelessComponent = ({ variables={{ assetID: assetID!, commentID: commentID!, - authRevision, }} render={render} /> @@ -81,7 +73,6 @@ const enhanced = withLocalStateContainer( graphql` fragment PermalinkViewQueryLocal on Local { assetID - authRevision commentID } ` diff --git a/src/core/client/stream/queries/StreamQuery.tsx b/src/core/client/stream/queries/StreamQuery.tsx index b0f1f0c3f..ec61ec723 100644 --- a/src/core/client/stream/queries/StreamQuery.tsx +++ b/src/core/client/stream/queries/StreamQuery.tsx @@ -38,15 +38,12 @@ export const render = ({ }; const StreamQuery: StatelessComponent = ({ - local: { assetID, authRevision }, + local: { assetID }, }) => ( query={graphql` - query StreamQuery($assetID: ID!, $authRevision: Int!) { - # authRevision is increment every time auth state has changed. - # This is basically a cache invalidation and causes relay - # to automatically update this query. - me(clientAuthRevision: $authRevision) { + query StreamQuery($assetID: ID!) { + me { ...StreamContainer_me } asset(id: $assetID) { @@ -56,7 +53,6 @@ const StreamQuery: StatelessComponent = ({ `} variables={{ assetID: assetID!, - authRevision, }} render={render} /> @@ -66,7 +62,6 @@ const enhanced = withLocalStateContainer( graphql` fragment StreamQueryLocal on Local { assetID - authRevision } ` )(StreamQuery); diff --git a/src/core/client/stream/test/create.tsx b/src/core/client/stream/test/create.tsx index 49d885c66..2eecbc5a1 100644 --- a/src/core/client/stream/test/create.tsx +++ b/src/core/client/stream/test/create.tsx @@ -1,4 +1,6 @@ +import { EventEmitter2 } from "eventemitter2"; import { IResolvers } from "graphql-tools"; +import { noop } from "lodash"; import React from "react"; import TestRenderer from "react-test-renderer"; import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime"; @@ -30,7 +32,6 @@ export default function create(params: CreateParams) { logNetwork: params.logNetwork, resolvers: params.resolvers, initLocalState: (localRecord, source, env) => { - localRecord.setValue(0, "authRevision"); if (params.initLocalState) { params.initLocalState(localRecord, source, env); } @@ -46,6 +47,8 @@ export default function create(params: CreateParams) { postMessage: new PostMessageService(), browserInfo: { ios: false }, uuidGenerator: createUUIDGenerator(), + eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }), + clearSession: noop, }; const testRenderer = TestRenderer.create( diff --git a/src/core/client/stream/test/postComment.spec.tsx b/src/core/client/stream/test/postComment.spec.tsx index 658a9d036..78120589d 100644 --- a/src/core/client/stream/test/postComment.spec.tsx +++ b/src/core/client/stream/test/postComment.spec.tsx @@ -11,14 +11,8 @@ let testRenderer: ReactTestRenderer; beforeEach(() => { const resolvers = { Query: { - asset: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0]) - ), - me: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined, { clientAuthRevision: 0 }).returns(users[0]) - ), + asset: createSinonStub(s => s.throws(), s => s.returns(assets[0])), + me: createSinonStub(s => s.throws(), s => s.returns(users[0])), }, Mutation: { createComment: createSinonStub( diff --git a/src/core/client/stream/test/postReply.spec.tsx b/src/core/client/stream/test/postReply.spec.tsx index 3c64bc098..dd7f0eb6e 100644 --- a/src/core/client/stream/test/postReply.spec.tsx +++ b/src/core/client/stream/test/postReply.spec.tsx @@ -11,14 +11,8 @@ let testRenderer: ReactTestRenderer; beforeEach(() => { const resolvers = { Query: { - asset: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0]) - ), - me: createSinonStub( - s => s.throws(), - s => s.withArgs(undefined, { clientAuthRevision: 0 }).returns(users[0]) - ), + asset: createSinonStub(s => s.throws(), s => s.returns(assets[0])), + me: createSinonStub(s => s.throws(), s => s.returns(users[0])), }, Mutation: { createComment: createSinonStub( diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index dc689f403..3dcdb0894 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -809,12 +809,8 @@ type Query { """ me is the current logged in User. - - clientAuthRevision is an implementation detail that is only - used on the client to invalidate the cache. - TODO: This should move to a client side directive if this becomes possible. """ - me(clientAuthRevision: Int): User + me: User """ settings is the Settings for a given Tenant. From 6f367a00782e8a9bfffb637d00bd19464735df67 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 12 Sep 2018 13:13:09 -0300 Subject: [PATCH 32/53] changes --- src/core/client/ui/components/Message/Message.css | 8 ++------ .../ValidationMessage/ValidationMessage.tsx | 13 +++++++------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/core/client/ui/components/Message/Message.css b/src/core/client/ui/components/Message/Message.css index 57dbe34bf..5811ad377 100644 --- a/src/core/client/ui/components/Message/Message.css +++ b/src/core/client/ui/components/Message/Message.css @@ -4,12 +4,12 @@ display: inline-flex; justify-content: flex-start; align-items: center; - padding: calc(0.8 * var(--spacing-unit)) var(--spacing-unit); + padding: calc(0.5 * var(--spacing-unit)) var(--spacing-unit); box-sizing: border-box; border-radius: var(--round-corners); border-width: 1px; border-style: solid; - border-left-width: calc(0.8 * var(--spacing-unit)); + border-left-width: calc(0.5 * var(--spacing-unit)); } .colorGrey { @@ -28,7 +28,3 @@ display: flex; width: 100%; } - -.icon { - margin-right: calc(0.8 * var(--spacing-unit)); -} diff --git a/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx b/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx index 9a4819178..8d1343f8b 100644 --- a/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx +++ b/src/core/client/ui/components/ValidationMessage/ValidationMessage.tsx @@ -15,17 +15,18 @@ export interface ValidationMessageProps { * If set renders a full width message */ fullWidth?: boolean; - /* - * Name of the icon, if not provided it will default to warning icon - */ - icon?: string; } const ValidationMessage: StatelessComponent = props => { - const { className, fullWidth, children, icon, ...rest } = props; + const { className, fullWidth, children, ...rest } = props; return ( - + warning {children} From e6f8b7623ba21929945771cbf7d206d844b04c08 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 12 Sep 2018 15:10:14 -0300 Subject: [PATCH 33/53] Adding docs and changes --- src/core/client/ui/components/Tabs/Tab.css | 4 +- src/core/client/ui/components/Tabs/Tab.mdx | 25 --------- src/core/client/ui/components/Tabs/Tab.tsx | 10 ++-- src/core/client/ui/components/Tabs/TabBar.css | 2 +- src/core/client/ui/components/Tabs/TabBar.mdx | 51 ------------------- .../client/ui/components/Tabs/TabContent.mdx | 23 --------- .../client/ui/components/Tabs/TabPane.mdx | 23 --------- .../client/ui/components/Tabs}/tabs.mdx | 0 8 files changed, 8 insertions(+), 130 deletions(-) delete mode 100644 src/core/client/ui/components/Tabs/Tab.mdx delete mode 100644 src/core/client/ui/components/Tabs/TabBar.mdx delete mode 100644 src/core/client/ui/components/Tabs/TabContent.mdx delete mode 100644 src/core/client/ui/components/Tabs/TabPane.mdx rename src/{docs => core/client/ui/components/Tabs}/tabs.mdx (100%) diff --git a/src/core/client/ui/components/Tabs/Tab.css b/src/core/client/ui/components/Tabs/Tab.css index 9650b8a95..ca7ac0f66 100644 --- a/src/core/client/ui/components/Tabs/Tab.css +++ b/src/core/client/ui/components/Tabs/Tab.css @@ -13,7 +13,7 @@ } .root.primary { - background: #f2f2f2; + background: var(--palette-grey-lightest); color: var(--palette-grey-main); border: 1px solid var(--palette-grey-main); border-left-width: 0; @@ -32,7 +32,7 @@ background-color: var(--palette-common-white); color: var(--palette-common-black); border-bottom-color: var(--palette-common-white); - border-top-width: 5px; + border-top-width: calc(0.5 * var(--spacing-unit)); border-top-color: var(--palette-primary-main); } } diff --git a/src/core/client/ui/components/Tabs/Tab.mdx b/src/core/client/ui/components/Tabs/Tab.mdx deleted file mode 100644 index f81399cfc..000000000 --- a/src/core/client/ui/components/Tabs/Tab.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: Tab -menu: UI Kit ---- - -import { Playground, PropsTable } from 'docz' -import HorizontalGutter from '../HorizontalGutter' - - -import TabBar from './TabBar' -import Tab from './Tab' - -# Tab -`Tab` component is to be used within the `TabBar` component. This renders a Tab inside the Tab Bar.s - -## Basic Usage - - - - Tab one - Active Tab - Tab Three - - - diff --git a/src/core/client/ui/components/Tabs/Tab.tsx b/src/core/client/ui/components/Tabs/Tab.tsx index 9afe73023..e0fd95eb8 100644 --- a/src/core/client/ui/components/Tabs/Tab.tsx +++ b/src/core/client/ui/components/Tabs/Tab.tsx @@ -21,9 +21,9 @@ export interface TabProps { */ active?: boolean; /** - * Color style variant + * Style variant */ - color?: "primary" | "secondary"; + variant?: "primary" | "secondary"; /** * Action taken on tab click */ @@ -38,13 +38,13 @@ class Tab extends React.Component { }; public render() { - const { className, classes, children, tabId, active, color } = this.props; + const { className, classes, children, tabId, active, variant } = this.props; const rootClassName = cn( classes.root, { - [classes.primary]: color === "primary", - [classes.secondary]: color === "secondary", + [classes.primary]: variant === "primary", + [classes.secondary]: variant === "secondary", [classes.active]: active, }, className diff --git a/src/core/client/ui/components/Tabs/TabBar.css b/src/core/client/ui/components/Tabs/TabBar.css index cb75fbb27..7ea4a5ac3 100644 --- a/src/core/client/ui/components/Tabs/TabBar.css +++ b/src/core/client/ui/components/Tabs/TabBar.css @@ -9,5 +9,5 @@ } .secondary { - border-bottom: 1px solid #e0e0e0; + border-bottom: 1px solid var(--palette-divider); } diff --git a/src/core/client/ui/components/Tabs/TabBar.mdx b/src/core/client/ui/components/Tabs/TabBar.mdx deleted file mode 100644 index 14fc68a3b..000000000 --- a/src/core/client/ui/components/Tabs/TabBar.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: TabBar -menu: UI Kit ---- - -import { Playground, PropsTable } from 'docz' -import HorizontalGutter from '../HorizontalGutter' - - -import TabBar from './TabBar' -import Tab from './Tab' - -# TabBar - -## Basic Use - - - - - Tab one - Active Tab - Tab Three - - - - Tab one - Active Tab - Tab Three - - - - Tab one - Active Tab - Tab Three - - - - - -## Secondary Color - - - - - Tab one - Active Tab - Tab Three - - - - diff --git a/src/core/client/ui/components/Tabs/TabContent.mdx b/src/core/client/ui/components/Tabs/TabContent.mdx deleted file mode 100644 index 1646887a5..000000000 --- a/src/core/client/ui/components/Tabs/TabContent.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: TabContent -menu: UI Kit ---- - -import { Playground, PropsTable } from 'docz' -import HorizontalGutter from '../HorizontalGutter' - -import TabPane from './TabPane' -import TabContent from './TabContent' - -# TabContent - -## Basic Use - - - - Hola One - Hola Two - Hola Three - - - diff --git a/src/core/client/ui/components/Tabs/TabPane.mdx b/src/core/client/ui/components/Tabs/TabPane.mdx deleted file mode 100644 index 36cb915f0..000000000 --- a/src/core/client/ui/components/Tabs/TabPane.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: TabPane -menu: UI Kit ---- - -import { Playground, PropsTable } from 'docz' -import HorizontalGutter from '../HorizontalGutter' - -import TabPane from './TabPane' -import TabContent from './TabContent' - -# TabPane - -## Basic Use - - - - Hola One - Hola Two - Hola Three - - - diff --git a/src/docs/tabs.mdx b/src/core/client/ui/components/Tabs/tabs.mdx similarity index 100% rename from src/docs/tabs.mdx rename to src/core/client/ui/components/Tabs/tabs.mdx From 32e56e805c60107e35752b2ee4a2adf2b053169f Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 12 Sep 2018 20:35:07 +0200 Subject: [PATCH 34/53] Adapt integration test --- src/core/client/stream/test/editComment.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/client/stream/test/editComment.spec.tsx b/src/core/client/stream/test/editComment.spec.tsx index de22f2384..a562c3b38 100644 --- a/src/core/client/stream/test/editComment.spec.tsx +++ b/src/core/client/stream/test/editComment.spec.tsx @@ -15,7 +15,7 @@ function createTestRenderer() { ), me: createSinonStub( s => s.throws(), - s => s.withArgs(undefined, { clientAuthRevision: 0 }).returns(users[0]) + s => s.withArgs(undefined).returns(users[0]) ), }, Mutation: { From 2c8bdc6c9ac2a837ff0df78868cf42854ee309a9 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 12 Sep 2018 15:40:04 -0300 Subject: [PATCH 35/53] updated status --- .../test/__snapshots__/signIn.spec.tsx.snap | 12 +- .../test/__snapshots__/signUp.spec.tsx.snap | 82 +- .../test/__snapshots__/loadMore.spec.tsx.snap | 281 +-- .../__snapshots__/permalinkView.spec.tsx.snap | 74 +- ...permalinkViewCommentNotFound.spec.tsx.snap | 26 +- .../__snapshots__/postComment.spec.tsx.snap | 1018 +---------- .../__snapshots__/postReply.spec.tsx.snap | 1579 +---------------- .../__snapshots__/renderReplies.spec.tsx.snap | 377 +--- .../__snapshots__/renderStream.spec.tsx.snap | 265 +-- .../showAllReplies.spec.tsx.snap | 287 +-- 10 files changed, 105 insertions(+), 3896 deletions(-) diff --git a/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap b/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap index c21b65546..898850de7 100644 --- a/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap +++ b/src/core/client/auth/test/__snapshots__/signIn.spec.tsx.snap @@ -34,7 +34,7 @@ exports[`accepts correct password 1`] = ` value="" />