This commit is contained in:
EthanShoeDev
2025-09-15 23:31:34 -04:00
parent db5eb2ff8d
commit 6465407d5a
2 changed files with 50 additions and 91 deletions

View File

@@ -8,9 +8,16 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { formatDistanceToNow } from 'date-fns';
import { Link, Stack, useRouter } from 'expo-router';
import React from 'react';
import { Modal, Pressable, StyleSheet, Text, View } from 'react-native';
import {
ActionSheetIOS,
Modal,
Platform,
Pressable,
StyleSheet,
Text,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { IconSegmentedControl } from '@/components/icon-segmented-control';
import { preferences } from '@/lib/preferences';
import {
closeSshShellAndInvalidateQuery,
@@ -38,7 +45,7 @@ function ShellContent() {
<View style={{ flex: 1 }}>
<Stack.Screen
options={{
headerRight: () => <TopBarToggle />,
headerRight: () => <HeaderViewModeButton />,
}}
/>
{!connectionsQuery.data ? (
@@ -241,7 +248,6 @@ function EmptyState() {
<Link href="/" style={styles.link}>
Go to Hosts
</Link>
<TopBarToggle />
</View>
);
}
@@ -332,34 +338,50 @@ function ActionsSheet({
);
}
function TopBarToggle() {
function HeaderViewModeButton() {
const theme = useTheme();
const iconStyle = (isActive: boolean) => ({
color: isActive ? theme.colors.textPrimary : theme.colors.muted,
});
const [shellListViewMode, setShellListViewMode] =
preferences.shellListViewMode.useShellListViewModePref();
const icon = shellListViewMode === 'flat' ? 'list' : 'git-branch';
const accessibilityLabel =
shellListViewMode === 'flat'
? 'Switch to grouped view'
: 'Switch to flat list view';
const handleToggle = React.useCallback(() => {
const nextMode = shellListViewMode === 'flat' ? 'grouped' : 'flat';
setShellListViewMode(nextMode);
}, [setShellListViewMode, shellListViewMode]);
const handleLongPress = React.useCallback(() => {
if (Platform.OS !== 'ios') return;
ActionSheetIOS.showActionSheetWithOptions(
{
title: 'View Mode',
options: ['Flat list', 'Grouped by connection', 'Cancel'],
cancelButtonIndex: 2,
},
(buttonIndex) => {
if (buttonIndex === 0) setShellListViewMode('flat');
if (buttonIndex === 1) setShellListViewMode('grouped');
},
);
}, [setShellListViewMode]);
return (
<IconSegmentedControl
values={[
{
child: ({ isActive }) => (
<Ionicons name="list" size={18} style={iconStyle(isActive)} />
),
accessibilityLabel: 'Flat list',
value: 'flat',
},
{
child: ({ isActive }) => (
<Ionicons name="git-branch" size={18} style={iconStyle(isActive)} />
),
accessibilityLabel: 'Grouped by connection',
value: 'grouped',
},
]}
value={shellListViewMode}
onChange={setShellListViewMode}
/>
<Pressable
onPress={handleToggle}
onLongPress={handleLongPress}
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
accessibilityRole="button"
accessibilityLabel={accessibilityLabel}
style={({ pressed }) => ({
opacity: pressed ? 0.4 : 1,
})}
>
<Ionicons name={icon} size={22} color={theme.colors.textPrimary} />
</Pressable>
);
}

View File

@@ -1,63 +0,0 @@
import React from 'react';
import { Pressable, View } from 'react-native';
import { useTheme } from '@/lib/theme';
export function IconSegmentedControl<T extends string>(props: {
values: {
child: (props: { isActive: boolean }) => React.ReactNode;
accessibilityLabel: string;
value: T;
}[];
value: T;
onChange: (value: T) => void;
}) {
const theme = useTheme();
return (
<View
style={{
flexDirection: 'row',
backgroundColor: theme.colors.surface,
// borderWidth: 1,
// borderColor: theme.colors.border,
borderRadius: 10,
overflow: 'hidden',
}}
>
{props.values.map((item) => (
<Pressable
key={item.value}
accessibilityLabel={item.accessibilityLabel}
onPress={() => {
console.log('DEBUG onPress', {
currentValue: props.value,
itemValue: item.value,
});
if (props.values.length === 2) {
const newValue = props.values.find(
(v) => v.value !== props.value,
)!.value;
console.log('DEBUG newValue', newValue);
props.onChange(newValue);
} else {
props.onChange(item.value);
}
}}
style={[
{
paddingHorizontal: 10,
paddingVertical: 6,
alignItems: 'center',
justifyContent: 'center',
},
props.value === item.value && {
backgroundColor: theme.colors.inputBackground,
},
]}
>
{item.child({ isActive: props.value === item.value })}
</Pressable>
))}
</View>
);
}