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

feat(Popup): add 'offset' prop #606

Merged
merged 5 commits into from
Dec 13, 2018
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Export `call-recording` SVG icon @Bugaa92 ([#585](https://github.com/stardust-ui/react/pull/585))
- Export `canvas-add-page` SVG icon @priyankar205 ([#601](https://github.com/stardust-ui/react/pull/601))
- Add `sizeModifier` variable (with `x` and `xx` values) to `Icon`'s Teams theme styles @priyankar205 ([#601](https://github.com/stardust-ui/react/pull/601))
- Add `offset` prop to `Popup` to extend set of popup positioning options @kuzhelov ([#606](https://github.com/stardust-ui/react/pull/606))

### Documentation
- Add `prettier` support throughout the docs @levithomason ([#568](https://github.com/stardust-ui/react/pull/568))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'
import { Button, Grid, Popup, Alignment, Position } from '@stardust-ui/react'

const renderButton = rotateArrowUp => (
<Button
icon={{
name: 'arrow circle up',
styles: { transform: `rotate(${rotateArrowUp})` },
}}
styles={{ height: '80px', minWidth: '80px', padding: 0 }}
/>
)

const triggers = [
{ position: 'above', align: 'start', offset: '-100%p', rotateArrowUp: '-45deg' },
{ position: 'above', align: 'end', offset: '100%p', rotateArrowUp: '45deg' },
{ position: 'below', align: 'start', offset: '-100%p', rotateArrowUp: '-135deg' },
{ position: 'below', align: 'end', offset: '100%p', rotateArrowUp: '135deg' },
]

const PopupExamplePosition = () => (
<Grid columns="repeat(2, 80px)" variables={{ padding: '30px', gridGap: '30px' }}>
{triggers.map(({ position, align, offset, rotateArrowUp }) => (
<Popup
align={align as Alignment}
position={position as Position}
offset={offset}
trigger={renderButton(rotateArrowUp)}
content={{
content: (
<p>
The popup is rendered at {position}-{align}
<br />
corner of the trigger.
</p>
),
}}
key={`${position}-${align}`}
/>
))}
</Grid>
)

export default PopupExamplePosition
5 changes: 5 additions & 0 deletions docs/src/examples/components/Popup/Variations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ const Variations = () => (
description="A popup can be positioned around its trigger and aligned relative to the trigger's margins. Click on a button to open a popup on a specific position and alignment."
examplePath="components/Popup/Variations/PopupExamplePosition"
/>
<ComponentExample
title="Offset"
description="Popup position could be further customized by providing offset value. Note that percentage values of both trigger and popup elements' lengths are supported."
examplePath="components/Popup/Variations/PopupExampleOffset"
/>
</ExampleSection>
)

Expand Down
27 changes: 26 additions & 1 deletion src/components/Popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ export interface PopupProps
/** Initial value for 'open'. */
defaultOpen?: boolean

/** Offset value to apply to rendered popup. Accepts the following units:
* - px or unit-less, interpreted as pixels
* - %, percentage relative to the length of the trigger element
* - %p, percentage relative to the length of the popup element
* - vw, CSS viewport width unit
* - vh, CSS viewport height unit
*/
offset?: string

/** Defines whether popup is displayed. */
open?: boolean

Expand Down Expand Up @@ -245,17 +254,23 @@ export default class Popup extends AutoControlledComponent<Extendable<PopupProps
rtl: boolean,
accessibility: AccessibilityBehavior,
): JSX.Element {
const { align, position } = this.props
const { align, position, offset } = this.props
const { target } = this.state

const placement = computePopupPlacement({ align, position, rtl })

const popperModifiers = {
// https://popper.js.org/popper-documentation.html#modifiers..offset
...(offset && { offset: { offset: this.applyRtlToOffset(offset, rtl, position) } }),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too bad applying modifiers is the only way we can adjust the offset... There is no way we can use this prop inside the styles in order to achieve the position, right? It really feels awkward to have to tackle rtl in the component's logic..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, there is no any - so, there are several reasons for that.

First is simple - there are no additional props of Popper that would allow client to customize positioning (actually, good thing). And here we are getting to the next alternative option that might come to mind (the one you've mentioned) - use styles for influencing these offsets.

I've tried this one - and have done that only to clearly see that even in LTR mode taken in isolation we have lots of issues. As an example: if it is about 'start' value for align property, which corresponds to popup aligned with the left side of trigger,

image

we might use left to specify an offset (as absolute positioning strategy is used by Popper). However, when we will achieve necessary result and switch to the ride sides alignment, we will see that Popper is using transform property to ensure alignment for the right sides, and here we cannot use offsets of left-right CSS properties anymore.

For the sake of not preventing havoc to wreak our code, decided to just reuse solution Popper has already introduced for solving exactly this problem (probably, the most reasonable thing) - and this, in fact, what we've done with the modifiers. Note, we are not abusing it - we are using the tool from Popper that is, actually, dedicated to solve this exact problem :)

}

return (
target && (
<Popper
placement={placement}
referenceElement={target}
children={this.renderPopperChildren.bind(this, popupPositionClasses, rtl, accessibility)}
modifiers={popperModifiers}
/>
)
)
Expand Down Expand Up @@ -316,4 +331,14 @@ export default class Popup extends AutoControlledComponent<Extendable<PopupProps
_.invoke(this.props, 'onOpenChange', eventArgs, { ...this.props, ...{ open: newValue } })
}
}

private applyRtlToOffset(offset: string, rtl: boolean, position: Position): string {
if (rtl && (position === 'above' || position === 'below')) {
return offset.trimLeft().startsWith('-')
? offset.trimLeft().replace(/^-\s*/, '')
: `-${offset}`
}

return offset
}
}