임동욱 임동욱 08-20
merge: grid 토큰 브레이크포인트 정렬
@e1151695012ac2e447df88a7349c80ac1350d15d
@fox/styles/_mixins.scss
--- @fox/styles/_mixins.scss
+++ @fox/styles/_mixins.scss
@@ -1,3 +1,4 @@
+@use "sass:map";
 @use "tokens";
 
 /// PC 이상(>= 768px). 미디어 쿼리 조건부는 `var()`를 해석하지 못해 브레이크포인트만
@@ -14,3 +15,15 @@
     @content;
   }
 }
+
+/// 이름 붙은 브레이크포인트 이상에서 적용 — `xsm`(768) · `sm`(1024) · `default`(1440).
+/// 같은 이름의 `grid-wrap-*`이 그 폭의 컨테이너다.
+@mixin from($name) {
+  @if not map.has-key(tokens.$breakpoints, $name) {
+    @error "[@fox] 알 수 없는 브레이크포인트: `#{$name}` — 사용 가능한 값: #{map.keys(tokens.$breakpoints)}";
+  }
+
+  @media (min-width: map.get(tokens.$breakpoints, $name)) {
+    @content;
+  }
+}
@fox/styles/tokens/_responsive.scss
--- @fox/styles/tokens/_responsive.scss
+++ @fox/styles/tokens/_responsive.scss
@@ -6,6 +6,13 @@
 // 모바일 우선 — `$responsive`가 기본값이고 `$responsive-pc`가 브레이크포인트 이상에서 덮는다.
 $breakpoint-pc: 768px;
 
+// 컨테이너 폭(`grid-wrap-*`)과 같은 수다 — 미디어 쿼리와 폭이 어긋나지 않게 한 곳에서 낸다.
+$breakpoints: (
+  xsm: 768px,
+  sm: 1024px,
+  default: 1440px,
+);
+
 $responsive: (
   font-size-display-lg: 4.4rem,
   font-size-display-md: 3.2rem,
@@ -41,13 +48,13 @@
   form-height-md: 4rem,
   form-height-lg: 4.8rem,
   grid-wrap-full: 100%,
-  grid-wrap-default: 34.8rem,
+  grid-wrap-default: 36rem,
   grid-wrap-sm: 32rem,
   grid-wrap-xsm: 28rem,
   grid-margin-full: 0,
   grid-margin-default: 1.6rem,
-  grid-margin-sm: 3rem,
-  grid-margin-xsm: 5rem,
+  grid-margin-sm: 1.6rem,
+  grid-margin-xsm: 1.6rem,
   grid-gutter-sm: 0.8rem,
   grid-gutter-md: 1.6rem,
   grid-gutter-lg: 2rem,
@@ -119,13 +126,13 @@
   form-height-md: 4rem,
   form-height-lg: 5.6rem,
   grid-wrap-full: 100%,
-  grid-wrap-default: 155.2rem,
-  grid-wrap-sm: 108rem,
-  grid-wrap-xsm: 80rem,
+  grid-wrap-default: 144rem,
+  grid-wrap-sm: 102.4rem,
+  grid-wrap-xsm: 76.8rem,
   grid-margin-full: 0,
   grid-margin-default: 4.8rem,
-  grid-margin-sm: 42rem,
-  grid-margin-xsm: 56rem,
+  grid-margin-sm: 4.8rem,
+  grid-margin-xsm: 4.8rem,
   grid-gutter-sm: 0.8rem,
   grid-gutter-md: 1.6rem,
   grid-gutter-lg: 2.4rem,
@fox/tools/build-tokens.py
--- @fox/tools/build-tokens.py
+++ @fox/tools/build-tokens.py
@@ -206,16 +206,45 @@
 resp_pc = OrderedDict((name_responsive(p), css_len(n["$value"])) for p, n in raw["pc"].items())
 assert list(resp_mobile) == list(resp_pc)
 
-# Figma 값을 그대로 쓸 수 없는 토큰. 시안의 값은 **아트보드 폭**이라 CSS에서는 뜻이 달라진다.
-# 재export해도 이 치환이 유지되도록 변환기가 들고 있는다(생성 파일을 손으로 고치면 지워진다).
-DEV_OVERRIDES = {
-    # `full`은 "화면을 채운다"는 뜻인데 Figma에서는 아트보드 폭(모바일 380px / PC 1920px)으로
-    # 잡혀 있다. 그대로 쓰면 그보다 좁은 창에서 넘치고 넓은 창에서는 덜 찬다.
+# ── 개발용 치환 ───────────────────────────────────────────────────────────────
+# Figma의 grid 값은 **아트보드 좌표**다. 폭은 아트보드 폭이고 여백은 그 안에서 가운데로 맞추기
+# 위한 offset이라(예: PC sm = (1920-1080)/2 = 420px) CSS에서는 뜻이 달라진다.
+# 재export해도 유지되도록 변환기가 들고 있는다 — 생성 파일을 손으로 고치면 지워진다.
+#
+# 폭은 브레이크포인트와 같은 수를 쓴다. 그래야 "이 폭에서 레이아웃이 갈린다"와 "컨테이너가
+# 여기까지 넓어진다"가 어긋나지 않는다(사용자 확정 사항).
+DEV_OVERRIDES_MOBILE = {
     "grid-wrap-full": "100%",
+    "grid-wrap-default": "36rem",
+    "grid-wrap-sm": "32rem",
+    "grid-wrap-xsm": "28rem",
+    # 페이지 좌우 여백. 컨테이너가 좁아진다고 여백이 넓어지지는 않으므로 한 값을 공유한다.
+    "grid-margin-full": "0",
+    "grid-margin-default": "1.6rem",
+    "grid-margin-sm": "1.6rem",
+    "grid-margin-xsm": "1.6rem",
 }
-for _key, _value in DEV_OVERRIDES.items():
-    resp_mobile[_key] = _value
-    resp_pc[_key] = _value
+DEV_OVERRIDES_PC = {
+    "grid-wrap-full": "100%",
+    "grid-wrap-default": "144rem",
+    "grid-wrap-sm": "102.4rem",
+    "grid-wrap-xsm": "76.8rem",
+    "grid-margin-full": "0",
+    "grid-margin-default": "4.8rem",
+    "grid-margin-sm": "4.8rem",
+    "grid-margin-xsm": "4.8rem",
+}
+resp_mobile.update(DEV_OVERRIDES_MOBILE)
+resp_pc.update(DEV_OVERRIDES_PC)
+
+# 미디어 쿼리는 `var()`를 해석하지 못해 컴파일타임 값이어야 한다. 위 PC 폭과 같은 수다.
+BREAKPOINTS = OrderedDict(
+    (
+        ("xsm", "768px"),
+        ("sm", "1024px"),
+        ("default", "1440px"),
+    )
+)
 
 BREAKPOINT_PC = "768px"
 
@@ -282,6 +311,9 @@
 // 뷰포트에 따라 달라지는 크기 토큰. 키는 두 모드가 완전히 동일하다.
 // 모바일 우선 — `$responsive`가 기본값이고 `$responsive-pc`가 브레이크포인트 이상에서 덮는다.
 $breakpoint-pc: {BREAKPOINT_PC};
+
+// 컨테이너 폭(`grid-wrap-*`)과 같은 수다 — 미디어 쿼리와 폭이 어긋나지 않게 한 곳에서 낸다.
+{scss_map("breakpoints", BREAKPOINTS)}
 
 {scss_map("responsive", resp_mobile)}
 
@@ -433,6 +465,7 @@
 
 written.append(write("_mixins.scss", f"""// 믹스인 — 여러 선언이 항상 함께 가야 하는 패턴을 묶는다.
 
+@use "sass:map";
 @use "tokens";
 
 /// PC 이상(>= {BREAKPOINT_PC})에서 적용. 모바일 우선이므로 기본 스타일은 밖에 쓴다.
@@ -451,6 +484,18 @@
     @content;
   }}
 }}
+
+/// 이름 붙은 브레이크포인트 이상에서 적용 — `xsm`(768) · `sm`(1024) · `default`(1440).
+/// 같은 이름의 `grid-wrap-*`이 그 폭의 컨테이너다.
+@mixin from($name) {{
+  @if not map.has-key(tokens.$breakpoints, $name) {{
+    @error "[@fox] 알 수 없는 브레이크포인트: `#{{$name}}` — 사용 가능한 값: #{{map.keys(tokens.$breakpoints)}}";
+  }}
+
+  @media (min-width: map.get(tokens.$breakpoints, $name)) {{
+    @content;
+  }}
+}}
 """))
 
 written.append(write("abstracts.scss", """// 저작(authoring) 진입점 — 컴포넌트의 `.module.scss`가 `@use`하는 파일.
Add a comment
List