@toptal/picasso-slider 1.0.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-package/src/Slider/Slider.d.ts +20 -20
- package/dist-package/src/Slider/Slider.d.ts.map +1 -1
- package/dist-package/src/Slider/Slider.js +49 -41
- package/dist-package/src/Slider/Slider.js.map +1 -1
- package/dist-package/src/Slider/hooks/index.d.ts +2 -0
- package/dist-package/src/Slider/hooks/index.d.ts.map +1 -0
- package/dist-package/src/Slider/hooks/index.js +2 -0
- package/dist-package/src/Slider/hooks/index.js.map +1 -0
- package/dist-package/src/Slider/hooks/use-label-overlap.d.ts +8 -0
- package/dist-package/src/Slider/hooks/use-label-overlap.d.ts.map +1 -0
- package/dist-package/src/Slider/hooks/use-label-overlap.js +35 -0
- package/dist-package/src/Slider/hooks/use-label-overlap.js.map +1 -0
- package/dist-package/src/Slider/index.d.ts +0 -1
- package/dist-package/src/Slider/index.d.ts.map +1 -1
- package/dist-package/src/Slider/index.js +0 -1
- package/dist-package/src/Slider/index.js.map +1 -1
- package/dist-package/src/SliderMark/SliderMark.d.ts +13 -0
- package/dist-package/src/SliderMark/SliderMark.d.ts.map +1 -0
- package/dist-package/src/SliderMark/SliderMark.js +10 -0
- package/dist-package/src/SliderMark/SliderMark.js.map +1 -0
- package/dist-package/src/SliderMark/index.d.ts +2 -0
- package/dist-package/src/SliderMark/index.d.ts.map +1 -0
- package/dist-package/src/SliderMark/index.js +2 -0
- package/dist-package/src/SliderMark/index.js.map +1 -0
- package/dist-package/src/SliderValueLabel/SliderValueLabel.d.ts +9 -14
- package/dist-package/src/SliderValueLabel/SliderValueLabel.d.ts.map +1 -1
- package/dist-package/src/SliderValueLabel/SliderValueLabel.js +40 -23
- package/dist-package/src/SliderValueLabel/SliderValueLabel.js.map +1 -1
- package/dist-package/src/SliderValueLabel/index.d.ts +1 -2
- package/dist-package/src/SliderValueLabel/index.d.ts.map +1 -1
- package/dist-package/src/SliderValueLabel/index.js +1 -1
- package/dist-package/src/SliderValueLabel/index.js.map +1 -1
- package/dist-package/src/index.d.ts +0 -1
- package/dist-package/src/index.d.ts.map +1 -1
- package/dist-package/src/index.js +0 -1
- package/dist-package/src/index.js.map +1 -1
- package/dist-package/src/utils/check-bg-color.d.ts +7 -0
- package/dist-package/src/utils/check-bg-color.d.ts.map +1 -0
- package/dist-package/src/utils/check-bg-color.js +15 -0
- package/dist-package/src/utils/check-bg-color.js.map +1 -0
- package/dist-package/src/utils/check-overlap.d.ts +26 -0
- package/dist-package/src/utils/check-overlap.d.ts.map +1 -0
- package/dist-package/src/utils/check-overlap.js +32 -0
- package/dist-package/src/utils/check-overlap.js.map +1 -0
- package/dist-package/src/utils/get-x-placement.d.ts +8 -0
- package/dist-package/src/utils/get-x-placement.d.ts.map +1 -0
- package/dist-package/src/utils/get-x-placement.js +17 -0
- package/dist-package/src/utils/get-x-placement.js.map +1 -0
- package/dist-package/src/utils/index.d.ts +4 -0
- package/dist-package/src/utils/index.d.ts.map +1 -0
- package/dist-package/src/utils/index.js +4 -0
- package/dist-package/src/utils/index.js.map +1 -0
- package/package.json +5 -7
- package/src/Slider/Slider.tsx +114 -101
- package/src/Slider/__snapshots__/test.tsx.snap +58 -30
- package/src/Slider/hooks/index.ts +1 -0
- package/src/Slider/hooks/use-label-overlap.ts +48 -0
- package/src/Slider/index.ts +0 -1
- package/src/Slider/story/ControlledWithLabel.example.tsx +1 -1
- package/src/Slider/story/Marks.example.tsx +13 -4
- package/src/Slider/story/Range.example.tsx +0 -1
- package/src/Slider/story/RangeWithValueLabel.example.tsx +0 -1
- package/src/Slider/story/Tooltip.example.tsx +22 -6
- package/src/Slider/story/index.jsx +0 -8
- package/src/Slider/test.tsx +6 -0
- package/src/SliderMark/SliderMark.tsx +35 -0
- package/src/SliderMark/index.ts +1 -0
- package/src/SliderValueLabel/SliderValueLabel.tsx +74 -54
- package/src/SliderValueLabel/index.ts +1 -5
- package/src/index.ts +0 -1
- package/src/utils/check-bg-color.test.ts +92 -0
- package/src/utils/check-bg-color.ts +28 -0
- package/src/utils/check-overlap.test.ts +55 -0
- package/src/utils/check-overlap.ts +43 -0
- package/src/utils/get-x-placement.test.ts +93 -0
- package/src/utils/get-x-placement.ts +28 -0
- package/src/utils/index.ts +3 -0
- package/dist-package/src/Slider/SliderContext.d.ts +0 -11
- package/dist-package/src/Slider/SliderContext.d.ts.map +0 -1
- package/dist-package/src/Slider/SliderContext.js +0 -38
- package/dist-package/src/Slider/SliderContext.js.map +0 -1
- package/dist-package/src/Slider/styles.d.ts +0 -4
- package/dist-package/src/Slider/styles.d.ts.map +0 -1
- package/dist-package/src/Slider/styles.js +0 -68
- package/dist-package/src/Slider/styles.js.map +0 -1
- package/src/Slider/SliderContext.tsx +0 -80
- package/src/Slider/story/CustomTooltip.example.tsx +0 -36
- package/src/Slider/styles.ts +0 -71
|
@@ -1,14 +1,29 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
import { Container, Slider, Typography } from '@toptal/picasso'
|
|
3
3
|
import { SPACING_4, SPACING_8, SPACING_10 } from '@toptal/picasso-utils'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
type Value = number
|
|
6
|
+
|
|
7
|
+
const formatLabel = (value: Value) => {
|
|
8
|
+
const formattedVal = String(value).padStart(2, '0')
|
|
7
9
|
|
|
8
10
|
return <Typography color='inherit'>GMT+{formattedVal}:00</Typography>
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
const Example = () => {
|
|
14
|
+
const [value1, setValue1] = useState(0)
|
|
15
|
+
const [value2, setValue2] = useState(0)
|
|
16
|
+
const [value3, setValue3] = useState(0)
|
|
17
|
+
const handleChange1 = (_: React.ChangeEvent<{}>, newValue: Value) => {
|
|
18
|
+
setValue1(newValue)
|
|
19
|
+
}
|
|
20
|
+
const handleChange2 = (_: React.ChangeEvent<{}>, newValue: Value) => {
|
|
21
|
+
setValue2(newValue)
|
|
22
|
+
}
|
|
23
|
+
const handleChange3 = (_: React.ChangeEvent<{}>, newValue: Value) => {
|
|
24
|
+
setValue3(newValue)
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
return (
|
|
13
28
|
<Container padded={SPACING_4}>
|
|
14
29
|
<Container>
|
|
@@ -16,7 +31,7 @@ const Example = () => {
|
|
|
16
31
|
Display persistently
|
|
17
32
|
</Typography>
|
|
18
33
|
<Container top={SPACING_8}>
|
|
19
|
-
<Slider tooltip='on'
|
|
34
|
+
<Slider value={value1} onChange={handleChange1} tooltip='on' />
|
|
20
35
|
</Container>
|
|
21
36
|
</Container>
|
|
22
37
|
<Container top={SPACING_8}>
|
|
@@ -24,7 +39,7 @@ const Example = () => {
|
|
|
24
39
|
Display when the thumb is hovered or focused
|
|
25
40
|
</Typography>
|
|
26
41
|
<Container top={SPACING_8}>
|
|
27
|
-
<Slider tooltip='auto'
|
|
42
|
+
<Slider value={value2} onChange={handleChange2} tooltip='auto' />
|
|
28
43
|
</Container>
|
|
29
44
|
</Container>
|
|
30
45
|
<Container top={SPACING_8}>
|
|
@@ -36,8 +51,9 @@ const Example = () => {
|
|
|
36
51
|
min={0}
|
|
37
52
|
max={23}
|
|
38
53
|
tooltip='on'
|
|
54
|
+
value={value3}
|
|
55
|
+
onChange={handleChange3}
|
|
39
56
|
tooltipFormat={formatLabel}
|
|
40
|
-
compact
|
|
41
57
|
/>
|
|
42
58
|
</Container>
|
|
43
59
|
</Container>
|
|
@@ -53,14 +53,6 @@ page
|
|
|
53
53
|
'base/Slider'
|
|
54
54
|
)
|
|
55
55
|
.addExample('Slider/story/Marks.example.tsx', 'Marks', 'base/Slider')
|
|
56
|
-
.addExample(
|
|
57
|
-
'Slider/story/CustomTooltip.example.tsx',
|
|
58
|
-
{
|
|
59
|
-
title: 'Custom Tooltip',
|
|
60
|
-
takeScreenshot: false,
|
|
61
|
-
},
|
|
62
|
-
'base/Slider'
|
|
63
|
-
)
|
|
64
56
|
.addExample(
|
|
65
57
|
'Slider/story/HideThumb.example.tsx',
|
|
66
58
|
'Hide thumb when value is null or undefined',
|
package/src/Slider/test.tsx
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
|
+
import { describe, expect, it } from '@jest/globals'
|
|
2
3
|
import React from 'react'
|
|
3
4
|
import type { RenderResult } from '@testing-library/react'
|
|
4
5
|
import { render } from '@testing-library/react'
|
|
5
6
|
import type { OmitInternalProps } from '@toptal/picasso-shared'
|
|
6
7
|
|
|
8
|
+
jest.mock('@toptal/picasso-utils', () => ({
|
|
9
|
+
...jest.requireActual('@toptal/picasso-utils'),
|
|
10
|
+
useOnScreen: jest.fn().mockRejectedValueOnce(true),
|
|
11
|
+
}))
|
|
12
|
+
|
|
7
13
|
import type { Props } from './Slider'
|
|
8
14
|
import { Slider } from './Slider'
|
|
9
15
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { twJoin } from 'tailwind-merge'
|
|
3
|
+
|
|
4
|
+
import { getBgColor } from '../utils'
|
|
5
|
+
|
|
6
|
+
export type SliderMarkProps = {
|
|
7
|
+
markActive: boolean
|
|
8
|
+
ownerState: { value: number }
|
|
9
|
+
style: React.CSSProperties
|
|
10
|
+
'data-index': number
|
|
11
|
+
forceInactive: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// We need custom Mark component because we have
|
|
15
|
+
// different bg color based on value of the Slider
|
|
16
|
+
const SliderMark = ({
|
|
17
|
+
markActive,
|
|
18
|
+
ownerState,
|
|
19
|
+
'data-index': dataIndex,
|
|
20
|
+
style,
|
|
21
|
+
forceInactive,
|
|
22
|
+
}: SliderMarkProps) => {
|
|
23
|
+
return (
|
|
24
|
+
<span
|
|
25
|
+
data-index={dataIndex}
|
|
26
|
+
style={style}
|
|
27
|
+
className={twJoin(
|
|
28
|
+
'absolute w-[6px] h-[6px] rounded-[50%] border-[2px] top-[1.5px] border-solid border-white opacity-100 -translate-x-2/4 box-content',
|
|
29
|
+
getBgColor({ markActive, forceInactive, value: ownerState.value })
|
|
30
|
+
)}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default SliderMark
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './SliderMark'
|
|
@@ -1,73 +1,93 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
1
|
+
import type { SliderValueLabelSlotProps } from '@mui/base/Slider'
|
|
2
|
+
import type { RefObject } from 'react'
|
|
3
|
+
import React, { useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { twJoin } from 'tailwind-merge'
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import { getXPlacement } from '../utils'
|
|
6
7
|
|
|
7
8
|
type ValueLabelDisplay = 'on' | 'auto' | 'off'
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const classesByTooltip: Record<ValueLabelDisplay, string> = {
|
|
11
|
+
off: 'hidden',
|
|
12
|
+
// We need to use visibility: hidden instead of display: none to keep the
|
|
13
|
+
// label visible for javascript calculations.
|
|
14
|
+
auto: 'invisible group-hover/thumb:visible flex justify-center items-center',
|
|
15
|
+
on: 'flex justify-center items-center',
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
const xPlacementClasses = {
|
|
19
|
+
left: 'right-[calc(100%-13px)]',
|
|
20
|
+
right: 'left-[calc(100%-13px)]',
|
|
21
|
+
center: '',
|
|
22
|
+
} as const
|
|
23
|
+
|
|
24
|
+
const yPlacementClasses = {
|
|
25
|
+
bottom: 'top-[calc(100%+2px)]',
|
|
26
|
+
top: 'bottom-[calc(100%+2px)]',
|
|
27
|
+
} as const
|
|
22
28
|
|
|
23
29
|
const SliderValueLabel = ({
|
|
24
|
-
tooltip,
|
|
25
|
-
disablePortal,
|
|
26
|
-
compact,
|
|
27
30
|
children,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
index = -1,
|
|
32
|
+
tooltip = 'off',
|
|
33
|
+
onRender,
|
|
34
|
+
yPlacement,
|
|
35
|
+
isOverlaped,
|
|
36
|
+
ownerState: { value },
|
|
37
|
+
}: SliderValueLabelSlotProps & {
|
|
38
|
+
tooltip: ValueLabelDisplay
|
|
39
|
+
yPlacement: 'top' | 'bottom'
|
|
40
|
+
/** indicates if there are two SliderValueLabels that overlap each other */
|
|
41
|
+
isOverlaped: boolean
|
|
42
|
+
onRender: (index: number, ref: RefObject<HTMLSpanElement>) => void
|
|
43
|
+
}) => {
|
|
44
|
+
const ref = useRef<HTMLSpanElement>(null)
|
|
36
45
|
|
|
37
|
-
if
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (hasTooltipOverlow) {
|
|
43
|
-
return index === 0 ? 'top-end' : 'top-start'
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return 'top'
|
|
47
|
-
}
|
|
46
|
+
// we need to change the placement of the label if it is overlaped
|
|
47
|
+
// or if it is out of the viewport
|
|
48
|
+
const [xPlacement, setXPlacement] = useState<'left' | 'right' | 'center'>(
|
|
49
|
+
'center'
|
|
50
|
+
)
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
onRender(index, ref)
|
|
54
|
+
}, [index, onRender])
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!ref.current) {
|
|
58
|
+
return
|
|
55
59
|
}
|
|
56
|
-
|
|
60
|
+
|
|
61
|
+
setXPlacement(
|
|
62
|
+
getXPlacement({
|
|
63
|
+
rect: ref.current.getBoundingClientRect(),
|
|
64
|
+
isOverlaped: isOverlaped,
|
|
65
|
+
isFirstLabel: index === 0,
|
|
66
|
+
currentPlacement: xPlacement,
|
|
67
|
+
})
|
|
68
|
+
)
|
|
69
|
+
// we need to recalculate on value change to get new rect
|
|
70
|
+
}, [isOverlaped, index, xPlacement, value])
|
|
57
71
|
|
|
58
72
|
return (
|
|
59
|
-
<
|
|
60
|
-
ref={
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
compact={compact}
|
|
73
|
+
<span
|
|
74
|
+
ref={ref}
|
|
75
|
+
className={twJoin(
|
|
76
|
+
'absolute will-change-transform transition-transform',
|
|
77
|
+
classesByTooltip[tooltip],
|
|
78
|
+
yPlacementClasses[yPlacement],
|
|
79
|
+
xPlacementClasses[xPlacement]
|
|
80
|
+
)}
|
|
68
81
|
>
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
<span
|
|
83
|
+
className={twJoin(
|
|
84
|
+
'shadow-4 text-sm text-white bg-graphite-800',
|
|
85
|
+
'm-1 rounded-sm py-[2px] px-2 max-w-[300px] break-words'
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{children}
|
|
89
|
+
</span>
|
|
90
|
+
</span>
|
|
71
91
|
)
|
|
72
92
|
}
|
|
73
93
|
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals'
|
|
2
|
+
|
|
3
|
+
import { getBgColor } from './index'
|
|
4
|
+
import type { GetBgColorProps } from './index'
|
|
5
|
+
|
|
6
|
+
describe('getBgColor function', () => {
|
|
7
|
+
describe('when markActive is true, forceInactive is false, and value is not undefined', () => {
|
|
8
|
+
it('returns "bg-blue-500"', () => {
|
|
9
|
+
const options: GetBgColorProps = {
|
|
10
|
+
markActive: true,
|
|
11
|
+
forceInactive: false,
|
|
12
|
+
value: 0,
|
|
13
|
+
}
|
|
14
|
+
const bgColor = getBgColor(options)
|
|
15
|
+
|
|
16
|
+
expect(bgColor).toBe('bg-blue-500')
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('when markActive is true, forceInactive is false, and value is undefined', () => {
|
|
21
|
+
it('returns "bg-gray-500"', () => {
|
|
22
|
+
const options: GetBgColorProps = {
|
|
23
|
+
markActive: true,
|
|
24
|
+
forceInactive: false,
|
|
25
|
+
value: undefined,
|
|
26
|
+
}
|
|
27
|
+
const bgColor = getBgColor(options)
|
|
28
|
+
|
|
29
|
+
expect(bgColor).toBe('bg-gray-500')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('when markActive is true, forceInactive is true', () => {
|
|
34
|
+
const options: GetBgColorProps = { markActive: true, forceInactive: true }
|
|
35
|
+
|
|
36
|
+
describe('and value is not undefined', () => {
|
|
37
|
+
it('returns "bg-gray-500"', () => {
|
|
38
|
+
const bgColor = getBgColor({ ...options, value: 10 })
|
|
39
|
+
|
|
40
|
+
expect(bgColor).toBe('bg-gray-500')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('and value is undefined', () => {
|
|
45
|
+
it('returns "bg-gray-500"', () => {
|
|
46
|
+
const bgColor = getBgColor({ ...options, value: undefined })
|
|
47
|
+
|
|
48
|
+
expect(bgColor).toBe('bg-gray-500')
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('when markActive is false', () => {
|
|
54
|
+
const options: GetBgColorProps = { markActive: false }
|
|
55
|
+
|
|
56
|
+
describe('and forceInactive is false', () => {
|
|
57
|
+
it('returns "bg-gray-500" regardless of value', () => {
|
|
58
|
+
let bgColor = getBgColor({
|
|
59
|
+
...options,
|
|
60
|
+
forceInactive: false,
|
|
61
|
+
value: 10,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
expect(bgColor).toBe('bg-gray-500')
|
|
65
|
+
|
|
66
|
+
bgColor = getBgColor({
|
|
67
|
+
...options,
|
|
68
|
+
forceInactive: false,
|
|
69
|
+
value: undefined,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
expect(bgColor).toBe('bg-gray-500')
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('and forceInactive is true', () => {
|
|
77
|
+
it('returns "bg-gray-500" regardless of value', () => {
|
|
78
|
+
let bgColor = getBgColor({ ...options, forceInactive: true, value: 10 })
|
|
79
|
+
|
|
80
|
+
expect(bgColor).toBe('bg-gray-500')
|
|
81
|
+
|
|
82
|
+
bgColor = getBgColor({
|
|
83
|
+
...options,
|
|
84
|
+
forceInactive: true,
|
|
85
|
+
value: undefined,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
expect(bgColor).toBe('bg-gray-500')
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type GetBgColorProps = {
|
|
2
|
+
markActive?: boolean
|
|
3
|
+
forceInactive?: boolean
|
|
4
|
+
value?: number | readonly number[]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const getBgColor = ({
|
|
8
|
+
markActive,
|
|
9
|
+
forceInactive,
|
|
10
|
+
value,
|
|
11
|
+
}: GetBgColorProps) => {
|
|
12
|
+
const inactive = 'bg-gray-500'
|
|
13
|
+
const active = 'bg-blue-500'
|
|
14
|
+
|
|
15
|
+
if (forceInactive) {
|
|
16
|
+
return inactive
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (markActive) {
|
|
20
|
+
if (value === undefined) {
|
|
21
|
+
return inactive
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return active
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return inactive
|
|
28
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals'
|
|
2
|
+
|
|
3
|
+
import { checkOverlap } from './check-overlap' // Update your checkOverlap import
|
|
4
|
+
|
|
5
|
+
describe('checkOverlap', () => {
|
|
6
|
+
describe('when labels do not overlap', () => {
|
|
7
|
+
it('returns false', () => {
|
|
8
|
+
const firstLabelRect = { right: 0, width: 20 } as DOMRect
|
|
9
|
+
const secondLabelRect = { left: 30, width: 20 } as DOMRect
|
|
10
|
+
const previousResult = false
|
|
11
|
+
|
|
12
|
+
expect(
|
|
13
|
+
checkOverlap({ firstLabelRect, secondLabelRect, previousResult })
|
|
14
|
+
).toBe(false)
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('when labels do overlap', () => {
|
|
19
|
+
it('returns true', () => {
|
|
20
|
+
const firstLabelRect = { right: 30, width: 20 } as DOMRect
|
|
21
|
+
const secondLabelRect = { left: 40, width: 20 } as DOMRect
|
|
22
|
+
const previousResult = false
|
|
23
|
+
|
|
24
|
+
expect(
|
|
25
|
+
checkOverlap({ firstLabelRect, secondLabelRect, previousResult })
|
|
26
|
+
).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('when labels partially overlaped in previous render', () => {
|
|
31
|
+
describe('when the previousResult is true', () => {
|
|
32
|
+
it('they would still overlap in a non-overlapped state', () => {
|
|
33
|
+
const firstLabelRect = { right: 10, width: 20 } as DOMRect
|
|
34
|
+
const secondLabelRect = { left: 25, width: 20 } as DOMRect
|
|
35
|
+
const previousResult = true
|
|
36
|
+
|
|
37
|
+
expect(
|
|
38
|
+
checkOverlap({ firstLabelRect, secondLabelRect, previousResult })
|
|
39
|
+
).toBe(true)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('when previousResult is false', () => {
|
|
44
|
+
it('would not overlap in a non-overlapped state', () => {
|
|
45
|
+
const firstLabelRect = { right: 10, width: 20 } as DOMRect
|
|
46
|
+
const secondLabelRect = { left: 30, width: 20 } as DOMRect
|
|
47
|
+
const previousResult = false
|
|
48
|
+
|
|
49
|
+
expect(
|
|
50
|
+
checkOverlap({ firstLabelRect, secondLabelRect, previousResult })
|
|
51
|
+
).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type CheckOverlapProps = {
|
|
2
|
+
firstLabelRect: DOMRect
|
|
3
|
+
secondLabelRect: DOMRect
|
|
4
|
+
previousResult: boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* if we have range slider, there is a chance
|
|
9
|
+
* when we select values next to each other
|
|
10
|
+
* that the lables might overlap each other. Like this example:
|
|
11
|
+
* [ [ ] ]
|
|
12
|
+
* A B
|
|
13
|
+
*
|
|
14
|
+
* In that case we need to reposition the labels to the edges of the thumb.
|
|
15
|
+
* [ ] [ ]
|
|
16
|
+
* A B
|
|
17
|
+
*
|
|
18
|
+
* In next rerender when we recalculate the position of the labels the
|
|
19
|
+
* above example would end with result as not overlapping as the position was already changed.
|
|
20
|
+
* So we need to work with the previous result of this function `previousResult`. When
|
|
21
|
+
* true we need to add half of the width of the label to simulate the center position.
|
|
22
|
+
*
|
|
23
|
+
* This function checks and returns if the labels overlap.
|
|
24
|
+
* It is called on each render of the component and everytime the Slider changes value.
|
|
25
|
+
**/
|
|
26
|
+
export const checkOverlap = ({
|
|
27
|
+
firstLabelRect,
|
|
28
|
+
secondLabelRect,
|
|
29
|
+
previousResult,
|
|
30
|
+
}: CheckOverlapProps): boolean => {
|
|
31
|
+
const gap = 16
|
|
32
|
+
const halfWidth1 = firstLabelRect.width / 2
|
|
33
|
+
const halfWidth2 = secondLabelRect.width / 2
|
|
34
|
+
|
|
35
|
+
const rightBoundaryOfFirstLabel = previousResult
|
|
36
|
+
? firstLabelRect.right + halfWidth1
|
|
37
|
+
: firstLabelRect.right
|
|
38
|
+
const leftBoundaryOfSecondLabel = previousResult
|
|
39
|
+
? secondLabelRect.left - halfWidth2
|
|
40
|
+
: secondLabelRect.left
|
|
41
|
+
|
|
42
|
+
return rightBoundaryOfFirstLabel + gap > leftBoundaryOfSecondLabel
|
|
43
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals'
|
|
2
|
+
|
|
3
|
+
import { getXPlacement } from './get-x-placement' // Update your getXPlacement import
|
|
4
|
+
|
|
5
|
+
describe('getXPlacement', () => {
|
|
6
|
+
describe('when leftBoundary < gap', () => {
|
|
7
|
+
it('returns right', () => {
|
|
8
|
+
const rect = { width: 20, left: -5, right: 0 } as DOMRect
|
|
9
|
+
const isOverlaped = false
|
|
10
|
+
const isFirstLabel = true
|
|
11
|
+
const currentPlacement = 'center'
|
|
12
|
+
|
|
13
|
+
expect(
|
|
14
|
+
getXPlacement({ rect, isOverlaped, isFirstLabel, currentPlacement })
|
|
15
|
+
).toBe('right')
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('when rightBoundary > window.innerWidth - gap', () => {
|
|
20
|
+
it('returns left', () => {
|
|
21
|
+
const rect = { width: 100, left: 920, right: 1020 } as DOMRect
|
|
22
|
+
const isOverlaped = false
|
|
23
|
+
const isFirstLabel = true
|
|
24
|
+
const currentPlacement = 'center'
|
|
25
|
+
|
|
26
|
+
expect(
|
|
27
|
+
getXPlacement({ rect, isOverlaped, isFirstLabel, currentPlacement })
|
|
28
|
+
).toBe('left')
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('when rightBoundary < window.innerWidth - gap', () => {
|
|
33
|
+
it('returns left', () => {
|
|
34
|
+
const rect = { width: 100, left: 870, right: 970 } as DOMRect
|
|
35
|
+
const isOverlaped = false
|
|
36
|
+
const isFirstLabel = true
|
|
37
|
+
const currentPlacement = 'center'
|
|
38
|
+
|
|
39
|
+
expect(
|
|
40
|
+
getXPlacement({ rect, isOverlaped, isFirstLabel, currentPlacement })
|
|
41
|
+
).toBe('center')
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('when currentPlacement is left and rightBoundary > window.innerWidth - gap', () => {
|
|
46
|
+
it('returns left', () => {
|
|
47
|
+
const rect = { width: 100, left: 870, right: 970 } as DOMRect
|
|
48
|
+
const isOverlaped = true
|
|
49
|
+
const isFirstLabel = false
|
|
50
|
+
const currentPlacement = 'left'
|
|
51
|
+
|
|
52
|
+
expect(
|
|
53
|
+
getXPlacement({ rect, isOverlaped, isFirstLabel, currentPlacement })
|
|
54
|
+
).toBe('left')
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('when labels are overlapped', () => {
|
|
59
|
+
const rect = { width: 20, left: 50, right: 70 } as DOMRect
|
|
60
|
+
const isOverlaped = true
|
|
61
|
+
|
|
62
|
+
it('returns left for the first label', () => {
|
|
63
|
+
const isFirstLabel = true
|
|
64
|
+
const currentPlacement = 'center'
|
|
65
|
+
|
|
66
|
+
expect(
|
|
67
|
+
getXPlacement({ rect, isOverlaped, isFirstLabel, currentPlacement })
|
|
68
|
+
).toBe('left')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('returns right for the not first label', () => {
|
|
72
|
+
const isFirstLabel = false
|
|
73
|
+
const currentPlacement = 'center'
|
|
74
|
+
|
|
75
|
+
expect(
|
|
76
|
+
getXPlacement({ rect, isOverlaped, isFirstLabel, currentPlacement })
|
|
77
|
+
).toBe('right')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('when none of conditions are met', () => {
|
|
82
|
+
it('returns center', () => {
|
|
83
|
+
const rect = { width: 20, left: 100, right: 120 } as DOMRect
|
|
84
|
+
const isOverlaped = false
|
|
85
|
+
const isFirstLabel = false
|
|
86
|
+
const currentPlacement = 'center'
|
|
87
|
+
|
|
88
|
+
expect(
|
|
89
|
+
getXPlacement({ rect, isOverlaped, isFirstLabel, currentPlacement })
|
|
90
|
+
).toBe('center')
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type GetPositionProps = {
|
|
2
|
+
rect: DOMRect
|
|
3
|
+
isOverlaped: boolean
|
|
4
|
+
isFirstLabel: boolean
|
|
5
|
+
currentPlacement: 'left' | 'right' | 'center'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const getXPlacement = ({
|
|
9
|
+
rect: { width, left, right },
|
|
10
|
+
isOverlaped,
|
|
11
|
+
isFirstLabel,
|
|
12
|
+
currentPlacement,
|
|
13
|
+
}: GetPositionProps): 'left' | 'right' | 'center' => {
|
|
14
|
+
const gap = 16
|
|
15
|
+
const halfWidth = width / 2
|
|
16
|
+
const leftBoundary = currentPlacement === 'right' ? left - halfWidth : left
|
|
17
|
+
const rightBoundary = currentPlacement === 'left' ? right + halfWidth : right
|
|
18
|
+
|
|
19
|
+
if (leftBoundary < gap) {
|
|
20
|
+
return 'right'
|
|
21
|
+
} else if (rightBoundary > window.innerWidth - gap) {
|
|
22
|
+
return 'left'
|
|
23
|
+
} else if (isOverlaped) {
|
|
24
|
+
return isFirstLabel ? 'left' : 'right'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return 'center'
|
|
28
|
+
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
interface ProviderProps {
|
|
3
|
-
children: ReactNode;
|
|
4
|
-
}
|
|
5
|
-
declare const SliderContextProvider: ({ children }: ProviderProps) => JSX.Element;
|
|
6
|
-
declare const useSliderContext: () => {
|
|
7
|
-
registerValueLabel: (index: number, tooltip: HTMLDivElement, thumb: HTMLElement) => void;
|
|
8
|
-
hasTooltipOverlow: boolean;
|
|
9
|
-
};
|
|
10
|
-
export { SliderContextProvider, useSliderContext };
|
|
11
|
-
//# sourceMappingURL=SliderContext.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SliderContext.d.ts","sourceRoot":"","sources":["../../../src/Slider/SliderContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAsBtC,UAAU,aAAa;IACrB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAID,QAAA,MAAM,qBAAqB,iBAAkB,aAAa,gBA+CzD,CAAA;AAED,QAAA,MAAM,gBAAgB;gCAjEX,MAAM,WACJ,cAAc,SAChB,WAAW,KACf,IAAI;uBACU,OAAO;CA6D4B,CAAA;AAExD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,CAAA"}
|