Skip to content

Commit

Permalink
feat(ui-kit): 툴팁 컴포넌트 추가 (#37)
Browse files Browse the repository at this point in the history
* feat(ui-kit): child ref 포지션으로 툴팁 렌더

* 툴팁 애로우 추가

* 버튼 스타일 수정

* 툴팁이 ref에 반응하도록 수정

* feat(ui-kit): 툴팁 포지션 세팅 완료

* fix lint

* feat(ui-kit): 오타 수정

* feat(tooltip): 부모 중 relative 요소가 있어도 툴팁이 올바른 offset을 가지도록 수정
  • Loading branch information
evan-moon authored Feb 4, 2021
1 parent eaa7c0f commit be2bbb5
Show file tree
Hide file tree
Showing 10 changed files with 398 additions and 1 deletion.
37 changes: 37 additions & 0 deletions ui-kit/src/components/Tooltip/TooltipBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { forwardRef, Ref } from 'react';
import classnames from 'classnames';
import Text from '../Text';

export type TooltipArrowDirection =
| 'top-left'
| 'top-center'
| 'top-right'
| 'left'
| 'right'
| 'bottom-left'
| 'bottom-center'
| 'bottom-right';

interface Props {
children: string;
arrowDirection: TooltipArrowDirection;
}
const TooltipBody = forwardRef(function TooltipBody(
{ children, arrowDirection }: Props,
forwardedRef: Ref<HTMLDivElement>
) {
return (
<div
ref={forwardedRef}
className={classnames(
'lubycon-tooltip__body',
`lubycon-tooltip__body--arrow-${arrowDirection}`
)}
tabIndex={-1}
>
<Text typography="caption">{children}</Text>
</div>
);
});

export default TooltipBody;
64 changes: 64 additions & 0 deletions ui-kit/src/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { cloneElement, ReactElement, useState, useMemo, useCallback } from 'react';
import { animated, useSpring } from 'react-spring';
import { Portal } from 'src/contexts/Portal';
import TooltipBody from './TooltipBody';
import { OffsetPosition, TooltipElementSize, TooltipPosition } from './types';
import { getArrowDirection, getTooltipPosition } from './utils';

interface Props {
show: boolean;
children: ReactElement;
message: string;
position?: TooltipPosition;
}
const Tooltip = ({ show, children, message, position = 'top-center' }: Props) => {
const [tooltipSize, setTooltipSize] = useState<TooltipElementSize>({ width: 0, height: 0 });
const [tooltipOffset, setTooltipOffset] = useState<OffsetPosition>({
top: -1,
left: -1,
});
const arrowDirection = useMemo(() => getArrowDirection(position), [position]);

const childRef = useCallback(
(childElement: HTMLElement | null) => {
if (childElement !== null) {
setTooltipOffset(getTooltipPosition(childElement, tooltipSize, position));
}
},
[tooltipSize, position]
);

const tooltipRef = useCallback((node: HTMLDivElement | null) => {
if (node !== null) {
setTooltipSize({
width: node.clientWidth,
height: node.clientHeight,
});
}
}, []);

const animation = useSpring({
visibility: show ? 'visible' : 'hidden',
opacity: show ? 1 : 0,
});

return (
<>
{cloneElement(children, {
ref: childRef,
})}
<Portal>
<animated.div
className="lubycon-tooltip__positioner"
style={{ ...tooltipOffset, ...animation }}
>
<TooltipBody ref={tooltipRef} arrowDirection={arrowDirection}>
{message}
</TooltipBody>
</animated.div>
</Portal>
</>
);
};

export default Tooltip;
19 changes: 19 additions & 0 deletions ui-kit/src/components/Tooltip/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export type TooltipPosition =
| 'top-left'
| 'top-center'
| 'top-right'
| 'left'
| 'right'
| 'bottom-left'
| 'bottom-center'
| 'bottom-right';

export interface OffsetPosition {
top: number;
left: number;
}

export interface TooltipElementSize {
width: number;
height: number;
}
96 changes: 96 additions & 0 deletions ui-kit/src/components/Tooltip/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { getAbsoluteOffset } from 'utils/dom';
import { TooltipArrowDirection } from './TooltipBody';
import { OffsetPosition, TooltipElementSize, TooltipPosition } from './types';

export function getArrowDirection(position: TooltipPosition): TooltipArrowDirection {
switch (position) {
case 'top-left':
return 'bottom-left';
case 'top-center':
return 'bottom-center';
case 'top-right':
return 'bottom-right';
case 'bottom-left':
return 'top-left';
case 'bottom-center':
return 'top-center';
case 'bottom-right':
return 'top-right';
case 'left':
return 'right';
case 'right':
return 'left';
default:
return position;
}
}

export function getTooltipPosition(
childElement: HTMLElement,
tooltipSize: TooltipElementSize,
position: TooltipPosition
): OffsetPosition {
const arrowHeight = 8;
const spacing = 8;
const { clientWidth, clientHeight } = childElement;

const { top: offsetTop, left: offsetLeft } = getAbsoluteOffset(childElement);
const offsetRight = offsetLeft + clientWidth;
const offsetBottom = offsetTop + clientHeight;

const { width: tooltipWidth, height: tooltipHeight } = tooltipSize;

const topPosition = offsetTop - tooltipHeight - arrowHeight - spacing;
const bottomPosition = offsetBottom + arrowHeight + spacing;
const horizontalCenterOfChildren = offsetLeft + clientWidth / 2 - tooltipWidth / 2;
const verticalCenterOfChildren = offsetTop + clientHeight / 2 - tooltipHeight / 2;

switch (position) {
case 'top-left':
return {
top: topPosition,
left: offsetLeft,
};
case 'top-center':
return {
top: topPosition,
left: horizontalCenterOfChildren,
};
case 'top-right':
return {
top: topPosition,
left: offsetRight - tooltipWidth,
};
case 'bottom-left':
return {
top: bottomPosition,
left: offsetLeft,
};
case 'bottom-center':
return {
top: bottomPosition,
left: horizontalCenterOfChildren,
};
case 'bottom-right':
return {
top: bottomPosition,
left: offsetLeft + clientWidth - tooltipWidth,
};
case 'left':
return {
top: verticalCenterOfChildren,
left: offsetLeft - tooltipWidth - arrowHeight - spacing,
};
case 'right':
return {
top: verticalCenterOfChildren,
left: offsetRight + arrowHeight + spacing,
};

default:
return {
top: offsetTop - arrowHeight - spacing,
left: offsetLeft,
};
}
}
1 change: 1 addition & 0 deletions ui-kit/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export { default as Switch } from './Switch';
export { default as Text } from './Text';
export { default as LubyconUIKitProvider } from './LubyconUIKitProvider';
export { default as Toast } from './Toast';
export { default as Tooltip } from './Tooltip';
export { Tabs, TabPane } from './Tabs';
6 changes: 5 additions & 1 deletion ui-kit/src/sass/components/_Button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
border-radius: 4px;
color: #ffffff;
cursor: pointer;
-webkit-font-smoothing: antialiased;
transition: background-color 0.2s ease-in-out;

&__small {
height: 32px;
Expand All @@ -20,9 +22,11 @@
&__large {
height: 56px;
padding: 12px 32px;
border-radius: 8px;
}

&:hover, &:active {
&:hover,
&:active {
background-color: get-color('blue60');
}
&:disabled {
Expand Down
93 changes: 93 additions & 0 deletions ui-kit/src/sass/components/_Tooltip.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
$tooltip-h-padding: 12px;

.lubycon-tooltip__positioner {
position: absolute;
}

.lubycon-tooltip__body {
position: relative;
display: inline-flex;
background-color: get-color('gray100');
color: #ffffff;
padding: 4px $tooltip-h-padding;
border-radius: 4px;
-webkit-font-smoothing: antialiased;
}

%tooltip-arrow-position {
position: absolute;
width: 0;
height: 0;
content: '';
}
%tooltip-bottom-arrow {
@extend %tooltip-arrow-position;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 8px solid get-color('gray100');
}
%tooltip-left-arrow {
@extend %tooltip-arrow-position;
border-bottom: 6px solid transparent;
border-right: 8px solid get-color('gray100');
border-top: 6px solid transparent;
}
%tooltip-right-arrow {
@extend %tooltip-arrow-position;
border-left: 8px solid get-color('gray100');
border-bottom: 6px solid transparent;
border-top: 6px solid transparent;
}
%tooltip-top-arrow {
@extend %tooltip-arrow-position;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 8px solid get-color('gray100');
}

.lubycon-tooltip__body--arrow {
&-bottom-center::before {
@extend %tooltip-bottom-arrow;
top: 100%;
left: 50%;
transform: translateX(-50%);
}
&-bottom-left::before {
@extend %tooltip-bottom-arrow;
top: 100%;
left: $tooltip-h-padding;
}
&-bottom-right::before {
@extend %tooltip-bottom-arrow;
top: 100%;
right: $tooltip-h-padding;
}
&-right::before {
@extend %tooltip-right-arrow;
top: 50%;
transform: translateY(-50%);
left: 100%;
}
&-left::before {
@extend %tooltip-left-arrow;
top: 50%;
transform: translateY(-50%);
right: 100%;
}
&-top-center::before {
@extend %tooltip-top-arrow;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
}
&-top-left::before {
@extend %tooltip-top-arrow;
bottom: 100%;
left: $tooltip-h-padding;
}
&-top-right::before {
@extend %tooltip-top-arrow;
bottom: 100%;
right: $tooltip-h-padding;
}
}
1 change: 1 addition & 0 deletions ui-kit/src/sass/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
@import './Selection';
@import './Icon';
@import './Toast';
@import './Tooltip';
@import './Tabs';
Loading

0 comments on commit be2bbb5

Please sign in to comment.