PanelHeader | CoUI
본문으로 건너뛰기
LogoCoUI

PanelHeader

패널 머리 — 제목과 줄 끝의 작은 행동 하나

목차

PanelHeader 는 페이지 안의 패널 · 카드 하나의 머리입니다. 제목 한 줄과 줄 끝의 작은 행동 하나(보통 plain · sm 버튼에 뒤 화살표 chevron-right 14, "전체 보기 >" 꼴)를 놓고, 그 아래를 패널 본문과 띄웁니다. 폭이 좁아지면 행동이 제목 아래로 내려갑니다 — 머리는 넘치지 않습니다.

기본 사용법 (Basic Usage)#

PanelHeader(
  title: const Text('Recent files'),
  action: Button(
    variant: .plain,
    size: .sm,
    onPressed: () {},
    child: const Text('View all'),
    trailing: const Icon(
      LucideIcons.chevronRight,
      iconStyle: CoreIconStyle(size: CoreIconSize.size14),
    ),
  ),
)
PanelHeader(
  title: const Text('Recent files'),
  action: Button(
    variant: .plain,
    size: .sm,
    onPressed: () {},
    child: const Text('View all'),
    trailing: const Icon(
      LucideIcons.chevronRight,
      iconStyle: CoreIconStyle(size: CoreIconSize.size14),
    ),
  ),
)

제목에 넘긴 Text 는 머리가 정한 역할 · 색으로 그려집니다. 폭은 부모가 정합니다 — 머리는 받은 폭을 채웁니다.

Live Preview#

구조 (Anatomy)#

제목과 행동 두 칸입니다. 제목이 행동이 남긴 폭을 모두 차지합니다.

부분역할슬롯
title 패널 제목 — headingLevel 단계의 머리글 title
action 줄 끝의 행동 하나 — 보통 Button(variant: .plain, size: .sm) + 뒤 chevron-right action

Figma: ❖ PanelHeader

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 카드 · 패널 · 섹션 하나의 제목 줄에 "모두 보기" · "추가" 같은 작은 행동을 함께 둘 때
  • 페이지 제목 아래 단계(머리글 2단계)의 제목이 필요할 때

대신 다른 컴포넌트를 사용하세요:

  • PageHeader: 페이지 맨 위의 머리 — 보조 설명 · 큰 제목 · 설명과 여러 행동

Props / Parameters#

W 는 Flutter의 Widget / Web의 Jaspr Component입니다.

속성타입기본값설명
title W 필수 패널 제목. headingLevel 단계의 머리글로 읽힙니다
action W? null 줄 끝의 행동 하나. 없으면 제목만 그립니다
headingLevel int CorePanelHeaderContract.defaultHeadingLevel(2) 제목의 머리글 단계 1–6. 페이지 제목 아래의 패널은 보통 2 입니다
panelHeaderStyle CorePanelHeaderStyle? null 모든 chrome 이 지나는 단일 슬롯 (아래 표 참고)

CorePanelHeaderStyle 필드#

필드타입설명
titleTextStyle CoreTextStyle? Ambient text style of the title slot, merged on top of [defaultTitleTextStyle].
padding CoreEdgeInsets? Inset around the row (pre-scaling). null → [defaultPadding].
titleActionSpacing double? Space between the title and the action — across the row while they sit side by side, and down the column once the action has moved under the title (logical px, pre-scaling). null → [defaultTitleActionSpacing].
minTitleWidth double? The narrowest the title may get beside the action (logical px, pre-scaling). Below it the action moves under the title. null → [defaultMinTitleWidth].

스타일 시스템 (Style System)#

panelHeaderStyle#

  • 시맨틱 / behaviour: 위젯 파라미터로 직접 (headingLevel)
  • chrome / dimensional: CorePanelHeaderStyle 한 곳으로 (제목 글자 스타일, 머리 여백, 제목과 행동 사이 간격, 제목의 최소 폭)

제목은 titleMedium(18 · Bold) · onSurface 입니다. 머리 여백은 아래쪽 8(패널 본문과의 간격), 제목과 행동 사이(titleActionSpacing)는 8 입니다. 둘은 간격 사다리 위의 값이라 밀도(CoreDensity)가 한 칸 옮깁니다. 제목의 최소 폭(minTitleWidth) 128 은 폭이라 움직이지 않습니다.

Resolve chain

CorePanelHeaderStyle.defaultX            // 기본값
  → CorePanelHeaderTheme.style           // 프로젝트 공통
  → widget.panelHeaderStyle              // 인스턴스별

사용 예

CoreComponentTheme(
  panelHeader: CorePanelHeaderTheme(
    style: CorePanelHeaderStyle(
      padding: CoreEdgeInsets.only(bottom: CoreSpace.space12),
    ),
  ),
)

빠른 오버라이드 (Chain)

이미 만든 PanelHeader 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

class PanelHeaderChainExample extends StatelessWidget {
  const PanelHeaderChainExample({super.key});

  @override
  Widget build(BuildContext context) {
    return PanelHeader(
      title: const Text('Recent files'),
      action: Button(
        variant: .plain,
        size: .sm,
        onPressed: () {},
        child: const Text('View all'),
        trailing: const Icon(
          LucideIcons.chevronRight,
          iconStyle: CoreIconStyle(size: CoreIconSize.size14),
        ),
      ),
    ).withStyle(
      const CorePanelHeaderStyle(
        titleTextStyle: CoreTextStyle.token(CoreTextStyles.titleSmall),
        padding: CoreEdgeInsets.only(bottom: CoreSpace.space12),
      ),
    );
  }
}
class PanelHeaderChainExample extends StatelessComponent {
  const PanelHeaderChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return PanelHeader(
      title: const Text('Recent files'),
      action: Button(
        variant: .plain,
        size: .sm,
        onPressed: () {},
        child: const Text('View all'),
        trailing: const Icon(
          LucideIcons.chevronRight,
          iconStyle: CoreIconStyle(size: CoreIconSize.size14),
        ),
      ),
    ).withStyle(
      const CorePanelHeaderStyle(
        titleTextStyle: CoreTextStyle.token(CoreTextStyles.titleSmall),
        padding: CoreEdgeInsets.only(bottom: CoreSpace.space12),
      ),
    );
  }
}

동작 스펙 (Behavior)#

배치#

  • 나란히: 행동이 자기 폭을 차지하고, 제목이 남은 폭을 모두 차지합니다. 둘은 세로 가운데가 맞습니다 — 제목 한 줄과 sm 버튼(32)이 한 줄로 읽힙니다
  • 행동이 아래로: 제목이 받을 폭이 minTitleWidth(128)보다 좁아지면 행동이 제목 아래로 내려가 시작 쪽에 붙습니다. 제목과의 간격은 나란할 때와 같은 titleActionSpacing 입니다
  • 방향: 오른쪽에서 왼쪽으로 쓰는 로케일에서 두 배치가 함께 뒤집힙니다
  • 폭이 정해지지 않은 자리: 폭이 정해지지 않은 자리에서는 두 플랫폼 모두 제목을 내용 폭으로 잽니다 — 제목이 minTitleWidth 보다 짧으면 행동이 아래로 내려갑니다. 머리는 폭이 정해진 패널에 놓습니다

인터랙션#

  • 머리 자체는 입력을 받지 않습니다 — 행동의 동작은 행동이 가집니다

상태 전환#

  • 상태가 없습니다

애니메이션#

  • 없습니다

사용 가이드라인 (Usage Guidelines)#

Do#

행동은 작고 조용하게

PanelHeader(
  title: const Text('Recent files'),
  action: Button(
    variant: .plain,
    size: .sm,
    onPressed: viewAll,
    child: const Text('View all'),
    trailing: const Icon(
      LucideIcons.chevronRight,
      iconStyle: CoreIconStyle(size: CoreIconSize.size14),
    ),
  ),
)

이유: 패널 머리의 행동은 그 패널에 대한 보조 행동입니다. plain · sm 이면 바탕이 없어 제목 줄의 높이를 키우지 않고, 페이지의 주 행동(PageHeader 의 primary)과 겨루지 않습니다.

Don't#

행동을 여러 개 이어 붙이지 않는다

// ❌ action 은 하나다 — Row 로 묶어 넣으면 좁은 폭에서 묶음째 내려가 줄을 바꾸지 못한다
PanelHeader(title: const Text('Files'), action: Row(children: [a, b, c]))

// ✅ 행동이 여럿이면 페이지 머리의 행동으로 올리거나, 메뉴 하나로 접는다
PanelHeader(title: const Text('Files'), action: moreMenuButton)

이유: 패널 머리는 행동 하나를 위한 자리입니다. 여러 행동의 줄 바꿈은 PageHeader 가 가집니다.

접근성 (Accessibility)#

역할#

  • Flutter: 제목을 Semantics(headingLevel:) 로 감쌉니다 — 웹에서는 aria-level, Android 에서는 isHeading 으로 나갑니다. header: true 는 쓰지 않습니다 — 이 킷에서 header: 는 페이지 배너 랜드마크의 몫입니다
  • Web: 제목이 <h1>…<h6> 요소(headingLevel, 기본 <h2>)입니다. 브라우저 기본 여백은 지웁니다

키보드#

키FlutterWeb
Tab행동으로행동으로

머리 자체는 포커스를 받지 않습니다.

스크린 리더#

제목(머리글) → 행동 순서로 읽힙니다. 상태 통보(live region)는 없습니다.

포커스 관리#

  • 진입 · 이탈 · 트랩: 없음

알려진 제약#

  • 머리글 단계는 호출자가 고릅니다. 기본값 2 는 페이지 제목(1) 바로 아래의 패널을 가정합니다

전역 축(동작 줄이기 · 고대비 · 색 강제 모드 · 시맨틱 역할 · 최소 터치 타겟)은 접근성 페이지가 한 곳에서 다룹니다.

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명PanelHeaderPanelHeader
배치 렌더 오브젝트( HeaderWrapLayout , PageHeader 와 같은 것)가 행동의 폭을 재고, 제목에 남는 폭이 minTitleWidth 보다 좁으면 행동을 아래로 내립니다 flex-wrap 줄 — 제목의 flex-basis 가 minTitleWidth , 하나의 gap 이 가로 · 세로 간격입니다
머리글 Semantics(headingLevel:) <h1>…<h6>
최소 폭의 단위논리 px × 레이아웃 배율rem — 브라우저 글자 배율을 따릅니다
크기 질의 IntrinsicWidth · IntrinsicHeight · Table 안에서도 답합니다 해당 없음 — 브라우저 배치
Web 전용 HTML 표면 — attributes · classes · css · eventHandlers · id 통과

위 차이 밖에는 없습니다 — 이름 · 순서 · 기본값을 두 위젯 생성자에서 대조했습니다.

  • PageHeader: 페이지 맨 위의 머리 — 같은 배치 규칙
  • Card: 머리를 header 슬롯에 놓는 패널
  • Button: action 에 놓는 행동

조합 예제#

Card(
  header: PanelHeader(
    title: const Text('Members'),
    action: Button(
      variant: .plain,
      size: .sm,
      onPressed: invite,
      child: const Text('Invite'),
    ),
  ),
  body: const Text('3 members'),
)