Consistent Application Styling #39 #48

Merged
djwesty merged 1 commits from djwesty/39 into main 2025-03-02 16:53:50 -08:00
18 changed files with 429 additions and 372 deletions

View File

@ -1,7 +1,35 @@
import AppContext, { IAppContext } from "@/util/context";
import { MaterialIcons } from "@expo/vector-icons";
import { Stack } from "expo-router";
import React from "react";
import React, { useMemo, useState } from "react";
const RootLayout: React.FC = () => {
const [showSettings, setShowSettings] = useState<boolean>(false);
const ctx = useMemo<IAppContext>(
() => ({
showSettings,
}),
[showSettings]
);
return (
<AppContext.Provider value={ctx}>
<Stack
screenOptions={{
headerShown: true,
title: "Poker Chips Helper",
headerRight: () => (
<MaterialIcons
name="settings"
onPress={() => setShowSettings(!showSettings)}
size={30}
/>
),
}}
/>
</AppContext.Provider>
);
};
const RootLayout: React.FC = () => (
<Stack screenOptions={{ headerShown: true, title: "Poker Chips Helper" }} />
);
export default RootLayout;

View File

@ -1,14 +1,6 @@
import React, { useState, useEffect } from "react";
import {
ScrollView,
Text,
Alert,
Button,
View,
StyleSheet,
TouchableOpacity,
} from "react-native";
import { FontAwesome } from "@expo/vector-icons";
import React, { useState, useEffect, useContext, useMemo } from "react";
import { ScrollView, Alert } from "react-native";
import Button from "@/containers/Button";
import PlayerSelector from "@/components/PlayerSelector";
import BuyInSelector from "@/components/BuyInSelector";
import ChipsSelector from "@/components/ChipsSelector";
@ -20,14 +12,9 @@ import {
savePersistentState,
loadPersistentState,
} from "@/components/PersistentState";
export enum COLORS {
"white",
"red",
"green",
"blue",
"black",
}
import styles from "@/styles/styles";
import Section from "@/containers/Section";
import AppContext from "@/util/context";
const IndexScreen: React.FC = () => {
const [playerCount, setPlayerCount] = useState(2);
@ -35,7 +22,8 @@ const IndexScreen: React.FC = () => {
const [numberOfChips, setNumberOfChips] = useState<number>(5);
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
const [selectedCurrency, setSelectedCurrency] = useState<string>("$");
const [isSettingsVisible, setIsSettingsVisible] = useState(false);
const context = useContext(AppContext);
const isSettingsVisible = useMemo(() => context.showSettings, [context]);
useEffect(() => {
const loadPersistentData = async () => {
@ -104,96 +92,85 @@ const IndexScreen: React.FC = () => {
};
return (
<ScrollView contentContainerStyle={{ padding: 20, flexGrow: 1 }}>
<View style={styles.header}>
<TouchableOpacity
onPress={() => setIsSettingsVisible(!isSettingsVisible)}
>
<Text>
<FontAwesome name="cog" size={30} color="black" />
</Text>
</TouchableOpacity>
</View>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollViewContent}
>
{isSettingsVisible && (
<View style={styles.settingsContainer}>
<Section title={"Select Currency"} iconName={"attach-money"}>
<CurrencySelector
selectedCurrency={selectedCurrency}
setSelectedCurrency={setSelectedCurrency}
/>
</View>
</Section>
)}
<PlayerSelector
playerCount={playerCount}
setPlayerCount={setPlayerCount}
/>
<BuyInSelector
selectedCurrency={selectedCurrency}
setBuyInAmount={setBuyInAmount}
/>
<ChipDetection
updateChipCount={(chipData) => {
const chipCountArray = Object.values(chipData);
setTotalChipsCount(chipCountArray);
}}
/>
<ChipsSelector
totalChipsCount={totalChipsCount}
setTotalChipsCount={setTotalChipsCount}
numberOfChips={numberOfChips}
setNumberOfChips={setNumberOfChips}
/>
<ChipDistributionSummary
playerCount={playerCount}
buyInAmount={buyInAmount}
totalChipsCount={totalChipsCount}
selectedCurrency={selectedCurrency}
/>
<View style={styles.buttonContainer}>
<Button
title="Save to Slot 1"
onPress={() => handleSave("SLOT1")}
disabled={buyInAmount === null}
<Section
title={"Select the number of players"}
iconName={"people"}
orientation="row"
>
<PlayerSelector
playerCount={playerCount}
setPlayerCount={setPlayerCount}
/>
<Button
title="Save to Slot 2"
onPress={() => handleSave("SLOT2")}
disabled={buyInAmount === null}
</Section>
<Section title={"Select buy-in amount"} iconName={"monetization-on"}>
<BuyInSelector
selectedCurrency={selectedCurrency}
setBuyInAmount={setBuyInAmount}
/>
<Button title="Load from Slot 1" onPress={() => handleLoad("SLOT1")} />
<Button title="Load from Slot 2" onPress={() => handleLoad("SLOT2")} />
</View>
</Section>
<Section title={"Automatic Chip Detection"} iconName={"camera-alt"}>
<ChipDetection
updateChipCount={(chipData) => {
const chipCountArray = Object.values(chipData);
setTotalChipsCount(chipCountArray);
}}
/>
</Section>
<Section title={"Manual Chip Adjustment"} iconName={"account-balance"}>
<ChipsSelector
totalChipsCount={totalChipsCount}
setTotalChipsCount={setTotalChipsCount}
numberOfChips={numberOfChips}
setNumberOfChips={setNumberOfChips}
/>
</Section>
<Section
title={"Distribution & Denomination"}
iconName={"currency-exchange"}
>
<ChipDistributionSummary
playerCount={playerCount}
buyInAmount={buyInAmount}
totalChipsCount={totalChipsCount}
selectedCurrency={selectedCurrency}
/>
</Section>
<Section title={"Save + Load"} iconName={"save"} orientation="row">
<>
<Button
title={"Save\nSlot 1"}
onPress={() => handleSave("SLOT1")}
disabled={buyInAmount === null}
/>
<Button
title={"Save\nSlot 2"}
onPress={() => handleSave("SLOT2")}
disabled={buyInAmount === null}
/>
<Button title={"Load\nSlot 1"} onPress={() => handleLoad("SLOT1")} />
<Button title={"Load\nSlot 2"} onPress={() => handleLoad("SLOT2")} />
</>
</Section>
</ScrollView>
);
};
const styles = StyleSheet.create({
header: {
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "center",
marginBottom: 20,
},
settingsContainer: {
marginBottom: 20,
padding: 10,
backgroundColor: "#f5f5f5",
borderRadius: 5,
},
settingTitle: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 10,
},
buttonContainer: {
marginTop: 20,
},
});
export default IndexScreen;

View File

@ -1,12 +1,7 @@
import React, { useState } from "react";
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
} from "react-native";
import { MaterialIcons } from "@expo/vector-icons";
import { View, Text, TextInput } from "react-native";
import styles, { COLORS } from "@/styles/styles";
import Button from "@/containers/Button";
interface BuyInSelectorProps {
setBuyInAmount: React.Dispatch<React.SetStateAction<number>>;
@ -42,31 +37,19 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({
};
return (
<View style={styles.container}>
<View style={styles.header}>
<MaterialIcons name="monetization-on" size={30} color="green" />
<Text style={styles.title}>Select Buy-in Amount:</Text>
</View>
<View style={styles.optionsContainer}>
<>
<View style={{ ...styles.container, flexDirection: "row" }}>
{defaultBuyInOptions.map((amount) => (
<TouchableOpacity
<Button
key={amount}
style={[
styles.buyInButton,
buyInAmount === amount ? styles.selectedButton : null,
]}
color={buyInAmount === amount ? COLORS.PRIMARY : COLORS.SECONDARY}
onPress={() => handleBuyInSelection(amount)}
>
<Text style={styles.buttonText}>
{selectedCurrency} {amount}{" "}
{/* Display the selected currency before the amount */}
</Text>
</TouchableOpacity>
title={`${selectedCurrency} ${amount}`}
></Button>
))}
</View>
<Text style={styles.orText}>Or enter a custom amount:</Text>
<Text style={styles.p}>Or enter a custom amount:</Text>
<TextInput
style={styles.input}
value={customAmount}
@ -75,60 +58,12 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({
keyboardType="numeric"
/>
<Text style={styles.selectionText}>
<Text style={styles.h2}>
Selected Buy-in:{" "}
{buyInAmount !== null ? `${selectedCurrency} ${buyInAmount}` : "None"}{" "}
{/* Display the currency here */}
{buyInAmount !== null ? `${selectedCurrency} ${buyInAmount}` : "None"}
</Text>
</View>
</>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
},
header: {
flexDirection: "row",
alignItems: "center",
marginBottom: 10,
},
title: {
fontSize: 22,
marginLeft: 10,
},
optionsContainer: {
flexDirection: "row",
justifyContent: "space-around",
marginVertical: 10,
},
buyInButton: {
backgroundColor: "#ddd",
padding: 10,
borderRadius: 5,
},
selectedButton: {
backgroundColor: "#4caf50",
},
buttonText: {
fontSize: 16,
},
orText: {
marginTop: 10,
textAlign: "center",
},
input: {
borderWidth: 1,
borderColor: "#ccc",
padding: 8,
marginVertical: 10,
borderRadius: 5,
},
selectionText: {
marginTop: 15,
fontSize: 16,
fontWeight: "bold",
},
});
export default BuyInSelector;

View File

@ -1,15 +1,14 @@
import React, { useState } from "react";
import {
View,
Button,
Image,
ActivityIndicator,
Text,
ScrollView,
} from "react-native";
import { Image, ActivityIndicator, Text, View } from "react-native";
import Button from "@/containers/Button";
import * as ImagePicker from "expo-image-picker";
const ChipDetection = ({ updateChipCount }) => {
const ChipDetection = ({
updateChipCount,
}: {
updateChipCount: () => void;
}) => {
const [imageUri, setImageUri] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
@ -113,9 +112,11 @@ const ChipDetection = ({ updateChipCount }) => {
};
return (
<ScrollView contentContainerStyle={{ padding: 20, alignItems: "center" }}>
<Button title="Pick an Image" onPress={pickImage} />
<Button title="Take a Photo" onPress={takePhoto} />
<>
<View style={{ flexDirection: "row", justifyContent: "space-evenly" }}>
<Button title="Pick an Image" onPress={pickImage} />
<Button title="Take a Photo" onPress={takePhoto} />
</View>
{imageUri && (
<Image
source={{ uri: imageUri }}
@ -124,7 +125,7 @@ const ChipDetection = ({ updateChipCount }) => {
)}
{loading && <ActivityIndicator size="large" color="blue" />}
{error && <Text style={{ color: "red", marginTop: 10 }}>{error}</Text>}
</ScrollView>
</>
);
};

View File

@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { View, Text, StyleSheet } from "react-native";
import { ColorValue } from "react-native";
import styles from "@/styles/styles";
interface ChipDistributionSummaryProps {
playerCount: number;
@ -15,7 +16,7 @@ const ChipDistributionSummary = ({
buyInAmount,
totalChipsCount,
colors = ["white", "red", "green", "blue", "black"],
selectedCurrency,
selectedCurrency = "$",
}: ChipDistributionSummaryProps) => {
const validDenominations: validDenomination[] = [
0.05, 0.1, 0.25, 0.5, 1, 2, 2.5, 5, 10, 20, 50, 100,
@ -153,84 +154,32 @@ const ChipDistributionSummary = ({
}, [totalChipsCount, maxDenomination, buyInAmount, playerCount]);
return (
<View style={styles.container}>
<Text style={styles.title}>Distribution & Denomination</Text>
<Text style={styles.subTitle}>
{selectedCurrency} {potValue} Pot
</Text>
<View style={styles.chipContainer}>
<>
<View style={styles.container}>
{totalChipsCount.map((_, index) => (
<View style={styles.chipRow} key={index}>
<View style={{ flexDirection: "row" }} key={index}>
<Text
style={{
...styles.chipText,
...styles.h2,
color: colors[index],
...(colors[index] === "white" && styles.whiteShadow),
...(colors[index] === "white" && styles.shadow),
}}
>
{distributions[index]} chips:
</Text>
<Text
style={{
...styles.chipText,
color: colors[index],
...(colors[index] === "white" && styles.whiteShadow),
}}
>
{selectedCurrency} {denominations[index]} each
{`${distributions[index]} chips: ${selectedCurrency}${denominations[index]} each`}
</Text>
</View>
))}
</View>
<Text style={styles.chipText}>
Total Value: {selectedCurrency} {totalValue}
</Text>
</View>
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text style={styles.p}>
Total Value: {selectedCurrency} {totalValue}
</Text>
<Text style={styles.p}>
{selectedCurrency} {potValue} Pot
</Text>
</View>
</>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 20,
padding: 15,
borderRadius: 10,
display: "flex",
alignItems: "center",
},
title: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 10,
},
subTitle: {
fontSize: 16,
color: "gray",
fontWeight: "bold",
marginBottom: 10,
},
chipContainer: {
marginTop: 10,
},
chipRow: {
display: "flex",
flexDirection: "row",
justifyContent: "center",
gap: 10,
},
chipText: {
fontSize: 20,
marginVertical: 2,
fontWeight: "bold",
},
whiteShadow: {
textShadowColor: "black",
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 10,
},
noDataText: {
fontSize: 16,
color: "gray",
},
});
export default ChipDistributionSummary;

View File

@ -4,10 +4,14 @@ import {
Text,
TextInput,
StyleSheet,
Button,
ColorValue,
Modal,
TouchableOpacity,
} from "react-native";
import Button from "@/containers/Button";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import styles from "@/styles/styles";
const colors: ColorValue[] = ["white", "red", "green", "blue", "black"];
const ChipInputModal = ({
@ -31,16 +35,27 @@ const ChipInputModal = ({
setValue(totalChipsCount[colorIdx]);
}, [colorIdx]);
const shadow = useMemo(() => color === "white", [color]);
return (
<Modal
visible={showModal[0]}
onRequestClose={() => setShowModal([false, color])}
style={styles.modal}
presentationStyle="fullScreen"
animationType="slide"
>
{value !== undefined && (
<>
<Text>Number of {showModal[1]?.toString()} chips</Text>
<Text style={styles.h2}>
Number of {showModal[1]?.toString()} chips
</Text>
<TextInput
style={{ color: showModal[1] }}
style={{
...styles.input,
color: showModal[1],
...(shadow ? styles.shadow : {}),
}}
keyboardType="numeric"
value={value.toString()}
onChangeText={(v) => {
@ -71,14 +86,25 @@ const Chip = ({
count: number;
setShowModal: React.Dispatch<React.SetStateAction<[boolean, ColorValue]>>;
}) => {
const shadow = useMemo(() => color === "white", [color]);
return (
<Text
key={color.toString()}
<TouchableOpacity
onPress={() => setShowModal([true, color])}
style={[{ color: color }, styles.chip]}
style={{ alignItems: "center" }}
>
{count}
</Text>
<MaterialCommunityIcons
name="poker-chip"
size={24}
color={color}
style={shadow ? styles.shadow : {}}
/>
<Text
key={color.toString()}
style={[{ color: color }, styles.h2, shadow ? styles.shadow : {}]}
>
{count}
</Text>
</TouchableOpacity>
);
};
@ -134,34 +160,31 @@ const ChipsSelector = ({
return (
<>
<View style={styles.container}>
<Text style={styles.title}>Chips you have</Text>
<View style={styles.chipContainer}>
{colorsUsed.map((color) => (
<Chip
key={color.toString()}
color={color}
count={totalChipsCount[colors.indexOf(color)] ?? 0}
setShowModal={setShowModal}
/>
))}
</View>
<View style={styles.buttonContainer}>
<Button
title="-"
onPress={() => {
setNumberOfChips(Math.max(1, numberOfChips - 1));
}}
disabled={numberOfChips == 1}
<View style={[styles.container, { flexDirection: "row" }]}>
{colorsUsed.map((color) => (
<Chip
key={color.toString()}
color={color}
count={totalChipsCount[colors.indexOf(color)] ?? 0}
setShowModal={setShowModal}
/>
<Button
title="+"
onPress={() => {
setNumberOfChips(Math.min(5, numberOfChips + 1));
}}
disabled={numberOfChips == 5}
/>
</View>
))}
</View>
<View style={[styles.container, { flexDirection: "row" }]}>
<Button
title="-"
onPress={() => {
setNumberOfChips(Math.max(1, numberOfChips - 1));
}}
disabled={numberOfChips == 1}
/>
<Button
title="+"
onPress={() => {
setNumberOfChips(Math.min(5, numberOfChips + 1));
}}
disabled={numberOfChips == 5}
/>
</View>
<ChipInputModal
@ -174,7 +197,7 @@ const ChipsSelector = ({
);
};
const styles = StyleSheet.create({
const styles1 = StyleSheet.create({
container: {
marginBottom: 20,
gap: 10,

View File

@ -1,6 +1,6 @@
import React from "react";
import { View, StyleSheet, Text } from "react-native";
import { Picker } from "@react-native-picker/picker";
import styles from "@/styles/styles";
interface CurrencySelectorProps {
selectedCurrency: string;
@ -12,8 +12,7 @@ const CurrencySelector: React.FC<CurrencySelectorProps> = ({
setSelectedCurrency,
}) => {
return (
<View style={styles.container}>
<Text style={styles.title}>Select Currency:</Text>
<>
<Picker
selectedValue={selectedCurrency}
onValueChange={(itemValue) => setSelectedCurrency(itemValue)}
@ -25,22 +24,8 @@ const CurrencySelector: React.FC<CurrencySelectorProps> = ({
<Picker.Item label="Pound (£)" value="£" />
<Picker.Item label="INR (₹)" value="₹" />
</Picker>
</View>
</>
);
};
const styles = StyleSheet.create({
container: {
marginBottom: 20,
},
title: {
fontSize: 18,
marginBottom: 10,
},
picker: {
height: 50,
width: 150,
},
});
export default CurrencySelector;

View File

@ -1,64 +1,43 @@
import React from "react";
import { View, Text, Button, Image, StyleSheet } from "react-native";
import { View, Text } from "react-native";
import Button from "@/containers/Button";
import styles from "@/styles/styles";
interface PlayerSelectorProps {
playerCount: number;
setPlayerCount: React.Dispatch<React.SetStateAction<number>>;
}
const MIN = 2;
const MAX = 8;
const PlayerSelector: React.FC<PlayerSelectorProps> = ({
playerCount,
setPlayerCount,
}) => {
const increasePlayers = () => {
if (playerCount < 8) setPlayerCount(playerCount + 1);
if (playerCount < MAX) setPlayerCount(playerCount + 1);
};
const decreasePlayers = () => {
if (playerCount > 2) setPlayerCount(playerCount - 1);
if (playerCount > MIN) setPlayerCount(playerCount - 1);
};
return (
<View style={styles.container}>
<View style={styles.header}>
<Image
source={{
uri: "https://static.thenounproject.com/png/3890959-200.png",
}}
style={styles.icon}
<>
<Text style={styles.h1}>{playerCount}</Text>
<View style={{ flexDirection: "row", gap: 10 }}>
<Button
title="-"
onPress={decreasePlayers}
disabled={playerCount <= MIN}
/>
<Button
title="+"
onPress={increasePlayers}
disabled={playerCount >= MAX}
/>
<Text style={styles.title}>Select Number of Players:</Text>
</View>
<Text style={styles.playerCount}>{playerCount}</Text>
<View style={{ flexDirection: "row" }}>
<Button title="-" onPress={decreasePlayers} />
<Button title="+" onPress={increasePlayers} />
</View>
</View>
</>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
},
header: {
flexDirection: "row",
alignItems: "center",
},
title: {
fontSize: 18,
marginLeft: 10, // Spacing between icon and text
},
icon: {
width: 48, // Increased size
height: 48, // Increased size
},
playerCount: {
fontSize: 24,
marginVertical: 10,
},
});
export default PlayerSelector;

View File

@ -32,7 +32,6 @@ describe("BuyInSelector Component", () => {
});
it("renders the buy-in options and input correctly", () => {
expect(getByText("Select Buy-in Amount:")).toBeTruthy();
expect(getByText("$ 10")).toBeTruthy();
expect(getByText("$ 25")).toBeTruthy();
expect(getByText("$ 50")).toBeTruthy();

View File

@ -12,26 +12,20 @@ describe("ChipDistributionSummary Component", () => {
const expectedDistribution = [2, 2, 1, 2, 2];
const expectedDenominations = [0.5, 1, 2, 2.5, 5];
const { getByText, getAllByText } = render(
const { getByText } = render(
<ChipDistributionSummary
playerCount={playerCount}
buyInAmount={buyInAmount}
totalChipsCount={totalChipsCount}
selectedCurrency={"$"}
/>
);
expect(getByText("Distribution & Denomination")).toBeTruthy();
expectedDistribution.forEach((count, index) => {
// Ensure "X chips:" appears correctly
expect(getAllByText(new RegExp(`^${count}\\s+chips:`, "i"))).toBeTruthy();
// Ensure value format matches the rendered output
expect(
getByText(
new RegExp(`^\\s*${expectedDenominations[index]}\\s+each$`, "i")
)
).toBeTruthy();
const regex = new RegExp(
`^${count}\\s+chips:\\s+\\$${expectedDenominations[index]}\\s+each$`
);
expect(getByText(regex)).toBeTruthy();
});
});
@ -40,6 +34,7 @@ describe("ChipDistributionSummary Component", () => {
<ChipDistributionSummary
playerCount={0}
buyInAmount={20}
selectedCurrency={"$"}
totalChipsCount={[]}
/>
);
@ -66,6 +61,7 @@ describe("ChipDistributionSummary Component", () => {
playerCount={playerCount}
buyInAmount={100}
totalChipsCount={totalChipsCount}
selectedCurrency={"$"}
/>
);

View File

@ -10,6 +10,13 @@ import ChipsSelector from "@/components/ChipsSelector";
const TOTAL_CHIPS_COUNT = [100, 80, 60, 40, 20];
jest.mock("@expo/vector-icons", () => {
const { Text } = require("react-native");
return {
MaterialCommunityIcons: () => <Text>TestIcon</Text>,
};
});
const mocktTotalChipsCount = jest.fn();
const mockSetNumberOfChips = jest.fn();

View File

@ -13,7 +13,6 @@ describe("CurrencySelector Component", () => {
/>
);
expect(getByText("Select Currency:")).toBeTruthy(); // Check label exists
expect(getByTestId("currency-picker")).toBeTruthy(); // Check Picker exists
});

View File

@ -9,7 +9,6 @@ describe("PlayerSelector Component", () => {
<PlayerSelector playerCount={4} setPlayerCount={setPlayerCount} />
);
expect(getByText("Select Number of Players:")).toBeTruthy();
expect(getByText("4")).toBeTruthy();
expect(getByRole("button", { name: "-" })).toBeTruthy();
expect(getByRole("button", { name: "+" })).toBeTruthy();

9
containers/Button.tsx Normal file
View File

@ -0,0 +1,9 @@
import { ButtonProps, Button } from "react-native";
import { COLORS } from "@/styles/styles";
// More styling can be done, or swap this out with more flexible component like a TouchableOpacity if needed
const AppButton = (props: ButtonProps) => (
<Button color={COLORS.PRIMARY} {...props} />
);
export default AppButton;

67
containers/Section.tsx Normal file
View File

@ -0,0 +1,67 @@
import { View, Text, StyleSheet } from "react-native";
import React from "react";
import { MaterialIcons } from "@expo/vector-icons";
import globalStyles from "@/styles/styles";
const titleCase = (input: string) =>
input
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(" ");
//Higher Order Component (HOC) for styling purposes
const Section = ({
title,
iconName,
children,
orientation = "column",
}: {
title: string;
iconName: string | any;
children: React.JSX.Element;
orientation?: "row" | "column";
}) => {
return (
<View style={styles.container}>
<View style={styles.header}>
<MaterialIcons
style={styles.icon}
name={iconName}
size={30}
color={"black"}
/>
<Text style={styles.title}>{titleCase(title)}</Text>
</View>
<View style={{ ...styles.content, flexDirection: orientation }}>
{children}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginBottom: 20,
display: "flex",
alignContent: "center",
justifyContent: "center",
},
header: {
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 5,
marginBottom: 10,
},
icon: {},
title: {
...globalStyles.h1,
},
content: {
display: "flex",
justifyContent: "space-evenly",
gap: 5,
},
});
export default Section;

View File

@ -0,0 +1,32 @@
import { render, screen } from "@testing-library/react-native";
import Section from "../Section";
import { Text } from "react-native";
import React from "react";
jest.mock("@expo/vector-icons", () => {
const { Text } = require("react-native");
return {
MaterialIcons: () => <Text testID={"test-icon"}>TestIcon</Text>,
};
});
const TITLE = "Select the weather";
const rend = () =>
render(
<Section title={TITLE} iconName={"test-icon"}>
<Text>child</Text>
</Section>
);
describe("tests for the Section HOC Component", () => {
it("everything expected appears", () => {
rend();
const title = screen.getByText(/select the weather/i);
expect(title).toBeTruthy();
const child = screen.getByText("child");
expect(child).toBeTruthy();
const icon = screen.getByTestId("test-icon");
expect(icon).toBeTruthy();
});
});

59
styles/styles.ts Normal file
View File

@ -0,0 +1,59 @@
import { StyleSheet } from "react-native";
export const COLORS = {
PRIMARY: "#007bff",
SECONDARY: "#6c757d",
SUCCESS: "#28a745",
DANGER: "#dc3545",
WARNING: "#ffc107",
};
const lightStyles = StyleSheet.create({});
const darkStyles = StyleSheet.create({});
const GlobalStyles = StyleSheet.create({
scrollView: {},
scrollViewContent: {
padding: 15,
},
container: {
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
gap: 10,
},
h1: { fontSize: 20, fontWeight: "bold" },
h2: { fontSize: 18, fontWeight: "normal" },
p: {
fontSize: 16,
color: "#333",
},
input: {
borderWidth: 1,
borderColor: "#ccc",
padding: 8,
marginVertical: 10,
borderRadius: 5,
},
modal: {},
button: {
backgroundColor: "#007bff",
padding: 10,
borderRadius: 5,
},
shadow: {
textShadowColor: "black",
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 10,
},
picker: {
height: 50,
width: 150,
},
});
export default GlobalStyles;

13
util/context.ts Normal file
View File

@ -0,0 +1,13 @@
import React, { createContext } from "react";
export interface IAppContext {
showSettings: boolean;
}
const defaultContext: IAppContext = {
showSettings: false,
};
const AppContext = createContext<IAppContext>(defaultContext);
export default AppContext;