@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,40 @@
1
+ import * as CollapsiblePrimitive from '@rn-primitives/collapsible';
2
+
3
+ /**
4
+ * Root collapsible container
5
+ *
6
+ * An interactive component that can show and hide content. Unlike Accordion, it doesn't enforce
7
+ * any specific visual styling, making it more flexible for custom layouts.
8
+ *
9
+ * @component
10
+ * @example
11
+ * ```tsx
12
+ * <Collapsible open={isOpen} onOpenChange={setIsOpen}>
13
+ * <CollapsibleTrigger asChild>
14
+ * <Button variant="ghost"><Text>Toggle</Text></Button>
15
+ * </CollapsibleTrigger>
16
+ * <CollapsibleContent>
17
+ * <Text>This content can be shown or hidden</Text>
18
+ * </CollapsibleContent>
19
+ * </Collapsible>
20
+ * ```
21
+ */
22
+ const Collapsible = CollapsiblePrimitive.Root;
23
+
24
+ /**
25
+ * Trigger button that toggles collapsible content visibility
26
+ *
27
+ * @component
28
+ */
29
+ const CollapsibleTrigger = CollapsiblePrimitive.Trigger;
30
+
31
+ /**
32
+ * Collapsible content container
33
+ *
34
+ * Automatically animates height when expanding or collapsing.
35
+ *
36
+ * @component
37
+ */
38
+ const CollapsibleContent = CollapsiblePrimitive.Content;
39
+
40
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };
@@ -0,0 +1,407 @@
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 ContextMenuPrimitive from '@rn-primitives/context-menu';
6
+ import {
7
+ Check,
8
+ ChevronDown,
9
+ ChevronRight,
10
+ ChevronUp,
11
+ } from 'lucide-react-native';
12
+ import * as React from 'react';
13
+ import {
14
+ Platform,
15
+ type StyleProp,
16
+ StyleSheet,
17
+ Text,
18
+ type TextProps,
19
+ View,
20
+ type ViewStyle,
21
+ } from 'react-native';
22
+ import { cssInterop } from 'nativewind';
23
+ import { FadeIn } from 'react-native-reanimated';
24
+ import { FullWindowOverlay as RNFullWindowOverlay } from 'react-native-screens';
25
+
26
+ cssInterop(View, { className: 'style' });
27
+ cssInterop(Text, { className: 'style' });
28
+
29
+ /**
30
+ * Root context menu component
31
+ *
32
+ * @component
33
+ * @example
34
+ * ```tsx
35
+ * <ContextMenu>
36
+ * <ContextMenuTrigger>
37
+ * <View><Text>Right-click me</Text></View>
38
+ * </ContextMenuTrigger>
39
+ * <ContextMenuContent>
40
+ * <ContextMenuItem><Text>Edit</Text></ContextMenuItem>
41
+ * <ContextMenuItem><Text>Delete</Text></ContextMenuItem>
42
+ * </ContextMenuContent>
43
+ * </ContextMenu>
44
+ * ```
45
+ */
46
+ const ContextMenu = ContextMenuPrimitive.Root;
47
+
48
+ /**
49
+ * Trigger component that opens context menu on right-click or long-press
50
+ * @component
51
+ */
52
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
53
+
54
+ /**
55
+ * Groups related context menu items
56
+ * @component
57
+ */
58
+ const ContextMenuGroup = ContextMenuPrimitive.Group;
59
+
60
+ /**
61
+ * Root component for nested submenu
62
+ * @component
63
+ */
64
+ const ContextMenuSub = ContextMenuPrimitive.Sub;
65
+
66
+ /**
67
+ * Groups radio items for single selection
68
+ * @component
69
+ */
70
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
71
+
72
+ /**
73
+ * Trigger for opening a nested submenu
74
+ * @component
75
+ */
76
+ function ContextMenuSubTrigger({
77
+ className: _className,
78
+ inset,
79
+ children,
80
+ iconClassName,
81
+ ...props
82
+ }: ContextMenuPrimitive.SubTriggerProps &
83
+ React.RefAttributes<ContextMenuPrimitive.SubTriggerRef> & {
84
+ children?: React.ReactNode;
85
+ iconClassName?: string;
86
+ inset?: boolean;
87
+ }) {
88
+ const { open } = ContextMenuPrimitive.useSubContext();
89
+ const icon =
90
+ Platform.OS === 'web' ? ChevronRight : open ? ChevronUp : ChevronDown;
91
+ return (
92
+ <TextClassContext.Provider
93
+ value={cn(
94
+ 'text-sm select-none group-active:text-accent-foreground',
95
+ open && 'text-accent-foreground'
96
+ )}
97
+ >
98
+ <ContextMenuPrimitive.SubTrigger
99
+ className={cn(
100
+ 'active:bg-accent group flex flex-row items-center rounded-sm px-2 py-2 sm:py-1.5',
101
+ Platform.select({
102
+ web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none [&_svg]:pointer-events-none',
103
+ }),
104
+ open && cn('bg-accent', Platform.select({ native: 'mb-1' })),
105
+ inset && 'pl-8'
106
+ )}
107
+ {...props}
108
+ >
109
+ <>{children}</>
110
+ <Icon
111
+ as={icon}
112
+ className={cn(
113
+ 'text-foreground ml-auto size-4 shrink-0',
114
+ iconClassName
115
+ )}
116
+ />
117
+ </ContextMenuPrimitive.SubTrigger>
118
+ </TextClassContext.Provider>
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Content container for nested submenu items
124
+ * @component
125
+ */
126
+ function ContextMenuSubContent({
127
+ className,
128
+ ...props
129
+ }: ContextMenuPrimitive.SubContentProps &
130
+ React.RefAttributes<ContextMenuPrimitive.SubContentRef>) {
131
+ return (
132
+ <NativeOnlyAnimatedView entering={FadeIn}>
133
+ <ContextMenuPrimitive.SubContent
134
+ className={cn(
135
+ 'bg-popover border-border overflow-hidden rounded-md border p-1 shadow-lg shadow-black/5',
136
+ Platform.select({
137
+ 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]',
138
+ }),
139
+ className
140
+ )}
141
+ {...props}
142
+ />
143
+ </NativeOnlyAnimatedView>
144
+ );
145
+ }
146
+
147
+ /**
148
+ * Full window overlay wrapper for iOS
149
+ */
150
+ const FullWindowOverlay =
151
+ Platform.OS === 'ios' ? RNFullWindowOverlay : React.Fragment;
152
+
153
+ /**
154
+ * Main content container for context menu items
155
+ * @component
156
+ */
157
+ function ContextMenuContent({
158
+ className,
159
+ overlayClassName,
160
+ overlayStyle,
161
+ portalHost,
162
+ ...props
163
+ }: ContextMenuPrimitive.ContentProps &
164
+ React.RefAttributes<ContextMenuPrimitive.ContentRef> & {
165
+ overlayStyle?: StyleProp<ViewStyle>;
166
+ overlayClassName?: string;
167
+ portalHost?: string;
168
+ }) {
169
+ return (
170
+ <ContextMenuPrimitive.Portal hostName={portalHost}>
171
+ <FullWindowOverlay>
172
+ <ContextMenuPrimitive.Overlay
173
+ style={Platform.select({
174
+ web: overlayStyle ?? undefined,
175
+ native: overlayStyle
176
+ ? StyleSheet.flatten([
177
+ StyleSheet.absoluteFill,
178
+ overlayStyle as typeof StyleSheet.absoluteFill,
179
+ ])
180
+ : StyleSheet.absoluteFill,
181
+ })}
182
+ className={overlayClassName}
183
+ >
184
+ <NativeOnlyAnimatedView entering={FadeIn}>
185
+ <TextClassContext.Provider value="text-popover-foreground">
186
+ <ContextMenuPrimitive.Content
187
+ className={cn(
188
+ 'bg-popover border-border min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg shadow-black/5',
189
+ Platform.select({
190
+ web: cn(
191
+ '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',
192
+ props.side === 'bottom' && 'slide-in-from-top-2',
193
+ props.side === 'top' && 'slide-in-from-bottom-2'
194
+ ),
195
+ }),
196
+ className
197
+ )}
198
+ {...props}
199
+ />
200
+ </TextClassContext.Provider>
201
+ </NativeOnlyAnimatedView>
202
+ </ContextMenuPrimitive.Overlay>
203
+ </FullWindowOverlay>
204
+ </ContextMenuPrimitive.Portal>
205
+ );
206
+ }
207
+
208
+ /**
209
+ * Individual context menu item
210
+ * @component
211
+ */
212
+ function ContextMenuItem({
213
+ className,
214
+ inset,
215
+ variant,
216
+ ...props
217
+ }: ContextMenuPrimitive.ItemProps &
218
+ React.RefAttributes<ContextMenuPrimitive.ItemRef> & {
219
+ className?: string;
220
+ inset?: boolean;
221
+ variant?: 'default' | 'destructive';
222
+ }) {
223
+ return (
224
+ <TextClassContext.Provider
225
+ value={cn(
226
+ 'select-none text-sm text-popover-foreground group-active:text-popover-foreground',
227
+ variant === 'destructive' &&
228
+ 'text-destructive group-active:text-destructive'
229
+ )}
230
+ >
231
+ <ContextMenuPrimitive.Item
232
+ className={cn(
233
+ 'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm px-2 py-2 sm:py-1.5',
234
+ Platform.select({
235
+ web: cn(
236
+ 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
237
+ variant === 'destructive' &&
238
+ 'focus:bg-destructive/10 dark:focus:bg-destructive/20'
239
+ ),
240
+ }),
241
+ variant === 'destructive' &&
242
+ 'active:bg-destructive/10 dark:active:bg-destructive/20',
243
+ props.disabled && 'opacity-50',
244
+ inset && 'pl-8',
245
+ className
246
+ )}
247
+ {...props}
248
+ />
249
+ </TextClassContext.Provider>
250
+ );
251
+ }
252
+
253
+ /**
254
+ * Menu item with checkbox for multi-selection
255
+ * @component
256
+ */
257
+ function ContextMenuCheckboxItem({
258
+ className,
259
+ children,
260
+ ...props
261
+ }: ContextMenuPrimitive.CheckboxItemProps &
262
+ React.RefAttributes<ContextMenuPrimitive.CheckboxItemRef> & {
263
+ children?: React.ReactNode;
264
+ }) {
265
+ return (
266
+ <TextClassContext.Provider value="text-sm text-popover-foreground select-none group-active:text-accent-foreground">
267
+ <ContextMenuPrimitive.CheckboxItem
268
+ className={cn(
269
+ 'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm py-2 pl-8 pr-2 sm:py-1.5',
270
+ Platform.select({
271
+ web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
272
+ }),
273
+ props.disabled && 'opacity-50',
274
+ className
275
+ )}
276
+ {...props}
277
+ >
278
+ <View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
279
+ <ContextMenuPrimitive.ItemIndicator>
280
+ <Icon
281
+ as={Check}
282
+ className={cn(
283
+ 'text-foreground size-4',
284
+ Platform.select({ web: 'pointer-events-none' })
285
+ )}
286
+ />
287
+ </ContextMenuPrimitive.ItemIndicator>
288
+ </View>
289
+ <>{children}</>
290
+ </ContextMenuPrimitive.CheckboxItem>
291
+ </TextClassContext.Provider>
292
+ );
293
+ }
294
+
295
+ /**
296
+ * Menu item with radio button for single selection
297
+ * @component
298
+ */
299
+ function ContextMenuRadioItem({
300
+ className,
301
+ children,
302
+ ...props
303
+ }: ContextMenuPrimitive.RadioItemProps &
304
+ React.RefAttributes<ContextMenuPrimitive.RadioItemRef> & {
305
+ children?: React.ReactNode;
306
+ }) {
307
+ return (
308
+ <TextClassContext.Provider value="text-sm text-popover-foreground select-none group-active:text-accent-foreground">
309
+ <ContextMenuPrimitive.RadioItem
310
+ className={cn(
311
+ 'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm py-2 pl-8 pr-2 sm:py-1.5',
312
+ Platform.select({
313
+ web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
314
+ }),
315
+ props.disabled && 'opacity-50',
316
+ className
317
+ )}
318
+ {...props}
319
+ >
320
+ <View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
321
+ <ContextMenuPrimitive.ItemIndicator>
322
+ <View className="bg-foreground h-2 w-2 rounded-full" />
323
+ </ContextMenuPrimitive.ItemIndicator>
324
+ </View>
325
+ <>{children}</>
326
+ </ContextMenuPrimitive.RadioItem>
327
+ </TextClassContext.Provider>
328
+ );
329
+ }
330
+
331
+ /**
332
+ * Label for grouping context menu sections
333
+ * @component
334
+ */
335
+ function ContextMenuLabel({
336
+ className,
337
+ inset,
338
+ ...props
339
+ }: ContextMenuPrimitive.LabelProps &
340
+ React.RefAttributes<ContextMenuPrimitive.LabelRef> & {
341
+ className?: string;
342
+ inset?: boolean;
343
+ }) {
344
+ return (
345
+ <ContextMenuPrimitive.Label
346
+ className={cn(
347
+ 'text-foreground px-2 py-2 text-sm font-medium sm:py-1.5',
348
+ inset && 'pl-8',
349
+ className
350
+ )}
351
+ {...props}
352
+ />
353
+ );
354
+ }
355
+
356
+ /**
357
+ * Visual separator between menu items
358
+ * @component
359
+ */
360
+ function ContextMenuSeparator({
361
+ className,
362
+ ...props
363
+ }: ContextMenuPrimitive.SeparatorProps &
364
+ React.RefAttributes<ContextMenuPrimitive.SeparatorRef>) {
365
+ return (
366
+ <ContextMenuPrimitive.Separator
367
+ className={cn('bg-border -mx-1 my-1 h-px', className)}
368
+ {...props}
369
+ />
370
+ );
371
+ }
372
+
373
+ /**
374
+ * Displays keyboard shortcut hint for menu items
375
+ * @component
376
+ */
377
+ function ContextMenuShortcut({
378
+ className,
379
+ ...props
380
+ }: TextProps & React.RefAttributes<Text>) {
381
+ return (
382
+ <Text
383
+ className={cn(
384
+ 'text-muted-foreground ml-auto text-xs tracking-widest',
385
+ className
386
+ )}
387
+ {...props}
388
+ />
389
+ );
390
+ }
391
+
392
+ export {
393
+ ContextMenu,
394
+ ContextMenuCheckboxItem,
395
+ ContextMenuContent,
396
+ ContextMenuGroup,
397
+ ContextMenuItem,
398
+ ContextMenuLabel,
399
+ ContextMenuRadioGroup,
400
+ ContextMenuRadioItem,
401
+ ContextMenuSeparator,
402
+ ContextMenuShortcut,
403
+ ContextMenuSub,
404
+ ContextMenuSubContent,
405
+ ContextMenuSubTrigger,
406
+ ContextMenuTrigger,
407
+ };
@@ -0,0 +1,226 @@
1
+ import * as Slot from '@rn-primitives/slot';
2
+ import type { SlottableViewProps, ViewRef } from '@rn-primitives/types';
3
+ import * as React from 'react';
4
+ import { View, useColorScheme } from 'react-native';
5
+ import { SquircleView } from 'react-native-figma-squircle';
6
+ import { cn } from '../../lib/utils';
7
+ import { TextClassContext } from './Text';
8
+
9
+ /**
10
+ * CustomCard container component with squircle shape
11
+ *
12
+ * Provides elevated container with smooth squircle corners (iOS-like rounded corners),
13
+ * border, and shadow. Uses Figma's squircle algorithm for premium look.
14
+ * Use with CardHeader, CardTitle, CardDescription, CardContent, and CardFooter
15
+ * for a complete card structure.
16
+ *
17
+ * @component
18
+ * @example
19
+ * ```tsx
20
+ * <CustomCard>
21
+ * <CardHeader>
22
+ * <CardTitle><Text>Title</Text></CardTitle>
23
+ * <CardDescription><Text>Description</Text></CardDescription>
24
+ * </CardHeader>
25
+ * <CardContent>
26
+ * <Text>Card content goes here</Text>
27
+ * </CardContent>
28
+ * <CardFooter>
29
+ * <Button>Action</Button>
30
+ * </CardFooter>
31
+ * </CustomCard>
32
+ *
33
+ * // With custom corner smoothing
34
+ * <CustomCard cornerSmoothing={1}>
35
+ * <Text>Extra smooth corners</Text>
36
+ * </CustomCard>
37
+ * ```
38
+ *
39
+ * @property {boolean} [asChild] - When true, merges props into immediate child
40
+ * @property {string} [className] - Additional Tailwind classes
41
+ * @property {number} [cornerRadius=16] - Corner radius in pixels
42
+ * @property {number} [cornerSmoothing=0.8] - Smoothing factor (0-1), higher = more iOS-like
43
+ */
44
+ const CustomCard = React.forwardRef<
45
+ ViewRef,
46
+ SlottableViewProps & {
47
+ className?: string;
48
+ cornerRadius?: number;
49
+ cornerSmoothing?: number;
50
+ }
51
+ >(
52
+ (
53
+ {
54
+ className: _className,
55
+ asChild = false,
56
+ cornerRadius = 16,
57
+ cornerSmoothing = 1.0,
58
+ children,
59
+ ...props
60
+ },
61
+ ref
62
+ ) => {
63
+ const colorScheme = useColorScheme();
64
+ const isDark = colorScheme === 'dark';
65
+
66
+ if (asChild) {
67
+ const Component = Slot.View;
68
+ return (
69
+ <Component ref={ref as any} {...props}>
70
+ {children}
71
+ </Component>
72
+ );
73
+ }
74
+
75
+ // Use SquircleView directly as the container with inner View for content
76
+ return (
77
+ <SquircleView
78
+ squircleParams={{
79
+ cornerRadius,
80
+ cornerSmoothing,
81
+ fillColor: isDark ? 'hsl(224 71.4% 4.1%)' : 'hsl(0 0% 100%)',
82
+ strokeColor: isDark
83
+ ? 'hsl(215 27.9% 16.9%)'
84
+ : 'hsl(214.3 31.8% 91.4%)',
85
+ strokeWidth: 1,
86
+ }}
87
+ {...props}
88
+ >
89
+ <View ref={ref as any} className="px-4">
90
+ {children}
91
+ </View>
92
+ </SquircleView>
93
+ );
94
+ }
95
+ );
96
+ CustomCard.displayName = 'CustomCard';
97
+
98
+ /**
99
+ * CustomCardHeader - Container for card title and description
100
+ *
101
+ * Provides proper spacing for title and description elements at the top of a card.
102
+ *
103
+ * @property {boolean} [asChild] - When true, merges props into immediate child
104
+ * @property {string} [className] - Additional Tailwind classes
105
+ */
106
+ const CustomCardHeader = React.forwardRef<
107
+ ViewRef,
108
+ SlottableViewProps & { className?: string }
109
+ >(({ className, asChild = false, ...props }, ref) => {
110
+ const Component = asChild ? Slot.View : View;
111
+ return (
112
+ <Component
113
+ className={cn('flex flex-col gap-1.5 p-6', className)}
114
+ ref={ref as any}
115
+ {...props}
116
+ />
117
+ );
118
+ });
119
+ CustomCardHeader.displayName = 'CustomCardHeader';
120
+
121
+ /**
122
+ * CustomCardTitle - Main title text for the card
123
+ *
124
+ * Automatically styles child Text components with large, semibold typography.
125
+ * Uses TextClassContext to apply consistent styling to nested Text elements.
126
+ *
127
+ * @property {boolean} [asChild] - When true, merges props into immediate child
128
+ * @property {string} [className] - Additional Tailwind classes for text styling
129
+ */
130
+ const CustomCardTitle = React.forwardRef<
131
+ ViewRef,
132
+ SlottableViewProps & { className?: string }
133
+ >(({ className, asChild = false, ...props }, ref) => {
134
+ const Component = asChild ? Slot.View : View;
135
+ return (
136
+ <TextClassContext.Provider
137
+ value={cn(
138
+ 'text-2xl font-semibold leading-none tracking-tight text-card-foreground dark:text-card-foreground',
139
+ className
140
+ )}
141
+ >
142
+ <Component ref={ref as any} {...props} />
143
+ </TextClassContext.Provider>
144
+ );
145
+ });
146
+ CustomCardTitle.displayName = 'CustomCardTitle';
147
+
148
+ /**
149
+ * CustomCardDescription - Subtitle or description text for the card
150
+ *
151
+ * Automatically styles child Text components with muted, smaller typography.
152
+ * Uses TextClassContext to apply consistent styling to nested Text elements.
153
+ *
154
+ * @property {boolean} [asChild] - When true, merges props into immediate child
155
+ * @property {string} [className] - Additional Tailwind classes for text styling
156
+ */
157
+ const CustomCardDescription = React.forwardRef<
158
+ ViewRef,
159
+ SlottableViewProps & { className?: string }
160
+ >(({ className, asChild = false, ...props }, ref) => {
161
+ const Component = asChild ? Slot.View : View;
162
+ return (
163
+ <TextClassContext.Provider
164
+ value={cn(
165
+ 'text-sm text-muted-foreground dark:text-muted-foreground',
166
+ className
167
+ )}
168
+ >
169
+ <Component ref={ref as any} {...props} />
170
+ </TextClassContext.Provider>
171
+ );
172
+ });
173
+ CustomCardDescription.displayName = 'CustomCardDescription';
174
+
175
+ /**
176
+ * CustomCardContent - Main content area of the card
177
+ *
178
+ * Container for the primary card content with appropriate padding.
179
+ * Top padding is removed to flow naturally after CustomCardHeader.
180
+ *
181
+ * @property {boolean} [asChild] - When true, merges props into immediate child
182
+ * @property {string} [className] - Additional Tailwind classes
183
+ */
184
+ const CustomCardContent = React.forwardRef<
185
+ ViewRef,
186
+ SlottableViewProps & { className?: string }
187
+ >(({ className, asChild = false, ...props }, ref) => {
188
+ const Component = asChild ? Slot.View : View;
189
+ return (
190
+ <Component className={cn('p-6 pt-0', className)} ref={ref} {...props} />
191
+ );
192
+ });
193
+ CustomCardContent.displayName = 'CustomCardContent';
194
+
195
+ /**
196
+ * CustomCardFooter - Footer area for actions or additional info
197
+ *
198
+ * Typically contains buttons or other interactive elements.
199
+ * Arranged as a row with centered items.
200
+ *
201
+ * @property {boolean} [asChild] - When true, merges props into immediate child
202
+ * @property {string} [className] - Additional Tailwind classes
203
+ */
204
+ const CustomCardFooter = React.forwardRef<
205
+ ViewRef,
206
+ SlottableViewProps & { className?: string }
207
+ >(({ className, asChild = false, ...props }, ref) => {
208
+ const Component = asChild ? Slot.View : View;
209
+ return (
210
+ <Component
211
+ className={cn('flex flex-row items-center p-6 pt-0', className)}
212
+ ref={ref as any}
213
+ {...props}
214
+ />
215
+ );
216
+ });
217
+ CustomCardFooter.displayName = 'CustomCardFooter';
218
+
219
+ export {
220
+ CustomCard,
221
+ CustomCardContent,
222
+ CustomCardDescription,
223
+ CustomCardFooter,
224
+ CustomCardHeader,
225
+ CustomCardTitle,
226
+ };