Docs

docs/system/components/app-header.md

App Header

packages/ui/src/app-header.tsx

모바일 화면의 최상단 한 줄이다. 자리는 이 컴포넌트가 고정하고 무엇을 표시할지는 쓰는 화면이 정한다.

역할과 경계

  • AppHeader는 Container와 Title, 선택 요소인 Description, Leading element, Trailing element을 소유한다. 한 화면에 하나만 둔다.
  • 모바일 화면에서만 쓴다. 데스크톱 화면의 전역 바에는 쓰지 않는다. 데스크톱 상단 바는 로고와 전역 내비게이션, 계정 메뉴가 함께 서는 다른 구조이며 아직 시스템에 없다.
  • 화면 안쪽 영역의 제목에는 쓰지 않는다. 그 제목은 해당 영역이 직접 갖는다.
  • 좌우 아이콘 자리에 놓는 버튼은 button.md가 소유한다. AppHeader는 자기 버튼을 따로 만들지 않는다.
  • 아이콘의 LineFill 선택 규칙은 icon.md가 소유한다.

Anatomy

  1. Container: 모든 내부 요소를 감싸며 높이와 좌우 여백, 배경과 경계선을 소유한다.
  2. Leading element: 선택 요소다. 왼쪽 자리다.
  3. Title: 필수 요소다. h1으로 그린다.
  4. Description: 선택 요소다. Title 아래 한 줄이다.
  5. Trailing element: 선택 요소다. 오른쪽 자리다.

자리의 고정 순서는 Leading element → Title block → Trailing element이며 Title과 Description은 Title block 안에 위아래로 놓인다.

Leading element와 Trailing element는 성격이 같은 자리다. 어느 쪽에 무엇이 서는지를 이 컴포넌트가 정하지 않는다. 아이콘 버튼뿐 아니라 로고와 Label이 있는 버튼도 받으므로 이름에 Icon을 붙이지 않는다. 무엇을 넣는지는 Guidelines의 「왼쪽과 오른쪽을 가르는 기준」과 「화면 유형」이 정한다.

Properties

  • title: 필수다. 지금 보고 있는 것이 무엇인지 말한다.
  • titleHidden: false | true, 기본값 false. 제목을 화면에서만 감추고 이름은 남긴다.
  • description: 선택이다. Title 아래 한 줄이다. Title을 보조하는 줄이므로 titleHidden일 때 함께 감춰진다.
  • leadingElement: 왼쪽 자리. 아이콘 버튼 두 개까지, 또는 로고를 넣는다.
  • trailingElement: 오른쪽 자리. 아이콘 버튼 두 개까지, 또는 Label이 있는 버튼 하나를 넣는다.
  • titleAlign: center | start, 기본값 center
  • sticky: false | true, 기본값 true
  • State: Top, Stuck. Hover, Pressed와 Focus visible은 이 컴포넌트가 갖지 않고 좌우 자리에 놓인 Button이 갖는다.

title은 HTML의 title 속성과 이름이 겹치므로 그 속성을 넘길 수 없다.

높이

56px 한 값으로 고정한다. Description이 있든 없든, 아이콘이 몇 개든 같다.

interaction.md의 공통 Control Height 중 XXLarge와 값이 같지만 그 규칙을 따르는 것은 아니다. 헤더는 Control이 아니라 Control을 담는 Container이며 값이 같은 것은 우연이다.

높이를 내용에 맡기지 않는 이유는 화면 이동 때문이다. Description이 있는 화면과 없는 화면을 오갈 때 헤더가 늘었다 줄었다 하면 그 아래 본문이 위아래로 밀린다.

고정 높이 안에 두 줄이 들어가도록 크기를 미리 정해 둔다.

  • Title: text-base에 20px 행간, font-semibold, oe-foreground-primary
  • Description: text-xs에 16px 행간, oe-foreground-tertiary

Title의 크기는 Description 유무와 관계없이 같다. Description이 들어왔다고 Title이 작아지면 화면을 옮길 때 제목 크기가 흔들린다. Description이 없으면 Title이 56px 안에서 세로 가운데에 선다.

Title과 Description은 모두 한 줄에서 말줄임한다. 어느 쪽이든 두 줄로 넘어가면 고정 높이와 양립하지 않는다.

제목과 접근성

Title은 h1이다. 화면 낭독기를 쓰는 사람은 제목으로 화면을 훑으므로 heading이 아니면 그 목록에 잡히지 않는다. 한 화면에 하나만 두는 컴포넌트이니 레벨을 받지 않고 h1으로 고정한다. 본문에 같은 층의 제목이 또 있으면 그쪽을 h2로 내린다.

제목 글자를 화면에 두지 않는 화면에서도 제목을 지우지 않는다. 로고가 어느 화면인지 이미 말하고 있거나 닫기만 있으면 되는 자리에는 titleHidden을 쓴다. 글자는 보이지 않고 이름만 남으므로 그 화면에 이름이 없어지지 않는다. 감춘 제목은 흐름에서 빠지므로 좌우 자리가 남은 공간을 반씩 나눠 갖고 아이콘이 양 끝에 붙는다.

titleHidden일 때도 제목은 그 화면이 무엇인지 말하는 값이어야 한다. 「헤더」나 「타이틀」처럼 자리 이름을 넣지 않는다.

Title align과 균형

center에서 Title은 남은 공간의 가운데가 아니라 화면의 가운데에 선다.

좌우 자리가 남은 공간을 반씩 나눠 가지므로 아이콘이 한쪽에만 있거나 개수가 서로 달라도 Title의 중심이 움직이지 않는다. 왼쪽에 아이콘이 하나이고 오른쪽에 둘이어도 두 자리의 폭이 같아진다.

좌우 자리는 아이콘 폭보다 좁아지지 않는다. 폭이 모자랄 때 줄어드는 것은 아이콘이 아니라 Title이다.

start에서는 예약하지 않는다. Title이 왼쪽 자리 바로 옆에서 시작해 오른쪽 자리 앞까지 넓게 쓴다. 이 정렬을 고르는 이유가 긴 이름을 넓게 쓰기 위해서이므로, 예약하면 고른 뜻이 사라진다.

정렬을 고르는 기준은 Guidelines의 「정렬은 제품 전체에서 하나로 고르기」에 있다.

Stuck

sticky가 켜져 있으면 헤더가 화면 위에 머문다. 기본값이 켜짐이다.

경계선은 가리는 것이 있을 때만 나타난다. 맨 위에 있을 때는 본문과 같은 면으로 붙어 있다가, 본문이 헤더 아래로 들어가기 시작하면 아래에 1px oe-border-secondary가 나타난다.

선을 없앴다 만들지 않고 색만 바꾼다. 경계를 켜고 끄면 그때마다 본문이 1px씩 위아래로 움직인다.

헤더를 감싸고 실제로 스크롤되는 영역을 찾아 그 위치를 읽으므로, 페이지 전체가 스크롤되든 안쪽 영역이 스크롤되든 같게 동작한다. 현재 상태는 data-stuck으로 드러난다.

Guidelines

모바일 화면의 최상단에만 쓰기

  • 모바일 화면의 맨 윗줄에 쓴다. 데스크톱 화면의 전역 바에는 쓰지 않는다.
  • 한 화면에 하나만 둔다.
  • 화면 안쪽 영역의 제목에는 쓰지 않는다. 그 영역이 자기 제목을 갖는다.

자리는 고정하고 내용은 화면이 채우기

  • 목록 화면에서는 기능명을, 상세 화면에서는 그 대상의 이름을 같은 자리에 넣는다. 가맹점 목록에서는 「가맹점관리」를, 가맹점 상세에서는 가맹점명을 넣는 식이다.
  • 화면이 바뀌어도 헤더의 높이와 자리는 그대로 둔다.

제목은 한 덩어리로 쓰고 언제나 넣기

  • 지금 보고 있는 것이 무엇인지 한 덩어리로 말한다.
  • 경로를 이어 붙이지 않는다. 상위 화면의 이름은 제목이 아니라 뒤로 가는 길이 전달한다.
  • 화면에 글자를 두고 싶지 않아도 제목은 넣고 titleHidden으로 감춘다. 빼 버리면 그 화면에 이름이 없어진다.

설명에는 식별하는 값만 두기

  • 제목이 가리키는 대상을 식별하는 짧은 값에만 쓴다. 사업자번호나 코드가 그런 값이다.
  • 안내 문장을 넣지 않는다. 한 줄에서 잘리므로 문장은 끝까지 읽히지 않는다.
  • 화면을 설명해야 한다면 헤더가 아니라 본문 첫 칸이 그 자리다.

375px에서 잘리는지 확인하기

  • 좌우에 아이콘이 두 개씩 서는 조합이 제목에 가장 좁은 폭을 남긴다. 이 조합에서 확인한다.
  • 잘리는 것이 문제가 되는 값은 본문 첫 칸에서 전체를 다시 보여 준다.

정렬은 제품 전체에서 하나로 고르기

  • 이어지는 화면이 같은 정렬을 쓴다. 목록에서 상세로 들어갈 때 정렬이 바뀌면 제목이 좌우로 미끄러져 다른 화면으로 왔다는 신호처럼 읽힌다.
  • 긴 이름 때문에 start가 필요하면 그 화면만이 아니라 같은 흐름 전체를 start로 내린다.

왼쪽과 오른쪽을 가르는 기준

  • 왼쪽에는 나가는 길을 둔다. 뒤로가기, 닫기, 홈이 여기에 온다. 로고도 누르면 홈으로 가므로 같은 자리다.
  • 오른쪽에는 그 화면에서 하는 일을 둔다. 검색, 알림, 설정, 저장, 더보기가 여기에 온다.
  • 닫기는 레이어든 전체메뉴든 왼쪽이다. 화면을 덮고 자기 헤더를 갖는 레이어의 닫기는 앞 화면으로 되돌아가는 동작이라 뒤로가기와 성격이 같다. 본문 위에 겹쳐 뜨는 작은 팝업이나 bottom sheet는 이 컴포넌트를 쓰지 않으므로 이 규칙의 대상이 아니다.
  • 닫기의 자리를 화면마다 바꾸지 않는다. 연 자리 가까이 두면 손가락은 덜 움직이지만, 그때마다 닫기를 눈으로 찾게 된다.

아이콘 자리는 두 개까지만 채우기

  • 한쪽에 아이콘을 셋 이상 두지 않는다. 세 개부터는 제목이 들어갈 폭이 남지 않는다.
  • 화면에서 가장 중요한 동작이 아니면 헤더에 올리지 않고 본문에 둔다.

아이콘은 공용 Button으로 두고 이름 주기

  • Ghost를 tone="neutral"로 쓴다. Ghost도 tone 색을 따라가므로 중립으로 읽혀야 하는 자리는 기본값에 기대지 않고 밝혀 적는다.
  • aria-label로 그 버튼이 무엇을 하는지 전달한다. 모양만으로는 읽히지 않는다.

아이콘 크기는 헤더가 정한다

좌우 자리의 아이콘은 24px이다. Button의 크기 단계가 갖고 오는 값을 쓰지 않고 헤더가 자기 자리 안에서 덮어쓴다.

  • 화면이 크기를 직접 적지 않는다. 화면마다 적게 두면 헤더마다 값이 갈린다.
  • 크기를 바꿀 때는 이 줄과 SIDE_CONTENT_CLASS 한 곳만 고친다.
  • 로고는 그림이라 이 규칙에 걸리지 않는다. 크기는 그 자리에 넣는 이미지가 갖는다.

누르는 동안 아이콘이 작아진다. 90%로 줄었다가 손을 떼면 돌아온다. 눌린 것이 손에서 떨어지기 전에 화면에서 먼저 읽힌다. 공용 Button은 누를 때 움직이지 않기로 정해져 있으므로(button.md) 그 컴포넌트를 고치지 않고 헤더가 자기 자리 안에서만 건다. 로고는 그림이라 걸리지 않는다.

아이콘 버튼은 Large를 쓴다. 40px 안에 24px 아이콘이 가운데 서므로 버튼 안쪽에 한쪽 8px이 남는다. 헤더는 버튼이 든 자리를 그만큼 바깥으로 당겨서 아이콘 그림이 Container의 여백선에 맞게 한다. 당기지 않으면 로고는 16px 선에 서는데 아이콘만 24px 안쪽에 서서 양 끝의 여백이 달라 보인다. 로고처럼 버튼이 아닌 것이 서는 자리는 당기지 않는다.

버튼 자체는 공용 Button을 그대로 쓴다. 헤더 전용 버튼을 따로 만들면 tone과 Hover, Focus 규칙이 두 벌로 갈린다. 폼 안에 서는 Button은 자기 단계의 아이콘 크기를 그대로 유지하므로 Input의 Leading element와 줄이 맞는다.

화면 유형

블루키에서 실제로 쓰는 다섯 조합이다. 컴포넌트가 이 구분을 갖지 않는다. 다섯의 차이가 전부 titleleadingElement, trailingElement에 무엇을 넣는지로 설명되므로 Property로 만들지 않았다. 조합이 늘어도 컴포넌트는 그대로 두고 이 표에 줄을 더한다.

유형 Title Leading element Trailing element
Main 감춤 (titleHidden) 로고 알림, 전체메뉴
Menu 감춤 (titleHidden) 닫기 유저, 설정
Sub 보임 뒤로가기 그 화면이 정한다. 아이콘도 Label이 있는 버튼도 온다
Layer 보임 또는 감춤 닫기 선택
Plain 보임 없음 없음

Plain은 나가는 길을 일부러 두지 않는 화면에 쓴다. 개정된 약관에 동의해야만 넘어갈 수 있는 자리처럼, 그 화면을 벗어날 방법을 주지 않아야 할 때다. 좌우가 비어 있는 것이 빠뜨린 결과가 아니라 결정이라는 뜻이므로, 다른 유형처럼 뒤로가기를 더하지 않는다.

Main과 Menu처럼 Title을 감추는 유형에서는 titleAlign이 화면을 바꾸지 않는다. 감춘 제목이 흐름에서 빠져 좌우 자리가 남은 공간을 반씩 나눠 가지므로 아이콘이 양 끝에 붙기 때문이다.

현재 정의하지 않은 항목

  • 제목 옆 상태 표시. Badge를 붙이는 자리를 만들지 않았다.
  • 검색 입력으로 바뀌는 헤더.
  • 여러 항목을 고르는 선택 모드 헤더.
  • 데스크톱 폭에서의 동작.