Removing old implementation of attachment

Tiny refactor

Merge branch 'next' of github.com:coralproject/talk into permalink

* 'next' of github.com:coralproject/talk:
  [next] ClickOutside Component (#1757)

Support for ClickOutside
This commit is contained in:
Belén Curcio
2018-07-25 19:44:25 -03:00
parent 906940f152
commit 8b56e408f8
19 changed files with 195 additions and 117 deletions
+5
View File
@@ -20407,6 +20407,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",
+1
View File
@@ -150,6 +150,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",
@@ -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";
@@ -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<InnerProps> {
};
public render() {
const { permalinkUrl } = this.props;
const { permalinkUrl, toggleVisibility } = this.props;
const { copied } = this.state;
return (
<Flex>
<TextField defaultValue={permalinkUrl} className={styles.textField} />
<CopyToClipboard text={permalinkUrl} onCopy={this.onCopy}>
<Button color="primary" variant="filled">
{copied ? (
<Localized id="comments-permalink-copied">
<span>Copied!</span>
</Localized>
) : (
<Localized id="comments-permalink-copy">
<span>Copy</span>
</Localized>
)}
</Button>
</CopyToClipboard>
</Flex>
<ClickOutside onClickOutside={toggleVisibility}>
<Flex>
<TextField defaultValue={permalinkUrl} className={styles.textField} />
<CopyToClipboard text={permalinkUrl} onCopy={this.onCopy}>
<Button color="primary" variant="filled">
{copied ? (
<Localized id="comments-permalink-copied">
<span>Copied!</span>
</Localized>
) : (
<Localized id="comments-permalink-copy">
<span>Copy</span>
</Localized>
)}
</Button>
</CopyToClipboard>
</Flex>
</ClickOutside>
);
}
}
@@ -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;
@@ -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;
@@ -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
}
}
`,
+18 -18
View File
@@ -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<WrappedProps>
) => ({ error, props }: ReadyState<AppQueryResponse>) => {
if (error) {
return <div>{error.message}</div>;
}
if (props) {
return <WrappedComponent data={props} />;
}
return <div>Loading</div>;
};
const AppQuery: StatelessComponent<InnerProps> = ({
local: { commentID, assetID },
}) => {
@@ -37,15 +53,7 @@ const AppQuery: StatelessComponent<InnerProps> = ({
variables={{
commentID,
}}
render={({ error, props }: ReadyState<AppQueryResponse>) => {
if (error) {
return <div>{error.message}</div>;
}
if (props) {
return <PermalinkViewContainer data={props} />;
}
return <div>Loading</div>;
}}
render={renderWrapper(PermalinkViewContainer)}
/>
);
}
@@ -60,15 +68,7 @@ const AppQuery: StatelessComponent<InnerProps> = ({
variables={{
assetID,
}}
render={({ error, props }: ReadyState<AppQueryResponse>) => {
if (error) {
return <div>{error.message}</div>;
}
if (props) {
return <AppContainer data={props} />;
}
return <div>Loading</div>;
}}
render={renderWrapper(AppContainer)}
/>
);
};
@@ -1,2 +0,0 @@
.root {
}
@@ -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<any> | null;
children: (props: RenderProps) => React.ReactElement<any>;
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<InnerProps> {
public render() {
const { children, body, placement = "top" } = this.props;
return (
<Manager>
<Reference>{(props: Props) => children({ ref: props.ref })}</Reference>
<Popper
placement={placement}
modifiers={{ preventOverflow: { enabled: false } }}
eventsEnabled
positionFixed={false}
>
{(props: Props) =>
body
? React.cloneElement(body, {
innerRef: props.ref,
style: props.style,
})
: null
}
</Popper>
</Manager>
);
}
}
export default Attachment;
@@ -1,2 +0,0 @@
export * from "./Attachment";
export { default } from "./Attachment";
@@ -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 `<ClickOutside>` 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.
<Playground>
<div style={{background: 'blue', padding: '10px'}}>
<ClickOutside onClickOutside={() => alert('You clicked outside!')}>
<Button variant="filled">Push Me</Button>
</ClickOutside>
</div>
</Playground>
@@ -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(
<ClickOutside onClickOutside={noop}>
<span>Hello World!</span>
</ClickOutside>
);
expect(wrapper.html()).toMatchSnapshot();
wrapper.unmount();
});
it("should detect click outside", () => {
const onClickOutside = sinon.spy();
const wrapper = mount(
<ClickOutside onClickOutside={onClickOutside}>
<span>Hello World!</span>
</ClickOutside>,
{
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(
<ClickOutside onClickOutside={onClickOutside}>
<button id="click-outside-test-button">Push Me</button>
</ClickOutside>,
{
attachTo: container,
}
);
const target = document.getElementById("click-outside-test-button")!;
simulant.fire(target, "click");
expect(onClickOutside.calledOnce).toEqual(false);
wrapper.unmount();
});
@@ -0,0 +1,34 @@
import React from "react";
import { findDOMNode } from "react-dom";
export interface ClickOutsideProps {
onClickOutside: () => void;
children: React.ReactNode;
}
export class ClickOutside extends React.Component<ClickOutsideProps> {
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;
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render correctly 1`] = `"<span>Hello World!</span>"`;
@@ -0,0 +1 @@
export { default, ClickOutside } from "./ClickOutside";
@@ -42,7 +42,7 @@ interface Props {
}
interface RenderProps {
toggleVisibility?: () => void;
toggleVisibility: () => void;
forwardRef?: RefHandler;
}
@@ -115,8 +115,6 @@ class Popover extends React.Component<InnerProps> {
aria-hidden={!visible}
>
<AriaInfo id={`${id}-ariainfo`}>{description}</AriaInfo>
{/* <ClickOutside onClickOutside={toggleVisibility}> */}
<div
style={props.style}
className={cn(styles.root, className)}
@@ -129,7 +127,6 @@ class Popover extends React.Component<InnerProps> {
forwardRef: props.ref,
})}
</div>
{/* </ClickOutside> */}
</div>
)
}
+1
View File
@@ -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";
+16
View File
@@ -0,0 +1,16 @@
declare module "simulant" {
type SimulantEvent = {};
interface Simulant {
(event: string, extendedParams: Record<string, any>): SimulantEvent;
fire(
target: HTMLElement,
event: string | SimulantEvent,
extendedParams?: Record<string, any>
): void;
polyfill(): void;
}
const simulant: Simulant;
export default simulant;
}