Update files
This commit is contained in:
@@ -13,7 +13,7 @@ import { router, useLocalSearchParams } from "expo-router";
|
|||||||
import Constants from "expo-constants";
|
import Constants from "expo-constants";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
import { Sun, Moon, Monitor, MoreVertical, Globe } from "lucide-react-native";
|
import { Sun, Moon, Monitor, MoreVertical, Globe, Trash2, Pencil, RotateCw } from "lucide-react-native";
|
||||||
import { useAppSettings, type AppSettings } from "@/hooks/use-settings";
|
import { useAppSettings, type AppSettings } from "@/hooks/use-settings";
|
||||||
import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-context";
|
import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-context";
|
||||||
import { useDaemonConnections, type ActiveConnection, type ConnectionStatus } from "@/contexts/daemon-connections-context";
|
import { useDaemonConnections, type ActiveConnection, type ConnectionStatus } from "@/contexts/daemon-connections-context";
|
||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
import { AdaptiveModalSheet, AdaptiveTextInput } from "@/components/adaptive-modal-sheet";
|
||||||
|
|
||||||
const delay = (ms: number) =>
|
const delay = (ms: number) =>
|
||||||
new Promise<void>((resolve) => {
|
new Promise<void>((resolve) => {
|
||||||
@@ -119,7 +119,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
hostHeaderRight: {
|
hostHeaderRight: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[1],
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
hostLabel: {
|
hostLabel: {
|
||||||
@@ -142,6 +142,13 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingVertical: 4,
|
paddingVertical: 4,
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius.full,
|
||||||
},
|
},
|
||||||
|
statusPillMobile: {
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
borderRadius: 7,
|
||||||
|
},
|
||||||
statusDot: {
|
statusDot: {
|
||||||
width: 6,
|
width: 6,
|
||||||
height: 6,
|
height: 6,
|
||||||
@@ -163,6 +170,11 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
backgroundColor: theme.colors.surface3,
|
backgroundColor: theme.colors.surface3,
|
||||||
maxWidth: 170,
|
maxWidth: 170,
|
||||||
},
|
},
|
||||||
|
connectionPillMobile: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: theme.borderRadius.full,
|
||||||
|
},
|
||||||
connectionText: {
|
connectionText: {
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
fontWeight: theme.fontWeight.normal,
|
fontWeight: theme.fontWeight.normal,
|
||||||
@@ -744,7 +756,7 @@ export default function SettingsScreen() {
|
|||||||
>
|
>
|
||||||
<View style={styles.formField}>
|
<View style={styles.formField}>
|
||||||
<Text style={styles.label}>Label</Text>
|
<Text style={styles.label}>Label</Text>
|
||||||
<TextInput
|
<AdaptiveTextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
value={editLabel}
|
value={editLabel}
|
||||||
onChangeText={setEditLabel}
|
onChangeText={setEditLabel}
|
||||||
@@ -795,26 +807,6 @@ export default function SettingsScreen() {
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{editingDaemon ? (
|
|
||||||
<View style={styles.formField}>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
style={{ alignSelf: "flex-start" }}
|
|
||||||
onPress={() => {
|
|
||||||
const serverId = editingDaemon.serverId;
|
|
||||||
handleCloseEditDaemon();
|
|
||||||
setAddConnectionTargetServerId(serverId);
|
|
||||||
setPendingEditReopenServerId(serverId);
|
|
||||||
setIsAddHostMethodVisible(true);
|
|
||||||
}}
|
|
||||||
testID="edit-host-add-connection"
|
|
||||||
>
|
|
||||||
Add connection
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<View style={styles.formActionsRow}>
|
<View style={styles.formActionsRow}>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -1060,17 +1052,21 @@ function DaemonCard({
|
|||||||
<View style={styles.hostHeaderRow}>
|
<View style={styles.hostHeaderRow}>
|
||||||
<Text style={styles.hostLabel} numberOfLines={1}>{daemon.label}</Text>
|
<Text style={styles.hostLabel} numberOfLines={1}>{daemon.label}</Text>
|
||||||
<View style={styles.hostHeaderRight}>
|
<View style={styles.hostHeaderRight}>
|
||||||
<View style={[styles.statusPill, { backgroundColor: statusPillBg }]}>
|
<View style={[Platform.OS === "web" ? styles.statusPill : styles.statusPillMobile, { backgroundColor: statusPillBg }]}>
|
||||||
<View style={[styles.statusDot, { backgroundColor: statusColor }]} />
|
<View style={[styles.statusDot, { backgroundColor: statusColor }]} />
|
||||||
<Text style={[styles.statusText, { color: statusColor }]}>{badgeText}</Text>
|
{Platform.OS === "web" ? (
|
||||||
|
<Text style={[styles.statusText, { color: statusColor }]}>{badgeText}</Text>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{connectionBadge ? (
|
{connectionBadge ? (
|
||||||
<View style={styles.connectionPill}>
|
<View style={Platform.OS === "web" ? styles.connectionPill : styles.connectionPillMobile}>
|
||||||
{connectionBadge.icon}
|
{connectionBadge.icon}
|
||||||
<Text style={styles.connectionText} numberOfLines={1}>
|
{Platform.OS === "web" ? (
|
||||||
{connectionBadge.text}
|
<Text style={styles.connectionText} numberOfLines={1}>
|
||||||
</Text>
|
{connectionBadge.text}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -1087,17 +1083,16 @@ function DaemonCard({
|
|||||||
<MoreVertical size={16} color={theme.colors.foregroundMuted} />
|
<MoreVertical size={16} color={theme.colors.foregroundMuted} />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" width={220} testID={`daemon-menu-content-${daemon.serverId}`}>
|
<DropdownMenuContent align="end" width={220} testID={`daemon-menu-content-${daemon.serverId}`}>
|
||||||
<DropdownMenuItem onSelect={() => onEdit(daemon)} testID={`daemon-menu-edit-${daemon.serverId}`}>
|
<DropdownMenuItem
|
||||||
|
onSelect={() => onEdit(daemon)}
|
||||||
|
leading={<Pencil size={16} color={theme.colors.foregroundMuted} />}
|
||||||
|
testID={`daemon-menu-edit-${daemon.serverId}`}
|
||||||
|
>
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem destructive onSelect={() => onRemove(daemon)} testID={`daemon-menu-remove-${daemon.serverId}`}>
|
|
||||||
Remove
|
|
||||||
</DropdownMenuItem>
|
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuLabel>Advanced</DropdownMenuLabel>
|
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={handleRestartPress}
|
onSelect={handleRestartPress}
|
||||||
|
leading={<RotateCw size={16} color={theme.colors.foregroundMuted} />}
|
||||||
status={isRestarting ? "pending" : "idle"}
|
status={isRestarting ? "pending" : "idle"}
|
||||||
pendingLabel="Restarting..."
|
pendingLabel="Restarting..."
|
||||||
disabled={!daemonClient || !isConnectedRef.current}
|
disabled={!daemonClient || !isConnectedRef.current}
|
||||||
@@ -1105,6 +1100,13 @@ function DaemonCard({
|
|||||||
>
|
>
|
||||||
Restart daemon
|
Restart daemon
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onSelect={() => onRemove(daemon)}
|
||||||
|
leading={<Trash2 size={16} color={theme.colors.foregroundMuted} />}
|
||||||
|
testID={`daemon-menu-remove-${daemon.serverId}`}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
import { forwardRef, useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import {
|
import {
|
||||||
@@ -7,14 +7,17 @@ import {
|
|||||||
Pressable,
|
Pressable,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
Text,
|
Text,
|
||||||
|
TextInput,
|
||||||
View,
|
View,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
|
import type { TextInputProps } from "react-native";
|
||||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
import { getOverlayRoot, OVERLAY_Z } from "../lib/overlay-root";
|
import { getOverlayRoot, OVERLAY_Z } from "../lib/overlay-root";
|
||||||
import {
|
import {
|
||||||
BottomSheetModal,
|
BottomSheetModal,
|
||||||
BottomSheetBackdrop,
|
BottomSheetBackdrop,
|
||||||
BottomSheetScrollView,
|
BottomSheetScrollView,
|
||||||
|
BottomSheetTextInput,
|
||||||
type BottomSheetBackgroundProps,
|
type BottomSheetBackgroundProps,
|
||||||
} from "@gorhom/bottom-sheet";
|
} from "@gorhom/bottom-sheet";
|
||||||
import { X } from "lucide-react-native";
|
import { X } from "lucide-react-native";
|
||||||
@@ -244,3 +247,20 @@ export function AdaptiveModalSheet({
|
|||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextInput that automatically uses BottomSheetTextInput on mobile
|
||||||
|
* for proper keyboard dodging in AdaptiveModalSheet.
|
||||||
|
*/
|
||||||
|
export const AdaptiveTextInput = forwardRef<TextInput, TextInputProps>(
|
||||||
|
function AdaptiveTextInput(props, ref) {
|
||||||
|
const isMobile =
|
||||||
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return <BottomSheetTextInput ref={ref as any} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <TextInput ref={ref} {...props} />;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { useCallback, useMemo, useRef, useState } from "react";
|
import { useCallback, useRef, useState } from "react";
|
||||||
import { Alert, Text, TextInput, View } from "react-native";
|
import { Alert, Text, TextInput, View } from "react-native";
|
||||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
|
|
||||||
import { Link2 } from "lucide-react-native";
|
import { Link2 } from "lucide-react-native";
|
||||||
import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-context";
|
import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-context";
|
||||||
import { normalizeHostPort } from "@/utils/daemon-endpoints";
|
import { normalizeHostPort } from "@/utils/daemon-endpoints";
|
||||||
import { DaemonConnectionTestError, probeDaemonEndpoint } from "@/utils/test-daemon-connection";
|
import { DaemonConnectionTestError, probeDaemonEndpoint } from "@/utils/test-daemon-connection";
|
||||||
import { AdaptiveModalSheet } from "./adaptive-modal-sheet";
|
import { AdaptiveModalSheet, AdaptiveTextInput } from "./adaptive-modal-sheet";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
@@ -134,7 +133,6 @@ export function AddHostModal({ visible, onClose, onCancel, onSaved, targetServer
|
|||||||
const isMobile =
|
const isMobile =
|
||||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
|
|
||||||
const InputComponent = useMemo(() => (isMobile ? BottomSheetTextInput : TextInput), [isMobile]);
|
|
||||||
const hostInputRef = useRef<TextInput>(null);
|
const hostInputRef = useRef<TextInput>(null);
|
||||||
|
|
||||||
const [endpointRaw, setEndpointRaw] = useState("");
|
const [endpointRaw, setEndpointRaw] = useState("");
|
||||||
@@ -223,8 +221,8 @@ export function AddHostModal({ visible, onClose, onCancel, onSaved, targetServer
|
|||||||
|
|
||||||
<View style={styles.field}>
|
<View style={styles.field}>
|
||||||
<Text style={styles.label}>Host</Text>
|
<Text style={styles.label}>Host</Text>
|
||||||
<InputComponent
|
<AdaptiveTextInput
|
||||||
ref={hostInputRef as any}
|
ref={hostInputRef}
|
||||||
value={endpointRaw}
|
value={endpointRaw}
|
||||||
onChangeText={setEndpointRaw}
|
onChangeText={setEndpointRaw}
|
||||||
placeholder="host:6767"
|
placeholder="host:6767"
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={mode.id}
|
key={mode.id}
|
||||||
selected={isActive}
|
selected={isActive}
|
||||||
selectedVariant="accent"
|
|
||||||
description={mode.description}
|
description={mode.description}
|
||||||
onSelect={() => handleModeChange(mode.id)}
|
onSelect={() => handleModeChange(mode.id)}
|
||||||
>
|
>
|
||||||
@@ -155,7 +154,6 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={model.id}
|
key={model.id}
|
||||||
selected={isActive}
|
selected={isActive}
|
||||||
selectedVariant="accent"
|
|
||||||
description={model.description}
|
description={model.description}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@@ -202,7 +200,6 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={opt.id}
|
key={opt.id}
|
||||||
selected={isActive}
|
selected={isActive}
|
||||||
selectedVariant="accent"
|
|
||||||
description={opt.description}
|
description={opt.description}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@@ -222,7 +219,6 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -271,7 +267,6 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={model.id}
|
key={model.id}
|
||||||
selected={isActive}
|
selected={isActive}
|
||||||
selectedVariant="accent"
|
|
||||||
description={model.description}
|
description={model.description}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@@ -314,7 +309,6 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={opt.id}
|
key={opt.id}
|
||||||
selected={isActive}
|
selected={isActive}
|
||||||
selectedVariant="accent"
|
|
||||||
description={opt.description}
|
description={opt.description}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { ScrollView, type ScrollView as ScrollViewType } from "react-native-gest
|
|||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import * as Linking from "expo-linking";
|
import * as Linking from "expo-linking";
|
||||||
import { ChevronDown, ChevronRight, GitBranch, MoreVertical, ArrowLeftRight, ListChevronsDownUp, ListChevronsUpDown } from "lucide-react-native";
|
import { Archive, ChevronDown, ChevronRight, GitBranch, MoreVertical, ArrowLeftRight, ListChevronsDownUp, ListChevronsUpDown } from "lucide-react-native";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import {
|
import {
|
||||||
@@ -75,7 +75,7 @@ interface GitAction {
|
|||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
status: ActionStatus;
|
status: ActionStatus;
|
||||||
description?: string;
|
description?: string;
|
||||||
destructive?: boolean;
|
icon?: ReactElement;
|
||||||
handler: () => void;
|
handler: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,7 +1048,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
successLabel: "Archived",
|
successLabel: "Archived",
|
||||||
disabled: archiveDisabled,
|
disabled: archiveDisabled,
|
||||||
status: archiveAction.status,
|
status: archiveAction.status,
|
||||||
destructive: true,
|
icon: <Archive size={16} color={theme.colors.foregroundMuted} />,
|
||||||
handler: archiveAction.trigger,
|
handler: archiveAction.trigger,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1198,7 +1198,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={action.id}
|
key={action.id}
|
||||||
testID={`changes-menu-${action.id}`}
|
testID={`changes-menu-${action.id}`}
|
||||||
destructive={action.destructive}
|
leading={action.icon}
|
||||||
disabled={action.disabled}
|
disabled={action.disabled}
|
||||||
status={action.status}
|
status={action.status}
|
||||||
pendingLabel={action.pendingLabel}
|
pendingLabel={action.pendingLabel}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { Text, TextInput, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { AdaptiveModalSheet, AdaptiveTextInput } from "./adaptive-modal-sheet";
|
||||||
import { AdaptiveModalSheet } from "./adaptive-modal-sheet";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
@@ -45,8 +44,6 @@ export interface NameHostModalProps {
|
|||||||
|
|
||||||
export function NameHostModal({ visible, serverId, hostname, onSkip, onSave }: NameHostModalProps) {
|
export function NameHostModal({ visible, serverId, hostname, onSkip, onSave }: NameHostModalProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
|
||||||
const InputComponent = useMemo(() => (isMobile ? BottomSheetTextInput : TextInput), [isMobile]);
|
|
||||||
|
|
||||||
const [label, setLabel] = useState("");
|
const [label, setLabel] = useState("");
|
||||||
const hasEditedRef = useRef(false);
|
const hasEditedRef = useRef(false);
|
||||||
@@ -91,7 +88,7 @@ export function NameHostModal({ visible, serverId, hostname, onSkip, onSave }: N
|
|||||||
|
|
||||||
<View style={styles.field}>
|
<View style={styles.field}>
|
||||||
<Text style={styles.label}>Label</Text>
|
<Text style={styles.label}>Label</Text>
|
||||||
<InputComponent
|
<AdaptiveTextInput
|
||||||
value={label}
|
value={label}
|
||||||
onChangeText={handleChange}
|
onChangeText={handleChange}
|
||||||
placeholder={suggested}
|
placeholder={suggested}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { Alert, Text, TextInput, View } from "react-native";
|
import { Alert, Text, View } from "react-native";
|
||||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
|
|
||||||
import { Link } from "lucide-react-native";
|
import { Link } from "lucide-react-native";
|
||||||
import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-context";
|
import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-context";
|
||||||
import { decodeOfferFragmentPayload } from "@/utils/daemon-endpoints";
|
import { decodeOfferFragmentPayload } from "@/utils/daemon-endpoints";
|
||||||
import { ConnectionOfferSchema } from "@server/shared/connection-offer";
|
import { ConnectionOfferSchema } from "@server/shared/connection-offer";
|
||||||
import { AdaptiveModalSheet } from "./adaptive-modal-sheet";
|
import { AdaptiveModalSheet, AdaptiveTextInput } from "./adaptive-modal-sheet";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
@@ -55,7 +54,6 @@ export function PairLinkModal({ visible, onClose, onCancel, onSaved, targetServe
|
|||||||
const { daemons, upsertDaemonFromOfferUrl } = useDaemonRegistry();
|
const { daemons, upsertDaemonFromOfferUrl } = useDaemonRegistry();
|
||||||
const isMobile =
|
const isMobile =
|
||||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
const InputComponent = useMemo(() => (isMobile ? BottomSheetTextInput : TextInput), [isMobile]);
|
|
||||||
|
|
||||||
const [offerUrl, setOfferUrl] = useState("");
|
const [offerUrl, setOfferUrl] = useState("");
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
@@ -143,7 +141,7 @@ export function PairLinkModal({ visible, onClose, onCancel, onSaved, targetServe
|
|||||||
|
|
||||||
<View style={styles.field}>
|
<View style={styles.field}>
|
||||||
<Text style={styles.label}>Pairing link</Text>
|
<Text style={styles.label}>Pairing link</Text>
|
||||||
<InputComponent
|
<AdaptiveTextInput
|
||||||
testID="pair-link-input"
|
testID="pair-link-input"
|
||||||
nativeID="pair-link-input"
|
nativeID="pair-link-input"
|
||||||
accessibilityLabel="pair-link-input"
|
accessibilityLabel="pair-link-input"
|
||||||
|
|||||||
@@ -470,12 +470,11 @@ export function DropdownMenuItem({
|
|||||||
}
|
}
|
||||||
onSelect?.();
|
onSelect?.();
|
||||||
}}
|
}}
|
||||||
style={({ pressed }) => [
|
style={({ pressed, hovered }) => [
|
||||||
styles.item,
|
styles.item,
|
||||||
selected ? (selectedVariant === "accent" ? styles.itemSelectedAccent : styles.itemSelected) : null,
|
selected ? (selectedVariant === "accent" ? styles.itemSelectedAccent : styles.itemSelected) : null,
|
||||||
destructive && !isSuccess ? styles.itemDestructive : null,
|
|
||||||
isDisabled ? styles.itemDisabled : null,
|
isDisabled ? styles.itemDisabled : null,
|
||||||
pressed && !isDisabled ? styles.itemPressed : null,
|
(hovered || pressed) && !isDisabled ? styles.itemHovered : null,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{showSelectedCheck ? (
|
{showSelectedCheck ? (
|
||||||
@@ -527,7 +526,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
content: {
|
content: {
|
||||||
backgroundColor: theme.colors.surface0,
|
backgroundColor: theme.colors.surface0,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: theme.colors.borderAccent,
|
borderColor: theme.colors.border,
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
shadowColor: "#000",
|
shadowColor: "#000",
|
||||||
@@ -567,7 +566,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
},
|
},
|
||||||
itemPressed: {
|
itemHovered: {
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface2,
|
||||||
},
|
},
|
||||||
itemSelected: {
|
itemSelected: {
|
||||||
@@ -579,13 +578,10 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
itemDisabled: {
|
itemDisabled: {
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
},
|
},
|
||||||
itemDestructive: {
|
|
||||||
backgroundColor: "rgba(248, 81, 73, 0.08)",
|
|
||||||
},
|
|
||||||
itemText: {
|
itemText: {
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.normal,
|
||||||
},
|
},
|
||||||
itemTextDestructive: {
|
itemTextDestructive: {
|
||||||
color: theme.colors.destructive,
|
color: theme.colors.destructive,
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 2.1 MiB |
@@ -26,17 +26,17 @@ function Home() {
|
|||||||
style={{ backgroundImage: 'url(/hero-bg.jpg)' }}
|
style={{ backgroundImage: 'url(/hero-bg.jpg)' }}
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-background/90" />
|
<div className="absolute inset-0 bg-background/90" />
|
||||||
<div className="absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-black to-transparent" />
|
<div className="absolute inset-x-0 bottom-0 h-64 bg-gradient-to-t from-black to-transparent" />
|
||||||
|
|
||||||
<div className="relative p-6 md:px-20 md:pt-20 md:pb-2 max-w-3xl mx-auto">
|
<div className="relative p-6 pb-10 md:px-20 md:pt-20 md:pb-12 max-w-3xl mx-auto">
|
||||||
<Nav />
|
<Nav />
|
||||||
<Hero />
|
<Hero />
|
||||||
<GetStarted />
|
<GetStarted />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mockup - inside hero so it's above the gradient, positioned to overflow into black section */}
|
{/* Mockup - inside hero so it's above the gradient, positioned to overflow into black section */}
|
||||||
<div className="relative px-6 md:px-8 pb-8 md:pb-16 md:mb-[-200px]">
|
<div className="relative px-6 md:px-8 pb-8 md:pb-16">
|
||||||
<div className="max-w-5xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<img
|
<img
|
||||||
src="/paseo-mockup.png"
|
src="/paseo-mockup.png"
|
||||||
alt="Paseo app showing agent management interface"
|
alt="Paseo app showing agent management interface"
|
||||||
@@ -48,7 +48,7 @@ function Home() {
|
|||||||
|
|
||||||
{/* Content section */}
|
{/* Content section */}
|
||||||
<div className="bg-black">
|
<div className="bg-black">
|
||||||
<main className="p-6 md:p-20 md:pt-56 max-w-3xl mx-auto">
|
<main className="p-6 md:p-20 md:pt-8 max-w-3xl mx-auto">
|
||||||
<Features />
|
<Features />
|
||||||
<Story />
|
<Story />
|
||||||
<FAQ />
|
<FAQ />
|
||||||
|
|||||||
Reference in New Issue
Block a user