This commit is contained in:
Nathan Bierema 2020-06-27 09:18:26 -04:00
parent 93d6bc4350
commit f21ab41244
116 changed files with 1461 additions and 757 deletions

View File

@ -1,4 +1,11 @@
{ {
"presets": ["@babel/preset-env", "@babel/preset-react"], "presets": [
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-export-default-from"] "@babel/env",
"@babel/react",
"@babel/typescript"
],
"plugins": [
"@babel/proposal-class-properties"
],
"sourceType": "unambiguous"
} }

View File

@ -0,0 +1,2 @@
lib
demo

View File

@ -0,0 +1,21 @@
module.exports = {
extends: '../../.eslintrc',
overrides: [
{
files: ['*.ts', '*.tsx'],
extends: '../../eslintrc.ts.react.base.json',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json']
}
},
{
files: ['test/*.ts', 'test/*.tsx'],
extends: '../../eslintrc.ts.react.jest.base.json',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./test/tsconfig.json']
}
}
]
};

View File

@ -0,0 +1,2 @@
lib
demo

View File

@ -1,9 +1,9 @@
import { configure, setAddon, addDecorator } from '@storybook/react'; import { configure, addDecorator } from '@storybook/react';
import { withOptions } from '@storybook/addon-options'; import { withOptions } from '@storybook/addon-options';
import { withInfo } from '@storybook/addon-info'; import { withInfo } from '@storybook/addon-info';
import { withKnobs } from '@storybook/addon-knobs'; import { withKnobs } from '@storybook/addon-knobs';
import { withTheme } from './themeAddon/theme'; import { withTheme } from './themeAddon/theme';
import '../src/presets.js'; import '../src/presets';
addDecorator( addDecorator(
withOptions({ withOptions({
@ -21,7 +21,7 @@ addDecorator(withTheme);
addDecorator(withKnobs); addDecorator(withKnobs);
addDecorator(withInfo); addDecorator(withInfo);
const req = require.context('../src/', true, /stories\/index\.js$/); const req = require.context('../src/', true, /stories\/index\.tsx$/);
function loadStories() { function loadStories() {
req.keys().forEach(filename => req(filename)); req.keys().forEach(filename => req(filename));

View File

@ -2,7 +2,7 @@ import React from 'react';
import Form from '@storybook/addon-knobs/dist/components/PropForm'; import Form from '@storybook/addon-knobs/dist/components/PropForm';
import styled from 'styled-components'; import styled from 'styled-components';
import { EVENT_ID_DATA, DEFAULT_THEME_STATE } from './constant'; import { EVENT_ID_DATA, DEFAULT_THEME_STATE } from './constant';
import { listSchemes, listThemes } from '../../src/utils/theme'; import { listSchemes, listThemes } from '../../lib/utils/theme';
const FormWrapper = styled.div` const FormWrapper = styled.div`
width: 100%; width: 100%;

14
packages/devui/.storybook/webpack.config.js Executable file → Normal file
View File

@ -1,8 +1,12 @@
const path = require('path'); const path = require('path');
const TSDocgenPlugin = require('react-docgen-typescript-webpack-plugin');
module.exports = (baseConfig, env, defaultConfig) => { module.exports = (baseConfig, env, config) => {
// Add custom webpack config here like: config.module.rules.push({
// defaultConfig.module.rules.push test: /\.(ts|tsx)$/,
loader: require.resolve('babel-loader')
return defaultConfig; });
config.plugins.push(new TSDocgenPlugin()); // optional
config.resolve.extensions.push('.ts', '.tsx');
return config;
}; };

View File

@ -12,13 +12,22 @@
}, },
"author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)", "author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
"license": "MIT", "license": "MIT",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": { "scripts": {
"type-check": "tsc --noEmit",
"type-check:watch": "npm run type-check -- --watch",
"start": "npm run storybook", "start": "npm run storybook",
"build": "rimraf ./lib && babel ./src --out-dir ./lib --ignore tests,stories", "clean": "rimraf lib",
"build": "npm run build:types && npm run build:js",
"build:types": "tsc --emitDeclarationOnly",
"build:js": "babel src --out-dir lib --ignore tests,stories --extensions \".ts,.tsx\" --source-maps inline",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"lint:css": "stylelint './src/**/styles/*.js'", "lint:css": "stylelint './src/**/styles/*.js'",
"test:update": "npm run jest -- -u", "test:update": "npm run jest -- -u",
"test": "jest --no-cache", "test": "jest --no-cache",
"storybook": "start-storybook -p 9001 -c .storybook -s ./fonts", "storybook": "npm run build && start-storybook -p 9001 -c .storybook -s ./fonts",
"publish-storybook": "bash .scripts/publish_storybook.sh", "publish-storybook": "bash .scripts/publish_storybook.sh",
"prepare": "npm run build", "prepare": "npm run build",
"prepublishOnly": "npm run test && npm run build" "prepublishOnly": "npm run test && npm run build"
@ -36,11 +45,22 @@
"@babel/plugin-transform-runtime": "^7.2.0", "@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.3.1", "@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@storybook/addon-actions": "^4.1.4", "@storybook/addon-actions": "^4.1.18",
"@storybook/addon-info": "^4.1.4", "@storybook/addon-info": "^4.1.18",
"@storybook/addon-knobs": "^4.1.4", "@storybook/addon-knobs": "^4.1.18",
"@storybook/addon-options": "^4.1.4", "@storybook/addon-options": "^4.1.18",
"@storybook/react": "4.0.9", "@storybook/react": "^4.1.18",
"@types/codemirror": "^0.0.95",
"@types/color": "^2.0.1",
"@types/json-schema": "^7.0.4",
"@types/react-icons": "^2.2.7",
"@types/react-jsonschema-form": "^1.7.3",
"@types/react-select": "^1.3.4",
"@types/storybook__addon-actions": "^3.4.3",
"@types/storybook__addon-knobs": "^4.0.5",
"@types/storybook__react": "^4.0.2",
"babel-loader": "^8.1.0",
"csstype": "^2.6.10",
"enzyme": "^3.1.0", "enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.2", "enzyme-adapter-react-16": "^1.0.2",
"enzyme-to-json": "^3.1.4", "enzyme-to-json": "^3.1.4",
@ -48,18 +68,20 @@
"jest": "^24.1.0", "jest": "^24.1.0",
"jsdom": "^11.3.0", "jsdom": "^11.3.0",
"react": "^16.0.0", "react": "^16.0.0",
"react-addons-test-utils": "^15.6.2", "react-docgen-typescript-webpack-plugin": "^1.1.0",
"react-dom": "^16.0.0", "react-dom": "^16.0.0",
"react-test-renderer": "^16.0.0", "react-test-renderer": "^16.0.0",
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"stylelint": "^7.6.0", "stylelint": "^7.6.0",
"stylelint-config-standard": "^15.0.0", "stylelint-config-standard": "^15.0.0",
"stylelint-processor-styled-components": "^0.0.4" "stylelint-processor-styled-components": "^0.0.4",
"typescript": "^3.8.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^0.14.9 || ^15.3.0" "react": "^0.14.9 || ^15.3.0"
}, },
"dependencies": { "dependencies": {
"@types/prop-types": "^15.6.0",
"base16": "^1.0.0", "base16": "^1.0.0",
"codemirror": "^5.21.0", "codemirror": "^5.21.0",
"color": "^2.0.0", "color": "^2.0.0",
@ -73,6 +95,5 @@
}, },
"jest": { "jest": {
"setupTestFrameworkScriptFile": "<rootDir>/tests/setup.js" "setupTestFrameworkScriptFile": "<rootDir>/tests/setup.js"
}, }
"main": "lib/index.js"
} }

View File

@ -1,92 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import createStyledComponent from '../utils/createStyledComponent';
import * as styles from './styles';
import { commonStyle, tooltipStyle } from './styles/common';
const ButtonWrapper = createStyledComponent(styles, 'button');
const TooltipWrapper = createStyledComponent(tooltipStyle);
const CommonWrapper = createStyledComponent(commonStyle);
export default class Button extends Component {
shouldComponentUpdate(nextProps) {
return (
nextProps.children !== this.props.children ||
nextProps.disabled !== this.props.disabled ||
nextProps.mark !== this.props.mark ||
nextProps.size !== this.props.size ||
nextProps.primary !== this.props.primary ||
nextProps.tooltipPosition !== this.props.tooltipPosition ||
nextProps.title !== this.props.title
);
}
onMouseUp = e => {
e.target.blur();
};
render() {
const button = (
<ButtonWrapper
theme={this.props.theme}
aria-label={this.props.title}
primary={this.props.primary}
disabled={this.props.disabled}
onMouseUp={this.onMouseUp}
onClick={this.props.onClick}
type={this.props.type}
>
{this.props.children}
</ButtonWrapper>
);
const Wrapper = this.props.title ? TooltipWrapper : CommonWrapper;
return (
<Wrapper
theme={this.props.theme}
tooltipTitle={this.props.title}
tooltipPosition={this.props.tooltipPosition}
size={this.props.size}
mark={this.props.mark}
>
{button}
</Wrapper>
);
}
}
Button.propTypes = {
children: PropTypes.any.isRequired,
title: PropTypes.string,
tooltipPosition: PropTypes.oneOf([
'top',
'bottom',
'left',
'right',
'bottom-left',
'bottom-right',
'top-left',
'top-right'
]),
onClick: PropTypes.func,
type: PropTypes.string,
disabled: PropTypes.bool,
primary: PropTypes.bool,
size: PropTypes.oneOf(['big', 'normal', 'small']),
mark: PropTypes.oneOf([
false,
'base08',
'base09',
'base0A',
'base0B',
'base0C',
'base0D',
'base0E',
'base0F'
]),
theme: PropTypes.object
};
Button.defaultProps = {
tooltipPosition: 'top'
};

View File

@ -0,0 +1,128 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import createStyledComponent from '../utils/createStyledComponent';
import * as styles from './styles';
import { commonStyle, tooltipStyle } from './styles/common';
import { Theme } from '../themes/default';
const ButtonWrapper = createStyledComponent(styles, 'button');
const TooltipWrapper = createStyledComponent(tooltipStyle);
const CommonWrapper = createStyledComponent(commonStyle);
export type TooltipPosition =
| 'top'
| 'bottom'
| 'left'
| 'right'
| 'bottom-left'
| 'bottom-right'
| 'top-left'
| 'top-right';
export type Size = 'big' | 'normal' | 'small';
export type Mark =
| 'base08'
| 'base09'
| 'base0A'
| 'base0B'
| 'base0C'
| 'base0D'
| 'base0E'
| 'base0F';
interface Props {
children: unknown;
title?: string;
tooltipPosition: TooltipPosition;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
type?: 'button' | 'reset' | 'submit';
disabled?: boolean;
primary?: boolean;
size?: Size;
mark?: Mark | false;
theme?: Theme;
}
export default class Button extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return (
nextProps.children !== this.props.children ||
nextProps.disabled !== this.props.disabled ||
nextProps.mark !== this.props.mark ||
nextProps.size !== this.props.size ||
nextProps.primary !== this.props.primary ||
nextProps.tooltipPosition !== this.props.tooltipPosition ||
nextProps.title !== this.props.title
);
}
onMouseUp: React.MouseEventHandler<HTMLButtonElement> = e => {
e.currentTarget.blur();
};
render() {
const button = (
<ButtonWrapper
theme={this.props.theme}
aria-label={this.props.title}
primary={this.props.primary}
disabled={this.props.disabled}
onMouseUp={this.onMouseUp}
onClick={this.props.onClick}
type={this.props.type}
>
{this.props.children}
</ButtonWrapper>
);
const Wrapper = this.props.title ? TooltipWrapper : CommonWrapper;
return (
<Wrapper
theme={this.props.theme}
tooltipTitle={this.props.title}
tooltipPosition={this.props.tooltipPosition}
size={this.props.size}
mark={this.props.mark}
>
{button}
</Wrapper>
);
}
static propTypes = {
children: PropTypes.any.isRequired,
title: PropTypes.string,
tooltipPosition: PropTypes.oneOf([
'top',
'bottom',
'left',
'right',
'bottom-left',
'bottom-right',
'top-left',
'top-right'
]),
onClick: PropTypes.func,
type: PropTypes.string,
disabled: PropTypes.bool,
primary: PropTypes.bool,
size: PropTypes.oneOf(['big', 'normal', 'small']),
mark: PropTypes.oneOf([
false,
'base08',
'base09',
'base0A',
'base0B',
'base0C',
'base0D',
'base0E',
'base0F'
]),
theme: PropTypes.object
};
static defaultProps = {
tooltipPosition: 'top'
};
}

View File

@ -5,6 +5,7 @@ import { action } from '@storybook/addon-actions';
import { withKnobs, text, boolean, select } from '@storybook/addon-knobs'; import { withKnobs, text, boolean, select } from '@storybook/addon-knobs';
import MdFiberManualRecord from 'react-icons/lib/md/fiber-manual-record'; import MdFiberManualRecord from 'react-icons/lib/md/fiber-manual-record';
import Button from '../'; import Button from '../';
import { Mark, Size, TooltipPosition } from '../Button';
export const Container = styled.div` export const Container = styled.div`
display: flex; display: flex;
@ -20,7 +21,9 @@ storiesOf('Button', module)
<Container> <Container>
<Button <Button
title={text('Title', 'Hello Tooltip! \\a And from new line hello!')} title={text('Title', 'Hello Tooltip! \\a And from new line hello!')}
tooltipPosition={select('tooltipPosition', [ tooltipPosition={select<TooltipPosition>(
'tooltipPosition',
[
'top', 'top',
'bottom', 'bottom',
'left', 'left',
@ -29,9 +32,11 @@ storiesOf('Button', module)
'bottom-right', 'bottom-right',
'top-left', 'top-left',
'top-right' 'top-right'
])} ],
'top'
)}
primary={boolean('primary', true)} primary={boolean('primary', true)}
size={select('size', ['big', 'normal', 'small'], 'normal')} size={select<Size>('size', ['big', 'normal', 'small'], 'normal')}
disabled={boolean('Disabled', false)} disabled={boolean('Disabled', false)}
onClick={action('button clicked')} onClick={action('button clicked')}
> >
@ -42,7 +47,7 @@ storiesOf('Button', module)
.add('mark', () => ( .add('mark', () => (
<Container> <Container>
<Button <Button
mark={select( mark={select<Mark>(
'mark', 'mark',
[ [
'base08', 'base08',
@ -57,7 +62,9 @@ storiesOf('Button', module)
'base08' 'base08'
)} )}
title={text('Title', 'Hello Tooltip')} title={text('Title', 'Hello Tooltip')}
tooltipPosition={select('tooltipPosition', [ tooltipPosition={select<TooltipPosition>(
'tooltipPosition',
[
'top', 'top',
'bottom', 'bottom',
'left', 'left',
@ -66,8 +73,10 @@ storiesOf('Button', module)
'bottom-right', 'bottom-right',
'top-left', 'top-left',
'top-right' 'top-right'
])} ],
size={select('size', ['big', 'normal', 'small'], 'normal')} 'top'
)}
size={select<Size>('size', ['big', 'normal', 'small'], 'normal')}
disabled={boolean('Disabled', false)} disabled={boolean('Disabled', false)}
onClick={action('button clicked')} onClick={action('button clicked')}
> >

View File

@ -1,8 +1,10 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { fadeIn } from '../../utils/animations'; import { fadeIn } from '../../utils/animations';
import colorEffect from '../../utils/color'; import colorEffect from '../../utils/color';
import { Mark, Size, TooltipPosition } from '../Button';
import { Theme } from '../../themes/default';
const both = tooltipPosition => { const both = (tooltipPosition: TooltipPosition) => {
switch (tooltipPosition) { switch (tooltipPosition) {
case 'bottom': case 'bottom':
return ` return `
@ -46,7 +48,7 @@ const both = tooltipPosition => {
} }
}; };
const before = tooltipPosition => { const before = (tooltipPosition: TooltipPosition) => {
switch (tooltipPosition) { switch (tooltipPosition) {
case 'bottom-left': case 'bottom-left':
return ` return `
@ -69,7 +71,7 @@ const before = tooltipPosition => {
} }
}; };
const after = (tooltipPosition, color) => { const after = (tooltipPosition: TooltipPosition, color: string) => {
switch (tooltipPosition) { switch (tooltipPosition) {
case 'bottom': case 'bottom':
return ` return `
@ -110,13 +112,13 @@ const after = (tooltipPosition, color) => {
} }
}; };
const getDirection = tooltipPosition => { const getDirection = (tooltipPosition: TooltipPosition) => {
return tooltipPosition.indexOf('-') > 0 return tooltipPosition.indexOf('-') > 0
? tooltipPosition.substring(0, tooltipPosition.indexOf('-')) ? tooltipPosition.substring(0, tooltipPosition.indexOf('-'))
: tooltipPosition; : tooltipPosition;
}; };
const getSize = size => { const getSize = (size: Size | undefined) => {
switch (size) { switch (size) {
case 'big': case 'big':
return 'min-height: 34px; padding: 2px 12px;'; return 'min-height: 34px; padding: 2px 12px;';
@ -127,7 +129,16 @@ const getSize = size => {
} }
}; };
export const commonStyle = ({ theme, mark, size }) => css` interface CommonStyleProps {
size: Size | undefined;
mark: Mark | false | undefined;
}
export const commonStyle = ({
theme,
mark,
size
}: ThemedStyledProps<CommonStyleProps, Theme>) => css`
display: inline-block; display: inline-block;
position: relative; position: relative;
flex-shrink: 0; flex-shrink: 0;
@ -145,8 +156,8 @@ export const commonStyle = ({ theme, mark, size }) => css`
pointer-events: none; pointer-events: none;
} }
${mark && ${mark
` ? `
background-color: ${colorEffect( background-color: ${colorEffect(
theme[mark], theme[mark],
'fade', 'fade',
@ -160,21 +171,29 @@ export const commonStyle = ({ theme, mark, size }) => css`
stroke-opacity: 0.2; stroke-opacity: 0.2;
user-select: none; user-select: none;
} }
`} `
: ''}
} }
`; `;
interface TooltipStyleProps {
tooltipTitle: string | undefined;
tooltipPosition: TooltipPosition;
size: Size | undefined;
mark: Mark | false | undefined;
}
export const tooltipStyle = ({ export const tooltipStyle = ({
theme, theme,
tooltipTitle, tooltipTitle,
tooltipPosition, tooltipPosition,
mark, mark,
size size
}) => css` }: ThemedStyledProps<TooltipStyleProps, Theme>) => css`
${commonStyle({ theme, mark, size })} ${commonStyle({ theme, mark, size })}
&:before { &:before {
content: "${tooltipTitle}"; content: "${tooltipTitle ?? ''}";
white-space: pre; white-space: pre;
color: ${theme.base06}; color: ${theme.base06};
line-height: 16px; line-height: 16px;
@ -208,8 +227,9 @@ export const tooltipStyle = ({
${theme.type === 'material' ? `animation: ${fadeIn} 500ms;` : ''} ${theme.type === 'material' ? `animation: ${fadeIn} 500ms;` : ''}
} }
${theme.type !== 'material' && ${
` theme.type !== 'material'
? `
&:after { &:after {
content: ""; content: "";
border-style: solid; border-style: solid;
@ -218,7 +238,9 @@ export const tooltipStyle = ({
${after(tooltipPosition, theme.base02)} ${after(tooltipPosition, theme.base02)}
${getDirection(tooltipPosition)}: 7px; ${getDirection(tooltipPosition)}: 7px;
} }
`} `
: ''
}
&:hover:after, &:hover:after,
&:hover:before { &:hover:before {

View File

@ -1,6 +1,16 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { Theme } from '../../themes/default';
export const style = ({ theme, primary, disabled }) => css` export interface StyleProps {
primary: boolean | undefined;
disabled: boolean | undefined;
}
export const style = ({
theme,
primary,
disabled
}: ThemedStyledProps<StyleProps, Theme>) => css`
box-sizing: border-box; box-sizing: border-box;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
outline: none; outline: none;
@ -33,14 +43,17 @@ export const style = ({ theme, primary, disabled }) => css`
` `
} }
${!disabled && ${
` !disabled
? `
&:hover, &:hover,
&:focus { &:focus {
background-color: ${primary ? theme.base07 : theme.base02}; background-color: ${primary ? theme.base07 : theme.base02};
box-shadow: 1px 1px 2px ${theme.base03}; box-shadow: 1px 1px 2px ${theme.base03};
} }
`} `
: ''
}
&:focus { &:focus {
border: 1px solid ${theme.base0D}; border: 1px solid ${theme.base0D};
} }

View File

@ -1,7 +1,13 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { ripple } from '../../utils/animations'; import { ripple } from '../../utils/animations';
import { StyleProps } from './default';
import { Theme } from '../../themes/default';
export const style = ({ theme, primary, disabled }) => css` export const style = ({
theme,
primary,
disabled
}: ThemedStyledProps<StyleProps, Theme>) => css`
box-sizing: border-box; box-sizing: border-box;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
outline: none; outline: none;

View File

@ -1,10 +1,23 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ThemeProvider } from 'styled-components'; import { ThemeProvider } from 'styled-components';
import { getTheme } from '../utils/theme'; import { Theme } from '../themes/default';
import { getTheme, ThemeData } from '../utils/theme';
import { MainContainerWrapper, ContainerWrapper } from './styles'; import { MainContainerWrapper, ContainerWrapper } from './styles';
const Container = ({ themeData, className, theme, children }) => { interface Props {
children?: React.ReactNode;
themeData?: ThemeData;
theme?: Theme;
className?: string;
}
const Container: React.FunctionComponent<Props> = ({
themeData,
className,
theme,
children
}) => {
if (!themeData) { if (!themeData) {
return ( return (
<ContainerWrapper className={className} theme={theme}> <ContainerWrapper className={className} theme={theme}>
@ -24,8 +37,8 @@ const Container = ({ themeData, className, theme, children }) => {
Container.propTypes = { Container.propTypes = {
children: PropTypes.node, children: PropTypes.node,
themeData: PropTypes.object, themeData: PropTypes.any,
theme: PropTypes.object, theme: PropTypes.any,
className: PropTypes.string className: PropTypes.string
}; };

View File

@ -2,16 +2,34 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createStyledComponent from '../utils/createStyledComponent'; import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles/index'; import styles from './styles/index';
import { ReactButtonElement } from '../Tabs/Tabs';
const ContextMenuWrapper = createStyledComponent(styles); const ContextMenuWrapper = createStyledComponent(styles);
export default class ContextMenu extends Component { type Item = { name: string; value?: string } | ReactButtonElement;
constructor(props) {
function isReactButtonElement(item: Item): item is ReactButtonElement {
return (item as ReactButtonElement).type === 'button';
}
interface Props {
items: Item[];
onClick: (value: string) => void;
x: number;
y: number;
visible?: boolean;
}
export default class ContextMenu extends Component<Props> {
constructor(props: Props) {
super(props); super(props);
this.updateItems(props.items); this.updateItems(props.items);
} }
componentWillReceiveProps(nextProps) { menu?: HTMLDivElement | null;
items?: React.ReactNode[];
componentWillReceiveProps(nextProps: Props) {
if ( if (
nextProps.items !== this.props.items || nextProps.items !== this.props.items ||
nextProps.visible !== this.props.visible nextProps.visible !== this.props.visible
@ -24,25 +42,25 @@ export default class ContextMenu extends Component {
this.amendPosition(); this.amendPosition();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: Props) {
if (prevProps.x !== this.props.x || prevProps.y !== this.props.y) { if (prevProps.x !== this.props.x || prevProps.y !== this.props.y) {
this.amendPosition(); this.amendPosition();
} }
} }
onMouseUp = e => { onMouseUp: React.MouseEventHandler<HTMLButtonElement> = e => {
e.target.blur(); e.currentTarget.blur();
}; };
onClick = e => { onClick: React.MouseEventHandler<HTMLButtonElement> = e => {
this.props.onClick(e.target.value); this.props.onClick(e.currentTarget.value);
}; };
amendPosition() { amendPosition() {
const { x, y } = this.props; const { x, y } = this.props;
const { scrollTop, scrollLeft } = document.documentElement; const { scrollTop, scrollLeft } = document.documentElement;
const { innerWidth, innerHeight } = window; const { innerWidth, innerHeight } = window;
const rect = this.menu.getBoundingClientRect(); const rect = this.menu!.getBoundingClientRect();
let left = x + scrollLeft; let left = x + scrollLeft;
let top = y + scrollTop; let top = y + scrollTop;
@ -59,14 +77,14 @@ export default class ContextMenu extends Component {
left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0; left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
} }
this.menu.style.top = `${top}px`; this.menu!.style.top = `${top}px`;
this.menu.style.left = `${left}px`; this.menu!.style.left = `${left}px`;
} }
updateItems(items) { updateItems(items: Item[]) {
this.items = items.map(item => { this.items = items.map(item => {
if (isReactButtonElement(item)) return item;
const value = item.value || item.name; const value = item.value || item.name;
if (item.type === 'button') return item;
return ( return (
<button <button
key={value} key={value}
@ -80,7 +98,7 @@ export default class ContextMenu extends Component {
}); });
} }
menuRef = c => { menuRef: React.RefCallback<HTMLDivElement> = c => {
this.menu = c; this.menu = c;
}; };
@ -96,12 +114,12 @@ export default class ContextMenu extends Component {
</ContextMenuWrapper> </ContextMenuWrapper>
); );
} }
}
ContextMenu.propTypes = { static propTypes = {
items: PropTypes.array.isRequired, items: PropTypes.array.isRequired,
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
x: PropTypes.number.isRequired, x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired, y: PropTypes.number.isRequired,
visible: PropTypes.bool visible: PropTypes.bool
}; };
}

View File

@ -1 +0,0 @@
export default from './ContextMenu';

View File

@ -0,0 +1 @@
export { default } from './ContextMenu';

View File

@ -1,6 +1,18 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { Theme } from '../../themes/default';
export default ({ theme, left, top, visible }) => css` interface StyleProps {
left: number;
top: number;
visible: boolean | undefined;
}
export default ({
theme,
left,
top,
visible
}: ThemedStyledProps<StyleProps, Theme>) => css`
${visible ${visible
? ` ? `
visibility: visible; visibility: visible;

View File

@ -4,20 +4,60 @@ import createStyledComponent from '../utils/createStyledComponent';
import * as styles from './styles'; import * as styles from './styles';
import Button from '../Button'; import Button from '../Button';
import Form from '../Form'; import Form from '../Form';
import { Props as FormProps } from '../Form/Form';
import { Theme } from '../themes/default';
const DialogWrapper = createStyledComponent(styles); const DialogWrapper = createStyledComponent(styles);
export default class Dialog extends (PureComponent || Component) { interface Props {
open?: boolean;
title?: string;
children?: React.ReactNode;
actions?: React.ReactNode[];
submitText?: string;
fullWidth?: boolean;
noHeader?: boolean;
noFooter?: boolean;
modal?: boolean;
onDismiss: (
e: React.MouseEvent<HTMLButtonElement | HTMLDivElement> | false
) => void;
onSubmit: () => void;
theme?: Theme;
}
type Rest<P> = Omit<
Props & FormProps<P>,
| 'modal'
| 'open'
| 'fullWidth'
| 'title'
| 'children'
| 'actions'
| 'noHeader'
| 'noFooter'
| 'submitText'
| 'onDismiss'
>;
function isForm<P>(rest?: FormProps<P>): rest is FormProps<P> {
return (rest as FormProps<P>).schema !== undefined;
}
export default class Dialog<P> extends (PureComponent || Component)<
Props | (Props & FormProps<P>)
> {
submitButton?: HTMLInputElement | null;
onSubmit = () => { onSubmit = () => {
if (this.submitButton) this.submitButton.click(); if (this.submitButton) this.submitButton.click();
else this.props.onSubmit(); else this.props.onSubmit();
}; };
getFormButtonRef = node => { getFormButtonRef: React.RefCallback<HTMLInputElement> = node => {
this.submitButton = node; this.submitButton = node;
}; };
onKeyDown = e => { onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = e => {
if (e.keyCode === 27 /* esc */) { if (e.keyCode === 27 /* esc */) {
e.preventDefault(); e.preventDefault();
this.props.onDismiss(false); this.props.onDismiss(false);
@ -38,7 +78,7 @@ export default class Dialog extends (PureComponent || Component) {
onDismiss, onDismiss,
...rest ...rest
} = this.props; } = this.props;
const schema = rest.schema; const schema = (rest as Props & FormProps<P>).schema;
return ( return (
<DialogWrapper <DialogWrapper
@ -47,7 +87,7 @@ export default class Dialog extends (PureComponent || Component) {
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
theme={rest.theme} theme={rest.theme}
> >
<div onClick={!modal && onDismiss} /> <div onClick={!modal ? onDismiss : undefined} />
<div> <div>
{!noHeader && ( {!noHeader && (
<div className="mc-dialog--header"> <div className="mc-dialog--header">
@ -57,8 +97,8 @@ export default class Dialog extends (PureComponent || Component) {
)} )}
<div className="mc-dialog--body"> <div className="mc-dialog--body">
{children} {children}
{schema && ( {isForm(rest as FormProps<P>) && (
<Form {...rest}> <Form {...(rest as FormProps<P>)}>
{!noFooter && ( {!noFooter && (
<input <input
type="submit" type="submit"
@ -97,9 +137,8 @@ export default class Dialog extends (PureComponent || Component) {
</DialogWrapper> </DialogWrapper>
); );
} }
}
Dialog.propTypes = { static propTypes = {
open: PropTypes.bool, open: PropTypes.bool,
title: PropTypes.string, title: PropTypes.string,
children: PropTypes.any, children: PropTypes.any,
@ -113,3 +152,4 @@ Dialog.propTypes = {
onSubmit: PropTypes.func, onSubmit: PropTypes.func,
theme: PropTypes.object theme: PropTypes.object
}; };
}

View File

@ -1,6 +1,16 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { Theme } from '../../themes/default';
export const style = ({ theme, open, fullWidth }) => css` export interface StyleProps {
open: boolean | undefined;
fullWidth: boolean | undefined;
}
export const style = ({
theme,
open,
fullWidth
}: ThemedStyledProps<StyleProps, Theme>) => css`
position: fixed; position: fixed;
top: 0px; top: 0px;
right: 0px; right: 0px;

View File

@ -1,6 +1,12 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { StyleProps } from './default';
import { Theme } from '../../themes/default';
export const style = ({ theme, open, fullWidth }) => css` export const style = ({
theme,
open,
fullWidth
}: ThemedStyledProps<StyleProps, Theme>) => css`
position: fixed; position: fixed;
top: 0px; top: 0px;
right: 0px; right: 0px;

View File

@ -1,88 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import CodeMirror from 'codemirror';
import { defaultStyle, themedStyle } from './styles';
const EditorContainer = styled.div('', ({ theme }) =>
theme.scheme === 'default' && theme.light ? defaultStyle : themedStyle(theme)
);
export default class Editor extends Component {
componentDidMount() {
this.cm = CodeMirror(this.node, {
value: this.props.value,
mode: this.props.mode,
lineNumbers: this.props.lineNumbers,
lineWrapping: this.props.lineWrapping,
readOnly: this.props.readOnly,
autofocus: this.props.autofocus,
foldGutter: this.props.foldGutter,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
});
if (this.props.onChange) {
this.cm.on('change', (doc, change) => {
this.props.onChange(doc.getValue(), change);
});
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.cm.getValue()) {
this.cm.setValue(nextProps.value);
}
if (nextProps.readOnly !== this.props.readOnly) {
this.cm.setOption('readOnly', nextProps.readOnly);
}
if (nextProps.lineNumbers !== this.props.lineNumbers) {
this.cm.setOption('lineNumbers', nextProps.lineNumbers);
}
if (nextProps.lineWrapping !== this.props.lineWrapping) {
this.cm.setOption('lineWrapping', nextProps.lineWrapping);
}
if (nextProps.foldGutter !== this.props.foldGutter) {
this.cm.setOption('foldGutter', nextProps.foldGutter);
}
}
shouldComponentUpdate() {
return false;
}
componentWillUnmount() {
const node = this.node;
node.removeChild(node.children[0]);
this.cm = null;
}
getRef = node => {
this.node = node;
};
render() {
return <EditorContainer innerRef={this.getRef} theme={this.props.theme} />;
}
}
Editor.propTypes = {
value: PropTypes.string,
mode: PropTypes.string,
lineNumbers: PropTypes.bool,
lineWrapping: PropTypes.bool,
readOnly: PropTypes.bool,
theme: PropTypes.object,
foldGutter: PropTypes.bool,
autofocus: PropTypes.bool,
onChange: PropTypes.func
};
Editor.defaultProps = {
value: '',
mode: 'javascript',
lineNumbers: true,
lineWrapping: false,
readOnly: false,
foldGutter: true,
autofocus: false
};

View File

@ -0,0 +1,108 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import CodeMirror from 'codemirror';
import { defaultStyle, themedStyle } from './styles';
import { Theme } from '../themes/default';
const EditorContainer = styled.div(
('' as unknown) as TemplateStringsArray,
({ theme }: { theme: Theme }) =>
theme.scheme === 'default' && theme.light
? defaultStyle
: themedStyle(theme)
);
interface Props {
value: string;
mode: string;
lineNumbers: boolean;
lineWrapping: boolean;
readOnly: boolean;
theme?: Theme;
foldGutter: boolean;
autofocus: boolean;
onChange: (value: string, change: CodeMirror.EditorChangeLinkedList) => void;
}
export default class Editor extends Component<Props> {
cm?: CodeMirror.Editor | null;
node?: HTMLDivElement | null;
componentDidMount() {
this.cm = CodeMirror(this.node!, {
value: this.props.value,
mode: this.props.mode,
lineNumbers: this.props.lineNumbers,
lineWrapping: this.props.lineWrapping,
readOnly: this.props.readOnly,
autofocus: this.props.autofocus,
foldGutter: this.props.foldGutter,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
});
if (this.props.onChange) {
this.cm.on('change', (doc, change) => {
this.props.onChange(doc.getValue(), change);
});
}
}
componentWillReceiveProps(nextProps: Props) {
if (nextProps.value !== this.cm!.getValue()) {
this.cm!.setValue(nextProps.value);
}
if (nextProps.readOnly !== this.props.readOnly) {
this.cm!.setOption('readOnly', nextProps.readOnly);
}
if (nextProps.lineNumbers !== this.props.lineNumbers) {
this.cm!.setOption('lineNumbers', nextProps.lineNumbers);
}
if (nextProps.lineWrapping !== this.props.lineWrapping) {
this.cm!.setOption('lineWrapping', nextProps.lineWrapping);
}
if (nextProps.foldGutter !== this.props.foldGutter) {
this.cm!.setOption('foldGutter', nextProps.foldGutter);
}
}
shouldComponentUpdate() {
return false;
}
componentWillUnmount() {
const node = this.node!;
node.removeChild(node.children[0]);
this.cm = null;
}
getRef: React.RefCallback<HTMLDivElement> = node => {
this.node = node;
};
render() {
return <EditorContainer innerRef={this.getRef} theme={this.props.theme} />;
}
static propTypes = {
value: PropTypes.string,
mode: PropTypes.string,
lineNumbers: PropTypes.bool,
lineWrapping: PropTypes.bool,
readOnly: PropTypes.bool,
theme: PropTypes.object,
foldGutter: PropTypes.bool,
autofocus: PropTypes.bool,
onChange: PropTypes.func
};
static defaultProps = {
value: '',
mode: 'javascript',
lineNumbers: true,
lineWrapping: false,
readOnly: false,
foldGutter: true,
autofocus: false
};
}

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { Component, ComponentType } from 'react';
import { select } from '@storybook/addon-knobs';
import Editor from '../'; import Editor from '../';
import Tabs from '../../Tabs'; import Tabs from '../../Tabs';
@ -11,8 +10,17 @@ const value2 = `
const func2 = () => {} const func2 = () => {}
`; `;
interface Props {
lineNumbers: boolean;
}
interface TabProps {
value: string;
lineNumbers: boolean;
}
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
export default class WithTabs extends Component { export default class WithTabs extends Component<Props> {
state = { state = {
selected: 'Function 1' selected: 'Function 1'
}; };
@ -20,16 +28,16 @@ export default class WithTabs extends Component {
render() { render() {
const { lineNumbers } = this.props; const { lineNumbers } = this.props;
return ( return (
<Tabs <Tabs<TabProps>
tabs={[ tabs={[
{ {
name: 'Function 1', name: 'Function 1',
component: Editor, component: (Editor as unknown) as ComponentType<TabProps>,
selector: () => ({ value: value1, lineNumbers }) selector: () => ({ value: value1, lineNumbers })
}, },
{ {
name: 'Function 2', name: 'Function 2',
component: Editor, component: (Editor as unknown) as ComponentType<TabProps>,
selector: () => ({ value: value2, lineNumbers }) selector: () => ({ value: value2, lineNumbers })
} }
]} ]}
@ -37,7 +45,6 @@ export default class WithTabs extends Component {
onClick={selected => { onClick={selected => {
this.setState({ selected }); this.setState({ selected });
}} }}
align={select('align', ['left', 'right', 'center'], 'left')}
/> />
); );
} }

View File

@ -1,4 +1,5 @@
import { css } from 'styled-components'; import { css } from 'styled-components';
import { Theme } from '../../themes/default';
export const defaultStyle = ` export const defaultStyle = `
height: 100%; height: 100%;
@ -10,7 +11,7 @@ export const defaultStyle = `
} }
`; `;
export const themedStyle = theme => css` export const themedStyle = (theme: Theme) => css`
height: 100%; height: 100%;
> div { > div {

View File

@ -1,14 +1,23 @@
import React, { PureComponent, Component } from 'react'; import React, { PureComponent, Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import JSONSchemaForm from 'react-jsonschema-form'; import JSONSchemaForm, { FormProps } from 'react-jsonschema-form';
import createStyledComponent from '../utils/createStyledComponent'; import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles'; import styles from './styles';
import Button from '../Button'; import Button from '../Button';
import customWidgets from './widgets'; import customWidgets from './widgets';
import { Theme } from '../themes/default';
const FormContainer = createStyledComponent(styles, JSONSchemaForm); const FormContainer = createStyledComponent(styles, JSONSchemaForm);
export default class Form extends (PureComponent || Component) { export interface Props<T> extends FormProps<T> {
children?: React.ReactNode;
submitText?: string;
primaryButton?: boolean;
noSubmit?: boolean;
theme?: Theme;
}
export default class Form<T> extends (PureComponent || Component)<Props<T>> {
render() { render() {
const { const {
widgets, widgets,
@ -19,7 +28,10 @@ export default class Form extends (PureComponent || Component) {
...rest ...rest
} = this.props; } = this.props;
return ( return (
<FormContainer {...rest} widgets={{ ...customWidgets, ...widgets }}> <FormContainer
{...(rest as Props<unknown>)}
widgets={{ ...customWidgets, ...widgets }}
>
{noSubmit ? ( {noSubmit ? (
<noscript /> <noscript />
) : ( ) : (
@ -37,9 +49,8 @@ export default class Form extends (PureComponent || Component) {
</FormContainer> </FormContainer>
); );
} }
}
Form.propTypes = { static propTypes = {
children: PropTypes.any, children: PropTypes.any,
submitText: PropTypes.string, submitText: PropTypes.string,
primaryButton: PropTypes.bool, primaryButton: PropTypes.bool,
@ -51,3 +62,4 @@ Form.propTypes = {
PropTypes.oneOfType([PropTypes.func, PropTypes.object]) PropTypes.oneOfType([PropTypes.func, PropTypes.object])
) )
}; };
}

View File

@ -1,89 +0,0 @@
module.exports = {
schema: {
title: 'Example form',
description: 'A simple form example.',
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
title: 'Full name'
},
age: {
type: 'integer',
title: 'Age'
},
bio: {
type: 'string',
title: 'Bio'
},
password: {
type: 'string',
title: 'Password',
minLength: 3
},
multipleChoicesList: {
type: 'array',
title: 'A multiple choices list',
items: {
type: 'string',
enum: ['foo', 'bar', 'fuzz']
},
uniqueItems: true
},
numberEnum: {
type: 'number',
title: 'Number enum',
enum: [1, 2, 3]
},
numberEnumRadio: {
type: 'number',
title: 'Number enum',
enum: [1, 2, 3]
},
integerRange: {
title: 'Integer range',
type: 'integer',
minimum: 42,
maximum: 100
}
}
},
uiSchema: {
name: {
'ui:autofocus': true
},
age: {
'ui:widget': 'updown'
},
bio: {
'ui:widget': 'textarea'
},
password: {
'ui:widget': 'password',
'ui:help': 'Hint: Make it strong!'
},
date: {
'ui:widget': 'alt-datetime'
},
multipleChoicesList: {
'ui:widget': 'checkboxes'
},
numberEnumRadio: {
'ui:widget': 'radio',
'ui:options': {
inline: true
}
},
integerRange: {
'ui:widget': 'range'
}
},
formData: {
name: 'Chuck Norris',
age: 75,
bio: 'Roundhouse kicking asses since 1940',
password: 'noneed',
integerRange: 52
}
};

View File

@ -0,0 +1,91 @@
import { JSONSchema6 } from 'json-schema';
export const schema: JSONSchema6 = {
title: 'Example form',
description: 'A simple form example.',
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
title: 'Full name'
},
age: {
type: 'integer',
title: 'Age'
},
bio: {
type: 'string',
title: 'Bio'
},
password: {
type: 'string',
title: 'Password',
minLength: 3
},
multipleChoicesList: {
type: 'array',
title: 'A multiple choices list',
items: {
type: 'string',
enum: ['foo', 'bar', 'fuzz']
},
uniqueItems: true
},
numberEnum: {
type: 'number',
title: 'Number enum',
enum: [1, 2, 3]
},
numberEnumRadio: {
type: 'number',
title: 'Number enum',
enum: [1, 2, 3]
},
integerRange: {
title: 'Integer range',
type: 'integer',
minimum: 42,
maximum: 100
}
}
};
export const uiSchema = {
name: {
'ui:autofocus': true
},
age: {
'ui:widget': 'updown'
},
bio: {
'ui:widget': 'textarea'
},
password: {
'ui:widget': 'password',
'ui:help': 'Hint: Make it strong!'
},
date: {
'ui:widget': 'alt-datetime'
},
multipleChoicesList: {
'ui:widget': 'checkboxes'
},
numberEnumRadio: {
'ui:widget': 'radio',
'ui:options': {
inline: true
}
},
integerRange: {
'ui:widget': 'range'
}
};
export const formData = {
name: 'Chuck Norris',
age: 75,
bio: 'Roundhouse kicking asses since 1940',
password: 'noneed',
integerRange: 52
};

View File

@ -1,6 +1,7 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { Theme } from '../../themes/default';
export default ({ theme }) => css` export default ({ theme }: ThemedStyledProps<{}, Theme>) => css`
padding: 10px; padding: 10px;
line-height: 1.846; line-height: 1.846;
font-size: 14px; font-size: 14px;

View File

@ -1,31 +0,0 @@
import React from 'react';
import Select from '../Select';
import Slider from '../Slider';
/* eslint-disable react/prop-types */
const SelectWidget = ({ options, multi, ...rest }) => (
<Select options={options.enumOptions} multiple={multi} {...rest} />
);
const RangeWidget = ({
schema,
readonly,
autofocus,
label, // eslint-disable-line
options, // eslint-disable-line
formContext, // eslint-disable-line
registry, // eslint-disable-line
...rest
}) => (
<Slider
{...rest}
autoFocus={autofocus}
readOnly={readonly}
min={schema.minimum}
max={schema.maximum}
step={schema.multipleOf}
withValue
/>
);
export default { SelectWidget, RangeWidget };

View File

@ -0,0 +1,31 @@
import React from 'react';
import Select from '../Select';
import Slider from '../Slider';
import { FieldProps, Widget, WidgetProps } from 'react-jsonschema-form';
import { Options } from 'react-select';
/* eslint-disable react/prop-types */
const SelectWidget: Widget = ({
options,
onFocus,
onBlur,
...rest
}: WidgetProps) => (
<Select options={options.enumOptions as Options} {...rest} />
);
const RangeWidget: Widget = (({
schema,
readonly,
autofocus,
label, // eslint-disable-line
options, // eslint-disable-line
formContext, // eslint-disable-line
registry, // eslint-disable-line
...rest
}: WidgetProps & { registry: FieldProps['registry'] }) =>
(
<Slider {...rest} min={schema.minimum} max={schema.maximum} withValue />
) as unknown) as Widget;
export default { SelectWidget, RangeWidget };

View File

@ -6,11 +6,21 @@ import ErrorIcon from 'react-icons/lib/md/error';
import SuccessIcon from 'react-icons/lib/md/check-circle'; import SuccessIcon from 'react-icons/lib/md/check-circle';
import createStyledComponent from '../utils/createStyledComponent'; import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles'; import styles from './styles';
import { Theme } from '../themes/default';
const NotificationWrapper = createStyledComponent(styles); const NotificationWrapper = createStyledComponent(styles);
export default class Notification extends Component { export type Type = 'info' | 'success' | 'warning' | 'error';
shouldComponentUpdate(nextProps) {
interface Props {
children?: React.ReactNode;
type: Type;
onClose?: React.MouseEventHandler<HTMLButtonElement>;
theme?: Theme;
}
export default class Notification extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return ( return (
nextProps.children !== this.props.children || nextProps.children !== this.props.children ||
nextProps.type !== this.props.type nextProps.type !== this.props.type
@ -43,15 +53,15 @@ export default class Notification extends Component {
</NotificationWrapper> </NotificationWrapper>
); );
} }
}
Notification.propTypes = { static propTypes = {
children: PropTypes.any.isRequired, children: PropTypes.any.isRequired,
type: PropTypes.oneOf(['info', 'success', 'warning', 'error']), type: PropTypes.oneOf(['info', 'success', 'warning', 'error']),
onClose: PropTypes.func, onClose: PropTypes.func,
theme: PropTypes.object theme: PropTypes.object
}; };
Notification.defaultProps = { static defaultProps = {
type: 'info' type: 'info'
}; };
}

View File

@ -4,6 +4,7 @@ import { action } from '@storybook/addon-actions';
import styled from 'styled-components'; import styled from 'styled-components';
import { withKnobs, text, select } from '@storybook/addon-knobs'; import { withKnobs, text, select } from '@storybook/addon-knobs';
import Notification from '../'; import Notification from '../';
import { Type } from '../Notification';
export const Container = styled.div` export const Container = styled.div`
display: flex; display: flex;
@ -18,7 +19,7 @@ storiesOf('Notification', module)
.add('default', () => ( .add('default', () => (
<Container> <Container>
<Notification <Notification
type={select( type={select<Type>(
'type', 'type',
['info', 'success', 'warning', 'error'], ['info', 'success', 'warning', 'error'],
'warning' 'warning'

View File

@ -1,6 +1,8 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { Theme } from '../../themes/default';
import { Type } from '../Notification';
const getBackground = (theme, type) => { const getBackground = (theme: Theme, type: Type) => {
switch (type) { switch (type) {
case 'success': case 'success':
return `background-color: ${theme.base0B};`; return `background-color: ${theme.base0B};`;
@ -13,7 +15,11 @@ const getBackground = (theme, type) => {
} }
}; };
export default ({ theme, type }) => css` interface StyleProps {
type: Type;
}
export default ({ theme, type }: ThemedStyledProps<StyleProps, Theme>) => css`
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
flex-shrink: 0; flex-shrink: 0;

View File

@ -2,23 +2,32 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createStyledComponent from '../utils/createStyledComponent'; import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles'; import styles from './styles';
import { Theme } from '../themes/default';
const SegmentedWrapper = createStyledComponent(styles); const SegmentedWrapper = createStyledComponent(styles);
export default class SegmentedControl extends Component { interface Props {
shouldComponentUpdate(nextProps) { values: string[];
selected?: string;
onClick: (value: string) => void;
disabled?: boolean;
theme?: Theme;
}
export default class SegmentedControl extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return ( return (
nextProps.disabled !== this.props.disabled || nextProps.disabled !== this.props.disabled ||
nextProps.selected !== this.props.selected nextProps.selected !== this.props.selected
); );
} }
onClick = e => { onClick: React.MouseEventHandler<HTMLButtonElement> = e => {
this.props.onClick(e.target.value); this.props.onClick(e.currentTarget.value);
}; };
onMouseUp = e => { onMouseUp: React.MouseEventHandler<HTMLButtonElement> = e => {
e.target.blur(); e.currentTarget.blur();
}; };
render() { render() {
@ -39,12 +48,12 @@ export default class SegmentedControl extends Component {
</SegmentedWrapper> </SegmentedWrapper>
); );
} }
}
SegmentedControl.propTypes = { static propTypes = {
values: PropTypes.array.isRequired, values: PropTypes.array.isRequired,
selected: PropTypes.string, selected: PropTypes.string,
onClick: PropTypes.func, onClick: PropTypes.func,
disabled: PropTypes.bool, disabled: PropTypes.bool,
theme: PropTypes.object theme: PropTypes.object
}; };
}

View File

@ -1,7 +1,15 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import color from '../../utils/color'; import color from '../../utils/color';
import { Theme } from '../../themes/default';
export default ({ theme, disabled }) => css` interface StyleProps {
disabled: boolean | undefined;
}
export default ({
theme,
disabled
}: ThemedStyledProps<StyleProps, Theme>) => css`
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;

View File

@ -1,34 +0,0 @@
import React, { PureComponent, Component } from 'react';
import PropTypes from 'prop-types';
import ReactSelect from 'react-select';
import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles';
const SelectContainer = createStyledComponent(styles, ReactSelect);
export default class Select extends (PureComponent || Component) {
render() {
return <SelectContainer {...this.props} />;
}
}
Select.propTypes = {
autosize: PropTypes.bool, // whether to enable autosizing or not
clearable: PropTypes.bool, // should it be possible to reset value
disabled: PropTypes.bool, // whether the Select is disabled or not
isLoading: PropTypes.bool, // whether the Select is loading externally or not
menuMaxHeight: PropTypes.number, // maximum css height for the opened menu of options
multi: PropTypes.bool, // multi-value input
searchable: PropTypes.bool, // whether to enable searching feature or not
simpleValue: PropTypes.bool, // pass the value with label to onChange
value: PropTypes.any, // initial field value
valueKey: PropTypes.string, // path of the label value in option objects
openOuterUp: PropTypes.bool // value to control the opening direction
};
Select.defaultProps = {
autosize: true,
clearable: false,
simpleValue: true,
menuMaxHeight: 200
};

View File

@ -0,0 +1,42 @@
import React, { PureComponent, Component } from 'react';
import PropTypes from 'prop-types';
import ReactSelect, { ReactSelectProps } from 'react-select';
import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles';
const SelectContainer = createStyledComponent<Props>(
styles,
(ReactSelect as unknown) as React.ComponentClass<Props>
);
interface Props extends Omit<ReactSelectProps, 'ref'> {
menuMaxHeight: number;
openOuterUp?: boolean;
}
export default class Select extends (PureComponent || Component)<Props> {
render() {
return <SelectContainer {...this.props} />;
}
static propTypes = {
autosize: PropTypes.bool, // whether to enable autosizing or not
clearable: PropTypes.bool, // should it be possible to reset value
disabled: PropTypes.bool, // whether the Select is disabled or not
isLoading: PropTypes.bool, // whether the Select is loading externally or not
menuMaxHeight: PropTypes.number, // maximum css height for the opened menu of options
multi: PropTypes.bool, // multi-value input
searchable: PropTypes.bool, // whether to enable searching feature or not
simpleValue: PropTypes.bool, // pass the value with label to onChange
value: PropTypes.any, // initial field value
valueKey: PropTypes.string, // path of the label value in option objects
openOuterUp: PropTypes.bool // value to control the opening direction
};
static defaultProps = {
autosize: true,
clearable: false,
simpleValue: true,
menuMaxHeight: 200
};
}

View File

@ -1 +0,0 @@
export default from './Select';

View File

@ -0,0 +1 @@
export { default } from './Select';

View File

@ -1,7 +1,17 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { fadeIn, spinner } from '../../utils/animations'; import { fadeIn, spinner } from '../../utils/animations';
import { Theme } from '../../themes/default';
export default ({ theme, openOuterUp, menuMaxHeight }) => css` interface StyleProps {
openOuterUp?: boolean;
menuMaxHeight: number;
}
export default ({
theme,
openOuterUp,
menuMaxHeight
}: ThemedStyledProps<StyleProps, Theme>) => css`
&.Select { &.Select {
position: relative; position: relative;

View File

@ -3,12 +3,25 @@ import PropTypes from 'prop-types';
import createStyledComponent from '../utils/createStyledComponent'; import createStyledComponent from '../utils/createStyledComponent';
import * as styles from './styles'; import * as styles from './styles';
import { containerStyle } from './styles/common'; import { containerStyle } from './styles/common';
import { Theme } from '../themes/default';
const SliderWrapper = createStyledComponent(styles); const SliderWrapper = createStyledComponent(styles);
const ContainerWithValue = createStyledComponent(containerStyle); const ContainerWithValue = createStyledComponent(containerStyle);
export default class Slider extends Component { interface Props {
shouldComponentUpdate(nextProps) { value: number;
min: number;
max: number;
label?: string;
sublabel?: string;
withValue?: boolean;
disabled?: boolean;
onChange: (value: number) => void;
theme?: Theme;
}
export default class Slider extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return ( return (
nextProps.label !== this.props.label || nextProps.label !== this.props.label ||
nextProps.value !== this.props.value || nextProps.value !== this.props.value ||
@ -19,7 +32,7 @@ export default class Slider extends Component {
); );
} }
onChange = e => { onChange: React.ChangeEventHandler<HTMLInputElement> = e => {
this.props.onChange(parseFloat(e.target.value)); this.props.onChange(parseFloat(e.target.value));
}; };
@ -53,9 +66,8 @@ export default class Slider extends Component {
</SliderWrapper> </SliderWrapper>
); );
} }
}
Slider.propTypes = { static propTypes = {
value: PropTypes.number, value: PropTypes.number,
min: PropTypes.number, min: PropTypes.number,
max: PropTypes.number, max: PropTypes.number,
@ -67,4 +79,5 @@ Slider.propTypes = {
theme: PropTypes.object theme: PropTypes.object
}; };
Slider.defaultProps = { value: 0, min: 0, max: 100 }; static defaultProps = { value: 0, min: 0, max: 100 };
}

View File

@ -1 +0,0 @@
export default from './Slider';

View File

@ -0,0 +1 @@
export { default } from './Slider';

View File

@ -1,6 +1,7 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { Theme } from '../../themes/default';
export const containerStyle = ({ theme }) => css` export const containerStyle = ({ theme }: ThemedStyledProps<{}, Theme>) => css`
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -6,10 +6,22 @@ Based on:
http://codepen.io/thebabydino/pen/YPOPxr http://codepen.io/thebabydino/pen/YPOPxr
*/ */
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { prefixSelectors } from '../../utils/autoPrefix'; import { prefixSelectors } from '../../utils/autoPrefix';
import { Theme } from '../../themes/default';
export const style = ({ theme, percent, disabled, withLabel }) => css` export interface StyleProps {
percent: number;
disabled: boolean;
withLabel: boolean;
}
export const style = ({
theme,
percent,
disabled,
withLabel
}: ThemedStyledProps<StyleProps, Theme>) => css`
display: block; display: block;
width: 100%; width: 100%;
position: relative; position: relative;
@ -56,9 +68,7 @@ export const style = ({ theme, percent, disabled, withLabel }) => css`
height: 0.8em; height: 0.8em;
border-radius: 0.5em; border-radius: 0.5em;
box-shadow: 0 0 .125em ${theme.base04}; box-shadow: 0 0 .125em ${theme.base04};
background: linear-gradient(${theme.base01}, ${theme.base02} 40%, ${ background: linear-gradient(${theme.base01}, ${theme.base02} 40%, ${theme.base01})
theme.base01
})
no-repeat ${theme.base00}; no-repeat ${theme.base00};
background-size: ${percent}% 100%; background-size: ${percent}% 100%;
}` }`

View File

@ -1,9 +1,16 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { prefixSelectors } from '../../utils/autoPrefix'; import { prefixSelectors } from '../../utils/autoPrefix';
import color from '../../utils/color'; import color from '../../utils/color';
import { animationCurve } from '../../utils/animations'; import { animationCurve } from '../../utils/animations';
import { StyleProps } from './default';
import { Theme } from '../../themes/default';
export const style = ({ theme, percent, disabled, withLabel }) => css` export const style = ({
theme,
percent,
disabled,
withLabel
}: ThemedStyledProps<StyleProps, Theme>) => css`
display: block; display: block;
width: 100%; width: 100%;
position: relative; position: relative;

View File

@ -3,27 +3,54 @@ import PropTypes from 'prop-types';
import TabsHeader from './TabsHeader'; import TabsHeader from './TabsHeader';
import { TabsContainer } from './styles/common'; import { TabsContainer } from './styles/common';
export default class Tabs extends Component { export type ReactButtonElement = React.ReactElement<
constructor(props) { JSX.IntrinsicElements['button'],
'button'
>;
export interface Tab<P> {
name: string;
value?: string;
component?: React.ComponentType<P>;
selector?: (tab: this) => P;
}
export type Position = 'left' | 'right' | 'center';
interface Props<P> {
tabs: Tab<P>[];
selected?: string;
main?: boolean;
onClick: (value: string) => void;
collapsible?: boolean;
position: Position;
}
export default class Tabs<P> extends Component<Props<P>> {
constructor(props: Props<P>) {
super(props); super(props);
this.updateTabs(props); this.updateTabs(props);
} }
componentWillReceiveProps(nextProps) { tabsHeader?: ReactButtonElement[];
SelectedComponent?: React.ComponentType<P>;
selector?: () => P;
componentWillReceiveProps(nextProps: Props<P>) {
if (nextProps.selected !== this.props.selected) { if (nextProps.selected !== this.props.selected) {
this.updateTabs(nextProps); this.updateTabs(nextProps);
} }
} }
onMouseUp = e => { onMouseUp: React.MouseEventHandler<HTMLButtonElement> = e => {
e.target.blur(); e.currentTarget.blur();
}; };
onClick = e => { onClick: React.MouseEventHandler<HTMLButtonElement> = e => {
this.props.onClick(e.target.value); this.props.onClick(e.currentTarget.value);
}; };
updateTabs(props) { updateTabs(props: Props<P>) {
const tabs = props.tabs; const tabs = props.tabs;
const selected = props.selected; const selected = props.selected;
@ -34,7 +61,7 @@ export default class Tabs extends Component {
isSelected = true; isSelected = true;
if (tab.component) { if (tab.component) {
this.SelectedComponent = tab.component; this.SelectedComponent = tab.component;
if (tab.selector) this.selector = () => tab.selector(tab); if (tab.selector) this.selector = () => tab.selector!(tab);
} }
} }
return ( return (
@ -54,7 +81,7 @@ export default class Tabs extends Component {
render() { render() {
const tabsHeader = ( const tabsHeader = (
<TabsHeader <TabsHeader
tabs={this.tabsHeader} tabs={this.tabsHeader!}
items={this.props.tabs} items={this.props.tabs}
main={this.props.main} main={this.props.main}
collapsible={this.props.collapsible} collapsible={this.props.collapsible}
@ -76,14 +103,13 @@ export default class Tabs extends Component {
<TabsContainer position={this.props.position}> <TabsContainer position={this.props.position}>
{tabsHeader} {tabsHeader}
<div> <div>
<this.SelectedComponent {...this.selector && this.selector()} /> <this.SelectedComponent {...(this.selector! && this.selector!())} />
</div> </div>
</TabsContainer> </TabsContainer>
); );
} }
}
Tabs.propTypes = { static propTypes = {
tabs: PropTypes.array.isRequired, tabs: PropTypes.array.isRequired,
selected: PropTypes.string, selected: PropTypes.string,
main: PropTypes.bool, main: PropTypes.bool,
@ -92,4 +118,5 @@ Tabs.propTypes = {
position: PropTypes.oneOf(['left', 'right', 'center']) position: PropTypes.oneOf(['left', 'right', 'center'])
}; };
Tabs.defaultProps = { position: 'left' }; static defaultProps = { position: 'left' };
}

View File

@ -5,11 +5,29 @@ import CollapseIcon from 'react-icons/lib/fa/angle-double-right';
import ContextMenu from '../ContextMenu'; import ContextMenu from '../ContextMenu';
import createStyledComponent from '../utils/createStyledComponent'; import createStyledComponent from '../utils/createStyledComponent';
import * as styles from './styles'; import * as styles from './styles';
import { ReactButtonElement, Tab } from './Tabs';
const TabsWrapper = createStyledComponent(styles); const TabsWrapper = createStyledComponent(styles);
export default class TabsHeader extends Component { interface Props<P> {
constructor(props) { tabs: ReactButtonElement[];
items: Tab<P>[];
main: boolean | undefined;
onClick: (value: string) => void;
position: 'left' | 'right' | 'center';
collapsible: boolean | undefined;
selected: string | undefined;
}
interface State {
visibleTabs: ReactButtonElement[];
hiddenTabs: ReactButtonElement[];
subMenuOpened: boolean;
contextMenu: { top: number; left: number } | undefined;
}
export default class TabsHeader<P> extends Component<Props<P>, State> {
constructor(props: Props<P>) {
super(props); super(props);
this.state = { this.state = {
visibleTabs: props.tabs.slice(), visibleTabs: props.tabs.slice(),
@ -21,7 +39,13 @@ export default class TabsHeader extends Component {
this.hiddenTabsWidth = []; this.hiddenTabsWidth = [];
} }
componentWillReceiveProps(nextProps) { iconWidth: number;
hiddenTabsWidth: number[];
tabsWrapperRef?: HTMLDivElement | null;
tabsRef?: HTMLDivElement | null;
resizeDetector?: HTMLIFrameElement;
componentWillReceiveProps(nextProps: Props<P>) {
if ( if (
nextProps.tabs !== this.props.tabs || nextProps.tabs !== this.props.tabs ||
nextProps.selected !== this.props.selected || nextProps.selected !== this.props.selected ||
@ -38,7 +62,7 @@ export default class TabsHeader extends Component {
} }
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: Props<P>) {
const { collapsible } = this.props; const { collapsible } = this.props;
if (!collapsible) { if (!collapsible) {
if (prevProps.collapsible !== collapsible) this.disableResizeEvents(); if (prevProps.collapsible !== collapsible) this.disableResizeEvents();
@ -47,8 +71,11 @@ export default class TabsHeader extends Component {
let shouldCollapse = false; let shouldCollapse = false;
if (this.iconWidth === 0) { if (this.iconWidth === 0) {
const tabButtons = this.tabsRef.children; const tabButtons = this.tabsRef!.children;
if (this.tabsRef.children[tabButtons.length - 1].value === 'expandIcon') { if (
(this.tabsRef!.children[tabButtons.length - 1] as HTMLButtonElement)
.value === 'expandIcon'
) {
this.iconWidth = tabButtons[ this.iconWidth = tabButtons[
tabButtons.length - 1 tabButtons.length - 1
].getBoundingClientRect().width; ].getBoundingClientRect().width;
@ -73,12 +100,12 @@ export default class TabsHeader extends Component {
} }
enableResizeEvents() { enableResizeEvents() {
this.resizeDetector = observeResize(this.tabsWrapperRef, this.collapse); this.resizeDetector = observeResize(this.tabsWrapperRef!, this.collapse);
window.addEventListener('mousedown', this.hideSubmenu); window.addEventListener('mousedown', this.hideSubmenu);
} }
disableResizeEvents() { disableResizeEvents() {
this.resizeDetector.remove(); this.resizeDetector!.remove();
window.removeEventListener('mousedown', this.hideSubmenu); window.removeEventListener('mousedown', this.hideSubmenu);
} }
@ -88,13 +115,13 @@ export default class TabsHeader extends Component {
const { selected, tabs } = this.props; const { selected, tabs } = this.props;
const tabsWrapperRef = this.tabsWrapperRef; const tabsWrapperRef = this.tabsWrapperRef;
const tabsRef = this.tabsRef; const tabsRef = this.tabsRef;
const tabButtons = this.tabsRef.children; const tabButtons = this.tabsRef!.children;
const visibleTabs = this.state.visibleTabs; const visibleTabs = this.state.visibleTabs;
const hiddenTabs = this.state.hiddenTabs; const hiddenTabs = this.state.hiddenTabs;
let tabsWrapperRight = tabsWrapperRef.getBoundingClientRect().right; let tabsWrapperRight = tabsWrapperRef!.getBoundingClientRect().right;
if (!tabsWrapperRight) return; // tabs are hidden if (!tabsWrapperRight) return; // tabs are hidden
const tabsRefRight = tabsRef.getBoundingClientRect().right; const tabsRefRight = tabsRef!.getBoundingClientRect().right;
let i = visibleTabs.length - 1; let i = visibleTabs.length - 1;
let hiddenTab; let hiddenTab;
@ -102,16 +129,16 @@ export default class TabsHeader extends Component {
if ( if (
this.props.position === 'right' && this.props.position === 'right' &&
hiddenTabs.length > 0 && hiddenTabs.length > 0 &&
tabsRef.getBoundingClientRect().width + this.hiddenTabsWidth[0] < tabsRef!.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
tabsWrapperRef.getBoundingClientRect().width tabsWrapperRef!.getBoundingClientRect().width
) { ) {
while ( while (
i < tabs.length - 1 && i < tabs.length - 1 &&
tabsRef.getBoundingClientRect().width + this.hiddenTabsWidth[0] < tabsRef!.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
tabsWrapperRef.getBoundingClientRect().width tabsWrapperRef!.getBoundingClientRect().width
) { ) {
hiddenTab = hiddenTabs.shift(); hiddenTab = hiddenTabs.shift();
visibleTabs.splice(Number(hiddenTab.key), 0, hiddenTab); visibleTabs.splice(Number(hiddenTab!.key), 0, hiddenTab!);
i++; i++;
} }
} else { } else {
@ -121,7 +148,7 @@ export default class TabsHeader extends Component {
tabButtons[i].getBoundingClientRect().right >= tabButtons[i].getBoundingClientRect().right >=
tabsWrapperRight - this.iconWidth tabsWrapperRight - this.iconWidth
) { ) {
if (tabButtons[i].value !== selected) { if ((tabButtons[i] as HTMLButtonElement).value !== selected) {
hiddenTabs.unshift(...visibleTabs.splice(i, 1)); hiddenTabs.unshift(...visibleTabs.splice(i, 1));
this.hiddenTabsWidth.unshift( this.hiddenTabsWidth.unshift(
tabButtons[i].getBoundingClientRect().width tabButtons[i].getBoundingClientRect().width
@ -140,7 +167,7 @@ export default class TabsHeader extends Component {
tabsWrapperRight - this.iconWidth tabsWrapperRight - this.iconWidth
) { ) {
hiddenTab = hiddenTabs.shift(); hiddenTab = hiddenTabs.shift();
visibleTabs.splice(Number(hiddenTab.key), 0, hiddenTab); visibleTabs.splice(Number(hiddenTab!.key), 0, hiddenTab!);
this.hiddenTabsWidth.shift(); this.hiddenTabsWidth.shift();
i++; i++;
} }
@ -152,15 +179,15 @@ export default class TabsHeader extends Component {
this.setState({ subMenuOpened: false, contextMenu: undefined }); this.setState({ subMenuOpened: false, contextMenu: undefined });
}; };
getTabsWrapperRef = node => { getTabsWrapperRef: React.RefCallback<HTMLDivElement> = node => {
this.tabsWrapperRef = node; this.tabsWrapperRef = node;
}; };
getTabsRef = node => { getTabsRef: React.RefCallback<HTMLDivElement> = node => {
this.tabsRef = node; this.tabsRef = node;
}; };
expandMenu = e => { expandMenu: React.MouseEventHandler<HTMLButtonElement> = e => {
const rect = e.currentTarget.children[0].getBoundingClientRect(); const rect = e.currentTarget.children[0].getBoundingClientRect();
this.setState({ this.setState({
contextMenu: { contextMenu: {
@ -174,11 +201,7 @@ export default class TabsHeader extends Component {
render() { render() {
const { visibleTabs, hiddenTabs, contextMenu } = this.state; const { visibleTabs, hiddenTabs, contextMenu } = this.state;
return ( return (
<TabsWrapper <TabsWrapper innerRef={this.getTabsWrapperRef} main={this.props.main}>
innerRef={this.getTabsWrapperRef}
main={this.props.main}
position={this.props.position}
>
<div ref={this.getTabsRef}> <div ref={this.getTabsRef}>
{visibleTabs} {visibleTabs}
{this.props.collapsible && {this.props.collapsible &&
@ -200,9 +223,8 @@ export default class TabsHeader extends Component {
</TabsWrapper> </TabsWrapper>
); );
} }
}
TabsHeader.propTypes = { static propTypes = {
tabs: PropTypes.array.isRequired, tabs: PropTypes.array.isRequired,
items: PropTypes.array.isRequired, items: PropTypes.array.isRequired,
main: PropTypes.bool, main: PropTypes.bool,
@ -211,3 +233,4 @@ TabsHeader.propTypes = {
collapsible: PropTypes.bool, collapsible: PropTypes.bool,
selected: PropTypes.string selected: PropTypes.string
}; };
}

View File

@ -1 +0,0 @@
export default from './Tabs';

View File

@ -0,0 +1 @@
export { default } from './Tabs';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
const Component = ({ selected }) => ( const Component = ({ selected }: { selected: string }) => (
<div <div
style={{ style={{
display: 'flex', display: 'flex',
@ -17,7 +17,9 @@ const Component = ({ selected }) => (
); );
/* eslint-enable react/prop-types */ /* eslint-enable react/prop-types */
const selector = tab => ({ selected: tab.name }); const selector = (tab: { name: string; value?: string }) => ({
selected: tab.name
});
export const tabs = [ export const tabs = [
{ {
@ -37,6 +39,6 @@ export const tabs = [
} }
]; ];
export const simple10Tabs = []; export const simple10Tabs: { name: string; value: string }[] = [];
for (let i = 1; i <= 10; i++) for (let i = 1; i <= 10; i++)
simple10Tabs.push({ name: `Tab${i}`, value: `${i}` }); simple10Tabs.push({ name: `Tab${i}`, value: `${i}` });

View File

@ -5,6 +5,7 @@ import { withKnobs, text, boolean, select } from '@storybook/addon-knobs';
import styled from 'styled-components'; import styled from 'styled-components';
import Tabs from '../'; import Tabs from '../';
import { tabs, simple10Tabs } from './data'; import { tabs, simple10Tabs } from './data';
import { Position } from '../Tabs';
const Container = styled.div` const Container = styled.div`
display: flex; display: flex;
@ -24,7 +25,11 @@ storiesOf('Tabs', module)
main={boolean('main', true)} main={boolean('main', true)}
onClick={action('tab selected')} onClick={action('tab selected')}
collapsible={boolean('collapsible', true)} collapsible={boolean('collapsible', true)}
position={select('position', ['left', 'right', 'center'], 'left')} position={select<Position>(
'position',
['left', 'right', 'center'],
'left'
)}
/> />
</Container> </Container>
)) ))
@ -35,6 +40,10 @@ storiesOf('Tabs', module)
main={boolean('main', false)} main={boolean('main', false)}
onClick={action('tab selected')} onClick={action('tab selected')}
collapsible={boolean('collapsible', false)} collapsible={boolean('collapsible', false)}
position={select('position', ['left', 'right', 'center'], 'left')} position={select<Position>(
'position',
['left', 'right', 'center'],
'left'
)}
/> />
)); ));

View File

@ -1,6 +1,11 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { Position } from '../Tabs';
export const TabsContainer = styled.div` interface StyleProps {
position: Position;
}
export const TabsContainer = styled.div<StyleProps>`
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -12,15 +17,17 @@ export const TabsContainer = styled.div`
> div > div:first-child { > div > div:first-child {
${props => ${props =>
props.position !== 'left' && props.position !== 'left'
` ? `
margin-left: auto !important; margin-left: auto !important;
`}
${props =>
props.position === 'center' &&
` `
: ''}
${props =>
props.position === 'center'
? `
margin-right: auto !important; margin-right: auto !important;
`} `
: ''}
} }
> div:nth-child(2) { > div:nth-child(2) {

View File

@ -1,17 +1,28 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { Theme } from '../../themes/default';
export const style = ({ theme, main }) => css` export interface StyleProps {
main: boolean | undefined;
}
export const style = ({
theme,
main
}: ThemedStyledProps<StyleProps, Theme>) => css`
display: flex; display: flex;
flex: 0 0 1; flex: 0 0 1;
padding-left: 1px; padding-left: 1px;
background-color: ${theme.base01}; background-color: ${theme.base01};
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
${!main && ${
` !main
? `
border-top: 1px solid ${theme.base01}; border-top: 1px solid ${theme.base01};
border-bottom: 1px solid ${theme.base02}; border-bottom: 1px solid ${theme.base02};
`} `
: ''
}
> div { > div {
display: flex; display: flex;

View File

@ -1,18 +1,24 @@
import { css } from 'styled-components'; import { css, ThemedStyledProps } from 'styled-components';
import { ripple } from '../../utils/animations'; import { ripple } from '../../utils/animations';
import { StyleProps } from './default';
import { Theme } from '../../themes/default';
export const style = ({ theme, main }) => css` export const style = ({
theme,
main
}: ThemedStyledProps<StyleProps, Theme>) => css`
display: flex; display: flex;
flex: 0 0 1; flex: 0 0 1;
padding-left: 1px; padding-left: 1px;
background-color: ${theme.base01}; background-color: ${theme.base01};
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
${!main && ${!main
` ? `
border-top: 1px solid ${theme.base01}; border-top: 1px solid ${theme.base01};
border-bottom: 1px solid ${theme.base02}; border-bottom: 1px solid ${theme.base02};
`} `
: ''}
> div { > div {
display: flex; display: flex;
@ -24,7 +30,7 @@ export const style = ({ theme, main }) => css`
color: ${theme.base07}; color: ${theme.base07};
min-height: 30px; min-height: 30px;
padding: 0 2em; padding: 0 2em;
${main && 'text-transform: uppercase;'} ${main ? 'text-transform: uppercase;' : ''}
cursor: pointer; cursor: pointer;
border: none; border: none;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;

View File

@ -1,3 +0,0 @@
export Toolbar from './styles/Toolbar';
export Divider from './styles/Divider';
export Spacer from './styles/Spacer';

View File

@ -0,0 +1,3 @@
export { default as Toolbar } from './styles/Toolbar';
export { default as Divider } from './styles/Divider';
export { default as Spacer } from './styles/Spacer';

View File

@ -25,6 +25,9 @@ import {
} from '../../'; } from '../../';
import { options } from '../../Select/stories/options'; import { options } from '../../Select/stories/options';
import { simple10Tabs } from '../../Tabs/stories/data'; import { simple10Tabs } from '../../Tabs/stories/data';
import { TooltipPosition } from '../../Button/Button';
import { BorderPosition } from '../styles/Toolbar';
import { Position } from '../../Tabs/Tabs';
export const Container = styled.div` export const Container = styled.div`
display: flex; display: flex;
@ -43,10 +46,18 @@ storiesOf('Toolbar', module)
.addDecorator(withKnobs) .addDecorator(withKnobs)
.add('default', () => ( .add('default', () => (
<Container> <Container>
<Toolbar borderPosition={select('borderPosition', ['top', 'bottom'])}> <Toolbar
borderPosition={select<BorderPosition>(
'borderPosition',
['top', 'bottom'],
'top'
)}
>
<Button <Button
title={text('Title', 'Hello Tooltip')} title={text('Title', 'Hello Tooltip')}
tooltipPosition={select('tooltipPosition', [ tooltipPosition={select<TooltipPosition>(
'tooltipPosition',
[
'top', 'top',
'bottom', 'bottom',
'left', 'left',
@ -55,7 +66,9 @@ storiesOf('Toolbar', module)
'bottom-right', 'bottom-right',
'top-left', 'top-left',
'top-right' 'top-right'
])} ],
'top'
)}
disabled={boolean('Disabled', false)} disabled={boolean('Disabled', false)}
onClick={action('button clicked')} onClick={action('button clicked')}
> >
@ -64,7 +77,9 @@ storiesOf('Toolbar', module)
<Divider /> <Divider />
<Button <Button
title={text('Title', 'Hello Tooltip')} title={text('Title', 'Hello Tooltip')}
tooltipPosition={select('tooltipPosition', [ tooltipPosition={select<TooltipPosition>(
'tooltipPosition',
[
'top', 'top',
'bottom', 'bottom',
'left', 'left',
@ -73,7 +88,9 @@ storiesOf('Toolbar', module)
'bottom-right', 'bottom-right',
'top-left', 'top-left',
'top-right' 'top-right'
])} ],
'top'
)}
disabled={boolean('Disabled', false)} disabled={boolean('Disabled', false)}
onClick={action('button clicked')} onClick={action('button clicked')}
> >
@ -90,7 +107,9 @@ storiesOf('Toolbar', module)
<Toolbar> <Toolbar>
<Button <Button
title={text('Title', 'Hello Tooltip')} title={text('Title', 'Hello Tooltip')}
tooltipPosition={select('tooltipPosition', [ tooltipPosition={select<TooltipPosition>(
'tooltipPosition',
[
'top', 'top',
'bottom', 'bottom',
'left', 'left',
@ -99,7 +118,9 @@ storiesOf('Toolbar', module)
'bottom-right', 'bottom-right',
'top-left', 'top-left',
'top-right' 'top-right'
])} ],
'top'
)}
disabled={boolean('Disabled', false)} disabled={boolean('Disabled', false)}
onClick={action('button clicked')} onClick={action('button clicked')}
> >
@ -111,11 +132,17 @@ storiesOf('Toolbar', module)
main={boolean('main', true)} main={boolean('main', true)}
onClick={action('tab selected')} onClick={action('tab selected')}
collapsible={boolean('collapsible', true)} collapsible={boolean('collapsible', true)}
position={select('position', ['left', 'right', 'center'], 'center')} position={select<Position>(
'position',
['left', 'right', 'center'],
'center'
)}
/> />
<Button <Button
title={text('Title', 'Hello Tooltip')} title={text('Title', 'Hello Tooltip')}
tooltipPosition={select('tooltipPosition', [ tooltipPosition={select<TooltipPosition>(
'tooltipPosition',
[
'top', 'top',
'bottom', 'bottom',
'left', 'left',
@ -124,7 +151,9 @@ storiesOf('Toolbar', module)
'bottom-right', 'bottom-right',
'top-left', 'top-left',
'top-right' 'top-right'
])} ],
'top'
)}
disabled={boolean('Disabled', false)} disabled={boolean('Disabled', false)}
onClick={action('button clicked')} onClick={action('button clicked')}
> >
@ -139,7 +168,9 @@ storiesOf('Toolbar', module)
<Toolbar noBorder fullHeight compact> <Toolbar noBorder fullHeight compact>
<Button <Button
title={text('play title', 'Play')} title={text('play title', 'Play')}
tooltipPosition={select('tooltipPosition', [ tooltipPosition={select<TooltipPosition>(
'tooltipPosition',
[
'top', 'top',
'bottom', 'bottom',
'left', 'left',
@ -148,7 +179,9 @@ storiesOf('Toolbar', module)
'bottom-right', 'bottom-right',
'top-left', 'top-left',
'top-right' 'top-right'
])} ],
'top'
)}
onClick={action('button clicked')} onClick={action('button clicked')}
> >
<PlayIcon /> <PlayIcon />
@ -163,7 +196,9 @@ storiesOf('Toolbar', module)
/> />
<Button <Button
title="Previous state" title="Previous state"
tooltipPosition={select('tooltipPosition', [ tooltipPosition={select<TooltipPosition>(
'tooltipPosition',
[
'top', 'top',
'bottom', 'bottom',
'left', 'left',
@ -172,7 +207,9 @@ storiesOf('Toolbar', module)
'bottom-right', 'bottom-right',
'top-left', 'top-left',
'top-right' 'top-right'
])} ],
'top'
)}
disabled disabled
onClick={action('previous state clicked')} onClick={action('previous state clicked')}
> >
@ -180,7 +217,9 @@ storiesOf('Toolbar', module)
</Button> </Button>
<Button <Button
title="Next state" title="Next state"
tooltipPosition={select('tooltipPosition', [ tooltipPosition={select<TooltipPosition>(
'tooltipPosition',
[
'top', 'top',
'bottom', 'bottom',
'left', 'left',
@ -189,7 +228,9 @@ storiesOf('Toolbar', module)
'bottom-right', 'bottom-right',
'top-left', 'top-left',
'top-right' 'top-right'
])} ],
'top'
)}
onClick={action('next state clicked')} onClick={action('next state clicked')}
> >
<RightIcon /> <RightIcon />

View File

@ -1,6 +1,15 @@
import styled from 'styled-components'; import styled from 'styled-components';
const Toolbar = styled.div` export type BorderPosition = 'top' | 'bottom';
interface Props {
fullHeight?: boolean;
compact?: boolean;
borderPosition?: BorderPosition;
noBorder?: boolean;
}
const Toolbar = styled.div<Props>`
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
box-sizing: border-box; box-sizing: border-box;
@ -8,13 +17,15 @@ const Toolbar = styled.div`
font-family: ${props => props.theme.fontFamily || 'monospace'}; font-family: ${props => props.theme.fontFamily || 'monospace'};
font-size: 12px; font-size: 12px;
line-height: 16px; line-height: 16px;
${props => props.fullHeight && 'height: 100%;'} ${props => (props.fullHeight ? 'height: 100%;' : '')}
padding: ${props => (props.compact ? '0' : '5px')} 5px; padding: ${props => (props.compact ? '0' : '5px')} 5px;
background-color: ${props => props.theme.base01}; background-color: ${props => props.theme.base01};
text-align: center; text-align: center;
position: relative; position: relative;
${({ borderPosition, theme }) => ${({ borderPosition, theme }) =>
borderPosition && `border-${borderPosition}: 1px solid ${theme.base02};`} borderPosition
? `border-${borderPosition}: 1px solid ${theme.base02};`
: ''}
& > div { & > div {
margin: auto ${props => (props.noBorder ? '0' : '1px;')}; margin: auto ${props => (props.noBorder ? '0' : '1px;')};
@ -22,7 +33,7 @@ const Toolbar = styled.div`
& button { & button {
border-radius: 0; border-radius: 0;
${props => props.noBorder && 'border-color: transparent;'} ${props => (props.noBorder ? 'border-color: transparent;' : '')}
white-space: nowrap; white-space: nowrap;
box-shadow: none !important; box-shadow: none !important;
} }

View File

@ -0,0 +1,61 @@
declare module 'base16' {
export interface Base16Theme {
scheme?: string;
author?: string;
base00: string;
base01: string;
base02: string;
base03: string;
base04: string;
base05: string;
base06: string;
base07: string;
base08: string;
base09: string;
base0A: string;
base0B: string;
base0C: string;
base0D: string;
base0E: string;
base0F: string;
}
export const threezerotwofour: Base16Theme;
export const apathy: Base16Theme;
export const ashes: Base16Theme;
export const atelierDune: Base16Theme;
export const atelierForest: Base16Theme;
export const atelierHeath: Base16Theme;
export const atelierLakeside: Base16Theme;
export const atelierSeaside: Base16Theme;
export const bespin: Base16Theme;
export const brewer: Base16Theme;
export const bright: Base16Theme;
export const chalk: Base16Theme;
export const codeschool: Base16Theme;
export const colors: Base16Theme;
const _default: Base16Theme;
export default _default;
export const eighties: Base16Theme;
export const embers: Base16Theme;
export const flat: Base16Theme;
export const google: Base16Theme;
export const grayscale: Base16Theme;
export const greenscreen: Base16Theme;
export const harmonic: Base16Theme;
export const hopscotch: Base16Theme;
export const isotope: Base16Theme;
export const marrakesh: Base16Theme;
export const mocha: Base16Theme;
export const monokai: Base16Theme;
export const ocean: Base16Theme;
export const paraiso: Base16Theme;
export const pop: Base16Theme;
export const railscasts: Base16Theme;
export const shapeshifter: Base16Theme;
export const solarized: Base16Theme;
export const summerfruit: Base16Theme;
export const tomorrow: Base16Theme;
export const tube: Base16Theme;
export const twilight: Base16Theme;
}

View File

@ -13,4 +13,4 @@ export * from './Toolbar';
import color from './utils/color'; import color from './utils/color';
export const effects = { color }; export const effects = { color };
export createStyledComponent from './utils/createStyledComponent'; export { default as createStyledComponent } from './utils/createStyledComponent';

Some files were not shown because too many files have changed in this diff Show More