@miragon/mcp-toolkit-ui 1.1.0 → 2.0.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/dist/app/builder/BuilderBanners.d.ts +19 -0
- package/dist/app/builder/BuilderBanners.d.ts.map +1 -0
- package/dist/app/builder/BuilderBanners.js +22 -0
- package/dist/app/builder/BuilderBanners.js.map +1 -0
- package/dist/app/builder/CanvasRow.d.ts +26 -0
- package/dist/app/builder/CanvasRow.d.ts.map +1 -0
- package/dist/app/builder/CanvasRow.js +98 -0
- package/dist/app/builder/CanvasRow.js.map +1 -0
- package/dist/app/builder/CellPropsSheet.d.ts +19 -0
- package/dist/app/builder/CellPropsSheet.d.ts.map +1 -0
- package/dist/app/builder/CellPropsSheet.js +28 -0
- package/dist/app/builder/CellPropsSheet.js.map +1 -0
- package/dist/app/builder/EmptyHint.d.ts +6 -0
- package/dist/app/builder/EmptyHint.d.ts.map +1 -0
- package/dist/app/builder/EmptyHint.js +10 -0
- package/dist/app/builder/EmptyHint.js.map +1 -0
- package/dist/app/builder/LayoutSurface.d.ts +27 -0
- package/dist/app/builder/LayoutSurface.d.ts.map +1 -0
- package/dist/app/builder/LayoutSurface.js +45 -0
- package/dist/app/builder/LayoutSurface.js.map +1 -0
- package/dist/app/builder/Workspace.d.ts +9 -3
- package/dist/app/builder/Workspace.d.ts.map +1 -1
- package/dist/app/builder/Workspace.js +3 -97
- package/dist/app/builder/Workspace.js.map +1 -1
- package/dist/app/builder/builder-reducer.d.ts.map +1 -1
- package/dist/app/builder/builder-reducer.js +176 -164
- package/dist/app/builder/builder-reducer.js.map +1 -1
- package/dist/app/builder/use-builder-catalogue.d.ts +33 -0
- package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -0
- package/dist/app/builder/use-builder-catalogue.js +155 -0
- package/dist/app/builder/use-builder-catalogue.js.map +1 -0
- package/dist/app/builder/use-key-step-entries.d.ts +22 -0
- package/dist/app/builder/use-key-step-entries.d.ts.map +1 -0
- package/dist/app/builder/use-key-step-entries.js +46 -0
- package/dist/app/builder/use-key-step-entries.js.map +1 -0
- package/dist/app/builder/use-save-dashboard.d.ts +33 -0
- package/dist/app/builder/use-save-dashboard.d.ts.map +1 -0
- package/dist/app/builder/use-save-dashboard.js +78 -0
- package/dist/app/builder/use-save-dashboard.js.map +1 -0
- package/dist/app/layout-builder.d.ts.map +1 -1
- package/dist/app/layout-builder.js +46 -277
- package/dist/app/layout-builder.js.map +1 -1
- package/dist/app/mcp-app-view-chrome.d.ts +77 -0
- package/dist/app/mcp-app-view-chrome.d.ts.map +1 -0
- package/dist/app/mcp-app-view-chrome.js +53 -0
- package/dist/app/mcp-app-view-chrome.js.map +1 -0
- package/dist/app/mcp-app-view.d.ts +3 -9
- package/dist/app/mcp-app-view.d.ts.map +1 -1
- package/dist/app/mcp-app-view.js +4 -31
- package/dist/app/mcp-app-view.js.map +1 -1
- package/dist/render-cases-primitives.d.ts +17 -0
- package/dist/render-cases-primitives.d.ts.map +1 -0
- package/dist/render-cases-primitives.js +88 -0
- package/dist/render-cases-primitives.js.map +1 -0
- package/dist/render-cases.d.ts +4 -0
- package/dist/render-cases.d.ts.map +1 -0
- package/dist/render-cases.js +140 -0
- package/dist/render-cases.js.map +1 -0
- package/package.json +10 -5
- package/src/app/builder/BuilderBanners.tsx +61 -0
- package/src/app/builder/CanvasRow.tsx +316 -0
- package/src/app/builder/CellPropsSheet.tsx +47 -0
- package/src/app/builder/EmptyHint.tsx +29 -0
- package/src/app/builder/LayoutSurface.tsx +140 -0
- package/src/app/builder/Workspace.tsx +33 -342
- package/src/app/builder/builder-reducer.ts +204 -170
- package/src/app/builder/use-builder-catalogue.ts +187 -0
- package/src/app/builder/use-key-step-entries.ts +60 -0
- package/src/app/builder/use-save-dashboard.ts +97 -0
- package/src/app/layout-builder.tsx +92 -497
- package/src/app/mcp-app-view-chrome.tsx +204 -0
- package/src/app/mcp-app-view.tsx +32 -140
- package/src/lib/parse-tool-result.property.test.ts +90 -0
- package/src/render-cases-primitives.tsx +255 -0
- package/src/render-cases.tsx +239 -0
- package/src/render-smoke.test.tsx +100 -0
- package/src/ui-catalog.test.ts +52 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Button, CountPill, DrillButton, FilterBar, GridItem, GridLayout, GroupCard, KpiGrid, ListFooter, LivePill, LocaleProvider, SectionHeading, ThemeProvider, themePresets, useLocale, WidgetHeader, } from "./index.js";
|
|
3
|
+
import { createStandaloneHostBridge, HostBridgeProvider, useHostBridge } from "./app/host-bridge.js";
|
|
4
|
+
import { HostModelContext } from "./app/adapt-data-widget.js";
|
|
5
|
+
import { WidgetFixtureHost } from "./app/widget-fixture.js";
|
|
6
|
+
import { PRIMITIVE_RENDER_CASES } from "./render-cases-primitives.js";
|
|
7
|
+
/**
|
|
8
|
+
* SSR render cases for every catalogued component/composite (FITNESS.md phase
|
|
9
|
+
* 5c). DATA module, not a test: `render-smoke.test.tsx` renders each case via
|
|
10
|
+
* `react-dom/server`'s `renderToStaticMarkup` (jsdom is deliberately renounced
|
|
11
|
+
* — SSR is the house render path) and enforces completeness against
|
|
12
|
+
* `ui-catalog.json` ∪ `ratchets/render-allowlist.json` (shrink-only).
|
|
13
|
+
*
|
|
14
|
+
* Each case mounts the component with minimal realistic props and, where the
|
|
15
|
+
* markup can carry one, a distinctive `marker` string the test asserts on.
|
|
16
|
+
* The primitive cases live in `render-cases-primitives.tsx` (file-length
|
|
17
|
+
* budget); this module aggregates them with the composed/host cases.
|
|
18
|
+
*/
|
|
19
|
+
const noop = () => { };
|
|
20
|
+
/** Static bridge for the host-bridge cases — theme "dark" is the probed marker. */
|
|
21
|
+
const probeBridge = createStandaloneHostBridge({
|
|
22
|
+
callTool: () => Promise.resolve({}),
|
|
23
|
+
theme: "dark",
|
|
24
|
+
});
|
|
25
|
+
/** Proves HostBridgeProvider actually provides: reads the bridge from context. */
|
|
26
|
+
function BridgeThemeProbe() {
|
|
27
|
+
const bridge = useHostBridge();
|
|
28
|
+
return _jsxs("span", { children: ["RC:HostBridgeProvider ", bridge.theme ?? "unset"] });
|
|
29
|
+
}
|
|
30
|
+
/** Proves LocaleProvider actually provides: reads the locale from context. */
|
|
31
|
+
function LocaleProbe() {
|
|
32
|
+
const locale = useLocale();
|
|
33
|
+
return _jsxs("span", { children: ["RC:LocaleProvider ", locale] });
|
|
34
|
+
}
|
|
35
|
+
/** Minimal raw `({ data })` widget for the WidgetFixtureHost case. */
|
|
36
|
+
function FixtureProbeWidget({ data }) {
|
|
37
|
+
return _jsxs("div", { children: ["RC:WidgetFixtureHost ", data?.label ?? "no fixture"] });
|
|
38
|
+
}
|
|
39
|
+
export const RENDER_CASES = [
|
|
40
|
+
...PRIMITIVE_RENDER_CASES,
|
|
41
|
+
// ── Composed components ──
|
|
42
|
+
{
|
|
43
|
+
name: "GridLayout",
|
|
44
|
+
marker: "RC:GridLayout",
|
|
45
|
+
render: () => (_jsxs(GridLayout, { children: [_jsx(GridItem, { span: 8, children: _jsx("div", { children: "RC:GridLayout" }) }), _jsx(GridItem, { span: 4, children: _jsx("div", { children: "side tile" }) })] })),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "GridItem",
|
|
49
|
+
marker: "RC:GridItem",
|
|
50
|
+
render: () => (_jsx(GridLayout, { children: _jsx(GridItem, { span: 6, children: _jsx("div", { children: "RC:GridItem" }) }) })),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "KpiGrid",
|
|
54
|
+
marker: "RC:KpiGrid",
|
|
55
|
+
render: () => (_jsx(KpiGrid, { boxed: true, header: { label: "RC:KpiGrid", badge: "live" }, cells: [
|
|
56
|
+
{
|
|
57
|
+
label: "Open",
|
|
58
|
+
value: 7,
|
|
59
|
+
tone: "danger",
|
|
60
|
+
trend: "+2 vs yesterday",
|
|
61
|
+
trendDirection: "up",
|
|
62
|
+
},
|
|
63
|
+
{ label: "Done", value: 12, fraction: "/19", tone: "success" },
|
|
64
|
+
{ label: "Total", value: 19, onClick: noop, ariaLabel: "Show all" },
|
|
65
|
+
] })),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "LivePill",
|
|
69
|
+
marker: "RC:LivePill",
|
|
70
|
+
render: () => _jsx(LivePill, { tone: "success", children: "RC:LivePill" }),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "CountPill",
|
|
74
|
+
marker: "RC:CountPill",
|
|
75
|
+
render: () => _jsx(CountPill, { tone: "warning", children: "RC:CountPill" }),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "SectionHeading",
|
|
79
|
+
marker: "RC:SectionHeading",
|
|
80
|
+
render: () => _jsx(SectionHeading, { title: "RC:SectionHeading", hint: "3 shown" }),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "WidgetHeader",
|
|
84
|
+
marker: "RC:WidgetHeader",
|
|
85
|
+
render: () => (_jsx(WidgetHeader, { icon: "\u2713", iconTone: "info", title: "RC:WidgetHeader", sub: _jsx(LivePill, { children: "Live" }), actions: _jsx(Button, { size: "sm", variant: "ghost", onClick: noop, children: "Refresh" }) })),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "GroupCard",
|
|
89
|
+
marker: "RC:GroupCard",
|
|
90
|
+
render: () => (_jsx(GroupCard, { expanded: true, onToggle: noop, summary: _jsx("div", { children: "RC:GroupCard" }), children: _jsx("div", { children: "Expanded body row" }) })),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
// The search marker lands in the input's value attribute.
|
|
94
|
+
name: "FilterBar",
|
|
95
|
+
marker: "RC:FilterBar",
|
|
96
|
+
render: () => (_jsx(FilterBar, { search: "RC:FilterBar", searchPlaceholder: "Filter tasks\u2026", onSearchChange: noop, chips: [
|
|
97
|
+
{ id: "high", label: "high", count: 2, active: true },
|
|
98
|
+
{ id: "low", label: "low" },
|
|
99
|
+
], onChipToggle: noop })),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "DrillButton",
|
|
103
|
+
marker: "RC:DrillButton",
|
|
104
|
+
render: () => (_jsx(DrillButton, { onDrill: noop, icon: "\u2197", size: "sm", ariaLabel: "Open order o-1001", children: "RC:DrillButton" })),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "ListFooter",
|
|
108
|
+
marker: "of 42 tasks",
|
|
109
|
+
render: () => _jsx(ListFooter, { shown: 8, total: 42, hasMore: true, onLoadMore: noop, noun: "tasks" }),
|
|
110
|
+
},
|
|
111
|
+
// ── Host seam / providers ──
|
|
112
|
+
{
|
|
113
|
+
name: "HostBridgeProvider",
|
|
114
|
+
marker: "RC:HostBridgeProvider dark",
|
|
115
|
+
render: () => (_jsx(HostBridgeProvider, { bridge: probeBridge, children: _jsx(BridgeThemeProbe, {}) })),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
// Outside an mcp-use view HostModelContext reports through the bridge in
|
|
119
|
+
// an effect (a no-op in SSR) and must still render its children.
|
|
120
|
+
name: "HostModelContext",
|
|
121
|
+
marker: "RC:HostModelContext",
|
|
122
|
+
render: () => (_jsx(HostBridgeProvider, { bridge: probeBridge, children: _jsx(HostModelContext, { content: "Viewing 3 open incidents", children: _jsx("div", { children: "RC:HostModelContext" }) }) })),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "ThemeProvider",
|
|
126
|
+
marker: "RC:ThemeProvider",
|
|
127
|
+
render: () => (_jsx(ThemeProvider, { theme: themePresets.miragon, mode: "dark", children: _jsx("div", { children: "RC:ThemeProvider" }) })),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "LocaleProvider",
|
|
131
|
+
marker: "RC:LocaleProvider de",
|
|
132
|
+
render: () => (_jsx(LocaleProvider, { locale: "de", children: _jsx(LocaleProbe, {}) })),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "WidgetFixtureHost",
|
|
136
|
+
marker: "RC:WidgetFixtureHost fixture-data",
|
|
137
|
+
render: () => (_jsx(WidgetFixtureHost, { widget: FixtureProbeWidget, data: { label: "fixture-data" } })),
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
//# sourceMappingURL=render-cases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-cases.js","sourceRoot":"","sources":["../src/render-cases.tsx"],"names":[],"mappings":";AACA,OAAO,EACL,MAAM,EACN,SAAS,EACT,WAAW,EACX,SAAS,EACT,QAAQ,EACR,UAAU,EACV,SAAS,EACT,OAAO,EACP,UAAU,EACV,QAAQ,EACR,cAAc,EACd,cAAc,EACd,aAAa,EACb,YAAY,EACZ,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpG,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EAAE,iBAAiB,EAAsB,MAAM,yBAAyB,CAAA;AAC/E,OAAO,EAAE,sBAAsB,EAAmB,MAAM,8BAA8B,CAAA;AAItF;;;;;;;;;;;GAWG;AAEH,MAAM,IAAI,GAAG,GAAS,EAAE,GAAE,CAAC,CAAA;AAE3B,mFAAmF;AACnF,MAAM,WAAW,GAAG,0BAA0B,CAAC;IAC7C,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;IACnC,KAAK,EAAE,MAAM;CACd,CAAC,CAAA;AAEF,kFAAkF;AAClF,SAAS,gBAAgB;IACvB,MAAM,MAAM,GAAG,aAAa,EAAE,CAAA;IAC9B,OAAO,qDAA6B,MAAM,CAAC,KAAK,IAAI,OAAO,IAAQ,CAAA;AACrE,CAAC;AAED,8EAA8E;AAC9E,SAAS,WAAW;IAClB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,OAAO,iDAAyB,MAAM,IAAQ,CAAA;AAChD,CAAC;AAED,sEAAsE;AACtE,SAAS,kBAAkB,CAAC,EAAE,IAAI,EAAwC;IACxE,OAAO,mDAA2B,IAAI,EAAE,KAAK,IAAI,YAAY,IAAO,CAAA;AACtE,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,GAAG,sBAAsB;IAEzB,4BAA4B;IAC5B;QACE,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,eAAe;QACvB,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,MAAC,UAAU,eACT,KAAC,QAAQ,IAAC,IAAI,EAAE,CAAC,YACf,0CAAwB,GACf,EACX,KAAC,QAAQ,IAAC,IAAI,EAAE,CAAC,YACf,sCAAoB,GACX,IACA,CACd;KACF;IACD;QACE,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,UAAU,cACT,KAAC,QAAQ,IAAC,IAAI,EAAE,CAAC,YACf,wCAAsB,GACb,GACA,CACd;KACF;IACD;QACE,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,YAAY;QACpB,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,OAAO,IACN,KAAK,QACL,MAAM,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,EAC9C,KAAK,EAAE;gBACL;oBACE,KAAK,EAAE,MAAM;oBACb,KAAK,EAAE,CAAC;oBACR,IAAI,EAAE,QAAQ;oBACd,KAAK,EAAE,iBAAiB;oBACxB,cAAc,EAAE,IAAI;iBACrB;gBACD,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE;gBAC9D,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE;aACpE,GACD,CACH;KACF;IACD;QACE,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,GAAG,EAAE,CAAC,KAAC,QAAQ,IAAC,IAAI,EAAC,SAAS,4BAAuB;KAC9D;IACD;QACE,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,KAAC,SAAS,IAAC,IAAI,EAAC,SAAS,6BAAyB;KACjE;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,MAAM,EAAE,mBAAmB;QAC3B,MAAM,EAAE,GAAG,EAAE,CAAC,KAAC,cAAc,IAAC,KAAK,EAAC,mBAAmB,EAAC,IAAI,EAAC,SAAS,GAAG;KAC1E;IACD;QACE,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,iBAAiB;QACzB,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,YAAY,IACX,IAAI,EAAC,QAAG,EACR,QAAQ,EAAC,MAAM,EACf,KAAK,EAAC,iBAAiB,EACvB,GAAG,EAAE,KAAC,QAAQ,uBAAgB,EAC9B,OAAO,EACL,KAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,OAAO,EAAE,IAAI,wBAEtC,GAEX,CACH;KACF;IACD;QACE,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,SAAS,IAAC,QAAQ,QAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,yCAAuB,YAClE,8CAA4B,GAClB,CACb;KACF;IACD;QACE,0DAA0D;QAC1D,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,SAAS,IACR,MAAM,EAAC,cAAc,EACrB,iBAAiB,EAAC,oBAAe,EACjC,cAAc,EAAE,IAAI,EACpB,KAAK,EAAE;gBACL,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;gBACrD,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;aAC5B,EACD,YAAY,EAAE,IAAI,GAClB,CACH;KACF;IACD;QACE,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,gBAAgB;QACxB,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,WAAW,IAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAC,QAAG,EAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,mBAAmB,+BAE9D,CACf;KACF;IACD;QACE,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,GAAG,EAAE,CAAC,KAAC,UAAU,IAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,QAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAC,OAAO,GAAG;KACzF;IAED,8BAA8B;IAC9B;QACE,IAAI,EAAE,oBAAoB;QAC1B,MAAM,EAAE,4BAA4B;QACpC,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,kBAAkB,IAAC,MAAM,EAAE,WAAW,YACrC,KAAC,gBAAgB,KAAG,GACD,CACtB;KACF;IACD;QACE,yEAAyE;QACzE,iEAAiE;QACjE,IAAI,EAAE,kBAAkB;QACxB,MAAM,EAAE,qBAAqB;QAC7B,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,kBAAkB,IAAC,MAAM,EAAE,WAAW,YACrC,KAAC,gBAAgB,IAAC,OAAO,EAAC,0BAA0B,YAClD,gDAA8B,GACb,GACA,CACtB;KACF;IACD;QACE,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,kBAAkB;QAC1B,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,aAAa,IAAC,KAAK,EAAE,YAAY,CAAC,OAAO,EAAE,IAAI,EAAC,MAAM,YACrD,6CAA2B,GACb,CACjB;KACF;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,MAAM,EAAE,sBAAsB;QAC9B,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,cAAc,IAAC,MAAM,EAAC,IAAI,YACzB,KAAC,WAAW,KAAG,GACA,CAClB;KACF;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,MAAM,EAAE,mCAAmC;QAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,KAAC,iBAAiB,IAChB,MAAM,EAAE,kBAAmC,EAC3C,IAAI,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,GAC/B,CACH;KACF;CACF,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miragon/mcp-toolkit-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "React UI for MCP servers: shadcn primitives, composite components, TanStack Query hooks, and the MCP App shell (McpAppView + WidgetRenderer).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,29 +42,33 @@
|
|
|
42
42
|
"react": "19.2.8",
|
|
43
43
|
"react-dom": "19.2.8",
|
|
44
44
|
"tailwindcss": "4.3.3",
|
|
45
|
-
"@miragon/mcp-toolkit-core": "
|
|
45
|
+
"@miragon/mcp-toolkit-core": "2.0.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@base-ui/react": "1.
|
|
48
|
+
"@base-ui/react": "1.7.0",
|
|
49
49
|
"@fontsource-variable/geist": "5.3.0",
|
|
50
50
|
"@tanstack/react-query": "5.101.4",
|
|
51
51
|
"class-variance-authority": "0.7.1",
|
|
52
52
|
"clsx": "2.1.1",
|
|
53
|
-
"lucide-react": "
|
|
53
|
+
"lucide-react": "1.27.0",
|
|
54
54
|
"radix-ui": "1.6.7",
|
|
55
55
|
"tailwind-merge": "3.6.0",
|
|
56
56
|
"tw-animate-css": "1.4.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
+
"@stryker-mutator/core": "9.6.1",
|
|
60
|
+
"@stryker-mutator/vitest-runner": "9.6.1",
|
|
59
61
|
"@types/react": "19.2.18",
|
|
60
62
|
"@types/react-dom": "19.2.4",
|
|
63
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
64
|
+
"fast-check": "4.9.0",
|
|
61
65
|
"mcp-use": "2.1.0",
|
|
62
66
|
"react": "19.2.8",
|
|
63
67
|
"react-dom": "19.2.8",
|
|
64
68
|
"tailwindcss": "4.3.3",
|
|
65
69
|
"typescript": "6.0.3",
|
|
66
70
|
"vitest": "4.1.10",
|
|
67
|
-
"@miragon/mcp-toolkit-core": "
|
|
71
|
+
"@miragon/mcp-toolkit-core": "2.0.0"
|
|
68
72
|
},
|
|
69
73
|
"engines": {
|
|
70
74
|
"node": ">=22.22.2"
|
|
@@ -73,6 +77,7 @@
|
|
|
73
77
|
"build": "tsc -p tsconfig.build.json",
|
|
74
78
|
"typecheck": "tsc --noEmit",
|
|
75
79
|
"test": "vitest run",
|
|
80
|
+
"test:coverage": "vitest run --coverage",
|
|
76
81
|
"lint": "eslint src --max-warnings 0",
|
|
77
82
|
"lint:fix": "eslint src --fix"
|
|
78
83
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AlertTriangle } from "lucide-react"
|
|
2
|
+
import type { LayoutBuilderLabels } from "./labels.js"
|
|
3
|
+
|
|
4
|
+
// -------------------------------------------------------------------------- //
|
|
5
|
+
// Builder banners — catalogue-fetch failure + pipeline validation warning
|
|
6
|
+
// -------------------------------------------------------------------------- //
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Inline feedback strip under the toolbar. Surfaces two things the builder
|
|
10
|
+
* previously swallowed:
|
|
11
|
+
* - a `get-builder-catalogue` fetch failure (Finding [3]) — only console-
|
|
12
|
+
* logged before, so the user couldn't tell the palette was stale, and
|
|
13
|
+
* - pipeline validation issues (Finding [10]/[6]) the catalogue reports
|
|
14
|
+
* fail-soft but `render-view` would reject fail-hard.
|
|
15
|
+
*
|
|
16
|
+
* Renders nothing when there's no error and no issues, so the happy path is
|
|
17
|
+
* visually unchanged.
|
|
18
|
+
*/
|
|
19
|
+
export function BuilderBanners({
|
|
20
|
+
L,
|
|
21
|
+
catalogueToolName,
|
|
22
|
+
refreshError,
|
|
23
|
+
validationIssues,
|
|
24
|
+
}: {
|
|
25
|
+
L: Required<LayoutBuilderLabels>
|
|
26
|
+
catalogueToolName: string
|
|
27
|
+
refreshError: string | null
|
|
28
|
+
validationIssues: string[]
|
|
29
|
+
}) {
|
|
30
|
+
if (!refreshError && validationIssues.length === 0) return null
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex flex-col gap-2" role="status" aria-live="polite">
|
|
33
|
+
{refreshError && (
|
|
34
|
+
<div className="border-destructive/30 bg-destructive/10 text-destructive flex items-start gap-2 rounded-md border px-3 py-2 text-xs">
|
|
35
|
+
<AlertTriangle className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
|
|
36
|
+
<div className="min-w-0">
|
|
37
|
+
<p className="font-medium">{L.catalogueError}</p>
|
|
38
|
+
<p className="text-destructive/80 mt-0.5 break-words">
|
|
39
|
+
<span className="font-mono">{catalogueToolName}</span>: {refreshError}
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
{validationIssues.length > 0 && (
|
|
45
|
+
<div className="flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-700 dark:text-amber-400">
|
|
46
|
+
<AlertTriangle className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
|
|
47
|
+
<div className="min-w-0">
|
|
48
|
+
<p className="font-medium">{L.validationWarning}</p>
|
|
49
|
+
<ul className="mt-0.5 list-inside list-disc space-y-0.5">
|
|
50
|
+
{validationIssues.map((issue, idx) => (
|
|
51
|
+
<li key={idx} className="break-words">
|
|
52
|
+
{issue}
|
|
53
|
+
</li>
|
|
54
|
+
))}
|
|
55
|
+
</ul>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { memo, useCallback, useRef } from "react"
|
|
2
|
+
import { ChevronDown, ChevronUp, SlidersHorizontal, Trash2, X } from "lucide-react"
|
|
3
|
+
import type { RowDef, WidgetProps } from "@miragon/mcp-toolkit-core"
|
|
4
|
+
import { GridItem, GridLayout } from "../../components/GridLayout.js"
|
|
5
|
+
import { Button } from "../../primitives/button.js"
|
|
6
|
+
import {
|
|
7
|
+
Select,
|
|
8
|
+
SelectContent,
|
|
9
|
+
SelectItem,
|
|
10
|
+
SelectTrigger,
|
|
11
|
+
SelectValue,
|
|
12
|
+
} from "../../primitives/select.js"
|
|
13
|
+
import { cn } from "../../lib/utils.js"
|
|
14
|
+
import { RenderedWidget, type WidgetComponent } from "../widget-renderer.js"
|
|
15
|
+
import type { LayoutBuilderLabels } from "./labels.js"
|
|
16
|
+
import { WIDGET_DRAG_MIME } from "./labels.js"
|
|
17
|
+
|
|
18
|
+
// -------------------------------------------------------------------------- //
|
|
19
|
+
// Canvas row — drop target + per-cell controls + resize handle
|
|
20
|
+
// -------------------------------------------------------------------------- //
|
|
21
|
+
|
|
22
|
+
function CanvasRowImpl({
|
|
23
|
+
L,
|
|
24
|
+
row,
|
|
25
|
+
rowIdx,
|
|
26
|
+
focused,
|
|
27
|
+
isFirst,
|
|
28
|
+
isLast,
|
|
29
|
+
dragOver,
|
|
30
|
+
setDragOverRow,
|
|
31
|
+
setFocusedRowIndex,
|
|
32
|
+
onRemoveRow,
|
|
33
|
+
onMoveRow,
|
|
34
|
+
onSetCellSpan,
|
|
35
|
+
onRemoveCell,
|
|
36
|
+
onAddWidget,
|
|
37
|
+
onConfigureCell,
|
|
38
|
+
propsSchemaByWidgetId,
|
|
39
|
+
widgets,
|
|
40
|
+
widgetProps,
|
|
41
|
+
}: {
|
|
42
|
+
L: Required<LayoutBuilderLabels>
|
|
43
|
+
row: RowDef
|
|
44
|
+
rowIdx: number
|
|
45
|
+
focused: boolean
|
|
46
|
+
isFirst: boolean
|
|
47
|
+
isLast: boolean
|
|
48
|
+
dragOver: boolean
|
|
49
|
+
setDragOverRow: (idx: number | null) => void
|
|
50
|
+
setFocusedRowIndex: (idx: number) => void
|
|
51
|
+
onRemoveRow: (rowIdx: number) => void
|
|
52
|
+
onMoveRow: (rowIdx: number, direction: -1 | 1) => void
|
|
53
|
+
onSetCellSpan: (rowIdx: number, cellIdx: number, span: number) => void
|
|
54
|
+
onRemoveCell: (rowIdx: number, cellIdx: number) => void
|
|
55
|
+
onAddWidget: (widgetId: string, targetRowIdx?: number) => void
|
|
56
|
+
onConfigureCell: (rowIdx: number, cellIdx: number) => void
|
|
57
|
+
propsSchemaByWidgetId: Map<string, Record<string, unknown>>
|
|
58
|
+
widgets: Record<string, WidgetComponent>
|
|
59
|
+
widgetProps: WidgetProps
|
|
60
|
+
}) {
|
|
61
|
+
const gridRef = useRef<HTMLDivElement | null>(null)
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
onClick={() => setFocusedRowIndex(rowIdx)}
|
|
66
|
+
onDragOver={(e) => {
|
|
67
|
+
if (!e.dataTransfer.types.includes(WIDGET_DRAG_MIME)) return
|
|
68
|
+
e.preventDefault()
|
|
69
|
+
e.dataTransfer.dropEffect = "copy"
|
|
70
|
+
setDragOverRow(rowIdx)
|
|
71
|
+
}}
|
|
72
|
+
onDragLeave={(e) => {
|
|
73
|
+
if (e.currentTarget.contains(e.relatedTarget as Node)) return
|
|
74
|
+
setDragOverRow(null)
|
|
75
|
+
}}
|
|
76
|
+
onDrop={(e) => {
|
|
77
|
+
const id = e.dataTransfer.getData(WIDGET_DRAG_MIME)
|
|
78
|
+
setDragOverRow(null)
|
|
79
|
+
if (id) {
|
|
80
|
+
e.preventDefault()
|
|
81
|
+
onAddWidget(id, rowIdx)
|
|
82
|
+
}
|
|
83
|
+
}}
|
|
84
|
+
className={cn(
|
|
85
|
+
"group relative rounded-lg border-l-2 px-3 py-2 transition-colors",
|
|
86
|
+
focused
|
|
87
|
+
? "border-l-primary bg-card"
|
|
88
|
+
: "bg-background hover:bg-card/60 border-l-transparent",
|
|
89
|
+
dragOver && "bg-primary/5 border-l-primary",
|
|
90
|
+
)}
|
|
91
|
+
>
|
|
92
|
+
<div className="mb-2 flex items-center justify-between">
|
|
93
|
+
<div className="text-muted-foreground flex items-center gap-2 text-[11px]">
|
|
94
|
+
<span className="font-mono">row {rowIdx + 1}</span>
|
|
95
|
+
<span className="bg-muted/60 rounded px-1 py-0.5 text-[10px]">
|
|
96
|
+
{row.row.length} {row.row.length === 1 ? "widget" : "widgets"}
|
|
97
|
+
</span>
|
|
98
|
+
</div>
|
|
99
|
+
<div className="opacity-0 transition-opacity group-hover:opacity-100">
|
|
100
|
+
<Button
|
|
101
|
+
variant="ghost"
|
|
102
|
+
size="icon-xs"
|
|
103
|
+
onClick={(e) => {
|
|
104
|
+
e.stopPropagation()
|
|
105
|
+
onMoveRow(rowIdx, -1)
|
|
106
|
+
}}
|
|
107
|
+
disabled={isFirst}
|
|
108
|
+
aria-label="Move row up"
|
|
109
|
+
>
|
|
110
|
+
<ChevronUp />
|
|
111
|
+
</Button>
|
|
112
|
+
<Button
|
|
113
|
+
variant="ghost"
|
|
114
|
+
size="icon-xs"
|
|
115
|
+
onClick={(e) => {
|
|
116
|
+
e.stopPropagation()
|
|
117
|
+
onMoveRow(rowIdx, 1)
|
|
118
|
+
}}
|
|
119
|
+
disabled={isLast}
|
|
120
|
+
aria-label="Move row down"
|
|
121
|
+
>
|
|
122
|
+
<ChevronDown />
|
|
123
|
+
</Button>
|
|
124
|
+
<Button
|
|
125
|
+
variant="ghost"
|
|
126
|
+
size="icon-xs"
|
|
127
|
+
onClick={(e) => {
|
|
128
|
+
e.stopPropagation()
|
|
129
|
+
onRemoveRow(rowIdx)
|
|
130
|
+
}}
|
|
131
|
+
aria-label={L.removeRow}
|
|
132
|
+
>
|
|
133
|
+
<Trash2 />
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
{row.row.length === 0 ? (
|
|
138
|
+
<div
|
|
139
|
+
className={cn(
|
|
140
|
+
"rounded-md border border-dashed py-6 text-center text-[11px] transition-colors",
|
|
141
|
+
dragOver
|
|
142
|
+
? "border-primary bg-primary/5 text-primary"
|
|
143
|
+
: "border-muted-foreground/30 text-muted-foreground",
|
|
144
|
+
)}
|
|
145
|
+
>
|
|
146
|
+
{dragOver ? L.canvasDropHint : L.canvasEmpty}
|
|
147
|
+
</div>
|
|
148
|
+
) : (
|
|
149
|
+
<div ref={gridRef} data-row-grid>
|
|
150
|
+
<GridLayout>
|
|
151
|
+
{row.row.map((cell, cellIdx) => {
|
|
152
|
+
const span = cell.span ?? 12
|
|
153
|
+
const Widget = widgets[cell.widget]
|
|
154
|
+
const hasPropsSchema = propsSchemaByWidgetId.has(cell.widget)
|
|
155
|
+
const propCount = cell.props ? Object.keys(cell.props).length : 0
|
|
156
|
+
return (
|
|
157
|
+
<GridItem key={`${cell.widget}-${cellIdx}`} span={span}>
|
|
158
|
+
<div className="border-border bg-card relative overflow-hidden rounded-md border shadow-sm">
|
|
159
|
+
<div className="text-muted-foreground bg-muted/30 flex items-center justify-between gap-2 border-b px-2 py-1 text-[10px]">
|
|
160
|
+
<span className="truncate font-mono">{cell.widget}</span>
|
|
161
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
162
|
+
<label className="text-muted-foreground/80 inline-flex items-center gap-0.5 font-mono">
|
|
163
|
+
<span className="sr-only">Column span</span>
|
|
164
|
+
<Select
|
|
165
|
+
value={String(span)}
|
|
166
|
+
onValueChange={(v) => onSetCellSpan(rowIdx, cellIdx, Number(v))}
|
|
167
|
+
>
|
|
168
|
+
<SelectTrigger
|
|
169
|
+
size="sm"
|
|
170
|
+
className="h-5 gap-0.5 border-none bg-transparent px-1 py-0 font-mono text-[10px] shadow-none focus:ring-0"
|
|
171
|
+
aria-label="Column span"
|
|
172
|
+
onClick={(e) => e.stopPropagation()}
|
|
173
|
+
>
|
|
174
|
+
<SelectValue />
|
|
175
|
+
</SelectTrigger>
|
|
176
|
+
<SelectContent>
|
|
177
|
+
{Array.from({ length: 12 }, (_, i) => i + 1).map((n) => (
|
|
178
|
+
<SelectItem key={n} value={String(n)} className="font-mono text-xs">
|
|
179
|
+
{n}
|
|
180
|
+
</SelectItem>
|
|
181
|
+
))}
|
|
182
|
+
</SelectContent>
|
|
183
|
+
</Select>
|
|
184
|
+
<span aria-hidden="true">/12</span>
|
|
185
|
+
</label>
|
|
186
|
+
{hasPropsSchema && (
|
|
187
|
+
<Button
|
|
188
|
+
variant="ghost"
|
|
189
|
+
size="icon-xs"
|
|
190
|
+
onClick={(e) => {
|
|
191
|
+
e.stopPropagation()
|
|
192
|
+
onConfigureCell(rowIdx, cellIdx)
|
|
193
|
+
}}
|
|
194
|
+
aria-label="Configure widget props"
|
|
195
|
+
title={
|
|
196
|
+
propCount > 0
|
|
197
|
+
? `Configure widget props (${propCount} set)`
|
|
198
|
+
: "Configure widget props"
|
|
199
|
+
}
|
|
200
|
+
className={cn(propCount > 0 && "text-primary")}
|
|
201
|
+
>
|
|
202
|
+
<SlidersHorizontal />
|
|
203
|
+
</Button>
|
|
204
|
+
)}
|
|
205
|
+
<Button
|
|
206
|
+
variant="ghost"
|
|
207
|
+
size="icon-xs"
|
|
208
|
+
onClick={(e) => {
|
|
209
|
+
e.stopPropagation()
|
|
210
|
+
onRemoveCell(rowIdx, cellIdx)
|
|
211
|
+
}}
|
|
212
|
+
aria-label="Remove widget"
|
|
213
|
+
>
|
|
214
|
+
<X />
|
|
215
|
+
</Button>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
<div className="p-2">
|
|
219
|
+
<RenderedWidget
|
|
220
|
+
widget={Widget}
|
|
221
|
+
widgetId={cell.widget}
|
|
222
|
+
props={widgetProps}
|
|
223
|
+
cellProps={cell.props}
|
|
224
|
+
showEmptyHint
|
|
225
|
+
notBundledClassName="text-muted-foreground rounded border border-dashed p-3 text-center text-[11px]"
|
|
226
|
+
/>
|
|
227
|
+
</div>
|
|
228
|
+
<ResizeHandle
|
|
229
|
+
rowIdx={rowIdx}
|
|
230
|
+
cellIdx={cellIdx}
|
|
231
|
+
span={span}
|
|
232
|
+
getGridEl={() => gridRef.current}
|
|
233
|
+
onSetCellSpan={onSetCellSpan}
|
|
234
|
+
/>
|
|
235
|
+
</div>
|
|
236
|
+
</GridItem>
|
|
237
|
+
)
|
|
238
|
+
})}
|
|
239
|
+
</GridLayout>
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const CanvasRow = memo(CanvasRowImpl)
|
|
247
|
+
|
|
248
|
+
// -------------------------------------------------------------------------- //
|
|
249
|
+
// Resize handle
|
|
250
|
+
// -------------------------------------------------------------------------- //
|
|
251
|
+
|
|
252
|
+
function ResizeHandle({
|
|
253
|
+
rowIdx,
|
|
254
|
+
cellIdx,
|
|
255
|
+
span,
|
|
256
|
+
getGridEl,
|
|
257
|
+
onSetCellSpan,
|
|
258
|
+
}: {
|
|
259
|
+
rowIdx: number
|
|
260
|
+
cellIdx: number
|
|
261
|
+
span: number
|
|
262
|
+
getGridEl: () => HTMLElement | null
|
|
263
|
+
onSetCellSpan: (rowIdx: number, cellIdx: number, span: number) => void
|
|
264
|
+
}) {
|
|
265
|
+
const onPointerDown = useCallback(
|
|
266
|
+
(e: React.PointerEvent<HTMLDivElement>) => {
|
|
267
|
+
e.preventDefault()
|
|
268
|
+
e.stopPropagation()
|
|
269
|
+
const grid = getGridEl()
|
|
270
|
+
if (!grid) return
|
|
271
|
+
const rect = grid.getBoundingClientRect()
|
|
272
|
+
const colWidth = rect.width / 12
|
|
273
|
+
const startX = e.clientX
|
|
274
|
+
const startSpan = span
|
|
275
|
+
const onMove = (ev: PointerEvent) => {
|
|
276
|
+
const delta = ev.clientX - startX
|
|
277
|
+
const next = Math.round(startSpan + delta / colWidth)
|
|
278
|
+
onSetCellSpan(rowIdx, cellIdx, Math.max(1, Math.min(12, next)))
|
|
279
|
+
}
|
|
280
|
+
const onUp = () => {
|
|
281
|
+
window.removeEventListener("pointermove", onMove)
|
|
282
|
+
window.removeEventListener("pointerup", onUp)
|
|
283
|
+
}
|
|
284
|
+
window.addEventListener("pointermove", onMove)
|
|
285
|
+
window.addEventListener("pointerup", onUp)
|
|
286
|
+
},
|
|
287
|
+
[cellIdx, getGridEl, onSetCellSpan, rowIdx, span],
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
const onKeyDown = useCallback(
|
|
291
|
+
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
292
|
+
const dir = e.key === "ArrowLeft" ? -1 : e.key === "ArrowRight" ? 1 : 0
|
|
293
|
+
if (dir === 0) return
|
|
294
|
+
e.preventDefault()
|
|
295
|
+
e.stopPropagation()
|
|
296
|
+
onSetCellSpan(rowIdx, cellIdx, Math.max(1, Math.min(12, span + dir)))
|
|
297
|
+
},
|
|
298
|
+
[cellIdx, onSetCellSpan, rowIdx, span],
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<div
|
|
303
|
+
onPointerDown={onPointerDown}
|
|
304
|
+
onClick={(e) => e.stopPropagation()}
|
|
305
|
+
onKeyDown={onKeyDown}
|
|
306
|
+
tabIndex={0}
|
|
307
|
+
className="hover:bg-primary/40 focus-visible:bg-primary/40 focus-visible:ring-primary/60 absolute top-0 right-0 h-full w-1.5 cursor-col-resize touch-none select-none focus-visible:ring-1 focus-visible:outline-none"
|
|
308
|
+
aria-label="Resize widget"
|
|
309
|
+
role="separator"
|
|
310
|
+
aria-orientation="vertical"
|
|
311
|
+
aria-valuenow={span}
|
|
312
|
+
aria-valuemin={1}
|
|
313
|
+
aria-valuemax={12}
|
|
314
|
+
/>
|
|
315
|
+
)
|
|
316
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RowDef } from "@miragon/mcp-toolkit-core"
|
|
2
|
+
import { WidgetPropsSheet } from "../widget-props-sheet.js"
|
|
3
|
+
|
|
4
|
+
// -------------------------------------------------------------------------- //
|
|
5
|
+
// Cell props sheet — mounts the WidgetPropsSheet for the configured cell
|
|
6
|
+
// -------------------------------------------------------------------------- //
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mounted once at the LayoutBuilder root so that exactly one props editor
|
|
10
|
+
* can be open at a time across the entire workspace — including across
|
|
11
|
+
* tabs. `configuringCell` addresses `{ rowIdx, cellIdx }` on the currently
|
|
12
|
+
* active draft surface (rows or active tab's rows); renders nothing when no
|
|
13
|
+
* cell is being configured or the address no longer resolves.
|
|
14
|
+
*/
|
|
15
|
+
export function CellPropsSheet({
|
|
16
|
+
configuringCell,
|
|
17
|
+
activeRows,
|
|
18
|
+
propsSchemaByWidgetId,
|
|
19
|
+
onApply,
|
|
20
|
+
onClose,
|
|
21
|
+
}: {
|
|
22
|
+
configuringCell: { rowIdx: number; cellIdx: number } | null
|
|
23
|
+
activeRows: RowDef[]
|
|
24
|
+
propsSchemaByWidgetId: Map<string, Record<string, unknown>>
|
|
25
|
+
onApply: (rowIdx: number, cellIdx: number, next: Record<string, unknown> | undefined) => void
|
|
26
|
+
onClose: () => void
|
|
27
|
+
}) {
|
|
28
|
+
if (!configuringCell) return null
|
|
29
|
+
const cell = activeRows[configuringCell.rowIdx]?.row[configuringCell.cellIdx]
|
|
30
|
+
if (!cell) return null
|
|
31
|
+
const schema = propsSchemaByWidgetId.get(cell.widget)
|
|
32
|
+
return (
|
|
33
|
+
<WidgetPropsSheet
|
|
34
|
+
open
|
|
35
|
+
onOpenChange={(open) => {
|
|
36
|
+
if (!open) onClose()
|
|
37
|
+
}}
|
|
38
|
+
widgetId={cell.widget}
|
|
39
|
+
schema={schema}
|
|
40
|
+
value={cell.props}
|
|
41
|
+
onApply={(next) => {
|
|
42
|
+
onApply(configuringCell.rowIdx, configuringCell.cellIdx, next)
|
|
43
|
+
onClose()
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Layers, SlidersHorizontal } from "lucide-react"
|
|
2
|
+
import { Button } from "../../primitives/button.js"
|
|
3
|
+
import type { LayoutBuilderLabels } from "./labels.js"
|
|
4
|
+
|
|
5
|
+
// -------------------------------------------------------------------------- //
|
|
6
|
+
// Empty hint — shown on the Layout view before any widget is reachable
|
|
7
|
+
// -------------------------------------------------------------------------- //
|
|
8
|
+
|
|
9
|
+
export function EmptyHint({
|
|
10
|
+
L,
|
|
11
|
+
onGotoPipeline,
|
|
12
|
+
}: {
|
|
13
|
+
L: Required<LayoutBuilderLabels>
|
|
14
|
+
onGotoPipeline: () => void
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<div className="border-muted-foreground/30 text-muted-foreground flex flex-col items-center gap-2 rounded-md border border-dashed p-10 text-sm">
|
|
18
|
+
<Layers className="size-5" />
|
|
19
|
+
<p>No widgets reachable yet.</p>
|
|
20
|
+
<p className="text-xs">
|
|
21
|
+
Open the <span className="font-medium">{L.viewPipelineTab}</span> tab to seed keys or add a
|
|
22
|
+
pipeline step — the palette unlocks as soon as a widget's contract is satisfied.
|
|
23
|
+
</p>
|
|
24
|
+
<Button variant="outline" size="sm" onClick={onGotoPipeline} className="mt-1">
|
|
25
|
+
<SlidersHorizontal /> Open {L.viewPipelineTab}
|
|
26
|
+
</Button>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|