Skip to content

Commit

Permalink
Merge f2a1c1a into f5ca8c8
Browse files Browse the repository at this point in the history
  • Loading branch information
igorarkhipenko committed Dec 9, 2019
2 parents f5ca8c8 + f2a1c1a commit 0a83c87
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 1 deletion.
104 changes: 104 additions & 0 deletions src/components/Tooltip/Tooltip.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
.Tooltip {
&__container {
background-color: var(--color-neutral-90);
color: var(--color-background);
box-sizing: border-box;
padding: var(--spacing-3x);
border-radius: var(--border-radius);
max-width: 350px;
opacity: 0;
animation: appearing 0.3s forwards;
}

&__arrow {
position: absolute;
}

[x-placement^="top"] &__container {
margin-bottom: var(--spacing-5x);
transform: translateY(-20px);
}
[x-placement^="top"] &__arrow {
border-top: 6px solid var(--color-neutral-90);
border-left: 6px solid transparent;
border-right: 6px solid transparent;
bottom: -6px;
left: calc(50% - 6px);
margin-top: 0;
margin-bottom: 0;
}
[x-placement^="top-start"] &__arrow {
left: var(--spacing-3x);
}
[x-placement^="top-end"] &__arrow {
left: auto;
right: var(--spacing-3x);
}
[x-placement^="bottom"] &__container {
margin-top: var(--spacing-5x);
transform: translateY(20px);
}
[x-placement^="bottom"] &__arrow {
border-bottom: 6px solid var(--color-neutral-90);
border-left: 6px solid transparent;
border-right: 6px solid transparent;
top: -6px;
left: calc(50% - 6px);
margin-top: 0;
margin-bottom: 0;
}
[x-placement^="bottom-start"] &__arrow {
left: var(--spacing-3x);
}
[x-placement^="bottom-end"] &__arrow {
left: auto;
right: var(--spacing-3x);
}
[x-placement^="right"] &__container {
margin-left: var(--spacing-5x);
transform: translateX(20px);
}
[x-placement^="right"] &__arrow {
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-right: 6px solid var(--color-neutral-90);
left: -6px;
top: calc(50% - 6px);
margin-left: 0;
margin-right: 0;
}
[x-placement^="right-start"] &__arrow {
top: var(--spacing-3x);
}
[x-placement^="right-end"] &__arrow {
top: auto;
bottom: var(--spacing-3x);
}
[x-placement^="left"] &__container {
margin-right: var(--spacing-5x);
transform: translateX(-20px);
}
[x-placement^="left"] &__arrow {
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-left: 6px solid var(--color-neutral-90);
right: -6px;
top: calc(50% - 6px);
margin-left: 0;
margin-right: 0;
}
[x-placement^="left-start"] &__arrow {
top: var(--spacing-3x);
}
[x-placement^="left-end"] &__arrow {
top: auto;
bottom: var(--spacing-3x);
}

@keyframes appearing {
100% {
opacity: 1;
transform: translateX(0) translateY(0);
}
}
}
66 changes: 66 additions & 0 deletions src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as React from 'react';
import { PopupPlacement } from '../../constants';
import PopupBase from '../PopupBase';
import styles from './Tooltip.scss';
import bem from '../../utils/bem';

const { block, elem } = bem('Tooltip', styles);

interface Props extends React.HTMLAttributes<HTMLElement> {
/** Anchor component */
children: NonEmptySingleReactNode;
/** Popup component */
content: NonEmptySingleReactNode;
/** Placement of the popup dialog relative to anchor */
placement?: PopupPlacement;
}

const Tooltip: React.FC<Props> = props => {
const { placement, content, children, ...rest } = props;

const createMouseOverHandler = setPopupVisibility => () => {
setPopupVisibility(true);
};

const createMouseLeaveHandler = setPopupVisibility => () => {
setPopupVisibility(false);
};

const renderAnchor = ({ setPopupVisibility }) => {
const mouseEventHandlers = {
onMouseOver: createMouseOverHandler(setPopupVisibility),
onMouseLeave: createMouseLeaveHandler(setPopupVisibility),
};

if (React.isValidElement(children)) {
return React.cloneElement(children, mouseEventHandlers);
}

return <span {...mouseEventHandlers}>{children}</span>;
};

const renderPopup = () => (
<div>
<div {...rest} {...block('container', props)}>
{content}
<div {...elem('arrow', props)} />
</div>
</div>
);

return (
<PopupBase
anchorRenderer={renderAnchor}
popupRenderer={renderPopup}
placement={placement}
/>
);
};

Tooltip.displayName = 'Tooltip';

Tooltip.defaultProps = {
placement: 'bottom',
};

export default Tooltip;
52 changes: 52 additions & 0 deletions src/components/Tooltip/__tests__/Tooltip.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import toJson from 'enzyme-to-json';
import Tooltip from '../Tooltip';

describe('<Tooltip> that renders a Tooltip', () => {
it('should render default Tooltip correctly', () => {
const wrapper = mount(
<Tooltip placement="bottom" content="content">
Hover me
</Tooltip>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
it('should display a tooltip on hover', () => {
const wrapper = mount(
<Tooltip placement="bottom" content="content">
Hover me
</Tooltip>
);

wrapper.childAt(0).simulate('mouseover');
expect(toJson(wrapper)).toMatchSnapshot();
expect(wrapper.find('div[data-popup="true"]')).toHaveLength(1);
});
it('should display proper text inside a tooltip', () => {
const tooltipText = 'Tooltip text';
const wrapper = mount(
<Tooltip placement="bottom" content={tooltipText}>
Hover me
</Tooltip>
);

wrapper.childAt(0).simulate('mouseover');
expect(
wrapper
.childAt(0)
.childAt(1)
.text()
).toEqual(tooltipText);
});
it('should hide a tooltip on mouse leave', () => {
const wrapper = mount(
<Tooltip placement="bottom" content="content">
Hover me
</Tooltip>
);

wrapper.childAt(0).simulate('mouseover');
wrapper.childAt(0).simulate('mouseleave');
expect(wrapper.find('div[data-popup="true"]')).toHaveLength(0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<Tooltip> that renders a Tooltip should display a tooltip on hover 1`] = `
<Tooltip
content="content"
placement="bottom"
>
<PopupBase
anchorRef={null}
anchorRenderer={[Function]}
placement="bottom"
popupRef={null}
popupRenderer={[Function]}
renderInPortal={false}
>
<span
onMouseLeave={[Function]}
onMouseOver={[Function]}
>
Hover me
</span>
<div
data-popup="true"
>
<div>
content
<div
className="Tooltip__arrow"
/>
</div>
</div>
</PopupBase>
</Tooltip>
`;
exports[`<Tooltip> that renders a Tooltip should render default Tooltip correctly 1`] = `
<Tooltip
content="content"
placement="bottom"
>
<PopupBase
anchorRef={null}
anchorRenderer={[Function]}
placement="bottom"
popupRef={null}
popupRenderer={[Function]}
renderInPortal={false}
>
<span
onMouseLeave={[Function]}
onMouseOver={[Function]}
>
Hover me
</span>
</PopupBase>
</Tooltip>
`;
1 change: 1 addition & 0 deletions src/components/Tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Tooltip';
4 changes: 3 additions & 1 deletion src/constants/component-specific.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ export const POPUP_PLACEMENTS = [
'left',
'left-start',
'left-end',
];
] as const;

export type PopupPlacement = typeof POPUP_PLACEMENTS[number];
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export { default as Pagination } from './components/Pagination';
export { Pill, PillButton, PillDropdown } from './components/Pill';
export { RadioButton, RadioButtonGroup } from './components/RadioButton';
export { default as Teaser } from './components/Teaser';
export { default as Tooltip } from './components/Tooltip';
export { RightPane, LeftPane, TwoPaneView } from './components/TwoPaneView';
// Organisms
export { Autosuggest, ItemTag } from './components/Autosuggest';
Expand Down
18 changes: 18 additions & 0 deletions stories/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs, select, text } from '@storybook/addon-knobs';
import { Tooltip } from '@textkernel/oneui';
import { POPUP_PLACEMENTS } from '../src/constants';

storiesOf('Molecules|Tooltip', module)
.addDecorator(withKnobs)
.add('Tooltip', () => (
<div style={{ position: 'relative', display: 'table', margin: '0 auto' }}>
<Tooltip
placement={select('Placement', POPUP_PLACEMENTS, 'bottom')}
content={text('Tooltip text', 'this is my tooltip text')}
>
hover here to see the tooltip
</Tooltip>
</div>
));
1 change: 1 addition & 0 deletions stories/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import './Pagination';
import './Pill';
import './RadioButton';
import './Teaser';
import './Tooltip';
import './TwoPaneView';
// Organisms
import './Autosuggest';
Expand Down

0 comments on commit 0a83c87

Please sign in to comment.