From d2e0f1b7d84a53a12eb199b61896862032c2259a Mon Sep 17 00:00:00 2001 From: Liu Xin Date: Tue, 24 Oct 2023 09:53:23 +0800 Subject: [PATCH] fix: The 0 in pvc autosizer input can not be deleted (#4227) fix:The 0 in pvc autosizer input can not be deleted Signed-off-by: harrisonliu5 --- src/components/Base/Slider/index.jsx | 346 +++++++++++++++++++++ src/components/Base/Slider/index.scss | 99 ++++++ src/components/Base/index.js | 1 + src/components/Inputs/UnitSlider/index.jsx | 2 +- 4 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 src/components/Base/Slider/index.jsx create mode 100644 src/components/Base/Slider/index.scss diff --git a/src/components/Base/Slider/index.jsx b/src/components/Base/Slider/index.jsx new file mode 100644 index 00000000000..27114605a8a --- /dev/null +++ b/src/components/Base/Slider/index.jsx @@ -0,0 +1,346 @@ +import React from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import { isUndefined, isEmpty, clone, throttle } from 'lodash' + +import { Tooltip } from '@kube-design/components' +import NumberInput from 'components/Inputs/NumberInput' + +export default class Slider extends React.Component { + static propTypes = { + value: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.number, PropTypes.string]) + ), + ]), + defaultValue: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.number, PropTypes.string]) + ), + ]), + min: PropTypes.number, + max: PropTypes.number, + step: PropTypes.number, + marks: PropTypes.object, + unit: PropTypes.string, + range: PropTypes.bool, + hasTooltip: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object, + railStyle: PropTypes.object, + handlerStyle: PropTypes.object, + trackStyle: PropTypes.object, + onChange: PropTypes.func, + } + + static defaultProps = { + min: 0, + unit: '', + step: 1, + marks: {}, + range: false, + hasTooltip: false, + className: '', + style: {}, + railStyle: {}, + handlerStyle: {}, + trackStyle: {}, + onChange() {}, + } + + constructor(props) { + super(props) + + this.state = this.getStateFromProps() + + const index = String(props.step).indexOf('.') + this.precision = + index === -1 ? 0 : String(props.step).slice(index).length - 1 + + this.ref = React.createRef() + } + + getValueFromPercent(percent) { + const { min, max, step } = this.props + const value = ((max - min) * percent) / 100 + min + return Math.min( + (value - ((value - min) % step)).toFixed(this.precision), + max + ) + } + + getValuePercent(value) { + const { min, max } = this.props + return Math.min((value - min) / (max - min), 1) + } + + getStateFromProps() { + const { unit, range, min, value: _value, defaultValue } = this.props + let value = clone(!isUndefined(_value) ? _value : defaultValue) + + let left + let right + let stateValue + + if (range) { + if (!value) { + value = [min, min] + } else { + value[0] = Number(String(value[0]).replace(unit, '')) + value[1] = Number(String(value[1]).replace(unit, '')) + } + left = this.getValuePercent(value[0]) * 100 + right = this.getValuePercent(value[1]) * 100 + stateValue = value + } else { + if (!value) { + value = min + } else { + value = Number(String(value).replace(unit, '')) + } + left = 0 + right = this.getValuePercent(value) * 100 + stateValue = [min, value] + } + + return { left, right, value: stateValue, originValue: _value } + } + + componentDidMount() { + if (this.ref && this.ref.current) { + this.ref.current.addEventListener('mousedown', this.handleMouseDown) + } + window.addEventListener('resize', this.handleResize) + } + + componentDidUpdate(prevProps, prevState) { + if (this.props.value !== prevState.originValue) { + this.setState(this.getStateFromProps()) + } + } + + componentWillUnmount() { + document.removeEventListener('mousedown', this.handleMouseDown) + document.removeEventListener('mousemove', this.handleMouseMove) + document.removeEventListener('mouseup', this.handleMouseUp) + window.removeEventListener('resize', this.handleResize) + } + + triggerChange = ({ left, right }) => { + const { range, onChange } = this.props + + const value = [ + this.getValueFromPercent(left), + this.getValueFromPercent(right), + ] + + if (value[0] !== this.state.value[0] || value[1] !== this.state.value[1]) { + this.setState( + { + left: this.getValuePercent(value[0]) * 100, + right: this.getValuePercent(value[1]) * 100, + value, + }, + () => { + onChange(range ? value : value[1]) + } + ) + } + } + + handleResize = () => { + this.rect = this.ref.current.getBoundingClientRect() + } + + handleMouseDown = e => { + document.removeEventListener('mouseup', this.handleMouseUp) + document.addEventListener('mouseup', this.handleMouseUp) + + if (this.ref.current.contains(e.target)) { + this.rect = this.ref.current.getBoundingClientRect() + + if (e.target.getAttribute('role') === 'slider') { + document.addEventListener('mousemove', this.handleMouseMove) + this.type = e.target.dataset.type + return + } + + const { left, right } = this.state + const { range } = this.props + + let dx = ((e.x - this.rect.x) / this.rect.width) * 100 + if (dx > this.endPercent) { + dx = 100 + } + + const middle = range ? left + right / 2 : 0 + this.triggerChange( + dx < middle ? { left: dx, right } : { left, right: dx } + ) + } + } + + handleMouseMove = throttle(e => { + const { range } = this.props + const { left, right } = this.state + let percent = ((e.x - this.rect.x) * 100) / this.rect.width + + if (percent > this.endPercent) { + percent = 100 + } + + if (percent < 0) { + percent = 0 + } + + if (this.type === 'left' && range) { + if (percent >= right) { + this.type = 'right' + + return this.triggerChange({ + left: right, + right: percent, + }) + } + + return this.triggerChange({ + left: percent, + right, + }) + } + + if (percent <= left) { + this.type = 'left' + + return this.triggerChange({ + left: percent, + right: left, + }) + } + + return this.triggerChange({ + left, + right: percent, + }) + }, 80) + + handleMouseUp = () => { + document.removeEventListener('mousemove', this.handleMouseMove) + } + + handleInputChange = value => { + const { min, unit, onChange } = this.props + const _value = value === '' ? '' : Number(value) + + this.setState( + { + value: [min, _value], + left: 0, + right: this.getValuePercent(Number(value)) * 100, + }, + () => { + onChange(`${value || 0}${unit}`) + } + ) + } + + renderSlider(type) { + const { hasTooltip, handlerStyle, unit } = this.props + const { value } = this.state + const percent = this.state[type] + + const slider = ( +
+ ) + + if (!hasTooltip) { + return slider + } + + const tip = `${type === 'left' ? value[0] : value[1]} ${unit}` + + return {slider} + } + + renderMarks() { + const { marks, max, min } = this.props + + if (isEmpty(marks)) { + return null + } + + return ( +
+ {Object.keys(marks).map(key => ( + + {marks[key]} + + ))} +
+ ) + } + + render() { + const { left, right, value } = this.state + const { + className, + style, + railStyle, + trackStyle, + range, + unit, + withInput, + min, + max, + } = this.props + + return ( +
+
+
+
+ {range && this.renderSlider('left')} + {this.renderSlider('right')} + {this.renderMarks()} +
+ {withInput && ( +
+ + {unit && {unit}} +
+ )} +
+ ) + } +} diff --git a/src/components/Base/Slider/index.scss b/src/components/Base/Slider/index.scss new file mode 100644 index 00000000000..50dd53a3b9f --- /dev/null +++ b/src/components/Base/Slider/index.scss @@ -0,0 +1,99 @@ +@import '~scss/variables'; +@import '~scss/mixins'; + +.slider-wrapper { + display: flex; + padding: 5px; + + .input { + width: 100px; + margin-left: 20px; + margin-right: 8px; + } +} + +.slider { + position: relative; + flex: 1; + height: 20px; + padding: 5px 0; + margin-bottom: 14px; + + &-rail { + height: 10px; + border-radius: 5px; + background-color: $bg-color; + } + + &-track { + position: absolute; + top: 5px; + height: 10px; + border-radius: 5px; + background-color: $primary; + } + + &-handler { + position: absolute; + top: 0; + width: 20px; + height: 20px; + border-radius: 50%; + border: 1px solid $primary; + background-color: #fff; + box-shadow: 0 4px 8px 0 rgba(85, 188, 138, 0.36); + transition: box-shadow $trans-speed ease-in-out; + cursor: pointer; + outline: none; + user-select: none; + + &:active { + box-shadow: none; + } + + &::after { + content: ''; + display: block; + position: absolute; + top: 2px; + left: 2px; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: $primary; + transition: transform $trans-speed ease-in-out; + } + + &:hover, + &:active { + &::after { + transform: scale(1.35); + } + } + } + + &-marks { + position: absolute; + top: 22px; + left: 0; + width: 100%; + user-select: none; + + & > span { + position: absolute; + top: 0; + @include TypographyParagraph; + user-select: none; + white-space: nowrap; + transform: translateX(-50%); + + &:first-of-type { + transform: none; + } + + &:last-of-type { + transform: translateX(-100%); + } + } + } +} diff --git a/src/components/Base/index.js b/src/components/Base/index.js index dfa73a15876..e3b0f135702 100644 --- a/src/components/Base/index.js +++ b/src/components/Base/index.js @@ -43,3 +43,4 @@ export { default as Panel } from './Panel' export { default as Text } from './Text' export { default as Image } from './Image' export { default as ToggleField } from './ToggleField' +export { default as Slider } from './Slider' diff --git a/src/components/Inputs/UnitSlider/index.jsx b/src/components/Inputs/UnitSlider/index.jsx index 8bdc017b554..92c493db7be 100644 --- a/src/components/Inputs/UnitSlider/index.jsx +++ b/src/components/Inputs/UnitSlider/index.jsx @@ -17,7 +17,7 @@ */ import React from 'react' -import { Slider } from '@kube-design/components' +import { Slider } from 'components/Base' export default class UnitSlider extends React.Component { handleChange = value => {