cnnative-ui 1.0.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 (175) hide show
  1. package/README.md +34 -0
  2. package/babel.config.js +6 -0
  3. package/jest.config.js +22 -0
  4. package/jest.init.js +5 -0
  5. package/jest.setup.js +173 -0
  6. package/package.json +87 -0
  7. package/src/__tests__/a11y/accessibility.test.tsx +33 -0
  8. package/src/__tests__/components/badge.test.tsx +25 -0
  9. package/src/__tests__/components/button.test.tsx +53 -0
  10. package/src/__tests__/components/card.test.tsx +28 -0
  11. package/src/__tests__/components/input.test.tsx +33 -0
  12. package/src/__tests__/hooks/use-controllable.test.ts +58 -0
  13. package/src/__tests__/integration.test.tsx +35 -0
  14. package/src/__tests__/lib/utils.test.ts +23 -0
  15. package/src/__tests__/mocks/handlers.ts +19 -0
  16. package/src/components/accordion/accordion.tsx +143 -0
  17. package/src/components/accordion/index.ts +1 -0
  18. package/src/components/alert/alert.tsx +65 -0
  19. package/src/components/alert/index.ts +1 -0
  20. package/src/components/alert-dialog/alert-dialog.tsx +145 -0
  21. package/src/components/alert-dialog/index.ts +1 -0
  22. package/src/components/aspect-ratio/aspect-ratio.tsx +18 -0
  23. package/src/components/aspect-ratio/index.ts +1 -0
  24. package/src/components/avatar/avatar.tsx +93 -0
  25. package/src/components/avatar/index.ts +1 -0
  26. package/src/components/badge/badge.tsx +64 -0
  27. package/src/components/badge/index.ts +1 -0
  28. package/src/components/breadcrumb/breadcrumb.tsx +75 -0
  29. package/src/components/breadcrumb/index.ts +1 -0
  30. package/src/components/button/button.tsx +119 -0
  31. package/src/components/button/index.ts +1 -0
  32. package/src/components/card/card.tsx +40 -0
  33. package/src/components/card/index.ts +1 -0
  34. package/src/components/checkbox/checkbox.tsx +87 -0
  35. package/src/components/checkbox/index.ts +1 -0
  36. package/src/components/collapsible/collapsible.tsx +92 -0
  37. package/src/components/collapsible/index.ts +1 -0
  38. package/src/components/context-menu/context-menu.tsx +121 -0
  39. package/src/components/context-menu/index.ts +1 -0
  40. package/src/components/dialog/dialog.tsx +124 -0
  41. package/src/components/dialog/index.ts +1 -0
  42. package/src/components/dropdown-menu/dropdown-menu.tsx +145 -0
  43. package/src/components/dropdown-menu/index.ts +1 -0
  44. package/src/components/form/form.tsx +84 -0
  45. package/src/components/form/index.ts +1 -0
  46. package/src/components/input/index.ts +1 -0
  47. package/src/components/input/input.tsx +115 -0
  48. package/src/components/label/index.ts +1 -0
  49. package/src/components/label/label.tsx +13 -0
  50. package/src/components/navigation-menu/index.ts +1 -0
  51. package/src/components/navigation-menu/navigation-menu.tsx +68 -0
  52. package/src/components/pagination/index.ts +1 -0
  53. package/src/components/pagination/pagination.tsx +70 -0
  54. package/src/components/progress/index.ts +1 -0
  55. package/src/components/progress/progress.tsx +66 -0
  56. package/src/components/radio-group/index.ts +1 -0
  57. package/src/components/radio-group/radio-group.tsx +90 -0
  58. package/src/components/scroll-area/index.ts +1 -0
  59. package/src/components/scroll-area/scroll-area.tsx +27 -0
  60. package/src/components/select/index.ts +1 -0
  61. package/src/components/select/select.tsx +154 -0
  62. package/src/components/separator/index.ts +1 -0
  63. package/src/components/separator/separator.tsx +37 -0
  64. package/src/components/sheet/index.ts +1 -0
  65. package/src/components/sheet/sheet.tsx +128 -0
  66. package/src/components/skeleton/index.ts +1 -0
  67. package/src/components/skeleton/skeleton.tsx +84 -0
  68. package/src/components/slider/index.ts +1 -0
  69. package/src/components/slider/slider.tsx +145 -0
  70. package/src/components/switch/index.ts +1 -0
  71. package/src/components/switch/switch.tsx +78 -0
  72. package/src/components/table/index.ts +1 -0
  73. package/src/components/table/table.tsx +71 -0
  74. package/src/components/tabs/index.ts +1 -0
  75. package/src/components/tabs/tabs.tsx +124 -0
  76. package/src/components/textarea/index.ts +1 -0
  77. package/src/components/textarea/textarea.tsx +83 -0
  78. package/src/components/toast/index.ts +1 -0
  79. package/src/components/toast/toast.tsx +124 -0
  80. package/src/components/toggle/index.ts +1 -0
  81. package/src/components/toggle/toggle.tsx +87 -0
  82. package/src/components/toggle-group/index.ts +1 -0
  83. package/src/components/toggle-group/toggle-group.tsx +87 -0
  84. package/src/components/tooltip/index.ts +1 -0
  85. package/src/components/tooltip/tooltip.tsx +103 -0
  86. package/src/components/typography/index.ts +1 -0
  87. package/src/components/typography/typography.tsx +57 -0
  88. package/src/context/index.ts +3 -0
  89. package/src/context/provider.tsx +35 -0
  90. package/src/context/theme-context.tsx +81 -0
  91. package/src/context/toast-context.tsx +63 -0
  92. package/src/env.d.ts +2 -0
  93. package/src/hooks/index.ts +15 -0
  94. package/src/hooks/use-biometric.ts +27 -0
  95. package/src/hooks/use-color-scheme.ts +10 -0
  96. package/src/hooks/use-controllable.ts +40 -0
  97. package/src/hooks/use-countdown.ts +33 -0
  98. package/src/hooks/use-debounce.ts +18 -0
  99. package/src/hooks/use-disclosure.ts +14 -0
  100. package/src/hooks/use-haptics.ts +47 -0
  101. package/src/hooks/use-keyboard.ts +35 -0
  102. package/src/hooks/use-media-query.ts +27 -0
  103. package/src/hooks/use-press-animation.ts +45 -0
  104. package/src/hooks/use-previous.ts +14 -0
  105. package/src/hooks/use-scroll-header.ts +42 -0
  106. package/src/hooks/use-spring.ts +18 -0
  107. package/src/hooks/use-theme.ts +6 -0
  108. package/src/hooks/use-toast.ts +6 -0
  109. package/src/index.ts +53 -0
  110. package/src/lib/create-animated.tsx +25 -0
  111. package/src/lib/create-component.tsx +56 -0
  112. package/src/lib/index.ts +4 -0
  113. package/src/lib/platform.ts +25 -0
  114. package/src/lib/types.ts +28 -0
  115. package/src/lib/utils.ts +35 -0
  116. package/src/lib/variants.ts +7 -0
  117. package/src/premium/ai/chat-bubble.tsx +58 -0
  118. package/src/premium/ai/typing-indicator.tsx +59 -0
  119. package/src/premium/charts/bar-chart.tsx +66 -0
  120. package/src/premium/charts/progress-ring.tsx +63 -0
  121. package/src/premium/glass/glass-bottom-sheet.tsx +50 -0
  122. package/src/premium/glass/glass-card.tsx +51 -0
  123. package/src/premium/glass/glass-header.tsx +61 -0
  124. package/src/premium/glass/glass-panel.tsx +32 -0
  125. package/src/premium/glass/glass-sidebar.tsx +56 -0
  126. package/src/premium/index.ts +44 -0
  127. package/src/premium/index2.ts +13 -0
  128. package/src/premium/index3.ts +1 -0
  129. package/src/premium/inputs/color-picker.tsx +92 -0
  130. package/src/premium/inputs/currency-input.tsx +50 -0
  131. package/src/premium/inputs/otp-input.tsx +92 -0
  132. package/src/premium/inputs/phone-input.tsx +58 -0
  133. package/src/premium/inputs/rating.tsx +51 -0
  134. package/src/premium/layout/carousel.tsx +57 -0
  135. package/src/premium/layout/floating-dock.tsx +63 -0
  136. package/src/premium/layout/masonry-grid.tsx +41 -0
  137. package/src/premium/layout/parallax-scroll.tsx +81 -0
  138. package/src/premium/magic/animated-number.tsx +104 -0
  139. package/src/premium/magic/bento-grid.tsx +55 -0
  140. package/src/premium/magic/border-beam.tsx +68 -0
  141. package/src/premium/magic/confetti.tsx +88 -0
  142. package/src/premium/magic/magic-card.tsx +65 -0
  143. package/src/premium/magic/meteors.tsx +95 -0
  144. package/src/premium/magic/ripple.tsx +70 -0
  145. package/src/premium/magic/shimmer.tsx +58 -0
  146. package/src/premium/magic/shiny-button.tsx +70 -0
  147. package/src/premium/mobile/biometric-button.tsx +82 -0
  148. package/src/premium/mobile/bottom-tab-bar.tsx +81 -0
  149. package/src/premium/mobile/fab.tsx +74 -0
  150. package/src/premium/mobile/haptic-pressable.tsx +53 -0
  151. package/src/premium/mobile/notification-badge.tsx +61 -0
  152. package/src/premium/mobile/pull-to-refresh.tsx +84 -0
  153. package/src/premium/mobile/scroll-header.tsx +57 -0
  154. package/src/premium/mobile/swipe-row.tsx +128 -0
  155. package/src/premium/mobile/swipeable-card-stack.tsx +121 -0
  156. package/src/premium/motion/blur-fade.tsx +51 -0
  157. package/src/premium/motion/fade-up.tsx +34 -0
  158. package/src/premium/motion/marquee.tsx +67 -0
  159. package/src/premium/motion/pulsating-button.tsx +95 -0
  160. package/src/premium/motion/slide-in.tsx +38 -0
  161. package/src/premium/motion/stagger-children.tsx +28 -0
  162. package/src/premium/motion/typing-text.tsx +55 -0
  163. package/src/premium/motion/word-pull-up.tsx +34 -0
  164. package/src/premium/onboarding/step-indicator.tsx +65 -0
  165. package/src/tokens/colors.ts +83 -0
  166. package/src/tokens/global.css +83 -0
  167. package/src/tokens/index.ts +10 -0
  168. package/src/tokens/layout.ts +121 -0
  169. package/src/tokens/motion.ts +94 -0
  170. package/src/tokens/themes/dark.ts +7 -0
  171. package/src/tokens/themes/default.ts +8 -0
  172. package/src/tokens/themes/ocean.ts +28 -0
  173. package/src/tokens/themes/rose.ts +29 -0
  174. package/src/tokens/typography.ts +127 -0
  175. package/tsconfig.json +15 -0
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # cnnative-ui
2
+
3
+ The core component library source code for the **cnnative** ecosystem. Beautiful, accessible, and high-performance React Native and Expo component primitives styled with NativeWind.
4
+
5
+ This library is designed around the **copy-paste component model** popularized by shadcn/ui. You own the code of your components—it is written directly into your application directories rather than hidden deep inside `node_modules`.
6
+
7
+ ## Component Features
8
+
9
+ * **Premium Micro-Animations**: Built-in support for fluid spring transitions using React Native Reanimated and Moti.
10
+ * **Vibrant Glassmorphism**: Stunning pre-configured frosted glass cards, panel layouts, and sheets.
11
+ * **Tailwind Styling**: Out-of-the-box support for NativeWind styling variables and Tailwind utility configurations.
12
+ * **Tactile Haptics**: Native-feeling haptic feedback integrated directly into interactive primitives (buttons, switches, dials).
13
+
14
+ ## Peer Dependencies
15
+
16
+ To use components added by the `cnnative` CLI in your project, ensure the following peer dependencies are installed:
17
+
18
+ ```bash
19
+ # Using Expo
20
+ npx expo install react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-svg expo-haptics expo-blur expo-linear-gradient expo-local-authentication
21
+
22
+ # Standard utility libraries
23
+ npm install clsx tailwind-merge class-variance-authority lucide-react-native moti
24
+ ```
25
+
26
+ ## How It Works
27
+
28
+ 1. Initialize your workspace: `npx cnnative init`
29
+ 2. Copy components dynamically: `npx cnnative add button`
30
+ 3. Customize the component code directly within your `components/ui/` folder!
31
+
32
+ ## License
33
+
34
+ MIT
@@ -0,0 +1,6 @@
1
+ module.exports = function (api) {
2
+ api.cache(true);
3
+ return {
4
+ presets: ['babel-preset-expo'],
5
+ };
6
+ };
package/jest.config.js ADDED
@@ -0,0 +1,22 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = {
3
+ preset: '@react-native/jest-preset',
4
+ setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
5
+ moduleDirectories: ['node_modules', '<rootDir>/node_modules', '<rootDir>/../../node_modules'],
6
+ testMatch: ['**/__tests__/**/*.test.[jt]s?(x)'],
7
+ transformIgnorePatterns: [
8
+ 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|lucide-react-native|react-native-reanimated|react-native-gesture-handler|moti)'
9
+ ],
10
+ testPathIgnorePatterns: ['/node_modules/', '/dist/'],
11
+ moduleNameMapper: {
12
+ '^@/(.*)$': '<rootDir>/src/$1'
13
+ },
14
+ collectCoverage: true,
15
+ coverageReporters: ['json', 'html', 'text'],
16
+ collectCoverageFrom: [
17
+ 'src/**/*.{ts,tsx}',
18
+ '!src/**/*.d.ts',
19
+ '!src/**/index.ts',
20
+ '!src/tokens/**'
21
+ ]
22
+ };
package/jest.init.js ADDED
@@ -0,0 +1,5 @@
1
+ // Initialize global mocks before jest-expo setup runs
2
+ const NativeModules = require('react-native/Libraries/BatchedBridge/NativeModules');
3
+ if (!NativeModules.UIManager) {
4
+ NativeModules.UIManager = {};
5
+ }
package/jest.setup.js ADDED
@@ -0,0 +1,173 @@
1
+ import '@testing-library/jest-native/extend-expect';
2
+
3
+ // Mock react-native-gesture-handler
4
+ jest.mock('react-native-gesture-handler', () => {
5
+ const React = require('react');
6
+ const { View } = require('react-native');
7
+ return {
8
+ __esModule: true,
9
+ GestureHandlerRootView: ({ children, style, ...props }) => React.createElement(View, { ...props, style }, children),
10
+ Swipeable: ({ children }) => children,
11
+ DrawerLayout: ({ children }) => children,
12
+ State: {},
13
+ PanGestureHandler: ({ children }) => children,
14
+ TapGestureHandler: ({ children }) => children,
15
+ FlingGestureHandler: ({ children }) => children,
16
+ ForceTouchGestureHandler: ({ children }) => children,
17
+ LongPressGestureHandler: ({ children }) => children,
18
+ PinchGestureHandler: ({ children }) => children,
19
+ RotationGestureHandler: ({ children }) => children,
20
+ };
21
+ });
22
+
23
+ // Mock react-native-reanimated
24
+ jest.mock('react-native-reanimated', () => {
25
+ const react = require('react');
26
+ const { View, Text } = require('react-native');
27
+ return {
28
+ __esModule: true,
29
+ default: {
30
+ createAnimatedComponent: (c) => c,
31
+ View: ({ children, style, ...props }) => react.createElement(View, { ...props, style }, children),
32
+ Text: ({ children, style, ...props }) => react.createElement(Text, { ...props, style }, children),
33
+ },
34
+ useAnimatedStyle: (fn) => fn(),
35
+ useSharedValue: (val) => ({ value: val }),
36
+ withRepeat: (val) => val,
37
+ withTiming: (val, config, cb) => {
38
+ if (cb) cb(true);
39
+ return val;
40
+ },
41
+ withSpring: (val, config, cb) => {
42
+ if (cb) cb(true);
43
+ return val;
44
+ },
45
+ withDelay: (delay, animation) => animation,
46
+ Easing: {
47
+ linear: (t) => t,
48
+ ease: (t) => t,
49
+ quad: (t) => t,
50
+ cubic: (t) => t,
51
+ poly: (n) => (t) => Math.pow(t, n),
52
+ sin: (t) => t,
53
+ circle: (t) => t,
54
+ exp: (t) => t,
55
+ elastic: (bounciness) => (t) => t,
56
+ back: (s) => (t) => t,
57
+ bounce: (t) => t,
58
+ bezier: (x1, y1, x2, y2) => (t) => t,
59
+ in: (easing) => easing,
60
+ out: (easing) => easing,
61
+ inOut: (easing) => easing,
62
+ },
63
+ interpolate: (value, inputRange, outputRange, extrapolate) => {
64
+ return outputRange[0];
65
+ },
66
+ Extrapolate: {
67
+ CLAMP: 'clamp',
68
+ IDENTITY: 'identity',
69
+ EXTEND: 'extend',
70
+ },
71
+ interpolateColor: (value, inputRange, outputRange) => {
72
+ return outputRange[0];
73
+ },
74
+ runOnJS: (fn) => fn,
75
+ runOnUI: (fn) => fn,
76
+ useAnimatedRef: () => react.createRef(),
77
+ measure: () => ({ x: 0, y: 0, width: 100, height: 100, pageX: 0, pageY: 0 }),
78
+ createAnimatedComponent: (c) => c,
79
+ useReducedMotion: () => false,
80
+ };
81
+ });
82
+
83
+ // Mock expo-haptics
84
+ jest.mock('expo-haptics', () => ({
85
+ impactAsync: jest.fn(),
86
+ notificationAsync: jest.fn(),
87
+ ImpactFeedbackStyle: {
88
+ Light: 'light',
89
+ Medium: 'medium',
90
+ Heavy: 'heavy'
91
+ },
92
+ NotificationFeedbackType: {
93
+ Success: 'success',
94
+ Warning: 'warning',
95
+ Error: 'error'
96
+ }
97
+ }));
98
+
99
+ // Mock lucide-react-native
100
+ jest.mock('lucide-react-native', () => {
101
+ const React = require('react');
102
+ const { View } = require('react-native');
103
+ const createMockIcon = (name) => {
104
+ const Component = (props) => React.createElement(View, { ...props, testID: `lucide-${name}` });
105
+ Component.displayName = `MockIcon(${name})`;
106
+ return Component;
107
+ };
108
+ return {
109
+ __esModule: true,
110
+ Check: createMockIcon('Check'),
111
+ RefreshCw: createMockIcon('RefreshCw'),
112
+ Fingerprint: createMockIcon('Fingerprint'),
113
+ ScanFace: createMockIcon('ScanFace'),
114
+ Star: createMockIcon('Star'),
115
+ ChevronDown: createMockIcon('ChevronDown'),
116
+ X: createMockIcon('X'),
117
+ ChevronLeft: createMockIcon('ChevronLeft'),
118
+ ChevronRight: createMockIcon('ChevronRight'),
119
+ MoreHorizontal: createMockIcon('MoreHorizontal'),
120
+ };
121
+ });
122
+
123
+ // Mock expo-blur
124
+ jest.mock('expo-blur', () => {
125
+ const React = require('react');
126
+ const { View } = require('react-native');
127
+ return {
128
+ BlurView: (props) => React.createElement(View, { ...props, testID: 'expo-blur-view' })
129
+ };
130
+ });
131
+
132
+ // Mock react-native-safe-area-context
133
+ jest.mock('react-native-safe-area-context', () => {
134
+ const React = require('react');
135
+ const { View } = require('react-native');
136
+ return {
137
+ SafeAreaProvider: ({ children }) => React.createElement(View, {}, children),
138
+ SafeAreaView: ({ children }) => React.createElement(View, {}, children),
139
+ useSafeAreaInsets: () => ({ top: 0, right: 0, bottom: 0, left: 0 }),
140
+ };
141
+ });
142
+
143
+ // Mock theme-context
144
+ jest.mock('./src/context/theme-context', () => {
145
+ return {
146
+ __esModule: true,
147
+ ThemeProvider: ({ children }) => children,
148
+ useThemeContext: () => ({
149
+ colorScheme: 'light',
150
+ setColorScheme: jest.fn(),
151
+ isDark: false,
152
+ themeName: 'default',
153
+ setThemeName: jest.fn(),
154
+ theme: {
155
+ background: 'hsl(0 0% 100%)',
156
+ foreground: 'hsl(222.2 84% 4.9%)',
157
+ card: 'hsl(0 0% 100%)',
158
+ cardForeground: 'hsl(222.2 84% 4.9%)',
159
+ popover: 'hsl(0 0% 100%)',
160
+ popoverForeground: 'hsl(222.2 84% 4.9%)',
161
+ primary: { DEFAULT: 'hsl(222.2 47.4% 11.2%)', foreground: 'hsl(210 40% 98%)' },
162
+ secondary: { DEFAULT: 'hsl(210 40% 96.1%)', foreground: 'hsl(222.2 47.4% 11.2%)' },
163
+ muted: { DEFAULT: 'hsl(210 40% 96.1%)', foreground: 'hsl(215.4 16.3% 46.9%)' },
164
+ accent: { DEFAULT: 'hsl(210 40% 96.1%)', foreground: 'hsl(222.2 47.4% 11.2%)' },
165
+ destructive: { DEFAULT: 'hsl(0 84.2% 60.2%)', foreground: 'hsl(210 40% 98%)' },
166
+ border: 'hsl(214.3 31.8% 91.4%)',
167
+ input: 'hsl(214.3 31.8% 91.4%)',
168
+ ring: 'hsl(222.2 84% 4.9%)',
169
+ shimmer: '#E2E8F0',
170
+ },
171
+ }),
172
+ };
173
+ });
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "cnnative-ui",
3
+ "version": "1.0.0",
4
+ "description": "A shadcn/ui + Radix-inspired React Native component library that surpasses both",
5
+ "keywords": [
6
+ "react-native",
7
+ "expo",
8
+ "ui",
9
+ "components",
10
+ "shadcn",
11
+ "nativecn"
12
+ ],
13
+ "license": "MIT",
14
+ "main": "./src/index.ts",
15
+ "types": "./src/index.ts",
16
+ "exports": {
17
+ ".": "./src/index.ts",
18
+ "./tokens": "./src/tokens/index.ts",
19
+ "./lib": "./src/lib/index.ts",
20
+ "./hooks": "./src/hooks/index.ts",
21
+ "./components/*": "./src/components/*/index.ts",
22
+ "./premium/*": "./src/premium/*/index.ts"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc --noEmit",
26
+ "typecheck": "tsc --noEmit",
27
+ "lint": "eslint src --ext .ts,.tsx",
28
+ "test": "jest",
29
+ "test:coverage": "jest --coverage",
30
+ "test:watch": "jest --watch",
31
+ "clean": "rimraf dist coverage"
32
+ },
33
+ "peerDependencies": {
34
+ "expo": ">=52.0.0",
35
+ "nativewind": ">=5.0.0",
36
+ "react": ">=18.0.0",
37
+ "react-native": ">=0.76.0",
38
+ "react-native-gesture-handler": ">=2.0.0",
39
+ "react-native-reanimated": ">=3.0.0",
40
+ "react-native-safe-area-context": ">=4.0.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "expo": {
44
+ "optional": true
45
+ },
46
+ "react-native-gesture-handler": {
47
+ "optional": false
48
+ }
49
+ },
50
+ "dependencies": {
51
+ "class-variance-authority": "^0.7.1",
52
+ "clsx": "^2.1.1",
53
+ "lucide-react-native": "^0.469.0",
54
+ "moti": "^0.29.0",
55
+ "tailwind-merge": "^2.6.0"
56
+ },
57
+ "devDependencies": {
58
+ "@react-native/jest-preset": "^0.85.3",
59
+ "@testing-library/jest-native": "^5.4.3",
60
+ "@testing-library/react-native": "^13.2.0",
61
+ "@testing-library/user-event": "^14.5.2",
62
+ "@types/jest": "^30.0.0",
63
+ "@types/react": "^19.0.8",
64
+ "@types/react-native": "^0.73.0",
65
+ "babel-jest": "^29.7.0",
66
+ "babel-preset-expo": "^56.0.14",
67
+ "expo-blur": "^56.0.3",
68
+ "expo-haptics": "^56.0.3",
69
+ "expo-linear-gradient": "^56.0.4",
70
+ "expo-local-authentication": "^56.0.4",
71
+ "jest": "^29.7.0",
72
+ "jest-expo": "^52.0.0",
73
+ "jest-html-reporters": "^3.1.7",
74
+ "memfs": "^4.14.1",
75
+ "nativewind": "^4.2.4",
76
+ "react": "19.2.6",
77
+ "react-native": "^0.85.3",
78
+ "react-native-gesture-handler": "^3.0.0",
79
+ "react-native-reanimated": "^4.4.0",
80
+ "react-native-safe-area-context": "^5.8.0",
81
+ "react-native-svg": "^15.15.5",
82
+ "react-test-renderer": "19.2.6",
83
+ "rimraf": "^6.0.1",
84
+ "tsd": "^0.31.2",
85
+ "typescript": "^5.7.3"
86
+ }
87
+ }
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react-native';
3
+ import { Button } from '../../components/button/button';
4
+ import { Text } from '../../components/typography';
5
+ import { Switch } from '../../components/switch/switch';
6
+ import { Checkbox } from '../../components/checkbox/checkbox';
7
+
8
+ describe('Accessibility Standards', () => {
9
+ it('Button should be accessible as a button role', () => {
10
+ const { getByRole } = render(
11
+ <Button accessibilityRole="button">
12
+ <Text>Accessible Button</Text>
13
+ </Button>
14
+ );
15
+ expect(getByRole('button')).toBeTruthy();
16
+ });
17
+
18
+ it('Switch should reflect checked accessibility state', () => {
19
+ const { getByRole } = render(
20
+ <Switch checked={true} accessibilityRole="switch" accessibilityState={{ checked: true }} />
21
+ );
22
+ const switchElement = getByRole('switch');
23
+ expect(switchElement.props.accessibilityState.checked).toBe(true);
24
+ });
25
+
26
+ it('Checkbox should reflect checked accessibility state', () => {
27
+ const { getByRole } = render(
28
+ <Checkbox checked={true} accessibilityRole="checkbox" accessibilityState={{ checked: true }} />
29
+ );
30
+ const checkboxElement = getByRole('checkbox');
31
+ expect(checkboxElement.props.accessibilityState.checked).toBe(true);
32
+ });
33
+ });
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react-native';
3
+ import { Badge } from '../../components/badge/badge';
4
+
5
+ describe('Badge Component', () => {
6
+ it('renders correctly with default variant', () => {
7
+ const { getByText } = render(<Badge>Default Badge</Badge>);
8
+ expect(getByText('Default Badge')).toBeTruthy();
9
+ });
10
+
11
+ it('renders correctly with outline variant', () => {
12
+ const { getByText, getByTestId } = render(
13
+ <Badge variant="outline" testID="badge-outline">
14
+ Outline Badge
15
+ </Badge>
16
+ );
17
+ expect(getByText('Outline Badge')).toBeTruthy();
18
+ expect(getByTestId('badge-outline')).toBeTruthy();
19
+ });
20
+
21
+ it('renders correctly with destructive variant', () => {
22
+ const { getByText } = render(<Badge variant="destructive">Destructive Badge</Badge>);
23
+ expect(getByText('Destructive Badge')).toBeTruthy();
24
+ });
25
+ });
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react-native';
3
+ import { Button } from '../../components/button/button';
4
+ import { Text } from '../../components/typography';
5
+
6
+ // We wrap the tests with a basic setup
7
+ describe('Button Component', () => {
8
+ it('renders correctly with children', () => {
9
+ const { getByText } = render(
10
+ <Button>
11
+ <Text>Click Me</Text>
12
+ </Button>
13
+ );
14
+ expect(getByText('Click Me')).toBeTruthy();
15
+ });
16
+
17
+ it('calls onPress when pressed', () => {
18
+ const onPressMock = jest.fn();
19
+ const { getByText } = render(
20
+ <Button onPress={onPressMock}>
21
+ <Text>Pressable</Text>
22
+ </Button>
23
+ );
24
+
25
+ fireEvent.press(getByText('Pressable'));
26
+ expect(onPressMock).toHaveBeenCalledTimes(1);
27
+ });
28
+
29
+ it('does not call onPress when disabled', () => {
30
+ const onPressMock = jest.fn();
31
+ const { getByText } = render(
32
+ <Button onPress={onPressMock} disabled>
33
+ <Text>Disabled</Text>
34
+ </Button>
35
+ );
36
+
37
+ fireEvent.press(getByText('Disabled'));
38
+ expect(onPressMock).not.toHaveBeenCalled();
39
+ });
40
+
41
+ it('applies variant classes correctly', () => {
42
+ const { getByTestId } = render(
43
+ <Button variant="destructive" testID="my-button">
44
+ <Text>Destructive</Text>
45
+ </Button>
46
+ );
47
+
48
+ // We expect the native element to have the destruct style strings in its className or style
49
+ // In React Native testing with nativewind, checking exact styles is complex without a full provider,
50
+ // but we can check if it renders without throwing.
51
+ expect(getByTestId('my-button')).toBeTruthy();
52
+ });
53
+ });
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react-native';
3
+ import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../../components/card/card';
4
+ import { Text } from '../../components/typography';
5
+
6
+ describe('Card Component', () => {
7
+ it('renders all card subcomponents correctly', () => {
8
+ const { getByText } = render(
9
+ <Card>
10
+ <CardHeader>
11
+ <CardTitle>Card Title</CardTitle>
12
+ <CardDescription>Card Description</CardDescription>
13
+ </CardHeader>
14
+ <CardContent>
15
+ <Text>Card Content</Text>
16
+ </CardContent>
17
+ <CardFooter>
18
+ <Text>Card Footer</Text>
19
+ </CardFooter>
20
+ </Card>
21
+ );
22
+
23
+ expect(getByText('Card Title')).toBeTruthy();
24
+ expect(getByText('Card Description')).toBeTruthy();
25
+ expect(getByText('Card Content')).toBeTruthy();
26
+ expect(getByText('Card Footer')).toBeTruthy();
27
+ });
28
+ });
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react-native';
3
+ import { Input } from '../../components/input/input';
4
+
5
+ describe('Input Component', () => {
6
+ it('renders correctly', () => {
7
+ const { getByPlaceholderText } = render(
8
+ <Input placeholder="Enter email" />
9
+ );
10
+ expect(getByPlaceholderText('Enter email')).toBeTruthy();
11
+ });
12
+
13
+ it('handles text changes', () => {
14
+ const onChangeTextMock = jest.fn();
15
+ const { getByPlaceholderText } = render(
16
+ <Input placeholder="Type here" onChangeText={onChangeTextMock} />
17
+ );
18
+
19
+ const input = getByPlaceholderText('Type here');
20
+ fireEvent.changeText(input, 'Hello Nativecn');
21
+
22
+ expect(onChangeTextMock).toHaveBeenCalledWith('Hello Nativecn');
23
+ });
24
+
25
+ it('passes generic props correctly', () => {
26
+ const { getByDisplayValue } = render(
27
+ <Input value="test value" secureTextEntry={true} />
28
+ );
29
+
30
+ const input = getByDisplayValue('test value');
31
+ expect(input.props.secureTextEntry).toBe(true);
32
+ });
33
+ });
@@ -0,0 +1,58 @@
1
+ import { renderHook, act } from '@testing-library/react-native';
2
+ import { useControllableState } from '../../hooks/use-controllable';
3
+
4
+ describe('useControllableState', () => {
5
+ it('should use defaultProp when no prop is provided', () => {
6
+ const { result } = renderHook(() =>
7
+ useControllableState({ defaultProp: 'default' })
8
+ );
9
+
10
+ expect(result.current[0]).toBe('default');
11
+ });
12
+
13
+ it('should use prop when provided', () => {
14
+ const { result } = renderHook(() =>
15
+ useControllableState({ prop: 'controlled', defaultProp: 'default' })
16
+ );
17
+
18
+ expect(result.current[0]).toBe('controlled');
19
+ });
20
+
21
+ it('should update state internally when uncontrolled', () => {
22
+ const { result } = renderHook(() =>
23
+ useControllableState({ defaultProp: 'default' })
24
+ );
25
+
26
+ act(() => {
27
+ result.current[1]('new value');
28
+ });
29
+
30
+ expect(result.current[0]).toBe('new value');
31
+ });
32
+
33
+ it('should NOT update state internally when controlled', () => {
34
+ const { result } = renderHook(() =>
35
+ useControllableState({ prop: 'controlled', defaultProp: 'default' })
36
+ );
37
+
38
+ act(() => {
39
+ result.current[1]('new value');
40
+ });
41
+
42
+ // Still 'controlled' because the parent didn't change the prop
43
+ expect(result.current[0]).toBe('controlled');
44
+ });
45
+
46
+ it('should call onChange with new value', () => {
47
+ const onChange = jest.fn();
48
+ const { result } = renderHook(() =>
49
+ useControllableState({ defaultProp: 'default', onChange })
50
+ );
51
+
52
+ act(() => {
53
+ result.current[1]('new value');
54
+ });
55
+
56
+ expect(onChange).toHaveBeenCalledWith('new value');
57
+ });
58
+ });
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react-native';
3
+ import { NativecnProvider } from '../context/provider';
4
+ import { Button } from '../components/button/button';
5
+ import { Text } from '../components/typography';
6
+ import { ToastProvider, useToastContext } from '../context/toast-context';
7
+
8
+ const TestComponent = () => {
9
+ const { toast } = useToastContext();
10
+
11
+ return (
12
+ <Button onPress={() => toast({ title: 'Success', description: 'Integration works' })}>
13
+ <Text>Trigger Toast</Text>
14
+ </Button>
15
+ );
16
+ };
17
+
18
+ describe('Integration Tests', () => {
19
+ it('should render components within the global provider and trigger context actions', () => {
20
+ // In React Native Testing Library, rendering the provider tree evaluates integration
21
+ const { getByText } = render(
22
+ <NativecnProvider>
23
+ <TestComponent />
24
+ </NativecnProvider>
25
+ );
26
+
27
+ const btn = getByText('Trigger Toast');
28
+ expect(btn).toBeTruthy();
29
+
30
+ // Fire the button, which triggers the toast context.
31
+ // The visual toast won't appear instantaneously in the same tick without advanceTimers,
32
+ // but the context function will be executed without throwing.
33
+ fireEvent.press(btn);
34
+ });
35
+ });
@@ -0,0 +1,23 @@
1
+ import { cn } from '../../lib/utils';
2
+
3
+ describe('utils', () => {
4
+ describe('cn', () => {
5
+ it('should merge class names correctly', () => {
6
+ expect(cn('bg-red-500', 'text-white')).toBe('bg-red-500 text-white');
7
+ });
8
+
9
+ it('should handle conditional classes', () => {
10
+ expect(cn('bg-red-500', true && 'text-white', false && 'p-4')).toBe('bg-red-500 text-white');
11
+ });
12
+
13
+ it('should resolve tailwind conflicts using tailwind-merge', () => {
14
+ // tailwind-merge resolves conflicts by keeping the last class
15
+ expect(cn('bg-red-500 bg-blue-500')).toBe('bg-blue-500');
16
+ expect(cn('p-2 p-4')).toBe('p-4');
17
+ });
18
+
19
+ it('should handle arrays of classes', () => {
20
+ expect(cn(['bg-red-500', 'text-white'])).toBe('bg-red-500 text-white');
21
+ });
22
+ });
23
+ });
@@ -0,0 +1,19 @@
1
+ // Mock definitions for integration testing environments
2
+ export const mockHaptics = {
3
+ impactAsync: jest.fn(),
4
+ notificationAsync: jest.fn(),
5
+ };
6
+
7
+ export const mockReanimated = {
8
+ useSharedValue: jest.fn((init) => ({ value: init })),
9
+ useAnimatedStyle: jest.fn(() => ({})),
10
+ withSpring: jest.fn((val) => val),
11
+ withTiming: jest.fn((val) => val),
12
+ };
13
+
14
+ export const mockThemeContext = {
15
+ theme: 'light',
16
+ isDark: false,
17
+ setColorScheme: jest.fn(),
18
+ toggleTheme: jest.fn(),
19
+ };