@asteby/metacore-runtime-react 27.1.1 → 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 +29 -0
- package/dist/dashboard-empty-mockup.d.ts +9 -0
- package/dist/dashboard-empty-mockup.d.ts.map +1 -0
- package/dist/dashboard-empty-mockup.js +78 -0
- package/dist/dashboard-grid.d.ts.map +1 -1
- package/dist/dashboard-grid.js +4 -4
- package/dist/dynamic-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +2 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +3 -3
- package/src/__tests__/dashboard-grid.test.tsx +10 -0
- package/src/dashboard-empty-mockup.tsx +122 -0
- package/src/dashboard-grid.tsx +12 -12
- package/src/dynamic-select-field.tsx +2 -15
- package/src/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
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
|
+
|
|
21
|
+
## 27.2.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- 27edd94: Las opciones del picker de referencias SIEMPRE llevan lead visual. El gate
|
|
26
|
+
`optionsHaveVisual` suprimía el avatar de toda la lista cuando ninguna opción
|
|
27
|
+
traía imagen/icono/color — un select de almacenes quedaba como texto pelado
|
|
28
|
+
mientras el de productos (una opción con foto) mostraba avatares. Ahora cada
|
|
29
|
+
opción renderiza su `OptionLead`, que cae a la inicial neutra cuando no hay
|
|
30
|
+
imagen, en el dropdown y en el trigger.
|
|
31
|
+
|
|
3
32
|
## 27.1.1
|
|
4
33
|
|
|
5
34
|
### Patch 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;
|
|
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"}
|
package/dist/dashboard-grid.js
CHANGED
|
@@ -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
|
|
15
|
-
emptyDescription: 'Install an addon with dashboard widgets
|
|
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: "
|
|
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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AAwCA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAAgD,CAAA;AAEjF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,IAAI,EACJ,IAAS,GACZ,EAAE;IACC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,+BAoBA;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,IAAS,GACZ,EAAE;IACC,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,sCA4BA;
|
|
1
|
+
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AAwCA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAAgD,CAAA;AAEjF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,IAAI,EACJ,IAAS,GACZ,EAAE;IACC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,+BAoBA;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,IAAS,GACZ,EAAE;IACC,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,sCA4BA;AAWD,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAC1B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,KAAK,EACL,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAgB,GACnB,EAAE,uBAAuB,+BAqOzB;AAED,eAAe,kBAAkB,CAAA"}
|
|
@@ -79,11 +79,6 @@ export function OptionLead({ option, size = 20, }) {
|
|
|
79
79
|
return _jsx(InitialsAvatar, { name: option.label, size: size, rounded: "sm", tone: "neutral" });
|
|
80
80
|
return null;
|
|
81
81
|
}
|
|
82
|
-
/** True when any option (or the selected one) carries a renderable visual. */
|
|
83
|
-
function optionsHaveVisual(options, selected) {
|
|
84
|
-
const has = (o) => !!(o && (o.image || o.color || o.icon));
|
|
85
|
-
return has(selected) || options.some(has);
|
|
86
|
-
}
|
|
87
82
|
function useDebounced(value, ms) {
|
|
88
83
|
const [debounced, setDebounced] = useState(value);
|
|
89
84
|
useEffect(() => {
|
|
@@ -155,7 +150,6 @@ export function DynamicSelectField({ field, value, onChange, seedOption, depends
|
|
|
155
150
|
// Only switch the picker into "with thumbnails" mode when the data actually
|
|
156
151
|
// carries images — a relation whose options have no `image` keeps the plain
|
|
157
152
|
// text list it had before (no empty placeholder column).
|
|
158
|
-
const hasVisual = optionsHaveVisual(options, selectedOption);
|
|
159
153
|
const handlePick = (opt) => {
|
|
160
154
|
setPicked(opt);
|
|
161
155
|
onChange(String(opt.id));
|
|
@@ -195,14 +189,14 @@ export function DynamicSelectField({ field, value, onChange, seedOption, depends
|
|
|
195
189
|
// (the long empty-state placeholder) and overflows the column, pushing the
|
|
196
190
|
// "+" off-screen — it only "fit" once a short value was selected.
|
|
197
191
|
return (_jsxs("div", { className: "flex w-full min-w-0 items-center gap-1.5", children: [_jsxs(Popover, { open: open && !blockedByDependency, onOpenChange: (o) => { if (!blockedByDependency)
|
|
198
|
-
setOpen(o); }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, disabled: blockedByDependency, className: "min-w-0 flex-1 justify-between font-normal", "data-empty": !value, "data-depends-blocked": blockedByDependency ? '' : undefined, children: [_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [
|
|
192
|
+
setOpen(o); }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, disabled: blockedByDependency, className: "min-w-0 flex-1 justify-between font-normal", "data-empty": !value, "data-depends-blocked": blockedByDependency ? '' : undefined, children: [_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [value && selectedOption ? (_jsx(OptionLead, { option: selectedOption, size: 20 })) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedLabel ? '' : 'text-muted-foreground'), children: blockedByDependency
|
|
199
193
|
? (dependsHint || DEFAULT_DEPENDS_HINT)
|
|
200
194
|
: selectedLabel || field.placeholder || 'Buscar…' })] }), _jsx(ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "p-0", align: "start",
|
|
201
195
|
// Match the trigger width without an arbitrary Tailwind class
|
|
202
196
|
// (those don't always survive a consuming app's Tailwind scan).
|
|
203
197
|
style: { width: 'var(--radix-popover-trigger-width)' }, children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: field.placeholder || 'Buscar…', value: search, onValueChange: setSearch }), _jsxs(CommandList, { children: [loading && (_jsxs("div", { className: "text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm", children: [_jsx(Loader2, { className: "size-4 animate-spin" }), "Buscando\u2026"] })), !loading && options.length === 0 && (_jsx(CommandEmpty, { children: debounced ? 'Sin resultados' : 'Escribí para buscar…' })), !loading && options.length > 0 && (_jsx(CommandGroup, { className: "max-h-64 overflow-auto", children: options.map((opt) => {
|
|
204
198
|
const isSel = String(opt.id) === String(value);
|
|
205
|
-
return (_jsxs(CommandItem, { value: String(opt.id), onSelect: () => handlePick(opt), children: [_jsx(Check, { className: 'mr-2 size-4 shrink-0 ' + (isSel ? 'opacity-100' : 'opacity-0') }),
|
|
199
|
+
return (_jsxs(CommandItem, { value: String(opt.id), onSelect: () => handlePick(opt), children: [_jsx(Check, { className: 'mr-2 size-4 shrink-0 ' + (isSel ? 'opacity-100' : 'opacity-0') }), _jsx(OptionLead, { option: opt, size: 24 }), _jsxs("div", { className: "ml-2 flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate", children: opt.label }), opt.description && (_jsx("span", { className: "text-muted-foreground truncate text-xs", children: opt.description }))] })] }, String(opt.id)));
|
|
206
200
|
}) }))] })] }) })] }), fieldRef && (_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "size-9 shrink-0", onClick: openCreate, title: `Crear ${field.label ?? fieldRef}`, "aria-label": `Crear ${field.label ?? fieldRef}`, children: _jsx(Plus, { className: "size-4" }) }))] }));
|
|
207
201
|
}
|
|
208
202
|
export default DynamicSelectField;
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
@@ -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.
|
|
3
|
+
"version": "27.3.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"react-i18next": ">=13",
|
|
38
38
|
"sonner": ">=1.7",
|
|
39
39
|
"zustand": ">=5",
|
|
40
|
-
"@asteby/metacore-
|
|
41
|
-
"@asteby/metacore-
|
|
40
|
+
"@asteby/metacore-sdk": "^3.3.0",
|
|
41
|
+
"@asteby/metacore-ui": "^2.12.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@tanstack/react-router": {
|
|
@@ -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
|
+
}
|
package/src/dashboard-grid.tsx
CHANGED
|
@@ -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
|
|
21
|
+
emptyTitle: 'Your dashboard is taking shape',
|
|
22
22
|
emptyDescription:
|
|
23
|
-
'Install an addon with dashboard widgets
|
|
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
|
-
<
|
|
162
|
-
|
|
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
|
}
|
|
@@ -129,16 +129,6 @@ export function OptionLead({
|
|
|
129
129
|
return null
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
/** True when any option (or the selected one) carries a renderable visual. */
|
|
133
|
-
function optionsHaveVisual(
|
|
134
|
-
options: ReadonlyArray<Pick<ResolvedOption, 'image' | 'color' | 'icon'>>,
|
|
135
|
-
selected?: Pick<ResolvedOption, 'image' | 'color' | 'icon'> | null,
|
|
136
|
-
): boolean {
|
|
137
|
-
const has = (o?: Pick<ResolvedOption, 'image' | 'color' | 'icon'> | null) =>
|
|
138
|
-
!!(o && (o.image || o.color || o.icon))
|
|
139
|
-
return has(selected) || options.some(has)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
132
|
function useDebounced<T>(value: T, ms: number): T {
|
|
143
133
|
const [debounced, setDebounced] = useState(value)
|
|
144
134
|
useEffect(() => {
|
|
@@ -257,7 +247,6 @@ export function DynamicSelectField({
|
|
|
257
247
|
// Only switch the picker into "with thumbnails" mode when the data actually
|
|
258
248
|
// carries images — a relation whose options have no `image` keeps the plain
|
|
259
249
|
// text list it had before (no empty placeholder column).
|
|
260
|
-
const hasVisual = optionsHaveVisual(options, selectedOption)
|
|
261
250
|
|
|
262
251
|
const handlePick = (opt: ResolvedOption) => {
|
|
263
252
|
setPicked(opt)
|
|
@@ -336,7 +325,7 @@ export function DynamicSelectField({
|
|
|
336
325
|
data-depends-blocked={blockedByDependency ? '' : undefined}
|
|
337
326
|
>
|
|
338
327
|
<span className="flex min-w-0 flex-1 items-center gap-2 text-left">
|
|
339
|
-
{
|
|
328
|
+
{value && selectedOption ? (
|
|
340
329
|
<OptionLead option={selectedOption} size={20} />
|
|
341
330
|
) : null}
|
|
342
331
|
<span className={'min-w-0 flex-1 truncate ' + (selectedLabel ? '' : 'text-muted-foreground')}>
|
|
@@ -384,9 +373,7 @@ export function DynamicSelectField({
|
|
|
384
373
|
onSelect={() => handlePick(opt)}
|
|
385
374
|
>
|
|
386
375
|
<Check className={'mr-2 size-4 shrink-0 ' + (isSel ? 'opacity-100' : 'opacity-0')} />
|
|
387
|
-
{
|
|
388
|
-
<OptionLead option={opt} size={24} />
|
|
389
|
-
)}
|
|
376
|
+
<OptionLead option={opt} size={24} />
|
|
390
377
|
<div className="ml-2 flex min-w-0 flex-col">
|
|
391
378
|
<span className="truncate">{opt.label}</span>
|
|
392
379
|
{opt.description && (
|