Skip to content

Commit

Permalink
[Feature/FE] 웹 접근성을 개선. (#788)
Browse files Browse the repository at this point in the history
* [FE] 제품 탐색 및 검색 로직에 접근성 구현 (#776)

* feat: 검색어 입력란 접근성 개선

* feat: 카테고리 선택 필터 접근성 개선

* feat: 아이콘을 숨길 수 있도록 빈 버튼에서 div로 변경

* fix: 제품 목록을 Masonry => Grid로 변경

접근성 개선

* feat: lazy image에 alt 속성을 추가할 수 있도록 수정

* feat: 헤더 로고 링크 레이블 추가

* style: grid 레이아웃에서 각 항목을 가운데 정렬하도록 수정

* feat: 제품 목록 로딩, 추가 로딩 시 로딩 상태에 대해서 알려주는 로직 추가

* style: 모바일 제품 목록 페이지에서 링크가 전체 너비를 차지하는 현상 수정

* feat: 포커스를 이용해서 무한 스크롤을 이용할 수 있도록 수정

* feat: 평점 및 리뷰 접근성 개선

* feat: 섹션 탐색 용이하도록 article 대신 div 사용, 제품 카드 접근성 개선

* feat: 리뷰 카드를 랜드마크 탐색에 잡히지 않도록 article에서 div로 변경

* feat: 평점 컴포넌트에 평점 레이블 추가

* test: 변경된 태그에 맞도록 테스트 수정

* fix: 스크린리더에게만 읽히는 요소에 대한 스타일 적용 변경

* feat: 제품 검색, 카테고리 적용 시 접근성 개선

* feat: 정렬 기준 select 요소 레이블 추가

* feat: 검색 시 검색 키워드가 섹션의 제목에 반영되도록 수정

* fix: navbar 배치 스타일 문제 해결

* style: 누락된 제품 목록 스타일 추가

* refactor: 이미지와 내용을 내용과 단위로 읽어주는 기능 구현 방식 통일

* fix: 키보드 상태 기본값 적용 오류 수정

* [FE] 상하단 메뉴바 접근성 개선 (#777)

* feat: 하단 메뉴바에 접근성을 위해 목록 추가, 레이블 추가

* feat: 상단 메뉴바에 프로필 버튼 레이블 추가

* refactor: 모바일 상단 메뉴바 컴포넌트 위치 headerNav로 이동, 레이블 추가

* 무한 스크롤 로딩정보 관련 메시지 추가 (#781)

feat: 무한 스크롤 로딩정보 관련 메시지 추가

* [FE] 제품 목록 불러오기 실패나 항목 없을 때 스크린 리더 alert 추가 (#783)

feat: 제품 목록 불러오기 실패나 항목 없을 때 스크린 리더 alert 추가

* [FE] 상품 상세 조회 + 리뷰 작성 플로우의 웹 접근성을 개선 (#787)

* feat: 제품 이미지 aria-label 적용

* feat: ReviewCount 접근성 적용

* feat: Rating 접근성 적용

* feat: BarGraph 접근성 적용

* feat: 리뷰 목록에서 평점 읽어주도록 접근성 개선

* feat: 바텀 시트 표시될 경우 시트 내부로 포커스 이동

* [BugFix/FE] 사파리 브라우저에서 좌우측 프로필 카드 버튼이 동일하게 렌더링되는 문제를 수정 (#772)

fix: 화살표 이미지 교체

* fix: 메뉴바 사라지는 현상 수정

* feat: 모달이 띄워지는 경우 모달 내부로 포커스 이동

* feat: 리뷰 작성 시 리뷰 목록 focus

* feat: 평점 입력 접근성 향상

* feat: 리뷰 1000자 입력시 스크린리더 알림

* [FE] 제품 탐색 및 검색 로직에 접근성 구현 (#776)

* feat: 검색어 입력란 접근성 개선

* feat: 카테고리 선택 필터 접근성 개선

* feat: 아이콘을 숨길 수 있도록 빈 버튼에서 div로 변경

* fix: 제품 목록을 Masonry => Grid로 변경

접근성 개선

* feat: lazy image에 alt 속성을 추가할 수 있도록 수정

* feat: 헤더 로고 링크 레이블 추가

* style: grid 레이아웃에서 각 항목을 가운데 정렬하도록 수정

* feat: 제품 목록 로딩, 추가 로딩 시 로딩 상태에 대해서 알려주는 로직 추가

* style: 모바일 제품 목록 페이지에서 링크가 전체 너비를 차지하는 현상 수정

* feat: 포커스를 이용해서 무한 스크롤을 이용할 수 있도록 수정

* feat: 평점 및 리뷰 접근성 개선

* feat: 섹션 탐색 용이하도록 article 대신 div 사용, 제품 카드 접근성 개선

* feat: 리뷰 카드를 랜드마크 탐색에 잡히지 않도록 article에서 div로 변경

* feat: 평점 컴포넌트에 평점 레이블 추가

* test: 변경된 태그에 맞도록 테스트 수정

* fix: 스크린리더에게만 읽히는 요소에 대한 스타일 적용 변경

* feat: 제품 검색, 카테고리 적용 시 접근성 개선

* feat: 정렬 기준 select 요소 레이블 추가

* feat: 검색 시 검색 키워드가 섹션의 제목에 반영되도록 수정

* fix: navbar 배치 스타일 문제 해결

* style: 누락된 제품 목록 스타일 추가

* refactor: 이미지와 내용을 내용과 단위로 읽어주는 기능 구현 방식 통일

* fix: 키보드 상태 기본값 적용 오류 수정

* [FE] 상하단 메뉴바 접근성 개선 (#777)

* feat: 하단 메뉴바에 접근성을 위해 목록 추가, 레이블 추가

* feat: 상단 메뉴바에 프로필 버튼 레이블 추가

* refactor: 모바일 상단 메뉴바 컴포넌트 위치 headerNav로 이동, 레이블 추가

* 무한 스크롤 로딩정보 관련 메시지 추가 (#781)

feat: 무한 스크롤 로딩정보 관련 메시지 추가

* [FE] 제품 목록 불러오기 실패나 항목 없을 때 스크린 리더 alert 추가 (#783)

feat: 제품 목록 불러오기 실패나 항목 없을 때 스크린 리더 alert 추가

Co-authored-by: Yo Wook Kim <[email protected]>

* fix: 하단바, 테스트 오류 수정, focus 방식 수정

* feat: 리뷰 작성 후 focusing 되는 요소 변경

* refactor: 스크린리더용 요소 적용 방식 통일

* refactor: floating button label prop으로 넘기도록 수정

* feat: 페이지 이동 시 스크린리더로 안내

* feat: 3초 뒤 페이지 이동 안내 문구 삭제

Co-authored-by: Yo Wook Kim <[email protected]>
  • Loading branch information
jswith and uk960214 authored Oct 17, 2022
1 parent 0314f44 commit 7fe05eb
Show file tree
Hide file tree
Showing 47 changed files with 445 additions and 221 deletions.
29 changes: 16 additions & 13 deletions frontend/cypress/e2e/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ describe('비회원 사용자 기본 플로우', () => {
cy.wait('@productsRequest');

cy.findByRole('region', { name: /제품/ })
.findAllByRole('article')
.findAllByRole('link')
.should('be.visible');
});

it('홈 페이지에 접속하면 후기를 조회할 수 있다.', () => {
cy.wait('@reviewsRequest');

cy.findByRole('region', { name: /후기/ })
.findAllByRole('article')
.findAllByRole('link')
.should('be.visible');
});

Expand All @@ -33,9 +33,11 @@ describe('비회원 사용자 기본 플로우', () => {
cy.findByRole('region', { name: /후기/ }).isCardImageLoadDone();
});

cy.findByRole('region', {
name: '무한스크롤 목록 끝 지표',
}).scrollIntoView();
cy.findByRole('region', { name: /후기/ })
.findAllByRole('link')
.should('be.visible')
.last()
.scrollIntoView();

cy.wait('@reviewsRequest').then((res) => {
const page = new URL(res.request.url).searchParams.get('page');
Expand All @@ -52,7 +54,7 @@ describe('비회원 사용자 기본 플로우', () => {
cy.wait('@productsRequest');

cy.findByRole('region', { name: /키보드/ })
.findAllByRole('article')
.findAllByRole('link')
.should('be.visible');
});

Expand All @@ -63,7 +65,7 @@ describe('비회원 사용자 기본 플로우', () => {
cy.wait('@productsRequest');

cy.findByRole('region', { name: /마우스/ })
.findAllByRole('article')
.findAllByRole('link')
.should('be.visible');
});

Expand All @@ -74,7 +76,7 @@ describe('비회원 사용자 기본 플로우', () => {
cy.wait('@productsRequest');

cy.findByRole('region', { name: /모니터/ })
.findAllByRole('article')
.findAllByRole('link')
.should('be.visible');
});

Expand All @@ -85,7 +87,7 @@ describe('비회원 사용자 기본 플로우', () => {
cy.wait('@productsRequest');

cy.findByRole('region', { name: /거치대/ })
.findAllByRole('article')
.findAllByRole('link')
.should('be.visible');
});

Expand All @@ -95,26 +97,27 @@ describe('비회원 사용자 기본 플로우', () => {

cy.wait('@productsRequest');
cy.findByRole('region', { name: /소프트웨어/ })
.findAllByRole('article')
.findAllByRole('link')
.should('be.visible');
});

it('제품 상세 페이지에 진입하면, 제품 사진과 리뷰와 통계정보를 볼 수 있다.', () => {
cy.wait('@productsRequest');

cy.findByRole('region', { name: '인기 있는 제품' })
.findAllByRole('article')
.findAllByRole('link')
.first()
// .findByRole('img')
.click({ force: true });

// 후기는 없는 제품이 있을 수도 있기 때문에 삭제하거나 빈 데이터 이미지 표시 필요
// cy.findByRole('region', { name: '최근 후기' })
// .findAllByRole('article')
// .findAllByRole('link')
// .should('be.visible');
cy.wait('@productRequest');

expect(cy.findByRole('img', { name: '제품 이미지' })).to.exist;
expect(cy.findByRole('region', { name: '제품 상세 정보' }).findByRole('img')).to
.exist;

cy.findByRole('region', { name: '통계 정보' })
.scrollIntoView()
Expand Down
6 changes: 2 additions & 4 deletions frontend/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Cypress.Commands.add(
{ prevSubject: true },
(card: Cypress.Chainable<JQuery<HTMLElement>>) => {
cy.wrap(card)
.findAllByRole('article')
.findAllByRole('link')
.each((element) => {
cy.wrap(element).get('img').should('be.visible');
});
Expand All @@ -16,8 +16,6 @@ Cypress.Commands.add(
'isNotLoading',
{ prevSubject: true },
(container: Cypress.Chainable<JQuery<HTMLElement>>) => {
cy.wrap(container)
.findByRole('region', { name: 'loading' })
.should('not.exist');
cy.wrap(container).findByRole('region', { name: 'loading' }).should('not.exist');
}
);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled, { css } from 'styled-components';

export const Container = styled.article<{ index: number; size: 's' | 'm' | 'l' }>`
export const Container = styled.div<{ index: number; size: 's' | 'm' | 'l' }>`
display: flex;
flex-direction: column;
justify-content: space-between;
Expand All @@ -16,9 +16,6 @@ export const Container = styled.article<{ index: number; size: 's' | 'm' | 'l' }
transform: scale(1.03);
transition: 0.2s;
}
h2 {
text-decoration: underline;
}
}
${({ index }) => css`
Expand All @@ -41,6 +38,7 @@ export const Container = styled.article<{ index: number; size: 's' | 'm' | 'l' }
`;
export const ImageWrapper = styled.div`
width: 100%;
aspect-ratio: 1 / 1;
overflow: hidden;
border: 1px solid ${({ theme }) => theme.colors.secondary};
background-color: #fff;
Expand All @@ -64,7 +62,12 @@ const nameFontSize = {
l: '1rem',
};

export const Name = styled.h2<{ size: 's' | 'm' | 'l' }>`
export const Name = styled.p<{ size: 's' | 'm' | 'l' }>`
line-height: 1.3;
font-weight: 500;
font-size: ${({ size }) => nameFontSize[size]};
${Container}:hover & {
text-decoration: underline;
}
`;
4 changes: 2 additions & 2 deletions frontend/src/components/Product/ProductCard/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ function ProductCard({
size = 'l',
}: Props) {
return (
<S.Container aria-label={name} index={index} size={size}>
<S.Container index={index} size={size}>
<S.ImageWrapper>
<LazyImage src={imageUrl} />
<LazyImage src={imageUrl} alt={''} />
</S.ImageWrapper>
<S.Name size={size}>{name}</S.Name>
<S.BottomWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from 'styled-components';

export const Container = styled.div`
export const Container = styled.section`
display: flex;
flex-direction: column;
padding: 1rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ function ProductDetail({ product }: Props) {
const { imageUrl, name, rating, reviewCount } = product;

return (
<S.Container>
<S.Image src={imageUrl} aria-label="제품 이미지" />
<S.Container aria-label="제품 상세 정보">
<S.Image src={imageUrl} alt={''} />
<S.Wrapper>
<S.Name>{name}</S.Name>
<S.Details>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const FlexWrapper = styled.div`
}
@media screen and ${device.desktop} {
min-height: 28rem;
justify-content: space-between;
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
Expand All @@ -36,12 +37,24 @@ export const FlexWrapper = styled.div`
`}
`;

export const Grid = styled.ul<{ columnCount: number }>`
display: grid;
grid-template-columns: repeat(${({ columnCount }) => columnCount}, 1fr);
`;

export const Title = styled.h1`
font-size: 1.5rem;
`;

export const CustomLink = styled(Link)``;

export const ProductLink = styled(Link)`
display: flex;
justify-content: center;
width: max-content;
margin: 0 auto;
`;

export const Wrapper = styled.div`
margin: 1rem auto 0 auto;
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { Link } from 'react-router-dom';

import InfiniteScroll from '@/components/common/InfiniteScroll/InfiniteScroll';
import Masonry from '@/components/common/Masonry/Masonry';
import NoDataPlaceholder from '@/components/common/NoDataPlaceholder/NoDataPlaceholder';

import ProductCard from '@/components/Product/ProductCard/ProductCard';
Expand Down Expand Up @@ -42,16 +39,18 @@ function ProductListSection({
displayType === 'flex' ? DEVICE_TO_SIZE[device] : device === 'tablet' ? 'm' : 'l';

const productList = data.map(({ id, imageUrl, name, rating, reviewCount }, index) => (
<Link to={`${ROUTES.PRODUCT}/${id}`} key={id}>
<ProductCard
imageUrl={imageUrl}
name={name}
rating={rating}
reviewCount={reviewCount}
index={index % pageSize}
size={cardSize}
/>
</Link>
<li key={id}>
<S.ProductLink to={`${ROUTES.PRODUCT}/${id}`}>
<ProductCard
imageUrl={imageUrl}
name={name}
rating={rating}
reviewCount={reviewCount}
index={index % pageSize}
size={cardSize}
/>
</S.ProductLink>
</li>
));

return (
Expand All @@ -69,11 +68,11 @@ function ProductListSection({
isLoading={isLoading}
isError={isError}
>
<Masonry
<S.Grid
columnCount={displayType === 'masonry' ? ROW_COUNT(displayWidth) : pageSize}
>
{productList}
</Masonry>
</S.Grid>
</InfiniteScroll>
)}
</S.Wrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Props = Partial<Pick<Review, 'id' | 'rating' | 'content'>> & {
handleEdit?: (reviewInput: ReviewInput, id: number) => Promise<void>;
isEdit: boolean;
handleUnmount?: () => void;
handleFocus?: () => void;
animationTrigger?: boolean;
};

Expand All @@ -21,6 +22,7 @@ function ReviewBottomSheet({
rating,
content,
handleUnmount,
handleFocus,
animationTrigger,
}: Props) {
const handleCloseWithSubmit = async (reviewInput: ReviewInput) => {
Expand All @@ -31,23 +33,25 @@ function ReviewBottomSheet({
await handleSubmit(reviewInput);
}
handleClose();
handleFocus();
} catch (error) {
console.log(error);
}
};

return (
<BottomSheet
handleClose={handleClose}
handleUnmount={handleUnmount}
animationTrigger={animationTrigger}
>
<S.Button onClick={handleClose}>닫기</S.Button>
<ReviewForm
handleSubmit={handleCloseWithSubmit}
isEdit={isEdit ? true : false}
rating={rating}
content={content}
/>
<S.Button onClick={handleClose}>닫기</S.Button>
</BottomSheet>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Link } from 'react-router-dom';
import styled, { css } from 'styled-components';

export const Container = styled.article<{ index: number }>`
export const Container = styled.div<{ index: number }>`
display: flex;
gap: 1rem;
border-radius: 0.375rem;
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/Review/ReviewCard/ReviewCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Props = {
reviewData: Omit<Review, 'id'>;
handleDelete?: (id: number) => void;
handleEdit?: (reviewInput: ReviewInput, id: number) => Promise<void>;
handleFocus?: () => void;
index?: number;
userNameVisible?: boolean;
};
Expand All @@ -25,6 +26,7 @@ function ReviewCard({
reviewId,
handleDelete,
handleEdit,
handleFocus,
reviewData,
index = 0,
userNameVisible = true,
Expand Down Expand Up @@ -52,6 +54,7 @@ function ReviewCard({

const handleDeleteClick = () => {
handleDelete(reviewId);
handleFocus();
};

return (
Expand Down
Loading

0 comments on commit 7fe05eb

Please sign in to comment.