임동욱 임동욱 08-18
feat: FoxPageHeader 추가
관리자페이지 파일의 page-tit-area(3002:6823)를 옮겼다. 현재 위치와 제목을 얹고 본문과의
간격(아래 여백 36px)까지 갖는다 — 시안이 그렇게 묶어 놓아 화면마다 따로 띄우지 않아도 된다.

현재 위치는 FoxBreadcrumb를 그대로 쓴다(시안에서도 breadcrumb 인스턴스다). 항목 배열만
넘기면 되고, 넘기지 않으면 그 줄을 그리지 않는다.

제목은 화면에 하나뿐인 이름이라 `

`으로 낸다. 시안은 `

`지만 그건 Figma에 문단밖에 없어서고, 마크업에서는 제목이 맞다. `actions` 자리를 열어 뒀다. 시안에 들어간 것은 없지만 배치가 자리를 비워 두고 있다 — 제목 칸이 `flex: 1`이고 오른쪽에 24px 간격이 남는다. 값을 지어내지 않고 그 자리만 연다. 검증: 바깥 간격 24 · 아래 여백 36 · 안쪽 간격 8 · 제목 32px w800 #1e2124 줄높이 48 · 자간 -0.8px이 시안과 일치. 오른쪽 자리를 쓸 때 제목이 남는 폭을 먹고 24px이 유지되며, 제목이 길어져도 오른쪽을 침범하지 않는다. sass 634규칙 · tsc · eslint 통과. Co-Authored-By: Claude Opus 5

@21f76593ae723dcb92d582ca2425206521706ff8
 
@fox/core/components/fox-page-header/fox-page-header.tsx (added)
+++ @fox/core/components/fox-page-header/fox-page-header.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import type { ReactNode } from "react";
+import { cx } from "../../utils";
+import { FoxBreadcrumb, type FoxBreadcrumbItem } from "../fox-breadcrumb";
+
+export interface FoxPageHeaderProps {
+  /** 화면 제목. 페이지에 하나뿐인 이름이라 `<h1>`으로 나간다. */
+  title: ReactNode;
+  /** 제목 위에 붙는 현재 위치. 넘기지 않으면 영역을 그리지 않는다. */
+  breadcrumb?: FoxBreadcrumbItem[];
+  /**
+   * 제목 오른쪽 자리. 시안에는 들어간 것이 없지만 배치가 자리를 비워 두고 있어
+   * (제목이 남는 폭을 먹고 24px 간격이 있다) 화면이 버튼 같은 것을 얹을 수 있게 연다.
+   */
+  actions?: ReactNode;
+  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
+  hidden?: boolean;
+  /** 배치 조정용. */
+  className?: string;
+}
+
+/**
+ * @fox 화면 머리말. 현재 위치와 제목을 얹고 본문과의 간격까지 갖는다.
+ *
+ * 현재 위치는 `FoxBreadcrumb`를 그대로 쓴다 — 시안에서도 breadcrumb 인스턴스다.
+ *
+ * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
+ * (또는 개별 파티셜)로 한 번 불러와야 한다.
+ */
+export function FoxPageHeader({
+  title,
+  breadcrumb,
+  actions,
+  hidden = false,
+  className,
+}: FoxPageHeaderProps) {
+  if (hidden) {
+    return null;
+  }
+
+  return (
+    <div className={cx("fox-page-header", className)}>
+      <div className="fox-page-header__main">
+        {breadcrumb && breadcrumb.length > 0 && <FoxBreadcrumb items={breadcrumb} />}
+        <h1 className="fox-page-header__title">{title}</h1>
+      </div>
+      {actions && <div className="fox-page-header__actions">{actions}</div>}
+    </div>
+  );
+}
 
@fox/core/components/fox-page-header/index.ts (added)
+++ @fox/core/components/fox-page-header/index.ts
@@ -0,0 +1,1 @@
+export { FoxPageHeader, type FoxPageHeaderProps } from "./fox-page-header";
@fox/core/components/index.ts
--- @fox/core/components/index.ts
+++ @fox/core/components/index.ts
@@ -25,6 +25,7 @@
 export * from "./fox-icon-button";
 export * from "./fox-input";
 export * from "./fox-link-button";
+export * from "./fox-page-header";
 export * from "./fox-pagination";
 export * from "./fox-phone-number";
 export * from "./fox-radio";
@@ -48,4 +49,3 @@
 export * from "./fox-text-button";
 export * from "./fox-toggle-quantity";
 export * from "./fox-toggle-switch";
-
@fox/dev-test/component-registry.tsx
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
@@ -56,6 +56,7 @@
 import { FoxSegmentedControl } from "../core/components/fox-segmented-control";
 import { FoxSegmentedControlItem } from "../core/components/fox-segmented-control-item";
 import { FoxToggleQuantity } from "../core/components/fox-toggle-quantity";
+import { FoxPageHeader } from "../core/components/fox-page-header";
 import styles from "./dev-test.module.scss";
 
 export interface ComponentExample {
@@ -3255,4 +3256,45 @@
       },
     ],
   },
+  {
+    id: "fox-page-header",
+    name: "FoxPageHeader",
+    description:
+      "Figma 시안(page-tit-area 3002:6823, 관리자페이지 파일)을 옮겼습니다. 현재 위치와 제목을 얹고 본문과의 간격(아래 여백)까지 갖습니다 — 시안이 그렇게 묶어 놓아 화면마다 따로 띄우지 않아도 됩니다. 제목은 화면에 하나뿐인 이름이라 <h1>으로 나갑니다.",
+    variants: [
+      {
+        label: "시안 그대로 — 현재 위치 + 제목",
+        node: <FoxPageHeader breadcrumb={BREADCRUMB_ITEMS} title="리스트 페이지" />,
+      },
+      {
+        label: "현재 위치 없이 제목만",
+        node: <FoxPageHeader title="리스트 페이지" />,
+      },
+      {
+        label: "오른쪽 자리 (actions) — 시안엔 없지만 배치가 자리를 비워 둡니다",
+        node: (
+          <FoxPageHeader
+            breadcrumb={BREADCRUMB_ITEMS}
+            title="리스트 페이지"
+            actions={
+              <FoxButtonGroup size="md">
+                <FoxButton size="md" type="secondary" label="엑셀 다운로드" />
+                <FoxButton size="md" type="primary" label="등록" />
+              </FoxButtonGroup>
+            }
+          />
+        ),
+      },
+      {
+        label: "제목이 길어질 때 — 오른쪽 자리를 침범하지 않고 접힙니다",
+        node: (
+          <FoxPageHeader
+            breadcrumb={BREADCRUMB_ITEMS}
+            title="아주 긴 화면 제목이 들어가면 이렇게 여러 줄로 접힙니다 아주 긴 화면 제목"
+            actions={<FoxButton size="md" type="primary" label="등록" />}
+          />
+        ),
+      },
+    ],
+  },
 ];
 
@fox/styles/_fox-page-header.scss (added)
+++ @fox/styles/_fox-page-header.scss
@@ -0,0 +1,60 @@
+// FoxPageHeader — 시안: 관리자페이지(QsFVFFUKGH68xAPVkF3SJ9) page-tit-area (3002:6823)
+//
+// 마크업 계약 (React 밖 소비자용):
+//   <div class="fox-page-header">
+//     <div class="fox-page-header__main">
+//       …fox-breadcrumb…                          <!-- 선택 -->
+//       <h1 class="fox-page-header__title">제목</h1>
+//     </div>
+//     <div class="fox-page-header__actions">…</div>  <!-- 선택 -->
+//   </div>
+//
+// 제목이 남는 폭을 먹고 오른쪽에 24px 간격이 남는다. 시안에는 그 자리에 들어간 것이 없지만
+// 배치가 자리를 비워 두고 있어 `__actions`로 열어 둔다.
+//
+// 본문과의 간격(아래 여백)을 머리말이 갖는다 — 시안이 그렇게 묶어 놓았다. 화면마다 따로
+// 띄우지 않아도 된다.
+//
+// 토큰이 아닌 값:
+//   - line-height / letter-spacing: 시안 텍스트 스타일의 값이나 Figma가 변수로
+//     export하지 않는다.
+//   - `<h1>`의 브라우저 기본 여백 0: UA 기본값 지우기.
+
+@use "abstracts" as fox;
+
+.fox-page-header {
+  display: flex;
+  align-items: flex-start;
+  gap: fox.gap(7);
+  inline-size: 100%;
+  padding-block-end: fox.spacing(bottom-xlg);
+
+  // 현재 위치와 제목이 세로로 쌓이고 남는 폭을 먹는다.
+  &__main {
+    display: flex;
+    flex: 1 0 0;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: fox.gap(3);
+    min-inline-size: 0;
+  }
+
+  &__title {
+    inline-size: 100%;
+    margin: 0;
+    color: fox.color(font-neutral-strong);
+    font-family: fox.font-family(title);
+    font-size: fox.font-size(display-md);
+    font-weight: fox.font-weight(bold);
+    line-height: 1.5;
+    letter-spacing: -0.025em;
+    word-break: break-word;
+  }
+
+  // 제목 옆에 서고 줄어들지 않는다.
+  &__actions {
+    display: flex;
+    flex-shrink: 0;
+    align-items: center;
+  }
+}
@fox/styles/components.scss
--- @fox/styles/components.scss
+++ @fox/styles/components.scss
@@ -51,3 +51,4 @@
 @use "fox-tab-item";
 @use "fox-spinner";
 @use "fox-toggle-quantity";
+@use "fox-page-header";
Add a comment
List