@beyondcorp/beyond-ui-native 0.1.2 → 0.1.4

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 (71) hide show
  1. package/dist/index.js +78 -61
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +78 -61
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +11 -5
  6. package/src/components/accordion/accordion.tsx +245 -0
  7. package/src/components/accordion/index.ts +1 -0
  8. package/src/components/alert/alert.tsx +118 -0
  9. package/src/components/alert/index.ts +1 -0
  10. package/src/components/avatar/avatar.tsx +125 -0
  11. package/src/components/avatar/index.ts +1 -0
  12. package/src/components/badge/badge.tsx +89 -0
  13. package/src/components/badge/index.ts +1 -0
  14. package/src/components/bottom-sheet/bottom-sheet.tsx +336 -0
  15. package/src/components/bottom-sheet/index.ts +1 -0
  16. package/src/components/button/button.tsx +172 -0
  17. package/src/components/button/index.ts +1 -0
  18. package/src/components/card/card.tsx +121 -0
  19. package/src/components/card/index.ts +1 -0
  20. package/src/components/checkbox/checkbox.tsx +100 -0
  21. package/src/components/checkbox/index.ts +1 -0
  22. package/src/components/data-table/data-table.tsx +276 -0
  23. package/src/components/data-table/index.ts +1 -0
  24. package/src/components/dialog/dialog.tsx +278 -0
  25. package/src/components/dialog/index.ts +1 -0
  26. package/src/components/input/index.ts +1 -0
  27. package/src/components/input/input.tsx +130 -0
  28. package/src/components/radio/index.ts +1 -0
  29. package/src/components/radio/radio.tsx +117 -0
  30. package/src/components/select/index.ts +1 -0
  31. package/src/components/select/select.tsx +377 -0
  32. package/src/components/separator/index.ts +1 -0
  33. package/src/components/separator/separator.tsx +28 -0
  34. package/src/components/skeleton/index.ts +1 -0
  35. package/src/components/skeleton/skeleton.tsx +43 -0
  36. package/src/components/spinner/index.ts +1 -0
  37. package/src/components/spinner/spinner.tsx +46 -0
  38. package/src/components/stats-card/index.ts +1 -0
  39. package/src/components/stats-card/stats-card.tsx +92 -0
  40. package/src/components/switch/index.ts +1 -0
  41. package/src/components/switch/switch.tsx +80 -0
  42. package/src/components/tabs/index.ts +1 -0
  43. package/src/components/tabs/tabs.tsx +172 -0
  44. package/src/components/text/heading.tsx +50 -0
  45. package/src/components/text/index.ts +2 -0
  46. package/src/components/text/text.tsx +62 -0
  47. package/src/components/textarea/index.ts +1 -0
  48. package/src/components/textarea/textarea.tsx +87 -0
  49. package/src/components/toast/index.ts +2 -0
  50. package/src/components/toast/toast.tsx +136 -0
  51. package/src/components/toast/types.ts +32 -0
  52. package/src/index.ts +42 -0
  53. package/src/primitives/box.tsx +25 -0
  54. package/src/primitives/index.ts +2 -0
  55. package/src/primitives/pressable.tsx +49 -0
  56. package/src/providers/BeyondUIProvider.tsx +119 -0
  57. package/src/templates/auth/ForgotPasswordTemplate.tsx +132 -0
  58. package/src/templates/auth/SignInTemplate.tsx +213 -0
  59. package/src/templates/auth/SignUpTemplate.tsx +233 -0
  60. package/src/templates/auth/index.ts +3 -0
  61. package/src/templates/dashboard/DashboardTemplate.tsx +204 -0
  62. package/src/templates/dashboard/index.ts +1 -0
  63. package/src/templates/index.ts +3 -0
  64. package/src/templates/settings/SettingsTemplate.tsx +226 -0
  65. package/src/templates/settings/index.ts +1 -0
  66. package/src/theme/preset.ts +45 -0
  67. package/src/theme/tokens.ts +144 -0
  68. package/src/types/nativewind-env.d.ts +26 -0
  69. package/src/utilities/accessibility.ts +43 -0
  70. package/src/utilities/cn.ts +9 -0
  71. package/src/utilities/index.ts +2 -0
@@ -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";
@@ -0,0 +1,377 @@
1
+ import * as React from "react";
2
+ import {
3
+ Modal,
4
+ View,
5
+ Pressable,
6
+ ScrollView,
7
+ TextInput,
8
+ type ViewProps,
9
+ } from "react-native";
10
+ import { cva, type VariantProps } from "class-variance-authority";
11
+ import { cn } from "../../utilities/cn";
12
+ import { Text } from "../text/text";
13
+
14
+ const selectTriggerVariants = cva(
15
+ "flex-row items-center justify-between rounded-xl border bg-white dark:bg-neutral-900 px-4 py-3 active:opacity-80",
16
+ {
17
+ variants: {
18
+ variant: {
19
+ default: "border-neutral-300 dark:border-neutral-700",
20
+ error: "border-danger",
21
+ success: "border-success",
22
+ },
23
+ size: {
24
+ sm: "h-9 px-3 py-1.5",
25
+ md: "h-12 px-4 py-3",
26
+ lg: "h-14 px-5 py-4",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "md",
32
+ },
33
+ }
34
+ );
35
+
36
+ export interface SelectOption {
37
+ label: string;
38
+ value: string;
39
+ disabled?: boolean;
40
+ }
41
+
42
+ interface SelectContextValue {
43
+ open: boolean;
44
+ setOpen: (open: boolean) => void;
45
+ value?: string;
46
+ onSelect: (val: string) => void;
47
+ }
48
+
49
+ const SelectContext = React.createContext<SelectContextValue | null>(null);
50
+
51
+ function useSelectContext() {
52
+ const context = React.useContext(SelectContext);
53
+ if (!context) {
54
+ throw new Error("Select compound subcomponents must be used within a Select");
55
+ }
56
+ return context;
57
+ }
58
+
59
+ export interface SelectProps extends VariantProps<typeof selectTriggerVariants> {
60
+ options?: SelectOption[];
61
+ value?: string;
62
+ defaultValue?: string;
63
+ onValueChange?: (value: string) => void;
64
+ placeholder?: string;
65
+ label?: string;
66
+ helperText?: string;
67
+ error?: string;
68
+ disabled?: boolean;
69
+ searchable?: boolean;
70
+ className?: string;
71
+ children?: React.ReactNode;
72
+ }
73
+
74
+ export const Select: React.FC<SelectProps> = ({
75
+ options,
76
+ value: controlledValue,
77
+ defaultValue,
78
+ onValueChange,
79
+ placeholder = "Select an option...",
80
+ label,
81
+ helperText,
82
+ error,
83
+ disabled = false,
84
+ searchable = false,
85
+ variant,
86
+ size,
87
+ className,
88
+ children,
89
+ }) => {
90
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
91
+ const [open, setOpen] = React.useState(false);
92
+ const [searchQuery, setSearchQuery] = React.useState("");
93
+
94
+ const isControlled = controlledValue !== undefined;
95
+ const currentValue = isControlled ? controlledValue : uncontrolledValue;
96
+
97
+ const handleSelect = React.useCallback(
98
+ (newVal: string) => {
99
+ if (!isControlled) {
100
+ setUncontrolledValue(newVal);
101
+ }
102
+ onValueChange?.(newVal);
103
+ setOpen(false);
104
+ setSearchQuery("");
105
+ },
106
+ [isControlled, onValueChange]
107
+ );
108
+
109
+ const selectedOption = options?.find((o) => o.value === currentValue);
110
+ const displayLabel = selectedOption ? selectedOption.label : "";
111
+
112
+ const filteredOptions = React.useMemo(() => {
113
+ if (!options) return [];
114
+ if (!searchQuery) return options;
115
+ return options.filter((o) =>
116
+ o.label.toLowerCase().includes(searchQuery.toLowerCase())
117
+ );
118
+ }, [options, searchQuery]);
119
+
120
+ const activeVariant = error ? "error" : variant;
121
+
122
+ return (
123
+ <SelectContext.Provider
124
+ value={{
125
+ open,
126
+ setOpen,
127
+ value: currentValue,
128
+ onSelect: handleSelect,
129
+ }}
130
+ >
131
+ <View className={cn("w-full gap-1.5", className)}>
132
+ {label && (
133
+ <Text className="text-sm font-medium text-neutral-800 dark:text-neutral-200">{label}</Text>
134
+ )}
135
+
136
+ {children ? (
137
+ children
138
+ ) : (
139
+ <Pressable
140
+ disabled={disabled}
141
+ onPress={() => setOpen(true)}
142
+ accessibilityRole="combobox"
143
+ accessibilityState={{ disabled, expanded: open }}
144
+ className={cn(
145
+ selectTriggerVariants({ variant: activeVariant, size }),
146
+ disabled && "opacity-50"
147
+ )}
148
+ >
149
+ <Text
150
+ className={cn(
151
+ "text-base",
152
+ displayLabel
153
+ ? "text-neutral-900 dark:text-neutral-100 font-medium"
154
+ : "text-neutral-500 dark:text-neutral-400 font-normal"
155
+ )}
156
+ >
157
+ {displayLabel || placeholder}
158
+ </Text>
159
+ <Text className="text-neutral-500 dark:text-neutral-400 text-xs">▼</Text>
160
+ </Pressable>
161
+ )}
162
+
163
+ {error ? (
164
+ <Text className="text-xs text-danger font-medium">{error}</Text>
165
+ ) : helperText ? (
166
+ <Text className="text-xs text-neutral-500 dark:text-neutral-400">{helperText}</Text>
167
+ ) : null}
168
+
169
+ {/* Modal Options Picker if options prop was passed */}
170
+ {options && (
171
+ <Modal
172
+ transparent
173
+ visible={open}
174
+ animationType="fade"
175
+ onRequestClose={() => setOpen(false)}
176
+ statusBarTranslucent
177
+ >
178
+ <View className="flex-1 justify-end bg-black/60">
179
+ <Pressable className="flex-1" onPress={() => setOpen(false)} />
180
+ <View className="w-full bg-white dark:bg-neutral-900 rounded-t-3xl border-t border-neutral-200 dark:border-neutral-800 p-6 max-h-[75%]">
181
+ <View className="flex-row items-center justify-between pb-4 border-b border-neutral-200 dark:border-neutral-800 mb-3">
182
+ <Text className="text-lg font-bold text-neutral-900 dark:text-neutral-50">
183
+ {label || placeholder}
184
+ </Text>
185
+ <Pressable onPress={() => setOpen(false)} className="p-1">
186
+ <Text className="text-neutral-500 dark:text-neutral-400 text-lg font-semibold">✕</Text>
187
+ </Pressable>
188
+ </View>
189
+
190
+ {searchable && (
191
+ <View className="mb-4">
192
+ <TextInput
193
+ value={searchQuery}
194
+ onChangeText={setSearchQuery}
195
+ placeholder="Search options..."
196
+ placeholderTextColor="#9ca3af"
197
+ className="h-11 px-4 rounded-xl border border-neutral-300 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 text-base"
198
+ />
199
+ </View>
200
+ )}
201
+
202
+ <ScrollView className="max-h-80" showsVerticalScrollIndicator={false}>
203
+ {filteredOptions.length === 0 ? (
204
+ <View className="py-8 items-center">
205
+ <Text className="text-neutral-500 dark:text-neutral-400 text-sm">No options found</Text>
206
+ </View>
207
+ ) : (
208
+ filteredOptions.map((opt) => {
209
+ const isSelected = opt.value === currentValue;
210
+ return (
211
+ <Pressable
212
+ key={opt.value}
213
+ disabled={opt.disabled}
214
+ onPress={() => handleSelect(opt.value)}
215
+ className={cn(
216
+ "flex-row items-center justify-between py-3.5 px-3 rounded-xl active:bg-neutral-100 dark:active:bg-neutral-800",
217
+ isSelected && "bg-primary-50 dark:bg-primary-950/40",
218
+ opt.disabled && "opacity-40"
219
+ )}
220
+ >
221
+ <Text
222
+ className={cn(
223
+ "text-base",
224
+ isSelected
225
+ ? "text-primary-600 dark:text-primary-400 font-semibold"
226
+ : "text-neutral-900 dark:text-neutral-100 font-normal"
227
+ )}
228
+ >
229
+ {opt.label}
230
+ </Text>
231
+ {isSelected && (
232
+ <Text className="text-primary-600 dark:text-primary-400 font-bold text-base">✓</Text>
233
+ )}
234
+ </Pressable>
235
+ );
236
+ })
237
+ )}
238
+ </ScrollView>
239
+ </View>
240
+ </View>
241
+ </Modal>
242
+ )}
243
+ </View>
244
+ </SelectContext.Provider>
245
+ );
246
+ };
247
+ Select.displayName = "Select";
248
+
249
+ // --- Compound Subcomponents ---
250
+
251
+ export interface SelectTriggerProps extends ViewProps {
252
+ className?: string;
253
+ children: React.ReactNode;
254
+ }
255
+
256
+ export const SelectTrigger: React.FC<SelectTriggerProps> = ({
257
+ className,
258
+ children,
259
+ ...props
260
+ }) => {
261
+ const { setOpen, open } = useSelectContext();
262
+ return (
263
+ <Pressable
264
+ onPress={() => setOpen(true)}
265
+ accessibilityRole="combobox"
266
+ accessibilityState={{ expanded: open }}
267
+ className={cn(
268
+ "flex-row items-center justify-between rounded-xl border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-4 py-3 active:opacity-80",
269
+ className
270
+ )}
271
+ {...props}
272
+ >
273
+ {children}
274
+ <Text className="text-neutral-500 dark:text-neutral-400 text-xs">▼</Text>
275
+ </Pressable>
276
+ );
277
+ };
278
+ SelectTrigger.displayName = "SelectTrigger";
279
+
280
+ export interface SelectValueProps {
281
+ placeholder?: string;
282
+ className?: string;
283
+ }
284
+
285
+ export const SelectValue: React.FC<SelectValueProps> = ({
286
+ placeholder = "Select...",
287
+ className,
288
+ }) => {
289
+ const { value } = useSelectContext();
290
+ return (
291
+ <Text
292
+ className={cn(
293
+ "text-base",
294
+ value ? "text-neutral-900 dark:text-neutral-100 font-medium" : "text-neutral-500 dark:text-neutral-400",
295
+ className
296
+ )}
297
+ >
298
+ {value || placeholder}
299
+ </Text>
300
+ );
301
+ };
302
+ SelectValue.displayName = "SelectValue";
303
+
304
+ export interface SelectContentProps {
305
+ title?: string;
306
+ children: React.ReactNode;
307
+ }
308
+
309
+ export const SelectContent: React.FC<SelectContentProps> = ({ title, children }) => {
310
+ const { open, setOpen } = useSelectContext();
311
+ if (!open) return null;
312
+
313
+ return (
314
+ <Modal
315
+ transparent
316
+ visible={open}
317
+ animationType="fade"
318
+ onRequestClose={() => setOpen(false)}
319
+ statusBarTranslucent
320
+ >
321
+ <View className="flex-1 justify-end bg-black/60">
322
+ <Pressable className="flex-1" onPress={() => setOpen(false)} />
323
+ <View className="w-full bg-white dark:bg-neutral-900 rounded-t-3xl border-t border-neutral-200 dark:border-neutral-800 p-6 max-h-[75%]">
324
+ <View className="flex-row items-center justify-between pb-3 border-b border-neutral-200 dark:border-neutral-800 mb-3">
325
+ <Text className="text-lg font-bold text-neutral-900 dark:text-neutral-100">{title || "Select"}</Text>
326
+ <Pressable onPress={() => setOpen(false)} className="p-1">
327
+ <Text className="text-neutral-500 dark:text-neutral-400 text-lg font-semibold">✕</Text>
328
+ </Pressable>
329
+ </View>
330
+ <ScrollView className="max-h-80">{children}</ScrollView>
331
+ </View>
332
+ </View>
333
+ </Modal>
334
+ );
335
+ };
336
+ SelectContent.displayName = "SelectContent";
337
+
338
+ export interface SelectItemProps {
339
+ value: string;
340
+ className?: string;
341
+ children: React.ReactNode;
342
+ }
343
+
344
+ export const SelectItem: React.FC<SelectItemProps> = ({
345
+ value: itemValue,
346
+ className,
347
+ children,
348
+ }) => {
349
+ const { value: selectedValue, onSelect } = useSelectContext();
350
+ const isSelected = selectedValue === itemValue;
351
+
352
+ return (
353
+ <Pressable
354
+ onPress={() => onSelect(itemValue)}
355
+ className={cn(
356
+ "flex-row items-center justify-between py-3.5 px-3 rounded-xl active:bg-neutral-100 dark:active:bg-neutral-800",
357
+ isSelected && "bg-primary-50 dark:bg-primary-950/40",
358
+ className
359
+ )}
360
+ >
361
+ {typeof children === "string" ? (
362
+ <Text
363
+ className={cn(
364
+ "text-base",
365
+ isSelected ? "text-primary-600 dark:text-primary-400 font-semibold" : "text-neutral-900 dark:text-neutral-100 font-normal"
366
+ )}
367
+ >
368
+ {children}
369
+ </Text>
370
+ ) : (
371
+ children
372
+ )}
373
+ {isSelected && <Text className="text-primary-600 dark:text-primary-400 font-bold text-base">✓</Text>}
374
+ </Pressable>
375
+ );
376
+ };
377
+ SelectItem.displayName = "SelectItem";
@@ -0,0 +1 @@
1
+ export * from "./separator";
@@ -0,0 +1,28 @@
1
+ import * as React from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { cn } from "../../utilities/cn";
4
+
5
+ export interface SeparatorProps extends ViewProps {
6
+ orientation?: "horizontal" | "vertical";
7
+ className?: string;
8
+ }
9
+
10
+ export function Separator({
11
+ orientation = "horizontal",
12
+ className,
13
+ ...props
14
+ }: SeparatorProps) {
15
+ return (
16
+ <View
17
+ accessibilityRole="none"
18
+ className={cn(
19
+ "bg-neutral-200 dark:bg-neutral-800",
20
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ Separator.displayName = "Separator";
@@ -0,0 +1 @@
1
+ export * from "./skeleton";
@@ -0,0 +1,43 @@
1
+ import * as React from "react";
2
+ import { Animated, type ViewProps } from "react-native";
3
+ import { cn } from "../../utilities/cn";
4
+
5
+ export interface SkeletonProps extends ViewProps {
6
+ className?: string;
7
+ }
8
+
9
+ export function Skeleton({ className, style, ...props }: SkeletonProps) {
10
+ const pulseAnim = React.useRef(new Animated.Value(0.4)).current;
11
+
12
+ React.useEffect(() => {
13
+ const animation = Animated.loop(
14
+ Animated.sequence([
15
+ Animated.timing(pulseAnim, {
16
+ toValue: 1,
17
+ duration: 900,
18
+ useNativeDriver: true,
19
+ }),
20
+ Animated.timing(pulseAnim, {
21
+ toValue: 0.4,
22
+ duration: 900,
23
+ useNativeDriver: true,
24
+ }),
25
+ ])
26
+ );
27
+ animation.start();
28
+ return () => animation.stop();
29
+ }, [pulseAnim]);
30
+
31
+ return (
32
+ <Animated.View
33
+ style={[{ opacity: pulseAnim }, style]}
34
+ className={cn(
35
+ "rounded-xl bg-neutral-200 dark:bg-neutral-800",
36
+ className
37
+ )}
38
+ {...props}
39
+ />
40
+ );
41
+ }
42
+
43
+ Skeleton.displayName = "Skeleton";