refactor(dify-ui): expose autocomplete popup anatomy (#40895)

This commit is contained in:
yyh 2026-08-18 02:06:03 +00:00 committed by GitHub
parent 394b43376a
commit 76a91eb433
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 292 additions and 236 deletions

View File

@ -229,7 +229,7 @@ Convert Figma output such as `rounded-[var(--radius/sm, 6px)]` to the mapped Tai
## Overlay & portal contract
Overlay primitives render their floating surfaces inside a [Base UI Portal] attached to `document.body`. This is the Base UI default — see the upstream [Portals][Base UI Portal] docs for the underlying behavior. Convenience content components such as `DialogContent`, `PopoverContent`, and `SelectContent` own their portal internally; primitives with explicit portal anatomy such as `Drawer` expose the matching `DrawerPortal` part so consumers can compose the full Base UI structure.
Overlay primitives render their floating surfaces inside a [Base UI Portal] attached to `document.body`. This is the Base UI default — see the upstream [Portals][Base UI Portal] docs for the underlying behavior. Convenience content components such as `DialogContent`, `PopoverContent`, and `SelectContent` own their portal internally. Primitives with explicit anatomy expose their constituent parts so consumers can compose the Base UI structure and put behavior on its owner.
### Root isolation requirement

View File

@ -3,7 +3,6 @@ import { render } from 'vitest-browser-react'
import {
Autocomplete,
AutocompleteClear,
AutocompleteContent,
AutocompleteEmpty,
AutocompleteGroup,
AutocompleteGroupLabel,
@ -13,6 +12,9 @@ import {
AutocompleteItemIndicator,
AutocompleteItemText,
AutocompleteList,
AutocompletePopup,
AutocompletePortal,
AutocompletePositioner,
AutocompleteSeparator,
AutocompleteStatus,
AutocompleteTrigger,
@ -41,28 +43,23 @@ const renderAutocomplete = ({
<AutocompleteClear data-testid="clear" />
<AutocompleteTrigger data-testid="trigger" />
</AutocompleteInputGroup>
<AutocompleteContent
positionerProps={{
role: 'group',
'aria-label': 'autocomplete positioner',
}}
popupProps={{
role: 'dialog',
'aria-label': 'autocomplete popup',
}}
>
<AutocompleteStatus data-testid="status">2 suggestions</AutocompleteStatus>
<AutocompleteList role="listbox" aria-label="autocomplete list" data-testid="list">
<AutocompleteItem value="workflow">
<AutocompleteItemText>Workflow</AutocompleteItemText>
<AutocompleteItemIndicator />
</AutocompleteItem>
<AutocompleteItem value="dataset">
<AutocompleteItemText>Dataset</AutocompleteItemText>
</AutocompleteItem>
</AutocompleteList>
<AutocompleteEmpty data-testid="empty">No suggestions</AutocompleteEmpty>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner role="group" aria-label="autocomplete positioner">
<AutocompletePopup role="dialog" aria-label="autocomplete popup">
<AutocompleteStatus data-testid="status">2 suggestions</AutocompleteStatus>
<AutocompleteList role="listbox" aria-label="autocomplete list" data-testid="list">
<AutocompleteItem value="workflow">
<AutocompleteItemText>Workflow</AutocompleteItemText>
<AutocompleteItemIndicator />
</AutocompleteItem>
<AutocompleteItem value="dataset">
<AutocompleteItemText>Dataset</AutocompleteItemText>
</AutocompleteItem>
</AutocompleteList>
<AutocompleteEmpty data-testid="empty">No suggestions</AutocompleteEmpty>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</React.Fragment>
)}
</Autocomplete>,
@ -182,30 +179,34 @@ describe('Autocomplete wrappers', () => {
.toHaveAttribute('data-align', 'start')
})
it('should apply custom placement side and passthrough popup props', async () => {
it('should apply custom placement and popup props to their owning parts', async () => {
const onPopupClick = vi.fn()
const screen = await renderWithSafeViewport(
<Autocomplete open defaultValue="workflow" items={['workflow']}>
<AutocompleteInputGroup>
<AutocompleteInput aria-label="Search suggestions" />
</AutocompleteInputGroup>
<AutocompleteContent
placement="top-end"
sideOffset={12}
alignOffset={6}
positionerProps={{ role: 'group', 'aria-label': 'autocomplete positioner' }}
popupProps={{
role: 'dialog',
'aria-label': 'autocomplete popup',
onClick: onPopupClick,
}}
>
<AutocompleteList role="listbox" aria-label="autocomplete list">
<AutocompleteItem value="workflow">
<AutocompleteItemText>Workflow</AutocompleteItemText>
</AutocompleteItem>
</AutocompleteList>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner
placement="top-end"
sideOffset={12}
alignOffset={6}
role="group"
aria-label="autocomplete positioner"
>
<AutocompletePopup
role="dialog"
aria-label="autocomplete popup"
onClick={onPopupClick}
>
<AutocompleteList role="listbox" aria-label="autocomplete list">
<AutocompleteItem value="workflow">
<AutocompleteItemText>Workflow</AutocompleteItemText>
</AutocompleteItem>
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>,
)
@ -223,18 +224,27 @@ describe('Autocomplete wrappers', () => {
<AutocompleteInputGroup>
<AutocompleteInput aria-label="Search suggestions" />
</AutocompleteInputGroup>
<AutocompleteContent popupProps={{ role: 'dialog', 'aria-label': 'autocomplete popup' }}>
<AutocompleteList role="listbox" aria-label="autocomplete list">
<AutocompleteGroup items={['workflow']}>
<AutocompleteGroupLabel className="custom-label">Resources</AutocompleteGroupLabel>
<AutocompleteSeparator className="custom-separator" data-testid="separator" />
<AutocompleteItem value="workflow" className="custom-item">
<AutocompleteItemText className="custom-text">Workflow</AutocompleteItemText>
<AutocompleteItemIndicator className="custom-indicator" data-testid="indicator" />
</AutocompleteItem>
</AutocompleteGroup>
</AutocompleteList>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup role="dialog" aria-label="autocomplete popup">
<AutocompleteList role="listbox" aria-label="autocomplete list">
<AutocompleteGroup items={['workflow']}>
<AutocompleteGroupLabel className="custom-label">
Resources
</AutocompleteGroupLabel>
<AutocompleteSeparator className="custom-separator" data-testid="separator" />
<AutocompleteItem value="workflow" className="custom-item">
<AutocompleteItemText className="custom-text">Workflow</AutocompleteItemText>
<AutocompleteItemIndicator
className="custom-indicator"
data-testid="indicator"
/>
</AutocompleteItem>
</AutocompleteGroup>
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>,
)
@ -253,15 +263,19 @@ describe('Autocomplete wrappers', () => {
<AutocompleteInputGroup>
<AutocompleteInput aria-label="Search resources" />
</AutocompleteInputGroup>
<AutocompleteContent>
<AutocompleteList<string>>
{(item) => (
<AutocompleteItem key={item} value={item}>
<AutocompleteItemText>{item}</AutocompleteItemText>
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteList<string>>
{(item) => (
<AutocompleteItem key={item} value={item}>
<AutocompleteItemText>{item}</AutocompleteItemText>
</AutocompleteItem>
)}
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>,
)

View File

@ -6,7 +6,6 @@ import {
Autocomplete,
AutocompleteClear,
AutocompleteCollection,
AutocompleteContent,
AutocompleteEmpty,
AutocompleteGroup,
AutocompleteGroupLabel,
@ -15,6 +14,9 @@ import {
AutocompleteItem,
AutocompleteItemText,
AutocompleteList,
AutocompletePopup,
AutocompletePortal,
AutocompletePositioner,
AutocompleteSeparator,
AutocompleteStatus,
AutocompleteTrigger,
@ -369,12 +371,16 @@ const BasicTagAutocomplete = ({ size = 'medium' }: { size?: 'small' | 'medium' |
<AutocompleteClear size={size} />
<AutocompleteTrigger size={size} />
</AutocompleteInputGroup>
<AutocompleteContent>
<AutocompleteList<Suggestion>>
{(item) => <TagSuggestionItem key={item.value} item={item} />}
</AutocompleteList>
<AutocompleteEmpty>No tag suggestion. Keep the typed value.</AutocompleteEmpty>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteList<Suggestion>>
{(item) => <TagSuggestionItem key={item.value} item={item} />}
</AutocompleteList>
<AutocompleteEmpty>No tag suggestion. Keep the typed value.</AutocompleteEmpty>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
)
@ -511,15 +517,16 @@ const AsyncSearchDemo = () => {
<AutocompleteClear />
<AutocompleteTrigger />
</AutocompleteInputGroup>
<AutocompleteContent
portalProps={{ hidden: !status }}
popupProps={{ 'aria-busy': isPending || undefined }}
>
<AutocompleteStatus>{status}</AutocompleteStatus>
<AutocompleteList<Suggestion>>
{(item) => <SuggestionItem key={item.value} item={item} />}
</AutocompleteList>
</AutocompleteContent>
<AutocompletePortal hidden={!status}>
<AutocompletePositioner>
<AutocompletePopup aria-busy={isPending || undefined}>
<AutocompleteStatus>{status}</AutocompleteStatus>
<AutocompleteList<Suggestion>>
{(item) => <SuggestionItem key={item.value} item={item} />}
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
)
@ -642,29 +649,33 @@ const FuzzyMatchingDemo = () => {
<AutocompleteClear />
<AutocompleteTrigger />
</AutocompleteInputGroup>
<AutocompleteContent>
<AutocompleteList<Suggestion>>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.icon && (
<span
className={cn(item.icon, 'size-4 shrink-0 text-text-tertiary')}
aria-hidden="true"
/>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteList<Suggestion>>
{(item) => (
<AutocompleteItem key={item.value} value={item}>
{item.icon && (
<span
className={cn(item.icon, 'size-4 shrink-0 text-text-tertiary')}
aria-hidden="true"
/>
)}
<div className="min-w-0 grow">
<AutocompleteItemText className="block px-0">
<FuzzyHighlight text={item.label} query={value} />
</AutocompleteItemText>
<span className="block truncate system-xs-regular text-text-tertiary">
{item.description}
</span>
</div>
</AutocompleteItem>
)}
<div className="min-w-0 grow">
<AutocompleteItemText className="block px-0">
<FuzzyHighlight text={item.label} query={value} />
</AutocompleteItemText>
<span className="block truncate system-xs-regular text-text-tertiary">
{item.description}
</span>
</div>
</AutocompleteItem>
)}
</AutocompleteList>
<AutocompleteEmpty>No workflow suggestion. Keep typing freely.</AutocompleteEmpty>
</AutocompleteContent>
</AutocompleteList>
<AutocompleteEmpty>No workflow suggestion. Keep typing freely.</AutocompleteEmpty>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
)
@ -729,12 +740,16 @@ export const InlineAutocomplete: Story = {
<AutocompleteClear />
<AutocompleteTrigger />
</AutocompleteInputGroup>
<AutocompleteContent>
<AutocompleteList<Suggestion>>
{(item) => <SuggestionItem key={item.value} item={item} dense />}
</AutocompleteList>
<AutocompleteEmpty>No inline completion. Continue typing freely.</AutocompleteEmpty>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteList<Suggestion>>
{(item) => <SuggestionItem key={item.value} item={item} dense />}
</AutocompleteList>
<AutocompleteEmpty>No inline completion. Continue typing freely.</AutocompleteEmpty>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
),
@ -761,10 +776,14 @@ export const GroupedSuggestions: Story = {
<AutocompleteClear />
<AutocompleteTrigger />
</AutocompleteInputGroup>
<AutocompleteContent popupClassName="w-[420px]">
<GroupedSuggestionList />
<AutocompleteEmpty>No suggestion. Use the text as entered.</AutocompleteEmpty>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup className="w-[420px]">
<GroupedSuggestionList />
<AutocompleteEmpty>No suggestion. Use the text as entered.</AutocompleteEmpty>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
),
@ -796,15 +815,19 @@ export const LimitResults: Story = {
<AutocompleteClear />
<AutocompleteTrigger />
</AutocompleteInputGroup>
<AutocompleteContent popupClassName="w-[420px]">
<AutocompleteStatus className="border-b border-divider-subtle">
<LimitedStatus total={workflowSuggestions.length} />
</AutocompleteStatus>
<AutocompleteList<Suggestion>>
{(item) => <SuggestionItem key={item.value} item={item} />}
</AutocompleteList>
<AutocompleteEmpty>No suggestion. Submit the typed text instead.</AutocompleteEmpty>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup className="w-[420px]">
<AutocompleteStatus className="border-b border-divider-subtle">
<LimitedStatus total={workflowSuggestions.length} />
</AutocompleteStatus>
<AutocompleteList<Suggestion>>
{(item) => <SuggestionItem key={item.value} item={item} />}
</AutocompleteList>
<AutocompleteEmpty>No suggestion. Submit the typed text instead.</AutocompleteEmpty>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
),
@ -867,11 +890,15 @@ const VirtualizedLongSuggestionsDemo = () => {
<AutocompleteClear />
<AutocompleteTrigger />
</AutocompleteInputGroup>
<AutocompleteContent popupClassName="w-[440px] p-1">
<VirtualizedStatus />
<VirtualizedSuggestionList virtualizerRef={virtualizerRef} />
<AutocompleteEmpty>No suggestion. Free-form text is still valid.</AutocompleteEmpty>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup className="w-[440px] p-1">
<VirtualizedStatus />
<VirtualizedSuggestionList virtualizerRef={virtualizerRef} />
<AutocompleteEmpty>No suggestion. Free-form text is still valid.</AutocompleteEmpty>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
)
@ -907,12 +934,18 @@ export const Empty: Story = {
<AutocompleteClear />
<AutocompleteTrigger />
</AutocompleteInputGroup>
<AutocompleteContent>
<AutocompleteList<Suggestion>>
{(item) => <TagSuggestionItem key={item.value} item={item} />}
</AutocompleteList>
<AutocompleteEmpty>No tag suggestion. The custom text remains valid.</AutocompleteEmpty>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteList<Suggestion>>
{(item) => <TagSuggestionItem key={item.value} item={item} />}
</AutocompleteList>
<AutocompleteEmpty>
No tag suggestion. The custom text remains valid.
</AutocompleteEmpty>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
),
@ -933,11 +966,15 @@ export const DisabledAndReadOnly: Story = {
<AutocompleteClear />
<AutocompleteTrigger />
</AutocompleteInputGroup>
<AutocompleteContent>
<AutocompleteList<Suggestion>>
{(item) => <TagSuggestionItem key={item.value} item={item} />}
</AutocompleteList>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteList<Suggestion>>
{(item) => <TagSuggestionItem key={item.value} item={item} />}
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
<Autocomplete
items={promptCompletions}
@ -951,11 +988,15 @@ export const DisabledAndReadOnly: Story = {
<AutocompleteClear />
<AutocompleteTrigger />
</AutocompleteInputGroup>
<AutocompleteContent>
<AutocompleteList<Suggestion>>
{(item) => <SuggestionItem key={item.value} item={item} />}
</AutocompleteList>
</AutocompleteContent>
<AutocompletePortal>
<AutocompletePositioner>
<AutocompletePopup>
<AutocompleteList<Suggestion>>
{(item) => <SuggestionItem key={item.value} item={item} />}
</AutocompleteList>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</div>
),

View File

@ -250,56 +250,46 @@ function AutocompleteIcon({ className, children, ...props }: AutocompleteIconPro
)
}
type AutocompleteContentProps = {
children: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number
const AutocompletePortal = BaseAutocomplete.Portal
type AutocompletePortalProps = BaseAutocomplete.Portal.Props
type AutocompletePositionerProps = Omit<
BaseAutocomplete.Positioner.Props,
'className' | 'side' | 'align'
> & {
className?: string
popupClassName?: string
portalProps?: Omit<BaseAutocomplete.Portal.Props, 'children'>
positionerProps?: Omit<
BaseAutocomplete.Positioner.Props,
'children' | 'className' | 'side' | 'align' | 'sideOffset' | 'alignOffset'
>
popupProps?: Omit<BaseAutocomplete.Popup.Props, 'children' | 'className'>
placement?: Placement
}
function AutocompleteContent({
children,
function AutocompletePositioner({
className,
placement = 'bottom-start',
sideOffset = 4,
alignOffset = 0,
className,
popupClassName,
portalProps,
positionerProps,
popupProps,
}: AutocompleteContentProps) {
...props
}: AutocompletePositionerProps) {
const { side, align } = parsePlacement(placement)
return (
<BaseAutocomplete.Portal {...portalProps}>
<BaseAutocomplete.Positioner
side={side}
align={align}
sideOffset={sideOffset}
alignOffset={alignOffset}
className={cn('z-50 outline-hidden', className)}
{...positionerProps}
>
<BaseAutocomplete.Popup
className={cn(
autocompletePopupClassName,
floatingPopupAnimationClassName,
popupClassName,
)}
{...popupProps}
>
{children}
</BaseAutocomplete.Popup>
</BaseAutocomplete.Positioner>
</BaseAutocomplete.Portal>
<BaseAutocomplete.Positioner
side={side}
align={align}
sideOffset={sideOffset}
className={cn('z-50 outline-hidden', className)}
{...props}
/>
)
}
type AutocompletePopupProps = Omit<BaseAutocomplete.Popup.Props, 'className'> & {
className?: string
}
function AutocompletePopup({ className, ...props }: AutocompletePopupProps) {
return (
<BaseAutocomplete.Popup
className={cn(autocompletePopupClassName, floatingPopupAnimationClassName, className)}
{...props}
/>
)
}
@ -405,7 +395,6 @@ export {
Autocomplete,
AutocompleteClear,
AutocompleteCollection,
AutocompleteContent,
AutocompleteEmpty,
AutocompleteGroup,
AutocompleteGroupLabel,
@ -416,6 +405,9 @@ export {
AutocompleteItemIndicator,
AutocompleteItemText,
AutocompleteList,
AutocompletePopup,
AutocompletePortal,
AutocompletePositioner,
AutocompleteRow,
AutocompleteSeparator,
AutocompleteStatus,
@ -429,7 +421,6 @@ export type {
AutocompleteChangeEventDetails,
AutocompleteClearProps,
AutocompleteCollectionProps,
AutocompleteContentProps,
AutocompleteEmptyProps,
AutocompleteFlatProps,
AutocompleteGroupedProps,
@ -442,6 +433,9 @@ export type {
AutocompleteItemProps,
AutocompleteItemTextProps,
AutocompleteListProps,
AutocompletePopupProps,
AutocompletePortalProps,
AutocompletePositionerProps,
AutocompleteProps,
AutocompleteRowProps,
AutocompleteSeparatorProps,

View File

@ -2,13 +2,15 @@ import type { AutocompleteChangeEventDetails } from '@langgenius/dify-ui/autocom
import {
Autocomplete,
AutocompleteCollection,
AutocompleteContent,
AutocompleteEmpty,
AutocompleteInput,
AutocompleteInputGroup,
AutocompleteItem,
AutocompleteItemText,
AutocompleteList,
AutocompletePopup,
AutocompletePortal,
AutocompletePositioner,
AutocompleteStatus,
} from '@langgenius/dify-ui/autocomplete'
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
@ -101,58 +103,63 @@ const InstitutionField = ({ value, onValueChange }: InstitutionFieldProps) => {
placeholder={t(($) => $['form.schoolName.placeholder'], { ns: 'education' })}
/>
</AutocompleteInputGroup>
<AutocompleteContent
popupClassName="w-(--anchor-width) max-w-(--available-width)"
portalProps={{ keepMounted: true }}
popupProps={{ 'aria-busy': (shouldOpenPopup && isLoading) || undefined }}
>
<AutocompleteList ref={listRef}>
<AutocompleteCollection<string>>
{(institution) => (
<AutocompleteItem key={institution} value={institution} title={institution}>
<AutocompleteItemText>{institution}</AutocompleteItemText>
</AutocompleteItem>
)}
</AutocompleteCollection>
{footerState !== null ? (
<div
className="relative flex h-10 items-center justify-center px-3"
aria-hidden="true"
>
{hasNextPage ? (
<InfiniteScrollSentinel
className="absolute inset-x-0 top-0"
canLoadMore={canLoadMore}
onLoadMore={() => {
void fetchNextPage({ cancelRefetch: false })
}}
preloadDistance={5}
scrollContainerRef={listRef}
/>
<AutocompletePortal keepMounted>
<AutocompletePositioner>
<AutocompletePopup
className="w-(--anchor-width) max-w-(--available-width)"
aria-busy={(shouldOpenPopup && isLoading) || undefined}
>
<AutocompleteList ref={listRef}>
<AutocompleteCollection<string>>
{(institution) => (
<AutocompleteItem key={institution} value={institution} title={institution}>
<AutocompleteItemText>{institution}</AutocompleteItemText>
</AutocompleteItem>
)}
</AutocompleteCollection>
{footerState !== null ? (
<div
className="relative flex h-10 items-center justify-center px-3"
aria-hidden="true"
>
{hasNextPage ? (
<InfiniteScrollSentinel
className="absolute inset-x-0 top-0"
canLoadMore={canLoadMore}
onLoadMore={() => {
void fetchNextPage({ cancelRefetch: false })
}}
preloadDistance={5}
scrollContainerRef={listRef}
/>
) : null}
{footerState === 'error' ? (
<span className="system-sm-regular text-text-destructive">
{t(($) => $['dynamicSelect.error'], { ns: 'common' })}
</span>
) : (
<Loading className="h-10" />
)}
</div>
) : null}
{footerState === 'error' ? (
<span className="system-sm-regular text-text-destructive">
{t(($) => $['dynamicSelect.error'], { ns: 'common' })}
</span>
) : (
<Loading className="h-10" />
)}
</div>
) : null}
</AutocompleteList>
<AutocompleteEmpty>
{shouldShowEmpty ? t(($) => $['form.schoolName.noResults'], { ns: 'education' }) : null}
</AutocompleteEmpty>
<AutocompleteStatus className="p-0">
<span className="sr-only">
{shouldOpenPopup && isLoading
? t(($) => $.loading, { ns: 'common' })
: footerState === 'error'
? t(($) => $['dynamicSelect.error'], { ns: 'common' })
</AutocompleteList>
<AutocompleteEmpty>
{shouldShowEmpty
? t(($) => $['form.schoolName.noResults'], { ns: 'education' })
: null}
</span>
</AutocompleteStatus>
</AutocompleteContent>
</AutocompleteEmpty>
<AutocompleteStatus className="p-0">
<span className="sr-only">
{shouldOpenPopup && isLoading
? t(($) => $.loading, { ns: 'common' })
: footerState === 'error'
? t(($) => $['dynamicSelect.error'], { ns: 'common' })
: null}
</span>
</AutocompleteStatus>
</AutocompletePopup>
</AutocompletePositioner>
</AutocompletePortal>
</Autocomplete>
</Field>
)