칸반
파인 홈(열)에 작은 카드(지붕 10)가 쌓인 판입니다. 열마다 따로 쌓이고, 열 머리는 상태 글리프 · 이름 · 개수 · 접기를 담습니다. uyoopack 작업 보드에서 끌어올렸습니다.
- import
- import { Board, BoardColumn, BoardGroup, BoardCard, BoardLine } from "@uyoolabs/ui/board";
- 쓰는 경우
- 단계(열)를 따라 흘러가는 작업 · 요청 · 채용 후보처럼 '지금 어디에 있나'가 중요한 것. 열은 7개 이하입니다. 하루 수십 건 쌓이는 열(완료)은 줄 목록(variant="line")과 'n개 더 보기'로 둡니다. 옮길 수 있게 하려면 onMove 를 넘깁니다 — 그때 이동 메뉴가 켜지고, ./board/dnd 를 함께 주면 끌기도 켜집니다.
- 쓰지 않는 경우
- 끌기만 있고 이동 메뉴가 없는 보드를 만들지 않습니다(키보드 · 스크린리더 사용자가 옮기지 못합니다). 사람이 아니라 시스템이 열을 정하는 데이터에는 onMove 를 넘기지 않습니다(uyoopack CON-28). 열 색으로 상태를 말하지 않습니다 — 머리의 상태 글리프와 이름이 말합니다. 카드 안에 버튼을 여러 개 두지 않습니다(동작은 ⋯ 메뉴 하나). 들린 카드를 기울이거나 반투명하게 하지 않습니다. 열이 많거나 정렬 · 비교가 중요하면 표를 씁니다.
- 끌기
- import { boardDnd } from "@uyoolabs/ui/board/dnd"; // 끌기를 켤 때만
읽기 전용 — onMove 없음
손잡이 · 이동 버튼 · 이동 메뉴 · 라이브 영역이 DOM 에 없고 카드 전체가 링크 하나입니다. 할 일은 묶음(details, 서버가 그리고 펴고 접는 데 자바스크립트가 들지 않습니다), 진행 중은 한곳, 완료는 줄 목록입니다.
할 일
3개2.1 결제 웹훅 2개
- PAY-142다음
2.3 영수증 메일 1개
진행 중
2개- PAY-138
- PAY-140
완료
5개그 전 완료 32건은 보관 목록에 있습니다.
옮길 수 있는 보드 — onMove + ./board/dnd
카드를 끌어 열 사이 · 열 안 순서를 바꿉니다. 키보드는 카드의 ⋯ 또는 카드 링크에서 Shift+F10 으로 이동 메뉴를 엽니다. 결과는 라이브 영역이 한 · 영 · 일로 읽습니다. 진행 중 열은 WIP 상한 3 입니다.
할 일
3개- PAY-142
- PAY-147
- PAY-151
진행 중
2개 · 상한 3개- PAY-138
- PAY-140
검토
0개검토를 기다리는 작업이 없습니다. 작업을 마치면 여기로 옮깁니다.
동작
- 열마다 쌓기
- 열이 각자 쌓입니다. 묶음 가로줄마다 열을 두는 격자가 아닙니다(한 묶음은 보통 한 열에만 카드가 있어 칸 대부분이 빕니다).
- 좁은 화면
- 보드 폭 768 미만에서는 고른 열 하나만 섭니다. 세그먼트(radiogroup)로 바꾸고, 처음 열은 primary 열에 카드가 있으면 그 열, 없으면 첫 열입니다. 서버가 그린 열은 컨테이너 쿼리가 먼저 숨깁니다. 접기는 쓰지 않습니다.
- 붙는 머리
- 열 머리는 stickyTop 아래에 붙습니다. 블러 · 반투명 없이 열과 같은 불투명 면이고, 붙으면 아래 헤어라인이 생깁니다.
- 이동
- Board 가 먼저 옮겨 그리고(낙관적) onMove 의 Promise 를 기다립니다. 기다리는 동안 카드는 aria-busy, 거절되면 원래 자리로 돌리고 moveFailed 를 읽습니다. 카드 순서의 정본은 앱 데이터입니다.
- 끌기
- pragmatic-drag-and-drop core 와 auto-scroll 만 씁니다. 터치 · 좁은 화면에서는 같은 열 안 순서만 바꾸고, 열 사이 이동은 메뉴가 맡습니다. 끄는 동안의 위치는 읽지 않고 결과 · 취소 · 실패만 읽습니다. 포인터로 놓은 뒤에는 포커스를 옮기지 않습니다.
- WIP 상한
- limit 을 주면 머리 개수가 3/5 로 보이고, 넘으면 주의색 글자와 세모 아이콘이 붙습니다. 막지는 않습니다.
키보드
| 키 | 동작 |
|---|---|
| TabShift+Tab | 카드 제목 링크 → ⋯ 이동 버튼 → 다음 카드. 열 머리의 접기 버튼 |
| Enter | 카드를 엽니다(링크) |
| EnterSpace↓ | ⋯ 에서 이동 메뉴를 엽니다 |
| Shift+F10메뉴 키 | 카드 링크에서 이동 메뉴를 엽니다(onMove 가 있을 때) |
| ↑↓EnterEsc | 메뉴 안에서 고르기 · 실행 · 닫기. 닫으면 포커스가 카드 링크로, 옮겼으면 새 자리의 카드 링크로 갑니다 |
| ←→ | 좁은 화면의 열 전환 세그먼트에서 바로 고릅니다 |
| EnterSpace | 접힌 열 띠 · 접기 버튼에서 펼치기 · 접기. 포커스는 바뀐 쪽 버튼으로 |
| Esc | 포인터로 끄는 중이면 취소합니다(원래 자리, moveCancelled) |
라이브 영역
보드에 하나(role=status, polite)입니다. onMove 가 없으면 그리지 않습니다.
| 알림 | ko | en | ja |
|---|---|---|---|
| 옮김 | ‘환불 사유 입력 화면’ 카드를 진행 중 3개 중 1번째로 옮겼습니다. | Moved “Refund reason form” to In progress, position 1 of 3. | 「返金理由の入力画面」を進行中の1番目(3件中)に移動しました。 |
| 실패 · 되돌림 | ‘환불 사유 입력 화면’ 카드를 옮기지 못해 원래 자리로 돌려놓았습니다. | Couldn’t move “Refund reason form”. It’s back in its original place. | 「返金理由の入力画面」を移動できなかったため、元の位置に戻しました。 |
| 끌기 취소 | ‘환불 사유 입력 화면’ 카드 옮기기를 취소했습니다. | Cancelled moving “Refund reason form”. | 「返金理由の入力画面」の移動を取り消しました。 |
props
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
Board
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| aria-label필수 | string | — | 보드 영역의 이름(region). 화면 이름과 같게 |
| activeColumn | string | — | 좁은 화면에서 보일 열(제어). 넓은 화면에서는 모든 열이 보이고 이 값은 쓰지 않는다 |
| defaultActiveColumn | string | — | 비제어 첫 값. 기본: primary 열에 카드가 있으면 그 열, 없으면 첫 열 |
| onActiveColumnChange | (columnId: string) => void | — | |
| collapsed | string[] | — | 접힌 열 id(제어). 좁은 화면에서는 접기를 쓰지 않는다 |
| defaultCollapsed | string[] | — | 접힌 열 id(비제어 첫 값) |
| onCollapsedChange | (columnIds: string[]) => void | — | |
| onMove | (move: BoardMove) => void | Promise<void> | — | 주면 카드를 옮길 수 있다. 이동 메뉴(키보드 · 스크린리더)가 켜지고, dnd 를 함께 주면 포인터 끌기도 켜진다. Promise 를 돌려주면 Board 가 먼저 옮겨 그리고(낙관적), 거절되면 원래 자리로 되돌린 뒤 moveFailed 를 읽는다. 앱은 resolve 하기 전에 자기 데이터를 새 순서로 바꾼다. 카드 순서의 정본은 언제나 앱 데이터다 |
| dnd | BoardDndAdapter | — | ./board/dnd 의 boardDnd. onMove 없이 주면 개발 모드 경고만 내고 끌기를 켜지 않는다 |
| stickyTop | number | string | — | 열 머리가 붙는 위치. 기본 0. 상단 바 아래면 "var(--uyoo-topbar-h)" |
| headingLevel | 2 | 3 | 4 | 3 | 열 이름의 제목 수준. 기본 3(화면에 h1 이 없고 섹션 제목이 h2) |
| toolbar | ReactNode | — | 툴바 앞에 둘 것(요약 문장 · 필터). 모두 펼치기 · 접기는 묶음이 있으면 Board 가 그린다 |
| now | Date | — | 줄 목록의 상대 시각 기준(테스트 · 스냅샷). 없으면 지금 |
| timeZone | string | — | 줄 목록 시각의 IANA 시간대. 없으면 서울 |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<BoardLabels> | — | |
| className | string | — | 배치용 |
| children필수 | ReactNode | — | BoardColumn. 열 안의 카드 · 묶음 · 줄은 감싸지 않고 바로 둔다(Board 가 순서를 읽는다) |
BoardColumn
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| id필수 | string | — | |
| title필수 | string | — | |
| glyph | "todo" | "in-progress" | "done" | "blocked" | "draft" | "pending" | "approved" | "approved-done" | "rejected" | — | 열 머리의 상태 글리프(D-Status). 색이 아니라 모양이 열을 말한다 |
| count | number | — | 머리의 개수. 기본은 카드 · 줄 수. 서버가 센 전체 수를 넘길 수 있다 |
| limit | number | — | WIP 상한. 개수 옆에 3/5 로 보이고 넘으면 주의색이다. 막지는 않는다(결정 #13) |
| primary | boolean | — | 좁은 화면에서 처음 볼 열 후보(진행 중). 카드가 없으면 첫 열을 본다 |
| variant | "card" | "line" | — | card(기본) · line(완료처럼 수십 건 쌓이는 열). 줄 목록 열은 카드를 옮겨 올 곳이 아니다 |
| visible | number | — | line 에서 처음 보이는 줄 수. 나머지는 접힌 "n개 더 보기" |
| empty필수 | ReactNode | — | 필수. 무엇이 없고 어떻게 생기는지 한 문장 |
| foot | ReactNode | — | 열 아래 한 줄(그 전 완료 n건) |
| collapsible | boolean | — | 좁은 화면에서는 무시한다 |
| children | ReactNode | — | BoardGroup · BoardCard · BoardLine. 감싸지 않고 바로 둔다 |
BoardGroup
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| id필수 | string | — | |
| title필수 | string | — | |
| number | string | — | 절 번호(2.1). 제목 앞 보조 색 |
| count | number | — | 머리의 개수. 기본은 카드 수 |
| defaultOpen | boolean | — | <details open> 의 첫 값. 모두 펼치기 · 접기가 바꾼다 |
| children | ReactNode | — | BoardCard |
BoardCard
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| id필수 | string | — | |
| title필수 | string | — | |
| href | string | — | 제목 링크. 카드 전체가 이 링크로 눌린다 |
| asChild | boolean | — | 앱의 링크를 쓴다. 자식 링크 요소 하나에 제목 · 클래스를 입힌다. <BoardCard asChild …><Link href="…" scroll={false} /></BoardCard> |
| children | ReactNode | — | asChild 일 때 링크 요소 하나 |
| itemKey | string | — | 보조 키(PAY-142). mono · muted |
| badges | ReactNode | — | 키 옆 배지(다음 · 우선순위). 5톤 Badge |
| meta | ReactNode | — | 맡은 사람 · 마감 · 댓글 수 줄 |
| wait | ReactNode | — | 먼저 끝나야 하는 작업 문장. 시계 아이콘과 함께 |
| locked | boolean | false | 이 카드만 옮기지 못하게 한다(손잡이 · 이동 메뉴 없음) |
BoardLine
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| id필수 | string | — | |
| title필수 | string | — | |
| href | string | — | 줄 링크. 앱의 링크를 쓰려면 asChild 로 자식 링크 요소 하나를 넘긴다 |
| asChild | boolean | — | |
| at필수 | string | — | UTC ISO. 표시할 때 상대 시각으로 바꾼다 |
| children | ReactNode | — | asChild 일 때 링크 요소 하나 |
import
./board 는 radix-ui 가 peer 로 필요합니다(열 전환 세그먼트 · 이동 메뉴). ./board/dnd 는 @atlaskit/pragmatic-drag-and-drop 과 -auto-scroll 이 선택 peer 로 필요하고, 끌기를 켜는 화면만 가져옵니다. 화면은 @uyoolabs/ui/css 에 들어 있습니다.