├── .DS_Store ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── README.md ├── nwb.config.js ├── package-lock.json ├── package.json ├── src ├── .babelrc ├── Code.js ├── Markdown.js ├── index.js └── makeCompiler.js ├── tests ├── .eslintrc └── index-test.js ├── themes ├── smackdown-dark.css └── smackdown-light.css ├── www ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── package.json ├── public │ └── robots.txt ├── src │ ├── App.js │ ├── components │ │ ├── ClickOutside.js │ │ ├── Markdown.js │ │ └── Sidebar.js │ ├── containers │ │ ├── 404.js │ │ ├── Code.js │ │ ├── Doc.js │ │ └── Home.js │ ├── docs │ │ ├── markdown.md │ │ └── syntax │ │ │ ├── elixir.md │ │ │ ├── javascript.md │ │ │ └── ruby.md │ ├── index.js │ └── logo.png ├── static.config.js └── yarn.lock ├── yarn-error.log └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tannerlinsley/react-smackdown/7fb4e8d770d4522a7d1d169832c5ae058d36f1c5/.DS_Store -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-tools" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es 4 | /lib 5 | /node_modules 6 | /umd 7 | npm-debug.log* 8 | yarn-error.log 9 | .DS_Store 10 | yarn-error.log 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /tests 2 | /www -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 6 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= v4 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the components's root directory will install everything you need for development. 8 | 9 | ## Demo Development Server 10 | 11 | - `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` will build the component for publishing to npm and also bundle the demo app. 24 | 25 | - `npm run clean` will delete built resources. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Smackdown 2 | 3 | React Smackdown is the easiest way to render markdown as html in React. With a single component and string of markdown, you get server-side-compatible JSX, complete with syntax highlighting. This is made possible with the following amazing libraries: 4 | 5 | * [RePrism](https://github.com/tannerlinsley/reprism) 6 | * [Showdown](https://github.com/showdownjs/showdown) 7 | * [React-Html-Parser](https://github.com/wrakky/react-html-parser) 8 | 9 | ## Features 10 | 11 | * ⚛️ React or Preact 12 | * 🚀 [Blazing](https://twitter.com/acdlite/status/974390255393505280) fast. 13 | * ⚙️ Custom Markdown-to-React Renderers 14 | * 🥇 Server-side-rendering compatible 15 | * 🎨 Themeable and Extensible 16 | 17 | ## Installation 18 | 19 | ```bash 20 | # yarn 21 | $ yarn add react-smackdown 22 | # npm 23 | $ npm install --save react-smackdown 24 | ``` 25 | 26 | ## `` 27 | 28 | Use the `` component to render markdown and also handle syntax highlighting automatically. 29 | 30 | **Props**: 31 | 32 | * `source: String (Required)` - The markdown string you want to render 33 | * `renderers: Object` - An object of optional renderers (`{tag: component}`), used to render any tags encountered in the markdown. 34 | * `markdownConfig: Object` - An optional configuration object for the underlying [`showdown`](https://github.com/showdownjs/showdown) instance. 35 | * `syntax: Object` - Optional props that will be forwarded to the `Code` component for any syntax in the markdown 36 | * `highlightInline: Boolean` - Defaults to `true`. If `true`, will also highlight any inline code blocks. 37 | 38 | ````javascript 39 | import { Markdown, loadLanguages } from 'react-smackdown' 40 | 41 | // Import any non-default RePrism languages you need: 42 | import jsx from 'reprism/languages/jsx' 43 | import bash from 'reprism/languages/bash' 44 | import go from 'reprism/languages/go' 45 | 46 | // Import any Prism-compatible theme. We even have our own "smackdown" themes! 47 | import 'smackdown/themes/smackdown-dark.css' 48 | // or 'smackdown/themes/smackdown-light.css' 49 | 50 | // Load the languages into smackdown (via RePrism) 51 | loadLanguages(jsx, bash, go) 52 | 53 | const someMarkdown = ` 54 | # Hello! 55 | 56 | This is some **markdown** with some code! 57 | 58 | ```javascript 59 | const foo = 'bar' 60 | ``` 61 | 62 | ```jsx 63 | const foo =
Hello!
64 | ``` 65 | 66 | ```bash 67 | $ npm install react-smackdown 68 | ``` 69 | 70 | ```go 71 | package main 72 | import "fmt" 73 | func main() { 74 | fmt.Println("hello world") 75 | } 76 | ``` 77 | ` 78 | 79 | 82 | ```` 83 | 84 | The above code would produce this markdown: 85 | 86 | > # Hello! 87 | > 88 | > This is some **markdown** with some code! 89 | > 90 | > ```javascript 91 | > const foo = "bar"; 92 | > ``` 93 | > 94 | > ```jsx 95 | > const foo =
Hello!
; 96 | > ``` 97 | > 98 | > ```bash 99 | > $ npm install react-smackdown 100 | > ``` 101 | > 102 | > ```go 103 | > package main 104 | > import "fmt" 105 | > func main() { 106 | > fmt.Println("hello world") 107 | > } 108 | > ``` 109 | 110 |
111 | 112 | #### React Renderers 113 | 114 | If you want to use your own components to render markdown, it's easy! Just provide an object of tag-to-component values to the `renderers` prop. 115 | 116 | **Important Note**: _If you override the `pre` or `code` tag renderers, you will most likely break the automatic syntax highlighting!_ 117 | 118 | ```javascript 119 | import { Markdown } from "smackdown"; 120 | 121 | const renderers = { 122 | a: ({ href, ...rest }) => 123 | } 124 | 125 | ; 129 | ``` 130 | 131 | ## `` 132 | 133 | If you don't need to render any markdown, but still want syntax highlighting, you can use the `` component directly! 134 | 135 | **Props**: 136 | 137 | * `source: String (Required)` - The code string you want to highlight and render 138 | * `language: String (Recommended)` - Defaults to `markup`. The [`reprism`](https://github.com/tannerlinsley/reprism) language you want to use to highlight the source. 139 | * `component: String | Component` - Defaults to `pre` The component you want to use to render the outer element. Change this to `code` if you need to do inline rendering of code. 140 | * `showLineNumbers: Boolean` - Defaults to `true` 141 | 142 | ```javascript 143 | import { Code } from "smackdown"; 144 | 145 | const source = ` 146 | const foo = 'bar' 147 | 148 | if (foo) { 149 | console.log('I has foo!') 150 | } 151 | ` 152 | 153 | ; 157 | ``` 158 | 159 | The above example would produce this code block: 160 | 161 | ```javascript 162 | const foo = "bar"; 163 | 164 | if (foo) { 165 | console.log("I has foo!"); 166 | } 167 | ``` 168 | 169 |
170 | -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-component', 3 | npm: { 4 | esModules: true, 5 | umd: { 6 | global: 'ReactSmackdown', 7 | externals: { 8 | react: 'React', 9 | }, 10 | }, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-smackdown", 3 | "version": "0.6.6", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "asap": { 7 | "version": "2.0.6", 8 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 9 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", 10 | "dev": true 11 | }, 12 | "core-js": { 13 | "version": "1.2.7", 14 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", 15 | "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", 16 | "dev": true 17 | }, 18 | "create-react-class": { 19 | "version": "15.6.0", 20 | "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.0.tgz", 21 | "integrity": "sha1-q0SEl8JlZuHilBPogyB9V8/nvtQ=", 22 | "dev": true 23 | }, 24 | "encoding": { 25 | "version": "0.1.12", 26 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", 27 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", 28 | "dev": true 29 | }, 30 | "fbjs": { 31 | "version": "0.8.14", 32 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.14.tgz", 33 | "integrity": "sha1-0dviviVMNakeCfMfnNUKQLKg7Rw=", 34 | "dev": true 35 | }, 36 | "iconv-lite": { 37 | "version": "0.4.18", 38 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", 39 | "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", 40 | "dev": true 41 | }, 42 | "is-stream": { 43 | "version": "1.1.0", 44 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 45 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", 46 | "dev": true 47 | }, 48 | "isomorphic-fetch": { 49 | "version": "2.2.1", 50 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", 51 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", 52 | "dev": true 53 | }, 54 | "js-tokens": { 55 | "version": "3.0.2", 56 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 57 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 58 | "dev": true 59 | }, 60 | "loose-envify": { 61 | "version": "1.3.1", 62 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", 63 | "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", 64 | "dev": true 65 | }, 66 | "node-fetch": { 67 | "version": "1.7.1", 68 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz", 69 | "integrity": "sha512-j8XsFGCLw79vWXkZtMSmmLaOk9z5SQ9bV/tkbZVCqvgwzrjAGq66igobLofHtF63NvMTp2WjytpsNTGKa+XRIQ==", 70 | "dev": true 71 | }, 72 | "object-assign": { 73 | "version": "4.1.1", 74 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 75 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 76 | "dev": true 77 | }, 78 | "promise": { 79 | "version": "7.3.1", 80 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 81 | "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", 82 | "dev": true 83 | }, 84 | "prop-types": { 85 | "version": "15.5.10", 86 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", 87 | "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", 88 | "dev": true 89 | }, 90 | "react": { 91 | "version": "15.6.1", 92 | "resolved": "https://registry.npmjs.org/react/-/react-15.6.1.tgz", 93 | "integrity": "sha1-uqhDTsZ4C96ZfNw4C3nNM7ljk98=", 94 | "dev": true 95 | }, 96 | "react-dom": { 97 | "version": "15.6.1", 98 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.1.tgz", 99 | "integrity": "sha1-LLDtQZEDjlPCCes6eaI+Kkz5lHA=", 100 | "dev": true 101 | }, 102 | "setimmediate": { 103 | "version": "1.0.5", 104 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 105 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", 106 | "dev": true 107 | }, 108 | "ua-parser-js": { 109 | "version": "0.7.14", 110 | "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.14.tgz", 111 | "integrity": "sha1-EQ1T+kw/MmwSEpK76skE0uAzh8o=", 112 | "dev": true 113 | }, 114 | "whatwg-fetch": { 115 | "version": "2.0.3", 116 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", 117 | "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=", 118 | "dev": true 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-smackdown", 3 | "version": "1.0.0-beta.3", 4 | "description": "react-smackdown React component", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "nwb build-react-component", 8 | "clean": "nwb clean-module && nwb clean-demo", 9 | "test": "nwb test-react", 10 | "test:coverage": "nwb test-react --coverage", 11 | "test:watch": "nwb test-react --server", 12 | "prepublishOnly": "yarn build", 13 | "startDocs": "cd www && yarn start", 14 | "buildDocs": "cd www && yarn build", 15 | "serveDocs": "cd www && yarn serve", 16 | "buildDocsProd": "cd www && yarn && yarn build" 17 | }, 18 | "dependencies": { 19 | "react-html-parser": "^2.0.2", 20 | "reprism": "^0.0.11", 21 | "showdown": "^1.8.6" 22 | }, 23 | "peerDependencies": { 24 | "react": ">=15" 25 | }, 26 | "devDependencies": { 27 | "eslint-config-react-tools": "^1.1.2", 28 | "nwb": "^0.19.1", 29 | "prop-types": "^15.6.0", 30 | "react": "^16.0.0", 31 | "react-dom": "^16.0.0", 32 | "styled-components": "^3.1.4" 33 | }, 34 | "author": "", 35 | "homepage": "", 36 | "license": "MIT", 37 | "repository": "", 38 | "keywords": [ 39 | "react-component" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "stage-0", "react"], 3 | "plugins": ["transform-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /src/Code.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Prism, { highlight, loadLanguages } from 'reprism' 3 | // 4 | 5 | export { loadLanguages } 6 | 7 | const languages = Prism.languages 8 | 9 | const toStyle = style => 10 | Object.keys(style) 11 | .map(key => `${key}:${style[key]};`) 12 | .join('') 13 | 14 | const rawEl = (el, props, children) => { 15 | const propsArr = [] 16 | Object.keys(props).forEach(key => { 17 | const value = key === 'style' ? toStyle(props[key]) : props[key] 18 | propsArr.push(`${key}${value ? `='${value.toString()}'` : ''}`) 19 | }) 20 | 21 | return `<${el}${propsArr.length ? ` ${propsArr.join(' ')}` : ''}>${ 22 | Array.isArray(children) ? children.join('') : children 23 | }` 24 | } 25 | 26 | export default class SyntaxHighlighter extends React.Component { 27 | render () { 28 | const { 29 | showLineNumbers = true, 30 | language, 31 | className = '', 32 | component = 'pre', 33 | source, 34 | children, 35 | ...rest 36 | } = this.props 37 | 38 | let resolvedLanguage = language 39 | 40 | if (!language) { 41 | resolvedLanguage = 'markup' 42 | } else if (!languages[language]) { 43 | console.warn( 44 | `The reprism language '${language}' hasn't been loaded yet! Using reprism's 'markdown' language for now. 45 | You can import this language using the following snippet: 46 | 47 | import { loadLanguages } from 'reprism' 48 | import ${language} from 'reprism/languages/${language}' 49 | 50 | loadLanguages(${language}) 51 | ` 52 | ) 53 | resolvedLanguage = 'markup' 54 | } 55 | 56 | if (resolvedLanguage === 'javascript' && languages.jsx) { 57 | resolvedLanguage = 'jsx' 58 | } 59 | 60 | const classes = [ 61 | ...className.split(' '), 62 | showLineNumbers && 'line-numbers', 63 | `language-${resolvedLanguage}`, 64 | ] 65 | .filter(Boolean) 66 | .join(' ') 67 | 68 | const resolvedSource = source || '' 69 | const lines = resolvedSource.split('\n') 70 | const lineCount = lines.length 71 | 72 | const lineNumbers = showLineNumbers 73 | ? rawEl( 74 | 'div', 75 | { 76 | class: 'line-numbers', 77 | style: { 78 | position: 'absolute', 79 | transform: 'translateX(-4em)', 80 | width: '3.5em', 81 | 'text-align': 'right', 82 | 'border-right': '2px solid #9999992b', 83 | 'padding-right': '.5em', 84 | 'pointer-events': 'none', 85 | color: '#999', 86 | }, 87 | }, 88 | [...new Array(lineCount)].map((d, i) => rawEl('span', {}, `${i + 1}
`)) 89 | ) 90 | : '' 91 | 92 | const html = highlight(resolvedSource, resolvedLanguage, { 93 | component: false, 94 | }) 95 | 96 | const finalHtml = `${lineNumbers}${html}` 97 | 98 | const Component = component 99 | 100 | return ( 101 | 116 | ) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Markdown.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import ReactHtmlParser, { convertNodeToElement, processNodes } from 'react-html-parser' 4 | import generatePropsFromAttributes from 'react-html-parser/lib/utils/generatePropsFromAttributes' 5 | // 6 | import Code from './Code' 7 | import makeCompiler from './makeCompiler' 8 | 9 | export default class Markdown extends React.Component { 10 | static propTypes = { 11 | renderers: PropTypes.object, 12 | source: PropTypes.string, 13 | syntax: PropTypes.object, 14 | markdownConfig: PropTypes.object, 15 | highlightInline: PropTypes.bool, 16 | } 17 | static defaultProps = { 18 | renderers: {}, 19 | source: '', 20 | syntax: {}, 21 | markdownConfig: {}, 22 | highlightInline: true, 23 | } 24 | constructor (props) { 25 | super() 26 | this.markdownCompiler = makeCompiler(props.markdownConfig) 27 | } 28 | componentWillReceiveProps (next) { 29 | if (next.markdownConfig !== this.props.markdownConfig) { 30 | this.markdownCompiler = makeCompiler(next.markdownConfig) 31 | } 32 | } 33 | render () { 34 | const { 35 | source, renderers, syntax, markdownConfig, highlightInline, ...rest 36 | } = this.props 37 | 38 | const PreCode = props => 39 | 40 | const transform = (node, index) => { 41 | // Transform any component renderers as react components 42 | if (node.type === 'tag' && renderers[node.name]) { 43 | const Component = renderers[node.name] 44 | const props = generatePropsFromAttributes(node.attribs, index) 45 | const children = processNodes(node.children, transform) 46 | return React.createElement(Component, props, children) 47 | } 48 | // Transform any code usin the Code component 49 | if (node.type === 'tag') { 50 | if ( 51 | node.name === 'pre' && 52 | (node.children && node.children.length === 1 && node.children[0].name === 'code') 53 | ) { 54 | const codeNode = node.children[0] 55 | const generatedProps = generatePropsFromAttributes(codeNode.attribs, index) 56 | let language = (generatedProps.className || '') 57 | .split(' ') 58 | .find(d => d.includes('language-')) 59 | language = language && language.substring('language-'.length) 60 | 61 | const props = { 62 | ...generatedProps, 63 | language, 64 | component: 'pre', 65 | source: 66 | codeNode.children && codeNode.children[0] && codeNode.children[0].data 67 | ? codeNode.children[0].data 68 | : null, 69 | } 70 | return React.createElement(PreCode, props) 71 | } else if (highlightInline && node.name === 'code' && node.parent.name !== 'pre') { 72 | const codeNode = node.children[0] 73 | const generatedProps = generatePropsFromAttributes(node.attribs, index) 74 | const props = { 75 | ...generatedProps, 76 | component: 'code', 77 | showLineNumbers: false, 78 | className: 'code-inline', 79 | language: 'clike', 80 | source: codeNode.data, 81 | } 82 | return React.createElement(PreCode, props) 83 | } 84 | } 85 | } 86 | 87 | return ( 88 |
89 | {ReactHtmlParser(this.markdownCompiler(source), { 90 | transform, 91 | })} 92 |
93 | ) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Markdown from './Markdown' 2 | import Code, { loadLanguages } from './Code' 3 | import makeCompiler from './makeCompiler' 4 | 5 | export { Markdown, Code, loadLanguages, makeCompiler } 6 | -------------------------------------------------------------------------------- /src/makeCompiler.js: -------------------------------------------------------------------------------- 1 | import showdown from 'showdown' 2 | 3 | showdown.setFlavor('github') 4 | 5 | export default (options = {}) => { 6 | const compiler = new showdown.Converter(options) 7 | return (markdown = '') => compiler.makeHtml(markdown) 8 | } 9 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/index-test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import React from 'react' 3 | import {render, unmountComponentAtNode} from 'react-dom' 4 | 5 | import Component from 'src/' 6 | 7 | describe('Component', () => { 8 | let node 9 | 10 | beforeEach(() => { 11 | node = document.createElement('div') 12 | }) 13 | 14 | afterEach(() => { 15 | unmountComponentAtNode(node) 16 | }) 17 | 18 | it('displays a welcome message', () => { 19 | render(, node, () => { 20 | expect(node.innerHTML).toContain('Welcome to React components') 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /themes/smackdown-dark.css: -------------------------------------------------------------------------------- 1 | /** 2 | * atom-dark theme for `prism.js` 3 | * Based on Atom's `atom-dark` theme: https://github.com/atom/atom-dark-syntax 4 | * @author Joe Gibson (@gibsjose) 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: #cecece; 10 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 11 | font-family: Inconsolata, Monaco, Consolas, "Courier New", Courier, monospace; 12 | direction: ltr; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | word-break: normal; 17 | line-height: 1.5; 18 | 19 | -moz-tab-size: 4; 20 | -o-tab-size: 4; 21 | tab-size: 4; 22 | 23 | -webkit-hyphens: none; 24 | -moz-hyphens: none; 25 | -ms-hyphens: none; 26 | hyphens: none; 27 | } 28 | 29 | /* Code blocks */ 30 | pre[class*="language-"] { 31 | padding: 1em; 32 | margin: 0.5em 0; 33 | overflow: auto; 34 | border-radius: 0.3em; 35 | } 36 | 37 | :not(pre) > code[class*="language-"], 38 | pre[class*="language-"] { 39 | background: #22252c; 40 | } 41 | 42 | /* Inline code */ 43 | :not(pre) > code[class*="language-"] { 44 | padding: 0.1em; 45 | border-radius: 0.3em; 46 | } 47 | 48 | .token.comment, 49 | .token.prolog, 50 | .token.doctype, 51 | .token.cdata { 52 | color: #8a8a8a; 53 | } 54 | 55 | .token.punctuation { 56 | color: #c8c8c5; 57 | } 58 | 59 | .namespace { 60 | opacity: 0.7; 61 | } 62 | 63 | .token.keyword, 64 | .token.property, 65 | .token.atrule, 66 | .token.variable, 67 | .token.attr-value, 68 | .token.symbol, 69 | .token.deleted { 70 | color: #ff6673; 71 | } 72 | 73 | .token.class, 74 | .token.attr, 75 | .token.attr-name, 76 | .token.number, 77 | .token.important { 78 | color: #ff8c49; 79 | } 80 | 81 | .token.selector, 82 | .token.string, 83 | .token.char, 84 | .token.builtin, 85 | .token.boolean, 86 | .token.constant, 87 | .token.inserted { 88 | color: #70d175; 89 | } 90 | 91 | .token.operator { 92 | color: #42c5c5; 93 | } 94 | 95 | .token.url, 96 | .token.function { 97 | color: #41b3ff; 98 | } 99 | 100 | .token.tag .token.tag, 101 | .token.regex, 102 | .token.entity { 103 | color: #ffd446; 104 | } 105 | 106 | .token.important, 107 | .token.bold { 108 | font-weight: bold; 109 | } 110 | .token.italic { 111 | font-style: italic; 112 | } 113 | 114 | .token.entity { 115 | cursor: help; 116 | } 117 | -------------------------------------------------------------------------------- /themes/smackdown-light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * atom-dark theme for `prism.js` 3 | * Based on Atom's `atom-dark` theme: https://github.com/atom/atom-dark-syntax 4 | * @author Joe Gibson (@gibsjose) 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: #444444; 10 | font-family: Inconsolata, Monaco, Consolas, "Courier New", Courier, monospace; 11 | direction: ltr; 12 | text-align: left; 13 | white-space: pre; 14 | word-spacing: normal; 15 | word-break: normal; 16 | line-height: 1.5; 17 | 18 | -moz-tab-size: 4; 19 | -o-tab-size: 4; 20 | tab-size: 4; 21 | 22 | -webkit-hyphens: none; 23 | -moz-hyphens: none; 24 | -ms-hyphens: none; 25 | hyphens: none; 26 | } 27 | 28 | code[class*="language-javascript"], 29 | pre[class*="language-javascript"] { 30 | color: #525252; 31 | } 32 | 33 | /* Code blocks */ 34 | pre[class*="language-"] { 35 | padding: 1em; 36 | margin: 0.5em 0; 37 | overflow: auto; 38 | border-radius: 0.3em; 39 | } 40 | 41 | :not(pre) > code[class*="language-"], 42 | pre[class*="language-"] { 43 | background: rgba(0, 0, 0, 0.02); 44 | border: 1px solid rgba(0, 0, 0, 0.04); 45 | } 46 | 47 | /* Inline code */ 48 | :not(pre) > code[class*="language-"] { 49 | padding: 0.1em; 50 | border-radius: 0.3em; 51 | } 52 | 53 | .token.comment, 54 | .token.prolog, 55 | .token.doctype, 56 | .token.cdata { 57 | color: #747474; 58 | } 59 | 60 | .token.punctuation { 61 | color: #505050; 62 | } 63 | 64 | .namespace { 65 | opacity: 0.7; 66 | } 67 | 68 | .token.keyword, 69 | .token.property, 70 | .token.atrule, 71 | .token.variable, 72 | .token.attr, 73 | .token.attr-value, 74 | .token.symbol, 75 | .token.deleted { 76 | color: #006fb9; 77 | } 78 | 79 | .token.class, 80 | .token.attr-name, 81 | .token.number, 82 | .token.important { 83 | color: #da8300; 84 | } 85 | 86 | .token.selector, 87 | .token.string, 88 | .token.char, 89 | .token.builtin, 90 | .token.boolean, 91 | .token.constant, 92 | .token.inserted { 93 | color: #008a39; 94 | } 95 | 96 | .token.operator { 97 | color: #009696; 98 | } 99 | 100 | .token.url, 101 | .token.function { 102 | color: #d80085; 103 | } 104 | 105 | .token.tag .token.tag, 106 | .token.regex, 107 | .token.entity { 108 | color: #e70f00; 109 | } 110 | 111 | .token.important, 112 | .token.bold { 113 | font-weight: bold; 114 | } 115 | .token.italic { 116 | font-style: italic; 117 | } 118 | 119 | .token.entity { 120 | cursor: help; 121 | } 122 | -------------------------------------------------------------------------------- /www/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-static/.babelrc", 3 | "plugins": ["babel-plugin-styled-components"] 4 | } 5 | -------------------------------------------------------------------------------- /www/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'react-tools' 3 | } 4 | -------------------------------------------------------------------------------- /www/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # production 8 | /dist 9 | /tmp 10 | 11 | # misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* -------------------------------------------------------------------------------- /www/README.md: -------------------------------------------------------------------------------- 1 | # React-Static - Documentation Example 2 | 3 | This example includes: 4 | 5 | * Styled-Components 6 | * Basic Documentation Setup w/ styles 7 | 8 | To get started: 9 | 10 | 1. Run `react-static create` and use the `documentation` template. 11 | 2. Follow installation instructions 12 | 3. Edit `TODO` commented items `static.config.js 13 | -------------------------------------------------------------------------------- /www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-static-starter", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "react-static start", 8 | "stage": "react-static build --staging", 9 | "build": "react-static build", 10 | "serve": "serve dist -p 3000" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.16.2", 14 | "nprogress": "^0.2.0", 15 | "react": "^16.0.0", 16 | "react-click-outside": "^3.0.1", 17 | "react-dom": "^16.0.0", 18 | "react-router": "^4.2.0", 19 | "react-smackdown": "^0.8.8", 20 | "react-static": "^5.9.1", 21 | "reprism": "^0.0.11", 22 | "styled-components": "^3" 23 | }, 24 | "devDependencies": { 25 | "babel-plugin-styled-components": "^1.3.0", 26 | "eslint-config-react-tools": "1.x.x", 27 | "serve": "^6.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /www/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /www/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Router, onLoading } from 'react-static' 3 | import styled, { injectGlobal } from 'styled-components' 4 | import { hot } from 'react-hot-loader' 5 | import nprogress from 'nprogress' 6 | // 7 | import Routes from 'react-static-routes' 8 | 9 | import jsx from 'reprism/languages/jsx' 10 | import bash from 'reprism/languages/bash' 11 | import ruby from 'reprism/languages/ruby' 12 | import elixir from 'reprism/languages/elixir' 13 | import go from 'reprism/languages/go' 14 | 15 | import 'nprogress/nprogress.css' 16 | import '../../themes/smackdown-light.css' 17 | 18 | import { loadLanguages } from '../../src/' 19 | 20 | loadLanguages(jsx, bash, ruby, elixir, go) 21 | 22 | injectGlobal` 23 | body { 24 | font-family: 'Roboto', sans-serif; 25 | font-weight: normal; 26 | font-size: 16px; 27 | margin: 0; 28 | padding: 0; 29 | line-height: 1.5; 30 | } 31 | * { 32 | box-sizing: border-box; 33 | -webkit-overflow-scrolling: touch; 34 | } 35 | #root { 36 | min-height: 100vh; 37 | } 38 | 39 | a { 40 | text-decoration: none; 41 | color: #108db8; 42 | } 43 | 44 | img { 45 | max-width: 100%; 46 | } 47 | 48 | pre, code { 49 | user-select: text; 50 | } 51 | 52 | pre { 53 | font-size: 13px; 54 | border-radius: 5px; 55 | } 56 | 57 | code.code-inline { 58 | padding: 2px 5px; 59 | border-radius: 5px; 60 | font-size: .8em; 61 | line-height: 1.5; 62 | } 63 | } 64 | 65 | .react-syntax-highlighter-line-number { 66 | pointer-events: none; 67 | } 68 | ` 69 | 70 | const AppStyles = styled.div` 71 | min-height: 100vh; 72 | ` 73 | 74 | class App extends React.Component { 75 | componentDidMount () { 76 | onLoading(loading => { 77 | if (loading) { 78 | nprogress.start() 79 | } else { 80 | nprogress.done() 81 | } 82 | }) 83 | } 84 | render () { 85 | return ( 86 | 87 | 88 | 89 | 90 | 91 | ) 92 | } 93 | } 94 | 95 | export default hot(module)(App) 96 | -------------------------------------------------------------------------------- /www/src/components/ClickOutside.js: -------------------------------------------------------------------------------- 1 | import { PureComponent } from 'react' 2 | import reactClickOutside from 'react-click-outside' 3 | // 4 | 5 | class ClickOutside extends PureComponent { 6 | handleClickOutside () { 7 | if (this.props.onClickOutside) { 8 | this.props.onClickOutside() 9 | } 10 | } 11 | render () { 12 | return this.props.children 13 | } 14 | } 15 | 16 | export default reactClickOutside(ClickOutside) 17 | -------------------------------------------------------------------------------- /www/src/components/Markdown.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-static' 3 | import { Markdown } from '../../../src' 4 | 5 | const customRenderers = { 6 | a: ({ href = '', ...rest }) => { 7 | const to = href.startsWith('/') ? href.replace('.md', '') : href 8 | return 9 | }, 10 | } 11 | 12 | export default ({ renderers, ...rest }) => ( 13 | 20 | ) 21 | -------------------------------------------------------------------------------- /www/src/components/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { css } from 'styled-components' 3 | import { SiteData, Link } from 'react-static' 4 | // 5 | import ClickOutside from 'components/ClickOutside' 6 | 7 | const breakpoint = 800 8 | const sidebarBackground = '#f7f7f7' 9 | 10 | const SidebarStyles = styled.div` 11 | position: relative; 12 | width: 100%; 13 | max-width: 100%; 14 | padding-left: 300px; 15 | margin: 0 auto; 16 | transition: all 0.2s ease-out; 17 | 18 | @media screen and (max-width: ${breakpoint}px) { 19 | padding-left: 0px; 20 | } 21 | 22 | .sidebar { 23 | z-index: 1; 24 | position: fixed; 25 | display: flex; 26 | flex-direction: column; 27 | top: 0; 28 | left: 0; 29 | height: 100%; 30 | width: 300px; 31 | max-width: calc(100% - 45px); 32 | border-right: 1px solid rgba(0, 0, 0, 0.1); 33 | -webkit-overflow-scrolling: touch; 34 | background: ${sidebarBackground}; 35 | transition: all 0.2s ease-out; 36 | 37 | @media screen and (max-width: ${breakpoint}px) { 38 | transform: translateX(-100%); 39 | ${props => 40 | props.isOpen && 41 | css` 42 | box-shadow: 0 0 40px rgba(0, 0, 0, 0.3); 43 | transform: translateX(0%); 44 | `}; 45 | } 46 | 47 | .toggle { 48 | appearance: none; 49 | position: absolute; 50 | top: 5px; 51 | right: -6px; 52 | transform: translateX(100%); 53 | display: flex; 54 | align-items: center; 55 | justify-content: center; 56 | width: 32px; 57 | height: 32px; 58 | background: ${sidebarBackground}; 59 | border: 1px solid rgba(0, 0, 0, 0.1); 60 | color: #555; 61 | font-size: 1.2rem; 62 | border-radius: 3px; 63 | cursor: pointer; 64 | opacity: 0; 65 | pointer-events: none; 66 | transition: all 0.2s ease-out; 67 | transform-origin: center; 68 | outline: none; 69 | 70 | @media screen and (max-width: ${breakpoint}px) { 71 | opacity: 1; 72 | pointer-events: all; 73 | } 74 | 75 | ${props => 76 | !props.isOpen && 77 | css` 78 | transform: translateX(100%) rotate(180deg); 79 | `}; 80 | } 81 | 82 | .header { 83 | flex: 0 0 auto; 84 | display: flex; 85 | align-items: center; 86 | justify-content: space-between; 87 | padding: 0.5rem 0.7rem; 88 | border-bottom: 3px solid rgba(0, 0, 0, 0.1); 89 | 90 | .link { 91 | font-weight: bold; 92 | } 93 | 94 | .version { 95 | font-size: 0.9rem; 96 | font-weight: bold; 97 | opacity: 0.3; 98 | } 99 | } 100 | 101 | .scroll { 102 | flex: 1 1 auto; 103 | overflow-y: auto; 104 | padding-bottom: 5rem; 105 | } 106 | 107 | .list { 108 | margin: 0; 109 | padding: 0; 110 | list-style-type: none; 111 | .list { 112 | padding-left: 1rem; 113 | } 114 | } 115 | 116 | .item { 117 | border-bottom: 1px solid rgba(0, 0, 0, 0.05); 118 | 119 | .title, 120 | a { 121 | display: block; 122 | padding: 0.5rem 0.7rem; 123 | } 124 | 125 | a { 126 | color: rgba(0, 0, 0, 0.8); 127 | 128 | &.active { 129 | font-weight: bold; 130 | } 131 | } 132 | 133 | .title { 134 | font-size: 0.8rem; 135 | text-transform: uppercase; 136 | font-weight: bold; 137 | color: rgba(0, 0, 0, 0.5); 138 | } 139 | } 140 | } 141 | 142 | .content { 143 | position: relative; 144 | z-index: 0; 145 | padding: 1rem 2.5rem; 146 | } 147 | ` 148 | 149 | const Menu = ({ items }) => ( 150 |
151 | {items.map(({ 152 | title, fullPath, component, children, 153 | }) => ( 154 |
155 | {component ? ( 156 | 157 | {title} 158 | 159 | ) : ( 160 |
{title}
161 | )} 162 | {children ? : null} 163 |
164 | ))} 165 |
166 | ) 167 | 168 | class Sidebar extends React.Component { 169 | state = { 170 | isOpen: false, 171 | } 172 | toggle = isOpen => 173 | this.setState({ 174 | isOpen, 175 | }) 176 | render () { 177 | const { children } = this.props 178 | const { isOpen } = this.state 179 | return ( 180 | ( 182 | 183 | { 185 | if (isOpen) { 186 | this.setState({ 187 | isOpen: false, 188 | }) 189 | } 190 | }} 191 | > 192 |
193 | 201 |
202 | 203 | {repoURL ? {repoName} : repoName} 204 | 205 |
v{process.env.REPO_VERSION}
206 |
207 |
208 | 209 |
210 |
211 |
212 |
{children}
213 |
214 | )} 215 | /> 216 | ) 217 | } 218 | } 219 | 220 | export default Sidebar 221 | -------------------------------------------------------------------------------- /www/src/containers/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | // 4 | 5 | import Sidebar from 'components/Sidebar' 6 | 7 | export default () => ( 8 | 9 |

10 | 404 - Oh no's! We couldn't find that page{' '} 11 | 12 | 😩 13 | 14 |

15 |
16 | ) 17 | -------------------------------------------------------------------------------- /www/src/containers/Code.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SiteData, Head } from 'react-static' 3 | import styled from 'styled-components' 4 | 5 | // 6 | 7 | import Sidebar from 'components/Sidebar' 8 | import { Code } from '../../../src/' 9 | 10 | const TextArea = styled.textarea` 11 | width: 100%; 12 | height: 400px; 13 | border: 2px solid rgba(0, 0, 0, 0.1); 14 | border-radius: 5px; 15 | padding: 0.5rem; 16 | font-size: 13px; 17 | font-family: monospace; 18 | ` 19 | 20 | const defaultSource = `const MyComponent = () => ( 21 |
22 | Paste some code! 23 |
24 | ) 25 | 26 | export default MyComponent` 27 | 28 | export default class CodeExample extends React.Component { 29 | state = { 30 | source: defaultSource, 31 | } 32 | render () { 33 | const { source } = this.state 34 | return ( 35 | ( 37 | 38 | 39 | {`Code Example | ${repoName}`} 40 | 41 |