This commit is contained in:
oneao committed 2026-04-08 21:28:52 +08:00
1 parent f28107399e
commit 7823fe2768
13 files changed
+294 -28

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
{
"Print RN Component (NativeWind)": {
"prefix": "rn",
"body": [
"import { View, Text } from 'react-native';",
"",
"export default function ${1:ComponentName}() {",
" return (",
" <View className=\"flex-1\">",
" <Text>${2:Hello World}</Text>",
" </View>",
" );",
"}",
],
"description": "React Native NativeWind 基础组件",
},
}
+32
View File
@@ -0,0 +1,32 @@
import { View } from "react-native";
import CustomTabbar from "@/components/common/tabbar";
import { TabbarItem } from "@/components/common/tabbar";
import { LayoutGridIcon, UserCircleIcon } from "lucide-react-native";
import { Slot } from "expo-router";
import { Icon } from "lucide-react-native";
const tabs: TabbarItem[] = [
{
href: "/home",
name: "home",
label: "首页",
icon: "BookA",
},
{
href: "/user",
name: "user",
label: "我的",
icon: "User",
},
];
export default function BaseLayout() {
return (
<View className="flex-1">
<View className="flex-1">
<Slot />
</View>
<CustomTabbar data={tabs} />
</View>
);
}
@@ -0,0 +1,9 @@
import { View, Text } from "react-native";
export default function Home() {
return (
<View className="flex-1 items-center justify-center">
<Text>Home</Text>
</View>
);
}
@@ -0,0 +1,9 @@
import { View, Text } from "react-native";
export default function User() {
return (
<View className="flex-1">
<Text>User</Text>
</View>
);
}
+14 -5
View File
@@ -1,14 +1,23 @@
import { useColorScheme } from "@/hooks/use-color-scheme";
import { Stack } from "expo-router";
import { useEffect } from "react";
import "@/styles/global.css";
import { useIsDark } from "@/hooks/use-color-scheme";
import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";
import { StatusBar } from "expo-status-bar";
export default function RootLayout() {
const colorScheme = useColorScheme();
const isDark = useIsDark();
useEffect(() => {
console.log(colorScheme);
}, [colorScheme]);
console.log(isDark);
}, [isDark]);
return <Stack />;
return (
<ThemeProvider value={isDark ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
<StatusBar style="auto" />
</ThemeProvider>
);
}
+2
View File
@@ -1,6 +1,7 @@
import React from "react";
import { Button, ButtonText, ButtonSpinner, ButtonIcon } from "@/components/ui/button";
import { Text, View } from "react-native";
import { Camera } from "lucide-react-native";
export default function App() {
return (
@@ -11,6 +12,7 @@ export default function App() {
<Button action={"primary"} variant={"solid"} size={"lg"} isDisabled={false}>
<ButtonText>Hello World</ButtonText>
</Button>
<Camera />
</View>
);
}
@@ -0,0 +1,127 @@
import type { Href } from "expo-router";
import { usePathname, useRouter } from "expo-router";
import { useEffect, useState } from "react";
import { Pressable, Text, View } from "react-native";
import Animated, {
useAnimatedStyle,
useSharedValue,
withSequence,
withTiming,
} from "react-native-reanimated";
import * as icons from "lucide-react-native/icons";
export interface TabbarItem {
name: string;
href: Href;
icon: keyof typeof icons; // 👈 传 icon 名字
label: string; // 👈 label
onPress?: () => void;
}
interface AppTabbarProps {
data: TabbarItem[];
}
function TabButton({
iconName,
label,
isFocused = false,
onPress,
}: {
iconName: keyof typeof icons;
label: string;
isFocused?: boolean;
onPress?: () => void;
}) {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
useEffect(() => {
if (isFocused) {
scale.value = withSequence(
withTiming(0.9, { duration: 120 }),
withTiming(1, { duration: 160 }),
);
}
}, [isFocused]);
// 👇 在内部统一处理 icon
const LucideIcon = icons[iconName];
return (
<Pressable
onPress={onPress}
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
}}
>
<Animated.View style={animatedStyle}>
<LucideIcon
size={22}
color={"#333"}
fill={isFocused ? "#0284c7" : "#fff"} // 填充颜色
strokeWidth={2}
/>
<Text
style={{
fontSize: 10,
marginTop: 2,
color: "#333",
}}
>
{label}
</Text>
</Animated.View>
</Pressable>
);
}
export default function CustomTabbar({ data }: AppTabbarProps) {
const router = useRouter();
const pathname = usePathname();
const [activeTab, setActiveTab] = useState("");
// 路由同步
useEffect(() => {
const match = data.find((item) => item.href === pathname);
if (match) {
setActiveTab(match.name);
}
}, [pathname, data]);
const handlePressTab = (item: TabbarItem) => {
setActiveTab(item.name);
router.push(item.href);
item.onPress?.();
};
return (
<View
style={{
flexDirection: "row",
height: 48,
alignItems: "center",
backgroundColor: "#fff",
}}
>
{data.map((item) => (
<TabButton
key={item.name}
iconName={item.icon}
label={item.label}
isFocused={activeTab === item.name}
onPress={() => handlePressTab(item)}
/>
))}
</View>
);
}
+15 -1
View File
@@ -1 +1,15 @@
export { useColorScheme } from 'react-native';
import { useColorScheme } from "react-native";
import { useAppStore } from "@/store/app";
function useIsDark() {
const systemScheme = useColorScheme(); // 'light' | 'dark'
const themeScheme = useAppStore((state) => state.themeScheme);
if (themeScheme === "system") {
return systemScheme === "dark";
}
return themeScheme === "dark";
}
export { useIsDark };
+4 -4
View File
@@ -3,14 +3,14 @@
* https://docs.expo.dev/guides/color-schemes/
*/
import { Colors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';
import { Colors } from "@/constants/theme";
import { useColorScheme } from "@/hooks/use-color-scheme";
export function useThemeColor(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
colorName: keyof typeof Colors.light & keyof typeof Colors.dark,
) {
const theme = useColorScheme() ?? 'light';
const theme = useColorScheme() ?? "light";
const colorFromProps = props[theme];
if (colorFromProps) {
+3 -1
View File
@@ -30,6 +30,7 @@
"expo-symbols": "~1.0.8",
"expo-system-ui": "~6.0.9",
"expo-web-browser": "~15.0.10",
"lucide-react-native": "^1.7.0",
"nativewind": "^4.2.3",
"react": "19.1.0",
"react-dom": "19.1.0",
@@ -41,7 +42,8 @@
"react-native-screens": "~4.16.0",
"react-native-svg": "15.12.1",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1"
"react-native-worklets": "0.5.1",
"zustand": "^5.0.12"
},
"devDependencies": {
"@types/react": "~19.1.0",
+44 -17
View File
@@ -65,6 +65,9 @@ importers:
expo-web-browser:
specifier: ~15.0.10
version: 15.0.10(expo@54.0.33)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))
lucide-react-native:
specifier: ^1.7.0
version: 1.7.0(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
nativewind:
specifier: ^4.2.3
version: 4.2.3(react-native-reanimated@4.1.7(react-native-worklets@0.5.1(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.19(yaml@2.8.3))
@@ -101,6 +104,9 @@ importers:
react-native-worklets:
specifier: 0.5.1
version: 0.5.1(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
zustand:
specifier: ^5.0.12
version: 5.0.12(@types/react@19.1.17)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0))
devDependencies:
'@types/react':
specifier: ~19.1.0
@@ -927,56 +933,48 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-arm64-musl@0.44.0':
resolution: {integrity: sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@oxfmt/binding-linux-ppc64-gnu@0.44.0':
resolution: {integrity: sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-riscv64-gnu@0.44.0':
resolution: {integrity: sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-riscv64-musl@0.44.0':
resolution: {integrity: sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@oxfmt/binding-linux-s390x-gnu@0.44.0':
resolution: {integrity: sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-x64-gnu@0.44.0':
resolution: {integrity: sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@oxfmt/binding-linux-x64-musl@0.44.0':
resolution: {integrity: sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@oxfmt/binding-openharmony-arm64@0.44.0':
resolution: {integrity: sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw==}
@@ -2783,7 +2781,7 @@ packages:
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -3068,56 +3066,48 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-gnu@1.32.0:
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.27.0:
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.27.0:
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.27.0:
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.27.0:
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
@@ -3186,6 +3176,13 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-react-native@1.7.0:
resolution: {integrity: sha512-wGJY5nosSawh028jg8r1ZKqnGPDIVfIL9xvKOs4wPYFQHeJMHsADYm/lmuFYXMXXatSkHhpsCjeqIRgeFGzf8g==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-native: '*'
react-native-svg: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
makeerror@1.0.12:
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
@@ -4451,6 +4448,24 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
zustand@5.0.12:
resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
immer: '>=9.0.6'
react: '>=18.0.0'
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
snapshots:
'@0no-co/graphql.web@1.2.0': {}
@@ -8347,6 +8362,12 @@ snapshots:
dependencies:
yallist: 3.1.1
lucide-react-native@1.7.0(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
react-native-svg: 15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
makeerror@1.0.12:
dependencies:
tmpl: 1.0.5
@@ -9803,3 +9824,9 @@ snapshots:
yargs-parser: 21.1.1
yocto-queue@0.1.0: {}
zustand@5.0.12(@types/react@19.1.17)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)):
optionalDependencies:
'@types/react': 19.1.17
react: 19.1.0
use-sync-external-store: 1.6.0(react@19.1.0)
+13
View File
@@ -0,0 +1,13 @@
import { create } from "zustand";
export type ThemeScheme = "light" | "dark" | "system";
interface AppState {
themeScheme: ThemeScheme;
setThemeScheme: (theme: ThemeScheme) => void;
}
export const useAppStore = create<AppState>((set) => ({
themeScheme: "system",
setThemeScheme: (theme) => set({ themeScheme: theme }),
}));
+5
View File
@@ -0,0 +1,5 @@
export {}
declare global {
type ThemeScheme = 'light' | 'dark' | 'system'
}