Skip to content

Commit

Permalink
Merge pull request #570 from woowacourse-teams/fix/553-weird-gap-betw…
Browse files Browse the repository at this point in the history
…een-buttons

홈 화면의 아이콘 버튼 레이아웃 조정 및 리팩토링
  • Loading branch information
solo5star authored Oct 12, 2023
2 parents a444cfd + c524dbe commit 95c1b7e
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 274 deletions.
114 changes: 49 additions & 65 deletions client/src/components/CafeActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,78 @@
import { Suspense, useState, type PropsWithChildren } from 'react';
import { PiReadCvLogoFill } from 'react-icons/pi';
import { styled } from 'styled-components';
import { Suspense, useState } from 'react';
import { BiSolidInfoCircle } from 'react-icons/bi';
import { FaShare } from 'react-icons/fa';
import { PiHeartFill, PiReadCvLogoFill } from 'react-icons/pi';
import { styled, useTheme } from 'styled-components';
import { useToast } from '../context/ToastContext';
import useCafeLikes from '../hooks/useCafeLikes';
import useClipboard from '../hooks/useClipboard';
import useUser from '../hooks/useUser';
import type { Cafe } from '../types';
import CafeMenuBottomSheet from './CafeMenuBottomSheet';
import LikeButton from './LikeButton';
import ShareButton from './ShareButton';
import IconButton from './IconButton';

type CafeActionBarProps = {
cafe: Cafe;
};

const CafeActionBar = (props: CafeActionBarProps) => {
const { cafe } = props;
const theme = useTheme();
const showToast = useToast();
const clipboard = useClipboard();

const { isLiked, setLiked } = useCafeLikes(cafe);
const { data: user } = useUser();
const [isMenuOpened, setIsMenuOpened] = useState(false);

const likeCount = cafe.likeCount + (isLiked ? 1 : 0);

const handleShare = async () => {
try {
await clipboard.copyToClipboard(`https://yozm.cafe/cafes/${cafe.id}`);
showToast('success', 'URL이 복사되었습니다!');
} catch (error) {
showToast('error', `URL 복사 실패: ${error}`);
}
};

const handleLikeCountIncrease = () => {
if (!user) {
alert('로그인이 필요합니다!');
showToast('error', '로그인이 필요합니다!');
return;
}

setLiked({ isLiked: !isLiked });
};

const handlePreventClickPropagation: React.MouseEventHandler<HTMLDivElement> = (event) => {
event.stopPropagation();
const handleMenuOpen = () => {
setIsMenuOpened(true);
};

const handleMenuClose = () => {
setIsMenuOpened(false);
};

return (
<Container onClick={handlePreventClickPropagation}>
<Action>
<ShareButton url={`https://yozm.cafe/cafes/${cafe.id}`} />
<LikeButton
likeCount={cafe.likeCount + (isLiked ? 1 : 0)}
active={isLiked}
onChange={handleLikeCountIncrease}
/>
</Action>
<Action onClick={() => setIsMenuOpened(true)}>
<ActionButton label="메뉴">
<PiReadCvLogoFill />
</ActionButton>
</Action>
<Container>
<IconButton label="공유" onClick={handleShare}>
<FaShare />
</IconButton>

<IconButton label={String(likeCount)} onClick={handleLikeCountIncrease}>
<PiHeartFill fill={isLiked ? theme.color.primary : theme.color.white} />
</IconButton>

<IconButton label="메뉴" onClick={handleMenuOpen}>
<PiReadCvLogoFill />
</IconButton>

<IconButton label="더보기">
<BiSolidInfoCircle />
</IconButton>

{isMenuOpened && (
<Suspense>
<CafeMenuBottomSheet cafe={cafe} onClose={() => setIsMenuOpened(false)} />
<CafeMenuBottomSheet cafe={cafe} onClose={handleMenuClose} />
</Suspense>
)}
</Container>
Expand All @@ -59,51 +82,12 @@ const CafeActionBar = (props: CafeActionBarProps) => {
export default CafeActionBar;

const Container = styled.aside`
position: absolute;
right: 0;
bottom: 133px;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.space[3]};
gap: ${({ theme }) => theme.space[5]};
align-self: flex-end;
padding-right: ${({ theme }) => theme.space[3]};
`;
padding: ${({ theme }) => theme.space[3]};
const Action = styled.button`
padding: 0;
color: white;
background: none;
border: none;
`;

type ActionButtonProps = PropsWithChildren<{
label: string;
}>;

const ActionButton = (props: ActionButtonProps) => {
const { label, children } = props;

return (
<ActionButtonContainer>
<ActionButtonIcon>{children}</ActionButtonIcon>
{label}
</ActionButtonContainer>
);
};

const ActionButtonContainer = styled.button`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
`;

const ActionButtonIcon = styled.div`
font-size: ${({ theme }) => theme.fontSize['4xl']};
& > svg {
display: block;
}
`;
45 changes: 35 additions & 10 deletions client/src/components/CafeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,40 @@ const CafeCard = (props: CardProps) => {
{`${currentImageIndex + 1}`}/{cafe.images.length}
</CardQuantityContents>
</CardQuantityContainer>

<CarouselImageList onScroll={handleScroll}>
{cafe.images.map((image, index) => (
<CarouselImage
key={index}
src={Resource.getImageUrl({ size: '500', filename: image })}
alt={`${cafe} 이미지`}
alt={`${cafe.name}${index + 1}번째 이미지`}
loading={Math.abs(currentImageIndex - index) <= 1 ? 'eager' : 'lazy'}
/>
))}
</CarouselImageList>

<Bottom>
<BottomItem $fullWidth>
<CafeSummary
title={cafe.name}
address={cafe.address}
onClick={(event) => {
event.stopPropagation();
setIsShowDetail(true);
}}
/>
</BottomItem>
<BottomItem $align="right">
<CafeActionBar cafe={cafe} />
</BottomItem>
</Bottom>

<DotsContainer>
{cafe.images.map((_, index) => (
<Dot key={index} $active={index === currentImageIndex} />
))}
</DotsContainer>
<CafeSummary
title={cafe.name}
address={cafe.address}
onClick={(event) => {
event.stopPropagation();
setIsShowDetail(true);
}}
/>
<CafeActionBar cafe={cafe} />

{isShowDetail && <CafeDetailBottomSheet cafe={cafe} onClose={() => setIsShowDetail(false)} />}
</Container>
);
Expand Down Expand Up @@ -138,3 +148,18 @@ const CardQuantityContents = styled.div`
background-color: ${({ theme }) => theme.color.background.secondary};
border-radius: 10px;
`;

const Bottom = styled.div`
position: absolute;
bottom: 40px;
display: flex;
width: 100%;
`;

const BottomItem = styled.div<{ $fullWidth?: boolean; $align?: 'left' | 'right' }>`
position: absolute;
bottom: 0;
${({ $align }) => $align === 'left' && 'left: 0;'}
${({ $align }) => $align === 'right' && 'right: 0;'}
${({ $fullWidth }) => $fullWidth && 'width: 100%;'}
`;
30 changes: 0 additions & 30 deletions client/src/components/CafeSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { BiSolidInfoCircle } from 'react-icons/bi';
import { SlLocationPin } from 'react-icons/sl';
import { styled } from 'styled-components';

Expand All @@ -20,26 +19,14 @@ const CafeSummary = (props: CafeSummaryProps) => {
{address}
</Address>
</Summary>
<ButtonList>
<Button>
<SolidInfoCircleIcon />
<ButtonText>더보기</ButtonText>
</Button>
</ButtonList>
</Container>
);
};

const Container = styled.div`
cursor: pointer;
position: absolute;
bottom: 0;
display: flex;
width: 100%;
margin-bottom: 40px;
padding: ${({ theme }) => theme.space[3]};
`;

Expand All @@ -63,21 +50,4 @@ const LocationPinIcon = styled(SlLocationPin)`
font-size: ${({ theme }) => theme.fontSize.sm};
`;

const ButtonList = styled.div`
margin-left: auto;
font-size: ${({ theme }) => theme.fontSize.sm};
`;

const ButtonText = styled.span``;

const Button = styled.button`
display: flex;
flex-direction: column;
align-items: flex-end;
`;

const SolidInfoCircleIcon = styled(BiSolidInfoCircle)`
font-size: ${({ theme }) => theme.fontSize['4xl']};
`;

export default CafeSummary;
15 changes: 0 additions & 15 deletions client/src/components/CommentButton.stories.tsx

This file was deleted.

32 changes: 0 additions & 32 deletions client/src/components/CommentButton.tsx

This file was deleted.

25 changes: 25 additions & 0 deletions client/src/components/IconButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Meta, StoryObj } from '@storybook/react';
import { FaShare } from 'react-icons/fa';
import IconButton from './IconButton';

type Story = StoryObj<typeof IconButton>;

const meta: Meta<typeof IconButton> = {
title: 'IconButton',
component: IconButton,
};

export default meta;

export const Default: Story = {
args: {
children: <FaShare />,
},
};

export const WithLabel: Story = {
args: {
children: <FaShare />,
label: '공유',
},
};
36 changes: 36 additions & 0 deletions client/src/components/IconButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { ComponentPropsWithoutRef } from 'react';
import styled from 'styled-components';

type IconButtonProps = ComponentPropsWithoutRef<'button'> & {
label?: string;
};

const IconButton = (props: IconButtonProps) => {
const { label, children, ...restProps } = props;

return (
<Container aria-label={label} {...restProps}>
<Icon>{children}</Icon>

{label && <Label>{label}</Label>}
</Container>
);
};

export default IconButton;

const Container = styled.button`
display: flex;
flex-direction: column;
align-items: center;
`;

const Icon = styled.div`
font-size: ${({ theme }) => theme.fontSize['4xl']};
& > * {
display: block;
}
`;

const Label = styled.span``;
Loading

0 comments on commit 95c1b7e

Please sign in to comment.