@asteby/metacore-runtime-react 27.2.0 → 27.3.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,23 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 27.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8b46e55: El empty state del Tablero ahora muestra un mockup animado en vez de un ícono
8
+ estático. Cuando no hay widgets (o ninguno visible tras el gating de permisos),
9
+ `DashboardGrid` pinta una silueta del propio dashboard —tiles skeleton (un
10
+ gráfico grande, un par de stat cards, una barra de progreso y una lista)— que
11
+ se desplazan y reacomodan suavemente entre sí, como piezas encajando en su
12
+ lugar. El mensaje pasa de "no hay nada" a "acá va a vivir tu tablero".
13
+
14
+ Detalles: animación CSS pura (un loop lento de ~11s, translate+scale sutil, sin
15
+ dependencias nuevas), tokens de tema (`bg-muted`) para light/dark automáticos,
16
+ `prefers-reduced-motion: reduce` congela todo, y el mock es decorativo
17
+ (`aria-hidden`). Se exporta el componente `DashboardEmptyMockup` por si un host
18
+ quiere reutilizarlo. Copy por defecto del empty ajustado al nuevo framing
19
+ (override vía `strings`).
20
+
3
21
  ## 27.2.0
4
22
 
5
23
  ### Minor Changes
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Animated skeleton of a dashboard (chart + stats + bar + list) whose tiles
4
+ * slowly drift and settle. Purely decorative — always `aria-hidden`.
5
+ */
6
+ export declare function DashboardEmptyMockup({ className }: {
7
+ className?: string;
8
+ }): React.JSX.Element;
9
+ //# sourceMappingURL=dashboard-empty-mockup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-empty-mockup.d.ts","sourceRoot":"","sources":["../src/dashboard-empty-mockup.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAoD9B;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,qBA4CzE"}
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // DashboardEmptyMockup — the animated empty state for the modular dashboard.
3
+ //
4
+ // Instead of a static "nothing here" card, we paint a silhouette of the
5
+ // dashboard itself: skeleton tiles (a big chart, a couple of stat cards, a
6
+ // bar, a list) that slowly drift and swap places, like puzzle pieces settling
7
+ // into a layout. The metaphor is "your board is getting ready", not "empty".
8
+ //
9
+ // Design constraints (kept deliberately simple so it ships from a package):
10
+ // - Pure CSS keyframes, no dependencies. One slow ~11s loop, subtle motion
11
+ // (small translate + scale, never opacity strobing).
12
+ // - Theme tokens only (bg-muted / border tokens) → light & dark for free.
13
+ // - `prefers-reduced-motion: reduce` freezes everything (tiles rest in place).
14
+ // - The whole mock is decorative → aria-hidden; the copy below carries the
15
+ // accessible meaning.
16
+ //
17
+ // The keyframes ship inline in a <style> tag rather than as Tailwind utilities:
18
+ // consumer apps scan only their OWN source for Tailwind classes, so arbitrary
19
+ // animation utilities declared here would never be generated in their build.
20
+ // Static tokens (bg-muted, rounded-lg…) are standard classes the host emits
21
+ // anyway, so those stay as className.
22
+ import * as React from 'react';
23
+ import { cn } from '@asteby/metacore-ui/lib';
24
+ // Scoped, collision-proof keyframe + class names (prefixed, unlikely to clash).
25
+ const STYLE_ID = 'mc-dashboard-empty-mockup-style';
26
+ const MOCKUP_CSS = `
27
+ .mc-demock{--mc-demock-dur:11s}
28
+ .mc-demock-tile{will-change:transform;transform-origin:center;
29
+ animation-duration:var(--mc-demock-dur);animation-timing-function:cubic-bezier(.65,0,.35,1);
30
+ animation-iteration-count:infinite}
31
+ .mc-demock-t1{animation-name:mc-demock-drift-a}
32
+ .mc-demock-t2{animation-name:mc-demock-drift-b;animation-delay:-1.4s}
33
+ .mc-demock-t3{animation-name:mc-demock-drift-c;animation-delay:-3.1s}
34
+ .mc-demock-t4{animation-name:mc-demock-drift-d;animation-delay:-2.2s}
35
+ .mc-demock-t5{animation-name:mc-demock-drift-e;animation-delay:-4.6s}
36
+ .mc-demock-shimmer{position:relative;overflow:hidden}
37
+ .mc-demock-shimmer::after{content:"";position:absolute;inset:0;
38
+ background:linear-gradient(105deg,transparent 30%,currentColor 50%,transparent 70%);
39
+ opacity:.05;transform:translateX(-100%);
40
+ animation:mc-demock-sheen var(--mc-demock-dur) ease-in-out infinite}
41
+ @keyframes mc-demock-drift-a{0%,100%{transform:translate3d(0,0,0) scale(1)}
42
+ 50%{transform:translate3d(3%,4%,0) scale(1.015)}}
43
+ @keyframes mc-demock-drift-b{0%,100%{transform:translate3d(0,0,0) scale(1)}
44
+ 50%{transform:translate3d(-4%,-3%,0) scale(.985)}}
45
+ @keyframes mc-demock-drift-c{0%,100%{transform:translate3d(0,0,0) scale(1)}
46
+ 50%{transform:translate3d(5%,-2%,0) scale(1.02)}}
47
+ @keyframes mc-demock-drift-d{0%,100%{transform:translate3d(0,0,0) scale(1)}
48
+ 50%{transform:translate3d(-3%,3%,0) scale(.99)}}
49
+ @keyframes mc-demock-drift-e{0%,100%{transform:translate3d(0,0,0) scale(1)}
50
+ 50%{transform:translate3d(2%,-4%,0) scale(1.01)}}
51
+ @keyframes mc-demock-sheen{0%,100%{transform:translateX(-120%)}55%,70%{transform:translateX(120%)}}
52
+ @media (prefers-reduced-motion:reduce){
53
+ .mc-demock-tile{animation:none!important}
54
+ .mc-demock-shimmer::after{animation:none!important;opacity:0}
55
+ }
56
+ `;
57
+ /** Injects the keyframes once per document (idempotent, SSR-safe). */
58
+ function useMockupStyles() {
59
+ React.useInsertionEffect(() => {
60
+ if (typeof document === 'undefined')
61
+ return;
62
+ if (document.getElementById(STYLE_ID))
63
+ return;
64
+ const el = document.createElement('style');
65
+ el.id = STYLE_ID;
66
+ el.textContent = MOCKUP_CSS;
67
+ document.head.appendChild(el);
68
+ }, []);
69
+ }
70
+ const tile = 'rounded-lg bg-muted mc-demock-tile mc-demock-shimmer text-foreground';
71
+ /**
72
+ * Animated skeleton of a dashboard (chart + stats + bar + list) whose tiles
73
+ * slowly drift and settle. Purely decorative — always `aria-hidden`.
74
+ */
75
+ export function DashboardEmptyMockup({ className }) {
76
+ useMockupStyles();
77
+ return (_jsx("div", { "aria-hidden": "true", "data-testid": "dashboard-empty-mockup", className: cn('mc-demock pointer-events-none w-full max-w-md select-none', className), children: _jsxs("div", { className: "grid grid-cols-3 grid-rows-[repeat(3,minmax(0,1fr))] gap-3 [aspect-ratio:16/11]", children: [_jsx("div", { className: cn(tile, 'mc-demock-t1 col-span-2 row-span-2 flex flex-col justify-end gap-2 p-3'), children: _jsxs("div", { className: "flex items-end gap-1.5", children: [_jsx("div", { className: "h-6 w-full rounded-sm bg-foreground/10" }), _jsx("div", { className: "h-10 w-full rounded-sm bg-foreground/10" }), _jsx("div", { className: "h-7 w-full rounded-sm bg-foreground/10" }), _jsx("div", { className: "h-12 w-full rounded-sm bg-foreground/10" }), _jsx("div", { className: "h-9 w-full rounded-sm bg-foreground/10" })] }) }), _jsxs("div", { className: cn(tile, 'mc-demock-t2 flex flex-col justify-center gap-2 p-3'), children: [_jsx("div", { className: "h-2 w-1/2 rounded-full bg-foreground/15" }), _jsx("div", { className: "h-4 w-2/3 rounded bg-foreground/15" })] }), _jsxs("div", { className: cn(tile, 'mc-demock-t3 flex flex-col justify-center gap-2 p-3'), children: [_jsx("div", { className: "h-2 w-1/2 rounded-full bg-foreground/15" }), _jsx("div", { className: "h-4 w-1/2 rounded bg-foreground/15" })] }), _jsxs("div", { className: cn(tile, 'mc-demock-t4 flex flex-col justify-center gap-2 p-3'), children: [_jsx("div", { className: "h-2 w-full rounded-full bg-foreground/12" }), _jsx("div", { className: "h-2 w-4/5 rounded-full bg-foreground/12" }), _jsx("div", { className: "h-2 w-2/3 rounded-full bg-foreground/12" })] }), _jsx("div", { className: cn(tile, 'mc-demock-t5 col-span-2 flex items-center px-3'), children: _jsx("div", { className: "h-2.5 w-full rounded-full bg-foreground/10", children: _jsx("div", { className: "h-full w-2/5 rounded-full bg-foreground/20" }) }) })] }) }));
78
+ }
@@ -1 +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,qBAiJpB"}
1
+ {"version":3,"file":"dashboard-grid.d.ts","sourceRoot":"","sources":["../src/dashboard-grid.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EACR,kBAAkB,EAElB,oBAAoB,EACpB,mBAAmB,EACtB,MAAM,mBAAmB,CAAA;AAY1B,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,qBAiJpB"}
@@ -8,11 +8,11 @@ import * as React from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
  import { cn } from '@asteby/metacore-ui/lib';
10
10
  import { useCan, usePermissionsActive } from './permissions-context';
11
- import { DynamicIcon } from './dynamic-icon';
12
11
  import { WidgetRenderer, WidgetSkeleton, isTallWidget } from './widgets/widget-renderer';
12
+ import { DashboardEmptyMockup } from './dashboard-empty-mockup';
13
13
  const DEFAULT_STRINGS = {
14
- emptyTitle: 'Your dashboard is empty',
15
- emptyDescription: 'Install an addon with dashboard widgets to start seeing metrics here.',
14
+ emptyTitle: 'Your dashboard is taking shape',
15
+ emptyDescription: 'Install an addon with dashboard widgets and your metrics will start living here.',
16
16
  widgetError: 'Could not load this widget.',
17
17
  widgetEmpty: 'No data yet.',
18
18
  };
@@ -120,7 +120,7 @@ export function DashboardGrid({ groups, widgets, loadData, isAdmin, locale, curr
120
120
  }, [visibleGroups]);
121
121
  // Global empty state (no widgets at all / none visible after gating).
122
122
  if (visibleGroups.length === 0) {
123
- 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 })] }));
123
+ return (_jsxs("div", { "data-testid": "dashboard-empty", className: cn('flex min-h-[40vh] flex-col items-center justify-center gap-6 rounded-xl border border-dashed border-border/60 p-10 text-center', className), children: [_jsx(DashboardEmptyMockup, {}), _jsxs("div", { className: "flex flex-col items-center", children: [_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 })] })] }));
124
124
  }
125
125
  return (_jsx("div", { "data-testid": "dashboard-grid", className: cn(
126
126
  // Masonry: balanced CSS columns. Cards take their natural height
package/dist/index.d.ts CHANGED
@@ -58,6 +58,7 @@ export { ActivityDiff, type ActivityEvent, type ActivityDiffProps, } from './act
58
58
  export { RecordHistory, type RecordHistoryProps, } from './record-history';
59
59
  export { ActivityTimeline, type ActivityTimelineProps, } from './activity-timeline';
60
60
  export { DashboardGrid, normalizeGroups, } from './dashboard-grid';
61
+ export { DashboardEmptyMockup } from './dashboard-empty-mockup';
61
62
  export type { WidgetKind, WidgetSize, WidgetFormat, WidgetAccent, WidgetAggregate, WidgetWhereOp, DashboardWidgetQuery, DashboardWidgetCompare, DashboardWidgetSpec, WidgetSeriesPoint, WidgetData, DashboardWidgetGroup, LoadWidgetData, DashboardGridProps, DashboardGridStrings, } from './dashboard-types';
62
63
  export { StatWidget, BarWidget, LineWidget, AreaWidget, PieWidget, DonutWidget, ListWidget, ProgressWidget, type WidgetRenderProps, } from './widgets/renderers';
63
64
  export { WidgetRenderer, WidgetSkeleton, SIZE_SPAN, SIZE_CLASS, type WidgetRendererProps, } from './widgets/widget-renderer';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,WAAW,CAAA;AAClB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,SAAS,GACjB,MAAM,gBAAgB,CAAA;AACvB,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,EACvB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,GACnC,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,mBAAmB,EACnB,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,GAC9B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,oBAAoB,GAC5B,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,GAC/B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,gBAAgB,GACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC/B,MAAM,uBAAuB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,GACzB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,SAAS,GACjB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,cAAc,EACd,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EACH,OAAO,EACP,QAAQ,EACR,cAAc,EACd,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,UAAU,EACV,SAAS,EACT,UAAU,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,cAAc,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,UAAU,EACV,KAAK,mBAAmB,EACxB,KAAK,SAAS,IAAI,uBAAuB,GAC5C,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AACxE,YAAY,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC5G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,0BAA0B,GAClC,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,GAC1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,gBAAgB,EAChB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,aAAa,EACb,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACR,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,UAAU,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,KAAK,iBAAiB,GACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,cAAc,EACd,cAAc,EACd,SAAS,EACT,UAAU,EACV,KAAK,mBAAmB,GAC3B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,UAAU,EACV,SAAS,EACT,WAAW,EACX,WAAW,EACX,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -60,6 +60,7 @@ export { ActivityDiff, } from './activity-diff';
60
60
  export { RecordHistory, } from './record-history';
61
61
  export { ActivityTimeline, } from './activity-timeline';
62
62
  export { DashboardGrid, normalizeGroups, } from './dashboard-grid';
63
+ export { DashboardEmptyMockup } from './dashboard-empty-mockup';
63
64
  export { StatWidget, BarWidget, LineWidget, AreaWidget, PieWidget, DonutWidget, ListWidget, ProgressWidget, } from './widgets/renderers';
64
65
  export { WidgetRenderer, WidgetSkeleton, SIZE_SPAN, SIZE_CLASS, } from './widgets/widget-renderer';
65
66
  export { WidgetCard, DeltaChip, WidgetEmpty, WidgetError, } from './widgets/widget-card';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "27.2.0",
3
+ "version": "27.3.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -156,6 +156,16 @@ describe('DashboardGrid render', () => {
156
156
  expect(screen.getByTestId('dashboard-empty')).toBeTruthy()
157
157
  })
158
158
 
159
+ it('renders the animated mockup (decorative) in the empty state', () => {
160
+ render(<DashboardGrid widgets={[]} loadData={loaderOf({})} />)
161
+ const mock = screen.getByTestId('dashboard-empty-mockup')
162
+ expect(mock).toBeTruthy()
163
+ // Purely decorative → hidden from the a11y tree.
164
+ expect(mock.getAttribute('aria-hidden')).toBe('true')
165
+ // Keyframes are injected once into the document head.
166
+ expect(document.getElementById('mc-dashboard-empty-mockup-style')).toBeTruthy()
167
+ })
168
+
159
169
  it('survives an empty → populated transition (React #310 regression)', async () => {
160
170
  // The flatten/order useMemo must run BEFORE the empty-state early return.
161
171
  // When it sat after the return, an empty render called one fewer hook
@@ -0,0 +1,122 @@
1
+ // DashboardEmptyMockup — the animated empty state for the modular dashboard.
2
+ //
3
+ // Instead of a static "nothing here" card, we paint a silhouette of the
4
+ // dashboard itself: skeleton tiles (a big chart, a couple of stat cards, a
5
+ // bar, a list) that slowly drift and swap places, like puzzle pieces settling
6
+ // into a layout. The metaphor is "your board is getting ready", not "empty".
7
+ //
8
+ // Design constraints (kept deliberately simple so it ships from a package):
9
+ // - Pure CSS keyframes, no dependencies. One slow ~11s loop, subtle motion
10
+ // (small translate + scale, never opacity strobing).
11
+ // - Theme tokens only (bg-muted / border tokens) → light & dark for free.
12
+ // - `prefers-reduced-motion: reduce` freezes everything (tiles rest in place).
13
+ // - The whole mock is decorative → aria-hidden; the copy below carries the
14
+ // accessible meaning.
15
+ //
16
+ // The keyframes ship inline in a <style> tag rather than as Tailwind utilities:
17
+ // consumer apps scan only their OWN source for Tailwind classes, so arbitrary
18
+ // animation utilities declared here would never be generated in their build.
19
+ // Static tokens (bg-muted, rounded-lg…) are standard classes the host emits
20
+ // anyway, so those stay as className.
21
+
22
+ import * as React from 'react'
23
+ import { cn } from '@asteby/metacore-ui/lib'
24
+
25
+ // Scoped, collision-proof keyframe + class names (prefixed, unlikely to clash).
26
+ const STYLE_ID = 'mc-dashboard-empty-mockup-style'
27
+
28
+ const MOCKUP_CSS = `
29
+ .mc-demock{--mc-demock-dur:11s}
30
+ .mc-demock-tile{will-change:transform;transform-origin:center;
31
+ animation-duration:var(--mc-demock-dur);animation-timing-function:cubic-bezier(.65,0,.35,1);
32
+ animation-iteration-count:infinite}
33
+ .mc-demock-t1{animation-name:mc-demock-drift-a}
34
+ .mc-demock-t2{animation-name:mc-demock-drift-b;animation-delay:-1.4s}
35
+ .mc-demock-t3{animation-name:mc-demock-drift-c;animation-delay:-3.1s}
36
+ .mc-demock-t4{animation-name:mc-demock-drift-d;animation-delay:-2.2s}
37
+ .mc-demock-t5{animation-name:mc-demock-drift-e;animation-delay:-4.6s}
38
+ .mc-demock-shimmer{position:relative;overflow:hidden}
39
+ .mc-demock-shimmer::after{content:"";position:absolute;inset:0;
40
+ background:linear-gradient(105deg,transparent 30%,currentColor 50%,transparent 70%);
41
+ opacity:.05;transform:translateX(-100%);
42
+ animation:mc-demock-sheen var(--mc-demock-dur) ease-in-out infinite}
43
+ @keyframes mc-demock-drift-a{0%,100%{transform:translate3d(0,0,0) scale(1)}
44
+ 50%{transform:translate3d(3%,4%,0) scale(1.015)}}
45
+ @keyframes mc-demock-drift-b{0%,100%{transform:translate3d(0,0,0) scale(1)}
46
+ 50%{transform:translate3d(-4%,-3%,0) scale(.985)}}
47
+ @keyframes mc-demock-drift-c{0%,100%{transform:translate3d(0,0,0) scale(1)}
48
+ 50%{transform:translate3d(5%,-2%,0) scale(1.02)}}
49
+ @keyframes mc-demock-drift-d{0%,100%{transform:translate3d(0,0,0) scale(1)}
50
+ 50%{transform:translate3d(-3%,3%,0) scale(.99)}}
51
+ @keyframes mc-demock-drift-e{0%,100%{transform:translate3d(0,0,0) scale(1)}
52
+ 50%{transform:translate3d(2%,-4%,0) scale(1.01)}}
53
+ @keyframes mc-demock-sheen{0%,100%{transform:translateX(-120%)}55%,70%{transform:translateX(120%)}}
54
+ @media (prefers-reduced-motion:reduce){
55
+ .mc-demock-tile{animation:none!important}
56
+ .mc-demock-shimmer::after{animation:none!important;opacity:0}
57
+ }
58
+ `
59
+
60
+ /** Injects the keyframes once per document (idempotent, SSR-safe). */
61
+ function useMockupStyles() {
62
+ React.useInsertionEffect(() => {
63
+ if (typeof document === 'undefined') return
64
+ if (document.getElementById(STYLE_ID)) return
65
+ const el = document.createElement('style')
66
+ el.id = STYLE_ID
67
+ el.textContent = MOCKUP_CSS
68
+ document.head.appendChild(el)
69
+ }, [])
70
+ }
71
+
72
+ const tile = 'rounded-lg bg-muted mc-demock-tile mc-demock-shimmer text-foreground'
73
+
74
+ /**
75
+ * Animated skeleton of a dashboard (chart + stats + bar + list) whose tiles
76
+ * slowly drift and settle. Purely decorative — always `aria-hidden`.
77
+ */
78
+ export function DashboardEmptyMockup({ className }: { className?: string }) {
79
+ useMockupStyles()
80
+ return (
81
+ <div
82
+ aria-hidden="true"
83
+ data-testid="dashboard-empty-mockup"
84
+ className={cn('mc-demock pointer-events-none w-full max-w-md select-none', className)}
85
+ >
86
+ <div className="grid grid-cols-3 grid-rows-[repeat(3,minmax(0,1fr))] gap-3 [aspect-ratio:16/11]">
87
+ {/* Big chart tile */}
88
+ <div className={cn(tile, 'mc-demock-t1 col-span-2 row-span-2 flex flex-col justify-end gap-2 p-3')}>
89
+ <div className="flex items-end gap-1.5">
90
+ <div className="h-6 w-full rounded-sm bg-foreground/10" />
91
+ <div className="h-10 w-full rounded-sm bg-foreground/10" />
92
+ <div className="h-7 w-full rounded-sm bg-foreground/10" />
93
+ <div className="h-12 w-full rounded-sm bg-foreground/10" />
94
+ <div className="h-9 w-full rounded-sm bg-foreground/10" />
95
+ </div>
96
+ </div>
97
+ {/* Stat card */}
98
+ <div className={cn(tile, 'mc-demock-t2 flex flex-col justify-center gap-2 p-3')}>
99
+ <div className="h-2 w-1/2 rounded-full bg-foreground/15" />
100
+ <div className="h-4 w-2/3 rounded bg-foreground/15" />
101
+ </div>
102
+ {/* Stat card */}
103
+ <div className={cn(tile, 'mc-demock-t3 flex flex-col justify-center gap-2 p-3')}>
104
+ <div className="h-2 w-1/2 rounded-full bg-foreground/15" />
105
+ <div className="h-4 w-1/2 rounded bg-foreground/15" />
106
+ </div>
107
+ {/* List tile */}
108
+ <div className={cn(tile, 'mc-demock-t4 flex flex-col justify-center gap-2 p-3')}>
109
+ <div className="h-2 w-full rounded-full bg-foreground/12" />
110
+ <div className="h-2 w-4/5 rounded-full bg-foreground/12" />
111
+ <div className="h-2 w-2/3 rounded-full bg-foreground/12" />
112
+ </div>
113
+ {/* Progress bar tile */}
114
+ <div className={cn(tile, 'mc-demock-t5 col-span-2 flex items-center px-3')}>
115
+ <div className="h-2.5 w-full rounded-full bg-foreground/10">
116
+ <div className="h-full w-2/5 rounded-full bg-foreground/20" />
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ )
122
+ }
@@ -8,7 +8,6 @@ import * as React from 'react'
8
8
  import { useTranslation } from 'react-i18next'
9
9
  import { cn } from '@asteby/metacore-ui/lib'
10
10
  import { useCan, usePermissionsActive } from './permissions-context'
11
- import { DynamicIcon } from './dynamic-icon'
12
11
  import type {
13
12
  DashboardGridProps,
14
13
  DashboardGridStrings,
@@ -16,11 +15,12 @@ import type {
16
15
  DashboardWidgetSpec,
17
16
  } from './dashboard-types'
18
17
  import { WidgetRenderer, WidgetSkeleton, isTallWidget } from './widgets/widget-renderer'
18
+ import { DashboardEmptyMockup } from './dashboard-empty-mockup'
19
19
 
20
20
  const DEFAULT_STRINGS: DashboardGridStrings = {
21
- emptyTitle: 'Your dashboard is empty',
21
+ emptyTitle: 'Your dashboard is taking shape',
22
22
  emptyDescription:
23
- 'Install an addon with dashboard widgets to start seeing metrics here.',
23
+ 'Install an addon with dashboard widgets and your metrics will start living here.',
24
24
  widgetError: 'Could not load this widget.',
25
25
  widgetEmpty: 'No data yet.',
26
26
  }
@@ -154,19 +154,19 @@ export function DashboardGrid({
154
154
  <div
155
155
  data-testid="dashboard-empty"
156
156
  className={cn(
157
- 'flex min-h-[40vh] flex-col items-center justify-center rounded-xl border border-dashed border-border/60 p-10 text-center',
157
+ 'flex min-h-[40vh] flex-col items-center justify-center gap-6 rounded-xl border border-dashed border-border/60 p-10 text-center',
158
158
  className,
159
159
  )}
160
160
  >
161
- <div className="mb-4 flex size-14 items-center justify-center rounded-2xl bg-muted text-muted-foreground">
162
- <DynamicIcon name="LayoutDashboard" className="size-7" />
161
+ <DashboardEmptyMockup />
162
+ <div className="flex flex-col items-center">
163
+ <h3 className="text-base font-semibold text-foreground">
164
+ {tr(undefined, s.emptyTitle) || s.emptyTitle}
165
+ </h3>
166
+ <p className="mt-1 max-w-sm text-sm text-muted-foreground">
167
+ {s.emptyDescription}
168
+ </p>
163
169
  </div>
164
- <h3 className="text-base font-semibold text-foreground">
165
- {tr(undefined, s.emptyTitle) || s.emptyTitle}
166
- </h3>
167
- <p className="mt-1 max-w-sm text-sm text-muted-foreground">
168
- {s.emptyDescription}
169
- </p>
170
170
  </div>
171
171
  )
172
172
  }
package/src/index.ts CHANGED
@@ -325,6 +325,7 @@ export {
325
325
  DashboardGrid,
326
326
  normalizeGroups,
327
327
  } from './dashboard-grid'
328
+ export { DashboardEmptyMockup } from './dashboard-empty-mockup'
328
329
  export type {
329
330
  WidgetKind,
330
331
  WidgetSize,