Skip to content

Add react native reusables#1

Merged
chakrihacker merged 7 commits into
mainfrom
feat/check-native-wind
Jul 18, 2025
Merged

Add react native reusables#1
chakrihacker merged 7 commits into
mainfrom
feat/check-native-wind

Conversation

@chakrihacker

@chakrihacker chakrihacker commented Jul 18, 2025

Copy link
Copy Markdown
Contributor

PR Type

Enhancement


Description

  • Migrate from react-native-unistyles to NativeWind and react-native-reusables

  • Add persistent color scheme management with AsyncStorage

  • Implement new UI components with Tailwind CSS styling

  • Update authentication components with improved design


Diagram Walkthrough

flowchart LR
  A["react-native-unistyles"] --> B["NativeWind + Tailwind CSS"]
  C["Custom UI Components"] --> D["react-native-reusables"]
  E["Theme System"] --> F["CSS Variables + AsyncStorage"]
  B --> G["Enhanced Styling"]
  D --> G
  F --> G
Loading

File Walkthrough

Relevant files

Summary by CodeRabbit

  • New Features

    • Introduced a responsive navigation menu, theme toggle, and avatar components.
    • Added new About page.
    • Implemented persistent theme switching with light/dark mode support.
    • Added utility hooks and helpers for color scheme, navigation bar, and storage.
  • Refactor

    • Migrated styling from react-native-unistyles to NativeWind and Tailwind CSS utility classes.
    • Refactored core UI components (Button, Card, Input, Text) to use class-based styling and variants.
    • Simplified and modernized layouts and authentication screens.
    • Streamlined tab bar configuration and replaced complex explore tab with a simple placeholder.
  • Bug Fixes

    • Improved error handling in password reset flow.
  • Chores

    • Updated dependencies and configuration for NativeWind and Tailwind CSS integration.
    • Enhanced TypeScript path aliases and Babel/Metro build setup.
    • Removed obsolete components, hooks, and documentation related to unistyles.
    • Added global CSS with theme variables supporting light and dark modes.

@sourcery-ai

sourcery-ai Bot commented Jul 18, 2025

Copy link
Copy Markdown

Reviewer's Guide

This pull request replaces the existing react-native-unistyles-based styling system with NativeWind’s Tailwind CSS approach, introduces a suite of unstyled reusable component primitives via @rn-primitives, migrates the PricingPage to utility-class styling, revamps application layouts for theme and portal support, and updates project dependencies and configuration accordingly.

Class diagram for new reusable UI primitives

classDiagram
    class Button {
        +variant: string
        +size: string
        +className: string
        +onPress()
    }
    class Text {
        +className: string
        +asChild: boolean
    }
    class Card {
        +className: string
    }
    class CardHeader
    class CardTitle
    class CardDescription
    class CardContent
    class CardFooter
    class Input {
        +className: string
        +placeholderClassName: string
    }
    class ActivityIndicator
    class Avatar
    class AvatarImage
    class AvatarFallback
    class NavigationMenu
    class NavigationMenuList
    class NavigationMenuItem
    class NavigationMenuTrigger
    class NavigationMenuContent
    class NavigationMenuLink
    class NavigationMenuIndicator
    class NavigationMenuViewport

    Card --> CardHeader
    Card --> CardTitle
    Card --> CardDescription
    Card --> CardContent
    Card --> CardFooter
    Avatar --> AvatarImage
    Avatar --> AvatarFallback
    NavigationMenu --> NavigationMenuList
    NavigationMenuList --> NavigationMenuItem
    NavigationMenuItem --> NavigationMenuTrigger
    NavigationMenuItem --> NavigationMenuContent
    NavigationMenuContent --> NavigationMenuLink
    NavigationMenuList --> NavigationMenuIndicator
    NavigationMenuList --> NavigationMenuViewport
Loading

Class diagram for theme and color scheme management

classDiagram
    class useColorScheme {
        +colorScheme: "light"|"dark"|"system"
        +isDarkColorScheme: boolean
        +setColorScheme(newColorScheme)
        +toggleColorScheme()
        +isLoaded: boolean
    }
    class storage {
        +getItem(key)
        +setItem(key, value)
        +removeItem(key)
    }
    useColorScheme --> storage
Loading

File-Level Changes

Change Details Files
Styling infrastructure overhaul
  • Added global.css and tailwind.config.js for Tailwind theming and utility classes
  • Updated babel.config.js to include nativewind presets and metro.config.js with withNativeWind wrapper
  • Created cn utility in src/lib/utils.ts for className merging
  • Removed react-native-unistyles plugin references
global.css
tailwind.config.js
babel.config.js
metro.config.js
src/lib/utils.ts
Introduce reusable UI primitives with react-native-reusables
  • Implemented Text, Button, Card, Input, ActivityIndicator, Avatar, NavigationMenu, and ThemeToggle components
  • Leveraged @rn-primitives packages (slot, portal, avatar, navigation-menu) and cva for variant props
  • Added useColorScheme hook, storage utilities, and iconWithClassName for consistent theming
  • Wrapped new Button and Card primitives with TextClassContext for nested styling
src/components/ui/Text.tsx
src/components/ui/Button.tsx
src/components/ui/Card.tsx
src/components/ui/Input.tsx
src/components/ui/activity-indicator.tsx
src/components/ui/avatar.tsx
src/components/ui/navigation-menu.tsx
src/components/ThemeToggle.tsx
src/lib/useColorScheme.tsx
src/lib/storage.ts
src/lib/android-navigation-bar.ts
src/lib/icons
nativewind-env.d.ts
Migrate PricingPage to Tailwind utility classes
  • Removed StyleSheet.create and useUnistyles hook
  • Replaced style arrays with className strings and cn utility on View, Text, TouchableOpacity
  • Applied responsive and conditional classes for layout, badges, buttons, and typography
  • Stripped out legacy styles block at file end
src/app/pricing.tsx
Revise application layout and screens for new styling and theme support
  • Updated RootLayout to use useIsomorphicLayoutEffect, PortalHost, and Android navigation bar setup
  • Simplified tab and auth layouts to use new primitives (View, Text) and removed custom Themed components
  • Refactored Explore, NotFound, SignIn, SignUp screens accordingly
  • Integrated ThemeToggle and removed legacy Collapsible, Parallax, and unistyles usage
src/app/_layout.tsx
src/app/(tabs)/_layout.tsx
src/app/(tabs)/explore.tsx
src/app/(auth)/_layout.tsx
src/app/(auth)/sign-in.tsx
src/app/(auth)/sign-up.tsx
src/app/+not-found.tsx
Update dependencies and TypeScript configuration
  • Added nativewind, tailwindcss, class-variance-authority, clsx, rn-primitives, expo-navigation-bar, and related packages
  • Updated tsconfig.json with '~/‘ path aliases and added nativewind-env.d.ts
  • Removed react-native-unistyles entries from package.json
package.json
tsconfig.json
nativewind-env.d.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Error Handling

The storage wrapper only logs errors to console without propagating them or providing fallback mechanisms. This could lead to silent failures in critical operations like theme persistence.

	} catch (error) {
		console.error("Error getting item from storage:", error);
		return null;
	}
},

async setItem(key: string, value: string): Promise<void> {
	try {
		await AsyncStorage.setItem(key, value);
	} catch (error) {
		console.error("Error setting item in storage:", error);
	}
},

async removeItem(key: string): Promise<void> {
	try {
		await AsyncStorage.removeItem(key);
	} catch (error) {
		console.error("Error removing item from storage:", error);
	}
},
Performance Concern

The navigation menu uses complex animations and multiple useAnimatedStyle hooks that could impact performance, especially on lower-end devices. The component also has deep nesting and multiple context dependencies.

const progress = useDerivedValue(() =>
  value === itemValue ? withTiming(1, { duration: 250 }) : withTiming(0, { duration: 200 })
);
const chevronStyle = useAnimatedStyle(() => ({
  transform: [{ rotate: `${progress.value * 180}deg` }],
  opacity: interpolate(progress.value, [0, 1], [1, 0.8], Extrapolation.CLAMP),
}));

return (
  <NavigationMenuPrimitive.Trigger
    className={cn(
      navigationMenuTriggerStyle(),
      'web:group gap-1.5',
      value === itemValue && 'bg-accent',
      className
    )}
    {...props}
  >
    {children}
    <Animated.View style={chevronStyle}>
      <ChevronDown
        size={12}
        className={cn('relative text-foreground h-3 w-3 web:transition web:duration-200')}
        aria-hidden={true}
      />
    </Animated.View>
  </NavigationMenuPrimitive.Trigger>
);
Code Maintainability

The pricing component has extremely long className strings with complex conditional logic that makes it difficult to read and maintain. Consider extracting these into separate style functions or using more structured approaches.

		<View
			className={`
                bg-white rounded-2xl border border-gray-200 p-6 w-full sm:w-[350px] max-w-[350px]
                shadow-sm shadow-black/10
                ${tier.popular ? "border-2 border-purple-500 bg-purple-50 scale-105" : ""}
            `}
		>
			{tier.popular && (
				<View className="absolute top-0 right-0 bg-purple-500 rounded-bl-xl px-3 py-1.5 z-10">
					<Text className="text-white text-xs font-semibold">Most Popular</Text>
				</View>
			)}

			<View className="mb-6">
				<View className="flex-row justify-between items-center mb-4">
					<View
						className={`
                            w-12 h-12 rounded-full bg-gray-200 items-center justify-center
                            ${tier.popular ? "bg-purple-100" : ""}
                        `}
					>
						<Text className="text-2xl">{tier.icon}</Text>
					</View>
					{tier.savings && (
						<View
							className={`
                                rounded px-2 py-1
                                ${tier.popular ? "bg-green-500" : "bg-orange-500"}
                            `}
						>
							<Text className="text-white text-xs font-semibold">
								{tier.savings}
							</Text>
						</View>
					)}
				</View>

				<Text
					className={`
                        text-2xl font-bold text-gray-900 mb-2
                        ${tier.popular ? "text-purple-600" : ""}
                    `}
				>
					{tier.name}
				</Text>

				<View className="flex-row items-baseline mb-2">
					<Text
						className={`
                            text-4xl font-bold text-gray-900
                            ${tier.popular ? "text-purple-600" : ""}
                        `}
					>
						{tier.price}
					</Text>
					<Text className="text-base text-gray-500 ml-2">{tier.period}</Text>
				</View>

				{tier.originalPrice && (
					<Text className="text-base text-gray-500 line-through mb-2">
						{tier.originalPrice}
					</Text>
				)}

				<Text className="text-sm text-gray-500 leading-5">
					{tier.description}
				</Text>
			</View>

			<View className="gap-6">
				<TouchableOpacity
					className={`
                        rounded-lg py-4 px-6 items-center
                        ${
													tier.popular
														? "bg-purple-600"
														: "bg-transparent border border-gray-200"
												}
                    `}
					onPress={() => console.log(`Selected: ${tier.name}`)}
				>
					<Text
						className={`
                            text-base font-semibold
                            ${tier.popular ? "text-white" : "text-gray-900"}
                        `}
					>
						{tier.buttonText}
					</Text>
				</TouchableOpacity>

				<View className="gap-3">
					{tier.features.map((feature, index) => (
						<View
							key={`${tier.id}-${index}`}
							className="flex-row items-center gap-3"
						>
							<View className="w-5 h-5 rounded-full bg-green-500 items-center justify-center">
								<Text className="text-white text-xs font-semibold"></Text>
							</View>
							<Text className="text-sm text-gray-600 flex-1">{feature}</Text>
						</View>
					))}
				</View>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @chakrihacker - I've reviewed your changes - here's some feedback:

  • Ensure your custom primitives forward refs (e.g., Text, Button, Input) to their underlying native elements to support imperative API use.
  • Extract repeating Tailwind className strings into shared cva variants (for cards, buttons, etc.) to reduce duplication and improve maintainability.
  • Review and add accessibilityRole/accessibilityLabel and proper focus management to interactive components (Button, Input, NavigationMenu) to maintain a11y compliance.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Ensure your custom primitives forward refs (e.g., Text, Button, Input) to their underlying native elements to support imperative API use.
- Extract repeating Tailwind className strings into shared cva variants (for cards, buttons, etc.) to reduce duplication and improve maintainability.
- Review and add accessibilityRole/accessibilityLabel and proper focus management to interactive components (Button, Input, NavigationMenu) to maintain a11y compliance.

## Individual Comments

### Comment 1
<location> `src/app/(auth)/forgot-password.tsx:1` </location>
<code_context>
-import React, { useState } from "react";
-import { Alert, Text, TextInput, TouchableOpacity, View } from "react-native";
-import { StyleSheet } from "react-native-unistyles";
+import { useState } from "react";
+import {
+	Alert,
</code_context>

<issue_to_address>
ForgotPassword still uses StyleSheet, which is inconsistent with the new className-based approach.

Please update forgot-password.tsx to use className and Tailwind classes instead of StyleSheet.

Suggested implementation:

```typescript
import {
	Alert,
	Text,
	TextInput,
	TouchableOpacity,
	View,
} from "react-native";

```

```typescript
export default function ForgotPassword() {
	const [email, setEmail] = useState("");

```

You will also need to:
1. Replace all usages of `style={styles.something}` with `className="..."` and use Tailwind CSS classes for styling.
2. Remove the `const styles = StyleSheet.create({ ... })` block entirely from the file.
3. If you use any custom styles, translate them to equivalent Tailwind classes.

Example:
Replace
```jsx
<View style={styles.container}>
```
with
```jsx
<View className="flex-1 justify-center items-center bg-white">
```
and so on for all styled components in the file.
</issue_to_address>

### Comment 2
<location> `src/components/ui/Text.tsx:5` </location>
<code_context>

 Example component pattern:
 ```tsx
-const styles = StyleSheet.create((theme) => ({
-  button: {
-    backgroundColor: theme.colors.primary,
</code_context>

<issue_to_address>
Consider keeping the new implementation as is, since it is already significantly simpler than the previous version.

I don’t see any actionable simplifications here – the new implementation is already much leaner than the previous variant-heavy approach. No changes recommended.
</issue_to_address>

### Comment 3
<location> `src/components/ui/Button.tsx:7` </location>
<code_context>
+import { cva, type VariantProps } from "class-variance-authority";
+import { Pressable } from "react-native";
+
+const buttonVariants = cva(
+  "flex items-center justify-center rounded-md",
+  {
</code_context>

<issue_to_address>
Consider merging `buttonVariants` and `buttonTextVariants` into a single CVA with slots to simplify the component structure.

Consider collapsing both `buttonVariants` and `buttonTextVariants` into a single CVA withslotsso you dont need a contextprovider or two separate calls.

```ts
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { Pressable, Text } from "react-native";
import { cn } from "~/lib/utils";

const button = cva({
  slots: {
    base: "group flex items-center justify-center rounded-md …", 
    text: "whitespace-nowrap text-sm font-medium web:transition-colors"
  },
  variants: {
    variant: {
      default: {
        base: "bg-primary web:hover:opacity-90 active:opacity-90",
        text: "text-primary-foreground",
      },
      destructive: {
        base: "bg-destructive web:hover:opacity-90 active:opacity-90",
        text: "text-destructive-foreground",
      },
      // …other variants
    },
    size: {
      default: {
        base: "h-10 px-4 py-2 native:h-12 native:px-5 native:py-3",
        text: "",
      },
      sm: { base: "h-9 rounded-md px-3", text: "" },
      lg: { base: "h-11 rounded-md px-8 native:h-14", text: "native:text-lg" },
      icon: { base: "h-10 w-10", text: "" },
    },
  },
  defaultVariants: {
    variant: "default",
    size: "default",
  },
});

type ButtonProps = React.ComponentProps<typeof Pressable> & VariantProps<typeof button>;

function Button({ variant, size, className, children, disabled, ...props }: ButtonProps) {
  const { base, text } = button({ variant, size, className });

  return (
    <Pressable
      className={cn(disabled && "opacity-50 web:pointer-events-none", base)}
      disabled={disabled}
      {...props}
    >
      {typeof children === "string" ? (
        <Text className={text}>{children}</Text>
      ) : (
        React.Children.map(children, child => {
          // If user already wraps their own <Text>, pass the text slot via className:
          if (React.isValidElement(child) && child.type === Text) {
            return React.cloneElement(child, { className: cn(child.props.className, text) });
          }
          return child;
        })
      )}
    </Pressable>
  );
}

export { Button };
export type { ButtonProps };
```

This:

- Merges your two CVA calls into one.
- Removes the `TextClassContext` indirection.
- Keeps the same `variant`/`size` API, handles disabled state, and preserves child-text styling.
</issue_to_address>

### Comment 4
<location> `src/components/ui/Card.tsx:6` </location>
<code_context>
-import React from "react";
-import { Text, type TextProps, View, type ViewProps } from "react-native";
-import { StyleSheet } from "react-native-unistyles";
+function Card({
+  className,
+  ...props
</code_context>

<issue_to_address>
Consider using a factory/helper function to generate the Card components and eliminate repetitive boilerplate.

You can collapse all of these into a small factory/helper and then just pass in your component and default classes. That gets rid of the repetitive `forwardRef` + `cn(...)` boilerplate:

```tsx
// components/Card.tsx

import * as React from 'react';
import { Text, View } from 'react-native';
import { cn } from '~/lib/utils';

type WithClassName<P> = P & { className?: string };

function createCardSlot<
  C extends React.ComponentType<any>
>(Component: C, defaultClasses: string) {
  type Props = WithClassName<React.ComponentPropsWithoutRef<C>>;
  const Slot = React.forwardRef<any, Props>(
    ({ className, ...props }, ref) => (
      <Component
        ref={ref}
        className={cn(defaultClasses, className)}
        {...props}
      />
    )
  );
  return Slot;
}

export const Card = createCardSlot(
  View,
  'rounded-lg border border-border bg-card shadow-sm shadow-foreground/10'
);
export const CardHeader = createCardSlot(
  View,
  'flex flex-col space-y-1.5 p-6'
);
export const CardTitle = createCardSlot(
  Text,
  'text-2xl font-semibold leading-none tracking-tight text-card-foreground'
);
export const CardDescription = createCardSlot(
  Text,
  'text-sm text-muted-foreground'
);
export const CardContent = createCardSlot(
  View,
  'p-6 pt-0'
);
export const CardFooter = createCardSlot(
  View,
  'flex flex-row items-center p-6 pt-0'
);
```

This preserves all functionality but:

1. Eliminates six copies of identical `forwardRef` + `cn(...)` patterns.  
2. Keeps each slots default classes collocated in a single concise spot.  
3. Makes adding a new slot as easy as one line (no more boilerplate).
</issue_to_address>

### Comment 5
<location> `src/app/_layout.tsx:43` </location>
<code_context>
+	default: noop,
+});

 export default function RootLayout() {
-	const colorScheme = useColorScheme();
-	const [loaded] = useFonts({
</code_context>

<issue_to_address>
Consider extracting all setup logic into a single custom hook to keep RootLayout focused on rendering.

Here’s one way to collapse all of that mounting, font‐loading, color‐scheme and platform setup into a single hook so your `RootLayout` stays focused on rendering:

```ts
// hooks/useAppSetup.ts
import { useIsomorphicLayoutEffect } from "@/src/hooks/useIsomorphicLayout";
import { useFonts } from "expo-font";
import { Appearance, Platform } from "react-native";
import { setAndroidNavigationBar } from "../lib/android-navigation-bar";
import { useColorScheme } from "@/src/lib/useColorScheme";
import { DarkTheme, DefaultTheme, type Theme } from "@react-navigation/native";
import { NAV_THEME } from "@/src/lib/constants";

const LIGHT_THEME: Theme = { ...DefaultTheme, colors: NAV_THEME.light };
const DARK_THEME: Theme = { ...DarkTheme,  colors: NAV_THEME.dark  };

export function useAppSetup() {
  // 1. load fonts
  const [fontsLoaded] = useFonts({
    SpaceMono: require("../../assets/fonts/SpaceMono-Regular.ttf"),
  });

  // 2. load persisted color‐scheme
  const { isDarkColorScheme, isLoaded: schemeLoaded } = useColorScheme();

  // 3. platform side‐effects
  useIsomorphicLayoutEffect(() => {
    if (Platform.OS === "web") {
      document.documentElement.classList.add("bg-background");
    }
    if (Platform.OS === "android") {
      setAndroidNavigationBar(Appearance.getColorScheme() ?? "light");
    }
  }, []);

  const ready = fontsLoaded && schemeLoaded;
  return {
    ready,
    theme: isDarkColorScheme ? DARK_THEME : LIGHT_THEME,
    statusBarStyle: isDarkColorScheme ? "light" : "dark",
  };
}
```

And then your `RootLayout` becomes:

```tsx
// RootLayout.tsx
import { SafeAreaView } from "react-native-safe-area-context";
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import { ThemeProvider } from "@react-navigation/native";
import { Toaster } from "@/src/lib/sonner/sonner";
import { PortalHost } from "@rn-primitives/portal";
import { useAppSetup } from "@/src/hooks/useAppSetup";

export default function RootLayout() {
  const { ready, theme, statusBarStyle } = useAppSetup();

  if (!ready) return null;

  return (
    <SafeAreaView style={{ flex: 1, flexDirection: "column" }}>
      <ThemeProvider value={theme}>
        <Stack>
          <Stack.Screen name="(auth)" options={{ headerShown: false }} />
          <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          <Stack.Screen name="+not-found" />
        </Stack>
        <StatusBar style={statusBarStyle} />
      </ThemeProvider>
      <Toaster />
      <PortalHost name="root-portal" />
    </SafeAreaView>
  );
}
```

What this does:

• Removes the separate `useSetWebBackgroundClassName`, `useSetAndroidNavigationBar`, `noop`, `hasMounted`, extra refs/state  
• Collocates all “ready” signals (fonts + scheme) and side-effects into one hook  
• Keeps your rendering code in `RootLayout` very declarative and minimal.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

import React, { useState } from "react";
import { Alert, Text, TextInput, TouchableOpacity, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
import { useState } from "react";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: ForgotPassword still uses StyleSheet, which is inconsistent with the new className-based approach.

Please update forgot-password.tsx to use className and Tailwind classes instead of StyleSheet.

Suggested implementation:

import {
	Alert,
	Text,
	TextInput,
	TouchableOpacity,
	View,
} from "react-native";
export default function ForgotPassword() {
	const [email, setEmail] = useState("");

You will also need to:

  1. Replace all usages of style={styles.something} with className="..." and use Tailwind CSS classes for styling.
  2. Remove the const styles = StyleSheet.create({ ... }) block entirely from the file.
  3. If you use any custom styles, translate them to equivalent Tailwind classes.

Example:
Replace

<View style={styles.container}>

with

<View className="flex-1 justify-center items-center bg-white">

and so on for all styled components in the file.

import { Text as RNText } from 'react-native';
import { cn } from '~/lib/utils';

const styles = StyleSheet.create((theme) => ({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider keeping the new implementation as is, since it is already significantly simpler than the previous version.

I don’t see any actionable simplifications here – the new implementation is already much leaner than the previous variant-heavy approach. No changes recommended.

import { TextClassContext } from '~/components/ui/text';

type ButtonVariants = UnistylesVariants<typeof styles>;
const buttonVariants = cva(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider merging buttonVariants and buttonTextVariants into a single CVA with slots to simplify the component structure.

Consider collapsing both buttonVariants and buttonTextVariants into a single CVA with “slots” so you don’t need a context‐provider or two separate calls.

import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { Pressable, Text } from "react-native";
import { cn } from "~/lib/utils";

const button = cva({
  slots: {
    base: "group flex items-center justify-center rounded-md …", 
    text: "whitespace-nowrap text-sm font-medium web:transition-colors"
  },
  variants: {
    variant: {
      default: {
        base: "bg-primary web:hover:opacity-90 active:opacity-90",
        text: "text-primary-foreground",
      },
      destructive: {
        base: "bg-destructive web:hover:opacity-90 active:opacity-90",
        text: "text-destructive-foreground",
      },
      // …other variants
    },
    size: {
      default: {
        base: "h-10 px-4 py-2 native:h-12 native:px-5 native:py-3",
        text: "",
      },
      sm: { base: "h-9 rounded-md px-3", text: "" },
      lg: { base: "h-11 rounded-md px-8 native:h-14", text: "native:text-lg" },
      icon: { base: "h-10 w-10", text: "" },
    },
  },
  defaultVariants: {
    variant: "default",
    size: "default",
  },
});

type ButtonProps = React.ComponentProps<typeof Pressable> & VariantProps<typeof button>;

function Button({ variant, size, className, children, disabled, ...props }: ButtonProps) {
  const { base, text } = button({ variant, size, className });

  return (
    <Pressable
      className={cn(disabled && "opacity-50 web:pointer-events-none", base)}
      disabled={disabled}
      {...props}
    >
      {typeof children === "string" ? (
        <Text className={text}>{children}</Text>
      ) : (
        React.Children.map(children, child => {
          // If user already wraps their own <Text>, pass the text slot via className:
          if (React.isValidElement(child) && child.type === Text) {
            return React.cloneElement(child, { className: cn(child.props.className, text) });
          }
          return child;
        })
      )}
    </Pressable>
  );
}

export { Button };
export type { ButtonProps };

This:

  • Merges your two CVA calls into one.
  • Removes the TextClassContext indirection.
  • Keeps the same variant/size API, handles disabled state, and preserves child-text styling.

import React from "react";
import { Text, type TextProps, View, type ViewProps } from "react-native";
import { StyleSheet } from "react-native-unistyles";
function Card({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider using a factory/helper function to generate the Card components and eliminate repetitive boilerplate.

You can collapse all of these into a small factory/helper and then just pass in your component and default classes. That gets rid of the repetitive forwardRef + cn(...) boilerplate:

// components/Card.tsx

import * as React from 'react';
import { Text, View } from 'react-native';
import { cn } from '~/lib/utils';

type WithClassName<P> = P & { className?: string };

function createCardSlot<
  C extends React.ComponentType<any>
>(Component: C, defaultClasses: string) {
  type Props = WithClassName<React.ComponentPropsWithoutRef<C>>;
  const Slot = React.forwardRef<any, Props>(
    ({ className, ...props }, ref) => (
      <Component
        ref={ref}
        className={cn(defaultClasses, className)}
        {...props}
      />
    )
  );
  return Slot;
}

export const Card = createCardSlot(
  View,
  'rounded-lg border border-border bg-card shadow-sm shadow-foreground/10'
);
export const CardHeader = createCardSlot(
  View,
  'flex flex-col space-y-1.5 p-6'
);
export const CardTitle = createCardSlot(
  Text,
  'text-2xl font-semibold leading-none tracking-tight text-card-foreground'
);
export const CardDescription = createCardSlot(
  Text,
  'text-sm text-muted-foreground'
);
export const CardContent = createCardSlot(
  View,
  'p-6 pt-0'
);
export const CardFooter = createCardSlot(
  View,
  'flex flex-row items-center p-6 pt-0'
);

This preserves all functionality but:

  1. Eliminates six copies of identical forwardRef + cn(...) patterns.
  2. Keeps each slot’s default classes collocated in a single concise spot.
  3. Makes adding a new slot as easy as one line (no more boilerplate).

Comment thread src/app/_layout.tsx
default: noop,
});

export default function RootLayout() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider extracting all setup logic into a single custom hook to keep RootLayout focused on rendering.

Here’s one way to collapse all of that mounting, font‐loading, color‐scheme and platform setup into a single hook so your RootLayout stays focused on rendering:

// hooks/useAppSetup.ts
import { useIsomorphicLayoutEffect } from "@/src/hooks/useIsomorphicLayout";
import { useFonts } from "expo-font";
import { Appearance, Platform } from "react-native";
import { setAndroidNavigationBar } from "../lib/android-navigation-bar";
import { useColorScheme } from "@/src/lib/useColorScheme";
import { DarkTheme, DefaultTheme, type Theme } from "@react-navigation/native";
import { NAV_THEME } from "@/src/lib/constants";

const LIGHT_THEME: Theme = { ...DefaultTheme, colors: NAV_THEME.light };
const DARK_THEME: Theme = { ...DarkTheme,  colors: NAV_THEME.dark  };

export function useAppSetup() {
  // 1. load fonts
  const [fontsLoaded] = useFonts({
    SpaceMono: require("../../assets/fonts/SpaceMono-Regular.ttf"),
  });

  // 2. load persisted color‐scheme
  const { isDarkColorScheme, isLoaded: schemeLoaded } = useColorScheme();

  // 3. platform side‐effects
  useIsomorphicLayoutEffect(() => {
    if (Platform.OS === "web") {
      document.documentElement.classList.add("bg-background");
    }
    if (Platform.OS === "android") {
      setAndroidNavigationBar(Appearance.getColorScheme() ?? "light");
    }
  }, []);

  const ready = fontsLoaded && schemeLoaded;
  return {
    ready,
    theme: isDarkColorScheme ? DARK_THEME : LIGHT_THEME,
    statusBarStyle: isDarkColorScheme ? "light" : "dark",
  };
}

And then your RootLayout becomes:

// RootLayout.tsx
import { SafeAreaView } from "react-native-safe-area-context";
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import { ThemeProvider } from "@react-navigation/native";
import { Toaster } from "@/src/lib/sonner/sonner";
import { PortalHost } from "@rn-primitives/portal";
import { useAppSetup } from "@/src/hooks/useAppSetup";

export default function RootLayout() {
  const { ready, theme, statusBarStyle } = useAppSetup();

  if (!ready) return null;

  return (
    <SafeAreaView style={{ flex: 1, flexDirection: "column" }}>
      <ThemeProvider value={theme}>
        <Stack>
          <Stack.Screen name="(auth)" options={{ headerShown: false }} />
          <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          <Stack.Screen name="+not-found" />
        </Stack>
        <StatusBar style={statusBarStyle} />
      </ThemeProvider>
      <Toaster />
      <PortalHost name="root-portal" />
    </SafeAreaView>
  );
}

What this does:

• Removes the separate useSetWebBackgroundClassName, useSetAndroidNavigationBar, noop, hasMounted, extra refs/state
• Collocates all “ready” signals (fonts + scheme) and side-effects into one hook
• Keeps your rendering code in RootLayout very declarative and minimal.

Comment on lines +76 to +80
const sub = navigation.addListener("blur", () => {
closeAll();
});

return sub;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const sub = navigation.addListener("blur", () => {
closeAll();
});
return sub;
return navigation.addListener("blur", () => {
closeAll();
});


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

@qodo-code-review

qodo-code-review Bot commented Jul 18, 2025

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Fix async state timing issue

The newTheme calculation uses the current isDarkColorScheme state, but
toggleColorScheme() is async and the state may not update immediately. This
could cause the navigation bar to be set to the wrong theme.

src/components/ThemeToggle.tsx [10-15]

-function handleToggleColorScheme() {
-    toggleColorScheme();
+async function handleToggleColorScheme() {
+    const newTheme = isDarkColorScheme ? "light" : "dark";
+    await toggleColorScheme();
     // Update Android navigation bar
-    const newTheme = isDarkColorScheme ? "light" : "dark";
     setAndroidNavigationBar(newTheme);
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a race condition where setAndroidNavigationBar is called with a stale theme value because toggleColorScheme updates state asynchronously.

Medium
  • Update

…r handling; remove unused styles and hooks

feat: simplify TabLayout component by removing unused color scheme logic
feat: implement useAppSetup hook for centralized theme and font loading; remove deprecated useColorScheme hooks
@coderabbitai

coderabbitai Bot commented Jul 18, 2025

Copy link
Copy Markdown

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between e6d7b1e and fcd2ec3.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (58)
  • .github/copilot-instructions.md (3 hunks)
  • babel.config.js (1 hunks)
  • components.json (1 hunks)
  • global.css (1 hunks)
  • index.ts (1 hunks)
  • metro.config.js (2 hunks)
  • nativewind-env.d.ts (1 hunks)
  • package.json (1 hunks)
  • src/app/(auth)/_layout.tsx (1 hunks)
  • src/app/(auth)/forgot-password.tsx (3 hunks)
  • src/app/(auth)/sign-in.tsx (4 hunks)
  • src/app/(auth)/sign-up.tsx (1 hunks)
  • src/app/(tabs)/_layout.tsx (1 hunks)
  • src/app/(tabs)/explore.tsx (1 hunks)
  • src/app/+html.tsx (0 hunks)
  • src/app/+not-found.tsx (1 hunks)
  • src/app/_layout.tsx (1 hunks)
  • src/app/about.tsx (1 hunks)
  • src/app/pricing.tsx (3 hunks)
  • src/components/Collapsible.tsx (0 hunks)
  • src/components/ExternalLink.tsx (0 hunks)
  • src/components/HapticTab.tsx (0 hunks)
  • src/components/HelloWave.tsx (0 hunks)
  • src/components/ParallaxScrollView.tsx (0 hunks)
  • src/components/ThemeToggle.tsx (1 hunks)
  • src/components/ThemedText.tsx (0 hunks)
  • src/components/ThemedView.tsx (0 hunks)
  • src/components/layouts/default/Header.native.tsx (1 hunks)
  • src/components/layouts/default/Header.tsx (1 hunks)
  • src/components/layouts/default/Header.web.tsx (0 hunks)
  • src/components/ui/Button.tsx (1 hunks)
  • src/components/ui/Card.tsx (1 hunks)
  • src/components/ui/Input.tsx (1 hunks)
  • src/components/ui/NavigationMenu.md (0 hunks)
  • src/components/ui/NavigationMenu.web.tsx (0 hunks)
  • src/components/ui/Spinner.tsx (0 hunks)
  • src/components/ui/Text.tsx (1 hunks)
  • src/components/ui/activity-indicator.tsx (1 hunks)
  • src/components/ui/avatar.tsx (1 hunks)
  • src/components/ui/navigation-menu.tsx (1 hunks)
  • src/constants/Colors.ts (0 hunks)
  • src/hooks/useAppSetup.ts (1 hunks)
  • src/hooks/useColorScheme.ts (1 hunks)
  • src/hooks/useColorScheme.web.ts (0 hunks)
  • src/hooks/useIsomorphicLayout.ts (1 hunks)
  • src/hooks/useThemeColor.ts (0 hunks)
  • src/lib/android-navigation-bar.ts (1 hunks)
  • src/lib/constants.ts (1 hunks)
  • src/lib/icons/ChevronDown.tsx (1 hunks)
  • src/lib/icons/MoonStar.tsx (1 hunks)
  • src/lib/icons/Sparkles.tsx (1 hunks)
  • src/lib/icons/Sun.tsx (1 hunks)
  • src/lib/icons/iconWithClassName.ts (1 hunks)
  • src/lib/storage.ts (1 hunks)
  • src/lib/utils.ts (1 hunks)
  • tailwind.config.js (1 hunks)
  • tsconfig.json (2 hunks)
  • unistyles.ts (0 hunks)
 ___________________________________________________________________________________________________________________________________
< Finish what you start. Where possible, the routine or object that allocates a resource should be responsible for deallocating it. >
 -----------------------------------------------------------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

Tip

CodeRabbit can use Shopify Theme Check to improve the quality of Shopify theme reviews.

Add a configuration file to your project to customize how CodeRabbit runs Shopify Theme Check.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests

✅ Unit Test PR creation complete.

  • Create PR with unit tests
  • Commit unit tests in branch feat/check-native-wind
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

…; consolidate imports and enhance persistence
@coderabbitai

coderabbitai Bot commented Jul 18, 2025

Copy link
Copy Markdown

Note

Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🔭 Outside diff range comments (1)
src/app/(auth)/sign-up.tsx (1)

11-29: Naming & UX consistency – it’s a sign-up, not a login

handleLogin and the "Login" button title are misleading. Align names with the screen’s purpose.

-const handleLogin = async () => {
+const handleSignUp = async () => {
-<Button title="Login" onPress={handleLogin} />
+<Button title="Sign Up" onPress={handleSignUp} />

While touching this, consider wrapping the await in a try / catch to surface backend errors to the user.

♻️ Duplicate comments (3)
src/app/(auth)/forgot-password.tsx (1)

30-56: Successful migration to NativeWind styling.

The StyleSheet has been properly replaced with Tailwind utility classes. The className-based approach is consistent with the new styling paradigm.

src/components/ui/Card.tsx (1)

6-82: Consider using a factory/helper function to reduce boilerplate

As mentioned in a previous review, you can eliminate repetitive patterns by using a factory function.

src/components/ui/Button.tsx (1)

7-77: Consider merging buttonVariants and buttonTextVariants into a single CVA

As suggested in a previous review, using CVA with slots would simplify the component structure and eliminate the need for context providers.

🧹 Nitpick comments (15)
src/hooks/useIsomorphicLayout.ts (1)

1-5: LGTM! Consider improving readability with a function-based approach.

The implementation correctly handles the isomorphic layout effect pattern for SSR compatibility. This is a standard approach for React Native web applications that need to work in both server and client environments.

Consider refactoring to a function-based approach for better readability and maintainability:

-export const useIsomorphicLayoutEffect =
-    Platform.OS === 'web' && typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
+export const useIsomorphicLayoutEffect = 
+  Platform.OS === 'web' && typeof window === 'undefined' 
+    ? React.useEffect 
+    : React.useLayoutEffect;

This aligns well with the PR's migration to NativeWind and improved platform compatibility.

components.json (1)

1-6: Is this file actually wired-up?

Metro/Babel/tsconfig don’t consume a components.json file out-of-the-box. Unless you added custom tooling, this JSON will be ignored, and the aliases will not be applied at runtime / build time.
Confirm that:

  1. A plugin reads this file (e.g. a custom Metro resolver or babel plugin).
  2. The single source of truth for path aliases (usually tsconfig.json + babel.config.js) stays in sync to avoid drift.

If no consumer exists, drop the file and rely solely on tsconfig.json/babel for aliases.

src/components/layouts/default/Header.native.tsx (1)

1-3: Export shape may not match web counterpart

You export a named Header. If the web implementation uses export default function Header() { … }, an import Header from "./Header" in shared code will break on native.

Either mirror the export style across platforms or adjust imports to import { Header }.

src/app/about.tsx (1)

6-9: Consider adding basic styling and structure to the About page.

The current implementation is functional but lacks styling and proper structure. Consider adding some basic Tailwind classes for spacing and layout.

 	return (
-		<View>
-			<Text>About</Text>
-			<Text>This is the about page.</Text>
+		<View className="flex-1 p-4">
+			<Text className="text-2xl font-bold mb-4">About</Text>
+			<Text className="text-base text-muted-foreground">This is the about page.</Text>
 		</View>
 	);
src/lib/icons/ChevronDown.tsx (1)

1-4: Fix formatting inconsistencies for better code consistency.

The implementation is correct but has formatting inconsistencies compared to other icon wrappers like Sun.tsx.

-import { ChevronDown } from 'lucide-react-native';
-import { iconWithClassName } from './iconWithClassName';
+import { ChevronDown } from "lucide-react-native";
+import { iconWithClassName } from "./iconWithClassName";
+
 iconWithClassName(ChevronDown);
 export { ChevronDown };
src/lib/storage.ts (1)

3-28: Consider enhancing error handling and type safety.

The storage wrapper implementation is functional but could benefit from improvements:

  1. Silent failures: setItem and removeItem operations fail silently, which could lead to data inconsistencies without user awareness.
  2. Console logging in production: Error logging to console might not be suitable for production environments where you'd want structured logging or error reporting.
  3. Generic type support: Consider adding generic type parameters for better type safety when storing/retrieving typed data.

Here's an enhanced version that addresses these concerns:

+type StorageResult<T> = { success: true; data: T } | { success: false; error: Error };
+
 export const storage = {
-	async getItem(key: string): Promise<string | null> {
+	async getItem<T = string>(key: string): Promise<StorageResult<T | null>> {
 		try {
-			return await AsyncStorage.getItem(key);
+			const value = await AsyncStorage.getItem(key);
+			return { success: true, data: value as T | null };
 		} catch (error) {
-			console.error("Error getting item from storage:", error);
-			return null;
+			return { success: false, error: error as Error };
 		}
 	},

-	async setItem(key: string, value: string): Promise<void> {
+	async setItem(key: string, value: string): Promise<StorageResult<void>> {
 		try {
 			await AsyncStorage.setItem(key, value);
+			return { success: true, data: undefined };
 		} catch (error) {
-			console.error("Error setting item in storage:", error);
+			return { success: false, error: error as Error };
 		}
 	},

-	async removeItem(key: string): Promise<void> {
+	async removeItem(key: string): Promise<StorageResult<void>> {
 		try {
 			await AsyncStorage.removeItem(key);
+			return { success: true, data: undefined };
 		} catch (error) {
-			console.error("Error removing item from storage:", error);
+			return { success: false, error: error as Error };
 		}
 	},
 };
src/app/+not-found.tsx (2)

18-23: Remove unused container styles.

The container styles are defined but not applied to any element since the container View was removed. This creates dead code and potential confusion.

 const styles = StyleSheet.create({
-	container: {
-		flex: 1,
-		alignItems: "center",
-		justifyContent: "center",
-		padding: 20,
-	},
 	link: {
 		marginTop: 15,
 		paddingVertical: 15,
 	},
 });

6-14: Consider adding proper layout structure.

The removal of the container View leaves the content without proper centering or padding. This might affect the user experience on the not-found screen.

Consider wrapping the content in a container with proper styling:

 export default function NotFoundScreen() {
 	return (
 		<>
 			<Stack.Screen options={{ title: "Oops!" }} />
+			<View style={styles.container}>
 				<Text>This screen does not exist.</Text>
 				<Link href="/" style={styles.link}>
 					<Text>Go to home screen!</Text>
 				</Link>
+			</View>
 		</>
 	);
 }

And restore the container styles:

 const styles = StyleSheet.create({
+	container: {
+		flex: 1,
+		alignItems: "center",
+		justifyContent: "center",
+		padding: 20,
+	},
 	link: {
 		marginTop: 15,
 		paddingVertical: 15,
 	},
 });
src/hooks/useAppSetup.ts (1)

33-35: Consider using managed color scheme for consistency.

The Android navigation bar setup uses Appearance.getColorScheme() instead of the managed color scheme from the useColorScheme hook. This could lead to inconsistencies if the user has manually set a different theme preference.

Consider using the managed color scheme for consistency:

 		if (Platform.OS === "android") {
-			setAndroidNavigationBar(Appearance.getColorScheme() ?? "light");
+			setAndroidNavigationBar(isDarkColorScheme ? "dark" : "light");
 		}

This ensures the navigation bar color matches the app's theme state rather than the system preference.

src/app/pricing.tsx (1)

1-331: Consider splitting this large component into smaller modules

This 330+ line component would benefit from being decomposed into smaller, more focused components for better maintainability and testability.

Consider extracting these into separate components:

  • PricingCard (already defined inline, but could be a separate file)
  • BillingToggle (lines 217-243)
  • CommonFeatures (lines 252-271)
  • FAQSection (lines 273-309)
  • CTASection (lines 311-324)

This would make the main component more readable and each sub-component easier to test and maintain.

src/components/layouts/default/Header.tsx (1)

97-98: Multiple TODO comments for navigation implementation

There are three TODO comments for navigation functionality:

  1. Navigate to home (line 97)
  2. Navigate to login or handle auth (line 191)
  3. Add navigation to href on NavigationMenuLink (line 213)

Would you like me to implement the navigation logic using expo-router? I can create a solution that handles all three navigation cases.

Also applies to: 191-192, 213-213

src/hooks/useColorScheme.ts (2)

15-33: Optimize useEffect dependencies to prevent unnecessary storage reads

The useEffect includes colorScheme in its dependencies, which could cause the stored value to be re-read every time the color scheme changes. Since the effect is only meant to load the initial stored value on mount, consider using an empty dependency array or a flag to ensure it runs only once.

 // Load stored color scheme on mount
 useEffect(() => {
+  let mounted = true;
   const loadStoredColorScheme = async () => {
     try {
       const storedValue = await storage.getItem(COLOR_SCHEME_STORAGE_KEY);
-      if (storedValue && ["light", "dark", "system"].includes(storedValue)) {
+      if (mounted && storedValue && ["light", "dark", "system"].includes(storedValue)) {
         const storedColorScheme = storedValue as ColorScheme;
         if (storedColorScheme !== colorScheme) {
           setNativewindColorScheme(storedColorScheme);
         }
       }
     } catch (error) {
       console.error("Error loading stored color scheme:", error);
     } finally {
-      setIsLoaded(true);
+      if (mounted) {
+        setIsLoaded(true);
+      }
     }
   };

   loadStoredColorScheme();
-}, [colorScheme, setNativewindColorScheme]);
+  return () => { mounted = false; };
+}, [setNativewindColorScheme]);

51-55: Consider handling "system" mode in toggleColorScheme

The toggle function defaults to "dark" when colorScheme is nullish or "system", which might not reflect the actual system preference. Consider checking the actual rendered theme when in "system" mode.

 // Enhanced toggleColorScheme that persists to storage
 const toggleColorScheme = useCallback(async () => {
-  const currentScheme = colorScheme ?? "dark";
-  const newScheme: ColorScheme = currentScheme === "dark" ? "light" : "dark";
+  // When in system mode, check isDarkColorScheme to determine current appearance
+  const effectiveScheme = colorScheme === "system" 
+    ? (isDarkColorScheme ? "dark" : "light")
+    : (colorScheme ?? "dark");
+  const newScheme: ColorScheme = effectiveScheme === "dark" ? "light" : "dark";
   await setColorScheme(newScheme);
-}, [colorScheme, setColorScheme]);
+}, [colorScheme, isDarkColorScheme, setColorScheme]);
src/components/ui/navigation-menu.tsx (2)

135-154: Consider ref forwarding approach for NavigationMenuViewport

The ref prop is defined but not used. Since this component wraps the primitive in multiple View components, you'll need to decide which element should receive the ref.

Consider one of these approaches:

  1. Forward ref to the NavigationMenuPrimitive.Viewport:
 function NavigationMenuViewport({
   className,
+  ref,
   ...props
 }: NavigationMenuPrimitive.ViewportProps & {
   ref?: React.RefObject<NavigationMenuPrimitive.ViewportRef>;
 }) {
   return (
     <View className={cn('absolute left-0 top-full flex justify-center')}>
       <View
         className={cn(
           'web:origin-top-center relative mt-1.5 web:h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-lg web:animate-in web:zoom-in-90',
           className
         )}
         {...props}
       >
-        <NavigationMenuPrimitive.Viewport />
+        <NavigationMenuPrimitive.Viewport ref={ref} />
       </View>
     </View>
   );
 }
  1. Or remove the ref from the type if it's not needed:
-}: NavigationMenuPrimitive.ViewportProps & {
-  ref?: React.RefObject<NavigationMenuPrimitive.ViewportRef>;
-}) {
+}: NavigationMenuPrimitive.ViewportProps) {

17-154: Consider using React.forwardRef for components with ref props

Several components define ref in their type signatures but don't forward them. Consider using React.forwardRef for a more idiomatic approach to ref forwarding in React.

Example pattern:

const NavigationMenu = React.forwardRef<
  NavigationMenuPrimitive.RootRef,
  NavigationMenuPrimitive.RootProps & { className?: string }
>(({ className, children, ...props }, ref) => {
  return (
    <NavigationMenuPrimitive.Root
      ref={ref}
      className={cn('relative z-10 flex flex-row max-w-max items-center justify-center', className)}
      {...props}
    >
      {children}
      {Platform.OS === 'web' && <NavigationMenuViewport />}
    </NavigationMenuPrimitive.Root>
  );
});
NavigationMenu.displayName = 'NavigationMenu';

This pattern provides better TypeScript support and is the recommended approach for ref forwarding in React components.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6d7b1e and fcd2ec3.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (58)
  • .github/copilot-instructions.md (3 hunks)
  • babel.config.js (1 hunks)
  • components.json (1 hunks)
  • global.css (1 hunks)
  • index.ts (1 hunks)
  • metro.config.js (2 hunks)
  • nativewind-env.d.ts (1 hunks)
  • package.json (1 hunks)
  • src/app/(auth)/_layout.tsx (1 hunks)
  • src/app/(auth)/forgot-password.tsx (3 hunks)
  • src/app/(auth)/sign-in.tsx (4 hunks)
  • src/app/(auth)/sign-up.tsx (1 hunks)
  • src/app/(tabs)/_layout.tsx (1 hunks)
  • src/app/(tabs)/explore.tsx (1 hunks)
  • src/app/+html.tsx (0 hunks)
  • src/app/+not-found.tsx (1 hunks)
  • src/app/_layout.tsx (1 hunks)
  • src/app/about.tsx (1 hunks)
  • src/app/pricing.tsx (3 hunks)
  • src/components/Collapsible.tsx (0 hunks)
  • src/components/ExternalLink.tsx (0 hunks)
  • src/components/HapticTab.tsx (0 hunks)
  • src/components/HelloWave.tsx (0 hunks)
  • src/components/ParallaxScrollView.tsx (0 hunks)
  • src/components/ThemeToggle.tsx (1 hunks)
  • src/components/ThemedText.tsx (0 hunks)
  • src/components/ThemedView.tsx (0 hunks)
  • src/components/layouts/default/Header.native.tsx (1 hunks)
  • src/components/layouts/default/Header.tsx (1 hunks)
  • src/components/layouts/default/Header.web.tsx (0 hunks)
  • src/components/ui/Button.tsx (1 hunks)
  • src/components/ui/Card.tsx (1 hunks)
  • src/components/ui/Input.tsx (1 hunks)
  • src/components/ui/NavigationMenu.md (0 hunks)
  • src/components/ui/NavigationMenu.web.tsx (0 hunks)
  • src/components/ui/Spinner.tsx (0 hunks)
  • src/components/ui/Text.tsx (1 hunks)
  • src/components/ui/activity-indicator.tsx (1 hunks)
  • src/components/ui/avatar.tsx (1 hunks)
  • src/components/ui/navigation-menu.tsx (1 hunks)
  • src/constants/Colors.ts (0 hunks)
  • src/hooks/useAppSetup.ts (1 hunks)
  • src/hooks/useColorScheme.ts (1 hunks)
  • src/hooks/useColorScheme.web.ts (0 hunks)
  • src/hooks/useIsomorphicLayout.ts (1 hunks)
  • src/hooks/useThemeColor.ts (0 hunks)
  • src/lib/android-navigation-bar.ts (1 hunks)
  • src/lib/constants.ts (1 hunks)
  • src/lib/icons/ChevronDown.tsx (1 hunks)
  • src/lib/icons/MoonStar.tsx (1 hunks)
  • src/lib/icons/Sparkles.tsx (1 hunks)
  • src/lib/icons/Sun.tsx (1 hunks)
  • src/lib/icons/iconWithClassName.ts (1 hunks)
  • src/lib/storage.ts (1 hunks)
  • src/lib/utils.ts (1 hunks)
  • tailwind.config.js (1 hunks)
  • tsconfig.json (2 hunks)
  • unistyles.ts (0 hunks)
💤 Files with no reviewable changes (16)
  • src/app/+html.tsx
  • unistyles.ts
  • src/components/HelloWave.tsx
  • src/components/Collapsible.tsx
  • src/components/ui/Spinner.tsx
  • src/hooks/useColorScheme.web.ts
  • src/components/ThemedView.tsx
  • src/components/ExternalLink.tsx
  • src/components/ThemedText.tsx
  • src/components/ParallaxScrollView.tsx
  • src/components/ui/NavigationMenu.md
  • src/constants/Colors.ts
  • src/components/layouts/default/Header.web.tsx
  • src/components/HapticTab.tsx
  • src/hooks/useThemeColor.ts
  • src/components/ui/NavigationMenu.web.tsx
🧰 Additional context used
🧬 Code Graph Analysis (22)
src/lib/icons/Sun.tsx (1)
src/lib/icons/iconWithClassName.ts (1)
  • iconWithClassName (4-14)
src/app/(auth)/_layout.tsx (1)
src/components/layouts/default/Header.tsx (1)
  • Header (59-200)
metro.config.js (1)
tailwind.config.js (1)
  • require (1-1)
src/lib/icons/MoonStar.tsx (1)
src/lib/icons/iconWithClassName.ts (1)
  • iconWithClassName (4-14)
src/components/ui/activity-indicator.tsx (2)
src/hooks/useColorScheme.ts (1)
  • useColorScheme (9-64)
src/lib/constants.ts (1)
  • NAV_THEME (1-18)
src/lib/icons/Sparkles.tsx (1)
src/lib/icons/iconWithClassName.ts (1)
  • iconWithClassName (4-14)
src/lib/icons/ChevronDown.tsx (1)
src/lib/icons/iconWithClassName.ts (1)
  • iconWithClassName (4-14)
src/app/(tabs)/_layout.tsx (2)
src/components/ui/IconSymbol.ios.tsx (1)
  • IconSymbol (4-32)
src/components/ui/IconSymbol.tsx (1)
  • IconSymbol (28-41)
src/app/(auth)/sign-in.tsx (4)
src/components/ui/Card.tsx (2)
  • CardContent (82-82)
  • CardFooter (82-82)
src/components/ui/Text.tsx (1)
  • Text (26-26)
src/components/ui/Button.tsx (1)
  • Button (80-80)
src/components/ui/activity-indicator.tsx (1)
  • ActivityIndicator (14-14)
src/components/layouts/default/Header.tsx (6)
src/lib/icons/Sparkles.tsx (1)
  • Sparkles (5-5)
src/components/ui/Text.tsx (1)
  • Text (26-26)
src/components/ui/navigation-menu.tsx (7)
  • NavigationMenu (182-182)
  • NavigationMenuList (187-187)
  • NavigationMenuItem (185-185)
  • NavigationMenuTrigger (188-188)
  • NavigationMenuContent (183-183)
  • NavigationMenuLink (186-186)
  • navigationMenuTriggerStyle (189-189)
src/components/ThemeToggle.tsx (1)
  • ThemeToggle (7-31)
src/components/ui/Button.tsx (1)
  • Button (80-80)
src/lib/utils.ts (1)
  • cn (4-6)
src/app/+not-found.tsx (1)
src/components/ui/Text.tsx (1)
  • Text (26-26)
src/hooks/useAppSetup.ts (4)
src/lib/constants.ts (1)
  • NAV_THEME (1-18)
src/hooks/useColorScheme.ts (1)
  • useColorScheme (9-64)
src/hooks/useIsomorphicLayout.ts (1)
  • useIsomorphicLayoutEffect (4-5)
src/lib/android-navigation-bar.ts (1)
  • setAndroidNavigationBar (5-11)
src/app/_layout.tsx (4)
src/lib/constants.ts (1)
  • NAV_THEME (1-18)
src/hooks/useAppSetup.ts (1)
  • useAppSetup (13-45)
src/hooks/useIsomorphicLayout.ts (1)
  • useIsomorphicLayoutEffect (4-5)
src/lib/android-navigation-bar.ts (1)
  • setAndroidNavigationBar (5-11)
src/app/(tabs)/explore.tsx (1)
src/components/ui/Text.tsx (1)
  • Text (26-26)
src/lib/android-navigation-bar.ts (1)
src/lib/constants.ts (1)
  • NAV_THEME (1-18)
src/components/ui/Input.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/Card.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/Text.tsx (2)
  • Text (26-26)
  • TextClassContext (26-26)
src/components/ui/avatar.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/navigation-menu.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/lib/icons/ChevronDown.tsx (1)
  • ChevronDown (4-4)
src/hooks/useColorScheme.ts (1)
src/lib/storage.ts (1)
  • storage (3-28)
src/components/ui/Button.tsx (2)
src/components/ui/Text.tsx (1)
  • TextClassContext (26-26)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/Text.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
🔇 Additional comments (45)
src/app/(auth)/sign-up.tsx (1)

4-4: Double-check ~/ alias resolution on all platforms

The "~" prefix works only if Metro and TypeScript are configured to recognise it.
Run the app on Android/iOS to ensure there’s no “Unable to resolve module ~/components/...” error.
If problems arise, either:

-import { Input } from "~/components/ui/input";
+import { Input } from "@/components/ui/input";   // tsconfig / babel-paths

or extend babel-plugin-module-resolver & metro.config.js to include "~".

index.ts (1)

2-2: CSS import is supported via NativeWind in metro.config.js
You already have Metro configured with NativeWind (withNativeWind(config, { input: "./global.css" })), so import "./global.css" will be transformed correctly on iOS/Android. No changes are needed.

nativewind-env.d.ts (1)

1-1: Type reference looks good

This brings NativeWind’s JSX typings into the project – no issues spotted.

src/lib/icons/Sun.tsx (1)

1-5: Clean icon wrapper implementation that follows the established pattern.

The implementation correctly wraps the Lucide icon with iconWithClassName to enable NativeWind styling support, maintaining API consistency across the icon library.

tsconfig.json (2)

6-14: Well-structured path aliases that improve import readability.

The new path aliases follow good conventions and will make imports cleaner throughout the codebase. The hierarchical structure (~/, ~components/, ~lib/*) provides both general and specific shortcuts.


24-25: Proper NativeWind TypeScript integration.

Including nativewind-env.d.ts correctly enables TypeScript support for NativeWind's className prop and utility types.

src/app/(auth)/_layout.tsx (1)

3-3: Clean import using the new path alias pattern.

The import correctly uses the new ~/ path alias for improved readability and consistency.

src/lib/icons/Sparkles.tsx (1)

1-6: LGTM! Clean implementation following the established pattern.

The icon wrapper correctly applies the iconWithClassName utility to enable NativeWind className support, maintaining consistency with other icon modules in the project.

metro.config.js (2)

3-3: LGTM! Proper NativeWind integration.

The import of withNativeWind correctly enables NativeWind processing in the Metro bundler.


35-35: LGTM! Correct NativeWind wrapper configuration.

The Metro config is properly wrapped with withNativeWind and correctly specifies the global CSS input file, enabling NativeWind's CSS processing pipeline.

src/lib/icons/MoonStar.tsx (1)

1-6: LGTM! Consistent implementation with established icon pattern.

The MoonStar icon wrapper correctly follows the same pattern as other icon modules, enabling NativeWind className support through the iconWithClassName utility.

src/lib/utils.ts (1)

1-6: LGTM! Standard and well-implemented class name utility.

The cn function correctly combines clsx for conditional class handling with twMerge for Tailwind-specific deduplication. This is a common and recommended pattern for Tailwind CSS projects.

src/lib/icons/iconWithClassName.ts (1)

4-14: LGTM! Correct NativeWind interop implementation.

The iconWithClassName function properly uses NativeWind's cssInterop to enable className-based styling for Lucide icons. The configuration correctly maps color and opacity properties to be controlled via class names, which is essential for the NativeWind styling system.

src/components/ui/activity-indicator.tsx (3)

1-3: LGTM: Import statements are well-organized.

The imports are properly structured with clear aliases and consistent path usage.


5-7: LGTM: Proper TypeScript typing for prop forwarding.

The component signature correctly uses ComponentPropsWithoutRef to maintain type safety while allowing all native ActivityIndicator props to be passed through.


14-14: LGTM: Clean named export.

The export follows consistent patterns used throughout the codebase.

babel.config.js (1)

4-7: LGTM: Proper NativeWind Babel configuration.

The presets are correctly configured for NativeWind integration with Expo:

  • The jsxImportSource: "nativewind" enables NativeWind JSX transforms
  • The nativewind/babel preset adds the necessary transformations

This aligns with the migration from react-native-unistyles to NativeWind.

src/components/ThemeToggle.tsx (4)

1-5: LGTM: Well-organized imports with platform-specific considerations.

The imports are properly structured and include platform-specific functionality for Android navigation bar updates.


10-15: LGTM: Comprehensive theme toggle with platform-specific updates.

The toggle function properly handles both theme state changes and Android navigation bar updates, showing good attention to platform-specific details.


18-28: LGTM: Excellent cross-platform styling with accessibility features.

The component demonstrates:

  • Proper web-specific focus and ring styles for accessibility
  • Clean active state handling with opacity changes
  • Responsive icon rendering based on theme state
  • Good use of Tailwind CSS utility classes

23-27: LGTM: Intuitive icon usage with consistent styling.

The conditional icon rendering follows good UX patterns (moon for dark, sun for light) with consistent sizing and stroke width.

global.css (3)

1-3: LGTM: Standard Tailwind CSS layer imports.

The imports include all necessary Tailwind CSS layers (base, components, utilities) for proper styling functionality.


5-26: LGTM: Comprehensive light theme CSS variables.

The light theme variables are well-structured with:

  • Semantic naming conventions for UI elements
  • HSL color format for better manipulation
  • Complete coverage of all necessary UI components
  • Proper organization within the base layer

28-49: LGTM: Well-implemented dark theme with proper contrast.

The dark theme implementation demonstrates:

  • Proper CSS specificity with .dark:root selector
  • Complete mirror of light theme structure
  • Appropriate color adjustments for dark mode
  • Good contrast ratios for accessibility
src/app/(tabs)/_layout.tsx (3)

1-5: LGTM: Simplified imports align with styling migration.

The imports are streamlined and focused on essential components, properly removing dependencies on the old theming system.


10-20: LGTM: Proper simplification while maintaining essential functionality.

The screen options are appropriately simplified by removing custom theming logic while preserving:

  • Tab bar background functionality
  • Platform-specific iOS blur effect styling
  • Core navigation structure

This aligns well with the migration to NativeWind and centralized theming.


22-40: LGTM: Tab screens maintain proper structure with theming support.

The tab screens are correctly configured with:

  • Consistent naming and titles
  • Proper icon components with color props
  • The IconSymbol component will handle theming internally

This maintains functionality while delegating theming to the new system.

src/lib/android-navigation-bar.ts (1)

5-11: LGTM! Clean platform-specific implementation.

The implementation correctly:

  • Guards against non-Android platforms early
  • Uses inverted button styles for proper contrast (light buttons on dark theme, dark buttons on light theme)
  • Properly integrates with the NAV_THEME constants
  • Handles async operations appropriately

The button style inversion logic is correct for accessibility and visual consistency.

src/hooks/useAppSetup.ts (1)

13-45: Well-structured app setup hook.

The hook demonstrates good practices:

  • Clear separation of concerns (fonts, color scheme, platform effects)
  • Proper use of useRef to prevent duplicate effects
  • Platform-specific optimizations
  • Clean return interface with proper typing

The implementation integrates well with the new theming system.

src/lib/constants.ts (1)

1-18: Clean and well-structured theme constants.

The NAV_THEME constant is well-organized with:

  • Consistent HSL color format
  • Clear semantic color mapping (comments indicate purpose)
  • Proper contrast ratios between light and dark themes
  • Maintainable structure for future theme updates

This provides a solid foundation for the new theming system.

package.json (1)

17-17: Dependencies align well with the NativeWind migration.

The new dependencies support the migration effectively:

  • React Native primitives for cross-platform UI components
  • Tailwind CSS ecosystem for utility-first styling
  • AsyncStorage for theme persistence
  • Lucide icons for consistent iconography

Also applies to: 22-25, 27-28, 36-36, 44-44, 46-47, 55-55, 62-65

src/app/(auth)/forgot-password.tsx (2)

1-1: Clean import optimization.

Good cleanup removing the unused React import while keeping only the necessary useState hook.


21-21: Good addition of error logging.

Adding console.error for debugging password reset failures improves developer experience.

src/app/(auth)/sign-in.tsx (2)

1-17: Well-organized import migration.

The imports have been properly updated to use the new component paths and the ActivityIndicator component, maintaining clean organization.


48-49: Successful StyleSheet to className migration.

The component has been properly migrated from StyleSheet to Tailwind utility classes while preserving all functionality. The ActivityIndicator integration in the button is well-implemented.

Also applies to: 62-62, 78-81, 84-90

src/app/(tabs)/explore.tsx (1)

1-10: Significant UI simplification - verify this is intentional.

The explore screen has been dramatically simplified from a complex UI with ParallaxScrollView, collapsible sections, and external links to just displaying "Explore" text. While this aligns with the migration away from deprecated components, it represents a significant reduction in functionality.

Please confirm this simplification is intentional and whether the complex UI will be reimplemented using the new component system in a future iteration.

src/components/ui/Input.tsx (3)

1-3: Clean imports and utility integration.

Good use of the cn utility function for class name merging, which is consistent with the new styling approach.


5-11: Improved component props interface.

The addition of className and placeholderClassName props provides better styling flexibility while maintaining type safety.


12-23: Comprehensive Tailwind utility class implementation.

The utility classes cover all necessary styling states including:

  • Responsive design (web/native specific styles)
  • Focus states with ring effects
  • Disabled state handling
  • Proper semantic color tokens

The conditional styling for the editable state is well-implemented.

src/app/_layout.tsx (1)

36-44: LGTM! Clean platform-specific setup pattern

The separation of platform-specific setup logic using Platform.select is a clean approach that makes the code easy to understand and maintain. The pattern allows for easy extension to other platforms if needed.

src/components/ui/avatar.tsx (1)

1-48: Well-structured Avatar component implementation

The Avatar component follows the established patterns perfectly:

  • Proper use of the primitives library
  • Consistent className handling with cn()
  • Appropriate TypeScript types including ref support
  • Clean component composition
.github/copilot-instructions.md (1)

1-95: Documentation properly reflects the new styling system

The instructions have been comprehensively updated to guide AI agents in using NativeWind, react-native-reusables, and CVA for styling. The examples and patterns are consistent with the codebase migration.

src/components/ui/navigation-menu.tsx (3)

156-179: LGTM!

The NavigationMenuIndicator component correctly handles the ref prop and implements smooth animations based on the context state.


17-33: Forward the ref prop to the primitive component

The ref prop is defined in the type signature but not forwarded to the underlying primitive component.

Apply this diff to properly forward the ref:

 function NavigationMenu({
   className,
   children,
+  ref,
   ...props
 }: NavigationMenuPrimitive.RootProps & {
   ref?: React.RefObject<NavigationMenuPrimitive.RootRef>;
 }) {
   return (
     <NavigationMenuPrimitive.Root
+      ref={ref}
       className={cn('relative z-10 flex flex-row max-w-max items-center justify-center', className)}
       {...props}
     >

Likely an incorrect or invalid review comment.


35-50: Forward the ref prop to the primitive component

The ref prop is defined in the type signature but not forwarded to the underlying primitive component.

Apply this diff to properly forward the ref:

 function NavigationMenuList({
   className,
+  ref,
   ...props
 }: NavigationMenuPrimitive.ListProps & {
   ref?: React.RefObject<NavigationMenuPrimitive.ListRef>;
 }) {
   return (
     <NavigationMenuPrimitive.List
+      ref={ref}
       className={cn(

Likely an incorrect or invalid review comment.

export default function AuthLayout() {
return (
<SafeAreaView style={{ flex: 1 }}>
<Header />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider the UX implications of the Header on auth screens.

The Header component includes a login button, which might be confusing on authentication screens where users are already in the login/signup flow. Consider if this provides the intended user experience.

Let me check what the Header component contains to better understand the UX impact:


🏁 Script executed:

#!/bin/bash
# Check if the Header component has conditional logic for auth screens
rg -A 10 -B 5 "Login|Sign" src/components/layouts/default/Header.tsx

Length of output: 589


Hide the Login button on authentication screens

The Header component always renders a Login button, which is confusing on the login/signup pages. It should be conditionally hidden in the auth flow.

• src/components/layouts/default/Header.tsx
– Remove or wrap the Login <Button> in a check (e.g. a hideAuthButton prop)
• src/app/(auth)/_layout.tsx
– Pass a prop to suppress the Login button when rendering <Header />

Suggested change in Header.tsx:

   {/* Right Section - Theme Toggle & Login */}
   <View className="flex-row items-center gap-2">
     <ThemeToggle />
-    <Button
-      variant="outline"
-      size="sm"
-      onPress={() => { /* navigate to login */ }}
-    >
-      <Text>Login</Text>
-    </Button>
+    {!hideAuthButton && (
+      <Button
+        variant="outline"
+        size="sm"
+        onPress={() => { /* navigate to login */ }}
+      >
+        <Text>Login</Text>
+      </Button>
+    )}
   </View>

And in the auth layout:

- <Header />
+ <Header hideAuthButton />

This prevents showing “Login” when users are already in the authentication flow.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Header />
<Header hideAuthButton />
🤖 Prompt for AI Agents
In src/app/(auth)/_layout.tsx at line 8, the Header component always shows the
Login button, which should be hidden on authentication pages. Modify
src/components/layouts/default/Header.tsx to accept a prop like hideAuthButton
and conditionally render the Login button based on it. Then, update
src/app/(auth)/_layout.tsx to pass this prop to Header to suppress the Login
button during the auth flow.

Comment on lines +8 to +11
const { colorScheme } = useColorScheme();
return (
<RNActivityIndicator color={NAV_THEME[colorScheme].background} {...props} />
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix visibility issue: Using background color makes the spinner invisible.

The component uses NAV_THEME[colorScheme].background which will make the ActivityIndicator the same color as the background, rendering it invisible. This defeats the purpose of a loading indicator.

Use a contrasting color instead:

-		<RNActivityIndicator color={NAV_THEME[colorScheme].background} {...props} />
+		<RNActivityIndicator color={NAV_THEME[colorScheme].primary} {...props} />

Or alternatively use foreground for better contrast:

-		<RNActivityIndicator color={NAV_THEME[colorScheme].background} {...props} />
+		<RNActivityIndicator color={NAV_THEME[colorScheme].text} {...props} />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { colorScheme } = useColorScheme();
return (
<RNActivityIndicator color={NAV_THEME[colorScheme].background} {...props} />
);
const { colorScheme } = useColorScheme();
return (
<RNActivityIndicator color={NAV_THEME[colorScheme].primary} {...props} />
);
🤖 Prompt for AI Agents
In src/components/ui/activity-indicator.tsx around lines 8 to 11, the
ActivityIndicator color is set to NAV_THEME[colorScheme].background, causing it
to blend into the background and become invisible. Change the color prop to use
a contrasting color such as NAV_THEME[colorScheme].foreground or another color
that stands out against the background to ensure the spinner is visible.

Comment thread package.json Outdated
style={styles.input}
className="w-full h-12 border border-border rounded-lg px-4 mb-5 text-base text-foreground bg-background"
placeholder="Enter your email"
placeholderTextColor="#999"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use semantic color token for placeholder.

The hard-coded #999 color doesn't follow the theming system and won't adapt to dark mode.

-				placeholderTextColor="#999"
+				placeholderTextColor="rgb(var(--muted-foreground))"

Or better yet, use the placeholderClassName prop if available in your Input component:

-				placeholderTextColor="#999"
+				placeholderClassName="text-muted-foreground"
🤖 Prompt for AI Agents
In src/app/(auth)/forgot-password.tsx at line 41, replace the hard-coded
placeholderTextColor="#999" with a semantic color token from the theming system
to ensure it adapts to dark mode. If the Input component supports a
placeholderClassName prop, use that instead to apply a CSS class with the
appropriate placeholder color from the theme.

},
},
}));
const TextClassContext = React.createContext<string | undefined>(undefined);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing TextClassContext.Provider implementation

The TextClassContext is created and consumed within the Text component, but there's no Provider implementation visible. This means textClass will always be undefined. If this context is meant to be used, you should export a Provider component or document where it should be provided.

Consider adding a Provider:

+export function TextClassProvider({ children, className }: { children: React.ReactNode; className?: string }) {
+  return (
+    <TextClassContext.Provider value={className}>
+      {children}
+    </TextClassContext.Provider>
+  );
+}

Also applies to: 16-16

🤖 Prompt for AI Agents
In src/components/ui/Text.tsx at line 6, the TextClassContext is created but
lacks a Provider implementation, causing the context value to always be
undefined. To fix this, create and export a Provider component for
TextClassContext that wraps its children and supplies the appropriate textClass
value. Alternatively, document clearly where the Provider should be used to
ensure consumers receive the context value.

Comment on lines +6 to +21
function Card({
className,
...props
}: ViewProps & {
ref?: React.RefObject<View>;
}) {
return (
<View
className={cn(
'rounded-lg border border-border bg-card shadow-sm shadow-foreground/10',
className
)}
{...props}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Refs are not being forwarded to the underlying components

The component accepts a ref prop but doesn't pass it to the View component. Either use React.forwardRef or pass the ref prop directly.

 function Card({
   className,
+  ref,
   ...props
 }: ViewProps & {
   ref?: React.RefObject<View>;
 }) {
   return (
     <View
+      ref={ref}
       className={cn(
         'rounded-lg border border-border bg-card shadow-sm shadow-foreground/10',
         className
       )}
       {...props}
     />
   );
 }

Apply the same fix to all other Card components (CardHeader, CardTitle, CardDescription, CardContent, CardFooter).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function Card({
className,
...props
}: ViewProps & {
ref?: React.RefObject<View>;
}) {
return (
<View
className={cn(
'rounded-lg border border-border bg-card shadow-sm shadow-foreground/10',
className
)}
{...props}
/>
);
}
function Card({
className,
ref,
...props
}: ViewProps & {
ref?: React.RefObject<View>;
}) {
return (
<View
ref={ref}
className={cn(
'rounded-lg border border-border bg-card shadow-sm shadow-foreground/10',
className
)}
{...props}
/>
);
}
🤖 Prompt for AI Agents
In src/components/ui/Card.tsx around lines 6 to 21, the Card component accepts a
ref prop but does not forward it to the underlying View component, so the ref is
not properly attached. Fix this by wrapping the Card component with
React.forwardRef and passing the ref to the View element. Apply the same
React.forwardRef pattern and ref forwarding to all other Card-related components
like CardHeader, CardTitle, CardDescription, CardContent, and CardFooter.

import * as React from 'react';
import { Pressable } from 'react-native';
import { cn } from '~/lib/utils';
import { TextClassContext } from '~/components/ui/text';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix import case sensitivity

The import path uses lowercase 'text' but should match the actual file name.

-import { TextClassContext } from '~/components/ui/text';
+import { TextClassContext } from '~/components/ui/Text';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { TextClassContext } from '~/components/ui/text';
-import { TextClassContext } from '~/components/ui/text';
+import { TextClassContext } from '~/components/ui/Text';
🤖 Prompt for AI Agents
In src/components/ui/Button.tsx at line 5, the import path for TextClassContext
uses lowercase 'text' which does not match the actual file name. Update the
import statement to use the correct case that matches the file system, ensuring
the path reflects the exact casing of the target file or directory.

</Pressable>
);
};
function Button({ ref, className, variant, size, ...props }: ButtonProps) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Destructure ref prop correctly for React Native

In React Native, the ref should not be destructured from props when using function components. It should be handled separately.

-function Button({ ref, className, variant, size, ...props }: ButtonProps) {
+const Button = React.forwardRef<React.ElementRef<typeof Pressable>, ButtonProps>(
+  ({ className, variant, size, ...props }, ref) => {
   return (
     <TextClassContext.Provider
       value={buttonTextVariants({ variant, size, className: 'web:pointer-events-none' })}
     >
       <Pressable
         className={cn(
           props.disabled && 'opacity-50 web:pointer-events-none',
           buttonVariants({ variant, size, className })
         )}
         ref={ref}
         role='button'
         {...props}
       />
     </TextClassContext.Provider>
   );
-}
+});
+Button.displayName = 'Button';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function Button({ ref, className, variant, size, ...props }: ButtonProps) {
const Button = React.forwardRef<React.ElementRef<typeof Pressable>, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<TextClassContext.Provider
value={buttonTextVariants({ variant, size, className: 'web:pointer-events-none' })}
>
<Pressable
className={cn(
props.disabled && 'opacity-50 web:pointer-events-none',
buttonVariants({ variant, size, className })
)}
ref={ref}
role="button"
{...props}
/>
</TextClassContext.Provider>
);
}
);
Button.displayName = 'Button';
🤖 Prompt for AI Agents
In src/components/ui/Button.tsx at line 62, the ref prop is incorrectly
destructured from the function component's props. To fix this, remove ref from
the destructured props and instead use React.forwardRef to handle the ref
properly in the function component, passing it as a second argument and
forwarding it to the appropriate element.

Comment on lines +58 to +97
function NavigationMenuTrigger({
className,
children,
...props
}: Omit<NavigationMenuPrimitive.TriggerProps, 'children'> & {
children?: React.ReactNode;
ref?: React.RefObject<NavigationMenuPrimitive.TriggerRef>;
}) {
const { value } = NavigationMenuPrimitive.useRootContext();
const { value: itemValue } = NavigationMenuPrimitive.useItemContext();

const progress = useDerivedValue(() =>
value === itemValue ? withTiming(1, { duration: 250 }) : withTiming(0, { duration: 200 })
);
const chevronStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${progress.value * 180}deg` }],
opacity: interpolate(progress.value, [0, 1], [1, 0.8], Extrapolation.CLAMP),
}));

return (
<NavigationMenuPrimitive.Trigger
className={cn(
navigationMenuTriggerStyle(),
'web:group gap-1.5',
value === itemValue && 'bg-accent',
className
)}
{...props}
>
{children}
<Animated.View style={chevronStyle}>
<ChevronDown
size={12}
className={cn('relative text-foreground h-3 w-3 web:transition web:duration-200')}
aria-hidden={true}
/>
</Animated.View>
</NavigationMenuPrimitive.Trigger>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Forward the ref prop to the primitive component

The ref prop is defined in the type signature but not forwarded to the underlying primitive component.

Apply this diff to properly forward the ref:

 function NavigationMenuTrigger({
   className,
   children,
+  ref,
   ...props
 }: Omit<NavigationMenuPrimitive.TriggerProps, 'children'> & {
   children?: React.ReactNode;
   ref?: React.RefObject<NavigationMenuPrimitive.TriggerRef>;
 }) {
   const { value } = NavigationMenuPrimitive.useRootContext();
   const { value: itemValue } = NavigationMenuPrimitive.useItemContext();

   const progress = useDerivedValue(() =>
     value === itemValue ? withTiming(1, { duration: 250 }) : withTiming(0, { duration: 200 })
   );
   const chevronStyle = useAnimatedStyle(() => ({
     transform: [{ rotate: `${progress.value * 180}deg` }],
     opacity: interpolate(progress.value, [0, 1], [1, 0.8], Extrapolation.CLAMP),
   }));

   return (
     <NavigationMenuPrimitive.Trigger
+      ref={ref}
       className={cn(

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/ui/navigation-menu.tsx between lines 58 and 97, the ref prop
is declared in the component's props but is not forwarded to the
NavigationMenuPrimitive.Trigger component. To fix this, accept the ref as a
parameter using React.forwardRef and pass it down to the
NavigationMenuPrimitive.Trigger element to ensure the ref is properly forwarded.

Comment on lines +99 to +131
function NavigationMenuContent({
className,
children,
portalHost,
...props
}: NavigationMenuPrimitive.ContentProps & {
portalHost?: string;
ref?: React.RefObject<NavigationMenuPrimitive.ContentRef>;
}) {
const { value } = NavigationMenuPrimitive.useRootContext();
const { value: itemValue } = NavigationMenuPrimitive.useItemContext();
return (
<NavigationMenuPrimitive.Portal hostName={portalHost}>
<NavigationMenuPrimitive.Content
className={cn(
'w-full native:border native:border-border native:rounded-lg native:shadow-lg native:bg-popover native:text-popover-foreground native:overflow-hidden',
value === itemValue
? 'web:animate-in web:fade-in web:slide-in-from-right-20'
: 'web:animate-out web:fade-out web:slide-out-to-left-20',
className
)}
{...props}
>
<Animated.View
entering={Platform.OS !== 'web' ? FadeInLeft : undefined}
exiting={Platform.OS !== 'web' ? FadeOutLeft : undefined}
>
{children}
</Animated.View>
</NavigationMenuPrimitive.Content>
</NavigationMenuPrimitive.Portal>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Forward the ref prop to the primitive component

The ref prop is defined in the type signature but not forwarded to the underlying primitive component.

Apply this diff to properly forward the ref:

 function NavigationMenuContent({
   className,
   children,
   portalHost,
+  ref,
   ...props
 }: NavigationMenuPrimitive.ContentProps & {
   portalHost?: string;
   ref?: React.RefObject<NavigationMenuPrimitive.ContentRef>;
 }) {
   const { value } = NavigationMenuPrimitive.useRootContext();
   const { value: itemValue } = NavigationMenuPrimitive.useItemContext();
   return (
     <NavigationMenuPrimitive.Portal hostName={portalHost}>
       <NavigationMenuPrimitive.Content
+        ref={ref}
         className={cn(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function NavigationMenuContent({
className,
children,
portalHost,
...props
}: NavigationMenuPrimitive.ContentProps & {
portalHost?: string;
ref?: React.RefObject<NavigationMenuPrimitive.ContentRef>;
}) {
const { value } = NavigationMenuPrimitive.useRootContext();
const { value: itemValue } = NavigationMenuPrimitive.useItemContext();
return (
<NavigationMenuPrimitive.Portal hostName={portalHost}>
<NavigationMenuPrimitive.Content
className={cn(
'w-full native:border native:border-border native:rounded-lg native:shadow-lg native:bg-popover native:text-popover-foreground native:overflow-hidden',
value === itemValue
? 'web:animate-in web:fade-in web:slide-in-from-right-20'
: 'web:animate-out web:fade-out web:slide-out-to-left-20',
className
)}
{...props}
>
<Animated.View
entering={Platform.OS !== 'web' ? FadeInLeft : undefined}
exiting={Platform.OS !== 'web' ? FadeOutLeft : undefined}
>
{children}
</Animated.View>
</NavigationMenuPrimitive.Content>
</NavigationMenuPrimitive.Portal>
);
}
function NavigationMenuContent({
className,
children,
portalHost,
ref,
...props
}: NavigationMenuPrimitive.ContentProps & {
portalHost?: string;
ref?: React.RefObject<NavigationMenuPrimitive.ContentRef>;
}) {
const { value } = NavigationMenuPrimitive.useRootContext();
const { value: itemValue } = NavigationMenuPrimitive.useItemContext();
return (
<NavigationMenuPrimitive.Portal hostName={portalHost}>
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
'w-full native:border native:border-border native:rounded-lg native:shadow-lg native:bg-popover native:text-popover-foreground native:overflow-hidden',
value === itemValue
? 'web:animate-in web:fade-in web:slide-in-from-right-20'
: 'web:animate-out web:fade-out web:slide-out-to-left-20',
className
)}
{...props}
>
<Animated.View
entering={Platform.OS !== 'web' ? FadeInLeft : undefined}
exiting={Platform.OS !== 'web' ? FadeOutLeft : undefined}
>
{children}
</Animated.View>
</NavigationMenuPrimitive.Content>
</NavigationMenuPrimitive.Portal>
);
}
🤖 Prompt for AI Agents
In src/components/ui/navigation-menu.tsx lines 99 to 131, the ref prop is
declared in the component's props but not forwarded to the
NavigationMenuPrimitive.Content component. To fix this, destructure the ref from
props and pass it explicitly to the NavigationMenuPrimitive.Content component
using the ref attribute, ensuring the ref is properly forwarded to the
underlying primitive component.

@chakrihacker chakrihacker merged commit cabb3a9 into main Jul 18, 2025
1 check passed
@coderabbitai

coderabbitai Bot commented Jul 18, 2025

Copy link
Copy Markdown

UTG Post-Process Complete

No new issues were detected in the generated code and all check runs have completed. The unit test generation process has completed successfully.

@coderabbitai

coderabbitai Bot commented Jul 18, 2025

Copy link
Copy Markdown

Creating a PR to put the unit tests in...

The changes have been created in this pull request: View PR

@chakrihacker chakrihacker deleted the feat/check-native-wind branch July 18, 2025 10:19
@coderabbitai coderabbitai Bot mentioned this pull request Jul 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant