Add more control
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import React, { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
|
||||
const SliderWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
width: 40px;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
interface SliderProps {
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
value?: number;
|
||||
onChange?: (value: number) => void;
|
||||
}
|
||||
|
||||
|
||||
interface TrackTransientProps {
|
||||
$fillPercent: number;
|
||||
}
|
||||
|
||||
const Track = styled.input.attrs({ type: "range" }) <TrackTransientProps>`
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
writing-mode: vertical-lr;
|
||||
direction: rtl;
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
white ${({ $fillPercent }) => $fillPercent}%,
|
||||
var(--secondary-color) ${({ $fillPercent }) => $fillPercent}%
|
||||
);
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 0;
|
||||
background: #ffffff;
|
||||
border: 3px solid var(--secondary-color);
|
||||
box-shadow: 0 0 6px rgba(255, 68, 68, 0.6);
|
||||
cursor: grab;
|
||||
transition: transform 0.1s ease, box-shadow 0.1s ease;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
transform: scale(1.2);
|
||||
box-shadow: 0 0 12px rgba(255, 68, 68, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 0;
|
||||
background: #ffffff;
|
||||
border: 3px solid #ff4444;
|
||||
box-shadow: 0 0 6px rgba(255, 68, 68, 0.6);
|
||||
cursor: grab;
|
||||
transition: transform 0.1s ease;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
background: transparent;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const ValueLabel = styled.span`
|
||||
font-size: 13px;
|
||||
color: white;
|
||||
letter-spacing: 0.05em;
|
||||
`;
|
||||
|
||||
interface SliderProps {
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
value?: number;
|
||||
onLabelClick?: () => void;
|
||||
onChange?: (value: number) => void;
|
||||
}
|
||||
|
||||
export function VerticalSlider({
|
||||
min = 0,
|
||||
max = 100,
|
||||
step = 1,
|
||||
onLabelClick,
|
||||
value: controlledValue,
|
||||
onChange,
|
||||
}: SliderProps) {
|
||||
const [internalValue, setInternalValue] = useState(
|
||||
controlledValue ?? Math.floor((max - min) / 2)
|
||||
);
|
||||
|
||||
const value = controlledValue ?? internalValue;
|
||||
const fillPercent = ((value - min) / (max - min)) * 100;
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const next = Number(e.target.value);
|
||||
setInternalValue(next);
|
||||
onChange?.(next);
|
||||
};
|
||||
|
||||
return (
|
||||
<SliderWrapper>
|
||||
<ValueLabel onDoubleClick={() => {
|
||||
onLabelClick?.();
|
||||
}}>{value}</ValueLabel>
|
||||
<Track
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
$fillPercent={fillPercent}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</SliderWrapper>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user