임동욱 임동욱 08-26
feat: 스크롤바 디자인을 디자인시스템에 등록
시안 scroll(7341:2675)이다. 컴포넌트가 아니라 스크롤이 생기는 요소에 입히는
외형이라 `fox.scroll()` 믹스인과 `.fox-scroll` 클래스로 냈다.

둘레 4px 여백은 트랙 padding이 아니라 막대의 투명 테두리로 만든다 — padding으로
주면 막대가 트랙 끝까지 가지 못한다. Chrome 121+가 표준 속성을 보면 웹킷 규칙을
통째로 무시하므로 Firefox 대비는 @supports로 갈라 놓았다.

이미 스크롤이 있던 @fox 컴포넌트 넷(셀렉트 목록·칩 셀렉트·모달·파일목록)에 적용했다.

Co-Authored-By: Claude Opus 5 
@1297aee5d704d281fc5497a2f755eff0a43c4f96
@fox/dev-test/component-groups.ts
--- @fox/dev-test/component-groups.ts
+++ @fox/dev-test/component-groups.ts
@@ -14,6 +14,11 @@
     label: "Icon",
     subgroups: [{ label: "Icon", ids: ["fox-ico"] }],
   },
+  // 스크롤바도 시안 섹션이 아니라 모든 화면이 쓰는 기반이라 앞쪽에 둔다.
+  {
+    label: "Scroll",
+    subgroups: [{ label: "Scroll", ids: ["fox-scroll"] }],
+  },
   {
     label: "Action",
     subgroups: [
@fox/dev-test/component-registry.tsx
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
@@ -900,6 +900,42 @@
     ],
   },
   {
+    id: "fox-scroll",
+    name: "fox-scroll (스크롤바)",
+    description:
+      "Figma 시안(scroll 7341:2675)을 옮겼습니다. React 컴포넌트가 아니라 스크롤이 생기는 요소에 붙이는 클래스입니다 — <div class=\"fox-scroll\"> 처럼요. 넘침 여부는 정하지 않고 외형만 입힙니다. 막대는 4px이고 둘레 4px 여백은 막대의 투명 테두리로 만듭니다(트랙에 padding을 주면 막대가 끝까지 가지 못합니다). 컴포넌트 안에서는 클래스 대신 fox.scroll() 믹스인을 부르며, 이미 셀렉트 목록·칩 셀렉트·모달·파일목록 네 곳에 들어가 있습니다. ⚠️ Firefox는 ::-webkit-scrollbar가 없어 표준 속성으로 근사합니다 — 두께가 thin 한 단계뿐이고 모서리는 정할 수 없습니다.",
+    variants: [
+      {
+        label: "세로 (시안 col) — 상자 안을 굴려 보세요",
+        node: (
+          <div className={`fox-scroll ${styles.scrollDemo}`}>
+            {Array.from({ length: 12 }, (_, index) => (
+              <p key={index}>스크롤 내용 {index + 1}번째 줄입니다.</p>
+            ))}
+          </div>
+        ),
+      },
+      {
+        label: "가로 (시안 row)",
+        node: (
+          <div className={`fox-scroll ${styles.scrollDemoRow}`}>
+            <p>가로로 넘치는 내용입니다. 오른쪽 끝까지 굴려 보세요.</p>
+          </div>
+        ),
+      },
+      {
+        label: "붙이지 않았을 때 — 브라우저 기본 스크롤바",
+        node: (
+          <div className={styles.scrollDemo}>
+            {Array.from({ length: 12 }, (_, index) => (
+              <p key={index}>스크롤 내용 {index + 1}번째 줄입니다.</p>
+            ))}
+          </div>
+        ),
+      },
+    ],
+  },
+  {
     id: "fox-button",
     name: "FoxButton",
     description:
@fox/dev-test/dev-test.module.scss
--- @fox/dev-test/dev-test.module.scss
+++ @fox/dev-test/dev-test.module.scss
@@ -624,3 +624,29 @@
     color: fox.color(icon-neutral-default);
   }
 }
+
+// 스크롤바를 보이려면 넘치는 상자가 있어야 한다 — 그 상자를 만드는 데모 전용 규칙.
+.scrollDemo {
+  overflow-y: auto;
+  inline-size: 100%;
+  max-inline-size: 32rem;
+  block-size: 16rem;
+  padding: fox.padding(6);
+  border: fox.border(1) solid fox.color(border-neutral-subtle);
+  border-radius: fox.radius(3);
+  background: fox.color(surface-neutral-default);
+}
+
+.scrollDemoRow {
+  overflow-x: auto;
+  inline-size: 100%;
+  max-inline-size: 32rem;
+  padding: fox.padding(6);
+  border: fox.border(1) solid fox.color(border-neutral-subtle);
+  border-radius: fox.radius(3);
+  background: fox.color(surface-neutral-default);
+
+  > p {
+    inline-size: 64rem;
+  }
+}
@fox/styles/_fox-chip-select-option.scss
--- @fox/styles/_fox-chip-select-option.scss
+++ @fox/styles/_fox-chip-select-option.scss
@@ -69,6 +69,7 @@
     // ⚠️ 확정 전 임시값 — 파일 상단 승인 목록 참고.
     max-block-size: 24rem;
     overflow-y: auto;
+    @include fox.scroll;
   }
 
   // 모바일 전용. 판 좌우 끝까지 닿는다(`align-self: stretch`).
@fox/styles/_fox-file-upload.scss
--- @fox/styles/_fox-file-upload.scss
+++ @fox/styles/_fox-file-upload.scss
@@ -187,6 +187,7 @@
     padding: 0;
     overflow-x: clip;
     overflow-y: auto;
+    @include fox.scroll;
     border: fox.border(1) solid fox.color(border-neutral-subtle);
     border-radius: fox.radius(3);
     list-style: none;
@fox/styles/_fox-modal.scss
--- @fox/styles/_fox-modal.scss
+++ @fox/styles/_fox-modal.scss
@@ -52,6 +52,7 @@
   // 없다.
   overflow-x: hidden;
   overflow-y: auto;
+  @include fox.scroll;
   border: fox.border(1) solid fox.color(border-neutral-subtle);
   border-radius: fox.card(radius-xlg);
   background: fox.color(surface-neutral-default);
 
@fox/styles/_fox-scroll.scss (added)
+++ @fox/styles/_fox-scroll.scss
@@ -0,0 +1,14 @@
+// fox-scroll — 시안: 통합관리자페이지 디자인시스템(KE9UILWhB8qTBe14uXUn4F) scroll (7341:2675)
+//
+// 마크업 계약:
+//   <div class="fox-scroll" style="overflow-y: auto">…</div>
+//
+// 외형만 입힌다. 세로(col)·가로(row)를 가리지 않고 그 요소에 생기는 스크롤바 전부가 대상이다.
+// 규칙 본체는 `fox.scroll()` 믹스인이고, 이 클래스는 그것을 마크업에서 바로 쓰게 열어 둔 것이다
+// — 컴포넌트 안에서는 클래스 대신 믹스인을 부른다.
+
+@use "abstracts" as fox;
+
+.fox-scroll {
+  @include fox.scroll;
+}
@fox/styles/_fox-select-option.scss
--- @fox/styles/_fox-select-option.scss
+++ @fox/styles/_fox-select-option.scss
@@ -40,6 +40,7 @@
   // ⚠️ 확정 전 임시값 — 파일 상단 TODO 참고.
   max-block-size: 24rem;
   overflow-y: auto;
+  @include fox.scroll;
   list-style: none;
   border: fox.border(1) solid fox.color(border-neutral-default);
   border-radius: fox.form(radius-md);
@fox/styles/_mixins.scss
--- @fox/styles/_mixins.scss
+++ @fox/styles/_mixins.scss
@@ -1,4 +1,5 @@
 @use "sass:map";
+@use "functions";
 @use "tokens";
 
 /// PC 이상(>= 768px). 미디어 쿼리 조건부는 `var()`를 해석하지 못해 브레이크포인트만
@@ -27,3 +28,38 @@
     @content;
   }
 }
+
+/// 스크롤바 외형 — 시안 `scroll`(7341:2675). 스크롤이 생기는 요소에 붙인다. 넘침 여부는
+/// 정하지 않는다 — `overflow`는 붙이는 쪽이 이미 갖고 있다.
+///
+/// 막대는 4px이고 둘레에 4px 여백이 있다. 여백을 트랙 `padding`으로 주면 막대가 끝까지
+/// 가지 못해, 막대에 투명 테두리를 둘러 안쪽으로만 들어가게 한다.
+///
+/// 웹킷 계열은 `::-webkit-scrollbar`로 시안대로 그리고, 그 의사요소가 없는 Firefox는 표준
+/// 속성으로 근사한다(두께가 `thin` 한 단계뿐이고 모서리는 정할 수 없다). 두 경로를 같이 쓰면
+/// Chrome 121+가 표준 속성을 보고 웹킷 규칙을 통째로 무시하므로 `@supports`로 갈라 놓는다.
+///
+/// 웹킷 의사요소는 표준이 아니라 논리 속성(`inline-size`)을 받지 않아 물리 속성으로 적는다.
+@mixin scroll {
+  &::-webkit-scrollbar {
+    width: calc(#{functions.number(3)} + #{functions.padding(2)} * 2);
+    height: calc(#{functions.number(3)} + #{functions.padding(2)} * 2);
+  }
+
+  &::-webkit-scrollbar-track,
+  &::-webkit-scrollbar-corner {
+    background: transparent;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    border: functions.padding(2) solid transparent;
+    border-radius: functions.radius(max);
+    background-color: functions.color(element-neutral-alpha);
+    background-clip: padding-box;
+  }
+
+  @supports not selector(::-webkit-scrollbar) {
+    scrollbar-width: thin;
+    scrollbar-color: functions.color(element-neutral-alpha) transparent;
+  }
+}
@fox/styles/components.scss
--- @fox/styles/components.scss
+++ @fox/styles/components.scss
@@ -1,6 +1,7 @@
 // 공용 컴포넌트 스타일 묶음. 필요한 것만 쓰려면 개별 파티셜(`@fox/styles/fox-button`)을
 // 직접 @use 한다. 둘을 같이 써도 Sass가 모듈을 한 번만 로드해 중복되지 않는다.
 
+@use "fox-scroll";
 @use "fox-ico";
 @use "fox-button";
 @use "fox-text-button";
Add a comment
List