Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mobile): toggle dark mode #62

Merged
merged 1 commit into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
)
}
21 changes: 21 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,24 @@ 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,
},
headerStyle: {
backgroundColor: theme[colorScheme ?? 'light'].background,
}
}}
/>
</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