@ekanos/sdk 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -13
- package/api-report.md +51 -6
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.js +2 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/widgets/widget-context.d.ts +17 -0
- package/dist/components/widgets/widget-context.js.map +1 -1
- package/dist/components/widgets/widget-preview-provider.d.ts +4 -2
- package/dist/components/widgets/widget-preview-provider.js +3 -1
- package/dist/components/widgets/widget-preview-provider.js.map +1 -1
- package/dist/components/widgets/widget-state-components.d.ts +59 -3
- package/dist/components/widgets/widget-state-components.js +65 -7
- package/dist/components/widgets/widget-state-components.js.map +1 -1
- package/dist/components/widgets/widget.d.ts +124 -21
- package/dist/components/widgets/widget.js +159 -37
- package/dist/components/widgets/widget.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/partner-oauth.js +1 -0
- package/dist/hooks/partner-oauth.js.map +1 -1
- package/dist/hooks/use-oauth-connection-status.d.ts +17 -1
- package/dist/hooks/use-oauth-connection-status.js +12 -17
- package/dist/hooks/use-oauth-connection-status.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/actor-role.d.ts +14 -0
- package/dist/integration/actor-role.js +21 -0
- package/dist/integration/actor-role.js.map +1 -0
- package/dist/integration/index.d.ts +3 -1
- package/dist/integration/index.js +4 -0
- package/dist/integration/index.js.map +1 -1
- package/dist/integration/types.d.ts +1 -1
- package/dist/integration/types.js.map +1 -1
- package/dist/integration/untrusted-text.d.ts +24 -0
- package/dist/integration/untrusted-text.js +36 -0
- package/dist/integration/untrusted-text.js.map +1 -0
- package/dist/layout/index.d.ts +8 -0
- package/dist/layout/index.js +9 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/widget-layout.d.ts +84 -0
- package/dist/layout/widget-layout.js +138 -0
- package/dist/layout/widget-layout.js.map +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock-context.js +3 -1
- package/dist/testing/mock-context.js.map +1 -1
- package/dist/testing/verify-signature.d.ts +26 -0
- package/dist/testing/verify-signature.js +45 -0
- package/dist/testing/verify-signature.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/integration.d.ts +5 -0
- package/dist/types/integration.js.map +1 -1
- package/dist/types/widget-verdict.d.ts +6 -0
- package/dist/types/widget-verdict.js +2 -0
- package/dist/types/widget-verdict.js.map +1 -0
- package/eslint.design-preset.mjs +226 -0
- package/eslint.design-rules.mjs +437 -0
- package/package.json +16 -6
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
2
13
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
14
|
import { Component, use, useMemo, } from 'react';
|
|
4
15
|
import Link from 'next/link.js';
|
|
5
16
|
import { Button } from '@ekanos/ui/button';
|
|
6
17
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle, } from '@ekanos/ui/card';
|
|
7
|
-
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@ekanos/ui/dropdown-menu';
|
|
18
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@ekanos/ui/dropdown-menu';
|
|
8
19
|
import { Icon } from '@ekanos/ui/icon';
|
|
20
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@ekanos/ui/select';
|
|
21
|
+
import { StatusBadge } from '@ekanos/ui/status-badge';
|
|
9
22
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@ekanos/ui/tooltip';
|
|
10
23
|
import { Trans } from '@ekanos/ui/trans';
|
|
11
24
|
import { cn } from '@ekanos/ui/utils';
|
|
@@ -13,7 +26,7 @@ import { useAnimatedHeight } from './use-animated-height.js';
|
|
|
13
26
|
import { useRelativeTime } from './use-relative-time.js';
|
|
14
27
|
import { WidgetAskBar } from './widget-ask-bar.js';
|
|
15
28
|
import { WidgetContext } from './widget-context.js';
|
|
16
|
-
import { WidgetDisabledState, WidgetErrorState, WidgetInactiveState,
|
|
29
|
+
import { WidgetDisabledState, WidgetEmptyState, WidgetErrorState, WidgetInactiveState, WidgetLoadingSkeleton, } from './widget-state-components.js';
|
|
17
30
|
// ─── Error Boundary ─────────────────────────────────────────────────────────
|
|
18
31
|
class WidgetErrorBoundary extends Component {
|
|
19
32
|
constructor() {
|
|
@@ -49,13 +62,22 @@ function useWidget() {
|
|
|
49
62
|
}
|
|
50
63
|
return ctx;
|
|
51
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The verdict the widget reported through `Widget.DataState`, for a body that
|
|
67
|
+
* wants to echo it (an empty state, a summary row). `undefined` before the
|
|
68
|
+
* widget has one.
|
|
69
|
+
*/
|
|
70
|
+
export function useWidgetVerdict() {
|
|
71
|
+
var _a;
|
|
72
|
+
return (_a = useWidget().data) === null || _a === void 0 ? void 0 : _a.verdict;
|
|
73
|
+
}
|
|
52
74
|
// ─── Card ───────────────────────────────────────────────────────────────────
|
|
53
75
|
function WidgetCard({ children }) {
|
|
54
76
|
const { meta } = useWidget();
|
|
55
77
|
return (_jsxs(Card, { className: cn(
|
|
56
78
|
// Neutralize the shadcn card's own py-6/gap-6 box model — widget
|
|
57
79
|
// chrome owns all vertical spacing via its header/content/footer.
|
|
58
|
-
'shadow-widget relative flex h-full flex-col gap-0 overflow-hidden rounded-lg border-0 py-0', meta.className), "data-widget-id": meta.widgetId, children: [children, _jsx(WidgetFreshness, {})] }));
|
|
80
|
+
'shadow-widget @container/widget relative flex h-full flex-col gap-0 overflow-hidden rounded-lg border-0 py-0', meta.className), "data-widget-id": meta.widgetId, children: [children, _jsx(WidgetFreshness, {})] }));
|
|
59
81
|
}
|
|
60
82
|
// ─── Header ─────────────────────────────────────────────────────────────────
|
|
61
83
|
function CollapseButton() {
|
|
@@ -65,14 +87,62 @@ function CollapseButton() {
|
|
|
65
87
|
? `Expand ${(_a = meta.title) !== null && _a !== void 0 ? _a : 'widget'}`
|
|
66
88
|
: `Collapse ${(_b = meta.title) !== null && _b !== void 0 ? _b : 'widget'}`, children: _jsx(Icon, { name: "fa-light fa-chevron-right", className: cn('ease-out-strong h-4 w-4 transition-transform duration-200 motion-reduce:transition-none', !state.collapsed && 'rotate-90') }) }));
|
|
67
89
|
}
|
|
68
|
-
function
|
|
69
|
-
|
|
90
|
+
function HeaderTrailing({ trailing }) {
|
|
91
|
+
var _a;
|
|
92
|
+
if (trailing.kind === 'link') {
|
|
93
|
+
const link = trailing.external ? (_jsx("a", { href: trailing.href, target: "_blank", rel: "noopener noreferrer" })) : (_jsx(Link, { href: trailing.href }));
|
|
94
|
+
return (_jsxs(Button, { variant: "ghost", size: "sm", render: link, nativeButton: false, "data-test": "widget-header-link", children: [trailing.label, _jsx(Icon, { name: "fa-light fa-chevron-right", className: "h-3 w-3", "aria-hidden": true })] }));
|
|
95
|
+
}
|
|
96
|
+
return (_jsxs(Select, { value: trailing.value, onValueChange: (value) => {
|
|
97
|
+
if (typeof value === 'string')
|
|
98
|
+
trailing.onChange(value);
|
|
99
|
+
}, children: [_jsx(SelectTrigger, { size: "sm", "aria-label": (_a = trailing.label) !== null && _a !== void 0 ? _a : 'Period', "data-test": "widget-header-period", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: trailing.options.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }));
|
|
100
|
+
}
|
|
101
|
+
const VERDICT_BADGE_TONE = {
|
|
102
|
+
attention: 'warning',
|
|
103
|
+
critical: 'destructive',
|
|
104
|
+
degraded: 'warning',
|
|
105
|
+
unknown: 'neutral',
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* The verdict under the title. Anything but `clear` is a status pill, the
|
|
109
|
+
* words carrying the tone and the dot confirming it; `clear` reads muted,
|
|
110
|
+
* with a check, because good news shouldn't shout.
|
|
111
|
+
*/
|
|
112
|
+
function VerdictLine({ verdict }) {
|
|
113
|
+
if (verdict.tone === 'clear') {
|
|
114
|
+
return (_jsxs("p", { className: "text-muted-foreground flex min-w-0 items-center gap-1.5 text-sm", "data-test": "widget-verdict", "data-verdict": "clear", children: [_jsx(Icon, { name: "fa-light fa-circle-check", className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }), _jsx("span", { className: "truncate", children: verdict.sentence })] }));
|
|
115
|
+
}
|
|
116
|
+
return (_jsx(StatusBadge, { tone: VERDICT_BADGE_TONE[verdict.tone], className: "mt-0.5 max-w-full", "data-test": "widget-verdict", "data-verdict": verdict.tone, children: _jsx("span", { className: "truncate", children: verdict.sentence }) }));
|
|
117
|
+
}
|
|
118
|
+
/** The integration's logo at `size-5`, or its initial when it has none. */
|
|
119
|
+
function SourceMark({ name, logoPath }) {
|
|
120
|
+
if (!logoPath) {
|
|
121
|
+
return (_jsx("span", { "aria-hidden": true, className: "bg-muted text-muted-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-sm text-xs font-semibold", children: name.charAt(0).toUpperCase() }));
|
|
122
|
+
}
|
|
123
|
+
return (_jsx("img", { src: logoPath, alt: "", className: "mt-0.5 size-5 shrink-0 object-contain", "data-test": "widget-source-mark" }));
|
|
124
|
+
}
|
|
125
|
+
/** Widgets already warned about `children` beside `trailing`, so each warns once. */
|
|
126
|
+
const warnedChildrenWithTrailing = new Set();
|
|
127
|
+
function WidgetHeader({ title, subtitle, description, context, trailing, sourceMark = true, menu, children, }) {
|
|
128
|
+
var _a;
|
|
129
|
+
const { meta, state, data } = useWidget();
|
|
70
130
|
const effectiveTitle = title !== null && title !== void 0 ? title : meta.title;
|
|
71
|
-
const effectiveSubtitle = subtitle !== null && subtitle !== void 0 ? subtitle : meta.subtitle;
|
|
72
131
|
const effectiveDescription = description !== null && description !== void 0 ? description : meta.description;
|
|
132
|
+
const contextLine = (_a = context !== null && context !== void 0 ? context : subtitle) !== null && _a !== void 0 ? _a : meta.subtitle;
|
|
133
|
+
// A verdict speaks for data on screen, so it shows only while active.
|
|
134
|
+
const verdict = state.state === 'active' ? data === null || data === void 0 ? void 0 : data.verdict : undefined;
|
|
135
|
+
if (process.env.NODE_ENV !== 'production' &&
|
|
136
|
+
children &&
|
|
137
|
+
trailing &&
|
|
138
|
+
!warnedChildrenWithTrailing.has(meta.widgetId)) {
|
|
139
|
+
warnedChildrenWithTrailing.add(meta.widgetId);
|
|
140
|
+
console.warn(`Widget.Header (${meta.widgetId}): pass \`trailing\` or \`children\`, not both. \`children\` is deprecated; move other controls into \`menu\`.`);
|
|
141
|
+
}
|
|
73
142
|
if (!effectiveTitle)
|
|
74
143
|
return null;
|
|
75
|
-
|
|
144
|
+
const titleNode = effectiveDescription ? (_jsxs(_Fragment, { children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx(CardTitle, { className: "cursor-help truncate text-base font-semibold" }), children: effectiveTitle }), _jsx(TooltipContent, { children: _jsx("p", { children: effectiveDescription }) })] }) }), _jsx("span", { className: "sr-only", children: effectiveDescription })] })) : (_jsx(CardTitle, { className: "truncate text-base font-semibold", children: effectiveTitle }));
|
|
145
|
+
return (_jsx(CardHeader, { className: "flex-shrink-0 px-5 pt-4 pb-3", children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [meta.isCollapsible ? _jsx(CollapseButton, {}) : null, sourceMark && meta.source ? (_jsx(SourceMark, { name: meta.source.name, logoPath: meta.source.logoPath })) : null, _jsxs("div", { className: "min-w-0 space-y-0.5", children: [titleNode, verdict && verdict.tone !== 'clear' ? (_jsx(VerdictLine, { verdict: verdict })) : contextLine ? (_jsxs("p", { className: "text-muted-foreground truncate text-sm", "data-test": "widget-header-context", children: [contextLine, verdict ? (_jsxs(_Fragment, { children: [' · ', _jsx("span", { "data-test": "widget-verdict", "data-verdict": "clear", children: verdict.sentence })] })) : null] })) : verdict ? (_jsx(VerdictLine, { verdict: verdict })) : null] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [trailing ? _jsx(HeaderTrailing, { trailing: trailing }) : null, children, _jsx(WidgetMenu, { items: menu })] })] }) }));
|
|
76
146
|
}
|
|
77
147
|
/**
|
|
78
148
|
* The actions every widget shares, whatever it renders: open its full
|
|
@@ -103,6 +173,19 @@ function WidgetMenuItem({ icon, href, external = false, onSelect, disabled, chil
|
|
|
103
173
|
? 'fa-light fa-arrow-up-right-from-square'
|
|
104
174
|
: 'fa-light fa-arrow-right'), "aria-hidden": true }), children] }));
|
|
105
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* A setting that doesn't earn the header's one trailing slot (a comparison
|
|
178
|
+
* basis, an as-of month), as a submenu of choices in the ⋯ menu. Pass it
|
|
179
|
+
* through `Widget.Header`'s `menu` prop.
|
|
180
|
+
*/
|
|
181
|
+
function WidgetMenuChoice({ icon, label, value, options, onChange, }) {
|
|
182
|
+
const current = options.find((option) => option.value === value);
|
|
183
|
+
return (_jsxs(DropdownMenuSub, { children: [_jsxs(DropdownMenuSubTrigger, { children: [_jsx(Icon, { name: icon !== null && icon !== void 0 ? icon : 'fa-light fa-sliders', "aria-hidden": true }), _jsx("span", { className: "flex-1", children: label }), current ? (_jsx("span", { className: "text-muted-foreground text-xs", children: current.label })) : null] }), _jsx(DropdownMenuSubContent, { className: "min-w-44", children: _jsx(DropdownMenuRadioGroup, { value: value, onValueChange: (next) => {
|
|
184
|
+
const match = options.find((option) => option.value === next);
|
|
185
|
+
if (match)
|
|
186
|
+
onChange(match.value);
|
|
187
|
+
}, children: options.map((option) => (_jsx(DropdownMenuRadioItem, { value: option.value, children: option.label }, option.value))) }) })] }));
|
|
188
|
+
}
|
|
106
189
|
/**
|
|
107
190
|
* "Updated 3 minutes ago" under an active widget whose data layer reports
|
|
108
191
|
* when it last loaded (see `Widget.DataState`'s `updatedAt`).
|
|
@@ -136,29 +219,27 @@ function StaticBody({ maxContentHeight, children, }) {
|
|
|
136
219
|
* card's sides while `Widget.Header` stays inset (`px-5`) unless the body
|
|
137
220
|
* supplies its own padding. `Widget.Body` is that padding: `px-5` matches
|
|
138
221
|
* the header's horizontal inset so header and body align, `pt-4 pb-5`
|
|
139
|
-
* balances the header's `pb-
|
|
222
|
+
* balances the header's `pb-3`/card's bottom edge.
|
|
140
223
|
*
|
|
141
|
-
*
|
|
224
|
+
* Every widget wraps its content in it:
|
|
142
225
|
*
|
|
143
226
|
* ```tsx
|
|
144
227
|
* <Widget.Active>
|
|
145
|
-
* <Widget.Body>{/*
|
|
228
|
+
* <Widget.Body>{/* text, a Stat, a form *\/}</Widget.Body>
|
|
146
229
|
* </Widget.Active>
|
|
147
230
|
* ```
|
|
148
231
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* its own header row, an image).
|
|
232
|
+
* Pass `flush` when the content runs to the card's edges: edge-to-edge rows
|
|
233
|
+
* that pad themselves, a chart, a table with its own header row, an image.
|
|
152
234
|
*/
|
|
153
|
-
function WidgetBody({ className, children, }) {
|
|
154
|
-
return (_jsx("div", { className: cn('flex flex-1 flex-col gap-4 px-5 pt-4 pb-5', className), children: children }));
|
|
235
|
+
function WidgetBody({ flush = false, className, children, }) {
|
|
236
|
+
return (_jsx("div", { className: cn('flex flex-1 flex-col', flush ? 'pb-2' : 'gap-4 px-5 pt-4 pb-5', className), "data-flush": flush || undefined, children: children }));
|
|
155
237
|
}
|
|
156
238
|
/**
|
|
157
|
-
* Renders `children` once the widget's state is `active`.
|
|
158
|
-
* flush
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* `Widget.Body` rather than hand-rolling a `px-*` wrapper.
|
|
239
|
+
* Renders `children` once the widget's state is `active`. Its `CardContent`
|
|
240
|
+
* is flush (`p-0`); wrap content in `Widget.Body`, which pads it to line up
|
|
241
|
+
* with the header, or `Widget.Body flush` for edge-to-edge rows and charts.
|
|
242
|
+
* Never hand-roll a `px-*` wrapper.
|
|
162
243
|
*/
|
|
163
244
|
function WidgetActive({ children }) {
|
|
164
245
|
const { state, meta, data } = useWidget();
|
|
@@ -167,23 +248,49 @@ function WidgetActive({ children }) {
|
|
|
167
248
|
const body = (_jsx(WidgetErrorBoundary, { widgetId: meta.widgetId, onRetry: data === null || data === void 0 ? void 0 : data.refresh, children: children }));
|
|
168
249
|
return meta.isCollapsible ? (_jsx(CollapsibleBody, { collapsed: state.collapsed, maxContentHeight: meta.maxContentHeight, widgetId: meta.widgetId, children: body })) : (_jsx(StaticBody, { maxContentHeight: meta.maxContentHeight, children: body }));
|
|
169
250
|
}
|
|
170
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Shows while the widget loads: a skeleton shaped like its body (`variant`,
|
|
253
|
+
* `rows` by default), never a spinner.
|
|
254
|
+
*/
|
|
255
|
+
function WidgetLoading({ variant, children, }) {
|
|
171
256
|
const { state } = useWidget();
|
|
172
257
|
if (state.state !== 'loading')
|
|
173
258
|
return null;
|
|
174
|
-
return (_jsx(CardContent, { className: "flex min-h-0 flex-1 flex-col p-0", children: children !== null && children !== void 0 ? children : _jsx(
|
|
259
|
+
return (_jsx(CardContent, { className: "flex min-h-0 flex-1 flex-col p-0", children: children !== null && children !== void 0 ? children : _jsx(WidgetLoadingSkeleton, { variant: variant }) }));
|
|
175
260
|
}
|
|
176
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Shows when the widget's data failed to load. Offers Try again whenever the
|
|
263
|
+
* widget reports `onRefresh` through `Widget.DataState`. `title` and
|
|
264
|
+
* `description` replace the default copy with something specific ("Couldn't
|
|
265
|
+
* load your inbox").
|
|
266
|
+
*/
|
|
267
|
+
function WidgetError({ title, description, children, }) {
|
|
177
268
|
const { state, data } = useWidget();
|
|
178
269
|
if (state.state !== 'error')
|
|
179
270
|
return null;
|
|
180
|
-
return (_jsx(CardContent, { className: "flex min-h-0 flex-1 flex-col p-0", children: children !== null && children !== void 0 ? children : (_jsx(WidgetErrorState, { onRetry: data === null || data === void 0 ? void 0 : data.refresh, retrying: data === null || data === void 0 ? void 0 : data.refreshing })) }));
|
|
271
|
+
return (_jsx(CardContent, { className: "flex min-h-0 flex-1 flex-col p-0", children: children !== null && children !== void 0 ? children : (_jsx(WidgetErrorState, { title: title, description: description, onRetry: data === null || data === void 0 ? void 0 : data.refresh, retrying: data === null || data === void 0 ? void 0 : data.refreshing })) }));
|
|
181
272
|
}
|
|
182
|
-
|
|
183
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Shows when the widget can't reach its data. Give it a `reason`:
|
|
275
|
+
* `not-connected` (one Connect button in `action`), `no-access` (a calm note
|
|
276
|
+
* pointing to an admin) or `setup-needed`. `integrationName` defaults to the
|
|
277
|
+
* host's source name.
|
|
278
|
+
*/
|
|
279
|
+
function WidgetInactive(_a) {
|
|
280
|
+
var _b;
|
|
281
|
+
var { children, integrationName } = _a, props = __rest(_a, ["children", "integrationName"]);
|
|
282
|
+
const { state, meta } = useWidget();
|
|
184
283
|
if (state.state !== 'inactive')
|
|
185
284
|
return null;
|
|
186
|
-
return (_jsx(CardContent, { className: "flex min-h-0 flex-1 flex-col p-0", children: children !== null && children !== void 0 ? children : _jsx(WidgetInactiveState, {}) }));
|
|
285
|
+
return (_jsx(CardContent, { className: "flex min-h-0 flex-1 flex-col p-0", children: children !== null && children !== void 0 ? children : (_jsx(WidgetInactiveState, Object.assign({ integrationName: integrationName !== null && integrationName !== void 0 ? integrationName : (_b = meta.source) === null || _b === void 0 ? void 0 : _b.name }, props))) }));
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* What an active widget shows when it has nothing to list. Render it inside
|
|
289
|
+
* `Widget.Active`. Positive by default ("You're all caught up"), because an
|
|
290
|
+
* empty queue is good news; `tone="neutral"` for an empty search.
|
|
291
|
+
*/
|
|
292
|
+
function WidgetEmpty(props) {
|
|
293
|
+
return _jsx(WidgetEmptyState, Object.assign({}, props));
|
|
187
294
|
}
|
|
188
295
|
function WidgetDisabled({ children }) {
|
|
189
296
|
const { state } = useWidget();
|
|
@@ -205,19 +312,29 @@ function WidgetActions({ children }) {
|
|
|
205
312
|
}
|
|
206
313
|
function WidgetAskFooter({ context }) {
|
|
207
314
|
var _a, _b;
|
|
208
|
-
const { state, meta } = useWidget();
|
|
315
|
+
const { state, meta, data } = useWidget();
|
|
209
316
|
const effectiveContext = context !== null && context !== void 0 ? context : meta.askContext;
|
|
210
317
|
const shouldShow = (meta.aiFooterEnabled || !!effectiveContext) &&
|
|
211
318
|
state.state === 'active' &&
|
|
212
319
|
!state.collapsed;
|
|
213
320
|
if (!shouldShow)
|
|
214
321
|
return null;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
322
|
+
const base = effectiveContext !== null && effectiveContext !== void 0 ? effectiveContext : {
|
|
323
|
+
widgetId: meta.widgetId,
|
|
324
|
+
title: (_a = meta.title) !== null && _a !== void 0 ? _a : meta.widgetId,
|
|
325
|
+
snapshot: null,
|
|
326
|
+
suggestedPrompt: `Tell me about ${(_b = meta.title) !== null && _b !== void 0 ? _b : 'this widget'}.`,
|
|
327
|
+
};
|
|
328
|
+
return _jsx(WidgetAskBar, { context: withVerdict(base, data === null || data === void 0 ? void 0 : data.verdict) });
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Adds the widget's own conclusion to what the assistant sees, so an answer
|
|
332
|
+
* starts from the verdict on the card instead of re-deriving it.
|
|
333
|
+
*/
|
|
334
|
+
function withVerdict(context, verdict) {
|
|
335
|
+
if (!verdict)
|
|
336
|
+
return context;
|
|
337
|
+
return Object.assign(Object.assign({}, context), { snapshot: Object.assign(Object.assign({}, context.snapshot), { verdict: Object.assign({ tone: verdict.tone, sentence: verdict.sentence }, (verdict.count !== undefined ? { count: verdict.count } : {})) }) });
|
|
221
338
|
}
|
|
222
339
|
/**
|
|
223
340
|
* Renders a subtle "data may be stale" banner when the widget's integration is
|
|
@@ -277,7 +394,7 @@ function toEpochMs(value) {
|
|
|
277
394
|
* >
|
|
278
395
|
* ```
|
|
279
396
|
*/
|
|
280
|
-
function WidgetDataState({ loading, error, inactive, onRefresh, refreshing = false, updatedAt, children, }) {
|
|
397
|
+
function WidgetDataState({ loading, error, inactive, onRefresh, refreshing = false, updatedAt, verdict, children, }) {
|
|
281
398
|
const parent = useWidget();
|
|
282
399
|
const overrideState = parent.state.state !== 'active'
|
|
283
400
|
? parent.state.state
|
|
@@ -289,13 +406,15 @@ function WidgetDataState({ loading, error, inactive, onRefresh, refreshing = fal
|
|
|
289
406
|
? 'error'
|
|
290
407
|
: 'active';
|
|
291
408
|
const updatedAtMs = toEpochMs(updatedAt);
|
|
292
|
-
const reportsData = onRefresh !== undefined ||
|
|
409
|
+
const reportsData = onRefresh !== undefined ||
|
|
410
|
+
updatedAtMs !== undefined ||
|
|
411
|
+
verdict !== undefined;
|
|
293
412
|
const value = useMemo(() => ({
|
|
294
413
|
state: Object.assign(Object.assign({}, parent.state), { state: overrideState }),
|
|
295
414
|
actions: parent.actions,
|
|
296
415
|
meta: parent.meta,
|
|
297
416
|
data: reportsData
|
|
298
|
-
? { updatedAt: updatedAtMs, refreshing, refresh: onRefresh }
|
|
417
|
+
? { updatedAt: updatedAtMs, refreshing, refresh: onRefresh, verdict }
|
|
299
418
|
: parent.data,
|
|
300
419
|
}), [
|
|
301
420
|
parent.state,
|
|
@@ -307,6 +426,7 @@ function WidgetDataState({ loading, error, inactive, onRefresh, refreshing = fal
|
|
|
307
426
|
updatedAtMs,
|
|
308
427
|
refreshing,
|
|
309
428
|
onRefresh,
|
|
429
|
+
verdict,
|
|
310
430
|
]);
|
|
311
431
|
return _jsx(WidgetContext, { value: value, children: children });
|
|
312
432
|
}
|
|
@@ -315,9 +435,11 @@ export const Widget = {
|
|
|
315
435
|
Card: WidgetCard,
|
|
316
436
|
Header: WidgetHeader,
|
|
317
437
|
MenuItem: WidgetMenuItem,
|
|
438
|
+
MenuChoice: WidgetMenuChoice,
|
|
318
439
|
DataState: WidgetDataState,
|
|
319
440
|
Active: WidgetActive,
|
|
320
441
|
Body: WidgetBody,
|
|
442
|
+
Empty: WidgetEmpty,
|
|
321
443
|
Loading: WidgetLoading,
|
|
322
444
|
Error: WidgetError,
|
|
323
445
|
Inactive: WidgetInactive,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget.js","sourceRoot":"","sources":["../../../src/components/widgets/widget.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n Component,\n type CSSProperties,\n type ReactNode,\n use,\n useMemo,\n} from 'react';\n\nimport Link from 'next/link.js';\n\nimport { Button } from '@ekanos/ui/button';\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@ekanos/ui/dropdown-menu';\nimport { Icon } from '@ekanos/ui/icon';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@ekanos/ui/tooltip';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport { useAnimatedHeight } from './use-animated-height';\nimport { useRelativeTime } from './use-relative-time';\nimport { WidgetAskBar } from './widget-ask-bar';\nimport { WidgetContext, type WidgetRenderState } from './widget-context';\nimport {\n WidgetDisabledState,\n WidgetErrorState,\n WidgetInactiveState,\n WidgetLoadingState,\n} from './widget-state-components';\n\n// ─── Error Boundary ─────────────────────────────────────────────────────────\n\nclass WidgetErrorBoundary extends Component<\n { widgetId: string; onRetry?: () => void; children: ReactNode },\n { hasError: boolean }\n> {\n state = { hasError: false };\n static getDerivedStateFromError() {\n return { hasError: true };\n }\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n console.error(`Widget \"${this.props.widgetId}\" crashed:`, error, info);\n }\n retry = () => {\n this.props.onRetry?.();\n this.setState({ hasError: false });\n };\n render() {\n if (this.state.hasError) {\n return (\n <div\n role=\"alert\"\n className=\"flex flex-1 flex-col items-center justify-center gap-3 px-5 py-8 text-center\"\n >\n <div className=\"bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"text-destructive h-5 w-5\"\n aria-hidden\n />\n </div>\n <div className=\"space-y-1\">\n <p className=\"text-foreground text-sm font-medium\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedTitle\"\n defaults=\"This widget ran into a problem\"\n />\n </p>\n <p className=\"text-muted-foreground max-w-xs text-sm\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedBody\"\n defaults=\"Try again. If it keeps happening, reload the page.\"\n />\n </p>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={this.retry}\n data-test=\"widget-crash-retry\"\n >\n <Icon\n name=\"fa-light fa-arrows-rotate\"\n className=\"h-4 w-4\"\n aria-hidden\n />\n <Trans i18nKey=\"dashboard:widgetRetry\" defaults=\"Try again\" />\n </Button>\n </div>\n );\n }\n return this.props.children;\n }\n}\n\n// ─── Hook ───────────────────────────────────────────────────────────────────\n\nfunction useWidget() {\n const ctx = use(WidgetContext);\n if (!ctx) {\n throw new Error(\n 'Widget.* compound components must be rendered inside a widget provider. ' +\n 'On the Ekanos dashboard the host supplies one. Anywhere else — your own ' +\n 'app, a story, a component test — wrap the widget in ' +\n \"`WidgetPreviewProvider` from '@ekanos/sdk/components'.\",\n );\n }\n return ctx;\n}\n\n// ─── Card ───────────────────────────────────────────────────────────────────\n\nfunction WidgetCard({ children }: { children: ReactNode }) {\n const { meta } = useWidget();\n return (\n <Card\n className={cn(\n // Neutralize the shadcn card's own py-6/gap-6 box model — widget\n // chrome owns all vertical spacing via its header/content/footer.\n 'shadow-widget relative flex h-full flex-col gap-0 overflow-hidden rounded-lg border-0 py-0',\n meta.className,\n )}\n data-widget-id={meta.widgetId}\n >\n {children}\n <WidgetFreshness />\n </Card>\n );\n}\n\n// ─── Header ─────────────────────────────────────────────────────────────────\n\nfunction CollapseButton() {\n const { state, actions, meta } = useWidget();\n return (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={actions.toggleCollapsed}\n disabled={state.pendingCollapse}\n className=\"bg-background dark:bg-secondary relative h-6 w-6 rounded-full before:absolute before:top-1/2 before:left-1/2 before:h-11 before:w-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\"\n aria-expanded={!state.collapsed}\n aria-controls={`widget-content-${meta.widgetId}`}\n aria-label={\n state.collapsed\n ? `Expand ${meta.title ?? 'widget'}`\n : `Collapse ${meta.title ?? 'widget'}`\n }\n >\n <Icon\n name=\"fa-light fa-chevron-right\"\n className={cn(\n 'ease-out-strong h-4 w-4 transition-transform duration-200 motion-reduce:transition-none',\n !state.collapsed && 'rotate-90',\n )}\n />\n </Button>\n );\n}\n\nfunction WidgetHeader({\n title,\n subtitle,\n description,\n menu,\n children,\n}: {\n /** Override the provider's title — useful when the rendered name differs from the config name. */\n title?: string;\n /** Override the provider's subtitle — useful for widget-computed values. */\n subtitle?: string;\n /** Override the provider's description — useful for widget-computed values. */\n description?: string;\n /**\n * `Widget.MenuItem`s placed at the top of the header's ⋯ menu. Use it for\n * occasional actions — \"Open in QuickBooks\", \"Open in Outlook\" — and keep\n * `children` for the controls people reach for every visit, like filters.\n */\n menu?: ReactNode;\n children?: ReactNode;\n}) {\n const { meta } = useWidget();\n const effectiveTitle = title ?? meta.title;\n const effectiveSubtitle = subtitle ?? meta.subtitle;\n const effectiveDescription = description ?? meta.description;\n if (!effectiveTitle) return null;\n return (\n <CardHeader className=\"flex-shrink-0 border-b pt-6 pb-4 [.border-b]:pb-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n {meta.isCollapsible ? <CollapseButton /> : null}\n {effectiveDescription ? (\n <>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger\n render={\n <CardTitle className=\"cursor-help text-lg font-semibold\" />\n }\n >\n {effectiveTitle}\n </TooltipTrigger>\n <TooltipContent>\n <p>{effectiveDescription}</p>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n <span className=\"sr-only\">{effectiveDescription}</span>\n </>\n ) : (\n <CardTitle className=\"text-lg font-semibold\">\n {effectiveTitle}\n </CardTitle>\n )}\n {effectiveSubtitle && (\n <span className=\"text-muted-foreground text-base font-medium\">\n {effectiveSubtitle}\n </span>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {children}\n <WidgetMenu items={menu} />\n </div>\n </div>\n </CardHeader>\n );\n}\n\n/**\n * The actions every widget shares, whatever it renders: open its full\n * surface, reload it, pin or unpin it. Each item needs something from the host\n * or the widget's data layer, so the menu shows only what applies — and\n * nothing at all when nothing does.\n */\nfunction WidgetMenu({ items }: { items?: ReactNode }) {\n const { state, actions, meta, data } = useWidget();\n const refresh = state.state === 'active' ? data?.refresh : undefined;\n const hasNavigation = !!items || !!meta.openHref || !!refresh;\n\n if (!hasNavigation && !meta.isPinnable) return null;\n\n const title = meta.title ?? 'widget';\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n aria-label={`Actions for ${title}`}\n data-test={`widget-menu-${meta.widgetId}`}\n />\n }\n >\n <Icon name=\"fa-light fa-ellipsis\" className=\"h-4 w-4\" aria-hidden />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-48\">\n {items}\n {meta.openHref ? (\n <DropdownMenuItem render={<Link href={meta.openHref} />}>\n <Icon name=\"fa-light fa-arrow-up-right-from-square\" aria-hidden />\n <Trans\n i18nKey=\"dashboard:widgetOpenInWorkspace\"\n defaults=\"Open in workspace\"\n />\n </DropdownMenuItem>\n ) : null}\n {refresh ? (\n <DropdownMenuItem onClick={refresh} disabled={data?.refreshing}>\n <Icon name=\"fa-light fa-arrows-rotate\" aria-hidden />\n <Trans i18nKey=\"dashboard:widgetRefresh\" defaults=\"Refresh\" />\n </DropdownMenuItem>\n ) : null}\n {hasNavigation && meta.isPinnable ? <DropdownMenuSeparator /> : null}\n {meta.isPinnable ? (\n <DropdownMenuItem\n onClick={actions.togglePinned}\n disabled={state.pendingPin}\n data-test={`widget-menu-pin-${meta.widgetId}`}\n >\n <Icon\n name={\n state.pinned\n ? 'fa-light fa-thumbtack-slash'\n : 'fa-light fa-thumbtack'\n }\n aria-hidden\n />\n {state.pinned ? (\n <Trans\n i18nKey=\"dashboard:widgetUnpin\"\n defaults=\"Unpin from board\"\n />\n ) : (\n <Trans i18nKey=\"dashboard:widgetPin\" defaults=\"Pin to board\" />\n )}\n </DropdownMenuItem>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/**\n * One widget-specific entry in the header's ⋯ menu; pass it through\n * `Widget.Header`'s `menu` prop. With `href` it is a link, opened in a new tab\n * when `external`; otherwise it runs `onSelect`.\n */\nfunction WidgetMenuItem({\n icon,\n href,\n external = false,\n onSelect,\n disabled,\n children,\n}: {\n /** A Font Awesome class, e.g. `fa-light fa-envelope`. */\n icon?: string;\n href?: string;\n external?: boolean;\n onSelect?: () => void;\n disabled?: boolean;\n children: ReactNode;\n}) {\n const link = !href ? undefined : external ? (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" />\n ) : (\n <Link href={href} />\n );\n\n return (\n <DropdownMenuItem render={link} onClick={onSelect} disabled={disabled}>\n <Icon\n name={\n icon ??\n (external\n ? 'fa-light fa-arrow-up-right-from-square'\n : 'fa-light fa-arrow-right')\n }\n aria-hidden\n />\n {children}\n </DropdownMenuItem>\n );\n}\n\n/**\n * \"Updated 3 minutes ago\" under an active widget whose data layer reports\n * when it last loaded (see `Widget.DataState`'s `updatedAt`).\n */\nfunction WidgetFreshness() {\n const { state, data } = useWidget();\n const updated = useRelativeTime(data?.updatedAt);\n\n if (state.state !== 'active' || state.collapsed || !updated) return null;\n\n return (\n <div\n className=\"text-muted-foreground border-border/60 flex shrink-0 items-center gap-1.5 border-t px-5 py-2.5 text-xs\"\n aria-live=\"polite\"\n >\n {data?.refreshing ? (\n <>\n <Icon\n name=\"fa-light fa-arrows-rotate\"\n className=\"h-3 w-3 animate-spin\"\n aria-hidden\n />\n <Trans i18nKey=\"dashboard:widgetRefreshing\" defaults=\"Refreshing…\" />\n </>\n ) : (\n <Trans\n i18nKey=\"dashboard:widgetUpdated\"\n defaults=\"Updated {{time}}\"\n values={{ time: updated }}\n />\n )}\n </div>\n );\n}\n\n// ─── State-gated content ────────────────────────────────────────────────────\n\nfunction CollapsibleBody({\n collapsed,\n maxContentHeight,\n widgetId,\n children,\n}: {\n collapsed: boolean;\n maxContentHeight?: string;\n widgetId: string;\n children: ReactNode;\n}) {\n return (\n <div\n className={cn(\n 'ease-out-strong grid min-h-0 flex-1 transition-all duration-300 motion-reduce:transition-none',\n collapsed ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]',\n )}\n >\n <div className=\"min-h-0 overflow-hidden\">\n <CardContent\n id={`widget-content-${widgetId}`}\n className={cn(\n 'flex h-full min-h-0 flex-1 flex-col overflow-y-auto p-0',\n 'ease-out-strong transition-opacity duration-200 motion-reduce:transition-none',\n collapsed ? 'opacity-0' : 'opacity-100',\n maxContentHeight && 'max-h-(--widget-max-height)',\n )}\n style={\n maxContentHeight\n ? ({ '--widget-max-height': maxContentHeight } as CSSProperties)\n : undefined\n }\n >\n {children}\n </CardContent>\n </div>\n </div>\n );\n}\n\nfunction StaticBody({\n maxContentHeight,\n children,\n}: {\n maxContentHeight?: string;\n children: ReactNode;\n}) {\n const { innerRef, height } = useAnimatedHeight();\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n <div\n className={cn(\n 'h-(--body-height) overflow-hidden transition-all duration-250 ease-out motion-reduce:transition-none',\n maxContentHeight && 'max-h-(--widget-max-height)',\n )}\n style={\n {\n '--body-height': height === 'auto' ? 'auto' : `${height}px`,\n '--widget-max-height': maxContentHeight,\n } as CSSProperties\n }\n >\n <div ref={innerRef}>{children}</div>\n </div>\n </CardContent>\n );\n}\n\n/**\n * The standard padded body for content rendered inside `Widget.Active`.\n *\n * `Widget.Active`'s own `CardContent` renders `p-0` — deliberately, so a\n * full-bleed chart or table can run edge-to-edge inside the card. That means\n * ordinary content (text, rows, a KPI readout) is left flush against the\n * card's sides while `Widget.Header` stays inset (`px-5`) unless the body\n * supplies its own padding. `Widget.Body` is that padding: `px-5` matches\n * the header's horizontal inset so header and body align, `pt-4 pb-5`\n * balances the header's `pb-4`/card's bottom edge.\n *\n * Use it for any widget whose content is NOT already full-bleed:\n *\n * ```tsx\n * <Widget.Active>\n * <Widget.Body>{/* rows, text, a KPI readout *\\/}</Widget.Body>\n * </Widget.Active>\n * ```\n *\n * Skip it — render children directly inside `Widget.Active` — when the\n * content itself needs to run flush to the card edges (a chart, a table with\n * its own header row, an image).\n */\nfunction WidgetBody({\n className,\n children,\n}: {\n className?: string;\n children: ReactNode;\n}) {\n return (\n <div className={cn('flex flex-1 flex-col gap-4 px-5 pt-4 pb-5', className)}>\n {children}\n </div>\n );\n}\n\n/**\n * Renders `children` once the widget's state is `active`. Intentionally\n * flush: its `CardContent` renders `p-0` (no horizontal/vertical inset) so a\n * chart, table, or other full-bleed content can run edge-to-edge — existing\n * widgets depend on this. For ordinary padded content, wrap `children` in\n * `Widget.Body` rather than hand-rolling a `px-*` wrapper.\n */\nfunction WidgetActive({ children }: { children: ReactNode }) {\n const { state, meta, data } = useWidget();\n if (state.state !== 'active') return null;\n const body = (\n <WidgetErrorBoundary widgetId={meta.widgetId} onRetry={data?.refresh}>\n {children}\n </WidgetErrorBoundary>\n );\n return meta.isCollapsible ? (\n <CollapsibleBody\n collapsed={state.collapsed}\n maxContentHeight={meta.maxContentHeight}\n widgetId={meta.widgetId}\n >\n {body}\n </CollapsibleBody>\n ) : (\n <StaticBody maxContentHeight={meta.maxContentHeight}>{body}</StaticBody>\n );\n}\n\nfunction WidgetLoading({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'loading') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetLoadingState />}\n </CardContent>\n );\n}\n\nfunction WidgetError({ children }: { children?: ReactNode }) {\n const { state, data } = useWidget();\n if (state.state !== 'error') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? (\n <WidgetErrorState onRetry={data?.refresh} retrying={data?.refreshing} />\n )}\n </CardContent>\n );\n}\n\nfunction WidgetInactive({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'inactive') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetInactiveState />}\n </CardContent>\n );\n}\n\nfunction WidgetDisabled({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'disabled') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetDisabledState />}\n </CardContent>\n );\n}\n\n// ─── Footers ────────────────────────────────────────────────────────────────\n\nfunction WidgetFooter({ children }: { children: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'active') return null;\n return (\n <CardFooter\n className={cn(\n 'ease-out-strong flex-shrink-0 pb-6 transition-opacity duration-200 motion-reduce:transition-none',\n meta.isCollapsible && state.collapsed\n ? 'pointer-events-none opacity-0'\n : 'opacity-100',\n )}\n aria-hidden={meta.isCollapsible && state.collapsed}\n >\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetActions({ children }: { children: ReactNode }) {\n return (\n <CardFooter className=\"bg-muted flex-shrink-0 justify-end border-t py-3\">\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetAskFooter({ context }: { context?: WidgetAskContext }) {\n const { state, meta } = useWidget();\n const effectiveContext = context ?? meta.askContext;\n const shouldShow =\n (meta.aiFooterEnabled || !!effectiveContext) &&\n state.state === 'active' &&\n !state.collapsed;\n if (!shouldShow) return null;\n return (\n <WidgetAskBar\n context={\n effectiveContext ?? {\n widgetId: meta.widgetId,\n title: meta.title ?? meta.widgetId,\n snapshot: null,\n suggestedPrompt: `Tell me about ${meta.title ?? 'this widget'}.`,\n }\n }\n />\n );\n}\n\n/**\n * Renders a subtle \"data may be stale\" banner when the widget's integration is\n * `degraded` (but still active). Unhealthy integrations route to `disabled`\n * instead, so this only fires for the degraded middle ground.\n */\nfunction WidgetHealthFooter() {\n const { state, meta } = useWidget();\n const lastSuccessAt = toEpochMs(meta.health?.lastSuccessAt);\n const lastSynced = useRelativeTime(lastSuccessAt);\n const shouldShow =\n state.state === 'active' &&\n !state.collapsed &&\n meta.health?.status === 'degraded';\n if (!shouldShow) return null;\n return (\n <div className=\"text-warning border-warning/30 bg-warning/5 flex items-center gap-2 border-t px-4 py-2 text-xs\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"h-4 w-4 shrink-0\"\n aria-hidden\n />\n <span>\n {lastSynced ? (\n <Trans\n i18nKey=\"common:integrationHealth.degradedWidgetNoticeSince\"\n defaults=\"Data may be out of date. Last synced {{time}}.\"\n values={{ time: lastSynced }}\n />\n ) : (\n <Trans\n i18nKey=\"common:integrationHealth.degradedWidgetNotice\"\n defaults=\"This integration is degraded — data may be stale.\"\n />\n )}\n </span>\n </div>\n );\n}\n\nfunction toEpochMs(\n value: number | string | Date | null | undefined,\n): number | undefined {\n if (value === null || value === undefined) return undefined;\n const ms =\n value instanceof Date ? value.getTime() : new Date(value).getTime();\n return Number.isFinite(ms) && ms > 0 ? ms : undefined;\n}\n\n// ─── Data state override ────────────────────────────────────────────────────\n\n/**\n * Wraps children in a sub-provider that overrides the effective render state\n * with the widget's own data loading / error state. Only overrides when the\n * parent state is `active` — so the integration's `inactive` / `disabled` /\n * `loading` (initial) states still win.\n *\n * Read the parent integration state BEFORE wrapping in this component if you\n * need it to gate data fetching:\n *\n * ```tsx\n * const { state } = use(WidgetContext);\n * const integrationActive = state.state === 'active';\n * const { isLoading, isError } = useMyData(accountId, integrationActive);\n * return (\n * <Widget.DataState loading={isLoading} error={isError}>\n * <Widget.Card>…</Widget.Card>\n * </Widget.DataState>\n * );\n * ```\n *\n * Pass `onRefresh`, `refreshing` and `updatedAt` to give the widget the\n * shared Refresh menu item, a Try again button on its error view, and an\n * \"Updated 3 minutes ago\" footer. With TanStack Query:\n *\n * ```tsx\n * <Widget.DataState\n * loading={query.isLoading}\n * error={query.isError}\n * onRefresh={() => void query.refetch()}\n * refreshing={query.isFetching}\n * updatedAt={query.dataUpdatedAt}\n * >\n * ```\n */\nfunction WidgetDataState({\n loading,\n error,\n inactive,\n onRefresh,\n refreshing = false,\n updatedAt,\n children,\n}: {\n loading?: boolean;\n error?: boolean;\n /**\n * Force the `inactive` render state even though the integration itself is\n * active. Use for widget-level \"not connected\" states (e.g. the product is\n * activated but the upstream OAuth connection is missing). Takes priority\n * over `loading` / `error`.\n */\n inactive?: boolean;\n /** Reloads the widget's data. */\n onRefresh?: () => void;\n /** A reload is in flight. */\n refreshing?: boolean;\n /**\n * When the data on screen last loaded successfully: epoch milliseconds, an\n * ISO string or a `Date`. `0` means never, which TanStack Query's\n * `dataUpdatedAt` reports before the first success.\n */\n updatedAt?: number | string | Date | null;\n children: ReactNode;\n}) {\n const parent = useWidget();\n const overrideState: WidgetRenderState =\n parent.state.state !== 'active'\n ? parent.state.state\n : inactive\n ? 'inactive'\n : loading\n ? 'loading'\n : error\n ? 'error'\n : 'active';\n const updatedAtMs = toEpochMs(updatedAt);\n const reportsData = onRefresh !== undefined || updatedAtMs !== undefined;\n const value = useMemo(\n () => ({\n state: { ...parent.state, state: overrideState },\n actions: parent.actions,\n meta: parent.meta,\n data: reportsData\n ? { updatedAt: updatedAtMs, refreshing, refresh: onRefresh }\n : parent.data,\n }),\n [\n parent.state,\n parent.actions,\n parent.meta,\n parent.data,\n overrideState,\n reportsData,\n updatedAtMs,\n refreshing,\n onRefresh,\n ],\n );\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n\n// ─── Compound export ────────────────────────────────────────────────────────\n\nexport const Widget = {\n Card: WidgetCard,\n Header: WidgetHeader,\n MenuItem: WidgetMenuItem,\n DataState: WidgetDataState,\n Active: WidgetActive,\n Body: WidgetBody,\n Loading: WidgetLoading,\n Error: WidgetError,\n Inactive: WidgetInactive,\n Disabled: WidgetDisabled,\n Footer: WidgetFooter,\n Actions: WidgetActions,\n AskFooter: WidgetAskFooter,\n HealthFooter: WidgetHealthFooter,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"widget.js","sourceRoot":"","sources":["../../../src/components/widgets/widget.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n Component,\n type CSSProperties,\n type ReactNode,\n use,\n useMemo,\n} from 'react';\n\nimport Link from 'next/link.js';\n\nimport { Button } from '@ekanos/ui/button';\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '@ekanos/ui/dropdown-menu';\nimport { Icon } from '@ekanos/ui/icon';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@ekanos/ui/select';\nimport { StatusBadge } from '@ekanos/ui/status-badge';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@ekanos/ui/tooltip';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport type { WidgetVerdict } from '../../types/widget-verdict';\nimport { useAnimatedHeight } from './use-animated-height';\nimport { useRelativeTime } from './use-relative-time';\nimport { WidgetAskBar } from './widget-ask-bar';\nimport { WidgetContext, type WidgetRenderState } from './widget-context';\nimport {\n WidgetDisabledState,\n WidgetEmptyState,\n type WidgetEmptyStateProps,\n WidgetErrorState,\n WidgetInactiveState,\n type WidgetInactiveStateProps,\n WidgetLoadingSkeleton,\n type WidgetSkeletonVariant,\n} from './widget-state-components';\n\n// ─── Error Boundary ─────────────────────────────────────────────────────────\n\nclass WidgetErrorBoundary extends Component<\n { widgetId: string; onRetry?: () => void; children: ReactNode },\n { hasError: boolean }\n> {\n state = { hasError: false };\n static getDerivedStateFromError() {\n return { hasError: true };\n }\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n console.error(`Widget \"${this.props.widgetId}\" crashed:`, error, info);\n }\n retry = () => {\n this.props.onRetry?.();\n this.setState({ hasError: false });\n };\n render() {\n if (this.state.hasError) {\n return (\n <div\n role=\"alert\"\n className=\"flex flex-1 flex-col items-center justify-center gap-3 px-5 py-8 text-center\"\n >\n <div className=\"bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"text-destructive h-5 w-5\"\n aria-hidden\n />\n </div>\n <div className=\"space-y-1\">\n <p className=\"text-foreground text-sm font-medium\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedTitle\"\n defaults=\"This widget ran into a problem\"\n />\n </p>\n <p className=\"text-muted-foreground max-w-xs text-sm\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedBody\"\n defaults=\"Try again. If it keeps happening, reload the page.\"\n />\n </p>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={this.retry}\n data-test=\"widget-crash-retry\"\n >\n <Icon\n name=\"fa-light fa-arrows-rotate\"\n className=\"h-4 w-4\"\n aria-hidden\n />\n <Trans i18nKey=\"dashboard:widgetRetry\" defaults=\"Try again\" />\n </Button>\n </div>\n );\n }\n return this.props.children;\n }\n}\n\n// ─── Hook ───────────────────────────────────────────────────────────────────\n\nfunction useWidget() {\n const ctx = use(WidgetContext);\n if (!ctx) {\n throw new Error(\n 'Widget.* compound components must be rendered inside a widget provider. ' +\n 'On the Ekanos dashboard the host supplies one. Anywhere else — your own ' +\n 'app, a story, a component test — wrap the widget in ' +\n \"`WidgetPreviewProvider` from '@ekanos/sdk/components'.\",\n );\n }\n return ctx;\n}\n\n/**\n * The verdict the widget reported through `Widget.DataState`, for a body that\n * wants to echo it (an empty state, a summary row). `undefined` before the\n * widget has one.\n */\nexport function useWidgetVerdict(): WidgetVerdict | undefined {\n return useWidget().data?.verdict;\n}\n\n// ─── Card ───────────────────────────────────────────────────────────────────\n\nfunction WidgetCard({ children }: { children: ReactNode }) {\n const { meta } = useWidget();\n return (\n <Card\n className={cn(\n // Neutralize the shadcn card's own py-6/gap-6 box model — widget\n // chrome owns all vertical spacing via its header/content/footer.\n 'shadow-widget @container/widget relative flex h-full flex-col gap-0 overflow-hidden rounded-lg border-0 py-0',\n meta.className,\n )}\n data-widget-id={meta.widgetId}\n >\n {children}\n <WidgetFreshness />\n </Card>\n );\n}\n\n// ─── Header ─────────────────────────────────────────────────────────────────\n\nfunction CollapseButton() {\n const { state, actions, meta } = useWidget();\n return (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={actions.toggleCollapsed}\n disabled={state.pendingCollapse}\n className=\"bg-background dark:bg-secondary relative h-6 w-6 rounded-full before:absolute before:top-1/2 before:left-1/2 before:h-11 before:w-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\"\n aria-expanded={!state.collapsed}\n aria-controls={`widget-content-${meta.widgetId}`}\n aria-label={\n state.collapsed\n ? `Expand ${meta.title ?? 'widget'}`\n : `Collapse ${meta.title ?? 'widget'}`\n }\n >\n <Icon\n name=\"fa-light fa-chevron-right\"\n className={cn(\n 'ease-out-strong h-4 w-4 transition-transform duration-200 motion-reduce:transition-none',\n !state.collapsed && 'rotate-90',\n )}\n />\n </Button>\n );\n}\n\n/**\n * The header's one trailing control (the design system's widget rules): a \"View all ›\"\n * link to where the data lives, or a period switch. Everything else goes in\n * the ⋯ menu.\n */\nexport type WidgetHeaderTrailing =\n | {\n kind: 'link';\n href: string;\n label: ReactNode;\n /** Open in a new tab. */\n external?: boolean;\n }\n | {\n kind: 'period';\n value: string;\n options: ReadonlyArray<{ value: string; label: string }>;\n onChange: (value: string) => void;\n /** Accessible name; defaults to \"Period\". */\n label?: string;\n };\n\nfunction HeaderTrailing({ trailing }: { trailing: WidgetHeaderTrailing }) {\n if (trailing.kind === 'link') {\n const link = trailing.external ? (\n <a href={trailing.href} target=\"_blank\" rel=\"noopener noreferrer\" />\n ) : (\n <Link href={trailing.href} />\n );\n\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n render={link}\n nativeButton={false}\n data-test=\"widget-header-link\"\n >\n {trailing.label}\n <Icon\n name=\"fa-light fa-chevron-right\"\n className=\"h-3 w-3\"\n aria-hidden\n />\n </Button>\n );\n }\n\n return (\n <Select\n value={trailing.value}\n onValueChange={(value) => {\n if (typeof value === 'string') trailing.onChange(value);\n }}\n >\n <SelectTrigger\n size=\"sm\"\n aria-label={trailing.label ?? 'Period'}\n data-test=\"widget-header-period\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {trailing.options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n\nconst VERDICT_BADGE_TONE = {\n attention: 'warning',\n critical: 'destructive',\n degraded: 'warning',\n unknown: 'neutral',\n} as const;\n\n/**\n * The verdict under the title. Anything but `clear` is a status pill, the\n * words carrying the tone and the dot confirming it; `clear` reads muted,\n * with a check, because good news shouldn't shout.\n */\nfunction VerdictLine({ verdict }: { verdict: WidgetVerdict }) {\n if (verdict.tone === 'clear') {\n return (\n <p\n className=\"text-muted-foreground flex min-w-0 items-center gap-1.5 text-sm\"\n data-test=\"widget-verdict\"\n data-verdict=\"clear\"\n >\n <Icon\n name=\"fa-light fa-circle-check\"\n className=\"h-3.5 w-3.5 shrink-0\"\n aria-hidden\n />\n <span className=\"truncate\">{verdict.sentence}</span>\n </p>\n );\n }\n\n return (\n <StatusBadge\n tone={VERDICT_BADGE_TONE[verdict.tone]}\n className=\"mt-0.5 max-w-full\"\n data-test=\"widget-verdict\"\n data-verdict={verdict.tone}\n >\n <span className=\"truncate\">{verdict.sentence}</span>\n </StatusBadge>\n );\n}\n\n/** The integration's logo at `size-5`, or its initial when it has none. */\nfunction SourceMark({ name, logoPath }: { name: string; logoPath?: string }) {\n if (!logoPath) {\n return (\n <span\n aria-hidden\n className=\"bg-muted text-muted-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-sm text-xs font-semibold\"\n >\n {name.charAt(0).toUpperCase()}\n </span>\n );\n }\n\n return (\n // eslint-disable-next-line @next/next/no-img-element -- a bundled integration logo; next/image would need the host's loader config.\n <img\n src={logoPath}\n alt=\"\"\n className=\"mt-0.5 size-5 shrink-0 object-contain\"\n data-test=\"widget-source-mark\"\n />\n );\n}\n\n/** Widgets already warned about `children` beside `trailing`, so each warns once. */\nconst warnedChildrenWithTrailing = new Set<string>();\n\nfunction WidgetHeader({\n title,\n subtitle,\n description,\n context,\n trailing,\n sourceMark = true,\n menu,\n children,\n}: {\n /** Override the provider's title — useful when the rendered name differs from the config name. */\n title?: string;\n /**\n * Override the provider's subtitle. It renders as the context line when\n * `context` is not set.\n */\n subtitle?: string;\n /** Override the provider's description — useful for widget-computed values. */\n description?: string;\n /**\n * One muted line under the title that frames the data: \"This month\",\n * \"As of today\", \"3 accounts\". A verdict that needs the owner replaces it;\n * a clear one joins it (\"This month · All caught up\").\n */\n context?: ReactNode;\n /** At most one control on the right: a \"View all ›\" link or a period switch. */\n trailing?: WidgetHeaderTrailing;\n /**\n * Draw the integration's logo left of the title (the default) when the\n * host supplies one. Pass `false` only for a card that mixes sources.\n */\n sourceMark?: boolean;\n /**\n * `Widget.MenuItem`s and `Widget.MenuChoice`s placed at the top of the\n * header's ⋯ menu: occasional actions (\"Open in QuickBooks\") and secondary\n * settings (a comparison basis) that don't earn the one trailing slot.\n */\n menu?: ReactNode;\n /**\n * @deprecated Free controls beside the ⋯ menu. Use `trailing` for the one\n * control a header carries and `menu` for the rest; `children` goes away\n * in a later major release.\n */\n children?: ReactNode;\n}) {\n const { meta, state, data } = useWidget();\n const effectiveTitle = title ?? meta.title;\n const effectiveDescription = description ?? meta.description;\n const contextLine = context ?? subtitle ?? meta.subtitle;\n // A verdict speaks for data on screen, so it shows only while active.\n const verdict = state.state === 'active' ? data?.verdict : undefined;\n\n if (\n process.env.NODE_ENV !== 'production' &&\n children &&\n trailing &&\n !warnedChildrenWithTrailing.has(meta.widgetId)\n ) {\n warnedChildrenWithTrailing.add(meta.widgetId);\n console.warn(\n `Widget.Header (${meta.widgetId}): pass \\`trailing\\` or \\`children\\`, not both. \\`children\\` is deprecated; move other controls into \\`menu\\`.`,\n );\n }\n\n if (!effectiveTitle) return null;\n\n const titleNode = effectiveDescription ? (\n <>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger\n render={\n <CardTitle className=\"cursor-help truncate text-base font-semibold\" />\n }\n >\n {effectiveTitle}\n </TooltipTrigger>\n <TooltipContent>\n <p>{effectiveDescription}</p>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n <span className=\"sr-only\">{effectiveDescription}</span>\n </>\n ) : (\n <CardTitle className=\"truncate text-base font-semibold\">\n {effectiveTitle}\n </CardTitle>\n );\n\n return (\n <CardHeader className=\"flex-shrink-0 px-5 pt-4 pb-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-2.5\">\n {meta.isCollapsible ? <CollapseButton /> : null}\n {sourceMark && meta.source ? (\n <SourceMark\n name={meta.source.name}\n logoPath={meta.source.logoPath}\n />\n ) : null}\n <div className=\"min-w-0 space-y-0.5\">\n {titleNode}\n {verdict && verdict.tone !== 'clear' ? (\n <VerdictLine verdict={verdict} />\n ) : contextLine ? (\n <p\n className=\"text-muted-foreground truncate text-sm\"\n data-test=\"widget-header-context\"\n >\n {contextLine}\n {verdict ? (\n <>\n {' · '}\n <span data-test=\"widget-verdict\" data-verdict=\"clear\">\n {verdict.sentence}\n </span>\n </>\n ) : null}\n </p>\n ) : verdict ? (\n <VerdictLine verdict={verdict} />\n ) : null}\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {trailing ? <HeaderTrailing trailing={trailing} /> : null}\n {children}\n <WidgetMenu items={menu} />\n </div>\n </div>\n </CardHeader>\n );\n}\n\n/**\n * The actions every widget shares, whatever it renders: open its full\n * surface, reload it, pin or unpin it. Each item needs something from the host\n * or the widget's data layer, so the menu shows only what applies — and\n * nothing at all when nothing does.\n */\nfunction WidgetMenu({ items }: { items?: ReactNode }) {\n const { state, actions, meta, data } = useWidget();\n const refresh = state.state === 'active' ? data?.refresh : undefined;\n const hasNavigation = !!items || !!meta.openHref || !!refresh;\n\n if (!hasNavigation && !meta.isPinnable) return null;\n\n const title = meta.title ?? 'widget';\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n aria-label={`Actions for ${title}`}\n data-test={`widget-menu-${meta.widgetId}`}\n />\n }\n >\n <Icon name=\"fa-light fa-ellipsis\" className=\"h-4 w-4\" aria-hidden />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-48\">\n {items}\n {meta.openHref ? (\n <DropdownMenuItem render={<Link href={meta.openHref} />}>\n <Icon name=\"fa-light fa-arrow-up-right-from-square\" aria-hidden />\n <Trans\n i18nKey=\"dashboard:widgetOpenInWorkspace\"\n defaults=\"Open in workspace\"\n />\n </DropdownMenuItem>\n ) : null}\n {refresh ? (\n <DropdownMenuItem onClick={refresh} disabled={data?.refreshing}>\n <Icon name=\"fa-light fa-arrows-rotate\" aria-hidden />\n <Trans i18nKey=\"dashboard:widgetRefresh\" defaults=\"Refresh\" />\n </DropdownMenuItem>\n ) : null}\n {hasNavigation && meta.isPinnable ? <DropdownMenuSeparator /> : null}\n {meta.isPinnable ? (\n <DropdownMenuItem\n onClick={actions.togglePinned}\n disabled={state.pendingPin}\n data-test={`widget-menu-pin-${meta.widgetId}`}\n >\n <Icon\n name={\n state.pinned\n ? 'fa-light fa-thumbtack-slash'\n : 'fa-light fa-thumbtack'\n }\n aria-hidden\n />\n {state.pinned ? (\n <Trans\n i18nKey=\"dashboard:widgetUnpin\"\n defaults=\"Unpin from board\"\n />\n ) : (\n <Trans i18nKey=\"dashboard:widgetPin\" defaults=\"Pin to board\" />\n )}\n </DropdownMenuItem>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/**\n * One widget-specific entry in the header's ⋯ menu; pass it through\n * `Widget.Header`'s `menu` prop. With `href` it is a link, opened in a new tab\n * when `external`; otherwise it runs `onSelect`.\n */\nfunction WidgetMenuItem({\n icon,\n href,\n external = false,\n onSelect,\n disabled,\n children,\n}: {\n /** A Font Awesome class, e.g. `fa-light fa-envelope`. */\n icon?: string;\n href?: string;\n external?: boolean;\n onSelect?: () => void;\n disabled?: boolean;\n children: ReactNode;\n}) {\n const link = !href ? undefined : external ? (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" />\n ) : (\n <Link href={href} />\n );\n\n return (\n <DropdownMenuItem render={link} onClick={onSelect} disabled={disabled}>\n <Icon\n name={\n icon ??\n (external\n ? 'fa-light fa-arrow-up-right-from-square'\n : 'fa-light fa-arrow-right')\n }\n aria-hidden\n />\n {children}\n </DropdownMenuItem>\n );\n}\n\n/**\n * A setting that doesn't earn the header's one trailing slot (a comparison\n * basis, an as-of month), as a submenu of choices in the ⋯ menu. Pass it\n * through `Widget.Header`'s `menu` prop.\n */\nfunction WidgetMenuChoice<Value extends string>({\n icon,\n label,\n value,\n options,\n onChange,\n}: {\n /** A Font Awesome class, e.g. `fa-light fa-scale-balanced`. */\n icon?: string;\n label: ReactNode;\n value: Value;\n options: ReadonlyArray<{ value: Value; label: ReactNode }>;\n onChange: (value: Value) => void;\n}) {\n const current = options.find((option) => option.value === value);\n\n return (\n <DropdownMenuSub>\n <DropdownMenuSubTrigger>\n <Icon name={icon ?? 'fa-light fa-sliders'} aria-hidden />\n <span className=\"flex-1\">{label}</span>\n {current ? (\n <span className=\"text-muted-foreground text-xs\">{current.label}</span>\n ) : null}\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent className=\"min-w-44\">\n <DropdownMenuRadioGroup\n value={value}\n onValueChange={(next) => {\n const match = options.find((option) => option.value === next);\n if (match) onChange(match.value);\n }}\n >\n {options.map((option) => (\n <DropdownMenuRadioItem key={option.value} value={option.value}>\n {option.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n );\n}\n\n/**\n * \"Updated 3 minutes ago\" under an active widget whose data layer reports\n * when it last loaded (see `Widget.DataState`'s `updatedAt`).\n */\nfunction WidgetFreshness() {\n const { state, data } = useWidget();\n const updated = useRelativeTime(data?.updatedAt);\n\n if (state.state !== 'active' || state.collapsed || !updated) return null;\n\n return (\n <div\n className=\"text-muted-foreground border-border/60 flex shrink-0 items-center gap-1.5 border-t px-5 py-2.5 text-xs\"\n aria-live=\"polite\"\n >\n {data?.refreshing ? (\n <>\n <Icon\n name=\"fa-light fa-arrows-rotate\"\n className=\"h-3 w-3 animate-spin\"\n aria-hidden\n />\n <Trans i18nKey=\"dashboard:widgetRefreshing\" defaults=\"Refreshing…\" />\n </>\n ) : (\n <Trans\n i18nKey=\"dashboard:widgetUpdated\"\n defaults=\"Updated {{time}}\"\n values={{ time: updated }}\n />\n )}\n </div>\n );\n}\n\n// ─── State-gated content ────────────────────────────────────────────────────\n\nfunction CollapsibleBody({\n collapsed,\n maxContentHeight,\n widgetId,\n children,\n}: {\n collapsed: boolean;\n maxContentHeight?: string;\n widgetId: string;\n children: ReactNode;\n}) {\n return (\n <div\n className={cn(\n 'ease-out-strong grid min-h-0 flex-1 transition-all duration-300 motion-reduce:transition-none',\n collapsed ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]',\n )}\n >\n <div className=\"min-h-0 overflow-hidden\">\n <CardContent\n id={`widget-content-${widgetId}`}\n className={cn(\n 'flex h-full min-h-0 flex-1 flex-col overflow-y-auto p-0',\n 'ease-out-strong transition-opacity duration-200 motion-reduce:transition-none',\n collapsed ? 'opacity-0' : 'opacity-100',\n maxContentHeight && 'max-h-(--widget-max-height)',\n )}\n style={\n maxContentHeight\n ? ({ '--widget-max-height': maxContentHeight } as CSSProperties)\n : undefined\n }\n >\n {children}\n </CardContent>\n </div>\n </div>\n );\n}\n\nfunction StaticBody({\n maxContentHeight,\n children,\n}: {\n maxContentHeight?: string;\n children: ReactNode;\n}) {\n const { innerRef, height } = useAnimatedHeight();\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n <div\n className={cn(\n 'h-(--body-height) overflow-hidden transition-all duration-250 ease-out motion-reduce:transition-none',\n maxContentHeight && 'max-h-(--widget-max-height)',\n )}\n style={\n {\n '--body-height': height === 'auto' ? 'auto' : `${height}px`,\n '--widget-max-height': maxContentHeight,\n } as CSSProperties\n }\n >\n <div ref={innerRef}>{children}</div>\n </div>\n </CardContent>\n );\n}\n\n/**\n * The standard padded body for content rendered inside `Widget.Active`.\n *\n * `Widget.Active`'s own `CardContent` renders `p-0` — deliberately, so a\n * full-bleed chart or table can run edge-to-edge inside the card. That means\n * ordinary content (text, rows, a KPI readout) is left flush against the\n * card's sides while `Widget.Header` stays inset (`px-5`) unless the body\n * supplies its own padding. `Widget.Body` is that padding: `px-5` matches\n * the header's horizontal inset so header and body align, `pt-4 pb-5`\n * balances the header's `pb-3`/card's bottom edge.\n *\n * Every widget wraps its content in it:\n *\n * ```tsx\n * <Widget.Active>\n * <Widget.Body>{/* text, a Stat, a form *\\/}</Widget.Body>\n * </Widget.Active>\n * ```\n *\n * Pass `flush` when the content runs to the card's edges: edge-to-edge rows\n * that pad themselves, a chart, a table with its own header row, an image.\n */\nfunction WidgetBody({\n flush = false,\n className,\n children,\n}: {\n /**\n * Drop the padding for content that runs to the card's edges: edge-to-edge\n * rows (which pad themselves, `px-5 py-3`), a full-bleed chart, an image.\n */\n flush?: boolean;\n className?: string;\n children: ReactNode;\n}) {\n return (\n <div\n className={cn(\n 'flex flex-1 flex-col',\n flush ? 'pb-2' : 'gap-4 px-5 pt-4 pb-5',\n className,\n )}\n data-flush={flush || undefined}\n >\n {children}\n </div>\n );\n}\n\n/**\n * Renders `children` once the widget's state is `active`. Its `CardContent`\n * is flush (`p-0`); wrap content in `Widget.Body`, which pads it to line up\n * with the header, or `Widget.Body flush` for edge-to-edge rows and charts.\n * Never hand-roll a `px-*` wrapper.\n */\nfunction WidgetActive({ children }: { children: ReactNode }) {\n const { state, meta, data } = useWidget();\n if (state.state !== 'active') return null;\n const body = (\n <WidgetErrorBoundary widgetId={meta.widgetId} onRetry={data?.refresh}>\n {children}\n </WidgetErrorBoundary>\n );\n return meta.isCollapsible ? (\n <CollapsibleBody\n collapsed={state.collapsed}\n maxContentHeight={meta.maxContentHeight}\n widgetId={meta.widgetId}\n >\n {body}\n </CollapsibleBody>\n ) : (\n <StaticBody maxContentHeight={meta.maxContentHeight}>{body}</StaticBody>\n );\n}\n\n/**\n * Shows while the widget loads: a skeleton shaped like its body (`variant`,\n * `rows` by default), never a spinner.\n */\nfunction WidgetLoading({\n variant,\n children,\n}: {\n variant?: WidgetSkeletonVariant;\n children?: ReactNode;\n}) {\n const { state } = useWidget();\n if (state.state !== 'loading') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetLoadingSkeleton variant={variant} />}\n </CardContent>\n );\n}\n\n/**\n * Shows when the widget's data failed to load. Offers Try again whenever the\n * widget reports `onRefresh` through `Widget.DataState`. `title` and\n * `description` replace the default copy with something specific (\"Couldn't\n * load your inbox\").\n */\nfunction WidgetError({\n title,\n description,\n children,\n}: {\n title?: ReactNode;\n description?: ReactNode;\n children?: ReactNode;\n}) {\n const { state, data } = useWidget();\n if (state.state !== 'error') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? (\n <WidgetErrorState\n title={title}\n description={description}\n onRetry={data?.refresh}\n retrying={data?.refreshing}\n />\n )}\n </CardContent>\n );\n}\n\n/**\n * Shows when the widget can't reach its data. Give it a `reason`:\n * `not-connected` (one Connect button in `action`), `no-access` (a calm note\n * pointing to an admin) or `setup-needed`. `integrationName` defaults to the\n * host's source name.\n */\nfunction WidgetInactive({\n children,\n integrationName,\n ...props\n}: WidgetInactiveStateProps & { children?: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'inactive') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? (\n <WidgetInactiveState\n integrationName={integrationName ?? meta.source?.name}\n {...props}\n />\n )}\n </CardContent>\n );\n}\n\n/**\n * What an active widget shows when it has nothing to list. Render it inside\n * `Widget.Active`. Positive by default (\"You're all caught up\"), because an\n * empty queue is good news; `tone=\"neutral\"` for an empty search.\n */\nfunction WidgetEmpty(props: WidgetEmptyStateProps) {\n return <WidgetEmptyState {...props} />;\n}\n\nfunction WidgetDisabled({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'disabled') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetDisabledState />}\n </CardContent>\n );\n}\n\n// ─── Footers ────────────────────────────────────────────────────────────────\n\nfunction WidgetFooter({ children }: { children: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'active') return null;\n return (\n <CardFooter\n className={cn(\n 'ease-out-strong flex-shrink-0 pb-6 transition-opacity duration-200 motion-reduce:transition-none',\n meta.isCollapsible && state.collapsed\n ? 'pointer-events-none opacity-0'\n : 'opacity-100',\n )}\n aria-hidden={meta.isCollapsible && state.collapsed}\n >\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetActions({ children }: { children: ReactNode }) {\n return (\n <CardFooter className=\"bg-muted flex-shrink-0 justify-end border-t py-3\">\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetAskFooter({ context }: { context?: WidgetAskContext }) {\n const { state, meta, data } = useWidget();\n const effectiveContext = context ?? meta.askContext;\n const shouldShow =\n (meta.aiFooterEnabled || !!effectiveContext) &&\n state.state === 'active' &&\n !state.collapsed;\n if (!shouldShow) return null;\n const base = effectiveContext ?? {\n widgetId: meta.widgetId,\n title: meta.title ?? meta.widgetId,\n snapshot: null,\n suggestedPrompt: `Tell me about ${meta.title ?? 'this widget'}.`,\n };\n return <WidgetAskBar context={withVerdict(base, data?.verdict)} />;\n}\n\n/**\n * Adds the widget's own conclusion to what the assistant sees, so an answer\n * starts from the verdict on the card instead of re-deriving it.\n */\nfunction withVerdict(\n context: WidgetAskContext,\n verdict: WidgetVerdict | undefined,\n): WidgetAskContext {\n if (!verdict) return context;\n return {\n ...context,\n snapshot: {\n ...context.snapshot,\n verdict: {\n tone: verdict.tone,\n sentence: verdict.sentence,\n ...(verdict.count !== undefined ? { count: verdict.count } : {}),\n },\n },\n };\n}\n\n/**\n * Renders a subtle \"data may be stale\" banner when the widget's integration is\n * `degraded` (but still active). Unhealthy integrations route to `disabled`\n * instead, so this only fires for the degraded middle ground.\n */\nfunction WidgetHealthFooter() {\n const { state, meta } = useWidget();\n const lastSuccessAt = toEpochMs(meta.health?.lastSuccessAt);\n const lastSynced = useRelativeTime(lastSuccessAt);\n const shouldShow =\n state.state === 'active' &&\n !state.collapsed &&\n meta.health?.status === 'degraded';\n if (!shouldShow) return null;\n return (\n <div className=\"text-warning border-warning/30 bg-warning/5 flex items-center gap-2 border-t px-4 py-2 text-xs\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"h-4 w-4 shrink-0\"\n aria-hidden\n />\n <span>\n {lastSynced ? (\n <Trans\n i18nKey=\"common:integrationHealth.degradedWidgetNoticeSince\"\n defaults=\"Data may be out of date. Last synced {{time}}.\"\n values={{ time: lastSynced }}\n />\n ) : (\n <Trans\n i18nKey=\"common:integrationHealth.degradedWidgetNotice\"\n defaults=\"This integration is degraded — data may be stale.\"\n />\n )}\n </span>\n </div>\n );\n}\n\nfunction toEpochMs(\n value: number | string | Date | null | undefined,\n): number | undefined {\n if (value === null || value === undefined) return undefined;\n const ms =\n value instanceof Date ? value.getTime() : new Date(value).getTime();\n return Number.isFinite(ms) && ms > 0 ? ms : undefined;\n}\n\n// ─── Data state override ────────────────────────────────────────────────────\n\n/**\n * Wraps children in a sub-provider that overrides the effective render state\n * with the widget's own data loading / error state. Only overrides when the\n * parent state is `active` — so the integration's `inactive` / `disabled` /\n * `loading` (initial) states still win.\n *\n * Read the parent integration state BEFORE wrapping in this component if you\n * need it to gate data fetching:\n *\n * ```tsx\n * const { state } = use(WidgetContext);\n * const integrationActive = state.state === 'active';\n * const { isLoading, isError } = useMyData(accountId, integrationActive);\n * return (\n * <Widget.DataState loading={isLoading} error={isError}>\n * <Widget.Card>…</Widget.Card>\n * </Widget.DataState>\n * );\n * ```\n *\n * Pass `onRefresh`, `refreshing` and `updatedAt` to give the widget the\n * shared Refresh menu item, a Try again button on its error view, and an\n * \"Updated 3 minutes ago\" footer. With TanStack Query:\n *\n * ```tsx\n * <Widget.DataState\n * loading={query.isLoading}\n * error={query.isError}\n * onRefresh={() => void query.refetch()}\n * refreshing={query.isFetching}\n * updatedAt={query.dataUpdatedAt}\n * >\n * ```\n */\nfunction WidgetDataState({\n loading,\n error,\n inactive,\n onRefresh,\n refreshing = false,\n updatedAt,\n verdict,\n children,\n}: {\n loading?: boolean;\n error?: boolean;\n /**\n * Force the `inactive` render state even though the integration itself is\n * active. Use for widget-level \"not connected\" states (e.g. the product is\n * activated but the upstream OAuth connection is missing). Takes priority\n * over `loading` / `error`.\n */\n inactive?: boolean;\n /** Reloads the widget's data. */\n onRefresh?: () => void;\n /** A reload is in flight. */\n refreshing?: boolean;\n /**\n * When the data on screen last loaded successfully: epoch milliseconds, an\n * ISO string or a `Date`. `0` means never, which TanStack Query's\n * `dataUpdatedAt` reports before the first success.\n */\n updatedAt?: number | string | Date | null;\n /**\n * The widget's verdict on its data (the design system's widget rules): `{ tone,\n * sentence, count? }`. Compute it with the same function the integration's\n * server `getAttention` uses, so the card and home's Needs you agree.\n */\n verdict?: WidgetVerdict;\n children: ReactNode;\n}) {\n const parent = useWidget();\n const overrideState: WidgetRenderState =\n parent.state.state !== 'active'\n ? parent.state.state\n : inactive\n ? 'inactive'\n : loading\n ? 'loading'\n : error\n ? 'error'\n : 'active';\n const updatedAtMs = toEpochMs(updatedAt);\n const reportsData =\n onRefresh !== undefined ||\n updatedAtMs !== undefined ||\n verdict !== undefined;\n const value = useMemo(\n () => ({\n state: { ...parent.state, state: overrideState },\n actions: parent.actions,\n meta: parent.meta,\n data: reportsData\n ? { updatedAt: updatedAtMs, refreshing, refresh: onRefresh, verdict }\n : parent.data,\n }),\n [\n parent.state,\n parent.actions,\n parent.meta,\n parent.data,\n overrideState,\n reportsData,\n updatedAtMs,\n refreshing,\n onRefresh,\n verdict,\n ],\n );\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n\n// ─── Compound export ────────────────────────────────────────────────────────\n\nexport const Widget = {\n Card: WidgetCard,\n Header: WidgetHeader,\n MenuItem: WidgetMenuItem,\n MenuChoice: WidgetMenuChoice,\n DataState: WidgetDataState,\n Active: WidgetActive,\n Body: WidgetBody,\n Empty: WidgetEmpty,\n Loading: WidgetLoading,\n Error: WidgetError,\n Inactive: WidgetInactive,\n Disabled: WidgetDisabled,\n Footer: WidgetFooter,\n Actions: WidgetActions,\n AskFooter: WidgetAskFooter,\n HealthFooter: WidgetHealthFooter,\n};\n"]}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ export { IntegrationActivationProvider } from './activation-actions-context.js';
|
|
|
20
20
|
export type { ActivateIntegrationAction, ActivateIntegrationInput, DeactivateIntegrationAction, DeactivateIntegrationInput, IntegrationActionResult, IntegrationActivationActions, } from './activation-actions-context.js';
|
|
21
21
|
export { fetchIntegrationStorage, integrationStorageUrl, } from './fetch-integration-storage.js';
|
|
22
22
|
export type { FetchIntegrationStorageOptions } from './fetch-integration-storage.js';
|
|
23
|
-
export { useOAuthConnectionStatus, useOAuthWidgetGate, } from './use-oauth-connection-status.js';
|
|
24
|
-
export type { ConnectionCheckAction, ConnectionCheckResult, OAuthErrorParam, UseOAuthConnectionStatusOptions, } from './use-oauth-connection-status.js';
|
|
23
|
+
export { oauthWidgetGateOptionsFor, useOAuthConnectionStatus, useOAuthWidgetGate, } from './use-oauth-connection-status.js';
|
|
24
|
+
export type { ConnectionCheckAction, ConnectionCheckResult, OAuthErrorParam, OAuthWidgetGateOptions, UseOAuthConnectionStatusOptions, } from './use-oauth-connection-status.js';
|
|
25
25
|
export { createPartnerConnectionCheck, partnerOAuthConnectPath, partnerOAuthErrorParam, partnerOAuthStatusUrl, } from './partner-oauth.js';
|
|
26
26
|
export type { PartnerOAuthPathOptions } from './partner-oauth.js';
|
|
27
27
|
export { useIntegrationAction, integrationActionUrl, IntegrationActionError, } from './use-integration-action.js';
|
package/dist/hooks/index.js
CHANGED
|
@@ -23,7 +23,7 @@ export { IntegrationActivationProvider } from './activation-actions-context.js';
|
|
|
23
23
|
// `clientReadable: true`; a key that did not opt in reads as absent.
|
|
24
24
|
export { fetchIntegrationStorage, integrationStorageUrl, } from './fetch-integration-storage.js';
|
|
25
25
|
// OAuth connection status
|
|
26
|
-
export { useOAuthConnectionStatus, useOAuthWidgetGate, } from './use-oauth-connection-status.js';
|
|
26
|
+
export { oauthWidgetGateOptionsFor, useOAuthConnectionStatus, useOAuthWidgetGate, } from './use-oauth-connection-status.js';
|
|
27
27
|
// Hosted partner OAuth transport (host routes:
|
|
28
28
|
// GET /api/integrations/[slug]/oauth/{connect,callback,status}). A partner
|
|
29
29
|
// passes `connectPath: partnerOAuthConnectPath(slug)` to OAuthActivationForm
|