diff --git a/docs/app/Components/ComponentDoc/ComponentExample.js b/docs/app/Components/ComponentDoc/ComponentExample.js index 8bff17c09f..6d4ea5eebb 100644 --- a/docs/app/Components/ComponentDoc/ComponentExample.js +++ b/docs/app/Components/ComponentDoc/ComponentExample.js @@ -1,13 +1,15 @@ import * as Babel from 'babel-standalone' import _ from 'lodash' import React, { Component, createElement, isValidElement, PropTypes } from 'react' +import { withRouter } from 'react-router' import { renderToStaticMarkup } from 'react-dom/server' import { html } from 'js-beautify' import copyToClipboard from 'copy-to-clipboard' import { exampleContext, repoURL } from 'docs/app/utils' -import { Divider, Grid, Icon, Header, Menu } from 'src' +import { Divider, Grid, Icon, Header, Menu, Popup } from 'src' import Editor from 'docs/app/Components/Editor/Editor' +import { scrollToAnchor } from 'docs/app/utils' const babelConfig = { presets: ['es2015', 'react', 'stage-1'], @@ -50,41 +52,91 @@ const errorStyle = { background: '#fff2f2', } +const ToolTip = ({ children, content }) => ( + +) +ToolTip.propTypes = { + children: PropTypes.node, + content: PropTypes.node, +} + /** * Renders a `component` and the raw `code` that produced it. * Allows toggling the the raw `code` code block. */ -export default class ComponentExample extends Component { +class ComponentExample extends Component { static propTypes = { children: PropTypes.node, description: PropTypes.node, examplePath: PropTypes.string.isRequired, + history: PropTypes.object.isRequired, + location: PropTypes.object.isRequired, + match: PropTypes.object.isRequired, title: PropTypes.node, } componentWillMount() { - const { title } = this.props + const { examplePath } = this.props const sourceCode = this.getOriginalSourceCode() + this.anchorName = _.kebabCase(_.last(examplePath.split('/'))) + // show code for direct links to examples - const active = title && _.kebabCase(title) === location.hash.replace('#', '') + const showCode = this.anchorName === location.hash.replace('#', '') const exampleElement = this.renderOriginalExample() - const staticMarkup = renderToStaticMarkup(exampleElement) + const markup = renderToStaticMarkup(exampleElement) this.setState({ exampleElement, - showCode: active, - showHTML: active, + showCode, sourceCode, - staticMarkup, + markup, }) } + setHashAndScroll = () => { + const { history } = this.props + history.replace(location.pathname + '#' + this.anchorName) + scrollToAnchor() + } + + removeHash = () => { + const { history } = this.props + history.replace(location.pathname) + } + + handleDirectLinkClick = (e) => { + e.preventDefault() + this.setHashAndScroll() + + copyToClipboard(this.anchorName) + this.setState({ copiedDirectLink: true }) + + setTimeout(() => this.setState({ copiedDirectLink: false }), 1000) + } + + handleShowCodeClick = (e) => { + e.preventDefault() + + const { showCode } = this.state + this.setState({ showCode: !showCode }) + + if (!showCode) this.setHashAndScroll() + else this.removeHash() + } + copyJSX = () => { - const { sourceCode } = this.state - copyToClipboard(sourceCode) - this.setState({ copied: true }) - setTimeout(() => this.setState({ copied: false }), 1000) + copyToClipboard(this.getOriginalSourceCode()) + this.setState({ copiedCode: true }) + setTimeout(() => this.setState({ copiedCode: false }), 1000) } resetJSX = () => { @@ -98,14 +150,17 @@ export default class ComponentExample extends Component { getOriginalSourceCode = () => { const { examplePath } = this.props - return require(`!raw!docs/app/Examples/${examplePath}`) - } - getKebabExamplePath = () => _.kebabCase(this.props.examplePath) + if (!this.sourceCode) this.sourceCode = require(`!raw!docs/app/Examples/${examplePath}`) + + return this.sourceCode + } - toggleShowCode = () => this.setState({ showCode: !this.state.showCode }) + getKebabExamplePath = () => { + if (!this.kebabExamplePath) this.kebabExamplePath = _.kebabCase(this.props.examplePath) - toggleShowHTML = () => this.setState({ showHTML: !this.state.showHTML }) + return this.kebabExamplePath + } renderError = _.debounce((error) => { this.setState({ error }) @@ -191,7 +246,7 @@ export default class ComponentExample extends Component { this.setState({ error, exampleElement, - staticMarkup: renderToStaticMarkup(exampleElement), + markup: renderToStaticMarkup(exampleElement), }) } } catch (err) { @@ -204,9 +259,10 @@ export default class ComponentExample extends Component { this.renderSourceCode() } - renderJSXControls = () => { + setGitHubHrefs = () => { const { examplePath } = this.props - const { copied, error } = this.state + + if (this.ghEditHref && this.ghBugHref) return // get component name from file path: // elements/Button/Types/ButtonButtonExample @@ -214,12 +270,12 @@ export default class ComponentExample extends Component { const componentName = pathParts[1] const filename = pathParts[pathParts.length - 1] - const color = error ? 'red' : 'black' - const ghEditHref = [ + this.ghEditHref = [ `${repoURL}/edit/master/docs/app/Examples/${examplePath}.js`, `?message=docs(${filename}): your description`, ].join('') - const ghBugHref = [ + + this.ghBugHref = [ `${repoURL}/issues/new?`, _.map({ title: `fix(${componentName}): your description`, @@ -240,16 +296,23 @@ export default class ComponentExample extends Component { ].join('\n'), }, (val, key) => `${key}=${encodeURIComponent(val)}`).join('&'), ].join('') + } + + renderJSXControls = () => { + const { copiedCode, error } = this.state + this.setGitHubHrefs() + + const color = error ? 'red' : 'black' return ( @@ -304,12 +367,12 @@ export default class ComponentExample extends Component { } renderHTML = () => { - const { showHTML, staticMarkup } = this.state - if (!showHTML) return + const { showCode, markup } = this.state + if (!showCode) return // add new lines between almost all adjacent elements // moves inline elements to their own line - const preFormattedHTML = staticMarkup + const preFormattedHTML = markup .replace(/><(?!\/i|\/label|\/span|option)/g, '>\n<') const beautifiedHTML = html(preFormattedHTML, { @@ -330,36 +393,40 @@ export default class ComponentExample extends Component { render() { const { children, description, title } = this.props - const { exampleElement, showCode, showHTML } = this.state - const active = showCode || showHTML + const { copiedDirectLink, exampleElement, showCode } = this.state + const exampleStyle = { marginBottom: '2em', paddingBottom: '1em', transition: 'box-shadow 300ms' } - const style = { marginBottom: '4em', transition: 'box-shadow 0 ease-out' } - if (active) { - style.transitionDuration = '0.2s' - style.boxShadow = '0 0 30px #ccc' + if (showCode || location.hash === `#${this.anchorName}`) { + exampleStyle.boxShadow = '0 0 30px #ccc' } return ( - + - {title &&
{title}
} + {title &&
{title}
} {description &&

{description}

}
- - + + + + + + + + + + + + +
{children && ( @@ -379,3 +446,5 @@ export default class ComponentExample extends Component { ) } } + +export default withRouter(ComponentExample) diff --git a/docs/app/Components/ComponentDoc/ComponentProps.js b/docs/app/Components/ComponentDoc/ComponentProps.js index 89234697c9..63bc7d16d9 100644 --- a/docs/app/Components/ComponentDoc/ComponentProps.js +++ b/docs/app/Components/ComponentDoc/ComponentProps.js @@ -1,12 +1,28 @@ import _ from 'lodash' import React, { Component, PropTypes } from 'react' -import { Icon, Popup, Table } from 'src' +import { Header, Icon, Popup, Table } from 'src' import { SUI } from 'src/lib' -const descriptionExtraStyle = { - fontSize: '0.95em', - color: '#777', +const extraDescriptionStyle = { + color: '#666', +} +const extraDescriptionContentStyle = { + marginLeft: '0.5em', +} + +const Extra = ({ title, children, inline, ...rest }) => ( +
+ {title} +
+ {children} +
+
+) +Extra.propTypes = { + children: PropTypes.node, + inline: PropTypes.bool, + title: PropTypes.node, } const getTagType = tag => tag.type.type === 'AllLiteral' ? 'any' : tag.type.name @@ -71,24 +87,28 @@ export default class ComponentProps extends Component { const paramSignature = params .map(param => `${param.name}: ${getTagType(param)}`) + // prevent object properties from showing as individual params + .filter(p => !p.includes('.')) .join(', ') - const tagDescriptions = _.compact([...params, returns]).map(tag => ( -
- {tag.name || tag.title} - {tag.description} -
- )) - - const signature = ( -
{item.name}({paramSignature}){returns ? `: ${getTagType(returns)}` : ''}
- ) + const tagDescriptionRows = _.compact([...params, returns]).map(tag => { + const name = tag.name || tag.title + return ( +
+
+ {name} +
+
+ {tag.description} +
+
+ ) + }) return ( -
- Signature: - {signature} - {tagDescriptions} -
+ {item.name}({paramSignature}){returns ? `: ${getTagType(returns)}` : ''}}> + {tagDescriptionRows} + ) } @@ -104,7 +124,7 @@ export default class ComponentProps extends Component { if (item.type !== '{enum}') return const { showEnumsFor } = this.state - const truncateAt = 30 + const truncateAt = 10 if (!item.value) return null @@ -112,37 +132,37 @@ export default class ComponentProps extends Component { return accumulator.concat(this.expandEnums(_.trim(v.value || v, '.\''))) }, []) + const valueElements = _.map(values, val => {val} ) + // show all if there are few if (values.length < truncateAt) { return ( -

- Enums: {values.join(', ')} -

+ + {valueElements} + ) } // add button to show more when there are many values and it is not toggled if (!showEnumsFor[item.name]) { return ( -

- Enums: + Show all {values.length} -

{values.slice(0, truncateAt - 1).join(', ')}...
-

+
{valueElements.slice(0, truncateAt - 1)}...
+ ) } // add "show more" button when there are many return ( -

- Enums: + Show less -

{values.join(', ')}
-

+
{valueElements}
+ ) } @@ -153,9 +173,7 @@ export default class ComponentProps extends Component { {this.renderDefaultValue(item)} {item.type} - {item.description && ( -

{item.description}

- )} + {item.description &&

{item.description}

} {this.renderFunctionSignature(item)} {this.renderEnums(item)}
@@ -188,7 +206,7 @@ export default class ComponentProps extends Component { }), 'name') return ( - +
Name diff --git a/docs/app/Components/ExternalExampleLayout.js b/docs/app/Components/ExternalExampleLayout.js new file mode 100644 index 0000000000..00bf67abad --- /dev/null +++ b/docs/app/Components/ExternalExampleLayout.js @@ -0,0 +1,34 @@ +import 'semantic-ui-css/semantic.css' +import _ from 'lodash/fp' +import React, { Component, PropTypes } from 'react' + +import PageNotFound from '../Views/PageNotFound' +import { exampleContext } from 'docs/app/utils' + +const exampleKeys = exampleContext.keys() + +export default class ExternalExampleLayout extends Component { + static propTypes = { + children: PropTypes.node, + history: PropTypes.object.isRequired, + location: PropTypes.object.isRequired, + match: PropTypes.shape({ + params: PropTypes.shape({ + kebabCaseName: PropTypes.string.isRequired, + }), + }).isRequired, + } + + render() { + const { kebabCaseName } = this.props.match.params + const componentName = _.startCase(kebabCaseName).replace(/ /g, '') + + const componentKey = _.find(_.endsWith(`${componentName}.js`), exampleKeys) + if (!componentKey) return + + const ExampleComponent = exampleContext(componentKey).default + if (!ExampleComponent) return + + return + } +} diff --git a/docs/app/Components/Layout.js b/docs/app/Components/Layout.js index 839fe2b016..0a2d3b2afa 100644 --- a/docs/app/Components/Layout.js +++ b/docs/app/Components/Layout.js @@ -5,6 +5,7 @@ import React, { Component, PropTypes } from 'react' import Sidebar from 'docs/app/Components/Sidebar/Sidebar' import style from 'docs/app/Style' import TAAttribution from 'docs/app/Components/TAAttribution/TAAttribution' +import { scrollToAnchor } from 'docs/app/utils' const anchors = new AnchorJS({ icon: '#', @@ -28,6 +29,9 @@ export default class Layout extends Component { } resetPage = () => { + // only reset the page when changing routes + if (this.pathname === location.pathname) return + clearTimeout(this.scrollStartTimeout) scrollTo(0, 0) @@ -36,30 +40,8 @@ export default class Layout extends Component { anchors.remove([1, 2, 3, 4, 5, 6].map(n => `.rendered-example h${n}`).join(', ')) anchors.remove('.no-anchor') - this.scrollStartTimeout = setTimeout(this.scrollToAnchor, 500) - } - - scrollToAnchor = () => { - const anchor = location.hash && document.querySelector(location.hash) - - // no scroll to target, stop - if (!anchor) return - - const elementTop = Math.round(anchor.getBoundingClientRect().top) - - // scrolled to element, stop - if (elementTop === 0) return - - // hit max scroll boundaries, stop - const isScrolledToTop = scrollY === 0 - const isScrolledToBottom = scrollY + document.body.clientHeight === document.body.scrollHeight - const scrollStep = Math.ceil((Math.abs(elementTop / 8))) * Math.sign(elementTop) - - if (isScrolledToBottom && scrollStep > 0 || isScrolledToTop && scrollStep < 0) return - - // more scrolling to do! - scrollBy(0, scrollStep) - requestAnimationFrame(this.scrollToAnchor) + this.scrollStartTimeout = setTimeout(scrollToAnchor, 500) + this.pathname = location.pathname } render() { diff --git a/docs/app/routes.js b/docs/app/routes.js index f50c7105e9..17582cfd58 100644 --- a/docs/app/routes.js +++ b/docs/app/routes.js @@ -3,9 +3,11 @@ import { BrowserRouter, Route, Redirect, + Switch, } from 'react-router-dom' import Root from './Components/Root' import Layout from './Components/Layout' +import ExternalExampleLayout from './Components/ExternalExampleLayout' import Introduction from './Views/Introduction' import Usage from './Views/Usage' import PageNotFound from './Views/PageNotFound' @@ -14,13 +16,18 @@ const RedirectToIntro = () => const Router = () => ( - - - - - - - + + + + + + + + + + + + ) diff --git a/docs/app/utils.js b/docs/app/utils.js index 4905daa62e..aa5d514180 100644 --- a/docs/app/utils.js +++ b/docs/app/utils.js @@ -24,3 +24,26 @@ export const repoURL = 'https://github.com/Semantic-Org/Semantic-UI-React' export const semanticUIDocsURL = 'https://semantic-ui.com/' export const semanticUIRepoURL = 'https://github.com/Semantic-Org/Semantic-UI' export const semanticUICSSRepoURL = 'https://github.com/Semantic-Org/Semantic-UI-CSS' + +export const scrollToAnchor = () => { + const anchor = location.hash && document.querySelector(location.hash) + + // no scroll to target, stop + if (!anchor) return + + const elementTop = Math.round(anchor.getBoundingClientRect().top) + + // scrolled to element, stop + if (elementTop === 0) return + + // hit max scroll boundaries, stop + const isScrolledToTop = scrollY === 0 + const isScrolledToBottom = scrollY + document.body.clientHeight === document.body.scrollHeight + const scrollStep = Math.ceil((Math.abs(elementTop / 8))) * Math.sign(elementTop) + + if (isScrolledToBottom && scrollStep > 0 || isScrolledToTop && scrollStep < 0) return + + // more scrolling to do! + scrollBy(0, scrollStep) + requestAnimationFrame(scrollToAnchor) +} diff --git a/src/addons/Portal/Portal.js b/src/addons/Portal/Portal.js index 09ed29dc09..044d980920 100644 --- a/src/addons/Portal/Portal.js +++ b/src/addons/Portal/Portal.js @@ -15,6 +15,9 @@ const debug = makeDebugger('portal') /** * A component that allows you to render children outside their parent. * @see Modal + * @see Popup + * @see Dimmer + * @see Confirm */ class Portal extends Component { static propTypes = { diff --git a/src/collections/Menu/Menu.js b/src/collections/Menu/Menu.js index 1d3c5cb28a..aaca9c9e32 100644 --- a/src/collections/Menu/Menu.js +++ b/src/collections/Menu/Menu.js @@ -66,7 +66,7 @@ class Menu extends Component { /** A vertical menu may take the size of its container. */ fluid: PropTypes.bool, - /** A menu may have labeled icons. */ + /** A menu may have just icons (bool) or labeled icons. */ icon: PropTypes.oneOfType([ PropTypes.bool, PropTypes.oneOf(['labeled']),