feat(app): grouped project settings with docs links + scrollbar fix (#837)

* feat(app): group project settings into Worktree / Scripts / Metadata

Adds SettingsGroup component (title + info tooltip + trailing slot) and
restructures the project settings screen into three categorized groups
with explanatory tooltips. Metadata prompt labels drop the "prompt"
suffix now that they live under a "Metadata generation" group, and
placeholders read as example instructions a real user would write.
Applies useWebScrollbarStyle to the settings ScrollViews.

* Add ExternalLink component and docs links to project settings
This commit is contained in:
Mohamed Boudra
2026-05-09 19:31:19 +08:00
committed by GitHub
parent c34fb26359
commit ca503c1560
5 changed files with 296 additions and 52 deletions
@@ -0,0 +1,94 @@
import { useMemo, type ReactNode } from "react";
import { Pressable, Text, View, type StyleProp, type ViewStyle } from "react-native";
import { Info } from "lucide-react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
interface SettingsGroupProps {
title: string;
info?: ReactNode;
trailing?: ReactNode;
testID?: string;
style?: StyleProp<ViewStyle>;
children: ReactNode;
}
/**
* Top-level grouping above one or more SettingsSection blocks. Use when a
* settings screen has more than one logical area — the group title carries the
* category, the optional info tooltip explains it, and the inner sections keep
* their muted iOS-style labels.
*/
export function SettingsGroup({
title,
info,
trailing,
testID,
style,
children,
}: SettingsGroupProps) {
const { theme } = useUnistyles();
const groupStyle = useMemo(() => [styles.group, style], [style]);
return (
<View style={groupStyle} testID={testID}>
<View style={styles.header}>
<View style={styles.titleRow}>
<Text style={styles.title}>{title}</Text>
{info ? (
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile>
<TooltipTrigger asChild>
<Pressable
accessibilityRole="button"
accessibilityLabel={`About ${title}`}
testID={testID ? `${testID}-info` : undefined}
hitSlop={8}
style={styles.infoButton}
>
<Info size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
</Pressable>
</TooltipTrigger>
<TooltipContent side="top" align="start" offset={8}>
<Text style={styles.tooltipText}>{info}</Text>
</TooltipContent>
</Tooltip>
) : null}
</View>
{trailing}
</View>
{children}
</View>
);
}
const styles = StyleSheet.create((theme) => ({
group: {
marginBottom: theme.spacing[8],
},
header: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
gap: theme.spacing[2],
marginBottom: theme.spacing[4],
},
titleRow: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
},
title: {
color: theme.colors.foreground,
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
},
infoButton: {
padding: theme.spacing[1],
marginLeft: -theme.spacing[1],
},
tooltipText: {
color: theme.colors.foreground,
fontSize: theme.fontSize.sm,
maxWidth: 280,
lineHeight: theme.fontSize.sm * 1.4,
},
}));
@@ -8,6 +8,11 @@ interface SettingsSectionProps {
trailing?: ReactNode;
testID?: string;
style?: StyleProp<ViewStyle>;
/**
* Drops the section's bottom margin. Use when the section is the last child
* of a SettingsGroup, so the group's own bottom margin owns the trailing gap.
*/
flush?: boolean;
children: ReactNode;
}
@@ -21,9 +26,13 @@ export function SettingsSection({
trailing,
testID,
style,
flush,
children,
}: SettingsSectionProps) {
const sectionStyle = useMemo(() => [settingsStyles.section, style], [style]);
const sectionStyle = useMemo(
() => [settingsStyles.section, flush ? styles.flush : null, style],
[flush, style],
);
return (
<View style={sectionStyle} testID={testID}>
<View style={styles.header}>
@@ -47,4 +56,7 @@ const styles = StyleSheet.create((theme) => ({
content: {
gap: theme.spacing[3],
},
flush: {
marginBottom: 0,
},
}));