Skip to content

Commit

Permalink
feat: new api - AvatarHeader
Browse files Browse the repository at this point in the history
- add AvatarHeader components
- add useStickyHeaderScrollProps used to handle snap-effect props
- add usePredefinedHeader used to handle header props
  • Loading branch information
mateusz1913 committed May 4, 2022
1 parent 72b2155 commit 0410cc6
Show file tree
Hide file tree
Showing 26 changed files with 1,374 additions and 2 deletions.
6 changes: 6 additions & 0 deletions example/src/navigation/AppNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { createStackNavigator } from '@react-navigation/stack';
import { HomeScreen, CardScreen } from '../screens';
import YodaScreen from '../screens/additionalExamples/YodaScreen';
import AppStoreHeader from '../screens/additionalExamples/SimsScreen';
import { AvatarHeaderFlatListExample } from '../screens/additionalExamples/AvatarHeaderFlatListExample';
import { AvatarHeaderScrollViewExample } from '../screens/additionalExamples/AvatarHeaderScrollViewExample';
import { AvatarHeaderSectionListExample } from '../screens/additionalExamples/AvatarHeaderSectionListExample';
import { StickyHeaderFlatListExample } from '../screens/additionalExamples/StickyHeaderFlatListExample';
import { StickyHeaderScrollViewExample } from '../screens/additionalExamples/StickyHeaderScrollViewExample';
import { StickyHeaderSectionListExample } from '../screens/additionalExamples/StickyHeaderSectionListExample';
Expand All @@ -20,6 +23,9 @@ const AppNavigator = () => (
<Stack.Screen name="StickyHeaderFlatList" component={StickyHeaderFlatListExample} />
<Stack.Screen name="StickyHeaderScrollView" component={StickyHeaderScrollViewExample} />
<Stack.Screen name="StickyHeaderSectionList" component={StickyHeaderSectionListExample} />
<Stack.Screen name="AvatarHeaderFlatList" component={AvatarHeaderFlatListExample} />
<Stack.Screen name="AvatarHeaderScrollView" component={AvatarHeaderScrollViewExample} />
<Stack.Screen name="AvatarHeaderSectionList" component={AvatarHeaderSectionListExample} />
</Stack.Navigator>
</NavigationContainer>
);
Expand Down
18 changes: 18 additions & 0 deletions example/src/screens/HomeScreen/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,24 @@ const HomeScreen: VFC = () => {
navigation.navigate('StickyHeaderSectionList');
}}
/>
<Button
title={'New AvatarHeaderFlatList'}
onPress={() => {
navigation.navigate('AvatarHeaderFlatList');
}}
/>
<Button
title={'New AvatarHeaderScrollView'}
onPress={() => {
navigation.navigate('AvatarHeaderScrollView');
}}
/>
<Button
title={'New AvatarHeaderSectionList'}
onPress={() => {
navigation.navigate('AvatarHeaderSectionList');
}}
/>
</View>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useNavigation } from '@react-navigation/native';
import type { FC } from 'react';
import React from 'react';
import { StyleSheet, useColorScheme } from 'react-native';

// TODO: Change path when removing old API
import { AvatarHeaderFlatList } from '../../../../src/predefinedComponents/AvatarHeader/AvatarHeaderFlatList';
import { Brandon } from '../../assets/data/cards';
import QuizCard from '../../components/QuizCard/QuizCard';

export const AvatarHeaderFlatListExample: FC = () => {
const navigation = useNavigation();

function goBack() {
navigation.goBack();
}

const isDarkTheme = useColorScheme() === 'dark';

return <AvatarHeaderFlatList
leftTopIcon={require('../../assets/icons/iconCloseWhite.png')}
leftTopIconOnPress={goBack}
rightTopIcon={require('../../assets/icons/Icon-Menu.png')}
contentContainerStyle={[ styles.content, isDarkTheme ? styles.darkBackground : styles.lightBackground ]}
backgroundColor={Brandon.color}
hasBorderRadius
image={Brandon.image}
subtitle={Brandon.about}
title={Brandon.author}
data={Brandon.cards}
keyExtractor={(item) => item.question}
renderItem={({ item, index }) => <QuizCard data={item} num={index} cardsAmount={Brandon.cards.length} />}
/>;
};

const styles = StyleSheet.create({
content: {
alignItems: 'center',
alignSelf: 'stretch',
paddingHorizontal: 24,
},
darkBackground: {
backgroundColor: 'black',
},
lightBackground: {
backgroundColor: 'white',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useNavigation } from '@react-navigation/native';
import type { FC } from 'react';
import React from 'react';
import { StyleSheet, View, useColorScheme } from 'react-native';

// TODO: Change path when removing old API
import { AvatarHeaderScrollView } from '../../../../src/predefinedComponents/AvatarHeader/AvatarHeaderScrollView';
import { Brandon } from '../../assets/data/cards';
import QuizCard from '../../components/QuizCard/QuizCard';

export const AvatarHeaderScrollViewExample: FC = () => {
const navigation = useNavigation();

function goBack() {
navigation.goBack();
}

const isDarkTheme = useColorScheme() === 'dark';

return <AvatarHeaderScrollView
leftTopIcon={require('../../assets/icons/iconCloseWhite.png')}
leftTopIconOnPress={goBack}
rightTopIcon={require('../../assets/icons/Icon-Menu.png')}
contentContainerStyle={[ isDarkTheme ? styles.darkBackground : styles.lightBackground ]}
backgroundColor={Brandon.color}
hasBorderRadius
image={Brandon.image}
subtitle={Brandon.about}
title={Brandon.author}
>
<View style={styles.content}>
{Brandon.cards.map((data, i, arr) =>
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
)}
</View>
</AvatarHeaderScrollView>;
};

const styles = StyleSheet.create({
content: {
alignItems: 'center',
flex: 1,
paddingHorizontal: 24,
},
darkBackground: {
backgroundColor: 'black',
},
lightBackground: {
backgroundColor: 'white',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useNavigation } from '@react-navigation/native';
import type { FC } from 'react';
import React, { useMemo } from 'react';
import type { SectionListData } from 'react-native';
import { StyleSheet, useColorScheme } from 'react-native';

// TODO: Change path when removing old API
import { AvatarHeaderSectionList } from '../../../../src/predefinedComponents/AvatarHeader/AvatarHeaderSectionList';
import { Brandon } from '../../assets/data/cards';
import QuizCard from '../../components/QuizCard/QuizCard';
import { SectionFooter } from '../../components/primitiveComponents/SectionFooter';
import { SectionHeader } from '../../components/primitiveComponents/SectionHeader';

export const AvatarHeaderSectionListExample: FC = () => {
const navigation = useNavigation();

function goBack() {
navigation.goBack();
}

const isDarkTheme = useColorScheme() === 'dark';

const sections = useMemo(() => {
const section: SectionListData<typeof Brandon.cards[0]> = {
data: Brandon.cards,
keyExtractor: (item) => item.question,
renderItem: ({ item, index }) => <QuizCard data={item} num={index} cardsAmount={Brandon.cards.length} />,
};

return [ section, section, section ];
}, []);

return <AvatarHeaderSectionList
leftTopIcon={require('../../assets/icons/iconCloseWhite.png')}
leftTopIconOnPress={goBack}
rightTopIcon={require('../../assets/icons/Icon-Menu.png')}
contentContainerStyle={[ styles.content, isDarkTheme ? styles.darkBackground : styles.lightBackground ]}
backgroundColor={Brandon.color}
hasBorderRadius
image={Brandon.image}
subtitle={Brandon.about}
title={Brandon.author}
renderSectionHeader={() => {
return <SectionHeader />;
}}
renderSectionFooter={() => {
return <SectionFooter />;
}}
sections={sections}
/>;
};

const styles = StyleSheet.create({
content: {
alignSelf: 'stretch',
paddingHorizontal: 24,
},
darkBackground: {
backgroundColor: 'black',
},
lightBackground: {
backgroundColor: 'white',
},
});
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"peerDependencies": {
"react": "*",
"react-native": "*",
"react-native-reanimated": ">=2.0.0"
"react-native-reanimated": ">=2.0.0",
"react-native-safe-area-context": ">=3.0.0"
},
"devDependencies": {
"@commitlint/cli": "^8.3.5",
Expand All @@ -60,6 +61,7 @@
"react-native": "0.64.3",
"react-native-builder-bob": "0.18.2",
"react-native-reanimated": "2.3.1",
"react-native-safe-area-context": "3.3.2",
"react-test-renderer": "17.0.1",
"release-it": "14.14.2",
"typescript": "4.6.3"
Expand Down
2 changes: 1 addition & 1 deletion src/components/IconRenderer/IconRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Image, ImageSourcePropType } from 'react-native';
import styles from '../../predefinedComponents/DetailsHeader/DetailsHeader.styles';

interface Props {
icon?: (() => ReactElement) | ImageSourcePropType;
icon?: (() => ReactElement | null) | ImageSourcePropType;
}

const IconRenderer: VFC<Props> = ({ icon }) => {
Expand Down
4 changes: 4 additions & 0 deletions src/constants/screenStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ const screenStyles = StyleSheet.create({
foregroundText: {
color: colors.white,
},
wrapper: {
alignSelf: 'stretch',
flex: 1,
},
});

export default screenStyles;
15 changes: 15 additions & 0 deletions src/hooks/useResponsiveSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useWindowDimensions } from 'react-native';

export function useResponsiveSize() {
const { height, width } = useWindowDimensions();

function responsiveHeight(value: number) {
return height * (value / 100);
}

function responsiveWidth(value: number) {
return width * (value / 100);
}

return { responsiveHeight, responsiveWidth };
}
98 changes: 98 additions & 0 deletions src/predefinedComponents/AvatarHeader/AvatarHeaderFlatList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { ForwardedRef, ReactElement, RefAttributes } from 'react';
import React, { forwardRef, useImperativeHandle } from 'react';
import type { FlatList } from 'react-native';
import { View } from 'react-native';

import commonStyles from '../../constants/screenStyles';
import { StickyHeaderFlatList } from '../../primitiveComponents/StickyHeaderFlatList';
import type { AvatarHeaderFlatListProps } from './AvatarHeaderProps';
import { HeaderBar } from './components/HeaderBar';
import { useAvatarHeader } from './useAvatarHeader';

function AvatarHeaderFlatListInner<ItemT>(
props: AvatarHeaderFlatListProps<ItemT>,
ref: ForwardedRef<FlatList<ItemT>>
) {
const {
backgroundColor,
contentContainerStyle,
data,
decelerationRate = 'fast',
keyExtractor,
leftTopIcon,
leftTopIconAccessibilityLabel,
leftTopIconOnPress,
leftTopIconTestID,
nestedScrollEnabled = true,
overScrollMode = 'never',
renderHeaderBar,
renderItem,
rightTopIcon,
rightTopIconAccessibilityLabel,
rightTopIconOnPress,
rightTopIconTestID,
scrollEventThrottle = 16,
title,
...rest
} = props;
const {
onMomentumScrollEnd,
onScroll,
onScrollEndDrag,
parallaxHeight,
renderHeader,
scrollValue,
scrollViewRef,
} = useAvatarHeader<FlatList<ItemT>>(props);

useImperativeHandle(ref, () => scrollViewRef.current as FlatList<ItemT>);

return (
<View style={[commonStyles.wrapper, { backgroundColor }]}>
{renderHeaderBar ? (
renderHeaderBar()
) : (
<HeaderBar
backgroundColor={backgroundColor}
height={parallaxHeight}
leftTopIcon={leftTopIcon}
leftTopIconAccessibilityLabel={leftTopIconAccessibilityLabel}
leftTopIconOnPress={leftTopIconOnPress}
leftTopIconTestID={leftTopIconTestID}
rightTopIcon={rightTopIcon}
rightTopIconAccessibilityLabel={rightTopIconAccessibilityLabel}
rightTopIconOnPress={rightTopIconOnPress}
rightTopIconTestID={rightTopIconTestID}
scrollValue={scrollValue}
title={title}
/>
)}
<View style={commonStyles.wrapper}>
<StickyHeaderFlatList
ref={scrollViewRef}
{...rest}
contentContainerStyle={contentContainerStyle}
data={data}
decelerationRate={decelerationRate}
keyExtractor={keyExtractor}
nestedScrollEnabled={nestedScrollEnabled}
onMomentumScrollEnd={onMomentumScrollEnd}
onScrollEndDrag={onScrollEndDrag}
onScroll={onScroll}
overScrollMode={overScrollMode}
renderHeader={renderHeader}
renderItem={renderItem}
scrollEventThrottle={scrollEventThrottle}
/>
</View>
</View>
);
}

type AvatarHeaderFlatListType = <ItemT>(
props: AvatarHeaderFlatListProps<ItemT> & RefAttributes<FlatList<ItemT>>
) => ReactElement;

export const AvatarHeaderFlatList = forwardRef(
AvatarHeaderFlatListInner
) as AvatarHeaderFlatListType;
28 changes: 28 additions & 0 deletions src/predefinedComponents/AvatarHeader/AvatarHeaderProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ColorValue, ImageSourcePropType } from 'react-native';

import type {
StickyHeaderFlatListProps,
StickyHeaderScrollViewProps,
StickyHeaderSectionListProps,
} from '../../primitiveComponents/StickyHeaderProps';
import type { IconProps, SharedPredefinedProps } from '../common/SharedProps';

export interface AvatarHeaderSharedProps extends IconProps, SharedPredefinedProps {
hasBorderRadius?: boolean;
image?: ImageSourcePropType;
subtitle?: string;
tabsContainerBackgroundColor?: ColorValue;
title?: string;
}

export interface AvatarHeaderScrollViewProps
extends AvatarHeaderSharedProps,
StickyHeaderScrollViewProps {}

export interface AvatarHeaderFlatListProps<ItemT>
extends AvatarHeaderSharedProps,
StickyHeaderFlatListProps<ItemT> {}

export interface AvatarHeaderSectionListProps<ItemT, SectionT>
extends AvatarHeaderSharedProps,
StickyHeaderSectionListProps<ItemT, SectionT> {}
Loading

0 comments on commit 0410cc6

Please sign in to comment.