Skip to content

Commit

Permalink
feat(Slider): add tooltip on active thumb button
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed Sep 1, 2022
1 parent 7f492a5 commit af468de
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const SliderExampleDefault = () => (
max={100}
value={70}
label="Default Slider:"
numberFormat={{ currency: 'EUR' }}
onChange={({ value }) => console.log('onChange:', value)}
/>
`
Expand All @@ -33,6 +34,7 @@ export const SliderExampleMultiButtons = () => (
value={[30, 70]}
step={5}
label="Range with steps:"
numberFormat={{ currency: 'EUR' }}
onChange={({ value }) => console.log('onChange:', value)}
bottom
/>
Expand All @@ -41,7 +43,7 @@ export const SliderExampleMultiButtons = () => (
max={100}
value={[10, 30, 50, 70]}
label="Multi thumbs:"
numberFormat={{ decimals: 2, currency: true }}
numberFormat={{ currency: true }}
onChange={({ value, number }) => console.log('onChange:', value, number)}
/>
</FormRow>
Expand All @@ -59,6 +61,7 @@ export const SliderExampleMultiButtonsThumbBehavior = () => (
multiThumbBehavior="omit"
value={[30, 70]}
label="Omit behavior:"
numberFormat={{ currency: 'EUR' }}
onChange={({ value }) => console.log('onChange:', value)}
bottom
/>
Expand All @@ -67,7 +70,7 @@ export const SliderExampleMultiButtonsThumbBehavior = () => (
value={[10, 50, 70]}
step={1}
label="Push behavior:"
numberFormat={{ decimals: 2, currency: true }}
numberFormat={{ currency: true }}
onChange={({ value, number }) => console.log('onChange:', value, number)}
/>
</FormRow>
Expand All @@ -88,6 +91,7 @@ const Component = () => {
step={10}
hideButtons
label="Slider A:"
numberFormat={{ currency: 'EUR' }}
onChange={({ value }) => setValue(value)}
/>
<VerticalWrapper>
Expand All @@ -98,6 +102,7 @@ const Component = () => {
step={1}
label="Slider B:"
labelDirection="vertical"
numberFormat={{ currency: 'NOK' }}
onChange={({ value }) => setValue(value)}
/>
<Input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ The Slider component provides a visual indication of adjustable value. A value c
### Define a `min` and `max` value

Keep in mind, you should most probably define your `min` and `max` value, because they are tied closely to your given value property.

### Tooltip

When a `numberFormat` is given, a Tooltip will be shown above the thumb button.
11 changes: 4 additions & 7 deletions packages/dnb-eufemia/src/components/slider/SliderHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,16 @@ export const formatNumber = (
return value
}

export const getHumanNumber = (
export const getFormattedNumber = (
value: number,
numberFormat: formatOptionParams
) => {
const num = value as number
const { aria: humanNumber } = (
return (
numberFormat
? formatNumber(num, {
? formatNumber(value as number, {
...(numberFormat || {}),
returnAria: true,
})
: { aria: null }
: { aria: null, number: null }
) as formatReturnValue

return String(humanNumber || value)
}
16 changes: 11 additions & 5 deletions packages/dnb-eufemia/src/components/slider/SliderInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
} from './SliderTrack'
import { SliderThumb } from './SliderThumb'
import { useSliderProps } from './hooks/useSliderProps'
import { clamp, getHumanNumber } from './SliderHelpers'
import { clamp, getFormattedNumber } from './SliderHelpers'

export function SliderInstance() {
const context = React.useContext(Context)
Expand Down Expand Up @@ -164,15 +164,18 @@ function SubtractButton() {
subtractParams['aria-hidden'] = attributes['aria-hidden']
}

const humanNumber = getHumanNumber(value as number, numberFormat)
const humanNumber = getFormattedNumber(value as number, numberFormat)

return (
<Button
className="dnb-slider__button dnb-slider__button--subtract"
variant="secondary"
icon="subtract"
size="small"
aria-label={subtractTitle?.replace('%s', humanNumber)}
aria-label={subtractTitle?.replace(
'%s',
humanNumber.aria || String(value)
)}
on_click={onSubtractClickHandler}
disabled={disabled}
skeleton={skeleton}
Expand All @@ -196,15 +199,18 @@ function AddButton() {
addParams['aria-hidden'] = attributes['aria-hidden']
}

const humanNumber = getHumanNumber(value as number, numberFormat)
const humanNumber = getFormattedNumber(value as number, numberFormat)

return (
<Button
className="dnb-slider__button dnb-slider__button--add"
variant="secondary"
icon="add"
size="small"
aria-label={addTitle?.replace('%s', humanNumber)}
aria-label={addTitle?.replace(
'%s',
humanNumber.aria || String(value)
)}
on_click={onAddClickHandler}
disabled={disabled}
skeleton={skeleton}
Expand Down
142 changes: 88 additions & 54 deletions packages/dnb-eufemia/src/components/slider/SliderThumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,30 @@ import {
validateDOMAttributes,
} from '../../shared/component-helper'
import Button from '../button/Button'
import Tooltip from '../tooltip/Tooltip'
import { useSliderEvents } from './hooks/useSliderEvents'
import { useSliderProps } from './hooks/useSliderProps'
import { clamp, getHumanNumber } from './SliderHelpers'
import { clamp, getFormattedNumber } from './SliderHelpers'

export function SliderThumb() {
const { values } = useSliderProps()

return (
<>
{values.map((value, i) => {
return <Thumb key={i} value={value} currentIndex={i} />
})}
</>
)
}

type ThumbProps = {
value: number
currentIndex: number
}

function Thumb({ value, currentIndex }: ThumbProps) {
const {
values,
thumbIndex,
isVertical,
isReverse,
Expand All @@ -32,6 +49,28 @@ export function SliderThumb() {
numberFormat,
} = allProps

const index = thumbIndex.current
let percent = clamp(((value - min) * 100) / (max - min))

if (isReverse) {
percent = 100 - percent
}

const style = {
zIndex: index === currentIndex ? 4 : 3,
[`${isVertical ? 'top' : 'left'}`]: `${percent}%`,
} as React.CSSProperties

const { number, aria } = getFormattedNumber(value, numberFormat)

const [hover, setHover] = React.useState(false)
const onMouseEnterHandler = () => {
setHover(true)
}
const onMouseLeaveHandler = () => {
setHover(false)
}

const {
onThumbMouseDownHandler,
onThumbMouseUpHandler,
Expand Down Expand Up @@ -68,58 +107,53 @@ export function SliderThumb() {

return (
<>
{values.map((value, i) => {
const index = thumbIndex.current
let percent = clamp(((value - min) * 100) / (max - min))

if (isReverse) {
percent = 100 - percent
}

const style: React.CSSProperties = {
zIndex: index === i ? 4 : 3,
[`${isVertical ? 'top' : 'left'}`]: `${percent}%`,
}

const humanNumber = getHumanNumber(value, numberFormat)

return (
<React.Fragment key={i}>
<span className="dnb-slider__thumb" style={style}>
<input
type="range"
className="dnb-slider__button-helper"
min={min}
max={max}
step={step}
value={value}
disabled={disabled}
onChange={onHelperChangeHandler}
onFocus={onHelperFocusHandler}
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value}
aria-valuetext={humanNumber ? humanNumber : undefined}
aria-orientation={isVertical ? 'vertical' : 'horizontal'}
data-index={i}
{...helperParams}
/>

<Button
element="span"
type=""
variant="secondary"
disabled={disabled}
skeleton={skeleton}
data-index={i}
onMouseDown={onThumbMouseDownHandler}
onMouseUp={onThumbMouseUpHandler}
{...thumbParams}
/>
</span>
</React.Fragment>
)
})}
<span
className="dnb-slider__thumb"
style={style}
onMouseEnter={onMouseEnterHandler}
onTouchStart={onMouseEnterHandler}
onMouseLeave={onMouseLeaveHandler}
onTouchEnd={onMouseLeaveHandler}
>
<input
type="range"
className="dnb-slider__button-helper"
min={min}
max={max}
step={step}
value={value}
disabled={disabled}
onChange={onHelperChangeHandler}
onFocus={onHelperFocusHandler}
onFocusCapture={onMouseEnterHandler}
onBlurCapture={onMouseLeaveHandler}
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value}
aria-valuetext={aria ? aria : undefined}
aria-orientation={isVertical ? 'vertical' : 'horizontal'}
data-index={currentIndex}
{...helperParams}
/>

<Button
element="span"
type=""
variant="secondary"
disabled={disabled}
skeleton={skeleton}
data-index={currentIndex}
onMouseDown={onThumbMouseDownHandler}
onMouseUp={onThumbMouseUpHandler}
{...thumbParams}
/>

{numberFormat && (
<Tooltip active={hover} skip_portal>
{number || value}
</Tooltip>
)}
</span>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('Slider screenshot', () => {

it('have to match the focus state', async () => {
const screenshot = await testPageScreenshot({
style: { width: '20rem' },
style: { width: '20rem', 'padding-top': '3rem' },
selector: '[data-visual-test="slider-default"] .dnb-slider__wrapper',
simulateSelector:
'[data-visual-test="slider-default"] .dnb-slider__thumb .dnb-slider__button-helper',
Expand All @@ -32,7 +32,7 @@ describe('Slider screenshot', () => {

it('have to match the hover state', async () => {
const screenshot = await testPageScreenshot({
style: { width: '20rem' },
style: { width: '20rem', 'padding-top': '3rem' },
selector: '[data-visual-test="slider-default"] .dnb-slider__wrapper',
simulateSelector:
'[data-visual-test="slider-default"] .dnb-slider__thumb .dnb-slider__button-helper',
Expand All @@ -43,7 +43,7 @@ describe('Slider screenshot', () => {

it('have to match the active state', async () => {
const screenshot = await testPageScreenshot({
style: { width: '20rem' },
style: { width: '20rem', 'padding-top': '3rem' },
selector: '[data-visual-test="slider-default"] .dnb-slider__wrapper',
simulateSelector:
'[data-visual-test="slider-default"] .dnb-slider__thumb .dnb-slider__button-helper',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const props: SliderProps = {
max: 100,
value: 70,
step: 10,
numberFormat: { currency: true, decimals: 0 },
numberFormat: null,
labelDirection: 'horizontal',
}

Expand Down Expand Up @@ -153,6 +153,35 @@ describe('Slider component', () => {
)
})

it('shows Tooltip on hover when numberFormat is given', () => {
render(<Slider {...props} numberFormat={{ currency: 'EUR' }} />)

const mainElem = document.querySelector('.dnb-slider')
const thumbElem = mainElem.querySelector('.dnb-slider__thumb')
const tooltipElem = thumbElem.querySelector('.dnb-tooltip')

expect(tooltipElem.textContent).toBe('70,00 €')
expect(Array.from(tooltipElem.classList)).toEqual(
expect.arrayContaining(['dnb-tooltip'])
)

fireEvent.mouseOver(thumbElem)

simulateMouseMove({ pageX: 80, width: 100, height: 10 })

expect(Array.from(tooltipElem.classList)).toEqual(
expect.arrayContaining(['dnb-tooltip', 'dnb-tooltip--active'])
)

expect(tooltipElem.textContent).toBe('80,00 €')

fireEvent.mouseOut(thumbElem)

expect(Array.from(tooltipElem.classList)).toEqual(
expect.arrayContaining(['dnb-tooltip', 'dnb-tooltip--hide'])
)
})

it('has events that return a correct value', () => {
const onChange = jest.fn()

Expand All @@ -174,7 +203,7 @@ describe('Slider component', () => {
rawValue: 80,
raw_value: 80,
value: 80,
number: '80 kr',
number: null,
width: 100,
}
expect(onChange).toBeCalledWith(changeObject)
Expand Down
Loading

0 comments on commit af468de

Please sign in to comment.