diff --git a/package.json b/package.json index fa00b452..383e4661 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-native-sticky-parallax-header", - "version": "0.4.1", + "name": "react-native-gizwits-sticky-parallax-header", + "version": "2.0.1", "main": "lib/commonjs/index", "module": "lib/module/index", "types": "lib/typescript/index.d.ts", @@ -36,7 +36,8 @@ "prepush": "yarn test && yarn lint & yarn typescript" }, "dependencies": { - "prop-types": "^15.7.2" + "prop-types": "^15.7.2", + "react-native-iphone-x-helper": "^1.3.1" }, "peerDependencies": { "react": "*", diff --git a/src/StickyParallaxHeaderComponent.tsx b/src/StickyParallaxHeader.js similarity index 60% rename from src/StickyParallaxHeaderComponent.tsx rename to src/StickyParallaxHeader.js index 2c1cd9e9..623e176c 100644 --- a/src/StickyParallaxHeaderComponent.tsx +++ b/src/StickyParallaxHeader.js @@ -1,146 +1,50 @@ -import React, { Component, MutableRefObject, ReactNode } from 'react'; -import { - Dimensions, - ScrollView, - View, - Animated, - Easing, - ScrollViewProps, - ViewStyle, - TextStyle, - ImageSourcePropType, - LayoutChangeEvent, - LayoutRectangle, - NativeSyntheticEvent, - NativeScrollEvent, - StyleProp, -} from 'react-native'; +import React, { Component } from 'react'; +import { Dimensions, ScrollView, View, Animated, Easing, Image, ImageBackground } from 'react-native'; import { ScrollableTabBar, ScrollableTabView, HeaderBackgroundImage } from './components'; -import { colors, constants } from './constants'; +import { constants } from './constants'; import styles from './styles'; import { getSafelyScrollNode, setRef } from './utils'; -import type { Tab } from './index'; -import type { ScrollableTabBarProps } from './components/ScrollableTabBar/ScrollableTabBar'; -import type { MountedTabType } from './index'; -const { Value, createAnimatedComponent, event, timing, ValueXY } = Animated; +const { divide, Value, createAnimatedComponent, event, timing, ValueXY } = Animated; const AnimatedScrollView = createAnimatedComponent(ScrollView); -export type DetailsData = { - cardsAmount: number; - author: string; - type: string; - label: string; - color: string; - labelColor: string; - image: ImageSourcePropType; - about: string; -}; - -export interface StickyParallaxHeaderProps { - headerType?: undefined | 'Default'; - background?: ReactNode; - backgroundColor: string; - backgroundImage?: ImageSourcePropType; - bounces?: boolean; - children?: ReactNode; - contentContainerStyles: StyleProp; - foreground: ReactNode; - header: React.ReactElement<{ style?: ViewStyle }>; - headerHeight: number; - headerSize?: (h: LayoutRectangle) => void; - initialPage: number; - onChangeTab?: (tab: MountedTabType) => void; - onEndReached?: () => void; - parallaxHeight: number; - rememberTabScrollPosition: boolean; - scrollEvent?: ScrollViewProps['onScroll']; - snapToEdge?: boolean; - tabTextActiveStyle: StyleProp; - tabTextContainerActiveStyle: StyleProp; - tabTextContainerStyle: StyleProp; - tabTextStyle: StyleProp; - tabs?: Tab[]; - tabsContainerBackgroundColor?: string; - tabWrapperStyle?: StyleProp; - tabsContainerStyle?: StyleProp; - snapStartThreshold?: number; - snapStopThreshold?: number; - snapValue?: number; - transparentHeader?: boolean; - onRef: (t: StickyParallaxHeaderComponent | null) => void; - onTopReached?: () => void; - scrollRef: (t: ScrollView) => void | MutableRefObject; - keyboardShouldPersistTaps: ScrollViewProps['keyboardShouldPersistTaps']; - refreshControl: ScrollViewProps['refreshControl']; - onMomentumScrollEnd: ScrollViewProps['onMomentumScrollEnd']; - onMomentumScrollBegin: ScrollViewProps['onMomentumScrollBegin']; - decelerationRate: 'fast' | 'normal'; - tabUnderlineColor: string | null; - tabsContainerHorizontalPadding?: number; - horizontalScrollBounces?: boolean; -} - -type State = { - containerWidth: number; - currentPage: number; - isFolded: boolean; -}; - -type XYValue = { x: number; y: number }; - -class StickyParallaxHeaderComponent extends Component { - tabsScrollPosition: number[]; - - scrollY: Animated.ValueXY; - - scrollXIOS: Animated.Value; - - _value: XYValue | null; - - scroll: ScrollView | null; - - scrollView: ScrollView | null; - - tab: View | null; - - constructor(props: StickyParallaxHeaderProps) { +class StickyParallaxHeader extends Component { + constructor(props) { super(props); const { initialPage } = this.props; const { width } = Dimensions.get('window'); - - this.scrollXIOS = new Value(initialPage * width); - + const scrollXIOS = new Value(initialPage * width); + const containerWidthAnimatedValue = new Value(width); this.tabsScrollPosition = []; - this._value = null; - this.tab = null; + // eslint-disable-next-line no-underscore-dangle + containerWidthAnimatedValue.__makeNative(); + const scrollValue = divide(scrollXIOS, containerWidthAnimatedValue); this.state = { + scrollValue, containerWidth: width, currentPage: initialPage, isFolded: false, + height: props.headerBackgroundHeight }; this.scrollY = new ValueXY(); - this.scroll = null; - this.scrollView = null; } componentDidMount() { const { onRef } = this.props; - - this.scrollY.addListener((value) => (this._value = value)); + // eslint-disable-next-line + this.scrollY.addListener(({ value }) => (this._value = value)); onRef?.(this); } - componentDidUpdate(_prevProps: Readonly, prevState: Readonly) { + componentDidUpdate(prevProps, prevState) { const { headerHeight, parallaxHeight, tabs, rememberTabScrollPosition } = this.props; const prevPage = prevState.currentPage; const { currentPage, isFolded } = this.state; const isRenderingTabs = tabs && tabs.length > 0; if (isRenderingTabs && prevPage !== currentPage && isFolded) { - // @ts-ignore - const currentScrollPosition: number = this.scrollY.__getValue().y; + const currentScrollPosition = this.scrollY.__getValue().y; const scrollHeight = Math.max(parallaxHeight, headerHeight * 2); this.tabsScrollPosition[prevPage] = currentScrollPosition; @@ -151,50 +55,44 @@ class StickyParallaxHeaderComponent extends Component { const scrollNode = getSafelyScrollNode(this.scroll); - - scrollNode?.scrollTo({ x: 0, y: 40, animated: true }); + scrollNode.scrollTo({ x: 0, y: 40, animated: true }); return setTimeout(() => { setTimeout(() => { - scrollNode?.scrollTo({ x: 0, y: 25, animated: true }); + scrollNode.scrollTo({ x: 0, y: 25, animated: true }); }, 200); - scrollNode?.scrollTo({ x: 0, y: 0, animated: true }); + scrollNode.scrollTo({ x: 0, y: 0, animated: true }); }, 300); }; - onScrollEndSnapToEdge = (height: number) => { + onScrollEndSnapToEdge = (height) => { const { snapStartThreshold, snapStopThreshold, snapValue } = this.props; const scrollHeight = snapStopThreshold || height; const snap = snapValue || height; const { snapToEdge, refreshControl } = this.props; const scrollNode = getSafelyScrollNode(this.scroll); - // @ts-ignore const scrollValue = this.scrollY.__getValue(); - const { y } = scrollValue; const snapToEdgeAnimatedValue = new ValueXY(scrollValue); const snapToEdgeThreshold = snapStartThreshold || height / 2; const id = snapToEdgeAnimatedValue.addListener((value) => { - scrollNode?.scrollTo({ x: 0, y: value.y, animated: false }); + scrollNode.scrollTo({ x: 0, y: value.y, animated: false }); }); - if (y < -20 && !constants.isAndroid && !refreshControl) this.spring(); + if (y < -20 && !constants.isAndroid && !refreshControl) this.spring(y); if (snapToEdge) { if (y > 0 && y < snapToEdgeThreshold) { @@ -203,7 +101,7 @@ class StickyParallaxHeaderComponent extends Component scrollNode?.scrollTo({ x: 0, y: 0, animated: true }) + scrollNode.scrollTo({ x: 0, y: 0, animated: true }) ) : timing(snapToEdgeAnimatedValue, { toValue: { x: 0, y: 0 }, @@ -223,7 +121,7 @@ class StickyParallaxHeaderComponent extends Component scrollNode?.scrollTo({ x: 0, y: scrollHeight, animated: true }) + scrollNode.scrollTo({ x: 0, y: scrollHeight, animated: true }) ) : timing(snapToEdgeAnimatedValue, { toValue: { x: 0, y: snap }, @@ -242,13 +140,13 @@ class StickyParallaxHeaderComponent extends Component { + onChangeTabHandler = (tab) => { const { onChangeTab } = this.props; return onChangeTab && onChangeTab(tab); }; - onLayout = (e: LayoutChangeEvent) => { + onLayout = (e) => { const { x, y, width, height } = e.nativeEvent.layout; const { headerSize } = this.props; const headerLayout = { @@ -257,14 +155,12 @@ class StickyParallaxHeaderComponent extends Component { + goToPage = (pageNumber) => { const { containerWidth, currentPage } = this.state; const offset = pageNumber * containerWidth; - if (currentPage !== pageNumber) { this.setState({ currentPage: pageNumber, @@ -279,7 +175,7 @@ class StickyParallaxHeaderComponent extends Component { + isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => { const { onEndReached } = this.props; if (layoutMeasurement.height + contentOffset.y >= contentSize.height - 20) { @@ -289,9 +185,8 @@ class StickyParallaxHeaderComponent extends Component { + isCloseToTop = ({ contentOffset }) => { const { onTopReached } = this.props; - if (contentOffset.y <= 0) { return onTopReached && onTopReached(); } @@ -302,10 +197,9 @@ class StickyParallaxHeaderComponent extends Component { const { header, headerHeight, backgroundColor, transparentHeader } = this.props; - const headerStyle = header?.props?.style ?? {}; + const headerStyle = header.props.style; const isArray = Array.isArray(headerStyle); - const arrayHeaderStyle: ViewStyle = {}; - + const arrayHeaderStyle = {}; if (isArray) { headerStyle.map((el) => Object.assign(arrayHeaderStyle, el)); } @@ -316,7 +210,9 @@ class StickyParallaxHeaderComponent extends Component @@ -325,7 +221,7 @@ class StickyParallaxHeaderComponent extends Component { + renderPlainBackground = (backgroundHeight) => { const { background } = this.props; return ( @@ -341,7 +237,7 @@ class StickyParallaxHeaderComponent extends Component { + renderForeground = (backgroundHeight) => { const { foreground, tabsContainerBackgroundColor, backgroundImage } = this.props; return ( @@ -349,7 +245,7 @@ class StickyParallaxHeaderComponent extends Component {foreground} @@ -366,25 +262,26 @@ class StickyParallaxHeaderComponent extends Component; @@ -409,14 +306,13 @@ class StickyParallaxHeaderComponent extends Component Object.assign(arrayHeaderStyle, el)); } @@ -425,12 +321,14 @@ class StickyParallaxHeaderComponent extends Component 0; + const shouldUseBgColor = contentContainerStyles && contentContainerStyles.backgroundColor; const hasSingleTab = tabs?.length === 1 || false; const hasSingleElement = hasSingleTab || (!tabs && children !== undefined); return ( + {header && this.renderHeader()} { - this.scroll = getSafelyScrollNode(c) as ScrollView; - setRef(scrollRef, this.scroll); + this.scroll = c; + setRef(scrollRef, c); }} contentContainerStyle={{ minHeight: scrollViewMinHeight, + backgroundColor: shouldUseBgColor, }} onScrollEndDrag={() => this.onScrollEndSnapToEdge(scrollHeight)} scrollEventThrottle={1} @@ -465,10 +364,12 @@ class StickyParallaxHeaderComponent extends Component) => { + listener: (e) => { + console.log('onScrroll========',300-e.nativeEvent.contentOffset.y) + this.setState({height: 300-e.nativeEvent.contentOffset.y>100?300-e.nativeEvent.contentOffset.y:100}) this.isCloseToBottom(e.nativeEvent); this.isCloseToTop(e.nativeEvent); - scrollEvent?.(e); + scrollEvent(e); }, } )}> @@ -481,10 +382,9 @@ class StickyParallaxHeaderComponent extends Component - {backgroundImage ? ( + {/* {backgroundImage ? ( ) : ( this.renderPlainBackground(scrollHeight) - )} + )} */} {this.renderForeground(scrollHeight)} {shouldRenderTabs && this.renderTabs()} + {/* {renderBody && renderBody} */} this.scrollXIOS.setValue(x)} contentContainerStyles={contentContainerStyles} initialPage={initialPage} onChangeTab={(i) => this.onChangeTabHandler(i)} + tabs={tabs} page={currentPage} swipedPage={this.goToPage} + scrollRef={this.scroll} + scrollHeight={scrollHeight} + isHeaderFolded={isFolded} minScrollHeight={innerScrollHeight} scrollEnabled={!hasSingleElement} keyboardShouldPersistTaps={keyboardShouldPersistTaps}> @@ -511,8 +414,9 @@ class StickyParallaxHeaderComponent extends Component ( { this.tab = c; }}> @@ -524,34 +428,34 @@ class StickyParallaxHeaderComponent extends Component ); } - - static defaultProps = { - bounces: true, - contentContainerStyles: {}, - headerHeight: 92, - backgroundColor: '', - initialPage: 0, - parallaxHeight: 0, - snapToEdge: true, - tabTextActiveStyle: {}, - tabTextContainerActiveStyle: {}, - tabTextContainerStyle: {}, - tabTextStyle: {}, - tabWrapperStyle: {}, - rememberTabScrollPosition: false, - snapStartThreshold: false, - snapStopThreshold: false, - snapValue: false, - transparentHeader: false, - onRef: null, - scrollRef: null, - keyboardShouldPersistTaps: undefined, - refreshControl: undefined, - decelerationRate: 'fast', - onMomentumScrollEnd: undefined, - onMomentumScrollBegin: undefined, - tabUnderlineColor: colors.white, - }; } -export default StickyParallaxHeaderComponent; + +StickyParallaxHeader.defaultProps = { + bounces: true, + contentContainerStyles: {}, + headerHeight: 92, + backgroundColor: '', + initialPage: 0, + parallaxHeight: 0, + snapToEdge: true, + tabTextActiveStyle: {}, + tabTextContainerActiveStyle: {}, + tabTextContainerStyle: {}, + tabTextStyle: {}, + tabWrapperStyle: {}, + rememberTabScrollPosition: false, + snapStartThreshold: false, + snapStopThreshold: false, + snapValue: false, + transparentHeader: false, + onRef: null, + scrollRef: null, + keyboardShouldPersistTaps: undefined, + refreshControl: undefined, + decelerationRate: 'fast', + onMomentumScrollEnd: undefined, + onMomentumScrollBegin: undefined, +}; + +export default StickyParallaxHeader; diff --git a/src/assets/data/cards.js b/src/assets/data/cards.js new file mode 100644 index 00000000..fac7e9ad --- /dev/null +++ b/src/assets/data/cards.js @@ -0,0 +1,185 @@ +export const cardsReact = [ + { + question: 'We can go for keys when there is possibility that our user could change the data.', + cards: [ + { number: 'A', question: 'Keys', value: true, revealed: false, picked: false }, + { number: 'B', question: 'Ref', value: false, revealed: false, picked: false }, + { number: 'C', question: 'Both', value: false, revealed: false, picked: false }, + { number: 'D', question: 'None of above', value: false, revealed: false, picked: false }, + ], + }, + { + question: 'JSX is typesafe.', + cards: [ + { number: 'A', question: 'True', value: true, revealed: false, picked: false }, + { number: 'B', question: 'False', value: false, revealed: false, picked: false }, + ], + }, + { + question: 'Which of the following needs to be updated to achieve dynamic UI updates?', + cards: [ + { number: 'A', question: 'State', value: true, revealed: false, picked: false }, + { number: 'B', question: 'Props', value: false, revealed: false, picked: false }, + ], + }, +]; + +const cardsAgile = [ + { + question: 'When might a Sprint be abnormally cancelled?', + cardsAmount: 31, + author: 'Ewa', + type: 'Agile Basics', + cards: [ + { + number: 'A', + question: 'When the Sprint Goal becomes obsolete', + value: true, + revealed: false, + picked: false, + }, + { + number: 'B', + question: 'When the sales department has an important new opportunity', + value: false, + revealed: false, + picked: false, + }, + { + number: 'C', + question: + 'When it becomes clear that not everything will be finished by the end of the Sprint', + value: false, + revealed: false, + picked: false, + }, + ], + }, + { + question: 'Who is required to attend the Daily Scrum?', + cards: [ + { + number: 'A', + question: 'The Development Team and Scrum Master', + value: true, + revealed: false, + picked: false, + }, + { + number: 'B', + question: 'The Scrum Master and Product Owner', + value: false, + revealed: false, + picked: false, + }, + { + number: 'C', + question: 'The Development Team', + value: false, + revealed: false, + picked: false, + }, + ], + }, + { + question: 'The Development Team should have all the skills needed to:', + cards: [ + { + number: 'A', + question: + 'Complete the project as estimated when the date and cost are committed to the Product Owner', + value: true, + revealed: false, + picked: false, + }, + { + number: 'B', + question: + 'Do all of the development work, except for specialized testing that requires additional tools and environments', + value: false, + revealed: false, + picked: false, + }, + { + number: 'C', + question: + 'Turn the Product Backlog items it selects into an increment of potentially releasable product functionality', + value: false, + revealed: false, + picked: false, + }, + ], + }, +]; + +const cardsDesign = [ + { + question: 'Can design system contain information about copywriting?', + cards: [ + { number: 'A', question: 'Yes', value: true, revealed: false, picked: false }, + { number: 'B', question: 'No', value: false, revealed: false, picked: false }, + ], + }, + { + question: 'Who is taking care of managing a design system?', + cards: [ + { number: 'A', question: 'Product Designer', value: true, revealed: false, picked: false }, + { number: 'B', question: 'UI Designer', value: false, revealed: false, picked: false }, + { number: 'C', question: 'None of above', value: false, revealed: false, picked: false }, + ], + }, + { + question: 'Are there official standards for Design Systems?', + cards: [ + { number: 'A', question: 'Yes', value: true, revealed: false, picked: false }, + { number: 'B', question: 'No', value: false, revealed: false, picked: false }, + ], + }, + { + question: 'What kind of animal is the dolphin?', + cards: [ + { number: 'A', question: 'Mammalr', value: true, revealed: false, picked: false }, + { number: 'B', question: 'Reptile', value: false, revealed: false, picked: false }, + { number: 'C', question: 'Fish', value: false, revealed: false, picked: false }, + { number: 'C', question: 'Amphibian', value: false, revealed: false, picked: false }, + ], + }, +]; + +const Brandon = { + cardsAmount: 10, + author: 'Brandon', + type: 'Product Design', + label: 'Design System', + cards: cardsDesign, + color: 'rgb(78,15,255)', + labelColor: 'rgb(89,80,249)', + image: require('../images/photosPortraitBrandon.png'), + about: 'Coffee buff. Web enthusiast. Unapologetic student. Gamer. Avid organizer.', +}; + +const Ewa = { + cardsAmount: 31, + author: 'Ewa', + type: 'Project Management', + label: 'Agile Basics', + cards: cardsAgile, + color: 'rgb(138,85,192)', + labelColor: 'rgb(163,109,217)', + image: require('../images/photosPortraitEwa.png'), + about: 'Wannabe entrepreneur. Reader. Devoted organizer. Social media lover. Analyst.', +}; + +const Jennifer = { + cardsAmount: 16, + author: 'Jennifer', + type: 'Development', + label: 'React Native 101', + cards: cardsReact, + color: 'rgb(255,94,107)', + labelColor: 'rgb(255,130,140)', + image: require('../images/photosPortraitJennifer.png'), + about: 'Web nerd. Alcohol trailblazer. Organizer. Hipster-friendly explorer.', +}; + +export { Brandon, Ewa, Jennifer }; diff --git a/src/assets/icons/Check.png b/src/assets/icons/Check.png new file mode 100755 index 00000000..21aa7e7f Binary files /dev/null and b/src/assets/icons/Check.png differ diff --git a/src/assets/icons/Check@2x.png b/src/assets/icons/Check@2x.png new file mode 100755 index 00000000..211a1c8b Binary files /dev/null and b/src/assets/icons/Check@2x.png differ diff --git a/src/assets/icons/Check@3x.png b/src/assets/icons/Check@3x.png new file mode 100755 index 00000000..26373aa4 Binary files /dev/null and b/src/assets/icons/Check@3x.png differ diff --git a/src/assets/icons/Close.png b/src/assets/icons/Close.png new file mode 100755 index 00000000..588c48b6 Binary files /dev/null and b/src/assets/icons/Close.png differ diff --git a/src/assets/icons/Close@2x.png b/src/assets/icons/Close@2x.png new file mode 100755 index 00000000..5b0df79f Binary files /dev/null and b/src/assets/icons/Close@2x.png differ diff --git a/src/assets/icons/Close@3x.png b/src/assets/icons/Close@3x.png new file mode 100755 index 00000000..ef635771 Binary files /dev/null and b/src/assets/icons/Close@3x.png differ diff --git a/src/assets/icons/Icon-Menu.png b/src/assets/icons/Icon-Menu.png new file mode 100755 index 00000000..6df4fa76 Binary files /dev/null and b/src/assets/icons/Icon-Menu.png differ diff --git a/src/assets/icons/Icon-Menu@2x.png b/src/assets/icons/Icon-Menu@2x.png new file mode 100755 index 00000000..e77b8830 Binary files /dev/null and b/src/assets/icons/Icon-Menu@2x.png differ diff --git a/src/assets/icons/Icon-Menu@3x.png b/src/assets/icons/Icon-Menu@3x.png new file mode 100755 index 00000000..5650dff3 Binary files /dev/null and b/src/assets/icons/Icon-Menu@3x.png differ diff --git a/src/assets/icons/cards.png b/src/assets/icons/cards.png new file mode 100644 index 00000000..c9821637 Binary files /dev/null and b/src/assets/icons/cards.png differ diff --git a/src/assets/icons/cards@2x.png b/src/assets/icons/cards@2x.png new file mode 100644 index 00000000..2f14b3c5 Binary files /dev/null and b/src/assets/icons/cards@2x.png differ diff --git a/src/assets/icons/cards@3x.png b/src/assets/icons/cards@3x.png new file mode 100644 index 00000000..834737fe Binary files /dev/null and b/src/assets/icons/cards@3x.png differ diff --git a/src/assets/icons/cards_black.png b/src/assets/icons/cards_black.png new file mode 100755 index 00000000..0b92dcf5 Binary files /dev/null and b/src/assets/icons/cards_black.png differ diff --git a/src/assets/icons/cards_black@2x.png b/src/assets/icons/cards_black@2x.png new file mode 100755 index 00000000..36097a49 Binary files /dev/null and b/src/assets/icons/cards_black@2x.png differ diff --git a/src/assets/icons/cards_black@3x.png b/src/assets/icons/cards_black@3x.png new file mode 100755 index 00000000..f17ee7ea Binary files /dev/null and b/src/assets/icons/cards_black@3x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/Contents.json b/src/assets/icons/iOS/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a3bac376 --- /dev/null +++ b/src/assets/icons/iOS/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images": [ + { + "size": "20x20", + "idiom": "iphone", + "filename": "iconApp-20@2x.png", + "scale": "2x" + }, + { + "size": "20x20", + "idiom": "iphone", + "filename": "iconApp-20@3x.png", + "scale": "3x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "iconApp-20.png", + "scale": "1x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "iconApp-20@2x.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "iconApp-29@2x.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "iconApp-29@3x.png", + "scale": "3x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "iconApp-40@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "iconApp-40@3x.png", + "scale": "3x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "iconApp-60@2x.png", + "scale": "2x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "iconApp-60@3x.png", + "scale": "3x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "iconApp-29.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "iconApp-29@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "iconApp-40.png", + "scale": "1x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "iconApp-40@2x.png", + "scale": "2x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "iconApp-76.png", + "scale": "1x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "iconApp-76@2x.png", + "scale": "2x" + }, + { + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "iconApp-83.5@2x.png", + "scale": "2x" + }, + { + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "iconApp-1024.png", + "scale": "1x" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-1024.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-1024.png new file mode 100644 index 00000000..10e4fbb2 Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-1024.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-20.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-20.png new file mode 100644 index 00000000..581137a9 Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-20.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-20@2x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-20@2x.png new file mode 100644 index 00000000..27d2169c Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-20@2x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-20@3x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-20@3x.png new file mode 100644 index 00000000..04d42912 Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-20@3x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-29.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-29.png new file mode 100644 index 00000000..952f4482 Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-29.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-29@2x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-29@2x.png new file mode 100644 index 00000000..4b640a9b Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-29@2x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-29@3x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-29@3x.png new file mode 100644 index 00000000..0c1f937e Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-29@3x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-40.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-40.png new file mode 100644 index 00000000..27d2169c Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-40.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-40@2x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-40@2x.png new file mode 100644 index 00000000..34406a5e Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-40@2x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-40@3x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-40@3x.png new file mode 100644 index 00000000..61670709 Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-40@3x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-60@2x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-60@2x.png new file mode 100644 index 00000000..61670709 Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-60@2x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-60@3x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-60@3x.png new file mode 100644 index 00000000..2ab6f38f Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-60@3x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-76.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-76.png new file mode 100644 index 00000000..8f4005c2 Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-76.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-76@2x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-76@2x.png new file mode 100644 index 00000000..f490c17f Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-76@2x.png differ diff --git a/src/assets/icons/iOS/AppIcon.appiconset/iconApp-83.5@2x.png b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-83.5@2x.png new file mode 100644 index 00000000..4f1f2755 Binary files /dev/null and b/src/assets/icons/iOS/AppIcon.appiconset/iconApp-83.5@2x.png differ diff --git a/src/assets/icons/iconApp.png b/src/assets/icons/iconApp.png new file mode 100644 index 00000000..3598bec5 Binary files /dev/null and b/src/assets/icons/iconApp.png differ diff --git a/src/assets/icons/iconApp@2x.png b/src/assets/icons/iconApp@2x.png new file mode 100644 index 00000000..e19f96fe Binary files /dev/null and b/src/assets/icons/iconApp@2x.png differ diff --git a/src/assets/icons/iconApp@3x.png b/src/assets/icons/iconApp@3x.png new file mode 100644 index 00000000..53be0381 Binary files /dev/null and b/src/assets/icons/iconApp@3x.png differ diff --git a/src/assets/icons/iconCloseWhite.png b/src/assets/icons/iconCloseWhite.png new file mode 100644 index 00000000..34059664 Binary files /dev/null and b/src/assets/icons/iconCloseWhite.png differ diff --git a/src/assets/icons/iconCloseWhite@2x.png b/src/assets/icons/iconCloseWhite@2x.png new file mode 100644 index 00000000..3370c11e Binary files /dev/null and b/src/assets/icons/iconCloseWhite@2x.png differ diff --git a/src/assets/icons/iconCloseWhite@3x.png b/src/assets/icons/iconCloseWhite@3x.png new file mode 100644 index 00000000..b0079e3d Binary files /dev/null and b/src/assets/icons/iconCloseWhite@3x.png differ diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100755 index 00000000..29bbf8f7 Binary files /dev/null and b/src/assets/images/logo.png differ diff --git a/src/assets/images/logo@2x.png b/src/assets/images/logo@2x.png new file mode 100755 index 00000000..d556c26d Binary files /dev/null and b/src/assets/images/logo@2x.png differ diff --git a/src/assets/images/logo@3x.png b/src/assets/images/logo@3x.png new file mode 100755 index 00000000..a9a68c36 Binary files /dev/null and b/src/assets/images/logo@3x.png differ diff --git a/src/assets/images/photosPortraitBrandon.png b/src/assets/images/photosPortraitBrandon.png new file mode 100644 index 00000000..f4121068 Binary files /dev/null and b/src/assets/images/photosPortraitBrandon.png differ diff --git a/src/assets/images/photosPortraitBrandon@2x.png b/src/assets/images/photosPortraitBrandon@2x.png new file mode 100644 index 00000000..d685f36f Binary files /dev/null and b/src/assets/images/photosPortraitBrandon@2x.png differ diff --git a/src/assets/images/photosPortraitBrandon@3x.png b/src/assets/images/photosPortraitBrandon@3x.png new file mode 100644 index 00000000..b98068a9 Binary files /dev/null and b/src/assets/images/photosPortraitBrandon@3x.png differ diff --git a/src/assets/images/photosPortraitEwa.png b/src/assets/images/photosPortraitEwa.png new file mode 100644 index 00000000..6d422117 Binary files /dev/null and b/src/assets/images/photosPortraitEwa.png differ diff --git a/src/assets/images/photosPortraitEwa@2x.png b/src/assets/images/photosPortraitEwa@2x.png new file mode 100644 index 00000000..4fec1d8c Binary files /dev/null and b/src/assets/images/photosPortraitEwa@2x.png differ diff --git a/src/assets/images/photosPortraitEwa@3x.png b/src/assets/images/photosPortraitEwa@3x.png new file mode 100644 index 00000000..84032bc1 Binary files /dev/null and b/src/assets/images/photosPortraitEwa@3x.png differ diff --git a/src/assets/images/photosPortraitJennifer.png b/src/assets/images/photosPortraitJennifer.png new file mode 100644 index 00000000..089cc080 Binary files /dev/null and b/src/assets/images/photosPortraitJennifer.png differ diff --git a/src/assets/images/photosPortraitJennifer@2x.png b/src/assets/images/photosPortraitJennifer@2x.png new file mode 100644 index 00000000..3ea45d13 Binary files /dev/null and b/src/assets/images/photosPortraitJennifer@2x.png differ diff --git a/src/assets/images/photosPortraitJennifer@3x.png b/src/assets/images/photosPortraitJennifer@3x.png new file mode 100644 index 00000000..3cb06ce5 Binary files /dev/null and b/src/assets/images/photosPortraitJennifer@3x.png differ diff --git a/src/assets/images/photosPortraitMe.png b/src/assets/images/photosPortraitMe.png new file mode 100644 index 00000000..c02f06e4 Binary files /dev/null and b/src/assets/images/photosPortraitMe.png differ diff --git a/src/assets/images/photosPortraitMe@2x.png b/src/assets/images/photosPortraitMe@2x.png new file mode 100644 index 00000000..61a52449 Binary files /dev/null and b/src/assets/images/photosPortraitMe@2x.png differ diff --git a/src/assets/images/photosPortraitMe@3x.png b/src/assets/images/photosPortraitMe@3x.png new file mode 100644 index 00000000..1c8c23e6 Binary files /dev/null and b/src/assets/images/photosPortraitMe@3x.png differ diff --git a/src/components/HeaderBackgroundImage/HeaderBackgroundImage.tsx b/src/components/HeaderBackgroundImage/HeaderBackgroundImage.js similarity index 57% rename from src/components/HeaderBackgroundImage/HeaderBackgroundImage.tsx rename to src/components/HeaderBackgroundImage/HeaderBackgroundImage.js index ef3d42d7..ced87665 100644 --- a/src/components/HeaderBackgroundImage/HeaderBackgroundImage.tsx +++ b/src/components/HeaderBackgroundImage/HeaderBackgroundImage.js @@ -1,15 +1,11 @@ -import React, { ReactNode, VFC } from 'react'; -import { ImageBackground, Animated, ImageSourcePropType } from 'react-native'; +import React from 'react'; +import { node, number } from 'prop-types'; +import { ImageBackground, Animated, Image } from 'react-native'; import styles from './HeaderBackgroundImage.styles'; const { createAnimatedComponent } = Animated; -type HeaderBackgroundImageProps = { - background: ReactNode; - backgroundHeight: number; - backgroundImage: ImageSourcePropType; -}; -const HeaderBackgroundImage: VFC = (props) => { +const HeaderBackgroundImage = (props) => { const { backgroundHeight, backgroundImage, background } = props; const AnimatedImageBackground = createAnimatedComponent(ImageBackground); @@ -27,12 +23,14 @@ const HeaderBackgroundImage: VFC = (props) => { ); }; -const headerImagesAreEqual = ( - prevProps: HeaderBackgroundImageProps, - props: HeaderBackgroundImageProps -) => - // @ts-ignore - prevProps?.backgroundImage?.uri === props?.backgroundImage?.uri && +HeaderBackgroundImage.propTypes = { + background: node, + backgroundHeight: number, + backgroundImage: Image.propTypes.source, +}; + +const headerImagesAreEqual = (prevProps, props) => + prevProps.backgroundImage.uri === props.backgroundImage.uri && prevProps.backgroundHeight === props.backgroundHeight && prevProps.background === props.background; diff --git a/src/components/HeaderBackgroundImage/HeaderBackgroundImage.styles.ts b/src/components/HeaderBackgroundImage/HeaderBackgroundImage.styles.js similarity index 100% rename from src/components/HeaderBackgroundImage/HeaderBackgroundImage.styles.ts rename to src/components/HeaderBackgroundImage/HeaderBackgroundImage.styles.js diff --git a/src/components/IconRenderer/IconRenderer.tsx b/src/components/IconRenderer/IconRenderer.tsx deleted file mode 100644 index 8ccdd47a..00000000 --- a/src/components/IconRenderer/IconRenderer.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { ReactElement, VFC } from 'react'; -import { Image, ImageSourcePropType } from 'react-native'; -import styles from '../../predefinedComponents/DetailsHeader/DetailsHeader.styles'; - -interface Props { - icon?: (() => ReactElement) | ImageSourcePropType; -} - -const IconRenderer: VFC = ({ icon }) => { - if (typeof icon === 'function') { - return icon(); - } - - return icon ? : null; -}; - -export default IconRenderer; diff --git a/src/components/ScrollableTabBar/ScrollableTabBar.tsx b/src/components/ScrollableTabBar/ScrollableTabBar.js similarity index 53% rename from src/components/ScrollableTabBar/ScrollableTabBar.tsx rename to src/components/ScrollableTabBar/ScrollableTabBar.js index c8ddb230..8b939a7a 100644 --- a/src/components/ScrollableTabBar/ScrollableTabBar.tsx +++ b/src/components/ScrollableTabBar/ScrollableTabBar.js @@ -1,92 +1,58 @@ +/* eslint-disable react/forbid-prop-types */ +/* eslint-disable no-return-assign */ import React from 'react'; -import { - Animated, - Text, - TouchableOpacity, - View, - ScrollView, - ViewStyle, - TextStyle, - StyleProp, - Dimensions, -} from 'react-native'; +import { Animated, Text, TouchableOpacity, View, ScrollView } from 'react-native'; +import { arrayOf, func, number, object, shape, string, element, oneOfType } from 'prop-types'; import { constants } from '../../constants'; import styles from './ScrollableTabBar.styles'; -import type { Tab } from 'react-native-sticky-parallax-header'; - -const UNDERLINE_PADDING = 20; - -export type ScrollableTabBarProps = { - tabs: Tab[]; - activeTab: number; - goToPage: (index: number) => void; - scrollValue: Animated.Value; - tabTextStyle: StyleProp; - tabTextActiveStyle: StyleProp; - tabTextContainerStyle: StyleProp; - tabTextContainerActiveStyle: StyleProp; - tabsContainerBackgroundColor?: string; - tabWrapperStyle?: StyleProp; - tabsContainerStyle?: StyleProp; - tabUnderlineColor?: string | null; - tabsContainerHorizontalPadding?: number; -}; -type State = { tabUnderlineWidth: number[] }; - -class ScrollableTabBar extends React.PureComponent { - tabsWidth: number[]; - - scrollView: ScrollView | null; - - currentXPosition: number; - - constructor(props: ScrollableTabBarProps) { + +const UNDERLINE_PADDING = 16; + +class ScrollableTabBar extends React.PureComponent { + constructor(props) { super(props); this.currentXPosition = 0; - this.scrollView = null; - this.tabsWidth = this.props.tabs.map(() => 0); this.state = { tabUnderlineWidth: props.tabs.map((_) => 0), }; } - componentDidUpdate(prevProps: Readonly) { + componentDidUpdate(prevProps) { const { activeTab } = this.props; - if (prevProps.activeTab !== activeTab) { this.scrollToTab(activeTab); } } - adjustPrevious = (page: number) => { + adjustPrevious = (page) => { const lastHidden = Math.floor(this.currentXPosition / (constants.deviceWidth * 0.3)); - if (page <= lastHidden) { this.currentXPosition = constants.deviceWidth * 0.3 * page; - this?.scrollView?.scrollTo?.({ + this.scrollView.scrollTo({ x: this.currentXPosition, }); } }; - adjustNext = (page: number) => { + adjustNext = (page) => { + // eslint-disable-next-line max-len const invisibleX = constants.deviceWidth + this.currentXPosition - constants.deviceWidth * 0.3 * (page + 1); if (invisibleX < 0) { this.currentXPosition = this.currentXPosition - invisibleX; - this?.scrollView?.scrollTo?.({ + this.scrollView.scrollTo({ x: this.currentXPosition, }); } }; - scrollToTab = (page: number) => { + scrollToTab = (page) => { const { tabs } = this.props; if (tabs.length > 3) { if (page === 0) { - this?.scrollView?.scrollTo?.({ + this.scrollView.scrollTo({ x: 0, }); this.currentXPosition = 0; @@ -94,24 +60,22 @@ class ScrollableTabBar extends React.PureComponent this.adjustPrevious(page - 1); this.adjustNext(page + 1); } else { - this?.scrollView?.scrollToEnd?.(); + this.scrollView.scrollToEnd(); this.currentXPosition = constants.deviceWidth * 0.3 * tabs.length - constants.deviceWidth; } } }; - goToPage = (page: number) => { + goToPage = (page) => { const { goToPage } = this.props; - this.scrollToTab(page); return goToPage(page); }; - renderIcon = (icon: Tab['icon'], page: number) => { + renderIcon = (icon, page) => { const { activeTab } = this.props; const isActive = activeTab === page; - if (typeof icon === 'function') { return icon(isActive); } @@ -128,36 +92,29 @@ class ScrollableTabBar extends React.PureComponent tabTextActiveStyle, tabTextContainerStyle, tabTextContainerActiveStyle, - tabsContainerBackgroundColor, + // tabsContainerBackgroundColor, tabWrapperStyle, tabsContainerStyle, - tabUnderlineColor, - tabsContainerHorizontalPadding, + tabBackgroundColor, + tabBarUnderlineStyle } = this.props; const { tabUnderlineWidth } = this.state; - const HORIZONTAL_PADDINGS = tabsContainerHorizontalPadding ?? UNDERLINE_PADDING; - - const allSizes = - this.tabsWidth.every((it) => !!it) && this.tabsWidth.length > 0 && tabUnderlineColor; - const inputRanges = allSizes ? [0] : [0, 1]; - const outputRanges = allSizes ? [HORIZONTAL_PADDINGS] : [HORIZONTAL_PADDINGS, 100]; - let tabWidth = HORIZONTAL_PADDINGS; - - if (tabUnderlineColor) { - for (let i = 0; i < this.tabsWidth.length; i++) { - if (this.tabsWidth.length && allSizes) { - if (i > 0) { - inputRanges[i] = Dimensions.get('screen').width * i; - outputRanges[i] = tabWidth; - } - tabWidth += this.tabsWidth[i]; - } - } - } + const tabWidth = tabs.length > 3 ? constants.deviceWidth * 0.3 : constants.deviceWidth * 0.33; + + const tabUnderlineStyle = { + position: 'absolute', + width: tabUnderlineWidth[activeTab] + 2 * UNDERLINE_PADDING, + marginLeft: (tabWidth - tabUnderlineWidth[activeTab] - 2 * UNDERLINE_PADDING) / 2, + marginRight: (tabWidth - tabUnderlineWidth[activeTab] - 2 * UNDERLINE_PADDING) / 2, + bottom: 0, + borderRadius: 6, + height: 3, + }; + const translateX = scrollValue.interpolate({ - inputRange: inputRanges, - outputRange: outputRanges, + inputRange: [0, 1], + outputRange: [0, tabWidth], }); return ( @@ -165,21 +122,20 @@ class ScrollableTabBar extends React.PureComponent style={[ styles.container, { - backgroundColor: tabsContainerBackgroundColor, + // backgroundColor: tabsContainerBackgroundColor, + backgroundColor: tabBackgroundColor, + marginBottom:3 }, ]}> (this.scrollView = r)} onScrollEndDrag={(event) => (this.currentXPosition = event.nativeEvent.contentOffset.x)} + vertical={false} horizontal bounces={false} + showsHorizontalScrollIndicator={false}> {tabs.map((tab, page) => { const isTabActive = activeTab === page; @@ -187,12 +143,9 @@ class ScrollableTabBar extends React.PureComponent return ( { - this.tabsWidth[page] = x.nativeEvent.layout.width; - }} key={tabKey} accessible - style={[tabWrapperStyle, styles.noMargins]} + style={tabWrapperStyle} accessibilityLabel={tabKey} accessibilityTraits="button" activeOpacity={0.9} @@ -206,13 +159,13 @@ class ScrollableTabBar extends React.PureComponent {this.renderIcon(tab.icon, page)} {tab.title && ( { const newWidth = [...tabUnderlineWidth]; - newWidth[page] = width; this.setState({ tabUnderlineWidth: newWidth, @@ -226,22 +179,18 @@ class ScrollableTabBar extends React.PureComponent ); })} - {tabUnderlineColor && ( - - )} + ); diff --git a/src/components/ScrollableTabBar/ScrollableTabBar.styles.ts b/src/components/ScrollableTabBar/ScrollableTabBar.styles.js similarity index 68% rename from src/components/ScrollableTabBar/ScrollableTabBar.styles.ts rename to src/components/ScrollableTabBar/ScrollableTabBar.styles.js index 5cf603c8..b599946e 100644 --- a/src/components/ScrollableTabBar/ScrollableTabBar.styles.ts +++ b/src/components/ScrollableTabBar/ScrollableTabBar.styles.js @@ -6,7 +6,8 @@ export default StyleSheet.create({ alignItems: 'center', }, contentContainer: { - alignItems: 'center', + // alignItems: 'center', + paddingHorizontal: 20, }, tabContainer: { alignItems: 'center', @@ -25,11 +26,4 @@ export default StyleSheet.create({ nestedStyle: { alignSelf: 'center', }, - tabUnderlineStyles: { - position: 'absolute', - bottom: 0, - borderRadius: 6, - height: 3, - }, - noMargins: { paddingLeft: 0, paddingRight: 0, marginLeft: 0, marginRight: 0 }, }); diff --git a/src/components/ScrollableTabView/SceneComponent.tsx b/src/components/ScrollableTabView/SceneComponent.js similarity index 51% rename from src/components/ScrollableTabView/SceneComponent.tsx rename to src/components/ScrollableTabView/SceneComponent.js index 8bb7853e..ba6667b6 100644 --- a/src/components/ScrollableTabView/SceneComponent.tsx +++ b/src/components/ScrollableTabView/SceneComponent.js @@ -1,11 +1,10 @@ -import React, { FC } from 'react'; -import { View, ViewProps } from 'react-native'; +import React from 'react'; +import { View } from 'react-native'; +import { node } from 'prop-types'; import StaticContainer from './StaticContainer'; -type Props = { shouldUpdated: boolean } & ViewProps; - -const SceneComponent: FC = (props) => { - const { shouldUpdated } = props; +const SceneComponent = (Props) => { + const { shouldUpdated, ...props } = Props; const { children } = props; return ( @@ -15,4 +14,8 @@ const SceneComponent: FC = (props) => { ); }; +SceneComponent.propTypes = { + children: node, +}; + export default SceneComponent; diff --git a/src/components/ScrollableTabView/ScrollableTabView.tsx b/src/components/ScrollableTabView/ScrollableTabView.js similarity index 53% rename from src/components/ScrollableTabView/ScrollableTabView.tsx rename to src/components/ScrollableTabView/ScrollableTabView.js index 7d5f5f05..7df2631f 100644 --- a/src/components/ScrollableTabView/ScrollableTabView.tsx +++ b/src/components/ScrollableTabView/ScrollableTabView.js @@ -1,20 +1,9 @@ -import React, { ReactElement } from 'react'; -import { - Animated, - LayoutChangeEvent, - NativeScrollEvent, - NativeSyntheticEvent, - ScrollView, - ScrollViewProps, - StyleProp, - StyleSheet, - View, - ViewStyle, -} from 'react-native'; +/* eslint-disable react/destructuring-assignment */ +import React from 'react'; +import { Animated, StyleSheet, View } from 'react-native'; import SceneComponent from './SceneComponent'; import constants from '../../constants/constants'; import { getSafelyScrollNode } from '../../utils'; -import type { MountedTabType } from 'react-native-sticky-parallax-header'; const styles = StyleSheet.create({ container: { @@ -22,48 +11,22 @@ const styles = StyleSheet.create({ }, }); -type ReactChildMapType = React.ReactNode; - const { deviceWidth } = constants; -type ScrollableTabViewProps = { - children: React.ReactNode; - contentContainerStyles: StyleProp; - initialPage: number; - page: number; - onChangeTab: (p: MountedTabType) => void; - swipedPage: (index: number) => void; - minScrollHeight: number; - keyboardShouldPersistTaps?: ScrollViewProps['keyboardShouldPersistTaps']; - horizontalScrollBounces?: boolean; - scrollEnabled: boolean; - onScrollXIOS?: (x: number) => void; -}; -type SceneKeys = string[]; -type State = { - currentPage: number; - scrollXIOS: Animated.Value; - containerWidth: number; - sceneKeys: SceneKeys; -}; - -class ScrollableTabView extends React.Component { - scrollOnMountCalled: boolean = false; - - scrollView: ScrollView | null = null; +class ScrollableTabView extends React.Component { + scrollOnMountCalled = false; - constructor(props: ScrollableTabViewProps) { + constructor(props) { super(props); const { initialPage } = this.props; const scrollXIOS = new Animated.Value(initialPage * deviceWidth); const containerWidthAnimatedValue = new Animated.Value(deviceWidth); - const scrollValue = new Animated.Value((initialPage * deviceWidth) / deviceWidth); - - // @ts-ignore + // eslint-disable-next-line no-underscore-dangle containerWidthAnimatedValue.__makeNative(); - scrollValue.setValue(400); + const scrollValue = Animated.divide(scrollXIOS, containerWidthAnimatedValue); + const callListeners = this.polyfillAnimatedValue(scrollValue); this.state = { @@ -73,13 +36,10 @@ class ScrollableTabView extends React.Component { sceneKeys: this.newSceneKeys({ currentPage: initialPage }), }; - scrollXIOS.addListener(({ value }) => { - this.props.onScrollXIOS?.(value); - callListeners(value / deviceWidth); - }); + scrollXIOS.addListener(({ value }) => callListeners(value / deviceWidth)); } - componentDidUpdate(prevProps: Readonly) { + componentDidUpdate(prevProps) { const { children, page } = this.props; const { currentPage } = this.state; @@ -94,7 +54,7 @@ class ScrollableTabView extends React.Component { scrollXIOS.removeAllListeners(); } - onMomentumScrollBeginAndEnd = (e: NativeSyntheticEvent) => { + onMomentumScrollBeginAndEnd = (e) => { const { containerWidth, currentPage } = this.state; const { swipedPage } = this.props; const offsetX = e.nativeEvent.contentOffset.x; @@ -107,9 +67,8 @@ class ScrollableTabView extends React.Component { } }; - onChangeTab(prevPage: number, currentPage: number) { + onChangeTab(prevPage, currentPage) { const { onChangeTab } = this.props; - onChangeTab({ i: currentPage, ref: this.children()[currentPage], @@ -117,19 +76,11 @@ class ScrollableTabView extends React.Component { }); } - updateSelectedPage = (nextPage: number) => { + updateSelectedPage = (nextPage) => { let localNextPage = nextPage; - - /** - * @TODO Check that block of code - */ if (typeof localNextPage === 'object') { - // @ts-ignore - localNextPage = nextPage?.nativeEvent?.position ?? 0; + localNextPage = nextPage.nativeEvent.position; } - /** - * End - */ this.updateSceneKeys({ page: localNextPage, @@ -137,22 +88,22 @@ class ScrollableTabView extends React.Component { }; composeScenes = () => - this.children?.()?.map((child, idx) => { - const key = this.makeSceneKey(child as ReactElement, idx); + this.children().map((child, idx) => { + const key = this.makeSceneKey(child, idx); const { currentPage, containerWidth, sceneKeys } = this.state; const { contentContainerStyles, minScrollHeight } = this.props; - const actualChild = child as ReactElement; return ( @@ -161,104 +112,78 @@ class ScrollableTabView extends React.Component { ); }); - makeSceneKey = (child: React.ReactElement, idx: number): string => - `${child.props.accessibilityLabel}_${idx}`; + makeSceneKey = (child, idx) => `${child.props.tabLabel}_${idx}`; - keyExists = (sceneKeys: string[], key: string) => sceneKeys.find((sceneKey) => key === sceneKey); + keyExists = (sceneKeys, key) => sceneKeys.find((sceneKey) => key === sceneKey); - shouldRenderSceneKey = (idx: number, currentPageKey: number) => + // eslint-disable-next-line max-len + shouldRenderSceneKey = (idx, currentPageKey) => idx < currentPageKey + 1 && idx > currentPageKey - 1; - polyfillAnimatedValue = (animatedValue: Animated.Value) => { - const listeners = new Map(); - const addListener: Animated.Value['addListener'] = (listener) => { - const key = Date.now().toString(10); - - listeners.set(key, listener); - - return key; + polyfillAnimatedValue = (animatedValue) => { + const listeners = new Set(); + const addListener = (listener) => { + listeners.add(listener); }; - const removeListener: Animated.Value['removeListener'] = (listener) => { + const removeListener = (listener) => { listeners.delete(listener); }; - const removeAllListeners: Animated.Value['removeAllListeners'] = () => { + const removeAllListeners = () => { listeners.clear(); }; + /* eslint-disable no-param-reassign */ animatedValue.addListener = addListener; animatedValue.removeListener = removeListener; animatedValue.removeAllListeners = removeAllListeners; + /* eslint-disable no-param-reassign */ - return (value: any) => listeners.forEach((listener) => listener({ value })); + return (value) => listeners.forEach((listener) => listener({ value })); }; - newSceneKeys = ({ - previousKeys = [] as SceneKeys, - currentPage = 0, - children = this.props.children, - }): string[] => { - const newKeys: string[] = []; - - if (children) { - this.children(children).forEach((child, idx) => { - const key = this.makeSceneKey(child as ReactElement, idx); - - if (this.keyExists(previousKeys, key) || this.shouldRenderSceneKey(idx, currentPage)) { - newKeys.push(key); - } - }); - } + newSceneKeys = ({ previousKeys = [], currentPage = 0, children = this.props.children }) => { + const newKeys = []; + this.children(children).forEach((child, idx) => { + const key = this.makeSceneKey(child, idx); + if (this.keyExists(previousKeys, key) || this.shouldRenderSceneKey(idx, currentPage)) { + newKeys.push(key); + } + }); return newKeys; }; - updateSceneKeys = ({ - page, - children = this.props.children, - callback = () => {}, - }: { - children?: ReactChildMapType; - page: number; - callback?: () => void; - }) => { + updateSceneKeys = ({ page, children = this.props.children, callback = () => {} }) => { const { sceneKeys } = this.state; - const newKeys: SceneKeys = this.newSceneKeys({ - previousKeys: sceneKeys, - currentPage: page, - children, - }); - + const newKeys = this.newSceneKeys({ previousKeys: sceneKeys, currentPage: page, children }); this.setState({ currentPage: page, sceneKeys: newKeys }, callback); }; - goToPage = (pageNumber: number) => { + goToPage = (pageNumber) => { const { containerWidth } = this.state; const offset = pageNumber * containerWidth; const scrollNode = getSafelyScrollNode(this.scrollView); - if (scrollNode) { scrollNode.scrollTo({ x: offset, y: 0, animated: true }); } const { currentPage } = this.state; - this.updateSceneKeys({ page: pageNumber, callback: this.onChangeTab.bind(this, currentPage, pageNumber), }); }; - onScroll: ScrollViewProps['onScroll'] = (e) => { + onScroll = (e) => { const offsetX = e.nativeEvent.contentOffset.x; - if (offsetX === 0 && !this.scrollOnMountCalled) { this.scrollOnMountCalled = true; } }; - handleLayout = (e: LayoutChangeEvent) => { + handleLayout = (e) => { const { width } = e.nativeEvent.layout; const { containerWidth, currentPage } = this.state; @@ -272,19 +197,16 @@ class ScrollableTabView extends React.Component { }); }; - children = (children = this.props.children) => { - return React.Children.map(children, (child: ReactChildMapType) => child) ?? []; - }; + children = (children = this.props.children) => React.Children.map(children, (child) => child); renderScrollableContent() { const scenes = this.composeScenes(); - const { initialPage, scrollEnabled, horizontalScrollBounces } = this.props; + const { initialPage, scrollEnabled } = this.props; const { containerWidth, scrollXIOS } = this.state; const { minScrollHeight, keyboardShouldPersistTaps } = this.props; return ( { automaticallyAdjustContentInsets={false} contentOffset={{ x: initialPage * containerWidth }} ref={(scrollView) => { - this.scrollView = getSafelyScrollNode(scrollView); + this.scrollView = scrollView; }} onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollXIOS } } }], { useNativeDriver: true, @@ -318,15 +240,14 @@ class ScrollableTabView extends React.Component { ); } - - static defaultProps = { - contentContainerStyles: {}, - initialPage: 0, - page: -1, - onChangeTab: () => {}, - keyboardShouldPersistTaps: undefined, - children: [], - }; } +ScrollableTabView.defaultProps = { + contentContainerStyles: {}, + initialPage: 0, + page: -1, + onChangeTab: () => {}, + keyboardShouldPersistTaps: undefined, +}; + export default ScrollableTabView; diff --git a/src/components/ScrollableTabView/StaticContainer.tsx b/src/components/ScrollableTabView/StaticContainer.js similarity index 52% rename from src/components/ScrollableTabView/StaticContainer.tsx rename to src/components/ScrollableTabView/StaticContainer.js index 77e1430a..498fa32b 100644 --- a/src/components/ScrollableTabView/StaticContainer.tsx +++ b/src/components/ScrollableTabView/StaticContainer.js @@ -1,14 +1,12 @@ import React from 'react'; +import { bool, node } from 'prop-types'; -type Props = { shouldUpdate: boolean }; - -class StaticContainer extends React.Component { - shouldComponentUpdate = (nextProps: Props) => !!nextProps.shouldUpdate; +class StaticContainer extends React.Component { + shouldComponentUpdate = (nextProps) => !!nextProps.shouldUpdate; render() { const { children } = this.props; const child = children; - if (child === null || child === false) { return null; } @@ -17,4 +15,9 @@ class StaticContainer extends React.Component { } } +StaticContainer.propTypes = { + children: node, + shouldUpdate: bool, +}; + export default StaticContainer; diff --git a/src/components/index.ts b/src/components/index.js similarity index 100% rename from src/components/index.ts rename to src/components/index.js diff --git a/src/constants/colors.ts b/src/constants/colors.js similarity index 89% rename from src/constants/colors.ts rename to src/constants/colors.js index cb92143c..f541f822 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.js @@ -13,5 +13,4 @@ export default { greyishBrown: 'rgb(71,71,71)', coralPink: 'rgb(255,94,107)', jade: 'rgb(29,167,93)', - whiteTransparent10: 'rgba(255, 255, 255, 0.1)', }; diff --git a/src/constants/constants.ts b/src/constants/constants.js similarity index 79% rename from src/constants/constants.ts rename to src/constants/constants.js index a881e8a7..5b711079 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.js @@ -7,8 +7,8 @@ const scale = width / 320; const deviceWidth = width; const deviceHeight = height; -const responsiveHeight = (h: number): number => height * (h / 100); -const responsiveWidth = (w: number): number => width * (w / 100); +const responsiveHeight = (h) => height * (h / 100); +const responsiveWidth = (w) => width * (w / 100); const breakpoints = { smallPhoneWidth: 320, smallPhoneHeight: 600, @@ -22,9 +22,8 @@ const isBigScreen = width >= breakpoints.mediumPhoneWidth; const isBiggestPhoneScreen = width >= breakpoints.bigPhoneWidth; const isAndroid = Platform.OS === 'android'; -const getResponsiveFontSize = (fontSize: number): number => { +const getResponsiveFontSize = (fontSize) => { const newSize = fontSize * scale; - if (Platform.OS === 'ios') { return Math.round(PixelRatio.roundToNearestPixel(newSize)); } @@ -32,7 +31,7 @@ const getResponsiveFontSize = (fontSize: number): number => { return Math.round(PixelRatio.roundToNearestPixel(newSize)) - 2; }; -const normalizedFontSize = (basicFontSize: number): number => { +const normalizedFontSize = (basicFontSize) => { if (isSmallScreen) { return basicFontSize - 6; } @@ -46,7 +45,7 @@ const normalizedFontSize = (basicFontSize: number): number => { return basicFontSize; }; -const scrollPosition = (scrollHeight: number, x: number): number => x * 0.01 * scrollHeight; +const scrollPosition = (scrollHeight, x) => x * 0.01 * scrollHeight; export default { getResponsiveFontSize, diff --git a/src/constants/index.ts b/src/constants/index.js similarity index 100% rename from src/constants/index.ts rename to src/constants/index.js diff --git a/src/constants/screenStyles.ts b/src/constants/screenStyles.js similarity index 92% rename from src/constants/screenStyles.ts rename to src/constants/screenStyles.js index 00b16d8f..5964fc42 100644 --- a/src/constants/screenStyles.ts +++ b/src/constants/screenStyles.js @@ -1,4 +1,5 @@ import { StyleSheet, Platform } from 'react-native'; +import { getStatusBarHeight } from 'react-native-iphone-x-helper'; import { ifIphoneX } from '../utils'; import colors from './colors'; import constants from './constants'; @@ -38,8 +39,9 @@ const screenStyles = StyleSheet.create({ }, headerWrapper: { width: '100%', + flex: 1, paddingHorizontal: 24, - paddingTop: Platform.OS === 'ios' ? ifIphoneX(50, 35) : 18, + marginTop: getStatusBarHeight(true), flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', diff --git a/src/constants/sizes.ts b/src/constants/sizes.js similarity index 100% rename from src/constants/sizes.ts rename to src/constants/sizes.js diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 00000000..54b61b9a --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,135 @@ +import { ReactElement, Component } from 'react'; +import { + ImageSourcePropType, + ScrollView, + NativeScrollEvent, + NativeSyntheticEvent, + TextStyle, + ViewStyle, + ImageResizeMode, +} from 'react-native'; + +export interface HeaderTypeProp { + headerType?: 'TabbedHeader' | 'DetailsHeader' | 'AvatarHeader'; + keyboardShouldPersistTaps?: 'always' | 'never' | 'handled' | false | true; + scrollRef?: (ref: ScrollView) => void | object; +} + +export interface HeaderSizeProps { + height: number; + width: number; + x: number; + y: number; +} + +export interface OnChangeTabArguments { + from: number; + i: number; + ref: any; +} + +export interface Tab { + content: ReactElement; + title?: string; + icon?: ReactElement | ((isActive: boolean) => ReactElement); +} +export interface SharedProps { + backgroundImage?: ImageSourcePropType; + headerHeight?: number; + snapToEdge?: boolean; + bounces?: boolean; + contentContainerStyles?: ViewStyle; + refreshControl?: ReactElement; + decelerationRate: number | string; + children?: ReactElement; + parallaxHeight?: number; + foreground?: () => ReactElement; + headerSize?: (headerSizeProps: HeaderSizeProps) => void; +} + +export interface IconProps { + leftTopIcon?: ImageSourcePropType; + leftTopIconOnPress?: () => void; + rightTopIcon?: ImageSourcePropType; + rightTopIconOnPress?: () => void; +} + +export interface TabsSharedProps { + tabTextActiveStyle?: TextStyle; + tabTextContainerActiveStyle?: ViewStyle; + tabTextContainerStyle?: ViewStyle; + tabTextStyle?: TextStyle; + tabWrapperStyle?: ViewStyle; + tabs?: Tab[]; + tabsContainerStyle?: ViewStyle; +} + +export type TabbedHeaderProps = HeaderTypeProp & + SharedProps & + TabsSharedProps & { + headerType: 'TabbedHeader'; + backgroundColor?: string; + foregroundImage?: ImageSourcePropType; + header?: () => ReactElement; + logo?: ImageSourcePropType; + logoContainerStyle?: ViewStyle; + logoResizeMode?: ImageResizeMode; + logoStyle?: ViewStyle; + rememberTabScrollPosition?: boolean; + renderBody?: (title: string) => ReactElement; + scrollEvent?: (event: NativeSyntheticEvent) => void; + title?: string; + titleStyle?: TextStyle; + }; + +export type DetailsHeaderProps = SharedProps & + IconProps & { + headerType: 'DetailsHeader'; + backgroundColor?: string; + hasBorderRadius?: boolean; + iconNumber?: number; + image?: number; + renderBody?: (title: string) => ReactElement; + tag?: string; + title?: string; + transparentHeader?: boolean; + }; + +export type AvatarHeaderProps = SharedProps & + IconProps & { + headerType: 'AvatarHeader'; + backgroundColor?: string; + hasBorderRadius?: boolean; + header?: () => ReactElement; + image?: number; + renderBody?: (title: string) => ReactElement; + scrollEvent?: (event: NativeSyntheticEvent) => void; + snapStartThreshold?: number; + snapStopThreshold?: number; + snapValue?: number; + subtitle?: string; + title?: string; + transparentHeader?: boolean; + }; + +export type CustomHeaderProps = SharedProps & + TabsSharedProps & { + headerType: undefined; + background: ReactElement; + backgroundColor: string; + children?: ReactElement; + header: ReactElement; + initialPage?: number; + onChangeTab?: (changeTabArguments: OnChangeTabArguments) => void; + onEndReached?: () => void; + scrollEvent?: (event: NativeSyntheticEvent) => void; + snapStartThreshold?: number; + snapStopThreshold?: number; + snapValue?: number; + tabsContainerBackgroundColor?: string; + }; + +type StickyParallaxHeaderProps = HeaderTypeProp & + (DetailsHeaderProps | AvatarHeaderProps | TabbedHeaderProps | CustomHeaderProps); + +export default class StickyParallaxHeader extends Component {} diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..058099f9 --- /dev/null +++ b/src/index.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { AvatarHeader, TabbedHeader, DetailsHeader } from './predefinedComponents'; +import StickyParallaxHeader from './StickyParallaxHeader'; + +const index = React.forwardRef((props, ref) => { + // eslint-disable-next-line react/prop-types + switch (props.headerType) { + case 'TabbedHeader': + return ; + case 'AvatarHeader': + return ; + case 'DetailsHeader': + return ; + default: + return ; + } +}); + +export default index; diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100644 index ceb6430a..00000000 --- a/src/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { ReactElement, ReactNode, VFC } from 'react'; -import type { ImageSourcePropType } from 'react-native'; -import { AvatarHeader, TabbedHeader, DetailsHeader } from './predefinedComponents'; -import { - default as StickyParallaxHeaderComponent, - StickyParallaxHeaderProps, -} from './StickyParallaxHeaderComponent'; -import type { - AvatarHeaderProps, - TabbedHeaderProps, - DetailsHeaderProps, -} from './predefinedComponents'; - -export type { AvatarHeaderProps }; -export type { TabbedHeaderProps }; -export type { DetailsHeaderProps }; -export type { StickyParallaxHeaderProps }; - -export interface OnChangeTabArguments { - from: number; - i: number; - ref: any; -} - -export type MountedTabType = { - i: number; - ref: React.ReactElement | React.ReactFragment | null | undefined; - from: number; -}; - -export interface Tab { - content: ReactElement; - title?: string; - icon?: ReactElement | ((isActive: boolean) => ReactElement); -} - -export interface SharedPredefinedHeaderProps { - backgroundColor?: string; - backgroundImage?: StickyParallaxHeaderProps['backgroundImage']; - bounces?: StickyParallaxHeaderProps['bounces']; - horizontalScrollBounces?: StickyParallaxHeaderProps['horizontalScrollBounces']; - contentContainerStyles?: StickyParallaxHeaderProps['contentContainerStyles']; - headerHeight?: StickyParallaxHeaderProps['headerHeight']; - headerSize?: StickyParallaxHeaderProps['headerSize']; - keyboardShouldPersistTaps?: StickyParallaxHeaderProps['keyboardShouldPersistTaps']; - onMomentumScrollBegin?: StickyParallaxHeaderProps['onMomentumScrollBegin']; - onMomentumScrollEnd?: StickyParallaxHeaderProps['onMomentumScrollEnd']; - parallaxHeight?: StickyParallaxHeaderProps['parallaxHeight']; - refreshControl?: StickyParallaxHeaderProps['refreshControl']; - scrollEvent?: StickyParallaxHeaderProps['scrollEvent']; - scrollRef?: StickyParallaxHeaderProps['scrollRef']; - snapStartThreshold?: StickyParallaxHeaderProps['snapStartThreshold']; - snapStopThreshold?: StickyParallaxHeaderProps['snapStopThreshold']; - snapToEdge?: StickyParallaxHeaderProps['snapToEdge']; - snapValue?: StickyParallaxHeaderProps['snapValue']; -} - -export interface IconProps { - leftTopIcon?: (() => ReactElement) | ImageSourcePropType; - leftTopIconOnPress?: () => void; - rightTopIcon?: (() => ReactElement) | ImageSourcePropType; - rightTopIconOnPress?: () => void; -} -export interface RenderBody { - children?: ReactNode; -} - -type Props = TabbedHeaderProps | AvatarHeaderProps | DetailsHeaderProps | StickyParallaxHeaderProps; - -const StickyParallaxHeader: VFC = (props: Props) => { - switch (props.headerType) { - case 'TabbedHeader': - return ; - case 'AvatarHeader': - return ; - case 'DetailsHeader': - return ; - default: - return ; - } -}; - -export default StickyParallaxHeader; diff --git a/src/predefinedComponents/AvatarHeader/AvatarHeader.tsx b/src/predefinedComponents/AvatarHeader/AvatarHeader.js similarity index 73% rename from src/predefinedComponents/AvatarHeader/AvatarHeader.tsx rename to src/predefinedComponents/AvatarHeader/AvatarHeader.js index 30cedb2f..27792989 100644 --- a/src/predefinedComponents/AvatarHeader/AvatarHeader.tsx +++ b/src/predefinedComponents/AvatarHeader/AvatarHeader.js @@ -1,46 +1,26 @@ -import React, { ReactElement, ReactNode } from 'react'; +import React from 'react'; import { Text, View, + Image, TouchableOpacity, Animated, StatusBar, - LayoutRectangle, - ImageSourcePropType, - NativeSyntheticEvent, - NativeScrollEvent, + ScrollView, } from 'react-native'; -import StickyParallaxHeader, { - StickyParallaxHeaderProps, -} from '../../StickyParallaxHeaderComponent'; +import StickyParallaxHeader from '../../StickyParallaxHeader'; import { constants, sizes } from '../../constants'; import styles from './AvatarHeader.styles'; -import type { IconProps, RenderBody, SharedPredefinedHeaderProps } from '../../index'; -import IconRenderer from '../../components/IconRenderer/IconRenderer'; +import { Brandon } from '../../assets/data/cards'; +import RenderContent from './defaultProps/defaultProps'; const { event, ValueXY } = Animated; -export interface AvatarHeaderProps extends IconProps, SharedPredefinedHeaderProps, RenderBody { - headerType: 'AvatarHeader'; - hasBorderRadius?: boolean; - image: ImageSourcePropType; - subtitle?: string; - title?: string; -} - -type State = { - headerLayout: { - height: number; - }; -}; - const finishImgPosition = 31; const startImgPosition = 27; -class AvatarHeader extends React.Component { - scrollY: Animated.ValueXY; - - constructor(props: AvatarHeaderProps) { +class AvatarHeader extends React.Component { + constructor(props) { super(props); this.state = { @@ -51,14 +31,13 @@ class AvatarHeader extends React.Component { this.scrollY = new ValueXY(); } - setHeaderSize = (headerLayout: LayoutRectangle) => { + setHeaderSize = (headerLayout) => { const { headerSize } = this.props; - if (headerSize) headerSize(headerLayout); this.setState({ headerLayout }); }; - scrollPosition(value: number): number { + scrollPosition(value) { const { headerLayout: { height }, } = this.state; @@ -66,7 +45,7 @@ class AvatarHeader extends React.Component { return constants.scrollPosition(height, value); } - renderAvatarHeader = (): StickyParallaxHeaderProps['header'] => { + renderAvatarHeader = () => { const { leftTopIconOnPress, leftTopIcon, @@ -100,13 +79,13 @@ class AvatarHeader extends React.Component { }); return ( - + - + @@ -118,14 +97,21 @@ class AvatarHeader extends React.Component { hitSlop={sizes.hitSlop} onPress={rightTopIconOnPress} style={styles.rightHeaderButton}> - + ); }; - renderAvatarForeground = (): ReactNode => { + renderHeader = () => { + const { header } = this.props; + const renderHeader = header || this.renderAvatarHeader; + + return renderHeader(); + }; + + renderAvatarForeground = () => { const { image, subtitle, title } = this.props; const startSize = constants.responsiveWidth(18); const endSize = constants.responsiveWidth(12); @@ -184,7 +170,14 @@ class AvatarHeader extends React.Component { ); }; - renderBackground = (): ReactElement => { + renderForeground = () => { + const { foreground } = this.props; + const renderForeground = foreground || this.renderAvatarForeground; + + return renderForeground(); + }; + + renderBackground = () => { const { headerLayout: { height }, } = this.state; @@ -217,6 +210,7 @@ class AvatarHeader extends React.Component { backgroundImage, contentContainerStyles, children, + renderBody, headerHeight, snapToEdge, bounces, @@ -225,55 +219,77 @@ class AvatarHeader extends React.Component { snapStartThreshold, snapStopThreshold, snapValue, + transparentHeader, scrollRef, keyboardShouldPersistTaps, refreshControl, onMomentumScrollEnd, onMomentumScrollBegin, - horizontalScrollBounces, } = this.props; + if (renderBody) { + console.warn('Warning: renderBody prop is deprecated. Please use children instead'); + } + return ( <> ) => scrollEvent && scrollEvent(e), + listener: (e) => scrollEvent && scrollEvent(e), })} + headerSize={this.setHeaderSize} + headerHeight={headerHeight} background={this.renderBackground()} backgroundImage={backgroundImage} bounces={bounces} - contentContainerStyles={contentContainerStyles} - foreground={this.renderAvatarForeground()} - header={this.renderAvatarHeader()} - headerHeight={headerHeight} - headerSize={this.setHeaderSize} - keyboardShouldPersistTaps={keyboardShouldPersistTaps} - onMomentumScrollBegin={onMomentumScrollBegin} - onMomentumScrollEnd={onMomentumScrollEnd} + snapToEdge={snapToEdge} parallaxHeight={parallaxHeight} - refreshControl={refreshControl} - scrollRef={scrollRef} + transparentHeader={transparentHeader} snapStartThreshold={snapStartThreshold} snapStopThreshold={snapStopThreshold} - snapToEdge={snapToEdge} snapValue={snapValue} - horizontalScrollBounces={horizontalScrollBounces} - transparentHeader={false}> - {children} + scrollRef={scrollRef} + keyboardShouldPersistTaps={keyboardShouldPersistTaps} + refreshControl={refreshControl} + onMomentumScrollEnd={onMomentumScrollEnd} + onMomentumScrollBegin={onMomentumScrollBegin}> + {renderBody ? renderBody() : children} ); } - - static defaultProps = { - headerHeight: sizes.userModalHeaderHeight, - bounces: true, - snapToEdge: true, - hasBorderRadius: true, - parallaxHeight: sizes.userScreenParallaxHeader, - }; } +AvatarHeader.defaultProps = { + leftTopIconOnPress: () => {}, + rightTopIconOnPress: () => {}, + leftTopIcon: require('../../assets/icons/iconCloseWhite.png'), + rightTopIcon: require('../../assets/icons/Icon-Menu.png'), + backgroundColor: Brandon.color, + headerHeight: sizes.userModalHeaderHeight, + backgroundImage: null, + contentContainerStyles: {}, + title: Brandon.author, + subtitle: Brandon.about, + image: Brandon.image, + children: , + bounces: true, + snapToEdge: true, + hasBorderRadius: true, + parallaxHeight: sizes.userScreenParallaxHeader, + transparentHeader: false, + scrollRef: null, + keyboardShouldPersistTaps: undefined, + refreshControl: undefined, + headerSize: undefined, + onMomentumScrollEnd: undefined, + onMomentumScrollBegin: undefined, +}; + export default AvatarHeader; diff --git a/src/predefinedComponents/AvatarHeader/AvatarHeader.styles.ts b/src/predefinedComponents/AvatarHeader/AvatarHeader.styles.js similarity index 88% rename from src/predefinedComponents/AvatarHeader/AvatarHeader.styles.ts rename to src/predefinedComponents/AvatarHeader/AvatarHeader.styles.js index a725815a..be7fc265 100644 --- a/src/predefinedComponents/AvatarHeader/AvatarHeader.styles.ts +++ b/src/predefinedComponents/AvatarHeader/AvatarHeader.styles.js @@ -1,8 +1,12 @@ -import { StyleSheet } from 'react-native'; +import { StyleSheet, Platform } from 'react-native'; +import { ifIphoneX } from '../../utils'; import { colors, constants, screenStyles } from '../../constants'; export default StyleSheet.create({ ...screenStyles, + userModalHeader: { + paddingTop: Platform.OS === 'ios' ? ifIphoneX(50, 30) : 18, + }, headerPic: { width: 32, height: 32, diff --git a/src/predefinedComponents/AvatarHeader/defaultProps/defaultProps.js b/src/predefinedComponents/AvatarHeader/defaultProps/defaultProps.js new file mode 100644 index 00000000..b3ca5d14 --- /dev/null +++ b/src/predefinedComponents/AvatarHeader/defaultProps/defaultProps.js @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import { View, Text, Platform } from 'react-native'; +import { QuizListElement } from '../../components'; +import { sizes, constants } from '../../../constants'; +import styles from '../../TabbedHeader/TabbedHeader.styles'; +import { Brandon } from '../../../assets/data/cards'; + +const RenderContent = ({ user }) => { + const [contentHeight, setContentHeight] = useState(''); + const title = "Author's Quizes"; + const cards = [ + { + id: '4850294857', + elements: user.cardsAmount, + authorName: user.author, + mainText: user.label, + labelText: user.type, + imageSource: user.image, + }, + ]; + + const calcMargin = () => { + let marginBottom = 0; + + if (contentHeight) { + const padding = 24; + const isBigContent = constants.deviceHeight - contentHeight < 0; + + if (isBigContent) { + return marginBottom; + } + + marginBottom = constants.deviceHeight - padding - sizes.headerHeight - contentHeight; + + return marginBottom; + } + + return marginBottom; + }; + + const onLayoutContent = (e) => { + setContentHeight(e.nativeEvent.layout.height); + }; + + const marginContentBottom = Platform.select({ ios: calcMargin(), android: 0 }); + + return ( + + {title} + {cards.map((card) => ( + + ))} + + ); +}; + +RenderContent.defaultProps = { + user: Brandon, +}; + +export default RenderContent; diff --git a/src/predefinedComponents/DetailsHeader/DetailsHeader.js b/src/predefinedComponents/DetailsHeader/DetailsHeader.js new file mode 100644 index 00000000..98362ea5 --- /dev/null +++ b/src/predefinedComponents/DetailsHeader/DetailsHeader.js @@ -0,0 +1,256 @@ +import React from 'react'; +import { Text, View, Image, TouchableOpacity, StatusBar, Animated } from 'react-native'; +import StickyParallaxHeader from '../../StickyParallaxHeader'; +import { constants, sizes } from '../../constants'; +import { Brandon } from '../../assets/data/cards'; +import styles from './DetailsHeader.styles'; +import { renderContent } from './defaultProps/defaultProps'; + +const { event, ValueXY } = Animated; +class DetailsHeader extends React.Component { + constructor(props) { + super(props); + this.state = { + headerLayout: { + height: 0, + }, + }; + + this.scrollY = new ValueXY(); + } + + setHeaderSize = (headerLayout) => { + const { headerSize } = this.props; + if (headerSize) headerSize(headerLayout); + this.setState({ headerLayout }); + }; + + scrollPosition = (value) => { + const { headerLayout } = this.state; + + return constants.scrollPosition(headerLayout.height, value); + }; + + renderHeader = (user) => { + const { + backgroundColor, + leftTopIconOnPress, + rightTopIconOnPress, + leftTopIcon, + rightTopIcon, + search, + left, + right + } = this.props; + + // const opacity = this.scrollY.y.interpolate({ + // inputRange: [0, this.scrollPosition(60), this.scrollPosition(90)], + // outputRange: [0, 0, 1], + // extrapolate: 'clamp', + // }); + + return ( + + + {/* + + */} + {left} + {/* + {user.label} + {search && search} + */} + {right} + {/* + + */} + + + ); + }; + + renderDetailsForeground = (user) => () => { + const { labelColor } = user; + const { image, title, tag, iconNumber } = this.props; + const labelOpacity = this.scrollY.y.interpolate({ + inputRange: [0, this.scrollPosition(19), this.scrollPosition(25)], + outputRange: [1, 0.8, 0], + extrapolate: 'clamp', + }); + const titleOpacity = this.scrollY.y.interpolate({ + inputRange: [0, this.scrollPosition(45), this.scrollPosition(55)], + outputRange: [1, 0.8, 0], + extrapolate: 'clamp', + }); + const authorOpacity = this.scrollY.y.interpolate({ + inputRange: [0, this.scrollPosition(55), this.scrollPosition(70)], + outputRange: [1, 0.8, 0], + extrapolate: 'clamp', + }); + + return ( + + + {tag} + + + + {title} + + + + + + {iconNumber} + + + + + {user.author} + + + + + ); + }; + + renderForeground = (user) => { + const { foreground } = this.props; + const renderForeground = foreground || this.renderDetailsForeground(user); + + return renderForeground(user); + }; + + renderBackground = () => { + const { hasBorderRadius, backgroundColor } = this.props; + const { + headerLayout: { height }, + } = this.state; + const headerBorderRadius = + hasBorderRadius && + this.scrollY.y.interpolate({ + inputRange: [0, height], + outputRange: [80, 0], + extrapolate: 'extend', + }); + + return ( + + ); + }; + + render() { + const user = Brandon; + const { + backgroundColor, + backgroundImage, + renderBody, + children, + headerHeight, + snapToEdge, + bounces, + parallaxHeight, + transparentHeader, + onMomentumScrollEnd, + onMomentumScrollBegin, + scrollRef, + search, + tabs, + tabTextStyle, + tabTextActiveStyle, + tabTextContainerStyle, + tabTextContainerActiveStyle, + tabWrapperStyle, + tabsContainerStyle, + tabBackgroundColor, + tabBarUnderlineStyle, + backgroundHeight + } = this.props; + + if (renderBody) { + console.warn('Warning: renderBody prop is deprecated. Please use children instead'); + } + + return ( + <> + + + {renderBody ? renderBody() : children} + + + ); + } +} + +DetailsHeader.defaultProps = { + leftTopIconOnPress: () => {}, + rightTopIconOnPress: () => {}, + leftTopIcon: require('../../assets/icons/iconCloseWhite.png'), + rightTopIcon: require('../../assets/icons/Icon-Menu.png'), + backgroundColor: Brandon.color, + headerHeight: sizes.cardScreenHeaderHeight, + backgroundImage: null, + tag: Brandon.type, + title: Brandon.label, + image: Brandon.image, + children: renderContent(Brandon), + bounces: true, + snapToEdge: true, + hasBorderRadius: true, + iconNumber: Brandon.cardsAmount, + parallaxHeight: sizes.cardScreenParallaxHeader, + transparentHeader: false, + headerSize: undefined, + scrollRef: null, + onMomentumScrollEnd: undefined, + onMomentumScrollBegin: undefined, + search: null, + tabs: [], + tabTextStyle: styles.tabText, + tabTextActiveStyle: styles.tabText, + tabTextContainerStyle: styles.tabTextContainerStyle, + tabTextContainerActiveStyle: styles.tabTextContainerActiveStyle, + tabWrapperStyle: styles.tabsWrapper, + tabBackgroundColor: Brandon.color, + tabBarUnderlineStyle: {}, + backgroundHeight: 300 +}; + +export default DetailsHeader; diff --git a/src/predefinedComponents/DetailsHeader/DetailsHeader.styles.ts b/src/predefinedComponents/DetailsHeader/DetailsHeader.styles.js similarity index 100% rename from src/predefinedComponents/DetailsHeader/DetailsHeader.styles.ts rename to src/predefinedComponents/DetailsHeader/DetailsHeader.styles.js diff --git a/src/predefinedComponents/DetailsHeader/DetailsHeader.tsx b/src/predefinedComponents/DetailsHeader/DetailsHeader.tsx deleted file mode 100644 index 9ef82d4d..00000000 --- a/src/predefinedComponents/DetailsHeader/DetailsHeader.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import React, { ReactElement } from 'react'; -import { - Text, - View, - Image, - TouchableOpacity, - StatusBar, - Animated, - ImageSourcePropType, -} from 'react-native'; -import StickyParallaxHeader, { - StickyParallaxHeaderProps, -} from '../../StickyParallaxHeaderComponent'; -import { constants, sizes, colors } from '../../constants'; -import styles from './DetailsHeader.styles'; -import type { IconProps, RenderBody, SharedPredefinedHeaderProps } from '../../index'; -import IconRenderer from '../../components/IconRenderer/IconRenderer'; - -const { event, ValueXY } = Animated; - -export interface DetailsHeaderProps extends IconProps, SharedPredefinedHeaderProps, RenderBody { - headerType: 'DetailsHeader'; - hasBorderRadius?: boolean; - image?: ImageSourcePropType; - contentIcon?: ImageSourcePropType; - contentIconNumber?: number; - tag?: string; - title?: string; -} -type State = { - headerLayout: { - height: number; - }; -}; - -class DetailsHeader extends React.Component { - scrollY: Animated.ValueXY; - - constructor(props: DetailsHeaderProps) { - super(props); - this.state = { - headerLayout: { - height: 0, - }, - }; - - this.scrollY = new ValueXY(); - } - - setHeaderSize: StickyParallaxHeaderProps['headerSize'] = (headerLayout) => { - const { headerSize } = this.props; - - if (headerSize) headerSize(headerLayout); - this.setState({ headerLayout }); - }; - - scrollPosition = (value: number): number => { - const { headerLayout } = this.state; - - return constants.scrollPosition(headerLayout.height, value); - }; - - renderHeader = (): ReactElement => { - const { - backgroundColor, - leftTopIconOnPress, - rightTopIconOnPress, - leftTopIcon, - rightTopIcon, - title, - } = this.props; - - const opacity = this.scrollY.y.interpolate({ - inputRange: [0, this.scrollPosition(60), this.scrollPosition(90)], - outputRange: [0, 0, 1], - extrapolate: 'clamp', - }); - - return ( - - - - - - - {title} - - - - - - - ); - }; - - renderDetailsForeground = () => { - const { image, title, tag, contentIconNumber, contentIcon } = this.props; - const labelOpacity = this.scrollY.y.interpolate({ - inputRange: [0, this.scrollPosition(19), this.scrollPosition(25)], - outputRange: [1, 0.8, 0], - extrapolate: 'clamp', - }); - const titleOpacity = this.scrollY.y.interpolate({ - inputRange: [0, this.scrollPosition(45), this.scrollPosition(55)], - outputRange: [1, 0.8, 0], - extrapolate: 'clamp', - }); - const authorOpacity = this.scrollY.y.interpolate({ - inputRange: [0, this.scrollPosition(55), this.scrollPosition(70)], - outputRange: [1, 0.8, 0], - extrapolate: 'clamp', - }); - - return ( - - - {tag} - - - - {title} - - - - - {contentIcon && } - {contentIconNumber} - - - {image && } - - {title} - - - - - ); - }; - - renderBackground = () => { - const { hasBorderRadius, backgroundColor } = this.props; - const { - headerLayout: { height }, - } = this.state; - const headerBorderRadius = - hasBorderRadius && - this.scrollY.y.interpolate({ - inputRange: [0, height], - outputRange: [80, 0], - extrapolate: 'extend', - }); - - return ( - - ); - }; - - render() { - const { - backgroundColor, - backgroundImage, - children, - headerHeight, - snapToEdge, - bounces, - parallaxHeight, - onMomentumScrollEnd, - onMomentumScrollBegin, - scrollRef, - contentContainerStyles, - keyboardShouldPersistTaps, - refreshControl, - horizontalScrollBounces, - } = this.props; - - return ( - <> - - - {children} - - - ); - } - - static defaultProps = { - headerHeight: sizes.cardScreenHeaderHeight, - bounces: true, - snapToEdge: true, - hasBorderRadius: true, - parallaxHeight: sizes.cardScreenParallaxHeader, - headerSize: undefined, - scrollRef: null, - }; -} - -export default DetailsHeader; diff --git a/src/predefinedComponents/DetailsHeader/defaultProps/defaultProps.js b/src/predefinedComponents/DetailsHeader/defaultProps/defaultProps.js new file mode 100644 index 00000000..e899973c --- /dev/null +++ b/src/predefinedComponents/DetailsHeader/defaultProps/defaultProps.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { View } from 'react-native'; +import { QuizCard } from '../../components'; +import styles from '../DetailsHeader.styles'; + +const renderContent = (user) => ( + + {user.cards.map((data, i, arr) => ( + + ))} + +); + +export { renderContent }; diff --git a/src/predefinedComponents/TabbedHeader/TabbedHeader.js b/src/predefinedComponents/TabbedHeader/TabbedHeader.js new file mode 100644 index 00000000..abaf4c8b --- /dev/null +++ b/src/predefinedComponents/TabbedHeader/TabbedHeader.js @@ -0,0 +1,271 @@ +import React from 'react'; +import { Text, View, Image, StatusBar, Animated } from 'react-native'; + +import StickyParallaxHeader from '../../StickyParallaxHeader'; +import { constants, colors, sizes } from '../../constants'; +import styles from './TabbedHeader.styles'; +import RenderContent from './defaultProps/defaultProps'; + +const { event, ValueXY } = Animated; +export default class TabbedHeader extends React.Component { + constructor(props) { + super(props); + + this.state = { + contentHeight: {}, + headerLayout: { + height: 0, + }, + }; + this.scrollY = new ValueXY(); + } + + componentDidMount() { + // eslint-disable-next-line + this.scrollY.y.addListener(({ value }) => (this._value = value)); + } + + componentWillUnmount() { + this.scrollY.y.removeListener(); + } + + setHeaderSize = (headerLayout) => { + const { headerSize } = this.props; + if (headerSize) headerSize(headerLayout); + this.setState({ headerLayout }); + }; + + scrollPosition = (value) => { + const { headerLayout } = this.state; + + return constants.scrollPosition(headerLayout.height, value); + }; + + renderLogoHeader = () => { + const { backgroundColor, logo, logoResizeMode, logoStyle, logoContainerStyle } = this.props; + + return ( + + + + ); + }; + + renderHeader = () => { + const { header } = this.props; + const renderHeader = header || this.renderLogoHeader; + + return renderHeader(); + }; + + renderTabbedForeground = (scrollY) => () => { + const { title, titleStyle, foregroundImage } = this.props; + const messageStyle = titleStyle || styles.message; + const startSize = constants.responsiveWidth(18); + const endSize = constants.responsiveWidth(10); + const [startImgFade, finishImgFade] = [this.scrollPosition(22), this.scrollPosition(27)]; + const [startImgSize, finishImgSize] = [this.scrollPosition(20), this.scrollPosition(30)]; + const [startTitleFade, finishTitleFade] = [this.scrollPosition(25), this.scrollPosition(45)]; + + const imageOpacity = scrollY.y.interpolate({ + inputRange: [0, startImgFade, finishImgFade], + outputRange: [1, 1, 0], + extrapolate: 'clamp', + }); + const imageSize = scrollY.y.interpolate({ + inputRange: [0, startImgSize, finishImgSize], + outputRange: [startSize, startSize, endSize], + extrapolate: 'clamp', + }); + const titleOpacity = scrollY.y.interpolate({ + inputRange: [0, startTitleFade, finishTitleFade], + outputRange: [1, 1, 0], + extrapolate: 'clamp', + }); + + const renderImage = () => { + const logo = + foregroundImage === undefined + ? require('../../assets/images/photosPortraitMe.png') + : foregroundImage; + + if (foregroundImage !== null) { + return ( + + + + ); + } + + return null; + }; + + return ( + + {renderImage()} + + {title} + + + ); + }; + + renderForeground = (scrollY) => { + const { foreground } = this.props; + const renderForeground = foreground || this.renderTabbedForeground(scrollY); + + return renderForeground(); + }; + + onLayoutContent = (e, title) => { + const { contentHeight } = this.state; + const contentHeightTmp = { ...contentHeight }; + contentHeightTmp[title] = e.nativeEvent.layout.height; + + this.setState({ + contentHeight: { ...contentHeightTmp }, + }); + }; + + calcMargin = (title) => { + const { contentHeight } = this.state; + let marginBottom = 50; + + if (contentHeight[title]) { + const padding = 24; + const isBigContent = constants.deviceHeight - contentHeight[title] < 0; + + if (isBigContent) { + return marginBottom; + } + + marginBottom = + constants.deviceHeight - padding * 2 - sizes.headerHeight - contentHeight[title]; + + return marginBottom; + } + + return marginBottom; + }; + + render() { + const { + tabs, + headerHeight, + backgroundColor, + backgroundImage, + bounces, + snapToEdge, + scrollEvent, + renderBody, + children, + tabTextStyle, + tabTextActiveStyle, + tabTextContainerStyle, + tabTextContainerActiveStyle, + tabWrapperStyle, + tabsContainerStyle, + onRef, + keyboardShouldPersistTaps, + scrollRef, + contentContainerStyles, + refreshControl, + rememberTabScrollPosition, + parallaxHeight, + onMomentumScrollEnd, + onMomentumScrollBegin, + } = this.props; + + if (renderBody) { + console.warn('Warning: renderBody prop is deprecated. Please use children instead'); + } + + return ( + <> + + scrollEvent && scrollEvent(e), + })} + headerSize={this.setHeaderSize} + headerHeight={headerHeight} + tabs={tabs} + tabTextStyle={tabTextStyle} + tabTextActiveStyle={tabTextActiveStyle} + tabTextContainerStyle={tabTextContainerStyle} + tabTextContainerActiveStyle={tabTextContainerActiveStyle} + tabsContainerBackgroundColor={backgroundColor} + tabWrapperStyle={tabWrapperStyle} + backgroundImage={backgroundImage} + bounces={bounces} + snapToEdge={snapToEdge} + tabsContainerStyle={tabsContainerStyle} + onRef={onRef} + onMomentumScrollEnd={onMomentumScrollEnd} + onMomentumScrollBegin={onMomentumScrollBegin}> + {renderBody ? renderBody() : children} + + + ); + } +} + +TabbedHeader.defaultProps = { + backgroundColor: colors.primaryGreen, + headerHeight: sizes.headerHeight, + backgroundImage: null, + title: "Mornin' Mark! \nReady for a quiz?", + bounces: true, + snapToEdge: true, + logo: require('../../assets/images/logo.png'), + logoResizeMode: 'contain', + logoStyle: styles.logo, + logoContainerStyle: styles.headerWrapper, + children: , + tabs: [ + { + title: 'Popular', + content: , + }, + { + title: 'Product Design', + content: , + }, + { + title: 'Development', + content: , + }, + { + title: 'Project Management', + content: , + }, + ], + tabTextStyle: styles.tabText, + tabTextActiveStyle: styles.tabText, + tabTextContainerStyle: styles.tabTextContainerStyle, + tabTextContainerActiveStyle: styles.tabTextContainerActiveStyle, + tabWrapperStyle: styles.tabsWrapper, + contentContainerStyles: {}, + rememberTabScrollPosition: false, + keyboardShouldPersistTaps: undefined, + refreshControl: undefined, + scrollRef: null, + onRef: null, + parallaxHeight: sizes.homeScreenParallaxHeader, + headerSize: undefined, + onMomentumScrollEnd: undefined, + onMomentumScrollBegin: undefined, +}; diff --git a/src/predefinedComponents/TabbedHeader/TabbedHeader.styles.ts b/src/predefinedComponents/TabbedHeader/TabbedHeader.styles.js similarity index 100% rename from src/predefinedComponents/TabbedHeader/TabbedHeader.styles.ts rename to src/predefinedComponents/TabbedHeader/TabbedHeader.styles.js diff --git a/src/predefinedComponents/TabbedHeader/TabbedHeader.tsx b/src/predefinedComponents/TabbedHeader/TabbedHeader.tsx deleted file mode 100644 index a548a310..00000000 --- a/src/predefinedComponents/TabbedHeader/TabbedHeader.tsx +++ /dev/null @@ -1,267 +0,0 @@ -import React from 'react'; -import { - Animated, - Image, - ImageProps, - ImageSourcePropType, - ImageStyle, - LayoutRectangle, - NativeScrollEvent, - NativeSyntheticEvent, - StatusBar, - StyleProp, - Text, - TextStyle, - View, - ViewStyle, -} from 'react-native'; -import StickyParallaxHeader, { - StickyParallaxHeaderProps, -} from '../../StickyParallaxHeaderComponent'; -import { constants, colors, sizes } from '../../constants'; -import styles from './TabbedHeader.styles'; -import type { SharedPredefinedHeaderProps } from '../../index'; - -const { event, ValueXY } = Animated; - -export interface TabbedHeaderProps extends SharedPredefinedHeaderProps { - foregroundImage?: ImageSourcePropType; - header?: () => StickyParallaxHeaderProps['header']; - headerType: 'TabbedHeader'; - logo: ImageSourcePropType; - logoContainerStyle?: StyleProp; - logoResizeMode?: ImageProps['resizeMode']; - logoStyle?: StyleProp; - onChangeTab?: StickyParallaxHeaderProps['onChangeTab']; - onRef?: StickyParallaxHeaderProps['onRef']; - rememberTabScrollPosition?: StickyParallaxHeaderProps['rememberTabScrollPosition']; - tabTextActiveStyle?: StickyParallaxHeaderProps['tabTextActiveStyle']; - tabTextContainerActiveStyle?: StickyParallaxHeaderProps['tabTextContainerActiveStyle']; - tabTextContainerStyle?: StickyParallaxHeaderProps['tabTextContainerStyle']; - tabTextStyle?: StickyParallaxHeaderProps['tabTextStyle']; - tabWrapperStyle?: StickyParallaxHeaderProps['tabWrapperStyle']; - tabs: StickyParallaxHeaderProps['tabs']; - tabsContainerBackgroundColor?: string; - initialPage?: StickyParallaxHeaderProps['initialPage']; - tabUnderlineColor?: StickyParallaxHeaderProps['tabUnderlineColor']; - tabsContainerStyle?: StickyParallaxHeaderProps['tabsContainerStyle']; - tabsContainerHorizontalPadding?: StickyParallaxHeaderProps['tabsContainerHorizontalPadding']; - title?: string; - titleStyle?: StyleProp; -} - -type State = { - contentHeight: {}; - headerLayout: { - height: number; - }; -}; - -class TabbedHeader extends React.Component { - scrollY: Animated.ValueXY; - - _value: number = 0; - - constructor(props: TabbedHeaderProps) { - super(props); - - this.state = { - contentHeight: {}, - headerLayout: { - height: 0, - }, - }; - this.scrollY = new ValueXY(); - } - - componentDidMount() { - this.scrollY.y.addListener(({ value }) => (this._value = value)); - } - - componentWillUnmount() { - this.scrollY.y.removeAllListeners(); - } - - setHeaderSize = (headerLayout: LayoutRectangle) => { - const { headerSize } = this.props; - - headerSize?.(headerLayout); - - this.setState({ headerLayout }); - }; - - scrollPosition = (value: number): number => { - const { headerLayout } = this.state; - - return constants.scrollPosition(headerLayout.height, value); - }; - - renderLogoHeader = (): StickyParallaxHeaderProps['header'] => { - const { backgroundColor, logo, logoResizeMode, logoStyle, logoContainerStyle } = this.props; - - return ( - - - - ); - }; - - renderHeader = () => { - const { header } = this.props; - - return header?.() ?? this.renderLogoHeader(); - }; - - renderTabbedForeground = (scrollY: Animated.ValueXY) => { - const { title, titleStyle, foregroundImage } = this.props; - const messageStyle = [styles.message, titleStyle]; - - const startSize = constants.responsiveWidth(18); - const endSize = constants.responsiveWidth(10); - const [startImgFade, finishImgFade] = [this.scrollPosition(22), this.scrollPosition(27)]; - const [startImgSize, finishImgSize] = [this.scrollPosition(20), this.scrollPosition(30)]; - const [startTitleFade, finishTitleFade] = [this.scrollPosition(25), this.scrollPosition(45)]; - - const imageOpacity = scrollY.y.interpolate({ - inputRange: [0, startImgFade, finishImgFade], - outputRange: [1, 1, 0], - extrapolate: 'clamp', - }); - const imageSize = scrollY.y.interpolate({ - inputRange: [0, startImgSize, finishImgSize], - outputRange: [startSize, startSize, endSize], - extrapolate: 'clamp', - }); - const titleOpacity = scrollY.y.interpolate({ - inputRange: [0, startTitleFade, finishTitleFade], - outputRange: [1, 1, 0], - extrapolate: 'clamp', - }); - - const renderImage = () => { - if (foregroundImage) { - return ( - - - - ); - } - - return null; - }; - - return ( - - {renderImage()} - - {title} - - - ); - }; - - renderForeground = (scrollY: Animated.ValueXY) => { - return this.renderTabbedForeground(scrollY); - }; - - render() { - const { - backgroundColor, - backgroundImage, - bounces, - contentContainerStyles, - headerHeight, - initialPage, - keyboardShouldPersistTaps, - onChangeTab, - onMomentumScrollBegin, - onMomentumScrollEnd, - onRef, - parallaxHeight, - refreshControl, - rememberTabScrollPosition, - scrollEvent, - scrollRef, - snapStartThreshold, - snapStopThreshold, - snapToEdge, - snapValue, - tabTextActiveStyle, - tabTextContainerActiveStyle, - tabTextContainerStyle, - tabTextStyle, - tabWrapperStyle, - tabs, - tabsContainerBackgroundColor, - tabsContainerStyle, - tabUnderlineColor, - tabsContainerHorizontalPadding, - horizontalScrollBounces, - } = this.props; - - const tabsContainerBgColor = tabsContainerBackgroundColor ?? backgroundColor; - - return ( - <> - - ) => scrollEvent?.(e), - })} - scrollRef={scrollRef} - snapStartThreshold={snapStartThreshold} - snapStopThreshold={snapStopThreshold} - snapToEdge={snapToEdge} - snapValue={snapValue} - tabTextActiveStyle={[styles.tabText, tabTextActiveStyle]} - tabTextContainerActiveStyle={[ - styles.tabTextContainerActiveStyle, - tabTextContainerActiveStyle, - ]} - tabTextContainerStyle={[styles.tabTextContainerStyle, tabTextContainerStyle]} - tabTextStyle={[styles.tabText, tabTextStyle]} - tabWrapperStyle={[styles.tabsWrapper, tabWrapperStyle]} - tabs={tabs} - tabsContainerBackgroundColor={tabsContainerBgColor} - tabsContainerStyle={tabsContainerStyle} - tabsContainerHorizontalPadding={tabsContainerHorizontalPadding} - horizontalScrollBounces={horizontalScrollBounces} - /> - - ); - } - - static defaultProps = { - initialPage: 0, - backgroundColor: colors.primaryGreen, - bounces: true, - headerHeight: sizes.headerHeight, - logoResizeMode: 'contain', - parallaxHeight: sizes.homeScreenParallaxHeader, - snapToEdge: true, - transparentHeader: true, - }; -} - -export default TabbedHeader; diff --git a/src/predefinedComponents/TabbedHeader/defaultProps/defaultProps.js b/src/predefinedComponents/TabbedHeader/defaultProps/defaultProps.js new file mode 100644 index 00000000..099d10ad --- /dev/null +++ b/src/predefinedComponents/TabbedHeader/defaultProps/defaultProps.js @@ -0,0 +1,71 @@ +import React, { useState } from 'react'; +import { string } from 'prop-types'; +import { View, Text, Platform } from 'react-native'; +import { QuizListElement } from '../../components'; +import { Brandon, Jennifer, Ewa } from '../../../assets/data/cards'; +import styles from '../TabbedHeader.styles'; +import { constants, sizes } from '../../../constants'; + +const RenderContent = ({ title }) => { + const [contentHeight, setcontentHeight] = useState({}); + const users = [Brandon, Jennifer, Ewa]; + + const onLayoutContent = (e) => { + const contentHeightTmp = { ...contentHeight }; + contentHeightTmp[title] = e.nativeEvent.layout.height; + + setcontentHeight({ ...contentHeightTmp }); + }; + + const calcMargin = () => { + let marginBottom = 50; + + if (contentHeight[title]) { + const padding = 24; + const isBigContent = constants.deviceHeight - contentHeight[title] < 0; + + if (isBigContent) { + return marginBottom; + } + + marginBottom = + constants.deviceHeight - padding * 2 - sizes.headerHeight - contentHeight[title]; + + return marginBottom; + } + + return marginBottom; + }; + const marginBottom = Platform.select({ ios: calcMargin(title), android: 0 }); + + return ( + onLayoutContent(e, title)}> + {title} + {users.map( + (user) => + (title === 'Popular Quizes' || title === user.type) && ( + {}} + pressUser={() => {}} + /> + ) + )} + + ); +}; + +RenderContent.propTypes = { + title: string, +}; + +RenderContent.defaultProps = { + title: 'Popular Quizes', +}; + +export default RenderContent; diff --git a/src/predefinedComponents/components/QuizCard/QuizCard.js b/src/predefinedComponents/components/QuizCard/QuizCard.js new file mode 100644 index 00000000..8067c18d --- /dev/null +++ b/src/predefinedComponents/components/QuizCard/QuizCard.js @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import styles from './QuizCard.styles'; +import QuizOption from '../QuizOption/QuizOption'; + +const QuizCard = ({ data: { question, cards }, num, onPress, cardsAmount }) => { + const [revealed, setRevealed] = useState(false); + + return ( + + + + {`${num + 1}/${cardsAmount}`} + + + + {question} + + {cards.map((card) => ( + { + setRevealed(true); + }} + revealed={revealed} + card={card} + /> + ))} + + ); +}; + + +export default QuizCard; diff --git a/src/predefinedComponents/components/QuizCard/QuizCard.styles.js b/src/predefinedComponents/components/QuizCard/QuizCard.styles.js new file mode 100644 index 00000000..8ff11f95 --- /dev/null +++ b/src/predefinedComponents/components/QuizCard/QuizCard.styles.js @@ -0,0 +1,47 @@ +import { StyleSheet } from 'react-native'; +import { colors } from '../../../constants'; + +export default StyleSheet.create({ + container: { + marginTop: 12, + shadowColor: colors.shadowColor, + shadowOffset: { + width: 2, + heght: 2, + }, + shadowRadius: 40, + shadowOpacity: 0.08, + borderWidth: 2, + borderColor: colors.paleGrey, + width: '100%', + backgroundColor: colors.white, + borderRadius: 24, + paddingHorizontal: 20, + paddingVertical: 16, + }, + labelContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingBottom: 8, + }, + labelTextContainer: { + backgroundColor: colors.paleGrey, + borderRadius: 16, + }, + labelText: { + fontSize: 12, + lineHeight: 16, + color: colors.greyishBrown, + paddingHorizontal: 8, + paddingVertical: 4, + letterSpacing: 0.8, + }, + mainText: { + fontSize: 20, + lineHeight: 24, + color: colors.black, + paddingTop: 8, + paddingBottom: 20, + }, +}); diff --git a/src/predefinedComponents/components/QuizListElement/QuizListElement.js b/src/predefinedComponents/components/QuizListElement/QuizListElement.js new file mode 100644 index 00000000..aed57ed4 --- /dev/null +++ b/src/predefinedComponents/components/QuizListElement/QuizListElement.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, Image } from 'react-native'; +import styles from './QuizListElement.styles'; +import { colors } from '../../../constants'; + +const QuizListElement = ({ + onPress, + authorName, + imageSource, + mainText, + labelText, + elements, + pressUser, +}) => ( + + + + {labelText} + + = 20 && { backgroundColor: colors.coralPink }]}> + + {elements} + + + + {mainText} + + + + + + {authorName} + + + + + +); + +export default QuizListElement; diff --git a/src/predefinedComponents/components/QuizListElement/QuizListElement.styles.js b/src/predefinedComponents/components/QuizListElement/QuizListElement.styles.js new file mode 100644 index 00000000..2feb94e8 --- /dev/null +++ b/src/predefinedComponents/components/QuizListElement/QuizListElement.styles.js @@ -0,0 +1,100 @@ +import { StyleSheet, Platform } from 'react-native'; +import { colors, constants } from '../../../constants'; + +export default StyleSheet.create({ + container: { + marginTop: 12, + shadowColor: colors.shadowColor, + shadowOffset: { + width: 2, + heght: 2, + }, + shadowRadius: 40, + shadowOpacity: 0.08, + width: '100%', + backgroundColor: colors.white, + borderRadius: 24, + paddingHorizontal: 20, + paddingVertical: 16, + borderWidth: Platform.OS === 'android' ? 2 : 0, + borderColor: colors.paleGrey, + }, + labelContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + }, + labelTextContainer: { + backgroundColor: colors.paleGrey, + borderRadius: 16, + }, + labelText: { + fontSize: 12, + lineHeight: 16, + color: colors.greyishBrown, + paddingHorizontal: 8, + paddingVertical: 4, + }, + iconContainer: { + flexDirection: 'row', + padding: 8, + borderRadius: 8, + shadowColor: colors.shadowColor, + shadowOffset: { + width: 0, + heght: 2, + }, + shadowRadius: 32, + shadowOpacity: 0.016, + backgroundColor: colors.purplishBlue, + width: 56, + alignItems: 'center', + }, + icon: { + width: 16, + height: 16, + }, + number: { + color: colors.white, + paddingLeft: 5, + fontSize: 16, + lineHeight: 20, + }, + mainText: { + fontSize: 24, + lineHeight: 28, + color: colors.black, + letterSpacing: -0.2, + paddingTop: 8, + }, + mainTextContainer: {}, + footerContainer: { + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + paddingTop: 22, + }, + authorPhoto: { + width: 24, + height: 24, + borderRadius: constants.isAndroid ? 50 : 8, + }, + authorName: { + fontSize: 12, + lineHeight: 16, + color: colors.black, + paddingLeft: 8, + }, + authorWrapper: { + flexDirection: 'row', + }, + authorContainer: { + paddingRight: 40, + }, + authorBlankContainer: { + width: '38%', + }, + iconCardElement: { + paddingLeft: 8, + }, +}); diff --git a/src/predefinedComponents/components/QuizOption/QuizOption.js b/src/predefinedComponents/components/QuizOption/QuizOption.js new file mode 100644 index 00000000..784128e2 --- /dev/null +++ b/src/predefinedComponents/components/QuizOption/QuizOption.js @@ -0,0 +1,67 @@ +import React, { useState } from 'react'; +import { View, Text, Image, TouchableOpacity } from 'react-native'; +import styles from './QuizOption.styles'; +import { colors } from '../../../constants'; + +const QuizOption = ({ reveal, revealed, card: { number, question, value } }) => { + const [picked, setPicked] = useState(false); + const [paddingVertical, setPaddingVertical] = useState(0); + const calcPaddings = (event) => { + const { height } = event.nativeEvent.layout; + const circleRadius = 40; + const padding = height > circleRadius ? height / 2.5 : 0; + setPaddingVertical(padding); + }; + + const renderValue = () => { + if (value) { + return ; + } + + return ; + }; + + if (revealed) { + let backgroundColor = 'white'; + let color = 'black'; + if (picked) color = 'white'; + if (picked && value) backgroundColor = colors.jade; + if (picked && !value) backgroundColor = colors.coralPink; + + return ( + + {renderValue()} + { + calcPaddings(event); + }} + style={styles.textContainer}> + {question} + + + ); + } + + return ( + { + reveal(); + setPicked(true); + }} + style={styles.container}> + + {number} + + { + calcPaddings(event); + }} + style={styles.textContainer}> + {question} + + + ); +}; + + +export default QuizOption; diff --git a/src/predefinedComponents/components/QuizOption/QuizOption.styles.js b/src/predefinedComponents/components/QuizOption/QuizOption.styles.js new file mode 100644 index 00000000..acad3f1e --- /dev/null +++ b/src/predefinedComponents/components/QuizOption/QuizOption.styles.js @@ -0,0 +1,39 @@ +import { StyleSheet } from 'react-native'; +import { colors, constants } from '../../../constants'; + +export default StyleSheet.create({ + container: { + width: constants.responsiveWidth(75), + minHeight: 48, + borderRadius: 24, + backgroundColor: colors.paleGrey, + flexDirection: 'row', + alignItems: 'center', + marginTop: 4, + marginBottom: 4, + }, + letter: { + color: colors.black, + }, + letterContainer: { + alignItems: 'center', + justifyContent: 'center', + minHeight: 36, + width: 36, + borderRadius: 17.5, + backgroundColor: colors.white, + margin: 6, + }, + textContainer: { + width: '80%', + alignContent: 'center', + justifyContent: 'center', + paddingLeft: 7, + paddingVertical: 5, + }, + text: { + fontSize: 16, + lineHeight: 24, + color: colors.black, + }, +}); diff --git a/src/predefinedComponents/components/UserModal/UserModal.js b/src/predefinedComponents/components/UserModal/UserModal.js new file mode 100644 index 00000000..6c7f312c --- /dev/null +++ b/src/predefinedComponents/components/UserModal/UserModal.js @@ -0,0 +1,262 @@ +import React from 'react'; +import { Text, View, Image, TouchableOpacity, Animated, StatusBar, Platform } from 'react-native'; +import StickyParallaxHeader from '../../../StickyParallaxHeader'; +import { constants, sizes } from '../../../constants'; +import styles from './UserModal.styles'; +import QuizListElement from '../QuizListElement/QuizListElement'; +import { Brandon } from '../../../assets/data/cards'; + +const { event, ValueXY } = Animated; + +class UserModal extends React.Component { + constructor(props) { + super(props); + + this.state = { + headerLayout: { + height: 0, + }, + contentHeight: 0, + }; + this.scrollY = new ValueXY(); + } + + setHeaderSize = (headerLayout) => this.setState({ headerLayout }); + + renderHeader = () => { + const { onPressCloseModal, user } = this.props; + + const [beforeFadeImg, startFadeImg, finishFadeImg] = [ + this.scrollPosition(30), + this.scrollPosition(40), + this.scrollPosition(70), + ]; + const [beforeFadeName, startFadeName, finishFadeName] = [ + this.scrollPosition(50), + this.scrollPosition(60), + this.scrollPosition(75), + ]; + + const imageOpacity = this.scrollY.y.interpolate({ + inputRange: [0, beforeFadeImg, startFadeImg, finishFadeImg], + outputRange: [0, 0, 0.5, 1], + extrapolate: 'clamp', + }); + const nameOpacity = this.scrollY.y.interpolate({ + inputRange: [0, beforeFadeName, startFadeName, finishFadeName], + outputRange: [0, 0, 0.5, 1], + extrapolate: 'clamp', + }); + + return ( + + + + + + + + + {user.author} + + + + + + + + ); + }; + + renderForeground = () => { + const { user } = this.props; + const startSize = constants.responsiveWidth(18); + const endSize = constants.responsiveWidth(12); + + const [startImgAnimation, finishImgAnimation] = [ + this.scrollPosition(27), + this.scrollPosition(31), + ]; + const [startAuthorFade, finishAuthorFade] = [this.scrollPosition(40), this.scrollPosition(50)]; + + const [startAboutFade, fininshAboutFade] = [this.scrollPosition(60), this.scrollPosition(70)]; + + const imageOpacity = this.scrollY.y.interpolate({ + inputRange: [0, startImgAnimation, finishImgAnimation], + outputRange: [1, 0.8, 0], + extrapolate: 'clamp', + }); + const imageSize = this.scrollY.y.interpolate({ + inputRange: [0, startImgAnimation, finishImgAnimation], + outputRange: [startSize, startSize, endSize], + extrapolate: 'clamp', + }); + const authorOpacity = this.scrollY.y.interpolate({ + inputRange: [0, startAuthorFade, finishAuthorFade], + outputRange: [1, 1, 0], + extrapolate: 'clamp', + }); + const aboutOpacity = this.scrollY.y.interpolate({ + inputRange: [0, startAboutFade, fininshAboutFade], + outputRange: [1, 1, 0], + extrapolate: 'clamp', + }); + + return ( + + + + + + {user.author} + + + {user.about} + + + ); + }; + + renderBackground = () => { + const { + headerLayout: { height }, + } = this.state; + const headerBorderRadius = this.scrollY.y.interpolate({ + inputRange: [0, height], + outputRange: [80, 0], + extrapolate: 'extend', + }); + + const { user } = this.props; + + return ( + + ); + }; + + calcMargin = () => { + const { contentHeight } = this.state; + let marginBottom = 0; + + if (contentHeight) { + const isBigContent = constants.deviceHeight - contentHeight < 0; + + if (isBigContent) { + return marginBottom; + } + + marginBottom = constants.deviceHeight - sizes.headerHeight - contentHeight; + + return marginBottom; + } + + return marginBottom; + }; + + onLayoutContent = (e) => { + this.setState({ + contentHeight: e.nativeEvent.layout.height, + }); + }; + + scrollPosition(value) { + const { + headerLayout: { height }, + } = this.state; + + return constants.scrollPosition(height, value); + } + + renderContent = () => { + const marginBottom = Platform.select({ ios: this.calcMargin(), android: 0 }); + const user = Brandon; + const title = "Author's Quizes"; + const cards = [ + { + id: '4850294857', + elements: user.cardsAmount, + authorName: user.author, + mainText: user.label, + labelText: user.type, + imageSource: user.image, + }, + ]; + + return ( + + {title} + {cards.map((card) => ( + {}} + /> + ))} + + ); + }; + + render() { + const { user } = this.props; + + return ( + <> + + + {this.renderContent()} + + + ); + } +} + +export default UserModal; diff --git a/src/predefinedComponents/components/UserModal/UserModal.styles.js b/src/predefinedComponents/components/UserModal/UserModal.styles.js new file mode 100644 index 00000000..7aedd325 --- /dev/null +++ b/src/predefinedComponents/components/UserModal/UserModal.styles.js @@ -0,0 +1,64 @@ +import { StyleSheet, Platform } from 'react-native'; +import { ifIphoneX } from '../../../utils'; +import { colors, constants, screenStyles } from '../../../constants'; + +export default StyleSheet.create({ + ...screenStyles, + userModalHeader: { + paddingTop: Platform.OS === 'ios' ? ifIphoneX(50, 30) : 18, + }, + headerPic: { + width: 32, + height: 32, + borderRadius: 8, + }, + headerMenu: { + flexDirection: 'row', + alignItems: 'center', + }, + headerTitleContainer: { + flexDirection: 'row', + alignItems: 'center', + marginLeft: -(constants.deviceWidth / 3), + }, + headerTitle: { + fontSize: 16, + lineHeight: 20, + color: colors.white, + marginLeft: 12, + }, + infoContainer: { + flexDirection: 'row', + paddingBottom: 32, + }, + iconContainer: { + flexDirection: 'row', + padding: 8, + borderRadius: 8, + backgroundColor: colors.white, + width: 56, + }, + infoText: { + color: colors.white, + fontSize: constants.normalizedFontSize(16), + lineHeight: 24, + }, + icon: { + width: 16, + height: 16, + }, + footerContainer: { + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + marginHorizontal: 24, + }, + authorPhoto: { + width: 32, + height: 32, + borderRadius: 8, + }, + userModalMessageContainer: { + paddingBottom: 8, + }, +}); diff --git a/src/predefinedComponents/components/index.js b/src/predefinedComponents/components/index.js new file mode 100644 index 00000000..4a45c336 --- /dev/null +++ b/src/predefinedComponents/components/index.js @@ -0,0 +1,6 @@ +import QuizListElement from './QuizListElement/QuizListElement'; +import UserModal from './UserModal/UserModal'; +import QuizCard from './QuizCard/QuizCard'; +import QuizOption from './QuizOption/QuizOption'; + +export { QuizListElement, UserModal, QuizCard, QuizOption }; diff --git a/src/predefinedComponents/index.ts b/src/predefinedComponents/index.js similarity index 51% rename from src/predefinedComponents/index.ts rename to src/predefinedComponents/index.js index f33b7592..db58d427 100644 --- a/src/predefinedComponents/index.ts +++ b/src/predefinedComponents/index.js @@ -2,8 +2,4 @@ import AvatarHeader from './AvatarHeader/AvatarHeader'; import TabbedHeader from './TabbedHeader/TabbedHeader'; import DetailsHeader from './DetailsHeader/DetailsHeader'; -export type { AvatarHeaderProps } from './AvatarHeader/AvatarHeader'; -export type { TabbedHeaderProps } from './TabbedHeader/TabbedHeader'; -export type { DetailsHeaderProps } from './DetailsHeader/DetailsHeader'; - export { AvatarHeader, TabbedHeader, DetailsHeader }; diff --git a/src/styles.tsx b/src/styles.js similarity index 87% rename from src/styles.tsx rename to src/styles.js index dccac20e..c7f8ad67 100644 --- a/src/styles.tsx +++ b/src/styles.js @@ -1,5 +1,4 @@ import { StyleSheet, Dimensions } from 'react-native'; -import { colors } from './constants'; const { width } = Dimensions.get('window'); @@ -35,10 +34,10 @@ const styles = StyleSheet.create({ position: 'absolute', left: 0, bottom: 0, - backgroundColor: colors.transparent, + backgroundColor: 'transparent', }, transparentHeader: { - backgroundColor: colors.transparent, + backgroundColor: 'transparent', position: 'absolute', top: 0, left: 0, diff --git a/src/tests/AvatarHeader.test.tsx b/src/tests/AvatarHeader.test.js similarity index 70% rename from src/tests/AvatarHeader.test.tsx rename to src/tests/AvatarHeader.test.js index ae07fca4..ed6675cb 100644 --- a/src/tests/AvatarHeader.test.tsx +++ b/src/tests/AvatarHeader.test.js @@ -4,10 +4,7 @@ import AvatarHeader from '../predefinedComponents/AvatarHeader/AvatarHeader'; describe('AvatarHeader Test [Hacktoberfest]', () => { test('-Snapshot AvatarHeader', () => { - const tree = renderer - .create() - .toJSON(); - + const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); diff --git a/src/tests/DetailsHeader.test.js b/src/tests/DetailsHeader.test.js new file mode 100644 index 00000000..b1328139 --- /dev/null +++ b/src/tests/DetailsHeader.test.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { cleanup } from '@testing-library/react-native'; +import renderer from 'react-test-renderer'; +import DetailsHeader from '../predefinedComponents/DetailsHeader/DetailsHeader'; +import StickyParallaxHeader from '../StickyParallaxHeader'; + +describe('DetailsHeader empty', () => { + afterEach(cleanup); + + test('Snapshot for default', () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + + test('Should have default leftTopIconOnPress prop', () => { + expect(DetailsHeader.defaultProps.leftTopIconOnPress).toBeDefined(); + }); + + test('LeftTopIconOnPress should return undefined', () => { + const result = DetailsHeader.defaultProps.leftTopIconOnPress(); + expect(result).toBeUndefined(); + }); + + test('Should have default rightTopIconOnPress prop', () => { + expect(DetailsHeader.defaultProps.rightTopIconOnPress).toBeDefined(); + }); + + test('RightTopIconOnPress should return undefined', () => { + const result = DetailsHeader.defaultProps.rightTopIconOnPress(); + expect(result).toBeUndefined(); + }); + + test('Renders title prop correctly', () => { + const component = renderer.create(); + const testInstance = component.root; + + expect(testInstance.props.title).toBe('Test 1'); + }); + + test('Renders headerLayout correctly', () => { + const component = renderer.create(); + const testInstance = component.root; + const stickyHeader = testInstance.findByType(StickyParallaxHeader); + + expect(testInstance.instance.state.headerLayout).toMatchObject({ height: 0 }); + stickyHeader.props.headerSize({ height: 1 }); + expect(testInstance.instance.state.headerLayout).toMatchObject({ height: 1 }); + }); +}); diff --git a/src/tests/DetailsHeader.test.tsx b/src/tests/DetailsHeader.test.tsx deleted file mode 100644 index 02513277..00000000 --- a/src/tests/DetailsHeader.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { cleanup } from '@testing-library/react-native'; -import renderer from 'react-test-renderer'; -import DetailsHeader from '../predefinedComponents/DetailsHeader/DetailsHeader'; -import StickyParallaxHeaderComponent from '../StickyParallaxHeaderComponent'; - -describe('DetailsHeader empty', () => { - afterEach(cleanup); - - test('Snapshot for default', () => { - const tree = renderer.create().toJSON(); - - expect(tree).toMatchSnapshot(); - }); - - test('Renders title prop correctly', () => { - const component = renderer.create( - - ); - const testInstance = component.root; - - expect(testInstance.props.title).toBe('Test 1'); - }); - - test('Renders headerLayout correctly', () => { - const component = renderer.create(); - const testInstance = component.root; - const stickyHeader = testInstance.findByType(StickyParallaxHeaderComponent); - - expect(testInstance.instance.state.headerLayout).toMatchObject({ height: 0 }); - stickyHeader.props.headerSize({ height: 1 }); - expect(testInstance.instance.state.headerLayout).toMatchObject({ height: 1 }); - }); -}); diff --git a/src/tests/StickyParallaxHeader.test.tsx b/src/tests/StickyParallaxHeader.test.js similarity index 53% rename from src/tests/StickyParallaxHeader.test.tsx rename to src/tests/StickyParallaxHeader.test.js index b3a98551..7d1dbffa 100644 --- a/src/tests/StickyParallaxHeader.test.tsx +++ b/src/tests/StickyParallaxHeader.test.js @@ -1,15 +1,11 @@ import React from 'react'; import { render, cleanup } from '@testing-library/react-native'; -import StickyParallaxHeaderComponent from '../StickyParallaxHeaderComponent'; +import StickyParallaxHeader from '../StickyParallaxHeader'; describe('StickyParallaxHeader empty', () => { afterEach(cleanup); - test('Snapshot for default', () => { - const tree = render( - } headerSize={() => {}} header={<>} /> - ).toJSON(); - + const tree = render( {}} header={<>} />).toJSON(); expect(tree).toMatchSnapshot(); }); }); diff --git a/src/tests/__snapshots__/AvatarHeader.test.js.snap b/src/tests/__snapshots__/AvatarHeader.test.js.snap new file mode 100644 index 00000000..9c9433cc --- /dev/null +++ b/src/tests/__snapshots__/AvatarHeader.test.js.snap @@ -0,0 +1,631 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AvatarHeader Test [Hacktoberfest] -Snapshot AvatarHeader 1`] = ` + + + + + + + + + + + Brandon + + + + + + + + + + + + + + + + + + + + + + + Brandon + + + + + Coffee buff. Web enthusiast. Unapologetic student. Gamer. Avid organizer. + + + + + + + + + + + + Author's Quizes + + + + + + Product Design + + + + + + 10 + + + + + + Design System + + + + + + + + Brandon + + + + + + + + + + + + + + +`; diff --git a/src/tests/__snapshots__/AvatarHeader.test.tsx.snap b/src/tests/__snapshots__/AvatarHeader.test.tsx.snap deleted file mode 100644 index 6ddc83b5..00000000 --- a/src/tests/__snapshots__/AvatarHeader.test.tsx.snap +++ /dev/null @@ -1,343 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AvatarHeader Test [Hacktoberfest] -Snapshot AvatarHeader 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; diff --git a/src/tests/__snapshots__/DetailsHeader.test.js.snap b/src/tests/__snapshots__/DetailsHeader.test.js.snap new file mode 100644 index 00000000..e4ec3a5d --- /dev/null +++ b/src/tests/__snapshots__/DetailsHeader.test.js.snap @@ -0,0 +1,1614 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DetailsHeader empty Snapshot for default 1`] = ` + + + + + + + + + + Design System + + + + + + + + + + + + + + + + + + + + Product Design + + + + + Design System + + + + + + + 10 + + + + + + Brandon + + + + + + + + + + + + + + + + 1/4 + + + + + + Can design system contain information about copywriting? + + + + + + A + + + + + Yes + + + + + + + B + + + + + No + + + + + + + + + 2/4 + + + + + + Who is taking care of managing a design system? + + + + + + A + + + + + Product Designer + + + + + + + B + + + + + UI Designer + + + + + + + C + + + + + None of above + + + + + + + + + 3/4 + + + + + + Are there official standards for Design Systems? + + + + + + A + + + + + Yes + + + + + + + B + + + + + No + + + + + + + + + 4/4 + + + + + + What kind of animal is the dolphin? + + + + + + A + + + + + Mammalr + + + + + + + B + + + + + Reptile + + + + + + + C + + + + + Fish + + + + + + + C + + + + + Amphibian + + + + + + + + + + + + +`; diff --git a/src/tests/__snapshots__/DetailsHeader.test.tsx.snap b/src/tests/__snapshots__/DetailsHeader.test.tsx.snap deleted file mode 100644 index 6a5f2ea6..00000000 --- a/src/tests/__snapshots__/DetailsHeader.test.tsx.snap +++ /dev/null @@ -1,348 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DetailsHeader empty Snapshot for default 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; diff --git a/src/tests/__snapshots__/StickyParallaxHeader.test.tsx.snap b/src/tests/__snapshots__/StickyParallaxHeader.test.js.snap similarity index 95% rename from src/tests/__snapshots__/StickyParallaxHeader.test.tsx.snap rename to src/tests/__snapshots__/StickyParallaxHeader.test.js.snap index de6581d5..195b20aa 100644 --- a/src/tests/__snapshots__/StickyParallaxHeader.test.tsx.snap +++ b/src/tests/__snapshots__/StickyParallaxHeader.test.js.snap @@ -11,7 +11,7 @@ exports[`StickyParallaxHeader empty Snapshot for default 1`] = ` diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 00000000..19ec21b7 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,37 @@ +import { Dimensions, Platform } from 'react-native'; + +export function isIphoneX() { + const dimen = Dimensions.get('window'); + + return ( + Platform.OS === 'ios' && + !Platform.isPad && + !Platform.isTVOS && + (dimen.height === 812 || dimen.width === 812 || dimen.height === 896 || dimen.width === 896) + ); +} + +export function ifIphoneX(iphoneXStyle, regularStyle) { + if (isIphoneX()) { + return iphoneXStyle; + } + + return regularStyle; +} + +export function getSafelyScrollNode(scrollNode) { + // after react-native 0.62 + if (scrollNode && scrollNode.scrollTo) return scrollNode; + + // before react-native 0.62 + return scrollNode.getNode(); +} + +export function setRef(ref, value) { + if (typeof ref === 'function') { + ref(value); + } else if (ref !== null) { + // eslint-disable-next-line no-param-reassign + ref.current = value; + } +} diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 2640a5c6..00000000 --- a/src/utils.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Dimensions, ImageStyle, Platform, ScrollView, TextStyle, ViewStyle } from 'react-native'; -import type { MutableRefObject, RefObject } from 'react'; - -export function isIphoneX(): boolean { - const dimen = Dimensions.get('window'); - - return ( - Platform.OS === 'ios' && - !Platform.isPad && - !Platform.isTVOS && - (dimen.height === 812 || dimen.width === 812 || dimen.height === 896 || dimen.width === 896) - ); -} - -export function ifIphoneX( - iphoneXStyle: T, - regularStyle: T -): T { - if (isIphoneX()) { - return iphoneXStyle; - } - - return regularStyle; -} - -export function getSafelyScrollNode( - scrollNode: { getNode(): ScrollView } | ScrollView | null -): ScrollView | null { - // after react-native 0.62 - if (scrollNode && (scrollNode as ScrollView)?.scrollTo) { - return scrollNode as ScrollView; - } - if (scrollNode) { - // before react-native 0.62 - // @ts-ignore - return scrollNode.getNode(); - } - - return null; -} - -type RefCallback = (r: T) => void; -export function setRef( - ref: RefObject | MutableRefObject | RefCallback, - value: T -): void { - if (typeof ref === 'function') { - ref?.(value); - } else if (ref !== null) { - // @ts-ignore - ref.current = value; - } -} diff --git a/yarn.lock b/yarn.lock index 387ec974..0f525d33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7979,6 +7979,11 @@ react-native-builder-bob@^0.18.0: optionalDependencies: jetifier "^1.6.6" +react-native-iphone-x-helper@^1.3.1: + version "1.3.1" + resolved "https://mirrors.cloud.tencent.com/npm/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010" + integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg== + react-native@0.62.2: version "0.62.2" resolved "https://registry.npmjs.org/react-native/-/react-native-0.62.2.tgz"