심화 가이드
접근성 지원

메뉴

개요

메뉴는 사용자에게 작업이나 옵션 목록을 제공하며, 일반적으로 버튼을 클릭하거나 마우스 오른쪽 버튼을 클릭하면 나타납니다. 메뉴는 방향키를 이용한 키보드 탐색, 하위 메뉴, 체크박스, 라디오 버튼, 비활성화된 항목을 지원합니다.

사용법

메뉴는 사용자가 고를 수 있는 작업이나 명령 목록을 제공할 때 적합합니다.

메뉴를 사용하기 좋은 경우:

  • 애플리케이션 명령 메뉴(File, Edit, View)를 만들 때
  • 컨텍스트 메뉴(마우스 오른쪽 버튼 클릭으로 실행하는 작업)를 만들 때
  • 드롭다운 작업 목록을 표시할 때
  • 툴바 드롭다운을 구현할 때
  • 설정이나 옵션을 정리할 때

메뉴를 피해야 하는 경우:

  • 사이트 탐색 기능을 만들 때(탐색 랜드마크를 사용하세요)
  • 폼의 선택 컨트롤을 만들 때(선택 컴포넌트를 사용하세요)
  • 콘텐츠 패널 사이를 전환할 때(탭을 사용하세요)
  • 접을 수 있는 콘텐츠를 표시할 때(아코디언을 사용하세요)

주요 기능

  • 키보드 탐색: 방향키, Home/End, 문자 검색으로 효율적으로 탐색합니다.
  • 하위 메뉴: 자동 위치 지정을 지원하는 중첩 메뉴를 제공합니다.
  • 메뉴 유형: 독립형 메뉴, 트리거로 여는 메뉴, 메뉴바를 지원합니다.
  • 체크박스와 라디오 버튼: 상태를 전환하거나 선택하는 메뉴 항목을 제공합니다.
  • 비활성화된 항목: 포커스가 가능한 비활성화 상태와 포커스를 받지 않는 비활성화 상태를 지원합니다.
  • 자동 닫기 동작: 선택 시 닫기 동작을 설정할 수 있습니다.
  • RTL 지원: 오른쪽에서 왼쪽으로 쓰는 언어의 탐색을 지원합니다.

예제

트리거가 있는 메뉴

트리거 버튼과 메뉴를 연결해서 드롭다운 메뉴를 만듭니다. 트리거는 메뉴를 열고 닫습니다.

사용자가 항목을 선택하거나 Escape를 누르면 메뉴가 자동으로 닫힙니다.

컨텍스트 메뉴

사용자가 엘리먼트를 마우스 오른쪽 버튼으로 클릭하면 커서 위치에 컨텍스트 메뉴가 나타납니다.

contextmenu 이벤트의 좌표로 메뉴 위치를 지정하세요.

독립형 메뉴

독립형 메뉴는 트리거 없이 인터페이스에 계속 표시됩니다.

독립형 메뉴는 항상 표시되는 작업 목록이나 탐색 기능에 적합합니다.

비활성화된 메뉴 항목

disabled 입력 프로퍼티로 특정 메뉴 항목을 비활성화하세요. 포커스 동작은 softDisabled로 제어합니다.

[softDisabled]="true"이면 비활성화된 항목도 포커스를 받을 수 있지만 활성화할 수는 없습니다. [softDisabled]="false"이면 키보드 탐색에서 비활성화된 항목을 건너뜁니다.

테스트

Angular Aria는 메뉴 컴포넌트를 테스트할 수 있는 컴포넌트 하네스를 제공합니다. 컴포넌트 테스트에서 하네스를 사용하는 예제는 다음과 같습니다.

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {MenuHarness, MenuItemHarness} from '@angular/aria/menu/testing';
import {MyMenuComponent} from './my-menu'; // 테스트할 컴포넌트

describe('MyMenuComponent', () => {
  let fixture: ComponentFixture<MyMenuComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyMenuComponent],
    });

    fixture = TestBed.createComponent(MyMenuComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should open menu and click item', async () => {
    // 트리거 텍스트로 메뉴 하네스를 불러옵니다.
    const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));

    // 초기 상태를 확인합니다.
    expect(await menu.isOpen()).toBe(false);

    // 메뉴를 엽니다.
    await menu.open();
    expect(await menu.isOpen()).toBe(true);

    // 항목을 가져옵니다.
    const items = await menu.getItems();
    expect(items.length).toBe(3);
    expect(await items[0].getText()).toBe('Item 1');

    // 첫 번째 항목을 클릭합니다.
    await items[0].click();

    // 선택 후 메뉴가 닫혀야 합니다(구현에 따라 다를 수 있습니다).
    expect(await menu.isOpen()).toBe(false);
  });

  it('should interact with submenus', async () => {
    const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));
    await menu.open();

    // 하위 메뉴를 여는 항목을 가져옵니다.
    const subItem = await loader.getHarness(MenuItemHarness.with({text: 'Submenu'}));
    expect(await subItem.hasSubmenu()).toBe(true);

    // 하위 메뉴를 엽니다.
    await subItem.click();
    const submenu = await subItem.getSubmenu();
    expect(submenu).toBeTruthy();
    expect(await submenu!.isOpen()).toBe(true);

    // 하위 메뉴 항목을 조작합니다.
    const subItems = await submenu!.getItems();
    expect(subItems.length).toBe(1);
  });
});

API 레퍼런스

자세한 API 문서는 다음 API 레퍼런스를 참고하세요.