├── .expo-shared
└── assets.json
├── .gitignore
├── App.tsx
├── Document
├── index.tsx
└── styles.tsx
├── Header
├── index.tsx
├── styles.tsx
└── types.ts
├── README.md
├── ScrollContext
├── index.tsx
└── types.ts
├── Text
├── index.tsx
└── types.ts
├── app.json
├── assets
├── icon.png
└── splash.png
├── babel.config.js
├── package.json
├── tsconfig.json
├── types.ts
└── yarn.lock
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true,
3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true
4 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 | web-report/
12 |
13 | # macOS
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ScrollContextProvider } from './ScrollContext'
3 | import { Document } from './Document'
4 |
5 | export const App = () =>
6 |
7 |
8 | ;
9 |
10 | export default App;
--------------------------------------------------------------------------------
/Document/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { SafeAreaView } from 'react-native'
3 | import { Text } from '../Text'
4 | import Header from '../Header'
5 | import styles from './styles'
6 | import { styles as headerStyles } from '../Header/styles'
7 | import FontAwesome from 'react-native-vector-icons/Ionicons'
8 | import { ScrollView } from '../ScrollContext'
9 |
10 | export const Document = () => {
11 |
12 | return (
13 |
14 | console.log('Go Back')}
20 | primaryColor heading>
21 | Back
25 |
26 | }
27 | />
28 |
31 | My Header
32 |
33 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor nisl ut sapien blandit congue. Donec vehicula nulla augue, ac pretium justo malesuada eget. Donec placerat, nulla in convallis consectetur, est erat ornare ex, non lobortis augue risus eu lectus. Curabitur egestas ut magna vitae vehicula. Quisque non semper felis. Cras dictum nisl sed magna tempus lobortis. Nunc arcu ligula, dignissim nec tincidunt vel, eleifend et est.
34 |
35 | Cras viverra turpis ac iaculis posuere. Mauris ut nulla sed leo maximus vulputate. Aenean augue eros, viverra egestas tristique at, aliquam et lacus. Etiam suscipit varius ligula, at tempus lectus pellentesque ac. Donec maximus euismod velit ultrices imperdiet. Suspendisse sem tellus, accumsan quis faucibus sed, tempus id urna. Phasellus id mauris aliquam, elementum mi eu, vestibulum nisl. Sed quis fringilla lorem.
36 |
37 | Nullam ac est id dolor vulputate blandit sed ut purus. Aliquam pharetra sollicitudin eros ac condimentum. Cras luctus risus ante, a mollis libero vehicula eget. Nulla nulla nulla, lacinia vitae feugiat eu, placerat non metus. Cras viverra neque id augue scelerisque, id tristique diam finibus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce tempus, est non ornare porta, urna leo pulvinar orci, vel blandit quam magna quis justo. Maecenas magna orci, pulvinar eu ligula vel, convallis cursus lorem.
38 |
39 | Curabitur commodo, massa ut scelerisque elementum, purus augue venenatis ex, et tempus massa ex nec ipsum. Nam eu ante quis mauris ornare egestas eget vitae lorem. Mauris ullamcorper nunc commodo turpis rutrum suscipit. Fusce convallis semper ultrices. Donec vel mi id elit aliquam tristique sed et nulla. Morbi feugiat lorem ut eleifend iaculis. Mauris tempus elit vitae enim rutrum porttitor vitae quis ex. Fusce lobortis efficitur tortor, sit amet auctor nunc vulputate id. Nunc sit amet congue lacus.
40 |
41 | Praesent blandit, ante et condimentum laoreet, nisl est maximus orci, nec blandit tortor diam eget massa. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi vehicula molestie nibh, in volutpat nisi placerat eget. In porttitor erat in lobortis euismod. Maecenas ultricies dapibus leo, lacinia congue augue placerat in. Aenean at sapien eget lectus tristique tempor quis ac enim. Curabitur efficitur felis convallis lorem iaculis, quis venenatis augue rutrum. Ut elementum ultricies purus vitae sagittis. Fusce tempor nibh eu orci dapibus, id ultrices ex euismod. Nulla euismod enim eget enim semper, vel sodales nisl sollicitudin. Donec nunc sem, malesuada in commodo eget, euismod ut enim. Morbi eu lobortis elit, et elementum mauris. Nulla eros velit, suscipit at nulla sed, tristique porta tortor. Nunc fringilla fermentum ex sit amet gravida. Vestibulum et metus nunc.
42 |
43 | Cras posuere est ipsum, rhoncus porttitor ligula accumsan sit amet. Mauris dapibus, nisl vitae interdum scelerisque, diam lorem maximus magna, nec imperdiet eros velit nec metus. Aenean in commodo nibh. Sed feugiat, leo tincidunt semper eleifend, sem dui placerat metus, et efficitur lorem nisi auctor massa. Aenean quis pulvinar nulla. Nunc vitae tortor tortor. Ut dignissim tincidunt sapien, at malesuada ante molestie vitae. Aenean consequat congue nulla. Phasellus quis rutrum sem. Integer et semper justo. Donec ut cursus est. Fusce sagittis in velit at egestas. Nullam tincidunt mi ac odio pulvinar interdum eu a lorem.
44 |
45 | Ut fringilla velit quis justo accumsan convallis. Proin ultricies ex at placerat ultricies. Curabitur sed tortor nunc. Suspendisse nec mauris nec turpis lacinia venenatis id et lectus. Pellentesque non viverra mi. Donec mollis, metus ac iaculis rutrum, ante augue mollis nisl, commodo eleifend odio purus quis erat. Donec auctor nec augue ac lobortis. Morbi augue enim, fringilla aliquet lectus eu, ultricies pulvinar leo. Suspendisse potenti. Quisque sit amet bibendum tellus, a consequat nulla. In mattis neque non est pretium ultrices. Morbi in quam neque. Nullam feugiat orci risus, eget convallis risus lobortis ac. Integer pulvinar justo a odio lobortis, eu pharetra nisl luctus. Vestibulum at venenatis massa, a sodales sem. Nunc commodo condimentum felis, id dapibus sem tempor ut.
46 |
47 | Aliquam vehicula nibh sit amet leo luctus cursus. Ut arcu arcu, lacinia rhoncus tortor a, ornare lobortis ex. Etiam eu metus purus. Donec consequat convallis ligula, aliquet sollicitudin ipsum facilisis non. Donec aliquam leo eu vestibulum consequat. Maecenas maximus justo sit amet justo molestie, nec placerat sapien pharetra. Nam ac consectetur ipsum. Maecenas porta placerat lectus. Pellentesque porta rhoncus turpis non tincidunt. Pellentesque lorem orci, rhoncus nec augue ut, fermentum faucibus nisi. Sed at leo sit amet purus pulvinar gravida sit amet vitae tellus. Praesent ullamcorper, ante at mollis dignissim, metus enim cursus elit, non dignissim augue sapien sit amet dolor.
48 |
49 |
50 | );
51 | }
52 |
53 | export default Document;
--------------------------------------------------------------------------------
/Document/styles.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native'
2 |
3 | const styles = StyleSheet.create({
4 | container: {
5 | alignContent: 'flex-start',
6 | flexDirection: 'row',
7 | flexWrap: 'wrap',
8 | justifyContent: 'center',
9 | paddingHorizontal: 10,
10 | paddingTop: 10,
11 | marginBottom: 50,
12 | },
13 | title: {
14 | paddingTop: 0,
15 | marginBottom: 12,
16 | textAlign: 'left',
17 | width: '100%',
18 | fontSize: 28,
19 | fontWeight: 'bold',
20 | color: 'blue',
21 | },
22 | paragraph: {
23 | marginVertical: 10,
24 | fontSize: 15,
25 | fontWeight: '500',
26 | lineHeight: 21,
27 | }
28 | })
29 |
30 | export default styles;
--------------------------------------------------------------------------------
/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { View, Text, Animated, Easing } from 'react-native'
3 | import { useScroller } from '../ScrollContext'
4 | import { styles } from './styles'
5 | import { HeaderProps } from './types'
6 |
7 | export const Header = (props: HeaderProps) => {
8 |
9 | const { titleShowing, opacity } = useScroller();
10 |
11 | const [titleFade] = useState(
12 | new Animated.Value(0)
13 | );
14 |
15 | useEffect(() => {
16 | titleShowing === false &&
17 | Animated.timing(
18 | titleFade, {
19 | toValue: 0,
20 | duration: 200,
21 | useNativeDriver: true,
22 | easing: Easing.sin
23 | }).start();
24 |
25 | titleShowing === true &&
26 | Animated.timing(
27 | titleFade, {
28 | toValue: 1,
29 | duration: 200,
30 | useNativeDriver: true,
31 | easing: Easing.sin
32 | }).start();
33 | });
34 |
35 | return (
36 |
40 |
41 | {props.headerLeft !== undefined && props.headerLeft}
42 |
43 |
49 |
50 | {props.title}
51 |
52 |
53 |
54 | {props.headerRight !== undefined && props.headerRight}
55 |
56 |
57 | )
58 | }
59 |
60 | export default Header;
--------------------------------------------------------------------------------
/Header/styles.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native'
2 |
3 | export const styles = StyleSheet.create({
4 | header: {
5 | display: 'flex',
6 | width: '100%',
7 | height: 44,
8 | flexDirection: 'row',
9 | flexWrap: 'wrap',
10 | justifyContent: 'center',
11 | alignItems: 'center',
12 | alignContent: 'center',
13 | backgroundColor: '#fff',
14 | shadowRadius: 0,
15 | shadowColor: 'blue',
16 | shadowOffset: {
17 | width: 0,
18 | height: 1
19 | },
20 | zIndex: 9,
21 | },
22 | headerTitle: {
23 | display: 'flex',
24 | flexBasis: '33%',
25 | flex: 1,
26 | justifyContent: 'center',
27 | alignItems: 'center',
28 | alignContent: 'center',
29 | },
30 | headerLeft: {
31 | flexBasis: '33%',
32 | display: 'flex',
33 | flexDirection: 'row',
34 | justifyContent: 'flex-start',
35 | alignItems: 'center',
36 | alignContent: 'center',
37 | },
38 | headerRight: {
39 | flexBasis: '33%',
40 | display: 'flex',
41 | flexDirection: 'row',
42 | justifyContent: 'flex-end',
43 | alignItems: 'center',
44 | alignContent: 'center',
45 | },
46 | title: {
47 | fontSize: 17,
48 | fontWeight: '600',
49 | textAlign: 'center',
50 | color: 'blue',
51 | },
52 | headerText: {
53 | textAlign: 'center',
54 | paddingHorizontal: 10,
55 | display: 'flex',
56 | flexDirection: 'row',
57 | justifyContent: 'flex-start',
58 | alignItems: 'center',
59 | alignContent: 'center',
60 | fontSize: 17,
61 | fontWeight: '600',
62 | },
63 | });
64 |
65 | export default styles;
--------------------------------------------------------------------------------
/Header/types.ts:
--------------------------------------------------------------------------------
1 | export interface HeaderProps {
2 | headerLeft?: JSX.Element;
3 | headerRight?: JSX.Element;
4 | title: string;
5 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Scroll Aware Header Transitions
2 |
--------------------------------------------------------------------------------
/ScrollContext/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { ScrollView as ScrollViewNative, ScrollViewProps } from 'react-native'
3 | import { ScrollContextInterface } from './types'
4 | import { ChildProps } from '../types'
5 |
6 | const withinLimits = (val: number, min: number, max: number): number =>
7 | val > max
8 | ? max
9 | : val < min
10 | ? min
11 | : val
12 |
13 | export const ScrollContext = React.createContext({
14 | opacity: 0,
15 | maxOffset: 0,
16 | offset: 0,
17 | titleShowing: false,
18 | updateOffset: (val: number) => { }
19 | });
20 |
21 | export const useScroller = () => React.useContext(ScrollContext);
22 |
23 | export const ScrollContextProvider = (props: ChildProps) => {
24 |
25 | const minOffset: number = 0;
26 | const maxOffset: number = 30;
27 |
28 | const [offset, setOffset] = useState(0);
29 | const [titleShowing, setTitleShowing] = useState(false);
30 | const [opacity, setOpacity] = useState(0);
31 |
32 | const updateOffset = (val: number) => {
33 | setOffset(withinLimits(val, minOffset, maxOffset));
34 | setTitleShowing(val > maxOffset);
35 | setOpacity(withinLimits(val * maxOffset / 1000, 0, 1));
36 | }
37 |
38 | return (
39 |
46 | {props.children}
47 |
48 | )
49 | }
50 |
51 | export const ScrollView = (props: ChildProps & ScrollViewProps) => {
52 |
53 | const { updateOffset } = useScroller();
54 |
55 | return (
56 | {
59 | updateOffset(nativeEvent.contentOffset.y);
60 | }}
61 | scrollEventThrottle={200}
62 | >
63 | {props.children}
64 |
65 | )
66 | }
67 |
68 | export default ScrollContextProvider;
--------------------------------------------------------------------------------
/ScrollContext/types.ts:
--------------------------------------------------------------------------------
1 | export interface ScrollContextInterface {
2 | opacity: number;
3 | maxOffset: number;
4 | offset: number;
5 | titleShowing: boolean;
6 | updateOffset (val: number): void;
7 | };
--------------------------------------------------------------------------------
/Text/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Text as TextNative } from 'react-native'
3 | import { TextProps } from './types'
4 |
5 | const defaultText: string = '#000';
6 | const primaryText: string = 'blue';
7 |
8 | export const Text = (props: TextProps | any) => {
9 |
10 | const style: Object = props.style;
11 | let color: string = defaultText;
12 |
13 | if (style !== undefined) {
14 | delete props.style;
15 |
16 | if (style['color'] !== undefined)
17 | color = style['color'];
18 |
19 | if (props.primaryColor)
20 | color = primaryText;
21 | }
22 |
23 | return (
24 |
31 | )
32 | }
33 |
34 | export default Text;
--------------------------------------------------------------------------------
/Text/types.ts:
--------------------------------------------------------------------------------
1 | import { StyleProp, TextStyle } from 'react-native'
2 |
3 | export interface TextProps {
4 | primaryColor?: string;
5 | style?: StyleProp;
6 | }
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "Dynamic Header",
4 | "slug": "dynamic-header",
5 | "privacy": "public",
6 | "sdkVersion": "36.0.0",
7 | "platforms": [
8 | "ios",
9 | "android",
10 | "web"
11 | ],
12 | "version": "1.0.0",
13 | "orientation": "portrait",
14 | "icon": "./assets/icon.png",
15 | "splash": {
16 | "image": "./assets/splash.png",
17 | "resizeMode": "contain",
18 | "backgroundColor": "#ffffff"
19 | },
20 | "updates": {
21 | "fallbackToCacheTimeout": 0
22 | },
23 | "assetBundlePatterns": [
24 | "**/*"
25 | ],
26 | "ios": {
27 | "supportsTablet": true
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rossbulat/rn-dynamic-header/a2914271cbf8d7e8bca58d1dce65eda5c8ad3825/assets/icon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rossbulat/rn-dynamic-header/a2914271cbf8d7e8bca58d1dce65eda5c8ad3825/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject"
9 | },
10 | "dependencies": {
11 | "@types/react-native-vector-icons": "^6.4.5",
12 | "expo": "~36.0.0",
13 | "react": "~16.9.0",
14 | "react-dom": "~16.9.0",
15 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz",
16 | "react-native-screens": "2.0.0-alpha.12",
17 | "react-native-vector-icons": "^6.6.0",
18 | "react-native-web": "~0.11.7"
19 | },
20 | "devDependencies": {
21 | "@babel/core": "^7.0.0",
22 | "@types/react": "~16.9.0",
23 | "@types/react-native": "~0.60.23",
24 | "babel-preset-expo": "~8.0.0",
25 | "typescript": "~3.6.3"
26 | },
27 | "private": true
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "jsx": "react-native",
5 | "lib": ["dom", "esnext"],
6 | "moduleResolution": "node",
7 | "noEmit": true,
8 | "skipLibCheck": true,
9 | "resolveJsonModule": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/types.ts:
--------------------------------------------------------------------------------
1 | export interface ChildProps {
2 | children: JSX.Element[] | JSX.Element
3 | }
--------------------------------------------------------------------------------