LikeLion-FE-React-Project04 / project-repo

멋쟁이사자처럼 프론트엔드 스쿨 4기에서 Final Project로 진행한 Karly입니다.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[FEATURE] 상품후기/문의 팝업창 접근성 고려하기

SeoMiYoung opened this issue · comments

🌳 작업 브랜치

feat/#212

👀 참고 이슈

  • #212
    => 맥락이 거의 비슷합니다. 위의 이슈에서 자세히 서술해놓았으니, 참고하세요.

📝 TASK 개요

[STEP1] 팝업창이 딱 떴을 때, 초점이동이 팝업창 내부로 이동해야 함

Before...(팝업창이 떠도, 팝업창 외부에서 초점 이동이 됨)

Animation

해결과정...

우선, 모달 전체창을 참조하는 modalRef를 생성한다.

const modalRef = useRef(); // 모달 전체를 참조

그리고, 모달 전체를 감싸는 <article>태그에 modalRef를 연결해준다.

<article 
          className={styles.detailPopUpWrap}
          ref={modalRef}
          tabIndex="-1"
>

=> 주의해야될 점이, tabIndex를 넣어주지 않으면, article태그 자체는 focus를 받을 수 없음. 따라서, 모달창 전체에 focus를 주기 위해서는 꼭 tabIndex를 설정해줘야 함!

그리고, 다음 코드를 추가해서, 모달창이 열리는 순간, 모달창에 포커스가 되도록 설정한다.

// 모달창이 뜰 때, 포커스 
  useEffect(() => {
    modalRef.current.focus();
  }, []);

After...

Animation


[STEP2] 모달창이 떠 있을 때, 초점은 계속 모달창 내부에서 순환해야 함

Before...

Animation
계속해서 Tab키를 누름에도 불구하고, 모달창 내에서 포커스 순환이 이루어지고 있지 않음

해결과정...

이벤트 핸들러 연결

const handleModalKeyEvent = (e) => {
    const firstFocusableElement = smallCloseBtnRef.current; 
    let lastFocusableElement;
    if(registrationBtnRef.current.disabled == true) { // 등록버튼이 disabled라면,
      lastFocusableElement = cancelBtnRef.current;
    }
    else { // 등록버튼이 활성화 상태라면 
      lastFocusableElement = registrationBtnRef.current;
    }

    // Tab, Shift키 누른 여부를 저장
    const isTabPressed = (e.key === 'Tab');
    const isShiftPressed = e.shiftKey;

    if(!isTabPressed) { // Tab키를 누르지 않은 경우
      console.log('Tab이 아닙니다.');

      return; 
    }
    if(isShiftPressed) { // Shift+Tab 누른 경우
      // document.activeElement는 현재 focus요소를 가리킨다
      if(document.activeElement === firstFocusableElement) {
        lastFocusableElement.focus();
        e.preventDefault();
      }
    }
    else { // Shift를 누르지 않고, tab키만 눌렀을 경우
      if(document.activeElement === lastFocusableElement) {
        firstFocusableElement.focus(); // 다시 첫번째 요소로 focus
        e.preventDefault();
      }
    }
  }

단, 메인모달창, 카트모달창과 다르게, focusable한 요소들을 배열로 굳이 관리하고 있지 않음.
그냥 굳이 그렇게 관리 안해도 될 것 같아서 굳이 배열을 생성하지 않기로 함.
단, 순환을 할 때, '첫번째 요소'와 '마지막 요소'는 구분해야하므로, focusable한 요소들을 참조할 수 있는 ref는 사용함.

const smallCloseBtnRef = useRef(); // X버튼
const cancelBtnRef = useRef(); // 취소 버튼
const registrationBtnRef = useRef(); // 등록 버튼

참고로, disabled상태는 focus를 받을 수 없으므로, 등록 버튼이 disabled인 경우, 마지막 요소의 경우 대상이 달라짐.
따라서, if, else문으로 등록 버튼의 disabled상태에 따라 다른 마지막 요소를 부여함.

After...

Animation


[STEP3] textarea에 tab키로 focus가 될 때 placeholder가 사라지지 않는 현상 해결하기

Before...

Animation

해결과정...

<textarea>태그 부분에 onFocus 이벤트 핸들러를 연결해줌

After...

Animation


[STEP4] 메인모달창 내에서 포커스 이동이 되다가, 갑자기 외부영역을 클릭하면, 외부영역이 포커스가 잡히는 점 해결하기

ProductDetailPopUp.jsx에서 다음 코드들 추가
import { darkFilterFocusState } from '../../store/darkFilterState';

  const setDarkFilterFocus = useSetRecoilState(darkFilterFocusState);

  // 모달창이 뜰 때, 포커스 
  useEffect(() => {
    modalRef.current.focus();
    setDarkFilterFocus(modalRef.current);
  }, []);

[STEP5] 모달창 전체를 감싸는 태그에 추가한 속성들 살펴보기

  • 관련 이슈의 [STEP6]참고: #212
<article 
          className={styles.detailPopUpWrap}
          ref={modalRef}
          tabIndex="-1"
          onKeyDown={handleModalKeyEvent}
          role="dialog"
          aria-modal="true"
          aria-label={`${title} 모달창이 열렸습니다.`}
>

[STEP6] 비밀글 관련, 문제 발생 해결하기..(Q/A질문 등록)

문제 내용: 상품 문의 팝업창의 경우, '비밀글'에 focus가 되지 않는 현상 발생

Secret.module.scss 수정

  input[type="checkbox"] {
    //display: none;
    appearance: none; // 체크박스 input요소의 기본 UI가 보이지 않게 하기
    position: absolute; //input요소가 영역을 차지하지 않게끔 붕 띄우기
    padding: 12px;
  }

✅ To Do 및 진행상황

  • [STEP1] 팝업창이 딱 떴을 때, 초점이동이 팝업창 내부로 이동해야 함
  • [STEP2] 모달창이 떠 있을 때, 초점은 계속 모달창 내부에서 순환해야 함
  • [STEP3] textarea에 tab키로 focus가 될 때 placeholder가 사라지지 않는 현상 해결하기
  • [STEP4] 메인모달창 내에서 포커스 이동이 되다가, 갑자기 외부영역을 클릭하면, 외부영역이 포커스가 잡히는 점 해결하기
  • [STEP5] 모달창 전체를 감싸는 태그에 추가한 속성들 살펴보기
  • [STEP6] 비밀글로 설정하기에 포커스 가능하게 하기