Skip to content

Commit

Permalink
Merge pull request #1048 from MoveRoad/feat/add-border-option
Browse files Browse the repository at this point in the history
Add "border" option including arrow
  • Loading branch information
gabrieljablonski committed Jul 3, 2023
2 parents 2ce8c44 + cd0a869 commit fc7e41a
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 5 deletions.
3 changes: 2 additions & 1 deletion docs/docs/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,14 @@ import { Tooltip } from 'react-tooltip';
| `closeOnEsc` | `boolean` | no | `false` | `true` `false` | Pressing escape key will close the tooltip |
| `closeOnScroll` | `boolean` | no | `false` | `true` `false` | Scrolling will close the tooltip (for this to work, scroll element must be either the root html tag, the tooltip parent, or the anchor parent) |
| `closeOnEsc` | `boolean` | no | `false` | `true` `false` | Resizing the window will close the tooltip |
| `style` | `CSSProperties` | no | | a React inline style | Add inline styles directly to the tooltip |
| `style` | `CSSProperties` | no | | a CSS style object | Add inline styles directly to the tooltip |
| `position` | `{ x: number; y: number }` | no | | any `number` value for both `x` and `y` | Override the tooltip position on the DOM |
| `isOpen` | `boolean` | no | handled by internal state | `true` `false` | The tooltip can be controlled or uncontrolled, this attribute can be used to handle show and hide tooltip outside tooltip (can be used **without** `setIsOpen`) |
| `setIsOpen` | `function` | no | | | The tooltip can be controlled or uncontrolled, this attribute can be used to handle show and hide tooltip outside tooltip |
| `afterShow` | `function` | no | | | A function to be called after the tooltip is shown |
| `afterHide` | `function` | no | | | A function to be called after the tooltip is hidden |
| `middlewares` | `Middleware[]` | no | | array of valid `floating-ui` middlewares | Allows for advanced customization. Check the [`floating-ui` docs](https://floating-ui.com/docs/middleware) for more information |
| `border` | `CSSProperties['border']` | no | | a CSS border style | Change the style of the tooltip border (including the arrow) |

### Envs

Expand Down
3 changes: 3 additions & 0 deletions src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const Tooltip = ({
setIsOpen,
activeAnchor,
setActiveAnchor,
border,
}: ITooltip) => {
const tooltipRef = useRef<HTMLElement>(null)
const tooltipArrowRef = useRef<HTMLElement>(null)
Expand Down Expand Up @@ -237,6 +238,7 @@ const Tooltip = ({
tooltipArrowReference: tooltipArrowRef.current,
strategy: positionStrategy,
middlewares,
border,
}).then((computedStylesData) => {
if (Object.keys(computedStylesData.tooltipStyles).length) {
setInlineStyles(computedStylesData.tooltipStyles)
Expand Down Expand Up @@ -503,6 +505,7 @@ const Tooltip = ({
tooltipArrowReference: tooltipArrowRef.current,
strategy: positionStrategy,
middlewares,
border,
}).then((computedStylesData) => {
if (!mounted.current) {
// invalidate computed positions after remount
Expand Down
1 change: 1 addition & 0 deletions src/components/Tooltip/TooltipTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,5 @@ export interface ITooltip {
afterHide?: () => void
activeAnchor: HTMLElement | null
setActiveAnchor: (anchor: HTMLElement | null) => void
border?: CSSProperties['border']
}
16 changes: 16 additions & 0 deletions src/components/TooltipController/TooltipController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const TooltipController = ({
style,
position,
isOpen,
border,
setIsOpen,
afterShow,
afterHide,
Expand Down Expand Up @@ -235,6 +236,20 @@ const TooltipController = ({
}
}, [anchorRefs, providerActiveAnchor, activeAnchor, anchorId, anchorSelect])

useEffect(() => {
if (process.env.NODE_ENV === 'production') {
return
}
if (style?.border) {
// eslint-disable-next-line no-console
console.warn('[react-tooltip] Do not set `style.border`. Use `border` prop instead.')
}
if (border && !CSS.supports('border', `${border}`)) {
// eslint-disable-next-line no-console
console.warn(`[react-tooltip] "${border}" is not a valid \`border\`.`)
}
}, [])

/**
* content priority: children < render or content < html
* children should be lower priority so that it can be used as the "default" content
Expand Down Expand Up @@ -283,6 +298,7 @@ const TooltipController = ({
style,
position,
isOpen,
border,
setIsOpen,
afterShow,
afterHide,
Expand Down
11 changes: 9 additions & 2 deletions src/components/TooltipController/TooltipControllerTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ export interface ITooltipController {
style?: CSSProperties
position?: IPosition
isOpen?: boolean
/**
* @description see https://developer.mozilla.org/en-US/docs/Web/CSS/border.
*
* Adding a border with width > 3px, or with `em/cm/rem/...` instead of `px`
* might break the tooltip arrow positioning.
*/
border?: CSSProperties['border']
setIsOpen?: (value: boolean) => void
afterShow?: () => void
afterHide?: () => void
Expand All @@ -69,8 +76,8 @@ declare module 'react' {
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
'data-tooltip-id'?: string
'data-tooltip-place'?: PlacesType
'data-tooltip-content'?: string
'data-tooltip-html'?: string
'data-tooltip-content'?: string | null
'data-tooltip-html'?: string | null
'data-tooltip-variant'?: VariantType
'data-tooltip-offset'?: number
'data-tooltip-wrapper'?: WrapperType
Expand Down
2 changes: 2 additions & 0 deletions src/utils/compute-positions-types.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CSSProperties } from 'react'
import type { Middleware } from '../components/Tooltip/TooltipTypes'

export interface IComputePositions {
Expand All @@ -20,4 +21,5 @@ export interface IComputePositions {
offset?: number
strategy?: 'absolute' | 'fixed'
middlewares?: Middleware[]
border?: CSSProperties['border']
}
28 changes: 26 additions & 2 deletions src/utils/compute-positions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const computeTooltipPosition = async ({
offset: offsetValue = 10,
strategy = 'absolute',
middlewares = [offset(Number(offsetValue)), flip(), shift({ padding: 5 })],
border,
}: IComputePositions) => {
if (!elementReference) {
// elementReference can be null or undefined and we will not compute the position
Expand All @@ -31,7 +32,7 @@ export const computeTooltipPosition = async ({
strategy,
middleware,
}).then(({ x, y, placement, middlewareData }) => {
const styles = { left: `${x}px`, top: `${y}px` }
const styles = { left: `${x}px`, top: `${y}px`, border }

const { x: arrowX, y: arrowY } = middlewareData.arrow ?? { x: 0, y: 0 }

Expand All @@ -43,12 +44,35 @@ export const computeTooltipPosition = async ({
left: 'right',
}[placement.split('-')[0]] ?? 'bottom'

const borderSide =
border &&
{
top: { borderBottom: border, borderRight: border },
right: { borderBottom: border, borderLeft: border },
bottom: { borderTop: border, borderLeft: border },
left: { borderTop: border, borderRight: border },
}[placement.split('-')[0]]

let borderWidth = 0
if (border) {
const match = `${border}`.match(/(\d+)px/)
if (match?.[1]) {
borderWidth = Number(match[1])
} else {
/**
* this means `border` was set without `width`, or non-px value
*/
borderWidth = 1
}
}

const arrowStyle = {
left: arrowX != null ? `${arrowX}px` : '',
top: arrowY != null ? `${arrowY}px` : '',
right: '',
bottom: '',
[staticSide]: '-4px',
...borderSide,
[staticSide]: `-${4 + borderWidth}px`,
}

return { tooltipStyles: styles, tooltipArrowStyles: arrowStyle, place: placement }
Expand Down

0 comments on commit fc7e41a

Please sign in to comment.