fix: keep dashboard shell dimensions stable #41
@@ -1,11 +1,11 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" style="min-width: 0; scrollbar-gutter: stable">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
||||||
<title>Ekklesia</title>
|
<title>Ekklesia</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body style="margin: 0; min-width: 0">
|
||||||
<!-- The shell's root. A module UI never receives this element: it is given a container the
|
<!-- The shell's root. A module UI never receives this element: it is given a container the
|
||||||
shell creates inside its own layout. -->
|
shell creates inside its own layout. -->
|
||||||
<div id="ekklesia-host"></div>
|
<div id="ekklesia-host"></div>
|
||||||
|
|||||||
@@ -115,8 +115,10 @@ const shellStyles = {
|
|||||||
actions: { display: "flex", gap: designToken("spacing.sm"), justifyContent: "center" },
|
actions: { display: "flex", gap: designToken("spacing.sm"), justifyContent: "center" },
|
||||||
aside: {
|
aside: {
|
||||||
borderRight: `${designToken("border.width")} solid ${designToken("border.divider")}`,
|
borderRight: `${designToken("border.width")} solid ${designToken("border.divider")}`,
|
||||||
|
boxSizing: "border-box",
|
||||||
minWidth: "13rem",
|
minWidth: "13rem",
|
||||||
padding: designToken("spacing.lg"),
|
padding: designToken("spacing.lg"),
|
||||||
|
width: "13rem",
|
||||||
},
|
},
|
||||||
groupLabel: {
|
groupLabel: {
|
||||||
color: designToken("text.muted"),
|
color: designToken("text.muted"),
|
||||||
@@ -140,10 +142,17 @@ const shellStyles = {
|
|||||||
color: designToken("text.primary"),
|
color: designToken("text.primary"),
|
||||||
display: "grid",
|
display: "grid",
|
||||||
fontFamily: designToken("typography.family-sans"),
|
fontFamily: designToken("typography.family-sans"),
|
||||||
gridTemplateColumns: "auto 1fr",
|
gridTemplateColumns: "13rem minmax(0, 1fr)",
|
||||||
minHeight: "100vh",
|
minHeight: "100vh",
|
||||||
|
minWidth: 0,
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
main: {
|
||||||
|
boxSizing: "border-box",
|
||||||
|
minWidth: 0,
|
||||||
|
overflowX: "auto",
|
||||||
|
padding: designToken("spacing.lg"),
|
||||||
},
|
},
|
||||||
main: { padding: designToken("spacing.lg") },
|
|
||||||
message: {
|
message: {
|
||||||
color: designToken("text.secondary"),
|
color: designToken("text.secondary"),
|
||||||
display: "grid",
|
display: "grid",
|
||||||
@@ -531,7 +540,7 @@ export function HostApp(props: HostAppProps): ReactElement {
|
|||||||
const { groups, ungrouped } = bootstrap.navigation;
|
const { groups, ungrouped } = bootstrap.navigation;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={shellStyles.layout}>
|
<div data-testid="host-shell" style={shellStyles.layout}>
|
||||||
<header style={shellStyles.header}>
|
<header style={shellStyles.header}>
|
||||||
<h1 style={shellStyles.heading}>Ekklesia</h1>
|
<h1 style={shellStyles.heading}>Ekklesia</h1>
|
||||||
<Button data-testid="host-logout" onClick={() => void signOut()}>
|
<Button data-testid="host-logout" onClick={() => void signOut()}>
|
||||||
|
|||||||
@@ -116,7 +116,10 @@ export function ModuleUiFrame(props: ModuleUiFrameProps): ReactElement {
|
|||||||
}, [config, descriptor, importModule, onAuthorizationResponse, principal]);
|
}, [config, descriptor, importModule, onAuthorizationResponse, principal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section aria-live="polite" style={{ display: "grid", gap: "0.75rem" }}>
|
<section
|
||||||
|
aria-live="polite"
|
||||||
|
style={{ display: "grid", gap: "0.75rem", minWidth: 0, width: "100%" }}
|
||||||
|
>
|
||||||
{status === "loading" ? (
|
{status === "loading" ? (
|
||||||
<p data-testid="module-loading" style={{ color: "#57534e", margin: 0 }}>
|
<p data-testid="module-loading" style={{ color: "#57534e", margin: 0 }}>
|
||||||
Loading module…
|
Loading module…
|
||||||
@@ -128,7 +131,11 @@ export function ModuleUiFrame(props: ModuleUiFrameProps): ReactElement {
|
|||||||
to check the installation.
|
to check the installation.
|
||||||
</p>
|
</p>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
<div data-testid="module-container" ref={containerRef} />
|
<div
|
||||||
|
data-testid="module-container"
|
||||||
|
ref={containerRef}
|
||||||
|
style={{ minWidth: 0, width: "100%" }}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -971,6 +971,20 @@ describe("Ekklesia shell — Dashboard navigation", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Ekklesia shell — mount lifecycle", () => {
|
describe("Ekklesia shell — mount lifecycle", () => {
|
||||||
|
it("keeps fixed shell columns while module content changes width", async () => {
|
||||||
|
await render({ apiBaseUrl, fetch: platformFetch() }, async () => artifact("example", "0.1.0"));
|
||||||
|
|
||||||
|
const shell = query("host-shell") as HTMLElement;
|
||||||
|
const aside = shell.querySelector("aside") as HTMLElement;
|
||||||
|
const main = shell.querySelector("main") as HTMLElement;
|
||||||
|
const frame = query("module-container") as HTMLElement;
|
||||||
|
expect(shell.style.gridTemplateColumns).toBe("13rem minmax(0, 1fr)");
|
||||||
|
expect(aside.style.width).toBe("13rem");
|
||||||
|
expect(main.style.minWidth).toBe("0");
|
||||||
|
expect(main.style.overflowX).toBe("auto");
|
||||||
|
expect(frame.style.minWidth).toBe("0");
|
||||||
|
});
|
||||||
|
|
||||||
it("mounts the first module into the host's own container", async () => {
|
it("mounts the first module into the host's own container", async () => {
|
||||||
const mountedInto: Element[] = [];
|
const mountedInto: Element[] = [];
|
||||||
await render({ apiBaseUrl, fetch: platformFetch() }, async () =>
|
await render({ apiBaseUrl, fetch: platformFetch() }, async () =>
|
||||||
|
|||||||
Reference in New Issue
Block a user