@lunar-kit/core 0.1.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.
Files changed (59) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.js +14 -0
  3. package/package.json +31 -0
  4. package/src/components/ui/accordion.tsx +334 -0
  5. package/src/components/ui/avatar.tsx +326 -0
  6. package/src/components/ui/badge.tsx +84 -0
  7. package/src/components/ui/banner.tsx +151 -0
  8. package/src/components/ui/bottom-sheet.tsx +579 -0
  9. package/src/components/ui/button.tsx +142 -0
  10. package/src/components/ui/calendar.tsx +502 -0
  11. package/src/components/ui/card.tsx +163 -0
  12. package/src/components/ui/checkbox.tsx +129 -0
  13. package/src/components/ui/date-picker.tsx +190 -0
  14. package/src/components/ui/date-range-picker.tsx +262 -0
  15. package/src/components/ui/dialog.tsx +204 -0
  16. package/src/components/ui/form.tsx +139 -0
  17. package/src/components/ui/input.tsx +107 -0
  18. package/src/components/ui/radio-group.tsx +123 -0
  19. package/src/components/ui/radio.tsx +109 -0
  20. package/src/components/ui/select-sheet.tsx +814 -0
  21. package/src/components/ui/select.tsx +547 -0
  22. package/src/components/ui/tabs.tsx +254 -0
  23. package/src/components/ui/text.tsx +229 -0
  24. package/src/components/ui/textarea.tsx +77 -0
  25. package/src/components/v0/accordion.tsx +199 -0
  26. package/src/components/v1/accordion.tsx +234 -0
  27. package/src/components/v1/avatar.tsx +259 -0
  28. package/src/components/v1/bottom-sheet.tsx +1090 -0
  29. package/src/components/v1/button.tsx +61 -0
  30. package/src/components/v1/calendar.tsx +498 -0
  31. package/src/components/v1/card.tsx +86 -0
  32. package/src/components/v1/checkbox.tsx +46 -0
  33. package/src/components/v1/date-picker.tsx +135 -0
  34. package/src/components/v1/date-range-picker.tsx +218 -0
  35. package/src/components/v1/dialog.tsx +211 -0
  36. package/src/components/v1/radio-group.tsx +76 -0
  37. package/src/components/v1/select.tsx +217 -0
  38. package/src/components/v1/tabs.tsx +253 -0
  39. package/src/registry/ui/accordion.json +30 -0
  40. package/src/registry/ui/avatar.json +41 -0
  41. package/src/registry/ui/badge.json +26 -0
  42. package/src/registry/ui/banner.json +27 -0
  43. package/src/registry/ui/bottom-sheet.json +29 -0
  44. package/src/registry/ui/button.json +24 -0
  45. package/src/registry/ui/calendar.json +29 -0
  46. package/src/registry/ui/card.json +25 -0
  47. package/src/registry/ui/checkbox.json +25 -0
  48. package/src/registry/ui/date-picker.json +30 -0
  49. package/src/registry/ui/date-range-picker.json +33 -0
  50. package/src/registry/ui/dialog.json +25 -0
  51. package/src/registry/ui/form.json +27 -0
  52. package/src/registry/ui/input.json +22 -0
  53. package/src/registry/ui/radio-group.json +26 -0
  54. package/src/registry/ui/radio.json +23 -0
  55. package/src/registry/ui/select-sheet.json +29 -0
  56. package/src/registry/ui/select.json +26 -0
  57. package/src/registry/ui/tabs.json +29 -0
  58. package/src/registry/ui/text.json +22 -0
  59. package/src/registry/ui/textarea.json +24 -0
@@ -0,0 +1,204 @@
1
+ // components/ui/dialog.tsx
2
+ import * as React from 'react';
3
+ import { Modal, View, Pressable, Animated } from 'react-native';
4
+ import { cn } from '@/lib/utils';
5
+ import { Text } from './text';
6
+
7
+ interface DialogProps {
8
+ open?: boolean;
9
+ onOpenChange?: (open: boolean) => void;
10
+ children: React.ReactNode;
11
+ }
12
+
13
+ interface DialogContentProps {
14
+ children: React.ReactNode;
15
+ className?: string;
16
+ }
17
+
18
+ interface DialogHeaderProps {
19
+ children: React.ReactNode;
20
+ className?: string;
21
+ }
22
+
23
+ interface DialogTitleProps {
24
+ children: React.ReactNode;
25
+ className?: string;
26
+ }
27
+
28
+ interface DialogDescriptionProps {
29
+ children: React.ReactNode;
30
+ className?: string;
31
+ }
32
+
33
+ interface DialogFooterProps {
34
+ children: React.ReactNode;
35
+ className?: string;
36
+ }
37
+
38
+ const DialogContext = React.createContext<{
39
+ open: boolean;
40
+ onOpenChange: (open: boolean) => void;
41
+ } | null>(null);
42
+
43
+ function useDialog() {
44
+ const context = React.useContext(DialogContext);
45
+ if (!context) {
46
+ throw new Error('Dialog components must be used within Dialog');
47
+ }
48
+ return context;
49
+ }
50
+
51
+ export function Dialog({ open: controlledOpen, onOpenChange: controlledOnOpenChange, children }: DialogProps) {
52
+ const [internalOpen, setInternalOpen] = React.useState(false);
53
+
54
+ const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
55
+ const onOpenChange = controlledOnOpenChange || setInternalOpen;
56
+
57
+ return (
58
+ <DialogContext.Provider value={{ open, onOpenChange }}>
59
+ {children}
60
+ </DialogContext.Provider>
61
+ );
62
+ }
63
+
64
+ export function DialogTrigger({ children }: { children: React.ReactNode }) {
65
+ const { onOpenChange } = useDialog();
66
+
67
+ if (React.isValidElement(children)) {
68
+ return React.cloneElement(children as React.ReactElement<any>, {
69
+ onPress: () => onOpenChange(true),
70
+ });
71
+ }
72
+
73
+ return (
74
+ <Pressable onPress={() => onOpenChange(true)}>
75
+ {children}
76
+ </Pressable>
77
+ );
78
+ }
79
+
80
+ export function DialogContent({ children, className }: DialogContentProps) {
81
+ const { open, onOpenChange } = useDialog();
82
+
83
+ const [visible, setVisible] = React.useState(false);
84
+
85
+ const scaleAnim = React.useRef(new Animated.Value(0.9)).current;
86
+ const opacityAnim = React.useRef(new Animated.Value(0)).current;
87
+
88
+ React.useEffect(() => {
89
+ if (open) {
90
+ setVisible(true);
91
+
92
+ Animated.parallel([
93
+ Animated.spring(scaleAnim, {
94
+ toValue: 1,
95
+ useNativeDriver: true,
96
+ tension: 80,
97
+ friction: 10,
98
+ }),
99
+ Animated.timing(opacityAnim, {
100
+ toValue: 1,
101
+ duration: 200,
102
+ useNativeDriver: true,
103
+ }),
104
+ ]).start();
105
+ } else {
106
+ Animated.parallel([
107
+ Animated.timing(scaleAnim, {
108
+ toValue: 0.9,
109
+ duration: 150,
110
+ useNativeDriver: true,
111
+ }),
112
+ Animated.timing(opacityAnim, {
113
+ toValue: 0,
114
+ duration: 150,
115
+ useNativeDriver: true,
116
+ }),
117
+ ]).start(() => {
118
+ setVisible(false);
119
+ });
120
+ }
121
+ }, [open]);
122
+
123
+ if (!visible) return null;
124
+
125
+ return (
126
+ <Modal
127
+ visible={visible}
128
+ transparent
129
+ animationType="none"
130
+ onRequestClose={() => onOpenChange(false)}
131
+ >
132
+ <Pressable
133
+ onPress={() => onOpenChange(false)}
134
+ className="flex-1 bg-black/50 dark:bg-black/70 items-center justify-center p-4"
135
+ >
136
+ <Animated.View
137
+ style={{
138
+ transform: [{ scale: scaleAnim }],
139
+ opacity: opacityAnim,
140
+ }}
141
+ className="w-full max-w-md"
142
+ >
143
+ <Pressable
144
+ onPress={(e) => e.stopPropagation()}
145
+ className={cn(
146
+ 'bg-background rounded-lg p-6 shadow-lg web:min-w-[400px] border border-border',
147
+ className
148
+ )}
149
+ >
150
+ {children}
151
+ </Pressable>
152
+ </Animated.View>
153
+ </Pressable>
154
+ </Modal>
155
+ );
156
+ }
157
+
158
+ export function DialogHeader({ children, className }: DialogHeaderProps) {
159
+ return (
160
+ <View className={cn('mb-4', className)}>
161
+ {children}
162
+ </View>
163
+ );
164
+ }
165
+
166
+ export function DialogTitle({ children, className }: DialogTitleProps) {
167
+ return (
168
+ <Text size="xl" variant="title" className={cn(className)}>
169
+ {children}
170
+ </Text>
171
+ );
172
+ }
173
+
174
+ export function DialogDescription({ children, className }: DialogDescriptionProps) {
175
+ return (
176
+ <Text size="sm" className={cn('text-muted-foreground mt-2', className)}>
177
+ {children}
178
+ </Text>
179
+ );
180
+ }
181
+
182
+ export function DialogFooter({ children, className }: DialogFooterProps) {
183
+ return (
184
+ <View className={cn('flex-row justify-end gap-2 mt-6', className)}>
185
+ {children}
186
+ </View>
187
+ );
188
+ }
189
+
190
+ export function DialogClose({ children }: { children: React.ReactNode }) {
191
+ const { onOpenChange } = useDialog();
192
+
193
+ if (React.isValidElement(children)) {
194
+ return React.cloneElement(children as React.ReactElement<any>, {
195
+ onPress: () => onOpenChange(false),
196
+ });
197
+ }
198
+
199
+ return (
200
+ <Pressable onPress={() => onOpenChange(false)}>
201
+ {children}
202
+ </Pressable>
203
+ );
204
+ }
@@ -0,0 +1,139 @@
1
+ // components/ui/form.tsx
2
+ import * as React from 'react';
3
+ import { View } from 'react-native';
4
+ import {
5
+ Controller,
6
+ ControllerProps,
7
+ FieldPath,
8
+ FieldValues,
9
+ FormProvider,
10
+ useFormContext,
11
+ } from 'react-hook-form';
12
+ import { cn } from '@/lib/utils';
13
+ import { Text } from './text';
14
+
15
+ export const Form = FormProvider;
16
+
17
+ type FormFieldContextValue<
18
+ TFieldValues extends FieldValues = FieldValues,
19
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
20
+ > = {
21
+ name: TName;
22
+ };
23
+
24
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
25
+ {} as FormFieldContextValue
26
+ );
27
+
28
+ export const FormField = <
29
+ TFieldValues extends FieldValues = FieldValues,
30
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
31
+ >({
32
+ ...props
33
+ }: ControllerProps<TFieldValues, TName>) => {
34
+ return (
35
+ <FormFieldContext.Provider value={{ name: props.name }}>
36
+ <Controller {...props} />
37
+ </FormFieldContext.Provider>
38
+ );
39
+ };
40
+
41
+ export const useFormField = () => {
42
+ const fieldContext = React.useContext(FormFieldContext);
43
+ const itemContext = React.useContext(FormItemContext);
44
+ const { getFieldState, formState } = useFormContext();
45
+
46
+ const fieldState = getFieldState(fieldContext.name, formState);
47
+
48
+ if (!fieldContext) {
49
+ throw new Error('useFormField should be used within <FormField>');
50
+ }
51
+
52
+ const { id } = itemContext;
53
+
54
+ return {
55
+ id,
56
+ name: fieldContext.name,
57
+ formItemId: `${id}-form-item`,
58
+ formDescriptionId: `${id}-form-item-description`,
59
+ formMessageId: `${id}-form-item-message`,
60
+ ...fieldState,
61
+ };
62
+ };
63
+
64
+ type FormItemContextValue = {
65
+ id: string;
66
+ };
67
+
68
+ const FormItemContext = React.createContext<FormItemContextValue>(
69
+ {} as FormItemContextValue
70
+ );
71
+
72
+ export const FormItem = React.forwardRef<
73
+ View,
74
+ React.ComponentPropsWithoutRef<typeof View>
75
+ >(({ className, ...props }, ref) => {
76
+ const id = React.useId();
77
+
78
+ return (
79
+ <FormItemContext.Provider value={{ id }}>
80
+ <View ref={ref} className={cn('mb-4', className)} {...props} />
81
+ </FormItemContext.Provider>
82
+ );
83
+ });
84
+ FormItem.displayName = 'FormItem';
85
+
86
+ export const FormLabel = ({ className, ...props }: React.ComponentPropsWithoutRef<typeof Text>) => {
87
+ const { error, formItemId } = useFormField();
88
+
89
+ return (
90
+ <Text
91
+ size="sm"
92
+ variant="label"
93
+ className={cn(
94
+ 'mb-2',
95
+ error && 'text-destructive',
96
+ className
97
+ )}
98
+ nativeID={formItemId}
99
+ {...props}
100
+ />
101
+ );
102
+ };
103
+ FormLabel.displayName = 'FormLabel';
104
+
105
+ export const FormDescription = ({ className, ...props }: React.ComponentPropsWithoutRef<typeof Text>) => {
106
+ const { formDescriptionId } = useFormField();
107
+
108
+ return (
109
+ <Text
110
+ size="sm"
111
+ nativeID={formDescriptionId}
112
+ className={cn('text-muted-foreground mt-2', className)}
113
+ {...props}
114
+ />
115
+ );
116
+ };
117
+ FormDescription.displayName = 'FormDescription';
118
+
119
+ export const FormMessage = ({ className, children, ...props }: React.ComponentPropsWithoutRef<typeof Text>) => {
120
+ const { error, formMessageId } = useFormField();
121
+
122
+ const body = error ? String(error?.message) : children;
123
+
124
+ if (!body) {
125
+ return null;
126
+ }
127
+
128
+ return (
129
+ <Text
130
+ size="sm"
131
+ nativeID={formMessageId}
132
+ className={cn('text-destructive mt-1', className)}
133
+ {...props}
134
+ >
135
+ {body}
136
+ </Text>
137
+ );
138
+ };
139
+ FormMessage.displayName = 'FormMessage';
@@ -0,0 +1,107 @@
1
+ // components/ui/input.tsx
2
+ import React, { forwardRef, useState } from 'react';
3
+ import { View, TextInput, Platform, type TextInputProps } from 'react-native';
4
+ import { cn } from '@/lib/utils';
5
+ import { useThemeColors } from '@/hooks/useThemeColors';
6
+
7
+ export interface InputProps extends TextInputProps {
8
+ variant?: 'outline' | 'underline';
9
+ size?: 'sm' | 'md' | 'lg';
10
+ prefix?: React.ReactNode;
11
+ suffix?: React.ReactNode;
12
+ error?: boolean;
13
+ containerClassName?: string;
14
+ inputClassName?: string;
15
+ }
16
+
17
+ export const Input = forwardRef<TextInput, InputProps>(
18
+ (
19
+ {
20
+ variant = 'outline',
21
+ size = 'md',
22
+ prefix,
23
+ suffix,
24
+ error = false,
25
+ containerClassName,
26
+ inputClassName,
27
+ style,
28
+ ...props
29
+ },
30
+ ref
31
+ ) => {
32
+ const [isFocused, setIsFocused] = useState(false);
33
+ const { colors } = useThemeColors();
34
+ const isOutline = variant === 'outline';
35
+
36
+ const sizeStyles = {
37
+ sm: 'web:h-9 h-10',
38
+ md: 'web:h-10 h-11',
39
+ lg: 'web:h-12 h-13',
40
+ };
41
+
42
+ const handleFocus = (e: any) => {
43
+ setIsFocused(true);
44
+ props.onFocus?.(e);
45
+ };
46
+
47
+ const handleBlur = (e: any) => {
48
+ setIsFocused(false);
49
+ props.onBlur?.(e);
50
+ };
51
+
52
+ return (
53
+ <View
54
+ className={cn(
55
+ 'flex-row items-center bg-background',
56
+ sizeStyles[size],
57
+ isOutline && 'border rounded-lg px-3',
58
+ !isOutline && 'border-b',
59
+ isOutline && !error && !isFocused && 'border-input',
60
+ isOutline && !error && isFocused && 'border-ring',
61
+ isOutline && error && 'border-destructive',
62
+ !isOutline && !error && !isFocused && 'border-input',
63
+ !isOutline && !error && isFocused && 'border-ring',
64
+ !isOutline && error && 'border-destructive',
65
+ containerClassName
66
+ )}
67
+ >
68
+ {prefix && <View className="mr-3">{prefix}</View>}
69
+
70
+ <TextInput
71
+ ref={ref}
72
+ {...props}
73
+ style={[
74
+ {
75
+ flex: 1,
76
+ fontSize: 16,
77
+ color: colors.foreground,
78
+ padding: 0,
79
+ paddingVertical: 0,
80
+ paddingHorizontal: 0,
81
+ margin: 0,
82
+ height: '100%',
83
+ textAlignVertical: 'center',
84
+ outlineWidth: 0,
85
+ ...(Platform.OS === 'ios' && {
86
+ lineHeight: 20,
87
+ }),
88
+ ...(Platform.OS === 'android' && {
89
+ textAlignVertical: 'center',
90
+ }),
91
+ },
92
+ style,
93
+ ]}
94
+ className={cn('text-foreground outline-none', inputClassName)}
95
+ placeholderTextColor={colors.mutedForeground}
96
+ onFocus={handleFocus}
97
+ onBlur={handleBlur}
98
+ underlineColorAndroid="transparent"
99
+ />
100
+
101
+ {suffix && <View className="ml-3">{suffix}</View>}
102
+ </View>
103
+ );
104
+ }
105
+ );
106
+
107
+ Input.displayName = 'Input';
@@ -0,0 +1,123 @@
1
+ // components/ui/radio-group.tsx
2
+ import * as React from 'react';
3
+ import { View } from 'react-native';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+ import { cn } from '@/lib/utils';
6
+ import { Text } from './text';
7
+ import { Radio } from './radio';
8
+
9
+ // Radio Group Variants
10
+ const radioGroupVariants = cva(
11
+ '',
12
+ {
13
+ variants: {
14
+ orientation: {
15
+ vertical: 'gap-3',
16
+ horizontal: 'flex-row gap-4',
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ orientation: 'vertical',
21
+ },
22
+ }
23
+ );
24
+
25
+ interface RadioGroupProps extends VariantProps<typeof radioGroupVariants> {
26
+ value?: string;
27
+ onValueChange?: (value: string) => void;
28
+ children: React.ReactNode;
29
+ className?: string;
30
+ disabled?: boolean;
31
+ size?: 'sm' | 'md' | 'lg';
32
+ }
33
+
34
+ interface RadioGroupItemProps {
35
+ value: string;
36
+ id?: string;
37
+ children?: React.ReactNode;
38
+ className?: string;
39
+ disabled?: boolean;
40
+ }
41
+
42
+ interface RadioGroupLabelProps {
43
+ children: React.ReactNode;
44
+ className?: string;
45
+ }
46
+
47
+ const RadioGroupContext = React.createContext<{
48
+ value?: string;
49
+ onValueChange?: (value: string) => void;
50
+ disabled?: boolean;
51
+ size: 'sm' | 'md' | 'lg';
52
+ } | null>(null);
53
+
54
+ function useRadioGroup() {
55
+ const context = React.useContext(RadioGroupContext);
56
+ if (!context) {
57
+ throw new Error('RadioGroupItem must be used within RadioGroup');
58
+ }
59
+ return context;
60
+ }
61
+
62
+ export function RadioGroup({
63
+ value,
64
+ onValueChange,
65
+ children,
66
+ className,
67
+ orientation,
68
+ disabled = false,
69
+ size = 'md',
70
+ }: RadioGroupProps) {
71
+ return (
72
+ <RadioGroupContext.Provider value={{ value, onValueChange, disabled, size }}>
73
+ <View className={cn(radioGroupVariants({ orientation }), className)}>
74
+ {children}
75
+ </View>
76
+ </RadioGroupContext.Provider>
77
+ );
78
+ }
79
+
80
+ export function RadioGroupItem({
81
+ value,
82
+ id,
83
+ children,
84
+ className,
85
+ disabled: itemDisabled,
86
+ }: RadioGroupItemProps) {
87
+ const { value: selectedValue, onValueChange, disabled: groupDisabled, size } = useRadioGroup();
88
+ const isSelected = selectedValue === value;
89
+ const isDisabled = groupDisabled || itemDisabled;
90
+
91
+ return (
92
+ <Radio
93
+ checked={isSelected}
94
+ onCheckedChange={() => !isDisabled && onValueChange?.(value)}
95
+ disabled={isDisabled}
96
+ size={size}
97
+ value={value}
98
+ className={className}
99
+ >
100
+ {children}
101
+ </Radio>
102
+ );
103
+ }
104
+
105
+ export function RadioGroupLabel({ children, className }: RadioGroupLabelProps) {
106
+ const { size } = useRadioGroup();
107
+
108
+ const textSize = size === 'sm' ? 'sm' : size === 'lg' ? 'md' : 'sm';
109
+
110
+ return (
111
+ <Text variant="body" size={textSize} className={className}>
112
+ {children}
113
+ </Text>
114
+ );
115
+ }
116
+
117
+ export function RadioGroupDescription({ children, className }: RadioGroupLabelProps) {
118
+ return (
119
+ <Text variant="muted" size="sm" className={cn('mt-0.5', className)}>
120
+ {children}
121
+ </Text>
122
+ );
123
+ }
@@ -0,0 +1,109 @@
1
+ // components/ui/radio.tsx
2
+ import * as React from 'react';
3
+ import { View, Pressable } from 'react-native';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+ import { cn } from '@/lib/utils';
6
+
7
+ // Radio Circle Variants
8
+ const radioCircleVariants = cva(
9
+ 'rounded-full border-2 items-center justify-center',
10
+ {
11
+ variants: {
12
+ size: {
13
+ sm: 'h-4 w-4 border',
14
+ md: 'h-5 w-5 border-2',
15
+ lg: 'h-6 w-6 border-2',
16
+ },
17
+ checked: {
18
+ true: 'border-primary',
19
+ false: 'border-input',
20
+ },
21
+ disabled: {
22
+ true: 'opacity-50',
23
+ false: '',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ size: 'md',
28
+ checked: false,
29
+ disabled: false,
30
+ },
31
+ }
32
+ );
33
+
34
+ // Radio Dot Variants
35
+ const radioDotVariants = cva(
36
+ 'rounded-full bg-primary',
37
+ {
38
+ variants: {
39
+ size: {
40
+ sm: 'h-1.5 w-1.5',
41
+ md: 'h-2.5 w-2.5',
42
+ lg: 'h-3 w-3',
43
+ },
44
+ },
45
+ defaultVariants: {
46
+ size: 'md',
47
+ },
48
+ }
49
+ );
50
+
51
+ // Radio Container Variants
52
+ const radioContainerVariants = cva(
53
+ 'flex-row items-center',
54
+ {
55
+ variants: {
56
+ size: {
57
+ sm: 'gap-2',
58
+ md: 'gap-3',
59
+ lg: 'gap-4',
60
+ },
61
+ },
62
+ defaultVariants: {
63
+ size: 'md',
64
+ },
65
+ }
66
+ );
67
+
68
+ interface RadioProps extends VariantProps<typeof radioCircleVariants> {
69
+ checked?: boolean;
70
+ onCheckedChange?: (checked: boolean) => void;
71
+ children?: React.ReactNode;
72
+ className?: string;
73
+ disabled?: boolean;
74
+ value?: string;
75
+ }
76
+
77
+ export function Radio({
78
+ checked = false,
79
+ onCheckedChange,
80
+ children,
81
+ className,
82
+ size = 'md',
83
+ disabled = false,
84
+ value,
85
+ }: RadioProps) {
86
+ return (
87
+ <Pressable
88
+ onPress={() => !disabled && onCheckedChange?.(!checked)}
89
+ disabled={disabled}
90
+ className={cn(radioContainerVariants({ size }), className)}
91
+ >
92
+ {/* Radio Circle */}
93
+ <View
94
+ className={cn(
95
+ radioCircleVariants({
96
+ size,
97
+ checked,
98
+ disabled,
99
+ })
100
+ )}
101
+ >
102
+ {checked && <View className={cn(radioDotVariants({ size }))} />}
103
+ </View>
104
+
105
+ {/* Label */}
106
+ {children}
107
+ </Pressable>
108
+ );
109
+ }