@ganeshdole/brainbox-react 0.0.0-stage → 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/LICENSE +28 -0
- package/README.md +69 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/provider.d.ts +38 -0
- package/dist/provider.js +54 -0
- package/dist/use-draft.d.ts +3 -0
- package/dist/use-draft.js +5 -0
- package/package.json +52 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
O'Saasy License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026, Ganesh Dole.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
1. The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
2. No licensee or downstream recipient may use the Software (including
|
|
17
|
+
any modified or derivative versions) to directly compete with the
|
|
18
|
+
original Licensor by offering it to third parties as a hosted, managed,
|
|
19
|
+
or Software-as-a-Service (SaaS) product or cloud service where the
|
|
20
|
+
primary value of the service is the functionality of the Software itself.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
23
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
24
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
25
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
26
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
27
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
28
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,69 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @ganeshdole/brainbox-react
|
|
2
|
+
|
|
3
|
+
React bindings for `@ganeshdole/brainbox-core`: a provider that owns one instance, and
|
|
4
|
+
hooks that read it. The `react-redux` to core's `redux`. No UI lives here;
|
|
5
|
+
for Brainbox's own widget see `@ganeshdole/brainbox-widget`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Inside this monorepo the package is consumed from source. `react` (19+) is a
|
|
10
|
+
peer dependency, so a host ends up with one copy.
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{ "dependencies": { "@ganeshdole/brainbox-react": "workspace:*" } }
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Outside it, from npm:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install @ganeshdole/brainbox-react @ganeshdole/brainbox-core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`pnpm build` emits `dist/` (JS plus `.d.ts`); the published package's
|
|
23
|
+
`exports` point at it (`publishConfig`).
|
|
24
|
+
|
|
25
|
+
## Use
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { BrainboxProvider, useBrainbox, useDraft } from "@ganeshdole/brainbox-react";
|
|
29
|
+
import { brainboxTransport } from "@ganeshdole/brainbox-core";
|
|
30
|
+
|
|
31
|
+
// Keep the transport stable: a new one means a new instance.
|
|
32
|
+
const transport = brainboxTransport({ endpoint, projectKey });
|
|
33
|
+
|
|
34
|
+
export function Root() {
|
|
35
|
+
return (
|
|
36
|
+
<BrainboxProvider transport={transport} identity={{ id: user.id, email: user.email }}>
|
|
37
|
+
<App />
|
|
38
|
+
</BrainboxProvider>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Your own UI on top of the engine:
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
function FeedbackPanel() {
|
|
47
|
+
const brainbox = useBrainbox(); // null on the server and the first render
|
|
48
|
+
const panel = useRef<HTMLDivElement>(null);
|
|
49
|
+
const draft = useMemo(() => brainbox?.draft({ exclude: panel.current ?? undefined }), [brainbox]);
|
|
50
|
+
if (!draft) return null;
|
|
51
|
+
return <Panel ref={panel} draft={draft} />;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Panel({ draft }: { draft: Draft }) {
|
|
55
|
+
const state = useDraft(draft); // re-renders on every change
|
|
56
|
+
return state.screenshotPending ? <Spinner /> : <img src={state.screenshotUrl ?? ""} />;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## How the provider behaves
|
|
61
|
+
|
|
62
|
+
- The instance is created in an effect and destroyed in the cleanup. That
|
|
63
|
+
keeps a server render side-effect free and leaves exactly one instance
|
|
64
|
+
under StrictMode's mount-unmount-mount. The cost is one render in which
|
|
65
|
+
`useBrainbox()` is `null`.
|
|
66
|
+
- `identity` changes call `identify()` on the live instance. `onSubmitted`
|
|
67
|
+
may change freely. Only a new `transport` rebuilds the instance.
|
|
68
|
+
- `<BrainboxProvider brainbox={instance}>` provides an instance you created
|
|
69
|
+
and own. Shells outside React (the script-tag bundle) use this.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type Brainbox, type Identity, type SubmitResult, type Transport } from "@ganeshdole/brainbox-core";
|
|
3
|
+
interface OwnedProps {
|
|
4
|
+
/** Where finished reports go. Keep it stable (module-level or `useMemo`):
|
|
5
|
+
* a new transport means a new instance. */
|
|
6
|
+
transport: Transport;
|
|
7
|
+
/** Who is filing. Changing it calls `identify()` on the live instance. */
|
|
8
|
+
identity?: Identity;
|
|
9
|
+
/** Called after every successful submit. May change between renders. */
|
|
10
|
+
onSubmitted?: (result: SubmitResult) => void;
|
|
11
|
+
brainbox?: never;
|
|
12
|
+
}
|
|
13
|
+
interface GivenProps {
|
|
14
|
+
/** An instance the caller created and will `destroy()` itself. For shells
|
|
15
|
+
* outside React, like the script-tag bundle, that need the instance before
|
|
16
|
+
* anything renders. */
|
|
17
|
+
brainbox: Brainbox;
|
|
18
|
+
transport?: never;
|
|
19
|
+
identity?: never;
|
|
20
|
+
onSubmitted?: never;
|
|
21
|
+
}
|
|
22
|
+
export type BrainboxProviderProps = (OwnedProps | GivenProps) & {
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Makes one `Brainbox` available to the tree below it.
|
|
27
|
+
*
|
|
28
|
+
* Given `transport`, it owns the instance: created in an effect, not during
|
|
29
|
+
* render, because creating it patches `console.error`, which must not happen
|
|
30
|
+
* on the server and must happen exactly once under StrictMode's
|
|
31
|
+
* mount-unmount-mount. The cost is one render where `useBrainbox()` is still
|
|
32
|
+
* `null`. Given `brainbox`, it only provides it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function BrainboxProvider(props: BrainboxProviderProps): import("react").JSX.Element;
|
|
35
|
+
/** The nearest provider's instance. `null` during the first render and on the
|
|
36
|
+
* server, before the provider's effect has created it. */
|
|
37
|
+
export declare function useBrainbox(): Brainbox | null;
|
|
38
|
+
export {};
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { createBrainbox, } from "@ganeshdole/brainbox-core";
|
|
4
|
+
const BrainboxContext = createContext(null);
|
|
5
|
+
/**
|
|
6
|
+
* Makes one `Brainbox` available to the tree below it.
|
|
7
|
+
*
|
|
8
|
+
* Given `transport`, it owns the instance: created in an effect, not during
|
|
9
|
+
* render, because creating it patches `console.error`, which must not happen
|
|
10
|
+
* on the server and must happen exactly once under StrictMode's
|
|
11
|
+
* mount-unmount-mount. The cost is one render where `useBrainbox()` is still
|
|
12
|
+
* `null`. Given `brainbox`, it only provides it.
|
|
13
|
+
*/
|
|
14
|
+
export function BrainboxProvider(props) {
|
|
15
|
+
if (props.brainbox) {
|
|
16
|
+
return (_jsx(BrainboxContext.Provider, { value: props.brainbox, children: props.children }));
|
|
17
|
+
}
|
|
18
|
+
return _jsx(OwnedProvider, { ...props });
|
|
19
|
+
}
|
|
20
|
+
function OwnedProvider({ transport, identity, onSubmitted, children, }) {
|
|
21
|
+
const [brainbox, setBrainbox] = useState(null);
|
|
22
|
+
// Read through refs so a new callback or identity does not rebuild the instance.
|
|
23
|
+
const onSubmittedRef = useRef(onSubmitted);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
onSubmittedRef.current = onSubmitted;
|
|
26
|
+
}, [onSubmitted]);
|
|
27
|
+
const identityRef = useRef(identity);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
identityRef.current = identity;
|
|
30
|
+
}, [identity]);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const options = {
|
|
33
|
+
transport,
|
|
34
|
+
identity: identityRef.current,
|
|
35
|
+
onSubmitted: (result) => onSubmittedRef.current?.(result),
|
|
36
|
+
};
|
|
37
|
+
const instance = createBrainbox(options);
|
|
38
|
+
setBrainbox(instance);
|
|
39
|
+
return () => {
|
|
40
|
+
instance.destroy();
|
|
41
|
+
setBrainbox(null);
|
|
42
|
+
};
|
|
43
|
+
}, [transport]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (brainbox && identity)
|
|
46
|
+
brainbox.identify(identity);
|
|
47
|
+
}, [brainbox, identity]);
|
|
48
|
+
return _jsx(BrainboxContext.Provider, { value: brainbox, children: children });
|
|
49
|
+
}
|
|
50
|
+
/** The nearest provider's instance. `null` during the first render and on the
|
|
51
|
+
* server, before the provider's effect has created it. */
|
|
52
|
+
export function useBrainbox() {
|
|
53
|
+
return useContext(BrainboxContext);
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ganeshdole/brainbox-react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React bindings for @ganeshdole/brainbox-core: a provider that owns one Brainbox instance and hooks that read it. No UI.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"homepage": "https://www.brainbox.sh",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ganeshdole/brainbox.git",
|
|
10
|
+
"directory": "packages/react"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@ganeshdole/brainbox-core": "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": "^19.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@eslint/js": "^10.0.1",
|
|
37
|
+
"@types/react": "^19.2.16",
|
|
38
|
+
"@types/react-dom": "^19.2.3",
|
|
39
|
+
"eslint": "^10.4.1",
|
|
40
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
41
|
+
"globals": "^17.6.0",
|
|
42
|
+
"jsdom": "^26.1.0",
|
|
43
|
+
"react": "^19.2.7",
|
|
44
|
+
"react-dom": "^19.2.7",
|
|
45
|
+
"typescript": "^6.0.3",
|
|
46
|
+
"typescript-eslint": "^8.60.1",
|
|
47
|
+
"vitest": "^3.2.4"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"lint": "eslint .",
|
|
51
|
+
"build": "tsc -p tsconfig.build.json",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
53
|
+
"test": "vitest run"
|
|
54
|
+
}
|
|
6
55
|
}
|