Skip to content

PageHeader

진행 중 (PageHeader component — work in progress)

  • 페이지 본문 영역 상단에 자리 잡는 tab-level 타이틀바. admin / team-manager 등 공통 페이지 헤더로 사용.
  • 3개 region (TitleBar / ActionGroup / Toolbar) 구조로 분할되어 있어 페이지별 액션을 끼워넣을 수 있음.
  • 토큰 fix 완료 (시각 변화 0). 단, font-weight / line-height / letter-spacing / transition 일부는 정책 결정 대기 — 검토 필요 사항 참고.
규칙 (강제) · 코딩 에이전트용 — 사람은 접힌 채로 두어도 됩니다

PageHeader 강제 규칙. 상세는 아래 본문.

템플릿 3종 — 서로 바꿔 쓸 수 없어요

파일언제
html/page-header.htmlActionGroup이 없는 페이지에서만. <header class="ds-page-header">이미 emit 하므로 또 감싸지 마세요.
html/page-header-title-bar.html셸을 직접 쓸 때 region 1
html/page-header-toolbar.html셸을 직접 쓸 때 region 3
  • 세 파셜 모두 주인 없는 마크업이라, 조상에 x-data="pageHeader({...})"가 반드시 있어야 합니다.
  • ActionGroup이 있으면 page-header.html을 include 하지 마세요. 셸을 직접 쓰고 region을 title-bar → <div class="ds-page-header__action-group"> → toolbar 순서로 넣으세요.
  • 순서가 레이아웃을 지탱합니다. .ds-page-headerjustify-content: flex-start이고, 오른쪽 정렬은 오직 .ds-page-header__title-bar 블록의 margin-right: auto 한 줄이 만듭니다. title-bar를 빼거나 순서를 바꾸면 레이아웃이 무너집니다(space-between 폴백 없음).
  • 헤더 줄을 감싸는 래퍼에는 .ds-page-header-layer를 씁니다(--ds-z-page-header 40) — Tailwind z-* 유틸을 붙이지 마세요. 표의 sticky thead(30)보다 위(아니면 thead가 알림 팝업을 가림), 모달(50)보다는 아래입니다.
  • ⚠️ 팝업이 뜨면 툴바의 「불편·기능 신고」는 딤에 가려 못 누릅니다. 그 순간을 위한 대체 진입점은 딤 위에 뜨는 플로팅 버튼(.ds-report-fab)이고, 헤더가 아니라 cs-report.html이 소유합니다 → FAB 배치 계약. 헤더 안 버튼만 따로 올리려 하지 마세요 — 래퍼의 z-index(그리고 position: sticky)가 쌓임 맥락을 만들어 그 안의 z-index는 바깥과 겨루지 못합니다(자식에 position: fixed를 줘도 같습니다). 줄 전체를 올리면 팝업 중에 상단바만 밝게 남아 어색합니다.
  • ActionGroup은 반드시 .ds-page-header__action-group 래퍼로 감싸세요 — 맨 버튼을 헤더에 직접 넣으면 gap·shrink가 틀어집니다.

props

  • showNotification의 기본값은 true입니다. 종을 숨기려면 **명시적으로 showNotification: false**를 넘겨야 해요(생략 ≠ 숨김). showProfile은 기본 false(opt-in).
  • 반응형 prop과 정적 prop은 이름이 다릅니다: getTitle/getIcon/getShowBackget*는 매 렌더 재평가, title/icon/showBackinit에서 한 번만 캡처. 값이 나중에 바뀌는데 title:로 넘기면 헤더가 조용히 얼어붙습니다.
  • showProfile: trueSDK 스토어를 필수로 요구합니다($store.hereby.currentUser, logout()).
  • 툴바는 notification-bell.html + notificationBell 팩토리를 끌어옵니다 — 등록 안 되어 있으면 툴바 전체가 터집니다.
  • onOrgInfo를 안 넘기면 프로필 팝오버의 '조직 설정' 줄이 통째로 사라집니다(showOrgInfo: !!onOrgInfoCb).

⚠️ 트랩 ① 여기서 radius를 고쳐도 아무 일도 안 일어납니다 전역 <button> 4px !important 강제(design-system.cssbutton:not([class*="bg-"]):not(.ds-tab-item)… 규칙)에서 .ds-page-header__avatar만 제외돼 있습니다. 나머지는 6px를 선언만 하고 실제로는 4px로 렌더돼요:

요소선언값실제 렌더
.ds-page-header__back6px4px
.ds-page-header__refresh6px4px
.ds-page-header__profile-menu-item6px4px

→ 선언을 고쳐도 소용없어요. 바꾸려면 button:not(…) 규칙의 :not() 체인에 클래스를 추가해야 합니다.

  • 참고: 아바타는 원형이 아니라 8px 라운드 사각형(32px 박스)입니다. 원형인 건 팝오버 안의 .ds-page-header__profile-menu-thumb(<span>이라 강제와 무관)예요.

⚠️ 트랩 ② 팀 셀렉트에 background: 축약형을 쓰면 chevron이 사라집니다.ds-page-header__team-select 블록은 background-color + background-image: var(--ds-dropdown-chevron)일부러 분리해서 씁니다(블록 안에 경고 주석 있음). background: 하나로 합치면 chevron이 지워지고 컨트롤이 맨몸으로 렌더돼요.

⚠️ 트랩 ③ admin은 헤더를 덮어씁니다

  • admin main.css.ds-page-header { border-bottom: none } 블록(미디어쿼리 )이 이를 전역으로 겁니다. 구분선은 바깥 sticky 컨테이너가 그려요 — admin에서 그 컨테이너 밖에 헤더를 놓으면 밑줄이 없습니다.
  • 같은 파일의 @media (max-width: 767px) 에 있는 .ds-page-header 블록: 767px 이하에서 헤더는 56px(min-height: var(--ds-spacing-56))이고 아이콘이 숨겨집니다. "헤더는 64px"는 ≥768px에서만 참이에요.

라이브 미리보기

실제 .ds-page-header* 클래스로 렌더되는 인터랙티브 데모 — 풀 상태(back + icon + title + refresh + ActionGroup + team filter + bell + 프로필 아바타). ↻ 버튼은 약 1초 로딩(스피너 + disabled), 팀 필터로 선택을 바꾸고, 우측 끝 아바타를 눌러 프로필 팝오버를 확인하세요.

조직 관리

선택 팀: 전체 팀 · 새로고침: 대기— ↻ 클릭 시 약 1초 로딩, 팀 필터로 선택 변경, 우측 끝 아바타 클릭 시 프로필 팝오버

단일 출처

파트파일
Alpine 컴포넌트packages/components/src/components/PageHeader.ts
Wrapper partialpackages/components/html/page-header.html
TitleBar partialpackages/components/html/page-header-title-bar.html
Toolbar partialpackages/components/html/page-header-toolbar.html
스타일packages/static/styles/design-system.css (.ds-page-header*)

구조 (3 Region)

┌─ TitleBar ────────────────┬─ ActionGroup ─┬─ Toolbar ──────────────────────┐
│ ←  [icon]  Title       ↻  │ [page btns]   │  team ▼  │  🔔   (프로필)      │
└───────────────────────────┴───────────────┴────────────────────────────────┘
   페이지 정체성                페이지별 액션      공용 도구
  • TitleBar (필수) — 뒤로가기(opt-in) · 아이콘(opt-in) · 제목 · 새로고침(opt-in). 모든 호출 위치에서 partial 재사용으로 동일.
  • ActionGroup (opt-in) — 페이지가 직접 작성하는 액션 버튼 (예: admin의 "역할 관리 →"). 없으면 wrapper 통째 생략 가능.
  • Toolbar (필수) — 팀 필터(opt-in) · 구분선 · 알림 종(기본 ON) · 프로필 아바타(opt-in, 맨 끝). 모든 호출에서 partial 재사용.

전체 헤더 높이는 64px (이전 65px → 2px step 정렬).

두 가지 호출 패턴

1. 단순 — ActionGroup 없음

html
<div x-data="pageHeader({ title: '직원관리', icon: 'ic-employee', showRefresh: true })">
  <!-- include "../../packages/components/html/page-header.html" -->
</div>

→ wrapper partial 한 줄로 TitleBar + Toolbar 자동 렌더링.

2. ActionGroup 필요 — <header> shell 직접 작성

html
<div x-data="pageHeader({ title: '조직관리', icon: 'ic-organization', showRefresh: true })">
  <header class="ds-page-header">
    <!-- include "../../packages/components/html/page-header-title-bar.html" -->
    <div class="ds-page-header__action-group">
      <button class="btn-primary">역할 관리 →</button>
    </div>
    <!-- include "../../packages/components/html/page-header-toolbar.html" -->
  </header>
</div>

→ 3 region을 순서대로 배치. TitleBar의 margin-right: auto가 ActionGroup + Toolbar를 우측에 정렬.

API

pageHeader(opts: PageHeaderOptions) Alpine.data factory.

Props (모두 optional)

그룹Prop타입설명
Titletitlestring정적 제목
getTitle() => string호스트 상태에 반응하는 제목 source
Iconiconstring아이콘 이름 (@hereby/components icon registry)
getIcon() => string반응형 아이콘 source
Back navshowBackboolean뒤로가기 ← 버튼 표시 (정적)
getShowBack() => boolean반응형 toggle
onBack() => void클릭 핸들러
RefreshshowRefreshboolean새로고침 ↻ 버튼 표시
onRefresh() => void클릭 핸들러
getRefreshLoading() => boolean로딩 중일 때 disable + 스피너
Team filtershowTeamFilterboolean팀 드롭다운 표시
getShowTeamFilter() => boolean반응형 toggle
getTeams() => Team[]팀 목록
getSelectedTeamId() => string현재 선택 ID
onTeamChange(id: string) => void변경 핸들러
NotificationshowNotificationboolean알림 종 표시 (기본 true)
ProfileshowProfileboolean프로필 아바타 표시 (기본 false, opt-in)
onLogout() => void로그아웃 핸들러 — 호스트의 확인 플로우(예: admin doLogout)를 연결. 미지정 시 store 직접 로그아웃

Team 타입: { id: string; name: string; memberCount?: number }

프로필 아바타 & 팝오버

LNB 하단에 있던 사용자 프로필을 헤더 우측 끝으로 이동하며 도입된 영역 (LNB 개편, 2026-06).

  • 아바타.ds-page-header__avatar. 32px · --radius-8 · 브랜드 그라데이션(컴포넌트 전용 예외) · 메일 첫 글자(대문자). 알림 벨과의 시각 간격 24(= toolbar gap 8 + margin 16). 전역 버튼 radius 강제(4px) 규칙의 제외 명단에 등록되어 있음
  • 팝오버.ds-page-header__profile-menu. 아바타 클릭 시 우측 정렬 드롭다운: 썸네일(64px 원형) → 메일 → 이름(있을 때만) → 구분선 → 내 정보 · 로그아웃(danger). 알림 팝업과 동일한 1px 테두리(--color-line-normal) + --shadow-blur4
  • 동작 — 내 정보는 공유 MyInfoForm을 여는 window open-my-info 이벤트 사용. 로그아웃은 onLogout 콜백(없으면 $store.hereby.logout()). 바깥 클릭으로 닫힘
  • 색은 전부 DS 토큰: neutral-25/100/600/900, neutral-75(hover), red-500(로그아웃), --ds-bw-white

시각 미리보기

아이콘은 packages/components/src/icons.ts에 등록된 실제 hereby 아이콘 SVG 그대로 사용. 아래 1)~4)는 개별 상태 정적 레퍼런스입니다.

1) 미니멀 — title + Toolbar (기본 호출)

대시보드

2) Title + Icon + Refresh

직원 관리

3) Back nav + Title + Refresh (embed wrapper)

휴가 신청 상세

4) 풀 상태 — Back + Icon + Title + Refresh + ActionGroup + Team filter + Bell + 프로필

조직 관리

t

→ 사용된 실제 아이콘: chevron-left (back), ic-employee / ic-calendar / ic-organization (title icon), ic-refresh, ic-bell. TitleBar의 margin-right: auto가 ActionGroup + Toolbar를 우측에 정렬. 우측 끝 아바타(showProfile)는 radius 8 · 벨과 간격 24(gap 8 + margin 16).

사용된 토큰 (fix 후)

카테고리토큰
Spacing--ds-spacing-{2, 8, 12, 14, 16, 20, 22, 28, 64} + 프로필 {4, 6, 8, 10, 16, 32, 64, 240}
Color (text/border/bg)--color-neutral-{25, 50, 75, 100, 300, 500, 600, 700, 900}, --color-orange-500, --color-red-500(로그아웃), --color-line-normal, --ds-bw-white
Radius--radius-4 (team-select), --ds-radius-sm (back/refresh 버튼, 6px), --radius-8 (아바타), --radius-16 (프로필 팝오버), --radius-32 (팝오버 썸네일)
Opacity--ds-opacity-50 (disabled refresh), --ds-opacity-90 (아바타 hover)
Typography--text-b2 (title 18px), --text-b6 (team-select·메일 12px), --text-b4/--text-b5/--text-h3 (프로필 팝오버)
Shadow--shadow-blur4 (프로필 팝오버)

검토 필요 사항

토큰 체계가 아직 커버하지 못해 하드코딩으로 남겨둔 항목. 디자이너 결정 후 토큰화 진행:

  • 🟡 font-weight: 700 (title) — font-weight 토큰 미정의 (typography 페이지에도 명시됨)
  • 🟡 title line-height: 1.3 — B2 기본(1.4)과 다름. PageHeader 전용 타이포 필요 여부 결정
  • 🟡 title letter-spacing: -0.3px — B2(-0.18px) / H3(-0.24px) 어디와도 다름 — line-height 항목처럼 PageHeader 전용 타이포 정의 여부를 함께 결정 필요
  • 🟡 transition 0.15s / 0.3s (4곳) — motion scale 부족 (--duration-fast 100ms / --duration-normal 200ms만 정의)
  • 🟡 divider width: 1px — spacing 토큰 최소가 2px. hairline 1px 토큰 정책 결정 필요
  • min-height: 65px → 64px — 2px step 정렬을 위해 1px 축소 적용 (시각 영향 미미)
  • color fallback을 잘못된 #767676에서 neutral-500 실제값 #5e5e5e로 교체 (var(--color-neutral-500, #5e5e5e))

사용처

목표 & 로드맵

항목현재목표
3 Region 구조✅ TitleBar / ActionGroup / Toolbar유지
Spacing 토큰 매핑✅ 완료유지
Color 토큰 매핑✅ 완료 (fallback 정합성 포함)유지
Radius 토큰 매핑✅ 완료유지
Font-size 토큰 매핑✅ b2 / b6유지
Font-weight 토큰화🔴 미정의typography 페이지에서 weight scale 정의 후 적용
Title typography 정렬🟡 line-height/letter-spacing 비표준디자이너 검토 후 표준 매핑 or 전용 토큰 정의
Motion scale 확장🟡 0.15s/0.3s 사용 중--duration-{xs, sm, md, lg} 등 세분화
Hairline 1px 토큰 정책🔴 미정의spacing 또는 별도 border 토큰 결정
컴포넌트 단위 가이드라인🔴 미작성호출 패턴 1/2 권장 시점, ActionGroup 디자인 규칙 등

변경 이력

날짜내용
2026-06-12프로필 아바타+팝오버 도입 (showProfile/onLogout opt-in) — LNB 하단 프로필을 헤더 우측 끝으로 이동. 아바타 radius 8·벨과 간격 24, 팝오버(썸네일·메일·이름·내 정보·로그아웃) 전 색 DS 토큰. workspace(admin) native·embed 헤더 적용
2026-05-28토큰 audit + fix: spacing 14곳·radius 3곳·opacity 1곳·typography 2곳·color fallback 1곳 → DS 토큰으로 교체. min-height 65→64px (2px step 정렬). 시각 변화 ≈ 0
이전PageHeader family 신규 (TitleBar/ActionGroup/Toolbar 3 region 분할 + partial 분리 + 65px 통일 + 뒤로가기·refresh 좌측 통일)