mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 14:22:51 +00:00
improve detail screen
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
type InputConnectionDetails,
|
||||
} from '@/lib/secrets-manager';
|
||||
import { useTheme } from '@/lib/theme';
|
||||
import { useBottomTabPadding } from '@/lib/useBottomTabPadding';
|
||||
import { useBottomTabSpacing } from '@/lib/useBottomTabSpacing';
|
||||
|
||||
export default function TabsIndex() {
|
||||
return <Host />;
|
||||
@@ -45,7 +45,7 @@ function Host() {
|
||||
const sshConnMutation = useSshConnMutation({
|
||||
onConnectionProgress: (s) => setLastConnectionProgressEvent(s),
|
||||
});
|
||||
const { paddingBottom, onLayout } = useBottomTabPadding(12);
|
||||
const marginBottom = useBottomTabSpacing();
|
||||
const connectionForm = useAppForm({
|
||||
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
|
||||
defaultValues,
|
||||
@@ -86,10 +86,9 @@ function Host() {
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: theme.colors.background }}>
|
||||
<ScrollView
|
||||
contentContainerStyle={[{ paddingBottom }]}
|
||||
contentContainerStyle={[{ marginBottom }]}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
style={{ backgroundColor: theme.colors.background }}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
|
||||
@@ -12,14 +12,15 @@ import {
|
||||
useFocusEffect,
|
||||
} from 'expo-router';
|
||||
import React, { startTransition, useEffect, useRef, useState } from 'react';
|
||||
import { Dimensions, Platform, Pressable, Text, View } from 'react-native';
|
||||
import { Pressable, Text, View } from 'react-native';
|
||||
|
||||
import {
|
||||
SafeAreaView,
|
||||
useSafeAreaInsets,
|
||||
} from 'react-native-safe-area-context';
|
||||
KeyboardAvoidingView,
|
||||
KeyboardToolbar,
|
||||
} from 'react-native-keyboard-controller';
|
||||
import { useSshStore } from '@/lib/ssh-store';
|
||||
import { useTheme } from '@/lib/theme';
|
||||
import { useBottomTabSpacing } from '@/lib/useBottomTabSpacing';
|
||||
|
||||
export default function TabsShellDetail() {
|
||||
const [ready, setReady] = useState(false);
|
||||
@@ -104,37 +105,20 @@ function ShellDetail() {
|
||||
};
|
||||
}, [shell]);
|
||||
|
||||
const insets = useSafeAreaInsets();
|
||||
const estimatedTabBarHeight = Platform.select({
|
||||
ios: 49,
|
||||
android: 80,
|
||||
default: 56,
|
||||
});
|
||||
const windowH = Dimensions.get('window').height;
|
||||
const computeBottomExtra = (y: number, height: number) => {
|
||||
const extra = windowH - (y + height);
|
||||
return extra > 0 ? extra : 0;
|
||||
};
|
||||
|
||||
// Measure any bottom overlap (e.g., native tab bar) and add padding to avoid it
|
||||
const [bottomExtra, setBottomExtra] = useState(0);
|
||||
const marginBottom = useBottomTabSpacing();
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={['left', 'right']}
|
||||
onLayout={(e) => {
|
||||
const { y, height } = e.nativeEvent.layout;
|
||||
const extra = computeBottomExtra(y, height);
|
||||
if (extra !== bottomExtra) setBottomExtra(extra);
|
||||
}}
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'flex-start',
|
||||
backgroundColor: theme.colors.background,
|
||||
paddingTop: 2,
|
||||
paddingLeft: 8,
|
||||
paddingRight: 8,
|
||||
paddingBottom: insets.bottom + (bottomExtra || estimatedTabBarHeight),
|
||||
paddingBottom: 0,
|
||||
marginBottom,
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
@@ -158,6 +142,11 @@ function ShellDetail() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
behavior="height"
|
||||
keyboardVerticalOffset={210}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<XtermJsWebView
|
||||
ref={xtermRef}
|
||||
style={{ flex: 1 }}
|
||||
@@ -228,6 +217,13 @@ function ShellDetail() {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
<KeyboardToolbar
|
||||
offset={{
|
||||
opened: -80,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useTheme } from '@/lib/theme';
|
||||
|
||||
export default function TabsLayout() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<NativeTabs
|
||||
// common
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function ToolbarExample() {
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior="height"
|
||||
keyboardVerticalOffset={150}
|
||||
keyboardVerticalOffset={250}
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingHorizontal: 16,
|
||||
@@ -49,7 +49,11 @@ export default function ToolbarExample() {
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
<KeyboardToolbar />
|
||||
<KeyboardToolbar
|
||||
offset={{
|
||||
opened: -80,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -62,15 +66,21 @@ const TextInputAndLabel = (props: CustomTextInputProps) => {
|
||||
const { title, ...rest } = props;
|
||||
const [isFocused, setFocused] = useState(false);
|
||||
|
||||
const onFocus = useCallback<NonNullable<TextInputProps['onFocus']>>((e) => {
|
||||
const onFocus = useCallback<NonNullable<TextInputProps['onFocus']>>(
|
||||
(e) => {
|
||||
setFocused(true);
|
||||
props.onFocus?.(e);
|
||||
}, []);
|
||||
},
|
||||
[props],
|
||||
);
|
||||
|
||||
const onBlur = useCallback<NonNullable<TextInputProps['onBlur']>>((e) => {
|
||||
const onBlur = useCallback<NonNullable<TextInputProps['onBlur']>>(
|
||||
(e) => {
|
||||
setFocused(false);
|
||||
props.onBlur?.(e);
|
||||
}, []);
|
||||
},
|
||||
[props],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Redirect } from 'expo-router';
|
||||
|
||||
export default function RootRedirect() {
|
||||
return <Redirect href="/(test)" />;
|
||||
return <Redirect href="/(tabs)" />;
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Dimensions, Platform } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
type LayoutEvent = {
|
||||
nativeEvent: {
|
||||
layout: {
|
||||
y: number;
|
||||
height: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export function useBottomTabPadding(basePadding = 12) {
|
||||
const insets = useSafeAreaInsets();
|
||||
const windowH = Dimensions.get('window').height;
|
||||
const estimatedTabBarHeight = Platform.select({
|
||||
ios: 49,
|
||||
android: 80,
|
||||
default: 56,
|
||||
});
|
||||
const [bottomExtra, setBottomExtra] = React.useState(0);
|
||||
|
||||
const onLayout = React.useCallback(
|
||||
(e: LayoutEvent) => {
|
||||
const { y, height } = e.nativeEvent.layout;
|
||||
const extra = windowH - (y + height);
|
||||
setBottomExtra(extra > 0 ? extra : 0);
|
||||
},
|
||||
[windowH],
|
||||
);
|
||||
|
||||
const paddingBottom =
|
||||
basePadding + insets.bottom + (bottomExtra || estimatedTabBarHeight!);
|
||||
return { paddingBottom, onLayout } as const;
|
||||
}
|
||||
12
apps/mobile/src/lib/useBottomTabSpacing.ts
Normal file
12
apps/mobile/src/lib/useBottomTabSpacing.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
export function useBottomTabSpacing() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const estimatedTabBarHeight = Platform.select({
|
||||
ios: 49,
|
||||
android: 80,
|
||||
default: 56,
|
||||
});
|
||||
return insets.bottom + estimatedTabBarHeight;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ window.onload = () => {
|
||||
window.addEventListener('message', handler);
|
||||
|
||||
// Initial handshake (send once)
|
||||
setTimeout(() => sendToRn({ type: 'initialized' }), 50);
|
||||
setTimeout(() => sendToRn({ type: 'initialized' }), 100);
|
||||
} catch (e) {
|
||||
sendToRn({
|
||||
type: 'debug',
|
||||
|
||||
Reference in New Issue
Block a user