iconoir/iconoir.com/components/Slider.tsx
2023-01-11 19:08:14 +01:00

127 lines
3.1 KiB
TypeScript

import { NumberFormatOptions } from '@internationalized/number';
import { useFocusRing } from '@react-aria/focus';
import { useNumberFormatter } from '@react-aria/i18n';
import { useSlider, useSliderThumb } from '@react-aria/slider';
import { mergeProps } from '@react-aria/utils';
import { VisuallyHidden } from '@react-aria/visually-hidden';
import { SliderState, useSliderState } from '@react-stately/slider';
import { SliderProps as ReactSliderProps } from '@react-types/slider';
import React from 'react';
import styled from 'styled-components';
import { Text13 } from './Typography';
export interface SliderProps extends ReactSliderProps<number[]> {
formatOptions?: NumberFormatOptions;
}
export function Slider(props: SliderProps) {
let trackRef = React.useRef(null);
let numberFormatter = useNumberFormatter(props.formatOptions);
let state = useSliderState({ ...props, numberFormatter });
let { groupProps, trackProps, labelProps, outputProps } = useSlider(
props,
state,
trackRef
);
return (
<SliderContainer {...groupProps}>
<SliderHeader>
{props.label && (
<Text13 as={'label'} {...labelProps}>
{props.label}
</Text13>
)}
<Output as={'output'} {...outputProps}>
{state.getThumbValueLabel(0)}
</Output>
</SliderHeader>
<Track {...trackProps} ref={trackRef}>
<TrackBackground />
<Thumb index={0} state={state} trackRef={trackRef} />
</Track>
</SliderContainer>
);
}
interface ThumbProps {
state: SliderState;
trackRef: React.RefObject<HTMLElement>;
index: number;
}
function Thumb({ state, trackRef, index }: ThumbProps) {
let inputRef = React.useRef(null);
let { thumbProps, inputProps } = useSliderThumb(
{
index,
trackRef,
inputRef,
},
state
);
let { focusProps, isFocusVisible } = useFocusRing();
return (
<ThumbContainer
style={{
left: `${state.getThumbPercent(index) * 100}%`,
}}
>
<ThumbInner
{...thumbProps}
style={{
backgroundColor: isFocusVisible
? 'var(--accent)'
: state.isThumbDragging(index)
? 'var(--accent)'
: 'var(--black)',
}}
>
<VisuallyHidden>
<input ref={inputRef} {...mergeProps(inputProps, focusProps)} />
</VisuallyHidden>
</ThumbInner>
</ThumbContainer>
);
}
const SliderContainer = styled.div`
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
touch-action: none;
`;
const SliderHeader = styled.div`
display: flex;
align-self: stretch;
`;
const Output = styled(Text13)`
flex: 1 0 auto;
text-align: end;
margin-bottom: 10px;
`;
const Track = styled.div`
position: relative;
height: 30px;
width: 100%;
`;
const TrackBackground = styled.div`
position: absolute;
height: 3px;
top: 13px;
background: #f4f3f4;
width: 100%;
`;
const ThumbContainer = styled.div`
position: absolute;
top: 4px;
transform: translateX(-50%);
`;
const ThumbInner = styled.div`
width: 20px;
height: 20px;
border-radius: 50%;
cursor: pointer;
`;