시안 scroll(7341:2675)이다. 컴포넌트가 아니라 스크롤이 생기는 요소에 입히는
외형이라 `fox.scroll()` 믹스인과 `.fox-scroll` 클래스로 냈다.
둘레 4px 여백은 트랙 padding이 아니라 막대의 투명 테두리로 만든다 — padding으로
주면 막대가 트랙 끝까지 가지 못한다. Chrome 121+가 표준 속성을 보면 웹킷 규칙을
통째로 무시하므로 Firefox 대비는 @supports로 갈라 놓았다.
이미 스크롤이 있던 @fox 컴포넌트 넷(셀렉트 목록·칩 셀렉트·모달·파일목록)에 적용했다.
Co-Authored-By: Claude Opus 5
Fox*Icon 컴포넌트 141곳을 fox-ico 클래스로 바꾸고 옛 SVG 모듈을 지웠다.
아이콘 상자 규칙에는 상자 크기를 그대로 둔 채 font-size만 더했다 — 손으로 그린
인라인 svg가 든 자리도 같이 쓰이기 때문이다.
사이드바 메뉴 아이콘의 화이트리스트가 사라졌다. 폰트는 통째로 하나라 이름이 늘어도
번들이 커지지 않아, 백엔드가 주는 kebab 이름을 PascalCase로 바꿔 붙이기만 하면 된다.
@fox 13.3MB → 6.4MB.
Co-Authored-By: Claude Opus 5
디자인팀 요구다. 클래스명 하나로 쓰고 이름은 Figma의 `ico/House`를 대소문자까지
그대로 담는다. 굵기별 @font-face가 따로라 화면이 쓰는 굵기만 내려받는다.
기존 SVG 컴포넌트에서 뽑은 path로 구워 그림이 같다는 것이 데이터 수준에서 보장된다
(글리프 대조 결과 불일치 0.97%, 전부 외곽선 안티에일리어싱).
duotone은 글리프 하나가 두 색을 못 가져 배경겹을 별도 글리프로 넣고 CSS가 겹친다.
Co-Authored-By: Claude Opus 5
본 프로젝트 사이드바처럼 상단에 FoxInput 검색창을 두고, 그 아래 테마 컨트롤을
가운데 세웠다. 제목·부제는 지웠다.
검색은 라벨과 id를 함께 본다(FoxButton / fox-button). 분류 이름이 걸리면 그 아래는
통째로 남기고, 검색 중에는 접힘을 무시한다 — 걸린 항목이 접힌 가지에 숨으면 안 된다.
섹션 개수도 걸린 것만 센다.
Co-Authored-By: Claude Opus 5
FoxSegmentedControl은 inline-flex라 내용만큼만 넓은데, 상단을 세로 flex로 만들면서
기본값 align-items: stretch가 216px로 늘렸다. 마지막 칸은 구분선이 투명이라 남은
78px이 "다크" 칸의 일부처럼 보였다.
Co-Authored-By: Claude Opus 5
admin-sidebar(시안 snb-area 3041:11860)의 규칙을 그대로 옮겼다. 240px 폭,
회색→옅은 파랑 그라데이션, 1depth 알약(multiply)·2depth 흰 카드·3depth 들여쓰기.
@fox 폴더만 복사해도 하니스가 따라가야 해서 app SCSS를 참조하지 않고 옮겨 적었다.
깊이를 맞추려고 트리도 본 프로젝트와 같은 3depth 접힘으로 바꿨다. 상위 분류가 없는
토큰 섹션(Size·Theme·Primitive)은 자식 없는 1depth라 바로 선택된다. 펼침은
useSidebarNav와 같이 "선택 항목에서 파생된 기본값 + 사용자가 누른 값" 2층이다.
시안에 없어 남긴 것: 선택 가능한 행의 개수 배지(캐럿 자리)와 focus-visible 윤곽선.
Co-Authored-By: Claude Opus 5
본문이 길어지면 문서 전체가 스크롤되어 사이드바까지 함께 밀려 올라갔다. shell을 뷰포트 높이로
못박고 overflow를 끊은 뒤, 두 칸이 각자 overflow-y를 갖게 했다(AdminShell과 같은 방식).
좁은 화면에서는 종전처럼 위아래로 쌓이고 문서가 스크롤된다.
사이드바를 디자인 시스템으로 옮겼다 — 손으로 그린 테마 전환 버튼 묶음을 FoxSegmentedControl로,
항목 옆 개수를 FoxBadge로 바꿨다. 대체된 .themeSwitch·.themeButton·.navCount 스타일은 지웠다.
검증 — 1600×900 실측: shell 900·overflow hidden, 문서 전체 스크롤 없음, 사이드바(3237)·
본문(9432) 각각 자체 스크롤.
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
표의 선택 열처럼 네모(동그라미)만 필요한 자리가 있는데 글자를 감출 방법이 없었다.
표 예시에서는 카탈로그 전용 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 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 Navigation 섹션(391:4035)의 7개 컴포넌트 타입을 5개로 옮겼다.
- FoxBreadcrumb (breadcrumb)
- FoxPagination (pagination + pagination-item)
- FoxCarouselIndicator (carousel-dot + carousel-number)
- FoxCarouselButton (carousel-arrow + carousel-atomic)
- FoxCarouselArea (carousel-area)
합친 근거는 껍데기가 같고 안에 든 것만 다르다는 것이다. 원형 버튼 둘은
테두리·그림자만 달라 type으로, 위치 표시 둘은 흰 알약이 같아 type으로 갈랐다.
pagination의 시안 device 축은 프롭이 아니라 화면 폭이 정한다 — 768px 미만에서
이전·다음이 첫 줄로 빠지고 번호가 아래 줄에 선다. 마크업은 한 벌이고 DOM 순서는
읽는 순서 그대로다.
@fox/core/icons 신설 — 손으로 그리지 않고 Figma export 자산의 path를 옮겼다.
시안이 아이콘을 정사각 박스 안에 여백을 두고 앉히므로 그 여백까지 translate로
재현했다. CaretLeft/Right는 Regular·Bold 두 벌이다(시안이 자리마다 다르게 물렸다).
승인된 비토큰 값: 숫자칸 3.2rem/2.4rem, 캐러셀 점 0.8rem, 활성 막대 2.4rem,
가운뎃점 0.4rem — 모두 시안 값이나 Figma가 변수로 export하지 않는다.
Co-Authored-By: Claude Opus 5
Figma form-email(136:3092)과 form-address(5346:5823)를 옮긴다. 두 시안 모두
상자가 input·select 인스턴스이고 버튼도 btn 인스턴스라 FoxInput·FoxSelect·
FoxButton을 그대로 조합했다 — 두 SCSS는 배치와 `@` 구분자만 갖는다.
FoxEmail
값은 아이디@도메인이 합쳐진 문자열 하나다. 도메인을 목록에서 고르면 옆 칸이
그 값으로 덮이고 읽기 전용이 되며, "직접입력"을 고르면 잠금만 풀린다.
확인 버튼은 onVerify를 넘겼을 때만 렌더된다.
칸은 시안의 240px을 최소폭으로 두고 남는 폭을 나눠 갖는다 — 좁아지면 줄이
넘어간다(사용자 승인). 토큰 스케일에 240이 없어 이 한 줄만 리터럴이다.
FoxAddress
우편번호와 주소는 검색 결과로만 채워지는 읽기 전용이고 상세주소만 입력한다.
어떤 우편번호 서비스를 쓰는지 @fox는 모른다 — 호출부가 onSearch에서
FoxAddressSearchResult(zipCode·address·선택 detail)로 바꿔 돌려주고, 취소는
null이다. detail을 안 넘기면 사용자가 적어 둔 상세주소를 건드리지 않는다.
Co-Authored-By: Claude Opus 5
Figma input(135:621)의 2타입 × 3크기 × 6상태를 옮긴다. 네이티브 을
감싸므로 시안에 없는 password·email·number 등 모든 타입이 그대로 통한다.
상태는 두 갈래다. 기본은 브라우저가 판단한다 — 포커스는 :focus-within,
비활성·읽기전용은 네이티브 속성, 오류는 aria-invalid. 시안의 completed는
default와 글자색만 다른데 그건 플레이스홀더와 입력값의 네이티브 차이라 규칙이
필요 없다. 모양을 못박아야 하면 루트에 data-state를 준다.
date는 브라우저가 그리는 달력 아이콘과 피커를 그대로 쓴다(사용자 승인).
지우기 버튼은 포커스 중이고 값이 있을 때만 뜨고 누르면 값을 비운다 —
비제어 입력은 DOM을 직접 비우고 제어 입력은 onChange()로 알린다.
헬퍼 메시지 색은 상태를 따라간다(기본 회색 · 포커스 파랑 · 완료 초록 ·
오류 빨강). 아이콘은 @fox에 아이콘 세트가 없어 슬롯으로 두었다.
Co-Authored-By: Claude Opus 5
Figma btn-group(398:4854)에서 가져온 건 간격뿐이다 — 크기·방향과 무관하게
gap-2(4px) 하나여서 그룹에 크기별 스타일이 없다. 배치 규칙(방향·정렬·줄바꿈)은
지시받은 사양이다.
크기는 그룹이 단일 진실 공급원이다. 자식을 cloneElement로 덮어써서 자식이
저마다 다른 size를 줘도 그룹 값으로 통일된다. 세로일 때는 fullWidth까지 함께
내려주고 CSS도 stretch라, React를 안 쓰는 소비자도 같은 결과를 얻는다.
Children.map이 프래그먼트 안으로 들어가지 않아 직접 내려간다. 그러지 않으면
<>…로 감싼 버튼이 덮어쓰기를 피해 가고 Fragment에 size가 붙어 경고가 난다.
호스트 요소(div·span)는 DOM에 없는 속성이 넘어가지 않도록 건드리지 않는다.
Co-Authored-By: Claude Opus 5
값의 출처가 컴파일된 CSS로 옮겨간 뒤 매니페스트가 제공하던 것은 Figma 변수 경로와
"원본에선 참조였으나 컬렉션이 없어 값으로 굳은 것"(chart-alt 18개) 표시 둘뿐이었다.
둘 다 쓰지 않기로 해 생성물·화면·변환기에서 모두 걷어낸다.
변환기는 이제 SCSS만 생성한다. 화면은 스타일시트 외에 아무것도 참조하지 않는다.
Co-Authored-By: Claude Opus 5
5px에서는 밝은 회색 계열(#FFFFFF · #F4F5F6 · #E6E8EA)이 서로 구분되지 않았고
반투명 토큰의 알파 여부도 보이지 않았다.
16px는 icon-3(1.6rem)과 같은 값이라 리터럴 대신 토큰을 쓴다 — 5px일 때는 스케일에
맞는 값이 없어 리터럴이 불가피했던 예외가 사라졌다.
Co-Authored-By: Claude Opus 5
Figma 변수 페이지와 1:1로 대조하기 위한 감사 화면으로 바꾼다. 분류를 우리 CSS 그룹이
아니라 원본 컬렉션/모드로 두고 — Mode(Light/Dark) · Responsive(PC/Mobile) · Size ·
Theme · Primitive — 파일에 값이 적혀 있으면 그 값을, 다른 토큰을 가리키면 참조 토큰을
표시한다. 토큰 이름 아래에 Figma 변수 경로를 함께 적는다.
CSSOM 읽기로는 이 요구를 만족할 수 없어 걷어냈다. CSS는 alias를 var()로 흡수하고
light/dark를 light-dark() 한 줄로 합치므로 "원본이 값을 적었는지 토큰을 가리켰는지"를
되살릴 수 없다. 대신 변환기가 원본 구조 그대로를 담은 매니페스트를 함께 생성한다 —
여전히 자동 생성이라 목록을 손으로 관리하지 않는다.
미리보기도 CSS 변수가 아니라 매니페스트의 해석값을 쓴다. 변수를 쓰면 PC 섹션을 좁은
창에서 볼 때 모바일 값이, Light 섹션을 다크 테마에서 볼 때 다크 색이 그려진다.
⚠️ 매니페스트를 만들며 잡은 것: 이 export에는 `theme`이라는 이름의 컬렉션이 둘이고
(제공된 파랑 계열, 미제공 청록 계열) 변수 경로까지 같아, 이름만으로 매칭하면 chart-alt
18개가 엉뚱하게 해결된 것처럼 보인다. 값 대조를 넣어 "미제공 컬렉션"으로 표시한다.
SCSS 생성 쪽은 원래부터 값 대조를 하고 있어 영향이 없었다.
Co-Authored-By: Claude Opus 5
토큰 표기를 `fox.color(background-default)`에서 `color-background-default`로 바꾼다.
`--fox-` 접두사만 뗀 실제 CSS 변수 이름이라 devtools에서 보는 것과 1:1로 대응하고,
함수 표기처럼 그룹명이 괄호 밖으로 빠지지 않아 목록을 훑을 때 이름이 바로 읽힌다.
표기를 만드는 함수가 하나뿐이라 스와치·서체 목록·값 표가 갈릴 일이 없다.
그 결과 쓰이지 않게 된 fn prop을 제거한다.
사이드바의 "추가 방법"은 컴포넌트 등록 여부와 무관하게 목록 맨 아래에 고정한다 —
이전에는 컴포넌트가 하나라도 생기면 사라져 추가 절차를 찾을 수 없었다. 컴포넌트가
아니라 안내이므로 점선 테두리와 흐린 글자로 등록된 탭과 층위를 구분한다.
Co-Authored-By: Claude Opus 5
값만 보여주던 막대를 걷어내고 그룹별로 실제 적용 결과를 보인다 — 서체는 그 속성을
적용한 문장, 패딩·간격은 문단 두 개 사이의 공간, 반경·테두리는 실제 테두리를 가진
div, 아이콘은 브라우저 기본 체크박스, 그림자는 실제 box-shadow, 배경 흐림은 사진 위
절반에만 건 backdrop-filter다. 원시 수치는 값만 남긴다.
그림자는 Figma에 offset·blur 수치만 있고 조합과 색이 정의돼 있지 않다. 각 수치의
효과를 보이려고 location은 `0 값 0`, blur는 `0 0 값`으로 걸고 색은 실재 토큰인
alpha-default-inverse25를 쓴다 — 조합 자체는 토큰이 아니며 섹션 설명에도 적었다.
배경 흐림 예제 이미지는 next/image가 아니라 다. @fox가 Next에 의존하면 폴더째
복사되는 이식성이 깨지고 외부 호스트마다 remotePatterns 설정이 필요해진다.
Co-Authored-By: Claude Opus 5
토큰 배치 확인과 컴포넌트 예제를 한 페이지에서 본다. 사이드바는 토큰/컴포넌트
두 대분류이며, 토큰 그룹은 CSSOM에서 읽으므로 목록을 코드에 두지 않는다.
컴포넌트는 component-registry의 배열에 항목 하나만 추가하면 사이드바와 본문이
함께 갱신된다 — 만들 때마다 다른 파일을 고칠 필요가 없다.
하니스를 앱이 아니라 @fox에 두는 이유는 폴더를 다른 프로젝트로 복사할 때 함께
따라가게 하기 위함이다. 그래서 앱 상수(테마 저장 키 등)를 import 하지 않고,
테마 전환은 @fox가 소유한 data-theme 계약만 건드린다(저장하지 않는다).
선택 상태는 URL 해시가 SSOT라 새로고침해도 보던 섹션이 유지된다.
같은 목적의 카탈로그를 두 벌 두면 갈라지므로 구 app/design은 함께 제거한다.
Co-Authored-By: Claude Opus 5