본문으로 건너뛰기

ScrollZone

무엇이든 한 방향으로 늘어놓고 그 방향으로 스크롤하는 스트립입니다. 카드나 chip, 아바타, 썸네일이 상자를 가로지르거나 아래로 흐르며, 원하는 만큼의 줄로 놓이고, 휠도 손가락도 없는 포인터를 위한 버튼 한 쌍이 붙습니다.

tsx
import { ScrollZone } from 'neba';

<ScrollZone label="Continue watching" spacing={3}>
  {shows.map((show) => (
    <Card key={show.name} className="w-40" title={show.name} />
  ))}
</ScrollZone>;

Props

Prop타입기본값설명
orientation공통'horizontal' | 'vertical''horizontal'자식들이 놓이는 방향이자 스크롤되는 방향
linesnumber1가로 zone이 새 열로 넘어가기 전까지 쓰는 줄 수(세로 zone에서는 열 수). 2는 같은 너비에 두 배를 담는 선반입니다
spacingnumber2자식 사이의 간격. Tailwind 스페이싱 스케일이며 2는 0.5rem으로, GridContainer의 spacing과 같은 사다리입니다
buttons'auto' | 'always' | 'none''auto'스크롤 버튼을 언제 그릴지. auto는 전부 들어맞으면 둘 다 그리지 않고, 끝에서는 overlay 버튼을 지우되 자리를 지키는 inline 버튼은 disabled로 남깁니다. always는 늘 둘 다, none은 아예 그리지 않습니다
buttonPlacement'inline' | 'overlay''inline'버튼이 스트립 옆에 설지, 위에 겹칠지. inline이면 스크롤 영역이 버튼 앞에서 끝나므로 항목이 버튼에 가리지 않고, 더 스크롤할 곳이 없을 때 버튼은 사라지지 않고 disabled가 됩니다
mode'item' | 'page' | 'hold''item'버튼을 눌렀을 때 하는 일. item은 다음 자식으로, page는 지금 보이는 만큼, hold는 누르고 있는 동안 계속, hold에서 짧게 누르면 item 한 칸입니다
stepnumber1item 모드에서 한 번에 지나갈 자식 수
speednumber900hold 모드에서 초당 스크롤되는 픽셀
snapbooleanfalse멈출 때 가장 가까운 자식을 시작 가장자리에 맞춥니다. 버튼뿐 아니라 드래그와 휠에도 적용됩니다
dragbooleantrue마우스나 펜으로도 끌어서 스크롤합니다. 손가락은 브라우저 자신의 스크롤에 맡깁니다. 관성과 스크롤바가 딸려 오기 때문입니다
wheelbooleanfalse스트립 위에서 굴린 휠을 스트립을 따라가는 이동으로 바꾸고, 양 끝에서도 놓지 않습니다. 포인터가 스트립 밖으로 나가야 페이지가 휠을 되찾습니다. 옆으로 미는 트랙패드와 세로 zone은 건드리지 않습니다
scrollbarbooleanfalse기본 스크롤바를 보입니다
variant공통'solid' | 'outline' | 'text''solid'스크롤 버튼의 무게. zone 자체는 아무것도 그리지 않습니다
size공통'xs' | 'sm' | 'md' | 'lg' | 'xl''md'스크롤 버튼의 크기와 가장자리에서 들어온 거리
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'버튼과 포커스 링의 의미론적 색 역할
density공통'default' | 'compact''default'버튼의 여백만 바꿉니다
localestring버튼이 자기 이름을 말하는 언어(BCP 47). 지원하지 않는 태그는 영어로 돌아갑니다
labelstringlocale's word스크롤 영역의 이름, "Categories", "Recent files". 띠는 키보드로 움직일 수 있어야 하므로 언제나 tab 정거장이며, 이름을 주지 않으면 locale의 일반 명사가 대신 쓰입니다
previousLabelstring뒤로 가는 버튼의 이름
nextLabelstring앞으로 가는 버튼의 이름
childrenReactNode배치할 것들. 최상위 자식 하나가 스트립의 항목 하나입니다

나머지 <div> 속성은 그대로 루트에 전달됩니다. 공용 축(variant size color density orientation)은 prop 규약에 있습니다.

예시

orientation과 lines

orientation은 스트립이 흐르는 방향, 곧 스크롤되는 방향을 정합니다. lines는 가로 zone이 새 열로 넘어가기 전까지 채우는 줄 수입니다. 두 줄이면 같은 너비에 두 배가 들어가고, 스크롤은 여전히 하나입니다.

spacing은 자식 사이의 간격이며, GridContainer와 같은 스케일입니다. 20.5rem입니다.

mode

버튼을 눌렀을 때 하는 일입니다. item은 다음 자식으로 이동하며 step이 한 번에 몇 개인지를 정하고, page는 지금 화면에 보이는 만큼 움직이며, hold는 누르고 있는 동안 초당 speed 픽셀로 계속 스크롤합니다. hold라 하기엔 너무 짧은 누름은 item 한 칸으로 처리되므로, 짧게 눌러도 아무 일이 없는 버튼은 없습니다.

buttons

기본값 auto는 전부 들어맞을 때에는 둘 다 그리지 않습니다. 끝에 닿았을 때에는 더 싸게 먹히는 쪽을 택합니다. overlay 버튼은 갈 곳이 없으면 아예 지우고, inline 버튼은 어차피 자기 자리를 지키고 있으므로 그 자리에 disabled로 남습니다. always는 전부 들어맞는 동안에도 첫 페인트부터 둘 다 그립니다. 내용이 나중에 도착하는 스트립이 원하는 쪽입니다. none은 아무것도 그리지 않고 드래그와 방향키, 포인터가 이미 밀 수 있는 것에 맡깁니다.

snap은 어떻게 스크롤했든 멈추는 순간 가장 가까운 자식을 시작 가장자리에 맞춥니다.

buttonPlacement

기본값 inline은 버튼을 스트립 옆에 세웁니다. 스크롤 영역이 버튼 앞에서 끝나므로 항목은 버튼 밑으로 숨는 대신 그 가장자리에서 잘리고, 버튼은 무엇 위에 얹혔든 상관없이 페이지 위에서 또렷하게 읽힙니다. overlay는 버튼을 스트립의 양 끝 위에 겹칩니다. 상자의 모든 픽셀이 내용의 것이 되고, 항목은 버튼 밑을 지나갑니다.

inline 버튼은 갈 곳이 없을 때에도 자기 자리를 지킵니다. 그러지 않으면 끝에 막 닿은 포인터 아래에서 스트립이 크기를 바꾸게 됩니다. buttons="auto"가 끝에서 하는 일도 여기서 갈립니다. 자리 값은 어차피 치르고 있으니 inline 버튼은 그 자리에 남아 disabled가 되고, 자리를 차지하지 않는 overlay 버튼은 지워집니다.

아래로 흐르게 하기

세로 zone은 스크롤할 높이가 있어야 하고, 그 높이를 컴포넌트에서 받습니다. 루트가 flex 컬럼이고 스크롤 박스가 그것을 채우므로, 높이가 있는 상자 안에서 className="h-full" 하나면 충분합니다.

drag

손가락은 이미 스트립을 스크롤합니다. 밑에 있는 것이 평범한 스크롤 컨테이너이고, 터치 스크롤은 브라우저 자신의 것이기 때문입니다. 관성과 러버밴딩, 그리고 어떤 핸들러도 재현하지 못하는 스크롤바가 함께 옵니다. drag는 같은 제스처를 마우스와 펜에도 붙이며, 진짜로 끌었을 때 뒤따르는 click은 삼켜집니다. 카드 위를 지나 스트립을 당겨도 그 카드가 열리지 않습니다.

tsx
<ScrollZone drag={false} scrollbar>
  {items}
</ScrollZone>

wheel

마우스에는 휠이 하나뿐이고 그 휠은 페이지 아래쪽을 가리킵니다. 가로 스트립이 흐르지 않는 바로 그 축입니다. wheel은 스트립 위에서 굴린 휠을 스트립을 따라가는 이동으로 바꿉니다.

기본값은 꺼짐입니다. 페이지에서 가져온 휠은 페이지의 것이기 때문입니다. 선반을 지나쳐 내려가려던 독자가 거기에 붙잡힙니다. 게다가 이 스트립에는 다른 길이 없는 포인터를 위한 버튼이 이미 달려 있습니다.

가져간 휠은 양 끝에서도 놓지 않습니다. 스트립이 다 떨어졌다고 해서 그 손짓이 글 아래로 뛰어내리는 동작이 되면 안 되기 때문입니다. 페이지가 휠을 되찾는 것은 포인터가 스트립 밖으로 나갈 때입니다. 옆으로 미는 트랙패드는 그대로 둡니다. 그쪽은 이미 스트립을 스크롤하기 때문입니다. 세로 zone은 이 prop을 무시합니다.

접근성

  • 스트립은 포커스를 받고 방향키로 스크롤됩니다. 브라우저 자신의 키 처리이므로 RTL에서도 이미 올바릅니다.
  • label이 영역의 이름이며 스크린 리더가 내용보다 먼저 읽습니다. 없으면 포커스는 받되 이름이 없는 상태가 됩니다.
  • 스크롤 버튼은 이름을 가진 진짜 버튼입니다. previousLabel / nextLabel, 또는 locale이 그 이름을 정합니다.
  • hold 모드에서 버튼은 Enter와 Space에도 같은 방식으로 답하며, 키를 누르고 있는 동안 스크롤합니다.
  • 화면 밖으로 나간 것에는 aria-hidden을 붙이지 않습니다. 스크롤하면 실제로 닿을 수 있는 것이고, 키보드 사용자가 그 안으로 들어갈 수 있기 때문입니다.

Released under the MIT License