frontend: add screen layout
Parents:
7a3f55e2 file(s) changed
- frontend/components/ui/ScreenLayout.tsx +158 -0
- frontend/components/ui/icon-symbol.tsx +1 -0
frontend/components/ui/ScreenLayout.tsx
@@ -0,0 +1,158 @@
1 + import React from 'react';
2 +
3 + import {
4 + View,
5 + ScrollView,
6 + StyleSheet,
7 + Image,
8 + Pressable,
9 + StatusBar,
10 + } from 'react-native';
11 +
12 + import { SafeAreaView } from 'react-native-safe-area-context';
13 + import { IconSymbol } from '@/components/ui/icon-symbol';
14 + import { useThemeColor } from '@/hooks/use-theme-color';
15 + import { Typography, Spacing, BorderRadius } from '@/constants/theme'
16 + import { ThemedText } from '@/components/misc/themed-text'
17 +
18 + interface ScreenLayoutProps {
19 + children: React.ReactNode;
20 + userName?: string;
21 + userAvatar?: string;
22 + onNotificationPress?: () => void;
23 + showNotificationBadge?: boolean;
24 + scrollEnabled?: boolean;
25 + }
26 +
27 + export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
28 + children,
29 + userName = 'Usuário',
30 + userAvatar,
31 + onNotificationPress,
32 + showNotificationBadge = false,
33 + scrollEnabled = true,
34 + }) => {
35 + const textColor = useThemeColor({}, 'text');
36 + const backgroundColor = useThemeColor({}, 'background');
37 + const surfaceColor = useThemeColor({}, 'surface');
38 + const dividerColor = useThemeColor({}, 'divider');
39 + const accentBlue = useThemeColor({}, 'accentBlue');
40 + const tintColor = useThemeColor({}, 'tint');
41 +
42 + const styles = StyleSheet.create({
43 + container: {
44 + flex: 1,
45 + backgroundColor: backgroundColor,
46 + },
47 + safeAreaView: {
48 + flex: 1,
49 + backgroundColor: backgroundColor,
50 + },
51 + header: {
52 + paddingHorizontal: Spacing.containerPadding,
53 + paddingVertical: 12,
54 + backgroundColor: surfaceColor,
55 + borderBottomWidth: 1,
56 + borderBottomColor: dividerColor,
57 + flexDirection: 'row',
58 + justifyContent: 'space-between',
59 + alignItems: 'center',
60 + },
61 + headerLeft: {
62 + flexDirection: 'row',
63 + alignItems: 'center',
64 + gap: Spacing.inlineGapSm,
65 + },
66 + avatar: {
67 + width: 40,
68 + height: 40,
69 + borderRadius: 20,
70 + backgroundColor: accentBlue,
71 + },
72 + userName: {
73 + fontSize: Typography.bodyLg.fontSize,
74 + fontWeight: '600',
75 + color: textColor,
76 + },
77 + notificationButton: {
78 + width: 40,
79 + height: 40,
80 + borderRadius: BorderRadius.md,
81 + justifyContent: 'center',
82 + alignItems: 'center',
83 + activeOpacity: 0.7,
84 + },
85 + notificationBadge: {
86 + position: 'absolute',
87 + top: 4,
88 + right: 4,
89 + width: 8,
90 + height: 8,
91 + borderRadius: 4,
92 + backgroundColor: tintColor,
93 + },
94 + scrollContent: {
95 + paddingHorizontal: Spacing.containerPadding,
96 + paddingVertical: Spacing.sectionGap,
97 + },
98 + });
99 +
100 + return (
101 + <View style={styles.container}>
102 + <StatusBar
103 + barStyle="dark-content"
104 + backgroundColor={surfaceColor}
105 + />
106 + <SafeAreaView style={styles.safeAreaView}>
107 + <View style={styles.header}>
108 + <View style={styles.headerLeft}>
109 + {userAvatar ? (
110 + <Image
111 + source={{ uri: userAvatar }}
112 + style={styles.avatar}
113 + />
114 + ) : (
115 + <View style={styles.avatar} />
116 + )}
117 + <View>
118 + <ThemedText style={styles.userName}>
119 + Olá, {userName}
120 + </ThemedText>
121 + </View>
122 + </View>
123 +
124 + <Pressable
125 + style={styles.notificationButton}
126 + onPress={onNotificationPress}
127 + hitSlop={8}
128 + >
129 + <View
130 + style={{
131 + fontSize: 24,
132 + color: textColor,
133 + }}
134 + >
135 + <IconSymbol size={24} name="notifications" color={textColor} />
136 + </View>
137 + {showNotificationBadge && (
138 + <View style={styles.notificationBadge} />
139 + )}
140 + </Pressable>
141 + </View>
142 +
143 + <ScrollView
144 + scrollEnabled={scrollEnabled}
145 + showsVerticalScrollIndicator={false}
146 + contentContainerStyle={scrollEnabled ? undefined : { flex: 1 }}
147 + scrollEventThrottle={16}
148 + >
149 + <View style={styles.scrollContent}>
150 + {children}
151 + </View>
152 + </ScrollView>
153 + </SafeAreaView>
154 + </View>
155 + );
156 + };
157 +
158 + export default ScreenLayout;
frontend/components/ui/icon-symbol.tsx
@@ -18,6 +18,7 @@ * since we won't have iOS stuff
18 18 */
19 19 const MAPPING = {
20 20 'house.fill': 'home',
21 + 'notifications': 'notifications',
21 22 'add-circle': 'add-circle',
22 23 'history': 'history',
23 24 'lightbulb-outline': 'lightbulb-outline',