Repository: styleguidist/react-styleguidist
Pull request #835 (2018-02-21 14:48:17)

Page per section
Added routing variant that will show sections & components in isolated mode (when `pagePerSection` is set to `true` in the config part), starting with the first section or component for the index page.

There are some minor changes and tweaks in comparison to PR #768. Instead of showing the first component per default, I set the first section or component as the default. I assume most of the larger styleguides start with some kind of _Intro_ or _Getting started_ section anyways. 

This PR should address #494. 

What do you think about it?

codecov-io: # [Codecov](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835?src=pr&el=h1) Report
> Merging [#835](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835?src=pr&el=desc) into [master](https://codecov.io/gh/styleguidist/react-styleguidist/commit/48c0b5a4381fc4720d30bd819ea92bd178fb84f1?src=pr&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `100%`.

| [Impacted Files](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [scripts/schemas/config.js](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835/diff?src=pr&el=tree#diff-c2NyaXB0cy9zY2hlbWFzL2NvbmZpZy5qcw==) | `86.2% <ø> (ø)` | :arrow_up: |
| [loaders/styleguide-loader.js](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835/diff?src=pr&el=tree#diff-bG9hZGVycy9zdHlsZWd1aWRlLWxvYWRlci5qcw==) | `100% <ø> (ø)` | :arrow_up: |
| [...omponents/ComponentsList/ComponentsListRenderer.js](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835/diff?src=pr&el=tree#diff-c3JjL3JzZy1jb21wb25lbnRzL0NvbXBvbmVudHNMaXN0L0NvbXBvbmVudHNMaXN0UmVuZGVyZXIuanM=) | `100% <100%> (ø)` | :arrow_up: |
| [src/rsg-components/StyleGuide/StyleGuide.js](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835/diff?src=pr&el=tree#diff-c3JjL3JzZy1jb21wb25lbnRzL1N0eWxlR3VpZGUvU3R5bGVHdWlkZS5qcw==) | `84.61% <100%> (+1.28%)` | :arrow_up: |
| [src/utils/renderStyleguide.js](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835/diff?src=pr&el=tree#diff-c3JjL3V0aWxzL3JlbmRlclN0eWxlZ3VpZGUuanM=) | `75% <100%> (ø)` | :arrow_up: |
| [src/utils/getRouteData.js](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835/diff?src=pr&el=tree#diff-c3JjL3V0aWxzL2dldFJvdXRlRGF0YS5qcw==) | `100% <100%> (ø)` | :arrow_up: |
| [.../rsg-components/TableOfContents/TableOfContents.js](https://codecov.io/gh/styleguidist/react-styleguidist/pull/835/diff?src=pr&el=tree#diff-c3JjL3JzZy1jb21wb25lbnRzL1RhYmxlT2ZDb250ZW50cy9UYWJsZU9mQ29udGVudHMuanM=) | `94.44% <100%> (+0.32%)` | :arrow_up: |

sapegin: Awesome, thanks for picking it up! I haven't looked into the code yet, let's finalize the API first.

> Instead of showing the first component per default, I set the first section or component as the default. I assume most of the larger styleguides start with some kind of Intro or Getting started section anyways.

I thinks that's a great idea!

What do you think about making the option a string instead of a boolean with values `always`, `never`, `static-only`, with the latter as a default value? `static-only` will show one component in dev mode and all in the static build, I believe that would be most convenient for the majority of users.

SaraVieira: Hi @bitionaire!

This is awesome! This is just what I needed to solve a major problem in our styleguide! Anxiously awaiting for this to get merged! 
bitionaire: > What do you think about making the option a string instead of a boolean with values `always`, `never`, `static-only`, with the latter as a default value? static-only will show one component in dev mode and all in the static build, I believe that would be most convenient for the majority of users.

Sure, maybe there are even more modes we didn't think of yet. Good point! 👍 
I also noticed that the page doesn't scroll to top after `#!/` route changes. I'll fix that, too. 
bitionaire: I just added a few commits, and `pagePerSection` may be defined as a function. `static-only` may look like `pagePerSection: () => (process.env.NODE_ENV !== 'production')`.

I added `window.scrollTo(0,0)` in `componentDidUpdate` of `StyleGuide.js`. The location of this code seems really hacky, and it does not work for all use-cases (e.g. scrolling down and navigating to the same section won't trigger the execution), but I'm not sure where to put it. I'll think about a better solution. If you have any ideas, I'm open to any suggestions :)
tizmagik: Looking forward to this! 🙏
bitionaire: Allright, please have look 👍 
bitionaire: I think we're getting close 🍻 
sapegin: Awesome, thanks! I'll try to make a release soon 🦄 
bitionaire: Hey @sapegin,

I've added one change, can you have a look at that? 
It's hard to define a correct behaviour for the `IsolateButton` if `pagePerSection=true`. Therefore I excluded the button from any toolbars for the `slots` object, based on the styleguide configuration. 

 

Patch:
diff --git a/.github/Contributing.md b/.github/Contributing.md
index 560333e1..3c0ced56 100644
--- a/.github/Contributing.md
+++ b/.github/Contributing.md
@@ -52,6 +52,19 @@ npm run test:jest -- -u
 * We’re using [Prettier](https://github.com/prettier/prettier) to format JavaScript, so don’t worry much about code formatting.
 * Don’t commit generated files, like minified JavaScript.
 * Don’t change version number and change log.
+* If you're updating examples other then `examples/basic`, you'll need to modify your watch and start commands:
+
+```bash
+npm run compile:watch & npm run start:customised # if making changes to examples/customised
+npm run compile:watch & npm run start:sections # if making changes to examples/sections
+```
+See the `scripts` section of the top level [package.json](https://github.com/styleguidist/react-styleguidist/blob/master/package.json#L135)
+
+. If an example doesn't have a script just point to its config:
+
+```bash
+node bin/styleguidist.js server --config examples/path/to/example/styleguide.config.js
+```
 
 ## Need help?
 
diff --git a/docs/Components.md b/docs/Components.md
index 97b7fafc..9946bf7b 100644
--- a/docs/Components.md
+++ b/docs/Components.md
@@ -5,6 +5,7 @@
 <!-- toc -->
 
 * [Finding components](#finding-components)
+* [Loading and exposing components](#loading-and-exposing-components)
 * [Sections](#sections)
 * [Limitations](#limitations)
 
@@ -41,6 +42,63 @@ module.exports = {
 
 > **Note:** Use [getComponentPathLine](Configuration.md#getcomponentpathline) option to change a path you see below a component name.
 
+## Loading and exposing components
+
+Styleguidist _loads_ your components and _exposes_ them globally for your examples to consume.
+
+### Identifier
+
+It will try to use the `displayName` of your component as the identifier. If it cannot understand a `displayName` (for example if it is dynamically generated), it will fall back to something it can understand.
+
+In each of the following cases, the global identifier will be `Component`.
+
+| Path | Code | Styleguidist understands |
+| ---- | ---- | ------------------------ |
+| /whatever.js | `export default function Component() { ... }` | displayName |
+| /whatever.js | `export default function SomeName() { ... }`<br>`SomeName.displayName = 'Component';` | displayName |
+| /whatever.js | `export default function Component() { ... }`<br>`Component.displayName = dynamicNamer();` | displayName at declaration
+| /component.js | `const name = 'SomeName';`<br>`const componentMap = {`<br>`[name]: function() { ... }`<br>`};`<br>`export default componentMap[name];` | File name |
+| /component/index.js | `const name = 'SomeName';`<br>`const componentMap = {`<br>`[name]: function() { ... }`<br>`};`<br>`export default componentMap[name];` | Folder name |
+
+
+### Default vs named exports
+
+Stylegudist will use an ECMAScript module’s `default` export or CommonJS `module.exports` if they are defined.
+
+```javascript
+// /component.js
+export default function Component() { ... }
+// will be exposed globally as Component
+
+// /component.js
+function Component() { ... }
+module.exports = Component;
+// will be exposed globally as Component
+```
+
+If you use only named exports, Styleguidist will expose named exports from modules as follows...
+
+If there is only one named export, it will expose that.
+
+```javascript
+// /component.js
+export function Component() { ... }
+// will be exposed globally as Component
+```
+
+If there are several named exports, it will expose the named export which has the same name as the understood identifier.
+
+```javascript
+// /component.js
+export function someUtil() { ... }
+// will not be exposed
+
+export function Component() { ... }
+// will be exposed globally as Component
+```
+
+If you export several React components as named exports from a single module, Styleguidist is likely to behave unreliably. If it cannot understand which named export to expose, you may not be able to access that export.
+
 ## Sections
 
 Group components into sections or add extra Markdown documents to your style guide.
@@ -49,7 +107,7 @@ Each section consists of (all fields are optional):
 
 * `name` — section title.
 * `content` — location of a Markdown file containing the overview content.
-* `components` — a glob pattern string or a function returning a list of components. The same rules apply as for the root `components` option.
+* `components` — a glob pattern string, an array of component paths or a function returning a list of components. The same rules apply as for the root `components` option.
 * `sections` — array of subsections (can be nested).
 * `description` — A small description of this section.
 * `ignore` — string/array of globs that should not be included in the section.
diff --git a/docs/Configuration.md b/docs/Configuration.md
index 0ddc62e0..8aea7889 100644
--- a/docs/Configuration.md
+++ b/docs/Configuration.md
@@ -20,6 +20,7 @@ By default, Styleguidist will look for `styleguide.config.js` file in your proje
 * [`handlers`](#handlers)
 * [`ignore`](#ignore)
 * [`logger`](#logger)
+* [`pagePerSection`](#pagepersection)
 * [`previewDelay`](#previewdelay)
 * [`propsParser`](#propsparser)
 * [`require`](#require)
@@ -37,6 +38,7 @@ By default, Styleguidist will look for `styleguide.config.js` file in your proje
 * [`template`](#template)
 * [`theme`](#theme)
 * [`title`](#title)
+* [`transformProps`](#transformprops)
 * [`updateExample`](#updateexample)
 * [`verbose`](#verbose)
 * [`webpackConfig`](#webpackconfig)
@@ -57,10 +59,11 @@ Styleguidist uses [Bublé](https://buble.surge.sh/guide/) to run ES6 code on the
 
 #### `components`
 
-Type: `String` or `Function`, default: `src/components/**/*.{js,jsx,ts,tsx}`
+Type: `String`, `Function` or `Array`, default: `src/components/**/*.{js,jsx,ts,tsx}`
 
 * when `String`: a [glob pattern](https://github.com/isaacs/node-glob#glob-primer) that matches all your component modules.
 * when `Function`: a function that returns an array of module paths.
+* when `Array`: an array of module paths.
 
 All paths are relative to config folder.
 
@@ -259,6 +262,22 @@ module.exports = {
 }
 ```
 
+#### `pagePerSection`
+
+Type: `Boolean`, default: `false`
+
+Render one section or component per page, starting with the first.
+
+If set to `true`, the sidebar will be visible on each page, except for the examples.
+
+The value may be differ on each environment.
+
+```javascript
+module.exports = {
+  pagePerSection: process.env.NODE_ENV !== 'production'
+}
+```
+
 #### `previewDelay`
 
 Type: `Number`, default: 500
@@ -430,6 +449,20 @@ Type: `String`, default: `<app name from package.json> Style Guide`
 
 Style guide title.
 
+#### `transformProps`
+
+Type: `Function`, optional
+
+Function that transforms component properties. By default properties are sorted such that required properties come first, optional prameters come second. Properties in both groups are sorted by their property names.
+
+To disable sorting the identity function can be used:
+
+```javascript
+module.exports = {
+  transformProps: props => props
+}
+```
+
 #### `updateExample`
 
 Type: `Function`, optional
diff --git a/docs/Documenting.md b/docs/Documenting.md
index 14ecc63a..d243ae3d 100644
--- a/docs/Documenting.md
+++ b/docs/Documenting.md
@@ -51,7 +51,7 @@ export default class Button extends React.Component {
 
 > **Note:** You can change its behavior using [propsParser](Configuration.md#propsparser) and [resolver](Configuration.md#resolver) options.
 
-> **Note:** Component’s `PropTypes` and documentation comments are parsed by the [react-docgen](https://github.com/reactjs/react-docgen) library.
+> **Note:** Component’s `PropTypes` and documentation comments are parsed by the [react-docgen](https://github.com/reactjs/react-docgen) library. They can be modified using the [transformProps](Configuration.md#transformprops) function.
 
 ## Usage examples and Readme files
 
diff --git a/docs/Thirdparties.md b/docs/Thirdparties.md
index 6afcf435..6b42f786 100644
--- a/docs/Thirdparties.md
+++ b/docs/Thirdparties.md
@@ -17,7 +17,7 @@
 
 ## How Styleguidist works
 
-Styleguidist always uses the default export to _load_ your components but it uses [react-docgen](https://github.com/reactjs/react-docgen) to _generate documentation_ which may require changes in your code to work properly.
+Styleguidist _loads_ your components (see [Loading and exposing components](Components.md#loading-and-exposing-components) for more) but it uses [react-docgen](https://github.com/reactjs/react-docgen) to _generate documentation_ which may require changes in your code to work properly.
 
 React-docgen reads your components as static text files and looks for patterns like class or function declarations that looks like React components. It does not run any JavaScript code, so, if your component is dynamically generated, is wrapped in a higher-order component, or is split into several files, then react-docgen may not understand it.
 
diff --git a/loaders/props-loader.js b/loaders/props-loader.js
index 65cfb7ec..0857658e 100644
--- a/loaders/props-loader.js
+++ b/loaders/props-loader.js
@@ -8,6 +8,7 @@ const toAst = require('to-ast');
 const logger = require('glogg')('rsg');
 const getExamples = require('./utils/getExamples');
 const getProps = require('./utils/getProps');
+const sortProps = require('./utils/sortProps');
 const consts = require('../scripts/consts');
 
 const ERROR_MISSING_DEFINITION = 'No suitable component definition found.';
@@ -24,6 +25,7 @@ module.exports = function(source) {
 	const defaultParser = (filePath, source, resolver, handlers) =>
 		reactDocs.parse(source, resolver, handlers);
 	const propsParser = config.propsParser || defaultParser;
+	const propsTransform = config.propsTransform || sortProps;
 
 	let props = {};
 	try {
@@ -53,6 +55,19 @@ module.exports = function(source) {
 
 	props = getProps(props, file);
 
+	const componentProps = props.props;
+	if (componentProps) {
+		// Transform the properties to an array. This will allow for sorting.
+		const propsAsArray = Object.keys(componentProps).reduce((acc, name) => {
+			componentProps[name].name = name;
+			acc.push(componentProps[name]);
+			return acc;
+		}, []);
+
+		// Pipe through transform method and override component properties.
+		props.props = propsTransform(propsAsArray);
+	}
+
 	// Examples from Markdown file
 	const examplesFile = config.getExampleFilename(file);
 	props.examples = getExamples(examplesFile, props.displayName, config.defaultExample);
diff --git a/loaders/styleguide-loader.js b/loaders/styleguide-loader.js
index 4e7a24de..6ae37024 100644
--- a/loaders/styleguide-loader.js
+++ b/loaders/styleguide-loader.js
@@ -24,6 +24,7 @@ const CLIENT_CONFIG_OPTIONS = [
 	'styles',
 	'compilerConfig',
 	'editorConfig',
+	'pagePerSection',
 ];
 
 module.exports = function() {};
diff --git a/loaders/utils/getComponentFiles.js b/loaders/utils/getComponentFiles.js
index cd6524f5..9fa4cfcb 100644
--- a/loaders/utils/getComponentFiles.js
+++ b/loaders/utils/getComponentFiles.js
@@ -21,11 +21,13 @@ module.exports = function getComponentFiles(components, rootDir, ignore) {
 	let componentFiles;
 	if (isFunction(components)) {
 		componentFiles = components();
+	} else if (Array.isArray(components)) {
+		componentFiles = components;
 	} else if (isString(components)) {
 		componentFiles = glob.sync(path.resolve(rootDir, components), { ignore });
 	} else {
 		throw new Error(
-			`Styleguidist: components should be string or function, received ${typeof components}.`
+			`Styleguidist: components should be string, function or array, received ${typeof components}.`
 		);
 	}
 
diff --git a/loaders/utils/sortProps.js b/loaders/utils/sortProps.js
new file mode 100644
index 00000000..934263db
--- /dev/null
+++ b/loaders/utils/sortProps.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const sortBy = require('lodash/sortBy');
+
+/**
+ * Sorts an array of properties by their 'required' property first and 'name'
+ * property second.
+ *
+ * @param {array} props
+ * @return {array} Sorted properties
+ */
+function sortProps(props) {
+	const requiredPropNames = sortBy(props.filter(prop => prop.required), 'name');
+	const optionalPropNames = sortBy(props.filter(prop => !prop.required), 'name');
+	const sortedProps = requiredPropNames.concat(optionalPropNames);
+	return sortedProps;
+}
+
+module.exports = sortProps;
diff --git a/scripts/schemas/config.js b/scripts/schemas/config.js
index 453b0cda..cb9b4211 100644
--- a/scripts/schemas/config.js
+++ b/scripts/schemas/config.js
@@ -124,6 +124,10 @@ module.exports = {
 	logger: {
 		type: 'object',
 	},
+	pagePerSection: {
+		type: 'boolean',
+		default: false,
+	},
 	previewDelay: {
 		type: 'number',
 		default: 500,
@@ -235,6 +239,9 @@ module.exports = {
 		},
 		example: 'My Style Guide',
 	},
+	transformProps: {
+		type: 'function',
+	},
 	updateExample: {
 		type: 'function',
 		default: props => {
diff --git a/src/index.js b/src/index.js
index f7e44ebc..fa6caa64 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,13 +1,20 @@
 /* eslint-disable import/first */
-
 import './polyfills';
 import './styles';
 import ReactDOM from 'react-dom';
 import renderStyleguide from './utils/renderStyleguide';
 
 // Examples code revision to rerender only code examples (not the whole page) when code changes
+// eslint-disable-next-line no-unused-vars
 let codeRevision = 0;
 
+/** Scrolls to origin when current window location hash points to an isolated view. */
+const scrollToOrigin = () => {
+	if (window.location.hash.indexOf('#!/') === 0) {
+		window.scrollTo(0, 0);
+	}
+};
+
 const render = () => {
 	// eslint-disable-next-line import/no-unresolved
 	const styleguide = require('!!../loaders/styleguide-loader!./index.js');
@@ -15,6 +22,7 @@ const render = () => {
 };
 
 window.addEventListener('hashchange', render);
+window.addEventListener('hashchange', scrollToOrigin);
 
 /* istanbul ignore if */
 if (module.hot) {
diff --git a/src/rsg-components/ComponentsList/ComponentsList.js b/src/rsg-components/ComponentsList/ComponentsList.js
new file mode 100644
index 00000000..dcad24b9
--- /dev/null
+++ b/src/rsg-components/ComponentsList/ComponentsList.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import ComponentsListRenderer from 'rsg-components/ComponentsList/ComponentsListRenderer';
+import PropTypes from 'prop-types';
+import getUrl from '../../utils/getUrl';
+
+function ComponentsList({ classes, items, useIsolatedLinks = false }) {
+	const mappedItems = items.map(item => ({
+		...item,
+		href: getUrl({
+			name: item.name,
+			slug: item.slug,
+			anchor: !useIsolatedLinks,
+			isolated: useIsolatedLinks,
+		}),
+	}));
+	return <ComponentsListRenderer classes={classes} items={mappedItems} />;
+}
+
+ComponentsList.propTypes = {
+	items: PropTypes.array.isRequired,
+	classes: PropTypes.object,
+	useIsolatedLinks: PropTypes.bool,
+};
+
+export default ComponentsList;
diff --git a/src/rsg-components/ComponentsList/ComponentsList.spec.js b/src/rsg-components/ComponentsList/ComponentsList.spec.js
index a6fa3168..c416d0b7 100644
--- a/src/rsg-components/ComponentsList/ComponentsList.spec.js
+++ b/src/rsg-components/ComponentsList/ComponentsList.spec.js
@@ -1,15 +1,50 @@
 import React from 'react';
+import ComponentsList from './ComponentsList';
 import { ComponentsListRenderer } from './ComponentsListRenderer';
 
+it('should set the correct href for items', () => {
+	const components = [
+		{
+			name: 'Button',
+			slug: 'button',
+		},
+		{
+			name: 'Input',
+			slug: 'input',
+		},
+	];
+
+	const actual = shallow(<ComponentsList items={components} classes={{}} />);
+	expect(actual).toMatchSnapshot();
+});
+
+it('should set the correct href for items when isolated links should be used', () => {
+	const components = [
+		{
+			name: 'Button',
+			slug: 'button',
+		},
+		{
+			name: 'Input',
+			slug: 'input',
+		},
+	];
+
+	const actual = shallow(<ComponentsList items={components} classes={{}} useIsolatedLinks />);
+	expect(actual).toMatchSnapshot();
+});
+
 it('should render sections with nested components', () => {
 	const components = [
 		{
 			name: 'Button',
 			slug: 'button',
+			href: '#button',
 		},
 		{
 			name: 'Input',
 			slug: 'input',
+			href: '#input',
 		},
 	];
 	const actual = shallow(<ComponentsListRenderer items={components} classes={{}} />);
@@ -28,9 +63,11 @@ it('should ignore items without name', () => {
 		{
 			name: 'Button',
 			slug: 'button',
+			href: '#button',
 		},
 		{
 			slug: 'input',
+			href: '#input',
 		},
 	];
 	const actual = shallow(<ComponentsListRenderer items={components} classes={{}} />);
diff --git a/src/rsg-components/ComponentsList/ComponentsListRenderer.js b/src/rsg-components/ComponentsList/ComponentsListRenderer.js
index 900aa1fd..4b376a22 100644
--- a/src/rsg-components/ComponentsList/ComponentsListRenderer.js
+++ b/src/rsg-components/ComponentsList/ComponentsListRenderer.js
@@ -42,12 +42,12 @@ export function ComponentsListRenderer({ classes, items }) {
 
 	return (
 		<ul className={classes.list}>
-			{items.map(({ heading, name, slug, content }) => (
+			{items.map(({ heading, name, href, content }) => (
 				<li
 					className={cx(classes.item, (!content || !content.props.items.length) && classes.isChild)}
 					key={name}
 				>
-					<Link className={cx(heading && classes.heading)} href={`#${slug}`}>
+					<Link className={cx(heading && classes.heading)} href={href}>
 						{name}
 					</Link>
 					{content}
@@ -60,6 +60,7 @@ export function ComponentsListRenderer({ classes, items }) {
 ComponentsListRenderer.propTypes = {
 	items: PropTypes.array.isRequired,
 	classes: PropTypes.object.isRequired,
+	useIsolatedLinks: PropTypes.bool,
 };
 
 export default Styled(styles)(ComponentsListRenderer);
diff --git a/src/rsg-components/ComponentsList/__snapshots__/ComponentsList.spec.js.snap b/src/rsg-components/ComponentsList/__snapshots__/ComponentsList.spec.js.snap
index 9a2fae29..8eb43c4a 100644
--- a/src/rsg-components/ComponentsList/__snapshots__/ComponentsList.spec.js.snap
+++ b/src/rsg-components/ComponentsList/__snapshots__/ComponentsList.spec.js.snap
@@ -42,3 +42,43 @@ exports[`should render sections with nested components 1`] = `
   </li>
 </ul>
 `;
+
+exports[`should set the correct href for items 1`] = `
+<Styled(ComponentsList)
+  classes={Object {}}
+  items={
+    Array [
+      Object {
+        "href": "blank#button",
+        "name": "Button",
+        "slug": "button",
+      },
+      Object {
+        "href": "blank#input",
+        "name": "Input",
+        "slug": "input",
+      },
+    ]
+  }
+/>
+`;
+
+exports[`should set the correct href for items when isolated links should be used 1`] = `
+<Styled(ComponentsList)
+  classes={Object {}}
+  items={
+    Array [
+      Object {
+        "href": "blank#!/Button",
+        "name": "Button",
+        "slug": "button",
+      },
+      Object {
+        "href": "blank#!/Input",
+        "name": "Input",
+        "slug": "input",
+      },
+    ]
+  }
+/>
+`;
diff --git a/src/rsg-components/ComponentsList/index.js b/src/rsg-components/ComponentsList/index.js
index 96588be8..564ab475 100644
--- a/src/rsg-components/ComponentsList/index.js
+++ b/src/rsg-components/ComponentsList/index.js
@@ -1,1 +1,1 @@
-export { default } from 'rsg-components/ComponentsList/ComponentsListRenderer';
+export { default } from 'rsg-components/ComponentsList/ComponentsList';
diff --git a/src/rsg-components/Playground/Playground.spec.js b/src/rsg-components/Playground/Playground.spec.js
index f02b2ced..0df35c82 100644
--- a/src/rsg-components/Playground/Playground.spec.js
+++ b/src/rsg-components/Playground/Playground.spec.js
@@ -23,7 +23,7 @@ const options = {
 			highlightTheme: 'base16-light',
 		},
 		codeRevision: 0,
-		slots,
+		slots: slots({}),
 	},
 	childContextTypes: {
 		slots: PropTypes.object.isRequired,
diff --git a/src/rsg-components/Props/Props.spec.js b/src/rsg-components/Props/Props.spec.js
index 21f6827c..cb769cfb 100644
--- a/src/rsg-components/Props/Props.spec.js
+++ b/src/rsg-components/Props/Props.spec.js
@@ -1,14 +1,16 @@
+/* eslint-disable react/prop-types */
 import React from 'react';
 import { parse } from 'react-docgen';
-import PropsRenderer, { propsToArray, columns } from './PropsRenderer';
+import PropsRenderer, { columns, getRowKey } from './PropsRenderer';
 import { unquote, getType, showSpaces } from './util';
 
+const propsToArray = props => Object.keys(props).map(name => ({ ...props[name], name }));
+
 // Test renderers with clean readable snapshot diffs
-// eslint-disable-next-line react/prop-types
 export default function ColumnsRenderer({ props }) {
 	return (
 		<ul>
-			{propsToArray(props).map((row, rowIdx) => (
+			{props.map((row, rowIdx) => (
 				<li key={rowIdx}>
 					{columns.map(({ render }, colIdx) => <div key={colIdx}>{render(row)}</div>)}
 				</li>
@@ -32,14 +34,32 @@ function render(propTypes, defaultProps = []) {
 			}
 		}
 	`);
-	return shallow(<ColumnsRenderer props={props.props} />);
+	return shallow(<ColumnsRenderer props={propsToArray(props.props)} />);
+}
+
+function renderFlow(propsType, defaultProps = []) {
+	const props = parse(`
+	  // @flow
+		import * as React from 'react';
+		type Props = {
+			${propsType.join(',')}
+		};
+		export default class Cmpnt extends React.Component<Props> {
+			static defaultProps = {
+				${defaultProps.join(',')}
+			}
+			render() {
+			}
+		}
+	`);
+	return shallow(<ColumnsRenderer props={propsToArray(props.props)} />);
 }
 
 describe('PropsRenderer', () => {
 	it('should render a table', () => {
 		const actual = shallow(
 			<PropsRenderer
-				props={{ color: { type: { name: 'string' }, required: false, description: '' } }}
+				props={[{ type: { name: 'string' }, required: false, description: '', name: 'color' }]}
 			/>
 		);
 
@@ -220,8 +240,9 @@ describe('props columns', () => {
 	});
 
 	it('should render arguments from JsDoc tags', () => {
-		const props = {
-			size: {
+		const props = [
+			{
+				name: 'size',
 				type: {
 					name: 'number',
 				},
@@ -242,15 +263,16 @@ describe('props columns', () => {
 					],
 				},
 			},
-		};
+		];
 		const actual = shallow(<ColumnsRenderer props={props} />);
 
 		expect(actual).toMatchSnapshot();
 	});
 
 	it('should render return from JsDoc tags', () => {
-		const getProps = tag => ({
-			size: {
+		const getProps = tag => [
+			{
+				name: 'size',
 				type: {
 					name: 'number',
 				},
@@ -266,7 +288,7 @@ describe('props columns', () => {
 					],
 				},
 			},
-		});
+		];
 
 		const actualForReturn = shallow(<ColumnsRenderer props={getProps('return')} />);
 
@@ -278,8 +300,9 @@ describe('props columns', () => {
 	});
 
 	it('should render name as deprecated when tag deprecated is present', () => {
-		const props = {
-			size: {
+		const props = [
+			{
+				name: 'size',
 				type: {
 					name: 'number',
 				},
@@ -294,11 +317,65 @@ describe('props columns', () => {
 					],
 				},
 			},
-		};
+		];
 		const actual = shallow(<ColumnsRenderer props={props} />);
 
 		expect(actual).toMatchSnapshot();
 	});
+
+	it('should render type string', () => {
+		const actual = renderFlow(['foo: string']);
+
+		expect(actual).toMatchSnapshot();
+	});
+
+	it('should render optional type string', () => {
+		const actual = renderFlow(['foo?: string']);
+
+		expect(actual).toMatchSnapshot();
+	});
+
+	it('should render type string with a default value', () => {
+		const actual = renderFlow(['foo?: string'], ['foo: "bar"']);
+
+		expect(actual).toMatchSnapshot();
+	});
+
+	it('should render literal type', () => {
+		const actual = renderFlow(['foo?: "bar"']);
+
+		expect(actual).toMatchSnapshot();
+	});
+
+	it('should render object type with body in tooltip', () => {
+		const actual = renderFlow(['foo: { bar: string }']);
+
+		expect(actual).toMatchSnapshot();
+	});
+
+	it('should render function type with body in tooltip', () => {
+		const actual = renderFlow(['foo: () => void']);
+
+		expect(actual).toMatchSnapshot();
+	});
+
+	it('should render union type with body in tooltip', () => {
+		const actual = renderFlow(['foo: "bar" | number']);
+
+		expect(actual).toMatchSnapshot();
+	});
+
+	it('should render tuple type with body in tooltip', () => {
+		const actual = renderFlow(['foo: ["bar", number]']);
+
+		expect(actual).toMatchSnapshot();
+	});
+
+	it('should render custom class type', () => {
+		const actual = renderFlow(['foo: React.ReactNode']);
+
+		expect(actual).toMatchSnapshot();
+	});
 });
 
 describe('unquote', () => {
@@ -334,3 +411,10 @@ describe('showSpaces', () => {
 		expect(result).toBe('␣pizza␣');
 	});
 });
+
+describe('getRowKey', () => {
+	it('should return type name', () => {
+		const result = getRowKey({ name: 'number' });
+		expect(result).toBe('number');
+	});
+});
diff --git a/src/rsg-components/Props/PropsRenderer.js b/src/rsg-components/Props/PropsRenderer.js
index 0c1e4db7..b32b0632 100644
--- a/src/rsg-components/Props/PropsRenderer.js
+++ b/src/rsg-components/Props/PropsRenderer.js
@@ -12,7 +12,6 @@ import Type from 'rsg-components/Type';
 import Text from 'rsg-components/Text';
 import Para from 'rsg-components/Para';
 import Table from 'rsg-components/Table';
-import map from 'lodash/map';
 import { unquote, getType, showSpaces } from './util';
 
 function renderType(type) {
@@ -34,6 +33,34 @@ function renderType(type) {
 	}
 }
 
+function renderFlowType(type) {
+	if (!type) {
+		return 'unknown';
+	}
+
+	const { name, raw, value } = type;
+
+	switch (name) {
+		case 'literal':
+			return value;
+		case 'signature':
+			return renderComplexType(type.type, raw);
+		case 'union':
+		case 'tuple':
+			return renderComplexType(name, raw);
+		default:
+			return raw || name;
+	}
+}
+
+function renderComplexType(name, title) {
+	return (
+		<Text size="small" underlined title={title}>
+			<Type>{name}</Type>
+		</Text>
+	);
+}
+
 function renderEnum(prop) {
 	if (!Array.isArray(getType(prop).value)) {
 		return <span>{getType(prop).value}</span>;
@@ -76,19 +103,15 @@ function renderShape(props) {
 const defaultValueBlacklist = ['null', 'undefined'];
 
 function renderDefault(prop) {
-	if (prop.required) {
-		return (
-			<Text size="small" color="light">
-				Required
-			</Text>
-		);
-	} else if (prop.defaultValue) {
-		if (prop.type) {
-			const propName = prop.type.name;
+	// Workaround for issue https://github.com/reactjs/react-docgen/issues/221
+	// If prop has defaultValue it can not be required
+	if (prop.defaultValue) {
+		if (prop.type || prop.flowType) {
+			const propName = prop.type ? prop.type.name : prop.flowType.type;
 
 			if (defaultValueBlacklist.indexOf(prop.defaultValue.value) > -1) {
 				return <Code>{showSpaces(unquote(prop.defaultValue.value))}</Code>;
-			} else if (propName === 'func') {
+			} else if (propName === 'func' || propName === 'function') {
 				return (
 					<Text
 						size="small"
@@ -125,6 +148,12 @@ function renderDefault(prop) {
 		}
 
 		return <Code>{showSpaces(unquote(prop.defaultValue.value))}</Code>;
+	} else if (prop.required) {
+		return (
+			<Text size="small" color="light">
+				Required
+			</Text>
+		);
 	}
 	return '';
 }
@@ -198,6 +227,9 @@ function renderName(prop) {
 }
 
 function renderTypeColumn(prop) {
+	if (prop.flowType) {
+		return <Type>{renderFlowType(getType(prop))}</Type>;
+	}
 	return <Type>{renderType(getType(prop))}</Type>;
 }
 
@@ -205,10 +237,6 @@ export function getRowKey(row) {
 	return row.name;
 }
 
-export function propsToArray(props) {
-	return map(props, (prop, name) => ({ ...prop, name }));
-}
-
 export const columns = [
 	{
 		caption: 'Prop name',
@@ -229,9 +257,9 @@ export const columns = [
 ];
 
 export default function PropsRenderer({ props }) {
-	return <Table columns={columns} rows={propsToArray(props)} getRowKey={getRowKey} />;
+	return <Table columns={columns} rows={props} getRowKey={getRowKey} />;
 }
 
 PropsRenderer.propTypes = {
-	props: PropTypes.object.isRequired,
+	props: PropTypes.array.isRequired,
 };
diff --git a/src/rsg-components/Props/__snapshots__/Props.spec.js.snap b/src/rsg-components/Props/__snapshots__/Props.spec.js.snap
index ec3c66fe..d226acf6 100644
--- a/src/rsg-components/Props/__snapshots__/Props.spec.js.snap
+++ b/src/rsg-components/Props/__snapshots__/Props.spec.js.snap
@@ -879,6 +879,48 @@ exports[`props columns should render arguments from JsDoc tags 1`] = `
 </ul>
 `;
 
+exports[`props columns should render custom class type 1`] = `
+<ul>
+  <li
+    key="0"
+  >
+    <div
+      key="0"
+    >
+      <Styled(Name)
+        deprecated={false}
+      >
+        foo
+      </Styled(Name)>
+    </div>
+    <div
+      key="1"
+    >
+      <Styled(Type)>
+        React.ReactNode
+      </Styled(Type)>
+    </div>
+    <div
+      key="2"
+    >
+      <Styled(Text)
+        color="light"
+        size="small"
+      >
+        Required
+      </Styled(Text)>
+    </div>
+    <div
+      key="3"
+    >
+      <div>
+        <JsDoc />
+      </div>
+    </div>
+  </li>
+</ul>
+`;
+
 exports[`props columns should render description in Markdown 1`] = `
 <ul>
   <li
@@ -1039,6 +1081,91 @@ exports[`props columns should render function defaultValue as code when undefine
 </ul>
 `;
 
+exports[`props columns should render function type with body in tooltip 1`] = `
+<ul>
+  <li
+    key="0"
+  >
+    <div
+      key="0"
+    >
+      <Styled(Name)
+        deprecated={false}
+      >
+        foo
+      </Styled(Name)>
+    </div>
+    <div
+      key="1"
+    >
+      <Styled(Type)>
+        <Styled(Text)
+          size="small"
+          title="() => void"
+          underlined={true}
+        >
+          <Styled(Type)>
+            function
+          </Styled(Type)>
+        </Styled(Text)>
+      </Styled(Type)>
+    </div>
+    <div
+      key="2"
+    >
+      <Styled(Text)
+        color="light"
+        size="small"
+      >
+        Required
+      </Styled(Text)>
+    </div>
+    <div
+      key="3"
+    >
+      <div>
+        <JsDoc />
+      </div>
+    </div>
+  </li>
+</ul>
+`;
+
+exports[`props columns should render literal type 1`] = `
+<ul>
+  <li
+    key="0"
+  >
+    <div
+      key="0"
+    >
+      <Styled(Name)
+        deprecated={false}
+      >
+        foo
+      </Styled(Name)>
+    </div>
+    <div
+      key="1"
+    >
+      <Styled(Type)>
+        "bar"
+      </Styled(Type)>
+    </div>
+    <div
+      key="2"
+    />
+    <div
+      key="3"
+    >
+      <div>
+        <JsDoc />
+      </div>
+    </div>
+  </li>
+</ul>
+`;
+
 exports[`props columns should render name as deprecated when tag deprecated is present 1`] = `
 <ul>
   <li
@@ -1086,6 +1213,91 @@ exports[`props columns should render name as deprecated when tag deprecated is p
 </ul>
 `;
 
+exports[`props columns should render object type with body in tooltip 1`] = `
+<ul>
+  <li
+    key="0"
+  >
+    <div
+      key="0"
+    >
+      <Styled(Name)
+        deprecated={false}
+      >
+        foo
+      </Styled(Name)>
+    </div>
+    <div
+      key="1"
+    >
+      <Styled(Type)>
+        <Styled(Text)
+          size="small"
+          title="{ bar: string }"
+          underlined={true}
+        >
+          <Styled(Type)>
+            object
+          </Styled(Type)>
+        </Styled(Text)>
+      </Styled(Type)>
+    </div>
+    <div
+      key="2"
+    >
+      <Styled(Text)
+        color="light"
+        size="small"
+      >
+        Required
+      </Styled(Text)>
+    </div>
+    <div
+      key="3"
+    >
+      <div>
+        <JsDoc />
+      </div>
+    </div>
+  </li>
+</ul>
+`;
+
+exports[`props columns should render optional type string 1`] = `
+<ul>
+  <li
+    key="0"
+  >
+    <div
+      key="0"
+    >
+      <Styled(Name)
+        deprecated={false}
+      >
+        foo
+      </Styled(Name)>
+    </div>
+    <div
+      key="1"
+    >
+      <Styled(Type)>
+        string
+      </Styled(Type)>
+    </div>
+    <div
+      key="2"
+    />
+    <div
+      key="3"
+    >
+      <div>
+        <JsDoc />
+      </div>
+    </div>
+  </li>
+</ul>
+`;
+
 exports[`props columns should render return from JsDoc tags 1`] = `
 <ul>
   <li
@@ -1206,6 +1418,190 @@ exports[`props columns should render return from JsDoc tags 2`] = `
 </ul>
 `;
 
+exports[`props columns should render tuple type with body in tooltip 1`] = `
+<ul>
+  <li
+    key="0"
+  >
+    <div
+      key="0"
+    >
+      <Styled(Name)
+        deprecated={false}
+      >
+        foo
+      </Styled(Name)>
+    </div>
+    <div
+      key="1"
+    >
+      <Styled(Type)>
+        <Styled(Text)
+          size="small"
+          title="[\\"bar\\", number]"
+          underlined={true}
+        >
+          <Styled(Type)>
+            tuple
+          </Styled(Type)>
+        </Styled(Text)>
+      </Styled(Type)>
+    </div>
+    <div
+      key="2"
+    >
+      <Styled(Text)
+        color="light"
+        size="small"
+      >
+        Required
+      </Styled(Text)>
+    </div>
+    <div
+      key="3"
+    >
+      <div>
+        <JsDoc />
+      </div>
+    </div>
+  </li>
+</ul>
+`;
+
+exports[`props columns should render type string 1`] = `
+<ul>
+  <li
+    key="0"
+  >
+    <div
+      key="0"
+    >
+      <Styled(Name)
+        deprecated={false}
+      >
+        foo
+      </Styled(Name)>
+    </div>
+    <div
+      key="1"
+    >
+      <Styled(Type)>
+        string
+      </Styled(Type)>
+    </div>
+    <div
+      key="2"
+    >
+      <Styled(Text)
+        color="light"
+        size="small"
+      >
+        Required
+      </Styled(Text)>
+    </div>
+    <div
+      key="3"
+    >
+      <div>
+        <JsDoc />
+      </div>
+    </div>
+  </li>
+</ul>
+`;
+
+exports[`props columns should render type string with a default value 1`] = `
+<ul>
+  <li
+    key="0"
+  >
+    <div
+      key="0"
+    >
+      <Styled(Name)
+        deprecated={false}
+      >
+        foo
+      </Styled(Name)>
+    </div>
+    <div
+      key="1"
+    >
+      <Styled(Type)>
+        string
+      </Styled(Type)>
+    </div>
+    <div
+      key="2"
+    >
+      <Styled(Code)>
+        bar
+      </Styled(Code)>
+    </div>
+    <div
+      key="3"
+    >
+      <div>
+        <JsDoc />
+      </div>
+    </div>
+  </li>
+</ul>
+`;
+
+exports[`props columns should render union type with body in tooltip 1`] = `
+<ul>
+  <li
+    key="0"
+  >
+    <div
+      key="0"
+    >
+      <Styled(Name)
+        deprecated={false}
+      >
+        foo
+      </Styled(Name)>
+    </div>
+    <div
+      key="1"
+    >
+      <Styled(Type)>
+        <Styled(Text)
+          size="small"
+          title="\\"bar\\" | number"
+          underlined={true}
+        >
+          <Styled(Type)>
+            union
+          </Styled(Type)>
+        </Styled(Text)>
+      </Styled(Type)>
+    </div>
+    <div
+      key="2"
+    >
+      <Styled(Text)
+        color="light"
+        size="small"
+      >
+        Required
+      </Styled(Text)>
+    </div>
+    <div
+      key="3"
+    >
+      <div>
+        <Styled(Para)>
+          <span />
+        </Styled(Para)>
+        <JsDoc />
+      </div>
+    </div>
+  </li>
+</ul>
+`;
+
 exports[`props columns should render unknown proptype for a prop when a relevant proptype is not assigned 1`] = `
 <ul>
   <li
diff --git a/src/rsg-components/StyleGuide/StyleGuide.js b/src/rsg-components/StyleGuide/StyleGuide.js
index 7b63f7a9..a2cd8954 100644
--- a/src/rsg-components/StyleGuide/StyleGuide.js
+++ b/src/rsg-components/StyleGuide/StyleGuide.js
@@ -8,6 +8,29 @@ import Error from 'rsg-components/Error';
 import { HOMEPAGE } from '../../../scripts/consts';
 import { DisplayModes } from '../../consts';
 
+/**
+ * This function will return true, if the sidebar should be visible and false otherwise.
+ *
+ * These sorted conditions (highest precedence first) define the visibility
+ * state of the sidebar.
+ *
+ * - Sidebar is hidden for isolated example views
+ * - Sidebar is always visible when pagePerSection
+ * - Sidebar is hidden when showSidebar is set to false
+ * - Sidebar is visible when showSidebar is set to true for non-isolated views
+ *
+ * @param {boolean} displayMode
+ * @param {boolean} showSidebar
+ * @param {boolean} pagePerSection
+ * @returns {boolean}
+ */
+function hasSidebar(displayMode, showSidebar, pagePerSection = false) {
+	return (
+		(pagePerSection && displayMode !== DisplayModes.example) ||
+		(showSidebar && displayMode === DisplayModes.all)
+	);
+}
+
 export default class StyleGuide extends Component {
 	static propTypes = {
 		codeRevision: PropTypes.number.isRequired,
@@ -17,6 +40,8 @@ export default class StyleGuide extends Component {
 		welcomeScreen: PropTypes.bool,
 		patterns: PropTypes.array,
 		displayMode: PropTypes.string,
+		allSections: PropTypes.array.isRequired,
+		pagePerSection: PropTypes.bool,
 	};
 
 	static childContextTypes = {
@@ -52,7 +77,15 @@ export default class StyleGuide extends Component {
 	}
 
 	render() {
-		const { config, sections, welcomeScreen, patterns, displayMode } = this.props;
+		const {
+			config,
+			sections,
+			welcomeScreen,
+			patterns,
+			displayMode,
+			allSections,
+			pagePerSection,
+		} = this.props;
 
 		if (this.state.error) {
 			return <Error error={this.state.error} info={this.state.info} />;
@@ -66,8 +99,8 @@ export default class StyleGuide extends Component {
 			<StyleGuideRenderer
 				title={config.title}
 				homepageUrl={HOMEPAGE}
-				toc={<TableOfContents sections={sections} />}
-				hasSidebar={config.showSidebar && displayMode === DisplayModes.all}
+				toc={<TableOfContents sections={allSections} useIsolatedLinks={pagePerSection} />}
+				hasSidebar={hasSidebar(displayMode, config.showSidebar, pagePerSection)}
 			>
 				<Sections sections={sections} depth={1} />
 			</StyleGuideRenderer>
diff --git a/src/rsg-components/StyleGuide/StyleGuide.spec.js b/src/rsg-components/StyleGuide/StyleGuide.spec.js
index aa9e01f2..85780e28 100644
--- a/src/rsg-components/StyleGuide/StyleGuide.spec.js
+++ b/src/rsg-components/StyleGuide/StyleGuide.spec.js
@@ -33,7 +33,13 @@ const config = {
 
 it('should render components list', () => {
 	const actual = shallow(
-		<StyleGuide codeRevision={1} config={config} sections={sections} slots={{}} />
+		<StyleGuide
+			codeRevision={1}
+			config={config}
+			sections={sections}
+			allSections={sections}
+			slots={{}}
+		/>
 	);
 
 	expect(actual).toMatchSnapshot();
@@ -41,14 +47,23 @@ it('should render components list', () => {
 
 it('should render welcome screen', () => {
 	const actual = shallow(
-		<StyleGuide codeRevision={1} config={config} sections={[]} slots={{}} welcomeScreen />
+		<StyleGuide
+			codeRevision={1}
+			config={config}
+			sections={[]}
+			allSections={[]}
+			slots={{}}
+			welcomeScreen
+		/>
 	);
 
 	expect(actual).toMatchSnapshot();
 });
 
 it('should render an error when componentDidCatch() is triggered', () => {
-	const wrapper = shallow(<StyleGuide codeRevision={1} config={config} sections={[]} slots={{}} />);
+	const wrapper = shallow(
+		<StyleGuide codeRevision={1} config={config} sections={[]} allSections={[]} slots={{}} />
+	);
 	wrapper
 		.instance()
 		.componentDidCatch({ toString: () => 'error' }, { componentStack: { toString: () => 'info' } });
@@ -59,7 +74,13 @@ it('should render an error when componentDidCatch() is triggered', () => {
 describe('sidebar rendering', () => {
 	it('renderer should have sidebar if showSidebar is not set', () => {
 		const wrapper = shallow(
-			<StyleGuide codeRevision={1} config={config} sections={sections} slots={{}} />
+			<StyleGuide
+				codeRevision={1}
+				config={config}
+				sections={sections}
+				allSections={sections}
+				slots={{}}
+			/>
 		);
 
 		expect(wrapper.prop('hasSidebar')).toEqual(true);
@@ -74,6 +95,7 @@ describe('sidebar rendering', () => {
 					showSidebar: false,
 				}}
 				sections={sections}
+				allSections={sections}
 				slots={{}}
 			/>
 		);
@@ -87,6 +109,7 @@ describe('sidebar rendering', () => {
 				codeRevision={1}
 				config={config}
 				sections={sections}
+				allSections={sections}
 				slots={{}}
 				displayMode={DisplayModes.component}
 			/>
@@ -94,6 +117,22 @@ describe('sidebar rendering', () => {
 
 		expect(wrapper.prop('hasSidebar')).toEqual(false);
 	});
+
+	it('renderer should have sidebar if pagePerSection is true', () => {
+		const wrapper = shallow(
+			<StyleGuide
+				codeRevision={1}
+				config={config}
+				sections={sections}
+				allSections={sections}
+				slots={{}}
+				displayMode={DisplayModes.component}
+				pagePerSection
+			/>
+		);
+
+		expect(wrapper.prop('hasSidebar')).toEqual(true);
+	});
 });
 
 it('renderer should render logo, table of contents and passed children', () => {
diff --git a/src/rsg-components/StyleGuide/__snapshots__/StyleGuide.spec.js.snap b/src/rsg-components/StyleGuide/__snapshots__/StyleGuide.spec.js.snap
index 55373806..374520e9 100644
--- a/src/rsg-components/StyleGuide/__snapshots__/StyleGuide.spec.js.snap
+++ b/src/rsg-components/StyleGuide/__snapshots__/StyleGuide.spec.js.snap
@@ -98,6 +98,7 @@ exports[`should render components list 1`] = `
           },
         ]
       }
+      useIsolatedLinks={undefined}
     />
   }
 >
diff --git a/src/rsg-components/TableOfContents/TableOfContents.js b/src/rsg-components/TableOfContents/TableOfContents.js
index 06f5ce58..01b6dc42 100644
--- a/src/rsg-components/TableOfContents/TableOfContents.js
+++ b/src/rsg-components/TableOfContents/TableOfContents.js
@@ -7,32 +7,32 @@ import filterSectionsByName from '../../utils/filterSectionsByName';
 export default class TableOfContents extends Component {
 	static propTypes = {
 		sections: PropTypes.array.isRequired,
+		useIsolatedLinks: PropTypes.bool,
 	};
 	state = {
 		searchTerm: '',
 	};
 
-	renderLevel(sections) {
+	renderLevel(sections, useIsolatedLinks = false) {
 		const items = sections.map(section => {
 			const children = [...(section.sections || []), ...(section.components || [])];
 			return Object.assign({}, section, {
 				heading: !!section.name && children.length > 0,
-				content: children.length > 0 && this.renderLevel(children),
+				content: children.length > 0 && this.renderLevel(children, useIsolatedLinks),
 			});
 		});
-		return <ComponentsList items={items} />;
+		return <ComponentsList items={items} useIsolatedLinks={useIsolatedLinks} />;
 	}
 
 	renderSections() {
 		const { searchTerm } = this.state;
-		const { sections } = this.props;
-
+		const { sections, useIsolatedLinks } = this.props;
 		// If there is only one section, we treat it as a root section
 		// In this case the name of the section won't be rendered and it won't get left padding
 		const firstLevel = sections.length === 1 ? sections[0].components : sections;
 		const filtered = filterSectionsByName(firstLevel, searchTerm);
 
-		return this.renderLevel(filtered);
+		return this.renderLevel(filtered, useIsolatedLinks);
 	}
 
 	render() {
diff --git a/src/rsg-components/TableOfContents/__snapshots__/TableOfContents.spec.js.snap b/src/rsg-components/TableOfContents/__snapshots__/TableOfContents.spec.js.snap
index fa9b98ca..027fda5d 100644
--- a/src/rsg-components/TableOfContents/__snapshots__/TableOfContents.spec.js.snap
+++ b/src/rsg-components/TableOfContents/__snapshots__/TableOfContents.spec.js.snap
@@ -19,7 +19,7 @@ exports[`should filter list when search field contains a query 1`] = `
   onSearchTermChange={[Function]}
   searchTerm=""
 >
-  <Styled(ComponentsList)
+  <ComponentsList
     items={
       Array [
         Object {
@@ -48,6 +48,7 @@ exports[`should filter list when search field contains a query 1`] = `
         },
       ]
     }
+    useIsolatedLinks={false}
   />
 </Styled(TableOfContents)>
 `;
@@ -57,7 +58,7 @@ exports[`should filter list when search field contains a query 2`] = `
   onSearchTermChange={[Function]}
   searchTerm="but"
 >
-  <Styled(ComponentsList)
+  <ComponentsList
     items={
       Array [
         Object {
@@ -70,6 +71,7 @@ exports[`should filter list when search field contains a query 2`] = `
         },
       ]
     }
+    useIsolatedLinks={false}
   />
 </Styled(TableOfContents)>
 `;
@@ -79,7 +81,7 @@ exports[`should filter section names 1`] = `
   onSearchTermChange={[Function]}
   searchTerm=""
 >
-  <Styled(ComponentsList)
+  <ComponentsList
     items={
       Array [
         Object {
@@ -97,7 +99,7 @@ exports[`should filter section names 1`] = `
               "slug": "button",
             },
           ],
-          "content": <Styled(ComponentsList)
+          "content": <ComponentsList
             items={
               Array [
                 Object {
@@ -108,6 +110,7 @@ exports[`should filter section names 1`] = `
                 },
               ]
             }
+            useIsolatedLinks={false}
           />,
           "heading": true,
           "name": "Buttons",
@@ -125,7 +128,7 @@ exports[`should filter section names 1`] = `
               "slug": "textarea",
             },
           ],
-          "content": <Styled(ComponentsList)
+          "content": <ComponentsList
             items={
               Array [
                 Object {
@@ -142,6 +145,7 @@ exports[`should filter section names 1`] = `
                 },
               ]
             }
+            useIsolatedLinks={false}
           />,
           "heading": true,
           "name": "Forms",
@@ -150,6 +154,7 @@ exports[`should filter section names 1`] = `
         },
       ]
     }
+    useIsolatedLinks={false}
   />
 </Styled(TableOfContents)>
 `;
@@ -159,7 +164,7 @@ exports[`should filter section names 2`] = `
   onSearchTermChange={[Function]}
   searchTerm="frm"
 >
-  <Styled(ComponentsList)
+  <ComponentsList
     items={
       Array [
         Object {
@@ -172,6 +177,7 @@ exports[`should filter section names 2`] = `
         },
       ]
     }
+    useIsolatedLinks={false}
   />
 </Styled(TableOfContents)>
 `;
@@ -181,7 +187,7 @@ exports[`should render a renderer 1`] = `
   onSearchTermChange={[Function]}
   searchTerm=""
 >
-  <Styled(ComponentsList)
+  <ComponentsList
     items={
       Array [
         Object {
@@ -210,6 +216,7 @@ exports[`should render a renderer 1`] = `
         },
       ]
     }
+    useIsolatedLinks={false}
   />
 </Styled(TableOfContents)>
 `;
@@ -219,7 +226,7 @@ exports[`should render renderer with sections with nested components 1`] = `
   onSearchTermChange={[Function]}
   searchTerm=""
 >
-  <Styled(ComponentsList)
+  <ComponentsList
     items={
       Array [
         Object {
@@ -237,7 +244,7 @@ exports[`should render renderer with sections with nested components 1`] = `
               "slug": "button",
             },
           ],
-          "content": <Styled(ComponentsList)
+          "content": <ComponentsList
             items={
               Array [
                 Object {
@@ -248,6 +255,7 @@ exports[`should render renderer with sections with nested components 1`] = `
                 },
               ]
             }
+            useIsolatedLinks={false}
           />,
           "heading": true,
           "name": "Buttons",
@@ -265,7 +273,7 @@ exports[`should render renderer with sections with nested components 1`] = `
               "slug": "textarea",
             },
           ],
-          "content": <Styled(ComponentsList)
+          "content": <ComponentsList
             items={
               Array [
                 Object {
@@ -282,6 +290,7 @@ exports[`should render renderer with sections with nested components 1`] = `
                 },
               ]
             }
+            useIsolatedLinks={false}
           />,
           "heading": true,
           "name": "Forms",
@@ -290,6 +299,7 @@ exports[`should render renderer with sections with nested components 1`] = `
         },
       ]
     }
+    useIsolatedLinks={false}
   />
 </Styled(TableOfContents)>
 `;
diff --git a/src/rsg-components/Usage/Usage.js b/src/rsg-components/Usage/Usage.js
index c5500de6..02641c92 100644
--- a/src/rsg-components/Usage/Usage.js
+++ b/src/rsg-components/Usage/Usage.js
@@ -21,7 +21,7 @@ export default function Usage({ props: { props, methods } }) {
 
 Usage.propTypes = {
 	props: PropTypes.shape({
-		props: PropTypes.object,
+		props: PropTypes.array,
 		methods: PropTypes.array,
 	}).isRequired,
 };
diff --git a/src/rsg-components/Usage/Usage.spec.js b/src/rsg-components/Usage/Usage.spec.js
index 5a0c5b29..ee987ea5 100644
--- a/src/rsg-components/Usage/Usage.spec.js
+++ b/src/rsg-components/Usage/Usage.spec.js
@@ -1,13 +1,14 @@
 import React from 'react';
 import Usage from './Usage';
 
-const props = {
-	children: {
+const props = [
+	{
+		name: 'children',
 		type: { name: 'string' },
 		required: true,
 		description: 'Button label.',
 	},
-};
+];
 const methods = [
 	{
 		name: 'set',
diff --git a/src/rsg-components/Usage/__snapshots__/Usage.spec.js.snap b/src/rsg-components/Usage/__snapshots__/Usage.spec.js.snap
index 55603d93..d9435adb 100644
--- a/src/rsg-components/Usage/__snapshots__/Usage.spec.js.snap
+++ b/src/rsg-components/Usage/__snapshots__/Usage.spec.js.snap
@@ -29,15 +29,16 @@ exports[`Usage should render props table 1`] = `
 <div>
   <PropsRenderer
     props={
-      Object {
-        "children": Object {
+      Array [
+        Object {
           "description": "Button label.",
+          "name": "children",
           "required": true,
           "type": Object {
             "name": "string",
           },
         },
-      }
+      ]
     }
   />
 </div>
diff --git a/src/rsg-components/slots/index.js b/src/rsg-components/slots/index.js
index 838c4c68..0301f39d 100644
--- a/src/rsg-components/slots/index.js
+++ b/src/rsg-components/slots/index.js
@@ -7,34 +7,36 @@ import UsageTabButton from './UsageTabButton';
 export const EXAMPLE_TAB_CODE_EDITOR = 'rsg-code-editor';
 export const DOCS_TAB_USAGE = 'rsg-usage';
 
-const toolbar = [IsolateButton];
+export default ({ pagePerSection }) => {
+	const toolbar = pagePerSection ? [] : [IsolateButton];
 
-export default {
-	sectionToolbar: toolbar,
-	componentToolbar: toolbar,
-	exampleToolbar: toolbar,
-	exampleTabButtons: [
-		{
-			id: EXAMPLE_TAB_CODE_EDITOR,
-			render: CodeTabButton,
-		},
-	],
-	exampleTabs: [
-		{
-			id: EXAMPLE_TAB_CODE_EDITOR,
-			render: Editor,
-		},
-	],
-	docsTabButtons: [
-		{
-			id: DOCS_TAB_USAGE,
-			render: UsageTabButton,
-		},
-	],
-	docsTabs: [
-		{
-			id: DOCS_TAB_USAGE,
-			render: Usage,
-		},
-	],
+	return {
+		sectionToolbar: toolbar,
+		componentToolbar: toolbar,
+		exampleToolbar: toolbar,
+		exampleTabButtons: [
+			{
+				id: EXAMPLE_TAB_CODE_EDITOR,
+				render: CodeTabButton,
+			},
+		],
+		exampleTabs: [
+			{
+				id: EXAMPLE_TAB_CODE_EDITOR,
+				render: Editor,
+			},
+		],
+		docsTabButtons: [
+			{
+				id: DOCS_TAB_USAGE,
+				render: UsageTabButton,
+			},
+		],
+		docsTabs: [
+			{
+				id: DOCS_TAB_USAGE,
+				render: Usage,
+			},
+		],
+	};
 };
diff --git a/src/rsg-components/slots/index.spec.js b/src/rsg-components/slots/index.spec.js
new file mode 100644
index 00000000..eda6fe19
--- /dev/null
+++ b/src/rsg-components/slots/index.spec.js
@@ -0,0 +1,18 @@
+import slots from './index';
+import IsolateButton from './IsolateButton';
+
+describe('slots', () => {
+	it('should contain IsolateButton in toolbars if pagePerSection is false', () => {
+		const config = slots({ pagePerSection: false });
+		expect(config.componentToolbar).toContain(IsolateButton);
+		expect(config.exampleToolbar).toContain(IsolateButton);
+		expect(config.sectionToolbar).toContain(IsolateButton);
+	});
+
+	it('should not contain IsolateButton in toolbars if pagePerSection is true', () => {
+		const config = slots({ pagePerSection: true });
+		expect(config.componentToolbar).not.toContain(IsolateButton);
+		expect(config.exampleToolbar).not.toContain(IsolateButton);
+		expect(config.sectionToolbar).not.toContain(IsolateButton);
+	});
+});
diff --git a/src/utils/getComponent.js b/src/utils/getComponent.js
new file mode 100644
index 00000000..172ada01
--- /dev/null
+++ b/src/utils/getComponent.js
@@ -0,0 +1,60 @@
+/**
+ * Given a component module and a name,
+ * return the appropriate export.
+ * See /docs/Components.md
+ *
+ * @param  {object} module
+ * @param  {string} name
+ * @return {function|object}
+ */
+export default function getComponent(module, name) {
+	//
+	// If the module defines a default export, return that
+	// e.g.
+	//
+	// ```
+	// export default function Component() { ... }
+	// ```
+	//
+	if (module.default) {
+		return module.default;
+	}
+
+	// If it is a CommonJS module which exports a function, return that
+	// e.g.
+	//
+	// ```
+	// function Component() { ... }
+	// module.exports = Component;
+	// ```
+	//
+	if (!module.__esModule && typeof module === 'function') {
+		return module;
+	}
+	const moduleKeys = Object.keys(module);
+
+	// If the module exports just one named export, return that
+	// e.g.
+	//
+	// ```
+	// export function Component() { ... }
+	// ```
+	//
+	if (moduleKeys.length === 1) {
+		return module[moduleKeys[0]];
+	}
+
+	// If the module exports a named export with the same name as the
+	// understood Component identifier, return that
+	// e.g.
+	//
+	// ```
+	// // /component.js
+	// export function someUtil() { ... }
+	// export Component() { ... } // if identifier is Component, return this named export
+	// ```
+	//
+	// Else return the module itself
+	//
+	return module[name] || module;
+}
diff --git a/src/utils/getRouteData.js b/src/utils/getRouteData.js
index 179141ab..23d8276f 100644
--- a/src/utils/getRouteData.js
+++ b/src/utils/getRouteData.js
@@ -6,6 +6,21 @@ import findSection from './findSection';
 import getInfoFromHash from './getInfoFromHash';
 import { DisplayModes } from '../consts';
 
+/**
+ * Returns the first element of the sections, which can be one of section or
+ * component.
+ *
+ * @param {object} sections
+ * @returns {object}
+ */
+const getFirstSectionOrComponent = sections => {
+	const firstSection = sections[0];
+	if (firstSection.components && firstSection.components.length > 0) {
+		return { ...firstSection, components: [firstSection.components[0]] };
+	}
+	return firstSection;
+};
+
 /**
  * Return sections / components / examples to show on a screen according to a current route.
  *
@@ -15,9 +30,10 @@ import { DisplayModes } from '../consts';
  *
  * @param {object} sections
  * @param {string} hash
+ * @param {boolean} pagePerSection
  * @returns {object}
  */
-export default function getRouteData(sections, hash) {
+export default function getRouteData(sections, hash, pagePerSection) {
 	// Parse URL hash to check if the components list must be filtered
 	const {
 		// Name of the filtered component/section to show isolated (/#!/Button → Button)
@@ -50,6 +66,9 @@ export default function getRouteData(sections, hash) {
 				displayMode = DisplayModes.example;
 			}
 		}
+	} else if (pagePerSection) {
+		// If one component per page mode then show demos for first component
+		sections = [getFirstSectionOrComponent(sections)];
 	}
 
 	return { sections, displayMode };
diff --git a/src/utils/globalizeComponent.js b/src/utils/globalizeComponent.js
index 2c7244b5..ba00a03f 100644
--- a/src/utils/globalizeComponent.js
+++ b/src/utils/globalizeComponent.js
@@ -1,3 +1,5 @@
+import getComponent from './getComponent';
+
 /**
  * Expose component as global variables.
  *
@@ -8,8 +10,5 @@ export default function globalizeComponent(component) {
 		return;
 	}
 
-	global[component.name] =
-		!component.props.path || component.props.path === 'default'
-			? component.module.default || component.module
-			: component.module[component.props.path];
+	global[component.name] = getComponent(component.module, component.name);
 }
diff --git a/src/utils/renderStyleguide.js b/src/utils/renderStyleguide.js
index f9f67494..18d4c544 100644
--- a/src/utils/renderStyleguide.js
+++ b/src/utils/renderStyleguide.js
@@ -27,10 +27,11 @@ export default function renderStyleguide(
 	// all components accessible to all examples
 	globalizeComponents(allSections);
 
-	const { sections, displayMode } = getRouteData(allSections, loc.hash);
+	const { title, pagePerSection } = styleguide.config;
+	const { sections, displayMode } = getRouteData(allSections, loc.hash, pagePerSection);
 
 	// Update page title
-	doc.title = getPageTitle(sections, styleguide.config.title, displayMode);
+	doc.title = getPageTitle(sections, title, displayMode);
 
 	// If the current hash location was set to just `/` (e.g. when navigating back from isolated view to overview)
 	// replace the URL with one without hash, to present the user with a single address of the overview screen
@@ -43,11 +44,13 @@ export default function renderStyleguide(
 		<StyleGuide
 			codeRevision={codeRevision}
 			config={styleguide.config}
-			slots={slots}
+			slots={slots(styleguide.config)}
 			welcomeScreen={styleguide.welcomeScreen}
 			patterns={styleguide.patterns}
 			sections={sections}
+			allSections={allSections}
 			displayMode={displayMode}
+			pagePerSection={pagePerSection}
 		/>
 	);
 }


Test patch:
diff --git a/loaders/__tests__/props-loader.spec.js b/loaders/__tests__/props-loader.spec.js
index 3232e05e..41a22497 100644
--- a/loaders/__tests__/props-loader.spec.js
+++ b/loaders/__tests__/props-loader.spec.js
@@ -1,6 +1,8 @@
 import vm from 'vm';
 import { readFileSync } from 'fs';
 import glogg from 'glogg';
+
+import sortBy from 'lodash/sortBy';
 import config from '../../scripts/schemas/config';
 import propsLoader from '../props-loader';
 
@@ -44,6 +46,67 @@ it('should extract doclets', () => {
 	expect(result).toMatch(/require\('!!.*?\/loaders\/examples-loader\.js!\.\/examples.md'\)/);
 });
 
+describe('property sorting', () => {
+	it('should sort properties by default', () => {
+		const file = './test/components/Price/Price.js';
+		const result = propsLoader.call(
+			{
+				request: file,
+				_styleguidist,
+			},
+			readFileSync(file, 'utf8')
+		);
+		expect(result).toBeTruthy();
+
+		expect(() => new vm.Script(result)).not.toThrow();
+		expect(result.includes('makeABarrelRoll')).toBe(false);
+		expect(result).toMatch(
+			/props[\s\S]*?name': 'symbol'[\s\S]*?name': 'value'[\s\S]*?name': 'emphasize'[\s\S]*?name': 'unit'/m
+		);
+	});
+
+	it('should be possible to disable sorting', () => {
+		const file = './test/components/Price/Price.js';
+		const result = propsLoader.call(
+			{
+				request: file,
+				_styleguidist: { ..._styleguidist, propsTransform: props => props },
+			},
+			readFileSync(file, 'utf8')
+		);
+		expect(result).toBeTruthy();
+
+		expect(() => new vm.Script(result)).not.toThrow();
+		expect(result.includes('makeABarrelRoll')).toBe(false);
+		expect(result).toMatch(
+			/props[\s\S]*?name': 'value'[\s\S]*?name': 'unit'[\s\S]*?name': 'emphasize'[\s\S]*?name': 'symbol'/m
+		);
+	});
+
+	it('should be possible to write custom sort function', () => {
+		const sortFn = props => {
+			const requiredProps = sortBy(props.filter(prop => prop.required), 'name').reverse();
+			const optionalProps = sortBy(props.filter(prop => !prop.required), 'name').reverse();
+			return optionalProps.concat(requiredProps);
+		};
+		const file = './test/components/Price/Price.js';
+		const result = propsLoader.call(
+			{
+				request: file,
+				_styleguidist: { ..._styleguidist, propsTransform: sortFn },
+			},
+			readFileSync(file, 'utf8')
+		);
+		expect(result).toBeTruthy();
+
+		expect(() => new vm.Script(result)).not.toThrow();
+		expect(result.includes('makeABarrelRoll')).toBe(false);
+		expect(result).toMatch(
+			/props[\s\S]*?name': 'unit'[\s\S]*?name': 'emphasize'[\s\S]*?name': 'value'[\s\S]*?name': 'symbol'/m
+		);
+	});
+});
+
 it('should work with JSDoc annnotated components', () => {
 	const file = './test/components/Annotation/Annotation.js';
 	const result = propsLoader.call(
diff --git a/loaders/utils/__tests__/__snapshots__/getSections.spec.js.snap b/loaders/utils/__tests__/__snapshots__/getSections.spec.js.snap
index 57e8da85..6af4869a 100644
--- a/loaders/utils/__tests__/__snapshots__/getSections.spec.js.snap
+++ b/loaders/utils/__tests__/__snapshots__/getSections.spec.js.snap
@@ -56,6 +56,19 @@ Array [
         },
         "slug": "placeholder-2",
       },
+      Object {
+        "filepath": "components/Price/Price.js",
+        "hasExamples": true,
+        "metadata": Object {},
+        "module": Object {
+          "require": "~/test/components/Price/Price.js",
+        },
+        "pathLine": "components/Price/Price.js",
+        "props": Object {
+          "require": "!!~/loaders/props-loader.js!~/test/components/Price/Price.js",
+        },
+        "slug": "price-2",
+      },
       Object {
         "filepath": "components/RandomButton/RandomButton.js",
         "hasExamples": true,
@@ -107,6 +120,19 @@ Array [
         },
         "slug": "placeholder-3",
       },
+      Object {
+        "filepath": "components/Price/Price.js",
+        "hasExamples": true,
+        "metadata": Object {},
+        "module": Object {
+          "require": "~/test/components/Price/Price.js",
+        },
+        "pathLine": "components/Price/Price.js",
+        "props": Object {
+          "require": "!!~/loaders/props-loader.js!~/test/components/Price/Price.js",
+        },
+        "slug": "price-3",
+      },
       Object {
         "filepath": "components/RandomButton/RandomButton.js",
         "hasExamples": true,
@@ -175,6 +201,19 @@ Object {
       },
       "slug": "placeholder",
     },
+    Object {
+      "filepath": "components/Price/Price.js",
+      "hasExamples": true,
+      "metadata": Object {},
+      "module": Object {
+        "require": "~/test/components/Price/Price.js",
+      },
+      "pathLine": "components/Price/Price.js",
+      "props": Object {
+        "require": "!!~/loaders/props-loader.js!~/test/components/Price/Price.js",
+      },
+      "slug": "price",
+    },
     Object {
       "filepath": "components/RandomButton/RandomButton.js",
       "hasExamples": true,
@@ -243,6 +282,19 @@ Object {
       },
       "slug": "placeholder-1",
     },
+    Object {
+      "filepath": "components/Price/Price.js",
+      "hasExamples": true,
+      "metadata": Object {},
+      "module": Object {
+        "require": "~/test/components/Price/Price.js",
+      },
+      "pathLine": "components/Price/Price.js",
+      "props": Object {
+        "require": "!!~/loaders/props-loader.js!~/test/components/Price/Price.js",
+      },
+      "slug": "price-1",
+    },
     Object {
       "filepath": "components/RandomButton/RandomButton.js",
       "hasExamples": true,
diff --git a/loaders/utils/__tests__/getComponentFiles.spec.js b/loaders/utils/__tests__/getComponentFiles.spec.js
index 97b0ed1f..91f2ba4c 100644
--- a/loaders/utils/__tests__/getComponentFiles.spec.js
+++ b/loaders/utils/__tests__/getComponentFiles.spec.js
@@ -24,12 +24,24 @@ it('getComponentFiles() should accept components as a function that returns abso
 	expect(deabs(result)).toEqual(['~/one.js', '~/two.js']);
 });
 
+it('getComponentFiles() should accept components as an array of file names', () => {
+	const result = getComponentFiles(components, configDir);
+	expect(deabs(result)).toEqual(['~/one.js', '~/two.js']);
+});
+
+it('getComponentFiles() should accept components as a function that returns absolute paths', () => {
+	const absolutize = files => files.map(file => path.join(configDir, file));
+	const result = getComponentFiles(absolutize(components), configDir);
+	expect(deabs(result)).toEqual(['~/one.js', '~/two.js']);
+});
+
 it('getComponentFiles() should accept components as a glob', () => {
 	const result = getComponentFiles(glob, configDir);
 	expect(deabs(result)).toEqual([
 		'~/components/Annotation/Annotation.js',
 		'~/components/Button/Button.js',
 		'~/components/Placeholder/Placeholder.js',
+		'~/components/Price/Price.js',
 		'~/components/RandomButton/RandomButton.js',
 	]);
 });
@@ -39,10 +51,11 @@ it('getComponentFiles() should ignore specified patterns', () => {
 	expect(deabs(result)).toEqual([
 		'~/components/Annotation/Annotation.js',
 		'~/components/Placeholder/Placeholder.js',
+		'~/components/Price/Price.js',
 	]);
 });
 
-it('getComponentFiles() should throw if components is not a function or a string', () => {
+it('getComponentFiles() should throw if components is not a function, array or a string', () => {
 	const fn = () => getComponentFiles(42, configDir);
-	expect(fn).toThrowError('should be string or function');
+	expect(fn).toThrowError('should be string, function or array');
 });
diff --git a/loaders/utils/__tests__/getComponentFilesFromSections.spec.js b/loaders/utils/__tests__/getComponentFilesFromSections.spec.js
index 54da82b0..9127315d 100644
--- a/loaders/utils/__tests__/getComponentFilesFromSections.spec.js
+++ b/loaders/utils/__tests__/getComponentFilesFromSections.spec.js
@@ -30,10 +30,14 @@ it('getComponentFilesFromSections() should return a list of files', () => {
 	expect(deabs(result)).toEqual([
 		'~/components/Button/Button.js',
 		'~/components/Placeholder/Placeholder.js',
+		'~/components/Price/Price.js',
 	]);
 });
 
 it('getComponentFilesFromSections() should ignore specified patterns', () => {
 	const result = getComponentFilesFromSections(sections, configDir, ['**/*Button*']);
-	expect(deabs(result)).toEqual(['~/components/Placeholder/Placeholder.js']);
+	expect(deabs(result)).toEqual([
+		'~/components/Placeholder/Placeholder.js',
+		'~/components/Price/Price.js',
+	]);
 });
diff --git a/loaders/utils/__tests__/sortProps.spec.js b/loaders/utils/__tests__/sortProps.spec.js
new file mode 100644
index 00000000..a3d99662
--- /dev/null
+++ b/loaders/utils/__tests__/sortProps.spec.js
@@ -0,0 +1,37 @@
+import sortProps from '../sortProps';
+
+function makeProp(name, required, defaultValue, type) {
+	// Default values.
+	required = required === undefined ? false : required;
+	type = type === undefined ? { name: 'string' } : type;
+
+	return {
+		name,
+		required,
+		defaultValue,
+		type,
+	};
+}
+
+it('should sort required props', () => {
+	const props = [makeProp('prop2', true), makeProp('prop1', true)];
+	const result = sortProps(props);
+	expect(result.map(prop => prop.name)).toEqual(['prop1', 'prop2']);
+});
+
+it('should sort optional props', () => {
+	const props = [makeProp('prop2', false), makeProp('prop1', false)];
+	const result = sortProps(props);
+	expect(result.map(prop => prop.name)).toEqual(['prop1', 'prop2']);
+});
+
+it('should sort mixed props (required props should come first)', () => {
+	const props = [
+		makeProp('prop2', false),
+		makeProp('prop1', true),
+		makeProp('prop3', true),
+		makeProp('prop4', false),
+	];
+	const result = sortProps(props);
+	expect(result.map(prop => prop.name)).toEqual(['prop1', 'prop3', 'prop2', 'prop4']);
+});
diff --git a/src/utils/__tests__/__snapshots__/getRouteData.spec.js.snap b/src/utils/__tests__/__snapshots__/getRouteData.spec.js.snap
index a4df1d18..9f866ecf 100644
--- a/src/utils/__tests__/__snapshots__/getRouteData.spec.js.snap
+++ b/src/utils/__tests__/__snapshots__/getRouteData.spec.js.snap
@@ -1,5 +1,73 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`getRouteData should return first component if pagePerSection and hash is empty 1`] = `
+Object {
+  "displayMode": "all",
+  "sections": Array [
+    Object {
+      "components": Array [
+        Object {
+          "module": 1,
+          "name": "Button",
+          "props": Object {
+            "displayName": "Button",
+            "examples": Array [
+              "example 0",
+              "example 1",
+            ],
+          },
+        },
+      ],
+      "sections": Array [],
+    },
+  ],
+}
+`;
+
+exports[`getRouteData should return first section if pagePerSection and hash is empty 1`] = `
+Object {
+  "displayMode": "all",
+  "sections": Array [
+    Object {
+      "sections": Array [
+        Object {
+          "components": Array [
+            Object {
+              "module": 1,
+              "name": "Button",
+              "props": Object {
+                "displayName": "Button",
+                "examples": Array [
+                  "example 0",
+                  "example 1",
+                ],
+              },
+            },
+            Object {
+              "module": 2,
+              "name": "Image",
+              "props": Object {
+                "displayName": "Image",
+              },
+            },
+          ],
+          "sections": Array [],
+        },
+        Object {
+          "components": Array [],
+          "content": Array [
+            "example 0",
+            "example 1",
+          ],
+          "name": "Section",
+          "sections": Array [],
+        },
+      ],
+    },
+  ],
+}
+`;
+
 exports[`getRouteData should return one component 1`] = `
 Object {
   "displayMode": "component",
diff --git a/src/utils/__tests__/__snapshots__/renderStyleguide.spec.js.snap b/src/utils/__tests__/__snapshots__/renderStyleguide.spec.js.snap
index d0ead0d4..a2416be1 100644
--- a/src/utils/__tests__/__snapshots__/renderStyleguide.spec.js.snap
+++ b/src/utils/__tests__/__snapshots__/renderStyleguide.spec.js.snap
@@ -31,6 +31,7 @@ exports[`renderStyleguide should render StyleGuide component 1`] = `
           },
         ]
       }
+      useIsolatedLinks={undefined}
     />
   }
 >
diff --git a/src/utils/__tests__/getComponent.spec.js b/src/utils/__tests__/getComponent.spec.js
new file mode 100644
index 00000000..ed038099
--- /dev/null
+++ b/src/utils/__tests__/getComponent.spec.js
@@ -0,0 +1,47 @@
+import getComponent from '../getComponent';
+
+describe('getComponent', () => {
+	describe('if there is a default export in the module', () => {
+		it('should return that', () => {
+			const module = { default: 'useMe' };
+			const actual = getComponent(module);
+			expect(actual).toBe(module.default);
+		});
+	});
+
+	describe('if it is a CommonJS module and exports a function', () => {
+		it('should return the module', () => {
+			const testCases = [() => {}, function() {}, class Class {}];
+			testCases.forEach(testCase => {
+				const actual = getComponent(testCase);
+				expect(actual).toBe(testCase);
+			});
+		});
+	});
+
+	describe('if there is only one named export in the module', () => {
+		it('should return that', () => {
+			const module = { oneNamedExport: 'isLonely' };
+			const actual = getComponent(module);
+			expect(actual).toBe(module.oneNamedExport);
+		});
+	});
+
+	describe('if there is a named export whose name matches the name argument', () => {
+		it('should return that', () => {
+			const name = 'Component';
+			const module = { [name]: 'isNamed', OtherComponent: 'isAlsoNamed' };
+			const actual = getComponent(module, name);
+			expect(actual).toBe(module[name]);
+		});
+	});
+
+	describe('if there is more than one named export and no matching name', () => {
+		it('should fall back on returning the module as a whole', () => {
+			const name = 'Component';
+			const module = { RandomName: 'isNamed', confusingExport: 123 };
+			const actual = getComponent(module, name);
+			expect(actual).toBe(module);
+		});
+	});
+});
diff --git a/src/utils/__tests__/getRouteData.spec.js b/src/utils/__tests__/getRouteData.spec.js
index b7fce1d7..7f37694b 100644
--- a/src/utils/__tests__/getRouteData.spec.js
+++ b/src/utils/__tests__/getRouteData.spec.js
@@ -60,4 +60,14 @@ describe('getRouteData', () => {
 		const result = getRouteData(sections, '#!/Section/1');
 		expect(result).toMatchSnapshot();
 	});
+
+	it('should return first section if pagePerSection and hash is empty', () => {
+		const result = getRouteData(sections, '', true);
+		expect(result).toMatchSnapshot();
+	});
+
+	it('should return first component if pagePerSection and hash is empty', () => {
+		const result = getRouteData(sections[0].sections, '', true);
+		expect(result).toMatchSnapshot();
+	});
 });
diff --git a/src/utils/__tests__/globalizeComponent.spec.js b/src/utils/__tests__/globalizeComponent.spec.js
index 78fb384a..6c4079fa 100644
--- a/src/utils/__tests__/globalizeComponent.spec.js
+++ b/src/utils/__tests__/globalizeComponent.spec.js
@@ -1,18 +1,21 @@
 import globalizeComponent from '../globalizeComponent';
 
-afterEach(() => {
-	delete global.Foo;
-});
+const component = { module: 'someModule', name: 'SomeName' };
 
 describe('globalizeComponent', () => {
-	it('should set component’s module as a global variable', () => {
-		const globalsCount = Object.keys(global).length;
-		globalizeComponent({
-			name: 'Foo',
-			props: {},
-			module: 13,
-		});
-		expect(Object.keys(global).length).toBe(globalsCount + 1);
-		expect(global.Foo).toBe(13);
+	afterEach(() => {
+		delete global[component.name];
+	});
+
+	it('should not add anything as a global variable if there is no component name', () => {
+		expect(global[component.name]).toBeUndefined();
+		globalizeComponent({});
+		expect(global[component.name]).toBeUndefined();
+	});
+
+	it('should set the return value of getComponent as a global variable', () => {
+		expect(global[component.name]).toBeUndefined();
+		globalizeComponent(component);
+		expect(global[component.name]).toBe(component.module);
 	});
 });
diff --git a/test/components/Price/Price.js b/test/components/Price/Price.js
new file mode 100644
index 00000000..aa9abb9b
--- /dev/null
+++ b/test/components/Price/Price.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const unitSymbols = {
+	USD: '$',
+	EUR: '€',
+};
+
+/**
+ * Price component that renders a price and a unit.
+ */
+export default function Price(props) {
+	let Host = 'span';
+	if (props.emphasize) {
+		Host = 'em';
+	}
+	return (
+		<Host>
+			{props.value}
+			{!props.symbol ? props.unit : unitSymbols[props.unit]}
+		</Host>
+	);
+}
+
+Price.propTypes = {
+	/** Price value. */
+	value: PropTypes.number.isRequired,
+	/** Price unit */
+	unit: PropTypes.oneOf(['EUR', 'USD']),
+	/** Flag that determines if the price should be emphasized or not. */
+	emphasize: PropTypes.bool,
+	/** Defines if the unit should be shown as a symbol or not. */
+	symbol: PropTypes.bool.isRequired,
+};