Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] 약속 입장, 약속 현황 조회 페이지(달력)의 웹 접근성을 개선 #421

Merged
merged 10 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"plugin:storybook/recommended"
],
"rules": {
"react/prop-types": "off",
"@typescript-eslint/consistent-type-imports": "error",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
Expand Down
7 changes: 5 additions & 2 deletions frontend/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Global, ThemeProvider } from '@emotion/react';
import type { Preview } from '@storybook/react';
import React from 'react';

import ToastProvider from '../src/contexts/ToastProvider';
import globalStyles from '../src/styles/global';
import theme from '../src/styles/theme';

Expand All @@ -21,8 +22,10 @@ export default preview;
export const decorators = [
(Story) => (
<ThemeProvider theme={theme}>
<Global styles={globalStyles} />
<Story />
<ToastProvider>
<Global styles={globalStyles} />
<Story />
</ToastProvider>
</ThemeProvider>
),
];
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { DAY_OF_WEEK_KR } from '../../../src/constants/date';

export const formatDate = (dateString: string) => {
const currentDateObj = new Date(dateString);
const currentMonth = currentDateObj.getMonth() + 1;
const currentDay = currentDateObj.getDate();
const dayOfWeek = ['일', '월', '화', '수', '목', '금', '토'][currentDateObj.getDay()];
const dayOfWeek = DAY_OF_WEEK_KR[currentDateObj.getDay()];

return {
dayOfWeek,
Expand Down
9 changes: 0 additions & 9 deletions frontend/src/assets/images/logo.svg

This file was deleted.

Binary file added frontend/src/assets/images/logo.webp
Binary file not shown.
9 changes: 0 additions & 9 deletions frontend/src/assets/images/logoSunglass.svg

This file was deleted.

Binary file added frontend/src/assets/images/logoSunglass.webp
Binary file not shown.
2 changes: 2 additions & 0 deletions frontend/src/components/MeetingCalendar/Date/Date.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getHolidayNames } from '@hyunbinseo/holidays-kr';
import { getDate, getDay, getFullDate } from '@utils/date';

import CALENDAR_PROPERTIES from '@constants/calendar';
import { DAY_OF_WEEK_KR } from '@constants/date';

export const getDateInfo = (targetDate: Date, today: Date) => {
const targetDateNumber = getDate(targetDate);
Expand All @@ -28,6 +29,7 @@ export const getDateInfo = (targetDate: Date, today: Date) => {
return {
date: targetDateNumber,
targetFullDate,
targetDayOfWeekKR: DAY_OF_WEEK_KR[targetDayOfWeek],
isHoliday,
isToday,
isSunday,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { PropsWithChildren } from 'react';

import {
s_monthNavigation,
s_monthNavigationContainer,
Expand All @@ -23,7 +25,10 @@ export default function Header({
moveToNextMonth,
moveToPrevMonth,
isCurrentMonth,
}: HeaderProps) {
children,
}: PropsWithChildren<HeaderProps>) {
const yearMonthText = `${currentYear}년 ${currentMonth + 1}월`;

return (
<header css={s_container}>
<div css={s_monthNavigationContainer}>
Expand All @@ -35,9 +40,9 @@ export default function Header({
>
{'<'}
</button>

<span css={s_yearMonthText}>
{currentYear}년 {currentMonth + 1}월
<span css={s_yearMonthText} aria-live="polite" role="text">
{yearMonthText}
{children}
</span>
<button css={s_monthNavigation} onClick={moveToNextMonth} aria-label="다음 달">
{'>'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { usePostScheduleByMode } from '@stores/servers/schedule/mutations';

import { getFullDate } from '@utils/date';

import Header from '../Header/Header';
import SingleDate from '../SingleDate/SingleDate';
import Header from '../Header';
import SingleDate from '../SingleDate';
import WeekDays from '../WeekDays';

interface PickerProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
} from '@components/MeetingCalendar/Date/Date.styles';
import { getDateInfo } from '@components/MeetingCalendar/Date/Date.utils';

import { formatAriaFullDate } from '@utils/a11y';

import Check from '@assets/images/attendeeCheck.svg';

import { s_additionalText, s_viewer } from './SingleDate.styles';
Expand All @@ -30,7 +32,16 @@ export default function SingleDateViewer({
availableAttendees,
}: DateProps) {
const { value, status } = dateInfo;
const { date, isHoliday, isToday, isSaturday, isSunday, isPrevDate } = getDateInfo(value, today);
const {
date,
targetFullDate,
targetDayOfWeekKR,
isHoliday,
isToday,
isSaturday,
isSunday,
isPrevDate,
} = getDateInfo(value, today);

const additionalText = () => {
if (!availableAttendees) return '\u00A0';
Expand All @@ -43,8 +54,7 @@ export default function SingleDateViewer({
availableAttendees && <AttendeeTooltip attendeeNames={availableAttendees} position="top" />;

return status === 'current' ? (
<button
disabled={!isAvailable || isPrevDate}
<div
css={[
s_dateContainer,
s_baseDateButton,
Expand All @@ -58,11 +68,16 @@ export default function SingleDateViewer({
isSaturday,
}),
]}
role="text"
aria-hidden={!isAvailable}
aria-label={
isAvailable ? formatAriaFullDate(targetFullDate, targetDayOfWeekKR, availableAttendees) : ''
}
>
<span css={s_baseDateText}>{date}</span>
<span css={s_additionalText}>{additionalText()}</span>
{renderTooltip()}
</button>
</div>
) : (
<div css={s_dateContainer}></div>
);
Expand Down
31 changes: 27 additions & 4 deletions frontend/src/components/MeetingConfirmCalendar/Viewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ import {
import { Button } from '@components/_common/Buttons/Button';
import TabButton from '@components/_common/Buttons/TabButton';
import Calendar from '@components/_common/Calendar';
import ScreenReaderOnly from '@components/_common/ScreenReaderOnly';

import useRouter from '@hooks/useRouter/useRouter';

import { useGetSchedules } from '@stores/servers/schedule/queries';

import { getFullDate } from '@utils/date';
import { formatAriaTab } from '@utils/a11y';
import { getFullDate, hasSelectableDaysInMonth } from '@utils/date';

import Check from '@assets/images/attendeeCheck.svg';
import Pen from '@assets/images/pen.svg';

import Header from '../Header/Header';
import Header from '../Header';
import SingleDateViewer from '../SingleDate/SingleDateViewer';
import WeekDays from '../WeekDays';

Expand Down Expand Up @@ -84,6 +86,7 @@ export default function Viewer({
tabButtonVariants="outlinedFloating"
onClick={() => setSelectedAttendee('')}
isActive={selectedAttendee === ''}
aria-label={formatAriaTab('전체', selectedAttendee === '')}
>
{selectedAttendee === '' && <Check width="12" height="12" />}
전체
Expand All @@ -94,6 +97,7 @@ export default function Viewer({
tabButtonVariants="outlinedFloating"
onClick={() => setSelectedAttendee(attendee)}
isActive={selectedAttendee === attendee}
aria-label={formatAriaTab(attendee, selectedAttendee === attendee)}
>
{selectedAttendee === attendee && <Check width="12" height="12" />}
{attendee}
Expand All @@ -102,8 +106,22 @@ export default function Viewer({
</section>

<Calendar>
<Calendar.Header render={(props) => <Header {...props} />} />
<Calendar.Header
render={(props) => (
<Header {...props}>
<ScreenReaderOnly>
{hasSelectableDaysInMonth(
props.currentMonth,
meetingSchedules.schedules.map(({ date }) => date),
)
? '선택 가능한 날이 있습니다.'
: '선택 가능한 날이 없습니다.'}
</ScreenReaderOnly>
</Header>
)}
/>
<Calendar.WeekDays render={(weekdays) => <WeekDays weekdays={weekdays} />} />

<Calendar.Body
renderDate={(dateInfo, today) => (
<SingleDateViewer
Expand Down Expand Up @@ -138,7 +156,12 @@ export default function Viewer({
</Button>
)}
</div>
<button disabled={isLocked} onClick={handleScheduleUpdate} css={s_circleButton}>
<button
disabled={isLocked}
onClick={handleScheduleUpdate}
css={s_circleButton}
aria-label="약속 수정하기"
>
<Pen width="28" height="28" />
</button>
</footer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface WeekDaysProps {

export default function WeekDays({ weekdays }: WeekDaysProps) {
return (
<section css={[s_calendarContent, s_dayOfWeekContainer]}>
<section css={[s_calendarContent, s_dayOfWeekContainer]} aria-hidden={true}>
{weekdays.map((day, index) => (
<div key={day} css={[s_baseDayOfWeek, s_dayOfWeek(index)]}>
{day}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import TabButton from '@components/_common/Buttons/TabButton';
import useRouter from '@hooks/useRouter/useRouter';
import useSelectSchedule from '@hooks/useSelectSchedule/useSelectSchedule';

import { formatAriaTab } from '@utils/a11y';

import Check from '@assets/images/attendeeCheck.svg';
import Pen from '@assets/images/pen.svg';

Expand Down Expand Up @@ -73,6 +75,7 @@ export default function SchedulesViewer({
tabButtonVariants="outlinedFloating"
onClick={() => handleAttendeeChange('')}
isActive={selectedAttendee === ''}
aria-label={formatAriaTab('전체', selectedAttendee === '')}
>
{selectedAttendee === '' && <Check width="12" height="12" />}
전체
Expand All @@ -83,6 +86,7 @@ export default function SchedulesViewer({
tabButtonVariants="outlinedFloating"
onClick={() => handleAttendeeChange(attendee)}
isActive={selectedAttendee === attendee}
aria-label={formatAriaTab(attendee, selectedAttendee === attendee)}
>
{selectedAttendee === attendee && <Check width="12" height="12" />}
{attendee}
Expand Down Expand Up @@ -127,7 +131,12 @@ export default function SchedulesViewer({
</Button>
)}
</div>
<button disabled={isLocked} onClick={handleScheduleUpdate} css={s_circleButton}>
<button
disabled={isLocked}
onClick={handleScheduleUpdate}
css={s_circleButton}
aria-label="약속 수정하기"
>
<Pen width="28" height="28" />
</button>
</footer>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/Schedules/Schedules.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import type {
} from 'types/schedule';
import type { TooltipPosition } from 'types/tooltip';

import { DAY_OF_WEEK_KR } from '@constants/date';

export const formatDate = (dateString: string) => {
const currentDateObj = new Date(dateString);
const currentMonth = currentDateObj.getMonth() + 1;
const currentDay = currentDateObj.getDate();
const dayOfWeek = ['일', '월', '화', '수', '목', '금', '토'][currentDateObj.getDay()];
const dayOfWeek = DAY_OF_WEEK_KR[currentDateObj.getDay()];

return {
dayOfWeek,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function BackButton({ path }: BackButtonProps) {
};

return (
<button css={s_headerIconButton} onClick={handleBackButtonClick}>
<button css={s_headerIconButton} onClick={handleBackButtonClick} aria-label="뒤로가기">
<BackSVG width="24" height="24" />
</button>
);
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/components/_common/Buttons/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
isLoading?: boolean;
customCss?: SerializedStyles;
ariaLabel?: string;
}

export function Button({
Expand All @@ -27,13 +28,20 @@ export function Button({
customCss,
type = 'button',
onClick,
'aria-label': ariaLabel,
}: ButtonProps) {
const cssProps = [s_baseButton(borderRadius), s_size(size)];

if (variant) cssProps.push(s_variant[variant]);

return (
<button disabled={disabled} css={[cssProps, customCss]} type={type} onClick={onClick}>
<button
disabled={disabled}
css={[cssProps, customCss]}
type={type}
onClick={onClick}
aria-label={ariaLabel}
>
{isLoading && <Spinner backgroundColor={'#f4f4f5'} />}
{!isLoading && children}
</button>
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/components/_common/Calendar/Body/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// import 하지 않으면 스토리북에서 캘린더 컴포넌트가 렌더링 되지 않아 일단 추가(@해리)
import React from 'react';
import type { DateInfo } from 'types/calendar';

import { useCalendarContext } from '@hooks/useCalendarContext/useCalendarContext';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import Calendar from '.';
const meta: Meta<typeof Calendar> = {
title: 'Components/Calendar',
component: Calendar,
tags: ['autodocs'],
parameters: {
layout: 'centered',
},
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/components/_common/Calendar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type { PropsWithChildren } from 'react';
// import 하지 않으면 스토리북에서 캘린더 컴포넌트가 렌더링 되지 않아 일단 추가(@해리)
import React from 'react';

import CalendarProvider from '@contexts/CalendarProvider';

Expand Down
7 changes: 7 additions & 0 deletions frontend/src/components/_common/Header/Header.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ export const s_header = css`
`;

export const s_title = css`
pointer-events: none;

position: absolute;
left: 50%;
transform: translateX(-50%);

font-weight: ${theme.typography.bodyBold};

&:focus {
outline: none;
}
`;
12 changes: 10 additions & 2 deletions frontend/src/components/_common/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PropsWithChildren } from 'react';
import { type PropsWithChildren, useEffect, useRef } from 'react';

import { s_header, s_title } from './Header.styles';

Expand All @@ -7,10 +7,18 @@ interface HeaderProps {
}

export default function Header({ title, children }: PropsWithChildren<HeaderProps>) {
const titleRef = useRef<HTMLHeadingElement>(null);

useEffect(() => {
titleRef.current?.focus();
}, []);

return (
<header css={s_header}>
{children}
<h1 css={s_title}>{title}</h1>
<h1 css={s_title} ref={titleRef} tabIndex={-1}>
{title}
</h1>
</header>
);
}
Loading