Skip to content

Commit e9db6ed

Browse files
authored
feat: create baseline components (#49)
1 parent e67ad0c commit e9db6ed

File tree

8 files changed

+278
-1
lines changed

8 files changed

+278
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@
88
.DS_Store
99
.devenv
1010
node_modules
11+
12+
package-lock.json

frontend/app/(tabs)/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function HomeScreen() {
1313
headerImage={<Image source={require("@/assets/images/partial-react-logo.png")} style={styles.reactLogo} />}>
1414
<ThemedView style={styles.titleContainer}>
1515
<ThemedText type={"title"} style={{ fontFamily: "Outfit" }}>
16-
PlatMate - React Native Boilerplate
16+
PlateMate - React Native Boilerplate
1717
</ThemedText>
1818
</ThemedView>
1919
<ThemedView>

frontend/components/Avatar.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React from "react";
2+
import { Image, View, Text, ImageSourcePropType, StyleProp, ImageStyle, ViewStyle, TextStyle } from "react-native";
3+
4+
export interface AvatarProps {
5+
imageSource?: ImageSourcePropType;
6+
size?: number; // in dp
7+
containerStyle?: StyleProp<ViewStyle>;
8+
fallbackText?: string;
9+
textStyle?: StyleProp<TextStyle>;
10+
imageStyle?: StyleProp<ImageStyle>;
11+
}
12+
13+
// Note: By default, the container uses overflow: "hidden" and a borderRadius
14+
// of size/2 for a circular shape. If you pass any additional containerStyle,
15+
// it will merge with these defaults.
16+
17+
export function Avatar({ imageSource, size = 40, containerStyle, fallbackText, textStyle, imageStyle }: AvatarProps) {
18+
return (
19+
<View
20+
style={[
21+
{
22+
width: size,
23+
height: size,
24+
borderRadius: size / 2, // Circular!
25+
overflow: "hidden",
26+
alignItems: "center",
27+
justifyContent: "center",
28+
},
29+
containerStyle,
30+
]}>
31+
{imageSource ? (
32+
// Fill the container if theres an image
33+
<Image
34+
source={imageSource}
35+
style={[
36+
{
37+
width: "100%",
38+
height: "100%",
39+
borderRadius: size / 2, // Circular!
40+
resizeMode: "cover",
41+
},
42+
imageStyle,
43+
]}
44+
/>
45+
) : fallbackText ? (
46+
// Display fallback text if no image
47+
<Text style={textStyle}>{fallbackText}</Text>
48+
) : null}
49+
</View>
50+
);
51+
}
52+
53+
// Example usage:
54+
55+
// <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
56+
// {/* with image source */}
57+
// <Avatar
58+
// imageSource={{ uri: "https://course.khoury.northeastern.edu/cs2510a/ben_lerner.jpg" }}
59+
// size={50}
60+
// imageStyle={{ resizeMode: "cover" }}
61+
// />
62+
63+
// {/* with fallback text */}
64+
// <Avatar
65+
// fallbackText="AB"
66+
// size={40}
67+
// containerStyle={{
68+
// backgroundColor: "#007AFF",
69+
// }}
70+
// textStyle={{
71+
// fontWeight: "bold",
72+
// }}
73+
// />
74+
// </View>

frontend/components/Badge.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from "react";
2+
import { View, Text, StyleProp, ViewStyle, TextStyle } from "react-native";
3+
4+
export interface BadgeProps {
5+
text?: string;
6+
containerStyle?: StyleProp<ViewStyle>;
7+
textStyle?: StyleProp<TextStyle>;
8+
}
9+
10+
export function Badge({ text, containerStyle, textStyle }: BadgeProps) {
11+
return <View style={containerStyle}>{text ? <Text style={textStyle}>{text}</Text> : null}</View>;
12+
}
13+
14+
// Example usage:
15+
16+
// import { Badge } from "@/components/Badge";
17+
{
18+
/* <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
19+
<Badge
20+
text="New!"
21+
containerStyle={{
22+
// backgroundColor: "red",
23+
// borderRadius: 12,
24+
// paddingHorizontal: 8,
25+
// paddingVertical: 4,
26+
}}
27+
textStyle={{
28+
// fontWeight: "bold",
29+
}}
30+
/>
31+
</View> */
32+
}

frontend/components/Button.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { PropsWithChildren } from "react";
2+
import {
3+
TouchableOpacity,
4+
Text,
5+
type StyleProp,
6+
type TextStyle,
7+
type ViewStyle,
8+
type TouchableOpacityProps,
9+
} from "react-native";
10+
11+
export interface ButtonProps extends TouchableOpacityProps, PropsWithChildren<{}> {
12+
title?: string;
13+
containerStyle?: StyleProp<ViewStyle>;
14+
textStyle?: StyleProp<TextStyle>;
15+
}
16+
17+
export function Button(props: ButtonProps) {
18+
const { onPress, title, containerStyle, textStyle, children, ...rest } = props;
19+
20+
return (
21+
// Fill in rest with whatever you want to pass to the View or TouchableOpacity component!
22+
<TouchableOpacity onPress={onPress} style={containerStyle} {...rest}>
23+
{title ? <Text style={textStyle}>{title}</Text> : children}
24+
</TouchableOpacity>
25+
);
26+
}
27+
28+
// Example usage:
29+
30+
// import { Button } from "@/components/Button";
31+
// import { Alert, View } from "react-native";
32+
{
33+
/* <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
34+
<Button
35+
title="Click Me!"
36+
onPress={() => Alert.alert("Button Pressed!")}
37+
containerStyle={{
38+
padding: 12,
39+
borderRadius: 8,
40+
// more styles etc...
41+
}}
42+
textStyle={{
43+
// color: "#FFFFFF",
44+
}}
45+
/>
46+
</View> */
47+
}

frontend/components/Card.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React, { PropsWithChildren } from "react";
2+
import {
3+
View,
4+
Image,
5+
TouchableOpacity,
6+
type ImageSourcePropType,
7+
type ViewProps,
8+
type StyleProp,
9+
type ViewStyle,
10+
type ImageStyle,
11+
} from "react-native";
12+
13+
interface CardProps extends ViewProps, PropsWithChildren<{}> {
14+
imageSource: ImageSourcePropType;
15+
imageStyle?: StyleProp<ImageStyle>;
16+
contentContainerStyle?: StyleProp<ViewStyle>;
17+
onPress?: () => void;
18+
}
19+
20+
export function Card({ imageSource, imageStyle, contentContainerStyle, onPress, children, style, ...rest }: CardProps) {
21+
// Use TouchableOpacity if onPress is availabele for tapping!
22+
const Container = onPress ? TouchableOpacity : View;
23+
24+
return (
25+
// Fill in rest with whatever you want to pass to the View or TouchableOpacity component!
26+
<Container onPress={onPress} style={style} {...rest}>
27+
<Image source={imageSource} style={imageStyle} />
28+
{children && <View style={contentContainerStyle}>{children}</View>}
29+
</Container>
30+
);
31+
}
32+
33+
// Example usage:
34+
35+
// In parent (index.tsx here):
36+
// import { Card } from "@/components/Card";
37+
38+
// <ThemedView style={{ marginTop: 20 }}>
39+
// <Card
40+
// imageSource={require("@/assets/images/partial-react-logo.png")}
41+
// imageStyle={styles.cardImage}
42+
// contentContainerStyle={styles.cardContent}
43+
// onPress={() => console.log("Card Pressed")}
44+
// style={styles.cardContainer}>
45+
// <ThemedText type={"default"} style={{ fontFamily: "Outfit", textAlign: "center" }}>
46+
// This is a sample card with an image.
47+
// </ThemedText>
48+
// </Card>
49+
// </ThemedView>
50+
51+
// In styylesheet:
52+
// cardContainer: {
53+
// borderWidth: 1,
54+
// borderColor: "#ddd",
55+
// borderRadius: 10,
56+
// overflow: "hidden",
57+
// margin: 10,
58+
// padding: 10,
59+
// alignItems: "center",
60+
// },
61+
// cardImage: {
62+
// width: 150,
63+
// height: 100,
64+
// borderRadius: 5,
65+
// marginBottom: 10,
66+
// },
67+
// cardContent: {
68+
// alignItems: "center",
69+
// },

frontend/components/SearchBox.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from "react";
2+
import { TextInput, TextInputProps } from "react-native";
3+
4+
interface SearchBoxProps extends TextInputProps {
5+
value: string;
6+
onChangeText: (text: string) => void;
7+
}
8+
9+
export function SearchBox({ value, onChangeText, ...rest }: SearchBoxProps) {
10+
return <TextInput value={value} onChangeText={onChangeText} {...rest} />;
11+
}
12+
13+
// Example usage:
14+
15+
// import { SearchBox } from "@/components/SearchBox";
16+
17+
// <SearchBox
18+
// placeholder="Type to search..."
19+
// value={searchValue}
20+
// onChangeText={(text) => setSearchValue(text)}
21+
// // Can also pass in other TextInputProps: keyboardType, etc...
22+
// />
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React, { PropsWithChildren } from "react";
2+
import { View, TouchableOpacity, Text, ViewProps } from "react-native";
3+
4+
interface SearchResultItemProps extends PropsWithChildren<ViewProps> {
5+
title: string;
6+
subtitle?: string;
7+
onPress?: () => void;
8+
}
9+
10+
export function SearchResultItem({ title, subtitle, onPress, children, style, ...rest }: SearchResultItemProps) {
11+
const Container = onPress ? TouchableOpacity : View;
12+
13+
return (
14+
// Fill in rest with whatever you want to pass to the View or TouchableOpacity component!
15+
<Container onPress={onPress} style={style} {...rest}>
16+
<Text>{title}</Text>
17+
{subtitle && <Text>{subtitle}</Text>}
18+
{children}
19+
</Container>
20+
);
21+
}
22+
23+
// Example usage:
24+
25+
// import { SearchResultItem } from "@/components/SearchResultItem";
26+
27+
// <SearchResultItem
28+
// title="Spaghetti Bolognese"
29+
// subtitle="A classic Italian favorite!"
30+
// onPress={() => console.log("Item pressed!")}
31+
// />

0 commit comments

Comments
 (0)