docs: add basic example app
BIN
packages/example/assets/album-art-1.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
packages/example/assets/album-art-2.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
packages/example/assets/album-art-3.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
packages/example/assets/album-art-4.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
packages/example/assets/album-art-5.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
packages/example/assets/album-art-6.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
packages/example/assets/album-art-7.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
packages/example/assets/album-art-8.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
packages/example/assets/avatar-1.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
packages/example/assets/avatar-2.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
packages/example/assets/book.jpg
Normal file
|
After Width: | Height: | Size: 98 KiB |
@@ -18,7 +18,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-community/masked-view": "^0.1.1",
|
||||
"@expo/vector-icons": "10.0.3",
|
||||
"expo": "^34.0.1",
|
||||
"expo-asset": "4.0.0",
|
||||
"react": "16.8.3",
|
||||
"react-dom": "^16.8.3",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",
|
||||
|
||||
62
packages/example/src/Screens/BottomTabs.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable import/namespace, import/default */
|
||||
|
||||
import * as React from 'react';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import createBottomTabNavigator from '@navigation-ex/bottom-tabs';
|
||||
// @ts-ignore
|
||||
import TouchableBounce from 'react-native/Libraries/Components/Touchable/TouchableBounce';
|
||||
import Albums from '../Shared/Albums';
|
||||
import Contacts from '../Shared/Contacts';
|
||||
import Chat from '../Shared/Chat';
|
||||
|
||||
const getTabBarIcon = (name: string) => ({
|
||||
tintColor,
|
||||
horizontal,
|
||||
}: {
|
||||
tintColor: string;
|
||||
horizontal: boolean;
|
||||
}) => (
|
||||
<MaterialIcons name={name} color={tintColor} size={horizontal ? 17 : 24} />
|
||||
);
|
||||
|
||||
type BottomTabParams = {
|
||||
albums: undefined;
|
||||
contacts: undefined;
|
||||
chat: undefined;
|
||||
};
|
||||
|
||||
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
|
||||
|
||||
export default function BottomTabsScreen() {
|
||||
return (
|
||||
<BottomTabs.Navigator>
|
||||
<BottomTabs.Screen
|
||||
name="chat"
|
||||
component={Chat}
|
||||
options={{
|
||||
title: 'Chat',
|
||||
tabBarIcon: getTabBarIcon('chat-bubble'),
|
||||
tabBarButtonComponent: TouchableBounce,
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="contacts"
|
||||
component={Contacts}
|
||||
options={{
|
||||
title: 'Contacts',
|
||||
tabBarIcon: getTabBarIcon('contacts'),
|
||||
tabBarButtonComponent: TouchableBounce,
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="albums"
|
||||
component={Albums}
|
||||
options={{
|
||||
title: 'Albums',
|
||||
tabBarIcon: getTabBarIcon('photo-album'),
|
||||
tabBarButtonComponent: TouchableBounce,
|
||||
}}
|
||||
/>
|
||||
</BottomTabs.Navigator>
|
||||
);
|
||||
}
|
||||
54
packages/example/src/Screens/MaterialTabs.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import createMaterialTopTabNavigator from '@navigation-ex/material-top-tabs';
|
||||
import Albums from '../Shared/Albums';
|
||||
import Contacts from '../Shared/Contacts';
|
||||
import Chat from '../Shared/Chat';
|
||||
|
||||
type MaterialTabParams = {
|
||||
albums: undefined;
|
||||
contacts: undefined;
|
||||
chat: undefined;
|
||||
};
|
||||
|
||||
const MaterialTabs = createMaterialTopTabNavigator<MaterialTabParams>();
|
||||
|
||||
export default function MaterialTabsScreen() {
|
||||
return (
|
||||
<MaterialTabs.Navigator
|
||||
tabBarOptions={{
|
||||
style: styles.tabBar,
|
||||
labelStyle: styles.tabLabel,
|
||||
indicatorStyle: styles.tabIndicator,
|
||||
}}
|
||||
>
|
||||
<MaterialTabs.Screen
|
||||
name="chat"
|
||||
component={Chat}
|
||||
options={{ title: 'Chat' }}
|
||||
/>
|
||||
<MaterialTabs.Screen
|
||||
name="contacts"
|
||||
component={Contacts}
|
||||
options={{ title: 'Contacts' }}
|
||||
/>
|
||||
<MaterialTabs.Screen
|
||||
name="albums"
|
||||
component={Albums}
|
||||
options={{ title: 'Albums' }}
|
||||
/>
|
||||
</MaterialTabs.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabBar: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
tabLabel: {
|
||||
color: 'black',
|
||||
},
|
||||
tabIndicator: {
|
||||
backgroundColor: 'tomato',
|
||||
},
|
||||
});
|
||||
110
packages/example/src/Screens/SimpleStack.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { RouteProp, ParamListBase } from '@navigation-ex/core';
|
||||
import createStackNavigator, {
|
||||
StackNavigationProp,
|
||||
} from '@navigation-ex/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
|
||||
type SimpleStackParams = {
|
||||
article: { author: string };
|
||||
album: undefined;
|
||||
};
|
||||
|
||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
}: {
|
||||
navigation: SimpleStackNavigation;
|
||||
route: RouteProp<SimpleStackParams, 'article'>;
|
||||
}) => (
|
||||
<React.Fragment>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('album')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: route.params.author }} />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const AlbumsScreen = ({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: SimpleStackNavigation;
|
||||
}) => (
|
||||
<React.Fragment>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('article', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||
|
||||
export default function SimpleStackScreen({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: StackNavigationProp<ParamListBase>;
|
||||
}) {
|
||||
navigation.setOptions({
|
||||
header: null,
|
||||
});
|
||||
|
||||
return (
|
||||
<SimpleStack.Navigator>
|
||||
<SimpleStack.Screen
|
||||
name="article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params.author}`,
|
||||
})}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<SimpleStack.Screen
|
||||
name="album"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Album' }}
|
||||
/>
|
||||
</SimpleStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
42
packages/example/src/Shared/Albums.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as React from 'react';
|
||||
import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native';
|
||||
|
||||
const COVERS = [
|
||||
require('../../assets/album-art-1.jpg'),
|
||||
require('../../assets/album-art-2.jpg'),
|
||||
require('../../assets/album-art-3.jpg'),
|
||||
require('../../assets/album-art-4.jpg'),
|
||||
require('../../assets/album-art-5.jpg'),
|
||||
require('../../assets/album-art-6.jpg'),
|
||||
require('../../assets/album-art-7.jpg'),
|
||||
require('../../assets/album-art-8.jpg'),
|
||||
];
|
||||
|
||||
export default class Albums extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={styles.content}
|
||||
>
|
||||
{COVERS.map((source, i) => (
|
||||
<Image key={i} source={source} style={styles.cover} />
|
||||
))}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: '#343C46',
|
||||
},
|
||||
content: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
cover: {
|
||||
width: '50%',
|
||||
height: Dimensions.get('window').width / 2,
|
||||
},
|
||||
});
|
||||
115
packages/example/src/Shared/Article.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import * as React from 'react';
|
||||
import { View, Text, Image, ScrollView, StyleSheet } from 'react-native';
|
||||
|
||||
type Props = {
|
||||
date: string;
|
||||
author: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default class Article extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
date: '1st Jan 2025',
|
||||
author: {
|
||||
name: 'Knowledge Bot',
|
||||
},
|
||||
};
|
||||
|
||||
render() {
|
||||
const { date, author } = this.props;
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={styles.content}
|
||||
>
|
||||
<View style={styles.author}>
|
||||
<Image
|
||||
style={styles.avatar}
|
||||
source={require('../../assets/avatar-1.png')}
|
||||
/>
|
||||
<View style={styles.meta}>
|
||||
<Text style={styles.name}>{author.name}</Text>
|
||||
<Text style={styles.timestamp}>{date}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.title}>Lorem Ipsum</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Contrary to popular belief, Lorem Ipsum is not simply random text. It
|
||||
has roots in a piece of classical Latin literature from 45 BC, making
|
||||
it over 2000 years old.
|
||||
</Text>
|
||||
<Image style={styles.image} source={require('../../assets/book.jpg')} />
|
||||
<Text style={styles.paragraph}>
|
||||
Richard McClintock, a Latin professor at Hampden-Sydney College in
|
||||
Virginia, looked up one of the more obscure Latin words, consectetur,
|
||||
from a Lorem Ipsum passage, and going through the cites of the word in
|
||||
classical literature, discovered the undoubtable source.
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de
|
||||
Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by
|
||||
Cicero, written in 45 BC. This book is a treatise on the theory of
|
||||
ethics, very popular during the Renaissance. The first line of Lorem
|
||||
Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in
|
||||
section 1.10.32.
|
||||
</Text>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
content: {
|
||||
paddingVertical: 16,
|
||||
},
|
||||
author: {
|
||||
flexDirection: 'row',
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
meta: {
|
||||
marginHorizontal: 8,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
name: {
|
||||
color: '#000',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
},
|
||||
timestamp: {
|
||||
color: '#999',
|
||||
fontSize: 14,
|
||||
lineHeight: 21,
|
||||
},
|
||||
avatar: {
|
||||
height: 48,
|
||||
width: 48,
|
||||
borderRadius: 24,
|
||||
},
|
||||
title: {
|
||||
color: '#000',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 36,
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
paragraph: {
|
||||
color: '#000',
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: 200,
|
||||
resizeMode: 'cover',
|
||||
marginVertical: 8,
|
||||
},
|
||||
});
|
||||
114
packages/example/src/Shared/Chat.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
View,
|
||||
Image,
|
||||
Text,
|
||||
TextInput,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
|
||||
const MESSAGES = [
|
||||
'okay',
|
||||
'sudo make me a sandwich',
|
||||
'what? make it yourself',
|
||||
'make me a sandwich',
|
||||
];
|
||||
|
||||
export default class Chat extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScrollView
|
||||
style={styles.inverted}
|
||||
contentContainerStyle={styles.content}
|
||||
>
|
||||
{MESSAGES.map((text, i) => {
|
||||
const odd = i % 2;
|
||||
|
||||
return (
|
||||
<View
|
||||
key={i}
|
||||
style={[odd ? styles.odd : styles.even, styles.inverted]}
|
||||
>
|
||||
<Image
|
||||
style={styles.avatar}
|
||||
source={
|
||||
odd
|
||||
? require('../../assets/avatar-2.png')
|
||||
: require('../../assets/avatar-1.png')
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={[styles.bubble, odd ? styles.received : styles.sent]}
|
||||
>
|
||||
<Text style={odd ? styles.receivedText : styles.sentText}>
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Write a message"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#eceff1',
|
||||
},
|
||||
inverted: {
|
||||
transform: [{ scaleY: -1 }],
|
||||
},
|
||||
content: {
|
||||
padding: 16,
|
||||
},
|
||||
even: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
odd: {
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
avatar: {
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 6,
|
||||
height: 40,
|
||||
width: 40,
|
||||
borderRadius: 20,
|
||||
borderColor: 'rgba(0, 0, 0, .16)',
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
},
|
||||
bubble: {
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 6,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 20,
|
||||
},
|
||||
sent: {
|
||||
backgroundColor: '#cfd8dc',
|
||||
},
|
||||
received: {
|
||||
backgroundColor: '#2196F3',
|
||||
},
|
||||
sentText: {
|
||||
color: 'black',
|
||||
},
|
||||
receivedText: {
|
||||
color: 'white',
|
||||
},
|
||||
input: {
|
||||
height: 48,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 24,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
});
|
||||
136
packages/example/src/Shared/Contacts.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import * as React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
|
||||
type Item = { name: string; number: number };
|
||||
|
||||
const CONTACTS: Item[] = [
|
||||
{ name: 'Marissa Castillo', number: 7766398169 },
|
||||
{ name: 'Denzel Curry', number: 9394378449 },
|
||||
{ name: 'Miles Ferguson', number: 8966872888 },
|
||||
{ name: 'Desiree Webster', number: 6818656371 },
|
||||
{ name: 'Samantha Young', number: 6538288534 },
|
||||
{ name: 'Irene Hunter', number: 2932176249 },
|
||||
{ name: 'Annie Ryan', number: 4718456627 },
|
||||
{ name: 'Sasha Oliver', number: 9743195919 },
|
||||
{ name: 'Jarrod Avila', number: 8339212305 },
|
||||
{ name: 'Griffin Weaver', number: 6059349721 },
|
||||
{ name: 'Emilee Moss', number: 7382905180 },
|
||||
{ name: 'Angelique Oliver', number: 9689298436 },
|
||||
{ name: 'Emanuel Little', number: 6673376805 },
|
||||
{ name: 'Wayne Day', number: 6918839582 },
|
||||
{ name: 'Lauren Reese', number: 4652613201 },
|
||||
{ name: 'Kailey Ward', number: 2232609512 },
|
||||
{ name: 'Gabrielle Newman', number: 2837997127 },
|
||||
{ name: 'Luke Strickland', number: 8404732322 },
|
||||
{ name: 'Payton Garza', number: 7916140875 },
|
||||
{ name: 'Anna Moss', number: 3504954657 },
|
||||
{ name: 'Kailey Vazquez', number: 3002136330 },
|
||||
{ name: 'Jennifer Coleman', number: 5469629753 },
|
||||
{ name: 'Cindy Casey', number: 8446175026 },
|
||||
{ name: 'Dillon Doyle', number: 5614510703 },
|
||||
{ name: 'Savannah Garcia', number: 5634775094 },
|
||||
{ name: 'Kailey Hudson', number: 3289239675 },
|
||||
{ name: 'Ariel Green', number: 2103492196 },
|
||||
{ name: 'Weston Perez', number: 2984221823 },
|
||||
{ name: 'Kari Juarez', number: 9502125065 },
|
||||
{ name: 'Sara Sanders', number: 7696668206 },
|
||||
{ name: 'Griffin Le', number: 3396937040 },
|
||||
{ name: 'Fernando Valdez', number: 9124257306 },
|
||||
{ name: 'Taylor Marshall', number: 9656072372 },
|
||||
{ name: 'Elias Dunn', number: 9738536473 },
|
||||
{ name: 'Diane Barrett', number: 6886824829 },
|
||||
{ name: 'Samuel Freeman', number: 5523948094 },
|
||||
{ name: 'Irene Garza', number: 2077694008 },
|
||||
{ name: 'Devante Alvarez', number: 9897002645 },
|
||||
{ name: 'Sydney Floyd', number: 6462897254 },
|
||||
{ name: 'Toni Dixon', number: 3775448213 },
|
||||
{ name: 'Anastasia Spencer', number: 4548212752 },
|
||||
{ name: 'Reid Cortez', number: 6668056507 },
|
||||
{ name: 'Ramon Duncan', number: 8889157751 },
|
||||
{ name: 'Kenny Moreno', number: 5748219540 },
|
||||
{ name: 'Shelby Craig', number: 9473708675 },
|
||||
{ name: 'Jordyn Brewer', number: 7552277991 },
|
||||
{ name: 'Tanya Walker', number: 4308189657 },
|
||||
{ name: 'Nolan Figueroa', number: 9173443776 },
|
||||
{ name: 'Sophia Gibbs', number: 6435942770 },
|
||||
{ name: 'Vincent Sandoval', number: 2606111495 },
|
||||
];
|
||||
|
||||
class ContactItem extends React.PureComponent<{
|
||||
item: { name: string; number: number };
|
||||
}> {
|
||||
render() {
|
||||
const { item } = this.props;
|
||||
|
||||
return (
|
||||
<View style={styles.item}>
|
||||
<View style={styles.avatar}>
|
||||
<Text style={styles.letter}>
|
||||
{item.name.slice(0, 1).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.details}>
|
||||
<Text style={styles.name}>{item.name}</Text>
|
||||
<Text style={styles.number}>{item.number}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class Contacts extends React.Component {
|
||||
private renderItem = ({ item }: { item: Item }) => (
|
||||
<ContactItem item={item} />
|
||||
);
|
||||
|
||||
private ItemSeparator = () => <View style={styles.separator} />;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FlatList
|
||||
data={CONTACTS}
|
||||
keyExtractor={(_, i) => String(i)}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={this.ItemSeparator}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
item: {
|
||||
backgroundColor: 'white',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 8,
|
||||
},
|
||||
avatar: {
|
||||
height: 36,
|
||||
width: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: '#e91e63',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
letter: {
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
details: {
|
||||
margin: 8,
|
||||
},
|
||||
name: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 14,
|
||||
color: 'black',
|
||||
},
|
||||
number: {
|
||||
fontSize: 12,
|
||||
color: '#999',
|
||||
},
|
||||
separator: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: 'rgba(0, 0, 0, .08)',
|
||||
},
|
||||
});
|
||||
99
packages/example/src/Shared/Profile.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import * as React from 'react';
|
||||
import { View, Text, Image, ScrollView, StyleSheet } from 'react-native';
|
||||
|
||||
export default class Profile extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={styles.content}
|
||||
>
|
||||
<View style={styles.author}>
|
||||
<Image
|
||||
style={styles.avatar}
|
||||
source={require('../../assets/avatar-1.png')}
|
||||
/>
|
||||
<View style={styles.meta}>
|
||||
<Text style={styles.name}>Knowledge Bot</Text>
|
||||
<Text style={styles.timestamp}>1st Jan 2025</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.title}>Lorem Ipsum</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Contrary to popular belief, Lorem Ipsum is not simply random text. It
|
||||
has roots in a piece of classical Latin literature from 45 BC, making
|
||||
it over 2000 years old.
|
||||
</Text>
|
||||
<Image style={styles.image} source={require('../../assets/book.jpg')} />
|
||||
<Text style={styles.paragraph}>
|
||||
Richard McClintock, a Latin professor at Hampden-Sydney College in
|
||||
Virginia, looked up one of the more obscure Latin words, consectetur,
|
||||
from a Lorem Ipsum passage, and going through the cites of the word in
|
||||
classical literature, discovered the undoubtable source.
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de
|
||||
Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by
|
||||
Cicero, written in 45 BC. This book is a treatise on the theory of
|
||||
ethics, very popular during the Renaissance. The first line of Lorem
|
||||
Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in
|
||||
section 1.10.32.
|
||||
</Text>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
content: {
|
||||
paddingVertical: 16,
|
||||
},
|
||||
author: {
|
||||
flexDirection: 'row',
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
meta: {
|
||||
marginHorizontal: 8,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
name: {
|
||||
color: '#000',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
},
|
||||
timestamp: {
|
||||
color: '#999',
|
||||
fontSize: 14,
|
||||
lineHeight: 21,
|
||||
},
|
||||
avatar: {
|
||||
height: 48,
|
||||
width: 48,
|
||||
borderRadius: 24,
|
||||
},
|
||||
title: {
|
||||
color: '#000',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 36,
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
paragraph: {
|
||||
color: '#000',
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: 200,
|
||||
resizeMode: 'cover',
|
||||
marginVertical: 8,
|
||||
},
|
||||
});
|
||||
@@ -1,152 +1,45 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Platform,
|
||||
AsyncStorage,
|
||||
YellowBox,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import {
|
||||
NavigationContainer,
|
||||
CompositeNavigationProp,
|
||||
NavigationHelpers,
|
||||
RouteProp,
|
||||
InitialState,
|
||||
useFocusEffect,
|
||||
} from '@navigation-ex/core';
|
||||
import { ScrollView, AsyncStorage, YellowBox } from 'react-native';
|
||||
import { Appbar, List } from 'react-native-paper';
|
||||
import { Asset } from 'expo-asset';
|
||||
import { NavigationContainer, InitialState } from '@navigation-ex/core';
|
||||
import createDrawerNavigator, {
|
||||
DrawerNavigationProp,
|
||||
} from '@navigation-ex/drawer';
|
||||
import createStackNavigator, {
|
||||
Assets as StackAssets,
|
||||
StackNavigationProp,
|
||||
} from '@navigation-ex/stack';
|
||||
import createMaterialTopTabNavigator, {
|
||||
MaterialTopTabNavigationProp,
|
||||
} from '@navigation-ex/material-top-tabs';
|
||||
|
||||
type StackParamList = {
|
||||
first: { author: string };
|
||||
second: undefined;
|
||||
third: undefined;
|
||||
};
|
||||
|
||||
type TabParamList = {
|
||||
fourth: undefined;
|
||||
fifth: undefined;
|
||||
};
|
||||
import SimpleStackScreen from './Screens/SimpleStack';
|
||||
import BottomTabsScreen from './Screens/BottomTabs';
|
||||
import MaterialTabsScreen from './Screens/MaterialTabs';
|
||||
|
||||
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
||||
|
||||
const Stack = createStackNavigator<StackParamList>();
|
||||
|
||||
const Tab = createMaterialTopTabNavigator<TabParamList>();
|
||||
|
||||
const First = ({
|
||||
navigation,
|
||||
route,
|
||||
}: {
|
||||
navigation: CompositeNavigationProp<
|
||||
StackNavigationProp<StackParamList, 'first'>,
|
||||
NavigationHelpers<TabParamList>
|
||||
>;
|
||||
route: RouteProp<StackParamList, 'first'>;
|
||||
}) => {
|
||||
const updateTitle = React.useCallback(() => {
|
||||
if (Platform.OS !== 'web') {
|
||||
return;
|
||||
}
|
||||
|
||||
document.title = `${route.name} (${route.params.author})`;
|
||||
|
||||
return () => {
|
||||
document.title = '';
|
||||
};
|
||||
}, [route.name, route.params.author]);
|
||||
|
||||
useFocusEffect(updateTitle);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text style={styles.title}>First, {route.params.author}</Text>
|
||||
<Button onPress={() => navigation.push('second')}>Push second</Button>
|
||||
<Button onPress={() => navigation.push('third')}>Push third</Button>
|
||||
<Button onPress={() => navigation.navigate('fourth')}>
|
||||
Navigate to fourth
|
||||
</Button>
|
||||
<Button onPress={() => navigation.navigate('first', { author: 'John' })}>
|
||||
Navigate with params
|
||||
</Button>
|
||||
<Button onPress={() => navigation.pop()}>Pop</Button>
|
||||
</View>
|
||||
);
|
||||
type RootDrawerParamList = {
|
||||
Root: undefined;
|
||||
};
|
||||
|
||||
const Second = ({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: CompositeNavigationProp<
|
||||
StackNavigationProp<StackParamList, 'second'>,
|
||||
NavigationHelpers<TabParamList>
|
||||
>;
|
||||
}) => {
|
||||
const [count, setCount] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setInterval(() => setCount(c => c + 1), 1000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
navigation.setOptions({
|
||||
title: `Count ${count}`,
|
||||
});
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text style={styles.title}>Second</Text>
|
||||
<Button onPress={() => navigation.push('first', { author: 'Joel' })}>
|
||||
Push first
|
||||
</Button>
|
||||
<Button onPress={() => navigation.pop()}>Pop</Button>
|
||||
</View>
|
||||
);
|
||||
type RootStackParamList = {
|
||||
Home: undefined;
|
||||
} & {
|
||||
[P in keyof typeof SCREENS]: undefined;
|
||||
};
|
||||
|
||||
const Fourth = ({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: CompositeNavigationProp<
|
||||
MaterialTopTabNavigationProp<TabParamList, 'fourth'>,
|
||||
StackNavigationProp<StackParamList>
|
||||
>;
|
||||
}) => (
|
||||
<View>
|
||||
<Text style={styles.title}>Fourth</Text>
|
||||
<Button onPress={() => navigation.jumpTo('fifth')}>Jump to fifth</Button>
|
||||
<Button onPress={() => navigation.push('first', { author: 'Jake' })}>
|
||||
Push first
|
||||
</Button>
|
||||
<Button onPress={() => navigation.goBack()}>Go back</Button>
|
||||
</View>
|
||||
);
|
||||
const SCREENS = {
|
||||
SimpleStack: { title: 'Simple Stack', component: SimpleStackScreen },
|
||||
BottomTabs: { title: 'Bottom Tabs', component: BottomTabsScreen },
|
||||
MaterialTabs: { title: 'Material Tabs', component: MaterialTabsScreen },
|
||||
};
|
||||
|
||||
const Fifth = ({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: CompositeNavigationProp<
|
||||
MaterialTopTabNavigationProp<TabParamList, 'fifth'>,
|
||||
StackNavigationProp<StackParamList>
|
||||
>;
|
||||
}) => (
|
||||
<View>
|
||||
<Text style={styles.title}>Fifth</Text>
|
||||
<Button onPress={() => navigation.jumpTo('fourth')}>Jump to fourth</Button>
|
||||
<Button onPress={() => navigation.push('second')}>Push second</Button>
|
||||
<Button onPress={() => navigation.pop()}>Pop</Button>
|
||||
</View>
|
||||
);
|
||||
const Drawer = createDrawerNavigator<RootDrawerParamList>();
|
||||
const Stack = createStackNavigator<RootStackParamList>();
|
||||
|
||||
const PERSISTENCE_KEY = 'NAVIGATION_STATE';
|
||||
|
||||
Asset.loadAsync(StackAssets);
|
||||
|
||||
export default function App() {
|
||||
const [isReady, setIsReady] = React.useState(false);
|
||||
const [initialState, setInitialState] = React.useState<
|
||||
@@ -181,43 +74,58 @@ export default function App() {
|
||||
AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state))
|
||||
}
|
||||
>
|
||||
<Stack.Navigator>
|
||||
<Stack.Screen
|
||||
name="first"
|
||||
component={First}
|
||||
options={({ route }) => ({
|
||||
title: `Foo (${route.params ? route.params.author : ''})`,
|
||||
})}
|
||||
initialParams={{ author: 'Jane' }}
|
||||
/>
|
||||
<Stack.Screen name="second" options={{ title: 'Bar' }}>
|
||||
{props => <Second {...props} />}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen name="third" options={{ title: 'Baz' }}>
|
||||
{() => (
|
||||
<Tab.Navigator initialRouteName="fifth">
|
||||
<Tab.Screen
|
||||
name="fourth"
|
||||
component={Fourth}
|
||||
options={{ title: 'This' }}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="fifth"
|
||||
component={Fifth}
|
||||
options={{ title: 'That' }}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
<Drawer.Navigator>
|
||||
<Drawer.Screen name="Root" options={{ title: 'Examples' }}>
|
||||
{({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: DrawerNavigationProp<RootDrawerParamList>;
|
||||
}) => (
|
||||
<Stack.Navigator>
|
||||
<Stack.Screen
|
||||
name="Home"
|
||||
options={{
|
||||
title: 'Examples',
|
||||
headerLeft: () => (
|
||||
<Appbar.Action
|
||||
icon="menu"
|
||||
onPress={() => navigation.toggleDrawer()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: StackNavigationProp<RootStackParamList>;
|
||||
}) => (
|
||||
<ScrollView>
|
||||
{(Object.keys(SCREENS) as Array<keyof typeof SCREENS>).map(
|
||||
name => (
|
||||
<List.Item
|
||||
key={name}
|
||||
title={SCREENS[name].title}
|
||||
onPress={() => navigation.push(name)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</ScrollView>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
{(Object.keys(SCREENS) as Array<keyof typeof SCREENS>).map(
|
||||
name => (
|
||||
<Stack.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
component={SCREENS[name].component}
|
||||
options={{ title: SCREENS[name].title }}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Stack.Navigator>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
</Stack.Navigator>
|
||||
</Drawer.Screen>
|
||||
</Drawer.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
},
|
||||
});
|
||||
|
||||
10
yarn.lock
@@ -1300,7 +1300,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@expo/traveling-fastlane-linux/-/traveling-fastlane-linux-1.9.11.tgz#a26a94c0e4b118130da27a7edb3410d67706b81b"
|
||||
integrity sha512-xCHnqKfwWYygQfRbi9MGNSG28EHKDtzMB+QDklSH1GeD90TA0beAjlNF+Z+KPxj28s3WZlQr9yw1AqBS7aipcw==
|
||||
|
||||
"@expo/vector-icons@^10.0.2":
|
||||
"@expo/vector-icons@10.0.3", "@expo/vector-icons@^10.0.2":
|
||||
version "10.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@expo/vector-icons/-/vector-icons-10.0.3.tgz#9dec25cf6c29871f2bb1fe932029878c221f1f75"
|
||||
integrity sha512-3iTdjnBlleddgcRGZV9JQXi+WRL3n2BehW48JOFv/mydx8BjHD0QAcYLOXGuwrcRKR0AIRhHxFQwKVyQ4YcYLA==
|
||||
@@ -5572,6 +5572,14 @@ expo-app-loader-provider@~6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/expo-app-loader-provider/-/expo-app-loader-provider-6.0.0.tgz#c187a39942ac27cfaec3b394a5c9851d3f39678b"
|
||||
integrity sha512-GtpztJVxOz+vVwdLyHskpzVzFWMXZPIFC/zczHZPsTwjS+wXj6n8MVaLxX6GaTyhNEtYjp0VIQUw3b7eP+vO6w==
|
||||
|
||||
expo-asset@4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-4.0.0.tgz#41f8de836b0e1426dd4ae461cffbcaad0fdc4e15"
|
||||
integrity sha512-oFIaq1V3D23wos3Lc1+3ABAAbXTytVDCXF0lTSDNsCEGHk+rt2WUedD0FL+RNw+C4zG7aqxI6cPaDNc+4lyKdA==
|
||||
dependencies:
|
||||
path-browserify "^1.0.0"
|
||||
url-parse "^1.4.4"
|
||||
|
||||
expo-asset@~6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-6.0.0.tgz#caa3f45e7a27d978f8055fc58df6e33a4e661937"
|
||||
|
||||