coze-studio/frontend/packages/agent-ide/space-bot/src/component/input-slider/input-slider.tsx

195 lines
5.2 KiB
TypeScript

/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { type FC } from 'react';
import { isInteger, isNumber, isUndefined } from 'lodash-es';
import classNames from 'classnames';
import { type SliderProps } from '@coze-arch/bot-semi/Slider';
import { type CommonFieldProps } from '@coze-arch/bot-semi/Form';
import { withField, InputNumber, Slider } from '@coze-arch/bot-semi';
import { IconMinus, IconPlus } from '@douyinfe/semi-icons';
import { RCSliderWrapper, type RCSliderProps } from '../rc-slider-wrapper';
import s from './index.module.less';
interface InputSliderProps {
value?: number;
onChange?: (v: number) => void;
max?: number;
min?: number;
step?: number;
disabled?: boolean;
decimalPlaces?: number;
marks?: SliderProps['marks'];
className?: string;
/** Whether to use rc-slider to replace semi-slider? At present, there is an obvious bug in semi-slider. In the zoom scene, there is a problem with drag and drop positioning, which has been fed back and is waiting to be fixed. */
useRcSlider?: boolean;
}
const POWVAL = 10;
const formateDecimalPlacesString = (
value: string | number,
prevValue?: number,
decimalPlaces?: number,
) => {
if (isUndefined(decimalPlaces)) {
return value.toString();
}
const numberValue = Number(value);
const stringValue = value.toString();
if (Number.isNaN(numberValue)) {
return `${value}`;
}
if (decimalPlaces === 0 && !isInteger(Number(value)) && prevValue) {
return `${prevValue}`;
}
const decimalPointIndex = stringValue.indexOf('.');
if (decimalPointIndex < 0) {
return stringValue;
}
const formattedValue = stringValue.substring(
0,
decimalPointIndex + 1 + decimalPlaces,
);
if (formattedValue.endsWith('.') && decimalPlaces === 0) {
return formattedValue.substring(0, formattedValue.length - 1);
}
return formattedValue;
};
const formateDecimalPlacesNumber = (
value: number,
prevValue?: number,
decimalPlaces?: number,
) => {
if (isUndefined(decimalPlaces)) {
return value;
}
if (decimalPlaces === 0 && !isInteger(value) && prevValue) {
return prevValue;
}
const pow = Math.pow(POWVAL, decimalPlaces);
return Math.round(value * pow) / pow;
};
const BaseInputSlider: React.FC<InputSliderProps> = ({
value,
onChange,
max = 1,
min = 0,
step = 1,
disabled,
decimalPlaces,
marks,
className,
useRcSlider = false,
}) => {
const onNumberChange = (numberValue: number) => {
const formattedValue = formateDecimalPlacesNumber(
numberValue,
value,
decimalPlaces,
);
onChange?.(formattedValue);
};
return (
<div className={classNames(s['input-slider'], className)}>
{useRcSlider ? (
<RCSliderWrapper
disabled={disabled}
value={value}
max={max}
min={min}
step={step}
marks={marks as RCSliderProps['marks']}
onChange={v => {
if (typeof v === 'number') {
onChange?.(v);
}
}}
/>
) : (
<Slider
className={s.slider}
disabled={disabled}
value={value}
max={max}
min={min}
step={step}
marks={marks}
onChange={v => {
if (typeof v === 'number') {
onChange?.(v);
}
}}
/>
)}
<div style={{ position: 'relative', marginLeft: 24 }}>
<IconMinus
className={classNames(
s['input-btn'],
disabled && s['input-btn-disabled'],
)}
onClick={e => {
e.stopPropagation();
if (isNumber(value) && value <= min) {
return;
}
if (!disabled && value !== undefined) {
onNumberChange(value - step);
}
}}
/>
<InputNumber
className={s['input-number']}
value={value}
disabled={disabled}
formatter={inputValue =>
formateDecimalPlacesString(inputValue, value)
}
hideButtons
onNumberChange={onNumberChange}
max={max}
min={min}
/>
<IconPlus
className={classNames(
s['input-btn'],
disabled && s['input-btn-disabled'],
)}
onClick={e => {
if (isNumber(value) && value >= max) {
return;
}
e.stopPropagation();
if (!disabled && value !== undefined) {
onNumberChange(value + step);
}
}}
/>
</div>
</div>
);
};
export const InputSlider: FC<CommonFieldProps & InputSliderProps> =
withField(BaseInputSlider);