feat(sidebar): restructure layout and fix agent selection highlighting

- Move New Agent button to top header, Settings button to bottom footer
- Settings screen now uses MenuHeader for consistent hamburger menu
- Remove default background from agent list items (only show on hover/selected)
- Fix selected agent highlighting by parsing serverId:agentId from pathname
  instead of using useLocalSearchParams (which doesn't update on same-pattern route navigation)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2026-01-04 10:06:23 +07:00
co-authored by Claude Opus 4.5
parent 7b55740a9e
commit dbf9b7e75d
4 changed files with 66 additions and 29 deletions
+11 -7
View File
@@ -1,4 +1,4 @@
import { Stack, useLocalSearchParams, usePathname } from "expo-router"; import { Stack, usePathname } from "expo-router";
import { SafeAreaProvider } from "react-native-safe-area-context"; import { SafeAreaProvider } from "react-native-safe-area-context";
import { KeyboardProvider } from "react-native-keyboard-controller"; import { KeyboardProvider } from "react-native-keyboard-controller";
import { GestureHandlerRootView } from "react-native-gesture-handler"; import { GestureHandlerRootView } from "react-native-gesture-handler";
@@ -72,17 +72,21 @@ function ProvidersWrapper({ children }: { children: ReactNode }) {
function AppWithSidebar({ children }: { children: ReactNode }) { function AppWithSidebar({ children }: { children: ReactNode }) {
const pathname = usePathname(); const pathname = usePathname();
const params = useLocalSearchParams<{ agentId?: string }>();
const selectedAgentId = useMemo(() => { // Parse selectedAgentKey directly from pathname
if (pathname.startsWith("/agent/") && params.agentId) { // useLocalSearchParams doesn't update when navigating between same-pattern routes
return params.agentId; const selectedAgentKey = useMemo(() => {
// Match /agent/[serverId]/[agentId] pattern
const match = pathname.match(/^\/agent\/([^/]+)\/([^/]+)$/);
if (match) {
const [, serverId, agentId] = match;
return `${serverId}:${agentId}`;
} }
return undefined; return undefined;
}, [pathname, params.agentId]); }, [pathname]);
return ( return (
<AppContainer selectedAgentId={selectedAgentId}>{children}</AppContainer> <AppContainer selectedAgentId={selectedAgentKey}>{children}</AppContainer>
); );
} }
+2 -2
View File
@@ -18,7 +18,7 @@ import { useDaemonRegistry, type DaemonProfile } from "@/contexts/daemon-registr
import { useDaemonConnections, type ConnectionStatus } from "@/contexts/daemon-connections-context"; import { useDaemonConnections, type ConnectionStatus } from "@/contexts/daemon-connections-context";
import { formatConnectionStatus, getConnectionStatusTone } from "@/utils/daemons"; import { formatConnectionStatus, getConnectionStatusTone } from "@/utils/daemons";
import { theme as defaultTheme } from "@/styles/theme"; import { theme as defaultTheme } from "@/styles/theme";
import { BackHeader } from "@/components/headers/back-header"; import { MenuHeader } from "@/components/headers/menu-header";
import { useSessionStore } from "@/stores/session-store"; import { useSessionStore } from "@/stores/session-store";
import type { UseWebSocketReturn } from "@/hooks/use-websocket"; import type { UseWebSocketReturn } from "@/hooks/use-websocket";
@@ -575,7 +575,7 @@ export default function SettingsScreen() {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<BackHeader title="Settings" /> <MenuHeader title="Settings" />
<ScrollView style={styles.scrollView}> <ScrollView style={styles.scrollView}>
<View style={styles.content}> <View style={styles.content}>
+5 -2
View File
@@ -57,9 +57,12 @@ export function AgentList({ agents, isRefreshing = false, onRefresh, selectedAge
to: "agent", to: "agent",
params: { serverId, agentId }, params: { serverId, agentId },
}); });
const shouldReplace = pathname.startsWith("/agent/"); const shouldReplace = pathname.startsWith("/agent/");
const navigate = shouldReplace ? router.replace : router.push; const navigate = shouldReplace ? router.replace : router.push;
onAgentSelect?.(); onAgentSelect?.();
navigate({ navigate({
pathname: "/agent/[serverId]/[agentId]", pathname: "/agent/[serverId]/[agentId]",
params: { params: {
@@ -91,7 +94,8 @@ export function AgentList({ agents, isRefreshing = false, onRefresh, selectedAge
({ item: agent }) => { ({ item: agent }) => {
const timeAgo = formatTimeAgo(agent.lastActivityAt); const timeAgo = formatTimeAgo(agent.lastActivityAt);
const isRunning = agent.status === "running"; const isRunning = agent.status === "running";
const isSelected = selectedAgentId === agent.id; const agentKey = `${agent.serverId}:${agent.id}`;
const isSelected = selectedAgentId === agentKey;
const statusColor = isRunning ? "#3b82f6" : agent.requiresAttention ? "#22c55e" : null; const statusColor = isRunning ? "#3b82f6" : agent.requiresAttention ? "#22c55e" : null;
return ( return (
@@ -225,7 +229,6 @@ const styles = StyleSheet.create((theme) => ({
paddingHorizontal: theme.spacing[3], paddingHorizontal: theme.spacing[3],
borderRadius: theme.borderRadius.lg, borderRadius: theme.borderRadius.lg,
marginBottom: theme.spacing[2], marginBottom: theme.spacing[2],
backgroundColor: theme.colors.palette.zinc[950],
}, },
agentItemSelected: { agentItemSelected: {
backgroundColor: theme.colors.palette.zinc[800], backgroundColor: theme.colors.palette.zinc[800],
+48 -18
View File
@@ -234,17 +234,12 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
<Animated.View <Animated.View
style={[ style={[
styles.mobileSidebar, styles.mobileSidebar,
{ width: windowWidth, paddingTop: insets.top }, { width: windowWidth, paddingTop: insets.top, paddingBottom: insets.bottom },
sidebarAnimatedStyle, sidebarAnimatedStyle,
]} ]}
> >
{/* Header: New Agent button */}
<View style={styles.sidebarHeader}> <View style={styles.sidebarHeader}>
<Pressable
style={styles.headerIconButton}
onPress={handleSettingsMobile}
>
<Settings size={20} color={theme.colors.foreground} />
</Pressable>
<Pressable <Pressable
style={[ style={[
styles.newAgentButton, styles.newAgentButton,
@@ -263,6 +258,8 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
</Text> </Text>
</Pressable> </Pressable>
</View> </View>
{/* Middle: scrollable agent list */}
<AgentList <AgentList
agents={agents} agents={agents}
isRefreshing={isRevalidating} isRefreshing={isRevalidating}
@@ -270,6 +267,17 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
selectedAgentId={selectedAgentId} selectedAgentId={selectedAgentId}
onAgentSelect={handleAgentSelectMobile} onAgentSelect={handleAgentSelectMobile}
/> />
{/* Footer: Settings button */}
<View style={styles.sidebarFooter}>
<Pressable
style={styles.settingsButton}
onPress={handleSettingsMobile}
>
<Settings size={20} color={theme.colors.mutedForeground} />
<Text style={styles.settingsButtonText}>Settings</Text>
</Pressable>
</View>
</Animated.View> </Animated.View>
</GestureDetector> </GestureDetector>
</View> </View>
@@ -283,10 +291,8 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
return ( return (
<View style={[styles.desktopSidebar, { width: DESKTOP_SIDEBAR_WIDTH }]}> <View style={[styles.desktopSidebar, { width: DESKTOP_SIDEBAR_WIDTH }]}>
{/* Header: New Agent button */}
<View style={styles.sidebarHeader}> <View style={styles.sidebarHeader}>
<Pressable style={styles.headerIconButton} onPress={handleSettingsDesktop}>
<Settings size={20} color={theme.colors.foreground} />
</Pressable>
<Pressable <Pressable
style={[ style={[
styles.newAgentButton, styles.newAgentButton,
@@ -305,12 +311,25 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
</Text> </Text>
</Pressable> </Pressable>
</View> </View>
{/* Middle: scrollable agent list */}
<AgentList <AgentList
agents={agents} agents={agents}
isRefreshing={isRevalidating} isRefreshing={isRevalidating}
onRefresh={refreshAll} onRefresh={refreshAll}
selectedAgentId={selectedAgentId} selectedAgentId={selectedAgentId}
/> />
{/* Footer: Settings button */}
<View style={styles.sidebarFooter}>
<Pressable
style={styles.settingsButton}
onPress={handleSettingsDesktop}
>
<Settings size={20} color={theme.colors.mutedForeground} />
<Text style={styles.settingsButtonText}>Settings</Text>
</Pressable>
</View>
</View> </View>
); );
} }
@@ -336,19 +355,11 @@ const styles = StyleSheet.create((theme) => ({
backgroundColor: theme.colors.background, backgroundColor: theme.colors.background,
}, },
sidebarHeader: { sidebarHeader: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: theme.spacing[4], paddingHorizontal: theme.spacing[4],
paddingTop: theme.spacing[4], paddingTop: theme.spacing[4],
paddingBottom: theme.spacing[2], paddingBottom: theme.spacing[2],
gap: theme.spacing[2],
},
headerIconButton: {
padding: theme.spacing[2],
borderRadius: theme.borderRadius.lg,
}, },
newAgentButton: { newAgentButton: {
flex: 1,
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
@@ -360,4 +371,23 @@ const styles = StyleSheet.create((theme) => ({
fontSize: theme.fontSize.base, fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal, fontWeight: theme.fontWeight.normal,
}, },
sidebarFooter: {
paddingHorizontal: theme.spacing[4],
paddingTop: theme.spacing[2],
paddingBottom: theme.spacing[4],
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
settingsButton: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[3],
paddingVertical: theme.spacing[2],
paddingHorizontal: theme.spacing[2],
},
settingsButtonText: {
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
color: theme.colors.mutedForeground,
},
})); }));