@bbearai/react-native 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.
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # @bugbearai/react-native
2
+
3
+ BugBear QA widget for React Native mobile apps.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @bugbearai/react-native @bugbearai/core
9
+ # or
10
+ yarn add @bugbearai/react-native @bugbearai/core
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```tsx
16
+ import { BugBearProvider, BugBearWidget } from '@bugbearai/react-native';
17
+
18
+ function App() {
19
+ return (
20
+ <BugBearProvider
21
+ projectId="your-project-id"
22
+ getCurrentUser={async () => ({
23
+ id: user.id,
24
+ email: user.email,
25
+ })}
26
+ >
27
+ <YourApp />
28
+ <BugBearWidget />
29
+ </BugBearProvider>
30
+ );
31
+ }
32
+ ```
33
+
34
+ ## Monorepo Setup
35
+
36
+ When using BugBear in a monorepo (e.g., with Turborepo, Nx, or Yarn Workspaces), you may encounter React duplicate instance errors like:
37
+
38
+ - "Invalid hook call"
39
+ - "Cannot read property 'useState' of null"
40
+ - "Hooks can only be called inside the body of a function component"
41
+
42
+ This happens when Metro (React Native's bundler) picks up different React instances from different `node_modules` folders.
43
+
44
+ ### Fix: Configure Metro to use a single React instance
45
+
46
+ Update your `metro.config.js` in the React Native app:
47
+
48
+ ```javascript
49
+ const path = require('path');
50
+ const { getDefaultConfig } = require('@react-native/metro-config');
51
+
52
+ // Get the monorepo root (adjust path as needed)
53
+ const monorepoRoot = path.resolve(__dirname, '../..');
54
+
55
+ const config = getDefaultConfig(__dirname);
56
+
57
+ // Block React from monorepo root to prevent duplicates
58
+ config.resolver.blockList = [
59
+ ...(config.resolver.blockList || []),
60
+ new RegExp(`${monorepoRoot}/node_modules/react/.*`),
61
+ new RegExp(`${monorepoRoot}/node_modules/react-native/.*`),
62
+ ];
63
+
64
+ // Ensure Metro uses the app's React instance
65
+ config.resolver.extraNodeModules = {
66
+ react: path.resolve(__dirname, 'node_modules/react'),
67
+ 'react-native': path.resolve(__dirname, 'node_modules/react-native'),
68
+ };
69
+
70
+ // Watch the monorepo packages
71
+ config.watchFolders = [
72
+ monorepoRoot,
73
+ ];
74
+
75
+ module.exports = config;
76
+ ```
77
+
78
+ ### Alternative: Yarn/npm Workspaces hoisting
79
+
80
+ If you're using Yarn workspaces, you can also configure hoisting to ensure a single React version:
81
+
82
+ ```json
83
+ // package.json (workspace root)
84
+ {
85
+ "workspaces": {
86
+ "packages": ["apps/*", "packages/*"],
87
+ "nohoist": [
88
+ "**/react-native",
89
+ "**/react-native/**"
90
+ ]
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## Configuration Options
96
+
97
+ ```tsx
98
+ <BugBearProvider
99
+ projectId="your-project-id" // Required: Your BugBear project ID
100
+ getCurrentUser={async () => {...}} // Required: Return current user info
101
+ getNavigationHistory={() => [...]} // Optional: Custom navigation tracking
102
+ onReportSubmitted={(report) => {...}} // Optional: Callback after report submission
103
+ >
104
+ ```
105
+
106
+ ## Navigation Tracking
107
+
108
+ BugBear can automatically track navigation history for better bug context. If you're using React Navigation:
109
+
110
+ ```tsx
111
+ import { useNavigationContainerRef } from '@react-navigation/native';
112
+
113
+ function App() {
114
+ const navigationRef = useNavigationContainerRef();
115
+ const routeNameRef = useRef<string>();
116
+
117
+ return (
118
+ <BugBearProvider
119
+ projectId="your-project-id"
120
+ getCurrentUser={...}
121
+ getNavigationHistory={() => routeNameRef.current ? [routeNameRef.current] : []}
122
+ >
123
+ <NavigationContainer
124
+ ref={navigationRef}
125
+ onReady={() => {
126
+ routeNameRef.current = navigationRef.getCurrentRoute()?.name;
127
+ }}
128
+ onStateChange={() => {
129
+ const currentRouteName = navigationRef.getCurrentRoute()?.name;
130
+ routeNameRef.current = currentRouteName;
131
+ }}
132
+ >
133
+ {/* Your navigation */}
134
+ </NavigationContainer>
135
+ <BugBearWidget />
136
+ </BugBearProvider>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ## Button Configuration
142
+
143
+ The BugBear button is draggable by default with edge-snapping behavior:
144
+
145
+ ```tsx
146
+ <BugBearButton
147
+ draggable={true} // Enable/disable dragging (default: true)
148
+ position="bottom-right" // Initial position: 'bottom-right' | 'bottom-left'
149
+ initialX={100} // Custom initial X position (optional)
150
+ initialY={500} // Custom initial Y position (optional)
151
+ minY={100} // Minimum Y from top (default: 100)
152
+ maxYOffset={160} // Max Y offset from bottom (default: 160)
153
+ />
154
+ ```
155
+
156
+ The button automatically snaps to the nearest screen edge when released.
157
+
158
+ ## Troubleshooting
159
+
160
+ ### Widget not showing
161
+
162
+ 1. Make sure the current user's email is registered as a tester in the BugBear dashboard
163
+ 2. Verify QA mode is enabled for your project
164
+ 3. Check that `getCurrentUser` returns a valid email
165
+
166
+ ### Supabase connection issues
167
+
168
+ If you're using your own Supabase instance, provide the credentials:
169
+
170
+ ```tsx
171
+ <BugBearProvider
172
+ projectId="your-project-id"
173
+ supabaseUrl="https://your-instance.supabase.co"
174
+ supabaseAnonKey="your-anon-key"
175
+ getCurrentUser={...}
176
+ >
177
+ ```
178
+
179
+ ## Known Limitations
180
+
181
+ ### Button hidden behind modals
182
+
183
+ The BugBear button renders within the normal React Native view hierarchy using absolute positioning. When your app displays modals, bottom sheets, or the keyboard, the button may be hidden behind them.
184
+
185
+ This is a platform limitation - React Native doesn't provide a built-in way to render views above all modals without using a Modal component (which would block touch events on the underlying app).
186
+
187
+ **Workarounds:**
188
+ - Accept that the button won't be accessible when modals are open
189
+ - Consider dismissing the modal to access BugBear
190
+ - For testing flows that involve modals, provide an alternative way to trigger the BugBear widget
191
+
192
+ We're investigating solutions using `react-native-portal` or similar libraries for future releases.
193
+
194
+ ## License
195
+
196
+ MIT
@@ -0,0 +1,60 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { BugBearConfig, BugBearClient, TesterInfo, TestAssignment, DeviceInfo, TesterThread, TesterMessage, AppContext } from '@bbearai/core';
3
+ export { AppContext, BugBearConfig, BugBearReport, DeviceInfo, MessageSenderType, ReportType, Severity, TestAssignment, TesterInfo, TesterMessage, TesterThread, ThreadPriority, ThreadType } from '@bbearai/core';
4
+
5
+ interface BugBearContextValue {
6
+ client: BugBearClient | null;
7
+ isTester: boolean;
8
+ isQAEnabled: boolean;
9
+ shouldShowWidget: boolean;
10
+ testerInfo: TesterInfo | null;
11
+ assignments: TestAssignment[];
12
+ currentAssignment: TestAssignment | null;
13
+ refreshAssignments: () => Promise<void>;
14
+ isLoading: boolean;
15
+ getDeviceInfo: () => DeviceInfo;
16
+ /** Navigate to a route/screen (for deep linking from test cases) */
17
+ onNavigate?: (route: string) => void;
18
+ /** Message threads for the current tester */
19
+ threads: TesterThread[];
20
+ /** Total unread message count */
21
+ unreadCount: number;
22
+ /** Refresh message threads */
23
+ refreshThreads: () => Promise<void>;
24
+ /** Get all messages in a thread */
25
+ getThreadMessages: (threadId: string) => Promise<TesterMessage[]>;
26
+ /** Send a message to a thread */
27
+ sendMessage: (threadId: string, content: string) => Promise<boolean>;
28
+ /** Mark a thread as read */
29
+ markAsRead: (threadId: string) => Promise<void>;
30
+ }
31
+ declare function useBugBear(): BugBearContextValue;
32
+ interface BugBearProviderProps {
33
+ config: BugBearConfig;
34
+ children: ReactNode;
35
+ /** App version string */
36
+ appVersion?: string;
37
+ }
38
+ declare function BugBearProvider({ config, children, appVersion }: BugBearProviderProps): React.JSX.Element;
39
+
40
+ interface BugBearButtonProps {
41
+ /** Get current app context */
42
+ getAppContext?: () => AppContext;
43
+ /** Position of the floating button (used as initial position when draggable) */
44
+ position?: 'bottom-right' | 'bottom-left';
45
+ /** Custom button style */
46
+ buttonStyle?: object;
47
+ /** Enable draggable button with edge snapping (default: true) */
48
+ draggable?: boolean;
49
+ /** Initial X position (overrides position prop) */
50
+ initialX?: number;
51
+ /** Initial Y position (overrides position prop) */
52
+ initialY?: number;
53
+ /** Minimum Y position from top of screen */
54
+ minY?: number;
55
+ /** Maximum Y position from bottom of screen */
56
+ maxYOffset?: number;
57
+ }
58
+ declare function BugBearButton({ getAppContext, position, buttonStyle, draggable, initialX, initialY, minY, maxYOffset, }: BugBearButtonProps): React.JSX.Element | null;
59
+
60
+ export { BugBearButton, BugBearProvider, useBugBear };
@@ -0,0 +1,60 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { BugBearConfig, BugBearClient, TesterInfo, TestAssignment, DeviceInfo, TesterThread, TesterMessage, AppContext } from '@bbearai/core';
3
+ export { AppContext, BugBearConfig, BugBearReport, DeviceInfo, MessageSenderType, ReportType, Severity, TestAssignment, TesterInfo, TesterMessage, TesterThread, ThreadPriority, ThreadType } from '@bbearai/core';
4
+
5
+ interface BugBearContextValue {
6
+ client: BugBearClient | null;
7
+ isTester: boolean;
8
+ isQAEnabled: boolean;
9
+ shouldShowWidget: boolean;
10
+ testerInfo: TesterInfo | null;
11
+ assignments: TestAssignment[];
12
+ currentAssignment: TestAssignment | null;
13
+ refreshAssignments: () => Promise<void>;
14
+ isLoading: boolean;
15
+ getDeviceInfo: () => DeviceInfo;
16
+ /** Navigate to a route/screen (for deep linking from test cases) */
17
+ onNavigate?: (route: string) => void;
18
+ /** Message threads for the current tester */
19
+ threads: TesterThread[];
20
+ /** Total unread message count */
21
+ unreadCount: number;
22
+ /** Refresh message threads */
23
+ refreshThreads: () => Promise<void>;
24
+ /** Get all messages in a thread */
25
+ getThreadMessages: (threadId: string) => Promise<TesterMessage[]>;
26
+ /** Send a message to a thread */
27
+ sendMessage: (threadId: string, content: string) => Promise<boolean>;
28
+ /** Mark a thread as read */
29
+ markAsRead: (threadId: string) => Promise<void>;
30
+ }
31
+ declare function useBugBear(): BugBearContextValue;
32
+ interface BugBearProviderProps {
33
+ config: BugBearConfig;
34
+ children: ReactNode;
35
+ /** App version string */
36
+ appVersion?: string;
37
+ }
38
+ declare function BugBearProvider({ config, children, appVersion }: BugBearProviderProps): React.JSX.Element;
39
+
40
+ interface BugBearButtonProps {
41
+ /** Get current app context */
42
+ getAppContext?: () => AppContext;
43
+ /** Position of the floating button (used as initial position when draggable) */
44
+ position?: 'bottom-right' | 'bottom-left';
45
+ /** Custom button style */
46
+ buttonStyle?: object;
47
+ /** Enable draggable button with edge snapping (default: true) */
48
+ draggable?: boolean;
49
+ /** Initial X position (overrides position prop) */
50
+ initialX?: number;
51
+ /** Initial Y position (overrides position prop) */
52
+ initialY?: number;
53
+ /** Minimum Y position from top of screen */
54
+ minY?: number;
55
+ /** Maximum Y position from bottom of screen */
56
+ maxYOffset?: number;
57
+ }
58
+ declare function BugBearButton({ getAppContext, position, buttonStyle, draggable, initialX, initialY, minY, maxYOffset, }: BugBearButtonProps): React.JSX.Element | null;
59
+
60
+ export { BugBearButton, BugBearProvider, useBugBear };