Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Popover, Tooltip): fix arrow positioning in RTL #9638

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/react-core/src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,20 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
'right-start': styles.modifiers.rightTop,
'right-end': styles.modifiers.rightBottom
};
const positionModifiersRTL = {
top: styles.modifiers.top,
bottom: styles.modifiers.bottom,
left: styles.modifiers.right,
right: styles.modifiers.left,
'top-start': styles.modifiers.topRight,
'top-end': styles.modifiers.topLeft,
'bottom-start': styles.modifiers.bottomRight,
'bottom-end': styles.modifiers.bottomLeft,
'left-start': styles.modifiers.rightTop,
'left-end': styles.modifiers.rightBottom,
'right-start': styles.modifiers.leftTop,
'right-end': styles.modifiers.leftBottom
};
const hasCustomMinWidth = minWidth !== popoverMinWidth.value;
const hasCustomMaxWidth = maxWidth !== popoverMaxWidth.value;
const onDocumentKeyDown = (event: KeyboardEvent) => {
Expand Down Expand Up @@ -508,6 +522,7 @@ export const Popover: React.FunctionComponent<PopoverProps> = ({
onFocus={triggerAction === 'hover' && onFocus}
onBlur={triggerAction === 'hover' && onBlur}
positionModifiers={positionModifiers}
positionModifiersRTL={positionModifiersRTL}
distance={distance}
placement={position}
onTriggerClick={triggerAction === 'click' && onTriggerClick}
Expand Down
15 changes: 15 additions & 0 deletions packages/react-core/src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,20 @@ export const Tooltip: React.FunctionComponent<TooltipProps> = ({
'right-start': styles.modifiers.rightTop,
'right-end': styles.modifiers.rightBottom
};
const positionModifiersRTL = {
top: styles.modifiers.top,
bottom: styles.modifiers.bottom,
left: styles.modifiers.right,
right: styles.modifiers.left,
'top-start': styles.modifiers.topRight,
'top-end': styles.modifiers.topLeft,
'bottom-start': styles.modifiers.bottomRight,
'bottom-end': styles.modifiers.bottomLeft,
'left-start': styles.modifiers.rightTop,
'left-end': styles.modifiers.rightBottom,
'right-start': styles.modifiers.leftTop,
'right-end': styles.modifiers.leftBottom
};
const hasCustomMaxWidth = maxWidth !== tooltipMaxWidth.value;
const content = (
<div
Expand Down Expand Up @@ -279,6 +293,7 @@ export const Tooltip: React.FunctionComponent<TooltipProps> = ({
appendTo={appendTo}
isVisible={visible}
positionModifiers={positionModifiers}
positionModifiersRTL={positionModifiersRTL}
distance={distance}
placement={position}
onMouseEnter={triggerOnMouseenter && show}
Expand Down
89 changes: 79 additions & 10 deletions packages/react-core/src/helpers/Popper/Popper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ const getOppositePlacement = (placement: Placement): any =>
export const getOpacityTransition = (animationDuration: number) =>
`opacity ${animationDuration}ms cubic-bezier(.54, 1.5, .38, 1.11)`;

export const getLanguageDirection = (targetElement: HTMLElement) => {
const defaultDirection = 'ltr';
let direction = defaultDirection;

if (targetElement) {
direction = getComputedStyle(targetElement).getPropertyValue('direction');
}

if (['ltr', 'rtl'].includes(direction)) {
return direction as 'ltr' | 'rtl';
}

return defaultDirection;
};

export interface PopperProps {
/**
* Trigger reference element to which the popper is relatively placed to.
Expand All @@ -66,7 +81,7 @@ export interface PopperProps {
/** popper direction */
direction?: 'up' | 'down';
/** popper position */
position?: 'right' | 'left' | 'center';
position?: 'right' | 'left' | 'center' | 'start' | 'end';
/** Instead of direction and position can set the placement of the popper */
placement?: Placement;
/** Custom width of the popper. If the value is "trigger", it will set the width to the trigger element's width */
Expand Down Expand Up @@ -104,6 +119,29 @@ export interface PopperProps {
rightStart?: string;
rightEnd?: string;
};
/**
* Map class names to positions when the popper is in RTL, for example:
* {
* top: styles.modifiers.top,
* bottom: styles.modifiers.bottom,
* left: styles.modifiers.left,
* right: styles.modifiers.right
* }
*/
positionModifiersRTL?: {
top?: string;
right?: string;
bottom?: string;
left?: string;
topStart?: string;
topEnd?: string;
bottomStart?: string;
bottomEnd?: string;
leftStart?: string;
leftEnd?: string;
rightStart?: string;
rightEnd?: string;
};
/** Distance of the popper to the trigger */
distance?: number;
/** Callback function when mouse enters trigger */
Expand Down Expand Up @@ -177,7 +215,7 @@ export const Popper: React.FunctionComponent<PopperProps> = ({
trigger,
popper,
direction = 'down',
position = 'left',
position = 'start',
placement,
width,
minWidth = 'trigger',
Expand All @@ -186,6 +224,7 @@ export const Popper: React.FunctionComponent<PopperProps> = ({
zIndex = 9999,
isVisible = true,
positionModifiers,
positionModifiersRTL,
distance = 0,
onMouseEnter,
onMouseLeave,
Expand Down Expand Up @@ -226,6 +265,36 @@ export const Popper: React.FunctionComponent<PopperProps> = ({
const refOrTrigger = refElement || triggerElement;
const showPopper = isVisible || internalIsVisible;

const triggerParent = ((triggerRef as React.RefObject<any>)?.current || triggerElement)?.parentElement;
const languageDirection = getLanguageDirection(triggerParent);

const internalPositionModifiers = React.useMemo<PopperProps['positionModifiers']>(() => {
if (languageDirection === 'rtl' && positionModifiersRTL) {
return positionModifiersRTL;
}

return positionModifiers;
}, [positionModifiers, positionModifiersRTL, languageDirection]);

const internalPosition = React.useMemo<'left' | 'right' | 'center'>(() => {
const fixedPositions = { left: 'left', right: 'right', center: 'center' };

const positionMap = {
ltr: {
start: 'left',
end: 'right',
...fixedPositions
},
rtl: {
start: 'right',
end: 'left',
...fixedPositions
}
};

return positionMap[languageDirection][position] as 'left' | 'right' | 'center';
}, [position, languageDirection]);

const onDocumentClickCallback = React.useCallback(
(event: MouseEvent) => onDocumentClick(event, refOrTrigger, popperElement),
[showPopper, triggerElement, refElement, popperElement, onDocumentClick]
Expand Down Expand Up @@ -333,15 +402,15 @@ export const Popper: React.FunctionComponent<PopperProps> = ({
return placement;
}
let convertedPlacement = direction === 'up' ? 'top' : 'bottom';
if (position !== 'center') {
convertedPlacement = `${convertedPlacement}-${position === 'right' ? 'end' : 'start'}`;
if (internalPosition !== 'center') {
convertedPlacement = `${convertedPlacement}-${internalPosition === 'right' ? 'end' : 'start'}`;
}
return convertedPlacement as Placement;
};
const getPlacementMemo = React.useMemo(getPlacement, [direction, position, placement]);
const getPlacementMemo = React.useMemo(getPlacement, [direction, internalPosition, placement]);
const getOppositePlacementMemo = React.useMemo(
() => getOppositePlacement(getPlacement()),
[direction, position, placement]
[direction, internalPosition, placement]
);

const widthMods: Modifier<'widthMods', {}> = React.useMemo(
Expand Down Expand Up @@ -475,14 +544,14 @@ export const Popper: React.FunctionComponent<PopperProps> = ({
// Depends on the position of the Popper relative to the reference element
const modifierFromPopperPosition = () => {
if (attributes && attributes.popper && attributes.popper['data-popper-placement']) {
const popperPlacement = attributes.popper['data-popper-placement'] as keyof typeof positionModifiers;
return positionModifiers[popperPlacement];
const popperPlacement = attributes.popper['data-popper-placement'] as keyof typeof internalPositionModifiers;
return internalPositionModifiers[popperPlacement];
}
return positionModifiers.top;
return internalPositionModifiers.top;
};

const options = {
className: css(popper.props && popper.props.className, positionModifiers && modifierFromPopperPosition()),
className: css(popper.props && popper.props.className, internalPositionModifiers && modifierFromPopperPosition()),
style: {
...((popper.props && popper.props.style) || {}),
...popperStyles.popper,
Expand Down
Loading