refactor(app): restructure settings with modular section navigation

This commit is contained in:
Mohamed Boudra
2026-04-18 14:21:40 +07:00
parent 63a46acaf1
commit 5c3cc99d8c
41 changed files with 2526 additions and 2317 deletions
@@ -1,31 +1,35 @@
import type { ReactNode } from "react";
import { Pressable, Text } from "react-native";
import { Pressable } from "react-native";
import { router } from "expo-router";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { ArrowLeft } from "lucide-react-native";
import { ScreenHeader } from "./screen-header";
import { ScreenTitle } from "./screen-title";
interface BackHeaderProps {
title?: string;
titleAccessory?: ReactNode;
rightContent?: ReactNode;
onBack?: () => void;
}
export function BackHeader({ title, rightContent, onBack }: BackHeaderProps) {
export function BackHeader({ title, titleAccessory, rightContent, onBack }: BackHeaderProps) {
const { theme } = useUnistyles();
return (
<ScreenHeader
left={
<>
<Pressable onPress={onBack ?? (() => router.back())} style={styles.backButton}>
<Pressable
onPress={onBack ?? (() => router.back())}
style={styles.backButton}
accessibilityRole="button"
accessibilityLabel="Back"
>
<ArrowLeft size={theme.iconSize.lg} color={theme.colors.foregroundMuted} />
</Pressable>
{title && (
<Text style={styles.title} numberOfLines={1}>
{title}
</Text>
)}
{title && <ScreenTitle>{title}</ScreenTitle>}
{titleAccessory}
</>
}
right={rightContent}
@@ -45,13 +49,4 @@ const styles = StyleSheet.create((theme) => ({
},
borderRadius: theme.borderRadius.lg,
},
title: {
flex: 1,
fontSize: theme.fontSize.lg,
fontWeight: {
xs: theme.fontWeight.semibold,
md: "400",
},
color: theme.colors.foreground,
},
}));
@@ -0,0 +1,13 @@
import type { ReactNode } from "react";
import { View } from "react-native";
import { headerIconSlotStyle } from "./header-toggle-button";
/**
* Non-interactive icon slot sitting at the start of a screen header's left
* cluster. Shares the same padding + border-radius as `HeaderToggleButton` so
* decorative headers (settings sections, host detail) line up with the sidebar
* toggle across screens.
*/
export function HeaderIconBadge({ children }: { children: ReactNode }) {
return <View style={headerIconSlotStyle.slot}>{children}</View>;
}
@@ -51,7 +51,7 @@ export function HeaderToggleButton({
onPress={(e) => {
onPress(e);
}}
style={[styles.button, style]}
style={[headerIconSlotStyle.slot, style]}
>
{typeof children === "function"
? (state: { pressed: boolean; hovered?: boolean }) =>
@@ -68,14 +68,17 @@ export function HeaderToggleButton({
);
}
const styles = StyleSheet.create((theme) => ({
button: {
export const headerIconSlotStyle = StyleSheet.create((theme) => ({
slot: {
padding: {
xs: theme.spacing[3],
md: theme.spacing[2],
},
borderRadius: theme.borderRadius.lg,
},
}));
const styles = StyleSheet.create((theme) => ({
tooltipRow: {
flexDirection: "row",
alignItems: "center",
@@ -1,8 +1,9 @@
import type { ReactNode } from "react";
import { Text, View, type StyleProp, type ViewStyle } from "react-native";
import { View, type StyleProp, type ViewStyle } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { PanelLeft } from "lucide-react-native";
import { ScreenHeader } from "./screen-header";
import { ScreenTitle } from "./screen-title";
import { HeaderToggleButton } from "./header-toggle-button";
import { usePanelStore } from "@/stores/panel-store";
import { useIsCompactFormFactor } from "@/constants/layout";
@@ -83,11 +84,7 @@ export function MenuHeader({ title, rightContent, borderless }: MenuHeaderProps)
left={
<>
<SidebarMenuToggle />
{title && (
<Text style={styles.title} numberOfLines={1}>
{title}
</Text>
)}
{title && <ScreenTitle>{title}</ScreenTitle>}
</>
}
right={rightContent}
@@ -101,15 +98,6 @@ const styles = StyleSheet.create((theme) => ({
left: {
gap: theme.spacing[2],
},
title: {
flex: 1,
fontSize: theme.fontSize.base,
fontWeight: {
xs: "400",
md: "300",
},
color: theme.colors.foreground,
},
mobileMenuIcon: {
width: MOBILE_MENU_LINE_WIDTH,
height: 12,
@@ -0,0 +1,35 @@
import type { ReactNode } from "react";
import { Text, type StyleProp, type TextStyle } from "react-native";
import { StyleSheet } from "react-native-unistyles";
interface ScreenTitleProps {
children: ReactNode;
numberOfLines?: number;
testID?: string;
style?: StyleProp<TextStyle>;
}
/**
* Canonical screen title for use inside `ScreenHeader`. One typography, one
* color, responsive weight. Leading icons are siblings (HeaderToggleButton,
* HeaderIconBadge) — never nested inside this component.
*/
export function ScreenTitle({ children, numberOfLines = 1, testID, style }: ScreenTitleProps) {
return (
<Text style={[styles.text, style]} numberOfLines={numberOfLines} testID={testID}>
{children}
</Text>
);
}
const styles = StyleSheet.create((theme) => ({
text: {
flexShrink: 1,
fontSize: theme.fontSize.base,
fontWeight: {
xs: "400",
md: "300",
},
color: theme.colors.foreground,
},
}));