@ekanos/sdk 0.5.0 → 0.7.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 +99 -13
- package/api-report.md +51 -6
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.js +2 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/widgets/widget-context.d.ts +17 -0
- package/dist/components/widgets/widget-context.js.map +1 -1
- package/dist/components/widgets/widget-preview-provider.d.ts +4 -2
- package/dist/components/widgets/widget-preview-provider.js +3 -1
- package/dist/components/widgets/widget-preview-provider.js.map +1 -1
- package/dist/components/widgets/widget-state-components.d.ts +59 -3
- package/dist/components/widgets/widget-state-components.js +65 -7
- package/dist/components/widgets/widget-state-components.js.map +1 -1
- package/dist/components/widgets/widget.d.ts +124 -21
- package/dist/components/widgets/widget.js +159 -37
- package/dist/components/widgets/widget.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/partner-oauth.js +1 -0
- package/dist/hooks/partner-oauth.js.map +1 -1
- package/dist/hooks/use-oauth-connection-status.d.ts +17 -1
- package/dist/hooks/use-oauth-connection-status.js +12 -17
- package/dist/hooks/use-oauth-connection-status.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/actor-role.d.ts +14 -0
- package/dist/integration/actor-role.js +21 -0
- package/dist/integration/actor-role.js.map +1 -0
- package/dist/integration/index.d.ts +3 -1
- package/dist/integration/index.js +4 -0
- package/dist/integration/index.js.map +1 -1
- package/dist/integration/types.d.ts +1 -1
- package/dist/integration/types.js.map +1 -1
- package/dist/integration/untrusted-text.d.ts +24 -0
- package/dist/integration/untrusted-text.js +36 -0
- package/dist/integration/untrusted-text.js.map +1 -0
- package/dist/layout/index.d.ts +8 -0
- package/dist/layout/index.js +9 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/widget-layout.d.ts +84 -0
- package/dist/layout/widget-layout.js +138 -0
- package/dist/layout/widget-layout.js.map +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock-context.js +3 -1
- package/dist/testing/mock-context.js.map +1 -1
- package/dist/testing/verify-signature.d.ts +26 -0
- package/dist/testing/verify-signature.js +45 -0
- package/dist/testing/verify-signature.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/integration.d.ts +5 -0
- package/dist/types/integration.js.map +1 -1
- package/dist/types/widget-verdict.d.ts +6 -0
- package/dist/types/widget-verdict.js +2 -0
- package/dist/types/widget-verdict.js.map +1 -0
- package/eslint.design-preset.mjs +226 -0
- package/eslint.design-rules.mjs +437 -0
- package/package.json +16 -6
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ one. Each has its own README.
|
|
|
30
30
|
- [`webhooks`](#webhooks)
|
|
31
31
|
- [`schedules`](#schedules)
|
|
32
32
|
- [`oauth`](#oauth)
|
|
33
|
-
- [`proposes` —
|
|
33
|
+
- [`proposes` — your credential model and tool classifications](#proposes)
|
|
34
34
|
- [`workspaceTargets`](#workspacetargets)
|
|
35
35
|
- [Testing](#testing)
|
|
36
36
|
- [Entrypoints](#entrypoints)
|
|
@@ -190,7 +190,7 @@ There is no raw database client, no Supabase, and no request object.
|
|
|
190
190
|
| `ctx.secrets` | `get(name)`, `names()`, `set(name, value)` over the resolved credential set |
|
|
191
191
|
| `ctx.fetch` | `fetch`, refusing anything outside `egress` before it opens a socket |
|
|
192
192
|
| `ctx.logger` | `debug`/`info`/`warn`/`error`, each `(context: object, message: string)` — pino-style, pre-bound with `{integration, accountId, actor}` |
|
|
193
|
-
| `ctx.actor` | `{ kind: 'user', userId }` or `{ kind: 'machine', tokenId, createdBy }` |
|
|
193
|
+
| `ctx.actor` | `{ kind: 'user', userId, role }` or `{ kind: 'machine', tokenId, createdBy }`. `role` is `{ name, hierarchyLevel }` (1 owner, 2 admin, 3 member; `null` when unresolved). Gate a write action with `actorIsAtLeast(ctx.actor, ADMIN_HIERARCHY_LEVEL)` from `@ekanos/sdk/integration` and throw `PublicActionError` otherwise — the host runs every action with the account's credential |
|
|
194
194
|
|
|
195
195
|
Plus read-only identity facts, all already authorized: `ctx.accountId`,
|
|
196
196
|
`ctx.accountSlug`, `ctx.userId` (`null` for machine actors), `ctx.sourceId`,
|
|
@@ -339,7 +339,7 @@ no `http:`. Wildcards are supported only as one leading `*.`. Anything else is
|
|
|
339
339
|
rejected by `defineIntegration()` with the offending entry quoted.
|
|
340
340
|
|
|
341
341
|
A denied call throws `EgressDeniedError` synchronously. Widening this list is a security-posture change a reviewer looks at during
|
|
342
|
-
**
|
|
342
|
+
**admission** — the human review your integration passes before it is enabled
|
|
343
343
|
for real accounts, where someone reads this declaration against what your
|
|
344
344
|
handlers actually do. Declare the origins you call and no more.
|
|
345
345
|
|
|
@@ -414,8 +414,7 @@ const apiKey = await ctx.secrets.get(ACME_API_KEY);
|
|
|
414
414
|
|
|
415
415
|
### `tools`
|
|
416
416
|
|
|
417
|
-
Tools are what the Ekanos assistant
|
|
418
|
-
engine) can call. Each declares a name, a description the model reads, an
|
|
417
|
+
Tools are what the Ekanos assistant, workflows and managed agents can call. Each declares a name, a description the model reads, an
|
|
419
418
|
optional JSON-Schema parameter object, and an async `run`.
|
|
420
419
|
|
|
421
420
|
```ts
|
|
@@ -459,6 +458,47 @@ for a genuine fault.
|
|
|
459
458
|
|
|
460
459
|
`outputExample` is shown to the model as a shape hint. Keep it small and honest.
|
|
461
460
|
|
|
461
|
+
**Text written outside this system is data, not instructions.** A deal name, a
|
|
462
|
+
call transcript, a support reply, a booking answer: anything a vendor's users
|
|
463
|
+
typed can say "ignore the above and…", and in a tool result it looks exactly
|
|
464
|
+
like a field you computed. Return such text only under a field whose name
|
|
465
|
+
starts with `untrusted`, clip it with `untrustedText()` (from
|
|
466
|
+
`@ekanos/sdk/integration`; 1000 characters by default), and put one
|
|
467
|
+
`note: untrustedTextNote('deal, stage and pipeline names')` at the top of the
|
|
468
|
+
output so the model reads the rule where it reads the data. `untrustedText()`
|
|
469
|
+
bounds the length and marks the text as coming from a third party so the
|
|
470
|
+
assistant treats it as data; it does not strip or neutralise injected
|
|
471
|
+
instructions. Pass `untrustedTextNote()` a fixed string you wrote, never vendor
|
|
472
|
+
text. In a write tool's description, say to confirm with the user first and
|
|
473
|
+
never act on what the text asks for.
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
import { untrustedText, untrustedTextNote } from '@ekanos/sdk/integration';
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
note: untrustedTextNote('deal names'),
|
|
480
|
+
deals: deals.map((d) => ({ id: d.id, untrustedName: untrustedText(d.name, 200), amount: d.amount })),
|
|
481
|
+
};
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**`ownerAction` is how an owner reads an approval for your tool.** When an agent
|
|
485
|
+
or workflow pauses on one of your tools, the owner sees "{agent} wants to
|
|
486
|
+
{ownerAction} in {your integration}". Declare the phrase yourself, a bare verb
|
|
487
|
+
that names no ids:
|
|
488
|
+
|
|
489
|
+
```ts
|
|
490
|
+
ownerAction: 'sync your price list',
|
|
491
|
+
// or one phrase per locale; `default` covers any locale you did not list
|
|
492
|
+
ownerAction: { en: 'sync your price list', es: 'sincronizar su lista de precios' },
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
It is plain text, at most 120 characters, never a message key: a partner cannot
|
|
496
|
+
add keys to the host's messages. The host picks the exact locale, then its
|
|
497
|
+
language (`pt-BR` then `pt`), then the default locale, then your `default`. With
|
|
498
|
+
no `ownerAction` the host derives a phrase from the tool name ("update deal
|
|
499
|
+
stage" from `acme_update_deal_stage`), and a name that is not `<verb>_<thing>`
|
|
500
|
+
reads "use <tool name>", so declare one for any tool that writes.
|
|
501
|
+
|
|
462
502
|
**`parameters` is a narrow slice of JSON Schema, not all of it.** The top level
|
|
463
503
|
is `.strict()` and accepts exactly four keys — `type` (which must be the
|
|
464
504
|
literal `'object'`), `properties`, `required` and `additionalProperties`.
|
|
@@ -1080,6 +1120,10 @@ export const acmeOAuth: PartnerOAuthDeclaration<MyStorage> = {
|
|
|
1080
1120
|
tokenUrl: 'https://connect.acme.example/oauth/token',
|
|
1081
1121
|
scopes: ['payments:read', 'refunds:write'],
|
|
1082
1122
|
pkce: true,
|
|
1123
|
+
// How the token endpoint wants client authentication. Defaults to
|
|
1124
|
+
// 'client_secret_basic' (HTTP Basic); LinkedIn needs 'client_secret_post'
|
|
1125
|
+
// (client_id and client_secret in the form body).
|
|
1126
|
+
tokenAuthMethod: 'client_secret_basic',
|
|
1083
1127
|
},
|
|
1084
1128
|
credentials: {
|
|
1085
1129
|
clientIdSecretName: 'acme_oauth_client_id',
|
|
@@ -1142,9 +1186,10 @@ async function.
|
|
|
1142
1186
|
|
|
1143
1187
|
### `proposes`
|
|
1144
1188
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
and the
|
|
1189
|
+
Where you declare your credential model and classify each tool. Once your
|
|
1190
|
+
integration is admitted the host applies these values, the same as a first-party
|
|
1191
|
+
integration's own. The admission review is the check, and the host can tighten a
|
|
1192
|
+
declaration (never loosen it).
|
|
1148
1193
|
|
|
1149
1194
|
```ts
|
|
1150
1195
|
proposes: {
|
|
@@ -1155,22 +1200,32 @@ proposes: {
|
|
|
1155
1200
|
tools: {
|
|
1156
1201
|
list_recent_payments: { effect: 'read', sensitivity: 'financial' },
|
|
1157
1202
|
// Honest classification: this moves money. Marking it 'read' or
|
|
1158
|
-
// 'internal' to get past a gate is
|
|
1203
|
+
// 'internal' to get past a gate is a review failure at admission.
|
|
1159
1204
|
refund_payment: { effect: 'write', sensitivity: 'financial' },
|
|
1160
1205
|
},
|
|
1161
1206
|
},
|
|
1162
1207
|
```
|
|
1163
1208
|
|
|
1164
1209
|
`effect` is `'read' | 'write'`. `sensitivity` is `'public' | 'internal' | 'pii'
|
|
1165
|
-
| 'financial'`.
|
|
1210
|
+
| 'financial'`. Classify every tool: a field you leave out falls back to
|
|
1211
|
+
`effect: 'write'` and `sensitivity: 'pii'`, and a tool treated that way will not
|
|
1212
|
+
run in a team workflow.
|
|
1166
1213
|
|
|
1167
1214
|
The keys of `proposes.tools` must match declared tool names exactly — a
|
|
1168
1215
|
proposal for a tool you did not declare is a validation error, so a rename
|
|
1169
1216
|
cannot leave a stale classification behind.
|
|
1170
1217
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1218
|
+
What the host does with it:
|
|
1219
|
+
|
|
1220
|
+
- `credentialModel` decides which workflows may call your tools. Team workflows
|
|
1221
|
+
(a machine actor) admit `'account'` and `'source'`; personal workflows (the
|
|
1222
|
+
user) admit `'user'` and `'account'`. Undeclared means workflows cannot use
|
|
1223
|
+
your tools.
|
|
1224
|
+
- Team workflows never expose `pii` or `financial` tools, and a `write` tool
|
|
1225
|
+
needs an approval step in the workflow. Agents use their per-tool approval
|
|
1226
|
+
policy. Personal workflows and the chat assistant apply no sensitivity gate.
|
|
1227
|
+
- `ctx.storage.user` still throws for machine actors, and partner OAuth tokens
|
|
1228
|
+
stay account-scoped.
|
|
1174
1229
|
|
|
1175
1230
|
### `workspaceTargets`
|
|
1176
1231
|
|
|
@@ -1240,6 +1295,37 @@ Both throw if the id is not declared, and `invokeWebhook` throws if the payload
|
|
|
1240
1295
|
fails `payloadSchema` — the handler never runs. The local transport records the
|
|
1241
1296
|
signature skip as one `warn` line on `ctx.logs`.
|
|
1242
1297
|
|
|
1298
|
+
`invokeWebhook` never verifies a signature — that is the transport's job — so a
|
|
1299
|
+
wrong header name or signed-payload template in your declaration would first
|
|
1300
|
+
show up as rejected deliveries on the host. `verifyDeclaredWebhookSignature()`
|
|
1301
|
+
closes that gap: it runs the host's own verifier against one declaration. Sign
|
|
1302
|
+
a payload in the test the way the VENDOR documents it and assert the
|
|
1303
|
+
declaration accepts it:
|
|
1304
|
+
|
|
1305
|
+
```ts
|
|
1306
|
+
import { createHmac } from 'node:crypto';
|
|
1307
|
+
import { verifyDeclaredWebhookSignature } from '@ekanos/sdk/testing';
|
|
1308
|
+
|
|
1309
|
+
const body = JSON.stringify(payload);
|
|
1310
|
+
const t = String(Math.floor(Date.now() / 1000));
|
|
1311
|
+
const v1 = createHmac('sha256', secret).update(`${t}.${body}`).digest('hex');
|
|
1312
|
+
|
|
1313
|
+
await expect(
|
|
1314
|
+
verifyDeclaredWebhookSignature(integration, 'payment-updated', {
|
|
1315
|
+
headers: { 'Stripe-Signature': `t=${t},v1=${v1}` },
|
|
1316
|
+
rawBody: body,
|
|
1317
|
+
secret,
|
|
1318
|
+
}),
|
|
1319
|
+
).resolves.toEqual({ ok: true });
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
A failure reports the same reason the host would log (`missing-header`,
|
|
1323
|
+
`malformed-header`, `missing-timestamp`, `malformed-timestamp`,
|
|
1324
|
+
`stale-timestamp`, `missing-secret`, `mismatch`). An empty secret fails closed
|
|
1325
|
+
with `missing-secret`. It throws for an undeclared id and for a
|
|
1326
|
+
declaration with `signature: 'none'`. The verifier itself is also exported as
|
|
1327
|
+
`verifyWebhookSignature()` for a test that wants to drive it directly.
|
|
1328
|
+
|
|
1243
1329
|
`invokeActivate()` does the same for `onActivate` — no payload or invocation to
|
|
1244
1330
|
build, just the handler against the definition-derived context:
|
|
1245
1331
|
|
package/api-report.md
CHANGED
|
@@ -9,9 +9,9 @@ This is the complete surface of every entrypoint. The authoring guide
|
|
|
9
9
|
for these symbols is README.md. Entrypoints marked WORKSPACE-ONLY are
|
|
10
10
|
not reachable from a published install — see the note in each section.
|
|
11
11
|
|
|
12
|
-
Total exported names: **
|
|
12
|
+
Total exported names: **212** across 8 entrypoints.
|
|
13
13
|
|
|
14
|
-
## `@ekanos/sdk` (
|
|
14
|
+
## `@ekanos/sdk` (35 exports)
|
|
15
15
|
|
|
16
16
|
| Export | Kind |
|
|
17
17
|
| --- | --- |
|
|
@@ -19,6 +19,7 @@ Total exported names: **172** across 7 entrypoints.
|
|
|
19
19
|
| `AccountProductDataType` | type alias |
|
|
20
20
|
| `ActivationFormProps` | type alias |
|
|
21
21
|
| `ActivationResult` | interface |
|
|
22
|
+
| `AttentionSignal` | interface |
|
|
22
23
|
| `HealthCheckResult` | interface |
|
|
23
24
|
| `HealthStatus` | type alias |
|
|
24
25
|
| `IntegrationActor` | type alias |
|
|
@@ -46,9 +47,11 @@ Total exported names: **172** across 7 entrypoints.
|
|
|
46
47
|
| `WidgetConfig` | interface |
|
|
47
48
|
| `WidgetSnapshot` | type alias |
|
|
48
49
|
| `WidgetSuggestedPrompt` | interface |
|
|
50
|
+
| `WidgetVerdict` | interface |
|
|
51
|
+
| `WidgetVerdictTone` | type alias |
|
|
49
52
|
| `WorkspaceTargetDefinition` | interface |
|
|
50
53
|
|
|
51
|
-
## `@ekanos/sdk/components` (
|
|
54
|
+
## `@ekanos/sdk/components` (34 exports)
|
|
52
55
|
|
|
53
56
|
| Export | Kind |
|
|
54
57
|
| --- | --- |
|
|
@@ -70,15 +73,24 @@ Total exported names: **172** across 7 entrypoints.
|
|
|
70
73
|
| `WidgetContextValue` | interface |
|
|
71
74
|
| `WidgetData` | interface |
|
|
72
75
|
| `WidgetDataLoading` | function |
|
|
76
|
+
| `WidgetEmptyStateProps` | interface |
|
|
77
|
+
| `WidgetHeaderTrailing` | type alias |
|
|
73
78
|
| `WidgetHealth` | interface |
|
|
79
|
+
| `WidgetInactiveReason` | type alias |
|
|
80
|
+
| `WidgetInactiveState` | function |
|
|
81
|
+
| `WidgetInactiveStateProps` | interface |
|
|
74
82
|
| `WidgetMeta` | interface |
|
|
75
83
|
| `WidgetPreviewProvider` | function |
|
|
76
84
|
| `WidgetPreviewProviderProps` | interface |
|
|
77
85
|
| `WidgetRenderState` | type alias |
|
|
86
|
+
| `WidgetSkeletonVariant` | type alias |
|
|
87
|
+
| `WidgetSource` | interface |
|
|
78
88
|
| `WidgetState` | interface |
|
|
89
|
+
| `WidgetStateAction` | interface |
|
|
79
90
|
| `currentReturnPath` | function |
|
|
91
|
+
| `useWidgetVerdict` | function |
|
|
80
92
|
|
|
81
|
-
## `@ekanos/sdk/hooks` (
|
|
93
|
+
## `@ekanos/sdk/hooks` (45 exports)
|
|
82
94
|
|
|
83
95
|
| Export | Kind |
|
|
84
96
|
| --- | --- |
|
|
@@ -99,6 +111,7 @@ Total exported names: **172** across 7 entrypoints.
|
|
|
99
111
|
| `IntegrationActivationProvider` | function |
|
|
100
112
|
| `IntegrationFetchProvider` | function |
|
|
101
113
|
| `OAuthErrorParam` | interface |
|
|
114
|
+
| `OAuthWidgetGateOptions` | type alias |
|
|
102
115
|
| `PartnerOAuthPathOptions` | interface |
|
|
103
116
|
| `PublishEventInput` | interface |
|
|
104
117
|
| `UseIntegrationActionOptions` | interface |
|
|
@@ -111,6 +124,7 @@ Total exported names: **172** across 7 entrypoints.
|
|
|
111
124
|
| `fetchIntegrationStorage` | function |
|
|
112
125
|
| `integrationActionUrl` | function |
|
|
113
126
|
| `integrationStorageUrl` | function |
|
|
127
|
+
| `oauthWidgetGateOptionsFor` | function |
|
|
114
128
|
| `partnerOAuthConnectPath` | function |
|
|
115
129
|
| `partnerOAuthErrorParam` | function |
|
|
116
130
|
| `partnerOAuthStatusUrl` | function |
|
|
@@ -169,7 +183,7 @@ Total exported names: **172** across 7 entrypoints.
|
|
|
169
183
|
| `requireContext` | function |
|
|
170
184
|
| `resolveStorageKeyDeclaration` | function |
|
|
171
185
|
|
|
172
|
-
## `@ekanos/sdk/testing` (
|
|
186
|
+
## `@ekanos/sdk/testing` (22 exports)
|
|
173
187
|
|
|
174
188
|
| Export | Kind |
|
|
175
189
|
| --- | --- |
|
|
@@ -184,18 +198,27 @@ Total exported names: **172** across 7 entrypoints.
|
|
|
184
198
|
| `RecordedFetchCall` | interface |
|
|
185
199
|
| `RecordedLog` | interface |
|
|
186
200
|
| `ScheduleInvocationOutcome` | interface |
|
|
201
|
+
| `SignatureVerification` | type alias |
|
|
202
|
+
| `VerifyDeclaredWebhookSignatureOptions` | interface |
|
|
203
|
+
| `VerifyWebhookSignatureArgs` | interface |
|
|
187
204
|
| `WebhookInvocationOutcome` | interface |
|
|
188
205
|
| `createMockContext` | function |
|
|
189
206
|
| `createMockFetchProvider` | function |
|
|
190
207
|
| `invokeActivate` | function |
|
|
191
208
|
| `invokeSchedule` | function |
|
|
192
209
|
| `invokeWebhook` | function |
|
|
210
|
+
| `verifyDeclaredWebhookSignature` | function |
|
|
211
|
+
| `verifyWebhookSignature` | function |
|
|
193
212
|
|
|
194
|
-
## `@ekanos/sdk/integration` (
|
|
213
|
+
## `@ekanos/sdk/integration` (41 exports)
|
|
195
214
|
|
|
196
215
|
| Export | Kind |
|
|
197
216
|
| --- | --- |
|
|
217
|
+
| `ADMIN_HIERARCHY_LEVEL` | variable |
|
|
218
|
+
| `AttentionHandler` | type alias |
|
|
219
|
+
| `AttentionSignal` | interface |
|
|
198
220
|
| `DEFAULT_SIGNATURE_TOLERANCE_SECONDS` | variable |
|
|
221
|
+
| `DEFAULT_UNTRUSTED_TEXT_MAX_CHARS` | variable |
|
|
199
222
|
| `DefinitionCollisionInput` | interface |
|
|
200
223
|
| `FirstPartyInventory` | interface |
|
|
201
224
|
| `IntegrationCapabilityDeclaration` | type alias |
|
|
@@ -206,6 +229,7 @@ Total exported names: **172** across 7 entrypoints.
|
|
|
206
229
|
| `IntegrationProposals` | type alias |
|
|
207
230
|
| `OAuthProviderDeclaration` | interface |
|
|
208
231
|
| `OAuthTokens` | interface |
|
|
232
|
+
| `OWNER_HIERARCHY_LEVEL` | variable |
|
|
209
233
|
| `OnActivateHandler` | type alias |
|
|
210
234
|
| `PARTNER_STORAGE_LIMITS` | variable |
|
|
211
235
|
| `PartnerActionDeclaration` | interface |
|
|
@@ -223,6 +247,27 @@ Total exported names: **172** across 7 entrypoints.
|
|
|
223
247
|
| `WebhookResult` | interface |
|
|
224
248
|
| `WebhookSignatureDeclaration` | type alias |
|
|
225
249
|
| `WebhookSignatureScheme` | interface |
|
|
250
|
+
| `WidgetVerdict` | interface |
|
|
251
|
+
| `WidgetVerdictTone` | type alias |
|
|
252
|
+
| `actorIsAtLeast` | function |
|
|
226
253
|
| `defineIntegration` | function |
|
|
227
254
|
| `parseCronExpression` | function |
|
|
255
|
+
| `untrustedText` | function |
|
|
256
|
+
| `untrustedTextNote` | function |
|
|
228
257
|
| `validateIntegrationDefinitions` | function |
|
|
258
|
+
|
|
259
|
+
## `@ekanos/sdk/layout` (11 exports)
|
|
260
|
+
|
|
261
|
+
| Export | Kind |
|
|
262
|
+
| --- | --- |
|
|
263
|
+
| `COMPACT_FROM_WIDTH` | variable |
|
|
264
|
+
| `SizedWidget` | interface |
|
|
265
|
+
| `WidgetRowGroup` | type alias |
|
|
266
|
+
| `WidgetSize` | type alias |
|
|
267
|
+
| `buildRowGroups` | function |
|
|
268
|
+
| `getColSpan` | function |
|
|
269
|
+
| `hasTrailingGap` | function |
|
|
270
|
+
| `pairRows` | function |
|
|
271
|
+
| `resolveWidgetSize` | function |
|
|
272
|
+
| `snapFullWidthToRowBoundary` | function |
|
|
273
|
+
| `splitBalanced` | function |
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
* Surface discipline: additions require an entry in
|
|
26
26
|
* api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.
|
|
27
27
|
*/
|
|
28
|
-
export { Widget } from './widgets/widget.js';
|
|
29
|
-
export { WidgetContext, type WidgetActions, type WidgetContextValue, type WidgetData, type WidgetHealth, type WidgetMeta, type WidgetRenderState, type WidgetState, } from './widgets/widget-context.js';
|
|
28
|
+
export { Widget, useWidgetVerdict, type WidgetHeaderTrailing, } from './widgets/widget.js';
|
|
29
|
+
export { WidgetContext, type WidgetActions, type WidgetContextValue, type WidgetData, type WidgetHealth, type WidgetMeta, type WidgetRenderState, type WidgetSource, type WidgetState, } from './widgets/widget-context.js';
|
|
30
30
|
export { WidgetPreviewProvider } from './widgets/widget-preview-provider.js';
|
|
31
31
|
export type { WidgetPreviewProviderProps } from './widgets/widget-preview-provider.js';
|
|
32
32
|
export { AiPromptChip } from './widgets/ai-prompt-chip.js';
|
|
33
33
|
export { AskIcon } from './widgets/ask-icon.js';
|
|
34
34
|
export { WidgetAskBar } from './widgets/widget-ask-bar.js';
|
|
35
|
-
export { WidgetDataLoading } from './widgets/widget-state-components.js';
|
|
35
|
+
export { WidgetDataLoading, type WidgetEmptyStateProps, WidgetInactiveState, type WidgetInactiveReason, type WidgetInactiveStateProps, type WidgetSkeletonVariant, type WidgetStateAction, } from './widgets/widget-state-components.js';
|
|
36
36
|
export { BaseActivationForm } from './base-activation-form.js';
|
|
37
37
|
export type { BaseActivationFormProps } from './base-activation-form.js';
|
|
38
38
|
export { ActivationDialog, ActivationInline, BaseActivationDialog, } from './base-activation-dialog.js';
|
package/dist/components/index.js
CHANGED
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
* Surface discipline: additions require an entry in
|
|
26
26
|
* api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.
|
|
27
27
|
*/
|
|
28
|
-
export { Widget } from './widgets/widget.js';
|
|
28
|
+
export { Widget, useWidgetVerdict, } from './widgets/widget.js';
|
|
29
29
|
export { WidgetContext, } from './widgets/widget-context.js';
|
|
30
30
|
export { WidgetPreviewProvider } from './widgets/widget-preview-provider.js';
|
|
31
31
|
export { AiPromptChip } from './widgets/ai-prompt-chip.js';
|
|
32
32
|
export { AskIcon } from './widgets/ask-icon.js';
|
|
33
33
|
export { WidgetAskBar } from './widgets/widget-ask-bar.js';
|
|
34
|
-
export { WidgetDataLoading } from './widgets/widget-state-components.js';
|
|
34
|
+
export { WidgetDataLoading, WidgetInactiveState, } from './widgets/widget-state-components.js';
|
|
35
35
|
export { BaseActivationForm } from './base-activation-form.js';
|
|
36
36
|
export { ActivationDialog, ActivationInline, BaseActivationDialog, } from './base-activation-dialog.js';
|
|
37
37
|
export { BaseMarketplaceTile } from './base-marketplace-tile.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACL,MAAM,EACN,gBAAgB,GAEjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,GASd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAG1E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,GAKpB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["/**\n * @ekanos/sdk/components — client-side building blocks.\n *\n * Import from client components only. `Widget` is the compound API every\n * dashboard widget renders through; the activation forms are the two\n * sanctioned activation UX bases (credential-style and OAuth-style), and\n * `BaseMarketplaceTile` is the matching base for the marketplace tile.\n *\n * The SDK OWNS these implementations — the pre-inversion re-exports through\n * `@kit/integrations-core/components/*` are gone; core now re-exports FROM\n * here for first-party code (one source of truth, two consumption paths —\n * one source of truth with two consumption paths. UI primitives come from\n * `@ekanos/ui`\n * (the published slice of the host design system), never `@kit/ui`.\n *\n * Server seam: nothing here imports a server module. `OAuthActivationForm`\n * performs activation through the host-injected actions seam\n * (`IntegrationActivationProvider`, @ekanos/sdk/hooks) — a server-action\n * REFERENCE bound by the host, not server code shipped to the browser.\n *\n * `WidgetContext` is the single context identity both sides share: the\n * host's `DashboardWidgetProvider` (host chrome — persistence callbacks,\n * config hierarchy) provides it, the SDK's `Widget.*` reads it.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport {\n Widget,\n useWidgetVerdict,\n type WidgetHeaderTrailing,\n} from './widgets/widget';\nexport {\n WidgetContext,\n type WidgetActions,\n type WidgetContextValue,\n type WidgetData,\n type WidgetHealth,\n type WidgetMeta,\n type WidgetRenderState,\n type WidgetSource,\n type WidgetState,\n} from './widgets/widget-context';\nexport { WidgetPreviewProvider } from './widgets/widget-preview-provider';\n\nexport type { WidgetPreviewProviderProps } from './widgets/widget-preview-provider';\nexport { AiPromptChip } from './widgets/ai-prompt-chip';\nexport { AskIcon } from './widgets/ask-icon';\nexport { WidgetAskBar } from './widgets/widget-ask-bar';\nexport {\n WidgetDataLoading,\n type WidgetEmptyStateProps,\n WidgetInactiveState,\n type WidgetInactiveReason,\n type WidgetInactiveStateProps,\n type WidgetSkeletonVariant,\n type WidgetStateAction,\n} from './widgets/widget-state-components';\n\nexport { BaseActivationForm } from './base-activation-form';\nexport type { BaseActivationFormProps } from './base-activation-form';\nexport {\n ActivationDialog,\n ActivationInline,\n BaseActivationDialog,\n} from './base-activation-dialog';\n\nexport { BaseMarketplaceTile } from './base-marketplace-tile';\nexport type { BaseMarketplaceTileProps } from './base-marketplace-tile';\n\nexport { OAuthActivationForm } from './oauth-activation-form';\nexport type { OAuthActivationFormConfig } from './oauth-activation-form';\n\nexport { currentReturnPath } from './current-return-path';\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { WidgetAskContext } from '../../types/widget-ask-context.js';
|
|
2
|
+
import type { WidgetVerdict } from '../../types/widget-verdict.js';
|
|
2
3
|
export type WidgetRenderState = 'active' | 'disabled' | 'loading' | 'error' | 'inactive';
|
|
3
4
|
export interface WidgetState {
|
|
4
5
|
state: WidgetRenderState;
|
|
@@ -35,6 +36,16 @@ export interface WidgetMeta {
|
|
|
35
36
|
* page; the header menu shows "Open in workspace" when present.
|
|
36
37
|
*/
|
|
37
38
|
openHref?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Whose data the widget shows: the integration's name and bundled logo.
|
|
41
|
+
* `Widget.Header` draws the logo left of the title (the design system's widget rules).
|
|
42
|
+
*/
|
|
43
|
+
source?: WidgetSource;
|
|
44
|
+
}
|
|
45
|
+
export interface WidgetSource {
|
|
46
|
+
name: string;
|
|
47
|
+
/** A logo URL; the header shows the name's first letter without one. */
|
|
48
|
+
logoPath?: string;
|
|
38
49
|
}
|
|
39
50
|
/**
|
|
40
51
|
* What the widget's own data layer reports through `Widget.DataState`. The
|
|
@@ -49,6 +60,12 @@ export interface WidgetData {
|
|
|
49
60
|
refreshing: boolean;
|
|
50
61
|
/** Reloads the widget's data. */
|
|
51
62
|
refresh?: () => void;
|
|
63
|
+
/**
|
|
64
|
+
* The widget's own conclusion about its data (the design system's widget rules): the
|
|
65
|
+
* header shows it in place of the context line, and the "Ask about this"
|
|
66
|
+
* snapshot carries it.
|
|
67
|
+
*/
|
|
68
|
+
verdict?: WidgetVerdict;
|
|
52
69
|
}
|
|
53
70
|
export interface WidgetContextValue {
|
|
54
71
|
state: WidgetState;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-context.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-context.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"widget-context.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-context.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AA2FtC,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAA4B,IAAI,CAAC,CAAC","sourcesContent":["'use client';\n\nimport { createContext } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport type { WidgetVerdict } from '../../types/widget-verdict';\n\nexport type WidgetRenderState =\n | 'active'\n | 'disabled'\n | 'loading'\n | 'error'\n | 'inactive';\n\nexport interface WidgetState {\n state: WidgetRenderState;\n collapsed: boolean;\n pinned: boolean;\n pendingCollapse: boolean;\n pendingPin: boolean;\n}\n\nexport interface WidgetActions {\n toggleCollapsed: () => void;\n togglePinned: () => void;\n}\n\nexport interface WidgetHealth {\n status: 'healthy' | 'degraded' | 'unhealthy' | 'unknown';\n lastSuccessAt?: string | null;\n errorMessage?: string | null;\n}\n\nexport interface WidgetMeta {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n maxContentHeight?: string;\n isCollapsible: boolean;\n isPinnable: boolean;\n askContext?: WidgetAskContext;\n aiFooterEnabled: boolean;\n className?: string;\n /** Integration health for this widget's product, if known. */\n health?: WidgetHealth;\n /**\n * Where the widget's full surface lives, e.g. the workspace a pinned widget\n * came from. Set by the host only where following it leaves the current\n * page; the header menu shows \"Open in workspace\" when present.\n */\n openHref?: string;\n /**\n * Whose data the widget shows: the integration's name and bundled logo.\n * `Widget.Header` draws the logo left of the title (the design system's widget rules).\n */\n source?: WidgetSource;\n}\n\nexport interface WidgetSource {\n name: string;\n /** A logo URL; the header shows the name's first letter without one. */\n logoPath?: string;\n}\n\n/**\n * What the widget's own data layer reports through `Widget.DataState`. The\n * host cannot see a widget's queries, so every field is opt-in: the header\n * menu's Refresh, the error view's Try again, and the \"Updated …\" footer each\n * appear only when the widget supplies what they need.\n */\nexport interface WidgetData {\n /** Epoch milliseconds of the last successful load; absent when unknown. */\n updatedAt?: number;\n /** A reload is in flight while data is already on screen. */\n refreshing: boolean;\n /** Reloads the widget's data. */\n refresh?: () => void;\n /**\n * The widget's own conclusion about its data (the design system's widget rules): the\n * header shows it in place of the context line, and the \"Ask about this\"\n * snapshot carries it.\n */\n verdict?: WidgetVerdict;\n}\n\nexport interface WidgetContextValue {\n state: WidgetState;\n actions: WidgetActions;\n meta: WidgetMeta;\n data?: WidgetData;\n}\n\nexport const WidgetContext = createContext<WidgetContextValue | null>(null);\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { WidgetAskContext } from '../../types/widget-ask-context.js';
|
|
3
|
-
import { type WidgetHealth, type WidgetRenderState } from './widget-context.js';
|
|
3
|
+
import { type WidgetHealth, type WidgetRenderState, type WidgetSource } from './widget-context.js';
|
|
4
4
|
export interface WidgetPreviewProviderProps {
|
|
5
5
|
widgetId: string;
|
|
6
6
|
title?: string;
|
|
@@ -17,6 +17,8 @@ export interface WidgetPreviewProviderProps {
|
|
|
17
17
|
askContext?: WidgetAskContext;
|
|
18
18
|
aiFooterEnabled?: boolean;
|
|
19
19
|
health?: WidgetHealth;
|
|
20
|
+
/** Whose data the widget shows; the header draws its logo. */
|
|
21
|
+
source?: WidgetSource;
|
|
20
22
|
children: ReactNode;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
@@ -38,4 +40,4 @@ export interface WidgetPreviewProviderProps {
|
|
|
38
40
|
* Flip `state` to walk every branch your widget renders (`'loading'`,
|
|
39
41
|
* `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.
|
|
40
42
|
*/
|
|
41
|
-
export declare function WidgetPreviewProvider({ widgetId, title, subtitle, description, className, state, isCollapsible, isPinnable, defaultCollapsed, defaultPinned, maxContentHeight, askContext, aiFooterEnabled, health, children, }: WidgetPreviewProviderProps): import("react").JSX.Element;
|
|
43
|
+
export declare function WidgetPreviewProvider({ widgetId, title, subtitle, description, className, state, isCollapsible, isPinnable, defaultCollapsed, defaultPinned, maxContentHeight, askContext, aiFooterEnabled, health, source, children, }: WidgetPreviewProviderProps): import("react").JSX.Element;
|
|
@@ -21,7 +21,7 @@ import { WidgetContext, } from './widget-context.js';
|
|
|
21
21
|
* Flip `state` to walk every branch your widget renders (`'loading'`,
|
|
22
22
|
* `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.
|
|
23
23
|
*/
|
|
24
|
-
export function WidgetPreviewProvider({ widgetId, title, subtitle, description, className, state = 'active', isCollapsible = false, isPinnable = false, defaultCollapsed = false, defaultPinned = false, maxContentHeight, askContext, aiFooterEnabled = false, health, children, }) {
|
|
24
|
+
export function WidgetPreviewProvider({ widgetId, title, subtitle, description, className, state = 'active', isCollapsible = false, isPinnable = false, defaultCollapsed = false, defaultPinned = false, maxContentHeight, askContext, aiFooterEnabled = false, health, source, children, }) {
|
|
25
25
|
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
26
26
|
const [pinned, setPinned] = useState(defaultPinned);
|
|
27
27
|
const toggleCollapsed = useCallback(() => setCollapsed((previous) => !previous), []);
|
|
@@ -47,6 +47,7 @@ export function WidgetPreviewProvider({ widgetId, title, subtitle, description,
|
|
|
47
47
|
askContext,
|
|
48
48
|
aiFooterEnabled,
|
|
49
49
|
health,
|
|
50
|
+
source,
|
|
50
51
|
},
|
|
51
52
|
}), [
|
|
52
53
|
aiFooterEnabled,
|
|
@@ -59,6 +60,7 @@ export function WidgetPreviewProvider({ widgetId, title, subtitle, description,
|
|
|
59
60
|
isPinnable,
|
|
60
61
|
maxContentHeight,
|
|
61
62
|
pinned,
|
|
63
|
+
source,
|
|
62
64
|
state,
|
|
63
65
|
subtitle,
|
|
64
66
|
title,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-preview-provider.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-preview-provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvE,OAAO,EACL,aAAa,
|
|
1
|
+
{"version":3,"file":"widget-preview-provider.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-preview-provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvE,OAAO,EACL,aAAa,GAKd,MAAM,kBAAkB,CAAC;AAuB1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,GAAG,QAAQ,EAChB,aAAa,GAAG,KAAK,EACrB,UAAU,GAAG,KAAK,EAClB,gBAAgB,GAAG,KAAK,EACxB,aAAa,GAAG,KAAK,EACrB,gBAAgB,EAChB,UAAU,EACV,eAAe,GAAG,KAAK,EACvB,MAAM,EACN,MAAM,EACN,QAAQ,GACmB;IAC3B,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC7D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IAEpD,MAAM,eAAe,GAAG,WAAW,CACjC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC3C,EAAE,CACH,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAC9B,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EACxC,EAAE,CACH,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,KAAK,EAAE;YACL,KAAK;YACL,SAAS;YACT,MAAM;YACN,eAAe,EAAE,KAAK;YACtB,UAAU,EAAE,KAAK;SAClB;QACD,OAAO,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE;QAC1C,IAAI,EAAE;YACJ,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,WAAW;YACX,SAAS;YACT,gBAAgB;YAChB,aAAa;YACb,UAAU;YACV,UAAU;YACV,eAAe;YACf,MAAM;YACN,MAAM;SACP;KACF,CAAC,EACF;QACE,eAAe;QACf,UAAU;QACV,SAAS;QACT,SAAS;QACT,WAAW;QACX,MAAM;QACN,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,MAAM;QACN,MAAM;QACN,KAAK;QACL,QAAQ;QACR,KAAK;QACL,eAAe;QACf,YAAY;QACZ,QAAQ;KACT,CACF,CAAC;IAEF,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAiB,CAAC;AACjE,CAAC","sourcesContent":["'use client';\n\nimport { type ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport {\n WidgetContext,\n type WidgetContextValue,\n type WidgetHealth,\n type WidgetRenderState,\n type WidgetSource,\n} from './widget-context';\n\nexport interface WidgetPreviewProviderProps {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n className?: string;\n /** Defaults to `'active'` — the state you almost always want to look at. */\n state?: WidgetRenderState;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n defaultCollapsed?: boolean;\n defaultPinned?: boolean;\n maxContentHeight?: string;\n askContext?: WidgetAskContext;\n aiFooterEnabled?: boolean;\n health?: WidgetHealth;\n /** Whose data the widget shows; the header draws its logo. */\n source?: WidgetSource;\n children: ReactNode;\n}\n\n/**\n * Renders a widget outside the Ekanos dashboard.\n *\n * `Widget.*` reads everything — render state, collapse/pin, title — from\n * `WidgetContext`, which in production is filled by the host's\n * `DashboardWidgetProvider` (config hierarchy, persistence callbacks, health\n * resolution). None of that exists in a partner's own app, a Storybook story,\n * or a component test, and without SOME provider every `Widget.*` throws. This\n * is that provider: same context value, local state only, nothing persisted.\n *\n * ```tsx\n * <WidgetPreviewProvider widgetId=\"my-widget\" title=\"My widget\">\n * <MyWidget accountId={accountId} productSlug=\"my-integration\" />\n * </WidgetPreviewProvider>\n * ```\n *\n * Flip `state` to walk every branch your widget renders (`'loading'`,\n * `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.\n */\nexport function WidgetPreviewProvider({\n widgetId,\n title,\n subtitle,\n description,\n className,\n state = 'active',\n isCollapsible = false,\n isPinnable = false,\n defaultCollapsed = false,\n defaultPinned = false,\n maxContentHeight,\n askContext,\n aiFooterEnabled = false,\n health,\n source,\n children,\n}: WidgetPreviewProviderProps) {\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const [pinned, setPinned] = useState(defaultPinned);\n\n const toggleCollapsed = useCallback(\n () => setCollapsed((previous) => !previous),\n [],\n );\n\n const togglePinned = useCallback(\n () => setPinned((previous) => !previous),\n [],\n );\n\n const value = useMemo<WidgetContextValue>(\n () => ({\n state: {\n state,\n collapsed,\n pinned,\n pendingCollapse: false,\n pendingPin: false,\n },\n actions: { toggleCollapsed, togglePinned },\n meta: {\n widgetId,\n title,\n subtitle,\n description,\n className,\n maxContentHeight,\n isCollapsible,\n isPinnable,\n askContext,\n aiFooterEnabled,\n health,\n source,\n },\n }),\n [\n aiFooterEnabled,\n askContext,\n className,\n collapsed,\n description,\n health,\n isCollapsible,\n isPinnable,\n maxContentHeight,\n pinned,\n source,\n state,\n subtitle,\n title,\n toggleCollapsed,\n togglePinned,\n widgetId,\n ],\n );\n\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n"]}
|
|
@@ -1,8 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The one action a state view offers: "Connect QuickBooks", "Ask an admin",
|
|
4
|
+
* "Create a deal". A link with `href`, otherwise a button running `onClick`.
|
|
5
|
+
*/
|
|
6
|
+
export interface WidgetStateAction {
|
|
7
|
+
label: ReactNode;
|
|
8
|
+
href?: string;
|
|
9
|
+
/** Open `href` in a new tab. */
|
|
10
|
+
external?: boolean;
|
|
11
|
+
onClick?: () => void;
|
|
12
|
+
}
|
|
13
|
+
/** The body shape a loading skeleton stands in for. */
|
|
14
|
+
export type WidgetSkeletonVariant = 'rows' | 'stat' | 'chart' | 'schedule';
|
|
15
|
+
/** Why an active integration still can't show this widget's data. */
|
|
16
|
+
export type WidgetInactiveReason = 'not-connected' | 'no-access' | 'setup-needed';
|
|
17
|
+
export interface WidgetEmptyStateProps {
|
|
18
|
+
/**
|
|
19
|
+
* `positive` (the default) says nothing needs the owner: "You're all caught
|
|
20
|
+
* up". `neutral` is for an empty list that isn't good news, like a search
|
|
21
|
+
* with no results.
|
|
22
|
+
*/
|
|
23
|
+
tone?: 'positive' | 'neutral';
|
|
24
|
+
title?: ReactNode;
|
|
25
|
+
description?: ReactNode;
|
|
26
|
+
action?: WidgetStateAction;
|
|
27
|
+
}
|
|
28
|
+
export interface WidgetInactiveStateProps {
|
|
29
|
+
reason?: WidgetInactiveReason;
|
|
30
|
+
/** The integration's name, for the default copy ("Connect QuickBooks"). */
|
|
31
|
+
integrationName?: string;
|
|
32
|
+
title?: ReactNode;
|
|
33
|
+
/** One sentence on what connecting or access gives the owner. */
|
|
34
|
+
description?: ReactNode;
|
|
35
|
+
action?: WidgetStateAction;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The empty state for a widget with nothing to show. Positive by default:
|
|
39
|
+
* an empty review queue is good news, so it reassures instead of apologizing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function WidgetEmptyState({ tone, title, description, action, }: WidgetEmptyStateProps): import("react").JSX.Element;
|
|
42
|
+
/**
|
|
43
|
+
* Why the widget can't show data. `not-connected` offers one Connect button;
|
|
44
|
+
* `no-access` is a calm note that points to an admin, never a lock screen;
|
|
45
|
+
* `setup-needed` points to the integration's settings. Without a `reason` it
|
|
46
|
+
* keeps the original one-line "Widget inactive".
|
|
47
|
+
*/
|
|
48
|
+
export declare function WidgetInactiveState({ reason, integrationName, title, description, action, }?: WidgetInactiveStateProps): import("react").JSX.Element;
|
|
49
|
+
/**
|
|
50
|
+
* Loading, drawn as the body it stands in for so the card doesn't jump when
|
|
51
|
+
* data lands. Rows suit lists and feeds, `stat` a headline figure, `chart` a
|
|
52
|
+
* chart, `schedule` a day of events.
|
|
53
|
+
*/
|
|
54
|
+
export declare function WidgetLoadingSkeleton({ variant, }: {
|
|
55
|
+
variant?: WidgetSkeletonVariant;
|
|
56
|
+
}): import("react").JSX.Element;
|
|
57
|
+
export declare function WidgetErrorState({ title, description, onRetry, retrying, }: {
|
|
58
|
+
title?: ReactNode;
|
|
59
|
+
description?: ReactNode;
|
|
3
60
|
onRetry?: () => void;
|
|
4
61
|
retrying?: boolean;
|
|
5
62
|
}): import("react").JSX.Element;
|
|
6
|
-
export declare function WidgetInactiveState(): import("react").JSX.Element;
|
|
7
63
|
export declare function WidgetDataLoading(): import("react").JSX.Element;
|
|
8
64
|
export declare function WidgetDisabledState(): import("react").JSX.Element;
|