Skip to content

Commit 50e87cd

Browse files
Luke BowermanLuke Bowerman
authored andcommitted
Tooltip now supports render props OR cloneElement
Also updates CHANGELOG for 0.7.32
1 parent 94bbcfd commit 50e87cd

File tree

11 files changed

+166
-101
lines changed

11 files changed

+166
-101
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929
- `Banner` fontSize adjusted and external margin removed
3030
- `Button` and `ButtonOutline` horizontal padding on increased, decreased for `ButtonTransparent`
3131
- `DateTimeFormat` uses date-fns to format human-readable date string (rather than built-in browser default functionality)
32-
- `useTooltip` includes a generated id (or passed-in prop value) for the resulting tooltip in the return object
32+
- `Tooltip`
33+
- Now offers a cloneElement version as well as the existing render props version
34+
- Documentation update to reflect new `children` structure
35+
- now supports `aria-describedby`
36+
- `useTooltip` includes a generated id (or passed-in prop value) for the resulting tooltip in the return object
3337
- `MenuDisclosure`, `Banner`, `IconButton`, `ModalHeader` explicitly use their id props to either provide `useTooltip` with an id or to provide another component that uses `useTooltip` with an id to generate the tooltip's id
3438
- Icon used for error states in inputs changed to `CircleInfo`
35-
- Doing this means that tooltip trigger elements can now have an `aria-describedby` property with said id as the value
3639
- Support Warning icon display on Select and SelectMulti inputs
3740
- Refactor use of InputSearch to support more flexible layouts
3841
- Use Babel for building Monorepo ES artifacts

packages/components/src/Button/IconButton.test.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,14 +191,7 @@ test('IconButton built-in tooltip defers to outer tooltip', () => {
191191
const label = 'Mark as my Favorite'
192192
const { container, getByText, getByTitle } = renderWithTheme(
193193
<Tooltip content={tooltip}>
194-
{(tooltipProps) => (
195-
<IconButton
196-
tooltipDisabled
197-
label={label}
198-
icon="Favorite"
199-
{...tooltipProps}
200-
/>
201-
)}
194+
<IconButton tooltipDisabled label={label} icon="Favorite" />
202195
</Tooltip>
203196
)
204197

packages/components/src/Calendar/CalendarNav.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,11 @@ export const CalendarNav: FC<NavbarElementProps> = ({
9292
</NextButtonWrapper>
9393

9494
<Tooltip content="View Current Month">
95-
{(tooltipProps) => (
96-
<ButtonTransparent
97-
{...tooltipProps}
98-
onClick={handleLabelClick}
99-
color="neutral"
100-
>
101-
<Heading as={headingSizeMap(size)} fontWeight="semiBold">
102-
{localeUtils.formatMonthTitle(month, locale)}
103-
</Heading>
104-
</ButtonTransparent>
105-
)}
95+
<ButtonTransparent onClick={handleLabelClick} color="neutral">
96+
<Heading as={headingSizeMap(size)} fontWeight="semiBold">
97+
{localeUtils.formatMonthTitle(month, locale)}
98+
</Heading>
99+
</ButtonTransparent>
106100
</Tooltip>
107101

108102
<PrevButtonWrapper>

packages/components/src/Tooltip/Tooltip.test.tsx

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,23 @@ describe('Tooltip', () => {
5555
test('snapshot', () => {
5656
assertSnapshotShallow(
5757
<Tooltip content="Hello world" isOpen>
58-
{(tooltipProps) => <Button {...tooltipProps}>Example</Button>}
58+
<Button>Example</Button>
5959
</Tooltip>
6060
)
6161
})
6262

6363
test('Tooltip can hide its arrow', () => {
6464
assertSnapshotShallow(
6565
<Tooltip content="Hello world" arrow={false} isOpen>
66-
{(tooltipProps) => <Button {...tooltipProps}>Example</Button>}
66+
<Button>Example</Button>
6767
</Tooltip>
6868
)
6969
})
7070

7171
test('trigger: open on mouseover, close on mouseout', () => {
7272
const tooltip = mountWithTheme(
7373
<Tooltip content="Hello world">
74-
{(tooltipProps) => <Button {...tooltipProps}>Test</Button>}
74+
<Button>Test</Button>
7575
</Tooltip>
7676
)
7777

@@ -89,7 +89,7 @@ describe('Tooltip', () => {
8989
test('close on surface mouseout', () => {
9090
const tooltip = mountWithTheme(
9191
<Tooltip content="Hello world" isOpen>
92-
{(tooltipProps) => <Button {...tooltipProps}>Test</Button>}
92+
<Button>Test</Button>
9393
</Tooltip>
9494
)
9595

@@ -104,7 +104,7 @@ describe('Tooltip', () => {
104104
test('contains content', () => {
105105
const tooltip = mountWithTheme(
106106
<Tooltip content="Hello world">
107-
{(tooltipProps) => <Button {...tooltipProps}>Test</Button>}
107+
<Button>Test</Button>
108108
</Tooltip>
109109
)
110110

@@ -117,7 +117,7 @@ describe('Tooltip', () => {
117117
test('open initially, collapse on mouseout', () => {
118118
const tooltip = mountWithTheme(
119119
<Tooltip content="Hello world" isOpen>
120-
{(tooltipProps) => <Button {...tooltipProps}>Test</Button>}
120+
<Button>Test</Button>
121121
</Tooltip>
122122
)
123123

@@ -134,7 +134,7 @@ describe('Tooltip', () => {
134134
test('supports styling props', () => {
135135
const tooltip = mountWithTheme(
136136
<Tooltip content="Hello world" width="20rem" textAlign="right">
137-
{(tooltipProps) => <Button {...tooltipProps}>Test</Button>}
137+
<Button>Test</Button>
138138
</Tooltip>
139139
)
140140

@@ -153,9 +153,7 @@ describe('Tooltip', () => {
153153
test('tooltip can exceed bounds of containing overlay', () => {
154154
const tooltip = (
155155
<Tooltip content="Great knowledge here!" isOpen>
156-
{(tooltipProps) => (
157-
<Box {...tooltipProps}>I wish I knew more about this...</Box>
158-
)}
156+
<Box>I wish I knew more about this...</Box>
159157
</Tooltip>
160158
)
161159

@@ -171,4 +169,40 @@ describe('Tooltip', () => {
171169

172170
assertSnapshotShallow(popover)
173171
})
172+
173+
test('Render props version works', () => {
174+
const tooltip = mountWithTheme(
175+
<Tooltip content="Hello world">
176+
{(props) => <Button {...props}>Test</Button>}
177+
</Tooltip>
178+
)
179+
180+
const trigger = tooltip.find(Button)
181+
182+
trigger.simulate('mouseover', mouseEventSimulator)
183+
const surface = tooltip.find(OverlaySurface)
184+
expect(surface.exists()).toBeTruthy()
185+
186+
trigger.simulate('mouseout', mouseEventSimulator)
187+
const postMouseoutSurface = tooltip.find(OverlaySurface)
188+
expect(postMouseoutSurface.exists()).toBeFalsy()
189+
})
190+
191+
test('Render props version works', () => {
192+
const tooltip = mountWithTheme(
193+
<Tooltip content="Hello world">
194+
{(props) => <Button {...props}>Test</Button>}
195+
</Tooltip>
196+
)
197+
198+
const trigger = tooltip.find(Button)
199+
200+
trigger.simulate('mouseover', mouseEventSimulator)
201+
const surface = tooltip.find(OverlaySurface)
202+
expect(surface.exists()).toBeTruthy()
203+
204+
trigger.simulate('mouseout', mouseEventSimulator)
205+
const postMouseoutSurface = tooltip.find(OverlaySurface)
206+
expect(postMouseoutSurface.exists()).toBeFalsy()
207+
})
174208
})

packages/components/src/Tooltip/Tooltip.tsx

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@
2727
import { CustomizableAttributes } from '@looker/design-tokens'
2828
import { TextAlignProperty } from 'csstype'
2929
import { Placement } from '@popperjs/core'
30-
import React, { useMemo, useState, Ref, FC, ReactNode } from 'react'
30+
import React, {
31+
cloneElement,
32+
FC,
33+
isValidElement,
34+
useMemo,
35+
useState,
36+
ReactNode,
37+
Ref,
38+
} from 'react'
3139
import { omit } from 'lodash'
3240
import { ModalContext } from '../Modal'
3341
import {
@@ -100,20 +108,22 @@ export interface UseTooltipProps {
100108

101109
export const CustomizableTooltipAttributes: CustomizableAttributes = {}
102110

111+
type TooltipRenderProp = (tooltipProps: {
112+
'aria-describedby': string
113+
onBlur: () => void
114+
onFocus: () => void
115+
onMouseOut: (event: React.MouseEvent<Element, MouseEvent>) => void
116+
onMouseOver: () => void
117+
ref: Ref<any>
118+
}) => ReactNode
119+
103120
export interface TooltipProps extends UseTooltipProps {
104121
content: ReactNode
105122
/**
106123
* Component to wrap. The HOC will listen for mouse events on this component, maintain the
107124
* state of isOpen accordingly, and pass that state into the children or "trigger" element
108125
*/
109-
children: (tooltipProps: {
110-
'aria-describedby': string
111-
onBlur: () => void
112-
onFocus: () => void
113-
onMouseOut: (event: React.MouseEvent<Element, MouseEvent>) => void
114-
onMouseOver: () => void
115-
ref: Ref<any>
116-
}) => ReactNode
126+
children: ReactNode | TooltipRenderProp
117127
}
118128
export function useTooltip({
119129
arrow = true,
@@ -234,17 +244,36 @@ export function useTooltip({
234244
}
235245
}
236246

247+
function isRenderProp(
248+
children: ReactNode | TooltipRenderProp
249+
): children is TooltipRenderProp {
250+
return typeof children === 'function'
251+
}
252+
237253
export const Tooltip: FC<TooltipProps> = ({ children, ...props }) => {
238254
const tooltipProps = useTooltip(props)
239255

240256
const tooltipPropsLabeled = {
241257
...omit(tooltipProps, ['tooltip', 'popperInstanceRef']),
242258
}
243259

260+
let target = children
261+
262+
if (isValidElement(children)) {
263+
target = cloneElement(children, { ...tooltipPropsLabeled })
264+
} else if (isRenderProp(children)) {
265+
target = children(tooltipPropsLabeled)
266+
} else {
267+
// eslint-disable-next-line no-console
268+
console.warn(
269+
`Element "${typeof target}" can't be used as target for Tooltip`
270+
)
271+
}
272+
244273
return (
245274
<>
246275
{tooltipProps.tooltip}
247-
{children(tooltipPropsLabeled)}
276+
{target}
248277
</>
249278
)
250279
}

packages/playground/src/Tooltip/TooltipDemo.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,28 @@
2525
*/
2626

2727
import React from 'react'
28-
import { Box, IconButton, Tooltip } from '@looker/components'
28+
import { SpaceVertical, IconButton, Tooltip } from '@looker/components'
2929

3030
export function TooltipDemo() {
3131
return (
32-
<Box p="xxxlarge">
33-
<Tooltip content="Start editing" placement="top">
34-
{(tooltipProps) => (
35-
<IconButton icon="Edit" label="Edit something" {...tooltipProps} />
36-
)}
37-
</Tooltip>
38-
</Box>
32+
<SpaceVertical m="xxxlarge">
33+
<div>
34+
<Tooltip content="I'm a little teapot">Some Text</Tooltip>
35+
</div>
36+
37+
<div>
38+
<Tooltip content="Start editing" placement="top">
39+
{(tooltipProps) => (
40+
<IconButton icon="Edit" label="Edit something" {...tooltipProps} />
41+
)}
42+
</Tooltip>
43+
</div>
44+
45+
<div>
46+
<Tooltip content="Start editing" placement="top">
47+
<IconButton icon="Edit" label="Edit something" />
48+
</Tooltip>
49+
</div>
50+
</SpaceVertical>
3951
)
4052
}

packages/playground/src/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626
import React, { FC } from 'react'
2727
import { render } from 'react-dom'
2828
import { ComponentsProvider } from '@looker/components'
29-
import { AccordionDemo } from './Accordion/AccordionDemo'
29+
import { TooltipDemo } from './Tooltip/TooltipDemo'
3030

3131
const App: FC = () => {
3232
return (
3333
<ComponentsProvider ie11Support>
34-
<AccordionDemo />
34+
<TooltipDemo />
3535
</ComponentsProvider>
3636
)
3737
}

packages/www/src/MDX/Pre/CodeSandbox.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,16 +196,13 @@ export const ToggleCodeButton: FC<ToggleButtonProps> = ({
196196
const toggleLabel = editorIsVisible ? 'Hide code editor' : 'Show code editor'
197197
return (
198198
<Tooltip content={toggleLabel} placement="left">
199-
{(tooltipProps) => (
200-
<ActionButton
201-
{...tooltipProps}
202-
editorIsVisible={editorIsVisible}
203-
onClick={onClick}
204-
label={toggleLabel}
205-
icon={toggleIcon}
206-
size="xsmall"
207-
/>
208-
)}
199+
<ActionButton
200+
editorIsVisible={editorIsVisible}
201+
onClick={onClick}
202+
label={toggleLabel}
203+
icon={toggleIcon}
204+
size="xsmall"
205+
/>
209206
</Tooltip>
210207
)
211208
}

packages/www/src/documentation/components/actions/icon-button.mdx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,6 @@ You can also use your own `Tooltip` around `IconButton` (this will effectively r
9292

9393
```jsx
9494
<Tooltip content="I'm a little teapot">
95-
{(tooltipProps) => (
96-
<IconButton
97-
{...tooltipProps}
98-
label="Add Something Neat"
99-
icon="Plus"
100-
size="xxsmall"
101-
/>
102-
)}
95+
<IconButton label="Add Something Neat" icon="Plus" size="xxsmall" />
10396
</Tooltip>
10497
```

packages/www/src/documentation/components/overlays/popover.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ If you want to hide the popover arrow you can set the prop `arrow` prop to false
7373
7474
return (
7575
<SpaceVertical>
76-
<Space between>
76+
<Space between width="100%">
7777
<Popover content={popoverContent}>
7878
{(onClick, ref, className) =>
7979
button('Default', onClick, ref, className)
@@ -88,7 +88,7 @@ If you want to hide the popover arrow you can set the prop `arrow` prop to false
8888
{(onClick, ref, className) => button('Left', onClick, ref, className)}
8989
</Popover>
9090
</Space>
91-
<Space>
91+
<Space between width="100%">
9292
<Popover content={popoverContent} placement="bottom-start">
9393
{(onClick, ref, className) =>
9494
button('Bottom Start', onClick, ref, className)

0 commit comments

Comments
 (0)