Skip to content

feat: 공연 미리보기 데스크탑 버전에서 공유 드롭다운 구현 #294

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

Merged
merged 1 commit into from
May 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 44 additions & 20 deletions apps/preview/src/pages/ShowPreviewPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ShowCastTeamReadResponse, ShowPreviewResponse } from '@boolti/api';
import { Footer, ShowPreview, useDialog } from '@boolti/ui';
import { Footer, ShowPreview, useDeviceByWidth, useDialog } from '@boolti/ui';
import { format, setDefaultOptions } from 'date-fns';
import { ko } from 'date-fns/locale';
import { QRCodeSVG } from 'qrcode.react';
Expand Down Expand Up @@ -43,12 +43,20 @@ const getShareText = (show: {

const ShowPreviewPage = () => {
const [shareDialogOpen, setShareDialogOpen] = useState<boolean>(false);
const [shareDropdownOpen, setShareDropdownOpen] = useState<boolean>(false);

const loaderData = useLoaderData() as
| [ShowPreviewResponse, ShowCastTeamReadResponse[]]
| undefined;

const dialog = useDialog();
const { device } = useDeviceByWidth({
onChangeDeviceByWidth: () => {
setShareDialogOpen(false);
setShareDropdownOpen(false);
dialog.close();
}
});

useBodyScrollLock(shareDialogOpen);

Expand Down Expand Up @@ -105,26 +113,32 @@ const ShowPreviewPage = () => {
}
};

const shareButtonClickHandler = async () => {
dialog.open({
content: (
<Styled.ShareBottomSheet>
<Styled.ShareBottomSheetButton type="button" onClick={shareShowPreviewLink}>
URL만 공유하기
</Styled.ShareBottomSheetButton>
<Styled.ShareBottomSheetButton type="button" onClick={shareShowInfo}>
공연 정보 함께 공유하기
</Styled.ShareBottomSheetButton>
</Styled.ShareBottomSheet>
),
isAuto: true,
mobileType: 'darkBottomSheet',
onClose: () => {
setShareDialogOpen(false);
},
});
const shareButtonClickHandler = () => {
if (device === 'mobile') {
dialog.open({
content: (
<Styled.ShareBottomSheet>
<Styled.ShareBottomSheetButton type="button" onClick={shareShowPreviewLink}>
URL만 공유하기
</Styled.ShareBottomSheetButton>
<Styled.ShareBottomSheetButton type="button" onClick={shareShowInfo}>
공연 정보 함께 공유하기
</Styled.ShareBottomSheetButton>
</Styled.ShareBottomSheet>
),
isAuto: true,
mobileType: 'darkBottomSheet',
onClose: () => {
setShareDialogOpen(false);
setShareDropdownOpen(false);
},
});

setShareDialogOpen(true);
return
}

setShareDialogOpen(true);
setShareDropdownOpen(true);
};

const reservationButtonClickHandler = () => {
Expand All @@ -151,6 +165,12 @@ const ShowPreviewPage = () => {
});
};

const shareDropdownCloseHandler = () => {
setShareDialogOpen(false);
setShareDropdownOpen(false);
dialog.close();
};

const reservationButtonMobileClickHandler = () => {
window.location.href = getDynamicLink(id);
};
Expand Down Expand Up @@ -186,10 +206,14 @@ const ShowPreviewPage = () => {
userImgPath,
})),
}))}
shareDropdownOpen={shareDropdownOpen}
logoLinkHref="https://boolti.in"
onClickLink={reservationButtonClickHandler}
onClickLinkMobile={reservationButtonMobileClickHandler}
onClickShareButton={shareButtonClickHandler}
onShareShowPreviewLink={shareShowPreviewLink}
onShareShowInfo={shareShowInfo}
onCloseShareDropdown={shareDropdownCloseHandler}
/>
<Styled.FooterWrapper>
<Footer darkMode />
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@react-spring/web": "^9.7.3",
"linkify-react": "^4.1.3",
"linkifyjs": "^4.1.3",
"lodash.debounce": "^4.0.8",
"mdast": "^3.0.0",
"nanoid": "^5.0.4",
"rdndmb-html5-to-touch": "^8.0.3",
Expand All @@ -34,6 +35,7 @@
"devDependencies": {
"@boolti/eslint-config": "*",
"@boolti/typescript-config": "*",
"@types/lodash.debounce": "^4.0.9",
"@types/mdast": "^4.0.4",
"@types/navermaps": "^3.7.9",
"@types/react": "^18.2.43",
Expand Down
33 changes: 33 additions & 0 deletions packages/ui/src/components/ShowPreview/ShowPreview.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,41 @@ const ShareButton = styled.button`
width: 24px;
height: 24px;
cursor: pointer;
position: relative;

&:disabled {
cursor: default;
}

svg {
pointer-events: none;
}
`;

const ShareDropdownMenu = styled.div<{ open: boolean }>`
visibility: ${({ open }) => (open ? 'visible' : 'hidden')};
opacity: ${({ open }) => (open ? 1 : 0)};
position: absolute;
top: 54px;
right: 16px;
width: 164px;
background-color: ${({ theme }) => theme.palette.mobile.grey.g85};
border-radius: 6px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 10;
display: flex;
flex-direction: column;
box-shadow: 0 8px 14px 0 #8B8B8B26;
`;

const ShareDropdownItem = styled.button`
width: 100%;
padding: 7px 12px;
text-align: left;
${({ theme }) => theme.typo.b1};
color: ${({ theme }) => theme.palette.mobile.grey.g10};
border: none;
cursor: pointer;
`;

const ShowImage = styled.img`
Expand Down Expand Up @@ -420,6 +451,8 @@ export default {
ShowPreviewNavbar,
LogoLink,
ShareButton,
ShareDropdownMenu,
ShareDropdownItem,
ShowImage,
ShowName,
ShowHeaderInfoList,
Expand Down
50 changes: 48 additions & 2 deletions packages/ui/src/components/ShowPreview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'swiper/css';
import 'swiper/css/pagination';

import { useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Pagination } from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/react';
import { BooltiDark, ClockMobileIcon, MapMarkerIcon, ShareIcon } from '@boolti/icon';
Expand Down Expand Up @@ -38,26 +38,52 @@ interface ShowPreviewProps {
userImgPath: string;
}[];
}>;
shareDropdownOpen?: boolean;
logoLinkHref?: string;
containerRef?: React.RefObject<HTMLDivElement>;
onClickLink?: () => void;
onClickLinkMobile?: () => void;
onClickShareButton?: () => void;
onShareShowPreviewLink?: () => void;
onShareShowInfo?: () => void;
onCloseShareDropdown?: () => void;
}

const ShowPreview = ({
show,
showCastTeams,
shareDropdownOpen,
logoLinkHref,
containerRef,
onClickLink,
onClickLinkMobile,
onClickShareButton,
onShareShowPreviewLink,
onShareShowInfo,
onCloseShareDropdown,
}: ShowPreviewProps) => {
const { images, name, date, startTime, runningTime, placeName } = show;

const [noticeOpen, setNoticeOpen] = useState<boolean>(false);
const containerScrollTop = useRef<number | null>(null);
const shareDropdownMenuRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const outsideClickHandler = (event: MouseEvent) => {
if (
shareDropdownOpen &&
!shareDropdownMenuRef.current?.contains(event.target as HTMLElement)
) {
onCloseShareDropdown?.();
}
};

document.addEventListener('click', outsideClickHandler);

return () => {
document.removeEventListener('click', outsideClickHandler);
};
}, [onCloseShareDropdown, shareDropdownOpen]);

if (noticeOpen) {
return (
Expand All @@ -81,9 +107,29 @@ const ShowPreview = ({
<Styled.LogoLink href={logoLinkHref}>
<BooltiDark />
</Styled.LogoLink>
<Styled.ShareButton onClick={onClickShareButton} disabled={!onClickShareButton}>
<Styled.ShareButton
onClick={(event) => {
event.stopPropagation();
onClickShareButton?.();
}}
disabled={!onClickShareButton}
>
<ShareIcon />
</Styled.ShareButton>
<Styled.ShareDropdownMenu ref={shareDropdownMenuRef} open={!!shareDropdownOpen}>
<Styled.ShareDropdownItem
onClick={() => {
onShareShowPreviewLink?.();
}}
>
URL만 공유하기
</Styled.ShareDropdownItem>
<Styled.ShareDropdownItem
onClick={() => {
onShareShowInfo?.();
}}
>공연 정보 함께 공유하기</Styled.ShareDropdownItem>
</Styled.ShareDropdownMenu>
</Styled.ShowPreviewNavbar>
<Styled.ShowPreviewHeader>
<Swiper
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import useDropdown from './useDropdown';
import useToast from './useToast';
import useAlert from './useAlert';
import useStepDialog from './useStepDialog';
import useDeviceByWidth from './useDeviceByWidth';

export { useConfirm, useDialog, useDropdown, useToast, useAlert, useStepDialog };
export { useConfirm, useDialog, useDropdown, useToast, useAlert, useStepDialog, useDeviceByWidth };
47 changes: 47 additions & 0 deletions packages/ui/src/hooks/useDeviceByWidth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { breakpoint } from '@boolti/ui';
import debounce from 'lodash.debounce';
import { useState, useEffect, useMemo, useCallback } from 'react';

type DeviceType = 'desktop' | 'tablet' | 'mobile';

interface UseDeviceByWidthParams {
onChangeDeviceByWidth?: (value: DeviceType) => void;
}

const useDeviceByWidth = ({ onChangeDeviceByWidth }: UseDeviceByWidthParams) => {
const [device, setDevice] = useState<DeviceType>('desktop');

const updateDeviceByWidth = useCallback((value: DeviceType) => {
if (device !== value) {
setDevice(value);
onChangeDeviceByWidth?.(value);
}
}, [device, onChangeDeviceByWidth]);

const handleResize = useMemo(
() => debounce(() => {
const width = window.innerWidth;
if (width >= parseInt(breakpoint.desktop)) {
updateDeviceByWidth('desktop');
} else if (width >= parseInt(breakpoint.tablet)) {
updateDeviceByWidth('tablet');
} else {
updateDeviceByWidth('mobile');
}
}, 300),
[updateDeviceByWidth]
);

useEffect(() => {
handleResize();
window.addEventListener('resize', handleResize);

return () => {
window.removeEventListener('resize', handleResize);
};
}, [handleResize]);

return { device };
};

export default useDeviceByWidth;
2 changes: 2 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1786,12 +1786,14 @@ __metadata:
"@emotion/react": "npm:^11.11.3"
"@emotion/styled": "npm:^11.11.0"
"@react-spring/web": "npm:^9.7.3"
"@types/lodash.debounce": "npm:^4.0.9"
"@types/mdast": "npm:^4.0.4"
"@types/navermaps": "npm:^3.7.9"
"@types/react": "npm:^18.2.43"
"@types/react-dom": "npm:^18.2.17"
linkify-react: "npm:^4.1.3"
linkifyjs: "npm:^4.1.3"
lodash.debounce: "npm:^4.0.8"
mdast: "npm:^3.0.0"
nanoid: "npm:^5.0.4"
rdndmb-html5-to-touch: "npm:^8.0.3"
Expand Down
Loading