refactor: redesign permission request UI with improved spacing and icons
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import Markdown from "react-native-markdown-display";
|
import Markdown from "react-native-markdown-display";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
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 { useMutation } from "@tanstack/react-query";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
FadeIn,
|
FadeIn,
|
||||||
@@ -27,7 +27,7 @@ import Animated, {
|
|||||||
withSequence,
|
withSequence,
|
||||||
withTiming,
|
withTiming,
|
||||||
} from "react-native-reanimated";
|
} 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 { usePanelStore } from "@/stores/panel-store";
|
||||||
import {
|
import {
|
||||||
AssistantMessage,
|
AssistantMessage,
|
||||||
@@ -242,6 +242,11 @@ export function AgentStreamView({
|
|||||||
return tightGap;
|
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)
|
// Different types get loose gap (16px)
|
||||||
return looseGap;
|
return looseGap;
|
||||||
},
|
},
|
||||||
@@ -765,6 +770,8 @@ function PermissionRequestCard({
|
|||||||
client: DaemonClientV2 | null;
|
client: DaemonClientV2 | null;
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
|
const isMobile =
|
||||||
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
|
|
||||||
const { request } = permission;
|
const { request } = permission;
|
||||||
const isPlanRequest = request.kind === "plan";
|
const isPlanRequest = request.kind === "plan";
|
||||||
@@ -987,14 +994,16 @@ function PermissionRequestCard({
|
|||||||
|
|
||||||
{planMarkdown ? (
|
{planMarkdown ? (
|
||||||
<View style={permissionStyles.section}>
|
<View style={permissionStyles.section}>
|
||||||
<Text
|
{!isPlanRequest ? (
|
||||||
style={[
|
<Text
|
||||||
permissionStyles.sectionTitle,
|
style={[
|
||||||
{ color: theme.colors.foregroundMuted },
|
permissionStyles.sectionTitle,
|
||||||
]}
|
{ color: theme.colors.foregroundMuted },
|
||||||
>
|
]}
|
||||||
{isPlanRequest ? "Plan" : "Proposed Plan"}
|
>
|
||||||
</Text>
|
Proposed plan
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
permissionStyles.contentCard,
|
permissionStyles.contentCard,
|
||||||
@@ -1024,36 +1033,27 @@ function PermissionRequestCard({
|
|||||||
How would you like to proceed?
|
How would you like to proceed?
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View style={permissionStyles.optionsContainer}>
|
<View
|
||||||
|
style={[
|
||||||
|
permissionStyles.optionsContainer,
|
||||||
|
!isMobile && permissionStyles.optionsContainerDesktop,
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[
|
style={(state) => {
|
||||||
permissionStyles.optionButton,
|
const hovered = Boolean((state as any).hovered);
|
||||||
{ backgroundColor: theme.colors.primary },
|
const pressed = Boolean(state.pressed);
|
||||||
]}
|
return [
|
||||||
onPress={() => handleResponse({ behavior: "allow" })}
|
permissionStyles.optionButton,
|
||||||
disabled={isResponding}
|
{
|
||||||
>
|
backgroundColor: hovered
|
||||||
{isResponding ? (
|
? theme.colors.surface1
|
||||||
<ActivityIndicator
|
: theme.colors.surface2,
|
||||||
size="small"
|
borderColor: theme.colors.borderAccent,
|
||||||
color={theme.colors.primaryForeground}
|
},
|
||||||
/>
|
pressed ? permissionStyles.optionButtonPressed : null,
|
||||||
) : (
|
];
|
||||||
<Text
|
}}
|
||||||
style={[
|
|
||||||
permissionStyles.optionText,
|
|
||||||
{ color: theme.colors.primaryForeground },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
Allow
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
<Pressable
|
|
||||||
style={[
|
|
||||||
permissionStyles.optionButton,
|
|
||||||
{ backgroundColor: theme.colors.destructive },
|
|
||||||
]}
|
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
handleResponse({
|
handleResponse({
|
||||||
behavior: "deny",
|
behavior: "deny",
|
||||||
@@ -1063,19 +1063,54 @@ function PermissionRequestCard({
|
|||||||
disabled={isResponding}
|
disabled={isResponding}
|
||||||
>
|
>
|
||||||
{isResponding ? (
|
{isResponding ? (
|
||||||
<ActivityIndicator
|
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
||||||
size="small"
|
|
||||||
color={theme.colors.primaryForeground}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<Text
|
<View style={permissionStyles.optionContent}>
|
||||||
style={[
|
<X size={14} color={theme.colors.foregroundMuted} />
|
||||||
permissionStyles.optionText,
|
<Text
|
||||||
{ color: theme.colors.primaryForeground },
|
style={[
|
||||||
]}
|
permissionStyles.optionText,
|
||||||
>
|
{ color: theme.colors.foregroundMuted },
|
||||||
Deny
|
]}
|
||||||
</Text>
|
>
|
||||||
|
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>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
@@ -1190,7 +1225,7 @@ const stylesheet = StyleSheet.create((theme) => ({
|
|||||||
const permissionStyles = StyleSheet.create((theme) => ({
|
const permissionStyles = StyleSheet.create((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
marginVertical: theme.spacing[3],
|
marginVertical: theme.spacing[3],
|
||||||
padding: theme.spacing[4],
|
padding: theme.spacing[3],
|
||||||
borderRadius: theme.spacing[2],
|
borderRadius: theme.spacing[2],
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
@@ -1208,8 +1243,6 @@ const permissionStyles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
textTransform: "uppercase" as const,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
},
|
},
|
||||||
contentCard: {
|
contentCard: {
|
||||||
padding: theme.spacing[3],
|
padding: theme.spacing[3],
|
||||||
@@ -1220,19 +1253,35 @@ const permissionStyles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
question: {
|
question: {
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
marginTop: theme.spacing[2],
|
marginTop: theme.spacing[1],
|
||||||
marginBottom: theme.spacing[2],
|
marginBottom: theme.spacing[1],
|
||||||
},
|
},
|
||||||
optionsContainer: {
|
optionsContainer: {
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
|
optionsContainerDesktop: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
alignItems: "center",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
optionButton: {
|
optionButton: {
|
||||||
padding: theme.spacing[3],
|
paddingVertical: theme.spacing[2],
|
||||||
|
paddingHorizontal: theme.spacing[3],
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
borderWidth: theme.borderWidth[1],
|
||||||
|
},
|
||||||
|
optionButtonPressed: {
|
||||||
|
opacity: 0.9,
|
||||||
|
},
|
||||||
|
optionContent: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
optionText: {
|
optionText: {
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
fontWeight: theme.fontWeight.semibold,
|
fontWeight: theme.fontWeight.normal,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -180,11 +180,13 @@ export const UserMessage = memo(function UserMessage({
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
userMessageStylesheet.container,
|
userMessageStylesheet.container,
|
||||||
isFirstInGroup && { marginTop: theme.spacing[4] },
|
!resolvedDisableOuterSpacing && [
|
||||||
isLastInGroup && { marginBottom: theme.spacing[4] },
|
isFirstInGroup && { marginTop: theme.spacing[4] },
|
||||||
!isFirstInGroup || !isLastInGroup
|
isLastInGroup && { marginBottom: theme.spacing[4] },
|
||||||
? { marginBottom: theme.spacing[1] }
|
!isFirstInGroup || !isLastInGroup
|
||||||
: undefined,
|
? { marginBottom: theme.spacing[1] }
|
||||||
|
: undefined,
|
||||||
|
],
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
|||||||
Reference in New Issue
Block a user