u
This commit is contained in:
1 parent
157c84b0b4
commit
c7222a346d
7 files changed
+190
-101
No files matched your search
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user