From e4d0b285242ae8c5e9f6f9191f39776b0943c132 Mon Sep 17 00:00:00 2001 From: ENvironmentSet Date: Tue, 6 Oct 2026 15:46:01 +0900 Subject: [PATCH 1/3] fix(plugin-basic-ui): respect horizontal safe area insets (FEP-2885) --- .changeset/tidy-safe-area-insets.md | 5 ++++ .../plugins/plugin-basic-ui.en.mdx | 19 +++++++++++++++ .../plugins/plugin-basic-ui.ko.mdx | 19 +++++++++++++++ .../src/components/AppBar.css.ts | 23 +++++++++++++++---- .../plugin-basic-ui/src/components/AppBar.tsx | 5 ++-- .../src/components/AppScreen.css.ts | 9 ++++++++ 6 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 .changeset/tidy-safe-area-insets.md diff --git a/.changeset/tidy-safe-area-insets.md b/.changeset/tidy-safe-area-insets.md new file mode 100644 index 000000000..bc1a1f641 --- /dev/null +++ b/.changeset/tidy-safe-area-insets.md @@ -0,0 +1,5 @@ +--- +"@stackflow/plugin-basic-ui": patch +--- + +Apply left and right safe area insets to AppScreen content and AppBar controls. Keep the Cupertino title centered within the safe area while backgrounds continue to fill the screen. diff --git a/docs/pages/api-references/plugins/plugin-basic-ui.en.mdx b/docs/pages/api-references/plugins/plugin-basic-ui.en.mdx index 33ef82985..1f8f4d19b 100644 --- a/docs/pages/api-references/plugins/plugin-basic-ui.en.mdx +++ b/docs/pages/api-references/plugins/plugin-basic-ui.en.mdx @@ -73,6 +73,25 @@ const Something = () => { }; ``` +### Safe Area + +`AppScreen` automatically applies the device's left and right safe area insets to its content and app bar in both themes. The Cupertino title is centered within the safe area. Backgrounds and navigation transitions still span the full screen. + +Include `viewport-fit=cover` in the viewport meta tag so the browser exposes the safe area insets. + +```html copy + +``` + +To extend media or other content to the screen edges, wrap it in a block element and cancel the insets with negative margins on the wrapper: + +```css copy +.full-bleed { + margin-left: calc(-1 * env(safe-area-inset-left, 0px)); + margin-right: calc(-1 * env(safe-area-inset-right, 0px)); +} +``` + ### `appBar` | | | | diff --git a/docs/pages/api-references/plugins/plugin-basic-ui.ko.mdx b/docs/pages/api-references/plugins/plugin-basic-ui.ko.mdx index f55834154..0692b9b2f 100644 --- a/docs/pages/api-references/plugins/plugin-basic-ui.ko.mdx +++ b/docs/pages/api-references/plugins/plugin-basic-ui.ko.mdx @@ -71,6 +71,25 @@ const Something = () => { ); }; ``` +### Safe Area + +`AppScreen`은 두 테마 모두에서 본문과 앱 바에 기기의 좌우 Safe Area Inset을 자동으로 반영해요. Cupertino 제목은 안전 영역 안에서 가운데에 배치돼요. 배경과 화면 전환은 화면 전체 너비를 유지해요. + +브라우저가 Safe Area Inset을 제공하도록 viewport 메타 태그에 `viewport-fit=cover`를 포함해 주세요. + +```html copy + +``` + +이미지 등 본문 요소를 화면 끝까지 넓히려면 블록 요소로 감싸고, 감싼 요소에 음수 마진을 지정해 Inset을 상쇄해 주세요. + +```css copy +.full-bleed { + margin-left: calc(-1 * env(safe-area-inset-left, 0px)); + margin-right: calc(-1 * env(safe-area-inset-right, 0px)); +} +``` + ### `appBar` | | | | diff --git a/extensions/plugin-basic-ui/src/components/AppBar.css.ts b/extensions/plugin-basic-ui/src/components/AppBar.css.ts index 3b5da2f51..142390231 100644 --- a/extensions/plugin-basic-ui/src/components/AppBar.css.ts +++ b/extensions/plugin-basic-ui/src/components/AppBar.css.ts @@ -192,9 +192,18 @@ export const safeArea = style({ export const container = style([ f.flexAlignEnd, + f.posRel, appBarOverflow, { height: globalVars.appBar.height, + marginLeft: [ + "constant(safe-area-inset-left)", + "env(safe-area-inset-left, 0px)", + ], + marginRight: [ + "constant(safe-area-inset-right)", + "env(safe-area-inset-right, 0px)", + ], transition: transitions({ height: globalVars.appBar.heightTransitionDuration, }), @@ -249,7 +258,14 @@ export const backButton = style([ export const closeButton = style([backButton]); -export const center = style([f.flexAlignCenter, f.flex1, appBarMinHeight]); +export const center = style([ + f.flexAlignCenter, + f.flex1, + appBarMinHeight, + { + minWidth: 0, + }, +]); export const centerMain = style({ display: "flex", @@ -277,10 +293,7 @@ export const centerMain = style({ left: "50%", transform: "translate(-50%)", height: globalVars.appBar.height, - top: [ - `max(${globalVars.appBar.minSafeAreaInsetTop}, constant(safe-area-inset-top))`, - `max(${globalVars.appBar.minSafeAreaInsetTop}, env(safe-area-inset-top))`, - ], + top: 0, }, }, }); diff --git a/extensions/plugin-basic-ui/src/components/AppBar.tsx b/extensions/plugin-basic-ui/src/components/AppBar.tsx index 9867878be..3e8cc3d3d 100644 --- a/extensions/plugin-basic-ui/src/components/AppBar.tsx +++ b/extensions/plugin-basic-ui/src/components/AppBar.tsx @@ -118,10 +118,11 @@ const AppBar = forwardRef( const globalCloseButton = globalOptions.appBar?.closeButton; const globalBackButton = globalOptions.appBar?.backButton; + const containerRef = useRef(null); const centerRef = useRef(null); const { maxWidth } = useAppBarTitleMaxWidth({ - outerRef: ref, + outerRef: containerRef, innerRef: centerRef, enable: globalOptions.theme === "cupertino", }); @@ -347,7 +348,7 @@ const AppBar = forwardRef( {...activityDataAttributes} >
-
+
{closeButtonLocation === "left" && renderCloseButton()} {renderBackButton()} diff --git a/extensions/plugin-basic-ui/src/components/AppScreen.css.ts b/extensions/plugin-basic-ui/src/components/AppScreen.css.ts index 293243c13..746729bcd 100644 --- a/extensions/plugin-basic-ui/src/components/AppScreen.css.ts +++ b/extensions/plugin-basic-ui/src/components/AppScreen.css.ts @@ -172,7 +172,16 @@ export const paper = recipe({ export const paperContent = recipe({ base: [ f.posAbsFull, + f.borderBox, { + paddingLeft: [ + "constant(safe-area-inset-left)", + "env(safe-area-inset-left, 0px)", + ], + paddingRight: [ + "constant(safe-area-inset-right)", + "env(safe-area-inset-right, 0px)", + ], overflowY: "scroll", WebkitOverflowScrolling: "touch", "::-webkit-scrollbar": { From 71684bd0de9cbcfc4e750705ed448629c0ffc656 Mon Sep 17 00:00:00 2001 From: ENvironmentSet Date: Tue, 6 Oct 2026 16:35:25 +0900 Subject: [PATCH 2/3] chore(plugin-basic-ui): classify horizontal safe area support as minor --- .changeset/tidy-safe-area-insets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tidy-safe-area-insets.md b/.changeset/tidy-safe-area-insets.md index bc1a1f641..fcdeb1e55 100644 --- a/.changeset/tidy-safe-area-insets.md +++ b/.changeset/tidy-safe-area-insets.md @@ -1,5 +1,5 @@ --- -"@stackflow/plugin-basic-ui": patch +"@stackflow/plugin-basic-ui": minor --- Apply left and right safe area insets to AppScreen content and AppBar controls. Keep the Cupertino title centered within the safe area while backgrounds continue to fill the screen. From 506b33b48895c07bbe96a654045f6f0ac1aa2600 Mon Sep 17 00:00:00 2001 From: ENvironmentSet Date: Tue, 6 Oct 2026 17:35:54 +0900 Subject: [PATCH 3/3] fix(demo): respect AppScreen padding in activity layouts --- demo/src/activities/Article/Article.content.css.ts | 2 +- demo/src/activities/Main/Main.css.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/src/activities/Article/Article.content.css.ts b/demo/src/activities/Article/Article.content.css.ts index f225e6057..b0f536021 100644 --- a/demo/src/activities/Article/Article.content.css.ts +++ b/demo/src/activities/Article/Article.content.css.ts @@ -4,7 +4,7 @@ import { style } from "@vanilla-extract/css"; import { f } from "../../styles"; export const container = style([ - f.posAbsFull, + f.fullHeight, f.overflowScroll, f.rootLineHeight, ]); diff --git a/demo/src/activities/Main/Main.css.ts b/demo/src/activities/Main/Main.css.ts index 01afe22cf..391fdaa3f 100644 --- a/demo/src/activities/Main/Main.css.ts +++ b/demo/src/activities/Main/Main.css.ts @@ -2,7 +2,7 @@ import { style } from "@vanilla-extract/css"; import { f } from "../../styles"; -export const wrapper = style([f.posAbsFull, f.flexColumn, f.rootLineHeight]); +export const wrapper = style([f.fullHeight, f.flexColumn, f.rootLineHeight]); export const appBarLeft = style([ f.flex,