refactor headers into shared screen shell

This commit is contained in:
Mohamed Boudra
2025-11-15 20:29:23 +01:00
parent 7bdafee818
commit b39497d08f
4 changed files with 109 additions and 105 deletions
@@ -1,71 +1,45 @@
import { View, Pressable, Text } from "react-native"; import type { ReactNode } from "react";
import { Pressable, Text } from "react-native";
import { router } from "expo-router"; import { router } from "expo-router";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { ArrowLeft } from "lucide-react-native"; import { ArrowLeft } from "lucide-react-native";
import { ScreenHeader } from "./screen-header";
interface BackHeaderProps { interface BackHeaderProps {
title?: string; title?: string;
rightContent?: React.ReactNode; rightContent?: ReactNode;
} }
export function BackHeader({ title, rightContent }: BackHeaderProps) { export function BackHeader({ title, rightContent }: BackHeaderProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const insets = useSafeAreaInsets();
return ( return (
<View style={styles.header}> <ScreenHeader
<View style={{ paddingTop: insets.top + 12 }}> left={
<View style={styles.headerRow}> <>
{/* Left side - Back button */} <Pressable
<View style={styles.headerLeft}> onPress={() => router.back()}
<Pressable style={styles.backButton}
onPress={() => router.back()} >
style={styles.backButton} <ArrowLeft size={20} color={theme.colors.foreground} />
> </Pressable>
<ArrowLeft size={20} color={theme.colors.foreground} /> {title && (
</Pressable> <Text style={styles.title} numberOfLines={1}>
{title && ( {title}
<Text style={styles.title} numberOfLines={1}> </Text>
{title} )}
</Text> </>
)} }
</View> right={rightContent}
leftStyle={styles.left}
{/* Right side */} />
<View style={styles.headerRight}>
{rightContent}
</View>
</View>
</View>
</View>
); );
} }
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
header: { left: {
backgroundColor: theme.colors.background,
},
headerRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: theme.spacing[3],
paddingBottom: theme.spacing[2],
borderBottomWidth: theme.borderWidth[1],
borderBottomColor: theme.colors.border,
},
headerLeft: {
flex: 1,
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[3], gap: theme.spacing[3],
}, },
headerRight: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
},
backButton: { backButton: {
padding: theme.spacing[3], padding: theme.spacing[3],
borderRadius: theme.borderRadius.lg, borderRadius: theme.borderRadius.lg,
@@ -1,8 +1,8 @@
import { View, Pressable } from "react-native"; import { Pressable } from "react-native";
import { router } from "expo-router"; import { router } from "expo-router";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { Settings, MessageSquare, Plus } from "lucide-react-native"; import { Settings, MessageSquare, Plus } from "lucide-react-native";
import { ScreenHeader } from "./screen-header";
interface HomeHeaderProps { interface HomeHeaderProps {
onCreateAgent: () => void; onCreateAgent: () => void;
@@ -10,64 +10,35 @@ interface HomeHeaderProps {
export function HomeHeader({ onCreateAgent }: HomeHeaderProps) { export function HomeHeader({ onCreateAgent }: HomeHeaderProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const insets = useSafeAreaInsets();
return ( return (
<View style={styles.header}> <ScreenHeader
<View style={{ paddingTop: insets.top + 12 }}> left={
<View style={styles.headerRow}> <Pressable
{/* Left side - Settings */} onPress={() => router.push("/settings")}
<View style={styles.headerLeft}> style={styles.iconButton}
<Pressable >
onPress={() => router.push("/settings")} <Settings size={20} color={theme.colors.foreground} />
style={styles.iconButton} </Pressable>
> }
<Settings size={20} color={theme.colors.foreground} /> right={
</Pressable> <>
</View> <Pressable
onPress={() => router.push("/orchestrator")}
{/* Right side - Activity and New Agent */} style={styles.iconButton}
<View style={styles.headerRight}> >
<Pressable <MessageSquare size={20} color={theme.colors.foreground} />
onPress={() => router.push("/orchestrator")} </Pressable>
style={styles.iconButton} <Pressable onPress={onCreateAgent} style={styles.iconButton}>
> <Plus size={20} color={theme.colors.foreground} />
<MessageSquare size={20} color={theme.colors.foreground} /> </Pressable>
</Pressable> </>
<Pressable onPress={onCreateAgent} style={styles.iconButton}> }
<Plus size={20} color={theme.colors.foreground} /> />
</Pressable>
</View>
</View>
</View>
</View>
); );
} }
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
header: {
backgroundColor: theme.colors.background,
},
headerRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: theme.spacing[3],
paddingBottom: theme.spacing[2],
borderBottomWidth: theme.borderWidth[1],
borderBottomColor: theme.colors.border,
},
headerLeft: {
flex: 1,
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
},
headerRight: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
},
iconButton: { iconButton: {
padding: theme.spacing[3], padding: theme.spacing[3],
borderRadius: theme.borderRadius.lg, borderRadius: theme.borderRadius.lg,
@@ -0,0 +1,58 @@
import type { ReactNode } from "react";
import { View, type StyleProp, type ViewStyle } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
interface ScreenHeaderProps {
left?: ReactNode;
right?: ReactNode;
leftStyle?: StyleProp<ViewStyle>;
rightStyle?: StyleProp<ViewStyle>;
}
/**
* Shared frame for the home/back headers so we only maintain padding, border,
* and safe-area logic in one place.
*/
export function ScreenHeader({ left, right, leftStyle, rightStyle }: ScreenHeaderProps) {
const insets = useSafeAreaInsets();
const { theme } = useUnistyles();
return (
<View style={styles.header}>
<View style={[styles.inner, { paddingTop: insets.top + 12 }]}>
<View style={styles.row}>
<View style={[styles.left, leftStyle]}>{left}</View>
<View style={[styles.right, rightStyle]}>{right}</View>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create((theme) => ({
header: {
backgroundColor: theme.colors.background,
},
inner: {},
row: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: theme.spacing[3],
paddingBottom: theme.spacing[2],
borderBottomWidth: theme.borderWidth[1],
borderBottomColor: theme.colors.border,
},
left: {
flex: 1,
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
},
right: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
},
}));
+2 -1
View File
@@ -86,5 +86,6 @@
- Split the server build/typecheck configs so the typechecker can include the app stream helpers without breaking builds, added path aliases for `@server/*`, and tightened the shared stream types (status unions + `isAgentToolCallItem`) so the server e2e suites can import them safely. Cleaned up every failing app/server test by narrowing tool-call payloads via the helper, updated the harness + Codex/Claude specs, and re-ran `npm run typecheck` (app + server) to green. - Split the server build/typecheck configs so the typechecker can include the app stream helpers without breaking builds, added path aliases for `@server/*`, and tightened the shared stream types (status unions + `isAgentToolCallItem`) so the server e2e suites can import them safely. Cleaned up every failing app/server test by narrowing tool-call payloads via the helper, updated the harness + Codex/Claude specs, and re-ran `npm run typecheck` (app + server) to green.
- [x] audit codebase for unecessary untyped code, hacks, casts, and `any`, type things properly - [x] audit codebase for unecessary untyped code, hacks, casts, and `any`, type things properly
- Tightened the Codex rollout parser typings so permission requests, rollout/event payloads, plan arguments, and JSON helpers all operate on well-defined TypeScript structures instead of `any`. Added explicit payload/entry guards, removed unsafe casts throughout `packages/server/src/server/agent/providers/codex-agent.ts`, and re-ran `npm run typecheck --workspace=@voice-dev/server` to validate the stricter typing. - Tightened the Codex rollout parser typings so permission requests, rollout/event payloads, plan arguments, and JSON helpers all operate on well-defined TypeScript structures instead of `any`. Added explicit payload/entry guards, removed unsafe casts throughout `packages/server/src/server/agent/providers/codex-agent.ts`, and re-ran `npm run typecheck --workspace=@voice-dev/server` to validate the stricter typing.
- [ ] audit codebase for duplicated code, clean it up - [x] audit codebase for duplicated code, clean it up
- Consolidated the duplicated home/back header layouts into a shared `ScreenHeader` component so safe-area padding, borders, and spacing live in one place; both headers now only define their unique button/title content. Ran `npm run typecheck --workspace=@voice-dev/app` to confirm the refactor stays type-safe.
- [ ] rename the project to Paseo, including the Expo app, package names etc. - [ ] rename the project to Paseo, including the Expo app, package names etc.