Merge branch 'toggleshow' of https://github.com/coralproject/talk into toggleshow

This commit is contained in:
Chi Vinh Le
2018-07-20 11:05:59 -03:00
52 changed files with 1062 additions and 149 deletions
+17 -1
View File
@@ -21,6 +21,12 @@ jobs:
at: ~/coralproject/talk
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: Update NPM
command: sudo npm update -g npm
- run:
name: Audit dependencies
command: npm audit
- run:
name: Install dependencies
command: npm install
@@ -46,6 +52,10 @@ jobs:
# unit_tests will run the unit tests.
unit_tests:
<<: *job_defaults
environment:
<<: *job_environment
CI: true
JEST_JUNIT_OUTPUT: "reports/junit/js-test-results.xml"
steps:
- checkout
- attach_workspace:
@@ -55,7 +65,13 @@ jobs:
command: npm run compile
- run:
name: Perform testing
command: npm run test
# We're running these tests in band to avoid errors where the circleci
# test runner runs out of RAM trying to run them all in parallel.
command: npm run test -- --ci --runInBand --testResultsProcessor="jest-junit"
- store_test_results:
path: reports/junit
- store_artifacts:
path: reports/junit
# build will build the static assets and server typescript files.
build:
+3 -1
View File
@@ -42,7 +42,9 @@ const config: Config = {
},
runDocz: {
paths: [],
executor: new LongRunningExecutor("npm run docz -- dev"),
executor: new LongRunningExecutor(
"NODE_ENV=development npm run docz -- dev"
),
},
},
defaultSet: "client",
+4
View File
@@ -25,6 +25,10 @@ export default {
options: {
modules: true,
importLoaders: 1,
localIdentName:
process.env.NODE_ENV === "production"
? "[hash:base64]"
: "[name]-[local]-[hash:base64:5]",
},
},
{
+53
View File
@@ -12421,6 +12421,35 @@
"pretty-format": "^23.2.0"
}
},
"jest-junit": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-5.1.0.tgz",
"integrity": "sha512-3EVf1puv2ox5wybQDfLX3AEn3IKOgDV4E76y4pO2hBu46DEtAFZZAm//X1pzPQpqKji0zqgMIzqzF/K+uGAX9A==",
"dev": true,
"requires": {
"jest-validate": "^23.0.1",
"mkdirp": "^0.5.1",
"strip-ansi": "^4.0.0",
"xml": "^1.0.1"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
}
}
},
"jest-leak-detector": {
"version": "23.2.0",
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.2.0.tgz",
@@ -14289,6 +14318,12 @@
"css-mediaquery": "^0.1.2"
}
},
"material-design-icons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz",
"integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=",
"dev": true
},
"math-expression-evaluator": {
"version": "1.2.17",
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
@@ -21758,6 +21793,18 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
"typeface-manuale": {
"version": "0.0.54",
"resolved": "https://registry.npmjs.org/typeface-manuale/-/typeface-manuale-0.0.54.tgz",
"integrity": "sha512-wWgWFPSvoxEDdm8Dmq0cZzgmBfRXho5WhxU6x4dKBk7WIBsyvRgcjT+j7cQ3ah/9VYp1+nJzQWhCue+TiqA7FA==",
"dev": true
},
"typeface-source-sans-pro": {
"version": "0.0.54",
"resolved": "https://registry.npmjs.org/typeface-source-sans-pro/-/typeface-source-sans-pro-0.0.54.tgz",
"integrity": "sha512-4nsvldyZsOBuvR4oEuUMTquJFVssP4iIVnMEIU4paCWo2940b6r/t95sk7KwDpCablS8DprM/YlhpgqnS7gpKg==",
"dev": true
},
"typescript": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
@@ -23133,6 +23180,12 @@
"integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
"dev": true
},
"xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=",
"dev": true
},
"xml-name-validator": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
+4
View File
@@ -113,8 +113,10 @@
"graphql-playground-middleware-express": "^1.7.2",
"html-webpack-plugin": "^3.2.0",
"jest": "^23.4.1",
"jest-junit": "^5.1.0",
"jsdom": "^11.11.0",
"loader-utils": "^1.1.0",
"material-design-icons": "^3.0.1",
"npm-run-all": "^4.1.3",
"postcss-advanced-variables": "^2.3.3",
"postcss-css-variables": "^0.9.0",
@@ -157,6 +159,8 @@
"tslint-plugin-prettier": "^1.3.0",
"tslint-react": "^3.6.0",
"typed-css-modules": "^0.3.4",
"typeface-manuale": "0.0.54",
"typeface-source-sans-pro": "0.0.54",
"typescript": "^2.9.2",
"uglifyjs-webpack-plugin": "^1.2.5",
"webpack": "4.12.0",
+5 -1
View File
@@ -22,7 +22,11 @@ let argv = process.argv.slice(2);
argv.push("--config", paths.appJestConfig);
// Watch unless on CI or in coverage mode
if (!process.env.CI && argv.indexOf("--coverage") < 0) {
if (
!process.env.CI && // ensure that the ci env var is not set.
argv.indexOf("--ci") < 0 && // ensure that the ci flag is not passed
argv.indexOf("--coverage") < 0 // ensure that the coverage flag is not passed
) {
argv.push("--watch");
}
@@ -22,10 +22,10 @@ function createMutationContainer<T extends string, I, R>(
): InferableComponentEnhancer<{ [P in T]: (input: I) => Promise<R> }> {
return compose(
withContext(({ relayEnvironment }) => ({ relayEnvironment })),
hoistStatics((WrappedComponent: React.ComponentType<any>) => {
hoistStatics((BaseComponent: React.ComponentType<any>) => {
class CreateMutationContainer extends React.Component<any> {
public static displayName = wrapDisplayName(
WrappedComponent,
BaseComponent,
"createMutationContainer"
);
@@ -38,7 +38,7 @@ function createMutationContainer<T extends string, I, R>(
const inject = {
[propName]: this.commit,
};
return <WrappedComponent {...rest} {...inject} />;
return <BaseComponent {...rest} {...inject} />;
}
}
return CreateMutationContainer as React.ComponentType<any>;
@@ -34,7 +34,7 @@ function withLocalStateContainer<T>(
): InferableComponentEnhancer<{ local: T }> {
return compose(
withContext(({ relayEnvironment }) => ({ relayEnvironment })),
hoistStatics((WrappedComponent: React.ComponentType<any>) => {
hoistStatics((BaseComponent: React.ComponentType<any>) => {
class LocalStateContainer extends React.Component<Props, any> {
constructor(props: Props) {
super(props);
@@ -65,7 +65,7 @@ function withLocalStateContainer<T>(
public render() {
const { relayEnvironment: _, ...rest } = this.props;
return <WrappedComponent {...rest} local={this.state.data} />;
return <BaseComponent {...rest} local={this.state.data} />;
}
}
return LocalStateContainer as React.ComponentType<any>;
+1 -1
View File
@@ -1,2 +1,2 @@
// TODO: (@cvle) Extract useful common types into its own package.
export { Diff, Omit, Overwrite, PropTypesOf } from "talk-ui/types";
export { Omit, Overwrite, PropTypesOf } from "talk-ui/types";
@@ -39,4 +39,11 @@ describe("when there is more", () => {
.simulate("click");
expect((props.onShowAll as SinonSpy).calledOnce).toBe(true);
});
const wrapperDisabledButton = shallow(
<ReplyList {...props} disableShowAll />
);
it("disables load more button", () => {
expect(wrapperDisabledButton).toMatchSnapshot();
});
});
@@ -1,14 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<Flex
<withPropsOnChange(Flex)
className="App-root"
justifyContent="center"
>
<Relay(StreamContainer)
asset={Object {}}
/>
</Flex>
</withPropsOnChange(Flex)>
`;
exports[`renders correctly when asset is null 1`] = `
@@ -2,7 +2,7 @@
exports[`renders correctly 1`] = `
<Indent>
<Flex
<withPropsOnChange(Flex)
direction="column"
id="talk-comments-replyList-log--comment-id"
itemGutter={true}
@@ -24,13 +24,56 @@ exports[`renders correctly 1`] = `
}
key="comment-2"
/>
</Flex>
</withPropsOnChange(Flex)>
</Indent>
`;
exports[`when there is more disables load more button 1`] = `
<Indent>
<withPropsOnChange(Flex)
direction="column"
id="talk-comments-replyList-log--comment-id"
itemGutter={true}
role="log"
>
<Relay(CommentContainer)
data={
Object {
"id": "comment-1",
}
}
key="comment-1"
/>
<Relay(CommentContainer)
data={
Object {
"id": "comment-2",
}
}
key="comment-2"
/>
<Localized
id="comments-replyList-showAll"
>
<withPropsOnChange(Button)
aria-controls="talk-comments-replyList-log--comment-id"
disabled={true}
fullWidth={true}
id="talk-comments-replyList-showAll--comment-id"
invert={true}
onClick={[Function]}
secondary={true}
>
Show All Replies
</withPropsOnChange(Button)>
</Localized>
</withPropsOnChange(Flex)>
</Indent>
`;
exports[`when there is more renders a load more button 1`] = `
<Indent>
<Flex
<withPropsOnChange(Flex)
direction="column"
id="talk-comments-replyList-log--comment-id"
itemGutter={true}
@@ -67,6 +110,6 @@ exports[`when there is more renders a load more button 1`] = `
Show All Replies
</withPropsOnChange(Button)>
</Localized>
</Flex>
</withPropsOnChange(Flex)>
</Indent>
`;
@@ -10,14 +10,14 @@ exports[`renders correctly 1`] = `
<withContext(createMutationContainer(PostCommentFormContainer))
assetID="asset-id"
/>
<Flex
<withPropsOnChange(Flex)
aria-live="polite"
direction="column"
id="talk-comments-stream-log"
itemGutter={true}
role="log"
>
<Flex
<withPropsOnChange(Flex)
direction="column"
itemGutter={true}
key="comment-1"
@@ -36,8 +36,8 @@ exports[`renders correctly 1`] = `
}
}
/>
</Flex>
<Flex
</withPropsOnChange(Flex)>
<withPropsOnChange(Flex)
direction="column"
itemGutter={true}
key="comment-2"
@@ -56,8 +56,8 @@ exports[`renders correctly 1`] = `
}
}
/>
</Flex>
</Flex>
</withPropsOnChange(Flex)>
</withPropsOnChange(Flex)>
</div>
`;
@@ -71,14 +71,14 @@ exports[`when there is more disables load more button 1`] = `
<withContext(createMutationContainer(PostCommentFormContainer))
assetID="asset-id"
/>
<Flex
<withPropsOnChange(Flex)
aria-live="polite"
direction="column"
id="talk-comments-stream-log"
itemGutter={true}
role="log"
>
<Flex
<withPropsOnChange(Flex)
direction="column"
itemGutter={true}
key="comment-1"
@@ -97,8 +97,8 @@ exports[`when there is more disables load more button 1`] = `
}
}
/>
</Flex>
<Flex
</withPropsOnChange(Flex)>
<withPropsOnChange(Flex)
direction="column"
itemGutter={true}
key="comment-2"
@@ -117,7 +117,7 @@ exports[`when there is more disables load more button 1`] = `
}
}
/>
</Flex>
</withPropsOnChange(Flex)>
<Localized
id="comments-stream-loadMore"
>
@@ -133,7 +133,7 @@ exports[`when there is more disables load more button 1`] = `
Load More
</withPropsOnChange(Button)>
</Localized>
</Flex>
</withPropsOnChange(Flex)>
</div>
`;
@@ -147,14 +147,14 @@ exports[`when there is more renders a load more button 1`] = `
<withContext(createMutationContainer(PostCommentFormContainer))
assetID="asset-id"
/>
<Flex
<withPropsOnChange(Flex)
aria-live="polite"
direction="column"
id="talk-comments-stream-log"
itemGutter={true}
role="log"
>
<Flex
<withPropsOnChange(Flex)
direction="column"
itemGutter={true}
key="comment-1"
@@ -173,8 +173,8 @@ exports[`when there is more renders a load more button 1`] = `
}
}
/>
</Flex>
<Flex
</withPropsOnChange(Flex)>
<withPropsOnChange(Flex)
direction="column"
itemGutter={true}
key="comment-2"
@@ -193,7 +193,7 @@ exports[`when there is more renders a load more button 1`] = `
}
}
/>
</Flex>
</withPropsOnChange(Flex)>
<Localized
id="comments-stream-loadMore"
>
@@ -209,6 +209,6 @@ exports[`when there is more renders a load more button 1`] = `
Load More
</withPropsOnChange(Button)>
</Localized>
</Flex>
</withPropsOnChange(Flex)>
</div>
`;
@@ -33,6 +33,9 @@ exports[`loads more comments 1`] = `
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
>
Post
</button>
@@ -169,6 +172,9 @@ exports[`renders comment stream 1`] = `
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
>
Post
</button>
@@ -247,6 +253,9 @@ exports[`renders comment stream 1`] = `
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
>
Load More
</button>
@@ -33,6 +33,9 @@ exports[`renders comment stream 1`] = `
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
>
Post
</button>
@@ -33,6 +33,9 @@ exports[`renders comment stream 1`] = `
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
>
Post
</button>
@@ -33,6 +33,9 @@ exports[`renders comment stream 1`] = `
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
>
Post
</button>
@@ -114,6 +117,9 @@ exports[`renders comment stream 1`] = `
onClick={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
>
Show All Replies
</button>
@@ -158,6 +164,9 @@ exports[`show all replies 1`] = `
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
>
Post
</button>
@@ -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>
`;
+40 -32
View File
@@ -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 */
}
+28 -13
View File
@@ -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();
});
+29 -9
View File
@@ -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;
+1
View File
@@ -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";
+2
View File
@@ -1,2 +1,4 @@
export { default as withStyles } from "./withStyles";
export { default as withKeyboardFocus } from "./withKeyboardFocus";
export { default as withMouseHover } from "./withMouseHover";
export { default as withForwardRef } from "./withForwardRef";
@@ -0,0 +1,36 @@
import React, { Ref } from "react";
// TODO: ForwardRef API is not supported in enzymes shallow rendering
// https://github.com/airbnb/enzyme/issues/1553
// As for now we do props passing instead until full React16 support lands in
// enzyme.
/**
* withForwardRef provides a property called `forwardRef` using
* the `React.forwardRef` api.
*/
/*
function withForwardRef<P extends { ref?: Ref<any>; forwardRef?: Ref<any> }>(
BaseComponent: React.ComponentType<P>
): React.ComponentType<Omit<P, "forwardRef">> {
const forwardRef: RefForwardingComponent<any, P> = (props, ref) => (
<BaseComponent {...props} forwardRef={ref} />
);
return React.forwardRef<any, P>(forwardRef);
}
// TODO: workaround, add bug link.
export default withForwardRef as <
P extends { ref?: Ref<any>; forwardRef?: Ref<any> }
>(
BaseComponent: React.ComponentType<P>
) => React.ComponentType<P>;
*/
// Stub, currently doesn't do anything except adding types.
export default function withForwardRef<P extends { forwardRef?: Ref<any> }>(
BaseComponent: React.ComponentType<P>
): React.ComponentType<P> {
return BaseComponent;
}
+12 -7
View File
@@ -3,10 +3,10 @@ import { FocusEvent, MouseEvent } from "react";
import { hoistStatics } from "recompose";
interface InjectedProps {
onFocus?: React.EventHandler<FocusEvent<any>>;
onBlur?: React.EventHandler<FocusEvent<any>>;
onMouseDown?: React.EventHandler<MouseEvent<any>>;
keyboardFocus?: boolean;
onFocus: React.EventHandler<FocusEvent<any>>;
onBlur: React.EventHandler<FocusEvent<any>>;
onMouseDown: React.EventHandler<MouseEvent<any>>;
keyboardFocus: boolean;
}
/**
@@ -14,8 +14,8 @@ interface InjectedProps {
* to indicate a focus on the element, that wasn't triggered by mouse
* or touch.
*/
export default hoistStatics<InjectedProps>(
<T extends InjectedProps>(WrappedComponent: React.ComponentType<T>) => {
const withKeyboardFocus = hoistStatics<InjectedProps>(
<T extends InjectedProps>(BaseComponent: React.ComponentType<T>) => {
class WithKeyboardFocus extends React.Component<any> {
public state = {
keyboardFocus: false,
@@ -48,7 +48,7 @@ export default hoistStatics<InjectedProps>(
public render() {
return (
<WrappedComponent
<BaseComponent
{...this.props}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
@@ -62,3 +62,8 @@ export default hoistStatics<InjectedProps>(
return WithKeyboardFocus as React.ComponentType<any>;
}
);
// TODO: workaround, add bug link.
export default withKeyboardFocus as <P extends Partial<InjectedProps>>(
BaseComponent: React.ComponentType<P>
) => React.ComponentType<P>;
@@ -0,0 +1,69 @@
import * as React from "react";
import { MouseEvent, TouchEvent } from "react";
import { hoistStatics } from "recompose";
interface InjectedProps {
onMouseOver: React.EventHandler<MouseEvent<any>>;
onMouseOut: React.EventHandler<MouseEvent<any>>;
onTouchEnd: React.EventHandler<TouchEvent<any>>;
mouseHover: boolean;
}
/**
* withMouseHover provides a property `MouseHover: boolean`
* to indicate a focus on the element, that wasn't triggered by mouse
* or touch.
*/
const withMouseHover = hoistStatics<InjectedProps>(
<T extends InjectedProps>(BaseComponent: React.ComponentType<T>) => {
class WithMouseHover extends React.Component<any> {
public state = {
mouseHover: false,
lastTouchEndTime: 0,
};
private handleTouchEnd: React.EventHandler<TouchEvent<any>> = event => {
if (this.props.onTouchEnd) {
this.props.onTouchEnd(event);
}
this.setState({ lastTouchEndTime: new Date().getTime() });
};
private handleMouseOver: React.EventHandler<MouseEvent<any>> = event => {
if (this.props.onMouseOver) {
this.props.onMouseOver(event);
}
const now = new Date().getTime();
if (now - this.state.lastTouchEndTime > 750) {
this.setState({ mouseHover: true });
}
};
private handleMouseOut: React.EventHandler<MouseEvent<any>> = event => {
if (this.props.onMouseOut) {
this.props.onMouseOut(event);
}
this.setState({ mouseHover: false });
};
public render() {
return (
<BaseComponent
{...this.props}
onMouseOver={this.handleMouseOver}
onMouseOut={this.handleMouseOut}
onTouchEnd={this.handleTouchEnd}
mouseHover={this.state.mouseHover}
/>
);
}
}
return WithMouseHover as React.ComponentType<any>;
}
);
// TODO: workaround, add bug link.
export default withMouseHover as <P extends Partial<InjectedProps>>(
BaseComponent: React.ComponentType<P>
) => React.ComponentType<P>;
+3 -2
View File
@@ -13,17 +13,18 @@ function withStyles<T>(
): DefaultingInferableComponentEnhancer<{ classes?: Partial<T> }> {
const classes = { ...(styles as any) };
return withPropsOnChange<any, any>(["classes"], props => {
const resolvedClasses = { ...classes };
if (props.classes) {
Object.keys(props.classes).forEach(k => {
if (classes[k]) {
classes[k] += ` ${props.classes[k]}`;
resolvedClasses[k] += ` ${props.classes[k]}`;
} else if (process.env.NODE_ENV !== "production") {
// tslint:disable:next-line: no-console
console.warn("Extending non existant className", k);
}
});
}
return { classes };
return { classes: resolvedClasses };
});
}
+58
View File
@@ -1,3 +1,61 @@
@font-face {
font-family: "Source Sans Pro";
font-style: normal;
font-display: swap;
font-weight: 300;
src: local("Source Sans Pro Light "), local("Source Sans Pro-Light"),
url("typeface-source-sans-pro/files/source-sans-pro-latin-300.woff2")
format("woff2"),
url("typeface-source-sans-pro/files/source-sans-pro-latin-300.woff")
format("woff");
}
@font-face {
font-family: "Source Sans Pro";
font-style: normal;
font-display: swap;
font-weight: 400;
src: local("Source Sans Pro Regular italic"),
local("Source Sans Pro-Regularitalic"),
url("typeface-source-sans-pro/files/source-sans-pro-latin-400.woff2")
format("woff2"),
url("typeface-source-sans-pro/files/source-sans-pro-latin-400.woff")
format("woff");
}
@font-face {
font-family: "Source Sans Pro";
font-style: normal;
font-display: swap;
font-weight: 600;
src: local("Source Sans Pro SemiBold italic"),
local("Source Sans Pro-SemiBolditalic"),
url("typeface-source-sans-pro/files/source-sans-pro-latin-600.woff2")
format("woff2"),
url("typeface-source-sans-pro/files/source-sans-pro-latin-600.woff")
format("woff");
}
@font-face {
font-family: "Manuale";
font-style: normal;
font-display: swap;
font-weight: 400;
src: local("Manuale Regular "), local("Manuale-Regular"),
url("typeface-manuale/files/manuale-latin-400.woff2") format("woff2"),
url("typeface-manuale/files/manuale-latin-400.woff") format("woff");
}
@font-face {
font-family: "Manuale";
font-style: normal;
font-display: swap;
font-weight: 600;
src: local("Manuale SemiBold "), local("Manuale-SemiBold"),
url("typeface-manuale/files/manuale-latin-600.woff2") format("woff2"),
url("typeface-manuale/files/manuale-latin-600.woff") format("woff");
}
.heading1 {
font-size: calc(24rem / var(--rem-base));
font-weight: var(--font-weight-medium);
+2 -2
View File
@@ -19,7 +19,7 @@ const variables = {
dark: "#65696B",
main: "#787D80",
light: "#9A9DA0",
lighter: "#636E72",
lighter: "#BBBEBF",
},
/* Success colors */
success: {
@@ -68,7 +68,7 @@ const variables = {
fontSize: 16,
fontWeightLight: 300,
fontWeightRegular: 400,
fontWeightMedium: 550,
fontWeightMedium: 600,
/* Breakpoints */
breakpoints: {
xs: 320,
+2 -10
View File
@@ -2,27 +2,19 @@ import React from "react";
// TODO: Extract useful common types into its own package.
/**
* Returns literals types that are in T but not in U.
*
* E.g. Diff<"a" | "b", "a"> = "b"
*/
export type Diff<T extends keyof any, U extends keyof any> = ({ [P in T]: P } &
{ [P in U]: never } & { [x: string]: never; [x: number]: never })[T];
/**
* Overwrite properties of `T`.
*
* E.g. Omit<{a: boolean, b: boolean}, "b"> = {a: boolean}
*/
export type Omit<U, K extends keyof U> = Pick<U, Diff<keyof U, K>>;
export type Omit<U, K extends keyof U> = Pick<U, Exclude<keyof U, K>>;
/**
* Overwrite properties of `T`.
*
* E.g. Overwrite<{a: boolean}, {a: string}> = {a: string}
*/
export type Overwrite<T, U> = Pick<T, Diff<keyof T, keyof U>> & U;
export type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;
/**
* Returns the PropTypes from a React Component.