rn-cn-ui 1.0.0 → 1.0.1

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 CHANGED
@@ -1,90 +1,63 @@
1
- # Native UI
1
+ # rn-cn-ui
2
2
 
3
3
  A high-quality, open-source UI library for React Native, inspired by shadcn/ui.
4
+ **This is a CLI tool** that copies the component code into your project, allowing you to customize it fully.
4
5
 
5
- ## Features
6
+ ## Prerequisites
6
7
 
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
- ```
8
+ 1. **React Native** project (Expo or CLI).
9
+ 2. **NativeWind** and **Tailwind CSS** configured.
17
10
 
18
11
  ## Usage
19
12
 
20
- ### Button
13
+ You do not install this library as a dependency. Instead, you use the CLI to add components to your project.
21
14
 
22
- ```tsx
23
- import { Button } from "rn-cn-ui"
15
+ ### Adding a Component
24
16
 
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
- ```
17
+ Run the following command to add a component (e.g., `button`) to your project:
38
18
 
39
- ### Input
40
-
41
- ```tsx
42
- import { Input, Label } from "rn-cn-ui"
43
-
44
- <Label>Email</Label>
45
- <Input placeholder="Enter your email" />
19
+ ```bash
20
+ npx rn-cn-ui add button
46
21
  ```
47
22
 
48
- ### Card
23
+ This will:
24
+ 1. Download `button.tsx` to your `src/components/ui/` directory.
25
+ 2. Install necessary dependencies (like `class-variance-authority`, `clsx`, `tailwind-merge`).
49
26
 
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
- ```
27
+ ### Using the Component
66
28
 
67
- ### Badge
29
+ Once added, import the component from your local directory:
68
30
 
69
31
  ```tsx
70
- import { Badge } from "rn-cn-ui"
32
+ import { Button } from "./src/components/ui/button"
71
33
 
72
- <Badge label="New" />
73
- <Badge variant="destructive" label="Error" />
34
+ export default function App() {
35
+ return (
36
+ <Button label="Click me" onPress={() => console.log("Pressed")} />
37
+ )
38
+ }
74
39
  ```
75
40
 
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
- ```
41
+ ## Available Components
42
+
43
+ You can add any of the following components:
44
+
45
+ - `button`
46
+ - `text`
47
+ - `input`
48
+ - `card`
49
+ - `badge`
50
+ - `avatar`
51
+ - `label`
52
+ - `separator`
53
+ - `skeleton`
54
+ - `spinner`
55
+ - `switch`
56
+ - `checkbox`
57
+ - `radio-group`
58
+ - `textarea`
59
+ - `alert`
60
+ - `progress`
88
61
 
89
62
  ## Configuration
90
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-cn-ui",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A high-quality, open-source UI library for React Native, inspired by shadcn/ui.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/registry.json CHANGED
@@ -78,5 +78,40 @@
78
78
  "name": "progress",
79
79
  "dependencies": ["clsx", "tailwind-merge"],
80
80
  "files": ["src/components/ui/progress.tsx"]
81
+ },
82
+ "accordion": {
83
+ "name": "accordion",
84
+ "dependencies": ["lucide-react-native", "clsx", "tailwind-merge"],
85
+ "files": ["src/components/ui/accordion.tsx"]
86
+ },
87
+ "aspect-ratio": {
88
+ "name": "aspect-ratio",
89
+ "dependencies": ["clsx", "tailwind-merge"],
90
+ "files": ["src/components/ui/aspect-ratio.tsx"]
91
+ },
92
+ "dialog": {
93
+ "name": "dialog",
94
+ "dependencies": ["lucide-react-native", "clsx", "tailwind-merge"],
95
+ "files": ["src/components/ui/dialog.tsx"]
96
+ },
97
+ "select": {
98
+ "name": "select",
99
+ "dependencies": ["lucide-react-native", "clsx", "tailwind-merge"],
100
+ "files": ["src/components/ui/select.tsx"]
101
+ },
102
+ "tabs": {
103
+ "name": "tabs",
104
+ "dependencies": ["clsx", "tailwind-merge"],
105
+ "files": ["src/components/ui/tabs.tsx"]
106
+ },
107
+ "toggle": {
108
+ "name": "toggle",
109
+ "dependencies": ["class-variance-authority", "clsx", "tailwind-merge"],
110
+ "files": ["src/components/ui/toggle.tsx"]
111
+ },
112
+ "toggle-group": {
113
+ "name": "toggle-group",
114
+ "dependencies": ["class-variance-authority", "clsx", "tailwind-merge"],
115
+ "files": ["src/components/ui/toggle-group.tsx", "src/components/ui/toggle.tsx"]
81
116
  }
82
117
  }
@@ -0,0 +1,109 @@
1
+ import * as React from "react"
2
+ import { View, Pressable, Text, LayoutAnimation, Platform, UIManager } from "react-native"
3
+ import { ChevronDown } from "lucide-react-native"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ if (
7
+ Platform.OS === "android" &&
8
+ UIManager.setLayoutAnimationEnabledExperimental
9
+ ) {
10
+ UIManager.setLayoutAnimationEnabledExperimental(true)
11
+ }
12
+
13
+ const Accordion = React.forwardRef<
14
+ React.ElementRef<typeof View>,
15
+ React.ComponentPropsWithoutRef<typeof View> & {
16
+ type?: "single" | "multiple"
17
+ collapsible?: boolean
18
+ defaultValue?: string | string[]
19
+ onValueChange?: (value: string | string[]) => void
20
+ }
21
+ >(({ className, type = "single", collapsible = false, defaultValue, onValueChange, children, ...props }, ref) => {
22
+ const [value, setValue] = React.useState<string | string[]>(defaultValue || (type === "multiple" ? [] : ""))
23
+
24
+ const handleValueChange = (itemValue: string) => {
25
+ if (type === "single") {
26
+ const newValue = value === itemValue && collapsible ? "" : itemValue
27
+ setValue(newValue)
28
+ onValueChange?.(newValue)
29
+ } else {
30
+ const currentValues = Array.isArray(value) ? value : []
31
+ const newValue = currentValues.includes(itemValue)
32
+ ? currentValues.filter((v) => v !== itemValue)
33
+ : [...currentValues, itemValue]
34
+ setValue(newValue)
35
+ onValueChange?.(newValue)
36
+ }
37
+ }
38
+
39
+ return (
40
+ <View ref={ref} className={cn("gap-2", className)} {...props}>
41
+ {React.Children.map(children, (child) => {
42
+ if (React.isValidElement(child)) {
43
+ return React.cloneElement(child, {
44
+ // @ts-ignore
45
+ expanded: Array.isArray(value) ? value.includes(child.props.value) : value === child.props.value,
46
+ // @ts-ignore
47
+ onPress: () => handleValueChange(child.props.value),
48
+ })
49
+ }
50
+ return child
51
+ })}
52
+ </View>
53
+ )
54
+ })
55
+ Accordion.displayName = "Accordion"
56
+
57
+ const AccordionItem = React.forwardRef<
58
+ React.ElementRef<typeof View>,
59
+ React.ComponentPropsWithoutRef<typeof View> & { value: string }
60
+ >(({ className, ...props }, ref) => (
61
+ <View ref={ref} className={cn("border-b border-border", className)} {...props} />
62
+ ))
63
+ AccordionItem.displayName = "AccordionItem"
64
+
65
+ const AccordionTrigger = React.forwardRef<
66
+ React.ElementRef<typeof Pressable>,
67
+ React.ComponentPropsWithoutRef<typeof Pressable> & { expanded?: boolean }
68
+ >(({ className, children, expanded, onPress, ...props }, ref) => (
69
+ <Pressable
70
+ ref={ref}
71
+ onPress={(e) => {
72
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
73
+ onPress?.(e)
74
+ }}
75
+ className={cn(
76
+ "flex-row items-center justify-between py-4 font-medium transition-all",
77
+ className
78
+ )}
79
+ {...props}
80
+ >
81
+ {typeof children === 'string' ? (
82
+ <Text className="text-sm font-medium text-foreground">{children}</Text>
83
+ ) : children}
84
+ <ChevronDown
85
+ size={18}
86
+ className={cn("text-muted-foreground transition-transform duration-200", expanded ? "rotate-180" : "")}
87
+ />
88
+ </Pressable>
89
+ ))
90
+ AccordionTrigger.displayName = "AccordionTrigger"
91
+
92
+ const AccordionContent = React.forwardRef<
93
+ React.ElementRef<typeof View>,
94
+ React.ComponentPropsWithoutRef<typeof View> & { expanded?: boolean }
95
+ >(({ className, children, expanded, ...props }, ref) => {
96
+ if (!expanded) return null
97
+ return (
98
+ <View
99
+ ref={ref}
100
+ className={cn("overflow-hidden text-sm transition-all pb-4", className)}
101
+ {...props}
102
+ >
103
+ <View className="pt-0 pb-4">{children}</View>
104
+ </View>
105
+ )
106
+ })
107
+ AccordionContent.displayName = "AccordionContent"
108
+
109
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
@@ -0,0 +1,21 @@
1
+ import * as React from "react"
2
+ import { View } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ interface AspectRatioProps extends React.ComponentPropsWithoutRef<typeof View> {
6
+ ratio?: number
7
+ }
8
+
9
+ const AspectRatio = React.forwardRef<React.ElementRef<typeof View>, AspectRatioProps>(
10
+ ({ className, ratio = 1, style, ...props }, ref) => (
11
+ <View
12
+ ref={ref}
13
+ style={[style, { aspectRatio: ratio }]}
14
+ className={cn("w-full", className)}
15
+ {...props}
16
+ />
17
+ )
18
+ )
19
+ AspectRatio.displayName = "AspectRatio"
20
+
21
+ export { AspectRatio }
@@ -0,0 +1,100 @@
1
+ import * as React from "react"
2
+ import { Modal, View, Pressable, Text } from "react-native"
3
+ import { X } from "lucide-react-native"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const Dialog = React.forwardRef<
7
+ React.ElementRef<typeof Modal>,
8
+ React.ComponentPropsWithoutRef<typeof Modal> & {
9
+ open?: boolean
10
+ onOpenChange?: (open: boolean) => void
11
+ }
12
+ >(({ className, children, open, onOpenChange, ...props }, ref) => (
13
+ <Modal
14
+ ref={ref}
15
+ transparent
16
+ animationType="fade"
17
+ visible={open}
18
+ onRequestClose={() => onOpenChange?.(false)}
19
+ {...props}
20
+ >
21
+ <View className="flex-1 items-center justify-center bg-black/50 p-4">
22
+ <Pressable className="absolute inset-0" onPress={() => onOpenChange?.(false)} />
23
+ <View
24
+ className={cn(
25
+ "w-full max-w-lg gap-4 rounded-lg border border-border bg-background p-6 shadow-lg",
26
+ className
27
+ )}
28
+ >
29
+ {children}
30
+ <Pressable
31
+ className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"
32
+ onPress={() => onOpenChange?.(false)}
33
+ >
34
+ <X size={18} className="text-muted-foreground" />
35
+ </Pressable>
36
+ </View>
37
+ </View>
38
+ </Modal>
39
+ ))
40
+ Dialog.displayName = "Dialog"
41
+
42
+ const DialogHeader = ({
43
+ className,
44
+ ...props
45
+ }: React.ComponentPropsWithoutRef<typeof View>) => (
46
+ <View
47
+ className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}
48
+ {...props}
49
+ />
50
+ )
51
+ DialogHeader.displayName = "DialogHeader"
52
+
53
+ const DialogFooter = ({
54
+ className,
55
+ ...props
56
+ }: React.ComponentPropsWithoutRef<typeof View>) => (
57
+ <View
58
+ className={cn(
59
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
60
+ className
61
+ )}
62
+ {...props}
63
+ />
64
+ )
65
+ DialogFooter.displayName = "DialogFooter"
66
+
67
+ const DialogTitle = React.forwardRef<
68
+ React.ElementRef<typeof Text>,
69
+ React.ComponentPropsWithoutRef<typeof Text>
70
+ >(({ className, ...props }, ref) => (
71
+ <Text
72
+ ref={ref}
73
+ className={cn(
74
+ "text-lg font-semibold leading-none tracking-tight text-foreground",
75
+ className
76
+ )}
77
+ {...props}
78
+ />
79
+ ))
80
+ DialogTitle.displayName = "DialogTitle"
81
+
82
+ const DialogDescription = React.forwardRef<
83
+ React.ElementRef<typeof Text>,
84
+ React.ComponentPropsWithoutRef<typeof Text>
85
+ >(({ className, ...props }, ref) => (
86
+ <Text
87
+ ref={ref}
88
+ className={cn("text-sm text-muted-foreground", className)}
89
+ {...props}
90
+ />
91
+ ))
92
+ DialogDescription.displayName = "DialogDescription"
93
+
94
+ export {
95
+ Dialog,
96
+ DialogHeader,
97
+ DialogFooter,
98
+ DialogTitle,
99
+ DialogDescription,
100
+ }
@@ -0,0 +1,143 @@
1
+ import * as React from "react"
2
+ import { View, Pressable, Text, Modal, FlatList } from "react-native"
3
+ import { Check, ChevronDown } from "lucide-react-native"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ interface SelectContextValue {
7
+ value?: string
8
+ onValueChange?: (value: string) => void
9
+ open: boolean
10
+ setOpen: (open: boolean) => void
11
+ }
12
+
13
+ const SelectContext = React.createContext<SelectContextValue>({
14
+ open: false,
15
+ setOpen: () => {},
16
+ })
17
+
18
+ const Select = ({
19
+ value,
20
+ onValueChange,
21
+ children,
22
+ }: {
23
+ value?: string
24
+ onValueChange?: (value: string) => void
25
+ children: React.ReactNode
26
+ }) => {
27
+ const [open, setOpen] = React.useState(false)
28
+
29
+ return (
30
+ <SelectContext.Provider value={{ value, onValueChange, open, setOpen }}>
31
+ {children}
32
+ </SelectContext.Provider>
33
+ )
34
+ }
35
+
36
+ const SelectTrigger = React.forwardRef<
37
+ React.ElementRef<typeof Pressable>,
38
+ React.ComponentPropsWithoutRef<typeof Pressable>
39
+ >(({ className, children, ...props }, ref) => {
40
+ const { setOpen } = React.useContext(SelectContext)
41
+ return (
42
+ <Pressable
43
+ ref={ref}
44
+ onPress={() => setOpen(true)}
45
+ className={cn(
46
+ "flex h-10 w-full flex-row items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
47
+ className
48
+ )}
49
+ {...props}
50
+ >
51
+ {children}
52
+ <ChevronDown size={16} className="opacity-50" />
53
+ </Pressable>
54
+ )
55
+ })
56
+ SelectTrigger.displayName = "SelectTrigger"
57
+
58
+ const SelectValue = React.forwardRef<
59
+ React.ElementRef<typeof Text>,
60
+ React.ComponentPropsWithoutRef<typeof Text> & { placeholder?: string }
61
+ >(({ className, placeholder, ...props }, ref) => {
62
+ const { value } = React.useContext(SelectContext)
63
+ return (
64
+ <Text
65
+ ref={ref}
66
+ className={cn("text-sm text-foreground", !value && "text-muted-foreground", className)}
67
+ {...props}
68
+ >
69
+ {value || placeholder}
70
+ </Text>
71
+ )
72
+ })
73
+ SelectValue.displayName = "SelectValue"
74
+
75
+ const SelectContent = ({
76
+ className,
77
+ children,
78
+ ...props
79
+ }: React.ComponentPropsWithoutRef<typeof View>) => {
80
+ const { open, setOpen } = React.useContext(SelectContext)
81
+
82
+ if (!open) return null
83
+
84
+ return (
85
+ <Modal
86
+ transparent
87
+ animationType="fade"
88
+ visible={open}
89
+ onRequestClose={() => setOpen(false)}
90
+ >
91
+ <Pressable className="flex-1 bg-black/50" onPress={() => setOpen(false)}>
92
+ <View className="flex-1 justify-center p-4">
93
+ <View
94
+ className={cn(
95
+ "relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80",
96
+ className
97
+ )}
98
+ {...props}
99
+ >
100
+ <View className="p-1">
101
+ {children}
102
+ </View>
103
+ </View>
104
+ </View>
105
+ </Pressable>
106
+ </Modal>
107
+ )
108
+ }
109
+ SelectContent.displayName = "SelectContent"
110
+
111
+ const SelectItem = React.forwardRef<
112
+ React.ElementRef<typeof Pressable>,
113
+ React.ComponentPropsWithoutRef<typeof Pressable> & { value: string }
114
+ >(({ className, children, value, ...props }, ref) => {
115
+ const { value: selectedValue, onValueChange, setOpen } = React.useContext(SelectContext)
116
+ const isSelected = selectedValue === value
117
+
118
+ return (
119
+ <Pressable
120
+ ref={ref}
121
+ onPress={() => {
122
+ onValueChange?.(value)
123
+ setOpen(false)
124
+ }}
125
+ className={cn(
126
+ "relative flex w-full cursor-default select-none flex-row items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
127
+ isSelected && "bg-accent",
128
+ className
129
+ )}
130
+ {...props}
131
+ >
132
+ {isSelected && (
133
+ <View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
134
+ <Check size={14} className="text-foreground" />
135
+ </View>
136
+ )}
137
+ <Text className="text-popover-foreground">{children}</Text>
138
+ </Pressable>
139
+ )
140
+ })
141
+ SelectItem.displayName = "SelectItem"
142
+
143
+ export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem }
@@ -0,0 +1,174 @@
1
+ import * as React from "react"
2
+ import { View, Pressable, Text } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const Tabs = React.forwardRef<
6
+ React.ElementRef<typeof View>,
7
+ React.ComponentPropsWithoutRef<typeof View> & {
8
+ value: string
9
+ onValueChange: (value: string) => void
10
+ }
11
+ >(({ className, value, onValueChange, children, ...props }, ref) => (
12
+ <View ref={ref} className={cn("", className)} {...props}>
13
+ {React.Children.map(children, (child) => {
14
+ if (React.isValidElement(child)) {
15
+ return React.cloneElement(child, {
16
+ // @ts-ignore
17
+ value,
18
+ // @ts-ignore
19
+ onValueChange,
20
+ })
21
+ }
22
+ return child
23
+ })}
24
+ </View>
25
+ ))
26
+ Tabs.displayName = "Tabs"
27
+
28
+ const TabsList = React.forwardRef<
29
+ React.ElementRef<typeof View>,
30
+ React.ComponentPropsWithoutRef<typeof View>
31
+ >(({ className, ...props }, ref) => (
32
+ <View
33
+ ref={ref}
34
+ className={cn(
35
+ "inline-flex h-10 flex-row items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
36
+ className
37
+ )}
38
+ {...props}
39
+ />
40
+ ))
41
+ TabsList.displayName = "TabsList"
42
+
43
+ const TabsTrigger = React.forwardRef<
44
+ React.ElementRef<typeof Pressable>,
45
+ React.ComponentPropsWithoutRef<typeof Pressable> & {
46
+ value: string
47
+ activeValue?: string
48
+ onValueChange?: (value: string) => void
49
+ }
50
+ >(({ className, value, activeValue, onValueChange, children, ...props }, ref) => {
51
+ // Note: activeValue and onValueChange are injected by parent Tabs/TabsList if we structured it that way,
52
+ // but here we rely on the user passing context or we can use a Context API.
53
+ // For simplicity in this "copy-paste" component, let's assume Tabs injects props or we use Context.
54
+ // Let's switch to Context for cleaner API.
55
+ return (
56
+ <Pressable
57
+ ref={ref}
58
+ onPress={() => onValueChange?.(value)}
59
+ className={cn(
60
+ "inline-flex flex-1 items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
61
+ activeValue === value && "bg-background text-foreground shadow-sm",
62
+ className
63
+ )}
64
+ {...props}
65
+ >
66
+ <Text className={cn("text-sm font-medium", activeValue === value ? "text-foreground" : "text-muted-foreground")}>
67
+ {children}
68
+ </Text>
69
+ </Pressable>
70
+ )
71
+ })
72
+ TabsTrigger.displayName = "TabsTrigger"
73
+
74
+ const TabsContent = React.forwardRef<
75
+ React.ElementRef<typeof View>,
76
+ React.ComponentPropsWithoutRef<typeof View> & {
77
+ value: string
78
+ activeValue?: string
79
+ }
80
+ >(({ className, value, activeValue, children, ...props }, ref) => {
81
+ if (value !== activeValue) return null
82
+ return (
83
+ <View
84
+ ref={ref}
85
+ className={cn(
86
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
87
+ className
88
+ )}
89
+ {...props}
90
+ >
91
+ {children}
92
+ </View>
93
+ )
94
+ })
95
+ TabsContent.displayName = "TabsContent"
96
+
97
+ // Re-implementing Tabs with Context to make it work properly
98
+ const TabsContext = React.createContext<{
99
+ value: string
100
+ onValueChange: (value: string) => void
101
+ }>({ value: "", onValueChange: () => {} })
102
+
103
+ const TabsRoot = React.forwardRef<
104
+ React.ElementRef<typeof View>,
105
+ React.ComponentPropsWithoutRef<typeof View> & {
106
+ defaultValue: string
107
+ onValueChange?: (value: string) => void
108
+ }
109
+ >(({ className, defaultValue, onValueChange, children, ...props }, ref) => {
110
+ const [value, setValue] = React.useState(defaultValue)
111
+ const handleValueChange = (v: string) => {
112
+ setValue(v)
113
+ onValueChange?.(v)
114
+ }
115
+
116
+ return (
117
+ <TabsContext.Provider value={{ value, onValueChange: handleValueChange }}>
118
+ <View ref={ref} className={cn("", className)} {...props}>
119
+ {children}
120
+ </View>
121
+ </TabsContext.Provider>
122
+ )
123
+ })
124
+ TabsRoot.displayName = "Tabs"
125
+
126
+ const TabsTriggerWithContext = React.forwardRef<
127
+ React.ElementRef<typeof Pressable>,
128
+ React.ComponentPropsWithoutRef<typeof Pressable> & { value: string }
129
+ >(({ className, value, children, ...props }, ref) => {
130
+ const context = React.useContext(TabsContext)
131
+ const isActive = context.value === value
132
+ return (
133
+ <Pressable
134
+ ref={ref}
135
+ onPress={() => context.onValueChange(value)}
136
+ className={cn(
137
+ "inline-flex flex-1 items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
138
+ isActive && "bg-background text-foreground shadow-sm",
139
+ className
140
+ )}
141
+ {...props}
142
+ >
143
+ {typeof children === 'string' ? (
144
+ <Text className={cn("text-sm font-medium", isActive ? "text-foreground" : "text-muted-foreground")}>
145
+ {children}
146
+ </Text>
147
+ ) : children}
148
+ </Pressable>
149
+ )
150
+ })
151
+ TabsTriggerWithContext.displayName = "TabsTrigger"
152
+
153
+ const TabsContentWithContext = React.forwardRef<
154
+ React.ElementRef<typeof View>,
155
+ React.ComponentPropsWithoutRef<typeof View> & { value: string }
156
+ >(({ className, value, children, ...props }, ref) => {
157
+ const context = React.useContext(TabsContext)
158
+ if (context.value !== value) return null
159
+ return (
160
+ <View
161
+ ref={ref}
162
+ className={cn(
163
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
164
+ className
165
+ )}
166
+ {...props}
167
+ >
168
+ {children}
169
+ </View>
170
+ )
171
+ })
172
+ TabsContentWithContext.displayName = "TabsContent"
173
+
174
+ export { TabsRoot as Tabs, TabsList, TabsTriggerWithContext as TabsTrigger, TabsContentWithContext as TabsContent }
@@ -0,0 +1,87 @@
1
+ import * as React from "react"
2
+ import { View } from "react-native"
3
+ import { cn } from "../../lib/utils"
4
+ import { toggleVariants } from "./toggle"
5
+ import { type VariantProps } from "class-variance-authority"
6
+
7
+ const ToggleGroupContext = React.createContext<{
8
+ size?: VariantProps<typeof toggleVariants>["size"]
9
+ variant?: VariantProps<typeof toggleVariants>["variant"]
10
+ value: string | string[]
11
+ onValueChange: (value: string) => void
12
+ type: "single" | "multiple"
13
+ }>({
14
+ size: "default",
15
+ variant: "default",
16
+ value: "",
17
+ onValueChange: () => {},
18
+ type: "single",
19
+ })
20
+
21
+ const ToggleGroup = React.forwardRef<
22
+ React.ElementRef<typeof View>,
23
+ React.ComponentPropsWithoutRef<typeof View> &
24
+ VariantProps<typeof toggleVariants> & {
25
+ type: "single" | "multiple"
26
+ value?: string | string[]
27
+ onValueChange?: (value: string | string[]) => void
28
+ }
29
+ >(({ className, variant, size, children, type, value: valueProp, onValueChange, ...props }, ref) => {
30
+ const [value, setValue] = React.useState<string | string[]>(valueProp || (type === "multiple" ? [] : ""))
31
+
32
+ const handleValueChange = (itemValue: string) => {
33
+ let newValue: string | string[]
34
+ if (type === "single") {
35
+ newValue = itemValue === value ? "" : itemValue
36
+ } else {
37
+ const currentValues = Array.isArray(value) ? value : []
38
+ newValue = currentValues.includes(itemValue)
39
+ ? currentValues.filter((v) => v !== itemValue)
40
+ : [...currentValues, itemValue]
41
+ }
42
+ setValue(newValue)
43
+ onValueChange?.(newValue)
44
+ }
45
+
46
+ return (
47
+ <ToggleGroupContext.Provider value={{ variant, size, value, onValueChange: handleValueChange, type }}>
48
+ <View
49
+ ref={ref}
50
+ className={cn("flex flex-row items-center justify-center gap-1", className)}
51
+ {...props}
52
+ >
53
+ {children}
54
+ </View>
55
+ </ToggleGroupContext.Provider>
56
+ )
57
+ })
58
+ ToggleGroup.displayName = "ToggleGroup"
59
+
60
+ import { Toggle } from "./toggle"
61
+
62
+ const ToggleGroupItem = React.forwardRef<
63
+ React.ElementRef<typeof Toggle>,
64
+ React.ComponentPropsWithoutRef<typeof Toggle> & { value: string }
65
+ >(({ className, children, value, ...props }, ref) => {
66
+ const context = React.useContext(ToggleGroupContext)
67
+ const pressed = Array.isArray(context.value)
68
+ ? context.value.includes(value)
69
+ : context.value === value
70
+
71
+ return (
72
+ <Toggle
73
+ ref={ref}
74
+ variant={context.variant}
75
+ size={context.size}
76
+ pressed={pressed}
77
+ onPressedChange={() => context.onValueChange(value)}
78
+ className={cn(className)}
79
+ {...props}
80
+ >
81
+ {children}
82
+ </Toggle>
83
+ )
84
+ })
85
+ ToggleGroupItem.displayName = "ToggleGroupItem"
86
+
87
+ export { ToggleGroup, ToggleGroupItem }
@@ -0,0 +1,58 @@
1
+ import * as React from "react"
2
+ import { Pressable, Text, View } from "react-native"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const toggleVariants = cva(
7
+ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-transparent",
12
+ outline:
13
+ "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
14
+ },
15
+ size: {
16
+ default: "h-10 px-3",
17
+ sm: "h-9 px-2.5",
18
+ lg: "h-11 px-5",
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ variant: "default",
23
+ size: "default",
24
+ },
25
+ }
26
+ )
27
+
28
+ const Toggle = React.forwardRef<
29
+ React.ElementRef<typeof Pressable>,
30
+ React.ComponentPropsWithoutRef<typeof Pressable> &
31
+ VariantProps<typeof toggleVariants> & {
32
+ pressed?: boolean
33
+ onPressedChange?: (pressed: boolean) => void
34
+ }
35
+ >(({ className, variant, size, pressed, onPressedChange, children, ...props }, ref) => (
36
+ <Pressable
37
+ ref={ref}
38
+ onPress={() => onPressedChange?.(!pressed)}
39
+ className={cn(
40
+ toggleVariants({ variant, size, className }),
41
+ pressed && "bg-accent text-accent-foreground"
42
+ )}
43
+ {...props}
44
+ >
45
+ {typeof children === 'string' ? (
46
+ <Text className={cn("text-sm font-medium", pressed ? "text-accent-foreground" : "text-foreground")}>
47
+ {children}
48
+ </Text>
49
+ ) : (
50
+ // Inject color prop if child is an icon? For now just render child.
51
+ // Users should style their icons based on pressed state if needed, or we can use a context.
52
+ children
53
+ )}
54
+ </Pressable>
55
+ ))
56
+ Toggle.displayName = "Toggle"
57
+
58
+ export { Toggle, toggleVariants }
package/src/index.ts CHANGED
@@ -15,4 +15,11 @@ export * from "./components/ui/radio-group"
15
15
  export * from "./components/ui/textarea"
16
16
  export * from "./components/ui/alert"
17
17
  export * from "./components/ui/progress"
18
+ export * from "./components/ui/accordion"
19
+ export * from "./components/ui/aspect-ratio"
20
+ export * from "./components/ui/dialog"
21
+ export * from "./components/ui/select"
22
+ export * from "./components/ui/tabs"
23
+ export * from "./components/ui/toggle"
24
+ export * from "./components/ui/toggle-group"
18
25
  import "./global.css"