두 변경이 등록 팝업의 같은 파일에 겹쳐 한 커밋으로 둔다.
## 중복 확인 실패 사유
확인 호출이 백엔드에서 깨지면 `isAdminLoginIdTaken`이 던진 예외가 클라이언트 transition의
처리되지 않은 rejection으로 사라졌다 — 버튼만 원복되고 아무 말도 남지 않아 "눌러도 아무 일이
없는" 상태로 보였다. 확인 결과에 `failed`를 더해 **값의 판정(unavailable)과 확인 실패(failed)를
구분**하고, 백엔드 메시지를 ID 칸 아래 같은 자리에 그대로 띄운다.
## @fox/core/validation
Angular `Validators`의 계약을 옮겼다 — 검증기는 값을 받아 통과면 `null`, 아니면 **오류 객체**를
돌려주는 순수 함수이고, 키가 오류 이름, 값이 문구를 만들 맥락이다
(`{ minlength: { requiredLength, actualLength } }`). 인자가 `AbstractControl`이 아니라 문자열인
것만 다르다 — @fox에는 폼 모델이 없고 검증 대상이 칸의 값 하나뿐이다.
`required`를 뺀 검증기는 빈 값을 통과시키고(Angular와 같다), `compose`는 오류를 **병합**해
"10자 이상"과 "2종류 이상"을 함께 판정할 수 있게 한다.
**문구는 검증기가 갖지 않는다**(Angular와 같은 이유 — 같은 규칙도 자리마다 다른 말이 필요하다).
`foxValidationMessage(errors, overrides)`가 옮기고, 한 번에 한 문구만 낸다.
비밀번호처럼 규칙이 여러 개인 칸을 위해 `foxPasswordValidator(policy)`를 둔다 — 길이·조합에
더해 같은 문자 연속·잇따르는 문자·특정 문자열 포함 금지를 옵션으로 받는다.
## FoxInput 연결
`validators` · `validationMessages` · `validateOn` · `onValidationChange`.
- `message`는 평소 헬퍼이고 검증이 걸리면 그 자리에 사유가 들어간다. 호출부가 `invalid`를 직접
켜면 `message`가 이긴다 — 서버가 준 사유(중복·권한)는 화면 규칙이 알 수 없어 덮이면 안 된다.
- 오류는 **한 번 포커스를 벗어난 뒤부터** 보이고 그 뒤로는 입력할 때마다 갱신된다
(Angular의 `touched`. `updateOn` 기본값만 다르다 — 한 글자에 "10자 이상"이 뜨면 방해가 된다).
## 사용
비밀번호 규칙을 `ADMIN_PASSWORD_POLICY` 한 곳으로 모아 **화면과 Server Action이 같은 값을 본다**
— 한쪽만 고치면 화면은 통과시키고 저장은 거부하는 상태가 된다. 등록은 `required: true`,
수정은 비우면 "바꾸지 않음"이라 걸지 않는다.
Co-Authored-By: Claude Opus 5
둘 다 시안 ADM_ADM_102_p(5227:2035)가 요구하는데 없던 것이다.
- `FoxInput`에 `requirement`를 열었다. 라벨을 FoxFormLabel로 그리면서 그 값을 넘기지 않아
`이름*`처럼 필수 별표가 붙은 시안을 그릴 수 없었다. 이미 같은 prop을 가진 `FoxSelect`와
같은 모양으로 맞췄다.
- `FoxChipArea`에 `wrap`을 열었다. 스타일 주석이 "칩이 여러 줄로 흘러야 하는 자리가 나오면
시안에 먼저 추가한다"고 적어 둔 그 자리다 — 시안의 "메뉴 선택"이 칩을 두 줄로 감는다.
기본값은 종전대로 한 줄이라 기존 호출부는 그대로다. `FoxButtonGroup`의 같은 prop과 맞췄다.
Co-Authored-By: Claude Opus 5
FoxListContainer가 FoxListSearch에 검색어·검색 대상을 제어 값으로만 넘기고 변경 핸들러를
넘기지 않아, 입력창은 값이 고정되고(FoxListSearch가 controlled로 판정) 검색 대상 셀렉트도
스스로 값을 바꾸지 않았다(FoxSelectText의 같은 규칙). 즉 검색 자체가 불가능했다.
넘어오는 keyword·searchField는 이미 **적용된** 조건이라 입력 중인 값과 역할이 다르다.
컨테이너가 입력 초안을 따로 들고, 적용된 조건이 바뀌면 초안을 그 값으로 되돌린다 —
뒤로가기처럼 주소가 검색어를 바꾸는 경로에서도 입력창이 따라온다.
학생 회원 목록이 같은 증상이었고 이 수정으로 함께 낫는다.
Co-Authored-By: Claude Opus 5
시안(관리자페이지 5199:9014)을 다시 대조해 고쳤다. 1·2·5는 내가 잘못 읽은 것이고, 3·4는
@fox에 기능이 없어 추가했다(사용자 확정).
1. 이름 열의 bold를 뺐다. 시안 셀 서체가 `Pretendard:Regular`인데 `emphasis`를 넣었다.
2. 역할을 배지로 바꿨다. 시안 셀에 `type=pastel, color=primary, size=md` 배지가 들어 있는데
평문으로 그렸다.
3. 관리 열을 없애고 행을 눌러 상세를 열게 했다. FoxTableRow에 `onSelect`,
FoxListContainer에 `onRowSelect`를 추가했다. ``에 `role="button"`은 씌우지 않는다 —
보조기술에서 행이 표의 일부가 아니게 되어 열 이름 읽기가 끊긴다. 초점만 받게 하고
Enter·Space를 직접 듣는다. 모달 상태는 student-list가 갖고, 쓰임이 사라진
student-row-actions는 지웠다.
4. 정렬 셀렉트 아이콘을 ArrowsDownUp으로 바꿨다. 시안은 정렬과 페이지 크기가 같은 select-txt를
쓰면서 아이콘만 다른데(노드 293:138376 vs 293:139748), FoxSelectText가 캐럿을 하드코딩해
교체할 수 없었다. `icon` prop을 넣고 기본값은 캐럿 그대로 뒀다.
5. 엑셀 버튼을 secondary + DownloadSimple 아이콘으로 바꿨다. 시안 글자색이
`button/secondary-font`(#0b50d0)이고 20px 아이콘이 붙어 있다.
검증 — 이름 굵기 400, 역할 `fox-badge--primary --pastel --md`, 열 12개(마지막 가입일)에
행이 clickable·tabIndex 0이고 클릭 시 모달이 열린다, 정렬 아이콘이 페이지 크기 아이콘과 다르다,
엑셀 버튼이 `fox-button--secondary`에 아이콘을 갖는다.
Co-Authored-By: Claude Opus 5
시안: 관리자페이지(QsFVFFUKGH68xAPVkF3SJ9) container 5199:9014.
@fox에 없는 조각은 없었다 — 지난 작업의 FoxListContainer가 머리말·도구 줄·표·페이지네이션을
한 번에 덮고, 정렬 셀렉트는 FoxSelectText, 검색은 FoxListSearch, 버튼은 FoxButton이다.
데이터도 이미 맞물려 있었다: StudentMember에 시안의 13열이 전부 있고 StudentMemberQuery에
검색필드·정렬·페이지크기·buildStudentMemberHref가 있다.
- students/_components/student-list.tsx 신설. 이 화면 고유의 것만 갖는다 — 13열 정의,
정렬·페이지 크기 셀렉트, 엑셀 다운로드, URL 갱신 방법.
- page.tsx는 조회만 하고 넘긴다(Server Component 유지).
- student-search-bar · student-list-toolbar · student-table 3종을 지웠다. 셋이 하던 일을
FoxListContainer가 대신한다.
이동은 두 갈래다. 페이지네이션은 `buildHref`로 링크가 되고(주소가 곧 상태), 검색·정렬·페이지
크기는 `router.replace`로 같은 주소 규칙을 태운다. 조건이 바뀌면 늘 1페이지로 되돌린다.
사용자 확정 2건
- 열 폭 합계가 1880px로 컨테이너(1552)보다 넓어 FoxListContainer에 가로 스크롤 래퍼를 넣었다.
좁은 화면에서는 목록표가 카드로 바뀌므로(FoxTable의 mobile 규칙) PC에서만 건다.
- 관리 열을 "학생 회원 조회" 텍스트 버튼으로 바꿨다. 앞서 기획 시안을 근거로 돋보기 아이콘을
넣었으나 이 Figma 시안이 더 최신이다. 누르면 상세 팝업이 열리는 동작은 그대로다.
지킨 것 — 순번은 저장된 값이 아니라 현재 페이지 기준 표시 순번((page-1)*pageSize+index+1)으로
종전 표와 같은 규칙이다. 엑셀은 네이티브 GET 폼이라야 첨부파일 응답을 브라우저가 받는다.
검증 — 실측이 시안과 일치한다. 제목·총 123건·정렬 셀렉트("가입일순"·"10개씩 보기")·검색 대상
"회원명"·엑셀 버튼, 13열 이름 전부, 관리 버튼 "학생 회원 조회", 표 1880/래퍼 1552로 가로
스크롤, 페이지 링크 `/students?page=2`, 모바일용 셀 라벨은 PC에서 display:none.
Co-Authored-By: Claude Opus 5
시안: 관리자페이지(QsFVFFUKGH68xAPVkF3SJ9) container 3019:8608.
시안 조각 대부분은 이미 @fox에 있었다(FoxPageHeader·FoxTable·FoxPagination·FoxSelectText·
FoxChipArea·FoxBadge·FoxButtonGroup). 없던 것은 list-toolbar 하나였고, 그 안의 검색 상자까지
합쳐 셋을 새로 만들었다.
- FoxListSearch: 앞쪽에 검색 대상 셀렉트가 들어가는 상자. FoxInput에는 그 슬롯이 없어 따로
만들었다. `
아코디언(194:3563 box · 194:3680 line)과 카드(441:6006)를 옮겼다.
FoxAccordion — 계열 둘이 성격이 다르다. box는 색을 깐 상자로 여백·모서리를 상자가 갖고
제목이 강조 색이며, line은 위쪽 선 하나로 나뉘는 목록형이라 바탕이 없고 제목 줄이 세로
여백을 갖는다. 크기 lg·md가 여백·글자·모서리를 함께 바꾼다.
열림은 넘기면 호출부가, 안 넘기면 컴포넌트가 쥔다 — 여러 개 중 하나만 열리게 하려면
호출부가 쥐면 된다. 닫히면 내용을 DOM에서 뺀다. 안의 초점 가능한 것들이 탭 순서에 남으면
보이지 않는 곳으로 초점이 사라진다.
FoxCard — 크기 넷은 여백과 모서리만 바꾼다. 폭은 놓이는 자리가 정한다(시안은 320px 자리에
뒀을 뿐 카드 자체의 값이 아니다). 아래쪽 동작 줄은 좌우·아래 여백만 갖는다 — 위쪽은 글
영역의 아래 여백이 이미 띄워 준다.
검증: 아코디언 16항목 중 15개 일치(나머지 1건은 내 예상값 오류였고 코드가 맞다 —
action/secondary는 투명 검정이 아니라 투명 흰색이다). 카드 18항목 전부 일치.
sass 694규칙 · tsc · eslint 통과.
승인된 비토큰 값: 그림 비율 16/9.
Co-Authored-By: Claude Opus 5
제목을 안 넘기면 제목 줄이 통째로 빠져 닫기 아이콘이 내용 위로 내려앉았다. 시안의 tit
프레임은 제목이 없어도 28px(제목 글자 크기 × 줄높이)을 차지한다.
줄은 항상 내고 `
`만 제목이 있을 때 낸다 — 빈 제목을 심으면 이름 없는 heading이 되어
스크린리더가 헛읽는다. 높이는 토큰에서 계산해 브레이크포인트가 바뀌어도 따라간다.
검증: 제목이 있든 없든 제목줄 높이 28 · 위치 41 · 내용 시작 85로 같다. 제목이 없을 때
닫기 아이콘 아래끝 56이 내용 시작 85보다 위라 겹치지 않는다.
Co-Authored-By: Claude Opus 5
디자인시스템 파일의 modal(190:1136)을 옮겼다. 크기 넷(sm 400 · md 560 · lg 760 · full)이
모두 폭 토큰이고, 모바일에서는 넷 다 같은 폭으로 모이며 여백·모서리·닫기 버튼도 함께
줄어든다 — 반응형 토큰이 그렇게 정의돼 있다.
네이티브 `
관리자페이지 파일의 page-tit-area(3002:6823)를 옮겼다. 현재 위치와 제목을 얹고 본문과의
간격(아래 여백 36px)까지 갖는다 — 시안이 그렇게 묶어 놓아 화면마다 따로 띄우지 않아도 된다.
현재 위치는 FoxBreadcrumb를 그대로 쓴다(시안에서도 breadcrumb 인스턴스다). 항목 배열만
넘기면 되고, 넘기지 않으면 그 줄을 그리지 않는다.
제목은 화면에 하나뿐인 이름이라 `
`으로 낸다. 시안은 `
`지만 그건 Figma에 문단밖에
없어서고, 마크업에서는 제목이 맞다.
`actions` 자리를 열어 뒀다. 시안에 들어간 것은 없지만 배치가 자리를 비워 두고 있다 —
제목 칸이 `flex: 1`이고 오른쪽에 24px 간격이 남는다. 값을 지어내지 않고 그 자리만 연다.
검증: 바깥 간격 24 · 아래 여백 36 · 안쪽 간격 8 · 제목 32px w800 #1e2124 줄높이 48 ·
자간 -0.8px이 시안과 일치. 오른쪽 자리를 쓸 때 제목이 남는 폭을 먹고 24px이 유지되며,
제목이 길어져도 오른쪽을 침범하지 않는다. sass 634규칙 · tsc · eslint 통과.
Co-Authored-By: Claude Opus 5
카탈로그 등록 파일에서 충돌 일곱 구역이 났다. 양쪽이 같은 배열 끝에 항목을 붙였는데
git이 서로 다른 항목의 앞뒤를 맞물려 갈라 놓아, 구역별로 이어 붙이면 남의 항목 머리에
내 항목 꼬리가 붙는다.
그래서 충돌 파일을 손보지 않고 양쪽 원본을 꺼내 항목 단위로 다시 합쳤다. 중괄호를 세어
최상위 항목을 잘라내므로 JSX 안의 괄호나 타입 표기의 대괄호에 속지 않는다.
- 겹치는 항목 없음: 우리 12개(breadcrumb·pagination·carousel 3·bullet-list·badge 3·
table·tab·spinner), 상대 6개(chip·chip-area·select-text·segmented-control 2·
toggle-quantity)
- fox-select는 상대만 고쳤으므로(다중 선택 지원) 그쪽 판을 쓴다
- fox-toggle-switch는 둘 다 고친 것으로 보였지만 뒤에 항목이 붙으며 닫는 괄호가 옮겨진
것뿐이라 실제 충돌이 아니다
components.scss는 자동 병합되며 `@use "fox-toggle-switch"`가 두 번 들어가 Sass가 같은
네임스페이스 중복으로 컴파일을 멈췄다. 중복을 지웠다.
검증: 카탈로그 45개 항목이 모두 뜨고 양쪽 새 컴포넌트가 화면에 그려진다. 파티셜 50개가
빠짐없이 등록돼 있고 배럴도 폴더 수와 같다. sass 630규칙 · tsc · eslint 통과.
Co-Authored-By: Claude Opus 5
관리자페이지 파일의 img-loading(5384:9710)을 옮겼다. 시안은 80px 그림 한 장으로 왔지만
래스터를 그대로 쓰지 않았다 — 크기를 바꾸면 뭉개지고 색도 토큰을 못 쓴다. 원본 픽셀을
재서 같은 도형을 CSS로 다시 그렸다.
잰 값: 바깥 반지름 40 · 안쪽 28 → 굵기 12px(지름의 15%), 앞머리 원 12px이 6시 자리 고리
중심선 위. 색은 6시에서 시작해 시계방향 60°까지 알파 1.00이고 나머지 300°에 걸쳐 0까지
선형으로 준다(5°마다 샘플링해 확인).
굵기·앞머리를 퍼센트로 잡아 어떤 지름에서도 비율이 같다. `size`만 주면 된다.
FoxButton의 로딩 자리는 "시안 미정"으로 비어 있었는데 이제 이 스피너가 들어간다.
버튼 안에서는 `color: inherit`으로 되돌려 계열별 글자색을 따라간다 — 요소에 준 색이
상속보다 강해서, 기본색을 두는 한 감싸기만으로는 바뀌지 않기 때문이다.
승인된 비토큰 값: 지름 8rem, 굵기·앞머리 15%, 마스크 정지점 70%, 그라디언트 60°.
한 바퀴 도는 시간 1초는 시안이 정지 그림이라 정보가 없어 흔한 기본값을 썼다.
검증: 지름 80 · 색 #256ef4 · 앞머리 12×12가 중심에서 아래로 34px(시안과 동일) ·
그라디언트 from 180deg 0~60deg 불투명. 버튼 세 크기에서 스피너 색이 글자색과 일치하고
아이콘 슬롯 크기를 그대로 채운다.
Co-Authored-By: Claude Opus 5
관리자페이지 파일(QsFVFFUKGH68xAPVkF3SJ9)의 tab(391:4497)·tab-item(391:4431)을
FoxTab / FoxTabItem 둘로 옮겼다. 계열(line·fill) × 크기(lg·md) × 상태 다섯이 모두
시안에 있다.
크기가 배치를 정한다. lg는 탭이 폭을 고르게 나눠 갖고 목록 전체에 옅은 밑줄이 깔리며,
md는 글자만큼만 차지하고 8px 간격으로 선다. 아래 여백도 크기마다 다르다.
계열·크기 수식어는 항목이 아니라 묶음에만 붙인다 — 항목이 홀로 쓰이는 자리가 없어
`.fox-tab--line .fox-tab-item`으로 내려받는 편이 단순하다. 탭은 탭 안에 들어가지 않으므로
후손 선택자가 새는 문제도 없다.
line 계열은 기본 상태에도 투명한 1px 밑줄을 둔다(시안 그대로) — 고름으로 바뀔 때 글자가
밀리지 않으려면 자리를 미리 잡아야 한다. 고른 탭의 4px 밑줄은 목록의 1px 선 위로
끌어올려 겹치는 자리에서 굵은 선이 이기게 했다.
키보드는 ARIA 탭 규약을 따른다 — 좌우 화살표·Home·End로 옮기고 탭키는 고른 탭에만
닿으며, 잠긴 탭은 건너뛴다. 항목이 href를 가지면 주소가 바뀌는 이동이라 보고 로
내며 탭 역할을 붙이지 않는다.
검증: 여백·글자·색·모서리·마크업 23항목이 시안과 일치. 화살표 이동이 양끝에서 감기고
잠긴 탭을 건너뛰는 것을 확인했다. sass 550규칙 · tsc · eslint 통과.
Co-Authored-By: Claude Opus 5
표의 선택 열처럼 네모(동그라미)만 필요한 자리가 있는데 글자를 감출 방법이 없었다.
표 예시에서는 카탈로그 전용 CSS로 가려 두었던 것을 컴포넌트가 갖게 한다.
`display: none`으로 지우지 않는다 — 그러면 접근성 트리에서도 빠져 이름 없는 조작기가
된다. 자리만 없애고 DOM에는 남긴다. 루트가 `
시안 tbl-list(699:6304)를 카탈로그에 넣으려 보니 두 가지가 빠져 있었다.
- FoxTableRow에 `selected` — 고른 줄의 바탕이 action/primary-hover, 글자가
font/theme/primary가 된다. `aria-selected`도 함께 나간다.
- FoxTableCell에 `emphasis` — 그 줄을 대표하는 칸. 시안 list 표의 제목 열이
heading/xsm(15px ExtraBold)를 쓴다.
고른 줄 규칙은 자식 결합자로 자기 칸만 고른다 — 후손 선택자를 쓰면 칸 안에 든 표까지
파랗게 물든다(글머리 목록에서 겪은 것과 같은 문제다).
예시는 시안 그대로 체크 열·정렬 머리·제목 강조·버튼 묶음·더보기·고른 줄을 담았고,
전체 선택 체크박스와 줄 선택이 실제로 동작한다. 정렬 아이콘과 더보기 아이콘은 이미
이식된 Phosphor 세트의 CaretUpDown·DotsThreeVertical을 그대로 쓴다.
검증: 고른 줄 바탕 #ecf2fe·글자 #0b50d0, 강조 칸 15px w800 lh 21px이 시안과 일치.
sass · tsc · eslint 통과.
Co-Authored-By: Claude Opus 5
Figma 표 시안 9종(tbl-basic·tbl-list·tbl-form과 각각의 cell-head·cell-body)을
FoxTable / FoxTableRow / FoxTableCell 셋으로 옮겼다. 세 타입이 뼈대가 같고 여백·글자·
기본 정렬만 달라 한 벌로 충분하다.
진짜
이라 스크린리더가 행·열을 읽어 주고 colSpan·rowSpan이 그대로 동작한다.
본문 행의 제목 칸은 그 줄의 이름이라 scope="row", 머리행 칸은 열 이름이라 scope="col"이
붙는다. 호출부는 행을 늘어놓기만 하면 되고 head 여부로 thead·tbody가 갈린다.
타입 차이를 클래스가 아니라 커스텀 프로퍼티로 내려보낸다. `.fox-table--basic
.fox-table-cell`처럼 후손 선택자를 쓰면 표 안에 표가 들어갔을 때 바깥 규칙이 안쪽까지
덮어쓴다 — 글머리 목록에서 겪은 것과 같은 문제다. 커스텀 프로퍼티는 안쪽 표가 자기
값으로 다시 덮으므로 그 일이 생기지 않는다.
좁은 화면 대응도 시안대로 넣었다. list는 머리행을 감추고 한 줄이 카드로 접히며, 이때
열 이름을 알 수 없으므로 칸이 label을 받아 글자로 보여 준다. form은 레이블이 입력 위로
올라가고 레이블 칸의 회색 바탕이 사라진다. basic은 시안에 모바일 변형이 없어 그대로 둔다.
승인된 비토큰 값: form 머리칸 최소 크기 12rem×4.8rem.
검증: 여백·바탕·글자·정렬 18항목이 시안과 일치(나머지 1건은 내 예상값 오류였고 코드가
맞다 — font/weight/medium은 SemiBold 600이다). 모바일에서 list 카드 접힘·열 이름 노출,
form 위아래 쌓임, basic 유지를 확인했다. sass 524규칙 · tsc · eslint 통과.
Co-Authored-By: Claude Opus 5
Figma badge(391:4804) · badge-number(392:4810) · badge-dot(392:4816) ·
badge-group(392:4848)을 FoxBadge / FoxBadgeDot / FoxBadgeGroup으로 옮겼다.
badge-number와 badge-dot을 합친 이유는 둘 다 알림 표시이고 색 축(primary·new)이
같으며 개수를 아느냐 모르느냐만 다르기 때문이다. count를 주면 숫자, 없으면 점이다.
색 9종 × 계열 3종 × 크기 3종은 색마다 쓰는 토큰이 규칙적이지 않아 SCSS 맵에 모아
한 번에 펼친다 — 손으로 27벌을 적으면 어긋난다. theme 계열은 solid에도 `-strong`을
쓰고, system 계열은 테두리가 `-strong`·solid가 기본값이며, warning만 solid 글자가
검정이고, disabled는 pastel도 solid와 같은 색이다(모두 시안 확인).
테두리를 border가 아니라 안쪽 그림자로 그린다. 시안에서 테두리가 크기를 늘리지 않아
outline과 solid의 높이가 같은데, CSS border는 상자를 키워 sm이 20px 대신 21px이
된다. 굵기는 그대로 border/1 토큰을 쓴다.
FoxBadgeGroup의 size는 자식 배지를 덮어쓴다(FoxButtonGroup과 같은 방식).
승인된 비토큰 값: 최소 높이 2.4rem(md)·2rem(sm), 점 0.6rem, 숫자 배지 2rem —
모두 시안 값이나 Figma가 변수로 export하지 않는다.
검증: 색·계열 17조합의 바탕·글자·테두리 색이 시안 토큰과 정확히 일치. 크기 3종의
높이 31/24/20과 글자 15/13/11이 일치하며 계열이 달라도 높이가 같다. 숫자 배지는
자릿수가 늘어도 높이가 20으로 고정된다. sass 498규칙 · tsc · eslint 통과.
Co-Authored-By: Claude Opus 5
Figma bullet(391:4049) · bullet-list-item(391:4081) · bullet-list(484:5722)를
FoxBulletList / FoxBulletListItem 둘로 옮겼다.
bullet을 따로 두지 않은 이유는 마커가 목록 밖에서 홀로 쓰이는 자리가 없고, 모양이
전부 단계·번호 여부로 갈리기 때문이다. 그 두 축이 항목의 수식어로 들어가 있다.
호출부는 단계를 평평하게 늘어놓고 level만 적는다. 어느 항목 안에 무엇을 넣을지는
목록이 정한다.
레벨1레벨2
시안은 2·3단계를 형제 프레임으로 두고 왼쪽 여백만 20·40px 줬지만, 나가는 마크업은
/ 안에
/이 든 진짜 중첩이다 — 목록의 단계를 스크린리더에 알리려면
그래야 한다. 한 단계마다 20px씩 들어가 3단계가 40px이 되므로 서는 자리는 시안과 같다.
앞에 걸어 둘 항목이 없는 경우(첫 줄부터 2단계)는 빈
에 매달아 목록을 유효하게 둔다.
번호는 같은 묶음 안에서 1부터 다시 센다. 시안에 없는 범위는 z 다음 aa, ⑳ 다음 (21)로
잇고, 다르게 매겨야 하면 marker 프롭으로 덮는다.
승인된 비토큰 값: 마커 칸 2.2×2.6rem · 1.6×2.3rem · 2.4×2.3rem, 채운 원 0.6rem,
빈 원 0.4rem — 모두 시안 값이나 Figma가 변수로 export하지 않는다.
검증: 시안 대조 20항목 전부 일치(칸 크기·마커 모양·글자 크기·단계별 들여쓰기
0/20/40px). 목록의 자식이 li뿐이고 중첩 목록은 모두 li 안에 있다.
sass 463규칙 · tsc --noEmit · eslint 통과.
Co-Authored-By: Claude Opus 5
develop에서 온 FoxFormLabel과 내가 두었던 form-field의 label 믹스인이 같은 일을
두 갈래로 하고 있었다. 라벨 모양을 아는 곳을 하나로 줄인다.
- FoxFormLabel에 `as` 프롭 추가 — 칸 하나를 가리킬 때는 label, 칸 여러 개를 묶은
필드에서는 span. 가리킬 대상이 하나가 아니라 htmlFor를 쓸 수 없고, 아무것도
가리키지 않는
FoxFormLabel · FoxRadio(+Icon·Group) · FoxConditionalOption · FoxCheckbox(+Icon·Group)
· FoxTag(+List) · FoxToggleSwitch가 들어왔다.
충돌 2건은 양쪽을 모두 살리는 union으로 해소했다 — 둘 다 "컴포넌트를 만들 때마다
항목 하나를 추가하는" 등록 파일이라 서로 지울 이유가 없다.
- @fox/styles/components.scss: 새 파티셜 10개 + Navigation 파티셜 5개
- @fox/dev-test/component-registry.tsx: 카탈로그 항목 32개
검증: sass 컴파일 450규칙 · tsc --noEmit · eslint 모두 통과.
Co-Authored-By: Claude Opus 5