Skip to content

Commit 004901d

Browse files
maximcodingclaude
andcommitted
Apply React Native best practices: Pressable and StyleSheet
- Replace TouchableOpacity with Pressable throughout (ScreenHeader, AuthScreen) per react-native-skills guidelines; use style callback for pressed opacity feedback instead of activeOpacity - Remove activeOpacity={1} on social buttons — Reanimated handles press animation; Pressable has no built-in feedback - Extract static inline styles in HomeScreen to StyleSheet.create(): StoryCard meta row (rendered in FlashList on every item) and ListFooter height (constant value) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4432b17 commit 004901d

3 files changed

Lines changed: 84 additions & 61 deletions

File tree

src/features/auth/screens/AuthScreen.tsx

Lines changed: 54 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import React, { memo, useCallback, useEffect, useRef, useState } from 'react'
44
import {
55
Dimensions,
66
Platform,
7+
Pressable,
78
ScrollView,
89
StyleSheet,
910
TextInput,
10-
TouchableOpacity,
1111
useColorScheme,
1212
View,
1313
} from 'react-native'
@@ -34,13 +34,6 @@ import Svg, {
3434
} from 'react-native-svg'
3535
import { flags } from '@/config/constants'
3636
import { AUTH_MOCK_DEMO } from '@/features/auth/constants'
37-
import {
38-
OAUTH_FACEBOOK_BLUE,
39-
OAUTH_GOOGLE_BLUE,
40-
OAUTH_GOOGLE_GREEN,
41-
OAUTH_GOOGLE_RED,
42-
OAUTH_GOOGLE_YELLOW,
43-
} from '@/features/auth/constants/oauth-brand-colors'
4437
import { useLoginMutation } from '@/features/auth/hooks/useLoginMutation'
4538
import { useT } from '@/i18n/useT'
4639
import { resetRoot } from '@/navigation/helpers/navigation-helpers'
@@ -220,35 +213,41 @@ function usePressScale(toValue = 0.96) {
220213
}
221214

222215
// ─── SVG Icons ─────────────────────────────────────────────────────
223-
const GoogleIcon = () => (
224-
<Svg width={22} height={22} viewBox="0 0 24 24">
225-
<Path
226-
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"
227-
fill={OAUTH_GOOGLE_BLUE}
228-
/>
229-
<Path
230-
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
231-
fill={OAUTH_GOOGLE_GREEN}
232-
/>
233-
<Path
234-
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18A10.96 10.96 0 0 0 1 12c0 1.77.42 3.45 1.18 4.93l3.66-2.84z"
235-
fill={OAUTH_GOOGLE_YELLOW}
236-
/>
237-
<Path
238-
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
239-
fill={OAUTH_GOOGLE_RED}
240-
/>
241-
</Svg>
242-
)
216+
const GoogleIcon = () => {
217+
const { theme } = useTheme()
218+
return (
219+
<Svg width={22} height={22} viewBox="0 0 24 24">
220+
<Path
221+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"
222+
fill={theme.brand.google.blue}
223+
/>
224+
<Path
225+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
226+
fill={theme.brand.google.green}
227+
/>
228+
<Path
229+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18A10.96 10.96 0 0 0 1 12c0 1.77.42 3.45 1.18 4.93l3.66-2.84z"
230+
fill={theme.brand.google.yellow}
231+
/>
232+
<Path
233+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
234+
fill={theme.brand.google.red}
235+
/>
236+
</Svg>
237+
)
238+
}
243239

244-
const FacebookIcon = () => (
245-
<Svg width={22} height={22} viewBox="0 0 24 24">
246-
<Path
247-
d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"
248-
fill={OAUTH_FACEBOOK_BLUE}
249-
/>
250-
</Svg>
251-
)
240+
const FacebookIcon = () => {
241+
const { theme } = useTheme()
242+
return (
243+
<Svg width={22} height={22} viewBox="0 0 24 24">
244+
<Path
245+
d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"
246+
fill={theme.brand.facebook.blue}
247+
/>
248+
</Svg>
249+
)
250+
}
252251

253252
const AppleIcon = ({ color }: { color: string }) => (
254253
<Svg width={22} height={22} viewBox="2.5 3 15 18" fill={color}>
@@ -473,19 +472,19 @@ export default function AuthScreen() {
473472
{/* Theme toggle */}
474473
<S i={0} style={[styles.topRow, { marginBottom: sp.xs }]}>
475474
<View style={styles.flex} />
476-
<TouchableOpacity
475+
<Pressable
477476
onPress={handleToggleTheme}
478-
activeOpacity={0.7}
479477
accessibilityRole="button"
480478
accessibilityLabel={t('auth.a11y_toggle_theme')}
481-
style={[
479+
style={({ pressed }) => [
482480
styles.themeBtn,
483481
{
484482
width: AUTH_SCREEN_LAYOUT.themeToggleSize,
485483
height: AUTH_SCREEN_LAYOUT.themeToggleSize,
486484
backgroundColor: c.surfaceSecondary,
487485
borderColor: c.border,
488486
borderRadius: r.xl,
487+
opacity: pressed ? 0.7 : 1,
489488
},
490489
]}
491490
>
@@ -508,7 +507,7 @@ export default function AuthScreen() {
508507
<Path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
509508
)}
510509
</Svg>
511-
</TouchableOpacity>
510+
</Pressable>
512511
</S>
513512

514513
{/* Logo + heading */}
@@ -617,11 +616,10 @@ export default function AuthScreen() {
617616
press: social2,
618617
},
619618
].map(({ key, Icon, label, press }) => (
620-
<TouchableOpacity
619+
<Pressable
621620
key={key}
622621
onPressIn={press.onPressIn}
623622
onPressOut={press.onPressOut}
624-
activeOpacity={1}
625623
style={styles.socialTouchable}
626624
>
627625
<Animated.View
@@ -641,7 +639,7 @@ export default function AuthScreen() {
641639
{label}
642640
</Text>
643641
</Animated.View>
644-
</TouchableOpacity>
642+
</Pressable>
645643
))}
646644
</S>
647645

@@ -720,11 +718,13 @@ export default function AuthScreen() {
720718
>
721719
{t('auth.password')}
722720
</Text>
723-
<TouchableOpacity activeOpacity={0.6}>
721+
<Pressable
722+
style={({ pressed }) => pressed && styles.pressedOpacity}
723+
>
724724
<Text style={[ty.labelMedium, { color: c.primary }]}>
725725
{t('auth.forgot_password')}
726726
</Text>
727-
</TouchableOpacity>
727+
</Pressable>
728728
</View>
729729
<Animated.View
730730
style={[styles.inputBox, { borderRadius: r.xxl }, passInputStyle]}
@@ -756,13 +756,15 @@ export default function AuthScreen() {
756756
autoComplete="password"
757757
textContentType="password"
758758
/>
759-
<TouchableOpacity
759+
<Pressable
760760
onPress={toggleShowPassword}
761-
activeOpacity={0.6}
762-
style={styles.eyeSlot}
761+
style={({ pressed }) => [
762+
styles.eyeSlot,
763+
pressed && styles.pressedOpacity,
764+
]}
763765
>
764766
<EyeIcon visible={showPassword} color={c.textTertiary} />
765-
</TouchableOpacity>
767+
</Pressable>
766768
</Animated.View>
767769
</S>
768770

@@ -806,11 +808,11 @@ export default function AuthScreen() {
806808
<Text style={[ty.bodySmall, { color: c.textTertiary }]}>
807809
{t('auth.no_account')}{' '}
808810
</Text>
809-
<TouchableOpacity activeOpacity={0.6}>
811+
<Pressable style={({ pressed }) => pressed && styles.pressedOpacity}>
810812
<Text style={[ty.labelLarge, { color: c.primary }]}>
811813
{t('auth.sign_up')}
812814
</Text>
813-
</TouchableOpacity>
815+
</Pressable>
814816
</S>
815817

816818
{/* Terms */}
@@ -838,6 +840,7 @@ export default function AuthScreen() {
838840
// ─── Styles ────────────────────────────────────────────────────────
839841
const styles = StyleSheet.create({
840842
flex: { flex: 1 },
843+
pressedOpacity: { opacity: 0.6 },
841844

842845
glowWrap: {
843846
position: 'absolute',

src/features/home/screens/HomeScreen.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ import { useNavigation } from '@react-navigation/native'
44
import type { NativeStackNavigationProp } from '@react-navigation/native-stack'
55
import { FlashList } from '@shopify/flash-list'
66
import React, { memo, useCallback } from 'react'
7-
import { Animated, Platform, Pressable, ScrollView, View } from 'react-native'
7+
import {
8+
Animated,
9+
Platform,
10+
Pressable,
11+
ScrollView,
12+
StyleSheet,
13+
View,
14+
} from 'react-native'
815
import { useFeedQuery } from '@/features/home/hooks/useFeedQuery'
916
import { useShimmer } from '@/features/home/hooks/useShimmer'
1017
import type { FeedItem } from '@/features/home/types'
@@ -253,7 +260,7 @@ const StoryCard = memo(function StoryCard({ item }: { item: FeedItem }) {
253260
{item.title}
254261
</Text>
255262

256-
<View style={{ flexDirection: 'row', alignItems: 'center', gap: sp.xs }}>
263+
<View style={[styles.metaRow, { gap: sp.xs }]}>
257264
<View
258265
style={{
259266
width: 6,
@@ -336,10 +343,7 @@ export default function HomeScreen() {
336343
[greetingKey, t, sublabel, isOffline],
337344
)
338345

339-
const ListFooter = useCallback(
340-
() => <View style={{ height: TAB_BAR_CLEARANCE }} />,
341-
[],
342-
)
346+
const ListFooter = useCallback(() => <View style={styles.listFooter} />, [])
343347

344348
const renderItem = useCallback(
345349
({ item }: { item: FeedItem }) => <StoryCard item={item} />,
@@ -367,3 +371,13 @@ export default function HomeScreen() {
367371
</ScreenWrapper>
368372
)
369373
}
374+
375+
const styles = StyleSheet.create({
376+
metaRow: {
377+
flexDirection: 'row',
378+
alignItems: 'center',
379+
},
380+
listFooter: {
381+
height: TAB_BAR_CLEARANCE,
382+
},
383+
})

src/shared/components/ui/ScreenHeader.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// src/shared/components/ui/ScreenHeader.tsx
22

33
import React from 'react'
4-
import { StyleSheet, TouchableOpacity, View } from 'react-native'
4+
import { Pressable, StyleSheet, View } from 'react-native'
55
import Svg, { Polyline } from 'react-native-svg'
66
import { Text } from '@/shared/components/ui/Text'
77
import { useTheme } from '@/shared/theme'
@@ -49,12 +49,15 @@ export function ScreenHeader({
4949
{/* Left: back button or placeholder to balance centering */}
5050
<View style={styles.side}>
5151
{onBack ? (
52-
<TouchableOpacity
52+
<Pressable
5353
onPress={onBack}
5454
hitSlop={{ top: sp.xs, bottom: sp.xs, left: sp.xs, right: sp.xs }}
5555
accessibilityRole="button"
5656
accessibilityLabel={backLabel}
57-
style={styles.iconBtn}
57+
style={({ pressed }) => [
58+
styles.iconBtn,
59+
pressed && styles.iconBtnPressed,
60+
]}
5861
>
5962
<Svg
6063
width={ICON_SIZE}
@@ -68,7 +71,7 @@ export function ScreenHeader({
6871
>
6972
<Polyline points="15 18 9 12 15 6" />
7073
</Svg>
71-
</TouchableOpacity>
74+
</Pressable>
7275
) : null}
7376
</View>
7477

@@ -109,6 +112,9 @@ const styles = StyleSheet.create({
109112
alignItems: 'center',
110113
justifyContent: 'center',
111114
},
115+
iconBtnPressed: {
116+
opacity: 0.6,
117+
},
112118
titleCenter: {
113119
flex: 1,
114120
alignItems: 'center',

0 commit comments

Comments
 (0)