Skip to content

Commit 65dfd47

Browse files
committed
[ui] Slider: Replace blueprint with radix
1 parent 02a8ad8 commit 65dfd47

8 files changed

Lines changed: 377 additions & 184 deletions

File tree

js_modules/dagster-ui/packages/ui-components/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"styled-components": "^5.3.3"
3535
},
3636
"dependencies": {
37+
"@radix-ui/react-slider": "^1.2.2",
3738
"@react-hook/resize-observer": "^1.2.6",
3839
"amator": "^1.1.0",
3940
"clsx": "^2.1.1",
Lines changed: 52 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,58 @@
1-
// eslint-disable-next-line no-restricted-imports
2-
import {
3-
MultiSlider as BlueprintMultiSlider,
4-
MultiSliderProps as BlueprintMultiSliderProps,
5-
Slider as BlueprintSlider,
6-
SliderProps as BlueprintSliderProps,
7-
} from '@blueprintjs/core';
1+
import * as RadixSlider from '@radix-ui/react-slider';
82
import * as React from 'react';
9-
import styled, {css} from 'styled-components';
103

114
import {Colors} from './Color';
12-
13-
interface SliderProps extends BlueprintSliderProps {
14-
fillColor?: string;
15-
}
16-
17-
export const Slider = ({fillColor = Colors.accentGray(), ...rest}: SliderProps) => {
18-
return <StyledSlider {...rest} intent="none" $fillColor={fillColor} />;
19-
};
20-
21-
interface MultiSliderProps extends BlueprintMultiSliderProps {
22-
fillColor?: string;
23-
children: React.ReactNode;
5+
import styles from './css/Slider.module.css';
6+
7+
interface Props {
8+
value: number;
9+
onChange: (value: number) => void;
10+
min?: number;
11+
max?: number;
12+
step: number;
13+
orientation?: 'horizontal' | 'vertical';
14+
trackColor?: string;
15+
rangeColor?: string;
16+
disabled?: boolean;
17+
name?: string;
2418
}
2519

26-
export const MultiSlider = ({fillColor = Colors.accentGray(), ...rest}: MultiSliderProps) => {
27-
return <StyledMultiSlider {...rest} intent="none" $fillColor={fillColor} />;
20+
export const Slider = (props: Props) => {
21+
const {
22+
value,
23+
onChange,
24+
min = 0,
25+
max = 100,
26+
step,
27+
orientation = 'horizontal',
28+
trackColor = Colors.backgroundGray(),
29+
rangeColor = Colors.accentGray(),
30+
disabled = false,
31+
name,
32+
} = props;
33+
34+
const style = {
35+
'--slider-track-color': trackColor,
36+
'--slider-range-color': rangeColor,
37+
} as React.CSSProperties;
38+
39+
return (
40+
<RadixSlider.Root
41+
className={styles.slider}
42+
style={style}
43+
value={[value]}
44+
onValueChange={([first]) => first !== undefined && onChange(first)}
45+
min={min}
46+
max={max}
47+
step={step}
48+
disabled={disabled}
49+
orientation={orientation}
50+
name={name}
51+
>
52+
<RadixSlider.Track className={styles.track}>
53+
<RadixSlider.Range className={styles.range} />
54+
</RadixSlider.Track>
55+
<RadixSlider.Thumb className={styles.thumb} />
56+
</RadixSlider.Root>
57+
);
2858
};
29-
30-
MultiSlider.Handle = BlueprintMultiSlider.Handle;
31-
32-
export const SliderStyles = css<{$fillColor: string}>`
33-
.bp5-slider-track {
34-
height: 8px;
35-
.bp5-slider-progress {
36-
background-color: ${(p) => p.$fillColor};
37-
opacity: 0.4;
38-
height: 8px;
39-
}
40-
.bp5-slider-progress.bp5-intent-primary {
41-
background-color: ${(p) => p.$fillColor};
42-
opacity: 1;
43-
height: 8px;
44-
}
45-
}
46-
&.bp5-vertical {
47-
width: 20px;
48-
min-width: 20px;
49-
}
50-
&.bp5-vertical .bp5-slider-track,
51-
&.bp5-vertical .bp5-slider-track .bp5-slider-progress {
52-
height: initial;
53-
width: 8px;
54-
}
55-
.bp5-slider-handle {
56-
width: 18px;
57-
height: 18px;
58-
border-radius: 50%;
59-
border: 2px solid ${Colors.accentGray()};
60-
background: ${Colors.backgroundLighter()};
61-
box-shadow: none;
62-
&:hover {
63-
border: 2px solid ${Colors.accentGrayHover()};
64-
box-shadow: ${Colors.shadowDefault()} 0px 2px 12px 0px;
65-
}
66-
67-
.bp5-slider-label {
68-
background: ${Colors.accentBlue()};
69-
box-shadow: 0 1px 4px ${Colors.shadowDefault()};
70-
padding: 4px 8px;
71-
}
72-
}
73-
`;
74-
75-
const StyledMultiSlider = styled(BlueprintMultiSlider)<{$fillColor: string}>`
76-
${SliderStyles}
77-
`;
78-
const StyledSlider = styled(BlueprintSlider)<{$fillColor: string}>`
79-
${SliderStyles}
80-
`;

js_modules/dagster-ui/packages/ui-components/src/components/__stories__/MiddleTruncate.stories.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ export const Simple = () => {
3131
<Slider
3232
min={200}
3333
max={600}
34-
stepSize={10}
34+
step={10}
3535
value={controlledWidth}
36-
labelRenderer={false}
3736
onChange={(value) => setControlledWidth(value)}
3837
/>
3938
<div ref={sizer} style={{width: controlledWidth}}>
@@ -147,9 +146,8 @@ export const Containers = () => {
147146
<Slider
148147
min={200}
149148
max={600}
150-
stepSize={10}
149+
step={10}
151150
value={controlledWidth}
152-
labelRenderer={false}
153151
onChange={(value) => setControlledWidth(value)}
154152
/>
155153
<div ref={sizer} style={{width: controlledWidth}}>
Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {useState} from 'react';
22

3-
import {Colors} from '../Color';
4-
import {Group} from '../Group';
5-
import {MultiSlider, Slider} from '../Slider';
3+
import {Box} from '../Box';
4+
import {Slider} from '../Slider';
65

76
// eslint-disable-next-line import/no-default-export
87
export default {
@@ -12,58 +11,21 @@ export default {
1211

1312
export const Sizes = () => {
1413
const [value, setValue] = useState(2);
15-
const [minValue, setMinValue] = useState(1);
1614

1715
return (
18-
<Group direction="column" spacing={32}>
19-
<Slider value={value} onChange={setValue} max={10} labelStepSize={2} />
20-
21-
<Slider
22-
vertical
23-
min={0}
24-
max={10}
25-
stepSize={1}
26-
value={value}
27-
labelRenderer={false}
28-
onChange={setValue}
29-
/>
30-
31-
<MultiSlider
32-
min={0}
33-
max={10}
34-
stepSize={0.1}
35-
labelRenderer={(value: number) => (
36-
<span style={{whiteSpace: 'nowrap'}}>Value: {value.toFixed(1)}</span>
37-
)}
38-
onChange={(values: number[]) => {
39-
const [first] = values;
40-
if (typeof first === 'number') {
41-
setValue(first);
42-
}
43-
}}
44-
>
45-
<MultiSlider.Handle value={value} type="full" intentAfter="primary" />
46-
</MultiSlider>
47-
48-
<MultiSlider
49-
min={0}
50-
max={10}
51-
stepSize={0.01}
52-
fillColor={Colors.accentBlue()}
53-
labelRenderer={(value: number) => (
54-
<span style={{whiteSpace: 'nowrap'}}>Value: {value.toFixed(1)}</span>
55-
)}
56-
onChange={(values: number[]) => {
57-
const [first, second] = values;
58-
if (typeof first === 'number' && typeof second === 'number') {
59-
setMinValue(Math.min(first, second));
60-
setValue(Math.max(first, second));
61-
}
62-
}}
63-
>
64-
<MultiSlider.Handle value={minValue} type="full" intentAfter="primary" />
65-
<MultiSlider.Handle value={value} type="full" intentBefore="primary" />
66-
</MultiSlider>
67-
</Group>
16+
<Box flex={{direction: 'column', gap: 32}}>
17+
<Slider value={value} onChange={setValue} min={0} max={10} step={0.1} />
18+
<div style={{height: 200}}>
19+
<Slider
20+
orientation="vertical"
21+
min={0}
22+
max={10}
23+
step={1}
24+
value={value}
25+
onChange={setValue}
26+
/>
27+
</div>
28+
<Slider disabled value={value} onChange={setValue} min={0} max={10} step={0.01} />
29+
</Box>
6830
);
6931
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/* Slider Root */
2+
.slider {
3+
--slider-track-height: 8px;
4+
--slider-handle-size: 18px;
5+
--slider-track-color: var(--color-background-gray);
6+
--slider-range-color: var(--color-accent-gray);
7+
8+
position: relative;
9+
display: flex;
10+
align-items: center;
11+
user-select: none;
12+
touch-action: none;
13+
width: 100%;
14+
}
15+
16+
.slider[data-orientation='vertical'] {
17+
flex-direction: column;
18+
width: 20px;
19+
min-width: 20px;
20+
height: 100%;
21+
}
22+
23+
/* Track - the background rail */
24+
.track {
25+
position: relative;
26+
flex-grow: 1;
27+
background-color: var(--slider-track-color);
28+
height: var(--slider-track-height);
29+
border-radius: 4px;
30+
}
31+
32+
.slider[data-orientation='vertical'] .track {
33+
width: var(--slider-track-height);
34+
}
35+
36+
/* Range - the filled portion */
37+
.range {
38+
position: absolute;
39+
background-color: var(--slider-range-color);
40+
opacity: 1;
41+
height: 100%;
42+
border-radius: 4px;
43+
}
44+
45+
.slider[data-orientation='vertical'] .range {
46+
width: 100%;
47+
height: auto;
48+
}
49+
50+
/* Thumb - the draggable handle */
51+
.thumb {
52+
display: block;
53+
width: var(--slider-handle-size);
54+
height: var(--slider-handle-size);
55+
border-radius: 50%;
56+
border: 2px solid var(--color-accent-gray);
57+
background: var(--color-background-lighter);
58+
box-shadow: none;
59+
transition:
60+
border-color 100ms,
61+
box-shadow 150ms;
62+
cursor: pointer;
63+
}
64+
65+
.thumb:hover {
66+
border-color: var(--color-accent-gray-hover);
67+
box-shadow: var(--color-shadow-default) 0px 2px 12px 0px;
68+
}
69+
70+
.thumb:focus,
71+
.thumb:focus-visible {
72+
outline: none;
73+
box-shadow: var(--color-focus-ring) 0 0 0 2px;
74+
}
75+
76+
.thumb[data-disabled] {
77+
opacity: 0.5;
78+
cursor: default;
79+
}
80+
81+
.slider[data-disabled] {
82+
opacity: 0.5;
83+
}
Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,19 @@
1-
import {Colors, SliderStyles} from '@dagster-io/ui-components';
1+
import {Colors, Slider} from '@dagster-io/ui-components';
22
import * as React from 'react';
3-
import styled from 'styled-components';
43

54
/**
65
* Renders a horizontal slider that lets you adjust the graph's relative zoom from 1-100.
7-
* It uses Blueprint CSS but not the Slider component, because that renders twice and
8-
* forces a DOM layout to determine it's size (I think for tick marks, which we aren't using)
96
*/
107
export const ZoomSlider = React.memo((props: {value: number; onChange: (v: number) => void}) => {
118
return (
12-
<ZoomSliderContainer
13-
$fillColor={Colors.accentGray()}
14-
className="bp5-slider bp5-slider-unlabeled"
15-
onMouseDown={(e: React.MouseEvent) => {
16-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17-
const rect = e.currentTarget.closest('.bp5-slider')!.getBoundingClientRect();
18-
19-
let initialX: number;
20-
if (e.target instanceof HTMLElement && e.target.classList.contains('bp5-slider-handle')) {
21-
initialX = e.pageX;
22-
} else {
23-
initialX = rect.left + (props.value / 100) * rect.width;
24-
}
25-
26-
const onUpdate = (e: MouseEvent) => {
27-
const nextValue = props.value + (e.pageX - initialX) * (100 / rect.width);
28-
props.onChange(Math.max(0, Math.min(100, nextValue)));
29-
};
30-
const onRelease = (e: MouseEvent) => {
31-
onUpdate(e);
32-
document.removeEventListener('mousemove', onUpdate);
33-
document.removeEventListener('mouseup', onRelease);
34-
};
35-
document.addEventListener('mousemove', onUpdate);
36-
document.addEventListener('mouseup', onRelease);
37-
}}
38-
>
39-
<div className="bp5-slider-track">
40-
<div className="bp5-slider-progress" style={{left: 0, right: 0, top: 0}} />
41-
<div
42-
className="bp5-slider-progress bp5-intent-primary"
43-
style={{left: 0, right: `${100 - props.value}%`, top: 0}}
44-
/>
45-
</div>
46-
<div className="bp5-slider-axis" />
47-
<span
48-
className="bp5-slider-handle"
49-
style={{left: `calc(${props.value}% - 8px)`}}
50-
tabIndex={0}
51-
/>
52-
</ZoomSliderContainer>
9+
<Slider
10+
value={props.value}
11+
onChange={props.onChange}
12+
min={0}
13+
max={100}
14+
step={0.1}
15+
trackColor={Colors.backgroundGray()}
16+
rangeColor={Colors.accentGray()}
17+
/>
5318
);
5419
});
55-
56-
const ZoomSliderContainer = styled.div`
57-
${SliderStyles}
58-
`;

0 commit comments

Comments
 (0)