signal-desktop/ts/components/Slider.tsx

128 lines
3 KiB
TypeScript
Raw Normal View History

2021-05-28 16:15:17 +00:00
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { CSSProperties, KeyboardEvent } from 'react';
import React, { useRef } from 'react';
2021-05-28 16:15:17 +00:00
import { getClassNamesFor } from '../util/getClassNamesFor';
export type PropsType = {
containerStyle?: CSSProperties;
label: string;
handleStyle?: CSSProperties;
moduleClassName?: string;
onChange: (value: number) => unknown;
value: number;
};
export const Slider = ({
containerStyle = {},
label,
handleStyle = {},
moduleClassName,
onChange,
value,
}: PropsType): JSX.Element => {
const diff = useRef<number>(0);
const handleRef = useRef<HTMLDivElement | null>(null);
const sliderRef = useRef<HTMLDivElement | null>(null);
const getClassName = getClassNamesFor('Slider', moduleClassName);
const handleValueChange = (ev: MouseEvent | React.MouseEvent) => {
if (!sliderRef || !sliderRef.current) {
return;
}
let x =
ev.clientX -
diff.current -
sliderRef.current.getBoundingClientRect().left;
const max = sliderRef.current.offsetWidth;
x = Math.min(max, Math.max(0, x));
const nextValue = (100 * x) / max;
onChange(nextValue);
ev.preventDefault();
ev.stopPropagation();
};
const handleMouseUp = () => {
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('mousemove', handleValueChange);
};
// We want to use React.MouseEvent here because above we
// use the regular MouseEvent
const handleMouseDown = (ev: React.MouseEvent) => {
if (!handleRef || !handleRef.current) {
return;
}
diff.current = ev.clientX - handleRef.current.getBoundingClientRect().left;
document.addEventListener('mousemove', handleValueChange);
document.addEventListener('mouseup', handleMouseUp);
};
const handleKeyDown = (ev: KeyboardEvent) => {
let preventDefault = false;
if (ev.key === 'ArrowRight') {
const nextValue = value + 1;
onChange(Math.min(nextValue, 100));
preventDefault = true;
}
if (ev.key === 'ArrowLeft') {
const nextValue = value - 1;
onChange(Math.max(0, nextValue));
preventDefault = true;
}
if (ev.key === 'Home') {
onChange(0);
preventDefault = true;
}
if (ev.key === 'End') {
onChange(100);
preventDefault = true;
}
if (preventDefault) {
ev.preventDefault();
ev.stopPropagation();
}
};
return (
<div
aria-label={label}
className={getClassName('')}
onClick={handleValueChange}
onKeyDown={handleKeyDown}
ref={sliderRef}
role="button"
style={containerStyle}
tabIndex={0}
>
<div
aria-label={label}
aria-valuenow={value}
className={getClassName('__handle')}
onMouseDown={handleMouseDown}
ref={handleRef}
role="slider"
style={{ ...handleStyle, left: `${value}%` }}
tabIndex={-1}
/>
</div>
);
};