@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,331 @@
1
+ /**
2
+ * Comprehensive Dark Theme Configuration
3
+ *
4
+ * This file provides complete theme tokens and utilities for consistent
5
+ * dark mode support across all components in the Blueprint library.
6
+ *
7
+ * Best Practices:
8
+ * - Uses HSL color format for better color manipulation
9
+ * - Provides semantic color tokens (not raw colors)
10
+ * - Maintains proper contrast ratios for accessibility (WCAG AA/AAA)
11
+ * - Consistent with Tailwind CSS conventions
12
+ */
13
+
14
+ import type { ColorValue } from 'react-native';
15
+
16
+ /**
17
+ * Color tokens for light and dark themes
18
+ * Each token follows the pattern: purpose-variant
19
+ */
20
+ export const THEME_COLORS = {
21
+ light: {
22
+ // Base colors
23
+ background: 'hsl(0, 0%, 100%)', // Pure white
24
+ foreground: 'hsl(0, 0%, 3.9%)', // Near black
25
+
26
+ // Card colors
27
+ card: 'hsl(0, 0%, 100%)',
28
+ cardForeground: 'hsl(0, 0%, 3.9%)',
29
+
30
+ // Popover colors
31
+ popover: 'hsl(0, 0%, 100%)',
32
+ popoverForeground: 'hsl(0, 0%, 3.9%)',
33
+
34
+ // Primary action colors
35
+ primary: 'hsl(0, 0%, 9%)',
36
+ primaryForeground: 'hsl(0, 0%, 98%)',
37
+
38
+ // Secondary action colors
39
+ secondary: 'hsl(0, 0%, 96.1%)',
40
+ secondaryForeground: 'hsl(0, 0%, 9%)',
41
+
42
+ // Muted/subtle colors
43
+ muted: 'hsl(0, 0%, 96.1%)',
44
+ mutedForeground: 'hsl(0, 0%, 45.1%)',
45
+
46
+ // Accent colors
47
+ accent: 'hsl(0, 0%, 96.1%)',
48
+ accentForeground: 'hsl(0, 0%, 9%)',
49
+
50
+ // Destructive/error colors
51
+ destructive: 'hsl(0, 84.2%, 60.2%)',
52
+ destructiveForeground: 'hsl(0, 0%, 98%)',
53
+
54
+ // Border and input colors
55
+ border: 'hsl(0, 0%, 89.8%)',
56
+ input: 'hsl(0, 0%, 89.8%)',
57
+ ring: 'hsl(0, 0%, 63%)',
58
+
59
+ // Success colors
60
+ success: 'hsl(142, 76%, 36%)',
61
+ successForeground: 'hsl(0, 0%, 98%)',
62
+
63
+ // Warning colors
64
+ warning: 'hsl(38, 92%, 50%)',
65
+ warningForeground: 'hsl(0, 0%, 9%)',
66
+
67
+ // Info colors
68
+ info: 'hsl(199, 89%, 48%)',
69
+ infoForeground: 'hsl(0, 0%, 98%)',
70
+ },
71
+ dark: {
72
+ // Base colors
73
+ background: 'hsl(0, 0%, 3.9%)', // Near black
74
+ foreground: 'hsl(0, 0%, 98%)', // Near white
75
+
76
+ // Card colors
77
+ card: 'hsl(0, 0%, 3.9%)',
78
+ cardForeground: 'hsl(0, 0%, 98%)',
79
+
80
+ // Popover colors
81
+ popover: 'hsl(0, 0%, 3.9%)',
82
+ popoverForeground: 'hsl(0, 0%, 98%)',
83
+
84
+ // Primary action colors (inverted from light)
85
+ primary: 'hsl(0, 0%, 98%)',
86
+ primaryForeground: 'hsl(0, 0%, 9%)',
87
+
88
+ // Secondary action colors
89
+ secondary: 'hsl(0, 0%, 14.9%)',
90
+ secondaryForeground: 'hsl(0, 0%, 98%)',
91
+
92
+ // Muted/subtle colors
93
+ muted: 'hsl(0, 0%, 14.9%)',
94
+ mutedForeground: 'hsl(0, 0%, 63.9%)',
95
+
96
+ // Accent colors
97
+ accent: 'hsl(0, 0%, 14.9%)',
98
+ accentForeground: 'hsl(0, 0%, 98%)',
99
+
100
+ // Destructive/error colors
101
+ destructive: 'hsl(0, 62.8%, 30.6%)',
102
+ destructiveForeground: 'hsl(0, 0%, 98%)',
103
+
104
+ // Border and input colors
105
+ border: 'hsl(0, 0%, 14.9%)',
106
+ input: 'hsl(0, 0%, 14.9%)',
107
+ ring: 'hsl(0, 0%, 45%)',
108
+
109
+ // Success colors
110
+ success: 'hsl(142, 76%, 36%)',
111
+ successForeground: 'hsl(0, 0%, 98%)',
112
+
113
+ // Warning colors
114
+ warning: 'hsl(38, 92%, 50%)',
115
+ warningForeground: 'hsl(0, 0%, 9%)',
116
+
117
+ // Info colors
118
+ info: 'hsl(199, 89%, 48%)',
119
+ infoForeground: 'hsl(0, 0%, 98%)',
120
+ },
121
+ } as const;
122
+
123
+ /**
124
+ * Convert HSL string to React Native ColorValue
125
+ * Useful for native platform styling where HSL needs to be converted
126
+ */
127
+ export function hslToRgb(hsl: string): ColorValue {
128
+ // Extract h, s, l from "hsl(h, s%, l%)" format
129
+ const match = hsl.match(
130
+ /hsl\((\d+),\s*(\d+(?:\.\d+)?)%,\s*(\d+(?:\.\d+)?)%\)/
131
+ );
132
+ if (!match || !match[1] || !match[2] || !match[3]) return hsl as ColorValue;
133
+
134
+ const h = parseInt(match[1], 10) / 360;
135
+ const s = parseFloat(match[2]) / 100;
136
+ const l = parseFloat(match[3]) / 100;
137
+
138
+ let r, g, b;
139
+
140
+ if (s === 0) {
141
+ r = g = b = l;
142
+ } else {
143
+ const hue2rgb = (p: number, q: number, t: number) => {
144
+ if (t < 0) t += 1;
145
+ if (t > 1) t -= 1;
146
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
147
+ if (t < 1 / 2) return q;
148
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
149
+ return p;
150
+ };
151
+
152
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
153
+ const p = 2 * l - q;
154
+
155
+ r = hue2rgb(p, q, h + 1 / 3);
156
+ g = hue2rgb(p, q, h);
157
+ b = hue2rgb(p, q, h - 1 / 3);
158
+ }
159
+
160
+ const toHex = (x: number) => {
161
+ const hex = Math.round(x * 255).toString(16);
162
+ return hex.length === 1 ? '0' + hex : hex;
163
+ };
164
+
165
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}` as ColorValue;
166
+ }
167
+
168
+ /**
169
+ * Get theme color value based on current theme mode
170
+ * Automatically selects the appropriate color from light/dark theme
171
+ */
172
+ export function getThemeColorValue(
173
+ colorKey: keyof typeof THEME_COLORS.light,
174
+ isDark: boolean,
175
+ asRgb = false
176
+ ): ColorValue {
177
+ const color = isDark
178
+ ? THEME_COLORS.dark[colorKey]
179
+ : THEME_COLORS.light[colorKey];
180
+ return asRgb ? hslToRgb(color) : (color as ColorValue);
181
+ }
182
+
183
+ /**
184
+ * Component-specific dark mode class mappings
185
+ * Use these to ensure consistent styling across all components
186
+ */
187
+ export const DARK_MODE_CLASSES = {
188
+ // Text variants
189
+ text: {
190
+ default: 'dark:text-foreground',
191
+ muted: 'dark:text-muted-foreground',
192
+ primary: 'dark:text-primary-foreground',
193
+ secondary: 'dark:text-secondary-foreground',
194
+ accent: 'dark:text-accent-foreground',
195
+ destructive: 'dark:text-destructive-foreground',
196
+ card: 'dark:text-card-foreground',
197
+ },
198
+
199
+ // Background variants
200
+ background: {
201
+ default: 'dark:bg-background',
202
+ card: 'dark:bg-card',
203
+ primary: 'dark:bg-primary',
204
+ secondary: 'dark:bg-secondary',
205
+ muted: 'dark:bg-muted',
206
+ accent: 'dark:bg-accent',
207
+ destructive: 'dark:bg-destructive',
208
+ popover: 'dark:bg-popover',
209
+ },
210
+
211
+ // Border variants
212
+ border: {
213
+ default: 'dark:border-border',
214
+ input: 'dark:border-input',
215
+ primary: 'dark:border-primary',
216
+ muted: 'dark:border-muted',
217
+ accent: 'dark:border-accent',
218
+ destructive: 'dark:border-destructive',
219
+ },
220
+
221
+ // Interactive states
222
+ state: {
223
+ hover: 'dark:hover:bg-accent',
224
+ active: 'dark:active:bg-accent',
225
+ focus: 'dark:focus:ring-ring',
226
+ disabled: 'dark:disabled:opacity-50',
227
+ },
228
+ } as const;
229
+
230
+ /**
231
+ * Helper to combine base classes with dark mode classes
232
+ * Ensures consistent application of dark mode styles
233
+ */
234
+ export function withDarkMode(
235
+ baseClass: string,
236
+ darkClass: string,
237
+ additionalClasses?: string
238
+ ): string {
239
+ return [baseClass, darkClass, additionalClasses].filter(Boolean).join(' ');
240
+ }
241
+
242
+ /**
243
+ * Pre-configured class strings for common component patterns
244
+ * Use these for consistent styling across the library
245
+ */
246
+ export const THEME_PRESETS = {
247
+ // Card-like containers
248
+ card: 'bg-card text-card-foreground dark:bg-card dark:text-card-foreground border border-border dark:border-border',
249
+
250
+ // Input fields
251
+ input:
252
+ 'bg-background text-foreground dark:bg-background dark:text-foreground border border-input dark:border-input',
253
+
254
+ // Buttons - primary
255
+ buttonPrimary:
256
+ 'bg-primary text-primary-foreground dark:bg-primary dark:text-primary-foreground',
257
+
258
+ // Buttons - secondary
259
+ buttonSecondary:
260
+ 'bg-secondary text-secondary-foreground dark:bg-secondary dark:text-secondary-foreground',
261
+
262
+ // Buttons - outline
263
+ buttonOutline:
264
+ 'bg-background text-foreground dark:bg-background dark:text-foreground border border-input dark:border-input',
265
+
266
+ // Buttons - ghost
267
+ buttonGhost:
268
+ 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent dark:hover:text-accent-foreground',
269
+
270
+ // Destructive elements
271
+ destructive:
272
+ 'bg-destructive text-destructive-foreground dark:bg-destructive dark:text-destructive-foreground',
273
+
274
+ // Muted/subtle elements
275
+ muted:
276
+ 'bg-muted text-muted-foreground dark:bg-muted dark:text-muted-foreground',
277
+
278
+ // Popover/dropdown
279
+ popover:
280
+ 'bg-popover text-popover-foreground dark:bg-popover dark:text-popover-foreground border border-border dark:border-border',
281
+ } as const;
282
+
283
+ /**
284
+ * Semantic color map for native components
285
+ * Use these for StyleSheet-based styling
286
+ */
287
+ export function getNativeThemeColors(isDark: boolean) {
288
+ return {
289
+ background: hslToRgb(getThemeColorValue('background', isDark) as string),
290
+ foreground: hslToRgb(getThemeColorValue('foreground', isDark) as string),
291
+ card: hslToRgb(getThemeColorValue('card', isDark) as string),
292
+ cardForeground: hslToRgb(
293
+ getThemeColorValue('cardForeground', isDark) as string
294
+ ),
295
+ primary: hslToRgb(getThemeColorValue('primary', isDark) as string),
296
+ primaryForeground: hslToRgb(
297
+ getThemeColorValue('primaryForeground', isDark) as string
298
+ ),
299
+ secondary: hslToRgb(getThemeColorValue('secondary', isDark) as string),
300
+ secondaryForeground: hslToRgb(
301
+ getThemeColorValue('secondaryForeground', isDark) as string
302
+ ),
303
+ muted: hslToRgb(getThemeColorValue('muted', isDark) as string),
304
+ mutedForeground: hslToRgb(
305
+ getThemeColorValue('mutedForeground', isDark) as string
306
+ ),
307
+ accent: hslToRgb(getThemeColorValue('accent', isDark) as string),
308
+ accentForeground: hslToRgb(
309
+ getThemeColorValue('accentForeground', isDark) as string
310
+ ),
311
+ destructive: hslToRgb(getThemeColorValue('destructive', isDark) as string),
312
+ destructiveForeground: hslToRgb(
313
+ getThemeColorValue('destructiveForeground', isDark) as string
314
+ ),
315
+ border: hslToRgb(getThemeColorValue('border', isDark) as string),
316
+ input: hslToRgb(getThemeColorValue('input', isDark) as string),
317
+ ring: hslToRgb(getThemeColorValue('ring', isDark) as string),
318
+ };
319
+ }
320
+
321
+ /**
322
+ * Type-safe theme color keys
323
+ */
324
+ export type ThemeColorKey = keyof typeof THEME_COLORS.light;
325
+
326
+ /**
327
+ * Check if a color key exists in the theme
328
+ */
329
+ export function isValidThemeColor(key: string): key is ThemeColorKey {
330
+ return key in THEME_COLORS.light;
331
+ }
@@ -0,0 +1,253 @@
1
+ import * as AccordionPrimitive from '@rn-primitives/accordion';
2
+ import { ChevronDown } from 'lucide-react-native';
3
+ import { cssInterop } from 'nativewind';
4
+ import { Platform, Pressable, View } from 'react-native';
5
+ import Animated, {
6
+ FadeOutUp,
7
+ LayoutAnimationConfig,
8
+ LinearTransition,
9
+ useAnimatedStyle,
10
+ useDerivedValue,
11
+ withTiming,
12
+ } from 'react-native-reanimated';
13
+ import { cn } from '../../lib/utils';
14
+ import { Icon } from './Icon';
15
+ import { TextClassContext } from './Text';
16
+
17
+ cssInterop(View, { className: 'style' });
18
+ cssInterop(Pressable, { className: 'style' });
19
+
20
+ /**
21
+ * Root accordion container with smooth animations
22
+ *
23
+ * A vertically stacked set of interactive panels where only one or multiple panels can be expanded at a time.
24
+ * Features smooth layout animations using Reanimated for a polished user experience.
25
+ *
26
+ * @component
27
+ * @example
28
+ * ```tsx
29
+ * // Single selection
30
+ * <Accordion type="single" collapsible defaultValue="item-1">
31
+ * <AccordionItem value="item-1">
32
+ * <AccordionTrigger>
33
+ * <Text>Question 1</Text>
34
+ * </AccordionTrigger>
35
+ * <AccordionContent>
36
+ * <Text>Answer 1</Text>
37
+ * </AccordionContent>
38
+ * </AccordionItem>
39
+ * </Accordion>
40
+ *
41
+ * // Multiple selection
42
+ * <Accordion type="multiple">
43
+ * <AccordionItem value="item-1">
44
+ * <AccordionTrigger><Text>Section 1</Text></AccordionTrigger>
45
+ * <AccordionContent><Text>Content 1</Text></AccordionContent>
46
+ * </AccordionItem>
47
+ * <AccordionItem value="item-2">
48
+ * <AccordionTrigger><Text>Section 2</Text></AccordionTrigger>
49
+ * <AccordionContent><Text>Content 2</Text></AccordionContent>
50
+ * </AccordionItem>
51
+ * </Accordion>
52
+ * ```
53
+ *
54
+ * @accessibility
55
+ * - Implements WAI-ARIA accordion pattern
56
+ * - Keyboard navigation with arrow keys and Enter/Space
57
+ * - Proper ARIA attributes for expanded/collapsed states
58
+ */
59
+ function Accordion({
60
+ children,
61
+ ...props
62
+ }: Omit<AccordionPrimitive.RootProps, 'asChild'> &
63
+ React.RefAttributes<AccordionPrimitive.RootRef>) {
64
+ return (
65
+ <LayoutAnimationConfig skipEntering>
66
+ <AccordionPrimitive.Root
67
+ {...(props as AccordionPrimitive.RootProps)}
68
+ asChild={Platform.OS !== 'web'}
69
+ >
70
+ <Animated.View layout={LinearTransition.duration(200)}>
71
+ {children}
72
+ </Animated.View>
73
+ </AccordionPrimitive.Root>
74
+ </LayoutAnimationConfig>
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Individual accordion item with border styling
80
+ *
81
+ * Contains a trigger and content section. Each item must have a unique value prop
82
+ * for identification within the parent Accordion.
83
+ *
84
+ * @component
85
+ * @example
86
+ * ```tsx
87
+ * <AccordionItem value="item-1">
88
+ * <AccordionTrigger>
89
+ * <Text>Click to expand</Text>
90
+ * </AccordionTrigger>
91
+ * <AccordionContent>
92
+ * <Text>Hidden content revealed on expansion</Text>
93
+ * </AccordionContent>
94
+ * </AccordionItem>
95
+ * ```
96
+ */
97
+ function AccordionItem({
98
+ children,
99
+ className,
100
+ value,
101
+ ...props
102
+ }: AccordionPrimitive.ItemProps &
103
+ React.RefAttributes<AccordionPrimitive.ItemRef>) {
104
+ return (
105
+ <AccordionPrimitive.Item
106
+ className={cn(
107
+ 'border-border border-b',
108
+ Platform.select({ web: 'last:border-b-0' }),
109
+ className
110
+ )}
111
+ value={value}
112
+ asChild
113
+ {...props}
114
+ >
115
+ <Animated.View
116
+ className="native:overflow-hidden"
117
+ layout={Platform.select({ native: LinearTransition.duration(200) })}
118
+ >
119
+ {children}
120
+ </Animated.View>
121
+ </AccordionPrimitive.Item>
122
+ );
123
+ }
124
+
125
+ const Trigger = Platform.OS === 'web' ? View : Pressable;
126
+
127
+ /**
128
+ * Clickable trigger button that toggles accordion item expansion
129
+ *
130
+ * Features an animated chevron icon that rotates based on expansion state.
131
+ * Automatically manages focus states and hover effects on web.
132
+ *
133
+ * @component
134
+ * @example
135
+ * ```tsx
136
+ * <AccordionTrigger>
137
+ * <Text>Click to toggle</Text>
138
+ * </AccordionTrigger>
139
+ * ```
140
+ *
141
+ * @accessibility
142
+ * - Automatically includes chevron icon for visual expansion indicator
143
+ * - Focus visible states for keyboard navigation on web
144
+ * - Disabled state prevents interaction
145
+ */
146
+ function AccordionTrigger({
147
+ className,
148
+ children,
149
+ ...props
150
+ }: AccordionPrimitive.TriggerProps & {
151
+ children?: React.ReactNode;
152
+ } & React.RefAttributes<AccordionPrimitive.TriggerRef>) {
153
+ const { isExpanded } = AccordionPrimitive.useItemContext();
154
+
155
+ const progress = useDerivedValue(
156
+ () =>
157
+ isExpanded
158
+ ? withTiming(1, { duration: 250 })
159
+ : withTiming(0, { duration: 200 }),
160
+ [isExpanded]
161
+ );
162
+ const chevronStyle = useAnimatedStyle(
163
+ () => ({
164
+ transform: [{ rotate: `${progress.value * 180}deg` }],
165
+ }),
166
+ [progress]
167
+ );
168
+
169
+ return (
170
+ <TextClassContext.Provider
171
+ value={cn(
172
+ 'text-foreground text-left text-sm font-medium',
173
+ Platform.select({ web: 'group-hover:underline' })
174
+ )}
175
+ >
176
+ <AccordionPrimitive.Header>
177
+ <AccordionPrimitive.Trigger {...props} asChild>
178
+ <Trigger
179
+ className={cn(
180
+ 'flex-row items-start justify-between gap-4 rounded-md py-4 disabled:opacity-50',
181
+ Platform.select({
182
+ web: 'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 outline-none transition-all hover:underline focus-visible:ring-[3px] disabled:pointer-events-none [&[data-state=open]>svg]:rotate-180',
183
+ }),
184
+ className
185
+ )}
186
+ >
187
+ <>{children}</>
188
+ <Animated.View style={chevronStyle}>
189
+ <Icon
190
+ as={ChevronDown}
191
+ size={16}
192
+ className={cn(
193
+ 'text-muted-foreground shrink-0',
194
+ Platform.select({
195
+ web: 'pointer-events-none translate-y-0.5 transition-transform duration-200',
196
+ })
197
+ )}
198
+ />
199
+ </Animated.View>
200
+ </Trigger>
201
+ </AccordionPrimitive.Trigger>
202
+ </AccordionPrimitive.Header>
203
+ </TextClassContext.Provider>
204
+ );
205
+ }
206
+
207
+ /**
208
+ * Collapsible content container with smooth animations
209
+ *
210
+ * Automatically animates height changes when expanding/collapsing. Uses platform-specific
211
+ * animations (CSS transitions on web, Reanimated on native).
212
+ *
213
+ * @component
214
+ * @example
215
+ * ```tsx
216
+ * <AccordionContent>
217
+ * <Text>This content slides in and out smoothly</Text>
218
+ * <View className="mt-2">
219
+ * <Text>Additional content here</Text>
220
+ * </View>
221
+ * </AccordionContent>
222
+ * ```
223
+ */
224
+ function AccordionContent({
225
+ className,
226
+ children,
227
+ ...props
228
+ }: AccordionPrimitive.ContentProps &
229
+ React.RefAttributes<AccordionPrimitive.ContentRef>) {
230
+ const { isExpanded } = AccordionPrimitive.useItemContext();
231
+ return (
232
+ <TextClassContext.Provider value="text-foreground text-sm">
233
+ <AccordionPrimitive.Content
234
+ className={cn(
235
+ 'overflow-hidden',
236
+ Platform.select({
237
+ web: isExpanded ? 'animate-accordion-down' : 'animate-accordion-up',
238
+ })
239
+ )}
240
+ {...props}
241
+ >
242
+ <Animated.View
243
+ exiting={Platform.select({ native: FadeOutUp.duration(200) })}
244
+ className={cn('pb-4', className)}
245
+ >
246
+ {children}
247
+ </Animated.View>
248
+ </AccordionPrimitive.Content>
249
+ </TextClassContext.Provider>
250
+ );
251
+ }
252
+
253
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };