diff --git a/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js b/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js
index a2529856b7..70bdb3e4f8 100644
--- a/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js
+++ b/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js
@@ -166,6 +166,7 @@ class ComponentExample extends Component {
const LODASH = require('lodash')
const REACT = require('react')
const SEMANTIC_UI_REACT = require('semantic-ui-react')
+ let WIREFRAME
let COMMON
/* eslint-enable no-unused-vars */
@@ -191,6 +192,8 @@ class ComponentExample extends Component {
if (module === 'COMMON') {
const componentPath = examplePath.split(__PATH_SEP__).splice(0, 2).join(__PATH_SEP__)
COMMON = require(`docs/app/Examples/${componentPath}/common`)
+ } else if (module === 'WIREFRAME') {
+ WIREFRAME = require('docs/app/Examples/behaviors/Visibility/Wireframe').default
}
const constStatements = []
@@ -420,8 +423,10 @@ class ComponentExample extends Component {
{exampleElement}
- {this.renderJSX()}
- {this.renderHTML()}
+
+ {this.renderJSX()}
+ {this.renderHTML()}
+
)
diff --git a/docs/app/Examples/behaviors/Visibility/Settings/CallbackFrequencyExample.js b/docs/app/Examples/behaviors/Visibility/Settings/CallbackFrequencyExample.js
deleted file mode 100644
index 94bc3a1848..0000000000
--- a/docs/app/Examples/behaviors/Visibility/Settings/CallbackFrequencyExample.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import React, { Component } from 'react'
-import { Button, Checkbox, Divider, Grid, Segment, Visibility } from 'semantic-ui-react'
-
-import Wireframe from '../Wireframe'
-
-export default class VisibilityExample extends Component {
- state = {
- continuous: false,
- log: [],
- once: true,
- }
-
- updateLog = eventName => () => this.setState({ log: [eventName, ...this.state.log] })
-
- clearLog = () => this.setState({ log: [] })
-
- toggleOnce = () => this.setState({ once: !this.state.once })
-
- toggleContinuous = () => this.setState({ continuous: !this.state.continuous })
-
- render() {
- const { continuous, log, once } = this.state
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Event Log
-
-
- {log.map((e, i) => {e} fired
)}
-
-
-
-
-
- )
- }
-}
diff --git a/docs/app/Examples/behaviors/Visibility/Settings/GroupedCallbacksExample.js b/docs/app/Examples/behaviors/Visibility/Settings/GroupedCallbacksExample.js
deleted file mode 100644
index 7e78225358..0000000000
--- a/docs/app/Examples/behaviors/Visibility/Settings/GroupedCallbacksExample.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, { Component } from 'react'
-import { Button, Checkbox, Divider, Grid, Segment, Visibility } from 'semantic-ui-react'
-import Wireframe from '../Wireframe'
-
-class VisibilityExample extends Component {
- state = {
- continuous: false,
- log: [],
- once: true,
- }
-
- updateLog = eventName => () => this.setState({ log: [eventName, ...this.state.log] })
-
- clearLog = () => this.setState({ log: [] })
-
- toggleOnce = () => this.setState({ once: !this.state.once })
-
- toggleContinuous = () => this.setState({ continuous: !this.state.continuous })
-
- render() {
- const { continuous, log, once } = this.state
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Event Log
-
-
- {log.map((e, i) => {e} fired
)}
-
-
-
-
-
- )
- }
-}
-
-export default VisibilityExample
diff --git a/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleCallbackFrequency.js b/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleCallbackFrequency.js
new file mode 100644
index 0000000000..f2aaa0e5b3
--- /dev/null
+++ b/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleCallbackFrequency.js
@@ -0,0 +1,79 @@
+import React, { Component } from 'react'
+import { Button, Checkbox, Divider, Grid, Label, Segment, Sticky, Visibility } from 'semantic-ui-react'
+
+import Wireframe from '../Wireframe'
+
+export default class VisibilityExampleCallbackFrequency extends Component {
+ state = {
+ continuous: false,
+ log: [],
+ logCount: 0,
+ once: true,
+ }
+
+ handleContextRef = contextRef => this.setState({ contextRef })
+
+ updateLog = eventName => () => this.setState(({
+ log: [
+ `${new Date().toLocaleTimeString()}: ${eventName}`,
+ ...this.state.log,
+ ].slice(0, 20),
+ logCount: this.state.logCount + 1,
+ }))
+
+ clearLog = () => this.setState({ log: [], logCount: 0 })
+
+ toggleOnce = () => this.setState({ once: !this.state.once })
+
+ toggleContinuous = () => this.setState({ continuous: !this.state.continuous })
+
+ render() {
+ const { continuous, contextRef, log, logCount, once } = this.state
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Event Log
+
+
+ {log.map((e, i) => {e}
)}
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleGroupedCallbacks.js b/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleGroupedCallbacks.js
new file mode 100644
index 0000000000..9652bfb6a6
--- /dev/null
+++ b/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleGroupedCallbacks.js
@@ -0,0 +1,75 @@
+import React, { Component } from 'react'
+import { Button, Checkbox, Divider, Grid, Label, Segment, Sticky, Visibility } from 'semantic-ui-react'
+
+import Wireframe from '../Wireframe'
+
+export default class VisibilityExampleGroupedCallbacks extends Component {
+ state = {
+ continuous: false,
+ log: [],
+ logCount: 0,
+ once: true,
+ }
+
+ handleContextRef = contextRef => this.setState({ contextRef })
+
+ updateLog = eventName => () => this.setState(({
+ log: [
+ `${new Date().toLocaleTimeString()}: ${eventName}`,
+ ...this.state.log,
+ ].slice(0, 20),
+ logCount: this.state.logCount + 1,
+ }))
+
+ clearLog = () => this.setState({ log: [], logCount: 0 })
+
+ toggleOnce = () => this.setState({ once: !this.state.once })
+
+ toggleContinuous = () => this.setState({ continuous: !this.state.continuous })
+
+ render() {
+ const { continuous, contextRef, log, logCount, once } = this.state
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Event Log
+
+
+ {log.map((e, i) => {e}
)}
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleOffset.js b/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleOffset.js
new file mode 100644
index 0000000000..6b7a9c823e
--- /dev/null
+++ b/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleOffset.js
@@ -0,0 +1,66 @@
+import React, { Component } from 'react'
+import { Grid, Sticky, Table, Visibility } from 'semantic-ui-react'
+
+import Wireframe from '../Wireframe'
+
+export default class VisibilityExampleOffset extends Component {
+ state = {
+ calculations: {
+ topPassed: false,
+ bottomPassed: false,
+ topVisible: false,
+ bottomVisible: false,
+ },
+ }
+
+ handleContextRef = contextRef => this.setState({ contextRef })
+
+ handleUpdate = (e, { calculations }) => this.setState({ calculations })
+
+ render() {
+ const { calculations, contextRef } = this.state
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Calculation
+ Value
+
+
+
+
+ topVisible
+ {calculations.topVisible.toString()}
+
+
+ bottomVisible
+ {calculations.bottomVisible.toString()}
+
+
+ topPassed
+ {calculations.topPassed.toString()}
+
+
+ bottomPassed
+ {calculations.bottomPassed.toString()}
+
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/docs/app/Examples/behaviors/Visibility/Settings/index.js b/docs/app/Examples/behaviors/Visibility/Settings/index.js
index c68dbd1dfc..7c21456f3a 100644
--- a/docs/app/Examples/behaviors/Visibility/Settings/index.js
+++ b/docs/app/Examples/behaviors/Visibility/Settings/index.js
@@ -3,24 +3,32 @@ import React from 'react'
import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
-const VisibilityExample = () => (
+const VisibilitySettingsExamples = () => (
+
)
-export default VisibilityExample
+export default VisibilitySettingsExamples
diff --git a/docs/app/Examples/behaviors/Visibility/Types/VisibilityExample.js b/docs/app/Examples/behaviors/Visibility/Types/VisibilityExample.js
deleted file mode 100644
index 78f66398b7..0000000000
--- a/docs/app/Examples/behaviors/Visibility/Types/VisibilityExample.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import React, { Component } from 'react'
-import { Grid, Table, Visibility } from 'semantic-ui-react'
-import Wireframe from '../Wireframe'
-
-class VisibilityExample extends Component {
- state = {
- calculations: {
- height: 0,
- width: 0,
- topPassed: false,
- bottomPassed: false,
- pixelsPassed: 0,
- percentagePassed: 0,
- topVisible: false,
- bottomVisible: false,
- fits: false,
- passing: false,
- onScreen: false,
- offScreen: false,
- },
- }
-
- handleUpdate = (e, { calculations }) => this.setState({ calculations })
-
- render() {
- const { calculations } = this.state
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- Calculation
- Value
-
-
-
-
- pixelsPassed
- {calculations.pixelsPassed.toFixed()}px
-
-
- percentagePassed
- {(calculations.percentagePassed * 100).toFixed()}%
-
-
- fits
- {calculations.fits.toString()}
-
-
- width
- {calculations.width.toFixed()}px
-
-
- height
- {calculations.height.toFixed()}px
-
-
- onScreen
- {calculations.onScreen.toString()}
-
-
- offScreen
- {calculations.offScreen.toString()}
-
-
- passing
- {calculations.passing.toString()}
-
-
- topVisible
- {calculations.topVisible.toString()}
-
-
- bottomVisible
- {calculations.bottomVisible.toString()}
-
-
- topPassed
- {calculations.topPassed.toString()}
-
-
- bottomPassed
- {calculations.bottomPassed.toString()}
-
-
-
-
-
- )
- }
-}
-
-export default VisibilityExample
diff --git a/docs/app/Examples/behaviors/Visibility/Types/VisibilityExampleVisibility.js b/docs/app/Examples/behaviors/Visibility/Types/VisibilityExampleVisibility.js
new file mode 100644
index 0000000000..418c85f676
--- /dev/null
+++ b/docs/app/Examples/behaviors/Visibility/Types/VisibilityExampleVisibility.js
@@ -0,0 +1,106 @@
+import React, { Component } from 'react'
+import { Grid, Sticky, Table, Visibility } from 'semantic-ui-react'
+
+import Wireframe from '../Wireframe'
+
+export default class VisibilityExampleVisibility extends Component {
+ state = {
+ calculations: {
+ height: 0,
+ width: 0,
+ topPassed: false,
+ bottomPassed: false,
+ pixelsPassed: 0,
+ percentagePassed: 0,
+ topVisible: false,
+ bottomVisible: false,
+ fits: false,
+ passing: false,
+ onScreen: false,
+ offScreen: false,
+ },
+ }
+
+ handleContextRef = contextRef => this.setState({ contextRef })
+
+ handleUpdate = (e, { calculations }) => this.setState({ calculations })
+
+ render() {
+ const { calculations, contextRef } = this.state
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Calculation
+ Value
+
+
+
+
+ pixelsPassed
+ {calculations.pixelsPassed.toFixed()}px
+
+
+ percentagePassed
+ {(calculations.percentagePassed * 100).toFixed()}%
+
+
+ fits
+ {calculations.fits.toString()}
+
+
+ width
+ {calculations.width.toFixed()}px
+
+
+ height
+ {calculations.height.toFixed()}px
+
+
+ onScreen
+ {calculations.onScreen.toString()}
+
+
+ offScreen
+ {calculations.offScreen.toString()}
+
+
+ passing
+ {calculations.passing.toString()}
+
+
+ topVisible
+ {calculations.topVisible.toString()}
+
+
+ bottomVisible
+ {calculations.bottomVisible.toString()}
+
+
+ topPassed
+ {calculations.topPassed.toString()}
+
+
+ bottomPassed
+ {calculations.bottomPassed.toString()}
+
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/docs/app/Examples/behaviors/Visibility/Types/index.js b/docs/app/Examples/behaviors/Visibility/Types/index.js
index ceed463a2b..52ff021199 100644
--- a/docs/app/Examples/behaviors/Visibility/Types/index.js
+++ b/docs/app/Examples/behaviors/Visibility/Types/index.js
@@ -3,14 +3,14 @@ import React from 'react'
import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
-const VisibilityExample = () => (
+const VisibilityTypesExamples = () => (
)
-export default VisibilityExample
+export default VisibilityTypesExamples
diff --git a/src/behaviors/Visibility/Visibility.d.ts b/src/behaviors/Visibility/Visibility.d.ts
index 1ce1d60b90..9d6b24abf8 100644
--- a/src/behaviors/Visibility/Visibility.d.ts
+++ b/src/behaviors/Visibility/Visibility.d.ts
@@ -53,6 +53,12 @@ export interface VisibilityProps {
*/
onBottomVisibleReverse?: (nothing: null, data: VisibilityEventData) => void;
+ /**
+ * Value that context should be adjusted in pixels. Useful for making content appear below content fixed to the
+ * page.
+ */
+ offset?: number | string | Array;
+
/** When set to false a callback will occur each time an element passes the threshold for a condition. */
once?: boolean;
diff --git a/src/behaviors/Visibility/Visibility.js b/src/behaviors/Visibility/Visibility.js
index ef41889e02..3c4d9c5882 100644
--- a/src/behaviors/Visibility/Visibility.js
+++ b/src/behaviors/Visibility/Visibility.js
@@ -7,6 +7,7 @@ import {
getElementType,
getUnhandledProps,
META,
+ normalizeOffset,
isBrowser,
} from '../../lib'
@@ -65,6 +66,19 @@ export default class Visibility extends Component {
*/
onBottomVisibleReverse: PropTypes.func,
+ /**
+ * Value that context should be adjusted in pixels. Useful for making content appear below content fixed to the
+ * page.
+ */
+ offset: PropTypes.oneOfType([
+ PropTypes.number,
+ PropTypes.string,
+ PropTypes.arrayOf([
+ PropTypes.number,
+ PropTypes.string,
+ ]),
+ ]),
+
/** When set to false a callback will occur each time an element passes the threshold for a condition. */
once: PropTypes.bool,
@@ -147,6 +161,7 @@ export default class Visibility extends Component {
static defaultProps = {
context: isBrowser ? window : null,
continuous: false,
+ offset: [0, 0],
once: true,
}
@@ -280,16 +295,18 @@ export default class Visibility extends Component {
handleRef = c => (this.ref = c)
handleUpdate = () => {
+ const { offset } = this.props
const { bottom, height, top, width } = this.ref.getBoundingClientRect()
+ const [topOffset, bottomOffset] = normalizeOffset(offset)
- const topPassed = top < 0
- const bottomPassed = bottom < 0
+ const topPassed = top < topOffset
+ const bottomPassed = bottom < bottomOffset
const pixelsPassed = bottomPassed ? 0 : Math.max(top * -1, 0)
const percentagePassed = pixelsPassed / height
- const bottomVisible = bottom >= 0 && bottom <= window.innerHeight
- const topVisible = top >= 0 && top <= window.innerHeight
+ const bottomVisible = bottom >= bottomOffset && bottom <= window.innerHeight
+ const topVisible = top >= topOffset && top <= window.innerHeight
const fits = topVisible && bottomVisible
const passing = topPassed && !bottomPassed
diff --git a/src/lib/index.js b/src/lib/index.js
index eea44556e1..4778c777c9 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -39,6 +39,7 @@ export * as SUI from './SUI'
export { default as keyboardKey } from './keyboardKey'
export { numberToWordMap, numberToWord } from './numberToWord'
+export normalizeOffset from './normalizeOffset'
export normalizeTransitionDuration from './normalizeTransitionDuration'
export { default as objectDiff } from './objectDiff'
export shallowEqual from './shallowEqual'
diff --git a/src/lib/normalizeOffset.js b/src/lib/normalizeOffset.js
new file mode 100644
index 0000000000..861d02f90b
--- /dev/null
+++ b/src/lib/normalizeOffset.js
@@ -0,0 +1,6 @@
+/**
+ * Normalizes the offset value.
+ * @param {number|array} value The value to normalize.
+ * @returns {number}
+ */
+export default value => ((typeof value === 'number' || typeof value === 'string') ? [value, value] : value)
diff --git a/test/specs/behaviors/Visibility/Visibility-test.js b/test/specs/behaviors/Visibility/Visibility-test.js
index a9da20a139..244d372f4d 100644
--- a/test/specs/behaviors/Visibility/Visibility-test.js
+++ b/test/specs/behaviors/Visibility/Visibility-test.js
@@ -1,3 +1,4 @@
+import _ from 'lodash'
import React from 'react'
import Visibility from 'src/behaviors/Visibility'
@@ -200,6 +201,41 @@ describe('Visibility', () => {
})
})
+ describe('offset', () => {
+ _.each(_.filter(expectations, 'callback'), (expectation) => {
+ it(`fires ${expectation.name} when offset is number`, () => {
+ const callback = sandbox.spy()
+ const opts = { [expectation.callback]: callback }
+
+ const offset = 10
+ const falseCond = _.map(expectation.false[0], value => value + offset)
+ const trueCond = _.map(expectation.true[0], value => value + offset)
+
+ wrapperMount()
+ mockScroll(...trueCond)
+ mockScroll(...falseCond)
+
+ callback.should.have.been.calledOnce()
+ })
+
+ it(`fires ${expectation.name} when offset is array`, () => {
+ const callback = sandbox.spy()
+ const opts = { [expectation.callback]: callback }
+
+ const bottomOffset = 20
+ const topOffset = 10
+ const falseCond = [expectation.false[0][0] + topOffset, expectation.false[0][1] + bottomOffset]
+ const trueCond = [expectation.true[0][0] + topOffset, expectation.true[0][1] + bottomOffset]
+
+ wrapperMount()
+ mockScroll(...trueCond)
+ mockScroll(...falseCond)
+
+ callback.should.have.been.calledOnce()
+ })
+ })
+ })
+
describe('onPassed', () => {
it('fires callback when pixels passed', () => {
const onPassed = {