From c0f79b15f33d7ab50bc888e9c81665ee6a6520ca Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Mon, 25 May 2026 16:14:06 +0200 Subject: [PATCH 01/14] [stories] fixed visible setting for autosuggest after clear query and typing the new one --- .../docs/examples/autosuggest_example.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx b/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx index f3a9d18e17..eb1992d9ac 100644 --- a/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx +++ b/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx @@ -67,6 +67,7 @@ const Demo = () => { const [visible, setVisible] = React.useState(false); const [query, setQuery] = React.useState(''); const [suggestions, setSuggestions] = React.useState([]); + const [isEmptyQuery, setIsEmptyQuery] = React.useState(true); const loadSuggestions = React.useCallback( debounce( (query: string) => fakeFetch(query).then((suggestions) => setSuggestions(suggestions)), @@ -75,8 +76,17 @@ const Demo = () => { [], ); React.useEffect(() => { - loadSuggestions(query); - }, [query]); + if (query === '') { + setSuggestions([]); + setIsEmptyQuery(true); + } else { + if (isEmptyQuery) { + setVisible(true); + setIsEmptyQuery(false); + } + loadSuggestions(query); + } + }, [query, isEmptyQuery]); const handleSelect = React.useCallback((x: string) => { setQuery(x); setVisible(false); From 1a93246abb146cf3d7d8a8af2ed3683228bb371b Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Wed, 3 Jun 2026 12:49:44 +0200 Subject: [PATCH 02/14] [select] added new AutoSuggest component --- semcore/select/package.json | 2 +- .../components/AutoSuggest/AutoSuggest.tsx | 146 ++++++++++++++ .../AutoSuggest/AutoSuggest.type.ts | 29 +++ .../src/components/AutoSuggest/Highlight.tsx | 20 ++ semcore/select/src/index.d.ts | 6 +- semcore/select/src/index.js | 1 + .../docs/examples/autosuggest_example.tsx | 189 ++++++------------ 7 files changed, 262 insertions(+), 131 deletions(-) create mode 100644 semcore/select/src/components/AutoSuggest/AutoSuggest.tsx create mode 100644 semcore/select/src/components/AutoSuggest/AutoSuggest.type.ts create mode 100644 semcore/select/src/components/AutoSuggest/Highlight.tsx diff --git a/semcore/select/package.json b/semcore/select/package.json index 3eb66f056b..bd8b0bd0e6 100644 --- a/semcore/select/package.json +++ b/semcore/select/package.json @@ -8,7 +8,7 @@ "author": "UI-kit team ", "license": "MIT", "scripts": { - "build": "pnpm semcore-builder --source=js && pnpm vite build" + "build": "pnpm semcore-builder --source=js,ts && pnpm vite build" }, "exports": { "types": "./lib/types/index.d.ts", diff --git a/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx b/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx new file mode 100644 index 0000000000..5d9c75ab2d --- /dev/null +++ b/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx @@ -0,0 +1,146 @@ +import type { Intergalactic } from '@semcore/core'; +import { Component, createComponent, Root } from '@semcore/core'; +import Input from '@semcore/input'; +import Spin from '@semcore/spin'; +import React from 'react'; + +import type { NSAutoSuggest } from './AutoSuggest.type'; +import { Highlight } from './Highlight'; +import Select from '../../index'; + +class AutoSuggestRoot extends Component< + Intergalactic.InternalTypings.InferComponentProps, + [], + { value: string }, + {}, + NSAutoSuggest.State, + NSAutoSuggest.DefaultProps +> { + static defaultProps: NSAutoSuggest.DefaultProps = { + defaultValue: '', + }; + + private abortController: AbortController | undefined; + private changeDebounce = 0; + + state: NSAutoSuggest.State = { + isVisible: false, + highlightedIndex: -1, + suggestions: [], + openOnChanges: true, + isLoading: false, + }; + + protected uncontrolledProps() { + return { + value: (value: string) => { + return value; + }, + }; + } + + handleChange = (value: string) => { + if (this.changeDebounce) { + clearTimeout(this.changeDebounce); + } + if (this.abortController) { + this.abortController.abort(); + } + + if (value !== this.asProps.value && this.state.openOnChanges) { + const { suggestions } = this.asProps; + + if (!Array.isArray(suggestions)) { + this.setState({ isLoading: true }); + } + + this.changeDebounce = setTimeout(async () => { + this.handleChangeVisible(true); + + if (Array.isArray(suggestions)) { + const filteredSuggestions = value === '' ? [] : suggestions.filter((breed) => breed.toLowerCase().includes(value.toLowerCase())); + + this.setState({ suggestions: filteredSuggestions }); + } else { + this.abortController = new AbortController(); + const abortSignal = this.abortController.signal; + + const filteredSuggestions = await suggestions(value, abortSignal); + this.setState({ suggestions: filteredSuggestions, isLoading: false }); + } + }, 300); + } + }; + + handleChangeVisible = (isVisible: boolean) => { + this.setState({ isVisible }); + }; + + handleChangeHighlightedIndex = (index: number | null) => { + this.setState({ highlightedIndex: index ?? -1 }); + }; + + handleKeyDown = (e: React.KeyboardEvent) => { + if (!e.key.startsWith('Array')) { + this.setState({ highlightedIndex: -1 }); + } + if (e.key === 'Escape' && this.state.isVisible) { + this.setState({ openOnChanges: false }); + } + }; + + handleChangeSelect = (value: string) => { + this.handlers.value(value); + }; + + handleFocus = () => { + const { value } = this.asProps; + this.setState({ openOnChanges: true, isVisible: value === '' }); + }; + + handleBlur = () => { + this.handleChangeVisible(false); + }; + + render() { + const { value } = this.asProps; + const { isVisible, highlightedIndex, suggestions, isLoading } = this.state; + + return ( + + ); + } +} + +export const AutoSuggest = createComponent(AutoSuggestRoot); diff --git a/semcore/select/src/components/AutoSuggest/AutoSuggest.type.ts b/semcore/select/src/components/AutoSuggest/AutoSuggest.type.ts new file mode 100644 index 0000000000..a55e703dea --- /dev/null +++ b/semcore/select/src/components/AutoSuggest/AutoSuggest.type.ts @@ -0,0 +1,29 @@ +import type { Intergalactic } from '@semcore/core'; + +declare namespace NSAutoSuggest { + type Suggestion = string; + + type Props = { + value?: string; + onChange?: (value: string) => void; + suggestions: Suggestion[] | ((value: string, signal: AbortSignal) => Promise); + }; + + type State = { + isVisible: boolean; + highlightedIndex: number; + suggestions: Suggestion[]; + openOnChanges: boolean; + isLoading: boolean; + }; + + type DefaultProps = { + defaultValue: string; + }; + + type Component = Intergalactic.Component<'input', Props>; +} + +export { + NSAutoSuggest, +}; diff --git a/semcore/select/src/components/AutoSuggest/Highlight.tsx b/semcore/select/src/components/AutoSuggest/Highlight.tsx new file mode 100644 index 0000000000..b7d7b80844 --- /dev/null +++ b/semcore/select/src/components/AutoSuggest/Highlight.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +type HighlightProps = { + highlight: string; + children: string; +}; + +export function Highlight({ highlight, children }: HighlightProps) { + let html = children; + if (highlight) { + try { + const re = new RegExp(highlight.toLowerCase(), 'g'); + html = html.replace( + re, + `${highlight}`, + ); + } catch (e) {} + } + return ; +} diff --git a/semcore/select/src/index.d.ts b/semcore/select/src/index.d.ts index ab57acb032..feb4a2bbbc 100644 --- a/semcore/select/src/index.d.ts +++ b/semcore/select/src/index.d.ts @@ -16,6 +16,8 @@ import type Input from '@semcore/input'; import type { Text } from '@semcore/typography'; import type React from 'react'; +import { NSAutoSuggest } from './components/AutoSuggest/AutoSuggest.type.ts'; + export type SelectInputSearch = InputValueProps & {}; export type OptionValue = string | number; @@ -170,5 +172,7 @@ declare const wrapSelect: ( ) => React.ReactNode, ) => IntergalacticSelectComponent; -export { InputSearch, wrapSelect }; +declare const AutoSuggest = NSAutoSuggest.Component; + +export { InputSearch, wrapSelect, AutoSuggest }; export default Select; diff --git a/semcore/select/src/index.js b/semcore/select/src/index.js index 96e699c8ca..843f03a702 100644 --- a/semcore/select/src/index.js +++ b/semcore/select/src/index.js @@ -1,3 +1,4 @@ export { default as InputSearch } from './InputSearch'; export { default } from './Select'; export * from './Select'; +export { AutoSuggest } from './components/AutoSuggest/AutoSuggest'; diff --git a/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx b/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx index 6fd43d6406..95db403c1d 100644 --- a/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx +++ b/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx @@ -1,150 +1,81 @@ import { Box } from '@semcore/ui/base-components'; -import Input from '@semcore/ui/input'; -import Select from '@semcore/ui/select'; +import { AutoSuggest } from '@semcore/ui/select'; import { Text } from '@semcore/ui/typography'; import React from 'react'; -const Highlight = ({ highlight, children }: { highlight: string; children: string }) => { - let html = children; - if (highlight) { - try { - const re = new RegExp(highlight.toLowerCase(), 'g'); - html = html.replace( - re, - `${highlight}`, - ); - } catch (e) {} - } - return ; -}; +const suggestions = [ + 'persian', + 'maine coon', + 'ragdoll', + 'sphynx', + 'siamese', + 'bengal', + 'british shorthair', + 'abyssinian', + 'birman', + 'oriental shorthair', + 'scottish fold', + 'devon rex', + 'norwegian forest', + 'siberian', + 'russian blue', + 'savannah', + 'american shorthair', + 'exotic shorthair', + 'ragamuffin', + 'balinese', +]; -const debounce = (func: Function, timeout: number) => { - let timer: number; - return (...args: any[]) => { - window.clearTimeout(timer); - timer = window.setTimeout(() => { - func(...args); - }, timeout); - }; -}; +const fakeFetch = async (query: string, signal: AbortSignal): Promise => { + if (!query) return []; -type Suggestion = { - value: string; - title: string; -}; + if (signal.aborted) { + return []; + } -const fakeFetch = async (query: string): Promise => { - if (!query) return []; + return new Promise((resolve) => { + const onAbort = () => { + signal.removeEventListener('abort', onAbort); + resolve([]); + }; + signal.addEventListener('abort', onAbort); + + setTimeout(() => { + signal.removeEventListener('abort', onAbort); - return [ - 'persian', - 'maine coon', - 'ragdoll', - 'sphynx', - 'siamese', - 'bengal', - 'british shorthair', - 'abyssinian', - 'birman', - 'oriental shorthair', - 'scottish fold', - 'devon rex', - 'norwegian forest', - 'siberian', - 'russian blue', - 'savannah', - 'american shorthair', - 'exotic shorthair', - 'ragamuffin', - 'balinese', - ] - .filter((breed) => breed.toLowerCase().includes(query.toLowerCase())) - .map((value) => ({ value, title: value })); + resolve(suggestions.filter((breed) => breed.toLowerCase().includes(query.toLowerCase()))); + }, 2000); + }); }; const Demo = () => { - const [highlightedIndex, setHighlightedIndex] = React.useState(-1); - const [visible, setVisible] = React.useState(false); const [query, setQuery] = React.useState(''); - const [suggestions, setSuggestions] = React.useState([]); - const [isEmptyQuery, setIsEmptyQuery] = React.useState(true); - const loadSuggestions = React.useCallback( - debounce( - (query: string) => fakeFetch(query).then((suggestions) => setSuggestions(suggestions)), - 300, - ), - [], - ); - React.useEffect(() => { - if (query === '') { - setSuggestions([]); - setIsEmptyQuery(true); - } else { - if (isEmptyQuery) { - setVisible(true); - setIsEmptyQuery(false); - } - loadSuggestions(query); - } - }, [query, isEmptyQuery]); - const handleSelect = React.useCallback((x: string) => { - setQuery(x); - setVisible(false); - }, []); - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (!e.key.startsWith('Array')) { - setHighlightedIndex(-1); - } - }; - - const handleHighlightedIndexChange = (index: number | null) => { - setHighlightedIndex(index); - }; - - const handleChangeVisible = (visible: boolean) => { - setVisible(visible); - }; return ( <> - - Your pet breed + + ASYNC Your pet breed + + + + +
+
+ + SYNC Your pet breed - + id='sync-autosuggest' + onChange={setQuery} + suggestions={suggestions} + /> ); From 1f60ee5dfba4a2783d28b699ded7b47fa61e7ddf Mon Sep 17 00:00:00 2001 From: Sheila Sheikh <166654065+sheila-semrush@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:30:11 +0200 Subject: [PATCH 03/14] [docs] updated illustration --- .../patterns/auto-suggest/static/start.png | Bin 4973 -> 2973 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/website/docs/patterns/auto-suggest/static/start.png b/website/docs/patterns/auto-suggest/static/start.png index f0b96a1fa2d088cc87271ef4ab7110124121f75c..586fa1def3c5a8d924d286c2a7d14d69bf399ca5 100644 GIT binary patch literal 2973 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 From b931767a54417ce97537b9ca88f23231a89f3d12 Mon Sep 17 00:00:00 2001 From: Sheila Sheikh <166654065+sheila-semrush@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:33:53 +0200 Subject: [PATCH 04/14] [stories] limited width for autosuggest example --- .../auto-suggest/docs/examples/autosuggest_example.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx b/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx index 95db403c1d..34f6a38ff8 100644 --- a/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx +++ b/stories/patterns/ux-patterns/auto-suggest/docs/examples/autosuggest_example.tsx @@ -56,7 +56,7 @@ const Demo = () => { ASYNC Your pet breed - + { SYNC Your pet breed - + Date: Wed, 10 Jun 2026 09:49:55 +0200 Subject: [PATCH 05/14] [select] added states to autosuggest --- .../components/AutoSuggest/AutoSuggest.tsx | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx b/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx index 5d9c75ab2d..3f31997c9e 100644 --- a/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx +++ b/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx @@ -1,5 +1,6 @@ import type { Intergalactic } from '@semcore/core'; import { Component, createComponent, Root } from '@semcore/core'; +import uniqueIDEnhancement from '@semcore/core/lib/utils/uniqueID'; import Input from '@semcore/input'; import Spin from '@semcore/spin'; import React from 'react'; @@ -10,8 +11,8 @@ import Select from '../../index'; class AutoSuggestRoot extends Component< Intergalactic.InternalTypings.InferComponentProps, - [], - { value: string }, + typeof AutoSuggestRoot.enhance, + { value: (value: string) => string }, {}, NSAutoSuggest.State, NSAutoSuggest.DefaultProps @@ -20,6 +21,8 @@ class AutoSuggestRoot extends Component< defaultValue: '', }; + static enhance = [uniqueIDEnhancement()] as const; + private abortController: AbortController | undefined; private changeDebounce = 0; @@ -54,7 +57,7 @@ class AutoSuggestRoot extends Component< this.setState({ isLoading: true }); } - this.changeDebounce = setTimeout(async () => { + this.changeDebounce = window.setTimeout(async () => { this.handleChangeVisible(true); if (Array.isArray(suggestions)) { @@ -103,8 +106,9 @@ class AutoSuggestRoot extends Component< }; render() { - const { value } = this.asProps; + const { value, uid } = this.asProps; const { isVisible, highlightedIndex, suggestions, isLoading } = this.state; + const id = `${uid}_autosuggest-trigger`; return ( ); } From dfe133d49e706bd1c41487f03cba6b93fb3ce392 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Wed, 10 Jun 2026 22:44:17 +0200 Subject: [PATCH 06/14] [select] added StatusItem to autoSuggest --- semcore/icon/package.json | 2 +- .../select/src/components/AutoSuggest/AutoSuggest.tsx | 11 ++++++----- semcore/select/src/translations/en.json | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/semcore/icon/package.json b/semcore/icon/package.json index ff0eea93a6..66419506a0 100644 --- a/semcore/icon/package.json +++ b/semcore/icon/package.json @@ -4414,4 +4414,4 @@ "require": "./lib/ZoomPlus/m/index.js" } } -} +} \ No newline at end of file diff --git a/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx b/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx index 3f31997c9e..1de81b371c 100644 --- a/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx +++ b/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx @@ -1,5 +1,6 @@ import type { Intergalactic } from '@semcore/core'; import { Component, createComponent, Root } from '@semcore/core'; +import i18nEnhance from '@semcore/core/lib/utils/enhances/i18nEnhance'; import uniqueIDEnhancement from '@semcore/core/lib/utils/uniqueID'; import Input from '@semcore/input'; import Spin from '@semcore/spin'; @@ -8,6 +9,7 @@ import React from 'react'; import type { NSAutoSuggest } from './AutoSuggest.type'; import { Highlight } from './Highlight'; import Select from '../../index'; +import { localizedMessages } from '../../translations/__intergalactic-dynamic-locales'; class AutoSuggestRoot extends Component< Intergalactic.InternalTypings.InferComponentProps, @@ -21,7 +23,7 @@ class AutoSuggestRoot extends Component< defaultValue: '', }; - static enhance = [uniqueIDEnhancement()] as const; + static enhance = [uniqueIDEnhancement(), i18nEnhance(localizedMessages)] as const; private abortController: AbortController | undefined; private changeDebounce = 0; @@ -106,7 +108,7 @@ class AutoSuggestRoot extends Component< }; render() { - const { value, uid } = this.asProps; + const { value, uid, getI18nText } = this.asProps; const { isVisible, highlightedIndex, suggestions, isLoading } = this.state; const id = `${uid}_autosuggest-trigger`; @@ -124,7 +126,6 @@ class AutoSuggestRoot extends Component< render={Input.Value} value={value} role='combobox' - placeholder='Start typing for options' onChange={this.handleChange} onKeyDown={this.handleKeyDown} autoComplete='off' @@ -135,11 +136,11 @@ class AutoSuggestRoot extends Component< {isLoading - ? (
loading...
) /* todo use DD.StatusItem */ + ? () : ( <> {suggestions.length === 0 - ? (value.length > 0 &&
start typing to see options
) /* todo use DD.StatusItem */ + ? (value.length === 0 && {getI18nText('AutoSuggest.Popper.placeholderText')}) : ( {suggestions.map((option) => ( diff --git a/semcore/select/src/translations/en.json b/semcore/select/src/translations/en.json index a8a32c02d4..30c237891b 100644 --- a/semcore/select/src/translations/en.json +++ b/semcore/select/src/translations/en.json @@ -2,5 +2,6 @@ "clearSearch": "Clear search field", "selectPlaceholder": "Select option", "Select.InputSearch.Value:placeholder": "Search", - "Select.InputSearch.Value:aria-label": "Search" + "Select.InputSearch.Value:aria-label": "Search", + "AutoSuggest.Popper.placeholderText": "Start typing to see options" } From d37bcef9904476eba35674f31c6234ef1bdce193 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Fri, 12 Jun 2026 11:59:49 +0200 Subject: [PATCH 07/14] [select] fixed build --- pnpm-lock.yaml | 5 ++++- semcore/select/package.json | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45e5ee755e..7d56a218a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1585,6 +1585,9 @@ importers: '@semcore/input': specifier: ^17.2.0 version: link:../input + '@semcore/spin': + specifier: ^17.2.0 + version: link:../spin '@semcore/typography': specifier: ^17.2.0 version: link:../typography @@ -19613,7 +19616,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.17 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.2)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@9.20.3)(jsdom@22.1.0)(lightningcss@1.31.1)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(yaml@2.8.3) + vitest: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.2)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@9.20.3)(jsdom@22.1.0)(lightningcss@1.31.1)(sass@1.100.0)(sugarss@5.0.1(postcss@8.5.14))(terser@5.48.0)(yaml@2.8.3) '@vitest/utils@3.2.4': dependencies: diff --git a/semcore/select/package.json b/semcore/select/package.json index 254cfe7f06..fbcd24cce6 100644 --- a/semcore/select/package.json +++ b/semcore/select/package.json @@ -23,6 +23,7 @@ "@semcore/dropdown": "^17.2.0", "@semcore/dropdown-menu": "^17.2.0", "@semcore/input": "^17.2.0", + "@semcore/spin": "^17.2.0", "@semcore/typography": "^17.2.0", "classnames": "2.2.6" }, From 5b0fa1d5c005a115e725523f3dd052f8830d1606 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Fri, 12 Jun 2026 15:39:32 +0200 Subject: [PATCH 08/14] [select] fixed visual issue with empty suggestions list --- semcore/select/src/components/AutoSuggest/AutoSuggest.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx b/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx index 1de81b371c..97912a9a1c 100644 --- a/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx +++ b/semcore/select/src/components/AutoSuggest/AutoSuggest.tsx @@ -112,10 +112,12 @@ class AutoSuggestRoot extends Component< const { isVisible, highlightedIndex, suggestions, isLoading } = this.state; const id = `${uid}_autosuggest-trigger`; + const isVisiblePopper = isVisible && (value === '' || suggestions.length > 0 || isLoading); + return (