@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,93 @@
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 Textarea component
9
+ *
10
+ * @extends TextInputProps - All React Native TextInput properties
11
+ * @property {string} [className] - Additional Tailwind classes for styling
12
+ * @property {string} [placeholderClassName] - Tailwind classes for placeholder text styling
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * <Textarea
17
+ * placeholder="Enter your message"
18
+ * value={message}
19
+ * onChangeText={setMessage}
20
+ * numberOfLines={4}
21
+ * />
22
+ * ```
23
+ */
24
+ interface TextareaProps extends TextInputProps {
25
+ className?: string;
26
+ placeholderClassName?: string;
27
+ }
28
+
29
+ /**
30
+ * Multi-line text input component
31
+ *
32
+ * A textarea component for longer text input with configurable height and styling.
33
+ * Automatically configured for multi-line input with appropriate platform-specific defaults.
34
+ *
35
+ * @component
36
+ * @example
37
+ * ```tsx
38
+ * // Basic textarea
39
+ * <Textarea
40
+ * placeholder="Enter description"
41
+ * value={description}
42
+ * onChangeText={setDescription}
43
+ * />
44
+ *
45
+ * // Controlled textarea with validation
46
+ * <Textarea
47
+ * value={bio}
48
+ * onChangeText={setBio}
49
+ * placeholder="Tell us about yourself"
50
+ * numberOfLines={6}
51
+ * maxLength={500}
52
+ * />
53
+ *
54
+ * // Disabled textarea
55
+ * <Textarea value={content} editable={false} />
56
+ * ```
57
+ *
58
+ * @accessibility
59
+ * - Supports standard TextInput accessibility props
60
+ * - Placeholder text with appropriate color contrast
61
+ * - Focus states on web for keyboard navigation
62
+ * - Disabled state with reduced opacity
63
+ */
64
+ function Textarea({
65
+ className,
66
+ multiline = true,
67
+ numberOfLines = Platform.select({ web: 2, native: 8 }),
68
+ placeholderClassName,
69
+ editable = true,
70
+ ...props
71
+ }: TextareaProps & React.RefAttributes<TextInput>) {
72
+ return (
73
+ <TextInput
74
+ className={cn(
75
+ 'text-foreground border-input dark:bg-input/30 flex min-h-16 w-full flex-row rounded-md border bg-transparent px-3 py-2 text-base shadow-sm shadow-black/5 md:text-sm',
76
+ Platform.select({
77
+ web: 'placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive field-sizing-content resize-y outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed',
78
+ }),
79
+ !editable && 'opacity-50',
80
+ className
81
+ )}
82
+ placeholderClassName={cn('text-muted-foreground', placeholderClassName)}
83
+ multiline={multiline}
84
+ numberOfLines={numberOfLines}
85
+ textAlignVertical="top"
86
+ editable={editable}
87
+ {...props}
88
+ />
89
+ );
90
+ }
91
+
92
+ export { Textarea };
93
+ export type { TextareaProps };
@@ -0,0 +1,204 @@
1
+ import * as React from 'react';
2
+ import { View, Pressable } from 'react-native';
3
+ import { cssInterop } from 'nativewind';
4
+ import { Moon, Sun, Monitor } from 'lucide-react-native';
5
+ import { cn } from '../../lib/utils';
6
+ import { useTheme, type ThemeMode } from '../../lib/ThemeProvider';
7
+ import { Icon } from './Icon';
8
+ import { Text } from './Text';
9
+ import type { SlottableViewProps, ViewRef } from '@rn-primitives/types';
10
+
11
+ cssInterop(View, { className: 'style' });
12
+ cssInterop(Pressable, { className: 'style' });
13
+
14
+ /**
15
+ * Theme toggle button variants
16
+ */
17
+ type ThemeToggleVariant = 'icon' | 'button' | 'segmented';
18
+
19
+ /**
20
+ * Props for ThemeToggle component
21
+ *
22
+ * @property {'icon' | 'button' | 'segmented'} [variant='icon'] - Visual style variant
23
+ * @property {boolean} [showLabel=false] - Show text label next to icon (for button variant)
24
+ * @property {string} [className] - Additional Tailwind classes
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * // Icon only (default)
29
+ * <ThemeToggle />
30
+ *
31
+ * // Button with label
32
+ * <ThemeToggle variant="button" showLabel />
33
+ *
34
+ * // Segmented control
35
+ * <ThemeToggle variant="segmented" />
36
+ * ```
37
+ */
38
+ type ThemeToggleProps = SlottableViewProps & {
39
+ variant?: ThemeToggleVariant;
40
+ showLabel?: boolean;
41
+ className?: string;
42
+ };
43
+
44
+ /**
45
+ * Theme toggle component for switching between light, dark, and system themes
46
+ *
47
+ * Provides three variants:
48
+ * - **icon**: Simple icon button that cycles through themes (light → dark → system)
49
+ * - **button**: Button with icon and optional label
50
+ * - **segmented**: Segmented control showing all three options
51
+ *
52
+ * @component
53
+ * @example
54
+ * ```tsx
55
+ * // Icon toggle (minimal)
56
+ * <ThemeToggle />
57
+ *
58
+ * // Button with label
59
+ * <ThemeToggle variant="button" showLabel />
60
+ *
61
+ * // Segmented control (shows all options)
62
+ * <ThemeToggle variant="segmented" />
63
+ *
64
+ * // Custom styling
65
+ * <ThemeToggle className="my-4" />
66
+ * ```
67
+ *
68
+ * @accessibility
69
+ * - Proper button role for screen readers
70
+ * - Announces current theme state
71
+ * - Keyboard navigable on web
72
+ */
73
+ const ThemeToggle = React.forwardRef<ViewRef, ThemeToggleProps>(
74
+ ({ variant = 'icon', showLabel = false, className, ...props }, ref) => {
75
+ const { mode, isDark, setMode } = useTheme();
76
+
77
+ // Cycle through themes: light → dark → system → light
78
+ const cycleTheme = () => {
79
+ const nextMode: ThemeMode =
80
+ mode === 'light' ? 'dark' : mode === 'dark' ? 'system' : 'light';
81
+ setMode(nextMode);
82
+ };
83
+
84
+ if (variant === 'segmented') {
85
+ return (
86
+ <View
87
+ ref={ref as any}
88
+ className={cn(
89
+ 'flex-row bg-muted dark:bg-muted rounded-lg p-1',
90
+ className
91
+ )}
92
+ {...props}
93
+ >
94
+ {(['light', 'dark', 'system'] as const).map((themeMode) => {
95
+ const isActive = mode === themeMode;
96
+ const IconComponent =
97
+ themeMode === 'light'
98
+ ? Sun
99
+ : themeMode === 'dark'
100
+ ? Moon
101
+ : Monitor;
102
+
103
+ return (
104
+ <Pressable
105
+ key={themeMode}
106
+ onPress={() => setMode(themeMode)}
107
+ className={cn(
108
+ 'flex-1 flex-row items-center justify-center rounded-md px-3 py-2 transition-colors',
109
+ isActive
110
+ ? 'bg-background dark:bg-background shadow-sm'
111
+ : 'active:bg-background/50 dark:active:bg-background/50'
112
+ )}
113
+ role="button"
114
+ accessibilityLabel={`Switch to ${themeMode} theme`}
115
+ accessibilityState={{ selected: isActive }}
116
+ >
117
+ <Icon
118
+ as={IconComponent}
119
+ size={16}
120
+ className={cn(
121
+ isActive
122
+ ? 'text-foreground dark:text-foreground'
123
+ : 'text-muted-foreground dark:text-muted-foreground'
124
+ )}
125
+ />
126
+ <Text
127
+ className={cn(
128
+ 'ml-2 text-sm font-medium capitalize',
129
+ isActive
130
+ ? 'text-foreground dark:text-foreground'
131
+ : 'text-muted-foreground dark:text-muted-foreground'
132
+ )}
133
+ >
134
+ {themeMode}
135
+ </Text>
136
+ </Pressable>
137
+ );
138
+ })}
139
+ </View>
140
+ );
141
+ }
142
+
143
+ if (variant === 'button') {
144
+ const IconComponent = isDark ? Moon : Sun;
145
+ const label = mode === 'system' ? 'System' : isDark ? 'Dark' : 'Light';
146
+
147
+ return (
148
+ <Pressable
149
+ ref={ref as any}
150
+ onPress={cycleTheme}
151
+ className={cn(
152
+ 'flex-row items-center justify-center rounded-md px-4 py-2',
153
+ 'bg-secondary dark:bg-secondary active:opacity-80',
154
+ className
155
+ )}
156
+ role="button"
157
+ accessibilityLabel={`Current theme: ${label}. Tap to change.`}
158
+ {...props}
159
+ >
160
+ <Icon
161
+ as={IconComponent}
162
+ size={16}
163
+ className="text-secondary-foreground dark:text-secondary-foreground"
164
+ />
165
+ {showLabel && (
166
+ <Text className="ml-2 text-sm font-medium text-secondary-foreground dark:text-secondary-foreground">
167
+ {label}
168
+ </Text>
169
+ )}
170
+ </Pressable>
171
+ );
172
+ }
173
+
174
+ // Default: icon variant
175
+ const IconComponent = mode === 'system' ? Monitor : isDark ? Moon : Sun;
176
+
177
+ return (
178
+ <Pressable
179
+ ref={ref as any}
180
+ onPress={cycleTheme}
181
+ className={cn(
182
+ 'h-10 w-10 items-center justify-center rounded-md',
183
+ 'active:bg-accent dark:active:bg-accent',
184
+ 'web:hover:bg-accent/50 dark:web:hover:bg-accent/50',
185
+ 'web:transition-colors',
186
+ className
187
+ )}
188
+ role="button"
189
+ accessibilityLabel={`Current theme: ${mode}. Tap to cycle themes.`}
190
+ {...props}
191
+ >
192
+ <Icon
193
+ as={IconComponent}
194
+ size={20}
195
+ className="text-foreground dark:text-foreground"
196
+ />
197
+ </Pressable>
198
+ );
199
+ }
200
+ );
201
+ ThemeToggle.displayName = 'ThemeToggle';
202
+
203
+ export { ThemeToggle };
204
+ export type { ThemeToggleProps, ThemeToggleVariant };
@@ -0,0 +1,127 @@
1
+ import { cn } from '../../lib/utils';
2
+ import { Text, TextClassContext } from './Text';
3
+ import { Icon } from './Icon';
4
+ import type { LucideIcon } from 'lucide-react-native';
5
+ import * as React from 'react';
6
+ import { View, type ViewProps } from 'react-native';
7
+ import { cssInterop } from 'nativewind';
8
+
9
+ cssInterop(View, { className: 'style' });
10
+
11
+ /**
12
+ * Toast notification component
13
+ *
14
+ * Displays temporary messages or notifications with optional icons.
15
+ * Supports default and destructive variants for different message types.
16
+ *
17
+ * @component
18
+ * @example
19
+ * ```tsx
20
+ * <Toast icon={CheckCircle}>
21
+ * <ToastTitle>
22
+ * <Text>Success</Text>
23
+ * </ToastTitle>
24
+ * <ToastDescription>
25
+ * <Text>Your changes have been saved</Text>
26
+ * </ToastDescription>
27
+ * </Toast>
28
+ *
29
+ * <Toast variant="destructive" icon={AlertCircle}>
30
+ * <ToastTitle>
31
+ * <Text>Error</Text>
32
+ * </ToastTitle>
33
+ * <ToastDescription>
34
+ * <Text>Failed to save changes</Text>
35
+ * </ToastDescription>
36
+ * </Toast>
37
+ * ```
38
+ *
39
+ * @accessibility
40
+ * - Announces messages to screen readers
41
+ * - Proper role for notifications
42
+ */
43
+ function Toast({
44
+ className,
45
+ variant,
46
+ children,
47
+ icon,
48
+ iconClassName,
49
+ ...props
50
+ }: ViewProps &
51
+ React.RefAttributes<View> & {
52
+ icon?: LucideIcon;
53
+ variant?: 'default' | 'destructive';
54
+ iconClassName?: string;
55
+ }) {
56
+ return (
57
+ <TextClassContext.Provider
58
+ value={cn(
59
+ 'text-sm text-foreground',
60
+ variant === 'destructive' && 'text-destructive',
61
+ className
62
+ )}
63
+ >
64
+ <View
65
+ className={cn(
66
+ 'bg-background border-border relative flex-row items-start gap-3 rounded-lg border p-4 shadow-lg shadow-black/10',
67
+ variant === 'destructive' && 'border-destructive',
68
+ className
69
+ )}
70
+ {...props}
71
+ >
72
+ {icon && (
73
+ <Icon
74
+ as={icon}
75
+ className={cn(
76
+ 'size-5 shrink-0',
77
+ variant === 'destructive' && 'text-destructive',
78
+ iconClassName
79
+ )}
80
+ />
81
+ )}
82
+ <View className="flex-1 gap-1">{children}</View>
83
+ </View>
84
+ </TextClassContext.Provider>
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Toast title component
90
+ *
91
+ * @component
92
+ */
93
+ function ToastTitle({
94
+ className,
95
+ ...props
96
+ }: React.ComponentProps<typeof Text> & React.RefAttributes<Text>) {
97
+ return (
98
+ <Text
99
+ className={cn('font-semibold leading-none tracking-tight', className)}
100
+ {...props}
101
+ />
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Toast description component
107
+ *
108
+ * @component
109
+ */
110
+ function ToastDescription({
111
+ className,
112
+ ...props
113
+ }: React.ComponentProps<typeof Text> & React.RefAttributes<Text>) {
114
+ const textClass = React.useContext(TextClassContext);
115
+ return (
116
+ <Text
117
+ className={cn(
118
+ 'text-muted-foreground text-sm',
119
+ textClass?.includes('text-destructive') && 'text-destructive/90',
120
+ className
121
+ )}
122
+ {...props}
123
+ />
124
+ );
125
+ }
126
+
127
+ export { Toast, ToastDescription, ToastTitle };
@@ -0,0 +1,160 @@
1
+ import { Icon } from './Icon';
2
+ import { TextClassContext } from './Text';
3
+ import { toggleVariants } from './Toggle';
4
+ import { cn } from '../../lib/utils';
5
+ import * as ToggleGroupPrimitive from '@rn-primitives/toggle-group';
6
+ import type { VariantProps } from 'class-variance-authority';
7
+ import * as React from 'react';
8
+ import { Platform } from 'react-native';
9
+
10
+ /**
11
+ * Context for sharing variant and size props across toggle group items
12
+ */
13
+ const ToggleGroupContext = React.createContext<VariantProps<
14
+ typeof toggleVariants
15
+ > | null>(null);
16
+
17
+ /**
18
+ * Toggle group component for grouping related toggle buttons
19
+ *
20
+ * Allows single or multiple selection among a set of toggle buttons.
21
+ * Supports both outline and default variants.
22
+ *
23
+ * @component
24
+ * @example
25
+ * ```tsx
26
+ * <ToggleGroup type="single" value={alignment} onValueChange={setAlignment}>
27
+ * <ToggleGroupItem value="left" isFirst>
28
+ * <ToggleGroupIcon as={AlignLeft} />
29
+ * </ToggleGroupItem>
30
+ * <ToggleGroupItem value="center">
31
+ * <ToggleGroupIcon as={AlignCenter} />
32
+ * </ToggleGroupItem>
33
+ * <ToggleGroupItem value="right" isLast>
34
+ * <ToggleGroupIcon as={AlignRight} />
35
+ * </ToggleGroupItem>
36
+ * </ToggleGroup>
37
+ * ```
38
+ *
39
+ * @accessibility
40
+ * - Uses proper ARIA attributes for grouped toggles
41
+ * - Keyboard navigation support
42
+ */
43
+ function ToggleGroup({
44
+ className,
45
+ variant,
46
+ size,
47
+ children,
48
+ ...props
49
+ }: ToggleGroupPrimitive.RootProps &
50
+ VariantProps<typeof toggleVariants> &
51
+ React.RefAttributes<ToggleGroupPrimitive.RootRef>) {
52
+ return (
53
+ <ToggleGroupPrimitive.Root
54
+ className={cn(
55
+ 'flex flex-row items-center rounded-md shadow-none',
56
+ Platform.select({ web: 'w-fit' }),
57
+ variant === 'outline' && 'shadow-sm shadow-black/5',
58
+ className
59
+ )}
60
+ {...props}
61
+ >
62
+ <ToggleGroupContext.Provider value={{ variant, size }}>
63
+ {children}
64
+ </ToggleGroupContext.Provider>
65
+ </ToggleGroupPrimitive.Root>
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Hook to access toggle group context
71
+ *
72
+ * @throws Error if used outside of ToggleGroup component
73
+ */
74
+ function useToggleGroupContext() {
75
+ const context = React.useContext(ToggleGroupContext);
76
+ if (context === null) {
77
+ throw new Error(
78
+ 'ToggleGroup compound components cannot be rendered outside the ToggleGroup component'
79
+ );
80
+ }
81
+ return context;
82
+ }
83
+
84
+ /**
85
+ * Individual toggle item within a toggle group
86
+ *
87
+ * @component
88
+ */
89
+ function ToggleGroupItem({
90
+ className,
91
+ children,
92
+ variant,
93
+ size,
94
+ isFirst,
95
+ isLast,
96
+ ...props
97
+ }: ToggleGroupPrimitive.ItemProps &
98
+ VariantProps<typeof toggleVariants> &
99
+ React.RefAttributes<ToggleGroupPrimitive.ItemRef> & {
100
+ isFirst?: boolean;
101
+ isLast?: boolean;
102
+ }) {
103
+ const context = useToggleGroupContext();
104
+ const { value } = ToggleGroupPrimitive.useRootContext();
105
+
106
+ return (
107
+ <TextClassContext.Provider
108
+ value={cn(
109
+ 'text-sm text-foreground font-medium',
110
+ ToggleGroupPrimitive.utils.getIsSelected(value, props.value)
111
+ ? 'text-accent-foreground'
112
+ : Platform.select({ web: 'group-hover:text-muted-foreground' })
113
+ )}
114
+ >
115
+ <ToggleGroupPrimitive.Item
116
+ className={cn(
117
+ toggleVariants({
118
+ variant: context.variant || variant,
119
+ size: context.size || size,
120
+ }),
121
+ props.disabled && 'opacity-50',
122
+ ToggleGroupPrimitive.utils.getIsSelected(value, props.value) &&
123
+ 'bg-accent',
124
+ 'min-w-0 shrink-0 rounded-none shadow-none',
125
+ isFirst && 'rounded-l-md',
126
+ isLast && 'rounded-r-md',
127
+ (context.variant === 'outline' || variant === 'outline') &&
128
+ 'border-l-0',
129
+ (context.variant === 'outline' || variant === 'outline') &&
130
+ isFirst &&
131
+ 'border-l',
132
+ Platform.select({
133
+ web: 'flex-1 focus:z-10 focus-visible:z-10',
134
+ }),
135
+ className
136
+ )}
137
+ {...props}
138
+ >
139
+ {children}
140
+ </ToggleGroupPrimitive.Item>
141
+ </TextClassContext.Provider>
142
+ );
143
+ }
144
+
145
+ /**
146
+ * Icon component for use within toggle group items
147
+ *
148
+ * @component
149
+ */
150
+ function ToggleGroupIcon({
151
+ className,
152
+ ...props
153
+ }: React.ComponentProps<typeof Icon>) {
154
+ const textClass = React.useContext(TextClassContext);
155
+ return (
156
+ <Icon className={cn('size-4 shrink-0', textClass, className)} {...props} />
157
+ );
158
+ }
159
+
160
+ export { ToggleGroup, ToggleGroupIcon, ToggleGroupItem };
@@ -0,0 +1,122 @@
1
+ import { Icon } from './Icon';
2
+ import { TextClassContext } from './Text';
3
+ import { cn } from '../../lib/utils';
4
+ import * as TogglePrimitive from '@rn-primitives/toggle';
5
+ import { cva, type VariantProps } from 'class-variance-authority';
6
+ import * as React from 'react';
7
+ import { Platform } from 'react-native';
8
+
9
+ /**
10
+ * Toggle button style variants using class-variance-authority
11
+ *
12
+ * @variant default - Standard toggle with transparent background
13
+ * @variant outline - Toggle with border and background
14
+ *
15
+ * @size default - Standard size (40px/36px)
16
+ * @size sm - Small size (36px/32px)
17
+ * @size lg - Large size (44px/40px)
18
+ */
19
+ const toggleVariants = cva(
20
+ cn(
21
+ 'active:bg-muted group flex flex-row items-center justify-center gap-2 rounded-md',
22
+ Platform.select({
23
+ web: 'hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex cursor-default whitespace-nowrap outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:pointer-events-none [&_svg]:pointer-events-none',
24
+ })
25
+ ),
26
+ {
27
+ variants: {
28
+ variant: {
29
+ default: 'bg-transparent',
30
+ outline: cn(
31
+ 'border-input active:bg-accent border bg-transparent shadow-sm shadow-black/5',
32
+ Platform.select({
33
+ web: 'hover:bg-accent hover:text-accent-foreground',
34
+ })
35
+ ),
36
+ },
37
+ size: {
38
+ default: 'h-10 min-w-10 px-2.5 sm:h-9 sm:min-w-9 sm:px-2',
39
+ sm: 'h-9 min-w-9 px-2 sm:h-8 sm:min-w-8 sm:px-1.5',
40
+ lg: 'h-11 min-w-11 px-3 sm:h-10 sm:min-w-10 sm:px-2.5',
41
+ },
42
+ },
43
+ defaultVariants: {
44
+ variant: 'default',
45
+ size: 'default',
46
+ },
47
+ }
48
+ );
49
+
50
+ /**
51
+ * Toggle button component for on/off states
52
+ *
53
+ * A two-state button that can be toggled between pressed and unpressed states.
54
+ * Supports multiple variants and sizes.
55
+ *
56
+ * @component
57
+ * @example
58
+ * ```tsx
59
+ * <Toggle pressed={isBold} onPressedChange={setIsBold}>
60
+ * <ToggleIcon as={Bold} />
61
+ * <Text>Bold</Text>
62
+ * </Toggle>
63
+ *
64
+ * <Toggle variant="outline" size="sm">
65
+ * <ToggleIcon as={Italic} />
66
+ * </Toggle>
67
+ * ```
68
+ *
69
+ * @accessibility
70
+ * - Uses proper ARIA pressed state
71
+ * - Disabled state prevents interaction
72
+ * - Focus visible states on web
73
+ */
74
+ function Toggle({
75
+ className,
76
+ variant,
77
+ size,
78
+ ...props
79
+ }: TogglePrimitive.RootProps &
80
+ VariantProps<typeof toggleVariants> &
81
+ React.RefAttributes<TogglePrimitive.RootRef>) {
82
+ return (
83
+ <TextClassContext.Provider
84
+ value={cn(
85
+ 'text-sm text-foreground font-medium',
86
+ props.pressed
87
+ ? 'text-accent-foreground'
88
+ : Platform.select({ web: 'group-hover:text-muted-foreground' }),
89
+ className
90
+ )}
91
+ >
92
+ <TogglePrimitive.Root
93
+ className={cn(
94
+ toggleVariants({ variant, size }),
95
+ props.disabled && 'opacity-50',
96
+ props.pressed && 'bg-accent',
97
+ className
98
+ )}
99
+ {...props}
100
+ />
101
+ </TextClassContext.Provider>
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Icon component for use within Toggle buttons
107
+ *
108
+ * Automatically inherits text styling from the toggle context.
109
+ *
110
+ * @component
111
+ */
112
+ function ToggleIcon({
113
+ className,
114
+ ...props
115
+ }: React.ComponentProps<typeof Icon>) {
116
+ const textClass = React.useContext(TextClassContext);
117
+ return (
118
+ <Icon className={cn('size-4 shrink-0', textClass, className)} {...props} />
119
+ );
120
+ }
121
+
122
+ export { Toggle, ToggleIcon, toggleVariants };