mirror of
https://github.com/wassname/talk.git
synced 2026-08-11 05:54:07 +08:00
Merge branch 'toggleshow' of https://github.com/coralproject/talk into toggleshow
This commit is contained in:
@@ -8,3 +8,6 @@
|
||||
outline-color: -webkit-focus-ring-color;
|
||||
outline-style: auto;
|
||||
}
|
||||
|
||||
.mouseHover {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import BaseButton from "./BaseButton";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof BaseButton> = {
|
||||
className: "my-class",
|
||||
children: "Push Me",
|
||||
};
|
||||
const renderer = TestRenderer.create(<BaseButton {...props} />);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders as anchor", () => {
|
||||
const props: PropTypesOf<typeof BaseButton> = {
|
||||
anchor: true,
|
||||
children: "Push Me",
|
||||
};
|
||||
const renderer = TestRenderer.create(<BaseButton {...props} />);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,8 +1,13 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import React, { Ref } from "react";
|
||||
import { ButtonHTMLAttributes, StatelessComponent } from "react";
|
||||
|
||||
import { withKeyboardFocus, withStyles } from "talk-ui/hocs";
|
||||
import {
|
||||
withForwardRef,
|
||||
withKeyboardFocus,
|
||||
withMouseHover,
|
||||
withStyles,
|
||||
} from "talk-ui/hocs";
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import * as styles from "./BaseButton.css";
|
||||
@@ -10,7 +15,6 @@ import * as styles from "./BaseButton.css";
|
||||
interface InnerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
/** If set renders an anchor tag instead */
|
||||
anchor?: boolean;
|
||||
|
||||
/**
|
||||
* This prop can be used to add custom classnames.
|
||||
* It is handled by the `withStyles `HOC.
|
||||
@@ -18,7 +22,16 @@ interface InnerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
classes: typeof styles;
|
||||
|
||||
/** This is passed by the `withKeyboardFocus` HOC */
|
||||
keyboardFocus: boolean;
|
||||
keyboardFocus?: boolean;
|
||||
|
||||
/** This is passed by the `withMouseHover` HOC */
|
||||
mouseHover?: boolean;
|
||||
|
||||
/** ref to the HTMLButtonElement */
|
||||
ref?: Ref<HTMLButtonElement>;
|
||||
|
||||
/** Internal: Forwarded Ref */
|
||||
forwardRef?: Ref<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,6 +43,8 @@ const BaseButton: StatelessComponent<InnerProps> = ({
|
||||
className,
|
||||
classes,
|
||||
keyboardFocus,
|
||||
mouseHover,
|
||||
forwardRef,
|
||||
type: typeProp,
|
||||
...rest
|
||||
}) => {
|
||||
@@ -51,11 +66,14 @@ const BaseButton: StatelessComponent<InnerProps> = ({
|
||||
|
||||
const rootClassName = cn(classes.root, className, {
|
||||
[classes.keyboardFocus]: keyboardFocus,
|
||||
[classes.mouseHover]: mouseHover,
|
||||
});
|
||||
|
||||
return <Element {...rest} className={rootClassName} />;
|
||||
return <Element {...rest} className={rootClassName} ref={forwardRef} />;
|
||||
};
|
||||
|
||||
const enhanced = withStyles(styles)(withKeyboardFocus(BaseButton));
|
||||
const enhanced = withForwardRef(
|
||||
withStyles(styles)(withMouseHover(withKeyboardFocus(BaseButton)))
|
||||
);
|
||||
export type BaseButtonProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders as anchor 1`] = `
|
||||
<a
|
||||
className="BaseButton-root"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
Push Me
|
||||
</a>
|
||||
`;
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<button
|
||||
className="BaseButton-root my-class"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
Push Me
|
||||
</button>
|
||||
`;
|
||||
@@ -1,35 +1,50 @@
|
||||
.root {
|
||||
composes: button from "talk-ui/shared/typography.css";
|
||||
|
||||
&:enabled,
|
||||
&:disabled {
|
||||
padding: 5px 20px;
|
||||
border-radius: var(--round-corners);
|
||||
background-color: transparent;
|
||||
/* TODO: hover styles for the default button */
|
||||
padding: 5px 15px;
|
||||
border-radius: var(--round-corners);
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
& > * {
|
||||
margin: 0 calc(0.5 * var(--spacing-unit)) 0 0;
|
||||
}
|
||||
& > *:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.root:disabled {
|
||||
.root.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.fullWidth:enabled,
|
||||
.fullWidth:disabled {
|
||||
.fullWidth {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.primary:enabled,
|
||||
.primary:disabled {
|
||||
.regular:not(.disabled) {
|
||||
&.mouseHover {
|
||||
color: var(--palette-secondary-light);
|
||||
}
|
||||
&:active {
|
||||
color: var(--palette-secondary-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
.primary,
|
||||
.primary:not(.disabled) {
|
||||
background-color: var(--palette-primary-main);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.primary:enabled {
|
||||
&:hover {
|
||||
.primary:not(.disabled) {
|
||||
&.mouseHover {
|
||||
background-color: var(--palette-primary-light);
|
||||
}
|
||||
|
||||
@@ -38,15 +53,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.primary:enabled.invert,
|
||||
.primary:disabled.invert {
|
||||
.primary.invert,
|
||||
.primary.invert:not(.disabled) {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--palette-primary-main);
|
||||
color: var(--palette-primary-main);
|
||||
}
|
||||
|
||||
.primary:enabled.invert {
|
||||
&:hover {
|
||||
.primary.invert:not(.disabled) {
|
||||
&.mouseHover {
|
||||
border-color: var(--palette-primary-light);
|
||||
color: var(--palette-primary-light);
|
||||
}
|
||||
@@ -57,14 +72,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.secondary:enabled,
|
||||
.secondary:disabled {
|
||||
.secondary,
|
||||
.secondary:not(.disabled) {
|
||||
background-color: var(--palette-secondary-main);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.secondary:enabled {
|
||||
&:hover {
|
||||
.secondary:not(.disabled) {
|
||||
&.mouseHover {
|
||||
background-color: var(--palette-secondary-light);
|
||||
}
|
||||
&:active {
|
||||
@@ -72,15 +87,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.secondary:enabled.invert,
|
||||
.secondary:disabled.invert {
|
||||
.secondary.invert,
|
||||
.secondary:not(.disabled).invert {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--palette-secondary-main);
|
||||
color: var(--palette-secondary-main);
|
||||
}
|
||||
|
||||
.secondary:enabled.invert {
|
||||
&:hover {
|
||||
.secondary:not(.disabled).invert {
|
||||
&.mouseHover {
|
||||
border-color: var(--palette-secondary-light);
|
||||
color: var(--palette-secondary-light);
|
||||
}
|
||||
@@ -90,10 +105,3 @@
|
||||
color: var(--palette-secondary-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This seems to be the best way to target modern touch device browsers.
|
||||
*/
|
||||
@media (-moz-touch-enabled: 1), (pointer: coarse) {
|
||||
/* TODO: Remove hover styles */
|
||||
}
|
||||
|
||||
@@ -5,23 +5,38 @@ menu: UI Kit
|
||||
|
||||
import { Playground } from 'docz'
|
||||
import Button from './Button'
|
||||
import Icon from '../Icon'
|
||||
import Flex from '../Flex'
|
||||
|
||||
# Button
|
||||
|
||||
## Basic usage
|
||||
<Playground>
|
||||
<Button style={{margin: "0 10px 10px 0"}}>Push Me</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} disabled>Push Me</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} anchor>I'm an Anchor Tag</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} primary>Primary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} primary disabled>Primary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} secondary>Secondary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} secondary disabled>Secondary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} primary invert>Primary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} primary invert disabled>Primary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} secondary invert>Secondary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} secondary invert disabled>Secondary</Button>
|
||||
<Button style={{marginBottom: "10px"}} primary fullWidth>Full Width</Button>
|
||||
<Button style={{marginBottom: "10px"}} primary invert fullWidth>Full Width Invert</Button>
|
||||
<Flex itemGutter wrap>
|
||||
<Button>Push Me</Button>
|
||||
<Button disabled>Push Me</Button>
|
||||
<Button anchor>I'm an Anchor Tag</Button>
|
||||
<Button primary>Primary</Button>
|
||||
<Button primary disabled>Primary</Button>
|
||||
<Button secondary>Secondary</Button>
|
||||
<Button secondary disabled>Secondary</Button>
|
||||
<Button primary invert>Primary</Button>
|
||||
<Button primary invert disabled>Primary</Button>
|
||||
<Button secondary invert>Secondary</Button>
|
||||
<Button secondary invert disabled>Secondary</Button>
|
||||
<Button primary fullWidth>Full Width</Button>
|
||||
<Button primary invert fullWidth>Full Width Invert</Button>
|
||||
</Flex>
|
||||
</Playground>
|
||||
|
||||
|
||||
## Button with Icon
|
||||
<Playground>
|
||||
<Flex itemGutter>
|
||||
<Button><Icon>face</Icon><span>Push Me</span></Button>
|
||||
<Button primary><Icon>bookmark</Icon><span>Push Me</span></Button>
|
||||
<Button primary><span>Push Me</span><Icon>build</Icon></Button>
|
||||
</Flex>
|
||||
</Playground>
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import cn from "classnames";
|
||||
import { pick } from "lodash";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
import React, { ButtonHTMLAttributes, Ref } from "react";
|
||||
|
||||
import { withStyles } from "talk-ui/hocs";
|
||||
import { withForwardRef, withStyles } from "talk-ui/hocs";
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import BaseButton, { BaseButtonProps } from "../BaseButton";
|
||||
@@ -28,6 +28,12 @@ interface InnerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
|
||||
/** If set renders a button with secondary colors */
|
||||
secondary?: boolean;
|
||||
|
||||
/** ref to the HTMLButtonElement */
|
||||
ref?: Ref<HTMLButtonElement>;
|
||||
|
||||
/** Internal: Forwarded Ref */
|
||||
forwardRef?: Ref<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
class Button extends React.Component<InnerProps> {
|
||||
@@ -39,6 +45,8 @@ class Button extends React.Component<InnerProps> {
|
||||
invert,
|
||||
primary,
|
||||
secondary,
|
||||
disabled,
|
||||
forwardRef,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
@@ -47,18 +55,22 @@ class Button extends React.Component<InnerProps> {
|
||||
[classes.fullWidth]: fullWidth,
|
||||
[classes.primary]: primary,
|
||||
[classes.secondary]: secondary,
|
||||
[classes.regular]: !primary && !secondary,
|
||||
[classes.disabled]: disabled,
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseButton
|
||||
className={rootClassName}
|
||||
classes={pick(classes, "keyboardFocus")}
|
||||
classes={pick(classes, "keyboardFocus", "mouseHover")}
|
||||
disabled={disabled}
|
||||
forwardRef={forwardRef}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withStyles(styles)(Button);
|
||||
const enhanced = withForwardRef(withStyles(styles)(Button));
|
||||
export type ButtonProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
.root {
|
||||
display: flex;
|
||||
& > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.halfItemGutter {
|
||||
@@ -50,6 +53,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
.wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.wrapReverse {
|
||||
flex-wrap: wrap-reverse;
|
||||
}
|
||||
|
||||
.wrap,
|
||||
.wrapReverse {
|
||||
&.itemGutter {
|
||||
&:not(:empty) {
|
||||
margin-top: calc(-1 * var(--spacing-unit));
|
||||
}
|
||||
& > * {
|
||||
margin-top: var(--spacing-unit);
|
||||
}
|
||||
}
|
||||
&.directionColumn.itemGutter {
|
||||
&:not(:empty) {
|
||||
margin-left: calc(-1 * var(--spacing-unit));
|
||||
}
|
||||
&.itemGutter > * {
|
||||
margin-left: var(--spacing-unit);
|
||||
}
|
||||
}
|
||||
|
||||
&.halfItemGutter {
|
||||
&:not(:empty) {
|
||||
margin-top: calc(-0.5 * var(--spacing-unit));
|
||||
}
|
||||
& > * {
|
||||
margin-top: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
}
|
||||
&.directionColumn.halfItemGutter {
|
||||
&:not(:empty) {
|
||||
margin-left: calc(-0.5 * var(--spacing-unit));
|
||||
}
|
||||
&.halfItemGutter > * {
|
||||
margin-left: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.justifyFlexStart {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
@@ -11,10 +11,58 @@ it("renders correctly", () => {
|
||||
alignItems: "center",
|
||||
direction: "row",
|
||||
};
|
||||
const wrapper = shallow(
|
||||
const renderer = TestRenderer.create(
|
||||
<Flex {...props}>
|
||||
<div>Hello World</div>
|
||||
</Flex>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with wrap", () => {
|
||||
const props: PropTypesOf<typeof Flex> = {
|
||||
wrap: true,
|
||||
};
|
||||
const renderer = TestRenderer.create(
|
||||
<Flex {...props}>
|
||||
<div>Hello World</div>
|
||||
</Flex>
|
||||
);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with wrap reverse", () => {
|
||||
const props: PropTypesOf<typeof Flex> = {
|
||||
wrap: "reverse",
|
||||
};
|
||||
const renderer = TestRenderer.create(
|
||||
<Flex {...props}>
|
||||
<div>Hello World</div>
|
||||
</Flex>
|
||||
);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with item gutter", () => {
|
||||
const props: PropTypesOf<typeof Flex> = {
|
||||
itemGutter: true,
|
||||
};
|
||||
const renderer = TestRenderer.create(
|
||||
<Flex {...props}>
|
||||
<div>Hello World</div>
|
||||
</Flex>
|
||||
);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with halfe item gutter", () => {
|
||||
const props: PropTypesOf<typeof Flex> = {
|
||||
itemGutter: "half",
|
||||
};
|
||||
const renderer = TestRenderer.create(
|
||||
<Flex {...props}>
|
||||
<div>Hello World</div>
|
||||
</Flex>
|
||||
);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import React, { Ref } from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { pascalCase } from "talk-common/utils";
|
||||
import { withForwardRef, withStyles } from "talk-ui/hocs";
|
||||
|
||||
import * as styles from "./Flex.css";
|
||||
|
||||
interface InnerProps {
|
||||
/**
|
||||
* This prop can be used to add custom classnames.
|
||||
* It is handled by the `withStyles `HOC.
|
||||
*/
|
||||
classes: typeof styles;
|
||||
id?: string;
|
||||
role?: string;
|
||||
justifyContent?:
|
||||
@@ -20,38 +26,52 @@ interface InnerProps {
|
||||
direction?: "row" | "column" | "row-reverse" | "column-reverse";
|
||||
itemGutter?: boolean | "half";
|
||||
className?: string;
|
||||
wrap?: boolean | "reverse";
|
||||
|
||||
/** Ref to the root element */
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
|
||||
/** Internal: Forwarded Ref */
|
||||
forwardRef?: Ref<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const Flex: StatelessComponent<InnerProps> = props => {
|
||||
const {
|
||||
classes,
|
||||
className,
|
||||
justifyContent,
|
||||
alignItems,
|
||||
direction,
|
||||
itemGutter,
|
||||
wrap,
|
||||
forwardRef,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const classObject: Record<string, boolean> = {
|
||||
[styles.itemGutter]: itemGutter === true,
|
||||
[styles.halfItemGutter]: itemGutter === "half",
|
||||
[classes.itemGutter]: itemGutter === true,
|
||||
[classes.halfItemGutter]: itemGutter === "half",
|
||||
[classes.wrap]: wrap === true,
|
||||
[classes.wrapReverse]: wrap === "reverse",
|
||||
};
|
||||
|
||||
if (justifyContent) {
|
||||
classObject[(styles as any)[`justify${pascalCase(justifyContent)}`]] = true;
|
||||
classObject[
|
||||
(classes as any)[`justify${pascalCase(justifyContent)}`]
|
||||
] = true;
|
||||
}
|
||||
|
||||
if (alignItems) {
|
||||
classObject[(styles as any)[`align${pascalCase(alignItems)}`]] = true;
|
||||
classObject[(classes as any)[`align${pascalCase(alignItems)}`]] = true;
|
||||
}
|
||||
|
||||
if (direction) {
|
||||
classObject[(styles as any)[`direction${pascalCase(direction)}`]] = true;
|
||||
classObject[(classes as any)[`direction${pascalCase(direction)}`]] = true;
|
||||
}
|
||||
|
||||
const classNames: string = cn(styles.root, className, classObject);
|
||||
const classNames: string = cn(classes.root, className, classObject);
|
||||
|
||||
return <div className={classNames} {...rest} />;
|
||||
return <div ref={forwardRef} className={classNames} {...rest} />;
|
||||
};
|
||||
|
||||
export default Flex;
|
||||
export default withForwardRef(withStyles(styles)(Flex));
|
||||
|
||||
@@ -9,3 +9,43 @@ exports[`renders correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders with halfe item gutter 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-halfItemGutter"
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders with item gutter 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-itemGutter"
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders with wrap 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-wrap"
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders with wrap reverse 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-wrapReverse"
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
@font-face {
|
||||
font-family: "Material Icons";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local("Material Icons"), local("MaterialIcons-Regular"),
|
||||
url(material-design-icons/iconfont/MaterialIcons-Regular.woff2)
|
||||
format("woff2"),
|
||||
url(material-design-icons/iconfont/MaterialIcons-Regular.woff)
|
||||
format("woff"),
|
||||
url(material-design-icons/iconfont/MaterialIcons-Regular.ttf)
|
||||
format("truetype");
|
||||
}
|
||||
|
||||
.root {
|
||||
font-family: "Material Icons";
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
letter-spacing: 0;
|
||||
|
||||
/* Enable Ligatures */
|
||||
font-feature-settings: "liga";
|
||||
font-variant-ligatures: "discretionary-ligatures";
|
||||
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Better Font Rendering */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.sm {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
.md {
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
.lg {
|
||||
font-size: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
.xl {
|
||||
font-size: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Icon
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
import Icon from './Icon'
|
||||
|
||||
# Icon
|
||||
|
||||
Renders an icon.
|
||||
|
||||
Checkout available icons https://material.io/tools/icons/
|
||||
|
||||
## Basic usage
|
||||
<Playground>
|
||||
<Icon size="sm">face</Icon>
|
||||
<Icon size="md">face</Icon>
|
||||
<Icon size="lg">face</Icon>
|
||||
<Icon size="xl">face</Icon>
|
||||
</Playground>
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import Icon from "./Icon";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof Icon> = {
|
||||
children: "face",
|
||||
};
|
||||
const renderer = TestRenderer.create(<Icon {...props} />);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly with specified size", () => {
|
||||
const props: PropTypesOf<typeof Icon> = {
|
||||
size: "lg",
|
||||
children: "bookmark",
|
||||
};
|
||||
const renderer = TestRenderer.create(<Icon {...props} />);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import cn from "classnames";
|
||||
import React, { HTMLAttributes, Ref, StatelessComponent } from "react";
|
||||
|
||||
import { withForwardRef, withStyles } from "talk-ui/hocs";
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import * as styles from "./Icon.css";
|
||||
|
||||
interface InnerProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
/**
|
||||
* This prop can be used to add custom classnames.
|
||||
* It is handled by the `withStyles `HOC.
|
||||
*/
|
||||
classes: typeof styles;
|
||||
|
||||
size?: "sm" | "md" | "lg" | "xl";
|
||||
|
||||
/** The name of the icon to render */
|
||||
children: string;
|
||||
|
||||
/** ref to the HTMLIconElement */
|
||||
ref?: Ref<HTMLSpanElement>;
|
||||
|
||||
/** Internal: Forwarded Ref */
|
||||
forwardRef?: Ref<HTMLSpanElement>;
|
||||
}
|
||||
|
||||
const Icon: StatelessComponent<InnerProps> = props => {
|
||||
const { classes, className, size, forwardRef, ...rest } = props;
|
||||
const rootClassName = cn(classes.root, className, classes[size!]);
|
||||
return <span className={rootClassName} {...rest} ref={forwardRef} />;
|
||||
};
|
||||
|
||||
Icon.defaultProps = {
|
||||
size: "sm",
|
||||
};
|
||||
|
||||
const enhanced = withForwardRef(withStyles(styles)(Icon));
|
||||
export type IconProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<span
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
face
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`renders correctly with specified size 1`] = `
|
||||
<span
|
||||
className="Icon-root Icon-lg"
|
||||
>
|
||||
bookmark
|
||||
</span>
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./Icon";
|
||||
export { default } from "./Icon";
|
||||
@@ -1,9 +1,9 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import React, { Ref } from "react";
|
||||
import TimeAgo, { Formatter } from "react-timeago";
|
||||
|
||||
import { UIContext } from "talk-ui/components";
|
||||
import { withStyles } from "talk-ui/hocs";
|
||||
import { withForwardRef, withStyles } from "talk-ui/hocs";
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import * as styles from "./RelativeTime.css";
|
||||
@@ -14,29 +14,33 @@ interface InnerProps {
|
||||
classes: typeof styles;
|
||||
className?: string;
|
||||
formatter?: Formatter;
|
||||
|
||||
/** Ref to the root element */
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
|
||||
/** Internal: Forwarded Ref */
|
||||
forwardRef?: Ref<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const defaultFormatter: Formatter = (value, unit, suffix, timestamp: string) =>
|
||||
new Date(timestamp).toISOString();
|
||||
|
||||
class RelativeTime extends React.Component<InnerProps> {
|
||||
public render() {
|
||||
const { date, classes, live, className, formatter } = this.props;
|
||||
return (
|
||||
<UIContext.Consumer>
|
||||
{({ timeagoFormatter }) => (
|
||||
<TimeAgo
|
||||
date={date}
|
||||
className={cn(className, classes.root)}
|
||||
live={live}
|
||||
formatter={timeagoFormatter || formatter || defaultFormatter}
|
||||
/>
|
||||
)}
|
||||
</UIContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
const RelativeTime: React.StatelessComponent<InnerProps> = props => {
|
||||
const { date, classes, live, className, formatter } = props;
|
||||
return (
|
||||
<UIContext.Consumer>
|
||||
{({ timeagoFormatter }) => (
|
||||
<TimeAgo
|
||||
date={date}
|
||||
className={cn(className, classes.root)}
|
||||
live={live}
|
||||
formatter={timeagoFormatter || formatter || defaultFormatter}
|
||||
/>
|
||||
)}
|
||||
</UIContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
const enhanced = withStyles(styles)(RelativeTime);
|
||||
const enhanced = withForwardRef(withStyles(styles)(RelativeTime));
|
||||
export type RelativeTimeProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
|
||||
@@ -18,13 +18,13 @@ it("renders correctly", () => {
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should work correctly", () => {
|
||||
it("should hide the div", () => {
|
||||
const renderer = create(
|
||||
<ToggleShow>
|
||||
{({ toggleShow, show }) => (
|
||||
<div>
|
||||
{show && <div>SHOW ME</div>}
|
||||
<button onClick={toggleShow}>Click me and I disapear</button>
|
||||
<button onClick={toggleShow}>Click me and I disappear</button>
|
||||
</div>
|
||||
)}
|
||||
</ToggleShow>
|
||||
|
||||
@@ -13,12 +13,12 @@ exports[`renders correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should work correctly 1`] = `
|
||||
exports[`should hide the div 1`] = `
|
||||
<div>
|
||||
<button
|
||||
onClick={[Function]}
|
||||
>
|
||||
Click me and I disapear
|
||||
Click me and I disappear
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: TrapFocus
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground } from 'docz'
|
||||
import TrapFocus from './TrapFocus'
|
||||
|
||||
# TrapFocus
|
||||
|
||||
Traps focus inside component when using keyboard navigation for accessibility purposes.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```ts
|
||||
import React from "react";
|
||||
|
||||
class Dialog extends React.Component {
|
||||
private firstFocusable: HTMLElement;
|
||||
private lastFocusable: HTMLElement;
|
||||
|
||||
private setFirstFocusable = (ref: HTMLElement) => (this.firstFocusable = ref);
|
||||
private setLastFocusable = (ref: HTMLElement) => (this.lastFocusable = ref);
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div>
|
||||
<TrapFocus
|
||||
firstFocusable={this.firstFocusable}
|
||||
lastFocusable={this.lastFocusable}
|
||||
>
|
||||
<TextField ref={this.setFirstFocusable} />
|
||||
<Button ref={this.setLastFocusable}>Send</Button>
|
||||
</TrapFocus>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from "react";
|
||||
import { create } from "react-test-renderer";
|
||||
import Sinon from "sinon";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
import TrapFocus from "./TrapFocus";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<TrapFocus> = {
|
||||
firstFocusable: null,
|
||||
lastFocusable: null,
|
||||
children: (
|
||||
<>
|
||||
<span>child1</span>
|
||||
<span>child2</span>
|
||||
</>
|
||||
),
|
||||
};
|
||||
const renderer = create(
|
||||
<div>
|
||||
<TrapFocus {...props} />
|
||||
</div>
|
||||
);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Change focus to `lastFocusable` when focus reaches beginning", () => {
|
||||
const fakeHTMLElementBegin = { focus: Sinon.spy() };
|
||||
const fakeHTMLElementEnd = { focus: Sinon.spy() };
|
||||
const props: PropTypesOf<TrapFocus> = {
|
||||
firstFocusable: fakeHTMLElementBegin as any,
|
||||
lastFocusable: fakeHTMLElementEnd as any,
|
||||
children: (
|
||||
<>
|
||||
<span>child1</span>
|
||||
<span>child2</span>
|
||||
</>
|
||||
),
|
||||
};
|
||||
const renderer = create(
|
||||
<div>
|
||||
<TrapFocus {...props} />
|
||||
</div>
|
||||
);
|
||||
renderer.root.findAllByProps({ tabIndex: 0 })[0].props.onFocus();
|
||||
expect(fakeHTMLElementBegin.focus.called).toBe(false);
|
||||
expect(fakeHTMLElementEnd.focus.called).toBe(true);
|
||||
});
|
||||
|
||||
it("Change focus to `firstFocusable` when focus reaches the end", () => {
|
||||
const fakeHTMLElementBegin = { focus: Sinon.spy() };
|
||||
const fakeHTMLElementEnd = { focus: Sinon.spy() };
|
||||
const props: PropTypesOf<TrapFocus> = {
|
||||
firstFocusable: fakeHTMLElementBegin as any,
|
||||
lastFocusable: fakeHTMLElementEnd as any,
|
||||
children: (
|
||||
<>
|
||||
<span>child1</span>
|
||||
<span>child2</span>
|
||||
</>
|
||||
),
|
||||
};
|
||||
const renderer = create(
|
||||
<div>
|
||||
<TrapFocus {...props} />
|
||||
</div>
|
||||
);
|
||||
renderer.root.findAllByProps({ tabIndex: 0 })[1].props.onFocus();
|
||||
expect(fakeHTMLElementBegin.focus.called).toBe(true);
|
||||
expect(fakeHTMLElementEnd.focus.called).toBe(false);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
|
||||
export interface Focusable {
|
||||
focus: () => void;
|
||||
}
|
||||
|
||||
export interface TrapFocusProps {
|
||||
firstFocusable: Focusable | null;
|
||||
lastFocusable: Focusable | null;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default class TrapFocus extends React.Component<TrapFocusProps> {
|
||||
// Trap keyboard focus inside the dropdown until a value has been chosen.
|
||||
public focusBegin = () => this.props.firstFocusable!.focus();
|
||||
public focusEnd = () => this.props.lastFocusable!.focus();
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<div tabIndex={0} onFocus={this.focusEnd} />
|
||||
{this.props.children}
|
||||
<div tabIndex={0} onFocus={this.focusBegin} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div>
|
||||
<div
|
||||
onFocus={[Function]}
|
||||
tabIndex={0}
|
||||
/>
|
||||
<span>
|
||||
child1
|
||||
</span>
|
||||
<span>
|
||||
child2
|
||||
</span>
|
||||
<div
|
||||
onFocus={[Function]}
|
||||
tabIndex={0}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./TrapFocus";
|
||||
export { default } from "./TrapFocus";
|
||||
@@ -1,8 +1,8 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import React, { Ref } from "react";
|
||||
import { HTMLAttributes, ReactNode, StatelessComponent } from "react";
|
||||
|
||||
import { withStyles } from "talk-ui/hocs";
|
||||
import { withForwardRef, withStyles } from "talk-ui/hocs";
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import * as styles from "./Typography.css";
|
||||
@@ -77,6 +77,12 @@ interface InnerProps extends HTMLAttributes<any> {
|
||||
* Applies the theme typography styles.
|
||||
*/
|
||||
variant?: Variant;
|
||||
|
||||
/** Ref to the root element */
|
||||
ref?: Ref<HTMLElement>;
|
||||
|
||||
/** Internal: Forwarded Ref */
|
||||
forwardRef?: Ref<HTMLElement>;
|
||||
}
|
||||
|
||||
const Typography: StatelessComponent<InnerProps> = props => {
|
||||
@@ -91,6 +97,7 @@ const Typography: StatelessComponent<InnerProps> = props => {
|
||||
noWrap,
|
||||
paragraph,
|
||||
variant,
|
||||
forwardRef,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@@ -116,7 +123,7 @@ const Typography: StatelessComponent<InnerProps> = props => {
|
||||
const Component =
|
||||
component || (paragraph ? "p" : headlineMapping![variant!]) || "span";
|
||||
|
||||
return <Component className={rootClassName} {...rest} />;
|
||||
return <Component ref={forwardRef} className={rootClassName} {...rest} />;
|
||||
};
|
||||
|
||||
Typography.defaultProps = {
|
||||
@@ -139,6 +146,6 @@ Typography.defaultProps = {
|
||||
variant: "body1",
|
||||
};
|
||||
|
||||
const enhanced = withStyles(styles)(Typography);
|
||||
const enhanced = withForwardRef(withStyles(styles)(Typography));
|
||||
export type CenterProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
|
||||
@@ -5,3 +5,4 @@ export { default as RelativeTime } from "./RelativeTime";
|
||||
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";
|
||||
|
||||
Reference in New Issue
Block a user