@proveanything/smartlinks 1.3.1 → 1.3.2
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 +13 -4
- package/dist/API_SUMMARY.md +3230 -0
- package/dist/i18n.md +287 -0
- package/dist/liquid-templates.md +484 -0
- package/dist/realtime.md +764 -0
- package/dist/theme-defaults.md +100 -0
- package/dist/theme.system.md +338 -0
- package/dist/widgets.md +510 -0
- package/docs/API_SUMMARY.md +3230 -0
- package/docs/i18n.md +287 -0
- package/docs/liquid-templates.md +484 -0
- package/docs/realtime.md +764 -0
- package/docs/theme-defaults.md +100 -0
- package/docs/theme.system.md +338 -0
- package/docs/widgets.md +510 -0
- package/package.json +2 -1
package/dist/widgets.md
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# SmartLinks Widget System
|
|
2
|
+
|
|
3
|
+
This document covers the widget system that allows SmartLinks apps to expose embeddable React components for use in parent applications (like a SmartLinks portal or homepage).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Widgets are self-contained React components that:
|
|
10
|
+
- Run inside the parent React application (not iframes)
|
|
11
|
+
- Receive standardized context props
|
|
12
|
+
- Inherit styling from the parent via CSS variables
|
|
13
|
+
- Can trigger navigation to the full app
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
17
|
+
│ Parent SmartLinks Portal (React 18) │
|
|
18
|
+
│ │
|
|
19
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
20
|
+
│ │ Competition │ │ Music App │ │ Warranty │ │
|
|
21
|
+
│ │ Widget │ │ Widget │ │ Widget │ │
|
|
22
|
+
│ │ (ESM import) │ │ (ESM import) │ │ (ESM import) │ │
|
|
23
|
+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
24
|
+
│ ↑ ↑ ↑ │
|
|
25
|
+
│ │ │ │ │
|
|
26
|
+
│ Props + SL SDK Props + SL SDK Props + SL SDK │
|
|
27
|
+
│ │
|
|
28
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Widget Props Interface
|
|
34
|
+
|
|
35
|
+
All widgets receive the `SmartLinksWidgetProps` interface:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
interface SmartLinksWidgetProps {
|
|
39
|
+
// Required context
|
|
40
|
+
collectionId: string;
|
|
41
|
+
appId: string;
|
|
42
|
+
|
|
43
|
+
// Optional context
|
|
44
|
+
productId?: string;
|
|
45
|
+
proofId?: string;
|
|
46
|
+
|
|
47
|
+
// User info (if logged in)
|
|
48
|
+
user?: {
|
|
49
|
+
id?: string;
|
|
50
|
+
email?: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
admin?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// SmartLinks SDK (pre-initialized by parent)
|
|
56
|
+
SL: typeof import('@proveanything/smartlinks');
|
|
57
|
+
|
|
58
|
+
// Callback to navigate within the parent application
|
|
59
|
+
onNavigate?: (path: string) => void;
|
|
60
|
+
|
|
61
|
+
// Base URL to the full public portal for deep linking
|
|
62
|
+
publicPortalUrl?: string;
|
|
63
|
+
|
|
64
|
+
// Size hint for responsive rendering
|
|
65
|
+
size?: 'compact' | 'standard' | 'large';
|
|
66
|
+
|
|
67
|
+
// Internationalization
|
|
68
|
+
lang?: string;
|
|
69
|
+
translations?: Record<string, string>;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Props Explained
|
|
74
|
+
|
|
75
|
+
| Prop | Type | Description |
|
|
76
|
+
|------|------|-------------|
|
|
77
|
+
| `collectionId` | `string` | The collection context (required) |
|
|
78
|
+
| `appId` | `string` | This app's identifier (required) |
|
|
79
|
+
| `productId` | `string?` | Optional product context |
|
|
80
|
+
| `proofId` | `string?` | Optional proof context |
|
|
81
|
+
| `user` | `object?` | Current user info if authenticated |
|
|
82
|
+
| `SL` | `typeof SL` | Pre-initialized SmartLinks SDK |
|
|
83
|
+
| `onNavigate` | `function?` | Callback to navigate within parent app |
|
|
84
|
+
| `publicPortalUrl` | `string?` | Base URL to full portal for deep links |
|
|
85
|
+
| `size` | `string?` | Size hint: 'compact', 'standard', or 'large' |
|
|
86
|
+
| `lang` | `string?` | Language code (e.g., 'en', 'de', 'fr') |
|
|
87
|
+
| `translations` | `object?` | Translation overrides |
|
|
88
|
+
|
|
89
|
+
### Navigation: onNavigate vs publicPortalUrl
|
|
90
|
+
|
|
91
|
+
Widgets support two navigation patterns:
|
|
92
|
+
|
|
93
|
+
**`onNavigate` (parent-controlled)**
|
|
94
|
+
- Parent provides a callback to handle navigation
|
|
95
|
+
- Widget passes a relative path (e.g., `/#/?collectionId=x&tab=details`)
|
|
96
|
+
- Parent decides what to do (router push, open modal, etc.)
|
|
97
|
+
|
|
98
|
+
**`publicPortalUrl` (direct redirect)**
|
|
99
|
+
- Widget knows the full URL to the public portal
|
|
100
|
+
- Uses `SL.iframe.redirectParent()` for navigation
|
|
101
|
+
- Automatically handles iframe escape via postMessage
|
|
102
|
+
- Useful when widget needs to break out of nested iframes
|
|
103
|
+
|
|
104
|
+
**Priority:** If both are provided, `onNavigate` takes precedence.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Parent provides callback
|
|
108
|
+
<MyWidget
|
|
109
|
+
onNavigate={(path) => router.push(path)}
|
|
110
|
+
// ...
|
|
111
|
+
/>
|
|
112
|
+
|
|
113
|
+
// Widget uses direct redirect
|
|
114
|
+
<MyWidget
|
|
115
|
+
publicPortalUrl="https://my-app.smartlinks.io"
|
|
116
|
+
// ...
|
|
117
|
+
/>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Building a Widget
|
|
123
|
+
|
|
124
|
+
### 1. Create the Widget Component
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// src/widgets/MyWidget/MyWidget.tsx
|
|
128
|
+
import { SmartLinksWidgetProps } from '../types';
|
|
129
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
130
|
+
import { Button } from '@/components/ui/button';
|
|
131
|
+
|
|
132
|
+
export const MyWidget: React.FC<SmartLinksWidgetProps> = ({
|
|
133
|
+
collectionId,
|
|
134
|
+
appId,
|
|
135
|
+
productId,
|
|
136
|
+
user,
|
|
137
|
+
SL,
|
|
138
|
+
onNavigate,
|
|
139
|
+
publicPortalUrl,
|
|
140
|
+
size = 'standard'
|
|
141
|
+
}) => {
|
|
142
|
+
// Use the SL SDK for API calls
|
|
143
|
+
const handleAction = async () => {
|
|
144
|
+
const data = await SL.appConfiguration.getConfig({
|
|
145
|
+
collectionId,
|
|
146
|
+
appId
|
|
147
|
+
});
|
|
148
|
+
console.log('Config:', data);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Navigate to full app (supports both patterns)
|
|
152
|
+
const handleOpenApp = () => {
|
|
153
|
+
const params = new URLSearchParams({ collectionId, appId });
|
|
154
|
+
if (productId) params.set('productId', productId);
|
|
155
|
+
const relativePath = `/#/?${params.toString()}`;
|
|
156
|
+
|
|
157
|
+
if (onNavigate) {
|
|
158
|
+
// Parent-controlled navigation
|
|
159
|
+
onNavigate(relativePath);
|
|
160
|
+
} else if (publicPortalUrl) {
|
|
161
|
+
// Direct redirect (handles iframe escape automatically)
|
|
162
|
+
SL.iframe.redirectParent(`${publicPortalUrl}${relativePath}`);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<Card>
|
|
168
|
+
<CardHeader>
|
|
169
|
+
<CardTitle>My Widget</CardTitle>
|
|
170
|
+
</CardHeader>
|
|
171
|
+
<CardContent>
|
|
172
|
+
<p className="text-muted-foreground mb-4">
|
|
173
|
+
Widget content goes here
|
|
174
|
+
</p>
|
|
175
|
+
<Button onClick={handleOpenApp}>Open App</Button>
|
|
176
|
+
</CardContent>
|
|
177
|
+
</Card>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 2. Create the Index File
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// src/widgets/MyWidget/index.tsx
|
|
186
|
+
export { MyWidget } from './MyWidget';
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 3. Export from Widget Barrel
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// src/widgets/index.ts
|
|
193
|
+
export { MyWidget } from './MyWidget';
|
|
194
|
+
|
|
195
|
+
// Update the manifest
|
|
196
|
+
export const WIDGET_MANIFEST = {
|
|
197
|
+
version: '1.0.0',
|
|
198
|
+
reactVersion: '18.x',
|
|
199
|
+
widgets: [
|
|
200
|
+
// ... existing widgets
|
|
201
|
+
{
|
|
202
|
+
name: 'MyWidget',
|
|
203
|
+
description: 'Description of my widget',
|
|
204
|
+
sizes: ['compact', 'standard', 'large']
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
};
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Size Modes
|
|
213
|
+
|
|
214
|
+
Widgets should support three size modes:
|
|
215
|
+
|
|
216
|
+
| Size | Use Case | Typical Height |
|
|
217
|
+
|------|----------|----------------|
|
|
218
|
+
| `compact` | Sidebar, small spaces | 80-120px |
|
|
219
|
+
| `standard` | Default widget display | 150-250px |
|
|
220
|
+
| `large` | Featured/expanded view | 300px+ |
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const MyWidget: React.FC<SmartLinksWidgetProps> = ({ size = 'standard' }) => {
|
|
224
|
+
const isCompact = size === 'compact';
|
|
225
|
+
const isLarge = size === 'large';
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<Card className={isCompact ? 'p-2' : ''}>
|
|
229
|
+
<CardHeader className={isCompact ? 'pb-2' : ''}>
|
|
230
|
+
<CardTitle className={isCompact ? 'text-sm' : 'text-lg'}>
|
|
231
|
+
Widget Title
|
|
232
|
+
</CardTitle>
|
|
233
|
+
</CardHeader>
|
|
234
|
+
<CardContent>
|
|
235
|
+
{isLarge && (
|
|
236
|
+
<p>Additional content shown only in large mode</p>
|
|
237
|
+
)}
|
|
238
|
+
<Button size={isCompact ? 'sm' : 'default'}>
|
|
239
|
+
Action
|
|
240
|
+
</Button>
|
|
241
|
+
</CardContent>
|
|
242
|
+
</Card>
|
|
243
|
+
);
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Building Widget Bundle
|
|
250
|
+
|
|
251
|
+
The project includes a separate Vite config for building widgets:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Build widgets only
|
|
255
|
+
vite build --config vite.config.widget.ts
|
|
256
|
+
|
|
257
|
+
# Output: dist/widgets.es.js
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Build Configuration
|
|
261
|
+
|
|
262
|
+
The widget build:
|
|
263
|
+
- Outputs ESM format for modern browsers
|
|
264
|
+
- Externalizes dependencies the parent already provides (see below)
|
|
265
|
+
- Generates source maps for debugging
|
|
266
|
+
- Minifies with esbuild for production
|
|
267
|
+
- Outputs to `/dist` alongside the main app (not a separate folder)
|
|
268
|
+
|
|
269
|
+
### Externalized Dependencies (Peer Dependencies)
|
|
270
|
+
|
|
271
|
+
The widget bundle does **not** include these libraries—the parent app must provide them:
|
|
272
|
+
|
|
273
|
+
| Package | Why Externalized |
|
|
274
|
+
|---------|------------------|
|
|
275
|
+
| `react`, `react-dom` | Parent's React context |
|
|
276
|
+
| `@proveanything/smartlinks` | Passed via props as `SL` |
|
|
277
|
+
| `tailwind-merge` | Utility for merging Tailwind classes |
|
|
278
|
+
| `clsx` | Utility for conditional class names |
|
|
279
|
+
| `class-variance-authority` | Utility for component variants |
|
|
280
|
+
|
|
281
|
+
These are standard packages that any modern React + Tailwind app will have. Externalizing them:
|
|
282
|
+
1. Reduces bundle size significantly
|
|
283
|
+
2. Removes JSDoc comments that inflate the bundle
|
|
284
|
+
3. Ensures consistent behavior with parent's versions
|
|
285
|
+
|
|
286
|
+
### Enabling Widget Builds
|
|
287
|
+
|
|
288
|
+
Widget builds are disabled by default to keep builds fast. To enable:
|
|
289
|
+
|
|
290
|
+
1. Add to `.env`:
|
|
291
|
+
```
|
|
292
|
+
VITE_ENABLE_WIDGETS=true
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
2. The build script should be: `vite build && vite build --config vite.config.widget.ts`
|
|
296
|
+
|
|
297
|
+
If `VITE_ENABLE_WIDGETS` is not set to `true`, the build produces a minimal stub file and skips quickly.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Parent Integration
|
|
302
|
+
|
|
303
|
+
### Dynamic Import
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// In parent SmartLinks portal
|
|
307
|
+
import { lazy, Suspense } from 'react';
|
|
308
|
+
import * as SL from '@proveanything/smartlinks';
|
|
309
|
+
|
|
310
|
+
// Dynamic import from app's CDN
|
|
311
|
+
const CompetitionWidget = lazy(() =>
|
|
312
|
+
import('https://competition-app.example.com/widgets.es.js')
|
|
313
|
+
.then(m => ({ default: m.CompetitionWidget }))
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
function Portal() {
|
|
317
|
+
return (
|
|
318
|
+
<Suspense fallback={<WidgetSkeleton />}>
|
|
319
|
+
{/* Option 1: Parent controls navigation */}
|
|
320
|
+
<CompetitionWidget
|
|
321
|
+
collectionId="abc123"
|
|
322
|
+
appId="competition"
|
|
323
|
+
user={currentUser}
|
|
324
|
+
SL={SL}
|
|
325
|
+
onNavigate={(path) => window.open(`https://competition-app.example.com${path}`)}
|
|
326
|
+
size="standard"
|
|
327
|
+
/>
|
|
328
|
+
|
|
329
|
+
{/* Option 2: Widget handles its own navigation */}
|
|
330
|
+
<CompetitionWidget
|
|
331
|
+
collectionId="abc123"
|
|
332
|
+
appId="competition"
|
|
333
|
+
user={currentUser}
|
|
334
|
+
SL={SL}
|
|
335
|
+
publicPortalUrl="https://competition-app.example.com"
|
|
336
|
+
size="standard"
|
|
337
|
+
/>
|
|
338
|
+
</Suspense>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Using WidgetWrapper
|
|
344
|
+
|
|
345
|
+
The `WidgetWrapper` component provides error boundary and loading handling:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { WidgetWrapper, CompetitionWidget } from 'competition-app/widgets';
|
|
349
|
+
|
|
350
|
+
<WidgetWrapper
|
|
351
|
+
loading={<CustomLoader />}
|
|
352
|
+
error={<CustomError />}
|
|
353
|
+
>
|
|
354
|
+
<CompetitionWidget {...props} />
|
|
355
|
+
</WidgetWrapper>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Checking Compatibility
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import { WIDGET_MANIFEST } from 'competition-app/widgets';
|
|
362
|
+
|
|
363
|
+
// Verify React version compatibility
|
|
364
|
+
if (!WIDGET_MANIFEST.reactVersion.startsWith('18')) {
|
|
365
|
+
console.warn('Widget React version mismatch');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// List available widgets
|
|
369
|
+
console.log('Available widgets:', WIDGET_MANIFEST.widgets);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Styling
|
|
375
|
+
|
|
376
|
+
Widgets inherit styling from the parent application via CSS variables:
|
|
377
|
+
|
|
378
|
+
```css
|
|
379
|
+
/* Parent defines these in their index.css */
|
|
380
|
+
:root {
|
|
381
|
+
--background: 0 0% 100%;
|
|
382
|
+
--foreground: 222.2 84% 4.9%;
|
|
383
|
+
--primary: 222.2 47.4% 11.2%;
|
|
384
|
+
--muted: 210 40% 96.1%;
|
|
385
|
+
/* ... etc */
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Widgets use semantic class names that reference these variables:
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
// ✅ DO: Use semantic classes
|
|
393
|
+
<div className="bg-background text-foreground">
|
|
394
|
+
<p className="text-muted-foreground">
|
|
395
|
+
<Button className="bg-primary text-primary-foreground">
|
|
396
|
+
|
|
397
|
+
// ❌ DON'T: Use hardcoded colors
|
|
398
|
+
<div className="bg-white text-black">
|
|
399
|
+
<p className="text-gray-500">
|
|
400
|
+
<Button className="bg-blue-500 text-white">
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Best Practices
|
|
406
|
+
|
|
407
|
+
### Do's
|
|
408
|
+
|
|
409
|
+
- ✅ Keep widgets focused and single-purpose
|
|
410
|
+
- ✅ Support all three size modes
|
|
411
|
+
- ✅ Use semantic color classes for theming
|
|
412
|
+
- ✅ Handle loading and error states gracefully
|
|
413
|
+
- ✅ Use the provided `SL` SDK for API calls
|
|
414
|
+
- ✅ Provide meaningful navigation via `onNavigate`
|
|
415
|
+
|
|
416
|
+
### Don'ts
|
|
417
|
+
|
|
418
|
+
- ❌ Don't bundle React or SmartLinks SDK
|
|
419
|
+
- ❌ Don't use hardcoded colors
|
|
420
|
+
- ❌ Don't assume specific viewport sizes
|
|
421
|
+
- ❌ Don't make widgets too complex (use full app for that)
|
|
422
|
+
- ❌ Don't store state that should persist (use parent or SDK)
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Widget Manifest
|
|
427
|
+
|
|
428
|
+
Each app exports a `WIDGET_MANIFEST` for discovery:
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
export const WIDGET_MANIFEST = {
|
|
432
|
+
version: '1.0.0', // Widget bundle version
|
|
433
|
+
reactVersion: '18.x', // Required React version
|
|
434
|
+
widgets: [
|
|
435
|
+
{
|
|
436
|
+
name: 'ExampleWidget',
|
|
437
|
+
description: 'Demo widget showing SmartLinks integration',
|
|
438
|
+
sizes: ['compact', 'standard', 'large']
|
|
439
|
+
}
|
|
440
|
+
]
|
|
441
|
+
};
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Parent applications can use this manifest to:
|
|
445
|
+
- Verify version compatibility
|
|
446
|
+
- Discover available widgets
|
|
447
|
+
- Show widget descriptions in UI
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Testing Widgets Locally
|
|
452
|
+
|
|
453
|
+
During development, you can test widgets within the app itself:
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
// In a development page
|
|
457
|
+
import { ExampleWidget } from '@/widgets';
|
|
458
|
+
import * as SL from '@proveanything/smartlinks';
|
|
459
|
+
|
|
460
|
+
function WidgetTestPage() {
|
|
461
|
+
return (
|
|
462
|
+
<div className="p-8 space-y-8">
|
|
463
|
+
<h2>Compact</h2>
|
|
464
|
+
<ExampleWidget
|
|
465
|
+
collectionId="test"
|
|
466
|
+
appId="example"
|
|
467
|
+
SL={SL}
|
|
468
|
+
size="compact"
|
|
469
|
+
/>
|
|
470
|
+
|
|
471
|
+
<h2>Standard</h2>
|
|
472
|
+
<ExampleWidget
|
|
473
|
+
collectionId="test"
|
|
474
|
+
appId="example"
|
|
475
|
+
SL={SL}
|
|
476
|
+
size="standard"
|
|
477
|
+
/>
|
|
478
|
+
|
|
479
|
+
<h2>Large</h2>
|
|
480
|
+
<ExampleWidget
|
|
481
|
+
collectionId="test"
|
|
482
|
+
appId="example"
|
|
483
|
+
SL={SL}
|
|
484
|
+
size="large"
|
|
485
|
+
/>
|
|
486
|
+
</div>
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## File Structure
|
|
494
|
+
|
|
495
|
+
```
|
|
496
|
+
src/widgets/
|
|
497
|
+
├── index.ts # Main exports barrel
|
|
498
|
+
├── types.ts # SmartLinksWidgetProps and related types
|
|
499
|
+
├── WidgetWrapper.tsx # Error boundary + Suspense wrapper
|
|
500
|
+
└── ExampleWidget/
|
|
501
|
+
├── index.tsx # Re-export
|
|
502
|
+
└── ExampleWidget.tsx # Widget implementation
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
When creating new widgets, follow this structure:
|
|
506
|
+
1. Create a folder: `src/widgets/MyWidget/`
|
|
507
|
+
2. Add component: `MyWidget.tsx`
|
|
508
|
+
3. Add re-export: `index.tsx`
|
|
509
|
+
4. Export from: `src/widgets/index.ts`
|
|
510
|
+
5. Add to: `WIDGET_MANIFEST`
|