final flow
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
import React, { useState, ChangeEvent } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
// --- Типы ---
|
||||
interface SliderProps {
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
defaultValue?: number;
|
||||
onChange?: (value: number) => void;
|
||||
}
|
||||
|
||||
// --- Стили (Styled-Components) ---
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
`;
|
||||
|
||||
const SliderWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 24px; // Высота для кликабельной области бегунка
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Track = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background-color: #f0f7ec; // Светло-зеленый фон
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const Fill = styled.div<{ $percent: number }>`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background-color: #5b9e30; // Зеленый цвет заполнения
|
||||
width: ${({ $percent }) => $percent}%;
|
||||
// Скругление только слева, если не достигнут максимум
|
||||
border-radius: ${({ $percent }) =>
|
||||
$percent >= 100 ? '6px' : $percent <= 0 ? '6px' : '6px 0 0 6px'
|
||||
};
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
const Thumb = styled.div<{ $percent: number }>`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: ${({ $percent }) => $percent}%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: #5b9e30;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
|
||||
// Мобильная версия: немного увеличиваем для удобного нажатия пальцем
|
||||
@media (max-width: 768px) {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
`;
|
||||
|
||||
// Прозрачный нативный инпут для обработки перетаскивания и кликов
|
||||
const RangeInput = styled.input`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const LabelsContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
const Label = styled.span`
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const LeftLabel = styled(Label)`
|
||||
left: 0;
|
||||
transform: translateX(0);
|
||||
`;
|
||||
|
||||
const RightLabel = styled(Label)`
|
||||
right: 0;
|
||||
transform: translateX(0);
|
||||
`;
|
||||
|
||||
const CenterLabel = styled(Label)<{ $percent: number }>`
|
||||
left: ${({ $percent }) => $percent}%;
|
||||
transform: translateX(-50%);
|
||||
`;
|
||||
|
||||
// --- Основной Компонент ---
|
||||
const CustomSlider: React.FC<SliderProps> = ({
|
||||
min = 30,
|
||||
max = 170,
|
||||
step = 10,
|
||||
defaultValue = 90,
|
||||
onChange,
|
||||
}) => {
|
||||
const [value, setValue] = useState<number>(defaultValue);
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = Number(e.target.value);
|
||||
setValue(newValue);
|
||||
if (onChange) onChange(newValue);
|
||||
};
|
||||
|
||||
// Процент заполнения трека (от 0% до 100%)
|
||||
const percent = ((value - min) / (max - min)) * 100;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<SliderWrapper>
|
||||
<Track>
|
||||
<Fill $percent={percent} />
|
||||
<Thumb $percent={percent} />
|
||||
<RangeInput
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
aria-label="Процентное значение"
|
||||
/>
|
||||
</Track>
|
||||
</SliderWrapper>
|
||||
|
||||
<LabelsContainer>
|
||||
<LeftLabel>{min}%</LeftLabel>
|
||||
{(percent > 10) && (percent < 90) ? (<CenterLabel $percent={percent}>{value}%</CenterLabel>) : null}
|
||||
<RightLabel>{max}%</RightLabel>
|
||||
</LabelsContainer>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomSlider;
|
||||
Reference in New Issue
Block a user