@campxdev/react-native-blueprint 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 (178) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +358 -0
  3. package/lib/module/app/_layout.js +23 -0
  4. package/lib/module/app/_layout.js.map +1 -0
  5. package/lib/module/assets/icons/weather_icons/drizzle.png +0 -0
  6. package/lib/module/assets/icons/weather_icons/foggy.png +0 -0
  7. package/lib/module/assets/icons/weather_icons/freezing_rain.png +0 -0
  8. package/lib/module/assets/icons/weather_icons/partly_cloudy.png +0 -0
  9. package/lib/module/assets/icons/weather_icons/rainy.png +0 -0
  10. package/lib/module/assets/icons/weather_icons/showers.png +0 -0
  11. package/lib/module/assets/icons/weather_icons/sunny_weather.png +0 -0
  12. package/lib/module/assets/icons/weather_icons/thunderstorm.png +0 -0
  13. package/lib/module/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
  14. package/lib/module/components/theme-config.js +265 -0
  15. package/lib/module/components/theme-config.js.map +1 -0
  16. package/lib/module/components/ui/Accordion.js +228 -0
  17. package/lib/module/components/ui/Accordion.js.map +1 -0
  18. package/lib/module/components/ui/Alert-Dialog.js +266 -0
  19. package/lib/module/components/ui/Alert-Dialog.js.map +1 -0
  20. package/lib/module/components/ui/Alert.js +107 -0
  21. package/lib/module/components/ui/Alert.js.map +1 -0
  22. package/lib/module/components/ui/AppBar.js +403 -0
  23. package/lib/module/components/ui/AppBar.js.map +1 -0
  24. package/lib/module/components/ui/Aspect-Ratio.js +27 -0
  25. package/lib/module/components/ui/Aspect-Ratio.js.map +1 -0
  26. package/lib/module/components/ui/Avatar.js +97 -0
  27. package/lib/module/components/ui/Avatar.js.map +1 -0
  28. package/lib/module/components/ui/Badge.js +127 -0
  29. package/lib/module/components/ui/Badge.js.map +1 -0
  30. package/lib/module/components/ui/Bottom-Sheet.js +144 -0
  31. package/lib/module/components/ui/Bottom-Sheet.js.map +1 -0
  32. package/lib/module/components/ui/Button.js +88 -0
  33. package/lib/module/components/ui/Button.js.map +1 -0
  34. package/lib/module/components/ui/Card.js +176 -0
  35. package/lib/module/components/ui/Card.js.map +1 -0
  36. package/lib/module/components/ui/Checkbox.js +65 -0
  37. package/lib/module/components/ui/Checkbox.js.map +1 -0
  38. package/lib/module/components/ui/Collapsible.js +42 -0
  39. package/lib/module/components/ui/Collapsible.js.map +1 -0
  40. package/lib/module/components/ui/Context-Menu.js +287 -0
  41. package/lib/module/components/ui/Context-Menu.js.map +1 -0
  42. package/lib/module/components/ui/Custom-Card.js +202 -0
  43. package/lib/module/components/ui/Custom-Card.js.map +1 -0
  44. package/lib/module/components/ui/Dialog.js +202 -0
  45. package/lib/module/components/ui/Dialog.js.map +1 -0
  46. package/lib/module/components/ui/Dropdown-Menu.js +421 -0
  47. package/lib/module/components/ui/Dropdown-Menu.js.map +1 -0
  48. package/lib/module/components/ui/Floating-Action.js +50 -0
  49. package/lib/module/components/ui/Floating-Action.js.map +1 -0
  50. package/lib/module/components/ui/Greeting-Card.js +392 -0
  51. package/lib/module/components/ui/Greeting-Card.js.map +1 -0
  52. package/lib/module/components/ui/Hover-Card.js +96 -0
  53. package/lib/module/components/ui/Hover-Card.js.map +1 -0
  54. package/lib/module/components/ui/Icon.js +73 -0
  55. package/lib/module/components/ui/Icon.js.map +1 -0
  56. package/lib/module/components/ui/Input.js +74 -0
  57. package/lib/module/components/ui/Input.js.map +1 -0
  58. package/lib/module/components/ui/Label.js +44 -0
  59. package/lib/module/components/ui/Label.js.map +1 -0
  60. package/lib/module/components/ui/Menubar.js +375 -0
  61. package/lib/module/components/ui/Menubar.js.map +1 -0
  62. package/lib/module/components/ui/Native-Only-Animated-View.js +41 -0
  63. package/lib/module/components/ui/Native-Only-Animated-View.js.map +1 -0
  64. package/lib/module/components/ui/NavBar.js +352 -0
  65. package/lib/module/components/ui/NavBar.js.map +1 -0
  66. package/lib/module/components/ui/Popover.js +101 -0
  67. package/lib/module/components/ui/Popover.js.map +1 -0
  68. package/lib/module/components/ui/Progress.js +124 -0
  69. package/lib/module/components/ui/Progress.js.map +1 -0
  70. package/lib/module/components/ui/Radio-Group.js +75 -0
  71. package/lib/module/components/ui/Radio-Group.js.map +1 -0
  72. package/lib/module/components/ui/Select.js +269 -0
  73. package/lib/module/components/ui/Select.js.map +1 -0
  74. package/lib/module/components/ui/Separator.js +58 -0
  75. package/lib/module/components/ui/Separator.js.map +1 -0
  76. package/lib/module/components/ui/SizedBox.js +101 -0
  77. package/lib/module/components/ui/SizedBox.js.map +1 -0
  78. package/lib/module/components/ui/Skeleton.js +57 -0
  79. package/lib/module/components/ui/Skeleton.js.map +1 -0
  80. package/lib/module/components/ui/Slider.js +169 -0
  81. package/lib/module/components/ui/Slider.js.map +1 -0
  82. package/lib/module/components/ui/Switch.js +55 -0
  83. package/lib/module/components/ui/Switch.js.map +1 -0
  84. package/lib/module/components/ui/Table.js +150 -0
  85. package/lib/module/components/ui/Table.js.map +1 -0
  86. package/lib/module/components/ui/Tabs.js +106 -0
  87. package/lib/module/components/ui/Tabs.js.map +1 -0
  88. package/lib/module/components/ui/Text.js +69 -0
  89. package/lib/module/components/ui/Text.js.map +1 -0
  90. package/lib/module/components/ui/Textarea.js +88 -0
  91. package/lib/module/components/ui/Textarea.js.map +1 -0
  92. package/lib/module/components/ui/Theme-Toggle.js +156 -0
  93. package/lib/module/components/ui/Theme-Toggle.js.map +1 -0
  94. package/lib/module/components/ui/Toast.js +101 -0
  95. package/lib/module/components/ui/Toast.js.map +1 -0
  96. package/lib/module/components/ui/Toggle-Group.js +129 -0
  97. package/lib/module/components/ui/Toggle-Group.js.map +1 -0
  98. package/lib/module/components/ui/Toggle.js +106 -0
  99. package/lib/module/components/ui/Toggle.js.map +1 -0
  100. package/lib/module/components/ui/Tooltip.js +106 -0
  101. package/lib/module/components/ui/Tooltip.js.map +1 -0
  102. package/lib/module/components/ui/index.js +45 -0
  103. package/lib/module/components/ui/index.js.map +1 -0
  104. package/lib/module/index.js +19 -0
  105. package/lib/module/index.js.map +1 -0
  106. package/lib/module/lib/ThemeProvider.js +173 -0
  107. package/lib/module/lib/ThemeProvider.js.map +1 -0
  108. package/lib/module/lib/cornerRadius.js +164 -0
  109. package/lib/module/lib/cornerRadius.js.map +1 -0
  110. package/lib/module/lib/fonts.js +25 -0
  111. package/lib/module/lib/fonts.js.map +1 -0
  112. package/lib/module/lib/theme.js +212 -0
  113. package/lib/module/lib/theme.js.map +1 -0
  114. package/lib/module/lib/utils.js +137 -0
  115. package/lib/module/lib/utils.js.map +1 -0
  116. package/lib/module/package.json +1 -0
  117. package/package.json +208 -0
  118. package/src/app/_layout.tsx +25 -0
  119. package/src/assets/icons/weather_icons/drizzle.png +0 -0
  120. package/src/assets/icons/weather_icons/foggy.png +0 -0
  121. package/src/assets/icons/weather_icons/freezing_rain.png +0 -0
  122. package/src/assets/icons/weather_icons/partly_cloudy.png +0 -0
  123. package/src/assets/icons/weather_icons/rainy.png +0 -0
  124. package/src/assets/icons/weather_icons/showers.png +0 -0
  125. package/src/assets/icons/weather_icons/sunny_weather.png +0 -0
  126. package/src/assets/icons/weather_icons/thunderstorm.png +0 -0
  127. package/src/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
  128. package/src/components/theme-config.ts +331 -0
  129. package/src/components/ui/Accordion.tsx +253 -0
  130. package/src/components/ui/Alert-Dialog.tsx +295 -0
  131. package/src/components/ui/Alert.tsx +137 -0
  132. package/src/components/ui/AppBar.tsx +551 -0
  133. package/src/components/ui/Aspect-Ratio.tsx +25 -0
  134. package/src/components/ui/Avatar.tsx +103 -0
  135. package/src/components/ui/Badge.tsx +121 -0
  136. package/src/components/ui/Bottom-Sheet.tsx +224 -0
  137. package/src/components/ui/Button.tsx +100 -0
  138. package/src/components/ui/Card.tsx +185 -0
  139. package/src/components/ui/Checkbox.tsx +81 -0
  140. package/src/components/ui/Collapsible.tsx +40 -0
  141. package/src/components/ui/Context-Menu.tsx +407 -0
  142. package/src/components/ui/Custom-Card.tsx +226 -0
  143. package/src/components/ui/Dialog.tsx +240 -0
  144. package/src/components/ui/Dropdown-Menu.tsx +544 -0
  145. package/src/components/ui/Floating-Action.tsx +54 -0
  146. package/src/components/ui/Greeting-Card.tsx +471 -0
  147. package/src/components/ui/Hover-Card.tsx +101 -0
  148. package/src/components/ui/Icon.tsx +75 -0
  149. package/src/components/ui/Input.tsx +90 -0
  150. package/src/components/ui/Label.tsx +48 -0
  151. package/src/components/ui/Menubar.tsx +509 -0
  152. package/src/components/ui/Native-Only-Animated-View.tsx +37 -0
  153. package/src/components/ui/NavBar.tsx +397 -0
  154. package/src/components/ui/Popover.tsx +110 -0
  155. package/src/components/ui/Progress.tsx +138 -0
  156. package/src/components/ui/Radio-Group.tsx +79 -0
  157. package/src/components/ui/Select.tsx +344 -0
  158. package/src/components/ui/Separator.tsx +68 -0
  159. package/src/components/ui/SizedBox.tsx +116 -0
  160. package/src/components/ui/Skeleton.tsx +55 -0
  161. package/src/components/ui/Slider.tsx +222 -0
  162. package/src/components/ui/Switch.tsx +67 -0
  163. package/src/components/ui/Table.tsx +170 -0
  164. package/src/components/ui/Tabs.tsx +119 -0
  165. package/src/components/ui/Text.tsx +73 -0
  166. package/src/components/ui/Textarea.tsx +93 -0
  167. package/src/components/ui/Theme-Toggle.tsx +204 -0
  168. package/src/components/ui/Toast.tsx +127 -0
  169. package/src/components/ui/Toggle-Group.tsx +160 -0
  170. package/src/components/ui/Toggle.tsx +122 -0
  171. package/src/components/ui/Tooltip.tsx +117 -0
  172. package/src/components/ui/index.ts +42 -0
  173. package/src/index.tsx +24 -0
  174. package/src/lib/ThemeProvider.tsx +204 -0
  175. package/src/lib/cornerRadius.ts +160 -0
  176. package/src/lib/fonts.ts +28 -0
  177. package/src/lib/theme.ts +151 -0
  178. package/src/lib/utils.ts +146 -0
@@ -0,0 +1,90 @@
1
+ import { cn } from '../../lib/utils';
2
+ import { Platform, TextInput, type TextInputProps } from 'react-native';
3
+ import { cssInterop } from 'nativewind';
4
+
5
+ cssInterop(TextInput, { className: 'style' });
6
+
7
+ /**
8
+ * Props for Input component
9
+ *
10
+ * @extends TextInputProps - All standard React Native TextInput props
11
+ * @property {string} [className] - Additional Tailwind classes for custom styling
12
+ * @property {boolean} [editable=true] - Whether the input is editable (disabled when false)
13
+ */
14
+ interface InputProps extends TextInputProps {
15
+ className?: string;
16
+ }
17
+
18
+ /**
19
+ * A styled text input component with consistent design across platforms
20
+ *
21
+ * Features platform-specific behaviors:
22
+ * - Web: Focus ring, selection styling, aria-invalid support
23
+ * - Native: Optimized placeholder opacity and input styling
24
+ *
25
+ * Automatically adapts styling when disabled/non-editable.
26
+ *
27
+ * @component
28
+ * @example
29
+ * ```tsx
30
+ * // Basic input
31
+ * <Input
32
+ * placeholder="Enter your name"
33
+ * value={name}
34
+ * onChangeText={setName}
35
+ * />
36
+ *
37
+ * // Disabled input
38
+ * <Input
39
+ * value="Read only"
40
+ * editable={false}
41
+ * />
42
+ *
43
+ * // With custom styling
44
+ * <Input
45
+ * className="border-primary"
46
+ * placeholder="Search..."
47
+ * />
48
+ * ```
49
+ *
50
+ * @accessibility
51
+ * - Supports aria-invalid for form validation states (web)
52
+ * - Placeholder text uses appropriate contrast colors
53
+ * - Disabled state is visually indicated with opacity
54
+ */
55
+ function Input({
56
+ className,
57
+ editable = true,
58
+ ...props
59
+ }: InputProps & React.RefAttributes<TextInput>) {
60
+ return (
61
+ <TextInput
62
+ className={cn(
63
+ 'bg-background dark:bg-background border-input dark:border-input text-foreground dark:text-foreground flex h-10 w-full min-w-0 flex-row items-center rounded-md border px-3 py-1 text-base leading-5 shadow-sm shadow-black/5 dark:shadow-white/5 sm:h-9',
64
+ !editable &&
65
+ cn(
66
+ 'opacity-50',
67
+ Platform.select({
68
+ web: 'disabled:pointer-events-none disabled:cursor-not-allowed',
69
+ })
70
+ ),
71
+ Platform.select({
72
+ web: cn(
73
+ 'placeholder:text-muted-foreground dark:placeholder:text-muted-foreground selection:bg-primary dark:selection:bg-primary selection:text-primary-foreground dark:selection:text-primary-foreground outline-none transition-[color,box-shadow] md:text-sm',
74
+ 'focus-visible:border-ring dark:focus-visible:border-ring focus-visible:ring-ring/50 dark:focus-visible:ring-ring/50 focus-visible:ring-[3px]',
75
+ 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive'
76
+ ),
77
+ native:
78
+ 'placeholder:text-muted-foreground/50 dark:placeholder:text-muted-foreground/50',
79
+ }),
80
+ className
81
+ )}
82
+ placeholderTextColor={undefined}
83
+ editable={editable}
84
+ {...props}
85
+ />
86
+ );
87
+ }
88
+
89
+ export { Input };
90
+ export type { InputProps };
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../../lib/utils';
3
+ import { Text } from './Text';
4
+ import type { SlottableTextProps, TextRef } from '@rn-primitives/types';
5
+
6
+ /**
7
+ * Accessible label component for form controls
8
+ *
9
+ * Provides semantic labeling for form inputs with proper styling and accessibility support.
10
+ * Automatically associates with form controls and respects disabled states.
11
+ *
12
+ * @component
13
+ * @example
14
+ * ```tsx
15
+ * // Label with input
16
+ * <View className="gap-2">
17
+ * <Label htmlFor="email">Email Address</Label>
18
+ * <Input id="email" placeholder="Enter email" />
19
+ * </View>
20
+ *
21
+ * // Label for checkbox
22
+ * <View className="flex-row items-center gap-2">
23
+ * <Checkbox id="terms" />
24
+ * <Label htmlFor="terms">I agree to the terms and conditions</Label>
25
+ * </View>
26
+ * ```
27
+ *
28
+ * @accessibility
29
+ * - Properly associates with form controls via htmlFor/id
30
+ * - Reduced opacity when associated control is disabled
31
+ * - Screen reader compatible
32
+ */
33
+ const Label = React.forwardRef<
34
+ TextRef,
35
+ SlottableTextProps & { className?: string }
36
+ >(({ className, ...props }, ref) => (
37
+ <Text
38
+ className={cn(
39
+ 'text-sm native:text-base font-medium leading-none text-foreground dark:text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
40
+ className
41
+ )}
42
+ ref={ref}
43
+ {...props}
44
+ />
45
+ ));
46
+ Label.displayName = 'Label';
47
+
48
+ export { Label };
@@ -0,0 +1,509 @@
1
+ import { Icon } from './Icon';
2
+ import { NativeOnlyAnimatedView } from './Native-Only-Animated-View';
3
+ import { TextClassContext } from './Text';
4
+ import { cn } from '../../lib/utils';
5
+ import * as MenubarPrimitive from '@rn-primitives/menubar';
6
+ import { Portal } from '@rn-primitives/portal';
7
+ import {
8
+ Check,
9
+ ChevronDown,
10
+ ChevronRight,
11
+ ChevronUp,
12
+ } from 'lucide-react-native';
13
+ import * as React from 'react';
14
+ import {
15
+ Platform,
16
+ Pressable,
17
+ type StyleProp,
18
+ StyleSheet,
19
+ Text,
20
+ type TextProps,
21
+ View,
22
+ type ViewStyle,
23
+ } from 'react-native';
24
+ import { cssInterop } from 'nativewind';
25
+ import { FadeIn } from 'react-native-reanimated';
26
+ import { FullWindowOverlay as RNFullWindowOverlay } from 'react-native-screens';
27
+
28
+ cssInterop(View, { className: 'style' });
29
+ cssInterop(Text, { className: 'style' });
30
+ cssInterop(Pressable, { className: 'style' });
31
+
32
+ /**
33
+ * Individual menu within the menubar
34
+ *
35
+ * @component
36
+ */
37
+ const MenubarMenu = MenubarPrimitive.Menu;
38
+
39
+ /**
40
+ * Groups related menu items together
41
+ *
42
+ * @component
43
+ */
44
+ const MenubarGroup = MenubarPrimitive.Group;
45
+
46
+ /**
47
+ * Portal component to render menu content outside the DOM hierarchy
48
+ *
49
+ * @component
50
+ */
51
+ const MenubarPortal = MenubarPrimitive.Portal;
52
+
53
+ /**
54
+ * Root component for nested submenu
55
+ *
56
+ * @component
57
+ */
58
+ const MenubarSub = MenubarPrimitive.Sub;
59
+
60
+ /**
61
+ * Groups radio items together with single selection
62
+ *
63
+ * @component
64
+ */
65
+ const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
66
+
67
+ /**
68
+ * Full window overlay wrapper for iOS, Fragment for other platforms
69
+ */
70
+ const FullWindowOverlay =
71
+ Platform.OS === 'ios' ? RNFullWindowOverlay : React.Fragment;
72
+
73
+ /**
74
+ * Root menubar component for application-level menu navigation
75
+ *
76
+ * Provides horizontal menu bar similar to desktop applications, with support
77
+ * for nested menus and automatic close on outside press.
78
+ *
79
+ * @component
80
+ * @example
81
+ * ```tsx
82
+ * <Menubar>
83
+ * <MenubarMenu>
84
+ * <MenubarTrigger>
85
+ * <Text>File</Text>
86
+ * </MenubarTrigger>
87
+ * <MenubarContent>
88
+ * <MenubarItem>
89
+ * <Text>New File</Text>
90
+ * </MenubarItem>
91
+ * <MenubarItem>
92
+ * <Text>Open</Text>
93
+ * </MenubarItem>
94
+ * </MenubarContent>
95
+ * </MenubarMenu>
96
+ * </Menubar>
97
+ * ```
98
+ *
99
+ * @accessibility
100
+ * - Keyboard navigation support
101
+ * - Proper ARIA attributes for menu structure
102
+ */
103
+ function Menubar({
104
+ className,
105
+ value: valueProp,
106
+ onValueChange: onValueChangeProp,
107
+ ...props
108
+ }: MenubarPrimitive.RootProps & React.RefAttributes<MenubarPrimitive.RootRef>) {
109
+ const id = React.useId();
110
+ const [value, setValue] = React.useState<string | undefined>(undefined);
111
+
112
+ function closeMenu() {
113
+ if (onValueChangeProp) {
114
+ onValueChangeProp(undefined);
115
+ return;
116
+ }
117
+ setValue(undefined);
118
+ }
119
+
120
+ return (
121
+ <>
122
+ {Platform.OS !== 'web' && (value || valueProp) ? (
123
+ <Portal name={`menubar-overlay-${id}`}>
124
+ <Pressable onPress={closeMenu} style={StyleSheet.absoluteFill} />
125
+ </Portal>
126
+ ) : null}
127
+ <MenubarPrimitive.Root
128
+ className={cn(
129
+ 'bg-background border-border flex h-10 flex-row items-center gap-1 rounded-md border p-1 shadow-sm shadow-black/5 sm:h-9',
130
+ className
131
+ )}
132
+ value={value ?? valueProp}
133
+ onValueChange={onValueChangeProp ?? setValue}
134
+ {...props}
135
+ />
136
+ </>
137
+ );
138
+ }
139
+
140
+ /**
141
+ * Trigger button for a menubar menu
142
+ *
143
+ * @component
144
+ * @example
145
+ * ```tsx
146
+ * <MenubarTrigger>
147
+ * <Text>Edit</Text>
148
+ * </MenubarTrigger>
149
+ * ```
150
+ */
151
+ function MenubarTrigger({
152
+ className,
153
+ ...props
154
+ }: MenubarPrimitive.TriggerProps &
155
+ React.RefAttributes<MenubarPrimitive.TriggerRef>) {
156
+ const { value } = MenubarPrimitive.useRootContext();
157
+ const { value: itemValue } = MenubarPrimitive.useMenuContext();
158
+
159
+ return (
160
+ <TextClassContext.Provider
161
+ value={cn(
162
+ 'text-sm font-medium select-none group-active:text-accent-foreground',
163
+ value === itemValue && 'text-accent-foreground'
164
+ )}
165
+ >
166
+ <MenubarPrimitive.Trigger
167
+ className={cn(
168
+ 'group flex items-center rounded-md px-2 py-1.5 sm:py-1',
169
+ Platform.select({
170
+ web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none',
171
+ }),
172
+ value === itemValue && 'bg-accent',
173
+ className
174
+ )}
175
+ {...props}
176
+ />
177
+ </TextClassContext.Provider>
178
+ );
179
+ }
180
+
181
+ /**
182
+ * Trigger for opening a nested submenu
183
+ * @component
184
+ */
185
+ function MenubarSubTrigger({
186
+ className: _className,
187
+ inset,
188
+ children,
189
+ iconClassName,
190
+ ...props
191
+ }: MenubarPrimitive.SubTriggerProps &
192
+ React.RefAttributes<MenubarPrimitive.SubTriggerRef> & {
193
+ children?: React.ReactNode;
194
+ iconClassName?: string;
195
+ inset?: boolean;
196
+ }) {
197
+ const { open } = MenubarPrimitive.useSubContext();
198
+ const icon =
199
+ Platform.OS === 'web' ? ChevronRight : open ? ChevronUp : ChevronDown;
200
+ return (
201
+ <TextClassContext.Provider
202
+ value={cn(
203
+ 'text-sm select-none group-active:text-accent-foreground',
204
+ open && 'text-accent-foreground'
205
+ )}
206
+ >
207
+ <MenubarPrimitive.SubTrigger
208
+ className={cn(
209
+ 'active:bg-accent group flex flex-row items-center rounded-sm px-2 py-2 sm:py-1.5',
210
+ Platform.select({
211
+ web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none [&_svg]:pointer-events-none',
212
+ }),
213
+ open && 'bg-accent',
214
+ inset && 'pl-8'
215
+ )}
216
+ {...props}
217
+ >
218
+ <>{children}</>
219
+ <Icon
220
+ as={icon}
221
+ className={cn(
222
+ 'text-foreground ml-auto size-4 shrink-0',
223
+ iconClassName
224
+ )}
225
+ />
226
+ </MenubarPrimitive.SubTrigger>
227
+ </TextClassContext.Provider>
228
+ );
229
+ }
230
+
231
+ /**
232
+ * Content container for nested submenu items
233
+ * @component
234
+ */
235
+ function MenubarSubContent({
236
+ className,
237
+ ...props
238
+ }: MenubarPrimitive.SubContentProps &
239
+ React.RefAttributes<MenubarPrimitive.SubContentRef>) {
240
+ return (
241
+ <NativeOnlyAnimatedView entering={FadeIn}>
242
+ <MenubarPrimitive.SubContent
243
+ className={cn(
244
+ 'bg-popover border-border overflow-hidden rounded-md border p-1 shadow-lg shadow-black/5',
245
+ Platform.select({
246
+ web: 'animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 fade-in-0 data-[state=closed]:zoom-out-95 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-context-menu-content-transform-origin) z-50 min-w-[8rem]',
247
+ }),
248
+ className
249
+ )}
250
+ {...props}
251
+ />
252
+ </NativeOnlyAnimatedView>
253
+ );
254
+ }
255
+
256
+ /**
257
+ * Main content container for menu items
258
+ * @component
259
+ */
260
+ function MenubarContent({
261
+ className,
262
+ overlayClassName: _overlayClassName,
263
+ overlayStyle: _overlayStyle,
264
+ portalHost,
265
+ align = 'start',
266
+ alignOffset = -4,
267
+ sideOffset = 8,
268
+ ...props
269
+ }: MenubarPrimitive.ContentProps &
270
+ React.RefAttributes<MenubarPrimitive.ContentRef> & {
271
+ overlayStyle?: StyleProp<ViewStyle>;
272
+ overlayClassName?: string;
273
+ portalHost?: string;
274
+ }) {
275
+ return (
276
+ <MenubarPrimitive.Portal hostName={portalHost}>
277
+ <FullWindowOverlay>
278
+ <NativeOnlyAnimatedView
279
+ entering={FadeIn}
280
+ style={StyleSheet.absoluteFill}
281
+ pointerEvents="box-none"
282
+ >
283
+ <TextClassContext.Provider value="text-popover-foreground">
284
+ <MenubarPrimitive.Content
285
+ className={cn(
286
+ 'bg-popover border-border min-w-[12rem] overflow-hidden rounded-md border p-1 shadow-lg shadow-black/5',
287
+ Platform.select({
288
+ web: cn(
289
+ 'animate-in fade-in-0 zoom-in-95 max-h-(--radix-context-menu-content-available-height) origin-(--radix-context-menu-content-transform-origin) z-50 cursor-default',
290
+ props.side === 'bottom' && 'slide-in-from-top-2',
291
+ props.side === 'top' && 'slide-in-from-bottom-2'
292
+ ),
293
+ }),
294
+ className
295
+ )}
296
+ align={align}
297
+ alignOffset={alignOffset}
298
+ sideOffset={sideOffset}
299
+ {...props}
300
+ />
301
+ </TextClassContext.Provider>
302
+ </NativeOnlyAnimatedView>
303
+ </FullWindowOverlay>
304
+ </MenubarPrimitive.Portal>
305
+ );
306
+ }
307
+
308
+ /**
309
+ * Individual menu item component
310
+ * @component
311
+ */
312
+ function MenubarItem({
313
+ className,
314
+ inset,
315
+ variant,
316
+ ...props
317
+ }: MenubarPrimitive.ItemProps &
318
+ React.RefAttributes<MenubarPrimitive.ItemRef> & {
319
+ className?: string;
320
+ inset?: boolean;
321
+ variant?: 'default' | 'destructive';
322
+ }) {
323
+ return (
324
+ <TextClassContext.Provider
325
+ value={cn(
326
+ 'select-none text-sm text-popover-foreground group-active:text-popover-foreground',
327
+ variant === 'destructive' &&
328
+ 'text-destructive group-active:text-destructive'
329
+ )}
330
+ >
331
+ <MenubarPrimitive.Item
332
+ className={cn(
333
+ 'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm px-2 py-2 sm:py-1.5',
334
+ Platform.select({
335
+ web: cn(
336
+ 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
337
+ variant === 'destructive' &&
338
+ 'focus:bg-destructive/10 dark:focus:bg-destructive/20'
339
+ ),
340
+ }),
341
+ variant === 'destructive' &&
342
+ 'active:bg-destructive/10 dark:active:bg-destructive/20',
343
+ props.disabled && 'opacity-50',
344
+ inset && 'pl-8',
345
+ className
346
+ )}
347
+ {...props}
348
+ />
349
+ </TextClassContext.Provider>
350
+ );
351
+ }
352
+
353
+ /**
354
+ * Menu item with checkbox for multi-selection
355
+ * @component
356
+ */
357
+ function MenubarCheckboxItem({
358
+ className,
359
+ children,
360
+ ...props
361
+ }: MenubarPrimitive.CheckboxItemProps &
362
+ React.RefAttributes<MenubarPrimitive.CheckboxItemRef> & {
363
+ children?: React.ReactNode;
364
+ }) {
365
+ return (
366
+ <TextClassContext.Provider value="text-sm text-popover-foreground select-none group-active:text-accent-foreground">
367
+ <MenubarPrimitive.CheckboxItem
368
+ className={cn(
369
+ 'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm py-2 pl-8 pr-2 sm:py-1.5',
370
+ Platform.select({
371
+ web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
372
+ }),
373
+ props.disabled && 'opacity-50',
374
+ className
375
+ )}
376
+ {...props}
377
+ >
378
+ <View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
379
+ <MenubarPrimitive.ItemIndicator>
380
+ <Icon
381
+ as={Check}
382
+ className={cn(
383
+ 'text-foreground size-4',
384
+ Platform.select({ web: 'pointer-events-none' })
385
+ )}
386
+ />
387
+ </MenubarPrimitive.ItemIndicator>
388
+ </View>
389
+ <>{children}</>
390
+ </MenubarPrimitive.CheckboxItem>
391
+ </TextClassContext.Provider>
392
+ );
393
+ }
394
+
395
+ /**
396
+ * Menu item with radio button for single selection within a group
397
+ * @component
398
+ */
399
+ function MenubarRadioItem({
400
+ className,
401
+ children,
402
+ ...props
403
+ }: MenubarPrimitive.RadioItemProps &
404
+ React.RefAttributes<MenubarPrimitive.RadioItemRef> & {
405
+ children?: React.ReactNode;
406
+ }) {
407
+ return (
408
+ <TextClassContext.Provider value="text-sm text-popover-foreground select-none group-active:text-accent-foreground">
409
+ <MenubarPrimitive.RadioItem
410
+ className={cn(
411
+ 'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm py-2 pl-8 pr-2 sm:py-1.5',
412
+ Platform.select({
413
+ web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
414
+ }),
415
+ props.disabled && 'opacity-50',
416
+ className
417
+ )}
418
+ {...props}
419
+ >
420
+ <View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
421
+ <MenubarPrimitive.ItemIndicator>
422
+ <View className="bg-foreground h-2 w-2 rounded-full" />
423
+ </MenubarPrimitive.ItemIndicator>
424
+ </View>
425
+ <>{children}</>
426
+ </MenubarPrimitive.RadioItem>
427
+ </TextClassContext.Provider>
428
+ );
429
+ }
430
+
431
+ /**
432
+ * Label component for grouping and describing menu sections
433
+ * @component
434
+ */
435
+ function MenubarLabel({
436
+ className,
437
+ inset,
438
+ ...props
439
+ }: MenubarPrimitive.LabelProps &
440
+ React.RefAttributes<MenubarPrimitive.LabelRef> & {
441
+ className?: string;
442
+ inset?: boolean;
443
+ }) {
444
+ return (
445
+ <MenubarPrimitive.Label
446
+ className={cn(
447
+ 'text-foreground px-2 py-2 text-sm font-medium sm:py-1.5',
448
+ inset && 'pl-8',
449
+ className
450
+ )}
451
+ {...props}
452
+ />
453
+ );
454
+ }
455
+
456
+ /**
457
+ * Visual separator between menu items or sections
458
+ * @component
459
+ */
460
+ function MenubarSeparator({
461
+ className,
462
+ ...props
463
+ }: MenubarPrimitive.SeparatorProps &
464
+ React.RefAttributes<MenubarPrimitive.SeparatorRef>) {
465
+ return (
466
+ <MenubarPrimitive.Separator
467
+ className={cn('bg-border -mx-1 my-1 h-px', className)}
468
+ {...props}
469
+ />
470
+ );
471
+ }
472
+
473
+ /**
474
+ * Displays keyboard shortcut hint for menu items
475
+ * @component
476
+ */
477
+ function MenubarShortcut({
478
+ className,
479
+ ...props
480
+ }: TextProps & React.RefAttributes<Text>) {
481
+ return (
482
+ <Text
483
+ className={cn(
484
+ 'text-muted-foreground ml-auto text-xs tracking-widest',
485
+ className
486
+ )}
487
+ {...props}
488
+ />
489
+ );
490
+ }
491
+
492
+ export {
493
+ Menubar,
494
+ MenubarCheckboxItem,
495
+ MenubarContent,
496
+ MenubarGroup,
497
+ MenubarItem,
498
+ MenubarLabel,
499
+ MenubarMenu,
500
+ MenubarPortal,
501
+ MenubarRadioGroup,
502
+ MenubarRadioItem,
503
+ MenubarSeparator,
504
+ MenubarShortcut,
505
+ MenubarSub,
506
+ MenubarSubContent,
507
+ MenubarSubTrigger,
508
+ MenubarTrigger,
509
+ };
@@ -0,0 +1,37 @@
1
+ import { Platform } from 'react-native';
2
+ import Animated from 'react-native-reanimated';
3
+
4
+ /**
5
+ * Platform-conditional animated view component
6
+ *
7
+ * Renders an Animated.View on native platforms (iOS/Android) and a React Fragment
8
+ * on web. This utility helps maintain consistent animation APIs across platforms
9
+ * while avoiding web-incompatible animations.
10
+ *
11
+ * Used internally by other components like Dialog, Popover, Tooltip, etc. to provide
12
+ * smooth entrance/exit animations on native while gracefully degrading to no animation
13
+ * on web where certain Reanimated features are not supported.
14
+ *
15
+ * @component
16
+ * @example
17
+ * ```tsx
18
+ * <NativeOnlyAnimatedView entering={FadeIn} exiting={FadeOut}>
19
+ * <View><Text>Animated content on native, static on web</Text></View>
20
+ * </NativeOnlyAnimatedView>
21
+ * ```
22
+ *
23
+ * @platform iOS, Android - Full animation support
24
+ * @platform web - Renders as Fragment without animations
25
+ */
26
+ function NativeOnlyAnimatedView(
27
+ props: React.ComponentProps<typeof Animated.View> &
28
+ React.RefAttributes<Animated.View>
29
+ ) {
30
+ if (Platform.OS === 'web') {
31
+ return <>{props.children as React.ReactNode}</>;
32
+ } else {
33
+ return <Animated.View {...props} />;
34
+ }
35
+ }
36
+
37
+ export { NativeOnlyAnimatedView };