InfoGrab DocsInfoGrab Docs

페이지 레이아웃과 패널

요약

페이지 구조에 대한 자세한 내용은 Design System 문서를 참고합니다. static panel 안에서 index 레이아웃과 detail 레이아웃 컴포넌트는 페이지 제목, 알림, 콘텐츠 영역에 일관된 간격과 구조를 제공합니다.

페이지는 세 개의 패널로 나뉩니다.

  • static panel은 페이지의 기본 컨텍스트입니다. 모든 표준 애플리케이션 페이지가 자동으로 사용합니다.
  • dynamic panel(선택 사항)은 static panel의 컨텍스트에서 상세 정보를 표시합니다. 각 기능은 여기에 Vue 애플리케이션을 마운트하여 컨텍스트에 맞는 콘텐츠를 표시합니다.
  • AI panel(접을 수 있음)은 지능형 기능을 위한 패널입니다.

페이지 구조에 대한 자세한 내용은 Design System 문서를 참고합니다.

static panel 안에서 index 레이아웃과 detail 레이아웃 컴포넌트는 페이지 제목, 알림, 콘텐츠 영역에 일관된 간격과 구조를 제공합니다.

패널#

정적 패널#

Layouts::StaticPanelComponent ViewComponent는 주요 콘텐츠 영역을 감쌉니다. 모든 표준 애플리케이션 페이지가 자동으로 사용합니다. 새 페이지를 만들 때 명시적으로 적용할 필요는 없습니다.

동적 패널#

DynamicPanel Vue 컴포넌트는 헤더, 액션, 콘텐츠 영역을 포함해 dynamic panel의 구조를 정의합니다. MountingPortal의 직계 자식으로 사용하며, mount-to="#contextual-panel-portal"과 append prop을 함께 지정합니다.

Props:

Prop Type Default Description
header String null 헤더 텍스트입니다. header 슬롯이 제공되면 슬롯이 우선합니다.
maximizeUrl String null 설정하면 이 URL로 연결되는 최대화 버튼이 렌더링됩니다.

Slots:

Slot Description
Default 패널 본문의 주요 콘텐츠입니다.
header 커스텀 헤더 마크업입니다. header prop보다 우선합니다.
actions 패널 헤더 액션입니다. 자세한 내용은 패널 액션을 참고합니다.
footer 패널 푸터 콘텐츠입니다. 이 슬롯에 콘텐츠가 있을 때만 푸터가 렌더링됩니다.

Events:

Event Payload Description
close None 닫기 버튼을 클릭할 때 발생합니다.
maximize MouseEvent 최대화 버튼을 클릭할 때 발생합니다.

Example:

<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>
        <span class="panel-header-inner-text">{{ entityName }}</span>
      </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. dynamic panel의 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의 static_panel_actions 콘텐츠 영역(HAML)

static panel에 액션을 렌더링할 때 HAML 뷰 파일에서 이 방식을 사용합니다.

- content_for :static_panel_actions do
  = link_button_to _("Example action"), path_to_action, category: :tertiary, size: :small

액션이 단순 링크가 아니라면 PanelActionsPortal 사용을 검토합니다.

3. PanelActionsPortal Vue 컴포넌트

다음 중 하나 이상에 해당하면 PanelActionsPortal을 사용합니다.

  • static panel에 렌더링할 액션이 단순 링크가 아니라 버튼이나 드롭다운처럼 클라이언트 측 동작이 필요한 경우입니다.
  • 액션이 컴포넌트 트리 깊은 곳에 정의되어 있어 DynamicPanel의 actions 슬롯으로 전달하기 어려운 경우입니다.
  • 작업 항목처럼 애플리케이션이 static panel과 dynamic panel 양쪽에 렌더링되는 경우입니다.
<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>

임포트 경로는 ~/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을 툴팁과 같은 문자열로 설정합니다.
  • 액션이 네 개 이상이면 자주 쓰지 않는 액션을 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 기능 플래그 값과 무관하게 static panel의 헤더가 항상 렌더링됩니다.

"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:

인덱스 레이아웃을 참고합니다.

Slots:

인덱스 레이아웃을 참고합니다.

HAML (Layouts::IndexLayout)#

Example:

= 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 이면 페이지 제목을 시각적으로 숨깁니다.

Slots:

Slot Description
before 페이지 제목 앞에 렌더링되는 콘텐츠입니다.
heading-wrapper 제목 요소 전체를 대체합니다.
heading 커스텀 제목 마크업입니다.
description 커스텀 설명 마크업입니다.
alerts 페이지 알림입니다. 슬롯이 제공될 때만 렌더링됩니다.
loading 커스텀 로딩 상태입니다. 제공하지 않으면 GlLoadingIcon으로 대체됩니다.
Default 페이지의 주요 콘텐츠입니다.

Example:

<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>

상세 레이아웃#

상세 페이지나 show 페이지에는 상세 레이아웃을 사용합니다. 인덱스 레이아웃에 sidebar 슬롯을 더한 형태입니다.

HAML (Layouts::DetailLayout)#

Parameters:

상세 레이아웃을 참고합니다.

Slots:

상세 레이아웃과 같으며, 다음이 추가됩니다.

Slot Description
sidebar 사이드바 콘텐츠입니다.

Example:

= 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과 같습니다(인덱스 레이아웃 참고).

Slots:

IndexLayout과 같으며, 다음이 추가됩니다.

Slot Description
sidebar 사이드바 콘텐츠입니다.

Example:

<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>

페이지 레이아웃과 패널

GitLab v19.4
원문 보기

요약

페이지 구조에 대한 자세한 내용은 Design System 문서를 참고합니다. static panel 안에서 index 레이아웃과 detail 레이아웃 컴포넌트는 페이지 제목, 알림, 콘텐츠 영역에 일관된 간격과 구조를 제공합니다.

페이지는 세 개의 패널로 나뉩니다.

  • static panel은 페이지의 기본 컨텍스트입니다. 모든 표준 애플리케이션 페이지가 자동으로 사용합니다.
  • dynamic panel(선택 사항)은 static panel의 컨텍스트에서 상세 정보를 표시합니다. 각 기능은 여기에 Vue 애플리케이션을 마운트하여 컨텍스트에 맞는 콘텐츠를 표시합니다.
  • AI panel(접을 수 있음)은 지능형 기능을 위한 패널입니다.

페이지 구조에 대한 자세한 내용은 Design System 문서를 참고합니다.

static panel 안에서 index 레이아웃과 detail 레이아웃 컴포넌트는 페이지 제목, 알림, 콘텐츠 영역에 일관된 간격과 구조를 제공합니다.

패널#

정적 패널#

Layouts::StaticPanelComponent ViewComponent는 주요 콘텐츠 영역을 감쌉니다. 모든 표준 애플리케이션 페이지가 자동으로 사용합니다. 새 페이지를 만들 때 명시적으로 적용할 필요는 없습니다.

동적 패널#

DynamicPanel Vue 컴포넌트는 헤더, 액션, 콘텐츠 영역을 포함해 dynamic panel의 구조를 정의합니다. MountingPortal의 직계 자식으로 사용하며, mount-to="#contextual-panel-portal"과 append prop을 함께 지정합니다.

Props:

Prop Type Default Description
header String null 헤더 텍스트입니다. header 슬롯이 제공되면 슬롯이 우선합니다.
maximizeUrl String null 설정하면 이 URL로 연결되는 최대화 버튼이 렌더링됩니다.

Slots:

Slot Description
Default 패널 본문의 주요 콘텐츠입니다.
header 커스텀 헤더 마크업입니다. header prop보다 우선합니다.
actions 패널 헤더 액션입니다. 자세한 내용은 패널 액션을 참고합니다.
footer 패널 푸터 콘텐츠입니다. 이 슬롯에 콘텐츠가 있을 때만 푸터가 렌더링됩니다.

Events:

Event Payload Description
close None 닫기 버튼을 클릭할 때 발생합니다.
maximize MouseEvent 최대화 버튼을 클릭할 때 발생합니다.

Example:

<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>
        <span class="panel-header-inner-text">{{ entityName }}</span>
      </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. dynamic panel의 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의 static_panel_actions 콘텐츠 영역(HAML)

static panel에 액션을 렌더링할 때 HAML 뷰 파일에서 이 방식을 사용합니다.

- content_for :static_panel_actions do
  = link_button_to _("Example action"), path_to_action, category: :tertiary, size: :small

액션이 단순 링크가 아니라면 PanelActionsPortal 사용을 검토합니다.

3. PanelActionsPortal Vue 컴포넌트

다음 중 하나 이상에 해당하면 PanelActionsPortal을 사용합니다.

  • static panel에 렌더링할 액션이 단순 링크가 아니라 버튼이나 드롭다운처럼 클라이언트 측 동작이 필요한 경우입니다.
  • 액션이 컴포넌트 트리 깊은 곳에 정의되어 있어 DynamicPanel의 actions 슬롯으로 전달하기 어려운 경우입니다.
  • 작업 항목처럼 애플리케이션이 static panel과 dynamic panel 양쪽에 렌더링되는 경우입니다.
<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>

임포트 경로는 ~/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을 툴팁과 같은 문자열로 설정합니다.
  • 액션이 네 개 이상이면 자주 쓰지 않는 액션을 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 기능 플래그 값과 무관하게 static panel의 헤더가 항상 렌더링됩니다.

"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:

인덱스 레이아웃을 참고합니다.

Slots:

인덱스 레이아웃을 참고합니다.

HAML (Layouts::IndexLayout)#

Example:

= 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 이면 페이지 제목을 시각적으로 숨깁니다.

Slots:

Slot Description
before 페이지 제목 앞에 렌더링되는 콘텐츠입니다.
heading-wrapper 제목 요소 전체를 대체합니다.
heading 커스텀 제목 마크업입니다.
description 커스텀 설명 마크업입니다.
alerts 페이지 알림입니다. 슬롯이 제공될 때만 렌더링됩니다.
loading 커스텀 로딩 상태입니다. 제공하지 않으면 GlLoadingIcon으로 대체됩니다.
Default 페이지의 주요 콘텐츠입니다.

Example:

<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>

상세 레이아웃#

상세 페이지나 show 페이지에는 상세 레이아웃을 사용합니다. 인덱스 레이아웃에 sidebar 슬롯을 더한 형태입니다.

HAML (Layouts::DetailLayout)#

Parameters:

상세 레이아웃을 참고합니다.

Slots:

상세 레이아웃과 같으며, 다음이 추가됩니다.

Slot Description
sidebar 사이드바 콘텐츠입니다.

Example:

= 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과 같습니다(인덱스 레이아웃 참고).

Slots:

IndexLayout과 같으며, 다음이 추가됩니다.

Slot Description
sidebar 사이드바 콘텐츠입니다.

Example:

<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>