Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

Commit 98af14c

Browse files
authored
fix(Popup): allow to 'detach' from trigger and RTL adjustments (#612)
* introduce offset prop * correct description of supported values * update changelog * introduce fix * ensure RTL is properly applied to complex offset expressions * rename method to make logic more expressive * add unit tests * remove unnecessary grid props from offset example * update changelog
1 parent f5ddf0c commit 98af14c

File tree

5 files changed

+103
-43
lines changed

5 files changed

+103
-43
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1717

1818
## [Unreleased]
1919

20+
### Fixes
21+
- Ensure `Popup` properly flips values of `offset` prop in RTL @kuzhelov ([#612](https://github.com/stardust-ui/react/pull/612))
22+
2023
### Features
2124
- Add `color` prop to `Text` component @Bugaa92 ([#597](https://github.com/stardust-ui/react/pull/597))
2225
- Add `color` prop to `Header` and `HeaderDescription` components @Bugaa92 ([#628](https://github.com/stardust-ui/react/pull/628))

docs/src/examples/components/Popup/Variations/PopupExampleOffset.shorthand.tsx

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react'
2-
import { Button, Grid, Popup, Alignment, Position } from '@stardust-ui/react'
2+
import { Button, Grid, Popup } from '@stardust-ui/react'
33

44
const renderButton = rotateArrowUp => (
55
<Button
@@ -11,33 +11,24 @@ const renderButton = rotateArrowUp => (
1111
/>
1212
)
1313

14-
const triggers = [
15-
{ position: 'above', align: 'start', offset: '-100%p', rotateArrowUp: '-45deg' },
16-
{ position: 'above', align: 'end', offset: '100%p', rotateArrowUp: '45deg' },
17-
{ position: 'below', align: 'start', offset: '-100%p', rotateArrowUp: '-135deg' },
18-
{ position: 'below', align: 'end', offset: '100%p', rotateArrowUp: '135deg' },
19-
]
20-
2114
const PopupExamplePosition = () => (
22-
<Grid columns="repeat(2, 80px)" variables={{ padding: '30px', gridGap: '30px' }}>
23-
{triggers.map(({ position, align, offset, rotateArrowUp }) => (
24-
<Popup
25-
align={align as Alignment}
26-
position={position as Position}
27-
offset={offset}
28-
trigger={renderButton(rotateArrowUp)}
29-
content={{
30-
content: (
31-
<p>
32-
The popup is rendered at {position}-{align}
33-
<br />
34-
corner of the trigger.
35-
</p>
36-
),
37-
}}
38-
key={`${position}-${align}`}
39-
/>
40-
))}
15+
<Grid columns="1, 80px" variables={{ padding: '30px' }}>
16+
<Popup
17+
align="start"
18+
position="above"
19+
offset="-100%p"
20+
trigger={renderButton('-45deg')}
21+
content={{
22+
content: (
23+
<p>
24+
The popup is rendered at above-start
25+
<br />
26+
corner of the trigger.
27+
</p>
28+
),
29+
}}
30+
key="above-start"
31+
/>
4132
</Grid>
4233
)
4334

src/components/Popup/Popup.tsx

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
import { ComponentEventHandler, Extendable, ShorthandValue } from '../../../types/utils'
1919

2020
import Ref from '../Ref/Ref'
21-
import computePopupPlacement, { Alignment, Position } from './positioningHelper'
21+
import { getPopupPlacement, applyRtlToOffset, Alignment, Position } from './positioningHelper'
2222

2323
import PopupContent from './PopupContent'
2424

@@ -257,11 +257,14 @@ export default class Popup extends AutoControlledComponent<Extendable<PopupProps
257257
const { align, position, offset } = this.props
258258
const { target } = this.state
259259

260-
const placement = computePopupPlacement({ align, position, rtl })
260+
const placement = getPopupPlacement({ align, position, rtl })
261261

262262
const popperModifiers = {
263263
// https://popper.js.org/popper-documentation.html#modifiers..offset
264-
...(offset && { offset: { offset: this.applyRtlToOffset(offset, rtl, position) } }),
264+
...(offset && {
265+
offset: { offset: rtl ? applyRtlToOffset(offset, position) : offset },
266+
keepTogether: { enabled: false },
267+
}),
265268
}
266269

267270
return (
@@ -331,14 +334,4 @@ export default class Popup extends AutoControlledComponent<Extendable<PopupProps
331334
_.invoke(this.props, 'onOpenChange', eventArgs, { ...this.props, ...{ open: newValue } })
332335
}
333336
}
334-
335-
private applyRtlToOffset(offset: string, rtl: boolean, position: Position): string {
336-
if (rtl && (position === 'above' || position === 'below')) {
337-
return offset.trimLeft().startsWith('-')
338-
? offset.trimLeft().replace(/^-\s*/, '')
339-
: `-${offset}`
340-
}
341-
342-
return offset
343-
}
344337
}

src/components/Popup/positioningHelper.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const shouldAlignToCenter = (p: Position, a: Alignment) => {
5656
* | after | center | right | left
5757
* | after | bottom | right-end | left-end
5858
*/
59-
export default ({
59+
export const getPopupPlacement = ({
6060
align,
6161
position,
6262
rtl,
@@ -74,3 +74,30 @@ export default ({
7474

7575
return `${computedPosition}${stringifiedAlignment}` as Placement
7676
}
77+
78+
/////////////////////////////////
79+
// OFFSET VALUES ADJUSTMENT
80+
/////////////////////////////////
81+
82+
const flipPlusMinusSigns = (offset: string): string => {
83+
return offset
84+
.replace(/\-/g, '<plus>')
85+
.replace(/^(\s*)(?=\d)/, '<minus>')
86+
.replace(/\+/g, '<minus>')
87+
.replace(/<plus>/g, '+')
88+
.replace(/<minus>/g, '-')
89+
.trimLeft()
90+
.replace(/^\+/, '')
91+
}
92+
93+
export const applyRtlToOffset = (offset: string, position: Position): string => {
94+
if (position === 'above' || position === 'below') {
95+
const [horizontal, vertical] = offset.split(',')
96+
return [flipPlusMinusSigns(horizontal), vertical]
97+
.join(', ')
98+
.replace(/, $/, '')
99+
.trim()
100+
}
101+
102+
return offset
103+
}

test/specs/components/Popup/Popup-test.tsx

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import computePopupPlacement, { Position, Alignment } from 'src/components/Popup/positioningHelper'
1+
import {
2+
getPopupPlacement,
3+
applyRtlToOffset,
4+
Position,
5+
Alignment,
6+
} from 'src/components/Popup/positioningHelper'
27
import { Placement } from 'popper.js'
38

49
type PositionTestInput = {
@@ -16,7 +21,7 @@ describe('Popup', () => {
1621
rtl = false,
1722
}: PositionTestInput) =>
1823
it(`Popup ${position} position is transformed to ${expectedPlacement} Popper's placement`, () => {
19-
const actualPlacement = computePopupPlacement({ align, position, rtl })
24+
const actualPlacement = getPopupPlacement({ align, position, rtl })
2025
expect(actualPlacement).toEqual(expectedPlacement)
2126
})
2227

@@ -56,4 +61,45 @@ describe('Popup', () => {
5661
testPopupPositionInRtl({ position: 'after', align: 'center', expectedPlacement: 'left' })
5762
testPopupPositionInRtl({ position: 'after', align: 'bottom', expectedPlacement: 'left-end' })
5863
})
64+
65+
describe('Popup offset transformed correctly in RTL', () => {
66+
it("applies transform only for 'above' and 'below' postioning", () => {
67+
const originalOffsetValue = '100%'
68+
69+
expect(applyRtlToOffset(originalOffsetValue, 'above')).not.toBe(originalOffsetValue)
70+
expect(applyRtlToOffset(originalOffsetValue, 'below')).not.toBe(originalOffsetValue)
71+
72+
expect(applyRtlToOffset(originalOffsetValue, 'before')).toBe(originalOffsetValue)
73+
expect(applyRtlToOffset(originalOffsetValue, 'after')).toBe(originalOffsetValue)
74+
})
75+
76+
const expectOffsetTransformResult = (originalOffset, resultOffset) => {
77+
expect(applyRtlToOffset(originalOffset, 'above')).toBe(resultOffset)
78+
}
79+
80+
it('flips sign of simple expressions', () => {
81+
expectOffsetTransformResult('100%', '-100%')
82+
expectOffsetTransformResult(' 2000%p ', '-2000%p')
83+
expectOffsetTransformResult('100 ', '-100')
84+
expectOffsetTransformResult(' - 200vh', '200vh')
85+
})
86+
87+
it('flips sign of complex expressions', () => {
88+
expectOffsetTransformResult('100% + 200', '-100% - 200')
89+
expectOffsetTransformResult(' - 2000%p - 400 +800vh ', '2000%p + 400 -800vh')
90+
})
91+
92+
it('transforms only horizontal offset value', () => {
93+
const xOffset = '-100%'
94+
const yOffset = '800vh'
95+
96+
const offsetValue = [xOffset, yOffset].join(',')
97+
const [xOffsetTransformed, yOffsetTransformed] = applyRtlToOffset(offsetValue, 'above').split(
98+
',',
99+
)
100+
101+
expect(xOffsetTransformed.trim()).not.toBe(xOffset)
102+
expect(yOffsetTransformed.trim()).toBe(yOffset)
103+
})
104+
})
59105
})

0 commit comments

Comments
 (0)