);
}
From 704a168eb334c293cfef0d566d51999f6ed46557 Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Mon, 8 Jun 2015 19:07:11 -0600
Subject: [PATCH 06/27] Release v0.24.0-alpha.1
---
CHANGELOG-alpha.md | 11 +++++++++++
package.json | 2 +-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG-alpha.md b/CHANGELOG-alpha.md
index 9c16c92cb7..ded6d073d6 100644
--- a/CHANGELOG-alpha.md
+++ b/CHANGELOG-alpha.md
@@ -1,3 +1,14 @@
+v0.24.0-alpha.1 - Tue, 09 Jun 2015 01:06:34 GMT
+-----------------------------------------------
+
+- [7211dcb](../../commit/7211dcb) [added] Add prevIcon and nextIcon props as node proptypes to Carousel
+- [5734ec3](../../commit/5734ec3) [added] Pagination component
+- [2f8c454](../../commit/2f8c454) [changed] Assert ProgressBar children can be ProgressBar only.
+- [2c46820](../../commit/2c46820) [added] `createSelectedEvent` for consistent onSelect handling
+- [c2ff9ad](../../commit/c2ff9ad) [added] property disabled on MenuItem
+
+
+
v0.24.0-alpha.0 - Tue, 02 Jun 2015 17:08:11 GMT
-----------------------------------------------
diff --git a/package.json b/package.json
index 7f8e9ddc53..9c4b05b14e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap",
- "version": "0.24.0-alpha.0",
+ "version": "0.24.0-alpha.1",
"description": "Bootstrap 3 components build with React",
"repository": {
"type": "git",
From 598b9d85820edc8eafac2f95a0d1e8bae0623485 Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Tue, 9 Jun 2015 18:16:20 -0600
Subject: [PATCH 07/27] [fixed] SafeAnchor event ordering
If we prevent default before applying the `onClick` function provided in
props then we prevent elements from using the `event.preventDefault()`
mechanics for anchors as buttons. For example in the Dropdown re-work
this prevented me from having the Dropdown work when in a Nav since the
toggle is an anchor. Yet that functionality should allow uses to prevent
the Dropdown if they want in their own `onClick` handler. This will
enable such a use case.
---
src/SafeAnchor.js | 7 ++-----
test/SafeAnchorSpec.js | 16 ++++++++++++----
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/src/SafeAnchor.js b/src/SafeAnchor.js
index 8316bbe634..ae45a9c261 100644
--- a/src/SafeAnchor.js
+++ b/src/SafeAnchor.js
@@ -1,4 +1,5 @@
import React from 'react';
+import createChainedFunction from './utils/createChainedFunction';
/**
* Note: This is intended as a stop-gap for accessibility concerns that the
@@ -16,17 +17,13 @@ export default class SafeAnchor extends React.Component {
if (this.props.href === undefined) {
event.preventDefault();
}
-
- if (this.props.onClick) {
- this.props.onClick(event);
- }
}
render() {
return (
);
}
diff --git a/test/SafeAnchorSpec.js b/test/SafeAnchorSpec.js
index 75b9d0a8de..2271c2fbbe 100644
--- a/test/SafeAnchorSpec.js
+++ b/test/SafeAnchorSpec.js
@@ -43,8 +43,12 @@ describe('SafeAnchor', function() {
it('prevents default when no href is provided', function(done) {
const handleClick = (event) => {
- event.defaultPrevented.should.be.true;
- done();
+ expect(event.isDefaultPrevented()).to.not.be.ok;
+
+ setTimeout(() => {
+ event.isDefaultPrevented().should.be.true;
+ done();
+ }, 100);
};
const instance = ReactTestUtils.renderIntoDocument();
const anchor = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'A');
@@ -54,8 +58,12 @@ describe('SafeAnchor', function() {
it('does not prevent default when href is provided', function(done) {
const handleClick = (event) => {
- expect(event.defaultPrevented).to.not.be.ok;
- done();
+ expect(event.isDefaultPrevented()).to.not.be.ok;
+
+ setTimeout(() => {
+ expect(event.isDefaultPrevented()).to.not.be.ok;
+ done();
+ });
};
const instance = ReactTestUtils.renderIntoDocument();
const anchor = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'A');
From 6e985b0537550d7123ef58bdff125da626acda2c Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Wed, 10 Jun 2015 19:28:05 -0600
Subject: [PATCH 08/27] [removed] Individual files in bower release
Resolves #693
---
tools/amd/bower.json | 3 +--
tools/amd/build.js | 16 ++--------------
tools/buildBabel.js | 8 +-------
3 files changed, 4 insertions(+), 23 deletions(-)
diff --git a/tools/amd/bower.json b/tools/amd/bower.json
index 0e3451d5c4..5c56aa2f4d 100644
--- a/tools/amd/bower.json
+++ b/tools/amd/bower.json
@@ -16,7 +16,6 @@
"**/.*"
],
"dependencies": {
- "react": ">= 0.13.0",
- "classnames": "^2.0.0"
+ "react": ">= 0.13.0"
}
}
diff --git a/tools/amd/build.js b/tools/amd/build.js
index ae47031c22..70f87c72e0 100644
--- a/tools/amd/build.js
+++ b/tools/amd/build.js
@@ -3,9 +3,7 @@ import path from 'path';
import fsp from 'fs-promise';
import { copy } from '../fs-utils';
import { exec } from '../exec';
-import generateFactories from '../generateFactories';
-import { repoRoot, srcRoot, bowerRoot } from '../constants';
-import { buildFolder } from '../buildBabel';
+import { repoRoot, bowerRoot } from '../constants';
const packagePath = path.join(repoRoot, 'package.json');
const bowerTemplate = path.join(__dirname, 'bower.json');
@@ -14,14 +12,6 @@ const bowerJson = path.join(bowerRoot, 'bower.json');
const readme = path.join(__dirname, 'README.md');
const license = path.join(repoRoot, 'LICENSE');
-const babelOptions = {
- __reactBootstrapDeprecationWarning: true,
- modules: 'amd'
-};
-
-const libDestination = path.join(bowerRoot, 'lib');
-const factoriesDestination = path.join(libDestination, 'factories');
-
function bowerConfig() {
return Promise.all([
fsp.readFile(packagePath)
@@ -37,11 +27,9 @@ export default function BuildBower() {
console.log('Building: '.cyan + 'bower module'.green);
return exec(`rimraf ${bowerRoot}`)
- .then(() => fsp.mkdirs(factoriesDestination))
+ .then(() => fsp.mkdirs(bowerRoot))
.then(() => Promise.all([
bowerConfig(),
- generateFactories(factoriesDestination, babelOptions),
- buildFolder(srcRoot, libDestination, babelOptions),
copy(readme, bowerRoot),
copy(license, bowerRoot)
]))
diff --git a/tools/buildBabel.js b/tools/buildBabel.js
index 7811d21bb4..e2ef5c66e2 100644
--- a/tools/buildBabel.js
+++ b/tools/buildBabel.js
@@ -12,13 +12,7 @@ export function buildContent(content, filename, destination, babelOptions={}) {
}
export function buildFile(filename, destination, babelOptions={}) {
- let content = fs.readFileSync(filename, {encoding: 'utf8'});
- if (babelOptions.__reactBootstrapDeprecationWarning) {
- content = `console.warn('This file is deprecated, and will be removed in v0.24.0. Use react-bootstrap.js or react-bootstrap.min.js instead.');
-console.warn('You can read more about it at https://github.com/react-bootstrap/react-bootstrap/issues/693');
-${content}`;
- }
-
+ const content = fs.readFileSync(filename, {encoding: 'utf8'});
if(babelUtil.canCompile(filename)) {
// Get file basename without the extension (in case not .js)
let outputName = path.basename(filename, path.extname(filename));
From 36358d6176ae9be1d4266fce20ae308602939736 Mon Sep 17 00:00:00 2001
From: Matthew Smith
Date: Fri, 12 Jun 2015 15:52:46 -0600
Subject: [PATCH 09/27] Release v0.24.0-alpha.2
---
CHANGELOG-alpha.md | 14 ++++++++++++++
package.json | 2 +-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG-alpha.md b/CHANGELOG-alpha.md
index ded6d073d6..62db670361 100644
--- a/CHANGELOG-alpha.md
+++ b/CHANGELOG-alpha.md
@@ -1,3 +1,17 @@
+v0.24.0-alpha.2 - Fri, 12 Jun 2015 21:52:11 GMT
+-----------------------------------------------
+
+- [9ca26e9](../../commit/9ca26e9) [added] contains "polyfill" to domUtils
+- [6e985b0](../../commit/6e985b0) [removed] Individual files in bower release
+- [3a254a1](../../commit/3a254a1) [added] Deprecation warning for individual file use in the Bower release
+- [73c7705](../../commit/73c7705) [changed] Update chai. Dev dependency.
+- [3ca90c7](../../commit/3ca90c7) [changed] Update karma-sinon-chai. Dev dependency.
+- [cc4e820](../../commit/cc4e820) [changed] Update fs-extra. Dev dependency.
+- [598b9d8](../../commit/598b9d8) [fixed] SafeAnchor event ordering
+- [beaa1fa](../../commit/beaa1fa) [changed] `PaginationButton` to use `SafeAnchor`
+
+
+
v0.24.0-alpha.1 - Tue, 09 Jun 2015 01:06:34 GMT
-----------------------------------------------
diff --git a/package.json b/package.json
index d61be1e689..b075ef7e69 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap",
- "version": "0.24.0-alpha.1",
+ "version": "0.24.0-alpha.2",
"description": "Bootstrap 3 components build with React",
"repository": {
"type": "git",
From fafe46f0037814773b5bce49f1b7b2bdf3fb3f2b Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Sat, 13 Jun 2015 12:18:33 -0400
Subject: [PATCH 10/27] [changed] Use named exports in index files
Fixes #350
---
.babelrc | 4 +-
docs/src/GettingStartedPage.js | 7 +-
docs/src/ReactPlayground.js | 10 +-
src/FormControls/index.js | 6 +-
src/Input.js | 2 +-
src/index.js | 170 +++++++++++----------------------
src/utils/index.js | 18 +---
test/FormControlsSpec.js | 2 +-
tools/public-components.js | 2 +-
9 files changed, 77 insertions(+), 144 deletions(-)
diff --git a/.babelrc b/.babelrc
index 8f85e598f6..3162e1a075 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,3 @@
{
- "optional": [
- "es7.objectRestSpread"
- ]
+ "stage": 1
}
diff --git a/docs/src/GettingStartedPage.js b/docs/src/GettingStartedPage.js
index f19af4f741..883dff20e9 100644
--- a/docs/src/GettingStartedPage.js
+++ b/docs/src/GettingStartedPage.js
@@ -43,7 +43,12 @@ $ npm install react-bootstrap`
codeText={
`var Alert = require('react-bootstrap/lib/Alert');
// or
-var Alert = require('react-bootstrap').Alert;`
+var Alert = require('react-bootstrap').Alert;
+
+// with ES6 modules
+import Alert from 'react-bootstrap/lib/Alert';
+// or
+import {Alert} from 'react-bootstrap';`
}
/>
diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js
index 0b33a304a3..743bb65979 100644
--- a/docs/src/ReactPlayground.js
+++ b/docs/src/ReactPlayground.js
@@ -3,10 +3,10 @@ import * as modClassNames from 'classnames';
import * as modAccordion from '../../src/Accordion';
import * as modAlert from '../../src/Alert';
import * as modBadge from '../../src/Badge';
-import * as modmodButton from '../../src/Button';
+import * as modButton from '../../src/Button';
import * as modButtonGroup from '../../src/ButtonGroup';
import * as modButtonInput from '../../src/ButtonInput';
-import * as modmodButtonToolbar from '../../src/ButtonToolbar';
+import * as modButtonToolbar from '../../src/ButtonToolbar';
import * as modCollapsibleNav from '../../src/CollapsibleNav';
import * as modCollapsibleMixin from '../../src/CollapsibleMixin';
import * as modCarousel from '../../src/Carousel';
@@ -56,17 +56,17 @@ const React = modReact.default;
const Accordion = modAccordion.default;
const Alert = modAlert.default;
const Badge = modBadge.default;
-const Button = modmodButton.default;
+const Button = modButton.default;
const ButtonGroup = modButtonGroup.default;
const ButtonInput = modButtonInput.default;
-const ButtonToolbar = modmodButtonToolbar.default;
+const ButtonToolbar = modButtonToolbar.default;
const CollapsibleNav = modCollapsibleNav.default;
const CollapsibleMixin = modCollapsibleMixin.default;
const Carousel = modCarousel.default;
const CarouselItem = modCarouselItem.default;
const Col = modCol.default;
const DropdownButton = modDropdownButton.default;
-const FormControls = modFormControls.default;
+const FormControls = modFormControls;
const Glyphicon = modGlyphicon.default;
const Grid = modGrid.default;
const Input = modInput.default;
diff --git a/src/FormControls/index.js b/src/FormControls/index.js
index 5a7c16286e..ef0f49c381 100644
--- a/src/FormControls/index.js
+++ b/src/FormControls/index.js
@@ -1,5 +1 @@
-import Static from './Static';
-
-export default {
- Static
-};
+export Static from './Static';
diff --git a/src/Input.js b/src/Input.js
index f385c5607d..d08eac7b35 100644
--- a/src/Input.js
+++ b/src/Input.js
@@ -1,6 +1,6 @@
import React from 'react';
import InputBase from './InputBase';
-import FormControls from './FormControls';
+import * as FormControls from './FormControls';
import deprecationWarning from './utils/deprecationWarning';
class Input extends InputBase {
diff --git a/src/index.js b/src/index.js
index 814c690572..5167ae7474 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,115 +1,57 @@
-import Accordion from './Accordion';
-import Affix from './Affix';
-import AffixMixin from './AffixMixin';
-import Alert from './Alert';
-import BootstrapMixin from './BootstrapMixin';
-import Badge from './Badge';
-import Button from './Button';
-import ButtonGroup from './ButtonGroup';
-import ButtonInput from './ButtonInput';
-import ButtonToolbar from './ButtonToolbar';
-import CollapsibleNav from './CollapsibleNav';
-import Carousel from './Carousel';
-import CarouselItem from './CarouselItem';
-import Col from './Col';
-import CollapsibleMixin from './CollapsibleMixin';
-import DropdownButton from './DropdownButton';
-import DropdownMenu from './DropdownMenu';
-import DropdownStateMixin from './DropdownStateMixin';
-import FadeMixin from './FadeMixin';
-import FormControls from './FormControls';
-import Glyphicon from './Glyphicon';
-import Grid from './Grid';
-import Input from './Input';
-import Interpolate from './Interpolate';
-import Jumbotron from './Jumbotron';
-import Label from './Label';
-import ListGroup from './ListGroup';
-import ListGroupItem from './ListGroupItem';
-import MenuItem from './MenuItem';
-import Modal from './Modal';
-import Nav from './Nav';
-import Navbar from './Navbar';
-import NavItem from './NavItem';
-import ModalTrigger from './ModalTrigger';
-import OverlayTrigger from './OverlayTrigger';
-import OverlayMixin from './OverlayMixin';
-import PageHeader from './PageHeader';
-import Pagination from './Pagination';
-import Panel from './Panel';
-import PanelGroup from './PanelGroup';
-import PageItem from './PageItem';
-import Pager from './Pager';
-import Popover from './Popover';
-import ProgressBar from './ProgressBar';
-import Row from './Row';
-import SafeAnchor from './SafeAnchor';
-import SplitButton from './SplitButton';
-import SubNav from './SubNav';
-import TabbedArea from './TabbedArea';
-import Table from './Table';
-import TabPane from './TabPane';
-import Thumbnail from './Thumbnail';
-import Tooltip from './Tooltip';
-import utils from './utils';
-import Well from './Well';
-import styleMaps from './styleMaps';
+export Accordion from './Accordion';
+export Affix from './Affix';
+export AffixMixin from './AffixMixin';
+export Alert from './Alert';
+export Badge from './Badge';
+export BootstrapMixin from './BootstrapMixin';
+export Button from './Button';
+export ButtonGroup from './ButtonGroup';
+export ButtonInput from './ButtonInput';
+export ButtonToolbar from './ButtonToolbar';
+export Carousel from './Carousel';
+export CarouselItem from './CarouselItem';
+export Col from './Col';
+export CollapsibleMixin from './CollapsibleMixin';
+export CollapsibleNav from './CollapsibleNav';
+export DropdownButton from './DropdownButton';
+export DropdownMenu from './DropdownMenu';
+export DropdownStateMixin from './DropdownStateMixin';
+export FadeMixin from './FadeMixin';
+export Glyphicon from './Glyphicon';
+export Grid from './Grid';
+export Input from './Input';
+export Interpolate from './Interpolate';
+export Jumbotron from './Jumbotron';
+export Label from './Label';
+export ListGroup from './ListGroup';
+export ListGroupItem from './ListGroupItem';
+export MenuItem from './MenuItem';
+export Modal from './Modal';
+export ModalTrigger from './ModalTrigger';
+export Nav from './Nav';
+export Navbar from './Navbar';
+export NavItem from './NavItem';
+export OverlayMixin from './OverlayMixin';
+export OverlayTrigger from './OverlayTrigger';
+export PageHeader from './PageHeader';
+export PageItem from './PageItem';
+export Pager from './Pager';
+export Pagination from './Pagination';
+export Panel from './Panel';
+export PanelGroup from './PanelGroup';
+export Popover from './Popover';
+export ProgressBar from './ProgressBar';
+export Row from './Row';
+export SafeAnchor from './SafeAnchor';
+export SplitButton from './SplitButton';
+export styleMaps from './styleMaps';
+export SubNav from './SubNav';
+export TabbedArea from './TabbedArea';
+export Table from './Table';
+export TabPane from './TabPane';
+export Thumbnail from './Thumbnail';
+export Tooltip from './Tooltip';
+export Well from './Well';
-export default {
- Accordion,
- Affix,
- AffixMixin,
- Alert,
- BootstrapMixin,
- Badge,
- Button,
- ButtonGroup,
- ButtonInput,
- ButtonToolbar,
- CollapsibleNav,
- Carousel,
- CarouselItem,
- Col,
- CollapsibleMixin,
- DropdownButton,
- DropdownMenu,
- DropdownStateMixin,
- FadeMixin,
- FormControls,
- Glyphicon,
- Grid,
- Input,
- Interpolate,
- Jumbotron,
- Label,
- ListGroup,
- ListGroupItem,
- MenuItem,
- Modal,
- Nav,
- Navbar,
- NavItem,
- ModalTrigger,
- OverlayTrigger,
- OverlayMixin,
- PageHeader,
- Panel,
- PanelGroup,
- PageItem,
- Pager,
- Pagination,
- Popover,
- ProgressBar,
- Row,
- SafeAnchor,
- SplitButton,
- SubNav,
- TabbedArea,
- Table,
- TabPane,
- Thumbnail,
- Tooltip,
- utils,
- Well,
- styleMaps
-};
+export * as FormControls from './FormControls';
+export * as utils from './utils';
diff --git a/src/utils/index.js b/src/utils/index.js
index aad9066a10..f024432a7b 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -1,13 +1,5 @@
-import childrenValueInputValidation from './childrenValueInputValidation';
-import createChainedFunction from './createChainedFunction';
-import CustomPropTypes from './CustomPropTypes';
-import domUtils from './domUtils';
-import ValidComponentChildren from './ValidComponentChildren';
-
-export default {
- childrenValueInputValidation,
- createChainedFunction,
- CustomPropTypes,
- domUtils,
- ValidComponentChildren
-};
+export childrenValueInputValidation from './childrenValueInputValidation';
+export createChainedFunction from './createChainedFunction';
+export CustomPropTypes from './CustomPropTypes';
+export domUtils from './domUtils';
+export ValidComponentChildren from './ValidComponentChildren';
diff --git a/test/FormControlsSpec.js b/test/FormControlsSpec.js
index cc56ecd618..31c9941d30 100644
--- a/test/FormControlsSpec.js
+++ b/test/FormControlsSpec.js
@@ -1,6 +1,6 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
-import FormControls from '../src/FormControls';
+import * as FormControls from '../src/FormControls';
describe('Form Controls', function () {
describe('Static', function () {
diff --git a/tools/public-components.js b/tools/public-components.js
index a030df7253..67fc67f0c7 100644
--- a/tools/public-components.js
+++ b/tools/public-components.js
@@ -1,5 +1,5 @@
import React from 'react';
-import index from '../src/index';
+import * as index from '../src/index';
let components = [];
Object.keys(index).forEach(function (item) {
From fbbb3440c0ef409613ace104791b914ca6ca61ec Mon Sep 17 00:00:00 2001
From: AlexKVal
Date: Sat, 13 Jun 2015 23:21:33 +0300
Subject: [PATCH 11/27] [fixed] bower template.
Removed "classnames" from dependencies because webpack bundles it.
---
tools/amd/bower.json | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/tools/amd/bower.json b/tools/amd/bower.json
index 5c56aa2f4d..5d22b3626e 100644
--- a/tools/amd/bower.json
+++ b/tools/amd/bower.json
@@ -8,14 +8,12 @@
"react-bootstrap.js"
],
"keywords": [
- "react",
- "react-component",
- "bootstrap"
+ <%= _.map(pkg.keywords, function(keyword) { return '"' + keyword + '"' }).join(',\n ')%>
],
"ignore": [
"**/.*"
],
"dependencies": {
- "react": ">= 0.13.0"
+ "react": "<%= pkg.peerDependencies.react %>"
}
}
From b67081b81d7c74edce6d341f8f54a880380d65b8 Mon Sep 17 00:00:00 2001
From: Jimmy Jia
Date: Sun, 14 Jun 2015 15:21:13 -0400
Subject: [PATCH 12/27] [fixed] rootClose behavior on replaced elements
Fixes #802
---
src/OverlayMixin.js | 8 ++++--
src/RootCloseWrapper.js | 31 ++++++++++++++++----
test/OverlayTriggerSpec.js | 59 ++++++++++++++++++++++++++++++++++++--
3 files changed, 88 insertions(+), 10 deletions(-)
diff --git a/src/OverlayMixin.js b/src/OverlayMixin.js
index 76524ed61c..f8874a8828 100644
--- a/src/OverlayMixin.js
+++ b/src/OverlayMixin.js
@@ -37,7 +37,7 @@ export default {
let overlay = this.renderOverlay();
- // Save reference to help testing
+ // Save reference for future access.
if (overlay !== null) {
this._overlayInstance = React.render(overlay, this._overlayTarget);
} else {
@@ -57,7 +57,11 @@ export default {
}
if (this._overlayInstance) {
- return React.findDOMNode(this._overlayInstance);
+ if (this._overlayInstance.getWrappedDOMNode) {
+ return this._overlayInstance.getWrappedDOMNode();
+ } else {
+ return React.findDOMNode(this._overlayInstance);
+ }
}
return null;
diff --git a/src/RootCloseWrapper.js b/src/RootCloseWrapper.js
index 146c688ab9..35fecab769 100644
--- a/src/RootCloseWrapper.js
+++ b/src/RootCloseWrapper.js
@@ -4,6 +4,15 @@ import EventListener from './utils/EventListener';
// TODO: Merge this logic with dropdown logic once #526 is done.
+// TODO: Consider using an ES6 symbol here, once we use babel-runtime.
+const CLICK_WAS_INSIDE = '__click_was_inside';
+
+function suppressRootClose(event) {
+ // Tag the native event to prevent the root close logic on document click.
+ // This seems safer than using event.nativeEvent.stopImmediatePropagation(),
+ // which is only supported in IE >= 9.
+ event.nativeEvent[CLICK_WAS_INSIDE] = true;
+}
export default class RootCloseWrapper extends React.Component {
constructor(props) {
@@ -23,10 +32,8 @@ export default class RootCloseWrapper extends React.Component {
}
handleDocumentClick(e) {
- // If the click originated from within this component, don't do anything.
- // e.srcElement is required for IE8 as e.target is undefined
- let target = e.target || e.srcElement;
- if (domUtils.contains(React.findDOMNode(this), target)) {
+ // This is now the native event.
+ if (e[CLICK_WAS_INSIDE]) {
return;
}
@@ -54,7 +61,21 @@ export default class RootCloseWrapper extends React.Component {
}
render() {
- return React.Children.only(this.props.children);
+ // Wrap the child in a new element, so the child won't have to handle
+ // potentially combining multiple onClick listeners.
+ return (
+
+ {React.Children.only(this.props.children)}
+
+ );
+ }
+
+ getWrappedDOMNode() {
+ // We can't use a ref to identify the wrapped child, since we might be
+ // stealing the ref from the owner, but we know exactly the DOM structure
+ // that will be rendered, so we can just do this to get the child's DOM
+ // node for doing size calculations in OverlayMixin.
+ return React.findDOMNode(this).children[0];
}
componentWillUnmount() {
diff --git a/test/OverlayTriggerSpec.js b/test/OverlayTriggerSpec.js
index 756cb4a46d..75ae2fcf40 100644
--- a/test/OverlayTriggerSpec.js
+++ b/test/OverlayTriggerSpec.js
@@ -236,13 +236,66 @@ describe('OverlayTrigger', function() {
});
it('Should have correct isOverlayShown state', function () {
- const event = document.createEvent('HTMLEvents');
- event.initEvent('click', true, true);
- document.documentElement.dispatchEvent(event);
+ document.documentElement.click();
+ // Need to click this way for it to propagate to document element.
instance.state.isOverlayShown.should.equal(testCase.shownAfterClick);
});
});
});
+
+ describe('replaced overlay', function () {
+ let instance;
+
+ beforeEach(function () {
+ class ReplacedOverlay extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClick = this.handleClick.bind(this);
+ this.state = {replaced: false};
+ }
+
+ handleClick() {
+ this.setState({replaced: true});
+ }
+
+ render() {
+ if (this.state.replaced) {
+ return (
+
Ipsum molestiae natus adipisci modi eligendi? Debitis amet quae unde commodi aspernatur enim, consectetur. Cumque deleniti temporibus ipsam atque a dolores quisquam quisquam adipisci possimus laboriosam. Quibusdam facilis doloribus debitis! Sit quasi quod accusamus eos quod. Ab quos consequuntur eaque quo rem!
Mollitia reiciendis porro quo magni incidunt dolore amet atque facilis ipsum deleniti rem! Dolores debitis voluptatibus ipsum dicta. Dolor quod amet ab sint esse distinctio tenetur. Veritatis laudantium quibusdam quidem corporis architecto veritatis. Ex facilis minima beatae sunt perspiciatis placeat. Quasi corporis
@@ -19,10 +22,10 @@ const MyModal = React.createClass({
magni delectus maxime. Sit odit provident vel magnam quod. Possimus eligendi non corrupti tenetur culpa accusantium quod quis. Voluptatum quaerat animi dolore maiores molestias voluptate? Necessitatibus illo omnis laborum hic enim minima! Similique. Dolor voluptatum reprehenderit nihil adipisci aperiam voluptatem soluta
magnam accusamus iste incidunt tempore consequatur illo illo odit. Asperiores nesciunt iusto nemo animi ratione. Sunt odit similique doloribus temporibus reiciendis! Ullam. Dolor dolores veniam animi sequi dolores molestias voluptatem iure velit. Elit dolore quaerat incidunt enim aut distinctio. Ratione molestiae laboriosam
similique laboriosam eum et nemo expedita. Consequuntur perspiciatis cumque dolorem.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
-
-
-
-
+
+
+
+
);
}
});
-const overlayTriggerInstance = (
-
- }>
-
-
- }>
-
-
-
-);
+const App = React.createClass({
+ getInitialState(){
+ return { smShow: false, lgShow: false };
+ },
+ render(){
+ let smClose = e => this.setState({ smShow: false });
+ let lgClose = e => this.setState({ lgShow: false });
-React.render(overlayTriggerInstance, mountNode);
+ return (
+
+
+
+
+
+
+
+ );
+ }
+});
+
+React.render(, mountNode);
diff --git a/docs/examples/ModalOverlayMixin.js b/docs/examples/ModalOverlayMixin.js
deleted file mode 100644
index 3a47c75b33..0000000000
--- a/docs/examples/ModalOverlayMixin.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// Our custom component is managing whether the Modal is visible
-const CustomModalTrigger = React.createClass({
- mixins: [OverlayMixin],
-
- getInitialState() {
- return {
- isModalOpen: false
- };
- },
-
- handleToggle() {
- this.setState({
- isModalOpen: !this.state.isModalOpen
- });
- },
-
- render() {
- return (
-
- );
- },
-
- // This is called by the `OverlayMixin` when this component
- // is mounted or updated and the return value is appended to the body.
- renderOverlay() {
- if (!this.state.isModalOpen) {
- return ;
- }
-
- return (
-
-
- This modal is controlled by our custom trigger component.
-
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
-
-
Popover in a modal
-
TODO
-
-
Tooltips in a modal
-
TODO
-
-
-
-
Overflowing text to show scroll behavior
-
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
-
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
-
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
-
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
-
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
-
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
-
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
-
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
-
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
-
-
-
-
-
+
+
Click to get the full Modal experience!
+
+
+
+
+
+ Modal heading
+
+
+
Text in a modal
+
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
+
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
+
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
+
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
+
Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
+
Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla.
- Tight pants next level keffiyeh you probably haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel Another tooltip} href='#'>have a terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan whatever keytar, scenester farm-to-table banksy Austin twitter handle freegan cred raw denim single-origin coffee viral.
+ Tight pants next level keffiyeh you probably haven't
+ heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's
+ fixie sustainable quinoa 8-bit american apparel Another tooltip} href='#'>have a
+ terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four
+ loko mcsweeney's cleanse vegan chambray. A really ironic artisan whatever keytar,
+ scenester farm-to-table banksy Austin twitter handle freegan
+ cred raw denim single-origin coffee viral.
);
diff --git a/docs/examples/TooltipPositioned.js b/docs/examples/TooltipPositioned.js
index cedb961fa9..5f94bca114 100644
--- a/docs/examples/TooltipPositioned.js
+++ b/docs/examples/TooltipPositioned.js
@@ -1,15 +1,23 @@
+
+const tooltip = (
+ Holy guacamole! Check this info.
+);
+
const positionerInstance = (
- Holy guacamole! Check this info.}>
+
- Holy guacamole! Check this info.}>
+
+
- Holy guacamole! Check this info.}>
+
+
- Holy guacamole! Check this info.}>
+
+
diff --git a/docs/generate-metadata.js b/docs/generate-metadata.js
new file mode 100644
index 0000000000..bf30c07d0e
--- /dev/null
+++ b/docs/generate-metadata.js
@@ -0,0 +1,104 @@
+import metadata from 'react-component-metadata';
+import glob from 'glob';
+import fsp from 'fs-promise';
+import promisify from '../tools/promisify';
+import marked from 'marked';
+
+marked.setOptions({
+ xhtml: true
+});
+
+let globp = promisify(glob);
+
+// removes doclet syntax from comments
+let cleanDoclets = desc => {
+ let idx = desc.indexOf('@');
+ return (idx === -1 ? desc : desc.substr(0, idx )).trim();
+};
+
+let cleanDocletValue = str => str.trim().replace(/^\{/, '').replace(/\}$/, '');
+
+
+let isLiteral = str => (/^('|")/).test(str.trim());
+
+/**
+ * parse out description doclets to an object and remove the comment
+ *
+ * @param {ComponentMetadata|PropMetadata} obj
+ */
+function parseDoclets(obj){
+ obj.doclets = metadata.parseDoclets(obj.desc || '') || {};
+ obj.desc = cleanDoclets(obj.desc || '');
+ obj.descHtml = marked(obj.desc || '');
+}
+
+/**
+ * Reads the JSDoc "doclets" and applies certain ones to the prop type data
+ * This allows us to "fix" parsing errors, or unparsable data with JSDoc style comments
+ *
+ * @param {Object} props Object Hash of the prop metadata
+ * @param {String} propName
+ */
+function applyPropDoclets(props, propName){
+ let prop = props[propName];
+ let doclets = prop.doclets;
+ let value;
+
+ // the @type doclet to provide a prop type
+ // Also allows enums (oneOf) if string literals are provided
+ // ex: @type {("optionA"|"optionB")}
+ if (doclets.type) {
+ value = cleanDocletValue(doclets.type);
+ prop.type.name = value;
+
+ if ( value[0] === '(' ) {
+ value = value.substring(1, value.length - 1).split('|');
+
+ prop.type.value = value;
+ prop.type.name = value.every(isLiteral) ? 'enum' : 'union';
+ }
+ }
+
+ // Use @required to mark a prop as required
+ // useful for custom propTypes where there isn't a `.isRequired` addon
+ if ( doclets.required) {
+ prop.required = true;
+ }
+}
+
+
+export default function generate(destination, options = { mixins: true }){
+
+ return globp(__dirname + '/../src/**/*.js') //eslint-disable-line no-path-concat
+ .then( files => {
+
+ let results = files.map(
+ filename => fsp.readFile(filename).then(content => metadata(content, options)) );
+
+ return Promise.all(results)
+ .then( data => {
+ let result = {};
+
+ data.forEach(components => {
+ Object.keys(components).forEach(key => {
+ const component = components[key];
+
+ parseDoclets(component);
+
+ Object.keys(component.props).forEach( propName => {
+ const prop = component.props[propName];
+
+ parseDoclets(prop);
+ applyPropDoclets(component.props, propName);
+ });
+ });
+
+ //combine all the component metadata into one large object
+ result = { ...result, ...components };
+ });
+
+ return result;
+ })
+ .catch( e => setTimeout(()=> { throw e; }));
+ });
+}
diff --git a/docs/server.js b/docs/server.js
index 5d0157dfca..66b49144c8 100644
--- a/docs/server.js
+++ b/docs/server.js
@@ -5,6 +5,8 @@ import path from 'path';
import Router from 'react-router';
import routes from './src/Routes';
import httpProxy from 'http-proxy';
+
+import metadata from './generate-metadata';
import ip from 'ip';
const development = process.env.NODE_ENV !== 'production';
@@ -21,20 +23,27 @@ if (development) {
proxy.web(req, res, { target });
});
- app.use(function renderApp(req, res) {
- res.header('Access-Control-Allow-Origin', target);
- res.header('Access-Control-Allow-Headers', 'X-Requested-With');
-
- Router.run(routes, req.url, Handler => {
- let html = React.renderToString();
- res.send('' + html);
- });
- });
-
proxy.on('error', function(e) {
console.log('Could not connect to webpack proxy'.red);
console.log(e.toString().red);
});
+
+ console.log('Prop data generation started:'.green);
+
+ metadata().then( props => {
+ console.log('Prop data generation finished:'.green);
+
+ app.use(function renderApp(req, res) {
+ res.header('Access-Control-Allow-Origin', target);
+ res.header('Access-Control-Allow-Headers', 'X-Requested-With');
+
+ Router.run(routes, req.url, Handler => {
+ let html = React.renderToString();
+ res.send('' + html);
+ });
+ });
+ });
+
} else {
app.use(express.static(path.join(__dirname, '../docs-built')));
}
diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js
index 93aa06266e..c3166eca4d 100644
--- a/docs/src/ComponentsPage.js
+++ b/docs/src/ComponentsPage.js
@@ -1,4 +1,4 @@
-/* eslint no-path-concat: 0, react/no-did-mount-set-state: 0 */
+/* eslint react/no-did-mount-set-state: 0 */
import React from 'react';
@@ -9,6 +9,7 @@ import NavItem from '../../src/NavItem';
import NavMain from './NavMain';
import PageHeader from './PageHeader';
+import PropTable from './PropTable';
import PageFooter from './PageFooter';
import ReactPlayground from './ReactPlayground';
import Samples from './Samples';
@@ -67,6 +68,7 @@ const ComponentsPage = React.createClass({
flush against each other. To preserve the spacing between multiple inline buttons, wrap your
button group in {''}.
+
Sizes
Fancy larger or smaller buttons? Add bsSize="large", bsSize="small", or bsSize="xsmall" for additional sizes.
@@ -99,6 +101,10 @@ const ComponentsPage = React.createClass({
feedback as to the loading state, this can easily be done by updating
your {''}’s props from a state change like below.
CollapsibleMixin can be used to create your own components with collapse functionality.
+
+
Props
+
+
Panels, Accordion
+
+
+
PanelGroup
+
+
Modals Modal
A static example
-
A rendered modal with header, body, and set of actions in the footer.
-
The header is added automatically if you pass in a title prop.
+
+ A rendered modal with header, body, and set of actions in the footer. The {''} Component comes with
+ a few convenient "sub components": {''}, {''}, {''},
+ and {''}, which you can use to build the Modal content.
+
+
+
Additional Import Options
+
+ The Modal Header, Title, Body, and Footer components are available as static properties the {''} component, but you can also,
+ import them directly from the /lib directory like: {"require('react-bootstrap/lib/ModalHeader')"}.
+
+
Live demo
-
Use <ModalTrigger /> to create a real modal that's added to the document body when opened.
+
Use {''} in combination with other components to show or hide your Modal.
-
Custom trigger
-
Use OverlayMixin in a custom component to manage the modal's state yourself.
-
-
-
Contained Modal
+
Contained Modal
You will need to add the following css to your project and ensure that your container has the modal-container class.
You can apply custom css to the modal dialog div using the "dialogClassName" prop. Example is using a custom css class with width set to 90%.
+
+
Props
+
+
Modal
+
+
+
Modal.Header
+
+
+
Modal.Title
+
+
+
Modal.Body
+
+
+
Modal.Footer
+
+
+
ModalTrigger Deprecated: use the Modal directly to manage it's visibility
+
+
+
{/* Tooltip */}
-
Tooltips Tooltip
-
Example tooltips
-
-
Tooltip component.
-
+
Tooltip
+
+ Tooltip component for a more stylish alternative to that anchor tag title attribute.
+
+
-
Positioned tooltip component.
+
Attach and position tooltips with OverlayTrigger.
-
Positioned tooltip in copy.
+
Positioned tooltip in text copy.
+
+
Props
+
+
Overlay Trigger
+
+
+
Tooltip
+
{/* Popover */}
-
Popovers Popover
-
Example popovers
+
Popovers
-
Popover component.
-
+
+ The Popover, offers a more robust alternative to the Tooltip for displaying overlays of content.
+
+
-
Positioned popover component.
+
The Popover component, like the Tooltip can be used with an OverlayTrigger Component, and positioned around it.
Trigger behaviors. It's inadvisable to use "hover" or "focus" triggers for popovers, because they have poor accessibility from keyboard and on mobile devices.
Positioned popover components in scrolling container.
+
+
Props
+
+
+
+
+ {/* Overlay */}
+
+
Overlay
+
+
+ The OverlayTrigger component is great for most use cases, but as a higher level abstraction it can lack the flexibility needed
+ to build more nuanced or custom behaviors into your Overlay components. For these cases it can be helpful to forgo the trigger and use
+ the Overlay component directly.
+
+
+
+
+ You don't need to use the provided Tooltip or Popover components. Creating custom overlays
+ is as easy as wrapping some markup in an Overlay component
+
Add responsive prop to make them scroll horizontally up to small devices (under 768px). When viewing on anything larger than 768px wide, you will not see any difference in these tables.
If type is not set, child element(s) will be rendered instead of an input element.
getValue() will not work when used this way.
+
+
Props
+
+
+ {/* Utilities */}
+
+
Utilities Portal, Position
+
+
Portal
+
+ A Component that renders its children into a new React "subtree" or container. The Portal component kind of like the React
+ equivalent to jQuery's .appendTo(), which is helpful for components that need to be appended to a DOM node other than
+ the component's direct parent. The Modal, and Overlay components use the Portal component internally.
+
+
Props
+
+
+
+
Position
+
+ A Component that absolutely positions its child to a target component or DOM node. Useful for creating custom
+ popups or tooltips. Used by the Overlay Components.
+
+ );
+ });
+ },
+
+ renderRequiredLabel(prop) {
+ if (!prop.required) {
+ return null;
+ }
+
+ return (
+
+ );
+ },
+
+ getType(prop) {
+ let type = prop.type || {};
+ let name = this.getDisplayTypeName(type.name);
+ let doclets = prop.doclets || {};
+
+ switch (name) {
+ case 'object':
+ return name;
+ case 'union':
+ return type.value.reduce((current, val, i, list) => {
+ let item = this.getType({ type: val });
+ if (React.isValidElement(item)) {
+ item = React.cloneElement(item, {key: i});
+ }
+ current = current.concat(item);
+
+ return i === (list.length - 1) ? current : current.concat(' | ');
+ }, []);
+ case 'array':
+ let child = this.getType({ type: type.value });
+
+ return {'array<'}{ child }{'>'};
+ case 'enum':
+ return this.renderEnum(type);
+ case 'custom':
+ return cleanDocletValue(doclets.type || name);
+ default:
+ return name;
+ }
+ },
+
+ getDisplayTypeName(typeName) {
+ if (typeName === 'func') {
+ return 'function';
+ } else if (typeName === 'bool') {
+ return 'boolean';
+ } else {
+ return typeName;
+ }
+ },
+
+ renderEnum(enumType) {
+ const enumValues = enumType.value || [];
+
+ const renderedEnumValues = [];
+ enumValues.forEach(function renderEnumValue(enumValue, i) {
+ if (i > 0) {
+ renderedEnumValues.push(
+ ,
+ );
+ }
+
+ renderedEnumValues.push(
+ {enumValue}
+ );
+ });
+
+ return (
+ one of: {renderedEnumValues}
+ );
+ }
+});
+
+
+
+export default PropTable;
diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js
index 743bb65979..02e170c48f 100644
--- a/docs/src/ReactPlayground.js
+++ b/docs/src/ReactPlayground.js
@@ -36,6 +36,7 @@ import * as modPagination from '../../src/Pagination';
import * as modPanel from '../../src/Panel';
import * as modPanelGroup from '../../src/PanelGroup';
import * as modPopover from '../../src/Popover';
+//import * as modPopoverTrigger from '../../src/PopoverTrigger';
import * as modProgressBar from '../../src/ProgressBar';
import * as modRow from '../../src/Row';
import * as modSplitButton from '../../src/SplitButton';
@@ -44,14 +45,22 @@ import * as modTable from '../../src/Table';
import * as modTabPane from '../../src/TabPane';
import * as modThumbnail from '../../src/Thumbnail';
import * as modTooltip from '../../src/Tooltip';
+//import * as modTooltipTrigger from '../../src/TooltipTrigger';
import * as modWell from '../../src/Well';
+import * as modPortal from '../../src/Portal';
+import * as modOverlay from '../../src/Overlay';
+
import babel from 'babel-core/browser';
import CodeExample from './CodeExample';
+
+
const classNames = modClassNames.default;
/* eslint-disable */
+const Portal = modPortal.default;
+
const React = modReact.default;
const Accordion = modAccordion.default;
const Alert = modAlert.default;
@@ -89,6 +98,7 @@ const Pager = modPager.default;
const Panel = modPanel.default;
const PanelGroup = modPanelGroup.default;
const Popover = modPopover.default;
+//const PopoverTrigger = modPopoverTrigger.default;
const ProgressBar = modProgressBar.default;
const Row = modRow.default;
const SplitButton = modSplitButton.default;
@@ -97,7 +107,10 @@ const Table = modTable.default;
const TabPane = modTabPane.default;
const Thumbnail = modThumbnail.default;
const Tooltip = modTooltip.default;
+//const TooltipTrigger = modTooltipTrigger.default;
const Well = modWell.default;
+const Overlay = modOverlay.default;
+
/* eslint-enable */
const IS_MOBILE = typeof navigator !== 'undefined' && (
diff --git a/docs/src/Root.js b/docs/src/Root.js
index f3ce4b2b76..449db4528e 100644
--- a/docs/src/Root.js
+++ b/docs/src/Root.js
@@ -14,7 +14,8 @@ const Root = React.createClass({
'index.html',
'introduction.html',
'getting-started.html',
- 'components.html'
+ 'components.html',
+ 'support.html'
];
}
},
@@ -25,6 +26,14 @@ const Root = React.createClass({
};
},
+ childContextTypes: {
+ metadata: React.PropTypes.object
+ },
+
+ getChildContext(){
+ return { metadata: this.props.propData };
+ },
+
render() {
// Dump out our current props to a global object via a script tag so
// when initialising the browser environment we can bootstrap from the
@@ -64,7 +73,7 @@ const Root = React.createClass({
-
+
diff --git a/docs/src/Routes.js b/docs/src/Routes.js
index 790880e163..ae95030507 100644
--- a/docs/src/Routes.js
+++ b/docs/src/Routes.js
@@ -5,6 +5,7 @@ import HomePage from './HomePage';
import IntroductionPage from './IntroductionPage';
import GettingStartedPage from './GettingStartedPage';
import ComponentsPage from './ComponentsPage';
+import SupportPage from './SupportPage';
import NotFoundPage from './NotFoundPage';
import {Route, DefaultRoute, NotFoundRoute} from 'react-router';
@@ -17,5 +18,6 @@ export default (
+
);
diff --git a/docs/src/Samples.js b/docs/src/Samples.js
index c9b0aee582..47e6720802 100644
--- a/docs/src/Samples.js
+++ b/docs/src/Samples.js
@@ -32,7 +32,7 @@ export default {
CollapsibleParagraph: require('fs').readFileSync(__dirname + '/../examples/CollapsibleParagraph.js', 'utf8'),
ModalStatic: require('fs').readFileSync(__dirname + '/../examples/ModalStatic.js', 'utf8'),
ModalTrigger: require('fs').readFileSync(__dirname + '/../examples/ModalTrigger.js', 'utf8'),
- ModalOverlayMixin: require('fs').readFileSync(__dirname + '/../examples/ModalOverlayMixin.js', 'utf8'),
+
ModalContained: require('fs').readFileSync(__dirname + '/../examples/ModalContained.js', 'utf8'),
ModalDefaultSizing: require('fs').readFileSync(__dirname + '/../examples/ModalDefaultSizing.js', 'utf8'),
ModalCustomSizing: require('fs').readFileSync(__dirname + '/../examples/ModalCustomSizing.js', 'utf8'),
@@ -99,5 +99,8 @@ export default {
InputValidation: require('fs').readFileSync(__dirname + '/../examples/InputValidation.js', 'utf8'),
InputHorizontal: require('fs').readFileSync(__dirname + '/../examples/InputHorizontal.js', 'utf8'),
InputWrapper: require('fs').readFileSync(__dirname + '/../examples/InputWrapper.js', 'utf8'),
- MenuItem: require('fs').readFileSync(__dirname + '/../examples/MenuItem.js', 'utf8')
+ MenuItem: require('fs').readFileSync(__dirname + '/../examples/MenuItem.js', 'utf8'),
+
+ Overlay: require('fs').readFileSync(__dirname + '/../examples/Overlay.js', 'utf8'),
+ OverlayCustom: require('fs').readFileSync(__dirname + '/../examples/OverlayCustom.js', 'utf8')
};
diff --git a/docs/src/SupportPage.js b/docs/src/SupportPage.js
new file mode 100644
index 0000000000..0a792dfbd5
--- /dev/null
+++ b/docs/src/SupportPage.js
@@ -0,0 +1,48 @@
+import React from 'react';
+
+import NavMain from './NavMain';
+import PageHeader from './PageHeader';
+import PageFooter from './PageFooter';
+
+export default class Page extends React.Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
Stay up to date on the development of React-Bootstrap and reach out to the community with these helpful resources.
+
+
Stack Overflow
+
Ask questions about specific problems you have faced, including details about what exactly you are trying to do. Make sure you tag your question with react-bootstrap. You can also read through existing React-Bootstrap questions.
+
+
Live help
+
Bring your questions and pair with other react-bootstrap users in a live Thinkful hangout. Hear about the challenges other developers are running into, or screenshare your own code with the group for feedback.
The issue tracker is the preferred channel for bug reports, features requests and submitting pull requests. See more about how we use issues in the contribution guidelines.
+
);
}
});
diff --git a/src/Modal.js b/src/Modal.js
index 3a28a7ff62..70b5945bab 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -1,15 +1,21 @@
-import React from 'react';
+/*eslint-disable react/prop-types */
+import React, { cloneElement } from 'react';
+
import classNames from 'classnames';
+import createChainedFunction from './utils/createChainedFunction';
import BootstrapMixin from './BootstrapMixin';
import FadeMixin from './FadeMixin';
import domUtils from './utils/domUtils';
import EventListener from './utils/EventListener';
+import deprecationWarning from './utils/deprecationWarning';
+
+import Portal from './Portal';
+import Body from './ModalBody';
+import Header from './ModalHeader';
+import Title from './ModalTitle';
+import Footer from './ModalFooter';
-// TODO:
-// - aria-labelledby
-// - Add `modal-body` div if only one child passed in that doesn't already have it
-// - Tests
/**
* Gets the correct clientHeight of the modal container
@@ -31,9 +37,32 @@ function getContainer(context){
domUtils.ownerDocument(context).body;
}
+function requiredIfNot(key, type){
+ return function(props, propName, componentName){
+ let propType = type;
+
+ if ( props[ key] === undefined ){
+ propType = propType.isRequired;
+ }
+ return propType(props, propName, componentName);
+ };
+}
+
+function toChildArray(children){
+ let result = [];
+ React.Children.forEach(children, c => result.push(c));
+ return result;
+}
+
+
+let currentFocusListener;
+
/**
* Firefox doesn't have a focusin event so using capture is easiest way to get bubbling
* IE8 can't do addEventListener, but does have onfocusin, so we use that in ie8
+ *
+ * We only allow one Listener at a time to avoid stack overflows
+ *
* @param {ReactElement|HTMLElement} context
* @param {Function} handler
*/
@@ -42,6 +71,10 @@ function onFocus(context, handler) {
let useFocusin = !doc.addEventListener;
let remove;
+ if ( currentFocusListener ) {
+ currentFocusListener.remove();
+ }
+
if (useFocusin) {
document.attachEvent('onfocusin', handler);
remove = () => document.detachEvent('onfocusin', handler);
@@ -49,7 +82,10 @@ function onFocus(context, handler) {
document.addEventListener('focus', handler, true);
remove = () => document.removeEventListener('focus', handler, true);
}
- return { remove };
+
+ currentFocusListener = { remove };
+
+ return currentFocusListener;
}
let scrollbarSize;
@@ -75,19 +111,64 @@ function getScrollbarSize(){
}
-const Modal = React.createClass({
+const ModalMarkup = React.createClass({
mixins: [BootstrapMixin, FadeMixin],
propTypes: {
+ /**
+ * The Modal title text
+ * @deprecated Use the "Modal.Header" component instead
+ */
title: React.PropTypes.node,
+ /**
+ * Include a backdrop component. Specify 'static' for a backdrop that doesn't trigger an "onHide" when clicked.
+ */
backdrop: React.PropTypes.oneOf(['static', true, false]),
+ /**
+ * Include a backdrop component. Specify 'static' for a backdrop that doesn't trigger an "onHide" when clicked.
+ */
keyboard: React.PropTypes.bool,
+
+ /**
+ * Specify whether the Modal heading should contain a close button
+ * @deprecated Use the "Modal.Header" Component instead
+ */
closeButton: React.PropTypes.bool,
- container: React.PropTypes.object,
+
+ /**
+ * Open and close the Modal with a slide and fade animation.
+ */
animation: React.PropTypes.bool,
- onRequestHide: React.PropTypes.func.isRequired,
+ /**
+ * A Callback fired when the header closeButton or non-static backdrop is clicked.
+ * @type {function}
+ * @required
+ */
+ onHide: requiredIfNot('onRequestHide', React.PropTypes.func),
+
+ /**
+ * A Callback fired when the header closeButton or non-static backdrop is clicked.
+ * @deprecated Replaced by `onHide`.
+ */
+ onRequestHide: React.PropTypes.func,
+
+ /**
+ * A css class to apply to the Modal dialog DOM node.
+ */
dialogClassName: React.PropTypes.string,
+
+ /**
+ * When `true` The modal will automatically shift focus to itself when it opens, and replace it to the last focused element when it closes.
+ * Generally this should never be set to false as it makes the Modal less accessible to assistive technologies, like screen-readers.
+ */
+ autoFocus: React.PropTypes.bool,
+
+ /**
+ * When `true` The modal will prevent focus from leaving the Modal while open.
+ * Consider leaving the default value here, as it is necessary to make the Modal work well with assistive technologies,
+ * such as screen readers.
+ */
enforceFocus: React.PropTypes.bool
},
@@ -98,6 +179,8 @@ const Modal = React.createClass({
keyboard: true,
animation: true,
closeButton: true,
+
+ autoFocus: true,
enforceFocus: true
};
},
@@ -131,9 +214,8 @@ const Modal = React.createClass({
onClick={this.props.backdrop === true ? this.handleBackdropClick : null}
ref="modal">
+ );
+ }
+}
+
+//used in liue of parent contexts right now to auto wire the close button
+ModalHeader.__isModalHeader = true;
+
+ModalHeader.propTypes = {
+ /**
+ * A css class applied to the Component
+ */
+ modalClassName: React.PropTypes.string,
+ /**
+ * Specify whether the Component should contain a close button
+ */
+ closeButton: React.PropTypes.bool,
+ /**
+ * A Callback fired when the close button is clicked. If used directly inside a Modal component, the onHide will automatically
+ * be propagated up to the parent Modal `onHide`.
+ */
+ onHide: React.PropTypes.func
+};
+
+ModalHeader.defaultProps = {
+ modalClassName: 'modal-header',
+ closeButton: false
+};
+
+
+export default ModalHeader;
diff --git a/src/ModalTitle.js b/src/ModalTitle.js
new file mode 100644
index 0000000000..0c44bbaee5
--- /dev/null
+++ b/src/ModalTitle.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import classnames from 'classnames';
+
+class ModalTitle extends React.Component {
+
+ render() {
+ return (
+
}
trigger='click' rootClose={testCase.rootClose}
- >
-
+ >
+
);
const overlayTrigger = React.findDOMNode(instance);
diff --git a/test/PortalSpec.js b/test/PortalSpec.js
new file mode 100644
index 0000000000..66a1b49fc0
--- /dev/null
+++ b/test/PortalSpec.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import Portal from '../src/Portal';
+
+describe('Portal', function () {
+ let instance;
+
+ let Overlay = React.createClass({
+ render() {
+ return (
+
+ {this.props.overlay}
+
+ );
+ },
+ getOverlayDOMNode(){
+ return this.refs.p.getOverlayDOMNode();
+ }
+ });
+
+ afterEach(function() {
+ if (instance && ReactTestUtils.isCompositeComponent(instance) && instance.isMounted()) {
+ React.unmountComponentAtNode(React.findDOMNode(instance));
+ }
+ });
+
+ it('Should render overlay into container (DOMNode)', function() {
+ let container = document.createElement('div');
+
+ instance = ReactTestUtils.renderIntoDocument(
+ } />
+ );
+
+ assert.equal(container.querySelectorAll('#test1').length, 1);
+ });
+
+ it('Should render overlay into container (ReactComponent)', function() {
+ let Container = React.createClass({
+ render() {
+ return } />;
+ }
+ });
+
+ instance = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ assert.equal(React.findDOMNode(instance).querySelectorAll('#test1').length, 1);
+ });
+
+ it('Should not render a null overlay', function() {
+ let Container = React.createClass({
+ render() {
+ return ;
+ }
+ });
+
+ instance = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ assert.equal(instance.refs.overlay.getOverlayDOMNode(), null);
+ });
+
+ it('Should render only an overlay', function() {
+ let OnlyOverlay = React.createClass({
+ render() {
+ return {this.props.overlay};
+ }
+ });
+
+ let overlayInstance = ReactTestUtils.renderIntoDocument(
+ } />
+ );
+
+ assert.equal(overlayInstance.refs.p.getOverlayDOMNode().nodeName, 'DIV');
+ });
+});
diff --git a/test/index.js b/test/index.js
index b3998914c4..73c1d4ba3e 100644
--- a/test/index.js
+++ b/test/index.js
@@ -19,5 +19,9 @@ describe('Process environment for tests', function () {
});
});
+// Ensure all files in src folder are loaded for proper code coverage analysis
+const srcContext = require.context('../src', true, /.*\.js$/);
+srcContext.keys().forEach(srcContext);
+
const testsContext = require.context('.', true, /Spec$/);
testsContext.keys().forEach(testsContext);
diff --git a/test/utils/CustomPropTypesSpec.js b/test/utils/CustomPropTypesSpec.js
index 9e4e78de07..ef6d8e152e 100644
--- a/test/utils/CustomPropTypesSpec.js
+++ b/test/utils/CustomPropTypesSpec.js
@@ -2,46 +2,82 @@ import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import CustomPropTypes from '../../src/utils/CustomPropTypes';
+function isChainableAndUndefinedOK(validatorUnderTest) {
+ it('Should validate OK with undefined or null values', function() {
+ assert.isUndefined(validatorUnderTest({}, 'p', 'Component'));
+ assert.isUndefined(validatorUnderTest({p: null}, 'p', 'Component'));
+ });
+
+ it('Should be able to chain', function() {
+ let err = validatorUnderTest.isRequired({}, 'p', 'Component');
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'Required prop');
+ assert.include(err.message, 'was not specified in');
+ });
+}
+
describe('CustomPropTypes', function() {
describe('mountable', function () {
function validate(prop) {
return CustomPropTypes.mountable({p: prop}, 'p', 'Component');
}
- function validateRequired(prop) {
- return CustomPropTypes.mountable.isRequired({p: prop}, 'p', 'Component');
- }
+
+ isChainableAndUndefinedOK(CustomPropTypes.mountable);
it('Should return error with non mountable values', function() {
- assert.instanceOf(validateRequired(), Error);
- assert.instanceOf(validateRequired(null), Error);
- assert.instanceOf(validate({}), Error);
+ let err = validate({});
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'expected a DOM element or an object that has a `render` method');
});
+
it('Should return undefined with mountable values', function() {
- assert.isUndefined(validate());
- assert.isUndefined(validate(null));
assert.isUndefined(validate(document.createElement('div')));
assert.isUndefined(validate(document.body));
assert.isUndefined(validate(ReactTestUtils.renderIntoDocument()));
});
});
+ describe('elementType', function () {
+ function validate(prop) {
+ return CustomPropTypes.elementType({p: prop}, 'p', 'TestComponent');
+ }
+
+ isChainableAndUndefinedOK(CustomPropTypes.elementType);
+
+ it('Should validate OK with elementType values', function() {
+ assert.isUndefined(validate('span'));
+ assert.isUndefined(validate(function(){}));
+ });
+
+ it('Should return error with not a string or function values', function() {
+ let err = validate({});
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'Expected an Element `type` such as a tag name or return value of React.createClass(...)');
+ });
+
+ it('Should return error with react element', function() {
+ let err = validate(React.createElement('span'));
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'Expected an Element `type`, not an actual Element');
+ });
+ });
+
describe('keyOf', function () {
let obj = {'foo': 1};
function validate(prop) {
return CustomPropTypes.keyOf(obj)({p: prop}, 'p', 'Component');
}
- function validateRequired(prop) {
- return CustomPropTypes.keyOf(obj).isRequired({p: prop}, 'p', 'Component');
- }
+
+ isChainableAndUndefinedOK(CustomPropTypes.keyOf(obj));
it('Should return error with non-key values', function() {
- assert.instanceOf(validateRequired(), Error);
- assert.instanceOf(validateRequired(null), Error);
- assert.instanceOf(validate('bar'), Error);
+ let err = validate('bar');
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'expected one of ["foo"]');
});
- it('Should return undefined with key values', function() {
- assert.isUndefined(validate());
+
+ it('Should validate OK with key values', function() {
assert.isUndefined(validate('foo'));
obj.bar = 2;
assert.isUndefined(validate('bar'));
@@ -55,16 +91,16 @@ describe('CustomPropTypes', function() {
return CustomPropTypes.singlePropFrom(propList)(testProps, 'value', 'Component');
}
- it('Should return undefined if only one listed prop in used', function () {
+ it('Should validate OK if only one listed prop in used', function () {
const testProps = {value: 5};
assert.isUndefined(validate(testProps));
});
it('Should return error if multiple of the listed properties have values', function () {
- const testProps = {value: 5, children: 5};
-
- validate(testProps).should.be.instanceOf(Error);
+ let err = validate({value: 5, children: 5});
+ assert.instanceOf(err, Error);
+ assert.include(err.message, 'only one of the following may be provided: value and children');
});
});
diff --git a/test/utils/overlayPositionUtilsSpec.js b/test/utils/overlayPositionUtilsSpec.js
new file mode 100644
index 0000000000..50703e4b4c
--- /dev/null
+++ b/test/utils/overlayPositionUtilsSpec.js
@@ -0,0 +1,92 @@
+import position from '../../src/utils/overlayPositionUtils';
+
+describe('calcOverlayPosition()', function() {
+ [
+ {
+ placement: 'left',
+ noOffset: [50, 300, null, '50%'],
+ offsetBefore: [-200, 150, null, '0%'],
+ offsetAfter: [300, 450, null, '100%']
+ },
+ {
+ placement: 'top',
+ noOffset: [200, 150, '50%', null],
+ offsetBefore: [50, -100, '0%', null],
+ offsetAfter: [350, 400, '100%', null]
+ },
+ {
+ placement: 'bottom',
+ noOffset: [200, 450, '50%', null],
+ offsetBefore: [50, 200, '0%', null],
+ offsetAfter: [350, 700, '100%', null]
+ },
+ {
+ placement: 'right',
+ noOffset: [350, 300, null, '50%'],
+ offsetBefore: [100, 150, null, '0%'],
+ offsetAfter: [600, 450, null, '100%']
+ }
+ ].forEach(function(testCase) {
+
+ describe(`placement = ${testCase.placement}`, function() {
+ let overlayStub, padding, placement;
+
+ beforeEach(function() {
+ placement = testCase.placement;
+ padding = 50;
+ overlayStub = {
+ offsetHeight: 200, offsetWidth: 200
+ };
+
+ position.getContainerDimensions = sinon.stub().returns({
+ width: 600, height: 600, scroll: 100
+ });
+ });
+
+ function checkPosition(expected) {
+ const [
+ positionLeft,
+ positionTop,
+ arrowOffsetLeft,
+ arrowOffsetTop
+ ] = expected;
+
+ it('Should calculate the correct position', function() {
+ position.calcOverlayPosition(placement, overlayStub, {}, {}, padding).should.eql(
+ { positionLeft, positionTop, arrowOffsetLeft, arrowOffsetTop }
+ );
+ });
+ }
+
+ describe('no viewport offset', function() {
+ beforeEach(function() {
+ position.getPosition = sinon.stub().returns({
+ left: 250, top: 350, width: 100, height: 100
+ });
+ });
+
+ checkPosition(testCase.noOffset);
+ });
+
+ describe('viewport offset before', function() {
+ beforeEach(function() {
+ position.getPosition = sinon.stub().returns({
+ left: 0, top: 100, width: 100, height: 100
+ });
+ });
+
+ checkPosition(testCase.offsetBefore);
+ });
+
+ describe('viewport offset after', function() {
+ beforeEach(function() {
+ position.getPosition = sinon.stub().returns({
+ left: 500, top: 600, width: 100, height: 100
+ });
+ });
+
+ checkPosition(testCase.offsetAfter);
+ });
+ });
+ });
+ });
diff --git a/tools/promisify.js b/tools/promisify.js
new file mode 100644
index 0000000000..649829c506
--- /dev/null
+++ b/tools/promisify.js
@@ -0,0 +1,17 @@
+
+export default function promisify(fn){
+ return function (...args){
+ return new Promise(function(resolve, reject){
+
+ function finish(err, result){
+ if (err) {
+ return reject(err);
+ }
+ resolve(result);
+ }
+
+ fn.apply(null, args.concat(finish));
+ });
+
+ };
+}
From 4fb7e0d64b4ef3d2c97157213736ea7a5aa76d47 Mon Sep 17 00:00:00 2001
From: jquense
Date: Sun, 5 Jul 2015 21:09:59 -0400
Subject: [PATCH 22/27] [changed] Remove Overlay and Modal deprecations
---
docs/examples/{ModalTrigger.js => Modal.js} | 0
docs/examples/ModalCustomSizing.js | 80 ++++---
docs/src/ComponentsPage.js | 2 +-
docs/src/ReactPlayground.js | 11 +-
docs/src/Samples.js | 2 +-
src/Input.js | 2 +-
src/Modal.js | 95 ++------
src/ModalTrigger.js | 121 ----------
src/OverlayMixin.js | 95 --------
src/OverlayTrigger.js | 65 ++----
src/Portal.js | 77 +++++-
src/index.js | 2 -
test/FactoriesSpec.js | 2 +-
test/ModalSpec.js | 244 ++++++++------------
test/ModalTriggerSpec.js | 123 ----------
test/OverlayMixinSpec.js | 89 -------
test/helpers.js | 25 ++
test/server/ModalSpec.js | 2 +-
18 files changed, 278 insertions(+), 759 deletions(-)
rename docs/examples/{ModalTrigger.js => Modal.js} (100%)
delete mode 100644 src/ModalTrigger.js
delete mode 100644 src/OverlayMixin.js
delete mode 100644 test/ModalTriggerSpec.js
delete mode 100644 test/OverlayMixinSpec.js
diff --git a/docs/examples/ModalTrigger.js b/docs/examples/Modal.js
similarity index 100%
rename from docs/examples/ModalTrigger.js
rename to docs/examples/Modal.js
diff --git a/docs/examples/ModalCustomSizing.js b/docs/examples/ModalCustomSizing.js
index 4122e352d4..701d67338a 100644
--- a/docs/examples/ModalCustomSizing.js
+++ b/docs/examples/ModalCustomSizing.js
@@ -1,40 +1,52 @@
-const MyModal = React.createClass({
- render() {
+const Example = React.createClass({
+
+ getInitialState(){
+ return { show: false };
+ },
+
+ render(){
+ let close = e => this.setState({ show: false });
+
return (
-
-
- Modal heading
-
-
-
Wrapped Text
-
Ipsum molestiae natus adipisci modi eligendi? Debitis amet quae unde commodi aspernatur enim, consectetur. Cumque deleniti temporibus ipsam atque a dolores quisquam quisquam adipisci possimus laboriosam. Quibusdam facilis doloribus debitis! Sit quasi quod accusamus eos quod. Ab quos consequuntur eaque quo rem!
- Mollitia reiciendis porro quo magni incidunt dolore amet atque facilis ipsum deleniti rem! Dolores debitis voluptatibus ipsum dicta. Dolor quod amet ab sint esse distinctio tenetur. Veritatis laudantium quibusdam quidem corporis architecto veritatis. Ex facilis minima beatae sunt perspiciatis placeat. Quasi corporis
- odio eaque voluptatibus ratione magnam nulla? Amet cum maiores consequuntur totam dicta! Inventore adipisicing vel vero odio modi doloremque? Vitae porro impedit ea minima laboriosam quisquam neque. Perspiciatis omnis obcaecati consequatur sunt deleniti similique facilis sequi. Ipsum harum vitae modi reiciendis officiis.
- Quas laudantium laudantium modi corporis nihil provident consectetur omnis, natus nulla distinctio illum corporis. Sit ex earum odio ratione consequatur odit minus laborum? Eos? Sit ipsum illum architecto aspernatur perspiciatis error fuga illum, tempora harum earum, a dolores. Animi facilis inventore harum dolore accusamus
- fuga provident molestiae eum! Odit dicta error dolorem sunt reprehenderit. Sit similique iure quae obcaecati harum. Eum saepe fugit magnam dicta aliquam? Sapiente possimus aliquam fugiat officia culpa sint! Beatae voluptates voluptatem excepturi molestiae alias in tenetur beatae placeat architecto. Sit possimus rerum
- fugiat sapiente aspernatur. Necessitatibus tempora animi dicta perspiciatis tempora a velit in! Doloribus perspiciatis doloribus suscipit nam earum. Deleniti veritatis eaque totam assumenda fuga sapiente! Id recusandae. Consectetur necessitatibus eaque velit nobis aliquid? Fugit illum qui suscipit aspernatur alias ipsum
- repudiandae! Quia omnis quisquam dignissimos a mollitia. Suscipit aspernatur eum maiores repellendus ipsum doloribus alias voluptatum consequatur. Consectetur quibusdam veniam quas tenetur necessitatibus repudiandae? Rem optio vel alias neque optio sapiente quidem similique reiciendis tempore. Illum accusamus officia
- cum enim minima eligendi consectetur nemo veritatis nam nisi! Adipisicing nobis perspiciatis dolorum adipisci soluta architecto doloremque voluptatibus omnis debitis quas repellendus. Consequuntur assumenda illum commodi mollitia asperiores? Quis aspernatur consequatur modi veritatis aliquid at? Atque vel iure quos.
- Amet provident voluptatem amet aliquam deserunt sint, elit dolorem ipsa, voluptas? Quos esse facilis neque nihil sequi non? Voluptates rem ab quae dicta culpa dolorum sed atque molestias debitis omnis! Sit sint repellendus deleniti officiis distinctio. Impedit vel quos harum doloribus corporis. Laborum ullam nemo quaerat
- reiciendis recusandae minima dicta molestias rerum. Voluptas et ut omnis est ipsum accusamus harum. Amet exercitationem quasi velit inventore neque doloremque! Consequatur neque dolorem vel impedit sunt voluptate. Amet quo amet magni exercitationem libero recusandae possimus pariatur. Cumque eum blanditiis vel vitae
- distinctio! Tempora! Consectetur sit eligendi neque sunt soluta laudantium natus qui aperiam quisquam consectetur consequatur sit sint a unde et. At voluptas ut officiis esse totam quasi dolorem! Hic deserunt doloribus repudiandae! Lorem quod ab nostrum asperiores aliquam ab id consequatur, expedita? Tempora quaerat
- ex ea temporibus in tempore voluptates cumque. Quidem nam dolor reiciendis qui dolor assumenda ipsam veritatis quasi. Esse! Sit consectetur hic et sunt iste! Accusantium atque elit voluptate asperiores corrupti temporibus mollitia! Placeat soluta odio ad blanditiis nisi. Eius reiciendis id quos dolorum eaque suscipit
- magni delectus maxime. Sit odit provident vel magnam quod. Possimus eligendi non corrupti tenetur culpa accusantium quod quis. Voluptatum quaerat animi dolore maiores molestias voluptate? Necessitatibus illo omnis laborum hic enim minima! Similique. Dolor voluptatum reprehenderit nihil adipisci aperiam voluptatem soluta
- magnam accusamus iste incidunt tempore consequatur illo illo odit. Asperiores nesciunt iusto nemo animi ratione. Sunt odit similique doloribus temporibus reiciendis! Ullam. Dolor dolores veniam animi sequi dolores molestias voluptatem iure velit. Elit dolore quaerat incidunt enim aut distinctio. Ratione molestiae laboriosam
- similique laboriosam eum et nemo expedita. Consequuntur perspiciatis cumque dolorem.
-
-
-
-
-
+
+
+
+
+
+ Modal heading
+
+
+
Wrapped Text
+
Ipsum molestiae natus adipisci modi eligendi? Debitis amet quae unde commodi aspernatur enim, consectetur. Cumque deleniti temporibus ipsam atque a dolores quisquam quisquam adipisci possimus laboriosam. Quibusdam facilis doloribus debitis! Sit quasi quod accusamus eos quod. Ab quos consequuntur eaque quo rem!
+ Mollitia reiciendis porro quo magni incidunt dolore amet atque facilis ipsum deleniti rem! Dolores debitis voluptatibus ipsum dicta. Dolor quod amet ab sint esse distinctio tenetur. Veritatis laudantium quibusdam quidem corporis architecto veritatis. Ex facilis minima beatae sunt perspiciatis placeat. Quasi corporis
+ odio eaque voluptatibus ratione magnam nulla? Amet cum maiores consequuntur totam dicta! Inventore adipisicing vel vero odio modi doloremque? Vitae porro impedit ea minima laboriosam quisquam neque. Perspiciatis omnis obcaecati consequatur sunt deleniti similique facilis sequi. Ipsum harum vitae modi reiciendis officiis.
+ Quas laudantium laudantium modi corporis nihil provident consectetur omnis, natus nulla distinctio illum corporis. Sit ex earum odio ratione consequatur odit minus laborum? Eos? Sit ipsum illum architecto aspernatur perspiciatis error fuga illum, tempora harum earum, a dolores. Animi facilis inventore harum dolore accusamus
+ fuga provident molestiae eum! Odit dicta error dolorem sunt reprehenderit. Sit similique iure quae obcaecati harum. Eum saepe fugit magnam dicta aliquam? Sapiente possimus aliquam fugiat officia culpa sint! Beatae voluptates voluptatem excepturi molestiae alias in tenetur beatae placeat architecto. Sit possimus rerum
+ fugiat sapiente aspernatur. Necessitatibus tempora animi dicta perspiciatis tempora a velit in! Doloribus perspiciatis doloribus suscipit nam earum. Deleniti veritatis eaque totam assumenda fuga sapiente! Id recusandae. Consectetur necessitatibus eaque velit nobis aliquid? Fugit illum qui suscipit aspernatur alias ipsum
+ repudiandae! Quia omnis quisquam dignissimos a mollitia. Suscipit aspernatur eum maiores repellendus ipsum doloribus alias voluptatum consequatur. Consectetur quibusdam veniam quas tenetur necessitatibus repudiandae? Rem optio vel alias neque optio sapiente quidem similique reiciendis tempore. Illum accusamus officia
+ cum enim minima eligendi consectetur nemo veritatis nam nisi! Adipisicing nobis perspiciatis dolorum adipisci soluta architecto doloremque voluptatibus omnis debitis quas repellendus. Consequuntur assumenda illum commodi mollitia asperiores? Quis aspernatur consequatur modi veritatis aliquid at? Atque vel iure quos.
+ Amet provident voluptatem amet aliquam deserunt sint, elit dolorem ipsa, voluptas? Quos esse facilis neque nihil sequi non? Voluptates rem ab quae dicta culpa dolorum sed atque molestias debitis omnis! Sit sint repellendus deleniti officiis distinctio. Impedit vel quos harum doloribus corporis. Laborum ullam nemo quaerat
+ reiciendis recusandae minima dicta molestias rerum. Voluptas et ut omnis est ipsum accusamus harum. Amet exercitationem quasi velit inventore neque doloremque! Consequatur neque dolorem vel impedit sunt voluptate. Amet quo amet magni exercitationem libero recusandae possimus pariatur. Cumque eum blanditiis vel vitae
+ distinctio! Tempora! Consectetur sit eligendi neque sunt soluta laudantium natus qui aperiam quisquam consectetur consequatur sit sint a unde et. At voluptas ut officiis esse totam quasi dolorem! Hic deserunt doloribus repudiandae! Lorem quod ab nostrum asperiores aliquam ab id consequatur, expedita? Tempora quaerat
+ ex ea temporibus in tempore voluptates cumque. Quidem nam dolor reiciendis qui dolor assumenda ipsam veritatis quasi. Esse! Sit consectetur hic et sunt iste! Accusantium atque elit voluptate asperiores corrupti temporibus mollitia! Placeat soluta odio ad blanditiis nisi. Eius reiciendis id quos dolorum eaque suscipit
+ magni delectus maxime. Sit odit provident vel magnam quod. Possimus eligendi non corrupti tenetur culpa accusantium quod quis. Voluptatum quaerat animi dolore maiores molestias voluptate? Necessitatibus illo omnis laborum hic enim minima! Similique. Dolor voluptatum reprehenderit nihil adipisci aperiam voluptatem soluta
+ magnam accusamus iste incidunt tempore consequatur illo illo odit. Asperiores nesciunt iusto nemo animi ratione. Sunt odit similique doloribus temporibus reiciendis! Ullam. Dolor dolores veniam animi sequi dolores molestias voluptatem iure velit. Elit dolore quaerat incidunt enim aut distinctio. Ratione molestiae laboriosam
+ similique laboriosam eum et nemo expedita. Consequuntur perspiciatis cumque dolorem.
} onMouseOver={callback}>
-
-
- );
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.mouseOver(modalTrigger);
- callback.called.should.be.true;
- });
-
- it('Should pass ModalTrigger onMouseOut prop to child', function() {
- const callback = sinon.spy();
- const instance = ReactTestUtils.renderIntoDocument(
- test} onMouseOut={callback}>
-
-
- );
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.mouseOut(modalTrigger);
- callback.called.should.be.true;
- });
-
- it('Should pass ModalTrigger onFocus prop to child', function() {
- const callback = sinon.spy();
- const instance = ReactTestUtils.renderIntoDocument(
- test} onFocus={callback}>
-
-
- );
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.focus(modalTrigger);
- callback.called.should.be.true;
- });
-
- it('Should pass ModalTrigger onBlur prop to child', function() {
- const callback = sinon.spy();
- const instance = ReactTestUtils.renderIntoDocument(
- test} onBlur={callback}>
-
-
- );
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.blur(modalTrigger);
- callback.called.should.be.true;
- });
-
- // This is just a copy of the test case for OverlayTrigger.
- it('Should forward requested context', function() {
- const contextTypes = {
- key: React.PropTypes.string
- };
-
- const contextSpy = sinon.spy();
- class ContextReader extends React.Component {
- render() {
- contextSpy(this.context.key);
- return ;
- }
- }
- ContextReader.contextTypes = contextTypes;
-
- const TriggerWithContext = ModalTrigger.withContext(contextTypes);
- class ContextHolder extends React.Component {
- getChildContext() {
- return {key: 'value'};
- }
-
- render() {
- return (
- }
- >
-
-
- );
- }
- }
- ContextHolder.childContextTypes = contextTypes;
-
- const instance = ReactTestUtils.renderIntoDocument();
- const modalTrigger = React.findDOMNode(instance);
- ReactTestUtils.Simulate.click(modalTrigger);
-
- contextSpy.calledWith('value').should.be.true;
- });
-});
diff --git a/test/OverlayMixinSpec.js b/test/OverlayMixinSpec.js
deleted file mode 100644
index 66818d48f5..0000000000
--- a/test/OverlayMixinSpec.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import React from 'react';
-import ReactTestUtils from 'react/lib/ReactTestUtils';
-import OverlayMixin from '../src/OverlayMixin';
-import { shouldWarn } from './helpers';
-
-describe('OverlayMixin', function () {
- let instance;
-
- let Overlay = React.createClass({
- mixins: [OverlayMixin],
-
- render() {
- return ;
- },
-
- renderOverlay() {
- return this.props.overlay;
- }
- });
-
-
- afterEach(function() {
- if (instance && ReactTestUtils.isCompositeComponent(instance) && instance.isMounted()) {
- React.unmountComponentAtNode(React.findDOMNode(instance));
- }
-
- if ( console.warn.called ) {
- shouldWarn('Overlay mixin is deprecated');
- }
- });
-
- it('Should render overlay into container (DOMNode)', function() {
- let container = document.createElement('div');
-
- instance = ReactTestUtils.renderIntoDocument(
- } />
- );
-
- assert.equal(container.querySelectorAll('#test1').length, 1);
- });
-
- it('Should render overlay into container (ReactComponent)', function() {
- let Container = React.createClass({
- render() {
- return } />;
- }
- });
-
- instance = ReactTestUtils.renderIntoDocument(
-
- );
-
- assert.equal(React.findDOMNode(instance).querySelectorAll('#test1').length, 1);
- });
-
- it('Should not render a null overlay', function() {
- let Container = React.createClass({
- render() {
- return ;
- }
- });
-
- instance = ReactTestUtils.renderIntoDocument(
-
- );
-
- assert.equal(instance.refs.overlay.getOverlayDOMNode(), null);
- });
-
- it('Should render only an overlay', function() {
- let OnlyOverlay = React.createClass({
- mixins: [OverlayMixin],
-
- render() {
- return null;
- },
-
- renderOverlay() {
- return this.props.overlay;
- }
- });
-
- let overlayInstance = ReactTestUtils.renderIntoDocument(
- } />
- );
-
- assert.equal(overlayInstance.getOverlayDOMNode().nodeName, 'DIV');
- });
-});
diff --git a/test/helpers.js b/test/helpers.js
index 74960b4db4..97fb38d589 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -1,5 +1,30 @@
+import React from 'react';
+import { cloneElement } from 'react';
+
export function shouldWarn(about) {
console.warn.called.should.be.true;
console.warn.calledWithMatch(about).should.be.true;
console.warn.reset();
}
+
+/**
+ * Helper for rendering and updating props for plain class Components
+ * since `setProps` is deprecated.
+ * @param {ReactElement} element Root element to render
+ * @param {HTMLElement?} mountPoint Optional mount node, when empty it uses an unattached div like `renderIntoDocument()`
+ * @return {ComponentInstance} The instance, with a new method `renderWithProps` which will return a new instance wiht updated props
+ */
+export function render(element, mountPoint){
+ let mount = mountPoint || document.createElement('div');
+ let instance = React.render(element, mount);
+
+ if (!instance.renderWithProps) {
+ instance.renderWithProps = function(newProps) {
+
+ return render(
+ cloneElement(element, newProps), mount);
+ };
+ }
+
+ return instance;
+}
diff --git a/test/server/ModalSpec.js b/test/server/ModalSpec.js
index e78832c956..fae565c4c8 100644
--- a/test/server/ModalSpec.js
+++ b/test/server/ModalSpec.js
@@ -8,7 +8,7 @@ describe('Modal', () => {
assert.doesNotThrow(function renderOnServerSide() {
return React.renderToString(
-
+ Message
);
From 3a0b4da0ca7c4fd7fa409a168173875c6ada8eb4 Mon Sep 17 00:00:00 2001
From: jquense
Date: Sat, 11 Jul 2015 11:12:17 -0400
Subject: [PATCH 23/27] Add transition Component
---
src/Transition.js | 286 +++++++++++++++++++++++++++++++++++++++++
test/TransitionSpec.js | 231 +++++++++++++++++++++++++++++++++
2 files changed, 517 insertions(+)
create mode 100644 src/Transition.js
create mode 100644 test/TransitionSpec.js
diff --git a/src/Transition.js b/src/Transition.js
new file mode 100644
index 0000000000..ce8eb200cf
--- /dev/null
+++ b/src/Transition.js
@@ -0,0 +1,286 @@
+'use strict';
+import React from 'react';
+import TransitionEvents from './utils/TransitionEvents';
+import classnames from 'classnames';
+
+function omit(obj, keys) {
+ let included = Object.keys(obj).filter( k => keys.indexOf(k) === -1);
+ let newObj = {};
+
+ included.forEach( key => newObj[key] = obj[key] );
+ return newObj;
+}
+
+function ensureTransitionEnd(node, handler, duration){
+ let fired = false;
+ let done = e => {
+ if (!fired) {
+ fired = true;
+ handler(e);
+ }
+ };
+
+ if ( node ) {
+ TransitionEvents.addEndEventListener(node, done);
+ setTimeout(done, duration);
+ } else {
+ setTimeout(done, 0);
+ }
+}
+
+// reading a dimension prop will cause the browser to recalculate,
+// which will let our animations work
+let triggerBrowserReflow = node => node.offsetHeight; //eslint-disable-line no-unused-expressions
+
+class Transition extends React.Component {
+
+ constructor(props, context){
+ super(props, context);
+
+ this.state = {
+ in: !props.in,
+ transitioning: false
+ };
+
+ this.needsTransition = true;
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.in !== this.props.in) {
+ this.needsTransition = true;
+ }
+ }
+
+ componentDidUpdate() {
+ this.processChild();
+ }
+
+ componentWillMount() {
+ this._mounted = true;
+
+ if (!this.props.transitionAppear) {
+ this.needsTransition = false;
+ this.setState({ in: this.props.in });
+ }
+ }
+
+ componentWillUnmount(){
+ this._mounted = false;
+ }
+
+ componentDidMount() {
+ if (this.props.transitionAppear) {
+ this.processChild();
+ }
+ }
+
+ processChild(){
+ let needsTransition = this.needsTransition;
+ let enter = this.props.in;
+
+ if (needsTransition) {
+ this.needsTransition = false;
+ this[enter ? 'performEnter' : 'performLeave']();
+ }
+ }
+
+ performEnter() {
+ let maybeNode = React.findDOMNode(this);
+
+ let enter = node => {
+ node = this.props.transitioningNode(node) || node;
+
+ this.props.onEnter(node);
+
+ this.safeSetState({ in: true, transitioning: true, needInitialRender: false }, ()=> {
+
+ this.props.onEntering(node);
+
+ ensureTransitionEnd(node, () => {
+ if ( this.state.in ){
+ this.safeSetState({
+ transitioning: false
+ }, () => this.props.onEntered(node));
+ }
+
+ }, this.props.duration);
+ });
+ };
+
+ if (maybeNode) {
+ enter(maybeNode);
+ }
+ else if (this.props.unmountOnExit) {
+ this._ensureNode(enter);
+ }
+ }
+
+ performLeave() {
+ let node = React.findDOMNode(this);
+
+ node = this.props.transitioningNode(node) || node;
+
+ this.props.onExit(node);
+
+ this.setState({ in: false, transitioning: true }, () => {
+ this.props.onExiting(node);
+
+ ensureTransitionEnd(node, () => {
+ if ( !this.state.in ){
+ this.safeSetState({ transitioning: false }, ()=> this.props.onExited(node));
+ }
+ }, this.props.duration);
+ });
+ }
+
+ _ensureNode(callback) {
+
+ this.setState({ needInitialRender: true }, ()=> {
+ let node = React.findDOMNode(this);
+
+ triggerBrowserReflow(node);
+
+ callback(node);
+ });
+ }
+
+ safeSetState(newState, cb){
+ if (this._mounted) {
+ this.setState(newState, cb);
+ }
+ }
+
+ render() {
+ let childProps = omit(this.props, Object.keys(Transition.propTypes).concat('children'));
+
+ let child = this.props.children;
+ let starting = this.state.needInitialRender;
+ let out = !this.state.in && !this.state.transitioning;
+
+ if ( !child || (this.props.unmountOnExit && out && !starting) ){
+ return null;
+ }
+
+ let classes = '';
+
+ // for whatever reason classnames() doesn't actually work here,
+ // maybe because they aren't always single classes?
+ if (this.state.in && !this.state.transitioning) {
+ classes = this.props.enteredClassName;
+ }
+
+ else if (this.state.in && this.state.transitioning) {
+ classes = this.props.enteringClassName;
+ }
+
+ else if (!this.state.in && !this.state.transitioning) {
+ classes = this.props.exitedClassName;
+ }
+
+ else if (!this.state.in && this.state.transitioning) {
+ classes = this.props.exitingClassName;
+ }
+
+ return React.cloneElement(child, {
+ ...childProps,
+ className: classnames(
+ child.props.className
+ , this.props.className
+ , classes)
+ });
+ }
+}
+
+Transition.propTypes = {
+ /**
+ * Triggers the Enter or Exit animation
+ */
+ in: React.PropTypes.bool,
+
+ /**
+ * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
+ */
+ unmountOnExit: React.PropTypes.bool,
+
+ /**
+ * Specify whether transitions should run when the Transition component mounts.
+ */
+ transitionAppear: React.PropTypes.bool,
+
+ /**
+ * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * original browser transition end events are canceled.
+ */
+ duration: React.PropTypes.number,
+
+ /**
+ * A css class or classes applied once the Component has exited.
+ */
+ exitedClassName: React.PropTypes.string,
+ /**
+ * A css class or classes applied while the Component is exiting.
+ */
+ exitingClassName: React.PropTypes.string,
+ /**
+ * A css class or classes applied once the Component has entered.
+ */
+ enteredClassName: React.PropTypes.string,
+ /**
+ * A css class or classes applied while the Component is entering.
+ */
+ enteringClassName: React.PropTypes.string,
+
+ /**
+ * A function that returns the DOM node to animate. This Node will have the transition classes applied to it.
+ * When left out, the Component will use its immediate child.
+ *
+ * @private
+ */
+ transitioningNode: React.PropTypes.func,
+
+ /**
+ * A callback fired just before the "entering" classes are applied
+ */
+ onEnter: React.PropTypes.func,
+ /**
+ * A callback fired just after the "entering" classes are applied
+ */
+ onEntering: React.PropTypes.func,
+ /**
+ * A callback fired after "enter" classes are applied
+ */
+ onEntered: React.PropTypes.func,
+ /**
+ * A callback fired after "exiting" classes are applied
+ */
+ onExit: React.PropTypes.func,
+ /**
+ * A callback fired after "exiting" classes are applied
+ */
+ onExiting: React.PropTypes.func,
+ /**
+ * A callback fired after "exit" classes are applied
+ */
+ onExited: React.PropTypes.func
+};
+
+// name the function so it is clearer in the documentation
+const noop = ()=>{};
+
+Transition.defaultProps = {
+ in: false,
+ duration: 300,
+ unmountOnExit: false,
+ transitionAppear: false,
+ transitioningNode: noop,
+
+ onEnter: noop,
+ onEntering: noop,
+ onEntered: noop,
+
+ onExit: noop,
+ onExiting: noop,
+ onExited: noop
+};
+
+export default Transition;
diff --git a/test/TransitionSpec.js b/test/TransitionSpec.js
new file mode 100644
index 0000000000..7945ba8ceb
--- /dev/null
+++ b/test/TransitionSpec.js
@@ -0,0 +1,231 @@
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import { render } from './helpers';
+import Transition from '../src/Transition';
+//import classNames from 'classnames';
+
+describe('Transition', function () {
+
+
+ it('should not transition on mount', function(){
+ let instance = render(
+ { throw new Error('should not Enter'); }}>
+
+
+ );
+
+ instance.state.in.should.equal(true);
+ assert.ok(!instance.state.transitioning);
+ });
+
+ it('should transition on mount with transitionAppear', done =>{
+ let instance = ReactTestUtils.renderIntoDocument(
+ done()}
+ >
+
+
+ );
+
+ instance.state.in.should.equal(true);
+ instance.state.transitioning.should.equal(true);
+ });
+
+ describe('entering', ()=> {
+ let instance;
+
+ beforeEach(function(){
+ instance = render(
+
+
+
+ );
+ });
+
+ it('should fire callbacks', done => {
+ let onEnter = sinon.spy();
+ let onEntering = sinon.spy();
+
+ instance.state.in.should.equal(false);
+
+ instance = instance.renderWithProps({
+
+ in: true,
+
+ onEnter,
+
+ onEntering,
+
+ onEntered(){
+ assert.ok(onEnter.calledOnce);
+ assert.ok(onEntering.calledOnce);
+ assert.ok(onEnter.calledBefore(onEntering));
+ done();
+ }
+ });
+ });
+
+ it('should move to each transition state', done => {
+ let count = 0;
+
+ instance.state.in.should.equal(false);
+
+ instance = instance.renderWithProps({
+
+ in: true,
+
+ onEnter(){
+ count++;
+ instance.state.in.should.equal(false);
+ instance.state.transitioning.should.equal(false);
+ },
+
+ onEntering(){
+ count++;
+ instance.state.in.should.equal(true);
+ instance.state.transitioning.should.equal(true);
+ },
+
+ onEntered(){
+ instance.state.in.should.equal(true);
+ instance.state.transitioning.should.equal(false);
+ assert.ok(count === 2);
+ done();
+ }
+ });
+ });
+
+ it('should apply classes at each transition state', done => {
+ let count = 0;
+
+ instance.state.in.should.equal(false);
+
+ instance = instance.renderWithProps({
+
+ in: true,
+
+ onEnter(node){
+ count++;
+ assert.equal(node.className, '');
+ },
+
+ onEntering(node){
+ count++;
+ assert.equal(node.className, 'test-entering');
+ },
+
+ onEntered(node){
+ assert.equal(node.className, 'test-enter');
+ assert.ok(count === 2);
+ done();
+ }
+ });
+ });
+
+ });
+
+
+ describe('exiting', ()=> {
+ let instance;
+
+ beforeEach(function(){
+ instance = render(
+
+
+
+ );
+ });
+
+ it('should fire callbacks', done => {
+ let onExit = sinon.spy();
+ let onExiting = sinon.spy();
+
+ instance.state.in.should.equal(true);
+
+ instance = instance.renderWithProps({
+
+ in: false,
+
+ onExit,
+
+ onExiting,
+
+ onExited(){
+ assert.ok(onExit.calledOnce);
+ assert.ok(onExiting.calledOnce);
+ assert.ok(onExit.calledBefore(onExiting));
+ done();
+ }
+ });
+ });
+
+ it('should move to each transition state', done => {
+ let count = 0;
+
+ instance.state.in.should.equal(true);
+
+ instance = instance.renderWithProps({
+
+ in: false,
+
+ onExit(){
+ count++;
+ instance.state.in.should.equal(true);
+ instance.state.transitioning.should.equal(false);
+ },
+
+ onExiting(){
+ count++;
+ instance.state.in.should.equal(false);
+ instance.state.transitioning.should.equal(true);
+ },
+
+ onExited(){
+ instance.state.in.should.equal(false);
+ instance.state.transitioning.should.equal(false);
+ //assert.ok(count === 2);
+ done();
+ }
+ });
+ });
+
+ it('should apply classes at each transition state', done => {
+ let count = 0;
+
+ instance.state.in.should.equal(true);
+
+ instance = instance.renderWithProps({
+
+ in: false,
+
+ onExit(node){
+ count++;
+ assert.equal(node.className, '');
+ },
+
+ onExiting(node){
+ count++;
+ assert.equal(node.className, 'test-exiting');
+ },
+
+ onExited(node){
+ assert.equal(node.className, 'test-exit');
+ assert.ok(count === 2);
+ done();
+ }
+ });
+ });
+
+ });
+
+});
From 0503507dd26d5a08f0a79339b67868e5f6b32c7e Mon Sep 17 00:00:00 2001
From: jquense
Date: Sat, 11 Jul 2015 11:14:06 -0400
Subject: [PATCH 24/27] [added] Collapse Component, replaces CollapsibleMixin
---
docs/examples/Collapse.js | 28 +++++
src/Collapse.js | 200 +++++++++++++++++++++++++++++++++++
src/CollapsibleNav.js | 54 +++++-----
src/Nav.js | 30 ++----
src/Panel.js | 49 +++++----
test/CollapseSpec.js | 216 ++++++++++++++++++++++++++++++++++++++
6 files changed, 513 insertions(+), 64 deletions(-)
create mode 100644 docs/examples/Collapse.js
create mode 100644 src/Collapse.js
create mode 100644 test/CollapseSpec.js
diff --git a/docs/examples/Collapse.js b/docs/examples/Collapse.js
new file mode 100644
index 0000000000..c30069d8d0
--- /dev/null
+++ b/docs/examples/Collapse.js
@@ -0,0 +1,28 @@
+class Example extends React.Component {
+ constructor(...args){
+ super(...args);
+
+ this.state = {};
+ }
+
+ render(){
+
+ return (
+
+
+
+
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
+ Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
+
+
+
+
+ );
+ }
+}
+
+React.render(, mountNode);
diff --git a/src/Collapse.js b/src/Collapse.js
new file mode 100644
index 0000000000..0b14eec0b4
--- /dev/null
+++ b/src/Collapse.js
@@ -0,0 +1,200 @@
+/*eslint-disable react/prop-types */
+'use strict';
+import React from 'react';
+import Transition from './Transition';
+import domUtils from './utils/domUtils';
+import createChainedFunction from './utils/createChainedFunction';
+
+let capitalize = str => str[0].toUpperCase() + str.substr(1);
+
+// reading a dimension prop will cause the browser to recalculate,
+// which will let our animations work
+let triggerBrowserReflow = node => node.offsetHeight; //eslint-disable-line no-unused-expressions
+
+const MARGINS = {
+ height: ['marginTop', 'marginBottom'],
+ width: ['marginLeft', 'marginRight']
+};
+
+function getDimensionValue(dimension, elem){
+ let value = elem[`offset${capitalize(dimension)}`];
+ let computedStyles = domUtils.getComputedStyles(elem);
+ let margins = MARGINS[dimension];
+
+ return (value +
+ parseInt(computedStyles[margins[0]], 10) +
+ parseInt(computedStyles[margins[1]], 10)
+ );
+}
+
+class Collapse extends React.Component {
+
+ constructor(props, context){
+ super(props, context);
+
+ this.onEnterListener = this.handleEnter.bind(this);
+ this.onEnteringListener = this.handleEntering.bind(this);
+ this.onEnteredListener = this.handleEntered.bind(this);
+ this.onExitListener = this.handleExit.bind(this);
+ this.onExitingListener = this.handleExiting.bind(this);
+ }
+
+ render() {
+ let enter = createChainedFunction(this.onEnterListener, this.props.onEnter);
+ let entering = createChainedFunction(this.onEnteringListener, this.props.onEntering);
+ let entered = createChainedFunction(this.onEnteredListener, this.props.onEntered);
+ let exit = createChainedFunction(this.onExitListener, this.props.onExit);
+ let exiting = createChainedFunction(this.onExitingListener, this.props.onExiting);
+
+ return (
+
+ { this.props.children }
+
+ );
+ }
+
+ /* -- Expanding -- */
+ handleEnter(elem){
+ let dimension = this._dimension();
+ elem.style[dimension] = '0';
+ }
+
+ handleEntering(elem){
+ let dimension = this._dimension();
+
+ elem.style[dimension] = this._getScrollDimensionValue(elem, dimension);
+ }
+
+ handleEntered(elem){
+ let dimension = this._dimension();
+ elem.style[dimension] = null;
+ }
+
+ /* -- Collapsing -- */
+ handleExit(elem){
+ let dimension = this._dimension();
+
+ elem.style[dimension] = this.props.getDimensionValue(dimension, elem) + 'px';
+ }
+
+ handleExiting(elem){
+ let dimension = this._dimension();
+
+ triggerBrowserReflow(elem);
+ elem.style[dimension] = '0';
+ }
+
+ _dimension(){
+ return typeof this.props.dimension === 'function'
+ ? this.props.dimension()
+ : this.props.dimension;
+ }
+
+ //for testing
+ _getTransitionInstance(){
+ return this.refs.transition;
+ }
+
+ _getScrollDimensionValue(elem, dimension){
+ return elem[`scroll${capitalize(dimension)}`] + 'px';
+ }
+}
+
+Collapse.propTypes = {
+ /**
+ * Collapse the Component in or out.
+ */
+ in: React.PropTypes.bool,
+
+ /**
+ * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * original browser transition end events are canceled.
+ */
+ duration: React.PropTypes.number,
+
+ /**
+ * Specifies the dimension used when collapsing.
+ *
+ * _Note: Bootstrap only partially supports this!
+ * You will need to supply your own css animation for the `.width` css class._
+ */
+ dimension: React.PropTypes.oneOfType([
+ React.PropTypes.oneOf(['height', 'width']),
+ React.PropTypes.func
+ ]),
+
+ /**
+ * A function that returns the height or width of the animating DOM node. Allows for providing some custom logic how much
+ * Collapse component should animation in its specified dimension.
+ *
+ * `getDimensionValue` is called with the current dimension prop value and the DOM node.
+ */
+ getDimensionValue: React.PropTypes.func,
+
+ /**
+ * A Callback fired before the component starts to expand.
+ */
+ onEnter: React.PropTypes.func,
+
+ /**
+ * A Callback fired immediately after the component starts to expand.
+ */
+ onEntering: React.PropTypes.func,
+
+ /**
+ * A Callback fired after the component has expanded.
+ */
+ onEntered: React.PropTypes.func,
+
+ /**
+ * A Callback fired before the component starts to collapse.
+ */
+ onExit: React.PropTypes.func,
+
+ /**
+ * A Callback fired immediately after the component starts to collapse.
+ */
+ onExiting: React.PropTypes.func,
+
+ /**
+ * A Callback fired after the component has collapsed.
+ */
+ onExited: React.PropTypes.func,
+
+ /**
+ * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
+ */
+ unmountOnExit: React.PropTypes.bool,
+
+ /**
+ * Specify whether the component should collapse or expand when it mounts.
+ */
+ transitionAppear: React.PropTypes.bool
+};
+
+Collapse.defaultProps = {
+ in: false,
+ duration: 300,
+ dimension: 'height',
+ transitionAppear: false,
+ unmountOnExit: false,
+ getDimensionValue
+};
+
+export default Collapse;
+
diff --git a/src/CollapsibleNav.js b/src/CollapsibleNav.js
index 427d0b30f3..027f7cb29a 100644
--- a/src/CollapsibleNav.js
+++ b/src/CollapsibleNav.js
@@ -1,14 +1,13 @@
import React, { cloneElement } from 'react';
import BootstrapMixin from './BootstrapMixin';
-import CollapsibleMixin from './CollapsibleMixin';
+import Collapse from './Collapse';
import classNames from 'classnames';
-import domUtils from './utils/domUtils';
import ValidComponentChildren from './utils/ValidComponentChildren';
import createChainedFunction from './utils/createChainedFunction';
const CollapsibleNav = React.createClass({
- mixins: [BootstrapMixin, CollapsibleMixin],
+ mixins: [BootstrapMixin],
propTypes: {
onSelect: React.PropTypes.func,
@@ -19,41 +18,48 @@ const CollapsibleNav = React.createClass({
eventKey: React.PropTypes.any
},
- getCollapsibleDOMNode() {
- return React.findDOMNode(this);
- },
- getCollapsibleDimensionValue() {
- let height = 0;
- let nodes = this.refs;
- for (let key in nodes) {
- if (nodes.hasOwnProperty(key)) {
+ // getCollapsibleDimensionValue() {
+ // let height = 0;
+ // let nodes = this.refs;
+ // for (let key in nodes) {
+ // if (nodes.hasOwnProperty(key)) {
- let n = React.findDOMNode(nodes[key]);
- let h = n.offsetHeight;
- let computedStyles = domUtils.getComputedStyles(n);
+ // let n = React.findDOMNode(nodes[key]);
+ // let h = n.offsetHeight;
+ // let computedStyles = domUtils.getComputedStyles(n);
- height += (h +
- parseInt(computedStyles.marginTop, 10) +
- parseInt(computedStyles.marginBottom, 10)
- );
- }
- }
- return height;
- },
+ // height += (h +
+ // parseInt(computedStyles.marginTop, 10) +
+ // parseInt(computedStyles.marginBottom, 10)
+ // );
+ // }
+ // }
+ // return height;
+ // },
render() {
/*
* this.props.collapsible is set in NavBar when an eventKey is supplied.
*/
- const classes = this.props.collapsible ? this.getCollapsibleClassSet('navbar-collapse') : null;
+ const classes = this.props.collapsible ? 'navbar-collapse' : null;
const renderChildren = this.props.collapsible ? this.renderCollapsibleNavChildren : this.renderChildren;
- return (
+ let nav = (
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
+ Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
+
+
+
+
+ );
+ }
+}
+
+React.render(, mountNode);
diff --git a/src/Fade.js b/src/Fade.js
new file mode 100644
index 0000000000..b91cb32b51
--- /dev/null
+++ b/src/Fade.js
@@ -0,0 +1,90 @@
+'use strict';
+import React from 'react';
+import Transition from './Transition';
+
+class Fade extends React.Component {
+
+ constructor(props, context){
+ super(props, context);
+ }
+
+ render() {
+ return (
+
+ { this.props.children }
+
+ );
+ }
+}
+
+Fade.propTypes = {
+ /**
+ * Fade the Component in or out.
+ */
+ in: React.PropTypes.bool,
+
+ /**
+ * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * original browser transition end events are canceled.
+ */
+ duration: React.PropTypes.number,
+
+ /**
+ * A Callback fired before the component starts to fade in.
+ */
+ onEnter: React.PropTypes.func,
+
+ /**
+ * A Callback fired immediately after the component has started to faded in.
+ */
+ onEntering: React.PropTypes.func,
+
+ /**
+ * A Callback fired after the component has faded in.
+ */
+ onEntered: React.PropTypes.func,
+
+ /**
+ * A Callback fired before the component starts to fade out.
+ */
+ onExit: React.PropTypes.func,
+
+ /**
+ * A Callback fired immediately after the component has started to faded out.
+ */
+ onExiting: React.PropTypes.func,
+
+ /**
+ * A Callback fired after the component has faded out.
+ */
+ onExited: React.PropTypes.func,
+
+
+ /**
+ * Specify whether the transitioning component should be unmounted (removed from the DOM) once the exit animation finishes.
+ */
+ unmountOnExit: React.PropTypes.bool,
+
+ /**
+ * Specify whether the component should fade in or out when it mounts.
+ */
+ transitionAppear: React.PropTypes.bool
+
+};
+
+Fade.defaultProps = {
+ in: false,
+ duration: 300,
+ dimension: 'height',
+ transitionAppear: false,
+ unmountOnExit: false
+};
+
+export default Fade;
+
diff --git a/src/Modal.js b/src/Modal.js
index 3816318c51..55df65f433 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -4,11 +4,11 @@ import React, { cloneElement } from 'react';
import classNames from 'classnames';
import createChainedFunction from './utils/createChainedFunction';
import BootstrapMixin from './BootstrapMixin';
-import FadeMixin from './FadeMixin';
import domUtils from './utils/domUtils';
import EventListener from './utils/EventListener';
import Portal from './Portal';
+import Fade from './Fade';
import Body from './ModalBody';
import Header from './ModalHeader';
@@ -90,12 +90,17 @@ function getScrollbarSize(){
document.body.removeChild(scrollDiv);
scrollDiv = null;
+ return scrollbarSize;
}
const ModalMarkup = React.createClass({
+<<<<<<< HEAD
mixins: [ BootstrapMixin, FadeMixin ],
+=======
+ mixins: [ BootstrapMixin ],
+>>>>>>> [added] Fade Component, replaces FadeMixin
propTypes: {
@@ -166,8 +171,7 @@ const ModalMarkup = React.createClass({
let classes = {
modal: true,
- fade: this.props.animation,
- 'in': !this.props.animation
+ in: this.props.show && !this.props.animation
};
let modal = (
@@ -206,18 +210,22 @@ const ModalMarkup = React.createClass({
},
renderBackdrop(modal) {
- let classes = {
- 'modal-backdrop': true,
- fade: this.props.animation,
- 'in': !this.props.animation
- };
-
- let onClick = this.props.backdrop === true ?
- this.handleBackdropClick : null;
+ let { animation } = this.props;
+ let duration = Modal.BACKDROP_TRANSITION_DURATION; //eslint-disable-line no-use-before-define
+
+ let backdrop = (
+
+ );
return (
);
@@ -381,16 +389,40 @@ const Modal = React.createClass({
...ModalMarkup.propTypes
},
+ getDefaultProps(){
+ return {
+ show: false,
+ animation: true
+ };
+ },
+
render() {
- let { show, ...props } = this.props;
+ let { children, ...props } = this.props;
+
+ let show = !!props.show;
let modal = (
- {this.props.children}
+
+ { children }
+
);
return (
-
- { show && modal }
+
+ { props.animation
+ ? (
+
+ { modal }
+
+ )
+ : show && modal
+ }
+
);
}
@@ -401,4 +433,7 @@ Modal.Header = Header;
Modal.Title = Title;
Modal.Footer = Footer;
+Modal.TRANSITION_DURATION = 300;
+Modal.BACKDROP_TRANSITION_DURATION = 150;
+
export default Modal;
diff --git a/src/Overlay.js b/src/Overlay.js
index d425fe91a4..a5b27d923f 100644
--- a/src/Overlay.js
+++ b/src/Overlay.js
@@ -1,13 +1,26 @@
/*eslint-disable object-shorthand, react/prop-types */
-import React from 'react';
+import React, { cloneElement } from 'react';
import Portal from './Portal';
import Position from './Position';
import RootCloseWrapper from './RootCloseWrapper';
+import CustomPropTypes from './utils/CustomPropTypes';
+import Fade from './Fade';
+import classNames from 'classnames';
+
class Overlay extends React.Component {
constructor(props, context){
super(props, context);
+
+ this.state = { exited: false };
+ this.onHiddenListener = this.handleHidden.bind(this);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.props.show){
+ this.setState({ exited: false });
+ }
}
render(){
@@ -17,30 +30,60 @@ class Overlay extends React.Component {
, target
, placement
, rootClose
+ , children
+ , animation: Transition
, ...props } = this.props;
- let positionedChild = (
-
- { this.props.children }
-
- );
+ let child = null;
- if (rootClose) {
- positionedChild = (
-
- { positionedChild }
-
+ if ( Transition === true ){
+ Transition = Fade;
+ }
+
+ if (props.show || (Transition && !this.state.exited)) {
+
+ child = children;
+
+ // Position the child before the animation to avoid `null` DOM nodes
+ child = (
+
+ { child }
+
);
+
+ child = Transition
+ ? (
+
+ { child }
+
+ )
+ : cloneElement(child, { className: classNames('in', child.className) });
+
+
+ if (rootClose) {
+ child = (
+
+ { child }
+
+ );
+ }
}
return (
-
- { props.show &&
- positionedChild
- }
+
+ { child }
);
}
+
+ handleHidden(){
+ this.setState({ exited: true });
+ }
}
Overlay.propTypes = {
@@ -57,7 +100,19 @@ Overlay.propTypes = {
/**
* A Callback fired by the Overlay when it wishes to be hidden.
*/
- onHide: React.PropTypes.func
+ onHide: React.PropTypes.func,
+
+ /**
+ * Use animation
+ */
+ animation: React.PropTypes.oneOfType([
+ React.PropTypes.bool,
+ CustomPropTypes.elementType
+ ])
+};
+
+Overlay.defaultProps = {
+ animation: Fade
};
export default Overlay;
diff --git a/src/Popover.js b/src/Popover.js
index e24b0c51a6..1133703238 100644
--- a/src/Popover.js
+++ b/src/Popover.js
@@ -2,12 +2,11 @@
import React from 'react';
import classNames from 'classnames';
import BootstrapMixin from './BootstrapMixin';
-import FadeMixin from './FadeMixin';
import CustomPropTypes from './utils/CustomPropTypes';
const Popover = React.createClass({
- mixins: [BootstrapMixin, FadeMixin],
+ mixins: [ BootstrapMixin ],
propTypes: {
/**
@@ -42,15 +41,11 @@ const Popover = React.createClass({
arrowOffsetTop: React.PropTypes.oneOfType([
React.PropTypes.number, React.PropTypes.string
]),
+
/**
* Title text
*/
- title: React.PropTypes.node,
- /**
- * Specify whether the Popover should be use show and hide animations.
- */
- animation: React.PropTypes.bool
-
+ title: React.PropTypes.node
},
@@ -64,10 +59,7 @@ const Popover = React.createClass({
render() {
const classes = {
'popover': true,
- [this.props.placement]: true,
- // in class will be added by the FadeMixin when the animation property is true
- 'in': !this.props.animation && (this.props.positionLeft != null || this.props.positionTop != null),
- 'fade': this.props.animation
+ [this.props.placement]: true
};
const style = {
diff --git a/src/Position.js b/src/Position.js
index b92c256965..36f9d817ba 100644
--- a/src/Position.js
+++ b/src/Position.js
@@ -31,13 +31,13 @@ class Position extends React.Component {
}
render() {
- let { placement, children } = this.props;
+ let { children, ...props } = this.props;
let { positionLeft, positionTop, ...arrows } = this.props.target ? this.state : {};
return cloneElement(
React.Children.only(children), {
+ ...props,
...arrows,
- placement,
positionTop,
positionLeft,
style: {
@@ -61,13 +61,18 @@ class Position extends React.Component {
return;
}
+ let overlay = React.findDOMNode(this);
let target = React.findDOMNode(this.props.target(this.props));
let container = React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body;
+ // if ( !overlay || !target || !container ){
+ // return;
+ // }
+
this.setState(
calcOverlayPosition(
this.props.placement
- , React.findDOMNode(this)
+ , overlay
, target
, container
, this.props.containerPadding));
diff --git a/src/Tooltip.js b/src/Tooltip.js
index 640267829e..e21c57c845 100644
--- a/src/Tooltip.js
+++ b/src/Tooltip.js
@@ -2,11 +2,10 @@
import React from 'react';
import classNames from 'classnames';
import BootstrapMixin from './BootstrapMixin';
-import FadeMixin from './FadeMixin';
import CustomPropTypes from './utils/CustomPropTypes';
const Tooltip = React.createClass({
- mixins: [BootstrapMixin, FadeMixin],
+ mixins: [BootstrapMixin],
propTypes: {
/**
@@ -44,11 +43,7 @@ const Tooltip = React.createClass({
/**
* Title text
*/
- title: React.PropTypes.node,
- /**
- * Specify whether the Tooltip should be use show and hide animations.
- */
- animation: React.PropTypes.bool
+ title: React.PropTypes.node
},
getDefaultProps() {
@@ -61,10 +56,7 @@ const Tooltip = React.createClass({
render() {
const classes = {
'tooltip': true,
- [this.props.placement]: true,
- // in class will be added by the FadeMixin when the animation property is true
- 'in': !this.props.animation && (this.props.positionLeft != null || this.props.positionTop != null),
- 'fade': this.props.animation
+ [this.props.placement]: true
};
const style = {
diff --git a/test/FadeSpec.js b/test/FadeSpec.js
new file mode 100644
index 0000000000..1aa710fcd6
--- /dev/null
+++ b/test/FadeSpec.js
@@ -0,0 +1,82 @@
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import Fade from '../src/Fade';
+//import classNames from 'classnames';
+
+describe('Fade', function () {
+
+ let Component, instance;
+
+ beforeEach(function(){
+
+ Component = React.createClass({
+ render(){
+ let { children, ...props } = this.props;
+
+ return (
+ this.fade = r}
+ {...props}
+ >
+
+ {children}
+
+
+ );
+ }
+ });
+ });
+
+ it('Should default to hidden', function () {
+ instance = ReactTestUtils.renderIntoDocument(
+ Panel content
+ );
+
+ assert.ok(
+ instance.fade.props.in === false);
+ });
+
+ it('Should always have the "fade" class', () => {
+ instance = ReactTestUtils.renderIntoDocument(
+ Panel content
+ );
+
+ assert.ok(
+ instance.fade.props.in === false);
+
+ assert.equal(
+ React.findDOMNode(instance).className, 'fade');
+
+ });
+
+ it('Should add "in" class when entering', done => {
+ instance = ReactTestUtils.renderIntoDocument(
+ Panel content
+ );
+
+ function onEntering(){
+ assert.equal(React.findDOMNode(instance).className, 'fade in');
+ done();
+ }
+
+ assert.ok(
+ instance.fade.props.in === false);
+
+ instance.setProps({ in: true, onEntering });
+ });
+
+ it('Should remove "in" class when exiting', done => {
+ instance = ReactTestUtils.renderIntoDocument(
+ Panel content
+ );
+
+ function onExiting(){
+ assert.equal(React.findDOMNode(instance).className, 'fade');
+ done();
+ }
+
+ assert.equal(
+ React.findDOMNode(instance).className, 'fade in');
+
+ instance.setProps({ in: false, onExiting });
+ });
+});
diff --git a/test/ModalSpec.js b/test/ModalSpec.js
index 6065c0bbc3..d45636cd1d 100644
--- a/test/ModalSpec.js
+++ b/test/ModalSpec.js
@@ -3,6 +3,7 @@ import ReactTestUtils from 'react/lib/ReactTestUtils';
import Modal from '../src/Modal';
import { render } from './helpers';
+
describe('Modal', function () {
let mountPoint;
@@ -172,10 +173,8 @@ describe('Modal', function () {
document.activeElement.should.equal(focusableContainer);
});
- it('Should not focus on the Modal when autoFocus is false', function () {
-
- document.activeElement.should.equal(focusableContainer);
+ it('Should not focus on the Modal when autoFocus is false', function () {
render(
{}} animation={false}>
Message
diff --git a/test/PopoverSpec.js b/test/PopoverSpec.js
index 45b6f00757..30509376e4 100644
--- a/test/PopoverSpec.js
+++ b/test/PopoverSpec.js
@@ -11,16 +11,16 @@ describe('Popover', function () {
);
assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'popover-title'));
assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'popover-content'));
- assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'fade'));
+
assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong'));
});
- it('Should not have the fade class if animation is false', function () {
- let instance = ReactTestUtils.renderIntoDocument(
-
- Popover Content
-
- );
- assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
- });
+ // it('Should not have the fade class if animation is false', function () {
+ // let instance = ReactTestUtils.renderIntoDocument(
+ //
+ // Popover Content
+ //
+ // );
+ // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
+ // });
});
diff --git a/test/TooltipSpec.js b/test/TooltipSpec.js
index a07bb06073..a21c6317bf 100644
--- a/test/TooltipSpec.js
+++ b/test/TooltipSpec.js
@@ -10,15 +10,15 @@ describe('Tooltip', function () {
);
assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong'));
- assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'fade'));
- });
- it('Should not have the fade class if animation is false', function () {
- let instance = ReactTestUtils.renderIntoDocument(
-
- Tooltip Content
-
- );
- assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
});
+
+ // it('Should not have the fade class if animation is false', function () {
+ // let instance = ReactTestUtils.renderIntoDocument(
+ //
+ // Tooltip Content
+ //
+ // );
+ // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
+ // });
});
From 411050ba8b1e7780b61a25237e9a43cfc4243dce Mon Sep 17 00:00:00 2001
From: jquense
Date: Sat, 11 Jul 2015 11:15:18 -0400
Subject: [PATCH 26/27] add docs and exports
---
docs/examples/.eslintrc | 4 +++-
docs/src/ComponentsPage.js | 21 +++++++++++++++++++--
docs/src/ReactPlayground.js | 11 +++++++++++
docs/src/Samples.js | 3 +++
src/Collapse.js | 2 +-
src/Input.js | 2 +-
src/index.js | 3 +++
7 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/docs/examples/.eslintrc b/docs/examples/.eslintrc
index 9dbc49c623..00c22821a7 100644
--- a/docs/examples/.eslintrc
+++ b/docs/examples/.eslintrc
@@ -52,6 +52,8 @@
"TabPane",
"Tooltip",
"Well",
- "Thumbnail"
+ "Thumbnail",
+ "Collapse",
+ "Fade"
}
}
diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js
index 8e656f7048..52f422f6a7 100644
--- a/docs/src/ComponentsPage.js
+++ b/docs/src/ComponentsPage.js
@@ -56,6 +56,7 @@ const ComponentsPage = React.createClass({
+
{/* Buttons */}
Buttons Button
@@ -843,7 +844,7 @@ const ComponentsPage = React.createClass({
equivalent to jQuery's .appendTo(), which is helpful for components that need to be appended to a DOM node other than
the component's direct parent. The Modal, and Overlay components use the Portal component internally.
-
Props
+
Props
@@ -852,9 +853,25 @@ const ComponentsPage = React.createClass({
A Component that absolutely positions its child to a target component or DOM node. Useful for creating custom
popups or tooltips. Used by the Overlay Components.
-
Props
+
Props
+
+
Transitions
+
+
Collapse
+
Add a collapse toggle animation to an element or component.
+
+
+
Props
+
+
+
Fade
+
Add a fade animation to a child element or component.
+
+
+
Props
+
diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js
index 4ab4cd230b..4e60798965 100644
--- a/docs/src/ReactPlayground.js
+++ b/docs/src/ReactPlayground.js
@@ -6,13 +6,19 @@ import * as modBadge from '../../src/Badge';
import * as modButton from '../../src/Button';
import * as modButtonGroup from '../../src/ButtonGroup';
import * as modButtonInput from '../../src/ButtonInput';
+
import * as modButtonToolbar from '../../src/ButtonToolbar';
+import * as modCollapse from '../../src/Collapse';
+
import * as modCollapsibleNav from '../../src/CollapsibleNav';
import * as modCollapsibleMixin from '../../src/CollapsibleMixin';
import * as modCarousel from '../../src/Carousel';
import * as modCarouselItem from '../../src/CarouselItem';
import * as modCol from '../../src/Col';
import * as modDropdownButton from '../../src/DropdownButton';
+
+import * as modFade from '../../src/Fade';
+
import * as modFormControls from '../../src/FormControls';
import * as modGlyphicon from '../../src/Glyphicon';
import * as modGrid from '../../src/Grid';
@@ -57,8 +63,13 @@ import CodeExample from './CodeExample';
const classNames = modClassNames.default;
+
/* eslint-disable */
+
const Portal = modPortal.default;
+const Collapse = modCollapse.default;
+const Fade = modFade.default;
+
const React = modReact.default;
const Accordion = modAccordion.default;
diff --git a/docs/src/Samples.js b/docs/src/Samples.js
index e6401dc08f..d8a4f8caf1 100644
--- a/docs/src/Samples.js
+++ b/docs/src/Samples.js
@@ -1,6 +1,9 @@
/* eslint no-path-concat: 0, no-var: 0 */
export default {
+ Collapse: require('fs').readFileSync(__dirname + '/../examples/Collapse.js', 'utf8'),
+ Fade: require('fs').readFileSync(__dirname + '/../examples/Fade.js', 'utf8'),
+
ButtonTypes: require('fs').readFileSync(__dirname + '/../examples/ButtonTypes.js', 'utf8'),
ButtonSizes: require('fs').readFileSync(__dirname + '/../examples/ButtonSizes.js', 'utf8'),
ButtonBlock: require('fs').readFileSync(__dirname + '/../examples/ButtonBlock.js', 'utf8'),
diff --git a/src/Collapse.js b/src/Collapse.js
index 0b14eec0b4..f9799b8b60 100644
--- a/src/Collapse.js
+++ b/src/Collapse.js
@@ -130,7 +130,7 @@ Collapse.propTypes = {
/**
* Specifies the dimension used when collapsing.
*
- * _Note: Bootstrap only partially supports this!
+ * _Note: Bootstrap only partially supports 'width'!
* You will need to supply your own css animation for the `.width` css class._
*/
dimension: React.PropTypes.oneOfType([
diff --git a/src/Input.js b/src/Input.js
index ec1917da8e..2e81cac1bc 100644
--- a/src/Input.js
+++ b/src/Input.js
@@ -5,7 +5,7 @@ import deprecationWarning from './utils/deprecationWarning';
class Input extends InputBase {
render() {
- if (this.props.type === 'static') { //eslint-disable-line react/prop-types
+ if (this.props.type === 'static') { // eslint-disable-line react/prop-types
deprecationWarning('Input type=static', 'StaticText');
return ;
}
diff --git a/src/index.js b/src/index.js
index 70802d2b28..ae6077a6f2 100644
--- a/src/index.js
+++ b/src/index.js
@@ -63,5 +63,8 @@ export Well from './Well';
export Portal from './Portal';
export Position from './Position';
+export Collapse from './Collapse';
+export Fade from './Collapse';
+
export * as FormControls from './FormControls';
export * as utils from './utils';
From c3b41af621e9b4d4fbdc8a095b2d666420735534 Mon Sep 17 00:00:00 2001
From: jquense
Date: Sun, 12 Jul 2015 16:46:04 -0400
Subject: [PATCH 27/27] fix typos, and remove comments
---
src/Collapse.js | 6 ++----
src/CollapsibleMixin.js | 10 ++++++++++
src/CollapsibleNav.js | 19 -------------------
src/Fade.js | 3 +--
src/FadeMixin.js | 10 ++++++++++
src/Modal.js | 4 ----
src/Position.js | 4 ----
src/Transition.js | 13 ++++++-------
test/CollapsibleMixinSpec.js | 6 ++++++
test/FadeMixinSpec.js | 7 +++++++
test/FadeSpec.js | 1 -
test/PopoverSpec.js | 8 --------
test/TooltipSpec.js | 8 --------
test/helpers.js | 2 +-
14 files changed, 43 insertions(+), 58 deletions(-)
diff --git a/src/Collapse.js b/src/Collapse.js
index f9799b8b60..505c116085 100644
--- a/src/Collapse.js
+++ b/src/Collapse.js
@@ -1,5 +1,3 @@
-/*eslint-disable react/prop-types */
-'use strict';
import React from 'react';
import Transition from './Transition';
import domUtils from './utils/domUtils';
@@ -122,7 +120,7 @@ Collapse.propTypes = {
in: React.PropTypes.bool,
/**
- * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * Provide the duration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
* original browser transition end events are canceled.
*/
duration: React.PropTypes.number,
@@ -140,7 +138,7 @@ Collapse.propTypes = {
/**
* A function that returns the height or width of the animating DOM node. Allows for providing some custom logic how much
- * Collapse component should animation in its specified dimension.
+ * Collapse component should animate in its specified dimension.
*
* `getDimensionValue` is called with the current dimension prop value and the DOM node.
*/
diff --git a/src/CollapsibleMixin.js b/src/CollapsibleMixin.js
index 31895e4041..c1abcbaada 100644
--- a/src/CollapsibleMixin.js
+++ b/src/CollapsibleMixin.js
@@ -1,5 +1,8 @@
import React from 'react';
import TransitionEvents from './utils/TransitionEvents';
+import deprecationWarning from './utils/deprecationWarning';
+
+let warned = false;
const CollapsibleMixin = {
@@ -21,6 +24,13 @@ const CollapsibleMixin = {
};
},
+ componentWillMount(){
+ if ( !warned ){
+ deprecationWarning('CollapsibleMixin', 'Collapse Component');
+ warned = true;
+ }
+ },
+
componentWillUpdate(nextProps, nextState){
let willExpanded = nextProps.expanded != null ? nextProps.expanded : nextState.expanded;
if (willExpanded === this.isExpanded()) {
diff --git a/src/CollapsibleNav.js b/src/CollapsibleNav.js
index 027f7cb29a..04a21db3ef 100644
--- a/src/CollapsibleNav.js
+++ b/src/CollapsibleNav.js
@@ -19,25 +19,6 @@ const CollapsibleNav = React.createClass({
},
- // getCollapsibleDimensionValue() {
- // let height = 0;
- // let nodes = this.refs;
- // for (let key in nodes) {
- // if (nodes.hasOwnProperty(key)) {
-
- // let n = React.findDOMNode(nodes[key]);
- // let h = n.offsetHeight;
- // let computedStyles = domUtils.getComputedStyles(n);
-
- // height += (h +
- // parseInt(computedStyles.marginTop, 10) +
- // parseInt(computedStyles.marginBottom, 10)
- // );
- // }
- // }
- // return height;
- // },
-
render() {
/*
* this.props.collapsible is set in NavBar when an eventKey is supplied.
diff --git a/src/Fade.js b/src/Fade.js
index b91cb32b51..523508fff5 100644
--- a/src/Fade.js
+++ b/src/Fade.js
@@ -1,4 +1,3 @@
-'use strict';
import React from 'react';
import Transition from './Transition';
@@ -30,7 +29,7 @@ Fade.propTypes = {
in: React.PropTypes.bool,
/**
- * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * Provide the duration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
* original browser transition end events are canceled.
*/
duration: React.PropTypes.number,
diff --git a/src/FadeMixin.js b/src/FadeMixin.js
index b5597d013d..b4dfe666f2 100644
--- a/src/FadeMixin.js
+++ b/src/FadeMixin.js
@@ -1,5 +1,6 @@
import React from 'react';
import domUtils from './utils/domUtils';
+import deprecationWarning from './utils/deprecationWarning';
// TODO: listen for onTransitionEnd to remove el
function getElementsAndSelf (root, classes){
@@ -16,7 +17,16 @@ function getElementsAndSelf (root, classes){
return els;
}
+let warned = false;
+
export default {
+ componentWillMount(){
+ if ( !warned ){
+ deprecationWarning('FadeMixin', 'Fade Component');
+ warned = true;
+ }
+ },
+
_fadeIn() {
let els;
diff --git a/src/Modal.js b/src/Modal.js
index 55df65f433..881b160e59 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -96,11 +96,7 @@ function getScrollbarSize(){
const ModalMarkup = React.createClass({
-<<<<<<< HEAD
- mixins: [ BootstrapMixin, FadeMixin ],
-=======
mixins: [ BootstrapMixin ],
->>>>>>> [added] Fade Component, replaces FadeMixin
propTypes: {
diff --git a/src/Position.js b/src/Position.js
index 36f9d817ba..c8c11b7904 100644
--- a/src/Position.js
+++ b/src/Position.js
@@ -65,10 +65,6 @@ class Position extends React.Component {
let target = React.findDOMNode(this.props.target(this.props));
let container = React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body;
- // if ( !overlay || !target || !container ){
- // return;
- // }
-
this.setState(
calcOverlayPosition(
this.props.placement
diff --git a/src/Transition.js b/src/Transition.js
index ce8eb200cf..a49124aac7 100644
--- a/src/Transition.js
+++ b/src/Transition.js
@@ -1,4 +1,3 @@
-'use strict';
import React from 'react';
import TransitionEvents from './utils/TransitionEvents';
import classnames from 'classnames';
@@ -163,8 +162,8 @@ class Transition extends React.Component {
let classes = '';
- // for whatever reason classnames() doesn't actually work here,
- // maybe because they aren't always single classes?
+ // using `classnames()` here causes a subtle bug,
+ // hence the verbose if/else if sequence.
if (this.state.in && !this.state.transitioning) {
classes = this.props.enteredClassName;
}
@@ -184,9 +183,9 @@ class Transition extends React.Component {
return React.cloneElement(child, {
...childProps,
className: classnames(
- child.props.className
- , this.props.className
- , classes)
+ child.props.className,
+ this.props.className,
+ classes)
});
}
}
@@ -208,7 +207,7 @@ Transition.propTypes = {
transitionAppear: React.PropTypes.bool,
/**
- * Provide the durration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
+ * Provide the duration of the animation in milliseconds, used to ensure that finishing callbacks are fired even if the
* original browser transition end events are canceled.
*/
duration: React.PropTypes.number,
diff --git a/test/CollapsibleMixinSpec.js b/test/CollapsibleMixinSpec.js
index 324b32ad44..c38527d2ae 100644
--- a/test/CollapsibleMixinSpec.js
+++ b/test/CollapsibleMixinSpec.js
@@ -32,6 +32,12 @@ describe('CollapsibleMixin', function () {
});
});
+ afterEach(()=> {
+ if (console.warn.calledWithMatch('CollapsibleMixin is deprecated')){
+ console.warn.reset();
+ }
+ });
+
describe('getInitialState', function(){
it('Should check defaultExpanded', function () {
instance = ReactTestUtils.renderIntoDocument(
diff --git a/test/FadeMixinSpec.js b/test/FadeMixinSpec.js
index bcd055b5c6..7e6086dd0f 100644
--- a/test/FadeMixinSpec.js
+++ b/test/FadeMixinSpec.js
@@ -19,6 +19,13 @@ describe('FadeMixin', function () {
});
});
+ afterEach(()=> {
+ if (console.warn.calledWithMatch('FadeMixin is deprecated')){
+ console.warn.reset();
+ }
+ });
+
+
it('Should add the in class to all elements', function (done) {
let instance = ReactTestUtils.renderIntoDocument();
diff --git a/test/FadeSpec.js b/test/FadeSpec.js
index 1aa710fcd6..0f85fdfd40 100644
--- a/test/FadeSpec.js
+++ b/test/FadeSpec.js
@@ -1,7 +1,6 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import Fade from '../src/Fade';
-//import classNames from 'classnames';
describe('Fade', function () {
diff --git a/test/PopoverSpec.js b/test/PopoverSpec.js
index 30509376e4..cca9014442 100644
--- a/test/PopoverSpec.js
+++ b/test/PopoverSpec.js
@@ -15,12 +15,4 @@ describe('Popover', function () {
assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'strong'));
});
- // it('Should not have the fade class if animation is false', function () {
- // let instance = ReactTestUtils.renderIntoDocument(
- //
- // Popover Content
- //
- // );
- // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
- // });
});
diff --git a/test/TooltipSpec.js b/test/TooltipSpec.js
index a21c6317bf..01b93bc42c 100644
--- a/test/TooltipSpec.js
+++ b/test/TooltipSpec.js
@@ -13,12 +13,4 @@ describe('Tooltip', function () {
});
- // it('Should not have the fade class if animation is false', function () {
- // let instance = ReactTestUtils.renderIntoDocument(
- //
- // Tooltip Content
- //
- // );
- // assert.equal(React.findDOMNode(instance).className.match(/\bfade\b/), null, 'The fade class should not be present');
- // });
});
diff --git a/test/helpers.js b/test/helpers.js
index 97fb38d589..9df659ecae 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -12,7 +12,7 @@ export function shouldWarn(about) {
* since `setProps` is deprecated.
* @param {ReactElement} element Root element to render
* @param {HTMLElement?} mountPoint Optional mount node, when empty it uses an unattached div like `renderIntoDocument()`
- * @return {ComponentInstance} The instance, with a new method `renderWithProps` which will return a new instance wiht updated props
+ * @return {ComponentInstance} The instance, with a new method `renderWithProps` which will return a new instance with updated props
*/
export function render(element, mountPoint){
let mount = mountPoint || document.createElement('div');