@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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* if we have range slider, there is a chance
|
|
3
|
+
* when we select values next to each other
|
|
4
|
+
* that the lables might overlap each other. Like this example:
|
|
5
|
+
* [ [ ] ]
|
|
6
|
+
* A B
|
|
7
|
+
*
|
|
8
|
+
* In that case we need to reposition the labels to the edges of the thumb.
|
|
9
|
+
* [ ] [ ]
|
|
10
|
+
* A B
|
|
11
|
+
*
|
|
12
|
+
* In next rerender when we recalculate the position of the labels the
|
|
13
|
+
* above example would end with result as not overlapping as the position was already changed.
|
|
14
|
+
* So we need to work with the previous result of this function `previousResult`. When
|
|
15
|
+
* true we need to add half of the width of the label to simulate the center position.
|
|
16
|
+
*
|
|
17
|
+
* This function checks and returns if the labels overlap.
|
|
18
|
+
* It is called on each render of the component and everytime the Slider changes value.
|
|
19
|
+
**/
|
|
20
|
+
export const checkOverlap = ({ firstLabelRect, secondLabelRect, previousResult, }) => {
|
|
21
|
+
const gap = 16;
|
|
22
|
+
const halfWidth1 = firstLabelRect.width / 2;
|
|
23
|
+
const halfWidth2 = secondLabelRect.width / 2;
|
|
24
|
+
const rightBoundaryOfFirstLabel = previousResult
|
|
25
|
+
? firstLabelRect.right + halfWidth1
|
|
26
|
+
: firstLabelRect.right;
|
|
27
|
+
const leftBoundaryOfSecondLabel = previousResult
|
|
28
|
+
? secondLabelRect.left - halfWidth2
|
|
29
|
+
: secondLabelRect.left;
|
|
30
|
+
return rightBoundaryOfFirstLabel + gap > leftBoundaryOfSecondLabel;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=check-overlap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-overlap.js","sourceRoot":"","sources":["../../../src/utils/check-overlap.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;IAkBI;AACJ,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,EAC3B,cAAc,EACd,eAAe,EACf,cAAc,GACI,EAAW,EAAE;IAC/B,MAAM,GAAG,GAAG,EAAE,CAAA;IACd,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,GAAG,CAAC,CAAA;IAC3C,MAAM,UAAU,GAAG,eAAe,CAAC,KAAK,GAAG,CAAC,CAAA;IAE5C,MAAM,yBAAyB,GAAG,cAAc;QAC9C,CAAC,CAAC,cAAc,CAAC,KAAK,GAAG,UAAU;QACnC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAA;IACxB,MAAM,yBAAyB,GAAG,cAAc;QAC9C,CAAC,CAAC,eAAe,CAAC,IAAI,GAAG,UAAU;QACnC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAA;IAExB,OAAO,yBAAyB,GAAG,GAAG,GAAG,yBAAyB,CAAA;AACpE,CAAC,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare type GetPositionProps = {
|
|
2
|
+
rect: DOMRect;
|
|
3
|
+
isOverlaped: boolean;
|
|
4
|
+
isFirstLabel: boolean;
|
|
5
|
+
currentPlacement: 'left' | 'right' | 'center';
|
|
6
|
+
};
|
|
7
|
+
export declare const getXPlacement: ({ rect: { width, left, right }, isOverlaped, isFirstLabel, currentPlacement, }: GetPositionProps) => 'left' | 'right' | 'center';
|
|
8
|
+
//# sourceMappingURL=get-x-placement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-x-placement.d.ts","sourceRoot":"","sources":["../../../src/utils/get-x-placement.ts"],"names":[],"mappings":"AAAA,oBAAY,gBAAgB,GAAG;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,OAAO,CAAA;IACpB,YAAY,EAAE,OAAO,CAAA;IACrB,gBAAgB,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;CAC9C,CAAA;AAED,eAAO,MAAM,aAAa,mFAKvB,gBAAgB,KAAG,MAAM,GAAG,OAAO,GAAG,QAexC,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const getXPlacement = ({ rect: { width, left, right }, isOverlaped, isFirstLabel, currentPlacement, }) => {
|
|
2
|
+
const gap = 16;
|
|
3
|
+
const halfWidth = width / 2;
|
|
4
|
+
const leftBoundary = currentPlacement === 'right' ? left - halfWidth : left;
|
|
5
|
+
const rightBoundary = currentPlacement === 'left' ? right + halfWidth : right;
|
|
6
|
+
if (leftBoundary < gap) {
|
|
7
|
+
return 'right';
|
|
8
|
+
}
|
|
9
|
+
else if (rightBoundary > window.innerWidth - gap) {
|
|
10
|
+
return 'left';
|
|
11
|
+
}
|
|
12
|
+
else if (isOverlaped) {
|
|
13
|
+
return isFirstLabel ? 'left' : 'right';
|
|
14
|
+
}
|
|
15
|
+
return 'center';
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=get-x-placement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-x-placement.js","sourceRoot":"","sources":["../../../src/utils/get-x-placement.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC5B,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAC5B,WAAW,EACX,YAAY,EACZ,gBAAgB,GACC,EAA+B,EAAE;IAClD,MAAM,GAAG,GAAG,EAAE,CAAA;IACd,MAAM,SAAS,GAAG,KAAK,GAAG,CAAC,CAAA;IAC3B,MAAM,YAAY,GAAG,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;IAC3E,MAAM,aAAa,GAAG,gBAAgB,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAA;IAE7E,IAAI,YAAY,GAAG,GAAG,EAAE;QACtB,OAAO,OAAO,CAAA;KACf;SAAM,IAAI,aAAa,GAAG,MAAM,CAAC,UAAU,GAAG,GAAG,EAAE;QAClD,OAAO,MAAM,CAAA;KACd;SAAM,IAAI,WAAW,EAAE;QACtB,OAAO,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA;KACvC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toptal/picasso-slider",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Toptal UI components library - Slider",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -22,18 +22,16 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://github.com/toptal/picasso/tree/master/packages/picasso#readme",
|
|
24
24
|
"dependencies": {
|
|
25
|
+
"@mui/base": "5.0.0-beta.40",
|
|
25
26
|
"@toptal/picasso-shared": "15.0.0",
|
|
26
27
|
"@toptal/picasso-tooltip": "1.1.3",
|
|
27
28
|
"@toptal/picasso-utils": "1.0.3",
|
|
28
|
-
"
|
|
29
|
+
"tailwind-merge": "^2.2.2"
|
|
29
30
|
},
|
|
30
|
-
"sideEffects":
|
|
31
|
-
"**/styles.ts",
|
|
32
|
-
"**/styles.js"
|
|
33
|
-
],
|
|
31
|
+
"sideEffects": false,
|
|
34
32
|
"peerDependencies": {
|
|
35
|
-
"@material-ui/core": "4.12.4",
|
|
36
33
|
"@toptal/picasso-provider": "*",
|
|
34
|
+
"@toptal/picasso-tailwind": ">2.1.0",
|
|
37
35
|
"react": ">=16.12.0 < 19.0.0"
|
|
38
36
|
},
|
|
39
37
|
"exports": {
|
package/src/Slider/Slider.tsx
CHANGED
|
@@ -1,75 +1,59 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import React, { forwardRef, useRef
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import cx from 'classnames'
|
|
8
|
-
import { useCombinedRefs } from '@toptal/picasso-utils'
|
|
1
|
+
// import type { ComponentProps } from 'react'
|
|
2
|
+
import React, { forwardRef, useRef } from 'react'
|
|
3
|
+
import { Slider as MUIBaseSlider } from '@mui/base/Slider'
|
|
4
|
+
import { useCombinedRefs, useOnScreen } from '@toptal/picasso-utils'
|
|
5
|
+
import { twJoin, twMerge } from 'tailwind-merge'
|
|
6
|
+
import type { BaseProps } from '@toptal/picasso-shared'
|
|
9
7
|
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import styles from './styles'
|
|
8
|
+
import SliderMark from '../SliderMark'
|
|
9
|
+
import SliderValueLabel from '../SliderValueLabel'
|
|
10
|
+
import { useLabelOverlap } from './hooks'
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
type Value = number | number[]
|
|
18
|
-
type ValueLabelDisplay = 'on' | 'auto' | 'off'
|
|
19
|
-
|
|
20
|
-
export interface Props extends ComponentProps<typeof MUISlider> {
|
|
12
|
+
export interface Props extends BaseProps {
|
|
21
13
|
/** Minimum slider value */
|
|
22
14
|
min?: number
|
|
23
15
|
/** Maximum slider value */
|
|
24
16
|
max?: number
|
|
25
17
|
/** Controlled value of the component */
|
|
26
|
-
value?:
|
|
27
|
-
/** The default value. Use when the component is not controlled
|
|
28
|
-
defaultValue?:
|
|
18
|
+
value?: number | number[]
|
|
19
|
+
/** The default value. Use when the component is not controlled */
|
|
20
|
+
defaultValue?: number | number[]
|
|
29
21
|
/** Step for the thumb movement */
|
|
30
22
|
step?: number
|
|
31
23
|
/** Whether marks are shown or not */
|
|
32
24
|
marks?: boolean
|
|
33
25
|
/** Whether component is disabled or not */
|
|
34
26
|
disabled?: boolean
|
|
35
|
-
// Workaround for https://github.com/mui-org/material-ui/issues/21889
|
|
36
|
-
/** The tooltip component. */
|
|
37
|
-
TooltipComponent?: React.ElementType<ValueLabelProps & { index: number }>
|
|
38
27
|
/** Controls when tooltip is displayed:
|
|
39
28
|
- **auto** the value tooltip will display when the thumb is hovered or focused.
|
|
40
29
|
- **on** will display persistently.
|
|
41
30
|
- **off** will never display
|
|
42
31
|
*/
|
|
43
|
-
tooltip?:
|
|
32
|
+
tooltip?: 'on' | 'auto' | 'off'
|
|
44
33
|
/** The format function the value tooltip's value. */
|
|
45
34
|
tooltipFormat?: string | ((value: number, index: number) => React.ReactNode)
|
|
46
|
-
/** Show a compact tooltip */
|
|
47
|
-
compact?: boolean
|
|
48
|
-
/** Disable the portal behavior of the tooltip. The children stay within it's parent */
|
|
49
|
-
disablePortal?: boolean
|
|
50
35
|
/** Callback invoked when slider changes its state. */
|
|
51
|
-
onChange?: (
|
|
36
|
+
onChange?: (
|
|
37
|
+
event: Event,
|
|
38
|
+
value: number | number[],
|
|
39
|
+
activeThumb: number
|
|
40
|
+
) => void
|
|
41
|
+
/** Callback invoked on focus */
|
|
42
|
+
onFocus?: (event: React.FocusEvent<HTMLElement>) => void
|
|
43
|
+
/** Callback invoked on blur */
|
|
44
|
+
onBlur?: (event: React.FocusEvent<HTMLElement>) => void
|
|
52
45
|
/** Hide thumb when value is undefined or null. Works only when the component is controlled. */
|
|
53
46
|
hideThumbOnEmpty?: boolean
|
|
54
47
|
/** Disable track highlight. */
|
|
55
48
|
disableTrackHighlight?: boolean
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<SliderValueLabel
|
|
65
|
-
{...props}
|
|
66
|
-
tooltip={tooltip}
|
|
67
|
-
disablePortal={disablePortal}
|
|
68
|
-
compact={compact}
|
|
69
|
-
/>
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
return ValueLableComponent
|
|
49
|
+
/**
|
|
50
|
+
* Name attribute of the `input` element.
|
|
51
|
+
*/
|
|
52
|
+
name?: string
|
|
53
|
+
/**
|
|
54
|
+
* Id attribute of the `input` element.
|
|
55
|
+
*/
|
|
56
|
+
id?: string
|
|
73
57
|
}
|
|
74
58
|
|
|
75
59
|
export const Slider = forwardRef<HTMLElement, Props>(function Slider(
|
|
@@ -82,73 +66,103 @@ export const Slider = forwardRef<HTMLElement, Props>(function Slider(
|
|
|
82
66
|
marks,
|
|
83
67
|
value,
|
|
84
68
|
defaultValue = 0,
|
|
85
|
-
tooltip,
|
|
69
|
+
tooltip = 'off',
|
|
86
70
|
tooltipFormat,
|
|
87
|
-
compact,
|
|
88
|
-
TooltipComponent: UserDefinedTooltip,
|
|
89
71
|
step,
|
|
90
72
|
disabled,
|
|
91
|
-
disablePortal,
|
|
92
73
|
onChange,
|
|
74
|
+
onBlur,
|
|
75
|
+
onFocus,
|
|
93
76
|
hideThumbOnEmpty,
|
|
94
77
|
disableTrackHighlight,
|
|
95
|
-
|
|
78
|
+
className,
|
|
79
|
+
style,
|
|
80
|
+
name,
|
|
81
|
+
id,
|
|
82
|
+
'data-private': dataPrivate,
|
|
83
|
+
'data-testid': dataTestid,
|
|
96
84
|
} = props
|
|
97
|
-
const
|
|
98
|
-
wrapper,
|
|
99
|
-
markTrack,
|
|
100
|
-
hideThumb,
|
|
101
|
-
markInactive,
|
|
102
|
-
unmarkTrack,
|
|
103
|
-
...classes
|
|
104
|
-
} = useStyles()
|
|
85
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
105
86
|
const sliderRef = useCombinedRefs<HTMLElement>(ref, useRef<HTMLElement>(null))
|
|
87
|
+
const { isPartiallyOverlapped, handleValueLabelOnRender } = useLabelOverlap({
|
|
88
|
+
value,
|
|
89
|
+
})
|
|
106
90
|
|
|
107
91
|
const isThumbHidden =
|
|
108
92
|
hideThumbOnEmpty && (typeof value === 'undefined' || value === null)
|
|
109
93
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
DefaultValueLabelComponent) as unknown as React.ElementType<MUIValueLabelProps>
|
|
94
|
+
// The rootMargin is not working correctly in the storybooks iframe
|
|
95
|
+
// To test properly we can open the iframe in new window
|
|
96
|
+
const isContainerOnScreen = useOnScreen({
|
|
97
|
+
ref: containerRef,
|
|
98
|
+
rootMargin: '-24px 0px 0px 0px',
|
|
99
|
+
threshold: 1,
|
|
100
|
+
})
|
|
118
101
|
|
|
119
102
|
return (
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
103
|
+
<div
|
|
104
|
+
ref={containerRef}
|
|
105
|
+
className={twMerge('my-[6px] mx-0', className)}
|
|
106
|
+
style={style}
|
|
107
|
+
>
|
|
108
|
+
<MUIBaseSlider
|
|
109
|
+
ref={sliderRef}
|
|
110
|
+
defaultValue={defaultValue}
|
|
111
|
+
value={value}
|
|
112
|
+
min={min}
|
|
113
|
+
max={max}
|
|
114
|
+
step={step}
|
|
115
|
+
marks={marks}
|
|
116
|
+
disabled={disabled}
|
|
117
|
+
data-testid={dataTestid}
|
|
118
|
+
data-private={dataPrivate}
|
|
119
|
+
onFocus={onFocus}
|
|
120
|
+
onBlur={onBlur}
|
|
121
|
+
name={name}
|
|
122
|
+
id={id}
|
|
123
|
+
slots={{
|
|
124
|
+
mark: SliderMark,
|
|
125
|
+
valueLabel: SliderValueLabel,
|
|
126
|
+
}}
|
|
127
|
+
slotProps={{
|
|
128
|
+
mark: {
|
|
129
|
+
// @ts-expect-error we have custom Mark component, where we extend props and MUI does not understand it
|
|
130
|
+
forceInactive: disableTrackHighlight,
|
|
131
|
+
},
|
|
132
|
+
root: {
|
|
133
|
+
className:
|
|
134
|
+
'block cursor-pointer width-full relative py-[6px] -my-[6px]',
|
|
135
|
+
},
|
|
136
|
+
rail: {
|
|
137
|
+
className:
|
|
138
|
+
'block absolute w-full h-[1px] opacity-[0.24] rounded-none bg-gray-500',
|
|
139
|
+
},
|
|
140
|
+
thumb: {
|
|
141
|
+
className: twJoin(
|
|
142
|
+
'group/thumb flex justify-center items-center w-[15px] h-[15px]',
|
|
143
|
+
'rounded-[50%] bg-blue-500 border-[2px] border-solid border-white',
|
|
144
|
+
'-mt-[7px] -ml-[6px] outline-0 absolute transition-shadow cursor-pointer',
|
|
145
|
+
isThumbHidden && 'hidden'
|
|
146
|
+
),
|
|
147
|
+
role: 'slider',
|
|
148
|
+
},
|
|
149
|
+
track: {
|
|
150
|
+
className: twJoin(
|
|
151
|
+
'block absolute h-[1px]',
|
|
152
|
+
disableTrackHighlight ? 'bg-gray-200' : 'bg-blue-500'
|
|
153
|
+
),
|
|
154
|
+
},
|
|
155
|
+
valueLabel: {
|
|
156
|
+
tooltip,
|
|
157
|
+
onRender: handleValueLabelOnRender,
|
|
158
|
+
yPlacement: isContainerOnScreen ? 'top' : 'bottom',
|
|
159
|
+
isOverlaped: isPartiallyOverlapped,
|
|
160
|
+
},
|
|
161
|
+
}}
|
|
162
|
+
valueLabelFormat={tooltipFormat}
|
|
163
|
+
onChange={onChange}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
152
166
|
)
|
|
153
167
|
})
|
|
154
168
|
|
|
@@ -159,7 +173,6 @@ Slider.defaultProps = {
|
|
|
159
173
|
min: 0,
|
|
160
174
|
max: 100,
|
|
161
175
|
tooltip: 'off',
|
|
162
|
-
disablePortal: false,
|
|
163
176
|
}
|
|
164
177
|
|
|
165
178
|
export default Slider
|
|
@@ -3,33 +3,47 @@
|
|
|
3
3
|
exports[`Slider renders 1`] = `
|
|
4
4
|
<div>
|
|
5
5
|
<div
|
|
6
|
-
class="
|
|
6
|
+
class="my-[6px] mx-0"
|
|
7
7
|
>
|
|
8
8
|
<span
|
|
9
|
-
class="
|
|
9
|
+
class="base-Slider block cursor-pointer width-full relative py-[6px] -my"
|
|
10
10
|
>
|
|
11
11
|
<span
|
|
12
|
-
class="
|
|
12
|
+
class="base-Slider block absolute w-full h-[1px] opacity-[0.24] rounded-none bg-gray"
|
|
13
13
|
/>
|
|
14
14
|
<span
|
|
15
|
-
class="
|
|
15
|
+
class="base-Slider block absolute h-[1px] bg-blue"
|
|
16
16
|
style="left: 0%; width: 0%;"
|
|
17
17
|
/>
|
|
18
|
-
<input
|
|
19
|
-
type="hidden"
|
|
20
|
-
value="0"
|
|
21
|
-
/>
|
|
22
18
|
<span
|
|
23
|
-
|
|
24
|
-
aria-valuemax="100"
|
|
25
|
-
aria-valuemin="0"
|
|
26
|
-
aria-valuenow="0"
|
|
27
|
-
class="MuiSlider-thumb makeStyles-thumb MuiSlider-thumbColorPrimary"
|
|
19
|
+
class="base-Slider group/thumb flex justify-center items-center w-[15px] h-[15px] rounded-[50%] bg-blue border-[2px] border-solid border-white -mt -ml outline-0 absolute transition-shadow cursor-pointer"
|
|
28
20
|
data-index="0"
|
|
29
21
|
role="slider"
|
|
30
22
|
style="left: 0%;"
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
>
|
|
24
|
+
<input
|
|
25
|
+
aria-orientation="horizontal"
|
|
26
|
+
aria-valuemax="100"
|
|
27
|
+
aria-valuemin="0"
|
|
28
|
+
aria-valuenow="0"
|
|
29
|
+
data-index="0"
|
|
30
|
+
max="100"
|
|
31
|
+
min="0"
|
|
32
|
+
step="1"
|
|
33
|
+
style="border: 0px; height: 100%; margin: -1px; overflow: hidden; padding: 0px; position: absolute; white-space: nowrap; width: 100%; direction: ltr;"
|
|
34
|
+
type="range"
|
|
35
|
+
value="0"
|
|
36
|
+
/>
|
|
37
|
+
<span
|
|
38
|
+
class="absolute will-change transition-transform hidden top-[calc(100%+2px)] left-[calc(100%-13px)]"
|
|
39
|
+
>
|
|
40
|
+
<span
|
|
41
|
+
class="shadow-4 text-sm text-white bg-graphite m-1 rounded-sm py-[2px] px-2 max-w break-words"
|
|
42
|
+
>
|
|
43
|
+
0
|
|
44
|
+
</span>
|
|
45
|
+
</span>
|
|
46
|
+
</span>
|
|
33
47
|
</span>
|
|
34
48
|
</div>
|
|
35
49
|
</div>
|
|
@@ -38,33 +52,47 @@ exports[`Slider renders 1`] = `
|
|
|
38
52
|
exports[`Slider with initial value 1`] = `
|
|
39
53
|
<div>
|
|
40
54
|
<div
|
|
41
|
-
class="
|
|
55
|
+
class="my-[6px] mx-0"
|
|
42
56
|
>
|
|
43
57
|
<span
|
|
44
|
-
class="
|
|
58
|
+
class="base-Slider block cursor-pointer width-full relative py-[6px] -my"
|
|
45
59
|
>
|
|
46
60
|
<span
|
|
47
|
-
class="
|
|
61
|
+
class="base-Slider block absolute w-full h-[1px] opacity-[0.24] rounded-none bg-gray"
|
|
48
62
|
/>
|
|
49
63
|
<span
|
|
50
|
-
class="
|
|
64
|
+
class="base-Slider block absolute h-[1px] bg-blue"
|
|
51
65
|
style="left: 0%; width: 4%;"
|
|
52
66
|
/>
|
|
53
|
-
<input
|
|
54
|
-
type="hidden"
|
|
55
|
-
value="4"
|
|
56
|
-
/>
|
|
57
67
|
<span
|
|
58
|
-
|
|
59
|
-
aria-valuemax="100"
|
|
60
|
-
aria-valuemin="0"
|
|
61
|
-
aria-valuenow="4"
|
|
62
|
-
class="MuiSlider-thumb makeStyles-thumb MuiSlider-thumbColorPrimary"
|
|
68
|
+
class="base-Slider group/thumb flex justify-center items-center w-[15px] h-[15px] rounded-[50%] bg-blue border-[2px] border-solid border-white -mt -ml outline-0 absolute transition-shadow cursor-pointer"
|
|
63
69
|
data-index="0"
|
|
64
70
|
role="slider"
|
|
65
71
|
style="left: 4%;"
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
>
|
|
73
|
+
<input
|
|
74
|
+
aria-orientation="horizontal"
|
|
75
|
+
aria-valuemax="100"
|
|
76
|
+
aria-valuemin="0"
|
|
77
|
+
aria-valuenow="4"
|
|
78
|
+
data-index="0"
|
|
79
|
+
max="100"
|
|
80
|
+
min="0"
|
|
81
|
+
step="1"
|
|
82
|
+
style="border: 0px; height: 100%; margin: -1px; overflow: hidden; padding: 0px; position: absolute; white-space: nowrap; width: 100%; direction: ltr;"
|
|
83
|
+
type="range"
|
|
84
|
+
value="4"
|
|
85
|
+
/>
|
|
86
|
+
<span
|
|
87
|
+
class="absolute will-change transition-transform hidden top-[calc(100%+2px)] left-[calc(100%-13px)]"
|
|
88
|
+
>
|
|
89
|
+
<span
|
|
90
|
+
class="shadow-4 text-sm text-white bg-graphite m-1 rounded-sm py-[2px] px-2 max-w break-words"
|
|
91
|
+
>
|
|
92
|
+
4
|
|
93
|
+
</span>
|
|
94
|
+
</span>
|
|
95
|
+
</span>
|
|
68
96
|
</span>
|
|
69
97
|
</div>
|
|
70
98
|
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './use-label-overlap'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { RefObject } from 'react'
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { checkOverlap } from '../../utils'
|
|
5
|
+
|
|
6
|
+
export const useLabelOverlap = ({ value }: { value?: number | number[] }) => {
|
|
7
|
+
const [isPartiallyOverlapped, setIsPartiallyOverlapped] = useState(false)
|
|
8
|
+
const [valueLabels, setValueLabels] = useState<RefObject<HTMLSpanElement>[]>(
|
|
9
|
+
[]
|
|
10
|
+
)
|
|
11
|
+
const isRangeSlider = Array.isArray(value)
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!isRangeSlider) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
const isFullyOverlaped = value[0] === value[1]
|
|
18
|
+
|
|
19
|
+
if (isFullyOverlaped) {
|
|
20
|
+
setIsPartiallyOverlapped(false)
|
|
21
|
+
} else {
|
|
22
|
+
if (!(valueLabels[0]?.current && valueLabels[1]?.current)) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setIsPartiallyOverlapped(
|
|
27
|
+
checkOverlap({
|
|
28
|
+
firstLabelRect: valueLabels[0].current.getBoundingClientRect(),
|
|
29
|
+
secondLabelRect: valueLabels[1].current.getBoundingClientRect(),
|
|
30
|
+
previousResult: isPartiallyOverlapped,
|
|
31
|
+
})
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
}, [value, isRangeSlider, isPartiallyOverlapped, valueLabels])
|
|
35
|
+
|
|
36
|
+
const handleValueLabelOnRender = useCallback(
|
|
37
|
+
(index: number, labelRef: RefObject<HTMLSpanElement>) => {
|
|
38
|
+
setValueLabels(valLabels => {
|
|
39
|
+
valLabels[index] = labelRef
|
|
40
|
+
|
|
41
|
+
return valLabels
|
|
42
|
+
})
|
|
43
|
+
},
|
|
44
|
+
[setValueLabels]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return { isPartiallyOverlapped, handleValueLabelOnRender }
|
|
48
|
+
}
|
package/src/Slider/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ const Example = () => {
|
|
|
14
14
|
<Grid.Item sm={6}>
|
|
15
15
|
<Grid alignItems='center'>
|
|
16
16
|
<Grid.Item sm>
|
|
17
|
-
<Slider value={value} onChange={handleChange} max={100}
|
|
17
|
+
<Slider value={value} onChange={handleChange} max={100} />
|
|
18
18
|
</Grid.Item>
|
|
19
19
|
<Grid.Item>
|
|
20
20
|
<Typography size='medium'>{value}</Typography>
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
import { Container, Slider } from '@toptal/picasso'
|
|
3
3
|
|
|
4
4
|
type Value = number | number[]
|
|
5
5
|
|
|
6
6
|
const Example = () => {
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const [value, setValue] = useState<Value>(10)
|
|
8
|
+
const handleChange = (_: React.ChangeEvent<{}>, val: Value) => {
|
|
9
|
+
window.console.log('onChange: ', val)
|
|
10
|
+
setValue(val)
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
return (
|
|
12
14
|
<Container>
|
|
13
|
-
<Slider
|
|
15
|
+
<Slider
|
|
16
|
+
value={value}
|
|
17
|
+
step={10}
|
|
18
|
+
marks
|
|
19
|
+
min={10}
|
|
20
|
+
max={110}
|
|
21
|
+
onChange={handleChange}
|
|
22
|
+
/>
|
|
14
23
|
</Container>
|
|
15
24
|
)
|
|
16
25
|
}
|