From a05b71319596596f6d939764e593f6423167c1e3 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Thu, 23 Jan 2020 16:53:17 -0500 Subject: [PATCH 01/33] Start with new RangeControl component --- packages/components/src/range-control/rail.js | 48 +++++ .../src/range-control/range-control.js | 144 +++++++++++++ .../src/range-control/stories/index.js | 11 + .../styles/range-control-styles.js | 202 ++++++++++++++++++ .../components/src/range-control/tooltip.js | 55 +++++ .../components/src/utils/colors-values.js | 2 +- 6 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 packages/components/src/range-control/rail.js create mode 100644 packages/components/src/range-control/range-control.js create mode 100644 packages/components/src/range-control/styles/range-control-styles.js create mode 100644 packages/components/src/range-control/tooltip.js diff --git a/packages/components/src/range-control/rail.js b/packages/components/src/range-control/rail.js new file mode 100644 index 00000000000000..1fe02eeaec9b89 --- /dev/null +++ b/packages/components/src/range-control/rail.js @@ -0,0 +1,48 @@ +/** + * Internal dependencies + */ +import { Mark, Rail } from './styles/range-control-styles'; + +export default function RangeRail( { + marks = false, + min = 0, + max = 100, + step = 1, + value = 0, +} ) { + const marksData = useMarks( { min, max, step, value } ); + const LastMark = Mark; + + return ( + + { marks && ( + <> + { marksData.map( ( mark ) => ( + + ); +} + +function useMarks( { min = 0, max = 100, step = 1, value = 0 } ) { + const markCount = ( max - min ) / step; + + const marks = [ ...Array( markCount ) ].map( ( _, index ) => { + const key = `mark-${ index }`; + const isFilled = value >= index; + const style = { + left: `${ index / markCount * 100 }%`, + background: isFilled ? 'currentColor' : undefined, + }; + + return { + key, + style, + }; + } ); + + return marks; +} diff --git a/packages/components/src/range-control/range-control.js b/packages/components/src/range-control/range-control.js new file mode 100644 index 00000000000000..0eb84d381cbd31 --- /dev/null +++ b/packages/components/src/range-control/range-control.js @@ -0,0 +1,144 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useRef, useEffect, useState, forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import BaseControl from '../base-control'; +import { color } from '../utils/colors'; +import RangeRail from './rail'; +import RangeTooltip from './tooltip'; +import { Root, Track, ThumbWrapper, Thumb, InputRange } from './styles/range-control-styles'; + +export const RangeControlNext = forwardRef( ( { + alwaysShowTooltip = false, + className, + color: colorProp = color( 'blue.wordpress.700' ), + disableToolTip = false, + disabled, + help, + id, + label, + marks = false, + min = 0, + max = 100, + onChange = noop, + step = 1, + value: valueProp = 0, + renderTooltipContent = ( v ) => v, + tooltipZIndex = 100, +}, ref ) => { + const [ value, setValue ] = useControlledRangeValue( valueProp ); + const [ showTooltip, setShowTooltip ] = useState( false ); + + const inputRef = useRef(); + + const handleOnChange = ( event ) => { + const newValue = event.target.value; + // If the input value is invalid temporarily save it to the state, + // without calling on change. + if ( ! event.target.checkValidity() ) { + return; + } + + setValue( newValue ); + + onChange( ( newValue === '' ) ? + undefined : + parseFloat( newValue ) + ); + }; + + const isFocused = showTooltip; + const fillValue = `${ parseFloat( value ) / max * 100 }%`; + + const handleShowTooltip = () => { + setShowTooltip( true ); + }; + + const handleHideTooltip = () => { + setShowTooltip( false ); + }; + + const setRef = ( nodeRef ) => { + inputRef.current = nodeRef; + if ( ref ) { + ref( nodeRef ); + } + }; + + const classes = classnames( 'components-range-control', className ); + const describedBy = !! help ? `${ id }__help` : undefined; + const enableTooltip = ! disableToolTip; + + return ( + + + + + + + { enableTooltip && ( + + ) } + + + + + ); +} ); + +function useControlledRangeValue( { value: valueProp = 0 } ) { + const [ value, setValue ] = useState( valueProp ); + const valueRef = useRef( valueProp ); + + useEffect( () => { + if ( valueRef.current !== valueProp ) { + setValue( valueProp ); + valueRef.current = valueProp; + } + }, [ valueRef, valueProp, setValue ] ); + + return [ value, setValue ]; +} diff --git a/packages/components/src/range-control/stories/index.js b/packages/components/src/range-control/stories/index.js index 219dad5d42a46b..a9a603e80eb774 100644 --- a/packages/components/src/range-control/stories/index.js +++ b/packages/components/src/range-control/stories/index.js @@ -1,6 +1,7 @@ /** * External dependencies */ +import styled from '@emotion/styled'; import { number, text } from '@storybook/addon-knobs'; /** @@ -12,6 +13,7 @@ import { useState } from '@wordpress/element'; * Internal dependencies */ import RangeControl from '../'; +import { RangeControlNext } from '../range-control'; export default { title: 'Components/RangeControl', component: RangeControl }; @@ -112,3 +114,12 @@ export const withReset = () => { /> ); }; + +export const next = () => { + return ; +}; + +const Wrapper = styled.div` + padding: 20px; +` +; diff --git a/packages/components/src/range-control/styles/range-control-styles.js b/packages/components/src/range-control/styles/range-control-styles.js new file mode 100644 index 00000000000000..bcc53dcefde97f --- /dev/null +++ b/packages/components/src/range-control/styles/range-control-styles.js @@ -0,0 +1,202 @@ +/** + * External dependencies + */ +import { css } from '@emotion/core'; +import styled from '@emotion/styled'; + +/** + * Internal dependencies + */ +import { color } from '../../utils/colors'; + +const rootColor = ( { color: colorProp } ) => css( { color: colorProp } ); + +export const Root = styled.span` + -webkit-tap-highlight-color: transparent; + box-sizing: border-box; + cursor: pointer; + display: inline-block; + height: 2px; + padding: 14px 0; + position: relative; + touch-action: none; + width: 100%; + + ${ rootColor }; +`; + +export const Rail = styled.span` + background-color: ${ color( 'lightGray.600' ) }; + box-sizing: border-box; + left: 0; + pointer-events: none; + right: 0; + border-radius: 1px; + display: block; + height: 2px; + position: absolute; + margin-top: -1px; +`; + +export const Track = styled.span` + background-color: currentColor; + border-radius: 1px; + box-sizing: border-box; + height: 2px; + pointer-events: none; + display: block; + position: absolute; + margin-top: -1px; +`; + +export const Mark = styled.span` + background-color: ${ color( 'lightGray.600' ) }; + height: 8px; + left: 0; + position: absolute; + top: -3px; + width: 1px; +`; + +export const ThumbWrapper = styled.span` + align-items: center; + margin-left: -10px; + margin-top: -10px; + pointer-events: none; + width: 20px; + box-sizing: border-box; + display: flex; + height: 20px; + justify-content: center; + outline: 0; + position: absolute; +`; + +const handleReducedMotion = () => { + return css` + @media (prefers-reduced-motion: reduce) { + transition: none !important; + } + `; +}; + +const thumbFocus = ( { isFocused } ) => { + return css( { + borderColor: isFocused ? + color( 'darkGray.300' ) : + color( 'lightGray.700' ), + boxShadow: isFocused ? ` + 0 1px 2px rgba(0, 0, 0, 0.1), + 0 4px 8px rgba(0, 0, 0, 0.2) + ` : ` + 0 0 0 rgba(0, 0, 0, 0), + 0 0 0 rgba(0, 0, 0, 0) + `, + + } ); +}; + +export const Thumb = styled.span` + background-color: white; + border: 1px solid ${ color( 'lightGray.700' ) }; + box-sizing: border-box; + height: 100%; + pointer-events: none; + position: absolute; + transition: box-shadow 60ms linear; + width: 100%; + align-items: center; + border-radius: 50%; + box-sizing: border-box; + outline: 0; + + ${ thumbFocus }; + ${ handleReducedMotion }; +`; + +export const InputRange = styled.input` + display: block; + height: 100%; + left: 0; + margin: 0; + opacity: 0; + outline: none; + position: absolute; + right: 0; + top: 0; + width: 100%; + cursor: pointer; +`; + +const tooltipShow = ( { position, show } ) => { + const isTop = position === 'top'; + + return css( { + opacity: show ? 1 : 0, + transform: show ? 'scale(1)' : 'scale(0.5)', + transformOrigin: isTop ? 'bottom' : 'top', + } ); +}; + +const tooltipPosition = ( { position } ) => { + const isTop = position === 'top'; + + if ( isTop ) { + return css` + margin-top: -32px; + top: -100%; + + &::after { + border-bottom: none; + border-top-style: solid; + bottom: -6px; + } + `; + } + + return css` + margin-bottom: -32px; + bottom: -100%; + + &::after { + border-bottom-style: solid; + border-top: none; + top: -6px; + } + `; +}; + +export const Tooltip = styled.div` + background: ${ color( 'darkGray.800' ) }; + border-radius: 3px; + color: white; + font-size: 11px; + min-width: 32px; + opacity: 0; + padding: 8px; + pointer-events: none; + position: absolute; + position: relative; + text-align: center; + transform: scale(0.5); + transition: all 120ms ease; + user-select: none; + + &::after { + border: 6px solid ${ color( 'darkGray.800' ) }; + border-left-color: transparent; + border-right-color: transparent; + bottom: -6px; + content: ""; + height: 0; + left: 50%; + line-height: 0; + margin-left: -6px; + position: absolute; + width: 0; + } + + ${ tooltipShow }; + ${ tooltipPosition }; + ${ handleReducedMotion }; +`; diff --git a/packages/components/src/range-control/tooltip.js b/packages/components/src/range-control/tooltip.js new file mode 100644 index 00000000000000..15cb9bc86ac207 --- /dev/null +++ b/packages/components/src/range-control/tooltip.js @@ -0,0 +1,55 @@ +/** + * WordPress dependencies + */ +import { useCallback, useEffect, useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { Tooltip } from './styles/range-control-styles'; + +const TOOLTIP_OFFSET_HEIGHT = 32; + +export default function RangeTooltip( { + inputRef, + show = false, + value = 0, + renderTooltipContent = ( v ) => v, + zIndex = 100, +} ) { + const position = useTooltipPosition( { inputRef } ); + + return ( + + { renderTooltipContent( value ) } + + ); +} + +function useTooltipPosition( { inputRef } ) { + const [ position, setPosition ] = useState( 'top' ); + + const calculatePosition = useCallback( () => { + if ( inputRef.current ) { + const { top } = inputRef.current.getBoundingClientRect(); + const isOverlay = top - TOOLTIP_OFFSET_HEIGHT < 0; + const nextPosition = isOverlay ? 'bottom' : 'top'; + + setPosition( nextPosition ); + } + }, [] ); + + useEffect( () => { + calculatePosition(); + }, [ calculatePosition ] ); + + useEffect( () => { + window.addEventListener( 'resize', calculatePosition ); + + return () => { + window.removeEventListener( 'resize', calculatePosition ); + }; + } ); + + return position; +} diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js index 8dfca09543f5e9..537a8e7810300b 100644 --- a/packages/components/src/utils/colors-values.js +++ b/packages/components/src/utils/colors-values.js @@ -103,7 +103,7 @@ export const ALERT = { export const COLORS = { ...BASE, - darkGrey: DARK_GRAY, + darkGray: DARK_GRAY, darkOpacity: DARK_OPACITY, darkOpacityLight: DARK_OPACITY_LIGHT, lightGray: LIGHT_GRAY, From 8f11bcac8d4db0c53a93a41045e8664716c5cb3d Mon Sep 17 00:00:00 2001 From: Jon Q Date: Thu, 23 Jan 2020 17:11:17 -0500 Subject: [PATCH 02/33] Add tooltipPosition to RangeControl --- packages/components/src/range-control/range-control.js | 10 ++++++---- packages/components/src/range-control/tooltip.js | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/components/src/range-control/range-control.js b/packages/components/src/range-control/range-control.js index 0eb84d381cbd31..21f793ab853013 100644 --- a/packages/components/src/range-control/range-control.js +++ b/packages/components/src/range-control/range-control.js @@ -22,19 +22,20 @@ export const RangeControlNext = forwardRef( ( { alwaysShowTooltip = false, className, color: colorProp = color( 'blue.wordpress.700' ), - disableToolTip = false, disabled, + disableToolTip = false, help, id, label, marks = false, - min = 0, max = 100, + min = 0, onChange = noop, - step = 1, - value: valueProp = 0, renderTooltipContent = ( v ) => v, + step = 1, + tooltipPosition, tooltipZIndex = 100, + value: valueProp = 0, }, ref ) => { const [ value, setValue ] = useControlledRangeValue( valueProp ); const [ showTooltip, setShowTooltip ] = useState( false ); @@ -101,6 +102,7 @@ export const RangeControlNext = forwardRef( ( { v, zIndex = 100, } ) { - const position = useTooltipPosition( { inputRef } ); + const tooltipPosition = useTooltipPosition( { inputRef } ); + const position = positionProp || tooltipPosition; return ( From 78d2813a811d22305860a01f24f09fd632227729 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Thu, 23 Jan 2020 22:44:42 -0500 Subject: [PATCH 03/33] Improve styles and interactions for new RangeControl --- packages/components/src/range-control/rail.js | 5 +- .../src/range-control/range-control.js | 303 +++++++++++------- .../src/range-control/stories/index.js | 81 ++--- .../styles/range-control-styles.js | 75 +++-- .../components/src/range-control/tooltip.js | 40 ++- 5 files changed, 293 insertions(+), 211 deletions(-) diff --git a/packages/components/src/range-control/rail.js b/packages/components/src/range-control/rail.js index 1fe02eeaec9b89..230dd4cd6b2ec5 100644 --- a/packages/components/src/range-control/rail.js +++ b/packages/components/src/range-control/rail.js @@ -9,12 +9,13 @@ export default function RangeRail( { max = 100, step = 1, value = 0, + ...restProps } ) { const marksData = useMarks( { min, max, step, value } ); const LastMark = Mark; return ( - + { marks && ( <> { marksData.map( ( mark ) => ( @@ -34,7 +35,7 @@ function useMarks( { min = 0, max = 100, step = 1, value = 0 } ) { const key = `mark-${ index }`; const isFilled = value >= index; const style = { - left: `${ index / markCount * 100 }%`, + left: `${ ( index / markCount ) * 100 }%`, background: isFilled ? 'currentColor' : undefined, }; diff --git a/packages/components/src/range-control/range-control.js b/packages/components/src/range-control/range-control.js index 21f793ab853013..78e2554547f5f5 100644 --- a/packages/components/src/range-control/range-control.js +++ b/packages/components/src/range-control/range-control.js @@ -7,132 +7,160 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { useRef, useEffect, useState, forwardRef } from '@wordpress/element'; +import { + useCallback, + useRef, + useEffect, + useState, + forwardRef, +} from '@wordpress/element'; /** * Internal dependencies */ import BaseControl from '../base-control'; +import Dashicon from '../dashicon'; + import { color } from '../utils/colors'; import RangeRail from './rail'; -import RangeTooltip from './tooltip'; -import { Root, Track, ThumbWrapper, Thumb, InputRange } from './styles/range-control-styles'; - -export const RangeControlNext = forwardRef( ( { - alwaysShowTooltip = false, - className, - color: colorProp = color( 'blue.wordpress.700' ), - disabled, - disableToolTip = false, - help, - id, - label, - marks = false, - max = 100, - min = 0, - onChange = noop, - renderTooltipContent = ( v ) => v, - step = 1, - tooltipPosition, - tooltipZIndex = 100, - value: valueProp = 0, -}, ref ) => { - const [ value, setValue ] = useControlledRangeValue( valueProp ); - const [ showTooltip, setShowTooltip ] = useState( false ); - - const inputRef = useRef(); - - const handleOnChange = ( event ) => { - const newValue = event.target.value; - // If the input value is invalid temporarily save it to the state, - // without calling on change. - if ( ! event.target.checkValidity() ) { - return; - } - - setValue( newValue ); - - onChange( ( newValue === '' ) ? - undefined : - parseFloat( newValue ) - ); - }; - - const isFocused = showTooltip; - const fillValue = `${ parseFloat( value ) / max * 100 }%`; - - const handleShowTooltip = () => { - setShowTooltip( true ); - }; - - const handleHideTooltip = () => { - setShowTooltip( false ); - }; - - const setRef = ( nodeRef ) => { - inputRef.current = nodeRef; - if ( ref ) { - ref( nodeRef ); - } - }; - - const classes = classnames( 'components-range-control', className ); - const describedBy = !! help ? `${ id }__help` : undefined; - const enableTooltip = ! disableToolTip; - - return ( - - - - - - - { enableTooltip && ( - v, + step = 1, + tooltipPosition = 'auto', + tooltipTimeout = 250, + tooltipZIndex = 100, + value: valueProp = 0, + ...props + }, + ref + ) => { + const [ value, setValue ] = useControlledRangeValue( valueProp ); + const [ showTooltip, setShowTooltip ] = useState( false ); + + const inputRef = useRef(); + + const setRef = ( nodeRef ) => { + inputRef.current = nodeRef; + + if ( ref ) { + ref( nodeRef ); + } + }; + + const isFocused = ! disabled && showTooltip; + const fillValue = `${ ( value / max ) * 100 }%`; + + const classes = classnames( 'components-range-control', className ); + const describedBy = !! help ? `${ id }__help` : undefined; + const enableTooltip = ! disableToolTip; + + const handleOnChange = ( event ) => { + const nextValue = event.target.value; + + if ( ! event.target.checkValidity() ) { + return; + } + + setValue( nextValue ); + onChange( nextValue ); + }; + + const handleShowTooltip = () => setShowTooltip( true ); + const handleHideTooltip = () => setShowTooltip( false ); + + const hoverInteractions = useDebouncedHoverInteraction( { + onShow: handleShowTooltip, + onHide: handleHideTooltip, + timeout: tooltipTimeout, + } ); + + return ( + + + { beforeIcon && } + + - ) } - - - - - ); -} ); + + + + + { enableTooltip && ( + + ) } + + + { afterIcon && } + + + ); + } +); function useControlledRangeValue( { value: valueProp = 0 } ) { - const [ value, setValue ] = useState( valueProp ); + const [ value, _setValue ] = useState( parseFloat( valueProp ) ); const valueRef = useRef( valueProp ); useEffect( () => { @@ -142,5 +170,50 @@ function useControlledRangeValue( { value: valueProp = 0 } ) { } }, [ valueRef, valueProp, setValue ] ); + const setValue = useCallback( + ( nextValue ) => { + _setValue( parseFloat( nextValue ) ); + }, + [ _setValue ] + ); + return [ value, setValue ]; } + +function useDebouncedHoverInteraction( { + onShow = noop, + onHide = noop, + onMouseEnter = noop, + onMouseLeave = noop, + timeout = 250, +} ) { + const [ show, setShow ] = useState( false ); + const timeoutRef = useRef(); + + const handleOnMouseEnter = useCallback( ( event ) => { + onMouseEnter( event ); + + if ( timeoutRef.current ) { + window.clearTimeout( timeoutRef.current ); + } + + if ( ! show ) { + setShow( true ); + onShow(); + } + }, [] ); + + const handleOnMouseLeave = useCallback( ( event ) => { + onMouseLeave( event ); + + timeoutRef.current = setTimeout( () => { + setShow( false ); + onHide(); + }, timeout ); + }, [] ); + + return { + onMouseEnter: handleOnMouseEnter, + onMouseLeave: handleOnMouseLeave, + }; +} diff --git a/packages/components/src/range-control/stories/index.js b/packages/components/src/range-control/stories/index.js index a9a603e80eb774..651e56a60b9112 100644 --- a/packages/components/src/range-control/stories/index.js +++ b/packages/components/src/range-control/stories/index.js @@ -2,7 +2,7 @@ * External dependencies */ import styled from '@emotion/styled'; -import { number, text } from '@storybook/addon-knobs'; +import { boolean, number, select, text } from '@storybook/addon-knobs'; /** * WordPress dependencies @@ -21,23 +21,13 @@ const RangeControlWithState = ( props ) => { const initialValue = props.value === undefined ? 5 : props.value; const [ value, setValue ] = useState( initialValue ); - return ( - - ); + return ; }; export const _default = () => { const label = text( 'Label', 'How many columns should this use?' ); - return ( - - ); + return ; }; export const InitialValueZero = () => { @@ -56,14 +46,12 @@ export const InitialValueZero = () => { export const withHelp = () => { const label = text( 'Label', 'How many columns should this use?' ); - const help = text( 'Help Text', 'Please select the number of columns you would like this to contain.' ); - - return ( - + const help = text( + 'Help Text', + 'Please select the number of columns you would like this to contain.' ); + + return ; }; export const withMinimumAndMaximumLimits = () => { @@ -71,55 +59,50 @@ export const withMinimumAndMaximumLimits = () => { const min = number( 'Min Value', 2 ); const max = number( 'Max Value', 10 ); - return ( - - ); + return ; }; export const withIconBefore = () => { const label = text( 'Label', 'How many columns should this use?' ); const icon = text( 'Icon', 'wordpress' ); - return ( - - ); + return ; }; export const withIconAfter = () => { const label = text( 'Label', 'How many columns should this use?' ); const icon = text( 'Icon', 'wordpress' ); - return ( - - ); + return ; }; export const withReset = () => { const label = text( 'Label', 'How many columns should this use?' ); - return ( - - ); + return ; }; export const next = () => { - return ; + const props = { + alwaysShowTooltip: boolean( 'alwaysShowToolTip', false ), + min: number( 'min', 0 ), + max: number( 'max', 10 ), + step: number( 'step', 1 ), + marks: boolean( 'marks', false ), + tooltipPosition: select( 'tooltipPosition', { + top: 'top', + bottom: 'bottom', + auto: 'auto', + }, 'auto' ), + }; + + return ( + + + + ); }; const Wrapper = styled.div` - padding: 20px; -` -; + padding: 40px; +`; diff --git a/packages/components/src/range-control/styles/range-control-styles.js b/packages/components/src/range-control/styles/range-control-styles.js index bcc53dcefde97f..b64333db9b8ce2 100644 --- a/packages/components/src/range-control/styles/range-control-styles.js +++ b/packages/components/src/range-control/styles/range-control-styles.js @@ -9,20 +9,26 @@ import styled from '@emotion/styled'; */ import { color } from '../../utils/colors'; -const rootColor = ( { color: colorProp } ) => css( { color: colorProp } ); - export const Root = styled.span` - -webkit-tap-highlight-color: transparent; + -webkit-tap-highlight-color: transparent; box-sizing: border-box; - cursor: pointer; - display: inline-block; - height: 2px; - padding: 14px 0; - position: relative; - touch-action: none; + cursor: pointer; + align-items: center; + display: inline-flex; + height: 2px; + padding: 14px 0; + position: relative; + touch-action: none; + width: 100%; +`; + +const wrapperColor = ( { color: colorProp } ) => css( { color: colorProp } ); + +export const Wrapper = styled.span` + position: relative; width: 100%; - ${ rootColor }; + ${ wrapperColor }; `; export const Rail = styled.span` @@ -31,9 +37,9 @@ export const Rail = styled.span` left: 0; pointer-events: none; right: 0; - border-radius: 1px; - display: block; - height: 2px; + border-radius: 1px; + display: block; + height: 2px; position: absolute; margin-top: -1px; `; @@ -44,7 +50,7 @@ export const Track = styled.span` box-sizing: border-box; height: 2px; pointer-events: none; - display: block; + display: block; position: absolute; margin-top: -1px; `; @@ -62,14 +68,15 @@ export const ThumbWrapper = styled.span` align-items: center; margin-left: -10px; margin-top: -10px; - pointer-events: none; width: 20px; - box-sizing: border-box; - display: flex; - height: 20px; - justify-content: center; - outline: 0; - position: absolute; + box-sizing: border-box; + display: flex; + height: 20px; + justify-content: center; + outline: 0; + position: absolute; + pointer-events: none; + user-select: none; `; const handleReducedMotion = () => { @@ -82,39 +89,40 @@ const handleReducedMotion = () => { const thumbFocus = ( { isFocused } ) => { return css( { - borderColor: isFocused ? - color( 'darkGray.300' ) : - color( 'lightGray.700' ), - boxShadow: isFocused ? ` + borderColor: isFocused ? color( 'darkGray.300' ) : color( 'lightGray.700' ), + boxShadow: isFocused ? + ` 0 1px 2px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.2) - ` : ` + ` : + ` 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(0, 0, 0, 0) `, - } ); }; export const Thumb = styled.span` + align-items: center; background-color: white; + border-radius: 50%; border: 1px solid ${ color( 'lightGray.700' ) }; box-sizing: border-box; + box-sizing: border-box; height: 100%; + outline: 0; pointer-events: none; position: absolute; transition: box-shadow 60ms linear; + user-select: none; width: 100%; - align-items: center; - border-radius: 50%; - box-sizing: border-box; - outline: 0; ${ thumbFocus }; ${ handleReducedMotion }; `; export const InputRange = styled.input` + cursor: pointer; display: block; height: 100%; left: 0; @@ -125,7 +133,6 @@ export const InputRange = styled.input` right: 0; top: 0; width: 100%; - cursor: pointer; `; const tooltipShow = ( { position, show } ) => { @@ -174,8 +181,6 @@ export const Tooltip = styled.div` min-width: 32px; opacity: 0; padding: 8px; - pointer-events: none; - position: absolute; position: relative; text-align: center; transform: scale(0.5); @@ -187,7 +192,7 @@ export const Tooltip = styled.div` border-left-color: transparent; border-right-color: transparent; bottom: -6px; - content: ""; + content: ''; height: 0; left: 50%; line-height: 0; diff --git a/packages/components/src/range-control/tooltip.js b/packages/components/src/range-control/tooltip.js index 2d9d5430ecd2a5..69eab7fa79fba8 100644 --- a/packages/components/src/range-control/tooltip.js +++ b/packages/components/src/range-control/tooltip.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -10,36 +15,51 @@ import { Tooltip } from './styles/range-control-styles'; const TOOLTIP_OFFSET_HEIGHT = 32; -export default function RangeTooltip( { +export default function SimpleTooltip( { + className, inputRef, position: positionProp, show = false, value = 0, renderTooltipContent = ( v ) => v, zIndex = 100, + ...restProps } ) { - const tooltipPosition = useTooltipPosition( { inputRef } ); - const position = positionProp || tooltipPosition; + const position = useTooltipPosition( { inputRef, position: positionProp } ); + const classes = classnames( 'components-simple-tooltip', className ); return ( - + { renderTooltipContent( value ) } ); } -function useTooltipPosition( { inputRef } ) { +function useTooltipPosition( { inputRef, position: positionProp } ) { const [ position, setPosition ] = useState( 'top' ); const calculatePosition = useCallback( () => { - if ( inputRef.current ) { - const { top } = inputRef.current.getBoundingClientRect(); - const isOverlay = top - TOOLTIP_OFFSET_HEIGHT < 0; - const nextPosition = isOverlay ? 'bottom' : 'top'; + if ( inputRef && inputRef.current ) { + let nextPosition = positionProp; + + if ( positionProp === 'auto' ) { + const { top } = inputRef.current.getBoundingClientRect(); + const isOffscreenTop = top - TOOLTIP_OFFSET_HEIGHT < 0; + + nextPosition = isOffscreenTop ? 'bottom' : 'top'; + } setPosition( nextPosition ); } - }, [] ); + }, [ positionProp ] ); useEffect( () => { calculatePosition(); From 99e409d3c55104bfbe499875bae6fb0fa8cd9e2a Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 24 Jan 2020 09:25:25 -0500 Subject: [PATCH 04/33] Add custom marks support --- packages/components/src/range-control/mark.js | 20 ++++++++++ packages/components/src/range-control/rail.js | 37 ++++++++++++------- .../src/range-control/stories/index.js | 31 ++++++++++++++++ .../styles/range-control-styles.js | 19 +++++++++- 4 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 packages/components/src/range-control/mark.js diff --git a/packages/components/src/range-control/mark.js b/packages/components/src/range-control/mark.js new file mode 100644 index 00000000000000..616da32fe3fcd6 --- /dev/null +++ b/packages/components/src/range-control/mark.js @@ -0,0 +1,20 @@ +/** + * Internal dependencies + */ +import { Mark, MarkLabel } from './styles/range-control-styles'; + +export default function RangeMark( { isFilled = false, label, left, style = {}, ...props } ) { + const styles = { + ...style, + left, + }; + + return ( + <> +