Skip to content

Commit 5d4c311

Browse files
feat: border typing/validation/compute position
1 parent c4be60b commit 5d4c311

File tree

6 files changed

+70
-6
lines changed

6 files changed

+70
-6
lines changed

src/components/Tooltip/TooltipTypes.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,5 @@ export interface ITooltip {
7777
afterHide?: () => void
7878
activeAnchor: HTMLElement | null
7979
setActiveAnchor: (anchor: HTMLElement | null) => void
80-
border?: string | null
80+
border?: CSSProperties['border']
8181
}

src/components/TooltipController/TooltipController.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
} from 'components/Tooltip/TooltipTypes'
1313
import { useTooltip } from 'components/TooltipProvider'
1414
import { TooltipContent } from 'components/TooltipContent'
15+
import { cssAttrIsValid } from 'utils/css-attr-is-valid'
1516
import type { ITooltipController } from './TooltipControllerTypes'
1617

1718
const TooltipController = ({
@@ -236,6 +237,24 @@ const TooltipController = ({
236237
}
237238
}, [anchorRefs, providerActiveAnchor, activeAnchor, anchorId, anchorSelect])
238239

240+
useEffect(() => {
241+
if (process.env.NODE_ENV === 'production') {
242+
return
243+
}
244+
if (style?.border) {
245+
// eslint-disable-next-line no-console
246+
console.warn('[react-tooltip] Do not set `style.border`. Use `border` prop instead.')
247+
}
248+
if (border) {
249+
if (!cssAttrIsValid('border', border)) {
250+
// eslint-disable-next-line no-console
251+
console.warn(
252+
`[react-tooltip] "${border}" is not a valid \`border\`. See https://developer.mozilla.org/en-US/docs/Web/CSS/border`,
253+
)
254+
}
255+
}
256+
}, [])
257+
239258
/**
240259
* content priority: children < render or content < html
241260
* children should be lower priority so that it can be used as the "default" content

src/components/TooltipController/TooltipControllerTypes.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,13 @@ export interface ITooltipController {
6060
style?: CSSProperties
6161
position?: IPosition
6262
isOpen?: boolean
63-
border?: string | null
63+
/**
64+
* @description see https://developer.mozilla.org/en-US/docs/Web/CSS/border.
65+
*
66+
* Adding a border with width > 3px, or with `em/cm/rem/...` instead of `px`
67+
* might break the tooltip arrow positioning.
68+
*/
69+
border?: CSSProperties['border']
6470
setIsOpen?: (value: boolean) => void
6571
afterShow?: () => void
6672
afterHide?: () => void

src/utils/compute-positions-types.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CSSProperties } from 'react'
12
import type { Middleware } from '../components/Tooltip/TooltipTypes'
23

34
export interface IComputePositions {
@@ -8,5 +9,5 @@ export interface IComputePositions {
89
offset?: number
910
strategy?: 'absolute' | 'fixed'
1011
middlewares?: Middleware[]
11-
border?: string | null
12+
border?: CSSProperties['border']
1213
}

src/utils/compute-positions.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const computeTooltipPosition = async ({
99
offset: offsetValue = 10,
1010
strategy = 'absolute',
1111
middlewares = [offset(Number(offsetValue)), flip(), shift({ padding: 5 })],
12-
border = null,
12+
border,
1313
}: IComputePositions) => {
1414
if (!elementReference) {
1515
// elementReference can be null or undefined and we will not compute the position
@@ -53,13 +53,26 @@ export const computeTooltipPosition = async ({
5353
left: { borderTop: border, borderRight: border },
5454
}[placement.split('-')[0]]
5555

56+
let borderWidth = 0
57+
if (border) {
58+
const match = `${border}`.match(/(\d+)px/)
59+
if (match?.[1]) {
60+
borderWidth = Number(match[1])
61+
} else {
62+
/**
63+
* this means `border` was set without `width`, or non-px value
64+
*/
65+
borderWidth = 1
66+
}
67+
}
68+
5669
const arrowStyle = {
5770
left: arrowX != null ? `${arrowX}px` : '',
5871
top: arrowY != null ? `${arrowY}px` : '',
5972
right: '',
6073
bottom: '',
61-
...{ ...borderSide },
62-
[staticSide]: border ? '-5px' : '-4px',
74+
...borderSide,
75+
[staticSide]: `-${4 + borderWidth}px`,
6376
}
6477

6578
return { tooltipStyles: styles, tooltipArrowStyles: arrowStyle, place: placement }

src/utils/css-attr-is-valid.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export const cssAttrIsValid = (attr: string, value: unknown) => {
2+
const iframe = document.createElement('iframe')
3+
Object.apply(iframe.style, {
4+
display: 'none',
5+
// in case `display: none` not supported
6+
width: '0px',
7+
height: '0px',
8+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9+
} as any)
10+
document.body.appendChild(iframe)
11+
if (!iframe.contentDocument) {
12+
return true
13+
}
14+
const style = iframe.contentDocument.createElement('style')
15+
style.innerHTML = `.test-css { ${attr}: ${value}; }`
16+
iframe.contentDocument.head.appendChild(style)
17+
const { sheet } = style
18+
if (!sheet) {
19+
return true
20+
}
21+
const result = sheet.cssRules[0].cssText
22+
iframe.remove()
23+
const match = result.match(new RegExp(`${attr}:`))
24+
return !!match
25+
}

0 commit comments

Comments
 (0)