mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-02-07 15:10:45 +03:00
Add devui package (#444)
This commit is contained in:
parent
dc26d4c180
commit
8449c6a9fd
3
packages/devui/.babelrc
Executable file
3
packages/devui/.babelrc
Executable file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["es2015", "stage-0", "react"]
|
||||
}
|
20
packages/devui/.eslintrc
Executable file
20
packages/devui/.eslintrc
Executable 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"
|
||||
}
|
11
packages/devui/.scripts/get_gh_pages_url.js
Executable file
11
packages/devui/.scripts/get_gh_pages_url.js
Executable 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);
|
33
packages/devui/.scripts/mocha_runner.js
Executable file
33
packages/devui/.scripts/mocha_runner.js
Executable 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');
|
16
packages/devui/.scripts/prepublish.sh
Executable file
16
packages/devui/.scripts/prepublish.sh
Executable 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
|
47
packages/devui/.scripts/publish_storybook.sh
Executable file
47
packages/devui/.scripts/publish_storybook.sh
Executable 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`"
|
1
packages/devui/.scripts/user/prepublish.sh
Executable file
1
packages/devui/.scripts/user/prepublish.sh
Executable file
|
@ -0,0 +1 @@
|
|||
# Use this file to your own code to run at NPM `prepublish` event.
|
1
packages/devui/.scripts/user/pretest.js
Executable file
1
packages/devui/.scripts/user/pretest.js
Executable file
|
@ -0,0 +1 @@
|
|||
// Use this file to setup any test utilities.
|
4
packages/devui/.storybook/addons.js
Executable file
4
packages/devui/.storybook/addons.js
Executable file
|
@ -0,0 +1,4 @@
|
|||
import '@storybook/addon-knobs/register';
|
||||
import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-options/register';
|
||||
import './themeAddon/register';
|
28
packages/devui/.storybook/config.js
Executable file
28
packages/devui/.storybook/config.js
Executable 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);
|
26
packages/devui/.storybook/preview-head.html
Normal file
26
packages/devui/.storybook/preview-head.html
Normal 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>
|
57
packages/devui/.storybook/themeAddon/Panel.js
Normal file
57
packages/devui/.storybook/themeAddon/Panel.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
9
packages/devui/.storybook/themeAddon/constant.js
Normal file
9
packages/devui/.storybook/themeAddon/constant.js
Normal 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
|
||||
};
|
12
packages/devui/.storybook/themeAddon/register.js
Normal file
12
packages/devui/.storybook/themeAddon/register.js
Normal 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} />
|
||||
});
|
||||
});
|
42
packages/devui/.storybook/themeAddon/theme.js
Normal file
42
packages/devui/.storybook/themeAddon/theme.js
Normal 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>;
|
4
packages/devui/.storybook/user/modify_webpack_config.js
Executable file
4
packages/devui/.storybook/user/modify_webpack_config.js
Executable 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.
|
||||
};
|
26
packages/devui/.storybook/webpack.config.js
Executable file
26
packages/devui/.storybook/webpack.config.js
Executable 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;
|
5
packages/devui/.stylelintrc
Normal file
5
packages/devui/.stylelintrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"processors": ["stylelint-processor-styled-components"],
|
||||
"extends": "stylelint-config-standard",
|
||||
"syntax": "scss"
|
||||
}
|
3
packages/devui/README.md
Executable file
3
packages/devui/README.md
Executable file
|
@ -0,0 +1,3 @@
|
|||
# DevUI
|
||||
|
||||
WIP
|
43
packages/devui/fonts/index.css
Normal file
43
packages/devui/fonts/index.css
Normal 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 */
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
97
packages/devui/package.json
Executable file
97
packages/devui/package.json
Executable 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"
|
||||
}
|
73
packages/devui/src/Button/Button.js
Normal file
73
packages/devui/src/Button/Button.js
Normal 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'
|
||||
};
|
1
packages/devui/src/Button/index.js
Normal file
1
packages/devui/src/Button/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './Button';
|
61
packages/devui/src/Button/stories/index.js
Executable file
61
packages/devui/src/Button/stories/index.js
Executable 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>
|
||||
)
|
||||
);
|
221
packages/devui/src/Button/styles/common.js
Normal file
221
packages/devui/src/Button/styles/common.js
Normal 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;
|
||||
}
|
||||
`;
|
42
packages/devui/src/Button/styles/default.js
Normal file
42
packages/devui/src/Button/styles/default.js
Normal 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};
|
||||
}
|
||||
`;
|
2
packages/devui/src/Button/styles/index.js
Normal file
2
packages/devui/src/Button/styles/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { style as default } from './default';
|
||||
export { style as material } from './material';
|
42
packages/devui/src/Button/styles/material.js
Normal file
42
packages/devui/src/Button/styles/material.js
Normal 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)}
|
||||
`;
|
32
packages/devui/src/Container/index.js
Normal file
32
packages/devui/src/Container/index.js
Normal 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;
|
38
packages/devui/src/Container/styles/index.js
Normal file
38
packages/devui/src/Container/styles/index.js
Normal 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;
|
||||
`;
|
105
packages/devui/src/ContextMenu/ContextMenu.js
Normal file
105
packages/devui/src/ContextMenu/ContextMenu.js
Normal 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
|
||||
};
|
1
packages/devui/src/ContextMenu/index.js
Normal file
1
packages/devui/src/ContextMenu/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default from './ContextMenu';
|
11
packages/devui/src/ContextMenu/stories/data.js
Normal file
11
packages/devui/src/ContextMenu/stories/data.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export const items = [
|
||||
{
|
||||
name: 'Menu Item 1'
|
||||
},
|
||||
{
|
||||
name: 'Menu Item 2'
|
||||
},
|
||||
{
|
||||
name: 'Menu Item 3'
|
||||
}
|
||||
];
|
33
packages/devui/src/ContextMenu/stories/index.js
Normal file
33
packages/devui/src/ContextMenu/stories/index.js
Normal 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>
|
||||
)
|
||||
);
|
42
packages/devui/src/ContextMenu/styles/index.js
Normal file
42
packages/devui/src/ContextMenu/styles/index.js
Normal 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;
|
||||
}
|
||||
}
|
||||
`;
|
115
packages/devui/src/Dialog/Dialog.js
Normal file
115
packages/devui/src/Dialog/Dialog.js
Normal 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
|
||||
};
|
1
packages/devui/src/Dialog/index.js
Normal file
1
packages/devui/src/Dialog/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './Dialog';
|
46
packages/devui/src/Dialog/stories/index.js
Executable file
46
packages/devui/src/Dialog/stories/index.js
Executable 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')}
|
||||
/>
|
||||
)
|
||||
);
|
111
packages/devui/src/Dialog/styles/default.js
Normal file
111
packages/devui/src/Dialog/styles/default.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
2
packages/devui/src/Dialog/styles/index.js
Normal file
2
packages/devui/src/Dialog/styles/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { style as default } from './default';
|
||||
export { style as material } from './material';
|
103
packages/devui/src/Dialog/styles/material.js
Normal file
103
packages/devui/src/Dialog/styles/material.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
89
packages/devui/src/Editor/Editor.js
Normal file
89
packages/devui/src/Editor/Editor.js
Normal 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
|
||||
};
|
1
packages/devui/src/Editor/index.js
Normal file
1
packages/devui/src/Editor/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './Editor';
|
42
packages/devui/src/Editor/stories/WithTabs.js
Normal file
42
packages/devui/src/Editor/stories/WithTabs.js
Normal 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')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
41
packages/devui/src/Editor/stories/index.js
Executable file
41
packages/devui/src/Editor/stories/index.js
Executable 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)}
|
||||
/>
|
||||
)
|
||||
);
|
125
packages/devui/src/Editor/styles/index.js
Normal file
125
packages/devui/src/Editor/styles/index.js
Normal 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};
|
||||
}
|
||||
}
|
||||
`;
|
37
packages/devui/src/Form/Form.js
Normal file
37
packages/devui/src/Form/Form.js
Normal 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]))
|
||||
};
|
1
packages/devui/src/Form/index.js
Normal file
1
packages/devui/src/Form/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './Form';
|
23
packages/devui/src/Form/stories/index.js
Executable file
23
packages/devui/src/Form/stories/index.js
Executable 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')}
|
||||
/>
|
||||
)
|
||||
);
|
101
packages/devui/src/Form/stories/schema.js
Normal file
101
packages/devui/src/Form/stories/schema.js
Normal 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
|
||||
}
|
||||
};
|
368
packages/devui/src/Form/styles/index.js
Normal file
368
packages/devui/src/Form/styles/index.js
Normal 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;
|
||||
}
|
||||
`;
|
29
packages/devui/src/Form/widgets.js
Normal file
29
packages/devui/src/Form/widgets.js
Normal 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 };
|
49
packages/devui/src/Notification/Notification.js
Normal file
49
packages/devui/src/Notification/Notification.js
Normal 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'
|
||||
};
|
1
packages/devui/src/Notification/index.js
Normal file
1
packages/devui/src/Notification/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './Notification';
|
33
packages/devui/src/Notification/stories/index.js
Normal file
33
packages/devui/src/Notification/stories/index.js
Normal 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>
|
||||
)
|
||||
);
|
59
packages/devui/src/Notification/styles/index.js
Normal file
59
packages/devui/src/Notification/styles/index.js
Normal 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};
|
||||
}
|
||||
`;
|
48
packages/devui/src/SegmentedControl/SegmentedControl.js
Normal file
48
packages/devui/src/SegmentedControl/SegmentedControl.js
Normal 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
|
||||
};
|
1
packages/devui/src/SegmentedControl/index.js
Normal file
1
packages/devui/src/SegmentedControl/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './SegmentedControl';
|
31
packages/devui/src/SegmentedControl/stories/index.js
Normal file
31
packages/devui/src/SegmentedControl/stories/index.js
Normal 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>
|
||||
)
|
||||
);
|
47
packages/devui/src/SegmentedControl/styles/index.js
Normal file
47
packages/devui/src/SegmentedControl/styles/index.js
Normal 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;
|
||||
}
|
||||
}
|
||||
`;
|
29
packages/devui/src/Select/Select.js
Normal file
29
packages/devui/src/Select/Select.js
Normal 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 };
|
1
packages/devui/src/Select/index.js
Normal file
1
packages/devui/src/Select/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default from './Select';
|
43
packages/devui/src/Select/stories/index.js
Executable file
43
packages/devui/src/Select/stories/index.js
Executable 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>
|
||||
)
|
||||
);
|
5
packages/devui/src/Select/stories/options.js
Normal file
5
packages/devui/src/Select/stories/options.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export const options = [
|
||||
{ value: 'one', label: 'One' },
|
||||
{ value: 'two', label: 'Two' },
|
||||
{ value: 'hundred', label: 'One hundred' }
|
||||
];
|
363
packages/devui/src/Select/styles/index.js
Normal file
363
packages/devui/src/Select/styles/index.js
Normal 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;
|
||||
}
|
||||
}
|
||||
`;
|
62
packages/devui/src/Slider/Slider.js
Normal file
62
packages/devui/src/Slider/Slider.js
Normal 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 };
|
1
packages/devui/src/Slider/index.js
Normal file
1
packages/devui/src/Slider/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default from './Slider';
|
35
packages/devui/src/Slider/stories/index.js
Executable file
35
packages/devui/src/Slider/stories/index.js
Executable 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>
|
||||
)
|
||||
);
|
15
packages/devui/src/Slider/styles/common.js
Normal file
15
packages/devui/src/Slider/styles/common.js
Normal 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;
|
||||
}
|
||||
`;
|
82
packages/devui/src/Slider/styles/default.js
Normal file
82
packages/devui/src/Slider/styles/default.js
Normal 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;
|
||||
}
|
||||
`;
|
2
packages/devui/src/Slider/styles/index.js
Normal file
2
packages/devui/src/Slider/styles/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { style as default } from './default';
|
||||
export { style as material } from './material';
|
68
packages/devui/src/Slider/styles/material.js
Normal file
68
packages/devui/src/Slider/styles/material.js
Normal 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;
|
||||
}
|
||||
`;
|
93
packages/devui/src/Tabs/Tabs.js
Normal file
93
packages/devui/src/Tabs/Tabs.js
Normal 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' };
|
201
packages/devui/src/Tabs/TabsHeader.js
Normal file
201
packages/devui/src/Tabs/TabsHeader.js
Normal 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
|
||||
};
|
||||
|
1
packages/devui/src/Tabs/index.js
Normal file
1
packages/devui/src/Tabs/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default from './Tabs';
|
41
packages/devui/src/Tabs/stories/data.js
Normal file
41
packages/devui/src/Tabs/stories/data.js
Normal 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}` });
|
46
packages/devui/src/Tabs/stories/index.js
Executable file
46
packages/devui/src/Tabs/stories/index.js
Executable 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')}
|
||||
/>
|
||||
)
|
||||
);
|
27
packages/devui/src/Tabs/styles/common.js
Normal file
27
packages/devui/src/Tabs/styles/common.js
Normal 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;
|
||||
}
|
||||
`;
|
70
packages/devui/src/Tabs/styles/default.js
Normal file
70
packages/devui/src/Tabs/styles/default.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
2
packages/devui/src/Tabs/styles/index.js
Normal file
2
packages/devui/src/Tabs/styles/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { style as default } from './default';
|
||||
export { style as material } from './material';
|
61
packages/devui/src/Tabs/styles/material.js
Normal file
61
packages/devui/src/Tabs/styles/material.js
Normal 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%;
|
||||
}
|
||||
}
|
||||
`;
|
3
packages/devui/src/Toolbar/index.js
Normal file
3
packages/devui/src/Toolbar/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export Toolbar from './styles/Toolbar';
|
||||
export Divider from './styles/Divider';
|
||||
export Spacer from './styles/Spacer';
|
160
packages/devui/src/Toolbar/stories/index.js
Executable file
160
packages/devui/src/Toolbar/stories/index.js
Executable 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>
|
||||
)
|
||||
);
|
12
packages/devui/src/Toolbar/styles/Divider.js
Normal file
12
packages/devui/src/Toolbar/styles/Divider.js
Normal 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;
|
7
packages/devui/src/Toolbar/styles/Spacer.js
Normal file
7
packages/devui/src/Toolbar/styles/Spacer.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import styled from 'styled-components';
|
||||
|
||||
const Spacer = styled.div`
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
export default Spacer;
|
53
packages/devui/src/Toolbar/styles/Toolbar.js
Normal file
53
packages/devui/src/Toolbar/styles/Toolbar.js
Normal 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;
|
20
packages/devui/src/colorSchemes/atom-one-dark.js
Normal file
20
packages/devui/src/colorSchemes/atom-one-dark.js
Normal 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'
|
||||
};
|
22
packages/devui/src/colorSchemes/default.js
Normal file
22
packages/devui/src/colorSchemes/default.js
Normal 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'
|
||||
};
|
21
packages/devui/src/colorSchemes/dracula.js
Normal file
21
packages/devui/src/colorSchemes/dracula.js
Normal 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'
|
||||
};
|
21
packages/devui/src/colorSchemes/github.js
Normal file
21
packages/devui/src/colorSchemes/github.js
Normal 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
Loading…
Reference in New Issue
Block a user