@dhis2-ui/popper 10.16.1 → 10.16.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhis2-ui/popper",
3
- "version": "10.16.1",
3
+ "version": "10.16.3-alpha.1",
4
4
  "description": "UI Popper",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@dhis2/prop-types": "^3.1.2",
36
- "@dhis2/ui-constants": "10.16.1",
36
+ "@dhis2/ui-constants": "10.16.3-alpha.1",
37
37
  "@popperjs/core": "^2.11.8",
38
38
  "classnames": "^2.3.1",
39
39
  "prop-types": "^15.7.2",
@@ -42,7 +42,8 @@
42
42
  },
43
43
  "files": [
44
44
  "build",
45
- "types"
45
+ "types",
46
+ "src"
46
47
  ],
47
48
  "devDependencies": {
48
49
  "react": "^18.3.1",
@@ -0,0 +1,58 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+ Given(
3
+ 'a Popper with placement bottom-start has a React Ref as its reference',
4
+ () => {
5
+ cy.visitStory('Popper', 'React Ref As Reference')
6
+ }
7
+ )
8
+ Given(
9
+ 'a Popper with placement bottom-start has a DOM node as its reference',
10
+ () => {
11
+ cy.visitStory('Popper', 'DOM Node As Reference')
12
+ }
13
+ )
14
+ Given(
15
+ 'a Popper with placement bottom-start has a virtual element as its reference',
16
+ () => {
17
+ cy.visitStory('Popper', 'Virtual Element As Reference')
18
+ }
19
+ )
20
+ Then(
21
+ 'the left of the popper is aligned with the left of the reference element',
22
+ () => {
23
+ cy.all(
24
+ () => cy.get('button:contains("Reference")'),
25
+ () => cy.get('[data-test="dhis2-uicore-popper"]')
26
+ ).should(([$reference, $popper]) => {
27
+ const referenceRect = $reference.get(0).getBoundingClientRect()
28
+ const popperRect = $popper.get(0).getBoundingClientRect()
29
+
30
+ expect(referenceRect.left).to.be.closeTo(popperRect.left, 1)
31
+ })
32
+ }
33
+ )
34
+ Then(
35
+ 'the top of the popper is adjacent to the bottom of the reference element',
36
+ () => {
37
+ cy.all(
38
+ () => cy.get('button:contains("Reference")'),
39
+ () => cy.get('[data-test="dhis2-uicore-popper"]')
40
+ ).should(([$button, $popper]) => {
41
+ const buttonRect = $button.get(0).getBoundingClientRect()
42
+ const popperRect = $popper.get(0).getBoundingClientRect()
43
+
44
+ expect(buttonRect.bottom).to.be.closeTo(popperRect.top, 1)
45
+ })
46
+ }
47
+ )
48
+ Then(
49
+ 'the top and left of the popper correspond with the virtualElement',
50
+ () => {
51
+ cy.get('[data-test="dhis2-uicore-popper"]').should(($popper) => {
52
+ const popperRect = $popper.get(0).getBoundingClientRect()
53
+
54
+ expect(popperRect.top).to.be.closeTo(200, 1)
55
+ expect(popperRect.left).to.be.closeTo(200, 1)
56
+ })
57
+ }
58
+ )
@@ -0,0 +1,15 @@
1
+ Feature: Accept different reference types
2
+
3
+ Scenario: Accepts a React Ref
4
+ Given a Popper with placement bottom-start has a React Ref as its reference
5
+ Then the top of the popper is adjacent to the bottom of the reference element
6
+ And the left of the popper is aligned with the left of the reference element
7
+
8
+ Scenario: Accepts a DOM node
9
+ Given a Popper with placement bottom-start has a DOM node as its reference
10
+ Then the top of the popper is adjacent to the bottom of the reference element
11
+ And the left of the popper is aligned with the left of the reference element
12
+
13
+ Scenario: Accepts a virtual element
14
+ Given a Popper with placement bottom-start has a virtual element as its reference
15
+ Then the top and left of the popper correspond with the virtualElement
@@ -0,0 +1,131 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ // Visit stories with different placements
4
+ Given('the Popper is rendered with placement top', () => {
5
+ cy.visitStory('Popper', 'top')
6
+ })
7
+ Given('the Popper is rendered with placement top-start', () => {
8
+ cy.visitStory('Popper', 'top-start')
9
+ })
10
+ Given('the Popper is rendered with placement top-end', () => {
11
+ cy.visitStory('Popper', 'top-end')
12
+ })
13
+ Given('the Popper is rendered with placement right', () => {
14
+ cy.visitStory('Popper', 'right')
15
+ })
16
+ Given('the Popper is rendered with placement right-start', () => {
17
+ cy.visitStory('Popper', 'right-start')
18
+ })
19
+ Given('the Popper is rendered with placement right-end', () => {
20
+ cy.visitStory('Popper', 'right-end')
21
+ })
22
+ Given('the Popper is rendered with placement bottom', () => {
23
+ cy.visitStory('Popper', 'bottom')
24
+ })
25
+ Given('the Popper is rendered with placement bottom-start', () => {
26
+ cy.visitStory('Popper', 'bottom-start')
27
+ })
28
+ Given('the Popper is rendered with placement bottom-end', () => {
29
+ cy.visitStory('Popper', 'bottom-end')
30
+ })
31
+ Given('the Popper is rendered with placement left', () => {
32
+ cy.visitStory('Popper', 'left')
33
+ })
34
+ Given('the Popper is rendered with placement left-start', () => {
35
+ cy.visitStory('Popper', 'left-start')
36
+ })
37
+ Given('the Popper is rendered with placement left-end', () => {
38
+ cy.visitStory('Popper', 'left-end')
39
+ })
40
+
41
+ // Directional assertions
42
+ // top
43
+ Then(
44
+ 'the bottom of the popper is adjacent to the top of the reference element',
45
+ () => {
46
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
47
+ expect(refPos.top).to.equal(popperPos.bottom)
48
+ })
49
+ }
50
+ )
51
+ // right
52
+ Then(
53
+ 'the left of the popper is adjacent to the right of the reference element',
54
+ () => {
55
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
56
+ expect(refPos.right).to.equal(popperPos.left)
57
+ })
58
+ }
59
+ )
60
+ // bottom
61
+ Then(
62
+ 'the top of the popper is adjacent to the bottom of the reference element',
63
+ () => {
64
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
65
+ expect(refPos.bottom).to.equal(popperPos.top)
66
+ })
67
+ }
68
+ )
69
+ // left
70
+ Then(
71
+ 'the right of the popper is adjacent to the left of the reference element',
72
+ () => {
73
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
74
+ expect(refPos.left).to.equal(popperPos.right)
75
+ })
76
+ }
77
+ )
78
+
79
+ // Horizontal alignments
80
+ // *-start
81
+ Then('it is horizontally left aligned with the reference element', () => {
82
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
83
+ expect(refPos.left).to.equal(popperPos.left)
84
+ })
85
+ })
86
+ // * (no suffix)
87
+ Then('it is horizontally center aligned with the reference element', () => {
88
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
89
+ const refCenterX = refPos.left + refPos.width / 2
90
+ const popperCenterX = popperPos.left + popperPos.width / 2
91
+
92
+ expect(refCenterX).to.equal(popperCenterX)
93
+ })
94
+ })
95
+ // *-end
96
+ Then('it is horizontally right aligned with the reference element', () => {
97
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
98
+ expect(refPos.right).to.equal(popperPos.right)
99
+ })
100
+ })
101
+
102
+ // Vertical alignments
103
+ // *-start
104
+ Then('it is vertically top aligned with the reference element', () => {
105
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
106
+ expect(refPos.top).to.equal(popperPos.top)
107
+ })
108
+ })
109
+ // * (no suffix)
110
+ Then('it is vertically center aligned with the reference element', () => {
111
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
112
+ const refCenterY = refPos.top + refPos.height / 2
113
+ const popperCenterY = popperPos.top + popperPos.height / 2
114
+
115
+ expect(refCenterY).to.equal(popperCenterY)
116
+ })
117
+ })
118
+ // *-end
119
+ Then('it is vertically bottom aligned with the reference element', () => {
120
+ getRefAndPopperPositions().should(([refPos, popperPos]) => {
121
+ expect(refPos.bottom).to.equal(popperPos.bottom)
122
+ })
123
+ })
124
+
125
+ // helper
126
+ function getRefAndPopperPositions() {
127
+ return cy.getPositionsBySelectors(
128
+ '.reference-element',
129
+ '[data-test="dhis2-uicore-popper"]'
130
+ )
131
+ }
@@ -0,0 +1,63 @@
1
+ Feature: Generic position options
2
+
3
+ # the definition of adjacent below: having a common endpoint or border
4
+
5
+ Scenario: Top position
6
+ Given the Popper is rendered with placement top
7
+ Then the bottom of the popper is adjacent to the top of the reference element
8
+ And it is horizontally center aligned with the reference element
9
+
10
+ Scenario: Top-start position
11
+ Given the Popper is rendered with placement top-start
12
+ Then the bottom of the popper is adjacent to the top of the reference element
13
+ And it is horizontally left aligned with the reference element
14
+
15
+ Scenario: Top-end position
16
+ Given the Popper is rendered with placement top-end
17
+ Then the bottom of the popper is adjacent to the top of the reference element
18
+ And it is horizontally right aligned with the reference element
19
+
20
+ Scenario: Right position
21
+ Given the Popper is rendered with placement right
22
+ Then the left of the popper is adjacent to the right of the reference element
23
+ And it is vertically center aligned with the reference element
24
+
25
+ Scenario: Right-start position
26
+ Given the Popper is rendered with placement right-start
27
+ Then the left of the popper is adjacent to the right of the reference element
28
+ And it is vertically top aligned with the reference element
29
+
30
+ Scenario: Right-end position
31
+ Given the Popper is rendered with placement right-end
32
+ Then the left of the popper is adjacent to the right of the reference element
33
+ And it is vertically bottom aligned with the reference element
34
+
35
+ Scenario: Bottom position
36
+ Given the Popper is rendered with placement bottom
37
+ Then the top of the popper is adjacent to the bottom of the reference element
38
+ And it is horizontally center aligned with the reference element
39
+
40
+ Scenario: Bottom-start position
41
+ Given the Popper is rendered with placement bottom-start
42
+ Then the top of the popper is adjacent to the bottom of the reference element
43
+ And it is horizontally left aligned with the reference element
44
+
45
+ Scenario: Bottom-end position
46
+ Given the Popper is rendered with placement bottom-end
47
+ Then the top of the popper is adjacent to the bottom of the reference element
48
+ And it is horizontally right aligned with the reference element
49
+
50
+ Scenario: Left position
51
+ Given the Popper is rendered with placement left
52
+ Then the right of the popper is adjacent to the left of the reference element
53
+ And it is vertically center aligned with the reference element
54
+
55
+ Scenario: Left-start position
56
+ Given the Popper is rendered with placement left-start
57
+ Then the right of the popper is adjacent to the left of the reference element
58
+ And it is vertically top aligned with the reference element
59
+
60
+ Scenario: Left-end position
61
+ Given the Popper is rendered with placement left-end
62
+ Then the right of the popper is adjacent to the left of the reference element
63
+ And it is vertically bottom aligned with the reference element
@@ -0,0 +1,16 @@
1
+ export const getReferenceElement = (reference) => {
2
+ // Elements or virtualElements
3
+ if (
4
+ reference instanceof Element ||
5
+ (reference && 'getBoundingClientRect' in reference)
6
+ ) {
7
+ return reference
8
+ }
9
+
10
+ // react refs
11
+ if (reference && 'current' in reference) {
12
+ return reference.current
13
+ }
14
+
15
+ return null
16
+ }
package/src/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { Popper } from './popper.js'
2
+ export { getReferenceElement } from './get-reference-element.js'
3
+ export { getBaseModifiers } from './modifiers.js'
4
+ export { usePopper } from 'react-popper'
@@ -0,0 +1,86 @@
1
+ import ResizeObserver from 'resize-observer-polyfill'
2
+
3
+ const attachResizeObservers = ({
4
+ state: { elements },
5
+ options,
6
+ instance: { update },
7
+ }) => {
8
+ const observers = Object.keys(options).reduce((acc, elementKey) => {
9
+ if (options[elementKey]) {
10
+ const observer = new ResizeObserver(update)
11
+ observer.observe(elements[elementKey])
12
+ acc.push(observer)
13
+ }
14
+ return acc
15
+ }, [])
16
+
17
+ return () => {
18
+ observers.forEach((observer) => {
19
+ observer.disconnect()
20
+ })
21
+ }
22
+ }
23
+
24
+ export const getBaseModifiers = ({
25
+ observePopperResize,
26
+ observeReferenceResize,
27
+ }) => [
28
+ {
29
+ name: 'preventOverflow',
30
+ options: {
31
+ altAxis: true,
32
+ rootBoundary: 'document',
33
+ boundary: document.body,
34
+ },
35
+ },
36
+ {
37
+ name: 'flip',
38
+ options: {
39
+ rootBoundary: 'document',
40
+ boundary: document.body,
41
+ },
42
+ },
43
+ {
44
+ name: 'resizeObserver',
45
+ enabled: true,
46
+ phase: 'write',
47
+ fn: () => {},
48
+ effect: attachResizeObservers,
49
+ options: {
50
+ popper: observePopperResize,
51
+ reference: observeReferenceResize,
52
+ },
53
+ },
54
+ ]
55
+
56
+ export const deduplicateModifiers = (modifiers, resizeObservers) => {
57
+ // Deduplicate modifiers from props and baseModifiers,
58
+ // when duplicates are encountered (by name), use the
59
+ // modifier from props so each Popper can be fully custom
60
+ return getBaseModifiers(resizeObservers)
61
+ .filter(({ name }) => !modifiers.some((m) => m.name === name))
62
+ .concat(modifiers)
63
+ }
64
+
65
+ export const resizeObserver = {
66
+ name: 'resizeObserver',
67
+ enabled: true,
68
+ phase: 'write',
69
+ fn: () => {},
70
+ effect: ({ state: { elements }, options, instance: { update } }) => {
71
+ const observers = Object.keys(options).reduce((acc, elementKey) => {
72
+ if (options[elementKey]) {
73
+ const observer = new ResizeObserver(update)
74
+ observer.observe(elements[elementKey])
75
+ acc.push(observer)
76
+ }
77
+ return acc
78
+ }, [])
79
+
80
+ return () => {
81
+ observers.forEach((observer) => {
82
+ observer.disconnect()
83
+ })
84
+ }
85
+ },
86
+ }
@@ -0,0 +1,123 @@
1
+ import PropTypes from 'prop-types'
2
+ import React, { useRef, useState } from 'react'
3
+ import { Popper } from './popper.js'
4
+
5
+ const PopperPlacement = ({ placement }) => {
6
+ const ref = useRef()
7
+
8
+ return (
9
+ <div className="box">
10
+ <div className="reference-element" ref={ref}>
11
+ Reference element
12
+ </div>
13
+ <Popper reference={ref} placement={placement}>
14
+ <div className="popper-content">Popper</div>
15
+ </Popper>
16
+ <style jsx>{`
17
+ .box {
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ width: 400px;
22
+ height: 400px;
23
+ margin-bottom: 1000px;
24
+ background-color: aliceblue;
25
+ }
26
+
27
+ .reference-element {
28
+ width: 100px;
29
+ height: 50px;
30
+ background-color: cadetblue;
31
+ text-align: center;
32
+ padding: 6px;
33
+ }
34
+
35
+ .popper-content {
36
+ width: 80px;
37
+ height: 30px;
38
+ background-color: lightblue;
39
+ text-align: center;
40
+ padding: 6px;
41
+ }
42
+ `}</style>
43
+ </div>
44
+ )
45
+ }
46
+
47
+ PopperPlacement.propTypes = {
48
+ placement: PropTypes.string,
49
+ }
50
+
51
+ export default {
52
+ title: 'Popper',
53
+ component: Popper,
54
+ }
55
+
56
+ export const Top = () => <PopperPlacement placement="top" />
57
+
58
+ export const TopStart = () => <PopperPlacement placement="top-start" />
59
+
60
+ export const TopEnd = () => <PopperPlacement placement="top-end" />
61
+
62
+ export const Bottom = () => <PopperPlacement placement="bottom" />
63
+
64
+ export const BottomStart = () => <PopperPlacement placement="bottom-start" />
65
+
66
+ export const BottomEnd = () => <PopperPlacement placement="bottom-end" />
67
+
68
+ export const Right = () => <PopperPlacement placement="right" />
69
+
70
+ export const RightStart = () => <PopperPlacement placement="right-start" />
71
+
72
+ export const RightEnd = () => <PopperPlacement placement="right-end" />
73
+
74
+ export const Left = () => <PopperPlacement placement="left" />
75
+
76
+ export const LeftStart = () => <PopperPlacement placement="left-start" />
77
+
78
+ export const LeftEnd = () => <PopperPlacement placement="left-end" />
79
+
80
+ export const ReactRefAsReference = () => {
81
+ const ref = useRef()
82
+
83
+ return (
84
+ <>
85
+ <button ref={ref}>Reference</button>
86
+ <Popper placement="bottom-start" reference={ref}>
87
+ Popper
88
+ </Popper>
89
+ </>
90
+ )
91
+ }
92
+
93
+ export const DOMNodeAsReference = () => {
94
+ const [node, setNode] = useState(null)
95
+
96
+ return (
97
+ <>
98
+ <button ref={setNode}>Reference</button>
99
+ <Popper placement="bottom-start" reference={node}>
100
+ Popper
101
+ </Popper>
102
+ </>
103
+ )
104
+ }
105
+
106
+ export const VirtualElementAsReference = () => {
107
+ const virtualElement = {
108
+ getBoundingClientRect: () => ({
109
+ top: 200,
110
+ left: 200,
111
+ bottom: 'auto',
112
+ right: 'auto',
113
+ width: 0,
114
+ height: 0,
115
+ }),
116
+ }
117
+
118
+ return (
119
+ <Popper placement="bottom-start" reference={virtualElement}>
120
+ Popper
121
+ </Popper>
122
+ )
123
+ }
package/src/popper.js ADDED
@@ -0,0 +1,101 @@
1
+ import { sharedPropTypes } from '@dhis2/ui-constants'
2
+ import PropTypes from 'prop-types'
3
+ import React, { useState, useMemo, useEffect } from 'react'
4
+ import { usePopper } from 'react-popper'
5
+ import { getReferenceElement } from './get-reference-element.js'
6
+ import { deduplicateModifiers } from './modifiers.js'
7
+
8
+ const flipPlacement = (placement) => {
9
+ if (placement.startsWith('right')) {
10
+ return placement.replace('right', 'left')
11
+ }
12
+ if (placement.startsWith('left')) {
13
+ return placement.replace('left', 'right')
14
+ }
15
+ return placement
16
+ }
17
+
18
+ // Stable object reference
19
+ const staticArray = []
20
+ const Popper = ({
21
+ children,
22
+ className,
23
+ dataTest = 'dhis2-uicore-popper',
24
+ modifiers = staticArray,
25
+ observePopperResize,
26
+ observeReferenceResize,
27
+ onFirstUpdate,
28
+ placement = 'auto',
29
+ reference,
30
+ strategy,
31
+ }) => {
32
+ const referenceElement = getReferenceElement(reference)
33
+ const [popperElement, setPopperElement] = useState(null)
34
+
35
+ const deduplicatedModifiers = useMemo(
36
+ () =>
37
+ deduplicateModifiers(modifiers, {
38
+ observePopperResize,
39
+ observeReferenceResize,
40
+ }),
41
+ [modifiers, observePopperResize, observeReferenceResize]
42
+ )
43
+
44
+ const { styles, attributes } = usePopper(referenceElement, popperElement, {
45
+ strategy,
46
+ onFirstUpdate,
47
+ placement:
48
+ document.documentElement.dir === 'rtl'
49
+ ? flipPlacement(placement)
50
+ : placement,
51
+ modifiers: deduplicatedModifiers,
52
+ })
53
+
54
+ useEffect(() => {
55
+ if (popperElement) {
56
+ popperElement?.firstElementChild?.focus()
57
+ }
58
+ }, [popperElement])
59
+
60
+ return (
61
+ <div
62
+ className={className}
63
+ data-test={dataTest}
64
+ ref={setPopperElement}
65
+ style={styles.popper}
66
+ {...attributes.popper}
67
+ tabIndex={0}
68
+ >
69
+ {children}
70
+ </div>
71
+ )
72
+ }
73
+
74
+ // Prop names follow the names here: https://popper.js.org/docs/v2/constructors/
75
+ Popper.propTypes = {
76
+ /** Content inside the Popper */
77
+ children: PropTypes.node.isRequired,
78
+ className: PropTypes.string,
79
+ dataTest: PropTypes.string,
80
+ /** A property of the `createPopper` options. See [popper docs](https://popper.js.org/docs/v2/constructors/) */
81
+ modifiers: PropTypes.arrayOf(
82
+ PropTypes.shape({
83
+ name: PropTypes.string,
84
+ options: PropTypes.object,
85
+ })
86
+ ),
87
+ /** Makes the Popper update position when the **Popper content** changes size */
88
+ observePopperResize: PropTypes.bool,
89
+ /** Makes the Popper update position when the **reference element** changes size */
90
+ observeReferenceResize: PropTypes.bool,
91
+ /** A property of the `createPopper` options. See [popper docs](https://popper.js.org/docs/v2/constructors/) */
92
+ placement: sharedPropTypes.popperPlacementPropType,
93
+ /** A React ref, DOM node, or [virtual element](https://popper.js.org/docs/v2/virtual-elements/) for the popper to position itself against */
94
+ reference: sharedPropTypes.popperReferencePropType,
95
+ /** A property of the `createPopper` options. See [popper docs](https://popper.js.org/docs/v2/constructors/) */
96
+ strategy: PropTypes.oneOf(['absolute', 'fixed']), // defaults to 'absolute'
97
+ /** A property of the `createPopper` options. See [popper docs](https://popper.js.org/docs/v2/constructors/) */
98
+ onFirstUpdate: PropTypes.func,
99
+ }
100
+
101
+ export { Popper }
@@ -0,0 +1,162 @@
1
+ import { sharedPropTypes } from '@dhis2/ui-constants'
2
+ import React, { useEffect, useRef } from 'react'
3
+ import { Popper } from './popper.js'
4
+
5
+ const description = `
6
+ A tool for adding additional information or content outside of the document flow, used for example in the Tooltip or Popover components.
7
+
8
+ Since it's built using [Popper.js](https://popper.js.org/docs/v2/) and [react-popper](https://popper.js.org/react-popper/), some of that functionality can be accessed through the props of this component, like modifiers.
9
+
10
+ \`\`\`js
11
+ import { Popper } from '@dhis2/ui'
12
+ \`\`\`
13
+
14
+ _**Note**: Some of the stories may not look right on this page. View those examples in the 'Canvas' tab instead._
15
+ `
16
+
17
+ export default {
18
+ title: 'Popper',
19
+ component: Popper,
20
+ parameters: { docs: { description: { component: description } } },
21
+ argTypes: {
22
+ placement: { ...sharedPropTypes.popperPlacementArgType },
23
+ reference: { ...sharedPropTypes.popperReferenceArgType },
24
+ },
25
+ }
26
+
27
+ const boxStyle = {
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ justifyContent: 'center',
31
+ width: 400,
32
+ height: 400,
33
+ backgroundColor: 'aliceblue',
34
+ }
35
+
36
+ const referenceElementStyle = {
37
+ width: 130,
38
+ height: 50,
39
+ backgroundColor: 'cadetblue',
40
+ textAlign: 'center',
41
+ padding: 6,
42
+ }
43
+
44
+ const popperStyle = {
45
+ width: 110,
46
+ height: 30,
47
+ backgroundColor: 'lightblue',
48
+ textAlign: 'center',
49
+ padding: 6,
50
+ }
51
+
52
+ const Template = (args) => {
53
+ const ref = useRef(null)
54
+
55
+ return (
56
+ <div className="box" style={boxStyle}>
57
+ <div
58
+ className="reference-element"
59
+ style={referenceElementStyle}
60
+ ref={ref}
61
+ >
62
+ Reference Element
63
+ </div>
64
+ <Popper {...args} reference={ref}>
65
+ <div style={popperStyle}>{args.placement}</div>
66
+ </Popper>
67
+ </div>
68
+ )
69
+ }
70
+
71
+ export const Top = Template.bind({})
72
+ Top.args = { placement: 'top' }
73
+
74
+ export const TopStart = Template.bind({})
75
+ TopStart.args = { placement: 'top-start' }
76
+
77
+ export const TopEnd = Template.bind({})
78
+ TopEnd.args = { placement: 'top-end' }
79
+
80
+ export const Bottom = Template.bind({})
81
+ Bottom.args = { placement: 'bottom' }
82
+
83
+ export const BottomStart = Template.bind({})
84
+ BottomStart.args = { placement: 'bottom-start' }
85
+
86
+ export const BottomEnd = Template.bind({})
87
+ BottomEnd.args = { placement: 'bottom-end' }
88
+
89
+ export const Right = Template.bind({})
90
+ Right.args = { placement: 'right' }
91
+
92
+ export const RightStart = Template.bind({})
93
+ RightStart.args = { placement: 'right-start' }
94
+
95
+ export const RightEnd = Template.bind({})
96
+ RightEnd.args = { placement: 'right-end' }
97
+
98
+ export const Left = Template.bind({})
99
+ Left.args = { placement: 'left' }
100
+
101
+ export const LeftStart = Template.bind({})
102
+ LeftStart.args = { placement: 'left-start' }
103
+
104
+ export const LeftEnd = Template.bind({})
105
+ LeftEnd.args = { placement: 'left-end' }
106
+
107
+ export const ElementRef = (args) => {
108
+ const anchor = document.createElement('div')
109
+ document.body.appendChild(anchor)
110
+
111
+ return (
112
+ <div className="box" style={{ ...boxStyle, marginBottom: '500px' }}>
113
+ <Popper {...args} reference={anchor}>
114
+ <div style={popperStyle}>{args.placement}</div>
115
+ </Popper>
116
+ </div>
117
+ )
118
+ }
119
+ ElementRef.args = { placement: 'left-end' }
120
+ ElementRef.parameters = { docs: { source: { type: 'code' } } }
121
+
122
+ export const VirtualElementRef = (args) => {
123
+ const virtualElement = {
124
+ getBoundingClientRect: () => ({
125
+ width: 0,
126
+ height: 0,
127
+ top: 100,
128
+ right: 0,
129
+ bottom: 0,
130
+ left: 200,
131
+ x: 200,
132
+ y: 100,
133
+ }),
134
+ }
135
+
136
+ return (
137
+ <div className="box" style={{ ...boxStyle, marginBottom: '500px' }}>
138
+ <Popper {...args} reference={virtualElement}>
139
+ <div style={popperStyle}>{args.placement}</div>
140
+ </Popper>
141
+ </div>
142
+ )
143
+ }
144
+ VirtualElementRef.args = { placement: 'left-end' }
145
+ VirtualElementRef.parameters = { docs: { source: { type: 'code' } } }
146
+
147
+ export const RTL = (args) => {
148
+ useEffect(() => {
149
+ document.documentElement.setAttribute('dir', 'rtl')
150
+ return () => {
151
+ document.documentElement.setAttribute('dir', 'ltr')
152
+ }
153
+ }, [])
154
+ return (
155
+ <div dir="rtl">
156
+ <span>If dir=rtl, `left` and `right` placement are reversed</span>
157
+ <Template {...args} placement="left" />
158
+ <br />
159
+ <Template {...args} placement="right-start" />
160
+ </div>
161
+ )
162
+ }