Add react native reusables#1
Conversation
…ind; refactor SignIn component for improved UI and add ActivityIndicator component
… refactor useColorScheme hook and ThemeToggle component
…ckage dependencies
Reviewer's GuideThis 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 primitivesclassDiagram
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
Class diagram for theme and color scheme managementclassDiagram
class useColorScheme {
+colorScheme: "light"|"dark"|"system"
+isDarkColorScheme: boolean
+setColorScheme(newColorScheme)
+toggleColorScheme()
+isLoaded: boolean
}
class storage {
+getItem(key)
+setItem(key, value)
+removeItem(key)
}
useColorScheme --> storage
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
There was a problem hiding this comment.
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 with “slots” so you don’t need a context‐provider 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 slot’s 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>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"; |
There was a problem hiding this comment.
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:
- Replace all usages of
style={styles.something}withclassName="..."and use Tailwind CSS classes for styling. - Remove the
const styles = StyleSheet.create({ ... })block entirely from the file. - 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) => ({ |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
TextClassContextindirection. - Keeps the same
variant/sizeAPI, 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({ |
There was a problem hiding this comment.
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:
- Eliminates six copies of identical
forwardRef+cn(...)patterns. - Keeps each slot’s default classes collocated in a single concise spot.
- Makes adding a new slot as easy as one line (no more boilerplate).
| default: noop, | ||
| }); | ||
|
|
||
| export default function RootLayout() { |
There was a problem hiding this comment.
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.
| const sub = navigation.addListener("blur", () => { | ||
| closeAll(); | ||
| }); | ||
|
|
||
| return sub; |
There was a problem hiding this comment.
suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)
| const sub = navigation.addListener("blur", () => { | |
| closeAll(); | |
| }); | |
| return sub; | |
| return navigation.addListener("blur", () => { | |
| closeAll(); | |
| }); | |
Explanation
Something that we often see in people's code is assigning to a result variableand 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.
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||
…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
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (58)
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 unit tests✅ Unit Test PR creation complete.
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
…; consolidate imports and enhance persistence
|
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. |
There was a problem hiding this comment.
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
handleLoginand 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
awaitin atry / catchto 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 boilerplateAs 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 CVAAs 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.jsonfile 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:
- A plugin reads this file (e.g. a custom Metro resolver or babel plugin).
- 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/babelfor aliases.src/components/layouts/default/Header.native.tsx (1)
1-3: Export shape may not match web counterpartYou export a named
Header. If the web implementation usesexport default function Header() { … }, animport 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:
- Silent failures:
setItemandremoveItemoperations fail silently, which could lead to data inconsistencies without user awareness.- Console logging in production: Error logging to console might not be suitable for production environments where you'd want structured logging or error reporting.
- 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
containerstyles are defined but not applied to any element since the containerViewwas 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
Viewleaves 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 theuseColorSchemehook. 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 modulesThis 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 implementationThere are three TODO comments for navigation functionality:
- Navigate to home (line 97)
- Navigate to login or handle auth (line 191)
- 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 readsThe
useEffectincludescolorSchemein 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 toggleColorSchemeThe toggle function defaults to "dark" when
colorSchemeis 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 NavigationMenuViewportThe
refprop 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:
- 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> ); }
- 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 propsSeveral components define ref in their type signatures but don't forward them. Consider using
React.forwardReffor 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
⛔ Files ignored due to path filters (2)
bun.lockis excluded by!**/*.lockpackage-lock.jsonis 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 platformsThe
"~"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-pathsor extend
babel-plugin-module-resolver&metro.config.jsto 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" })), soimport "./global.css"will be transformed correctly on iOS/Android. No changes are needed.nativewind-env.d.ts (1)
1-1: Type reference looks goodThis 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
iconWithClassNameto 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.tscorrectly 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
iconWithClassNameutility 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
withNativeWindcorrectly enables NativeWind processing in the Metro bundler.
35-35: LGTM! Correct NativeWind wrapper configuration.The Metro config is properly wrapped with
withNativeWindand 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
iconWithClassNameutility.src/lib/utils.ts (1)
1-6: LGTM! Standard and well-implemented class name utility.The
cnfunction correctly combinesclsxfor conditional class handling withtwMergefor 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
iconWithClassNamefunction properly uses NativeWind'scssInteropto enable className-based styling for Lucide icons. The configuration correctly mapscolorandopacityproperties 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
ComponentPropsWithoutRefto 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/babelpreset adds the necessary transformationsThis 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:rootselector- 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
classNameandplaceholderClassNameprops 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 patternThe separation of platform-specific setup logic using
Platform.selectis 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 implementationThe 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 systemThe 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 componentThe
refprop 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 componentThe
refprop 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 /> |
There was a problem hiding this comment.
💡 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.tsxLength 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.
| <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.
| const { colorScheme } = useColorScheme(); | ||
| return ( | ||
| <RNActivityIndicator color={NAV_THEME[colorScheme].background} {...props} /> | ||
| ); |
There was a problem hiding this comment.
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.
| 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.
| 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" |
There was a problem hiding this comment.
🛠️ 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); |
There was a problem hiding this comment.
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.
| 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} | ||
| /> | ||
| ); | ||
| } |
There was a problem hiding this comment.
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.
| 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'; |
There was a problem hiding this comment.
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.
| 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) { |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| 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> | ||
| ); | ||
| } |
There was a problem hiding this comment.
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.
| 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> | ||
| ); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
|
✅ 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. |
|
Creating a PR to put the unit tests in... The changes have been created in this pull request: View PR |
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
File Walkthrough
Summary by CodeRabbit
New Features
Refactor
Bug Fixes
Chores