add resize to content hook

This commit is contained in:
Fredrik Jensen 2025-12-07 11:52:50 +01:00
parent f6c8b2a299
commit 2e52837bdb
6 changed files with 86 additions and 4 deletions

View File

@ -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

Binary file not shown.

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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
*/ */