@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.
Files changed (61) hide show
  1. package/README.md +99 -13
  2. package/api-report.md +51 -6
  3. package/dist/components/index.d.ts +3 -3
  4. package/dist/components/index.js +2 -2
  5. package/dist/components/index.js.map +1 -1
  6. package/dist/components/widgets/widget-context.d.ts +17 -0
  7. package/dist/components/widgets/widget-context.js.map +1 -1
  8. package/dist/components/widgets/widget-preview-provider.d.ts +4 -2
  9. package/dist/components/widgets/widget-preview-provider.js +3 -1
  10. package/dist/components/widgets/widget-preview-provider.js.map +1 -1
  11. package/dist/components/widgets/widget-state-components.d.ts +59 -3
  12. package/dist/components/widgets/widget-state-components.js +65 -7
  13. package/dist/components/widgets/widget-state-components.js.map +1 -1
  14. package/dist/components/widgets/widget.d.ts +124 -21
  15. package/dist/components/widgets/widget.js +159 -37
  16. package/dist/components/widgets/widget.js.map +1 -1
  17. package/dist/hooks/index.d.ts +2 -2
  18. package/dist/hooks/index.js +1 -1
  19. package/dist/hooks/index.js.map +1 -1
  20. package/dist/hooks/partner-oauth.js +1 -0
  21. package/dist/hooks/partner-oauth.js.map +1 -1
  22. package/dist/hooks/use-oauth-connection-status.d.ts +17 -1
  23. package/dist/hooks/use-oauth-connection-status.js +12 -17
  24. package/dist/hooks/use-oauth-connection-status.js.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/integration/actor-role.d.ts +14 -0
  28. package/dist/integration/actor-role.js +21 -0
  29. package/dist/integration/actor-role.js.map +1 -0
  30. package/dist/integration/index.d.ts +3 -1
  31. package/dist/integration/index.js +4 -0
  32. package/dist/integration/index.js.map +1 -1
  33. package/dist/integration/types.d.ts +1 -1
  34. package/dist/integration/types.js.map +1 -1
  35. package/dist/integration/untrusted-text.d.ts +24 -0
  36. package/dist/integration/untrusted-text.js +36 -0
  37. package/dist/integration/untrusted-text.js.map +1 -0
  38. package/dist/layout/index.d.ts +8 -0
  39. package/dist/layout/index.js +9 -0
  40. package/dist/layout/index.js.map +1 -0
  41. package/dist/layout/widget-layout.d.ts +84 -0
  42. package/dist/layout/widget-layout.js +138 -0
  43. package/dist/layout/widget-layout.js.map +1 -0
  44. package/dist/testing/index.d.ts +4 -0
  45. package/dist/testing/index.js +5 -0
  46. package/dist/testing/index.js.map +1 -1
  47. package/dist/testing/mock-context.js +3 -1
  48. package/dist/testing/mock-context.js.map +1 -1
  49. package/dist/testing/verify-signature.d.ts +26 -0
  50. package/dist/testing/verify-signature.js +45 -0
  51. package/dist/testing/verify-signature.js.map +1 -0
  52. package/dist/types/index.d.ts +1 -0
  53. package/dist/types/index.js.map +1 -1
  54. package/dist/types/integration.d.ts +5 -0
  55. package/dist/types/integration.js.map +1 -1
  56. package/dist/types/widget-verdict.d.ts +6 -0
  57. package/dist/types/widget-verdict.js +2 -0
  58. package/dist/types/widget-verdict.js.map +1 -0
  59. package/eslint.design-preset.mjs +226 -0
  60. package/eslint.design-rules.mjs +437 -0
  61. package/package.json +16 -6
@@ -1,16 +1,74 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Link from 'next/link.js';
2
3
  import { Button } from '@ekanos/ui/button';
3
4
  import { Icon } from '@ekanos/ui/icon';
5
+ import { Skeleton } from '@ekanos/ui/skeleton';
4
6
  import { Trans } from '@ekanos/ui/trans';
5
7
  import { cn } from '@ekanos/ui/utils';
6
- export function WidgetLoadingState() {
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..." }) })] }));
8
+ function StateAction({ action, variant = 'default', }) {
9
+ if (action.href) {
10
+ const link = action.external ? (_jsx("a", { href: action.href, target: "_blank", rel: "noopener noreferrer" })) : (_jsx(Link, { href: action.href }));
11
+ return (_jsx(Button, { size: "sm", variant: variant, render: link, nativeButton: false, children: action.label }));
12
+ }
13
+ return (_jsx(Button, { size: "sm", variant: variant, type: "button", onClick: action.onClick, children: action.label }));
8
14
  }
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] }));
15
+ /**
16
+ * The shared shape of every non-data state: an icon, a title, one sentence
17
+ * and at most one action, left-aligned in the body's padding.
18
+ */
19
+ function StateNote({ icon, title, description, action, actionVariant, role, testId, }) {
20
+ return (_jsxs("div", { role: role, className: "flex flex-1 flex-col items-start gap-3 px-5 pt-2 pb-5", "data-test": testId, children: [_jsx(Icon, { name: icon, className: "text-muted-foreground h-6 w-6", "aria-hidden": true }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-foreground text-base font-medium", children: title }), description ? (_jsx("p", { className: "text-muted-foreground max-w-prose text-sm", children: description })) : null] }), action ? _jsx(StateAction, { action: action, variant: actionVariant }) : null] }));
11
21
  }
12
- export function WidgetInactiveState() {
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" }) }) }));
22
+ /**
23
+ * The empty state for a widget with nothing to show. Positive by default:
24
+ * an empty review queue is good news, so it reassures instead of apologizing.
25
+ */
26
+ export function WidgetEmptyState({ tone = 'positive', title, description, action, }) {
27
+ const positive = tone === 'positive';
28
+ return (_jsx(StateNote, { icon: positive ? 'fa-light fa-circle-check' : 'fa-light fa-inbox', title: title !== null && title !== void 0 ? title : (positive ? (_jsx(Trans, { i18nKey: "dashboard:widgetEmptyPositive", defaults: "You're all caught up" })) : (_jsx(Trans, { i18nKey: "dashboard:widgetEmptyNeutral", defaults: "Nothing here yet" }))), description: description, action: action, actionVariant: "outline", role: "status", testId: `widget-empty-${tone}` }));
29
+ }
30
+ const INACTIVE_ICON = {
31
+ 'not-connected': 'fa-light fa-plug',
32
+ 'no-access': 'fa-light fa-user-group',
33
+ 'setup-needed': 'fa-light fa-sliders',
34
+ };
35
+ /**
36
+ * Why the widget can't show data. `not-connected` offers one Connect button;
37
+ * `no-access` is a calm note that points to an admin, never a lock screen;
38
+ * `setup-needed` points to the integration's settings. Without a `reason` it
39
+ * keeps the original one-line "Widget inactive".
40
+ */
41
+ export function WidgetInactiveState({ reason, integrationName, title, description, action, } = {}) {
42
+ if (!reason) {
43
+ return (_jsx("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: _jsx("p", { className: "text-muted-foreground text-sm", children: title !== null && title !== void 0 ? title : (_jsx(Trans, { i18nKey: "dashboard:widgetInactive", defaults: "Widget inactive" })) }) }));
44
+ }
45
+ const name = integrationName !== null && integrationName !== void 0 ? integrationName : 'this integration';
46
+ const defaults = {
47
+ 'not-connected': {
48
+ title: (_jsx(Trans, { i18nKey: "dashboard:widgetNotConnectedTitle", defaults: "Connect {{name}}", values: { name } })),
49
+ description: (_jsx(Trans, { i18nKey: "dashboard:widgetNotConnectedBody", defaults: "Connect {{name}} and this card fills in with your own data.", values: { name } })),
50
+ },
51
+ 'no-access': {
52
+ title: (_jsx(Trans, { i18nKey: "dashboard:widgetNoAccessTitle", defaults: "Shared with admins for now" })),
53
+ description: (_jsx(Trans, { i18nKey: "dashboard:widgetNoAccessBody", defaults: "Your role doesn't include this data yet. Ask an admin if you need it." })),
54
+ },
55
+ 'setup-needed': {
56
+ title: (_jsx(Trans, { i18nKey: "dashboard:widgetSetupNeededTitle", defaults: "Finish setting up {{name}}", values: { name } })),
57
+ description: (_jsx(Trans, { i18nKey: "dashboard:widgetSetupNeededBody", defaults: "One more step and this card starts showing data." })),
58
+ },
59
+ };
60
+ return (_jsx(StateNote, { icon: INACTIVE_ICON[reason], title: title !== null && title !== void 0 ? title : defaults[reason].title, description: description !== null && description !== void 0 ? description : defaults[reason].description, action: action, actionVariant: reason === 'no-access' ? 'outline' : 'default', testId: `widget-inactive-${reason}` }));
61
+ }
62
+ /**
63
+ * Loading, drawn as the body it stands in for so the card doesn't jump when
64
+ * data lands. Rows suit lists and feeds, `stat` a headline figure, `chart` a
65
+ * chart, `schedule` a day of events.
66
+ */
67
+ export function WidgetLoadingSkeleton({ variant = 'rows', }) {
68
+ return (_jsxs("div", { role: "status", "aria-busy": "true", className: "flex flex-1 flex-col", "data-test": `widget-skeleton-${variant}`, children: [_jsx("span", { className: "sr-only", children: _jsx(Trans, { i18nKey: "dashboard:widgetLoading", defaults: "Loading..." }) }), variant === 'stat' ? (_jsxs("div", { className: "space-y-2 px-5 pt-2 pb-5", children: [_jsx(Skeleton, { className: "h-3 w-24" }), _jsx(Skeleton, { className: "h-8 w-40" }), _jsx(Skeleton, { className: "h-3 w-32" })] })) : variant === 'chart' ? (_jsxs("div", { className: "space-y-3 px-5 pt-2 pb-5", children: [_jsx(Skeleton, { className: "h-8 w-40" }), _jsx(Skeleton, { className: "h-60 w-full" })] })) : (_jsx("div", { className: "divide-border/50 divide-y", children: [0, 1, 2].map((row) => (_jsx("div", { className: cn('px-5 py-3', variant === 'schedule' ? 'flex items-start gap-3' : 'space-y-2'), children: variant === 'schedule' ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-4 w-14 shrink-0" }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Skeleton, { className: "h-4 w-2/3" }), _jsx(Skeleton, { className: "h-3 w-1/3" })] })] })) : (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-4 w-2/3" }), _jsx(Skeleton, { className: "h-3 w-1/2" })] })) }, row))) }))] }));
69
+ }
70
+ export function WidgetErrorState({ title, description, onRetry, retrying = false, }) {
71
+ return (_jsxs("div", { role: "alert", className: "flex flex-col items-center justify-center gap-3 px-5 py-8 text-center", children: [_jsx("div", { className: "bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full", children: _jsx(Icon, { name: "fa-light fa-circle-exclamation", className: "text-destructive h-5 w-5", "aria-hidden": true }) }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: title !== null && title !== void 0 ? title : (_jsx(Trans, { i18nKey: "dashboard:widgetErrorTitle", defaults: "Couldn't load this widget" })) }), _jsx("p", { className: "text-muted-foreground text-sm", children: description ? (description) : onRetry ? (_jsx(Trans, { i18nKey: "dashboard:widgetErrorBodyRetry", defaults: "Try again, or check back in a few minutes." })) : (_jsx(Trans, { i18nKey: "dashboard:widgetErrorBody", defaults: "Check back in a few minutes." })) })] }), onRetry ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: onRetry, disabled: retrying, "data-test": "widget-error-retry", children: [_jsx(Icon, { name: "fa-light fa-arrows-rotate", className: cn('h-4 w-4', retrying && 'animate-spin'), "aria-hidden": true }), _jsx(Trans, { i18nKey: "dashboard:widgetRetry", defaults: "Try again" })] })) : null] }));
14
72
  }
15
73
  export function WidgetDataLoading() {
16
74
  return (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Icon, { name: "fa-light fa-spinner-third", className: "text-muted-foreground h-8 w-8 animate-spin" }) }));
@@ -1 +1 @@
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"]}
1
+ {"version":3,"file":"widget-state-components.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-state-components.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react';\n\nimport Link from 'next/link.js';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Icon } from '@ekanos/ui/icon';\nimport { Skeleton } from '@ekanos/ui/skeleton';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\n/**\n * The one action a state view offers: \"Connect QuickBooks\", \"Ask an admin\",\n * \"Create a deal\". A link with `href`, otherwise a button running `onClick`.\n */\nexport interface WidgetStateAction {\n label: ReactNode;\n href?: string;\n /** Open `href` in a new tab. */\n external?: boolean;\n onClick?: () => void;\n}\n\n/** The body shape a loading skeleton stands in for. */\nexport type WidgetSkeletonVariant = 'rows' | 'stat' | 'chart' | 'schedule';\n\n/** Why an active integration still can't show this widget's data. */\nexport type WidgetInactiveReason =\n | 'not-connected'\n | 'no-access'\n | 'setup-needed';\n\nexport interface WidgetEmptyStateProps {\n /**\n * `positive` (the default) says nothing needs the owner: \"You're all caught\n * up\". `neutral` is for an empty list that isn't good news, like a search\n * with no results.\n */\n tone?: 'positive' | 'neutral';\n title?: ReactNode;\n description?: ReactNode;\n action?: WidgetStateAction;\n}\n\nexport interface WidgetInactiveStateProps {\n reason?: WidgetInactiveReason;\n /** The integration's name, for the default copy (\"Connect QuickBooks\"). */\n integrationName?: string;\n title?: ReactNode;\n /** One sentence on what connecting or access gives the owner. */\n description?: ReactNode;\n action?: WidgetStateAction;\n}\n\nfunction StateAction({\n action,\n variant = 'default',\n}: {\n action: WidgetStateAction;\n variant?: 'default' | 'outline';\n}) {\n if (action.href) {\n const link = action.external ? (\n <a href={action.href} target=\"_blank\" rel=\"noopener noreferrer\" />\n ) : (\n <Link href={action.href} />\n );\n\n return (\n <Button size=\"sm\" variant={variant} render={link} nativeButton={false}>\n {action.label}\n </Button>\n );\n }\n\n return (\n <Button size=\"sm\" variant={variant} type=\"button\" onClick={action.onClick}>\n {action.label}\n </Button>\n );\n}\n\n/**\n * The shared shape of every non-data state: an icon, a title, one sentence\n * and at most one action, left-aligned in the body's padding.\n */\nfunction StateNote({\n icon,\n title,\n description,\n action,\n actionVariant,\n role,\n testId,\n}: {\n icon: string;\n title: ReactNode;\n description?: ReactNode;\n action?: WidgetStateAction;\n actionVariant?: 'default' | 'outline';\n role?: 'alert' | 'status';\n testId: string;\n}) {\n return (\n <div\n role={role}\n className=\"flex flex-1 flex-col items-start gap-3 px-5 pt-2 pb-5\"\n data-test={testId}\n >\n <Icon name={icon} className=\"text-muted-foreground h-6 w-6\" aria-hidden />\n <div className=\"space-y-1\">\n <p className=\"text-foreground text-base font-medium\">{title}</p>\n {description ? (\n <p className=\"text-muted-foreground max-w-prose text-sm\">\n {description}\n </p>\n ) : null}\n </div>\n {action ? <StateAction action={action} variant={actionVariant} /> : null}\n </div>\n );\n}\n\n/**\n * The empty state for a widget with nothing to show. Positive by default:\n * an empty review queue is good news, so it reassures instead of apologizing.\n */\nexport function WidgetEmptyState({\n tone = 'positive',\n title,\n description,\n action,\n}: WidgetEmptyStateProps) {\n const positive = tone === 'positive';\n\n return (\n <StateNote\n icon={positive ? 'fa-light fa-circle-check' : 'fa-light fa-inbox'}\n title={\n title ??\n (positive ? (\n <Trans\n i18nKey=\"dashboard:widgetEmptyPositive\"\n defaults=\"You're all caught up\"\n />\n ) : (\n <Trans\n i18nKey=\"dashboard:widgetEmptyNeutral\"\n defaults=\"Nothing here yet\"\n />\n ))\n }\n description={description}\n action={action}\n actionVariant=\"outline\"\n role=\"status\"\n testId={`widget-empty-${tone}`}\n />\n );\n}\n\nconst INACTIVE_ICON: Record<WidgetInactiveReason, string> = {\n 'not-connected': 'fa-light fa-plug',\n 'no-access': 'fa-light fa-user-group',\n 'setup-needed': 'fa-light fa-sliders',\n};\n\n/**\n * Why the widget can't show data. `not-connected` offers one Connect button;\n * `no-access` is a calm note that points to an admin, never a lock screen;\n * `setup-needed` points to the integration's settings. Without a `reason` it\n * keeps the original one-line \"Widget inactive\".\n */\nexport function WidgetInactiveState({\n reason,\n integrationName,\n title,\n description,\n action,\n}: WidgetInactiveStateProps = {}) {\n if (!reason) {\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 {title ?? (\n <Trans\n i18nKey=\"dashboard:widgetInactive\"\n defaults=\"Widget inactive\"\n />\n )}\n </p>\n </div>\n );\n }\n\n const name = integrationName ?? 'this integration';\n\n const defaults: Record<\n WidgetInactiveReason,\n { title: ReactNode; description: ReactNode }\n > = {\n 'not-connected': {\n title: (\n <Trans\n i18nKey=\"dashboard:widgetNotConnectedTitle\"\n defaults=\"Connect {{name}}\"\n values={{ name }}\n />\n ),\n description: (\n <Trans\n i18nKey=\"dashboard:widgetNotConnectedBody\"\n defaults=\"Connect {{name}} and this card fills in with your own data.\"\n values={{ name }}\n />\n ),\n },\n 'no-access': {\n title: (\n <Trans\n i18nKey=\"dashboard:widgetNoAccessTitle\"\n defaults=\"Shared with admins for now\"\n />\n ),\n description: (\n <Trans\n i18nKey=\"dashboard:widgetNoAccessBody\"\n defaults=\"Your role doesn't include this data yet. Ask an admin if you need it.\"\n />\n ),\n },\n 'setup-needed': {\n title: (\n <Trans\n i18nKey=\"dashboard:widgetSetupNeededTitle\"\n defaults=\"Finish setting up {{name}}\"\n values={{ name }}\n />\n ),\n description: (\n <Trans\n i18nKey=\"dashboard:widgetSetupNeededBody\"\n defaults=\"One more step and this card starts showing data.\"\n />\n ),\n },\n };\n\n return (\n <StateNote\n icon={INACTIVE_ICON[reason]}\n title={title ?? defaults[reason].title}\n description={description ?? defaults[reason].description}\n action={action}\n actionVariant={reason === 'no-access' ? 'outline' : 'default'}\n testId={`widget-inactive-${reason}`}\n />\n );\n}\n\n/**\n * Loading, drawn as the body it stands in for so the card doesn't jump when\n * data lands. Rows suit lists and feeds, `stat` a headline figure, `chart` a\n * chart, `schedule` a day of events.\n */\nexport function WidgetLoadingSkeleton({\n variant = 'rows',\n}: {\n variant?: WidgetSkeletonVariant;\n}) {\n return (\n <div\n role=\"status\"\n aria-busy=\"true\"\n className=\"flex flex-1 flex-col\"\n data-test={`widget-skeleton-${variant}`}\n >\n <span className=\"sr-only\">\n <Trans i18nKey=\"dashboard:widgetLoading\" defaults=\"Loading...\" />\n </span>\n {variant === 'stat' ? (\n <div className=\"space-y-2 px-5 pt-2 pb-5\">\n <Skeleton className=\"h-3 w-24\" />\n <Skeleton className=\"h-8 w-40\" />\n <Skeleton className=\"h-3 w-32\" />\n </div>\n ) : variant === 'chart' ? (\n <div className=\"space-y-3 px-5 pt-2 pb-5\">\n <Skeleton className=\"h-8 w-40\" />\n <Skeleton className=\"h-60 w-full\" />\n </div>\n ) : (\n <div className=\"divide-border/50 divide-y\">\n {[0, 1, 2].map((row) => (\n <div\n key={row}\n className={cn(\n 'px-5 py-3',\n variant === 'schedule' ? 'flex items-start gap-3' : 'space-y-2',\n )}\n >\n {variant === 'schedule' ? (\n <>\n <Skeleton className=\"h-4 w-14 shrink-0\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"h-3 w-1/3\" />\n </div>\n </>\n ) : (\n <>\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport function WidgetErrorState({\n title,\n description,\n onRetry,\n retrying = false,\n}: {\n title?: ReactNode;\n description?: ReactNode;\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 {title ?? (\n <Trans\n i18nKey=\"dashboard:widgetErrorTitle\"\n defaults=\"Couldn't load this widget\"\n />\n )}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n {description ? (\n description\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 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"]}
@@ -1,21 +1,72 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { WidgetAskContext } from '../../types/widget-ask-context.js';
3
+ import type { WidgetVerdict } from '../../types/widget-verdict.js';
4
+ import { type WidgetEmptyStateProps, type WidgetInactiveStateProps, type WidgetSkeletonVariant } from './widget-state-components.js';
5
+ /**
6
+ * The verdict the widget reported through `Widget.DataState`, for a body that
7
+ * wants to echo it (an empty state, a summary row). `undefined` before the
8
+ * widget has one.
9
+ */
10
+ export declare function useWidgetVerdict(): WidgetVerdict | undefined;
3
11
  declare function WidgetCard({ children }: {
4
12
  children: ReactNode;
5
13
  }): import("react").JSX.Element;
6
- declare function WidgetHeader({ title, subtitle, description, menu, children, }: {
14
+ /**
15
+ * The header's one trailing control (the design system's widget rules): a "View all ›"
16
+ * link to where the data lives, or a period switch. Everything else goes in
17
+ * the ⋯ menu.
18
+ */
19
+ export type WidgetHeaderTrailing = {
20
+ kind: 'link';
21
+ href: string;
22
+ label: ReactNode;
23
+ /** Open in a new tab. */
24
+ external?: boolean;
25
+ } | {
26
+ kind: 'period';
27
+ value: string;
28
+ options: ReadonlyArray<{
29
+ value: string;
30
+ label: string;
31
+ }>;
32
+ onChange: (value: string) => void;
33
+ /** Accessible name; defaults to "Period". */
34
+ label?: string;
35
+ };
36
+ declare function WidgetHeader({ title, subtitle, description, context, trailing, sourceMark, menu, children, }: {
7
37
  /** Override the provider's title — useful when the rendered name differs from the config name. */
8
38
  title?: string;
9
- /** Override the provider's subtitle — useful for widget-computed values. */
39
+ /**
40
+ * Override the provider's subtitle. It renders as the context line when
41
+ * `context` is not set.
42
+ */
10
43
  subtitle?: string;
11
44
  /** Override the provider's description — useful for widget-computed values. */
12
45
  description?: string;
13
46
  /**
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.
47
+ * One muted line under the title that frames the data: "This month",
48
+ * "As of today", "3 accounts". A verdict that needs the owner replaces it;
49
+ * a clear one joins it ("This month · All caught up").
50
+ */
51
+ context?: ReactNode;
52
+ /** At most one control on the right: a "View all ›" link or a period switch. */
53
+ trailing?: WidgetHeaderTrailing;
54
+ /**
55
+ * Draw the integration's logo left of the title (the default) when the
56
+ * host supplies one. Pass `false` only for a card that mixes sources.
57
+ */
58
+ sourceMark?: boolean;
59
+ /**
60
+ * `Widget.MenuItem`s and `Widget.MenuChoice`s placed at the top of the
61
+ * header's ⋯ menu: occasional actions ("Open in QuickBooks") and secondary
62
+ * settings (a comparison basis) that don't earn the one trailing slot.
17
63
  */
18
64
  menu?: ReactNode;
65
+ /**
66
+ * @deprecated Free controls beside the ⋯ menu. Use `trailing` for the one
67
+ * control a header carries and `menu` for the rest; `children` goes away
68
+ * in a later major release.
69
+ */
19
70
  children?: ReactNode;
20
71
  }): import("react").JSX.Element | null;
21
72
  /**
@@ -32,6 +83,22 @@ declare function WidgetMenuItem({ icon, href, external, onSelect, disabled, chil
32
83
  disabled?: boolean;
33
84
  children: ReactNode;
34
85
  }): import("react").JSX.Element;
86
+ /**
87
+ * A setting that doesn't earn the header's one trailing slot (a comparison
88
+ * basis, an as-of month), as a submenu of choices in the ⋯ menu. Pass it
89
+ * through `Widget.Header`'s `menu` prop.
90
+ */
91
+ declare function WidgetMenuChoice<Value extends string>({ icon, label, value, options, onChange, }: {
92
+ /** A Font Awesome class, e.g. `fa-light fa-scale-balanced`. */
93
+ icon?: string;
94
+ label: ReactNode;
95
+ value: Value;
96
+ options: ReadonlyArray<{
97
+ value: Value;
98
+ label: ReactNode;
99
+ }>;
100
+ onChange: (value: Value) => void;
101
+ }): import("react").JSX.Element;
35
102
  /**
36
103
  * The standard padded body for content rendered inside `Widget.Active`.
37
104
  *
@@ -41,43 +108,71 @@ declare function WidgetMenuItem({ icon, href, external, onSelect, disabled, chil
41
108
  * card's sides while `Widget.Header` stays inset (`px-5`) unless the body
42
109
  * supplies its own padding. `Widget.Body` is that padding: `px-5` matches
43
110
  * the header's horizontal inset so header and body align, `pt-4 pb-5`
44
- * balances the header's `pb-4`/card's bottom edge.
111
+ * balances the header's `pb-3`/card's bottom edge.
45
112
  *
46
- * Use it for any widget whose content is NOT already full-bleed:
113
+ * Every widget wraps its content in it:
47
114
  *
48
115
  * ```tsx
49
116
  * <Widget.Active>
50
- * <Widget.Body>{/* rows, text, a KPI readout *\/}</Widget.Body>
117
+ * <Widget.Body>{/* text, a Stat, a form *\/}</Widget.Body>
51
118
  * </Widget.Active>
52
119
  * ```
53
120
  *
54
- * Skip it — render children directly inside `Widget.Active` — when the
55
- * content itself needs to run flush to the card edges (a chart, a table with
56
- * its own header row, an image).
121
+ * Pass `flush` when the content runs to the card's edges: edge-to-edge rows
122
+ * that pad themselves, a chart, a table with its own header row, an image.
57
123
  */
58
- declare function WidgetBody({ className, children, }: {
124
+ declare function WidgetBody({ flush, className, children, }: {
125
+ /**
126
+ * Drop the padding for content that runs to the card's edges: edge-to-edge
127
+ * rows (which pad themselves, `px-5 py-3`), a full-bleed chart, an image.
128
+ */
129
+ flush?: boolean;
59
130
  className?: string;
60
131
  children: ReactNode;
61
132
  }): import("react").JSX.Element;
62
133
  /**
63
- * Renders `children` once the widget's state is `active`. Intentionally
64
- * flush: its `CardContent` renders `p-0` (no horizontal/vertical inset) so a
65
- * chart, table, or other full-bleed content can run edge-to-edge — existing
66
- * widgets depend on this. For ordinary padded content, wrap `children` in
67
- * `Widget.Body` rather than hand-rolling a `px-*` wrapper.
134
+ * Renders `children` once the widget's state is `active`. Its `CardContent`
135
+ * is flush (`p-0`); wrap content in `Widget.Body`, which pads it to line up
136
+ * with the header, or `Widget.Body flush` for edge-to-edge rows and charts.
137
+ * Never hand-roll a `px-*` wrapper.
68
138
  */
69
139
  declare function WidgetActive({ children }: {
70
140
  children: ReactNode;
71
141
  }): import("react").JSX.Element | null;
72
- declare function WidgetLoading({ children }: {
142
+ /**
143
+ * Shows while the widget loads: a skeleton shaped like its body (`variant`,
144
+ * `rows` by default), never a spinner.
145
+ */
146
+ declare function WidgetLoading({ variant, children, }: {
147
+ variant?: WidgetSkeletonVariant;
73
148
  children?: ReactNode;
74
149
  }): import("react").JSX.Element | null;
75
- declare function WidgetError({ children }: {
150
+ /**
151
+ * Shows when the widget's data failed to load. Offers Try again whenever the
152
+ * widget reports `onRefresh` through `Widget.DataState`. `title` and
153
+ * `description` replace the default copy with something specific ("Couldn't
154
+ * load your inbox").
155
+ */
156
+ declare function WidgetError({ title, description, children, }: {
157
+ title?: ReactNode;
158
+ description?: ReactNode;
76
159
  children?: ReactNode;
77
160
  }): import("react").JSX.Element | null;
78
- declare function WidgetInactive({ children }: {
161
+ /**
162
+ * Shows when the widget can't reach its data. Give it a `reason`:
163
+ * `not-connected` (one Connect button in `action`), `no-access` (a calm note
164
+ * pointing to an admin) or `setup-needed`. `integrationName` defaults to the
165
+ * host's source name.
166
+ */
167
+ declare function WidgetInactive({ children, integrationName, ...props }: WidgetInactiveStateProps & {
79
168
  children?: ReactNode;
80
169
  }): import("react").JSX.Element | null;
170
+ /**
171
+ * What an active widget shows when it has nothing to list. Render it inside
172
+ * `Widget.Active`. Positive by default ("You're all caught up"), because an
173
+ * empty queue is good news; `tone="neutral"` for an empty search.
174
+ */
175
+ declare function WidgetEmpty(props: WidgetEmptyStateProps): import("react").JSX.Element;
81
176
  declare function WidgetDisabled({ children }: {
82
177
  children?: ReactNode;
83
178
  }): import("react").JSX.Element | null;
@@ -130,7 +225,7 @@ declare function WidgetHealthFooter(): import("react").JSX.Element | null;
130
225
  * >
131
226
  * ```
132
227
  */
133
- declare function WidgetDataState({ loading, error, inactive, onRefresh, refreshing, updatedAt, children, }: {
228
+ declare function WidgetDataState({ loading, error, inactive, onRefresh, refreshing, updatedAt, verdict, children, }: {
134
229
  loading?: boolean;
135
230
  error?: boolean;
136
231
  /**
@@ -150,15 +245,23 @@ declare function WidgetDataState({ loading, error, inactive, onRefresh, refreshi
150
245
  * `dataUpdatedAt` reports before the first success.
151
246
  */
152
247
  updatedAt?: number | string | Date | null;
248
+ /**
249
+ * The widget's verdict on its data (the design system's widget rules): `{ tone,
250
+ * sentence, count? }`. Compute it with the same function the integration's
251
+ * server `getAttention` uses, so the card and home's Needs you agree.
252
+ */
253
+ verdict?: WidgetVerdict;
153
254
  children: ReactNode;
154
255
  }): import("react").JSX.Element;
155
256
  export declare const Widget: {
156
257
  Card: typeof WidgetCard;
157
258
  Header: typeof WidgetHeader;
158
259
  MenuItem: typeof WidgetMenuItem;
260
+ MenuChoice: typeof WidgetMenuChoice;
159
261
  DataState: typeof WidgetDataState;
160
262
  Active: typeof WidgetActive;
161
263
  Body: typeof WidgetBody;
264
+ Empty: typeof WidgetEmpty;
162
265
  Loading: typeof WidgetLoading;
163
266
  Error: typeof WidgetError;
164
267
  Inactive: typeof WidgetInactive;