임동욱 임동욱 08-20
feat: 디자인 테스트 페이지 컴포넌트 목록을 3depth로
대분류는 Figma 섹션을 그대로 따랐다 — Action(391:698) · Select(391:2209) · Input(391:3733) ·
Navigation(391:4035) · Layout(392:5035) · Feedback(392:5381) · Help(392:5712).
소분류는 각 대분류 안에서 성격이 같은 것끼리 묶었다.

분류표를 레지스트리와 분리해 component-groups.ts에 뒀다 — 4천 줄짜리 레지스트리를 건드리지 않고,
표에 없는 컴포넌트는 "기타"로 흘려 아무것도 목록에서 사라지지 않게 했다.

검증 — 화면 실측: 대분류 7개, 소분류 20개, 컴포넌트 62개 전부 배치("기타" 없음).
Action 버튼(4)·묶음(2) / Select 라디오(3)·체크박스(3)·셀렉트(4)·태그칩(4)·스위치(4)·부가(2) /
Input 텍스트(2)·형식입력(4)·파일(1) / Navigation 위치이동(2)·캐러셀(3) /
Layout 표목록(2)·탭(1)·배지(3)·펼침(2)·컨테이너(3) / Feedback 알림(4)·상태(3)·단계(2) /
Help 도움말(4).

Co-Authored-By: Claude Opus 5 
@247d95b5783dad14fe14d235333d1656a9e3ce02
 
@fox/dev-test/component-groups.ts (added)
+++ @fox/dev-test/component-groups.ts
@@ -0,0 +1,92 @@
+/**
+ * 대분류는 Figma 섹션을 그대로 따른다.
+ * Action 391:698 · Select 391:2209 · Input 391:3733 · Navigation 391:4035 ·
+ * Layout 392:5035 · Feedback 392:5381 · Help 392:5712
+ */
+export type ComponentGroup = {
+  label: string;
+  subgroups: { label: string; ids: string[] }[];
+};
+
+export const COMPONENT_GROUPS: ComponentGroup[] = [
+  {
+    label: "Action",
+    subgroups: [
+      {
+        label: "버튼",
+        ids: ["fox-button", "fox-text-button", "fox-link-button", "fox-icon-button"],
+      },
+      { label: "묶음", ids: ["fox-button-group", "fox-button-panel"] },
+    ],
+  },
+  {
+    label: "Select",
+    subgroups: [
+      { label: "라디오", ids: ["fox-radio-icon", "fox-radio", "fox-radio-group"] },
+      { label: "체크박스", ids: ["fox-check-icon", "fox-checkbox", "fox-checkbox-group"] },
+      {
+        label: "셀렉트",
+        ids: ["fox-select", "fox-select-option", "fox-select-option-item", "fox-select-text"],
+      },
+      { label: "태그·칩", ids: ["fox-tag", "fox-tag-list", "fox-chip", "fox-chip-area"] },
+      {
+        label: "스위치",
+        ids: [
+          "fox-toggle-switch",
+          "fox-segmented-control",
+          "fox-segmented-control-item",
+          "fox-toggle-quantity",
+        ],
+      },
+      { label: "부가", ids: ["fox-form-label", "fox-conditional-option"] },
+    ],
+  },
+  {
+    label: "Input",
+    subgroups: [
+      { label: "텍스트", ids: ["fox-input", "fox-text-area"] },
+      {
+        label: "형식 입력",
+        ids: ["fox-phone-number", "fox-email", "fox-certified", "fox-address"],
+      },
+      { label: "파일", ids: ["fox-file-upload"] },
+    ],
+  },
+  {
+    label: "Navigation",
+    subgroups: [
+      { label: "위치·이동", ids: ["fox-breadcrumb", "fox-pagination"] },
+      {
+        label: "캐러셀",
+        ids: ["fox-carousel-indicator", "fox-carousel-button", "fox-carousel-area"],
+      },
+    ],
+  },
+  {
+    label: "Layout",
+    subgroups: [
+      { label: "표·목록", ids: ["fox-table", "fox-bullet-list"] },
+      { label: "탭", ids: ["fox-tab"] },
+      { label: "배지", ids: ["fox-badge", "fox-badge-dot", "fox-badge-group"] },
+      { label: "펼침", ids: ["fox-accordion", "fox-disclosure"] },
+      { label: "컨테이너", ids: ["fox-card", "fox-modal", "fox-page-header"] },
+    ],
+  },
+  {
+    label: "Feedback",
+    subgroups: [
+      { label: "알림", ids: ["fox-alert", "fox-toast", "fox-snackbar", "fox-snackbar-list"] },
+      { label: "상태", ids: ["fox-status-indicator", "fox-helper-text", "fox-spinner"] },
+      { label: "단계", ids: ["fox-step-indicator", "fox-step-indicator-item"] },
+    ],
+  },
+  {
+    label: "Help",
+    subgroups: [
+      {
+        label: "도움말",
+        ids: ["fox-tooltip", "fox-tooltip-rich", "fox-popover", "fox-contextual-trigger"],
+      },
+    ],
+  },
+];
@fox/dev-test/dev-test-page.tsx
--- @fox/dev-test/dev-test-page.tsx
+++ @fox/dev-test/dev-test-page.tsx
@@ -3,6 +3,7 @@
 import { useSyncExternalStore } from "react";
 import { FoxBadge } from "../core/components/fox-badge";
 import { cx } from "../core/utils";
+import { COMPONENT_GROUPS } from "./component-groups";
 import { COMPONENT_EXAMPLES } from "./component-registry";
 import { ComponentEmpty, ComponentView } from "./component-view";
 import type { CssSection } from "./read-css-tokens";
@@ -52,6 +53,34 @@
     } else {
       groups.push({ category: section.category, sections: [section] });
     }
+  }
+
+  return groups;
+}
+
+/** 분류표를 실제 예제로 바꾼다. 표에 없는 컴포넌트는 마지막 "기타"에 모인다. */
+function groupedComponents() {
+  const used = new Set<string>();
+  const groups = COMPONENT_GROUPS.map((group) => ({
+    label: group.label,
+    subgroups: group.subgroups
+      .map((subgroup) => ({
+        label: subgroup.label,
+        examples: subgroup.ids.flatMap((id) => {
+          const example = COMPONENT_EXAMPLES.find((entry) => entry.id === id);
+          if (!example) {
+            return [];
+          }
+          used.add(id);
+          return [example];
+        }),
+      }))
+      .filter((subgroup) => subgroup.examples.length > 0),
+  })).filter((group) => group.subgroups.length > 0);
+
+  const rest = COMPONENT_EXAMPLES.filter((example) => !used.has(example.id));
+  if (rest.length > 0) {
+    groups.push({ label: "기타", subgroups: [{ label: "미분류", examples: rest }] });
   }
 
   return groups;
@@ -117,27 +146,41 @@
 
           <div className={styles.navGroup}>
             <h2 className={styles.navGroupTitle}>컴포넌트 · {COMPONENT_EXAMPLES.length}</h2>
-            <ul className={styles.navList}>
-              {COMPONENT_EXAMPLES.map((example) => (
-                <li key={example.id}>
-                  <button
-                    type="button"
-                    className={styles.navItem}
-                    aria-current={
-                      current.kind === "component" && current.id === example.id
-                        ? "page"
-                        : undefined
-                    }
-                    onClick={() => select("component", example.id)}
-                  >
-                    <span>{example.name}</span>
-                    <FoxBadge type="pastel" color="neutral" size="sm">
-                      {String(example.variants.length)}
-                    </FoxBadge>
-                  </button>
-                </li>
-              ))}
 
+            {groupedComponents().map((group) => (
+              <div key={group.label} className={styles.navSubGroup}>
+                <h3 className={styles.navSubTitle}>{group.label}</h3>
+
+                {group.subgroups.map((subgroup) => (
+                  <div key={subgroup.label} className={styles.navLeaf}>
+                    <h4 className={styles.navLeafTitle}>{subgroup.label}</h4>
+                    <ul className={styles.navList}>
+                      {subgroup.examples.map((example) => (
+                        <li key={example.id}>
+                          <button
+                            type="button"
+                            className={styles.navItem}
+                            aria-current={
+                              current.kind === "component" && current.id === example.id
+                                ? "page"
+                                : undefined
+                            }
+                            onClick={() => select("component", example.id)}
+                          >
+                            <span>{example.name}</span>
+                            <FoxBadge type="pastel" color="neutral" size="sm">
+                              {String(example.variants.length)}
+                            </FoxBadge>
+                          </button>
+                        </li>
+                      ))}
+                    </ul>
+                  </div>
+                ))}
+              </div>
+            ))}
+
+            <ul className={styles.navList}>
               {/* 컴포넌트가 아니라 안내라 항상 맨 아래에 두고 생김새를 달리한다. */}
               <li>
                 <button
@fox/dev-test/dev-test.module.scss
--- @fox/dev-test/dev-test.module.scss
+++ @fox/dev-test/dev-test.module.scss
@@ -96,6 +96,23 @@
   opacity: 0.8;
 }
 
+// 3depth — 대분류(navSubTitle) 아래 소분류.
+.navLeaf {
+  display: flex;
+  flex-direction: column;
+  gap: fox.gap(1);
+  margin-block-start: fox.gap(1);
+  padding-inline-start: fox.padding(3);
+}
+
+.navLeafTitle {
+  padding-inline: fox.padding(3);
+  color: fox.color(font-neutral-subtler);
+  font-size: fox.font-size(label-xsm);
+  font-weight: fox.font-weight(regular);
+  opacity: 0.7;
+}
+
 .navList {
   display: flex;
   flex-direction: column;
Add a comment
List