Skip to content

Commit 1b233af

Browse files
committed
feat(range): add custom ticks & correct rect size
1 parent d0a191a commit 1b233af

File tree

18 files changed

+476
-145
lines changed

18 files changed

+476
-145
lines changed

packages/ods-react/src/components/range/src/components/range-bounds/RangeBounds.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import classNames from 'classnames';
22
import { type FC, type JSX } from 'react';
3+
import { useRange } from '../../contexts/useRange';
34
import style from './rangeBounds.module.scss';
45

56
interface RangeBoundsProp {
6-
disabled?: boolean,
77
max: number,
88
min: number
99
}
1010

1111
const RangeBounds: FC<RangeBoundsProp> = ({
12-
disabled,
1312
max,
1413
min,
1514
}): JSX.Element => {
15+
const { disabled } = useRange();
16+
1617
return (
1718
<div className={ classNames(
1819
style['range-bounds'],

packages/ods-react/src/components/range/src/components/range-bounds/rangeBounds.module.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
@use '../../../../../style/range';
2+
13
@layer ods-atoms {
24
.range-bounds {
35
display: flex;
46
justify-content: space-between;
5-
padding-top: 8px;
7+
padding-top: range.$ods-range-label-padding-top;
68
color: var(--ods-color-text);
79
font-weight: 600;
810

packages/ods-react/src/components/range/src/components/range-thumb/RangeThumb.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@ import classNames from 'classnames';
33
import { type FC, type JSX, type KeyboardEvent, useRef, useState } from 'react';
44
import { useFormField } from '../../../../form-field/src';
55
import { Tooltip, TooltipContent, TooltipTrigger } from '../../../../tooltip/src';
6+
import { useRange } from '../../contexts/useRange';
67
import style from './rangeThumb.module.scss';
78

89
interface RangeThumbProp {
9-
disabled?: boolean,
10+
displayTooltip?: boolean,
1011
index: number,
1112
invalid?: boolean,
1213
}
1314

1415
const RangeThumb: FC<RangeThumbProp> = ({
15-
disabled,
16+
displayTooltip,
1617
index,
1718
invalid,
1819
}): JSX.Element => {
1920
const thumbRef = useRef<HTMLDivElement>(null);
2021
const fieldContext = useFormField();
2122
const { value } = useSliderContext();
23+
const { disabled } = useRange();
2224
const [isFocused, setIsFocused] = useState(false);
2325
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
2426

@@ -39,11 +41,11 @@ const RangeThumb: FC<RangeThumbProp> = ({
3941
}
4042

4143
return (
42-
<Tooltip open={ !disabled && (isFocused || isTooltipOpen) }>
44+
<Tooltip open={ displayTooltip && !disabled && (isFocused || isTooltipOpen) }>
4345
<TooltipTrigger asChild>
4446
<Slider.Thumb
45-
aria-invalid={ invalid }
4647
aria-describedby={ fieldContext?.ariaDescribedBy }
48+
aria-invalid={ invalid }
4749
aria-labelledby={ index === 0 ? fieldContext?.labelId : undefined }
4850
className={ classNames(
4951
style['range-thumb'],
@@ -57,8 +59,7 @@ const RangeThumb: FC<RangeThumbProp> = ({
5759
onMouseLeave={ () => setIsTooltipOpen(false) }
5860
onMouseOver={ () => setIsTooltipOpen(true) }
5961
ref={ thumbRef }
60-
role="slider"
61-
>
62+
role="slider">
6263
<Slider.HiddenInput />
6364
</Slider.Thumb>
6465
</TooltipTrigger>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Slider } from '@ark-ui/react/slider';
2+
import classNames from 'classnames';
3+
import { type FC, type JSX, useEffect, useRef } from 'react';
4+
import { THUMB_SIZE } from '../../constants/thumb';
5+
import { type RangeTickItem, useRange } from '../../contexts/useRange';
6+
import style from './rangeTick.module.scss';
7+
8+
interface RangeTickProp {
9+
index: number,
10+
isLast: boolean,
11+
singleMode: boolean,
12+
tick: RangeTickItem,
13+
}
14+
15+
const RangeTick: FC<RangeTickProp> = ({
16+
index,
17+
isLast,
18+
singleMode,
19+
tick,
20+
}): JSX.Element => {
21+
const { disabled, setRootPadding } = useRange();
22+
const tickRef = useRef<HTMLSpanElement>(null);
23+
const isNumber = typeof tick === 'number';
24+
25+
useEffect(() => {
26+
if (!tickRef.current || typeof tick === 'number') {
27+
return;
28+
}
29+
30+
const resizeObserver = new ResizeObserver((entries) => {
31+
if (entries && entries.length) {
32+
const { height, top, width } = entries[0].contentRect;
33+
34+
if (index === 0) {
35+
setRootPadding((padding) => ({
36+
...padding,
37+
left: (width / 2) - (THUMB_SIZE / 2),
38+
}));
39+
} else if (isLast) {
40+
setRootPadding((padding) => ({
41+
...padding,
42+
right: (width / 2) - (THUMB_SIZE / 2),
43+
}));
44+
}
45+
46+
setRootPadding((padding) => ({
47+
...padding,
48+
bottom: height + top,
49+
}));
50+
}
51+
});
52+
53+
resizeObserver.observe(tickRef.current);
54+
55+
return () => {
56+
resizeObserver.disconnect();
57+
};
58+
}, [index, isLast, setRootPadding, tick, tickRef]);
59+
60+
return (
61+
<Slider.Marker
62+
className={ classNames(
63+
style['range-tick'],
64+
{ [style['range-tick--custom-marker']]: !isNumber },
65+
{ [style['range-tick--single-mode']]: singleMode },
66+
)}
67+
ref={ tickRef }
68+
value={ isNumber ? tick : tick.value }>
69+
{
70+
!isNumber &&
71+
<span className={ classNames(
72+
style['range-tick__label'],
73+
{ [style['range-tick__label--disabled']]: disabled },
74+
)}>
75+
{ tick.label }
76+
</span>
77+
}
78+
</Slider.Marker>
79+
);
80+
};
81+
82+
RangeTick.displayName = 'RangeTick';
83+
84+
export {
85+
RangeTick,
86+
type RangeTickProp,
87+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@use 'sass:math';
2+
@use '../../../../../style/range';
3+
4+
@layer ods-atoms {
5+
.range-tick {
6+
$tick-width: 2px;
7+
8+
position: absolute;
9+
10+
&::before {
11+
display: block;
12+
position: absolute;
13+
bottom: -(math.div(range.$ods-range-thumb-size, 2) - math.div(range.$ods-range-track-height, 2));
14+
border-radius: 6px;
15+
background-color: range.$ods-range-background-color;
16+
width: $tick-width;
17+
height: range.$ods-range-thumb-size;
18+
content: '';
19+
}
20+
21+
&[data-state="at-value"],
22+
&--single-mode[data-state="under-value"] {
23+
&::before {
24+
background-color: range.$ods-range-background-color-active;
25+
}
26+
}
27+
28+
&--custom-marker {
29+
padding-top: range.$ods-range-label-padding-top;
30+
31+
&::before {
32+
top: -(range.$ods-range-track-height + math.div(range.$ods-range-thumb-size - range.$ods-range-track-height, 2));
33+
bottom: auto;
34+
left: calc(50% - ($tick-width / 2));
35+
}
36+
}
37+
38+
&__label {
39+
color: var(--ods-color-text);
40+
font-weight: 600;
41+
42+
&--disabled {
43+
color: var(--ods-color-text-disabled-default);
44+
}
45+
}
46+
}
47+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Slider } from '@ark-ui/react/slider';
2+
import { type FC, type JSX } from 'react';
3+
import { type RangeTickItem } from '../../contexts/useRange';
4+
import { RangeTick } from '../range-tick/RangeTick';
5+
import style from './rangeTicks.module.scss';
6+
7+
interface RangeTicksProp {
8+
singleMode: boolean,
9+
ticks: RangeTickItem[],
10+
}
11+
12+
const RangeTicks: FC<RangeTicksProp> = ({
13+
singleMode,
14+
ticks,
15+
}): JSX.Element => {
16+
return (
17+
<Slider.MarkerGroup className={ style['range-ticks'] }>
18+
{
19+
ticks.map((tick, i) => (
20+
<RangeTick
21+
index={ i }
22+
isLast={ i === ticks.length - 1 }
23+
key={ typeof tick === 'number' ? tick : tick.value }
24+
singleMode={ singleMode }
25+
tick={ tick } />
26+
))
27+
}
28+
</Slider.MarkerGroup>
29+
);
30+
};
31+
32+
RangeTicks.displayName = 'RangeTicks';
33+
34+
export {
35+
RangeTicks,
36+
type RangeTicksProp,
37+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@layer ods-atoms {
2+
.range-ticks {
3+
position: relative;
4+
}
5+
}

0 commit comments

Comments
 (0)