diff --git a/CHANGELOG.md b/CHANGELOG.md
index 721a2c2f48..af197727ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
+### Fixes
+- Ensure `Popup` properly flips values of `offset` prop in RTL @kuzhelov ([#612](https://github.com/stardust-ui/react/pull/612))
+
### Features
- Add `color` prop to `Text` component @Bugaa92 ([#597](https://github.com/stardust-ui/react/pull/597))
- Add `color` prop to `Header` and `HeaderDescription` components @Bugaa92 ([#628](https://github.com/stardust-ui/react/pull/628))
diff --git a/docs/src/examples/components/Popup/Variations/PopupExampleOffset.shorthand.tsx b/docs/src/examples/components/Popup/Variations/PopupExampleOffset.shorthand.tsx
index 8376ee024c..d0901e6e01 100644
--- a/docs/src/examples/components/Popup/Variations/PopupExampleOffset.shorthand.tsx
+++ b/docs/src/examples/components/Popup/Variations/PopupExampleOffset.shorthand.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Button, Grid, Popup, Alignment, Position } from '@stardust-ui/react'
+import { Button, Grid, Popup } from '@stardust-ui/react'
const renderButton = rotateArrowUp => (
- ),
- }}
- key={`${position}-${align}`}
- />
- ))}
+
+
+ The popup is rendered at above-start
+
+ corner of the trigger.
+
+ ),
+ }}
+ key="above-start"
+ />
)
diff --git a/src/components/Popup/Popup.tsx b/src/components/Popup/Popup.tsx
index 3876f75dde..812c953743 100644
--- a/src/components/Popup/Popup.tsx
+++ b/src/components/Popup/Popup.tsx
@@ -18,7 +18,7 @@ import {
import { ComponentEventHandler, Extendable, ShorthandValue } from '../../../types/utils'
import Ref from '../Ref/Ref'
-import computePopupPlacement, { Alignment, Position } from './positioningHelper'
+import { getPopupPlacement, applyRtlToOffset, Alignment, Position } from './positioningHelper'
import PopupContent from './PopupContent'
@@ -257,11 +257,14 @@ export default class Popup extends AutoControlledComponent {
* | after | center | right | left
* | after | bottom | right-end | left-end
*/
-export default ({
+export const getPopupPlacement = ({
align,
position,
rtl,
@@ -74,3 +74,30 @@ export default ({
return `${computedPosition}${stringifiedAlignment}` as Placement
}
+
+/////////////////////////////////
+// OFFSET VALUES ADJUSTMENT
+/////////////////////////////////
+
+const flipPlusMinusSigns = (offset: string): string => {
+ return offset
+ .replace(/\-/g, '')
+ .replace(/^(\s*)(?=\d)/, '')
+ .replace(/\+/g, '')
+ .replace(//g, '+')
+ .replace(//g, '-')
+ .trimLeft()
+ .replace(/^\+/, '')
+}
+
+export const applyRtlToOffset = (offset: string, position: Position): string => {
+ if (position === 'above' || position === 'below') {
+ const [horizontal, vertical] = offset.split(',')
+ return [flipPlusMinusSigns(horizontal), vertical]
+ .join(', ')
+ .replace(/, $/, '')
+ .trim()
+ }
+
+ return offset
+}
diff --git a/test/specs/components/Popup/Popup-test.tsx b/test/specs/components/Popup/Popup-test.tsx
index 3470c4be09..8d79103ec5 100644
--- a/test/specs/components/Popup/Popup-test.tsx
+++ b/test/specs/components/Popup/Popup-test.tsx
@@ -1,4 +1,9 @@
-import computePopupPlacement, { Position, Alignment } from 'src/components/Popup/positioningHelper'
+import {
+ getPopupPlacement,
+ applyRtlToOffset,
+ Position,
+ Alignment,
+} from 'src/components/Popup/positioningHelper'
import { Placement } from 'popper.js'
type PositionTestInput = {
@@ -16,7 +21,7 @@ describe('Popup', () => {
rtl = false,
}: PositionTestInput) =>
it(`Popup ${position} position is transformed to ${expectedPlacement} Popper's placement`, () => {
- const actualPlacement = computePopupPlacement({ align, position, rtl })
+ const actualPlacement = getPopupPlacement({ align, position, rtl })
expect(actualPlacement).toEqual(expectedPlacement)
})
@@ -56,4 +61,45 @@ describe('Popup', () => {
testPopupPositionInRtl({ position: 'after', align: 'center', expectedPlacement: 'left' })
testPopupPositionInRtl({ position: 'after', align: 'bottom', expectedPlacement: 'left-end' })
})
+
+ describe('Popup offset transformed correctly in RTL', () => {
+ it("applies transform only for 'above' and 'below' postioning", () => {
+ const originalOffsetValue = '100%'
+
+ expect(applyRtlToOffset(originalOffsetValue, 'above')).not.toBe(originalOffsetValue)
+ expect(applyRtlToOffset(originalOffsetValue, 'below')).not.toBe(originalOffsetValue)
+
+ expect(applyRtlToOffset(originalOffsetValue, 'before')).toBe(originalOffsetValue)
+ expect(applyRtlToOffset(originalOffsetValue, 'after')).toBe(originalOffsetValue)
+ })
+
+ const expectOffsetTransformResult = (originalOffset, resultOffset) => {
+ expect(applyRtlToOffset(originalOffset, 'above')).toBe(resultOffset)
+ }
+
+ it('flips sign of simple expressions', () => {
+ expectOffsetTransformResult('100%', '-100%')
+ expectOffsetTransformResult(' 2000%p ', '-2000%p')
+ expectOffsetTransformResult('100 ', '-100')
+ expectOffsetTransformResult(' - 200vh', '200vh')
+ })
+
+ it('flips sign of complex expressions', () => {
+ expectOffsetTransformResult('100% + 200', '-100% - 200')
+ expectOffsetTransformResult(' - 2000%p - 400 +800vh ', '2000%p + 400 -800vh')
+ })
+
+ it('transforms only horizontal offset value', () => {
+ const xOffset = '-100%'
+ const yOffset = '800vh'
+
+ const offsetValue = [xOffset, yOffset].join(',')
+ const [xOffsetTransformed, yOffsetTransformed] = applyRtlToOffset(offsetValue, 'above').split(
+ ',',
+ )
+
+ expect(xOffsetTransformed.trim()).not.toBe(xOffset)
+ expect(yOffsetTransformed.trim()).toBe(yOffset)
+ })
+ })
})