marketing/src/components/Fullness/FullnessSlider.tsx

162 lines
4.4 KiB
TypeScript
Raw Normal View History

2026-08-30 11:47:34 +00:00
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;