@ekanos/sdk 0.6.0 → 0.7.1
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 +100 -17
- 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/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 -5
|
@@ -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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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-
|
|
111
|
+
* balances the header's `pb-3`/card's bottom edge.
|
|
45
112
|
*
|
|
46
|
-
*
|
|
113
|
+
* Every widget wraps its content in it:
|
|
47
114
|
*
|
|
48
115
|
* ```tsx
|
|
49
116
|
* <Widget.Active>
|
|
50
|
-
* <Widget.Body>{/*
|
|
117
|
+
* <Widget.Body>{/* text, a Stat, a form *\/}</Widget.Body>
|
|
51
118
|
* </Widget.Active>
|
|
52
119
|
* ```
|
|
53
120
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
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`.
|
|
64
|
-
* flush
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|