Fix compact provider sheet footer

This commit is contained in:
Mohamed Boudra
2026-06-04 08:46:47 +07:00
parent 801140abb2
commit f3b7dac768
2 changed files with 102 additions and 28 deletions
@@ -20,6 +20,7 @@ import {
useIsolatedBottomSheetVisibility, useIsolatedBottomSheetVisibility,
} from "@/components/ui/isolated-bottom-sheet-modal"; } from "@/components/ui/isolated-bottom-sheet-modal";
import { isNative, isWeb } from "@/constants/platform"; import { isNative, isWeb } from "@/constants/platform";
import { useSafeAreaInsets } from "react-native-safe-area-context";
// Horizontal indent token shared by the sheet header (title, back arrow, // Horizontal indent token shared by the sheet header (title, back arrow,
// leading icon, search input icon) and any row primitive rendered inside the // leading icon, search input icon) and any row primitive rendered inside the
@@ -458,7 +459,12 @@ export function AdaptiveModalSheet({
}: AdaptiveModalSheetProps) { }: AdaptiveModalSheetProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const isMobile = useIsCompactFormFactor(); const isMobile = useIsCompactFormFactor();
const insets = useSafeAreaInsets();
const resolvedSnapPoints = useMemo(() => snapPoints ?? ["65%", "90%"], [snapPoints]); const resolvedSnapPoints = useMemo(() => snapPoints ?? ["65%", "90%"], [snapPoints]);
const footerStyle = useMemo(
() => [styles.footer, isMobile ? { paddingBottom: theme.spacing[3] + insets.bottom } : null],
[insets.bottom, isMobile, theme.spacing],
);
const handleIndicatorStyle = useMemo( const handleIndicatorStyle = useMemo(
() => ({ backgroundColor: theme.colors.surface2 }), () => ({ backgroundColor: theme.colors.surface2 }),
[theme.colors.surface2], [theme.colors.surface2],
@@ -515,7 +521,7 @@ export function AdaptiveModalSheet({
) : ( ) : (
<View style={styles.bottomSheetStaticContent}>{children}</View> <View style={styles.bottomSheetStaticContent}>{children}</View>
)} )}
{footer ? <View style={styles.footer}>{footer}</View> : null} {footer ? <View style={footerStyle}>{footer}</View> : null}
</IsolatedBottomSheetModal> </IsolatedBottomSheetModal>
); );
} }
@@ -534,7 +540,7 @@ export function AdaptiveModalSheet({
) : ( ) : (
<View style={styles.desktopStaticContent}>{children}</View> <View style={styles.desktopStaticContent}>{children}</View>
)} )}
{footer ? <View style={styles.footer}>{footer}</View> : null} {footer ? <View style={footerStyle}>{footer}</View> : null}
</> </>
); );
@@ -16,6 +16,7 @@ import {
} from "@/components/adaptive-modal-sheet"; } from "@/components/adaptive-modal-sheet";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { LoadingSpinner } from "@/components/ui/loading-spinner"; import { LoadingSpinner } from "@/components/ui/loading-spinner";
import { useIsCompactFormFactor } from "@/constants/layout";
import { isWeb } from "@/constants/platform"; import { isWeb } from "@/constants/platform";
import { CODE_SURFACE_DATASET } from "@/styles/code-surface"; import { CODE_SURFACE_DATASET } from "@/styles/code-surface";
import { useDaemonConfig } from "@/hooks/use-daemon-config"; import { useDaemonConfig } from "@/hooks/use-daemon-config";
@@ -359,6 +360,69 @@ interface ProviderModalBodyProps {
theme: { iconSize: { md: number }; colors: { foregroundMuted: string } }; theme: { iconSize: { md: number }; colors: { foregroundMuted: string } };
} }
interface ProviderSheetFooterInput {
fetchedAtLabel: string | null;
isCompact: boolean;
modelsRefreshing: boolean;
onOpenAddSheet: () => void;
onOpenDiagSheet: () => void;
onRefreshModels: () => void;
}
function renderProviderSheetFooter({
fetchedAtLabel,
isCompact,
modelsRefreshing,
onOpenAddSheet,
onOpenDiagSheet,
onRefreshModels,
}: ProviderSheetFooterInput) {
const contentStyle = isCompact ? sheetStyles.compactFooterContent : sheetStyles.footerContent;
const actionsStyle = isCompact ? sheetStyles.compactFooterActions : sheetStyles.footerActions;
const buttonStyle = isCompact ? sheetStyles.compactFooterButton : null;
const metaStyle = isCompact ? COMPACT_FOOTER_META_STYLE : sheetStyles.footerMeta;
return (
<View style={contentStyle}>
{fetchedAtLabel || !isCompact ? (
<Text style={metaStyle} numberOfLines={1}>
{fetchedAtLabel ? `Updated ${fetchedAtLabel}` : ""}
</Text>
) : null}
<View style={actionsStyle}>
<Button
variant="secondary"
size="sm"
leftIcon={Plus}
onPress={onOpenAddSheet}
style={buttonStyle}
>
Add model
</Button>
<Button
variant="secondary"
size="sm"
leftIcon={FileText}
onPress={onOpenDiagSheet}
style={buttonStyle}
>
Diagnostic
</Button>
<Button
variant="default"
size="sm"
leftIcon={modelsRefreshing ? undefined : RotateCw}
onPress={onRefreshModels}
disabled={modelsRefreshing}
style={buttonStyle}
>
{modelsRefreshing ? "Refreshing…" : "Refresh"}
</Button>
</View>
</View>
);
}
function ProviderModalBody(props: ProviderModalBodyProps) { function ProviderModalBody(props: ProviderModalBodyProps) {
const { const {
discoveredCount, discoveredCount,
@@ -446,6 +510,7 @@ export function ProviderDiagnosticSheet({
serverId, serverId,
}: ProviderDiagnosticSheetProps) { }: ProviderDiagnosticSheetProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const isCompact = useIsCompactFormFactor();
const { entries: snapshotEntries, refresh, isRefreshing } = useProvidersSnapshot(serverId); const { entries: snapshotEntries, refresh, isRefreshing } = useProvidersSnapshot(serverId);
const { config, patchConfig } = useDaemonConfig(serverId); const { config, patchConfig } = useDaemonConfig(serverId);
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
@@ -545,38 +610,20 @@ export function ProviderDiagnosticSheet({
[providerLabel], [providerLabel],
); );
const footer = (
<>
<Text style={sheetStyles.footerMeta} numberOfLines={1}>
{fetchedAtLabel ? `Updated ${fetchedAtLabel}` : ""}
</Text>
<View style={sheetStyles.footerActions}>
<Button variant="secondary" size="sm" leftIcon={Plus} onPress={handleOpenAddSheet}>
Add model
</Button>
<Button variant="secondary" size="sm" leftIcon={FileText} onPress={handleOpenDiagSheet}>
Diagnostic
</Button>
<Button
variant="default"
size="sm"
leftIcon={modelsRefreshing ? undefined : RotateCw}
onPress={handleRefreshModels}
disabled={modelsRefreshing}
>
{modelsRefreshing ? "Refreshing…" : "Refresh"}
</Button>
</View>
</>
);
return ( return (
<> <>
<AdaptiveModalSheet <AdaptiveModalSheet
header={sheetHeader} header={sheetHeader}
visible={visible} visible={visible}
onClose={onClose} onClose={onClose}
footer={footer} footer={renderProviderSheetFooter({
fetchedAtLabel,
isCompact,
modelsRefreshing,
onOpenAddSheet: handleOpenAddSheet,
onOpenDiagSheet: handleOpenDiagSheet,
onRefreshModels: handleRefreshModels,
})}
snapPoints={MAIN_SNAP_POINTS} snapPoints={MAIN_SNAP_POINTS}
> >
<ProviderModalBody <ProviderModalBody
@@ -692,16 +739,36 @@ const sheetStyles = StyleSheet.create((theme) => ({
alignItems: "center", alignItems: "center",
gap: theme.spacing[3], gap: theme.spacing[3],
}, },
footerContent: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
gap: theme.spacing[2],
},
compactFooterContent: {
flex: 1,
gap: theme.spacing[2],
},
footerMeta: { footerMeta: {
flex: 1, flex: 1,
fontSize: theme.fontSize.xs, fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
}, },
compactFooterMeta: {
flex: 0,
},
footerActions: { footerActions: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
gap: theme.spacing[2], gap: theme.spacing[2],
}, },
compactFooterActions: {
gap: theme.spacing[2],
},
compactFooterButton: {
alignSelf: "stretch",
},
formGroup: { formGroup: {
gap: theme.spacing[3], gap: theme.spacing[3],
}, },
@@ -741,6 +808,7 @@ const sheetStyles = StyleSheet.create((theme) => ({
})); }));
const FORM_INPUT_STYLE = [sheetStyles.formInput, isWeb && { outlineStyle: "none" }]; const FORM_INPUT_STYLE = [sheetStyles.formInput, isWeb && { outlineStyle: "none" }];
const COMPACT_FOOTER_META_STYLE = [sheetStyles.footerMeta, sheetStyles.compactFooterMeta];
const MAIN_SNAP_POINTS = ["65%", "92%"]; const MAIN_SNAP_POINTS = ["65%", "92%"];
const ADD_SNAP_POINTS = ["40%"]; const ADD_SNAP_POINTS = ["40%"];