@asteby/metacore-runtime-react 18.16.1 → 18.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 18.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a6f5b98: Add the modular dashboard surface: `DashboardGrid` plus built-in widget
8
+ renderers (stat, bar, line, area, pie, donut, list, progress) and a federated
9
+ `kind:"custom"` path that renders through the `dashboard.widgets` slot inside the
10
+ same card chrome.
11
+
12
+ `DashboardGrid` renders the union of declarative + federated widgets in a
13
+ responsive 4-column grid, honouring per-widget `size`, grouping/ordering, batch
14
+ data loading with per-widget skeletons, isolated per-widget errors, a pro global
15
+ empty state, and permission gating via `useCan` (no provider / `isAdmin` ⇒
16
+ everything visible). Charts use recharts with theme CSS-var colors (dark-mode
17
+ safe); numbers reuse the org currency + locale formatting.
18
+
19
+ New exports: `DashboardGrid`, `normalizeGroups`, the widget renderers, the
20
+ `WidgetRenderer`/`WidgetSkeleton`/`WidgetCard`/`DeltaChip` primitives, the
21
+ `formatWidgetValue`/`accentClasses` helpers, and the types `WidgetKind`,
22
+ `WidgetSize`, `WidgetFormat`, `WidgetAccent`, `DashboardWidgetSpec`,
23
+ `DashboardWidgetQuery`, `WidgetData`, `WidgetSeriesPoint`,
24
+ `DashboardWidgetGroup`, `DashboardGridProps`, `DashboardGridStrings`,
25
+ `LoadWidgetData`. Adds `recharts` as a dependency.
26
+
3
27
  ## 18.16.1
4
28
 
5
29
  ### Patch Changes
@@ -0,0 +1,6 @@
1
+ import * as React from 'react';
2
+ import type { DashboardGridProps, DashboardWidgetGroup, DashboardWidgetSpec } from './dashboard-types';
3
+ /** Normalizes flat `widgets` + `groups` into an ordered group list. */
4
+ export declare function normalizeGroups(groups?: DashboardWidgetGroup[], widgets?: DashboardWidgetSpec[]): DashboardWidgetGroup[];
5
+ export declare function DashboardGrid({ groups, widgets, loadData, isAdmin, locale, currency, className, strings, }: DashboardGridProps): React.JSX.Element;
6
+ //# sourceMappingURL=dashboard-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-grid.d.ts","sourceRoot":"","sources":["../src/dashboard-grid.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,KAAK,EACR,kBAAkB,EAElB,oBAAoB,EACpB,mBAAmB,EACtB,MAAM,mBAAmB,CAAA;AAW1B,uEAAuE;AACvE,wBAAgB,eAAe,CAC3B,MAAM,CAAC,EAAE,oBAAoB,EAAE,EAC/B,OAAO,CAAC,EAAE,mBAAmB,EAAE,GAChC,oBAAoB,EAAE,CAgBxB;AASD,wBAAgB,aAAa,CAAC,EAC1B,MAAM,EACN,OAAO,EACP,QAAQ,EACR,OAAO,EACP,MAAM,EACN,QAAQ,EACR,SAAS,EACT,OAAO,GACV,EAAE,kBAAkB,qBA4IpB"}
@@ -0,0 +1,127 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // DashboardGrid — the modular dashboard surface. Renders the union of
3
+ // declarative + federated widgets in a responsive 4-column grid, grouped with
4
+ // headings, honouring per-widget size, permission gating (useCan), batch data
5
+ // loading with per-widget skeletons, isolated per-widget errors, and a pro
6
+ // global empty state. See CONTRACT-dashboard-widgets.md §4.
7
+ import * as React from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { cn } from '@asteby/metacore-ui/lib';
10
+ import { useCan, usePermissionsActive } from './permissions-context';
11
+ import { DynamicIcon } from './dynamic-icon';
12
+ import { WidgetRenderer, WidgetSkeleton, SIZE_CLASS } from './widgets/widget-renderer';
13
+ const DEFAULT_STRINGS = {
14
+ emptyTitle: 'Your dashboard is empty',
15
+ emptyDescription: 'Install an addon with dashboard widgets to start seeing metrics here.',
16
+ widgetError: 'Could not load this widget.',
17
+ widgetEmpty: 'No data yet.',
18
+ };
19
+ /** Normalizes flat `widgets` + `groups` into an ordered group list. */
20
+ export function normalizeGroups(groups, widgets) {
21
+ if (groups && groups.length > 0)
22
+ return groups;
23
+ if (!widgets || widgets.length === 0)
24
+ return [];
25
+ // Group flat widgets by `group` (preserve first-seen order), sort each
26
+ // group by `order` (default 100), keep insertion order across groups.
27
+ const map = new Map();
28
+ for (const w of widgets) {
29
+ const key = w.group ?? '';
30
+ const arr = map.get(key) ?? [];
31
+ arr.push(w);
32
+ map.set(key, arr);
33
+ }
34
+ return Array.from(map.entries()).map(([title, ws]) => ({
35
+ title,
36
+ widgets: [...ws].sort((a, b) => (a.order ?? 100) - (b.order ?? 100)),
37
+ }));
38
+ }
39
+ /** Collects every widget key across groups (for the batch loader). */
40
+ function allKeys(groups) {
41
+ const keys = [];
42
+ for (const g of groups)
43
+ for (const w of g.widgets)
44
+ keys.push(w.key);
45
+ return keys;
46
+ }
47
+ export function DashboardGrid({ groups, widgets, loadData, isAdmin, locale, currency, className, strings, }) {
48
+ const { t } = useTranslation();
49
+ const can = useCan();
50
+ const permissionsActive = usePermissionsActive();
51
+ const s = { ...DEFAULT_STRINGS, ...strings };
52
+ // i18n helper: translate a key, falling back to the raw key when missing
53
+ // (specs ship raw i18n keys; the host bundle may or may not have them).
54
+ const tr = React.useCallback((key, fallback) => {
55
+ if (!key)
56
+ return fallback ?? '';
57
+ const out = t(key);
58
+ return out === key ? fallback ?? key : out;
59
+ }, [t]);
60
+ // Permission gating: admin bypass; without a PermissionsProvider everything
61
+ // is visible (retrocompat). With one, honour each widget's `permission`.
62
+ const visibleGroups = React.useMemo(() => {
63
+ const norm = normalizeGroups(groups, widgets);
64
+ const gate = (w) => {
65
+ if (isAdmin)
66
+ return true;
67
+ if (!permissionsActive)
68
+ return true;
69
+ if (!w.permission)
70
+ return true;
71
+ return can(w.permission);
72
+ };
73
+ return norm
74
+ .map((g) => ({ ...g, widgets: g.widgets.filter(gate) }))
75
+ .filter((g) => g.widgets.length > 0);
76
+ }, [groups, widgets, isAdmin, permissionsActive, can]);
77
+ const keys = React.useMemo(() => allKeys(visibleGroups), [visibleGroups]);
78
+ const keySig = keys.join(',');
79
+ const [data, setData] = React.useState(null);
80
+ const [loading, setLoading] = React.useState(true);
81
+ React.useEffect(() => {
82
+ let cancelled = false;
83
+ if (keys.length === 0) {
84
+ setLoading(false);
85
+ setData({});
86
+ return;
87
+ }
88
+ setLoading(true);
89
+ loadData(keys)
90
+ .then((res) => {
91
+ if (!cancelled) {
92
+ setData(res ?? {});
93
+ setLoading(false);
94
+ }
95
+ })
96
+ .catch(() => {
97
+ // Batch failure: still render the grid; every widget falls to
98
+ // its empty/error state rather than blanking the page.
99
+ if (!cancelled) {
100
+ setData({});
101
+ setLoading(false);
102
+ }
103
+ });
104
+ return () => {
105
+ cancelled = true;
106
+ };
107
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
+ }, [keySig, loadData]);
109
+ // Global empty state (no widgets at all / none visible after gating).
110
+ if (visibleGroups.length === 0) {
111
+ return (_jsxs("div", { "data-testid": "dashboard-empty", className: cn('flex min-h-[40vh] flex-col items-center justify-center rounded-xl border border-dashed border-border/60 p-10 text-center', className), children: [_jsx("div", { className: "mb-4 flex size-14 items-center justify-center rounded-2xl bg-muted text-muted-foreground", children: _jsx(DynamicIcon, { name: "LayoutDashboard", className: "size-7" }) }), _jsx("h3", { className: "text-base font-semibold text-foreground", children: tr(undefined, s.emptyTitle) || s.emptyTitle }), _jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: s.emptyDescription })] }));
112
+ }
113
+ return (_jsx("div", { "data-testid": "dashboard-grid", className: cn('flex flex-col gap-8', className), children: visibleGroups.map((group) => (_jsxs("section", { className: "flex flex-col gap-3", children: [group.title && (_jsx("h2", { className: "text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: tr(group.title, group.title) })), _jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: group.widgets.map((spec) => {
114
+ const size = spec.size ?? 'sm';
115
+ return (_jsx("div", { className: SIZE_CLASS[size], children: loading ? (_jsx(WidgetSkeleton, { spec: {
116
+ ...spec,
117
+ title: tr(spec.title, spec.title),
118
+ subtitle: tr(spec.subtitle, spec.subtitle),
119
+ } })) : (_jsx(WidgetRenderer, { spec: {
120
+ ...spec,
121
+ title: tr(spec.title, spec.title),
122
+ subtitle: tr(spec.subtitle, spec.subtitle),
123
+ }, data: data?.[spec.key], locale: locale, currency: currency, emptyText: spec.empty
124
+ ? tr(spec.empty, spec.empty)
125
+ : s.widgetEmpty, errorText: s.widgetError })) }, spec.key));
126
+ }) })] }, group.title || '__default'))) }));
127
+ }
@@ -0,0 +1,130 @@
1
+ /** Widget renderer kinds. `custom` defers to a federated slot component. */
2
+ export type WidgetKind = 'stat' | 'bar' | 'line' | 'area' | 'pie' | 'donut' | 'list' | 'progress' | 'custom';
3
+ /** Grid footprint in a 4-column grid: sm=1, md=2, lg=3, full=4. */
4
+ export type WidgetSize = 'sm' | 'md' | 'lg' | 'full';
5
+ /** Value format applied to the scalar value and to series values. */
6
+ export type WidgetFormat = 'number' | 'currency' | 'percent' | 'compact';
7
+ /** Accent color token (theme CSS vars). */
8
+ export type WidgetAccent = 'emerald' | 'sky' | 'violet' | 'amber' | 'rose' | 'slate';
9
+ /** Aggregation kinds for declarative queries. */
10
+ export type WidgetAggregate = 'count' | 'sum' | 'avg' | 'min' | 'max';
11
+ /** Where-clause operators (mirror the list builder). */
12
+ export interface WidgetWhereOp {
13
+ eq?: unknown;
14
+ neq?: unknown;
15
+ gt?: unknown;
16
+ gte?: unknown;
17
+ lt?: unknown;
18
+ lte?: unknown;
19
+ contains?: unknown;
20
+ }
21
+ /**
22
+ * Declarative aggregation query (kinds other than `custom`). The host resolves
23
+ * the logical table from `model` and computes the aggregate org-scoped.
24
+ */
25
+ export interface DashboardWidgetQuery {
26
+ model: string;
27
+ aggregate: WidgetAggregate;
28
+ field?: string;
29
+ where?: Record<string, unknown | WidgetWhereOp>;
30
+ group_by?: string;
31
+ label_field?: string;
32
+ date_field?: string;
33
+ interval?: 'day' | 'week' | 'month';
34
+ range?: 'this_day' | 'last_7_days' | 'last_30_days' | 'last_12_months' | 'this_month' | 'this_year' | 'all';
35
+ order?: 'asc' | 'desc';
36
+ limit?: number;
37
+ }
38
+ /** Optional delta comparison against the previous window → `+14.2%` chip. */
39
+ export interface DashboardWidgetCompare {
40
+ to: 'previous_period';
41
+ }
42
+ /**
43
+ * A single dashboard widget spec (v3 contract §1). The host ships these as raw
44
+ * i18n keys (`title`, `subtitle`, `group`, `empty`); the grid translates them.
45
+ */
46
+ export interface DashboardWidgetSpec {
47
+ /** Unique within the addon. Capability = `<addon>.dashboard.<key>`. */
48
+ key: string;
49
+ /** i18n key for the title. */
50
+ title: string;
51
+ /** i18n key for the subtitle. */
52
+ subtitle?: string;
53
+ /** lucide icon slug. */
54
+ icon?: string;
55
+ kind: WidgetKind;
56
+ size?: WidgetSize;
57
+ /** i18n key for the group heading. */
58
+ group?: string;
59
+ /** Order within the group. */
60
+ order?: number;
61
+ accent?: WidgetAccent;
62
+ format?: WidgetFormat;
63
+ /** Capability gating the widget (useCan). */
64
+ permission?: string;
65
+ /** i18n key for the per-widget empty state. */
66
+ empty?: string;
67
+ query?: DashboardWidgetQuery;
68
+ compare?: DashboardWidgetCompare;
69
+ slot?: string;
70
+ expose?: string;
71
+ }
72
+ /** A bucket of an aggregated series. */
73
+ export interface WidgetSeriesPoint {
74
+ key: string;
75
+ label: string;
76
+ value: number;
77
+ }
78
+ /**
79
+ * The computed data for one widget (CONTRACT §3). `value` for scalars,
80
+ * `series` for bucketed/temporal aggregates, `delta` for the compare chip
81
+ * (fraction, e.g. `0.142` → `+14.2%`).
82
+ */
83
+ export interface WidgetData {
84
+ value?: number;
85
+ delta?: number;
86
+ series?: WidgetSeriesPoint[];
87
+ }
88
+ /** A titled group of widgets (CONTRACT §3 — backend grouping/order). */
89
+ export interface DashboardWidgetGroup {
90
+ /** i18n key for the group heading. */
91
+ title: string;
92
+ widgets: DashboardWidgetSpec[];
93
+ }
94
+ /**
95
+ * Loads the data for a batch of widget keys. The host runs the aggregation and
96
+ * returns a `{ [key]: WidgetData }` map. Keys missing from the result render
97
+ * their empty state.
98
+ */
99
+ export type LoadWidgetData = (keys: string[]) => Promise<Record<string, WidgetData>>;
100
+ /** Props for <DashboardGrid>. */
101
+ export interface DashboardGridProps {
102
+ /** Pre-grouped widgets (backend grouping/order). */
103
+ groups?: DashboardWidgetGroup[];
104
+ /** Flat widget list — grouped client-side by `group`/`order`. */
105
+ widgets?: DashboardWidgetSpec[];
106
+ /** Batch loader for widget data (org-scoped, host-side aggregation). */
107
+ loadData: LoadWidgetData;
108
+ /** When true, bypass permission gating (admin/owner sees everything). */
109
+ isAdmin?: boolean;
110
+ /** BCP-47 locale for number/currency/date formatting. */
111
+ locale?: string;
112
+ /** ISO-4217 currency for `format: 'currency'` widgets. */
113
+ currency?: string;
114
+ /** Extra class on the grid root. */
115
+ className?: string;
116
+ /** Optional override for empty/loading/error copy. */
117
+ strings?: Partial<DashboardGridStrings>;
118
+ }
119
+ /** Translatable copy used by the grid chrome. Defaults are English. */
120
+ export interface DashboardGridStrings {
121
+ /** Global empty state title (no widgets at all). */
122
+ emptyTitle: string;
123
+ /** Global empty state description. */
124
+ emptyDescription: string;
125
+ /** Per-widget error message. */
126
+ widgetError: string;
127
+ /** Per-widget empty (no data) fallback when the spec has no `empty` key. */
128
+ widgetEmpty: string;
129
+ }
130
+ //# sourceMappingURL=dashboard-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-types.d.ts","sourceRoot":"","sources":["../src/dashboard-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ // Dashboard widget types — the SDK-facing surface of the modular dashboard
2
+ // contract (CONTRACT-dashboard-widgets.md §1, §3, §4). The host (ops/kernel)
3
+ // computes the data and ships the specs as raw i18n keys; the SDK renders.
4
+ //
5
+ // These mirror the v3 `contributions.dashboard[]` shape so a host can forward
6
+ // the backend response straight into <DashboardGrid> without remapping.
7
+ export {};
package/dist/index.d.ts CHANGED
@@ -42,4 +42,10 @@ export { ActivityValueRenderer, type ActivityValueRendererProps, } from './activ
42
42
  export { ActivityDiff, type ActivityEvent, type ActivityDiffProps, } from './activity-diff';
43
43
  export { RecordHistory, type RecordHistoryProps, } from './record-history';
44
44
  export { ActivityTimeline, type ActivityTimelineProps, } from './activity-timeline';
45
+ export { DashboardGrid, normalizeGroups, } from './dashboard-grid';
46
+ export type { WidgetKind, WidgetSize, WidgetFormat, WidgetAccent, WidgetAggregate, WidgetWhereOp, DashboardWidgetQuery, DashboardWidgetCompare, DashboardWidgetSpec, WidgetSeriesPoint, WidgetData, DashboardWidgetGroup, LoadWidgetData, DashboardGridProps, DashboardGridStrings, } from './dashboard-types';
47
+ export { StatWidget, BarWidget, LineWidget, AreaWidget, PieWidget, DonutWidget, ListWidget, ProgressWidget, type WidgetRenderProps, } from './widgets/renderers';
48
+ export { WidgetRenderer, WidgetSkeleton, SIZE_SPAN, SIZE_CLASS, type WidgetRendererProps, } from './widgets/widget-renderer';
49
+ export { WidgetCard, DeltaChip, WidgetEmpty, WidgetError, type WidgetCardProps, } from './widgets/widget-card';
50
+ export { formatWidgetValue, formatAxisTick, formatDelta, accentClasses, paletteColor, CHART_PALETTE, type AccentClasses, type WidgetFormatCtx, } from './widgets/widget-format';
45
51
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -44,3 +44,8 @@ export { ActivityValueRenderer, } from './activity-value-renderer';
44
44
  export { ActivityDiff, } from './activity-diff';
45
45
  export { RecordHistory, } from './record-history';
46
46
  export { ActivityTimeline, } from './activity-timeline';
47
+ export { DashboardGrid, normalizeGroups, } from './dashboard-grid';
48
+ export { StatWidget, BarWidget, LineWidget, AreaWidget, PieWidget, DonutWidget, ListWidget, ProgressWidget, } from './widgets/renderers';
49
+ export { WidgetRenderer, WidgetSkeleton, SIZE_SPAN, SIZE_CLASS, } from './widgets/widget-renderer';
50
+ export { WidgetCard, DeltaChip, WidgetEmpty, WidgetError, } from './widgets/widget-card';
51
+ export { formatWidgetValue, formatAxisTick, formatDelta, accentClasses, paletteColor, CHART_PALETTE, } from './widgets/widget-format';
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ import type { DashboardWidgetSpec, WidgetData } from '../dashboard-types';
3
+ export interface WidgetRenderProps {
4
+ spec: DashboardWidgetSpec;
5
+ data?: WidgetData;
6
+ locale?: string;
7
+ currency?: string;
8
+ /** i18n: per-widget empty fallback (already translated). */
9
+ emptyText: string;
10
+ }
11
+ export declare function StatWidget(p: WidgetRenderProps): React.JSX.Element;
12
+ export declare function BarWidget(p: WidgetRenderProps): React.JSX.Element;
13
+ export declare function LineWidget(p: WidgetRenderProps): React.JSX.Element;
14
+ export declare function AreaWidget(p: WidgetRenderProps): React.JSX.Element;
15
+ export declare function PieWidget(p: WidgetRenderProps): React.JSX.Element;
16
+ export declare function DonutWidget(p: WidgetRenderProps): React.JSX.Element;
17
+ export declare function ListWidget(p: WidgetRenderProps): React.JSX.Element;
18
+ export declare function ProgressWidget(p: WidgetRenderProps): React.JSX.Element;
19
+ //# sourceMappingURL=renderers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderers.d.ts","sourceRoot":"","sources":["../../src/widgets/renderers.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkB9B,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAazE,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,mBAAmB,CAAA;IACzB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAA;CACpB;AAiCD,wBAAgB,UAAU,CAAC,CAAC,EAAE,iBAAiB,qBA2B9C;AAGD,wBAAgB,SAAS,CAAC,CAAC,EAAE,iBAAiB,qBAsC7C;AAgDD,wBAAgB,UAAU,CAAC,CAAC,EAAE,iBAAiB,qBAE9C;AACD,wBAAgB,UAAU,CAAC,CAAC,EAAE,iBAAiB,qBAE9C;AAqDD,wBAAgB,SAAS,CAAC,CAAC,EAAE,iBAAiB,qBAE7C;AACD,wBAAgB,WAAW,CAAC,CAAC,EAAE,iBAAiB,qBAE/C;AAGD,wBAAgB,UAAU,CAAC,CAAC,EAAE,iBAAiB,qBAqC9C;AAKD,wBAAgB,cAAc,CAAC,CAAC,EAAE,iBAAiB,qBAkClD"}
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Line, LineChart, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
3
+ import { cn } from '@asteby/metacore-ui/lib';
4
+ import { WidgetCard, DeltaChip, WidgetEmpty } from './widget-card';
5
+ import { accentClasses, paletteColor, formatWidgetValue, formatAxisTick, formatDelta, CHART_GRID, CHART_MUTED, } from './widget-format';
6
+ const fmtCtx = (spec, locale, currency) => ({ format: spec.format, locale, currency });
7
+ const hasSeries = (d) => Array.isArray(d?.series) && d.series.length > 0;
8
+ const CHART_HEIGHT = 132;
9
+ // Compact recharts tooltip styled with theme tokens.
10
+ function ChartTooltip({ ctx }) {
11
+ return (_jsx(Tooltip, { cursor: { fill: 'var(--muted, rgba(148,163,184,0.12))', opacity: 0.4 }, contentStyle: {
12
+ background: 'var(--popover, #fff)',
13
+ border: '1px solid var(--border, #e2e8f0)',
14
+ borderRadius: 8,
15
+ fontSize: 12,
16
+ color: 'var(--popover-foreground, #0f172a)',
17
+ boxShadow: '0 4px 16px rgba(0,0,0,0.08)',
18
+ }, labelStyle: { color: 'var(--muted-foreground, #64748b)', marginBottom: 2 }, formatter: (v) => formatWidgetValue(Number(v), ctx) }));
19
+ }
20
+ // --- stat -----------------------------------------------------------------
21
+ export function StatWidget(p) {
22
+ const ctx = fmtCtx(p.spec, p.locale, p.currency);
23
+ const value = p.data?.value;
24
+ const delta = p.data?.delta;
25
+ const hasValue = typeof value === 'number' && !Number.isNaN(value);
26
+ return (_jsx(WidgetCard, { "data-testid": `widget-${p.spec.key}`, title: p.spec.title, subtitle: p.spec.subtitle, icon: p.spec.icon, accent: p.spec.accent, headerExtra: typeof delta === 'number' ? (_jsx(DeltaChip, { delta: delta, text: formatDelta(delta, p.locale) })) : undefined, children: hasValue ? (_jsx("div", { className: "text-3xl font-semibold tabular-nums tracking-tight text-foreground", children: formatWidgetValue(value, ctx) })) : (_jsx(WidgetEmpty, { message: p.emptyText })) }));
27
+ }
28
+ // --- bar ------------------------------------------------------------------
29
+ export function BarWidget(p) {
30
+ const ctx = fmtCtx(p.spec, p.locale, p.currency);
31
+ const a = accentClasses(p.spec.accent);
32
+ return (_jsx(WidgetCard, { "data-testid": `widget-${p.spec.key}`, title: p.spec.title, subtitle: p.spec.subtitle, icon: p.spec.icon, accent: p.spec.accent, children: hasSeries(p.data) ? (_jsx(ResponsiveContainer, { width: "100%", height: CHART_HEIGHT, children: _jsxs(BarChart, { data: p.data.series, margin: { top: 4, right: 4, left: -16, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false, stroke: CHART_GRID, strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "label", tick: { fontSize: 10, fill: CHART_MUTED }, tickLine: false, axisLine: false, interval: "preserveStartEnd" }), _jsx(YAxis, { tick: { fontSize: 10, fill: CHART_MUTED }, tickLine: false, axisLine: false, width: 44, tickFormatter: (v) => formatAxisTick(v, ctx) }), _jsx(ChartTooltip, { ctx: ctx }), _jsx(Bar, { dataKey: "value", fill: a.chartVar, radius: [4, 4, 0, 0], maxBarSize: 40 })] }) })) : (_jsx(WidgetEmpty, { message: p.emptyText })) }));
33
+ }
34
+ // --- line / area (shared) -------------------------------------------------
35
+ function TimeSeriesWidget(p) {
36
+ const ctx = fmtCtx(p.spec, p.locale, p.currency);
37
+ const a = accentClasses(p.spec.accent);
38
+ const gradId = `wg-grad-${p.spec.key}`;
39
+ return (_jsx(WidgetCard, { "data-testid": `widget-${p.spec.key}`, title: p.spec.title, subtitle: p.spec.subtitle, icon: p.spec.icon, accent: p.spec.accent, children: hasSeries(p.data) ? (_jsx(ResponsiveContainer, { width: "100%", height: CHART_HEIGHT, children: p.variant === 'area' ? (_jsxs(AreaChart, { data: p.data.series, margin: { top: 4, right: 6, left: -16, bottom: 0 }, children: [_jsx("defs", { children: _jsxs("linearGradient", { id: gradId, x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: a.chartVar, stopOpacity: 0.35 }), _jsx("stop", { offset: "100%", stopColor: a.chartVar, stopOpacity: 0.02 })] }) }), _jsx(CartesianGrid, { vertical: false, stroke: CHART_GRID, strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "label", tick: { fontSize: 10, fill: CHART_MUTED }, tickLine: false, axisLine: false, interval: "preserveStartEnd" }), _jsx(YAxis, { tick: { fontSize: 10, fill: CHART_MUTED }, tickLine: false, axisLine: false, width: 44, tickFormatter: (v) => formatAxisTick(v, ctx) }), _jsx(ChartTooltip, { ctx: ctx }), _jsx(Area, { type: "monotone", dataKey: "value", stroke: a.chartVar, strokeWidth: 2, fill: `url(#${gradId})` })] })) : (_jsxs(LineChart, { data: p.data.series, margin: { top: 4, right: 6, left: -16, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false, stroke: CHART_GRID, strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "label", tick: { fontSize: 10, fill: CHART_MUTED }, tickLine: false, axisLine: false, interval: "preserveStartEnd" }), _jsx(YAxis, { tick: { fontSize: 10, fill: CHART_MUTED }, tickLine: false, axisLine: false, width: 44, tickFormatter: (v) => formatAxisTick(v, ctx) }), _jsx(ChartTooltip, { ctx: ctx }), _jsx(Line, { type: "monotone", dataKey: "value", stroke: a.chartVar, strokeWidth: 2, dot: false, activeDot: { r: 4 } })] })) })) : (_jsx(WidgetEmpty, { message: p.emptyText })) }));
40
+ }
41
+ export function LineWidget(p) {
42
+ return _jsx(TimeSeriesWidget, { ...p, variant: "line" });
43
+ }
44
+ export function AreaWidget(p) {
45
+ return _jsx(TimeSeriesWidget, { ...p, variant: "area" });
46
+ }
47
+ // --- pie / donut (shared) -------------------------------------------------
48
+ function CircularWidget(p) {
49
+ const ctx = fmtCtx(p.spec, p.locale, p.currency);
50
+ return (_jsx(WidgetCard, { "data-testid": `widget-${p.spec.key}`, title: p.spec.title, subtitle: p.spec.subtitle, icon: p.spec.icon, accent: p.spec.accent, children: hasSeries(p.data) ? (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(ResponsiveContainer, { width: "50%", height: CHART_HEIGHT, children: _jsxs(PieChart, { children: [_jsx(ChartTooltip, { ctx: ctx }), _jsx(Pie, { data: p.data.series, dataKey: "value", nameKey: "label", innerRadius: p.variant === 'donut' ? 32 : 0, outerRadius: 56, paddingAngle: p.variant === 'donut' ? 2 : 0, stroke: "var(--background, #fff)", strokeWidth: 2, children: p.data.series.map((_, i) => (_jsx(Cell, { fill: paletteColor(i) }, i))) })] }) }), _jsx("ul", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: p.data.series.slice(0, 6).map((pt, i) => (_jsxs("li", { className: "flex items-center gap-2 text-xs", children: [_jsx("span", { className: "size-2.5 shrink-0 rounded-sm", style: { background: paletteColor(i) } }), _jsx("span", { className: "truncate text-muted-foreground", children: pt.label }), _jsx("span", { className: "ml-auto shrink-0 font-medium tabular-nums text-foreground", children: formatWidgetValue(pt.value, ctx) })] }, pt.key))) })] })) : (_jsx(WidgetEmpty, { message: p.emptyText })) }));
51
+ }
52
+ export function PieWidget(p) {
53
+ return _jsx(CircularWidget, { ...p, variant: "pie" });
54
+ }
55
+ export function DonutWidget(p) {
56
+ return _jsx(CircularWidget, { ...p, variant: "donut" });
57
+ }
58
+ // --- list (top-N with proportion bars) ------------------------------------
59
+ export function ListWidget(p) {
60
+ const ctx = fmtCtx(p.spec, p.locale, p.currency);
61
+ const a = accentClasses(p.spec.accent);
62
+ const series = p.data?.series ?? [];
63
+ const max = series.reduce((m, s) => Math.max(m, s.value), 0) || 1;
64
+ return (_jsx(WidgetCard, { "data-testid": `widget-${p.spec.key}`, title: p.spec.title, subtitle: p.spec.subtitle, icon: p.spec.icon, accent: p.spec.accent, children: series.length > 0 ? (_jsx("ul", { className: "flex flex-col gap-2.5", children: series.map((pt) => (_jsxs("li", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2 text-xs", children: [_jsx("span", { className: "truncate text-foreground", children: pt.label }), _jsx("span", { className: "shrink-0 font-medium tabular-nums text-muted-foreground", children: formatWidgetValue(pt.value, ctx) })] }), _jsx("div", { className: cn('h-1.5 w-full overflow-hidden rounded-full', a.track), children: _jsx("div", { className: cn('h-full rounded-full transition-all', a.bar), style: { width: `${Math.max(2, (pt.value / max) * 100)}%` } }) })] }, pt.key))) })) : (_jsx(WidgetEmpty, { message: p.emptyText })) }));
65
+ }
66
+ // --- progress -------------------------------------------------------------
67
+ // A scalar rendered as a proportion bar. When `format:'percent'` the value is
68
+ // a fraction in [0,1]; otherwise it's shown as the big number with a full bar.
69
+ export function ProgressWidget(p) {
70
+ const ctx = fmtCtx(p.spec, p.locale, p.currency);
71
+ const a = accentClasses(p.spec.accent);
72
+ const value = p.data?.value;
73
+ const hasValue = typeof value === 'number' && !Number.isNaN(value);
74
+ const pct = p.spec.format === 'percent'
75
+ ? Math.min(100, Math.max(0, (value ?? 0) * 100))
76
+ : 100;
77
+ return (_jsx(WidgetCard, { "data-testid": `widget-${p.spec.key}`, title: p.spec.title, subtitle: p.spec.subtitle, icon: p.spec.icon, accent: p.spec.accent, children: hasValue ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "text-2xl font-semibold tabular-nums tracking-tight text-foreground", children: formatWidgetValue(value, ctx) }), _jsx("div", { className: cn('h-2 w-full overflow-hidden rounded-full', a.track), children: _jsx("div", { className: cn('h-full rounded-full transition-all duration-500', a.bar), style: { width: `${pct}%` } }) })] })) : (_jsx(WidgetEmpty, { message: p.emptyText })) }));
78
+ }
@@ -0,0 +1,34 @@
1
+ import * as React from 'react';
2
+ import type { WidgetAccent } from '../dashboard-types';
3
+ export interface WidgetCardProps {
4
+ title: string;
5
+ subtitle?: string;
6
+ icon?: string;
7
+ accent?: WidgetAccent;
8
+ /** Right-aligned header slot (e.g. a delta chip). */
9
+ headerExtra?: React.ReactNode;
10
+ /** Body content. */
11
+ children?: React.ReactNode;
12
+ className?: string;
13
+ /** Forwarded for testing/automation. */
14
+ 'data-testid'?: string;
15
+ }
16
+ /**
17
+ * The card frame shared by all widgets. Keep the chrome here so a single style
18
+ * change propagates to every kind (and to federated custom widgets).
19
+ */
20
+ export declare function WidgetCard({ title, subtitle, icon, accent, headerExtra, children, className, ...rest }: WidgetCardProps): React.JSX.Element;
21
+ /** Delta chip: green up / red down, neutral on zero. `text` is preformatted. */
22
+ export declare function DeltaChip({ delta, text }: {
23
+ delta: number;
24
+ text: string;
25
+ }): React.JSX.Element;
26
+ /** Centered empty state inside a widget body (no data / missing). */
27
+ export declare function WidgetEmpty({ message }: {
28
+ message: string;
29
+ }): React.JSX.Element;
30
+ /** Per-widget error state — isolated so a broken widget never tumbles the grid. */
31
+ export declare function WidgetError({ message }: {
32
+ message: string;
33
+ }): React.JSX.Element;
34
+ //# sourceMappingURL=widget-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-card.d.ts","sourceRoot":"","sources":["../../src/widgets/widget-card.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,qDAAqD;IACrD,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,WAAW,EACX,QAAQ,EACR,SAAS,EACT,GAAG,IAAI,EACV,EAAE,eAAe,qBA6CjB;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,qBAiBzE;AAED,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,qBAM3D;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,qBAO3D"}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, CardContent, CardHeader } from '@asteby/metacore-ui';
3
+ import { cn } from '@asteby/metacore-ui/lib';
4
+ import { DynamicIcon, isLucideIconName } from '../dynamic-icon';
5
+ import { accentClasses } from './widget-format';
6
+ /**
7
+ * The card frame shared by all widgets. Keep the chrome here so a single style
8
+ * change propagates to every kind (and to federated custom widgets).
9
+ */
10
+ export function WidgetCard({ title, subtitle, icon, accent, headerExtra, children, className, ...rest }) {
11
+ const a = accentClasses(accent);
12
+ const showIcon = icon && isLucideIconName(icon);
13
+ return (_jsxs(Card, { ...rest, className: cn(
14
+ // base: subtle border, ring on hover, gentle lift, mount fade-in
15
+ 'group/widget relative flex h-full flex-col overflow-hidden', 'border-border/60 transition-all duration-200', 'hover:border-border hover:ring-1 hover:ring-ring/30 hover:shadow-sm', 'motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-bottom-1 motion-safe:duration-500', className), children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-3 space-y-0 pb-2", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [showIcon && (_jsx("span", { className: cn('flex size-9 shrink-0 items-center justify-center rounded-lg', a.chip), children: _jsx(DynamicIcon, { name: icon, className: "size-[18px]" }) })), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium leading-tight text-foreground", children: title }), subtitle && (_jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: subtitle }))] })] }), headerExtra && _jsx("div", { className: "shrink-0", children: headerExtra })] }), _jsx(CardContent, { className: "flex flex-1 flex-col justify-end pt-1", children: children })] }));
16
+ }
17
+ /** Delta chip: green up / red down, neutral on zero. `text` is preformatted. */
18
+ export function DeltaChip({ delta, text }) {
19
+ const up = delta > 0;
20
+ const down = delta < 0;
21
+ return (_jsxs("span", { className: cn('inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium tabular-nums', up && 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400', down && 'bg-rose-500/10 text-rose-600 dark:text-rose-400', !up && !down && 'bg-muted text-muted-foreground'), children: [up && '▲', down && '▼', text] }));
22
+ }
23
+ /** Centered empty state inside a widget body (no data / missing). */
24
+ export function WidgetEmpty({ message }) {
25
+ return (_jsx("div", { className: "flex flex-1 items-center justify-center py-6 text-center text-xs text-muted-foreground", children: message }));
26
+ }
27
+ /** Per-widget error state — isolated so a broken widget never tumbles the grid. */
28
+ export function WidgetError({ message }) {
29
+ return (_jsxs("div", { className: "flex flex-1 items-center justify-center gap-2 py-6 text-center text-xs text-destructive", children: [_jsx(DynamicIcon, { name: "TriangleAlert", className: "size-4" }), _jsx("span", { children: message })] }));
30
+ }
@@ -0,0 +1,42 @@
1
+ import type { WidgetAccent, WidgetFormat } from '../dashboard-types';
2
+ export interface WidgetFormatCtx {
3
+ format?: WidgetFormat;
4
+ locale?: string;
5
+ currency?: string;
6
+ }
7
+ /**
8
+ * Formats a scalar/series value with the widget's declared format. `currency`
9
+ * uses the org currency + locale (same fallback chain as table cells); `percent`
10
+ * treats the value as a fraction (0.142 → "14.2%"); `compact` uses Intl compact
11
+ * notation (1_200 → "1.2K").
12
+ */
13
+ export declare function formatWidgetValue(value: number, { format, locale, currency }: WidgetFormatCtx): string;
14
+ /** Axis-tick formatter — always compact so charts stay legible. */
15
+ export declare function formatAxisTick(value: number, { format, locale, currency }: WidgetFormatCtx): string;
16
+ /** Formats the compare delta fraction (0.142 → "+14.2%"). */
17
+ export declare function formatDelta(delta: number, locale?: string): string;
18
+ export interface AccentClasses {
19
+ /** Icon chip background + foreground. */
20
+ chip: string;
21
+ /** Text color for accented labels. */
22
+ text: string;
23
+ /** Solid bar/fill background (progress, list proportion). */
24
+ bar: string;
25
+ /** Soft track background behind a bar. */
26
+ track: string;
27
+ /** Hex-ish CSS color used for recharts stroke/fill (var-based). */
28
+ chartVar: string;
29
+ }
30
+ /** Resolves the accent class bundle, defaulting to `sky`. */
31
+ export declare function accentClasses(accent?: WidgetAccent): AccentClasses;
32
+ /**
33
+ * A categorical palette for multi-series charts (pie/donut/bar by bucket).
34
+ * Cycles the accent chart vars so slices stay theme-aware + dark-mode safe.
35
+ */
36
+ export declare const CHART_PALETTE: string[];
37
+ /** Picks a palette color by index, wrapping around. */
38
+ export declare function paletteColor(index: number): string;
39
+ /** Grid/axis muted color from the theme (works in light + dark). */
40
+ export declare const CHART_MUTED = "var(--muted-foreground, #94a3b8)";
41
+ export declare const CHART_GRID = "var(--border, #e2e8f0)";
42
+ //# sourceMappingURL=widget-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-format.d.ts","sourceRoot":"","sources":["../../src/widgets/widget-format.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEpE,MAAM,WAAW,eAAe;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC7B,KAAK,EAAE,MAAM,EACb,EAAE,MAAiB,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,eAAe,GACzD,MAAM,CA2BR;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAC1B,KAAK,EAAE,MAAM,EACb,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,eAAe,GAC9C,MAAM,CAoBR;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAUlE;AAQD,MAAM,WAAW,aAAa;IAC1B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,GAAG,EAAE,MAAM,CAAA;IACX,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;CACnB;AAiDD,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,aAAa,CAElE;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,EAOjC,CAAA;AAED,uDAAuD;AACvD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,oEAAoE;AACpE,eAAO,MAAM,WAAW,qCAAqC,CAAA;AAC7D,eAAO,MAAM,UAAU,2BAA2B,CAAA"}