This commit is contained in:
oneao committed 2026-04-14 18:08:22 +08:00
1 parent 157c84b0b4
commit c7222a346d
7 files changed
+190 -101

No files matched your search

+2 -7
View File
@@ -1,10 +1,5 @@
import React from "react"; import React from "react";
import { import { CircleDollarSignIcon, ChartNoAxesColumnIcon, PlusIcon } from "lucide-react-native";
CircleDollarSignIcon,
ChartNoAxesColumnIcon,
LayoutGridIcon,
PlusIcon,
} from "lucide-react-native";
import ModuleLayout from "@/layouts/ModuleLayout"; import ModuleLayout from "@/layouts/ModuleLayout";
const tabs = [ const tabs = [
@@ -15,7 +10,7 @@ const tabs = [
}, },
{ {
id: "", id: "/money/add",
icon: PlusIcon, icon: PlusIcon,
isCenter: true, isCenter: true,
}, },
@@ -0,0 +1,10 @@
import React from "react";
import { View, Text } from "react-native";
export default function Add() {
return (
<View className="flex-1">
<Text className="text-base text-black">ADD</Text>
</View>
);
}
@@ -0,0 +1,10 @@
import React from "react";
import { View, Text } from "react-native";
export default function Ana() {
return (
<View className="flex-1">
<Text className="text-base text-black">Ana</Text>
</View>
);
}
+10 -1
View File
@@ -1,10 +1,19 @@
import React from "react"; import React from "react";
import { View, Text, ScrollView } from "react-native"; import { View, Text, ScrollView } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function Home() { export default function Home() {
const insets = useSafeAreaInsets();
return ( return (
<View className="flex-1"> <View className="flex-1">
<ScrollView className="flex-1 px-4 py-2"> <ScrollView
className="flex-1 px-4 py-2"
contentContainerStyle={{
paddingTop: 56 + insets.top + 20,
paddingBottom: 56 + insets.bottom + 20,
}}
>
<Text className="text-xl font-semibold mb-4">Money Home</Text> <Text className="text-xl font-semibold mb-4">Money Home</Text>
{Array.from({ length: 30 }).map((_, i) => ( {Array.from({ length: 30 }).map((_, i) => (
@@ -3,21 +3,34 @@ import { View, Text, StyleProp, ViewStyle, Pressable, StyleSheet } from "react-n
import { cn } from "@/utils/cn"; import { cn } from "@/utils/cn";
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated"; import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated";
import { Shadow } from "react-native-shadow-2"; import { Shadow } from "react-native-shadow-2";
import { ChevronLeft, SlidersHorizontal, CirclePower } from "lucide-react-native"; import { ChevronLeft, SlidersHorizontal, CirclePower, LucideIcon } from "lucide-react-native";
interface NavbarProps { interface NavbarProps {
title?: string; title?: string;
titleIcon?: LucideIcon;
showBack?: boolean; showBack?: boolean;
onBack?: () => void; onBack?: () => void;
onClose?: () => void;
className?: string; className?: string;
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
} }
const AnimatedView = Animated.View; const AnimatedView = Animated.View;
export default function Navbar({ title, showBack = true, onBack, className, style }: NavbarProps) { export default function Navbar({
title,
titleIcon,
showBack = true,
onBack,
onClose,
className,
style,
}: NavbarProps) {
const TitleIcon = titleIcon;
// 返回按钮 // 返回按钮
const scale = useSharedValue(1); const scale = useSharedValue(1);
const backAnimatedStyle = useAnimatedStyle(() => ({ const backAnimatedStyle = useAnimatedStyle(() => ({
@@ -39,56 +52,77 @@ export default function Navbar({ title, showBack = true, onBack, className, styl
})); }));
return ( return (
// <View
// pointerEvents="box-none"
// className={cn("absolute top-0 left-0 right-0", className)}
// style={[style]}
// >
<View <View
className={cn("flex-row gap-3 items-center justify-between mx-3", className)} className={cn("flex-row gap-3 items-center justify-between mx-3", className)}
style={[ style={[
{ {
...style, ...style,
height: 48
}, },
]} ]}
> >
{showBack && ( <View className="flex-1 flex-row gap-3">
<Pressable {showBack && (
onPress={onBack} <Pressable
onPressIn={() => { onPress={onBack}
scale.value = withSpring(0.8); onPressIn={() => {
}} scale.value = withSpring(0.8);
onPressOut={() => { }}
scale.value = withSpring(1); onPressOut={() => {
}} scale.value = withSpring(1);
> }}
<AnimatedView style={backAnimatedStyle}> >
<Shadow <AnimatedView style={backAnimatedStyle}>
distance={20} <Shadow
startColor="rgba(0,0,0,0.035)" distance={20}
endColor="rgba(255,255,255,0)" startColor="rgba(0,0,0,0.04)"
offset={[0, 0]} endColor="rgba(0,0,0,0)"
paintInside={false} offset={[0, 0]}
safeRender paintInside={false}
style={styles.iconButtonShadow} safeRender
> style={styles.iconButtonShadow}
<ChevronLeft size={24} /> >
</Shadow> <ChevronLeft size={24} />
</AnimatedView> </Shadow>
</Pressable> </AnimatedView>
)} </Pressable>
)}
{title && ( {title && (
<View className="flex-1"> <Shadow
<Text className="text-xl font-semibold tracking-tight text-black/90">{title}</Text> distance={20}
</View> startColor="rgba(0,0,0,0.04)"
)} endColor="rgba(0,0,0,0)"
offset={[0, 0]}
paintInside={false}
safeRender
style={{
...styles.titleShadown,
alignContent: "center",
justifyContent: "center",
maxWidth: 180,
gap: 4,
}}
>
{TitleIcon && <TitleIcon size={18} color="#000" />}
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={{
fontSize: 18,
fontWeight: "600",
color: "#111",
}}
>
{title}
</Text>
</Shadow>
)}
</View>
<Shadow <Shadow
distance={20} distance={20}
startColor="rgba(0,0,0,0.035)" startColor="rgba(0,0,0,0.04)"
endColor="rgba(255,255,255,0)" endColor="rgba(0,0,0,0)"
offset={[0, 0]} offset={[0, 0]}
paintInside={false} paintInside={false}
safeRender safeRender
@@ -109,8 +143,8 @@ export default function Navbar({ title, showBack = true, onBack, className, styl
<View className="w-[1px] h-4 bg-black/10" /> <View className="w-[1px] h-4 bg-black/10" />
{/* 右按钮 */}
<Pressable <Pressable
onPress={onClose}
onPressIn={() => { onPressIn={() => {
rightScale.value = withSpring(0.8); rightScale.value = withSpring(0.8);
}} }}
@@ -124,7 +158,6 @@ export default function Navbar({ title, showBack = true, onBack, className, styl
</Pressable> </Pressable>
</Shadow> </Shadow>
</View> </View>
// </View>
); );
} }
@@ -137,6 +170,7 @@ const styles = StyleSheet.create({
justifyContent: "center", justifyContent: "center",
borderWidth: 0.5, borderWidth: 0.5,
borderColor: "rgba(255,255,255,0.6)", borderColor: "rgba(255,255,255,0.6)",
backgroundColor: "#fff",
}, },
groupShadow: { groupShadow: {
borderRadius: 999, borderRadius: 999,
@@ -146,5 +180,21 @@ const styles = StyleSheet.create({
justifyContent: "center", justifyContent: "center",
borderWidth: 0.5, borderWidth: 0.5,
borderColor: "rgba(255,255,255,0.6)", borderColor: "rgba(255,255,255,0.6)",
backgroundColor: "#fff",
},
titleShadown: {
borderRadius: 999,
height: 36,
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-start",
borderWidth: 0.5,
borderColor: "rgba(255,255,255,0.6)",
paddingHorizontal: 16,
backgroundColor: "#fff",
}, },
}); });
@@ -2,7 +2,6 @@ import { View, StyleProp, ViewStyle, Pressable, StyleSheet } from "react-native"
import { LucideIcon } from "lucide-react-native"; import { LucideIcon } from "lucide-react-native";
import { cn } from "@/utils/cn"; import { cn } from "@/utils/cn";
import { Shadow } from "react-native-shadow-2"; import { Shadow } from "react-native-shadow-2";
import { useState } from "react";
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from "react-native-reanimated"; import Animated, { useSharedValue, useAnimatedStyle, withTiming } from "react-native-reanimated";
export interface TabbarItem { export interface TabbarItem {
@@ -22,14 +21,9 @@ export interface TabbarProps {
} }
export default function Tabbar({ tabs, value, onChange, className, style }: TabbarProps) { export default function Tabbar({ tabs, value, onChange, className, style }: TabbarProps) {
const [innerValue, setInnerValue] = useState(tabs[0]?.id); const activeTab = value;
const activeTab = value ?? innerValue;
const handlePress = (id: string) => { const handlePress = (id: string) => {
if (value === undefined) {
setInnerValue(id);
}
onChange?.(id); onChange?.(id);
}; };
@@ -37,8 +31,8 @@ export default function Tabbar({ tabs, value, onChange, className, style }: Tabb
<View className={cn("mx-3", className)} style={style}> <View className={cn("mx-3", className)} style={style}>
<Shadow <Shadow
distance={20} distance={20}
startColor="rgba(0,0,0,0.035)" startColor="rgba(0,0,0,0.04)"
endColor="rgba(255,255,255,0)" endColor="rgba(0,0,0,0)"
style={styles.tabbarShadow} style={styles.tabbarShadow}
> >
<View className="w-full flex-row items-center justify-between"> <View className="w-full flex-row items-center justify-between">
@@ -86,15 +80,15 @@ function TabItem({
onPressOut={() => { onPressOut={() => {
scale.value = withTiming(1, { duration: 120 }); scale.value = withTiming(1, { duration: 120 });
}} }}
className="flex-1 items-center justify-center" className="flex-1 "
> >
<Animated.View style={scaleStyle}> <Animated.View style={scaleStyle} className="items-center justify-center">
<Icon size={24} color={isActive ? "#5FAF7B" : "#666"} /> <Icon size={24} color={isActive ? "#5FAF7B" : "#666"} />
{tab.title && ( {tab.title && (
<Animated.Text <Animated.Text
style={[ style={[
{ {
fontSize: 11, fontSize: 10,
marginTop: 4, marginTop: 4,
fontWeight: isActive ? "600" : "400", fontWeight: isActive ? "600" : "400",
color: isActive ? "#5FAF7B" : "#666", color: isActive ? "#5FAF7B" : "#666",
@@ -131,13 +125,7 @@ function CenterButton({ tab, onPress }: { tab: TabbarItem; onPress?: () => void
}} }}
> >
<Animated.View style={animatedStyle}> <Animated.View style={animatedStyle}>
{/* <Shadow {/* <View
distance={12}
startColor="rgba(140,140,140,0.03)"
endColor="rgba(255,255,255,0)"
style={styles.centerButtonShadown}
> */}
<View
style={{ style={{
width: 64, width: 64,
height: 64, height: 64,
@@ -147,12 +135,19 @@ function CenterButton({ tab, onPress }: { tab: TabbarItem; onPress?: () => void
justifyContent: "center", justifyContent: "center",
borderWidth: 8, borderWidth: 8,
borderColor: "white", borderColor: "white",
top: -12, top: -4,
}} }}
> */}
<Shadow
distance={6}
startColor="rgba(0,0,0,0.06)"
endColor="rgba(0,0,0,0)"
offset={[0, 2]}
style={[styles.centerButtonShadown]}
> >
<Icon size={30} color="#fff" /> <Icon size={30} color="#fff" />
</View> </Shadow>
{/* </Shadow> */} {/* </View> */}
</Animated.View> </Animated.View>
</Pressable> </Pressable>
</View> </View>
@@ -161,14 +156,14 @@ function CenterButton({ tab, onPress }: { tab: TabbarItem; onPress?: () => void
const styles = StyleSheet.create({ const styles = StyleSheet.create({
centerButtonShadown: { centerButtonShadown: {
width: 48, width: 44,
height: 48, height: 44,
borderRadius: 999, borderRadius: 999,
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
borderWidth: 1, borderWidth: 1,
borderColor: "rgba(0,0,0,0.03)", borderColor: "rgba(0,0,0,0.03)",
backgroundColor: "#fff", backgroundColor: "#22C55E",
}, },
tabbarShadow: { tabbarShadow: {
borderRadius: 999, borderRadius: 999,
+46 -26
View File
@@ -1,42 +1,62 @@
import React from "react";
import { View } from "react-native"; import { View } from "react-native";
import Tabbar, { TabbarItem } from "@/components/layout/tabbar"; import Tabbar, { TabbarItem } from "@/components/layout/tabbar";
import Navbar from "@/components/layout/navbar"; import Navbar from "@/components/layout/navbar";
import { Slot } from "expo-router";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Slot, useRouter, usePathname } from "expo-router";
import { useMemo } from "react";
interface ModuleLayoutProps { export default function ModuleLayout({ tabs }: { tabs: TabbarItem[] }) {
tabs: TabbarItem[]; const router = useRouter();
}
export default function ModuleLayout({ tabs }: ModuleLayoutProps) {
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const pathname = usePathname();
const activeTabItem = useMemo(() => {
return tabs.find((t) => t.id === pathname);
}, [pathname]);
function handleClose() {
router.replace("/(tabs)/home");
}
return ( return (
<View className="flex-1"> <View style={{ flex: 1 }}>
<View <View style={{ flex: 1 }}>
pointerEvents="box-none"
className="absolute top-0 left-0 right-0 z-10 bg-white"
style={{ paddingTop: insets.top }}
>
<Navbar />
</View>
<View
className="flex-1 z-[1]"
style={{
paddingTop: insets.top + 48 + 4,
paddingBottom: insets.bottom + 12 + 16 + 56,
}}
>
<Slot /> <Slot />
</View> </View>
<View <View
className="absolute left-0 right-0 bottom-0 z-10" style={{
style={{ paddingBottom: insets.bottom }} position: "absolute",
top: 0,
left: 0,
right: 0,
zIndex: 10,
}}
> >
<Tabbar tabs={tabs} /> <View
style={{
paddingTop: insets.top,
}}
>
<Navbar
title={activeTabItem?.title}
titleIcon={activeTabItem?.icon}
showBack={false}
onClose={handleClose}
/>
</View>
</View>
<View style={{ position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 10 }}>
<View style={{ paddingBottom: insets.bottom }}>
<Tabbar
tabs={tabs}
value={activeTabItem?.id}
onChange={(id) => {
router.push(id as any);
}}
/>
</View>
</View> </View>
</View> </View>
); );