Skip to content

Commit

Permalink
Add home screen content
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen Hanson committed Aug 22, 2024
1 parent d1a8dbc commit 4d501b5
Show file tree
Hide file tree
Showing 14 changed files with 364 additions and 172 deletions.
9 changes: 6 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,14 @@ One way to build new features is to generate a new Belt app locally using the co
> cd builds/MyApp
# now make some changes
# now copy changes back into Belt, dry-run first (-n flag):
> rsync -avpn . ../../templates/boilerplate/ --exclude node_modules --exclude .cache --exclude .expo --exclude .vscode --exclude assets --exclude .git --exclude .gitignore
# now copy changes back into Belt. Go back to Belt project:
> cd ../..
# run sync script
> node bin/sync-from-app.js MyApp --dry-run
# now run without the dry-run flag:
> rsync -avp . ../../templates/boilerplate/ --exclude node_modules --exclude .cache --exclude .expo --exclude .vscode --exclude assets --exclude .git --exclude .gitignore
> node bin/sync-from-app.js MyApp
```

## Creating a pull request
Expand Down
40 changes: 40 additions & 0 deletions bin/sync-from-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { execSync } from 'child_process';

// copies files over from a sample app generated with `bin/belt.js`
// See CONTRIBUTING.md for more info
function run() {
const appDir = process.argv[2];

if (!appDir || appDir.includes('builds')) {
console.error('Please provide an app directory, relative to `builds`');
console.error('Usage: node bin/sync-from-app.js MyApp --dry-run');
process.exit(1);
}

const excludes = [
'node_modules',
'.cache',
'.expo',
'.vscode',
'assets',
'.git',
'.gitignore',
];

const excludesStr = excludes.map((e) => `--exclude ${e}`).join(' ');

// provide additional flags, eg. --dry-run
const flags = `-avp ${process.argv[3] || ''}`;

const command = `rsync ${flags} ${excludesStr} builds/${appDir}/ templates/boilerplate/`;
console.log(command);
execSync(command, {
stdio: 'inherit',
});

console.log(
"\n\n🎉 Success! Ensure that all files have copied over correctly, remove any unwanted modifications (eg. app.json, package.json, etc), and manually remove any files that need to be deleted (these don't sync)",
);
}

run();
1 change: 0 additions & 1 deletion templates/boilerplate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"react": "18.2.0",
"react-native": "0.74.5",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-keyboard-aware-scrollview": "^2.1.0",
"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1"
},
Expand Down
58 changes: 58 additions & 0 deletions templates/boilerplate/src/__tests__/App.integration.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { screen, userEvent } from '@testing-library/react-native';

import mock from 'src/test/mock';
import { renderApplication } from 'src/test/render';
import { GithubProjectsResponse } from 'src/util/api/api';

// Testing philosophy:
// - Tests that render the entire application with `renderApplication` go in the
// top level `src/__tests__` directory and are named with `mytest.integration.test.tsx`.
// These are ideal for when you need to test flows that include navigation between screens
// - Tests that render a single screen or component are colocated in
// `__tests__/MyComponent.test.tsx`. These call `render` and are not able to
// navigate between screens, since the Navigator is not mounted
test('renders app, can navigate between screens', async () => {
jest.useFakeTimers();

const mocks = [mockGitHubProjects()];

// load the app on the Home screen
renderApplication({ mocks });
expect(
await screen.findByRole('header', { name: /Welcome to your new app/ }),
).toBeDefined();

// go to About tab
await userEvent.press(screen.getByRole('button', { name: /About/ }));
expect(
await screen.findByRole('header', { name: 'Open Source' }),
).toBeDefined();

// expect GitHub project loaded via API
expect(await screen.findByText(/Belt is a CLI/)).toBeDefined();
});

// TODO: sample data, remove
// creates a mock for a GET request to the GitHub projects API.
// Pass this mock to `render` or `renderApplication` to register it with MSW.
// Recommended to place these mocks in a central location like `src/test/mocks`
function mockGitHubProjects() {
return mock.get<GithubProjectsResponse, null>(
'https://github-projects-api.vercel.app/api/projects',
{
response: {
projects: [
{
id: 635980144,
name: 'belt',
description:
'Belt is a CLI for starting a new React Native Expo app and will even keep your pants secure as you continue development.',
url: 'https://github.com/thoughtbot/belt',
stars: 8,
forks: 0,
},
],
},
},
);
}
31 changes: 0 additions & 31 deletions templates/boilerplate/src/__tests__/App.test.tsx

This file was deleted.

51 changes: 0 additions & 51 deletions templates/boilerplate/src/components/HeaderShadow.tsx

This file was deleted.

25 changes: 11 additions & 14 deletions templates/boilerplate/src/components/Screen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useNavigation } from '@react-navigation/native';
import { StatusBarStyle } from 'expo-status-bar';
import { ReactNode } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import { StyleSheet, View } from 'react-native';
import {
KeyboardAwareScrollView,
KeyboardAwareScrollViewProps,
Expand All @@ -12,7 +11,14 @@ import {
} from 'react-native-safe-area-context';

type Props = KeyboardAwareScrollViewProps & {
/**
* If true (default), horizontal padding is added to the screen content
*/
padHorizontal?: boolean;
/**
* If true, the screen will be scrollable. If false, the screen will not scroll.
* Set to false if screen includes a scrollable component like a FlatList
*/
scroll?: boolean;
/**
* If true, a safe area view is not added for the top of the screen, since it is
Expand All @@ -25,19 +31,13 @@ type Props = KeyboardAwareScrollViewProps & {
* is used on the bottom */
FixedBottomComponent?: ReactNode;
fixedBottomAddSafeArea?: boolean;
statusBarStyle?: StatusBarStyle;
};

const AnimatedKeyboardAwareScrollView = Animated.createAnimatedComponent(
KeyboardAwareScrollView,
);

export default function Screen({
style,
padHorizontal = true,
scroll = true,
testID,
/** if screen has a navigation header, safe area view is not needed, since header takes into account */
hasHeader = false,
children,
FixedBottomComponent,
Expand All @@ -64,20 +64,17 @@ export default function Screen({
]}
>
{scroll ? (
<AnimatedKeyboardAwareScrollView
<KeyboardAwareScrollView
keyboardShouldPersistTaps="handled"
contentInsetAdjustmentBehavior="automatic"
bounces
onAccessibilityEscape={() => navigation.goBack()}
testID={`${testID || 'ScreenContainer'}ScrollView`}
testID={`${testID || 'Screen'}ScrollView`}
showsVerticalScrollIndicator={false}
contentContainerStyle={{
paddingTop: 0,
}}
{...props}
>
{children}
</AnimatedKeyboardAwareScrollView>
</KeyboardAwareScrollView>
) : (
children
)}
Expand Down
6 changes: 5 additions & 1 deletion templates/boilerplate/src/navigators/DashboardStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ const Dashboard = createNativeStackNavigator<DashboardTabParamList>();
export default function DashboardStack() {
return (
<Dashboard.Navigator>
<Dashboard.Screen name="Home" component={HomeScreen} />
<Dashboard.Screen
name="Home"
component={HomeScreen}
options={{ headerShown: false }}
/>
<Dashboard.Screen name="Information" component={InformationScreen} />
</Dashboard.Navigator>
);
Expand Down
5 changes: 5 additions & 0 deletions templates/boilerplate/src/navigators/TabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ function AccountIcon({ focused = false, color = 'gray' }) {
return <Feather name="settings" color={color} size={26} />;
}

// To add a new bottom tab:
// 1. Create a new stack navigator for the tab's screens
// 2. Add a new screen to the stack navigator
// 3. Add a new Tab.Screen to the TabNavigator
// 4. Update navigatorTypes with the TypeScript types for the tab
export default function TabNavigator() {
return (
<Tab.Navigator
Expand Down
31 changes: 19 additions & 12 deletions templates/boilerplate/src/screens/AboutScreen/AboutScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ import Screen from 'src/components/Screen';
import api, { GithubProject } from 'src/util/api/api';

export default function AboutScreen() {
return <GitHubProjects />;
}

function Header() {
return (
<>
<Text style={styles.heading} accessibilityRole="header">
Open Source
</Text>
<Text style={styles.paragraph}>
Here are a few projects that we maintain.
</Text>
</>
);
}

// TODO: sample data, remove
function GitHubProjects() {
const { data, isLoading } = useQuery({
queryKey: ['githubRepos'],
queryFn: api.githubRepos,
Expand All @@ -25,7 +43,7 @@ export default function AboutScreen() {
<FlatList
data={projects}
renderItem={({ item }) => <Project project={item} />}
keyExtractor={(item) => item.id}
keyExtractor={(item) => item.id.toString()}
ListHeaderComponent={Header}
stickyHeaderHiddenOnScroll
ListEmptyComponent={
Expand All @@ -37,17 +55,6 @@ export default function AboutScreen() {
);
}

function Header() {
return (
<>
<Text style={styles.heading}>Open Source</Text>
<Text style={styles.paragraph}>
Here are a few projects that we maintain.
</Text>
</>
);
}

// TODO: sample data, remove
function Project({ project }: { project: GithubProject }) {
const { name, description, stars } = project;
Expand Down
28 changes: 5 additions & 23 deletions templates/boilerplate/src/screens/HomeScreen/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,12 @@
import { useNavigation } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar';
import { Button, StyleSheet, Text, View } from 'react-native';
import { HomeScreenProp } from 'src/navigators/navigatorTypes';
import Screen from 'src/components/Screen';
import HomeScreenContent from './HomeScreenContent';

export default function HomeScreen() {
const navigation = useNavigation<HomeScreenProp['navigation']>();

return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<Button
title="Go to information screen"
onPress={() =>
navigation.navigate('Information', { greeting: 'Hello 👋' })
}
/>
<Screen>
<HomeScreenContent />
<StatusBar style="auto" />
</View>
</Screen>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Loading

0 comments on commit 4d501b5

Please sign in to comment.