mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-19 10:51:19 +00:00
initialize project with @react-native-reusables/cli
This commit is contained in:
commit
08fc64a6a3
41
.gitignore
vendored
Normal file
41
.gitignore
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Expo
|
||||||
|
.expo/
|
||||||
|
dist/
|
||||||
|
web-build/
|
||||||
|
|
||||||
|
# Native
|
||||||
|
*.orig.*
|
||||||
|
*.jks
|
||||||
|
*.p8
|
||||||
|
*.p12
|
||||||
|
*.key
|
||||||
|
*.mobileprovision
|
||||||
|
|
||||||
|
# Metro
|
||||||
|
.metro-health-check*
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.*
|
||||||
|
yarn-debug.*
|
||||||
|
yarn-error.*
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
|
||||||
|
# The following patterns were generated by expo-cli
|
||||||
|
|
||||||
|
expo-env.d.ts
|
||||||
|
# @end expo-cli
|
16
README.md
Normal file
16
README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Starter base
|
||||||
|
|
||||||
|
A starting point to help you set up your project quickly and use the common components provided by `react-native-reusables`. The idea is to make it easier for you to get started.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- NativeWind v4
|
||||||
|
- Dark and light mode
|
||||||
|
- Android Navigation Bar matches mode
|
||||||
|
- Persistent mode
|
||||||
|
- Common components
|
||||||
|
- ThemeToggle, Avatar, Button, Card, Progress, Text, Tooltip
|
||||||
|
|
||||||
|
<img src="https://github.com/mrzachnugent/react-native-reusables/assets/63797719/42c94108-38a7-498b-9c70-18640420f1bc"
|
||||||
|
alt="starter-base-template"
|
||||||
|
style="width:270px;" />
|
40
app.json
Normal file
40
app.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"expo": {
|
||||||
|
"name": "rnr-test",
|
||||||
|
"slug": "rnr-test",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"icon": "./assets/images/icon.png",
|
||||||
|
"scheme": "myapp",
|
||||||
|
"userInterfaceStyle": "automatic",
|
||||||
|
"newArchEnabled": true,
|
||||||
|
"splash": {
|
||||||
|
"image": "./assets/images/splash.png",
|
||||||
|
"resizeMode": "contain",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
"assetBundlePatterns": [
|
||||||
|
"**/*"
|
||||||
|
],
|
||||||
|
"ios": {
|
||||||
|
"supportsTablet": true
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"adaptiveIcon": {
|
||||||
|
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"bundler": "metro",
|
||||||
|
"output": "static",
|
||||||
|
"favicon": "./assets/images/favicon.png"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"expo-router"
|
||||||
|
],
|
||||||
|
"experiments": {
|
||||||
|
"typedRoutes": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
app/+not-found.tsx
Normal file
18
app/+not-found.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Link, Stack } from 'expo-router';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { Text } from '~/components/ui/text';
|
||||||
|
|
||||||
|
export default function NotFoundScreen() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack.Screen options={{ title: 'Oops!' }} />
|
||||||
|
<View>
|
||||||
|
<Text>This screen doesn't exist.</Text>
|
||||||
|
|
||||||
|
<Link href='/'>
|
||||||
|
<Text>Go to home screen!</Text>
|
||||||
|
</Link>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
69
app/_layout.tsx
Normal file
69
app/_layout.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import '~/global.css';
|
||||||
|
|
||||||
|
import { DarkTheme, DefaultTheme, Theme, ThemeProvider } from '@react-navigation/native';
|
||||||
|
import { Stack } from 'expo-router';
|
||||||
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
import { NAV_THEME } from '~/lib/constants';
|
||||||
|
import { useColorScheme } from '~/lib/useColorScheme';
|
||||||
|
import { PortalHost } from '@rn-primitives/portal';
|
||||||
|
import { ThemeToggle } from '~/components/ThemeToggle';
|
||||||
|
import { setAndroidNavigationBar } from '~/lib/android-navigation-bar';
|
||||||
|
|
||||||
|
const LIGHT_THEME: Theme = {
|
||||||
|
...DefaultTheme,
|
||||||
|
colors: NAV_THEME.light,
|
||||||
|
};
|
||||||
|
const DARK_THEME: Theme = {
|
||||||
|
...DarkTheme,
|
||||||
|
colors: NAV_THEME.dark,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Catch any errors thrown by the Layout component.
|
||||||
|
ErrorBoundary,
|
||||||
|
} from 'expo-router';
|
||||||
|
|
||||||
|
export default function RootLayout() {
|
||||||
|
const hasMounted = React.useRef(false);
|
||||||
|
const { colorScheme, isDarkColorScheme } = useColorScheme();
|
||||||
|
const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false);
|
||||||
|
|
||||||
|
useIsomorphicLayoutEffect(() => {
|
||||||
|
if (hasMounted.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Platform.OS === 'web') {
|
||||||
|
// Adds the background color to the html element to prevent white background on overscroll.
|
||||||
|
document.documentElement.classList.add('bg-background');
|
||||||
|
}
|
||||||
|
setAndroidNavigationBar(colorScheme);
|
||||||
|
setIsColorSchemeLoaded(true);
|
||||||
|
hasMounted.current = true;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!isColorSchemeLoaded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
|
||||||
|
<StatusBar style={isDarkColorScheme ? 'light' : 'dark'} />
|
||||||
|
<Stack>
|
||||||
|
<Stack.Screen
|
||||||
|
name='index'
|
||||||
|
options={{
|
||||||
|
title: 'Starter Base',
|
||||||
|
headerRight: () => <ThemeToggle />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<PortalHost />
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useIsomorphicLayoutEffect =
|
||||||
|
Platform.OS === 'web' && typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
|
95
app/index.tsx
Normal file
95
app/index.tsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import Animated, { FadeInUp, FadeOutDown, LayoutAnimationConfig } from 'react-native-reanimated';
|
||||||
|
import { Info } from '~/lib/icons/Info';
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from '~/components/ui/avatar';
|
||||||
|
import { Button } from '~/components/ui/button';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '~/components/ui/card';
|
||||||
|
import { Progress } from '~/components/ui/progress';
|
||||||
|
import { Text } from '~/components/ui/text';
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from '~/components/ui/tooltip';
|
||||||
|
|
||||||
|
const GITHUB_AVATAR_URI =
|
||||||
|
'https://i.pinimg.com/originals/ef/a2/8d/efa28d18a04e7fa40ed49eeb0ab660db.jpg';
|
||||||
|
|
||||||
|
export default function Screen() {
|
||||||
|
const [progress, setProgress] = React.useState(78);
|
||||||
|
|
||||||
|
function updateProgressValue() {
|
||||||
|
setProgress(Math.floor(Math.random() * 100));
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View className='flex-1 justify-center items-center gap-5 p-6 bg-secondary/30'>
|
||||||
|
<Card className='w-full max-w-sm p-6 rounded-2xl'>
|
||||||
|
<CardHeader className='items-center'>
|
||||||
|
<Avatar alt="Rick Sanchez's Avatar" className='w-24 h-24'>
|
||||||
|
<AvatarImage source={{ uri: GITHUB_AVATAR_URI }} />
|
||||||
|
<AvatarFallback>
|
||||||
|
<Text>RS</Text>
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<View className='p-3' />
|
||||||
|
<CardTitle className='pb-2 text-center'>Rick Sanchez</CardTitle>
|
||||||
|
<View className='flex-row'>
|
||||||
|
<CardDescription className='text-base font-semibold'>Scientist</CardDescription>
|
||||||
|
<Tooltip delayDuration={150}>
|
||||||
|
<TooltipTrigger className='px-2 pb-0.5 active:opacity-50'>
|
||||||
|
<Info size={14} strokeWidth={2.5} className='w-4 h-4 text-foreground/70' />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className='py-2 px-4 shadow'>
|
||||||
|
<Text className='native:text-lg'>Freelance</Text>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</View>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<View className='flex-row justify-around gap-3'>
|
||||||
|
<View className='items-center'>
|
||||||
|
<Text className='text-sm text-muted-foreground'>Dimension</Text>
|
||||||
|
<Text className='text-xl font-semibold'>C-137</Text>
|
||||||
|
</View>
|
||||||
|
<View className='items-center'>
|
||||||
|
<Text className='text-sm text-muted-foreground'>Age</Text>
|
||||||
|
<Text className='text-xl font-semibold'>70</Text>
|
||||||
|
</View>
|
||||||
|
<View className='items-center'>
|
||||||
|
<Text className='text-sm text-muted-foreground'>Species</Text>
|
||||||
|
<Text className='text-xl font-semibold'>Human</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className='flex-col gap-3 pb-0'>
|
||||||
|
<View className='flex-row items-center overflow-hidden'>
|
||||||
|
<Text className='text-sm text-muted-foreground'>Productivity:</Text>
|
||||||
|
<LayoutAnimationConfig skipEntering>
|
||||||
|
<Animated.View
|
||||||
|
key={progress}
|
||||||
|
entering={FadeInUp}
|
||||||
|
exiting={FadeOutDown}
|
||||||
|
className='w-11 items-center'
|
||||||
|
>
|
||||||
|
<Text className='text-sm font-bold text-sky-600'>{progress}%</Text>
|
||||||
|
</Animated.View>
|
||||||
|
</LayoutAnimationConfig>
|
||||||
|
</View>
|
||||||
|
<Progress value={progress} className='h-2' indicatorClassName='bg-sky-600' />
|
||||||
|
<View />
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
className='shadow shadow-foreground/5'
|
||||||
|
onPress={updateProgressValue}
|
||||||
|
>
|
||||||
|
<Text>Update</Text>
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
BIN
assets/images/adaptive-icon.png
Normal file
BIN
assets/images/adaptive-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
assets/images/favicon.png
Normal file
BIN
assets/images/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/icon.png
Normal file
BIN
assets/images/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
assets/images/splash.png
Normal file
BIN
assets/images/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
6
babel.config.js
Normal file
6
babel.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = function (api) {
|
||||||
|
api.cache(true);
|
||||||
|
return {
|
||||||
|
presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'],
|
||||||
|
};
|
||||||
|
};
|
38
components/ThemeToggle.tsx
Normal file
38
components/ThemeToggle.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Pressable, View } from 'react-native';
|
||||||
|
import { setAndroidNavigationBar } from '~/lib/android-navigation-bar';
|
||||||
|
import { MoonStar } from '~/lib/icons/MoonStar';
|
||||||
|
import { Sun } from '~/lib/icons/Sun';
|
||||||
|
import { useColorScheme } from '~/lib/useColorScheme';
|
||||||
|
import { cn } from '~/lib/utils';
|
||||||
|
|
||||||
|
export function ThemeToggle() {
|
||||||
|
const { isDarkColorScheme, setColorScheme } = useColorScheme();
|
||||||
|
|
||||||
|
function toggleColorScheme() {
|
||||||
|
const newTheme = isDarkColorScheme ? 'light' : 'dark';
|
||||||
|
setColorScheme(newTheme);
|
||||||
|
setAndroidNavigationBar(newTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
onPress={toggleColorScheme}
|
||||||
|
className='web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2'
|
||||||
|
>
|
||||||
|
{({ pressed }) => (
|
||||||
|
<View
|
||||||
|
className={cn(
|
||||||
|
'flex-1 aspect-square pt-0.5 justify-center items-start web:px-5',
|
||||||
|
pressed && 'opacity-70'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isDarkColorScheme ? (
|
||||||
|
<MoonStar className='text-foreground' size={23} strokeWidth={1.25} />
|
||||||
|
) : (
|
||||||
|
<Sun className='text-foreground' size={24} strokeWidth={1.25} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
45
components/ui/avatar.tsx
Normal file
45
components/ui/avatar.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import * as AvatarPrimitive from '@rn-primitives/avatar';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { cn } from '~/lib/utils';
|
||||||
|
|
||||||
|
const AvatarPrimitiveRoot = AvatarPrimitive.Root;
|
||||||
|
const AvatarPrimitiveImage = AvatarPrimitive.Image;
|
||||||
|
const AvatarPrimitiveFallback = AvatarPrimitive.Fallback;
|
||||||
|
|
||||||
|
const Avatar = React.forwardRef<AvatarPrimitive.RootRef, AvatarPrimitive.RootProps>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitiveRoot
|
||||||
|
ref={ref}
|
||||||
|
className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Avatar.displayName = AvatarPrimitiveRoot.displayName;
|
||||||
|
|
||||||
|
const AvatarImage = React.forwardRef<AvatarPrimitive.ImageRef, AvatarPrimitive.ImageProps>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitiveImage
|
||||||
|
ref={ref}
|
||||||
|
className={cn('aspect-square h-full w-full', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
AvatarImage.displayName = AvatarPrimitiveImage.displayName;
|
||||||
|
|
||||||
|
const AvatarFallback = React.forwardRef<AvatarPrimitive.FallbackRef, AvatarPrimitive.FallbackProps>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitiveFallback
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex h-full w-full items-center justify-center rounded-full bg-muted',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
AvatarFallback.displayName = AvatarPrimitiveFallback.displayName;
|
||||||
|
|
||||||
|
export { Avatar, AvatarFallback, AvatarImage };
|
88
components/ui/button.tsx
Normal file
88
components/ui/button.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Pressable } from 'react-native';
|
||||||
|
import { TextClassContext } from '~/components/ui/text';
|
||||||
|
import { cn } from '~/lib/utils';
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
'group flex items-center justify-center rounded-md web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-primary web:hover:opacity-90 active:opacity-90',
|
||||||
|
destructive: 'bg-destructive web:hover:opacity-90 active:opacity-90',
|
||||||
|
outline:
|
||||||
|
'border border-input bg-background web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent',
|
||||||
|
secondary: 'bg-secondary web:hover:opacity-80 active:opacity-80',
|
||||||
|
ghost: 'web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent',
|
||||||
|
link: 'web:underline-offset-4 web:hover:underline web:focus:underline ',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: 'h-10 px-4 py-2 native:h-12 native:px-5 native:py-3',
|
||||||
|
sm: 'h-9 rounded-md px-3',
|
||||||
|
lg: 'h-11 rounded-md px-8 native:h-14',
|
||||||
|
icon: 'h-10 w-10',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const buttonTextVariants = cva(
|
||||||
|
'web:whitespace-nowrap text-sm native:text-base font-medium text-foreground web:transition-colors',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'text-primary-foreground',
|
||||||
|
destructive: 'text-destructive-foreground',
|
||||||
|
outline: 'group-active:text-accent-foreground',
|
||||||
|
secondary: 'text-secondary-foreground group-active:text-secondary-foreground',
|
||||||
|
ghost: 'group-active:text-accent-foreground',
|
||||||
|
link: 'text-primary group-active:underline',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: '',
|
||||||
|
sm: '',
|
||||||
|
lg: 'native:text-lg',
|
||||||
|
icon: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
type ButtonProps = React.ComponentPropsWithoutRef<typeof Pressable> &
|
||||||
|
VariantProps<typeof buttonVariants>;
|
||||||
|
|
||||||
|
const Button = React.forwardRef<React.ElementRef<typeof Pressable>, ButtonProps>(
|
||||||
|
({ className, variant, size, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<TextClassContext.Provider
|
||||||
|
value={cn(
|
||||||
|
props.disabled && 'web:pointer-events-none',
|
||||||
|
buttonTextVariants({ variant, size })
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<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';
|
||||||
|
|
||||||
|
export { Button, buttonTextVariants, buttonVariants };
|
||||||
|
export type { ButtonProps };
|
57
components/ui/card.tsx
Normal file
57
components/ui/card.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import type { TextRef, ViewRef } from '@rn-primitives/types';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Text, TextProps, View, ViewProps } from 'react-native';
|
||||||
|
import { TextClassContext } from '~/components/ui/text';
|
||||||
|
import { cn } from '~/lib/utils';
|
||||||
|
|
||||||
|
const Card = React.forwardRef<ViewRef, ViewProps>(({ className, ...props }, ref) => (
|
||||||
|
<View
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'rounded-lg border border-border bg-card shadow-sm shadow-foreground/10',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Card.displayName = 'Card';
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<ViewRef, ViewProps>(({ className, ...props }, ref) => (
|
||||||
|
<View ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
|
||||||
|
));
|
||||||
|
CardHeader.displayName = 'CardHeader';
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<TextRef, React.ComponentPropsWithoutRef<typeof Text>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<Text
|
||||||
|
role='heading'
|
||||||
|
aria-level={3}
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'text-2xl text-card-foreground font-semibold leading-none tracking-tight',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
CardTitle.displayName = 'CardTitle';
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<TextRef, TextProps>(({ className, ...props }, ref) => (
|
||||||
|
<Text ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
|
||||||
|
));
|
||||||
|
CardDescription.displayName = 'CardDescription';
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<ViewRef, ViewProps>(({ className, ...props }, ref) => (
|
||||||
|
<TextClassContext.Provider value='text-card-foreground'>
|
||||||
|
<View ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
||||||
|
</TextClassContext.Provider>
|
||||||
|
));
|
||||||
|
CardContent.displayName = 'CardContent';
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<ViewRef, ViewProps>(({ className, ...props }, ref) => (
|
||||||
|
<View ref={ref} className={cn('flex flex-row items-center p-6 pt-0', className)} {...props} />
|
||||||
|
));
|
||||||
|
CardFooter.displayName = 'CardFooter';
|
||||||
|
|
||||||
|
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
|
61
components/ui/progress.tsx
Normal file
61
components/ui/progress.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import * as ProgressPrimitive from '@rn-primitives/progress';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Platform, View } from 'react-native';
|
||||||
|
import Animated, {
|
||||||
|
Extrapolation,
|
||||||
|
interpolate,
|
||||||
|
useAnimatedStyle,
|
||||||
|
useDerivedValue,
|
||||||
|
withSpring,
|
||||||
|
} from 'react-native-reanimated';
|
||||||
|
import { cn } from '~/lib/utils';
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
ProgressPrimitive.RootRef,
|
||||||
|
ProgressPrimitive.RootProps & {
|
||||||
|
indicatorClassName?: string;
|
||||||
|
}
|
||||||
|
>(({ className, value, indicatorClassName, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn('relative h-4 w-full overflow-hidden rounded-full bg-secondary', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Indicator value={value} className={indicatorClassName} />
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
export { Progress };
|
||||||
|
|
||||||
|
function Indicator({ value, className }: { value: number | undefined | null; className?: string }) {
|
||||||
|
const progress = useDerivedValue(() => value ?? 0);
|
||||||
|
|
||||||
|
const indicator = useAnimatedStyle(() => {
|
||||||
|
return {
|
||||||
|
width: withSpring(
|
||||||
|
`${interpolate(progress.value, [0, 100], [1, 100], Extrapolation.CLAMP)}%`,
|
||||||
|
{ overshootClamping: true }
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Platform.OS === 'web') {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
className={cn('h-full w-full flex-1 bg-primary web:transition-all', className)}
|
||||||
|
style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator className={cn('h-full w-full ', className)} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProgressPrimitive.Indicator asChild>
|
||||||
|
<Animated.View style={indicator} className={cn('h-full bg-foreground', className)} />
|
||||||
|
</ProgressPrimitive.Indicator>
|
||||||
|
);
|
||||||
|
}
|
24
components/ui/text.tsx
Normal file
24
components/ui/text.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import * as Slot from '@rn-primitives/slot';
|
||||||
|
import type { SlottableTextProps, TextRef } from '@rn-primitives/types';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Text as RNText } from 'react-native';
|
||||||
|
import { cn } from '~/lib/utils';
|
||||||
|
|
||||||
|
const TextClassContext = React.createContext<string | undefined>(undefined);
|
||||||
|
|
||||||
|
const Text = React.forwardRef<TextRef, SlottableTextProps>(
|
||||||
|
({ className, asChild = false, ...props }, ref) => {
|
||||||
|
const textClass = React.useContext(TextClassContext);
|
||||||
|
const Component = asChild ? Slot.Text : RNText;
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
className={cn('text-base text-foreground web:select-text', textClass, className)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Text.displayName = 'Text';
|
||||||
|
|
||||||
|
export { Text, TextClassContext };
|
39
components/ui/tooltip.tsx
Normal file
39
components/ui/tooltip.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import * as TooltipPrimitive from '@rn-primitives/tooltip';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Platform, StyleSheet } from 'react-native';
|
||||||
|
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
|
||||||
|
import { TextClassContext } from '~/components/ui/text';
|
||||||
|
import { cn } from '~/lib/utils';
|
||||||
|
|
||||||
|
const Tooltip = TooltipPrimitive.Root;
|
||||||
|
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
TooltipPrimitive.ContentRef,
|
||||||
|
TooltipPrimitive.ContentProps & { portalHost?: string }
|
||||||
|
>(({ className, sideOffset = 4, portalHost, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Portal hostName={portalHost}>
|
||||||
|
<TooltipPrimitive.Overlay style={Platform.OS !== 'web' ? StyleSheet.absoluteFill : undefined}>
|
||||||
|
<Animated.View
|
||||||
|
entering={Platform.select({ web: undefined, default: FadeIn })}
|
||||||
|
exiting={Platform.select({ web: undefined, default: FadeOut })}
|
||||||
|
>
|
||||||
|
<TextClassContext.Provider value='text-sm native:text-base text-popover-foreground'>
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
'z-50 overflow-hidden rounded-md border border-border bg-popover px-3 py-1.5 shadow-md shadow-foreground/5 web:animate-in web:fade-in-0 web:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</TextClassContext.Provider>
|
||||||
|
</Animated.View>
|
||||||
|
</TooltipPrimitive.Overlay>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
));
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
export { Tooltip, TooltipContent, TooltipTrigger };
|
49
global.css
Normal file
49
global.css
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 240 10% 3.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 240 10% 3.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 240 10% 3.9%;
|
||||||
|
--primary: 240 5.9% 10%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
--secondary: 240 4.8% 95.9%;
|
||||||
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
|
--muted: 240 4.8% 95.9%;
|
||||||
|
--muted-foreground: 240 3.8% 46.1%;
|
||||||
|
--accent: 240 4.8% 95.9%;
|
||||||
|
--accent-foreground: 240 5.9% 10%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 240 5.9% 90%;
|
||||||
|
--input: 240 5.9% 90%;
|
||||||
|
--ring: 240 5.9% 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark:root {
|
||||||
|
--background: 240 10% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
--card: 240 10% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
--popover: 240 10% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 240 5.9% 10%;
|
||||||
|
--secondary: 240 3.7% 15.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
--muted: 240 3.7% 15.9%;
|
||||||
|
--muted-foreground: 240 5% 64.9%;
|
||||||
|
--accent: 240 3.7% 15.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
--destructive: 0 72% 51%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 240 3.7% 15.9%;
|
||||||
|
--input: 240 3.7% 15.9%;
|
||||||
|
--ring: 240 4.9% 83.9%;
|
||||||
|
}
|
||||||
|
}
|
12
index.js
Normal file
12
index.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { registerRootComponent } from 'expo';
|
||||||
|
import { ExpoRoot } from 'expo-router';
|
||||||
|
|
||||||
|
// https://docs.expo.dev/router/reference/troubleshooting/#expo_router_app_root-not-defined
|
||||||
|
|
||||||
|
// Must be exported or Fast Refresh won't update the context
|
||||||
|
export function App() {
|
||||||
|
const ctx = require.context('./app');
|
||||||
|
return <ExpoRoot context={ctx} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerRootComponent(App);
|
11
lib/android-navigation-bar.ts
Normal file
11
lib/android-navigation-bar.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as NavigationBar from 'expo-navigation-bar';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
import { NAV_THEME } from '~/lib/constants';
|
||||||
|
|
||||||
|
export async function setAndroidNavigationBar(theme: 'light' | 'dark') {
|
||||||
|
if (Platform.OS !== 'android') return;
|
||||||
|
await NavigationBar.setButtonStyleAsync(theme === 'dark' ? 'light' : 'dark');
|
||||||
|
await NavigationBar.setBackgroundColorAsync(
|
||||||
|
theme === 'dark' ? NAV_THEME.dark.background : NAV_THEME.light.background
|
||||||
|
);
|
||||||
|
}
|
18
lib/constants.ts
Normal file
18
lib/constants.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const NAV_THEME = {
|
||||||
|
light: {
|
||||||
|
background: 'hsl(0 0% 100%)', // background
|
||||||
|
border: 'hsl(240 5.9% 90%)', // border
|
||||||
|
card: 'hsl(0 0% 100%)', // card
|
||||||
|
notification: 'hsl(0 84.2% 60.2%)', // destructive
|
||||||
|
primary: 'hsl(240 5.9% 10%)', // primary
|
||||||
|
text: 'hsl(240 10% 3.9%)', // foreground
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
background: 'hsl(240 10% 3.9%)', // background
|
||||||
|
border: 'hsl(240 3.7% 15.9%)', // border
|
||||||
|
card: 'hsl(240 10% 3.9%)', // card
|
||||||
|
notification: 'hsl(0 72% 51%)', // destructive
|
||||||
|
primary: 'hsl(0 0% 98%)', // primary
|
||||||
|
text: 'hsl(0 0% 98%)', // foreground
|
||||||
|
},
|
||||||
|
};
|
4
lib/icons/Info.tsx
Normal file
4
lib/icons/Info.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Info } from 'lucide-react-native';
|
||||||
|
import { iconWithClassName } from './iconWithClassName';
|
||||||
|
iconWithClassName(Info);
|
||||||
|
export { Info };
|
4
lib/icons/MoonStar.tsx
Normal file
4
lib/icons/MoonStar.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { MoonStar } from 'lucide-react-native';
|
||||||
|
import { iconWithClassName } from './iconWithClassName';
|
||||||
|
iconWithClassName(MoonStar);
|
||||||
|
export { MoonStar };
|
4
lib/icons/Sun.tsx
Normal file
4
lib/icons/Sun.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Sun } from 'lucide-react-native';
|
||||||
|
import { iconWithClassName } from './iconWithClassName';
|
||||||
|
iconWithClassName(Sun);
|
||||||
|
export { Sun };
|
14
lib/icons/iconWithClassName.ts
Normal file
14
lib/icons/iconWithClassName.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { LucideIcon } from 'lucide-react-native';
|
||||||
|
import { cssInterop } from 'nativewind';
|
||||||
|
|
||||||
|
export function iconWithClassName(icon: LucideIcon) {
|
||||||
|
cssInterop(icon, {
|
||||||
|
className: {
|
||||||
|
target: 'style',
|
||||||
|
nativeStyleToProp: {
|
||||||
|
color: true,
|
||||||
|
opacity: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
11
lib/useColorScheme.tsx
Normal file
11
lib/useColorScheme.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { useColorScheme as useNativewindColorScheme } from 'nativewind';
|
||||||
|
|
||||||
|
export function useColorScheme() {
|
||||||
|
const { colorScheme, setColorScheme, toggleColorScheme } = useNativewindColorScheme();
|
||||||
|
return {
|
||||||
|
colorScheme: colorScheme ?? 'dark',
|
||||||
|
isDarkColorScheme: colorScheme === 'dark',
|
||||||
|
setColorScheme,
|
||||||
|
toggleColorScheme,
|
||||||
|
};
|
||||||
|
}
|
6
lib/utils.ts
Normal file
6
lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
6
metro.config.js
Normal file
6
metro.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const { getDefaultConfig } = require('expo/metro-config');
|
||||||
|
const { withNativeWind } = require('nativewind/metro');
|
||||||
|
|
||||||
|
const config = getDefaultConfig(__dirname);
|
||||||
|
|
||||||
|
module.exports = withNativeWind(config, { input: './global.css' });
|
1
nativewind-env.d.ts
vendored
Normal file
1
nativewind-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="nativewind/types" />
|
13443
package-lock.json
generated
Normal file
13443
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
package.json
Normal file
53
package.json
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "rnr-test",
|
||||||
|
"main": "index.js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "expo start -c",
|
||||||
|
"dev:web": "expo start -c --web",
|
||||||
|
"dev:android": "expo start -c --android",
|
||||||
|
"android": "expo start -c --android",
|
||||||
|
"ios": "expo start -c --ios",
|
||||||
|
"web": "expo start -c --web",
|
||||||
|
"clean": "rm -rf .expo node_modules",
|
||||||
|
"postinstall": "npx tailwindcss -i ./global.css -o ./node_modules/.cache/nativewind/global.css"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@react-navigation/native": "^7.0.0",
|
||||||
|
"@rn-primitives/avatar": "~1.1.0",
|
||||||
|
"@rn-primitives/portal": "~1.1.0",
|
||||||
|
"@rn-primitives/progress": "~1.1.0",
|
||||||
|
"@rn-primitives/slot": "~1.1.0",
|
||||||
|
"@rn-primitives/tooltip": "~1.1.0",
|
||||||
|
"@rn-primitives/types": "~1.1.0",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
|
"expo": "^52.0.25",
|
||||||
|
"expo-linking": "~7.0.4",
|
||||||
|
"expo-navigation-bar": "~4.0.7",
|
||||||
|
"expo-router": "~4.0.16",
|
||||||
|
"expo-splash-screen": "~0.29.20",
|
||||||
|
"expo-status-bar": "~2.0.1",
|
||||||
|
"expo-system-ui": "~4.0.7",
|
||||||
|
"lucide-react-native": "^0.378.0",
|
||||||
|
"nativewind": "^4.1.23",
|
||||||
|
"react": "18.3.1",
|
||||||
|
"react-dom": "18.3.1",
|
||||||
|
"react-native": "0.76.6",
|
||||||
|
"react-native-reanimated": "~3.16.1",
|
||||||
|
"react-native-safe-area-context": "4.12.0",
|
||||||
|
"react-native-screens": "~4.4.0",
|
||||||
|
"react-native-svg": "15.8.0",
|
||||||
|
"react-native-web": "~0.19.13",
|
||||||
|
"tailwind-merge": "^2.2.1",
|
||||||
|
"tailwindcss": "3.3.5",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"zustand": "^4.4.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.26.0",
|
||||||
|
"@types/react": "~18.3.12",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
65
tailwind.config.js
Normal file
65
tailwind.config.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
const { hairlineWidth } = require('nativewind/theme');
|
||||||
|
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
darkMode: 'class',
|
||||||
|
content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
|
||||||
|
presets: [require('nativewind/preset')],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: 'hsl(var(--border))',
|
||||||
|
input: 'hsl(var(--input))',
|
||||||
|
ring: 'hsl(var(--ring))',
|
||||||
|
background: 'hsl(var(--background))',
|
||||||
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
|
foreground: 'hsl(var(--destructive-foreground))',
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
|
foreground: 'hsl(var(--muted-foreground))',
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
|
foreground: 'hsl(var(--accent-foreground))',
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'hsl(var(--card))',
|
||||||
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderWidth: {
|
||||||
|
hairline: hairlineWidth(),
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
'accordion-down': {
|
||||||
|
from: { height: '0' },
|
||||||
|
to: { height: 'var(--radix-accordion-content-height)' },
|
||||||
|
},
|
||||||
|
'accordion-up': {
|
||||||
|
from: { height: 'var(--radix-accordion-content-height)' },
|
||||||
|
to: { height: '0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require('tailwindcss-animate')],
|
||||||
|
};
|
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "expo/tsconfig.base",
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"~/*": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".expo/types/**/*.ts",
|
||||||
|
"expo-env.d.ts",
|
||||||
|
"nativewind-env.d.ts"
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user