diff --git a/package-lock.json b/package-lock.json
index 289bcdd86..dbe5cc272 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4335,6 +4335,45 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/@gorhom/bottom-sheet": {
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-5.2.6.tgz",
+ "integrity": "sha512-vmruJxdiUGDg+ZYcDmS30XDhq/h/+QkINOI5LY/uGjx8cPGwgJW0H6AB902gNTKtccbiKe/rr94EwdmIEz+LAQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@gorhom/portal": "1.0.14",
+ "invariant": "^2.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-native": "*",
+ "react": "*",
+ "react-native": "*",
+ "react-native-gesture-handler": ">=2.16.1",
+ "react-native-reanimated": ">=3.16.0 || >=4.0.0-"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gorhom/portal": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/@gorhom/portal/-/portal-1.0.14.tgz",
+ "integrity": "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==",
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -20485,6 +20524,7 @@
"version": "1.0.0",
"dependencies": {
"@expo/vector-icons": "^15.0.2",
+ "@gorhom/bottom-sheet": "^5.2.6",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
diff --git a/packages/app/package.json b/packages/app/package.json
index db70703a3..a75826a97 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -12,6 +12,7 @@
},
"dependencies": {
"@expo/vector-icons": "^15.0.2",
+ "@gorhom/bottom-sheet": "^5.2.6",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx
index 7595ebdd1..2e7b449c1 100644
--- a/packages/app/src/app/_layout.tsx
+++ b/packages/app/src/app/_layout.tsx
@@ -2,6 +2,7 @@ import { Stack } from "expo-router";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { KeyboardProvider } from "react-native-keyboard-controller";
import { GestureHandlerRootView } from "react-native-gesture-handler";
+import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
import { SessionProvider } from "@/contexts/session-context";
import { RealtimeProvider } from "@/contexts/realtime-context";
import { useSettings } from "@/hooks/use-settings";
@@ -37,25 +38,27 @@ export default function RootLayout() {
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/app/src/components/create-agent-modal.tsx b/packages/app/src/components/create-agent-modal.tsx
index e58706356..083990d2c 100644
--- a/packages/app/src/components/create-agent-modal.tsx
+++ b/packages/app/src/components/create-agent-modal.tsx
@@ -1,16 +1,20 @@
-import { useState } from "react";
-import {
- Modal,
- View,
- Text,
- Pressable,
- TextInput,
- ScrollView,
-} from "react-native";
-import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
-import ReanimatedAnimated, { useAnimatedStyle } from "react-native-reanimated";
+import { useState, useRef, useEffect, useMemo, useCallback } from "react";
+import { View, Text, Pressable, ScrollView } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
-import { X } from "lucide-react-native";
+import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
+import Animated, { useAnimatedStyle } from "react-native-reanimated";
+import {
+ BottomSheetModal,
+ BottomSheetBackdrop,
+ BottomSheetView,
+ BottomSheetScrollView,
+ BottomSheetTextInput,
+ BottomSheetFooter,
+} from "@gorhom/bottom-sheet";
+import type {
+ BottomSheetBackdropProps,
+ BottomSheetFooterProps,
+} from "@gorhom/bottom-sheet";
import { StyleSheet } from "react-native-unistyles";
import { theme as defaultTheme } from "@/styles/theme";
import { useRecentPaths } from "@/hooks/use-recent-paths";
@@ -39,25 +43,68 @@ export function CreateAgentModal({
onClose,
onCreateAgent,
}: CreateAgentModalProps) {
+ const bottomSheetRef = useRef(null);
const insets = useSafeAreaInsets();
- const { height: keyboardHeight } = useReanimatedKeyboardAnimation();
const { recentPaths, addRecentPath } = useRecentPaths();
+ const { height: keyboardHeight } = useReanimatedKeyboardAnimation();
const [workingDir, setWorkingDir] = useState("");
const [selectedMode, setSelectedMode] = useState("plan");
const [errorMessage, setErrorMessage] = useState("");
- // Keyboard animation
- const bottomInset = insets.bottom;
- const animatedKeyboardStyle = useAnimatedStyle(() => {
+ const snapPoints = useMemo(() => ["90%"], []);
+
+ // Keyboard animation for footer
+ const animatedFooterStyle = useAnimatedStyle(() => {
"worklet";
const absoluteHeight = Math.abs(keyboardHeight.value);
- const padding = Math.max(0, absoluteHeight - bottomInset);
+ const padding = Math.max(0, absoluteHeight - insets.bottom);
return {
paddingBottom: padding,
};
});
+ const renderBackdrop = useMemo(
+ () => (props: BottomSheetBackdropProps) =>
+ (
+
+ ),
+ []
+ );
+
+ const renderFooter = useCallback(
+ (props: BottomSheetFooterProps) => (
+
+
+
+ Create Agent
+
+
+
+ ),
+ [insets.bottom, workingDir, animatedFooterStyle]
+ );
+
+ useEffect(() => {
+ if (isVisible) {
+ bottomSheetRef.current?.present();
+ }
+ }, [isVisible]);
+
async function handleCreate() {
if (!workingDir.trim()) {
setErrorMessage("Working directory is required");
@@ -79,6 +126,10 @@ export function CreateAgentModal({
}
function handleClose() {
+ bottomSheetRef.current?.dismiss();
+ }
+
+ function handleDismiss() {
setWorkingDir("");
setSelectedMode("plan");
setErrorMessage("");
@@ -86,174 +137,131 @@ export function CreateAgentModal({
}
return (
-
-
-
-
+ {/* Header */}
+
+ Create New Agent
+
+
+ {/* Form */}
+
-
- {/* Header */}
-
- Create New Agent
-
-
-
-
+ {/* Working Directory Input */}
+
+ Working Directory
+ {
+ setWorkingDir(text);
+ setErrorMessage("");
+ }}
+ autoCapitalize="none"
+ autoCorrect={false}
+ />
+ {errorMessage && (
+ {errorMessage}
+ )}
- {/* Form */}
-
- {/* Working Directory Input */}
-
- Working Directory
- {
- setWorkingDir(text);
- setErrorMessage("");
- }}
- autoCapitalize="none"
- autoCorrect={false}
- />
- {errorMessage ? (
- {errorMessage}
- ) : (
-
- Absolute path to the project directory
-
- )}
-
- {/* Recent Paths Chips */}
- {recentPaths.length > 0 && (
- 0 && (
+
+ {recentPaths.map((path) => (
+ setWorkingDir(path)}
>
- {recentPaths.map((path) => (
- setWorkingDir(path)}
- >
-
- {path}
-
-
- ))}
-
- )}
-
+
+ {path}
+
+
+ ))}
+
+ )}
+
- {/* Mode Selector */}
-
- Mode
-
- {MODES.map((mode) => (
- setSelectedMode(mode.value)}
+ {/* Mode Selector */}
+
+ Mode
+
+ {MODES.map((mode) => (
+ setSelectedMode(mode.value)}
+ style={[
+ styles.modeOption,
+ selectedMode === mode.value && styles.modeOptionSelected,
+ ]}
+ >
+
+
-
-
- {selectedMode === mode.value && (
-
- )}
-
-
- {mode.label}
-
- {mode.description}
-
-
-
-
- ))}
-
-
-
-
- {/* Fixed Footer with Create Button */}
-
-
- Create Agent
-
+ {selectedMode === mode.value && (
+
+ )}
+
+
+ {mode.label}
+
+ {mode.description}
+
+
+
+
+ ))}
-
-
-
+
+
+
);
}
const styles = StyleSheet.create((theme) => ({
- modalOverlay: {
- flex: 1,
- justifyContent: "flex-end",
- },
- modalBackdrop: {
- position: "absolute",
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- backgroundColor: "rgba(0,0,0,0.5)",
- zIndex: 1,
- },
- modalContent: {
+ sheetBackground: {
backgroundColor: theme.colors.card,
- borderTopLeftRadius: theme.spacing[6],
- borderTopRightRadius: theme.spacing[6],
- minHeight: "80%",
- zIndex: 2,
- elevation: 5,
},
- modalInner: {
+ handleIndicator: {
+ backgroundColor: theme.colors.border,
+ },
+ sheetContent: {
flex: 1,
- minHeight: 0,
},
header: {
- flexDirection: "row",
alignItems: "center",
- justifyContent: "space-between",
+ justifyContent: "center",
padding: theme.spacing[6],
borderBottomWidth: theme.borderWidth[1],
borderBottomColor: theme.colors.border,
@@ -268,7 +276,7 @@ const styles = StyleSheet.create((theme) => ({
},
scrollContent: {
padding: theme.spacing[6],
- paddingBottom: theme.spacing[4],
+ paddingBottom: theme.spacing[6],
},
formSection: {
marginBottom: theme.spacing[6],