Skip to content

Commit

Permalink
Merge pull request #128 from softeerbootcamp4th/feature/124-a11y
Browse files Browse the repository at this point in the history
[feat] 인터랙션 키보드 접근성 개선
  • Loading branch information
darkdulgi authored Aug 22, 2024
2 parents 38b3877 + 7295d5c commit 95f374f
Show file tree
Hide file tree
Showing 28 changed files with 525 additions and 171 deletions.
7 changes: 4 additions & 3 deletions admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="소프티어 부트캠프 4기-팀 어썸 오렌지의 IONIQ 5 어드민 페이지입니다." />
<link rel="preload" href="/font/HyundaiSansTextKROTFBold.otf" as="font" type="font/otf" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFMedium.otf" as="font" type="font/otf" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFRegular.otf" as="font" type="font/otf" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFBold.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFMedium.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFRegular.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/font/fonts.css" as="style" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
<title>Awesome Orange - Admin</title>
</head>
<body>
Expand Down
7 changes: 4 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="HI. Five. The new IONIQ 5. 소프티어 부트캠프 4기-팀 어썸 오렌지의 IONIQ 5 이벤트 페이지입니다." />
<link rel="preload" href="/font/DS-DIGI.TTF" as="font" type="font/ttf" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFBold.otf" as="font" type="font/otf" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFMedium.otf" as="font" type="font/otf" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFRegular.otf" as="font" type="font/otf" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFBold.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFMedium.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/font/HyundaiSansTextKROTFRegular.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/font/fonts.css" as="style" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
<title>Awesome Orange FE</title>
</head>
<body>
Expand Down
Binary file added public/font/HyundaiSansTextKROTFBold.woff2
Binary file not shown.
Binary file added public/font/HyundaiSansTextKROTFMedium.woff2
Binary file not shown.
Binary file added public/font/HyundaiSansTextKROTFRegular.woff2
Binary file not shown.
20 changes: 20 additions & 0 deletions public/font/fonts.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@font-face {
font-family: "hdsans";
src: url("/font/HyundaiSansTextKROTFBold.woff2") format("woff2");
font-weight: bold;
font-display: swap;
}

@font-face {
font-family: "hdsans";
src: url("/font/HyundaiSansTextKROTFMedium.woff2") format("woff2");
font-weight: 500;
font-display: swap;
}

@font-face {
font-family: "hdsans";
src: url("/font/HyundaiSansTextKROTFRegular.woff2") format("woff2");
font-weight: 400;
font-display: swap;
}
14 changes: 10 additions & 4 deletions src/adminPage/features/eventDetail/drawButton/DrawResultModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { Fragment, useMemo } from "react";
import useScrollControl from "./useScrollControl.js";
import { fetchServer } from "@common/dataFetch/fetchServer.js";
import { useQuery } from "@common/dataFetch/getQuery.js";
import { GroupMap } from "@common/utils.js";
import { GroupMap, addHyphen } from "@common/utils.js";

function mapResultSubsetGroup({ ranking, name, phoneNumber }) {
return (
<Fragment key={`${ranking}-${name}-${phoneNumber}`}>
<p className="font-medium">{ranking}</p>
<p>{name}</p>
<p>{phoneNumber}</p>
<p>{addHyphen(phoneNumber)}</p>
</Fragment>
);
}
Expand All @@ -21,7 +21,7 @@ function DrawResultModal({ eventId }) {
const { hullRef, mountMap, scrollTo, intersectState } = useScrollControl();

// render logic
const maxGrade = drawResultData.at(-1).ranking;
const maxGrade = drawResultData.length === 0 ? 0 : drawResultData.at(-1).ranking;
const tableStyle =
"w-full grid grid-cols-[4rem_1fr_2fr] auto-rows-[minmax(2rem,auto)] gap-4 items-center justify-items-center";

Expand Down Expand Up @@ -63,7 +63,13 @@ function DrawResultModal({ eventId }) {
<p>전화번호</p>
</div>
<div className="w-full flex-grow overflow-y-scroll" ref={hullRef}>
<div className="flex flex-col gap-2">{[...drawResultGroup].map(mapResultGroup)}</div>
{drawResultData.length === 0 ? (
<div className="w-full h-full flex justify-center items-center text-body-l font-bold text-neutral-600">
저런! 참가자가 없군요!
</div>
) : (
<div className="flex flex-col gap-2">{[...drawResultGroup].map(mapResultGroup)}</div>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function useScrollControl() {
},
{ root: hullRef.current ?? null, threshold: 0.01 },
);
for (let [, elem] of itemRef.current) {
for (let [, elem] of getMap()) {
observerRef.current.observe(elem);
}

Expand Down
27 changes: 0 additions & 27 deletions src/adminPage/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,6 @@
@tailwind components;
@tailwind utilities;

@font-face {
font-family: "ds-digital";
src: url("/font/DS-DIGI.TTF") format("truetype");
font-display: swap;
}

@font-face {
font-family: "hdsans";
src: url("/font/HyundaiSansTextKROTFBold.otf") format("opentype");
font-weight: bold;
font-display: swap;
}

@font-face {
font-family: "hdsans";
src: url("/font/HyundaiSansTextKROTFMedium.otf") format("opentype");
font-weight: 500;
font-display: swap;
}

@font-face {
font-family: "hdsans";
src: url("/font/HyundaiSansTextKROTFRegular.otf") format("opentype");
font-weight: 400;
font-display: swap;
}

@layer base {
body {
font-family: "hdsans", sans-serif;
Expand Down
9 changes: 1 addition & 8 deletions src/common/components/PhoneInput.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import Input from "./Input.jsx";
import { addHyphen } from "../utils.js";

function PhoneInput({ text, setText, ...otherProps }) {
function addHyphen(value) {
const plain = value.replace(/\D/g, "");

if (plain.length < 4) return plain;
if (plain.length <= 7) return plain.replace(/^(\d{3})(\d{0,4})$/, "$1-$2");
if (plain.length <= 10) return plain.replace(/^(\d{3})(\d{3})(\d{0,4})$/, "$1-$2-$3");
return plain.replace(/^(\d{3})(\d{4})(\d{4,})$/, "$1-$2-$3");
}
return (
<Input
text={text}
Expand Down
9 changes: 9 additions & 0 deletions src/common/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,12 @@ export function getDayDifference(_date1, _date2) {

return dayDifference;
}

export function addHyphen(value) {
const plain = value.replace(/\D/g, "");

if (plain.length < 4) return plain;
if (plain.length <= 7) return plain.replace(/^(\d{3})(\d{0,4})$/, "$1-$2");
if (plain.length <= 10) return plain.replace(/^(\d{3})(\d{3})(\d{0,4})$/, "$1-$2-$3");
return plain.replace(/^(\d{3})(\d{4})(\d{4,})$/, "$1-$2-$3");
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function InteractionSlide({ interactionDesc, index, isCurrent, sl
</span>

<div className="pt-5 flex items-center">
<img src={numberImgPath} alt="" role="presentation" />
<img src={numberImgPath} alt="" role="presentation" width="24" height="24" />

<span
className={`${isCurrent ? "opacity-100" : "opacity-50"} pl-3 text-title-m sm:text-head-s text-white font-bold`}
Expand Down
2 changes: 2 additions & 0 deletions src/mainPage/features/interactions/description/TapBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ function TabBarItem({ currentInteraction, slideTo, index }) {
alt="체크"
className={`${(!isJoined || isInvisible) && "invisible"}`}
draggable="false"
width="40"
height="40"
/>

<span
Expand Down
29 changes: 21 additions & 8 deletions src/mainPage/features/interactions/distanceDriven/index.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { useImperativeHandle } from "react";
import { useEffect, useImperativeHandle } from "react";
import InteractionDescription from "../InteractionDescription.jsx";
import AnswerText from "./AnswerText.jsx";
import usePointDrag from "./usePointDrag.js";
import useDeviceRatio from "./useDeviceRatio.js";

function DistanceDrivenInteraction({ interactCallback, $ref }) {
const { x, y, reset, isDragging, onPointerDown } = usePointDrag();
const MAX_DISTANCE = 800;

function DistanceDrivenInteraction({ interactCallback, $ref, disabled }) {
const { x, y, reset, isDragging, onPointerDown, handleRef, subtitle } = usePointDrag(!disabled);
const ratio = useDeviceRatio();
const km = Math.floor((Math.hypot(x, y) * MAX_DISTANCE) / ratio);

const circleStyle = {
transform: `translate(${x}px, ${y}px)`,
Expand All @@ -25,6 +29,10 @@ function DistanceDrivenInteraction({ interactCallback, $ref }) {
);
}

useEffect(() => {
if (km !== 0) interactCallback?.();
}, [km, interactCallback]);

useImperativeHandle($ref, () => ({ reset }), [reset]);

return (
Expand All @@ -36,15 +44,22 @@ function DistanceDrivenInteraction({ interactCallback, $ref }) {
directive="가운데 점을 드래그하여 최대 주행거리를 예측해보세요!"
shouldNotSelect={isDragging}
/>
<span aria-live="assertive" className="assistive-text">
{subtitle(x, y, km)}
</span>
<span aria-live="assertive" className="assistive-text">
스페이스바를 눌러서 드래그 상태를 전환하세요.
</span>
<div className="absolute top-1/2">
<div
tabIndex={disabled ? undefined : 0}
className="rounded-full size-8 bg-blue-500 cursor-pointer touch-none before:size-8 before:rounded-full before:absolute before:left-0 before:top-0 before:z-10 before:bg-blue-500 before:opacity-50"
onPointerDown={(e) => {
onPointerDown(e);
pulseAnimation(e);
interactCallback?.();
}}
style={circleStyle}
ref={handleRef}
/>
<svg
className="overflow-visible stroke-blue-500 absolute top-4 left-4 pointer-events-none"
Expand All @@ -55,9 +70,7 @@ function DistanceDrivenInteraction({ interactCallback, $ref }) {
</svg>
</div>
<p className="text-white absolute bottom-32 md:bottom-36 lg:bottom-[180px] text-title-s font-bold pointer-events-none">
<span className="text-head-m md:text-head-l lg:text-17.5 mr-1.5 lg:mr-2.5">
<AnswerText distance={Math.hypot(x, y)} />
</span>
<span className="text-head-m md:text-head-l lg:text-17.5 mr-1.5 lg:mr-2.5">{km}</span>
km
</p>
</article>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { useState, useEffect } from "react";
import throttleRaf from "@common/throttleRaf.js";

const MAX_ANSWER = 800;

function AnswerText({ distance }) {
function useDeviceRatio() {
const [ratio, setRatio] = useState(1);
useEffect(() => {
setRatio(Math.hypot(window.innerWidth, window.innerHeight) / 2);
const onResize = throttleRaf(() => {
setRatio(Math.hypot(window.innerWidth, window.innerHeight) / (2 * MAX_ANSWER));
setRatio(Math.hypot(window.innerWidth, window.innerHeight) / 2);
});
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);

return <>{Math.round(distance / ratio)}</>;
return ratio;
}

export default AnswerText;
export default useDeviceRatio;
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { useState, useRef, useCallback } from "react";
import useMountDragEvent from "@main/hooks/useMountDragEvent.js";
import useA11yDrag from "@main/hooks/useA11yDrag.js";

function usePointDrag() {
const grabText = (x, y, km) =>
`점을 잡았습니다. 현재 좌표는 (${x}, ${y})이며, 거리는 ${km}km입니다. 방향키를 눌러 점의 위치를 조정하세요. 스페이스바를 눌러 점을 놓을 수 있습니다.`;
const moveText = (x, y, km) => `현재 좌표는 (${x}, ${y})이며, 거리는 ${km}km입니다.`;
const dropText = (x, y, km) => `점이 놓였습니다. 새 좌표는 (${x}, ${y})이며, 거리는 ${km}km입니다.`;

function usePointDrag(enabled) {
const prevState = useRef({ x: 0, y: 0, mouseX: 0, mouseY: 0 });
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const [subtitle, setSubtitle] = useState(() => () => "");

const onDragStart = useCallback(
function ({ x: mouseX, y: mouseY }) {
Expand All @@ -17,9 +24,24 @@ function usePointDrag() {
setY(prevState.current.y + mouse.y - prevState.current.mouseY);
}, []);

const onKeyMove = useCallback(function (x, y) {
setX((prev) => prev + x * 10);
setY((prev) => prev + y * 10);
}, []);

const { onPointerDown, dragState } = useMountDragEvent({
onDragStart,
onDrag,
enabled,
});

const handleRef = useA11yDrag({
grabText,
moveText,
dropText,
onKeyMove,
enabled,
setSubtitle,
});

return {
Expand All @@ -31,6 +53,8 @@ function usePointDrag() {
},
isDragging: dragState,
onPointerDown,
handleRef,
subtitle,
};
}

Expand Down
32 changes: 23 additions & 9 deletions src/mainPage/features/interactions/fastCharge/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useImperativeHandle } from "react";
import { useEffect, useImperativeHandle } from "react";
import InteractionDescription from "../InteractionDescription.jsx";
import BatteryProgressBar from "./BatteryProgressBar.jsx";
import dialSvg from "./assets/timer.svg";
Expand All @@ -13,18 +13,25 @@ function getProgress(angle) {
return rawProgress;
}

function FastChargeInteraction({ interactCallback, $ref }) {
function FastChargeInteraction({ interactCallback, $ref, disabled }) {
const {
angle,
style: dialStyle,
ref: dialRef,
keyRef,
onPointerDown,
resetAngle: reset,
isDragging,
} = useDialDrag(0);
subtitle,
} = useDialDrag(!disabled);

useEffect(() => {
if (angle !== 0) interactCallback?.();
}, [angle, interactCallback]);

useImperativeHandle($ref, () => ({ reset }), [reset]);
const progress = getProgress(angle);
const answer = Math.round(progress * MAX_MINUTE);

return (
<article className="relative w-full h-full overflow-hidden flex items-center flex-col">
Expand All @@ -35,6 +42,12 @@ function FastChargeInteraction({ interactCallback, $ref }) {
directive="다이얼을 돌려 충전에 필요한 시간을 확인해보세요!"
shouldNotSelect={isDragging}
/>
<span aria-live="assertive" className="assistive-text">
{subtitle(answer, angle)}
</span>
<span aria-live="assertive" className="assistive-text">
스페이스바를 눌러서 다이얼 조작 여부를 전환하세요.
</span>
<div className="absolute top-[clamp(240px,40%,384px)] w-72 md:w-96 h-32 border-solid border-2 border-neutral-600 rounded-[30px] p-3.5">
<div className="absolute w-5 h-9 bg-neutral-600 right-[-1.25rem] top-[2.875rem] rounded-r-md"></div>
<BatteryProgressBar progress={progress} />
Expand All @@ -46,15 +59,16 @@ function FastChargeInteraction({ interactCallback, $ref }) {
className="w-full h-full absolute left-0 top-0 cursor-pointer touch-none select-none"
style={dialStyle}
ref={dialRef}
onPointerDown={(e) => {
onPointerDown(e);
interactCallback?.();
}}
onPointerDown={onPointerDown}
draggable="false"
/>
<p className="text-white font-bold absolute bottom-[calc(50%+48px)] md:bottom-[calc(50%+94px)] lg:bottom-[calc(50%+140px)] text-title-s pointer-events-none">
<span className="text-head-m md:text-head-l lg:text-17.5 mr-1.5 lg:mr-2.5">
{Math.round(progress * MAX_MINUTE)}
<span
className="text-head-m md:text-head-l lg:text-17.5 mr-1.5 lg:mr-2.5"
tabIndex={disabled ? undefined : 0}
ref={keyRef}
>
{answer}
</span>
</p>
Expand Down
Loading

0 comments on commit 95f374f

Please sign in to comment.