Tinyrack

AppShell

데스크톱에서는 고정 사이드바를, 모바일에서는 모달 탐색을 제공하는 반응형 애플리케이션 프레임이에요.

핵심 속성

Main은 페이지의 단일 메인 랜드마크를 담당해요. 선택한 중단점 이상에서 Sidebar는 고정되고 독립적으로 스크롤돼요. sidebarMode로 확장 모드와 아이콘 레일을 전환하며, SidebarLabel은 두 상태 모두에서 탐색 이름을 접근 가능하게 유지해요. 중단점 아래에서는 mobileSidebar="drawer"가 기존 Base UI TRDrawer 동작을 보존하고, mobileSidebar="rail"이 인라인 고정 레일을 활성화해요.

설치

pnpm add @tinyrack/ui

긴 줄은 코드 영역 안에서 가로로 스크롤해 확인하세요.

스타일

@import '@tinyrack/ui/components/app-shell.css';

긴 줄은 코드 영역 안에서 가로로 스크롤해 확인하세요.

가져오기

import { TRAppShell } from '@tinyrack/ui/components/app-shell';

긴 줄은 코드 영역 안에서 가로로 스크롤해 확인하세요.

플레이그라운드

사용법

반응형 셸 고유 링크

레이아웃, 중단점, 드로어 상태, 사이드바 모드, 모바일 동작, 컨트롤 모양을 바꿀 수 있어요. 초기화하면 제어형 미리 보기 전체가 처음 상태로 돌아가요.

예시

반응형 탐색 패턴 고유 링크

48rem·64rem 드로어 중단점과 항상 보이는 모바일 레일을 실제 제품형 화면으로 비교해요. 페이지 너비를 바꾸면 각 셸이 그 자리에서 적응해요.

컨트롤 모양 고유 링크

레이아웃 결정과 섞지 않고 solid, outline, ghost 셸 컨트롤을 비교해요.

API

Rootbreakpoint(sm 또는 lg), layout(header-first 또는 sidebar-first), mobileSidebar(drawer 또는 rail)를 받아요. 드로어 상태는 open, defaultOpen, onOpenChange로, 사이드바 너비는 sidebarMode, defaultSidebarMode, onSidebarModeChange로 제어해요. 포함형 미리 보기나 임베드된 앱에서는 portalContainer를, 팝업 스타일에는 drawerPopupClassName을 사용해요.

Header는 헤더 랜드마크를 렌더링해요. Sidebar는 독립적으로 스크롤되는 aside이며 모바일 대화상자에도 접근 가능한 이름을 전달해요. 자기 인라인 크기를 스스로 가지므로 펼침, 접힘, 레일 전환 같은 너비 변화가 모두 애니메이션돼요. collapsed를 넘기면 사이드바가 접히면서 즉시 inert가 되고 전환이 끝나면 화면에서 사라져요. 고정 너비 상자로 감싸지 마세요. Trigger는 모바일 드로어를 열고 Close는 닫아요. SidebarToggle은 포커스를 유지하고 aria-expanded를 노출하면서 확장 모드와 레일 모드를 전환해요. SidebarLabel은 레일 모드에서도 제거되지 않고 시각적으로 숨겨져요. Main은 메인 랜드마크와 Base UI render 규약을 제공해요.

안정적인 스타일 규약은 --tr-app-shell-background, --tr-app-shell-sidebar-background, --tr-app-shell-sidebar-width, --tr-app-shell-sidebar-rail-width, --tr-app-shell-header-block-size, --tr-app-shell-drawer-block-size예요. Root 상태는 data-breakpoint, data-layout, data-mobile-sidebar, data-page-scroll, data-sidebar-mode에 반영되고, 사이드바에는 data-collapseddata-sidebar-mode가 붙어요. 모션은 prefers-reduced-motion을 따라요.

페이지 스크롤

Root는 페이지를 어떤 요소가 스크롤할지 고르는 pageScroll을 받아요.

기본값인 container는 셸을 뷰포트 높이 프레임 안에 두고, Main scroll이 페이지 스크롤 위치를 소유하는 스크롤 패널을 렌더링해요. 미리보기나 화면 목업처럼 셸을 고정 높이 상자 안에 넣을 때 쓰세요.

document는 스크롤을 문서 자체에 넘기고 사이드바와 아웃라인만 독립 스크롤 영역으로 남겨요. 전체 페이지 사이트에는 이쪽이 맞아요. 스스로 스크롤을 잘라내는 페이지는 휴대폰에서 확대한 뒤 화면을 움직일 수 없기 때문에, 확대를 쓸 수 있게 유지하는 쪽이 document예요.

<TRAppShell.Root chrome="docs" pageScroll="document">

미리 염두에 둘 점이 두 가지 있어요. pageScroll="document"에서는 페이지의 스타일시트가 html이나 bodyoverflow: hidden이나 height: 100%를 지정하면 안 돼요. 셸은 자기가 만들지 않은 잘림을 되돌릴 수 없고, --tr-app-shell-block-size도 더 이상 적용되지 않아요. 이 자세에서는 Main에 중첩 스크롤 영역이 없으므로 viewportLabel은 무시되고, contentClassName과 로딩 중 aria-busy 표시는 그대로 동작해요.

헤더가 기본 컨트롤 높이보다 크면 --tr-app-shell-header-block-size를 지정하세요. 헤더 행과 사이드바의 sticky 오프셋, 아웃라인의 sticky 오프셋이 모두 이 값 하나를 기준으로 삼아서 서로 어긋나지 않아요.

SSR에서는 모듈을 평가할 때 브라우저 전역 객체를 읽지 않고 데스크톱용 정적 구조를 렌더링한 뒤 하이드레이션해요. 이후 클라이언트가 선택한 미디어 쿼리를 적용하므로 애플리케이션을 렌더링할 때 window에 따라 자식 마크업을 바꾸지 말아요. pageScroll은 미디어 쿼리 결과가 아니라 정적 prop이라서, 스크롤 자세가 미리 렌더링된 HTML에서 이미 정확하고 하이드레이션 중에 바뀌지 않아요. 모달 드로어는 포커스를 가두고 Escape 키나 배경을 누르면 닫혀요. 닫힌 뒤에는 포커스를 Trigger로 복원하고, 열려 있는 동안 페이지 스크롤을 막아요.