Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,18 +1,67 @@
import { Button, ChevronIcon } from '@what-today/design-system';
import { useCallback, useEffect, useRef, useState } from 'react';
import { twMerge } from 'tailwind-merge';

interface ActivitiesDescriptionProps {
description: string;
className?: string;
}

const COLLAPSED_HEIGHT = 300;

/**
* @description 체험 상세 페이지에서 체험 설명을 보여주는 섹션 컴포넌트입니다.
*/
export default function ActivitiesDescription({ description, className }: ActivitiesDescriptionProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [isOverflowing, setIsOverflowing] = useState(false);
const contentRef = useRef<HTMLDivElement>(null);

const checkOverflow = useCallback(() => {
if (!contentRef.current) return;
const height = contentRef.current.scrollHeight;
setIsOverflowing(height > COLLAPSED_HEIGHT);
}, []);

useEffect(() => {
checkOverflow();
}, [description, checkOverflow]);

useEffect(() => {
if (!contentRef.current) return;
const observer = new ResizeObserver(() => {
checkOverflow();
});
observer.observe(contentRef.current);
return () => observer.disconnect();
}, [checkOverflow]);

return (
<section className={twMerge(`flex h-fit w-full flex-col justify-start gap-8`, className)}>
<section className={twMerge('flex h-fit w-full flex-col justify-start gap-8', className)}>
<div className='section-text'>체험 설명</div>
<p className='body-text'>{description}</p>

<div
ref={contentRef}
className='relative transition-[max-height] duration-500 ease-in-out'
style={{
maxHeight: isExpanded ? `${contentRef.current?.scrollHeight ?? 1000}px` : `${COLLAPSED_HEIGHT}px`,
overflow: !isExpanded && isOverflowing ? 'hidden' : 'visible',
}}
>
<p className='body-text whitespace-pre-line'>{description}</p>

{isOverflowing && !isExpanded && (
<div className='pointer-events-none absolute inset-x-0 bottom-0 h-30 bg-gradient-to-t from-white to-transparent' />
)}
</div>

{isOverflowing && (
<div className='mt-8 flex justify-center'>
<Button size='none' variant='none' onClick={() => setIsExpanded((prev) => !prev)}>
<ChevronIcon className='size-20' color='var(--color-gray-200)' direction={isExpanded ? 'top' : 'bottom'} />
</Button>
</div>
)}
</section>
);
}
38 changes: 23 additions & 15 deletions apps/what-today/src/components/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default function KakaoMap({ address }: MapProps) {

const places = new window.kakao.maps.services.Places();

const placeMarker = (lat: number, lng: number, label: string) => {
const placeMarker = (lat: number, lng: number, label: string, placeUrl?: string) => {
const coords = new window.kakao.maps.LatLng(lat, lng);

map.setCenter(coords);
Expand All @@ -78,16 +78,24 @@ export default function KakaoMap({ address }: MapProps) {
});

const overlayHTML = `
<div style="
background: white;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 8px;
font-size: 16px;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
white-space: nowrap;
">
${label}
<div style="pointer-events: auto; cursor: pointer;">
<a href="${placeUrl ?? '#'}" target="_blank" rel="noopener noreferrer"
style="
display: inline-block;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 8px;
font-size: 16px;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
white-space: nowrap;
color: black;
text-decoration: none;
cursor: pointer;
"
>
${label}
</a>
</div>
`;

Expand All @@ -102,16 +110,16 @@ export default function KakaoMap({ address }: MapProps) {

places.keywordSearch(targetAddress, (data: KakaoMapDatas, status: KakaoMapStatus) => {
if (status === window.kakao.maps.services.Status.OK && data.length > 0) {
const { y, x, place_name } = data[0];
placeMarker(Number(y), Number(x), place_name);
const { y, x, place_name, place_url } = data[0];
placeMarker(Number(y), Number(x), place_name, place_url);
} else {
const fallbackKeyword = targetAddress.replace(/\s\d+(-\d+)?$/g, '').trim();

if (fallbackKeyword !== targetAddress) {
places.keywordSearch(fallbackKeyword, (retryData: KakaoMapDatas, retryStatus: KakaoMapStatus) => {
if (retryStatus === window.kakao.maps.services.Status.OK && retryData.length > 0) {
const { y, x, place_name } = retryData[0];
placeMarker(Number(y), Number(x), place_name);
const { y, x, place_name, place_url } = retryData[0];
placeMarker(Number(y), Number(x), place_name, place_url);
} else {
console.warn('2차 주소 검색 실패. 기본 위치로 이동합니다.');
placeMarker(FALLBACK_LOCATION.lat, FALLBACK_LOCATION.lng, FALLBACK_LOCATION.label);
Expand Down