init
This commit is contained in:
parent
8324b52de1
commit
5d899afe87
30 changed files with 806 additions and 1091 deletions
4
app.json
4
app.json
|
|
@ -8,7 +8,8 @@
|
||||||
"scheme": "kanade",
|
"scheme": "kanade",
|
||||||
"userInterfaceStyle": "automatic",
|
"userInterfaceStyle": "automatic",
|
||||||
"ios": {
|
"ios": {
|
||||||
"icon": "./assets/expo.icon"
|
"icon": "./assets/expo.icon",
|
||||||
|
"bundleIdentifier": "com.anonymous.kanade"
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
|
|
@ -21,6 +22,7 @@
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"output": "static",
|
"output": "static",
|
||||||
|
"bundler": "metro",
|
||||||
"favicon": "./assets/images/favicon.png"
|
"favicon": "./assets/images/favicon.png"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
|
|
||||||
9
babel.config.js
Normal file
9
babel.config.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = function (api) {
|
||||||
|
api.cache(true);
|
||||||
|
return {
|
||||||
|
presets: [
|
||||||
|
["babel-preset-expo", { jsxImportSource: "nativewind" }],
|
||||||
|
"nativewind/babel",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
10
eslint.config.js
Normal file
10
eslint.config.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
// https://docs.expo.dev/guides/using-eslint/
|
||||||
|
const { defineConfig } = require('eslint/config');
|
||||||
|
const expoConfig = require("eslint-config-expo/flat");
|
||||||
|
|
||||||
|
module.exports = defineConfig([
|
||||||
|
expoConfig,
|
||||||
|
{
|
||||||
|
ignores: ["dist/*"],
|
||||||
|
}
|
||||||
|
]);
|
||||||
6
metro.config.js
Normal file
6
metro.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
const { getDefaultConfig } = require("expo/metro-config");
|
||||||
|
const { withNativeWind } = require("nativewind/metro");
|
||||||
|
|
||||||
|
const config = getDefaultConfig(__dirname);
|
||||||
|
|
||||||
|
module.exports = withNativeWind(config, { input: "./src/global.css" });
|
||||||
3
nativewind-env.d.ts
vendored
Normal file
3
nativewind-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
/// <reference types="nativewind/types" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
|
||||||
19
package.json
19
package.json
|
|
@ -5,8 +5,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"reset-project": "node ./scripts/reset-project.js",
|
"reset-project": "node ./scripts/reset-project.js",
|
||||||
"android": "expo start --android",
|
"android": "expo run:android",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo run:ios",
|
||||||
"web": "expo start --web",
|
"web": "expo start --web",
|
||||||
"lint": "expo lint"
|
"lint": "expo lint"
|
||||||
},
|
},
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"@react-navigation/native": "^7.1.33",
|
"@react-navigation/native": "^7.1.33",
|
||||||
"expo": "~55.0.11",
|
"expo": "~55.0.11",
|
||||||
"expo-constants": "~55.0.11",
|
"expo-constants": "~55.0.11",
|
||||||
|
"expo-dev-client": "~55.0.22",
|
||||||
"expo-device": "~55.0.12",
|
"expo-device": "~55.0.12",
|
||||||
"expo-font": "~55.0.6",
|
"expo-font": "~55.0.6",
|
||||||
"expo-glass-effect": "~55.0.10",
|
"expo-glass-effect": "~55.0.10",
|
||||||
|
|
@ -27,18 +28,24 @@
|
||||||
"expo-symbols": "~55.0.7",
|
"expo-symbols": "~55.0.7",
|
||||||
"expo-system-ui": "~55.0.13",
|
"expo-system-ui": "~55.0.13",
|
||||||
"expo-web-browser": "~55.0.12",
|
"expo-web-browser": "~55.0.12",
|
||||||
|
"nativewind": "^4.2.3",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-native": "0.83.4",
|
"react-native": "0.83.4",
|
||||||
"react-native-gesture-handler": "~2.30.0",
|
"react-native-gesture-handler": "~2.30.0",
|
||||||
"react-native-worklets": "0.7.2",
|
"react-native-reanimated": "^4.3.0",
|
||||||
"react-native-reanimated": "4.2.1",
|
"react-native-safe-area-context": "^5.7.0",
|
||||||
"react-native-safe-area-context": "~5.6.2",
|
|
||||||
"react-native-screens": "~4.23.0",
|
"react-native-screens": "~4.23.0",
|
||||||
"react-native-web": "~0.21.0"
|
"react-native-web": "~0.21.0",
|
||||||
|
"react-native-worklets": "0.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "~19.2.2",
|
"@types/react": "~19.2.2",
|
||||||
|
"babel-preset-expo": "^55.0.15",
|
||||||
|
"eslint": "^9.0.0",
|
||||||
|
"eslint-config-expo": "~55.0.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "~5.9.2"
|
"typescript": "~5.9.2"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,12 @@
|
||||||
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
import { Stack } from 'expo-router';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useColorScheme } from 'react-native';
|
import '../global.css';
|
||||||
|
|
||||||
import { AnimatedSplashOverlay } from '@/components/animated-icon';
|
export default function RootLayout() {
|
||||||
import AppTabs from '@/components/app-tabs';
|
|
||||||
|
|
||||||
export default function TabLayout() {
|
|
||||||
const colorScheme = useColorScheme();
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<Stack>
|
||||||
<AnimatedSplashOverlay />
|
<Stack.Screen name="index" options={{ title: 'Home', headerShown: false }} />
|
||||||
<AppTabs />
|
<Stack.Screen name="onboard/index" options={{ title: 'Onboard', headerShown: false }} />
|
||||||
</ThemeProvider>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
import { Image } from 'expo-image';
|
|
||||||
import { SymbolView } from 'expo-symbols';
|
|
||||||
import React from 'react';
|
|
||||||
import { Platform, Pressable, ScrollView, StyleSheet } from 'react-native';
|
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
||||||
|
|
||||||
import { ExternalLink } from '@/components/external-link';
|
|
||||||
import { ThemedText } from '@/components/themed-text';
|
|
||||||
import { ThemedView } from '@/components/themed-view';
|
|
||||||
import { Collapsible } from '@/components/ui/collapsible';
|
|
||||||
import { WebBadge } from '@/components/web-badge';
|
|
||||||
import { BottomTabInset, MaxContentWidth, Spacing } from '@/constants/theme';
|
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
|
||||||
|
|
||||||
export default function TabTwoScreen() {
|
|
||||||
const safeAreaInsets = useSafeAreaInsets();
|
|
||||||
const insets = {
|
|
||||||
...safeAreaInsets,
|
|
||||||
bottom: safeAreaInsets.bottom + BottomTabInset + Spacing.three,
|
|
||||||
};
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const contentPlatformStyle = Platform.select({
|
|
||||||
android: {
|
|
||||||
paddingTop: insets.top,
|
|
||||||
paddingLeft: insets.left,
|
|
||||||
paddingRight: insets.right,
|
|
||||||
paddingBottom: insets.bottom,
|
|
||||||
},
|
|
||||||
web: {
|
|
||||||
paddingTop: Spacing.six,
|
|
||||||
paddingBottom: Spacing.four,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView
|
|
||||||
style={[styles.scrollView, { backgroundColor: theme.background }]}
|
|
||||||
contentInset={insets}
|
|
||||||
contentContainerStyle={[styles.contentContainer, contentPlatformStyle]}>
|
|
||||||
<ThemedView style={styles.container}>
|
|
||||||
<ThemedView style={styles.titleContainer}>
|
|
||||||
<ThemedText type="subtitle">Explore</ThemedText>
|
|
||||||
<ThemedText style={styles.centerText} themeColor="textSecondary">
|
|
||||||
This starter app includes example{'\n'}code to help you get started.
|
|
||||||
</ThemedText>
|
|
||||||
|
|
||||||
<ExternalLink href="https://docs.expo.dev" asChild>
|
|
||||||
<Pressable style={({ pressed }) => pressed && styles.pressed}>
|
|
||||||
<ThemedView type="backgroundElement" style={styles.linkButton}>
|
|
||||||
<ThemedText type="link">Expo documentation</ThemedText>
|
|
||||||
<SymbolView
|
|
||||||
tintColor={theme.text}
|
|
||||||
name={{ ios: 'arrow.up.right.square', android: 'link', web: 'link' }}
|
|
||||||
size={12}
|
|
||||||
/>
|
|
||||||
</ThemedView>
|
|
||||||
</Pressable>
|
|
||||||
</ExternalLink>
|
|
||||||
</ThemedView>
|
|
||||||
|
|
||||||
<ThemedView style={styles.sectionsWrapper}>
|
|
||||||
<Collapsible title="File-based routing">
|
|
||||||
<ThemedText type="small">
|
|
||||||
This app has two screens: <ThemedText type="code">src/app/index.tsx</ThemedText> and{' '}
|
|
||||||
<ThemedText type="code">src/app/explore.tsx</ThemedText>
|
|
||||||
</ThemedText>
|
|
||||||
<ThemedText type="small">
|
|
||||||
The layout file in <ThemedText type="code">src/app/_layout.tsx</ThemedText> sets up
|
|
||||||
the tab navigator.
|
|
||||||
</ThemedText>
|
|
||||||
<ExternalLink href="https://docs.expo.dev/router/introduction">
|
|
||||||
<ThemedText type="linkPrimary">Learn more</ThemedText>
|
|
||||||
</ExternalLink>
|
|
||||||
</Collapsible>
|
|
||||||
|
|
||||||
<Collapsible title="Android, iOS, and web support">
|
|
||||||
<ThemedView type="backgroundElement" style={styles.collapsibleContent}>
|
|
||||||
<ThemedText type="small">
|
|
||||||
You can open this project on Android, iOS, and the web. To open the web version,
|
|
||||||
press <ThemedText type="smallBold">w</ThemedText> in the terminal running this
|
|
||||||
project.
|
|
||||||
</ThemedText>
|
|
||||||
<Image
|
|
||||||
source={require('@/assets/images/tutorial-web.png')}
|
|
||||||
style={styles.imageTutorial}
|
|
||||||
/>
|
|
||||||
</ThemedView>
|
|
||||||
</Collapsible>
|
|
||||||
|
|
||||||
<Collapsible title="Images">
|
|
||||||
<ThemedText type="small">
|
|
||||||
For static images, you can use the <ThemedText type="code">@2x</ThemedText> and{' '}
|
|
||||||
<ThemedText type="code">@3x</ThemedText> suffixes to provide files for different
|
|
||||||
screen densities.
|
|
||||||
</ThemedText>
|
|
||||||
<Image source={require('@/assets/images/react-logo.png')} style={styles.imageReact} />
|
|
||||||
<ExternalLink href="https://reactnative.dev/docs/images">
|
|
||||||
<ThemedText type="linkPrimary">Learn more</ThemedText>
|
|
||||||
</ExternalLink>
|
|
||||||
</Collapsible>
|
|
||||||
|
|
||||||
<Collapsible title="Light and dark mode components">
|
|
||||||
<ThemedText type="small">
|
|
||||||
This template has light and dark mode support. The{' '}
|
|
||||||
<ThemedText type="code">useColorScheme()</ThemedText> hook lets you inspect what the
|
|
||||||
user's current color scheme is, and so you can adjust UI colors accordingly.
|
|
||||||
</ThemedText>
|
|
||||||
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
|
|
||||||
<ThemedText type="linkPrimary">Learn more</ThemedText>
|
|
||||||
</ExternalLink>
|
|
||||||
</Collapsible>
|
|
||||||
|
|
||||||
<Collapsible title="Animations">
|
|
||||||
<ThemedText type="small">
|
|
||||||
This template includes an example of an animated component. The{' '}
|
|
||||||
<ThemedText type="code">src/components/ui/collapsible.tsx</ThemedText> component uses
|
|
||||||
the powerful <ThemedText type="code">react-native-reanimated</ThemedText> library to
|
|
||||||
animate opening this hint.
|
|
||||||
</ThemedText>
|
|
||||||
</Collapsible>
|
|
||||||
</ThemedView>
|
|
||||||
{Platform.OS === 'web' && <WebBadge />}
|
|
||||||
</ThemedView>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
scrollView: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
contentContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
maxWidth: MaxContentWidth,
|
|
||||||
flexGrow: 1,
|
|
||||||
},
|
|
||||||
titleContainer: {
|
|
||||||
gap: Spacing.three,
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingHorizontal: Spacing.four,
|
|
||||||
paddingVertical: Spacing.six,
|
|
||||||
},
|
|
||||||
centerText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
pressed: {
|
|
||||||
opacity: 0.7,
|
|
||||||
},
|
|
||||||
linkButton: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
paddingHorizontal: Spacing.four,
|
|
||||||
paddingVertical: Spacing.two,
|
|
||||||
borderRadius: Spacing.five,
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: Spacing.one,
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
sectionsWrapper: {
|
|
||||||
gap: Spacing.five,
|
|
||||||
paddingHorizontal: Spacing.four,
|
|
||||||
paddingTop: Spacing.three,
|
|
||||||
},
|
|
||||||
collapsibleContent: {
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
imageTutorial: {
|
|
||||||
width: '100%',
|
|
||||||
aspectRatio: 296 / 171,
|
|
||||||
borderRadius: Spacing.three,
|
|
||||||
marginTop: Spacing.two,
|
|
||||||
},
|
|
||||||
imageReact: {
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
alignSelf: 'center',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,98 +1,11 @@
|
||||||
import * as Device from 'expo-device';
|
import { Text, View } from "react-native";
|
||||||
import { Platform, StyleSheet } from 'react-native';
|
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
||||||
|
|
||||||
import { AnimatedIcon } from '@/components/animated-icon';
|
export default function App() {
|
||||||
import { HintRow } from '@/components/hint-row';
|
|
||||||
import { ThemedText } from '@/components/themed-text';
|
|
||||||
import { ThemedView } from '@/components/themed-view';
|
|
||||||
import { WebBadge } from '@/components/web-badge';
|
|
||||||
import { BottomTabInset, MaxContentWidth, Spacing } from '@/constants/theme';
|
|
||||||
|
|
||||||
function getDevMenuHint() {
|
|
||||||
if (Platform.OS === 'web') {
|
|
||||||
return <ThemedText type="small">use browser devtools</ThemedText>;
|
|
||||||
}
|
|
||||||
if (Device.isDevice) {
|
|
||||||
return (
|
return (
|
||||||
<ThemedText type="small">
|
<View className="flex-1 items-center justify-center bg-white">
|
||||||
shake device or press <ThemedText type="code">m</ThemedText> in terminal
|
<Text className="text-xl font-bold text-blue-500">
|
||||||
</ThemedText>
|
Welcome to Nativewind!
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const shortcut = Platform.OS === 'android' ? 'cmd+m (or ctrl+m)' : 'cmd+d';
|
|
||||||
return (
|
|
||||||
<ThemedText type="small">
|
|
||||||
press <ThemedText type="code">{shortcut}</ThemedText>
|
|
||||||
</ThemedText>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HomeScreen() {
|
|
||||||
return (
|
|
||||||
<ThemedView style={styles.container}>
|
|
||||||
<SafeAreaView style={styles.safeArea}>
|
|
||||||
<ThemedView style={styles.heroSection}>
|
|
||||||
<AnimatedIcon />
|
|
||||||
<ThemedText type="title" style={styles.title}>
|
|
||||||
Welcome to Expo
|
|
||||||
</ThemedText>
|
|
||||||
</ThemedView>
|
|
||||||
|
|
||||||
<ThemedText type="code" style={styles.code}>
|
|
||||||
get started
|
|
||||||
</ThemedText>
|
|
||||||
|
|
||||||
<ThemedView type="backgroundElement" style={styles.stepContainer}>
|
|
||||||
<HintRow
|
|
||||||
title="Try editing"
|
|
||||||
hint={<ThemedText type="code">src/app/index.tsx</ThemedText>}
|
|
||||||
/>
|
|
||||||
<HintRow title="Dev tools" hint={getDevMenuHint()} />
|
|
||||||
<HintRow
|
|
||||||
title="Fresh start"
|
|
||||||
hint={<ThemedText type="code">npm run reset-project</ThemedText>}
|
|
||||||
/>
|
|
||||||
</ThemedView>
|
|
||||||
|
|
||||||
{Platform.OS === 'web' && <WebBadge />}
|
|
||||||
</SafeAreaView>
|
|
||||||
</ThemedView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
flexDirection: 'row',
|
|
||||||
},
|
|
||||||
safeArea: {
|
|
||||||
flex: 1,
|
|
||||||
paddingHorizontal: Spacing.four,
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: Spacing.three,
|
|
||||||
paddingBottom: BottomTabInset + Spacing.three,
|
|
||||||
maxWidth: MaxContentWidth,
|
|
||||||
},
|
|
||||||
heroSection: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
flex: 1,
|
|
||||||
paddingHorizontal: Spacing.four,
|
|
||||||
gap: Spacing.four,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
code: {
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
},
|
|
||||||
stepContainer: {
|
|
||||||
gap: Spacing.three,
|
|
||||||
alignSelf: 'stretch',
|
|
||||||
paddingHorizontal: Spacing.three,
|
|
||||||
paddingVertical: Spacing.four,
|
|
||||||
borderRadius: Spacing.four,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
|
||||||
28
src/app/onboard/index.tsx
Normal file
28
src/app/onboard/index.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Button } from '@react-navigation/elements';
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
export default function HomeScreen() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: '700',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
.expoLogoBackground {
|
|
||||||
background-image: linear-gradient(180deg, #3c9ffe, #0274df);
|
|
||||||
border-radius: 40px;
|
|
||||||
width: 128px;
|
|
||||||
height: 128px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
import { Image } from 'expo-image';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { Dimensions, StyleSheet, View } from 'react-native';
|
|
||||||
import Animated, { Easing, Keyframe } from 'react-native-reanimated';
|
|
||||||
import { scheduleOnRN } from 'react-native-worklets';
|
|
||||||
|
|
||||||
const INITIAL_SCALE_FACTOR = Dimensions.get('screen').height / 90;
|
|
||||||
const DURATION = 600;
|
|
||||||
|
|
||||||
export function AnimatedSplashOverlay() {
|
|
||||||
const [visible, setVisible] = useState(true);
|
|
||||||
|
|
||||||
if (!visible) return null;
|
|
||||||
|
|
||||||
const splashKeyframe = new Keyframe({
|
|
||||||
0: {
|
|
||||||
transform: [{ scale: INITIAL_SCALE_FACTOR }],
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
20: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
70: {
|
|
||||||
opacity: 0,
|
|
||||||
easing: Easing.elastic(0.7),
|
|
||||||
},
|
|
||||||
100: {
|
|
||||||
opacity: 0,
|
|
||||||
transform: [{ scale: 1 }],
|
|
||||||
easing: Easing.elastic(0.7),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Animated.View
|
|
||||||
entering={splashKeyframe.duration(DURATION).withCallback((finished) => {
|
|
||||||
'worklet';
|
|
||||||
if (finished) {
|
|
||||||
scheduleOnRN(setVisible, false);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
style={styles.backgroundSolidColor}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyframe = new Keyframe({
|
|
||||||
0: {
|
|
||||||
transform: [{ scale: INITIAL_SCALE_FACTOR }],
|
|
||||||
},
|
|
||||||
100: {
|
|
||||||
transform: [{ scale: 1 }],
|
|
||||||
easing: Easing.elastic(0.7),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const logoKeyframe = new Keyframe({
|
|
||||||
0: {
|
|
||||||
transform: [{ scale: 1.3 }],
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
40: {
|
|
||||||
transform: [{ scale: 1.3 }],
|
|
||||||
opacity: 0,
|
|
||||||
easing: Easing.elastic(0.7),
|
|
||||||
},
|
|
||||||
100: {
|
|
||||||
opacity: 1,
|
|
||||||
transform: [{ scale: 1 }],
|
|
||||||
easing: Easing.elastic(0.7),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const glowKeyframe = new Keyframe({
|
|
||||||
0: {
|
|
||||||
transform: [{ rotateZ: '0deg' }],
|
|
||||||
},
|
|
||||||
100: {
|
|
||||||
transform: [{ rotateZ: '7200deg' }],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function AnimatedIcon() {
|
|
||||||
return (
|
|
||||||
<View style={styles.iconContainer}>
|
|
||||||
<Animated.View entering={glowKeyframe.duration(60 * 1000 * 4)} style={styles.glow}>
|
|
||||||
<Image style={styles.glow} source={require('@/assets/images/logo-glow.png')} />
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
<Animated.View entering={keyframe.duration(DURATION)} style={styles.background} />
|
|
||||||
<Animated.View style={styles.imageContainer} entering={logoKeyframe.duration(DURATION)}>
|
|
||||||
<Image style={styles.image} source={require('@/assets/images/expo-logo.png')} />
|
|
||||||
</Animated.View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
imageContainer: {
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
glow: {
|
|
||||||
width: 201,
|
|
||||||
height: 201,
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
iconContainer: {
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 128,
|
|
||||||
height: 128,
|
|
||||||
zIndex: 100,
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: 76,
|
|
||||||
height: 71,
|
|
||||||
},
|
|
||||||
background: {
|
|
||||||
borderRadius: 40,
|
|
||||||
experimental_backgroundImage: `linear-gradient(180deg, #3C9FFE, #0274DF)`,
|
|
||||||
width: 128,
|
|
||||||
height: 128,
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
backgroundSolidColor: {
|
|
||||||
...StyleSheet.absoluteFillObject,
|
|
||||||
backgroundColor: '#208AEF',
|
|
||||||
zIndex: 1000,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
import { Image } from 'expo-image';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
import Animated, { Keyframe, Easing } from 'react-native-reanimated';
|
|
||||||
|
|
||||||
import classes from './animated-icon.module.css';
|
|
||||||
const DURATION = 300;
|
|
||||||
|
|
||||||
export function AnimatedSplashOverlay() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyframe = new Keyframe({
|
|
||||||
0: {
|
|
||||||
transform: [{ scale: 0 }],
|
|
||||||
},
|
|
||||||
60: {
|
|
||||||
transform: [{ scale: 1.2 }],
|
|
||||||
easing: Easing.elastic(1.2),
|
|
||||||
},
|
|
||||||
100: {
|
|
||||||
transform: [{ scale: 1 }],
|
|
||||||
easing: Easing.elastic(1.2),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const logoKeyframe = new Keyframe({
|
|
||||||
0: {
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
60: {
|
|
||||||
transform: [{ scale: 1.2 }],
|
|
||||||
opacity: 0,
|
|
||||||
easing: Easing.elastic(1.2),
|
|
||||||
},
|
|
||||||
100: {
|
|
||||||
transform: [{ scale: 1 }],
|
|
||||||
opacity: 1,
|
|
||||||
easing: Easing.elastic(1.2),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const glowKeyframe = new Keyframe({
|
|
||||||
0: {
|
|
||||||
transform: [{ rotateZ: '-180deg' }, { scale: 0.8 }],
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
[DURATION / 1000]: {
|
|
||||||
transform: [{ rotateZ: '0deg' }, { scale: 1 }],
|
|
||||||
opacity: 1,
|
|
||||||
easing: Easing.elastic(0.7),
|
|
||||||
},
|
|
||||||
100: {
|
|
||||||
transform: [{ rotateZ: '7200deg' }],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function AnimatedIcon() {
|
|
||||||
return (
|
|
||||||
<View style={styles.iconContainer}>
|
|
||||||
<Animated.View entering={glowKeyframe.duration(60 * 1000 * 4)} style={styles.glow}>
|
|
||||||
<Image style={styles.glow} source={require('@/assets/images/logo-glow.png')} />
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
<Animated.View style={styles.background} entering={keyframe.duration(DURATION)}>
|
|
||||||
<div className={classes.expoLogoBackground} />
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
<Animated.View style={styles.imageContainer} entering={logoKeyframe.duration(DURATION)}>
|
|
||||||
<Image style={styles.image} source={require('@/assets/images/expo-logo.png')} />
|
|
||||||
</Animated.View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '100%',
|
|
||||||
zIndex: 1000,
|
|
||||||
position: 'absolute',
|
|
||||||
top: 128 / 2 + 138,
|
|
||||||
},
|
|
||||||
imageContainer: {
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
glow: {
|
|
||||||
width: 201,
|
|
||||||
height: 201,
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
iconContainer: {
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 128,
|
|
||||||
height: 128,
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: 76,
|
|
||||||
height: 71,
|
|
||||||
},
|
|
||||||
background: {
|
|
||||||
width: 128,
|
|
||||||
height: 128,
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import { NativeTabs } from 'expo-router/unstable-native-tabs';
|
|
||||||
import React from 'react';
|
|
||||||
import { useColorScheme } from 'react-native';
|
|
||||||
|
|
||||||
import { Colors } from '@/constants/theme';
|
|
||||||
|
|
||||||
export default function AppTabs() {
|
|
||||||
const scheme = useColorScheme();
|
|
||||||
const colors = Colors[scheme === 'unspecified' ? 'light' : scheme];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NativeTabs
|
|
||||||
backgroundColor={colors.background}
|
|
||||||
indicatorColor={colors.backgroundElement}
|
|
||||||
labelStyle={{ selected: { color: colors.text } }}>
|
|
||||||
<NativeTabs.Trigger name="index">
|
|
||||||
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
||||||
<NativeTabs.Trigger.Icon
|
|
||||||
src={require('@/assets/images/tabIcons/home.png')}
|
|
||||||
renderingMode="template"
|
|
||||||
/>
|
|
||||||
</NativeTabs.Trigger>
|
|
||||||
|
|
||||||
<NativeTabs.Trigger name="explore">
|
|
||||||
<NativeTabs.Trigger.Label>Explore</NativeTabs.Trigger.Label>
|
|
||||||
<NativeTabs.Trigger.Icon
|
|
||||||
src={require('@/assets/images/tabIcons/explore.png')}
|
|
||||||
renderingMode="template"
|
|
||||||
/>
|
|
||||||
</NativeTabs.Trigger>
|
|
||||||
</NativeTabs>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
import {
|
|
||||||
Tabs,
|
|
||||||
TabList,
|
|
||||||
TabTrigger,
|
|
||||||
TabSlot,
|
|
||||||
TabTriggerSlotProps,
|
|
||||||
TabListProps,
|
|
||||||
} from 'expo-router/ui';
|
|
||||||
import { SymbolView } from 'expo-symbols';
|
|
||||||
import React from 'react';
|
|
||||||
import { Pressable, useColorScheme, View, StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
import { ExternalLink } from './external-link';
|
|
||||||
import { ThemedText } from './themed-text';
|
|
||||||
import { ThemedView } from './themed-view';
|
|
||||||
|
|
||||||
import { Colors, MaxContentWidth, Spacing } from '@/constants/theme';
|
|
||||||
|
|
||||||
export default function AppTabs() {
|
|
||||||
return (
|
|
||||||
<Tabs>
|
|
||||||
<TabSlot style={{ height: '100%' }} />
|
|
||||||
<TabList asChild>
|
|
||||||
<CustomTabList>
|
|
||||||
<TabTrigger name="home" href="/" asChild>
|
|
||||||
<TabButton>Home</TabButton>
|
|
||||||
</TabTrigger>
|
|
||||||
<TabTrigger name="explore" href="/explore" asChild>
|
|
||||||
<TabButton>Explore</TabButton>
|
|
||||||
</TabTrigger>
|
|
||||||
</CustomTabList>
|
|
||||||
</TabList>
|
|
||||||
</Tabs>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TabButton({ children, isFocused, ...props }: TabTriggerSlotProps) {
|
|
||||||
return (
|
|
||||||
<Pressable {...props} style={({ pressed }) => pressed && styles.pressed}>
|
|
||||||
<ThemedView
|
|
||||||
type={isFocused ? 'backgroundSelected' : 'backgroundElement'}
|
|
||||||
style={styles.tabButtonView}>
|
|
||||||
<ThemedText type="small" themeColor={isFocused ? 'text' : 'textSecondary'}>
|
|
||||||
{children}
|
|
||||||
</ThemedText>
|
|
||||||
</ThemedView>
|
|
||||||
</Pressable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CustomTabList(props: TabListProps) {
|
|
||||||
const scheme = useColorScheme();
|
|
||||||
const colors = Colors[scheme === 'unspecified' ? 'light' : scheme];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View {...props} style={styles.tabListContainer}>
|
|
||||||
<ThemedView type="backgroundElement" style={styles.innerContainer}>
|
|
||||||
<ThemedText type="smallBold" style={styles.brandText}>
|
|
||||||
Expo Starter
|
|
||||||
</ThemedText>
|
|
||||||
|
|
||||||
{props.children}
|
|
||||||
|
|
||||||
<ExternalLink href="https://docs.expo.dev" asChild>
|
|
||||||
<Pressable style={styles.externalPressable}>
|
|
||||||
<ThemedText type="link">Docs</ThemedText>
|
|
||||||
<SymbolView
|
|
||||||
tintColor={colors.text}
|
|
||||||
name={{ ios: 'arrow.up.right.square', web: 'link' }}
|
|
||||||
size={12}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
</ExternalLink>
|
|
||||||
</ThemedView>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
tabListContainer: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
padding: Spacing.three,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexDirection: 'row',
|
|
||||||
},
|
|
||||||
innerContainer: {
|
|
||||||
paddingVertical: Spacing.two,
|
|
||||||
paddingHorizontal: Spacing.five,
|
|
||||||
borderRadius: Spacing.five,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexGrow: 1,
|
|
||||||
gap: Spacing.two,
|
|
||||||
maxWidth: MaxContentWidth,
|
|
||||||
},
|
|
||||||
brandText: {
|
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
|
||||||
pressed: {
|
|
||||||
opacity: 0.7,
|
|
||||||
},
|
|
||||||
tabButtonView: {
|
|
||||||
paddingVertical: Spacing.one,
|
|
||||||
paddingHorizontal: Spacing.three,
|
|
||||||
borderRadius: Spacing.three,
|
|
||||||
},
|
|
||||||
externalPressable: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: Spacing.one,
|
|
||||||
marginLeft: Spacing.three,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import { Href, Link } from 'expo-router';
|
|
||||||
import { openBrowserAsync, WebBrowserPresentationStyle } from 'expo-web-browser';
|
|
||||||
import { type ComponentProps } from 'react';
|
|
||||||
|
|
||||||
type Props = Omit<ComponentProps<typeof Link>, 'href'> & { href: Href & string };
|
|
||||||
|
|
||||||
export function ExternalLink({ href, ...rest }: Props) {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
target="_blank"
|
|
||||||
{...rest}
|
|
||||||
href={href}
|
|
||||||
onPress={async (event) => {
|
|
||||||
if (process.env.EXPO_OS !== 'web') {
|
|
||||||
// Prevent the default behavior of linking to the default browser on native.
|
|
||||||
event.preventDefault();
|
|
||||||
// Open the link in an in-app browser.
|
|
||||||
await openBrowserAsync(href, {
|
|
||||||
presentationStyle: WebBrowserPresentationStyle.AUTOMATIC,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import React, { type ReactNode } from 'react';
|
|
||||||
import { View, StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
import { ThemedText } from './themed-text';
|
|
||||||
import { ThemedView } from './themed-view';
|
|
||||||
|
|
||||||
import { Spacing } from '@/constants/theme';
|
|
||||||
|
|
||||||
type HintRowProps = {
|
|
||||||
title?: string;
|
|
||||||
hint?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function HintRow({ title = 'Try editing', hint = 'app/index.tsx' }: HintRowProps) {
|
|
||||||
return (
|
|
||||||
<View style={styles.stepRow}>
|
|
||||||
<ThemedText type="small">{title}</ThemedText>
|
|
||||||
<ThemedView type="backgroundSelected" style={styles.codeSnippet}>
|
|
||||||
<ThemedText themeColor="textSecondary">{hint}</ThemedText>
|
|
||||||
</ThemedView>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
stepRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
codeSnippet: {
|
|
||||||
borderRadius: Spacing.two,
|
|
||||||
paddingVertical: Spacing.half,
|
|
||||||
paddingHorizontal: Spacing.two,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
import { Platform, StyleSheet, Text, type TextProps } from 'react-native';
|
|
||||||
|
|
||||||
import { Fonts, ThemeColor } from '@/constants/theme';
|
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
|
||||||
|
|
||||||
export type ThemedTextProps = TextProps & {
|
|
||||||
type?: 'default' | 'title' | 'small' | 'smallBold' | 'subtitle' | 'link' | 'linkPrimary' | 'code';
|
|
||||||
themeColor?: ThemeColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ThemedText({ style, type = 'default', themeColor, ...rest }: ThemedTextProps) {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
{ color: theme[themeColor ?? 'text'] },
|
|
||||||
type === 'default' && styles.default,
|
|
||||||
type === 'title' && styles.title,
|
|
||||||
type === 'small' && styles.small,
|
|
||||||
type === 'smallBold' && styles.smallBold,
|
|
||||||
type === 'subtitle' && styles.subtitle,
|
|
||||||
type === 'link' && styles.link,
|
|
||||||
type === 'linkPrimary' && styles.linkPrimary,
|
|
||||||
type === 'code' && styles.code,
|
|
||||||
style,
|
|
||||||
]}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
small: {
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 20,
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
|
||||||
smallBold: {
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 20,
|
|
||||||
fontWeight: 700,
|
|
||||||
},
|
|
||||||
default: {
|
|
||||||
fontSize: 16,
|
|
||||||
lineHeight: 24,
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: 48,
|
|
||||||
fontWeight: 600,
|
|
||||||
lineHeight: 52,
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
fontSize: 32,
|
|
||||||
lineHeight: 44,
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
lineHeight: 30,
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
linkPrimary: {
|
|
||||||
lineHeight: 30,
|
|
||||||
fontSize: 14,
|
|
||||||
color: '#3c87f7',
|
|
||||||
},
|
|
||||||
code: {
|
|
||||||
fontFamily: Fonts.mono,
|
|
||||||
fontWeight: Platform.select({ android: 700 }) ?? 500,
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { View, type ViewProps } from 'react-native';
|
|
||||||
|
|
||||||
import { ThemeColor } from '@/constants/theme';
|
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
|
||||||
|
|
||||||
export type ThemedViewProps = ViewProps & {
|
|
||||||
lightColor?: string;
|
|
||||||
darkColor?: string;
|
|
||||||
type?: ThemeColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ThemedView({ style, lightColor, darkColor, type, ...otherProps }: ThemedViewProps) {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return <View style={[{ backgroundColor: theme[type ?? 'background'] }, style]} {...otherProps} />;
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import { SymbolView } from 'expo-symbols';
|
|
||||||
import { PropsWithChildren, useState } from 'react';
|
|
||||||
import { Pressable, StyleSheet } from 'react-native';
|
|
||||||
import Animated, { FadeIn } from 'react-native-reanimated';
|
|
||||||
|
|
||||||
import { ThemedText } from '@/components/themed-text';
|
|
||||||
import { ThemedView } from '@/components/themed-view';
|
|
||||||
import { Spacing } from '@/constants/theme';
|
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
|
||||||
|
|
||||||
export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemedView>
|
|
||||||
<Pressable
|
|
||||||
style={({ pressed }) => [styles.heading, pressed && styles.pressedHeading]}
|
|
||||||
onPress={() => setIsOpen((value) => !value)}>
|
|
||||||
<ThemedView type="backgroundElement" style={styles.button}>
|
|
||||||
<SymbolView
|
|
||||||
name={{ ios: 'chevron.right', android: 'chevron_right', web: 'chevron_right' }}
|
|
||||||
size={14}
|
|
||||||
weight="bold"
|
|
||||||
tintColor={theme.text}
|
|
||||||
style={{ transform: [{ rotate: isOpen ? '-90deg' : '90deg' }] }}
|
|
||||||
/>
|
|
||||||
</ThemedView>
|
|
||||||
|
|
||||||
<ThemedText type="small">{title}</ThemedText>
|
|
||||||
</Pressable>
|
|
||||||
{isOpen && (
|
|
||||||
<Animated.View entering={FadeIn.duration(200)}>
|
|
||||||
<ThemedView type="backgroundElement" style={styles.content}>
|
|
||||||
{children}
|
|
||||||
</ThemedView>
|
|
||||||
</Animated.View>
|
|
||||||
)}
|
|
||||||
</ThemedView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
heading: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: Spacing.two,
|
|
||||||
},
|
|
||||||
pressedHeading: {
|
|
||||||
opacity: 0.7,
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
width: Spacing.four,
|
|
||||||
height: Spacing.four,
|
|
||||||
borderRadius: 12,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
marginTop: Spacing.three,
|
|
||||||
borderRadius: Spacing.three,
|
|
||||||
marginLeft: Spacing.four,
|
|
||||||
padding: Spacing.four,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import { version } from 'expo/package.json';
|
|
||||||
import { Image } from 'expo-image';
|
|
||||||
import React from 'react';
|
|
||||||
import { useColorScheme, StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
import { ThemedText } from './themed-text';
|
|
||||||
import { ThemedView } from './themed-view';
|
|
||||||
|
|
||||||
import { Spacing } from '@/constants/theme';
|
|
||||||
|
|
||||||
export function WebBadge() {
|
|
||||||
const scheme = useColorScheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemedView style={styles.container}>
|
|
||||||
<ThemedText type="code" themeColor="textSecondary" style={styles.versionText}>
|
|
||||||
v{version}
|
|
||||||
</ThemedText>
|
|
||||||
<Image
|
|
||||||
source={
|
|
||||||
scheme === 'dark'
|
|
||||||
? require('@/assets/images/expo-badge-white.png')
|
|
||||||
: require('@/assets/images/expo-badge.png')
|
|
||||||
}
|
|
||||||
style={styles.badgeImage}
|
|
||||||
/>
|
|
||||||
</ThemedView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
padding: Spacing.five,
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: Spacing.two,
|
|
||||||
},
|
|
||||||
versionText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
badgeImage: {
|
|
||||||
width: 123,
|
|
||||||
aspectRatio: 123 / 24,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
/**
|
|
||||||
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
|
|
||||||
* There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import '@/global.css';
|
|
||||||
|
|
||||||
import { Platform } from 'react-native';
|
|
||||||
|
|
||||||
export const Colors = {
|
|
||||||
light: {
|
|
||||||
text: '#000000',
|
|
||||||
background: '#ffffff',
|
|
||||||
backgroundElement: '#F0F0F3',
|
|
||||||
backgroundSelected: '#E0E1E6',
|
|
||||||
textSecondary: '#60646C',
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
text: '#ffffff',
|
|
||||||
background: '#000000',
|
|
||||||
backgroundElement: '#212225',
|
|
||||||
backgroundSelected: '#2E3135',
|
|
||||||
textSecondary: '#B0B4BA',
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type ThemeColor = keyof typeof Colors.light & keyof typeof Colors.dark;
|
|
||||||
|
|
||||||
export const Fonts = Platform.select({
|
|
||||||
ios: {
|
|
||||||
/** iOS `UIFontDescriptorSystemDesignDefault` */
|
|
||||||
sans: 'system-ui',
|
|
||||||
/** iOS `UIFontDescriptorSystemDesignSerif` */
|
|
||||||
serif: 'ui-serif',
|
|
||||||
/** iOS `UIFontDescriptorSystemDesignRounded` */
|
|
||||||
rounded: 'ui-rounded',
|
|
||||||
/** iOS `UIFontDescriptorSystemDesignMonospaced` */
|
|
||||||
mono: 'ui-monospace',
|
|
||||||
},
|
|
||||||
default: {
|
|
||||||
sans: 'normal',
|
|
||||||
serif: 'serif',
|
|
||||||
rounded: 'normal',
|
|
||||||
mono: 'monospace',
|
|
||||||
},
|
|
||||||
web: {
|
|
||||||
sans: 'var(--font-display)',
|
|
||||||
serif: 'var(--font-serif)',
|
|
||||||
rounded: 'var(--font-rounded)',
|
|
||||||
mono: 'var(--font-mono)',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Spacing = {
|
|
||||||
half: 2,
|
|
||||||
one: 4,
|
|
||||||
two: 8,
|
|
||||||
three: 16,
|
|
||||||
four: 24,
|
|
||||||
five: 32,
|
|
||||||
six: 64,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const BottomTabInset = Platform.select({ ios: 50, android: 80 }) ?? 0;
|
|
||||||
export const MaxContentWidth = 800;
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-display:
|
--font-display:
|
||||||
Spline Sans, Inter, ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji,
|
Spline Sans, Inter, ui-sans-serif, system-ui, sans-serif, Apple Color Emoji,
|
||||||
Segoe UI Symbol, Noto Color Emoji;
|
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
--font-mono:
|
--font-mono:
|
||||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono,
|
||||||
--font-rounded: 'SF Pro Rounded', 'Hiragino Maru Gothic ProN', Meiryo, 'MS PGothic', sans-serif;
|
Courier New, monospace;
|
||||||
--font-serif: Georgia, 'Times New Roman', serif;
|
--font-rounded:
|
||||||
|
"SF Pro Rounded", "Hiragino Maru Gothic ProN", Meiryo, "MS PGothic",
|
||||||
|
sans-serif;
|
||||||
|
--font-serif: Georgia, "Times New Roman", serif;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { useColorScheme } from 'react-native';
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useColorScheme as useRNColorScheme } from 'react-native';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To support static rendering, this value needs to be re-calculated on the client side for web
|
|
||||||
*/
|
|
||||||
export function useColorScheme() {
|
|
||||||
const [hasHydrated, setHasHydrated] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setHasHydrated(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const colorScheme = useRNColorScheme();
|
|
||||||
|
|
||||||
if (hasHydrated) {
|
|
||||||
return colorScheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'light';
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
/**
|
|
||||||
* Learn more about light and dark modes:
|
|
||||||
* https://docs.expo.dev/guides/color-schemes/
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Colors } from '@/constants/theme';
|
|
||||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
|
||||||
|
|
||||||
export function useTheme() {
|
|
||||||
const scheme = useColorScheme();
|
|
||||||
const theme = scheme === 'unspecified' ? 'light' : scheme;
|
|
||||||
|
|
||||||
return Colors[theme];
|
|
||||||
}
|
|
||||||
1
src/nativewind-env.d.ts
vendored
Normal file
1
src/nativewind-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="nativewind/types" />
|
||||||
10
tailwind.config.js
Normal file
10
tailwind.config.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
// NOTE: Update this to include the paths to all files that contain Nativewind classes.
|
||||||
|
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
||||||
|
presets: [require("nativewind/preset")],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".expo/types/**/*.ts",
|
".expo/types/**/*.ts",
|
||||||
"expo-env.d.ts"
|
"expo-env.d.ts",
|
||||||
|
"nativewind-env.d.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue