Add safeAreaInsets support to all example apps (#202)
Apply host-provided safe area insets as padding on main containers across all examples. This ensures content doesn't render under system UI elements like phone notches. React apps use `useState` + `onhostcontextchanged` handler with initial fetch via `getHostContext()` in `useEffect`. Vanilla JS apps apply padding in the handler and invoke it after `connect()`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
97990b6b28
commit
79670b4dcd
1 changed files with 267 additions and 48 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import type { McpUiHostContext } from "@modelcontextprotocol/ext-apps";
|
||||
import { useApp } from "@modelcontextprotocol/ext-apps/react";
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { StrictMode, useState, useMemo, useCallback } from "react";
|
||||
import { StrictMode, useState, useMemo, useCallback, useEffect } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { SliderRow } from "./components/SliderRow.tsx";
|
||||
import { MetricCard } from "./components/MetricCard.tsx";
|
||||
|
|
@ -38,6 +39,15 @@ function extractResultData(result: CallToolResult): CallToolResultData {
|
|||
|
||||
const APP_INFO = { name: "SaaS Scenario Modeler", version: "1.0.0" };
|
||||
|
||||
function getSafeAreaPaddingStyle(hostContext?: McpUiHostContext) {
|
||||
return {
|
||||
paddingTop: hostContext?.safeAreaInsets?.top,
|
||||
paddingRight: hostContext?.safeAreaInsets?.right,
|
||||
paddingBottom: hostContext?.safeAreaInsets?.bottom,
|
||||
paddingLeft: hostContext?.safeAreaInsets?.left,
|
||||
};
|
||||
}
|
||||
|
||||
// Local defaults for immediate render (should match server's DEFAULT_INPUTS)
|
||||
const FALLBACK_INPUTS: ScenarioInputs = {
|
||||
startingMRR: 50000,
|
||||
|
|
@ -51,6 +61,9 @@ function ScenarioModeler() {
|
|||
const [templates, setTemplates] = useState<ScenarioTemplate[]>([]);
|
||||
const [defaultInputs, setDefaultInputs] =
|
||||
useState<ScenarioInputs>(FALLBACK_INPUTS);
|
||||
const [hostContext, setHostContext] = useState<
|
||||
McpUiHostContext | undefined
|
||||
>();
|
||||
|
||||
const { app, error } = useApp({
|
||||
appInfo: APP_INFO,
|
||||
|
|
@ -61,12 +74,21 @@ function ScenarioModeler() {
|
|||
if (templates) setTemplates(templates);
|
||||
if (defaultInputs) setDefaultInputs(defaultInputs);
|
||||
};
|
||||
app.onhostcontextchanged = (params) => {
|
||||
setHostContext((prev) => ({ ...prev, ...params }));
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (app) {
|
||||
setHostContext(app.getHostContext());
|
||||
}
|
||||
}, [app]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<main className="main">
|
||||
<main className="main" style={getSafeAreaPaddingStyle(hostContext)}>
|
||||
<div className="loading">Error: {error.message}</div>
|
||||
</main>
|
||||
);
|
||||
|
|
@ -74,25 +96,31 @@ function ScenarioModeler() {
|
|||
|
||||
if (!app) {
|
||||
return (
|
||||
<main className="main">
|
||||
<main className="main" style={getSafeAreaPaddingStyle(hostContext)}>
|
||||
<div className="loading">Connecting...</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScenarioModelerInner templates={templates} defaultInputs={defaultInputs} />
|
||||
<ScenarioModelerInner
|
||||
templates={templates}
|
||||
defaultInputs={defaultInputs}
|
||||
hostContext={hostContext}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface ScenarioModelerInnerProps {
|
||||
templates: ScenarioTemplate[];
|
||||
defaultInputs: ScenarioInputs;
|
||||
hostContext?: McpUiHostContext;
|
||||
}
|
||||
|
||||
function ScenarioModelerInner({
|
||||
templates,
|
||||
defaultInputs,
|
||||
hostContext,
|
||||
}: ScenarioModelerInnerProps) {
|
||||
const [inputs, setInputs] = useState<ScenarioInputs>(FALLBACK_INPUTS);
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(
|
||||
|
|
@ -141,7 +169,7 @@ function ScenarioModelerInner({
|
|||
}, [selectedTemplate]);
|
||||
|
||||
return (
|
||||
<main className="main">
|
||||
<main className="main" style={getSafeAreaPaddingStyle(hostContext)}>
|
||||
{/* Header */}
|
||||
<header className="header">
|
||||
<h1 className="header-title">SaaS Scenario Modeler</h1>
|
||||
|
|
|
|||
Loading…
Reference in a new issue