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

모임 상세목록 접근성 개선 #710

Merged
merged 6 commits into from
Oct 23, 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
7 changes: 4 additions & 3 deletions frontend/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { HTMLAttributes, ReactNode } from 'react';
import {
Interpolation,
SerializedStyles,
Theme,
useTheme,
} from '@emotion/react';

import { ReactNode } from 'react';
import { shapes } from '@_components/Button/Button.style';

export interface ButtonProps {
export interface ButtonProps extends HTMLAttributes<HTMLButtonElement> {
shape: 'circle' | 'bar';
onClick?: () => void;
disabled?: boolean;
Expand All @@ -22,13 +22,14 @@ export interface ButtonProps {
}

export default function Button(props: ButtonProps) {
const { onClick, disabled, children, font } = props;
const { onClick, disabled, children, font, ...restProps } = props;
const theme = useTheme();
return (
<button
css={[shapes({ ...props, theme }), font]}
onClick={onClick}
disabled={disabled}
{...restProps}
>
{children}
</button>
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/Input/MessagInput/MessageInput.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as S from '@_components/Input/MessagInput/MessageInput.style';
import { useTheme } from '@emotion/react';

import { useState } from 'react';
import SubmitButton from '@_common/assets/submit_message_button.svg';
import { useState } from 'react';
import { useTheme } from '@emotion/react';

export interface MessageInputProps {
placeHolder: string;
Expand Down Expand Up @@ -36,6 +36,7 @@ export default function MessageInput(props: MessageInputProps) {
css={S.button({ theme })}
type="submit"
disabled={!message.trim() || disabled}
aria-label={`댓글쓰기 버튼 ${!message.trim() || disabled ? '댓글을 작성하여 버튼을 활성화하세요' : ''}`}
>
<SubmitButton />
</button>
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/components/KebabMenu/KebabMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as S from '@_components/KebabMenu/KebabMenu.style';
import { useRef, useState, FocusEvent } from 'react';

import { FocusEvent, useRef, useState } from 'react';

import KebabButton from '@_common/assets/kebab_menu.svg';
import { useTheme } from '@emotion/react';

Expand Down Expand Up @@ -35,14 +37,20 @@ export default function KebabMenu(props: KebabMenuProps) {

return (
<div css={S.kebabContainer({ theme })}>
<button onClick={handleKebabToggle} onBlur={handleKebabClose}>
<button
onClick={handleKebabToggle}
onBlur={handleKebabClose}
aria-label="케밥 메뉴 활성화 버튼"
aria-checked={isKebabOpen}
>
<KebabButton />
</button>
{isKebabOpen && (
<div ref={optionsRef} css={S.optionBox({ theme })}>
{options.map((option) => {
return (
<button
aria-label={option.name}
key={option.name}
onClick={() => handleOptionClick(option.onClick)}
disabled={option.disabled}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Theme, css } from '@emotion/react';

export const explanationSection = ({ theme }: { theme: Theme }) => css`
margin: 0 5rem;
${theme.typography.s1}
`;
245 changes: 136 additions & 109 deletions frontend/src/pages/Moim/MoimDetailPage/MoimDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { MoimInfo, Role } from '@_types/index';
import { useNavigate, useParams } from 'react-router-dom';

import BackLogo from '@_common/assets/back.svg';
import Button from '@_components/Button/Button';
import CommentList from './components/CommentList/CommentList';
import CommentListSkeleton from './components/CommentList/CommentListSkeleton';
Expand Down Expand Up @@ -28,7 +31,48 @@ import useOpenChat from '@_hooks/mutaions/useOpenChat';
import useReopenMoim from '@_hooks/mutaions/useReopenMoim';
import { useTheme } from '@emotion/react';
import useZzimMine from '@_hooks/queries/useZzimMine';
import BackArrowButton from '@_components/Button/BackArrowButton/BackArrowButton';

const getButtonMessage = (moim: MoimInfo, role: Role) => {
if (moim.status === 'CANCELED') return '취소된 모임이에요';

if (role === 'MOIMER') {
if (moim.status === 'MOIMING') return '모집 완료하기';
if (moim.status === 'COMPLETED') return '채팅방 열기(이동하기)';
return '';
}
if (role === 'NON_MOIMEE') {
if (moim.status === 'MOIMING') return '참여하기';
if (moim.status === 'COMPLETED') return '모집이 완료되었어요';
return '';
}
if (role === 'MOIMEE') {
if (moim.status === 'MOIMING') return '방장이 채팅방을 만들지 않았습니다';
if (moim.status === 'COMPLETED') return '채팅방으로 가기';
return '';
}
return '';
};

const getButtonDisabled = (moim: MoimInfo, role: Role) => {
if (moim.status === 'CANCELED') return true;

if (role === 'MOIMER') {
if (moim.status === 'MOIMING') return false;
if (moim.status === 'COMPLETED') return false;
return true;
}
if (role === 'NON_MOIMEE') {
if (moim.status === 'MOIMING') return false;
if (moim.status === 'COMPLETED') return true;
return true;
}
if (role === 'MOIMEE') {
if (moim.status === 'MOIMING') return true;
if (moim.status === 'COMPLETED') return false;
return true;
}
return true;
};

export default function MoimDetailPage() {
const navigate = useNavigate();
Expand All @@ -38,11 +82,11 @@ export default function MoimDetailPage() {
const moimId = Number(params.moimId);

const { moim, isLoading: isMoimLoading } = useMoim(moimId);
const { role, isChamyoMineLoading } = useChamyoMine(moimId);
const { role } = useChamyoMine(moimId);
const { isZzimed, isZzimMineLoading } = useZzimMine(moimId);
const { participants, chamyoAllIsLoading } = useChamyoAll(moimId);
const { mutate: changZzim } = useChangeZzim();
const { mutate, isPending: isPendingJoinMoim } = useJoinMoim(() => {
const { mutate: joinMoim } = useJoinMoim(() => {
navigate(GET_ROUTES.nowDarakbang.moimParticipateComplete());
});

Expand All @@ -51,49 +95,64 @@ export default function MoimDetailPage() {

const { mutate: ReopenMoim, isPending: isPendingReopenMoim } =
useReopenMoim();
const { mutate: completeMoim, isPending: isPendingCompleteMoim } =
useCompleteMoin();
const { mutate: completeMoim } = useCompleteMoin();
const { mutate: cancelChamyo, isPending: isPendingCancelChamyo } =
useCancelChamyo();
const { mutate: openChat, isPending: isPendingOpenChat } = useOpenChat(
(chatRoomId: number) =>
navigate(GET_ROUTES.nowDarakbang.chattingRoom(chatRoomId)),
const { mutate: openChat } = useOpenChat((chatRoomId: number) =>
navigate(GET_ROUTES.nowDarakbang.chattingRoom(chatRoomId)),
);

const kebabMenu = useMemo(() => {
return role === 'MOIMER' ? (
<KebabMenu
options={[
{
name: '모임 수정하기',
disabled: false,
onClick: () =>
navigate(GET_ROUTES.nowDarakbang.modify(moimId), {
state: {
...moim,
moimId,
},
}),
},
{
name: '모임 취소하기',
disabled: isPendingCancelMoim,
onClick: () => cancelMoim(moimId),
},
{
name: '모임 다시 열기',
disabled: isPendingReopenMoim,
onClick: () => ReopenMoim(moimId),
},
]}
/>
) : (
if (role === 'MOIMER') {
return (
<KebabMenu
options={[
{
name: '모임 수정하기',
disabled: false,
onClick: () =>
navigate(GET_ROUTES.nowDarakbang.modify(moimId), {
state: {
...moim,
moimId,
},
}),
},
{
name: '모임 취소하기',
disabled: isPendingCancelMoim,
onClick: () => cancelMoim(moimId),
},
{
name: '모임 다시 열기',
disabled: isPendingReopenMoim,
onClick: () => ReopenMoim(moimId),
},
]}
/>
);
}
if (role === 'MOIMEE') {
return (
<KebabMenu
options={[
{
name: '참여 취소하기',
disabled: isPendingCancelChamyo,
onClick: () => cancelChamyo(moimId),
},
]}
/>
);
}

return (
<KebabMenu
options={[
{
name: '참여 취소하기',
name: '사용할 수 있는 메뉴가 없습니다',
disabled: isPendingCancelChamyo,
onClick: () => cancelChamyo(moimId),
onClick: () => {},
},
]}
/>
Expand All @@ -111,89 +170,48 @@ export default function MoimDetailPage() {
role,
]);

const button = useMemo(() => {
return isChamyoMineLoading ? (
''
) : role === 'MOIMER' ? (
moim?.status === 'MOIMING' ? (
<Button
shape="bar"
disabled={false || isPendingCompleteMoim}
onClick={() => completeMoim(moimId)}
>
모집 완료하기
</Button>
) : moim?.status === 'CANCELED' ? (
<Button shape="bar" disabled={true}>
취소된 모임이예요
</Button>
) : (
<Button
shape="bar"
disabled={false || isPendingOpenChat}
onClick={() => openChat(moimId)}
>
채팅방 열기(이동하기)
</Button>
)
) : role === 'NON_MOIMEE' ? (
moim?.status === 'MOIMING' ? (
<Button
shape="bar"
disabled={false || isPendingJoinMoim}
onClick={() => mutate(moimId)}
>
참여하기
</Button>
) : moim?.status === 'COMPLETED' ? (
<Button shape="bar" disabled={true}>
모집이 완료되었어요
</Button>
) : (
<Button shape="bar" disabled={true}>
취소된 모임이예요
</Button>
)
) : moim?.status === 'MOIMING' ? (
<Button shape="bar" disabled={true}>
방장이 채팅방을 만들지 않았습니다
</Button>
) : (
<Button
shape="bar"
disabled={false}
onClick={() => navigate(GET_ROUTES.nowDarakbang.chattingRoom(moimId))}
>
채팅방으로 가기
</Button>
);
}, [
completeMoim,
isPendingCompleteMoim,
isPendingJoinMoim,
isPendingOpenChat,
moim?.status,
moimId,
navigate,
mutate,
openChat,
role,
isChamyoMineLoading,
]);
const buttonClickHandler = (moim: MoimInfo, role: Role) => {
if (moim.status === 'CANCELED') return;

if (role === 'MOIMER') {
if (moim.status === 'MOIMING') return completeMoim(moimId);
if (moim.status === 'COMPLETED') return openChat(moimId);
return;
}
if (role === 'NON_MOIMEE') {
if (moim.status === 'MOIMING') return joinMoim(moimId);
if (moim.status === 'COMPLETED') return;
return;
}
if (role === 'MOIMEE') {
if (moim.status === 'MOIMING') return;
if (moim.status === 'COMPLETED')
return navigate(GET_ROUTES.nowDarakbang.chattingRoom(moimId));
return;
}
return;
};

return (
<InformationLayout>
<InformationLayout.Header>
<InformationLayout.Header.Left>
<BackArrowButton
<div
role="button"
aria-label="뒤로가기"
onClick={() => navigate(GET_ROUTES.nowDarakbang.main())}
/>
>
<BackLogo />
</div>
</InformationLayout.Header.Left>
<InformationLayout.Header.Right>
{isZzimMineLoading && (
<ZzimButton isZzimed={false} onClick={() => {}} />
)}
{!isZzimMineLoading && (
<ZzimButton
aria-label={`찜 토글 버튼`}
aria-pressed={!!isZzimed}
isZzimed={!!isZzimed}
onClick={() => changZzim(moimId)}
/>
Expand Down Expand Up @@ -241,7 +259,16 @@ export default function MoimDetailPage() {
)}
</InformationLayout.ContentContainer>
<InformationLayout.BottomButtonWrapper>
{button}
{moim && role && (
<Button
shape="bar"
disabled={getButtonDisabled(moim, role)}
onClick={() => buttonClickHandler(moim, role)}
aria-label={getButtonMessage(moim, role)}
>
{getButtonMessage(moim, role)}
</Button>
)}
</InformationLayout.BottomButtonWrapper>
</InformationLayout>
);
Expand Down
Loading
Loading