diff --git a/modules/react/menu/lib/MenuItem.tsx b/modules/react/menu/lib/MenuItem.tsx index 07d5b8e451..fcfd83b425 100644 --- a/modules/react/menu/lib/MenuItem.tsx +++ b/modules/react/menu/lib/MenuItem.tsx @@ -184,6 +184,7 @@ export const useMenuItemFocus = createElemPropsHook(useMenuModel)( (model, ref, elemProps: {'data-id': string} = {'data-id': ''}) => { const {localRef, elementRef} = useLocalRef(ref as React.Ref); const id = elemProps['data-id']; + // focus on the item with the cursor React.useLayoutEffect(() => { if (model.state.mode === 'single') { @@ -196,6 +197,7 @@ export const useMenuItemFocus = createElemPropsHook(useMenuModel)( } // eslint-disable-next-line react-hooks/exhaustive-deps }, [id, localRef, model.state.cursorId, model.state.mode]); + return { ref: elementRef, className: isCursor(model.state, elemProps['data-id']) ? 'focus' : undefined, diff --git a/modules/react/popup/lib/Popper.tsx b/modules/react/popup/lib/Popper.tsx index 38c25723db..ccca9f834e 100644 --- a/modules/react/popup/lib/Popper.tsx +++ b/modules/react/popup/lib/Popper.tsx @@ -13,9 +13,25 @@ export type PopperOptions = Options; export const defaultFallbackPlacements: Placement[] = ['top', 'right', 'bottom', 'left']; import {usePopupStack} from './hooks'; -import {useLocalRef} from '@workday/canvas-kit-react/common'; +import {isElementRTL, useLocalRef} from '@workday/canvas-kit-react/common'; import {fallbackPlacementsModifier} from './fallbackPlacements'; +/** + * Flips a placement for RTL layouts. Popper.js doesn't automatically flip + * placements based on dir attribute, so we need to do it manually. In RTL: + * - `left` ↔ `right` + * - `-start` ↔ `-end` + */ +const flipPlacementForRTL = (placement: Placement): Placement => { + return placement + .replace(/left/g, '__LEFT__') + .replace(/right/g, 'left') + .replace(/__LEFT__/g, 'right') + .replace(/-start/g, '__START__') + .replace(/-end/g, '-start') + .replace(/__START__/g, '-end') as Placement; +}; + export interface PopperProps { /** * The reference element used to position the Popper. Popper content will try to follow the @@ -172,16 +188,25 @@ const OpenPopper = React.forwardRef( return undefined; } + // Check RTL from the popup container (stackRef) to flip placements. + // Popper.js doesn't automatically flip left/right placements in RTL mode. + // We use stackRef because it has the dir attribute set by usePopupStack. + const isRTL = stackRef.current ? isElementRTL(stackRef.current) : false; + const rtlAwarePlacement = isRTL ? flipPlacementForRTL(popperPlacement) : popperPlacement; + const rtlAwareFallbackPlacements = isRTL + ? fallbackPlacements.map(flipPlacementForRTL) + : fallbackPlacements; + if (stackRef.current) { const instance = createPopper(anchorEl, stackRef.current, { - placement: popperPlacement, + placement: rtlAwarePlacement, ...popperOptions, modifiers: [ placementModifier, { ...fallbackPlacementsModifier, options: { - fallbackPlacements, + fallbackPlacements: rtlAwareFallbackPlacements, }, }, ...(popperOptions.modifiers || []), @@ -204,15 +229,23 @@ const OpenPopper = React.forwardRef( React.useLayoutEffect(() => { // Only update options if this is _not_ the first render if (!firstRender.current) { + // Check RTL from the popup container to flip placements. + const popperElement = localRef.current?.state?.elements?.popper; + const isRTL = popperElement ? isElementRTL(popperElement) : false; + const rtlAwarePlacement = isRTL ? flipPlacementForRTL(popperPlacement) : popperPlacement; + const rtlAwareFallbackPlacements = isRTL + ? fallbackPlacements.map(flipPlacementForRTL) + : fallbackPlacements; + localRef.current?.setOptions({ - placement: popperPlacement, + placement: rtlAwarePlacement, ...popperOptions, modifiers: [ placementModifier, { ...fallbackPlacementsModifier, options: { - fallbackPlacements, + fallbackPlacements: rtlAwareFallbackPlacements, }, }, ...(popperOptions.modifiers || []), @@ -220,7 +253,14 @@ const OpenPopper = React.forwardRef( }); } firstRender.current = false; - }, [popperOptions, popperPlacement, fallbackPlacements, placementModifier, localRef]); + }, [ + popperOptions, + popperPlacement, + fallbackPlacements, + anchorElement, + placementModifier, + localRef, + ]); const contents = <>{isRenderProp(children) ? children({placement}) : children}; diff --git a/modules/react/popup/stories/visual-testing/Popup.stories.tsx b/modules/react/popup/stories/visual-testing/Popup.stories.tsx index cdf930f612..2b5e4018cd 100644 --- a/modules/react/popup/stories/visual-testing/Popup.stories.tsx +++ b/modules/react/popup/stories/visual-testing/Popup.stories.tsx @@ -148,16 +148,18 @@ export const PopupRTL = { }); return ( - - - - - - למחוק פריט - האם ברצונך למחוק פריט זה - - - +
+ + + + + + למחוק פריט + האם ברצונך למחוק פריט זה + + + +
); },