Files
MemoryBear/web/src/components/SliderInput/index.tsx
2026-03-25 18:31:52 +08:00

148 lines
3.7 KiB
TypeScript

/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:26:44
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-25 18:14:20
*/
/**
* SliderInput Component
*
* A combined slider and input number component for precise value control:
* - Synchronized slider and input number
* - Value range validation
* - Optional label and marks
* - Customizable tooltip
* - Disabled state support
*
* @component
*/
import { useState, useEffect, type FC } from 'react';
import { Slider, InputNumber, Row, Col } from 'antd';
/** Props interface for SliderInput component */
interface SliderInputProps {
/** Current value */
value?: number;
/** Callback fired when value changes */
onChange?: (value: number | null) => void;
/** Minimum value */
min?: number;
/** Maximum value */
max?: number;
/** Step increment */
step?: number;
/** Default value */
defaultValue?: number;
/** Whether the component is disabled */
disabled?: boolean;
/** Optional label text */
label?: string;
/** Additional CSS classes for container */
className?: string;
/** Additional CSS classes for slider */
sliderClassName?: string;
/** Additional CSS classes for input */
inputClassName?: string;
/** Marks to display on slider */
marks?: Record<number, string | { style: React.CSSProperties; label: string }>;
/** Tooltip configuration */
tooltip?: {
open?: boolean;
placement?: 'top' | 'left' | 'right' | 'bottom';
formatter?: (value?: number) => React.ReactNode;
};
}
/** Slider with input number component for precise value control */
const SliderInput: FC<SliderInputProps> = ({
value,
onChange,
min = 0,
max = 100,
step = 1,
defaultValue = 0,
disabled = false,
label,
className = '',
sliderClassName = '',
inputClassName = '',
marks,
tooltip,
}) => {
const [internalValue, setInternalValue] = useState<number>(value ?? defaultValue);
/** Sync internal value when external value changes */
useEffect(() => {
if (value !== undefined && value !== internalValue) {
setInternalValue(value);
}
}, [value]);
/** Handle slider value change */
const handleSliderChange = (newValue: number) => {
setInternalValue(newValue);
onChange?.(newValue);
};
/** Handle input number value change with range validation */
const handleInputChange = (newValue: number | null) => {
if (newValue === null) {
return;
}
/** Ensure value is within min/max range */
let validValue = newValue;
if (newValue < min) {
validValue = min;
} else if (newValue > max) {
validValue = max;
}
setInternalValue(validValue);
onChange?.(validValue);
};
return (
<div className={`rb:w-full ${className}`}>
{/* Optional label */}
{label && (
<div className="rb:text-[#5B6167] rb:leading-5 rb:mb-1">
{label}
</div>
)}
<Row gutter={16} align="middle">
{/* Slider component */}
<Col flex="auto">
<Slider
min={min}
max={max}
step={step}
value={internalValue}
onChange={handleSliderChange}
disabled={disabled}
marks={marks}
tooltip={tooltip}
className={sliderClassName}
/>
</Col>
{/* Input number component */}
<Col flex="80px">
<InputNumber
min={min}
max={max}
step={step}
value={internalValue}
onChange={handleInputChange}
disabled={disabled}
className={`rb:w-full! ${inputClassName}`}
controls
/>
</Col>
</Row>
</div>
);
};
export default SliderInput;