File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
08-14
08-14
08-18
08-18
08-18
08-14
08-14
08-14
08-18
08-18
08-14
08-18
// FoxChip — 시안: 통합관리자페이지 디자인시스템 Figma chip
//
// 각진 작은 조각이고 **켜짐 여부를 갖는다** — 이것이 `fox-tag`와의 차이다. 태그는 눌러서
// 사라지거나 어디로 넘어가는 조각이라 상태가 남지 않고, 칩은 누른 결과가 자기 자신에게 남는다.
// 그래서 알약(`radius: max`)이 아니라 폼 요소와 같은 모서리(`form-radius-*`)를 쓴다.
//
// 마크업 계약 (React 밖 소비자용):
// <button type="button" class="fox-chip fox-chip--md fox-chip--check" aria-pressed="false">
// <span class="fox-chip__label">레이블</span>
// </button>
// <button type="button" class="fox-chip fox-chip--md fox-chip--check fox-chip--checked"
// aria-pressed="true">
// <span class="fox-chip__icon">…체크 svg…</span> <!-- 켜졌을 때만 -->
// <span class="fox-chip__label">레이블</span>
// </button>
// <div class="fox-chip-select"> <!-- 목록의 위치 기준 -->
// <button type="button" class="fox-chip fox-chip--lg fox-chip--select" role="combobox"
// aria-haspopup="listbox" aria-expanded="false">
// <span class="fox-chip__icon">…앞 아이콘 svg…</span> <!-- 넘긴 때만 -->
// <span class="fox-chip__label">레이블</span>
// <span class="fox-chip__icon fox-chip__arrow">…화살표 svg…</span>
// </button>
// <ul class="fox-select-option fox-chip-select__list" role="listbox">…</ul> <!-- 열렸을 때만 -->
// </div>
//
// 켜짐을 `aria-pressed`가 아니라 `--checked` 클래스가 표시한다. 계열마다 켜짐의 출처가
// 달라서다 — check·action은 `aria-pressed`를 들지만 select는 `role="combobox"`라 그 속성을
// 쓸 수 없다(고른 값이 있으면 켜진 것으로 본다). 클래스 하나로 모으면 모양 규칙이 한 벌로 끝난다.
//
// 상태 규칙은 계열과 무관하게 같다 — 계열이 정하는 것은 아이콘의 유무와 자리뿐이고, 아이콘이
// 앞이냐 뒤냐는 **마크업 순서가 정한다**(`fox-tag`와 같은 방식. `order`나 `row-reverse`를 쓰지
// 않는다).
//
// ⚠️ hover는 꺼져 있을 때만 걸린다. 시안이 켜짐 위의 hover를 그리지 않았고, 임의로 만들면
// 남색 채움이 눌린 채로 흔들린다. 선택자에 `:not(.fox-chip--checked)`를 박아 못박는다.
//
// ⚠️ 비활성은 켜짐 여부와 무관하게 한 벌이다(시안 확인 완료 — 비활성+꺼짐 시안이 따로 없다).
// 체크 아이콘은 계속 `--checked`가 정하므로 비활성+켜짐이면 흐린 회색 체크가 남는다.
//
// 크기가 바꾸는 것: 높이·좌우 여백·모서리·글자 크기·아이콘 크기. gap은 두 크기가 같다(`gap-2`).
// 아이콘만 크기를 따라가는 점이 `fox-tag`와 다르다 — 태그는 세 크기가 모두 16px이지만
// 시안의 칩은 md 16px(`icon-3`) / lg 20px(`icon-4`)다.
//
// 토큰이 아닌 값(`fox-button`·`fox-tag`와 동일한 근거로 승인된 항목):
// - line-height / letter-spacing: 시안 텍스트 스타일 label/*의 값이나 Figma가 변수로
// export하지 않는다. 두 크기가 모두 `-0.025em`이라 규칙 하나로 끝난다(md는 label/md
// 15px→-0.375px, lg는 label/lg 17px→-0.425px — 시안 확인 완료). px이 아니라 em인 이유는
// 글자 크기가 크기 수식어마다, 그리고 브레이크포인트마다 달라지기 때문이다 — px으로
// 박으면 그 자리에서 비율이 어긋난다.
// - box-sizing / display / flex / cursor / white-space / aspect-ratio: 구조·상호작용 규칙.
// - `--checked`의 `border-color: transparent`: 시안이 켜짐에 테두리를 주지 않는다. 테두리를
// 지우는 대신 투명하게 두는 이유는 배경이 그 자리까지 칠해져 모양은 같으면서, 높이·좌우
// 여백이 계열·상태에 상관없이 한 값으로 유지되기 때문이다.
// - `fox-chip-select__list`의 위치·폭: 구조 규칙(아래 주석 참고).
@use "@fox/styles/abstracts" as fox;
.fox-chip {
// 안쪽 여백과 테두리가 바깥 크기를 늘리지 않게 한다 — 고정 높이가 흔들리지 않는다.
box-sizing: border-box;
display: inline-flex;
align-items: center;
border: fox.border(1) solid fox.color(border-neutral-default);
background: fox.color(action-default);
cursor: pointer;
color: fox.color(font-neutral-default);
font-family: fox.font-family(body);
font-weight: fox.font-weight(regular);
line-height: 1;
letter-spacing: -0.025em;
white-space: nowrap;
// 글자만 줄어들게 해 아이콘이 밀리지 않게 한다.
&__label {
min-inline-size: 0;
}
&__icon {
display: inline-flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
// 시안이 아이콘에 정사각 비율을 못박는다. 자리가 좁아 눌려도 글리프가 찌그러지지 않는다.
aspect-ratio: 1 / 1;
}
// 넘긴 SVG가 슬롯을 꽉 채운다 — 크기는 슬롯이 정하고 SVG는 따라온다.
&__icon > * {
inline-size: 100%;
block-size: 100%;
}
// 화살표만 글자보다 한 단계 흐리다(시안 #6D7882 = font-neutral-disabled-strong). 기본
// 상태에서만이다 — 아래 상태 규칙이 색을 물려주므로 남색 배경 위에서 회색으로 남지 않는다.
&__arrow {
color: fox.color(font-neutral-disabled-strong);
}
// ── 크기 ────────────────────────────────────────────────────────────────────
&--lg {
block-size: fox.form(height-md);
padding-inline: fox.padding(4);
gap: fox.gap(2);
border-radius: fox.form(radius-md);
font-size: fox.font-size(label-lg);
}
&--lg &__icon {
inline-size: fox.icon(4);
block-size: fox.icon(4);
}
&--md {
block-size: fox.form(height-sm);
padding-inline: fox.padding(3);
gap: fox.gap(2);
border-radius: fox.form(radius-xsm);
font-size: fox.font-size(label-md);
}
&--md &__icon {
inline-size: fox.icon(3);
block-size: fox.icon(3);
}
// ── 상태 ────────────────────────────────────────────────────────────────────
// 순서가 규칙이다: hover → checked → disabled. 뒤에 오는 것이 이긴다.
// 꺼져 있을 때만 걸린다(파일 상단 ⚠️ 참고).
&:not(&--checked):hover:not(:disabled) {
border-color: fox.color(border-theme-secondary);
background: fox.color(action-secondary-hover);
color: fox.color(font-theme-secondary);
}
&--checked {
// 배경이 테두리 자리까지 칠해져 모양은 테두리 없는 것과 같다(상단 승인 항목 참고).
border-color: transparent;
background: fox.color(action-secondary-active);
color: fox.color(font-neutral-static-inverse);
}
&:disabled {
border-color: fox.color(border-neutral-disabled);
background: fox.color(element-neutral-disabled);
color: fox.color(font-neutral-disabled-strong);
cursor: default;
}
// 화살표는 기본 상태에서만 흐리고, 상태가 바뀌면 글자 색을 그대로 따라간다.
&:not(&--checked):hover:not(:disabled) &__arrow,
&--checked &__arrow,
&:disabled &__arrow {
color: inherit;
}
}
// select 계열을 감싸는 자리. `fox-select-option`이 `position: absolute`라 위치 기준이 되는
// 조상이 필요하다(`fox-select__control`과 같은 역할).
//
// `inline-block`인 이유는 칩이 글자만큼만 넓어야 하기 때문이다 — 블록이면 줄 전체를 차지해
// 칩 여러 개를 나란히 놓을 수 없다.
.fox-chip-select {
position: relative;
display: inline-block;
}
// 목록의 폭·위치 규칙은 여기에 없다 — `fox-chip-select-option`이 280px 고정 폭을 스스로
// 갖는다. 예전에는 `fox-select-option`을 빌려 쓰면서 폭을 여기서 덮어썼는데, 전용 목록이
// 생기면서 그 덮어쓰기가 필요 없어졌다.