Add devui package (#444)

This commit is contained in:
Mihail Diordiev 2019-01-03 15:00:55 +02:00 committed by GitHub
parent dc26d4c180
commit 8449c6a9fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
148 changed files with 9299 additions and 260 deletions

3
packages/devui/.babelrc Executable file
View File

@ -0,0 +1,3 @@
{
"presets": ["es2015", "stage-0", "react"]
}

20
packages/devui/.eslintrc Executable file
View File

@ -0,0 +1,20 @@
{
"extends": "airbnb",
"rules": {
"arrow-body-style": 0,
"prefer-arrow-callback": 0,
"func-names": 0,
"comma-dangle": ["error", "never"],
"newline-per-chained-call": 0,
"react/sort-comp": 0,
"react/jsx-no-bind": 0,
"react/jsx-uses-react": 1,
"react/prefer-stateless-function": 0
},
"env": {
"browser": true,
"node": true,
"jest": true
},
"parser": "babel-eslint"
}

View File

@ -0,0 +1,11 @@
// IMPORTANT
// ---------
// This is an auto generated file with React CDK.
// Do not modify this file.
const parse = require('git-url-parse');
var ghUrl = process.argv[2];
const parsedUrl = parse(ghUrl);
const ghPagesUrl = 'https://' + parsedUrl.owner + '.github.io/' + parsedUrl.name;
console.log(ghPagesUrl);

View File

@ -0,0 +1,33 @@
// IMPORTANT
// ---------
// This is an auto generated file with React CDK.
// Do not modify this file.
// Use `.scripts/user/pretest.js instead`.
require('babel-core/register');
require('babel-polyfill');
// Add jsdom support, which is required for enzyme.
var jsdom = require('jsdom').jsdom;
var exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property);
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js'
};
process.on('unhandledRejection', function (error) {
console.error('Unhandled Promise Rejection:');
console.error(error && error.stack || error);
});
require('./user/pretest.js');

View File

@ -0,0 +1,16 @@
#!/bin/bash
# IMPORTANT
# ---------
# This is an auto generated file with React CDK.
# Do not modify this file.
# Use `.scripts/user/prepublish.sh instead`.
echo "=> Transpiling 'src' into ES5 ..."
echo ""
rm -rf ./dist
./node_modules/.bin/babel --ignore tests,stories --plugins "transform-runtime" ./src --out-dir ./lib
echo ""
echo "=> Transpiling completed."
. .scripts/user/prepublish.sh

View File

@ -0,0 +1,47 @@
#!/bin/bash
# IMPORTANT
# ---------
# This is an auto generated file with React CDK.
# Do not modify this file.
set -e # exit with nonzero exit code if anything fails
# get GIT url
GIT_URL=`git config --get remote.origin.url`
if [[ $GIT_URL == "" ]]; then
echo "This project is not configured with a remote git repo".
exit 1
fi
# clear and re-create the out directory
rm -rf .out || exit 0;
mkdir .out;
# run our compile script, discussed above
build-storybook -o .out
# go to the out directory and create a *new* Git repo
cd .out
git init
# inside this git repo we'll pretend to be a new user
git config user.name "GH Pages Bot"
git config user.email "hello@ghbot.com"
# The first and only commit to this new Git repo contains all the
# files present with the commit message "Deploy to GitHub Pages".
git add .
git commit -m "Deploy Storybook to GitHub Pages"
# Force push from the current repo's master branch to the remote
# repo's gh-pages branch. (All previous history on the gh-pages branch
# will be lost, since we are overwriting it.) We redirect any output to
# /dev/null to hide any sensitive credential data that might otherwise be exposed.
git push --force --quiet $GIT_URL master:gh-pages > /dev/null 2>&1
cd ..
rm -rf .out
echo ""
echo "=> Storybook deployed to: `node .scripts/get_gh_pages_url.js $GIT_URL`"

View File

@ -0,0 +1 @@
# Use this file to your own code to run at NPM `prepublish` event.

View File

@ -0,0 +1 @@
// Use this file to setup any test utilities.

View File

@ -0,0 +1,4 @@
import '@storybook/addon-knobs/register';
import '@storybook/addon-actions/register';
import '@storybook/addon-options/register';
import './themeAddon/register';

View File

@ -0,0 +1,28 @@
import { configure, setAddon, addDecorator } from '@storybook/react';
import { setOptions } from '@storybook/addon-options';
import infoAddon from '@storybook/addon-info';
import { withKnobs } from '@storybook/addon-knobs';
import { withTheme } from './themeAddon/theme';
import '../src/presets.js';
setAddon(infoAddon);
setOptions({
name: 'DevUI',
url: 'https://github.com/reduxjs/redux-devtools/tree/master/packages/devui',
goFullScreen: false,
showLeftPanel: true,
showDownPanel: true,
showSearchBox: false,
downPanelInRight: true
});
addDecorator(withTheme);
addDecorator(withKnobs);
const req = require.context('../src/', true, /stories\/index\.js$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);

View File

@ -0,0 +1,26 @@
<style>
html {
height: 100%;
width: 100%;
}
body {
overflow: hidden;
height: 100%;
width: 100%;
min-width: 350px;
min-height: 400px;
margin: 0;
padding: 0;
font-family: "Helvetica Neue", "Lucida Grande", sans-serif;
font-size: 11px;
}
#root, #root > div, #root > div > div {
height: 100%;
}
#root > div > div {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,57 @@
import React from 'react';
import Form from '@storybook/addon-knobs/dist/components/PropForm';
import styled from 'styled-components';
import { EVENT_ID_DATA, DEFAULT_THEME_STATE } from './constant';
import { listSchemes, listThemes } from '../../src/utils/theme';
const FormWrapper = styled.div`
width: 100%;
padding: 5px;
label {
white-space: nowrap;
}
`;
const schemes = listSchemes();
const themes = listThemes();
export default class Panel extends React.Component {
state = DEFAULT_THEME_STATE;
onChange = o => {
const state = { [o.name.split(' ').slice(-1)[0]]: o.value };
this.props.channel.emit(EVENT_ID_DATA, state);
this.setState(state);
};
render() {
const { theme, scheme, light } = this.state;
return (
<FormWrapper>
<Form
knobs={[
{
type: 'select',
name: 'theme',
value: theme,
options: themes
},
{
type: 'select',
name: 'color scheme',
value: scheme,
options: schemes
},
{
type: 'boolean',
name: 'light',
value: light
}
]}
onFieldChange={this.onChange}
/>
</FormWrapper>
);
}
}

View File

@ -0,0 +1,9 @@
export const ADDON_ID = 'remotedev-themes-storybook';
export const PANEL_ID = `${ADDON_ID}/panel`;
export const EVENT_ID_DATA = `${ADDON_ID}/event/data`;
export const CSS_CLASS = 'remotedev-storybook';
export const DEFAULT_THEME_STATE = {
theme: 'default',
scheme: 'default',
light: true
};

View File

@ -0,0 +1,12 @@
import React from 'react';
import addons from '@storybook/addons';
import Panel from './Panel';
import { ADDON_ID, PANEL_ID } from './constant';
addons.register(ADDON_ID, api => {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Theme',
render: () => <Panel channel={channel} api={api} />
});
});

View File

@ -0,0 +1,42 @@
import React from 'react';
import addons from '@storybook/addons';
import styled from 'styled-components';
import { EVENT_ID_DATA, DEFAULT_THEME_STATE } from './constant';
import { Container } from '../../src';
const ContainerStyled = styled(Container)`
> div {
height: 100%;
width: 100%;
> div {
height: 100%;
width: 100%;
overflow-y: auto;
}
}
`;
const channel = addons.getChannel();
class Theme extends React.Component {
state = DEFAULT_THEME_STATE;
componentDidMount() {
channel.on(EVENT_ID_DATA, this.onChannel);
}
componentWillUnmount() {
channel.removeListener(EVENT_ID_DATA, this.onChannel);
}
onChannel = state => {
this.setState(state);
};
render() {
return <ContainerStyled themeData={this.state}>{this.props.children}</ContainerStyled>;
}
}
export const withTheme = story => <Theme>{story()}</Theme>;

View File

@ -0,0 +1,4 @@
module.exports = function (config) {
// This is the default webpack config defined in the `../webpack.config.js`
// modify as you need.
};

View File

@ -0,0 +1,26 @@
const path = require('path');
const updateConfig = require('./user/modify_webpack_config');
const config = {
module: {
rules: [
{
test: /\.css?$/,
use: [{ loader: 'style-loader' }, { loader: 'raw-loader' }],
include: path.resolve(__dirname, '../')
},
{
test: /\.json?$/,
loader: 'json-loader',
include: path.resolve(__dirname, '../')
},
{
test: /\.woff2?(\?\S*)?$/,
loader: 'url?limit=65000&mimetype=application/font-woff'
}
]
}
};
updateConfig(config);
module.exports = config;

View File

@ -0,0 +1,5 @@
{
"processors": ["stylelint-processor-styled-components"],
"extends": "stylelint-config-standard",
"syntax": "scss"
}

3
packages/devui/README.md Executable file
View File

@ -0,0 +1,3 @@
# DevUI
WIP

View File

@ -0,0 +1,43 @@
/* source-code-pro-regular - latin */
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 400;
src: local('Source Code Pro'), local('SourceCodePro-Regular'),
url('./source-code-pro-v6-latin/source-code-pro-v6-latin-regular.woff2') format('woff2');
}
/* source-sans-pro-regular - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro'), local('SourceSansPro-Regular'),
url('./source-sans-pro-v9-latin/source-sans-pro-v9-latin-regular.woff2') format('woff2');
}
/* source-sans-pro-600 - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'),
url('./source-sans-pro-v9-latin/source-sans-pro-v9-latin-600.woff2') format('woff2');
}
/* roboto-regular - latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'),
url('./roboto-v15-latin/roboto-v15-latin-regular.woff2') format('woff2');
}
/* roboto-mono-regular - latin */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'),
url('./roboto-mono-v4-latin/roboto-mono-v4-latin-regular.woff2') format('woff2');
}
/* Generated with https://google-webfonts-helper.herokuapp.com */

97
packages/devui/package.json Executable file
View File

@ -0,0 +1,97 @@
{
"name": "devui",
"version": "1.0.0-3",
"description":
"Reusable React components for building DevTools monitors and apps.",
"files": ["lib", "fonts"],
"repository": {
"type": "git",
"url": "https://github.com/reduxjs/redux-devtools.git"
},
"author":
"Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
"license": "MIT",
"scripts": {
"start": "npm run storybook",
"build": "rimraf ./lib && babel ./src --out-dir ./lib --ignore tests,stories",
"lint": "eslint src",
"lintfix": "eslint src --fix",
"lint:css": "stylelint './src/**/styles/*.js'",
"format": "prettier-eslint --write ./src/**/*.js",
"test:update": "npm run jest -- -u",
"test": "jest --no-cache",
"storybook": "start-storybook -p 9001 -c .storybook -s ./fonts",
"publish-storybook": "bash .scripts/publish_storybook.sh",
"prepare": "npm run build",
"prepublishOnly": "npm run lint && npm run test && npm run build"
},
"bugs": {
"url": "https://github.com/reduxjs/redux-devtools/issues"
},
"homepage": "https://github.com/reduxjs/redux-devtools",
"devDependencies": {
"@storybook/addon-actions": "^3.2.13",
"@storybook/addon-info": "^3.2.13",
"@storybook/addon-knobs": "^3.2.13",
"@storybook/addon-options": "^3.2.13",
"@storybook/react": "^3.2.13",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-eslint": "^6.0.2",
"babel-jest": "^21.2.0",
"babel-loader": "^7.1.2",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.16.0",
"css-loader": "^0.28.7",
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.2",
"enzyme-to-json": "^3.1.4",
"eslint": "^2.7.0",
"eslint-config-airbnb": "^7.0.0",
"eslint-plugin-babel": "^3.2.0",
"eslint-plugin-jsx-a11y": "^0.6.2",
"eslint-plugin-react": "^4.3.0",
"file-loader": "^1.1.5",
"git-url-parse": "^7.0.1",
"jest": "^21.2.1",
"jsdom": "^11.3.0",
"node-sass": "^3.13.0",
"postcss-loader": "^2.0.8",
"prettier-eslint-cli": "^4.4.0",
"raw-loader": "^0.5.1",
"react": "^16.0.0",
"react-addons-test-utils": "^15.6.2",
"react-dom": "^16.0.0",
"react-test-renderer": "^16.0.0",
"rimraf": "^2.6.2",
"sass-loader": "^4.0.2",
"style-loader": "^0.19.0",
"stylelint": "^7.6.0",
"stylelint-config-standard": "^15.0.0",
"stylelint-processor-styled-components": "^0.0.4",
"url-loader": "^0.6.2"
},
"peerDependencies": {
"react": "^0.14.9 || ^15.3.0"
},
"dependencies": {
"base16": "^1.0.0",
"codemirror": "^5.21.0",
"color": "^2.0.0",
"prop-types": "^15.6.0",
"react-icons": "^2.2.7",
"react-jsonschema-form": "^1.0.0",
"react-select": "^1.0.0-rc.10",
"redux-devtools-themes": "^1.0.0",
"simple-element-resize-detector": "^1.1.0",
"styled-components": "^2.2.2"
},
"pre-commit": ["lint"],
"jest": {
"setupTestFrameworkScriptFile": "<rootDir>/tests/setup.js"
},
"main": "lib/index.js"
}

View File

@ -0,0 +1,73 @@
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 @@
export { default } from './Button';

View File

@ -0,0 +1,61 @@
import React from 'react';
import styled from 'styled-components';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, text, boolean, select } from '@storybook/addon-knobs';
import MdFiberManualRecord from 'react-icons/lib/md/fiber-manual-record';
import Button from '../';
export const Container = styled.div`
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
`;
storiesOf('Button', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'',
() => (
<Container>
<Button
title={text('Title', 'Hello Tooltip! \\a And from new line hello!')}
tooltipPosition={
select('tooltipPosition', ['top', 'bottom', 'left', 'right',
'bottom-left', 'bottom-right', 'top-left', 'top-right'])
}
primary={boolean('primary', true)}
size={select('size', ['big', 'normal', 'small'], 'normal')}
disabled={boolean('Disabled', false)}
onClick={action('button clicked')}
>
{text('Label', 'Hello Button')}
</Button>
</Container>
)
)
.addWithInfo(
'mark',
'',
() => (
<Container>
<Button
mark={select('mark', ['base08', 'base09', 'base0A', 'base0B',
'base0C', 'base0D', 'base0E', 'base0F'], 'base08')}
title={text('Title', 'Hello Tooltip')}
tooltipPosition={
select('tooltipPosition', ['top', 'bottom', 'left', 'right',
'bottom-left', 'bottom-right', 'top-left', 'top-right'])
}
size={select('size', ['big', 'normal', 'small'], 'normal')}
disabled={boolean('Disabled', false)}
onClick={action('button clicked')}
>
<MdFiberManualRecord />
</Button>
</Container>
)
);

View File

@ -0,0 +1,221 @@
import { css } from 'styled-components';
import { fadeIn } from '../../utils/animations';
import colorEffect from '../../utils/color';
const both = (tooltipPosition) => {
switch (tooltipPosition) {
case 'bottom':
return `
transform: translate(-50%, 100%);
top: auto;
`;
case 'left':
return `
transform: translate(-100%, -50%);
top: 50%;
right: auto;
`;
case 'right':
return `
transform: translate(100%, -50%);
top: 50%;
left: auto;
`;
case 'bottom-left':
return `
transform: translate(-100%, 100%);
top: auto;
`;
case 'bottom-right':
return `
transform: translateY(100%);
top: auto;
`;
case 'top-left':
return `
transform: translate(-100%, -100%);
`;
case 'top-right':
return `
transform: translateY(-100%);
`;
default:
return `
transform: translate(-50%, -100%);
`;
}
};
const before = (tooltipPosition) => {
switch (tooltipPosition) {
case 'bottom-left':
return `
left: calc(50% + 11px);
`;
case 'bottom-right':
return `
left: calc(50% - 11px);
`;
case 'top-left':
return `
left: calc(50% + 11px);
`;
case 'top-right':
return `
left: calc(50% - 11px);
`;
default:
return '';
}
};
const after = (tooltipPosition, color) => {
switch (tooltipPosition) {
case 'bottom':
return `
border-color: transparent transparent ${color} transparent;
`;
case 'left':
return `
border-color: transparent transparent transparent ${color};
`;
case 'right':
return `
border-color: transparent ${color} transparent transparent;
`;
case 'bottom-left':
return `
left: calc(50% + 10px);
border-color: transparent transparent ${color} transparent;
`;
case 'bottom-right':
return `
left: calc(50% - 10px);
border-color: transparent transparent ${color} transparent;
`;
case 'top-left':
return `
left: calc(50% + 10px);
border-color: ${color} transparent transparent transparent;
`;
case 'top-right':
return `
left: calc(50% - 10px);
border-color: ${color} transparent transparent transparent;
`;
default:
return `
border-color: ${color} transparent transparent transparent;
`;
}
};
const getDirection = (tooltipPosition) => {
return (tooltipPosition.indexOf('-') > 0) ?
tooltipPosition.substring(0, tooltipPosition.indexOf('-')) : tooltipPosition;
};
const getSize = (size) => {
switch (size) {
case 'big':
return 'min-height: 34px; padding: 2px 12px;';
case 'small':
return 'padding: 0;';
default:
return 'min-height: 30px; padding: 2px 7px;';
}
};
export const commonStyle = ({ theme, mark, size }) => css`
display: inline-block;
position: relative;
flex-shrink: 0;
line-height: 0;
margin: 0 1px;
& > button {
width: 100%;
line-height: 0;
${getSize(size)}
> svg {
font-size: 1.5em;
overflow: visible;
pointer-events: none;
}
${mark && `
background-color: ${colorEffect(theme[mark], 'fade', theme.light ? 0.92 : 0.82)};
> svg {
color: ${theme[mark]};
stroke: ${theme[mark]};
stroke-width: 14px;
stroke-opacity: 0.2;
user-select: none;
}
`}
}
`;
export const tooltipStyle = ({ theme, tooltipTitle, tooltipPosition, mark, size }) => css`
${commonStyle({ theme, mark, size })}
&:before {
content: "${tooltipTitle}";
white-space: pre;
color: ${theme.base06};
line-height: 16px;
padding: 4px 8px;
border-radius: 3px;
background: ${theme.base01};
border: 1px solid ${theme.base02};
box-shadow: 1px 1px 2px -1px ${theme.base02}, 1px 1px 2px 0px ${theme.base02};
}
&:after,
&:before {
opacity: 0;
visibility: hidden;
position: absolute;
left: 50%;
z-index: 999;
${both(tooltipPosition)}
user-select: none;
}
&:before {
transition: 0.3s ease-in-out;
}
&:before {
${before(tooltipPosition)}
${getDirection(tooltipPosition)}: 3px;
${theme.type === 'material' ? `animation: ${fadeIn} 500ms;` : ''}
}
${theme.type !== 'material' && `
&:after {
content: "";
border-style: solid;
border-width: 7px;
margin: 1px;
${after(tooltipPosition, theme.base02)}
${getDirection(tooltipPosition)}: 7px;
}
`}
&:hover:after,
&:hover:before {
opacity: 0.9;
visibility: visible;
}
&:hover:after {
${getDirection(tooltipPosition)}: 8px;
transition-delay: 500ms;
}
&:hover:before {
${getDirection(tooltipPosition)}: -4px;
transition-delay: 200ms;
}
`;

View File

@ -0,0 +1,42 @@
import { css } from 'styled-components';
export const style = ({ theme, primary, disabled }) => css`
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
outline: none;
font-weight: 600;
text-decoration: none;
display: inline-block;
transition: all 0.5s;
margin: auto 0;
border: 1px solid ${theme.base02};
border-radius: 4px;
${primary ? `
background-color: ${theme.base05};
color: ${theme.base00};
` : `
background-color: ${theme.base01};
color: ${theme.base05};
`}
${disabled ? `
cursor: not-allowed;
opacity: 0.6;
` : `
cursor: pointer;
`}
${!disabled && `
&:hover,
&:focus {
background-color: ${primary ? theme.base07 : theme.base02};
box-shadow: 1px 1px 2px ${theme.base03};
}
`}
&:focus {
border: 1px solid ${theme.base0D};
}
&:active {
border: 1px solid ${theme.base03};
box-shadow: 1px 1px 2px ${theme.base04};
}
`;

View File

@ -0,0 +1,2 @@
export { style as default } from './default';
export { style as material } from './material';

View File

@ -0,0 +1,42 @@
import { css } from 'styled-components';
import { ripple } from '../../utils/animations';
export const style = ({ theme, primary, disabled }) => css`
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
outline: none;
font-family: inherit;
font-weight: 600;
text-decoration: none;
display: inline-block;
border: none;
text-transform: uppercase;
margin: auto 0;
background-color: ${primary ? theme.base05 : theme.base01};
${disabled ? `
cursor: not-allowed;
color: ${theme.base04};
opacity: 0.6;
` : `
cursor: pointer;
color: ${primary ? theme.base00 : theme.base05};
`}
${!disabled ? `
box-shadow:
0 2px 2px 0 ${theme.base03},
0 3px 1px -2px ${theme.base02},
0 1px 5px 0 ${theme.base02};
` : ''}
&:hover, &:focus:not(:active) {
background-color: ${theme.base02};
}
&:focus:not(:active) {
background-color: ${theme.base02};
box-shadow: 0 0 4px ${theme.base02}, 0 4px 8px ${theme.base04};
}
${ripple(theme)}
`;

View File

@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ThemeProvider } from 'styled-components';
import { getTheme } from '../utils/theme';
import { MainContainerWrapper, ContainerWrapper } from './styles';
const Container = ({ themeData, className, theme, children }) => {
if (!themeData) {
return (
<ContainerWrapper className={className} theme={theme}>
{children}
</ContainerWrapper>
);
}
return (
<ThemeProvider theme={getTheme(themeData)}>
<MainContainerWrapper className={className}>
{children}
</MainContainerWrapper>
</ThemeProvider>
);
};
Container.propTypes = {
children: PropTypes.node,
themeData: PropTypes.object,
theme: PropTypes.object,
className: PropTypes.string
};
export default Container;

View File

@ -0,0 +1,38 @@
import styled from 'styled-components';
import color from '../../utils/color';
export const MainContainerWrapper = styled.div`
display: flex;
height: 100%;
width: 100%;
flex-flow: column nowrap;
background-color: ${props => color(props.theme.base00, 'lighten', 0.03)};
color: ${props => props.theme.base07};
font-size: 12px;
div, input, textarea, keygen, select, button {
font-family: ${props => props.theme.fontFamily || 'monaco, monospace'};
}
.CodeMirror div, pre, .monitor-LogMonitor * {
font-family: ${props => props.theme.codeFontFamily || props.theme.fontFamily || 'monospace'};
}
.monitor {
flex-grow: 1;
display: flex;
flex-flow: column nowrap;
height: 100%;
> div {
flex-grow: 1;
}
}
`;
export const ContainerWrapper = styled.div`
display: flex;
height: 100%;
width: 100%;
flex-flow: column nowrap;
`;

View File

@ -0,0 +1,105 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles/index';
const ContextMenuWrapper = createStyledComponent(styles);
export default class ContextMenu extends Component {
constructor(props) {
super(props);
this.updateItems(props.items);
}
componentWillReceiveProps(nextProps) {
if (nextProps.items !== this.props.items ||
nextProps.visible !== this.props.visible) {
this.updateItems(nextProps.items);
}
}
componentDidMount() {
this.amendPosition();
}
componentDidUpdate(prevProps) {
if (prevProps.x !== this.props.x || prevProps.y !== this.props.y) {
this.amendPosition();
}
}
onMouseUp = e => {
e.target.blur();
};
onClick = (e) => {
this.props.onClick(e.target.value);
};
amendPosition() {
const { x, y } = this.props;
const { scrollTop, scrollLeft } = document.documentElement;
const { innerWidth, innerHeight } = window;
const rect = this.menu.getBoundingClientRect();
let left = x + scrollLeft;
let top = y + scrollTop;
if (y + rect.height > innerHeight) {
top = innerHeight - rect.height;
}
if (x + rect.width > innerWidth) {
left = innerWidth - rect.width;
}
if (top < 0) {
top = rect.height < innerHeight ? (innerHeight - rect.height) / 2 : 0;
}
if (left < 0) {
left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
}
this.menu.style.top = `${top}px`;
this.menu.style.left = `${left}px`;
}
updateItems(items) {
this.items = items.map(item => {
const value = item.value || item.name;
if (item.type === 'button') return item;
return (
<button
key={value}
value={value}
onMouseUp={this.onMouseUp}
onClick={this.onClick}
>
{item.name}
</button>
);
});
}
menuRef = (c) => {
this.menu = c;
};
render() {
return (
<ContextMenuWrapper
innerRef={this.menuRef}
left={this.props.x}
top={this.props.y}
visible={this.props.visible}
>
{this.items}
</ContextMenuWrapper>
);
}
}
ContextMenu.propTypes = {
items: PropTypes.array.isRequired,
onClick: PropTypes.func.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
visible: PropTypes.bool
};

View File

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

View File

@ -0,0 +1,11 @@
export const items = [
{
name: 'Menu Item 1'
},
{
name: 'Menu Item 2'
},
{
name: 'Menu Item 3'
}
];

View File

@ -0,0 +1,33 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import styled from 'styled-components';
import { withKnobs, number } from '@storybook/addon-knobs';
import ContextMenu from '../';
import { items } from './data';
export const Container = styled.div`
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
`;
storiesOf('ContextMenu', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'',
() => (
<Container>
<ContextMenu
visible
onClick={action('menu item clicked')}
x={number('x', 100)}
y={number('y', 100)}
items={items}
/>
</Container>
)
);

View File

@ -0,0 +1,42 @@
import { css } from 'styled-components';
export default ({ theme, left, top, visible }) => css`
${visible ? `
visibility: visible;
opacity: 1;
transition: opacity 0.2s linear;
` : `
visibility: hidden;
opacity: 0;
transition: visibility 0s 0.2s, opacity 0.2s linear;
`}
position: fixed;
top: ${top}px;
left: ${left}px;
font-size: 14px;
color: ${theme.base07};
cursor: pointer;
display: block;
line-height: ${theme.inputInternalHeight / 2}px;
border: 1px solid ${theme.base02};
button {
box-sizing: border-box;
background-color: ${theme.base00};
color: ${theme.base07};
cursor: pointer;
display: block;
padding: ${theme.inputHeight / 3}px;
line-height: ${theme.inputInternalHeight / 2}px;
border: none;
white-space: nowrap;
&:hover {
background-color: ${theme.base02};
color: ${theme.base07};
}
&:focus {
outline:0;
}
}
`;

View File

@ -0,0 +1,115 @@
import React, { PureComponent, Component } from 'react';
import PropTypes from 'prop-types';
import createStyledComponent from '../utils/createStyledComponent';
import * as styles from './styles';
import Button from '../Button';
import Form from '../Form';
const DialogWrapper = createStyledComponent(styles);
export default class Dialog extends (PureComponent || Component) {
onSubmit = () => {
if (this.submitButton) this.submitButton.click();
else this.props.onSubmit();
};
getFormButtonRef = node => {
this.submitButton = node;
};
onKeyDown = e => {
if (e.keyCode === 27 /* esc */) {
e.preventDefault();
this.props.onDismiss(false);
}
};
render() {
const {
modal,
open,
fullWidth,
title,
children,
actions,
noHeader,
noFooter,
submitText,
onDismiss,
...rest
} = this.props;
const schema = rest.schema;
return (
<DialogWrapper
open={open}
fullWidth={fullWidth}
onKeyDown={this.onKeyDown}
theme={rest.theme}
>
<div onClick={!modal && onDismiss} />
<div>
{!noHeader && (
<div className="mc-dialog--header">
<div>{schema ? schema.title || title : title}</div>
{!modal && <button onClick={onDismiss}>×</button>}
</div>
)}
<div className="mc-dialog--body">
{children}
{schema && (
<Form {...rest}>
{!noFooter &&
(
<input
type="submit"
ref={this.getFormButtonRef}
className="mc-dialog--hidden"
/>
)
}
</Form>
)}
</div>
{
!noFooter &&
(actions ?
<div className="mc-dialog--footer">
{submitText ?
[...actions,
<Button key="default-submit" primary onClick={this.onSubmit}>
{submitText}
</Button>
]
: actions
}
</div>
:
<div className="mc-dialog--footer">
<Button onClick={onDismiss}>Cancel</Button>
<Button primary onClick={this.onSubmit}>
{submitText || 'Submit'}
</Button>
</div>
)
}
</div>
</DialogWrapper>
);
}
}
Dialog.propTypes = {
open: PropTypes.bool,
title: PropTypes.string,
children: PropTypes.any,
actions: PropTypes.node,
submitText: PropTypes.string,
fullWidth: PropTypes.bool,
noHeader: PropTypes.bool,
noFooter: PropTypes.bool,
modal: PropTypes.bool,
onDismiss: PropTypes.func,
onSubmit: PropTypes.func,
theme: PropTypes.object
};

View File

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

View File

@ -0,0 +1,46 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, text, boolean, object } from '@storybook/addon-knobs';
import Dialog from '../';
import { schema, uiSchema, formData } from '../../Form/stories/schema';
storiesOf('Dialog', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'',
() => (
<Dialog
title={text('title', 'Dialog Title')}
children={text('children', 'Hello Dialog!')}
submitText={text('submitText', 'Submit!')}
open={boolean('open', true)}
noHeader={boolean('noHeader', false)}
noFooter={boolean('noFooter', false)}
modal={boolean('modal', false)}
fullWidth={boolean('fullWidth', false)}
onDismiss={action('dialog dismissed')}
onSubmit={action('dialog submitted')}
/>
)
)
.addWithInfo(
'with form',
'',
() => (
<Dialog
open={boolean('open', true)}
noHeader={boolean('noHeader', false)}
noFooter={boolean('noFooter', false)}
fullWidth={boolean('fullWidth', false)}
submitText={text('submitText', 'Submit!')}
formData={object('formData', formData)}
schema={object('schema', schema)}
uiSchema={object('uiSchema', uiSchema)}
onChange={action('form changed')}
onSubmit={action('form submitted')}
onDismiss={action('dialog dismissed')}
/>
)
);

View File

@ -0,0 +1,111 @@
import { css } from 'styled-components';
export const style = ({ theme, open, fullWidth }) => css`
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
z-index: 4;
display: ${open ? 'flex' : 'none'};
flex-direction: column;
align-items: center;
justify-content: center;
> div:first-child {
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
background-color: ${theme.base05};
opacity: 0.2;
}
> div:last-child {
display: flex;
flex-direction: column;
box-sizing: border-box;
position: relative;
z-index: 2;
max-height: 100%;
min-width: 320px;
${fullWidth ? 'width: 99%;' : ''}
padding: 16px;
margin-bottom: 16px;
border: 1px outset ${theme.base01};
border-radius: 2px;
background-color: ${theme.base00};
box-shadow:
0 9px 46px 8px rgba(0, 0, 0, 0.14),
0 11px 15px -7px rgba(0, 0, 0, 0.12),
0 24px 38px 3px rgba(0, 0, 0, 0.2);
> div.mc-dialog--header {
display: flex;
align-items: center;
font-weight: 600;
margin: -17px -17px 0;
padding: 16px;
color: ${theme.light ? theme.base00 : theme.base07};
background-color: ${theme.light ? theme.base04 : theme.base02};
border: none;
> div:first-child {
flex-grow: 1;
}
> button {
box-sizing: border-box;
font-size: 1.5em;
line-height: 1;
font-weight: bold;
margin: 0px;
padding: 0px 5px;
cursor: pointer;
color: inherit;
background-color: transparent;
border: 0px;
-webkit-appearance: none;
outline: none;
}
}
> div.mc-dialog--body {
overflow: auto;
> form {
padding: 0;
> .form-group { margin-bottom: 0; }
> div > fieldset {
legend { display: none; }
#root__description { margin-top: 0; }
}
.mc-dialog--hidden { display: none; }
}
}
> div.mc-dialog--body:not(:empty) {
margin: 16px 0;
}
> div.mc-dialog--footer {
min-height: 45px;
box-sizing: border-box;
font-size: 12px;
display: flex;
align-items: center;
justify-content: flex-end;
margin: 0 -16px -16px;
padding: 2px 10px;
border-top: 1px solid ${theme.base03};
> button {
margin-left: 5px;
}
}
}
`;

View File

@ -0,0 +1,2 @@
export { style as default } from './default';
export { style as material } from './material';

View File

@ -0,0 +1,103 @@
import { css } from 'styled-components';
export const style = ({ theme, open, fullWidth }) => css`
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
z-index: 4;
display: ${open ? 'flex' : 'none'};
flex-direction: column;
align-items: center;
justify-content: center;
> div:first-child {
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
background-color: ${theme.base05};
opacity: 0.3;
}
> div:last-child {
display: flex;
flex-direction: column;
box-sizing: border-box;
position: relative;
z-index: 2;
max-height: 100%;
min-width: 320px;
${fullWidth ? 'width: 99%;' : ''}
padding: 16px;
margin-bottom: 16px;
border: none;
background-color: ${theme.base00};
box-shadow:
0 9px 46px 8px rgba(0, 0, 0, 0.14),
0 11px 15px -7px rgba(0, 0, 0, 0.12),
0 24px 38px 3px rgba(0, 0, 0, 0.2);
> div.mc-dialog--header {
display: flex;
align-items: center;
font-size: 20px;
margin: -17px -17px 6px;
padding: 16px;
border: none;
> div:first-child {
flex-grow: 1;
}
> button {
box-sizing: border-box;
font-size: 1em;
line-height: 1;
font-weight: bold;
margin: 0px;
padding: 0px 5px;
cursor: pointer;
color: inherit;
background-color: transparent;
border: 0px;
-webkit-appearance: none;
outline: none;
}
}
> div.mc-dialog--body {
overflow: auto;
> form {
padding: 0;
> .form-group { margin-bottom: 0; }
> div > fieldset {
legend { display: none; }
#root__description { margin-top: 0; }
}
.mc-dialog--hidden { display: none; }
}
}
> div.mc-dialog--footer {
min-height: 45px;
box-sizing: border-box;
font-size: 12px;
display: flex;
align-items: center;
justify-content: flex-end;
margin: 16px -16px -16px;
padding: 2px 10px;
> button {
box-shadow: none;
}
}
}
`;

View File

@ -0,0 +1,89 @@
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( // eslint-disable-line new-cap
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 @@
export { default } from './Editor';

View File

@ -0,0 +1,42 @@
import React, { Component } from 'react';
import { select } from '@storybook/addon-knobs';
import Editor from '../';
import Tabs from '../../Tabs';
const value1 = `
const func1 = () => {}
`;
const value2 = `
const func2 = () => {}
`;
/* eslint-disable react/prop-types */
export default class WithTabs extends Component {
state = {
selected: 'Function 1'
};
render() {
const { lineNumbers } = this.props;
return (
<Tabs
tabs={[
{
name: 'Function 1',
component: Editor,
selector: () => ({ value: value1, lineNumbers })
},
{
name: 'Function 2',
component: Editor,
selector: () => ({ value: value2, lineNumbers })
}
]}
selected={this.state.selected}
onClick={selected => { this.setState({ selected }); }}
align={select('align', ['left', 'right', 'center'], 'left')}
/>
);
}
}

View File

@ -0,0 +1,41 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import Editor from '../';
import WithTabs from './WithTabs';
const value = `
var themes = [];
function getThemes() {
return themes;
}
`;
storiesOf('Editor', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'Based on [CodeMirror](http://codemirror.net/).',
() => (
<Editor
value={text('value', value)}
lineNumbers={boolean('lineNumbers', true)}
lineWrapping={boolean('lineWrapping', false)}
foldGutter={boolean('foldGutter', true)}
readOnly={boolean('readOnly', false)}
onChange={action('change')}
autofocus
/>
)
)
.addWithInfo(
'with tabs',
'',
() => (
<WithTabs
lineNumbers={boolean('lineNumbers', true)}
/>
)
);

View File

@ -0,0 +1,125 @@
import { css } from 'styled-components';
export const defaultStyle = `
height: 100%;
overflow: auto;
> div {
height: 100%;
line-height: 1.45em;
}
`;
export const themedStyle = theme => css`
height: 100%;
> div {
height: 100%;
line-height: 1.45em;
background-color: ${theme.base00};
color: ${theme.base04};
.cm-header { color: ${theme.base05}; }
.cm-quote { color: ${theme.base09}; }
.cm-keyword { color: ${theme.base0F}; }
.cm-atom { color: ${theme.base0F}; }
.cm-number { color: ${theme.base0F}; }
.cm-def { color: ${theme.base0D}; }
.cm-variable { color: ${theme.base05}; }
.cm-variable-2 { color: ${theme.base0A}; }
.cm-variable-3 { color: ${theme.base0E}; }
.cm-property { color: ${theme.base0C}; }
.cm-operator { color: ${theme.base0E}; }
.cm-comment {
color: ${theme.base05};
font-style: italic;
}
.cm-string { color: ${theme.base0B}; }
.cm-string-2 { color: ${theme.base0A}; }
.cm-meta { color: ${theme.base0B}; }
.cm-qualifier { color: ${theme.base0A}; }
.cm-builtin { color: ${theme.base0F}; }
.cm-bracket { color: ${theme.base09}; }
.CodeMirror-matchingbracket { color: ${theme.base0B}; }
.CodeMirror-nonmatchingbracket { color: ${theme.base08}; }
.cm-tag { color: ${theme.base02}; }
.cm-attribute { color: ${theme.base0C}; }
.cm-hr {
color: transparent;
border-top: 1px solid ${theme.base05};
display: block;
}
.cm-link {
color: ${theme.base02};
cursor: pointer;
}
.cm-special { color: ${theme.base0E}; }
.cm-em {
color: #999;
text-decoration: underline;
text-decoration-style: dotted;
}
.cm-strong { color: ${theme.base01}; }
.cm-error,
.cm-invalidchar {
color: ${theme.base05};
border-bottom: 1px dotted ${theme.base08};
}
div.CodeMirror-selected { background: ${theme.base01}; }
.CodeMirror-line::selection,
.CodeMirror-line > span::selection,
.CodeMirror-line > span > span::selection {
background: ${theme.base01};
}
.CodeMirror {
box-shadow: inset 7px 0 12px -6px #000;
}
.CodeMirror-gutters {
border-right: 0;
}
.CodeMirror-gutters {
background-color: ${theme.base01};
}
.CodeMirror-linenumber {
color: ${theme.base03};
}
.CodeMirror-linenumber {
padding: 0 5px;
}
.CodeMirror-guttermarker-subtle { color: ${theme.base05}; }
.CodeMirror-guttermarker { color: ${theme.base09}; }
.CodeMirror-gutter .CodeMirror-gutter-text {
color: ${theme.base05};
}
.CodeMirror-cursor { border-left: 1px solid #819090; }
.cm-fat-cursor .CodeMirror-cursor { background: ${theme.base02}; }
.cm-animate-fat-cursor { background-color: ${theme.base02}; }
.CodeMirror-activeline-background {
background: ${theme.base07};
}
}
`;

View File

@ -0,0 +1,37 @@
import React, { PureComponent, Component } from 'react';
import PropTypes from 'prop-types';
import JSONSchemaForm from 'react-jsonschema-form';
import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles';
import Button from '../Button';
import customWidgets from './widgets';
const FormContainer = createStyledComponent(styles, JSONSchemaForm);
export default class Form extends (PureComponent || Component) {
render() {
const { widgets, children, submitText, primaryButton, noSubmit, ...rest } = this.props;
return (
<FormContainer {...rest} widgets={{ ...customWidgets, ...widgets }}>
{
noSubmit ? <noscript /> :
children ||
<Button size="big" primary={primaryButton} theme={rest.theme} type="submit">
{submitText || 'Submit'}
</Button>
}
</FormContainer>
);
}
}
Form.propTypes = {
children: PropTypes.any,
submitText: PropTypes.string,
primaryButton: PropTypes.bool,
noSubmit: PropTypes.bool,
schema: PropTypes.object.isRequired,
uiSchema: PropTypes.object,
formData: PropTypes.any,
widgets: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object]))
};

View File

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

View File

@ -0,0 +1,23 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, object, text } from '@storybook/addon-knobs';
import Form from '../';
import { schema, uiSchema, formData } from './schema';
storiesOf('Form', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'Wrapper around [`react-jsonschema-form`](https://github.com/mozilla-services/react-jsonschema-form) with custom widgets.',
() => (
<Form
formData={object('formData', formData)}
schema={object('schema', schema)}
uiSchema={object('uiSchema', uiSchema)}
submitText={text('submitText', 'Submit')}
onChange={action('form changed')}
onSubmit={action('form submitted')}
/>
)
);

View File

@ -0,0 +1,101 @@
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,368 @@
import { css } from 'styled-components';
export default ({ theme }) => css`
padding: 10px;
line-height: 1.846;
font-size: 14px;
color: ${theme.base06};
fieldset {
padding: 0;
margin: 0;
border: 0;
min-width: 0;
}
legend {
display: block;
width: 100%;
padding: 0;
font-size: 20px;
color: ${theme.base04};
border: 0;
}
label {
display: inline-block;
max-width: 100%;
font-weight: bold;
}
.form-control {
display: block;
box-sizing: border-box;
font-size: 12px;
width: 100%;
color: ${theme.base05};
background-color: transparent;
background-image: none;
line-height: ${theme.inputInternalHeight}px;
padding: 0 ${theme.inputPadding}px;
border-style: solid;
border-width: ${theme.inputBorderWidth}px;
border-color: ${theme.inputBorderColor};
border-radius: ${theme.inputBorderRadius}px;
}
.form-control:focus,
input.form-control:focus {
outline: 0;
${theme.inputFocusedStyle}
}
.form-control[disabled],
.form-control[readonly],
fieldset[disabled] .form-control {
background-color: transparent;
opacity: 1;
}
.form-control[disabled],
fieldset[disabled] .form-control {
cursor: not-allowed;
}
textarea.form-control {
height: auto;
}
.form-group {
margin-bottom: 5px;
}
.radio,
.checkbox {
position: relative;
display: block;
margin-top: 10px;
margin-bottom: 10px;
}
.radio label,
.checkbox label {
min-height: 23px;
padding-left: 20px;
margin-bottom: 0;
font-weight: normal;
cursor: pointer;
}
.radio input[type="radio"],
.radio-inline input[type="radio"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
position: absolute;
margin-left: -20px;
margin-top: 4px \\9;
}
.radio + .radio,
.checkbox + .checkbox {
margin-top: -5px;
}
.radio-inline,
.checkbox-inline {
position: relative;
display: inline-block;
padding-left: 25px;
margin-bottom: 0;
vertical-align: middle;
font-weight: normal;
cursor: pointer;
}
.radio-inline + .radio-inline,
.checkbox-inline + .checkbox-inline {
margin-top: 0;
margin-left: 10px;
}
.radio label,
.radio-inline label,
.checkbox label,
.checkbox-inline label {
padding-left: 25px;
}
.radio input[type="radio"],
.radio-inline input[type="radio"],
.checkbox input[type="radio"],
.checkbox-inline input[type="radio"],
.radio input[type="checkbox"],
.radio-inline input[type="checkbox"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
margin-left: -25px;
}
input[type="radio"],
.radio input[type="radio"],
.radio-inline input[type="radio"] {
position: relative;
margin-top: 6px;
margin-right: 4px;
vertical-align: top;
border: none;
background-color: transparent;
appearance: none;
cursor: pointer;
}
input[type="radio"]:focus,
.radio input[type="radio"]:focus,
.radio-inline input[type="radio"]:focus {
outline: none;
}
input[type="radio"]:before,
.radio input[type="radio"]:before,
.radio-inline input[type="radio"]:before,
input[type="radio"]:after,
.radio input[type="radio"]:after,
.radio-inline input[type="radio"]:after {
content: "";
display: block;
width: 18px;
height: 18px;
border-radius: 50%;
transition: 240ms;
box-sizing: border-box;
}
input[type="radio"]:before,
.radio input[type="radio"]:before,
.radio-inline input[type="radio"]:before {
position: absolute;
left: 0;
top: -3px;
background-color: ${theme.base0D};
transform: scale(0);
}
input[type="radio"]:after,
.radio input[type="radio"]:after,
.radio-inline input[type="radio"]:after {
position: relative;
top: -3px;
border: 2px solid ${theme.base03};
}
input[type="radio"]:checked:before,
.radio input[type="radio"]:checked:before,
.radio-inline input[type="radio"]:checked:before {
transform: scale(0.5);
}
input[type="radio"]:disabled:checked:before,
.radio input[type="radio"]:disabled:checked:before,
.radio-inline input[type="radio"]:disabled:checked:before {
background-color: ${theme.base03};
}
input[type="radio"]:checked:after,
.radio input[type="radio"]:checked:after,
.radio-inline input[type="radio"]:checked:after {
border-color: ${theme.base0D};
}
input[type="radio"]:disabled:after,
.radio input[type="radio"]:disabled:after,
.radio-inline input[type="radio"]:disabled:after,
input[type="radio"]:disabled:checked:after,
.radio input[type="radio"]:disabled:checked:after,
.radio-inline input[type="radio"]:disabled:checked:after {
border-color: ${theme.base03};
}
input[type="checkbox"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
position: relative;
border: none;
margin-bottom: -4px;
appearance: none;
cursor: pointer;
}
input[type="checkbox"]:focus,
.checkbox input[type="checkbox"]:focus,
.checkbox-inline input[type="checkbox"]:focus {
outline: none;
}
input[type="checkbox"]:focus:after,
.checkbox input[type="checkbox"]:focus:after,
.checkbox-inline input[type="checkbox"]:focus:after {
border-color: ${theme.base0D};
}
input[type="checkbox"]:after,
.checkbox input[type="checkbox"]:after,
.checkbox-inline input[type="checkbox"]:after {
content: "";
display: block;
width: 18px;
height: 18px;
margin-top: -2px;
margin-right: 5px;
border: 2px solid ${theme.base03};
border-radius: 4px;
transition: 240ms;
box-sizing: border-box;
}
input[type="checkbox"]:checked:before,
.checkbox input[type="checkbox"]:checked:before,
.checkbox-inline input[type="checkbox"]:checked:before {
content: "";
position: absolute;
top: 0;
left: 6px;
display: table;
width: 6px;
height: 12px;
border: 2px solid #fff;
border-top-width: 0;
border-left-width: 0;
transform: rotate(45deg);
box-sizing: border-box;
}
input[type="checkbox"]:checked:after,
.checkbox input[type="checkbox"]:checked:after,
.checkbox-inline input[type="checkbox"]:checked:after {
background-color: ${theme.base0D};
border-color: ${theme.base0D};
}
input[type="checkbox"]:disabled:after,
.checkbox input[type="checkbox"]:disabled:after,
.checkbox-inline input[type="checkbox"]:disabled:after {
border-color: ${theme.base03};
}
input[type="checkbox"]:disabled:checked:after,
.checkbox input[type="checkbox"]:disabled:checked:after,
.checkbox-inline input[type="checkbox"]:disabled:checked:after {
background-color: ${theme.base03};
border-color: transparent;
}
input[type="radio"][disabled],
input[type="checkbox"][disabled],
input[type="radio"].disabled,
input[type="checkbox"].disabled,
fieldset[disabled] input[type="radio"],
fieldset[disabled] input[type="checkbox"] {
cursor: not-allowed;
}
.radio-inline.disabled,
.checkbox-inline.disabled,
fieldset[disabled] .radio-inline,
fieldset[disabled] .checkbox-inline {
cursor: not-allowed;
}
.radio.disabled label,
.checkbox.disabled label,
fieldset[disabled] .radio label,
fieldset[disabled] .checkbox label {
cursor: not-allowed;
}
.has-error .help-block,
.has-error .control-label,
.has-error .radio,
.has-error .checkbox,
.has-error .radio-inline,
.has-error .checkbox-inline,
.has-error.radio label,
.has-error.checkbox label,
.has-error.radio-inline label,
.has-error.checkbox-inline label {
color: ${theme.base08};
}
.panel {
border: none;
border-radius: 2px;
box-shadow: 0 1px 4px ${theme.base03};
margin-bottom: 23px;
}
.panel-heading {
padding: 5px 15px;
}
.panel-title {
margin-top: 0;
margin-bottom: 0;
font-size: 15px;
}
.panel-danger {
box-shadow: 0 1px 4px ${theme.base08};
}
.panel-danger>.panel-heading {
color: ${theme.base00};
background-color: ${theme.base08};
}
.text-danger {
color: ${theme.base08};
}
.list-group {
padding: 0;
margin: 0;
}
.list-group-item {
position: relative;
display: block;
padding: 10px 15px;
}
`;

View File

@ -0,0 +1,29 @@
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,49 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import CloseIcon from 'react-icons/lib/md/close';
import WarningIcon from 'react-icons/lib/md/warning';
import ErrorIcon from 'react-icons/lib/md/error';
import SuccessIcon from 'react-icons/lib/md/check-circle';
import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles';
const NotificationWrapper = createStyledComponent(styles);
export default class Notification extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.children !== this.props.children ||
nextProps.type !== this.props.type;
}
getIcon = () => {
switch (this.props.type) {
case 'warning': return <WarningIcon />;
case 'error': return <ErrorIcon />;
case 'success': return <SuccessIcon />;
default: return null;
}
};
render() {
return (
<NotificationWrapper type={this.props.type} theme={this.props.theme}>
{this.getIcon()}
<span>{this.props.children}</span>
{this.props.onClose &&
<button onClick={this.props.onClose}><CloseIcon /></button>
}
</NotificationWrapper>
);
}
}
Notification.propTypes = {
children: PropTypes.any.isRequired,
type: PropTypes.oneOf(['info', 'success', 'warning', 'error']),
onClose: PropTypes.func,
theme: PropTypes.object
};
Notification.defaultProps = {
type: 'info'
};

View File

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

View File

@ -0,0 +1,33 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import styled from 'styled-components';
import { withKnobs, text, select } from '@storybook/addon-knobs';
import Notification from '../';
export const Container = styled.div`
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
`;
storiesOf('Notification', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'',
() => (
<Container>
<Notification
type={
select('type', ['info', 'success', 'warning', 'error'], 'warning')
}
onClose={action('notification closed')}
>
{text('Message', 'Hello Notification')}
</Notification>
</Container>
)
);

View File

@ -0,0 +1,59 @@
import { css } from 'styled-components';
const getBackground = (theme, type) => {
switch (type) {
case 'success':
return `background-color: ${theme.base0B};`;
case 'warning':
return `background-color: ${theme.base0A};`;
case 'error':
return `background-color: ${theme.base08};`;
default:
return `background-color: ${theme.base0D};`;
}
};
export default ({ theme, type }) => css`
display: flex;
align-items: flex-start;
flex-shrink: 0;
box-sizing: border-box;
box-shadow: inset ${theme.base05} 0 0 1px;
font-size: 1.1em;
padding: 7px;
width: 100%;
color: ${theme.base01};
${getBackground(theme, type)}
& > svg:first-child {
font-size: 1.4em;
opacity: 0.5;
}
& > span {
width: 100%;
text-align: center;
padding: 0.1em;
}
& > button {
cursor: pointer;
float: right;
font-size: 1.1em;
border: 1px solid transparent;
background: transparent;
padding: 0.1em;
line-height: 0;
outline: none;
color: inherit;
opacity: 0.8;
}
& > button:hover, & > button:active {
opacity: 1;
}
& > button:focus {
border: 1px solid ${theme.base03};
}
`;

View File

@ -0,0 +1,48 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import createStyledComponent from '../utils/createStyledComponent';
import styles from './styles';
const SegmentedWrapper = createStyledComponent(styles);
export default class SegmentedControl extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.disabled !== this.props.disabled ||
nextProps.selected !== this.props.selected;
}
onClick = e => {
this.props.onClick(e.target.value);
};
onMouseUp = e => {
e.target.blur();
};
render() {
const { values, selected } = this.props;
return (
<SegmentedWrapper disabled={this.props.disabled} theme={this.props.theme}>
{values.map(button => (
<button
key={button}
value={button}
data-selected={button === selected ? true : undefined}
onMouseUp={this.onMouseUp}
onClick={this.onClick}
>
{button}
</button>
))}
</SegmentedWrapper>
);
}
}
SegmentedControl.propTypes = {
values: PropTypes.array.isRequired,
selected: PropTypes.string,
onClick: PropTypes.func,
disabled: PropTypes.bool,
theme: PropTypes.object
};

View File

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

View File

@ -0,0 +1,31 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import styled from 'styled-components';
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import SegmentedControl from '../';
export const Container = styled.div`
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
`;
storiesOf('SegmentedControl', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'',
() => (
<Container>
<SegmentedControl
values={['Button1', 'Button2', 'Button3']}
selected={text('selected', 'Button1')}
onClick={action('button selected')}
disabled={boolean('Disabled', false)}
/>
</Container>
)
);

View File

@ -0,0 +1,47 @@
import { css } from 'styled-components';
import color from '../../utils/color';
export default ({ theme, disabled }) => css`
display: flex;
flex-shrink: 0;
> [data-selected], > [data-selected]:hover {
background-color: ${theme.base04};
color: ${theme.base00};
}
> button {
outline: none;
box-sizing: border-box;
flex-shrink: 0;
-webkit-font-smoothing: antialiased;
min-height: 30px;
border: 1px solid ${color(theme.base03, 'alpha', 0.4)};
border-left-width: 0;
padding: 5px 10px;
${disabled ? `
cursor: not-allowed;
opacity: 0.6;
` : `
cursor: pointer;
color: ${theme.base05};
background-color: ${theme.base01};
&:hover, &:focus {
background-color: ${theme.base02};
color: ${theme.base07};
}
`}
&:first-child {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
border-left-width: 1px;
}
&:last-child {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
}
`;

View File

@ -0,0 +1,29 @@
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 @@
export default from './Select';

View File

@ -0,0 +1,43 @@
import React from 'react';
import styled from 'styled-components';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, text, number, boolean } from '@storybook/addon-knobs';
import Select from '../';
import { options } from './options';
export const Container = styled.div`
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
> div {
width: 90%;
}
`;
storiesOf('Select', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'Wrapper around [React Select](https://github.com/JedWatson/react-select) with themes and new props like `openOuterUp` and `menuMaxHeight`.',
() => (
<Container>
<Select
value={text('value', 'one')}
menuMaxHeight={number('menuMaxHeight', 200)}
options={options}
onChange={action('selected')}
autosize={boolean('autosize', false)}
clearable={boolean('clearable', false)}
disabled={boolean('disabled', false)}
isLoading={boolean('isLoading', false)}
multi={boolean('multiselect', false)}
searchable={boolean('searchable', true)}
openOuterUp={boolean('openOuterUp', false)}
/>
</Container>
)
);

View File

@ -0,0 +1,5 @@
export const options = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'hundred', label: 'One hundred' }
];

View File

@ -0,0 +1,363 @@
import { css } from 'styled-components';
import { fadeIn, spinner } from '../../utils/animations';
export default ({ theme, openOuterUp, menuMaxHeight }) => css`
&.Select {
position: relative;
&,
& div,
& input,
& span {
box-sizing: border-box;
}
&.is-disabled > .Select-control {
background-color: ${theme.base02};
&:hover {
box-shadow: none;
}
}
&.is-disabled .Select-arrow-zone {
cursor: default;
pointer-events: none;
}
}
.Select-control {
background-color: ${theme.base00};
border-color: ${theme.inputBorderColor};
border-radius: ${theme.inputBorderRadius}px;
border-style: solid;
border-width: ${theme.inputBorderWidth}px;
color: ${theme.base07};
cursor: default;
display: table;
border-spacing: 0;
border-collapse: separate;
height: ${theme.inputHeight}px;
outline: none;
overflow: hidden;
position: relative;
width: 100%;
&:hover {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.Select-input:focus {
outline: none;
}
}
&.is-searchable {
&.is-open > .Select-control {
cursor: text;
}
}
&.is-open > .Select-control {
border-radius: ${openOuterUp ?
`0 0 ${theme.inputBorderRadius}px ${theme.inputBorderRadius}px` :
`${theme.inputBorderRadius}px ${theme.inputBorderRadius}px 0 0`
};
}
&.is-searchable {
&.is-focused:not(.is-open) > .Select-control {
cursor: text;
}
}
&.is-focused > .Select-control {
${theme.inputFocusedStyle}
}
.Select-placeholder,
&.Select--single > .Select-control .Select-value {
bottom: 0;
color: ${theme.base03};
left: 0;
line-height: ${theme.inputInternalHeight}px;
padding: 0 ${theme.inputPadding}px;
position: absolute;
right: 0;
top: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.has-value.Select--single > .Select-control .Select-value,
&.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value {
.Select-value-label {
color: ${theme.base07};
}
a.Select-value-label {
cursor: pointer;
text-decoration: none;
&:hover,
&:focus {
color: ${theme.base0D};
outline: none;
text-decoration: underline;
}
}
}
.Select-input {
height: ${theme.inputInternalHeight}px;
padding-left: ${theme.inputPadding}px;
padding-right: ${theme.inputPadding}px;
vertical-align: middle;
> input {
color: ${theme.base07};
background: none transparent;
border: 0 none;
box-shadow: none;
width: 100%;
cursor: default;
display: inline-block;
font-family: inherit;
font-size: inherit;
margin: 0;
outline: none;
line-height: 14px;
padding: ${(theme.inputInternalHeight - 14) / 2 - 2}px 0;
-webkit-appearance: none;
.is-focused & {
cursor: text;
}
}
}
&.has-value.is-pseudo-focused .Select-input {
opacity: 0;
}
.Select-control:not(.is-searchable) > .Select-input {
outline: none;
}
.Select-loading-zone {
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: ${theme.spinnerSize}px;
}
.Select-loading {
${spinner(theme)}
vertical-align: middle;
}
.Select-clear-zone {
animation: ${fadeIn} 200ms;
color: ${theme.base03};
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
&:hover {
color: #d0021b;
}
}
.Select-clear {
display: inline-block;
font-size: ${Math.floor(theme.inputHeight / 2)}px;
line-height: 1px;
}
.Select-clear-zone,
.Select--multi .Select-clear-zone {
width: ${theme.inputInternalHeight / 2}px;
}
.Select--multi .Select-multi-value-wrapper {
display: inline-block;
}
&.Select .Select-aria-only {
display: inline-block;
position: absolute;
height: 1px;
width: 1px;
margin: -1px;
clip: rect(0, 0, 0, 0);
overflow: hidden;
}
.Select-arrow-zone {
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: ${theme.selectArrowWidth * 5}px;
padding-right: ${theme.selectArrowWidth}px;
}
.Select-arrow {
border-color: ${theme.base03} transparent transparent;
border-style: solid;
border-width:
${theme.selectArrowWidth}px
${theme.selectArrowWidth}px
${theme.selectArrowWidth / 2}px;
display: inline-block;
height: 0;
width: 0;
}
.is-open .Select-arrow,
.Select-arrow-zone:hover > .Select-arrow {
border-top-color: ${theme.base04};
}
.Select-menu-outer {
border: 1px solid ${theme.base02};
box-shadow: 0 ${openOuterUp ? '-1px' : '1px'} 0 rgba(0, 0, 0, 0.06);
box-sizing: border-box;
/* stylelint-disable declaration-empty-line-before */
${openOuterUp ? 'margin-bottom' : 'margin-top'}: -1px;
/* stylelint-enable */
max-height: ${menuMaxHeight}px;
position: absolute;
top: auto;
left: 0;
bottom: ${openOuterUp ? '100%' : 'auto'};
width: 100%;
min-width: 70px;
z-index: 1000;
-webkit-overflow-scrolling: touch;
}
.Select-menu {
max-height: ${menuMaxHeight - 2}px;
overflow-y: auto;
}
.Select-option {
box-sizing: border-box;
background-color: ${theme.base00};
color: ${theme.base07};
cursor: pointer;
display: block;
padding: ${theme.inputHeight / 3}px;
line-height: ${theme.inputInternalHeight / 2}px;
&.is-selected {
background-color: ${theme.base01};
color: ${theme.base07};
}
&.is-focused {
background-color: ${theme.base02};
color: ${theme.base07};
}
&.is-disabled {
color: ${theme.base05};
cursor: default;
}
}
.Select-noresults {
box-sizing: border-box;
color: ${theme.base06};
background-color: ${theme.base00};
cursor: default;
display: block;
padding: ${theme.inputPadding}px;
}
&.Select--multi {
.Select-input {
display: inline-block;
vertical-align: middle;
margin-left: ${theme.inputPadding}px;
padding: 0;
}
&.has-value .Select-input {
margin-left: ${theme.selectArrowWidth}px;
}
.Select-value {
background-color: ${theme.base00};
border-radius: ${theme.inputBorderRadius}px;
border: 1px solid ${theme.base02};
color: ${theme.base07};
display: inline-block;
font-size: 0.9em;
margin-left: ${theme.inputPadding / 2}px;
margin-top: 0;
vertical-align: middle;
}
.Select-value-icon,
.Select-value-label {
display: inline-block;
vertical-align: middle;
}
.Select-value-label {
border-bottom-right-radius: ${theme.inputBorderRadius}px;
border-top-right-radius: ${theme.inputBorderRadius}px;
cursor: default;
padding: ${Math.floor(theme.inputPadding / 4)}px ${Math.floor(theme.inputPadding / 2)}px;
}
a.Select-value-label {
color: ${theme.base07};
cursor: pointer;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.Select-value-icon {
cursor: pointer;
border-bottom-left-radius: ${theme.inputBorderRadius}px;
border-top-left-radius: ${theme.inputBorderRadius}px;
border-right: 1px solid ${theme.base02};
padding: 0px ${Math.floor(theme.inputPadding / 2)}px;
&:hover,
&:focus {
background-color: ${theme.base03};
color: ${theme.base00};
}
&:active {
background-color: ${theme.base06};
}
}
}
&.Select--multi.is-disabled {
.Select-value {
background-color: ${theme.base00};
border: 1px solid ${theme.base01};
color: ${theme.base05};
}
.Select-value-icon {
cursor: not-allowed;
}
}
`;

View File

@ -0,0 +1,62 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import createStyledComponent from '../utils/createStyledComponent';
import * as styles from './styles';
import { containerStyle } from './styles/common';
const SliderWrapper = createStyledComponent(styles);
const ContainerWithValue = createStyledComponent(containerStyle);
export default class Slider extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.label !== this.props.label ||
nextProps.value !== this.props.value ||
nextProps.max !== this.props.max ||
nextProps.min !== this.props.min ||
nextProps.withValue !== this.props.withValue ||
nextProps.disabled !== this.props.disabled;
}
onChange = e => {
this.props.onChange(parseFloat(e.target.value));
};
render() {
const { label, sublabel, withValue, theme, ...rest } = this.props;
const { value, max, min, disabled } = rest;
const absMax = max - min;
const percent = (value - min) / absMax * 100;
const slider = <input {...rest} onChange={this.onChange} type="range" />;
return (
<SliderWrapper
percent={percent}
disabled={disabled || absMax === 0}
withLabel={!!label}
theme={theme}
>
{label && <label>{label} {sublabel && <span>{sublabel}</span>}</label>}
{!withValue ? slider :
<ContainerWithValue theme={theme}>
{slider}
<div>{value}</div>
</ContainerWithValue>
}
</SliderWrapper>
);
}
}
Slider.propTypes = {
value: PropTypes.number,
min: PropTypes.number,
max: PropTypes.number,
label: PropTypes.string,
sublabel: PropTypes.string,
withValue: PropTypes.bool,
disabled: PropTypes.bool,
onChange: PropTypes.func,
theme: PropTypes.object
};
Slider.defaultProps = { value: 0, min: 0, max: 100 };

View File

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

View File

@ -0,0 +1,35 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import styled from 'styled-components';
import { withKnobs, number, text, boolean } from '@storybook/addon-knobs';
import Slider from '../';
export const Container = styled.div`
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
`;
storiesOf('Slider', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'',
() => (
<Container>
<Slider
value={number('value', 0)}
min={number('min', 0)}
max={number('max', 100)}
label={text('label', 'Slider label')}
sublabel={text('sublabel', '(sublabel)')}
withValue={boolean('withValue', false)}
disabled={boolean('disabled', false)}
onChange={action('slider changed')}
/>
</Container>
)
);

View File

@ -0,0 +1,15 @@
import { css } from 'styled-components';
export const containerStyle = ({ theme }) => css`
display: flex;
align-items: center;
div {
margin-left: 4px;
padding: 0.3em 0.5em;
border: ${theme.inputBorderWidth}px solid ${theme.inputBorderColor};
border-radius: ${theme.inputBorderRadius}px;
background-color: ${theme.base00};
opacity: 0.7;
}
`;

View File

@ -0,0 +1,82 @@
/*
Based on:
http://codepen.io/thebabydino/pen/NPYBJQ
http://codepen.io/thebabydino/pen/zxRzPw
http://codepen.io/thebabydino/pen/dPqrrY
http://codepen.io/thebabydino/pen/YPOPxr
*/
import { css } from 'styled-components';
import { prefixSelectors } from '../../utils/autoPrefix';
export const style = ({ theme, percent, disabled, withLabel }) => css`
display: block;
width: 100%;
position: relative;
padding: ${withLabel ? '1.2em 0' : '0'};
label {
position: absolute;
display: block;
font-size: 11px;
padding: 0 10px;
top: 0.3em;
width: 100%;
color: ${theme.base06};
> span { color: ${theme.base04}; }
}
input {
opacity: ${disabled ? '0.5' : '1'};
outline: none;
margin: 0;
box-sizing: border-box;
display: block;
appearance: none;
border-top: solid 0.5em transparent;
border-bottom: solid 0.5em transparent;
padding: 0.5em;
width: 100%;
height: 2.5em;
border-radius: 0.8em/1.1em;
font-size: 1em;
cursor: pointer;
background: linear-gradient(${theme.base02}, ${theme.base00}) padding-box, 50% 50% border-box;
background-size: 100% 100%;
}
${prefixSelectors('input', ['webkit-slider-runnable-track', 'moz-range-track', 'ms-track'], `{
position: relative;
height: 0.8em;
border-radius: 0.5em;
box-shadow: 0 0 .125em ${theme.base04};
background: linear-gradient(${theme.base01}, ${theme.base02} 40%, ${theme.base01})
no-repeat ${theme.base00};
background-size: ${percent}% 100%;
}`)}
${prefixSelectors('input', ['webkit-slider-thumb', 'moz-range-thumb', 'ms-thumb'], `{
position: relative;
appearance: none;
cursor: ew-resize;
margin-top: -0.36em;
background: ${theme.light ? theme.base00 : theme.base06};
border: solid 1px ${theme.base03};
box-shadow: 0 1px .125em ${theme.base03};
width: 1.5em;
height: 1.5em;
border-radius: 50%;
cursor: pointer;
}`)}
${prefixSelectors('input:focus:not(:active)',
['webkit-slider-thumb', 'moz-range-thumb', 'ms-thumb'],
`{
box-shadow: 0 0 1px 2px ${theme.base0D};
}`)}
input::-moz-focus-outer {
border: 0;
}
`;

View File

@ -0,0 +1,2 @@
export { style as default } from './default';
export { style as material } from './material';

View File

@ -0,0 +1,68 @@
import { css } from 'styled-components';
import { prefixSelectors } from '../../utils/autoPrefix';
import color from '../../utils/color';
import { animationCurve } from '../../utils/animations';
export const style = ({ theme, percent, disabled, withLabel }) => css`
display: block;
width: 100%;
position: relative;
z-index: 1;
padding: ${withLabel ? '2em 0' : '0'};
label {
position: absolute;
display: block;
font-size: 11px;
padding: 0.3em 0.5em;
top: 0;
width: 100%;
color: ${theme.base06};
> span { color: ${theme.base04}; }
}
input {
opacity: ${disabled ? '0.7' : '1'};
outline: none;
box-sizing: border-box;
display: block;
width: 100%;
margin: 0;
cursor: pointer;
color: inherit;
background-color: ${theme.base02};
background-image:
linear-gradient(90deg, currentcolor, currentcolor ${percent}%, transparent ${percent}%);
background-clip: content-box;
height: 0.5em;
border-radius: 999px;
appearance: none;
font-size: 1em;
}
${prefixSelectors('input', ['webkit-slider-thumb', 'moz-range-thumb', 'ms-thumb'], `{
width: 1.5em;
height: 1.5em;
background-image: none;
background-color: ${percent === 0 ? theme.base00 : 'currentcolor'};
border: ${percent === 0 ? `5px solid ${theme.base03}` : 'none'};;
border-radius: 50%;
appearance: none;
transition: transform 0.18s ${animationCurve},
border 0.18s ${animationCurve},
box-shadow 0.18s ${animationCurve},
background 0.28s ${animationCurve};
}`)}
${prefixSelectors('input:focus:not(:active)',
['webkit-slider-thumb', 'moz-range-thumb', 'ms-thumb'],
`{
box-shadow: 0 0 0 8px ${color(theme.base0D, 'alpha', 0.5)};
transform: scale(1.2);
}`)}
input::-moz-focus-outer {
border: 0;
}
`;

View File

@ -0,0 +1,93 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import TabsHeader from './TabsHeader';
import { TabsContainer } from './styles/common';
export default class Tabs extends Component {
constructor(props) {
super(props);
this.updateTabs(props);
}
componentWillReceiveProps(nextProps) {
if (nextProps.selected !== this.props.selected) {
this.updateTabs(nextProps);
}
}
onMouseUp = e => {
e.target.blur();
};
onClick = e => {
this.props.onClick(e.target.value);
};
updateTabs(props) {
const tabs = props.tabs;
const selected = props.selected;
this.tabsHeader = tabs.map((tab, i) => {
let isSelected;
const value = typeof tab.value !== 'undefined' ? tab.value : tab.name;
if (value === selected) {
isSelected = true;
if (tab.component) {
this.SelectedComponent = tab.component;
if (tab.selector) this.selector = () => tab.selector(tab);
}
}
return (
<button
key={i}
value={value}
data-selected={isSelected}
onMouseUp={this.onMouseUp}
onClick={this.onClick}
>
{tab.name}
</button>
);
});
}
render() {
const tabsHeader = (
<TabsHeader
tabs={this.tabsHeader}
items={this.props.tabs}
main={this.props.main}
collapsible={this.props.collapsible}
onClick={this.props.onClick}
selected={this.props.selected}
position={this.props.position}
/>
);
if (!this.SelectedComponent) {
return (
<TabsContainer position={this.props.position}>
{tabsHeader}
</TabsContainer>
);
}
return (
<TabsContainer position={this.props.position}>
{tabsHeader}
<div><this.SelectedComponent {...(this.selector && this.selector())} /></div>
</TabsContainer>
);
}
}
Tabs.propTypes = {
tabs: PropTypes.array.isRequired,
selected: PropTypes.string,
main: PropTypes.bool,
onClick: PropTypes.func.isRequired,
collapsible: PropTypes.bool,
position: PropTypes.oneOf(['left', 'right', 'center'])
};
Tabs.defaultProps = { position: 'left' };

View File

@ -0,0 +1,201 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import observeResize from 'simple-element-resize-detector';
import CollapseIcon from 'react-icons/lib/fa/angle-double-right';
import ContextMenu from '../ContextMenu';
import createStyledComponent from '../utils/createStyledComponent';
import * as styles from './styles';
const TabsWrapper = createStyledComponent(styles);
export default class TabsHeader extends Component {
constructor(props) {
super(props);
this.state = {
visibleTabs: props.tabs.slice(),
hiddenTabs: [],
subMenuOpened: false,
contextMenu: undefined
};
this.iconWidth = 0;
this.hiddenTabsWidth = [];
}
componentWillReceiveProps(nextProps) {
if (nextProps.tabs !== this.props.tabs ||
nextProps.selected !== this.props.selected ||
nextProps.collapsible !== this.props.collapsible) {
this.setState({ hiddenTabs: [], visibleTabs: nextProps.tabs.slice() });
}
}
componentDidMount() {
if (this.props.collapsible) {
this.collapse();
this.enableResizeEvents();
}
}
componentDidUpdate(prevProps) {
const { collapsible } = this.props;
if (!collapsible) {
if (prevProps.collapsible !== collapsible) this.disableResizeEvents();
return;
}
let shouldCollapse = false;
if (this.iconWidth === 0) {
const tabButtons = this.tabsRef.children;
if (this.tabsRef.children[tabButtons.length - 1].value === 'expandIcon') {
this.iconWidth = tabButtons[tabButtons.length - 1].getBoundingClientRect().width;
shouldCollapse = true;
}
} else if (this.state.hiddenTabs.length === 0) {
this.iconWidth = 0;
}
if (prevProps.collapsible !== collapsible) {
this.enableResizeEvents();
shouldCollapse = true;
}
if (shouldCollapse || this.props.selected !== prevProps.selected) {
this.collapse();
}
}
componentWillUnmount() {
this.disableResizeEvents();
}
enableResizeEvents() {
this.resizeDetector = observeResize(this.tabsWrapperRef, this.collapse);
window.addEventListener('mousedown', this.hideSubmenu);
}
disableResizeEvents() {
this.resizeDetector.remove();
window.removeEventListener('mousedown', this.hideSubmenu);
}
collapse = () => {
if (this.state.subMenuOpened) this.hideSubmenu();
const { selected, tabs } = this.props;
const tabsWrapperRef = this.tabsWrapperRef;
const tabsRef = this.tabsRef;
const tabButtons = this.tabsRef.children;
const visibleTabs = this.state.visibleTabs;
const hiddenTabs = this.state.hiddenTabs;
let tabsWrapperRight = tabsWrapperRef.getBoundingClientRect().right;
if (!tabsWrapperRight) return; // tabs are hidden
const tabsRefRight = tabsRef.getBoundingClientRect().right;
let i = visibleTabs.length - 1;
let hiddenTab;
if (tabsRefRight >= tabsWrapperRight - this.iconWidth) {
if (
this.props.position === 'right' && hiddenTabs.length > 0 &&
tabsRef.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
tabsWrapperRef.getBoundingClientRect().width
) {
while (
i < tabs.length - 1 &&
tabsRef.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
tabsWrapperRef.getBoundingClientRect().width
) {
hiddenTab = hiddenTabs.shift();
visibleTabs.splice(Number(hiddenTab.key), 0, hiddenTab);
i++;
}
} else {
while (
i > 0 && tabButtons[i] &&
tabButtons[i].getBoundingClientRect().right >= tabsWrapperRight - this.iconWidth
) {
if (tabButtons[i].value !== selected) {
hiddenTabs.unshift(...visibleTabs.splice(i, 1));
this.hiddenTabsWidth.unshift(tabButtons[i].getBoundingClientRect().width);
} else {
tabsWrapperRight -= tabButtons[i].getBoundingClientRect().width;
}
i--;
}
}
} else {
while (
i < tabs.length - 1 && tabButtons[i] &&
tabButtons[i].getBoundingClientRect().right +
this.hiddenTabsWidth[0] < tabsWrapperRight - this.iconWidth
) {
hiddenTab = hiddenTabs.shift();
visibleTabs.splice(Number(hiddenTab.key), 0, hiddenTab);
this.hiddenTabsWidth.shift();
i++;
}
}
this.setState({ visibleTabs, hiddenTabs });
};
hideSubmenu = () => {
this.setState({ subMenuOpened: false, contextMenu: undefined });
};
getTabsWrapperRef = node => {
this.tabsWrapperRef = node;
};
getTabsRef = node => {
this.tabsRef = node;
};
expandMenu = (e) => {
const rect = e.currentTarget.children[0].getBoundingClientRect();
this.setState({
contextMenu: {
top: rect.top + 10,
left: rect.left
},
subMenuOpened: true
});
};
render() {
const { visibleTabs, hiddenTabs, contextMenu } = this.state;
return (
<TabsWrapper
innerRef={this.getTabsWrapperRef}
main={this.props.main}
position={this.props.position}
>
<div ref={this.getTabsRef}>
{visibleTabs}
{this.props.collapsible && visibleTabs.length < this.props.items.length &&
<button onClick={this.expandMenu} value="expandIcon"><CollapseIcon /></button>
}
</div>
{this.props.collapsible && contextMenu &&
<ContextMenu
items={hiddenTabs}
onClick={this.props.onClick}
x={contextMenu.left}
y={contextMenu.top}
visible={this.state.subMenuOpened}
/>
}
</TabsWrapper>
);
}
}
TabsHeader.propTypes = {
tabs: PropTypes.array.isRequired,
items: PropTypes.array.isRequired,
main: PropTypes.bool,
onClick: PropTypes.func,
position: PropTypes.string,
collapsible: PropTypes.bool,
selected: PropTypes.string
};

View File

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

View File

@ -0,0 +1,41 @@
import React from 'react';
/* eslint-disable react/prop-types */
const Component = ({ selected }) => (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
fontSize: '22px'
}}
>
Selected {selected}
</div>
);
/* eslint-enable react/prop-types */
const selector = tab => ({ selected: tab.name });
export const tabs = [
{
name: 'Tab1',
component: Component,
selector
},
{
name: 'Tab2',
component: Component,
selector
},
{
name: 'Tab3',
component: Component,
selector
}
];
export const simple10Tabs = [];
for (let i = 1; i <= 10; i++) simple10Tabs.push({ name: `Tab${i}`, value: `${i}` });

View File

@ -0,0 +1,46 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, text, boolean, select } from '@storybook/addon-knobs';
import styled from 'styled-components';
import Tabs from '../';
import { tabs, simple10Tabs } from './data';
const Container = styled.div`
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
`;
storiesOf('Tabs', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'',
() => (
<Container><Tabs
tabs={simple10Tabs}
selected={text('selected', '2')}
main={boolean('main', true)}
onClick={action('tab selected')}
collapsible={boolean('collapsible', true)}
position={select('position', ['left', 'right', 'center'], 'left')}
/></Container>
)
)
.addWithInfo(
'with content',
'',
() => (
<Tabs
tabs={tabs}
selected={text('selected', 'Tab2')}
main={boolean('main', false)}
onClick={action('tab selected')}
collapsible={boolean('collapsible', false)}
position={select('position', ['left', 'right', 'center'], 'left')}
/>
)
);

View File

@ -0,0 +1,27 @@
import styled from 'styled-components';
export const TabsContainer = styled.div`
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
overflow-y: hidden;
overflow-x: hidden;
height: 100%;
> div > div:first-child {
${props => props.position !== 'left' && `
margin-left: auto !important;
`}
${props => props.position === 'center' && `
margin-right: auto !important;
`}
}
> div:nth-child(2) {
flex: 1;
overflow-y: auto;
position: relative;
}
`;

View File

@ -0,0 +1,70 @@
import { css } from 'styled-components';
export const style = ({ theme, main }) => css`
display: flex;
flex: 0 0 1;
padding-left: 1px;
background-color: ${theme.base01};
width: 100%;
overflow: hidden;
${!main && `
border-top: 1px solid ${theme.base01};
border-bottom: 1px solid ${theme.base02};
`}
> div {
display: flex;
align-items: flex-end;
flex-wrap: nowrap;
button {
background-color: ${theme.base01};
color: ${theme.base05};
letter-spacing: 0.3px;
min-height: 30px;
padding: 2px 8px;
margin-right: 1px;
border: ${main ? '2' : '1'}px solid transparent;
cursor: pointer;
text-align: center;
overflow: hidden;
outline: 0;
transition: all 0.5s;
&:hover,
&:focus {
background-color: ${main ? theme.base02 : theme.base00};
text-shadow: ${theme.base01} 0 1px;
}
}
> [data-selected] {
${main ?
`border-bottom: 2px solid ${theme.base0D};` :
`
background-color: ${theme.base00};
border: 1px solid ${theme.base02};
border-bottom: 1px solid ${theme.base00};
box-shadow: 0 1px ${theme.base00};
`
}
color: ${theme.base07};
}
}
> div:nth-child(2) {
display: block;
z-index: 10;
button {
display: block;
background: ${theme.base00};
width: 100%;
&:hover,
&:focus {
background-color: ${main ? theme.base02 : theme.base00};
text-shadow: ${theme.base01} 0 1px;
}
}
}
`;

View File

@ -0,0 +1,2 @@
export { style as default } from './default';
export { style as material } from './material';

View File

@ -0,0 +1,61 @@
import { css } from 'styled-components';
import { ripple } from '../../utils/animations';
export const style = ({ theme, main }) => css`
display: flex;
flex: 0 0 1;
padding-left: 1px;
background-color: ${theme.base01};
width: 100%;
overflow: hidden;
${!main && `
border-top: 1px solid ${theme.base01};
border-bottom: 1px solid ${theme.base02};
`}
> div {
display: flex;
align-items: flex-end;
flex-wrap: nowrap;
button {
background-color: ${theme.base01};
color: ${theme.base07};
min-height: 30px;
padding: 0 2em;
${main && 'text-transform: uppercase;'}
cursor: pointer;
border: none;
border-bottom: 2px solid transparent;
text-align: center;
overflow: hidden;
outline: 0;
transition: all 0.5s;
&:hover,
&:focus {
border-bottom: 2px solid ${theme.base03};
color: ${theme.base04};
}
&.collapsed{
display: none;
}
${ripple(theme)}
}
> [data-selected] {
border-bottom: 2px solid ${theme.base0D};
}
}
> div:nth-child(2) {
display: block;
z-index: 10;
button {
display: block;
background: ${theme.base00};
width: 100%;
}
}
`;

View File

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

View File

@ -0,0 +1,160 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import styled from 'styled-components';
import { withKnobs, text, number, boolean, select } from '@storybook/addon-knobs';
import PlayIcon from 'react-icons/lib/md/play-arrow';
import RecordIcon from 'react-icons/lib/md/fiber-manual-record';
import LeftIcon from 'react-icons/lib/md/keyboard-arrow-left';
import RightIcon from 'react-icons/lib/md/keyboard-arrow-right';
import { Toolbar, Divider, Spacer, Button, Select, Slider, SegmentedControl, Tabs } from '../../';
import { options } from '../../Select/stories/options';
import { simple10Tabs } from '../../Tabs/stories/data';
export const Container = styled.div`
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
`;
export const SliderContainer = styled.div`
width: 90%;
height: 80px;
`;
storiesOf('Toolbar', module)
.addDecorator(withKnobs)
.addWithInfo(
'default',
'',
() => (
<Container>
<Toolbar borderPosition={select('borderPosition', ['top', 'bottom'])}>
<Button
title={text('Title', 'Hello Tooltip')}
tooltipPosition={
select('tooltipPosition', ['top', 'bottom', 'left', 'right',
'bottom-left', 'bottom-right', 'top-left', 'top-right'])
}
disabled={boolean('Disabled', false)}
onClick={action('button clicked')}
>
{text('Label', 'Hello Button')}
</Button>
<Divider />
<Button
title={text('Title', 'Hello Tooltip')}
tooltipPosition={
select('tooltipPosition', ['top', 'bottom', 'left', 'right',
'bottom-left', 'bottom-right', 'top-left', 'top-right'])
}
disabled={boolean('Disabled', false)}
onClick={action('button clicked')}
>
<RecordIcon />
</Button>
<Divider />
<Spacer />
<Select options={options} />
</Toolbar>
</Container>
)
)
.addWithInfo(
'tabs',
'',
() => (
<Container>
<Toolbar>
<Button
title={text('Title', 'Hello Tooltip')}
tooltipPosition={
select('tooltipPosition', ['top', 'bottom', 'left', 'right',
'bottom-left', 'bottom-right', 'top-left', 'top-right'])
}
disabled={boolean('Disabled', false)}
onClick={action('button clicked')}
>
{text('Label', 'Hello Button')}
</Button>
<Tabs
tabs={simple10Tabs}
selected={text('selected', '2')}
main={boolean('main', true)}
onClick={action('tab selected')}
collapsible={boolean('collapsible', true)}
position={select('position', ['left', 'right', 'center'], 'center')}
/>
<Button
title={text('Title', 'Hello Tooltip')}
tooltipPosition={
select('tooltipPosition', ['top', 'bottom', 'left', 'right',
'bottom-left', 'bottom-right', 'top-left', 'top-right'])
}
disabled={boolean('Disabled', false)}
onClick={action('button clicked')}
>
{text('Label', 'Hello Button')}
</Button>
</Toolbar>
</Container>
)
)
.addWithInfo(
'with slider',
'',
() => (
<Container>
<SliderContainer>
<Toolbar noBorder fullHeight compact>
<Button
title={text('play title', 'Play')}
tooltipPosition={
select('tooltipPosition', ['top', 'bottom', 'left', 'right',
'bottom-left', 'bottom-right', 'top-left', 'top-right'])
}
onClick={action('button clicked')}
>
<PlayIcon />
</Button>
<Slider
value={number('value', 80)}
min={number('min', 0)}
max={number('max', 100)}
label={text('label', 'Slider label')}
withValue={boolean('withValue', false)}
onChange={action('slider changed')}
/>
<Button
title="Previous state"
tooltipPosition={
select('tooltipPosition', ['top', 'bottom', 'left', 'right',
'bottom-left', 'bottom-right', 'top-left', 'top-right'])
}
disabled
onClick={action('previous state clicked')}
>
<LeftIcon />
</Button>
<Button
title="Next state"
tooltipPosition={
select('tooltipPosition', ['top', 'bottom', 'left', 'right',
'bottom-left', 'bottom-right', 'top-left', 'top-right'])
}
onClick={action('next state clicked')}
>
<RightIcon />
</Button>
<SegmentedControl
values={['live', '1x']}
selected={select('selected', ['live', '1x'], 'live')}
onClick={action('button selected')}
/>
</Toolbar>
</SliderContainer>
</Container>
)
);

View File

@ -0,0 +1,12 @@
import styled from 'styled-components';
const Divider = styled.div`
background-color: ${props => props.theme.base02};
box-shadow: 1px 1px 2px ${props => props.theme.base00};
height: ${props => props.theme.inputHeight || '30'}px;
width: 1px;
margin: auto 3px !important;
flex-shrink: 0;
`;
export default Divider;

View File

@ -0,0 +1,7 @@
import styled from 'styled-components';
const Spacer = styled.div`
flex-grow: 1;
`;
export default Spacer;

View File

@ -0,0 +1,53 @@
import styled from 'styled-components';
const Toolbar = styled.div`
display: flex;
flex-shrink: 0;
box-sizing: border-box;
width: 100%;
font-family: ${props => props.theme.fontFamily || 'monospace'};
font-size: 12px;
line-height: 16px;
${props => props.fullHeight && 'height: 100%;'}
padding: ${props => (props.compact ? '0' : '5px')} 5px;
background-color: ${props => props.theme.base01};
text-align: center;
position: relative;
${({ borderPosition, theme }) =>
borderPosition && `border-${borderPosition}: 1px solid ${theme.base02};`
}
& > div {
margin: auto ${props => (props.noBorder ? '0' : '1px;')};
}
& button {
border-radius: 0;
${props => props.noBorder && 'border-color: transparent;'}
white-space: nowrap;
box-shadow: none !important;
}
& > .Select {
position: static;
text-align: left;
margin: auto 1px;
flex-grow: 1;
.Select-control {
cursor: pointer;
border-radius: 0 !important;
text-align: center;
background-color: ${props => props.theme.base01};
}
.Select-menu-outer {
margin-top: 5px;
}
}
& > .Select.is-focused > .Select-control {
text-align: left;
}
`;
export default Toolbar;

View File

@ -0,0 +1,20 @@
export default {
scheme: 'atom one dark',
author: 'Lalit Magant (http://github.com/tilal6991)',
base00: '#282c34',
base01: '#353b45',
base02: '#3e4451',
base03: '#545862',
base04: '#565c64',
base05: '#abb2bf',
base06: '#b6bdca',
base07: '#c8ccd4',
base08: '#e06c75',
base09: '#d19a66',
base0A: '#e5c07b',
base0B: '#98c379',
base0C: '#56b6c2',
base0D: '#61afef',
base0E: '#c678dd',
base0F: '#be5046'
};

View File

@ -0,0 +1,22 @@
// Based on unikitty theme and on
// https://github.com/alexkuz/redux-devtools-inspector/blob/master/src/themes/inspector.js
export default {
scheme: 'default',
base00: '#ffffff',
base01: '#f3f3f3',
base02: '#e8e8e8',
base03: '#b8b8b8',
base04: '#585858',
base05: '#383838',
base06: '#282828',
base07: '#181818',
base08: '#d80000',
base09: '#d65407',
base0A: '#dc8a0e',
base0B: '#236e25',
base0C: '#86c1b9',
base0D: '#1155cc',
base0E: '#aa17e6',
base0F: '#a16946'
};

View File

@ -0,0 +1,21 @@
// Based on Dracula Theme (http://github.com/dracula)
export default {
scheme: 'dracula',
author: 'Mike Barkmin (http://github.com/mikebarkmin)',
base00: '#282936',
base01: '#3a3c4e',
base02: '#4d4f68',
base03: '#626483',
base04: '#62d6e8',
base05: '#e9e9f4',
base06: '#f1f2f8',
base07: '#f7f7fb',
base08: '#ea51b2',
base09: '#b45bcf',
base0A: '#00f769',
base0B: '#ebff87',
base0C: '#a1efe4',
base0D: '#62d6e8',
base0E: '#b45bcf',
base0F: '#00f769'
};

View File

@ -0,0 +1,21 @@
// Modified based on default theme
export default {
scheme: 'github',
author: 'defman21',
base00: '#181818',
base01: '#282828',
base02: '#333333',
base03: '#969896',
base04: '#c8c8fa',
base05: '#e8e8e8',
base06: '#f5f5f5',
base07: '#ffffff',
base08: '#a71d5d',
base09: '#ed6a43',
base0A: '#f7ca88',
base0B: '#a1b56c',
base0C: '#0086b3',
base0D: '#183691',
base0E: '#795da3',
base0F: '#ed6a43'
};

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