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

feat: 레스토랑 카드 및 마커 클릭 이벤트 변경 #198

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
462bbb7
feat: 마커 호버시 마커를 맨 앞으로 가져오기 (#192)
shackstack Jul 27, 2023
161d6fe
style: 마커 호버시 마커 강조 (#192)
shackstack Jul 27, 2023
0838f20
feat: 마커 클릭시 레스토랑 카드 띄우기 (#192)
shackstack Jul 27, 2023
d09af48
refactor: 불필요한 코드 제거 (#192)
shackstack Jul 27, 2023
11cf5f8
feat: 마커클릭 시 마커 위치에 따라 카드모달 위치 조정 (#192)
shackstack Jul 27, 2023
8748b91
style: 레스토랑 오버레이 스타일 수정 (#192)
shackstack Jul 28, 2023
11ba021
refactor: restaurantCard 컴포넌트를 용도에 따라 스타일 다르게 설정 (#192)
shackstack Jul 28, 2023
43ca047
feat: 마커 클릭시 강조 효과 주기 (#192)
shackstack Jul 28, 2023
fdeb97a
Squashed commit of the following:
shackstack Jul 28, 2023
f731c1d
feat: 다른 마커 클릭시 기존 마커 모달 닫기 기능 구현 (#192)
shackstack Jul 28, 2023
b4ad157
Merge branch 'develop-frontend' into 192-feat-레스토랑-카드-및-마커-클릭-이벤트-변경
shackstack Jul 28, 2023
d9bd80d
style: 파일명 오류 수정 (#192)
shackstack Jul 29, 2023
dbb297f
refactor: baseURL 환경변수 설정 및 type import 분리
shackstack Jul 29, 2023
a6ae8ef
feat: RestaurantCard 컴포넌트 props 수정 (#192)
shackstack Jul 29, 2023
5b69f96
refactor: getQuadrant 리팩터링 (#192)
shackstack Jul 29, 2023
3df8276
style: 상태 네이밍 수정 (#192)
shackstack Jul 30, 2023
c3a5c15
Merge branch 'shackstack' into 192-feat-레스토랑-카드-및-마커-클릭-이벤트-변경
shackstack Jul 30, 2023
e8e9e31
feat: 음식점 카드 호버시 해당 음식점 마커 강조 (#192)
shackstack Jul 30, 2023
ab9e38d
design: 강조시 애니메이션 효과 추가 및 음식점 리스트 스타일 수정 (#192)
shackstack Jul 31, 2023
9141668
refactor: 음식점 카드 호버시 마커 강조 로직 변경 (#192)
shackstack Jul 31, 2023
34b12a6
refactor: 프로필 이미지 컴포넌트 Props 타입 수정 (#192)
shackstack Jul 31, 2023
750fe7f
fix: setHoverId가 없을 때 default value 설정 (#192)
shackstack Jul 31, 2023
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
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import MainPage from './pages/MainPage';

export const { BASE_URL } = process.env;

function App() {
return <MainPage />;
}
Expand Down
37 changes: 23 additions & 14 deletions frontend/src/components/@common/Map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
import { Wrapper, Status } from '@googlemaps/react-wrapper';
import { styled } from 'styled-components';
import OverlayMarker from './OverlayMarker';
import type { Coordinate, CoordinateBoundary } from '~/@types/map.types';
import type { Celeb } from '~/@types/celeb.types';
import MapContent from './MapContent';
import OverlayMyLocation from './OverlayMyLocation';
import LoadingDots from '../LoadingDots';
Expand All @@ -13,10 +11,14 @@
import RightBracket from '~/assets/icons/right-bracket.svg';
import Minus from '~/assets/icons/minus.svg';
import Plus from '~/assets/icons/plus.svg';
import getQuadrant from '~/utils/getQuadrant';

import type { Coordinate, CoordinateBoundary } from '~/@types/map.types';
import type { RestaurantData } from '~/@types/api.types';

interface MapProps {
clickMarker: ({ lat, lng }: Coordinate) => void;
markers: { position: Coordinate; celebs: Celeb[] }[];
data: RestaurantData[];
hoveredId: number | null;
setBoundary: React.Dispatch<React.SetStateAction<CoordinateBoundary>>;
toggleMapExpand: () => void;
}
Expand All @@ -27,20 +29,24 @@
return <LoadingDots />;
};

function Map({ clickMarker, markers, setBoundary, toggleMapExpand }: MapProps) {
const [center, setCenter] = useState<Coordinate>({ lat: 37.5057482, lng: 127.050727 });
const JamsilCampus = { lat: 37.515271, lng: 127.1029949 };

function Map({ data, setBoundary, toggleMapExpand, hoveredId }: MapProps) {
const [center, setCenter] = useState<Coordinate>(JamsilCampus);
const [clicks, setClicks] = useState<google.maps.LatLng[]>([]);
const [zoom, setZoom] = useState(16);
const [myPosition, setMyPosition] = useState<Coordinate | null>(null);
const [isMapExpanded, setIsMapExpanded] = useState(false);
const [loading, setLoading] = useState(false);
const [currentCenter, setCurrentCenter] = useState<Coordinate>(JamsilCampus);

const onClick = (e: google.maps.MapMouseEvent) => {
setClicks([...clicks, e.latLng!]);

Check warning on line 44 in frontend/src/components/@common/Map/Map.tsx

View workflow job for this annotation

GitHub Actions / 🍔테스트 딱 대라 💢👊

Forbidden non-null assertion
};

const onIdle = (m: google.maps.Map) => {
setZoom(m.getZoom()!);

Check warning on line 48 in frontend/src/components/@common/Map/Map.tsx

View workflow job for this annotation

GitHub Actions / 🍔테스트 딱 대라 💢👊

Forbidden non-null assertion
setCurrentCenter({ lat: m.getCenter().lat(), lng: m.getCenter().lng() });

const lowLatitude = String(m.getBounds().getSouthWest().lat());
const highLatitude = String(m.getBounds().getNorthEast().lat());
Expand All @@ -60,11 +66,6 @@
});
};

const clickOverlayMarker = (position: Coordinate) => {
clickMarker(position);
setCenter(position);
};

const clickZoom =
(number: number): React.MouseEventHandler<HTMLButtonElement> =>
() => {
Expand All @@ -85,9 +86,17 @@
zoom={zoom}
center={center}
>
{markers.map(({ position, celebs }) => (
<OverlayMarker position={position} onClick={clickOverlayMarker} celeb={celebs[0]} />
))}
{data.map(({ celebs, ...restaurant }) => {
const { lat, lng } = restaurant;
return (
<OverlayMarker
restaurant={restaurant}
celeb={celebs[0]}
quadrant={getQuadrant(currentCenter, { lat, lng })}
isRestaurantHovered={restaurant.id === hoveredId}
/>
);
})}
{myPosition && <OverlayMyLocation position={myPosition} />}
{loading && (
<LoadingUI>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/@common/Map/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function Overlay({ position, pane = 'floatPane', map, zIndex, children }: Overla
const container = useMemo(() => {
const div = document.createElement('div');
div.style.position = 'absolute';

return div;
}, []);

Expand Down
91 changes: 78 additions & 13 deletions frontend/src/components/@common/Map/OverlayMarker.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,102 @@
import styled from 'styled-components';
import styled, { css, keyframes } from 'styled-components';
import { useRef, useState } from 'react';
import ProfileImage from '../ProfileImage';
import Overlay from './Overlay/Overlay';
import RestaurantCard from '~/components/RestaurantCard';
import useOnClickOutside from '~/hooks/useOnClickOutside';

import type { Quadrant } from '~/utils/getQuadrant';
import type { Restaurant } from '~/@types/restaurant.types';
import type { Celeb } from '~/@types/celeb.types';
import type { Coordinate } from '~/@types/map.types';

interface OverlayMarkerProps {
celeb: Celeb;
position: Coordinate;
onClick: ({ lat, lng }: Coordinate) => void;
map?: google.maps.Map;
restaurant: Restaurant;
quadrant: Quadrant;
isRestaurantHovered: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quadrant가 사분면을 뜻하는 군요 ㅋㅋㅋㅋ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다ㅋㅋ 저도 이번에 구현하면서 처음 알게된 단어입니다ㅋㅋ

}

function OverlayMarker({ celeb, position, map, onClick }: OverlayMarkerProps) {
function OverlayMarker({ celeb, restaurant, map, quadrant, isRestaurantHovered }: OverlayMarkerProps) {
const { lat, lng } = restaurant;
const [isClicked, setIsClicked] = useState(false);
const ref = useRef();
useOnClickOutside(ref, () => setIsClicked(false));

const clickMarker = () => setIsClicked(true);

return (
map && (
<Overlay position={position} map={map}>
<StyledMarker type="button" onClick={() => onClick(position)}>
<ProfileImage name={celeb.name} imageUrl={celeb.profileImageUrl} size={32} border />
<Overlay position={{ lat, lng }} map={map} zIndex={isClicked || isRestaurantHovered ? 18 : 0}>
<StyledMarker onClick={clickMarker} isClicked={isClicked} isRestaurantHovered={isRestaurantHovered} ref={ref}>
<ProfileImage name={celeb.name} imageUrl={celeb.profileImageUrl} border size="100%" />
</StyledMarker>
{isClicked && (
<StyledModal quadrant={quadrant}>
<RestaurantCard restaurant={restaurant} type="map" />
</StyledModal>
)}
</Overlay>
)
);
}

const StyledMarker = styled.button`
border: none;
background-color: transparent;
const scaleUp = keyframes`
0% {
transform: scale(1);
}
100% {
transform: scale(1.5);
}
`;

const StyledMarker = styled.div<{ isClicked: boolean; isRestaurantHovered: boolean }>`
display: flex;
justify-content: center;
align-items: center;

width: 36px;
height: 36px;

transition: all 0.2s ease-in-out;
border: ${({ isClicked, isRestaurantHovered }) =>
isClicked || isRestaurantHovered ? '3px solid var(--orange-2)' : '3px solid transparent'};
border-radius: 50%;

transition: transform 0.2s ease-in-out;
transform: ${({ isClicked }) => (isClicked ? 'scale(1.5)' : 'scale(1)')};

&:hover {
transform: scale(1.1);
transform: scale(1.5);
}

${({ isRestaurantHovered }) =>
isRestaurantHovered &&
css`
animation: ${scaleUp} 0.2s ease-in-out forwards;
`}
`;

const fadeInAnimation = keyframes`
from {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 애니메이션은 많이 쓰일거 같네용 ~~ 공통 css로 빼도 좋을 거 같아여!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 컴포넌트에서 위와 같은 애니메이션을 쓰게되면 그때 분리해도 늦진 않을 것 같아요.

opacity: 0;
}
to {
opacity: 1;
}
`;

const StyledModal = styled.div<{ quadrant: Quadrant }>`
position: absolute;
top: ${({ quadrant }) => (quadrant === 1 || quadrant === 2 ? '40px' : '-280px')};
right: ${({ quadrant }) => (quadrant === 1 || quadrant === 4 ? '45px' : '-210px')};

width: 200px;

border-radius: 12px;
background-color: #fff;

animation: ${fadeInAnimation} 100ms ease-in;
box-shadow: 0 4px 6px rgb(0 0 0 / 20%);
`;

export default OverlayMarker;
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ export const Default: Story = {
args: {
name: '누군가',
imageUrl: 'https://avatars.githubusercontent.com/u/51052049?v=4',
size: 64,
size: '64px',
},
};
9 changes: 4 additions & 5 deletions frontend/src/components/@common/ProfileImage/ProfileImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface ProfileImageProps extends React.HTMLAttributes<HTMLImageElement> {
name: string;
imageUrl: string;
border?: boolean;
size: number;
size?: string;
}

function ProfileImage({ name = '셀럽', imageUrl, size, border = false, ...props }: ProfileImageProps) {
Expand All @@ -13,11 +13,10 @@ function ProfileImage({ name = '셀럽', imageUrl, size, border = false, ...prop

export default ProfileImage;

const StyledProfile = styled.img<{ size: number; border: boolean }>`
width: ${({ size }) => `${size}px`};
height: ${({ size }) => `${size}px`};
const StyledProfile = styled.img<{ size: string; border: boolean }>`
width: ${({ size }) => size || 'auto'};
height: ${({ size }) => size || 'auto'};

border: ${({ border }) => (border ? `2px solid var(--primary-1)` : `none`)};
border-radius: 50%;
background: none;
`;
4 changes: 2 additions & 2 deletions frontend/src/components/CategoryNavbar/CategoryNavbar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useState } from 'react';
import styled from 'styled-components';
import { RestaurantCategory } from '~/@types/restaurant.types';
import NavItem from '~/components/@common/NavButton/NavButton';

import isEqual from '~/utils/compare';

import type { RestaurantCategory } from '~/@types/restaurant.types';

interface Category {
label: RestaurantCategory;
icon: React.ReactNode;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/CelebBanner/CelebBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function CelebBanner({
return (
<StyledContainer background={backgroundImageUrl}>
<StyledCelebInfo>
<ProfileImage name={name} imageUrl={profileImageUrl} size={172} />
<ProfileImage name={name} imageUrl={profileImageUrl} size="172px" />
<StyledName>{name}</StyledName>
<StyledDetail>
{youtubeChannelName} 구독자 {subscriberCount / 10_000}만명 ∙ 음식점 {restaurantCount}개
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/CelebDropDown/CelebDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function CelebDropDown({ celebs, externalOnClick, isOpen = false }: DropDownProp
{celebs.map(({ id, name, profileImageUrl }) => (
<StyledDropDownOption data-id={id} onMouseDown={onSelection(name)}>
<div>
<ProfileImage name={name} imageUrl={profileImageUrl} size={20} />
<ProfileImage name={name} imageUrl={profileImageUrl} size="20px" />
{name}
</div>
{isEqual(selected, name) && <SearchIcon />}
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/components/MapModalContent/MapModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { styled } from 'styled-components';
import { RestaurantModalInfo } from '~/@types/restaurant.types';
import { BORDER_RADIUS, FONT_SIZE } from '~/styles/common';
import TextButton from '../@common/Button';
import { BASE_URL } from '~/App';

interface MapModalContentProps {
content: RestaurantModalInfo;
Expand All @@ -17,10 +18,7 @@ function MapModalContent({ content }: MapModalContentProps) {
<div>{roadAddress}</div>
<div>{phoneNumber}</div>
</div>
<StyledRestaurantImage
src={`http://3.35.157.27:3000/images-data/${images[0].name}`}
alt={`${name} 식당 이미지`}
/>
<StyledRestaurantImage src={`${BASE_URL}images-data/${images[0].name}`} alt={`${name} 식당 이미지`} />
</StyledRestaurantInfo>
<TextButton
type="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ export const Default: Story = {
'https://yt3.googleusercontent.com/sL5ugPfl9vvwRwhf6l5APY__BZBw8qWiwgHs-uVsMPFoD5-a4opTJIcRSyrY8aY5LEESOMWJ=s176-c-k-c0x00ffffff-no-rj',
},
],
size: 42,
size: '42px',
},
};
42 changes: 31 additions & 11 deletions frontend/src/components/RestaurantCard/RestaurantCard.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
import { styled } from 'styled-components';
import { BORDER_RADIUS, FONT_SIZE, truncateText } from '~/styles/common';
import ProfileImage from '../@common/ProfileImage';
import { Restaurant } from '~/@types/restaurant.types';
import { Celeb } from '~/@types/celeb.types';
import { BASE_URL } from '~/App';

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

interface RestaurantCardProps {
restaurant: Restaurant;
celebs: Celeb[];
size: number;
onClick: React.MouseEventHandler;
celebs?: Celeb[];
size?: string;
type?: 'list' | 'map';
onClick?: React.MouseEventHandler;
setHoveredId?: React.Dispatch<React.SetStateAction<number>>;
}

function RestaurantCard({ restaurant, celebs, size, onClick }: RestaurantCardProps) {
function RestaurantCard({
restaurant,
celebs,
size,
type = 'list',
onClick = () => {},
setHoveredId = () => {},
}: RestaurantCardProps) {
const { images, name, roadAddress, category } = restaurant;

const onMouseEnter = () => {
setHoveredId(restaurant.id);
};

const onMouseLeave = () => {
setHoveredId(null);
};

return (
<StyledContainer onClick={onClick}>
<StyledImage alt={`${name} 대표 이미지`} src={`http://3.35.157.27:3000/images-data/${images[0].name}`} />
<StyledContainer onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<StyledImage alt={`${name} 대표 이미지`} src={`${BASE_URL}images-data/${images[0].name}`} type={type} />
<section>
<StyledInfo>
<StyledCategory>{category}</StyledCategory>
Expand All @@ -25,7 +44,7 @@ function RestaurantCard({ restaurant, celebs, size, onClick }: RestaurantCardPro
<StyledAddress>02-1234-5678</StyledAddress>
</StyledInfo>
<StyledProfileImageSection>
<ProfileImage name={celebs[0].name} imageUrl={celebs[0].profileImageUrl} size={size} />
{celebs && <ProfileImage name={celebs[0].name} imageUrl={celebs[0].profileImageUrl} size={size} />}
</StyledProfileImageSection>
</section>
</StyledContainer>
Expand All @@ -51,11 +70,12 @@ const StyledContainer = styled.div`
cursor: pointer;
`;

const StyledImage = styled.img`
const StyledImage = styled.img<{ type: 'list' | 'map' }>`
width: 100%;
aspect-ratio: 1.05 / 1;

border-radius: ${BORDER_RADIUS.md};
border-radius: ${({ type }) =>
type === 'list' ? `${BORDER_RADIUS.md}` : `${BORDER_RADIUS.md} ${BORDER_RADIUS.md} 0 0`};

object-fit: cover;
`;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/VideoPreview/VideoPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function VideoPreview({
</StyledVideoCover>
)}
<StyledVideoInfo>
<ProfileImage name={celebName} imageUrl={profileImageUrl} size={38} />
<ProfileImage name={celebName} imageUrl={profileImageUrl} size="38px" />
<StyledTitle>{title}</StyledTitle>
<StyledViewAndDate>
<div>{celebName}</div>
Expand Down
Loading