refactor: redesign permission request UI with improved spacing and icons

This commit is contained in:
Mohamed Boudra
2026-02-03 09:32:42 +07:00
parent 4db2e91cdd
commit 2623328872
2 changed files with 114 additions and 63 deletions
+107 -58
View File
@@ -14,7 +14,7 @@ import {
} from "react-native";
import Markdown from "react-native-markdown-display";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
import { useMutation } from "@tanstack/react-query";
import Animated, {
FadeIn,
@@ -27,7 +27,7 @@ import Animated, {
withSequence,
withTiming,
} from "react-native-reanimated";
import { ChevronDown } from "lucide-react-native";
import { Check, ChevronDown, X } from "lucide-react-native";
import { usePanelStore } from "@/stores/panel-store";
import {
AssistantMessage,
@@ -242,6 +242,11 @@ export function AgentStreamView({
return tightGap;
}
// And keep assistant messages visually connected to tool sequences (symmetry).
if (item.kind === "assistant_message" && isToolSequenceItem(aboveItem)) {
return tightGap;
}
// Different types get loose gap (16px)
return looseGap;
},
@@ -765,6 +770,8 @@ function PermissionRequestCard({
client: DaemonClientV2 | null;
}) {
const { theme } = useUnistyles();
const isMobile =
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
const { request } = permission;
const isPlanRequest = request.kind === "plan";
@@ -987,14 +994,16 @@ function PermissionRequestCard({
{planMarkdown ? (
<View style={permissionStyles.section}>
<Text
style={[
permissionStyles.sectionTitle,
{ color: theme.colors.foregroundMuted },
]}
>
{isPlanRequest ? "Plan" : "Proposed Plan"}
</Text>
{!isPlanRequest ? (
<Text
style={[
permissionStyles.sectionTitle,
{ color: theme.colors.foregroundMuted },
]}
>
Proposed plan
</Text>
) : null}
<View
style={[
permissionStyles.contentCard,
@@ -1024,36 +1033,27 @@ function PermissionRequestCard({
How would you like to proceed?
</Text>
<View style={permissionStyles.optionsContainer}>
<View
style={[
permissionStyles.optionsContainer,
!isMobile && permissionStyles.optionsContainerDesktop,
]}
>
<Pressable
style={[
permissionStyles.optionButton,
{ backgroundColor: theme.colors.primary },
]}
onPress={() => handleResponse({ behavior: "allow" })}
disabled={isResponding}
>
{isResponding ? (
<ActivityIndicator
size="small"
color={theme.colors.primaryForeground}
/>
) : (
<Text
style={[
permissionStyles.optionText,
{ color: theme.colors.primaryForeground },
]}
>
Allow
</Text>
)}
</Pressable>
<Pressable
style={[
permissionStyles.optionButton,
{ backgroundColor: theme.colors.destructive },
]}
style={(state) => {
const hovered = Boolean((state as any).hovered);
const pressed = Boolean(state.pressed);
return [
permissionStyles.optionButton,
{
backgroundColor: hovered
? theme.colors.surface1
: theme.colors.surface2,
borderColor: theme.colors.borderAccent,
},
pressed ? permissionStyles.optionButtonPressed : null,
];
}}
onPress={() =>
handleResponse({
behavior: "deny",
@@ -1063,19 +1063,54 @@ function PermissionRequestCard({
disabled={isResponding}
>
{isResponding ? (
<ActivityIndicator
size="small"
color={theme.colors.primaryForeground}
/>
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
) : (
<Text
style={[
permissionStyles.optionText,
{ color: theme.colors.primaryForeground },
]}
>
Deny
</Text>
<View style={permissionStyles.optionContent}>
<X size={14} color={theme.colors.foregroundMuted} />
<Text
style={[
permissionStyles.optionText,
{ color: theme.colors.foregroundMuted },
]}
>
Deny
</Text>
</View>
)}
</Pressable>
<Pressable
style={(state) => {
const hovered = Boolean((state as any).hovered);
const pressed = Boolean(state.pressed);
return [
permissionStyles.optionButton,
{
backgroundColor: hovered
? theme.colors.surface1
: theme.colors.surface2,
borderColor: theme.colors.primary,
},
pressed ? permissionStyles.optionButtonPressed : null,
];
}}
onPress={() => handleResponse({ behavior: "allow" })}
disabled={isResponding}
>
{isResponding ? (
<ActivityIndicator size="small" color={theme.colors.primary} />
) : (
<View style={permissionStyles.optionContent}>
<Check size={14} color={theme.colors.primary} />
<Text
style={[
permissionStyles.optionText,
{ color: theme.colors.primary },
]}
>
Accept
</Text>
</View>
)}
</Pressable>
</View>
@@ -1190,7 +1225,7 @@ const stylesheet = StyleSheet.create((theme) => ({
const permissionStyles = StyleSheet.create((theme) => ({
container: {
marginVertical: theme.spacing[3],
padding: theme.spacing[4],
padding: theme.spacing[3],
borderRadius: theme.spacing[2],
borderWidth: 1,
gap: theme.spacing[2],
@@ -1208,8 +1243,6 @@ const permissionStyles = StyleSheet.create((theme) => ({
},
sectionTitle: {
fontSize: theme.fontSize.xs,
textTransform: "uppercase" as const,
letterSpacing: 0.5,
},
contentCard: {
padding: theme.spacing[3],
@@ -1220,19 +1253,35 @@ const permissionStyles = StyleSheet.create((theme) => ({
},
question: {
fontSize: theme.fontSize.sm,
marginTop: theme.spacing[2],
marginBottom: theme.spacing[2],
marginTop: theme.spacing[1],
marginBottom: theme.spacing[1],
},
optionsContainer: {
gap: theme.spacing[2],
},
optionsContainerDesktop: {
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "center",
width: "100%",
},
optionButton: {
padding: theme.spacing[3],
paddingVertical: theme.spacing[2],
paddingHorizontal: theme.spacing[3],
borderRadius: theme.borderRadius.md,
alignItems: "center",
borderWidth: theme.borderWidth[1],
},
optionButtonPressed: {
opacity: 0.9,
},
optionContent: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
},
optionText: {
fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.semibold,
fontWeight: theme.fontWeight.normal,
},
}));
+7 -5
View File
@@ -180,11 +180,13 @@ export const UserMessage = memo(function UserMessage({
<View
style={[
userMessageStylesheet.container,
isFirstInGroup && { marginTop: theme.spacing[4] },
isLastInGroup && { marginBottom: theme.spacing[4] },
!isFirstInGroup || !isLastInGroup
? { marginBottom: theme.spacing[1] }
: undefined,
!resolvedDisableOuterSpacing && [
isFirstInGroup && { marginTop: theme.spacing[4] },
isLastInGroup && { marginBottom: theme.spacing[4] },
!isFirstInGroup || !isLastInGroup
? { marginBottom: theme.spacing[1] }
: undefined,
],
]}
>
<Pressable