Skip to content

Commit 6949a14

Browse files
committed
Simplify responsive & resizing inputs in sliders
1 parent 2dd3531 commit 6949a14

File tree

2 files changed

+41
-67
lines changed

2 files changed

+41
-67
lines changed

components/dash-core-components/src/components/css/sliders.css

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
align-items: center;
186186
gap: 16px;
187187
width: 100%;
188+
container-type: inline-size;
188189
}
189190

190191
.dash-slider-wrapper {
@@ -208,7 +209,8 @@
208209
}
209210

210211
.dash-range-slider-input {
211-
width: 64px;
212+
min-width: 5cqw; /* 5% of container width */
213+
max-width: 15cqw; /* 15% of container width */
212214
text-align: center;
213215
-webkit-appearance: textfield;
214216
-moz-appearance: textfield;
@@ -231,3 +233,19 @@
231233
-webkit-appearance: none;
232234
margin: 0;
233235
}
236+
237+
/* Hide inputs on small containers using container queries */
238+
/* single slider input */
239+
@container (max-width: 200px) {
240+
.dash-slider-container .dash-range-slider-input:only-of-type {
241+
display: none;
242+
}
243+
}
244+
245+
/* range slider inputs */
246+
@container (max-width: 300px) {
247+
.dash-slider-container .dash-range-slider-min-input,
248+
.dash-slider-container .dash-range-slider-max-input {
249+
display: none;
250+
}
251+
}

components/dash-core-components/src/fragments/RangeSlider.tsx

Lines changed: 22 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ export default function RangeSlider(props: RangeSliderProps) {
4545
// For range slider, we expect an array of values
4646
const [value, setValue] = useState<number[]>(propValue || []);
4747

48-
// Track slider dimension (width for horizontal, height for vertical) for conditional input rendering
48+
// Track slider dimension (width for horizontal, height for vertical) for marks rendering
4949
const [sliderWidth, setSliderWidth] = useState<number | null>(null);
50-
const [showInputs, setShowInputs] = useState<boolean>(value.length === 2);
5150

5251
const sliderRef = useRef<HTMLDivElement>(null);
5352
const inputRef = useRef<HTMLInputElement>(null);
@@ -64,42 +63,19 @@ export default function RangeSlider(props: RangeSliderProps) {
6463
}
6564
}, []);
6665

67-
// Dynamic dimension detection using ResizeObserver (width for horizontal, height for vertical)
66+
// Dynamic dimension detection using ResizeObserver for marks rendering
6867
useEffect(() => {
6968
if (!sliderRef.current) {
7069
return;
7170
}
7271

73-
if (step === null) {
74-
// If the user has explicitly disabled stepping (step=None), then
75-
// the slider values are constrained to the given marks and user
76-
// cannot enter arbitrary values via the input element
77-
setShowInputs(false);
78-
return;
79-
}
80-
81-
if (!value || value.length > 2) {
82-
setShowInputs(false);
83-
return;
84-
}
85-
8672
const measureWidth = () => {
8773
if (sliderRef.current) {
8874
const rect = sliderRef.current.getBoundingClientRect();
8975
// Use height for vertical sliders, width for horizontal sliders
9076
const dimension = vertical ? rect.height : rect.width;
9177
if (dimension > 0) {
9278
setSliderWidth(dimension);
93-
94-
// eslint-disable-next-line no-magic-numbers
95-
const HIDE_AT_WIDTH = value.length === 1 ? 200 : 250;
96-
// eslint-disable-next-line no-magic-numbers
97-
const SHOW_AT_WIDTH = value.length === 1 ? 300 : 450;
98-
if (showInputs && dimension < HIDE_AT_WIDTH) {
99-
setShowInputs(false);
100-
} else if (!showInputs && dimension >= SHOW_AT_WIDTH) {
101-
setShowInputs(true);
102-
}
10379
}
10480
}
10581
};
@@ -119,7 +95,7 @@ export default function RangeSlider(props: RangeSliderProps) {
11995
return () => {
12096
resizeObserver.disconnect();
12197
};
122-
}, [showInputs, vertical, step, value]);
98+
}, [vertical]);
12399

124100
// Handle prop value changes - equivalent to componentWillReceiveProps
125101
useEffect(() => {
@@ -168,49 +144,23 @@ export default function RangeSlider(props: RangeSliderProps) {
168144
});
169145
}, [min, max, processedMarks, step, sliderWidth]);
170146

171-
// Calculate dynamic input width based on digits needed and container size
147+
// Calculate dynamic input width based on min/max values
172148
const inputWidth = useMemo(() => {
173-
if (!sliderWidth) {
174-
return '64px'; // fallback to current width
175-
}
176-
177-
// Count digits needed for min and max values
178-
const maxDigits = Math.max(
179-
String(Math.floor(Math.abs(minMaxValues.max_mark))).length,
180-
String(Math.floor(Math.abs(minMaxValues.min_mark))).length
149+
const maxIntegerChars = Math.max(
150+
String(Math.floor(minMaxValues.max_mark)).length,
151+
String(Math.floor(minMaxValues.min_mark)).length
181152
);
182153

183-
// Add 1 for minus sign if min is negative
184-
const totalChars = maxDigits + (minMaxValues.min_mark < 0 ? 1 : 0);
185-
186-
// Calculate width as percentage of container (5% min, 15% max)
187-
/* eslint-disable no-magic-numbers */
188-
const minWidth = sliderWidth * 0.05;
189-
const maxWidth = sliderWidth * 0.15;
190-
const charBasedWidth = totalChars * 12; // approx 12px per character
191-
/* eslint-enable no-magic-numbers */
192-
193-
const calculatedWidth = Math.max(
194-
minWidth,
195-
Math.min(maxWidth, charBasedWidth)
154+
const maxDecimalChars = Math.min(
155+
String(stepValue).split('.')[1]?.length + 1 ?? 0,
156+
3
196157
);
197158

198-
// Add padding if box-sizing is border-box
199-
let inputPadding = 0;
200-
if (inputRef.current) {
201-
const computedStyle = window.getComputedStyle(inputRef.current);
202-
if (computedStyle.boxSizing === 'border-box') {
203-
const paddingLeft = parseFloat(computedStyle.paddingLeft) || 0;
204-
const paddingRight =
205-
parseFloat(computedStyle.paddingRight) || 0;
206-
inputPadding = paddingLeft + paddingRight;
207-
}
208-
}
209-
210-
const totalWidth = calculatedWidth + inputPadding;
159+
const totalChars = maxIntegerChars + maxDecimalChars;
160+
const charWidth = 12;
211161

212-
return `${totalWidth}px`;
213-
}, [sliderWidth, minMaxValues.min_mark, minMaxValues.max_mark]);
162+
return `${totalChars * charWidth}px`;
163+
}, [minMaxValues.min_mark, minMaxValues.max_mark, stepValue]);
214164

215165
const valueIsValid = (val: number): boolean => {
216166
// Check if value is within min/max bounds
@@ -312,11 +262,17 @@ export default function RangeSlider(props: RangeSliderProps) {
312262

313263
const classNames = ['dash-slider-container', className].filter(Boolean);
314264

265+
// Determine if inputs should be rendered at all (CSS will handle responsive visibility)
266+
const shouldShowInputs =
267+
step !== null && // Not disabled by step=None
268+
value.length <= 2 && // Only for single or range sliders
269+
!vertical; // Only for horizontal sliders
270+
315271
return (
316272
<LoadingElement>
317273
{loadingProps => (
318274
<div id={id} className={classNames.join(' ')} {...loadingProps}>
319-
{showInputs && value.length === 2 && !vertical && (
275+
{shouldShowInputs && value.length === 2 && (
320276
<input
321277
type="number"
322278
className="dash-input-container dash-range-slider-input dash-range-slider-min-input"
@@ -381,7 +337,7 @@ export default function RangeSlider(props: RangeSliderProps) {
381337
disabled={disabled}
382338
/>
383339
)}
384-
{showInputs && !vertical && (
340+
{shouldShowInputs && (
385341
<input
386342
ref={inputRef}
387343
type="number"

0 commit comments

Comments
 (0)