Update files

This commit is contained in:
Mohamed Boudra
2026-01-26 14:30:41 +07:00
parent fe5ab731e0
commit 6095bf975b
2 changed files with 115 additions and 61 deletions

View File

@@ -18,7 +18,7 @@ import {
import { router, usePathname } from "expo-router";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { useQueries, useQueryClient, type UseQueryOptions } from "@tanstack/react-query";
import { ChevronDown, ChevronRight } from "lucide-react-native";
import { ChevronDown, ChevronRight, Plus } from "lucide-react-native";
import { formatTimeAgo } from "@/utils/time";
import {
groupAgents,
@@ -51,6 +51,8 @@ interface SectionData {
firstAgentServerId?: string;
/** For project sections, the first agent's id (to lookup checkout status) */
firstAgentId?: string;
/** Working directory for the project (from first agent) */
workingDir?: string;
}
interface GroupedAgentListProps {
@@ -66,14 +68,17 @@ interface SectionHeaderProps {
section: SectionData;
isCollapsed: boolean;
onToggle: () => void;
onCreateAgent: (workingDir: string) => void;
}
function SectionHeader({
section,
isCollapsed,
onToggle,
onCreateAgent,
}: SectionHeaderProps) {
const { theme } = useUnistyles();
const [isHovered, setIsHovered] = useState(false);
// For project sections, try to get repo name from checkout status
const checkoutQuery = useCheckoutStatusCacheOnly({
@@ -98,13 +103,26 @@ function SectionHeader({
}
}
const handleCreatePress = useCallback(
(e: { stopPropagation: () => void }) => {
e.stopPropagation();
if (section.workingDir) {
onCreateAgent(section.workingDir);
}
},
[onCreateAgent, section.workingDir]
);
return (
<Pressable
style={({ hovered }) => [
style={[
styles.sectionHeader,
hovered && styles.sectionHeaderHovered,
isHovered && styles.sectionHeaderHovered,
!isCollapsed && styles.sectionHeaderExpanded,
]}
onPress={onToggle}
onHoverIn={() => setIsHovered(true)}
onHoverOut={() => setIsHovered(false)}
>
<View style={styles.sectionHeaderLeft}>
<Text style={styles.sectionTitle} numberOfLines={1}>
@@ -112,6 +130,24 @@ function SectionHeader({
</Text>
</View>
<View style={styles.sectionHeaderRight}>
{section.workingDir && (
<Pressable
style={[
styles.createAgentButton,
{ opacity: isHovered ? 1 : 0 },
]}
onPress={handleCreatePress}
onHoverIn={() => setIsHovered(true)}
onHoverOut={() => setIsHovered(true)}
>
{({ hovered }) => (
<Plus
size={14}
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
/>
)}
</Pressable>
)}
{isCollapsed ? (
<ChevronRight size={14} color={theme.colors.foregroundMuted} />
) : (
@@ -206,6 +242,13 @@ export function GroupedAgentList({
});
}, []);
const handleCreateAgentInProject = useCallback(
(workingDir: string) => {
router.push(`/agent?workingDir=${encodeURIComponent(workingDir)}` as any);
},
[]
);
// Subscribe to checkout status cache entries so project grouping can react
// to remote URL updates (e.g. git worktrees in different directories).
const checkoutCacheQueries = useQueries({
@@ -260,6 +303,7 @@ export function GroupedAgentList({
data: isCollapsed ? [] : group.agents,
firstAgentServerId: firstAgent?.serverId,
firstAgentId: firstAgent?.id,
workingDir: firstAgent?.cwd,
});
}
@@ -386,10 +430,11 @@ export function GroupedAgentList({
section={section}
isCollapsed={isCollapsed}
onToggle={() => toggleSection(section.key)}
onCreateAgent={handleCreateAgentInProject}
/>
);
},
[collapsedSections, toggleSection]
[collapsedSections, toggleSection, handleCreateAgentInProject]
);
const keyExtractor = useCallback(
@@ -495,14 +540,17 @@ const styles = StyleSheet.create((theme) => ({
alignItems: "center",
justifyContent: "space-between",
paddingVertical: theme.spacing[2],
paddingHorizontal: theme.spacing[4] + theme.spacing[2],
marginHorizontal: -theme.spacing[4],
paddingHorizontal: theme.spacing[3],
marginHorizontal: -theme.spacing[2],
marginTop: theme.spacing[2],
borderRadius: theme.borderRadius.md,
},
sectionHeaderHovered: {
backgroundColor: theme.colors.surface1,
},
sectionHeaderExpanded: {
marginBottom: theme.spacing[1],
},
sectionHeaderLeft: {
flexDirection: "row",
alignItems: "center",
@@ -511,13 +559,21 @@ const styles = StyleSheet.create((theme) => ({
minWidth: 0,
},
sectionHeaderRight: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: theme.spacing[2],
marginLeft: theme.spacing[2],
},
createAgentButton: {
width: 14,
height: 14,
alignItems: "center",
justifyContent: "center",
},
sectionTitle: {
fontSize: theme.fontSize.sm,
fontWeight: "500",
fontWeight: "normal",
color: theme.colors.foregroundMuted,
flex: 1,
textAlign: "left",
@@ -525,6 +581,7 @@ const styles = StyleSheet.create((theme) => ({
agentItem: {
paddingVertical: theme.spacing[2],
paddingHorizontal: theme.spacing[3],
marginHorizontal: -theme.spacing[2],
borderRadius: theme.borderRadius.lg,
marginBottom: theme.spacing[1],
},

View File

@@ -9,7 +9,7 @@ import Animated, {
} from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
import { ChevronRight, Plus, Settings } from "lucide-react-native";
import { Plus, Settings, Users } from "lucide-react-native";
import { router } from "expo-router";
import { usePanelStore } from "@/stores/panel-store";
import { GroupedAgentList } from "./grouped-agent-list";
@@ -170,20 +170,6 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
pointerEvents: backdropOpacity.value > 0.01 ? "auto" : "none",
}));
const viewMoreButton = (
<View style={styles.viewMoreContainer}>
<Pressable
style={({ hovered, pressed }) => [
styles.viewMoreButton,
(hovered || pressed) && styles.viewMoreButtonHovered,
]}
onPress={handleViewMore}
>
<Text style={styles.viewMoreButtonText}>All agents</Text>
<ChevronRight size={16} color={theme.colors.foregroundMuted} />
</Pressable>
</View>
);
// Render mobile sidebar
// On web, use "auto" instead of "box-none" because web's pointer-events: none blocks scroll
@@ -228,17 +214,30 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
onRefresh={handleRefresh}
selectedAgentId={selectedAgentId}
onAgentSelect={handleAgentSelectMobile}
listFooterComponent={viewMoreButton}
/>
{/* Footer */}
<View style={styles.sidebarFooter}>
<Pressable
style={styles.settingsButton}
style={styles.footerButton}
onPress={handleViewMore}
>
{({ hovered }) => (
<>
<Users size={18} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
<Text style={[styles.footerButtonText, hovered && styles.footerButtonTextHovered]}>
All agents
</Text>
</>
)}
</Pressable>
<Pressable
style={styles.footerIconButton}
onPress={handleSettingsMobile}
>
<Settings size={20} color={theme.colors.foregroundMuted} />
<Text style={styles.settingsButtonText}>Settings</Text>
{({ hovered }) => (
<Settings size={20} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
)}
</Pressable>
</View>
</View>
@@ -279,17 +278,30 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
isRefreshing={isManualRefresh && isRevalidating}
onRefresh={handleRefresh}
selectedAgentId={selectedAgentId}
listFooterComponent={viewMoreButton}
/>
{/* Footer: Settings button */}
{/* Footer */}
<View style={styles.sidebarFooter}>
<Pressable
style={styles.settingsButton}
style={styles.footerButton}
onPress={handleViewMore}
>
{({ hovered }) => (
<>
<Users size={18} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
<Text style={[styles.footerButtonText, hovered && styles.footerButtonTextHovered]}>
All agents
</Text>
</>
)}
</Pressable>
<Pressable
style={styles.footerIconButton}
onPress={handleSettingsDesktop}
>
<Settings size={20} color={theme.colors.foregroundMuted} />
<Text style={styles.settingsButtonText}>Settings</Text>
{({ hovered }) => (
<Settings size={20} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
)}
</Pressable>
</View>
</View>
@@ -349,47 +361,32 @@ const styles = StyleSheet.create((theme) => ({
fontWeight: theme.fontWeight.normal,
color: theme.colors.foreground,
},
viewMoreContainer: {
paddingTop: theme.spacing[2],
},
viewMoreButton: {
sidebarFooter: {
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-start",
gap: theme.spacing[1],
paddingVertical: theme.spacing[2],
paddingHorizontal: 0,
borderWidth: 0,
backgroundColor: "transparent",
alignSelf: "flex-start",
transitionProperty: "opacity",
transitionDuration: "150ms",
},
viewMoreButtonHovered: {
opacity: 0.8,
},
viewMoreButtonText: {
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
color: theme.colors.foregroundMuted,
},
sidebarFooter: {
justifyContent: "space-between",
paddingHorizontal: theme.spacing[4],
paddingTop: theme.spacing[2],
paddingBottom: theme.spacing[4],
paddingVertical: theme.spacing[3],
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
settingsButton: {
footerButton: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[3],
paddingVertical: theme.spacing[2],
paddingHorizontal: theme.spacing[2],
gap: theme.spacing[2],
paddingVertical: theme.spacing[1],
paddingHorizontal: theme.spacing[1],
},
settingsButtonText: {
footerIconButton: {
paddingVertical: theme.spacing[1],
paddingHorizontal: theme.spacing[1],
},
footerButtonText: {
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
color: theme.colors.foregroundMuted,
},
footerButtonTextHovered: {
color: theme.colors.foreground,
},
}));