From 4c64566749cdc8efc871226528ba3fb7e7c65222 Mon Sep 17 00:00:00 2001 From: Will Burgess Date: Wed, 12 Jun 2019 16:30:31 -0400 Subject: [PATCH] Slider tooltips (#564) * Bump rc-slider minor version This is so we can use the `visible` prop in `tipProps`, introduced in `rc-slider` 8.6.1 (undocumented in the `rc-slider` repo's HISTORY.md) See https://github.com/react-component/slider/pull/383 * Add tooltip (basic `tipProps` alias) to Slider and RangeSlider Adds support for hoverable and persistent (always visible) tooltips * Remove test css class + undo loading_state propTypes reshuffle * Add propTypes variations template * Put `createSliderWithTooltip` in constructor * Change 'visible' tooltip propType to bool * Fix Slider.react.js :spaghetti: from RangeSlider * Slider tooltip propTypes: `oneOfType[]` -> single `exact` * Move Slider instantiation from constructor to render method * Spacing * Add changelog entry for Slider/RangeSlider tooltips * Fix markdown syntax + parens * Prettier (comment + ternary formatting) * `visible` -> `always_visible`; `position` -> `placement` * Add DashSlider conditional in componentWillReceiveProps --- packages/dash-core-components/CHANGELOG.md | 3 ++ packages/dash-core-components/package.json | 2 +- .../src/components/RangeSlider.react.js | 54 ++++++++++++++++++- .../src/components/Slider.react.js | 54 ++++++++++++++++++- 4 files changed, 108 insertions(+), 5 deletions(-) diff --git a/packages/dash-core-components/CHANGELOG.md b/packages/dash-core-components/CHANGELOG.md index 6219811130..16f514e1b4 100644 --- a/packages/dash-core-components/CHANGELOG.md +++ b/packages/dash-core-components/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/packages/dash-core-components/package.json b/packages/dash-core-components/package.json index 107bf1a2d3..0c2bd605e9 100644 --- a/packages/dash-core-components/package.json +++ b/packages/dash-core-components/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/packages/dash-core-components/src/components/RangeSlider.react.js b/packages/dash-core-components/src/components/RangeSlider.react.js index 4352554cea..6298091521 100644 --- a/packages/dash-core-components/src/components/RangeSlider.react.js +++ b/packages/dash-core-components/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/packages/dash-core-components/src/components/Slider.react.js b/packages/dash-core-components/src/components/Slider.react.js index 6397f0d3a6..760740cdc0 100644 --- a/packages/dash-core-components/src/components/Slider.react.js +++ b/packages/dash-core-components/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 */