diff --git a/a11y/index.html b/a11y/index.html index 12fa3e7..8fd9bcf 100644 --- a/a11y/index.html +++ b/a11y/index.html @@ -1,16 +1,13 @@ - - - - - - - - Accessibility - - - -
- - - + + + + + + + Accessibility + + +
+ + diff --git a/a11y/package.json b/a11y/package.json index 1148c94..9396d14 100644 --- a/a11y/package.json +++ b/a11y/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" diff --git a/a11y/src/App.tsx b/a11y/src/App.tsx index a8159f9..b8fecc8 100644 --- a/a11y/src/App.tsx +++ b/a11y/src/App.tsx @@ -6,11 +6,11 @@ import FlightBooking from "./components/FlightBooking"; function App() { return (
-
-
+
+
-
-
+ +
); } diff --git a/a11y/src/components/FlightBooking.css b/a11y/src/components/FlightBooking.css index d9d6083..5ae3543 100644 --- a/a11y/src/components/FlightBooking.css +++ b/a11y/src/components/FlightBooking.css @@ -34,7 +34,7 @@ width: 30px; height: 30px; border-radius: 16px; - border: 1px solid #C0C0C0; + border: 1px solid #c0c0c0; background-color: #fff; cursor: pointer; display: flex; @@ -61,3 +61,17 @@ border-radius: 4px; cursor: pointer; } + +.visually-hidden { + overflow: hidden; + white-space: nowrap; + clip: rect(1px, 1px, 1px, 1px); + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + position: absolute; + width: 1px; + height: 1px; + margin: 0; + padding: 0; + border: 0; +} diff --git a/a11y/src/components/FlightBooking.tsx b/a11y/src/components/FlightBooking.tsx index 313cab3..b2bf3dc 100644 --- a/a11y/src/components/FlightBooking.tsx +++ b/a11y/src/components/FlightBooking.tsx @@ -1,19 +1,15 @@ -import { useState } from "react"; - import "./FlightBooking.css"; -const MAX_PASSENGERS = 3; +import useAdultCount from "../hooks/useAdultCount"; const FlightBooking = () => { - const [adultCount, setAdultCount] = useState(1); - - const incrementCount = () => { - setAdultCount((prev) => Math.min(MAX_PASSENGERS, prev + 1)); - }; - - const decrementCount = () => { - setAdultCount((prev) => Math.max(1, prev - 1)); - }; + const { + adultCount, + incrementCount, + decrementCount, + alertMessage, + messageForATUser, + } = useAdultCount(); return (
@@ -21,15 +17,38 @@ const FlightBooking = () => {
성인
- + {messageForATUser && ( +
+

{messageForATUser}

+
+ )} {adultCount} -
+ {alertMessage && ( +
+ {alertMessage} +
+ )}
); diff --git a/a11y/src/hooks/useAdultCount.ts b/a11y/src/hooks/useAdultCount.ts new file mode 100644 index 0000000..9c93e4d --- /dev/null +++ b/a11y/src/hooks/useAdultCount.ts @@ -0,0 +1,49 @@ +import { useState } from "react"; +import useDebouncedATMessage from "./useDebouncedATMessage"; + +const MAX_PASSENGERS = 3; + +const useAdultCount = () => { + const [adultCount, setAdultCount] = useState(1); + const [alertMessage, setAlertMessage] = useState(""); + const { handleDebouncedMessage, messageForATUser } = + useDebouncedATMessage(200); + + const incrementCount = () => { + if (adultCount + 1 > MAX_PASSENGERS) { + alert("더이상 승객을 추가할 수 없습니다."); + setAlertMessage("더이상 승객을 추가할 수 없습니다."); + return; + } + if (alertMessage) { + setAlertMessage(""); + } + + handleDebouncedMessage(`성인 승객 수는 ${adultCount + 1}명 입니다.`); + setAdultCount((prev) => Math.min(MAX_PASSENGERS, prev + 1)); + }; + + const decrementCount = () => { + if (adultCount === 1) { + alert("승객은 최소 1명 이상이어야 합니다."); + setAlertMessage("승객은 최소 1명 이상이어야 합니다."); + return; + } + if (alertMessage) { + setAlertMessage(""); + } + + handleDebouncedMessage(`성인 승객 수는 ${adultCount - 1}명 입니다.`); + setAdultCount((prev) => Math.max(1, prev - 1)); + }; + + return { + adultCount, + incrementCount, + decrementCount, + alertMessage, + messageForATUser, + }; +}; + +export default useAdultCount; diff --git a/a11y/src/hooks/useDebounce.ts b/a11y/src/hooks/useDebounce.ts new file mode 100644 index 0000000..320d44e --- /dev/null +++ b/a11y/src/hooks/useDebounce.ts @@ -0,0 +1,25 @@ +import { useCallback, useRef } from "react"; + +const useDebounce = ) => void>( + func: T, + delayTime: number +) => { + const debounceRef = useRef | null>(null); + + const debouncedCallback = useCallback( + (...args: Parameters) => { + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + + debounceRef.current = setTimeout(() => { + func(...args); + }, delayTime); + }, + [func, delayTime] + ); + + return debouncedCallback; +}; + +export default useDebounce; diff --git a/a11y/src/hooks/useDebouncedATMessage.ts b/a11y/src/hooks/useDebouncedATMessage.ts new file mode 100644 index 0000000..f0cbfa2 --- /dev/null +++ b/a11y/src/hooks/useDebouncedATMessage.ts @@ -0,0 +1,22 @@ +import { useState } from "react"; +import useDebounce from "./useDebounce"; + +const MESSAGE_MAINTAIN_TIME = 500; + +const useDebouncedATMessage = (delayTime: number) => { + const [messageForATUser, setMessageForATUser] = useState(""); + + const handleDebouncedMessage = useDebounce((message: string) => { + setMessageForATUser(message); + setTimeout(() => { + setMessageForATUser(""); + }, MESSAGE_MAINTAIN_TIME); + }, delayTime); + + return { + messageForATUser, + handleDebouncedMessage, + }; +}; + +export default useDebouncedATMessage;