From fa8c81e1b17fb713de172b5a763d8233ea5efba0 Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Tue, 8 Feb 2022 13:45:10 +0100 Subject: [PATCH] chore: upgrade BoundsControl to TS (#18200) * chore: upgrade BoundsControl to TS,FC, add storybook * chore: improve React import reference consistency --- .../components/controls/BoundsControl.jsx | 129 ------------------ .../controls/BoundsControl.stories.tsx | 54 ++++++++ .../components/controls/BoundsControl.tsx | 105 ++++++++++++++ 3 files changed, 159 insertions(+), 129 deletions(-) delete mode 100644 superset-frontend/src/explore/components/controls/BoundsControl.jsx create mode 100644 superset-frontend/src/explore/components/controls/BoundsControl.stories.tsx create mode 100644 superset-frontend/src/explore/components/controls/BoundsControl.tsx diff --git a/superset-frontend/src/explore/components/controls/BoundsControl.jsx b/superset-frontend/src/explore/components/controls/BoundsControl.jsx deleted file mode 100644 index 39a0d560bc333..0000000000000 --- a/superset-frontend/src/explore/components/controls/BoundsControl.jsx +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 from 'react'; -import PropTypes from 'prop-types'; -import { InputNumber } from 'src/common/components'; -import { t, styled } from '@superset-ui/core'; -import { isEqual, debounce } from 'lodash'; -import ControlHeader from 'src/explore/components/ControlHeader'; - -const propTypes = { - onChange: PropTypes.func, - value: PropTypes.array, -}; - -const defaultProps = { - onChange: () => {}, - value: [null, null], -}; - -const StyledDiv = styled.div` - display: flex; -`; - -const MinInput = styled(InputNumber)` - flex: 1; - margin-right: ${({ theme }) => theme.gridUnit}px; -`; - -const MaxInput = styled(InputNumber)` - flex: 1; - margin-left: ${({ theme }) => theme.gridUnit}px; -`; - -export default class BoundsControl extends React.Component { - constructor(props) { - super(props); - this.state = { - minMax: [ - Number.isNaN(this.props.value[0]) ? '' : props.value[0], - Number.isNaN(this.props.value[1]) ? '' : props.value[1], - ], - }; - this.onChange = debounce(this.onChange.bind(this), 300); - this.onMinChange = this.onMinChange.bind(this); - this.onMaxChange = this.onMaxChange.bind(this); - this.update = this.update.bind(this); - } - - componentDidUpdate(prevProps) { - if (!isEqual(prevProps.value, this.props.value)) { - this.update(); - } - } - - update() { - this.setState({ - minMax: [ - Number.isNaN(this.props.value[0]) ? '' : this.props.value[0], - Number.isNaN(this.props.value[1]) ? '' : this.props.value[1], - ], - }); - } - - onMinChange(value) { - this.setState( - prevState => ({ - minMax: [value, prevState.minMax[1]], - }), - this.onChange, - ); - } - - onMaxChange(value) { - this.setState( - prevState => ({ - minMax: [prevState.minMax[0], value], - }), - this.onChange, - ); - } - - onChange() { - const mm = this.state.minMax; - const min = Number.isNaN(parseFloat(mm[0])) ? null : parseFloat(mm[0]); - const max = Number.isNaN(parseFloat(mm[1])) ? null : parseFloat(mm[1]); - this.props.onChange([min, max]); - } - - render() { - return ( -
- - - - - -
- ); - } -} - -BoundsControl.propTypes = propTypes; -BoundsControl.defaultProps = defaultProps; diff --git a/superset-frontend/src/explore/components/controls/BoundsControl.stories.tsx b/superset-frontend/src/explore/components/controls/BoundsControl.stories.tsx new file mode 100644 index 0000000000000..04c197682f3c7 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/BoundsControl.stories.tsx @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 from 'react'; +import BoundsControl, { BoundsControlProps } from './BoundsControl'; + +export default { + title: 'BoundsControl', + component: BoundsControl, +}; + +export const InteractiveBoundsControl = ( + args: BoundsControlProps & { initialMin: number; initialMax: number }, +) => { + const { initialMin, initialMax, ...props } = args; + + return ( + <> + + + ); +}; + +InteractiveBoundsControl.args = { + initialMin: 0, + initialMax: 50, +}; + +InteractiveBoundsControl.argTypes = { + onChange: { action: 'onChange' }, +}; + +InteractiveBoundsControl.story = { + parameters: { + knobs: { + disable: true, + }, + }, +}; diff --git a/superset-frontend/src/explore/components/controls/BoundsControl.tsx b/superset-frontend/src/explore/components/controls/BoundsControl.tsx new file mode 100644 index 0000000000000..1565ce7d31c2c --- /dev/null +++ b/superset-frontend/src/explore/components/controls/BoundsControl.tsx @@ -0,0 +1,105 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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, { useEffect, useRef, useState } from 'react'; +import { InputNumber } from 'src/common/components'; +import { t, styled } from '@superset-ui/core'; +import { debounce } from 'lodash'; +import ControlHeader from 'src/explore/components/ControlHeader'; + +type ValueType = (number | null)[]; + +export type BoundsControlProps = { + onChange?: (value: ValueType) => void; + value?: ValueType; +}; + +const StyledDiv = styled.div` + display: flex; +`; + +const MinInput = styled(InputNumber)` + flex: 1; + margin-right: ${({ theme }) => theme.gridUnit}px; +`; + +const MaxInput = styled(InputNumber)` + flex: 1; + margin-left: ${({ theme }) => theme.gridUnit}px; +`; + +const parseNumber = (value: undefined | number | string | null) => + value === null || Number.isNaN(Number(value)) ? null : Number(value); + +export default function BoundsControl({ + onChange = () => {}, + value = [null, null], + ...props +}: BoundsControlProps) { + const [minMax, setMinMax] = useState([ + parseNumber(value[0]), + parseNumber(value[1]), + ]); + const min = value[0]; + const max = value[1]; + const debouncedOnChange = useRef(debounce(onChange, 300)).current; + + const update = (mm: ValueType) => { + setMinMax(mm); + debouncedOnChange([ + mm[0] === undefined ? null : mm[0], + mm[1] === undefined ? null : mm[1], + ]); + }; + + useEffect(() => { + setMinMax([parseNumber(min), parseNumber(max)]); + }, [min, max]); + + const onMinChange = (value: number | string | undefined) => { + update([parseNumber(value), minMax[1]]); + }; + + const onMaxChange = (value: number | string | undefined) => { + update([minMax[0], parseNumber(value)]); + }; + + return ( +
+ + + + + +
+ ); +}