@beyondcorp/beyond-ui-native 0.1.1 → 0.1.3
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/dist/{chunk-2B6HTSGM.mjs → chunk-RNHEGFYR.mjs} +16 -2
- package/dist/chunk-RNHEGFYR.mjs.map +1 -0
- package/dist/index.d.mts +2 -145
- package/dist/index.d.ts +2 -145
- package/dist/index.js +37 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -8
- package/dist/index.mjs.map +1 -1
- package/dist/preset-BmUHLoAa.d.mts +274 -0
- package/dist/preset-BmUHLoAa.d.ts +274 -0
- package/dist/preset.d.mts +1 -117
- package/dist/preset.d.ts +1 -117
- package/dist/preset.js +19 -3
- package/dist/preset.js.map +1 -1
- package/dist/preset.mjs +5 -3
- package/package.json +11 -5
- package/src/components/accordion/accordion.tsx +245 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.tsx +118 -0
- package/src/components/alert/index.ts +1 -0
- package/src/components/avatar/avatar.tsx +125 -0
- package/src/components/avatar/index.ts +1 -0
- package/src/components/badge/badge.tsx +89 -0
- package/src/components/badge/index.ts +1 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +336 -0
- package/src/components/bottom-sheet/index.ts +1 -0
- package/src/components/button/button.tsx +172 -0
- package/src/components/button/index.ts +1 -0
- package/src/components/card/card.tsx +121 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/checkbox/checkbox.tsx +100 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/data-table/data-table.tsx +277 -0
- package/src/components/data-table/index.ts +1 -0
- package/src/components/dialog/dialog.tsx +278 -0
- package/src/components/dialog/index.ts +1 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.tsx +130 -0
- package/src/components/radio/index.ts +1 -0
- package/src/components/radio/radio.tsx +117 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.tsx +375 -0
- package/src/components/separator/index.ts +1 -0
- package/src/components/separator/separator.tsx +28 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.tsx +43 -0
- package/src/components/spinner/index.ts +1 -0
- package/src/components/spinner/spinner.tsx +46 -0
- package/src/components/stats-card/index.ts +1 -0
- package/src/components/stats-card/stats-card.tsx +92 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.tsx +80 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.tsx +172 -0
- package/src/components/text/heading.tsx +50 -0
- package/src/components/text/index.ts +2 -0
- package/src/components/text/text.tsx +62 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.tsx +87 -0
- package/src/components/toast/index.ts +2 -0
- package/src/components/toast/toast.tsx +136 -0
- package/src/components/toast/types.ts +32 -0
- package/src/index.ts +42 -0
- package/src/primitives/box.tsx +25 -0
- package/src/primitives/index.ts +2 -0
- package/src/primitives/pressable.tsx +49 -0
- package/src/providers/BeyondUIProvider.tsx +100 -0
- package/src/templates/auth/ForgotPasswordTemplate.tsx +132 -0
- package/src/templates/auth/SignInTemplate.tsx +213 -0
- package/src/templates/auth/SignUpTemplate.tsx +233 -0
- package/src/templates/auth/index.ts +3 -0
- package/src/templates/dashboard/DashboardTemplate.tsx +205 -0
- package/src/templates/dashboard/index.ts +1 -0
- package/src/templates/index.ts +3 -0
- package/src/templates/settings/SettingsTemplate.tsx +226 -0
- package/src/templates/settings/index.ts +1 -0
- package/src/theme/preset.ts +45 -0
- package/src/theme/tokens.ts +144 -0
- package/src/types/nativewind-env.d.ts +26 -0
- package/src/utilities/accessibility.ts +43 -0
- package/src/utilities/cn.ts +9 -0
- package/src/utilities/index.ts +2 -0
- package/dist/chunk-2B6HTSGM.mjs.map +0 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
View,
|
|
5
|
+
Text,
|
|
6
|
+
Pressable,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
type ViewProps,
|
|
9
|
+
type TextProps,
|
|
10
|
+
type PressableProps,
|
|
11
|
+
type GestureResponderEvent,
|
|
12
|
+
} from "react-native";
|
|
13
|
+
import { cn } from "../../utilities/cn";
|
|
14
|
+
|
|
15
|
+
export interface DialogContextValue {
|
|
16
|
+
open: boolean;
|
|
17
|
+
onOpenChange: (open: boolean) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const DialogContext = React.createContext<DialogContextValue | null>(null);
|
|
21
|
+
|
|
22
|
+
export function useDialogContext(): DialogContextValue {
|
|
23
|
+
const context = React.useContext(DialogContext);
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error("Dialog compound components must be used within a Dialog component");
|
|
26
|
+
}
|
|
27
|
+
return context;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DialogProps {
|
|
31
|
+
open?: boolean;
|
|
32
|
+
defaultOpen?: boolean;
|
|
33
|
+
onOpenChange?: (open: boolean) => void;
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function Dialog({
|
|
38
|
+
open: controlledOpen,
|
|
39
|
+
defaultOpen = false,
|
|
40
|
+
onOpenChange,
|
|
41
|
+
children,
|
|
42
|
+
}: DialogProps) {
|
|
43
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
|
44
|
+
const isControlled = controlledOpen !== undefined;
|
|
45
|
+
const open = isControlled ? controlledOpen : uncontrolledOpen;
|
|
46
|
+
|
|
47
|
+
const handleOpenChange = React.useCallback(
|
|
48
|
+
(nextOpen: boolean) => {
|
|
49
|
+
if (!isControlled) {
|
|
50
|
+
setUncontrolledOpen(nextOpen);
|
|
51
|
+
}
|
|
52
|
+
onOpenChange?.(nextOpen);
|
|
53
|
+
},
|
|
54
|
+
[isControlled, onOpenChange]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<DialogContext.Provider value={{ open, onOpenChange: handleOpenChange }}>
|
|
59
|
+
{children}
|
|
60
|
+
</DialogContext.Provider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
Dialog.displayName = "Dialog";
|
|
64
|
+
|
|
65
|
+
export interface DialogTriggerProps extends PressableProps {
|
|
66
|
+
asChild?: boolean;
|
|
67
|
+
className?: string;
|
|
68
|
+
children: React.ReactNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const DialogTrigger = React.forwardRef<View, DialogTriggerProps>(
|
|
72
|
+
function DialogTrigger({ asChild, children, onPress, ...props }, ref) {
|
|
73
|
+
const { open, onOpenChange } = useDialogContext();
|
|
74
|
+
|
|
75
|
+
const handlePress = (e: GestureResponderEvent) => {
|
|
76
|
+
onOpenChange(!open);
|
|
77
|
+
onPress?.(e);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (asChild && React.isValidElement(children)) {
|
|
81
|
+
return React.cloneElement(children as React.ReactElement<any>, {
|
|
82
|
+
ref,
|
|
83
|
+
onPress: (e: GestureResponderEvent) => {
|
|
84
|
+
(children.props as any)?.onPress?.(e);
|
|
85
|
+
handlePress(e);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Pressable ref={ref} onPress={handlePress} role="button" {...props}>
|
|
92
|
+
{children}
|
|
93
|
+
</Pressable>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
DialogTrigger.displayName = "DialogTrigger";
|
|
98
|
+
|
|
99
|
+
export interface DialogPortalProps {
|
|
100
|
+
children: React.ReactNode;
|
|
101
|
+
forceMount?: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function DialogPortal({ children, forceMount }: DialogPortalProps) {
|
|
105
|
+
const { open, onOpenChange } = useDialogContext();
|
|
106
|
+
|
|
107
|
+
if (!open && !forceMount) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Modal
|
|
113
|
+
transparent
|
|
114
|
+
visible={open}
|
|
115
|
+
animationType="fade"
|
|
116
|
+
onRequestClose={() => onOpenChange(false)}
|
|
117
|
+
statusBarTranslucent
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</Modal>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
DialogPortal.displayName = "DialogPortal";
|
|
124
|
+
|
|
125
|
+
export interface DialogOverlayProps extends ViewProps {
|
|
126
|
+
className?: string;
|
|
127
|
+
closeOnPress?: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const DialogOverlay = React.forwardRef<View, DialogOverlayProps>(
|
|
131
|
+
function DialogOverlay({ className, closeOnPress = true, ...props }, ref) {
|
|
132
|
+
const { onOpenChange } = useDialogContext();
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<Pressable
|
|
136
|
+
ref={ref}
|
|
137
|
+
style={StyleSheet.absoluteFill}
|
|
138
|
+
className={cn("z-50 bg-black/60", className)}
|
|
139
|
+
onPress={() => {
|
|
140
|
+
if (closeOnPress) {
|
|
141
|
+
onOpenChange(false);
|
|
142
|
+
}
|
|
143
|
+
}}
|
|
144
|
+
{...props}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
DialogOverlay.displayName = "DialogOverlay";
|
|
150
|
+
|
|
151
|
+
export interface DialogContentProps extends ViewProps {
|
|
152
|
+
className?: string;
|
|
153
|
+
overlayClassName?: string;
|
|
154
|
+
children: React.ReactNode;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const DialogContent = React.forwardRef<View, DialogContentProps>(
|
|
158
|
+
function DialogContent({ className, overlayClassName, children, ...props }, ref) {
|
|
159
|
+
return (
|
|
160
|
+
<DialogPortal>
|
|
161
|
+
<View
|
|
162
|
+
pointerEvents="box-none"
|
|
163
|
+
style={StyleSheet.absoluteFill}
|
|
164
|
+
className="z-50 items-center justify-center p-4"
|
|
165
|
+
>
|
|
166
|
+
<DialogOverlay className={overlayClassName} />
|
|
167
|
+
<View
|
|
168
|
+
ref={ref}
|
|
169
|
+
role="dialog"
|
|
170
|
+
aria-modal={true}
|
|
171
|
+
className={cn(
|
|
172
|
+
"z-50 w-full max-w-lg rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-6 shadow-2xl",
|
|
173
|
+
className
|
|
174
|
+
)}
|
|
175
|
+
{...props}
|
|
176
|
+
>
|
|
177
|
+
{children}
|
|
178
|
+
</View>
|
|
179
|
+
</View>
|
|
180
|
+
</DialogPortal>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
DialogContent.displayName = "DialogContent";
|
|
185
|
+
|
|
186
|
+
export function DialogHeader({
|
|
187
|
+
className,
|
|
188
|
+
...props
|
|
189
|
+
}: ViewProps & { className?: string }) {
|
|
190
|
+
return (
|
|
191
|
+
<View
|
|
192
|
+
className={cn("flex-col space-y-2 text-center sm:text-left", className)}
|
|
193
|
+
{...props}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
DialogHeader.displayName = "DialogHeader";
|
|
198
|
+
|
|
199
|
+
export const DialogTitle = React.forwardRef<Text, TextProps & { className?: string }>(
|
|
200
|
+
function DialogTitle({ className, ...props }, ref) {
|
|
201
|
+
return (
|
|
202
|
+
<Text
|
|
203
|
+
ref={ref}
|
|
204
|
+
role="heading"
|
|
205
|
+
aria-level={2}
|
|
206
|
+
className={cn(
|
|
207
|
+
"text-xl font-bold tracking-tight text-neutral-900 dark:text-neutral-50",
|
|
208
|
+
className
|
|
209
|
+
)}
|
|
210
|
+
{...props}
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
DialogTitle.displayName = "DialogTitle";
|
|
216
|
+
|
|
217
|
+
export const DialogDescription = React.forwardRef<Text, TextProps & { className?: string }>(
|
|
218
|
+
function DialogDescription({ className, ...props }, ref) {
|
|
219
|
+
return (
|
|
220
|
+
<Text
|
|
221
|
+
ref={ref}
|
|
222
|
+
className={cn(
|
|
223
|
+
"text-sm text-neutral-500 dark:text-neutral-400 mt-1",
|
|
224
|
+
className
|
|
225
|
+
)}
|
|
226
|
+
{...props}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
DialogDescription.displayName = "DialogDescription";
|
|
232
|
+
|
|
233
|
+
export function DialogFooter({
|
|
234
|
+
className,
|
|
235
|
+
...props
|
|
236
|
+
}: ViewProps & { className?: string }) {
|
|
237
|
+
return (
|
|
238
|
+
<View
|
|
239
|
+
className={cn("flex-row justify-end space-x-2 mt-6", className)}
|
|
240
|
+
{...props}
|
|
241
|
+
/>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
DialogFooter.displayName = "DialogFooter";
|
|
245
|
+
|
|
246
|
+
export interface DialogCloseProps extends PressableProps {
|
|
247
|
+
asChild?: boolean;
|
|
248
|
+
className?: string;
|
|
249
|
+
children: React.ReactNode;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const DialogClose = React.forwardRef<View, DialogCloseProps>(
|
|
253
|
+
function DialogClose({ asChild, children, onPress, ...props }, ref) {
|
|
254
|
+
const { onOpenChange } = useDialogContext();
|
|
255
|
+
|
|
256
|
+
const handlePress = (e: GestureResponderEvent) => {
|
|
257
|
+
onOpenChange(false);
|
|
258
|
+
onPress?.(e);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
if (asChild && React.isValidElement(children)) {
|
|
262
|
+
return React.cloneElement(children as React.ReactElement<any>, {
|
|
263
|
+
ref,
|
|
264
|
+
onPress: (e: GestureResponderEvent) => {
|
|
265
|
+
(children.props as any)?.onPress?.(e);
|
|
266
|
+
handlePress(e);
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<Pressable ref={ref} onPress={handlePress} role="button" {...props}>
|
|
273
|
+
{children}
|
|
274
|
+
</Pressable>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
DialogClose.displayName = "DialogClose";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./dialog";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./input";
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Text,
|
|
4
|
+
TextInput,
|
|
5
|
+
View,
|
|
6
|
+
type TextInputProps,
|
|
7
|
+
} from "react-native";
|
|
8
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
9
|
+
import { cn } from "../../utilities/cn";
|
|
10
|
+
|
|
11
|
+
export const inputVariants = cva(
|
|
12
|
+
"w-full rounded-xl border bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 px-3.5 flex-row items-center",
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
inputSize: {
|
|
16
|
+
sm: "min-h-9 py-1 text-sm",
|
|
17
|
+
md: "min-h-11 py-2 text-base",
|
|
18
|
+
lg: "min-h-14 py-3 text-lg",
|
|
19
|
+
},
|
|
20
|
+
status: {
|
|
21
|
+
default: "border-neutral-300 dark:border-neutral-700 focus:border-primary-500",
|
|
22
|
+
error: "border-danger-500 dark:border-danger-500",
|
|
23
|
+
success: "border-success-500 dark:border-success-500",
|
|
24
|
+
},
|
|
25
|
+
disabled: {
|
|
26
|
+
true: "opacity-50 bg-neutral-100 dark:bg-neutral-800",
|
|
27
|
+
false: "",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
inputSize: "md",
|
|
32
|
+
status: "default",
|
|
33
|
+
disabled: false,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export interface InputProps
|
|
39
|
+
extends Omit<TextInputProps, "editable">,
|
|
40
|
+
VariantProps<typeof inputVariants> {
|
|
41
|
+
label?: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
helperText?: string;
|
|
44
|
+
leftIcon?: React.ReactNode;
|
|
45
|
+
rightIcon?: React.ReactNode;
|
|
46
|
+
leftElement?: React.ReactNode;
|
|
47
|
+
rightElement?: React.ReactNode;
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
containerClassName?: string;
|
|
50
|
+
className?: string;
|
|
51
|
+
labelClassName?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const Input = React.forwardRef<TextInput, InputProps>(function Input(
|
|
55
|
+
{
|
|
56
|
+
label,
|
|
57
|
+
error,
|
|
58
|
+
helperText,
|
|
59
|
+
leftIcon,
|
|
60
|
+
rightIcon,
|
|
61
|
+
leftElement,
|
|
62
|
+
rightElement,
|
|
63
|
+
disabled = false,
|
|
64
|
+
inputSize = "md",
|
|
65
|
+
containerClassName,
|
|
66
|
+
className,
|
|
67
|
+
labelClassName,
|
|
68
|
+
placeholderTextColor,
|
|
69
|
+
accessibilityLabel,
|
|
70
|
+
...props
|
|
71
|
+
},
|
|
72
|
+
ref
|
|
73
|
+
) {
|
|
74
|
+
const status = error ? "error" : "default";
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<View className={cn("w-full gap-1.5", containerClassName)}>
|
|
78
|
+
{label ? (
|
|
79
|
+
<Text
|
|
80
|
+
className={cn(
|
|
81
|
+
"text-sm font-medium text-neutral-800 dark:text-neutral-200",
|
|
82
|
+
labelClassName
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
{label}
|
|
86
|
+
</Text>
|
|
87
|
+
) : null}
|
|
88
|
+
|
|
89
|
+
<View
|
|
90
|
+
className={cn(
|
|
91
|
+
inputVariants({ inputSize, status, disabled }),
|
|
92
|
+
className
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
{leftIcon || leftElement ? (
|
|
96
|
+
<View className="mr-2">{leftIcon ?? leftElement}</View>
|
|
97
|
+
) : null}
|
|
98
|
+
|
|
99
|
+
<TextInput
|
|
100
|
+
ref={ref}
|
|
101
|
+
editable={!disabled}
|
|
102
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
103
|
+
accessibilityHint={error}
|
|
104
|
+
aria-invalid={!!error}
|
|
105
|
+
placeholderTextColor={
|
|
106
|
+
placeholderTextColor ?? "#9ca3af" // neutral-400
|
|
107
|
+
}
|
|
108
|
+
className="flex-1 py-1 text-neutral-900 dark:text-neutral-100"
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
{rightIcon || rightElement ? (
|
|
113
|
+
<View className="ml-2">{rightIcon ?? rightElement}</View>
|
|
114
|
+
) : null}
|
|
115
|
+
</View>
|
|
116
|
+
|
|
117
|
+
{error ? (
|
|
118
|
+
<Text accessibilityRole="alert" className="text-xs font-medium text-danger-600 dark:text-danger-400">
|
|
119
|
+
{error}
|
|
120
|
+
</Text>
|
|
121
|
+
) : helperText ? (
|
|
122
|
+
<Text className="text-xs text-neutral-500 dark:text-neutral-400">
|
|
123
|
+
{helperText}
|
|
124
|
+
</Text>
|
|
125
|
+
) : null}
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
Input.displayName = "Input";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./radio";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { View, Text, Pressable, type ViewProps } from "react-native";
|
|
3
|
+
import { cn } from "../../utilities/cn";
|
|
4
|
+
|
|
5
|
+
interface RadioGroupContextValue {
|
|
6
|
+
value?: string;
|
|
7
|
+
onValueChange?: (value: string) => void;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const RadioGroupContext = React.createContext<RadioGroupContextValue>({});
|
|
12
|
+
|
|
13
|
+
export interface RadioGroupProps extends ViewProps {
|
|
14
|
+
value?: string;
|
|
15
|
+
defaultValue?: string;
|
|
16
|
+
onValueChange?: (value: string) => void;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function RadioGroup({
|
|
22
|
+
value: controlledValue,
|
|
23
|
+
defaultValue,
|
|
24
|
+
onValueChange,
|
|
25
|
+
disabled = false,
|
|
26
|
+
className,
|
|
27
|
+
children,
|
|
28
|
+
...props
|
|
29
|
+
}: RadioGroupProps) {
|
|
30
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
31
|
+
const isControlled = controlledValue !== undefined;
|
|
32
|
+
const currentValue = isControlled ? controlledValue : internalValue;
|
|
33
|
+
|
|
34
|
+
const handleValueChange = (val: string) => {
|
|
35
|
+
if (!isControlled) setInternalValue(val);
|
|
36
|
+
onValueChange?.(val);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<RadioGroupContext.Provider
|
|
41
|
+
value={{
|
|
42
|
+
value: currentValue,
|
|
43
|
+
onValueChange: handleValueChange,
|
|
44
|
+
disabled,
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<View className={cn("gap-3", className)} {...props}>
|
|
48
|
+
{children}
|
|
49
|
+
</View>
|
|
50
|
+
</RadioGroupContext.Provider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RadioProps {
|
|
55
|
+
value: string;
|
|
56
|
+
label?: string;
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
className?: string;
|
|
59
|
+
indicatorClassName?: string;
|
|
60
|
+
labelClassName?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function Radio({
|
|
64
|
+
value,
|
|
65
|
+
label,
|
|
66
|
+
disabled: itemDisabled,
|
|
67
|
+
className,
|
|
68
|
+
indicatorClassName,
|
|
69
|
+
labelClassName,
|
|
70
|
+
}: RadioProps) {
|
|
71
|
+
const group = React.useContext(RadioGroupContext);
|
|
72
|
+
const isSelected = group.value === value;
|
|
73
|
+
const isDisabled = group.disabled || itemDisabled;
|
|
74
|
+
|
|
75
|
+
const handlePress = () => {
|
|
76
|
+
if (!isDisabled) {
|
|
77
|
+
group.onValueChange?.(value);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Pressable
|
|
83
|
+
onPress={handlePress}
|
|
84
|
+
disabled={isDisabled}
|
|
85
|
+
accessibilityRole="radio"
|
|
86
|
+
accessibilityState={{ selected: isSelected, disabled: isDisabled }}
|
|
87
|
+
className={cn("flex-row items-center gap-2.5", isDisabled && "opacity-50")}
|
|
88
|
+
>
|
|
89
|
+
<View
|
|
90
|
+
className={cn(
|
|
91
|
+
"h-5 w-5 rounded-full border border-neutral-300 dark:border-neutral-700 items-center justify-center bg-white dark:bg-neutral-900",
|
|
92
|
+
isSelected && "border-primary-600 dark:border-primary-600",
|
|
93
|
+
className
|
|
94
|
+
)}
|
|
95
|
+
>
|
|
96
|
+
{isSelected ? (
|
|
97
|
+
<View
|
|
98
|
+
className={cn(
|
|
99
|
+
"h-2.5 w-2.5 rounded-full bg-primary-600 dark:bg-primary-600",
|
|
100
|
+
indicatorClassName
|
|
101
|
+
)}
|
|
102
|
+
/>
|
|
103
|
+
) : null}
|
|
104
|
+
</View>
|
|
105
|
+
{label ? (
|
|
106
|
+
<Text
|
|
107
|
+
className={cn(
|
|
108
|
+
"text-sm font-medium text-neutral-800 dark:text-neutral-200",
|
|
109
|
+
labelClassName
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
{label}
|
|
113
|
+
</Text>
|
|
114
|
+
) : null}
|
|
115
|
+
</Pressable>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./select";
|