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

[노철] - TooltipButton 구현 #489

Merged
merged 10 commits into from
Mar 13, 2024
4 changes: 2 additions & 2 deletions src/app/plans/[planId]/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
&--share {
margin: 1rem 0;
display: flex;
flex-direction: column;
gap: 0.5rem;

justify-content: space-between;
&--buttons {
display: flex;
gap: 1rem;
Expand Down
24 changes: 22 additions & 2 deletions src/app/plans/[planId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
KakaoShareButton,
Modal,
ModalBasic,
TooltipButton,
} from '@/components';
import classNames from 'classnames';
import Link from 'next/link';
Expand Down Expand Up @@ -42,7 +43,26 @@ export default function PlanIdPage({ params }: { params: { planId: string } }) {
{isMyPlan && (
<div className="plans-page--share">
<h2>공유하기</h2>
<div className="plans-page--share--buttons">
<TooltipButton.Main>
<TooltipButton.Options>
<label className="font-size-xs" onClick={handleCopyLink}>
<Icon name="COPY" color="text-100" size="md" />
링크 복사
</label>
<label className="font-size-xs">
<KakaoShareButton linkURL={currentURL} />
카카오톡
</label>
</TooltipButton.Options>
<TooltipButton.Trigger>
<label>
<Icon name="SHARE" color="text-100" size="md" />
공유하기
</label>
</TooltipButton.Trigger>
</TooltipButton.Main>
{/*TODO 기존 공유버튼 삭제 예정 */}
{/* <div className="plans-page--share--buttons">
<label className="font-size-xs" onClick={handleCopyLink}>
<Icon name="COPY" color="text-100" size="md" />
링크 복사
Expand All @@ -51,7 +71,7 @@ export default function PlanIdPage({ params }: { params: { planId: string } }) {
<KakaoShareButton linkURL={currentURL} />
카카오톡
</label>
</div>
</div> */}
</div>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const ICON_NAME_MAP = {
COPY: 'content_copy',
ARROW_RIGHT: 'subdirectory_arrow_right',
CANCEL: 'cancel',
SHARE: 'share',
};

interface IconProps {
Expand Down
6 changes: 3 additions & 3 deletions src/components/KakaoShareButton/KakaoShareButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export default function KakaoShareButton({ linkURL }: KakaoShareButtonProps) {
const kakao = (window as any).Kakao;

useEffect(() => {
kakao.cleanup();
kakao.init(process.env.NEXT_PUBLIC_KAKAO_JAVASCRIPT_KEY);
kakao?.cleanup();
kakao?.init(process.env.NEXT_PUBLIC_KAKAO_JAVASCRIPT_KEY);
return () => {
kakao.cleanup();
kakao?.cleanup();
};
}, [kakao]);

Expand Down
120 changes: 120 additions & 0 deletions src/components/TooltipButton/TooltipButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use client';

import { useModalClose } from '@/hooks';
import classNames from 'classnames';
import {
ReactElement,
createContext,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import './index.scss';

//TooltipButton컴포넌트 내에서 공유할 상태, 함수
const contextDefaultValue = {
isOpen: false, // 툴팁 열림 여부
handleSetIsOpen: () => {}, //툴팁 상태 변경 함수
handleCloseTooltip: () => {}, //툴팁 닫기 함수
optionsPosition: 'top' as Position, //Options컴포넌트 위치(열림 방향을 위해서 필요)
Copy link
Contributor

Choose a reason for hiding this comment

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

top일때는 위로 카카오톡, 링크 복사하기 이렇게 두 개가 보이는 것이고
bottom일 때는 툴팁이 닫히는 방식으로 작동하는 건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

컴포넌트 넣어주는 순서에 따라서 팝업이 열리는 위치를 정하도록 해주었습니다
Options의 위치가 top일때는 카카오톡, 링크 복사하기가 위에 나오고, bottom 일때는 아래에 나오도록 설정한겁니다.

<TooltipButton.Main>
  <TooltipButton.Trigger />
  <TooltipButton.Options /> 
</TooltipButton.Main
//Options 컴포넌트가 아래에 있어서 팝업시 버튼 목록이 아래로 펼쳐집니다. 

<TooltipButton.Main>
<TooltipButton.Options/>
<TooltipButton.Trigger/>
</TooltipButton.Main>
// Options 컴포넌트가 위에 있어서 팝업시 버튼 목록이 위로 펼쳐집니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

작성 위치에 따라 변경되는 부분 너무 좋은 것 같은데 처음 보는 사람은 이해하기가 좀 어려운것 같습니다..!
props로 받아와 변경해주는게 좀 더 사용하기 편할것 같아요 😄

};

type Position = 'top' | 'bottom';

const TooltipButtonContext = createContext<{
isOpen: boolean;
handleSetIsOpen: () => void;
handleCloseTooltip: () => void;
optionsPosition: Position;
}>(contextDefaultValue);

interface MainProps {
className?: string; // 위치조정이이나, css 스탕일링을 위한 className
children: ReactElement[];
}

const Main = ({ children, className }: MainProps) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [optionsPosition, setOptionsPosition] = useState<Position>('top');
useEffect(() => {
setOptionsPosition(children[0]?.type !== Options ? 'bottom' : 'top');
}, [children]);

const handleCloseTooltip = () => {
setIsOpen(false);
};
const handleSetIsOpen = () => {
setIsOpen(!isOpen);
};

return (
<div className={classNames('tooltip-button__main', className)}>
<TooltipButtonContext.Provider
value={{
handleCloseTooltip,
optionsPosition,
isOpen,
handleSetIsOpen,
}}>
{children}
</TooltipButtonContext.Provider>
</div>
);
};

interface TriggerProps {
children: ReactElement;
className?: string;
}
//열고 닫힘 trigger 역할을 할 컴푸넌트 children을 넣얼줄때 외부에서 직접 스타일링 가능, 그외 부분 클릭시 Tooltip 닫힘
const Trigger = ({ children, className }: TriggerProps) => {
const { handleSetIsOpen, handleCloseTooltip } =
useContext(TooltipButtonContext);
const triggerRef = useRef(null);
useModalClose(triggerRef, handleCloseTooltip);
return (
<button
ref={triggerRef}
onClick={handleSetIsOpen}
className={classNames(className, 'tooltip-button__trigger')}>
{children}
</button>
);
};

interface OptionsProps {
className?: string;
children: ReactElement[] | ReactElement;
}
//열렸을때 보여줄 아이템들
const Options = ({ className, children }: OptionsProps) => {
const { isOpen, optionsPosition } = useContext(TooltipButtonContext);
return (
<ul
className={classNames(
className,
'tooltip-button__list',
`position-${optionsPosition}`,
isOpen ? 'open' : 'close',
)}>
{Array.isArray(children) ? (
children.map((component, index) => (
<li
data-order={index + 1}
className={classNames(
'tooltip-button__list__item',
isOpen ? 'open' : 'close',
)}
key={index}>
{component}
</li>
))
) : (
<li className="tooltip-button__list__item ">{children}</li>
)}
</ul>
);
};

export { Main, Options, Trigger };
67 changes: 67 additions & 0 deletions src/components/TooltipButton/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.tooltip-button {
&__main {
overflow: visible;
position: relative;
}

&__trigger {
background-color: transparent;
}

&__list {
display: flex;
flex-direction: column;
background-color: transparent;
gap: 0.75rem;
position: absolute;
transition: all 0.3s;
text-wrap: nowrap;

&__item {
background-color: transparent;
transition: all 0.2s;
}
&.position-top {
bottom: 140%;
}

&.position-top &__item {
&.close {
transform: translateY(100%);
opacity: 0;
visibility: hidden;
}
&.open {
transform: translateY(0);
opacity: 1;
}
}

&.position-bottom {
top: 125%;
}

&.position-bottom &__item {
&.close {
transform: translateY(-100%);
opacity: 0;
visibility: hidden;
}
&.open {
transform: translateY(0);
opacity: 1;
}
}
//순차적으로 나타나고 사라지는 애니메이션을 위한 delay
$n: 5;

@for $i from 1 through $n {
&__item:nth-child(#{$i}) {
transition-delay: calc(0.1s * $i);
}
}
}
&__list.close {
visibility: hidden;
}
}
1 change: 1 addition & 0 deletions src/components/TooltipButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as TooltipButton from '@components/TooltipButton/TooltipButton';
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ export { default as ToTopFloatingButton } from '@/components/ToTopFloatingButton
export { default as WritableRemindItem } from '@components/RemindItem/WritableRemindItem/WritableRemindItem';
export { default as ReadOnlyPlan } from '@components/ReadOnlyPlan/ReadOnlyPlan';
export { default as WrongApproach } from '@components/WrongApproach/WrongApproach';
export { TooltipButton } from '@components/TooltipButton/index';
3 changes: 2 additions & 1 deletion src/types/IconName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ export type IconName =
| 'COPY'
| 'HELP'
| 'ARROW_RIGHT'
| 'CANCEL';
| 'CANCEL'
| 'SHARE';
Loading