Tour
이미 만들어진 페이지 위에서 단계별로 안내합니다. 처음 온 독자에게 한 번은 보여 줘야 하는 것들을, 실제로 있는 자리에서 가리킵니다.
import { Tour } from 'neba';
<Tour
open={open}
onOpenChange={setOpen}
steps={[
{ target: '#search', title: 'Find anything', content: 'Everything is behind this field.' },
{ target: '#deploy', title: 'Ship it', content: 'Builds the current branch.', side: 'left' }
]}
/>;Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| steps * | readonly TourStep[] | — | 순서대로 놓인 정거장들 |
| open | boolean | — | tour가 실행 중인지. onOpenChange와 함께 쓰면 controlled 컴포넌트가 됩니다 |
| defaultOpen | boolean | false | 실행 중인 채로 시작할지 (uncontrolled) |
| onOpenChange | (open: boolean) => void | — | 시작하고 끝날 때마다 호출됩니다 |
| step | number | — | 몇 번째 정거장인지 (0부터) |
| defaultStep | number | 0 | 처음 시작하는 정거장 (uncontrolled) |
| onStepChange | (step: number) => void | — | 단계가 바뀔 때마다 호출됩니다 |
| onFinish | () => void | — | 마지막 단계의 버튼을 눌렀을 때, tour가 닫히기 전에 호출됩니다 |
| mask | boolean | true | 페이지를 어둡게 하고 대상만 도려냅니다. 어두운 층은 포인터를 가로채지 않습니다 |
| skippable | boolean | true | 카운터 옆에 Skip 버튼을 그립니다 |
| dismissible | boolean | true | Escape로 tour를 끝낼 수 있는지 |
| scrollIntoView | boolean | true | tour가 각 단계에 닿을 때 그 대상을 화면 안으로 스크롤합니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 카드의 타입 스케일과 너비 상한 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 카드의 가장자리와 버튼의 색 역할 |
| density공통 | 'default' | 'compact' | 'default' | 카드 안쪽 여백만 바꿉니다 |
| locale | string | — | BCP 47 태그. 버튼과 카운터를 이 언어로 씁니다. 지원하지 않는 태그는 영어로 |
| previousLabel | ReactNode | — | Previous 버튼의 문구 |
| nextLabel | ReactNode | — | Next 버튼의 문구 |
| doneLabel | ReactNode | — | 마지막 단계에서 Next가 되는 문구 |
| skipLabel | ReactNode | — | Skip 버튼의 문구 |
| className | string | — | 카드에 붙는 class |
| classNames | NebaSlots<'mask' | 'title' | 'description' | 'close' | 'footer'> | — | 루트 뒤에 있는 각 파트의 class. 루트 자체는 className이 맡으므로 root 키는 없습니다 |
TourStep
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| target | string | — | 이 단계가 가리키는 것의 CSS selector. 없으면 아무것도 도려내지 않고 가운데에 놓입니다 |
| title | ReactNode | — | 단계의 제목 |
| content | ReactNode | — | 단계가 하는 말 |
| side공통 | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | 카드가 놓이는 대상의 모서리 |
| align공통 | 'start' | 'center' | 'end' | 'center' | 그 모서리 위에서의 위치 |
| padding | number | 6 | 도려낸 구멍이 대상보다 얼마나 넓어지는지 (px) |
HowToSteps를 뒤집은 것입니다. 그쪽은 설명을 페이지 안에 두고 독자가 따라가게 하고, 이쪽은 페이지를 그대로 둔 채 그 위에 섭니다. 그래서 단계는 selector로 지정합니다. tour가 말하는 대상은 이미 화면에 있고, 카드 안에서 한 번 더 설명하면 관리해야 할 사본이 둘이 됩니다.
예시
steps · target
각 단계는 CSS selector로 대상을 지목하며, 그 시점의 페이지에 대해 조회됩니다. target이 없는 단계는 아무것도 도려내지 않은 채 가운데에 놓입니다. 환영 단계와 마무리 단계가 그런 것입니다.
open · step
open은 tour를 실행하고 step은 몇 번째 지점인지입니다. 둘 다 uncontrolled 짝이 있고 둘 다 변화를 보고합니다. onFinish는 마지막 단계의 버튼을 눌렀을 때, tour가 닫히기 전에 호출됩니다.
mask
페이지를 어둡게 하고 대상만 그 어둠에서 도려냅니다. 어두운 층은 포인터를 가로채지 않으므로 가리키는 대상을 그대로 쓸 수 있습니다. 이것이 tour와 dialog의 연속을 가르는 차이입니다.
locale과 라벨
버튼과 카운터의 문구는 locale에서 옵니다. previousLabel, nextLabel, doneLabel, skipLabel로 각각을 직접 쓸 수 있습니다.
className · classNames
className은 카드(각 step이 쓰이는 popup)에 붙습니다. 그 뒤의 dimming은 popup의 자손이 아니라 형제라서 classNames.mask로만 닿습니다.
<Tour
steps={steps}
className="max-w-sm"
classNames={{ mask: 'bg-black/70', footer: 'justify-between' }}
/>slot은 mask, title, description, close, footer입니다. 넘긴 class가 컴포넌트 자신의 class와 어떻게 겨루는지는 prop 규약을 보세요.
접근성
- 카드는 제목이 이름이 되고 본문이 설명이 되는 dialog이며, 각 단계가 열릴 때 focus가 그 안으로 옮겨갑니다.
dismissible을 끄지 않는 한 Escape로 tour가 끝납니다. 바깥을 누르는 것으로는 끝나지 않습니다. 페이지를 쓰는 것이 tour의 목적이기 때문입니다.- tour가 어떤 것에 이르는 유일한 통로가 되어서는 안 됩니다. tour가 가리키는 것은 tour 없이도 찾을 수 있어야 합니다. 이미 닫아버린 독자나 애초에 보지 못한 독자에게 두 번째 기회는 없습니다.