From e0a8f2e067f8dc871980e6ccbd7aa7e739f1a052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Wed, 25 Jul 2018 17:33:29 -0300 Subject: [PATCH 1/4] [next] ClickOutside Component (#1757) * working on the tests Merge branch 'next' of github.com:coralproject/talk into clickoutside * 'next' of github.com:coralproject/talk: (36 commits) [next] Implement AriaInfo (#1756) e render props for TrapFocus (#1755) [ToggleShow] Fix typo and better description (#1753) Fix wrong hour pluralization Implement button active state Styling fixes Fix Playground Commit snapshots Harmonize typography Fix Flex margin bug and add examples Update package-lock.json Use explicit export Implement ButtonIcon component Remove wrong ref prop Implement new button api Read env in watchers.ts updated tests Update ToggleShow.spec.tsx ToggleShow Component Fix CI thanks to wyatt ... docz added Added simulant Added simulant unnused Merge branch 'clickoutside' of github.com:coralproject/talk into clickoutside * 'clickoutside' of github.com:coralproject/talk: (40 commits) Added simulant Added simulant docz added [next] Implement AriaInfo (#1756) e render props for TrapFocus (#1755) [ToggleShow] Fix typo and better description (#1753) Fix wrong hour pluralization Implement button active state Styling fixes Fix Playground Commit snapshots Harmonize typography Fix Flex margin bug and add examples Update package-lock.json Use explicit export Implement ButtonIcon component Remove wrong ref prop Implement new button api Read env in watchers.ts working on the tests ... unnused Merge branch 'clickoutside' of github.com:coralproject/talk into clickoutside * 'clickoutside' of github.com:coralproject/talk: (42 commits) unnused working on the tests Added simulant Added simulant docz added [next] Implement AriaInfo (#1756) e render props for TrapFocus (#1755) [ToggleShow] Fix typo and better description (#1753) Fix wrong hour pluralization Implement button active state Styling fixes Fix Playground Commit snapshots Harmonize typography Fix Flex margin bug and add examples Update package-lock.json Use explicit export Implement ButtonIcon component Remove wrong ref prop Implement new button api ... Merge branch 'clickoutside' of github.com:coralproject/talk into clickoutside * 'clickoutside' of github.com:coralproject/talk: (42 commits) unnused working on the tests Added simulant Added simulant docz added [next] Implement AriaInfo (#1756) e render props for TrapFocus (#1755) [ToggleShow] Fix typo and better description (#1753) Fix wrong hour pluralization Implement button active state Styling fixes Fix Playground Commit snapshots Harmonize typography Fix Flex margin bug and add examples Update package-lock.json Use explicit export Implement ButtonIcon component Remove wrong ref prop Implement new button api ... Merge branch 'clickoutside' of github.com:coralproject/talk into clickoutside * 'clickoutside' of github.com:coralproject/talk: (43 commits) working on the tests unnused working on the tests Added simulant Added simulant docz added [next] Implement AriaInfo (#1756) e render props for TrapFocus (#1755) [ToggleShow] Fix typo and better description (#1753) Fix wrong hour pluralization Implement button active state Styling fixes Fix Playground Commit snapshots Harmonize typography Fix Flex margin bug and add examples Update package-lock.json Use explicit export Implement ButtonIcon component Remove wrong ref prop ... * Implement passing tests * Use html as snapshot * Refactor and simplify test * Remove unused import * ts * simulant moved to devdeps --- package-lock.json | 5 ++ package.json | 1 + .../components/ClickOutside/ClickOutside.mdx | 27 ++++++++ .../ClickOutside/ClickOutside.spec.tsx | 62 +++++++++++++++++++ .../components/ClickOutside/ClickOutside.tsx | 33 ++++++++++ .../__snapshots__/ClickOutside.spec.tsx.snap | 3 + src/types/simulant.d.ts | 16 +++++ 7 files changed, 147 insertions(+) create mode 100644 src/core/client/ui/components/ClickOutside/ClickOutside.mdx create mode 100644 src/core/client/ui/components/ClickOutside/ClickOutside.spec.tsx create mode 100644 src/core/client/ui/components/ClickOutside/ClickOutside.tsx create mode 100644 src/core/client/ui/components/ClickOutside/__snapshots__/ClickOutside.spec.tsx.snap create mode 100644 src/types/simulant.d.ts diff --git a/package-lock.json b/package-lock.json index 4f66c4e68..7517b6b8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20221,6 +20221,11 @@ } } }, + "simulant": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simulant/-/simulant-0.2.2.tgz", + "integrity": "sha1-8bzlJxK2p6DaON392n6DsgsdoB4=" + }, "sinon": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.1.3.tgz", diff --git a/package.json b/package.json index b3f29fe42..61a456398 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,7 @@ "relay-runtime": "github:coralproject/patched#relay-runtime", "relay-test-utils": "github:coralproject/patched#relay-test-utils", "sane": "^2.5.2", + "simulant": "^0.2.2", "sinon": "^6.1.3", "style-loader": "^0.21.0", "ts-jest": "^23.0.0", diff --git a/src/core/client/ui/components/ClickOutside/ClickOutside.mdx b/src/core/client/ui/components/ClickOutside/ClickOutside.mdx new file mode 100644 index 000000000..0d8126b5d --- /dev/null +++ b/src/core/client/ui/components/ClickOutside/ClickOutside.mdx @@ -0,0 +1,27 @@ +--- +name: ClickOutside +menu: UI Kit +--- + +import { Playground, PropsTable } from 'docz' +import ClickOutside from './ClickOutside' +import Button from '../Button/Button' + +# ClickOutside + +A Component to handle click events outside the children components. + +## Basic usage +Wrap a Component with `` and pass a function to `onClickOutside`. This function will trigger when the user +clicks outside the component. + +### Test +Click the blue background. It should trigger an alert. Nothing should happen if you click the button. + + +
+ alert('You clicked outside!')}> + + +
+
diff --git a/src/core/client/ui/components/ClickOutside/ClickOutside.spec.tsx b/src/core/client/ui/components/ClickOutside/ClickOutside.spec.tsx new file mode 100644 index 000000000..fd5ba04b8 --- /dev/null +++ b/src/core/client/ui/components/ClickOutside/ClickOutside.spec.tsx @@ -0,0 +1,62 @@ +import { mount } from "enzyme"; +import React from "react"; +import simulant from "simulant"; +import sinon from "sinon"; + +import ClickOutside from "./ClickOutside"; + +let container: HTMLElement; + +beforeAll(() => { + container = document.createElement("div"); + document.body.appendChild(container); +}); + +afterAll(() => { + document.body.removeChild(container); +}); + +it("should render correctly", () => { + const noop = () => null; + const wrapper = mount( + + Hello World! + + ); + expect(wrapper.html()).toMatchSnapshot(); + wrapper.unmount(); +}); + +it("should detect click outside", () => { + const onClickOutside = sinon.spy(); + const wrapper = mount( + + Hello World! + , + { + attachTo: container, + } + ); + simulant.fire(container, "click"); + + expect(onClickOutside.calledOnce).toEqual(true); + wrapper.unmount(); +}); + +it("should ignore click inside", () => { + const onClickOutside = sinon.spy(); + const wrapper = mount( + + + , + { + attachTo: container, + } + ); + + const target = document.getElementById("click-outside-test-button")!; + simulant.fire(target, "click"); + + expect(onClickOutside.calledOnce).toEqual(false); + wrapper.unmount(); +}); diff --git a/src/core/client/ui/components/ClickOutside/ClickOutside.tsx b/src/core/client/ui/components/ClickOutside/ClickOutside.tsx new file mode 100644 index 000000000..cf13762f0 --- /dev/null +++ b/src/core/client/ui/components/ClickOutside/ClickOutside.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { findDOMNode } from "react-dom"; + +interface Props { + onClickOutside: () => void; + children: React.ReactNode; +} + +class ClickOutside extends React.Component { + public domNode: Element | null = null; + + public handleClick = (e: MouseEvent) => { + const { onClickOutside } = this.props; + if (!e || !this.domNode!.contains(e.target as HTMLInputElement)) { + // tslint:disable-next-line:no-unused-expression + onClickOutside && onClickOutside(); + } + }; + + public componentDidMount() { + this.domNode = findDOMNode(this) as Element; + document.addEventListener("click", this.handleClick, true); + } + + public componentWillUnmount() { + document.removeEventListener("click", this.handleClick, true); + } + + public render() { + return this.props.children; + } +} +export default ClickOutside; diff --git a/src/core/client/ui/components/ClickOutside/__snapshots__/ClickOutside.spec.tsx.snap b/src/core/client/ui/components/ClickOutside/__snapshots__/ClickOutside.spec.tsx.snap new file mode 100644 index 000000000..883a02a51 --- /dev/null +++ b/src/core/client/ui/components/ClickOutside/__snapshots__/ClickOutside.spec.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = `"Hello World!"`; diff --git a/src/types/simulant.d.ts b/src/types/simulant.d.ts new file mode 100644 index 000000000..0537e0911 --- /dev/null +++ b/src/types/simulant.d.ts @@ -0,0 +1,16 @@ +declare module "simulant" { + type SimulantEvent = {}; + + interface Simulant { + (event: string, extendedParams: Record): SimulantEvent; + fire( + target: HTMLElement, + event: string | SimulantEvent, + extendedParams?: Record + ): void; + polyfill(): void; + } + + const simulant: Simulant; + export default simulant; +} From b787f260f28a3b7262847e7730d22699fa180026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Wed, 25 Jul 2018 17:33:49 -0300 Subject: [PATCH 2/4] Removing old implementation of attachment --- .../ui/components/Attachment/Attachment.css | 2 - .../ui/components/Attachment/Attachment.tsx | 62 ------------------- .../client/ui/components/Attachment/index.ts | 2 - 3 files changed, 66 deletions(-) delete mode 100644 src/core/client/ui/components/Attachment/Attachment.css delete mode 100644 src/core/client/ui/components/Attachment/Attachment.tsx delete mode 100644 src/core/client/ui/components/Attachment/index.ts diff --git a/src/core/client/ui/components/Attachment/Attachment.css b/src/core/client/ui/components/Attachment/Attachment.css deleted file mode 100644 index c3a2af639..000000000 --- a/src/core/client/ui/components/Attachment/Attachment.css +++ /dev/null @@ -1,2 +0,0 @@ -.root { -} diff --git a/src/core/client/ui/components/Attachment/Attachment.tsx b/src/core/client/ui/components/Attachment/Attachment.tsx deleted file mode 100644 index 549787707..000000000 --- a/src/core/client/ui/components/Attachment/Attachment.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { CSSProperties } from "react"; -import { Manager, Popper, Reference, RefHandler } from "react-popper"; - -interface RenderProps { - ref: RefHandler; - style?: CSSProperties; -} - -interface InnerProps { - body: React.ReactElement | null; - children: (props: RenderProps) => React.ReactElement; - className?: string; - placement?: - | "auto-start" - | "auto" - | "auto-end" - | "top-start" - | "top" - | "top-end" - | "right-start" - | "right" - | "right-end" - | "bottom-end" - | "bottom" - | "bottom-start" - | "left-end" - | "left" - | "left-start"; -} - -interface Props { - ref: any; - style: CSSProperties; -} - -class Attachment extends React.Component { - public render() { - const { children, body, placement = "top" } = this.props; - return ( - - {(props: Props) => children({ ref: props.ref })} - - {(props: Props) => - body - ? React.cloneElement(body, { - innerRef: props.ref, - style: props.style, - }) - : null - } - - - ); - } -} - -export default Attachment; diff --git a/src/core/client/ui/components/Attachment/index.ts b/src/core/client/ui/components/Attachment/index.ts deleted file mode 100644 index b3b8d5f4a..000000000 --- a/src/core/client/ui/components/Attachment/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./Attachment"; -export { default } from "./Attachment"; From f0e0051c972723a04bfbadd954ece0839ba69b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Wed, 25 Jul 2018 18:57:43 -0300 Subject: [PATCH 3/4] Tiny refactor --- .../stream/components/Comment/Comment.tsx | 2 +- .../{ => Permalink}/PermalinkView.tsx | 2 +- .../PermalinkContainer.tsx | 2 +- .../containers/PermalinkViewContainer.tsx | 9 ++--- src/core/client/stream/queries/AppQuery.tsx | 36 +++++++++---------- 5 files changed, 23 insertions(+), 28 deletions(-) rename src/core/client/stream/components/{ => Permalink}/PermalinkView.tsx (88%) rename src/core/client/stream/{components/Permalink => containers}/PermalinkContainer.tsx (91%) diff --git a/src/core/client/stream/components/Comment/Comment.tsx b/src/core/client/stream/components/Comment/Comment.tsx index 8f8553fce..10a6c9659 100644 --- a/src/core/client/stream/components/Comment/Comment.tsx +++ b/src/core/client/stream/components/Comment/Comment.tsx @@ -2,7 +2,7 @@ import React from "react"; import { StatelessComponent } from "react"; import { Typography } from "talk-ui/components"; -import PermalinkContainer from "../Permalink/PermalinkContainer"; +import PermalinkContainer from "../../containers/PermalinkContainer"; import Timestamp from "./Timestamp"; import TopBar from "./TopBar"; import Username from "./Username"; diff --git a/src/core/client/stream/components/PermalinkView.tsx b/src/core/client/stream/components/Permalink/PermalinkView.tsx similarity index 88% rename from src/core/client/stream/components/PermalinkView.tsx rename to src/core/client/stream/components/Permalink/PermalinkView.tsx index 889c8c28a..7e4a29177 100644 --- a/src/core/client/stream/components/PermalinkView.tsx +++ b/src/core/client/stream/components/Permalink/PermalinkView.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { StatelessComponent } from "react"; import { Flex } from "talk-ui/components"; -import CommentContainer from "../containers/CommentContainer"; +import CommentContainer from "../../containers/CommentContainer"; export interface InnerProps { comment: {} | null; diff --git a/src/core/client/stream/components/Permalink/PermalinkContainer.tsx b/src/core/client/stream/containers/PermalinkContainer.tsx similarity index 91% rename from src/core/client/stream/components/Permalink/PermalinkContainer.tsx rename to src/core/client/stream/containers/PermalinkContainer.tsx index 4364a8e0b..46a646600 100644 --- a/src/core/client/stream/components/Permalink/PermalinkContainer.tsx +++ b/src/core/client/stream/containers/PermalinkContainer.tsx @@ -3,7 +3,7 @@ import { graphql } from "react-relay"; import { withLocalStateContainer } from "talk-framework/lib/relay"; import { AppQueryLocal as Local } from "talk-stream/__generated__/AppQueryLocal.graphql"; -import Permalink from "./Permalink"; +import Permalink from "../components/Permalink/Permalink"; interface InnerProps { local: Local; diff --git a/src/core/client/stream/containers/PermalinkViewContainer.tsx b/src/core/client/stream/containers/PermalinkViewContainer.tsx index 6e0545383..a2c6216f8 100644 --- a/src/core/client/stream/containers/PermalinkViewContainer.tsx +++ b/src/core/client/stream/containers/PermalinkViewContainer.tsx @@ -3,7 +3,7 @@ import { graphql } from "react-relay"; import { withFragmentContainer } from "talk-framework/lib/relay"; import { PropTypesOf } from "talk-framework/types"; import { PermalinkViewContainerQuery as Data } from "talk-stream/__generated__/PermalinkViewContainerQuery.graphql"; -import PermalinkView from "../components/PermalinkView"; +import PermalinkView from "../components/Permalink/PermalinkView"; interface InnerProps { data: Data; @@ -18,12 +18,7 @@ const enhanced = withFragmentContainer<{ data: Data }>({ fragment PermalinkViewContainerQuery on Query @argumentDefinitions(commentID: { type: "ID!" }) { comment(id: $commentID) { - id - author { - username - } - body - createdAt + ...CommentContainer } } `, diff --git a/src/core/client/stream/queries/AppQuery.tsx b/src/core/client/stream/queries/AppQuery.tsx index 1cb252899..07601ed1d 100644 --- a/src/core/client/stream/queries/AppQuery.tsx +++ b/src/core/client/stream/queries/AppQuery.tsx @@ -20,9 +20,25 @@ interface InnerProps { local: Local; } +interface WrappedProps { + data: any; +} + // TODO (bc) refactor this into another component. break down the needs of each component. // (careful porting QueryRenderer into another stateless component) +export const renderWrapper = ( + WrappedComponent: React.ComponentType +) => ({ error, props }: ReadyState) => { + if (error) { + return
{error.message}
; + } + if (props) { + return ; + } + return
Loading
; +}; + const AppQuery: StatelessComponent = ({ local: { commentID, assetID }, }) => { @@ -37,15 +53,7 @@ const AppQuery: StatelessComponent = ({ variables={{ commentID, }} - render={({ error, props }: ReadyState) => { - if (error) { - return
{error.message}
; - } - if (props) { - return ; - } - return
Loading
; - }} + render={renderWrapper(PermalinkViewContainer)} /> ); } @@ -60,15 +68,7 @@ const AppQuery: StatelessComponent = ({ variables={{ assetID, }} - render={({ error, props }: ReadyState) => { - if (error) { - return
{error.message}
; - } - if (props) { - return ; - } - return
Loading
; - }} + render={renderWrapper(AppContainer)} /> ); }; From ad4fcfd002c7a15b79e870840b7109ebb1e837b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Wed, 25 Jul 2018 19:24:33 -0300 Subject: [PATCH 4/4] Support for ClickOutside --- .../components/Permalink/PermalinkPopover.tsx | 40 ++++++++++--------- .../components/ClickOutside/ClickOutside.tsx | 5 ++- .../ui/components/ClickOutside/index.ts | 1 + .../client/ui/components/Popover/Popover.tsx | 5 +-- src/core/client/ui/components/index.ts | 1 + 5 files changed, 27 insertions(+), 25 deletions(-) create mode 100644 src/core/client/ui/components/ClickOutside/index.ts diff --git a/src/core/client/stream/components/Permalink/PermalinkPopover.tsx b/src/core/client/stream/components/Permalink/PermalinkPopover.tsx index bb83aeb74..61a2d3ba4 100644 --- a/src/core/client/stream/components/Permalink/PermalinkPopover.tsx +++ b/src/core/client/stream/components/Permalink/PermalinkPopover.tsx @@ -2,14 +2,14 @@ import { Localized } from "fluent-react/compat"; import React, { CSSProperties } from "react"; import CopyToClipboard from "react-copy-to-clipboard"; import { RefHandler } from "react-popper"; -import { Button, Flex, TextField } from "talk-ui/components"; +import { Button, ClickOutside, Flex, TextField } from "talk-ui/components"; import * as styles from "./PermalinkPopover.css"; interface InnerProps { permalinkUrl: string; style?: CSSProperties; forwardRef?: RefHandler; - toggleVisibility?: () => any; + toggleVisibility: () => void; } interface State { @@ -35,25 +35,27 @@ class PermalinkPopover extends React.Component { }; public render() { - const { permalinkUrl } = this.props; + const { permalinkUrl, toggleVisibility } = this.props; const { copied } = this.state; return ( - - - - - - + + + + + + + + ); } } diff --git a/src/core/client/ui/components/ClickOutside/ClickOutside.tsx b/src/core/client/ui/components/ClickOutside/ClickOutside.tsx index cf13762f0..a113c9776 100644 --- a/src/core/client/ui/components/ClickOutside/ClickOutside.tsx +++ b/src/core/client/ui/components/ClickOutside/ClickOutside.tsx @@ -1,12 +1,12 @@ import React from "react"; import { findDOMNode } from "react-dom"; -interface Props { +export interface ClickOutsideProps { onClickOutside: () => void; children: React.ReactNode; } -class ClickOutside extends React.Component { +export class ClickOutside extends React.Component { public domNode: Element | null = null; public handleClick = (e: MouseEvent) => { @@ -30,4 +30,5 @@ class ClickOutside extends React.Component { return this.props.children; } } + export default ClickOutside; diff --git a/src/core/client/ui/components/ClickOutside/index.ts b/src/core/client/ui/components/ClickOutside/index.ts new file mode 100644 index 000000000..f3f59adad --- /dev/null +++ b/src/core/client/ui/components/ClickOutside/index.ts @@ -0,0 +1 @@ +export { default, ClickOutside } from "./ClickOutside"; diff --git a/src/core/client/ui/components/Popover/Popover.tsx b/src/core/client/ui/components/Popover/Popover.tsx index e318810d1..11d37ddd3 100644 --- a/src/core/client/ui/components/Popover/Popover.tsx +++ b/src/core/client/ui/components/Popover/Popover.tsx @@ -42,7 +42,7 @@ interface Props { } interface RenderProps { - toggleVisibility?: () => void; + toggleVisibility: () => void; forwardRef?: RefHandler; } @@ -115,8 +115,6 @@ class Popover extends React.Component { aria-hidden={!visible} > {description} - - {/* */}
{ forwardRef: props.ref, })}
- {/*
*/} ) } diff --git a/src/core/client/ui/components/index.ts b/src/core/client/ui/components/index.ts index 2a52122ec..0b2032cb5 100644 --- a/src/core/client/ui/components/index.ts +++ b/src/core/client/ui/components/index.ts @@ -8,3 +8,4 @@ export { default as UIContext, UIContextProps } from "./UIContext"; export { default as Flex } from "./Flex"; export { default as MatchMedia } from "./MatchMedia"; export { default as TrapFocus } from "./TrapFocus"; +export { default as ClickOutside } from "./ClickOutside";