�Button에 Mouse Hover, Out시 Tooltip이 나오는 TypeScript를 이용해 만든 React App 입니다.
- Button Hover시, Tooltip 생성
- Tooltip을 나오게 하는 Tooltip 컴포넌트 생성
- Tooltip 컴포넌트에서 필요한 props를 interface로 생성 (content - 툴팁 내용)
interface TooltipProps {
content: ReactNode;
children: ReactNode;
}
- useState를 사용하여 visivle이라는 state를 만들어 주고 기본적으로는 onMouseEvnet가 없기 때문에 false로 설정
const [visible, setVisible] = useState(false);
- onMouseEvent가 있을시 visible을 변경 해줘야 하므로 함수 생성
const showTooltip = () => setVisible(true);
const hideTooltip = () => setVisible(false);
- Tooltip 컴포넌트 랜더링 (visible이 true면 content props로 받아온 내용으로 tooltip 생성)
return (
<div className="tooltip-container">
<div
className="tooltip-trigger"
onMouseEnter={showTooltip}
onMouseLeave={hideTooltip}
>
{children}
</div>
{visible && <div className="tooltip-content">{content}</div>}
</div>
);
- App.tsx에 Tooltip 컴포넌트 import 한 뒤 Tooltip 컴포넌트에서 필요한 props 전달 후 랜더링
import React from 'react';
import Tooltip from './Tooltip';
const App: React.FC = () => {
return (
<div style={{ padding: '50px' }}>
<Tooltip content="This is a tooltip">
<button>Hover over me</button>
</Tooltip>
</div>
);
};
- 결과 화면
- overflow: hidden / scroll 일 경우에도 Tooltip이 최상위에 보이게 하기 위해 createPortal 사용
- Tooltip 컴포넌트 수정
- useState로 position state를 만들어 준 후, 초기값으로 {top:0, left:0} 객체로 설정
const [position, setPosition] = useState({ top: 0, left: 0 });
- useRef를 사용하여 triggerRef null로 초기화
const triggerRef = useRef<HTMLDivElement>(null);
- visible의 값이 바뀔 때마다 리랜더링 될 수 있게끔 useEffect 설정
- rect에 getBoundingClientRect()로 triggerRef의 current값을 저장 후 position을 지정
useEffect(() => {
if (visible && triggerRef.current) {
const rect = triggerRef.current.getBoundingClientRect();
setPosition({
top: rect.bottom + window.scrollY,
left: rect.left + window.scrollX + rect.width / 2,
});
}
}, [visible]);
- ReactDOM.createPortal으로 Tooltip컴포넌트를 최상위에서 랜더링 될 수 있게 지정해준다.
{visible &&
ReactDOM.createPortal(
<div
className="tooltip-content"
style={{ top: `${position.top}px`, left: `${position.left}px` }}
>
{content}
</div>,
document.body
)}
- 결과 화면
- 툴팁 방향을 left, right, bottom, topLeft... 으로 뜰 수 있게 방향 툴팁 컴포넌트 수정 / TooltipDirection 컴포넌트 생성
- Tooltip 컴포넌트 수정
- interface에 position 추가
interface TooltipProps {
content: ReactNode;
children: ReactNode;
position?: 'left' | 'right' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
}
- position state의 이름이 중복이므로 좌표인 coord, setCoord로 변경
const [coords, setCoords] = useState({ top: 0, left: 0 });
- position 위치 별로 설정 해야하므로 useEffect 수정
useEffect(() => {
if (visible && triggerRef.current) {
const rect = triggerRef.current.getBoundingClientRect();
let newCoords = { top: 0, left: 0 };
switch (position) {
case 'topLeft':
newCoords = { top: rect.top + window.scrollY, left: rect.left + window.scrollX };
break;
case 'topRight':
newCoords = { top: rect.top + window.scrollY, left: rect.right + window.scrollX };
break;
// ... (중략)
default: // 'top'
newCoords = { top: rect.top + window.scrollY, left: rect.left + rect.width / 2 + window.scrollX };
break;
}
setCoords(newCoords);
}
}, [visible, position]);
- 각 버튼들이 있어야 하므로 TooltipDirection 컴포넌트 생성
import Tooltip from "./Tooltip";
import "./TooltipDirection.css";
const TooltipDirection = () => {
return (
<div className="TooltipDirection">
<div>
<div className="top-wrapper">
<Tooltip content="Top Left Tooltip" position="topLeft">
<button className="test-button top-left">Top Left</button>
</Tooltip>
<Tooltip content="">
<button className="test-button top"></button>
</Tooltip>
<Tooltip content="Top Right Tooltip" position="topRight">
<button className="test-button top-right">Top Right</button>
</Tooltip>
</div>
// ... (중략)
- App 컴포넌트에 랜더링 될 수 있게 TooltipDirection 컴포넌트 추가
const App = () => {
return (
<div className="container">
<div className="container-layout">
<section style={{ height: "100vh", padding: "0px 200px" }}>
<TooltipDirection />
</section>
</div>
</div>
);
};
- 결과 화면
- 툴팁이 나타나거나 사라질 때 딜레이를 추가하려면 setTimeout을 사용하여 딜레이 구현
- Tooltip 컴포넌트 수정
- interface에�showDelay / hideDelay 추가
interface TooltipProps {
content: ReactNode;
children: ReactNode;
position?: 'left' | 'right' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
showDelay?: number;
hideDelay?: number;
}
- useRef로 showTimeoutRef와 hideTimeoutRef를 number타입과 null이 들어 오니 Union으로 설정 해주고 null로 초기화
const showTimeoutRef = useRef<number | null>(null);
const hideTimeoutRef = useRef<number | null>(null);
- showTooltip 함수를 생성 (onMouseEnter 할 때 props로 받아온 showDelay의 숫자로 delay 생성)
const showTooltip = () => {
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
hideTimeoutRef.current = null;
}
showTimeoutRef.current = window.setTimeout(() => {
if (triggerRef.current) {
const rect = triggerRef.current.getBoundingClientRect();
let newCoords = { top: 0, left: 0 };
switch (position) {
case 'topLeft':
newCoords = { top: rect.top + window.scrollY, left: rect.left + window.scrollX };
break;
case 'topRight':
newCoords = { top: rect.top + window.scrollY, left: rect.right + window.scrollX };
break;
// ...(중략)
default: // 'top'
newCoords = { top: rect.top + window.scrollY, left: rect.left + rect.width / 2 + window.scrollX };
break;
}
setCoords(newCoords);
setVisible(true);
}
}, showDelay);
};
- hideTooltip 함수 생성 (onMouseLeave 할 때 props로 받아온 hideDelay의 숫자로 delay 생성)
const hideTooltip = () => {
if (showTimeoutRef.current) {
clearTimeout(showTimeoutRef.current);
showTimeoutRef.current = null;
}
hideTimeoutRef.current = window.setTimeout(() => {
setVisible(false);
}, hideDelay);
};
- useEffect로 Mount될 때 만약 show/hideTimeoutRef에 null값이 아닌 숫자가 들어 가있으면 clearTimout으로 초기화 시켜줍니다.
useEffect(() => {
return () => {
if (showTimeoutRef.current) {
clearTimeout(showTimeoutRef.current);
}
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
}
};
}, []);
- 버튼 생성을 위해 TooltipDelay 컴포넌트 생성
const TooltipDelay = () => {
return (
<div className="TooltipDelay">
<div>
<Tooltip content="Enter Delay 1s" showDelay={1000}>
<button className="test-button">enter delay 1s</button>
</Tooltip>
<Tooltip content="Leave Delay 1s" hideDelay={1000}>
<button className="test-button">leave delay 1s</button>
</Tooltip>
</div>
</div>
);
};
export default TooltipDelay;
- App 컴포넌트에 랜더링 될 수 있게 TooltipDelay 컴포넌트 추가
const App = () => {
return (
<div className="container">
<div className="container-layout">
<section style={{ height: "100vh", padding: "0px 200px" }}>
<TooltipDirection />
<TooltipDelay />
</section>
</div>
</div>
);
};
- 결과 화면
- 이미 showDelay, hideDelay를 props에 추가 하여서 사용자가 원하는 시간으로 딜레이를 설정할 수 있습니다.
<Tooltip content="Enter Delay 1s" showDelay={1000}>
<button className="test-button">enter delay 1s</button>
</Tooltip>
<Tooltip content="Leave Delay 1s" hideDelay={1000}>
<button className="test-button">leave delay 1s</button>
</Tooltip>
- tooltip의 내용을 적는 content props를 ReactNode 타입으로 설정 해주어 다양한 형태의 데이터 전달 가능
interface TooltipProps {
content: ReactNode;
children: ReactNode;
position?: 'left' | 'right' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
showDelay?: number;
hideDelay?: number;
}
<Tooltip
content={
<span role="img" aria-label="icon">
⭐
</span>
}
showDelay={500}
hideDelay={500}
>
<button className="test-button">⭐</button>
</Tooltip>
<Tooltip
content={
<>
<span role="img" aria-label="icon">
🔍
</span>{" "}
Search
</>
}
showDelay={500}
hideDelay={500}
>
<button className="test-button">Search</button>
결과 화면
- Tooltip 컴포넌트에 tooltipStyle props를 추가하고 CSSProperties타입으로 설정하여 랜더링 될때 style에 spread 연산자로 추가
- Tooltip 컴포넌트 수정
- 인터페이스 수정
interface TooltipProps {
content: ReactNode;
children: ReactNode;
position?: 'left' | 'right' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
showDelay?: number;
hideDelay?: number;
tooltipStyle?: CSSProperties;
}
- 랜더링 될때 ...tooltipStyle 추가
{visible && ReactDOM.createPortal( <div className={`tooltip-content tooltip-${position}`} style={{ top: `${coords.top}px`, left: `${coords.left}px`, ...tooltipStyle }} > {content} </div>, document.body )}
- 버튼 생성을 위해 TooltipStyle 컴포넌트 생성
const TooltipStyle = () => { return ( <div className="TooltipStyle"> <Tooltip content="Pink" tooltipStyle={{ backgroundColor: "#fac" }}> <button className="test-button pink">Pink</button> </Tooltip> <Tooltip content="Yellow" tooltipStyle={{ backgroundColor: "#fff1aa", color: "#333" }} > <button className="test-button yellow">Yellow</button> </Tooltip> </div> ); };
- App 컴포넌트에 랜더링 될 수 있게 TooltipStyle 컴포넌트 추가
const App = () => {
return (
<div className="container">
<div className="container-layout">
<section style={{ height: "100vh", padding: "0px 200px" }}>
<TooltipDirection />
<TooltipDelay />
<TooltipStyle />
</section>
</div>
</div>
);
};
- 결과 화면
- Tooltip 컴포넌트에 disabled Props를 추가하여 props가 true일 때 툴팁이 나타나지 않도록 설정
- Tooltip 컴포넌트 수정
- interface에 disabled Props 추가
interface TooltipProps {
content: ReactNode;
children: ReactNode;
position?: 'left' | 'right' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
showDelay?: number;
hideDelay?: number;
tooltipStyle?: CSSProperties;
disabled?: boolean;
}
- showTooltip, hideTooltip 함수에 disalbed가 true일 경우 return 조건 추가
const showTooltip = () => {
if (disabled) return;
// ... (중략)
}
const hideTooltip = () => {
if (disabled) return;
// ... (중략)
}
- 버튼 및 기능을 확인 하기 위한 TooltipAvailable 컴포넌트 생성
- useState로 버튼에 Enable / Disable 이 들어가는 state 생성 기본값은 "Enalbe"로 생성
- useState로 tooltip의 disable인지를 확인하는 state 생성 기본값은 false로 생성
const [available, setAvailable] = useState("Enable");
const [isTooltipDisabled, setIsTooltipDisabled] = useState(false);
- onClickAvailable 함수를 만들어 onClick 이벤트가 발생 했을 때 avilable state값이 Enable이면 setAvailable로 "Disable"로 만들고 isTooltipDisabled를 반댓값으로 설정
- available이 Disable 이면 setAvailable로 "Enable"로 바꿔주고 isTooltipDisabled를 반댓값으로 설정
const onClickAvailable = () => {
if (available === "Enable") {
setAvailable("Disable");
setIsTooltipDisabled(!isTooltipDisabled);
} else if (available === "Disable") {
setAvailable("Enable");
setIsTooltipDisabled(!isTooltipDisabled);
}
};
- 랜더링 진행
return (
<div className="EnableTooltip">
<button className={`test-button ${available}`} onClick={onClickAvailable}>
{available}
</button>
<Tooltip
content="Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci
asperiores atque"
disabled={isTooltipDisabled}
>
<div className="tooltip-disable-test">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci
asperiores atque
</div>
</Tooltip>
</div>
);
- App 컴포넌트에 랜더링 될 수 있게 TooltipAvailable 컴포넌트 추가
const TooltipDelay = () => {
return (
<div className="TooltipDelay">
<div>
<Tooltip content="Enter Delay 1s" showDelay={1000}>
<button className="test-button">enter delay 1s</button>
</Tooltip>
<Tooltip content="Leave Delay 1s" hideDelay={1000}>
<button className="test-button">leave delay 1s</button>
</Tooltip>
</div>
</div>
);
};
export default TooltipDelay;
- 결과 화면
- 사용한 기술 스택 : HTML, CSS, TypeScript, React