@ekanos/sdk 0.7.1 → 0.9.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.
Files changed (35) hide show
  1. package/README.md +37 -6
  2. package/api-report.md +25 -2
  3. package/dist/components/base-activation-dialog.js +2 -2
  4. package/dist/components/base-activation-dialog.js.map +1 -1
  5. package/dist/components/base-activation-form.js +2 -2
  6. package/dist/components/base-activation-form.js.map +1 -1
  7. package/dist/components/base-marketplace-tile.js +4 -2
  8. package/dist/components/base-marketplace-tile.js.map +1 -1
  9. package/dist/components/widgets/ai-prompt-chip.js +4 -2
  10. package/dist/components/widgets/ai-prompt-chip.js.map +1 -1
  11. package/dist/components/widgets/widget-ask-bar.js +3 -1
  12. package/dist/components/widgets/widget-ask-bar.js.map +1 -1
  13. package/dist/components/widgets/widget-state-components.js +4 -4
  14. package/dist/components/widgets/widget-state-components.js.map +1 -1
  15. package/dist/components/widgets/widget.d.ts +1 -6
  16. package/dist/components/widgets/widget.js +8 -15
  17. package/dist/components/widgets/widget.js.map +1 -1
  18. package/dist/hooks/partner-oauth.js +4 -1
  19. package/dist/hooks/partner-oauth.js.map +1 -1
  20. package/dist/integration/define-agent-event.d.ts +18 -0
  21. package/dist/integration/define-agent-event.js +20 -0
  22. package/dist/integration/define-agent-event.js.map +1 -0
  23. package/dist/integration/event-surfaces.d.ts +5 -2
  24. package/dist/integration/event-surfaces.js +36 -9
  25. package/dist/integration/event-surfaces.js.map +1 -1
  26. package/dist/integration/index.d.ts +5 -2
  27. package/dist/integration/index.js +7 -1
  28. package/dist/integration/index.js.map +1 -1
  29. package/dist/types/integration.d.ts +3 -3
  30. package/dist/types/integration.js.map +1 -1
  31. package/dist/types/workspace-target.d.ts +6 -0
  32. package/dist/types/workspace-target.js.map +1 -1
  33. package/eslint.design-preset.mjs +7 -5
  34. package/eslint.design-rules.mjs +60 -3
  35. package/package.json +5 -4
package/README.md CHANGED
@@ -1230,12 +1230,41 @@ What the host does with it:
1230
1230
 
1231
1231
  ### `workspaceTargets`
1232
1232
 
1233
- Only `kind: 'integration'` packages declare workspace targets — the named
1234
- dashboard sections an integration ships with. This is a first-party surface
1235
- today: composition is admin-curated, and a partner integration's widgets are
1236
- placed individually by an admin. The field is validated if you set it (the
1237
- `WorkspaceTargetDefinition` type is exported from the root entrypoint), but you
1238
- almost certainly should not set it.
1233
+ The named dashboard workspaces your integration ships with — the sidebar
1234
+ section its widgets live in.
1235
+
1236
+ **If you declare `components.widgets`, you must declare at least one workspace
1237
+ target.** The admission gate rejects a submission that declares widgets with an
1238
+ empty or absent `workspaceTargets` (`widget-workspace/no-home`, an error-level
1239
+ finding). The reason is mechanical rather than stylistic: a widget reaches a
1240
+ dashboard through your integration's *inheriting* workspace attachment, which
1241
+ the host builds from a declared target. With no target your integration is
1242
+ absent from the placement map, so every widget you declared renders nowhere
1243
+ until an administrator places it by hand into somebody else's workspace.
1244
+
1245
+ One target is enough. `slug` (kebab-case — it is the `/workspace/<slug>` URL
1246
+ segment) and `name` (what the sidebar shows) are required. `inheritsWidgets:
1247
+ true` is the usual choice for a canvas-only integration: it is what makes this
1248
+ workspace receive the widgets you declared. `icon`, `description` and `layout`
1249
+ (`'masonry'` — the default — or `'two-column'`) are optional.
1250
+
1251
+ ```ts
1252
+ workspaceTargets: [
1253
+ {
1254
+ slug: 'acme-payments',
1255
+ name: 'Payments',
1256
+ description: 'Settlements, refunds and anything needing attention.',
1257
+ inheritsWidgets: true,
1258
+ },
1259
+ ],
1260
+ ```
1261
+
1262
+ At most one target may set `inheritsWidgets`, and a target that sets
1263
+ `includedWidgetIds` (limiting itself to named widgets) may not also inherit.
1264
+ The `WorkspaceTargetDefinition` type is exported from the root entrypoint.
1265
+
1266
+ Declaring no widgets at all is fine, and then `workspaceTargets` is optional
1267
+ too. What is not accepted is widgets with nowhere to put them.
1239
1268
 
1240
1269
  ## Testing
1241
1270
 
@@ -1391,6 +1420,8 @@ Awesome") for the Free-vs-Pro gap and how to size and re-family icons.
1391
1420
  | `slug` | kebab-case, `^[a-z0-9]+(-[a-z0-9]+)*$` | `acme-payments` |
1392
1421
  | `version` | semver, prerelease/build suffix allowed | `1.0.0`, `2.1.0-beta.3` |
1393
1422
  | `components.widgets[].id` | kebab-case, **globally unique** — prefix with your slug | `acme-payments-summary` |
1423
+ | `components.widgets` | if non-empty, `workspaceTargets` must declare at least one target | — |
1424
+ | `workspaceTargets[].slug` | kebab-case, the `/workspace/<slug>` URL segment | `acme-payments` |
1394
1425
  | `tools[].name` | `^[a-z][a-z0-9_]*$` — lowercase snake_case | `list_recent_payments` |
1395
1426
  | `webhooks[].id` | kebab-case | `payment-updated` |
1396
1427
  | `schedules[].id` | kebab-case | `daily-reconcile` |
package/api-report.md CHANGED
@@ -9,7 +9,7 @@ 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: **212** across 8 entrypoints.
12
+ Total exported names: **235** across 8 entrypoints.
13
13
 
14
14
  ## `@ekanos/sdk` (35 exports)
15
15
 
@@ -210,13 +210,22 @@ Total exported names: **212** across 8 entrypoints.
210
210
  | `verifyDeclaredWebhookSignature` | function |
211
211
  | `verifyWebhookSignature` | function |
212
212
 
213
- ## `@ekanos/sdk/integration` (41 exports)
213
+ ## `@ekanos/sdk/integration` (64 exports)
214
214
 
215
215
  | Export | Kind |
216
216
  | --- | --- |
217
217
  | `ADMIN_HIERARCHY_LEVEL` | variable |
218
+ | `AgentChannelDeliveryResult` | type alias |
219
+ | `AgentChannelReply` | interface |
220
+ | `AgentChannelRun` | interface |
221
+ | `AgentChannelRunOutcome` | interface |
222
+ | `AgentConversation` | interface |
223
+ | `AgentEventDeclaration` | interface |
224
+ | `AgentTriggerFilter` | type alias |
225
+ | `AgentTriggerFilterField` | type alias |
218
226
  | `AttentionHandler` | type alias |
219
227
  | `AttentionSignal` | interface |
228
+ | `DEFAULT_OAUTH_CONNECTION_ID` | variable |
220
229
  | `DEFAULT_SIGNATURE_TOLERANCE_SECONDS` | variable |
221
230
  | `DEFAULT_UNTRUSTED_TEXT_MAX_CHARS` | variable |
222
231
  | `DefinitionCollisionInput` | interface |
@@ -227,12 +236,16 @@ Total exported names: **212** across 8 entrypoints.
227
236
  | `IntegrationDefinitionSchema` | variable |
228
237
  | `IntegrationPermissionDeclaration` | type alias |
229
238
  | `IntegrationProposals` | type alias |
239
+ | `MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH` | variable |
240
+ | `MAX_RAISED_AGENT_EVENTS` | variable |
230
241
  | `OAuthProviderDeclaration` | interface |
231
242
  | `OAuthTokens` | interface |
232
243
  | `OWNER_HIERARCHY_LEVEL` | variable |
233
244
  | `OnActivateHandler` | type alias |
234
245
  | `PARTNER_STORAGE_LIMITS` | variable |
235
246
  | `PartnerActionDeclaration` | interface |
247
+ | `PartnerAgentChannelDeclaration` | interface |
248
+ | `PartnerOAuthConnectionDeclaration` | interface |
236
249
  | `PartnerOAuthDeclaration` | interface |
237
250
  | `PartnerScheduleDeclaration` | interface |
238
251
  | `PartnerToolModule` | interface |
@@ -240,8 +253,13 @@ Total exported names: **212** across 8 entrypoints.
240
253
  | `PartnerWebhookDeclaration` | interface |
241
254
  | `PartnerWidgetDeclaration` | type alias |
242
255
  | `PublicActionError` | class |
256
+ | `RESERVED_OAUTH_AUTHORIZATION_PARAMS` | variable |
257
+ | `RaisedAgentEvent` | interface |
258
+ | `ResolveOAuthProviderResult` | type alias |
259
+ | `ResolveOAuthUrlResult` | type alias |
243
260
  | `ScheduleInvocation` | interface |
244
261
  | `ScheduleResult` | interface |
262
+ | `TeamAppDeclaration` | interface |
245
263
  | `ToolClassificationProposal` | type alias |
246
264
  | `WebhookEvent` | interface |
247
265
  | `WebhookResult` | interface |
@@ -250,8 +268,13 @@ Total exported names: **212** across 8 entrypoints.
250
268
  | `WidgetVerdict` | interface |
251
269
  | `WidgetVerdictTone` | type alias |
252
270
  | `actorIsAtLeast` | function |
271
+ | `defineAgentEvent` | function |
253
272
  | `defineIntegration` | function |
273
+ | `normalizeOAuthUrlValue` | function |
274
+ | `oauthProviderPlaceholders` | function |
254
275
  | `parseCronExpression` | function |
276
+ | `resolveOAuthProvider` | function |
277
+ | `resolveOAuthUrl` | function |
255
278
  | `untrustedText` | function |
256
279
  | `untrustedTextNote` | function |
257
280
  | `validateIntegrationDefinitions` | function |
@@ -10,13 +10,13 @@ import { Separator } from '@ekanos/ui/separator';
10
10
  import { Trans } from '@ekanos/ui/trans';
11
11
  import { cn } from '@ekanos/ui/utils';
12
12
  function SuccessContent({ successTitle = 'Integration Activated!', successMessage = 'Your integration is now active and ready to use.', onClose, showSeparator = false, }) {
13
- return (_jsxs("div", { className: "flex flex-col items-center space-y-4 py-6", children: [_jsx("div", { className: "bg-success/10 flex h-16 w-16 items-center justify-center rounded-full", children: _jsx(Icon, { name: "fa-light fa-circle-check", className: "text-success h-8 w-8" }) }), _jsxs("div", { className: "text-center", children: [_jsx("h3", { className: "text-lg font-semibold", children: successTitle }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: successMessage })] }), showSeparator && _jsx(Separator, {}), _jsxs("div", { className: "flex gap-2", children: [showSeparator && (_jsx(Button, { variant: "outline", onClick: onClose, children: _jsx(Trans, { i18nKey: "common:stayOnMarketplace", defaults: "Stay on Marketplace" }) })), _jsx(Button, { onClick: onClose, children: _jsx(Trans, { i18nKey: "common:continue", defaults: "Continue" }) })] })] }));
13
+ return (_jsxs("div", { className: "flex flex-col items-center space-y-4 py-6", children: [_jsx("div", { className: "bg-success/10 flex h-16 w-16 items-center justify-center rounded-full", children: _jsx(Icon, { name: "fa-light fa-circle-check", className: "text-success h-8 w-8" }) }), _jsxs("div", { className: "text-center", children: [_jsx("h3", { className: "text-subhead", children: successTitle }), _jsx("p", { className: "text-muted-foreground text-meta mt-1", children: successMessage })] }), showSeparator && _jsx(Separator, {}), _jsxs("div", { className: "flex gap-2", children: [showSeparator && (_jsx(Button, { variant: "outline", onClick: onClose, children: _jsx(Trans, { i18nKey: "common:stayOnMarketplace", defaults: "Stay on Marketplace" }) })), _jsx(Button, { onClick: onClose, children: _jsx(Trans, { i18nKey: "common:continue", defaults: "Continue" }) })] })] }));
14
14
  }
15
15
  function ErrorAlert({ error }) {
16
16
  return (_jsx(Alert, { variant: "destructive", role: "alert", children: _jsx(AlertDescription, { children: error }) }));
17
17
  }
18
18
  function LoadingOverlay() {
19
- return (_jsx("div", { className: "bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center", children: _jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(Icon, { name: "fa-light fa-spinner-third", className: "text-primary h-8 w-8 animate-spin" }), _jsx("span", { className: "text-muted-foreground text-sm", children: "Loading..." })] }) }));
19
+ return (_jsx("div", { className: "bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center", children: _jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(Icon, { name: "fa-light fa-spinner-third", className: "text-primary h-8 w-8 animate-spin" }), _jsx("span", { className: "text-muted-foreground text-meta", children: "Loading..." })] }) }));
20
20
  }
21
21
  export function BaseActivationDialog({ isOpen, onClose, title, description, children, isLoading = false, error = null, success = false, successTitle, successMessage, onSuccessClose, maxWidth = 'sm:max-w-[600px]', inline = false, }) {
22
22
  const handleClose = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"base-activation-dialog.js","sourceRoot":"","sources":["../../src/components/base-activation-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { ReactNode, useEffect, useRef } from 'react';\n\nimport { Alert, AlertDescription } from '@ekanos/ui/alert';\nimport { Button } from '@ekanos/ui/button';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@ekanos/ui/dialog';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { Separator } from '@ekanos/ui/separator';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\ninterface BaseActivationDialogProps {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onSuccessClose?: () => void;\n maxWidth?: string;\n /** @deprecated Use `ActivationInline` instead for inline rendering */\n inline?: boolean;\n}\n\nfunction SuccessContent({\n successTitle = 'Integration Activated!',\n successMessage = 'Your integration is now active and ready to use.',\n onClose,\n showSeparator = false,\n}: {\n successTitle?: string;\n successMessage?: string;\n onClose: () => void;\n showSeparator?: boolean;\n}) {\n return (\n <div className=\"flex flex-col items-center space-y-4 py-6\">\n <div className=\"bg-success/10 flex h-16 w-16 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-check\"\n className=\"text-success h-8 w-8\"\n />\n </div>\n <div className=\"text-center\">\n <h3 className=\"text-lg font-semibold\">{successTitle}</h3>\n <p className=\"text-muted-foreground mt-1 text-sm\">{successMessage}</p>\n </div>\n {showSeparator && <Separator />}\n <div className=\"flex gap-2\">\n {showSeparator && (\n <Button variant=\"outline\" onClick={onClose}>\n <Trans\n i18nKey=\"common:stayOnMarketplace\"\n defaults=\"Stay on Marketplace\"\n />\n </Button>\n )}\n <Button onClick={onClose}>\n <Trans i18nKey=\"common:continue\" defaults=\"Continue\" />\n </Button>\n </div>\n </div>\n );\n}\n\nfunction ErrorAlert({ error }: { error: string }) {\n return (\n <Alert variant=\"destructive\" role=\"alert\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n );\n}\n\nfunction LoadingOverlay() {\n return (\n <div className=\"bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center\">\n <div className=\"flex flex-col items-center gap-2\">\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"text-primary h-8 w-8 animate-spin\"\n />\n <span className=\"text-muted-foreground text-sm\">Loading...</span>\n </div>\n </div>\n );\n}\n\nexport function BaseActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onSuccessClose,\n maxWidth = 'sm:max-w-[600px]',\n inline = false,\n}: BaseActivationDialogProps) {\n const handleClose = () => {\n if (success && onSuccessClose) {\n onSuccessClose();\n } else {\n onClose();\n }\n };\n\n if (inline) {\n return (\n <ActivationInline\n isLoading={isLoading}\n error={error}\n success={success}\n onSuccessClose={success ? handleClose : undefined}\n >\n {children}\n </ActivationInline>\n );\n }\n\n return (\n <ActivationDialog\n isOpen={isOpen}\n onClose={handleClose}\n title={title}\n description={description}\n isLoading={isLoading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n maxWidth={maxWidth}\n >\n {children}\n </ActivationDialog>\n );\n}\n\n// --- Explicit Variants ---\n\n/**\n * The form's body when it sits inside chrome the HOST owns (the connect\n * dialog, the workspace notice, first-use). The host already shows the\n * integration's name, what it does and what it can access, and it owns\n * the success moment (it closes and toasts) — so this renders the fields,\n * the error, and nothing else.\n *\n * `success` is a hand-off, not a screen: the moment it turns true,\n * `onSuccessClose` runs once, which is what lets a form written for the\n * old \"Continue\" panel finish the same way as one that reports success\n * directly. Nothing renders after it fires; the host takes over.\n */\nexport function ActivationInline({\n isLoading = false,\n error = null,\n success = false,\n onSuccessClose,\n children,\n}: {\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n onSuccessClose?: () => void;\n children: ReactNode;\n}) {\n const handedOff = useRef(false);\n\n // Hands the success state to the host exactly once. An effect is the\n // right tool here: this reacts to a state transition that the form's own\n // submit handler set, and the host callback it calls navigates.\n useEffect(() => {\n if (!success || !onSuccessClose || handedOff.current) return;\n\n handedOff.current = true;\n onSuccessClose();\n }, [success, onSuccessClose]);\n\n if (success) {\n return null;\n }\n\n return (\n <div\n className={cn('flex flex-col gap-4', isLoading && 'opacity-70')}\n aria-busy={isLoading}\n >\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n {children}\n </div>\n );\n}\n\nexport function ActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n maxWidth = 'sm:max-w-[600px]',\n}: {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n maxWidth?: string;\n}) {\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>\n <DialogContent className={`${maxWidth} max-h-[80vh] overflow-y-auto`}>\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n\n <If condition={success} fallback={children}>\n <SuccessContent\n successTitle={successTitle}\n successMessage={successMessage}\n onClose={onClose}\n showSeparator\n />\n </If>\n\n {isLoading && <LoadingOverlay />}\n </DialogContent>\n </Dialog>\n );\n}\n"]}
1
+ {"version":3,"file":"base-activation-dialog.js","sourceRoot":"","sources":["../../src/components/base-activation-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { ReactNode, useEffect, useRef } from 'react';\n\nimport { Alert, AlertDescription } from '@ekanos/ui/alert';\nimport { Button } from '@ekanos/ui/button';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@ekanos/ui/dialog';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { Separator } from '@ekanos/ui/separator';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\ninterface BaseActivationDialogProps {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onSuccessClose?: () => void;\n maxWidth?: string;\n /** @deprecated Use `ActivationInline` instead for inline rendering */\n inline?: boolean;\n}\n\nfunction SuccessContent({\n successTitle = 'Integration Activated!',\n successMessage = 'Your integration is now active and ready to use.',\n onClose,\n showSeparator = false,\n}: {\n successTitle?: string;\n successMessage?: string;\n onClose: () => void;\n showSeparator?: boolean;\n}) {\n return (\n <div className=\"flex flex-col items-center space-y-4 py-6\">\n <div className=\"bg-success/10 flex h-16 w-16 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-check\"\n className=\"text-success h-8 w-8\"\n />\n </div>\n <div className=\"text-center\">\n <h3 className=\"text-subhead\">{successTitle}</h3>\n <p className=\"text-muted-foreground text-meta mt-1\">{successMessage}</p>\n </div>\n {showSeparator && <Separator />}\n <div className=\"flex gap-2\">\n {showSeparator && (\n <Button variant=\"outline\" onClick={onClose}>\n <Trans\n i18nKey=\"common:stayOnMarketplace\"\n defaults=\"Stay on Marketplace\"\n />\n </Button>\n )}\n <Button onClick={onClose}>\n <Trans i18nKey=\"common:continue\" defaults=\"Continue\" />\n </Button>\n </div>\n </div>\n );\n}\n\nfunction ErrorAlert({ error }: { error: string }) {\n return (\n <Alert variant=\"destructive\" role=\"alert\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n );\n}\n\nfunction LoadingOverlay() {\n return (\n <div className=\"bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center\">\n <div className=\"flex flex-col items-center gap-2\">\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"text-primary h-8 w-8 animate-spin\"\n />\n <span className=\"text-muted-foreground text-meta\">Loading...</span>\n </div>\n </div>\n );\n}\n\nexport function BaseActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onSuccessClose,\n maxWidth = 'sm:max-w-[600px]',\n inline = false,\n}: BaseActivationDialogProps) {\n const handleClose = () => {\n if (success && onSuccessClose) {\n onSuccessClose();\n } else {\n onClose();\n }\n };\n\n if (inline) {\n return (\n <ActivationInline\n isLoading={isLoading}\n error={error}\n success={success}\n onSuccessClose={success ? handleClose : undefined}\n >\n {children}\n </ActivationInline>\n );\n }\n\n return (\n <ActivationDialog\n isOpen={isOpen}\n onClose={handleClose}\n title={title}\n description={description}\n isLoading={isLoading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n maxWidth={maxWidth}\n >\n {children}\n </ActivationDialog>\n );\n}\n\n// --- Explicit Variants ---\n\n/**\n * The form's body when it sits inside chrome the HOST owns (the connect\n * dialog, the workspace notice, first-use). The host already shows the\n * integration's name, what it does and what it can access, and it owns\n * the success moment (it closes and toasts) — so this renders the fields,\n * the error, and nothing else.\n *\n * `success` is a hand-off, not a screen: the moment it turns true,\n * `onSuccessClose` runs once, which is what lets a form written for the\n * old \"Continue\" panel finish the same way as one that reports success\n * directly. Nothing renders after it fires; the host takes over.\n */\nexport function ActivationInline({\n isLoading = false,\n error = null,\n success = false,\n onSuccessClose,\n children,\n}: {\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n onSuccessClose?: () => void;\n children: ReactNode;\n}) {\n const handedOff = useRef(false);\n\n // Hands the success state to the host exactly once. An effect is the\n // right tool here: this reacts to a state transition that the form's own\n // submit handler set, and the host callback it calls navigates.\n useEffect(() => {\n if (!success || !onSuccessClose || handedOff.current) return;\n\n handedOff.current = true;\n onSuccessClose();\n }, [success, onSuccessClose]);\n\n if (success) {\n return null;\n }\n\n return (\n <div\n className={cn('flex flex-col gap-4', isLoading && 'opacity-70')}\n aria-busy={isLoading}\n >\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n {children}\n </div>\n );\n}\n\nexport function ActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n maxWidth = 'sm:max-w-[600px]',\n}: {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n maxWidth?: string;\n}) {\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>\n <DialogContent className={`${maxWidth} max-h-[80vh] overflow-y-auto`}>\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n\n <If condition={success} fallback={children}>\n <SuccessContent\n successTitle={successTitle}\n successMessage={successMessage}\n onClose={onClose}\n showSeparator\n />\n </If>\n\n {isLoading && <LoadingOverlay />}\n </DialogContent>\n </Dialog>\n );\n}\n"]}
@@ -21,8 +21,8 @@ export function BaseActivationForm({ isOpen, onClose, title, description, form,
21
21
  const formContent = (_jsx(Form, Object.assign({}, form, { children: _jsxs("form", { onSubmit: form.handleSubmit(handleSubmit), className: "flex flex-col gap-5", "data-test": "activation-form", children: [hasFields ? (_jsx("fieldset", { className: "flex min-w-0 flex-col gap-5", disabled: loading, children: children })) : null, _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: hasFields
22
22
  ? 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end'
23
23
  : 'flex flex-col', children: [inline && showCancelButton && hasFields ? (_jsx(Button, { type: "button", variant: "ghost", onClick: onClose, disabled: loading, "data-test": "activation-cancel", children: _jsx(Trans, { i18nKey: "common:cancel", defaults: "Cancel" }) })) : null, _jsx(Button, { type: "submit", size: hasFields ? 'default' : 'lg', className: hasFields ? undefined : 'w-full', disabled: loading || submitButtonDisabled, "data-test": "activation-submit", children: loading ? (_jsxs(_Fragment, { children: [_jsx(Icon, { name: "fa-light fa-spinner-third", className: "h-4 w-4 animate-spin" }), submitButtonLoadingText !== null && submitButtonLoadingText !== void 0 ? submitButtonLoadingText : (_jsx(Trans, { i18nKey: "common:integrationConnect.connecting", defaults: "Connecting\u2026" }))] })) : ((submitButtonText !== null && submitButtonText !== void 0 ? submitButtonText : (_jsx(Trans, { i18nKey: "common:integrationConnect.connect", defaults: "Connect" })))) })] }), footnote ? (_jsx("p", { className: hasFields
24
- ? 'text-muted-foreground text-sm sm:text-right'
25
- : 'text-muted-foreground text-center text-sm', children: footnote })) : null] })] }) })));
24
+ ? 'text-muted-foreground text-meta sm:text-right'
25
+ : 'text-muted-foreground text-meta text-center', children: footnote })) : null] })] }) })));
26
26
  return (_jsx(BaseActivationDialog, { isOpen: isOpen, onClose: onClose, title: title, description: description, isLoading: loading, error: error, success: success, successTitle: successTitle, successMessage: successMessage, onSuccessClose: onSuccessClose, maxWidth: maxWidth, inline: inline, children: formContent }));
27
27
  }
28
28
  //# sourceMappingURL=base-activation-form.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-activation-form.js","sourceRoot":"","sources":["../../src/components/base-activation-form.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { ReactNode, useTransition } from 'react';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Form } from '@ekanos/ui/form';\nimport { Icon } from '@ekanos/ui/icon';\nimport { Trans } from '@ekanos/ui/trans';\nimport { FieldValues, UseFormReturn } from 'react-hook-form';\n\nimport { BaseActivationDialog } from './base-activation-dialog';\n\nexport interface BaseActivationFormProps<\n TFieldValues extends FieldValues = FieldValues,\n> {\n isOpen: boolean;\n onClose: () => void;\n /** Shown only when NOT inline. Inline, the host names the integration. */\n title: string;\n /** Shown only when NOT inline. */\n description: string;\n /**\n * Generic rather than `UseFormReturn<any>`: with current react-hook-form\n * typings a concrete `UseFormReturn<T>` is NOT assignable to\n * `UseFormReturn<any>` (the `control._options.validate` parameter is\n * checked contravariantly), which broke every out-of-repo consumer — the\n * clean-room pack test caught it. Inference from the `form` prop keeps\n * every call site source-compatible.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n form: UseFormReturn<TFieldValues, any, any>;\n onSubmit: (data: TFieldValues) => Promise<void> | void;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onSuccessClose?: () => void;\n /** Defaults to \"Connect\" — the one verb every integration shares. */\n submitButtonText?: ReactNode;\n /** Defaults to \"Connecting…\". */\n submitButtonLoadingText?: ReactNode;\n submitButtonDisabled?: boolean;\n /**\n * Inline with fields, a Cancel button sits beside Connect and calls\n * `onClose`; pass `false` for a surface that supplies its own way out.\n * A form with no fields never shows one — its single button spans the\n * frame and the surrounding dialog's close is the way out.\n */\n showCancelButton?: boolean;\n /** @deprecated The action row is right-aligned; this is ignored. */\n centerSubmitButton?: boolean;\n /**\n * One quiet line under the action row — what happens next (\"You'll sign\n * in on QuickBooks' site and come back here.\"). Nothing else belongs\n * there: not features, not permissions, the host shows those.\n */\n footnote?: ReactNode;\n maxWidth?: string;\n /** Render inline instead of in a dialog */\n inline?: boolean;\n}\n\nexport function BaseActivationForm<\n TFieldValues extends FieldValues = FieldValues,\n>({\n isOpen,\n onClose,\n title,\n description,\n form,\n onSubmit,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onSuccessClose,\n submitButtonText,\n submitButtonLoadingText,\n submitButtonDisabled = false,\n showCancelButton = true,\n footnote,\n maxWidth,\n inline = false,\n}: BaseActivationFormProps<TFieldValues>) {\n const [pending, startTransition] = useTransition();\n const loading = pending || isLoading;\n\n const handleSubmit = (data: TFieldValues) => {\n startTransition(async () => {\n await onSubmit(data);\n });\n };\n\n // `children` is null for a form with nothing to fill in (an OAuth button,\n // a one-click connect), and the action row is then the whole form —\n // no empty field block above it.\n const hasFields = children !== null && children !== undefined;\n\n const formContent = (\n <Form {...form}>\n <form\n onSubmit={form.handleSubmit(handleSubmit)}\n className=\"flex flex-col gap-5\"\n data-test=\"activation-form\"\n >\n {hasFields ? (\n <fieldset className=\"flex min-w-0 flex-col gap-5\" disabled={loading}>\n {children}\n </fieldset>\n ) : null}\n\n {/*\n Two shapes. With fields: Cancel + Connect, right-aligned, under\n what was filled in. Without (a sign-in button, a one-click\n connect): the button IS the form, so it spans the frame and needs\n no Cancel of its own — the dialog's close does that job.\n */}\n <div className=\"flex flex-col gap-2\">\n <div\n className={\n hasFields\n ? 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end'\n : 'flex flex-col'\n }\n >\n {inline && showCancelButton && hasFields ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={onClose}\n disabled={loading}\n data-test=\"activation-cancel\"\n >\n <Trans i18nKey=\"common:cancel\" defaults=\"Cancel\" />\n </Button>\n ) : null}\n\n <Button\n type=\"submit\"\n size={hasFields ? 'default' : 'lg'}\n className={hasFields ? undefined : 'w-full'}\n disabled={loading || submitButtonDisabled}\n data-test=\"activation-submit\"\n >\n {loading ? (\n <>\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"h-4 w-4 animate-spin\"\n />\n {submitButtonLoadingText ?? (\n <Trans\n i18nKey=\"common:integrationConnect.connecting\"\n defaults=\"Connecting…\"\n />\n )}\n </>\n ) : (\n (submitButtonText ?? (\n <Trans\n i18nKey=\"common:integrationConnect.connect\"\n defaults=\"Connect\"\n />\n ))\n )}\n </Button>\n </div>\n\n {footnote ? (\n <p\n className={\n hasFields\n ? 'text-muted-foreground text-sm sm:text-right'\n : 'text-muted-foreground text-center text-sm'\n }\n >\n {footnote}\n </p>\n ) : null}\n </div>\n </form>\n </Form>\n );\n\n return (\n <BaseActivationDialog\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n description={description}\n isLoading={loading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n onSuccessClose={onSuccessClose}\n maxWidth={maxWidth}\n inline={inline}\n >\n {formContent}\n </BaseActivationDialog>\n );\n}\n"]}
1
+ {"version":3,"file":"base-activation-form.js","sourceRoot":"","sources":["../../src/components/base-activation-form.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { ReactNode, useTransition } from 'react';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Form } from '@ekanos/ui/form';\nimport { Icon } from '@ekanos/ui/icon';\nimport { Trans } from '@ekanos/ui/trans';\nimport { FieldValues, UseFormReturn } from 'react-hook-form';\n\nimport { BaseActivationDialog } from './base-activation-dialog';\n\nexport interface BaseActivationFormProps<\n TFieldValues extends FieldValues = FieldValues,\n> {\n isOpen: boolean;\n onClose: () => void;\n /** Shown only when NOT inline. Inline, the host names the integration. */\n title: string;\n /** Shown only when NOT inline. */\n description: string;\n /**\n * Generic rather than `UseFormReturn<any>`: with current react-hook-form\n * typings a concrete `UseFormReturn<T>` is NOT assignable to\n * `UseFormReturn<any>` (the `control._options.validate` parameter is\n * checked contravariantly), which broke every out-of-repo consumer — the\n * clean-room pack test caught it. Inference from the `form` prop keeps\n * every call site source-compatible.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n form: UseFormReturn<TFieldValues, any, any>;\n onSubmit: (data: TFieldValues) => Promise<void> | void;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onSuccessClose?: () => void;\n /** Defaults to \"Connect\" — the one verb every integration shares. */\n submitButtonText?: ReactNode;\n /** Defaults to \"Connecting…\". */\n submitButtonLoadingText?: ReactNode;\n submitButtonDisabled?: boolean;\n /**\n * Inline with fields, a Cancel button sits beside Connect and calls\n * `onClose`; pass `false` for a surface that supplies its own way out.\n * A form with no fields never shows one — its single button spans the\n * frame and the surrounding dialog's close is the way out.\n */\n showCancelButton?: boolean;\n /** @deprecated The action row is right-aligned; this is ignored. */\n centerSubmitButton?: boolean;\n /**\n * One quiet line under the action row — what happens next (\"You'll sign\n * in on QuickBooks' site and come back here.\"). Nothing else belongs\n * there: not features, not permissions, the host shows those.\n */\n footnote?: ReactNode;\n maxWidth?: string;\n /** Render inline instead of in a dialog */\n inline?: boolean;\n}\n\nexport function BaseActivationForm<\n TFieldValues extends FieldValues = FieldValues,\n>({\n isOpen,\n onClose,\n title,\n description,\n form,\n onSubmit,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onSuccessClose,\n submitButtonText,\n submitButtonLoadingText,\n submitButtonDisabled = false,\n showCancelButton = true,\n footnote,\n maxWidth,\n inline = false,\n}: BaseActivationFormProps<TFieldValues>) {\n const [pending, startTransition] = useTransition();\n const loading = pending || isLoading;\n\n const handleSubmit = (data: TFieldValues) => {\n startTransition(async () => {\n await onSubmit(data);\n });\n };\n\n // `children` is null for a form with nothing to fill in (an OAuth button,\n // a one-click connect), and the action row is then the whole form —\n // no empty field block above it.\n const hasFields = children !== null && children !== undefined;\n\n const formContent = (\n <Form {...form}>\n <form\n onSubmit={form.handleSubmit(handleSubmit)}\n className=\"flex flex-col gap-5\"\n data-test=\"activation-form\"\n >\n {hasFields ? (\n <fieldset className=\"flex min-w-0 flex-col gap-5\" disabled={loading}>\n {children}\n </fieldset>\n ) : null}\n\n {/*\n Two shapes. With fields: Cancel + Connect, right-aligned, under\n what was filled in. Without (a sign-in button, a one-click\n connect): the button IS the form, so it spans the frame and needs\n no Cancel of its own — the dialog's close does that job.\n */}\n <div className=\"flex flex-col gap-2\">\n <div\n className={\n hasFields\n ? 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end'\n : 'flex flex-col'\n }\n >\n {inline && showCancelButton && hasFields ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={onClose}\n disabled={loading}\n data-test=\"activation-cancel\"\n >\n <Trans i18nKey=\"common:cancel\" defaults=\"Cancel\" />\n </Button>\n ) : null}\n\n <Button\n type=\"submit\"\n size={hasFields ? 'default' : 'lg'}\n className={hasFields ? undefined : 'w-full'}\n disabled={loading || submitButtonDisabled}\n data-test=\"activation-submit\"\n >\n {loading ? (\n <>\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"h-4 w-4 animate-spin\"\n />\n {submitButtonLoadingText ?? (\n <Trans\n i18nKey=\"common:integrationConnect.connecting\"\n defaults=\"Connecting…\"\n />\n )}\n </>\n ) : (\n (submitButtonText ?? (\n <Trans\n i18nKey=\"common:integrationConnect.connect\"\n defaults=\"Connect\"\n />\n ))\n )}\n </Button>\n </div>\n\n {footnote ? (\n <p\n className={\n hasFields\n ? 'text-muted-foreground text-meta sm:text-right'\n : 'text-muted-foreground text-meta text-center'\n }\n >\n {footnote}\n </p>\n ) : null}\n </div>\n </form>\n </Form>\n );\n\n return (\n <BaseActivationDialog\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n description={description}\n isLoading={loading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n onSuccessClose={onSuccessClose}\n maxWidth={maxWidth}\n inline={inline}\n >\n {formContent}\n </BaseActivationDialog>\n );\n}\n"]}
@@ -27,7 +27,7 @@ export function BaseMarketplaceTile(props) {
27
27
  if (target.nextSibling) {
28
28
  target.nextSibling.style.display = 'flex';
29
29
  }
30
- } }), _jsx("div", { className: "bg-border ring-border/50 hidden h-full w-full items-center justify-center rounded-lg text-2xl font-semibold ring-1", children: displayBrandName.charAt(0).toUpperCase() })] })) : (_jsx("div", { className: "bg-border ring-border/50 flex h-16 w-16 items-center justify-center rounded-lg border-2 text-2xl font-semibold ring-1", children: displayBrandName.charAt(0).toUpperCase() })) }), _jsx(CardTitle, { className: "line-clamp-2 text-xl leading-tight font-semibold", children: displayTitle }), _jsx(If, { condition: categories.length > 0, children: _jsxs("div", { className: "mt-2 flex flex-wrap justify-center gap-1", children: [categories.slice(0, 2).map((cat) => (_jsx(CategoryChip, { productColor: productColor, children: cat.name }, cat.id))), _jsx(If, { condition: categories.length > 2, children: _jsxs(CategoryChip, { productColor: productColor, children: ["+", categories.length - 2] }) })] }) })] }), _jsx(CardContent, { className: "flex-1 pb-8 text-center", children: _jsx(If, { condition: displaySummary, children: _jsx(CardDescription, { className: "text-muted-foreground line-clamp-2 text-base leading-relaxed", children: displaySummary }) }) }), _jsx(If, { condition: displayBrandName !== displayTitle, children: _jsx("p", { className: "text-muted-foreground/70 absolute right-3 bottom-3 text-sm", children: displayBrandName }) }), _jsx(If, { condition: !!ctaLink, children: _jsx("div", { className: "text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors", "aria-hidden": "true", children: _jsx(Icon, { name: "fa-light fa-arrow-up-right-from-square", className: "h-4 w-4" }) }) })] }));
30
+ } }), _jsx("div", { className: "bg-border ring-border/50 text-headline hidden h-full w-full items-center justify-center rounded-lg ring-1", children: displayBrandName.charAt(0).toUpperCase() })] })) : (_jsx("div", { className: "bg-border ring-border/50 text-headline flex h-16 w-16 items-center justify-center rounded-lg border-2 ring-1", children: displayBrandName.charAt(0).toUpperCase() })) }), _jsx(CardTitle, { className: "text-subhead-lg line-clamp-2 leading-tight", children: displayTitle }), _jsx(If, { condition: categories.length > 0, children: _jsxs("div", { className: "mt-2 flex flex-wrap justify-center gap-1", children: [categories.slice(0, 2).map((cat) => (_jsx(CategoryChip, { productColor: productColor, children: cat.name }, cat.id))), _jsx(If, { condition: categories.length > 2, children: _jsxs(CategoryChip, { productColor: productColor, children: ["+", categories.length - 2] }) })] }) })] }), _jsx(CardContent, { className: "flex-1 pb-8 text-center", children: _jsx(If, { condition: displaySummary, children: _jsx(CardDescription, { className: "text-muted-foreground text-body line-clamp-2 leading-relaxed", children: displaySummary }) }) }), _jsx(If, { condition: displayBrandName !== displayTitle, children: _jsx("p", { className: "text-muted-foreground/70 text-meta absolute right-3 bottom-3", children: displayBrandName }) }), _jsx(If, { condition: !!ctaLink, children: _jsx("div", { className: "text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors", "aria-hidden": "true", children: _jsx(Icon, { name: "fa-light fa-arrow-up-right-from-square", className: "h-4 w-4" }) }) })] }));
31
31
  // External link: opens in new tab
32
32
  if (ctaLink) {
33
33
  return (_jsx("a", { href: ctaLink, target: "_blank", rel: "noopener noreferrer", className: "block h-full", "aria-label": `${displayTitle} (opens in new tab)`, children: cardContent }));
@@ -46,7 +46,9 @@ export function BaseMarketplaceTile(props) {
46
46
  * its colour). The tint is that colour at 40% alpha.
47
47
  */
48
48
  function CategoryChip({ productColor, children, }) {
49
- return (_jsx("span", { className: cn('text-secondary-foreground inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap', productColor ? 'bg-(--product-color)/40' : 'bg-secondary'), style: productColor
49
+ return (_jsx("span", { className: cn(
50
+ // oxlint-disable-next-line design/no-raw-text-size -- category chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change
51
+ 'text-secondary-foreground inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap', productColor ? 'bg-(--product-color)/40' : 'bg-secondary'), style: productColor
50
52
  ? { '--product-color': productColor }
51
53
  : undefined, children: children }));
52
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"base-marketplace-tile.js","sourceRoot":"","sources":["../../src/components/base-marketplace-tile.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport type { CSSProperties, ReactNode } from 'react';\n\n// `next/link.js`, not `next/link`. `next` ships no `exports` map, so the\n// extensionless subpath only resolves under a bundler — Node's own ESM loader\n// throws ERR_MODULE_NOT_FOUND, and any partner running vitest or plain Node\n// against the published tarball loses this whole module. The `.js` form is not\n// a bypass of Next's resolution: `create-compiler-aliases.js` keys its aliases\n// on the RESOLVED FILE PATH (`<next>/link.js`, with the comment \"Handle fully\n// specified imports like `next/image.js`\"), so an App Router build still gets\n// `next/dist/client/app-dir/link`, exactly as `next/link` would. Next's dist\n// also ends with `Object.assign(exports.default, exports); module.exports =\n// exports.default`, so Node's CJS-default interop and a bundler's `__esModule`\n// interop land on the same component object. Do not \"tidy\" the extension away.\nimport Link from 'next/link.js';\n\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { MarketplaceTileProps } from '../types/integration';\n\n// Type for product category\nexport interface ProductCategory {\n id: string;\n name: string;\n slug: string;\n description?: string;\n}\n\nexport type BaseMarketplaceTileProps = MarketplaceTileProps & {\n /** The display name of the product */\n name: string;\n /** Full description of the product (used for detail pages) */\n description: string;\n /** URL-friendly identifier for the product */\n slug: string;\n /** Short summary text displayed on the tile (1-2 sentences) */\n summary?: string;\n /** Name of the company/brand that provides this product */\n brandName?: string;\n /** Path to the product logo image */\n logoPath?: string;\n /** Text for the call-to-action button */\n ctaText?: string;\n /** External URL for the CTA (opens in new tab when set) */\n ctaLink?: string;\n\n /** Array of categories this product belongs to */\n categories?: ProductCategory[];\n\n /** Hex color from product schema for tile theming */\n productColor?: string;\n /** Additional CSS classes to apply to the tile */\n className?: string;\n\n /** Internal navigation URL for the integration detail page */\n href?: string;\n};\n\nexport function BaseMarketplaceTile(props: BaseMarketplaceTileProps) {\n const {\n name,\n summary,\n brandName,\n logoPath,\n categories = [],\n productColor,\n className,\n href,\n ctaLink,\n } = props;\n\n const displayTitle = name || 'Unknown Product';\n const displaySummary = summary;\n const displayBrandName = brandName || displayTitle;\n\n const cardContent = (\n <Card\n className={cn(\n 'group bg-card relative flex h-full w-full cursor-pointer flex-col overflow-hidden border-0 pt-3 pb-2 shadow-sm transition-all duration-300 hover:shadow-md',\n className,\n )}\n >\n {/* Header: Centered Logo, Title, Categories */}\n <CardHeader className=\"flex flex-col items-center pt-6 pb-3 text-center\">\n {/* Logo */}\n <div className=\"mb-3\">\n {logoPath ? (\n <div className=\"flex h-16 w-16 items-center justify-center\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={logoPath}\n alt={`${displayBrandName} logo`}\n width={64}\n height={64}\n className=\"max-h-full max-w-full object-contain\"\n onError={(e) => {\n const target = e.target as HTMLImageElement;\n target.style.display = 'none';\n if (target.nextSibling) {\n (target.nextSibling as HTMLElement).style.display = 'flex';\n }\n }}\n />\n <div className=\"bg-border ring-border/50 hidden h-full w-full items-center justify-center rounded-lg text-2xl font-semibold ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n </div>\n ) : (\n <div className=\"bg-border ring-border/50 flex h-16 w-16 items-center justify-center rounded-lg border-2 text-2xl font-semibold ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n )}\n </div>\n\n {/* Title */}\n <CardTitle className=\"line-clamp-2 text-xl leading-tight font-semibold\">\n {displayTitle}\n </CardTitle>\n\n {/* Category Badges - Under Title */}\n <If condition={categories.length > 0}>\n <div className=\"mt-2 flex flex-wrap justify-center gap-1\">\n {categories.slice(0, 2).map((cat) => (\n <CategoryChip key={cat.id} productColor={productColor}>\n {cat.name}\n </CategoryChip>\n ))}\n <If condition={categories.length > 2}>\n <CategoryChip productColor={productColor}>\n +{categories.length - 2}\n </CategoryChip>\n </If>\n </div>\n </If>\n </CardHeader>\n\n {/* Content: Summary */}\n <CardContent className=\"flex-1 pb-8 text-center\">\n <If condition={displaySummary}>\n <CardDescription className=\"text-muted-foreground line-clamp-2 text-base leading-relaxed\">\n {displaySummary}\n </CardDescription>\n </If>\n </CardContent>\n\n {/* Brand Name - Bottom Right */}\n <If condition={displayBrandName !== displayTitle}>\n <p className=\"text-muted-foreground/70 absolute right-3 bottom-3 text-sm\">\n {displayBrandName}\n </p>\n </If>\n\n {/* External Link Indicator */}\n <If condition={!!ctaLink}>\n <div\n className=\"text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors\"\n aria-hidden=\"true\"\n >\n <Icon\n name=\"fa-light fa-arrow-up-right-from-square\"\n className=\"h-4 w-4\"\n />\n </div>\n </If>\n </Card>\n );\n\n // External link: opens in new tab\n if (ctaLink) {\n return (\n <a\n href={ctaLink}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block h-full\"\n aria-label={`${displayTitle} (opens in new tab)`}\n >\n {cardContent}\n </a>\n );\n }\n\n // Internal link: uses Next.js Link\n if (href) {\n return (\n <Link href={href} className=\"block h-full\">\n {cardContent}\n </Link>\n );\n }\n\n // Fallback: render without Link (for backwards compatibility)\n return cardContent;\n}\n\n/**\n * A category chip, tinted with the product's own colour when it has one. The\n * colour is a partner's product colour from the product schema, not a theme\n * token — a data colour, so it is a chip rather than a `Badge` (a Badge owns\n * its colour). The tint is that colour at 40% alpha.\n */\nfunction CategoryChip({\n productColor,\n children,\n}: {\n productColor?: string;\n children: ReactNode;\n}) {\n return (\n <span\n className={cn(\n 'text-secondary-foreground inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap',\n productColor ? 'bg-(--product-color)/40' : 'bg-secondary',\n )}\n style={\n productColor\n ? ({ '--product-color': productColor } as CSSProperties)\n : undefined\n }\n >\n {children}\n </span>\n );\n}\n"]}
1
+ {"version":3,"file":"base-marketplace-tile.js","sourceRoot":"","sources":["../../src/components/base-marketplace-tile.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport type { CSSProperties, ReactNode } from 'react';\n\n// `next/link.js`, not `next/link`. `next` ships no `exports` map, so the\n// extensionless subpath only resolves under a bundler — Node's own ESM loader\n// throws ERR_MODULE_NOT_FOUND, and any partner running vitest or plain Node\n// against the published tarball loses this whole module. The `.js` form is not\n// a bypass of Next's resolution: `create-compiler-aliases.js` keys its aliases\n// on the RESOLVED FILE PATH (`<next>/link.js`, with the comment \"Handle fully\n// specified imports like `next/image.js`\"), so an App Router build still gets\n// `next/dist/client/app-dir/link`, exactly as `next/link` would. Next's dist\n// also ends with `Object.assign(exports.default, exports); module.exports =\n// exports.default`, so Node's CJS-default interop and a bundler's `__esModule`\n// interop land on the same component object. Do not \"tidy\" the extension away.\nimport Link from 'next/link.js';\n\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { MarketplaceTileProps } from '../types/integration';\n\n// Type for product category\nexport interface ProductCategory {\n id: string;\n name: string;\n slug: string;\n description?: string;\n}\n\nexport type BaseMarketplaceTileProps = MarketplaceTileProps & {\n /** The display name of the product */\n name: string;\n /** Full description of the product (used for detail pages) */\n description: string;\n /** URL-friendly identifier for the product */\n slug: string;\n /** Short summary text displayed on the tile (1-2 sentences) */\n summary?: string;\n /** Name of the company/brand that provides this product */\n brandName?: string;\n /** Path to the product logo image */\n logoPath?: string;\n /** Text for the call-to-action button */\n ctaText?: string;\n /** External URL for the CTA (opens in new tab when set) */\n ctaLink?: string;\n\n /** Array of categories this product belongs to */\n categories?: ProductCategory[];\n\n /** Hex color from product schema for tile theming */\n productColor?: string;\n /** Additional CSS classes to apply to the tile */\n className?: string;\n\n /** Internal navigation URL for the integration detail page */\n href?: string;\n};\n\nexport function BaseMarketplaceTile(props: BaseMarketplaceTileProps) {\n const {\n name,\n summary,\n brandName,\n logoPath,\n categories = [],\n productColor,\n className,\n href,\n ctaLink,\n } = props;\n\n const displayTitle = name || 'Unknown Product';\n const displaySummary = summary;\n const displayBrandName = brandName || displayTitle;\n\n const cardContent = (\n <Card\n className={cn(\n 'group bg-card relative flex h-full w-full cursor-pointer flex-col overflow-hidden border-0 pt-3 pb-2 shadow-sm transition-all duration-300 hover:shadow-md',\n className,\n )}\n >\n {/* Header: Centered Logo, Title, Categories */}\n <CardHeader className=\"flex flex-col items-center pt-6 pb-3 text-center\">\n {/* Logo */}\n <div className=\"mb-3\">\n {logoPath ? (\n <div className=\"flex h-16 w-16 items-center justify-center\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={logoPath}\n alt={`${displayBrandName} logo`}\n width={64}\n height={64}\n className=\"max-h-full max-w-full object-contain\"\n onError={(e) => {\n const target = e.target as HTMLImageElement;\n target.style.display = 'none';\n if (target.nextSibling) {\n (target.nextSibling as HTMLElement).style.display = 'flex';\n }\n }}\n />\n <div className=\"bg-border ring-border/50 text-headline hidden h-full w-full items-center justify-center rounded-lg ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n </div>\n ) : (\n <div className=\"bg-border ring-border/50 text-headline flex h-16 w-16 items-center justify-center rounded-lg border-2 ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n )}\n </div>\n\n {/* Title */}\n <CardTitle className=\"text-subhead-lg line-clamp-2 leading-tight\">\n {displayTitle}\n </CardTitle>\n\n {/* Category Badges - Under Title */}\n <If condition={categories.length > 0}>\n <div className=\"mt-2 flex flex-wrap justify-center gap-1\">\n {categories.slice(0, 2).map((cat) => (\n <CategoryChip key={cat.id} productColor={productColor}>\n {cat.name}\n </CategoryChip>\n ))}\n <If condition={categories.length > 2}>\n <CategoryChip productColor={productColor}>\n +{categories.length - 2}\n </CategoryChip>\n </If>\n </div>\n </If>\n </CardHeader>\n\n {/* Content: Summary */}\n <CardContent className=\"flex-1 pb-8 text-center\">\n <If condition={displaySummary}>\n <CardDescription className=\"text-muted-foreground text-body line-clamp-2 leading-relaxed\">\n {displaySummary}\n </CardDescription>\n </If>\n </CardContent>\n\n {/* Brand Name - Bottom Right */}\n <If condition={displayBrandName !== displayTitle}>\n <p className=\"text-muted-foreground/70 text-meta absolute right-3 bottom-3\">\n {displayBrandName}\n </p>\n </If>\n\n {/* External Link Indicator */}\n <If condition={!!ctaLink}>\n <div\n className=\"text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors\"\n aria-hidden=\"true\"\n >\n <Icon\n name=\"fa-light fa-arrow-up-right-from-square\"\n className=\"h-4 w-4\"\n />\n </div>\n </If>\n </Card>\n );\n\n // External link: opens in new tab\n if (ctaLink) {\n return (\n <a\n href={ctaLink}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block h-full\"\n aria-label={`${displayTitle} (opens in new tab)`}\n >\n {cardContent}\n </a>\n );\n }\n\n // Internal link: uses Next.js Link\n if (href) {\n return (\n <Link href={href} className=\"block h-full\">\n {cardContent}\n </Link>\n );\n }\n\n // Fallback: render without Link (for backwards compatibility)\n return cardContent;\n}\n\n/**\n * A category chip, tinted with the product's own colour when it has one. The\n * colour is a partner's product colour from the product schema, not a theme\n * token — a data colour, so it is a chip rather than a `Badge` (a Badge owns\n * its colour). The tint is that colour at 40% alpha.\n */\nfunction CategoryChip({\n productColor,\n children,\n}: {\n productColor?: string;\n children: ReactNode;\n}) {\n return (\n <span\n className={cn(\n // oxlint-disable-next-line design/no-raw-text-size -- category chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change\n 'text-secondary-foreground inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap',\n productColor ? 'bg-(--product-color)/40' : 'bg-secondary',\n )}\n style={\n productColor\n ? ({ '--product-color': productColor } as CSSProperties)\n : undefined\n }\n >\n {children}\n </span>\n );\n}\n"]}
@@ -30,9 +30,11 @@ export function AiPromptChip({ label, prompt, context, sourceWidget, title, onAf
30
30
  return (_jsx(Button, { variant: "ghost", size: "sm", className: cn('h-8 w-8 p-0', className), onClick: handleClick, "aria-label": `Ask AI: ${label}`, children: _jsx(Icon, { name: "fa-light fa-sparkles", className: "h-4 w-4" }) }));
31
31
  }
32
32
  if (variant === 'footer') {
33
- return (_jsxs("button", { type: "button", onClick: handleClick, className: cn('flex w-full items-center gap-2 rounded-lg border border-dashed px-4 py-3', 'border-primary/20 bg-primary/5 text-primary-strong text-sm font-medium', 'hover:bg-primary/10 hover:border-primary/40 transition-colors', 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none', className), "aria-label": label, children: [_jsx(Icon, { name: "fa-light fa-sparkles", className: "h-4 w-4 shrink-0" }), _jsx("span", { className: "text-left", children: label })] }));
33
+ return (_jsxs("button", { type: "button", onClick: handleClick, className: cn('flex w-full items-center gap-2 rounded-lg border border-dashed px-4 py-3', 'border-primary/20 bg-primary/5 text-primary-strong text-label', 'hover:bg-primary/10 hover:border-primary/40 transition-colors', 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none', className), "aria-label": label, children: [_jsx(Icon, { name: "fa-light fa-sparkles", className: "h-4 w-4 shrink-0" }), _jsx("span", { className: "text-left", children: label })] }));
34
34
  }
35
35
  // inline (Type B)
36
- return (_jsxs("button", { type: "button", onClick: handleClick, className: cn('inline-flex items-center gap-1.5 rounded-full px-3 py-1.5', 'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium', 'hover:bg-primary/10 hover:border-primary/40 transition-colors', 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none', className), "aria-label": label, children: [_jsx(Icon, { name: "fa-light fa-sparkles", className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: label })] }));
36
+ return (_jsxs("button", { type: "button", onClick: handleClick, className: cn('inline-flex items-center gap-1.5 rounded-full px-3 py-1.5',
37
+ // oxlint-disable-next-line design/no-raw-text-size -- AI suggestion chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change. Repeated in widget-ask-bar.tsx's suggestion chip.
38
+ 'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium', 'hover:bg-primary/10 hover:border-primary/40 transition-colors', 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none', className), "aria-label": label, children: [_jsx(Icon, { name: "fa-light fa-sparkles", className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: label })] }));
37
39
  }
38
40
  //# sourceMappingURL=ai-prompt-chip.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai-prompt-chip.js","sourceRoot":"","sources":["../../../src/components/widgets/ai-prompt-chip.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAqBpE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,OAAO,EACP,YAAY,EACZ,KAAK,EACL,UAAU,EACV,OAAO,GAAG,QAAQ,EAClB,SAAS,GACS;IAClB,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvC,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,YAAY,CAAC;YACX,QAAQ,EAAE,YAAY;YACtB,KAAK,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,KAAK;YACrB,QAAQ,EAAE,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,EAAE;YACvB,eAAe,EAAE,MAAM;SACxB,CAAC,CAAC;QACH,UAAU,aAAV,UAAU,uBAAV,UAAU,EAAI,CAAC;IACjB,CAAC,CAAC;IAEF,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CACL,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,EACvC,OAAO,EAAE,WAAW,gBACR,WAAW,KAAK,EAAE,YAE9B,KAAC,IAAI,IAAC,IAAI,EAAC,sBAAsB,EAAC,SAAS,EAAC,SAAS,GAAG,GACjD,CACV,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CACL,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,EAAE,CACX,0EAA0E,EAC1E,wEAAwE,EACxE,+DAA+D,EAC/D,4EAA4E,EAC5E,SAAS,CACV,gBACW,KAAK,aAEjB,KAAC,IAAI,IAAC,IAAI,EAAC,sBAAsB,EAAC,SAAS,EAAC,kBAAkB,GAAG,EACjE,eAAM,SAAS,EAAC,WAAW,YAAE,KAAK,GAAQ,IACnC,CACV,CAAC;IACJ,CAAC;IAED,kBAAkB;IAClB,OAAO,CACL,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,EAAE,CACX,2DAA2D,EAC3D,+EAA+E,EAC/E,+DAA+D,EAC/D,4EAA4E,EAC5E,SAAS,CACV,gBACW,KAAK,aAEjB,KAAC,IAAI,IAAC,IAAI,EAAC,sBAAsB,EAAC,SAAS,EAAC,sBAAsB,GAAG,EACrE,yBAAO,KAAK,GAAQ,IACb,CACV,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Icon } from '@ekanos/ui/icon';\nimport { cn } from '@ekanos/ui/utils';\n\nimport { useAskAssistant } from '../../hooks/ask-assistant-context';\n\ntype AiPromptChipProps = {\n label: string;\n prompt: string;\n context?: Record<string, unknown>;\n sourceWidget: string;\n /**\n * Human-readable label for the dock's ArrivalChip (\"Asking about …\").\n * Falls back to the chip's display label when omitted.\n */\n title?: string;\n /**\n * Fires after the assistant has been invoked. Drawers pass their close\n * handler here so the dock takes over the screen on click.\n */\n onAfterAsk?: () => void;\n variant?: 'header' | 'inline' | 'footer';\n className?: string;\n};\n\n/**\n * AI handoff chip that opens the assistant dock pre-seeded with this\n * widget/drawer's snapshot. Routes through `useAskAssistant` so the dock\n * receives the standard `{widgetId, title, snapshot, suggestedPrompt}`\n * shape used by all AI handoffs in the app.\n *\n * Variants:\n * - header (Type A): Compact icon button for widget headers\n * - inline (Type B): Chip with sparkle icon + text, below data sections\n * - footer (Type C): Full-width chip in drawer footers\n */\nexport function AiPromptChip({\n label,\n prompt,\n context,\n sourceWidget,\n title,\n onAfterAsk,\n variant = 'inline',\n className,\n}: AiPromptChipProps) {\n const askAssistant = useAskAssistant();\n\n const handleClick = () => {\n askAssistant({\n widgetId: sourceWidget,\n title: title ?? label,\n snapshot: context ?? {},\n suggestedPrompt: prompt,\n });\n onAfterAsk?.();\n };\n\n if (variant === 'header') {\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className={cn('h-8 w-8 p-0', className)}\n onClick={handleClick}\n aria-label={`Ask AI: ${label}`}\n >\n <Icon name=\"fa-light fa-sparkles\" className=\"h-4 w-4\" />\n </Button>\n );\n }\n\n if (variant === 'footer') {\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n className={cn(\n 'flex w-full items-center gap-2 rounded-lg border border-dashed px-4 py-3',\n 'border-primary/20 bg-primary/5 text-primary-strong text-sm font-medium',\n 'hover:bg-primary/10 hover:border-primary/40 transition-colors',\n 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none',\n className,\n )}\n aria-label={label}\n >\n <Icon name=\"fa-light fa-sparkles\" className=\"h-4 w-4 shrink-0\" />\n <span className=\"text-left\">{label}</span>\n </button>\n );\n }\n\n // inline (Type B)\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5',\n 'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium',\n 'hover:bg-primary/10 hover:border-primary/40 transition-colors',\n 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none',\n className,\n )}\n aria-label={label}\n >\n <Icon name=\"fa-light fa-sparkles\" className=\"h-3.5 w-3.5 shrink-0\" />\n <span>{label}</span>\n </button>\n );\n}\n"]}
1
+ {"version":3,"file":"ai-prompt-chip.js","sourceRoot":"","sources":["../../../src/components/widgets/ai-prompt-chip.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAqBpE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,OAAO,EACP,YAAY,EACZ,KAAK,EACL,UAAU,EACV,OAAO,GAAG,QAAQ,EAClB,SAAS,GACS;IAClB,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvC,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,YAAY,CAAC;YACX,QAAQ,EAAE,YAAY;YACtB,KAAK,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,KAAK;YACrB,QAAQ,EAAE,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,EAAE;YACvB,eAAe,EAAE,MAAM;SACxB,CAAC,CAAC;QACH,UAAU,aAAV,UAAU,uBAAV,UAAU,EAAI,CAAC;IACjB,CAAC,CAAC;IAEF,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CACL,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,EACvC,OAAO,EAAE,WAAW,gBACR,WAAW,KAAK,EAAE,YAE9B,KAAC,IAAI,IAAC,IAAI,EAAC,sBAAsB,EAAC,SAAS,EAAC,SAAS,GAAG,GACjD,CACV,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CACL,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,EAAE,CACX,0EAA0E,EAC1E,+DAA+D,EAC/D,+DAA+D,EAC/D,4EAA4E,EAC5E,SAAS,CACV,gBACW,KAAK,aAEjB,KAAC,IAAI,IAAC,IAAI,EAAC,sBAAsB,EAAC,SAAS,EAAC,kBAAkB,GAAG,EACjE,eAAM,SAAS,EAAC,WAAW,YAAE,KAAK,GAAQ,IACnC,CACV,CAAC;IACJ,CAAC;IAED,kBAAkB;IAClB,OAAO,CACL,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,EAAE,CACX,2DAA2D;QAC3D,uNAAuN;QACvN,+EAA+E,EAC/E,+DAA+D,EAC/D,4EAA4E,EAC5E,SAAS,CACV,gBACW,KAAK,aAEjB,KAAC,IAAI,IAAC,IAAI,EAAC,sBAAsB,EAAC,SAAS,EAAC,sBAAsB,GAAG,EACrE,yBAAO,KAAK,GAAQ,IACb,CACV,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Icon } from '@ekanos/ui/icon';\nimport { cn } from '@ekanos/ui/utils';\n\nimport { useAskAssistant } from '../../hooks/ask-assistant-context';\n\ntype AiPromptChipProps = {\n label: string;\n prompt: string;\n context?: Record<string, unknown>;\n sourceWidget: string;\n /**\n * Human-readable label for the dock's ArrivalChip (\"Asking about …\").\n * Falls back to the chip's display label when omitted.\n */\n title?: string;\n /**\n * Fires after the assistant has been invoked. Drawers pass their close\n * handler here so the dock takes over the screen on click.\n */\n onAfterAsk?: () => void;\n variant?: 'header' | 'inline' | 'footer';\n className?: string;\n};\n\n/**\n * AI handoff chip that opens the assistant dock pre-seeded with this\n * widget/drawer's snapshot. Routes through `useAskAssistant` so the dock\n * receives the standard `{widgetId, title, snapshot, suggestedPrompt}`\n * shape used by all AI handoffs in the app.\n *\n * Variants:\n * - header (Type A): Compact icon button for widget headers\n * - inline (Type B): Chip with sparkle icon + text, below data sections\n * - footer (Type C): Full-width chip in drawer footers\n */\nexport function AiPromptChip({\n label,\n prompt,\n context,\n sourceWidget,\n title,\n onAfterAsk,\n variant = 'inline',\n className,\n}: AiPromptChipProps) {\n const askAssistant = useAskAssistant();\n\n const handleClick = () => {\n askAssistant({\n widgetId: sourceWidget,\n title: title ?? label,\n snapshot: context ?? {},\n suggestedPrompt: prompt,\n });\n onAfterAsk?.();\n };\n\n if (variant === 'header') {\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className={cn('h-8 w-8 p-0', className)}\n onClick={handleClick}\n aria-label={`Ask AI: ${label}`}\n >\n <Icon name=\"fa-light fa-sparkles\" className=\"h-4 w-4\" />\n </Button>\n );\n }\n\n if (variant === 'footer') {\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n className={cn(\n 'flex w-full items-center gap-2 rounded-lg border border-dashed px-4 py-3',\n 'border-primary/20 bg-primary/5 text-primary-strong text-label',\n 'hover:bg-primary/10 hover:border-primary/40 transition-colors',\n 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none',\n className,\n )}\n aria-label={label}\n >\n <Icon name=\"fa-light fa-sparkles\" className=\"h-4 w-4 shrink-0\" />\n <span className=\"text-left\">{label}</span>\n </button>\n );\n }\n\n // inline (Type B)\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5',\n // oxlint-disable-next-line design/no-raw-text-size -- AI suggestion chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change. Repeated in widget-ask-bar.tsx's suggestion chip.\n 'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium',\n 'hover:bg-primary/10 hover:border-primary/40 transition-colors',\n 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none',\n className,\n )}\n aria-label={label}\n >\n <Icon name=\"fa-light fa-sparkles\" className=\"h-3.5 w-3.5 shrink-0\" />\n <span>{label}</span>\n </button>\n );\n}\n"]}
@@ -52,7 +52,9 @@ export function WidgetAskBar({ context }) {
52
52
  }, [dismiss, isActive]);
53
53
  return (_jsxs("div", { ref: containerRef, className: "border-border/60 shrink-0 border-t", "data-test": "widget-ask-bar", children: [_jsxs("button", { type: "button", onClick: () => (isActive ? dismiss() : setIsActive(true)), "aria-expanded": isActive, "aria-label": isActive
54
54
  ? `Close ask input for ${context.title}`
55
- : `Ask about ${context.title}`, "data-test": "widget-ask-bar-toggle", className: cn('group/tag hover:bg-accent/40 flex w-full items-center gap-2.5 px-4 py-2.5 text-left transition-colors sm:px-5', isActive && 'bg-accent/30'), children: [_jsx(AskIcon, { className: cn('text-primary h-5 w-5 shrink-0 transition-transform duration-200', isActive && 'scale-110') }), _jsx("span", { className: "text-muted-foreground group-hover/tag:text-foreground flex-1 text-base font-medium transition-colors", children: "Ask about this" }), _jsx(Icon, { name: "fa-light fa-chevron-up", className: cn('text-muted-foreground/50 group-hover/tag:text-muted-foreground h-3.5 w-3.5 shrink-0 transition-transform duration-200', isActive && 'rotate-180') })] }), isActive && (_jsxs("div", { className: "border-border/40 space-y-3 border-t p-3 sm:px-5", "data-test": "widget-ask-bar-input", children: [suggestions.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", "data-test": "widget-ask-bar-suggestions", children: suggestions.map((s) => (_jsxs("button", { type: "button", onClick: () => handleSuggestion(s.prompt), className: cn('inline-flex items-center gap-1.5 rounded-full px-3 py-1.5', 'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium', 'hover:bg-primary/10 hover:border-primary/40 transition-colors', 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none'), "aria-label": s.label, children: [_jsx(Icon, { name: "fa-light fa-sparkles", className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "text-left", children: s.label })] }, s.label))) })), _jsxs(PromptInput, { onSubmit: handleSubmit, children: [_jsx(PromptInputBody, { children: _jsx(PromptInputTextarea, { autoFocus: true, placeholder: `Ask about ${context.title.toLowerCase()}…`, "aria-label": `Ask about ${context.title}`, onKeyDown: (e) => {
55
+ : `Ask about ${context.title}`, "data-test": "widget-ask-bar-toggle", className: cn('group/tag hover:bg-accent/40 flex w-full items-center gap-2.5 px-4 py-2.5 text-left transition-colors sm:px-5', isActive && 'bg-accent/30'), children: [_jsx(AskIcon, { className: cn('text-primary h-5 w-5 shrink-0 transition-transform duration-200', isActive && 'scale-110') }), _jsx("span", { className: "text-muted-foreground group-hover/tag:text-foreground flex-1 text-base font-medium transition-colors", children: "Ask about this" }), _jsx(Icon, { name: "fa-light fa-chevron-up", className: cn('text-muted-foreground/50 group-hover/tag:text-muted-foreground h-3.5 w-3.5 shrink-0 transition-transform duration-200', isActive && 'rotate-180') })] }), isActive && (_jsxs("div", { className: "border-border/40 space-y-3 border-t p-3 sm:px-5", "data-test": "widget-ask-bar-input", children: [suggestions.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", "data-test": "widget-ask-bar-suggestions", children: suggestions.map((s) => (_jsxs("button", { type: "button", onClick: () => handleSuggestion(s.prompt), className: cn('inline-flex items-center gap-1.5 rounded-full px-3 py-1.5',
56
+ // oxlint-disable-next-line design/no-raw-text-size -- AI suggestion chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change. Repeated in ai-prompt-chip.tsx's inline chip.
57
+ 'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium', 'hover:bg-primary/10 hover:border-primary/40 transition-colors', 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none'), "aria-label": s.label, children: [_jsx(Icon, { name: "fa-light fa-sparkles", className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "text-left", children: s.label })] }, s.label))) })), _jsxs(PromptInput, { onSubmit: handleSubmit, children: [_jsx(PromptInputBody, { children: _jsx(PromptInputTextarea, { autoFocus: true, placeholder: `Ask about ${context.title.toLowerCase()}…`, "aria-label": `Ask about ${context.title}`, onKeyDown: (e) => {
56
58
  if (e.key === 'Escape') {
57
59
  e.preventDefault();
58
60
  dismiss();
@@ -1 +1 @@
1
- {"version":3,"file":"widget-ask-bar.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-ask-bar.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport {\n PromptInput,\n PromptInputBody,\n PromptInputFooter,\n PromptInputSubmit,\n PromptInputTextarea,\n PromptInputTools,\n} from '@ekanos/ui/ai-prompt-input';\nimport { Icon } from '@ekanos/ui/icon';\nimport { cn } from '@ekanos/ui/utils';\n\nimport { useAskAssistant } from '../../hooks/ask-assistant-context';\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport { AskIcon } from './ask-icon';\n\ninterface Props {\n context: WidgetAskContext;\n}\n\n/**\n * Widget footer tag + expanding prompt input.\n *\n * - closed: a quiet, always-visible tag row at the bottom of the widget\n * (\"Aperture icon · Ask about this · chevron up\")\n * - open: clicking the tag reveals an inline `PromptInput` (the canonical\n * AI input component) below it. The tag itself doubles as the\n * close affordance (chevron rotates).\n *\n * Dismisses on click-outside or Escape. Submitting sends the user's typed\n * question (or the widget's suggestedPrompt fallback) to the assistant dock.\n */\nexport function WidgetAskBar({ context }: Props) {\n const [isActive, setIsActive] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const askAssistant = useAskAssistant();\n\n const dismiss = useCallback(() => {\n setIsActive(false);\n }, []);\n\n const handleSubmit = useCallback(\n (message: { text: string }) => {\n const prompt = message.text.trim() || context.suggestedPrompt;\n askAssistant({ ...context, suggestedPrompt: prompt });\n dismiss();\n },\n [askAssistant, context, dismiss],\n );\n\n const handleSuggestion = useCallback(\n (prompt: string) => {\n askAssistant({ ...context, suggestedPrompt: prompt });\n dismiss();\n },\n [askAssistant, context, dismiss],\n );\n\n const suggestions = context.suggestedPrompts ?? [];\n\n useEffect(() => {\n if (!isActive) return;\n const onPointerDown = (e: PointerEvent) => {\n const el = containerRef.current;\n if (!el) return;\n if (el.contains(e.target as Node)) return;\n dismiss();\n };\n document.addEventListener('pointerdown', onPointerDown);\n return () => document.removeEventListener('pointerdown', onPointerDown);\n }, [dismiss, isActive]);\n\n return (\n <div\n ref={containerRef}\n className=\"border-border/60 shrink-0 border-t\"\n data-test=\"widget-ask-bar\"\n >\n <button\n type=\"button\"\n onClick={() => (isActive ? dismiss() : setIsActive(true))}\n aria-expanded={isActive}\n aria-label={\n isActive\n ? `Close ask input for ${context.title}`\n : `Ask about ${context.title}`\n }\n data-test=\"widget-ask-bar-toggle\"\n className={cn(\n 'group/tag hover:bg-accent/40 flex w-full items-center gap-2.5 px-4 py-2.5 text-left transition-colors sm:px-5',\n isActive && 'bg-accent/30',\n )}\n >\n <AskIcon\n className={cn(\n 'text-primary h-5 w-5 shrink-0 transition-transform duration-200',\n isActive && 'scale-110',\n )}\n />\n <span className=\"text-muted-foreground group-hover/tag:text-foreground flex-1 text-base font-medium transition-colors\">\n Ask about this\n </span>\n <Icon\n name=\"fa-light fa-chevron-up\"\n className={cn(\n 'text-muted-foreground/50 group-hover/tag:text-muted-foreground h-3.5 w-3.5 shrink-0 transition-transform duration-200',\n isActive && 'rotate-180',\n )}\n />\n </button>\n\n {isActive && (\n <div\n className=\"border-border/40 space-y-3 border-t p-3 sm:px-5\"\n data-test=\"widget-ask-bar-input\"\n >\n {suggestions.length > 0 && (\n <div\n className=\"flex flex-wrap gap-2\"\n data-test=\"widget-ask-bar-suggestions\"\n >\n {suggestions.map((s) => (\n <button\n key={s.label}\n type=\"button\"\n onClick={() => handleSuggestion(s.prompt)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5',\n 'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium',\n 'hover:bg-primary/10 hover:border-primary/40 transition-colors',\n 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none',\n )}\n aria-label={s.label}\n >\n <Icon\n name=\"fa-light fa-sparkles\"\n className=\"h-3.5 w-3.5 shrink-0\"\n />\n <span className=\"text-left\">{s.label}</span>\n </button>\n ))}\n </div>\n )}\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputBody>\n <PromptInputTextarea\n autoFocus\n placeholder={`Ask about ${context.title.toLowerCase()}…`}\n aria-label={`Ask about ${context.title}`}\n onKeyDown={(e) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n dismiss();\n }\n }}\n />\n </PromptInputBody>\n <PromptInputFooter>\n <PromptInputTools />\n <PromptInputSubmit data-test=\"widget-ask-bar-send\" />\n </PromptInputFooter>\n </PromptInput>\n </div>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"widget-ask-bar.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-ask-bar.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport {\n PromptInput,\n PromptInputBody,\n PromptInputFooter,\n PromptInputSubmit,\n PromptInputTextarea,\n PromptInputTools,\n} from '@ekanos/ui/ai-prompt-input';\nimport { Icon } from '@ekanos/ui/icon';\nimport { cn } from '@ekanos/ui/utils';\n\nimport { useAskAssistant } from '../../hooks/ask-assistant-context';\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport { AskIcon } from './ask-icon';\n\ninterface Props {\n context: WidgetAskContext;\n}\n\n/**\n * Widget footer tag + expanding prompt input.\n *\n * - closed: a quiet, always-visible tag row at the bottom of the widget\n * (\"Aperture icon · Ask about this · chevron up\")\n * - open: clicking the tag reveals an inline `PromptInput` (the canonical\n * AI input component) below it. The tag itself doubles as the\n * close affordance (chevron rotates).\n *\n * Dismisses on click-outside or Escape. Submitting sends the user's typed\n * question (or the widget's suggestedPrompt fallback) to the assistant dock.\n */\nexport function WidgetAskBar({ context }: Props) {\n const [isActive, setIsActive] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const askAssistant = useAskAssistant();\n\n const dismiss = useCallback(() => {\n setIsActive(false);\n }, []);\n\n const handleSubmit = useCallback(\n (message: { text: string }) => {\n const prompt = message.text.trim() || context.suggestedPrompt;\n askAssistant({ ...context, suggestedPrompt: prompt });\n dismiss();\n },\n [askAssistant, context, dismiss],\n );\n\n const handleSuggestion = useCallback(\n (prompt: string) => {\n askAssistant({ ...context, suggestedPrompt: prompt });\n dismiss();\n },\n [askAssistant, context, dismiss],\n );\n\n const suggestions = context.suggestedPrompts ?? [];\n\n useEffect(() => {\n if (!isActive) return;\n const onPointerDown = (e: PointerEvent) => {\n const el = containerRef.current;\n if (!el) return;\n if (el.contains(e.target as Node)) return;\n dismiss();\n };\n document.addEventListener('pointerdown', onPointerDown);\n return () => document.removeEventListener('pointerdown', onPointerDown);\n }, [dismiss, isActive]);\n\n return (\n <div\n ref={containerRef}\n className=\"border-border/60 shrink-0 border-t\"\n data-test=\"widget-ask-bar\"\n >\n <button\n type=\"button\"\n onClick={() => (isActive ? dismiss() : setIsActive(true))}\n aria-expanded={isActive}\n aria-label={\n isActive\n ? `Close ask input for ${context.title}`\n : `Ask about ${context.title}`\n }\n data-test=\"widget-ask-bar-toggle\"\n className={cn(\n 'group/tag hover:bg-accent/40 flex w-full items-center gap-2.5 px-4 py-2.5 text-left transition-colors sm:px-5',\n isActive && 'bg-accent/30',\n )}\n >\n <AskIcon\n className={cn(\n 'text-primary h-5 w-5 shrink-0 transition-transform duration-200',\n isActive && 'scale-110',\n )}\n />\n {/* oxlint-disable-next-line design/no-raw-text-size -- \"Ask about this\" label at 16px/500 (text-base font-medium); no tier pairs 500 at 16px (text-title bakes 600, text-body bakes 400) */}\n <span className=\"text-muted-foreground group-hover/tag:text-foreground flex-1 text-base font-medium transition-colors\">\n Ask about this\n </span>\n <Icon\n name=\"fa-light fa-chevron-up\"\n className={cn(\n 'text-muted-foreground/50 group-hover/tag:text-muted-foreground h-3.5 w-3.5 shrink-0 transition-transform duration-200',\n isActive && 'rotate-180',\n )}\n />\n </button>\n\n {isActive && (\n <div\n className=\"border-border/40 space-y-3 border-t p-3 sm:px-5\"\n data-test=\"widget-ask-bar-input\"\n >\n {suggestions.length > 0 && (\n <div\n className=\"flex flex-wrap gap-2\"\n data-test=\"widget-ask-bar-suggestions\"\n >\n {suggestions.map((s) => (\n <button\n key={s.label}\n type=\"button\"\n onClick={() => handleSuggestion(s.prompt)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5',\n // oxlint-disable-next-line design/no-raw-text-size -- AI suggestion chip at 12px/500 (text-xs font-medium); text-fine is 12px but bakes weight 400, a weight change. Repeated in ai-prompt-chip.tsx's inline chip.\n 'border-primary/20 bg-primary/5 text-primary-strong border text-xs font-medium',\n 'hover:bg-primary/10 hover:border-primary/40 transition-colors',\n 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none',\n )}\n aria-label={s.label}\n >\n <Icon\n name=\"fa-light fa-sparkles\"\n className=\"h-3.5 w-3.5 shrink-0\"\n />\n <span className=\"text-left\">{s.label}</span>\n </button>\n ))}\n </div>\n )}\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputBody>\n <PromptInputTextarea\n autoFocus\n placeholder={`Ask about ${context.title.toLowerCase()}…`}\n aria-label={`Ask about ${context.title}`}\n onKeyDown={(e) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n dismiss();\n }\n }}\n />\n </PromptInputBody>\n <PromptInputFooter>\n <PromptInputTools />\n <PromptInputSubmit data-test=\"widget-ask-bar-send\" />\n </PromptInputFooter>\n </PromptInput>\n </div>\n )}\n </div>\n );\n}\n"]}
@@ -17,7 +17,7 @@ function StateAction({ action, variant = 'default', }) {
17
17
  * and at most one action, left-aligned in the body's padding.
18
18
  */
19
19
  function StateNote({ icon, title, description, action, actionVariant, role, testId, }) {
20
- return (_jsxs("div", { role: role, className: "flex flex-1 flex-col items-start gap-3 px-5 pt-2 pb-5", "data-test": testId, children: [_jsx(Icon, { name: icon, className: "text-muted-foreground h-6 w-6", "aria-hidden": true }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-foreground text-base font-medium", children: title }), description ? (_jsx("p", { className: "text-muted-foreground max-w-prose text-sm", children: description })) : null] }), action ? _jsx(StateAction, { action: action, variant: actionVariant }) : null] }));
20
+ return (_jsxs("div", { role: role, className: "flex flex-1 flex-col items-start gap-3 px-5 pt-2 pb-5", "data-test": testId, children: [_jsx(Icon, { name: icon, className: "text-muted-foreground h-6 w-6", "aria-hidden": true }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-foreground text-base font-medium", children: title }), description ? (_jsx("p", { className: "text-muted-foreground text-meta max-w-prose", children: description })) : null] }), action ? _jsx(StateAction, { action: action, variant: actionVariant }) : null] }));
21
21
  }
22
22
  /**
23
23
  * The empty state for a widget with nothing to show. Positive by default:
@@ -40,7 +40,7 @@ const INACTIVE_ICON = {
40
40
  */
41
41
  export function WidgetInactiveState({ reason, integrationName, title, description, action, } = {}) {
42
42
  if (!reason) {
43
- return (_jsx("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: _jsx("p", { className: "text-muted-foreground text-sm", children: title !== null && title !== void 0 ? title : (_jsx(Trans, { i18nKey: "dashboard:widgetInactive", defaults: "Widget inactive" })) }) }));
43
+ return (_jsx("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: _jsx("p", { className: "text-muted-foreground text-meta", children: title !== null && title !== void 0 ? title : (_jsx(Trans, { i18nKey: "dashboard:widgetInactive", defaults: "Widget inactive" })) }) }));
44
44
  }
45
45
  const name = integrationName !== null && integrationName !== void 0 ? integrationName : 'this integration';
46
46
  const defaults = {
@@ -68,12 +68,12 @@ export function WidgetLoadingSkeleton({ variant = 'rows', }) {
68
68
  return (_jsxs("div", { role: "status", "aria-busy": "true", className: "flex flex-1 flex-col", "data-test": `widget-skeleton-${variant}`, children: [_jsx("span", { className: "sr-only", children: _jsx(Trans, { i18nKey: "dashboard:widgetLoading", defaults: "Loading..." }) }), variant === 'stat' ? (_jsxs("div", { className: "space-y-2 px-5 pt-2 pb-5", children: [_jsx(Skeleton, { className: "h-3 w-24" }), _jsx(Skeleton, { className: "h-8 w-40" }), _jsx(Skeleton, { className: "h-3 w-32" })] })) : variant === 'chart' ? (_jsxs("div", { className: "space-y-3 px-5 pt-2 pb-5", children: [_jsx(Skeleton, { className: "h-8 w-40" }), _jsx(Skeleton, { className: "h-60 w-full" })] })) : (_jsx("div", { className: "divide-border/50 divide-y", children: [0, 1, 2].map((row) => (_jsx("div", { className: cn('px-5 py-3', variant === 'schedule' ? 'flex items-start gap-3' : 'space-y-2'), children: variant === 'schedule' ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-4 w-14 shrink-0" }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Skeleton, { className: "h-4 w-2/3" }), _jsx(Skeleton, { className: "h-3 w-1/3" })] })] })) : (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-4 w-2/3" }), _jsx(Skeleton, { className: "h-3 w-1/2" })] })) }, row))) }))] }));
69
69
  }
70
70
  export function WidgetErrorState({ title, description, onRetry, retrying = false, }) {
71
- return (_jsxs("div", { role: "alert", className: "flex flex-col items-center justify-center gap-3 px-5 py-8 text-center", children: [_jsx("div", { className: "bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full", children: _jsx(Icon, { name: "fa-light fa-circle-exclamation", className: "text-destructive h-5 w-5", "aria-hidden": true }) }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: title !== null && title !== void 0 ? title : (_jsx(Trans, { i18nKey: "dashboard:widgetErrorTitle", defaults: "Couldn't load this widget" })) }), _jsx("p", { className: "text-muted-foreground text-sm", children: description ? (description) : onRetry ? (_jsx(Trans, { i18nKey: "dashboard:widgetErrorBodyRetry", defaults: "Try again, or check back in a few minutes." })) : (_jsx(Trans, { i18nKey: "dashboard:widgetErrorBody", defaults: "Check back in a few minutes." })) })] }), onRetry ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: onRetry, disabled: retrying, "data-test": "widget-error-retry", children: [_jsx(Icon, { name: "fa-light fa-arrows-rotate", className: cn('h-4 w-4', retrying && 'animate-spin'), "aria-hidden": true }), _jsx(Trans, { i18nKey: "dashboard:widgetRetry", defaults: "Try again" })] })) : null] }));
71
+ return (_jsxs("div", { role: "alert", className: "flex flex-col items-center justify-center gap-3 px-5 py-8 text-center", children: [_jsx("div", { className: "bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full", children: _jsx(Icon, { name: "fa-light fa-circle-exclamation", className: "text-destructive h-5 w-5", "aria-hidden": true }) }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-foreground text-label", children: title !== null && title !== void 0 ? title : (_jsx(Trans, { i18nKey: "dashboard:widgetErrorTitle", defaults: "Couldn't load this widget" })) }), _jsx("p", { className: "text-muted-foreground text-meta", children: description ? (description) : onRetry ? (_jsx(Trans, { i18nKey: "dashboard:widgetErrorBodyRetry", defaults: "Try again, or check back in a few minutes." })) : (_jsx(Trans, { i18nKey: "dashboard:widgetErrorBody", defaults: "Check back in a few minutes." })) })] }), onRetry ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: onRetry, disabled: retrying, "data-test": "widget-error-retry", children: [_jsx(Icon, { name: "fa-light fa-arrows-rotate", className: cn('h-4 w-4', retrying && 'animate-spin'), "aria-hidden": true }), _jsx(Trans, { i18nKey: "dashboard:widgetRetry", defaults: "Try again" })] })) : null] }));
72
72
  }
73
73
  export function WidgetDataLoading() {
74
74
  return (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Icon, { name: "fa-light fa-spinner-third", className: "text-muted-foreground h-8 w-8 animate-spin" }) }));
75
75
  }
76
76
  export function WidgetDisabledState() {
77
- return (_jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [_jsx(Icon, { name: "fa-light fa-ban", className: "text-muted-foreground mb-4 h-8 w-8" }), _jsx("p", { className: "text-muted-foreground text-sm", children: _jsx(Trans, { i18nKey: "dashboard:widgetDisabled", defaults: "Widget Disabled" }) })] }));
77
+ return (_jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [_jsx(Icon, { name: "fa-light fa-ban", className: "text-muted-foreground mb-4 h-8 w-8" }), _jsx("p", { className: "text-muted-foreground text-meta", children: _jsx(Trans, { i18nKey: "dashboard:widgetDisabled", defaults: "Widget Disabled" }) })] }));
78
78
  }
79
79
  //# sourceMappingURL=widget-state-components.js.map