feat(ui): more flexible fallbacks for model picker

This commit is contained in:
psychedelicious
2025-04-22 19:50:50 +10:00
parent ba42c3e63f
commit fbc1aae52d
2 changed files with 26 additions and 23 deletions

View File

@@ -58,25 +58,33 @@ const DefaultGroupComponent = typedMemo(
);
DefaultGroupComponent.displayName = 'DefaultGroupComponent';
export const DefaultNoOptionsFallback = typedMemo(({ label }: { label?: string }) => {
export const NoOptionsFallbackWrapper = typedMemo(({ children }: PropsWithChildren) => {
const { t } = useTranslation();
return (
<Flex w="full" h="full" alignItems="center" justifyContent="center">
<Text variant="subtext">{label || t('common.noOptions')}</Text>
{typeof children === 'string' ? (
<Text variant="subtext">{children}</Text>
) : (
(children ?? <Text variant="subtext">{t('common.noOptions')}</Text>)
)}
</Flex>
);
});
DefaultNoOptionsFallback.displayName = 'DefaultNoOptionsFallback';
NoOptionsFallbackWrapper.displayName = 'NoOptionsFallbackWrapper';
export const DefaultNoMatchesFallback = typedMemo(({ label }: { label?: string }) => {
export const NoMatchesFallbackWrapper = typedMemo(({ children }: PropsWithChildren) => {
const { t } = useTranslation();
return (
<Flex w="full" h="full" alignItems="center" justifyContent="center">
<Text variant="subtext">{label || t('common.noMatches')}</Text>
{typeof children === 'string' ? (
<Text variant="subtext">{children}</Text>
) : (
(children ?? <Text variant="subtext">{t('common.noMatches')}</Text>)
)}
</Flex>
);
});
DefaultNoMatchesFallback.displayName = 'DefaultNoMatchesFallback';
NoMatchesFallbackWrapper.displayName = 'NoMatchesFallbackWrapper';
type PickerProps<T extends object, U, C> = {
options: (T | Group<T>)[];
@@ -108,8 +116,8 @@ type PickerContextState<T extends object, U, C> = {
onSelectById: (id: string) => void;
setSearchTerm: (searchTerm: string) => void;
SearchBarComponent: ReturnType<typeof fixedForwardRef<HTMLInputElement, InputProps>>;
noOptionsFallback: React.ReactNode;
noMatchesFallback: React.ReactNode;
noOptionsFallback?: React.ReactNode;
noMatchesFallback?: React.ReactNode;
OptionComponent: React.ComponentType<{ option: T } & BoxProps>;
GroupComponent: React.ComponentType<PropsWithChildren<{ group: Group<T, U> } & BoxProps>>;
ctx: C;
@@ -205,8 +213,8 @@ export const Picker = typedMemo(<T extends object, U = undefined, C = undefined>
onSelect,
selectedItem,
SearchBarComponent = DefaultPickerSearchBarComponent,
noMatchesFallback = <DefaultNoMatchesFallback />,
noOptionsFallback = <DefaultNoOptionsFallback />,
noMatchesFallback,
noOptionsFallback,
OptionComponent = DefaultOptionComponent,
GroupComponent = DefaultGroupComponent,
ctx: ctxProp,
@@ -410,8 +418,10 @@ export const Picker = typedMemo(<T extends object, U = undefined, C = undefined>
>
<SearchBarComponent ref={inputRef} value={searchTerm} onChange={onChangeSearchTerm} />
<Flex tabIndex={-1} w="full" flexGrow={1}>
{flattenedOptions.length === 0 && noOptionsFallback}
{flattenedOptions.length > 0 && flattenedFilteredOptions.length === 0 && noMatchesFallback}
{flattenedOptions.length === 0 && <NoOptionsFallbackWrapper>{noOptionsFallback}</NoOptionsFallbackWrapper>}
{flattenedOptions.length > 0 && flattenedFilteredOptions.length === 0 && (
<NoMatchesFallbackWrapper>{noMatchesFallback}</NoMatchesFallbackWrapper>
)}
{flattenedOptions.length > 0 && flattenedFilteredOptions.length > 0 && (
<ScrollableContent>
<PickerList

View File

@@ -20,13 +20,7 @@ import {
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import type { Group, ImperativeModelPickerHandle } from 'common/components/Picker/Picker';
import {
DefaultNoMatchesFallback,
DefaultNoOptionsFallback,
getRegex,
Picker,
usePickerContext,
} from 'common/components/Picker/Picker';
import { getRegex, Picker, usePickerContext } from 'common/components/Picker/Picker';
import { useDisclosure } from 'common/hooks/useBoolean';
import { fixedForwardRef } from 'common/util/fixedForwardRef';
import { typedMemo } from 'common/util/typedMemo';
@@ -183,13 +177,12 @@ export const MainModelPicker = memo(() => {
<FormLabel>{t('modelManager.model')}</FormLabel>
</InformationalPopover>
<PopoverTrigger>
<Button size="sm" flexGrow={1} variant="outline">
<Button size="sm" flexGrow={1} variant="outline" colorScheme={modelConfig ? undefined : 'error'}>
{modelConfig?.name ?? 'Select Model'}
<Spacer />
<PiCaretDownBold />
</Button>
</PopoverTrigger>
<NavigateToModelManagerButton />
<UseDefaultSettingsButton />
</Flex>
<Portal appendToParentPortal={false}>
@@ -206,8 +199,8 @@ export const MainModelPicker = memo(() => {
OptionComponent={PickerOptionComponent}
GroupComponent={PickerGroupComponent}
SearchBarComponent={SearchBarComponent}
noOptionsFallback={<DefaultNoOptionsFallback label={t('modelManager.noModelsInstalled')} />}
noMatchesFallback={<DefaultNoMatchesFallback label={t('modelManager.noMatchingModels')} />}
noOptionsFallback={t('modelManager.noModelsInstalled')}
noMatchesFallback={t('modelManager.noMatchingModels')}
ctx={ctx}
/>
</PopoverBody>