add resize to content hook
This commit is contained in:
parent
f6c8b2a299
commit
2e52837bdb
16
README.md
16
README.md
@ -120,6 +120,22 @@ createUI('dashboard', require.resolve('./Dashboard.tsx')); // CommonJS
|
|||||||
- **`useProps(defaults)`** — Get props passed from the server
|
- **`useProps(defaults)`** — Get props passed from the server
|
||||||
- **`sendPrompt(message)`** — Send a message to the AI chat
|
- **`sendPrompt(message)`** — Send a message to the AI chat
|
||||||
- **`callTool(name, params)`** — Invoke an MCP tool
|
- **`callTool(name, params)`** — Invoke an MCP tool
|
||||||
|
- **`resizeToContent(element?)`** — Request parent to resize iframe to fit content
|
||||||
|
- **`useResizeToContent()`** — Hook that auto-notifies parent when content size changes
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useResizeToContent } from 'mcp-ui-kit/ui';
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const containerRef = useResizeToContent();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef}>
|
||||||
|
{/* Content that may change size */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
BIN
nanobot.db
BIN
nanobot.db
Binary file not shown.
@ -9,8 +9,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run start --workspace=demo-server & npm run dev --workspace=@mcp-ui-kit/inspector",
|
"dev": "npm run start --workspace=demo-server & npm run dev --workspace=@mcp-ui-kit/inspector",
|
||||||
"start": "npm run start --workspace=demo-server",
|
"start": "npm run start --workspace=demo-server",
|
||||||
|
"build": "npm run build --workspace=packages/library",
|
||||||
"start:inspector": "npm run dev --workspace=@mcp-ui-kit/inspector",
|
"start:inspector": "npm run dev --workspace=@mcp-ui-kit/inspector",
|
||||||
"publish:library": "npm publish --workspace=packages/library"
|
"publish:library": "npm publish --workspace=packages/library",
|
||||||
|
"nanobot": "export $(cat .env | xargs) && nanobot run ./nanobot.yaml"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { generateMockStockData } from './stock-utils';
|
import { generateMockStockData } from './stock-utils';
|
||||||
import { callTool, sendPrompt, useProps } from 'mcp-ui-kit/ui';
|
import { callTool, sendPrompt, useProps, useResizeToContent } from 'mcp-ui-kit/ui';
|
||||||
|
|
||||||
|
|
||||||
// Types for props passed from the tool handler
|
// Types for props passed from the tool handler
|
||||||
@ -31,6 +31,9 @@ export function StockDashboard() {
|
|||||||
timeframe: '1M',
|
timeframe: '1M',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Auto-notify parent when content size changes
|
||||||
|
const containerRef = useResizeToContent();
|
||||||
|
|
||||||
const stocks: StockData[] = generateMockStockData(props.symbols);
|
const stocks: StockData[] = generateMockStockData(props.symbols);
|
||||||
|
|
||||||
const [selectedStock, setSelectedStock] = useState<string>(stocks[0]?.symbol || '');
|
const [selectedStock, setSelectedStock] = useState<string>(stocks[0]?.symbol || '');
|
||||||
@ -70,7 +73,7 @@ Please analyze this portfolio and provide recommendations.`;
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<style>{styles}</style>
|
<style>{styles}</style>
|
||||||
<div className="dashboard">
|
<div className="dashboard" ref={containerRef}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { styles } from './styles';
|
import { styles } from './styles';
|
||||||
|
import { useResizeToContent } from 'mcp-ui-kit/ui';
|
||||||
|
|
||||||
interface WeatherData {
|
interface WeatherData {
|
||||||
temp: number;
|
temp: number;
|
||||||
@ -35,6 +36,7 @@ const weatherDataByCity: WeatherData = {
|
|||||||
|
|
||||||
|
|
||||||
export function WeatherDashboard() {
|
export function WeatherDashboard() {
|
||||||
|
const containerRef = useResizeToContent();
|
||||||
const data = weatherDataByCity;
|
const data = weatherDataByCity;
|
||||||
const currentDate = new Date().toLocaleDateString('en-US', {
|
const currentDate = new Date().toLocaleDateString('en-US', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
@ -60,7 +62,7 @@ ${data.forecast.map(day => `${day.day}: ${day.icon} ${day.temp}°C`).join('\n')}
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<style>{styles}</style>
|
<style>{styles}</style>
|
||||||
<div className="container">
|
<div className="container" ref={containerRef}>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<h1>
|
<h1>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect, useRef, type RefObject } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a prompt to the parent window
|
* Send a prompt to the parent window
|
||||||
@ -24,6 +25,64 @@ export function callTool(toolName: string, params: Record<string, unknown> = {})
|
|||||||
}, '*');
|
}, '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the parent to resize the iframe to fit content
|
||||||
|
* Call this after your content has rendered
|
||||||
|
*/
|
||||||
|
export function resizeToContent(element?: HTMLElement) {
|
||||||
|
const target = element || document.body;
|
||||||
|
|
||||||
|
// Use scrollHeight/scrollWidth for full content size (not just visible area)
|
||||||
|
// Also account for any margin on the element
|
||||||
|
const style = window.getComputedStyle(target);
|
||||||
|
const marginTop = parseFloat(style.marginTop) || 0;
|
||||||
|
const marginBottom = parseFloat(style.marginBottom) || 0;
|
||||||
|
const marginLeft = parseFloat(style.marginLeft) || 0;
|
||||||
|
const marginRight = parseFloat(style.marginRight) || 0;
|
||||||
|
|
||||||
|
// For body, also account for body padding/margin
|
||||||
|
let extraHeight = 0;
|
||||||
|
let extraWidth = 0;
|
||||||
|
if (target !== document.body) {
|
||||||
|
const bodyStyle = window.getComputedStyle(document.body);
|
||||||
|
extraHeight = (parseFloat(bodyStyle.paddingTop) || 0) + (parseFloat(bodyStyle.paddingBottom) || 0);
|
||||||
|
extraWidth = (parseFloat(bodyStyle.paddingLeft) || 0) + (parseFloat(bodyStyle.paddingRight) || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = Math.ceil(target.scrollWidth + marginLeft + marginRight + extraWidth);
|
||||||
|
const height = Math.ceil(target.scrollHeight + marginTop + marginBottom + extraHeight);
|
||||||
|
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'resize',
|
||||||
|
payload: { width, height }
|
||||||
|
}, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook that automatically notifies parent when content size changes
|
||||||
|
* Returns a ref to attach to your container element
|
||||||
|
*/
|
||||||
|
export function useResizeToContent<T extends HTMLElement = HTMLDivElement>(): RefObject<T | null> {
|
||||||
|
const ref = useRef<T | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = ref.current;
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(() => {
|
||||||
|
resizeToContent(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(element);
|
||||||
|
// Initial resize
|
||||||
|
resizeToContent(element);
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use MCP props
|
* Use MCP props
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user