import { useCallback, useMemo, useState, type ReactElement } from "react"; import { Pressable, ScrollView, Text, View, type PressableStateCallbackType } from "react-native"; import { Archive, ChevronDown, ChevronRight } from "lucide-react-native"; import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { getProviderIcon } from "@/components/provider-icons"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useIsCompactFormFactor, MAX_CONTENT_WIDTH } from "@/constants/layout"; import { isNative } from "@/constants/platform"; import { WorkspaceTabIcon, type WorkspaceTabPresentation, } from "@/screens/workspace/workspace-tab-presentation"; import type { Theme } from "@/styles/theme"; import type { SubagentRow } from "./select"; import { buildSubagentRowPresentationData, formatHeaderLabel } from "./track-presentation"; const ThemedArchive = withUnistyles(Archive); const ThemedChevronDown = withUnistyles(ChevronDown); const ThemedChevronRight = withUnistyles(ChevronRight); const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground }); const foregroundMutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted, }); export interface SubagentsTrackProps { rows: SubagentRow[]; onOpenSubagent: (id: string) => void; onArchiveSubagent: (id: string) => void; } const SUBAGENTS_LIST_MAX_HEIGHT = 200; function buildRowPresentation(row: SubagentRow): WorkspaceTabPresentation { return { ...buildSubagentRowPresentationData(row), icon: getProviderIcon(row.provider), }; } export function SubagentsTrack({ rows, onOpenSubagent, onArchiveSubagent, }: SubagentsTrackProps): ReactElement | null { const [expanded, setExpanded] = useState(false); const toggleExpanded = useCallback(() => { setExpanded((current) => !current); }, []); const surfaceStyle = useMemo( () => [styles.surface, expanded && styles.surfaceExpanded], [expanded], ); const headerStyle = useCallback( ({ hovered, pressed }: PressableStateCallbackType) => [ styles.header, expanded ? styles.headerDivider : styles.headerCollapsed, (hovered || pressed) && styles.headerActive, ], [expanded], ); if (rows.length === 0) { return null; } const headerLabel = formatHeaderLabel(rows); return ( {expanded ? ( ) : ( )} {headerLabel} {expanded ? ( {rows.map((row) => ( ))} ) : null} ); } interface SubagentsTrackRowProps { row: SubagentRow; onOpenSubagent: (id: string) => void; onArchiveSubagent: (id: string) => void; } function SubagentsTrackRow({ row, onOpenSubagent, onArchiveSubagent, }: SubagentsTrackRowProps): ReactElement { const isCompact = useIsCompactFormFactor(); const [hovered, setHovered] = useState(false); const presentation = useMemo(() => buildRowPresentation(row), [row]); const displayLabel = presentation.titleState === "loading" ? "Loading..." : presentation.label; const handlePress = useCallback(() => { onOpenSubagent(row.id); }, [onOpenSubagent, row.id]); const handleArchivePress = useCallback(() => { onArchiveSubagent(row.id); }, [onArchiveSubagent, row.id]); const handlePointerEnter = useCallback(() => setHovered(true), []); const handlePointerLeave = useCallback(() => setHovered(false), []); const archiveAlwaysVisible = isNative || isCompact; const archiveVisible = archiveAlwaysVisible || hovered; return ( // Wrapper View handles hover so moving the pointer between the row and // the archive button doesn't drop the hover state — the same pattern // used by sidebar workspace rows. {({ pressed }) => ( {displayLabel} )} ); } function SubagentArchiveButton({ rowId, displayLabel, visible, onPress, }: { rowId: string; displayLabel: string; visible: boolean; onPress: () => void; }): ReactElement { return ( {({ hovered, pressed }) => ( )} Archive subagent ); } const styles = StyleSheet.create((theme) => ({ outer: { width: "100%", alignItems: "center", paddingHorizontal: theme.spacing[4], }, track: { width: "100%", maxWidth: MAX_CONTENT_WIDTH, marginBottom: -theme.spacing[4], }, surface: { alignSelf: "stretch", backgroundColor: theme.colors.surface1, borderWidth: theme.borderWidth[1], borderColor: theme.colors.borderAccent, borderBottomWidth: 0, borderTopLeftRadius: theme.borderRadius["2xl"], borderTopRightRadius: theme.borderRadius["2xl"], overflow: "hidden", }, surfaceExpanded: { paddingBottom: theme.spacing[4], }, header: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], paddingHorizontal: theme.spacing[3], paddingVertical: theme.spacing[2], }, headerCollapsed: { paddingBottom: theme.spacing[6], }, headerActive: { backgroundColor: theme.colors.surface2, }, headerDivider: { borderBottomWidth: theme.borderWidth[1], borderBottomColor: theme.colors.border, }, headerLabel: { flexShrink: 1, minWidth: 0, fontSize: theme.fontSize.xs, color: theme.colors.foregroundMuted, }, scroll: { maxHeight: SUBAGENTS_LIST_MAX_HEIGHT, }, scrollContent: { paddingVertical: theme.spacing[1], }, row: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], paddingHorizontal: theme.spacing[3], paddingVertical: theme.spacing[2], }, rowActive: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], paddingHorizontal: theme.spacing[3], paddingVertical: theme.spacing[2], backgroundColor: theme.colors.surface2, }, rowLabel: { flex: 1, minWidth: 0, fontSize: theme.fontSize.sm, color: theme.colors.foreground, }, archiveSlotVisible: { opacity: 1, }, archiveSlotHidden: { opacity: 0, }, archiveButton: { padding: theme.spacing[1], alignItems: "center", justifyContent: "center", }, tooltipText: { fontSize: theme.fontSize.xs, color: theme.colors.foreground, }, }));