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(