-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tooltip): re-implement new tooltip with positioner
- Loading branch information
Showing
8 changed files
with
362 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
--- | ||
name: Tooltip | ||
menu: Lab | ||
route: /labs/tooltip | ||
--- | ||
|
||
import { PropsTable, Playground } from 'docz'; | ||
import { Button, Space, Flex, Position } from 'tailor-ui'; | ||
import { Tooltip } from '@tailor-ui/lab'; | ||
|
||
# Tooltip | ||
|
||
A simple text popup tip. | ||
|
||
## When To Use | ||
|
||
- The tip is shown on mouse enter, and is hidden on mouse leave. The Tooltip doesn't support complex text or operations. | ||
- To provide an explanation of a `button/text/operation`. It's often used instead of the html `title` attribute. | ||
|
||
## Examples | ||
|
||
```js | ||
import { Position } from 'tailor-ui'; | ||
import { Tooltip } from '@tailor-ui/lab'; | ||
``` | ||
|
||
### Basic | ||
|
||
<Playground> | ||
<Tooltip content="Tooltip Content"> | ||
<Button>Hover me</Button> | ||
</Tooltip> | ||
</Playground> | ||
|
||
### Position | ||
|
||
<Playground> | ||
<Flex justifyContent="space-between" mx="4"> | ||
<Tooltip position={Position.TOP} content={<span>Tooltip Content</span>}> | ||
<Button>Top</Button> | ||
</Tooltip> | ||
|
||
<Tooltip position={Position.RIGHT} content={<span>Tooltip Content</span>}> | ||
<Button>Right</Button> | ||
</Tooltip> | ||
|
||
<Tooltip position={Position.BOTTOM} content={<span>Tooltip Content</span>}> | ||
<Button>Bottom</Button> | ||
</Tooltip> | ||
|
||
<Tooltip position={Position.LEFT} content={<span>Tooltip Content</span>}> | ||
<Button>Left</Button> | ||
</Tooltip> | ||
|
||
</Flex> | ||
</Playground> | ||
|
||
### Use mouseEnterDelay and mouseLeaveDelay | ||
|
||
<Playground> | ||
<Tooltip | ||
position={Position.BOTTOM} | ||
content="Tooltip Content" | ||
mouseEnterDelay={500} | ||
mouseLeaveDelay={500} | ||
> | ||
<Button>Hover me</Button> | ||
</Tooltip> | ||
</Playground> | ||
|
||
### Controlled usage | ||
|
||
<Playground> | ||
{() => { | ||
const [visible, setVisible] = React.useState(false); | ||
|
||
return ( | ||
<Tooltip | ||
content="Tooltip content" | ||
visible={visible} | ||
onVisibleChange={setVisible} | ||
> | ||
<Button>Hover me</Button> | ||
</Tooltip> | ||
); | ||
|
||
}} | ||
|
||
</Playground> | ||
|
||
### With defaultVisible | ||
|
||
<Playground> | ||
<Tooltip content="Tooltip content" defaultVisible> | ||
<Button>Hover me</Button> | ||
</Tooltip> | ||
</Playground> | ||
|
||
## API | ||
|
||
<PropsTable of={Tooltip} /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
import React, { | ||
CSSProperties, | ||
FunctionComponent, | ||
ReactNode, | ||
RefObject, | ||
cloneElement, | ||
forwardRef, | ||
isValidElement, | ||
useRef, | ||
useState, | ||
} from 'react'; | ||
import debounce from 'lodash.debounce'; | ||
import { animated } from 'react-spring'; | ||
|
||
import { Position, Positioner, Positions } from 'tailor-ui'; | ||
|
||
import { StyledTooltip, StyledTooltipProps } from './styles'; | ||
|
||
interface TooltipPopup { | ||
style: CSSProperties; | ||
content: ReactNode; | ||
handleOpen: () => void; | ||
handleClose: () => void; | ||
} | ||
|
||
const TooltipPopup = forwardRef<HTMLDivElement, TooltipPopup>( | ||
function TooltipPopup( | ||
{ style, content, handleOpen, handleClose, ...otherProps }, | ||
ref | ||
) { | ||
return ( | ||
<animated.div style={style} ref={ref}> | ||
<StyledTooltip | ||
onMouseEnter={handleOpen} | ||
onMouseLeave={handleClose} | ||
{...otherProps} | ||
> | ||
{content} | ||
</StyledTooltip> | ||
</animated.div> | ||
); | ||
} | ||
); | ||
|
||
export type TooltipProps = StyledTooltipProps & { | ||
/** | ||
* Whether the floating tooltip card is visible by default. Only support when the trigger is `click` | ||
*/ | ||
defaultVisible?: boolean; | ||
/** | ||
* Whether the floating tooltip card is visible. Only support when the trigger is `click` | ||
*/ | ||
visible?: boolean; | ||
/** | ||
* Callback executed when visibility of the tooltip card is changed | ||
*/ | ||
onVisibleChange?: (visible: boolean) => void; | ||
/** | ||
* The position base on the children component | ||
*/ | ||
position?: Positions; | ||
/** | ||
* A string or react component inside this tooltip. | ||
* If you are using click to trigger, it can be a | ||
* function that with `hide` callback as first argument | ||
*/ | ||
content: ReactNode; | ||
/** | ||
* Delay in milliseconds, before tooltip is shown on mouse enter | ||
*/ | ||
mouseEnterDelay?: number; | ||
/** | ||
* Delay in milliseconds, before tooltip is hidden on mouse leave | ||
*/ | ||
mouseLeaveDelay?: number; | ||
}; | ||
|
||
const Tooltip: FunctionComponent<TooltipProps> = ({ | ||
children, | ||
position = Position.TOP, | ||
content, | ||
defaultVisible = false, | ||
visible: visibleFromProps, | ||
onVisibleChange, | ||
mouseEnterDelay = 0, | ||
mouseLeaveDelay = 200, | ||
}) => { | ||
const childrenRef = useRef(null); | ||
const popupRef = useRef(null); | ||
const cancelEnterDebounce = useRef<null | (() => void)>(); | ||
const cancelLeaveDebounce = useRef<null | (() => void)>(); | ||
const [visibleFromSelf, setVisibleFromSelf] = useState(defaultVisible); | ||
|
||
const hanVisibleFromProps = typeof visibleFromProps !== 'undefined'; | ||
|
||
const visible = hanVisibleFromProps ? visibleFromProps : visibleFromSelf; | ||
|
||
const open = () => { | ||
if (onVisibleChange) { | ||
onVisibleChange(true); | ||
} else { | ||
setVisibleFromSelf(true); | ||
} | ||
}; | ||
|
||
const close = () => { | ||
if (onVisibleChange) { | ||
onVisibleChange(false); | ||
} else { | ||
setVisibleFromSelf(false); | ||
} | ||
}; | ||
|
||
const handleOpen = () => { | ||
if (cancelLeaveDebounce.current) { | ||
cancelLeaveDebounce.current(); | ||
cancelLeaveDebounce.current = null; | ||
} | ||
|
||
if (mouseEnterDelay === 0) { | ||
open(); | ||
} else { | ||
const debounced = debounce(open, mouseEnterDelay); | ||
debounced(); | ||
cancelEnterDebounce.current = debounced.cancel; | ||
} | ||
}; | ||
|
||
const handleClose = () => { | ||
if (cancelEnterDebounce.current) { | ||
cancelEnterDebounce.current(); | ||
cancelEnterDebounce.current = null; | ||
} | ||
|
||
if (mouseLeaveDelay === 0) { | ||
close(); | ||
} else { | ||
const debounced = debounce(close, mouseLeaveDelay); | ||
debounced(); | ||
cancelLeaveDebounce.current = debounced.cancel; | ||
} | ||
}; | ||
|
||
// TODO: compose events | ||
const renderChildren = ({ ref }: { ref: RefObject<HTMLElement> }) => { | ||
if (children instanceof Function) { | ||
return children({ | ||
ref, | ||
bind: { | ||
onMouseEnter: handleOpen, | ||
onMouseLeave: handleClose, | ||
}, | ||
}); | ||
} | ||
|
||
if (!isValidElement<any>(children)) { | ||
return children; | ||
} | ||
|
||
return cloneElement(children, { | ||
ref, | ||
onMouseEnter: handleOpen, | ||
onMouseLeave: handleClose, | ||
}); | ||
}; | ||
|
||
return ( | ||
<Positioner | ||
positionerRef={popupRef} | ||
targetRef={childrenRef} | ||
visible={visible} | ||
position={position} | ||
positioner={({ style }) => ( | ||
<TooltipPopup | ||
ref={popupRef} | ||
style={style} | ||
content={content} | ||
handleOpen={handleOpen} | ||
handleClose={handleClose} | ||
/> | ||
)} | ||
> | ||
{renderChildren} | ||
</Positioner> | ||
); | ||
}; | ||
|
||
export default Tooltip; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { default } from './Tooltip'; | ||
|
||
export * from './Tooltip'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import styled from 'styled-components'; | ||
import { | ||
BorderRadiusProps, | ||
ColorProps, | ||
SpaceProps, | ||
TextAlignProps, | ||
borderRadius, | ||
color, | ||
space, | ||
textAlign, | ||
} from 'styled-system'; | ||
|
||
export type StyledTooltipProps = SpaceProps & | ||
ColorProps & | ||
BorderRadiusProps & | ||
TextAlignProps; | ||
|
||
export const StyledTooltip = styled.div<StyledTooltipProps>` | ||
border: ${p => p.theme.borders.base}; | ||
border-color: ${p => p.theme.colors.primaryDark2}; | ||
opacity: 0.9; | ||
background-color: ${p => p.theme.colors.primaryDark2}; | ||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||
color: ${p => p.theme.colors.light}; | ||
font-size: ${p => p.theme.fontSizes.sm}; | ||
white-space: nowrap; | ||
${space}; | ||
${color}; | ||
${borderRadius}; | ||
${textAlign}; | ||
`; | ||
|
||
StyledTooltip.defaultProps = { | ||
py: 1, | ||
px: 2, | ||
textAlign: 'center', | ||
borderRadius: 'base', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.