@usesidekick/react 0.1.0 → 0.1.1
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 +190 -147
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,40 +1,55 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @usesidekick/react
|
|
2
2
|
|
|
3
|
-
Zero-change
|
|
3
|
+
Zero-change runtime for extensible React applications. Override modules can wrap, replace, or enhance any component by name without modifying the host app's source code.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
### 1. Install
|
|
5
|
+
## Installation
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
8
|
npm install @usesidekick/react
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
For the full automated setup (installs deps, configures build, creates API routes):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @usesidekick/cli init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### 1. Wrap Your App
|
|
14
20
|
|
|
15
21
|
```tsx
|
|
16
22
|
import { SidekickProvider } from '@usesidekick/react';
|
|
17
23
|
|
|
18
24
|
export default function App() {
|
|
19
25
|
return (
|
|
20
|
-
<SidekickProvider>
|
|
26
|
+
<SidekickProvider overridesEndpoint="/api/sidekick/overrides">
|
|
21
27
|
<YourApp />
|
|
22
28
|
</SidekickProvider>
|
|
23
29
|
);
|
|
24
30
|
}
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
###
|
|
33
|
+
### 2. Add the Build Plugin
|
|
34
|
+
|
|
35
|
+
The `babel-plugin-add-react-displayname` plugin preserves component names through minification so overrides can target them in production.
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
```bash
|
|
38
|
+
npm install -D babel-plugin-add-react-displayname
|
|
39
|
+
```
|
|
30
40
|
|
|
31
|
-
**Next.js** (`
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
**Next.js** (`.babelrc`):
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"presets": [
|
|
45
|
+
["next/babel", {
|
|
46
|
+
"preset-react": {
|
|
47
|
+
"runtime": "automatic",
|
|
48
|
+
"importSource": "@usesidekick/react"
|
|
49
|
+
}
|
|
50
|
+
}]
|
|
51
|
+
],
|
|
52
|
+
"plugins": ["add-react-displayname"]
|
|
38
53
|
}
|
|
39
54
|
```
|
|
40
55
|
|
|
@@ -45,41 +60,25 @@ import react from '@vitejs/plugin-react';
|
|
|
45
60
|
export default {
|
|
46
61
|
plugins: [
|
|
47
62
|
react({
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
63
|
+
jsxImportSource: '@usesidekick/react',
|
|
64
|
+
babel: { plugins: ['add-react-displayname'] }
|
|
51
65
|
})
|
|
52
66
|
]
|
|
53
67
|
}
|
|
54
68
|
```
|
|
55
69
|
|
|
56
|
-
|
|
57
|
-
```json
|
|
58
|
-
{
|
|
59
|
-
"plugins": ["add-react-displayname"]
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
That's it! Override modules can now customize any component in your app.
|
|
64
|
-
|
|
65
|
-
## How Override Modules Work
|
|
66
|
-
|
|
67
|
-
Override modules use the SDK to wrap or replace components by name:
|
|
68
|
-
|
|
69
|
-
### Wrapping Components
|
|
70
|
-
|
|
71
|
-
Add behavior around any component without modifying it:
|
|
70
|
+
### 3. Write an Override
|
|
72
71
|
|
|
73
72
|
```tsx
|
|
74
73
|
import { createOverride } from '@usesidekick/react';
|
|
75
74
|
|
|
76
75
|
export default createOverride({
|
|
77
|
-
name: '
|
|
76
|
+
name: 'Beta Banner',
|
|
78
77
|
primitives: ['ui.wrap'],
|
|
79
78
|
activate: (sdk) => {
|
|
80
|
-
sdk.ui.wrap('
|
|
81
|
-
<div
|
|
82
|
-
<
|
|
79
|
+
sdk.ui.wrap('TaskBoard', (Original) => (props) => (
|
|
80
|
+
<div>
|
|
81
|
+
<div style={{ background: 'purple', color: 'white', padding: 8 }}>Beta</div>
|
|
83
82
|
<Original {...props} />
|
|
84
83
|
</div>
|
|
85
84
|
));
|
|
@@ -87,159 +86,203 @@ export default createOverride({
|
|
|
87
86
|
});
|
|
88
87
|
```
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Completely swap out a component:
|
|
93
|
-
|
|
94
|
-
```tsx
|
|
95
|
-
import { createOverride } from '@usesidekick/react';
|
|
96
|
-
import { MyCustomModal } from './MyCustomModal';
|
|
97
|
-
|
|
98
|
-
export default createOverride({
|
|
99
|
-
name: 'Custom Modal',
|
|
100
|
-
primitives: ['ui.replace'],
|
|
101
|
-
activate: (sdk) => {
|
|
102
|
-
sdk.ui.replace('TaskModal', MyCustomModal);
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Injecting Styles
|
|
108
|
-
|
|
109
|
-
Add CSS without touching any files:
|
|
110
|
-
|
|
111
|
-
```tsx
|
|
112
|
-
sdk.ui.addStyles(`
|
|
113
|
-
.task-card {
|
|
114
|
-
border-radius: 12px;
|
|
115
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
116
|
-
}
|
|
117
|
-
`);
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Component Name Resolution
|
|
121
|
-
|
|
122
|
-
The SDK resolves component names in this order:
|
|
123
|
-
|
|
124
|
-
1. **displayName** - Explicit name, survives minification
|
|
125
|
-
2. **function name** - Works in development
|
|
126
|
-
3. **Inner component** - For memo/forwardRef wrapped components
|
|
127
|
-
|
|
128
|
-
| Environment | Name Source | Works? |
|
|
129
|
-
|------------|-------------|--------|
|
|
130
|
-
| Development | function name | Always |
|
|
131
|
-
| Production (with build plugin) | auto-added displayName | Always |
|
|
132
|
-
| Production (without plugin) | minified name | Breaks |
|
|
133
|
-
|
|
134
|
-
For best results, either:
|
|
135
|
-
- Use the babel plugin (recommended, zero per-component changes)
|
|
136
|
-
- Or manually add `displayName` to components you want to target
|
|
137
|
-
|
|
138
|
-
## API Reference
|
|
139
|
-
|
|
140
|
-
### SidekickProvider
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
<SidekickProvider
|
|
144
|
-
overridesEndpoint="/api/overrides" // Optional: API endpoint for remote overrides
|
|
145
|
-
>
|
|
146
|
-
{children}
|
|
147
|
-
</SidekickProvider>
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### SDK Primitives
|
|
89
|
+
## Override Primitives
|
|
151
90
|
|
|
152
|
-
|
|
91
|
+
### UI
|
|
153
92
|
|
|
154
93
|
| Method | Description |
|
|
155
94
|
|--------|-------------|
|
|
156
|
-
| `sdk.ui.wrap(name, wrapper)` | Wrap a component
|
|
157
|
-
| `sdk.ui.replace(name, component)` | Replace a component
|
|
158
|
-
| `sdk.ui.inject(extensionPointId, component)` | Inject into an ExtensionPoint |
|
|
95
|
+
| `sdk.ui.wrap(name, wrapper)` | Wrap a component with additional markup/logic |
|
|
96
|
+
| `sdk.ui.replace(name, component)` | Replace a component entirely |
|
|
97
|
+
| `sdk.ui.inject(extensionPointId, component)` | Inject into an `<ExtensionPoint>` slot |
|
|
159
98
|
| `sdk.ui.addStyles(css)` | Inject global CSS |
|
|
160
99
|
| `sdk.ui.addColumn(tableId, config)` | Add a column to a table |
|
|
161
100
|
| `sdk.ui.addMenuItem(menuId, config)` | Add a menu item |
|
|
162
101
|
| `sdk.ui.addTab(tabGroupId, config)` | Add a tab |
|
|
163
102
|
| `sdk.ui.addAction(actionBarId, config)` | Add an action button |
|
|
164
|
-
| `sdk.ui.modifyProps(componentId, modifier)` | Modify component props |
|
|
103
|
+
| `sdk.ui.modifyProps(componentId, modifier)` | Modify a component's props at render time |
|
|
165
104
|
|
|
166
|
-
|
|
105
|
+
### Data
|
|
167
106
|
|
|
168
107
|
| Method | Description |
|
|
169
108
|
|--------|-------------|
|
|
170
|
-
| `sdk.data.computed(fieldName, compute)` | Add a computed field |
|
|
109
|
+
| `sdk.data.computed(fieldName, compute)` | Add a computed/derived field |
|
|
171
110
|
| `sdk.data.addFilter(name, filter)` | Add a data filter |
|
|
172
|
-
| `sdk.data.transform(dataKey, transform)` | Transform data |
|
|
111
|
+
| `sdk.data.transform(dataKey, transform)` | Transform data before render |
|
|
173
112
|
| `sdk.data.intercept(pathPattern, handler)` | Intercept API responses |
|
|
174
113
|
| `sdk.data.addSortOption(tableId, config)` | Add a sort option |
|
|
175
114
|
| `sdk.data.addGroupBy(tableId, config)` | Add a group-by option |
|
|
176
115
|
|
|
177
|
-
|
|
116
|
+
### Behavior
|
|
178
117
|
|
|
179
118
|
| Method | Description |
|
|
180
119
|
|--------|-------------|
|
|
181
|
-
| `sdk.behavior.onEvent(name, handler)` |
|
|
182
|
-
| `sdk.behavior.addKeyboardShortcut(keys, action)` |
|
|
120
|
+
| `sdk.behavior.onEvent(name, handler)` | Listen for custom events |
|
|
121
|
+
| `sdk.behavior.addKeyboardShortcut(keys, action)` | Register keyboard shortcuts |
|
|
183
122
|
| `sdk.behavior.modifyRoute(pattern, handler)` | Modify routing behavior |
|
|
184
123
|
|
|
185
|
-
##
|
|
124
|
+
## React Hooks
|
|
186
125
|
|
|
187
|
-
|
|
126
|
+
The SDK provides hooks for host apps that want to read override state:
|
|
188
127
|
|
|
189
128
|
```tsx
|
|
190
|
-
import {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
129
|
+
import {
|
|
130
|
+
useAddedColumns,
|
|
131
|
+
useColumnRenames,
|
|
132
|
+
useHiddenColumns,
|
|
133
|
+
useColumnOrder,
|
|
134
|
+
useMenuItems,
|
|
135
|
+
useTabs,
|
|
136
|
+
useActions,
|
|
137
|
+
useValidations,
|
|
138
|
+
usePropsModifier,
|
|
139
|
+
useComputedField,
|
|
140
|
+
useFilter,
|
|
141
|
+
useSortOptions,
|
|
142
|
+
useGroupByOptions,
|
|
143
|
+
useKeyboardShortcuts,
|
|
144
|
+
useEventEmitter,
|
|
145
|
+
} from '@usesidekick/react';
|
|
200
146
|
```
|
|
201
147
|
|
|
202
|
-
|
|
148
|
+
## SidekickPanel
|
|
149
|
+
|
|
150
|
+
A built-in admin panel for managing overrides at runtime. Supports AI-powered generation, toggling, and deletion.
|
|
203
151
|
|
|
204
152
|
```tsx
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
153
|
+
import { SidekickPanel } from '@usesidekick/react';
|
|
154
|
+
|
|
155
|
+
<SidekickPanel
|
|
156
|
+
apiEndpoint="/api/sidekick/generate"
|
|
157
|
+
toggleEndpoint="/api/sidekick/toggle"
|
|
158
|
+
deleteEndpoint="/api/sidekick/delete"
|
|
159
|
+
/>
|
|
208
160
|
```
|
|
209
161
|
|
|
210
|
-
|
|
162
|
+
| Prop | Type | Description |
|
|
163
|
+
|------|------|-------------|
|
|
164
|
+
| `apiEndpoint` | `string?` | Endpoint for AI override generation |
|
|
165
|
+
| `toggleEndpoint` | `string?` | Endpoint for enable/disable |
|
|
166
|
+
| `deleteEndpoint` | `string?` | Endpoint for deletion |
|
|
167
|
+
| `onGenerate` | `function?` | Custom generate handler (overrides `apiEndpoint`) |
|
|
168
|
+
| `onToggle` | `function?` | Custom toggle handler (overrides `toggleEndpoint`) |
|
|
169
|
+
| `onDelete` | `function?` | Custom delete handler (overrides `deleteEndpoint`) |
|
|
170
|
+
|
|
171
|
+
## Server (`@usesidekick/react/server`)
|
|
172
|
+
|
|
173
|
+
Server-side utilities for building the Sidekick API backend. Handles override CRUD, AI generation, and validation.
|
|
211
174
|
|
|
212
|
-
###
|
|
175
|
+
### Setup with Drizzle + Neon
|
|
213
176
|
|
|
214
|
-
|
|
177
|
+
```ts
|
|
178
|
+
// app/api/sidekick/[...action]/route.ts
|
|
179
|
+
import { createSidekickHandler, createDrizzleStorage } from '@usesidekick/react/server';
|
|
180
|
+
import { db } from '@/lib/db';
|
|
181
|
+
import { overrides } from '@/lib/db/schema';
|
|
182
|
+
|
|
183
|
+
const handler = createSidekickHandler({
|
|
184
|
+
storage: createDrizzleStorage(db, overrides),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
export const GET = handler.GET;
|
|
188
|
+
export const POST = handler.POST;
|
|
189
|
+
```
|
|
215
190
|
|
|
216
|
-
|
|
217
|
-
|
|
191
|
+
This single catch-all route handles four actions (dispatched by the last URL path segment):
|
|
192
|
+
|
|
193
|
+
| Method | Path | Action |
|
|
194
|
+
|--------|------|--------|
|
|
195
|
+
| GET | `/api/sidekick/overrides` | List all overrides |
|
|
196
|
+
| POST | `/api/sidekick/generate` | AI-generate an override |
|
|
197
|
+
| POST | `/api/sidekick/toggle` | Enable/disable an override |
|
|
198
|
+
| POST | `/api/sidekick/delete` | Delete an override |
|
|
199
|
+
|
|
200
|
+
### Server Exports
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import {
|
|
204
|
+
// Handler factory
|
|
205
|
+
createSidekickHandler,
|
|
206
|
+
|
|
207
|
+
// Storage
|
|
208
|
+
createDrizzleStorage, // Drizzle ORM adapter
|
|
209
|
+
sidekickOverrides, // pgTable definition (for your schema)
|
|
210
|
+
|
|
211
|
+
// AI generation (for custom integrations)
|
|
212
|
+
buildSystemPrompt,
|
|
213
|
+
buildUserPrompt,
|
|
214
|
+
callAI,
|
|
215
|
+
parseAIResponse,
|
|
216
|
+
validateCode,
|
|
217
|
+
formatDesignSystem,
|
|
218
|
+
} from '@usesidekick/react/server';
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Custom Storage Adapter
|
|
222
|
+
|
|
223
|
+
Implement `SidekickStorage` to use any database:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import type { SidekickStorage } from '@usesidekick/react/server';
|
|
227
|
+
|
|
228
|
+
const myStorage: SidekickStorage = {
|
|
229
|
+
listOverrides: async () => { /* ... */ },
|
|
230
|
+
getOverride: async (id) => { /* ... */ },
|
|
231
|
+
createOverride: async (data) => { /* ... */ },
|
|
232
|
+
updateOverride: async (id, data) => { /* ... */ },
|
|
233
|
+
deleteOverride: async (id) => { /* ... */ },
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const handler = createSidekickHandler({ storage: myStorage });
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## How It Works
|
|
240
|
+
|
|
241
|
+
The SDK overrides `React.createElement` via a custom JSX runtime to intercept every component render. When a component renders, it checks:
|
|
242
|
+
|
|
243
|
+
1. Is there a **replacement** registered for this component? Use it.
|
|
244
|
+
2. Is there a **wrapper** registered? Wrap the original.
|
|
218
245
|
3. Otherwise, render normally.
|
|
219
246
|
|
|
220
|
-
This
|
|
247
|
+
This requires **zero changes** to existing components.
|
|
248
|
+
|
|
249
|
+
### Component Name Resolution
|
|
250
|
+
|
|
251
|
+
Names are resolved in order: `displayName` > `function.name` > inner component name (for memo/forwardRef).
|
|
252
|
+
|
|
253
|
+
| Environment | Name Source | Reliable? |
|
|
254
|
+
|-------------|-------------|-----------|
|
|
255
|
+
| Development | function name | Yes |
|
|
256
|
+
| Production + build plugin | auto-added displayName | Yes |
|
|
257
|
+
| Production without plugin | minified name | No |
|
|
221
258
|
|
|
222
259
|
### Performance
|
|
223
260
|
|
|
224
|
-
- One Map
|
|
225
|
-
-
|
|
226
|
-
- No impact when no overrides are registered
|
|
261
|
+
- One `Map.get()` per `createElement` call
|
|
262
|
+
- No overhead when no overrides are registered
|
|
227
263
|
|
|
228
264
|
### Compatibility
|
|
229
265
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
266
|
+
Works with: functional components, class components, `React.memo`, `React.forwardRef`, Suspense, Portals.
|
|
267
|
+
|
|
268
|
+
**Not supported:** React Server Components (client-side only).
|
|
269
|
+
|
|
270
|
+
## Package Exports
|
|
271
|
+
|
|
272
|
+
| Import Path | Description |
|
|
273
|
+
|-------------|-------------|
|
|
274
|
+
| `@usesidekick/react` | Provider, hooks, panel, override API, types |
|
|
275
|
+
| `@usesidekick/react/jsx-runtime` | Custom JSX runtime (configured via `jsxImportSource`) |
|
|
276
|
+
| `@usesidekick/react/jsx-dev-runtime` | Development JSX runtime |
|
|
277
|
+
| `@usesidekick/react/server` | Server handler, storage adapters, AI generation |
|
|
237
278
|
|
|
238
|
-
|
|
279
|
+
## Peer Dependencies
|
|
239
280
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
281
|
+
| Package | Version | Required? |
|
|
282
|
+
|---------|---------|-----------|
|
|
283
|
+
| `react` | ^18.2.0 | Yes |
|
|
284
|
+
| `drizzle-orm` | >=0.29.0 | Only if using `createDrizzleStorage` |
|
|
285
|
+
| `next` | >=14.0.0 | Only if using `createSidekickHandler` |
|
|
243
286
|
|
|
244
287
|
## License
|
|
245
288
|
|