back header types, transform-ignore-pattern & tidy up docs for imagefallback
This commit is contained in:
parent
608e784205
commit
7da14dbb6c
@ -19,7 +19,7 @@
|
|||||||
"jest": {
|
"jest": {
|
||||||
"preset": "jest-expo",
|
"preset": "jest-expo",
|
||||||
"transformIgnorePatterns": [
|
"transformIgnorePatterns": [
|
||||||
"node_modules/(?!((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|react-native-svg-charts|d3-path)/)"
|
"node_modules/(?!((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|react-native-svg-charts|d3-path|expo-constants|expo-modules-core)/)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
// BackHeader.tsx
|
import { NavigationProp } from "@react-navigation/native";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||||
import { shadows } from "../../styles";
|
import { shadows } from "../../styles";
|
||||||
|
|
||||||
type BackHeaderProps = {
|
type BackHeaderProps = {
|
||||||
navigation: any; // You can replace 'any' with the appropriate type for your navigation prop
|
navigation: NavigationProp<any>; // TODO: #135 should use a RootStackParamList
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -14,6 +14,8 @@ const BackHeader: React.FC<BackHeaderProps> = ({ navigation, title }) => {
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
onPress={() => navigation.goBack()}
|
onPress={() => navigation.goBack()}
|
||||||
|
accessibilityLabel="Go back"
|
||||||
|
accessibilityRole="button"
|
||||||
>
|
>
|
||||||
<Text style={styles.text}>{`< ${title}`}</Text>
|
<Text style={styles.text}>{`< ${title}`}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
@ -1,35 +1,34 @@
|
|||||||
import React, { useState } from "react";
|
import React, { memo, useState } from "react";
|
||||||
import { Image } from "react-native";
|
import { Image, ImageStyle, StyleProp } from "react-native";
|
||||||
|
|
||||||
type ImageWithFallbackProps = {
|
type ImageWithFallbackProps = {
|
||||||
source: { uri: string };
|
source: { uri: string };
|
||||||
fallbackSource?: { uri: string };
|
fallbackSource: { uri: string };
|
||||||
style?: object;
|
style?: StyleProp<ImageStyle>;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* A React component that displays an image with a fallback option.
|
* A React component that displays an image with a fallback option.
|
||||||
* If the primary image fails to load, it will display a fallback image instead.
|
* If the primary image fails to load, it will display a fallback image instead.
|
||||||
*
|
*
|
||||||
* @param {ImageWithFallbackProps} props - The props for the component.
|
* @param {Object} source - The source of the primary image.
|
||||||
* @param {Object} props.source - The source of the primary image. It should be an object with a `uri` property.
|
* @param {Object} fallbackSource - The source of the fallback image. Should be an asset rather than loaded from an endpoint.
|
||||||
* @param {Object} [props.fallbackSource] - Optional. The source of the fallback image. It should be an object with a `uri` property.
|
* @param {StyleProp<ImageStyle>} style - Optional. The style to apply to the image. It can be any valid React Native style object.
|
||||||
* @param {Object} [props.style] - Optional. The style to apply to the image. It can be any valid React Native style object.
|
|
||||||
* @returns {React.ReactElement} A React element representing the image with fallback.
|
* @returns {React.ReactElement} A React element representing the image with fallback.
|
||||||
*/
|
*/
|
||||||
const ImageWithFallback: React.FC<ImageWithFallbackProps> = ({
|
const ImageWithFallback: React.FC<ImageWithFallbackProps> = memo(
|
||||||
source,
|
({ source, fallbackSource, style }) => {
|
||||||
fallbackSource,
|
const [imageSource, setImageSource] = useState(source);
|
||||||
style,
|
|
||||||
}: ImageWithFallbackProps): React.ReactElement => {
|
|
||||||
const [imageSource, setImageSource] = useState(source);
|
|
||||||
|
|
||||||
const handleError = () => {
|
const handleError = () => {
|
||||||
if (fallbackSource) {
|
if (fallbackSource) {
|
||||||
setImageSource(fallbackSource);
|
setImageSource(fallbackSource);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Image source={imageSource} style={style} onError={handleError} />;
|
return <Image source={imageSource} style={style} onError={handleError} />;
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ImageWithFallback.displayName = "ImageWithFallback";
|
||||||
|
|
||||||
export default ImageWithFallback;
|
export default ImageWithFallback;
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { ScrollView, View } from "react-native";
|
|
||||||
import Modal from "react-native-modal";
|
|
||||||
|
|
||||||
import { modal } from "../../styles";
|
|
||||||
|
|
||||||
interface SlideModalInterface {
|
|
||||||
modalVisible: boolean;
|
|
||||||
setModalVisible: Function;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SlideModal Component
|
|
||||||
*
|
|
||||||
* A modal component that slides down from the bottom of the screen and can be dismissed by swiping down or tapping the backdrop.
|
|
||||||
*
|
|
||||||
* @param {Object} props - Props for SlideModal component.
|
|
||||||
* @param {boolean} props.modalVisible - State variable that controls the visibility of the modal.
|
|
||||||
* @param {Function} props.setModalVisible - State setter function to update the visibility of the modal.
|
|
||||||
* @param {React.ReactNode} props.children - The content to be rendered inside the modal.
|
|
||||||
*
|
|
||||||
* @component
|
|
||||||
* @example
|
|
||||||
* const [modalVisible, setModalVisible] = useState(false);
|
|
||||||
*
|
|
||||||
* return (
|
|
||||||
* <SlideModal
|
|
||||||
* modalVisible={modalVisible}
|
|
||||||
* setModalVisible={setModalVisible}
|
|
||||||
* >
|
|
||||||
* <Text>Modal Content</Text>
|
|
||||||
* </SlideModal>
|
|
||||||
* );
|
|
||||||
*/
|
|
||||||
export default function SlideModal({
|
|
||||||
modalVisible,
|
|
||||||
setModalVisible,
|
|
||||||
children,
|
|
||||||
}: SlideModalInterface): React.JSX.Element {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
hasBackdrop={true}
|
|
||||||
isVisible={modalVisible}
|
|
||||||
onBackdropPress={() => setModalVisible(false)}
|
|
||||||
onSwipeComplete={() => setModalVisible(false)}
|
|
||||||
swipeDirection="down"
|
|
||||||
style={modal.noMargin}
|
|
||||||
propagateSwipe
|
|
||||||
>
|
|
||||||
<View style={modal.alignModalContainer}>
|
|
||||||
<View style={modal.grabber} />
|
|
||||||
<ScrollView contentContainerStyle={modal.contentStyles}>
|
|
||||||
{children}
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { StyleSheet, Text, View } from "react-native";
|
|
||||||
|
|
||||||
const SessionCardFooter = ({ sessionName, lastPlayed }) => {
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Text style={styles.sessionName}>{sessionName}</Text>
|
|
||||||
<Text style={styles.sessionDatetime}>{lastPlayed}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
sessionName: {
|
|
||||||
fontSize: 18,
|
|
||||||
paddingTop: 5,
|
|
||||||
marginHorizontal: 16,
|
|
||||||
},
|
|
||||||
sessionDatetime: {
|
|
||||||
fontSize: 10,
|
|
||||||
color: "#A3A3A3",
|
|
||||||
marginHorizontal: 16,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SessionCardFooter;
|
|
@ -1,68 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Image, StyleSheet, Text, View } from "react-native";
|
|
||||||
|
|
||||||
const SessionCardHeader = ({
|
|
||||||
playerName,
|
|
||||||
location,
|
|
||||||
gameType,
|
|
||||||
locationIconURL,
|
|
||||||
profileImageURL,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<View style={styles.headerContainer}>
|
|
||||||
<Image
|
|
||||||
style={styles.headerProfileImage}
|
|
||||||
source={{ uri: profileImageURL }}
|
|
||||||
accessibilityLabel="Profile image"
|
|
||||||
/>
|
|
||||||
<View style={styles.headerText}>
|
|
||||||
<Text style={styles.playerName}>{playerName}</Text>
|
|
||||||
<View style={styles.locationContainer}>
|
|
||||||
<Image source={{ uri: locationIconURL }} style={styles.icon} />
|
|
||||||
<Text style={styles.locationText}>{location}</Text>
|
|
||||||
</View>
|
|
||||||
<Text style={styles.gameType}>{gameType}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
headerContainer: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
marginHorizontal: 16,
|
|
||||||
},
|
|
||||||
|
|
||||||
headerProfileImage: {
|
|
||||||
width: 60,
|
|
||||||
height: 60,
|
|
||||||
resizeMode: "contain",
|
|
||||||
},
|
|
||||||
|
|
||||||
headerText: {
|
|
||||||
flexDirection: "column",
|
|
||||||
padding: 10,
|
|
||||||
},
|
|
||||||
playerName: {
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: "bold",
|
|
||||||
},
|
|
||||||
locationContainer: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
},
|
|
||||||
locationText: {
|
|
||||||
fontSize: 13,
|
|
||||||
},
|
|
||||||
gameType: {
|
|
||||||
fontSize: 13,
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
width: 18,
|
|
||||||
height: 18,
|
|
||||||
resizeMode: "contain",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SessionCardHeader;
|
|
@ -1,25 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { StyleSheet, Text, View } from "react-native";
|
|
||||||
|
|
||||||
const SessionCardStat = ({ sessionStat, displayName }) => {
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Text style={styles.statItem}>{displayName}</Text>
|
|
||||||
<Text style={styles.statValue}>{sessionStat}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
statItem: {
|
|
||||||
fontSize: 10,
|
|
||||||
textAlign: "center",
|
|
||||||
color: "#666",
|
|
||||||
},
|
|
||||||
statValue: {
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: "400",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SessionCardStat;
|
|
@ -1,48 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { StyleSheet, View } from "react-native";
|
|
||||||
import SessionCardStat from "./session-card-stat";
|
|
||||||
|
|
||||||
const SessionCardStatsRowContainer = ({
|
|
||||||
makePercent,
|
|
||||||
medianRun,
|
|
||||||
duration,
|
|
||||||
shotPacing,
|
|
||||||
}) => {
|
|
||||||
const stats = [
|
|
||||||
{ displayName: "Make Percent", sessionStat: makePercent },
|
|
||||||
{ displayName: "Median Run", sessionStat: medianRun },
|
|
||||||
{ displayName: "Time Played", sessionStat: duration },
|
|
||||||
{ displayName: "Shot Pacing", sessionStat: shotPacing },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.statsContainer}>
|
|
||||||
{stats.map((stat, index) => (
|
|
||||||
<React.Fragment key={index}>
|
|
||||||
<SessionCardStat
|
|
||||||
displayName={stat.displayName}
|
|
||||||
sessionStat={stat.sessionStat}
|
|
||||||
/>
|
|
||||||
{index < stats.length - 1 && <View style={styles.verticalSpacer} />}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
statsContainer: {
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
margin: 16,
|
|
||||||
},
|
|
||||||
verticalSpacer: {
|
|
||||||
width: 1,
|
|
||||||
backgroundColor: "#A3A3A3",
|
|
||||||
height: "100%",
|
|
||||||
marginHorizontal: 12,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SessionCardStatsRowContainer;
|
|
@ -1,65 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Image, StyleSheet, View } from "react-native";
|
|
||||||
import SessionCardFooter from "./session-card-footer";
|
|
||||||
import SessionCardHeader from "./session-card-header";
|
|
||||||
import SessionCardStatsRowContainer from "./session-card-stats-row-container";
|
|
||||||
|
|
||||||
const SessionCard = ({
|
|
||||||
playerName,
|
|
||||||
location,
|
|
||||||
gameType,
|
|
||||||
makePercent,
|
|
||||||
medianRun,
|
|
||||||
duration,
|
|
||||||
shotPacing,
|
|
||||||
sessionName,
|
|
||||||
lastPlayed,
|
|
||||||
imageURL,
|
|
||||||
profileImageURL,
|
|
||||||
locationIconURL,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<View style={styles.card}>
|
|
||||||
<SessionCardHeader
|
|
||||||
playerName={playerName}
|
|
||||||
location={location}
|
|
||||||
gameType={gameType}
|
|
||||||
locationIconURL={locationIconURL}
|
|
||||||
profileImageURL={profileImageURL}
|
|
||||||
/>
|
|
||||||
<SessionCardStatsRowContainer
|
|
||||||
makePercent={makePercent}
|
|
||||||
medianRun={medianRun}
|
|
||||||
duration={duration}
|
|
||||||
shotPacing={shotPacing}
|
|
||||||
/>
|
|
||||||
<Image source={imageURL} style={styles.image} />
|
|
||||||
<SessionCardFooter sessionName={sessionName} lastPlayed={lastPlayed} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
card: {
|
|
||||||
backgroundColor: "white",
|
|
||||||
borderRadius: 8,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: "#ddd",
|
|
||||||
shadowColor: "#000",
|
|
||||||
shadowOffset: {
|
|
||||||
width: 0,
|
|
||||||
height: 2,
|
|
||||||
},
|
|
||||||
shadowOpacity: 0.25,
|
|
||||||
shadowRadius: 3.84,
|
|
||||||
elevation: 5,
|
|
||||||
margin: 10,
|
|
||||||
overflow: "hidden",
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
width: "100%",
|
|
||||||
height: "50%",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SessionCard;
|
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { StyleSheet, Text, View } from "react-native";
|
import { StyleProp, StyleSheet, Text, View, ViewStyle } from "react-native";
|
||||||
import { borders } from "../../styles";
|
import { borders } from "../../styles";
|
||||||
|
|
||||||
type StatItem = {
|
type StatItem = {
|
||||||
@ -9,14 +9,14 @@ type StatItem = {
|
|||||||
|
|
||||||
type StatListProps = {
|
type StatListProps = {
|
||||||
items: StatItem[];
|
items: StatItem[];
|
||||||
style?: object;
|
style?: StyleProp<ViewStyle>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StatList: React.FC<StatListProps> = ({ items, style }) => {
|
const StatList: React.FC<StatListProps> = ({ items, style }) => {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, style]}>
|
<View style={[styles.container, style]}>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<View key={index} style={styles.item}>
|
<View key={`${item.title}-${index}`} style={styles.item}>
|
||||||
<Text style={styles.title}>{item.title}</Text>
|
<Text style={styles.title}>{item.title}</Text>
|
||||||
<Text style={styles.value}>{item.value}</Text>
|
<Text style={styles.value}>{item.value}</Text>
|
||||||
</View>
|
</View>
|
||||||
@ -27,7 +27,6 @@ const StatList: React.FC<StatListProps> = ({ items, style }) => {
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flexDirection: "column",
|
|
||||||
flex: 1,
|
flex: 1,
|
||||||
padding: 5,
|
padding: 5,
|
||||||
},
|
},
|
||||||
|
@ -101,14 +101,15 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
paddingLeft: 45,
|
paddingLeft: 45,
|
||||||
paddingVertical: 42,
|
paddingTop: 42,
|
||||||
|
paddingBottom: 38,
|
||||||
},
|
},
|
||||||
statColumn: {
|
statColumn: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
padding: 5,
|
padding: 5,
|
||||||
},
|
},
|
||||||
horizontalDivider: {
|
horizontalDivider: {
|
||||||
width: "95%",
|
width: "92%",
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
...borders.dottedBottomBorder,
|
...borders.dottedBottomBorder,
|
||||||
},
|
},
|
||||||
|
@ -6,7 +6,7 @@ let STATUS_BAR_HEIGHT: number;
|
|||||||
|
|
||||||
Platform.OS === "android"
|
Platform.OS === "android"
|
||||||
? (STATUS_BAR_HEIGHT = 24)
|
? (STATUS_BAR_HEIGHT = 24)
|
||||||
: (STATUS_BAR_HEIGHT = Constants.statusBarHeight | 24);
|
: (STATUS_BAR_HEIGHT = Constants.statusBarHeight || 24);
|
||||||
|
|
||||||
// GLOBALLY STYLED
|
// GLOBALLY STYLED
|
||||||
export const globalInputStyles = StyleSheet.create({
|
export const globalInputStyles = StyleSheet.create({
|
||||||
|
Loading…
Reference in New Issue
Block a user