mirror of
https://github.com/wassname/talk.git
synced 2026-09-09 11:38:08 +08:00
Merge branch 'next' of github.com:coralproject/talk into ui-components
This commit is contained in:
@@ -15,6 +15,9 @@ import * as styles from "./BaseButton.css";
|
||||
interface InnerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
/** If set renders an anchor tag instead */
|
||||
anchor?: boolean;
|
||||
href?: string;
|
||||
target?: string;
|
||||
|
||||
/**
|
||||
* This prop can be used to add custom classnames.
|
||||
* It is handled by the `withStyles `HOC.
|
||||
|
||||
@@ -11,6 +11,10 @@ import * as styles from "./Button.css";
|
||||
// This should extend from BaseButton instead but we can't because of this bug
|
||||
// TODO: add bug link.
|
||||
interface InnerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
/** If set renders an anchor tag instead */
|
||||
anchor?: boolean;
|
||||
href?: string;
|
||||
target?: string;
|
||||
/**
|
||||
* This prop can be used to add custom classnames.
|
||||
* It is handled by the `withStyles `HOC.
|
||||
|
||||
@@ -19,8 +19,11 @@ clicks outside the component.
|
||||
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!')}>
|
||||
<div id="outside" style={{background: 'blue', padding: '10px'}}>
|
||||
<ClickOutside onClickOutside={e => {
|
||||
if (e.srcElement.id === "outside") {
|
||||
alert('You clicked outside!');
|
||||
}}}>
|
||||
<Button variant="filled">Push Me</Button>
|
||||
</ClickOutside>
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,8 @@ export type ClickFarAwayRegister = (
|
||||
callback: ClickFarAwayCallback
|
||||
) => ClickFarAwayUnlistenCallback;
|
||||
|
||||
interface Props {
|
||||
onClickOutside: () => void;
|
||||
export interface ClickOutsideProps {
|
||||
onClickOutside: (e?: MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* A way to listen for clicks that are e.g. outside of the
|
||||
@@ -22,7 +22,7 @@ interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export class ClickOutside extends React.Component<Props> {
|
||||
export class ClickOutside extends React.Component<ClickOutsideProps> {
|
||||
public domNode: Element | null = null;
|
||||
private unlisten?: ClickFarAwayUnlistenCallback;
|
||||
|
||||
@@ -30,7 +30,7 @@ export class ClickOutside extends React.Component<Props> {
|
||||
const { onClickOutside } = this.props;
|
||||
if (!e || !this.domNode!.contains(e.target as HTMLInputElement)) {
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
onClickOutside && onClickOutside();
|
||||
onClickOutside && onClickOutside(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -65,7 +65,9 @@ export class ClickOutside extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
const ClickOutsideWithContext: StatelessComponent<Props> = props => (
|
||||
const ClickOutsideWithContext: StatelessComponent<
|
||||
ClickOutsideProps
|
||||
> = props => (
|
||||
<UIContext.Consumer>
|
||||
{({ registerClickFarAway }) => (
|
||||
<ClickOutside {...props} registerClickFarAway={registerClickFarAway} />
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as ClickOutside, ClickFarAwayRegister } from "./ClickOutside";
|
||||
export { default, ClickFarAwayRegister } from "./ClickOutside";
|
||||
|
||||
@@ -37,18 +37,18 @@
|
||||
}
|
||||
|
||||
.sm {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
.md {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
.md {
|
||||
.lg {
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
.lg {
|
||||
.xl {
|
||||
font-size: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
.xl {
|
||||
font-size: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
.root {
|
||||
background: var(--palette-common-white);
|
||||
border: 1px solid var(--palette-grey-lighter);
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--elevation-main);
|
||||
border-radius: var(--round-corners);
|
||||
padding: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
|
||||
.top {
|
||||
margin: calc(0.5 * var(--spacing-unit)) 0;
|
||||
}
|
||||
.left {
|
||||
margin: 0 calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
.right {
|
||||
margin: 0 calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
.bottom {
|
||||
margin: calc(0.5 * var(--spacing-unit)) 0;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: Popover
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground } from 'docz'
|
||||
import Popover from './Popover'
|
||||
import Button from '../Button'
|
||||
import Flex from '../Flex'
|
||||
import Typography from '../Typography'
|
||||
import ButtonIcon from '../Button/ButtonIcon'
|
||||
|
||||
# Popover
|
||||
|
||||
`Popover` renders a popover dialog attached to another `Element`.
|
||||
|
||||
## Basic usage
|
||||
<Playground>
|
||||
<Popover
|
||||
body={<Typography>This is the body</Typography>}
|
||||
>
|
||||
{({ toggleVisibility, forwardRef }) => (
|
||||
<Button onClick={toggleVisibility} forwardRef={forwardRef} variant="filled" color="primary">
|
||||
Click me!
|
||||
</Button>
|
||||
)}
|
||||
</Popover>
|
||||
</Playground>
|
||||
|
||||
#### Example with `placement=top`
|
||||
<Playground>
|
||||
<Popover
|
||||
placement="top"
|
||||
body={({ toggleVisibility }) => (
|
||||
<Flex itemGutter="half">
|
||||
<Typography>This is the body</Typography>
|
||||
<Button onClick={toggleVisibility} size="small">
|
||||
<ButtonIcon>close</ButtonIcon>
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
>
|
||||
{({ toggleVisibility, forwardRef }) => (
|
||||
<Button onClick={toggleVisibility} forwardRef={forwardRef} variant="filled" color="primary">
|
||||
Click me!
|
||||
</Button>
|
||||
)}
|
||||
</Popover>
|
||||
</Playground>
|
||||
@@ -0,0 +1,147 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import {
|
||||
Manager,
|
||||
Popper,
|
||||
PopperArrowProps,
|
||||
Reference,
|
||||
RefHandler,
|
||||
} from "react-popper";
|
||||
import AriaInfo from "../AriaInfo";
|
||||
import * as styles from "./Popover.css";
|
||||
|
||||
type Placement =
|
||||
| "top-start"
|
||||
| "top"
|
||||
| "top-end"
|
||||
| "right-start"
|
||||
| "right"
|
||||
| "right-end"
|
||||
| "bottom-end"
|
||||
| "bottom"
|
||||
| "bottom-start"
|
||||
| "left-end"
|
||||
| "left"
|
||||
| "left-start";
|
||||
|
||||
interface BodyRenderProps {
|
||||
toggleVisibility: () => void;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface ChildrenRenderProps {
|
||||
toggleVisibility: () => void;
|
||||
forwardRef?: RefHandler;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface PopoverProps {
|
||||
body: (props: BodyRenderProps) => React.ReactNode | React.ReactElement<any>;
|
||||
children: (props: ChildrenRenderProps) => React.ReactNode;
|
||||
description: string;
|
||||
id: string;
|
||||
onClose?: () => void;
|
||||
className?: string;
|
||||
placement?: Placement;
|
||||
}
|
||||
|
||||
interface State {
|
||||
visible: false;
|
||||
}
|
||||
|
||||
class Popover extends React.Component<PopoverProps> {
|
||||
public static defaultProps = {
|
||||
placement: "top",
|
||||
};
|
||||
public state: State = {
|
||||
visible: false,
|
||||
};
|
||||
|
||||
public toggleVisibility = () => {
|
||||
this.setState((state: State) => ({
|
||||
visible: !state.visible,
|
||||
}));
|
||||
};
|
||||
|
||||
public close = () => {
|
||||
this.setState((state: State) => ({
|
||||
visible: false,
|
||||
}));
|
||||
};
|
||||
|
||||
public handleEsc = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
document.addEventListener("keydown", this.handleEsc, true);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener("keydown", this.handleEsc, true);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
id,
|
||||
body,
|
||||
children,
|
||||
description,
|
||||
className,
|
||||
placement,
|
||||
} = this.props;
|
||||
|
||||
const { visible } = this.state;
|
||||
const popoverClassName = cn(styles.root, className, {
|
||||
[styles.top]: placement!.startsWith("top"),
|
||||
[styles.left]: placement!.startsWith("left"),
|
||||
[styles.right]: placement!.startsWith("right"),
|
||||
[styles.bottom]: placement!.startsWith("bottom"),
|
||||
});
|
||||
|
||||
return (
|
||||
<Manager>
|
||||
<Reference>
|
||||
{(props: PopperArrowProps) =>
|
||||
children({
|
||||
forwardRef: props.ref,
|
||||
toggleVisibility: this.toggleVisibility,
|
||||
visible: this.state.visible,
|
||||
})
|
||||
}
|
||||
</Reference>
|
||||
<Popper placement={placement} eventsEnabled positionFixed={false}>
|
||||
{(props: PopperArrowProps) => (
|
||||
<div
|
||||
id={id}
|
||||
role="popup"
|
||||
aria-labelledby={`${id}-ariainfo`}
|
||||
aria-hidden={!visible}
|
||||
>
|
||||
<AriaInfo id={`${id}-ariainfo`}>{description}</AriaInfo>
|
||||
{visible && (
|
||||
<div
|
||||
style={props.style}
|
||||
className={popoverClassName}
|
||||
ref={props.ref}
|
||||
>
|
||||
{typeof body === "function"
|
||||
? body({
|
||||
toggleVisibility: this.toggleVisibility,
|
||||
visible: this.state.visible,
|
||||
})
|
||||
: body}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Popper>
|
||||
</Manager>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Popover;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./Popover";
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Popup
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground } from "docz"
|
||||
import Popup from "./Popup"
|
||||
import Button from "../Button"
|
||||
import Flex from "../Flex"
|
||||
import Container from "react-with-state-props"
|
||||
|
||||
# Popup
|
||||
|
||||
Declaratively control a popup.
|
||||
|
||||
## Basic usage
|
||||
|
||||
<Playground>
|
||||
<Container
|
||||
state={{ open: false, focus: false }}
|
||||
render={props => (
|
||||
<Flex itemGutter>
|
||||
<Popup
|
||||
href={"/static/js/src-core-client-ui-components-popup-popup.js"}
|
||||
title="Coral Project"
|
||||
features="menubar=0,resizable=0,width=500,height=550,top=200,left=500"
|
||||
open={props.open}
|
||||
focus={props.focus}
|
||||
onFocus={() => props.setFocus(true)}
|
||||
onBlur={() => props.setFocus(false)}
|
||||
onClose={() => props.setOpen(false)}
|
||||
/>
|
||||
<Button onClick={() => props.setOpen(true)} variant="filled" color="primary" disabled={props.open}>
|
||||
Open Popup
|
||||
</Button>
|
||||
<Button onClick={() => props.setOpen(false)} variant="outlined" disabled={!props.open}>
|
||||
Close Popup
|
||||
</Button>
|
||||
<Button onClick={() => props.setFocus(true)} variant="outlined" disabled={!props.open}>
|
||||
Focus Popup
|
||||
</Button>
|
||||
</Flex>
|
||||
)}/>
|
||||
</Playground>
|
||||
@@ -0,0 +1,174 @@
|
||||
import { Component } from "react";
|
||||
|
||||
interface PopupProps {
|
||||
open?: boolean;
|
||||
focus?: boolean;
|
||||
onFocus?: (e: FocusEvent) => void;
|
||||
onBlur?: (e: FocusEvent) => void;
|
||||
onLoad?: (e: Event) => void;
|
||||
onUnload?: (e: Event) => void;
|
||||
onClose?: () => void;
|
||||
href: string;
|
||||
features?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export default class Popup extends Component<PopupProps> {
|
||||
private ref: Window | null = null;
|
||||
private detectCloseInterval: any = null;
|
||||
private resetCallbackInterval: any = null;
|
||||
|
||||
constructor(props: PopupProps) {
|
||||
super(props);
|
||||
|
||||
if (props.open) {
|
||||
this.openWindow(props);
|
||||
}
|
||||
}
|
||||
|
||||
private openWindow(props = this.props) {
|
||||
this.ref = window.open(props.href, props.title, props.features);
|
||||
|
||||
this.setCallbacks();
|
||||
|
||||
// For some reasons IE needs a timeout before setting the callbacks...
|
||||
setTimeout(() => this.setCallbacks(), 1000);
|
||||
}
|
||||
|
||||
private setCallbacks() {
|
||||
this.ref!.onload = e => {
|
||||
if (this.detectCloseInterval) {
|
||||
clearInterval(this.detectCloseInterval);
|
||||
}
|
||||
this.onLoad(e);
|
||||
};
|
||||
|
||||
this.ref!.onfocus = e => {
|
||||
this.onFocus(e);
|
||||
};
|
||||
|
||||
this.ref!.onblur = e => {
|
||||
this.onBlur(e);
|
||||
};
|
||||
|
||||
// Use `onunload` instead of `onbeforeunload` which is not supported in iOS
|
||||
// Safari.
|
||||
this.ref!.onunload = e => {
|
||||
this.onUnload(e);
|
||||
|
||||
if (this.resetCallbackInterval) {
|
||||
clearInterval(this.resetCallbackInterval);
|
||||
}
|
||||
|
||||
this.resetCallbackInterval = setInterval(() => {
|
||||
try {
|
||||
if (this.ref && this.ref.onload === null) {
|
||||
if (this.resetCallbackInterval) {
|
||||
clearInterval(this.resetCallbackInterval);
|
||||
}
|
||||
this.resetCallbackInterval = null;
|
||||
this.setCallbacks();
|
||||
}
|
||||
} catch (err) {
|
||||
// We could be getting a security exception here if the login page
|
||||
// gets redirected to another domain to authenticate.
|
||||
}
|
||||
}, 50);
|
||||
|
||||
if (this.detectCloseInterval) {
|
||||
clearInterval(this.detectCloseInterval);
|
||||
}
|
||||
|
||||
this.detectCloseInterval = setInterval(() => {
|
||||
if (!this.ref || this.ref.closed) {
|
||||
if (this.detectCloseInterval) {
|
||||
clearInterval(this.detectCloseInterval);
|
||||
}
|
||||
this.detectCloseInterval = null;
|
||||
this.onClose();
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
}
|
||||
|
||||
private closeWindow() {
|
||||
if (this.ref) {
|
||||
if (!this.ref.closed) {
|
||||
this.ref.close();
|
||||
}
|
||||
this.ref = null;
|
||||
}
|
||||
}
|
||||
|
||||
private focusWindow() {
|
||||
if (this.ref && !this.ref.closed) {
|
||||
this.ref.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private blurWindow() {
|
||||
if (this.ref && !this.ref.closed) {
|
||||
this.ref.blur();
|
||||
}
|
||||
}
|
||||
|
||||
private onLoad = (e: Event) => {
|
||||
if (this.props.onLoad) {
|
||||
this.props.onLoad(e);
|
||||
}
|
||||
};
|
||||
|
||||
private onUnload = (e: Event) => {
|
||||
if (this.props.onUnload) {
|
||||
this.props.onUnload(e);
|
||||
}
|
||||
};
|
||||
|
||||
private onClose = () => {
|
||||
if (this.props.onClose) {
|
||||
this.props.onClose();
|
||||
}
|
||||
};
|
||||
|
||||
private onFocus = (e: FocusEvent) => {
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus(e);
|
||||
}
|
||||
};
|
||||
|
||||
private onBlur = (e: FocusEvent) => {
|
||||
if (this.props.onBlur) {
|
||||
this.props.onBlur(e);
|
||||
}
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps: PopupProps) {
|
||||
if (nextProps.open && !this.ref) {
|
||||
this.openWindow(nextProps);
|
||||
}
|
||||
|
||||
if (this.props.open && !nextProps.open) {
|
||||
this.closeWindow();
|
||||
}
|
||||
|
||||
if (!this.props.focus && nextProps.focus) {
|
||||
this.focusWindow();
|
||||
}
|
||||
|
||||
if (this.props.focus && !nextProps.focus) {
|
||||
this.blurWindow();
|
||||
}
|
||||
|
||||
if (this.props.href !== nextProps.href) {
|
||||
this.ref!.location.href = nextProps.href;
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.closeWindow();
|
||||
}
|
||||
|
||||
public render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./Popup";
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./TextField";
|
||||
export { default } from "./TextField";
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export { default as BaseButton } from "./BaseButton";
|
||||
export { default as Button } from "./Button";
|
||||
export { default as ButtonIcon } from "./Button/ButtonIcon";
|
||||
export { default as Typography } from "./Typography";
|
||||
export { default as Popover } from "./Popover";
|
||||
export { default as RelativeTime } from "./RelativeTime";
|
||||
export { default as UIContext, UIContextProps } from "./UIContext";
|
||||
export { default as Flex } from "./Flex";
|
||||
@@ -10,3 +12,5 @@ export { default as ValidationMessage } from "./ValidationMessage";
|
||||
export { default as InputLabel } from "./InputLabel";
|
||||
export { default as TextField } from "./TextField";
|
||||
export { default as CallOut } from "./CallOut";
|
||||
export { default as ClickOutside } from "./ClickOutside";
|
||||
export { default as Popup } from "./Popup";
|
||||
|
||||
Reference in New Issue
Block a user