로그인 카드 안에만 두었던 `:autofill` 우회를 `_form-field.scss`의 `field` 믹스인으로 옮긴다.
자동완성이 칠하는 배경은 특정 화면 문제가 아니라 텍스트 입력 전부에 해당한다.
- `_autofilled` 믹스인 신설. 배경은 `background`로 덮이지 않아 안쪽을 가득 채우는 inset
그림자로 가리고, 글자색은 `color`가 먹지 않아 `-webkit-text-fill-color`로 맞춘다.
- 처음엔 표준(`:autofill`)과 접두사(`:-webkit-autofill`)를 따로 적었으나, Lightning CSS가
browserslist 타깃을 보고 접두사를 표준형으로 합쳐 같은 규칙이 두 번 나오기만 했다
(컴파일 결과 8건 → 정리 후 4건). 표준 하나만 남겼다.
적용 범위 — 자체 ``을 가진 fox-input · fox-text-area · fox-certified ·
fox-phone-number 넷에 직접 붙고, fox-email · fox-address · fox-file-upload는 내부에서
fox-input을 조립하므로 `.fox-input__field` 규칙으로 함께 덮인다. 즉 @fox의 모든 텍스트
입력이 커버된다.
Co-Authored-By: Claude Opus 5
디자인시스템 파일의 tab(391:4433)을 다시 보니 fill lg가 낱개 탭이 아니라 세그먼트
컨트롤이었다. 목록이 테두리·둥근 모서리·흰 바탕을 갖고 항목이 그 안에 갇힌다.
`overflow: clip`이 있어야 끝 칸의 채운 색이 둥근 모서리를 넘지 않는다.
칸 사이 구분선은 고르지 않은 칸이 자기 오른쪽에 그린다 — 고른 칸은 색이 바뀌어 경계가
이미 보이므로 선을 두지 않는다(시안 그대로). 어느 칸이 골라져도 선이 맞아떨어진다.
fill md는 낱개 알약이 맞아 그대로 둔다.
고른 탭에 hover·pressed가 걸리던 것도 고쳤다. 규칙 특이도가 0,5,0이라 고름 규칙(0,2,0)을
이겨서, 고른 탭에 마우스를 올리면 남색·파란 밑줄이 옅은 파랑으로 덮였다.
검증: 목록 테두리 1px #cdd1d5 · 모서리 12px · clip · 흰 바탕, 구분선이 고르지 않은
칸에만 붙는 것을 확인. hover 규칙이 고른 탭 6개 어디에도 걸리지 않는다.
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
2단계 줄에 `-`와 점이 함께 나왔다. 항목이 항목을 품는 구조인데 단계별 규칙을 후손
선택자로 적어, `.--lv1 .__marker`가 그 안에 중첩된 2·3단계 마커에도 걸렸다.
단계별 규칙을 자식 결합자로 바꿔 자기 줄만 고르게 했다.
`.--lv1 > .__row > .__marker`
카탈로그의 34개 항목을 전부 훑어 글자와 도형이 함께 있는 줄이 없음을 확인했고,
여섯 조합이 시안대로 하나씩만 나온다 — 점 6px / `-` / 빈 원 4px / `1.` / `a.` / `①`.
시안 대조 20항목도 그대로 통과한다.
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
시안의 `ico/*`는 Phosphor Icons를 그대로 올린 것이다. `ico/House` Regular와 Phosphor
`house.svg`는 문 너비 16.67%·문 span 25%·벽 두께 8.33%가 전부 같고, 프레임 구조도
`Format=Stroke|Outline × Weight=Thin|Light|Regular|Bold|Fill|Duotone`으로 Phosphor의
weight 체계 그대로다. 그래서 Figma를 1,512번 호출하는 대신 원본에서 생성했다.
- @fox/tools/build-icons.py 신설. build-tokens.py와 같은 자리·같은 철학(Figma가 SSOT이고
생성물은 재현 가능)이다. 실행 1.4초.
- core/icons/glyphs/*.tsx 1,512개 + fox-icon.tsx(공용 껍데기) + index.ts 배럴.
기존 icons.tsx(8종 수기 이식)는 이 안에 포함되므로 삭제했다.
- 굵기를 이름이 아니라 `weight` prop으로 바꿨다. 시안의 `Weight=` 변형과 1:1이다.
FoxCaretLeftBoldIcon → .
- viewBox를 Phosphor 원본의 256으로 통일했다. 기존 8종은 Figma export라 16/24 박스에
translate가 붙어 있었지만 같은 그림을 축소한 것이다(House bbox x=32 → 32/256×16=2로
기존 translate와 일치).
검증 — 기존 8종과 새 생성물을 256×256으로 래스터화해 픽셀 비교했다. 차이가 잉크의 0.33%
이하(안티에일리어싱 경계)였고 Pause는 0픽셀이었다. weight 매핑이 유일한지도 오답을 넣어
확인했다: Play를 regular로 두면 58.4%, bold면 60.7%, Plus를 regular로 두면 34.0% 어긋난다.
즉 시안의 Play·Pause는 fill, Plus는 bold다 — 이름에 표시가 없어 놓치기 쉬운 지점이라
fox-carousel-button 주석과 README에 명시했다.
번들 — 배럴이 크지만 프로덕션에는 쓰는 글리프만 들어간다(안 쓰는 아이콘의 path 문자열이
프로덕션 .js 어디에도 없음을 확인). build 6초, tsc 3초로 영향 없다.
라이선스 — Phosphor는 MIT다. 사본에 고지를 함께 두도록 요구하므로 생성기가 원본 LICENSE를
core/icons/PHOSPHOR-LICENSE.txt로 옮긴다.
Co-Authored-By: Claude Opus 5
@fox 토큰이 패밀리명을 리터럴 `Pretendard`로 지정하는데 프로젝트 어디에도 그 이름의 폰트
공급이 없었다. 그래서 모든 @fox 컴포넌트가 OS 기본 서체(맥 Apple SD Gothic Neo / 윈도
맑은 고딕)로 갈라져 그려졌고, 글자 폭이 시안과 어긋났다.
- public/fonts: Pretendard v1.3.9 woff2 400/600/800 반입(OFL 1.1). 토큰이 쓰는 weight가
정확히 이 셋이다 — 특히 800은 wantedSans에 없어 가짜 굵기로 합성되고 있었다.
- globals.scss: @font-face로 직접 선언. next/font/local은 패밀리명을 해시로 바꿔 토큰의
리터럴과 만나지 못해 이 용도로는 쓸 수 없다.
- globals.scss: body에 `--app-font-sans` 연결. 이 계약 변수를 읽는 곳이 없어 wantedSans가
로드만 되고 어디에도 적용되지 않았다. 이제 @fox 밖 텍스트가 wantedSans를 쓴다.
- next.config.ts: `/fonts/*`를 no-store에서 제외. public/ 자산이라 그대로 두면 페이지를
열 때마다 2.2MB를 다시 받는다.
- proxy.ts: 폰트 확장자를 matcher에서 제외. 세션 없는 방문자의 폰트 요청이 `/login`으로
리다이렉트돼 로그인 화면만 대체 서체로 그려졌다.
- @fox/README.md: "호스트 앱과의 계약"이 @fox 컴포넌트도 `--app-font-sans`를 읽는 것처럼
적혀 있었으나 사실이 아니다. 실제 요구사항(Pretendard @font-face·400/600/800)으로 교체.
FoxCarouselIndicator number형 56.92px → 59.69px. 시안 62px과의 잔여 2.31px은 Figma가 텍스트
상자 폭을 정수로 올리고(+1.57) 마지막 글자의 음수 자간을 빼지 않는(+0.75) 표기 차이다.
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
FoxInput·TextArea·PhoneNumber·Certified·Email·Address·FileUpload가 라벨·
헬퍼·입력 상자·상태 규칙을 같은 값으로 되풀이하고 있었다. 시안에서 이들이 모두
같은 공용 컴포넌트라 값이 갈라질 이유가 없다.
- @fox/styles/_form-field.scss 추가 — root·label·message·message-icon·
box·box-size·field·auto-states·fixed-states
- 믹스인만 있어 @use 해도 CSS가 늘지 않는다(파티셜 단독·묶음 모두 확인)
- 컴포넌트마다 다른 것만 인자로: 상자 gap·교차정렬, 알맹이 글자 크기·줄높이,
알맹이 element 이름(field/segment), 오류 판정 범위
- PhoneNumber unit은 조각이 다른 컴포넌트라 헬퍼 오류를 넓게 본다 —
$invalid-within으로 그대로 유지
값은 하나도 바꾸지 않았다. 컴파일 결과를 규칙 단위로 대조해 304개 규칙의
선택자·선언·순서가 모두 동일함을 확인했다(선언 순서만 5개 규칙에서 다르며
서로 다른 속성이라 렌더링 동일).
Co-Authored-By: Claude Opus 5
세로 배치의 교차정렬이 기본값 stretch라 버튼이 오른쪽 칸 폭(567px)만큼
늘어났다. 시안(txt-area)은 items-start이고 버튼은 shrink-0이다.
- align-items: flex-start 추가 — 버튼이 글자만큼만 차지한다
- 설명에 inline-size: 100% — 시안 min-w-full대로 폭을 채운다
Co-Authored-By: Claude Opus 5
`로 그리면서 브라우저 기본값을 지우지 않아 왼쪽에 40px
(padding-inline-start)과 위아래 10px(margin-block)이 붙었다. 우리 SCSS에는
`__list` 패딩 규칙이 없었고 전부 UA 스타일이었다.
같은 자리를 쓰는 FoxSelectOption은 초기화가 되어 있었다 — 이 파일만 빠져 있었다.
Co-Authored-By: Claude Opus 5