Skip to content

SelectΒ #4

Description

@hsskey

πŸ“‹ μ»΄ν¬λ„ŒνŠΈ 정보

  • μ»΄ν¬λ„ŒνŠΈ 이름: Select
  • μ»΄ν¬λ„ŒνŠΈ μ„€λͺ…: Select μ»΄ν¬λ„ŒνŠΈλŠ” μ‚¬μš©μžκ°€ μ œν•œλœ μ˜΅μ…˜ μ§‘ν•©μ—μ„œ ν•˜λ‚˜ μ΄μƒμ˜ μ˜΅μ…˜μ„ 선택할 수 μžˆλ„λ‘ ν•˜λŠ” 컨트둀
  • μ‚¬μš© 사둀: μ‚¬μš©μžμ—κ²Œ 선택 μ˜΅μ…˜μ„ μ œκ³΅ν•˜κ³ μž ν•  λ•Œ μ‚¬μš©. κ΅­κ°€ λͺ©λ‘, μΉ΄ν…Œκ³ λ¦¬ 선택, μ„€μ • λ“± λ‹€μ–‘ν•œ μƒν™©μ—μ„œ ν™œμš© κ°€λŠ₯

πŸ“š μ°Έμ‘° 및 레퍼런슀

  • ARIA Authoring Practices: W3C ARIA Patters
    • κ΄€λ ¨ νŒ¨ν„΄ 및 적용된 μ ‘κ·Όμ„± κΈ°μ€€ 확인
  • 기타 레퍼런슀: MUI, Ant Design λ“±μ˜ λ¬Έμ„œμ™€ λΉ„κ΅ν•˜μ—¬ νŒ¨ν„΄ 뢄석

πŸ“ μ ‘κ·Όμ„± 및 ARIA 적용 μ—¬λΆ€

  • ARIA Pattern 적용: ARIA Pattern 링크
    • μ‚¬μš©λœ ARIA 속성: role="combobox", aria-controls, aria-expanded, aria-activedescendant 등이 μ‚¬μš©λ¨. role="combobox"둜 μš”μ†Œλ₯Ό μ½€λ³΄λ°•μŠ€λ‘œ μ‹λ³„ν•˜κ³ , aria-controls둜 νŒμ—… μš”μ†Œλ₯Ό 참쑰함. aria-expandedλŠ” νŒμ—… μƒνƒœλ₯Ό λ‚˜νƒ€λ‚΄κ³ , aria-activedescendantλŠ” ν˜„μž¬ 포컀슀된 μ˜΅μ…˜μ„ 가리킴
  • μ ‘κ·Όμ„± κ΄€λ ¨ 고렀사항: ν‚€λ³΄λ“œ λ‚΄λΉ„κ²Œμ΄μ…˜ 및 슀크린 리더 μ‚¬μš©μ„± κ³ λ €. μ˜΅μ…˜ 선택 μ‹œ live region을 톡해 변경사항을 μ•ˆλ‚΄ν•˜κ³ , μ„ νƒλœ μ˜΅μ…˜μ— aria-selected="true" 적용
  • μ ‘κ·Όμ„± κΈ°λŠ₯ μš”μ•½: μ½€λ³΄λ°•μŠ€μ™€ νŒμ—…μ˜ 관계λ₯Ό ARIA μ†μ„±μœΌλ‘œ λͺ…ν™•νžˆ ν•˜κ³ , ν‚€λ³΄λ“œλ‘œ μ ‘κ·Ό κ°€λŠ₯ν•˜λ„λ‘ κ΅¬ν˜„. μƒνƒœ λ³€ν™”λ₯Ό 슀크린 리더에 μ•Œλ¦¬κΈ° μœ„ν•΄ live region ν™œμš©

πŸ—οΈ λ§ˆν¬μ—… ꡬ쑰 뢄석

  • HTML μ‹œλ§¨ν‹± νƒœκ·Έ μ‚¬μš©: <select> μš”μ†Œ μ‚¬μš©. λ„€μ΄ν‹°λΈŒ HTML μš”μ†Œλ₯Ό μ‚¬μš©ν•˜μ—¬ μ½€λ³΄λ°•μŠ€ 역할을 λ‚˜νƒ€λƒ„
  • λ§ˆν¬μ—… ꡬ쑰 비ꡐ:
// WAI-ARIA μ˜ˆμ‹œ
<div class="select">
  <div id="exp_elem" role="combobox" aria-expanded="false">
    <div role="textbox" aria-readonly="true"></div>
  </div>
  <ul role="listbox" aria-labelledby="exp_elem">
    <li role="option"></li>
  </ul>
</div>

// MUI
<div class="MuiSelect-root">
  <div class="MuiSelect-select" aria-expanded="false">
    <span>Selected option</span>
    <input value="" />
  </div>
  <svg class="MuiSvgIcon-root"></svg>
</div>

// Ant Design 
<div class="ant-select">
  <div class="ant-select-selector">
    <span class="ant-select-selection-item"></span>
    <span class="ant-select-arrow">
      <span class="ant-select-arrow-icon"></span>
    </span>
  </div>
</div>
  • ꡬ쑰적 선택 이유:
    • WAI-ARIA: ARIA μ—­ν• κ³Ό μƒνƒœλ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ œκ³΅ν•˜μ—¬ 접근성을 확보함. role="combobox"둜 μš”μ†Œμ˜ 역할을 λͺ…ν™•νžˆ ν•˜κ³ , aria-expanded둜 νŒμ—… μƒνƒœ 전달. μ˜΅μ…˜μ€ role="listbox" 내뢀에 role="option"으둜 제곡
    • MUI, Ant Design: <div> μš”μ†Œλ₯Ό μ‚¬μš©ν•˜μ—¬ μ½€λ³΄λ°•μŠ€ ꡬ쑰λ₯Ό ꡬ성함. WAI-ARIA에 λΉ„ν•΄ λͺ…μ‹œμ μΈ μ—­ν•  ν‘œμ‹œλŠ” μ μ§€λ§Œ, 클래슀λͺ…을 톡해 의미λ₯Ό νŒŒμ•…ν•  수 있음. 기본적인 접근성은 κ³ λ €ν•˜κ³  μžˆμœΌλ‚˜, 좔가적인 ARIA 속성 적용이 ν•„μš”ν•΄ λ³΄μž„

πŸ’‘ UI 라이브러리 비ꡐ 뢄석

  • MUI: <div> 기반 ꡬ쑰. κΈ°λ³Έ 접근성을 κ³ λ €ν•˜κ³  μžˆμœΌλ‚˜ ARIA 속성 적용이 λΆ€μ‘±ν•΄ λ³΄μž„. 클래슀λͺ…을 톡해 μ½€λ³΄λ°•μŠ€μ˜ 각 뢀뢄을 식별할 수 있음
  • Ant Design: <div> 기반 ꡬ쑰. 클래슀λͺ…μœΌλ‘œ μ½€λ³΄λ°•μŠ€μ˜ 각 뢀뢄을 ν‘œμ‹œ. ARIA 속성 적용이 거의 μ—†μ–΄ μ ‘κ·Όμ„± κ°œμ„ μ΄ ν•„μš”ν•΄ λ³΄μž„

βš™οΈ κ΅¬ν˜„ 및 섀계 고렀사항

  • μ£Όμš” 섀계 고렀사항: λ„€μ΄ν‹°λΈŒ <select> λ˜λŠ” <div> 기반 μ»€μŠ€ν…€ μ½€λ³΄λ°•μŠ€ κ΅¬ν˜„ μ‹œ, WAI-ARIA νŒ¨ν„΄μ— λ”°λ₯Έ λ§ˆν¬μ—…κ³Ό ν‚€λ³΄λ“œ μΈν„°λž™μ…˜ κ΅¬ν˜„μ΄ μ€‘μš”ν•¨. μƒνƒœ λ³€ν™”λ₯Ό live region으둜 μ•Œλ¦¬λŠ” 것도 μ ‘κ·Όμ„± ν–₯상에 도움이 됨
  • μ½”λ“œ μ˜ˆμ‹œ:
<div class="select">
  <div tabindex="0" 
       id="exp_elem" 
       role="combobox" 
       aria-labelledby="exp_label"
       aria-expanded="false" 
       aria-controls="exp_elem_list"
       aria-activedescendant=""
       aria-required="true">
    <span id="exp_label">Select an option</span>
    <div role="textbox" aria-readonly="true"></div>
  </div>
  <ul id="exp_elem_list" role="listbox">
    <li id="exp_elem_list_1" role="option">Option 1</li>
    <li id="exp_elem_list_2" role="option">Option 2</li>
    <li id="exp_elem_list_3" role="option">Option 3</li>
  </ul>
  <div aria-live="polite" aria-atomic="true" class="visually-hidden">
    Option 1 selected
  </div>
</div>
// μ½€λ³΄λ°•μŠ€ ν† κΈ€ ν•¨μˆ˜
function toggleSelectExpanded(select) {
  const expanded = select.getAttribute('aria-expanded') === 'true';
  select.setAttribute('aria-expanded', !expanded);
}

// μ˜΅μ…˜ 선택 ν•¨μˆ˜
function selectOption(select, option) {
  const selected = select.querySelector('[aria-selected="true"]');
  if (selected) {
    selected.setAttribute('aria-selected', 'false');
  }
  option.setAttribute('aria-selected', 'true');
  select.querySelector('div[role="textbox"]').textContent = option.textContent;
  select.setAttribute('aria-activedescendant', option.id);
  
  // Live region으둜 선택 사항 μ•Œλ¦Ό 
  const liveRegion = select.nextElementSibling;
  liveRegion.textContent = option.textContent + ' selected';
}

// ν‚€λ³΄λ“œ 이벀트 핸듀링
function handleKeyboard(event) {
  const { key } = event;
  const select = event.currentTarget;
  const options = select.nextElementSibling.querySelectorAll('[role="option"]');
  let activeIndex = Array.from(options).indexOf(document.getElementById(select.getAttribute('aria-activedescendant')));

  if (key === 'ArrowDown') {
    event.preventDefault();
    activeIndex = Math.min(activeIndex + 1, options.length - 1);
    selectOption(select, options[activeIndex]);
  } 
  else if (key === 'ArrowUp') {
    event.preventDefault();
    activeIndex = Math.max(activeIndex - 1, 0);
    selectOption(select, options[activeIndex]);
  }
  else if (key === 'Escape') {
    select.querySelector('div[role="textbox"]').textContent = '';
    select.setAttribute('aria-activedescendant', '');
    toggleSelectExpanded(select);
  }
}

πŸ” ν…ŒμŠ€νŠΈ 및 κ²€ν† 

  • μ ‘κ·Όμ„± ν…ŒμŠ€νŠΈ: ν‚€λ³΄λ“œ λ‚΄λΉ„κ²Œμ΄μ…˜ 및 슀크린 리더 ν˜Έν™˜μ„± ν…ŒμŠ€νŠΈ. μ˜΅μ…˜ 선택 μ‹œ live region을 톡해 μ μ ˆν•œ μ•ˆλ‚΄κ°€ μ œκ³΅λ˜λŠ”μ§€ 확인
  • λΈŒλΌμš°μ € ν˜Έν™˜μ„± ν…ŒμŠ€νŠΈ: μ£Όμš” λΈŒλΌμš°μ €μ—μ„œμ˜ λ Œλ”λ§ 및 κΈ°λŠ₯ λ™μž‘ 확인
  • μ‚¬μš©μž ν”Όλ“œλ°±: λ‹€μ–‘ν•œ μ‚¬μš©μžλ₯Ό λŒ€μƒμœΌλ‘œ μ‚¬μš©μ„± ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•˜κ³ , ν”Όλ“œλ°±μ„ μˆ˜λ ΄ν•˜μ—¬ κ°œμ„ 

πŸ“Œ μΆ”κ°€ μ°Έκ³  사항

🎯 μ—­ν• , 속성, μƒνƒœ 및 νƒœκ·Έ 속성

  • Role, Attribute, State
    • Role: role="combobox", role="textbox", role="listbox", role="option" μ‚¬μš©ν•˜μ—¬ μ»΄ν¬λ„ŒνŠΈμ˜ μ—­ν•  λͺ…μ‹œ
    • Attribute & State: aria-expanded, aria-controls, aria-activedescendant, aria-selected 등을 μ‚¬μš©ν•˜μ—¬ μ½€λ³΄λ°•μŠ€μ˜ μƒνƒœμ™€ 관계 ν‘œν˜„
      • aria-expanded: μ½€λ³΄λ°•μŠ€ νŒμ—… μƒνƒœ(μ—΄λ¦Ό/λ‹«νž˜) ν‘œμ‹œ
      • aria-controls: μ½€λ³΄λ°•μŠ€κ°€ μ œμ–΄ν•˜λŠ” νŒμ—…(listbox) μš”μ†Œ λͺ…μ‹œ
      • aria-activedescendant: ν˜„μž¬ 포컀슀된 μ˜΅μ…˜ μš”μ†Œμ˜ ID μ°Έμ‘°
      • aria-selected: μ„ νƒλœ μ˜΅μ…˜μž„μ„ λ‚˜νƒ€λƒ„
  • JavaScript 및 CSS μ‚¬μš© μ—¬λΆ€
    • JavaScript μ‚¬μš©: ν‚€λ³΄λ“œ 이벀트 핸듀링, νŒμ—… ν† κΈ€, μ˜΅μ…˜ 선택 λ“±μ˜ κΈ°λŠ₯ κ΅¬ν˜„μ— μ‚¬μš©
    • CSS 적용: μ½€λ³΄λ°•μŠ€μ˜ λ ˆμ΄μ•„μ›ƒ 및 λ””μžμΈμ„ μœ„ν•œ CSS μŠ€νƒ€μΌ 적용. 단, μ‹œκ°μ  νš¨κ³Όκ°€ 접근성을 μ €ν•΄ν•˜μ§€ μ•Šλ„λ‘ 주의

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions