refactor(input): Migrate Input component to Ant Design 5 (#30730)
This commit is contained in:
parent
6e665c3e07
commit
a7069e60e2
|
|
@ -234,7 +234,7 @@ describe('Time range filter', () => {
|
|||
|
||||
cy.get('[data-test=time-range-trigger]').click();
|
||||
cy.get('[data-test=custom-frame]').then(() => {
|
||||
cy.get('.ant-input-number-input-wrap > input')
|
||||
cy.get('.antd5-input-number-input-wrap > input')
|
||||
.invoke('attr', 'value')
|
||||
.should('eq', '7');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export const GlobalStyles = () => (
|
|||
}
|
||||
}
|
||||
.column-config-popover {
|
||||
& .ant-input-number {
|
||||
& .antd5-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
&& .btn-group svg {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import {
|
|||
import rison from 'rison';
|
||||
import { debounce } from 'lodash';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import { AntdInput } from 'src/components';
|
||||
import Icons from 'src/components/Icons';
|
||||
import { Input } from 'src/components/Input';
|
||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
|
|
@ -55,6 +54,7 @@ import {
|
|||
supersetGetCache,
|
||||
} from 'src/utils/cachedSupersetGet';
|
||||
import { useVerboseMap } from 'src/hooks/apiResources/datasets';
|
||||
import { InputRef } from 'antd-v5';
|
||||
import { MenuItemTooltip } from '../DisabledMenuItemTooltip';
|
||||
import DrillByModal from './DrillByModal';
|
||||
import { getSubmenuYOffset } from '../utils';
|
||||
|
|
@ -114,11 +114,12 @@ export const DrillByMenuItems = ({
|
|||
const { addDangerToast } = useToasts();
|
||||
const [isLoadingColumns, setIsLoadingColumns] = useState(true);
|
||||
const [searchInput, setSearchInput] = useState('');
|
||||
const [debouncedSearchInput, setDebouncedSearchInput] = useState('');
|
||||
const [dataset, setDataset] = useState<Dataset>();
|
||||
const [columns, setColumns] = useState<Column[]>([]);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [currentColumn, setCurrentColumn] = useState();
|
||||
const ref = useRef<AntdInput>(null);
|
||||
const ref = useRef<InputRef>(null);
|
||||
const showSearch =
|
||||
loadDrillByOptions || columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD;
|
||||
const handleSelection = useCallback(
|
||||
|
|
@ -138,11 +139,11 @@ export const DrillByMenuItems = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
ref.current?.input.focus({ preventScroll: true });
|
||||
ref.current?.input?.focus({ preventScroll: true });
|
||||
} else {
|
||||
// Reset search input when menu is closed
|
||||
ref.current?.setValue('');
|
||||
setSearchInput('');
|
||||
setDebouncedSearchInput('');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
|
|
@ -207,19 +208,27 @@ export const DrillByMenuItems = ({
|
|||
hasDrillBy,
|
||||
]);
|
||||
|
||||
const handleInput = debounce(
|
||||
(value: string) => setSearchInput(value),
|
||||
FAST_DEBOUNCE,
|
||||
const debouncedSetSearchInput = useMemo(
|
||||
() =>
|
||||
debounce((value: string) => {
|
||||
setDebouncedSearchInput(value);
|
||||
}, FAST_DEBOUNCE),
|
||||
[],
|
||||
);
|
||||
|
||||
const handleInput = (value: string) => {
|
||||
setSearchInput(value);
|
||||
debouncedSetSearchInput(value);
|
||||
};
|
||||
|
||||
const filteredColumns = useMemo(
|
||||
() =>
|
||||
columns.filter(column =>
|
||||
(column.verbose_name || column.column_name)
|
||||
.toLowerCase()
|
||||
.includes(searchInput.toLowerCase()),
|
||||
.includes(debouncedSearchInput.toLowerCase()),
|
||||
),
|
||||
[columns, searchInput],
|
||||
[columns, debouncedSearchInput],
|
||||
);
|
||||
|
||||
const submenuYOffset = useMemo(
|
||||
|
|
@ -311,6 +320,7 @@ export const DrillByMenuItems = ({
|
|||
margin: ${theme.gridUnit * 2}px ${theme.gridUnit * 3}px;
|
||||
box-shadow: none;
|
||||
`}
|
||||
value={searchInput}
|
||||
/>
|
||||
)}
|
||||
{isLoadingColumns ? (
|
||||
|
|
|
|||
|
|
@ -41,13 +41,13 @@ import Dataset from 'src/types/Dataset';
|
|||
import { useDebouncedEffect } from 'src/explore/exploreUtils';
|
||||
import { SLOW_DEBOUNCE } from 'src/constants';
|
||||
import Loading from 'src/components/Loading';
|
||||
import { AntdInput } from 'src/components';
|
||||
import { Input } from 'src/components/Input';
|
||||
import {
|
||||
PAGE_SIZE as DATASET_PAGE_SIZE,
|
||||
SORT_BY as DATASET_SORT_BY,
|
||||
} from 'src/features/datasets/constants';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import { InputRef } from 'antd-v5';
|
||||
import FacePile from '../FacePile';
|
||||
|
||||
const CONFIRM_WARNING_MESSAGE = t(
|
||||
|
|
@ -115,7 +115,7 @@ const ChangeDatasourceModal: FunctionComponent<ChangeDatasourceModalProps> = ({
|
|||
const [sortBy, setSortBy] = useState<SortByType>(DATASET_SORT_BY);
|
||||
const [confirmChange, setConfirmChange] = useState(false);
|
||||
const [confirmedDataset, setConfirmedDataset] = useState<Datasource>();
|
||||
const searchRef = useRef<AntdInput>(null);
|
||||
const searchRef = useRef<InputRef>(null);
|
||||
|
||||
const {
|
||||
state: { loading, resourceCollection, resourceCount },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* 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 { InputProps, TextAreaProps } from 'antd-v5/lib/input';
|
||||
import { InputNumberProps } from 'antd-v5/lib/input-number';
|
||||
import { AntdThemeProvider } from 'src/components/AntdThemeProvider';
|
||||
import { Input, TextArea, InputNumber } from '.';
|
||||
|
||||
export default {
|
||||
title: 'Input',
|
||||
component: Input,
|
||||
};
|
||||
|
||||
export const InteractiveInput = (args: InputProps) => (
|
||||
<AntdThemeProvider>
|
||||
<Input {...args} />
|
||||
</AntdThemeProvider>
|
||||
);
|
||||
|
||||
export const InteractiveInputNumber = (args: InputNumberProps) => (
|
||||
<AntdThemeProvider>
|
||||
<InputNumber {...args} />
|
||||
</AntdThemeProvider>
|
||||
);
|
||||
|
||||
export const InteractiveTextArea = (args: TextAreaProps) => (
|
||||
<AntdThemeProvider>
|
||||
<TextArea {...args} />
|
||||
</AntdThemeProvider>
|
||||
);
|
||||
|
||||
InteractiveInput.args = {
|
||||
allowClear: false,
|
||||
disabled: false,
|
||||
showCount: false,
|
||||
type: 'text',
|
||||
variant: 'outlined',
|
||||
};
|
||||
|
||||
InteractiveInput.argTypes = {
|
||||
defaultValue: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
|
||||
id: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
|
||||
maxLength: {
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
|
||||
status: {
|
||||
control: {
|
||||
type: 'select',
|
||||
},
|
||||
options: ['error', 'warning'],
|
||||
},
|
||||
|
||||
size: {
|
||||
control: {
|
||||
type: 'select',
|
||||
},
|
||||
options: ['large', 'middle', 'small'],
|
||||
},
|
||||
variant: {
|
||||
control: {
|
||||
type: 'select',
|
||||
},
|
||||
options: ['outlined', 'borderless', 'filled'],
|
||||
},
|
||||
};
|
||||
|
||||
InteractiveTextArea.args = {
|
||||
...InteractiveInput.args,
|
||||
autoSize: false,
|
||||
};
|
||||
|
||||
InteractiveTextArea.argTypes = InteractiveInput.argTypes;
|
||||
|
||||
InteractiveInputNumber.args = {
|
||||
autoFocus: false,
|
||||
disabled: false,
|
||||
keyboard: true,
|
||||
max: Number.MAX_SAFE_INTEGER,
|
||||
min: Number.MIN_SAFE_INTEGER,
|
||||
readonly: false,
|
||||
step: 1,
|
||||
stringMode: false,
|
||||
};
|
||||
|
||||
InteractiveInputNumber.argTypes = {
|
||||
controls: {
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
|
||||
decimalSeperator: {
|
||||
control: { type: 'text' },
|
||||
},
|
||||
|
||||
placeholder: {
|
||||
control: { type: 'text' },
|
||||
},
|
||||
|
||||
defaultValue: {
|
||||
control: { type: 'number' },
|
||||
},
|
||||
|
||||
precision: {
|
||||
control: { type: 'number' },
|
||||
},
|
||||
|
||||
status: InteractiveInput.argTypes.status,
|
||||
size: InteractiveInput.argTypes.size,
|
||||
variant: InteractiveInput.argTypes.variant,
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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 { render } from 'spec/helpers/testing-library';
|
||||
import { Input, InputNumber, TextArea } from '.';
|
||||
|
||||
test('should render Input', () => {
|
||||
const { container } = render(<Input />);
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render InputNumber', () => {
|
||||
const { container } = render(<InputNumber />);
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render TextArea', () => {
|
||||
const { container } = render(<TextArea />);
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
|
@ -16,20 +16,11 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { styled } from '@superset-ui/core';
|
||||
import { Input as AntdInput, InputNumber as AntdInputNumber } from 'antd';
|
||||
|
||||
export const Input = styled(AntdInput)`
|
||||
border: 1px solid ${({ theme }) => theme.colors.secondary.light3};
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
`;
|
||||
import { Input as AntdInput, InputNumber as AntdInputNumber } from 'antd-v5';
|
||||
|
||||
export const InputNumber = styled(AntdInputNumber)`
|
||||
border: 1px solid ${({ theme }) => theme.colors.secondary.light3};
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
`;
|
||||
export const Input = AntdInput;
|
||||
|
||||
export const TextArea = styled(AntdInput.TextArea)`
|
||||
border: 1px solid ${({ theme }) => theme.colors.secondary.light3};
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
`;
|
||||
export const InputNumber = AntdInputNumber;
|
||||
|
||||
export const { TextArea } = AntdInput;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ test('renders two inputs', () => {
|
|||
test('receives null on non-numeric', async () => {
|
||||
render(<BoundsControl {...defaultProps} />);
|
||||
const minInput = screen.getAllByRole('spinbutton')[0];
|
||||
userEvent.type(minInput, 'text');
|
||||
userEvent.type(minInput, '1');
|
||||
userEvent.clear(minInput);
|
||||
await waitFor(() =>
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith([null, null]),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -79,11 +79,11 @@ export default function BoundsControl({
|
|||
setMinMax([parseNumber(min), parseNumber(max)]);
|
||||
}, [min, max]);
|
||||
|
||||
const onMinChange = (value: number | string | undefined) => {
|
||||
const onMinChange = (value: number | string | undefined | null) => {
|
||||
update([parseNumber(value), minMax[1]]);
|
||||
};
|
||||
|
||||
const onMaxChange = (value: number | string | undefined) => {
|
||||
const onMaxChange = (value: number | string | undefined | null) => {
|
||||
update([minMax[0], parseNumber(value)]);
|
||||
};
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ export default function BoundsControl({
|
|||
<MinInput
|
||||
data-test="min-bound"
|
||||
placeholder={t('Min')}
|
||||
// emit (string | number | undefined)
|
||||
// emit (string | number | undefined | null)
|
||||
onChange={onMinChange}
|
||||
// accept (number | undefined)
|
||||
value={minMax[0] === null ? undefined : minMax[0]}
|
||||
|
|
@ -102,7 +102,7 @@ export default function BoundsControl({
|
|||
<MaxInput
|
||||
data-test="max-bound"
|
||||
placeholder={t('Max')}
|
||||
// emit (number | string | undefined)
|
||||
// emit (number | string | undefined | null)
|
||||
onChange={onMaxChange}
|
||||
// accept (number | undefined)
|
||||
value={minMax[1] === null ? undefined : minMax[1]}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const ContentStyleWrapper = styled.div`
|
|||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.ant-input-number {
|
||||
.antd5-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ const SearchWrapper = styled.div`
|
|||
margin-bottom: ${theme.gridUnit}px;
|
||||
margin-left: ${theme.gridUnit * 3}px;
|
||||
margin-right: ${theme.gridUnit * 3}px;
|
||||
.ant-input-affix-wrapper {
|
||||
.antd5-input-affix-wrapper {
|
||||
padding-left: ${theme.gridUnit * 2}px;
|
||||
}
|
||||
`}
|
||||
|
|
|
|||
|
|
@ -1004,8 +1004,14 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const onCustomWidthChange = (value: number | null | undefined) => {
|
||||
updateAlertState('custom_width', value);
|
||||
const onCustomWidthChange = (value: number | string | null | undefined) => {
|
||||
const numValue =
|
||||
value === null ||
|
||||
value === undefined ||
|
||||
(typeof value === 'string' && Number.isNaN(Number(value)))
|
||||
? null
|
||||
: Number(value);
|
||||
updateAlertState('custom_width', numValue);
|
||||
};
|
||||
|
||||
const onTimeoutVerifyChange = (
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useState, useCallback, useRef, FocusEvent, FC } from 'react';
|
||||
import { useState, useCallback, FocusEvent, FC } from 'react';
|
||||
|
||||
import { t, useTheme } from '@superset-ui/core';
|
||||
|
||||
import { AntdInput, Select } from 'src/components';
|
||||
import { Select } from 'src/components';
|
||||
import { Input } from 'src/components/Input';
|
||||
import { CronPicker, CronError } from 'src/components/CronPicker';
|
||||
import { StyledInputContainer } from '../AlertReportModal';
|
||||
|
|
@ -51,7 +51,6 @@ export const AlertReportCronScheduler: FC<AlertReportCronSchedulerProps> = ({
|
|||
onChange,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const inputRef = useRef<AntdInput>(null);
|
||||
const [scheduleFormat, setScheduleFormat] = useState<ScheduleType>(
|
||||
ScheduleType.Picker,
|
||||
);
|
||||
|
|
@ -59,9 +58,8 @@ export const AlertReportCronScheduler: FC<AlertReportCronSchedulerProps> = ({
|
|||
const customSetValue = useCallback(
|
||||
(newValue: string) => {
|
||||
onChange(newValue);
|
||||
inputRef.current?.setValue(newValue);
|
||||
},
|
||||
[inputRef, onChange],
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
|
|
@ -72,8 +70,8 @@ export const AlertReportCronScheduler: FC<AlertReportCronSchedulerProps> = ({
|
|||
);
|
||||
|
||||
const handlePressEnter = useCallback(() => {
|
||||
onChange(inputRef.current?.input.value || '');
|
||||
}, [onChange]);
|
||||
onChange(value || '');
|
||||
}, [onChange, value]);
|
||||
|
||||
const [error, onError] = useState<CronError>();
|
||||
|
||||
|
|
@ -106,7 +104,6 @@ export const AlertReportCronScheduler: FC<AlertReportCronSchedulerProps> = ({
|
|||
<Input
|
||||
type="text"
|
||||
name="crontab"
|
||||
ref={inputRef}
|
||||
style={error ? { borderColor: theme.colors.error.base } : {}}
|
||||
placeholder={t('CRON expression')}
|
||||
value={value}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import { FilterType, RLSObject, RoleObject, TableObject } from './types';
|
|||
const noMargins = css`
|
||||
margin: 0;
|
||||
|
||||
.ant-input {
|
||||
.antd5-input {
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,20 @@ const baseConfig: ThemeConfig = {
|
|||
paddingLG: supersetTheme.gridUnit * 6,
|
||||
fontWeightStrong: supersetTheme.typography.weights.medium,
|
||||
},
|
||||
Input: {
|
||||
colorBorder: supersetTheme.colors.secondary.light3,
|
||||
colorBgContainer: supersetTheme.colors.grayscale.light5,
|
||||
activeShadow: `0 0 0 ${supersetTheme.gridUnit / 2}px ${
|
||||
supersetTheme.colors.primary.light3
|
||||
}`,
|
||||
},
|
||||
InputNumber: {
|
||||
colorBorder: supersetTheme.colors.secondary.light3,
|
||||
colorBgContainer: supersetTheme.colors.grayscale.light5,
|
||||
activeShadow: `0 0 0 ${supersetTheme.gridUnit / 2}px ${
|
||||
supersetTheme.colors.primary.light3
|
||||
}`,
|
||||
},
|
||||
Tag: {
|
||||
borderRadiusSM: 2,
|
||||
defaultBg: supersetTheme.colors.grayscale.light4,
|
||||
|
|
|
|||
Loading…
Reference in New Issue