Skip to content

Commit d7dabbd

Browse files
authored
feat(Popup): add popperDependencies prop (#3657)
* feat(Popup): add `popperDependencies` prop * Update src/modules/Popup/Popup.js Co-Authored-By: Youngrok Kim <rok0810@gmail.com> * add UTs, fix condition * simplify implemention
1 parent 7a8ba9a commit d7dabbd

File tree

5 files changed

+121
-2
lines changed

5 files changed

+121
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import faker from 'faker'
2+
import React from 'react'
3+
import { Button, Header, Placeholder, Popup } from 'semantic-ui-react'
4+
5+
const PopupExamplePopperDependencies = () => {
6+
const [data, setData] = React.useState(null)
7+
const timer = React.useRef()
8+
9+
return (
10+
<Popup
11+
on='click'
12+
onClose={() => {
13+
setData(null)
14+
clearTimeout(timer.current)
15+
}}
16+
onOpen={() => {
17+
setData(null)
18+
19+
timer.current = setTimeout(() => {
20+
setData({
21+
description: faker.lorem.sentences(5),
22+
name: faker.name.firstName(),
23+
title: faker.name.title(),
24+
})
25+
}, 2000)
26+
}}
27+
popperDependencies={[!!data]}
28+
trigger={<Button content='Simulate loading' icon='lab' />}
29+
wide
30+
>
31+
{data === null ? (
32+
<Placeholder style={{ minWidth: '200px' }}>
33+
<Placeholder.Header>
34+
<Placeholder.Line />
35+
<Placeholder.Line />
36+
</Placeholder.Header>
37+
<Placeholder.Paragraph>
38+
<Placeholder.Line length='medium' />
39+
<Placeholder.Line length='short' />
40+
</Placeholder.Paragraph>
41+
</Placeholder>
42+
) : (
43+
<React.Fragment>
44+
<Header as='h2' content={data.name} subheader={data.title} />
45+
<p>{data.description}</p>
46+
</React.Fragment>
47+
)}
48+
</Popup>
49+
)
50+
}
51+
52+
export default PopupExamplePopperDependencies

docs/src/examples/modules/Popup/Usage/index.js

+19
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,25 @@ const PopupUsageExamples = () => (
7979
examplePath='modules/Popup/Usage/PopupExampleDefaultOpen'
8080
renderHtml={false}
8181
/>
82+
<ComponentExample
83+
title='Popper Dependencies'
84+
description={
85+
<span>
86+
A popup can have dependencies which update will schedule a position
87+
update. Should be used in cases when content is changing, behaves like{' '}
88+
<a
89+
href='https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect'
90+
rel='noopener noreferrer'
91+
target='_blank'
92+
>
93+
<code>dependencies</code>
94+
</a>{' '}
95+
in React Hooks.
96+
</span>
97+
}
98+
examplePath='modules/Popup/Usage/PopupExamplePopperDependencies'
99+
renderHtml={false}
100+
/>
82101
</ExampleSection>
83102
)
84103

src/modules/Popup/Popup.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ export interface StrictPopupProps extends StrictPortalProps {
107107
/** An object containing custom settings for the Popper.js modifiers. */
108108
popperModifiers?: object
109109

110+
/** A popup can have dependencies which update will schedule a position update. */
111+
popperDependencies?: any[]
112+
110113
/** Popup size. */
111114
size?: 'mini' | 'tiny' | 'small' | 'large' | 'huge'
112115

src/modules/Popup/Popup.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import _ from 'lodash'
44
import PropTypes from 'prop-types'
55
import React, { Component, createRef } from 'react'
66
import { Popper } from 'react-popper'
7+
import shallowEqual from 'shallowequal'
78

89
import {
910
eventStack,
@@ -126,6 +127,9 @@ export default class Popup extends Component {
126127
/** An object containing custom settings for the Popper.js modifiers. */
127128
popperModifiers: PropTypes.object,
128129

130+
/** A popup can have dependencies which update will schedule a position update. */
131+
popperDependencies: PropTypes.array,
132+
129133
/** Popup size. */
130134
size: PropTypes.oneOf(_.without(SUI.SIZES, 'medium', 'big', 'massive')),
131135

@@ -151,6 +155,8 @@ export default class Popup extends Component {
151155
static Header = PopupHeader
152156

153157
state = {}
158+
159+
open = false
154160
triggerRef = createRef()
155161

156162
static getDerivedStateFromProps(props, state) {
@@ -171,6 +177,14 @@ export default class Popup extends Component {
171177
return { contentRestProps, portalRestProps }
172178
}
173179

180+
componentDidUpdate(prevProps) {
181+
const depsEqual = shallowEqual(this.props.popperDependencies, prevProps.popperDependencies)
182+
183+
if (!depsEqual) {
184+
this.handleUpdate()
185+
}
186+
}
187+
174188
componentWillUnmount() {
175189
clearTimeout(this.timeoutId)
176190
}
@@ -235,10 +249,21 @@ export default class Popup extends Component {
235249

236250
handlePortalUnmount = (e) => {
237251
debug('handlePortalUnmount()')
252+
253+
this.positionUpdate = null
238254
_.invoke(this.props, 'onUnmount', e, this.props)
239255
}
240256

241-
renderContent = ({ placement: popperPlacement, ref: popperRef, style: popperStyle }) => {
257+
handleUpdate() {
258+
if (this.positionUpdate) this.positionUpdate()
259+
}
260+
261+
renderContent = ({
262+
placement: popperPlacement,
263+
ref: popperRef,
264+
scheduleUpdate,
265+
style: popperStyle,
266+
}) => {
242267
const {
243268
basic,
244269
children,
@@ -254,6 +279,8 @@ export default class Popup extends Component {
254279
} = this.props
255280
const { contentRestProps } = this.state
256281

282+
this.positionUpdate = scheduleUpdate
283+
257284
const classes = cx(
258285
'ui',
259286
placementMapping[popperPlacement],

test/specs/modules/Popup/Popup-test.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ describe('Popup', () => {
276276
})
277277

278278
describe('popperModifiers', () => {
279-
it(`are passed to Popper`, () => {
279+
it('are passed to Popper', () => {
280280
const modifiers = {
281281
keepTogether: { enabled: false },
282282
preventOverflow: { padding: 0 },
@@ -290,6 +290,24 @@ describe('Popup', () => {
290290
})
291291
})
292292

293+
describe('popperDependencies', () => {
294+
it('will call "scheduleUpdate" if dependencies changed', () => {
295+
wrapperMount(<Popup popperDependencies={[1, 2, 3]} />)
296+
const scheduleUpdate = sandbox.spy(wrapper.instance(), 'handleUpdate')
297+
298+
wrapper.setProps({ popperDependencies: [2, 3, 4] })
299+
scheduleUpdate.should.have.been.calledOnce()
300+
})
301+
302+
it('will skip "scheduleUpdate" if dependencies are same', () => {
303+
wrapperMount(<Popup popperDependencies={[1, 2, 3]} />)
304+
const scheduleUpdate = sandbox.spy(wrapper.instance(), 'handleUpdate')
305+
306+
wrapper.setProps({ popperDependencies: [1, 2, 3] })
307+
scheduleUpdate.should.have.not.been.called()
308+
})
309+
})
310+
293311
describe('size', () => {
294312
const sizes = _.without(SUI.SIZES, 'medium', 'big', 'massive')
295313

0 commit comments

Comments
 (0)