@beechcms/widget-sdk 0.6.0-preview.3
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/dist/components/widget-empty.d.ts +9 -0
- package/dist/components/widget-empty.d.ts.map +1 -0
- package/dist/components/widget-empty.js +5 -0
- package/dist/components/widget-error.d.ts +13 -0
- package/dist/components/widget-error.d.ts.map +1 -0
- package/dist/components/widget-error.js +13 -0
- package/dist/components/widget-shell.d.ts +14 -0
- package/dist/components/widget-shell.d.ts.map +1 -0
- package/dist/components/widget-shell.js +10 -0
- package/dist/define-widget.d.ts +10 -0
- package/dist/define-widget.d.ts.map +1 -0
- package/dist/define-widget.js +19 -0
- package/dist/hooks/use-widget-data.d.ts +42 -0
- package/dist/hooks/use-widget-data.d.ts.map +1 -0
- package/dist/hooks/use-widget-data.js +101 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/lib/cn.d.ts +3 -0
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/lib/cn.js +7 -0
- package/dist/provider.d.ts +22 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +17 -0
- package/dist/widget-definition.d.ts +32 -0
- package/dist/widget-definition.d.ts.map +1 -0
- package/dist/widget-definition.js +3 -0
- package/package.json +46 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LucideIcon } from 'lucide-react';
|
|
2
|
+
export interface WidgetEmptyProps {
|
|
3
|
+
icon: LucideIcon;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Placeholder shown by a widget when it has nothing to render. */
|
|
8
|
+
export declare function WidgetEmpty({ icon: Icon, title, description }: WidgetEmptyProps): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=widget-empty.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-empty.d.ts","sourceRoot":"","sources":["../../src/components/widget-empty.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,gBAAgB,+BAQ/E"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Placeholder shown by a widget when it has nothing to render. */
|
|
3
|
+
export function WidgetEmpty({ icon: Icon, title, description }) {
|
|
4
|
+
return (_jsxs("div", { className: "flex h-full min-h-[80px] flex-col items-center justify-center gap-2 text-center", children: [_jsx(Icon, { className: "size-8 text-muted-foreground/40" }), _jsx("p", { className: "text-sm font-medium text-muted-foreground", children: title }), description && _jsx("p", { className: "text-xs text-muted-foreground/70", children: description })] }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface WidgetErrorProps {
|
|
2
|
+
message?: string;
|
|
3
|
+
onRetry?: () => void;
|
|
4
|
+
/** Label for the retry button. Default: "Retry". */
|
|
5
|
+
retryLabel?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Error state shown by a widget when its data fetch fails. Authors that
|
|
9
|
+
* need localized copy should pass `message`/`retryLabel` explicitly — this
|
|
10
|
+
* component has no i18n dependency.
|
|
11
|
+
*/
|
|
12
|
+
export declare function WidgetError({ message, onRetry, retryLabel }: WidgetErrorProps): import("react").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=widget-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-error.d.ts","sourceRoot":"","sources":["../../src/components/widget-error.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAiC,EAAE,OAAO,EAAE,UAAoB,EAAE,EAAE,gBAAgB,+BAsBjH"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
4
|
+
import { AlertCircle } from 'lucide-react';
|
|
5
|
+
import { cn } from '../lib/cn.js';
|
|
6
|
+
/**
|
|
7
|
+
* Error state shown by a widget when its data fetch fails. Authors that
|
|
8
|
+
* need localized copy should pass `message`/`retryLabel` explicitly — this
|
|
9
|
+
* component has no i18n dependency.
|
|
10
|
+
*/
|
|
11
|
+
export function WidgetError({ message = 'Something went wrong.', onRetry, retryLabel = 'Retry' }) {
|
|
12
|
+
return (_jsxs("div", { className: "flex h-full min-h-[80px] flex-col items-center justify-center gap-3 text-center", children: [_jsx(AlertCircle, { className: "size-8 text-destructive/60" }), _jsx("p", { className: "text-sm text-muted-foreground", children: message }), onRetry && (_jsx("button", { type: "button", onClick: onRetry, className: cn('inline-flex h-6 shrink-0 items-center justify-center gap-1.5 rounded-md border bg-background px-2', 'text-xs font-medium shadow-xs transition-all outline-none', 'hover:bg-accent hover:text-accent-foreground', 'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50', 'dark:border-input dark:bg-input/30 dark:hover:bg-input/50'), children: retryLabel }))] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { LucideIcon } from 'lucide-react';
|
|
3
|
+
export interface WidgetShellProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
bare?: boolean;
|
|
7
|
+
title?: string;
|
|
8
|
+
icon?: LucideIcon;
|
|
9
|
+
action?: ReactNode;
|
|
10
|
+
contentClassName?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Card chrome shared by every widget — built-in or custom. */
|
|
13
|
+
export declare function WidgetShell({ children, className, bare, title, icon: Icon, action, contentClassName, }: WidgetShellProps): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=widget-shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-shell.d.ts","sourceRoot":"","sources":["../../src/components/widget-shell.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,IAAY,EACZ,KAAK,EACL,IAAI,EAAE,IAAI,EACV,MAAM,EACN,gBAAgB,GACjB,EAAE,gBAAgB,+BAiClB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../lib/cn.js';
|
|
3
|
+
/** Card chrome shared by every widget — built-in or custom. */
|
|
4
|
+
export function WidgetShell({ children, className, bare = false, title, icon: Icon, action, contentClassName, }) {
|
|
5
|
+
if (bare) {
|
|
6
|
+
return _jsx("div", { className: cn('h-full w-full', className), children: children });
|
|
7
|
+
}
|
|
8
|
+
const hasHeader = !!(title || Icon || action);
|
|
9
|
+
return (_jsxs("div", { className: cn('h-full w-full flex flex-col', 'rounded-2xl border border-neutral-200/80 bg-white/75 backdrop-blur-md overflow-hidden', 'shadow-[0_1px_3px_0_rgb(0,0,0,0.05),0_1px_2px_-1px_rgb(0,0,0,0.04)]', 'transition-all duration-200 hover:shadow-[0_4px_12px_0_rgb(0,0,0,0.08),0_2px_4px_-1px_rgb(0,0,0,0.06)]', 'hover:border-neutral-300/80', 'dark:border-neutral-700/50 dark:bg-card/65 dark:hover:border-neutral-600/60', 'dark:shadow-[0_1px_3px_0_rgb(0,0,0,0.2)] dark:hover:shadow-[0_4px_12px_0_rgb(0,0,0,0.3)]', 'p-5', className), children: [hasHeader && (_jsxs("div", { className: "flex items-center justify-between mb-4 shrink-0", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [Icon && _jsx(Icon, { className: "size-4 text-muted-foreground" }), title && _jsx("h3", { className: "font-heading text-sm font-semibold text-foreground truncate", children: title })] }), action && _jsx("div", { className: "flex items-center", children: action })] })), _jsx("div", { className: cn('flex-1 min-h-0', contentClassName), children: children })] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WidgetDefinition } from './widget-definition.js';
|
|
2
|
+
/**
|
|
3
|
+
* Identity helper that gives widget authors full type inference for
|
|
4
|
+
* `defineWidget<TConfig>(...)` while validating the widget `type`.
|
|
5
|
+
*
|
|
6
|
+
* - `type` must match {@link WIDGET_TYPE_REGEX}.
|
|
7
|
+
* - The `core/` prefix is reserved for built-in widgets and is rejected.
|
|
8
|
+
*/
|
|
9
|
+
export declare function defineWidget<TConfig>(definition: WidgetDefinition<TConfig>): WidgetDefinition<TConfig>;
|
|
10
|
+
//# sourceMappingURL=define-widget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-widget.d.ts","sourceRoot":"","sources":["../src/define-widget.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAE9D;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAYtG"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { WIDGET_TYPE_REGEX } from '@beechcms/core';
|
|
4
|
+
/**
|
|
5
|
+
* Identity helper that gives widget authors full type inference for
|
|
6
|
+
* `defineWidget<TConfig>(...)` while validating the widget `type`.
|
|
7
|
+
*
|
|
8
|
+
* - `type` must match {@link WIDGET_TYPE_REGEX}.
|
|
9
|
+
* - The `core/` prefix is reserved for built-in widgets and is rejected.
|
|
10
|
+
*/
|
|
11
|
+
export function defineWidget(definition) {
|
|
12
|
+
if (definition.type.startsWith('core/')) {
|
|
13
|
+
throw new Error(`defineWidget: type "${definition.type}" uses the reserved "core/" prefix, which is reserved for built-in widgets.`);
|
|
14
|
+
}
|
|
15
|
+
if (!WIDGET_TYPE_REGEX.test(definition.type)) {
|
|
16
|
+
throw new Error(`defineWidget: type "${definition.type}" is invalid. It must match ${WIDGET_TYPE_REGEX}.`);
|
|
17
|
+
}
|
|
18
|
+
return definition;
|
|
19
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type AggregateFormula, type WidgetWindow, type TimeseriesPoint, type DistributionSlice } from '@beechcms/core';
|
|
2
|
+
export interface WidgetAggregateResponse {
|
|
3
|
+
value: number;
|
|
4
|
+
window: WidgetWindow;
|
|
5
|
+
}
|
|
6
|
+
export interface WidgetGrowthResponse {
|
|
7
|
+
current: number;
|
|
8
|
+
previous: number;
|
|
9
|
+
percentageChange: number;
|
|
10
|
+
trend: 'up' | 'down' | 'flat';
|
|
11
|
+
}
|
|
12
|
+
export interface WidgetTimeseriesResponse {
|
|
13
|
+
points: TimeseriesPoint[];
|
|
14
|
+
}
|
|
15
|
+
export interface WidgetDistributionResponse {
|
|
16
|
+
slices: DistributionSlice[];
|
|
17
|
+
}
|
|
18
|
+
export interface WidgetListEntry {
|
|
19
|
+
id: string;
|
|
20
|
+
slug: string;
|
|
21
|
+
status: string;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface WidgetListResponse {
|
|
27
|
+
entries: WidgetListEntry[];
|
|
28
|
+
total: number;
|
|
29
|
+
}
|
|
30
|
+
export interface WidgetListParams {
|
|
31
|
+
limit: number;
|
|
32
|
+
offset: number;
|
|
33
|
+
search?: string;
|
|
34
|
+
orderByColumn?: string;
|
|
35
|
+
orderDirection?: 'ASC' | 'DESC';
|
|
36
|
+
}
|
|
37
|
+
export declare function useWidgetAggregate(seedSlug: string, formula: AggregateFormula, window: WidgetWindow): import("@tanstack/react-query").UseQueryResult<NoInfer<WidgetAggregateResponse>, Error>;
|
|
38
|
+
export declare function useWidgetGrowth(seedSlug: string, formula: AggregateFormula, window: WidgetWindow): import("@tanstack/react-query").UseQueryResult<NoInfer<WidgetGrowthResponse>, Error>;
|
|
39
|
+
export declare function useWidgetTimeseries(seedSlug: string, formula: AggregateFormula, window: WidgetWindow, groupColumn: string): import("@tanstack/react-query").UseQueryResult<NoInfer<WidgetTimeseriesResponse>, Error>;
|
|
40
|
+
export declare function useWidgetDistribution(seedSlug: string, column: string, window: WidgetWindow, limit: number): import("@tanstack/react-query").UseQueryResult<NoInfer<WidgetDistributionResponse>, Error>;
|
|
41
|
+
export declare function useWidgetList(seedSlug: string, params: WidgetListParams): import("@tanstack/react-query").UseQueryResult<NoInfer<WidgetListResponse>, Error>;
|
|
42
|
+
//# sourceMappingURL=use-widget-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-widget-data.d.ts","sourceRoot":"","sources":["../../src/hooks/use-widget-data.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAKpI,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,YAAY,CAAA;CACrB;AAOD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,eAAe,EAAE,CAAA;CAC1B;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CAChC;AAgFD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,YAAY,2FAQnG;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,YAAY,wFAQhG;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,YAAY,EACpB,WAAW,EAAE,MAAM,4FASpB;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,8FAQ1G;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,sFAQvE"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { useQuery } from '@tanstack/react-query';
|
|
4
|
+
import { isDateRange } from '@beechcms/core';
|
|
5
|
+
import { useWidgetSdkClient } from '../provider.js';
|
|
6
|
+
const STALE_TIME = 60 * 1000;
|
|
7
|
+
/** Serialises a {@link WidgetWindow} into the route's `window` or `from`/`to` query params. */
|
|
8
|
+
function windowParams(window) {
|
|
9
|
+
return isDateRange(window) ? { from: window.from, to: window.to } : { window };
|
|
10
|
+
}
|
|
11
|
+
/** Translates the formula into the timeseries route's flat `formula`/`valueColumn` params. */
|
|
12
|
+
function timeseriesFormulaParams(formula) {
|
|
13
|
+
switch (formula.op) {
|
|
14
|
+
case 'sum':
|
|
15
|
+
case 'avg':
|
|
16
|
+
return { formula: formula.op, valueColumn: formula.column };
|
|
17
|
+
default:
|
|
18
|
+
return { formula: 'count' };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function fetchWidgetAggregate(client, seedSlug, formula, window) {
|
|
22
|
+
const res = await client.get(`/widget/aggregate/${seedSlug}`, {
|
|
23
|
+
params: { formula: JSON.stringify(formula), ...windowParams(window) },
|
|
24
|
+
});
|
|
25
|
+
return res.data;
|
|
26
|
+
}
|
|
27
|
+
async function fetchWidgetGrowth(client, seedSlug, formula, window) {
|
|
28
|
+
const res = await client.get(`/widget/growth/${seedSlug}`, {
|
|
29
|
+
params: { formula: JSON.stringify(formula), ...windowParams(window) },
|
|
30
|
+
});
|
|
31
|
+
return res.data;
|
|
32
|
+
}
|
|
33
|
+
async function fetchWidgetTimeseries(client, seedSlug, formula, window, groupColumn) {
|
|
34
|
+
const res = await client.get(`/widget/timeseries/${seedSlug}`, {
|
|
35
|
+
params: { ...timeseriesFormulaParams(formula), groupColumn, ...windowParams(window) },
|
|
36
|
+
});
|
|
37
|
+
return res.data;
|
|
38
|
+
}
|
|
39
|
+
async function fetchWidgetDistribution(client, seedSlug, column, window, limit) {
|
|
40
|
+
const res = await client.get(`/widget/distribution/${seedSlug}`, {
|
|
41
|
+
params: { column, limit, ...windowParams(window) },
|
|
42
|
+
});
|
|
43
|
+
return res.data;
|
|
44
|
+
}
|
|
45
|
+
async function fetchWidgetList(client, seedSlug, params) {
|
|
46
|
+
const res = await client.get(`/widget/list/${seedSlug}`, {
|
|
47
|
+
params: {
|
|
48
|
+
limit: params.limit,
|
|
49
|
+
offset: params.offset,
|
|
50
|
+
search: params.search,
|
|
51
|
+
orderBy: params.orderByColumn,
|
|
52
|
+
orderDir: params.orderDirection?.toLowerCase(),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return res.data;
|
|
56
|
+
}
|
|
57
|
+
export function useWidgetAggregate(seedSlug, formula, window) {
|
|
58
|
+
const client = useWidgetSdkClient();
|
|
59
|
+
return useQuery({
|
|
60
|
+
queryKey: ['widget', 'aggregate', seedSlug, formula, window],
|
|
61
|
+
queryFn: () => fetchWidgetAggregate(client, seedSlug, formula, window),
|
|
62
|
+
enabled: !!seedSlug,
|
|
63
|
+
staleTime: STALE_TIME,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function useWidgetGrowth(seedSlug, formula, window) {
|
|
67
|
+
const client = useWidgetSdkClient();
|
|
68
|
+
return useQuery({
|
|
69
|
+
queryKey: ['widget', 'growth', seedSlug, formula, window],
|
|
70
|
+
queryFn: () => fetchWidgetGrowth(client, seedSlug, formula, window),
|
|
71
|
+
enabled: !!seedSlug,
|
|
72
|
+
staleTime: STALE_TIME,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export function useWidgetTimeseries(seedSlug, formula, window, groupColumn) {
|
|
76
|
+
const client = useWidgetSdkClient();
|
|
77
|
+
return useQuery({
|
|
78
|
+
queryKey: ['widget', 'timeseries', seedSlug, formula, window, groupColumn],
|
|
79
|
+
queryFn: () => fetchWidgetTimeseries(client, seedSlug, formula, window, groupColumn),
|
|
80
|
+
enabled: !!seedSlug,
|
|
81
|
+
staleTime: STALE_TIME,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export function useWidgetDistribution(seedSlug, column, window, limit) {
|
|
85
|
+
const client = useWidgetSdkClient();
|
|
86
|
+
return useQuery({
|
|
87
|
+
queryKey: ['widget', 'distribution', seedSlug, column, window, limit],
|
|
88
|
+
queryFn: () => fetchWidgetDistribution(client, seedSlug, column, window, limit),
|
|
89
|
+
enabled: !!seedSlug && !!column,
|
|
90
|
+
staleTime: STALE_TIME,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function useWidgetList(seedSlug, params) {
|
|
94
|
+
const client = useWidgetSdkClient();
|
|
95
|
+
return useQuery({
|
|
96
|
+
queryKey: ['widget', 'list', seedSlug, params],
|
|
97
|
+
queryFn: () => fetchWidgetList(client, seedSlug, params),
|
|
98
|
+
enabled: !!seedSlug,
|
|
99
|
+
staleTime: STALE_TIME,
|
|
100
|
+
});
|
|
101
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beechcms/widget-sdk — public contract for custom dashboard widgets.
|
|
3
|
+
*
|
|
4
|
+
* Build-time registration only (decision D9): a custom widget is trusted
|
|
5
|
+
* code compiled into the operator's own dashboard build, not a runtime
|
|
6
|
+
* plugin. See `docs/custom-widgets.md`.
|
|
7
|
+
*/
|
|
8
|
+
export type { AggregateFormula, TimeWindow, DateRange, WidgetWindow, DistributionSlice, TimeseriesPoint, DashboardWidgetInstance, } from '@beechcms/core';
|
|
9
|
+
export { isDateRange } from '@beechcms/core';
|
|
10
|
+
export * from './widget-definition.js';
|
|
11
|
+
export * from './define-widget.js';
|
|
12
|
+
export * from './provider.js';
|
|
13
|
+
export * from './hooks/use-widget-data.js';
|
|
14
|
+
export { WidgetShell } from './components/widget-shell.js';
|
|
15
|
+
export type { WidgetShellProps } from './components/widget-shell.js';
|
|
16
|
+
export { WidgetEmpty } from './components/widget-empty.js';
|
|
17
|
+
export type { WidgetEmptyProps } from './components/widget-empty.js';
|
|
18
|
+
export { WidgetError } from './components/widget-error.js';
|
|
19
|
+
export type { WidgetErrorProps } from './components/widget-error.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,uBAAuB,GACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,4BAA4B,CAAA;AAE1C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
export { isDateRange } from '@beechcms/core';
|
|
4
|
+
export * from './widget-definition.js';
|
|
5
|
+
export * from './define-widget.js';
|
|
6
|
+
export * from './provider.js';
|
|
7
|
+
export * from './hooks/use-widget-data.js';
|
|
8
|
+
export { WidgetShell } from './components/widget-shell.js';
|
|
9
|
+
export { WidgetEmpty } from './components/widget-empty.js';
|
|
10
|
+
export { WidgetError } from './components/widget-error.js';
|
package/dist/lib/cn.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cn.d.ts","sourceRoot":"","sources":["../../src/lib/cn.ts"],"names":[],"mappings":"AAGA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
|
package/dist/lib/cn.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal HTTP client contract the SDK's data hooks depend on. The
|
|
4
|
+
* dashboard's Axios instance satisfies this shape — no Axios import lives
|
|
5
|
+
* in this package.
|
|
6
|
+
*/
|
|
7
|
+
export interface WidgetSdkClient {
|
|
8
|
+
get<T>(url: string, config?: {
|
|
9
|
+
params?: Record<string, unknown>;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
data: T;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export interface WidgetSdkProviderProps {
|
|
15
|
+
client: WidgetSdkClient;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
/** Mounted once by the host app, carrying its authenticated HTTP client. */
|
|
19
|
+
export declare function WidgetSdkProvider({ client, children }: WidgetSdkProviderProps): import("react").JSX.Element;
|
|
20
|
+
/** Used by SDK data hooks; throws if rendered outside a {@link WidgetSdkProvider}. */
|
|
21
|
+
export declare function useWidgetSdkClient(): WidgetSdkClient;
|
|
22
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,CAAA;CACzF;AAID,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,sBAAsB,+BAE7E;AAED,sFAAsF;AACtF,wBAAgB,kBAAkB,IAAI,eAAe,CAMpD"}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
4
|
+
import { createContext, useContext } from 'react';
|
|
5
|
+
const WidgetSdkContext = createContext(null);
|
|
6
|
+
/** Mounted once by the host app, carrying its authenticated HTTP client. */
|
|
7
|
+
export function WidgetSdkProvider({ client, children }) {
|
|
8
|
+
return _jsx(WidgetSdkContext.Provider, { value: client, children: children });
|
|
9
|
+
}
|
|
10
|
+
/** Used by SDK data hooks; throws if rendered outside a {@link WidgetSdkProvider}. */
|
|
11
|
+
export function useWidgetSdkClient() {
|
|
12
|
+
const client = useContext(WidgetSdkContext);
|
|
13
|
+
if (!client) {
|
|
14
|
+
throw new Error('useWidgetSdkClient must be used within a <WidgetSdkProvider>.');
|
|
15
|
+
}
|
|
16
|
+
return client;
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
import type { DashboardWidgetInstance } from '@beechcms/core';
|
|
4
|
+
/** Props every registered widget component receives. */
|
|
5
|
+
export interface DashboardWidgetProps<TConfig = unknown> {
|
|
6
|
+
instance: DashboardWidgetInstance;
|
|
7
|
+
/** Parsed (and defaulted) via the definition's `configSchema`. */
|
|
8
|
+
config: TConfig;
|
|
9
|
+
}
|
|
10
|
+
/** Describes a widget type that can be placed on the dashboard. */
|
|
11
|
+
export interface WidgetDefinition<TConfig = unknown> {
|
|
12
|
+
/** Namespaced type: `core/<name>` for built-ins, npm name for custom. */
|
|
13
|
+
type: string;
|
|
14
|
+
/** i18n key for the picker (built-ins); plain string allowed (custom). */
|
|
15
|
+
labelKey: string;
|
|
16
|
+
descriptionKey?: string;
|
|
17
|
+
/** Lucide icon name for the picker. */
|
|
18
|
+
icon?: string;
|
|
19
|
+
category: 'stats' | 'charts' | 'content' | 'system' | 'custom';
|
|
20
|
+
/** Schema with `.catch()`/`.optional()` so partial configs always parse. */
|
|
21
|
+
configSchema: z.ZodType<TConfig>;
|
|
22
|
+
defaultConfig: TConfig;
|
|
23
|
+
component: ComponentType<DashboardWidgetProps<TConfig>>;
|
|
24
|
+
/** Builder hint: minimum sensible column span out of 12. */
|
|
25
|
+
minColumnSpan?: number;
|
|
26
|
+
/** Builder hint: config panel. Absent = "no options" notice. */
|
|
27
|
+
ConfigPanel?: ComponentType<{
|
|
28
|
+
config: TConfig;
|
|
29
|
+
onChange: (next: TConfig) => void;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=widget-definition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-definition.d.ts","sourceRoot":"","sources":["../src/widget-definition.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAC5B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAE7D,wDAAwD;AACxD,MAAM,WAAW,oBAAoB,CAAC,OAAO,GAAG,OAAO;IACrD,QAAQ,EAAE,uBAAuB,CAAA;IACjC,kEAAkE;IAClE,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB,CAAC,OAAO,GAAG,OAAO;IACjD,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAA;IACZ,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAC9D,4EAA4E;IAC5E,YAAY,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAChC,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,aAAa,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAA;IACvD,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gEAAgE;IAChE,WAAW,CAAC,EAAE,aAAa,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC,CAAA;CACpF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beechcms/widget-sdk",
|
|
3
|
+
"version": "0.6.0-preview.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"react": "^19.0.0",
|
|
19
|
+
"@tanstack/react-query": "^5.0.0"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@beechcms/core": "^0.6.0-preview.3",
|
|
23
|
+
"clsx": "^2.1.1",
|
|
24
|
+
"lucide-react": "^0.564.0",
|
|
25
|
+
"tailwind-merge": "^3.4.0",
|
|
26
|
+
"zod": "^4.3.6"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@tanstack/react-query": "^5.90.21",
|
|
30
|
+
"@testing-library/react": "^16.3.0",
|
|
31
|
+
"@types/react": "^19.2.7",
|
|
32
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
33
|
+
"jsdom": "^25.0.1",
|
|
34
|
+
"react": "^19.2.7",
|
|
35
|
+
"react-dom": "^19.2.7",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vitest": "^4.1.0"
|
|
38
|
+
},
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc",
|
|
42
|
+
"dev": "tsc -w --preserveWatchOutput",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:coverage": "vitest run --coverage"
|
|
45
|
+
}
|
|
46
|
+
}
|