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:
@@ -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,
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user