diff --git a/CHANGELOG.md b/CHANGELOG.md index 621981113..16f514e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- Ability to add tooltips to `Slider` and `RangeSlider`, which can be visible always or on hover. Tooltips also take a position argument. [#564](https://github.com/plotly/dash-core-components/pull/564) + ### Fixed - Fixed `min_date_allowed` and `max_date_allowed` bug in `DatePickerRange` [#551]https://github.com/plotly/dash-core-components/issues/551) - Fixed unwanted `resize()` calls on unmounted `Graph`s [#534](https://github.com/plotly/dash-core-components/issues/534) diff --git a/package.json b/package.json index 107bf1a2d..0c2bd605e 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "moment": "^2.20.1", "prop-types": "^15.6.0", "ramda": "^0.24.1", - "rc-slider": "^8.3.1", + "rc-slider": "^8.6.11", "react-addons-shallow-compare": "^15.6.0", "react-dates": "^20.1.0", "react-docgen": "^3.0.0", diff --git a/src/components/RangeSlider.react.js b/src/components/RangeSlider.react.js index 4352554ce..629809152 100644 --- a/src/components/RangeSlider.react.js +++ b/src/components/RangeSlider.react.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {omit} from 'ramda'; -import {Range} from 'rc-slider'; +import {Range, createSliderWithTooltip} from 'rc-slider'; /** * A double slider with two handles. @@ -11,6 +11,9 @@ export default class RangeSlider extends Component { constructor(props) { super(props); this.propsToState = this.propsToState.bind(this); + this.DashSlider = props.tooltip + ? createSliderWithTooltip(Range) + : Range; } propsToState(newProps) { @@ -18,6 +21,11 @@ export default class RangeSlider extends Component { } componentWillReceiveProps(newProps) { + if (newProps.tooltip !== this.props.tooltip) { + this.DashSlider = newProps.tooltip + ? createSliderWithTooltip(Range) + : Range; + } this.propsToState(newProps); } @@ -31,11 +39,27 @@ export default class RangeSlider extends Component { id, loading_state, setProps, + tooltip, updatemode, vertical, } = this.props; const value = this.state.value; + let tipProps; + if (tooltip && tooltip.always_visible) { + /** + * clone `tooltip` but with renamed key `always_visible` -> `visible` + * the rc-tooltip API uses `visible`, but `always_visible is more semantic + * assigns the new (renamed) key to the old key and deletes the old key + */ + tipProps = Object.assign(tooltip, { + visible: tooltip.always_visible, + }); + delete tipProps.always_visible; + } else { + tipProps = tooltip; + } + return (
- { if (updatemode === 'drag') { setProps({value}); @@ -58,6 +82,7 @@ export default class RangeSlider extends Component { setProps({value}); } }} + tipProps={tipProps} value={value} {...omit( ['className', 'value', 'setProps', 'updatemode'], @@ -152,6 +177,31 @@ RangeSlider.propTypes = { */ pushable: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]), + tooltip: PropTypes.exact({ + /** + * Determines whether tooltips should always be visible + * (as opposed to the default, visible on hover) + */ + always_visible: PropTypes.bool, + + /** + * Determines the placement of tooltips + * See https://github.com/react-component/tooltip#api + * top/bottom{*} sets the _origin_ of the tooltip, so e.g. `topLeft` will + * in reality appear to be on the top right of the handle + */ + placement: PropTypes.oneOf([ + 'left', + 'right', + 'top', + 'bottom', + 'topLeft', + 'topRight', + 'bottomLeft', + 'bottomRight', + ]), + }), + /** * Value by which increments or decrements are made */ diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index 6397f0d3a..760740cdc 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -1,5 +1,5 @@ import React, {Component} from 'react'; -import ReactSlider from 'rc-slider'; +import ReactSlider, {createSliderWithTooltip} from 'rc-slider'; import PropTypes from 'prop-types'; import {omit} from 'ramda'; import './css/rc-slider@6.1.2.css'; @@ -11,6 +11,9 @@ export default class Slider extends Component { constructor(props) { super(props); this.propsToState = this.propsToState.bind(this); + this.DashSlider = props.tooltip + ? createSliderWithTooltip(ReactSlider) + : ReactSlider; } propsToState(newProps) { @@ -18,6 +21,11 @@ export default class Slider extends Component { } componentWillReceiveProps(newProps) { + if (newProps.tooltip !== this.props.tooltip) { + this.DashSlider = newProps.tooltip + ? createSliderWithTooltip(ReactSlider) + : ReactSlider; + } this.propsToState(newProps); } @@ -31,11 +39,27 @@ export default class Slider extends Component { id, loading_state, setProps, + tooltip, updatemode, vertical, } = this.props; const value = this.state.value; + let tipProps; + if (tooltip && tooltip.always_visible) { + /** + * clone `tooltip` but with renamed key `always_visible` -> `visible` + * the rc-tooltip API uses `visible`, but `always_visible is more semantic + * assigns the new (renamed) key to the old key and deletes the old key + */ + tipProps = Object.assign(tooltip, { + visible: tooltip.always_visible, + }); + delete tipProps.always_visible; + } else { + tipProps = tooltip; + } + return (
- { if (updatemode === 'drag') { setProps({value}); @@ -58,6 +82,7 @@ export default class Slider extends Component { setProps({value}); } }} + tipProps={tooltip} value={value} {...omit( ['className', 'setProps', 'updatemode', 'value'], @@ -133,6 +158,31 @@ Slider.propTypes = { */ max: PropTypes.number, + tooltip: PropTypes.exact({ + /** + * Determines whether tooltips should always be visible + * (as opposed to the default, visible on hover) + */ + always_visible: PropTypes.bool, + + /** + * Determines the placement of tooltips + * See https://github.com/react-component/tooltip#api + * top/bottom{*} sets the _origin_ of the tooltip, so e.g. `topLeft` will + * in reality appear to be on the top right of the handle + */ + placement: PropTypes.oneOf([ + 'left', + 'right', + 'top', + 'bottom', + 'topLeft', + 'topRight', + 'bottomLeft', + 'bottomRight', + ]), + }), + /** * Value by which increments or decrements are made */