File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
// ─────────────────────────────────────────────────────────────────────────────
// 토큰 CSS 출력 — `tokens/` 의 SCSS map을 `--fox-*` 커스텀 프로퍼티로 선언한다.
//
// **이 파일은 앱 전체에서 딱 한 번만 로드되어야 한다** (`index.scss` 경유).
// 컴포넌트의 `.module.scss`에서는 절대 `@use` 하지 않는다 — CSS Module은 파일마다
// 별도 컴파일 단위여서, 컴포넌트마다 `:root` 블록 전체가 복제된다.
//
// map을 순회해 생성하므로 토큰을 추가할 때 이 파일은 손대지 않는다. 값의 이름·개수는
// 언제나 `tokens/` 쪽 map 하나에만 존재한다.
// ─────────────────────────────────────────────────────────────────────────────
@use "sass:map";
@use "tokens";
:root {
// 기본은 OS 설정 추종. 아래 `[data-theme]` 규칙이 이 값만 덮어써 수동 선택을
// 처리하므로, 색상 값 자체는 `tokens/_color.scss` 한 곳에만 존재한다.
color-scheme: light dark;
@each $name, $value in tokens.$color {
--fox-color-#{$name}: #{$value};
}
@each $name, $value in tokens.$space {
--fox-space-#{$name}: #{$value};
}
@each $name, $value in tokens.$radius {
--fox-radius-#{$name}: #{$value};
}
@each $name, $value in tokens.$shadow {
--fox-shadow-#{$name}: #{$value};
}
@each $name, $value in tokens.$duration {
--fox-duration-#{$name}: #{$value};
}
@each $name, $value in tokens.$easing {
--fox-easing-#{$name}: #{$value};
}
@each $name, $value in tokens.$font-family {
--fox-font-#{$name}: #{$value};
}
@each $name, $value in tokens.$font-weight {
--fox-weight-#{$name}: #{$value};
}
@each $name, $value in tokens.$z {
--fox-z-#{$name}: #{$value};
}
// 브레이크포인트는 **미디어 쿼리에서 쓸 수 없다** — 조건부는 `var()`를 해석하지
// 않는다. 그럼에도 내보내는 이유는 컨테이너 `max-width`처럼 선언부에서 같은 값을
// 참조할 일이 있고, 카탈로그가 토큰 목록을 CSSOM에서 읽기 때문이다.
// 미디어 쿼리는 언제나 `fox.media()` 믹스인을 쓴다.
@each $name, $value in tokens.$breakpoint {
--fox-breakpoint-#{$name}: #{$value};
}
// 타이포 프리셋은 한 이름당 4개의 프로퍼티로 펼쳐진다 — `fox.typo()` 믹스인이
// 이 네 개를 함께 참조한다.
@each $name, $preset in tokens.$typo {
--fox-typo-#{$name}-size: #{map.get($preset, size)};
--fox-typo-#{$name}-weight: #{map.get($preset, weight)};
--fox-typo-#{$name}-line-height: #{map.get($preset, line-height)};
--fox-typo-#{$name}-letter-spacing: #{map.get($preset, letter-spacing)};
}
}
// 수동 테마 선택 — `color-scheme`만 고정하면 `light-dark()`가 해당 분기로 확정된다.
// 색상 값을 여기서 재정의하지 않으므로 라이트/다크 값이 어긋날 수 없다.
// 속성이 없거나 `"system"`이면 위 `:root`의 `light dark`가 유지되어 OS를 따른다.
:root[data-theme="light"] {
color-scheme: light;
}
:root[data-theme="dark"] {
color-scheme: dark;
}
// 테마 전환 애니메이션 — 호스트 앱이 토글 직전 `<html>`에 `[data-theme-transition]`을
// 부여하고 ~200ms 후 제거하는 **시간 한정 opt-in**이다. 상시 트랜지션이 아니므로
// 다른 hover 트랜지션과 경쟁하지 않는다.
// `prefers-reduced-motion` 게이팅으로 감소 모션 환경에서는 즉시 반영된다.
// `!important`는 이 200ms 동안만 존재하는 규칙이 컴포넌트의 transition 선언에
// 밀리지 않게 하기 위한 것이다.
@media (prefers-reduced-motion: no-preference) {
html[data-theme-transition],
html[data-theme-transition] *,
html[data-theme-transition] *::before,
html[data-theme-transition] *::after {
transition:
background-color var(--fox-duration-base) var(--fox-easing-out),
border-color var(--fox-duration-base) var(--fox-easing-out),
color var(--fox-duration-base) var(--fox-easing-out),
fill var(--fox-duration-base) var(--fox-easing-out),
stroke var(--fox-duration-base) var(--fox-easing-out),
outline-color var(--fox-duration-base) var(--fox-easing-out) !important;
}
}