Fix composer safe-area ownership
This commit is contained in:
@@ -14,7 +14,6 @@ import {
|
|||||||
Paperclip,
|
Paperclip,
|
||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
import Animated from "react-native-reanimated";
|
import Animated from "react-native-reanimated";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { FOOTER_HEIGHT, MAX_CONTENT_WIDTH } from "@/constants/layout";
|
import { FOOTER_HEIGHT, MAX_CONTENT_WIDTH } from "@/constants/layout";
|
||||||
import { generateMessageId, type StreamItem } from "@/types/stream";
|
import { generateMessageId, type StreamItem } from "@/types/stream";
|
||||||
@@ -169,7 +168,6 @@ export function Composer({
|
|||||||
markScrollInvestigationRender(`Composer:${serverId}:${agentId}`);
|
markScrollInvestigationRender(`Composer:${serverId}:${agentId}`);
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const buttonIconSize = isWeb ? theme.iconSize.md : theme.iconSize.lg;
|
const buttonIconSize = isWeb ? theme.iconSize.md : theme.iconSize.lg;
|
||||||
const insets = useSafeAreaInsets();
|
|
||||||
const client = useHostRuntimeClient(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
const isConnected = useHostRuntimeIsConnected(serverId);
|
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||||
const agentDirectoryStatus = useHostRuntimeAgentDirectoryStatus(serverId);
|
const agentDirectoryStatus = useHostRuntimeAgentDirectoryStatus(serverId);
|
||||||
@@ -882,9 +880,7 @@ export function Composer({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View style={[styles.container, keyboardAnimatedStyle]}>
|
||||||
style={[styles.container, { paddingBottom: insets.bottom }, keyboardAnimatedStyle]}
|
|
||||||
>
|
|
||||||
<AttachmentLightbox metadata={lightboxMetadata} onClose={() => setLightboxMetadata(null)} />
|
<AttachmentLightbox metadata={lightboxMetadata} onClose={() => setLightboxMetadata(null)} />
|
||||||
{/* Input area */}
|
{/* Input area */}
|
||||||
<View style={[styles.inputAreaContainer, isComposerLocked && styles.inputAreaLocked]}>
|
<View style={[styles.inputAreaContainer, isComposerLocked && styles.inputAreaLocked]}>
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ vi.mock("react-native-unistyles", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
vi.mock("react-native-safe-area-context", () => ({
|
||||||
|
useSafeAreaInsets: () => ({ top: 0, right: 0, bottom: 0, left: 0 }),
|
||||||
|
}));
|
||||||
|
|
||||||
function createPanelTestStyles(factory: PanelTestStyleFactory | PanelTestStyles): PanelTestStyles {
|
function createPanelTestStyles(factory: PanelTestStyleFactory | PanelTestStyles): PanelTestStyles {
|
||||||
return typeof factory === "function" ? factory(theme) : factory;
|
return typeof factory === "function" ? factory(theme) : factory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|||||||
import { ActivityIndicator, Text, View } from "react-native";
|
import { ActivityIndicator, Text, View } from "react-native";
|
||||||
import ReanimatedAnimated from "react-native-reanimated";
|
import ReanimatedAnimated from "react-native-reanimated";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { shallow, useShallow } from "zustand/shallow";
|
import { shallow, useShallow } from "zustand/shallow";
|
||||||
import { useStoreWithEqualityFn } from "zustand/traditional";
|
import { useStoreWithEqualityFn } from "zustand/traditional";
|
||||||
import invariant from "tiny-invariant";
|
import invariant from "tiny-invariant";
|
||||||
@@ -1165,6 +1166,7 @@ function ActiveAgentComposer({
|
|||||||
onComposerHeightChange: (height: number) => void;
|
onComposerHeightChange: (height: number) => void;
|
||||||
onMessageSent: () => void;
|
onMessageSent: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
const agentInputDraft = useAgentInputDraft({
|
const agentInputDraft = useAgentInputDraft({
|
||||||
draftKey: buildDraftStoreKey({
|
draftKey: buildDraftStoreKey({
|
||||||
serverId,
|
serverId,
|
||||||
@@ -1174,24 +1176,26 @@ function ActiveAgentComposer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Composer
|
<View style={[styles.inputAreaWrapper, { paddingBottom: insets.bottom }]}>
|
||||||
agentId={agentId}
|
<Composer
|
||||||
serverId={serverId}
|
agentId={agentId}
|
||||||
isPaneFocused={isPaneFocused}
|
serverId={serverId}
|
||||||
value={agentInputDraft.text}
|
isPaneFocused={isPaneFocused}
|
||||||
onChangeText={agentInputDraft.setText}
|
value={agentInputDraft.text}
|
||||||
attachments={agentInputDraft.attachments}
|
onChangeText={agentInputDraft.setText}
|
||||||
onChangeAttachments={agentInputDraft.setAttachments}
|
attachments={agentInputDraft.attachments}
|
||||||
cwd={agentInputDraft.cwd}
|
onChangeAttachments={agentInputDraft.setAttachments}
|
||||||
clearDraft={agentInputDraft.clear}
|
cwd={agentInputDraft.cwd}
|
||||||
autoFocus={isPaneFocused}
|
clearDraft={agentInputDraft.clear}
|
||||||
isSubmitLoading={isSubmitLoading}
|
autoFocus={isPaneFocused}
|
||||||
onAttentionInputFocus={onAttentionInputFocus}
|
isSubmitLoading={isSubmitLoading}
|
||||||
onAttentionPromptSend={onAttentionPromptSend}
|
onAttentionInputFocus={onAttentionInputFocus}
|
||||||
onAddImages={onAddImages}
|
onAttentionPromptSend={onAttentionPromptSend}
|
||||||
onComposerHeightChange={onComposerHeightChange}
|
onAddImages={onAddImages}
|
||||||
onMessageSent={onMessageSent}
|
onComposerHeightChange={onComposerHeightChange}
|
||||||
/>
|
onMessageSent={onMessageSent}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1271,6 +1275,10 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
content: {
|
content: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
inputAreaWrapper: {
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: theme.colors.surface0,
|
||||||
|
},
|
||||||
historySyncOverlay: {
|
historySyncOverlay: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|||||||
@@ -1221,7 +1221,7 @@ function DraftAgentScreenContent({
|
|||||||
</ScrollView>
|
</ScrollView>
|
||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
<View style={styles.inputAreaWrapper}>
|
<View style={[styles.inputAreaWrapper, { paddingBottom: insets.bottom }]}>
|
||||||
<Composer
|
<Composer
|
||||||
agentId={draftAgentIdRef.current}
|
agentId={draftAgentIdRef.current}
|
||||||
serverId={selectedServerId ?? ""}
|
serverId={selectedServerId ?? ""}
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ vi.mock("@/constants/layout", () => ({
|
|||||||
useIsCompactFormFactor: () => false,
|
useIsCompactFormFactor: () => false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("react-native-safe-area-context", () => ({
|
||||||
|
useSafeAreaInsets: () => ({ top: 0, right: 0, bottom: 0, left: 0 }),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock("lucide-react-native", () => {
|
vi.mock("lucide-react-native", () => {
|
||||||
const createIcon = (name: string) => (props: Record<string, unknown>) =>
|
const createIcon = (name: string) => (props: Record<string, unknown>) =>
|
||||||
React.createElement("span", { ...props, "data-icon": name });
|
React.createElement("span", { ...props, "data-icon": name });
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Pressable, Text, View } from "react-native";
|
import { Pressable, Text, View } from "react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { createNameId } from "mnemonic-id";
|
import { createNameId } from "mnemonic-id";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { ChevronDown, GitBranch, GitPullRequest } from "lucide-react-native";
|
import { ChevronDown, GitBranch, GitPullRequest } from "lucide-react-native";
|
||||||
@@ -12,12 +13,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
|
|||||||
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
import { SidebarMenuToggle } from "@/components/headers/menu-header";
|
import { SidebarMenuToggle } from "@/components/headers/menu-header";
|
||||||
import { ScreenHeader } from "@/components/headers/screen-header";
|
import { ScreenHeader } from "@/components/headers/screen-header";
|
||||||
import {
|
import { HEADER_INNER_HEIGHT, MAX_CONTENT_WIDTH, useIsCompactFormFactor } from "@/constants/layout";
|
||||||
HEADER_INNER_HEIGHT,
|
|
||||||
HEADER_INNER_HEIGHT_MOBILE,
|
|
||||||
HEADER_TOP_PADDING_MOBILE,
|
|
||||||
MAX_CONTENT_WIDTH,
|
|
||||||
} from "@/constants/layout";
|
|
||||||
import { useToast } from "@/contexts/toast-context";
|
import { useToast } from "@/contexts/toast-context";
|
||||||
import { useAgentInputDraft } from "@/hooks/use-agent-input-draft";
|
import { useAgentInputDraft } from "@/hooks/use-agent-input-draft";
|
||||||
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||||
@@ -107,6 +103,8 @@ export function NewWorkspaceScreen({
|
|||||||
displayName: displayNameProp,
|
displayName: displayNameProp,
|
||||||
}: NewWorkspaceScreenProps) {
|
}: NewWorkspaceScreenProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
const isCompact = useIsCompactFormFactor();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const mergeWorkspaces = useSessionStore((state) => state.mergeWorkspaces);
|
const mergeWorkspaces = useSessionStore((state) => state.mergeWorkspaces);
|
||||||
const setAgents = useSessionStore((state) => state.setAgents);
|
const setAgents = useSessionStore((state) => state.setAgents);
|
||||||
@@ -453,7 +451,13 @@ export function NewWorkspaceScreen({
|
|||||||
leftStyle={styles.headerLeft}
|
leftStyle={styles.headerLeft}
|
||||||
borderless
|
borderless
|
||||||
/>
|
/>
|
||||||
<View style={styles.content}>
|
<View
|
||||||
|
style={[
|
||||||
|
styles.content,
|
||||||
|
isCompact ? styles.contentCompact : styles.contentCentered,
|
||||||
|
isCompact ? { paddingBottom: insets.bottom } : null,
|
||||||
|
]}
|
||||||
|
>
|
||||||
<TitlebarDragRegion />
|
<TitlebarDragRegion />
|
||||||
<View style={styles.centered}>
|
<View style={styles.centered}>
|
||||||
<Composer
|
<Composer
|
||||||
@@ -567,12 +571,14 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
content: {
|
content: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingBottom: {
|
},
|
||||||
xs: HEADER_INNER_HEIGHT_MOBILE + HEADER_TOP_PADDING_MOBILE + theme.spacing[6],
|
contentCentered: {
|
||||||
md: HEADER_INNER_HEIGHT + theme.spacing[6],
|
justifyContent: "center",
|
||||||
},
|
paddingBottom: HEADER_INNER_HEIGHT + theme.spacing[6],
|
||||||
|
},
|
||||||
|
contentCompact: {
|
||||||
|
justifyContent: "flex-end",
|
||||||
},
|
},
|
||||||
centered: {
|
centered: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useMemo, useRef } from "react";
|
import { useCallback, useMemo, useRef } from "react";
|
||||||
import { Keyboard, ScrollView, Text, View } from "react-native";
|
import { Keyboard, ScrollView, Text, View } from "react-native";
|
||||||
import { StyleSheet } from "react-native-unistyles";
|
import { StyleSheet } from "react-native-unistyles";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import invariant from "tiny-invariant";
|
import invariant from "tiny-invariant";
|
||||||
import { Composer } from "@/components/composer";
|
import { Composer } from "@/components/composer";
|
||||||
import { FileDropZone } from "@/components/file-drop-zone";
|
import { FileDropZone } from "@/components/file-drop-zone";
|
||||||
@@ -48,6 +49,7 @@ export function WorkspaceDraftAgentTab({
|
|||||||
onCreated,
|
onCreated,
|
||||||
onOpenWorkspaceFile,
|
onOpenWorkspaceFile,
|
||||||
}: WorkspaceDraftAgentTabProps) {
|
}: WorkspaceDraftAgentTabProps) {
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
const client = useHostRuntimeClient(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
const isConnected = useHostRuntimeIsConnected(serverId);
|
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||||
const workspaceAuthority = useWorkspaceExecutionAuthority(serverId, workspaceId);
|
const workspaceAuthority = useWorkspaceExecutionAuthority(serverId, workspaceId);
|
||||||
@@ -282,7 +284,7 @@ export function WorkspaceDraftAgentTab({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.inputAreaWrapper}>
|
<View style={[styles.inputAreaWrapper, { paddingBottom: insets.bottom }]}>
|
||||||
<Composer
|
<Composer
|
||||||
agentId={tabId}
|
agentId={tabId}
|
||||||
serverId={serverId}
|
serverId={serverId}
|
||||||
|
|||||||
Reference in New Issue
Block a user