Skip to content

Commit

Permalink
feat(mobile): toggle dark mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Quốc Khánh committed Jun 9, 2024
1 parent 9bbc352 commit 75393cf
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 59 deletions.
6 changes: 5 additions & 1 deletion apps/mobile/app/(app)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ export default function TabLayout() {
tabBarShowLabel: false,
tabBarStyle: {
borderTopWidth: 0,
backgroundColor: theme[colorScheme ?? 'light'].background,
},
headerTitleStyle: {
fontFamily: 'Be Vietnam Pro',
fontFamily: 'Be Vietnam Pro Medium',
fontSize: 16,
color: theme[colorScheme ?? 'light'].primary,
},
headerStyle: {
backgroundColor: theme[colorScheme ?? 'light'].background,
}
}}
>
<Tabs.Screen
Expand Down
55 changes: 40 additions & 15 deletions apps/mobile/app/(app)/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import { Badge } from '@/components/Badge'
import { Button } from '@/components/Button'
import { IconButton } from '@/components/IconButton'
import { useAuth, useUser } from '@clerk/clerk-expo'
import { LogOutIcon, PencilIcon } from 'lucide-react-native'
import { ScrollView, Text, View } from 'react-native'
import { Link } from 'expo-router'
import { LogOutIcon, PencilIcon, SwatchBookIcon } from 'lucide-react-native'
import { Alert, ScrollView, Text, View } from 'react-native'

export default function SettingsScreen() {
const { signOut } = useAuth()
const { user } = useUser()

return (
<ScrollView contentContainerClassName="py-4" className="bg-card">
<ScrollView contentContainerClassName="py-4 gap-4" className="bg-card">
<View className="bg-muted rounded-lg mx-6 px-4 py-3 justify-end h-40">
<View className="flex flex-row items-center gap-2 justify-between">
<View className="flex flex-row items-center gap-3">
Expand All @@ -29,25 +30,49 @@ export default function SettingsScreen() {
label="Free"
className="self-start rounded-md mb-1"
/>
<Text className="font-medium">
<Text className="font-medium text-primary">
{user?.fullName ?? user?.primaryEmailAddress?.emailAddress}
</Text>
</View>
</View>
<IconButton icon={PencilIcon} size="sm" variant="ghost" />
</View>
</View>
<Text className='font-sans mx-6 text-muted-foreground mt-6'>
Others
</Text>
<Button
label="Sign out"
variant="ghost"
onPress={() => signOut()}
labelClasses="text-red-500 font-regular"
leftIcon={LogOutIcon}
className="justify-start gap-6 px-6"
/>
<View className="gap-2 mt-4">
<Text className="font-sans mx-6 text-muted-foreground">
App settings
</Text>
<Link href="/appearance" asChild>
<Button
label="Appearance"
variant="ghost"
labelClasses="font-regular"
leftIcon={SwatchBookIcon}
className="justify-start gap-6 px-6 flex-1"
/>
</Link>
</View>
<View className="gap-2 mt-4">
<Text className="font-sans mx-6 text-muted-foreground">Others</Text>
<Button
label="Sign out"
variant="ghost"
onPress={() => Alert.alert('Are you sure you want to sign out?', '', [
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Sign out',
style: 'destructive',
onPress: () => signOut(),
},
])}
labelClasses="text-red-500 font-regular"
leftIcon={LogOutIcon}
className="justify-start gap-6 px-6"
/>
</View>
</ScrollView>
)
}
18 changes: 18 additions & 0 deletions apps/mobile/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { useAuth } from '@clerk/clerk-expo'
import { Redirect, SplashScreen, Stack } from 'expo-router'
import { useEffect } from 'react'

export default function AuthenticatedLayout() {
const { isLoaded, isSignedIn } = useAuth()
const colorScheme = useColorScheme()

useEffect(() => {
if (isLoaded) {
Expand All @@ -23,6 +26,21 @@ export default function AuthenticatedLayout() {
presentation: 'modal',
}}
/>
<Stack.Screen
name="appearance"
options={{
headerShown: true,
headerBackTitleVisible: false,
headerTintColor: theme[colorScheme ?? 'light'].primary,
headerTitle: 'Appearance',
headerShadowVisible: false,
headerTitleStyle: {
fontFamily: 'Be Vietnam Pro Medium',
fontSize: 16,
color: theme[colorScheme ?? 'light'].primary,
}
}}
/>
</Stack>
)
}
30 changes: 30 additions & 0 deletions apps/mobile/app/(app)/appearance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Tabs, TabsList, TabsTrigger } from '@/components/Tabs'
import { MoonStarIcon, SmartphoneIcon, SunIcon } from 'lucide-react-native'
import { useColorScheme } from 'nativewind'
import { ScrollView, Text } from 'react-native'

export default function AppearanceScreen() {
const { colorScheme, setColorScheme } = useColorScheme()

return (
<ScrollView className="bg-card" contentContainerClassName="px-6 py-3">
<Text className="font-sans text-primary font-medium text-base">
App theme
</Text>
<Text className="font-sans text-muted-foreground text-sm mb-4">
Choose a preferred theme for the 6pm
</Text>
<Tabs
defaultValue={colorScheme || 'light'}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
onChange={(value: any) => setColorScheme(value)}
>
<TabsList>
<TabsTrigger value="light" title="Light" icon={SunIcon} />
<TabsTrigger value="dark" title="Dark" icon={MoonStarIcon} />
<TabsTrigger value="system" title="System" icon={SmartphoneIcon} />
</TabsList>
</Tabs>
</ScrollView>
)
}
4 changes: 2 additions & 2 deletions apps/mobile/app/(auth)/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ export default function LoginScreen() {
automaticallyAdjustKeyboardInsets
keyboardShouldPersistTaps="handled"
>
<Text className="text-3xl font-semibold font-sans">
<Text className="text-3xl text-primary font-semibold font-sans">
Manage your expense seamlessly
</Text>
<Text className="text-muted-foreground font-sans">
Let <Text className="text-primary">6pm</Text> a good time to spend
</Text>
<AuthIllustration className="h-[326px] my-16" />
<AuthIllustration className="h-[326px] my-16 text-primary" />
<View className="flex flex-col gap-3">
<AppleAuthButton />
<GoogleAuthButton />
Expand Down
9 changes: 6 additions & 3 deletions apps/mobile/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type VariantProps, cva } from 'class-variance-authority'
import { Text, TouchableOpacity } from 'react-native'

import { forwardRef } from 'react'
import type { SvgProps } from 'react-native-svg'
import { cn } from '../lib/utils'

Expand Down Expand Up @@ -59,7 +60,8 @@ export interface ButtonProps
leftIcon?: React.ComponentType<SvgProps>
rightIcon?: React.ComponentType<SvgProps>
}
function Button({

const Button = forwardRef(function ({
label,
labelClasses,
className,
Expand All @@ -69,9 +71,10 @@ function Button({
rightIcon: RightIcon,
disabled,
...props
}: ButtonProps) {
}: ButtonProps, ref: React.ForwardedRef<TouchableOpacity>) {
return (
<TouchableOpacity
ref={ref}
activeOpacity={0.8}
className={cn(
buttonVariants({ variant, size, className }),
Expand Down Expand Up @@ -105,6 +108,6 @@ function Button({
)}
</TouchableOpacity>
)
}
})

export { Button, buttonVariants, buttonTextVariants }
27 changes: 21 additions & 6 deletions apps/mobile/components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createContext, useContext, useState } from 'react'
import { Text, TouchableOpacity, View } from 'react-native'

import type { LucideIcon } from 'lucide-react-native'
import { cn } from '../lib/utils'

interface TabsContextProps {
Expand All @@ -10,18 +11,24 @@ interface TabsContextProps {
const TabsContext = createContext<TabsContextProps>({
activeTab: '',
// biome-ignore lint/suspicious/noEmptyBlockStatements: <explanation>
setActiveTab: () => {},
setActiveTab: () => { },
})

interface TabsProps {
defaultValue: string
children: React.ReactNode
onChange?: (value: string) => void
}
function Tabs({ defaultValue, children }: TabsProps) {
function Tabs({ defaultValue, children, onChange }: TabsProps) {
const [activeTab, setActiveTab] = useState(defaultValue)

const handleChange = (value: string) => {
setActiveTab(value)
onChange?.(value)
}

return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<TabsContext.Provider value={{ activeTab, setActiveTab: handleChange }}>
{children}
</TabsContext.Provider>
)
Expand All @@ -33,7 +40,7 @@ function TabsList({
}: React.ComponentPropsWithoutRef<typeof View>) {
return (
<View
className={cn('flex flex-row justify-center', className)}
className={cn('flex flex-row justify-center p-1 border border-border rounded-lg', className)}
{...props}
/>
)
Expand All @@ -44,28 +51,36 @@ interface TabsTriggerProps
value: string
title: string
textClasses?: string
icon?: LucideIcon
}
function TabsTrigger({
value,
title,
className,
textClasses,
icon: Icon,
...props
}: TabsTriggerProps) {
const { activeTab, setActiveTab } = useContext(TabsContext)

return (
<TouchableOpacity
className={cn('px-8 py-3 rounded-md w-1/2 bg-muted', {
activeOpacity={0.8}
className={cn('px-3 py-2.5 flex-row gap-2 items-center justify-center rounded-md flex-1', {
'bg-foreground': activeTab === value,
className,
})}
onPress={() => setActiveTab(value)}
{...props}
>
{Icon && <Icon className={cn(
'w-5 h-5 text-muted-foreground',
{ 'text-background': activeTab === value },
textClasses,
)} />}
<Text
className={cn(
'font-medium text-center text-muted-foreground',
'font-medium font-sans text-center text-muted-foreground',
{ 'text-background': activeTab === value },
textClasses,
)}
Expand Down
Loading

0 comments on commit 75393cf

Please sign in to comment.