rn-cn-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # Native UI
2
+
3
+ A high-quality, open-source UI library for React Native, inspired by shadcn/ui.
4
+
5
+ ## Features
6
+
7
+ - **Minimal Design**: Clean and modern aesthetic.
8
+ - **Highly Reusable**: Built with reusability in mind.
9
+ - **Class Merging**: Uses `clsx` and `tailwind-merge` for easy styling overrides.
10
+ - **NativeWind**: Built on top of NativeWind for Tailwind CSS in React Native.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install rn-cn-ui
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Button
21
+
22
+ ```tsx
23
+ import { Button } from "rn-cn-ui"
24
+
25
+ <Button label="Click me" onPress={() => console.log("Pressed")} />
26
+ <Button variant="destructive" label="Delete" />
27
+ <Button variant="outline" label="Cancel" />
28
+ ```
29
+
30
+ ### Text
31
+
32
+ ```tsx
33
+ import { Text } from "rn-cn-ui"
34
+
35
+ <Text variant="h1">Heading 1</Text>
36
+ <Text variant="lead">This is a lead text.</Text>
37
+ ```
38
+
39
+ ### Input
40
+
41
+ ```tsx
42
+ import { Input, Label } from "rn-cn-ui"
43
+
44
+ <Label>Email</Label>
45
+ <Input placeholder="Enter your email" />
46
+ ```
47
+
48
+ ### Card
49
+
50
+ ```tsx
51
+ import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Button } from "rn-cn-ui"
52
+
53
+ <Card>
54
+ <CardHeader>
55
+ <CardTitle>Card Title</CardTitle>
56
+ <CardDescription>Card Description</CardDescription>
57
+ </CardHeader>
58
+ <CardContent>
59
+ <Text>Content goes here</Text>
60
+ </CardContent>
61
+ <CardFooter>
62
+ <Button label="Action" />
63
+ </CardFooter>
64
+ </Card>
65
+ ```
66
+
67
+ ### Badge
68
+
69
+ ```tsx
70
+ import { Badge } from "rn-cn-ui"
71
+
72
+ <Badge label="New" />
73
+ <Badge variant="destructive" label="Error" />
74
+ ```
75
+
76
+ ### Avatar
77
+
78
+ ```tsx
79
+ import { Avatar, AvatarImage, AvatarFallback, AvatarFallbackText } from "rn-cn-ui"
80
+
81
+ <Avatar>
82
+ <AvatarImage source={{ uri: "https://github.com/shadcn.png" }} />
83
+ <AvatarFallback>
84
+ <AvatarFallbackText>CN</AvatarFallbackText>
85
+ </AvatarFallback>
86
+ </Avatar>
87
+ ```
88
+
89
+ ## Configuration
90
+
91
+ Ensure you have `nativewind` and `tailwindcss` configured in your project.
92
+
93
+ Add the following to your `tailwind.config.js`:
94
+
95
+ ```js
96
+ module.exports = {
97
+ // ...
98
+ presets: [require("nativewind/preset")],
99
+ // ...
100
+ }
101
+ ```
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ presets: ['module:metro-react-native-babel-preset'],
3
+ plugins: ['nativewind/babel'],
4
+ };
package/bin/cli.js ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { execSync } = require('child_process');
5
+
6
+ const BASE_URL = "https://raw.githubusercontent.com/KaloyanBehov/native-ui/main"; // TODO: Update this with your repo
7
+
8
+ const component = process.argv[2];
9
+ const command = process.argv[2];
10
+
11
+ let componentName = null;
12
+ if (command === 'add') {
13
+ componentName = process.argv[3];
14
+ } else {
15
+ componentName = command;
16
+ }
17
+
18
+ if (!componentName) {
19
+ console.error('Usage: npx rn-cn-ui add <component-name>');
20
+ process.exit(1);
21
+ }
22
+
23
+ async function main() {
24
+ try {
25
+ // 1. Fetch registry
26
+ console.log('Fetching registry...');
27
+ const registryResponse = await fetch(`${BASE_URL}/registry.json`);
28
+
29
+ if (!registryResponse.ok) {
30
+ throw new Error(`Failed to fetch registry: ${registryResponse.statusText}`);
31
+ }
32
+
33
+ const registry = await registryResponse.json();
34
+
35
+ if (!registry[componentName]) {
36
+ console.error(`Component "${componentName}" not found in registry.`);
37
+ console.log('Available components:', Object.keys(registry).join(', '));
38
+ process.exit(1);
39
+ }
40
+
41
+ const config = registry[componentName];
42
+ const targetBaseDir = path.join(process.cwd(), 'src', 'components', 'ui');
43
+
44
+ // 2. Create directory
45
+ if (!fs.existsSync(targetBaseDir)) {
46
+ console.log(`Creating directory: ${targetBaseDir}`);
47
+ fs.mkdirSync(targetBaseDir, { recursive: true });
48
+ }
49
+
50
+ // 3. Fetch and write files
51
+ for (const file of config.files) {
52
+ const fileUrl = `${BASE_URL}/${file}`;
53
+ const fileName = path.basename(file);
54
+ const destPath = path.join(targetBaseDir, fileName);
55
+
56
+ console.log(`Downloading ${fileName}...`);
57
+ const fileResponse = await fetch(fileUrl);
58
+
59
+ if (!fileResponse.ok) {
60
+ console.error(`Failed to download ${fileName}`);
61
+ continue;
62
+ }
63
+
64
+ const content = await fileResponse.text();
65
+ fs.writeFileSync(destPath, content);
66
+ }
67
+
68
+ // 4. Install dependencies
69
+ if (config.dependencies && config.dependencies.length > 0) {
70
+ console.log(`Installing dependencies: ${config.dependencies.join(', ')}...`);
71
+ try {
72
+ const userAgent = process.env.npm_config_user_agent;
73
+ let installCmd = 'npm install';
74
+ if (userAgent && userAgent.startsWith('yarn')) {
75
+ installCmd = 'yarn add';
76
+ } else if (userAgent && userAgent.startsWith('pnpm')) {
77
+ installCmd = 'pnpm add';
78
+ } else if (userAgent && userAgent.startsWith('bun')) {
79
+ installCmd = 'bun add';
80
+ }
81
+
82
+ execSync(`${installCmd} ${config.dependencies.join(' ')}`, { stdio: 'inherit' });
83
+ } catch (error) {
84
+ console.error('Failed to install dependencies manually.');
85
+ }
86
+ }
87
+
88
+ console.log(`\nSuccessfully added ${componentName}!`);
89
+
90
+ } catch (error) {
91
+ console.error('Error:', error.message);
92
+ process.exit(1);
93
+ }
94
+ }
95
+
96
+ main();
@@ -0,0 +1 @@
1
+ /// <reference types="nativewind/types" />
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "rn-cn-ui",
3
+ "version": "1.0.0",
4
+ "description": "A high-quality, open-source UI library for React Native, inspired by shadcn/ui.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "rn-cn-ui": "./bin/cli.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "react-native",
14
+ "ui",
15
+ "components",
16
+ "shadcn",
17
+ "tailwind",
18
+ "nativewind"
19
+ ],
20
+ "author": "",
21
+ "license": "ISC",
22
+ "type": "commonjs",
23
+ "devDependencies": {
24
+ "@types/react": "^19.2.13",
25
+ "@types/react-native": "^0.72.8",
26
+ "typescript": "^5.9.3"
27
+ },
28
+ "dependencies": {
29
+ "class-variance-authority": "^0.7.1",
30
+ "clsx": "^2.1.1",
31
+ "lucide-react-native": "^0.563.0",
32
+ "nativewind": "^4.2.1",
33
+ "react": "^19.2.4",
34
+ "react-native": "^0.83.1",
35
+ "tailwind-merge": "^3.4.0",
36
+ "tailwindcss": "^3.4.19"
37
+ }
38
+ }
package/registry.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "button": {
3
+ "name": "button",
4
+ "dependencies": ["class-variance-authority", "clsx", "tailwind-merge"],
5
+ "files": ["src/components/ui/button.tsx"]
6
+ },
7
+ "text": {
8
+ "name": "text",
9
+ "dependencies": ["class-variance-authority", "clsx", "tailwind-merge"],
10
+ "files": ["src/components/ui/text.tsx"]
11
+ },
12
+ "input": {
13
+ "name": "input",
14
+ "dependencies": ["clsx", "tailwind-merge"],
15
+ "files": ["src/components/ui/input.tsx"]
16
+ },
17
+ "card": {
18
+ "name": "card",
19
+ "dependencies": ["clsx", "tailwind-merge"],
20
+ "files": ["src/components/ui/card.tsx"]
21
+ },
22
+ "badge": {
23
+ "name": "badge",
24
+ "dependencies": ["class-variance-authority", "clsx", "tailwind-merge"],
25
+ "files": ["src/components/ui/badge.tsx"]
26
+ },
27
+ "avatar": {
28
+ "name": "avatar",
29
+ "dependencies": ["clsx", "tailwind-merge"],
30
+ "files": ["src/components/ui/avatar.tsx"]
31
+ },
32
+ "label": {
33
+ "name": "label",
34
+ "dependencies": ["class-variance-authority", "clsx", "tailwind-merge"],
35
+ "files": ["src/components/ui/label.tsx"]
36
+ },
37
+ "separator": {
38
+ "name": "separator",
39
+ "dependencies": ["class-variance-authority", "clsx", "tailwind-merge"],
40
+ "files": ["src/components/ui/separator.tsx"]
41
+ },
42
+ "skeleton": {
43
+ "name": "skeleton",
44
+ "dependencies": ["clsx", "tailwind-merge"],
45
+ "files": ["src/components/ui/skeleton.tsx"]
46
+ },
47
+ "spinner": {
48
+ "name": "spinner",
49
+ "dependencies": ["clsx", "tailwind-merge"],
50
+ "files": ["src/components/ui/spinner.tsx"]
51
+ },
52
+ "switch": {
53
+ "name": "switch",
54
+ "dependencies": ["clsx", "tailwind-merge"],
55
+ "files": ["src/components/ui/switch.tsx"]
56
+ },
57
+ "checkbox": {
58
+ "name": "checkbox",
59
+ "dependencies": ["lucide-react-native", "clsx", "tailwind-merge"],
60
+ "files": ["src/components/ui/checkbox.tsx"]
61
+ },
62
+ "radio-group": {
63
+ "name": "radio-group",
64
+ "dependencies": ["lucide-react-native", "clsx", "tailwind-merge"],
65
+ "files": ["src/components/ui/radio-group.tsx"]
66
+ },
67
+ "textarea": {
68
+ "name": "textarea",
69
+ "dependencies": ["clsx", "tailwind-merge"],
70
+ "files": ["src/components/ui/textarea.tsx"]
71
+ },
72
+ "alert": {
73
+ "name": "alert",
74
+ "dependencies": ["class-variance-authority", "clsx", "tailwind-merge"],
75
+ "files": ["src/components/ui/alert.tsx"]
76
+ },
77
+ "progress": {
78
+ "name": "progress",
79
+ "dependencies": ["clsx", "tailwind-merge"],
80
+ "files": ["src/components/ui/progress.tsx"]
81
+ }
82
+ }
@@ -0,0 +1,61 @@
1
+ import * as React from "react"
2
+ import { View } from "react-native"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "../../lib/utils"
5
+ import { Text } from "./text"
6
+
7
+ const alertVariants = cva(
8
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-background text-foreground",
13
+ destructive:
14
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ variant: "default",
19
+ },
20
+ }
21
+ )
22
+
23
+ const Alert = React.forwardRef<
24
+ React.ElementRef<typeof View>,
25
+ React.ComponentPropsWithoutRef<typeof View> &
26
+ VariantProps<typeof alertVariants>
27
+ >(({ className, variant, ...props }, ref) => (
28
+ <View
29
+ ref={ref}
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ ))
35
+ Alert.displayName = "Alert"
36
+
37
+ const AlertTitle = React.forwardRef<
38
+ React.ElementRef<typeof Text>,
39
+ React.ComponentPropsWithoutRef<typeof Text>
40
+ >(({ className, ...props }, ref) => (
41
+ <Text
42
+ ref={ref}
43
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
44
+ {...props}
45
+ />
46
+ ))
47
+ AlertTitle.displayName = "AlertTitle"
48
+
49
+ const AlertDescription = React.forwardRef<
50
+ React.ElementRef<typeof Text>,
51
+ React.ComponentPropsWithoutRef<typeof Text>
52
+ >(({ className, ...props }, ref) => (
53
+ <Text
54
+ ref={ref}
55
+ className={cn("text-sm text-muted-foreground [&_p]:leading-relaxed", className)}
56
+ {...props}
57
+ />
58
+ ))
59
+ AlertDescription.displayName = "AlertDescription"
60
+
61
+ export { Alert, AlertTitle, AlertDescription }
@@ -0,0 +1,60 @@
1
+ import * as React from "react"
2
+ import { Image, View, Text } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const Avatar = React.forwardRef<
6
+ React.ElementRef<typeof View>,
7
+ React.ComponentPropsWithoutRef<typeof View>
8
+ >(({ className, ...props }, ref) => (
9
+ <View
10
+ ref={ref}
11
+ className={cn(
12
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Avatar.displayName = "Avatar"
19
+
20
+ const AvatarImage = React.forwardRef<
21
+ React.ElementRef<typeof Image>,
22
+ React.ComponentPropsWithoutRef<typeof Image>
23
+ >(({ className, ...props }, ref) => (
24
+ <Image
25
+ ref={ref}
26
+ className={cn("aspect-square h-full w-full", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ AvatarImage.displayName = "AvatarImage"
31
+
32
+ const AvatarFallback = React.forwardRef<
33
+ React.ElementRef<typeof View>,
34
+ React.ComponentPropsWithoutRef<typeof View>
35
+ >(({ className, ...props }, ref) => (
36
+ <View
37
+ ref={ref}
38
+ className={cn(
39
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ AvatarFallback.displayName = "AvatarFallback"
46
+
47
+ const AvatarFallbackText = React.forwardRef<
48
+ React.ElementRef<typeof Text>,
49
+ React.ComponentPropsWithoutRef<typeof Text>
50
+ >(({ className, ...props }, ref) => (
51
+ <Text
52
+ ref={ref}
53
+ className={cn("text-sm font-medium text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ AvatarFallbackText.displayName = "AvatarFallbackText"
58
+
59
+
60
+ export { Avatar, AvatarImage, AvatarFallback, AvatarFallbackText }
@@ -0,0 +1,58 @@
1
+ import * as React from "react"
2
+ import { View, Text } from "react-native"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary shadow hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive shadow hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ const badgeTextVariants = cva("text-xs font-semibold", {
27
+ variants: {
28
+ variant: {
29
+ default: "text-primary-foreground",
30
+ secondary: "text-secondary-foreground",
31
+ destructive: "text-destructive-foreground",
32
+ outline: "text-foreground",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "default",
37
+ },
38
+ })
39
+
40
+ export interface BadgeProps
41
+ extends React.ComponentPropsWithoutRef<typeof View>,
42
+ VariantProps<typeof badgeVariants> {
43
+ label?: string
44
+ }
45
+
46
+ function Badge({ className, variant, label, children, ...props }: BadgeProps) {
47
+ return (
48
+ <View className={cn(badgeVariants({ variant }), className)} {...props}>
49
+ {label ? (
50
+ <Text className={cn(badgeTextVariants({ variant }))}>{label}</Text>
51
+ ) : (
52
+ children
53
+ )}
54
+ </View>
55
+ )
56
+ }
57
+
58
+ export { Badge, badgeVariants }
@@ -0,0 +1,79 @@
1
+ import * as React from "react"
2
+ import { Text, TouchableOpacity, View } from "react-native"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const buttonVariants = cva(
7
+ "flex-row items-center justify-center rounded-md",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary",
12
+ destructive: "bg-destructive",
13
+ outline: "border border-input bg-background",
14
+ secondary: "bg-secondary",
15
+ ghost: "hover:bg-accent hover:text-accent-foreground",
16
+ link: "text-primary underline-offset-4 hover:underline",
17
+ },
18
+ size: {
19
+ default: "h-10 px-4 py-2",
20
+ sm: "h-9 rounded-md px-3",
21
+ lg: "h-11 rounded-md px-8",
22
+ icon: "h-10 w-10",
23
+ },
24
+ },
25
+ defaultVariants: {
26
+ variant: "default",
27
+ size: "default",
28
+ },
29
+ }
30
+ )
31
+
32
+ const buttonTextVariants = cva(
33
+ "text-sm font-medium text-primary-foreground",
34
+ {
35
+ variants: {
36
+ variant: {
37
+ default: "text-primary-foreground",
38
+ destructive: "text-destructive-foreground",
39
+ outline: "text-foreground",
40
+ secondary: "text-secondary-foreground",
41
+ ghost: "text-foreground",
42
+ link: "text-primary",
43
+ },
44
+ },
45
+ defaultVariants: {
46
+ variant: "default",
47
+ },
48
+ }
49
+ )
50
+
51
+ export interface ButtonProps
52
+ extends React.ComponentPropsWithoutRef<typeof TouchableOpacity>,
53
+ VariantProps<typeof buttonVariants> {
54
+ label?: string
55
+ labelClasses?: string
56
+ }
57
+
58
+ const Button = React.forwardRef<React.ElementRef<typeof TouchableOpacity>, ButtonProps>(
59
+ ({ className, variant, size, label, labelClasses, children, ...props }, ref) => {
60
+ return (
61
+ <TouchableOpacity
62
+ className={cn(buttonVariants({ variant, size, className }))}
63
+ ref={ref}
64
+ {...props}
65
+ >
66
+ {label ? (
67
+ <Text className={cn(buttonTextVariants({ variant }), labelClasses)}>
68
+ {label}
69
+ </Text>
70
+ ) : (
71
+ children
72
+ )}
73
+ </TouchableOpacity>
74
+ )
75
+ }
76
+ )
77
+ Button.displayName = "Button"
78
+
79
+ export { Button, buttonVariants }
@@ -0,0 +1,80 @@
1
+ import * as React from "react"
2
+ import { Text, View } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+ import { Text as UiText } from "./text"
5
+
6
+ const Card = React.forwardRef<
7
+ React.ElementRef<typeof View>,
8
+ React.ComponentPropsWithoutRef<typeof View>
9
+ >(({ className, ...props }, ref) => (
10
+ <View
11
+ ref={ref}
12
+ className={cn(
13
+ "rounded-lg border border-border bg-card shadow-sm",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ ))
19
+ Card.displayName = "Card"
20
+
21
+ const CardHeader = React.forwardRef<
22
+ React.ElementRef<typeof View>,
23
+ React.ComponentPropsWithoutRef<typeof View>
24
+ >(({ className, ...props }, ref) => (
25
+ <View
26
+ ref={ref}
27
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
28
+ {...props}
29
+ />
30
+ ))
31
+ CardHeader.displayName = "CardHeader"
32
+
33
+ const CardTitle = React.forwardRef<
34
+ React.ElementRef<typeof UiText>,
35
+ React.ComponentPropsWithoutRef<typeof UiText>
36
+ >(({ className, ...props }, ref) => (
37
+ <UiText
38
+ ref={ref}
39
+ className={cn(
40
+ "text-2xl font-semibold leading-none tracking-tight",
41
+ className
42
+ )}
43
+ {...props}
44
+ />
45
+ ))
46
+ CardTitle.displayName = "CardTitle"
47
+
48
+ const CardDescription = React.forwardRef<
49
+ React.ElementRef<typeof UiText>,
50
+ React.ComponentPropsWithoutRef<typeof UiText>
51
+ >(({ className, ...props }, ref) => (
52
+ <UiText
53
+ ref={ref}
54
+ className={cn("text-sm text-muted-foreground", className)}
55
+ {...props}
56
+ />
57
+ ))
58
+ CardDescription.displayName = "CardDescription"
59
+
60
+ const CardContent = React.forwardRef<
61
+ React.ElementRef<typeof View>,
62
+ React.ComponentPropsWithoutRef<typeof View>
63
+ >(({ className, ...props }, ref) => (
64
+ <View ref={ref} className={cn("p-6 pt-0", className)} {...props} />
65
+ ))
66
+ CardContent.displayName = "CardContent"
67
+
68
+ const CardFooter = React.forwardRef<
69
+ React.ElementRef<typeof View>,
70
+ React.ComponentPropsWithoutRef<typeof View>
71
+ >(({ className, ...props }, ref) => (
72
+ <View
73
+ ref={ref}
74
+ className={cn("flex flex-row items-center p-6 pt-0", className)}
75
+ {...props}
76
+ />
77
+ ))
78
+ CardFooter.displayName = "CardFooter"
79
+
80
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -0,0 +1,38 @@
1
+ import * as React from "react"
2
+ import { Pressable, View } from "react-native"
3
+ import { Check } from "lucide-react-native"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ interface CheckboxProps extends React.ComponentPropsWithoutRef<typeof Pressable> {
7
+ checked?: boolean
8
+ onCheckedChange?: (checked: boolean) => void
9
+ }
10
+
11
+ const Checkbox = React.forwardRef<React.ElementRef<typeof Pressable>, CheckboxProps>(
12
+ ({ className, checked, onCheckedChange, disabled, ...props }, ref) => {
13
+ return (
14
+ <Pressable
15
+ ref={ref}
16
+ role="checkbox"
17
+ aria-checked={checked}
18
+ disabled={disabled}
19
+ onPress={() => onCheckedChange?.(!checked)}
20
+ className={cn(
21
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary bg-background ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
22
+ checked && "bg-primary text-primary-foreground",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {checked && (
28
+ <View className="flex items-center justify-center h-full w-full">
29
+ <Check size={12} color={checked ? "hsl(var(--primary-foreground))" : "transparent"} strokeWidth={3} />
30
+ </View>
31
+ )}
32
+ </Pressable>
33
+ )
34
+ }
35
+ )
36
+ Checkbox.displayName = "Checkbox"
37
+
38
+ export { Checkbox }
@@ -0,0 +1,25 @@
1
+ import * as React from "react"
2
+ import { TextInput, View } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ export interface InputProps
6
+ extends React.ComponentPropsWithoutRef<typeof TextInput> {}
7
+
8
+ const Input = React.forwardRef<React.ElementRef<typeof TextInput>, InputProps>(
9
+ ({ className, placeholderTextColor, ...props }, ref) => {
10
+ return (
11
+ <TextInput
12
+ ref={ref}
13
+ className={cn(
14
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
15
+ className
16
+ )}
17
+ placeholderTextColor={placeholderTextColor || "hsl(var(--muted-foreground))"}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+ )
23
+ Input.displayName = "Input"
24
+
25
+ export { Input }
@@ -0,0 +1,19 @@
1
+ import * as React from "react"
2
+ import { Text } from "react-native"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const labelVariants = cva(
7
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-foreground"
8
+ )
9
+
10
+ const Label = React.forwardRef<
11
+ React.ElementRef<typeof Text>,
12
+ React.ComponentPropsWithoutRef<typeof Text> &
13
+ VariantProps<typeof labelVariants>
14
+ >(({ className, ...props }, ref) => (
15
+ <Text ref={ref} className={cn(labelVariants(), className)} {...props} />
16
+ ))
17
+ Label.displayName = "Label"
18
+
19
+ export { Label }
@@ -0,0 +1,33 @@
1
+ import * as React from "react"
2
+ import { View } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ interface ProgressProps extends React.ComponentPropsWithoutRef<typeof View> {
6
+ value?: number
7
+ max?: number
8
+ }
9
+
10
+ const Progress = React.forwardRef<React.ElementRef<typeof View>, ProgressProps>(
11
+ ({ className, value = 0, max = 100, ...props }, ref) => {
12
+ const percentage = Math.min(Math.max(value || 0, 0), max) / max * 100
13
+
14
+ return (
15
+ <View
16
+ ref={ref}
17
+ className={cn(
18
+ "relative h-4 w-full overflow-hidden rounded-full bg-secondary",
19
+ className
20
+ )}
21
+ {...props}
22
+ >
23
+ <View
24
+ className="h-full w-full flex-1 bg-primary transition-all"
25
+ style={{ width: `${percentage}%` }}
26
+ />
27
+ </View>
28
+ )
29
+ }
30
+ )
31
+ Progress.displayName = "Progress"
32
+
33
+ export { Progress }
@@ -0,0 +1,55 @@
1
+ import * as React from "react"
2
+ import { View, Pressable } from "react-native"
3
+ import { Circle } from "lucide-react-native"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const RadioGroup = React.forwardRef<
7
+ React.ElementRef<typeof View>,
8
+ React.ComponentPropsWithoutRef<typeof View> & {
9
+ value?: string
10
+ onValueChange?: (value: string) => void
11
+ }
12
+ >(({ className, value, onValueChange, children, ...props }, ref) => {
13
+ return (
14
+ <View ref={ref} className={cn("gap-2", className)} {...props}>
15
+ {React.Children.map(children, (child) => {
16
+ if (React.isValidElement(child)) {
17
+ return React.cloneElement(child, {
18
+ // @ts-ignore
19
+ checked: child.props.value === value,
20
+ // @ts-ignore
21
+ onPress: () => onValueChange?.(child.props.value),
22
+ })
23
+ }
24
+ return child
25
+ })}
26
+ </View>
27
+ )
28
+ })
29
+ RadioGroup.displayName = "RadioGroup"
30
+
31
+ const RadioGroupItem = React.forwardRef<
32
+ React.ElementRef<typeof Pressable>,
33
+ React.ComponentPropsWithoutRef<typeof Pressable> & {
34
+ value: string
35
+ checked?: boolean
36
+ }
37
+ >(({ className, value, checked, ...props }, ref) => {
38
+ return (
39
+ <Pressable
40
+ ref={ref}
41
+ className={cn(
42
+ "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 items-center justify-center",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ {checked && (
48
+ <View className="h-2.5 w-2.5 rounded-full bg-primary" />
49
+ )}
50
+ </Pressable>
51
+ )
52
+ })
53
+ RadioGroupItem.displayName = "RadioGroupItem"
54
+
55
+ export { RadioGroup, RadioGroupItem }
@@ -0,0 +1,43 @@
1
+ import * as React from "react"
2
+ import { View } from "react-native"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const separatorVariants = cva("bg-border shrink-0", {
7
+ variants: {
8
+ orientation: {
9
+ horizontal: "h-[1px] w-full",
10
+ vertical: "h-full w-[1px]",
11
+ },
12
+ },
13
+ defaultVariants: {
14
+ orientation: "horizontal",
15
+ },
16
+ })
17
+
18
+ export interface SeparatorProps
19
+ extends React.ComponentPropsWithoutRef<typeof View>,
20
+ VariantProps<typeof separatorVariants> {
21
+ orientation?: "horizontal" | "vertical"
22
+ decorative?: boolean
23
+ }
24
+
25
+ const Separator = React.forwardRef<
26
+ React.ElementRef<typeof View>,
27
+ SeparatorProps
28
+ >(
29
+ (
30
+ { className, orientation = "horizontal", decorative = true, ...props },
31
+ ref
32
+ ) => (
33
+ <View
34
+ ref={ref}
35
+ className={cn(separatorVariants({ orientation }), className)}
36
+ role={decorative ? "none" : "separator"}
37
+ {...props}
38
+ />
39
+ )
40
+ )
41
+ Separator.displayName = "Separator"
42
+
43
+ export { Separator }
@@ -0,0 +1,41 @@
1
+ import * as React from "react"
2
+ import { Animated, View, StyleSheet, Easing } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ function Skeleton({
6
+ className,
7
+ ...props
8
+ }: React.ComponentPropsWithoutRef<typeof View>) {
9
+ const pulseAnim = React.useRef(new Animated.Value(0.5)).current
10
+
11
+ React.useEffect(() => {
12
+ const sharedAnimationConfig = {
13
+ duration: 1000,
14
+ useNativeDriver: true,
15
+ }
16
+ Animated.loop(
17
+ Animated.sequence([
18
+ Animated.timing(pulseAnim, {
19
+ ...sharedAnimationConfig,
20
+ toValue: 1,
21
+ easing: Easing.out(Easing.ease),
22
+ }),
23
+ Animated.timing(pulseAnim, {
24
+ ...sharedAnimationConfig,
25
+ toValue: 0.5,
26
+ easing: Easing.in(Easing.ease),
27
+ }),
28
+ ])
29
+ ).start()
30
+ }, [pulseAnim])
31
+
32
+ return (
33
+ <Animated.View
34
+ style={{ opacity: pulseAnim }}
35
+ className={cn("rounded-md bg-muted", className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ export { Skeleton }
@@ -0,0 +1,25 @@
1
+ import * as React from "react"
2
+ import { ActivityIndicator, View } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ export interface SpinnerProps
6
+ extends React.ComponentPropsWithoutRef<typeof ActivityIndicator> {
7
+ className?: string
8
+ }
9
+
10
+ const Spinner = React.forwardRef<
11
+ React.ElementRef<typeof ActivityIndicator>,
12
+ SpinnerProps
13
+ >(({ className, color, ...props }, ref) => {
14
+ return (
15
+ <ActivityIndicator
16
+ ref={ref}
17
+ color={color}
18
+ className={cn("text-primary", className)}
19
+ {...props}
20
+ />
21
+ )
22
+ })
23
+ Spinner.displayName = "Spinner"
24
+
25
+ export { Spinner }
@@ -0,0 +1,23 @@
1
+ import * as React from "react"
2
+ import { Switch as RNSwitch, Platform } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const Switch = React.forwardRef<
6
+ React.ElementRef<typeof RNSwitch>,
7
+ React.ComponentPropsWithoutRef<typeof RNSwitch>
8
+ >(({ className, ...props }, ref) => (
9
+ <RNSwitch
10
+ ref={ref}
11
+ className={cn(
12
+ "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
13
+ className
14
+ )}
15
+ trackColor={{ false: "hsl(var(--input))", true: "hsl(var(--primary))" }}
16
+ thumbColor={Platform.OS === "android" ? "hsl(var(--background))" : undefined}
17
+ ios_backgroundColor="hsl(var(--input))"
18
+ {...props}
19
+ />
20
+ ))
21
+ Switch.displayName = "Switch"
22
+
23
+ export { Switch }
@@ -0,0 +1,47 @@
1
+ import * as React from "react"
2
+ import { Text as RNText } from "react-native"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const textVariants = cva(
7
+ "text-foreground",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "text-base",
12
+ h1: "text-4xl font-extrabold tracking-tight lg:text-5xl",
13
+ h2: "text-3xl font-semibold tracking-tight first:mt-0",
14
+ h3: "text-2xl font-semibold tracking-tight",
15
+ h4: "text-xl font-semibold tracking-tight",
16
+ p: "leading-7",
17
+ blockquote: "mt-6 border-l-2 pl-6 italic",
18
+ lead: "text-xl text-muted-foreground",
19
+ large: "text-lg font-semibold",
20
+ small: "text-sm font-medium leading-none",
21
+ muted: "text-sm text-muted-foreground",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ },
27
+ }
28
+ )
29
+
30
+ export interface TextProps
31
+ extends React.ComponentPropsWithoutRef<typeof RNText>,
32
+ VariantProps<typeof textVariants> {}
33
+
34
+ const Text = React.forwardRef<React.ElementRef<typeof RNText>, TextProps>(
35
+ ({ className, variant, ...props }, ref) => {
36
+ return (
37
+ <RNText
38
+ className={cn(textVariants({ variant, className }))}
39
+ ref={ref}
40
+ {...props}
41
+ />
42
+ )
43
+ }
44
+ )
45
+ Text.displayName = "Text"
46
+
47
+ export { Text, textVariants }
@@ -0,0 +1,28 @@
1
+ import * as React from "react"
2
+ import { TextInput } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ export interface TextareaProps
6
+ extends React.ComponentPropsWithoutRef<typeof TextInput> {}
7
+
8
+ const Textarea = React.forwardRef<React.ElementRef<typeof TextInput>, TextareaProps>(
9
+ ({ className, numberOfLines = 4, placeholderTextColor, ...props }, ref) => {
10
+ return (
11
+ <TextInput
12
+ ref={ref}
13
+ multiline
14
+ numberOfLines={numberOfLines}
15
+ textAlignVertical="top"
16
+ className={cn(
17
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
18
+ className
19
+ )}
20
+ placeholderTextColor={placeholderTextColor || "hsl(var(--muted-foreground))"}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+ )
26
+ Textarea.displayName = "Textarea"
27
+
28
+ export { Textarea }
package/src/global.css ADDED
@@ -0,0 +1,50 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 240 10% 3.9%;
9
+ --card: 0 0% 100%;
10
+ --card-foreground: 240 10% 3.9%;
11
+ --popover: 0 0% 100%;
12
+ --popover-foreground: 240 10% 3.9%;
13
+ --primary: 240 5.9% 10%;
14
+ --primary-foreground: 0 0% 98%;
15
+ --secondary: 240 4.8% 95.9%;
16
+ --secondary-foreground: 240 5.9% 10%;
17
+ --muted: 240 4.8% 95.9%;
18
+ --muted-foreground: 240 3.8% 46.1%;
19
+ --accent: 240 4.8% 95.9%;
20
+ --accent-foreground: 240 5.9% 10%;
21
+ --destructive: 0 84.2% 60.2%;
22
+ --destructive-foreground: 0 0% 98%;
23
+ --border: 240 5.9% 90%;
24
+ --input: 240 5.9% 90%;
25
+ --ring: 240 10% 3.9%;
26
+ --radius: 0.5rem;
27
+ }
28
+
29
+ .dark {
30
+ --background: 240 10% 3.9%;
31
+ --foreground: 0 0% 98%;
32
+ --card: 240 10% 3.9%;
33
+ --card-foreground: 0 0% 98%;
34
+ --popover: 240 10% 3.9%;
35
+ --popover-foreground: 0 0% 98%;
36
+ --primary: 0 0% 98%;
37
+ --primary-foreground: 240 5.9% 10%;
38
+ --secondary: 240 3.7% 15.6%;
39
+ --secondary-foreground: 0 0% 98%;
40
+ --muted: 240 3.7% 15.6%;
41
+ --muted-foreground: 240 5% 64.9%;
42
+ --accent: 240 3.7% 15.6%;
43
+ --accent-foreground: 0 0% 98%;
44
+ --destructive: 0 62.8% 30.6%;
45
+ --destructive-foreground: 0 0% 98%;
46
+ --border: 240 3.7% 15.6%;
47
+ --input: 240 3.7% 15.6%;
48
+ --ring: 240 4.9% 83.9%;
49
+ }
50
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ export * from "./lib/utils"
2
+ export * from "./components/ui/button"
3
+ export * from "./components/ui/text"
4
+ export * from "./components/ui/input"
5
+ export * from "./components/ui/card"
6
+ export * from "./components/ui/badge"
7
+ export * from "./components/ui/avatar"
8
+ export * from "./components/ui/label"
9
+ export * from "./components/ui/separator"
10
+ export * from "./components/ui/skeleton"
11
+ export * from "./components/ui/spinner"
12
+ export * from "./components/ui/switch"
13
+ export * from "./components/ui/checkbox"
14
+ export * from "./components/ui/radio-group"
15
+ export * from "./components/ui/textarea"
16
+ export * from "./components/ui/alert"
17
+ export * from "./components/ui/progress"
18
+ import "./global.css"
@@ -0,0 +1,50 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: ["./src/**/*.{js,jsx,ts,tsx}"],
4
+ presets: [require("nativewind/preset")],
5
+ theme: {
6
+ extend: {
7
+ colors: {
8
+ border: "hsl(var(--border))",
9
+ input: "hsl(var(--input))",
10
+ ring: "hsl(var(--ring))",
11
+ background: "hsl(var(--background))",
12
+ foreground: "hsl(var(--foreground))",
13
+ primary: {
14
+ DEFAULT: "hsl(var(--primary))",
15
+ foreground: "hsl(var(--primary-foreground))",
16
+ },
17
+ secondary: {
18
+ DEFAULT: "hsl(var(--secondary))",
19
+ foreground: "hsl(var(--secondary-foreground))",
20
+ },
21
+ destructive: {
22
+ DEFAULT: "hsl(var(--destructive))",
23
+ foreground: "hsl(var(--destructive-foreground))",
24
+ },
25
+ muted: {
26
+ DEFAULT: "hsl(var(--muted))",
27
+ foreground: "hsl(var(--muted-foreground))",
28
+ },
29
+ accent: {
30
+ DEFAULT: "hsl(var(--accent))",
31
+ foreground: "hsl(var(--accent-foreground))",
32
+ },
33
+ popover: {
34
+ DEFAULT: "hsl(var(--popover))",
35
+ foreground: "hsl(var(--popover-foreground))",
36
+ },
37
+ card: {
38
+ DEFAULT: "hsl(var(--card))",
39
+ foreground: "hsl(var(--card-foreground))",
40
+ },
41
+ },
42
+ borderRadius: {
43
+ lg: "var(--radius)",
44
+ md: "calc(var(--radius) - 2px)",
45
+ sm: "calc(var(--radius) - 4px)",
46
+ },
47
+ },
48
+ },
49
+ plugins: [],
50
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "allowSyntheticDefaultImports": true,
5
+ "esModuleInterop": true,
6
+ "isolatedModules": true,
7
+ "jsx": "react-native",
8
+ "lib": ["es2017"],
9
+ "moduleResolution": "node",
10
+ "noEmit": true,
11
+ "strict": true,
12
+ "target": "esnext",
13
+ "skipLibCheck": true,
14
+ "baseUrl": ".",
15
+ "paths": {
16
+ "~/*": ["src/*"]
17
+ }
18
+ },
19
+ "exclude": ["node_modules", "babel.config.js", "metro.config.js", "jest.config.js"]
20
+ }