feat(ui): add non-linear scale to brightness slider for better low-value control (#2338)

Implement logarithmic mapping for screen brightness slider to improve
usability at low brightness levels where small percentage changes have
significant visual impact.
This commit is contained in:
Huang Xin
2025-10-27 20:45:43 +08:00
committed by GitHub
parent 3b86229c8f
commit e80a2268ac
2 changed files with 38 additions and 5 deletions
@@ -90,6 +90,22 @@ export const ColorPanel: React.FC<ColorPanelProps> = ({ actionTab, bottomOffset
onChange={handleScreenBrightnessChange}
min={SCREEN_BRIGHTNESS_LIMITS.MIN}
max={SCREEN_BRIGHTNESS_LIMITS.MAX}
valueToPosition={(value: number, min: number, max: number): number => {
if (value <= min) return 0;
if (value >= max) return 100;
// Use exponential mapping: position = 100 * ((value/max)^0.5)
const normalized = value / max;
const position = Math.pow(normalized, 0.5) * 100;
return position;
}}
positionToValue={(position: number, min: number, max: number): number => {
if (position <= 0) return min;
if (position >= 100) return max;
// Inverse of the above: value = max * (position/100)^2
const normalized = position / 100;
const value = Math.pow(normalized, 2) * max;
return Math.max(min, Math.min(max, value));
}}
/>
)}
+22 -5
View File
@@ -18,6 +18,8 @@ interface SliderProps {
maxClassName?: string;
bubbleClassName?: string;
onChange?: (value: number) => void;
valueToPosition?: (value: number, min: number, max: number) => number;
positionToValue?: (position: number, min: number, max: number) => number;
}
const Slider: React.FC<SliderProps> = ({
@@ -38,13 +40,28 @@ const Slider: React.FC<SliderProps> = ({
maxClassName = '',
bubbleClassName = '',
onChange,
valueToPosition,
positionToValue,
}) => {
const [value, setValue] = useState(initialValue);
const [isRtl, setIsRtl] = useState(false);
const sliderRef = useRef<HTMLDivElement>(null);
// Default linear mapping functions
const defaultValueToPosition = (val: number, minVal: number, maxVal: number) => {
return ((val - minVal) / (maxVal - minVal)) * 100;
};
const defaultPositionToValue = (pos: number, minVal: number, maxVal: number) => {
return minVal + (pos / 100) * (maxVal - minVal);
};
const valueToPos = valueToPosition || defaultValueToPosition;
const posToValue = positionToValue || defaultPositionToValue;
const handleChange = (e: React.ChangeEvent) => {
const newValue = parseInt((e.target as HTMLInputElement).value, 10);
const position = parseInt((e.target as HTMLInputElement).value, 10);
const newValue = Math.round(posToValue(position, min, max) / step) * step;
setValue(newValue);
if (onChange) {
onChange(newValue);
@@ -66,7 +83,7 @@ const Slider: React.FC<SliderProps> = ({
setValue(initialValue);
}, [initialValue]);
const percentage = ((value - min) / (max - min)) * 100;
const percentage = valueToPos(value, min, max);
const visualPercentage = (percentage / 100) * 95;
return (
@@ -113,10 +130,10 @@ const Slider: React.FC<SliderProps> = ({
</div>
<input
type='range'
min={min}
max={max}
min={0}
max={100}
step={step}
value={value}
value={percentage}
className='absolute inset-0 h-full w-full cursor-pointer opacity-0'
onChange={handleChange}
aria-label={label}