import * as React from 'react';
import styled from 'styled-components';
import Button from '../storybook/Button';
import { COLORS } from '../storybook/styles';
import { PositioningStrategy } from './PositioningPortal';
import PositioningPortalWithState from './PositioningPortalWithState';
import { extendStory } from '../storybook/utils';
import scrollable from '../storybook/decorators/scrollable';
import { ComponentStory } from '@storybook/react';
enum POSITION {
TOP = 'top',
LEFT = 'left',
RIGHT = 'right',
BOTTOM = 'bottom'
}
const StyledTooltip = styled.div`
display: inline-block;
max-width: 260px;
position: relative;
background-color: ${COLORS.primary};
color: ${COLORS.textInvert};
padding: 16px;
border-radius: 4px;
`;
interface Props {
children: React.ReactNode;
position: POSITION;
shift: number;
}
const StyledTooltipArrow = styled.div<{ position: POSITION; shift: number }>`
position: absolute;
width: 0;
height: 0;
${props =>
props.position === POSITION.TOP &&
`
top: 100%;
left: 50%;
margin-left: -10px;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid ${COLORS.primary};
`}
${props =>
props.position === POSITION.BOTTOM &&
`
top: auto;
bottom: 100%;
left: 50%;
margin-left: -10px;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid ${COLORS.primary};
`}
${props =>
props.position === POSITION.LEFT &&
`
left: 100%;
top: 50%;
margin-top: -10px;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 10px solid ${COLORS.primary};
`}
${props =>
props.position === POSITION.RIGHT &&
`
left: auto;
top: 50%;
right: 100%;
margin-top: -10px;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 10px solid ${COLORS.primary};
`}
${props =>
(props.position === POSITION.TOP || props.position === POSITION.BOTTOM) &&
`
transform: translateX(${-props.shift}px);
`}
${props =>
(props.position === POSITION.LEFT || props.position === POSITION.RIGHT) &&
`
transform: translateY(${-props.shift}px);
`}
`;
const Tooltip = ({ children, position = POSITION.TOP, shift = 0 }: Props) => {
return (
{children}
);
};
const positionStrategy: (
preferredPosition: POSITION
) => PositioningStrategy<{
position: POSITION;
shift: number;
}> = preferredPosition => (parentRect, portalRect) => {
const scrollX = window.scrollX || window.pageXOffset;
const scrollY = window.scrollY || window.pageYOffset;
const body = window.document.documentElement || window.document.body;
const horizontalCenter = (parentRect.width - portalRect.width) / 2;
const verticalCenter = (parentRect.height - portalRect.height) / 2;
const additionalPadding = 15;
const positions = {
[POSITION.BOTTOM]: {
position: POSITION.BOTTOM,
top: parentRect.top + parentRect.height + scrollY + additionalPadding,
left: parentRect.left + scrollX + horizontalCenter,
enoughSpace:
parentRect.top +
parentRect.height +
portalRect.height +
additionalPadding <
body.clientHeight
},
[POSITION.TOP]: {
position: POSITION.TOP,
top: parentRect.top - portalRect.height + scrollY - additionalPadding,
left: parentRect.left + scrollX + horizontalCenter,
enoughSpace: parentRect.top - portalRect.height - additionalPadding > 0
},
[POSITION.LEFT]: {
position: POSITION.LEFT,
top: parentRect.top + scrollY + verticalCenter,
left: parentRect.left + scrollX - portalRect.width - additionalPadding,
enoughSpace: parentRect.left - portalRect.width - additionalPadding > 0
},
[POSITION.RIGHT]: {
position: POSITION.RIGHT,
top: parentRect.top + scrollY + verticalCenter,
left: parentRect.left + scrollX + parentRect.width + additionalPadding,
enoughSpace:
parentRect.left +
parentRect.width +
portalRect.width +
additionalPadding <
body.clientWidth
}
};
// Horizontal fallback preferred
let sortedPositions = [
positions[preferredPosition],
positions[POSITION.BOTTOM],
positions[POSITION.TOP],
positions[POSITION.RIGHT],
positions[POSITION.LEFT]
];
// Vertical fallback preferred
if (
preferredPosition === POSITION.LEFT ||
preferredPosition === POSITION.RIGHT
) {
sortedPositions = [
positions[preferredPosition],
positions[POSITION.RIGHT],
positions[POSITION.LEFT],
positions[POSITION.BOTTOM],
positions[POSITION.TOP]
];
}
const pickedPosition =
sortedPositions.find(({ enoughSpace }) => enoughSpace) ||
positions[preferredPosition];
const finalTop = Math.max(
Math.min(
pickedPosition.top,
body.clientHeight + scrollY - portalRect.height
),
scrollY
);
const shiftY = Math.max(
Math.min(
finalTop - pickedPosition.top,
portalRect.height / 2 - additionalPadding
),
portalRect.height / -2 + additionalPadding
);
const finalLeft = Math.max(
Math.min(
pickedPosition.left,
body.clientWidth + scrollX - portalRect.width
),
scrollX
);
const shiftX = Math.max(
Math.min(
finalLeft - pickedPosition.left,
portalRect.width / 2 - additionalPadding
),
portalRect.width / -2 + additionalPadding
);
return {
top: Math.max(
Math.min(
pickedPosition.top,
body.clientHeight + scrollY - portalRect.height
),
scrollY
),
left: Math.max(
Math.min(
pickedPosition.left,
body.clientWidth + scrollX - portalRect.width
),
scrollX
),
strategy: {
position: pickedPosition.position,
shift:
pickedPosition.position === 'top' ||
pickedPosition.position === 'bottom'
? shiftX
: shiftY
}
};
};
export default {
title: 'Example: Tooltip'
};
export const Base: ComponentStory> = function(args) {
return ;
};
Base.args = {
positionStrategy: positionStrategy(POSITION.RIGHT),
portalContent: ({ strategy }) => (
Tooltip positioned with portal.
),
children: ({ open, close }) => (
)
};
export const ScrollableTest = extendStory(Base);
ScrollableTest.decorators = [scrollable];
export const PreferredPositionTop = extendStory(Base, {
positionStrategy: positionStrategy(POSITION.TOP)
});
PreferredPositionTop.decorators = [scrollable];