Skip to content

Commit

Permalink
refactor(tooltip): updates tooltip to use positioning engine hook
Browse files Browse the repository at this point in the history
  • Loading branch information
dzucconi committed Jun 10, 2021
1 parent d528df2 commit 2be3906
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 70 deletions.
47 changes: 43 additions & 4 deletions packages/palette/src/elements/Tooltip/Tooltip.story.tsx
@@ -1,6 +1,7 @@
import React from "react"
import { States } from "storybook-states"
import { HelpIcon } from "../../svgs"
import { Position, POSITION } from "../../utils/usePosition"
import { Text } from "../Text"
import { Tooltip, TooltipProps } from "./Tooltip"

Expand All @@ -15,21 +16,59 @@ export const Default = () => {
return (
<States<Partial<TooltipProps>>
states={[
{ placement: "top" },
{ placement: "bottom" },
{ placement: "top", width: 600 },
{ placement: "top-start" },
{ placement: "bottom", width: 600 },
{ placement: "bottom", visible: true },
]}
>
<Tooltip content={CONTENT} display="block">
<Text variant="xs" textAlign="center">
<Text
variant="xs"
textAlign="center"
p={1}
bg="black100"
color="white100"
>
This text has a tooltip
</Text>
</Tooltip>
</States>
)
}

export const Placement = () => {
return (
<States<Partial<TooltipProps>>
states={Object.keys(POSITION).map((placement) => ({
placement: placement as Position,
}))}
>
{(props) => {
return (
<Tooltip
content={JSON.stringify(props)}
visible
display="block"
maxWidth="50%"
mx="auto"
{...props}
>
<Text
variant="xs"
textAlign="center"
p={1}
bg="black100"
color="white100"
>
{JSON.stringify(props)}
</Text>
</Tooltip>
)
}}
</States>
)
}

export const IconExample = () => {
return (
<Text variant="xs" display="flex" alignItems="center" lineHeight={1}>
Expand Down
84 changes: 18 additions & 66 deletions packages/palette/src/elements/Tooltip/Tooltip.tsx
@@ -1,15 +1,16 @@
import React, { useEffect, useRef, useState } from "react"
import React, { useRef, useState } from "react"
import styled from "styled-components"
import { DROP_SHADOW } from "../../helpers"
import { useThemeConfig } from "../../Theme"
import { Position, usePosition } from "../../utils/usePosition"
import { Box, BoxProps } from "../Box"
import { Text, TextVariant } from "../Text"

export interface TooltipProps
extends BoxProps,
React.HTMLAttributes<HTMLDivElement> {
content: React.ReactNode
placement?: "top" | "bottom"
placement?: Position
size?: "sm" | "lg"
width?: number
visible?: boolean
Expand All @@ -23,61 +24,14 @@ export const Tooltip: React.FC<TooltipProps> = ({
content: _content,
size = "lg",
width = 230,
placement = "top",
placement = "bottom-end",
visible,
...rest
}) => {
const innerWrapper = useRef<HTMLDivElement | null>(null)
const tooltipRef = useRef<HTMLDivElement | null>(null)
const anchorRef = useRef<HTMLDivElement | null>(null)

const [active, setActive] = useState(false)
const [position, setPosition] = useState({
left: 0,
right: null,
centered: true,
})

const computeTipPosition = () => {
if (!innerWrapper.current) return

let left = 0
let right = null
let centered = false

const inner = innerWrapper.current.getBoundingClientRect()

left = inner.width / 2
centered = true
const spillOver = width / 2 - left

if (spillOver > inner.left) {
centered = false
left = 0
right = null
}

if (spillOver > window.innerWidth - inner.right) {
centered = false
left = null
right = 0
}

return {
centered,
left,
right,
}
}

useEffect(() => {
const handler = () => setPosition(computeTipPosition())

handler()
window.addEventListener("resize", handler)

return () => {
window.removeEventListener("resize", handler)
}
}, [])

const handleClick = () => {
setActive((prevActive) => !prevActive)
Expand All @@ -98,6 +52,14 @@ export const Tooltip: React.FC<TooltipProps> = ({
v3: { variant: "xs" as TextVariant },
})

usePosition({
anchorRef,
tooltipRef,
position: placement,
offset: 10,
active: visible ?? active,
})

return (
<Box
tabIndex={1}
Expand All @@ -106,37 +68,27 @@ export const Tooltip: React.FC<TooltipProps> = ({
onMouseOut={deactivate}
onFocus={activate}
onBlur={deactivate}
position="relative"
// TODO: Should avoid making assumptions about display
display="inline-block"
{...rest}
>
<Tip
p={size === "sm" ? 0.5 : 2}
width={width}
bg="white100"
ref={tooltipRef as any}
zIndex={1}
{...(visible
? // If there's a visible prop being passed; use that
{ opacity: visible ? 1 : 0 }
: // Otherwise use the active state
{ opacity: active ? 1 : 0 })}
// Positioning
{...{
bottom: { top: "100%", mt: 0.5 },
top: { bottom: "100%", mb: 0.5 },
}[placement]}
left={position.left ?? "auto"}
right={position.right ?? "auto"}
style={{
transform: position.centered ? "translateX(-50%)" : "none",
}}
>
<Text variant={tokens.variant} color="black60">
{content}
</Text>
</Tip>

<Box ref={innerWrapper as any}>{children}</Box>
<Box ref={anchorRef as any}>{children}</Box>
</Box>
)
}
Expand All @@ -157,6 +109,6 @@ const Tip = styled(Box)`
transition: opacity 250ms ease-out;
text-align: left;
box-shadow: ${DROP_SHADOW};
pointer-events: none;
cursor: default;
pointer-events: none;
`

0 comments on commit 2be3906

Please sign in to comment.