fix(settings): align host switcher with sidebar rows

The host picker reused the composer trigger's content-width pill, so its
label sat 6px left of the section rows and the chevron hugged the label
instead of the trailing edge. Add a `block` mode to ComboboxTrigger that
fills the width and uses the sidebar-row gap, and size the status dot to
the icon footprint so the label and chevron line up with the rows below.
This commit is contained in:
Mohamed Boudra
2026-06-29 12:16:20 +02:00
parent 6a23bbd121
commit 0d1c8db87d
2 changed files with 29 additions and 5 deletions
@@ -1,4 +1,4 @@
import { forwardRef, useCallback, type ReactElement, type ReactNode } from "react"; import { forwardRef, useCallback, useMemo, type ReactElement, type ReactNode } from "react";
import { import {
Pressable, Pressable,
View, View,
@@ -28,10 +28,15 @@ interface ComboboxTriggerProps extends Omit<PressableProps, "style" | "children"
style?: TriggerStyleProp; style?: TriggerStyleProp;
children?: ReactNode; children?: ReactNode;
chevron?: ReactNode | null; chevron?: ReactNode | null;
// Fill the Pressable's width and use the standard sidebar-row gap, so the
// trigger reads as a full-width row: the label expands and the chevron pins to
// the trailing edge. Default (false) keeps the content-width pill used by the
// composer triggers.
block?: boolean;
} }
export const ComboboxTrigger = forwardRef<View, ComboboxTriggerProps>(function ComboboxTrigger( export const ComboboxTrigger = forwardRef<View, ComboboxTriggerProps>(function ComboboxTrigger(
{ children, chevron, style, ...props }, { children, chevron, style, block = false, ...props },
ref, ref,
): ReactElement { ): ReactElement {
const pressableStyle = useCallback( const pressableStyle = useCallback(
@@ -44,9 +49,11 @@ export const ComboboxTrigger = forwardRef<View, ComboboxTriggerProps>(function C
[style], [style],
); );
const rowStyle = useMemo(() => [styles.row, block && styles.rowBlock], [block]);
return ( return (
<Pressable ref={ref} collapsable={false} style={pressableStyle} {...props}> <Pressable ref={ref} collapsable={false} style={pressableStyle} {...props}>
<View style={styles.row}> <View style={rowStyle}>
{children} {children}
{chevron !== null && {chevron !== null &&
(chevron ?? ( (chevron ?? (
@@ -68,6 +75,10 @@ const styles = StyleSheet.create((theme) => ({
alignItems: "center", alignItems: "center",
gap: theme.spacing[1], gap: theme.spacing[1],
}, },
rowBlock: {
flexGrow: 1,
gap: theme.spacing[2],
},
chevronContainer: { chevronContainer: {
flexShrink: 0, flexShrink: 0,
transform: [{ translateY: 1 }], transform: [{ translateY: 1 }],
+15 -2
View File
@@ -39,7 +39,8 @@ import { AppDiagnosticSheet } from "@/components/app-diagnostic-sheet";
import { ComboboxTrigger } from "@/components/ui/combobox-trigger"; import { ComboboxTrigger } from "@/components/ui/combobox-trigger";
import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row"; import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row";
import { SidebarSeparator } from "@/components/sidebar/sidebar-separator"; import { SidebarSeparator } from "@/components/sidebar/sidebar-separator";
import { HostPicker as SharedHostPicker, HostStatusDotSlot } from "@/components/hosts/host-picker"; import { HostPicker as SharedHostPicker } from "@/components/hosts/host-picker";
import { HostStatusDot } from "@/components/host-status-dot";
import { ScreenTitle } from "@/components/headers/screen-title"; import { ScreenTitle } from "@/components/headers/screen-title";
import { HeaderIconBadge } from "@/components/headers/header-icon-badge"; import { HeaderIconBadge } from "@/components/headers/header-icon-badge";
import { SettingsSection } from "@/screens/settings/settings-section"; import { SettingsSection } from "@/screens/settings/settings-section";
@@ -938,13 +939,18 @@ function HostPicker({ activeServerId, sortedHosts, onSelectHost, onAddHost }: Ho
> >
<ComboboxTrigger <ComboboxTrigger
ref={triggerRef} ref={triggerRef}
block
style={triggerStyle} style={triggerStyle}
onPress={handleOpen} onPress={handleOpen}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t("settings.hostPicker.switchHost")} accessibilityLabel={t("settings.hostPicker.switchHost")}
testID="settings-host-picker" testID="settings-host-picker"
> >
{activeHost ? <HostStatusDotSlot serverId={activeHost.serverId} /> : null} {activeHost ? (
<View style={sidebarStyles.pickerTriggerDot}>
<HostStatusDot serverId={activeHost.serverId} />
</View>
) : null}
<Text style={sidebarStyles.pickerTriggerLabel} numberOfLines={1}> <Text style={sidebarStyles.pickerTriggerLabel} numberOfLines={1}>
{activeHost?.label ?? t("settings.groups.host")} {activeHost?.label ?? t("settings.groups.host")}
</Text> </Text>
@@ -1708,4 +1714,11 @@ const sidebarStyles = StyleSheet.create((theme) => ({
color: theme.colors.foreground, color: theme.colors.foreground,
fontWeight: theme.fontWeight.normal, fontWeight: theme.fontWeight.normal,
}, },
// Match the setting items' icon footprint so the host label aligns with them.
pickerTriggerDot: {
width: theme.iconSize.md,
height: theme.iconSize.md,
alignItems: "center",
justifyContent: "center",
},
})); }));