Skip to content

Commit ac64e07

Browse files
authored
Merge pull request #351 from netguru/feat/animated-colors
feat: animated color props
2 parents a0f7ae7 + 7087f78 commit ac64e07

40 files changed

+923
-305
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
sidebar_position: 6
3+
---
4+
5+
# Animated color props
6+
7+
To make animated color props use Reanimated hooks to produce shared values that will be applied as a color/background color.
8+
9+
Full example code can be found in [example repo](https://github.com/netguru/sticky-parallax-header/blob/master/example/src/screens/additionalExamples/TabbedHeaderWithAnimatedColors.tsx)
10+
11+
```tsx
12+
const TabbedHeaderWithAnimatedColorsExample: React.FC = () => {
13+
const isDarkTheme = useColorScheme() === 'dark';
14+
15+
// Keep track of vertical and horizontal scroll values
16+
const horizontalScrollValue = useSharedValue(0);
17+
const scrollValue = useSharedValue(0);
18+
const onHorizontalScroll = useWorkletCallback((e: NativeScrollEvent) => {
19+
horizontalScrollValue.value = e.contentOffset.x;
20+
});
21+
const onScroll = useWorkletCallback((e: NativeScrollEvent) => {
22+
scrollValue.value = e.contentOffset.y;
23+
});
24+
25+
// Create interpolation configs
26+
const tabUnderlineColorInterpolateConfig = useInterpolateConfig(
27+
[0, 1242, 2484],
28+
[colors.activeOrange, colors.coralPink, colors.detailsBlue],
29+
ColorSpace.RGB
30+
);
31+
const tabsContainerBackgroundColorInterpolateConfig = useInterpolateConfig(
32+
[0, 800, 1600],
33+
[colors.primaryGreen, colors.activeOrange, colors.coralPink],
34+
ColorSpace.RGB
35+
);
36+
37+
// Create shared value with color prop based on scroll values
38+
const tabUnderlineColor = useDerivedValue(() =>
39+
interpolateSharableColor(horizontalScrollValue.value, tabUnderlineColorInterpolateConfig)
40+
);
41+
const tabsContainerBackgroundColor = useDerivedValue(() =>
42+
interpolateSharableColor(scrollValue.value, tabsContainerBackgroundColorInterpolateConfig)
43+
);
44+
45+
return (
46+
<>
47+
<TabbedHeaderPager
48+
contentContainerStyle={[
49+
isDarkTheme ? screenStyles.darkBackground : screenStyles.lightBackground,
50+
]}
51+
pagerProps={{ onScroll: onHorizontalScroll }} // Keep track of pager's horizontal scroll value
52+
onScroll={onScroll} // Keep track of vertical scroll value
53+
tabsContainerBackgroundColor={tabsContainerBackgroundColor} // Apply color prop
54+
tabUnderlineColor={tabUnderlineColor} // Apply color prop
55+
containerStyle={screenStyles.stretchContainer}
56+
backgroundColor={colors.primaryGreen}
57+
foregroundImage={photosPortraitMe}
58+
rememberTabScrollPosition
59+
logo={logo}
60+
title={"Mornin' Mark! \nReady for a quiz?"}
61+
titleStyle={screenStyles.text}
62+
titleTestID={tabbedHeaderTestIDs.title}
63+
tabs={TABBED_SECTIONS.map((section) => ({
64+
title: section.title,
65+
testID: section.tabTestID,
66+
}))}
67+
tabTextStyle={screenStyles.text}
68+
showsVerticalScrollIndicator={false}>
69+
<View style={styles.content}>
70+
{Brandon.cards.map((data, i, arr) => (
71+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
72+
))}
73+
</View>
74+
<View style={styles.content}>
75+
{Ewa.cards.map((data, i, arr) => (
76+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
77+
))}
78+
</View>
79+
<View style={styles.content}>
80+
{Jennifer.cards.map((data, i, arr) => (
81+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
82+
))}
83+
</View>
84+
<View style={styles.content}>
85+
{Brandon.cards.map((data, i, arr) => (
86+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
87+
))}
88+
</View>
89+
<View style={styles.content}>
90+
{Ewa.cards.map((data, i, arr) => (
91+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
92+
))}
93+
</View>
94+
<View style={styles.content}>
95+
{Jennifer.cards.map((data, i, arr) => (
96+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
97+
))}
98+
</View>
99+
</TabbedHeaderPager>
100+
<StatusBar barStyle="light-content" backgroundColor={colors.primaryGreen} translucent />
101+
</>
102+
);
103+
}
104+
```
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
sidebar_position: 6
3+
---
4+
5+
# Animated color props
6+
7+
To make animated color props use Reanimated hooks to produce shared values that will be applied as a color/background color.
8+
9+
Full example code can be found in [example repo](https://github.com/netguru/sticky-parallax-header/blob/master/example/src/screens/additionalExamples/TabbedHeaderWithAnimatedColors.tsx)
10+
11+
```tsx
12+
const TabbedHeaderWithAnimatedColorsExample: React.FC = () => {
13+
const isDarkTheme = useColorScheme() === 'dark';
14+
15+
// Keep track of vertical and horizontal scroll values
16+
const horizontalScrollValue = useSharedValue(0);
17+
const scrollValue = useSharedValue(0);
18+
const onHorizontalScroll = useWorkletCallback((e: NativeScrollEvent) => {
19+
horizontalScrollValue.value = e.contentOffset.x;
20+
});
21+
const onScroll = useWorkletCallback((e: NativeScrollEvent) => {
22+
scrollValue.value = e.contentOffset.y;
23+
});
24+
25+
// Create interpolation configs
26+
const tabUnderlineColorInterpolateConfig = useInterpolateConfig(
27+
[0, 1242, 2484],
28+
[colors.activeOrange, colors.coralPink, colors.detailsBlue],
29+
ColorSpace.RGB
30+
);
31+
const tabsContainerBackgroundColorInterpolateConfig = useInterpolateConfig(
32+
[0, 800, 1600],
33+
[colors.primaryGreen, colors.activeOrange, colors.coralPink],
34+
ColorSpace.RGB
35+
);
36+
37+
// Create shared value with color prop based on scroll values
38+
const tabUnderlineColor = useDerivedValue(() =>
39+
interpolateSharableColor(horizontalScrollValue.value, tabUnderlineColorInterpolateConfig)
40+
);
41+
const tabsContainerBackgroundColor = useDerivedValue(() =>
42+
interpolateSharableColor(scrollValue.value, tabsContainerBackgroundColorInterpolateConfig)
43+
);
44+
45+
return (
46+
<>
47+
<TabbedHeaderPager
48+
contentContainerStyle={[
49+
isDarkTheme ? screenStyles.darkBackground : screenStyles.lightBackground,
50+
]}
51+
pagerProps={{ onScroll: onHorizontalScroll }} // Keep track of pager's horizontal scroll value
52+
onScroll={onScroll} // Keep track of vertical scroll value
53+
tabsContainerBackgroundColor={tabsContainerBackgroundColor} // Apply color prop
54+
tabUnderlineColor={tabUnderlineColor} // Apply color prop
55+
containerStyle={screenStyles.stretchContainer}
56+
backgroundColor={colors.primaryGreen}
57+
foregroundImage={photosPortraitMe}
58+
rememberTabScrollPosition
59+
logo={logo}
60+
title={"Mornin' Mark! \nReady for a quiz?"}
61+
titleStyle={screenStyles.text}
62+
titleTestID={tabbedHeaderTestIDs.title}
63+
tabs={TABBED_SECTIONS.map((section) => ({
64+
title: section.title,
65+
testID: section.tabTestID,
66+
}))}
67+
tabTextStyle={screenStyles.text}
68+
showsVerticalScrollIndicator={false}>
69+
<View style={styles.content}>
70+
{Brandon.cards.map((data, i, arr) => (
71+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
72+
))}
73+
</View>
74+
<View style={styles.content}>
75+
{Ewa.cards.map((data, i, arr) => (
76+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
77+
))}
78+
</View>
79+
<View style={styles.content}>
80+
{Jennifer.cards.map((data, i, arr) => (
81+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
82+
))}
83+
</View>
84+
<View style={styles.content}>
85+
{Brandon.cards.map((data, i, arr) => (
86+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
87+
))}
88+
</View>
89+
<View style={styles.content}>
90+
{Ewa.cards.map((data, i, arr) => (
91+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
92+
))}
93+
</View>
94+
<View style={styles.content}>
95+
{Jennifer.cards.map((data, i, arr) => (
96+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
97+
))}
98+
</View>
99+
</TabbedHeaderPager>
100+
<StatusBar barStyle="light-content" backgroundColor={colors.primaryGreen} translucent />
101+
</>
102+
);
103+
}
104+
```

example/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as Font from 'expo-font';
22
import * as React from 'react';
3+
import { I18nManager } from 'react-native';
34
import { SafeAreaProvider } from 'react-native-safe-area-context';
45

56
import AppNavigator from './navigation/AppNavigator';
@@ -17,6 +18,7 @@ export default function App() {
1718

1819
React.useEffect(() => {
1920
loadFonts();
21+
I18nManager.allowRTL(true);
2022
}, [loadFonts]);
2123

2224
return <SafeAreaProvider>{loaded ? <AppNavigator /> : <></>}</SafeAreaProvider>;

example/src/navigation/AppNavigator.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { AvatarHeaderFlashListExample } from '../screens/additionalExamples/Avat
2020
import { DetailsHeaderFlashListExample } from '../screens/additionalExamples/DetailsHeaderFlashListExample';
2121
import { StickyHeaderFlashListExample } from '../screens/additionalExamples/StickyHeaderFlashListExample';
2222
import { TabbedHeaderFlashListExample } from '../screens/additionalExamples/TabbedHeaderFlashListExample';
23+
import { TabbedHeaderWithAnimatedColorsExample } from '../screens/additionalExamples/TabbedHeaderWithAnimatedColors';
2324
import { TabbedHeaderWithSectionListsExample } from '../screens/additionalExamples/TabbedHeaderWithSectionLists';
2425

2526
import { ROUTES } from './routes';
@@ -87,6 +88,10 @@ const AppNavigator: React.FC = () => (
8788
name={ROUTES.STICKY_HEADER_FLASHLIST}
8889
component={StickyHeaderFlashListExample}
8990
/>
91+
<Stack.Screen
92+
name={ROUTES.TABBED_HEADER_WITH_ANIMATED_COLORS}
93+
component={TabbedHeaderWithAnimatedColorsExample}
94+
/>
9095
</Stack.Navigator>
9196
</NavigationContainer>
9297
);

example/src/navigation/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ export const ROUTES = {
1919
AVATAR_HEADER_FLASHLIST: 'AvatarHeaderFlashList' as const,
2020
DETAILS_HEADER_FLASHLIST: 'DetailsHeaderFlashList' as const,
2121
STICKY_HEADER_FLASHLIST: 'StickyHeaderFlashList' as const,
22+
TABBED_HEADER_WITH_ANIMATED_COLORS: 'TabbedHeaderWithAnimatedColors' as const,
2223
};

example/src/navigation/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type RootStackParamList = {
2626
[ROUTES.AVATAR_HEADER_FLASHLIST]: undefined;
2727
[ROUTES.DETAILS_HEADER_FLASHLIST]: undefined;
2828
[ROUTES.STICKY_HEADER_FLASHLIST]: undefined;
29+
[ROUTES.TABBED_HEADER_WITH_ANIMATED_COLORS]: undefined;
2930
};
3031

3132
export type RootStackNavigationProp = StackNavigationProp<RootStackParamList>;

example/src/screens/HomeScreen/ExampleLink.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ export const EXAMPLES: Array<ExampleLinkProps> = [
105105
label: 'New StickyHeader (FlashList)',
106106
testID: homeScreenTestIDs.stickyHeaderFlashListLink,
107107
},
108+
{
109+
routeName: ROUTES.TABBED_HEADER_WITH_ANIMATED_COLORS,
110+
label: 'Pager with tabs and animated colors or styles',
111+
testID: homeScreenTestIDs.tabbedHeaderWithAnimatedColorsLink,
112+
},
108113
];
109114

110115
export const ExampleLink: React.FC<ExampleLinkProps> = ({ routeName, label, testID }) => {

example/src/screens/HomeScreen/testIDs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ export const homeScreenTestIDs = Object.freeze({
2626
avatarHeaderFlashListLink: 'AvatarHeaderFlashListLinkTestID',
2727
detailsHeaderFlashListLink: 'DetailsHeaderFlashListLinkTestID',
2828
stickyHeaderFlashListLink: 'StickyHeaderFlashListLinkTestID',
29+
tabbedHeaderWithAnimatedColorsLink: 'TabbedHeaderWithAnimatedColorsLinkTestID',
2930
});
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import * as React from 'react';
2+
import type { NativeScrollEvent } from 'react-native';
3+
import { StatusBar, StyleSheet, View, useColorScheme } from 'react-native';
4+
import {
5+
ColorSpace,
6+
interpolateSharableColor,
7+
useDerivedValue,
8+
useInterpolateConfig,
9+
useSharedValue,
10+
useWorkletCallback,
11+
} from 'react-native-reanimated';
12+
import { TabbedHeaderPager } from 'react-native-sticky-parallax-header';
13+
14+
import { Brandon, Ewa, Jennifer } from '../../assets/data/cards';
15+
import { TABBED_SECTIONS } from '../../assets/data/tabbedSections';
16+
import { logo, photosPortraitMe } from '../../assets/images';
17+
import { QuizCard } from '../../components';
18+
import { colors, screenStyles } from '../../constants';
19+
20+
import { tabbedHeaderTestIDs } from './testIDs';
21+
22+
export const TabbedHeaderWithAnimatedColorsExample: React.FC = () => {
23+
const isDarkTheme = useColorScheme() === 'dark';
24+
const horizontalScrollValue = useSharedValue(0);
25+
const scrollValue = useSharedValue(0);
26+
const onHorizontalScroll = useWorkletCallback((e: NativeScrollEvent) => {
27+
horizontalScrollValue.value = e.contentOffset.x;
28+
});
29+
const onScroll = useWorkletCallback((e: NativeScrollEvent) => {
30+
scrollValue.value = e.contentOffset.y;
31+
});
32+
33+
const tabUnderlineColorInterpolateConfig = useInterpolateConfig(
34+
[0, 1242, 2484],
35+
[colors.activeOrange, colors.coralPink, colors.detailsBlue],
36+
ColorSpace.RGB
37+
);
38+
const tabsContainerBackgroundColorInterpolateConfig = useInterpolateConfig(
39+
[0, 800, 1600],
40+
[colors.primaryGreen, colors.activeOrange, colors.coralPink],
41+
ColorSpace.RGB
42+
);
43+
const tabUnderlineColor = useDerivedValue(() =>
44+
interpolateSharableColor(horizontalScrollValue.value, tabUnderlineColorInterpolateConfig)
45+
);
46+
const tabsContainerBackgroundColor = useDerivedValue(() =>
47+
interpolateSharableColor(scrollValue.value, tabsContainerBackgroundColorInterpolateConfig)
48+
);
49+
50+
return (
51+
<>
52+
<TabbedHeaderPager
53+
contentContainerStyle={[
54+
isDarkTheme ? screenStyles.darkBackground : screenStyles.lightBackground,
55+
]}
56+
pagerProps={{ onScroll: onHorizontalScroll }}
57+
onScroll={onScroll}
58+
tabsContainerBackgroundColor={tabsContainerBackgroundColor}
59+
tabUnderlineColor={tabUnderlineColor}
60+
containerStyle={screenStyles.stretchContainer}
61+
backgroundColor={colors.primaryGreen}
62+
foregroundImage={photosPortraitMe}
63+
rememberTabScrollPosition
64+
logo={logo}
65+
title={"Mornin' Mark! \nReady for a quiz?"}
66+
titleStyle={screenStyles.text}
67+
titleTestID={tabbedHeaderTestIDs.title}
68+
tabs={TABBED_SECTIONS.map((section) => ({
69+
title: section.title,
70+
testID: section.tabTestID,
71+
}))}
72+
tabTextStyle={screenStyles.text}
73+
showsVerticalScrollIndicator={false}>
74+
<View style={styles.content}>
75+
{Brandon.cards.map((data, i, arr) => (
76+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
77+
))}
78+
</View>
79+
<View style={styles.content}>
80+
{Ewa.cards.map((data, i, arr) => (
81+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
82+
))}
83+
</View>
84+
<View style={styles.content}>
85+
{Jennifer.cards.map((data, i, arr) => (
86+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
87+
))}
88+
</View>
89+
<View style={styles.content}>
90+
{Brandon.cards.map((data, i, arr) => (
91+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
92+
))}
93+
</View>
94+
<View style={styles.content}>
95+
{Ewa.cards.map((data, i, arr) => (
96+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
97+
))}
98+
</View>
99+
<View style={styles.content}>
100+
{Jennifer.cards.map((data, i, arr) => (
101+
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
102+
))}
103+
</View>
104+
</TabbedHeaderPager>
105+
<StatusBar barStyle="light-content" backgroundColor={colors.primaryGreen} translucent />
106+
</>
107+
);
108+
};
109+
110+
const styles = StyleSheet.create({
111+
content: {
112+
alignItems: 'center',
113+
alignSelf: 'stretch',
114+
flex: 1,
115+
paddingHorizontal: 24,
116+
},
117+
});

0 commit comments

Comments
 (0)