-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(stepper): 对齐 mobile-vue * fix(stepper): 修改site文件 * fix: fix cr --------- Co-authored-by: taninsist <[email protected]> Co-authored-by: anlyyao <[email protected]>
- Loading branch information
1 parent
db4e311
commit 804b5d6
Showing
23 changed files
with
1,679 additions
and
199 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/** | ||
* 格式化数字 | ||
* @param value 传入的数字字符串 | ||
* @param allowDecimal 是否允许小数,默认为 true | ||
* @param allowNegative 是否允许负数,默认为 true | ||
* @returns 返回格式化后的数字字符串 | ||
*/ | ||
export const formatNumber = (value: string, allowDecimal = true, allowNegative = true) => { | ||
let resultValue = value; | ||
if (allowDecimal) { | ||
const index = value.indexOf('.'); | ||
if (index !== -1) { | ||
resultValue = `${value.slice(0, index + 1)}${value.slice(index).replace(/\./g, '')}`; | ||
} | ||
} else { | ||
const [splitValue = ''] = value.split('.'); | ||
resultValue = splitValue; | ||
} | ||
|
||
if (allowNegative) { | ||
const index = value.indexOf('-'); | ||
if (index !== -1) { | ||
resultValue = `${value.slice(0, index + 1)}${value.slice(index).replace(/-/g, '')}`; | ||
} | ||
} else { | ||
resultValue = value.replace(/-/g, ''); | ||
} | ||
|
||
return resultValue.replace(/[^\d.-]/g, ''); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,135 +1,164 @@ | ||
import React, { FC } from 'react'; | ||
import React, { FormEvent, forwardRef, memo } from 'react'; | ||
import classNames from 'classnames'; | ||
import identity from 'lodash/identity'; | ||
import { AddIcon, RemoveIcon } from 'tdesign-icons-react'; | ||
import useConfig from '../_util/useConfig'; | ||
import { usePrefixClass } from '../hooks/useClass'; | ||
import useDefaultProps from '../hooks/useDefaultProps'; | ||
import useDefault from '../_util/useDefault'; | ||
import { formatNumber } from '../_util/formatNumber'; | ||
import { StyledProps } from '../common'; | ||
import { TdStepperProps } from './type'; | ||
import withNativeProps, { NativeProps } from '../_util/withNativeProps'; | ||
|
||
export interface StepperProps extends TdStepperProps, NativeProps {} | ||
|
||
const defaultProps = { | ||
disabled: false, | ||
disableInput: false, | ||
max: 100, | ||
min: 0, | ||
step: 1, | ||
theme: 'normal', | ||
defaultValue: 0, | ||
onBlur: identity, | ||
onChange: identity, | ||
onOverlimit: identity, | ||
}; | ||
|
||
const Stepper: FC<StepperProps> = (props) => { | ||
import { stepperDefaultProps } from './defaultProps'; | ||
|
||
export interface StepperProps extends TdStepperProps, StyledProps {} | ||
|
||
const Stepper = forwardRef<HTMLDivElement, StepperProps>((originProps, ref) => { | ||
const props = useDefaultProps(originProps, stepperDefaultProps); | ||
const { | ||
disabled, | ||
className, | ||
style, | ||
disableInput, | ||
disabled, | ||
inputWidth, | ||
integer, | ||
max, | ||
size, | ||
min, | ||
step, | ||
theme, | ||
value, | ||
defaultValue, | ||
onBlur, | ||
onChange, | ||
onFocus, | ||
onOverlimit, | ||
} = props; | ||
|
||
const [currentValue, setCurrentValue] = useDefault(value, defaultValue, onChange); | ||
const { classPrefix } = useConfig(); | ||
const name = `${classPrefix}-stepper`; | ||
|
||
const stepperClass = usePrefixClass('stepper'); | ||
|
||
const inputStyle = inputWidth ? { width: `${inputWidth}px` } : {}; | ||
|
||
const isDisabled = (type: 'minus' | 'plus') => { | ||
if (disabled) return true; | ||
if (type === 'minus' && currentValue <= min) { | ||
if (type === 'minus' && Number(currentValue) <= min) { | ||
return true; | ||
} | ||
if (type === 'plus' && currentValue >= max) { | ||
if (type === 'plus' && Number(currentValue) >= max) { | ||
return true; | ||
} | ||
return false; | ||
}; | ||
|
||
const getLen = (num: number) => { | ||
const numStr = num.toString(); | ||
return numStr.indexOf('.') === -1 ? 0 : numStr.split('.')[1].length; | ||
}; | ||
|
||
/** | ||
* 精确加法 | ||
*/ | ||
const add = (a: number, b: number) => { | ||
const maxLen = Math.max(getLen(a), getLen(b)); | ||
const base = 10 ** maxLen; | ||
return Math.round(a * base + b * base) / base; | ||
}; | ||
|
||
const formatValue = (value: number) => | ||
Math.max(Math.min(max, value, Number.MAX_SAFE_INTEGER), min, Number.MIN_SAFE_INTEGER); | ||
Math.max(Math.min(max, value, Number.MAX_SAFE_INTEGER), min, Number.MIN_SAFE_INTEGER).toFixed( | ||
Math.max(getLen(step), getLen(value)), | ||
); | ||
|
||
const updateValue = (value: number) => { | ||
setCurrentValue(formatValue(value)); | ||
onChange(value); | ||
const updateValue = (value: TdStepperProps['value']) => { | ||
setCurrentValue(formatNumber(`${value}`, !integer)); | ||
}; | ||
|
||
const minusValue = () => { | ||
if (isDisabled('minus')) { | ||
onOverlimit('minus'); | ||
const plusValue = () => { | ||
if (isDisabled('plus')) { | ||
onOverlimit?.('plus'); | ||
return; | ||
} | ||
updateValue(Number(currentValue) - step); | ||
updateValue(formatValue(add(Number(currentValue), step))); | ||
}; | ||
|
||
const plusValue = () => { | ||
if (isDisabled('plus')) { | ||
onOverlimit('plus'); | ||
const minusValue = () => { | ||
if (isDisabled('minus')) { | ||
onOverlimit?.('minus'); | ||
return; | ||
} | ||
updateValue(Number(currentValue) + step); | ||
updateValue(formatValue(add(Number(currentValue), -step))); | ||
}; | ||
|
||
const handleChange = (e: React.FocusEvent<HTMLInputElement, Element>) => { | ||
const { value } = e.currentTarget; | ||
if (isNaN(Number(value))) return; | ||
const formattedValue = formatValue(Number(value)); | ||
setCurrentValue(formattedValue); | ||
onChange(formattedValue); | ||
const handleInput = (e: FormEvent<HTMLInputElement>) => { | ||
const value = formatNumber((e.target as HTMLInputElement).value, !integer); | ||
setCurrentValue(value); | ||
}; | ||
|
||
const handleBlur = (e: React.FocusEvent<HTMLInputElement, Element>) => { | ||
const { value } = e.currentTarget; | ||
if (isNaN(Number(value))) return; | ||
const formattedValue = formatValue(Number(value)); | ||
setCurrentValue(formattedValue); | ||
onBlur(formattedValue); | ||
const handleChange = () => { | ||
const formattedValue = formatValue(Number(currentValue)); | ||
updateValue(formattedValue); | ||
}; | ||
|
||
return withNativeProps( | ||
props, | ||
<div | ||
className={classNames(name, { | ||
[`${classPrefix}-is-disabled`]: disabled, | ||
[`${name}__pure`]: theme === 'grey', | ||
[`${name}__normal`]: theme !== 'grey', | ||
})} | ||
> | ||
const handleFocus = () => { | ||
onFocus(Number(currentValue)); | ||
}; | ||
|
||
const handleBlur = () => { | ||
onBlur(Number(currentValue)); | ||
}; | ||
|
||
return ( | ||
<div className={classNames(stepperClass, `${stepperClass}--${size}`, className)} style={style} ref={ref}> | ||
<div | ||
className={classNames(`${name}__minus`, { | ||
[`${classPrefix}-is-disabled`]: currentValue <= min, | ||
})} | ||
className={classNames( | ||
`${stepperClass}__minus`, | ||
`${stepperClass}__minus--${theme}`, | ||
`${stepperClass}__icon--${size}`, | ||
{ | ||
[`${stepperClass}--${theme}-disabled`]: disabled || Number(currentValue) <= props.min, | ||
}, | ||
)} | ||
onClick={minusValue} | ||
> | ||
<RemoveIcon className={`${name}__icon`} /> | ||
<RemoveIcon className={`${stepperClass}__minus-icon`} /> | ||
</div> | ||
<input | ||
className={`${name}__input`} | ||
style={{ width: inputWidth || 50 }} | ||
value={currentValue} | ||
onChange={handleChange} | ||
className={classNames( | ||
`${stepperClass}__input`, | ||
`${stepperClass}__input--${theme}`, | ||
`${stepperClass}__input--${size}`, | ||
{ | ||
[`${stepperClass}--${theme}-disabled`]: disabled, | ||
}, | ||
)} | ||
type={integer ? 'tel' : 'text'} | ||
inputMode={integer ? 'numeric' : 'decimal'} | ||
style={inputStyle} | ||
disabled={disableInput || disabled} | ||
readOnly={disableInput} | ||
onFocus={handleFocus} | ||
onBlur={handleBlur} | ||
disabled={disabled || disableInput} | ||
onInput={handleInput} | ||
onChange={handleChange} | ||
/> | ||
<div | ||
className={classNames(`${name}__plus`, { | ||
[`${classPrefix}-is-disabled`]: currentValue >= max, | ||
})} | ||
className={classNames( | ||
`${stepperClass}__plus`, | ||
`${stepperClass}__plus--${theme}`, | ||
`${stepperClass}__icon--${size}`, | ||
{ | ||
[`${stepperClass}--${theme}-disabled`]: disabled || Number(currentValue) >= max, | ||
}, | ||
)} | ||
onClick={plusValue} | ||
> | ||
<AddIcon className={`${name}__icon`} /> | ||
<AddIcon className={`${stepperClass}__plus-icon`} /> | ||
</div> | ||
</div>, | ||
</div> | ||
); | ||
}; | ||
}); | ||
|
||
Stepper.defaultProps = defaultProps as StepperProps; | ||
Stepper.displayName = 'Stepper'; | ||
|
||
export default Stepper; | ||
export default memo(Stepper); |
Oops, something went wrong.