Skip to content

Commit

Permalink
refactor: 범용적인 DropDown 컴포넌트를 celuveat-ui-library 배포 후 적용 (#652)
Browse files Browse the repository at this point in the history
* chore: celuveat-ui-library 설치

* refactor: celuveat-library를 적용,  InfoDropDown 코드 수정

* fix: svg 경로로 인한 storybook 에러 해결

* refactor: celuveat-library를 적용,  CelebDropDown 코드 수정

* fix: github action ci 오류 수정

* fix: 사용하지 않은 memo 제거 (#650)
  • Loading branch information
turtle601 authored Nov 20, 2023
1 parent 0ea233e commit a25c4f1
Show file tree
Hide file tree
Showing 15 changed files with 3,716 additions and 3,991 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ jobs:
${{ runner.os }}-yarn-
- name: 🍔 yarn install
run: pwd && ls && yarn install
run: rm -rf ./node_modules yarn.lock && yarn cache clean && yarn install
working-directory: ${{ env.working-directory }}

- name: 🍔 eslint 테스트
run: pwd && ls && yarn lint
run: yarn lint
working-directory: ${{ env.working-directory }}

- name: 🍔 React 프로젝트 빌드
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@tanstack/react-query-devtools": "^4.36.1",
"axios": "^1.5.1",
"browser-image-compression": "^2.0.2",
"celuveat-ui-library": "^1.2.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0",
Expand Down
52 changes: 17 additions & 35 deletions frontend/src/components/@common/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,34 @@ import { Link, useLocation, useNavigate } from 'react-router-dom';
import { Wrapper } from '@googlemaps/react-wrapper';

import Logo from '~/assets/icons/logo.svg';

import InfoDropDown from '~/components/InfoDropDown';
import LoginModal from '~/components/LoginModal';
import SearchBar from '~/components/SearchBar';

import useBooleanState from '~/hooks/useBooleanState';

import TextButton from '../Button';
import TextButton from '~/components/@common/Button';
import InfoDropDown from '~/components/InfoDropDown/InfoDropDown';

function Header() {
const navigator = useNavigate();
const { pathname } = useLocation();
const { value: isModalOpen, setTrue: openModal, setFalse: closeModal } = useBooleanState(false);

const handleInfoDropDown = (event: React.MouseEvent<HTMLElement>) => {
const currentOption = event.currentTarget.dataset.name;

if (currentOption === '로그인') openModal();
if (currentOption === '마이 페이지') navigator('/user');
if (currentOption === '위시리스트') navigator('/restaurants/like');
if (currentOption === '회원 탈퇴') navigator('/withdrawal');
};

const isMapPage = pathname === '/map';

return (
<>
<StyledHeader>
<Link aria-label="셀럽잇 홈페이지" role="button" to="/">
<Logo width={136} />
</Link>
{isMapPage && (
<Wrapper apiKey={process.env.GOOGLE_MAP_API_KEY} language="ko" libraries={['places']}>
<SearchBar />
</Wrapper>
<StyledHeader>
<Link aria-label="셀럽잇 홈페이지" role="button" to="/">
<Logo width={136} />
</Link>
{isMapPage && (
<Wrapper apiKey={process.env.GOOGLE_MAP_API_KEY} language="ko" libraries={['places']}>
<SearchBar />
</Wrapper>
)}
<StyledRightSection>
{!isMapPage && (
<TextButton text="지도로 보기" colorType="dark" type="button" onClick={() => navigator('/map')} />
)}
<StyledRightSection>
{!isMapPage && (
<TextButton text="지도로 보기" colorType="dark" type="button" onClick={() => navigator('/map')} />
)}
<InfoDropDown externalOnClick={handleInfoDropDown} isOpen={isModalOpen} label="로그인" />
</StyledRightSection>
</StyledHeader>

<LoginModal close={closeModal} isOpen={isModalOpen} />
</>
<InfoDropDown />
</StyledRightSection>
</StyledHeader>
);
}

Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/@common/Map/Marker/Marker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import styled, { css, keyframes } from 'styled-components';
import { memo } from 'react';
import Love from '~/assets/icons/love.svg';
import ProfileImage from '../../ProfileImage';
import { Restaurant } from '~/@types/restaurant.types';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react';
import All from '~/assets/icons/restaurantCategory/all.svg';
import All from '~/assets/icons/category/all.svg';
import NavItem from './NavItem';

const meta: Meta<typeof NavItem> = {
Expand Down
42 changes: 1 addition & 41 deletions frontend/src/components/CelebDropDown/CelebDropDown.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,4 @@ export default meta;

type Story = StoryObj<typeof CelebDropDown>;

export const Default: Story = {
args: {
celebs: [
{
id: 1,
name: '히밥',
youtubeChannelName: '@heebab',
profileImageUrl:
'https://yt3.googleusercontent.com/sL5ugPfl9vvwRwhf6l5APY__BZBw8qWiwgHs-uVsMPFoD5-a4opTJIcRSyrY8aY5LEESOMWJ=s176-c-k-c0x00ffffff-no-rj',
},
{
id: 2,
name: '정찬성',
youtubeChannelName: '@Korean_zzombi',
profileImageUrl:
'https://yt3.googleusercontent.com/sL5ugPfl9vvwRwhf6l5APY__BZBw8qWiwgHs-uVsMPFoD5-a4opTJIcRSyrY8aY5LEESOMWJ=s176-c-k-c0x00ffffff-no-rj',
},
{
id: 3,
name: '정찬',
youtubeChannelName: '@Korean_zzombi',
profileImageUrl:
'https://yt3.googleusercontent.com/sL5ugPfl9vvwRwhf6l5APY__BZBw8qWiwgHs-uVsMPFoD5-a4opTJIcRSyrY8aY5LEESOMWJ=s176-c-k-c0x00ffffff-no-rj',
},
{
id: 4,
name: '정성',
youtubeChannelName: '@Korean_zzombi',
profileImageUrl:
'https://yt3.googleusercontent.com/sL5ugPfl9vvwRwhf6l5APY__BZBw8qWiwgHs-uVsMPFoD5-a4opTJIcRSyrY8aY5LEESOMWJ=s176-c-k-c0x00ffffff-no-rj',
},
{
id: 5,
name: '정찬성1',
youtubeChannelName: '@Korean_zzombi',
profileImageUrl:
'https://yt3.googleusercontent.com/sL5ugPfl9vvwRwhf6l5APY__BZBw8qWiwgHs-uVsMPFoD5-a4opTJIcRSyrY8aY5LEESOMWJ=s176-c-k-c0x00ffffff-no-rj',
},
],
},
};
export const Default: Story = {};
96 changes: 31 additions & 65 deletions frontend/src/components/CelebDropDown/CelebDropDown.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,49 @@
import { styled, css } from 'styled-components';
import { MouseEvent, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { DropDown } from 'celuveat-ui-library';

import CelebIcon from '~/assets/icons/celeb.svg';
import ProfileImage from '~/components/@common/ProfileImage';
import NavItem from '~/components/@common/NavItem/NavItem';
import useBooleanState from '~/hooks/useBooleanState';

import type { Celeb } from '~/@types/celeb.types';
import { getCelebs } from '~/api/celeb';
import { BORDER_RADIUS, FONT_SIZE } from '~/styles/common';
import { OPTION_FOR_CELEB_ALL } from '~/constants/options';
import useMediaQuery from '~/hooks/useMediaQuery';

interface DropDownProps {
celebs: Celeb[];
isOpen?: boolean;
externalOnClick?: (e?: MouseEvent<HTMLLIElement>) => void;
}
import CelebNavItem from '~/components/CelebDropDown/CelebNavItem';
import CelebDropDownOption from '~/components/CelebDropDown/CelebDropDownOption';

function CelebDropDown({ celebs, externalOnClick, isOpen = false }: DropDownProps) {
import useMediaQuery from '~/hooks/useMediaQuery';
import { useCelebSelect } from '~/components/CelebDropDown/hooks/useCelebSelect';

function CelebDropDown() {
const { isMobile } = useMediaQuery();
const [selected, setSelected] = useState<Celeb>(OPTION_FOR_CELEB_ALL);
const { value: isShow, toggle: onToggleDropDown, setFalse: onCloseDropDown } = useBooleanState(isOpen);

const onSelection = (celebName: Celeb['name']) => (event?: MouseEvent<HTMLLIElement>) => {
setSelected(celebs.find(celeb => celebName === celeb.name));
const { selectedCeleb, selectCeleb } = useCelebSelect();

if (externalOnClick) externalOnClick(event);
};
const { data: celebOptions } = useQuery({
queryKey: ['celebOptions'],
queryFn: getCelebs,
suspense: true,
});

return (
<StyledCelebDropDown aria-hidden>
<StyledNavItemWrapper type="button" onClick={onToggleDropDown} onBlur={onCloseDropDown}>
{selected.id === -1 ? (
<NavItem label="전체 셀럽" icon={<CelebIcon />} isShow={isShow} />
) : (
<NavItem
label={selected.youtubeChannelName.replace('@', '')}
icon={<ProfileImage name={selected.name} imageUrl={selected.profileImageUrl} size="40px" />}
isShow={isShow}
/>
)}
</StyledNavItemWrapper>

{isShow && (
<DropDown>
<DropDown.Trigger isCustom>
<StyledNavItemWrapper>
<CelebNavItem celeb={selectedCeleb} />
</StyledNavItemWrapper>
</DropDown.Trigger>
<DropDown.Options as="ul" isCustom>
<StyledDropDownWrapper isMobile={isMobile}>
<StyledSelectContainer>
{celebs.map(({ id, name, youtubeChannelName, profileImageUrl }) => (
<StyledDropDownOption data-id={id} onMouseDown={onSelection(name)}>
<div>
{id === -1 ? <CelebIcon /> : <ProfileImage name={name} imageUrl={profileImageUrl} size="32px" />}
<div>
<StyledCelebName>{name}</StyledCelebName>
<StyledChannelName>{youtubeChannelName}</StyledChannelName>
</div>
</div>
</StyledDropDownOption>
{[OPTION_FOR_CELEB_ALL, ...celebOptions].map(celeb => (
<DropDown.Option as="li" key={celeb.id} externalClick={selectCeleb(celeb)} isCustom>
<StyledDropDownOption>
<CelebDropDownOption celeb={celeb} />
</StyledDropDownOption>
</DropDown.Option>
))}
</StyledSelectContainer>
</StyledDropDownWrapper>
)}
</StyledCelebDropDown>
</DropDown.Options>
</DropDown>
);
}

Expand All @@ -73,31 +57,24 @@ const StyledNavItemWrapper = styled.button`
outline: none;
`;

const StyledCelebDropDown = styled.div`
position: relative;
`;

const StyledDropDownWrapper = styled.ul<{ isMobile: boolean }>`
display: flex;
flex-direction: column;
align-content: center;
position: absolute;
top: calc(100% + 16px);
left: 0;
z-index: 1;
${({ isMobile }) =>
isMobile
? css`
left: 0;
width: 90vw;
height: 60vh;
max-height: 440px;
`
: css`
left: 18px;
width: 380px;
height: 440px;
`}
Expand Down Expand Up @@ -150,14 +127,3 @@ const StyledDropDownOption = styled.li`
background-color: var(--gray-1);
}
`;

const StyledCelebName = styled.div`
font-family: SUIT-Medium;
`;

const StyledChannelName = styled.div`
padding-top: 0.4rem;
color: var(--gray-3);
font-size: ${FONT_SIZE.sm};
`;
39 changes: 39 additions & 0 deletions frontend/src/components/CelebDropDown/CelebDropDownOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { styled } from 'styled-components';
import { Celeb } from '~/@types/celeb.types';
import ProfileImage from '~/components/@common/ProfileImage';
import { FONT_SIZE } from '~/styles/common';

import CelebIcon from '~/assets/icons/celeb.svg';

interface CelebDropDownOptionProps {
celeb: Celeb;
}

function CelebDropDownOption({ celeb }: CelebDropDownOptionProps) {
return (
<div>
{celeb.id === -1 ? (
<CelebIcon />
) : (
<ProfileImage name={celeb.name} imageUrl={celeb.profileImageUrl} size="32px" />
)}
<div>
<StyledCelebName>{celeb.name}</StyledCelebName>
<StyledChannelName>{celeb.youtubeChannelName}</StyledChannelName>
</div>
</div>
);
}

export default CelebDropDownOption;

const StyledCelebName = styled.div`
font-family: SUIT-Medium, sans-serif;
`;

const StyledChannelName = styled.div`
padding-top: 0.4rem;
color: var(--gray-3);
font-size: ${FONT_SIZE.sm};
`;
26 changes: 26 additions & 0 deletions frontend/src/components/CelebDropDown/CelebNavItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import NavItem from '~/components/@common/NavItem';
import ProfileImage from '~/components/@common/ProfileImage';
import CelebIcon from '~/assets/icons/celeb.svg';

import { Celeb } from '~/@types/celeb.types';

interface CelebNavItemProps {
celeb: Celeb;
}

function CelebNavItem({ celeb }: CelebNavItemProps) {
return (
<div>
{celeb.id === -1 ? (
<NavItem label="전체 셀럽" icon={<CelebIcon />} />
) : (
<NavItem
label={celeb.youtubeChannelName.replace('@', '')}
icon={<ProfileImage name={celeb.name} imageUrl={celeb.profileImageUrl} size="40px" />}
/>
)}
</div>
);
}

export default CelebNavItem;
27 changes: 27 additions & 0 deletions frontend/src/components/CelebDropDown/hooks/useCelebSelect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useState } from 'react';
import { shallow } from 'zustand/shallow';
import { OPTION_FOR_CELEB_ALL } from '~/constants/options';
import useRestaurantsQueryStringState from '~/hooks/store/useRestaurantsQueryStringState';

import { Celeb } from '~/@types/celeb.types';

export const useCelebSelect = () => {
const [selectedCeleb, setSelectedCeleb] = useState<Celeb>(OPTION_FOR_CELEB_ALL);

const [setCelebId, setCurrentPage] = useRestaurantsQueryStringState(
state => [state.setCelebId, state.setCurrentPage],
shallow,
);

const selectCeleb = (celeb: Celeb) => () => {
setCelebId(celeb.id);
setCurrentPage(0);

setSelectedCeleb(celeb);
};

return {
selectedCeleb,
selectCeleb,
};
};
3 changes: 3 additions & 0 deletions frontend/src/components/InfoDropDown/InfoDropDown.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import InfoDropDown from './InfoDropDown';
const meta: Meta<typeof InfoDropDown> = {
title: 'Selector/InfoDropDown',
component: InfoDropDown,
parameters: {
layout: 'centered',
},
};

export default meta;
Expand Down
Loading

0 comments on commit a25c4f1

Please sign in to comment.