mirror of https://github.com/apache/superset.git
fix: Adds a loading message when needed in the Select component (#16531)
This commit is contained in:
parent
2e11b05d73
commit
02798a3517
|
@ -300,6 +300,7 @@ const USERS = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const AsyncSelect = ({
|
export const AsyncSelect = ({
|
||||||
|
fetchOnlyOnSearch,
|
||||||
withError,
|
withError,
|
||||||
withInitialValue,
|
withInitialValue,
|
||||||
responseTime,
|
responseTime,
|
||||||
|
@ -381,7 +382,9 @@ export const AsyncSelect = ({
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
{...rest}
|
{...rest}
|
||||||
|
fetchOnlyOnSearch={fetchOnlyOnSearch}
|
||||||
options={withError ? fetchUserListError : fetchUserListPage}
|
options={withError ? fetchUserListError : fetchUserListPage}
|
||||||
|
placeholder={fetchOnlyOnSearch ? 'Type anything' : 'Select...'}
|
||||||
value={
|
value={
|
||||||
withInitialValue
|
withInitialValue
|
||||||
? { label: 'Valentina', value: 'Valentina' }
|
? { label: 'Valentina', value: 'Valentina' }
|
||||||
|
|
|
@ -137,6 +137,13 @@ const StyledSpin = styled(Spin)`
|
||||||
margin-top: ${({ theme }) => -theme.gridUnit}px;
|
margin-top: ${({ theme }) => -theme.gridUnit}px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledLoadingText = styled.span`
|
||||||
|
${({ theme }) => `
|
||||||
|
margin-left: ${theme.gridUnit * 3}px;
|
||||||
|
color: ${theme.colors.grayscale.light1};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
const MAX_TAG_COUNT = 4;
|
const MAX_TAG_COUNT = 4;
|
||||||
const TOKEN_SEPARATORS = [',', '\n', '\t', ';'];
|
const TOKEN_SEPARATORS = [',', '\n', '\t', ';'];
|
||||||
const DEBOUNCE_TIMEOUT = 500;
|
const DEBOUNCE_TIMEOUT = 500;
|
||||||
|
@ -175,7 +182,8 @@ const Select = ({
|
||||||
);
|
);
|
||||||
const [selectValue, setSelectValue] = useState(value);
|
const [selectValue, setSelectValue] = useState(value);
|
||||||
const [searchedValue, setSearchedValue] = useState('');
|
const [searchedValue, setSearchedValue] = useState('');
|
||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
|
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
@ -350,9 +358,10 @@ const Select = ({
|
||||||
const cachedCount = fetchedQueries.current.get(key);
|
const cachedCount = fetchedQueries.current.get(key);
|
||||||
if (cachedCount) {
|
if (cachedCount) {
|
||||||
setTotalCount(cachedCount);
|
setTotalCount(cachedCount);
|
||||||
|
setIsTyping(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setIsLoading(true);
|
||||||
const fetchOptions = options as OptionsPagePromise;
|
const fetchOptions = options as OptionsPagePromise;
|
||||||
fetchOptions(value, page, pageSize)
|
fetchOptions(value, page, pageSize)
|
||||||
.then(({ data, totalCount }: OptionsTypePage) => {
|
.then(({ data, totalCount }: OptionsTypePage) => {
|
||||||
|
@ -361,39 +370,56 @@ const Select = ({
|
||||||
setTotalCount(totalCount);
|
setTotalCount(totalCount);
|
||||||
})
|
})
|
||||||
.catch(onError)
|
.catch(onError)
|
||||||
.finally(() => setLoading(false));
|
.finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsTyping(false);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[options],
|
[options],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOnSearch = debounce((search: string) => {
|
const handleOnSearch = useMemo(
|
||||||
const searchValue = search.trim();
|
() =>
|
||||||
// enables option creation
|
debounce((search: string) => {
|
||||||
if (allowNewOptions && isSingleMode) {
|
const searchValue = search.trim();
|
||||||
const firstOption = selectOptions.length > 0 && selectOptions[0].value;
|
// enables option creation
|
||||||
// replaces the last search value entered with the new one
|
if (allowNewOptions && isSingleMode) {
|
||||||
// only when the value wasn't part of the original options
|
const firstOption =
|
||||||
if (
|
selectOptions.length > 0 && selectOptions[0].value;
|
||||||
searchValue &&
|
// replaces the last search value entered with the new one
|
||||||
firstOption === searchedValue &&
|
// only when the value wasn't part of the original options
|
||||||
!initialOptions.find(o => o.value === searchedValue)
|
if (
|
||||||
) {
|
searchValue &&
|
||||||
selectOptions.shift();
|
firstOption === searchedValue &&
|
||||||
setSelectOptions(selectOptions);
|
!initialOptions.find(o => o.value === searchedValue)
|
||||||
}
|
) {
|
||||||
if (searchValue && !hasOption(searchValue, selectOptions)) {
|
selectOptions.shift();
|
||||||
const newOption = {
|
setSelectOptions(selectOptions);
|
||||||
label: searchValue,
|
}
|
||||||
value: searchValue,
|
if (searchValue && !hasOption(searchValue, selectOptions)) {
|
||||||
};
|
const newOption = {
|
||||||
// adds a custom option
|
label: searchValue,
|
||||||
const newOptions = [...selectOptions, newOption];
|
value: searchValue,
|
||||||
setSelectOptions(newOptions);
|
};
|
||||||
setSelectValue(searchValue);
|
// adds a custom option
|
||||||
}
|
const newOptions = [...selectOptions, newOption];
|
||||||
}
|
setSelectOptions(newOptions);
|
||||||
setSearchedValue(searchValue);
|
setSelectValue(searchValue);
|
||||||
}, DEBOUNCE_TIMEOUT);
|
}
|
||||||
|
}
|
||||||
|
setSearchedValue(searchValue);
|
||||||
|
if (!searchValue) {
|
||||||
|
setIsTyping(false);
|
||||||
|
}
|
||||||
|
}, DEBOUNCE_TIMEOUT),
|
||||||
|
[
|
||||||
|
allowNewOptions,
|
||||||
|
initialOptions,
|
||||||
|
isSingleMode,
|
||||||
|
searchedValue,
|
||||||
|
selectOptions,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const handlePagination = (e: UIEvent<HTMLElement>) => {
|
const handlePagination = (e: UIEvent<HTMLElement>) => {
|
||||||
const vScroll = e.currentTarget;
|
const vScroll = e.currentTarget;
|
||||||
|
@ -469,9 +495,18 @@ const Select = ({
|
||||||
if (!isDropdownVisible) {
|
if (!isDropdownVisible) {
|
||||||
originNode.ref?.current?.scrollTo({ top: 0 });
|
originNode.ref?.current?.scrollTo({ top: 0 });
|
||||||
}
|
}
|
||||||
|
if ((isLoading && selectOptions.length === 0) || isTyping) {
|
||||||
|
return <StyledLoadingText>{t('Loading...')}</StyledLoadingText>;
|
||||||
|
}
|
||||||
return error ? <Error error={error} /> : originNode;
|
return error ? <Error error={error} /> : originNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onInputKeyDown = () => {
|
||||||
|
if (isAsync && !isTyping) {
|
||||||
|
setIsTyping(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const SuffixIcon = () => {
|
const SuffixIcon = () => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <StyledSpin size="small" />;
|
return <StyledSpin size="small" />;
|
||||||
|
@ -496,6 +531,7 @@ const Select = ({
|
||||||
mode={mappedMode}
|
mode={mappedMode}
|
||||||
onDeselect={handleOnDeselect}
|
onDeselect={handleOnDeselect}
|
||||||
onDropdownVisibleChange={handleOnDropdownVisibleChange}
|
onDropdownVisibleChange={handleOnDropdownVisibleChange}
|
||||||
|
onInputKeyDown={onInputKeyDown}
|
||||||
onPopupScroll={isAsync ? handlePagination : undefined}
|
onPopupScroll={isAsync ? handlePagination : undefined}
|
||||||
onSearch={shouldShowSearch ? handleOnSearch : undefined}
|
onSearch={shouldShowSearch ? handleOnSearch : undefined}
|
||||||
onSelect={handleOnSelect}
|
onSelect={handleOnSelect}
|
||||||
|
|
Loading…
Reference in New Issue