Skip to content

Commit

Permalink
feat(tooltip): re-implement new tooltip with positioner
Browse files Browse the repository at this point in the history
  • Loading branch information
jigsawye committed Mar 26, 2019
1 parent 640de09 commit 07bc232
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 6 deletions.
101 changes: 101 additions & 0 deletions docs/lab/Tooltip.mdx
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} />
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.3.3",
"@types/jest": "^24.0.11",
"@types/lodash.debounce": "^4.0.6",
"@types/ramda": "^0.26.3",
"@types/react": "16.8.8",
"@types/react-dom": "^16.8.2",
Expand Down Expand Up @@ -92,7 +93,7 @@
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-icons": "^3.5.0",
"react-spring": "^8.0.17",
"react-spring": "^8.0.18",
"react-testing-library": "^6.0.1",
"rollup": "^1.6.1",
"rollup-plugin-babel": "^4.3.2",
Expand Down
4 changes: 3 additions & 1 deletion packages/tailor-ui-lab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
"access": "public"
},
"dependencies": {
"polished": "^3.0.3"
"lodash.debounce": "^4.0.8",
"polished": "^3.0.3",
"styled-system": "^4.0.6"
}
}
188 changes: 188 additions & 0 deletions packages/tailor-ui-lab/src/Tooltip/Tooltip.tsx
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;
3 changes: 3 additions & 0 deletions packages/tailor-ui-lab/src/Tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default } from './Tooltip';

export * from './Tooltip';
39 changes: 39 additions & 0 deletions packages/tailor-ui-lab/src/Tooltip/styles.tsx
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',
};
2 changes: 2 additions & 0 deletions packages/tailor-ui-lab/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export * from './Mention';

export { default as Tabs } from './Tabs';
export * from './Tabs';

export { default as Tooltip } from './Tooltip';
Loading

0 comments on commit 07bc232

Please sign in to comment.