Skip to content

Commit

Permalink
feat(ui/toooltip): add Tooltip component
Browse files Browse the repository at this point in the history
  • Loading branch information
jozefhruska committed Oct 16, 2020
1 parent ff9bbd4 commit 963d6ff
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 47 deletions.
86 changes: 86 additions & 0 deletions packages/ui/__stories__/Tooltip.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from 'react'

import { Tooltip } from '../src/Tooltip'

import utilStyles from './utils.scss'

export default {
title: 'Components/Tooltip',
component: Tooltip,
}

export const Default = () => (
<div className={utilStyles.wrapperCentered}>
<Tooltip id="tooltip-default" content={'Tooltip content'}>
<button aria-labelledby="tooltip-default">Hover me</button>
</Tooltip>
</div>
)

Default.parameters = {
design: {
type: 'figma',
url: 'https://www.figma.com/file/kaC3jgqMSgqMEgnv7TIse1/%F0%9F%93%90Sign-in-flow?node-id=118%3A2349',
},
}

export const TopPlacement = () => (
<div className={utilStyles.wrapperCentered}>
<Tooltip id="tooltip-placement-top" placement="top" content={'Tooltip content'}>
<button aria-labelledby="tooltip-placement-top">Hover me</button>
</Tooltip>
</div>
)

export const RightPlacement = () => (
<div className={utilStyles.wrapperCentered}>
<Tooltip id="tooltip-placement-right" placement="right" content={'Tooltip content'}>
<button aria-labelledby="tooltip-placement-right">Hover me</button>
</Tooltip>
</div>
)

export const BottomPlacement = () => (
<div className={utilStyles.wrapperCentered}>
<Tooltip id="tooltip-placement-bottom" placement="bottom" content={'Tooltip content'}>
<button aria-labelledby="tooltip-placement-bottom">Hover me</button>
</Tooltip>
</div>
)

export const LeftPlacement = () => (
<div className={utilStyles.wrapperCentered}>
<Tooltip id="tooltip-placement-left" placement="left" content={'Tooltip content'}>
<button aria-labelledby="tooltip-placement-left">Hover me</button>
</Tooltip>
</div>
)

export const InteractiveContent = () => (
<div className={utilStyles.wrapperCentered} style={{ minHeight: '250px' }}>
<Tooltip
id="tooltip-interactive-content"
content={
<>
<p>
Parley come about mutiny swing the lead to go on account run a shot across the bow schooner fathom bounty carouser. Maroon
killick keel driver scourge of the seven seas Jolly Roger hands spyglass Brethren of the Coast booty. Boom rigging gally Plate
Fleet pink dance the hempen jig bilge water measured fer yer chains take a caulk tender.
</p>

<button>Aye Captain!</button>
</>
}
>
<button aria-labelledby="tooltip-interactive-content">Hover me</button>
</Tooltip>
</div>
)

export const Disabled = () => (
<div className={utilStyles.wrapperCentered}>
<Tooltip id="tooltip-disabled" content={'Tooltip content'} disabled>
<button aria-labelledby="tooltip-disabled">Hover me</button>
</Tooltip>
</div>
)
5 changes: 5 additions & 0 deletions packages/ui/__stories__/utils.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.wrapperCentered {
display: flex;
flex-direction: column;
align-items: center;
}
27 changes: 27 additions & 0 deletions packages/ui/package-lock.json

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

3 changes: 2 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@
"dependencies": {
"@hazelcast/helpers": "^0.0.1",
"@hazelcast/services": "^0.0.1",
"@popperjs/core": "^2.5.3",
"classnames": "^2.2.6",
"formik": "^2.2.0",
"rc-tooltip": "^4.2.3",
"react-feather": "^2.0.8",
"react-popper": "^2.2.3",
"uuid": "^8.3.1"
},
"devDependencies": {
Expand Down
120 changes: 103 additions & 17 deletions packages/ui/src/Tooltip.module.scss
Original file line number Diff line number Diff line change
@@ -1,26 +1,112 @@
@import '../styles/constants/index';

.wrapper {
max-width: $tooltipMaxWidth;
word-wrap: break-word;
}

.overlay {
// TODO: Update colors. Ask Pawel.
color: #fff;
.container {
display: inline;

:global .rc-tooltip-inner {
background-color: black;
.reference {
display: inline;
}

&.rc-tooltip-placement-bottom {
:global .rc-tooltip-arrow {
border-bottom-color: black;
.overlay {
position: relative;
max-width: $tooltipMaxWidth;
padding: $grid * 5;
background: $colorNeutralLighter;
border: $tooltipBorderWidth solid $colorNeutralLight;
border-radius: $borderRadius;
font-size: inherit;
color: $colorText;

&::before,
&::after {
content: '';
position: absolute;
}
}
&.rc-tooltip-placement-top {
:global .rc-tooltip-arrow {
border-top-color: black;

&::after {
z-index: 1;
}

&[data-popper-placement^='top'] {
&::before,
&::after {
border-left: $tooltipArrowSize solid transparent;
border-right: $tooltipArrowSize solid transparent;
top: 100%;
left: 50%;
margin-left: -$tooltipArrowSize;
}

&::before {
border-top: $tooltipArrowSize solid $colorNeutralLight;
}

&::after {
border-top: $tooltipArrowSize solid $colorNeutralLighter;
transform: translateY(-2px);
}
}

&[data-popper-placement^='right'] {
&::before,
&::after {
border-top: $tooltipArrowSize solid transparent;
border-bottom: $tooltipArrowSize solid transparent;
top: 50%;
left: 0%;
margin-top: -$tooltipArrowSize;
}

&::before {
border-right: $tooltipArrowSize solid $colorNeutralLight;
margin-left: -$tooltipArrowSize;
}

&::after {
border-right: $tooltipArrowSize solid $colorNeutralLighter;
transform: translateX(-4px);
}
}

&[data-popper-placement^='bottom'] {
&::before,
&::after {
border-left: $tooltipArrowSize solid transparent;
border-right: $tooltipArrowSize solid transparent;
top: 0;
left: 50%;
margin-left: -$tooltipArrowSize;
}

&::before {
border-bottom: $tooltipArrowSize solid $colorNeutralLight;
margin-top: -$tooltipArrowSize;
}

&::after {
border-bottom: $tooltipArrowSize solid $colorNeutralLighter;
transform: translateY(-4px);
}
}

&[data-popper-placement^='left'] {
&::before,
&::after {
border-top: $tooltipArrowSize solid transparent;
border-bottom: $tooltipArrowSize solid transparent;
top: 50%;
left: 100%;
margin-top: -$tooltipArrowSize;
}

&::before {
border-left: $tooltipArrowSize solid $colorNeutralLight;
}

&::after {
border-left: $tooltipArrowSize solid $colorNeutralLighter;
transform: translateX(-2px);
}
}
}
}
99 changes: 70 additions & 29 deletions packages/ui/src/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,78 @@
import React, { FC, ReactElement } from 'react'
import RCTooltip from 'rc-tooltip'
import { TooltipProps as RCTooltipProps } from 'rc-tooltip/lib/Tooltip'

import 'rc-tooltip/assets/bootstrap.css'
import React, { FC, ReactNode, useCallback, useState } from 'react'
import { usePopper } from 'react-popper'

import styles from './Tooltip.module.scss'

export type TooltipProps = RCTooltipProps & {
wrapMaxWidth?: boolean
wrapElement?: boolean
export type TooltipProps = {
content: ReactNode
id?: string
disabled?: boolean
offset?: number
placement?: 'top' | 'right' | 'bottom' | 'left'
}

export const Tooltip: FC<TooltipProps & { children: ReactElement }> = ({
overlay,
wrapMaxWidth = true,
wrapElement = false,
children,
...props
}) => {
return overlay ? (
<RCTooltip
overlay={wrapMaxWidth ? <div className={styles.wrapper}>{overlay}</div> : overlay}
overlayClassName={styles.overlay}
{...props}
>
{wrapElement ? (
// TODO: remove once https://github.com/react-component/tooltip/issues/18#issuecomment-411476678 has been resolved
<div style={{ display: 'inline-block' }}>{children}</div>
) : (
children
/**
* ### Purpose
* Useful when you need to display additional information/actionable content.
* A tooltip with this content is displayed when user hovers over a target element.
*
* ### General info
* - Tooltip has four available placements (top, right, bottom and left).
* - Tooltip automatically detects viewport overflow and changes placement to prevent it.
* - Offset (space between a target and tooltip elements) can be also configured via "offset" property.
* - It's recommended set the "id" parameter which will be assigned to the tooltip. This id parameter can be then used as a value of "aria-labelledby" attribute.
*
* ### Usage
* Wrap the target element with Tooltip component and use the "content" property to define what should be displayed inside the tooltip.
*/
export const Tooltip: FC<TooltipProps> = ({ content, disabled = false, offset = 10, placement = 'top', children, ...props }) => {
const [isShown, setShown] = useState<boolean>(false)
const [hideTimeout, setHideTimeout] = useState<NodeJS.Timeout | null>(null)
const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(null)
const [popperElement, setPopperElement] = useState<HTMLSpanElement | null>(null)

const { styles: popperStyles, attributes: popperAttributes } = usePopper(referenceElement, popperElement, {
placement,
modifiers: [
{
name: 'offset',
options: {
offset: [0, offset],
},
},
],
})

const onMouseEnter = useCallback(() => {
if (hideTimeout !== null) {
clearTimeout(hideTimeout)
}

setShown(true)
}, [hideTimeout])

const onMouseLeave = useCallback(() => {
setHideTimeout(setTimeout(() => setShown(false), 100))
}, [])

return (
<div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} className={styles.container}>
<div className={styles.reference} ref={setReferenceElement}>
{children}
</div>

{isShown && !disabled && (
<span
role="tooltip"
ref={setPopperElement}
className={styles.overlay}
style={popperStyles.popper}
{...popperAttributes.popper}
{...props}
>
{content}
</span>
)}
</RCTooltip>
) : (
children
</div>
)
}

0 comments on commit 963d6ff

Please sign in to comment.