Skip to content

Commit

Permalink
Merge pull request #137 from useon/step1
Browse files Browse the repository at this point in the history
[항공사 웹사이트의 컴포넌트 접근성 높이기] 썬데이(김유선) 미션 제출합니다.
  • Loading branch information
jinhokim98 authored Oct 4, 2024
2 parents 407ffcb + 67d489d commit af81559
Show file tree
Hide file tree
Showing 21 changed files with 235 additions and 102 deletions.
22 changes: 10 additions & 12 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
<!doctype html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>항공권 예약 및 여행 정보 | 썬데이 항공</title>
</head>

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React App</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"deploy": "npm run build && npx gh-pages -d dist"
},
"author": "woowacourse",
"homepage": "https://{username}.github.io/a11y-airline",
"homepage": "https://useon.github.io/a11y-airline",
"license": "MIT",
"dependencies": {
"react": "^18.3.1",
Expand Down
35 changes: 18 additions & 17 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import styles from './App.module.css';
import Navigation from './components/Navigation';
import FlightBooking from './components/FlightBooking';
import TravelSection from './components/TravelSection';
// import PromotionModal from './components/PromotionModal';
import styles from './styles/App.module.css';
import FlightBooking from './components/flightBooking/FlightBooking';
import Navigation from './components/navigation/Navigation';
import TravelSection from './components/travelSection/TravelSection';
import SkipToContent from './components/skipToContent/SkipToContet';
import PromotionModal from './components/promotionModal/PromotionModal';

function App() {
return (
<div className={styles.app}>
<SkipToContent />
<Navigation />
<div className={styles.header}>
<h1 className={`${styles.title} heading-1-text`}>A11Y AIRLINE</h1>
<header className={styles.header}>
<h1 className={`${styles.title} heading-1-text`}>SUNDAY AIRLINE</h1>
<p className="body-text">
A11Y AIRLINE은 고객 여러분의 안전하고 쾌적한 여행을 위해 최선을 다하고 있습니다.
SUNDAY AIRLINE은 고객 여러분의 안전하고 쾌적한 여행을 위해 최선을 다하고 있습니다.
</p>
</div>
<div id="main-content" className={styles.main}>
</header>
<main id="main-content" className={styles.main}>
<div className={styles.flightBooking}>
<FlightBooking />
</div>
<div className={styles.travelSection}>
<h1 className={`${styles.travelTitle} heading-2-text`}>지금 떠나기 좋은 여행</h1>
<h2 className={`${styles.travelTitle} heading-2-text`}>지금 떠나기 좋은 여행</h2>
<TravelSection />
</div>
</div>
<div className={styles.footer}>
<p className="body-text">&copy; A11Y AIRLINE</p>
</div>
{/* 추가 CHALLENGE: 모달 포커스 트랩 */}
{/* <PromotionModal /> */}
</main>
<footer className={styles.footer}>
<p className="body-text">&copy; SUNDAY AIRLINE</p>
</footer>
<PromotionModal />
</div>
);
}
Expand Down
38 changes: 0 additions & 38 deletions src/components/PromotionModal.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
position: relative;
margin-left: 5px;
cursor: pointer;
border-style: none;
background-color: transparent;
}

.helpIcon {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useCallback, useState } from 'react';

import helpIcon from '../assets/help.svg';
import plus from '../assets/plus.svg';
import minus from '../assets/minus.svg';
import helpIcon from '../../assets/help.svg';
import plus from '../../assets/plus.svg';
import minus from '../../assets/minus.svg';

import styles from './FlightBooking.module.css';

Expand All @@ -14,40 +14,54 @@ const FlightBooking = () => {
const [statusMessage, setStatusMessage] = useState('');
const [showTooltip, setShowTooltip] = useState(false);

const updateAlert = (message: string) => {
setStatusMessage('');
setTimeout(() => {
setStatusMessage(message);
}, 0);
};

const incrementCount = useCallback(() => {
if (adultCount === MAX_PASSENGERS) {
setStatusMessage('최대 승객 수에 도달했습니다');
updateAlert('최대 승객 수에 도달했습니다');
return;
}

setAdultCount((prev) => Math.min(MAX_PASSENGERS, prev + 1));
setStatusMessage('');
}, [adultCount]);

const decrementCount = useCallback(() => {
if (adultCount === MIN_PASSENGERS) {
setStatusMessage('최소 1명의 승객이 필요합니다');
updateAlert('최소 1명의 승객이 필요합니다');
return;
}

setAdultCount((prev) => Math.max(MIN_PASSENGERS, prev - 1));
setStatusMessage('');
}, [adultCount]);

return (
<div className={styles.flightBooking}>
<h2 className="heading-2-text">항공권 예매</h2>
<article className={styles.flightBooking}>
<h2 className="heading-2-text" tabIndex={0}>
항공권 예매
</h2>
<div className={styles.passengerCount}>
<div className={styles.passengerLabel}>
<span className="body-text">성인</span>
<div
<button
className={styles.helpIconWrapper}
onMouseEnter={() => setShowTooltip(true)}
onFocus={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
onBlur={() => setShowTooltip(false)}
aria-describedby="booking-desc"
>
<img src={helpIcon} alt="도움말" className={styles.helpIcon} />
{showTooltip && <div className={styles.tooltip}>최대 3명까지 예약할 수 있습니다</div>}
</div>
{showTooltip && (
<div id="booking-desc" className={styles.tooltip}>
최대 3명까지 예약할 수 있습니다.
</div>
)}
</button>
</div>
<div className={styles.counter}>
<button className="button-text" onClick={decrementCount} aria-label="성인 승객 감소">
Expand All @@ -65,7 +79,7 @@ const FlightBooking = () => {
</div>
)}
<button className={styles.searchButton}>항공편 검색</button>
</div>
</article>
);
};

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
98 changes: 98 additions & 0 deletions src/components/promotionModal/PromotionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useEffect, useRef, useState } from 'react';

import close from '../../assets/close.svg';

import styles from './PromotionModal.module.css';

const PromotionModal = () => {
const [isOpen, setIsOpen] = useState(true);
const modalRef = useRef<HTMLDivElement>(null);
const closeButtonRef = useRef<HTMLButtonElement>(null);

const closeModal = () => {
setIsOpen(false);
};

useEffect(() => {
if (isOpen && modalRef.current) {
const previouslyFocusedElement = document.activeElement as HTMLElement;
closeButtonRef.current?.focus();

const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
closeModal();
}

const focusableElements = modalRef.current?.querySelectorAll<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (!focusableElements) return;
const firstFocusableElement = focusableElements[0];
const lastFocusableElement = focusableElements[focusableElements.length - 1];

if (e.key === 'Tab') {
if (e.shiftKey) {
if (document.activeElement === firstFocusableElement) {
lastFocusableElement.focus();
e.preventDefault();
}
} else {
if (document.activeElement === lastFocusableElement) {
firstFocusableElement.focus();
e.preventDefault();
}
}
}
};

document.addEventListener('keydown', handleKeyDown);

return () => {
document.removeEventListener('keydown', handleKeyDown);
previouslyFocusedElement?.focus();
};
}
}, [isOpen]);

if (!isOpen) {
return null;
}

return (
<section
className={styles.modal}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-live="assertive"
ref={modalRef}
>
<div className="visually-hidden">
모달 창이 열렸습니다. 닫기 버튼 또는 esc 버튼을 눌러 이 창을 닫을 수 있습니다.
</div>
<div className={styles.modalBackdrop} onClick={closeModal}></div>
<div className={styles.modalContainer}>
<section className={styles.modalContent}>
<h2 id="modal-title" className={`${styles.modalTitle} heading-2-text`}>
여행할 땐 SUNDAY AIRLINE 앱
</h2>
<p className={`${styles.modalDescription} body-text`}>
체크인, 탑승권 저장, 수하물 알림까지
<br />- 앱으로 더욱 편하게 여행하세요!
</p>
<button className={`${styles.modalActionButton} button-text`}>앱에서 열기</button>
<button
ref={closeButtonRef}
className={`${styles.modalCloseButton} heading-2-text`}
onClick={closeModal}
aria-label="닫기"
>
<img src={close} alt="닫기" />
</button>
</section>
</div>
</section>
);
};

export default PromotionModal;
24 changes: 24 additions & 0 deletions src/components/skipToContent/SkipToContent.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.skipLink {
position: absolute;
transform: translateY(-40px);
left: 0;
background-color: #007bff;
color: #fff;
padding: 8px 16px;
z-index: 100;
border-radius: 4px;
border: none;
text-decoration: none;
transition: transform 0.3s ease-in-out;
clip: rect(1px, 1px, 1px, 1px);
overflow: hidden;
height: 1px;
width: 1px;
}

.skipLink:focus {
clip: auto;
transform: translateY(0);
height: auto;
width: auto;
}
15 changes: 15 additions & 0 deletions src/components/skipToContent/SkipToContet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import styles from './SkipToContent.module.css';

interface SkipToContentProps {
linkTarget?: string;
}

const SkipToContent = ({ linkTarget = '#main-content' }: SkipToContentProps) => {
return (
<a href={linkTarget} className={styles.skipLink}>
Skip to content
</a>
);
};

export default SkipToContent;
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
}

.card {
display: none;
width: 100%;
height: 246px;
position: relative;
padding: 0;
border-radius: 4px;
border: 1px solid rgba(217, 217, 217, 0.5);
overflow: hidden;
display: none;
width: 100%;
height: 246px;
}

.cardActive {
Expand All @@ -32,6 +33,9 @@
}

.cardContent {
display: flex;
flex-direction: column;
align-items: flex-start;
position: absolute;
top: 0;
left: 0;
Expand Down
Loading

0 comments on commit af81559

Please sign in to comment.