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
// FoxToggleSwitch — 시안: 통합관리자페이지 디자인시스템(KE9UILWhB8qTBe14uXUn4F) Figma toggle-switch
//
// 켜짐/꺼짐을 바로 바꾸는 스위치다 — 트랙(배경) + 동그라미(thumb) + 글자.
//
// 마크업 계약 (React 밖 소비자용):
// <label class="fox-toggle-switch fox-toggle-switch--lg">
// <input type="checkbox" role="switch" class="fox-toggle-switch__input" name="alarm">
// <span class="fox-toggle-switch__track">
// <span class="fox-toggle-switch__thumb">
// <svg class="fox-toggle-switch__mark-off">…X…</svg>
// <svg class="fox-toggle-switch__mark-on">…체크…</svg>
// </span>
// </span>
// <span class="fox-toggle-switch__label">알림 받기</span> <!-- 선택 -->
// </label>
//
// 루트가 `<label>`이라 글자를 눌러도 바뀐다. 상태는 전부 네이티브 `<input type="checkbox">`가
// 소유한다(규약 3) — `role="switch"`만 얹어 체크박스가 아니라 스위치로 읽히게 한다.
//
// input은 순서상 **맨 앞**에 와야 한다. 트랙과 글자가 `~` 형제 선택자로 그 상태를 따라간다.
// 시각적으로만 숨기고 포커스는 살려 둔다 — `display: none`으로 지우면 키보드로 도달할 수 없다.
//
// 표시 둘(X·체크)을 **항상 마크업에 둔다.** 켜졌을 때만 넣는 게 아니라 CSS가 보이기/숨기기를
// 정한다 — 그래야 비제어 입력에서도(React가 켜짐 여부를 모르는 상태에서도) 모양이 맞는다.
// `fox-check-icon`과 같은 처리다.
//
// 동그라미는 off일 때 왼쪽, on일 때 오른쪽이다. 트랙이 `display: flex`라 기본이 왼쪽이고,
// 켜지면 `margin-inline-start: auto`가 오른쪽으로 밀어낸다 — 이동 거리를 계산해 박지 않으므로
// 트랙 폭이나 동그라미 크기가 바뀌어도 따라온다. 시안에 모션이 없고 `@fox` 전체에
// `transition`을 쓰는 곳이 없어 위치만 바뀐다.
//
// **표시 색은 언제나 트랙 배경과 같은 값이다** — off는 `element-neutral-default`, on은
// `element-theme-primary`, 비활성은 `element-neutral-disabled`. 흰 동그라미에 트랙 색으로
// 글리프를 파낸 모양이다. 그래서 트랙 규칙이 `background`와 `color`를 항상 함께 주고, 표시는
// `currentColor`로 그려 물려받는다 — 상태별로 색을 두 군데 적지 않는다.
//
// 크기가 바꾸는 것은 트랙 폭·동그라미 지름·표시 크기·글자 크기 넷이다. 안쪽 여백(`padding-1`)과
// 바깥 간격(`gap-3`)은 두 크기가 같다.
//
// 트랙 동그라미 표시 글자
// lg 40px 20px 16px label-lg
// md 32px 16px 12px label-md
//
// 트랙 높이는 값으로 주지 않는다 — 여백(2px×2) + 동그라미가 곧 높이다. 그래서 lg 24px,
// md 20px이 자동으로 나오고, 동그라미 이동 거리도 (트랙 폭 − 여백 − 동그라미)로 따라온다.
//
//
// ⚠️ 포커스 표시는 시안에 없다. 네이티브 input을 시각적으로 숨기므로 브라우저 기본 링도
// 사라져서, 키보드 사용자를 위해 이 파일에서 만들어 준다(`fox-check-icon`과 같은 처리).
//
// 토큰이 아닌 값(사용자 승인):
// - line-height / letter-spacing: 시안 텍스트 스타일 label/*의 값이나 Figma가 변수로
// export하지 않는다. 두 크기가 같은 `-0.025em`이라 규칙 하나로 끝난다(17px→-0.425px,
// 15px→-0.375px). px이 아니라 em인 이유는 글자 크기가 크기 수식어마다, 그리고
// 브레이크포인트마다 달라지기 때문이다 — px으로 박으면 그 자리에서 비율이 어긋난다.
// - 트랙 폭 `4rem`·`3.2rem`(40·32px) / 동그라미 `2rem`·`1.6rem`(20·16px): 시안이 이 넷만
// 변수 없이 값으로 준다(표시 크기는 `--icon-3`으로 바인딩되어 있어 `fox.icon(*)`을 쓴다).
// 스위치 몸통 치수라 값이 같은 `icon-*`을 끌어다 쓰면 뜻이 어긋나서 값으로 둔다
// (1rem = 10px).
// - box-sizing: 안쪽 여백이 트랙 40px 안에 들어가야 한다. 없으면 44px이 된다.
// - display / flex / cursor / aspect-ratio: 구조·상호작용 규칙.
// - 숨긴 input의 `inline-size: 0`: 크기가 아니라 "보이지 않게" 하는 구조 값이다.
//
// 동그라미의 안쪽 여백(`padding: 6px 5px`)·`gap: 10px`·`flex-direction: column`은 쓰지 않는다.
// Figma auto-layout이 남긴 값이고 **그림에 영향을 주지 않는다** — 실제로 넣어 보고 확인했다.
// 안쪽 상자가 10×8px(md는 6×4px)로 표시(16px·12px)보다 작지만 `flex-shrink: 0`이라 표시가 줄지
// 않고, 여백이 좌우·상하 대칭이어서 안쪽 상자의 중심이 동그라미 중심과 같다 — 넘친 만큼 양쪽으로
// 고르게 삐져나와 가운데 정렬과 같은 자리에 놓인다. `gap`은 자식이 하나뿐이라, `flex-direction`도
// 자식이 하나라 각각 계산에 참여하지 않는다.
//
// 게다가 넣으려면 `box-sizing: border-box`가 딸려 와야 한다 — 이 프로젝트에 전역 리셋이 없어
// 그것 없이 여백만 주면 지름이 안쪽 크기가 되어 동그라미가 30×32px 타원으로 부푼다. 결과가 같고
// 규칙이 다섯 줄 늘어날 뿐이라 가운데 정렬만 남긴다.
@use "@fox/styles/abstracts" as fox;
.fox-toggle-switch {
// 숨긴 input의 위치 기준. 안 주면 input이 조상까지 올라가 엉뚱한 곳에 붙는다.
position: relative;
display: inline-flex;
align-items: center;
// 크기와 무관하게 같은 값이다(시안).
gap: fox.gap(3);
cursor: pointer;
// 시각적으로만 숨긴다. `opacity: 0`은 포커스를 유지하므로 키보드로 도달할 수 있고,
// 클릭은 루트 `<label>`이 받는다.
&__input {
position: absolute;
inline-size: 0;
block-size: 0;
margin: 0;
opacity: 0;
pointer-events: none;
}
// 트랙. `background`와 `color`를 항상 함께 준다 — `color`는 안쪽 표시가 물려받는 값이다.
// 폭은 크기 수식어가 정하고, 높이는 여백 + 동그라미가 정한다.
&__track {
// 안쪽 여백이 트랙 폭 안에 들어가야 한다. 없으면 44px·36px이 된다.
box-sizing: border-box;
display: flex;
align-items: center;
flex-shrink: 0;
padding: fox.padding(1);
border-radius: fox.radius(max);
background: fox.color(element-neutral-default);
color: fox.color(element-neutral-default);
}
// 동그라미. 지름은 크기 수식어가 정한다 — 그 값이 트랙 높이까지 정한다.
&__thumb {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
border-radius: fox.radius(max);
background: fox.color(element-neutral-inverse);
}
// 표시 둘. 보이기는 상태가, 크기는 크기 수식어가 정한다. 색은 `currentColor`라 트랙이 정한
// 값이다.
&__mark-off,
&__mark-on {
flex-shrink: 0;
// 시안이 표시에 정사각 비율을 못박는다. 자리가 좁아 눌려도 글리프가 찌그러지지 않는다.
aspect-ratio: 1 / 1;
}
// 꺼짐이 기본이라 X만 보인다.
&__mark-on {
display: none;
}
&__label {
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;
}
// ── 크기 ────────────────────────────────────────────────────────────────────
// 트랙 40px · 동그라미 20px(→ 트랙 높이 24px) · 표시 16px.
&--lg &__track {
inline-size: 4rem;
}
&--lg &__thumb {
inline-size: 2rem;
block-size: 2rem;
}
&--lg &__mark-off,
&--lg &__mark-on {
inline-size: fox.icon(3);
block-size: fox.icon(3);
}
&--lg &__label {
font-size: fox.font-size(label-lg);
}
// 트랙 32px · 동그라미 16px(→ 트랙 높이 20px) · 표시 12px.
&--md &__track {
inline-size: 3.2rem;
}
&--md &__thumb {
inline-size: 1.6rem;
block-size: 1.6rem;
}
&--md &__mark-off,
&--md &__mark-on {
inline-size: fox.icon(2);
block-size: fox.icon(2);
}
&--md &__label {
font-size: fox.font-size(label-md);
}
}
// ── 켜짐 ──────────────────────────────────────────────────────────────────────
.fox-toggle-switch__input:checked ~ .fox-toggle-switch__track {
background: fox.color(element-theme-primary);
color: fox.color(element-theme-primary);
}
// 동그라미가 오른쪽으로 간다.
.fox-toggle-switch__input:checked ~ .fox-toggle-switch__track .fox-toggle-switch__thumb {
margin-inline-start: auto;
}
.fox-toggle-switch__input:checked ~ .fox-toggle-switch__track .fox-toggle-switch__mark-off {
display: none;
}
.fox-toggle-switch__input:checked ~ .fox-toggle-switch__track .fox-toggle-switch__mark-on {
display: block;
}
// ── 비활성 ────────────────────────────────────────────────────────────────────
// 켜짐 규칙보다 뒤에 와야 이긴다 — 비활성이면 켜짐 여부와 무관하게 트랙·동그라미 색이 같다.
// 표시와 동그라미 위치는 건드리지 않아 켜짐 여부가 그대로 보인다.
.fox-toggle-switch:has(.fox-toggle-switch__input:disabled) {
cursor: default;
}
.fox-toggle-switch__input:disabled ~ .fox-toggle-switch__track {
background: fox.color(element-neutral-disabled);
color: fox.color(element-neutral-disabled);
}
.fox-toggle-switch__input:disabled ~ .fox-toggle-switch__track .fox-toggle-switch__thumb {
background: fox.color(element-neutral-disabled-strong);
}
.fox-toggle-switch__input:disabled ~ .fox-toggle-switch__label {
color: fox.color(font-neutral-disabled);
}
// ── 포커스 ────────────────────────────────────────────────────────────────────
// input이 시각적으로 숨겨져 브라우저 기본 링이 보이지 않으므로 여기서 대신 그린다.
.fox-toggle-switch__input:focus-visible ~ .fox-toggle-switch__track {
outline: fox.border(2) solid fox.color(border-theme-primary);
outline-offset: fox.border(1);
}