feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
.input-slider {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
:global {
|
||||
.semi-slider {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.slider {
|
||||
width: 174px;
|
||||
height: 52px;
|
||||
:global {
|
||||
.semi-slider-marks {
|
||||
top: 32px;
|
||||
font-size: 12px;
|
||||
color: var(--light-usage-text-color-text-2, rgba(28, 31, 35, 0.6));
|
||||
}
|
||||
.semi-slider-mark {
|
||||
transform: unset;
|
||||
}
|
||||
.semi-slider-mark:last-child {
|
||||
left: unset;
|
||||
right: 0;
|
||||
transform: translateX(-100%);
|
||||
width: fit-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.semi-slider-dot.semi-slider-dot-active {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.input-number {
|
||||
flex: 1;
|
||||
:global {
|
||||
.semi-input-wrapper {
|
||||
border: 1px solid
|
||||
var(--light-usage-border-color-border, rgba(28, 31, 35, 0.08));
|
||||
background-color: #fff;
|
||||
&:focus-within {
|
||||
border-color: var(--semi-color-focus-border);
|
||||
}
|
||||
}
|
||||
input {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.input-btn {
|
||||
position: absolute;
|
||||
padding: 10px 8px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: rgba(28, 29, 35, 0.8);
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
&:first-child {
|
||||
left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
right: 0;
|
||||
}
|
||||
&-disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { InputSlider } from './input-slider';
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/** 是否使用 rc-slider 替换 semi-slider,目前 semi-slider 存在一个比较明显的 bug,在缩放场景下,拖拽定位存在问题,已经反馈等待修复 */
|
||||
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);
|
||||
Reference in New Issue
Block a user