Skip to content

Commit

Permalink
게시글 컴포넌트, 댓글 컴포넌트, PostMenu 컴포넌트 웹접근성 높이기 (#451)
Browse files Browse the repository at this point in the history
* feat:(#409) 비회원 유저 게시글 웹 접근성 완료

* feat: (#409) 토스트에서 에러 메세지가 나타날 때 스크린리더기에서 들리도록 수정

* chore: (#409) package.json 맥북에서 휴대폰으로 들어갈 수 있는 로컬 아이피를 반환하는 명령어 추가

* feat: (#409) 비회원 댓글에서 로그인하라는 컴포넌트 스크린 리더기 웹 접근성 완료

* feat: (#409) 댓글 더 보기시 탭 인덱스가 맨 밑으로 이동하는 문제 수정

* feat: (#409) 투표 선택지를 클릭했을 때 투표 완료한 선택지 상태로 나오도록 구현

* feat: (#409) 댓글 메뉴, 게시글 메뉴 등 스크린 리더기에서 더 많은 정보를 주도록 구현

* feat: (#409) 의미있는 태그 사용 및 글자 색상 더 진하게 변경

* feat: (#409) 게시글 신고 메뉴 열기, 닫기가 스크린 리더기로 들리도록 구현

* fix: (#409) 동일하게 button 태그로 보이도록 한 코드 수정

* refactor: (#409) 댓글 작성/수정을 구분 짓는 대상 변경 - 코드 가독성 측면

* refactor: (#409) 댓글 더보기를 성공했을 경우 사용자에게 알려줌(접근성)

* refactor: (#409) 없어도 되는 속성 제거
  • Loading branch information
Gilpop8663 authored and tjdtls690 committed Sep 12, 2023
1 parent 2f4a8dc commit 54fc1da
Show file tree
Hide file tree
Showing 19 changed files with 196 additions and 73 deletions.
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test": "jest",
"prepare": "cd .. && husky install frontend/.husky"
"prepare": "cd .. && husky install frontend/.husky",
"mac-local-ip": "ifconfig | grep \"inet \" | grep -v 127.0.0.1"
},
"dependencies": {
"@tanstack/react-query": "^4.29.19",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/PostForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
POST_DEADLINE_POLICY,
POST_TITLE_POLICY,
} from '@constants/policyMessage';
import { CATEGORY_COUNT_LIMIT, IMAGE_BASE_URL, POST_CONTENT, POST_TITLE } from '@constants/post';
import { CATEGORY_COUNT_LIMIT, POST_CONTENT, POST_TITLE } from '@constants/post';

import { calculateDeadlineTime } from '@utils/post/calculateDeadlineTime';
import { checkWriter } from '@utils/post/checkWriter';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export default function CommentMenu({ menuList, handleMenuClick }: CommentMenuPr
<S.Menu
key={content}
type="button"
tabIndex={0}
aria-label={`댓글 ${content}`}
$color={color}
onClick={(event: MouseEvent) => {
event.stopPropagation();
Expand Down
19 changes: 15 additions & 4 deletions frontend/src/components/comment/CommentList/CommentItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,29 @@ export default function CommentItem({ comment, userType }: CommentItemProps) {
const isAllowedMenu = userType !== COMMENT_USER.GUEST && action !== COMMENT_ACTION.EDIT;

return (
<S.Container>
<S.Container tabIndex={0}>
<S.Header>
<S.UserContainer>
<S.Nickname>{member.nickname}</S.Nickname>
<S.Nickname aria-label={`댓글 작성자: ${member.nickname}`}>{member.nickname}</S.Nickname>
<S.SubTitleContainer>
<S.SubTitle>{createdAt}</S.SubTitle>
{isEdit && <S.SubTitle>(수정됨)</S.SubTitle>}
</S.SubTitleContainer>
</S.UserContainer>
{isAllowedMenu && (
<S.MenuContainer aria-label="댓글 메뉴" onClick={toggleComponent}>
<S.Image src={ellipsis}></S.Image>
<S.MenuContainer
as={isOpen ? 'div' : 'button'}
role="button"
tabIndex={0}
aria-label={isOpen ? '댓글 메뉴 닫기' : '댓글 메뉴 열기'}
onClick={toggleComponent}
>
<S.Image
tabIndex={0}
role="button"
alt={isOpen ? '댓글 메뉴 닫기' : '댓글 메뉴 열기'}
src={ellipsis}
></S.Image>
{isOpen && (
<S.MenuWrapper>
<CommentMenu handleMenuClick={handleMenuClick} menuList={COMMENT_MENU[USER_TYPE]} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const SubTitle = styled.span`
font: var(--text-small);
font-weight: 400;
color: var(--dark-gray);
color: var(--text-dark-gray);
&:nth-child(2) {
margin-left: 6px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function CommentLoginSectionSection({ name }: CommentLoginSection
<S.Container>
<S.Title>대화에 참여하려면 회원가입</S.Title>
<S.SubTitle>로그인하여 {name}님의 이야기에 대해 피드백을 제공해 보세요</S.SubTitle>
<S.LoginLink to={PATH.LOGIN}>
<S.LoginLink to={PATH.LOGIN} aria-label="카카오 로그인 페이지로 이동">
<S.Image src={kakaoLogin} alt="로그인 페이지로" />
</S.LoginLink>
</S.Container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const Title = styled.span`
}
`;

export const SubTitle = styled.span`
export const SubTitle = styled.p`
margin-top: 10px;
font: var(--text-caption);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export default function CommentTextForm({
const params = useParams() as { postId: string };
const postId = Number(params.postId);

const isEdit = initialComment.id !== -1;

const {
mutate: createComment,
isSuccess: isCreateSuccess,
Expand All @@ -44,14 +46,13 @@ export default function CommentTextForm({
error: editError,
} = useEditComment(postId, commentId);

const updateComment =
initialComment.id !== -1
? () => {
editComment({ ...initialComment, content });
}
: () => {
createComment({ content });
};
const updateComment = isEdit
? () => {
editComment({ ...initialComment, content });
}
: () => {
createComment({ content });
};

useEffect(() => {
isCreateSuccess && resetText();
Expand All @@ -72,19 +73,30 @@ export default function CommentTextForm({
return (
<S.Container>
<S.TextArea
aria-label={isEdit ? '댓글 수정' : '댓글 작성'}
value={content}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => handleTextChange(e, COMMENT)}
/>
<S.ButtonContainer>
{handleCancelClick && (
{isEdit && (
<S.ButtonWrapper>
<SquareButton onClick={handleCancelClick} theme="gray" type="button">
<SquareButton
aria-label="댓글 취소"
onClick={handleCancelClick}
theme="gray"
type="button"
>
취소
</SquareButton>
</S.ButtonWrapper>
)}
<S.ButtonWrapper>
<SquareButton onClick={() => updateComment()} theme="blank" type="button">
<SquareButton
aria-label="댓글 저장"
onClick={() => updateComment()}
theme="blank"
type="button"
>
저장
</SquareButton>
</S.ButtonWrapper>
Expand Down
45 changes: 36 additions & 9 deletions frontend/src/components/comment/CommentList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext } from 'react';
import { useContext, useRef, Fragment } from 'react';

import { AuthContext } from '@hooks/context/auth';
import { useCommentList } from '@hooks/query/comment/useCommentList';
Expand Down Expand Up @@ -31,6 +31,7 @@ const initialComment = {
};

export default function CommentList({ postId, postWriterName }: CommentListProps) {
const inputRef = useRef<HTMLInputElement>(null);
const { data: commentList } = useCommentList(postId);
const { loggedInfo } = useContext(AuthContext);
const { isLoggedIn, id: memberId } = loggedInfo;
Expand Down Expand Up @@ -63,17 +64,43 @@ export default function CommentList({ postId, postWriterName }: CommentListProps
)}
</S.TextOrLoginWrapper>
<S.ListContainer>
{slicedCommentList.map(comment => (
<CommentItem
key={comment.id}
comment={comment}
userType={getUserType(comment.member.id)}
/>
))}
{slicedCommentList.map((comment, index) => {
if (index % 10 === 9) {
return (
<Fragment key={comment.id}>
<CommentItem comment={comment} userType={getUserType(comment.member.id)} />
<S.HiddenInput
ref={inputRef}
maxLength={0}
aria-label={`${index + 1}번째 댓글입니다`}
role="contentinfo"
inputMode="none"
/>
</Fragment>
);
}
return (
<CommentItem
key={comment.id}
comment={comment}
userType={getUserType(comment.member.id)}
/>
);
})}
</S.ListContainer>
{hasMoreComment && (
<S.MoreButtonWrapper>
<SquareButton onClick={handleMoreComment} theme="fill">
<SquareButton
onClick={() => {
if (!inputRef.current) return;

handleMoreComment();
inputRef.current.focus();
inputRef.current.ariaLabel = '더보기 버튼을 눌러 댓글이 추가되었습니다';
}}
theme="fill"
aria-label="댓글 더보기"
>
더보기
</SquareButton>
</S.MoreButtonWrapper>
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/comment/CommentList/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export const ListContainer = styled.div`
padding-top: 25px;
border-top: 1px solid rgba(0, 0, 0, 0.2);
position: relative;
`;

export const MoreButtonWrapper = styled.div`
Expand Down Expand Up @@ -49,3 +51,11 @@ export const TopButtonWrapper = styled.div`
height: 46px;
}
`;

export const HiddenInput = styled.input`
position: absolute;
bottom: 0;
color: white;
z-index: -1;
`;
46 changes: 37 additions & 9 deletions frontend/src/components/common/Post/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,12 @@ export default function Post({ postInfo, isPreview }: PostProps) {
return voteInfo.options.map(option => option.imageUrl).some(url => url !== '');
};

const isPreviewTabIndex = isPreview ? undefined : 0;

return (
<S.Container>
<S.Container as={isPreview ? 'li' : 'div'}>
<S.DetailLink
as={isPreview ? '' : 'main'}
to={isPreview ? `${PATH.POST}/${postId}` : '#'}
$isPreview={isPreview}
onClick={handleLinkClick}
Expand All @@ -110,30 +113,55 @@ export default function Post({ postInfo, isPreview }: PostProps) {
? '해당 게시물의 상세페이지로 이동하기'
: '현재 상세페이지이므로 사용할 수 없습니다.'
}
aria-disabled={isPreview ? false : true}
>
<S.Category aria-label="카테고리">
<S.Category
tabIndex={isPreviewTabIndex}
aria-label={`카테고리 ${category.map(category => category.name).join('|')}`}
>
{category.map(category => category.name).join(' | ')}
</S.Category>
{isPreview && checkIncludeImage() && (
<S.ImageIconWrapper>
<S.ImageIcon src={photoIcon} alt="해당 게시물은 사진을 포함하고 있습니다." />
</S.ImageIconWrapper>
)}
<S.ActivateState aria-label="마감 상태" $isActive={isActive} />
<S.Title aria-label="제목" $isPreview={isPreview}>
<S.ActivateState
tabIndex={isPreviewTabIndex}
role="status"
aria-label={`게시글 ${isActive ? '진행중' : '마감완료'}`}
$isActive={isActive}
/>
<S.Title
tabIndex={isPreviewTabIndex}
aria-label={`게시글 제목: ${title}`}
$isPreview={isPreview}
>
{title}
</S.Title>
<S.Wrapper>
<span aria-label="작성자">{writer.nickname}</span>
<span aria-label={`작성자 ${writer.nickname}`} tabIndex={isPreviewTabIndex}>
{writer.nickname}
</span>
<S.Wrapper>
<span aria-label="작성일시">{convertTimeToWord(createTime)}</span>
<span aria-label="투표 마감일시">
<span
aria-label={`작성일시 ${convertTimeToWord(createTime)}`}
tabIndex={isPreviewTabIndex}
>
{convertTimeToWord(createTime)}
</span>
<span
aria-label={`투표 마감일시 ${isActive ? convertTimeToWord(deadline) : '마감 완료'}`}
tabIndex={isPreviewTabIndex}
>
{isActive ? convertTimeToWord(deadline) : '마감 완료'}
</span>
</S.Wrapper>
</S.Wrapper>
<S.Content aria-label="내용" $isPreview={isPreview}>
<S.Content
tabIndex={isPreviewTabIndex}
aria-label={`내용: ${content}`}
$isPreview={isPreview}
>
{content}
</S.Content>
{!isPreview && imageUrl && (
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/common/Toast/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ interface ToastProps {
export default function Toast({ children, size, position }: ToastProps) {
return (
<S.Wrapper $position={position}>
<S.Content $size={size}>{children}</S.Content>
<S.Content aria-live="polite" $size={size}>
{children}
</S.Content>
</S.Wrapper>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,27 @@ export default function WrittenVoteOption({
}: WrittenVoteOptionProps) {
return (
<S.Container
aria-label={`${ariaLabel}${isSelected ? '선택된 선택지' : ''}`}
aria-live={isSelected ? 'polite' : 'off'}
aria-label={ariaLabel}
$isSelected={isSelected}
onClick={handleVoteClick}
>
{!isPreview && imageUrl && (
<S.Image src={convertImageUrlToServerUrl(imageUrl)} alt={'선택지에 포함된 이미지'} />
)}
{isPreview ? (
<S.PreviewContent aria-label="선택지 내용">{text}</S.PreviewContent>
<S.PreviewContent>{text}</S.PreviewContent>
) : (
<S.DetailContent aria-label="선택지 내용">{text}</S.DetailContent>
<S.DetailContent>{text}</S.DetailContent>
)}
{isStatisticsVisible && (
<>
<S.ProgressContainer aria-label={''}>
<S.ProgressContainer>
<ProgressBar percent={percent} isSelected={isSelected} />
</S.ProgressContainer>
<S.TextContainer>
<S.PeopleText aria-label="투표한 인원">{peopleCount}</S.PeopleText>
<S.PercentText aria-label="전체 투표 중 차지 비율">
({percent.toFixed(1)}%)
</S.PercentText>
<S.PeopleText aria-hidden="true">{peopleCount}명</S.PeopleText>
<S.PercentText aria-hidden="true">({percent.toFixed(1)}%)</S.PercentText>
</S.TextContainer>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ export const PeopleText = styled.span`
export const PercentText = styled.span`
margin-left: 4px;
font: var(--text-small);
color: var(--text-dark-gray);
opacity: 0.7;
font: var(--text-small);
@media (min-width: ${theme.breakpoint.md}) {
font: var(--text-caption);
Expand Down
Loading

0 comments on commit 54fc1da

Please sign in to comment.