Updates example to use theme colors and adds border radius
This commit is contained in:
parent
35781865af
commit
2fa243ae8c
1 changed files with 212 additions and 98 deletions
|
|
@ -1,7 +1,12 @@
|
|||
/**
|
||||
* @file Customer Segmentation Explorer - interactive scatter/bubble visualization
|
||||
*/
|
||||
import { App, PostMessageTransport } from "@modelcontextprotocol/ext-apps";
|
||||
import {
|
||||
App,
|
||||
PostMessageTransport,
|
||||
applyHostStyles,
|
||||
applyDocumentTheme,
|
||||
} from "@modelcontextprotocol/ext-apps";
|
||||
import { Chart, registerables } from "chart.js";
|
||||
import "./global.css";
|
||||
import "./mcp-app.css";
|
||||
|
|
@ -138,11 +143,40 @@ function buildDatasets(): Chart["data"]["datasets"] {
|
|||
});
|
||||
}
|
||||
|
||||
// Hidden element for resolving CSS color values (reused to avoid DOM thrashing)
|
||||
let colorResolver: HTMLDivElement | null = null;
|
||||
|
||||
// Resolve a CSS color value (handles light-dark() function)
|
||||
function resolveColor(cssValue: string, fallback: string): string {
|
||||
if (!cssValue) return fallback;
|
||||
// If it's a simple color value, return it directly
|
||||
if (!cssValue.includes("light-dark(")) return cssValue;
|
||||
// Create resolver element once and keep it hidden
|
||||
if (!colorResolver) {
|
||||
colorResolver = document.createElement("div");
|
||||
colorResolver.style.position = "absolute";
|
||||
colorResolver.style.visibility = "hidden";
|
||||
colorResolver.style.pointerEvents = "none";
|
||||
document.body.appendChild(colorResolver);
|
||||
}
|
||||
colorResolver.style.color = cssValue;
|
||||
return getComputedStyle(colorResolver).color || fallback;
|
||||
}
|
||||
|
||||
// Get colors from CSS variables
|
||||
function getChartColors(): { textColor: string; gridColor: string } {
|
||||
const style = getComputedStyle(document.documentElement);
|
||||
const rawTextColor = style.getPropertyValue("--color-text-secondary").trim();
|
||||
const rawGridColor = style.getPropertyValue("--color-border-primary").trim();
|
||||
return {
|
||||
textColor: resolveColor(rawTextColor, "#6b7280"),
|
||||
gridColor: resolveColor(rawGridColor, "#e5e7eb"),
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize Chart.js
|
||||
function initChart(): Chart {
|
||||
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const textColor = isDarkMode ? "#9ca3af" : "#6b7280";
|
||||
const gridColor = isDarkMode ? "#374151" : "#e5e7eb";
|
||||
const { textColor, gridColor } = getChartColors();
|
||||
|
||||
return new Chart(chartCanvas, {
|
||||
type: "bubble",
|
||||
|
|
@ -243,8 +277,7 @@ function initChart(): Chart {
|
|||
function updateChart(): void {
|
||||
if (!state.chart) return;
|
||||
|
||||
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const textColor = isDarkMode ? "#9ca3af" : "#6b7280";
|
||||
const { textColor, gridColor } = getChartColors();
|
||||
|
||||
state.chart.data.datasets = buildDatasets();
|
||||
|
||||
|
|
@ -252,11 +285,13 @@ function updateChart(): void {
|
|||
const scales = state.chart.options.scales as {
|
||||
x: {
|
||||
title: { text: string; color: string };
|
||||
ticks: { callback: (value: number) => string };
|
||||
ticks: { color: string; callback: (value: number) => string };
|
||||
grid: { color: string };
|
||||
};
|
||||
y: {
|
||||
title: { text: string; color: string };
|
||||
ticks: { callback: (value: number) => string };
|
||||
ticks: { color: string; callback: (value: number) => string };
|
||||
grid: { color: string };
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -264,8 +299,12 @@ function updateChart(): void {
|
|||
scales.y.title.text = METRIC_LABELS[state.yAxis];
|
||||
scales.x.title.color = textColor;
|
||||
scales.y.title.color = textColor;
|
||||
scales.x.ticks.color = textColor;
|
||||
scales.y.ticks.color = textColor;
|
||||
scales.x.ticks.callback = (value: number) => formatValue(value, state.xAxis);
|
||||
scales.y.ticks.callback = (value: number) => formatValue(value, state.yAxis);
|
||||
scales.x.grid.color = gridColor;
|
||||
scales.y.grid.color = gridColor;
|
||||
|
||||
state.chart.update();
|
||||
}
|
||||
|
|
@ -388,20 +427,52 @@ document.addEventListener("click", (e) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Handle theme changes
|
||||
// Handle system theme changes (fallback when host doesn't provide styles)
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", () => {
|
||||
if (state.chart) {
|
||||
state.chart.destroy();
|
||||
state.chart = initChart();
|
||||
.addEventListener("change", (e) => {
|
||||
// Only apply if we haven't received host theme
|
||||
if (!app.getHostContext()?.theme) {
|
||||
applyDocumentTheme(e.matches ? "dark" : "light");
|
||||
if (state.chart) {
|
||||
state.chart.destroy();
|
||||
state.chart = initChart();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Apply initial theme based on system preference (before host context is available)
|
||||
const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
applyDocumentTheme(systemDark ? "dark" : "light");
|
||||
|
||||
// Register handlers and connect
|
||||
app.onerror = log.error;
|
||||
|
||||
app.connect(new PostMessageTransport(window.parent));
|
||||
// Handle host context changes (theme and styles from host)
|
||||
app.onhostcontextchanged = (params) => {
|
||||
if (params.theme) {
|
||||
applyDocumentTheme(params.theme);
|
||||
}
|
||||
if (params.styles) {
|
||||
applyHostStyles(params.styles);
|
||||
}
|
||||
// Recreate chart to pick up new colors
|
||||
if (state.chart && (params.theme || params.styles)) {
|
||||
state.chart.destroy();
|
||||
state.chart = initChart();
|
||||
}
|
||||
};
|
||||
|
||||
app.connect(new PostMessageTransport(window.parent)).then(() => {
|
||||
// Apply initial host context after connection
|
||||
const ctx = app.getHostContext();
|
||||
if (ctx?.theme) {
|
||||
applyDocumentTheme(ctx.theme);
|
||||
}
|
||||
if (ctx?.styles) {
|
||||
applyHostStyles(ctx.styles);
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch data after connection
|
||||
setTimeout(fetchData, 100);
|
||||
|
|
|
|||
Loading…
Reference in a new issue