diff --git a/.babelrc b/.babelrc index 3fed1fce7..e7e6f776d 100644 --- a/.babelrc +++ b/.babelrc @@ -1,20 +1,36 @@ { "presets": [ - ["es2015", {"modules": false}], - "react" - ], - "plugins": [ - "transform-object-rest-spread" + [ + "@babel/env", + { + "loose": true, + "shippedProposals": true, + "modules": "commonjs", + "targets": { + "ie": 9 + } + } + ], + "@babel/react" ], + "plugins": ["@babel/plugin-proposal-export-default-from", "@babel/plugin-proposal-export-namespace-from", "@babel/plugin-transform-runtime", "@babel/plugin-proposal-object-rest-spread"], "env": { - "lib-dir": { - "plugins": ["transform-es2015-modules-commonjs"] - }, - "webpack": { - "plugins": ["transform-es2015-modules-commonjs"] - }, - "test": { - "plugins": ["transform-es2015-modules-commonjs"] + "esm-dir": { + "presets": [ + [ + "@babel/env", + { + "loose": true, + "shippedProposals": true, + "modules": false, + "targets": { + "ie": 9 + } + } + ], + "@babel/react" + ], + "plugins": [["@babel/plugin-transform-runtime", { "useESModules": true }]] } } } diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 5f7037303..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,63 +0,0 @@ -var ecmaFeatures = { - 'jsx': true, - 'arrowFunctions': true, - 'blockBindings': true, - 'defaultParams': true, - 'destructuring': true, - 'forOf': true, - 'generators': true, - 'objectLiteralComputedProperties': true, - 'objectLiteralShorthandMethods': true, - 'objectLiteralShorthandProperties': true, - 'experimentalObjectRestSpread': true, - 'restParams': true, - 'spread': true, - 'templateStrings': true, - 'modules': true, - 'classes': true -}; - -var rules = { - 'comma-dangle': 0, - 'new-cap': 0, - 'arrow-body-style': 0, - 'prefer-template': 0, - 'no-underscore-dangle': 0, - 'object-shorthand': 0, - 'func-names': 0, - 'no-extra-parens': ['error', 'functions'], - 'dot-notation': 0, - 'max-len': 0, - 'camelcase': 0, - 'react/jsx-pascal-case': 0, - 'prefer-const': 0, - 'react/jsx-filename-extension': 0, - 'linebreak-style': 0, - 'react/require-extension': 0, - 'react/no-children-prop': 0, - 'react/require-default-props': 0, - 'react/forbid-prop-types': 0, - 'jsx-a11y/no-noninteractive-element-interactions': 1 -}; - -module.exports = { - "root": true, - 'extends': 'airbnb', - 'env': { - 'browser': true, - 'node': true, - 'es6': true - }, - 'globals': { - 'describe': true, - 'it': true - }, - 'plugins': [ - 'react' - ], - 'parserOptions': { - 'sourceType': 'module', - 'ecmaFeatures': ecmaFeatures - }, - rules: rules -}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..520a52e43 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": ["react-app"], + "rules": { + "react/forbid-foreign-prop-types": 0 + } +} \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 000000000..206c92516 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +- components: `name` +- reactstrap version `#x.x.x` +- import method `umd/csj/es` +- react version `#x.x.x` +- bootstrap version `#x.x.x` + +### What is happening? + + + +### What should be happening? + + + +### Steps to reproduce issue + +1. ... +2. ... + +### Error message in console + +``` +paste error message with stacktrack here +``` + +### Code + + diff --git a/.gitignore b/.gitignore index ef0517a35..3bd1d38db 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ node_modules /dist /build /lib +/es .idea .history npm-debug.log +package-lock.json diff --git a/.travis.yml b/.travis.yml index e3f4b8710..6e1d769ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: false dist: trusty language: node_js node_js: -- 6 +- 10 cache: directories: - node_modules @@ -21,15 +21,15 @@ env: branches: only: - master - - /^\d+\.\d+(\.\d+)?(-\S*)?$/ -before_deploy: - - "echo 'Preparing to publish'" - - npm run build + - "/^\\d+\\.\\d+(\\.\\d+)?(-\\S*)?$/" +before_deploy: +- echo 'Preparing to publish' +- npm run build deploy: provider: npm - email: "dumbdrun@gmail.com" + email: dumbdrum@gmail.com api_key: - secure: P+6SEhs1BQ8sUygZ3L+Mqe2lxrhA9CB35DOds4d8Lp1LneRMm3jDt/DDBhIxF3N1xhd5f9BaHifyXcOfm+EiNIczkTP2G8wJCaxoA7bVKLNx27//kXBUVq7J4vnM3nZltFO7YHd3l46XkTeapQuXVXmbNbT6enQISkwNKMGd/hJk/hNqtS/faJAk84wqjcSGh/wY1YQ7po7FaAUWN2V6y8M8GfW/8s9BEAlY0sY4QYZr90CnSmOsvFtY//0Mx0NevUbo7UcEa+8cO7YOsrgdIMxQBq3UlN9L2XkQyLBobwShk+GeQhFWNVw5aByluWg8DoY5GOkgRSMhTLUyQ3fKnV8B2Mo1SqsqdSD9830TWY+Br47O1Mx20WrbzkpMdGa/Msmvrk0wMFNY0+TuMyn6ItMpGnijbKLoM23deiq69TwgUlVLOHGRcuE7P9iyP+bCNgHxPtI3d3akO2ZWO+8wiVw8sJ29Ywr4x+omFeuyLalfNCGVYF2vg07hT8oWPXNPcNA7Lktqb6PWmaUMGAaLv6Awe8lBu/RT/Tx5rVzdygxKj+xti95vduKaSZp0KfNCkA1txemQCbiuURM5S7KhAhWsDrFJOlYdP5tSf53HhCNrpniAEUXPuNW0aPjgFYYcOVFL/7U4eWLUaXaFL9mBF4wT6K9ekjHaEOSCjZeKDCw= + secure: Fth8EnmhP+/TV7Uj5Czz3nwuek40GOnIlWRppqGs2IA+Q/le7V3R4xvgTlA/p5xN17IH1EhT/UVZBoNd1lOi6wcbgqrkeqPTarqNbkhzaEFJokNBkOJR7FmGC41Y76XbqPl2zeU//p+7dtrCEUacXCDWEfoCjA34GHaD0snd2yuPXYH0FOy1YkydauqGjLg8Y65g3f94xxAnY2Wm2TJzC5rnCjKPMXKEKAX6LpI5UIbq8UDPP8sFv+JpAkgI1/ygY8daBOeIurJEUUoVNazZWtvgdMFcBlL90QbP62HTU1vGTMK7cNT64COTlOrP4BdvIgWh1KaYXiboEwFrooA3fp/1KDNcCC1sXmXa2UoFOAUh9vr4SFBA8vE7tjWEvYtI2zCVD8b5ekLGfPandezKolh8V3sbeJrDFhY0G9lap8ftHWQejohvG/6xjI+/pe+UDpHH1USIqf0DtAnDn5mNWWav/5xCp8HlsIsmh5EKCM3//a8aWY6H+eMfeEFPzU/X3jC4ZUGZ+D5ZGQQ/NX9tvyeoH0pnRnlZil8MAjf/e2/IWu66ruAUde3eMpX4h+w0q8bVGvH4ObR7QKiJdTntwJSsp9iO4uBHTSpoo9yYO9ycepMAc5af0Np7K1xOv7ja5BvMesSVzyZsbDK3Q8iOOWBDk54bqV7PrFtRPn+VIjk= skip_cleanup: true on: tags: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 13eeae3e8..82e282d28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,138 @@ + +# [7.1.0](https://github.com/reactstrap/reactstrap/compare/7.0.2...7.1.0) (2019-01-15) + + +### Bug Fixes + +* **NavLink:** console error while using [@reach](https://github.com/reach)/Router ([#1350](https://github.com/reactstrap/reactstrap/issues/1350)) ([477e1a8](https://github.com/reactstrap/reactstrap/commit/477e1a8)), closes [#1308](https://github.com/reactstrap/reactstrap/issues/1308) + + +### Features + +* support forwardRef components as tag ([4cda8bf](https://github.com/reactstrap/reactstrap/commit/4cda8bf)) +* **Popover:** add legacy trigger, replacing unreleased isInteractive prop ([6b3c3ce](https://github.com/reactstrap/reactstrap/commit/6b3c3ce)) +* **Popover:** backward-compatible Popover behavior ([#1360](https://github.com/reactstrap/reactstrap/issues/1360)) ([1d5ce83](https://github.com/reactstrap/reactstrap/commit/1d5ce83)), closes [#1349](https://github.com/reactstrap/reactstrap/issues/1349) +* **Spinner:** Add spinner component ([#1352](https://github.com/reactstrap/reactstrap/issues/1352)) ([45952e2](https://github.com/reactstrap/reactstrap/commit/45952e2)), closes [#1347](https://github.com/reactstrap/reactstrap/issues/1347) +* **Switch:** Add support for CustomInput type='switch' ([#1353](https://github.com/reactstrap/reactstrap/issues/1353)) ([7c1e166](https://github.com/reactstrap/reactstrap/commit/7c1e166)), closes [#1348](https://github.com/reactstrap/reactstrap/issues/1348) + + + + +## [7.0.2](https://github.com/reactstrap/reactstrap/compare/7.0.0...7.0.2) (2018-12-31) + + +### Bug Fixes + +* fix release artifacts ([#1345](https://github.com/reactstrap/reactstrap/issues/1345)) ([b5710ef](https://github.com/reactstrap/reactstrap/commit/b5710ef)) +* **npm:** fix published files ([7eedbab](https://github.com/reactstrap/reactstrap/commit/7eedbab)) + + + + +## [7.0.1](https://github.com/reactstrap/reactstrap/compare/7.0.0...7.0.1) (2018-12-31) + + +### Bug Fixes + +* fix release artifacts ([#1345](https://github.com/reactstrap/reactstrap/issues/1345)) ([b5710ef](https://github.com/reactstrap/reactstrap/commit/b5710ef)) + + + + +# [7.0.0](https://github.com/reactstrap/reactstrap/compare/6.5.0...7.0.0) (2018-12-29) + + +### Bug Fixes + +* **CardTitle,CardSubtitle:** div as default tag ([#1298](https://github.com/reactstrap/reactstrap/issues/1298)) ([ea0f1f0](https://github.com/reactstrap/reactstrap/commit/ea0f1f0)), closes [#1297](https://github.com/reactstrap/reactstrap/issues/1297) +* **CarouselIndicators:** li key generate from provided item values ([#1311](https://github.com/reactstrap/reactstrap/issues/1311)) ([fd7506d](https://github.com/reactstrap/reactstrap/commit/fd7506d)), closes [#1310](https://github.com/reactstrap/reactstrap/issues/1310) +* **Dropdown:** enter key triggers onClick -- correction ([#1306](https://github.com/reactstrap/reactstrap/issues/1306)) ([6b50732](https://github.com/reactstrap/reactstrap/commit/6b50732)) +* **Dropdown:** improve keyboard ux, WAI-ARIA ([#1293](https://github.com/reactstrap/reactstrap/issues/1293)) ([506c46a](https://github.com/reactstrap/reactstrap/commit/506c46a)) +* **Dropdown:** Null check on children ([#1294](https://github.com/reactstrap/reactstrap/issues/1294)) ([#1295](https://github.com/reactstrap/reactstrap/issues/1295)) ([dcfde3b](https://github.com/reactstrap/reactstrap/commit/dcfde3b)) +* **FormGroup:** remove `.position-relative` ([#1270](https://github.com/reactstrap/reactstrap/issues/1270)) ([01eb5f9](https://github.com/reactstrap/reactstrap/commit/01eb5f9)), closes [#1269](https://github.com/reactstrap/reactstrap/issues/1269) +* **Input:** make plaintext output input by default ([#1226](https://github.com/reactstrap/reactstrap/issues/1226)) ([ff64c76](https://github.com/reactstrap/reactstrap/commit/ff64c76)), closes [#1225](https://github.com/reactstrap/reactstrap/issues/1225) +* **Modal:** don't propagate handled escape key event ([#1317](https://github.com/reactstrap/reactstrap/issues/1317)) ([5d45b26](https://github.com/reactstrap/reactstrap/commit/5d45b26)) +* **Modal:** only show backdrop when prop is true ([#1271](https://github.com/reactstrap/reactstrap/issues/1271)) ([07ec4b5](https://github.com/reactstrap/reactstrap/commit/07ec4b5)), closes [#1267](https://github.com/reactstrap/reactstrap/issues/1267) +* **PopperContent:** Use create portal instead of unstable_renderSubtreeIntoContainer ([#1254](https://github.com/reactstrap/reactstrap/issues/1254)) ([81da8c5](https://github.com/reactstrap/reactstrap/commit/81da8c5)), closes [#1216](https://github.com/reactstrap/reactstrap/issues/1216) + + +### Features + +* **Badge:** allow innerRef ([#1264](https://github.com/reactstrap/reactstrap/issues/1264)) ([2caaaa5](https://github.com/reactstrap/reactstrap/commit/2caaaa5)) +* **CardBody:** add innerRef to CardBody ([#1318](https://github.com/reactstrap/reactstrap/issues/1318)) ([4b0474f](https://github.com/reactstrap/reactstrap/commit/4b0474f)), closes [#1314](https://github.com/reactstrap/reactstrap/issues/1314) +* **Table:** add innerRef prop ([#1296](https://github.com/reactstrap/reactstrap/issues/1296)) ([bb84c85](https://github.com/reactstrap/reactstrap/commit/bb84c85)) +* **Tooltip,Popover:** base component for Tooltip and Popover ([#1222](https://github.com/reactstrap/reactstrap/issues/1222)) ([b45907b](https://github.com/reactstrap/reactstrap/commit/b45907b)), closes [#1022](https://github.com/reactstrap/reactstrap/issues/1022) [#1181](https://github.com/reactstrap/reactstrap/issues/1181) + + +### BREAKING CHANGES + +* **Input:** previously plaintext on Input would output a 'p' tag. To better line up with bootstrap it will not output an 'input' tag. If you need a 'p' tag, provide tag="p" prop +* **Popover:** Popover will no longer dismiss when clicking away from it. To get this behaviour please use trigger="focus". In 7.1.0, trigger="legacy" has been added to get the exact previous behavior. + + + + +# [6.5.0](https://github.com/reactstrap/reactstrap/compare/6.4.0...6.5.0) (2018-10-04) + + +### Bug Fixes + +* **boundariesElement:** add DOMElement to allowed proptypes ([#1238](https://github.com/reactstrap/reactstrap/issues/1238)) ([cfe7318](https://github.com/reactstrap/reactstrap/commit/cfe7318)) +* **Dropdown:** enter key triggers onClick ([#1232](https://github.com/reactstrap/reactstrap/issues/1232)) ([f2528da](https://github.com/reactstrap/reactstrap/commit/f2528da)), closes [#1228](https://github.com/reactstrap/reactstrap/issues/1228) +* **Modal:** do not trigger focus on SVG elements ([#1212](https://github.com/reactstrap/reactstrap/issues/1212)) ([c7e6ef5](https://github.com/reactstrap/reactstrap/commit/c7e6ef5)), closes [#1208](https://github.com/reactstrap/reactstrap/issues/1208) +* **Modal:** use static openCount to become resilient to classList modification ([#1190](https://github.com/reactstrap/reactstrap/issues/1190)) ([c8ceeeb](https://github.com/reactstrap/reactstrap/commit/c8ceeeb)), closes [#1189](https://github.com/reactstrap/reactstrap/issues/1189) +* **Tooltip:** clear timers on component unmount ([#1180](https://github.com/reactstrap/reactstrap/issues/1180)) ([9fea409](https://github.com/reactstrap/reactstrap/commit/9fea409)) + + +### Features + +* **Button:** add close icon support ([#1206](https://github.com/reactstrap/reactstrap/issues/1206)) ([02f5e9a](https://github.com/reactstrap/reactstrap/commit/02f5e9a)), closes [#1182](https://github.com/reactstrap/reactstrap/issues/1182) +* **FormGrid:** Add form-row ([#1237](https://github.com/reactstrap/reactstrap/issues/1237)) ([205e80d](https://github.com/reactstrap/reactstrap/commit/205e80d)), closes [#1195](https://github.com/reactstrap/reactstrap/issues/1195) +* **Modal:** add custom close button ([#1168](https://github.com/reactstrap/reactstrap/issues/1168)) ([5f33a1a](https://github.com/reactstrap/reactstrap/commit/5f33a1a)) +* **Popover/Tooltip:** Implented usage of react 16.3 RefObject as target ([#1200](https://github.com/reactstrap/reactstrap/issues/1200)) ([0eade39](https://github.com/reactstrap/reactstrap/commit/0eade39)), closes [#1198](https://github.com/reactstrap/reactstrap/issues/1198) + + + + +# [6.4.0](https://github.com/reactstrap/reactstrap/compare/6.3.1...6.4.0) (2018-08-17) + + +### Bug Fixes + +* **Modal:** don't fade backdrop if there is no transition ([#1172](https://github.com/reactstrap/reactstrap/issues/1172)) ([77e7beb](https://github.com/reactstrap/reactstrap/commit/77e7beb)), closes [#1100](https://github.com/reactstrap/reactstrap/issues/1100) +* **Modal:** prevent scrollbar from closing ([#1165](https://github.com/reactstrap/reactstrap/issues/1165)) ([9d7948f](https://github.com/reactstrap/reactstrap/commit/9d7948f)), closes [#1097](https://github.com/reactstrap/reactstrap/issues/1097) + + +### Features + +* **Dropdown:** Select first element matching pressed key ([#1160](https://github.com/reactstrap/reactstrap/issues/1160)) ([abbac56](https://github.com/reactstrap/reactstrap/commit/abbac56)), closes [#1156](https://github.com/reactstrap/reactstrap/issues/1156) +* **Modal:** add charCode prop for custom icon ([#1162](https://github.com/reactstrap/reactstrap/issues/1162)) ([4d19b09](https://github.com/reactstrap/reactstrap/commit/4d19b09)), closes [#1155](https://github.com/reactstrap/reactstrap/issues/1155) +* **Modal:** return focus after modal closes ([#1175](https://github.com/reactstrap/reactstrap/issues/1175)) ([1b27c49](https://github.com/reactstrap/reactstrap/commit/1b27c49)), closes [#1174](https://github.com/reactstrap/reactstrap/issues/1174) +* **Modal:** trap focus in modal ([#1161](https://github.com/reactstrap/reactstrap/issues/1161)) ([e6781d7](https://github.com/reactstrap/reactstrap/commit/e6781d7)), closes [#310](https://github.com/reactstrap/reactstrap/issues/310) +* **Popover/Tooltip:** add boundariesElement prop ([#1149](https://github.com/reactstrap/reactstrap/issues/1149)) ([02b4555](https://github.com/reactstrap/reactstrap/commit/02b4555)), closes [#1118](https://github.com/reactstrap/reactstrap/issues/1118) + + + + +## [6.3.1](https://github.com/reactstrap/reactstrap/compare/6.3.0...6.3.1) (2018-07-27) + + +### Bug Fixes + +* **Collapse:** add function and string to innerRef propType ([#1129](https://github.com/reactstrap/reactstrap/issues/1129)) ([f380b41](https://github.com/reactstrap/reactstrap/commit/f380b41)), closes [#1054](https://github.com/reactstrap/reactstrap/issues/1054) +* **CustomInput:** allow any node for label ([#1095](https://github.com/reactstrap/reactstrap/issues/1095)) ([c1374b4](https://github.com/reactstrap/reactstrap/commit/c1374b4)) + + + + +# [6.3.0](https://github.com/reactstrap/reactstrap/compare/6.2.0...6.3.0) (2018-07-10) + +### Features + +* **CustomInput:** add innerRef to CustomInput ([#1123](https://github.com/reactstrap/reactstrap/issues/1123)) ([418fdf8](https://github.com/reactstrap/reactstrap/commit/418fdf8)) + +* **Tooltip:** allow additional arrow classNames ([#1119](https://github.com/reactstrap/reactstrap/issues/1119)) ([9ffa55f](https://github.com/reactstrap/reactstrap/commit/9ffa55f)), closes [#1117](https://github.com/reactstrap/reactstrap/issues/1117) + # [6.2.0](https://github.com/reactstrap/reactstrap/compare/6.1.0...6.2.0) (2018-06-28) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..9a367031d --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ + +- [ ] Bug fix +- [ ] New feature +- [ ] Chore +- [ ] Breaking change +- [ ] There is an open issue which this change addresses +- [ ] I have read the **[CONTRIBUTING](./CONTRIBUTING.md)** document. +- [ ] My commits follow the [Git Commit Guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits) +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- - [ ] I have updated the documentation accordingly. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. + + + + + diff --git a/README.md b/README.md index 9700ac6ec..4ef32dbf8 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,24 @@ Stateless React Components for Bootstrap 4. ## Getting Started -Follow the [create-react-app instructions](https://github.com/facebookincubator/create-react-app#getting-started) up to the `Adding Bootstrap` section and instead follow the reactstrap version of [adding bootstrap](#adding-bootstrap). +Follow the [create-react-app instructions](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md) **up to** the `Adding Bootstrap` section and instead follow the reactstrap version of [adding bootstrap](#adding-bootstrap). ### tl;dr + ``` +npx create-react-app my-app +cd my-app/ +npm start +``` +or, if npx (Node >= 6 and npm >= 5.2 ) not available + ``` npm install -g create-react-app create-react-app my-app cd my-app/ npm start -``` +``` Then open [http://localhost:3000/](http://localhost:3000/) to see your app. The initial structure of your app is setup. Next, let's [add reactstrap and bootstrap](#adding-bootstrap). @@ -27,8 +34,8 @@ Then open [http://localhost:3000/](http://localhost:3000/) to see your app. The Install reactstrap and Bootstrap from NPM. Reactstrap does not include Bootstrap CSS so this needs to be installed as well: ``` -npm install --save bootstrap@4.1.1 -npm install --save reactstrap react@^16.3.2 react-dom@^16.3.2 +npm install --save bootstrap +npm install --save reactstrap react react-dom ``` Import Bootstrap CSS in the ```src/index.js``` file: diff --git a/config-overrides.js b/config-overrides.js new file mode 100644 index 000000000..b64b254c7 --- /dev/null +++ b/config-overrides.js @@ -0,0 +1,3 @@ +const { override, addBabelPlugins } = require('customize-cra'); + +module.exports = override(...addBabelPlugins('@babel/plugin-proposal-export-default-from', '@babel/plugin-proposal-export-namespace-from')); diff --git a/docs/lib/Components/AlertsPage.js b/docs/lib/Components/AlertsPage.js index 9c7097e34..c8a929592 100644 --- a/docs/lib/Components/AlertsPage.js +++ b/docs/lib/Components/AlertsPage.js @@ -6,22 +6,22 @@ import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import AlertExample from '../examples/Alert'; -const AlertExampleSource = require('!!raw!../examples/Alert'); +const AlertExampleSource = require('!!raw-loader!../examples/Alert'); import AlertLinkExample from '../examples/AlertLink'; -const AlertLinkExampleSource = require('!!raw!../examples/AlertLink'); +const AlertLinkExampleSource = require('!!raw-loader!../examples/AlertLink'); import AlertContentExample from '../examples/AlertContent'; -const AlertContentExampleSource = require('!!raw!../examples/AlertContent'); +const AlertContentExampleSource = require('!!raw-loader!../examples/AlertContent'); import AlertDismissExample from '../examples/AlertDismiss'; -const AlertDismissExampleSource = require('!!raw!../examples/AlertDismiss'); +const AlertDismissExampleSource = require('!!raw-loader!../examples/AlertDismiss'); import AlertUncontrolledDismissExample from '../examples/AlertUncontrolledDismiss'; -const AlertUncontrolledDismissExampleSource = require('!!raw!../examples/AlertUncontrolledDismiss'); +const AlertUncontrolledDismissExampleSource = require('!!raw-loader!../examples/AlertUncontrolledDismiss'); import {AlertFadelessExample, UncontrolledAlertFadelessExample} from '../examples/AlertFadeless'; -const AlertFadelessExampleSource = require('!!raw!../examples/AlertFadeless'); +const AlertFadelessExampleSource = require('!!raw-loader!../examples/AlertFadeless'); export default class AlertsPage extends React.Component { render() { @@ -99,7 +99,7 @@ export default class AlertsPage extends React.Component { Alerts without fade

- Fade can be disbaled using fade=false. + Fade can be disabled using fade=false.

diff --git a/docs/lib/Components/BadgePage.js b/docs/lib/Components/BadgePage.js index e15bc293c..b0e0f11e7 100644 --- a/docs/lib/Components/BadgePage.js +++ b/docs/lib/Components/BadgePage.js @@ -5,19 +5,19 @@ import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import BadgeExample from '../examples/Badge'; -const BadgeExampleSource = require('!!raw!../examples/Badge'); +const BadgeExampleSource = require('!!raw-loader!../examples/Badge'); import BadgeButtonExample from '../examples/BadgeButton'; -const BadgeButtonExampleSource = require('!!raw!../examples/BadgeButton'); +const BadgeButtonExampleSource = require('!!raw-loader!../examples/BadgeButton'); import BadgePillsExample from '../examples/BadgePills'; -const BadgePillsExampleSource = require('!!raw!../examples/BadgePills'); +const BadgePillsExampleSource = require('!!raw-loader!../examples/BadgePills'); import BadgeVariationsExample from '../examples/BadgeVariations'; -const BadgeVariationsExampleSource = require('!!raw!../examples/BadgeVariations'); +const BadgeVariationsExampleSource = require('!!raw-loader!../examples/BadgeVariations'); import BadgeLinksExample from '../examples/BadgeLinks'; -const BadgeLinksExampleSource = require('!!raw!../examples/BadgeLinks'); +const BadgeLinksExampleSource = require('!!raw-loader!../examples/BadgeLinks'); export default class BadgesPage extends React.Component { render() { diff --git a/docs/lib/Components/BreadcrumbsPage.js b/docs/lib/Components/BreadcrumbsPage.js index a7e441ad7..57070c046 100644 --- a/docs/lib/Components/BreadcrumbsPage.js +++ b/docs/lib/Components/BreadcrumbsPage.js @@ -5,10 +5,10 @@ import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import BreadcrumbExample from '../examples/Breadcrumb'; -const BreadcrumbExampleSource = require('!!raw!../examples/Breadcrumb'); +const BreadcrumbExampleSource = require('!!raw-loader!../examples/Breadcrumb'); import BreadcrumbNoListExample from '../examples/BreadcrumbNoList'; -const BreadcrumbNoListExampleSource = require('!!raw!../examples/BreadcrumbNoList'); +const BreadcrumbNoListExampleSource = require('!!raw-loader!../examples/BreadcrumbNoList'); export default class BreadcrumbsPage extends React.Component { render() { diff --git a/docs/lib/Components/ButtonDropdownPage.js b/docs/lib/Components/ButtonDropdownPage.js index ef9744014..2af9ecd11 100644 --- a/docs/lib/Components/ButtonDropdownPage.js +++ b/docs/lib/Components/ButtonDropdownPage.js @@ -10,9 +10,10 @@ import { import Example from '../examples/ButtonDropdownMulti'; import ExampleSplit from '../examples/ButtonDropdownMultiSplit'; import ButtonDropdownExample from '../examples/ButtonDropdown'; +import ButtonDropdownUncontrolledExample from '../examples/ButtonDropdownUncontrolled' import SectionTitle from '../UI/SectionTitle'; -const ButtonDropdownExampleSource = require('!!raw!../examples/ButtonDropdown'); +const ButtonDropdownExampleSource = require('!!raw-loader!../examples/ButtonDropdown'); export default class ButtonDropdownPage extends React.Component { constructor(props) { @@ -165,6 +166,33 @@ DropdownToggle.propTypes = { `} + Uncontrolled Dropdown +
+ +
+
+          
+{`import React from 'react';
+import { UncontrolledButtonDropdown, DropdownMenu, DropdownItem, DropdownToggle } from 'reactstrap';
+
+export default function Example () => {
+  return (
+    
+      
+        Dropdown
+      
+      
+        Header
+        Action
+        Another Action
+        
+        Another Action
+      
+    
+  );
+}`}
+          
+        
Drop direction variations
diff --git a/docs/lib/Components/ButtonGroupPage.js b/docs/lib/Components/ButtonGroupPage.js index 6354181b6..51ab48ef0 100644 --- a/docs/lib/Components/ButtonGroupPage.js +++ b/docs/lib/Components/ButtonGroupPage.js @@ -13,10 +13,10 @@ import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import ButtonGroupExample from '../examples/ButtonGroup'; -const ButtonGroupExampleSource = require('!!raw!../examples/ButtonGroup'); +const ButtonGroupExampleSource = require('!!raw-loader!../examples/ButtonGroup'); import ButtonToolbarExample from '../examples/ButtonToolbar'; -const ButtonToolbarExampleSource = require('!!raw!../examples/ButtonToolbar'); +const ButtonToolbarExampleSource = require('!!raw-loader!../examples/ButtonToolbar'); export default class ButtonGroupPage extends React.Component { diff --git a/docs/lib/Components/ButtonsPage.js b/docs/lib/Components/ButtonsPage.js index 065c2c6ff..0749f4cca 100644 --- a/docs/lib/Components/ButtonsPage.js +++ b/docs/lib/Components/ButtonsPage.js @@ -5,13 +5,16 @@ import { Button } from 'reactstrap'; import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import ButtonExample from '../examples/Button'; -const ButtonExampleSource = require('!!raw!../examples/Button'); +const ButtonExampleSource = require('!!raw-loader!../examples/Button'); import ButtonOutline from '../examples/ButtonOutline'; -const ButtonOutlineSource = require('!!raw!../examples/ButtonOutline'); +const ButtonOutlineSource = require('!!raw-loader!../examples/ButtonOutline'); import ButtonStateful from '../examples/ButtonStateful'; -const ButtonStatefulSource = require('!!raw!../examples/ButtonStateful'); +const ButtonStatefulSource = require('!!raw-loader!../examples/ButtonStateful'); + +import ButtonCloseIcon from '../examples/ButtonCloseIcon'; +const ButtonCloseIconSource = require('!!raw-loader!../examples/ButtonCloseIcon'); export default class ButtonsPage extends React.Component { render() { @@ -44,7 +47,10 @@ export default class ButtonsPage extends React.Component { innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), onClick: PropTypes.func, - size: PropTypes.string + size: PropTypes.string, + + // use close prop for BS4 close icon utility + close: PropTypes.bool, }`} @@ -113,7 +119,7 @@ export default class ButtonsPage extends React.Component { Checkbox and Radio Buttons (Stateful Buttons)

- In order to have checkbox and radio buttons, your component needs to manage the state of which button(s) are active/select. It is not in the opinion of this library to manage state within it's components so it is left up to you. Below is a simple example showcasing how this could be done uses the components which already exist in this library. + In order to have checkbox and radio buttons, your component needs to manage the state of which button(s) are active/select. It is not in the opinion of this library to manage state within it's components so it is left up to you. Below is a simple example showcasing how this could be done using the components which already exist in this library.

@@ -123,6 +129,20 @@ export default class ButtonsPage extends React.Component { {ButtonStatefulSource} + + Close icon +

+ Use a generic close icon to dismiss content. Use <Button close /> for the default icon. Otherwise, custom content for the button + may be defined. (e.g. JSX: <Button close><span aria-hidden="true">–</span></Button>) The default aria-label is "Close". +

+
+ +
+
+          
+            {ButtonCloseIconSource}
+          
+        
); } diff --git a/docs/lib/Components/CardPage.js b/docs/lib/Components/CardPage.js index 4536a3a8e..220e6554b 100644 --- a/docs/lib/Components/CardPage.js +++ b/docs/lib/Components/CardPage.js @@ -17,18 +17,18 @@ import CardGroupsExample from '../examples/CardGroups'; import CardDecksExample from '../examples/CardDecks'; import CardColumnsExample from '../examples/CardColumns'; -const CardExampleSource = require('!!raw!../examples/Card'); -const CardContentExampleSource = require('!!raw!../examples/CardContentTypes'); -const CardSizingExampleSource = require('!!raw!../examples/CardSizing'); -const CardAlignmentExampleSource = require('!!raw!../examples/CardAlignment'); -const CardHeaderFooterExampleSource = require('!!raw!../examples/CardHeaderFooter'); -const CardImageCapsExampleSource = require('!!raw!../examples/CardImageCaps'); -const CardImageOverlayExampleSource = require('!!raw!../examples/CardImageOverlay'); -const CardBackgroundsExampleSource = require('!!raw!../examples/CardBackgrounds'); -const CardOutlineExampleSource = require('!!raw!../examples/CardOutline'); -const CardGroupsExampleSource = require('!!raw!../examples/CardGroups'); -const CardDecksExampleSource = require('!!raw!../examples/CardDecks'); -const CardColumnsExampleSource = require('!!raw!../examples/CardColumns'); +const CardExampleSource = require('!!raw-loader!../examples/Card'); +const CardContentExampleSource = require('!!raw-loader!../examples/CardContentTypes'); +const CardSizingExampleSource = require('!!raw-loader!../examples/CardSizing'); +const CardAlignmentExampleSource = require('!!raw-loader!../examples/CardAlignment'); +const CardHeaderFooterExampleSource = require('!!raw-loader!../examples/CardHeaderFooter'); +const CardImageCapsExampleSource = require('!!raw-loader!../examples/CardImageCaps'); +const CardImageOverlayExampleSource = require('!!raw-loader!../examples/CardImageOverlay'); +const CardBackgroundsExampleSource = require('!!raw-loader!../examples/CardBackgrounds'); +const CardOutlineExampleSource = require('!!raw-loader!../examples/CardOutline'); +const CardGroupsExampleSource = require('!!raw-loader!../examples/CardGroups'); +const CardDecksExampleSource = require('!!raw-loader!../examples/CardDecks'); +const CardColumnsExampleSource = require('!!raw-loader!../examples/CardColumns'); export default class CardPage extends React.Component { render() { diff --git a/docs/lib/Components/CarouselPage.js b/docs/lib/Components/CarouselPage.js index a5af1499e..13f6b21c9 100644 --- a/docs/lib/Components/CarouselPage.js +++ b/docs/lib/Components/CarouselPage.js @@ -4,11 +4,11 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import CarouselExample from '../examples/Carousel'; -const CarouselExampleSource = require('!!raw!../examples/Carousel'); +const CarouselExampleSource = require('!!raw-loader!../examples/Carousel'); import CarouselUncontrolledExample from '../examples/CarouselUncontrolled'; -const CarouselUncontrolledExampleSource = require('!!raw!../examples/CarouselUncontrolled'); +const CarouselUncontrolledExampleSource = require('!!raw-loader!../examples/CarouselUncontrolled'); import CarouselCustomTagExample from '../examples/CarouselCustomTag'; -const CarouselCustomTagExampleSource = require('!!raw!../examples/CarouselCustomTag'); +const CarouselCustomTagExampleSource = require('!!raw-loader!../examples/CarouselCustomTag'); export default class CarouselPage extends React.Component { render() { @@ -130,7 +130,7 @@ export default class CarouselPage extends React.Component {
           
 {`UncontrolledCarousel.propTypes = {
-  items: PropTypes.array,isRequired,
+  items: PropTypes.array.isRequired,
   indicators: PropTypes.bool, // default: true
   controls: PropTypes.bool, // default: true
   autoPlay: PropTypes.bool, // default: true
diff --git a/docs/lib/Components/CollapsePage.js b/docs/lib/Components/CollapsePage.js
index 6b39e172f..c7ed7cea0 100644
--- a/docs/lib/Components/CollapsePage.js
+++ b/docs/lib/Components/CollapsePage.js
@@ -9,10 +9,10 @@ import UncontrolledCollapseExample from '../examples/CollapseUncontrolled';
 
 import CollapseEventsExample from '../examples/CollapseEvents';
 
-const CollapseExampleSource = require('!!raw!../examples/Collapse');
-const CollapseEventsExampleSource = require('!!raw!../examples/CollapseEvents');
+const CollapseExampleSource = require('!!raw-loader!../examples/Collapse');
+const CollapseEventsExampleSource = require('!!raw-loader!../examples/CollapseEvents');
 
-const UncontrolledCollapseExampleSource = require('!!raw!../examples/CollapseUncontrolled');
+const UncontrolledCollapseExampleSource = require('!!raw-loader!../examples/CollapseUncontrolled');
 
 export default class CollapsePage extends React.Component {
   render() {
@@ -30,7 +30,7 @@ export default class CollapsePage extends React.Component {
         
           
             {`Collapse.propTypes = {
-  ...Transition.propTypes,
+  ...Transition.propTypes, // see note below
   isOpen: PropTypes.bool,
   children: PropTypes.oneOfType([
     PropTypes.arrayOf(PropTypes.node),
@@ -44,6 +44,12 @@ export default class CollapsePage extends React.Component {
 };`}
           
         
+

+ Collapse is wrapped in a Transition component + from react-transition-group/transition. Transition props are passed through to + this wrapper. Refer to the Transition documentation for details: + http://reactcommunity.org/react-transition-group/transition/. +

Events

diff --git a/docs/lib/Components/DropdownsPage.js b/docs/lib/Components/DropdownsPage.js index 661db8557..d93666085 100644 --- a/docs/lib/Components/DropdownsPage.js +++ b/docs/lib/Components/DropdownsPage.js @@ -17,10 +17,10 @@ import CustomDropdownExample from '../examples/CustomDropdown'; import DropdownUncontrolledExample from '../examples/DropdownUncontrolled'; import DropdownSetActiveFromChildExample from '../examples/DropdownSetActiveFromChild'; -const DropdownExampleSource = require('!!raw!../examples/Dropdown'); -const CustomDropdownExampleSource = require('!!raw!../examples/CustomDropdown'); -const DropdownUncontrolledExampleSource = require('!!raw!../examples/DropdownUncontrolled'); -const DropdownSetActiveFromChildSource = require('!!raw!../examples/DropdownSetActiveFromChild'); +const DropdownExampleSource = require('!!raw-loader!../examples/Dropdown'); +const CustomDropdownExampleSource = require('!!raw-loader!../examples/CustomDropdown'); +const DropdownUncontrolledExampleSource = require('!!raw-loader!../examples/DropdownUncontrolled'); +const DropdownSetActiveFromChildSource = require('!!raw-loader!../examples/DropdownSetActiveFromChild'); export default class DropdownPage extends React.Component { constructor(props) { diff --git a/docs/lib/Components/FadePage.js b/docs/lib/Components/FadePage.js index 8535dc4b0..15cd5c4e4 100644 --- a/docs/lib/Components/FadePage.js +++ b/docs/lib/Components/FadePage.js @@ -6,7 +6,7 @@ import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import FadeExample from '../examples/Fade'; -const FadeExampleSource = require('!!raw!../examples/Fade'); +const FadeExampleSource = require('!!raw-loader!../examples/Fade'); export default class FadePage extends React.Component { render() { diff --git a/docs/lib/Components/FormPage.js b/docs/lib/Components/FormPage.js index b4b5ea51d..589469688 100644 --- a/docs/lib/Components/FormPage.js +++ b/docs/lib/Components/FormPage.js @@ -5,34 +5,37 @@ import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import FormExample from '../examples/Form'; -const FormExampleSource = require('!!raw!../examples/Form'); +const FormExampleSource = require('!!raw-loader!../examples/Form'); import FormGridExample from '../examples/FormGrid'; -const FormGridExampleSource = require('!!raw!../examples/FormGrid'); +const FormGridExampleSource = require('!!raw-loader!../examples/FormGrid'); + +import FormGridFormRowExample from '../examples/FormGridFormRow'; +const FormGridFormRowExampleSource = require('!!raw-loader!../examples/FormGridFormRow'); import FormInlineExample from '../examples/FormInline'; -const FormInlineExampleSource = require('!!raw!../examples/FormInline'); +const FormInlineExampleSource = require('!!raw-loader!../examples/FormInline'); import FormFeedbackExample from '../examples/FormFeedback'; -const FormFeedbackExampleSource = require('!!raw!../examples/FormFeedback'); +const FormFeedbackExampleSource = require('!!raw-loader!../examples/FormFeedback'); import InputTypeExample from '../examples/InputType'; -const InputTypeExampleSource = require('!!raw!../examples/InputType'); +const InputTypeExampleSource = require('!!raw-loader!../examples/InputType'); import InlineCheckboxesExample from '../examples/InlineCheckboxes'; -const InlineCheckboxesExampleSource = require('!!raw!../examples/InlineCheckboxes'); +const InlineCheckboxesExampleSource = require('!!raw-loader!../examples/InlineCheckboxes'); import InputSizingExample from '../examples/InputSizing'; -const InputSizingExampleSource = require('!!raw!../examples/InputSizing'); +const InputSizingExampleSource = require('!!raw-loader!../examples/InputSizing'); import InputGridSizingExample from '../examples/InputGridSizing'; -const InputGridSizingExampleSource = require('!!raw!../examples/InputGridSizing'); +const InputGridSizingExampleSource = require('!!raw-loader!../examples/InputGridSizing'); import LabelHiddenExample from '../examples/LabelHidden'; -const LabelHiddenExampleSource = require('!!raw!../examples/LabelHidden'); +const LabelHiddenExampleSource = require('!!raw-loader!../examples/LabelHidden'); import CustomControlsExample from '../examples/CustomControls'; -const CustomControlsExampleSource = require('!!raw!../examples/CustomControls'); +const CustomControlsExampleSource = require('!!raw-loader!../examples/CustomControls'); export default class FormPage extends React.Component { render() { @@ -68,15 +71,16 @@ export default class FormPage extends React.Component { addon: PropTypes.bool, className: PropTypes.string, cssModule: PropTypes.object, -};`} -{`CustomInput.propTypes = { +}; + +CustomInput.propTypes = { className: PropTypes.string, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, type: PropTypes.string.isRequired, // radio, checkbox, select, range. label: PropTypes.string, // used for checkbox and radios inline: PropTypes.bool, - valid: PropTypes.bool, // applied the is-invalid class when true, does nothing when false - invalid: PropTypes.bool, // applied the is-valid class when true, does nothing when false + valid: PropTypes.bool, // applied the is-valid class when true, does nothing when false + invalid: PropTypes.bool, // applied the is-invalid class when true, does nothing when false bsSize: PropTypes.string, cssModule: PropTypes.object, children: PropTypes.oneOfType([PropTypes.node, PropTypes.array, PropTypes.func]), // for type="select" @@ -86,6 +90,51 @@ export default class FormPage extends React.Component { PropTypes.string, PropTypes.func, ]) +}; + +Form.propTypes = { + children: PropTypes.node, + inline: PropTypes.bool, + // Pass in a Component to override default element + tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), // default: 'form' + innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.string]), + className: PropTypes.string, + cssModule: PropTypes.object, +}; + +FormFeedback.propTypes = { + children: PropTypes.node, + // Pass in a Component to override default element + tag: PropTypes.string, // default: 'div' + className: PropTypes.string, + cssModule: PropTypes.object, + valid: PropTypes.bool, // default: undefined + tooltip: PropTypes.bool +}; + +FormGroup.propTypes = { + children: PropTypes.node, + // Applied the row class when true, does nothing when false + row: PropTypes.bool, + // Applied the form-check class when true, form-group when false + check: PropTypes.bool, + inline: PropTypes.bool, + // Applied the disabled class when the check and disabled props are true, does nothing when false + disabled: PropTypes.bool, + // Pass in a Component to override default element + tag: PropTypes.string, // default: 'div' + className: PropTypes.string, + cssModule: PropTypes.object, +}; + +FormText.propTypes = { + children: PropTypes.node, + inline: PropTypes.bool, + // Pass in a Component to override default element + tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), // default: 'small' + color: PropTypes.string, // default: 'muted' + className: PropTypes.string, + cssModule: PropTypes.object, };`}

@@ -100,6 +149,16 @@ export default class FormPage extends React.Component { + Form Grid with Form Row +
+ +
+
+          
+            {FormGridFormRowExampleSource}
+          
+        
+ Inline Form
diff --git a/docs/lib/Components/InputGroupPage.js b/docs/lib/Components/InputGroupPage.js index 096d8569d..794e32866 100644 --- a/docs/lib/Components/InputGroupPage.js +++ b/docs/lib/Components/InputGroupPage.js @@ -4,15 +4,15 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import OverviewExample from '../examples/InputGroupOverview'; -const OverviewExampleSource = require('!!raw!../examples/InputGroupOverview'); +const OverviewExampleSource = require('!!raw-loader!../examples/InputGroupOverview'); import AddonExample from '../examples/InputGroupAddon'; -const AddonExampleSource = require('!!raw!../examples/InputGroupAddon'); +const AddonExampleSource = require('!!raw-loader!../examples/InputGroupAddon'); import AddonSizingExample from '../examples/InputGroupSizing'; -const AddonSizingExampleSource = require('!!raw!../examples/InputGroupSizing'); +const AddonSizingExampleSource = require('!!raw-loader!../examples/InputGroupSizing'); import ButtonExample from '../examples/InputGroupButton'; -const ButtonExampleSource = require('!!raw!../examples/InputGroupButton'); +const ButtonExampleSource = require('!!raw-loader!../examples/InputGroupButton'); import ButtonShorthandExample from '../examples/InputGroupButtonShorthand'; -const ButtonShorthandExampleSource = require('!!raw!../examples/InputGroupButtonShorthand'); +const ButtonShorthandExampleSource = require('!!raw-loader!../examples/InputGroupButtonShorthand'); export default class InputGroupPage extends React.Component { constructor(props) { diff --git a/docs/lib/Components/JumbotronPage.js b/docs/lib/Components/JumbotronPage.js index 1b1d341ac..662d179a0 100644 --- a/docs/lib/Components/JumbotronPage.js +++ b/docs/lib/Components/JumbotronPage.js @@ -6,8 +6,8 @@ import SectionTitle from '../UI/SectionTitle'; import JumbotronExample from '../examples/Jumbotron'; import JumbotronFluidExample from "../examples/JumbotronFluid"; -const JumbotronExampleSource = require('!!raw!../examples/Jumbotron'); -const JumbotronFluidExampleSource = require('!!raw!../examples/JumbotronFluid'); +const JumbotronExampleSource = require('!!raw-loader!../examples/Jumbotron'); +const JumbotronFluidExampleSource = require('!!raw-loader!../examples/JumbotronFluid'); export default class JumbotronPage extends React.Component { render() { diff --git a/docs/lib/Components/LayoutPage.js b/docs/lib/Components/LayoutPage.js index c5fa62740..44294f7ee 100644 --- a/docs/lib/Components/LayoutPage.js +++ b/docs/lib/Components/LayoutPage.js @@ -4,7 +4,7 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import LayoutExample from '../examples/Layout'; -const LayoutExampleSource = require('!!raw!../examples/Layout'); +const LayoutExampleSource = require('!!raw-loader!../examples/Layout'); export default class LayoutsPage extends React.Component { render() { @@ -32,7 +32,9 @@ export default class LayoutsPage extends React.Component {
           
 {`Row.propTypes = {
-  noGutters: PropTypes.bool
+  noGutters: PropTypes.bool,
+  // see https://reactstrap.github.io/components/form Form Grid with Form Row
+  form: PropTypes.bool
 }`}
           
         
diff --git a/docs/lib/Components/ListGroupPage.js b/docs/lib/Components/ListGroupPage.js index d70ffc169..f919183d2 100644 --- a/docs/lib/Components/ListGroupPage.js +++ b/docs/lib/Components/ListGroupPage.js @@ -11,13 +11,13 @@ import ListGroupContextualClassesExample from '../examples/ListGroupContextualCl import ListGroupCustomContentExample from '../examples/ListGroupCustomContent'; import ListGroupFlushExample from '../examples/ListGroupFlush'; -const ListGroupBadgeExampleSource = require('!!raw!../examples/ListGroupBadge'); -const ListGroupExampleSource = require('!!raw!../examples/ListGroup'); -const ListGroupDisabledItemsExampleSource = require('!!raw!../examples/ListGroupDisabledItems'); -const ListGroupAnchorsAndButtonsExampleSource = require('!!raw!../examples/ListGroupAnchorsAndButtons'); -const ListGroupContextualClassesExampleSource = require('!!raw!../examples/ListGroupContextualClasses'); -const ListGroupCustomContentExampleSource = require('!!raw!../examples/ListGroupCustomContent'); -const ListGroupFlushExampleSource = require('!!raw!../examples/ListGroupFlush') +const ListGroupBadgeExampleSource = require('!!raw-loader!../examples/ListGroupBadge'); +const ListGroupExampleSource = require('!!raw-loader!../examples/ListGroup'); +const ListGroupDisabledItemsExampleSource = require('!!raw-loader!../examples/ListGroupDisabledItems'); +const ListGroupAnchorsAndButtonsExampleSource = require('!!raw-loader!../examples/ListGroupAnchorsAndButtons'); +const ListGroupContextualClassesExampleSource = require('!!raw-loader!../examples/ListGroupContextualClasses'); +const ListGroupCustomContentExampleSource = require('!!raw-loader!../examples/ListGroupCustomContent'); +const ListGroupFlushExampleSource = require('!!raw-loader!../examples/ListGroupFlush') export default class ListGroupPage extends React.Component { render() { diff --git a/docs/lib/Components/MediaPage.js b/docs/lib/Components/MediaPage.js index 5762545ee..9f583ab42 100644 --- a/docs/lib/Components/MediaPage.js +++ b/docs/lib/Components/MediaPage.js @@ -5,16 +5,16 @@ import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import MediaExample from '../examples/Media'; -const MediaExampleSource = require('!!raw!../examples/Media'); +const MediaExampleSource = require('!!raw-loader!../examples/Media'); import MediaNestedExample from '../examples/MediaNested'; -const MediaNestedExampleSource = require('!!raw!../examples/MediaNested'); +const MediaNestedExampleSource = require('!!raw-loader!../examples/MediaNested'); import MediaAlignmentExample from '../examples/MediaAlignment'; -const MediaAlignmentExampleSource = require('!!raw!../examples/MediaAlignment'); +const MediaAlignmentExampleSource = require('!!raw-loader!../examples/MediaAlignment'); import MediaListExample from '../examples/MediaList'; -const MediaListExampleSource = require('!!raw!../examples/MediaList'); +const MediaListExampleSource = require('!!raw-loader!../examples/MediaList'); export default class MediaPage extends React.Component { render() { diff --git a/docs/lib/Components/ModalsPage.js b/docs/lib/Components/ModalsPage.js index 931033bd0..4d56b1a7b 100644 --- a/docs/lib/Components/ModalsPage.js +++ b/docs/lib/Components/ModalsPage.js @@ -1,53 +1,52 @@ /* eslint react/no-multi-comp: 0, react/prop-types: 0 */ import React from 'react'; import { PrismCode } from 'react-prism'; + import PageTitle from '../UI/PageTitle'; -import SectionTitle from '../UI/SectionTitle'; import ModalExample from '../examples/Modal'; -const ModalExampleSource = require('!!raw!../examples/Modal'); - import ModalBackdropExample from '../examples/ModalBackdrop'; -const ModalBackdropExampleSource = require('!!raw!../examples/ModalBackdrop'); - import ModalNestedExample from '../examples/ModalNested'; -const ModalNestedExampleSource = require('!!raw!../examples/ModalNested'); - import ModalCustomTimeoutExample from '../examples/ModalCustomTimeout'; -const ModalCustomTimeoutExampleSource = require('!!raw!../examples/ModalCustomTimeout'); - import ModalFadelessExample from '../examples/ModalFadeless'; -const ModalFadelessExampleSource = require('!!raw!../examples/ModalFadeless'); - import ModalExternalExample from '../examples/ModalExternal'; -const ModalExternalExampleSource = require('!!raw!../examples/ModalExternal'); +import ModalCustomCloseIconExample from '../examples/ModalCustomCloseIcon'; +import ModalCustomCloseButtonExample from '../examples/ModalCustomCloseButton'; -export default class ModalsPage extends React.Component { - render() { - return ( -
- -
-
-
- -
-
- -
+const ModalBackdropExampleSource = require('!!raw-loader!../examples/ModalBackdrop'); +const ModalCustomCloseButtonExampleSource = require('!!raw-loader!../examples/ModalCustomCloseButton'); +const ModalCustomCloseIconExampleSource = require('!!raw-loader!../examples/ModalCustomCloseIcon'); +const ModalCustomTimeoutExampleSource = require('!!raw-loader!../examples/ModalCustomTimeout'); +const ModalExampleSource = require('!!raw-loader!../examples/Modal'); +const ModalExternalExampleSource = require('!!raw-loader!../examples/ModalExternal'); +const ModalFadelessExampleSource = require('!!raw-loader!../examples/ModalFadeless'); +const ModalNestedExampleSource = require('!!raw-loader!../examples/ModalNested'); + +const ModalsPage = () => { + return ( +
+ +
+
+
+ +
+
+
-
-          
-            {ModalExampleSource}
-          
-        
-

Properties

-
-          
-{`Modal.propTypes = {
+      
+
+        
+          {ModalExampleSource}
+        
+      
+

Properties

+
+        
+          {`Modal.propTypes = {
   // boolean to control the state of the popover
   isOpen:  PropTypes.bool,
   autoFocus: PropTypes.bool,
@@ -66,7 +65,7 @@ export default class ModalsPage extends React.Component {
     PropTypes.bool,
     PropTypes.oneOf(['static'])
   ]),
-  // allows for a node/componet to exist next to the modal (outside of it). Useful for external close buttons
+  // allows for a node/component to exist next to the modal (outside of it). Useful for external close buttons
   // external: PropTypes.node,
   // called on componentDidMount
   onEnter: PropTypes.func,
@@ -99,79 +98,107 @@ export default class ModalsPage extends React.Component {
   modalTransition: PropTypes.shape(Fade.propTypes),
   innerRef: PropTypes.object,
 }`}
-          
-        
+ + -

Backdrop

-
-
-
- -
+

Backdrop

+
+
+
+
-
-          
-            {ModalBackdropExampleSource}
-          
-        
+
+
+        
+          {ModalBackdropExampleSource}
+        
+      
-

Nested Modals

-
-
-
- -
+

Nested Modals

+
+
+
+
-
-          
-            {ModalNestedExampleSource}
-          
-        
+
+
+        
+          {ModalNestedExampleSource}
+        
+      
-

Modals with Custom Transition Timeouts

-
-
-
- -
+

Modals with Custom Transition Timeouts

+
+
+
+
-
-          
-            {ModalCustomTimeoutExampleSource}
-          
-        
+
+
+        
+          {ModalCustomTimeoutExampleSource}
+        
+      
+ +

Modals without Fade Effect

+
+
+
+ +
+
+
+
+        
+          {ModalFadelessExampleSource}
+        
+      
-

Modals without Fade Effect

-
-
-
- -
+

Modals with external button

+
+
+
+
-
-          
-            {ModalFadelessExampleSource}
-          
-        
+
+
+        
+          {ModalExternalExampleSource}
+        
+      
-

Modals with external button

-
-
-
- -
+

Modals with custom close icon

+
+
+
+
-
-          
-            {ModalExternalExampleSource}
-          
-        
- ); - } -} +
+        
+          {ModalCustomCloseIconExampleSource}
+        
+      
+

Modals with custom close button

+
+
+
+ +
+
+
+
+        
+          {ModalCustomCloseButtonExampleSource}
+        
+      
+
+ ); +}; + +export default ModalsPage; diff --git a/docs/lib/Components/NavbarPage.js b/docs/lib/Components/NavbarPage.js index e86f73e76..5cb7f236d 100644 --- a/docs/lib/Components/NavbarPage.js +++ b/docs/lib/Components/NavbarPage.js @@ -5,9 +5,9 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import NavbarExample from '../examples/Navbar'; -const NavbarExampleSource = require('!!raw!../examples/Navbar'); +const NavbarExampleSource = require('!!raw-loader!../examples/Navbar'); import NavbarTogglerExample from '../examples/NavbarToggler'; -const NavbarTogglerExampleSource = require('!!raw!../examples/NavbarToggler'); +const NavbarTogglerExampleSource = require('!!raw-loader!../examples/NavbarToggler'); export default class NavsPage extends React.Component { render() { diff --git a/docs/lib/Components/NavsPage.js b/docs/lib/Components/NavsPage.js index 7427ca68e..b5e07a453 100644 --- a/docs/lib/Components/NavsPage.js +++ b/docs/lib/Components/NavsPage.js @@ -4,13 +4,13 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import NavsExample from '../examples/Navs'; -const NavsExampleSource = require('!!raw!../examples/Navs'); +const NavsExampleSource = require('!!raw-loader!../examples/Navs'); import NavVerticalExample from '../examples/NavVertical'; -const NavVerticalExampleSource = require('!!raw!../examples/NavVertical'); +const NavVerticalExampleSource = require('!!raw-loader!../examples/NavVertical'); import NavTabsExample from '../examples/NavTabs'; -const NavTabsExampleSource = require('!!raw!../examples/NavTabs'); +const NavTabsExampleSource = require('!!raw-loader!../examples/NavTabs'); import NavPillsExample from '../examples/NavPills'; -const NavPillsExampleSource = require('!!raw!../examples/NavPills'); +const NavPillsExampleSource = require('!!raw-loader!../examples/NavPills'); export default class NavssPage extends React.Component { render() { diff --git a/docs/lib/Components/PaginationPage.js b/docs/lib/Components/PaginationPage.js index cf843951b..eae50604c 100644 --- a/docs/lib/Components/PaginationPage.js +++ b/docs/lib/Components/PaginationPage.js @@ -5,16 +5,16 @@ import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import PaginationExample from '../examples/Pagination'; -const PaginationExampleSource = require('!!raw!../examples/Pagination'); +const PaginationExampleSource = require('!!raw-loader!../examples/Pagination'); import PaginationStateExample from '../examples/PaginationState'; -const PaginationStateExampleSource = require('!!raw!../examples/PaginationState'); +const PaginationStateExampleSource = require('!!raw-loader!../examples/PaginationState'); import PaginationSizingLargeExample from '../examples/PaginationSizingLarge'; -const PaginationSizingLargeExampleSource = require('!!raw!../examples/PaginationSizingLarge'); +const PaginationSizingLargeExampleSource = require('!!raw-loader!../examples/PaginationSizingLarge'); import PaginationSizingSmallExample from '../examples/PaginationSizingSmall'; -const PaginationSizingSmallExampleSource = require('!!raw!../examples/PaginationSizingSmall'); +const PaginationSizingSmallExampleSource = require('!!raw-loader!../examples/PaginationSizingSmall'); export default class PaginationPage extends React.Component { render() { diff --git a/docs/lib/Components/PopoversPage.js b/docs/lib/Components/PopoversPage.js index 21b6360fd..f9c017921 100644 --- a/docs/lib/Components/PopoversPage.js +++ b/docs/lib/Components/PopoversPage.js @@ -4,9 +4,13 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import PopoverExample from '../examples/Popover'; -const PopoverExampleSource = require('!!raw!../examples/Popover'); +const PopoverExampleSource = require('!!raw-loader!../examples/Popover'); import PopoverExampleMulti from '../examples/PopoverMulti'; -const PopoverExampleMultiSource = require('!!raw!../examples/PopoverMulti'); +const PopoverExampleMultiSource = require('!!raw-loader!../examples/PopoverMulti'); +import PopoverFocusExample from '../examples/PopoverFocus'; +const PopoverFocusExampleSource = require('!!raw-loader!../examples/PopoverFocus'); +import UncontrolledPopoverExample from '../examples/PopoverUncontrolled'; +const UncontrolledPopoverExampleSource = require('!!raw-loader!../examples/PopoverUncontrolled'); export default class PopoversPage extends React.Component { render() { @@ -26,10 +30,14 @@ export default class PopoversPage extends React.Component {
           
 {`Popover.propTypes = {
+  // space separated list of triggers (e.g. "click hover focus")
+  trigger: PropTypes.string,
   // boolean to control the state of the popover
   isOpen:  PropTypes.bool,
   // callback for toggling isOpen in the controlling component
   toggle:  PropTypes.func,
+  // boundaries for popper, can be scrollParent, window, viewport, or any DOM element
+  boundariesElement: PropTypes.oneOfType([PropTypes.string, DOMElement]),
   target:  PropTypes.oneOfType([
     PropTypes.string,
     PropTypes.func,
@@ -73,7 +81,17 @@ export default class PopoversPage extends React.Component {
 }`}
           
         
- Popovers List + Popovers Trigger +

Trigger each popover to see information about the trigger

+
+ +
+
+          
+            {PopoverFocusExampleSource}
+          
+        
+ Popovers Placements
@@ -82,6 +100,15 @@ export default class PopoversPage extends React.Component { {PopoverExampleMultiSource} + UncontrolledPopovers +
+ +
+
+          
+            {UncontrolledPopoverExampleSource}
+          
+        
); } diff --git a/docs/lib/Components/ProgressPage.js b/docs/lib/Components/ProgressPage.js index 0db48dea1..b60d1a8db 100644 --- a/docs/lib/Components/ProgressPage.js +++ b/docs/lib/Components/ProgressPage.js @@ -5,19 +5,19 @@ import PageTitle from '../UI/PageTitle'; import { Card, CardText } from 'reactstrap'; import SectionTitle from '../UI/SectionTitle'; import ProgressExample from '../examples/Progress'; -const ProgressExampleSource = require('!!raw!../examples/Progress'); +const ProgressExampleSource = require('!!raw-loader!../examples/Progress'); import ProgressColorExample from '../examples/ProgressColor'; -const ProgressColorExampleSource = require('!!raw!../examples/ProgressColor'); +const ProgressColorExampleSource = require('!!raw-loader!../examples/ProgressColor'); import ProgressLabelsExample from '../examples/ProgressLabels'; -const ProgressLabelsExampleSource = require('!!raw!../examples/ProgressLabels'); +const ProgressLabelsExampleSource = require('!!raw-loader!../examples/ProgressLabels'); import ProgressAnimatedExample from '../examples/ProgressAnimated'; -const ProgressAnimatedExampleSource = require('!!raw!../examples/ProgressAnimated'); +const ProgressAnimatedExampleSource = require('!!raw-loader!../examples/ProgressAnimated'); import ProgressStripedExample from '../examples/ProgressStriped'; -const ProgressStripedExampleSource = require('!!raw!../examples/ProgressStriped'); +const ProgressStripedExampleSource = require('!!raw-loader!../examples/ProgressStriped'); import ProgressMultiExample from '../examples/ProgressMulti'; -const ProgressMultiExampleSource = require('!!raw!../examples/ProgressMulti'); +const ProgressMultiExampleSource = require('!!raw-loader!../examples/ProgressMulti'); import ProgressMaxExample from '../examples/ProgressMax'; -const ProgressMaxExampleSource = require('!!raw!../examples/ProgressMax'); +const ProgressMaxExampleSource = require('!!raw-loader!../examples/ProgressMax'); export default class ProgressPage extends React.Component { render() { diff --git a/docs/lib/Components/SpinnersPage.js b/docs/lib/Components/SpinnersPage.js new file mode 100644 index 000000000..2eed19ec2 --- /dev/null +++ b/docs/lib/Components/SpinnersPage.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { PrismCode } from 'react-prism'; +import { Spinner } from 'reactstrap'; +import PageTitle from '../UI/PageTitle'; +import SectionTitle from '../UI/SectionTitle'; + +import SpinnerExample from '../examples/Spinner'; + +import SpinnerGrowerExample from '../examples/SpinnerGrower'; +const SpinnerExampleSource = require('!!raw-loader!../examples/Spinner'); +const SpinnerGrowerExampleSource = require('!!raw-loader!../examples/SpinnerGrower'); +export default class SpinnersPage extends React.Component { + render() { + return ( +
+ +
+ +
+
+          {SpinnerExampleSource}
+        
+

Properties

+
+          
+            {`Spinner.propTypes = {
+  type: PropTypes.string, // default: 'border'
+  size: PropTypes.string,
+  color: PropTypes.string,
+  className: PropTypes.string,
+  cssModule: PropTypes.object,
+  children: PropTypes.string, // default: 'Loading...'
+};
+`}
+          
+        
+ Growing Spinner +
+ +
+
+          
+            {SpinnerGrowerExampleSource}
+          
+        
+ Sizes +
+ {' '} + +
+
+          
+            {`{' '}
+`}
+          
+        
+
+ {' '} + +
+
+          
+            {`{' '}
+`}
+          
+        
+
+ ); + } +} diff --git a/docs/lib/Components/TablesPage.js b/docs/lib/Components/TablesPage.js index e63d552d1..04f172439 100644 --- a/docs/lib/Components/TablesPage.js +++ b/docs/lib/Components/TablesPage.js @@ -13,14 +13,14 @@ import TableResponsiveExample from '../examples/TableResponsive'; import TableSizingExample from '../examples/TableSizing'; import TableStripedExample from '../examples/TableStriped'; -const TableExampleSource = require('!!raw!../examples/Table'); -const TableBorderedExampleSource = require('!!raw!../examples/TableBordered'); -const TableBorderlessExampleSource = require('!!raw!../examples/TableBorderless'); -const TableHoverExampleSource = require('!!raw!../examples/TableHover'); -const TableDarkExampleSource = require('!!raw!../examples/TableDark'); -const TableResponsiveExampleSource = require('!!raw!../examples/TableResponsive'); -const TableSizingExampleSource = require('!!raw!../examples/TableSizing'); -const TableStripedExampleSource = require('!!raw!../examples/TableStriped'); +const TableExampleSource = require('!!raw-loader!../examples/Table'); +const TableBorderedExampleSource = require('!!raw-loader!../examples/TableBordered'); +const TableBorderlessExampleSource = require('!!raw-loader!../examples/TableBorderless'); +const TableHoverExampleSource = require('!!raw-loader!../examples/TableHover'); +const TableDarkExampleSource = require('!!raw-loader!../examples/TableDark'); +const TableResponsiveExampleSource = require('!!raw-loader!../examples/TableResponsive'); +const TableSizingExampleSource = require('!!raw-loader!../examples/TableSizing'); +const TableStripedExampleSource = require('!!raw-loader!../examples/TableStriped'); export default class TablesPage extends React.Component { render() { @@ -48,7 +48,13 @@ borderless: PropTypes.bool, striped: PropTypes.bool, dark: PropTypes.bool, hover: PropTypes.bool, -responsive: PropTypes.bool +responsive: PropTypes.bool, +// Custom ref handler that will be assigned to the "ref" of the inner element +innerRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, + PropTypes.object +]) };`} diff --git a/docs/lib/Components/TabsPage.js b/docs/lib/Components/TabsPage.js index eeaab9462..fc62e660b 100644 --- a/docs/lib/Components/TabsPage.js +++ b/docs/lib/Components/TabsPage.js @@ -4,7 +4,7 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import TabsExample from '../examples/Tabs'; -const TabsExampleSource = require('!!raw!../examples/Tabs'); +const TabsExampleSource = require('!!raw-loader!../examples/Tabs'); export default function TabsPage() { return ( diff --git a/docs/lib/Components/TooltipsPage.js b/docs/lib/Components/TooltipsPage.js index 47d86a4d8..f836b4626 100644 --- a/docs/lib/Components/TooltipsPage.js +++ b/docs/lib/Components/TooltipsPage.js @@ -4,13 +4,13 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import SectionTitle from '../UI/SectionTitle'; import TooltipExample from '../examples/Tooltip'; -const TooltipExampleSource = require('!!raw!../examples/Tooltip'); +const TooltipExampleSource = require('!!raw-loader!../examples/Tooltip'); import TooltipAutoHideExample from '../examples/TooltipAutoHide'; -const TooltipExampleAutoHideSource = require('!!raw!../examples/TooltipAutoHide'); +const TooltipExampleAutoHideSource = require('!!raw-loader!../examples/TooltipAutoHide'); import TooltipExampleMulti from '../examples/TooltipMulti'; -const TooltipExampleMultiSource = require('!!raw!../examples/TooltipMulti'); +const TooltipExampleMultiSource = require('!!raw-loader!../examples/TooltipMulti'); import TooltipExampleUncontrolled from '../examples/TooltipUncontrolled'; -const TooltipExampleUncontrolledSource = require('!!raw!../examples/TooltipUncontrolled'); +const TooltipExampleUncontrolledSource = require('!!raw-loader!../examples/TooltipUncontrolled'); export default class TooltipsPage extends React.Component { render() { @@ -30,6 +30,10 @@ export default class TooltipsPage extends React.Component {
           
 {`Tooltip.propTypes = {
+  // space separated list of triggers (e.g. "click hover focus")
+  trigger: PropTypes.string,
+  // boundaries for popper, can be scrollParent, window, viewport, or any DOM element
+  boundariesElement: PropTypes.oneOfType([PropTypes.string, DOMElement]),
   // boolean to control the state of the tooltip
   isOpen: PropTypes.bool,
   hideArrow: PropTypes.bool,
@@ -81,10 +85,10 @@ export default class TooltipsPage extends React.Component {
   ]),
   // Custom ref handler that will be assigned to the "ref" of the 
wrapping the tooltip elements innerRef: PropTypes.oneOfType([ - PropTypes.func, + PropTypes.func, PropTypes.string, PropTypes.object - ]), + ]) }`}
diff --git a/docs/lib/Components/index.js b/docs/lib/Components/index.js index 25ee4afe6..3d9055218 100644 --- a/docs/lib/Components/index.js +++ b/docs/lib/Components/index.js @@ -30,14 +30,14 @@ const items = [ name: 'Card', to: '/components/card/' }, - { - name: 'Collapse', - to: '/components/collapse/', - }, { name: 'Carousel', to: '/components/carousel/' }, + { + name: 'Collapse', + to: '/components/collapse/' + }, { name: 'Dropdowns', to: '/components/dropdowns/' @@ -82,6 +82,10 @@ const items = [ name: 'Navs', to: '/components/navs/' }, + { + name: 'Spinners', + to: '/components/spinners/' + }, { name: 'Pagination', to: '/components/pagination/' @@ -109,9 +113,7 @@ const items = [ ]; function Components(props) { - return ( - - ); + return ; } export default Components; diff --git a/docs/lib/Home/index.js b/docs/lib/Home/index.js index af6a5a397..306195f08 100644 --- a/docs/lib/Home/index.js +++ b/docs/lib/Home/index.js @@ -4,7 +4,7 @@ import { Button, Container, Row, Col, Jumbotron } from 'reactstrap'; import { Link } from 'react-router'; import Example from '../examples/import-basic'; -const importBasic = require('!!raw!../examples/import-basic'); +const importBasic = require('!!raw-loader!../examples/import-basic'); export default () => { return ( diff --git a/docs/lib/UI/Layout.js b/docs/lib/UI/Layout.js index 6650703f4..3ceb25f5a 100644 --- a/docs/lib/UI/Layout.js +++ b/docs/lib/UI/Layout.js @@ -11,7 +11,7 @@ export default (props) => { title="React Bootstrap 4 components" defaultTitle="React Bootstrap 4 components" meta={[ - { 'name': 'description', 'content': 'reactstrap - easy to use React Bootstrap 4 components compatible with React 0.14.x and 15' }, + { 'name': 'description', 'content': 'reactstrap - easy to use React Bootstrap 4 components compatible with React 16+' }, { 'property': 'og:type', 'content': 'article' } ]} /> diff --git a/docs/lib/Utilities/ClearfixPage.js b/docs/lib/Utilities/ClearfixPage.js index 81664b21a..1547c0649 100644 --- a/docs/lib/Utilities/ClearfixPage.js +++ b/docs/lib/Utilities/ClearfixPage.js @@ -4,7 +4,7 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import ClearfixExample from '../examples/Clearfix'; -const ClearfixExampleSource = require('!!raw!../examples/Clearfix'); +const ClearfixExampleSource = require('!!raw-loader!../examples/Clearfix'); export default function ClearfixPage() { return ( @@ -12,7 +12,7 @@ export default function ClearfixPage() {

Easily clear floats by adding - .clearfix + {' '}.clearfix{' '} to the parent element. Utilizes the micro clearfix as popularized by Nicolas Gallagher. diff --git a/docs/lib/Utilities/ColorsPage.js b/docs/lib/Utilities/ColorsPage.js index f20d955d7..99b357ff0 100644 --- a/docs/lib/Utilities/ColorsPage.js +++ b/docs/lib/Utilities/ColorsPage.js @@ -4,7 +4,7 @@ import { PrismCode } from 'react-prism'; import PageTitle from '../UI/PageTitle'; import ColorExample from '../examples/Color'; -const ColorExampleSource = require('!!raw!../examples/Color'); +const ColorExampleSource = require('!!raw-loader!../examples/Color'); export default function ColorsPage() { return ( diff --git a/docs/lib/app.js b/docs/lib/app.js index 7449345a9..0aa24ea4e 100644 --- a/docs/lib/app.js +++ b/docs/lib/app.js @@ -63,9 +63,9 @@ export default (locals, callback) => { ${head.title.toString()} ${head.meta.toString()} - + - +

${body}
diff --git a/docs/lib/examples/BreadcrumbNoList.js b/docs/lib/examples/BreadcrumbNoList.js index d0851fff1..2c39cfc2b 100644 --- a/docs/lib/examples/BreadcrumbNoList.js +++ b/docs/lib/examples/BreadcrumbNoList.js @@ -4,7 +4,7 @@ import { Breadcrumb, BreadcrumbItem } from 'reactstrap'; const Example = (props) => { return (
- + Home Library Data diff --git a/docs/lib/examples/ButtonCloseIcon.js b/docs/lib/examples/ButtonCloseIcon.js new file mode 100644 index 000000000..3bc65d356 --- /dev/null +++ b/docs/lib/examples/ButtonCloseIcon.js @@ -0,0 +1,38 @@ +import React, { Component } from 'react'; +import { Button, Card, CardBody, CardText, CardGroup, CardTitle } from 'reactstrap'; + +const Example = () => ( +
+ + + + + + + + + + Custom content and aria-label + + + + + + +
+); + +export default Example; diff --git a/docs/lib/examples/ButtonDropdownUncontrolled.js b/docs/lib/examples/ButtonDropdownUncontrolled.js new file mode 100644 index 000000000..1cf52e998 --- /dev/null +++ b/docs/lib/examples/ButtonDropdownUncontrolled.js @@ -0,0 +1,22 @@ +/* eslint react/no-multi-comp: 0, react/prop-types: 0 */ +import React from 'react'; +import { UncontrolledButtonDropdown, DropdownMenu, DropdownItem, DropdownToggle } from 'reactstrap'; + +export default class Example extends React.Component { + render() { + return ( + + + Dropdown + + + Header + Action + Another Action + + Another Action + + + ); + } +} diff --git a/docs/lib/examples/CustomControls.js b/docs/lib/examples/CustomControls.js index 0fd6ac26b..9e3dce4fc 100644 --- a/docs/lib/examples/CustomControls.js +++ b/docs/lib/examples/CustomControls.js @@ -21,6 +21,14 @@ export default class Example extends React.Component {
+ + +
+ + + +
+
diff --git a/docs/lib/examples/Dropdown.js b/docs/lib/examples/Dropdown.js index f5521e878..98e86765a 100644 --- a/docs/lib/examples/Dropdown.js +++ b/docs/lib/examples/Dropdown.js @@ -25,10 +25,12 @@ export default class Example extends React.Component { Header - Action - Another Action + Some Action + Action (disabled) - Another Action + Foo Action + Bar Action + Quo Action ); diff --git a/docs/lib/examples/FormGridFormRow.js b/docs/lib/examples/FormGridFormRow.js new file mode 100644 index 000000000..89be55e02 --- /dev/null +++ b/docs/lib/examples/FormGridFormRow.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { Col, Row, Button, Form, FormGroup, Label, Input, FormText } from 'reactstrap'; + +export default class Example extends React.Component { + render() { + return ( +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/docs/lib/examples/InputType.js b/docs/lib/examples/InputType.js index 6bdbe219f..40df4ab22 100644 --- a/docs/lib/examples/InputType.js +++ b/docs/lib/examples/InputType.js @@ -7,43 +7,88 @@ export default class Example extends React.Component { - Some plain text/ static value + - + - + - + - + - + - + - + - + - + @@ -57,7 +102,12 @@ export default class Example extends React.Component { - + @@ -79,14 +129,13 @@ export default class Example extends React.Component { diff --git a/docs/lib/examples/Layout.js b/docs/lib/examples/Layout.js index 2afc39e36..6d44e22de 100644 --- a/docs/lib/examples/Layout.js +++ b/docs/lib/examples/Layout.js @@ -29,14 +29,14 @@ export default class Example extends React.Component { .col-sm-4 - .col-sm-6 .col-sm-order-2 .col-sm-offset-2 + .col-sm-6 .order-sm-2 .offset-sm-1 - .col-sm-12 .col-md-6 .col-md-offset-3 + .col-sm-12 .col-md-6 .offset-md-3 - .col-sm .col-sm-offset-1 - .col-sm .col-sm-offset-1 + .col-sm-auto .offset-sm-1 + .col-sm-auto .offset-sm-1 ); diff --git a/docs/lib/examples/ModalCustomCloseButton.js b/docs/lib/examples/ModalCustomCloseButton.js new file mode 100644 index 000000000..ef1c6363f --- /dev/null +++ b/docs/lib/examples/ModalCustomCloseButton.js @@ -0,0 +1,47 @@ +/* eslint react/no-multi-comp: 0, react/prop-types: 0 */ + +import React from 'react'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; + +class ModalExample extends React.Component { + constructor(props) { + super(props); + this.state = { + modal: false + }; + + this.toggle = this.toggle.bind(this); + } + + toggle() { + this.setState({ + modal: !this.state.modal + }); + } + + render() { + const closeBtn = ; + + return ( +
+ + + Modal title + + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip + ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est laborum. + + + {' '} + + + +
+ ); + } +} + +export default ModalExample; diff --git a/docs/lib/examples/ModalCustomCloseIcon.js b/docs/lib/examples/ModalCustomCloseIcon.js new file mode 100644 index 000000000..6aa63bd74 --- /dev/null +++ b/docs/lib/examples/ModalCustomCloseIcon.js @@ -0,0 +1,41 @@ +/* eslint react/no-multi-comp: 0, react/prop-types: 0 */ + +import React from 'react'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; + +class ModalExample extends React.Component { + constructor(props) { + super(props); + this.state = { + modal: false + }; + + this.toggle = this.toggle.bind(this); + } + + toggle() { + this.setState({ + modal: !this.state.modal + }); + } + + render() { + return ( +
+ + + Modal title + + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + + + {' '} + + + +
+ ); + } +} + +export default ModalExample; diff --git a/docs/lib/examples/Popover.js b/docs/lib/examples/Popover.js index 86319b398..8785df827 100644 --- a/docs/lib/examples/Popover.js +++ b/docs/lib/examples/Popover.js @@ -21,7 +21,7 @@ export default class Example extends React.Component { render() { return (
- diff --git a/docs/lib/examples/PopoverFocus.js b/docs/lib/examples/PopoverFocus.js new file mode 100644 index 000000000..a03d89350 --- /dev/null +++ b/docs/lib/examples/PopoverFocus.js @@ -0,0 +1,36 @@ +/* eslint react/no-multi-comp: 0, react/prop-types: 0 */ +import React from 'react'; +import { Button, UncontrolledPopover, PopoverHeader, PopoverBody } from 'reactstrap'; + +export default class Example extends React.Component { + render() { + return ( +
+ + {' '} + + {' '} + + + Focus Trigger + Focusing on the trigging element makes this popover appear. Blurring (clicking away) makes it disappear. You cannot select this text as the popover will disappear when you try. + + + Click Trigger + Clicking on the triggering element makes this popover appear. Clicking on it again will make it disappear. You can select this text, but clicking away (somewhere other than the triggering element) will not dismiss this popover. + + + Legacy Trigger + + Legacy is a reactstrap special trigger value (outside of bootstrap's spec/standard). Before reactstrap correctly supported click and focus, it had a hybrid which was very useful and has been brought back as trigger="legacy". One advantage of the legacy trigger is that it allows the popover text to be selected while also closing when clicking outside the triggering element and popover itself. + +
+ ); + } +} diff --git a/docs/lib/examples/PopoverMulti.js b/docs/lib/examples/PopoverMulti.js index 2c75ad01e..aca0f43ff 100644 --- a/docs/lib/examples/PopoverMulti.js +++ b/docs/lib/examples/PopoverMulti.js @@ -22,7 +22,7 @@ class PopoverItem extends React.Component { render() { return ( - diff --git a/docs/lib/examples/PopoverUncontrolled.js b/docs/lib/examples/PopoverUncontrolled.js new file mode 100644 index 000000000..ebf33ec08 --- /dev/null +++ b/docs/lib/examples/PopoverUncontrolled.js @@ -0,0 +1,17 @@ +/* eslint react/no-multi-comp: 0, react/prop-types: 0 */ +import React from 'react'; +import { Button, UncontrolledPopover, PopoverHeader, PopoverBody } from 'reactstrap'; + +export default function () { + return ( +
+ + + Popover Title + Sed posuere consectetur est at lobortis. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. + +
+ ); +} diff --git a/docs/lib/examples/Spinner.js b/docs/lib/examples/Spinner.js new file mode 100644 index 000000000..75b99b3d7 --- /dev/null +++ b/docs/lib/examples/Spinner.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { Spinner } from 'reactstrap'; + +export default class Example extends React.Component { + render() { + return ( +
+ + + + + + + + +
+ ); + } +} diff --git a/docs/lib/examples/SpinnerGrower.js b/docs/lib/examples/SpinnerGrower.js new file mode 100644 index 000000000..95625a534 --- /dev/null +++ b/docs/lib/examples/SpinnerGrower.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { Spinner } from 'reactstrap'; + +export default class Example extends React.Component { + render() { + return ( +
+ + + + + + + + +
+ ); + } +} diff --git a/docs/lib/examples/Tooltip.js b/docs/lib/examples/Tooltip.js index d7e408b0c..d18fc906e 100644 --- a/docs/lib/examples/Tooltip.js +++ b/docs/lib/examples/Tooltip.js @@ -1,6 +1,8 @@ /* eslint react/no-multi-comp: 0, react/prop-types: 0 */ import React from 'react'; import { Tooltip } from 'reactstrap'; +import { mapToCssModules } from '../../../src/utils' +import classNames from 'classnames' export default class Example extends React.Component { constructor(props) { @@ -18,10 +20,13 @@ export default class Example extends React.Component { }); } + + render() { + const classes = 'tooltip-inner' return (
-

Somewhere in here is a tooltip.

+

Somewhere in here is a tooltip.

Hello world! diff --git a/docs/lib/examples/TooltipAutoHide.js b/docs/lib/examples/TooltipAutoHide.js index 8aa73cb17..90e8237c4 100644 --- a/docs/lib/examples/TooltipAutoHide.js +++ b/docs/lib/examples/TooltipAutoHide.js @@ -21,7 +21,7 @@ export default class Example extends React.Component { render() { return (
-

Sometimes you need to allow users to select text within a tooltip.

+

Sometimes you need to allow users to select text within a tooltip.

Try to select this text! diff --git a/docs/lib/examples/TooltipUncontrolled.js b/docs/lib/examples/TooltipUncontrolled.js index a86c2f903..fc4afdb26 100644 --- a/docs/lib/examples/TooltipUncontrolled.js +++ b/docs/lib/examples/TooltipUncontrolled.js @@ -5,7 +5,7 @@ import { UncontrolledTooltip } from 'reactstrap'; export default function Example() { return (
-

Somewhere in here is a tooltip.

+

Somewhere in here is a tooltip.

Hello world! diff --git a/docs/lib/routes.js b/docs/lib/routes.js index 59b8f92c5..f96004943 100644 --- a/docs/lib/routes.js +++ b/docs/lib/routes.js @@ -27,6 +27,7 @@ import AlertsPage from './Components/AlertsPage'; import CollapsePage from './Components/CollapsePage'; import CarouselPage from './Components/CarouselPage'; import ListGroupPage from './Components/ListGroupPage'; +import SpinnersPage from './Components/SpinnersPage'; import ClearfixPage from './Utilities/ClearfixPage'; import ColorsPage from './Utilities/ColorsPage'; import NotFound from './NotFound'; @@ -65,6 +66,7 @@ const routes = ( + diff --git a/package.json b/package.json index b608985cf..00a8c8ca2 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,28 @@ { "name": "reactstrap", - "version": "6.2.0", + "version": "7.1.0", "description": "React Bootstrap 4 components", - "main": "dist/reactstrap.cjs.js", - "jsnext:main": "dist/reactstrap.es.js", - "module": "dist/reactstrap.es.js", + "main": "lib/index.js", + "jsnext:main": "es/index.js", + "module": "es/index.js", "jsdelivr": "dist/reactstrap.min.js", "unpkg": "dist/reactstrap.min.js", "cdn": "dist/reactstrap.min.js", - "esnext": "src", + "esnext": "src/index.js", "sideEffects": false, "scripts": { "report-coverage": "coveralls < ./coverage/lcov.info", - "test": "cross-env BABEL_ENV=test react-scripts test --env=jsdom", + "test": "cross-env SKIP_PREFLIGHT_CHECK=true react-app-rewired test --env=jsdom", "cover": "npm test -- --coverage", - "start": "cross-env BABEL_ENV=webpack webpack-dev-server --config ./webpack.dev.config.js --watch", - "build-docs": "cross-env BABEL_ENV=webpack WEBPACK_BUILD=production webpack --config ./webpack.dev.config.js --progress --colors", - "build": "rollup -c", - "prebuild": "cross-env BABEL_ENV=lib-dir babel src --out-dir lib --ignore src/__tests__/", + "start": "webpack-dev-server --config ./webpack.docs.config.js --watch", + "build:docs": "cross-env WEBPACK_BUILD=production webpack --config ./webpack.docs.config.js --progress --colors", + "build": "npm run build:lib && npm run build:esm && npm run build:umd", + "build:umd": "rollup -c", + "build:lib": "babel src --out-dir lib --ignore src/__tests__/", + "build:esm": "cross-env BABEL_ENV=esm-dir babel src --out-dir es --ignore src/__tests__/", "postbuild": "node ./scripts/postbuild.js", - "create-release": "npm test && sh ./scripts/release", - "publish-release": "npm test && sh ./scripts/publish", + "create-release": "npm run cover && sh ./scripts/release", + "publish-release": "npm run cover && sh ./scripts/publish", "lint": "eslint src" }, "repository": { @@ -33,7 +35,8 @@ "CHANGELOG.md", "lib", "dist", - "src" + "src", + "es" ], "keywords": [ "reactstrap", @@ -95,6 +98,7 @@ }, "homepage": "https://github.com/reactstrap/reactstrap#readme", "dependencies": { + "@babel/runtime": "^7.2.0", "classnames": "^2.2.3", "lodash.isfunction": "^3.0.9", "lodash.isobject": "^3.0.2", @@ -109,50 +113,58 @@ "react-dom": "^16.0.0" }, "devDependencies": { - "babel-cli": "^6.14.0", - "babel-loader": "^6.2.2", - "babel-plugin-transform-object-rest-spread": "^6.23.0", - "babel-preset-es2015-rollup": "^3.0.0", - "babel-preset-react": "^6.11.1", - "babel-preset-react-app": "^0.2.1", - "bootstrap": "^4.1.1", - "clean-webpack-plugin": "^0.1.19", + "@babel/cli": "^7.2.3", + "@babel/core": "^7.2.2", + "@babel/plugin-proposal-export-default-from": "^7.2.0", + "@babel/plugin-proposal-export-namespace-from": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.2.0", + "@babel/plugin-transform-runtime": "^7.2.0", + "@babel/preset-env": "^7.2.3", + "@babel/preset-react": "^7.0.0", + "babel-eslint": "^9.0.0", + "babel-loader": "^8.0.4", + "bootstrap": "^4.1.3", + "clean-webpack-plugin": "^1.0.0", "conventional-changelog-cli": "^1.3.22", "conventional-recommended-bump": "^0.3.0", - "copy-webpack-plugin": "^3.0.1", + "copy-webpack-plugin": "^4.6.0", "coveralls": "^2.11.12", "cross-env": "^2.0.0", - "css-loader": "^0.25.0", + "css-loader": "^2.1.0", + "customize-cra": "^0.2.8", "ejs": "^2.5.9", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", - "eslint": "^4.19.1", - "eslint-config-airbnb": "^15.1.0", - "eslint-plugin-import": "^2.11.0", - "eslint-plugin-jsx-a11y": "^5.1.1", - "eslint-plugin-react": "^7.7.0", - "eslint-plugin-standard": "^2.0.0", - "extract-text-webpack-plugin": "^1.0.1", + "eslint": "^5.0.0", + "eslint-config-react-app": "^3.0.6", + "eslint-plugin-flowtype": "^3.2.0", + "eslint-plugin-import": "^2.0.0", + "eslint-plugin-jsx-a11y": "^6.0.0", + "eslint-plugin-react": "^7.0.0", + "extract-text-webpack-plugin": "^3.0.2", "history": "^3.0.0", "holderjs": "^2.9.3", - "json-loader": "^0.5.4", - "raw-loader": "^0.5.1", + "json-loader": "^0.5.7", + "mini-css-extract-plugin": "^0.5.0", + "raw-loader": "^1.0.0", "react": "^16.3.2", + "react-app-rewired": "^1.6.2", "react-dom": "^16.3.2", "react-helmet": "^5.0.3", "react-prism": "^4.3.2", "react-router": "^3.2.1", - "react-scripts": "^1.1.4", + "react-scripts": "2.1.1", "react-test-renderer": "^16.3.2", - "rollup": "^0.43.0", - "rollup-plugin-babel": "^2.7.1", - "rollup-plugin-babel-minify": "^3.1.2", - "rollup-plugin-commonjs": "^8.4.1", - "rollup-plugin-node-resolve": "^3.3.0", - "rollup-plugin-replace": "^1.1.1", - "static-site-generator-webpack-plugin": "^2.0.1", - "style-loader": "^0.13.1", - "webpack": "^1.12.13", - "webpack-dev-server": "^1.14.1" + "rollup": "^1.0.0", + "rollup-plugin-babel": "^4.2.0", + "rollup-plugin-babel-minify": "^6.2.0", + "rollup-plugin-commonjs": "^9.2.0", + "rollup-plugin-node-resolve": "^4.0.0", + "rollup-plugin-replace": "^2.1.0", + "static-site-generator-webpack-plugin": "^3.4.2", + "style-loader": "^0.23.1", + "webpack": "^4.28.3", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.14" } } diff --git a/rollup.config.js b/rollup.config.js index 425662abf..2e6840d87 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -8,30 +8,47 @@ const packageJson = require('./package.json'); const peerDependencies = Object.keys(packageJson.peerDependencies); const dependencies = Object.keys(packageJson.dependencies); +dependencies.push('react-transition-group/Transition'); + +function globals() { + return { + react: 'React', + 'react-dom': 'ReactDOM', + }; +} function baseConfig() { return { - moduleName: 'Reactstrap', - entry: 'src/index.js', + input: 'src/index.js', plugins: [ nodeResolve(), commonjs({ include: 'node_modules/**' }), babel({ - plugins: ['external-helpers'], + babelrc: false, + presets: [ + [ + '@babel/env', + { + loose: true, + shippedProposals: true, + modules: false, + targets: { + ie: 9 + } + } + ], + '@babel/react' + ], + plugins: ['@babel/plugin-proposal-export-default-from', '@babel/plugin-proposal-export-namespace-from'] }), - ], - sourceMap: true, + ] }; } function baseUmdConfig(minified) { const config = Object.assign(baseConfig(), { - globals: { - react: 'React', - 'react-dom': 'ReactDOM', - }, external: peerDependencies, }); config.plugins.push(replace({ @@ -55,9 +72,9 @@ function baseUmdConfig(minified) { const libConfig = baseConfig(); // Do not include any of the dependencies libConfig.external = peerDependencies.concat(dependencies); -libConfig.targets = [ - { dest: 'dist/reactstrap.cjs.js', format: 'cjs' }, - { dest: 'dist/reactstrap.es.js', format: 'es' }, +libConfig.output = [ + { sourcemap: true, name: 'Reactstrap', file: 'dist/reactstrap.cjs.js', format: 'cjs' }, + { sourcemap: true, name: 'Reactstrap', file: 'dist/reactstrap.es.js', format: 'es' }, ]; /* @@ -69,7 +86,7 @@ libConfig.targets = [ marked as peer dependencies. ** See below Defining this config will also check that all peer dependencies are set up - correctly in the globals entry. + correctly in the globals input. Reactstrap has two versions: @@ -86,12 +103,12 @@ libConfig.targets = [ */ const umdFullConfig = baseUmdConfig(false); -umdFullConfig.targets = [ - { dest: 'dist/reactstrap.full.js', format: 'umd' }, +umdFullConfig.output = [ + { globals: globals(), sourcemap: true, name: 'Reactstrap', file: 'dist/reactstrap.full.js', format: 'umd' }, ]; // Validate globals in main UMD config -const missingGlobals = peerDependencies.filter(dep => !(dep in umdFullConfig.globals)); +const missingGlobals = peerDependencies.filter(dep => !(dep in globals())); if (missingGlobals.length) { console.error('All peer dependencies need to be mentioned in globals, please update rollup.config.js.'); console.error('Missing: ' + missingGlobals.join(', ')); @@ -100,31 +117,29 @@ if (missingGlobals.length) { } const umdFullConfigMin = baseUmdConfig(true); -umdFullConfigMin.targets = [ - { dest: 'dist/reactstrap.full.min.js', format: 'umd' }, +umdFullConfigMin.output = [ + { globals: globals(), sourcemap: true, name: 'Reactstrap', file: 'dist/reactstrap.full.min.js', format: 'umd' }, ]; const external = umdFullConfig.external.slice(); external.push('react-transition-group/Transition'); external.push('react-popper'); -const globals = Object.assign({}, umdFullConfig.globals, { +const allGlobals = Object.assign({}, globals(), { 'react-popper': 'ReactPopper', 'react-transition-group/Transition': 'ReactTransitionGroup.Transition', }); const umdConfig = baseUmdConfig(false); umdConfig.external = external; -umdConfig.globals = globals; -umdConfig.targets = [ - { dest: 'dist/reactstrap.js', format: 'umd' }, +umdConfig.output = [ + { globals: allGlobals, sourcemap: true, name: 'Reactstrap', file: 'dist/reactstrap.js', format: 'umd' }, ]; const umdConfigMin = baseUmdConfig(true); umdConfigMin.external = external; -umdConfigMin.globals = globals; -umdConfigMin.targets = [ - { dest: 'dist/reactstrap.min.js', format: 'umd' }, +umdConfigMin.output = [ + { globals: allGlobals, sourcemap: true, name: 'Reactstrap', file: 'dist/reactstrap.min.js', format: 'umd' }, ]; diff --git a/scripts/docs b/scripts/docs index 9826f01e1..420434c16 100755 --- a/scripts/docs +++ b/scripts/docs @@ -27,7 +27,7 @@ if [ -z "$GITHUB_TOKEN" ]; then exit 1 fi -npm run build-docs +npm run build:docs if [ ! -d $BUILD_DIR ]; then error "Build directory does not exist. Stopping deploy." diff --git a/scripts/postbuild.js b/scripts/postbuild.js index 16cc67ae9..7eebbe466 100644 --- a/scripts/postbuild.js +++ b/scripts/postbuild.js @@ -1,3 +1,4 @@ +/* eslint-disable */ var fs = require('fs'); // Ensure that any optional peer dependency (e.g. ReactTransitionGroup and diff --git a/src/Alert.js b/src/Alert.js index 714fb8278..33d27cd03 100644 --- a/src/Alert.js +++ b/src/Alert.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; import Fade from './Fade'; const propTypes = { @@ -14,7 +14,7 @@ const propTypes = { fade: PropTypes.bool, isOpen: PropTypes.bool, toggle: PropTypes.func, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, transition: PropTypes.shape(Fade.propTypes), innerRef: PropTypes.oneOfType([ PropTypes.object, diff --git a/src/Badge.js b/src/Badge.js index 6c7c52178..82ebcd523 100644 --- a/src/Badge.js +++ b/src/Badge.js @@ -1,12 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { color: PropTypes.string, pill: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, + innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.string]), children: PropTypes.node, className: PropTypes.string, cssModule: PropTypes.object, @@ -23,6 +24,7 @@ const Badge = (props) => { className, cssModule, color, + innerRef, pill, tag: Tag, ...attributes @@ -40,7 +42,7 @@ const Badge = (props) => { } return ( - + ); }; diff --git a/src/Breadcrumb.js b/src/Breadcrumb.js index c24c0ec78..e1b2acf1e 100644 --- a/src/Breadcrumb.js +++ b/src/Breadcrumb.js @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), - listTag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, + listTag: tagPropType, className: PropTypes.string, listClassName: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/BreadcrumbItem.js b/src/BreadcrumbItem.js index e9731bd9c..ada240f23 100644 --- a/src/BreadcrumbItem.js +++ b/src/BreadcrumbItem.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, active: PropTypes.bool, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/Button.js b/src/Button.js index 92127bf4e..8f6979581 100644 --- a/src/Button.js +++ b/src/Button.js @@ -1,21 +1,23 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { active: PropTypes.bool, + 'aria-label': PropTypes.string, block: PropTypes.bool, color: PropTypes.string, disabled: PropTypes.bool, outline: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.string]), onClick: PropTypes.func, size: PropTypes.string, children: PropTypes.node, className: PropTypes.string, cssModule: PropTypes.object, + close: PropTypes.bool, }; const defaultProps = { @@ -44,8 +46,10 @@ class Button extends React.Component { render() { let { active, + 'aria-label': ariaLabel, block, className, + close, cssModule, color, outline, @@ -55,10 +59,17 @@ class Button extends React.Component { ...attributes } = this.props; + if (close && typeof attributes.children === 'undefined') { + attributes.children = ×; + } + + const btnOutlineColor = `btn${outline ? '-outline' : ''}-${color}`; + const classes = mapToCssModules(classNames( className, - 'btn', - `btn${outline ? '-outline' : ''}-${color}`, + { close }, + close || 'btn', + close || btnOutlineColor, size ? `btn-${size}` : false, block ? 'btn-block' : false, { active, disabled: this.props.disabled } @@ -68,6 +79,8 @@ class Button extends React.Component { Tag = 'a'; } + const defaultAriaLabel = close ? 'Close' : null; + return ( ); } diff --git a/src/ButtonGroup.js b/src/ButtonGroup.js index b6dcb9d31..f094c8ada 100644 --- a/src/ButtonGroup.js +++ b/src/ButtonGroup.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, 'aria-label': PropTypes.string, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/ButtonToolbar.js b/src/ButtonToolbar.js index a93e4debd..3aed28cf1 100644 --- a/src/ButtonToolbar.js +++ b/src/ButtonToolbar.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, 'aria-label': PropTypes.string, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/Card.js b/src/Card.js index ef585b01d..3f9c3774a 100644 --- a/src/Card.js +++ b/src/Card.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules, deprecated } from './utils'; +import { mapToCssModules, deprecated, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, inverse: PropTypes.bool, color: PropTypes.string, block: deprecated(PropTypes.bool, 'Please use the props "body"'), diff --git a/src/CardBody.js b/src/CardBody.js index ebbb6f6cf..6c5a5bf86 100644 --- a/src/CardBody.js +++ b/src/CardBody.js @@ -1,12 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, + innerRef: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string, + PropTypes.func, + ]), }; const defaultProps = { @@ -17,6 +22,7 @@ const CardBody = (props) => { const { className, cssModule, + innerRef, tag: Tag, ...attributes } = props; @@ -26,7 +32,7 @@ const CardBody = (props) => { ), cssModule); return ( - + ); }; diff --git a/src/CardColumns.js b/src/CardColumns.js index 6d37c4e1d..ad3fb9272 100644 --- a/src/CardColumns.js +++ b/src/CardColumns.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/CardDeck.js b/src/CardDeck.js index 8aade8c18..552f837cb 100644 --- a/src/CardDeck.js +++ b/src/CardDeck.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/CardFooter.js b/src/CardFooter.js index 5526ecd6a..1e3da9029 100644 --- a/src/CardFooter.js +++ b/src/CardFooter.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/CardGroup.js b/src/CardGroup.js index f58761d3e..989ea18ba 100644 --- a/src/CardGroup.js +++ b/src/CardGroup.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/CardHeader.js b/src/CardHeader.js index 4ff24c349..fba75507b 100644 --- a/src/CardHeader.js +++ b/src/CardHeader.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/CardImg.js b/src/CardImg.js index 6267336cf..dc1ded0b4 100644 --- a/src/CardImg.js +++ b/src/CardImg.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, top: PropTypes.bool, bottom: PropTypes.bool, className: PropTypes.string, diff --git a/src/CardImgOverlay.js b/src/CardImgOverlay.js index 6034bee94..462dd877f 100644 --- a/src/CardImgOverlay.js +++ b/src/CardImgOverlay.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/CardLink.js b/src/CardLink.js index 24de8da93..9de2fa32f 100644 --- a/src/CardLink.js +++ b/src/CardLink.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.string]), className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/CardSubtitle.js b/src/CardSubtitle.js index 24f1b4195..3927f9c66 100644 --- a/src/CardSubtitle.js +++ b/src/CardSubtitle.js @@ -1,16 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; const defaultProps = { - tag: 'h6' + tag: 'div' }; const CardSubtitle = (props) => { diff --git a/src/CardText.js b/src/CardText.js index cecc02b62..67fa80e96 100644 --- a/src/CardText.js +++ b/src/CardText.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/CardTitle.js b/src/CardTitle.js index 9acd187d3..764fe514e 100644 --- a/src/CardTitle.js +++ b/src/CardTitle.js @@ -1,16 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; const defaultProps = { - tag: 'h5' + tag: 'div' }; const CardTitle = (props) => { diff --git a/src/CarouselIndicators.js b/src/CarouselIndicators.js index 17f1679bc..12118010d 100644 --- a/src/CarouselIndicators.js +++ b/src/CarouselIndicators.js @@ -13,7 +13,7 @@ const CarouselIndicators = (props) => { ), cssModule); return (
  • { e.preventDefault(); onClickHandler(idx); diff --git a/src/CarouselItem.js b/src/CarouselItem.js index a05f4f443..b61ce5c47 100644 --- a/src/CarouselItem.js +++ b/src/CarouselItem.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import Transition from 'react-transition-group/Transition'; -import { mapToCssModules, TransitionTimeouts, TransitionStatuses } from './utils'; +import { Transition } from 'react-transition-group'; +import { mapToCssModules, TransitionTimeouts, TransitionStatuses, tagPropType } from './utils'; class CarouselItem extends React.Component { constructor(props) { @@ -92,7 +92,7 @@ class CarouselItem extends React.Component { CarouselItem.propTypes = { ...Transition.propTypes, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, in: PropTypes.bool, cssModule: PropTypes.object, children: PropTypes.node, diff --git a/src/Col.js b/src/Col.js index 6f62f4a94..511b0a1fc 100644 --- a/src/Col.js +++ b/src/Col.js @@ -2,7 +2,7 @@ import isobject from 'lodash.isobject'; import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules, deprecated } from './utils'; +import { mapToCssModules, deprecated, tagPropType } from './utils'; const colWidths = ['xs', 'sm', 'md', 'lg', 'xl']; const stringOrNumberProp = PropTypes.oneOfType([PropTypes.number, PropTypes.string]); @@ -21,7 +21,7 @@ const columnProps = PropTypes.oneOfType([ ]); const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, xs: columnProps, sm: columnProps, md: columnProps, diff --git a/src/Collapse.js b/src/Collapse.js index edd517280..9bac27e6d 100644 --- a/src/Collapse.js +++ b/src/Collapse.js @@ -1,8 +1,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import Transition from 'react-transition-group/Transition'; -import { mapToCssModules, omit, pick, TransitionTimeouts, TransitionPropTypeKeys, TransitionStatuses } from './utils'; +import { Transition } from 'react-transition-group'; +import { mapToCssModules, omit, pick, TransitionTimeouts, TransitionPropTypeKeys, TransitionStatuses, tagPropType } from './utils'; const propTypes = { ...Transition.propTypes, @@ -11,11 +11,15 @@ const propTypes = { PropTypes.arrayOf(PropTypes.node), PropTypes.node ]), - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.node, navbar: PropTypes.bool, cssModule: PropTypes.object, - innerRef: PropTypes.object, + innerRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, + PropTypes.object + ]), }; const defaultProps = { @@ -97,17 +101,6 @@ class Collapse extends Component { const { height } = this.state; - // In NODE_ENV=production the Transition.propTypes are wrapped which results in an - // empty object "{}". This is the result of the `react-transition-group` babel - // configuration settings. Therefore, to ensure that production builds work without - // error, we can either explicitly define keys or use the Transition.defaultProps. - // Using the Transition.defaultProps excludes any required props. Thus, the best - // solution is to explicitly define required props in our utilities and reference these. - // This also gives us more flexibility in the future to remove the prop-types - // dependency in distribution builds (Similar to how `react-transition-group` does). - // Note: Without omitting the `react-transition-group` props, the resulting child - // Tag component would inherit the Transition properties as attributes for the HTML - // element which results in errors/warnings for non-valid attributes. const transitionProps = pick(otherProps, TransitionPropTypeKeys); const childProps = omit(otherProps, TransitionPropTypeKeys); return ( diff --git a/src/Container.js b/src/Container.js index a9ea18c9e..47aeee49c 100644 --- a/src/Container.js +++ b/src/Container.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, fluid: PropTypes.bool, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/CustomInput.js b/src/CustomInput.js index 6ec6a9a1b..a885d366b 100644 --- a/src/CustomInput.js +++ b/src/CustomInput.js @@ -7,7 +7,7 @@ const propTypes = { className: PropTypes.string, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, type: PropTypes.string.isRequired, - label: PropTypes.string, + label: PropTypes.node, inline: PropTypes.bool, valid: PropTypes.bool, invalid: PropTypes.bool, @@ -61,7 +61,7 @@ function CustomInput(props) { ); } - if (type !== 'checkbox' && type !== 'radio') { + if (type !== 'checkbox' && type !== 'radio' && type !== 'switch') { return ; } @@ -77,6 +77,7 @@ function CustomInput(props) {
    diff --git a/src/Dropdown.js b/src/Dropdown.js index 0f7e501ab..459684aed 100644 --- a/src/Dropdown.js +++ b/src/Dropdown.js @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import { Manager } from 'react-popper'; import classNames from 'classnames'; -import { mapToCssModules, omit, keyCodes, deprecated } from './utils'; +import { mapToCssModules, omit, keyCodes, deprecated, tagPropType } from './utils'; const propTypes = { disabled: PropTypes.bool, @@ -18,7 +18,7 @@ const propTypes = { active: PropTypes.bool, addonType: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['prepend', 'append'])]), size: PropTypes.string, - tag: PropTypes.string, + tag: tagPropType, toggle: PropTypes.func, children: PropTypes.node, className: PropTypes.string, @@ -79,9 +79,21 @@ class Dropdown extends React.Component { } getContainer() { + if (this._$container) return this._$container; + this._$container = ReactDOM.findDOMNode(this); return ReactDOM.findDOMNode(this); } + getMenuCtrl() { + if (this._$menuCtrl) return this._$menuCtrl; + this._$menuCtrl = this.getContainer().querySelector('[aria-expanded]'); + return this._$menuCtrl; + } + + getMenuItems() { + return [].slice.call(this.getContainer().querySelectorAll('[role="menuitem"]')); + } + addEvents() { ['click', 'touchstart', 'keyup'].forEach(event => document.addEventListener(event, this.handleDocumentClick, true) @@ -106,56 +118,64 @@ class Dropdown extends React.Component { } handleKeyDown(e) { - if ([keyCodes.esc, keyCodes.up, keyCodes.down, keyCodes.space].indexOf(e.which) === -1 || - (/button/i.test(e.target.tagName) && e.which === keyCodes.space) || - /input|textarea/i.test(e.target.tagName)) { + if ( + /input|textarea/i.test(e.target.tagName) + || (keyCodes.tab === e.which && e.target.getAttribute('role') !== 'menuitem') + ) { return; } e.preventDefault(); - if (this.props.disabled) return; - - const container = this.getContainer(); - - if (e.which === keyCodes.space && this.props.isOpen && container !== e.target) { - e.target.click(); - } - - if (e.which === keyCodes.esc || !this.props.isOpen) { - this.toggle(e); - container.querySelector('[aria-expanded]').focus(); - return; - } - const menuClass = mapToCssModules('dropdown-menu', this.props.cssModule); - const itemClass = mapToCssModules('dropdown-item', this.props.cssModule); - const disabledClass = mapToCssModules('disabled', this.props.cssModule); - - const items = container.querySelectorAll(`.${menuClass} .${itemClass}:not(.${disabledClass})`); - - if (!items.length) return; + if (this.props.disabled) return; - let index = -1; - for (let i = 0; i < items.length; i += 1) { - if (items[i] === e.target) { - index = i; - break; + if (this.getMenuCtrl() === e.target) { + if ( + !this.props.isOpen + && ([keyCodes.space, keyCodes.enter, keyCodes.up, keyCodes.down].indexOf(e.which) > -1) + ) { + this.toggle(e); + setTimeout(() => this.getMenuItems()[0].focus()); } } - if (e.which === keyCodes.up && index > 0) { - index -= 1; - } - - if (e.which === keyCodes.down && index < items.length - 1) { - index += 1; - } - - if (index < 0) { - index = 0; + if (this.props.isOpen && (e.target.getAttribute('role') === 'menuitem')) { + if ([keyCodes.tab, keyCodes.esc].indexOf(e.which) > -1) { + this.toggle(e); + this.getMenuCtrl().focus(); + } else if ([keyCodes.space, keyCodes.enter].indexOf(e.which) > -1) { + e.target.click(); + this.getMenuCtrl().focus(); + } else if ( + [keyCodes.down, keyCodes.up].indexOf(e.which) > -1 + || ([keyCodes.n, keyCodes.p].indexOf(e.which) > -1 && e.ctrlKey) + ) { + const $menuitems = this.getMenuItems(); + let index = $menuitems.indexOf(e.target); + if (keyCodes.up === e.which || (keyCodes.p === e.which && e.ctrlKey)) { + index = index !== 0 ? index - 1 : $menuitems.length - 1; + } else if (keyCodes.down === e.which || (keyCodes.n === e.which && e.ctrlKey)) { + index = index === $menuitems.length - 1 ? 0 : index + 1; + } + $menuitems[index].focus(); + } else if (keyCodes.end === e.which) { + const $menuitems = this.getMenuItems(); + $menuitems[$menuitems.length - 1].focus(); + } else if (keyCodes.home === e.which) { + const $menuitems = this.getMenuItems(); + $menuitems[0].focus(); + } else if ((e.which >= 48) && (e.which <= 90)) { + const $menuitems = this.getMenuItems(); + const charPressed = String.fromCharCode(e.which).toLowerCase(); + for (let i = 0; i < $menuitems.length; i += 1) { + const firstLetter = $menuitems[i].textContent && $menuitems[i].textContent[0].toLowerCase(); + if (firstLetter === charPressed) { + $menuitems[i].focus(); + break; + } + } + } } - - items[index].focus(); } handleProps() { @@ -197,7 +217,7 @@ class Dropdown extends React.Component { if (setActiveFromChild) { React.Children.map(this.props.children[1].props.children, (dropdownItem) => { - if (dropdownItem.props.active) subItemIsActive = true; + if (dropdownItem && dropdownItem.props.active) subItemIsActive = true; } ); } diff --git a/src/DropdownItem.js b/src/DropdownItem.js index 262e47567..dfb63a890 100644 --- a/src/DropdownItem.js +++ b/src/DropdownItem.js @@ -1,14 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules, omit } from './utils'; +import { mapToCssModules, omit, tagPropType } from './utils'; const propTypes = { children: PropTypes.node, active: PropTypes.bool, disabled: PropTypes.bool, divider: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, header: PropTypes.bool, onClick: PropTypes.func, className: PropTypes.string, @@ -58,6 +58,7 @@ class DropdownItem extends React.Component { render() { const tabIndex = this.getTabIndex(); + const role = tabIndex > -1 ? 'menuitem' : undefined; let { className, cssModule, @@ -93,6 +94,7 @@ class DropdownItem extends React.Component { type={(Tag === 'button' && (props.onClick || this.props.toggle)) ? 'button' : undefined} {...props} tabIndex={tabIndex} + role={role} className={classes} onClick={this.onClick} /> diff --git a/src/DropdownMenu.js b/src/DropdownMenu.js index 07d6e2721..c19d82523 100644 --- a/src/DropdownMenu.js +++ b/src/DropdownMenu.js @@ -2,10 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { Popper } from 'react-popper'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.string, + tag: tagPropType, children: PropTypes.node.isRequired, right: PropTypes.bool, flip: PropTypes.bool, diff --git a/src/DropdownToggle.js b/src/DropdownToggle.js index c10d5a2ca..032bd3d93 100644 --- a/src/DropdownToggle.js +++ b/src/DropdownToggle.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { Target } from 'react-popper'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; import Button from './Button'; const propTypes = { @@ -15,7 +15,7 @@ const propTypes = { onClick: PropTypes.func, 'aria-haspopup': PropTypes.bool, split: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, nav: PropTypes.bool, }; diff --git a/src/Fade.js b/src/Fade.js index 76f9c5f8e..031959cb8 100644 --- a/src/Fade.js +++ b/src/Fade.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import Transition from 'react-transition-group/Transition'; -import { mapToCssModules, omit, pick, TransitionPropTypeKeys, TransitionTimeouts } from './utils'; +import { Transition } from 'react-transition-group'; +import { mapToCssModules, omit, pick, TransitionPropTypeKeys, TransitionTimeouts, tagPropType } from './utils'; const propTypes = { ...Transition.propTypes, @@ -10,7 +10,7 @@ const propTypes = { PropTypes.arrayOf(PropTypes.node), PropTypes.node ]), - tag: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + tag: tagPropType, baseClass: PropTypes.string, baseClassActive: PropTypes.string, className: PropTypes.string, @@ -46,17 +46,6 @@ function Fade(props) { ...otherProps } = props; - // In NODE_ENV=production the Transition.propTypes are wrapped which results in an - // empty object "{}". This is the result of the `react-transition-group` babel - // configuration settings. Therefore, to ensure that production builds work without - // error, we can either explicitly define keys or use the Transition.defaultProps. - // Using the Transition.defaultProps excludes any required props. Thus, the best - // solution is to explicitly define required props in our utilities and reference these. - // This also gives us more flexibility in the future to remove the prop-types - // dependency in distribution builds (Similar to how `react-transition-group` does). - // Note: Without omitting the `react-transition-group` props, the resulting child - // Tag component would inherit the Transition properties as attributes for the HTML - // element which results in errors/warnings for non-valid attributes. const transitionProps = pick(otherProps, TransitionPropTypeKeys); const childProps = omit(otherProps, TransitionPropTypeKeys); diff --git a/src/Form.js b/src/Form.js index 0ec51bbda..e5efd6093 100644 --- a/src/Form.js +++ b/src/Form.js @@ -1,12 +1,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { children: PropTypes.node, inline: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.string]), className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/FormFeedback.js b/src/FormFeedback.js index dd7c0acdd..0a81f9a78 100644 --- a/src/FormFeedback.js +++ b/src/FormFeedback.js @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { children: PropTypes.node, - tag: PropTypes.string, + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, valid: PropTypes.bool, diff --git a/src/FormGroup.js b/src/FormGroup.js index df6fdb23f..6d0ae8096 100644 --- a/src/FormGroup.js +++ b/src/FormGroup.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { children: PropTypes.node, @@ -9,7 +9,7 @@ const propTypes = { check: PropTypes.bool, inline: PropTypes.bool, disabled: PropTypes.bool, - tag: PropTypes.string, + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; @@ -32,7 +32,6 @@ const FormGroup = (props) => { const classes = mapToCssModules(classNames( className, - 'position-relative', row ? 'row' : false, check ? 'form-check' : 'form-group', check && inline ? 'form-check-inline' : false, diff --git a/src/FormText.js b/src/FormText.js index ff0578e99..259a243ca 100644 --- a/src/FormText.js +++ b/src/FormText.js @@ -1,12 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { children: PropTypes.node, inline: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, color: PropTypes.string, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/Input.js b/src/Input.js index a75cdb66d..d23b85a8c 100644 --- a/src/Input.js +++ b/src/Input.js @@ -3,27 +3,34 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules, deprecated, warnOnce } from './utils'; +import { mapToCssModules, deprecated, warnOnce, tagPropType } from './utils'; const propTypes = { children: PropTypes.node, type: PropTypes.string, size: PropTypes.string, bsSize: PropTypes.string, - state: deprecated(PropTypes.string, 'Please use the props "valid" and "invalid" to indicate the state.'), + state: deprecated( + PropTypes.string, + 'Please use the props "valid" and "invalid" to indicate the state.' + ), valid: PropTypes.bool, invalid: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), - innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.string]), + tag: tagPropType, + innerRef: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.func, + PropTypes.string + ]), static: deprecated(PropTypes.bool, 'Please use the prop "plaintext"'), plaintext: PropTypes.bool, addon: PropTypes.bool, className: PropTypes.string, - cssModule: PropTypes.object, + cssModule: PropTypes.object }; const defaultProps = { - type: 'text', + type: 'text' }; class Input extends React.Component { @@ -69,13 +76,13 @@ class Input extends React.Component { const fileInput = type === 'file'; const textareaInput = type === 'textarea'; const selectInput = type === 'select'; - let Tag = tag || ((selectInput || textareaInput) ? type : 'input'); + let Tag = tag || (selectInput || textareaInput ? type : 'input'); let formControlClass = 'form-control'; if (plaintext || staticInput) { formControlClass = `${formControlClass}-plaintext`; - Tag = tag || 'p'; + Tag = tag || 'input'; } else if (fileInput) { formControlClass = `${formControlClass}-file`; } else if (checkInput) { @@ -86,7 +93,11 @@ class Input extends React.Component { } } - if (state && typeof valid === 'undefined' && typeof invalid === 'undefined') { + if ( + state && + typeof valid === 'undefined' && + typeof invalid === 'undefined' + ) { if (state === 'danger') { invalid = true; } else if (state === 'success') { @@ -95,31 +106,45 @@ class Input extends React.Component { } if (attributes.size && isNotaNumber.test(attributes.size)) { - warnOnce('Please use the prop "bsSize" instead of the "size" to bootstrap\'s input sizing.'); + warnOnce( + 'Please use the prop "bsSize" instead of the "size" to bootstrap\'s input sizing.' + ); bsSize = attributes.size; delete attributes.size; } - const classes = mapToCssModules(classNames( - className, - invalid && 'is-invalid', - valid && 'is-valid', - bsSize ? `form-control-${bsSize}` : false, - formControlClass - ), cssModule); + const classes = mapToCssModules( + classNames( + className, + invalid && 'is-invalid', + valid && 'is-valid', + bsSize ? `form-control-${bsSize}` : false, + formControlClass + ), + cssModule + ); if (Tag === 'input' || (tag && typeof tag === 'function')) { attributes.type = type; } - if (attributes.children && !(plaintext || staticInput || type === 'select' || typeof Tag !== 'string' || Tag === 'select')) { - warnOnce(`Input with a type of "${type}" cannot have children. Please use "value"/"defaultValue" instead.`); + if ( + attributes.children && + !( + plaintext || + staticInput || + type === 'select' || + typeof Tag !== 'string' || + Tag === 'select' + ) + ) { + warnOnce( + `Input with a type of "${type}" cannot have children. Please use "value"/"defaultValue" instead.` + ); delete attributes.children; } - return ( - - ); + return ; } } diff --git a/src/InputGroup.js b/src/InputGroup.js index ac83312ff..f0a73d7e0 100644 --- a/src/InputGroup.js +++ b/src/InputGroup.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, size: PropTypes.string, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/InputGroupAddon.js b/src/InputGroupAddon.js index c993eed95..19767e87a 100644 --- a/src/InputGroupAddon.js +++ b/src/InputGroupAddon.js @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; import InputGroupText from './InputGroupText'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, addonType: PropTypes.oneOf(['prepend', 'append']).isRequired, children: PropTypes.node, className: PropTypes.string, diff --git a/src/InputGroupButton.js b/src/InputGroupButton.js index e62ad08b9..d5c6a3010 100644 --- a/src/InputGroupButton.js +++ b/src/InputGroupButton.js @@ -2,10 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import Button from './Button'; import InputGroupAddon from './InputGroupAddon'; -import { warnOnce } from './utils'; +import { warnOnce, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, addonType: PropTypes.oneOf(['prepend', 'append']).isRequired, children: PropTypes.node, groupClassName: PropTypes.string, diff --git a/src/InputGroupText.js b/src/InputGroupText.js index e139a3c89..6977a4a6d 100644 --- a/src/InputGroupText.js +++ b/src/InputGroupText.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/Jumbotron.js b/src/Jumbotron.js index 09767ee5e..69ee2f8de 100644 --- a/src/Jumbotron.js +++ b/src/Jumbotron.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, fluid: PropTypes.bool, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/Label.js b/src/Label.js index 572ea85d3..539ed76cd 100644 --- a/src/Label.js +++ b/src/Label.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import isobject from 'lodash.isobject'; -import { mapToCssModules, deprecated } from './utils'; +import { mapToCssModules, deprecated, tagPropType } from './utils'; const colWidths = ['xs', 'sm', 'md', 'lg', 'xl']; @@ -26,7 +26,7 @@ const propTypes = { check: PropTypes.bool, size: PropTypes.string, for: PropTypes.string, - tag: PropTypes.string, + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, xs: columnProps, diff --git a/src/ListGroup.js b/src/ListGroup.js index 2a8d37b2f..d2999780a 100644 --- a/src/ListGroup.js +++ b/src/ListGroup.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, flush: PropTypes.bool, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/ListGroupItem.js b/src/ListGroupItem.js index 819a96230..b8d75c82b 100644 --- a/src/ListGroupItem.js +++ b/src/ListGroupItem.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, active: PropTypes.bool, disabled: PropTypes.bool, color: PropTypes.string, diff --git a/src/ListGroupItemHeading.js b/src/ListGroupItemHeading.js index 21ac15cf1..000344438 100644 --- a/src/ListGroupItemHeading.js +++ b/src/ListGroupItemHeading.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.any, cssModule: PropTypes.object, }; diff --git a/src/ListGroupItemText.js b/src/ListGroupItemText.js index c8d88ee17..3b498864a 100644 --- a/src/ListGroupItemText.js +++ b/src/ListGroupItemText.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.any, cssModule: PropTypes.object, }; diff --git a/src/Media.js b/src/Media.js index 24b4e4059..ee68f5c93 100644 --- a/src/Media.js +++ b/src/Media.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { body: PropTypes.bool, @@ -15,7 +15,7 @@ const propTypes = { middle: PropTypes.bool, object: PropTypes.bool, right: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, top: PropTypes.bool, }; diff --git a/src/Modal.js b/src/Modal.js index b9c3425e5..3be845dc6 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -9,6 +9,7 @@ import { setScrollbarWidth, mapToCssModules, omit, + focusableElements, TransitionTimeouts } from './utils'; @@ -83,9 +84,11 @@ class Modal extends React.Component { this._element = null; this._originalBodyPadding = null; + this.getFocusableChildren = this.getFocusableChildren.bind(this); + this.handleBackdropClick = this.handleBackdropClick.bind(this); this.handleBackdropMouseDown = this.handleBackdropMouseDown.bind(this); - this.handleBackdropMouseUp = this.handleBackdropMouseUp.bind(this); this.handleEscape = this.handleEscape.bind(this); + this.handleTab = this.handleTab.bind(this); this.onOpened = this.onOpened.bind(this); this.onClosed = this.onClosed.bind(this); @@ -166,29 +169,79 @@ class Modal extends React.Component { } } - handleBackdropMouseDown(e) { - this._mouseDownElement = e.target; + getFocusableChildren() { + return this._element.querySelectorAll(focusableElements.join(', ')); + } + + getFocusedChild() { + let currentFocus; + const focusableChildren = this.getFocusableChildren(); + + try { + currentFocus = document.activeElement; + } catch (err) { + currentFocus = focusableChildren[0]; + } + return currentFocus; } - handleBackdropMouseUp(e) { + + // not mouseUp because scrollbar fires it, shouldn't close when user scrolls + handleBackdropClick(e) { if (e.target === this._mouseDownElement) { e.stopPropagation(); if (!this.props.isOpen || this.props.backdrop !== true) return; - const container = this._dialog; + const backdrop = this._dialog ? this._dialog.parentNode : null; - if (e.target && !container.contains(e.target) && this.props.toggle) { + if (backdrop && e.target === backdrop && this.props.toggle) { this.props.toggle(e); } } } + handleTab(e) { + if (e.which !== 9) return; + + const focusableChildren = this.getFocusableChildren(); + const totalFocusable = focusableChildren.length; + const currentFocus = this.getFocusedChild(); + + let focusedIndex = 0; + + for (let i = 0; i < totalFocusable; i += 1) { + if (focusableChildren[i] === currentFocus) { + focusedIndex = i; + break; + } + } + + if (e.shiftKey && focusedIndex === 0) { + e.preventDefault(); + focusableChildren[totalFocusable - 1].focus(); + } else if (!e.shiftKey && focusedIndex === totalFocusable - 1) { + e.preventDefault(); + focusableChildren[0].focus(); + } + } + + handleBackdropMouseDown(e) { + this._mouseDownElement = e.target; + } + handleEscape(e) { if (this.props.isOpen && this.props.keyboard && e.keyCode === 27 && this.props.toggle) { + e.preventDefault(); + e.stopPropagation(); this.props.toggle(e); } } init() { + try { + this._triggeringElement = document.activeElement; + } catch (err) { + this._triggeringElement = null; + } this._element = document.createElement('div'); this._element.setAttribute('tabindex', '-1'); this._element.style.position = 'relative'; @@ -198,14 +251,13 @@ class Modal extends React.Component { conditionallyUpdateScrollbar(); document.body.appendChild(this._element); - - if (!this.bodyClassAdded) { + if (Modal.openCount === 0) { document.body.className = classNames( document.body.className, mapToCssModules('modal-open', this.props.cssModule) ); - this.bodyClassAdded = true; } + Modal.openCount += 1; } destroy() { @@ -214,13 +266,18 @@ class Modal extends React.Component { this._element = null; } - if (this.bodyClassAdded) { + if (this._triggeringElement) { + if (this._triggeringElement.focus) this._triggeringElement.focus(); + this._triggeringElement = null; + } + + if (Modal.openCount <= 1) { const modalOpenClassName = mapToCssModules('modal-open', this.props.cssModule); // Use regex to prevent matching `modal-open` as part of a different class, e.g. `my-modal-opened` const modalOpenClassNameRegex = new RegExp(`(^| )${modalOpenClassName}( |$)`); document.body.className = document.body.className.replace(modalOpenClassNameRegex, ' ').trim(); - this.bodyClassAdded = false; } + Modal.openCount = Math.max(0, Modal.openCount - 1); setScrollbarWidth(this._originalBodyPadding); } @@ -269,9 +326,10 @@ class Modal extends React.Component { } = this.props; const modalAttributes = { + onClick: this.handleBackdropClick, onMouseDown: this.handleBackdropMouseDown, - onMouseUp: this.handleBackdropMouseUp, onKeyUp: this.handleEscape, + onKeyDown: this.handleTab, style: { display: 'block' }, 'aria-labelledby': labelledBy, role, @@ -292,6 +350,17 @@ class Modal extends React.Component { timeout: hasTransition ? this.props.backdropTransition.timeout : 0, }; + const Backdrop = backdrop && ( + hasTransition ? + () + :
    + ); + return (
    @@ -308,12 +377,7 @@ class Modal extends React.Component { {external} {this.renderModalDialog()} - + {Backdrop}
    ); @@ -325,5 +389,6 @@ class Modal extends React.Component { Modal.propTypes = propTypes; Modal.defaultProps = defaultProps; +Modal.openCount = 0; export default Modal; diff --git a/src/ModalBody.js b/src/ModalBody.js index d42f808ac..a2750facf 100644 --- a/src/ModalBody.js +++ b/src/ModalBody.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/ModalFooter.js b/src/ModalFooter.js index 7950d3b78..ba1afc686 100644 --- a/src/ModalFooter.js +++ b/src/ModalFooter.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/ModalHeader.js b/src/ModalHeader.js index 54a222a46..7f55fff77 100644 --- a/src/ModalHeader.js +++ b/src/ModalHeader.js @@ -1,22 +1,25 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), - wrapTag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, + wrapTag: tagPropType, toggle: PropTypes.func, className: PropTypes.string, cssModule: PropTypes.object, children: PropTypes.node, closeAriaLabel: PropTypes.string, + charCode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + close: PropTypes.object, }; const defaultProps = { tag: 'h5', wrapTag: 'div', closeAriaLabel: 'Close', + charCode: 215, }; const ModalHeader = (props) => { @@ -29,6 +32,8 @@ const ModalHeader = (props) => { tag: Tag, wrapTag: WrapTag, closeAriaLabel, + charCode, + close, ...attributes } = props; const classes = mapToCssModules(classNames( @@ -36,10 +41,11 @@ const ModalHeader = (props) => { 'modal-header' ), cssModule); - if (toggle) { + if (!close && toggle) { + const closeIcon = typeof charCode === 'number' ? String.fromCharCode(charCode) : charCode; closeButton = ( ); } @@ -49,7 +55,7 @@ const ModalHeader = (props) => { {children} - {closeButton} + {close || closeButton} ); }; diff --git a/src/Nav.js b/src/Nav.js index 78cf16517..e75abe351 100644 --- a/src/Nav.js +++ b/src/Nav.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { tabs: PropTypes.bool, @@ -12,7 +12,7 @@ const propTypes = { fill: PropTypes.bool, navbar: PropTypes.bool, card: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/NavItem.js b/src/NavItem.js index d6fb3576d..6817bb272 100644 --- a/src/NavItem.js +++ b/src/NavItem.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, active: PropTypes.bool, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/NavLink.js b/src/NavLink.js index c35d9f413..cdcd516c5 100644 --- a/src/NavLink.js +++ b/src/NavLink.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.string]), disabled: PropTypes.bool, active: PropTypes.bool, diff --git a/src/Navbar.js b/src/Navbar.js index 8cb4aa8fe..be6e8b373 100644 --- a/src/Navbar.js +++ b/src/Navbar.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules, deprecated } from './utils'; +import { mapToCssModules, deprecated, tagPropType } from './utils'; const propTypes = { light: PropTypes.bool, @@ -12,7 +12,7 @@ const propTypes = { sticky: PropTypes.string, color: PropTypes.string, role: PropTypes.string, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, toggleable: deprecated(PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), 'Please use the prop "expand"'), diff --git a/src/NavbarBrand.js b/src/NavbarBrand.js index 4c35d7d14..2f970deab 100644 --- a/src/NavbarBrand.js +++ b/src/NavbarBrand.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/NavbarToggler.js b/src/NavbarToggler.js index b680af778..65c6fa9be 100644 --- a/src/NavbarToggler.js +++ b/src/NavbarToggler.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, type: PropTypes.string, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/Pagination.js b/src/Pagination.js index d47bb7d80..e702bb86e 100644 --- a/src/Pagination.js +++ b/src/Pagination.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { children: PropTypes.node, @@ -9,8 +9,8 @@ const propTypes = { listClassName: PropTypes.string, cssModule: PropTypes.object, size: PropTypes.string, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), - listTag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, + listTag: tagPropType, 'aria-label': PropTypes.string }; diff --git a/src/PaginationItem.js b/src/PaginationItem.js index f05b0b932..70ad628e1 100644 --- a/src/PaginationItem.js +++ b/src/PaginationItem.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { active: PropTypes.bool, @@ -9,7 +9,7 @@ const propTypes = { className: PropTypes.string, cssModule: PropTypes.object, disabled: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, }; const defaultProps = { diff --git a/src/PaginationLink.js b/src/PaginationLink.js index 04346af81..1ef1570b8 100644 --- a/src/PaginationLink.js +++ b/src/PaginationLink.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { 'aria-label': PropTypes.string, @@ -10,7 +10,7 @@ const propTypes = { cssModule: PropTypes.object, next: PropTypes.bool, previous: PropTypes.bool, - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, }; const defaultProps = { diff --git a/src/Popover.js b/src/Popover.js index 5da44ecad..afbb7ed00 100644 --- a/src/Popover.js +++ b/src/Popover.js @@ -1,195 +1,36 @@ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; -import PopperContent from './PopperContent'; -import { getTarget, DOMElement, mapToCssModules, omit, PopperPlacements } from './utils'; - -const propTypes = { - placement: PropTypes.oneOf(PopperPlacements), - target: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.func, - DOMElement, - ]).isRequired, - container: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.func, - DOMElement, - ]), - isOpen: PropTypes.bool, - disabled: PropTypes.bool, - hideArrow: PropTypes.bool, - className: PropTypes.string, - innerClassName: PropTypes.string, - placementPrefix: PropTypes.string, - cssModule: PropTypes.object, - toggle: PropTypes.func, - delay: PropTypes.oneOfType([ - PropTypes.shape({ show: PropTypes.number, hide: PropTypes.number }), - PropTypes.number, - ]), - modifiers: PropTypes.object, - offset: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), -}; - -const DEFAULT_DELAYS = { - show: 0, - hide: 0, -}; +import TooltipPopoverWrapper, { propTypes } from './TooltipPopoverWrapper'; const defaultProps = { - isOpen: false, - hideArrow: false, placement: 'right', placementPrefix: 'bs-popover', - delay: DEFAULT_DELAYS, - toggle: () => {}, + trigger: 'click', }; -class Popover extends React.Component { - constructor(props) { - super(props); - - this.addTargetEvents = this.addTargetEvents.bind(this); - this.handleDocumentClick = this.handleDocumentClick.bind(this); - this.removeTargetEvents = this.removeTargetEvents.bind(this); - this.getRef = this.getRef.bind(this); - this.toggle = this.toggle.bind(this); - this.show = this.show.bind(this); - this.hide = this.hide.bind(this); - } - - componentDidMount() { - this._target = getTarget(this.props.target); - this.handleProps(); - } - - componentDidUpdate() { - this.handleProps(); - } - - componentWillUnmount() { - this.clearShowTimeout(); - this.clearHideTimeout(); - this.removeTargetEvents(); - } - - getRef(ref) { - this._popover = ref; - } - - getDelay(key) { - const { delay } = this.props; - if (typeof delay === 'object') { - return isNaN(delay[key]) ? DEFAULT_DELAYS[key] : delay[key]; - } - return delay; - } - - handleProps() { - if (this.props.isOpen) { - this.show(); - } else { - this.hide(); - } - } - - show() { - this.clearHideTimeout(); - this.addTargetEvents(); - if (!this.props.isOpen) { - this.clearShowTimeout(); - this._showTimeout = setTimeout(this.toggle, this.getDelay('show')); - } - } - - hide() { - this.clearShowTimeout(); - this.removeTargetEvents(); - if (this.props.isOpen) { - this.clearHideTimeout(); - this._hideTimeout = setTimeout(this.toggle, this.getDelay('hide')); - } - } - - clearShowTimeout() { - clearTimeout(this._showTimeout); - this._showTimeout = undefined; - } - - clearHideTimeout() { - clearTimeout(this._hideTimeout); - this._hideTimeout = undefined; - } - - handleDocumentClick(e) { - if (e.target !== this._target && !this._target.contains(e.target) && e.target !== this._popover && !(this._popover && this._popover.contains(e.target))) { - if (this._hideTimeout) { - this.clearHideTimeout(); - } - - if (this.props.isOpen) { - this.toggle(e); - } - } - } - - addTargetEvents() { - ['click', 'touchstart'].forEach(event => - document.addEventListener(event, this.handleDocumentClick, true) - ); - } - - removeTargetEvents() { - ['click', 'touchstart'].forEach(event => - document.removeEventListener(event, this.handleDocumentClick, true) - ); - } - - toggle(e) { - if (this.props.disabled) { - return e && e.preventDefault(); - } - - return this.props.toggle(e); - } - - render() { - if (!this.props.isOpen) { - return null; - } - - const attributes = omit(this.props, Object.keys(propTypes)); - const classes = mapToCssModules(classNames( - 'popover-inner', - this.props.innerClassName - ), this.props.cssModule); - - const popperClasses = mapToCssModules(classNames( - 'popover', - 'show', - this.props.className - ), this.props.cssModule); - - return ( - -
    - - ); - } -} +const Popover = (props) => { + const popperClasses = classNames( + 'popover', + 'show' + ); + + const classes = classNames( + 'popover-inner', + props.innerClassName + ); + + + return ( + + ); +}; Popover.propTypes = propTypes; Popover.defaultProps = defaultProps; + export default Popover; diff --git a/src/PopoverBody.js b/src/PopoverBody.js index c72b2625e..542972436 100644 --- a/src/PopoverBody.js +++ b/src/PopoverBody.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/PopoverHeader.js b/src/PopoverHeader.js index 49e2ae37c..463114398 100644 --- a/src/PopoverHeader.js +++ b/src/PopoverHeader.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, }; diff --git a/src/PopperContent.js b/src/PopperContent.js index 5e34a76c4..8d4051990 100644 --- a/src/PopperContent.js +++ b/src/PopperContent.js @@ -3,27 +3,35 @@ import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; import { Arrow, Popper as ReactPopper } from 'react-popper'; -import { getTarget, DOMElement, mapToCssModules } from './utils'; +import { getTarget, targetPropType, mapToCssModules, DOMElement, tagPropType } from './utils'; +import Fade from './Fade'; + +function noop() { } const propTypes = { children: PropTypes.node.isRequired, - className: PropTypes.string, + popperClassName: PropTypes.string, placement: PropTypes.string, placementPrefix: PropTypes.string, arrowClassName: PropTypes.string, hideArrow: PropTypes.bool, - tag: PropTypes.string, + tag: tagPropType, isOpen: PropTypes.bool.isRequired, cssModule: PropTypes.object, offset: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), fallbackPlacement: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), flip: PropTypes.bool, - container: PropTypes.oneOfType([PropTypes.string, PropTypes.func, DOMElement]), - target: PropTypes.oneOfType([PropTypes.string, PropTypes.func, DOMElement]).isRequired, + container: targetPropType, + target: targetPropType.isRequired, modifiers: PropTypes.object, + boundariesElement: PropTypes.oneOfType([PropTypes.string, DOMElement]), + onClosed: PropTypes.func, + fade: PropTypes.bool, + transition: PropTypes.shape(Fade.propTypes), }; const defaultProps = { + boundariesElement: 'scrollParent', placement: 'auto', hideArrow: false, isOpen: false, @@ -32,6 +40,11 @@ const defaultProps = { flip: true, container: 'body', modifiers: {}, + onClosed: noop, + fade: true, + transition: { + ...Fade.defaultProps, + } }; const childContextTypes = { @@ -45,7 +58,9 @@ class PopperContent extends React.Component { this.handlePlacementChange = this.handlePlacementChange.bind(this); this.setTargetNode = this.setTargetNode.bind(this); this.getTargetNode = this.getTargetNode.bind(this); - this.state = {}; + this.getRef = this.getRef.bind(this); + this.onClosed = this.onClosed.bind(this); + this.state = { isOpen: props.isOpen }; } getChildContext() { @@ -57,21 +72,17 @@ class PopperContent extends React.Component { }; } - componentDidMount() { - this.handleProps(); - } - - componentDidUpdate(prevProps) { - if (this.props.isOpen !== prevProps.isOpen) { - this.handleProps(); - } else if (this._element) { - // rerender - this.renderIntoSubtree(); + static getDerivedStateFromProps(props, state) { + if (props.isOpen && !state.isOpen) { + return { isOpen: props.isOpen }; } + else return null; } - componentWillUnmount() { - this.hide(); + componentDidUpdate() { + if (this._element && this._element.childNodes && this._element.childNodes[0] && this._element.childNodes[0].focus) { + this._element.childNodes[0].focus(); + } } setTargetNode(node) { @@ -86,6 +97,10 @@ class PopperContent extends React.Component { return getTarget(this.props.container); } + getRef(ref) { + this._element = ref; + } + handlePlacementChange(data) { if (this.state.placement !== data.placement) { this.setState({ placement: data.placement }); @@ -93,39 +108,9 @@ class PopperContent extends React.Component { return data; } - handleProps() { - if (this.props.container !== 'inline') { - if (this.props.isOpen) { - this.show(); - } else { - this.hide(); - } - } - } - - hide() { - if (this._element) { - this.getContainerNode().removeChild(this._element); - ReactDOM.unmountComponentAtNode(this._element); - this._element = null; - } - } - - show() { - this._element = document.createElement('div'); - this.getContainerNode().appendChild(this._element); - this.renderIntoSubtree(); - if (this._element.childNodes && this._element.childNodes[0] && this._element.childNodes[0].focus) { - this._element.childNodes[0].focus(); - } - } - - renderIntoSubtree() { - ReactDOM.unstable_renderSubtreeIntoContainer( - this, - this.renderChildren(), - this._element - ); + onClosed() { + this.props.onClosed(); + this.setState({ isOpen: false }); } renderChildren() { @@ -140,10 +125,14 @@ class PopperContent extends React.Component { placementPrefix, arrowClassName: _arrowClassName, hideArrow, - className, + popperClassName: _popperClassName, tag, container, modifiers, + boundariesElement, + onClosed, + fade, + transition, ...attrs } = this.props; const arrowClassName = mapToCssModules(classNames( @@ -152,13 +141,14 @@ class PopperContent extends React.Component { ), cssModule); const placement = (this.state.placement || attrs.placement).split('-')[0]; const popperClassName = mapToCssModules(classNames( - className, + _popperClassName, placementPrefix ? `${placementPrefix}-${placement}` : placement ), this.props.cssModule); const extendedModifiers = { offset: { offset }, flip: { enabled: flip, behavior: fallbackPlacement }, + preventOverflow: { boundariesElement }, update: { enabled: true, order: 950, @@ -167,19 +157,36 @@ class PopperContent extends React.Component { ...modifiers, }; + const popperTransition = { + ...Fade.defaultProps, + ...transition, + baseClass: fade ? transition.baseClass : '', + timeout: fade ? transition.timeout : 0, + } + return ( - - {children} - {!hideArrow && } - + + + {children} + {!hideArrow && } + + ); } render() { this.setTargetNode(getTarget(this.props.target)); - if (this.props.container === 'inline') { - return this.props.isOpen ? this.renderChildren() : null; + if (this.state.isOpen) { + return this.props.container === 'inline' ? + this.renderChildren() : + ReactDOM.createPortal((
    {this.renderChildren()}
    ), this.getContainerNode()); } return null; diff --git a/src/PopperTargetHelper.js b/src/PopperTargetHelper.js index cea7a85d9..d17aa803b 100644 --- a/src/PopperTargetHelper.js +++ b/src/PopperTargetHelper.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import { getTarget, DOMElement } from './utils'; +import { getTarget, targetPropType } from './utils'; const PopperTargetHelper = (props, context) => { context.popperManager.setTargetNode(getTarget(props.target)); @@ -11,7 +11,7 @@ PopperTargetHelper.contextTypes = { }; PopperTargetHelper.propTypes = { - target: PropTypes.oneOfType([PropTypes.string, PropTypes.func, DOMElement]).isRequired, + target: targetPropType.isRequired, }; export default PopperTargetHelper; diff --git a/src/Progress.js b/src/Progress.js index 84318c20e..5f1b346a0 100644 --- a/src/Progress.js +++ b/src/Progress.js @@ -2,13 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import toNumber from 'lodash.tonumber'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { children: PropTypes.node, bar: PropTypes.bool, multi: PropTypes.bool, - tag: PropTypes.string, + tag: tagPropType, value: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, diff --git a/src/Row.js b/src/Row.js index a55696249..133268042 100644 --- a/src/Row.js +++ b/src/Row.js @@ -1,13 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, noGutters: PropTypes.bool, className: PropTypes.string, cssModule: PropTypes.object, + form: PropTypes.bool }; const defaultProps = { @@ -20,13 +21,14 @@ const Row = (props) => { cssModule, noGutters, tag: Tag, + form, ...attributes } = props; const classes = mapToCssModules(classNames( className, noGutters ? 'no-gutters' : null, - 'row' + form ? 'form-row' : 'row' ), cssModule); return ( diff --git a/src/Spinner.js b/src/Spinner.js new file mode 100644 index 000000000..7c1765e3a --- /dev/null +++ b/src/Spinner.js @@ -0,0 +1,58 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { mapToCssModules, tagPropType } from './utils'; + +const propTypes = { + tag: tagPropType, + type: PropTypes.string, + size: PropTypes.string, + color: PropTypes.string, + className: PropTypes.string, + cssModule: PropTypes.object, + children: PropTypes.string +}; + +const defaultProps = { + tag: 'div', + type: 'border', + children: 'Loading...' +}; + +const Spinner = props => { + const { + className, + cssModule, + type, + size, + color, + children, + tag: Tag, + ...attributes + } = props; + + const classes = mapToCssModules( + classNames( + className, + size ? `spinner-${type}-${size}` : false, + `spinner-${type}`, + color ? `text-${color}` : false + ), + cssModule + ); + + return ( + + {children && + + {children} + + } + + ); +}; + +Spinner.propTypes = propTypes; +Spinner.defaultProps = defaultProps; + +export default Spinner; diff --git a/src/TabContent.js b/src/TabContent.js index adad7a6f9..79b03607f 100644 --- a/src/TabContent.js +++ b/src/TabContent.js @@ -2,11 +2,11 @@ import React, { Component } from 'react'; import { polyfill } from 'react-lifecycles-compat'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules, omit } from './utils'; +import { mapToCssModules, omit, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, activeTab: PropTypes.any, className: PropTypes.string, cssModule: PropTypes.object, diff --git a/src/TabPane.js b/src/TabPane.js index 2abffab20..fe1278e8e 100644 --- a/src/TabPane.js +++ b/src/TabPane.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules } from './utils'; +import { mapToCssModules, tagPropType } from './utils'; const propTypes = { - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, className: PropTypes.string, cssModule: PropTypes.object, tabId: PropTypes.any, diff --git a/src/Table.js b/src/Table.js index edd317843..d3d99c84e 100644 --- a/src/Table.js +++ b/src/Table.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { mapToCssModules, deprecated } from './utils'; +import { mapToCssModules, deprecated, tagPropType } from './utils'; const propTypes = { className: PropTypes.string, @@ -14,8 +14,9 @@ const propTypes = { dark: PropTypes.bool, hover: PropTypes.bool, responsive: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), - tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), - responsiveTag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + tag: tagPropType, + responsiveTag: tagPropType, + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.string, PropTypes.object]), }; const defaultProps = { @@ -37,6 +38,7 @@ const Table = (props) => { responsive, tag: Tag, responsiveTag: ResponsiveTag, + innerRef, ...attributes } = props; @@ -51,7 +53,7 @@ const Table = (props) => { hover ? 'table-hover' : false, ), cssModule); - const table = ; + const table = ; if (responsive) { const responsiveClassName = responsive === true ? 'table-responsive' : `table-responsive-${responsive}`; diff --git a/src/Tooltip.js b/src/Tooltip.js index f5095456e..40d254e03 100644 --- a/src/Tooltip.js +++ b/src/Tooltip.js @@ -1,257 +1,37 @@ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; -import PopperContent from './PopperContent'; -import { getTarget, DOMElement, mapToCssModules, omit, PopperPlacements } from './utils'; - -const propTypes = { - placement: PropTypes.oneOf(PopperPlacements), - target: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.func, - DOMElement, - ]).isRequired, - container: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.func, - DOMElement, - ]), - isOpen: PropTypes.bool, - disabled: PropTypes.bool, - hideArrow: PropTypes.bool, - className: PropTypes.string, - innerClassName: PropTypes.string, - arrowClassName: PropTypes.string, - cssModule: PropTypes.object, - toggle: PropTypes.func, - autohide: PropTypes.bool, - placementPrefix: PropTypes.string, - delay: PropTypes.oneOfType([ - PropTypes.shape({ show: PropTypes.number, hide: PropTypes.number }), - PropTypes.number, - ]), - modifiers: PropTypes.object, - offset: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number - ]), - innerRef: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.string, - PropTypes.object - ]), -}; - -const DEFAULT_DELAYS = { - show: 0, - hide: 250 -}; +import TooltipPopoverWrapper, { propTypes } from './TooltipPopoverWrapper'; const defaultProps = { - isOpen: false, - hideArrow: false, placement: 'top', - placementPrefix: 'bs-tooltip', - delay: DEFAULT_DELAYS, autohide: true, - toggle: function () { } + placementPrefix: 'bs-tooltip', + trigger: 'click hover focus', }; -class Tooltip extends React.Component { - constructor(props) { - super(props); - - this.addTargetEvents = this.addTargetEvents.bind(this); - this.handleDocumentClick = this.handleDocumentClick.bind(this); - this.removeTargetEvents = this.removeTargetEvents.bind(this); - this.toggle = this.toggle.bind(this); - this.onMouseOverTooltip = this.onMouseOverTooltip.bind(this); - this.onMouseLeaveTooltip = this.onMouseLeaveTooltip.bind(this); - this.onMouseOverTooltipContent = this.onMouseOverTooltipContent.bind(this); - this.onMouseLeaveTooltipContent = this.onMouseLeaveTooltipContent.bind(this); - this.show = this.show.bind(this); - this.hide = this.hide.bind(this); - this.onEscKeyDown = this.onEscKeyDown.bind(this); - } - - componentDidMount() { - this._target = getTarget(this.props.target); - this.addTargetEvents(); - } - - componentWillUnmount() { - this.removeTargetEvents(); - } - - onMouseOverTooltip(e) { - if (this._hideTimeout) { - this.clearHideTimeout(); - } - this._showTimeout = setTimeout(this.show.bind(this, e), this.getDelay('show')); - } - - onMouseLeaveTooltip(e) { - if (this._showTimeout) { - this.clearShowTimeout(); - } - this._hideTimeout = setTimeout(this.hide.bind(this, e), this.getDelay('hide')); - } - - onMouseOverTooltipContent() { - if (this.props.autohide) { - return; - } - if (this._hideTimeout) { - this.clearHideTimeout(); - } - } - - onMouseLeaveTooltipContent(e) { - if (this.props.autohide) { - return; - } - if (this._showTimeout) { - this.clearShowTimeout(); - } - e.persist(); - this._hideTimeout = setTimeout(this.hide.bind(this, e), this.getDelay('hide')); - } - - onEscKeyDown(e) { - if (e.key === 'Escape') { - this.hide(e); - } - } - - getDelay(key) { - const { delay } = this.props; - if (typeof delay === 'object') { - return isNaN(delay[key]) ? DEFAULT_DELAYS[key] : delay[key]; - } - return delay; - } - - show(e) { - if (!this.props.isOpen) { - this.clearShowTimeout(); - this.toggle(e); - } - } - - hide(e) { - if (this.props.isOpen) { - this.clearHideTimeout(); - this.toggle(e); - } - } - - clearShowTimeout() { - clearTimeout(this._showTimeout); - this._showTimeout = undefined; - } - - clearHideTimeout() { - clearTimeout(this._hideTimeout); - this._hideTimeout = undefined; - } - - handleDocumentClick(e) { - if (e.target === this._target || this._target.contains(e.target)) { - if (this._hideTimeout) { - this.clearHideTimeout(); - } - - if (!this.props.isOpen) { - this.toggle(e); - } - } else if (this.props.isOpen && e.target.getAttribute('role') !== 'tooltip') { - if (this._showTimeout) { - this.clearShowTimeout(); - } - this._hideTimeout = setTimeout(this.hide.bind(this, e), this.getDelay('hide')); - } - } - - addTargetEvents() { - this._target.addEventListener('mouseover', this.onMouseOverTooltip, true); - this._target.addEventListener('mouseout', this.onMouseLeaveTooltip, true); - this._target.addEventListener('keydown', this.onEscKeyDown, true); - this._target.addEventListener('focusin', this.show, true); - this._target.addEventListener('focusout', this.hide, true); - - - ['click', 'touchstart'].forEach(event => - document.addEventListener(event, this.handleDocumentClick, true) - ); - } - - removeTargetEvents() { - this._target.removeEventListener('mouseover', this.onMouseOverTooltip, true); - this._target.removeEventListener('mouseout', this.onMouseLeaveTooltip, true); - this._target.addEventListener('keydown', this.onEscKeyDown, true); - this._target.addEventListener('focusin', this.show, true); - this._target.addEventListener('focusout', this.hide, true); - - ['click', 'touchstart'].forEach(event => - document.removeEventListener(event, this.handleDocumentClick, true) - ); - } - - toggle(e) { - if (this.props.disabled) { - return e && e.preventDefault(); - } - - return this.props.toggle(e); - } - - render() { - if (!this.props.isOpen) { - return null; - } - - const attributes = omit(this.props, Object.keys(propTypes)); - const classes = mapToCssModules(classNames( - 'tooltip-inner', - this.props.innerClassName - ), this.props.cssModule); - - const popperClasses = mapToCssModules(classNames( - 'tooltip', - 'show', - this.props.className - ), this.props.cssModule); - - return ( - -
    - - ); - } -} +const Tooltip = (props) => { + const popperClasses = classNames( + 'tooltip', + 'show' + ); + + const classes = classNames( + 'tooltip-inner', + props.innerClassName + ); + + + return ( + + ); +}; Tooltip.propTypes = propTypes; Tooltip.defaultProps = defaultProps; + export default Tooltip; diff --git a/src/TooltipPopoverWrapper.js b/src/TooltipPopoverWrapper.js new file mode 100644 index 000000000..21a398e0d --- /dev/null +++ b/src/TooltipPopoverWrapper.js @@ -0,0 +1,363 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import PopperContent from './PopperContent'; +import { + getTarget, + targetPropType, + omit, + PopperPlacements, + mapToCssModules, + DOMElement +} from './utils'; + +export const propTypes = { + placement: PropTypes.oneOf(PopperPlacements), + target: targetPropType.isRequired, + container: targetPropType, + isOpen: PropTypes.bool, + disabled: PropTypes.bool, + hideArrow: PropTypes.bool, + boundariesElement: PropTypes.oneOfType([PropTypes.string, DOMElement]), + className: PropTypes.string, + innerClassName: PropTypes.string, + arrowClassName: PropTypes.string, + popperClassName: PropTypes.string, + cssModule: PropTypes.object, + toggle: PropTypes.func, + autohide: PropTypes.bool, + placementPrefix: PropTypes.string, + delay: PropTypes.oneOfType([ + PropTypes.shape({ show: PropTypes.number, hide: PropTypes.number }), + PropTypes.number + ]), + modifiers: PropTypes.object, + offset: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + innerRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, + PropTypes.object + ]), + trigger: PropTypes.string, + fade: PropTypes.bool, +}; + +const DEFAULT_DELAYS = { + show: 0, + hide: 0 +}; + +const defaultProps = { + isOpen: false, + hideArrow: false, + autohide: false, + delay: DEFAULT_DELAYS, + toggle: function () {}, + trigger: 'click', + fade: true, +}; + +function isInDOMSubtree(element, subtreeRoot) { + return subtreeRoot && (element === subtreeRoot || subtreeRoot.contains(element)); +} + +class TooltipPopoverWrapper extends React.Component { + constructor(props) { + super(props); + + this._target = null; + this.addTargetEvents = this.addTargetEvents.bind(this); + this.handleDocumentClick = this.handleDocumentClick.bind(this); + this.removeTargetEvents = this.removeTargetEvents.bind(this); + this.toggle = this.toggle.bind(this); + this.showWithDelay = this.showWithDelay.bind(this); + this.hideWithDelay = this.hideWithDelay.bind(this); + this.onMouseOverTooltipContent = this.onMouseOverTooltipContent.bind(this); + this.onMouseLeaveTooltipContent = this.onMouseLeaveTooltipContent.bind( + this + ); + this.show = this.show.bind(this); + this.hide = this.hide.bind(this); + this.onEscKeyDown = this.onEscKeyDown.bind(this); + this.getRef = this.getRef.bind(this); + this.onClosed = this.onClosed.bind(this); + this.state = { isOpen: props.isOpen }; + } + + componentDidMount() { + this.updateTarget(); + } + + componentWillUnmount() { + this.removeTargetEvents(); + } + + static getDerivedStateFromProps(props, state) { + if (props.isOpen && !state.isOpen) { + return { isOpen: props.isOpen }; + } + else return null; + } + + onMouseOverTooltipContent() { + if (this.props.trigger.indexOf('hover') > -1 && !this.props.autohide) { + if (this._hideTimeout) { + this.clearHideTimeout(); + } + if (this.state.isOpen && !this.props.isOpen) { + this.toggle(); + } + } + } + + onMouseLeaveTooltipContent(e) { + if (this.props.trigger.indexOf('hover') > -1 && !this.props.autohide) { + if (this._showTimeout) { + this.clearShowTimeout(); + } + e.persist(); + this._hideTimeout = setTimeout( + this.hide.bind(this, e), + this.getDelay('hide') + ); + } + } + + onEscKeyDown(e) { + if (e.key === 'Escape') { + this.hide(e); + } + } + + getRef(ref) { + const { innerRef } = this.props; + if (innerRef) { + if (typeof innerRef === 'function') { + innerRef(ref); + } else if (typeof innerRef === 'object') { + innerRef.current = ref; + } + } + this._popover = ref; + } + + getDelay(key) { + const { delay } = this.props; + if (typeof delay === 'object') { + return isNaN(delay[key]) ? DEFAULT_DELAYS[key] : delay[key]; + } + return delay; + } + + show(e) { + if (!this.props.isOpen) { + this.clearShowTimeout(); + this.toggle(e); + } + } + + showWithDelay(e) { + if (this._hideTimeout) { + this.clearHideTimeout(); + } + this._showTimeout = setTimeout( + this.show.bind(this, e), + this.getDelay('show') + ); + } + hide(e) { + if (this.props.isOpen) { + this.clearHideTimeout(); + this.toggle(e); + } + } + + hideWithDelay(e) { + if (this._showTimeout) { + this.clearShowTimeout(); + } + this._hideTimeout = setTimeout( + this.hide.bind(this, e), + this.getDelay('hide') + ); + } + + + clearShowTimeout() { + clearTimeout(this._showTimeout); + this._showTimeout = undefined; + } + + clearHideTimeout() { + clearTimeout(this._hideTimeout); + this._hideTimeout = undefined; + } + + handleDocumentClick(e) { + const triggers = this.props.trigger.split(' '); + + if (triggers.indexOf('legacy') > -1 && (this.props.isOpen || isInDOMSubtree(e.target, this._target))) { + if (this._hideTimeout) { + this.clearHideTimeout(); + } + if (this.props.isOpen && !isInDOMSubtree(e.target, this._popover)) { + this.hideWithDelay(e); + } else { + this.showWithDelay(e); + } + } else if (triggers.indexOf('click') > -1 && isInDOMSubtree(e.target, this._target)) { + if (this._hideTimeout) { + this.clearHideTimeout(); + } + + if (!this.props.isOpen) { + this.showWithDelay(e); + } else { + this.hideWithDelay(e); + } + } + } + + addTargetEvents() { + if (this.props.trigger) { + let triggers = this.props.trigger.split(' '); + if (triggers.indexOf('manual') === -1) { + if (triggers.indexOf('click') > -1 || triggers.indexOf('legacy') > -1) { + ['click', 'touchstart'].forEach(event => + document.addEventListener(event, this.handleDocumentClick, true) + ); + } + + if (this._target) { + if (triggers.indexOf('hover') > -1) { + this._target.addEventListener( + 'mouseover', + this.showWithDelay, + true + ); + this._target.addEventListener( + 'mouseout', + this.hideWithDelay, + true + ); + } + if (triggers.indexOf('focus') > -1) { + this._target.addEventListener('focusin', this.show, true); + this._target.addEventListener('focusout', this.hide, true); + } + this._target.addEventListener('keydown', this.onEscKeyDown, true); + } + } + } + } + + removeTargetEvents() { + if (this._target) { + this._target.removeEventListener( + 'mouseover', + this.showWithDelay, + true + ); + this._target.removeEventListener( + 'mouseout', + this.hideWithDelay, + true + ); + this._target.removeEventListener('keydown', this.onEscKeyDown, true); + this._target.removeEventListener('focusin', this.show, true); + this._target.removeEventListener('focusout', this.hide, true); + } + + ['click', 'touchstart'].forEach(event => + document.removeEventListener(event, this.handleDocumentClick, true) + ); + } + + updateTarget() { + const newTarget = getTarget(this.props.target); + if (newTarget !== this._target) { + this.removeTargetEvents(); + this._target = newTarget; + this.addTargetEvents(); + } + } + + toggle(e) { + if (this.props.disabled) { + return e && e.preventDefault(); + } + + return this.props.toggle(e); + } + + onClosed() { + this.setState({ isOpen: false }); + } + + render() { + if (!this.state.isOpen) { + return null; + } + + this.updateTarget(); + + const { + className, + cssModule, + innerClassName, + target, + isOpen, + hideArrow, + boundariesElement, + placement, + placementPrefix, + arrowClassName, + popperClassName, + container, + modifiers, + offset, + fade, + } = this.props; + + const attributes = omit(this.props, Object.keys(propTypes)); + + const popperClasses = mapToCssModules(popperClassName, cssModule); + + const classes = mapToCssModules(innerClassName, cssModule); + + return ( + +
    + + ); + } +} + +TooltipPopoverWrapper.propTypes = propTypes; +TooltipPopoverWrapper.defaultProps = defaultProps; + +export default TooltipPopoverWrapper; diff --git a/src/UncontrolledButtonDropdown.js b/src/UncontrolledButtonDropdown.js index 69af7133f..93cfc2111 100644 --- a/src/UncontrolledButtonDropdown.js +++ b/src/UncontrolledButtonDropdown.js @@ -1,11 +1,15 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import ButtonDropdown from './ButtonDropdown'; +import { omit } from './utils'; + +const omitKeys = ['defaultOpen']; export default class UncontrolledButtonDropdown extends Component { constructor(props) { super(props); - this.state = { isOpen: false }; + this.state = { isOpen: props.defaultOpen || false }; this.toggle = this.toggle.bind(this); } @@ -14,6 +18,11 @@ export default class UncontrolledButtonDropdown extends Component { } render() { - return ; + return ; } } + +UncontrolledButtonDropdown.propTypes = { + defaultOpen: PropTypes.bool, + ...ButtonDropdown.propTypes +}; diff --git a/src/UncontrolledCarousel.js b/src/UncontrolledCarousel.js index 22777c6b8..8fcd8ba87 100644 --- a/src/UncontrolledCarousel.js +++ b/src/UncontrolledCarousel.js @@ -11,6 +11,7 @@ const propTypes = { indicators: PropTypes.bool, controls: PropTypes.bool, autoPlay: PropTypes.bool, + defaultActiveIndex: PropTypes.number, activeIndex: PropTypes.number, next: PropTypes.func, previous: PropTypes.func, @@ -21,7 +22,7 @@ class UncontrolledCarousel extends Component { constructor(props) { super(props); this.animating = false; - this.state = { activeIndex: 0 }; + this.state = { activeIndex: props.defaultActiveIndex || 0 }; this.next = this.next.bind(this); this.previous = this.previous.bind(this); this.goToIndex = this.goToIndex.bind(this); @@ -55,7 +56,7 @@ class UncontrolledCarousel extends Component { } render() { - const { autoPlay, indicators, controls, items, goToIndex, ...props } = this.props; + const { defaultActiveIndex, autoPlay, indicators, controls, items, goToIndex, ...props } = this.props; const { activeIndex } = this.state; const slides = items.map((item) => { diff --git a/src/UncontrolledCollapse.js b/src/UncontrolledCollapse.js index 2d18e72b4..c5c77a9a9 100644 --- a/src/UncontrolledCollapse.js +++ b/src/UncontrolledCollapse.js @@ -1,9 +1,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Collapse from './Collapse'; -import { findDOMElements, defaultToggleEvents, addMultipleEventListeners } from './utils'; +import { omit, findDOMElements, defaultToggleEvents, addMultipleEventListeners } from './utils'; + +const omitKeys = ['toggleEvents', 'defaultOpen']; const propTypes = { + defaultOpen: PropTypes.bool, toggler: PropTypes.string.isRequired, toggleEvents: PropTypes.arrayOf(PropTypes.string) }; @@ -20,9 +23,7 @@ class UncontrolledCollapse extends Component { this.removeEventListeners = null; this.toggle = this.toggle.bind(this); - this.state = { - isOpen: false - }; + this.state = { isOpen: props.defaultOpen || false }; } componentDidMount() { @@ -48,8 +49,7 @@ class UncontrolledCollapse extends Component { } render() { - const { toggleEvents, ...rest } = this.props; - return ; + return ; } } diff --git a/src/UncontrolledDropdown.js b/src/UncontrolledDropdown.js index 26646b718..d091af1e0 100644 --- a/src/UncontrolledDropdown.js +++ b/src/UncontrolledDropdown.js @@ -1,11 +1,15 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import Dropdown from './Dropdown'; +import { omit } from './utils'; + +const omitKeys = ['defaultOpen']; export default class UncontrolledDropdown extends Component { constructor(props) { super(props); - this.state = { isOpen: false }; + this.state = { isOpen: props.defaultOpen || false }; this.toggle = this.toggle.bind(this); } @@ -14,6 +18,11 @@ export default class UncontrolledDropdown extends Component { } render() { - return ; + return ; } } + +UncontrolledDropdown.propTypes = { + defaultOpen: PropTypes.bool, + ...Dropdown.propTypes +}; diff --git a/src/UncontrolledPopover.js b/src/UncontrolledPopover.js new file mode 100644 index 000000000..c491db0a4 --- /dev/null +++ b/src/UncontrolledPopover.js @@ -0,0 +1,28 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import Popover from './Popover'; +import { omit } from './utils'; + +const omitKeys = ['defaultOpen']; + +export default class UncontrolledPopover extends Component { + constructor(props) { + super(props); + + this.state = { isOpen: props.defaultOpen || false }; + this.toggle = this.toggle.bind(this); + } + + toggle() { + this.setState({ isOpen: !this.state.isOpen }); + } + + render() { + return ; + } +} + +UncontrolledPopover.propTypes = { + defaultOpen: PropTypes.bool, + ...Popover.propTypes +}; diff --git a/src/UncontrolledTooltip.js b/src/UncontrolledTooltip.js index c2b9c1f55..d8c95dcaa 100644 --- a/src/UncontrolledTooltip.js +++ b/src/UncontrolledTooltip.js @@ -1,11 +1,15 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import Tooltip from './Tooltip'; +import { omit } from './utils'; + +const omitKeys = ['defaultOpen']; export default class UncontrolledTooltip extends Component { constructor(props) { super(props); - this.state = { isOpen: false }; + this.state = { isOpen: props.defaultOpen || false }; this.toggle = this.toggle.bind(this); } @@ -14,6 +18,11 @@ export default class UncontrolledTooltip extends Component { } render() { - return ; + return ; } } + +UncontrolledTooltip.propTypes = { + defaultOpen: PropTypes.bool, + ...Tooltip.propTypes +}; diff --git a/src/__tests__/Button.spec.js b/src/__tests__/Button.spec.js index b50d4b04c..208a2b015 100644 --- a/src/__tests__/Button.spec.js +++ b/src/__tests__/Button.spec.js @@ -97,6 +97,27 @@ describe('Button', () => { expect(block.hasClass('btn-block')).toBe(true); }); + it('should render close icon utility with default props', () => { + const times = '×'; // unicode: U+00D7 MULTIPLICATION SIGN + const expectedInnerHTML = ``; + + const wrapper = shallow(); + + expect(wrapper.contains(testChild)); + }); + describe('onClick', () => { it('calls props.onClick if it exists', () => { const onClick = jest.fn(); diff --git a/src/__tests__/CardSubtitle.spec.js b/src/__tests__/CardSubtitle.spec.js index 7dd84c0d9..9d9d98b22 100644 --- a/src/__tests__/CardSubtitle.spec.js +++ b/src/__tests__/CardSubtitle.spec.js @@ -24,4 +24,10 @@ describe('CardSubtitle', () => { expect(wrapper.hasClass('card-subtitle')).toBe(true); expect(wrapper.find('h3').length).toBe(1); }); + + it('should render a "div" tag by default', () => { + const wrapper = shallow(Yo!); + + expect(wrapper.find('div').length).toBe(1); + }); }); diff --git a/src/__tests__/CardTitle.spec.js b/src/__tests__/CardTitle.spec.js index fc098a590..82e9f9a3c 100644 --- a/src/__tests__/CardTitle.spec.js +++ b/src/__tests__/CardTitle.spec.js @@ -25,9 +25,9 @@ describe('CardTitle', () => { expect(wrapper.find('h1').length).toBe(1); }); - it('should render a "h5" tag by default', () => { + it('should render a "div" tag by default', () => { const wrapper = shallow(Yo!); - expect(wrapper.find('h5').length).toBe(1); + expect(wrapper.find('div').length).toBe(1); }); }); diff --git a/src/__tests__/CustomInput.spec.js b/src/__tests__/CustomInput.spec.js index 9a05a2212..5308b6b24 100644 --- a/src/__tests__/CustomInput.spec.js +++ b/src/__tests__/CustomInput.spec.js @@ -103,6 +103,56 @@ describe('Custom Inputs', () => { }); }); + describe('CustomSwitch', () => { + it('should render an optional label', () => { + const checkbox = mount(); + expect(checkbox.find('label').text()).toBe('Yo!'); + }); + + it('should render children in the outer div', () => { + const checkbox = shallow(); + expect(checkbox.type()).toBe('div'); + }); + + it('should render an input with the type of checkbox', () => { + const checkbox = mount(); + expect(checkbox.find('input').prop('type')).toBe('checkbox'); + }); + + it('should pass id to both the input and label nodes', () => { + const checkbox = mount(); + expect(checkbox.find('input').prop('id')).toBe('yo'); + expect(checkbox.find('label').prop('htmlFor')).toBe('yo'); + }); + + it('should pass classNames to the outer div', () => { + const checkbox = mount(); + expect(checkbox.find('.custom-control').prop('className').indexOf('yo') > -1).toBeTruthy(); + }); + + it('should add class is-invalid when invalid is true', () => { + const checkbox = mount(); + expect(checkbox.find('input').prop('className').indexOf('is-invalid') > -1).toBeTruthy(); + }); + + it('should add class is-valid when valid is true', () => { + const checkbox = mount(); + expect(checkbox.find('input').prop('className').indexOf('is-valid') > -1).toBeTruthy(); + }); + + it('should pass all other props to the input node', () => { + const checkbox = mount(); + expect(checkbox.find('input').prop('data-testprop')).toBe('yo'); + }); + + it('should reference innerRef to the input node', () => { + const ref = React.createRef(); + mount(); + expect(ref.current).not.toBeNull(); + expect(ref.current).toBeInstanceOf(HTMLInputElement); + }); + }); + describe('CustomSelect', () => { it('should render children in the outer div', () => { const select = shallow(); diff --git a/src/__tests__/Dropdown.spec.js b/src/__tests__/Dropdown.spec.js index b9b2b44d4..2b3ef7ff9 100644 --- a/src/__tests__/Dropdown.spec.js +++ b/src/__tests__/Dropdown.spec.js @@ -268,21 +268,21 @@ describe('Dropdown', () => { Toggle - Test + Test , { attachTo: element }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - wrapper.simulate('keydown', { which: keyCodes.esc }); + wrapper.find('#test').hostNodes().simulate('keydown', { which: keyCodes.esc }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); - it('should call toggle on ESC keydown when it isOpen is true', () => { + it('should call toggle on down arrow keydown when it isOpen is false', () => { isOpen = false; jest.spyOn(Dropdown.prototype, 'toggle'); @@ -297,14 +297,14 @@ describe('Dropdown', () => { expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - wrapper.simulate('keydown', { which: keyCodes.esc }); + wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.down }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); - it('should call toggle on down arrow keydown when it isOpen is false', () => { + it('should call toggle on up arrow keydown when it isOpen is false', () => { isOpen = false; jest.spyOn(Dropdown.prototype, 'toggle'); @@ -319,60 +319,101 @@ describe('Dropdown', () => { expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - wrapper.simulate('keydown', { which: keyCodes.down }); + wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.up }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); - it('should call toggle on up arrow keydown when it isOpen is false', () => { - isOpen = false; + it('should focus the first menuitem when toggle is triggered by enter keydown', () => { + jest.useFakeTimers(); jest.spyOn(Dropdown.prototype, 'toggle'); - + const focus = jest.fn(); const wrapper = mount( Toggle - - Test - + + Header + Disabled + Test + + Another Test - , { attachTo: element }); + , { attachTo: element } + ); + expect(focus.mock.calls.length).toBe(0); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - wrapper.simulate('keydown', { which: keyCodes.up }); + wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.enter }); + jest.runAllTimers(); + expect(focus.mock.calls.length).toBe(1); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); - it('should focus the first menu item on up arrow keydown when it isOpen is true', () => { - isOpen = true; + it('should focus the first menuitem when toggle is triggered by up arrow keydown', () => { + jest.useFakeTimers(); jest.spyOn(Dropdown.prototype, 'toggle'); const focus = jest.fn(); - const wrapper = mount( Toggle - + + Header + Disabled Test - + + Another Test - , { attachTo: element }); + , { attachTo: element } + ); + expect(focus.mock.calls.length).toBe(0); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - wrapper.simulate('keydown', { which: keyCodes.up }); + wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.up }); + jest.runAllTimers(); + + expect(focus.mock.calls.length).toBe(1); + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); + + wrapper.detach(); + }); + it('should focus the first menuitem when toggle is triggered by down arrow keydown', () => { + jest.useFakeTimers(); + jest.spyOn(Dropdown.prototype, 'toggle'); + const focus = jest.fn(); + const wrapper = mount( + + Toggle + + Header + Disabled + Test + + Another Test + + , { attachTo: element } + ); + + expect(focus.mock.calls.length).toBe(0); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + + wrapper.find('[aria-haspopup]').hostNodes().simulate('keydown', { which: keyCodes.down }); + jest.runAllTimers(); + expect(focus.mock.calls.length).toBe(1); + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); wrapper.detach(); }); - it('should focus the first menu item on down arrow keydown when it isOpen is true', () => { + it('should focus the next menuitem on down arrow keydown when isOpen is true', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); @@ -383,7 +424,7 @@ describe('Dropdown', () => { Toggle - Test + Test Test Test @@ -392,17 +433,17 @@ describe('Dropdown', () => { expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - wrapper.simulate('keydown', { which: keyCodes.down }); + wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.down }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - expect(focus1.mock.calls.length).toBe(1); - expect(focus2.mock.calls.length).toBe(0); + expect(focus1.mock.calls.length).toBe(0); + expect(focus2.mock.calls.length).toBe(1); expect(focus3.mock.calls.length).toBe(0); wrapper.detach(); }); - it('should focus the next menu item on down arrow keydown when it isOpen is true and anther item is focused', () => { + it('should focus the next menuitem on ctrl + n keydown when isOpen is true', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); @@ -422,7 +463,7 @@ describe('Dropdown', () => { expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.down }); + wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.n, ctrlKey: true }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(0); @@ -432,6 +473,36 @@ describe('Dropdown', () => { wrapper.detach(); }); + it('should focus the first menu item matching the character pressed when isOpen is true', () => { + isOpen = true; + jest.spyOn(Dropdown.prototype, 'toggle'); + const focus1 = jest.fn(); + const focus2 = jest.fn(); + const focus3 = jest.fn(); + + const wrapper = mount( + + Toggle + + Reactstrap + 4 + + Lyfe + + , { attachTo: element }); + + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + + wrapper.find('#first').hostNodes().simulate('keydown', { which: 52 }); + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + + expect(focus1.mock.calls.length).toBe(0); + expect(focus2.mock.calls.length).toBe(1); + expect(focus3.mock.calls.length).toBe(0); + + wrapper.detach(); + }); + it('should skip non-menu items focus the next menu item on down arrow keydown when it isOpen is true and anther item is focused', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); @@ -462,7 +533,7 @@ describe('Dropdown', () => { wrapper.detach(); }); - it('should focus the previous menu item on up arrow keydown when it isOpen is true and anther item is focused', () => { + it('should focus the previous menu item on up arrow keydown when isOpen is true and another item is focused', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); @@ -492,7 +563,37 @@ describe('Dropdown', () => { wrapper.detach(); }); - it('should not wrap focus with down arrow keydown', () => { + it('should focus the previous menuitem on ctrl + p keydown when isOpen is true and another item is focused', () => { + isOpen = true; + jest.spyOn(Dropdown.prototype, 'toggle'); + const focus1 = jest.fn(); + const focus2 = jest.fn(); + const focus3 = jest.fn(); + + const wrapper = mount( + + Toggle + + Test + Test + + Test + + , { attachTo: element }); + + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + + wrapper.find('#second').hostNodes().simulate('keydown', { which: keyCodes.p, ctrlKey: true }); + + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + expect(focus1.mock.calls.length).toBe(1); + expect(focus2.mock.calls.length).toBe(0); + expect(focus3.mock.calls.length).toBe(0); + + wrapper.detach(); + }); + + it('should wrap focus with down arrow keydown', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); @@ -515,14 +616,14 @@ describe('Dropdown', () => { wrapper.find('#third').hostNodes().simulate('keydown', { which: keyCodes.down }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - expect(focus1.mock.calls.length).toBe(0); + expect(focus1.mock.calls.length).toBe(1); expect(focus2.mock.calls.length).toBe(0); - expect(focus3.mock.calls.length).toBe(1); + expect(focus3.mock.calls.length).toBe(0); wrapper.detach(); }); - it('should not wrap focus with up arrow keydown', () => { + it('should wrap focus with up arrow keydown', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const focus1 = jest.fn(); @@ -544,6 +645,37 @@ describe('Dropdown', () => { wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.up }); + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + expect(focus1.mock.calls.length).toBe(0); + expect(focus2.mock.calls.length).toBe(0); + expect(focus3.mock.calls.length).toBe(1); + + wrapper.detach(); + }); + + it('should focus the 1st item on home key keyDown', () => { + isOpen = true; + jest.spyOn(Dropdown.prototype, 'toggle'); + const focus1 = jest.fn(); + const focus2 = jest.fn(); + const focus3 = jest.fn(); + + const wrapper = mount( + + Toggle + + Test + Test + Test + + Test + + , { attachTo: element }); + + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + + wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.home }); + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); expect(focus1.mock.calls.length).toBe(1); expect(focus2.mock.calls.length).toBe(0); @@ -552,6 +684,37 @@ describe('Dropdown', () => { wrapper.detach(); }); + it('should focus the last item on end key keyDown', () => { + isOpen = true; + jest.spyOn(Dropdown.prototype, 'toggle'); + const focus1 = jest.fn(); + const focus2 = jest.fn(); + const focus3 = jest.fn(); + + const wrapper = mount( + + Toggle + + Test + Test + Test + + Test + + , { attachTo: element }); + + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + + wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.end }); + + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + expect(focus1.mock.calls.length).toBe(0); + expect(focus2.mock.calls.length).toBe(0); + expect(focus3.mock.calls.length).toBe(1); + + wrapper.detach(); + }); + it('should trigger a click on links when an item is focused and space[bar] it pressed', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); @@ -578,7 +741,7 @@ describe('Dropdown', () => { wrapper.detach(); }); - it('should not trigger a click on buttons when an item is focused and space[bar] it pressed (browser does this)', () => { + it('should trigger a click on buttons when an item is focused and space[bar] it pressed (override browser defaults for focus management)', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); const click = jest.fn(); @@ -599,7 +762,7 @@ describe('Dropdown', () => { wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.space }); expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); - expect(click.mock.calls.length).toBe(0); + expect(click.mock.calls.length).toBe(1); wrapper.detach(); }); @@ -664,6 +827,32 @@ describe('Dropdown', () => { wrapper.detach(); }); + it('should toggle when isOpen is true and tab keyDown on menuitem', () => { + isOpen = true; + jest.spyOn(Dropdown.prototype, 'toggle'); + const focus = jest.fn(); + + const wrapper = mount( + + Toggle + + First + Second + + , + { attachTo: element } + ); + + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0); + + wrapper.find('#first').hostNodes().simulate('keydown', { which: keyCodes.tab }); + + expect(focus.mock.calls.length).toBe(0); + expect(Dropdown.prototype.toggle.mock.calls.length).toBe(1); + + wrapper.detach(); + }); + it('should not trigger anything when disabled', () => { isOpen = true; jest.spyOn(Dropdown.prototype, 'toggle'); diff --git a/src/__tests__/DropdownMenu.spec.js b/src/__tests__/DropdownMenu.spec.js index 57cf7de70..77e3209d5 100644 --- a/src/__tests__/DropdownMenu.spec.js +++ b/src/__tests__/DropdownMenu.spec.js @@ -92,6 +92,20 @@ describe('DropdownMenu', () => { expect(wrapper.find('.dropdown-menu').hostNodes().hasClass('dropdown-menu-right')).toBe(true); }); + it('should render down when direction is unknown on the context', () => { + isOpen = true; + direction = 'unknown'; + const wrapper = shallow( + Ello world, + { + context: { isOpen, direction, inNavbar, popperManager }, + childContextTypes: { popperManager } + } + ); + + expect(wrapper.find(Popper).prop('placement')).toBe('bottom-start'); + }); + it('should render down when direction is "down" on the context', () => { isOpen = true; const wrapper = shallow( diff --git a/src/__tests__/Input.spec.js b/src/__tests__/Input.spec.js index 91f23d3df..164e7515e 100644 --- a/src/__tests__/Input.spec.js +++ b/src/__tests__/Input.spec.js @@ -21,10 +21,10 @@ describe('Input', () => { expect(wrapper.type()).toBe('textarea'); }); - it('should render with "p" tag when plaintext prop is truthy', () => { + it('should render with "input" tag when plaintext prop is truthy', () => { const wrapper = shallow(); - expect(wrapper.type()).toBe('p'); + expect(wrapper.type()).toBe('input'); }); it('should render with "form-control-plaintext" class when plaintext prop is truthy', () => { diff --git a/src/__tests__/InputGroupAddon.spec.js b/src/__tests__/InputGroupAddon.spec.js index 11220878b..2f0e86d55 100644 --- a/src/__tests__/InputGroupAddon.spec.js +++ b/src/__tests__/InputGroupAddon.spec.js @@ -1,6 +1,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { InputGroupAddon } from '../'; +import { InputGroupAddon, InputGroupText } from '../'; describe('InputGroupAddon', () => { it('should render with "div" tag', () => { @@ -15,6 +15,19 @@ describe('InputGroupAddon', () => { expect(wrapper.text()).toBe(''); }); + it('should render children provided', () => { + const wrapper = shallow(); + + expect(wrapper.childAt(0).type()).toBe('button'); + }); + + it('should render the string provided in the child InputGroupText', () => { + const wrapper = shallow(Yo!); + + expect(wrapper.childAt(0).type()).toBe(InputGroupText); + expect(wrapper.childAt(0).prop('children')).toBe('Yo!'); + }); + it('should render with "input-group-*" classes', () => { const wrapperPrepend = shallow(Yo!); const wrapperAppend = shallow(Yo!); diff --git a/src/__tests__/Modal.spec.js b/src/__tests__/Modal.spec.js index 22585b044..8dbe5674f 100644 --- a/src/__tests__/Modal.spec.js +++ b/src/__tests__/Modal.spec.js @@ -1,6 +1,7 @@ +/* eslint-disable jsx-a11y/no-noninteractive-tabindex */ import React from 'react'; import { mount, shallow } from 'enzyme'; -import { Modal, ModalBody } from '../'; +import { Modal, ModalBody, ModalHeader, ModalFooter, Button } from '../'; describe('Modal', () => { let isOpen; @@ -489,10 +490,18 @@ describe('Modal', () => { expect(isOpen).toBe(true); expect(document.getElementsByClassName('modal').length).toBe(1); - instance.handleEscape({ keyCode: 27 }); + const escapeKeyUpEvent = { + keyCode: 27, + preventDefault: jest.fn(() => {}), + stopPropagation: jest.fn(() => {}), + }; + + instance.handleEscape(escapeKeyUpEvent); jest.runTimersToTime(300); expect(isOpen).toBe(false); + expect(escapeKeyUpEvent.preventDefault.mock.calls.length).toBe(1); + expect(escapeKeyUpEvent.stopPropagation.mock.calls.length).toBe(1); wrapper.setProps({ isOpen: isOpen @@ -563,9 +572,9 @@ describe('Modal', () => { mouseDownEvent.initEvent('mousedown', true, true); modal.dispatchEvent(mouseDownEvent); - const mouseUpEvent = document.createEvent('MouseEvents'); - mouseUpEvent.initEvent('mouseup', true, true); - modal.dispatchEvent(mouseUpEvent); + const clickEvent = document.createEvent('MouseEvents'); + clickEvent.initEvent('click', true, true); + modal.dispatchEvent(clickEvent); jest.runTimersToTime(300); @@ -657,7 +666,13 @@ describe('Modal', () => { jest.runTimersToTime(300); expect(document.getElementsByClassName('modal-dialog').length).toBe(2); - expect(document.body.className).toBe('modal-open modal-open'); + expect(document.body.className).toBe('modal-open'); + + toggleNested(); + jest.runTimersToTime(300); + expect(isOpenNested).toBe(false); + expect(document.getElementsByClassName('modal-dialog').length).toBe(2); + expect(document.body.className).toBe('modal-open'); wrapper.unmount(); expect(document.getElementsByClassName('modal-dialog').length).toBe(0); @@ -757,4 +772,63 @@ describe('Modal', () => { wrapper.setProps({ zIndex: 1 }); expect(wrapper.instance()._element.style.zIndex).toBe('1'); }); + + it('should allow focus on only focusable elements', () => { + isOpen = true; + + const wrapper = mount( + + Modal title + + Test + + test + + + + + + +