Skip to content

Commit

Permalink
feat(react-components): add "arrow" prop to Tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
donskov committed Jul 5, 2023
1 parent a6376da commit 689fae4
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 55 deletions.
56 changes: 35 additions & 21 deletions packages/react-components/src/Popper/popper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type BaseProps = {
/**
* Popper render node.
*/
children: React.ReactNode;
children: React.ReactNode | ((style: React.CSSProperties) => React.ReactNode);
/**
* It's used to set the position of the popper.
*/
Expand All @@ -27,9 +27,14 @@ type BaseProps = {
* Make your popper the same width as the reference.
*/
allowUseSameWidth?: boolean;
/**
* Popper.js is based on a "plugin-like" architecture,
* most of its features are fully encapsulated "modifiers".
*/
modifiers?: Modifier<any>[];
};

type PopperProps = BaseProps & React.HTMLAttributes<HTMLDivElement>;
type PopperProps = BaseProps & Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>;

export const Popper: React.FC<PopperProps> = (props) => {
const {
Expand All @@ -39,33 +44,42 @@ export const Popper: React.FC<PopperProps> = (props) => {
open,
disablePortal,
allowUseSameWidth,
modifiers,
...other
} = props;
const [popperElement, setPopperElement] = React.useState(null);
const sameWidthModifier: Modifier<'sameWidth'> = React.useMemo(
() => ({
name: 'sameWidth',
enabled: allowUseSameWidth,
phase: 'beforeWrite',
requires: ['computeStyles'],
fn: ({ state }) => {
// eslint-disable-next-line no-param-reassign
state.styles.popper.width = `${state.rects.reference.width}px`;
const popperModifiers: Modifier<any>[] = React.useMemo(() => {
let baseModifiers: Modifier<any>[] = [
{
name: 'sameWidth',
enabled: allowUseSameWidth,
phase: 'beforeWrite',
requires: ['computeStyles'],
fn: ({ state }) => {
// eslint-disable-next-line no-param-reassign
state.styles.popper.width = `${state.rects.reference.width}px`;
},
effect: ({ state }) => {
// @ts-ignore
// eslint-disable-next-line no-param-reassign
state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`;
},
},
effect: ({ state }) => {
// @ts-ignore
// eslint-disable-next-line no-param-reassign
state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`;
},
}),
[],
);
];

if (modifiers && modifiers.length) {
baseModifiers = baseModifiers.concat(modifiers);
}

return baseModifiers;
}, [allowUseSameWidth, modifiers]);

const { styles, attributes } = usePopper(
anchorEl,
popperElement,
{
placement,
modifiers: [sameWidthModifier],
modifiers: popperModifiers,
},
);

Expand All @@ -77,7 +91,7 @@ export const Popper: React.FC<PopperProps> = (props) => {
role="tooltip"
{...attributes.popper}
>
{children}
{typeof children === 'function' ? children(styles.arrow) : children}
</div>
);

Expand Down
127 changes: 93 additions & 34 deletions packages/react-components/src/Tooltip/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export type TooltipBaseProps = {
* Add delay in hiding the tooltip.
*/
leaveDelay?: number;
/**
* If `true`, adds an arrow to the tooltip.
*/
arrow?: boolean;
};

export type TooltipProps = TooltipBaseProps & Omit<React.HTMLAttributes<HTMLDivElement>, 'title' | 'children'>;
Expand All @@ -85,40 +89,72 @@ const stylesKeyframeOpacity = keyframes`
}
`;

const stylesTooltip = () => css({
const stylesTooltip = (props: TooltipBaseProps) => css({
label: 'Tooltip',
boxShadow: 'var(--pv-shadow-light-low)',
maxWidth: '300px',
wordWrap: 'break-word',
fontSize: 0,
animation: `${stylesKeyframeOpacity} 225ms`,
position: 'relative',
...(props.size === 'small' && {
padding: '5px 8px',
}),
...(props.size === 'large' && {
padding: '8px 10px',
}),
});

const stylesSizeSmall = () => css({
label: 'small',
padding: '5px 8px',
});

const stylesSizeLarge = () => css({
label: 'large',
padding: '8px 10px',
});

const stylesPopper = (interactive?: boolean) => css({
const stylesPopper = (props: TooltipBaseProps) => css({
label: 'Popper',
pointerEvents: interactive ? 'auto' : 'none',
pointerEvents: props.interactive ? 'auto' : 'none',
zIndex: 1500,
'&[data-popper-placement^="bottom"]': {
padding: 'var(--pv-size-base-3) 0px',
'[data-popper-arrow]': {
top: 0,
marginTop: '-4px',
},
},
'&[data-popper-placement^="top"]': {
padding: 'var(--pv-size-base-3) 0px',
'[data-popper-arrow]': {
bottom: 0,
marginBottom: '-4px',
},
},
'&[data-popper-placement^="right"]': {
padding: '0px var(--pv-size-base-3)',
'[data-popper-arrow]': {
left: 0,
marginLeft: '-4px',
},
},
'&[data-popper-placement^="left"]': {
padding: '0px var(--pv-size-base-3)',
'[data-popper-arrow]': {
right: 0,
marginRight: '-4px',
},
},
});

const stylesArrow = (props: TooltipBaseProps) => css({
label: 'arrow',
width: '8px',
height: '8px',
background: 'transparent',
position: 'absolute',
display: 'block',
color: props.color === 'white' ? 'var(--pv-color-white)' : 'var(--pv-color-gray-10)',
'&::before': {
content: '""',
margin: 'auto',
display: 'block',
width: '100%',
height: '100%',
backgroundColor: 'currentColor',
transform: 'rotate(45deg)',
},
});
/**
Expand All @@ -141,6 +177,7 @@ export const Tooltip: React.FC<TooltipProps> = (props) => {
disablePortal,
enterDelay,
leaveDelay,
arrow,
...other
} = props;
const [open, setOpen] = useControllableState({
Expand All @@ -149,6 +186,7 @@ export const Tooltip: React.FC<TooltipProps> = (props) => {
});
const nodeRef = React.useRef(null);
const multiRef = useMergedRef((children as any).ref, nodeRef);
const [arrowRef, setArrowRef] = React.useState(null);
const enterTimer = React.useRef<NodeJS.Timeout>();
const leaveTimer = React.useRef<NodeJS.Timeout>();

Expand Down Expand Up @@ -218,31 +256,51 @@ export const Tooltip: React.FC<TooltipProps> = (props) => {
anchorEl={nodeRef.current}
open={title && open}
placement={placement}
className={cx({
[stylesPopper(interactive)]: true,
})}
className={stylesPopper(props)}
disablePortal={disablePortal}
modifiers={[
{
name: 'arrow',
enabled: Boolean(arrowRef),
options: {
element: arrowRef,
padding: 8,
},
},
]}
{...popperProps}
>
<Box
{...other}
background={color === 'black' ? 'gray-10' : 'white'}
borderRadius={4}
className={cx({
[stylesTooltip()]: true,
[stylesSizeSmall()]: size === 'small',
[stylesSizeLarge()]: size === 'large',
[className]: !!className,
})}
>
<Typography
component="span"
variant={size === 'small' ? 'c2' : 'b3'}
color={color === 'black' ? 'white' : 'black'}
{(style) => (
<Box
{...other}
background={color === 'black' ? 'gray-10' : 'white'}
borderRadius={4}
className={cx({
[stylesTooltip(props)]: true,
[className]: !!className,
})}
>
{title}
</Typography>
</Box>
<Typography
component="span"
variant={size === 'small' ? 'c2' : 'b3'}
color={color === 'black' ? 'white' : 'black'}
>
{title}
</Typography>
{
arrow
? (
<span
className={stylesArrow(props)}
data-popper-arrow
ref={setArrowRef}
style={style}
/>
)
: null
}
</Box>
)}
</Popper>
</>
);
Expand All @@ -257,4 +315,5 @@ Tooltip.defaultProps = {
disablePortal: true,
enterDelay: 100,
leaveDelay: 0,
arrow: false,
};

0 comments on commit 689fae4

Please sign in to comment.