페이지 레이아웃과 패널
GitLab v19.1**정적 패널(static panel)**은 페이지의 기본 컨텍스트입니다. 동적 패널(dynamic panel)(선택 사항)은 정적 패널의 컨텍스트 내에서 상세 정보를 표시하기 위한 패널입니다. AI 패널(AI panel)(접을 수 있음)은 지능형 기능을 위한 패널입니다.
페이지는 세 가지 패널로 나뉩니다:
-
**정적 패널(static panel)**은 페이지의 기본 컨텍스트입니다. 모든 표준 애플리케이션 페이지에서 자동으로 사용됩니다.
-
동적 패널(dynamic panel)(선택 사항)은 정적 패널의 컨텍스트 내에서 상세 정보를 표시하기 위한 패널입니다. 개별 기능이 Vue 애플리케이션을 이 패널에 마운트하여 컨텍스트에 맞는 콘텐츠를 표시합니다.
-
AI 패널(AI panel)(접을 수 있음)은 지능형 기능을 위한 패널입니다.
페이지 구조에 대한 자세한 내용은 디자인 시스템 문서를 참고하세요.
정적 패널 내에서 인덱스 레이아웃과 상세 레이아웃 컴포넌트는 페이지 제목, 알림, 콘텐츠 영역에 일관된 간격과 구조를 제공합니다.
패널#
정적 패널#
Layouts::StaticPanelComponent ViewComponent는 메인 콘텐츠 영역을 감쌉니다.
모든 표준 애플리케이션 페이지에서 자동으로 사용됩니다.
새 페이지를 만들 때 명시적으로 적용할 필요가 없습니다.
동적 패널#
DynamicPanel Vue 컴포넌트는 동적 패널의 구조(헤더, 액션, 콘텐츠 영역 포함)를 정의합니다.
mount-to="#contextual-panel-portal" prop과 append prop을 사용하여 MountingPortal의 직접 자식으로 사용하세요.
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| header | String | null | 헤더 텍스트. header 슬롯이 제공되면 슬롯이 우선합니다. |
| maximizeUrl | String | null | 설정 시 해당 URL로 연결되는 최대화 버튼이 렌더링됩니다. |
슬롯:
| Slot | Description |
|---|---|
| Default | 패널 본문 메인 콘텐츠. |
| header | 커스텀 헤더 마크업. header prop보다 우선합니다. |
| actions | 패널 헤더 액션. 자세한 내용은 패널 액션을 참고하세요. |
| footer | 패널 푸터 콘텐츠. 이 슬롯에 콘텐츠가 있을 때만 푸터가 렌더링됩니다. |
이벤트:
| Event | Payload | Description |
|---|---|---|
| close | None | 닫기 버튼 클릭 시 발생합니다. |
| maximize | MouseEvent | 최대화 버튼 클릭 시 발생합니다. |
예시:
<script>
import DynamicPanel from '~/vue_shared/components/dynamic_panel.vue';
export default {
components: { DynamicPanel },
methods: {
onClose() {
// handle close
},
},
};
</script>
<template>
<mounting-portal mount-to="#contextual-panel-portal" append>
<dynamic-panel header="Example" @close="onClose">
<!-- Content goes here -->
</dynamic-panel>
</mounting-portal>
</template>
커스텀 헤더, 최대화 버튼, 액션이 있는 예시:
<template>
<mounting-portal mount-to="#contextual-panel-portal" append>
<dynamic-panel :maximize-url="fullUrlToEntity" @close="onClose" @maximize="onMaximize">
<template #header>
{{ entityName }}
</template>
<template #actions>
<gl-button
v-gl-tooltip.bottom="__('Example action')"
category="tertiary"
icon="remove"
size="small"
:aria-label="__('Example action')"
@click="onAction"
/>
</template>
<!-- Content goes here -->
</dynamic-panel>
</mounting-portal>
</template>
패널 액션#
패널 액션은 패널 헤더에 렌더링되는 아이콘 버튼으로, 내장된 닫기 및 최대화 버튼의 왼쪽에 위치합니다.
컨텍스트에 따라 세 가지 접근 방식을 사용할 수 있습니다:
1. 동적 패널 actions 슬롯
컴포넌트가 DynamicPanel의 직접 소비자일 때 사용합니다:
<template>
<dynamic-panel header="Details" @close="onClose">
<template #actions>
<gl-button
v-gl-tooltip.bottom="$options.i18n.editLabel"
category="tertiary"
icon="pencil"
size="small"
:aria-label="$options.i18n.editLabel"
@click="onEdit"
/>
</template>
<detail-view />
</dynamic-panel>
</template>
2. 정적 패널 static_panel_actions 콘텐츠 영역 (HAML)
정적 패널에서 액션을 렌더링할 때 HAML 뷰 파일에서 사용합니다:
- content_for :static_panel_actions do
= link_button_to _("Example action"), path_to_action, category: :tertiary, size: :small
액션이 단순 링크가 아닌 경우 PanelActionsPortal 사용을 고려하세요.
3. PanelActionsPortal Vue 컴포넌트
다음 중 하나 이상이 해당되는 경우 PanelActionsPortal을 사용합니다:
-
정적 패널에 렌더링할 액션이 단순 링크가 아니며 클라이언트 측 동작(예: 버튼 또는 드롭다운)이 필요한 경우.
-
액션이 컴포넌트 트리 깊숙이 정의되어 있어
DynamicPanel의actions슬롯에 전달하기 어려운 경우. -
애플리케이션이 정적 패널과 동적 패널 모두에서 렌더링되는 경우(예: work items).
<script>
import PanelActionsPortal from '~/vue_shared/components/panel_actions_portal.vue';
export default {
components: { PanelActionsPortal },
};
</script>
<template>
<panel-actions-portal>
<gl-button category="tertiary" size="small" @click="onAction">
{{ __('Example action') }}
</gl-button>
</panel-actions-portal>
</template>
Import 경로: ~/vue_shared/components/panel_actions_portal.vue.
패널 액션 가이드라인#
패널 액션 영역에 버튼을 추가할 때는 다음 규칙을 따르세요:
- 버튼(또는 버튼처럼 보이는 링크)만 사용하세요. 예:
GlButton (Vue)
-
GlDisclosureDropdown(Vue) -
Pajamas::ButtonComponent(HAML/Ruby) -
link_button_to(HAML/Ruby) -
버튼은
category="tertiary"및size="small"을 사용해야 합니다. -
아이콘 전용 버튼은 다음을 충족해야 합니다:
버튼 아래에 표시되는 툴팁 포함 (v-gl-tooltip.bottom="...")
-
툴팁과 동일한 문자열로
aria-label설정. -
액션이 4개 이상인 경우, 자주 사용하지 않는 액션들을
ellipsis_h아이콘을 사용하는 "More actions" 아이콘 전용 드롭다운으로 묶고no-caret,category="tertiary",size="small"옵션을 함께 사용하세요. -
페이지의 엔트리 템플릿에서
@force_show_panel_header = true를 설정하세요. 예시는app/views/groups/observability/show.html.haml#L2를 참고하세요. 이렇게 하면page_breadcrumbs_in_top_bar기능 플래그 값에 관계없이 정적 패널의 헤더가 항상 렌더링됩니다.
"More actions" 드롭다운 예시:
<template>
<dynamic-panel header="Details" @close="onClose">
<template #actions>
<gl-button
v-gl-tooltip.bottom="__('Edit')"
category="tertiary"
icon="pencil"
size="small"
:aria-label="__('Edit')"
@click="onEdit"
/>
<gl-disclosure-dropdown
v-gl-tooltip.bottom="__('More actions')"
icon="ellipsis_h"
category="tertiary"
size="small"
no-caret
:toggle-aria-label="__('More actions')"
:items="moreActions"
/>
</template>
<detail-view />
</dynamic-panel>
</template>
레이아웃#
레이아웃 컴포넌트는 패널의 콘텐츠 영역 내에서 일관된 간격과 구조를 제공합니다. ViewComponent와 Vue 컴포넌트 두 가지 버전이 모두 제공됩니다.
인덱스 레이아웃#
엔티티 목록을 표시하는 페이지에 인덱스 레이아웃을 사용합니다. 페이지 제목, 알림, 메인 콘텐츠 영역에 일관된 구조를 제공합니다.
Parameters:
인덱스 레이아웃을 참고하세요.
슬롯:
인덱스 레이아웃을 참고하세요.
HAML (Layouts::IndexLayout)#
예시:
= render ::Layouts::IndexLayout.new(heading: _('Tokens'), description: _('Manage your tokens.')) do |c|
- c.with_alerts do
= render Pajamas::AlertComponent.new(variant: :danger, title: _('Failed to create token.'))
= render 'tokens_table'
자세한 내용은 인덱스 레이아웃을 참고하세요.
Vue (IndexLayout)#
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| heading | String | null | 페이지 제목 텍스트. |
| headingTag | String | null | 제목 요소 태그: 'h1' 또는 'h2'. 컨텍스트에서 제공한 태그로 기본 설정됩니다. |
| description | String | null | 페이지 설명 텍스트. |
| loading | Boolean | false | true일 때 콘텐츠 대신 로딩 아이콘을 렌더링합니다. |
| pageHeadingSrOnly | Boolean | false | true일 때 페이지 제목을 시각적으로 숨깁니다. |
슬롯:
| Slot | Description |
|---|---|
| before | 페이지 제목 앞에 렌더링되는 콘텐츠. |
| heading-wrapper | 제목 요소 전체를 대체합니다. |
| heading | 커스텀 제목 마크업. |
| description | 커스텀 설명 마크업. |
| alerts | 페이지 알림. 슬롯이 제공될 때만 렌더링됩니다. |
| loading | 커스텀 로딩 상태. 제공하지 않으면 GlLoadingIcon으로 대체됩니다. |
| Default | 페이지 메인 콘텐츠. |
예시:
<script>
import IndexLayout from '~/vue_shared/components/index_layout.vue';
export default {
components: { IndexLayout },
};
</script>
<template>
<index-layout :heading="$options.i18n.heading" :description="$options.i18n.description">
<template v-if="hasAlerts" #alerts>
<gl-alert v-if="error" variant="danger" @dismiss="onDismissError">
{{ errorMessage }}
</gl-alert>
</template>
<tokens-table :tokens="tokens" />
</index-layout>
</template>
상세 레이아웃#
상세 페이지 또는 표시 페이지에 상세 레이아웃을 사용합니다.
인덱스 레이아웃에 sidebar 슬롯이 추가된 형태입니다.
HAML (Layouts::DetailLayout)#
Parameters:
상세 레이아웃을 참고하세요.
슬롯:
상세 레이아웃과 동일하며, 추가로:
| Slot | Description |
|---|---|
| sidebar | 사이드바 콘텐츠. |
예시:
= render ::Layouts::DetailLayout.new(heading: _('Page title'), description: _('Page description')) do |c|
- c.with_alerts do
= render Pajamas::AlertComponent.new(title: 'Alert message')
- c.with_sidebar do
= render 'sidebar'
= render 'items_table'
자세한 내용은 상세 레이아웃을 참고하세요.
Vue (DetailLayout)#
Props:
IndexLayout과 동일합니다(인덱스 레이아웃 참고).
슬롯:
IndexLayout과 동일하며, 추가로:
| Slot | Description |
|---|---|
| sidebar | 사이드바 콘텐츠. |
예시:
<script>
import DetailLayout from '~/vue_shared/components/detail_layout.vue';
export default {
components: { DetailLayout },
};
</script>
<template>
<detail-layout :heading="token.name">
<template #sidebar>
<token-metadata :token="token" />
</template>
<token-body :token="token" />
</detail-layout>
</template>