본문으로 건너뛰기

FloatingBottomNavigation

앱의 주요 목적지들을 담은 바입니다. 아래 가장자리에 붙는 대신 그 위로 떠 있으며, 담긴 목적지만큼만 넓고, 스타디움 모양으로 잘려 있고, 그 아래로 페이지가 계속 이어집니다.

tsx
import { BottomNavigationItem, FloatingBottomNavigation } from 'neba';

<FloatingBottomNavigation label="Main" value={section} onValueChange={setSection}>
  <BottomNavigationItem value="home" icon={<HomeIcon />}>
    Home
  </BottomNavigationItem>
  <BottomNavigationItem value="search" icon={<SearchIcon />}>
    Search
  </BottomNavigationItem>
</FloatingBottomNavigation>;

목적지는 BottomNavigationItem으로, BottomNavigation이 받는 것과 같은 항목입니다. value, icon, href, disabled 등 항목에 관한 것은 모두 그 페이지에 있습니다.

Props

Prop타입기본값설명
variant공통'solid' | 'outline' | 'text''outline'시트의 무게. 컨테이너의 방식대로 색을 들이지 않으며, 페이지 위에 떠 있는 만큼 헤어라인이 있는 outline이 기본입니다
size공통'xs' | 'sm' | 'md' | 'lg' | 'xl''md'행의 최소 높이와 글리프, 이름의 스케일. BottomNavigation과 같은 사다리로, md는 56px입니다
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'의미론적 색 역할. 임의 색상값은 받지 않습니다
density공통'default' | 'compact''default'여백만 바꿉니다. 높이와 글자 크기는 그대로
elevation공통0 | 1 | 2 | 32그림자 깊이. Pill과 같은 이유로 기본이 2입니다. 이 바는 페이지의 일부가 아니라 그 위에 떠 있습니다
valuestring | number | null지금 있는 목적지. onValueChange와 함께 제어할 때 씁니다
defaultValuestring | number | null제어하지 않을 때 처음 선택되는 목적지
onValueChange(value: string | number) => void눌린 목적지와 함께 호출됩니다
position공통'static' | 'sticky' | 'fixed' | 'absolute''fixed'페이지 스크롤 안에서 어떻게 앉는지. absolute는 창이 아니라 가장 가까운 positioned 조상에 붙입니다. FloatingActionButton과 같은 확장입니다
offsetnumber | string16아래 가장자리에서 얼마나 떠 있는지. BottomNavigation과의 차이 전부가 여기서 나옵니다. 그 아래로 페이지가 계속 이어집니다
labels'all' | 'selected' | 'none''selected'어떤 이름을 그릴지. 전체 너비 바의 all과 달리 기본이 selected입니다. 담긴 것만큼만 넓은 바에서 이름 다섯 개는 화면을 가로지릅니다. 그리지 않은 이름도 문서에는 남습니다
safeAreabooleantrueoffset에 env(safe-area-inset-bottom)을 더합니다. 시트 전체가 올라갑니다. 아래에 덮어야 할 것이 없기 때문입니다
disabledbooleanfalse모든 목적지가 응답을 멈춥니다
labelstring바가 읽히는 이름, "Main", "Sections"
renderuseRender.RenderPropnav 대신 다른 요소로 렌더링합니다. 여기서는 거의 필요하지 않습니다
childrenReactNodeBottomNavigationItem들, 두 바가 같은 항목을 씁니다

onChange를 빼면 나머지 <nav> 속성은 그대로 루트에 전달됩니다. 들을 만한 변화는 onValueChange입니다. 공용 축(variant size color density elevation position)은 prop 규약에 있습니다.

예시

offset

바가 아래 가장자리에서 얼마나 떠 있는지를 픽셀 수나 CSS 길이로 정합니다. BottomNavigation과의 차이는 전부 여기서 나옵니다. 아래로 페이지가 계속 이어지기 때문에 시트는 모서리 두 개짜리 바가 아니라 스타디움이고, 그림자를 지며, 담긴 것만큼만 넓습니다.

safeArea는 그 간격에 env(safe-area-inset-bottom)을 더해 폰의 홈 인디케이터를 피합니다. 전체 너비 바와 달리 시트 전체가 올라가는데, 아래에 덮고 있어야 할 것이 없기 때문입니다.

tsx
<FloatingBottomNavigation offset={24} safeArea={false} />

position

기본값 fixed는 창 아래에 바를 붙입니다. absolute는 가장 가까운 positioned 조상의 아래에 붙이며, 자기만의 화면 안에 놓이는 바가 원하는 값이자 위 미리보기가 쓰는 값입니다. sticky는 스크롤되는 것의 아래에 붙이고, static은 흐름 안에 가운데 정렬로 되돌립니다.

labels

여기서의 기본값 selected는 읽는 사람이 지금 있는 목적지의 이름만 그립니다. 떠 있는 바는 담긴 것만큼만 넓으므로, 이름 다섯 개를 그리면 화면을 가로지르게 되고 더 이상 로젠지가 아니게 됩니다.

all은 모든 이름을, none은 아무 이름도 그리지 않습니다. 그리지 않은 이름도 문서에는 남아, 옆의 글리프에게 접근성 이름을 주는 것이 바로 그 이름입니다.

하이라이트

하이라이트는 지금 선택된 목적지가 아니라 바의 것입니다. 그래서 움직일 수 있습니다. aria-current를 단 항목을 재어 left, top, width, height를 다음 항목까지 애니메이션합니다. transform은 쓰지 않으므로 그 위에 올라탄 이름이 다시 샘플링되는 일은 없습니다.

labels가 그리지 않는 이름은 잘리는 대신 접힙니다. 그 이름이 든 상자가 0과 글자 너비 사이를 오가므로, 목적지를 누르면 바가 새 배치로 튀는 대신 그 목적지를 중심으로 모양을 다시 잡습니다. 이름이 자라고, 옆의 것들이 비켜서고, 하이라이트가 그 아래로 미끄러져 들어오는 일이 같은 시계 위에서 일어납니다. 움직임을 줄여 달라고 한 사용자에게는 여정 없이 결과만 주어집니다.

variant · color · size

variant는 다른 모든 컨테이너에서와 같은 말을 합니다. 시트에는 색을 들이지 않으며, 색 계열을 입는 것은 지금 있는 목적지 하나뿐입니다. 가장자리가 없는 시트 대신 outline이 기본인데, 아래로 지나가는 것과 떠 있는 로젠지를 갈라 주는 것이 그 헤어라인이기 때문입니다.

직접 제어하기

value를 넘기면 바는 자기 상태를 갖지 않습니다. 라우터가 이미 읽는 사람의 위치를 알고 있을 때 쓰는 모양입니다.

tsx
<FloatingBottomNavigation value={pathname} onValueChange={navigate}>
  <BottomNavigationItem value="/home" icon={<HomeIcon />}>
    Home
  </BottomNavigationItem>
</FloatingBottomNavigation>

접근성

  • 루트는 <nav>이고 label이 그 이름입니다. role="tablist"가 아닙니다. tab list는 전체에 tab stop 하나와 그 안의 방향키 이동을 약속하지만, 하단 내비게이션은 패널이 아니라 페이지를 바꿉니다.
  • 지금 있는 목적지는 aria-current="page"를 답니다.
  • 각 목적지는 진짜 <button>이며, href를 주면 진짜 <a>입니다.
  • labels가 그리지 않은 이름도 문서에는 남아 목적지의 접근성 이름이 됩니다. 글리프뿐인 항목에게는 그것이 이름의 전부입니다.
  • position="fixed"일 때는 페이지 아래쪽에 바의 높이와 offset만큼 여백을 두세요. 그렇지 않으면 마지막 줄이 가려집니다.

Released under the MIT License