diff --git a/.babelrc b/.babelrc index c37be69d0..206f1317e 100644 --- a/.babelrc +++ b/.babelrc @@ -1,7 +1,7 @@ { - "presets": ["react", "es2015"], + "presets": ["@babel/react", "@babel/env"], "plugins": [ - "transform-object-rest-spread", + "@babel/plugin-proposal-object-rest-spread", [ "module-resolver", { diff --git a/.eslintrc b/.eslintrc index 913b8e07a..9c68ca719 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,6 +17,12 @@ "jsx": true } }, + "settings": { + "react": { + "pragma": "React", + "version": "detect" + } + }, "env": { "browser": true, "es6": true, @@ -54,7 +60,7 @@ "guard-for-in": ["off"], "import/named": ["off"], "import/no-duplicates": ["error"], - "import/no-named-as-default": ["error"], + "import/no-named-as-default": ["off"], "new-cap": ["error"], "no-alert": [1], "no-caller": ["error"], @@ -115,7 +121,7 @@ "yoda": ["error"], "spaced-comment": ["error", "always", { "block": { - exceptions: ["*"] + "exceptions": ["*"] } }], "no-unused-vars": ["error", { diff --git a/.gitignore b/.gitignore index 716f439b9..7dbb9268e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ npm-debug.log* *.sublime* .* +!.storybook !.gitignore !.gitattributes !.npmignore @@ -19,3 +20,5 @@ accessTokens.js yarn.lock yarn-error.log package-lock.json + +storybook-static diff --git a/.storybook/config.js b/.storybook/config.js new file mode 100644 index 000000000..9a88e6eb8 --- /dev/null +++ b/.storybook/config.js @@ -0,0 +1,14 @@ +import {configure, getStorybook, setAddon} from '@storybook/react'; + +function loadStories() { + require('../src/__stories__/index.js'); + // You can require as many stories as you need. +} +import createPercyAddon from '@percy-io/percy-storybook'; +const {percyAddon, serializeStories} = createPercyAddon(); +setAddon(percyAddon); + +configure(loadStories, module); + +// NOTE: if you're using the Storybook options addon, call serializeStories *BEFORE* the setOptions call +serializeStories(getStorybook); diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js new file mode 100644 index 000000000..e183d0f61 --- /dev/null +++ b/.storybook/webpack.config.js @@ -0,0 +1,13 @@ +const path = require('path'); + +module.exports = { + module: { + rules: [ + { + test: /\.(css|scss)?$/, + loaders: ['style-loader', 'css-loader', 'sass-loader'], + include: path.resolve(__dirname, '../'), + }, + ], + }, +}; diff --git a/README.md b/README.md index 45e7fec29..2eb5647de 100644 --- a/README.md +++ b/README.md @@ -155,14 +155,14 @@ Once you have your tokens, you can provide it as a config prop to the `15", @@ -117,12 +121,13 @@ "make:translation-keys": "node scripts/findTranslationKeys.js", "prepublishOnly": "npm run make:lib", "start": "webpack-dev-server --hot", + "storybook": "start-storybook -p 9001 -c .storybook", "test": "npm run test:lint && npm run test:pretty && npm run test:js", - "test:js": "jest --setupTestFrameworkScriptFile=raf/polyfill", + "test:js": "jest --setupTestFrameworkScriptFile=raf/polyfill --maxWorkers=2", "test:lint": "eslint \"src/**/*.js\" && echo -e '\\033[0;32m'PASS'\\033[0m'", "test:pretty": "prettier -l \"src/**/*.js\" && echo -e '\\033[0;32m'PASS'\\033[0m'", - "test:percy": "react-percy", - "test:percy-local": "react-percy --debug", + "test:percy": "build-storybook && percy-storybook --widths=500", + "test:percy-local": "build-storybook", "watch": "babel src --watch --out-dir lib --source-maps | node-sass -w src/styles/main.scss lib/react-chart-editor.css", "watch-test": "jest --watch" } diff --git a/scripts/findTranslationKeys.js b/scripts/findTranslationKeys.js index 498bfb6bb..c1079fcc0 100644 --- a/scripts/findTranslationKeys.js +++ b/scripts/findTranslationKeys.js @@ -1,5 +1,5 @@ -const transform = require('babel-core').transform; -const traverse = require('babel-traverse').default; +const transformSync = require('@babel/core').transformSync; +const traverse = require('@babel/traverse').default; const fs = require('fs'); const glob = require('glob'); const path = require('path'); @@ -10,10 +10,8 @@ const path = require('path'); const pathToSrc = process.argv[2] || path.join(__dirname, '../src'); const srcGlob = path.join(pathToSrc, '**/*.js'); -const pathToTranslationKeys = process.argv[3] || path.join( - __dirname, - './translationKeys/translation-keys.txt' -); +const pathToTranslationKeys = + process.argv[3] || path.join(__dirname, './translationKeys/translation-keys.txt'); findLocaleStrings(); @@ -30,16 +28,28 @@ function findLocaleStrings() { files.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const filePartialPath = file.substr(pathToSrc.length); - const ast = transform(code, { - presets: ['react', 'es2015', 'stage-2'], + const ast = transformSync(code, { + presets: ['@babel/preset-react', '@babel/preset-env'], + plugins: [ + '@babel/plugin-proposal-object-rest-spread', + [ + 'module-resolver', + { + root: ['./'], + alias: { + components: './src/components', + lib: './src/lib', + styles: './src/styles', + }, + }, + ], + ], + ast: true, }).ast; traverse(ast, { enter(path) { - if ( - path.node.type === 'CallExpression' && - path.node.callee.name === '_' - ) { + if (path.node.type === 'CallExpression' && path.node.callee.name === '_') { const strNode = path.node.arguments[0]; let strNodeValue = strNode.value; @@ -47,9 +57,7 @@ function findLocaleStrings() { logError(file, path.node, 'Localize takes 1 args'); } - if ( - ['StringLiteral', 'BinaryExpression'].indexOf(strNode.type) < 0 - ) { + if (['StringLiteral', 'BinaryExpression'].indexOf(strNode.type) < 0) { logError( file, path.node, @@ -71,8 +79,7 @@ function findLocaleStrings() { } if (!dict[strNodeValue]) { - dict[strNodeValue] = - filePartialPath + ':' + strNode.loc.start.line; + dict[strNodeValue] = filePartialPath + ':' + strNode.loc.start.line; maxLen = Math.max(maxLen, strNodeValue.length); hasTranslation = true; } @@ -96,9 +103,7 @@ function findLocaleStrings() { } function logError(file, node, msg) { - throw new Error( - file + ' [line ' + node.loc.start.line + '] ' + msg + '\n ' - ); + throw new Error(file + ' [line ' + node.loc.start.line + '] ' + msg + '\n '); } function spaces(len) { diff --git a/scripts/postcss.js b/scripts/postcss.js index a3746eee8..ecd738132 100644 --- a/scripts/postcss.js +++ b/scripts/postcss.js @@ -25,7 +25,7 @@ const internetExplorerPostCSS = () => { */ const ie11_plugins = [ combineSelectors, - customProperties, + customProperties({preserve: false}), removeRoot, autoprefixer({browsers: ['ie 11'], grid: true}), cssnano, diff --git a/src/EditorControls.js b/src/EditorControls.js index 9a5458886..0686f2c70 100644 --- a/src/EditorControls.js +++ b/src/EditorControls.js @@ -38,10 +38,9 @@ class EditorControls extends Component { config: gd._context, srcConverters: this.props.srcConverters, data: gd.data, - dataSources: this.props.dataSources, + dataSourceComponents: this.props.dataSourceComponents, dataSourceOptions: this.props.dataSourceOptions, - dataSourceValueRenderer: this.props.dataSourceValueRenderer, - dataSourceOptionRenderer: this.props.dataSourceOptionRenderer, + dataSources: this.props.dataSources, dictionaries: this.props.dictionaries || {}, localize: this.localize, frames: gd._transitionData ? gd._transitionData._frames : [], @@ -336,10 +335,9 @@ EditorControls.propTypes = { toSrc: PropTypes.func.isRequired, fromSrc: PropTypes.func.isRequired, }), - dataSourceOptionRenderer: PropTypes.func, + dataSourceComponents: PropTypes.object, dataSourceOptions: PropTypes.array, dataSources: PropTypes.object, - dataSourceValueRenderer: PropTypes.func, dictionaries: PropTypes.object, graphDiv: PropTypes.object, locale: PropTypes.string, @@ -373,10 +371,9 @@ EditorControls.childContextTypes = { fromSrc: PropTypes.func.isRequired, }), data: PropTypes.array, - dataSourceOptionRenderer: PropTypes.func, + dataSourceComponents: PropTypes.object, dataSourceOptions: PropTypes.array, dataSources: PropTypes.object, - dataSourceValueRenderer: PropTypes.func, dictionaries: PropTypes.object, frames: PropTypes.array, fullData: PropTypes.array, diff --git a/src/__percy__/panels.percy.js b/src/__stories__/index.js similarity index 90% rename from src/__percy__/panels.percy.js rename to src/__stories__/index.js index 6a78df1b4..ea9b1423d 100644 --- a/src/__percy__/panels.percy.js +++ b/src/__stories__/index.js @@ -7,7 +7,10 @@ import * as panels from '../default_panels/'; import '../../dev/styles.css'; import '../styles/main.scss'; -import './percy.css'; +import './stories.css'; + +import React from 'react'; +import {storiesOf} from '@storybook/react'; /** * To add more Percy tests - add a mock file to /dev/percy, add it to /dev/percy/index.js @@ -49,7 +52,7 @@ const panelFixture = (Panel, group, name, figure) => { ); }; -const snapshotWidth = 500; +let stories = storiesOf('Panels', module); Object.keys(mocks).forEach(m => { const selectedPanels = panelsToTest[m] ? panelsToTest[m] : Object.keys(panels); @@ -59,7 +62,7 @@ Object.keys(mocks).forEach(m => { const panelGroup = words[0]; const panelName = words.slice(1, -1).join(' '); - percySnapshot(`Panels: ${m}_${p}`, {widths: [snapshotWidth]}, () => + stories = stories.add(`${m}_${p}`, () => panelFixture(panels[p], panelGroup, panelName, mocks[m]) ); }); diff --git a/src/__percy__/percy.css b/src/__stories__/stories.css similarity index 100% rename from src/__percy__/percy.css rename to src/__stories__/stories.css diff --git a/src/components/PanelMenuWrapper.js b/src/components/PanelMenuWrapper.js index 31ea5d911..536e51b5e 100644 --- a/src/components/PanelMenuWrapper.js +++ b/src/components/PanelMenuWrapper.js @@ -87,14 +87,12 @@ class PanelsWithSidebar extends Component { return (
{menuOpts.map(this.renderSection)}
- {React.Children.map( - this.props.children, - (child, i) => - child === null || - this.state.group !== child.props.group || - this.state.panel !== child.props.name - ? null - : cloneElement(child, {key: i}) + {React.Children.map(this.props.children, (child, i) => + child === null || + this.state.group !== child.props.group || + this.state.panel !== child.props.name + ? null + : cloneElement(child, {key: i}) )}
); diff --git a/src/components/containers/ModalProvider.js b/src/components/containers/ModalProvider.js index 6cbb13958..4a520e397 100644 --- a/src/components/containers/ModalProvider.js +++ b/src/components/containers/ModalProvider.js @@ -1,4 +1,4 @@ -import React, {Fragment} from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; class ModalProvider extends React.Component { @@ -73,10 +73,10 @@ class ModalProvider extends React.Component { render() { const {component: Component, componentProps, isAnimatingOut} = this.state; return ( - + <> {this.props.children} {this.state.open ? : null} - + ); } } diff --git a/src/components/fields/ArrowSelector.js b/src/components/fields/ArrowSelector.js index cd7c1a5b6..b599a5dd6 100644 --- a/src/components/fields/ArrowSelector.js +++ b/src/components/fields/ArrowSelector.js @@ -4,7 +4,7 @@ import ARROW_PATHS from 'plotly.js/src/components/annotations/arrow_paths'; const ARROW_OPTIONS = ARROW_PATHS.map(({path}, index) => { const label = ( - + s.length > 1); const options = multipleSublots - ? axesOptions.map( - option => - option.value === 'allaxes' - ? option - : { - label: option.title, - value: option.value, - } + ? axesOptions.map(option => + option.value === 'allaxes' + ? option + : { + label: option.title, + value: option.value, + } ) : axesOptions; diff --git a/src/components/fields/DataSelector.js b/src/components/fields/DataSelector.js index b3dda05eb..a34f43111 100644 --- a/src/components/fields/DataSelector.js +++ b/src/components/fields/DataSelector.js @@ -117,11 +117,10 @@ export class UnconnectedDataSelector extends Component { onChange={this.updatePlot} multi={this.is2D} searchable={true} - optionRenderer={this.context.dataSourceOptionRenderer} - valueRenderer={this.context.dataSourceValueRenderer} clearable={true} placeholder={this.hasData ? 'Data inlined in figure' : 'Choose data...'} disabled={this.dataSourceOptions.length === 0} + components={this.props.dataSourceComponents} /> ); @@ -137,9 +136,8 @@ UnconnectedDataSelector.propTypes = { UnconnectedDataSelector.contextTypes = { dataSources: PropTypes.object, + dataSourceComponents: PropTypes.object, dataSourceOptions: PropTypes.array, - dataSourceValueRenderer: PropTypes.func, - dataSourceOptionRenderer: PropTypes.func, srcConverters: PropTypes.shape({ toSrc: PropTypes.func.isRequired, fromSrc: PropTypes.func.isRequired, diff --git a/src/components/fields/Dropdown.js b/src/components/fields/Dropdown.js index d5633da2f..1bbdda58f 100644 --- a/src/components/fields/Dropdown.js +++ b/src/components/fields/Dropdown.js @@ -19,10 +19,9 @@ export class UnconnectedDropdown extends Component { value={this.props.fullValue} onChange={this.props.updatePlot} clearable={this.props.clearable} - optionRenderer={this.props.optionRenderer} - valueRenderer={this.props.valueRenderer} placeholder={placeholder} disabled={this.props.disabled} + components={this.props.components} /> ); @@ -31,12 +30,11 @@ export class UnconnectedDropdown extends Component { UnconnectedDropdown.propTypes = { backgroundDark: PropTypes.bool, + components: PropTypes.object, clearable: PropTypes.bool, fullValue: PropTypes.any, - optionRenderer: PropTypes.func, options: PropTypes.array.isRequired, updatePlot: PropTypes.func, - valueRenderer: PropTypes.func, disabled: PropTypes.bool, ...Field.propTypes, }; diff --git a/src/components/fields/DropdownCustom.js b/src/components/fields/DropdownCustom.js index 49d44c994..ecc314cfb 100644 --- a/src/components/fields/DropdownCustom.js +++ b/src/components/fields/DropdownCustom.js @@ -59,8 +59,7 @@ export class UnconnectedDropdownCustom extends Component { value={value} onChange={this.setValue} clearable={this.props.clearable} - optionRenderer={this.props.optionRenderer} - valueRenderer={this.props.valueRenderer} + components={this.props.components} placeholder={this.props.placeholder} /> @@ -81,9 +80,12 @@ export class UnconnectedDropdownCustom extends Component { } UnconnectedDropdownCustom.propTypes = { + backgroundDark: PropTypes.bool, fullValue: PropTypes.any, updatePlot: PropTypes.func, clearable: PropTypes.bool, + components: PropTypes.object, + placeholder: PropTypes.any, defaultOpt: PropTypes.oneOfType([PropTypes.number, PropTypes.bool, PropTypes.string]), customOpt: PropTypes.oneOfType([PropTypes.number, PropTypes.bool, PropTypes.string]), label: PropTypes.string, diff --git a/src/components/fields/ErrorBars.js b/src/components/fields/ErrorBars.js index 9095851c2..23a47849f 100644 --- a/src/components/fields/ErrorBars.js +++ b/src/components/fields/ErrorBars.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {Component, Fragment} from 'react'; +import React, {Component} from 'react'; import {DataSelector, Radio, Numeric, MultiColorPicker} from '../index'; import RadioBlocks from '../widgets/RadioBlocks'; import Field from './Field'; @@ -88,7 +88,7 @@ class ErrorBars extends Component { const showCustomDataControl = this.props.fullValue.type === 'data'; const styleAttrs = ( - + <> - + ); if (mode === 'symmetric') { return ( - + <> ) : null} {styleAttrs} - + ); } if (mode === 'asymmetric') { return ( - + <> {showCustomDataControl ? ( - + <> - + ) : null} {styleAttrs} - + ); } @@ -157,10 +157,10 @@ class ErrorBars extends Component { render() { return ( - + <> {this.renderModeSelector()} {this.renderErrorBarControls()} - + ); } } diff --git a/src/components/fields/FilterOperation.js b/src/components/fields/FilterOperation.js index a154de697..771ffb9dc 100644 --- a/src/components/fields/FilterOperation.js +++ b/src/components/fields/FilterOperation.js @@ -63,7 +63,7 @@ class UnconnectedFilterOperation extends Component { } render() { - const {fullValue, updatePlot, optionRenderer, valueRenderer, backgroundDark, attr} = this.props; + const {fullValue, updatePlot, backgroundDark, attr} = this.props; const {localize: _} = this.context; const operators = [ @@ -99,8 +99,6 @@ class UnconnectedFilterOperation extends Component { value={findOperation(opValue, _)} onChange={this.setOperation} clearable={false} - optionRenderer={optionRenderer} - valueRenderer={valueRenderer} /> {this.state.operation === 'inset' || this.state.operation === 'exset' ? null : ( )} diff --git a/src/components/fields/FontSelector.js b/src/components/fields/FontSelector.js index 94237dc53..ebbce610c 100644 --- a/src/components/fields/FontSelector.js +++ b/src/components/fields/FontSelector.js @@ -2,28 +2,21 @@ import Dropdown from './Dropdown'; import React from 'react'; import PropTypes from 'prop-types'; -/* eslint-disable react/prop-types */ -const styledRenderer = ({value, label}) => {label}; -/* eslint-enable react/prop-types */ - -const FontSelector = (props, context) => { - return ( - - ); -}; +const FontSelector = (props, context) => ( + ({ + label: {label}, + value, + }))} + /> +); FontSelector.propTypes = { ...Dropdown.propTypes, }; -FontSelector.defaultProps = { - clearable: false, -}; +FontSelector.defaultProps = {clearable: false}; FontSelector.contextTypes = { fontOptions: PropTypes.array, diff --git a/src/components/fields/LineSelectors.js b/src/components/fields/LineSelectors.js index 62e6b53aa..1a684a7e7 100644 --- a/src/components/fields/LineSelectors.js +++ b/src/components/fields/LineSelectors.js @@ -5,18 +5,6 @@ import nestedProperty from 'plotly.js/src/lib/nested_property'; import {tooLight} from 'lib'; import {COLORS, MULTI_VALUED} from 'lib/constants'; -/* eslint-disable react/prop-types */ -const styledRenderer = ({label}) => { - return ( -
- - {label} - -
- ); -}; -/* eslint-enable react/prop-types */ - const strokeDashes = [ {value: 'solid', strokeDasharray: ''}, {value: 'dot', strokeDasharray: '3px, 3px'}, @@ -35,42 +23,43 @@ const strokeShapes = [ {d: 'M2,14V8H14V2', value: 'vhv'}, ]; -const strokeStyle = {fill: 'none', strokeWidth: '4px'}; - const computeOptions = (strokeData, stroke) => strokeData.map(({value, strokeDasharray, d = 'M0,8h100'}) => ({ label: ( - + + + + + ), value, })); -export const LineShapeSelector = props => { - return ; -}; +export const LineShapeSelector = props => ( + +); -export const LineDashSelector = props => { - return ( - - computeOptions(strokeDashes, lineColor).concat([ - { - label: '', - value: null, - }, - ]) - } - /> - ); -}; +export const LineDashSelector = props => ( + + computeOptions(strokeDashes, lineColor).concat([ + { + label: '', + value: null, + }, + ]) + } + /> +); class LineSelector extends Component { constructor(props, context) { @@ -93,13 +82,7 @@ class LineSelector extends Component { render() { return ( - + ); } } diff --git a/src/components/fields/LocationSelector.js b/src/components/fields/LocationSelector.js index c4fcbb8dd..20f2b7aad 100644 --- a/src/components/fields/LocationSelector.js +++ b/src/components/fields/LocationSelector.js @@ -1,4 +1,4 @@ -import React, {Fragment, Component} from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {connectToContainer} from 'lib'; import Field from './Field'; @@ -21,7 +21,7 @@ class UnconnectedLocation extends Component { const {localize: _} = this.context; return ( - + <> - + ); } } @@ -92,7 +92,7 @@ class UnconnectedLocationSelector extends Component { } = this.context; return type === 'scattergeo' ? ( - + <> {mode === 'latlon' ? ( - + <> - + ) : ( )} - + ) : type === 'choropleth' ? ( ) : ( - + <> - + ); } } diff --git a/src/components/fields/MarkerColor.js b/src/components/fields/MarkerColor.js index 4b0312efd..ea657e50f 100644 --- a/src/components/fields/MarkerColor.js +++ b/src/components/fields/MarkerColor.js @@ -1,6 +1,6 @@ import Field from './Field'; import PropTypes from 'prop-types'; -import React, {Component, Fragment} from 'react'; +import React, {Component} from 'react'; import {connectToContainer} from 'lib'; import RadioBlocks from '../widgets/RadioBlocks'; import MultiColorPicker from './MultiColorPicker'; @@ -157,7 +157,7 @@ class UnconnectedMarkerColor extends Component { ]; return ( - + <> @@ -174,11 +174,11 @@ class UnconnectedMarkerColor extends Component { {!type ? null : type === 'constant' - ? this.renderConstantControls() - : this.renderVariableControls()} + ? this.renderConstantControls() + : this.renderVariableControls()} {type === 'constant' ? null : ( - + <> - + )} - + ); } diff --git a/src/components/fields/TextPosition.js b/src/components/fields/TextPosition.js index 66507ff4e..84995ecdd 100644 --- a/src/components/fields/TextPosition.js +++ b/src/components/fields/TextPosition.js @@ -2,7 +2,7 @@ import Dropdown from './Dropdown'; import RadioBlocks from '../widgets/RadioBlocks'; import Field from './Field'; import PropTypes from 'prop-types'; -import React, {Component, Fragment} from 'react'; +import React, {Component} from 'react'; import {connectToContainer} from 'lib'; import Info from './Info'; import DataSelector from './DataSelector'; @@ -23,16 +23,16 @@ export class UnconnectedTextPosition extends Component { ]; const control = this.state.posType === 'simple' ? ( - + <> {_( 'This will position all text values on the plot according to the selected position.' )} - + ) : ( - + <>
{_( @@ -44,7 +44,7 @@ export class UnconnectedTextPosition extends Component {
{_('("Top", "Middle", "Bottom") + ("Left", "Center", "Right")')}
- + ); return ( diff --git a/src/components/fields/VisibilitySelect.js b/src/components/fields/VisibilitySelect.js index bcf159ea8..d36bb98da 100644 --- a/src/components/fields/VisibilitySelect.js +++ b/src/components/fields/VisibilitySelect.js @@ -1,4 +1,4 @@ -import React, {Fragment, Component} from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {connectToContainer} from 'lib'; import {MULTI_VALUED_PLACEHOLDER} from 'lib/constants'; @@ -35,7 +35,7 @@ export class UnconnectedVisibilitySelect extends Component { const {dropdown, clearable, options, showOn, attr, label} = this.props; return ( - + <> {dropdown ? ( + ); } } diff --git a/src/components/fields/derived.js b/src/components/fields/derived.js index 73bff7f37..6fe38850a 100644 --- a/src/components/fields/derived.js +++ b/src/components/fields/derived.js @@ -128,8 +128,8 @@ export const BinningDropdown = connectToContainer(UnconnectedDropdown, { plotProps.fullContainer.type === 'histogram2d' ? 'Z' : plotProps.fullContainer.orientation === 'v' - ? 'Y' - : 'X'; + ? 'Y' + : 'X'; plotProps.options = [ {label: _('Count ') + axis, value: 'count'}, {label: _('Sum ') + axis, value: 'sum'}, diff --git a/src/components/fields/index.js b/src/components/fields/index.js index 10a53211d..2a68d5227 100644 --- a/src/components/fields/index.js +++ b/src/components/fields/index.js @@ -55,7 +55,6 @@ import { PositioningNumeric, NumericFractionInverse, RangesliderVisible, - TraceOrientation, AxisOverlayDropdown, AxisSide, ShowInLegend, @@ -109,7 +108,6 @@ export { SymbolSelector, RangesliderVisible, TextEditor, - TraceOrientation, TraceSelector, AxesCreator, SubplotCreator, diff --git a/src/components/index.js b/src/components/index.js index 824c87f71..8377b4d30 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -45,7 +45,6 @@ import { GroupCreator, SymbolSelector, TextEditor, - TraceOrientation, TraceSelector, UpdateMenuButtons, Dropzone, @@ -167,7 +166,6 @@ export { SubplotAccordion, TraceAccordion, TraceMarkerSection, - TraceOrientation, TraceRequiredPanel, TraceSelector, TraceTypeSection, diff --git a/src/components/widgets/ColorPicker.js b/src/components/widgets/ColorPicker.js index c2ca80580..45efa1bb7 100644 --- a/src/components/widgets/ColorPicker.js +++ b/src/components/widgets/ColorPicker.js @@ -1,5 +1,5 @@ import Fields from 'react-color/lib/components/sketch/SketchFields'; -import React, {Component, Fragment} from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; import tinycolor from 'tinycolor2'; import {Hue, Saturation} from 'react-color/lib/components/common'; @@ -86,7 +86,7 @@ class ColorPicker extends Component { const swatchStyle = {backgroundColor: rgbString}; return ( - + <>
)} - + ); } } diff --git a/src/components/widgets/DateTimePicker.js b/src/components/widgets/DateTimePicker.js index eacb27b7a..72ce7ccd0 100644 --- a/src/components/widgets/DateTimePicker.js +++ b/src/components/widgets/DateTimePicker.js @@ -141,10 +141,10 @@ export default class DateTimePicker extends Component { return !isValidDateTime || time === '' || JSDate.toDateString() === 'Invalid Date' ? '' : localeTime[1] === 'PM' - ? isNoon - ? _('noon') - : 'PM' - : 'AM'; + ? isNoon + ? _('noon') + : 'PM' + : 'AM'; } adjustedTime(time) { diff --git a/src/components/widgets/Dropdown.js b/src/components/widgets/Dropdown.js index 0f9e85de8..f3ab27489 100644 --- a/src/components/widgets/Dropdown.js +++ b/src/components/widgets/Dropdown.js @@ -15,16 +15,13 @@ class Dropdown extends Component { if (!selection) { return onChange(null); - } else if (multi) { - return onChange(selection.map(s => s[valueKey])); } - return onChange(selection[valueKey]); + return multi ? onChange(selection.map(s => s[valueKey])) : onChange(selection[valueKey]); } render() { const { - backspaceToRemoveMessage, minWidth, placeholder, clearable, @@ -32,8 +29,6 @@ class Dropdown extends Component { options, searchable, multi, - optionRenderer, - valueRenderer, noResultsText, valueKey, disabled, @@ -48,35 +43,34 @@ class Dropdown extends Component { dropdownStyle.width = width; } - const opts = options.slice(); - for (let i = 0; i < opts.length; i++) { - if (typeof opts[i] === 'string') { - opts[i] = {label: opts[i], [valueKey]: opts[i]}; - } - } + const opts = options.map(opt => + typeof opt === 'string' ? {label: opt, [valueKey]: opt} : opt + ); const dropdownContainerClass = classnames('dropdown-container', { 'dropdown--dark': this.props.backgroundDark, - [this.props.className]: this.props.className, + [className]: className, }); return (
+
{this.state.content}
+
+ )} ); } diff --git a/src/components/widgets/TraceTypeSelector.js b/src/components/widgets/TraceTypeSelector.js index d249d19b2..394b1a9db 100644 --- a/src/components/widgets/TraceTypeSelector.js +++ b/src/components/widgets/TraceTypeSelector.js @@ -7,21 +7,20 @@ import {TRACES_WITH_GL} from 'lib/constants'; const renderActionItems = (actionItems, item) => actionItems - ? actionItems(item).map( - (action, i) => - !action.onClick ? null : ( - - {action.icon} - - ) + ? actionItems(item).map((action, i) => + !action.onClick ? null : ( + + {action.icon} + + ) ) : null; diff --git a/src/components/widgets/text_editors/RichText/DraftCommands.js b/src/components/widgets/text_editors/RichText/DraftCommands.js index 9cc997daf..888657805 100644 --- a/src/components/widgets/text_editors/RichText/DraftCommands.js +++ b/src/components/widgets/text_editors/RichText/DraftCommands.js @@ -145,9 +145,9 @@ export function insertSoftNewline(editorState) { */ export function toggleInlineStyle(editorState, inlineStyle) { /* - * TODO tech-debt. Link toggles should not go via toggleInlineStyle. - * https://github.com/plotly/streambed/issues/6354 - */ + * TODO tech-debt. Link toggles should not go via toggleInlineStyle. + * https://github.com/plotly/streambed/issues/6354 + */ if (inlineStyle === LINK) { return toggleLink(editorState); diff --git a/src/components/widgets/text_editors/RichText/LinkEditor.js b/src/components/widgets/text_editors/RichText/LinkEditor.js index 6b0b2cebd..0708374b6 100644 --- a/src/components/widgets/text_editors/RichText/LinkEditor.js +++ b/src/components/widgets/text_editors/RichText/LinkEditor.js @@ -59,9 +59,9 @@ class LinkEditor extends Component { onInputKeyDown(ev) { /* - * `KeyboardEvent.key` enjoys excellent cross-browser support. - * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key - */ + * `KeyboardEvent.key` enjoys excellent cross-browser support. + * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key + */ const {key} = ev; if (key === RETURN_KEY) { diff --git a/src/components/widgets/text_editors/RichText/configuration.js b/src/components/widgets/text_editors/RichText/configuration.js index 4f756305a..b303e1def 100644 --- a/src/components/widgets/text_editors/RichText/configuration.js +++ b/src/components/widgets/text_editors/RichText/configuration.js @@ -26,10 +26,10 @@ export const STYLE_MAP = { }, [SUBSCRIPT]: { /* - * Can't use text-align; IE renders `text-bottom` properly, but - * FF doesn't (same height as `bottom`). Chrome doesn't understand - * `text-align: bottom`. Use relative positioning instead. - */ + * Can't use text-align; IE renders `text-bottom` properly, but + * FF doesn't (same height as `bottom`). Chrome doesn't understand + * `text-align: bottom`. Use relative positioning instead. + */ lineHeight: 0, fontSize: '65%', position: 'relative', @@ -37,10 +37,10 @@ export const STYLE_MAP = { }, [SUPERSCRIPT]: { /* - * Can't use text-align; IE renders `text-top` properly, but - * FF doesn't (same height as `top`). Chrome doesn't understand - * `text-align: top`. Use relative positioning instead. - */ + * Can't use text-align; IE renders `text-top` properly, but + * FF doesn't (same height as `top`). Chrome doesn't understand + * `text-align: top`. Use relative positioning instead. + */ lineHeight: 0, fontSize: '65%', position: 'relative', diff --git a/src/components/widgets/text_editors/RichText/index.js b/src/components/widgets/text_editors/RichText/index.js index d6255af77..bc062db0a 100644 --- a/src/components/widgets/text_editors/RichText/index.js +++ b/src/components/widgets/text_editors/RichText/index.js @@ -37,12 +37,12 @@ class RichText extends Component { super(props, context); /* - * Initially set state based on the plotly.js annotation content. - * After this, as long as this component is mounted, it owns the source - * of truth for the annotation value via `this.state.editorState`. - * This state may be updated externally via a prop update. - * See `componentWillReceiveProps`. - */ + * Initially set state based on the plotly.js annotation content. + * After this, as long as this component is mounted, it owns the source + * of truth for the annotation value via `this.state.editorState`. + * This state may be updated externally via a prop update. + * See `componentWillReceiveProps`. + */ this.state = { editorState: props.value.toString().trim().length ? this.createEditorStateFromHTML(props.value) @@ -71,13 +71,13 @@ class RichText extends Component { const {linkEditorFocus, editorFocus} = this.state; /* - * Don't worry about what plotly.js thinks the annotation value - * should be while we're using our editor, for these reasons: - * - * 1. The editor should be considered the source of truth, unless the - * user is actually editing the annotation inline, in the chart. - * 2. Sometimes we get updates with stale values. - */ + * Don't worry about what plotly.js thinks the annotation value + * should be while we're using our editor, for these reasons: + * + * 1. The editor should be considered the source of truth, unless the + * user is actually editing the annotation inline, in the chart. + * 2. Sometimes we get updates with stale values. + */ if (linkEditorFocus || editorFocus) { return; } @@ -241,11 +241,11 @@ class RichText extends Component { */ onLinkEditorClose() { /* - * Focus on editor immediately to avoid error that occurs when - * `selection.extend` is called and another element has focus. - * https://bugzilla.mozilla.org/show_bug.cgi?id=921444 - * https://github.com/facebook/draft-js/blob/342576bf7186d07c82a41d9ca8169130669747d6/src/component/selection/setDraftEditorSelection.js#L128-L134 - */ + * Focus on editor immediately to avoid error that occurs when + * `selection.extend` is called and another element has focus. + * https://bugzilla.mozilla.org/show_bug.cgi?id=921444 + * https://github.com/facebook/draft-js/blob/342576bf7186d07c82a41d9ca8169130669747d6/src/component/selection/setDraftEditorSelection.js#L128-L134 + */ this.focus(); // Hide the editor. diff --git a/src/lib/striptags.js b/src/lib/striptags.js index d6d288ef5..334ad82d2 100644 --- a/src/lib/striptags.js +++ b/src/lib/striptags.js @@ -24,223 +24,210 @@ 'use strict'; -(function(global) { - // minimal symbol polyfill for IE11 and others - if (typeof Symbol !== 'function') { - var Symbol = function(name) { - return name; - }; - - Symbol.nonNative = true; - } - - const STATE_PLAINTEXT = Symbol('plaintext'); - const STATE_HTML = Symbol('html'); - const STATE_COMMENT = Symbol('comment'); - - const ALLOWED_TAGS_REGEX = /<(\w*)>/g; - const NORMALIZE_TAG_REGEX = /<\/?([^\s\/>]+)/; - - function striptags(html, allowable_tags, tag_replacement) { - html = html || ''; - allowable_tags = allowable_tags || []; - tag_replacement = tag_replacement || ''; - - const context = init_context(allowable_tags, tag_replacement); - - return striptags_internal(html, context); - } - - function init_striptags_stream(allowable_tags, tag_replacement) { - allowable_tags = allowable_tags || []; - tag_replacement = tag_replacement || ''; - - const context = init_context(allowable_tags, tag_replacement); - - return function striptags_stream(html) { - return striptags_internal(html || '', context); - }; - } - - striptags.init_streaming_mode = init_striptags_stream; - - function init_context(allowable_tags, tag_replacement) { - allowable_tags = parse_allowable_tags(allowable_tags); - - return { - allowable_tags: allowable_tags, - tag_replacement: tag_replacement, - - state: STATE_PLAINTEXT, - tag_buffer: '', - depth: 0, - in_quote_char: '', - }; - } +// minimal symbol polyfill for IE11 and others +if (typeof Symbol !== 'function') { + var Symbol = function(name) { + return name; + }; + + Symbol.nonNative = true; +} + +const STATE_PLAINTEXT = Symbol('plaintext'); +const STATE_HTML = Symbol('html'); +const STATE_COMMENT = Symbol('comment'); + +const ALLOWED_TAGS_REGEX = /<(\w*)>/g; +const NORMALIZE_TAG_REGEX = /<\/?([^\s\/>]+)/; + +function striptags(html, allowable_tags, tag_replacement) { + html = html || ''; + allowable_tags = allowable_tags || []; + tag_replacement = tag_replacement || ''; + + const context = init_context(allowable_tags, tag_replacement); + + return striptags_internal(html, context); +} + +function init_striptags_stream(allowable_tags, tag_replacement) { + allowable_tags = allowable_tags || []; + tag_replacement = tag_replacement || ''; + + const context = init_context(allowable_tags, tag_replacement); + + return function striptags_stream(html) { + return striptags_internal(html || '', context); + }; +} + +striptags.init_streaming_mode = init_striptags_stream; + +function init_context(allowable_tags, tag_replacement) { + allowable_tags = parse_allowable_tags(allowable_tags); + + return { + allowable_tags: allowable_tags, + tag_replacement: tag_replacement, + + state: STATE_PLAINTEXT, + tag_buffer: '', + depth: 0, + in_quote_char: '', + }; +} + +function striptags_internal(html, context) { + const allowable_tags = context.allowable_tags; + const tag_replacement = context.tag_replacement; + + let state = context.state; + let tag_buffer = context.tag_buffer; + let depth = context.depth; + let in_quote_char = context.in_quote_char; + let output = ''; + + for (let idx = 0, length = html.length; idx < length; idx++) { + const char = html[idx]; + + if (state === STATE_PLAINTEXT) { + switch (char) { + case '<': + state = STATE_HTML; + tag_buffer += char; + break; + + default: + output += char; + break; + } + } else if (state === STATE_HTML) { + switch (char) { + case '<': + // ignore '<' if inside a quote + if (in_quote_char) { + break; + } - function striptags_internal(html, context) { - const allowable_tags = context.allowable_tags; - const tag_replacement = context.tag_replacement; + // we're seeing a nested '<' + depth++; + break; - let state = context.state; - let tag_buffer = context.tag_buffer; - let depth = context.depth; - let in_quote_char = context.in_quote_char; - let output = ''; + case '>': + // ignore '>' if inside a quote + if (in_quote_char) { + break; + } - for (let idx = 0, length = html.length; idx < length; idx++) { - const char = html[idx]; + // something like this is happening: '<<>>' + if (depth) { + depth--; - if (state === STATE_PLAINTEXT) { - switch (char) { - case '<': - state = STATE_HTML; - tag_buffer += char; break; + } - default: - output += char; - break; - } - } else if (state === STATE_HTML) { - switch (char) { - case '<': - // ignore '<' if inside a quote - if (in_quote_char) { - break; - } - - // we're seeing a nested '<' - depth++; - break; + // this is closing the tag in tag_buffer + in_quote_char = ''; + state = STATE_PLAINTEXT; + tag_buffer += '>'; - case '>': - // ignore '>' if inside a quote - if (in_quote_char) { - break; - } + if (allowable_tags.has(normalize_tag(tag_buffer))) { + output += tag_buffer; + } else { + output += tag_replacement; + } - // something like this is happening: '<<>>' - if (depth) { - depth--; + tag_buffer = ''; + break; - break; - } + case '"': + case "'": + // catch both single and double quotes - // this is closing the tag in tag_buffer + if (char === in_quote_char) { in_quote_char = ''; - state = STATE_PLAINTEXT; - tag_buffer += '>'; + } else { + in_quote_char = in_quote_char || char; + } - if (allowable_tags.has(normalize_tag(tag_buffer))) { - output += tag_buffer; - } else { - output += tag_replacement; - } + tag_buffer += char; + break; - tag_buffer = ''; - break; - - case '"': - case "'": - // catch both single and double quotes - - if (char === in_quote_char) { - in_quote_char = ''; - } else { - in_quote_char = in_quote_char || char; - } + case '-': + if (tag_buffer === '': - if (tag_buffer.slice(-2) == '--') { - // close the comment - state = STATE_PLAINTEXT; - } + default: + tag_buffer += char; + break; + } + } else if (state === STATE_COMMENT) { + switch (char) { + case '>': + if (tag_buffer.slice(-2) == '--') { + // close the comment + state = STATE_PLAINTEXT; + } - tag_buffer = ''; - break; + tag_buffer = ''; + break; - default: - tag_buffer += char; - break; - } + default: + tag_buffer += char; + break; } } + } - // save the context for future iterations - context.state = state; - context.tag_buffer = tag_buffer; - context.depth = depth; - context.in_quote_char = in_quote_char; + // save the context for future iterations + context.state = state; + context.tag_buffer = tag_buffer; + context.depth = depth; + context.in_quote_char = in_quote_char; - return output; - } + return output; +} - function parse_allowable_tags(allowable_tags) { - let tag_set = new Set(); +function parse_allowable_tags(allowable_tags) { + let tag_set = new Set(); - if (typeof allowable_tags === 'string') { - let match; + if (typeof allowable_tags === 'string') { + let match; - while ((match = ALLOWED_TAGS_REGEX.exec(allowable_tags))) { - tag_set.add(match[1]); - } - } else if (!Symbol.nonNative && typeof allowable_tags[Symbol.iterator] === 'function') { - tag_set = new Set(allowable_tags); - } else if (typeof allowable_tags.forEach === 'function') { - // IE11 compatible - allowable_tags.forEach(tag_set.add, tag_set); + while ((match = ALLOWED_TAGS_REGEX.exec(allowable_tags))) { + tag_set.add(match[1]); } - - return tag_set; + } else if (!Symbol.nonNative && typeof allowable_tags[Symbol.iterator] === 'function') { + tag_set = new Set(allowable_tags); + } else if (typeof allowable_tags.forEach === 'function') { + // IE11 compatible + allowable_tags.forEach(tag_set.add, tag_set); } - function normalize_tag(tag_buffer) { - const match = NORMALIZE_TAG_REGEX.exec(tag_buffer); + return tag_set; +} - return match ? match[1].toLowerCase() : null; - } +function normalize_tag(tag_buffer) { + const match = NORMALIZE_TAG_REGEX.exec(tag_buffer); - if (typeof define === 'function' && define.amd) { - // AMD - define(function module_factory() { - return striptags; - }); - } else if (typeof module === 'object' && module.exports) { - // Node - module.exports = striptags; - } else { - // Browser - global.striptags = striptags; - } -})(this); + return match ? match[1].toLowerCase() : null; +} + +export default striptags; diff --git a/src/styles/components/widgets/_dropdown.scss b/src/styles/components/widgets/_dropdown.scss index 035f2c9a4..ec45988f8 100644 --- a/src/styles/components/widgets/_dropdown.scss +++ b/src/styles/components/widgets/_dropdown.scss @@ -1,5 +1,4 @@ .dropdown-container { - @import 'node_modules/react-select/scss/default'; flex-grow: 1; &:not(:last-child) { @@ -7,121 +6,115 @@ } .Select { - &.is-focused { - & > .Select-control { - background-color: var(--color-background-inputs); - .Select-input { - background-color: var(--color-background-inputs) !important; - } - } - } - &.is-open { - & > .Select-control { + &__control { + background-color: var(--color-background-inputs); + border: var(--border-default); + box-shadow: none !important; + color: var(--color-text-base); + min-height: 36px; + &:hover { border-color: var(--color-border-dark); - background-color: var(--color-background-inputs) !important; - .Select-input { - background-color: var(--color-background-inputs) !important; + .Select__arrow { + opacity: 1; } } - } - &:not(.is-open) { - .Select-control { + &:not(.is-open) { background-color: var(--color-background-inputs) !important; - &:hover { - border-color: var(--color-border-dark); - .Select-arrow { - opacity: 1; - } - } } } - - &-control { - background-color: var(--color-background-inputs); - border: var(--border-default); - box-shadow: none !important; - color: var(--color-text-base); - } - &-option { + &__option { color: var(--color-text-base); } - &-menu-outer { + &__menu { border: var(--border-default); border-top-color: var(--color-border-default); background-color: transparent; box-shadow: var(--box-shadow-base); + margin-bottom: 0; + margin-top: 0; + top: 98%; + &-list { + padding-top: 0; + padding-bottom: 0; + } + } + &__indicator-separator { + display: none; + } + &__clear-indicator { + padding: 6px 0; + color: $color-rhino-medium-2; + &:hover { + color: $color-sienna; + } + } + &__dropdown-indicator { + padding: 6px 6px 6px 0; + color: $color-rhino-medium-2; + &:hover { + color: var(--color-border-accent); + } + } + &__single-value { + font-size: 12px; + height: 16px; + color: var(--color-text-active); + font-weight: 400; + } + &__multi-value { + background-color: $color-rhino-light-2; + border: var(--border-default); + &__label { + max-width: 105px; + white-space: pre-wrap; + word-break: break-word; + } } } - .Select.is-focused:not(.is-open) > .Select-control { + .Select.is-focused:not(.is-open) > .Select__control { border-color: var(--color-border-accent); } - .Select.has-value.Select--single - > .Select-control - .Select-value - .Select-value-label, - .Select.has-value.is-pseudo-focused.Select--single - > .Select-control - .Select-value - .Select-value-label { - color: var(--color-text-base); - } - .Select.is-open .Select-arrow, - .editor_controls - .dropdown-container - .Select - .Select-arrow-zone:hover - > .Select-arrow { - border-color: transparent transparent var(--color-border-accent) !important; - } - .Select-arrow-zone:hover { - .Select-arrow { - border-top-color: var(--color-border-accent) !important; - } - } - .Select-arrow { + .Select__arrow { border-color: var(--color-border-default) transparent transparent; } - .Select-option { + .Select__option { background-color: var(--color-background-inputs); - &.is-selected { + &--is-selected { color: var(--color-text-active); font-weight: var(--font-weight-semibold); background-color: var(--color-background-light); } - &.is-focused { + &--is-focused { background-color: var(--color-background-light); color: var(--color-text-active); } } - .Select-option:hover { + .Select__option:hover { color: var(--color-text-active); background-color: var(--color-background-light); } - .Select-placeholder, - .Select--single > .Select-control .Select-value { + .Select__value-container { color: var(--color-text-base); + padding: 2px 6px; } - .Select-value-label { - color: var(--color-text-base) !important; - } - .Select-placeholder { + .Select__placeholder { color: var(--color-text-placeholder); } .dropdown--dark { .Select { - &-control { + &__control { background-color: var(--color-background-inverse); } - &-option { + &__option { background-color: var(--color-background-inverse); - &.is-selected { + &--is-selected { background-color: var(--color-border-default); } } - &.is-focused { + &--is-focused { background-color: var(--color-border-light); } } diff --git a/src/styles/components/widgets/_numeric-input.scss b/src/styles/components/widgets/_numeric-input.scss index be383d268..0e4ab6b7d 100644 --- a/src/styles/components/widgets/_numeric-input.scss +++ b/src/styles/components/widgets/_numeric-input.scss @@ -40,8 +40,7 @@ white-space: nowrap; text-align: left; border-radius: var(--border-radius-small); - padding: var(--spacing-quarter-unit) var(--spacing-quarter-unit) var(--spacing-quarter-unit) - var(--spacing-half-unit); + padding: var(--spacing-quarter-unit); width: 62px; vertical-align: middle; font-size: inherit; @@ -69,6 +68,7 @@ border-radius: 1px; line-height: var(--spacing-half-unit); text-align: center; + max-height: 13px; } .numeric-top-caret-modifier { diff --git a/webpack.config.js b/webpack.config.js index f540c3f3f..37a423049 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,10 +1,11 @@ const webpack = require('webpack'); module.exports = { - entry: ['babel-polyfill', 'react-hot-loader/patch', './dev/index.js'], + entry: ['@babel/polyfill', 'react-hot-loader/patch', './dev/index.js'], output: { filename: 'bundle.js', }, + mode: 'development', module: { rules: [ { @@ -12,10 +13,10 @@ module.exports = { use: { loader: 'babel-loader', options: { - presets: ['react', 'es2015'], + presets: ['@babel/react', '@babel/env'], plugins: [ 'react-hot-loader/babel', - 'transform-object-rest-spread', + '@babel/plugin-proposal-object-rest-spread', [ 'module-resolver', { @@ -38,7 +39,11 @@ module.exports = { }, ], }, - + resolve: { + alias: { + 'react-dom': '@hot-loader/react-dom', + }, + }, plugins: [new webpack.IgnorePlugin(/vertx/)], devServer: { open: true,