diff --git a/example/components/CodeSample.react.js b/example/components/CodeSample.react.js index cec1af60..a41f374a 100644 --- a/example/components/CodeSample.react.js +++ b/example/components/CodeSample.react.js @@ -1,21 +1,12 @@ import React from 'react'; +import PropTypes from 'prop-types'; import Markdown from './Markdown'; const START_STR = '/* example-start */'; const END_STR = '/* example-end */'; -const CodeSample = React.createClass({ - propTypes: { - lang: React.PropTypes.string, - }, - - getDefaultProps() { - return { - lang: 'jsx', - }; - }, - +class CodeSample extends React.Component { render() { // Strip out extraneous parts of the code. const code = this.props.children; @@ -29,7 +20,16 @@ ${code.slice(startIndex, endIndex)} \`\`\``} ); - }, -}); + } +} + +CodeSample.propTypes = { + lang: PropTypes.string, +}; + +CodeSample.defaultProps = { + lang: 'jsx', +}; + export default CodeSample; diff --git a/example/components/ExampleSection.react.js b/example/components/ExampleSection.react.js index 96fa7609..0bd015c5 100644 --- a/example/components/ExampleSection.react.js +++ b/example/components/ExampleSection.react.js @@ -2,12 +2,14 @@ import React from 'react'; import CodeSample from '../components/CodeSample'; -const ExampleSection = React.createClass({ - getInitialState() { - return { +class ExampleSection extends React.Component { + constructor(props) { + super(props); + + this.state = { open: false, }; - }, + } render() { const {children, code} = this.props; @@ -33,7 +35,7 @@ const ExampleSection = React.createClass({ {open ? {code} : null} ); - }, -}); + } +} export default ExampleSection; diff --git a/example/components/GithubStarsButton.react.js b/example/components/GithubStarsButton.react.js index ea689bb1..b8d52a04 100644 --- a/example/components/GithubStarsButton.react.js +++ b/example/components/GithubStarsButton.react.js @@ -3,11 +3,11 @@ import {findDOMNode} from 'react-dom'; const AUTHOR_REPO = 'ericgio/react-bootstrap-typeahead'; -const GitHubStarsButton = React.createClass({ +class GitHubStarsButton extends React.Component { componentDidMount() { const node = findDOMNode(this); node.dataset.style = window.innerWidth > 480 ? 'mega': null; - }, + } render() { return ( @@ -21,7 +21,7 @@ const GitHubStarsButton = React.createClass({ Star ); - }, -}); + } +} export default GitHubStarsButton; diff --git a/example/components/Markdown.react.js b/example/components/Markdown.react.js index 62893db4..97319e26 100644 --- a/example/components/Markdown.react.js +++ b/example/components/Markdown.react.js @@ -2,7 +2,7 @@ import cx from 'classnames'; import marked from 'marked'; import React from 'react'; -const Markdown = React.createClass({ +class Markdown extends React.Component { componentWillMount() { marked.setOptions({ gfm: true, @@ -16,7 +16,7 @@ const Markdown = React.createClass({ return require('highlight.js').highlightAuto(code).value; }, }); - }, + } render() { const html = marked.parse(this.props.children); @@ -27,7 +27,7 @@ const Markdown = React.createClass({ dangerouslySetInnerHTML={{__html: html}} /> ); - }, -}); + } +} export default Markdown; diff --git a/example/components/Page.react.js b/example/components/Page.react.js index 8843d8b4..b22115f9 100644 --- a/example/components/Page.react.js +++ b/example/components/Page.react.js @@ -1,4 +1,6 @@ -import React, {Children, PropTypes} from 'react'; +import React, {Children} from 'react'; +import PropTypes from 'prop-types'; +import createReactClass from 'create-react-class'; import {Col, Jumbotron, NavItem, Row} from 'react-bootstrap'; import Container from './Container'; @@ -8,7 +10,7 @@ import PageMenu from './PageMenu'; import getIdFromTitle from '../util/getIdFromTitle'; -const Page = React.createClass({ +const Page = createReactClass({ getInitialState() { return { activeHref: window.location.hash, diff --git a/example/components/Section.react.js b/example/components/Section.react.js index cb45b7d2..fdf0a0cd 100644 --- a/example/components/Section.react.js +++ b/example/components/Section.react.js @@ -1,4 +1,6 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; +import createReactClass from 'create-react-class'; import Anchor from './Anchor'; import ScrollSpy from './ScrollSpy'; @@ -6,7 +8,7 @@ import ScrollSpy from './ScrollSpy'; import getIdFromTitle from '../util/getIdFromTitle'; const sectionContainer = Component => ( - React.createClass({ + createReactClass({ contextTypes: { onAfter: PropTypes.func.isRequired, onBefore: PropTypes.func.isRequired, diff --git a/example/components/Title.react.js b/example/components/Title.react.js index f876eb44..d05430e2 100644 --- a/example/components/Title.react.js +++ b/example/components/Title.react.js @@ -1,4 +1,5 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import Anchor from './Anchor'; diff --git a/example/examples/AsyncExample.react.js b/example/examples/AsyncExample.react.js index 8f07f38c..48920d02 100644 --- a/example/examples/AsyncExample.react.js +++ b/example/examples/AsyncExample.react.js @@ -8,14 +8,17 @@ import {AsyncTypeahead} from '../../src/'; require('es6-promise').polyfill(); /* example-start */ -const AsyncExample = React.createClass({ - getInitialState() { - return { +class AsyncExample extends React.Component { + + constructor(props) { + super(props); + + this.state = { allowNew: false, multiple: false, options: [], }; - }, + } render() { return ( @@ -30,7 +33,7 @@ const AsyncExample = React.createClass({ {this._renderCheckboxes()} ); - }, + } _renderCheckboxes() { const checkboxes = [ @@ -47,7 +50,7 @@ const AsyncExample = React.createClass({ {label} )); - }, + } _renderMenuItemChildren(option, props, index) { return ( @@ -63,12 +66,12 @@ const AsyncExample = React.createClass({ {option.login} ); - }, + } _handleChange(e) { const {checked, name} = e.target; this.setState({[name]: checked}); - }, + } _handleSearch(query) { if (!query) { @@ -78,8 +81,8 @@ const AsyncExample = React.createClass({ fetch(`https://api.github.com/search/users?q=${query}`) .then(resp => resp.json()) .then(json => this.setState({options: json.items})); - }, -}); + } +} /* example-end */ export default AsyncExample; diff --git a/example/examples/BasicBehaviorsExample.react.js b/example/examples/BasicBehaviorsExample.react.js index ef3d9a07..b43b1341 100644 --- a/example/examples/BasicBehaviorsExample.react.js +++ b/example/examples/BasicBehaviorsExample.react.js @@ -6,14 +6,16 @@ import {Typeahead} from '../../src/'; import options from '../../example/exampleData'; /* example-start */ -const BasicBehaviorsExample = React.createClass({ - getInitialState() { - return { +class BasicBehaviorsExample extends React.Component { + constructor(props) { + super(props); + + this.state = { disabled: false, dropup: false, minLength: 0, }; - }, + } render() { const {disabled, dropup, emptyLabel, minLength} = this.state; @@ -56,7 +58,7 @@ const BasicBehaviorsExample = React.createClass({ ); - }, + } _handleChange(e) { const {checked, name} = e.target; @@ -67,8 +69,8 @@ const BasicBehaviorsExample = React.createClass({ } this.setState(newState); - }, -}); + } +} /* example-end */ export default BasicBehaviorsExample; diff --git a/example/examples/BasicExample.react.js b/example/examples/BasicExample.react.js index 4cf9ab89..583da35c 100644 --- a/example/examples/BasicExample.react.js +++ b/example/examples/BasicExample.react.js @@ -5,12 +5,14 @@ import {Typeahead} from '../../src/'; import options from '../../example/exampleData'; /* example-start */ -const BasicExample = React.createClass({ - getInitialState() { - return { +class BasicExample extends React.Component { + constructor(props) { + super(props); + + this.state = { multiple: false, }; - }, + } render() { const {multiple} = this.state; @@ -30,8 +32,8 @@ const BasicExample = React.createClass({ ); - }, -}); + } +} /* example-end */ export default BasicExample; diff --git a/example/examples/BodyContainerExample.react.js b/example/examples/BodyContainerExample.react.js index 28fd5841..27e0f363 100644 --- a/example/examples/BodyContainerExample.react.js +++ b/example/examples/BodyContainerExample.react.js @@ -5,12 +5,13 @@ import {Typeahead} from '../../src/'; import options from '../../example/exampleData'; /* example-start */ -const BodyContainerExample = React.createClass({ - getInitialState() { - return { +class BodyContainerExample extends React.Component { + constructor(props) { + super(props); + this.state = { bodyContainer: false, }; - }, + } render() { const {bodyContainer} = this.state; @@ -40,8 +41,8 @@ const BodyContainerExample = React.createClass({ ); - }, -}); + } +} /* example-end */ export default BodyContainerExample; diff --git a/example/examples/CustomFilteringExample.react.js b/example/examples/CustomFilteringExample.react.js index dcb4d3b4..34fa81fa 100644 --- a/example/examples/CustomFilteringExample.react.js +++ b/example/examples/CustomFilteringExample.react.js @@ -5,12 +5,14 @@ import {Typeahead} from '../../src/'; import options from '../../example/exampleData'; /* example-start */ -const CustomFilteringExample = React.createClass({ - getInitialState() { - return { +class CustomFilteringExample extends React.Component { + constructor(props) { + super(props); + + this.state ={ filterBy: 'callback', }; - }, + } render() { const {filterBy} = this.state; @@ -55,8 +57,8 @@ const CustomFilteringExample = React.createClass({ ))} ); - }, -}); + } +} /* example-end */ export default CustomFilteringExample; diff --git a/example/examples/CustomSelectionsExample.react.js b/example/examples/CustomSelectionsExample.react.js index 5340ddd6..1aba65a1 100644 --- a/example/examples/CustomSelectionsExample.react.js +++ b/example/examples/CustomSelectionsExample.react.js @@ -2,7 +2,7 @@ import React from 'react'; import {Typeahead} from '../../src/'; /* example-start */ -const CustomSelectionsExample = React.createClass({ +class CustomSelectionsExample extends React.Component { render() { return ( ); - }, -}); + } +} /* example-end */ export default CustomSelectionsExample; diff --git a/example/examples/FilteringExample.react.js b/example/examples/FilteringExample.react.js index c1b82d5d..288d3147 100644 --- a/example/examples/FilteringExample.react.js +++ b/example/examples/FilteringExample.react.js @@ -32,13 +32,15 @@ const options = [ ]; /* example-start */ -const FilteringExample = React.createClass({ - getInitialState() { - return { +class FilteringExample extends React.Component { + constructor(props) { + super(props); + + this.state = { caseSensitive: false, ignoreDiacritics: true, }; - }, + } render() { const {caseSensitive, ignoreDiacritics} = this.state; @@ -62,8 +64,8 @@ const FilteringExample = React.createClass({ ); - }, -}); + } +} /* example-end */ export default FilteringExample; diff --git a/example/examples/FormSubmitExample.react.js b/example/examples/FormSubmitExample.react.js index 52bb44f4..752fb18a 100644 --- a/example/examples/FormSubmitExample.react.js +++ b/example/examples/FormSubmitExample.react.js @@ -5,12 +5,14 @@ import {Typeahead} from '../../src/'; import options from '../../example/exampleData'; /* example-start */ -const FormSubmitExample = React.createClass({ - getInitialState() { - return { +class FormSubmitExample extends React.Component { + constructor(props) { + super(props); + + this.state = { submitFormOnEnter: true, }; - }, + } render() { const {submitFormOnEnter} = this.state; @@ -35,8 +37,8 @@ const FormSubmitExample = React.createClass({ ); - }, -}); + } +} /* example-end */ export default FormSubmitExample; diff --git a/example/examples/InputSizeExample.react.js b/example/examples/InputSizeExample.react.js index a2fd4c82..638a9084 100644 --- a/example/examples/InputSizeExample.react.js +++ b/example/examples/InputSizeExample.react.js @@ -5,12 +5,14 @@ import {Typeahead} from '../../src/'; import options from '../../example/exampleData'; /* example-start */ -const InputSizeExample = React.createClass({ - getInitialState() { - return { +class InputSizeExample extends React.Component { + constructor(props) { + super(props); + + this.state = { bsSize: undefined, }; - }, + } render() { const {bsSize} = this.state; @@ -39,8 +41,8 @@ const InputSizeExample = React.createClass({ ))} ); - }, -}); + } +} /* example-end */ export default InputSizeExample; diff --git a/example/examples/LabelKeyExample.react.js b/example/examples/LabelKeyExample.react.js index 0d39d700..2f05cfd0 100644 --- a/example/examples/LabelKeyExample.react.js +++ b/example/examples/LabelKeyExample.react.js @@ -2,7 +2,7 @@ import React from 'react'; import {Typeahead} from '../../src/'; /* example-start */ -const LabelKeyExample = React.createClass({ +class LabelKeyExample extends React.Component { render() { return ( ); - }, -}); + } +} /* example-end */ export default LabelKeyExample; diff --git a/example/examples/MenuAlignExample.react.js b/example/examples/MenuAlignExample.react.js index a9ac1598..4c50700e 100644 --- a/example/examples/MenuAlignExample.react.js +++ b/example/examples/MenuAlignExample.react.js @@ -5,12 +5,14 @@ import {Typeahead} from '../../src/'; import options from '../../example/exampleData'; /* example-start */ -const MenuAlignExample = React.createClass({ - getInitialState() { - return { +class MenuAlignExample extends React.Component { + constructor(props) { + super(props); + + this.state = { align: 'justify', }; - }, + } render() { const {align} = this.state; @@ -39,8 +41,8 @@ const MenuAlignExample = React.createClass({ ))} ); - }, -}); + } +} /* example-end */ export default MenuAlignExample; diff --git a/example/examples/PaginationExample.react.js b/example/examples/PaginationExample.react.js index 253fbdd5..45b86693 100644 --- a/example/examples/PaginationExample.react.js +++ b/example/examples/PaginationExample.react.js @@ -6,12 +6,14 @@ import {Typeahead} from '../../src/'; /* eslint-disable no-console */ /* example-start */ -const LabelKeyExample = React.createClass({ - getInitialState() { - return { +class LabelKeyExample extends React.Component { + constructor(props) { + super(props); + + this.state = { paginate: true, }; - }, + } render() { const {paginate} = this.state; @@ -31,8 +33,8 @@ const LabelKeyExample = React.createClass({ ); - }, -}); + } +} /* example-end */ /* eslint-disable no-console */ diff --git a/example/examples/PublicMethodsExample.react.js b/example/examples/PublicMethodsExample.react.js index f059e27a..befb4326 100644 --- a/example/examples/PublicMethodsExample.react.js +++ b/example/examples/PublicMethodsExample.react.js @@ -5,7 +5,7 @@ import {Typeahead} from '../../src/'; import options from '../../example/exampleData'; /* example-start */ -const PublicMethodsExample = React.createClass({ +class PublicMethodsExample extends React.Component { render() { return (
@@ -35,8 +35,8 @@ const PublicMethodsExample = React.createClass({
); - }, -}); + } +} /* example-end */ export default PublicMethodsExample; diff --git a/example/examples/RenderingExample.react.js b/example/examples/RenderingExample.react.js index c209bb6d..f73a4572 100644 --- a/example/examples/RenderingExample.react.js +++ b/example/examples/RenderingExample.react.js @@ -9,12 +9,14 @@ const MenuDivider = props =>
  • ; const MenuHeader = props =>
  • ; /* example-start */ -const RenderingExample = React.createClass({ - getInitialState() { - return { +class RenderingExample extends React.Component { + constructor(props) { + super(props); + + this.state = { selectedOption: 'renderMenu', }; - }, + } render() { const {selectedOption} = this.state; @@ -57,7 +59,7 @@ const RenderingExample = React.createClass({ ))} ); - }, + } _renderMenu(results, menuProps) { let idx = 0; @@ -81,7 +83,7 @@ const RenderingExample = React.createClass({ }); return {items}; - }, + } _renderMenuItemChildren(option, props, index) { return [ @@ -90,7 +92,7 @@ const RenderingExample = React.createClass({ Population: {option.population.toLocaleString()} , ]; - }, + } _renderToken(option, onRemove, index) { return ( @@ -100,8 +102,8 @@ const RenderingExample = React.createClass({ {`${option.name} (Pop: ${option.population.toLocaleString()})`} ); - }, -}); + } +} /* example-end */ export default RenderingExample; diff --git a/package.json b/package.json index 8fbf1650..f50797c2 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,14 @@ "license": "MIT", "dependencies": { "classnames": "^2.2.0", + "create-react-class": "^15.5.2", "invariant": "^2.2.1", "lodash": "^4.17.2", + "prop-types": "^15.5.8", "react-highlighter": "^0.3.3", "react-input-autosize": "^1.1.0", "react-onclickoutside": "^5.7.0", - "react-overlays": "^0.6.10", + "react-overlays": "^0.7.0", "react-prop-types": "^0.4.0", "warning": "^3.0.0" }, @@ -72,7 +74,7 @@ "raw-loader": "^0.5.1", "react": "^15.2.1", "react-addons-test-utils": "^15.4.0", - "react-bootstrap": "^0.30.3", + "react-bootstrap": "^0.31.0", "react-dom": "^15.2.1", "react-waypoint": "^5.0.3", "style-loader": "^0.13.1", diff --git a/src/ClearButton.react.js b/src/ClearButton.react.js index 4e80db69..47b7fa1f 100644 --- a/src/ClearButton.react.js +++ b/src/ClearButton.react.js @@ -1,5 +1,6 @@ import cx from 'classnames'; import React from 'react'; +import PropTypes from 'prop-types'; /** * CloseButton @@ -21,7 +22,7 @@ const ClearButton = ({bsSize, className, onClick}) => ( ClearButton.displayName = 'ClearButton'; ClearButton.propTypes = { - bsSize: React.PropTypes.oneOf(['large', 'lg', 'small', 'sm']), + bsSize: PropTypes.oneOf(['large', 'lg', 'small', 'sm']), }; export default ClearButton; diff --git a/src/Loader.react.js b/src/Loader.react.js index 67a7f2c3..beb03e4a 100644 --- a/src/Loader.react.js +++ b/src/Loader.react.js @@ -1,5 +1,6 @@ import cx from 'classnames'; import React from 'react'; +import PropTypes from 'prop-types'; const Loader = ({bsSize}) => (
    ( ); Loader.propTypes = { - bsSize: React.PropTypes.oneOf(['large', 'lg', 'small', 'sm']), + bsSize: PropTypes.oneOf(['large', 'lg', 'small', 'sm']), }; export default Loader; diff --git a/src/Menu.react.js b/src/Menu.react.js index 1474c9d1..0873fd5a 100644 --- a/src/Menu.react.js +++ b/src/Menu.react.js @@ -1,7 +1,8 @@ 'use strict'; import cx from 'classnames'; -import React, {Children, PropTypes} from 'react'; +import React, {Children} from 'react'; +import PropTypes from 'prop-types'; import {BaseMenuItem} from './MenuItem.react'; @@ -17,40 +18,8 @@ const BaseMenu = props => ( * Menu component that automatically handles pagination and empty state when * passed a set of filtered and truncated results. */ -const Menu = React.createClass({ - displayName: 'Menu', - - propTypes: { - /** - * Specify menu alignment. The default value is `justify`, which makes the - * menu as wide as the input and truncates long values. Specifying `left` - * or `right` will align the menu to that side and the width will be - * determined by the length of menu item values. - */ - align: PropTypes.oneOf(['justify', 'left', 'right']), - /** - * Message to display in the menu if there are no valid results. - */ - emptyLabel: PropTypes.string, - /** - * Maximum height of the dropdown menu, in px. - */ - maxHeight: PropTypes.number, - /** - * Prompt displayed when large data sets are paginated. - */ - paginationText: PropTypes.string, - }, - - getDefaultProps() { - return { - align: 'justify', - emptyLabel: 'No matches found.', - maxHeight: 300, - paginate: true, - paginationText: 'Display additional results...', - }; - }, +class Menu extends React.Component { + displayName = 'Menu'; render() { const {align, children, className, emptyLabel} = this.props; @@ -78,7 +47,7 @@ const Menu = React.createClass({ {this._renderPaginationMenuItem()} ); - }, + } /** * Allow user to see more results, if available. @@ -101,7 +70,7 @@ const Menu = React.createClass({ , ]; } - }, + } _getMenuStyle() { const {align, dropup, maxHeight, style} = this.props; @@ -123,7 +92,38 @@ const Menu = React.createClass({ } return menuStyle; - }, -}); + } +} + +Menu.PropTypes = { + /** + * Specify menu alignment. The default value is `justify`, which makes the + * menu as wide as the input and truncates long values. Specifying `left` + * or `right` will align the menu to that side and the width will be + * determined by the length of menu item values. + */ + align: PropTypes.oneOf(['justify', 'left', 'right']), + /** + * Message to display in the menu if there are no valid results. + */ + emptyLabel: PropTypes.string, + /** + * Maximum height of the dropdown menu, in px. + */ + maxHeight: PropTypes.number, + /** + * Prompt displayed when large data sets are paginated. + */ + paginationText: PropTypes.string, +}; + +Menu.defaultProps = { + align: 'justify', + emptyLabel: 'No matches found.', + maxHeight: 300, + paginate: true, + paginationText: 'Display additional results...', +}; + export default Menu; diff --git a/src/MenuItem.react.js b/src/MenuItem.react.js index 999938d3..4a0d0a9f 100644 --- a/src/MenuItem.react.js +++ b/src/MenuItem.react.js @@ -6,14 +6,14 @@ import React from 'react'; import menuItemContainer from './containers/menuItemContainer'; -const BaseMenuItem = React.createClass({ - displayName: 'BaseMenuItem', +class BaseMenuItem extends React.Component { + displayName = 'BaseMenuItem'; - getDefaultProps() { - return { - onClick: noop, - }; - }, + constructor(props) { + super(props); + + this._handleClick = this._handleClick.bind(this); + } render() { const {active, children, className, disabled} = this.props; @@ -29,15 +29,19 @@ const BaseMenuItem = React.createClass({
  • ); - }, + } _handleClick(e) { const {disabled, onClick} = this.props; e.preventDefault(); !disabled && onClick(e); - }, -}); + } +} + +BaseMenuItem.defaultProps = { + onClick: noop, +}; const MenuItem = menuItemContainer(BaseMenuItem); diff --git a/src/Overlay.react.js b/src/Overlay.react.js index 0645580c..bb25b55e 100644 --- a/src/Overlay.react.js +++ b/src/Overlay.react.js @@ -1,6 +1,7 @@ import cx from 'classnames'; import {isEqual} from 'lodash'; -import React, {Children, cloneElement, PropTypes} from 'react'; +import React, {Children, cloneElement} from 'react'; +import PropTypes from 'prop-types'; import {findDOMNode} from 'react-dom'; import {Portal} from 'react-overlays'; import componentOrElement from 'react-prop-types/lib/componentOrElement'; @@ -19,35 +20,21 @@ function isBody(container) { * work for our needs. Specifically, the `Position` component doesn't provide * the customized placement we need. */ -const Overlay = React.createClass({ - displayName: 'Overlay', - - propTypes: { - container: PropTypes.oneOfType([ - componentOrElement, - PropTypes.func, - ]).isRequired, - show: PropTypes.bool, - target: PropTypes.oneOfType([ - componentOrElement, - PropTypes.func, - ]).isRequired, - }, - - getDefaultProps() { - return { - show: false, - }; - }, +class Overlay extends React.Component { + displayName = 'Overlay'; + + constructor(props) { + super(props); + + this._updatePosition = this._updatePosition.bind(this); - getInitialState() { - return { + this.state = { bottom: 0, left: 0, right: 0, top: 0, }; - }, + } componentDidMount() { this._mounted = true; @@ -60,17 +47,17 @@ const Overlay = React.createClass({ window.addEventListener('resize', this._updatePositionThrottled); window.addEventListener('scroll', this._updatePositionThrottled, true); - }, + } componentWillReceiveProps(nextProps) { this._updatePositionThrottled(); - }, + } componentWillUnmount() { this._mounted = false; window.removeEventListener('resize', this._updatePositionThrottled); window.removeEventListener('scroll', this._updatePositionThrottled); - }, + } render() { if (!this.props.show) { @@ -97,7 +84,7 @@ const Overlay = React.createClass({ {child} ); - }, + } _updatePosition() { // Positioning is only used when body is the container. @@ -128,7 +115,24 @@ const Overlay = React.createClass({ this.setState(newState); } } - }, -}); + } +} + +Overlay.propTypes = { + container: PropTypes.oneOfType([ + componentOrElement, + PropTypes.func, + ]).isRequired, + show: PropTypes.bool, + target: PropTypes.oneOfType([ + componentOrElement, + PropTypes.func, + ]).isRequired, +}; + +Overlay.defaultProps = { + show: false, +}; + export default Overlay; diff --git a/src/TextInput.react.js b/src/TextInput.react.js index 513acfd6..40d877f0 100644 --- a/src/TextInput.react.js +++ b/src/TextInput.react.js @@ -1,15 +1,11 @@ 'use strict'; import cx from 'classnames'; -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; -const TextInput = React.createClass({ - propTypes: { - /** - * Specify the size of the input. - */ - bsSize: PropTypes.oneOf(['large', 'lg', 'small', 'sm']), - }, + +class TextInput extends React.Component { render() { const {bsSize, className, hasAux, ...otherProps} = this.props; @@ -26,11 +22,18 @@ const TextInput = React.createClass({ type="text" /> ); - }, + } getInstance() { return this._input; - }, -}); + } +} + +TextInput.propTypes = { + /** + * Specify the size of the input. + */ + bsSize: PropTypes.oneOf(['large', 'lg', 'small', 'sm']), +}; export default TextInput; diff --git a/src/Token.react.js b/src/Token.react.js index 641596a1..e2396640 100644 --- a/src/Token.react.js +++ b/src/Token.react.js @@ -2,7 +2,8 @@ import cx from 'classnames'; import {noop} from 'lodash'; -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import tokenContainer from './containers/tokenContainer'; @@ -12,30 +13,14 @@ import tokenContainer from './containers/tokenContainer'; * Individual token component, generally displayed within the TokenizerInput * component, but can also be rendered on its own. */ -const Token = React.createClass({ - displayName: 'Token', - - propTypes: { - /** - * Handler for removing/deleting the token. If not defined, the token will - * be rendered in a read-only state. - */ - onRemove: PropTypes.func, - selected: PropTypes.bool, - }, - - getDefaultProps() { - return { - onRemove: noop, - selected: false, - }; - }, +class Token extends React.Component { + displayName = 'Token'; render() { return this.props.onRemove && !this.props.disabled ? this._renderRemoveableToken() : this._renderToken(); - }, + } _renderRemoveableToken() { const {children, className, onRemove, selected, ...otherProps} = this.props; @@ -56,7 +41,7 @@ const Token = React.createClass({ ); - }, + } _renderToken() { const {children, className, disabled, href} = this.props; @@ -75,7 +60,22 @@ const Token = React.createClass({ {children} ); - }, -}); + } +} + +Token.propTypes = { + /** + * Handler for removing/deleting the token. If not defined, the token will + * be rendered in a read-only state. + */ + onRemove: PropTypes.func, + selected: PropTypes.bool, +}; + +Token.defaultProps = { + onRemove: noop, + selected: false, +}; + export default tokenContainer(Token); diff --git a/src/TokenizerInput.react.js b/src/TokenizerInput.react.js index 99da3bb3..9cbb4ba2 100644 --- a/src/TokenizerInput.react.js +++ b/src/TokenizerInput.react.js @@ -1,7 +1,8 @@ 'use strict'; import cx from 'classnames'; -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import {findDOMNode} from 'react-dom'; import AutosizeInput from 'react-input-autosize'; @@ -16,48 +17,22 @@ import {BACKSPACE} from './utils/keyCode'; * Accepts multiple selections from a Typeahead component and renders them as * tokens within an input. */ -const TokenizerInput = React.createClass({ - displayName: 'TokenizerInput', +class TokenizerInput extends React.Component { + displayName = 'TokenizerInput'; - /** - * In addition to the propTypes below, the following props are automatically - * passed down by `Typeahead`: - * - * - activeIndex - * - hasAux - * - labelKey - * - onAdd - * - onBlur - * - onChange - * - onClick - * - onFocus - * - onKeydown - * - onRemove - * - options - * - selected - * - value - */ - propTypes: { - /** - * Whether to disable the input and all selections. - */ - disabled: PropTypes.bool, - /** - * Placeholder text for the input. - */ - placeholder: PropTypes.string, - /** - * Provides a hook for customized rendering of tokens when multiple - * selections are enabled. - */ - renderToken: PropTypes.func, - }, - - getInitialState() { - return { + constructor(props) { + super(props); + + this._handleBlur = this._handleBlur.bind(this); + this._handleChange = this._handleChange.bind(this); + this._handleInputFocus = this._handleInputFocus.bind(this); + this._handleKeydown = this._handleKeydown.bind(this); + this._renderToken = this._renderToken.bind(this); + + this.state = { isFocused: false, }; - }, + } render() { const {bsSize, disabled, hasAux, placeholder, selected, value} = this.props; @@ -107,15 +82,15 @@ const TokenizerInput = React.createClass({ /> ); - }, + } blur() { this.refs.input.blur(); - }, + } focus() { this._handleInputFocus(); - }, + } _renderToken(option, idx) { const {disabled, labelKey, onRemove, renderToken} = this.props; @@ -133,16 +108,16 @@ const TokenizerInput = React.createClass({ {getOptionLabel(option, labelKey)} ); - }, + } _handleBlur(e) { this.setState({isFocused: false}); this.props.onBlur(e); - }, + } _handleChange(e) { this.props.onChange(e.target.value); - }, + } _handleKeydown(e) { switch (e.keyCode) { @@ -165,7 +140,7 @@ const TokenizerInput = React.createClass({ } this.props.onKeyDown(e); - }, + } _handleInputFocus(e) { if (this.props.disabled) { @@ -177,7 +152,42 @@ const TokenizerInput = React.createClass({ // focus the input. this.refs.input.focus(); this.setState({isFocused: true}); - }, -}); + } +} + +/** + * In addition to the propTypes below, the following props are automatically + * passed down by `Typeahead`: + * + * - activeIndex + * - hasAux + * - labelKey + * - onAdd + * - onBlur + * - onChange + * - onClick + * - onFocus + * - onKeydown + * - onRemove + * - options + * - selected + * - value + */ +TokenizerInput.propTypes = { + /** + * Whether to disable the input and all selections. + */ + disabled: PropTypes.bool, + /** + * Placeholder text for the input. + */ + placeholder: PropTypes.string, + /** + * Provides a hook for customized rendering of tokens when multiple + * selections are enabled. + */ + renderToken: PropTypes.func, +}; + export default TokenizerInput; diff --git a/src/Typeahead.react.js b/src/Typeahead.react.js index d7afefe3..9850f3b0 100644 --- a/src/Typeahead.react.js +++ b/src/Typeahead.react.js @@ -3,7 +3,9 @@ import cx from 'classnames'; import {find, isEqual, noop} from 'lodash'; import onClickOutside from 'react-onclickoutside'; -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; +import createReactClass from 'create-react-class'; import ClearButton from './ClearButton.react'; import Loader from './Loader.react'; @@ -25,7 +27,7 @@ import {DOWN, ESC, RETURN, TAB, UP} from './utils/keyCode'; /** * Typeahead */ -const Typeahead = React.createClass({ +const Typeahead = createReactClass({ displayName: 'Typeahead', propTypes: { diff --git a/src/TypeaheadInput.react.js b/src/TypeaheadInput.react.js index 616e7c6f..20f19c65 100644 --- a/src/TypeaheadInput.react.js +++ b/src/TypeaheadInput.react.js @@ -2,7 +2,8 @@ import cx from 'classnames'; import {head} from 'lodash'; -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import TextInput from './TextInput.react'; @@ -13,55 +14,28 @@ import {RIGHT, TAB} from './utils/keyCode'; * * Handles a single selection from the Typeahead component. */ -const TypeaheadInput = React.createClass({ - displayName: 'TypeaheadInput', +class TypeaheadInput extends React.Component { + displayName = 'TypeaheadInput'; - /** - * In addition to the propTypes below, the following props are automatically - * passed down by `Typeahead`: - * - * - activeIndex - * - activeItem - * - hasAux - * - hintText - * - labelKey - * - onAdd - * - onBlur - * - onChange - * - onClick - * - onFocus - * - onKeydown - * - onRemove - * - selected - * - value - */ - propTypes: { - /** - * Whether to disable the input and any selection, if present. - */ - disabled: PropTypes.bool, - /** - * Name property for the input. - */ - name: PropTypes.string, - /** - * Placeholder text for the input. - */ - placeholder: PropTypes.string, - }, - - getInitialState() { - return { + constructor(props) { + super(props); + + this._handleBlur = this._handleBlur.bind(this); + this._handleChange = this._handleChange.bind(this); + this._handleInputFocus = this._handleInputFocus.bind(this); + this._handleKeydown = this._handleKeydown.bind(this); + + this.state = { isFocused: false, }; - }, + } componentDidUpdate(prevProps, prevState) { const {activeIndex, value} = this.props; if (activeIndex !== prevProps.activeIndex) { this._input.getInstance().selectionStart = value.length; } - }, + } render() { const { @@ -134,20 +108,20 @@ const TypeaheadInput = React.createClass({ /> ); - }, + } blur() { this._input.getInstance().blur(); - }, + } focus() { this._handleInputFocus(); - }, + } _handleBlur(e) { this.setState({isFocused: false}); this.props.onBlur(e); - }, + } _handleChange(e) { // Clear any selections when text is entered. @@ -155,7 +129,7 @@ const TypeaheadInput = React.createClass({ !!selected.length && onRemove(head(selected)); this.props.onChange(e.target.value); - }, + } /** * If the containing parent div is focused or clicked, focus the input. @@ -163,7 +137,7 @@ const TypeaheadInput = React.createClass({ _handleInputFocus(e) { this.setState({isFocused: true}); this._input.getInstance().focus(); - }, + } _handleKeydown(e) { const { @@ -201,7 +175,42 @@ const TypeaheadInput = React.createClass({ } this.props.onKeyDown(e); - }, -}); + } +} + +/** + * In addition to the propTypes below, the following props are automatically + * passed down by `Typeahead`: + * + * - activeIndex + * - activeItem + * - hasAux + * - hintText + * - labelKey + * - onAdd + * - onBlur + * - onChange + * - onClick + * - onFocus + * - onKeydown + * - onRemove + * - selected + * - value + */ +TypeaheadInput.propTypes = { + /** + * Whether to disable the input and any selection, if present. + */ + disabled: PropTypes.bool, + /** + * Name property for the input. + */ + name: PropTypes.string, + /** + * Placeholder text for the input. + */ + placeholder: PropTypes.string, +}; + export default TypeaheadInput; diff --git a/src/TypeaheadMenu.react.js b/src/TypeaheadMenu.react.js index ae0baa4d..b02a6c21 100644 --- a/src/TypeaheadMenu.react.js +++ b/src/TypeaheadMenu.react.js @@ -2,7 +2,8 @@ import {pick} from 'lodash'; import Highlight from 'react-highlighter'; -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import Menu from './Menu.react'; import MenuItem from './MenuItem.react'; @@ -11,36 +12,14 @@ import getOptionLabel from './utils/getOptionLabel'; const MATCH_CLASS = 'bootstrap-typeahead-highlight'; -const TypeaheadMenu = React.createClass({ - displayName: 'TypeaheadMenu', +class TypeaheadMenu extends React.Component { + displayName = 'TypeaheadMenu'; - /** - * In addition to the propTypes below, the following props are automatically - * passed down by `Typeahead`: - * - * - labelKey - * - onPaginate - * - options - * - paginate - * - text - */ - propTypes: { - /** - * Provides the ability to specify a prefix before the user-entered text to - * indicate that the selection will be new. No-op unless `allowNew={true}`. - */ - newSelectionPrefix: PropTypes.string, - /** - * Provides a hook for customized rendering of menu item contents. - */ - renderMenuItemChildren: PropTypes.func, - }, - - getDefaultProps() { - return { - newSelectionPrefix: 'New selection: ', - }; - }, + constructor(props) { + super(props); + + this._renderMenuItem = this._renderMenuItem.bind(this); + } render() { const menuProps = pick(this.props, [ @@ -60,7 +39,7 @@ const TypeaheadMenu = React.createClass({ {this.props.options.map(this._renderMenuItem)} ); - }, + } _renderMenuItem(option, idx) { const { @@ -97,7 +76,34 @@ const TypeaheadMenu = React.createClass({ {getOptionLabel(option, labelKey)} ; - }, -}); + } +} + +/** + * In addition to the propTypes below, the following props are automatically + * passed down by `Typeahead`: + * + * - labelKey + * - onPaginate + * - options + * - paginate + * - text + */ +TypeaheadMenu.propTypes = { + /** + * Provides the ability to specify a prefix before the user-entered text to + * indicate that the selection will be new. No-op unless `allowNew={true}`. + */ + newSelectionPrefix: PropTypes.string, + /** + * Provides a hook for customized rendering of menu item contents. + */ + renderMenuItemChildren: PropTypes.func, +}; + +TypeaheadMenu.getDefaultProps = { + newSelectionPrefix: 'New selection: ', +}; + export default TypeaheadMenu; diff --git a/src/containers/asyncContainer.js b/src/containers/asyncContainer.js index 3d79ea85..d75bee54 100644 --- a/src/containers/asyncContainer.js +++ b/src/containers/asyncContainer.js @@ -1,5 +1,6 @@ import {debounce} from 'lodash'; -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; const DEFAULT_DELAY_MS = 200; @@ -12,53 +13,22 @@ const DEFAULT_DELAY_MS = 200; * - Search prompt and empty results behaviors */ const asyncContainer = Typeahead => { - return React.createClass({ - propTypes: { - /** - * Delay, in milliseconds, before performing search. - */ - delay: PropTypes.number, - /** - * Callback to perform when the search is executed. - */ - onSearch: PropTypes.func.isRequired, - /** - * Options to be passed to the typeahead. Will typically be the query - * results, but can also be initial default options. - */ - options: PropTypes.array, - /** - * Text displayed in the menu when there is no user input. - */ - promptText: PropTypes.string, - /** - * Text displayed in the menu while the request is pending. - */ - searchText: PropTypes.string, - /** - * Whether or not the component should cache query results. - */ - useCache: PropTypes.bool, - }, - - getDefaultProps() { - return { - delay: DEFAULT_DELAY_MS, - minLength: 2, - options: [], - promptText: 'Type to search...', - searchText: 'Searching...', - useCache: true, - }; - }, - getInitialState() { - return { + class Container extends React.Component { + + constructor(props) { + super(props); + + this._handleChange = this._handleChange.bind(this); + this._handleInputChange = this._handleInputChange.bind(this); + this._handleSearch = this._handleSearch.bind(this); + + this.state = { hasSelection: false, query: '', requestPending: false, }; - }, + } componentWillMount() { this._cache = {}; @@ -66,7 +36,7 @@ const asyncContainer = Typeahead => { this._handleSearch, this.props.delay ); - }, + } componentWillReceiveProps(nextProps) { const {options, useCache} = nextProps; @@ -81,12 +51,12 @@ const asyncContainer = Typeahead => { } this.setState({requestPending: false}); - }, + } componentWillUnmount() { this._cache = {}; this._handleSearchDebounced.cancel(); - }, + } render() { const {allowNew, options, useCache, ...props} = this.props; @@ -111,14 +81,14 @@ const asyncContainer = Typeahead => { ref={instance => this._instance = instance} /> ); - }, + } /** * Make the component instance available. */ getInstance() { return this._instance.getInstance(); - }, + } _getEmptyLabel() { const { @@ -140,17 +110,17 @@ const asyncContainer = Typeahead => { } return emptyLabel; - }, + } _handleChange(selected) { this.props.onChange && this.props.onChange(selected); this.setState({hasSelection: !!selected.length}); - }, + } _handleInputChange(query) { this.props.onInputChange && this.props.onInputChange(query); this._handleSearchDebounced(query); - }, + } _handleSearch(initialQuery) { const { @@ -185,8 +155,47 @@ const asyncContainer = Typeahead => { // Perform the async search. this.setState({requestPending: true}, () => onSearch(query)); - }, - }); + } + } + + Container.propTypes = { + /** + * Delay, in milliseconds, before performing search. + */ + delay: PropTypes.number, + /** + * Callback to perform when the search is executed. + */ + onSearch: PropTypes.func.isRequired, + /** + * Options to be passed to the typeahead. Will typically be the query + * results, but can also be initial default options. + */ + options: PropTypes.array, + /** + * Text displayed in the menu when there is no user input. + */ + promptText: PropTypes.string, + /** + * Text displayed in the menu while the request is pending. + */ + searchText: PropTypes.string, + /** + * Whether or not the component should cache query results. + */ + useCache: PropTypes.bool, + }; + + Container.defaultProps = { + delay: DEFAULT_DELAY_MS, + minLength: 2, + options: [], + promptText: 'Type to search...', + searchText: 'Searching...', + useCache: true, + }; + + return Container; }; export default asyncContainer; diff --git a/src/containers/menuItemContainer.js b/src/containers/menuItemContainer.js index bfa0ce68..c4183f9b 100644 --- a/src/containers/menuItemContainer.js +++ b/src/containers/menuItemContainer.js @@ -1,11 +1,13 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; +import createReactClass from 'create-react-class'; import {findDOMNode} from 'react-dom'; import getDisplayName from '../utils/getDisplayName'; import scrollIntoViewIfNeeded from '../utils/scrollIntoViewIfNeeded'; const menuItemContainer = Component => ( - React.createClass({ + createReactClass({ displayName: `menuItemContainer(${getDisplayName(Component)})`, propTypes: { diff --git a/src/containers/tokenContainer.js b/src/containers/tokenContainer.js index c761edb5..0916db15 100644 --- a/src/containers/tokenContainer.js +++ b/src/containers/tokenContainer.js @@ -11,14 +11,22 @@ import {BACKSPACE} from '../utils/keyCode'; * be easily re-used. */ const tokenContainer = Component => { - const WrappedComponent = React.createClass({ - displayName: `tokenContainer(${getDisplayName(Component)})`, + class WrappedComponent extends React.Component { + displayName = `tokenContainer(${getDisplayName(Component)})`; - getInitialState() { - return { + constructor(props) { + super(props); + + this._handleBlur = this._handleBlur.bind(this); + this._handleKeyDown = this._handleKeyDown.bind(this); + this._handleRemove = this._handleRemove.bind(this); + this._handleSelect = this._handleSelect.bind(this); + this.handleClickOutside = this.handleClickOutside.bind(this); + + this.state = { selected: false, }; - }, + } render() { const tokenProps = omit(this.props, [ @@ -36,13 +44,13 @@ const tokenContainer = Component => { onKeyDown={this._handleKeyDown} /> ); - }, + } _handleBlur(e) { findDOMNode(this).blur(); this.setState({selected: false}); this.props.disableOnClickOutside && this.props.disableOnClickOutside(); - }, + } _handleKeyDown(e) { switch (e.keyCode) { @@ -55,25 +63,25 @@ const tokenContainer = Component => { } break; } - }, + } /** * From `onClickOutside` HOC. */ handleClickOutside(e) { this._handleBlur(); - }, + } _handleRemove(e) { this.props.onRemove && this.props.onRemove(); - }, + } _handleSelect(e) { e.stopPropagation(); this.setState({selected: true}); this.props.enableOnClickOutside && this.props.enableOnClickOutside(); - }, - }); + } + } return onClickOutside(WrappedComponent); }; diff --git a/test/ClearButtonSpec.js b/test/ClearButtonSpec.js index 0d1db67d..7e922ae5 100644 --- a/test/ClearButtonSpec.js +++ b/test/ClearButtonSpec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import React from 'react'; -import ReactTestUtils from 'react-addons-test-utils'; +import ReactTestUtils from 'react-dom/test-utils'; import ClearButton from '../src/ClearButton'; diff --git a/test/LoaderSpec.js b/test/LoaderSpec.js index 4cdc7e11..17637aa8 100644 --- a/test/LoaderSpec.js +++ b/test/LoaderSpec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import React from 'react'; -import ReactTestUtils from 'react-addons-test-utils'; +import ReactTestUtils from 'react-dom/test-utils'; import Loader from '../src/Loader'; diff --git a/test/MenuItemSpec.js b/test/MenuItemSpec.js index e84f0a2d..04339999 100644 --- a/test/MenuItemSpec.js +++ b/test/MenuItemSpec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import React from 'react'; -import ReactTestUtils from 'react-addons-test-utils'; +import ReactTestUtils from 'react-dom/test-utils'; import {BaseMenuItem} from '../src/MenuItem'; diff --git a/test/TextInputSpec.js b/test/TextInputSpec.js index 62983208..38c85411 100644 --- a/test/TextInputSpec.js +++ b/test/TextInputSpec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import React from 'react'; -import ReactTestUtils from 'react-addons-test-utils'; +import ReactTestUtils from 'react-dom/test-utils'; import TextInput from '../src/TextInput'; diff --git a/test/TokenSpec.js b/test/TokenSpec.js index fb0bdb38..7d1b2e9c 100644 --- a/test/TokenSpec.js +++ b/test/TokenSpec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {noop} from 'lodash'; import React from 'react'; -import ReactTestUtils from 'react-addons-test-utils'; +import ReactTestUtils from 'react-dom/test-utils'; import Token from '../src/Token'; diff --git a/test/TokenizerInputSpec.js b/test/TokenizerInputSpec.js index e503d0df..15eec852 100644 --- a/test/TokenizerInputSpec.js +++ b/test/TokenizerInputSpec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import React from 'react'; -import ReactTestUtils from 'react-addons-test-utils'; +import ReactTestUtils from 'react-dom/test-utils'; import TokenizerInput from '../src/TokenizerInput'; diff --git a/test/TypeaheadInputSpec.js b/test/TypeaheadInputSpec.js index 45a645e2..3ce3a4e1 100644 --- a/test/TypeaheadInputSpec.js +++ b/test/TypeaheadInputSpec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import React from 'react'; -import ReactTestUtils from 'react-addons-test-utils'; +import ReactTestUtils from 'react-dom/test-utils'; import TypeaheadInput from '../src/TypeaheadInput'; diff --git a/test/TypeaheadMenuSpec.js b/test/TypeaheadMenuSpec.js index a5b24d95..acd3bac2 100644 --- a/test/TypeaheadMenuSpec.js +++ b/test/TypeaheadMenuSpec.js @@ -1,14 +1,16 @@ import {expect} from 'chai'; import {noop, range} from 'lodash'; -import React, {PropTypes} from 'react'; -import ReactTestUtils from 'react-addons-test-utils'; +import React from 'react'; +import PropTypes from 'prop-types'; +import createReactClass from 'create-react-class'; +import ReactTestUtils from 'react-dom/test-utils'; import MenuItem, {BaseMenuItem} from '../src/MenuItem'; import TypeaheadMenu from '../src/TypeaheadMenu'; import options from '../example/exampleData'; -const TypeaheadContext = React.createClass({ +const TypeaheadContext = createReactClass({ childContextTypes: { activeIndex: PropTypes.number.isRequired, onActiveItemChange: PropTypes.func.isRequired, diff --git a/test/TypeaheadSpec.js b/test/TypeaheadSpec.js index 35758273..b712eac2 100644 --- a/test/TypeaheadSpec.js +++ b/test/TypeaheadSpec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {range} from 'lodash'; import React from 'react'; -import ReactTestUtils from 'react-addons-test-utils'; +import ReactTestUtils from 'react-dom/test-utils'; import TokenizerInput from '../src/TokenizerInput'; import Typeahead from '../src/Typeahead';