Kantongku: bottom nav floating premium (rounded + shadow + pill aktif + FAB gradient)
This commit is contained in:
+47
-17
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user