diff --git a/README.md b/README.md
index bb4083024c..17ebfddf6c 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ These great products are built on Semantic UI React. Add yours [here][22].
- https://www.stackforge.co
- https://sublimefund.org
- https://thefaithcircle.com
+- https://appfollow.io
## Voice Your Opinion
diff --git a/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js b/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js
index a2529856b7..54a05f986a 100644
--- a/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js
+++ b/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js
@@ -8,21 +8,15 @@ import { html } from 'js-beautify'
import copyToClipboard from 'copy-to-clipboard'
import { exampleContext, repoURL, scrollToAnchor } from 'docs/app/utils'
-import { Divider, Grid, Header, Menu } from 'src'
+import { Divider, Grid, Menu } from 'src'
import Editor from 'docs/app/Components/Editor/Editor'
import ComponentControls from '../ComponentControls'
+import ComponentExampleTitle from './ComponentExampleTitle'
const babelConfig = {
presets: ['es2015', 'react', 'stage-1'],
}
-const titleStyle = {
- margin: 0,
-}
-const descriptionStyle = {
- maxWidth: '50rem',
-}
-
const headerColumnStyle = {
// provide room for absolutely positions toggle code icons
minHeight: '4em',
@@ -53,6 +47,7 @@ class ComponentExample extends Component {
history: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
+ suiVersion: PropTypes.string,
title: PropTypes.node,
}
@@ -166,6 +161,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 +187,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 = []
@@ -374,7 +372,7 @@ class ComponentExample extends Component {
}
render() {
- const { children, description, title } = this.props
+ const { children, description, suiVersion, title } = this.props
const { controlsVisible, exampleElement, showCode, showHTML } = this.state
const exampleStyle = {}
@@ -392,8 +390,11 @@ class ComponentExample extends Component {
>
- {title && }
- {description && {description}
}
+
{exampleElement}
- {this.renderJSX()}
- {this.renderHTML()}
+
+ {this.renderJSX()}
+ {this.renderHTML()}
+
)
diff --git a/docs/app/Components/ComponentDoc/ComponentExample/ComponentExampleTitle.js b/docs/app/Components/ComponentDoc/ComponentExample/ComponentExampleTitle.js
new file mode 100644
index 0000000000..151c7a7ae6
--- /dev/null
+++ b/docs/app/Components/ComponentDoc/ComponentExample/ComponentExampleTitle.js
@@ -0,0 +1,40 @@
+import PropTypes from 'prop-types'
+import React from 'react'
+import { Header, Label } from 'semantic-ui-react'
+
+import { pure } from 'docs/app/HOC'
+
+const titleStyle = {
+ margin: 0,
+}
+const descriptionStyle = {
+ maxWidth: '50rem',
+}
+
+const ComponentExampleTitle = ({ description, title, suiVersion }) => (
+
+ {title && (
+
+ {title}
+ {suiVersion && (
+
+ )}
+
+ )}
+ {description &&
{description}
}
+
+)
+
+ComponentExampleTitle.propTypes = {
+ description: PropTypes.node,
+ title: PropTypes.node,
+ suiVersion: PropTypes.string,
+}
+
+export default pure(ComponentExampleTitle)
diff --git a/docs/app/Components/ComponentDoc/ShorthandExample.js b/docs/app/Components/ComponentDoc/ShorthandExample.js
new file mode 100644
index 0000000000..1b67f9921a
--- /dev/null
+++ b/docs/app/Components/ComponentDoc/ShorthandExample.js
@@ -0,0 +1,16 @@
+import PropTypes from 'prop-types'
+import React from 'react'
+
+import ComponentExample from './ComponentExample'
+
+const ShorthandExample = ({ description, ...rest }) =>
+
+ShorthandExample.propTypes = {
+ description: PropTypes.node,
+}
+
+ShorthandExample.defaultProps = {
+ description: 'You can do the same using shorthands.',
+}
+
+export default ShorthandExample
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/Types/VisibilityExample.js b/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleFireOnMount.js
similarity index 77%
rename from docs/app/Examples/behaviors/Visibility/Types/VisibilityExample.js
rename to docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleFireOnMount.js
index 78f66398b7..1193681e9c 100644
--- a/docs/app/Examples/behaviors/Visibility/Types/VisibilityExample.js
+++ b/docs/app/Examples/behaviors/Visibility/Settings/VisibilityExampleFireOnMount.js
@@ -1,8 +1,7 @@
import React, { Component } from 'react'
-import { Grid, Table, Visibility } from 'semantic-ui-react'
-import Wireframe from '../Wireframe'
+import { Divider, Grid, Image, Table, Segment, Visibility } from 'semantic-ui-react'
-class VisibilityExample extends Component {
+export default class VisibilityExampleFireOnMount extends Component {
state = {
calculations: {
height: 0,
@@ -20,7 +19,9 @@ class VisibilityExample extends Component {
},
}
- handleUpdate = (e, { calculations }) => this.setState({ calculations })
+ handleOnScreen = (e, { calculations }) => this.setState({ calculations })
+
+ handleOffScreen = (e, { calculations }) => this.setState({ calculations })
render() {
const { calculations } = this.state
@@ -28,8 +29,20 @@ class VisibilityExample extends Component {
return (
-
-
+
+
+
+
+
+
+
+
+
+
@@ -97,5 +110,3 @@ class VisibilityExample extends Component {
)
}
}
-
-export default VisibilityExample
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 e2f372d761..7c21456f3a 100644
--- a/docs/app/Examples/behaviors/Visibility/Settings/index.js
+++ b/docs/app/Examples/behaviors/Visibility/Settings/index.js
@@ -3,19 +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/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 08f39c1ec4..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/docs/app/Examples/collections/Menu/Types/index.js b/docs/app/Examples/collections/Menu/Types/index.js
index 1ea5b94f87..ca12fe5cf6 100644
--- a/docs/app/Examples/collections/Menu/Types/index.js
+++ b/docs/app/Examples/collections/Menu/Types/index.js
@@ -2,6 +2,7 @@ import React from 'react'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
+import ShorthandExample from 'docs/app/Components/ComponentDoc/ShorthandExample'
// TODO: Add example with after it will be added
@@ -12,10 +13,7 @@ const Types = () => (
description='A menu.'
examplePath='collections/Menu/Types/MenuExampleBasic'
/>
-
+
(
@@ -20,11 +21,7 @@ const States = () => (
description='A cell or row may warn a user.'
examplePath='collections/Table/States/TableExampleWarning'
/>
-
+
(
@@ -19,7 +21,7 @@ const ButtonGroupVariationsExamples = () => (
description='Groups can be formatted as icons.'
examplePath='elements/Button/GroupVariations/ButtonExampleGroupIcon'
/>
-
diff --git a/docs/app/Examples/elements/Label/Content/index.js b/docs/app/Examples/elements/Label/Content/index.js
index 2fb6153e51..3d5d78a31d 100644
--- a/docs/app/Examples/elements/Label/Content/index.js
+++ b/docs/app/Examples/elements/Label/Content/index.js
@@ -2,6 +2,7 @@ import React from 'react'
import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
+import ShorthandExample from 'docs/app/Components/ComponentDoc/ShorthandExample'
const LabelContent = () => (
@@ -16,20 +17,14 @@ const LabelContent = () => (
description='A label can include an icon'
examplePath='elements/Label/Content/LabelExampleIcon'
/>
-
+
-
+
(
@@ -11,19 +12,13 @@ const ListTypes = () => (
description='A list groups related content'
examplePath='elements/List/Types/ListExampleBasic'
/>
-
+
-
+
diff --git a/docs/app/Examples/elements/Loader/Types/index.js b/docs/app/Examples/elements/Loader/Types/index.js
index ea360941ef..b656ee3e47 100644
--- a/docs/app/Examples/elements/Loader/Types/index.js
+++ b/docs/app/Examples/elements/Loader/Types/index.js
@@ -3,6 +3,7 @@ import { Message } from 'semantic-ui-react'
import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
+import ShorthandExample from 'docs/app/Components/ComponentDoc/ShorthandExample'
const LoaderTypesExamples = () => (
@@ -21,10 +22,7 @@ const LoaderTypesExamples = () => (
description='A loader can contain text.'
examplePath='elements/Loader/Types/LoaderExampleText'
/>
-
+
)
diff --git a/docs/app/Examples/elements/Step/Variations/index.js b/docs/app/Examples/elements/Step/Variations/index.js
index 6c0a281995..fc04b34268 100644
--- a/docs/app/Examples/elements/Step/Variations/index.js
+++ b/docs/app/Examples/elements/Step/Variations/index.js
@@ -15,6 +15,7 @@ const StepVariationsExamples = () => (
title='Unstackable'
description='A step can prevent itself from stacking on mobile.'
examplePath='elements/Step/Variations/StepExampleUnstackable'
+ suiVersion='2.2.11'
/>
(
@@ -17,11 +18,11 @@ const CheckboxTypesExamples = () => (
description='A box for checking.'
examplePath='modules/Checkbox/Types/CheckboxExampleCheckbox'
/>
-
-
diff --git a/docs/app/Examples/modules/Dropdown/Usage/index.js b/docs/app/Examples/modules/Dropdown/Usage/index.js
index 25143eb24d..50ef11b701 100644
--- a/docs/app/Examples/modules/Dropdown/Usage/index.js
+++ b/docs/app/Examples/modules/Dropdown/Usage/index.js
@@ -1,6 +1,8 @@
import React from 'react'
+
import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
+import ShorthandExample from 'docs/app/Components/ComponentDoc/ShorthandExample'
const DropdownUsageExamples = () => (
@@ -76,7 +78,7 @@ const DropdownUsageExamples = () => (
description='A dropdown item can be rendered differently inside the menu.'
examplePath='modules/Dropdown/Usage/DropdownExampleItemContent'
/>
- (
/>
- (
title='Scrolling Content'
description='A modal can use the entire size of the screen.'
examplePath='modules/Modal/Variations/ModalExampleScrollingContent'
+ suiVersion='2.2.11'
/>
(
@@ -15,7 +16,7 @@ const SearchVariationsExamples = () => (
description='A search can have its results aligned to its left or right container edge.'
examplePath='modules/Search/Variations/SearchExampleAligned'
/>
- (
@@ -19,7 +21,7 @@ const TabUsageExamples = () => (
description='You can capture the tab change event.'
examplePath='modules/Tab/Usage/TabExampleOnTabChange'
/>
- (
-
+
+
-
+
-
+
+
-
+
+
-
-
+
+
-
+
)
diff --git a/docs/app/HOC/index.js b/docs/app/HOC/index.js
index f65e11d07d..072a6b5e7c 100644
--- a/docs/app/HOC/index.js
+++ b/docs/app/HOC/index.js
@@ -1,2 +1,3 @@
export neverUpdate from './neverUpdate'
+export pure from './pure'
export updateForKeys from './updateForKeys'
diff --git a/docs/app/HOC/pure.js b/docs/app/HOC/pure.js
new file mode 100644
index 0000000000..f0d68ab18f
--- /dev/null
+++ b/docs/app/HOC/pure.js
@@ -0,0 +1,15 @@
+import React, { Component } from 'react'
+
+import { shallowEqual } from 'src/lib'
+
+const pure = ChildComponent => class extends Component {
+ shouldComponentUpdate(nextProps) {
+ return !shallowEqual(this.props, nextProps)
+ }
+
+ render() {
+ return
+ }
+}
+
+export default pure
diff --git a/index.d.ts b/index.d.ts
index 167d3b1842..df2212038c 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -3,7 +3,7 @@ export { default as Confirm, ConfirmProps } from './dist/commonjs/addons/Confirm
export { default as Portal, PortalProps } from './dist/commonjs/addons/Portal';
export { default as Radio, RadioProps } from './dist/commonjs/addons/Radio';
export { default as Select, SelectProps } from './dist/commonjs/addons/Select';
-export { default as TextArea, TextAreaProps, TextAreaOnChangeData } from './dist/commonjs/addons/TextArea';
+export { default as TextArea, TextAreaProps } from './dist/commonjs/addons/TextArea';
// Behaviors
export {
diff --git a/karma.conf.babel.js b/karma.conf.babel.js
index f1ea507c82..4ee0d06c5f 100644
--- a/karma.conf.babel.js
+++ b/karma.conf.babel.js
@@ -1,6 +1,13 @@
+import puppeteerPkg from 'puppeteer/package.json'
+import Downloader from 'puppeteer/utils/ChromiumDownloader'
import config from './config'
import webpackConfig from './webpack.config.babel'
+const revision = puppeteerPkg.puppeteer.chromium_revision
+const revisionInfo = Downloader.revisionInfo(Downloader.currentPlatform(), revision)
+
+process.env.CHROME_BIN = revisionInfo.executablePath
+
const formatError = (msg) => {
// filter out empty lines and node_modules
if (!msg.trim() || /~/.test(msg)) return ''
@@ -22,7 +29,7 @@ const formatError = (msg) => {
export default (karmaConfig) => {
karmaConfig.set({
basePath: process.cwd(),
- browsers: ['PhantomJS'],
+ browsers: ['ChromeHeadless'],
client: {
mocha: {
reporter: 'html', // change Karma's debug.html to mocha web reporter
@@ -37,11 +44,10 @@ export default (karmaConfig) => {
includeAllSources: true,
},
files: [
- 'node_modules/es6-shim/es6-shim.js',
'./test/tests.bundle.js',
],
formatError,
- frameworks: ['phantomjs-shim', 'mocha'],
+ frameworks: ['mocha'],
reporters: ['mocha', 'coverage'],
singleRun: true,
preprocessors: {
diff --git a/package.json b/package.json
index 369292f8a7..91a31044fa 100644
--- a/package.json
+++ b/package.json
@@ -85,7 +85,6 @@
"doctoc": "^1.3.0",
"doctrine": "^2.0.0",
"enzyme": "^2.9.1",
- "es6-shim": "^0.35.3",
"eslint": "^4.4.1",
"eslint-config-airbnb": "^15.1.0",
"eslint-plugin-import": "^2.7.0",
@@ -105,16 +104,15 @@
"js-beautify": "^1.6.14",
"json-loader": "^0.5.7",
"karma": "^1.7.0",
+ "karma-chrome-launcher": "^2.2.0",
"karma-cli": "^1.0.1",
"karma-coverage": "^1.1.1",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.3",
- "karma-phantomjs-launcher": "^1.0.4",
- "karma-phantomjs-shim": "^1.4.0",
"karma-webpack-with-fast-source-maps": "^1.10.2",
"mocha": "^3.5.0",
"node-sass": "^4.5.3",
- "phantomjs-prebuilt": "^2.1.15",
+ "puppeteer": "^0.10.1",
"raw-loader": "^0.5.1",
"react": "^15.6.1",
"react-ace": "^5.1.2",
diff --git a/src/addons/TextArea/TextArea.d.ts b/src/addons/TextArea/TextArea.d.ts
index 9e3e919359..32f8ffff62 100644
--- a/src/addons/TextArea/TextArea.d.ts
+++ b/src/addons/TextArea/TextArea.d.ts
@@ -15,7 +15,15 @@ export interface TextAreaProps {
* @param {SyntheticEvent} event - The React SyntheticEvent object
* @param {object} data - All props and the event value.
*/
- onChange?: (event: React.FormEvent, data: TextAreaOnChangeData) => void;
+ onChange?: (event: React.FormEvent, data: TextAreaProps) => void;
+
+ /**
+ * Called on input.
+ *
+ * @param {SyntheticEvent} event - The React SyntheticEvent object
+ * @param {object} data - All props and the event value.
+ */
+ onInput?: (event: React.FormEvent, data: TextAreaProps) => void;
/** Indicates row count for a TextArea. */
rows?: number | string;
@@ -27,10 +35,6 @@ export interface TextAreaProps {
value?: number | string;
}
-export interface TextAreaOnChangeData extends TextAreaProps {
- value?: string;
-}
-
declare class TextArea extends React.Component {
focus: () => void;
}
diff --git a/src/addons/TextArea/TextArea.js b/src/addons/TextArea/TextArea.js
index 4f41421ae2..be60e645e6 100644
--- a/src/addons/TextArea/TextArea.js
+++ b/src/addons/TextArea/TextArea.js
@@ -33,6 +33,13 @@ class TextArea extends Component {
*/
onChange: PropTypes.func,
+ /**
+ * Called on input.
+ * @param {SyntheticEvent} event - The React SyntheticEvent object
+ * @param {object} data - All props and the event value.
+ */
+ onInput: PropTypes.func,
+
/** Indicates row count for a TextArea. */
rows: PropTypes.oneOfType([
PropTypes.number,
@@ -75,6 +82,12 @@ class TextArea extends Component {
const value = _.get(e, 'target.value')
_.invoke(this.props, 'onChange', e, { ...this.props, value })
+ }
+
+ handleInput = (e) => {
+ const value = _.get(e, 'target.value')
+
+ _.invoke(this.props, 'onInput', e, { ...this.props, value })
this.updateHeight()
}
@@ -116,6 +129,7 @@ class TextArea extends Component {
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 7de4733aaa..eeab312af5 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'
@@ -30,6 +31,9 @@ export default class Visibility extends Component {
*/
continuous: PropTypes.bool,
+ /** Fires callbacks immediately after mount. */
+ fireOnMount: PropTypes.bool,
+
/**
* Element's bottom edge has passed top of screen.
*
@@ -62,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,
@@ -144,6 +161,7 @@ export default class Visibility extends Component {
static defaultProps = {
context: isBrowser ? window : null,
continuous: false,
+ offset: [0, 0],
once: true,
}
@@ -173,8 +191,10 @@ export default class Visibility extends Component {
componentDidMount() {
if (!isBrowser) return
- const { context } = this.props
+ const { context, fireOnMount } = this.props
+
context.addEventListener('scroll', this.handleScroll)
+ if (fireOnMount) this.handleUpdate()
}
componentWillUnmount() {
@@ -276,23 +296,26 @@ export default class Visibility extends Component {
if (this.ticking) return
this.ticking = true
- requestAnimationFrame(() => this.handleUpdate())
+ requestAnimationFrame(this.handleUpdate)
}
handleRef = c => (this.ref = c)
handleUpdate = () => {
this.ticking = false
+
+ 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/src/modules/Checkbox/Checkbox.js b/src/modules/Checkbox/Checkbox.js
index d8ec83c1b9..963db30392 100644
--- a/src/modules/Checkbox/Checkbox.js
+++ b/src/modules/Checkbox/Checkbox.js
@@ -159,7 +159,7 @@ export default class Checkbox extends Component {
if (!this.canToggle()) return
- _.invoke(this.props, 'onClick', e, { ...this.props, checked: !!checked, indeterminate: !!indeterminate })
+ _.invoke(this.props, 'onClick', e, { ...this.props, checked: !checked, indeterminate: !!indeterminate })
_.invoke(this.props, 'onChange', e, { ...this.props, checked: !checked, indeterminate: false })
this.trySetState({ checked: !checked, indeterminate: false })
diff --git a/src/modules/Dropdown/Dropdown.js b/src/modules/Dropdown/Dropdown.js
index 8b0ea2a615..0b53ce8e4b 100644
--- a/src/modules/Dropdown/Dropdown.js
+++ b/src/modules/Dropdown/Dropdown.js
@@ -795,7 +795,8 @@ export default class Dropdown extends Component {
filteredOptions = search(filteredOptions, searchQuery)
} else {
const re = new RegExp(_.escapeRegExp(searchQuery), 'i')
- filteredOptions = _.filter(filteredOptions, opt => re.test(opt.text))
+ // remove diacritics on search
+ filteredOptions = _.filter(filteredOptions, opt => re.test(_.deburr(opt.text)))
}
}
diff --git a/src/modules/Modal/Modal.js b/src/modules/Modal/Modal.js
index 19745eabed..d8cf253360 100644
--- a/src/modules/Modal/Modal.js
+++ b/src/modules/Modal/Modal.js
@@ -227,7 +227,7 @@ class Modal extends Component {
mountNode.classList.remove('blurring')
mountNode.classList.remove('dimmable')
mountNode.classList.remove('dimmed')
- mountNode.classList.remove('scrollable')
+ mountNode.classList.remove('scrolling')
cancelAnimationFrame(this.animationRequestId)
diff --git a/test/specs/addons/TextArea/TextArea-test.js b/test/specs/addons/TextArea/TextArea-test.js
index e16b778b7b..8d620acf3f 100644
--- a/test/specs/addons/TextArea/TextArea-test.js
+++ b/test/specs/addons/TextArea/TextArea-test.js
@@ -142,6 +142,20 @@ describe('TextArea', () => {
})
})
+ describe('onInput', () => {
+ it('is called with (e, data) on input', () => {
+ const spy = sandbox.spy()
+ const e = { target: { value: 'name' } }
+ const props = { 'data-foo': 'bar', onInput: spy }
+
+ wrapperShallow()
+ wrapper.find('textarea').simulate('input', e)
+
+ spy.should.have.been.calledOnce()
+ spy.should.have.been.calledWithMatch(e, { ...props, value: e.target.value })
+ })
+ })
+
describe('rows', () => {
it('has default value', () => {
shallow()
diff --git a/test/specs/behaviors/Visibility/Visibility-test.js b/test/specs/behaviors/Visibility/Visibility-test.js
index 099603aa1a..6c2d68af21 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'
@@ -201,6 +202,56 @@ describe('Visibility', () => {
})
})
+ describe('fireOnMount', () => {
+ it('fires callbacks after mount', () => {
+ const onUpdate = sandbox.spy()
+
+ mockScroll(0, 0)
+ wrapperMount()
+
+ onUpdate.should.have.been.calledOnce()
+ onUpdate.should.have.been.calledWithMatch(null, {
+ calculations: { height: 0, width: 0 },
+ fireOnMount: true,
+ })
+ })
+ })
+
+ 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 = {
diff --git a/test/specs/modules/Checkbox/Checkbox-test.js b/test/specs/modules/Checkbox/Checkbox-test.js
index 2a42a62f1f..8fbc4c4c34 100644
--- a/test/specs/modules/Checkbox/Checkbox-test.js
+++ b/test/specs/modules/Checkbox/Checkbox-test.js
@@ -155,7 +155,7 @@ describe('Checkbox', () => {
spy.should.have.been.calledOnce()
spy.should.have.been.calledWithMatch({}, {
...expectProps,
- checked: expectProps.checked,
+ checked: !expectProps.checked,
indeterminate: expectProps.indeterminate,
})
})
diff --git a/test/specs/modules/Dropdown/Dropdown-test.js b/test/specs/modules/Dropdown/Dropdown-test.js
index 456aae0ca4..9954bdc559 100644
--- a/test/specs/modules/Dropdown/Dropdown-test.js
+++ b/test/specs/modules/Dropdown/Dropdown-test.js
@@ -610,6 +610,23 @@ describe('Dropdown', () => {
.find('.selected')
.should.contain.text('a2')
})
+ it('filter after diacritics', () => {
+ const opts = [
+ { text: 'FLOREŞTI', value: '1' },
+ { text: 'ŞANŢU FLOREŞTI', value: '2' },
+ { text: 'FLOREŞTI Alba', value: '3' },
+ ]
+
+ // search for 'floresti'
+ wrapperMount()
+ .simulate('click')
+ .find('input.search')
+ .simulate('change', { target: { value: 'floresti' } })
+
+ wrapper
+ .find('.selected')
+ .should.contain.text('FLOREŞTI')
+ })
it('still works after encountering "no results"', () => {
const opts = [
{ text: 'a1', value: 'a1' },
diff --git a/test/specs/modules/Modal/Modal-test.js b/test/specs/modules/Modal/Modal-test.js
index f16db4fa1c..7304b33653 100644
--- a/test/specs/modules/Modal/Modal-test.js
+++ b/test/specs/modules/Modal/Modal-test.js
@@ -240,9 +240,14 @@ describe('Modal', () => {
})
describe('true', () => {
- it('adds classes "dimmable dimmed" to the body', () => {
+ it('adds/removes body classes "dimmable dimmed" on mount/unmount', () => {
+ assertBodyClasses('dimmable', 'dimmed', false)
+
wrapperMount()
assertBodyClasses('dimmable', 'dimmed')
+
+ wrapper.unmount()
+ assertBodyClasses('dimmable', 'dimmed', false)
})
it('adds a dimmer to the body', () => {
@@ -264,9 +269,14 @@ describe('Modal', () => {
})
describe('blurring', () => {
- it('adds class "dimmable dimmed blurring" to the body', () => {
+ it('adds/removes body classes "dimmable dimmed blurring" on mount/unmount', () => {
+ assertBodyClasses('dimmable', 'dimmed', 'blurring', false)
+
wrapperMount()
assertBodyClasses('dimmable', 'dimmed', 'blurring')
+
+ wrapper.unmount()
+ assertBodyClasses('dimmable', 'dimmed', 'blurring', false)
})
it('adds a dimmer to the body', () => {
@@ -276,10 +286,14 @@ describe('Modal', () => {
})
describe('inverted', () => {
- it('adds class "dimmable dimmed" to the body', () => {
- wrapperMount()
+ it('adds/removes body classes "dimmable dimmed" on mount/unmount', () => {
+ assertBodyClasses('dimmable', 'dimmed', false)
+
+ wrapperMount()
assertBodyClasses('dimmable', 'dimmed')
- assertBodyClasses('inverted', false)
+
+ wrapper.unmount()
+ assertBodyClasses('dimmable', 'dimmed', false)
})
it('adds an inverted dimmer to the body', () => {
@@ -460,19 +474,24 @@ describe('Modal', () => {
})
describe('scrolling', () => {
+ const innerHeight = window.innerHeight
+
afterEach(() => {
document.body.classList.remove('scrolling')
})
+ after(() => {
+ window.innerHeight = innerHeight
+ })
+
it('does not add the scrolling class to the body by default', () => {
wrapperMount()
assertBodyClasses('scrolling', false)
})
it('adds the scrolling class to the body when taller than the window', (done) => {
- wrapperMount(foo)
-
window.innerHeight = 10
+ wrapperMount(foo)
requestAnimationFrame(() => {
assertBodyClasses('scrolling')
@@ -480,7 +499,7 @@ describe('Modal', () => {
})
})
- it('removes the scrolling class from the body when the window grows taller', (done) => {
+ it('adds/removes the scrolling class to the body when the window grows/shrinks', (done) => {
assertBodyClasses('scrolling', false)
wrapperMount(foo)
@@ -496,5 +515,20 @@ describe('Modal', () => {
})
})
})
+
+ it('removes the scrolling class from the body on unmount', (done) => {
+ assertBodyClasses('scrolling', false)
+
+ window.innerHeight = 10
+ wrapperMount(foo)
+
+ requestAnimationFrame(() => {
+ assertBodyClasses('scrolling')
+ wrapper.unmount()
+
+ assertBodyClasses('scrolling', false)
+ done()
+ })
+ })
})
})