feat(app): add git diff viewer with dropdown menu and UI improvements
- Add git diff viewer screen to display local changes per agent - Parse git diff output into file sections with syntax highlighting - Horizontal scrolling for long diff lines (no text wrapping) - Request diff data on mount via new WebSocket messages - Add dropdown menu to agent header - Three-dot menu button with "View Changes" option - Position menu below button using window-aware measurements - Account for safe area insets and orientation changes - Implement server-side git operations - Add git_diff_request/response message types - Execute `git diff HEAD` in agent's working directory - Handle errors gracefully with proper error messages - Update session context for git diff state - Add gitDiffs Map to store diff content per agent - Add requestGitDiff helper function - WebSocket handler for git_diff_response messages - Improve agent input area layout - Move status bar inline with attachment button - Full-width text input above button row - Better spacing and visual hierarchy - Simplify agent status bar to show only status dot 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
import { useEffect, useMemo, useRef, useCallback, useState } from "react";
|
import { useEffect, useMemo, useRef, useCallback, useState } from "react";
|
||||||
import { View, Text, ActivityIndicator, Pressable, Modal, Dimensions } from "react-native";
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
ActivityIndicator,
|
||||||
|
Pressable,
|
||||||
|
Modal,
|
||||||
|
useWindowDimensions,
|
||||||
|
LayoutChangeEvent,
|
||||||
|
} from "react-native";
|
||||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
|
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
|
||||||
@@ -28,8 +36,10 @@ export default function AgentScreen() {
|
|||||||
initializeAgent,
|
initializeAgent,
|
||||||
} = useSession();
|
} = useSession();
|
||||||
const { registerFooterControls, unregisterFooterControls } = useFooterControls();
|
const { registerFooterControls, unregisterFooterControls } = useFooterControls();
|
||||||
|
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
||||||
const [menuVisible, setMenuVisible] = useState(false);
|
const [menuVisible, setMenuVisible] = useState(false);
|
||||||
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
||||||
|
const [menuContentHeight, setMenuContentHeight] = useState(0);
|
||||||
const menuButtonRef = useRef<View>(null);
|
const menuButtonRef = useRef<View>(null);
|
||||||
|
|
||||||
// Keyboard animation
|
// Keyboard animation
|
||||||
@@ -97,25 +107,75 @@ export default function AgentScreen() {
|
|||||||
};
|
};
|
||||||
}, [agentControls, agent, isInitializing, registerFooterControls, unregisterFooterControls]);
|
}, [agentControls, agent, isInitializing, registerFooterControls, unregisterFooterControls]);
|
||||||
|
|
||||||
const handleOpenMenu = useCallback(() => {
|
const recalculateMenuPosition = useCallback(
|
||||||
menuButtonRef.current?.measureInWindow((x, y, width, height) => {
|
(onMeasured?: () => void) => {
|
||||||
const screenWidth = Dimensions.get("window").width;
|
requestAnimationFrame(() => {
|
||||||
const verticalOffset = 6;
|
const anchor = menuButtonRef.current;
|
||||||
const horizontalMargin = 16;
|
|
||||||
const desiredLeft = x + width - DROPDOWN_WIDTH;
|
|
||||||
const maxLeft = screenWidth - DROPDOWN_WIDTH - horizontalMargin;
|
|
||||||
const clampedLeft = Math.min(Math.max(desiredLeft, horizontalMargin), maxLeft);
|
|
||||||
|
|
||||||
setMenuPosition({
|
if (!anchor) {
|
||||||
top: y + height + verticalOffset,
|
if (onMeasured) {
|
||||||
left: clampedLeft,
|
onMeasured();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchor.measureInWindow((x, y, width, height) => {
|
||||||
|
const verticalOffset = 8;
|
||||||
|
const horizontalMargin = 16;
|
||||||
|
const desiredLeft = x + width - DROPDOWN_WIDTH;
|
||||||
|
const maxLeft = windowWidth - DROPDOWN_WIDTH - horizontalMargin;
|
||||||
|
const clampedLeft = Math.min(Math.max(desiredLeft, horizontalMargin), maxLeft);
|
||||||
|
|
||||||
|
// Position menu below button - add insets.top to account for status bar
|
||||||
|
const buttonBottom = y + height + insets.top;
|
||||||
|
const top = buttonBottom + verticalOffset;
|
||||||
|
|
||||||
|
// If menu would go off screen, clamp to visible area
|
||||||
|
const bottomEdge = top + menuContentHeight;
|
||||||
|
const maxBottom = windowHeight - horizontalMargin;
|
||||||
|
const clampedTop = bottomEdge > maxBottom
|
||||||
|
? Math.max(verticalOffset, maxBottom - menuContentHeight)
|
||||||
|
: top;
|
||||||
|
|
||||||
|
console.log('[Menu] Button position:', { x, y, width, height, insetsTop: insets.top });
|
||||||
|
console.log('[Menu] Calculated position:', { buttonBottom, top, clampedTop, left: clampedLeft });
|
||||||
|
|
||||||
|
setMenuPosition({
|
||||||
|
top: clampedTop,
|
||||||
|
left: clampedLeft,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onMeasured) {
|
||||||
|
onMeasured();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
[menuContentHeight, windowHeight, windowWidth]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOpenMenu = useCallback(() => {
|
||||||
|
recalculateMenuPosition(() => {
|
||||||
setMenuVisible(true);
|
setMenuVisible(true);
|
||||||
});
|
});
|
||||||
}, []);
|
}, [recalculateMenuPosition]);
|
||||||
|
|
||||||
const handleCloseMenu = useCallback(() => {
|
const handleCloseMenu = useCallback(() => {
|
||||||
setMenuVisible(false);
|
setMenuVisible(false);
|
||||||
|
setMenuContentHeight(0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!menuVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recalculateMenuPosition();
|
||||||
|
}, [menuVisible, recalculateMenuPosition]);
|
||||||
|
|
||||||
|
const handleMenuLayout = useCallback((event: LayoutChangeEvent) => {
|
||||||
|
const { height } = event.nativeEvent.layout;
|
||||||
|
setMenuContentHeight((current) => (current === height ? current : height));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleViewChanges = useCallback(() => {
|
const handleViewChanges = useCallback(() => {
|
||||||
@@ -191,6 +251,7 @@ export default function AgentScreen() {
|
|||||||
width: DROPDOWN_WIDTH,
|
width: DROPDOWN_WIDTH,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
onLayout={handleMenuLayout}
|
||||||
>
|
>
|
||||||
<Pressable onPress={handleViewChanges} style={styles.menuItem}>
|
<Pressable onPress={handleViewChanges} style={styles.menuItem}>
|
||||||
<GitBranch size={20} color={theme.colors.foreground} />
|
<GitBranch size={20} color={theme.colors.foreground} />
|
||||||
|
|||||||
@@ -134,18 +134,28 @@ export default function GitDiffScreen() {
|
|||||||
>
|
>
|
||||||
<View style={styles.diffLinesContainer}>
|
<View style={styles.diffLinesContainer}>
|
||||||
{file.lines.map((line, lineIndex) => (
|
{file.lines.map((line, lineIndex) => (
|
||||||
<Text
|
<View
|
||||||
key={lineIndex}
|
key={lineIndex}
|
||||||
style={[
|
style={[
|
||||||
styles.diffLine,
|
styles.diffLineContainer,
|
||||||
line.type === "add" && styles.addLine,
|
line.type === "add" && styles.addLineContainer,
|
||||||
line.type === "remove" && styles.removeLine,
|
line.type === "remove" && styles.removeLineContainer,
|
||||||
line.type === "header" && styles.headerLine,
|
line.type === "header" && styles.headerLineContainer,
|
||||||
line.type === "context" && styles.contextLine,
|
line.type === "context" && styles.contextLineContainer,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{line.content}
|
<Text
|
||||||
</Text>
|
style={[
|
||||||
|
styles.diffLineText,
|
||||||
|
line.type === "add" && styles.addLineText,
|
||||||
|
line.type === "remove" && styles.removeLineText,
|
||||||
|
line.type === "header" && styles.headerLineText,
|
||||||
|
line.type === "context" && styles.contextLineText,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{line.content}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -232,27 +242,42 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
diffLinesContainer: {
|
diffLinesContainer: {
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
},
|
},
|
||||||
diffLine: {
|
diffLineContainer: {
|
||||||
fontSize: theme.fontSize.xs,
|
minWidth: "100%",
|
||||||
fontFamily: "monospace",
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: theme.spacing[1],
|
paddingVertical: theme.spacing[1],
|
||||||
flexShrink: 0,
|
alignSelf: "flex-start",
|
||||||
minWidth: "100%",
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
addLine: {
|
diffLineText: {
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
fontFamily: "monospace",
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
addLineContainer: {
|
||||||
backgroundColor: theme.colors.palette.green[900],
|
backgroundColor: theme.colors.palette.green[900],
|
||||||
|
},
|
||||||
|
addLineText: {
|
||||||
color: theme.colors.palette.green[200],
|
color: theme.colors.palette.green[200],
|
||||||
},
|
},
|
||||||
removeLine: {
|
removeLineContainer: {
|
||||||
backgroundColor: theme.colors.palette.red[900],
|
backgroundColor: theme.colors.palette.red[900],
|
||||||
|
},
|
||||||
|
removeLineText: {
|
||||||
color: theme.colors.palette.red[200],
|
color: theme.colors.palette.red[200],
|
||||||
},
|
},
|
||||||
headerLine: {
|
headerLineContainer: {
|
||||||
color: theme.colors.mutedForeground,
|
|
||||||
backgroundColor: theme.colors.muted,
|
backgroundColor: theme.colors.muted,
|
||||||
},
|
},
|
||||||
contextLine: {
|
headerLineText: {
|
||||||
|
color: theme.colors.mutedForeground,
|
||||||
|
},
|
||||||
|
contextLineContainer: {
|
||||||
|
backgroundColor: theme.colors.card,
|
||||||
|
},
|
||||||
|
contextLineText: {
|
||||||
color: theme.colors.mutedForeground,
|
color: theme.colors.mutedForeground,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -261,11 +261,6 @@ export function AgentInputArea({ agentId }: AgentInputAreaProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{/* Status bar */}
|
|
||||||
<View style={styles.statusBarContainer}>
|
|
||||||
<AgentStatusBar agentId={agentId} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Border separator */}
|
{/* Border separator */}
|
||||||
<View style={styles.borderSeparator} />
|
<View style={styles.borderSeparator} />
|
||||||
|
|
||||||
@@ -298,10 +293,26 @@ export function AgentInputArea({ agentId }: AgentInputAreaProps) {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Input row */}
|
{/* Full-width text input */}
|
||||||
<View style={styles.inputRow}>
|
<TextInput
|
||||||
{/* Attachment button */}
|
value={userInput}
|
||||||
{!isRecording && (
|
onChangeText={setUserInput}
|
||||||
|
placeholder="Message agent..."
|
||||||
|
placeholderTextColor={theme.colors.mutedForeground}
|
||||||
|
style={[
|
||||||
|
styles.textInput,
|
||||||
|
{ height: inputHeight, minHeight: MIN_INPUT_HEIGHT, maxHeight: MAX_INPUT_HEIGHT },
|
||||||
|
]}
|
||||||
|
multiline
|
||||||
|
scrollEnabled={inputHeight >= MAX_INPUT_HEIGHT}
|
||||||
|
onContentSizeChange={handleContentSizeChange}
|
||||||
|
editable={!isRecording && ws.isConnected}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Button row below input */}
|
||||||
|
<View style={styles.buttonRow}>
|
||||||
|
{/* Left button group */}
|
||||||
|
<View style={styles.leftButtonGroup}>
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={handlePickImage}
|
onPress={handlePickImage}
|
||||||
disabled={!ws.isConnected}
|
disabled={!ws.isConnected}
|
||||||
@@ -312,71 +323,52 @@ export function AgentInputArea({ agentId }: AgentInputAreaProps) {
|
|||||||
>
|
>
|
||||||
<Paperclip size={20} color={theme.colors.foreground} />
|
<Paperclip size={20} color={theme.colors.foreground} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)}
|
<AgentStatusBar agentId={agentId} />
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Text input */}
|
{/* Right button group */}
|
||||||
<TextInput
|
<View style={styles.rightButtonGroup}>
|
||||||
value={userInput}
|
{hasText || hasImages ? (
|
||||||
onChangeText={setUserInput}
|
<Pressable
|
||||||
placeholder="Message agent..."
|
onPress={handleSendMessage}
|
||||||
placeholderTextColor={theme.colors.mutedForeground}
|
disabled={!ws.isConnected || isProcessing}
|
||||||
style={[
|
style={[
|
||||||
styles.textInput,
|
styles.sendButton,
|
||||||
{ height: inputHeight, minHeight: MIN_INPUT_HEIGHT, maxHeight: MAX_INPUT_HEIGHT },
|
(!ws.isConnected || isProcessing) && styles.buttonDisabled,
|
||||||
]}
|
]}
|
||||||
multiline
|
>
|
||||||
scrollEnabled={inputHeight >= MAX_INPUT_HEIGHT}
|
<ArrowUp size={20} color="white" />
|
||||||
onContentSizeChange={handleContentSizeChange}
|
</Pressable>
|
||||||
editable={!isRecording && ws.isConnected}
|
) : !isRealtimeMode ? (
|
||||||
/>
|
<>
|
||||||
|
|
||||||
{/* Buttons */}
|
|
||||||
<View style={styles.buttonRow}>
|
|
||||||
{hasText || hasImages ? (
|
|
||||||
// Send button when text is entered or images are selected
|
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={handleSendMessage}
|
onPress={handleVoicePress}
|
||||||
disabled={!ws.isConnected || isProcessing}
|
disabled={!ws.isConnected}
|
||||||
style={[
|
style={[
|
||||||
styles.sendButton,
|
styles.voiceButton,
|
||||||
(!ws.isConnected || isProcessing) && styles.buttonDisabled,
|
!ws.isConnected && styles.buttonDisabled,
|
||||||
|
isRecording && styles.voiceButtonRecording,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<ArrowUp size={20} color="white" />
|
{isRecording ? (
|
||||||
|
<Square size={14} color="white" fill="white" />
|
||||||
|
) : (
|
||||||
|
<Mic size={20} color={theme.colors.foreground} />
|
||||||
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
) : !isRealtimeMode ? (
|
|
||||||
// Voice and Realtime buttons when no text and not in realtime mode
|
|
||||||
<>
|
|
||||||
{/* Voice recording button */}
|
|
||||||
<Pressable
|
|
||||||
onPress={handleVoicePress}
|
|
||||||
disabled={!ws.isConnected}
|
|
||||||
style={[
|
|
||||||
styles.voiceButton,
|
|
||||||
!ws.isConnected && styles.buttonDisabled,
|
|
||||||
isRecording && styles.voiceButtonRecording,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{isRecording ? (
|
|
||||||
<Square size={14} color="white" fill="white" />
|
|
||||||
) : (
|
|
||||||
<Mic size={20} color={theme.colors.foreground} />
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
|
|
||||||
{/* Realtime button */}
|
<Pressable
|
||||||
<Pressable
|
onPress={startRealtime}
|
||||||
onPress={startRealtime}
|
disabled={!ws.isConnected}
|
||||||
disabled={!ws.isConnected}
|
style={[
|
||||||
style={[
|
styles.realtimeButton,
|
||||||
styles.realtimeButton,
|
!ws.isConnected && styles.buttonDisabled,
|
||||||
!ws.isConnected && styles.buttonDisabled,
|
]}
|
||||||
]}
|
>
|
||||||
>
|
<AudioLines size={20} color={theme.colors.background} />
|
||||||
<AudioLines size={20} color={theme.colors.background} />
|
</Pressable>
|
||||||
</Pressable>
|
</>
|
||||||
</>
|
) : null}
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
@@ -400,9 +392,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
container: {
|
container: {
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
},
|
},
|
||||||
statusBarContainer: {
|
|
||||||
backgroundColor: theme.colors.background,
|
|
||||||
},
|
|
||||||
borderSeparator: {
|
borderSeparator: {
|
||||||
height: theme.borderWidth[1],
|
height: theme.borderWidth[1],
|
||||||
backgroundColor: theme.colors.border,
|
backgroundColor: theme.colors.border,
|
||||||
@@ -417,8 +406,8 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
inputContainer: {
|
inputContainer: {
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[4],
|
||||||
paddingVertical: BASE_VERTICAL_PADDING,
|
paddingVertical: theme.spacing[3],
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[3],
|
||||||
minHeight: FOOTER_HEIGHT,
|
minHeight: FOOTER_HEIGHT,
|
||||||
},
|
},
|
||||||
overlayContainer: {
|
overlayContainer: {
|
||||||
@@ -452,7 +441,26 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
inputRow: {
|
textInput: {
|
||||||
|
width: "100%",
|
||||||
|
paddingHorizontal: theme.spacing[4],
|
||||||
|
paddingVertical: theme.spacing[3],
|
||||||
|
backgroundColor: theme.colors.muted,
|
||||||
|
borderRadius: theme.borderRadius.lg,
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
fontSize: theme.fontSize.base,
|
||||||
|
},
|
||||||
|
buttonRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
},
|
||||||
|
leftButtonGroup: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
},
|
||||||
|
rightButtonGroup: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
@@ -463,20 +471,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
textInput: {
|
|
||||||
flex: 1,
|
|
||||||
paddingHorizontal: theme.spacing[4],
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
backgroundColor: theme.colors.muted,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
color: theme.colors.foreground,
|
|
||||||
fontSize: theme.fontSize.base,
|
|
||||||
},
|
|
||||||
buttonRow: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
|
||||||
sendButton: {
|
sendButton: {
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
|
|||||||
@@ -78,16 +78,13 @@ export function AgentStatusBar({ agentId }: AgentStatusBarProps) {
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Agent Status Indicator */}
|
{/* Agent Status Indicator - just a dot */}
|
||||||
<View style={styles.statusIndicator}>
|
<View
|
||||||
<View
|
style={[
|
||||||
style={[
|
styles.statusDot,
|
||||||
styles.statusDot,
|
{ backgroundColor: getStatusColor(agent.status) },
|
||||||
{ backgroundColor: getStatusColor(agent.status) },
|
]}
|
||||||
]}
|
/>
|
||||||
/>
|
|
||||||
<Text style={styles.statusText}>{getStatusLabel(agent.status)}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Mode selector modal */}
|
{/* Mode selector modal */}
|
||||||
<ModeSelectorModal
|
<ModeSelectorModal
|
||||||
@@ -105,9 +102,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[3],
|
gap: theme.spacing[3],
|
||||||
paddingHorizontal: theme.spacing[4],
|
|
||||||
paddingTop: theme.spacing[2],
|
|
||||||
paddingBottom: theme.spacing[1],
|
|
||||||
},
|
},
|
||||||
modeBadge: {
|
modeBadge: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
|
|||||||
Reference in New Issue
Block a user