├── .babelrc ├── .gitignore ├── .travis.yml ├── README.md ├── firebase.json ├── package.json ├── src ├── components │ ├── app.js │ ├── doc-member.js │ ├── doc-symbol.js │ ├── header.js │ ├── main.js │ └── sidebar.js ├── index.html ├── index.js ├── lib │ └── preact-mdl.js ├── manifest.json └── style │ └── index.less └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "loose": "all", 3 | "stage": 0, 4 | "sourceMaps": true, 5 | "nonStandard": true, 6 | "jsxPragma": "h" 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /node_modules 3 | /npm-debug.log 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4.0' 4 | sudo: false 5 | script: npm run build 6 | after_success: 7 | - if [[ "${TRAVIS_PULL_REQUEST}" == "false" ]]; then npm run deploy -s; fi 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Documentation Viewer 2 | 3 | A simple standalone viewer for `json` output from [documentation.js]. 4 | 5 | Built with [preact], [preact-mdl] and [fetch]. 6 | 7 | ## Live 8 | 9 | [documentation-viewer.firebaseapp.com](http://documentation-viewer.firebaseapp.com) 10 | 11 | 12 | ## Screenshots 13 | 14 | home 15 | 16 | docs 17 | 18 | 19 | [preact]: https://github.com/developit/preact 20 | [preact-mdl]: https://github.com/developit/preact-mdl 21 | [fetch]: https://github.com/github/fetch 22 | [documentation.js]: http://documentation.js.org 23 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firebase": "documentation-viewer", 3 | "public": "build", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ], 15 | "headers": [ 16 | { 17 | "source" : "**", 18 | "headers" : [ 19 | { 20 | "key" : "Access-Control-Allow-Origin", 21 | "value" : "*" 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentation-viewer", 3 | "version": "0.2.0", 4 | "description": "View documentation generated by documentation.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "WEBPACK_ENV=dev webpack-dev-server", 8 | "start": "firebase serve -p ${PORT:-8080}", 9 | "prestart": "npm run build", 10 | "build": "mkdir -p build && WEBPACK_ENV=production webpack && cpy 'src/{index.html,manifest.json}' build", 11 | "deploy": "firebase deploy --non-interactive --token \"${FIREBASE_TOKEN}\"" 12 | }, 13 | "keywords": [ 14 | "preact", 15 | "documentation" 16 | ], 17 | "author": "Jason Miller ", 18 | "license": "ISC", 19 | "devDependencies": { 20 | "autoprefixer-loader": "^3.1.0", 21 | "babel-loader": "^5.3.3", 22 | "cpy": "^3.4.1", 23 | "css-loader": "^0.21.0", 24 | "exports-loader": "^0.6.2", 25 | "extract-text-webpack-plugin": "^0.8.2", 26 | "firebase-tools": "^2.0.2", 27 | "http-server": "^0.8.5", 28 | "less-loader": "^2.2.0", 29 | "normalize.css": "^3.0.3", 30 | "source-map-loader": "^0.1.5", 31 | "style-loader": "^0.13.0", 32 | "webpack": "^1.12.1", 33 | "webpack-dev-server": "^1.10.1" 34 | }, 35 | "dependencies": { 36 | "babel-runtime": "^5.8.20", 37 | "decko": "^1.1.0", 38 | "material-design-lite": "^1.0.5", 39 | "object-assign": "^4.0.1", 40 | "preact": "^1.5.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/app.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { bind } from 'decko'; 3 | import assign from 'object-assign'; 4 | import { Layout } from 'preact-mdl'; 5 | import Header from './header'; 6 | import Sidebar from './sidebar'; 7 | import Main from './main'; 8 | 9 | const DEMO = 'https://github.com/documentationjs/documentation/blob/master/test/fixture/class.output.json'; 10 | 11 | export default class App extends Component { 12 | componentDidMount() { 13 | let url = (location.hash || '').substring(1); 14 | this.load(url || DEMO); 15 | } 16 | 17 | @bind 18 | load(url) { 19 | let resolvedUrl = url; 20 | 21 | // convert github URLs: 22 | let gh = resolvedUrl.match(/^https?:\/\/github\.com\/([^/]+\/[^/]+)\/blob\/(.+)$/i); 23 | if (gh) resolvedUrl = `https://raw.githubusercontent.com/${gh[1]}/${gh[2]}`; 24 | 25 | // convert gist URLs: 26 | let gist = resolvedUrl.match(/^https?:\/\/gist\.github\.com\/(.+)$/i); 27 | if (gist) resolvedUrl = `https://gist.githubusercontent.com/${gist[1]}/raw`; 28 | 29 | fetch(resolvedUrl) 30 | .catch( error => this.setState({ error }) ) 31 | .then( r => r.json() ) 32 | .then( docs => { 33 | assign(docs, { url, resolvedUrl }); 34 | console.log(docs); 35 | this.setState({ docs, error:null }); 36 | }); 37 | } 38 | 39 | @bind 40 | go(to) { 41 | if (to==='/home') return this.setState({ error:null, symbol:null }); 42 | 43 | let { docs } = this.state, 44 | symbol = docs.filter( s => s.name.match(to) )[0], 45 | error = symbol ? null : `Symbol "${to}" not found`; 46 | this.setState({ error, symbol }); 47 | } 48 | 49 | render({}, { error, docs, symbol }) { 50 | let actions = { go:this.go, load:this.load }, 51 | ctx = { error, docs, symbol, actions }; 52 | return ( 53 |
54 | 55 |
56 | 57 |
58 | 59 |
60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/components/doc-member.js: -------------------------------------------------------------------------------- 1 | import assign from 'object-assign'; 2 | import { h } from 'preact'; 3 | 4 | const NONE = {}; 5 | 6 | function getType(type=NONE, nameOnly=false) { 7 | let name = type.expression && type.expression.name || type.name; 8 | if (nameOnly) return name; 9 | if (type.type==='TypeApplication') { 10 | return `${name}<${type.applications.map(a=>a.name).join(', ')}>`; 11 | } 12 | return name; 13 | } 14 | 15 | let normalizeParams = params => params.reduce( (acc, p) => { 16 | let v = acc.map[p.name]; 17 | if (!v) acc.params.push( v=acc.map[p.name]={} ); 18 | assign(v, p); 19 | return acc; 20 | }, { map:{}, params:[] }).params; 21 | 22 | 23 | export default ({ member:{kind, name, description, params=[], returns} }) => { 24 | let doc = [], 25 | isFunction = kind==='function'; 26 | 27 | if (params.length) { 28 | isFunction = true; 29 | doc.push( 30 |
31 |

Parameters

32 | { normalizeParams(params).map( p => ( 33 | 34 | )) } 35 |
36 | ); 37 | } 38 | 39 | if (Array.isArray(returns)) returns = returns[0]; 40 | if (returns) doc.push( 41 |
42 |

Returns

43 | 44 |
45 | ); 46 | 47 | return ( 48 |
49 |

{ name }{ isFunction ? ({ params.map(p=>p.name).join(', ') }) : null }

50 |

{ description }

51 | { doc } 52 |
53 | ); 54 | }; 55 | 56 | 57 | 58 | const DocParam = ({ type='argument', param }) => { 59 | let properties = null; 60 | if (param.properties) { 61 | properties = ( 62 |
63 |
Properties:
64 |
{ param.properties.map( ({name,...p}) => ( 65 | 66 | )) }
67 |
68 | ); 69 | } 70 | 71 | let { name } = param; 72 | if (param.type && param.type.type==='OptionalType') { 73 | name = `[${name}${param.default?('='+param.default):''}]`; 74 | } 75 | 76 | return ( 77 |
78 |
79 | { getType(param.type) } { name } 80 |
81 |
{ param.description }{ properties }
82 |
83 | ); 84 | }; 85 | -------------------------------------------------------------------------------- /src/components/doc-symbol.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Card } from 'preact-mdl'; 3 | import DocMember from './doc-member'; 4 | 5 | export default ({ docs, symbol, ...props }) => { 6 | let type = symbol.kind || symbol.type; 7 | return ( 8 |
9 | 10 | 11 | { type } 12 | { symbol.name } 13 | 14 | 15 | 16 |
17 | 18 |
19 |
20 | 21 |
{ Object.keys(symbol.members || {}).map( type => ( 22 | symbol.members[type].length ? ( 23 |
24 |

{ type }

25 |
{ symbol.members[type].map( member => ( 26 | 27 | )) }
28 |
29 | ) : null 30 | )) }
31 |
32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/header.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Layout, TextField } from 'preact-mdl'; 3 | 4 | export default ({ docs, actions:{ go, load } }) => ( 5 | 6 | 7 | 8 | go('/home') }>Docs 9 | 10 | 11 | e.keyCode===13 ? load(e.target.value) : null } 16 | value={ docs && docs.url } /> 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /src/components/main.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Layout, Card, Icon } from 'preact-mdl'; 3 | import DocSymbol from './doc-symbol'; 4 | 5 | export default ({ error, docs, symbol }) => { 6 | let content = []; 7 | 8 | if (error) { 9 | content.push( 10 | 11 | 12 | Error 13 | 14 | 15 |

{ String(error) }

16 |
{ String(error.stack) }
17 |
18 |
19 | ); 20 | } 21 | else if (symbol) { 22 | content.push( 23 | 24 | ); 25 | } 26 | else { 27 | content.push( 28 | 29 | 30 | Documentation 31 | 32 | 33 | 34 | 35 |

36 | Paste your JSON documentation URL into the box above to get started. 37 |

38 | 39 | 53 |
54 | 55 | 56 | Made with by developit. 57 | Built with Preact. 58 | 59 |
60 | ); 61 | } 62 | 63 | return ( 64 | 65 | { content } 66 | 67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /src/components/sidebar.js: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { Layout, Navigation } from 'preact-mdl'; 3 | 4 | export default ({ docs=[], actions }) => ( 5 | 6 | Symbols 7 | { docs.map( ({ name, kind }) => ( 8 | actions.go(name) }> 11 | { name } 12 |
{ kind }
13 |
14 | ))}
15 |
16 | ); 17 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Documentation Viewer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './style/index.less'; 2 | import { h, render } from 'preact'; 3 | import App from './components/app'; 4 | 5 | render(, document.body); 6 | -------------------------------------------------------------------------------- /src/lib/preact-mdl.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import mdl from 'exports?componentHandler!material-design-lite/material.js'; 3 | 4 | // const mdl = window.componentHandler; 5 | 6 | const RIPPLE_CLASS = 'js-ripple-effect'; 7 | const MDL_PREFIX = s => `mdl-${s}`; 8 | 9 | let uidCounter = 1; 10 | let uid = () => ++uidCounter; 11 | 12 | let extend = (base, props) => { 13 | for (let i in props) if (props.hasOwnProperty(i)) base[i] = props[i]; 14 | return base; 15 | }; 16 | 17 | let propMaps = { 18 | disabled({ attributes }) { 19 | if (attributes.hasOwnProperty('disabled') && !attributes.disabled) { 20 | attributes.disabled = null; 21 | } 22 | }, 23 | badge({ attributes }) { 24 | attributes['data-badge'] = attributes.badge; 25 | delete attributes.badge; 26 | attributes.class += (attributes.class ? ' ' : '') + 'mdl-badge'; 27 | }, 28 | active({ attributes }) { 29 | if (attributes.active) { 30 | attributes.class += (attributes.class ? ' ' : '') + 'is-active'; 31 | } 32 | }, 33 | shadow({ attributes }) { 34 | let d = parseFloat(attributes.shadow)|0, 35 | c = attributes.class.replace(/\smdl-[^ ]+--shadow\b/g,''); 36 | attributes.class = c + (c ? ' ' : '') + `mdl-shadow--${d}dp`; 37 | } 38 | }; 39 | 40 | export class MaterialComponent extends Component { 41 | component = 'none'; 42 | js = false; 43 | ripple = false; 44 | mdlClasses = null; 45 | upgradedBase = null; 46 | 47 | mdlRender(props) { 48 | return
{ props.children }
; 49 | } 50 | 51 | render(props, state) { 52 | let r = this.mdlRender(props, state); 53 | if (this.nodeName) r.nodeName = this.nodeName; 54 | if (!r.attributes) r.attributes = {}; 55 | r.attributes.class = this.createMdlClasses(props).concat(r.attributes.class || []).join(' '); 56 | for (let i in propMaps) if (propMaps.hasOwnProperty(i)) { 57 | if (props.hasOwnProperty(i)) propMaps[i](r); 58 | } 59 | if (this.base && this.upgradedBase) { 60 | this.preserveMdlDom(this.base, r); 61 | } 62 | return r; 63 | } 64 | 65 | // Copy some transient properties back out of the DOM into VDOM prior to diffing so they don't get overwritten 66 | preserveMdlDom(base, r) { 67 | if (!base || !base.hasAttribute || !r) return; 68 | 69 | let c = base.childNodes, 70 | persist = [ 71 | 'mdl-js-ripple-effect--ignore-events', 72 | 'mdl-js-ripple-effect', 73 | 'is-upgraded', 74 | 'is-dirty' 75 | ], 76 | v = base.getAttribute('data-upgraded'), 77 | a = r.attributes, 78 | foundRipple = false; 79 | 80 | if (!a) a = {}; 81 | 82 | if (v) { 83 | a['data-upgraded'] = v; 84 | upgradeQueue.add(base); 85 | } 86 | 87 | if (base.hasAttribute('ink-enabled')) { 88 | if (!r.attributes) r.attributes = {}; 89 | r.attributes['ink-enabled'] = 'true'; 90 | } 91 | 92 | for (let i=0; i 110 | 111 | 112 | )); 113 | foundRipple = true; 114 | } 115 | else if (r && r.children && r.children[i] && typeof r.children[i].nodeName==='string') { 116 | this.preserveMdlDom(c[i], r.children[i]); 117 | } 118 | } 119 | } 120 | 121 | createMdlClasses(props) { 122 | let name = this.component, 123 | c = [name], 124 | js = props.js!==false && (this.js || this.ripple); 125 | if (this.mdlClasses) c.push(...this.mdlClasses); 126 | if (this.ripple && props.ripple!==false) { 127 | c.push(RIPPLE_CLASS); 128 | } 129 | if (js) c.push(`js-${name}`); 130 | for (let i in props) { 131 | if (props.hasOwnProperty(i) && props[i]===true) { 132 | c.push(`${name}--${i}`); 133 | } 134 | } 135 | return c.map(MDL_PREFIX); 136 | } 137 | 138 | componentDidMount() { 139 | if (this.base!==this.upgradedBase) { 140 | if (this.upgradedBase) { 141 | mdl.downgradeElements(this.upgradedBase); 142 | } 143 | this.upgradedBase = null; 144 | if (this.base && this.base.parentElement) { 145 | this.upgradedBase = this.base; 146 | mdl.upgradeElement(this.base); 147 | } 148 | } 149 | } 150 | 151 | componentWillUnmount() { 152 | if (this.upgradedBase) { 153 | mdl.downgradeElements(this.upgradedBase); 154 | this.upgradedBase = null; 155 | } 156 | } 157 | } 158 | 159 | 160 | let upgradeQueue = { 161 | items: [], 162 | add(base) { 163 | if (upgradeQueue.items.push(base)===1) { 164 | requestAnimationFrame(upgradeQueue.process); 165 | } 166 | }, 167 | process() { 168 | // console.log(`upgrading ${upgradeQueue.items.length} items`); 169 | let p = upgradeQueue.items; 170 | for (let i=p.length; i--; ) { 171 | let el = p[i], 172 | v = el.getAttribute('data-upgraded'), 173 | u = v && v.split(','); 174 | if (!u) continue; 175 | for (let j=u.length; j--; ) { 176 | let c = u[j], 177 | a = c && el[c]; 178 | if (a) { 179 | if (a.updateClasses_) { 180 | a.updateClasses_(); 181 | } 182 | if (a.onFocus_ && a.input_ && a.input_.matches && a.input_.matches(':focus')) { 183 | a.onFocus_(); 184 | } 185 | // if (a.tabs_) { 186 | // // console.log(a); 187 | // a.tabs_ = el.querySelectorAll('.mdl-tabs__tab-bar > .mdl-tabs__tab'); 188 | // console.log(a); 189 | // a.panels_ = [].map.call(a.tabs_, e => { 190 | // console.log(e.querySelector('.mdl-tabs__ripple-container')); 191 | // if (!e.querySelector('.mdl-tabs__ripple-container')) { 192 | // let s = document.createElement('span'); 193 | // s.className = 'mdl-tabs__ripple-container mdl-js-ripple-effect'; 194 | // s.innerHTML = ''; 195 | // e.appendChild(s); 196 | // } 197 | // 198 | // let tab = e.getAttribute('tab'); 199 | // if (tab) return el.querySelector(`.mdl-tabs__panel[tab="${tab}"]`); 200 | // return el.querySelector(`.mdl-tabs__panel[id="${e.href.replace(/^#/,'')}"]`); 201 | // }); 202 | // console.log(a); 203 | // } 204 | } 205 | // if (a.init) a.init(); 206 | } 207 | } 208 | p.length = 0; 209 | } 210 | }; 211 | 212 | 213 | 214 | /** Material Icon */ 215 | export class Icon extends Component { 216 | render(props) { 217 | let c = props.class || ''; 218 | if (typeof c==='string') { 219 | c = 'material-icons ' + c; 220 | } 221 | else { 222 | c['material-icons'] = true; 223 | } 224 | return { props.icon || props.children }; 225 | } 226 | } 227 | 228 | 229 | 230 | 231 | /** @prop primary = false 232 | * @prop accent = false 233 | * @prop colored = false 234 | * @prop raised = false 235 | * @prop icon = false 236 | * @prop fab = false 237 | * @prop mini-fab = false 238 | * @prop disabled = false 239 | */ 240 | export class Button extends MaterialComponent { 241 | component = 'button'; 242 | nodeName = 'button'; 243 | js = true; 244 | ripple = true; 245 | } 246 | 247 | 248 | 249 | 250 | 251 | 252 | /** Cards */ 253 | 254 | export class Card extends MaterialComponent { 255 | component = 'card'; 256 | } 257 | 258 | export class CardTitle extends MaterialComponent { 259 | component = 'card__title'; 260 | } 261 | 262 | export class CardTitleText extends MaterialComponent { 263 | component = 'card__title-text'; 264 | nodeName = 'h2'; 265 | } 266 | 267 | export class CardMedia extends MaterialComponent { 268 | component = 'card__media'; 269 | } 270 | 271 | export class CardText extends MaterialComponent { 272 | component = 'card__supporting-text'; 273 | } 274 | 275 | export class CardActions extends MaterialComponent { 276 | component = 'card__actions'; 277 | // mdlClasses = ['card--border']; 278 | } 279 | 280 | export class CardMenu extends MaterialComponent { 281 | component = 'card__menu'; 282 | } 283 | 284 | extend(Card, { 285 | Title: CardTitle, 286 | TitleText: CardTitleText, 287 | Media: CardMedia, 288 | Text: CardText, 289 | Actions: CardActions, 290 | Menu: CardMenu 291 | }); 292 | 293 | 294 | 295 | 296 | /** Layouts */ 297 | 298 | /** @prop fixed-header = false 299 | * @prop fixed-drawer = false 300 | * @prop overlay-drawer-button = false 301 | * @prop fixed-tabs = false 302 | */ 303 | export class Layout extends MaterialComponent { 304 | component = 'layout'; 305 | js = true; 306 | } 307 | 308 | /** @prop waterfall = false 309 | * @prop scroll = false 310 | */ 311 | export class LayoutHeader extends MaterialComponent { 312 | component = 'layout__header'; 313 | nodeName = 'header'; 314 | } 315 | 316 | export class LayoutHeaderRow extends MaterialComponent { 317 | component = 'layout__header-row'; 318 | } 319 | 320 | export class LayoutTitle extends MaterialComponent { 321 | component = 'layout-title'; 322 | nodeName = 'span'; 323 | } 324 | 325 | export class LayoutSpacer extends MaterialComponent { 326 | component = 'layout-spacer'; 327 | } 328 | 329 | export class LayoutDrawer extends MaterialComponent { 330 | component = 'layout__drawer'; 331 | } 332 | 333 | export class LayoutContent extends MaterialComponent { 334 | component = 'layout__content'; 335 | nodeName = 'main'; 336 | } 337 | 338 | export class LayoutTabBar extends MaterialComponent { 339 | component = 'layout__tab-bar'; 340 | js = true; 341 | ripple = false; 342 | } 343 | 344 | /** @prop active */ 345 | export class LayoutTab extends MaterialComponent { 346 | component = 'layout__tab'; 347 | nodeName = 'a'; 348 | } 349 | 350 | /** @prop active */ 351 | export class LayoutTabPanel extends MaterialComponent { 352 | component = 'layout__tab-panel'; 353 | 354 | mdlRender(props) { 355 | return
{ props.children }
; 356 | } 357 | } 358 | 359 | extend(Layout, { 360 | Header: LayoutHeader, 361 | HeaderRow: LayoutHeaderRow, 362 | Title: LayoutTitle, 363 | Spacer: LayoutSpacer, 364 | Drawer: LayoutDrawer, 365 | Content: LayoutContent, 366 | TabBar: LayoutTabBar, 367 | Tab: LayoutTab, 368 | TabPanel: LayoutTabPanel 369 | }); 370 | 371 | 372 | 373 | /** @prop large-screen-only = false */ 374 | export class Navigation extends MaterialComponent { 375 | component = 'navigation'; 376 | nodeName = 'nav'; 377 | 378 | mdlRender(props, state) { 379 | let r = super.mdlRender(props, state); 380 | r.children.forEach( item => { 381 | let c = item.attributes.class || ''; 382 | if (!c.match(/\bmdl-navigation__link\b/g)) { 383 | item.attributes.class = c + ' mdl-navigation__link'; 384 | } 385 | }); 386 | return r; 387 | } 388 | } 389 | 390 | export class NavigationLink extends MaterialComponent { 391 | component = 'navigation__link'; 392 | nodeName = 'a'; 393 | 394 | constructor(...args) { 395 | super(...args); 396 | this.handleClick = this.handleClick.bind(this); 397 | } 398 | 399 | handleClick(e) { 400 | if (typeof this.props.onclick==='function' && this.props.onclick({ type: 'click', target: this })===false) { 401 | } 402 | else if (typeof this.props.route==='function') { 403 | this.props.route(this.props.href); 404 | } 405 | e.preventDefault(); 406 | return false; 407 | } 408 | 409 | mdlRender(props, state) { 410 | return { props.children }; 411 | } 412 | } 413 | 414 | Navigation.Link = NavigationLink; 415 | 416 | 417 | 418 | 419 | export class Tabs extends MaterialComponent { 420 | component = 'tabs'; 421 | js = true; 422 | ripple = false; 423 | } 424 | 425 | export class TabBar extends MaterialComponent { 426 | component = 'tabs__tab-bar'; 427 | } 428 | 429 | export class Tab extends MaterialComponent { 430 | component = 'tabs__tab'; 431 | nodeName = 'a'; 432 | } 433 | 434 | export class TabPanel extends MaterialComponent { 435 | component = 'tabs__panel'; 436 | nodeName = 'section'; 437 | } 438 | 439 | extend(Tabs, { 440 | TabBar, 441 | Bar: TabBar, 442 | Tab, 443 | TabPanel, 444 | Panel: TabPanel 445 | }); 446 | 447 | 448 | 449 | export class MegaFooter extends MaterialComponent { 450 | component = 'mega-footer'; 451 | nodeName = 'footer'; 452 | } 453 | 454 | export class MegaFooterMiddleSection extends MaterialComponent { 455 | component = 'mega-footer__middle-section'; 456 | } 457 | 458 | export class MegaFooterDropDownSection extends MaterialComponent { 459 | component = 'mega-footer__drop-down-section'; 460 | } 461 | 462 | export class MegaFooterHeading extends MaterialComponent { 463 | component = 'mega-footer__heading'; 464 | nodeName = 'h1'; 465 | } 466 | 467 | export class MegaFooterLinkList extends MaterialComponent { 468 | component = 'mega-footer__link-list'; 469 | nodeName = 'ul'; 470 | } 471 | 472 | export class MegaFooterBottomSection extends MaterialComponent { 473 | component = 'mega-footer__bottom-section'; 474 | } 475 | 476 | extend(MegaFooter, { 477 | MiddleSection: MegaFooterMiddleSection, 478 | DropDownSection: MegaFooterDropDownSection, 479 | Heading: MegaFooterHeading, 480 | LinkList: MegaFooterLinkList, 481 | BottomSection: MegaFooterBottomSection 482 | }); 483 | 484 | 485 | 486 | 487 | export class MiniFooter extends MaterialComponent { 488 | component = 'mini-footer'; 489 | nodeName = 'footer'; 490 | } 491 | 492 | export class MiniFooterLeftSection extends MaterialComponent { 493 | component = 'mini-footer__left-section'; 494 | } 495 | 496 | export class MiniFooterLinkList extends MaterialComponent { 497 | component = 'mini-footer__link-list'; 498 | nodeName = 'ul'; 499 | } 500 | 501 | extend(MiniFooter, { 502 | LeftSection: MiniFooterLeftSection, 503 | LinkList: MiniFooterLinkList 504 | }); 505 | 506 | 507 | 508 | 509 | /** Responsive Grid 510 | * @prop no-spacing = false 511 | */ 512 | export class Grid extends MaterialComponent { 513 | component = 'grid'; 514 | } 515 | 516 | export class Cell extends MaterialComponent { 517 | component = 'cell'; 518 | } 519 | 520 | Grid.Cell = Cell; 521 | 522 | 523 | 524 | 525 | 526 | /** @prop indeterminate = false */ 527 | export class Progress extends MaterialComponent { 528 | component = 'progress'; 529 | js = true; 530 | 531 | mdlRender(props) { 532 | return ( 533 |
534 |
535 |
536 |
537 |
538 | ); 539 | } 540 | 541 | componentDidUpdate() { 542 | let api = this.base.MaterialProgress, 543 | p = this.props; 544 | if (p.progress) api.setProgress(p.progress); 545 | if (p.buffer) api.setBuffer(p.buffer); 546 | } 547 | } 548 | 549 | 550 | 551 | 552 | 553 | /** @prop active = false 554 | * @prop single-color = false 555 | */ 556 | export class Spinner extends MaterialComponent { 557 | component = 'spinner'; 558 | js = true; 559 | } 560 | 561 | 562 | 563 | 564 | 565 | /** @prop bottom-left = true 566 | * @prop bottom-right = false 567 | * @prop top-left = false 568 | * @prop top-right = false 569 | */ 570 | export class Menu extends MaterialComponent { 571 | component = 'menu'; 572 | js = true; 573 | ripple = true; 574 | } 575 | 576 | /** @prop disabled = false */ 577 | export class MenuItem extends MaterialComponent { 578 | component = 'menu__item'; 579 | nodeName = 'li'; 580 | } 581 | 582 | Menu.Item = MenuItem; 583 | 584 | 585 | 586 | 587 | 588 | /** @prop min = 0 589 | * @prop max = 100 590 | * @prop value = 0 591 | * @prop tabindex = 0 592 | * @prop disabled = false 593 | */ 594 | export class Slider extends MaterialComponent { 595 | component = 'slider'; 596 | js = true; 597 | 598 | mdlRender(props) { 599 | return ; 600 | } 601 | } 602 | 603 | 604 | 605 | 606 | /** @prop checked = false 607 | * @prop disabled = false 608 | */ 609 | export class CheckBox extends MaterialComponent { 610 | component = 'checkbox'; 611 | js = true; 612 | ripple = true; 613 | 614 | getValue() { 615 | return this.base.children[0].checked; 616 | } 617 | 618 | mdlRender(props) { 619 | return ( 620 | 628 | ); 629 | } 630 | } 631 | 632 | 633 | 634 | 635 | /** @prop name (required) 636 | * @prop value (required) 637 | * @prop checked = false 638 | * @prop disabled = false 639 | */ 640 | export class Radio extends MaterialComponent { 641 | component = 'radio'; 642 | js = true; 643 | ripple = true; 644 | 645 | getValue() { 646 | return this.base.children[0].checked; 647 | } 648 | 649 | mdlRender(props) { 650 | return ( 651 | 655 | ); 656 | } 657 | } 658 | 659 | 660 | 661 | 662 | /** @prop checked = false 663 | * @prop disabled = false 664 | */ 665 | export class IconToggle extends MaterialComponent { 666 | component = 'icon-toggle'; 667 | js = true; 668 | ripple = true; 669 | 670 | getValue() { 671 | return this.base.children[0].checked; 672 | } 673 | 674 | mdlRender(props) { 675 | return ( 676 | 680 | ); 681 | } 682 | } 683 | 684 | 685 | 686 | 687 | /** @prop checked = false 688 | * @prop disabled = false 689 | */ 690 | export class Switch extends MaterialComponent { 691 | component = 'switch'; 692 | nodeName = 'label'; 693 | js = true; 694 | ripple = true; 695 | 696 | getValue() { 697 | return this.base.children[0].checked; 698 | } 699 | 700 | mdlRender(props) { 701 | return ( 702 | 706 | ); 707 | } 708 | } 709 | 710 | 711 | 712 | 713 | /** @prop selectable = false */ 714 | export class Table extends MaterialComponent { 715 | component = 'data-table'; 716 | nodeName = 'table'; 717 | js = true; 718 | } 719 | 720 | /** @prop non-numeric = false */ 721 | export class TableCell extends MaterialComponent { 722 | component = 'data-table__cell'; 723 | nodeName = 'td'; 724 | } 725 | 726 | Table.Cell = TableCell; 727 | 728 | 729 | 730 | 731 | /** @prop floating-label = false 732 | * @prop multiline = false 733 | * @prop expandable = false 734 | * @prop icon (used with expandable) 735 | */ 736 | export class TextField extends MaterialComponent { 737 | component = 'textfield'; 738 | js = true; 739 | 740 | constructor(...args) { 741 | super(...args); 742 | this.id = uid(); 743 | } 744 | 745 | mdlRender(props={}) { 746 | let id = props.id || this.id, 747 | p = extend({}, props); 748 | 749 | delete p.class; 750 | 751 | let field = ( 752 |
753 | 754 | 755 |
756 | ); 757 | if (props.multiline) { 758 | field.children[0].nodeName = 'textarea'; 759 | field.children[0].children = [props.value]; 760 | } 761 | if (props.expandable===true) { 762 | field.class = 'mdl-textfield__expandable-holder'; 763 | field = ( 764 |
765 | 768 | { field } 769 |
770 | ); 771 | } 772 | if (props.class) { 773 | (field.attributes = field.attributes || {}).class = props.class; 774 | } 775 | return field; 776 | } 777 | } 778 | 779 | 780 | 781 | 782 | 783 | 784 | /** @prop for [id] 785 | * @prop large = false 786 | */ 787 | export class Tooltip extends MaterialComponent { 788 | component = 'tooltip'; 789 | } 790 | 791 | 792 | 793 | 794 | export default { 795 | Icon, 796 | Button, 797 | Card, 798 | Layout, 799 | Navigation, 800 | Tabs, 801 | MegaFooter, 802 | MiniFooter, 803 | Grid, 804 | Cell, 805 | Progress, 806 | Spinner, 807 | Menu, 808 | Slider, 809 | CheckBox, 810 | Radio, 811 | IconToggle, 812 | Switch, 813 | Table, 814 | TextField, 815 | Tooltip 816 | }; 817 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Documentation Viewer", 3 | "short_name": "Docs", 4 | "icons": [ 5 | { 6 | "src": "/assets/icon-256.png", 7 | "sizes": "256x256", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/assets/icon-192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/assets/icon-128.png", 17 | "sizes": "128x128", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": "/", 22 | "display": "standalone", 23 | "theme_color": "aliceblue", 24 | "background_color": "red" 25 | } 26 | -------------------------------------------------------------------------------- /src/style/index.less: -------------------------------------------------------------------------------- 1 | @import (css) '~normalize.css'; 2 | @import (css) '//storage.googleapis.com/code.getmdl.io/1.0.5/material.indigo-pink.min.css'; 3 | @import (css) '//fonts.googleapis.com/icon?family=Material+Icons'; 4 | @import (css) '//fonts.googleapis.com/css?family=Roboto:300,500,700'; 5 | 6 | 7 | html, body { 8 | background: #E0E4E9; 9 | } 10 | 11 | #docs { 12 | position: static; 13 | overflow: visible; 14 | } 15 | 16 | main { 17 | display: block; 18 | text-align: center; 19 | } 20 | 21 | .info { 22 | margin: 0 auto; 23 | padding: 10px; 24 | max-width: 280px; 25 | } 26 | 27 | .symbol { 28 | .symbol-kind { 29 | position: absolute; 30 | left: 20px; 31 | top: 80px; 32 | font-size: 12px; 33 | font-style: italic; 34 | color: #FFF; 35 | } 36 | } 37 | 38 | .mdl-card { 39 | display: inline-block; 40 | text-align: left; 41 | margin: 20px; 42 | padding: 0 0 10px; 43 | width: auto; 44 | &.wide { 45 | max-width: 1000px; 46 | min-width: 600px; 47 | @media (max-width:640px) { 48 | & { 49 | display: block; 50 | min-width: 0; 51 | } 52 | } 53 | } 54 | &.skinny { 55 | max-width: 600px; 56 | min-width: 400px; 57 | @media (max-width:440px) { 58 | & { 59 | display: block; 60 | min-width: 0; 61 | } 62 | } 63 | } 64 | } 65 | 66 | .mdl-card__title.graphic { 67 | height: 100px; 68 | background: url(https://lh3.googleusercontent.com/-WiwrPHajH08/VTpzIfl09SI/AAAAAAAA4S4/062nOW9T-I4/s630-fcrop64=1,00002f44ffffc017/material-design-wallpaper-1.png) center/cover; 69 | .mdl-card__title-text { 70 | color: #FFF; 71 | font-size: 300%; 72 | text-shadow: 0 0 1px #000, 0 0 5px rgba(0,0,0,0.3); 73 | } 74 | } 75 | 76 | .members { 77 | margin: 0 20px; 78 | .member { 79 | padding-left: 5px; 80 | border-left: 2px solid #E6E6E6; 81 | } 82 | h2 { 83 | margin: 5px 0 0; 84 | font-size: 160%; 85 | line-height: 1.21; 86 | color: #888; 87 | } 88 | h3 { 89 | font-size: 120%; 90 | line-height: 1.21; 91 | margin-bottom: 0; 92 | 93 | em { 94 | color: #BBB; 95 | } 96 | } 97 | p { 98 | font-size: 12px; 99 | } 100 | } 101 | 102 | h4 { 103 | font-size: 110%; 104 | line-height: 1.21; 105 | margin: 5px 0; 106 | color: #666; 107 | } 108 | .params { 109 | padding-bottom: 10px; 110 | } 111 | .param { 112 | margin: -1px 10px 0; 113 | padding: 5px; 114 | background: #FFF; 115 | border: 1px solid #DDD; 116 | font-size: 80%; 117 | overflow: hidden; 118 | 119 | .param { 120 | margin: -1px 0 0; 121 | } 122 | 123 | dt { 124 | float: left; 125 | padding-right: 10px; 126 | min-width: 8em; 127 | font-weight: normal; 128 | } 129 | dd { 130 | display: inline-block; 131 | margin: 0; 132 | 133 | h6 { 134 | margin: 0; 135 | pdading: 0; 136 | font-size: 100%; 137 | } 138 | } 139 | } 140 | 141 | .mdl-card__supporting-text, 142 | .mdl-card__supporting-text > p { 143 | font-size: 16px; 144 | width: auto; 145 | } 146 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'), 2 | ExtractTextPlugin = require("extract-text-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | output: { 7 | path: './build', 8 | filename: 'bundle.js' 9 | }, 10 | resolve: { 11 | modulesDirectories: [ 12 | './src/lib', 13 | 'node_modules' 14 | ] 15 | }, 16 | module: { 17 | preLoaders: [ 18 | { loader: 'source-map' } 19 | ], 20 | loaders: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | loader: 'babel' 25 | }, 26 | { 27 | test: /\.(less|css)$/, 28 | loader: ExtractTextPlugin.extract("style?sourceMap", "css?sourceMap!autoprefixer?browsers=last 2 version!less") 29 | } 30 | ] 31 | }, 32 | plugins: ([ 33 | new webpack.NoErrorsPlugin(), 34 | new webpack.optimize.DedupePlugin(), 35 | new ExtractTextPlugin('style.css', { allChunks: true }) 36 | ]).concat(process.env.WEBPACK_ENV==='dev' ? [] : [ 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.optimize.UglifyJsPlugin({ 39 | output: { comments: false }, 40 | exclude: [/\.min\.js$/gi] 41 | }) 42 | ]), 43 | stats: { colors: true }, 44 | devtool: 'source-map', 45 | devServer: { 46 | port: process.env.PORT || 8080, 47 | contentBase: './src', 48 | historyApiFallback: true 49 | } 50 | }; 51 | --------------------------------------------------------------------------------