feat(sidebar): add "View More" button and all agents screen

- Make AgentList a pure display component (remove sorting/slicing logic)
- Add listFooterComponent prop to AgentList for extensibility
- Add SIDEBAR_AGENT_LIMIT (15) in sidebar, show "View More" when exceeded
- Create /agents screen showing full sorted agent list
- Update settings page styling

🤖 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-06 00:14:27 +07:00
co-authored by Claude Opus 4.5
parent 0806fd7965
commit ecd9ddb4a2
5 changed files with 399 additions and 344 deletions
@@ -1,4 +1,4 @@
import { useCallback } from "react";
import { useCallback, useMemo } from "react";
import { View, Pressable, Text, Platform } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Animated, {
@@ -17,6 +17,7 @@ import { useAggregatedAgents } from "@/hooks/use-aggregated-agents";
import { useSidebarAnimation } from "@/contexts/sidebar-animation-context";
const DESKTOP_SIDEBAR_WIDTH = 320;
const SIDEBAR_AGENT_LIMIT = 15;
interface SlidingSidebarProps {
selectedAgentId?: string;
@@ -39,6 +40,20 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
const isMobile =
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
const sortedAgents = useMemo(() => {
return [...agents].sort((a, b) => {
if (a.requiresAttention && !b.requiresAttention) return -1;
if (!a.requiresAttention && b.requiresAttention) return 1;
return 0;
});
}, [agents]);
const limitedAgents = useMemo(
() => sortedAgents.slice(0, SIDEBAR_AGENT_LIMIT),
[sortedAgents]
);
const hasMore = agents.length > SIDEBAR_AGENT_LIMIT;
const handleClose = useCallback(() => {
close();
}, [close]);
@@ -74,6 +89,15 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
close();
}, [close, translateX, backdropOpacity, windowWidth]);
const handleViewMore = useCallback(() => {
if (isMobile) {
translateX.value = -windowWidth;
backdropOpacity.value = 0;
}
close();
router.push("/agents");
}, [backdropOpacity, close, isMobile, translateX, windowWidth]);
// Close gesture (swipe left to close when sidebar is open)
const closeGesture = Gesture.Pan()
.enabled(isOpen)
@@ -121,6 +145,21 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
pointerEvents: backdropOpacity.value > 0.01 ? "auto" : "none",
}));
const viewMoreButton = hasMore ? (
<View style={styles.viewMoreContainer}>
<Pressable
style={({ hovered }) => [
styles.newAgentButton,
styles.viewMoreButton,
hovered && styles.newAgentButtonHovered,
]}
onPress={handleViewMore}
>
<Text style={styles.viewMoreButtonText}>View More</Text>
</Pressable>
</View>
) : null;
// Render mobile sidebar
// On web, use "auto" instead of "box-none" because web's pointer-events: none blocks scroll
const overlayPointerEvents = Platform.OS === "web" ? "auto" : "box-none";
@@ -158,11 +197,12 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
{/* Middle: scrollable agent list */}
<AgentList
agents={agents}
agents={limitedAgents}
isRefreshing={isRevalidating}
onRefresh={refreshAll}
selectedAgentId={selectedAgentId}
onAgentSelect={handleAgentSelectMobile}
listFooterComponent={viewMoreButton}
/>
{/* Footer */}
@@ -205,10 +245,11 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
{/* Middle: scrollable agent list */}
<AgentList
agents={agents}
agents={limitedAgents}
isRefreshing={isRevalidating}
onRefresh={refreshAll}
selectedAgentId={selectedAgentId}
listFooterComponent={viewMoreButton}
/>
{/* Footer: Settings button */}
@@ -277,6 +318,17 @@ const styles = StyleSheet.create((theme) => ({
fontWeight: theme.fontWeight.normal,
color: theme.colors.foreground,
},
viewMoreContainer: {
paddingTop: theme.spacing[2],
},
viewMoreButton: {
paddingVertical: theme.spacing[2],
},
viewMoreButtonText: {
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
color: theme.colors.foreground,
},
sidebarFooter: {
paddingHorizontal: theme.spacing[4],
paddingTop: theme.spacing[2],