File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
// ─────────────────────────────────────────────────────────────────────────────
// 믹스인 — 여러 선언이 항상 함께 가야 하는 패턴을 한 줄로 묶는다.
//
// 함수(`_functions.scss`)가 "값 하나"를 강제한다면, 믹스인은 "값들의 조합"을
// 강제한다. 타이포 4종(크기·행간·굵기·자간)이나 감소 모션 분기처럼 하나만 빠져도
// 어긋나는 것들을 화면마다 되풀이해 적지 않게 만드는 것이 목적이다.
// ─────────────────────────────────────────────────────────────────────────────
@use "sass:map";
@use "tokens";
@use "functions" as fn;
/// 타이포 프리셋 적용 — 크기·행간·굵기·자간 4종을 한 번에.
/// 개별 스칼라를 따로 고르지 못하게 해서 타이포 조합이 무한히 갈라지는 것을 막는다.
@mixin typo($name) {
@if not map.has-key(tokens.$typo, $name) {
@error "[@fox] 알 수 없는 typo 프리셋: `#{$name}` — 사용 가능한 값: #{map.keys(tokens.$typo)}";
}
font-size: var(--fox-typo-#{$name}-size);
font-weight: var(--fox-typo-#{$name}-weight);
line-height: var(--fox-typo-#{$name}-line-height);
letter-spacing: var(--fox-typo-#{$name}-letter-spacing);
}
/// 지정 브레이크포인트 **이상**에서 적용 (모바일 우선).
@mixin media($name) {
@media (min-width: fn.bp($name)) {
@content;
}
}
/// 지정 브레이크포인트 **미만**에서 적용.
/// `media()`와 경계가 겹치지 않도록 0.01rem을 뺀다 — 같은 뷰포트 폭에서 두 블록이
/// 동시에 적용되면 나중 선언이 이기는 미묘한 버그가 생긴다.
@mixin media-below($name) {
@media (max-width: fn.bp($name) - 0.01rem) {
@content;
}
}
/// 트랜지션 — `prefers-reduced-motion: no-preference`로 감싸 감소 모션 환경에서는
/// 아예 선언되지 않게 한다(값을 0으로 만드는 것보다 확실하다).
/// @param {List} $properties - 전환할 속성. 여러 개면 괄호로 묶어 전달한다.
@mixin transition($properties, $duration: base, $easing: out) {
@media (prefers-reduced-motion: no-preference) {
transition-property: $properties;
transition-duration: fn.duration($duration);
transition-timing-function: fn.easing($easing);
}
}
/// 키프레임 애니메이션 — 감소 모션 분기는 `transition()`과 동일하게 내장.
@mixin animate($name, $duration: base, $easing: out, $fill: both) {
@media (prefers-reduced-motion: no-preference) {
animation: $name fn.duration($duration) fn.easing($easing) $fill;
}
}
/// 키보드 포커스 링 — `:focus-visible`만 대상으로 하므로 마우스 클릭 시에는
/// 나타나지 않는다. 모든 상호작용 요소에 반드시 넣는다(접근성).
@mixin focus-ring($color: null, $width: 0.2rem, $offset: 0.2rem) {
$ring-color: fn.color(primary);
@if $color != null {
$ring-color: $color;
}
&:focus-visible {
outline: $width solid $ring-color;
outline-offset: $offset;
}
}
/// 화면에는 보이지 않지만 스크린리더는 읽는 텍스트.
/// `display: none`이나 `visibility: hidden`은 보조기기에서도 사라지므로 쓰지 않는다.
@mixin visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
/// 말줄임 — 1줄이면 `text-overflow`, 여러 줄이면 line-clamp.
@mixin truncate($lines: 1) {
overflow: hidden;
@if $lines == 1 {
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $lines;
}
}
/// 페이지 콘텐츠 가로 폭 제한 + 좌우 여백.
@mixin container($max: xl, $gutter: 4) {
width: 100%;
max-width: fn.bp($max);
margin-inline: auto;
padding-inline: fn.space($gutter);
}