Kantongku: bottom nav floating premium (rounded + shadow + pill aktif + FAB gradient)

This commit is contained in:
Bro
2026-08-16 21:02:45 +07:00
parent a627d5bccc
commit 5d301c0b03
6 changed files with 58 additions and 27 deletions
+47 -17
View File
@@ -1,7 +1,9 @@
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Pressable, StyleSheet, View } from 'react-native'; import { Pressable, StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { colors } from '../theme/colors'; import { colors } from '../theme/colors';
import { DashboardScreen } from '../screens/DashboardScreen'; import { DashboardScreen } from '../screens/DashboardScreen';
import { HistoryScreen } from '../screens/HistoryScreen'; import { HistoryScreen } from '../screens/HistoryScreen';
@@ -48,11 +50,13 @@ const tabIcon =
(focusedName: IoniconName, unfocusedName: IoniconName) => (focusedName: IoniconName, unfocusedName: IoniconName) =>
({ color, size, focused }: { color: string; size: number; focused: boolean }) => ({ color, size, focused }: { color: string; size: number; focused: boolean }) =>
( (
<View style={[styles.tabIconWrap, focused && styles.tabIconWrapActive]}>
<Ionicons <Ionicons
name={focused ? focusedName : unfocusedName} name={focused ? focusedName : unfocusedName}
size={size} size={size}
color={color} color={color}
/> />
</View>
); );
/** Tombol "+" melingkar di tengah tab bar (FAB) */ /** Tombol "+" melingkar di tengah tab bar (FAB) */
@@ -70,21 +74,30 @@ function FabButton({
style={({ pressed }) => [styles.fabWrapper, pressed && styles.fabPressed]} style={({ pressed }) => [styles.fabWrapper, pressed && styles.fabPressed]}
hitSlop={8} hitSlop={8}
> >
<View style={[styles.fab, focused && styles.fabActive]}> <LinearGradient
colors={[colors.primary, colors.primaryDark]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={[styles.fab, focused && styles.fabActive]}
>
<Ionicons name="add" size={32} color={colors.white} /> <Ionicons name="add" size={32} color={colors.white} />
</View> </LinearGradient>
</Pressable> </Pressable>
); );
} }
function MainTabs() { function MainTabs() {
const insets = useSafeAreaInsets();
return ( return (
<Tab.Navigator <Tab.Navigator
screenOptions={{ screenOptions={{
tabBarActiveTintColor: colors.primary, tabBarActiveTintColor: colors.primary,
tabBarInactiveTintColor: colors.textMuted, tabBarInactiveTintColor: colors.textMuted,
tabBarLabelStyle: styles.tabLabel, tabBarLabelStyle: styles.tabLabel,
tabBarStyle: styles.tabBar, tabBarStyle: [
styles.tabBar,
{ bottom: insets.bottom > 0 ? insets.bottom + 10 : 14 },
],
headerTitleStyle: { fontWeight: '700', color: colors.text }, headerTitleStyle: { fontWeight: '700', color: colors.text },
headerStyle: { backgroundColor: colors.background }, headerStyle: { backgroundColor: colors.background },
headerShadowVisible: false, headerShadowVisible: false,
@@ -200,40 +213,57 @@ export function RootNavigator({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
tabBar: { tabBar: {
position: 'absolute',
left: 14,
right: 14,
height: 64,
borderRadius: 26,
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderTopColor: colors.border, borderTopWidth: 0,
borderTopWidth: 1, paddingTop: 6,
height: 62,
paddingTop: 4,
paddingBottom: 6, paddingBottom: 6,
shadowColor: '#0F172A',
shadowOpacity: 0.14,
shadowRadius: 18,
shadowOffset: { width: 0, height: 8 },
elevation: 12,
}, },
tabLabel: { tabLabel: {
fontSize: 10, fontSize: 10,
fontWeight: '600', fontWeight: '600',
}, },
tabIconWrap: {
width: 40,
height: 30,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
},
tabIconWrapActive: {
backgroundColor: colors.primarySoft,
},
fabWrapper: { fabWrapper: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
top: -24, top: -26,
}, },
fabPressed: { opacity: 0.85 }, fabPressed: { opacity: 0.85 },
fab: { fab: {
width: 58, width: 60,
height: 58, height: 60,
borderRadius: 29, borderRadius: 30,
backgroundColor: colors.primary,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderWidth: 4, borderWidth: 4,
borderColor: colors.surface, borderColor: colors.surface,
shadowColor: colors.primaryDark, shadowColor: colors.primaryDark,
shadowOpacity: 0.35, shadowOpacity: 0.4,
shadowRadius: 10, shadowRadius: 12,
shadowOffset: { width: 0, height: 5 }, shadowOffset: { width: 0, height: 6 },
elevation: 8, elevation: 10,
}, },
fabActive: { fabActive: {
backgroundColor: colors.primaryDark, shadowOpacity: 0.55,
}, },
}); });
+2 -1
View File
@@ -243,7 +243,7 @@ export function AddTransactionScreen() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
flex: { flex: 1, backgroundColor: colors.background }, flex: { flex: 1, backgroundColor: colors.background },
container: { flex: 1 }, container: { flex: 1 },
content: { padding: 16, paddingBottom: 32 }, content: { padding: 16, paddingBottom: 110 },
toggleRow: { toggleRow: {
flexDirection: 'row', flexDirection: 'row',
backgroundColor: colors.surface, backgroundColor: colors.surface,
@@ -336,6 +336,7 @@ const styles = StyleSheet.create({
noteInput: { paddingVertical: 10, fontSize: 14, color: colors.text }, noteInput: { paddingVertical: 10, fontSize: 14, color: colors.text },
saveBar: { saveBar: {
padding: 16, padding: 16,
paddingBottom: 84,
backgroundColor: colors.background, backgroundColor: colors.background,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.border, borderTopColor: colors.border,
+1 -1
View File
@@ -718,7 +718,7 @@ const styles = StyleSheet.create({
overflow: 'hidden', overflow: 'hidden',
}, },
container: { flex: 1 }, container: { flex: 1 },
content: { paddingBottom: 32 }, content: { paddingBottom: 110 },
header: { paddingHorizontal: 16, paddingTop: 12, paddingBottom: 8, marginBottom: 12 }, header: { paddingHorizontal: 16, paddingTop: 12, paddingBottom: 8, marginBottom: 12 },
headerRow: { headerRow: {
flexDirection: 'row', flexDirection: 'row',
+1 -1
View File
@@ -351,7 +351,7 @@ function DonutChart({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.background }, container: { flex: 1, backgroundColor: colors.background },
listContent: { paddingBottom: 32 }, listContent: { paddingBottom: 110 },
card: { card: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 16, borderRadius: 16,
+1 -1
View File
@@ -333,7 +333,7 @@ function ComparisonCard({ current, previous }: { current: number; previous: numb
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.background }, container: { flex: 1, backgroundColor: colors.background },
content: { padding: 16, paddingBottom: 32 }, content: { padding: 16, paddingBottom: 110 },
card: { card: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 16, borderRadius: 16,
+1 -1
View File
@@ -595,7 +595,7 @@ export function SettingsScreen() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.background }, container: { flex: 1, backgroundColor: colors.background },
content: { padding: 16, paddingBottom: 32 }, content: { padding: 16, paddingBottom: 110 },
sectionTitle: { sectionTitle: {
fontSize: 16, fontSize: 16,
fontWeight: '700', fontWeight: '700',