Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions src/popover/src/Popover.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Position, Positioner } from '../../positioner'
import { Tooltip } from '../../tooltip'
import PopoverStateless from './PopoverStateless'

export default class Popover extends Component {
Expand Down Expand Up @@ -237,12 +238,16 @@ export default class Popover extends Component {

renderTarget = ({ getRef, isShown }) => {
const { children } = this.props
const isTooltipInside = children && children.type === Tooltip

const getTargetRef = ref => {
this.targetRef = ref
getRef(ref)
}

/**
* When a function is passed, you can control the Popover manually.
*/
if (typeof children === 'function') {
return children({
toggle: this.toggle,
Expand All @@ -251,13 +256,39 @@ export default class Popover extends Component {
})
}

return React.cloneElement(children, {
const popoverTargetProps = {
onClick: this.toggle,
onKeyDown: this.handleKeyDown,
innerRef: getTargetRef,
role: 'button',
'aria-expanded': isShown,
'aria-haspopup': true
}

/**
* Tooltips can be used within a Popover (not the other way around)
* In this case the children is the Tooltip instead of a button.
* Pass the properties to the Tooltip and let the Tooltip
* add the properties to the target.
*/
if (isTooltipInside) {
return React.cloneElement(children, {
popoverProps: {
getTargetRef,
isShown,

// These propeties will be spread as `popoverTargetProps`
// in the Tooltip component.
...popoverTargetProps
}
})
}

/**
* With normal usage only popover props end up on the target.
*/
return React.cloneElement(children, {
innerRef: getTargetRef,
...popoverTargetProps
})
}

Expand Down
37 changes: 36 additions & 1 deletion src/popover/stories/index.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import React from 'react'
import PropTypes from 'prop-types'
import Box from 'ui-box'
import { Popover } from '../../popover'
import { Tooltip } from '../../tooltip'
import { TextInputField } from '../../text-input'
import { Pane } from '../../layers'
import { Text } from '../../typography'
import { Heading, Paragraph, Text } from '../../typography'
import { Button } from '../../buttons'
import { Position } from '../../positioner'
import { Icon, IconNames } from '../../icon'
Expand Down Expand Up @@ -186,3 +187,37 @@ storiesOf('popover', module)
</Popover>
</Box>
))
.add('Popover with tooltip', () => (
<Box padding={120}>
{(() => {
document.body.style.margin = '0'
document.body.style.height = '100vh'
})()}
<Popover content={<PopoverContentWithTextInput />}>
<Tooltip content="Click me">
<Button marginRight={20}>Tooltip Card + Popover</Button>
</Tooltip>
</Popover>
<Popover content={<PopoverContentWithTextInput />}>
<Tooltip
appearance="card"
content={
<React.Fragment>
<Heading>Heading</Heading>
<Paragraph color="muted" marginTop={4}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
</Paragraph>
</React.Fragment>
}
statelessProps={{
paddingY: 24,
paddingX: 24,
maxWidth: 280
}}
>
<Button>Tooltip + Popover</Button>
</Tooltip>
</Popover>
</Box>
))
20 changes: 15 additions & 5 deletions src/theme/src/default-theme/component-specific/getTooltipProps.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import tinycolor from 'tinycolor2'
import palette from '../foundational-styles/palette'

const getTooltipProps = () => {
return {
backgroundColor: tinycolor(palette.neutral.base)
.setAlpha(0.95)
.toString()
const getTooltipProps = appearance => {
switch (appearance) {
case 'card':
return {
backgroundColor: 'white',
elevation: 3
}
case 'default':
default:
return {
color: 'white',
backgroundColor: tinycolor(palette.neutral.base)
.setAlpha(0.95)
.toString()
}
}
}

Expand Down
64 changes: 59 additions & 5 deletions src/tooltip/src/Tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ let idCounter = 0

export default class Tooltip extends PureComponent {
static propTypes = {
/**
* The appearance of the tooltip.
*/
appearance: PropTypes.oneOf(['default', 'card']).isRequired,

/**
* The position the Popover is on.
*/
Expand All @@ -31,7 +36,7 @@ export default class Tooltip extends PureComponent {
/**
* The target button of the Tooltip.
*/
children: PropTypes.node.isRequried,
children: PropTypes.node.isRequired,

/**
* Properties passed through to the Tooltip.
Expand All @@ -40,6 +45,7 @@ export default class Tooltip extends PureComponent {
}

static defaultProps = {
appearance: 'default',
position: Position.BOTTOM,
hideDelay: 120
}
Expand Down Expand Up @@ -78,16 +84,56 @@ export default class Tooltip extends PureComponent {
renderTarget = ({ getRef }) => {
const { children } = this.props

return React.cloneElement(children, {
const tooltipTargetProps = {
onMouseEnter: this.show,
onMouseLeave: this.hide,
'aria-describedby': this.state.id,
'aria-describedby': this.state.id
}

/**
* Tooltips can be used within a Popover (not the other way around)
* When a Tooltip is used within a Popover, the Popover passes
* its props to the Tooltip in a `popoverProps` object.
*/
// eslint-disable-next-line react/prop-types
if (this.props.popoverProps) {
const {
// eslint-disable-next-line react/prop-types
getTargetRef,
// eslint-disable-next-line react/prop-types
isShown,
...popoverTargetProps
} = this.props.popoverProps

return React.cloneElement(children, {
// Add the Popover props to the target.
...popoverTargetProps,
// Add the Tooltip props to the target.
...tooltipTargetProps,

innerRef: ref => {
// Get the ref for the Tooltip.
getRef(ref)
// Pass the ref to the Popover.
getTargetRef(ref)
}
})
}

/**
* With normal usage only the props for a Tooltip are passed to the target.
*/
return React.cloneElement(children, {
...tooltipTargetProps,
innerRef: ref => {
getRef(ref)
}
})
}

isPopoverShown = () =>
this.props.popoverProps && this.props.popoverProps.isShown

handleMouseEnterTarget = () => {
this.setState({
isShownByTarget: true
Expand All @@ -101,10 +147,17 @@ export default class Tooltip extends PureComponent {
}

render() {
const { isShown, content, position, statelessProps } = this.props
const {
appearance,
isShown,
content,
position,
statelessProps
} = this.props
const { isShown: stateIsShown, isShownByTarget } = this.state

const shown = isShown || stateIsShown || isShownByTarget
const shown =
(isShown || stateIsShown || isShownByTarget) && !this.isPopoverShown()

return (
<Positioner
Expand All @@ -118,6 +171,7 @@ export default class Tooltip extends PureComponent {
{({ css, style, state, getRef }) => (
<TooltipStateless
id={this.state.id}
appearance={appearance}
innerRef={ref => getRef(ref)}
data-state={state}
css={css}
Expand Down
19 changes: 13 additions & 6 deletions src/tooltip/src/TooltipStateless.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Box from 'ui-box'
import { Pane } from '../../layers'
import { Paragraph } from '../../typography'
import { withTheme } from '../../theme'

class TooltipStateless extends PureComponent {
static propTypes = {
children: PropTypes.node,

/**
* The appearance of the tooltip.
*/
appearance: PropTypes.oneOf(['default', 'card']).isRequired,

/**
* Theme provided by ThemeProvider.
*/
theme: PropTypes.object.isRequired
}

render() {
const { theme, children, ...props } = this.props
const { theme, children, appearance, ...props } = this.props
const { color, ...themedProps } = theme.getTooltipProps(appearance)

let child
if (typeof children === 'string') {
child = (
<Paragraph color="white" size={400}>
<Paragraph color={color} size={400}>
{children}
</Paragraph>
)
Expand All @@ -28,16 +35,16 @@ class TooltipStateless extends PureComponent {
}

return (
<Box
{...theme.getTooltipProps()}
<Pane
borderRadius={3}
paddingX={8}
paddingY={4}
maxWidth={240}
{...themedProps}
{...props}
>
{child}
</Box>
</Pane>
)
}
}
Expand Down