Skip to content

Commit 7b093fb

Browse files
committed
feat: add Range component for adjustable settings
- Introduced a new Range component for user input, allowing adjustable values with labels and a value bubble. - Integrated the Range component into SettingsDialog for managing embed lock debounce time, enhancing user experience with visual feedback. - Added corresponding styles in Range.scss for improved UI consistency.
1 parent 63e0354 commit 7b093fb

File tree

3 files changed

+166
-18
lines changed

3 files changed

+166
-18
lines changed

src/frontend/src/ui/Range.scss

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
:root {
2+
--slider-thumb-size: 16px;
3+
}
4+
5+
.control-label {
6+
display: flex;
7+
flex-direction: column;
8+
width: 100%;
9+
font-size: 0.9rem;
10+
color: var(--text-primary-color);
11+
}
12+
13+
.range-wrapper {
14+
position: relative;
15+
padding-top: 10px;
16+
padding-bottom: 25px;
17+
width: 100%;
18+
}
19+
20+
.range-input {
21+
width: 100%;
22+
height: 4px;
23+
-webkit-appearance: none;
24+
background: var(--color-slider-track);
25+
border-radius: 2px;
26+
outline: none;
27+
}
28+
29+
.range-input::-webkit-slider-thumb {
30+
-webkit-appearance: none;
31+
appearance: none;
32+
width: var(--slider-thumb-size);
33+
height: var(--slider-thumb-size);
34+
background: var(--color-slider-thumb);
35+
border-radius: 50%;
36+
cursor: pointer;
37+
border: none;
38+
}
39+
40+
.range-input::-moz-range-thumb {
41+
width: var(--slider-thumb-size);
42+
height: var(--slider-thumb-size);
43+
background: var(--color-slider-thumb);
44+
border-radius: 50%;
45+
cursor: pointer;
46+
border: none;
47+
}
48+
49+
.value-bubble {
50+
position: absolute;
51+
bottom: 0;
52+
transform: translateX(-50%);
53+
font-size: 12px;
54+
color: var(--text-primary-color);
55+
}
56+
57+
.min-label, .zero-label {
58+
position: absolute;
59+
bottom: 0;
60+
left: 4px;
61+
font-size: 12px;
62+
color: var(--text-primary-color);
63+
}
64+
65+
.max-label {
66+
position: absolute;
67+
bottom: 0;
68+
right: 4px;
69+
font-size: 12px;
70+
color: var(--text-primary-color);
71+
}

src/frontend/src/ui/Range.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { useEffect } from "react";
2+
3+
import "./Range.scss";
4+
5+
export type RangeProps = {
6+
value: number;
7+
onChange: (value: number) => void;
8+
min?: number;
9+
max?: number;
10+
step?: number;
11+
label?: string;
12+
minLabel?: string;
13+
maxLabel?: string;
14+
showValueBubble?: boolean;
15+
};
16+
17+
export const Range = ({
18+
value,
19+
onChange,
20+
min = 0,
21+
max = 100,
22+
step = 1,
23+
minLabel,
24+
maxLabel,
25+
showValueBubble = true,
26+
}: RangeProps) => {
27+
const rangeRef = React.useRef<HTMLInputElement>(null);
28+
const valueRef = React.useRef<HTMLDivElement>(null);
29+
30+
useEffect(() => {
31+
if (rangeRef.current) {
32+
const rangeElement = rangeRef.current;
33+
34+
// Update value bubble position if it exists
35+
if (showValueBubble && valueRef.current) {
36+
const valueElement = valueRef.current;
37+
const inputWidth = rangeElement.offsetWidth;
38+
const thumbWidth = 16; // Match the --slider-thumb-size CSS variable
39+
const position =
40+
((value - min) / (max - min)) * (inputWidth - thumbWidth) + thumbWidth / 2;
41+
valueElement.style.left = `${position}px`;
42+
}
43+
44+
// Calculate percentage for gradient
45+
const percentage = ((value - min) / (max - min)) * 100;
46+
rangeElement.style.background = `linear-gradient(to right, var(--color-slider-track) 0%, var(--color-slider-track) ${percentage}%, var(--button-bg) ${percentage}%, var(--button-bg) 100%)`;
47+
}
48+
}, [value, min, max, showValueBubble]);
49+
50+
return (
51+
<label className="control-label">
52+
<div className="range-wrapper">
53+
<input
54+
ref={rangeRef}
55+
type="range"
56+
min={min}
57+
max={max}
58+
step={step}
59+
onChange={(event) => {
60+
onChange(+event.target.value);
61+
}}
62+
value={value}
63+
className="range-input"
64+
/>
65+
{showValueBubble && (
66+
<div className="value-bubble" ref={valueRef}>
67+
{value !== min ? value : null}
68+
</div>
69+
)}
70+
{min === 0 ? (
71+
<div className="zero-label">{minLabel || min}</div>
72+
) : (
73+
<div className="min-label">{minLabel || min}</div>
74+
)}
75+
<div className="max-label">{maxLabel || max}</div>
76+
</div>
77+
</label>
78+
);
79+
};

src/frontend/src/ui/SettingsDialog.tsx

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState, useCallback, useEffect } from "react";
2-
import { Dialog, Range } from "@atyrode/excalidraw";
2+
import { Dialog } from "@atyrode/excalidraw";
3+
import { Range } from "./Range";
34
import { UserSettings, DEFAULT_SETTINGS } from "../types/settings";
45
import "./SettingsDialog.scss";
56

@@ -69,23 +70,20 @@ const SettingsDialog: React.FC<SettingsDialogProps> = ({
6970
Embed Lock Debounce Time: {settings.embedLockDebounceTime}ms
7071
</label>
7172
<div className="settings-dialog__range-container">
72-
<Range
73-
updateData={(value) => handleEmbedLockDebounceTimeChange(
74-
// Map 0-100 range to 150-5000ms
75-
Math.round(150 + (value / 100) * 4850)
76-
)}
77-
appState={{
78-
currentItemOpacity:
79-
// Map 150-5000ms to 0-100 range
80-
Math.round(((settings.embedLockDebounceTime || 350) - 150) / 4850 * 100)
81-
}}
82-
elements={[]}
83-
testId="embed-lock-debounce-time"
84-
/>
85-
</div>
86-
<div className="settings-dialog__range-labels">
87-
<span>150ms</span>
88-
<span>5000ms</span>
73+
<Range
74+
value={Math.round(((settings.embedLockDebounceTime || 350) - 150) / 4850 * 100)}
75+
onChange={(value) => handleEmbedLockDebounceTimeChange(
76+
// Map 0-100 range to 150-5000ms
77+
Math.round(150 + (value / 100) * 4850)
78+
)}
79+
min={0}
80+
max={100}
81+
step={1}
82+
label="Embed Lock Time"
83+
minLabel="150ms"
84+
maxLabel="5000ms"
85+
showValueBubble={false}
86+
/>
8987
</div>
9088
</div>
9189
</div>

0 commit comments

Comments
 (0)