diff --git a/.changeset/fuzzy-portals-rest.md b/.changeset/fuzzy-portals-rest.md new file mode 100644 index 0000000..9502a25 --- /dev/null +++ b/.changeset/fuzzy-portals-rest.md @@ -0,0 +1,5 @@ +--- +"openstory": patch +--- + +Keep centered stories stable when components render content through portals. diff --git a/packages/openstory/src/cli/build.ts b/packages/openstory/src/cli/build.ts index e3bf311..d2c1c51 100644 --- a/packages/openstory/src/cli/build.ts +++ b/packages/openstory/src/cli/build.ts @@ -13,6 +13,7 @@ import { DEFAULT_IGNORE_GLOBS, DEFAULT_STORY_GLOBS, OPENSTORY_DEFAULT_LAYOUT, + OPENSTORY_IFRAME_STYLES, OPENSTORY_LAYOUT_CLASSES, OPENSTORY_ROOT_ELEMENT_ID, VIRTUAL_STORY_ENTRY_ID, @@ -118,10 +119,7 @@ const renderStoryHtmlForBuild = ( ${escapedStoryId} · ${titleSuffix} ${cssLinks} diff --git a/packages/openstory/src/constants.ts b/packages/openstory/src/constants.ts index c60b618..7304068 100644 --- a/packages/openstory/src/constants.ts +++ b/packages/openstory/src/constants.ts @@ -50,6 +50,10 @@ export const OPENSTORY_LAYOUT_CLASSES: Record = { padded: "openstory-layout-padded", }; export const OPENSTORY_DEFAULT_LAYOUT = "padded"; +export const OPENSTORY_IFRAME_STYLES = ` html, body { margin: 0; padding: 0; min-height: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } + body.openstory-layout-centered #${OPENSTORY_ROOT_ELEMENT_ID} { display: grid; place-items: center; min-height: 100vh; padding: 16px; box-sizing: border-box; } + body.openstory-layout-fullscreen #${OPENSTORY_ROOT_ELEMENT_ID} { min-height: 100vh; } + body.openstory-layout-padded #${OPENSTORY_ROOT_ELEMENT_ID} { padding: 16px; }`; export const URL_KV_PAIR_SEPARATOR = ";"; export const URL_KV_KEY_VALUE_SEPARATOR = ":"; diff --git a/packages/openstory/src/plugin/virtual-modules.ts b/packages/openstory/src/plugin/virtual-modules.ts index 9634b5c..8de7d2f 100644 --- a/packages/openstory/src/plugin/virtual-modules.ts +++ b/packages/openstory/src/plugin/virtual-modules.ts @@ -1,6 +1,7 @@ import { OPENSTORY_DEFAULT_LAYOUT, OPENSTORY_FRAMEWORK_TO_ADAPTER, + OPENSTORY_IFRAME_STYLES, OPENSTORY_LAYOUT_CLASSES, OPENSTORY_ROOT_ELEMENT_ID, VIRTUAL_NULL_PREFIX, @@ -94,10 +95,7 @@ export const renderStoryIframeHtml = (options: RenderStoryIframeHtmlOptions): st ${escapedStoryId} · ${titleSuffix} diff --git a/packages/openstory/tests/plugin/virtual-modules.test.ts b/packages/openstory/tests/plugin/virtual-modules.test.ts index 3601ad4..361bb8e 100644 --- a/packages/openstory/tests/plugin/virtual-modules.test.ts +++ b/packages/openstory/tests/plugin/virtual-modules.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { synthesizeStoryEntry } from "../../src/plugin/virtual-modules.js"; +import { renderStoryIframeHtml, synthesizeStoryEntry } from "../../src/plugin/virtual-modules.js"; import type { Framework, ManifestStory } from "../../src/types.js"; const baseSynthesizedStory: ManifestStory = { @@ -38,6 +38,21 @@ const FRAMEWORK_RENDER_EXPECTATIONS: Record { + it("centers the story root without making body portals grid items", () => { + const html = renderStoryIframeHtml({ + story: { + ...baseSynthesizedStory, + parameters: { layout: "centered" }, + }, + }); + + expect(html).toContain('body class="openstory-layout-centered"'); + expect(html).toContain("body.openstory-layout-centered #openstory-root { display: grid;"); + expect(html).not.toMatch(/body\.openstory-layout-centered\s*\{\s*display:\s*grid/); + }); +}); + describe("synthesizeStoryEntry - synthesized component stories", () => { for (const framework of Object.keys(FRAMEWORK_RENDER_EXPECTATIONS) as Framework[]) { it(`emits a render function for ${framework}`, () => {