flemov1.5.7

Screen

화면 한 장의 구조 — 앱 바, 네비게이션 바, 안전 영역

<Screen>은 라우트 element를 감싸는 컨테이너예요. fixed 레이아웃에 앱 바·네비게이션 바·상태 바 같은 모바일 화면 영역의 슬롯을 함께 가지고 있어요.

구조

Status Bar

App Bar

Children스크롤 영역

Navigation Bar

System Navigation Bar

Status Bar

상단 안전 영역. 노치·다이내믹 아일랜드를 차지해요.

statusBarHeightstatusBarColorhideStatusBar
App Bar

화면 상단의 헤더 영역. 화면별로 두거나 화면 사이에서 그대로 유지되는 공유로 둘 수 있어요.

appBarsharedAppBar
Children

화면의 본문 영역. 기본으로 스크롤되며, 자체 스크롤 컨테이너를 쓸 때만 꺼요.

contentScrollable
Navigation Bar

화면 하단의 바. App Bar와 마찬가지로 화면별·공유 둘 다 가능해요.

navigationBarsharedNavigationBar
System Navigation Bar

하단 안전 영역. 안드로이드 시스템 네비게이션 자리를 차지해요.

systemNavigationBarHeightsystemNavigationBarColorhideSystemNavigationBar

화면별 vs 공유 (shared)

App bar와 navigation bar는 두 가지 방식으로 둘 수 있어요. 각각 다른 시점에 마운트되고, 전환할 때 다르게 보여요.

  • appBar / navigationBar — 그 화면에만 속한 바예요. 화면이 바뀌면 함께 마운트됐다가 같이 사라져요. 헤더 타이틀이나 상세 화면의 액션 버튼처럼 화면마다 다른 UI에 어울려요.
  • sharedAppBar / sharedNavigationBar — 화면이 바뀌어도 같은 자리에 그대로 머물러요. push/pop 전환 동안 깜빡이지 않고, 모든 화면에 같이 보여야 하는 하단 탭 바·고정 헤더 같은 UI에 어울려요. 같은 노드를 다음 화면에서도 같은 prop으로 넘기면 flemo가 두 화면 사이를 이어 그려요.

가장 작은 사용

<Screen>
  <h1>Home</h1>
</Screen>

기본적으로 컨텐츠 영역은 스크롤돼요. 배경색은 따로 지정하지 않으면 흰색이에요.

앱 바와 네비게이션 바

각 슬롯은 두 가지가 있어요. 화면별(appBar, navigationBar)과 공유(sharedAppBar, sharedNavigationBar).

공유 버전은 화면이 바뀌어도 그대로 유지되기 때문에, 모든 화면에서 보여야 하는 하단 탭 바 같은 UI에 어울려요.

<Screen
  appBar={<TopBar title="받은편지함" />}
  navigationBar={<BottomActions />}
  sharedNavigationBar={<TabBar />}
>
  <MailList />
</Screen>

안전 영역

<Screen
  statusBarHeight="env(safe-area-inset-top)"
  statusBarColor="#000"
  systemNavigationBarHeight="env(safe-area-inset-bottom)"
  systemNavigationBarColor="#000"
>

</Screen>

해당 영역을 차지하지 않게 하려면 hideStatusBar 또는 hideSystemNavigationBar를 켜요.

배경색

<Screen backgroundColor="#0b0b0c">…</Screen>

기본값은 "white"예요. CSS 색이면 무엇이든 사용할 수 있어요.

컨텐츠 스크롤 끄기

자체 스크롤 컨테이너를 쓴다면 내장 스크롤을 꺼요.

<Screen contentScrollable={false}>
  <CustomScrollArea />
</Screen>

Screen 전체 prop

Prop타입기본값설명
appBarReactNode화면별 상단 바
navigationBarReactNode화면별 하단 바
sharedAppBarReactNode전환에서도 유지되는 상단 바
sharedNavigationBarReactNode전환에서도 유지되는 하단 바
backgroundColorstring"white"CSS 색
statusBarHeightstringCSS 길이, 보통 env(safe-area-inset-top)
statusBarColorstringCSS 색
hideStatusBarbooleanfalse상단 안전 영역을 차지하지 않음
systemNavigationBarHeightstringCSS 길이, 보통 env(safe-area-inset-bottom)
systemNavigationBarColorstringCSS 색
hideSystemNavigationBarbooleanfalse하단 안전 영역을 차지하지 않음
contentScrollablebooleantrue컨텐츠 영역의 스크롤 여부

LayoutScreen — 화면 사이를 잇는 모핑

목록의 작은 썸네일이 상세 화면의 큰 이미지로 자연스럽게 펼쳐지듯 이어지는 패턴 — flemo에서는 네 가지가 한 세트로 맞물려 동작해요.

역할어디에
<LayoutScreen>모프 후 도착하는 화면(상세)
<LayoutConfig>모프되는 컨테이너의 가장 바깥
transitionName: "layout"navigate.push 옵션
layoutIdnavigate 옵션 + 두 화면의 motion 요소 prop

각 조각이 하는 일

  • <LayoutScreen> — 도착 화면에서 <Screen> 대신 사용해요. 화면이 unmount될 때도 layoutId 짝짓기가 깨지지 않도록 해줍니다. 출발 화면(목록)은 평소대로 <Screen> 그대로 둬도 동작합니다.

  • <LayoutConfig> — 안쪽 motion 자식들의 모프 타이밍(duration·ease)을 현재 트랜지션 프리셋과 맞춰 줘요. 빠지면 모프가 화면 전환과 어긋나는 기본 spring으로 따로 움직입니다. 모프되는 컨테이너의 가장 바깥에 둡니다.

  • transitionName: "layout" — push 시점에 어떤 프리셋으로 화면 자체를 전환할지 정해요. layout 프리셋은 모프가 잘 보이도록 화면을 살짝만 페이드해 줍니다 (cupertino를 쓰면 슬라이드가 모프를 가려요).

  • layoutId — 두 화면의 같은 요소를 짝짓는 키. push 옵션의 layoutId는 도착 화면에서 useScreen()layoutId로 받을 수 있어요. 컨테이너·이미지·제목·가격까지 같은 체계로 묶으면 한 덩어리처럼 함께 모프합니다.

예시 — 갤러리 → 상세

Gallery.tsx
import { Screen, useNavigate } from "flemo";
import { motion } from "motion/react";

function Gallery() {
  const navigate = useNavigate();
  return (
    <Screen>
      {photos.map((p) => (
        <motion.div
          key={p.id}
          layoutId={`photo-card-${p.id}`}
          onClick={() =>
            navigate.push("/photos/:id", { id: p.id }, { transitionName: "layout", layoutId: p.id })
          }
        >
          <motion.img layoutId={`photo-image-${p.id}`} src={p.thumb} />
          <motion.span layoutId={`photo-title-${p.id}`}>{p.title}</motion.span>
        </motion.div>
      ))}
    </Screen>
  );
}
Photo.tsx
import { LayoutConfig, LayoutScreen, useScreen } from "flemo";
import { motion } from "motion/react";

function Photo() {
  const { layoutId } = useScreen();
  return (
    <LayoutScreen>
      <LayoutConfig>
        <motion.div layoutId={`photo-card-${layoutId}`} className="fixed inset-0">
          <motion.img layoutId={`photo-image-${layoutId}`} src={photo.full} />
          <motion.h1 layoutId={`photo-title-${layoutId}`}>{photo.title}</motion.h1>
        </motion.div>
      </LayoutConfig>
    </LayoutScreen>
  );
}

자주 만나는 함정

  • <LayoutConfig>를 빼먹음 → 모프가 화면 전환과 어긋난 spring으로 따로 움직여요.
  • transitionName: "layout" 누락 → 기본 cupertino 슬라이드가 모프를 가려서 그냥 새 화면이 들어오는 것처럼 보입니다.
  • 두 화면의 layoutId가 다름 → 짝짓기에 실패해 모프가 일어나지 않아요. 출발 화면이 찍어 두는 키와 도착 화면이 만들어 내는 키가 완전히 같은 문자열이 되도록 맞춰 주세요.
  • 부모 컨테이너만 묶음 → 안쪽 이미지·텍스트가 따로 페이드인되며 어색해요. 함께 움직여야 하는 요소는 모두 같은 prefix로 layoutId를 붙입니다.

이 페이지에서