diff --git a/.eslintrc b/.eslintrc index 06e1f98f2a..d52de5d000 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,10 +12,12 @@ "babel" ], "rules": { + "constructor-super": 2, "comma-spacing": 2, "comma-style": [2, "last"], "one-var": [2, { "initialized": "never" }], "key-spacing": 0, + "no-this-before-super": 2, "no-underscore-dangle": 0, "no-unused-vars": [2, { "vars": "all", "args": "none" }], "no-var": 2, diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e91ee2f30..59cae1dbfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +v0.23.7 - Wed, 01 Jul 2015 15:18:30 GMT +--------------------------------------- + +- [35ea201](../../commit/35ea201) [fixed] Accidental breaking change in Modal trigger + + + +v0.23.6 - Wed, 01 Jul 2015 00:48:02 GMT +--------------------------------------- + +- [1b1af04](../../commit/1b1af04) [changed] deprecate ModalTrigger +- [83b4cbc](../../commit/83b4cbc) [changed] Modal doesn't require ModalTrigger +- [d70f617](../../commit/d70f617) [changed] tooltips and popovers required id's for a11y +- [389cf3f](../../commit/389cf3f) [changed] Deprecate OverlayTrigger positioning api and "manual" trigger +- [5eb8666](../../commit/5eb8666) [added] Overlay component +- [1638f69](../../commit/1638f69) [added] Position component for custom Overlays +- [f799110](../../commit/f799110) [added] Portal component; replaces OverlayMixin +- [97ef415](../../commit/97ef415) [fixed] Modal won't steal focus from children +- [a8b177a](../../commit/a8b177a) [fixed] Stack overflow with nested Modals +- [3caa866](../../commit/3caa866) [changed] Update babel-loader +- [6ffa325](../../commit/6ffa325) [fixed] 'componentClass' property type is 'elementType' now +- [0e5980f](../../commit/0e5980f) [added] 'elementType' custom prop type validator +- [8f582d2](../../commit/8f582d2) [changed] Update karma-chrome-launcher. Dev dependency +- [d4089d0](../../commit/d4089d0) [changed] Update eslint-plugin-mocha. Dev dependency +- [fd547f4](../../commit/fd547f4) [changed] Update karma-mocha. Dev dependency. +- [c5797e8](../../commit/c5797e8) [added] componentClass prop to Jumbotron + + + v0.23.5 - Tue, 23 Jun 2015 01:31:35 GMT --------------------------------------- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f939d1f8d..dad46214b5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,9 +17,10 @@ and submitting pull requests, but please respect the following restrictions: - Please do not use the issue tracker for personal support requests. Stack Overflow ([react-bootstrap](http://stackoverflow.com/questions/tagged/react-bootstrap) - tag), [Slack](http://www.reactiflux.com/) or - [gitter](https://gitter.im/react-bootstrap/react-bootstrap) are better places - to get help. + tag), [Slack](http://www.reactiflux.com/), + [gitter](https://gitter.im/react-bootstrap/react-bootstrap), or + [Thinkful](http://start.thinkful.com/react/?utm_source=github&utm_medium=badge&utm_campaign=react-bootstrap) + are better places to get help. - Please do not open issues or pull requests regarding the code in React or Bootstrap (open them in their respective repositories). @@ -68,11 +69,52 @@ doesn't make sense to do this, then it doesn't make sense to use `[changed]` or `[removed]` :). For further reading on writing a well formed commit message, check out these [5 useful tips for a better commit message][commit-message] +### Using `[changed]` with development dependencies updates + +Use `[changed]` if dev-dependency has impact on the resulting code or API. +`babel` is a good example of such dev-dependency. +`chai`, `colors`, `express` or `eslint` are good examples when there is +no need to add `[changed]`. + +## Visual Changes + +When making a visual change, if at all feasible please provide screenshots +and/or screencasts of the proposed change. This will help us to understand the +desired change easier. + ## Docs Please update the docs with any API changes, the code and docs should always be in sync. +Component prop documentation is generated automatically from the React components +and their leading comments. Please make sure to provide comments for any `propTypes` you add +or change in a Component. + +```js +propTypes: { + /** + * Sets the visibility of the Component + */ + show: React.PropTypes.bool, + + /** + * A callback fired when the visibility changes + * @type {func} + * @required + */ + onHide: myCustomPropType +} +``` + +There are a few caveats to this format that differ from conventional JSDoc comments. + +- Only specific doclets (the @ things) should be used, and only when the data cannot be parsed from the component itself + - `@type`: Override the "type", use the same names as the default React PropTypes: string, func, bool, number, object. You can express enum and oneOfType types, Like `{("optionA"|"optionB")}`. + - `@required`: to mark a prop as required (use the normal React isRequired if possible) + - `@private`: Will hide the prop in the documentation +- All description text should be above the doclets. + ## Implement additional components and features This project is seeking parity with the core Bootstrap library. diff --git a/README.md b/README.md index 3b8833288e..3364b9ece7 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![HuBoard][huboard-badge]][huboard] [![Gitter][gitter-badge]][gitter] +[![Thinkful][thinkful-badge]][thinkful] [![NPM version][npm-badge]][npm] [![Bower version][bower-badge]][bower] @@ -75,5 +76,8 @@ Yes please! See the [contributing guidelines][contributing] for details. [huboard-badge]: https://img.shields.io/badge/Hu-Board-7965cc.svg [huboard]: https://huboard.com/react-bootstrap/react-bootstrap +[thinkful-badge]: https://tf-assets-staging.s3.amazonaws.com/badges/thinkful_repo_badge.svg +[thinkful]: http://start.thinkful.com/react/?utm_source=github&utm_medium=badge&utm_campaign=react-bootstrap + [appveyor-badge]: https://ci.appveyor.com/api/projects/status/ylitpyo6n5yq1s6i/branch/master?svg=true [appveyor]: https://ci.appveyor.com/project/react-bootstrap/react-bootstrap/branch/master diff --git a/appveyor.yml b/appveyor.yml index cac1fa5920..430ba4a1af 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ version: '{build}' install: # Get the latest stable version of io.js - ps: Install-Product node '' - - npm -g install npm@latest + - npm -g install npm@2.11.x - set PATH=%APPDATA%\npm;%PATH% - node --version - npm --version diff --git a/docs/assets/style.css b/docs/assets/style.css index 645481e0bf..d03afccb44 100644 --- a/docs/assets/style.css +++ b/docs/assets/style.css @@ -162,3 +162,13 @@ body { position: absolute; } +.prop-table { + background-color: white; +} + +.bs-example.tooltip-static .tooltip { + position: relative; + display: inline-block; + margin: 5px 10px; + +} diff --git a/docs/build.js b/docs/build.js index 1ad8005952..98a01c94e1 100644 --- a/docs/build.js +++ b/docs/build.js @@ -6,6 +6,7 @@ import Root from './src/Root'; import fsp from 'fs-promise'; import { copy } from '../tools/fs-utils'; import { exec } from '../tools/exec'; +import metadata from './generate-metadata'; const repoRoot = path.resolve(__dirname, '../'); const docsBuilt = path.join(repoRoot, 'docs-built'); @@ -21,12 +22,12 @@ const readmeDest = path.join(docsBuilt, 'README.md'); * @return {Promise} promise * @internal */ -function generateHTML(fileName) { +function generateHTML(fileName, propData) { return new Promise((resolve, reject) => { const urlSlug = fileName === 'index.html' ? '/' : `/${fileName}`; Router.run(routes, urlSlug, Handler => { - let html = React.renderToString(React.createElement(Handler)); + let html = React.renderToString(React.createElement(Handler, { propData })); html = '' + html; let write = fsp.writeFile(path.join(docsBuilt, fileName), html); resolve(write); @@ -41,8 +42,10 @@ export default function BuildDocs({dev}) { return exec(`rimraf ${docsBuilt}`) .then(() => fsp.mkdir(docsBuilt)) - .then(() => { - let pagesGenerators = Root.getPages().map(generateHTML); + .then(metadata) + .then(propData => { + + let pagesGenerators = Root.getPages().map( page => generateHTML(page, propData)); return Promise.all(pagesGenerators.concat([ exec(`webpack --config webpack.docs.js --bail ${devOption}`), diff --git a/docs/examples/.eslintrc b/docs/examples/.eslintrc index 6f2fa1bdf5..9dbc49c623 100644 --- a/docs/examples/.eslintrc +++ b/docs/examples/.eslintrc @@ -36,6 +36,7 @@ "ModalTrigger", "OverlayTrigger", "OverlayMixin", + "Overlay", "PageHeader", "PageItem", "Pager", diff --git a/docs/examples/ModalContained.js b/docs/examples/ModalContained.js index 626f17db42..aeab199ef7 100644 --- a/docs/examples/ModalContained.js +++ b/docs/examples/ModalContained.js @@ -9,28 +9,40 @@ * } */ -const ContainedModal = React.createClass({ - render() { - return ( - -
- Elit est explicabo ipsum eaque dolorem blanditiis doloribus sed id ipsam, beatae, rem fuga id earum? Inventore et facilis obcaecati. -
-
- -
-
- ); - } -}); - const Trigger = React.createClass({ + getInitialState(){ + return { show: false }; + }, + render() { + let close = e => this.setState({ show: false}); + return (
- } container={this}> - - + + + + + Contained Modal + + + Elit est explicabo ipsum eaque dolorem blanditiis doloribus sed id ipsam, beatae, rem fuga id earum? Inventore et facilis obcaecati. + + + + +
); } diff --git a/docs/examples/ModalCustomSizing.js b/docs/examples/ModalCustomSizing.js index 5ddeab19de..4122e352d4 100644 --- a/docs/examples/ModalCustomSizing.js +++ b/docs/examples/ModalCustomSizing.js @@ -1,8 +1,11 @@ const MyModal = React.createClass({ render() { 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 @@ -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.

-
-
- -
+ + + +
); } diff --git a/docs/examples/ModalDefaultSizing.js b/docs/examples/ModalDefaultSizing.js index aabd5dfc1c..19c9ffe0d3 100644 --- a/docs/examples/ModalDefaultSizing.js +++ b/docs/examples/ModalDefaultSizing.js @@ -1,8 +1,11 @@ const MySmallModal = React.createClass({ render() { return ( - -
+ + + Modal heading + +

Wrapped Text

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.

@@ -13,10 +16,10 @@ const MySmallModal = React.createClass({

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.

-
-
- -
+ + + +
); } @@ -25,8 +28,11 @@ const MySmallModal = React.createClass({ const MyLargeModal = React.createClass({ render() { return ( - -
+ + + Modal heading + +

Wrapped Text

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.

@@ -37,24 +43,37 @@ const MyLargeModal = React.createClass({

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. -
-
- -
-
- ); - } -}); - -React.render(, mountNode); diff --git a/docs/examples/ModalStatic.js b/docs/examples/ModalStatic.js index f01a4d058e..138c55f050 100644 --- a/docs/examples/ModalStatic.js +++ b/docs/examples/ModalStatic.js @@ -1,18 +1,25 @@ const modalInstance = (
- -
+ onHide={function(){}}> + + + Modal title + + + One fine body... -
-
+ + + -
+
); diff --git a/docs/examples/ModalTrigger.js b/docs/examples/ModalTrigger.js index 36b859d0df..4a25c06f86 100644 --- a/docs/examples/ModalTrigger.js +++ b/docs/examples/ModalTrigger.js @@ -1,42 +1,67 @@ -const MyModal = React.createClass({ +const Example = React.createClass({ + + getInitialState(){ + return { showModal: false }; + }, + + close(){ + this.setState({ showModal: false }); + }, + + open(){ + this.setState({ showModal: true }); + }, + render() { + let popover = very popover. such engagement; + let tooltip = wow.; + return ( - -
-

Text in a modal

-

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.

+ +

Popover in a modal

+

there is a popover here

+ +

Tooltips in a modal

+

there is a tooltip here

+ +
+ +

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.

+
+ + + +
+
); } }); -const overlayTriggerInstance = ( - }> - - -); - -React.render(overlayTriggerInstance, mountNode); +React.render(, mountNode); diff --git a/docs/examples/Overlay.js b/docs/examples/Overlay.js new file mode 100644 index 0000000000..4f5f1b9fde --- /dev/null +++ b/docs/examples/Overlay.js @@ -0,0 +1,43 @@ + +const Example = React.createClass({ + getInitialState(){ + return { show: true }; + }, + + toggle(){ + this.setState({ show: !this.state.show }); + }, + + render(){ + const tooltip = Tooltip overload!; + + const sharedProps = { + show: this.state.show, + container: this, + target: props => React.findDOMNode(this.refs.target) + }; + + return ( +
+ + + + { tooltip } + + + { tooltip } + + + { tooltip } + + + { tooltip } + +
+ ); + } +}); + +React.render(, mountNode); diff --git a/docs/examples/OverlayCustom.js b/docs/examples/OverlayCustom.js new file mode 100644 index 0000000000..20230b56c6 --- /dev/null +++ b/docs/examples/OverlayCustom.js @@ -0,0 +1,45 @@ + +const Example = React.createClass({ + getInitialState(){ + return { show: true }; + }, + + toggle(){ + this.setState({ show: !this.state.show }); + }, + + render(){ + const style = { + position: 'absolute', + backgroundColor: '#EEE', + boxShadow: '0 5px 10px rgba(0, 0, 0, 0.2)', + border: '1px solid #CCC', + borderRadius: 3, + marginLeft: -5, + marginTop: 5, + padding: 10 + }; + + return ( +
+ + + this.setState({ show: false })} + placement="right" + container={this} + target={ props => React.findDOMNode(this.refs.target)} + > +
+ Holy guacamole! Check this info. +
+
+
+ ); + } +}); + +React.render(, mountNode); diff --git a/docs/examples/PopoverContained.js b/docs/examples/PopoverContained.js index afd3019e3e..3e0c036cee 100644 --- a/docs/examples/PopoverContained.js +++ b/docs/examples/PopoverContained.js @@ -1,13 +1,33 @@ -const positionerInstance = ( - - Holy guacamole! Check this info.} - > - - - -); +class Example extends React.Component { + constructor(props, context){ + super(props, context); + this.state = { show: false }; + } + render(){ -React.render(positionerInstance, mountNode); + return ( + + + + React.findDOMNode(this.state.target)} + placement='bottom' + container={mountNode} + containerPadding={20} + > + + Holy guacamole! Check this info. + + + + ); + } +} + +React.render(, mountNode); diff --git a/docs/examples/TooltipBasic.js b/docs/examples/TooltipBasic.js index f2df4f4a66..ff21989d5e 100644 --- a/docs/examples/TooltipBasic.js +++ b/docs/examples/TooltipBasic.js @@ -1,7 +1,18 @@ const tooltipInstance = ( -
- - Holy guacamole! Check this info. +
+ + Tooltip right + + + Tooltip top + + + + Tooltip left + + + + Tooltip bottom
); diff --git a/docs/examples/TooltipInCopy.js b/docs/examples/TooltipInCopy.js index 8842122aee..40c8d09ab0 100644 --- a/docs/examples/TooltipInCopy.js +++ b/docs/examples/TooltipInCopy.js @@ -1,9 +1,9 @@ const LinkWithTooltip = React.createClass({ render() { - let tooltip = {this.props.tooltip}; + let tooltip = {this.props.tooltip}; return ( - + {this.props.children} ); @@ -12,7 +12,13 @@ const LinkWithTooltip = React.createClass({ const copyInstance = (

- 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 {'