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 +101 -0
- package/babel.config.js +4 -0
- package/bin/cli.js +96 -0
- package/nativewind-env.d.ts +1 -0
- package/package.json +38 -0
- package/registry.json +82 -0
- package/src/components/ui/alert.tsx +61 -0
- package/src/components/ui/avatar.tsx +60 -0
- package/src/components/ui/badge.tsx +58 -0
- package/src/components/ui/button.tsx +79 -0
- package/src/components/ui/card.tsx +80 -0
- package/src/components/ui/checkbox.tsx +38 -0
- package/src/components/ui/input.tsx +25 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/progress.tsx +33 -0
- package/src/components/ui/radio-group.tsx +55 -0
- package/src/components/ui/separator.tsx +43 -0
- package/src/components/ui/skeleton.tsx +41 -0
- package/src/components/ui/spinner.tsx +25 -0
- package/src/components/ui/switch.tsx +23 -0
- package/src/components/ui/text.tsx +47 -0
- package/src/components/ui/textarea.tsx +28 -0
- package/src/global.css +50 -0
- package/src/index.ts +18 -0
- package/tailwind.config.js +50 -0
- package/tsconfig.json +20 -0
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
|
+
```
|
package/babel.config.js
ADDED
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
|
+
}
|