back header types, transform-ignore-pattern & tidy up docs for imagefallback

This commit is contained in:
Loewy 2024-02-21 18:02:54 -08:00
parent 608e784205
commit 7da14dbb6c
12 changed files with 32 additions and 322 deletions

View File

@ -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": {

View File

@ -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>

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
}, },

View File

@ -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,
}, },

View File

@ -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({