Skip to content

Commit

Permalink
[FE] feat: 정렬 버튼 분리 (#141)
Browse files Browse the repository at this point in the history
* chore: 펀잇 디자인 시스템 버전 업데이트

* feat: 리뷰 정렬 옵션 상수 추가 및 정렬 옵션 타입 추가

* refactor: SortButton에서 BottomSheet 제거

* refactor: SortOptionList에서 options prop 추가

* refactor: BottomSheet 페이지로 이동

* style: SortOption -> SortOptionButton으로 수정

* feat: SortButton 스토리에 args 추가

* refactor: useSortOption 커스텀 훅 분리
  • Loading branch information
Leejin-Yang authored Jul 26, 2023
1 parent f34d1f7 commit d7c95de
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 84 deletions.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"build-storybook": "storybook build"
},
"dependencies": {
"@fun-eat/design-system": "^0.3.1",
"@fun-eat/design-system": "^0.3.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import SortButton from './SortButton';
const meta: Meta<typeof SortButton> = {
title: 'common/SortButton',
component: SortButton,
args: {
option: '높은 가격순',
},
};

export default meta;
Expand Down
47 changes: 16 additions & 31 deletions frontend/src/components/Common/SortButton/SortButton.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,31 @@
import { BottomSheet, Button, Text, theme } from '@fun-eat/design-system';
import { useState } from 'react';
import { Button, Text, useTheme } from '@fun-eat/design-system';
import styled from 'styled-components';

import BottomSheetContent from '../SortOptionList/SortOptionList';
import SvgIcon from '../Svg/SvgIcon';

import { SORT_OPTIONS } from '@/constants';
import useBottomSheet from '@/hooks/useBottomSheet';
interface SortButtonProps {
option: string;
onClick: () => void;
}

const SortButton = () => {
const { ref, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const [selectedOption, setSelectedOption] = useState(0);

const selectSortOption = (optionIndex: number) => {
setSelectedOption(optionIndex);
};
const SortButton = ({ option, onClick }: SortButtonProps) => {
const theme = useTheme();

return (
<>
<Button color="white" variant="filled" onClick={handleOpenBottomSheet}>
<SortButtonWrapper>
<SvgIcon variant="sort" color={theme.textColors.info} width={18} height={18} />
<Text element="span" weight="bold" color={theme.textColors.info}>
{SORT_OPTIONS[selectedOption].label}
</Text>
</SortButtonWrapper>
</Button>
<BottomSheet ref={ref} close={handleCloseBottomSheet}>
<BottomSheetContent
selectedOption={selectedOption}
selectSortOption={selectSortOption}
close={handleCloseBottomSheet}
/>
</BottomSheet>
</>
<SortButtonContainer color="white" variant="filled" onClick={onClick}>
<SvgIcon variant="sort" color={theme.textColors.info} width={18} height={18} />
<Text as="span" weight="bold" color={theme.textColors.info}>
{option}
</Text>
</SortButtonContainer>
);
};

export default SortButton;

const SortButtonWrapper = styled.div`
const SortButtonContainer = styled(Button)`
display: flex;
align-items: center;
gap: 3px;
column-gap: 4px;
padding: 0;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useEffect, useRef, useState } from 'react';

import SortOptionList from './SortOptionList';

import { PRODUCT_SORT_OPTIONS } from '@/constants';

const meta: Meta<typeof SortOptionList> = {
title: 'common/SortOptionList',
component: SortOptionList,
Expand All @@ -15,7 +17,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => {
const ref = useRef<HTMLDialogElement>(null);
const [selectedOption, setSelectedOption] = useState(0);
const [selectedOption, setSelectedOption] = useState<string>(PRODUCT_SORT_OPTIONS[0].label);

useEffect(() => {
ref.current?.showModal();
Expand All @@ -25,13 +27,18 @@ export const Default: Story = {
ref.current?.close();
};

const selectSortOption = (optionIndex: number) => {
setSelectedOption(optionIndex);
const selectSortOption = (selectedOptionLabel: string) => {
setSelectedOption(selectedOptionLabel);
};

return (
<BottomSheet ref={ref} close={closeBottomSheet}>
<SortOptionList selectedOption={selectedOption} selectSortOption={selectSortOption} close={closeBottomSheet} />
<SortOptionList
options={PRODUCT_SORT_OPTIONS}
selectedOption={selectedOption}
selectSortOption={selectSortOption}
close={closeBottomSheet}
/>
</BottomSheet>
);
},
Expand Down
55 changes: 30 additions & 25 deletions frontend/src/components/Common/SortOptionList/SortOptionList.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,40 @@
import { Button, theme } from '@fun-eat/design-system';
import styled from 'styled-components';

import { SORT_OPTIONS } from '@/constants';
import type { SortOption } from '@/types/common';

interface SortOptionListProps {
selectedOption: number;
selectSortOption: (optionIndex: number) => void;
options: readonly SortOption[];
selectedOption: string;
selectSortOption: (selectedOptionLabel: string) => void;
close: () => void;
}

const SortOptionList = ({ selectedOption, selectSortOption, close }: SortOptionListProps) => {
const handleSelectedOption = (optionIndex: number) => {
selectSortOption(optionIndex);
const SortOptionList = ({ options, selectedOption, selectSortOption, close }: SortOptionListProps) => {
const handleSelectedOption = (selectedOptionLabel: string) => {
selectSortOption(selectedOptionLabel);
close();
};

return (
<SortOptionListContainer>
{SORT_OPTIONS.map((option, index) => {
const isSelected = index === selectedOption;
const isLastItem = index < SORT_OPTIONS.length - 1;
{options.map(({ label }) => {
const isSelected = label === selectedOption;
return (
<SortOptionItem
key={option.label}
css={`
border-bottom: ${isLastItem ? `1px solid ${theme.dividerColors.disabled}` : 'none'};
`}
>
<SortOption
<li key={label}>
<SortOptionButton
color="white"
textColor={isSelected ? 'default' : 'sub'}
variant="filled"
size="lg"
css={`
font-weight: ${isSelected ? theme.fontWeights.bold : 'inherit'};
`}
onClick={() => handleSelectedOption(index)}
onClick={() => handleSelectedOption(label)}
>
{option.label}
</SortOption>
</SortOptionItem>
{label}
</SortOptionButton>
</li>
);
})}
</SortOptionListContainer>
Expand All @@ -50,15 +45,25 @@ export default SortOptionList;

const SortOptionListContainer = styled.ul`
padding: 20px;
`;
const SortOptionItem = styled.li``;
& > li {
height: 60px;
border-bottom: 1px solid ${({ theme }) => theme.dividerColors.disabled};
line-height: 60px;
}
& > li:last-of-type {
border: none;
}
`;

const SortOption = styled(Button)`
margin: 20px 0 10px 0;
padding: 0;
const SortOptionButton = styled(Button)`
width: 100%;
height: 100%;
padding: 10px 0;
border: none;
outline: transparent;
text-align: left;
&:hover {
font-weight: ${({ theme }) => theme.fontWeights.bold};
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ export const NAVIGATION_MENU: NavigationMenu[] = [
},
];

export const SORT_OPTIONS = [
export const PRODUCT_SORT_OPTIONS = [
{ label: '높은 가격순', value: 'price,desc' },
{ label: '낮은 가격순', value: 'price,asc' },
] as const;

export const REVIEW_SORT_OPTIONS = [
{ label: '높은 평점순', value: 'rating,desc' },
{ label: '낮은 평점순', value: 'rating,asc' },
{ label: '추천순', value: 'favoriteCount,asc' },
] as const;
13 changes: 13 additions & 0 deletions frontend/src/hooks/useSortOption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useState } from 'react';

const useSortOption = (initialOption: string) => {
const [selectedOption, setSelectedOption] = useState(initialOption);

const selectSortOption = (selectedOptionLabel: string) => {
setSelectedOption(selectedOptionLabel);
};

return { selectedOption, selectSortOption };
};

export default useSortOption;
22 changes: 17 additions & 5 deletions frontend/src/pages/ProductDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { Spacing } from '@fun-eat/design-system';
import { BottomSheet, Spacing } from '@fun-eat/design-system';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';

import { SortButton, TabMenu } from '@/components/Common';
import { SortButton, SortOptionList, TabMenu } from '@/components/Common';
import { ProductDetailItem, ProductTitle } from '@/components/Product';
import { ReviewItem } from '@/components/Review';
import { REVIEW_SORT_OPTIONS } from '@/constants';
import useBottomSheet from '@/hooks/useBottomSheet';
import useSortOption from '@/hooks/useSortOption';
import productDetails from '@/mocks/data/productDetails.json';
import mockReviews from '@/mocks/data/reviews.json';

const ProductDetailPage = () => {
const { productId } = useParams();
const { ref, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const { selectedOption, selectSortOption } = useSortOption(REVIEW_SORT_OPTIONS[0].label);

// TODO: productId param으로 api 요청 보내면 바뀔 로직
const targetProductDetail =
Expand All @@ -24,11 +29,9 @@ const ProductDetailPage = () => {
<ProductDetailItem product={targetProductDetail} />
<Spacing size={36} />
<TabMenu tabMenus={[`리뷰 ${reviews.length}`, '꿀조합']} />
<Spacing size={8} />
<SortButtonWrapper>
<SortButton />
<SortButton option={selectedOption} onClick={handleOpenBottomSheet} />
</SortButtonWrapper>
<Spacing size={8} />
<section>
{reviews && (
<ReviewItemWrapper>
Expand All @@ -40,6 +43,14 @@ const ProductDetailPage = () => {
</ReviewItemWrapper>
)}
</section>
<BottomSheet ref={ref} maxWidth="600px" close={handleCloseBottomSheet}>
<SortOptionList
options={REVIEW_SORT_OPTIONS}
selectedOption={selectedOption}
selectSortOption={selectSortOption}
close={handleCloseBottomSheet}
/>
</BottomSheet>
</>
);
};
Expand All @@ -50,6 +61,7 @@ const SortButtonWrapper = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
margin: 20px 0;
`;

const ReviewItemWrapper = styled.ul`
Expand Down
41 changes: 28 additions & 13 deletions frontend/src/pages/ProductListPage.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import { Spacing } from '@fun-eat/design-system';
import { BottomSheet, Spacing } from '@fun-eat/design-system';
import styled from 'styled-components';

import { CategoryMenu } from '@/components/Common';
import SortButton from '@/components/Common/SortButton/SortButton';
import Title from '@/components/Common/Title/Title';
import { CategoryMenu, SortButton, SortOptionList, Title } from '@/components/Common';
import { ProductList } from '@/components/Product';
import { PRODUCT_SORT_OPTIONS } from '@/constants';
import useBottomSheet from '@/hooks/useBottomSheet';
import useSortOption from '@/hooks/useSortOption';
import foodCategory from '@/mocks/data/foodCategory.json';

const ProductListPage = () => {
const { ref, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const { selectedOption, selectSortOption } = useSortOption(PRODUCT_SORT_OPTIONS[0].label);

return (
<section>
<Title headingTitle="공통 상품" />
<Spacing size={30} />
<CategoryMenu menuList={foodCategory} menuVariant="food" />
<SortButtonWrapper>
<SortButton />
</SortButtonWrapper>
<ProductList />
</section>
<>
<section>
<Title headingTitle="공통 상품" />
<Spacing size={30} />
<CategoryMenu menuList={foodCategory} menuVariant="food" />
<SortButtonWrapper>
<SortButton option={selectedOption} onClick={handleOpenBottomSheet} />
</SortButtonWrapper>
<ProductList />
</section>
<BottomSheet ref={ref} maxWidth="600px" close={handleCloseBottomSheet}>
<SortOptionList
options={PRODUCT_SORT_OPTIONS}
selectedOption={selectedOption}
selectSortOption={selectSortOption}
close={handleCloseBottomSheet}
/>
</BottomSheet>
</>
);
};

Expand All @@ -26,4 +40,5 @@ export default ProductListPage;
const SortButtonWrapper = styled.div`
display: flex;
justify-content: flex-end;
margin: 20px 0;
`;
3 changes: 3 additions & 0 deletions frontend/src/types/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { SvgIconVariant } from '@/components/Common/Svg/SvgIcon';
import type { PRODUCT_SORT_OPTIONS, REVIEW_SORT_OPTIONS } from '@/constants';
import type { PATH } from '@/constants/path';

export interface Category {
Expand All @@ -20,3 +21,5 @@ export interface NavigationMenu {
export type ProductSortOption = 'price' | 'averageRating' | 'reviewCount';

export type ReviewSortOption = 'favoriteCount' | 'rating';

export type SortOption = (typeof PRODUCT_SORT_OPTIONS)[number] | (typeof REVIEW_SORT_OPTIONS)[number];
8 changes: 4 additions & 4 deletions frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1565,10 +1565,10 @@
resolved "https://registry.yarnpkg.com/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz#c05ed35ad82df8e6ac616c68b92c2282bd083ba4"
integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==

"@fun-eat/design-system@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@fun-eat/design-system/-/design-system-0.3.1.tgz#3cc7887e2fa8e15301af3b6a95e6889921702a3f"
integrity sha512-oMSNE9E2PsJklqtgdXFB3elthWCohqDNdhdtIR0cNIoAZU7Jf85a79ktav+enaqyDbU87MupvLkwEs74QNVtqQ==
"@fun-eat/design-system@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@fun-eat/design-system/-/design-system-0.3.2.tgz#3b49b3d9868f8283df09a2605d1c6fa047943329"
integrity sha512-19pGO+VqblqQGivbayRtN2EpzdLBQ5PGWI/kRz9Mg8n/661MTrciiKyTjJ4if/QtBWufTXNFshsfz2SCMUGPFA==

"@humanwhocodes/config-array@^0.11.10":
version "0.11.10"
Expand Down

0 comments on commit d7c95de

Please sign in to comment.