@ekanos/sdk 0.3.0 → 0.5.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 +171 -75
- package/api-report.md +8 -3
- package/dist/components/base-activation-dialog.d.ts +15 -5
- package/dist/components/base-activation-dialog.js +31 -5
- package/dist/components/base-activation-dialog.js.map +1 -1
- package/dist/components/base-activation-form.d.ts +20 -5
- package/dist/components/base-activation-form.js +12 -5
- package/dist/components/base-activation-form.js.map +1 -1
- package/dist/components/base-marketplace-tile.js +12 -10
- package/dist/components/base-marketplace-tile.js.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/oauth-activation-form.d.ts +23 -16
- package/dist/components/oauth-activation-form.js +34 -20
- package/dist/components/oauth-activation-form.js.map +1 -1
- package/dist/components/widgets/ai-prompt-chip.js +2 -2
- package/dist/components/widgets/ai-prompt-chip.js.map +1 -1
- package/dist/components/widgets/use-relative-time.d.ts +7 -0
- package/dist/components/widgets/use-relative-time.js +55 -0
- package/dist/components/widgets/use-relative-time.js.map +1 -0
- package/dist/components/widgets/widget-ask-bar.js +1 -1
- package/dist/components/widgets/widget-ask-bar.js.map +1 -1
- package/dist/components/widgets/widget-context.d.ts +21 -0
- package/dist/components/widgets/widget-context.js.map +1 -1
- package/dist/components/widgets/widget-preview-provider.d.ts +1 -1
- package/dist/components/widgets/widget-preview-provider.js +1 -1
- package/dist/components/widgets/widget-preview-provider.js.map +1 -1
- package/dist/components/widgets/widget-state-components.d.ts +4 -1
- package/dist/components/widgets/widget-state-components.js +6 -4
- package/dist/components/widgets/widget-state-components.js.map +1 -1
- package/dist/components/widgets/widget.d.ts +47 -2
- package/dist/components/widgets/widget.js +111 -23
- package/dist/components/widgets/widget.js.map +1 -1
- package/dist/context/egress.js.map +1 -1
- package/dist/context/storage-validation.d.ts +24 -4
- package/dist/context/storage-validation.js +5 -2
- package/dist/context/storage-validation.js.map +1 -1
- package/dist/hooks/ask-assistant-context.js.map +1 -1
- package/dist/hooks/fetch-integration-storage.js.map +1 -1
- package/dist/hooks/partner-oauth.js +3 -0
- package/dist/hooks/partner-oauth.js.map +1 -1
- package/dist/hooks/use-integration-action.js.map +1 -1
- package/dist/hooks/use-oauth-connection-status.d.ts +78 -78
- package/dist/hooks/use-oauth-connection-status.js +17 -10
- package/dist/hooks/use-oauth-connection-status.js.map +1 -1
- package/dist/hooks/widget-event-context.js.map +1 -1
- package/dist/integration/event-surfaces.js.map +1 -1
- package/dist/integration/index.d.ts +2 -2
- package/dist/integration/index.js +1 -1
- package/dist/integration/index.js.map +1 -1
- package/dist/testing/invoke.js +9 -5
- package/dist/testing/invoke.js.map +1 -1
- package/dist/testing/mock-context.js +43 -8
- package/dist/testing/mock-context.js.map +1 -1
- package/dist/testing/mock-fetch-provider.js.map +1 -1
- package/dist/types/integration.d.ts +27 -21
- package/dist/types/integration.js.map +1 -1
- package/dist/types/widget-ask-context.d.ts +9 -0
- package/dist/types/widget-ask-context.js.map +1 -1
- package/dist/types/workspace-target.d.ts +3 -2
- package/dist/types/workspace-target.js.map +1 -1
- package/eslint.preset.mjs +9 -8
- package/package.json +37 -41
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
|
+
const TICK_MS = 30000;
|
|
4
|
+
const listeners = new Set();
|
|
5
|
+
let timer;
|
|
6
|
+
function subscribe(listener) {
|
|
7
|
+
listeners.add(listener);
|
|
8
|
+
timer !== null && timer !== void 0 ? timer : (timer = setInterval(() => {
|
|
9
|
+
for (const notify of listeners)
|
|
10
|
+
notify();
|
|
11
|
+
}, TICK_MS));
|
|
12
|
+
return () => {
|
|
13
|
+
listeners.delete(listener);
|
|
14
|
+
if (listeners.size === 0 && timer) {
|
|
15
|
+
clearInterval(timer);
|
|
16
|
+
timer = undefined;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Bucketed so consecutive reads inside one tick return the same value, which
|
|
21
|
+
// `useSyncExternalStore` requires of a snapshot.
|
|
22
|
+
function getSnapshot() {
|
|
23
|
+
return Math.floor(Date.now() / TICK_MS) * TICK_MS;
|
|
24
|
+
}
|
|
25
|
+
function getServerSnapshot() {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const UNITS = [
|
|
29
|
+
['day', 86400000],
|
|
30
|
+
['hour', 3600000],
|
|
31
|
+
['minute', 60000],
|
|
32
|
+
];
|
|
33
|
+
export function formatRelativeTime(from, now, locale) {
|
|
34
|
+
const format = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
|
35
|
+
const elapsed = from - now;
|
|
36
|
+
for (const [unit, ms] of UNITS) {
|
|
37
|
+
if (Math.abs(elapsed) >= ms) {
|
|
38
|
+
return format.format(Math.round(elapsed / ms), unit);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return format.format(0, 'second');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* "3 minutes ago" for `timestamp`, re-rendered every 30 seconds by one shared
|
|
45
|
+
* timer. Returns `null` on the server and during hydration, so the text never
|
|
46
|
+
* mismatches: the server cannot know the viewer's clock.
|
|
47
|
+
*/
|
|
48
|
+
export function useRelativeTime(timestamp) {
|
|
49
|
+
const now = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
50
|
+
if (timestamp === undefined || now === null)
|
|
51
|
+
return null;
|
|
52
|
+
const locale = document.documentElement.lang || undefined;
|
|
53
|
+
return formatRelativeTime(timestamp, Math.max(now, timestamp), locale);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=use-relative-time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-relative-time.js","sourceRoot":"","sources":["../../../src/components/widgets/use-relative-time.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE7C,MAAM,OAAO,GAAG,KAAM,CAAC;AAEvB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;AACxC,IAAI,KAAiD,CAAC;AAEtD,SAAS,SAAS,CAAC,QAAoB;IACrC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,KAAK,aAAL,KAAK,cAAL,KAAK,IAAL,KAAK,GAAK,WAAW,CAAC,GAAG,EAAE;QACzB,KAAK,MAAM,MAAM,IAAI,SAAS;YAAE,MAAM,EAAE,CAAC;IAC3C,CAAC,EAAE,OAAO,CAAC,EAAC;IAEZ,OAAO,GAAG,EAAE;QACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;YAClC,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,KAAK,GAAG,SAAS,CAAC;QACpB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,iDAAiD;AACjD,SAAS,WAAW;IAClB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC;AACpD,CAAC;AAED,SAAS,iBAAiB;IACxB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,KAAK,GAA4C;IACrD,CAAC,KAAK,EAAE,QAAU,CAAC;IACnB,CAAC,MAAM,EAAE,OAAS,CAAC;IACnB,CAAC,QAAQ,EAAE,KAAM,CAAC;CACnB,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,GAAW,EACX,MAAe;IAEf,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,IAAI,GAAG,GAAG,CAAC;IAE3B,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5B,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,SAA6B;IAC3D,MAAM,GAAG,GAAG,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;IAE5E,IAAI,SAAS,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,CAAC,IAAI,IAAI,SAAS,CAAC;IAC1D,OAAO,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;AACzE,CAAC","sourcesContent":["'use client';\n\nimport { useSyncExternalStore } from 'react';\n\nconst TICK_MS = 30_000;\n\nconst listeners = new Set<() => void>();\nlet timer: ReturnType<typeof setInterval> | undefined;\n\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n timer ??= setInterval(() => {\n for (const notify of listeners) notify();\n }, TICK_MS);\n\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0 && timer) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n}\n\n// Bucketed so consecutive reads inside one tick return the same value, which\n// `useSyncExternalStore` requires of a snapshot.\nfunction getSnapshot() {\n return Math.floor(Date.now() / TICK_MS) * TICK_MS;\n}\n\nfunction getServerSnapshot() {\n return null;\n}\n\nconst UNITS: [Intl.RelativeTimeFormatUnit, number][] = [\n ['day', 86_400_000],\n ['hour', 3_600_000],\n ['minute', 60_000],\n];\n\nexport function formatRelativeTime(\n from: number,\n now: number,\n locale?: string,\n): string {\n const format = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });\n const elapsed = from - now;\n\n for (const [unit, ms] of UNITS) {\n if (Math.abs(elapsed) >= ms) {\n return format.format(Math.round(elapsed / ms), unit);\n }\n }\n\n return format.format(0, 'second');\n}\n\n/**\n * \"3 minutes ago\" for `timestamp`, re-rendered every 30 seconds by one shared\n * timer. Returns `null` on the server and during hydration, so the text never\n * mismatches: the server cannot know the viewer's clock.\n */\nexport function useRelativeTime(timestamp: number | undefined): string | null {\n const now = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n if (timestamp === undefined || now === null) return null;\n\n const locale = document.documentElement.lang || undefined;\n return formatRelativeTime(timestamp, Math.max(now, timestamp), locale);\n}\n"]}
|
|
@@ -52,7 +52,7 @@ 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 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', '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
56
|
if (e.key === 'Escape') {
|
|
57
57
|
e.preventDefault();
|
|
58
58
|
dismiss();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-ask-bar.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-ask-bar.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjE,OAAO,EACL,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEpE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAMrC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,OAAO,EAAS;;IAC7C,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,YAAY,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,WAAW,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,OAAyB,EAAE,EAAE;QAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,OAAO,CAAC,eAAe,CAAC;QAC9D,YAAY,iCAAM,OAAO,KAAE,eAAe,EAAE,MAAM,IAAG,CAAC;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC,EACD,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CACjC,CAAC;IAEF,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,MAAc,EAAE,EAAE;QACjB,YAAY,iCAAM,OAAO,KAAE,eAAe,EAAE,MAAM,IAAG,CAAC;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC,EACD,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CACjC,CAAC;IAEF,MAAM,WAAW,
|
|
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"]}
|
|
@@ -29,10 +29,31 @@ export interface WidgetMeta {
|
|
|
29
29
|
className?: string;
|
|
30
30
|
/** Integration health for this widget's product, if known. */
|
|
31
31
|
health?: WidgetHealth;
|
|
32
|
+
/**
|
|
33
|
+
* Where the widget's full surface lives, e.g. the workspace a pinned widget
|
|
34
|
+
* came from. Set by the host only where following it leaves the current
|
|
35
|
+
* page; the header menu shows "Open in workspace" when present.
|
|
36
|
+
*/
|
|
37
|
+
openHref?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* What the widget's own data layer reports through `Widget.DataState`. The
|
|
41
|
+
* host cannot see a widget's queries, so every field is opt-in: the header
|
|
42
|
+
* menu's Refresh, the error view's Try again, and the "Updated …" footer each
|
|
43
|
+
* appear only when the widget supplies what they need.
|
|
44
|
+
*/
|
|
45
|
+
export interface WidgetData {
|
|
46
|
+
/** Epoch milliseconds of the last successful load; absent when unknown. */
|
|
47
|
+
updatedAt?: number;
|
|
48
|
+
/** A reload is in flight while data is already on screen. */
|
|
49
|
+
refreshing: boolean;
|
|
50
|
+
/** Reloads the widget's data. */
|
|
51
|
+
refresh?: () => void;
|
|
32
52
|
}
|
|
33
53
|
export interface WidgetContextValue {
|
|
34
54
|
state: WidgetState;
|
|
35
55
|
actions: WidgetActions;
|
|
36
56
|
meta: WidgetMeta;
|
|
57
|
+
data?: WidgetData;
|
|
37
58
|
}
|
|
38
59
|
export declare const WidgetContext: import("react").Context<WidgetContextValue | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-context.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-context.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"widget-context.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-context.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAyEtC,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAA4B,IAAI,CAAC,CAAC","sourcesContent":["'use client';\n\nimport { createContext } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\n\nexport type WidgetRenderState =\n | 'active'\n | 'disabled'\n | 'loading'\n | 'error'\n | 'inactive';\n\nexport interface WidgetState {\n state: WidgetRenderState;\n collapsed: boolean;\n pinned: boolean;\n pendingCollapse: boolean;\n pendingPin: boolean;\n}\n\nexport interface WidgetActions {\n toggleCollapsed: () => void;\n togglePinned: () => void;\n}\n\nexport interface WidgetHealth {\n status: 'healthy' | 'degraded' | 'unhealthy' | 'unknown';\n lastSuccessAt?: string | null;\n errorMessage?: string | null;\n}\n\nexport interface WidgetMeta {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n maxContentHeight?: string;\n isCollapsible: boolean;\n isPinnable: boolean;\n askContext?: WidgetAskContext;\n aiFooterEnabled: boolean;\n className?: string;\n /** Integration health for this widget's product, if known. */\n health?: WidgetHealth;\n /**\n * Where the widget's full surface lives, e.g. the workspace a pinned widget\n * came from. Set by the host only where following it leaves the current\n * page; the header menu shows \"Open in workspace\" when present.\n */\n openHref?: string;\n}\n\n/**\n * What the widget's own data layer reports through `Widget.DataState`. The\n * host cannot see a widget's queries, so every field is opt-in: the header\n * menu's Refresh, the error view's Try again, and the \"Updated …\" footer each\n * appear only when the widget supplies what they need.\n */\nexport interface WidgetData {\n /** Epoch milliseconds of the last successful load; absent when unknown. */\n updatedAt?: number;\n /** A reload is in flight while data is already on screen. */\n refreshing: boolean;\n /** Reloads the widget's data. */\n refresh?: () => void;\n}\n\nexport interface WidgetContextValue {\n state: WidgetState;\n actions: WidgetActions;\n meta: WidgetMeta;\n data?: WidgetData;\n}\n\nexport const WidgetContext = createContext<WidgetContextValue | null>(null);\n"]}
|
|
@@ -20,7 +20,7 @@ export interface WidgetPreviewProviderProps {
|
|
|
20
20
|
children: ReactNode;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* Renders a widget outside the
|
|
23
|
+
* Renders a widget outside the Ekanos dashboard.
|
|
24
24
|
*
|
|
25
25
|
* `Widget.*` reads everything — render state, collapse/pin, title — from
|
|
26
26
|
* `WidgetContext`, which in production is filled by the host's
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useCallback, useMemo, useState } from 'react';
|
|
4
4
|
import { WidgetContext, } from './widget-context.js';
|
|
5
5
|
/**
|
|
6
|
-
* Renders a widget outside the
|
|
6
|
+
* Renders a widget outside the Ekanos dashboard.
|
|
7
7
|
*
|
|
8
8
|
* `Widget.*` reads everything — render state, collapse/pin, title — from
|
|
9
9
|
* `WidgetContext`, which in production is filled by the host's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-preview-provider.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-preview-provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvE,OAAO,EACL,aAAa,GAId,MAAM,kBAAkB,CAAC;AAqB1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,GAAG,QAAQ,EAChB,aAAa,GAAG,KAAK,EACrB,UAAU,GAAG,KAAK,EAClB,gBAAgB,GAAG,KAAK,EACxB,aAAa,GAAG,KAAK,EACrB,gBAAgB,EAChB,UAAU,EACV,eAAe,GAAG,KAAK,EACvB,MAAM,EACN,QAAQ,GACmB;IAC3B,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC7D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IAEpD,MAAM,eAAe,GAAG,WAAW,CACjC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC3C,EAAE,CACH,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAC9B,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EACxC,EAAE,CACH,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,KAAK,EAAE;YACL,KAAK;YACL,SAAS;YACT,MAAM;YACN,eAAe,EAAE,KAAK;YACtB,UAAU,EAAE,KAAK;SAClB;QACD,OAAO,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE;QAC1C,IAAI,EAAE;YACJ,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,WAAW;YACX,SAAS;YACT,gBAAgB;YAChB,aAAa;YACb,UAAU;YACV,UAAU;YACV,eAAe;YACf,MAAM;SACP;KACF,CAAC,EACF;QACE,eAAe;QACf,UAAU;QACV,SAAS;QACT,SAAS;QACT,WAAW;QACX,MAAM;QACN,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,MAAM;QACN,KAAK;QACL,QAAQ;QACR,KAAK;QACL,eAAe;QACf,YAAY;QACZ,QAAQ;KACT,CACF,CAAC;IAEF,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAiB,CAAC;AACjE,CAAC","sourcesContent":["'use client';\n\nimport { type ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport {\n WidgetContext,\n type WidgetContextValue,\n type WidgetHealth,\n type WidgetRenderState,\n} from './widget-context';\n\nexport interface WidgetPreviewProviderProps {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n className?: string;\n /** Defaults to `'active'` — the state you almost always want to look at. */\n state?: WidgetRenderState;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n defaultCollapsed?: boolean;\n defaultPinned?: boolean;\n maxContentHeight?: string;\n askContext?: WidgetAskContext;\n aiFooterEnabled?: boolean;\n health?: WidgetHealth;\n children: ReactNode;\n}\n\n/**\n * Renders a widget outside the
|
|
1
|
+
{"version":3,"file":"widget-preview-provider.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-preview-provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvE,OAAO,EACL,aAAa,GAId,MAAM,kBAAkB,CAAC;AAqB1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,GAAG,QAAQ,EAChB,aAAa,GAAG,KAAK,EACrB,UAAU,GAAG,KAAK,EAClB,gBAAgB,GAAG,KAAK,EACxB,aAAa,GAAG,KAAK,EACrB,gBAAgB,EAChB,UAAU,EACV,eAAe,GAAG,KAAK,EACvB,MAAM,EACN,QAAQ,GACmB;IAC3B,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC7D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IAEpD,MAAM,eAAe,GAAG,WAAW,CACjC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC3C,EAAE,CACH,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAC9B,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EACxC,EAAE,CACH,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,KAAK,EAAE;YACL,KAAK;YACL,SAAS;YACT,MAAM;YACN,eAAe,EAAE,KAAK;YACtB,UAAU,EAAE,KAAK;SAClB;QACD,OAAO,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE;QAC1C,IAAI,EAAE;YACJ,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,WAAW;YACX,SAAS;YACT,gBAAgB;YAChB,aAAa;YACb,UAAU;YACV,UAAU;YACV,eAAe;YACf,MAAM;SACP;KACF,CAAC,EACF;QACE,eAAe;QACf,UAAU;QACV,SAAS;QACT,SAAS;QACT,WAAW;QACX,MAAM;QACN,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,MAAM;QACN,KAAK;QACL,QAAQ;QACR,KAAK;QACL,eAAe;QACf,YAAY;QACZ,QAAQ;KACT,CACF,CAAC;IAEF,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAiB,CAAC;AACjE,CAAC","sourcesContent":["'use client';\n\nimport { type ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport {\n WidgetContext,\n type WidgetContextValue,\n type WidgetHealth,\n type WidgetRenderState,\n} from './widget-context';\n\nexport interface WidgetPreviewProviderProps {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n className?: string;\n /** Defaults to `'active'` — the state you almost always want to look at. */\n state?: WidgetRenderState;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n defaultCollapsed?: boolean;\n defaultPinned?: boolean;\n maxContentHeight?: string;\n askContext?: WidgetAskContext;\n aiFooterEnabled?: boolean;\n health?: WidgetHealth;\n children: ReactNode;\n}\n\n/**\n * Renders a widget outside the Ekanos dashboard.\n *\n * `Widget.*` reads everything — render state, collapse/pin, title — from\n * `WidgetContext`, which in production is filled by the host's\n * `DashboardWidgetProvider` (config hierarchy, persistence callbacks, health\n * resolution). None of that exists in a partner's own app, a Storybook story,\n * or a component test, and without SOME provider every `Widget.*` throws. This\n * is that provider: same context value, local state only, nothing persisted.\n *\n * ```tsx\n * <WidgetPreviewProvider widgetId=\"my-widget\" title=\"My widget\">\n * <MyWidget accountId={accountId} productSlug=\"my-integration\" />\n * </WidgetPreviewProvider>\n * ```\n *\n * Flip `state` to walk every branch your widget renders (`'loading'`,\n * `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.\n */\nexport function WidgetPreviewProvider({\n widgetId,\n title,\n subtitle,\n description,\n className,\n state = 'active',\n isCollapsible = false,\n isPinnable = false,\n defaultCollapsed = false,\n defaultPinned = false,\n maxContentHeight,\n askContext,\n aiFooterEnabled = false,\n health,\n children,\n}: WidgetPreviewProviderProps) {\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const [pinned, setPinned] = useState(defaultPinned);\n\n const toggleCollapsed = useCallback(\n () => setCollapsed((previous) => !previous),\n [],\n );\n\n const togglePinned = useCallback(\n () => setPinned((previous) => !previous),\n [],\n );\n\n const value = useMemo<WidgetContextValue>(\n () => ({\n state: {\n state,\n collapsed,\n pinned,\n pendingCollapse: false,\n pendingPin: false,\n },\n actions: { toggleCollapsed, togglePinned },\n meta: {\n widgetId,\n title,\n subtitle,\n description,\n className,\n maxContentHeight,\n isCollapsible,\n isPinnable,\n askContext,\n aiFooterEnabled,\n health,\n },\n }),\n [\n aiFooterEnabled,\n askContext,\n className,\n collapsed,\n description,\n health,\n isCollapsible,\n isPinnable,\n maxContentHeight,\n pinned,\n state,\n subtitle,\n title,\n toggleCollapsed,\n togglePinned,\n widgetId,\n ],\n );\n\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n"]}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export declare function WidgetLoadingState(): import("react").JSX.Element;
|
|
2
|
-
export declare function WidgetErrorState(
|
|
2
|
+
export declare function WidgetErrorState({ onRetry, retrying, }: {
|
|
3
|
+
onRetry?: () => void;
|
|
4
|
+
retrying?: boolean;
|
|
5
|
+
}): import("react").JSX.Element;
|
|
3
6
|
export declare function WidgetInactiveState(): import("react").JSX.Element;
|
|
4
7
|
export declare function WidgetDataLoading(): import("react").JSX.Element;
|
|
5
8
|
export declare function WidgetDisabledState(): import("react").JSX.Element;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '@ekanos/ui/button';
|
|
2
3
|
import { Icon } from '@ekanos/ui/icon';
|
|
3
4
|
import { Trans } from '@ekanos/ui/trans';
|
|
5
|
+
import { cn } from '@ekanos/ui/utils';
|
|
4
6
|
export function WidgetLoadingState() {
|
|
5
|
-
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [_jsx(Icon, { name: "fa-light fa-spinner", className: "text-muted-foreground mb-4 h-8 w-8 animate-spin" }), _jsx("p", { className: "text-muted-foreground text-sm", children: _jsx(Trans, { i18nKey: "dashboard:widgetLoading", defaults: "Loading..." }) })] }));
|
|
7
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [_jsx(Icon, { name: "fa-light fa-spinner-third", className: "text-muted-foreground mb-4 h-8 w-8 animate-spin" }), _jsx("p", { className: "text-muted-foreground text-sm", children: _jsx(Trans, { i18nKey: "dashboard:widgetLoading", defaults: "Loading..." }) })] }));
|
|
6
8
|
}
|
|
7
|
-
export function WidgetErrorState() {
|
|
8
|
-
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [_jsx(Icon, { name: "fa-light fa-circle-exclamation", className: "text-destructive
|
|
9
|
+
export function WidgetErrorState({ onRetry, retrying = false, }) {
|
|
10
|
+
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: _jsx(Trans, { i18nKey: "dashboard:widgetErrorTitle", defaults: "Couldn't load this widget" }) }), _jsx("p", { className: "text-muted-foreground text-sm", children: 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] }));
|
|
9
11
|
}
|
|
10
12
|
export function WidgetInactiveState() {
|
|
11
13
|
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: _jsx(Trans, { i18nKey: "dashboard:widgetInactive", defaults: "Widget inactive" }) }) }));
|
|
12
14
|
}
|
|
13
15
|
export function WidgetDataLoading() {
|
|
14
|
-
return (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Icon, { name: "fa-light fa-spinner", className: "text-muted-foreground h-8 w-8 animate-spin" }) }));
|
|
16
|
+
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" }) }));
|
|
15
17
|
}
|
|
16
18
|
export function WidgetDisabledState() {
|
|
17
19
|
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" }) })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-state-components.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-state-components.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"widget-state-components.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-state-components.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEtC,MAAM,UAAU,kBAAkB;IAChC,OAAO,CACL,eAAK,SAAS,EAAC,4DAA4D,aACzE,KAAC,IAAI,IACH,IAAI,EAAC,2BAA2B,EAChC,SAAS,EAAC,iDAAiD,GAC3D,EACF,YAAG,SAAS,EAAC,+BAA+B,YAC1C,KAAC,KAAK,IAAC,OAAO,EAAC,yBAAyB,EAAC,QAAQ,EAAC,YAAY,GAAG,GAC/D,IACA,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAC/B,OAAO,EACP,QAAQ,GAAG,KAAK,GAIjB;IACC,OAAO,CACL,eACE,IAAI,EAAC,OAAO,EACZ,SAAS,EAAC,uEAAuE,aAEjF,cAAK,SAAS,EAAC,2EAA2E,YACxF,KAAC,IAAI,IACH,IAAI,EAAC,gCAAgC,EACrC,SAAS,EAAC,0BAA0B,wBAEpC,GACE,EACN,eAAK,SAAS,EAAC,WAAW,aACxB,YAAG,SAAS,EAAC,qCAAqC,YAChD,KAAC,KAAK,IACJ,OAAO,EAAC,4BAA4B,EACpC,QAAQ,EAAC,2BAA2B,GACpC,GACA,EACJ,YAAG,SAAS,EAAC,+BAA+B,YACzC,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,KAAK,IACJ,OAAO,EAAC,gCAAgC,EACxC,QAAQ,EAAC,4CAA4C,GACrD,CACH,CAAC,CAAC,CAAC,CACF,KAAC,KAAK,IACJ,OAAO,EAAC,2BAA2B,EACnC,QAAQ,EAAC,8BAA8B,GACvC,CACH,GACC,IACA,EACL,OAAO,CAAC,CAAC,CAAC,CACT,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,eACR,oBAAoB,aAE9B,KAAC,IAAI,IACH,IAAI,EAAC,2BAA2B,EAChC,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,QAAQ,IAAI,cAAc,CAAC,wBAEpD,EACF,KAAC,KAAK,IAAC,OAAO,EAAC,uBAAuB,EAAC,QAAQ,EAAC,WAAW,GAAG,IACvD,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,CACL,cAAK,SAAS,EAAC,4DAA4D,YACzE,YAAG,SAAS,EAAC,+BAA+B,YAC1C,KAAC,KAAK,IAAC,OAAO,EAAC,0BAA0B,EAAC,QAAQ,EAAC,iBAAiB,GAAG,GACrE,GACA,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CACL,cAAK,SAAS,EAAC,+CAA+C,YAC5D,KAAC,IAAI,IACH,IAAI,EAAC,2BAA2B,EAChC,SAAS,EAAC,4CAA4C,GACtD,GACE,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,CACL,eAAK,SAAS,EAAC,4DAA4D,aACzE,KAAC,IAAI,IACH,IAAI,EAAC,iBAAiB,EACtB,SAAS,EAAC,oCAAoC,GAC9C,EACF,YAAG,SAAS,EAAC,+BAA+B,YAC1C,KAAC,KAAK,IAAC,OAAO,EAAC,0BAA0B,EAAC,QAAQ,EAAC,iBAAiB,GAAG,GACrE,IACA,CACP,CAAC;AACJ,CAAC","sourcesContent":["import { Button } from '@ekanos/ui/button';\nimport { Icon } from '@ekanos/ui/icon';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\nexport function WidgetLoadingState() {\n return (\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"text-muted-foreground mb-4 h-8 w-8 animate-spin\"\n />\n <p className=\"text-muted-foreground text-sm\">\n <Trans i18nKey=\"dashboard:widgetLoading\" defaults=\"Loading...\" />\n </p>\n </div>\n );\n}\n\nexport function WidgetErrorState({\n onRetry,\n retrying = false,\n}: {\n onRetry?: () => void;\n retrying?: boolean;\n}) {\n return (\n <div\n role=\"alert\"\n className=\"flex 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:widgetErrorTitle\"\n defaults=\"Couldn't load this widget\"\n />\n </p>\n <p className=\"text-muted-foreground text-sm\">\n {onRetry ? (\n <Trans\n i18nKey=\"dashboard:widgetErrorBodyRetry\"\n defaults=\"Try again, or check back in a few minutes.\"\n />\n ) : (\n <Trans\n i18nKey=\"dashboard:widgetErrorBody\"\n defaults=\"Check back in a few minutes.\"\n />\n )}\n </p>\n </div>\n {onRetry ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={onRetry}\n disabled={retrying}\n data-test=\"widget-error-retry\"\n >\n <Icon\n name=\"fa-light fa-arrows-rotate\"\n className={cn('h-4 w-4', retrying && 'animate-spin')}\n aria-hidden\n />\n <Trans i18nKey=\"dashboard:widgetRetry\" defaults=\"Try again\" />\n </Button>\n ) : null}\n </div>\n );\n}\n\nexport function WidgetInactiveState() {\n return (\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <p className=\"text-muted-foreground text-sm\">\n <Trans i18nKey=\"dashboard:widgetInactive\" defaults=\"Widget inactive\" />\n </p>\n </div>\n );\n}\n\nexport function WidgetDataLoading() {\n return (\n <div className=\"flex flex-1 items-center justify-center py-12\">\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"text-muted-foreground h-8 w-8 animate-spin\"\n />\n </div>\n );\n}\n\nexport function WidgetDisabledState() {\n return (\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <Icon\n name=\"fa-light fa-ban\"\n className=\"text-muted-foreground mb-4 h-8 w-8\"\n />\n <p className=\"text-muted-foreground text-sm\">\n <Trans i18nKey=\"dashboard:widgetDisabled\" defaults=\"Widget Disabled\" />\n </p>\n </div>\n );\n}\n"]}
|
|
@@ -3,15 +3,35 @@ import type { WidgetAskContext } from '../../types/widget-ask-context.js';
|
|
|
3
3
|
declare function WidgetCard({ children }: {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
}): import("react").JSX.Element;
|
|
6
|
-
declare function WidgetHeader({ title, subtitle, description, children, }: {
|
|
6
|
+
declare function WidgetHeader({ title, subtitle, description, menu, children, }: {
|
|
7
7
|
/** Override the provider's title — useful when the rendered name differs from the config name. */
|
|
8
8
|
title?: string;
|
|
9
9
|
/** Override the provider's subtitle — useful for widget-computed values. */
|
|
10
10
|
subtitle?: string;
|
|
11
11
|
/** Override the provider's description — useful for widget-computed values. */
|
|
12
12
|
description?: string;
|
|
13
|
+
/**
|
|
14
|
+
* `Widget.MenuItem`s placed at the top of the header's ⋯ menu. Use it for
|
|
15
|
+
* occasional actions — "Open in QuickBooks", "Open in Outlook" — and keep
|
|
16
|
+
* `children` for the controls people reach for every visit, like filters.
|
|
17
|
+
*/
|
|
18
|
+
menu?: ReactNode;
|
|
13
19
|
children?: ReactNode;
|
|
14
20
|
}): import("react").JSX.Element | null;
|
|
21
|
+
/**
|
|
22
|
+
* One widget-specific entry in the header's ⋯ menu; pass it through
|
|
23
|
+
* `Widget.Header`'s `menu` prop. With `href` it is a link, opened in a new tab
|
|
24
|
+
* when `external`; otherwise it runs `onSelect`.
|
|
25
|
+
*/
|
|
26
|
+
declare function WidgetMenuItem({ icon, href, external, onSelect, disabled, children, }: {
|
|
27
|
+
/** A Font Awesome class, e.g. `fa-light fa-envelope`. */
|
|
28
|
+
icon?: string;
|
|
29
|
+
href?: string;
|
|
30
|
+
external?: boolean;
|
|
31
|
+
onSelect?: () => void;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}): import("react").JSX.Element;
|
|
15
35
|
/**
|
|
16
36
|
* The standard padded body for content rendered inside `Widget.Active`.
|
|
17
37
|
*
|
|
@@ -95,8 +115,22 @@ declare function WidgetHealthFooter(): import("react").JSX.Element | null;
|
|
|
95
115
|
* </Widget.DataState>
|
|
96
116
|
* );
|
|
97
117
|
* ```
|
|
118
|
+
*
|
|
119
|
+
* Pass `onRefresh`, `refreshing` and `updatedAt` to give the widget the
|
|
120
|
+
* shared Refresh menu item, a Try again button on its error view, and an
|
|
121
|
+
* "Updated 3 minutes ago" footer. With TanStack Query:
|
|
122
|
+
*
|
|
123
|
+
* ```tsx
|
|
124
|
+
* <Widget.DataState
|
|
125
|
+
* loading={query.isLoading}
|
|
126
|
+
* error={query.isError}
|
|
127
|
+
* onRefresh={() => void query.refetch()}
|
|
128
|
+
* refreshing={query.isFetching}
|
|
129
|
+
* updatedAt={query.dataUpdatedAt}
|
|
130
|
+
* >
|
|
131
|
+
* ```
|
|
98
132
|
*/
|
|
99
|
-
declare function WidgetDataState({ loading, error, inactive, children, }: {
|
|
133
|
+
declare function WidgetDataState({ loading, error, inactive, onRefresh, refreshing, updatedAt, children, }: {
|
|
100
134
|
loading?: boolean;
|
|
101
135
|
error?: boolean;
|
|
102
136
|
/**
|
|
@@ -106,11 +140,22 @@ declare function WidgetDataState({ loading, error, inactive, children, }: {
|
|
|
106
140
|
* over `loading` / `error`.
|
|
107
141
|
*/
|
|
108
142
|
inactive?: boolean;
|
|
143
|
+
/** Reloads the widget's data. */
|
|
144
|
+
onRefresh?: () => void;
|
|
145
|
+
/** A reload is in flight. */
|
|
146
|
+
refreshing?: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* When the data on screen last loaded successfully: epoch milliseconds, an
|
|
149
|
+
* ISO string or a `Date`. `0` means never, which TanStack Query's
|
|
150
|
+
* `dataUpdatedAt` reports before the first success.
|
|
151
|
+
*/
|
|
152
|
+
updatedAt?: number | string | Date | null;
|
|
109
153
|
children: ReactNode;
|
|
110
154
|
}): import("react").JSX.Element;
|
|
111
155
|
export declare const Widget: {
|
|
112
156
|
Card: typeof WidgetCard;
|
|
113
157
|
Header: typeof WidgetHeader;
|
|
158
|
+
MenuItem: typeof WidgetMenuItem;
|
|
114
159
|
DataState: typeof WidgetDataState;
|
|
115
160
|
Active: typeof WidgetActive;
|
|
116
161
|
Body: typeof WidgetBody;
|