mirror of
https://github.com/wassname/talk.git
synced 2026-08-01 13:00:55 +08:00
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:
Generated
+5
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
Vendored
+16
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user