From 6146f3d47e8e8f9606feb0874041bbb0fc6b9e6c Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Thu, 24 Jan 2019 16:39:17 -0600 Subject: [PATCH 01/27] WIP: range component break down; dualRange inital --- src/components/form/range/_range.scss | 30 ++ src/components/form/range/dual_range.js | 233 +++++++++++ src/components/form/range/index.js | 1 + src/components/form/range/range.js | 405 +++++-------------- src/components/form/range/range_highlight.js | 26 ++ src/components/form/range/range_input.js | 36 ++ src/components/form/range/range_label.js | 8 + src/components/form/range/range_slider.js | 34 ++ src/components/form/range/range_tooltip.js | 42 ++ src/components/form/range/range_track.js | 196 +++++++++ src/components/form/range/range_wrapper.js | 83 ++++ 11 files changed, 782 insertions(+), 312 deletions(-) create mode 100644 src/components/form/range/dual_range.js create mode 100644 src/components/form/range/range_highlight.js create mode 100644 src/components/form/range/range_input.js create mode 100644 src/components/form/range/range_label.js create mode 100644 src/components/form/range/range_slider.js create mode 100644 src/components/form/range/range_tooltip.js create mode 100644 src/components/form/range/range_track.js create mode 100644 src/components/form/range/range_wrapper.js diff --git a/src/components/form/range/_range.scss b/src/components/form/range/_range.scss index 5bbe22662ad..0035f0fbd7a 100644 --- a/src/components/form/range/_range.scss +++ b/src/components/form/range/_range.scss @@ -28,6 +28,7 @@ } .euiRange__inputWrapper { + height: 100%; // Don't overflow `euiRange__wrapper` flex-grow: 1; position: relative; // for positioning ticks/levels align-self: flex-start; /* 3 */ @@ -368,6 +369,12 @@ $euiRangeLevelColors: ( border-width: ($euiRangeThumbHeight / 2) 0; color: transparent; } + + &--dual { + @include euiRangeThumbPerBrowser { + opacity: 0; // display: hidden doesn't work. Find something better + } + } } .euiRange__wrapper--hasRange .euiRange, @@ -377,3 +384,26 @@ $euiRangeLevelColors: ( border-color: transparentize($euiRangeTrackBorderColor, .6); } } + +.euiRange__thumb { + @include euiCustomControl($type: 'round'); + + cursor: pointer; + border-color: $euiRangeThumbBorderColor; + padding: 0; + height: $euiRangeThumbHeight; + width: $euiRangeThumbWidth; + + content: ''; + position: absolute; + left: 0; + top: 50%; + margin-top: -($euiRangeThumbHeight / 2); + z-index: $euiZLevel2; + + pointer-events: none; + + &--hasTicks { + top: 25%; + } +} diff --git a/src/components/form/range/dual_range.js b/src/components/form/range/dual_range.js new file mode 100644 index 00000000000..d3be1304867 --- /dev/null +++ b/src/components/form/range/dual_range.js @@ -0,0 +1,233 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { EuiRangeHighlight } from './range_highlight'; +import { EuiRangeInput } from './range_input'; +import { EuiRangeLabel } from './range_label'; +import { EuiRangeSlider } from './range_slider'; +import { EuiRangeTrack } from './range_track'; +import { EuiRangeWrapper } from './range_wrapper'; + +export const LEVEL_COLORS = ['primary', 'success', 'warning', 'danger']; + +export class EuiDualRange extends Component { + + _determineThumbMovement = (newVal, e) => { + // Determine closer thumb + let lower = this.props.value[0]; + let upper = this.props.value[1]; + if (Math.abs(lower - newVal) <= Math.abs(upper - newVal)) { + lower = newVal; + } else { + upper = newVal; + } + this._handleOnChange(lower, upper, e); + } + + _handleOnChange = (lower, upper, e) => { + this.props.onChange([lower, upper], e); + } + + handleSliderChange = (e) => { + this._determineThumbMovement(e.target.value, e); + } + + handleLowerInputChange = (e) => { + this._handleOnChange(e.target.value, this.props.value[1], e); + } + + handleUpperInputChange = (e) => { + this._handleOnChange(this.props.value[0], e.target.value, e); + } + + calculateThumbPositionStyle = (value) => { + // Calculate the left position based on value + const decimal = (value - this.props.min) / (this.props.max - this.props.min); + // Must be between 0-100% + let valuePosition = decimal <= 1 ? decimal : 1; + valuePosition = valuePosition >= 0 ? valuePosition : 0; + + //TODO: left should be 100% - (ratio of thumb width to track width) + return { left: `${valuePosition * 95.5}%` }; + } + + render() { + + const { + className, + compressed, + disabled, + fullWidth, + id, + max, + min, + name, + step, + showLabels, + showInput, + showTicks, + tickInterval, + ticks, // eslint-disable-line no-unused-vars + levels, + showRange, + valueAppend, // eslint-disable-line no-unused-vars + value, + style, + ...rest + } = this.props; + + const thumbClasses = classNames( + 'euiRange__thumb', + { + 'euiRange__thumb--hasTicks': showTicks + }, + ); + const lowerThumbStyle = this.calculateThumbPositionStyle(value[0]); + const upperThumbStyle = this.calculateThumbPositionStyle(value[1]); + + return ( + + {showInput && ( + + )} + {showLabels && {this.props.min}} + + + +
+
+ + {showRange && ( + + )} + + {showLabels && {this.props.max}} + {showInput && ( + + )} + + ); + } +} + +EuiDualRange.propTypes = { + name: PropTypes.string, + id: PropTypes.string, + min: PropTypes.number.isRequired, + max: PropTypes.number.isRequired, + step: PropTypes.number, + value: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), + fullWidth: PropTypes.bool, + compressed: PropTypes.bool, + /** + * Shows static min/max labels on the sides of the range slider + */ + showLabels: PropTypes.bool, + /** + * Displays an extra input control for direct manipulation + */ + showInput: PropTypes.bool, + /** + * Shows clickable tick marks and labels at the given interval (`step`/`tickInterval`) + */ + showTicks: PropTypes.bool, + /** + * Modifies the number of tick marks and at what interval + */ + tickInterval: PropTypes.number, + /** + * Specified ticks at specified values + */ + ticks: PropTypes.arrayOf( + PropTypes.shape({ + value: PropTypes.number.isRequired, + label: PropTypes.node.isRequired, + }), + ), + onChange: PropTypes.func, + /** + * Create colored indicators for certain intervals + */ + levels: PropTypes.arrayOf( + PropTypes.shape({ + min: PropTypes.number, + max: PropTypes.number, + color: PropTypes.oneOf(LEVEL_COLORS), + }), + ), + /** + * Shows a thick line from min to value + */ + showRange: PropTypes.bool, + /** + * Shows a tooltip styled value + */ + valueAppend: PropTypes.node, +}; + +EuiDualRange.defaultProps = { + min: 1, + max: 100, + fullWidth: false, + compressed: false, + showLabels: false, + showInput: false, + showTicks: false, + levels: [], +}; diff --git a/src/components/form/range/index.js b/src/components/form/range/index.js index 3f3c671156c..b536c93abd0 100644 --- a/src/components/form/range/index.js +++ b/src/components/form/range/index.js @@ -1 +1,2 @@ +export { EuiDualRange } from './dual_range'; export { EuiRange } from './range'; diff --git a/src/components/form/range/range.js b/src/components/form/range/range.js index 46dd286c67f..c0b7aba68ef 100644 --- a/src/components/form/range/range.js +++ b/src/components/form/range/range.js @@ -1,335 +1,116 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { range, find } from 'lodash'; - -import { EuiFieldNumber } from '../field_number'; +import { EuiRangeHighlight } from './range_highlight'; +import { EuiRangeInput } from './range_input'; +import { EuiRangeLabel } from './range_label'; +import { EuiRangeSlider } from './range_slider'; +import { EuiRangeTooltip } from './range_tooltip'; +import { EuiRangeTrack } from './range_track'; +import { EuiRangeWrapper } from './range_wrapper'; export const LEVEL_COLORS = ['primary', 'success', 'warning', 'danger']; -export class EuiRange extends Component { - constructor(props) { - super(props); - } - - render() { - const { - className, - compressed, - disabled, - fullWidth, - id, - max, - min, - name, - step, - showLabels, - showInput, - showTicks, - tickInterval, - ticks, // eslint-disable-line no-unused-vars - levels, - showRange, - showValue, - valueAppend, // eslint-disable-line no-unused-vars - onChange, - value, - style, - ...rest - } = this.props; - - const classes = classNames( - 'euiRange', - { - 'euiRange--fullWidth': fullWidth, - 'euiRange--compressed': compressed, - }, - className - ); - - const wrapperClasses = classNames( - 'euiRange__wrapper', - { - 'euiRange__wrapper--fullWidth': fullWidth, - 'euiRange__wrapper--compressed': compressed, - 'euiRange__wrapper--disabled': disabled, - 'euiRange__wrapper--hasLabels': showLabels, - 'euiRange__wrapper--hasLevels': levels.length, - 'euiRange__wrapper--hasRange': showRange, - 'euiRange__wrapper--hasTicks': showTicks, - 'euiRange__wrapper--hasValue': showValue, - }, - ); +export const EuiRange = props => { + + const { + className, + compressed, + disabled, + fullWidth, + id, + max, + min, + name, + step, + showLabels, + showInput, + showTicks, + tickInterval, + ticks, // eslint-disable-line no-unused-vars + levels, + showRange, + showValue, + valueAppend, // eslint-disable-line no-unused-vars + onChange, + value, + style, + ...rest + } = props; - let sliderTabIndex; - let extraInputNode; - if (showInput) { - // Chrome will properly size the input based on the max value, but FF & IE does not. - // Calculate the max-width of the input based on number of characters in min or max unit, whichever is greater. - // Add 2 to accomodate for input stepper - const maxWidthStyle = { maxWidth: `${Math.max(String(min).length, String(max).length) + 2}em` }; - - // Make this input the main control by disabling screen reader access to slider control - sliderTabIndex = '-1'; - - extraInputNode = ( - + {showLabels && {props.min}} + + - ); - } - let tickObject; - const inputWrapperStyle = {}; - if (showTicks) { - tickObject = calculateTicksObject(min, max, tickInterval || step || 1); - - // Calculate if any extra margin should be added to the inputWrapper - // because of longer tick labels on the ends - const lengthOfMinLabel = String(tickObject.sequence[0]).length; - const lenghtOfMaxLabel = String(tickObject.sequence[tickObject.sequence.length - 1]).length; - const isLastTickTheMax = tickObject.sequence[tickObject.sequence.length - 1] === max; - if (lengthOfMinLabel > 2) { - inputWrapperStyle.marginLeft = `${(lengthOfMinLabel / 5)}em`; - } - if (isLastTickTheMax && lenghtOfMaxLabel > 2) { - inputWrapperStyle.marginRight = `${(lenghtOfMaxLabel / 5)}em`; - } - } - - return ( -
- {this.renderLabel('min')} - -
- + )} + + {showRange && ( + - - {this.renderValue()} - {this.renderRange()} - {this.renderLevels()} - {this.renderTicks(tickObject)} -
- - {this.renderLabel('max')} - {extraInputNode} -
- ); - } - - renderLabel = (side) => { - const { - showLabels, - } = this.props; - - if (!showLabels) { return; } - - return ( - - ); - - } - - renderTicks = (tickObject) => { - const { - disabled, - onChange, - showTicks, - ticks, - value, - max, - } = this.props; - - if (!showTicks) { - return; - } - - // Align with item labels across the range by adding - // left and right negative margins that is half of the tick marks - const ticksStyle = !!ticks ? undefined : { margin: `0 ${tickObject.percentageWidth / -2}%`, left: 0, right: 0 }; - - return ( -
- {tickObject.sequence.map((tickValue) => { - const tickStyle = {}; - let customTick; - if (ticks) { - customTick = find(ticks, function (o) { return o.value === tickValue; }); - - if (customTick == null) { - return; - } else { - tickStyle.left = `${(customTick.value / max) * 100}%`; - } - } else { - tickStyle.width = `${tickObject.percentageWidth}%`; - } - - const tickClasses = classNames( - 'euiRange__tick', - { - 'euiRange__tick--selected': value === tickValue, - 'euiRange__tick-isCustom': customTick, - } - ); - - return ( - - ); - })} -
- ); - } - - renderRange = () => { - const { - showRange, - value, - max, - min, - } = this.props; - - if (!showRange) { - return; - } - - // Calculate the width the range based on value - const rangeWidth = (value - min) / (max - min); - const rangeWidthStyle = { width: `${rangeWidth * 100}%` }; - - return ( -
-
-
- ); - } - - renderValue = () => { - const { - showValue, - value, - valueAppend, - max, - min, - name, - } = this.props; - - if (!showValue) { - return; - } - - // Calculate the left position based on value - const decimal = (value - min) / (max - min); - // Must be between 0-100% - let valuePosition = decimal <= 1 ? decimal : 1; - valuePosition = valuePosition >= 0 ? valuePosition : 0; - - let valuePositionSide; - if (valuePosition > .5) { - valuePositionSide = 'left'; - } else { - valuePositionSide = 'right'; - } - - const valuePositionStyle = { left: `${valuePosition * 100}%` }; - - // Change left/right position based on value (half way point) - const valueClasses = classNames( - 'euiRange__value', - `euiRange__value--${valuePositionSide}`, - ); - - return ( -
- - {value}{valueAppend} - -
- ); - } - - renderLevels = () => { - const { - levels, - max, - min, - } = this.props; - - if (levels.length < 1) { - return; - } - - return ( -
- {levels.map((level, index) => { - const range = level.max - level.min; - const width = (range / (max - min)) * 100; - - return ( - - ); - })} -
- ); - } -} - -function calculateTicksObject(min, max, interval) { - // Calculate the width of each tick mark - const tickWidthDecimal = (interval / ((max - min) + interval)); - const tickWidthPercentage = tickWidthDecimal * 100; - - // Loop from min to max, creating ticks at each interval - // (adds a very small number to the max since `range` is not inclusive of the max value) - const toBeInclusive = .000000001; - const sequence = range(min, max + toBeInclusive, interval); - - return ( - { - decimalWidth: tickWidthDecimal, - percentageWidth: tickWidthPercentage, - sequence: sequence, - } + )} + + {showLabels && {props.max}} + {showInput && ( + + )} + ); -} +}; EuiRange.propTypes = { name: PropTypes.string, diff --git a/src/components/form/range/range_highlight.js b/src/components/form/range/range_highlight.js new file mode 100644 index 00000000000..9d40af68d7b --- /dev/null +++ b/src/components/form/range/range_highlight.js @@ -0,0 +1,26 @@ +import React from 'react'; +// import PropTypes from 'prop-types'; + +export const EuiRangeHighlight = props => { + const { + lowerValue, + upperValue, + max, + min, + } = props; + + // Calculate the width the range based on value + // const rangeWidth = (value - min) / (max - min); + const leftPosition = (lowerValue - min) / (max - min); + const rangeWidth = (upperValue - lowerValue) / (max - min); + const rangeWidthStyle = { + marginLeft: `${leftPosition * 100}%`, + width: `${rangeWidth * 100}%` + }; + + return ( +
+
+
+ ); +}; diff --git a/src/components/form/range/range_input.js b/src/components/form/range/range_input.js new file mode 100644 index 00000000000..3bc8b879e9e --- /dev/null +++ b/src/components/form/range/range_input.js @@ -0,0 +1,36 @@ +import React from 'react'; +// import PropTypes from 'prop-types'; + +import { EuiFieldNumber } from '../field_number'; + +export const EuiRangeInput = props => { + const { + min, + max, + step, + value, + disabled, + compressed, + onChange, + name, + ...rest + } = props; + + const maxWidthStyle = { maxWidth: `${Math.max(String(min).length, String(max).length) + 2}em` }; + + return ( + + ); +}; diff --git a/src/components/form/range/range_label.js b/src/components/form/range/range_label.js new file mode 100644 index 00000000000..71255b32701 --- /dev/null +++ b/src/components/form/range/range_label.js @@ -0,0 +1,8 @@ +import React from 'react'; +// import PropTypes from 'prop-types'; + +export const EuiRangeLabel = ({ children, side }) => ( + +); diff --git a/src/components/form/range/range_slider.js b/src/components/form/range/range_slider.js new file mode 100644 index 00000000000..944ec732842 --- /dev/null +++ b/src/components/form/range/range_slider.js @@ -0,0 +1,34 @@ +import React from 'react'; + +export const EuiRangeSlider = ( + { + className, + disabled, + id, + max, + min, + name, + step, + onChange, + tabIndex, + value, + style, + ...rest + } +) => ( + +); diff --git a/src/components/form/range/range_tooltip.js b/src/components/form/range/range_tooltip.js new file mode 100644 index 00000000000..4f358baaf02 --- /dev/null +++ b/src/components/form/range/range_tooltip.js @@ -0,0 +1,42 @@ +import React from 'react'; +// import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export const EuiRangeTooltip = props => { + const { + value, + valueAppend, + max, + min, + name, + } = props; + + // Calculate the left position based on value + const decimal = (value - min) / (max - min); + // Must be between 0-100% + let valuePosition = decimal <= 1 ? decimal : 1; + valuePosition = valuePosition >= 0 ? valuePosition : 0; + + let valuePositionSide; + if (valuePosition > .5) { + valuePositionSide = 'left'; + } else { + valuePositionSide = 'right'; + } + + const valuePositionStyle = { left: `${valuePosition * 100}%` }; + + // Change left/right position based on value (half way point) + const valueClasses = classNames( + 'euiRange__value', + `euiRange__value--${valuePositionSide}`, + ); + + return ( +
+ + {value}{valueAppend} + +
+ ); +}; diff --git a/src/components/form/range/range_track.js b/src/components/form/range/range_track.js new file mode 100644 index 00000000000..7dbe6461822 --- /dev/null +++ b/src/components/form/range/range_track.js @@ -0,0 +1,196 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { range, find } from 'lodash'; + +export const LEVEL_COLORS = ['primary', 'success', 'warning', 'danger']; + +export class EuiRangeTrack extends Component { + + calculateTicksObject = (min, max, interval) => { + // Calculate the width of each tick mark + const tickWidthDecimal = (interval / ((max - min) + interval)); + const tickWidthPercentage = tickWidthDecimal * 100; + + // Loop from min to max, creating ticks at each interval + // (adds a very small number to the max since `range` is not inclusive of the max value) + const toBeInclusive = .000000001; + const sequence = range(min, max + toBeInclusive, interval); + + return ( + { + decimalWidth: tickWidthDecimal, + percentageWidth: tickWidthPercentage, + sequence: sequence, + } + ); + } + + render() { + const { + children, + disabled, + max, + min, + step, + showTicks, + tickInterval, + ticks, // eslint-disable-line no-unused-vars + levels, + onChange, + value + } = this.props; + + let tickObject; + const inputWrapperStyle = {}; + if (showTicks) { + tickObject = this.calculateTicksObject(min, max, tickInterval || step || 1); + + // Calculate if any extra margin should be added to the inputWrapper + // because of longer tick labels on the ends + const lengthOfMinLabel = String(tickObject.sequence[0]).length; + const lenghtOfMaxLabel = String(tickObject.sequence[tickObject.sequence.length - 1]).length; + const isLastTickTheMax = tickObject.sequence[tickObject.sequence.length - 1] === max; + if (lengthOfMinLabel > 2) { + inputWrapperStyle.marginLeft = `${(lengthOfMinLabel / 5)}em`; + } + if (isLastTickTheMax && lenghtOfMaxLabel > 2) { + inputWrapperStyle.marginRight = `${(lenghtOfMaxLabel / 5)}em`; + } + } + + return ( +
+ {children} + {!!levels.length && ( + + )} + {showTicks && ( + + )} +
+ ); + } +} + +EuiRangeTrack.propTypes = { + min: PropTypes.number.isRequired, + max: PropTypes.number.isRequired, + step: PropTypes.number, + value: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])) + ]), + /** + * Shows clickable tick marks and labels at the given interval (`step`/`tickInterval`) + */ + showTicks: PropTypes.bool, + /** + * Modifies the number of tick marks and at what interval + */ + tickInterval: PropTypes.number, + /** + * Specified ticks at specified values + */ + ticks: PropTypes.arrayOf( + PropTypes.shape({ + value: PropTypes.number.isRequired, + label: PropTypes.node.isRequired, + }), + ), + onChange: PropTypes.func, + /** + * Create colored indicators for certain intervals + */ + levels: PropTypes.arrayOf( + PropTypes.shape({ + min: PropTypes.number, + max: PropTypes.number, + color: PropTypes.oneOf(LEVEL_COLORS), + }), + ), +}; + +EuiRangeTrack.defaultProps = { + min: 1, + max: 100, + showInput: false, + showTicks: false, + levels: [], +}; + +const EuiRangeTicks = ({ disabled, onChange, ticks, tickObject, value, max }) => { + // Align with item labels across the range by adding + // left and right negative margins that is half of the tick marks + const ticksStyle = !!ticks ? undefined : { margin: `0 ${tickObject.percentageWidth / -2}%`, left: 0, right: 0 }; + + return ( +
+ {tickObject.sequence.map((tickValue) => { + const tickStyle = {}; + let customTick; + if (ticks) { + customTick = find(ticks, function (o) { return o.value === tickValue; }); + + if (customTick == null) { + return; + } else { + tickStyle.left = `${(customTick.value / max) * 100}%`; + } + } else { + tickStyle.width = `${tickObject.percentageWidth}%`; + } + + const tickClasses = classNames( + 'euiRange__tick', + { + 'euiRange__tick--selected': value === tickValue, + 'euiRange__tick-isCustom': customTick, + } + ); + + return ( + + ); + })} +
+ ); +}; + +const EuiRangeLevels = ({ levels, max, min }) => ( +
+ {levels.map((level, index) => { + const range = level.max - level.min; + const width = (range / (max - min)) * 100; + + return ( + + ); + })} +
+); diff --git a/src/components/form/range/range_wrapper.js b/src/components/form/range/range_wrapper.js new file mode 100644 index 00000000000..2f501e8f93d --- /dev/null +++ b/src/components/form/range/range_wrapper.js @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export const LEVEL_COLORS = ['primary', 'success', 'warning', 'danger']; + +export const EuiRangeWrapper = ({ + children, + compressed, + disabled, + fullWidth, + showLabels, + showTicks, + levels, + showRange, + showValue, +}) => { + + const classes = classNames( + 'euiRange__wrapper', + { + 'euiRange__wrapper--fullWidth': fullWidth, + 'euiRange__wrapper--compressed': compressed, + 'euiRange__wrapper--disabled': disabled, + 'euiRange__wrapper--hasLabels': showLabels, + 'euiRange__wrapper--hasLevels': levels.length, + 'euiRange__wrapper--hasRange': showRange, + 'euiRange__wrapper--hasTicks': showTicks, + 'euiRange__wrapper--hasValue': showValue, + }, + ); + + return ( +
+ {children} +
+ ); +}; + +EuiRangeWrapper.propTypes = { + fullWidth: PropTypes.bool, + compressed: PropTypes.bool, + /** + * Shows static min/max labels on the sides of the range slider + */ + showLabels: PropTypes.bool, + /** + * Displays an extra input control for direct manipulation + */ + showInput: PropTypes.bool, + /** + * Shows clickable tick marks and labels at the given interval (`step`/`tickInterval`) + */ + showTicks: PropTypes.bool, + /** + * Create colored indicators for certain intervals + */ + levels: PropTypes.arrayOf( + PropTypes.shape({ + min: PropTypes.number, + max: PropTypes.number, + color: PropTypes.oneOf(LEVEL_COLORS), + }), + ), + /** + * Shows a thick line from min to value + */ + showRange: PropTypes.bool, + /** + * Shows a tooltip styled value + */ + showValue: PropTypes.bool, +}; + +EuiRangeWrapper.defaultProps = { + fullWidth: false, + compressed: false, + showLabels: false, + showInput: false, + showTicks: false, + showValue: false, + levels: [], +}; From b781aeef68321187fb973d53c8758d401b9ccdc0 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 25 Jan 2019 09:29:52 -0600 Subject: [PATCH 02/27] WIP: dual range docs examples --- src-docs/src/views/form_controls/range.js | 59 +++++++++++++++++++++++ src/components/form/index.js | 2 +- src/components/index.js | 1 + 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src-docs/src/views/form_controls/range.js b/src-docs/src/views/form_controls/range.js index d071610942a..9a80a1df884 100644 --- a/src-docs/src/views/form_controls/range.js +++ b/src-docs/src/views/form_controls/range.js @@ -4,6 +4,7 @@ import React, { } from 'react'; import { + EuiDualRange, EuiRange, EuiSpacer, EuiFormHelpText, @@ -30,6 +31,7 @@ export default class extends Component { this.state = { value: '120', + values: [120, 180] }; } @@ -39,6 +41,12 @@ export default class extends Component { }); }; + onDualChange = (values) => { + this.setState({ + values: values, + }); + }; + render() { return ( @@ -54,6 +62,17 @@ export default class extends Component { name="firstRange" /> + + + + + + + + ); } diff --git a/src/components/form/index.js b/src/components/form/index.js index a0dca1c8bb8..2dd6490a490 100644 --- a/src/components/form/index.js +++ b/src/components/form/index.js @@ -18,7 +18,7 @@ export { EuiRadio, EuiRadioGroup, } from './radio'; -export { EuiRange } from './range'; +export { EuiDualRange, EuiRange } from './range'; export { EuiSelect } from './select'; export { EuiSuperSelect, diff --git a/src/components/index.js b/src/components/index.js index 654bed85b61..9589bd3c55f 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -131,6 +131,7 @@ export { EuiCheckbox, EuiCheckboxGroup, EuiDescribedFormGroup, + EuiDualRange, EuiFieldNumber, EuiFieldPassword, EuiFieldSearch, From b5fabbcbf85afad6229f3f19ce9a39bf28a4d367 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 25 Jan 2019 15:48:57 -0600 Subject: [PATCH 03/27] WIP: propTypes; type def updates; baseline keyboard nav --- src/components/form/range/_range.scss | 4 + src/components/form/range/dual_range.js | 146 +++++++++++++++---- src/components/form/range/index.d.ts | 21 ++- src/components/form/range/range.js | 72 ++++----- src/components/form/range/range_highlight.js | 18 +-- src/components/form/range/range_input.js | 35 +++-- src/components/form/range/range_label.js | 6 +- src/components/form/range/range_slider.js | 49 ++++--- src/components/form/range/range_tooltip.js | 20 +-- src/components/form/range/range_track.js | 20 --- src/components/form/range/range_wrapper.js | 36 +---- 11 files changed, 254 insertions(+), 173 deletions(-) diff --git a/src/components/form/range/_range.scss b/src/components/form/range/_range.scss index 0035f0fbd7a..c4c00c634a5 100644 --- a/src/components/form/range/_range.scss +++ b/src/components/form/range/_range.scss @@ -403,6 +403,10 @@ $euiRangeLevelColors: ( pointer-events: none; + &:focus { + @include euiCustomControlFocused; + } + &--hasTicks { top: 25%; } diff --git a/src/components/form/range/dual_range.js b/src/components/form/range/dual_range.js index d3be1304867..a69c4192466 100644 --- a/src/components/form/range/dual_range.js +++ b/src/components/form/range/dual_range.js @@ -2,21 +2,29 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import { keyCodes } from '../../../services'; + import { EuiRangeHighlight } from './range_highlight'; import { EuiRangeInput } from './range_input'; import { EuiRangeLabel } from './range_label'; import { EuiRangeSlider } from './range_slider'; -import { EuiRangeTrack } from './range_track'; +import { EuiRangeTrack, LEVEL_COLORS } from './range_track'; import { EuiRangeWrapper } from './range_wrapper'; -export const LEVEL_COLORS = ['primary', 'success', 'warning', 'danger']; - export class EuiDualRange extends Component { + rangeSliderRef = React.createRef(); + + get lowerValue() { + return this.props.value[0]; + } + get upperValue() { + return this.props.value[1]; + } _determineThumbMovement = (newVal, e) => { // Determine closer thumb - let lower = this.props.value[0]; - let upper = this.props.value[1]; + let lower = this.lowerValue; + let upper = this.upperValue; if (Math.abs(lower - newVal) <= Math.abs(upper - newVal)) { lower = newVal; } else { @@ -34,11 +42,52 @@ export class EuiDualRange extends Component { } handleLowerInputChange = (e) => { - this._handleOnChange(e.target.value, this.props.value[1], e); + this._handleOnChange(e.target.value, this.upperValue, e); } handleUpperInputChange = (e) => { - this._handleOnChange(this.props.value[0], e.target.value, e); + this._handleOnChange(this.lowerValue, e.target.value, e); + } + + _handleKeyDown = (value, e) => { + e.preventDefault(); + let newVal = value; + const change = this.props.step || 1; + switch (e.keyCode) { + case keyCodes.UP: + case keyCodes.RIGHT: + newVal += change; + break; + case keyCodes.DOWN: + case keyCodes.LEFT: + newVal -= change; + break; + } + return newVal; + } + + handleLowerKeyDown = (e) => { + let lower = this.lowerValue; + switch (e.keyCode) { + case keyCodes.TAB: + return; + default: + lower = this._handleKeyDown(lower, e); + } + if (lower >= this.upperValue || lower < this.props.min) return; + this._handleOnChange(lower, this.upperValue, e); + } + + handleUpperKeyDown = (e) => { + let upper = this.upperValue; + switch (e.keyCode) { + case keyCodes.TAB: + return; + default: + upper = this._handleKeyDown(upper, e); + } + if (upper <= this.lowerValue || upper > this.props.max) return; + this._handleOnChange(this.lowerValue, upper, e); } calculateThumbPositionStyle = (value) => { @@ -48,8 +97,10 @@ export class EuiDualRange extends Component { let valuePosition = decimal <= 1 ? decimal : 1; valuePosition = valuePosition >= 0 ? valuePosition : 0; - //TODO: left should be 100% - (ratio of thumb width to track width) - return { left: `${valuePosition * 95.5}%` }; + // TODO: Get ref earlier + const thumbToTrackRatio = this.rangeSliderRef.current ? (16 / this.rangeSliderRef.current.offsetWidth) : 0.05; + const trackPositionScale = (1 - thumbToTrackRatio) * 100; + return { left: `${valuePosition * trackPositionScale}%` }; } render() { @@ -70,6 +121,7 @@ export class EuiDualRange extends Component { tickInterval, ticks, // eslint-disable-line no-unused-vars levels, + onChange, // eslint-disable-line no-unused-vars showRange, valueAppend, // eslint-disable-line no-unused-vars value, @@ -77,14 +129,7 @@ export class EuiDualRange extends Component { ...rest } = this.props; - const thumbClasses = classNames( - 'euiRange__thumb', - { - 'euiRange__thumb--hasTicks': showTicks - }, - ); - const lowerThumbStyle = this.calculateThumbPositionStyle(value[0]); - const upperThumbStyle = this.calculateThumbPositionStyle(value[1]); + const rangeClasses = classNames('euiRange', 'euiRange--dual', className); return ( )} - {showLabels && {this.props.min}} + {showLabels && {min}} -
-
+ + {showRange && ( )} - {showLabels && {this.props.max}} + {showLabels && {max}} {showInput && ( { + const classes = classNames( + 'euiRange__thumb', + { + 'euiRange__thumb--hasTicks': showTicks + }, + ); + return ( +
+ ); +}; diff --git a/src/components/form/range/index.d.ts b/src/components/form/range/index.d.ts index 6b15cbf3bd8..4696c47dc89 100644 --- a/src/components/form/range/index.d.ts +++ b/src/components/form/range/index.d.ts @@ -3,12 +3,14 @@ import { CommonProps } from '../../common'; import { SFC, ReactNode, HTMLAttributes, ChangeEventHandler, InputHTMLAttributes } from 'react'; declare module '@elastic/eui' { + export type EuiRangeLevelColor = 'primary' | 'success' | 'warning' | 'danger'; + /** + * single range type def + * * @see './range.js' */ - export type EuiRangeLevelColor = 'primary' | 'success' | 'warning' | 'danger'; - export interface EuiRangeProps { compressed?: boolean; fullWidth?: boolean; @@ -32,4 +34,19 @@ declare module '@elastic/eui' { export const EuiRange: SFC< CommonProps & InputHTMLAttributes & EuiRangeProps >; + + /** + * dual range type defs + * + * @see './dual_range.js' + */ + + export interface EuiDualRangeProps { + // Override acceptable value type + value: Array + } + + export const EuiDualRange: SFC< + CommonProps & InputHTMLAttributes & EuiRangeProps & EuiDualRangeProps + >; } diff --git a/src/components/form/range/range.js b/src/components/form/range/range.js index c0b7aba68ef..6f0e8a989e7 100644 --- a/src/components/form/range/range.js +++ b/src/components/form/range/range.js @@ -1,42 +1,42 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; import { EuiRangeHighlight } from './range_highlight'; import { EuiRangeInput } from './range_input'; import { EuiRangeLabel } from './range_label'; import { EuiRangeSlider } from './range_slider'; import { EuiRangeTooltip } from './range_tooltip'; -import { EuiRangeTrack } from './range_track'; +import { EuiRangeTrack, LEVEL_COLORS } from './range_track'; import { EuiRangeWrapper } from './range_wrapper'; -export const LEVEL_COLORS = ['primary', 'success', 'warning', 'danger']; +export const EuiRange = ({ + className, + compressed, + disabled, + fullWidth, + id, + max, + min, + name, + step, + showLabels, + showInput, + showTicks, + tickInterval, + ticks, // eslint-disable-line no-unused-vars + levels, + showRange, + showValue, + valueAppend, // eslint-disable-line no-unused-vars + onChange, + value, + style, + tabIndex, + ...rest +}) => { -export const EuiRange = props => { - - const { - className, - compressed, - disabled, - fullWidth, - id, - max, - min, - name, - step, - showLabels, - showInput, - showTicks, - tickInterval, - ticks, // eslint-disable-line no-unused-vars - levels, - showRange, - showValue, - valueAppend, // eslint-disable-line no-unused-vars - onChange, - value, - style, - ...rest - } = props; + const classes = classNames('euiRange', className); return ( { showRange={showRange} showValue={showValue} > - {showLabels && {props.min}} + {showLabels && {min}} { { disabled={disabled} onChange={onChange} style={style} - tabIndex={showInput ? '-1' : '0'} + tabIndex={showInput ? '-1' : (tabIndex || null)} {...rest} /> @@ -88,14 +88,14 @@ export const EuiRange = props => { {showRange && ( )} - {showLabels && {props.max}} + {showLabels && {max}} {showInput && ( { - const { - lowerValue, - upperValue, - max, - min, - } = props; +import PropTypes from 'prop-types'; +export const EuiRangeHighlight = ({ lowerValue, upperValue, max, min }) => { // Calculate the width the range based on value // const rangeWidth = (value - min) / (max - min); const leftPosition = (lowerValue - min) / (max - min); @@ -24,3 +17,10 @@ export const EuiRangeHighlight = props => {
); }; + +EuiRangeHighlight.propTypes = { + lowerValue: PropTypes.number.isRequired, + upperValue: PropTypes.number.isRequired, + max: PropTypes.number.isRequired, + min: PropTypes.number.isRequired +}; diff --git a/src/components/form/range/range_input.js b/src/components/form/range/range_input.js index 3bc8b879e9e..353ad6fed4c 100644 --- a/src/components/form/range/range_input.js +++ b/src/components/form/range/range_input.js @@ -1,20 +1,19 @@ import React from 'react'; -// import PropTypes from 'prop-types'; +import PropTypes from 'prop-types'; import { EuiFieldNumber } from '../field_number'; -export const EuiRangeInput = props => { - const { - min, - max, - step, - value, - disabled, - compressed, - onChange, - name, - ...rest - } = props; +export const EuiRangeInput = ({ + min, + max, + step, + value, + disabled, + compressed, + onChange, + name, + ...rest +}) => { const maxWidthStyle = { maxWidth: `${Math.max(String(min).length, String(max).length) + 2}em` }; @@ -34,3 +33,13 @@ export const EuiRangeInput = props => { /> ); }; + +EuiRangeInput.propTypes = { + min: PropTypes.number.isRequired, + max: PropTypes.number.isRequired, + step: PropTypes.number, + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + compressed: PropTypes.bool, + onChange: PropTypes.func, + name: PropTypes.string +}; diff --git a/src/components/form/range/range_label.js b/src/components/form/range/range_label.js index 71255b32701..f32ce11eb0b 100644 --- a/src/components/form/range/range_label.js +++ b/src/components/form/range/range_label.js @@ -1,8 +1,12 @@ import React from 'react'; -// import PropTypes from 'prop-types'; +import PropTypes from 'prop-types'; export const EuiRangeLabel = ({ children, side }) => ( ); + +EuiRangeLabel.propTypes = { + side: PropTypes.string.isRequired +}; diff --git a/src/components/form/range/range_slider.js b/src/components/form/range/range_slider.js index 944ec732842..1203c04fa6e 100644 --- a/src/components/form/range/range_slider.js +++ b/src/components/form/range/range_slider.js @@ -1,22 +1,22 @@ import React from 'react'; +import PropTypes from 'prop-types'; -export const EuiRangeSlider = ( - { - className, - disabled, - id, - max, - min, - name, - step, - onChange, - tabIndex, - value, - style, - ...rest - } -) => ( +export const EuiRangeSlider = React.forwardRef(({ + className, + disabled, + id, + max, + min, + name, + step, + onChange, + tabIndex, + value, + style, + ...rest +}, ref) => ( -); +)); + +EuiRangeSlider.propTypes = { + id: PropTypes.string, + min: PropTypes.number.isRequired, + max: PropTypes.number.isRequired, + name: PropTypes.string, + step: PropTypes.number, + onChange: PropTypes.func, + tabIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + value: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])) + ]), +}; diff --git a/src/components/form/range/range_tooltip.js b/src/components/form/range/range_tooltip.js index 4f358baaf02..4ab13cbc1fa 100644 --- a/src/components/form/range/range_tooltip.js +++ b/src/components/form/range/range_tooltip.js @@ -1,16 +1,8 @@ import React from 'react'; -// import PropTypes from 'prop-types'; +import PropTypes from 'prop-types'; import classNames from 'classnames'; -export const EuiRangeTooltip = props => { - const { - value, - valueAppend, - max, - min, - name, - } = props; - +export const EuiRangeTooltip = ({ value, valueAppend, max, min, name }) => { // Calculate the left position based on value const decimal = (value - min) / (max - min); // Must be between 0-100% @@ -40,3 +32,11 @@ export const EuiRangeTooltip = props => {
); }; + +EuiRangeTooltip.propTypes = { + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + valueAppend: PropTypes.string, + max: PropTypes.number.isRequired, + min: PropTypes.number.isRequired, + name: PropTypes.string +}; diff --git a/src/components/form/range/range_track.js b/src/components/form/range/range_track.js index 7dbe6461822..2a2719b6ea1 100644 --- a/src/components/form/range/range_track.js +++ b/src/components/form/range/range_track.js @@ -94,17 +94,8 @@ EuiRangeTrack.propTypes = { PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])) ]), - /** - * Shows clickable tick marks and labels at the given interval (`step`/`tickInterval`) - */ showTicks: PropTypes.bool, - /** - * Modifies the number of tick marks and at what interval - */ tickInterval: PropTypes.number, - /** - * Specified ticks at specified values - */ ticks: PropTypes.arrayOf( PropTypes.shape({ value: PropTypes.number.isRequired, @@ -112,9 +103,6 @@ EuiRangeTrack.propTypes = { }), ), onChange: PropTypes.func, - /** - * Create colored indicators for certain intervals - */ levels: PropTypes.arrayOf( PropTypes.shape({ min: PropTypes.number, @@ -124,14 +112,6 @@ EuiRangeTrack.propTypes = { ), }; -EuiRangeTrack.defaultProps = { - min: 1, - max: 100, - showInput: false, - showTicks: false, - levels: [], -}; - const EuiRangeTicks = ({ disabled, onChange, ticks, tickObject, value, max }) => { // Align with item labels across the range by adding // left and right negative margins that is half of the tick marks diff --git a/src/components/form/range/range_wrapper.js b/src/components/form/range/range_wrapper.js index 2f501e8f93d..29157b1dab9 100644 --- a/src/components/form/range/range_wrapper.js +++ b/src/components/form/range/range_wrapper.js @@ -40,44 +40,10 @@ export const EuiRangeWrapper = ({ EuiRangeWrapper.propTypes = { fullWidth: PropTypes.bool, compressed: PropTypes.bool, - /** - * Shows static min/max labels on the sides of the range slider - */ showLabels: PropTypes.bool, - /** - * Displays an extra input control for direct manipulation - */ showInput: PropTypes.bool, - /** - * Shows clickable tick marks and labels at the given interval (`step`/`tickInterval`) - */ showTicks: PropTypes.bool, - /** - * Create colored indicators for certain intervals - */ - levels: PropTypes.arrayOf( - PropTypes.shape({ - min: PropTypes.number, - max: PropTypes.number, - color: PropTypes.oneOf(LEVEL_COLORS), - }), - ), - /** - * Shows a thick line from min to value - */ + levels: PropTypes.array, showRange: PropTypes.bool, - /** - * Shows a tooltip styled value - */ showValue: PropTypes.bool, }; - -EuiRangeWrapper.defaultProps = { - fullWidth: false, - compressed: false, - showLabels: false, - showInput: false, - showTicks: false, - showValue: false, - levels: [], -}; From 9cbe7938066940977d3de92cb6b0f951b8900bca Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 25 Jan 2019 16:04:40 -0600 Subject: [PATCH 04/27] WIP: hasFocus for range highlight --- src/components/form/range/_range.scss | 4 ++++ src/components/form/range/dual_range.js | 20 +++++++++++++++++--- src/components/form/range/range_highlight.js | 10 ++++++++-- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/components/form/range/_range.scss b/src/components/form/range/_range.scss index c4c00c634a5..9cee9ff7ab5 100644 --- a/src/components/form/range/_range.scss +++ b/src/components/form/range/_range.scss @@ -124,6 +124,10 @@ $euiRangeLevelColors: ( height: 4px; border-radius: 4px; background-color: $euiRangeTrackColor; + + &--hasFocus { + background-color: $euiColorPrimary; + } } .euiRange__value { diff --git a/src/components/form/range/dual_range.js b/src/components/form/range/dual_range.js index a69c4192466..77b28d53440 100644 --- a/src/components/form/range/dual_range.js +++ b/src/components/form/range/dual_range.js @@ -12,6 +12,10 @@ import { EuiRangeTrack, LEVEL_COLORS } from './range_track'; import { EuiRangeWrapper } from './range_wrapper'; export class EuiDualRange extends Component { + state = { + hasFocus: false + } + rangeSliderRef = React.createRef(); get lowerValue() { @@ -103,6 +107,12 @@ export class EuiDualRange extends Component { return { left: `${valuePosition * trackPositionScale}%` }; } + toggleHasFocus = (shouldFocused = !this.state.hasFocus) => { + this.setState({ + hasFocus: shouldFocused + }); + } + render() { const { @@ -190,6 +200,8 @@ export class EuiDualRange extends Component { showTicks={showTicks} showInput={showInput} onKeyDown={this.handleLowerKeyDown} + onFocus={() => this.toggleHasFocus(true)} + onBlur={() => this.toggleHasFocus(false)} style={this.calculateThumbPositionStyle(this.lowerValue)} /> this.toggleHasFocus(true)} + onBlur={() => this.toggleHasFocus(false)} style={this.calculateThumbPositionStyle(this.upperValue)} /> {showRange && ( { +const EuiRangeThumb = ({ min, max, value, disabled, showInput, showTicks, ...rest }) => { const classes = classNames( 'euiRange__thumb', { @@ -306,14 +321,13 @@ const EuiRangeThumb = ({ min, max, value, disabled, onKeyDown, showInput, showTi return (
); }; diff --git a/src/components/form/range/range_highlight.js b/src/components/form/range/range_highlight.js index ab1eed8cde4..320dfa2483f 100644 --- a/src/components/form/range/range_highlight.js +++ b/src/components/form/range/range_highlight.js @@ -1,7 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; -export const EuiRangeHighlight = ({ lowerValue, upperValue, max, min }) => { +export const EuiRangeHighlight = ({ hasFocus, lowerValue, upperValue, max, min }) => { // Calculate the width the range based on value // const rangeWidth = (value - min) / (max - min); const leftPosition = (lowerValue - min) / (max - min); @@ -11,14 +12,19 @@ export const EuiRangeHighlight = ({ lowerValue, upperValue, max, min }) => { width: `${rangeWidth * 100}%` }; + const classes = classNames('euiRange__range__progress', { + 'euiRange__range__progress--hasFocus': hasFocus + }); + return (
-
+
); }; EuiRangeHighlight.propTypes = { + hasFocus: PropTypes.bool, lowerValue: PropTypes.number.isRequired, upperValue: PropTypes.number.isRequired, max: PropTypes.number.isRequired, From 6b6acbd42759de5486571c685c100248b46c28d0 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 25 Jan 2019 16:30:04 -0600 Subject: [PATCH 05/27] WIP: better rangeSlider ref access --- src/components/form/range/dual_range.js | 69 ++++++++++++++----------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/src/components/form/range/dual_range.js b/src/components/form/range/dual_range.js index 77b28d53440..88190fb15d1 100644 --- a/src/components/form/range/dual_range.js +++ b/src/components/form/range/dual_range.js @@ -13,10 +13,17 @@ import { EuiRangeWrapper } from './range_wrapper'; export class EuiDualRange extends Component { state = { - hasFocus: false + hasFocus: false, + rangeSliderRefAvailable: false } - rangeSliderRef = React.createRef(); + rangeSliderRef = null; + handleRangeSliderRefUpdate = (ref) => { + this.rangeSliderRef = ref; + this.setState({ + rangeSliderRefAvailable: true + }); + } get lowerValue() { return this.props.value[0]; @@ -101,8 +108,8 @@ export class EuiDualRange extends Component { let valuePosition = decimal <= 1 ? decimal : 1; valuePosition = valuePosition >= 0 ? valuePosition : 0; - // TODO: Get ref earlier - const thumbToTrackRatio = this.rangeSliderRef.current ? (16 / this.rangeSliderRef.current.offsetWidth) : 0.05; + const EUI_THUMB_SIZE = 16; + const thumbToTrackRatio = (EUI_THUMB_SIZE / this.rangeSliderRef.clientWidth); const trackPositionScale = (1 - thumbToTrackRatio) * 100; return { left: `${valuePosition * trackPositionScale}%` }; } @@ -177,7 +184,7 @@ export class EuiDualRange extends Component { value={value} > - this.toggleHasFocus(true)} - onBlur={() => this.toggleHasFocus(false)} - style={this.calculateThumbPositionStyle(this.lowerValue)} - /> - this.toggleHasFocus(true)} - onBlur={() => this.toggleHasFocus(false)} - style={this.calculateThumbPositionStyle(this.upperValue)} - /> + {this.state.rangeSliderRefAvailable && ( + + this.toggleHasFocus(true)} + onBlur={() => this.toggleHasFocus(false)} + style={this.calculateThumbPositionStyle(this.lowerValue)} + /> + this.toggleHasFocus(true)} + onBlur={() => this.toggleHasFocus(false)} + style={this.calculateThumbPositionStyle(this.upperValue)} + /> + + )} {showRange && ( Date: Sat, 26 Jan 2019 12:53:47 -0600 Subject: [PATCH 06/27] WIP: sass refactor using component boundaries --- src/components/form/range/_dual_range.scss | 30 ++ src/components/form/range/_index.scss | 9 +- src/components/form/range/_mixins.scss | 8 + src/components/form/range/_range.scss | 417 ------------------ .../form/range/_range_highlight.scss | 23 + src/components/form/range/_range_input.scss | 17 + src/components/form/range/_range_label.scss | 18 + src/components/form/range/_range_slider.scss | 107 +++++ src/components/form/range/_range_tooltip.scss | 87 ++++ src/components/form/range/_range_track.scss | 94 ++++ src/components/form/range/_range_wrapper.scss | 18 + src/components/form/range/dual_range.js | 20 +- src/components/form/range/range.js | 20 +- src/components/form/range/range_highlight.js | 15 +- src/components/form/range/range_input.js | 9 +- src/components/form/range/range_label.js | 16 +- src/components/form/range/range_slider.js | 43 +- src/components/form/range/range_tooltip.js | 11 +- src/components/form/range/range_track.js | 44 +- src/components/form/range/range_wrapper.js | 31 +- 20 files changed, 519 insertions(+), 518 deletions(-) create mode 100644 src/components/form/range/_dual_range.scss delete mode 100644 src/components/form/range/_range.scss create mode 100644 src/components/form/range/_range_highlight.scss create mode 100644 src/components/form/range/_range_input.scss create mode 100644 src/components/form/range/_range_label.scss create mode 100644 src/components/form/range/_range_slider.scss create mode 100644 src/components/form/range/_range_tooltip.scss create mode 100644 src/components/form/range/_range_track.scss create mode 100644 src/components/form/range/_range_wrapper.scss diff --git a/src/components/form/range/_dual_range.scss b/src/components/form/range/_dual_range.scss new file mode 100644 index 00000000000..428b0702ba5 --- /dev/null +++ b/src/components/form/range/_dual_range.scss @@ -0,0 +1,30 @@ +.euiDualRange { + &__slider { + @include euiRangeThumbPerBrowser { + visibility: hidden; + } + } +} + +.euiRangeThumb { + @include euiCustomControl($type: 'round'); + + @include euiRangeThumbStyle; + + content: ''; + position: absolute; + left: 0; + top: 50%; + margin-top: -($euiRangeThumbHeight / 2); + z-index: $euiZLevel2; + + pointer-events: none; + + &:focus { + @include euiCustomControlFocused; + } + + &--hasTicks { + top: 25%; + } +} diff --git a/src/components/form/range/_index.scss b/src/components/form/range/_index.scss index 7ef7e10548a..d05ef6cd7d2 100644 --- a/src/components/form/range/_index.scss +++ b/src/components/form/range/_index.scss @@ -1,3 +1,10 @@ @import 'variables'; @import 'mixins'; -@import 'range'; +@import 'range_highlight'; +@import 'range_input'; +@import 'range_label'; +@import 'range_slider'; +@import 'range_tooltip'; +@import 'range_track'; +@import 'range_wrapper'; +@import 'dual_range'; diff --git a/src/components/form/range/_mixins.scss b/src/components/form/range/_mixins.scss index dd9bdb9874d..9103408d8f0 100644 --- a/src/components/form/range/_mixins.scss +++ b/src/components/form/range/_mixins.scss @@ -14,6 +14,14 @@ &::-ms-fill-upper { @content; } } +@mixin euiRangeThumbStyle { + cursor: pointer; + border-color: $euiRangeThumbBorderColor; + padding: 0; + height: $euiRangeThumbHeight; + width: $euiRangeThumbWidth; +} + @mixin euiRangeThumbPerBrowser { &::-webkit-slider-thumb { @content; } &::-moz-range-thumb { @content; } diff --git a/src/components/form/range/_range.scss b/src/components/form/range/_range.scss deleted file mode 100644 index 9cee9ff7ab5..00000000000 --- a/src/components/form/range/_range.scss +++ /dev/null @@ -1,417 +0,0 @@ -/* - * 1. There's no way to target the layout of the extra input, so we must - * use the descendant selector to allow the width to shrink. - * 2. Align extra input slightly better with slider labels, in an IE compliant way. - * 3. Adjust vertical alignment of input based on extras - */ - -.euiRange__wrapper { - @include euiFormControlSize; - display: flex; - align-items: center; - - &--fullWidth { - max-width: 100%; - } - - &--disabled { - .euiRange__minLabel, - .euiRange__maxLabel, - .euiRange__inputWrapper { - opacity: .25; - } - } - - > .euiFormControlLayout { /* 1 */ - width: auto; - } -} - -.euiRange__inputWrapper { - height: 100%; // Don't overflow `euiRange__wrapper` - flex-grow: 1; - position: relative; // for positioning ticks/levels - align-self: flex-start; /* 3 */ -} - -.euiRange__minLabel, -.euiRange__maxLabel { - font-size: $euiFontSizeXS; -} - -.euiRange__minLabel { - margin-right: $euiSizeS; -} - -.euiRange__maxLabel { - margin-left: $euiSizeS; -} - -.euiRange__extraInput { - width: auto; - margin-left: $euiSize; - position: relative; /* 2 */ - top: -2px; /* 2 */ -} - -.euiRange__tick { - overflow-x: hidden; - text-overflow: ellipsis; - font-size: $euiFontSizeXS; - position: relative; - padding-top: $euiSize; - - &::before { - @include size($euiSizeXS); - - content: ''; - background-color: $euiColorDarkShade; - border-radius: 100%; - position: absolute; - top: 0; - left: calc(50% - #{($euiSizeXS/2)}); - } - - &-isCustom { - position: absolute; - transform: translateX(-50%); - } - - &:enabled:hover, - &:focus, - &--selected { - color: $euiColorPrimary; - } - - &--selected { - font-weight: $euiFontWeightMedium; - } - - &:disabled { - cursor: not-allowed; - } -} - -.euiRange__levels { - display: flex; - justify-content: stretch; - z-index: $euiZLevel0; -} - -[class*='euiRange__level--'] { - display: block; - height: 6px; - border-radius: 6px; - margin: 2px; -} - -// Modifier naming and colors. -$euiRangeLevelColors: ( - primary: $euiColorPrimary, - success: $euiColorSuccess, - warning: $euiColorWarning, - danger: $euiColorDanger, -); - -// Create level modifiers based upon the map. -@each $name, $color in $euiRangeLevelColors { - .euiRange__level--#{$name} { - background-color: transparentize($color, .7); - } -} - -.euiRange__range__progress { - height: 4px; - border-radius: 4px; - background-color: $euiRangeTrackColor; - - &--hasFocus { - background-color: $euiColorPrimary; - } -} - -.euiRange__value { - // Indentation for legibility in transition - // sass-lint:disable-block indentation - @include euiFontSizeS; - border: 1px solid transparentize($euiColorDarkestShade, .8); - position: absolute; - border-radius: $euiBorderRadius; - padding: ($euiSizeXS / 2) $euiSizeS; - background-color: tintOrShade($euiColorFullShade, 25%, 90%); - color: $euiColorGhost; - max-width: 256px; - z-index: $euiZLevel4; - top: ($euiFormControlHeight / 2) - 1px; - transition: - box-shadow $euiAnimSpeedNormal $euiAnimSlightResistance, - transform $euiAnimSpeedNormal $euiAnimSlightResistance; - - // Custom sizing - $arrowSize: $euiSizeM; - $arrowMinusSize: (($arrowSize / 2) - 1px) * -1; - - &::after, - &::before { - content: ''; - position: absolute; - bottom: -$arrowSize / 2; - left: 50%; - transform-origin: center; - background-color: tintOrShade($euiColorFullShade, 25%, 90%); - width: $arrowSize; - height: $arrowSize; - border-radius: 2px; - } - - &::before { - background-color: transparentize($euiColorDarkestShade, .8); - } - - // Positions the arrow - &.euiRange__value--right { - transform: translateX(0) translateY(-50%); - margin-left: $euiSizeL; - - &:before, - &:after { - bottom: 50%; - left: $arrowMinusSize; - transform: translateY(50%) rotateZ(45deg); - } - - &::before { - margin-left: -1px; - } - } - - &.euiRange__value--left { - transform: translateX(-100%) translateY(-50%); - margin-left: -$euiSizeL; - - &:before, - &:after { - bottom: 50%; - left: auto; - right: $arrowMinusSize; - transform: translateY(50%) rotateZ(45deg); - } - - &::before { - margin-right: -1px; - } - } -} - - -/* - * Positioning - */ - -.euiRange__wrapper--hasLevels { - .euiRange__levels { - position: absolute; - left: 0; - right: 0; - top: ($euiFormControlHeight / 2) + 2px; - } -} - -.euiRange__wrapper--hasRange { - .euiRange__range { - position: absolute; - left: 0; - width: 100%; - top: ($euiFormControlHeight / 2) - 2px; - z-index: $euiZLevel0; - overflow: hidden; - } -} - -.euiRange__wrapper--hasTicks { - .euiRange { - height: $euiFormControlHeight / 2; /* 3 */ - } - - .euiRange__levels { - top: ($euiFormControlHeight / 4) + 2px; - } - - .euiRange__range { - top: ($euiFormControlHeight / 4) - 2px; - left: 0; - } - - .euiRange__value { - top: ($euiFormControlHeight / 4) - 1px; - } - - .euiRange__extraInput { - margin-top: 0; - } - - .euiRange__ticks { - position: absolute; - left: ($euiRangeThumbWidth / 2); - right: ($euiRangeThumbWidth / 2); - top: $euiSizeS; - display: flex; - z-index: $euiZLevel1; - } -} - -.euiRange__valueWrapper { - // Keeps tooltip (value) aligned to percentage of actual slider - display: block; - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: calc(100% - #{$euiRangeThumbWidth}); - margin-left: $euiRangeThumbWidth / 2; -} - -/* - * Input Range Customization by browser - */ - -// The following code is inspired by... - -// Github: https://github.com/darlanrod/input-range-sass -// Author: Darlan Rod https://github.com/darlanrod -// Version 1.4.1 -// MIT License - -// It has been modified to fit the styling patterns of Kibana and -// to be more easily maintained / themeable going forward. - -.euiRange { - // Auto means the height isn't defined - height: $euiFormControlHeight; - appearance: none; - background: transparent; // Otherwise white in Chrome - width: 100%; // ensures the slider expands to fill flex display - position: relative; - z-index: $euiZLevel2; // stay above tick marks - cursor: pointer; // Keep cursor to full range bounds - - &:disabled { - cursor: not-allowed; - - // sass-lint:disable-block mixins-before-declarations - @include euiRangeThumbPerBrowser { - cursor: not-allowed; - border-color: $euiRangeThumbBorderColor; - background-color: $euiRangeThumbBorderColor; - box-shadow: none; - } - } - - &:focus { - @include euiRangeThumbPerBrowser { - @include euiCustomControlFocused; - } - - @include euiRangeTrackPerBrowser { - background-color: $euiColorPrimary; - border-color: $euiColorPrimary; - } - - ~ .euiRange__range .euiRange__range__progress { - background-color: $euiColorPrimary; - } - - ~ .euiRange__valueWrapper .euiRange__value { - @include euiBottomShadowMedium; - - &.euiRange__value--right { - transform: translateX(0) translateY(-50%) scale(1.1); - } - - &.euiRange__value--left { - transform: translateX(-100%) translateY(-50%) scale(1.1); - } - } - } - - @include euiRangeThumbPerBrowser { - @include euiCustomControl($type: 'round'); - - cursor: pointer; - border-color: $euiRangeThumbBorderColor; - padding: 0; - height: $euiRangeThumbHeight; - width: $euiRangeThumbWidth; - } - - @include euiRangeTrackPerBrowser { - @include euiRangeTrackSize; - - background: $euiRangeTrackColor; - border: $euiRangeTrackBorderWidth solid $euiRangeTrackBorderColor; - border-radius: $euiRangeTrackRadius; - } - - // Resets - - // Disable linter for these very unique vendor controls - // sass-lint:disable-block no-vendor-prefixes - &::-webkit-slider-thumb { - -webkit-appearance: none; - margin-top: ((-$euiRangeTrackBorderWidth * 2 + $euiRangeTrackHeight) / 2) - ($euiRangeThumbHeight / 2); - } - - &::-ms-thumb { - margin-top: 0; - } - - &::-ms-track { - @include euiRangeTrackSize; - - background: transparent; - border-color: transparent; - border-width: ($euiRangeThumbHeight / 2) 0; - color: transparent; - } - - &--dual { - @include euiRangeThumbPerBrowser { - opacity: 0; // display: hidden doesn't work. Find something better - } - } -} - -.euiRange__wrapper--hasRange .euiRange, -.euiRange__wrapper--hasTicks .euiRange { - @include euiRangeTrackPerBrowser { - background-color: transparentize($euiRangeTrackColor, .6); - border-color: transparentize($euiRangeTrackBorderColor, .6); - } -} - -.euiRange__thumb { - @include euiCustomControl($type: 'round'); - - cursor: pointer; - border-color: $euiRangeThumbBorderColor; - padding: 0; - height: $euiRangeThumbHeight; - width: $euiRangeThumbWidth; - - content: ''; - position: absolute; - left: 0; - top: 50%; - margin-top: -($euiRangeThumbHeight / 2); - z-index: $euiZLevel2; - - pointer-events: none; - - &:focus { - @include euiCustomControlFocused; - } - - &--hasTicks { - top: 25%; - } -} diff --git a/src/components/form/range/_range_highlight.scss b/src/components/form/range/_range_highlight.scss new file mode 100644 index 00000000000..cc1da38aa55 --- /dev/null +++ b/src/components/form/range/_range_highlight.scss @@ -0,0 +1,23 @@ +.euiRangeHighlight { + position: absolute; + left: 0; + width: 100%; + top: ($euiFormControlHeight / 2) - 2px; + z-index: $euiZLevel0; + overflow: hidden; + + &__progress { + height: 4px; + border-radius: 4px; + background-color: $euiRangeTrackColor; + + &--hasFocus { + background-color: $euiColorPrimary; + } + } + + &--hasTicks { + top: ($euiFormControlHeight / 4) - 2px; + left: 0; + } +} diff --git a/src/components/form/range/_range_input.scss b/src/components/form/range/_range_input.scss new file mode 100644 index 00000000000..eb3addf4fcd --- /dev/null +++ b/src/components/form/range/_range_input.scss @@ -0,0 +1,17 @@ +/* + * 1. Align extra input slightly better with slider labels, in an IE compliant way. + */ + +.euiRangeInput { + width: auto; + position: relative; /* 1 */ + top: -2px; /* 1 */ + + &--min { + margin-right: $euiSize; + } + + &--max { + margin-left: $euiSize; + } +} diff --git a/src/components/form/range/_range_label.scss b/src/components/form/range/_range_label.scss new file mode 100644 index 00000000000..0cd3a8a1eec --- /dev/null +++ b/src/components/form/range/_range_label.scss @@ -0,0 +1,18 @@ +.euiRangeLabel { + &--min, + &--max { + font-size: $euiFontSizeXS; + } + + &--min { + margin-right: $euiSizeS; + } + + &--max { + margin-left: $euiSizeS; + } + + &--isDisabled { + opacity: .25; + } +} diff --git a/src/components/form/range/_range_slider.scss b/src/components/form/range/_range_slider.scss new file mode 100644 index 00000000000..6884431b1d4 --- /dev/null +++ b/src/components/form/range/_range_slider.scss @@ -0,0 +1,107 @@ +/* + * Input Range Customization by browser + */ + +// The following code is inspired by... + +// Github: https://github.com/darlanrod/input-range-sass +// Author: Darlan Rod https://github.com/darlanrod +// Version 1.4.1 +// MIT License + +// It has been modified to fit the styling patterns of Kibana and +// to be more easily maintained / themeable going forward. + +.euiRangeSlider { + // Auto means the height isn't defined + height: $euiFormControlHeight; + appearance: none; + background: transparent; // Otherwise white in Chrome + width: 100%; // ensures the slider expands to fill flex display + position: relative; + z-index: $euiZLevel2; // stay above tick marks + cursor: pointer; // Keep cursor to full range bounds + + &:disabled { + cursor: not-allowed; + + // sass-lint:disable-block mixins-before-declarations + @include euiRangeThumbPerBrowser { + cursor: not-allowed; + border-color: $euiRangeThumbBorderColor; + background-color: $euiRangeThumbBorderColor; + box-shadow: none; + } + } + + &:focus { + @include euiRangeThumbPerBrowser { + @include euiCustomControlFocused; + } + + @include euiRangeTrackPerBrowser { + background-color: $euiColorPrimary; + border-color: $euiColorPrimary; + } + + ~ .euiRangeHighlight .euiRangeHighlight__progress { + background-color: $euiColorPrimary; + } + + ~ .euiRangeTooltip .euiRangeTooltip__value { + @include euiBottomShadowMedium; + + &.euiRangeTooltip__value--right { + transform: translateX(0) translateY(-50%) scale(1.1); + } + + &.euiRangeTooltip__value--left { + transform: translateX(-100%) translateY(-50%) scale(1.1); + } + } + } + + @include euiRangeThumbPerBrowser { + @include euiCustomControl($type: 'round'); + + @include euiRangeThumbStyle; + } + + @include euiRangeTrackPerBrowser { + @include euiRangeTrackSize; + + background: $euiRangeTrackColor; + border: $euiRangeTrackBorderWidth solid $euiRangeTrackBorderColor; + border-radius: $euiRangeTrackRadius; + + background-color: transparentize($euiRangeTrackColor, .6); + border-color: transparentize($euiRangeTrackBorderColor, .6); + } + + // Resets + + // Disable linter for these very unique vendor controls + // sass-lint:disable-block no-vendor-prefixes + &::-webkit-slider-thumb { + -webkit-appearance: none; + margin-top: ((-$euiRangeTrackBorderWidth * 2 + $euiRangeTrackHeight) / 2) - ($euiRangeThumbHeight / 2); + } + + &::-ms-thumb { + margin-top: 0; + } + + &::-ms-track { + @include euiRangeTrackSize; + + background: transparent; + border-color: transparent; + border-width: ($euiRangeThumbHeight / 2) 0; + color: transparent; + } + + // States + &--hasTicks { + height: $euiFormControlHeight / 2; // Adjust vertical alignment of input based on extras + } +} diff --git a/src/components/form/range/_range_tooltip.scss b/src/components/form/range/_range_tooltip.scss new file mode 100644 index 00000000000..ebbe92dae4e --- /dev/null +++ b/src/components/form/range/_range_tooltip.scss @@ -0,0 +1,87 @@ +.euiRangeTooltip { + // Keeps tooltip (value) aligned to percentage of actual slider + display: block; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: calc(100% - #{$euiRangeThumbWidth}); + margin-left: $euiRangeThumbWidth / 2; +} + +.euiRangeTooltip__value { + // Indentation for legibility in transition + // sass-lint:disable-block indentation + @include euiFontSizeS; + border: 1px solid transparentize($euiColorDarkestShade, .8); + position: absolute; + border-radius: $euiBorderRadius; + padding: ($euiSizeXS / 2) $euiSizeS; + background-color: tintOrShade($euiColorFullShade, 25%, 90%); + color: $euiColorGhost; + max-width: 256px; + z-index: $euiZLevel4; + top: ($euiFormControlHeight / 2) - 1px; + transition: + box-shadow $euiAnimSpeedNormal $euiAnimSlightResistance, + transform $euiAnimSpeedNormal $euiAnimSlightResistance; + + // Custom sizing + $arrowSize: $euiSizeM; + $arrowMinusSize: (($arrowSize / 2) - 1px) * -1; + + &::after, + &::before { + content: ''; + position: absolute; + bottom: -$arrowSize / 2; + left: 50%; + transform-origin: center; + background-color: tintOrShade($euiColorFullShade, 25%, 90%); + width: $arrowSize; + height: $arrowSize; + border-radius: 2px; + } + + &::before { + background-color: transparentize($euiColorDarkestShade, .8); + } + + // Positions the arrow + &.euiRangeTooltip__value--right { + transform: translateX(0) translateY(-50%); + margin-left: $euiSizeL; + + &:before, + &:after { + bottom: 50%; + left: $arrowMinusSize; + transform: translateY(50%) rotateZ(45deg); + } + + &::before { + margin-left: -1px; + } + } + + &.euiRangeTooltip__value--left { + transform: translateX(-100%) translateY(-50%); + margin-left: -$euiSizeL; + + &:before, + &:after { + bottom: 50%; + left: auto; + right: $arrowMinusSize; + transform: translateY(50%) rotateZ(45deg); + } + + &::before { + margin-right: -1px; + } + } + + &--hasTicks { + top: ($euiFormControlHeight / 4) - 1px; + } +} diff --git a/src/components/form/range/_range_track.scss b/src/components/form/range/_range_track.scss new file mode 100644 index 00000000000..e613a2de664 --- /dev/null +++ b/src/components/form/range/_range_track.scss @@ -0,0 +1,94 @@ +.euiRangeTrack { + height: 100%; // Don't overflow `euiRangeWrapper` + flex-grow: 1; + position: relative; // for positioning ticks/levels + align-self: flex-start; // Adjust vertical alignment of input based on extras + + &--disabled { + opacity: .25; + } +} + +.euiRangeTicks { + position: absolute; + left: ($euiRangeThumbWidth / 2); + right: ($euiRangeThumbWidth / 2); + top: $euiSizeS; + display: flex; + z-index: $euiZLevel1; +} + +.euiRangeTick { + overflow-x: hidden; + text-overflow: ellipsis; + font-size: $euiFontSizeXS; + position: relative; + padding-top: $euiSize; + + &::before { + @include size($euiSizeXS); + + content: ''; + background-color: $euiColorDarkShade; + border-radius: 100%; + position: absolute; + top: 0; + left: calc(50% - #{($euiSizeXS/2)}); + } + + &--isCustom { + position: absolute; + transform: translateX(-50%); + } + + &:enabled:hover, + &:focus, + &--selected { + color: $euiColorPrimary; + } + + &--selected { + font-weight: $euiFontWeightMedium; + } + + &:disabled { + cursor: not-allowed; + } +} + +.euiRangeLevels { + display: flex; + justify-content: stretch; + z-index: $euiZLevel0; + + position: absolute; + left: 0; + right: 0; + top: ($euiFormControlHeight / 2) + 2px; + + &--hasTicks { + top: ($euiFormControlHeight / 4) + 2px; + } +} + +.euiRangeLevel { + display: block; + height: 6px; + border-radius: 6px; + margin: 2px; +} + +// Modifier naming and colors. +$euiRangeLevelColors: ( + primary: $euiColorPrimary, + success: $euiColorSuccess, + warning: $euiColorWarning, + danger: $euiColorDanger, +); + +// Create level modifiers based upon the map. +@each $name, $color in $euiRangeLevelColors { + .euiRangeLevel--#{$name} { + background-color: transparentize($color, .7); + } +} diff --git a/src/components/form/range/_range_wrapper.scss b/src/components/form/range/_range_wrapper.scss new file mode 100644 index 00000000000..a5427154129 --- /dev/null +++ b/src/components/form/range/_range_wrapper.scss @@ -0,0 +1,18 @@ +/* + * 1. There's no way to target the layout of the extra input, so we must + * use the descendant selector to allow the width to shrink. + */ + +.euiRangeWrapper { + @include euiFormControlSize; + display: flex; + align-items: center; + + &--fullWidth { + max-width: 100%; + } + + > .euiFormControlLayout { /* 1 */ + width: auto; + } +} diff --git a/src/components/form/range/dual_range.js b/src/components/form/range/dual_range.js index 88190fb15d1..91cc038b035 100644 --- a/src/components/form/range/dual_range.js +++ b/src/components/form/range/dual_range.js @@ -146,20 +146,16 @@ export class EuiDualRange extends Component { ...rest } = this.props; - const rangeClasses = classNames('euiRange', 'euiRange--dual', className); + const rangeClasses = classNames('euiDualRange__slider', className); return ( {showInput && ( )} - {showLabels && {min}} + {showLabels && {min}} @@ -231,6 +228,7 @@ export class EuiDualRange extends Component { {showRange && ( )} - {showLabels && {max}} + {showLabels && {max}} {showInput && ( { const classes = classNames( - 'euiRange__thumb', + 'euiRangeThumb', { - 'euiRange__thumb--hasTicks': showTicks + 'euiRangeThumb--hasTicks': showTicks }, ); return ( diff --git a/src/components/form/range/range.js b/src/components/form/range/range.js index 6f0e8a989e7..84795cba770 100644 --- a/src/components/form/range/range.js +++ b/src/components/form/range/range.js @@ -1,6 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import classNames from 'classnames'; import { EuiRangeHighlight } from './range_highlight'; import { EuiRangeInput } from './range_input'; @@ -36,20 +35,12 @@ export const EuiRange = ({ ...rest }) => { - const classes = classNames('euiRange', className); - return ( - {showLabels && {min}} + {showLabels && {min}} @@ -83,11 +75,13 @@ export const EuiRange = ({ max={max} min={min} name={name} + showTicks={showTicks} /> )} {showRange && ( )} - {showLabels && {max}} + {showLabels && {max}} {showInput && ( { +export const EuiRangeHighlight = ({ hasFocus, showTicks, lowerValue, upperValue, max, min }) => { // Calculate the width the range based on value // const rangeWidth = (value - min) / (max - min); const leftPosition = (lowerValue - min) / (max - min); @@ -12,19 +12,24 @@ export const EuiRangeHighlight = ({ hasFocus, lowerValue, upperValue, max, min } width: `${rangeWidth * 100}%` }; - const classes = classNames('euiRange__range__progress', { - 'euiRange__range__progress--hasFocus': hasFocus + const classes = classNames('euiRangeHighlight', { + 'euiRangeHighlight--hasTicks': showTicks + }); + + const progressClasses = classNames('euiRangeHighlight__progress', { + 'euiRangeHighlight__progress--hasFocus': hasFocus }); return ( -
-
+
+
); }; EuiRangeHighlight.propTypes = { hasFocus: PropTypes.bool, + showTicks: PropTypes.bool, lowerValue: PropTypes.number.isRequired, upperValue: PropTypes.number.isRequired, max: PropTypes.number.isRequired, diff --git a/src/components/form/range/range_input.js b/src/components/form/range/range_input.js index 353ad6fed4c..bfd96cbcee4 100644 --- a/src/components/form/range/range_input.js +++ b/src/components/form/range/range_input.js @@ -12,6 +12,7 @@ export const EuiRangeInput = ({ compressed, onChange, name, + side, ...rest }) => { @@ -20,7 +21,7 @@ export const EuiRangeInput = ({ return ( ( - -); +export const EuiRangeLabel = ({ children, disabled, side }) => { + const classes = classNames('euiRangeLabel', `euiRangeLabel--${side}`, { + 'euiRangeLabel--isDisabled': disabled + }); + return ( + + ); +}; EuiRangeLabel.propTypes = { side: PropTypes.string.isRequired diff --git a/src/components/form/range/range_slider.js b/src/components/form/range/range_slider.js index 1203c04fa6e..5c335a7e951 100644 --- a/src/components/form/range/range_slider.js +++ b/src/components/form/range/range_slider.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; export const EuiRangeSlider = React.forwardRef(({ className, @@ -13,25 +14,31 @@ export const EuiRangeSlider = React.forwardRef(({ tabIndex, value, style, + showTicks, ...rest -}, ref) => ( - -)); +}, ref) => { + const classes = classNames('euiRangeSlider', { + 'euiRangeSlider--hasTicks': showTicks + }, className); + return ( + + ); +}); EuiRangeSlider.propTypes = { id: PropTypes.string, diff --git a/src/components/form/range/range_tooltip.js b/src/components/form/range/range_tooltip.js index 4ab13cbc1fa..14c926cd853 100644 --- a/src/components/form/range/range_tooltip.js +++ b/src/components/form/range/range_tooltip.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -export const EuiRangeTooltip = ({ value, valueAppend, max, min, name }) => { +export const EuiRangeTooltip = ({ value, valueAppend, max, min, name, showTicks }) => { // Calculate the left position based on value const decimal = (value - min) / (max - min); // Must be between 0-100% @@ -20,12 +20,15 @@ export const EuiRangeTooltip = ({ value, valueAppend, max, min, name }) => { // Change left/right position based on value (half way point) const valueClasses = classNames( - 'euiRange__value', - `euiRange__value--${valuePositionSide}`, + 'euiRangeTooltip__value', + `euiRangeTooltip__value--${valuePositionSide}`, + { + 'euiRangeTooltip__value--hasTicks': showTicks + } ); return ( -
+
{value}{valueAppend} diff --git a/src/components/form/range/range_track.js b/src/components/form/range/range_track.js index 2a2719b6ea1..8d74a3c462c 100644 --- a/src/components/form/range/range_track.js +++ b/src/components/form/range/range_track.js @@ -60,14 +60,19 @@ export class EuiRangeTrack extends Component { } } + const trackClasses = classNames('euiRangeTrack', { + 'euiRangeTrack--disabled': disabled + }); + return ( -
+
{children} {!!levels.length && ( )} {showTicks && ( @@ -118,7 +123,7 @@ const EuiRangeTicks = ({ disabled, onChange, ticks, tickObject, value, max }) => const ticksStyle = !!ticks ? undefined : { margin: `0 ${tickObject.percentageWidth / -2}%`, left: 0, right: 0 }; return ( -
+
{tickObject.sequence.map((tickValue) => { const tickStyle = {}; let customTick; @@ -135,10 +140,10 @@ const EuiRangeTicks = ({ disabled, onChange, ticks, tickObject, value, max }) => } const tickClasses = classNames( - 'euiRange__tick', + 'euiRangeTick', { - 'euiRange__tick--selected': value === tickValue, - 'euiRange__tick-isCustom': customTick, + 'euiRangeTick--selected': value === tickValue, + 'euiRangeTick--isCustom': customTick, } ); @@ -162,15 +167,20 @@ const EuiRangeTicks = ({ disabled, onChange, ticks, tickObject, value, max }) => ); }; -const EuiRangeLevels = ({ levels, max, min }) => ( -
- {levels.map((level, index) => { - const range = level.max - level.min; - const width = (range / (max - min)) * 100; - - return ( - - ); - })} -
-); +const EuiRangeLevels = ({ levels, max, min, showTicks }) => { + const classes = classNames('euiRangeLevels', { + 'euiRangeLevels--hasTicks': showTicks + }); + return ( +
+ {levels.map((level, index) => { + const range = level.max - level.min; + const width = (range / (max - min)) * 100; + + return ( + + ); + })} +
+ ); +}; diff --git a/src/components/form/range/range_wrapper.js b/src/components/form/range/range_wrapper.js index 29157b1dab9..49a11589ce8 100644 --- a/src/components/form/range/range_wrapper.js +++ b/src/components/form/range/range_wrapper.js @@ -6,28 +6,16 @@ export const LEVEL_COLORS = ['primary', 'success', 'warning', 'danger']; export const EuiRangeWrapper = ({ children, - compressed, - disabled, - fullWidth, - showLabels, - showTicks, - levels, - showRange, - showValue, + className, + fullWidth }) => { const classes = classNames( - 'euiRange__wrapper', + 'euiRangeWrapper', { - 'euiRange__wrapper--fullWidth': fullWidth, - 'euiRange__wrapper--compressed': compressed, - 'euiRange__wrapper--disabled': disabled, - 'euiRange__wrapper--hasLabels': showLabels, - 'euiRange__wrapper--hasLevels': levels.length, - 'euiRange__wrapper--hasRange': showRange, - 'euiRange__wrapper--hasTicks': showTicks, - 'euiRange__wrapper--hasValue': showValue, + 'euiRangeWrapper--fullWidth': fullWidth }, + className ); return ( @@ -38,12 +26,5 @@ export const EuiRangeWrapper = ({ }; EuiRangeWrapper.propTypes = { - fullWidth: PropTypes.bool, - compressed: PropTypes.bool, - showLabels: PropTypes.bool, - showInput: PropTypes.bool, - showTicks: PropTypes.bool, - levels: PropTypes.array, - showRange: PropTypes.bool, - showValue: PropTypes.bool, + fullWidth: PropTypes.bool }; From 1fbd0b509ff8cffabc61103c0a4dcb79aa3236d1 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Mon, 28 Jan 2019 09:49:51 -0600 Subject: [PATCH 07/27] update euiRange tests; create euiDualRange tests --- .../__snapshots__/dual_range.test.js.snap | 288 ++++++++++++++++++ .../range/__snapshots__/range.test.js.snap | 98 +++--- src/components/form/range/_range_slider.scss | 2 +- src/components/form/range/dual_range.js | 8 +- src/components/form/range/dual_range.test.js | 123 ++++++++ src/components/form/range/range.js | 1 + src/components/form/range/range_tooltip.js | 2 +- 7 files changed, 467 insertions(+), 55 deletions(-) create mode 100644 src/components/form/range/__snapshots__/dual_range.test.js.snap create mode 100644 src/components/form/range/dual_range.test.js diff --git a/src/components/form/range/__snapshots__/dual_range.test.js.snap b/src/components/form/range/__snapshots__/dual_range.test.js.snap new file mode 100644 index 00000000000..13f511d25bf --- /dev/null +++ b/src/components/form/range/__snapshots__/dual_range.test.js.snap @@ -0,0 +1,288 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiDualRange allows value prop to accept numbers 1`] = ` +
+
+ +
+
+`; + +exports[`EuiDualRange is rendered 1`] = ` +
+
+ +
+
+`; + +exports[`EuiDualRange props compressed should render 1`] = ` +
+
+ +
+
+`; + +exports[`EuiDualRange props fullWidth should render 1`] = ` +
+
+ +
+
+`; + +exports[`EuiDualRange props inputs should render 1`] = ` +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+`; + +exports[`EuiDualRange props labels should render 1`] = ` +
+ +
+ +
+ +
+`; + +exports[`EuiDualRange props levels should render 1`] = ` +
+
+ +
+ + +
+
+
+`; + +exports[`EuiDualRange props range should render 1`] = ` +
+
+ +
+
+
+
+
+`; + +exports[`EuiDualRange props ticks should render 1`] = ` +
+
+ +
+ + + + + +
+
+
+`; diff --git a/src/components/form/range/__snapshots__/range.test.js.snap b/src/components/form/range/__snapshots__/range.test.js.snap index de45d8e5ab5..3f8922888c6 100644 --- a/src/components/form/range/__snapshots__/range.test.js.snap +++ b/src/components/form/range/__snapshots__/range.test.js.snap @@ -2,23 +2,23 @@ exports[`EuiRange allows value prop to accept a number 1`] = `
8 @@ -30,14 +30,14 @@ exports[`EuiRange allows value prop to accept a number 1`] = ` exports[`EuiRange is rendered 1`] = `
@@ -155,26 +155,26 @@ exports[`EuiRange props labels should render 1`] = ` exports[`EuiRange props levels should render 1`] = `
@@ -184,23 +184,23 @@ exports[`EuiRange props levels should render 1`] = ` exports[`EuiRange props range should render 1`] = `
@@ -209,23 +209,23 @@ exports[`EuiRange props range should render 1`] = ` exports[`EuiRange props ticks should render 1`] = `
`; @@ -56,6 +72,14 @@ exports[`EuiDualRange props compressed should render 1`] = ` tabindex="-1" type="range" /> +
+
+
`; @@ -74,6 +98,14 @@ exports[`EuiDualRange props fullWidth should render 1`] = ` tabindex="-1" type="range" /> +
+
+
`; @@ -114,6 +146,14 @@ exports[`EuiDualRange props inputs should render 1`] = ` type="range" value="0,8" /> +
+
+
+
+
+