├── .eslintrc.json ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .vscode └── settings.json ├── .yarn └── releases │ └── yarn-1.21.1.js ├── .yarnrc ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── babel.config.js ├── docs ├── ambient.d.ts ├── catalog_logo.svg ├── configuration │ ├── basics.md │ ├── imports.md │ ├── other.md │ ├── pages.md │ ├── responsive-specimens.md │ └── theming.md ├── guides │ ├── frameworks.md │ ├── markdown.md │ ├── react.md │ └── webpack-babel.md ├── index.html ├── index.tsx ├── installation │ ├── create-catalog.md │ ├── module.md │ └── standalone.md ├── intro.md ├── package.json ├── react-integration.md ├── specimens │ ├── audio.md │ ├── code.md │ ├── color-palette.md │ ├── color.md │ ├── download.md │ ├── hint.md │ ├── html.md │ ├── image.md │ ├── overview.md │ ├── react.md │ ├── table.md │ ├── type.md │ └── video.md ├── static │ ├── .htaccess │ ├── _redirects │ ├── assets │ │ ├── cat_favicon-32.png │ │ ├── catalog_logo--icon.png │ │ ├── catalog_logo--icon.svg │ │ ├── catalog_logo--white.png │ │ ├── catalog_logo--white.svg │ │ ├── catalog_logo-overlay.png │ │ ├── catalog_logo.png │ │ ├── catalog_logo.svg │ │ ├── gradient.png │ │ ├── image.jpg │ │ ├── image_bw.jpg │ │ ├── sound.mp3 │ │ └── video.m4v │ ├── coming-soon.md │ ├── example-style.css │ └── test │ │ ├── test.css │ │ └── test.js ├── test │ ├── foo.module.css │ ├── foo.module.scss │ ├── test.md │ └── testtemplate.tsx └── tsconfig.json ├── lerna.json ├── netlify.toml ├── package.json ├── packages ├── babel-preset │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── ambient.d.ts │ │ ├── index.ts │ │ ├── reactspecimen-source.test.ts │ │ └── reactspecimen-source.ts │ └── tsconfig.json ├── catalog │ ├── index.d.ts │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── cli │ ├── package.json │ ├── rollup.config.js │ ├── setup-template │ │ ├── WELCOME.md │ │ ├── index.html │ │ ├── index.js │ │ └── static │ │ │ ├── catalog_logo.svg │ │ │ └── favicon.png │ ├── src │ │ ├── .eslintrc.json │ │ ├── actions │ │ │ ├── loadConfigFile.ts │ │ │ ├── loadPaths.ts │ │ │ ├── loadWebpackConfig.ts │ │ │ ├── runBuild.ts │ │ │ ├── runDevServer.ts │ │ │ └── setupCatalog.ts │ │ ├── ambient.d.ts │ │ ├── bin │ │ │ ├── catalog-build.ts │ │ │ ├── catalog-start.ts │ │ │ └── catalog.ts │ │ ├── config │ │ │ └── env.ts │ │ ├── server.ts │ │ └── utils │ │ │ ├── format.ts │ │ │ └── paths.ts │ └── tsconfig.json ├── core │ ├── .babelrc │ ├── .prettierignore │ ├── CHANGELOG.md │ ├── Makefile │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── CatalogPropTypes.js │ │ ├── DefaultResponsiveSizes.js │ │ ├── DefaultTheme.js │ │ ├── __snapshots__ │ │ │ ├── configure.test.js.snap │ │ │ └── markdownPage.test.js.snap │ │ ├── components │ │ │ ├── App │ │ │ │ ├── App.js │ │ │ │ ├── AppLayout.js │ │ │ │ └── NavigationBar.js │ │ │ ├── Card │ │ │ │ └── Card.js │ │ │ ├── Catalog.js │ │ │ ├── CatalogContext.js │ │ │ ├── Content │ │ │ │ ├── Heading.js │ │ │ │ └── Markdown.js │ │ │ ├── Frame │ │ │ │ ├── Frame.js │ │ │ │ └── FrameComponent.js │ │ │ ├── HighlightedCode │ │ │ │ └── HighlightedCode.js │ │ │ ├── Link │ │ │ │ ├── HeadingLink.js │ │ │ │ └── Link.js │ │ │ ├── Menu │ │ │ │ ├── ListItem.js │ │ │ │ └── Menu.js │ │ │ ├── Page │ │ │ │ ├── Loader.js │ │ │ │ ├── NotFound.js │ │ │ │ ├── Page.js │ │ │ │ ├── PageHeader.js │ │ │ │ └── PageRenderer.js │ │ │ ├── ResponsiveTabs │ │ │ │ ├── Preview.js │ │ │ │ └── ResponsiveTabs.js │ │ │ └── Specimen │ │ │ │ ├── MarkdownSpecimen.js │ │ │ │ ├── Span.js │ │ │ │ └── Specimen.js │ │ ├── configure.js │ │ ├── configure.test.js │ │ ├── configureRoutes.js │ │ ├── configureRoutes.test.js │ │ ├── emotion.js │ │ ├── index.js │ │ ├── markdown │ │ │ ├── ReactRenderer.js │ │ │ ├── __snapshots__ │ │ │ │ └── renderMarkdown.test.js.snap │ │ │ ├── marked-react.js │ │ │ ├── renderMarkdown.js │ │ │ └── renderMarkdown.test.js │ │ ├── markdownPage.js │ │ ├── markdownPage.test.js │ │ ├── pageLoader.js │ │ ├── render.js │ │ ├── specimens.js │ │ ├── specimens │ │ │ ├── Audio.js │ │ │ ├── Code.js │ │ │ ├── Color.js │ │ │ ├── ColorPalette.js │ │ │ ├── Download.js │ │ │ ├── Hint.js │ │ │ ├── Html.js │ │ │ ├── Image.js │ │ │ ├── RawCode.js │ │ │ ├── ReactSpecimen │ │ │ │ ├── ReactSpecimen.js │ │ │ │ ├── reactElementToString.js │ │ │ │ └── reactElementToString.test.js │ │ │ ├── Table.js │ │ │ ├── Type.js │ │ │ └── Video.js │ │ ├── styled.js │ │ ├── styles │ │ │ └── typography.js │ │ └── utils │ │ │ ├── __snapshots__ │ │ │ └── transformJSX.test.js.snap │ │ │ ├── mapSpecimenOption.js │ │ │ ├── parseSpecimenBody.js │ │ │ ├── parseSpecimenBody.test.js │ │ │ ├── parseSpecimenOptions.js │ │ │ ├── parseSpecimenOptions.test.js │ │ │ ├── parseSpecimenType.js │ │ │ ├── parseSpecimenType.test.js │ │ │ ├── path.js │ │ │ ├── path.test.js │ │ │ ├── requireModuleDefault.js │ │ │ ├── runscript.js │ │ │ ├── seqKey.js │ │ │ ├── transformJSX.js │ │ │ ├── transformJSX.test.js │ │ │ ├── validateSizes.js │ │ │ ├── validateSizes.test.js │ │ │ └── warning.js │ └── types │ │ └── catalog │ │ ├── index.d.ts │ │ ├── test-page-2.tsx │ │ ├── test-page.tsx │ │ ├── test.tsx │ │ └── tsconfig.json ├── markdown-loader │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ └── index.ts │ └── tsconfig.json └── standalone │ ├── .babelrc │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── index.html │ ├── package.json │ ├── rollup.config.js │ └── src │ └── index.js ├── rollup.config.js ├── scripts └── publish-canary.sh ├── shell.nix ├── tsconfig.common.json ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:react/recommended", 5 | "prettier", 6 | "prettier/react" 7 | ], 8 | "parser": "babel-eslint", 9 | "globals": { 10 | "expect": true, 11 | "test": true 12 | }, 13 | "env": { 14 | "browser": true, 15 | "node": true, 16 | "es6": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [12.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | 21 | - name: Get yarn cache 22 | id: yarn-cache 23 | run: echo "::set-output name=dir::$(yarn cache dir)" 24 | 25 | - uses: actions/cache@v1 26 | with: 27 | path: ${{ steps.yarn-cache.outputs.dir }} 28 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-yarn- 31 | 32 | - name: Install dependencies 33 | run: yarn --frozen-lockfile 34 | 35 | - name: Run test 36 | run: yarn test 37 | env: 38 | CI: true 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /examples/**/build 4 | /docs/build 5 | *.log 6 | .rpt2_cache 7 | dist 8 | packages/**/lib -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "typescript.tsdk": "node_modules/typescript/lib" 4 | } 5 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | yarn-path ".yarn/releases/yarn-1.21.1.js" 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # DEVELOPERS 2 | Jeremy Stucki (https://github.com/herrstucki) 3 | Peter Gassner (https://github.com/naehrstoff) 4 | Gerhard Bliedung (https://github.com/bldng) 5 | Thomas Preusse (https://github.com/tpreusse) 6 | Piotr Fedorczyk (https://github.com/piotrf) 7 | Tomas Carnecky (https://github.com/wereHamster) 8 | Mark Hintz (https://github.com/mhintz) 9 | 10 | # DESIGNERS 11 | Christoph Schmid (http://www.interactivethings.com) 12 | Tania Boa (http://www.interactivethings.com) 13 | Jan Wächter (http://www.interactivethings.com) 14 | 15 | # And of course everyone else at http://www.interactivethings.com/team/ 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | ### 1. Install dependencies 4 | 5 | ``` 6 | npm ci 7 | ``` 8 | 9 | ### 2. Bootstrap packages 10 | 11 | This will install all dependencies in the individual packages and link packages with each other. 12 | 13 | ``` 14 | ./node_modules/.bin/lerna bootstrap 15 | ``` 16 | 17 | ### 3. Start the tests in watch mode 18 | 19 | ``` 20 | ./node_modules/.bin/jest --watch 21 | ``` 22 | 23 | ### 4. Start the docs catalog 24 | 25 | This will start up the local catalog that is stored in the `docs/` folder. Use it to test the changes you do to the core package. 26 | 27 | ``` 28 | make 29 | ``` 30 | 31 | # Release 32 | 33 | Releasing is done manually. We currently publish two kinds of releases: canary (alpha) and latest (stable, production-ready). 34 | 35 | TODO: The release process should eventually be automated through travis-ci. 36 | 37 | ### Canary 38 | 39 | Canary releases are published under the npm dist-tag `canary` and a semver tag `-alpha.N`. 40 | 41 | ``` 42 | ./node_modules/.bin/tsc --build packages 43 | make build -C packages/core build 44 | make build -C packages/standalone build 45 | ./node_modules/.bin/lerna publish --canary 46 | ``` 47 | 48 | ### Latest 49 | 50 | The following steps publish all packages under a new version. 51 | 52 | ``` 53 | ./node_modules/.bin/lerna version 54 | ./node_modules/.bin/tsc --build packages 55 | make build -C packages/core build 56 | make build -C packages/standalone build 57 | ./node_modules/.bin/lerna publish from-git 58 | ``` 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-present, Interactive Things GmbH 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * Neither the name of Interactive Things GmbH nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: all 3 | all: 4 | yarn bootstrap && yarn dev 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Catalog Logo](https://interactivethings.github.io/catalog/docs/assets/catalog_logo.svg) 2 | 3 | [![Travis](https://img.shields.io/travis/interactivethings/catalog.svg)](https://travis-ci.org/interactivethings/catalog/) [![Downloads](https://img.shields.io/npm/dm/catalog.svg)](https://www.npmjs.com/package/catalog) [![Version](https://img.shields.io/npm/v/catalog.svg)](https://www.npmjs.com/package/catalog) [![License](https://img.shields.io/npm/l/catalog.svg)]() 4 | 5 | > 🚧 This is the currently in-development v4 branch. Stable code is in [master](https://github.com/interactivethings/catalog/tree/master). 6 | 7 | # Catalog 8 | 9 | Catalog lets you create beautiful living and fully interactive style guides using Markdown and React components. 10 | 11 | Please read the [Catalog documentation](https://docs.catalog.style/) (built with Catalog!) for detailed installation and usage instructions. 12 | 13 | ## Installation 14 | 15 | ### yarn 16 | 17 | ``` 18 | yarn add catalog react react-dom 19 | ``` 20 | 21 | ### npm 22 | 23 | ``` 24 | npm install catalog react react-dom --save 25 | ``` 26 | 27 | ## Development 28 | 29 | > Please make sure that you have Node >= 6 and [yarn](https://yarnpkg.com/) installed. 30 | 31 | ### Build process 32 | 33 | Start the build process in watch mode: 34 | 35 | ``` 36 | make 37 | ``` 38 | 39 | ### Docs 40 | 41 | To edit Catalog documentation, run: 42 | 43 | ``` 44 | make docs 45 | ``` 46 | 47 | > Note: this uses the local Catalog build from `dist/`. Run `make` before/alongside `make docs`. 48 | 49 | ### Linking 50 | 51 | When developing Catalog you want to link it locally: 52 | 53 | ``` 54 | yarn link 55 | ``` 56 | 57 | You can then link to this version in your project (or one of the examples): 58 | 59 | ``` 60 | yarn link catalog 61 | ``` 62 | 63 | ### Tests 64 | 65 | To run [Jest](https://facebook.github.io/jest/) tests in watch mode: 66 | 67 | ``` 68 | make test-watch 69 | ``` 70 | 71 | ## Creating a Release 72 | 73 | Bump Catalog's version: 74 | 75 | ``` 76 | make version 77 | ``` 78 | 79 | Then push including tags: 80 | 81 | ``` 82 | git push && git push --tags 83 | ``` 84 | 85 | The CI server will automatically run tests, build and publish the new version to npm 86 | 87 | ## CI commands 88 | 89 | > These usually run automatically on the CI server 90 | 91 | ### Create a build 92 | 93 | ``` 94 | make build 95 | ``` 96 | 97 | ### Create a documentation build 98 | 99 | ``` 100 | make build-docs 101 | ``` 102 | 103 | ### Publish to npm and generate GitHub release notes 104 | 105 | ``` 106 | make publish 107 | ``` 108 | 109 | ## Credits 110 | 111 | Catalog is developed by [many people](https://github.com/interactivethings/catalog/blob/master/AUTHORS) at [Interactive Things](https://www.interactivethings.com/), a User Experience and Data Visualization Studio based in Zürich. 112 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | babelrcRoots: [".", "./packages/*"], 3 | presets: [ 4 | ["@babel/preset-env", { loose: true, targets: { node: 10 } }], 5 | "@babel/preset-typescript", 6 | "@babel/preset-react" 7 | ] 8 | }; 9 | -------------------------------------------------------------------------------- /docs/ambient.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg"; 2 | declare module "*.css"; 3 | declare module "*.scss"; 4 | -------------------------------------------------------------------------------- /docs/configuration/basics.md: -------------------------------------------------------------------------------- 1 | > Configure Catalog to add style guide pages, import code from your application, and theme it to match your brand 2 | 3 | To get Catalog running, you need to configure it with a `title` and some `pages`. 4 | 5 | Either provide a configuration object to `Catalog.render()` or props to the `Catalog` React component. 6 | 7 | ```code 8 | span: 3 9 | lang: js 10 | --- 11 | // In any script: 12 | 13 | Catalog.render( 14 | { 15 | title: 'My Style Guide', 16 | pages: [ 17 | // … 18 | ] 19 | }, 20 | element 21 | ); 22 | ``` 23 | 24 | ```code 25 | span: 3 26 | lang: jsx 27 | --- 28 | // With React: 29 | 30 | ReactDOM.render( 31 | , 37 | element 38 | ); 39 | ``` 40 | 41 | ## Basic Configuration Options 42 | 43 | ### `title` 44 | 45 | `string` 46 | 47 | The title of your Catalog 48 | -------------------------------------------------------------------------------- /docs/configuration/imports.md: -------------------------------------------------------------------------------- 1 | 2 | ## Including Code From Your Application 3 | 4 | To document your application properly, you need to include its styles and scripts. You have three options for doing that: 5 | 6 | ```hint|directive 7 | You can either set these at the top level, or on each page. Styles, scripts, and imports defined at the top level will be available on each page. 8 | ``` 9 | 10 | ### `styles` 11 | 12 | `Array` 13 | 14 | Catalog will include CSS files referenced in the `styles` option. 15 | 16 | #### Styles Example 17 | 18 | ```code 19 | lang: js 20 | --- 21 | { 22 | styles: ['/foo.css', '/bar.css'] 23 | // Other options … 24 | pages: [ 25 | { 26 | styles: ['/foobar.css'], 27 | // On this page, 'foo.css', 'bar.css', and 'foobar.css' will be included 28 | // Other page options … 29 | } 30 | ] 31 | } 32 | ``` 33 | 34 | ### `scripts` 35 | 36 | `Array` 37 | 38 | Catalog will inject JavaScript files referenced in the `scripts` option. 39 | 40 | #### Scripts Example 41 | 42 | ```code 43 | lang: js 44 | --- 45 | { 46 | scripts: ['/foo.js', '/bar.js'] 47 | // Other options … 48 | pages: [ 49 | { 50 | scripts: ['/foobar.js'], 51 | // On this page, 'foo.js', 'bar.js', and 'foobar.js' will be injected 52 | // Other page options … 53 | } 54 | ] 55 | } 56 | ``` 57 | 58 | ### `imports` 59 | 60 | `{[key: string]: any}` 61 | 62 | To make components and other code available to your [Specimens](/specimens), use the `imports` option. 63 | 64 | #### Imports Example 65 | 66 | ```code 67 | lang: js 68 | --- 69 | { 70 | imports: {Foo: require('Foo'), Bar: require('Bar')} 71 | // Other options … 72 | pages: [ 73 | { 74 | imports: {FooBar: require('FooBar')}, 75 | // On this page, 'Foo', 'Bar', and 'FooBar' will be available to use in React Specimens 76 | // Other page options … 77 | } 78 | ] 79 | } 80 | ``` -------------------------------------------------------------------------------- /docs/configuration/other.md: -------------------------------------------------------------------------------- 1 | ## Responsive Specimens 2 | 3 | ### `responsiveSizes` 4 | 5 | `Array<{name: string, width: number, height: number}>` 6 | 7 | To test or document responsive behavior of [React](/specimens/react#responsive-display) and [HTML](/specimens/html#responsive-display) components, Catalog provides some basic default screen sizes (`small, medium, large` and `xlarge`). Given that each project has different requirements, you can easily define new sizes. 8 | 9 | Let's assume you want to work with a smart watch, a tablet and Desktop, the Catalog configuration could look like this: 10 | 11 | ```code 12 | ... 13 | title: 'Catalog', 14 | responsiveSizes: [ 15 | {name: 'watch', width: 272, height: 340} 16 | {name: 'tablet', width: 1024, height: 768}, 17 | {name: 'desktop', width: 1920, height: 1080}, 18 | ], 19 | pages: [ 20 | ... 21 | ``` 22 | 23 | ## Routing 24 | 25 | ### `useBrowserHistory` 26 | 27 | `boolean` 28 | 29 | To maximize compatibility, Catalog uses hash-based routing (e.g. `my-styleguide.com/#/about`) by default. It can also use HTML5 pushstate-based routing, so you get "real" URLs (e.g. `my-styleguide.com/about`). Set `useBrowserHistory` to `true` to enable this. 30 | 31 | ```hint 32 | GitHub Pages doesn't support this style of routing without [hacks](https://github.com/rafrex/spa-github-pages). Instead, you can use: 33 | 34 | - [Netlify](https://www.netlify.com/) 35 | - [Surge](http://surge.sh/) 36 | - [now](https://zeit.co/docs/examples/create-react-app) 37 | - [Any nginx or Apache server that you can configure](http://readystate4.com/2012/05/17/nginx-and-apache-rewrite-to-support-html5-pushstate/) 38 | ``` 39 | 40 | ### `basePath` 41 | 42 | `string` 43 | 44 | If you want Catalog to run under a certain base path, e.g. `my-styleguide.com/catalog/about`, set the `basePath` configuration option. The base path will be prefixed to all page paths. 45 | 46 | This works best together with `useBrowserHistory`. 47 | 48 | #### Route Settings Example 49 | 50 | ```code 51 | lang: js 52 | --- 53 | { 54 | title: 'My Catalog', 55 | basePath: '/catalog', 56 | useBrowserHistory: true, 57 | pages: [ 58 | { 59 | path: '/', // Page will be accessible at `/catalog/` 60 | title: 'Introduction', 61 | // … 62 | }, 63 | { 64 | path: '/about', // Page will be accessible at `/catalog/about/` 65 | title: 'About', 66 | // … 67 | }, 68 | // Other pages … 69 | ] 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/configuration/pages.md: -------------------------------------------------------------------------------- 1 | ## Pages 2 | 3 | ### `pages` 4 | 5 | `Array` 6 | 7 | Add pages and page groups to your style guide. 8 | 9 | #### Page 10 | 11 | - `path: string`: The path where the page is accessible 12 | - `title: string`: The title of the page (also shows up in the navigation) 13 | - `content: React.Component`: A Catalog page 14 | - `hideFromMenu: boolean`: Hide the page in the navigation (optional) 15 | - `imports?: Imports`: page [imports](/configuration/imports#imports) (optional) 16 | - `styles?: Styles`: page [styles](/configuration/imports#styles) (optional) 17 | - `scripts?: Scripts`: page [scripts](/configuration/imports#scripts) (optional) 18 | 19 | ```hint|directive 20 | You should at least have a page with path `'/'` (which is the first page shown) 21 | ``` 22 | 23 | ##### A note on the `content` property 24 | 25 | A page's `content` property expects a valid React component. 26 | 27 | Either import page components statically or use Catalog's `pageLoader` function to create components that lazy-load pages from different sources. 28 | 29 | These are all valid examples for `content`: 30 | 31 | ```code 32 | lang: js 33 | --- 34 | // Statically import a page 35 | import Intro from './Intro'; 36 | { content: Intro } 37 | 38 | // Statically `require` a page 39 | { content: require('./Intro') } 40 | 41 | // Lazy-load a static Markdown page in the browser 42 | { content: Catalog.pageLoader('intro.md') } 43 | 44 | // Lazy-load a page component 45 | { content: Catalog.pageLoader(() => import('./Intro'))) } 46 | ``` 47 | 48 | ##### Page Example 49 | 50 | ```code 51 | lang: js 52 | --- 53 | { 54 | title: 'My Catalog', 55 | pages: [ 56 | { 57 | path: '/', 58 | title: 'Introduction', 59 | content: Catalog.pageLoader('intro.md') // Load page from a static Markdown file 60 | }, 61 | // Other pages … 62 | ] 63 | } 64 | ``` 65 | 66 | #### Page Group 67 | 68 | To create a page group, specify an object with a `title` and a `pages` property. The pages added to the group are grouped in the navigation. 69 | 70 | - `title: string`: The title of the page group 71 | - `pages: Array`: An array of pages 72 | 73 | ```hint 74 | Page groups can only contain pages but not other page groups. 75 | ``` 76 | 77 | ``` 78 | { 79 | title: 'My Catalog', 80 | pages: [ 81 | { 82 | path: '/', 83 | title: 'Introduction', 84 | content: Catalog.pageLoader('intro.md') 85 | }, 86 | { 87 | title: 'Basics', 88 | pages: [ 89 | { 90 | path: '/get-started', 91 | title: 'Get Started', 92 | content: Catalog.pageLoader('get-started.md') 93 | }, 94 | // Other subpages of 'Basics' 95 | ] 96 | }, 97 | 98 | ] 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /docs/configuration/responsive-specimens.md: -------------------------------------------------------------------------------- 1 | 2 | ## Responsive Specimens 3 | 4 | ### `responsiveSizes` 5 | 6 | `Array<{name: string, width: number, height: number}>` 7 | 8 | To test or document responsive behavior of [React](/specimens/react#responsive-display) and [HTML](/specimens/html#responsive-display) components, Catalog provides some basic default screen sizes (`small, medium, large` and `xlarge`). Given that each project has different requirements, you can easily define new sizes. 9 | 10 | Let's assume you want to work with a smart watch, a tablet and Desktop, the Catalog configuration could look like this: 11 | 12 | ```code 13 | ... 14 | title: 'Catalog', 15 | responsiveSizes: [ 16 | {name: 'watch', width: 272, height: 340} 17 | {name: 'tablet', width: 1024, height: 768}, 18 | {name: 'desktop', width: 1920, height: 1080}, 19 | ], 20 | pages: [ 21 | ... 22 | 23 | ``` -------------------------------------------------------------------------------- /docs/guides/frameworks.md: -------------------------------------------------------------------------------- 1 | > Catalog can be integrated with popular React frameworks 2 | 3 | ## Create React App 4 | 5 | … 6 | 7 | ## Next.js 8 | 9 | … -------------------------------------------------------------------------------- /docs/guides/markdown.md: -------------------------------------------------------------------------------- 1 | > With Catalog you write documentation using [Markdown](http://daringfireball.net/projects/markdown/syntax). 2 | 3 | For an introduction to the Markdown syntax, [Mastering Markdown](https://guides.github.com/features/mastering-markdown/) of GitHub is a good starting point. 4 | 5 | Catalog supports all basic Markdown features like headings, paragraphs, blockquotes, lists etc. To create interactive and rich documentation, Catalog introduces **Specimens**. 6 | 7 | ## Specimens 8 | 9 | Specimens are an extension of standard Markdown syntax to include special content like color palettes and fully interactive components in your style guide. 10 | 11 | All Specimens are written as _tagged_ Markdown code blocks (similar to how you specify the programming language of a code block for syntax highlighting on GitHub). 12 | 13 | For example, to place a color swatch in your documentation: 14 | 15 | ```` 16 | ```color 17 | value: '#f55' 18 | name: 'Red' 19 | span: 2 20 | ``` 21 | ```` 22 | 23 | … which will be rendered like this: 24 | 25 | ```color 26 | value: '#f55' 27 | name: 'Red' 28 | span: 2 29 | ``` 30 | 31 | Catalog provides many specimen types, from images, hints, code snippets to fully interactive HTML and React component specimens. For a full list of all specimens and their features, go to the [Specimen documentation](/specimens) 32 | 33 | ## Links 34 | 35 | Use regular Markdown link to pages within your Catalog documentation. 36 | 37 | Each page section can be linked to directly by adding a hash to your link. Click on the link icon next to a heading to jump to that section. 38 | 39 | ```code|lang-markdown 40 | - [A link to a page](/specimens) 41 | - [A link to a page section](/specimens#content) 42 | - [A link to section on the same page](#example-page) 43 | - [External links work too of course](https://www.interactivethings.com/) 44 | - [Also external links which don't point to a page](/assets/catalog_logo.png) 45 | ``` 46 | 47 | ## Example Page 48 | 49 | Specimens can be placed anywhere in a Markdown document. For example: 50 | 51 | ````code|lang-markdown 52 | # Page Title 53 | 54 | > Some lead text (a blockquote, optional) 55 | 56 | Introductory text 57 | 58 | ## A Title 59 | 60 | Some description. 61 | 62 | - A bullet point list 63 | - with 64 | - some 65 | - items 66 | 67 | ### A subtitle 68 | 69 | This is a code specimen: 70 | 71 | ```code 72 | function identity(x) { 73 | return x; 74 | } 75 | ``` 76 | 77 | ## Another Title 78 | 79 | And some more text. 80 | 81 | ```image 82 | src: img/cat.jpg 83 | title: Nice Cat 84 | ``` 85 | ```` 86 | -------------------------------------------------------------------------------- /docs/guides/react.md: -------------------------------------------------------------------------------- 1 | > Catalog itself is a React app. You can use its components to document your code more effectively. 2 | 3 | You can write your complete style guide in Markdown files, including interactive components. But Catalog also supports creating pages using JavaScript and React components directly, which allows you to do some interesting things: 4 | 5 | - import your components (and any code) like you'd do in any React app 6 | - include any other components, not just Specimens 7 | - use any data fetching or application state on pages and in Specimens 8 | - let static typecheckers like [Flow](https://flow.org) or [TypeScript](https://www.typescriptlang.org/) check your Specimens (preventing you from writing wrong documentation) 9 | - generate pages programmatically 10 | 11 | You can also mix pages from Markdown and JavaScript files. 12 | 13 | ## `Catalog` 14 | 15 | The main `Catalog` component accepts all [configuration options](/configuration) as props. Use it directly with `ReactDOM.render()`. 16 | 17 | ```code|lang-jsx 18 | import React from 'react'; 19 | import ReactDOM from 'react-dom'; 20 | import {Catalog} from 'catalog'; 21 | 22 | ReactDOM.render( 23 | , 30 | document.getElementById('root') 31 | ); 32 | ``` 33 | 34 | ## `*Specimen` 35 | 36 | All [Specimens](/specimens) are exported as React components. Specimen options can be used as props. 37 | 38 | The Specimen component names are capitalized versions of the Specimen type. E.g. 39 | 40 | ```table 41 | rows: 42 | - {Type: "react", Component: "**ReactSpecimen**"} 43 | - {Type: "color-palette", Component: "**ColorPaletteSpecimen**"} 44 | - {Type: "etc.", Component: ""} 45 | ``` 46 | 47 | These are equivalent: 48 | 49 | ````code 50 | span: 3 51 | --- 52 | ```color 53 | name: "Red" 54 | value: "#FF5500" 55 | span: 2 56 | ``` 57 | ```` 58 | 59 | ````code|lang-jsx 60 | span: 3 61 | --- 62 | 67 | ```` 68 | 69 | 70 | ## `markdown` 71 | 72 | Instead of using Markdown files, you can use Catalog's `markdown` tagged template literal to create pages. 73 | 74 | 75 | … while still retaining the convenience of writing Markdown for the page content. 76 | 77 | Include specimens (and other components) by interpolating them with `${...}`. 78 | 79 | For example: 80 | 81 | ```code|lang-jsx 82 | import React from 'react'; 83 | import {markdown, ReactSpecimen, ColorPaletteSpecimen} from 'catalog'; 84 | 85 | import Button from './components/Button/Button'; 86 | import {generateColorPalette} from './utils'; 87 | 88 | export default () => markdown` 89 | ## My Buttons 90 | 91 | Are so nice 92 | 93 | - Yes 94 | - or no? 95 | 96 | ${ 97 | 98 | } 99 | 100 | ${} 104 | `; 105 | ``` 106 | 107 | 108 | ## `Page` 109 | 110 | As an alternative to the `markdown` literal, you can also use the `Page` component directly. 111 | 112 | This example is equivalent to above's: 113 | 114 | ```code|lang-jsx 115 | import React from 'react'; 116 | import {Page, ReactSpecimen, ColorPaletteSpecimen} from 'catalog'; 117 | import Button from 'components/Button/Button'; 118 | 119 | export default () => ( 120 | 121 |

My Buttons

122 | 123 |

Are so nice

124 | 125 |
    126 |
  • Yes
  • 127 |
  • or no?
  • 128 |
129 | 130 | 131 | 132 | 133 | 134 | 138 |
139 | ); 140 | ``` 141 | -------------------------------------------------------------------------------- /docs/guides/webpack-babel.md: -------------------------------------------------------------------------------- 1 | > For customized build setups, Catalog provides a [webpack](https://webpack.js.org/) loader and a [Babel](http://babeljs.io/) preset 2 | 3 | This is for the adventurous who don't shy away from configuring webpack! Use this guide if you: 4 | 5 | - have a custom webpack/Babel setup already 6 | - need to use specific webpack loaders (e.g. for TypeScript) or Babel transforms 7 | 8 | ```hint|directive 9 | You _don't_ need a custom setup if you're using Catalog on its own or in combination with [Create React App](https://github.com/facebookincubator/create-react-app) or [next.js](https://github.com/zeit/next.js). Use [Create Catalog](/installation/create-catalog) instead. 10 | ``` 11 | 12 | ## `catalog.config.js` 13 | 14 | If you use the Catalog command line scripts (`catalog start` and `catalog build`), you can add a `catalog.config.js` file to modify Catalog's generated webpack configuration. This is useful when you want to add another webpack loader or plugin. 15 | 16 | Example `catalog.config.js`: 17 | 18 | ```code|lang-js 19 | module.exports = { 20 | webpack: (catalogWebpackConfig, {paths, dev, framework}) => { 21 | // Modify catalogWebpackConfig ... 22 | return modifiedWebpackConfig; 23 | } 24 | } 25 | ``` 26 | 27 | ```hint|warning 28 | # Warning 29 | 30 | Modifying a webpack configuration is tricky! Only do this if you know what you're doing! We don't make any guarantees that the shape of Catalog's webpack configuration will stay stable, so it's probably a good idea to lock Catalog to an exact version in your `package.json` to prevent unexpected results when Catalog updates. 31 | ``` 32 | 33 | ## Webpack loader 34 | 35 | Catalog's webpack loader allows you to import Markdown files as pages. 36 | 37 | ```code|lang-javascript 38 | { 39 | // Other webpack config ... 40 | module: { 41 | rules: [ 42 | { 43 | test: /\.md$/, 44 | use: ['@catalog/markdown-loader'] 45 | } 46 | ] 47 | } 48 | }; 49 | ``` 50 | 51 | ## Babel preset 52 | 53 | Catalog's Babel preset ensures that JSX source code of [ReactSpecimens](/specimens/react) is preserved. 54 | 55 | Add `@catalog/babel-preset` to your presets in `.babelrc` 56 | 57 | ```code|lang-javascript 58 | { 59 | "presets": ["@catalog/babel-preset"] 60 | } 61 | ``` 62 | 63 | Don't forget to install the preset via: 64 | 65 | ``` 66 | npm i -D @catalog/babel-preset 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Catalog 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/installation/create-catalog.md: -------------------------------------------------------------------------------- 1 | > Create Catalog is a command line tool to set up Catalog for your style guide. One single command takes care of installing all dependencies and configuring the basics. 2 | 3 | ```hint|directive 4 | To use Create Catalog, you need a recent version of [Node.js](https://nodejs.org/) installed on your computer (Version 8+ recommended). 5 | ``` 6 | 7 | ## Installation 8 | 9 | Install the create-catalog npm package globally: 10 | 11 | ```code 12 | npm install -g create-catalog 13 | ``` 14 | 15 | Then use the `create-catalog` command to set up Catalog: 16 | 17 | ```code 18 | create-catalog 19 | ``` 20 | 21 | Create Catalog will install everything that's necessary to run Catalog and then display instructions for using it. 22 | 23 | ```hint|neutral 24 | By default, Catalog integrates with [Create React App](https://github.com/facebookincubator/create-react-app) and [next.js](https://github.com/zeit/next.js). Run `create-catalog` in the same directory as you've set up your app. 25 | ``` 26 | 27 | ## Installing with npx or yarn 28 | 29 | If you have npm >= 5.2.0 (check with `npm -v`), you can use `npx` to skip the manual installation step. 30 | 31 | ```code 32 | npx create-catalog 33 | ``` 34 | 35 | If you're using [yarn](https://yarnpkg.com/) (>= 0.25) instead of npm, you can also skip the manual installation step. 36 | 37 | ```code 38 | yarn create catalog 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /docs/installation/module.md: -------------------------------------------------------------------------------- 1 | > Catalog can be integrated as a module into any React application 2 | 3 | ## Installation 4 | 5 | Install the `catalog` npm package and its peer dependencies. 6 | 7 | ``` 8 | npm install catalog react react-dom --save 9 | ``` 10 | 11 | Import Catalog, and render it. 12 | 13 | ```code|lang-js 14 | import React from 'react'; 15 | import ReactDOM from 'react-dom'; 16 | import {Catalog} from 'catalog'; 17 | 18 | ReactDOM.render( 19 | , 30 | document.getElementById('app') 31 | ); 32 | ``` 33 | 34 | See the [React API](/guides/react) guide for more details. 35 | -------------------------------------------------------------------------------- /docs/installation/standalone.md: -------------------------------------------------------------------------------- 1 | > The standalone version of Catalog doesn't need any installation and can be used directly in the browser. 2 | 3 | If using [Create Catalog](/installation/create-catalog) is not an option (maybe because you don't have/want Node.js on your computer), you can also embed Catalog with a ` 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 58 | 59 | 60 | ``` 61 | 62 | ## Running Catalog Standalone on your local machine 63 | 64 | Catalog doesn't need anything besides a single Javascript file. However, to run Catalog on your local machine, you will have to use some kind of web server due to browser security restrictions (the browser won't be able to load the Markdown files from your local file system). 65 | 66 | ### On macOS 67 | 68 | macOS comes preinstalled with python, which conveniently has a web server built-in. Run this in your terminal: 69 | 70 | ```code 71 | python -m SimpleHTTPServer 72 | ``` 73 | 74 | ### On Windows 75 | 76 | Use [xampp](https://www.apachefriends.org/de/index.html) or something similar. 77 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | > Catalog is a tool for creating living style guides for digital products. It combines design documentation and assets with real, live components in one single place. 2 | 3 | ### Get Started 4 | 5 | - [How to install Catalog](/installation/create-catalog) 6 | - [Catalog configuration](/configuration) 7 | - [Write content using Markdown](/writing-content) 8 | - [Document your design system and components with Specimens](/specimens) 9 | 10 | 11 | ### Useful Links 12 | 13 | - [Try Catalog in your browser](https://www.catalog.style/try) 14 | - [Catalog Website](https://www.catalog.style) 15 | 16 | ### Source Code 17 | 18 | Catalog is open source and available on [GitHub](https://github.com/interactivethings/catalog/). 19 | 20 | ### Credits 21 | 22 | Catalog is developed by [the team](https://github.com/interactivethings/catalog/blob/master/AUTHORS) at [Interactive Things](https://www.interactivethings.com/), a digital product design studio based in Zürich, Switzerland. 23 | 24 | ### License 25 | 26 | Catalog is distributed under the BSD-3-Clause license. -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@catalog/docs", 4 | "version": "4.0.1-canary.2", 5 | "dependencies": { 6 | "@babel/standalone": "^7.1.0", 7 | "catalog": "^4.0.1-canary.2", 8 | "node-sass": "^4.11.0", 9 | "react": "^16.8.6", 10 | "react-dom": "^16.8.6" 11 | }, 12 | "scripts": { 13 | "dev": "../packages/cli/dist/bin/catalog-start.js .", 14 | "build": "../packages/cli/dist/bin/catalog-build.js ." 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/react-integration.md: -------------------------------------------------------------------------------- 1 | > With Catalog you can develop React components directly in your style guide, enable hot-reloading documentation writing, and integrate Catalog in an existing application. 2 | 3 | ```hint 4 | This section assumes that you have a working development setup with npm, webpack and a ES2015 transpiler (for example Babel). 5 | ``` 6 | 7 | 8 | 9 | 10 | ## Advanced Integration 11 | 12 | > If you need more control over the integration into your application, Catalog is flexible enough to supports some advanced use cases. 13 | 14 | ### React Router Routes 15 | 16 | The `Catalog` component creates routes and renders React Router with a few presets internally. But with `configureRoutes` you can also generate Catalog routes, so you can 17 | 18 | - mix them with other routes, 19 | - use them for server-side rendering, 20 | - configure `Router` however you like. 21 | 22 | ```code|lang-jsx 23 | import React from 'react'; 24 | import ReactDOM from 'react-dom'; 25 | import {Router} from 'react-router'; 26 | import {configureRoutes} from 'catalog'; 27 | 28 | const catalogRoutes = configureRoutes({ 29 | title: 'My Styleguide', 30 | basePath: '/catalog', 31 | pages: [/* ... */] 32 | }); 33 | 34 | const routes = [ 35 | catalogRoutes, 36 | // other routes ... 37 | ]; 38 | 39 | 40 | ReactDOM.render( 41 | , 42 | document.getElementById('app') 43 | ); 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/specimens/audio.md: -------------------------------------------------------------------------------- 1 | > The Audio Specimen can be used to document the audible aspects, for example background music, sound effects or jingles in the browser-provided interface. 2 | 3 | ### Props 4 | 5 | - __`src: string`__ the url pointing to the source file 6 | - `title: string` a title for the audio clip, otherwise the filename is used 7 | - `autoplay: boolean` if true, the audio clip plays without interaction 8 | - `loop: boolean ` if true, the audio clip plays repeatedly 9 | - `span: number[1–6]` width of the specimen 10 | 11 | ### Example 12 | 13 | ```audio 14 | span: 3 15 | src: "/assets/sound.mp3" 16 | ``` 17 | 18 | ````code 19 | ```audio 20 | span: 3 21 | src: "/assets/sound.mp3" 22 | ``` 23 | ```` 24 | -------------------------------------------------------------------------------- /docs/specimens/code.md: -------------------------------------------------------------------------------- 1 | > The Code Specimen displays a piece of code. 2 | 3 | ### Props 4 | 5 | * `collapsed: boolean` useful for longer or redundant code that still needs to be accessible 6 | * `lang: string` defines the language for code highlighting 7 | * `span: number[1–6]` width of the specimen 8 | 9 | ### Examples 10 | 11 | #### Basic 12 | 13 | ```code 14 |
Hello, Code Specimen
15 | ``` 16 | 17 | ````code 18 | ```code 19 |
Hello, Code Specimen
20 | ``` 21 | ```` 22 | 23 | #### Syntax highlighting 24 | 25 | ```code 26 | lang: jsx 27 | --- 28 | import React from 'react'; 29 | 30 | class ExampleComponent extends React.Component { 31 | render() { 32 | return
...
; 33 | } 34 | } 35 | ``` 36 | 37 | ````raw-code 38 | ```code 39 | lang: jsx 40 | --- 41 | import React from 'react'; 42 | 43 | class ExampleComponent extends React.Component { 44 | render() { 45 | return
...
; 46 | } 47 | } 48 | ``` 49 | ```` 50 | 51 | #### Collapsed Code Block 52 | 53 | ```code 54 | collapsed: true 55 | --- 56 |
Hello, collapsed Code Specimen
57 | ``` 58 | 59 | ```` 60 | ```code 61 | collapsed: true 62 | --- 63 |
Hello, collapsed Code Specimen
64 | ``` 65 | ```` 66 | -------------------------------------------------------------------------------- /docs/specimens/color-palette.md: -------------------------------------------------------------------------------- 1 | > A Color Palette groups a sequence of colors into a block. Ideal for color scales. 2 | 3 | ### Props 4 | 5 | - __`colors: array`__ colors of the palette 6 | - __`value: string`__ defines the color value 7 | - `name: string` defines the color name 8 | - `horizontal: boolean` generates a horizontal list 9 | - `span: number[1–6]` width of the specimen 10 | 11 | ### Examples 12 | 13 | #### Regular color palette 14 | 15 | Color palettes can be used for sets that contain more colors. For example to document gradient steps or color schemes. 16 | 17 | ```color-palette 18 | colors: 19 | - {name: "100", value: "#e3f1fc"} 20 | - {name: "200", value: "#c2d8ea"} 21 | - {name: "300", value: "#a1c0d8"} 22 | - {name: "400", value: "#80a8c6"} 23 | ``` 24 | 25 | ````code|lang-javascript 26 | ```color-palette 27 | colors: 28 | - {name: "100", value: "#e3f1fc"} 29 | - {name: "200", value: "#c2d8ea"} 30 | - {name: "300", value: "#a1c0d8"} 31 | - {name: "400", value: "#80a8c6"} 32 | ``` 33 | ```` 34 | 35 | #### Horizontal color palette 36 | 37 | displaying a diverging color scale 38 | 39 | 40 | ```color-palette|horizontal 41 | colors: 42 | - {value: "#543005"} 43 | - {value: "#8c510a"} 44 | - {value: "#bf812d"} 45 | - {value: "#dfc27d"} 46 | - {value: "#f6e8c3"} 47 | - {value: "#c7eae5"} 48 | - {value: "#80cdc1"} 49 | - {value: "#35978f"} 50 | - {value: "#01665e"} 51 | - {value: "#003c30"} 52 | ``` 53 | 54 | ````code|lang-javascript 55 | ```color-palette|horizontal 56 | colors: 57 | - {value: "#543005"} 58 | - {value: "#8c510a"} 59 | - {value: "#bf812d"} 60 | - {value: "#dfc27d"} 61 | - {value: "#f6e8c3"} 62 | - {value: "#c7eae5"} 63 | - {value: "#80cdc1"} 64 | - {value: "#35978f"} 65 | - {value: "#01665e"} 66 | - {value: "#003c30"} 67 | ``` 68 | ```` 69 | 70 | 71 | #### Multiple columns 72 | 73 | 74 | ```color-palette|span-2 75 | colors: 76 | - {value: "#ffff00"} 77 | - {value: "#ffff22"} 78 | - {value: "#ffff44"} 79 | - {value: "#ffff66"} 80 | - {value: "#ffff88"} 81 | - {value: "#ffffaa"} 82 | - {value: "#ffffcc"} 83 | - {value: "#ffffee"} 84 | ``` 85 | ```color-palette|span-2 86 | colors: 87 | - {value: "#ff00ff"} 88 | - {value: "#ff22ff"} 89 | - {value: "#ff44ff"} 90 | - {value: "#ff66ff"} 91 | - {value: "#ff88ff"} 92 | - {value: "#ffaaff"} 93 | - {value: "#ffccff"} 94 | - {value: "#ffeeff"} 95 | ``` 96 | ```color-palette|span-2 97 | colors: 98 | - {value: "#00ffff"} 99 | - {value: "#22ffff"} 100 | - {value: "#44ffff"} 101 | - {value: "#66ffff"} 102 | - {value: "#88ffff"} 103 | - {value: "#aaffff"} 104 | - {value: "#ccffff"} 105 | - {value: "#eeffff"} 106 | ``` 107 | 108 | ````code|collapsed,lang-javascript 109 | ```color-palette|span-2 110 | colors: 111 | - {value: "#ffff00"} 112 | - {value: "#ffff22"} 113 | - {value: "#ffff44"} 114 | - {value: "#ffff66"} 115 | - {value: "#ffff88"} 116 | - {value: "#ffffaa"} 117 | - {value: "#ffffcc"} 118 | - {value: "#ffffee"} 119 | ``` 120 | ```color-palette|span-2 121 | colors: 122 | - {value: "#ff00ff"} 123 | - {value: "#ff22ff"} 124 | - {value: "#ff44ff"} 125 | - {value: "#ff66ff"} 126 | - {value: "#ff88ff"} 127 | - {value: "#ffaaff"} 128 | - {value: "#ffccff"} 129 | - {value: "#ffeeff"} 130 | ``` 131 | ```color-palette|span-2 132 | colors: 133 | - {value: "#00ffff"} 134 | - {value: "#22ffff"} 135 | - {value: "#44ffff"} 136 | - {value: "#66ffff"} 137 | - {value: "#88ffff"} 138 | - {value: "#aaffff"} 139 | - {value: "#ccffff"} 140 | - {value: "#eeffff"} 141 | ``` 142 | ```` 143 | -------------------------------------------------------------------------------- /docs/specimens/color.md: -------------------------------------------------------------------------------- 1 | > The Color Specimen is used to document colors. 2 | 3 | ### Props 4 | 5 | - __`value: string`__ defines the color 6 | - `name: string` defines the color name 7 | - `span: number[1–6]` width of the specimen 8 | 9 | ### Examples 10 | 11 | #### Color swatch 12 | 13 | The color swatches are useful to document single or important colors like the main brand scheme. 14 | 15 | ```color 16 | span: 3 17 | name: "Light Blue" 18 | value: "#b0f6ff" 19 | ``` 20 | 21 | ```color 22 | span: 2 23 | name: "Dark Blue" 24 | value: "#2666a4" 25 | ``` 26 | 27 | ```color 28 | span: 1 29 | name: "Bright Red" 30 | value: "#ff5555" 31 | ``` 32 | 33 | ````code 34 | ```color 35 | span: 3 36 | name: "Light Blue" 37 | value: "#b0f6ff" 38 | ``` 39 | 40 | ```color 41 | span: 2 42 | name: "Dark Blue" 43 | value: "#2666a4" 44 | ``` 45 | 46 | ```color 47 | span: 1 48 | name: "Bright Red" 49 | value: "#ff5555" 50 | ``` 51 | ```` 52 | 53 | 54 | ### Color palette 55 | 56 | Catalog also has a [Color Palette Specimen](/specimens/color-palette). 57 | -------------------------------------------------------------------------------- /docs/specimens/download.md: -------------------------------------------------------------------------------- 1 | > To link downloadable style guide resources, use the Download Specimen 2 | 3 | ### Props 4 | - __`url: string`__ The URL pointing to the file 5 | - __`title: string`__ The title for the button 6 | - `filename: string` Changes the file name under which it will be saved 7 | - `subtitle: string` For example the file size 8 | - `span: number[1–6]` width of the specimen 9 | 10 | ### Examples 11 | 12 | #### Basic example 13 | 14 | ```download 15 | title: Catalog Logo (.svg) 16 | subtitle: 8 KB 17 | url: /assets/catalog_logo.svg 18 | ``` 19 | 20 | ````code 21 | ```download 22 | title: Catalog Logo (.svg) 23 | subtitle: 8 KB 24 | url: /assets/catalog_logo.svg 25 | ``` 26 | ```` 27 | 28 | #### Different widths 29 | 30 | ```download|span-6 31 | { 32 | "title": "Catalog Logo (.svg)", 33 | "filename": "catalog-logo", 34 | "subtitle": "8 KB", 35 | "url": "/assets/catalog_logo.svg" 36 | } 37 | ``` 38 | ```download|span-3 39 | { 40 | "title": "Catalog Logo (.svg)", 41 | "filename": "catalog-logo", 42 | "subtitle": "8 KB", 43 | "url": "/assets/catalog_logo.svg" 44 | } 45 | ``` 46 | ```download|span-3 47 | { 48 | "title": "Catalog Logo with a veeerrryy long title (.svg)", 49 | "filename": "catalog-logo", 50 | "subtitle": "8 KB", 51 | "url": "/assets/catalog_logo.svg" 52 | } 53 | ``` 54 | ```download|span-2 55 | { 56 | "title": "Catalog Logo (.svg)", 57 | "filename": "catalog-logo", 58 | "subtitle": "8 KB", 59 | "url": "/assets/catalog_logo.svg" 60 | } 61 | ``` 62 | ```download|span-2 63 | { 64 | "title": "Catalog Logo (.svg)", 65 | "filename": "catalog-logo", 66 | "subtitle": "8 KB", 67 | "url": "/assets/catalog_logo.svg" 68 | } 69 | ``` 70 | ```download|span-2 71 | { 72 | "title": "Catalog Logo (.svg)", 73 | "filename": "catalog-logo", 74 | "subtitle": "8 KB", 75 | "url": "/assets/catalog_logo.svg" 76 | } 77 | ``` 78 | 79 | 80 | ````code|collapsed 81 | ```download|span-6 82 | { 83 | "title": "Catalog Logo (.svg)", 84 | "filename": "catalog-logo", 85 | "subtitle": "8 KB", 86 | "url": "/assets/catalog_logo.svg" 87 | } 88 | ``` 89 | ```download|span-3 90 | { 91 | "title": "Catalog Logo (.svg)", 92 | "filename": "catalog-logo", 93 | "subtitle": "8 KB", 94 | "url": "/assets/catalog_logo.svg" 95 | } 96 | ``` 97 | ```download|span-3 98 | { 99 | "title": "Catalog Logo (.svg)", 100 | "filename": "catalog-logo", 101 | "subtitle": "8 KB", 102 | "url": "/assets/catalog_logo.svg" 103 | } 104 | ``` 105 | ```download|span-2 106 | { 107 | "title": "Catalog Logo (.svg)", 108 | "filename": "catalog-logo", 109 | "subtitle": "8 KB", 110 | "url": "/assets/catalog_logo.svg" 111 | } 112 | ``` 113 | ```download|span-2 114 | { 115 | "title": "Catalog Logo (.svg)", 116 | "filename": "catalog-logo", 117 | "subtitle": "8 KB", 118 | "url": "/assets/catalog_logo.svg" 119 | } 120 | ``` 121 | ```download|span-2 122 | { 123 | "title": "Catalog Logo with a veeerrryy long title (.svg)", 124 | "filename": "catalog-logo", 125 | "subtitle": "8 KB", 126 | "url": "/assets/catalog_logo.svg" 127 | } 128 | ``` 129 | ```` 130 | 131 | -------------------------------------------------------------------------------- /docs/specimens/hint.md: -------------------------------------------------------------------------------- 1 | > The Hint Specimen highlights important aspects. 2 | 3 | ### Props 4 | 5 | - `directive: boolean` good for _dos_ 6 | - `warning: boolean` good for _don'ts_ 7 | - `neutral: boolean` a neutral style 8 | - `span: number[1–6]` width of the specimen 9 | 10 | ### Examples 11 | 12 | ```hint 13 | Make sure to use `text-rendering: optimizeLegibility;` on fonts over 36px, as well as `-webkit-font-smoothing: antialiased;` and `-moz-osx-font-smoothing: grayscale;` on dark backgrounds. 14 | ``` 15 | 16 | ````code 17 | ```hint 18 | Make sure to use `text-rendering: optimizeLegibility;`on fonts over 36px, as well as `-webkit-font-smoothing: antialiased;` and `-moz-osx-font-smoothing: grayscale;` on dark backgrounds. 19 | ``` 20 | ```` 21 | 22 | ```hint|directive,span-4 23 | Make it so! 24 | ``` 25 | 26 | ````code|span-2 27 | ```hint|directive 28 | Make it so! 29 | ``` 30 | ```` 31 | 32 | ```hint|warning,span-4 33 | No **stairway**. 34 | ``` 35 | 36 | ````code|span-2 37 | ```hint|warning 38 | No **stairway**. 39 | ``` 40 | ```` 41 | 42 | ```hint|neutral,span-4 43 | A neutral hint. 44 | ``` 45 | 46 | ````code|span-2 47 | ```hint|neutral 48 | A neutral hint. 49 | ``` 50 | ```` 51 | 52 | ```hint|span-4 53 | # Supports 54 | 55 | even Markdown with [links](http://example.com) 56 | 57 | - and 58 | - lists 59 | ``` 60 | 61 | ````code|span-2 62 | ```hint 63 | # Supports 64 | 65 | even Markdown with [links](http://example.com) 66 | 67 | - and 68 | - lists 69 | ``` 70 | ```` 71 | -------------------------------------------------------------------------------- /docs/specimens/table.md: -------------------------------------------------------------------------------- 1 | > Use the table specimen to generate simple tables. 2 | 3 | This specimen provides an easy way to create tabular content. Table rows are defined using YAML arrays. Cell contents can be formatted using Markdown syntax. 4 | 5 | ### Props 6 | 7 | * **`rows: array`** An array of objects where the property name is the column name and the value is the cell's content 8 | * `columns: array` An array of column names. The order describes the column order from left to right. Columns can be hidden by omitting them from this array 9 | 10 | ### Examples 11 | 12 | #### Basic example 13 | 14 | ```table 15 | span: 3 16 | rows: 17 | - Name: Jemaine 18 | Instrument: Bass among other things 19 | - Name: Bret 20 | Instrument: Guitar and piano 21 | ``` 22 | 23 | ````code|span-3 24 | ```table 25 | span: 3 26 | rows: 27 | - Name: Jemaine 28 | Instrument: Bass among other things 29 | - Name: Bret 30 | Instrument: Guitar and piano 31 | ``` 32 | ```` 33 | 34 | #### Markdown formatting 35 | 36 | This example uses [Markdown](https://daringfireball.net/projects/markdown/) to format the cell contents. Note that you have to use quotation marks around the cells for this to work. 37 | 38 | ```table 39 | rows: 40 | - Term: '**Catalog**' 41 | Definition: '_n._ A list or itemized display, as of titles, course offerings, or articles for exhibition or sale, usually including descriptive information or illustrations.' 42 | - Term: '' 43 | Definition: '_n._ A publication, such as a book or pamphlet, containing such a list or display: a catalog of fall fashions; a seed catalog.' 44 | - Term: '**Thing**' 45 | Definition: '_n._ An entity, an idea, or a quality perceived, known, or thought to have its own existence.' 46 | - Term: '' 47 | Definition: '_n._ The real or concrete substance of an entity.' 48 | ``` 49 | 50 | ````code 51 | ```table 52 | rows: 53 | - Term: '**Catalog**' 54 | Definition: '_n._ A list or itemized display, …' 55 | - Term: '' 56 | Definition: '_n._ A publication, such as a book …' 57 | - Term: '**Thing**' 58 | Definition: '_n._ An entity, an idea, or a quality …' 59 | - Term: '' 60 | Definition: '_n._ The real or concrete substance of …' 61 | ``` 62 | ```` 63 | 64 | 65 | #### Missing data, ordering and hiding columns 66 | 67 | In this example, several things happen: 68 | 69 | * Not all rows have the column "Status" defined. This column is added anyway, but cells without data are marked as empty. 70 | * The `columns` property is used to specify the order of the columns. For this example, we want to display the "Status" column last because it can sometimes be empty. 71 | * By omitting the "ID" column name from the `columns` property we hide it. 72 | 73 | ```table 74 | span: 3 75 | columns: 76 | - Name 77 | - Value 78 | - Status 79 | rows: 80 | - Status: 'running' 81 | ID: ID-1 82 | Value: 100 83 | Name: A 84 | - ID: ID-2 85 | Value: 200 86 | Name: B 87 | ``` 88 | 89 | ````code|span-3 90 | ```table 91 | span: 3 92 | columns: 93 | - Name 94 | - Value 95 | - Status 96 | rows: 97 | - Status: 'running' 98 | ID: ID-1 99 | Value: 100 100 | Name: A 101 | - ID: ID-2 102 | Value: 200 103 | Name: B 104 | ``` 105 | ```` 106 | -------------------------------------------------------------------------------- /docs/specimens/video.md: -------------------------------------------------------------------------------- 1 | > The video specimen can be used to embed videos. It can be used to document design specifications that are done in an animation program or highlight behaviors through a screencast. 2 | 3 | ### Props 4 | 5 | - __`src: string`__ the url pointing to the source file 6 | - `muted: boolean ` if true, the sound of the video clip is muted 7 | - `autoplay: boolean` if true, the video clip plays without interaction 8 | - `loop: boolean ` if true, the video clip plays repeatedly 9 | - `span: number[1–6]` width of the specimen 10 | 11 | ### Example 12 | 13 | ```video 14 | src: '/assets/video.m4v' 15 | ``` 16 | 17 | ````code|lang-javascript 18 | ```video 19 | src: '/assets/video.m4v' 20 | ``` 21 | ```` 22 | -------------------------------------------------------------------------------- /docs/static/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | RewriteBase / 4 | RewriteRule ^index\.html$ - [L] 5 | RewriteCond %{REQUEST_FILENAME} !-f 6 | RewriteCond %{REQUEST_FILENAME} !-d 7 | RewriteRule . /docs/index.html [L] 8 | -------------------------------------------------------------------------------- /docs/static/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /docs/static/assets/cat_favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/cat_favicon-32.png -------------------------------------------------------------------------------- /docs/static/assets/catalog_logo--icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/catalog_logo--icon.png -------------------------------------------------------------------------------- /docs/static/assets/catalog_logo--icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 1 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/static/assets/catalog_logo--white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/catalog_logo--white.png -------------------------------------------------------------------------------- /docs/static/assets/catalog_logo-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/catalog_logo-overlay.png -------------------------------------------------------------------------------- /docs/static/assets/catalog_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/catalog_logo.png -------------------------------------------------------------------------------- /docs/static/assets/gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/gradient.png -------------------------------------------------------------------------------- /docs/static/assets/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/image.jpg -------------------------------------------------------------------------------- /docs/static/assets/image_bw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/image_bw.jpg -------------------------------------------------------------------------------- /docs/static/assets/sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/sound.mp3 -------------------------------------------------------------------------------- /docs/static/assets/video.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/docs/static/assets/video.m4v -------------------------------------------------------------------------------- /docs/static/coming-soon.md: -------------------------------------------------------------------------------- 1 | Coming soon … stay tuned! -------------------------------------------------------------------------------- /docs/static/example-style.css: -------------------------------------------------------------------------------- 1 | .button { 2 | font-family:sans-serif; 3 | border-radius: 5px; 4 | background:#ccc; 5 | color:#555; 6 | text-align: center; 7 | padding:20px; 8 | cursor: pointer; 9 | transition: .2s background; 10 | } 11 | .button:hover{ 12 | background: #ddd; 13 | } 14 | -------------------------------------------------------------------------------- /docs/static/test/test.css: -------------------------------------------------------------------------------- 1 | /* Example Styles */ 2 | .button, .important-button { 3 | border-radius: 3px; 4 | display: inline-block; 5 | font-family: sans-serif; 6 | font-size: 12px; 7 | padding: 4px 8px; 8 | text-decoration: none; 9 | text-transform: uppercase; 10 | } 11 | .button { 12 | background: #2666a4; 13 | color: #ffffff; 14 | } 15 | .button.button--disabled { 16 | background: #a1afbc; 17 | color: #727c86; 18 | } 19 | .button.button--inverted { 20 | background: #ffffff; 21 | color: #2666a4; 22 | } 23 | .important-button { 24 | background: #a80050; 25 | color: #fff; 26 | } 27 | 28 | .cg-Catalog:before, .cg-Catalog:after { 29 | content: ""; 30 | position: absolute; 31 | top: 0; 32 | left: 0; 33 | bottom: 0; 34 | right: 0; 35 | width: 100%; 36 | display: none; 37 | } 38 | 39 | .cg-Catalog:before { 40 | z-index: 10; 41 | background-image: -webkit-linear-gradient(rgba(233, 0, 0, 0.2) 1px, rgba(0, 0, 0, 0) 1px); 42 | background-image: linear-gradient(rgba(233, 0, 0, 0.2) 1px, rgba(0, 0, 0, 0) 1px); 43 | background-size: 100% 28px; 44 | } 45 | 46 | .cg-Catalog:after { 47 | z-index: 9; 48 | background-image: -webkit-linear-gradient(rgba(0, 233, 0, 0.2) 1px, rgba(0, 0, 0, 0) 1px); 49 | background-image: linear-gradient(rgba(0, 233, 0, 0.2) 1px, rgba(0, 0, 0, 0) 1px); 50 | background-size: 100% 4px; 51 | } 52 | 53 | .cg-Catalog.va-debug:before, .cg-Catalog.va-debug:after { 54 | display: block; 55 | pointer-events: none; 56 | } 57 | -------------------------------------------------------------------------------- /docs/static/test/test.js: -------------------------------------------------------------------------------- 1 | // Test the baseline grid by pressing the "g" key when on the 2 | // test page. It will toggle a class on the body and show 3 | // a baseline grid. 4 | 5 | if (!window.cgToggleBaselineGrid) { 6 | window.cgToggleBaselineGrid = function(evt) { 7 | if (evt.keyCode === 71) { 8 | // "g" key to toggle grid 9 | document.body.classList.toggle("va-debug"); 10 | } 11 | }; 12 | } 13 | 14 | document.body.removeEventListener("keydown", window.cgToggleBaselineGrid); 15 | document.body.addEventListener("keydown", window.cgToggleBaselineGrid); 16 | -------------------------------------------------------------------------------- /docs/test/foo.module.css: -------------------------------------------------------------------------------- 1 | .red { 2 | background: red; 3 | padding: 10px; 4 | } 5 | -------------------------------------------------------------------------------- /docs/test/foo.module.scss: -------------------------------------------------------------------------------- 1 | $white: #fff; 2 | .white { 3 | color: $white; 4 | } 5 | -------------------------------------------------------------------------------- /docs/test/testtemplate.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { markdown, ReactSpecimen } from "@catalog/core"; 3 | import logo from "../catalog_logo.svg"; 4 | 5 | import styles from "./foo.module.css"; 6 | import styles2 from "./foo.module.scss"; 7 | 8 | export default () => markdown` 9 | # Yo yo 10 | 11 | This is a markdown template literal 12 | 13 | ~~~image 14 | src: ${logo} 15 | title: Neat! 16 | ~~~ 17 | 18 | ~~~image 19 | src: "/assets/image.jpg" 20 | title: Neat! 21 | ~~~ 22 | 23 | Super nice stuff here! 24 | 25 | Foo bar 26 | 27 | ${["foo", "bar"].map(d => [ 28 | `### ${d}`, 29 | 30 |
{d}
31 |
32 | ])} 33 | 34 | `; 35 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "strict": true, 7 | "jsx": "preserve", 8 | "lib": ["es2018", "dom"] 9 | }, 10 | "include": ["./**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4.0.1-canary.2", 3 | "packages": [ 4 | "docs", 5 | "packages/*" 6 | ], 7 | "npmClient": "yarn", 8 | "useWorkspaces": true, 9 | "command": { 10 | "publish": { 11 | "ignoreChanges": [ 12 | "examples/**", 13 | "Makefile" 14 | ], 15 | "npmClient": "npm", 16 | "registry": "https://registry.npmjs.org/" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "docs/build" 3 | command = "yarn build" 4 | 5 | [build.environment] 6 | NODE_VERSION = "12" 7 | YARN_VERSION = "1.12.1" 8 | YARN_FLAGS = "--frozen-lockfile" 9 | 10 | [[redirects]] 11 | from = "/*" 12 | to = "/index.html" 13 | status = 200 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "bootstrap": "lerna bootstrap", 5 | "dev": "run-p -l dev:*", 6 | "dev:lib": "rollup -c rollup.config.js --watch", 7 | "dev:doc": "yarn workspace @catalog/docs run dev", 8 | "build": "run-s -n build:*", 9 | "build:lib": "rollup -c rollup.config.js", 10 | "build:standalone": "yarn workspace @catalog/standalone run build", 11 | "build:doc": "yarn workspace @catalog/docs run build", 12 | "test": "run-s -n test:*", 13 | "test:types": "tsc --build", 14 | "test:jest": "jest", 15 | "version:canary": "run-s -n test version:lerna-canary", 16 | "version:lerna-canary": "lerna version prerelease --preid=canary", 17 | "publish:canary": "scripts/publish-canary.sh" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.1.2", 21 | "@babel/plugin-syntax-jsx": "^7.0.0", 22 | "@babel/plugin-syntax-object-rest-spread": "^7.0.0", 23 | "@babel/polyfill": "^7.0.0", 24 | "@babel/preset-env": "^7.1.0", 25 | "@babel/preset-react": "^7.0.0", 26 | "@babel/preset-typescript": "^7.1.0", 27 | "@types/express": "^4.16.0", 28 | "@types/jest": "^24.0.0", 29 | "@types/loader-utils": "^1.1.3", 30 | "@types/node": "^10.12.1", 31 | "@types/react": "^16.4.18", 32 | "@types/react-dom": "^16.0.9", 33 | "@types/source-map": "^0.5.7", 34 | "babel-core": "^7.0.0-bridge.0", 35 | "babel-eslint": "^10.0.1", 36 | "babel-jest": "^24.0.0", 37 | "babel-plugin-emotion": "^10.0.13", 38 | "depcheck": "^0.6.11", 39 | "eslint": "^5.8.0", 40 | "eslint-config-prettier": "^5.0.0", 41 | "eslint-plugin-react": "^7.11.1", 42 | "jest": "^24.0.0", 43 | "lerna": "^3.4.3", 44 | "npm-run-all": "^4.1.5", 45 | "release": "^6.0.0", 46 | "rollup": "^1.1.2", 47 | "rollup-plugin-babel": "^4.0.3", 48 | "rollup-plugin-commonjs": "^10.0.0", 49 | "rollup-plugin-node-resolve": "^5.0.3", 50 | "rollup-plugin-replace": "^2.1.0", 51 | "rollup-plugin-terser": "^5.0.0", 52 | "tslib": "^1.9.3", 53 | "typescript": "^3.1.4" 54 | }, 55 | "jest": { 56 | "roots": [ 57 | "/packages/" 58 | ], 59 | "testPathIgnorePatterns": [ 60 | "/packages/core/types/", 61 | "/packages/.*/lib/" 62 | ] 63 | }, 64 | "workspaces": [ 65 | "packages/*", 66 | "docs" 67 | ], 68 | "resolutions": { 69 | "prop-types": "^15.7.2", 70 | "react-is": "^16.12.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/babel-preset/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@catalog/babel-preset", 3 | "version": "4.0.1-canary.2", 4 | "description": "Babel preset for Catalog", 5 | "homepage": "https://www.catalog.style/", 6 | "bugs": "https://github.com/interactivethings/catalog/issues", 7 | "main": "dist/index.js", 8 | "files": [ 9 | "dist" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/interactivethings/catalog.git" 14 | }, 15 | "license": "BSD-3-Clause", 16 | "publishConfig": { 17 | "access": "public" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/babel-preset/rollup.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WARNING: Don't use rollup's ES import/export here because otherwise file paths won't resolve correctly 3 | */ 4 | const babel = require("rollup-plugin-babel"); 5 | const resolve = require("rollup-plugin-node-resolve"); 6 | const path = require("path"); 7 | 8 | const extensions = [".js", ".jsx", ".ts", ".tsx"]; 9 | 10 | module.exports = { 11 | input: path.resolve(__dirname, "src/index.ts"), 12 | plugins: [ 13 | resolve({ 14 | extensions 15 | }), 16 | babel({ 17 | extensions 18 | }) 19 | ], 20 | output: [ 21 | { 22 | dir: path.resolve(__dirname, "dist/"), 23 | entryFileNames: "[name].js", 24 | format: "cjs", 25 | sourcemap: true 26 | } 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /packages/babel-preset/src/ambient.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@babel/core"; 2 | -------------------------------------------------------------------------------- /packages/babel-preset/src/index.ts: -------------------------------------------------------------------------------- 1 | import reactspecimensource from "./reactspecimen-source"; 2 | 3 | export default () => ({ 4 | plugins: [reactspecimensource] 5 | }); 6 | -------------------------------------------------------------------------------- /packages/babel-preset/src/reactspecimen-source.test.ts: -------------------------------------------------------------------------------- 1 | import { transform } from "@babel/core"; 2 | import plugin from "./reactspecimen-source"; 3 | 4 | const input = ` 5 |
6 | foo 7 |
8 |
`; 9 | 10 | const output = `"use strict"; 11 | 12 | React.createElement(ReactSpecimen, { 13 | sourceText: "
\\n foo\\n
" 14 | }, React.createElement("div", null, "foo"));`; 15 | 16 | test("Adds sourceText prop", () => { 17 | expect( 18 | transform(input, { 19 | plugins: ["@babel/plugin-syntax-jsx", plugin], 20 | filename: "test.js" 21 | }).code 22 | ).toBe(output); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/babel-preset/src/reactspecimen-source.ts: -------------------------------------------------------------------------------- 1 | // From package "strip-indent" 2 | const stripIndent = (str: string): string => { 3 | const match = str.match(/^[ \t]*(?=\S)/gm); 4 | 5 | if (!match) { 6 | return str; 7 | } 8 | 9 | const indent = Math.min(...match.map(x => x.length)); 10 | const re = new RegExp(`^[ \\t]{${indent}}`, "gm"); 11 | 12 | return indent > 0 ? str.replace(re, "") : str; 13 | }; 14 | 15 | export default function({ types: t }: { types: any }) { 16 | return { 17 | visitor: { 18 | JSXElement(path: any, state: any) { 19 | if (path.node.openingElement.name.name === "ReactSpecimen") { 20 | if (path.node.openingElement.selfClosing) { 21 | return; 22 | } 23 | 24 | const attributes = path.node.openingElement.attributes; 25 | 26 | for (let i = 0; i < attributes.length; i++) { 27 | const name = attributes[i].name; 28 | if (name && name.name === "sourceText") { 29 | // The sourceText attibute already exists 30 | return; 31 | } 32 | } 33 | 34 | const sourceText = state.file.code.substring( 35 | path.node.openingElement.end, 36 | path.node.closingElement.start 37 | ); 38 | 39 | path.replaceWith( 40 | t.jSXElement( 41 | t.jSXOpeningElement( 42 | path.node.openingElement.name, 43 | [ 44 | ...path.node.openingElement.attributes, 45 | t.jSXAttribute( 46 | t.jSXIdentifier("sourceText"), 47 | t.jSXExpressionContainer( 48 | t.stringLiteral(stripIndent(sourceText).trim()) 49 | ) 50 | ) 51 | ], 52 | false 53 | ), 54 | path.node.closingElement, 55 | path.node.children, 56 | false 57 | ) 58 | ); 59 | } 60 | } 61 | } 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /packages/babel-preset/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/catalog/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "@catalog/core"; 2 | -------------------------------------------------------------------------------- /packages/catalog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catalog", 3 | "version": "4.0.1-canary.2", 4 | "main": "dist/index.js", 5 | "module": "dist/index.es.js", 6 | "dependencies": { 7 | "@catalog/core": "^4.0.1-canary.2" 8 | }, 9 | "files": [ 10 | "dist", 11 | "index.d.ts" 12 | ], 13 | "types": "index.d.ts" 14 | } 15 | -------------------------------------------------------------------------------- /packages/catalog/rollup.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WARNING: Don't use rollup's ES import/export here because otherwise file paths won't resolve correctly 3 | */ 4 | const babel = require("rollup-plugin-babel"); 5 | const resolve = require("rollup-plugin-node-resolve"); 6 | const path = require("path"); 7 | 8 | const extensions = [".js", ".jsx", ".ts", ".tsx"]; 9 | 10 | module.exports = { 11 | input: path.resolve(__dirname, "src/index.ts"), 12 | external: ["@catalog/core"], 13 | plugins: [ 14 | resolve({ 15 | extensions 16 | }), 17 | babel({ 18 | extensions 19 | }) 20 | ], 21 | output: [ 22 | { 23 | dir: path.resolve(__dirname, "dist/"), 24 | entryFileNames: "[name].js", 25 | format: "cjs", 26 | sourcemap: true 27 | }, 28 | { 29 | dir: path.resolve(__dirname, "dist/"), 30 | entryFileNames: "[name].es.js", 31 | format: "es", 32 | sourcemap: true 33 | } 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /packages/catalog/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "@catalog/core"; 2 | -------------------------------------------------------------------------------- /packages/catalog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@catalog/cli", 3 | "version": "4.0.1-canary.2", 4 | "description": "Commandline tool for Catalog", 5 | "keywords": [ 6 | "styleguide", 7 | "style guide", 8 | "docs", 9 | "documentation", 10 | "react", 11 | "markdown" 12 | ], 13 | "homepage": "https://www.catalog.style/", 14 | "bugs": "https://github.com/interactivethings/catalog/issues", 15 | "bin": { 16 | "catalog": "dist/bin/catalog.js", 17 | "catalog-start": "dist/bin/catalog-start.js", 18 | "catalog-build": "dist/bin/catalog-build.js" 19 | }, 20 | "files": [ 21 | "dist", 22 | "setup-template" 23 | ], 24 | "dependencies": { 25 | "@babel/core": "^7.2.2", 26 | "@catalog/babel-preset": "^4.0.1-canary.2", 27 | "@catalog/core": "^4.0.1-canary.2", 28 | "@catalog/markdown-loader": "^4.0.1-canary.2", 29 | "args": "^5.0.0", 30 | "babel-loader": "^8.0.5", 31 | "babel-preset-react-app": "^9.0.0", 32 | "chalk": "^2.1.0", 33 | "css-loader": "^2.1.1", 34 | "express": "^4.16.3", 35 | "file-loader": "^4.0.0", 36 | "friendly-errors-webpack-plugin": "^1.6.1", 37 | "html-webpack-plugin": "^4.0.0-beta.5", 38 | "mini-css-extract-plugin": "^0.7.0", 39 | "postcss-flexbugs-fixes": "^4.1.0", 40 | "postcss-loader": "^3.0.0", 41 | "postcss-preset-env": "^6.5.0", 42 | "react-app-polyfill": "^1.0.1", 43 | "react-dev-utils": "^9.0.1", 44 | "sander": "^0.6.0", 45 | "sass-loader": "^7.1.0", 46 | "style-loader": "^0.23.1", 47 | "terser-webpack-plugin": "^1.2.2", 48 | "url-loader": "^2.0.0", 49 | "webpack": "^4.29.1", 50 | "webpack-dev-server": "^3.1.14", 51 | "webpack-manifest-plugin": "^2.0.4" 52 | }, 53 | "devDependencies": { 54 | "@types/html-minifier": "^3.5.3", 55 | "@types/mini-css-extract-plugin": "^0.2.0", 56 | "@types/node-fetch": "^2.1.4", 57 | "@types/webpack": "^4.41.2", 58 | "@types/webpack-dev-server": "^3.1.1" 59 | }, 60 | "repository": { 61 | "type": "git", 62 | "url": "https://github.com/interactivethings/catalog.git" 63 | }, 64 | "license": "BSD-3-Clause", 65 | "publishConfig": { 66 | "access": "public" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/cli/rollup.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WARNING: Don't use rollup's ES import/export here because otherwise file paths won't resolve correctly 3 | */ 4 | const babel = require("rollup-plugin-babel"); 5 | const resolve = require("rollup-plugin-node-resolve"); 6 | const path = require("path"); 7 | const fs = require("fs"); 8 | 9 | const pkg = require("./package.json"); 10 | 11 | const extensions = [".js", ".jsx", ".ts", ".tsx"]; 12 | 13 | const externals = [ 14 | ...Object.keys(pkg.dependencies || {}), 15 | ...Object.keys(pkg.peerDependencies || {}), 16 | "url", 17 | "fs", 18 | "path" 19 | ]; 20 | 21 | module.exports = { 22 | input: [ 23 | path.resolve(__dirname, "src/bin/catalog.ts"), 24 | path.resolve(__dirname, "src/bin/catalog-start.ts"), 25 | path.resolve(__dirname, "src/bin/catalog-build.ts") 26 | ], 27 | external: id => externals.some(d => id.startsWith(d)), 28 | plugins: [ 29 | resolve({ 30 | extensions 31 | }), 32 | babel({ 33 | extensions 34 | }), 35 | // Make entries executable 36 | { 37 | writeBundle(bundle) { 38 | for (let d of Object.values(bundle)) { 39 | if (d.isEntry) { 40 | const filePath = path.resolve(__dirname, "dist/bin", d.fileName); 41 | const mode = fs.statSync(filePath).mode; 42 | fs.chmodSync(filePath, mode | 0o100); // Same as `chmod u+x` 43 | } 44 | } 45 | } 46 | } 47 | ], 48 | output: [ 49 | { 50 | dir: path.resolve(__dirname, "dist/bin/"), 51 | entryFileNames: "[name].js", 52 | format: "cjs", 53 | banner: "#!/usr/bin/env node", 54 | sourcemap: true 55 | } 56 | ] 57 | }; 58 | -------------------------------------------------------------------------------- /packages/cli/setup-template/WELCOME.md: -------------------------------------------------------------------------------- 1 | ```image 2 | src: catalog_logo.svg 3 | plain: true 4 | ``` 5 | 6 | Hi! 7 | 8 | Welcome to your freshly set up Catalog. To get started immediately, check out the `catalog/` directory (or wherever you've set it up). 9 | 10 | - `WELCOME.md`: This Markdown document 11 | - `index.js`: The entry file to start Catalog. Change configuration and add pages here. 12 | - `index.html`: The HTML document which gets served. Usually there's no need to edit this unless for example you want to load a custom font. 13 | - `static/`: A directory with files that are served statically. For example the Catalog logo above. 14 | 15 | For more details about how to use Catalog, check out the [documentation](https://docs.catalog.style/). -------------------------------------------------------------------------------- /packages/cli/setup-template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Catalog 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /packages/cli/setup-template/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { Catalog, pageLoader } from "@catalog/core"; 4 | 5 | const pages = [ 6 | { 7 | path: "/", 8 | title: "Welcome", 9 | content: pageLoader(() => import("./WELCOME.md")) 10 | } 11 | ]; 12 | 13 | ReactDOM.render( 14 | , 15 | document.getElementById("catalog") 16 | ); 17 | -------------------------------------------------------------------------------- /packages/cli/setup-template/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interactivethings/catalog/070137b8186a44bacb9521a5d660b76d8cea6912/packages/cli/setup-template/static/favicon.png -------------------------------------------------------------------------------- /packages/cli/src/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off" 4 | } 5 | } -------------------------------------------------------------------------------- /packages/cli/src/actions/loadConfigFile.ts: -------------------------------------------------------------------------------- 1 | import { resolveAppPath } from "../utils/paths"; 2 | import { exists } from "sander"; 3 | 4 | type ConfigFile = { 5 | webpack?: Function, 6 | useBabelrc?: boolean 7 | } | null; 8 | 9 | export default async (): Promise => { 10 | const configFilePath = resolveAppPath("catalog.config.js"); 11 | const configFileExists = await exists(configFilePath); 12 | return configFileExists ? require(configFilePath) : null; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/cli/src/actions/loadPaths.ts: -------------------------------------------------------------------------------- 1 | import { exists } from "sander"; 2 | 3 | import { 4 | resolveAppPath, 5 | resolveOwnPath, 6 | nodePaths, 7 | ensureSlash 8 | } from "../utils/paths"; 9 | 10 | export interface CatalogCLIPaths { 11 | unresolvedCatalogSrcDir: string; 12 | unresolvedCatalogBuildDir: string; 13 | catalogSrcDir: string; 14 | catalogBuildDir: string; 15 | catalogIndexJs: string; 16 | catalogIndexHtml: string; 17 | catalogStaticSrcDir: string; 18 | catalogStaticBuildDir: string; 19 | catalogSrcTemplateDir: string; 20 | appRoot: string; 21 | appStaticSrcDir: string; 22 | appStaticBuildDir: string; 23 | appPackageJson: string; 24 | appSrc: string; 25 | yarnLockFile: string; 26 | babelrc: string; 27 | appNodeModules: string; 28 | ownNodeModules: string; 29 | nodePaths: string[]; 30 | publicUrl: string; 31 | } 32 | 33 | export default async ( 34 | catalogSrcDir: string, 35 | catalogBuildDir: string, 36 | publicUrl: string 37 | ): Promise => ({ 38 | unresolvedCatalogSrcDir: catalogSrcDir, 39 | unresolvedCatalogBuildDir: catalogBuildDir, 40 | catalogSrcDir: resolveAppPath(catalogSrcDir), 41 | catalogBuildDir: resolveAppPath(catalogBuildDir), 42 | catalogIndexJs: resolveAppPath(catalogSrcDir, "index"), 43 | catalogIndexHtml: resolveAppPath(catalogSrcDir, "index.html"), 44 | catalogStaticSrcDir: resolveAppPath(catalogSrcDir, "static"), 45 | catalogStaticBuildDir: resolveAppPath(catalogBuildDir), 46 | 47 | catalogSrcTemplateDir: resolveOwnPath("..", "setup-template"), 48 | 49 | appRoot: resolveAppPath("."), 50 | appStaticSrcDir: resolveAppPath("static"), 51 | appStaticBuildDir: resolveAppPath(catalogBuildDir), 52 | 53 | appPackageJson: resolveAppPath("package.json"), 54 | appSrc: resolveAppPath("src"), 55 | yarnLockFile: resolveAppPath("yarn.lock"), 56 | babelrc: resolveAppPath(".babelrc"), 57 | appNodeModules: resolveAppPath("node_modules"), 58 | ownNodeModules: resolveOwnPath("..", "..", "node_modules"), 59 | nodePaths: nodePaths(), 60 | 61 | publicUrl: ensureSlash(publicUrl, true) 62 | }); 63 | -------------------------------------------------------------------------------- /packages/cli/src/actions/runBuild.ts: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | import { errorMessage } from "../utils/format"; 3 | import { rimraf, copydir, exists } from "sander"; 4 | 5 | // Print out errors 6 | function printErrors(summary: any, errors: any) { 7 | console.log(errorMessage(summary)); 8 | console.log(); 9 | errors.forEach((err: Error) => { 10 | console.log(err.message || err); 11 | console.log(); 12 | }); 13 | } 14 | 15 | export default async (config: any, paths: any) => { 16 | const compiler = webpack(config); 17 | await rimraf(paths.catalogBuildDir, "*"); 18 | 19 | // Copy app static assets to the correct location 20 | if (await exists(paths.appStaticSrcDir)) { 21 | await copydir(paths.appStaticSrcDir).to(paths.appStaticBuildDir); 22 | } 23 | 24 | // Copy Catalog's static files 25 | if (await exists(paths.catalogStaticSrcDir)) { 26 | await copydir(paths.catalogStaticSrcDir).to(paths.catalogStaticBuildDir); 27 | } 28 | 29 | return new Promise(resolve => { 30 | // We don't reject the promise but exit the process immediately 31 | compiler.run((err: any, stats: any) => { 32 | if (err) { 33 | printErrors("Failed to compile.", [err]); 34 | process.exit(1); 35 | } 36 | 37 | const info = stats.toJson(); 38 | 39 | if (stats.hasErrors()) { 40 | printErrors("Failed to compile.", info.errors); 41 | process.exit(1); 42 | } 43 | 44 | if (process.env.CI && stats.hasWarnings()) { 45 | printErrors( 46 | "Failed to compile. When process.env.CI = true, warnings are treated as failures. Most CI servers set this automatically.", 47 | info.warnings 48 | ); 49 | process.exit(1); 50 | } 51 | 52 | resolve(info); 53 | }); 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /packages/cli/src/actions/runDevServer.ts: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | import WebpackDevServer from "webpack-dev-server"; 3 | import express from "express"; 4 | import errorOverlayMiddleware from "react-dev-utils/errorOverlayMiddleware"; 5 | 6 | export default async ( 7 | config: any, 8 | host: string, 9 | port: number, 10 | https: boolean, 11 | paths: any, 12 | proxy: void | string 13 | ): Promise => { 14 | const compiler = webpack(config); 15 | const devServer = new WebpackDevServer(compiler, { 16 | compress: true, 17 | clientLogLevel: "none", 18 | contentBase: [paths.catalogStaticSrcDir, paths.appStaticSrcDir], 19 | // By default files from `contentBase` will not trigger a page reload. 20 | watchContentBase: true, 21 | hot: true, 22 | publicPath: config.output.publicPath, 23 | quiet: true, 24 | disableHostCheck: true, 25 | // Reportedly, this avoids CPU overload on some systems. 26 | // https://github.com/facebookincubator/create-react-app/issues/293 27 | watchOptions: { 28 | ignored: /node_modules/ 29 | }, 30 | historyApiFallback: { 31 | disableDotRule: true, 32 | htmlAcceptHeaders: proxy ? ["text/html"] : ["text/html", "*/*"] 33 | } as any /* because htmlAcceptHeaders is not documented */, 34 | https, 35 | host, 36 | ...(proxy 37 | ? { 38 | proxy: { 39 | "**": proxy 40 | } 41 | } 42 | : {}), 43 | overlay: false, 44 | before(app: any) { 45 | // Next.js serves static files from /static – which can't be configured with `contentBase` directly 46 | // if (framework === "NEXT") { 47 | // app.use("/static", express.static(paths.appStaticSrcDir)); 48 | // } 49 | // This lets us open files from the runtime error overlay. 50 | app.use(errorOverlayMiddleware()); 51 | } 52 | }); 53 | 54 | // Launch WebpackDevServer. 55 | return new Promise((resolve, reject) => { 56 | devServer.listen(port, host, (err: any) => { 57 | if (err) { 58 | reject(err); 59 | } else { 60 | resolve(devServer); 61 | } 62 | }); 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /packages/cli/src/actions/setupCatalog.ts: -------------------------------------------------------------------------------- 1 | import { exists } from "sander"; 2 | import chalk from "chalk"; 3 | import { CatalogCLIPaths } from "./loadPaths"; 4 | 5 | export default async (paths: CatalogCLIPaths) => { 6 | const [ 7 | indexJsExists, 8 | indexTsExists, 9 | indexTsxExists, 10 | htmlExists, 11 | dirExists 12 | ] = await Promise.all([ 13 | exists(paths.catalogIndexJs + ".js"), 14 | exists(paths.catalogIndexJs + ".ts"), 15 | exists(paths.catalogIndexJs + ".tsx"), 16 | exists(paths.catalogIndexHtml), 17 | exists(paths.catalogSrcDir) 18 | ]); 19 | 20 | const indexExists = indexJsExists || indexTsExists || indexTsxExists; 21 | 22 | if (!dirExists) { 23 | console.error(chalk` 24 | {red The '${ 25 | paths.unresolvedCatalogSrcDir 26 | }' directory doesn't exist. Please create it first.} 27 | `); 28 | process.exit(1); 29 | } else if (!indexExists || !htmlExists) { 30 | console.error(chalk` 31 | {red Can't find 'index.{js,ts,tsx}' and 'index.html' in ${ 32 | paths.unresolvedCatalogSrcDir 33 | }'. Please make sure they exist.} 34 | `); 35 | process.exit(1); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /packages/cli/src/ambient.d.ts: -------------------------------------------------------------------------------- 1 | declare module "args"; 2 | declare module "sander"; 3 | declare module "html-webpack-plugin"; 4 | declare module "webpack-manifest-plugin"; 5 | declare module "react-dev-utils/*"; 6 | declare module "friendly-errors-webpack-plugin"; 7 | declare module "terser-webpack-plugin"; 8 | -------------------------------------------------------------------------------- /packages/cli/src/bin/catalog-build.ts: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = "production"; 2 | 3 | import args from "args"; 4 | import chalk from "chalk"; 5 | import { exists } from "sander"; 6 | 7 | import { 8 | errorMessage, 9 | warningMessage, 10 | infoMessageDimmed 11 | } from "../utils/format"; 12 | 13 | import loadWebpackConfig from "../actions/loadWebpackConfig"; 14 | import loadConfigFile from "../actions/loadConfigFile"; 15 | import loadPaths from "../actions/loadPaths"; 16 | 17 | import setupCatalog from "../actions/setupCatalog"; 18 | import runBuild from "../actions/runBuild"; 19 | 20 | args 21 | .option(["o", "out"], "Directory to build into", "/build") 22 | .option( 23 | ["u", "public-url"], 24 | "The URL where production assets get loaded from", 25 | "/" 26 | ) 27 | .option("public-path", "[DEPRECATED] Use --public-url") 28 | .option("babelrc", "Use local .babelrc file (defaults to true)"); 29 | 30 | const cliOptions = args.parse(process.argv, { 31 | value: "", 32 | mri: { 33 | boolean: ["babelrc"] 34 | } 35 | }); 36 | 37 | const run = async ( 38 | catalogSrcDir: string = "catalog", 39 | { 40 | out, 41 | publicPath, 42 | publicUrl, 43 | babelrc 44 | }: { 45 | out: string; 46 | publicPath?: string; 47 | publicUrl: string; 48 | babelrc: void | boolean; 49 | } 50 | ) => { 51 | const configFile = await loadConfigFile(); 52 | 53 | let webpackPublicPath = publicUrl; 54 | if (publicPath) { 55 | console.warn( 56 | warningMessage( 57 | "The --public-path option has been deprecated. Use --public-url" 58 | ) 59 | ); 60 | webpackPublicPath = publicPath; 61 | } 62 | 63 | const paths = await loadPaths( 64 | catalogSrcDir, 65 | out.replace("", catalogSrcDir), 66 | webpackPublicPath 67 | ); 68 | 69 | const babelrcExists: boolean = await exists(paths.babelrc); 70 | 71 | const useBabelrc = 72 | babelrc !== undefined 73 | ? babelrc 74 | : configFile && configFile.useBabelrc !== undefined 75 | ? configFile.useBabelrc 76 | : babelrcExists; 77 | 78 | const webpackOptions = { paths, dev: false, useBabelrc }; 79 | 80 | let webpackConfig = await loadWebpackConfig(webpackOptions); 81 | 82 | if (configFile) { 83 | if (typeof configFile.webpack === "function") { 84 | webpackConfig = configFile.webpack(webpackConfig, webpackOptions); 85 | } 86 | } 87 | 88 | await setupCatalog(paths); 89 | 90 | console.log(chalk` 91 | Building Catalog. This may take a while … 92 | `); 93 | 94 | if (configFile) { 95 | console.log( 96 | infoMessageDimmed(" Using configuration file catalog.config.js") 97 | ); 98 | } 99 | 100 | if (useBabelrc) { 101 | console.log(infoMessageDimmed(" Using custom .babelrc")); 102 | } 103 | 104 | await runBuild(webpackConfig, paths); 105 | console.log(chalk` {green Built Catalog into} ${ 106 | paths.unresolvedCatalogBuildDir 107 | } 108 | `); 109 | }; 110 | 111 | run(args.sub[0], cliOptions).catch(err => { 112 | console.error( 113 | errorMessage("Failed to compile Catalog\n\n" + err.stack + "\n") 114 | ); 115 | process.exit(1); 116 | }); 117 | -------------------------------------------------------------------------------- /packages/cli/src/bin/catalog-start.ts: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = "development"; 2 | 3 | import args from "args"; 4 | import { errorMessage } from "../utils/format"; 5 | import { startServer } from "../server"; 6 | 7 | // Parse env 8 | 9 | args 10 | .option( 11 | "port", 12 | "Port on which the Catalog server runs", 13 | 4000, 14 | (port: string) => parseInt(port, 10) 15 | ) 16 | .option("https", "Use https", false) 17 | .option("host", "Host", "localhost") 18 | .option("proxy", "Proxy") 19 | .option("babelrc", "Use local .babelrc file (defaults to true)"); 20 | 21 | const cliOptions = args.parse(process.argv, { 22 | value: "[source directory]", 23 | mri: { 24 | boolean: ["babelrc"] 25 | } 26 | }); 27 | 28 | startServer(args.sub[0], cliOptions).catch(err => { 29 | console.error(errorMessage("Could not start Catalog\n\n" + err.stack + "\n")); 30 | process.exit(1); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/cli/src/bin/catalog.ts: -------------------------------------------------------------------------------- 1 | import args from "args"; 2 | 3 | args 4 | .command("start", "Starts the Catalog server") 5 | .command("build", "Builds a Catalog static site"); 6 | 7 | args.parse(process.argv); 8 | 9 | if (!args.sub.length) { 10 | // no commands 11 | args.showHelp(); 12 | } 13 | -------------------------------------------------------------------------------- /packages/cli/src/config/env.ts: -------------------------------------------------------------------------------- 1 | // Grab NODE_ENV and CATALOG_* environment variables and prepare them to be 2 | // injected into the application via DefinePlugin in Webpack configuration. 3 | const CATALOG = /^CATALOG_/i; 4 | 5 | export default function getClientEnvironment(publicUrl: string) { 6 | const raw = Object.keys(process.env) 7 | .filter(key => CATALOG.test(key)) 8 | .reduce( 9 | (env: any, key) => { 10 | env[key] = process.env[key]; 11 | return env; 12 | }, 13 | { 14 | // Useful for determining whether we’re running in production mode. 15 | // Most importantly, it switches React into the correct mode. 16 | NODE_ENV: process.env.NODE_ENV || "development", 17 | // Useful for resolving the correct path to static assets in `public`. 18 | // For example, . 19 | // This should only be used as an escape hatch. Normally you would put 20 | // images into the `src` and `import` them in code to get their paths. 21 | PUBLIC_URL: publicUrl 22 | } 23 | ); 24 | // Stringify all values so we can feed into Webpack DefinePlugin 25 | const stringified = { 26 | "process.env": Object.keys(raw).reduce((env: any, key) => { 27 | env[key] = JSON.stringify(raw[key]); 28 | return env; 29 | }, {}) 30 | }; 31 | 32 | return { raw, stringified }; 33 | } 34 | -------------------------------------------------------------------------------- /packages/cli/src/server.ts: -------------------------------------------------------------------------------- 1 | import { exists } from "sander"; 2 | import openBrowser from "react-dev-utils/openBrowser"; 3 | import { choosePort } from "react-dev-utils/WebpackDevServerUtils"; 4 | 5 | import { infoMessageDimmed } from "./utils/format"; 6 | 7 | import loadWebpackConfig from "./actions/loadWebpackConfig"; 8 | import loadConfigFile from "./actions/loadConfigFile"; 9 | import loadPaths from "./actions/loadPaths"; 10 | 11 | import setupCatalog from "./actions/setupCatalog"; 12 | import runDevServer from "./actions/runDevServer"; 13 | 14 | export interface Options { 15 | port: number; 16 | https: boolean; 17 | host: string; 18 | proxy: void | string; 19 | babelrc: void | boolean; 20 | } 21 | 22 | export interface Server { 23 | port: number; 24 | url: string; 25 | devServer: any; 26 | } 27 | 28 | export const startServer = async ( 29 | catalogSrcDir: string = "catalog", 30 | options: Options 31 | ): Promise => { 32 | const configFile = await loadConfigFile(); 33 | 34 | const paths = await loadPaths(catalogSrcDir, "", "/"); 35 | 36 | const port = await choosePort("0.0.0.0", options.port); 37 | 38 | const url = 39 | (options.https ? "https" : "http") + 40 | "://" + 41 | options.host + 42 | ":" + 43 | port + 44 | "/"; 45 | 46 | const babelrcExists: boolean = await exists(paths.babelrc); 47 | 48 | const useBabelrc = 49 | options.babelrc !== undefined 50 | ? options.babelrc 51 | : configFile && configFile.useBabelrc !== undefined 52 | ? configFile.useBabelrc 53 | : babelrcExists; 54 | 55 | const webpackOptions = { paths, dev: true, url, useBabelrc }; 56 | 57 | let webpackConfig = await loadWebpackConfig(webpackOptions); 58 | 59 | if (configFile) { 60 | if (typeof configFile.webpack === "function") { 61 | webpackConfig = configFile.webpack(webpackConfig, webpackOptions); 62 | } 63 | } 64 | 65 | await setupCatalog(paths); 66 | 67 | console.log(` 68 | Starting Catalog … 69 | `); 70 | if (configFile) { 71 | console.log( 72 | infoMessageDimmed(" Using configuration file catalog.config.js") 73 | ); 74 | } 75 | if (useBabelrc) { 76 | console.log(infoMessageDimmed(" Using custom .babelrc")); 77 | } 78 | 79 | const devServer = await runDevServer( 80 | webpackConfig, 81 | options.host, 82 | port, 83 | options.https, 84 | paths, 85 | options.proxy 86 | ); 87 | 88 | openBrowser(url); 89 | 90 | return { port, url, devServer }; 91 | }; 92 | 93 | export const stopServer = async (server: Server) => { 94 | server.devServer.close(); 95 | }; 96 | -------------------------------------------------------------------------------- /packages/cli/src/utils/format.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | 3 | export const infoMessage = (str: string) => str; 4 | export const infoMessageDimmed = (str: string) => chalk.dim(str); 5 | export const errorMessage = (str: string) => chalk.red(`✖ ${str}`); 6 | export const warningMessage = (str: string) => chalk.yellow(str); 7 | export const question = (str: string) => `❯ ${str}`; 8 | export const successMessage = (str: string) => chalk.green(`✔︎ ${str}`); 9 | export const link = (str: string) => chalk.underline(str); 10 | -------------------------------------------------------------------------------- /packages/cli/src/utils/paths.ts: -------------------------------------------------------------------------------- 1 | import { realpathSync } from "fs"; 2 | import { resolve, isAbsolute } from "path"; 3 | 4 | // Make sure any symlinks in the project folder are resolved: 5 | // https://github.com/facebookincubator/create-react-app/issues/637 6 | export const resolveAppPath = (...relativePaths: Array) => 7 | resolve(realpathSync(process.cwd()), ...relativePaths); 8 | 9 | export const resolveOwnPath = (...relativePaths: Array) => 10 | resolve(__dirname, "..", ...relativePaths); 11 | 12 | export const nodePaths = () => 13 | (process.env.NODE_PATH || "") 14 | .split(process.platform === "win32" ? ";" : ":") 15 | .filter(Boolean) 16 | .filter(folder => !isAbsolute(folder)) 17 | .map(path => resolveAppPath(path)); 18 | 19 | export const ensureSlash = (path: string, needsSlash: boolean) => { 20 | const hasSlash = path.endsWith("/"); 21 | if (hasSlash && !needsSlash) { 22 | return path.substr(0, path.length - 1); 23 | } else if (!hasSlash && needsSlash) { 24 | return `${path}/`; 25 | } 26 | return path; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "loose": true, 7 | "modules": false, 8 | "targets": { "node": 6, "browsers": ["last 2 versions", "ie 11"] } 9 | } 10 | ], 11 | "@babel/preset-react" 12 | ], 13 | "plugins": [["emotion", { "autoLabel": true }]], 14 | "env": { 15 | "test": { 16 | "presets": [ 17 | [ 18 | "@babel/preset-env", 19 | { "loose": true, "targets": { "node": "current" } } 20 | ], 21 | "@babel/preset-react", 22 | "@babel/preset-typescript" 23 | ], 24 | "plugins": [["emotion", { "autoLabel": true }]] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | dist 3 | docs/build 4 | -------------------------------------------------------------------------------- /packages/core/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | @../../node_modules/.bin/rollup --config=rollup.config.js 4 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@catalog/core", 3 | "version": "4.0.1-canary.2", 4 | "description": "Create living style guides using Markdown or React", 5 | "keywords": [ 6 | "styleguide", 7 | "style guide", 8 | "docs", 9 | "documentation", 10 | "react", 11 | "markdown" 12 | ], 13 | "homepage": "https://www.catalog.style/", 14 | "bugs": "https://github.com/interactivethings/catalog/issues", 15 | "main": "dist/catalog.cjs.js", 16 | "module": "dist/catalog.es.js", 17 | "engines": { 18 | "npm": ">=2.14.12" 19 | }, 20 | "files": [ 21 | "dist", 22 | "types" 23 | ], 24 | "types": "types/catalog", 25 | "dependencies": { 26 | "create-emotion": "^10.0.9", 27 | "d3-color": "^1.1.0", 28 | "github-slugger": "^1.1.3", 29 | "history": "^3.2.1", 30 | "js-yaml": "^3.9.1", 31 | "marked": "^0.4.0", 32 | "prismjs": "^1.14.0", 33 | "prop-types": "^15.7.2", 34 | "raf": "^3.3.2", 35 | "ramda": "^0.25.0", 36 | "react-document-title": "^2.0.3", 37 | "react-router": "^3.2.0", 38 | "react-router-scroll": "^0.4.2", 39 | "srcset": "^2.0.0", 40 | "url": "^0.11.0" 41 | }, 42 | "peerDependencies": { 43 | "@babel/standalone": "^7.1.0", 44 | "react": ">= 16.8.0", 45 | "react-dom": ">= 16.8.0" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "https://github.com/interactivethings/catalog.git" 50 | }, 51 | "license": "BSD-3-Clause", 52 | "jest": { 53 | "roots": [ 54 | "/src" 55 | ] 56 | }, 57 | "publishConfig": { 58 | "access": "public" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | const babel = require("rollup-plugin-babel"); 2 | const pkg = require("./package.json"); 3 | const path = require("path"); 4 | 5 | const banner = `/*! Catalog v${pkg.version} ${pkg.homepage} */`; 6 | 7 | const externals = [ 8 | ...Object.keys(pkg.dependencies), 9 | ...Object.keys(pkg.peerDependencies), 10 | "url" 11 | ]; 12 | 13 | module.exports = { 14 | input: { catalog: path.resolve(__dirname, "src/index.js") }, 15 | plugins: [ 16 | babel({ 17 | exclude: /node_modules/ 18 | }) 19 | ], 20 | external: id => externals.some(d => id.startsWith(d)), 21 | output: [ 22 | { 23 | dir: path.resolve(__dirname, "dist"), 24 | entryFileNames: "[name].cjs.js", 25 | format: "cjs", 26 | name: "Catalog", 27 | banner, 28 | sourcemap: true 29 | }, 30 | { 31 | dir: path.resolve(__dirname, "dist"), 32 | entryFileNames: "[name].es.js", 33 | format: "es", 34 | banner, 35 | sourcemap: true 36 | } 37 | ] 38 | }; 39 | -------------------------------------------------------------------------------- /packages/core/src/CatalogPropTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | 3 | export const pageShape = PropTypes.shape({ 4 | title: PropTypes.string.isRequired, 5 | id: PropTypes.number.isRequired, 6 | index: PropTypes.number, 7 | path: PropTypes.string, 8 | src: PropTypes.string, 9 | pages: PropTypes.array, // should be arrayOf(page) but that doesn't work 10 | styles: PropTypes.array.isRequired, 11 | scripts: PropTypes.array.isRequired, 12 | imports: PropTypes.object.isRequired, 13 | hideFromMenu: PropTypes.boolean 14 | }); 15 | 16 | export const pagesShape = PropTypes.arrayOf(pageShape); 17 | 18 | export const catalogShape = PropTypes.shape({ 19 | basePath: PropTypes.string.isRequired, 20 | publicUrl: PropTypes.string.isRequired, 21 | page: pageShape.isRequired, 22 | getSpecimen: PropTypes.func.isRequired, 23 | theme: PropTypes.object.isRequired, 24 | responsiveSizes: PropTypes.array.isRequired, 25 | title: PropTypes.string.isRequired, 26 | pages: pagesShape.isRequired, 27 | pageTree: pagesShape.isRequired, 28 | pagePaths: PropTypes.instanceOf(Set).isRequired, 29 | logoSrc: PropTypes.string 30 | }); 31 | -------------------------------------------------------------------------------- /packages/core/src/DefaultResponsiveSizes.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { name: "small", width: 360, height: 640 }, 3 | { name: "medium", width: 1024, height: 768 }, 4 | { name: "large", width: 1440, height: 900 }, 5 | { name: "xlarge", width: 1920, height: 1080 } 6 | ]; 7 | -------------------------------------------------------------------------------- /packages/core/src/DefaultTheme.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable key-spacing */ 2 | 3 | export default { 4 | // Colors 5 | background: "#F9F9F9", 6 | textColor: "#333333", 7 | codeColor: "#00263E", 8 | linkColor: "#FF5555", 9 | 10 | // NavigationBar background color, but also sometimes used as a foreground 11 | // or border color. 12 | lightColor: "#D6D6D6", 13 | 14 | // Used in PageHeader 15 | pageHeadingBackground: "#003B5C", 16 | pageHeadingTextColor: "#fff", 17 | 18 | // Used in Menu and PageHeader to make sure the top parts have 19 | // the same height. 20 | pageHeadingHeight: 200, 21 | 22 | // Used for navigation bar 23 | navBarBackground: "#F2F2F2", 24 | navBarTextColor: "#003B5C", 25 | 26 | // Used in ResponsiveTabs (tab text), Download specimen (title text). 27 | // Typography: headings. 28 | brandColor: "#003B5C", 29 | 30 | sidebarColor: "#FFFFFF", 31 | sidebarColorText: "#003B5C", 32 | sidebarColorTextActive: "#FF5555", 33 | sidebarColorLine: "#EBEBEB", 34 | sidebarColorHeading: "#003B5C", 35 | 36 | // Used in the html, react, and image specimens. 37 | bgLight: "#F2F2F2", 38 | bgDark: "#333333", 39 | 40 | // Keys appear to be PrismJS token types. 41 | codeStyles: { 42 | tag: { color: "#FF5555" }, 43 | punctuation: { color: "#535353" }, 44 | script: { color: "#3F7397" }, 45 | function: { color: "#FF5555" }, 46 | keyword: { color: "#3F7397" }, 47 | string: { color: "#00263E" } 48 | }, 49 | 50 | // Patterns 51 | checkerboardPatternLight: 52 | "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAAAAACoWZBhAAAAF0lEQVQI12P4BAI/QICBFCaYBPNJYQIAkUZftTbC4sIAAAAASUVORK5CYII=", 53 | checkerboardPatternDark: 54 | "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAAAAACoWZBhAAAAFklEQVQI12NQBQF2EGAghQkmwXxSmADZJQiZ2ZZ46gAAAABJRU5ErkJggg==", 55 | 56 | // Fonts 57 | fontFamily: "'Roboto', sans-serif", 58 | fontHeading: "'Roboto', sans-serif", 59 | fontMono: "'Roboto Mono', monospace", 60 | 61 | // Base font size in pixels. 62 | baseFontSize: 16, 63 | 64 | // Modular scale ratio that is used to figure out all the different font sizes 65 | msRatio: 1.2 66 | }; 67 | -------------------------------------------------------------------------------- /packages/core/src/__snapshots__/markdownPage.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Catalog markdown page template literal 1`] = ` 4 | 5 | 6 | # This is a title 7 | 8 | A paragraph 9 | 10 | 11 | 12 | # THIS IS A HINT 13 | 14 | Cool, eh? 15 | 16 | 17 | 18 | 19 | `; 20 | 21 | exports[`Catalog markdown page template literal with arbitrary values 1`] = ` 22 | 23 | 24 | # This is a title 25 | 26 | A paragraph 123 27 | 28 | ~~~ 29 | 30 | foo 31 | bar 32 |
33 | hello 34 |
35 | 36 | ~~~ 37 | 38 |
39 | `; 40 | -------------------------------------------------------------------------------- /packages/core/src/components/App/App.js: -------------------------------------------------------------------------------- 1 | import React, { Children } from "react"; 2 | import PropTypes from "prop-types"; 3 | import DocumentTitle from "react-document-title"; 4 | import { catalogShape } from "../../CatalogPropTypes"; 5 | 6 | import AppLayout from "./AppLayout"; 7 | import Menu from "../Menu/Menu"; 8 | 9 | const getDocumentTitle = ({ title, page }) => 10 | title === page.superTitle 11 | ? `${page.superTitle} – ${page.title}` 12 | : `${title} – ${page.superTitle} – ${page.title}`; 13 | 14 | class App extends React.Component { 15 | render() { 16 | const { catalog } = this.context; 17 | return ( 18 | }> 19 | 20 | {Children.only(this.props.children)} 21 | 22 | ); 23 | } 24 | } 25 | 26 | App.contextTypes = { 27 | catalog: catalogShape.isRequired 28 | }; 29 | 30 | App.propTypes = { 31 | children: PropTypes.element.isRequired 32 | }; 33 | 34 | export default App; 35 | -------------------------------------------------------------------------------- /packages/core/src/components/Card/Card.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import warning from "../../utils/warning"; 3 | import Page from "../Page/Page"; 4 | 5 | const Card = props => { 6 | warning(false, "The `Card` component is deprecated; use `Page` instead."); 7 | 8 | return ; 9 | }; 10 | 11 | export default Card; 12 | -------------------------------------------------------------------------------- /packages/core/src/components/Catalog.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React, { Component } from "react"; 3 | import { 4 | Router, 5 | applyRouterMiddleware, 6 | browserHistory, 7 | hashHistory 8 | } from "react-router"; 9 | import { useScroll } from "react-router-scroll"; 10 | import seqKey from "../utils/seqKey"; 11 | 12 | import configureRoutes from "../configureRoutes"; 13 | 14 | export default class Catalog extends Component { 15 | constructor() { 16 | super(); 17 | this.getKey = seqKey("CatalogRouter"); 18 | } 19 | render() { 20 | const configuration = this.props; 21 | return ( 22 | 28 | ); 29 | } 30 | } 31 | 32 | Catalog.propTypes = { 33 | useBrowserHistory: PropTypes.bool 34 | }; 35 | -------------------------------------------------------------------------------- /packages/core/src/components/CatalogContext.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React, { Component, Children } from "react"; 3 | import App from "./App/App"; 4 | import { catalogShape } from "../CatalogPropTypes"; 5 | 6 | const fallbackPathRe = /\*$/; 7 | 8 | class CatalogContext extends Component { 9 | getChildContext() { 10 | const { 11 | title, 12 | theme, 13 | responsiveSizes, 14 | logoSrc, 15 | pages, 16 | pageTree, 17 | specimens, 18 | basePath, 19 | publicUrl, 20 | useBrowserHistory 21 | } = this.props.configuration; 22 | const { router } = this.context; 23 | return { 24 | catalog: { 25 | page: pages.find( 26 | p => router.isActive(p.path) || fallbackPathRe.test(p.path) 27 | ), 28 | getSpecimen: specimen => specimens[specimen], 29 | theme, 30 | responsiveSizes, 31 | title, 32 | pages: pages.filter(p => !p.hideFromMenu), 33 | pagePaths: new Set(pages.map(p => p.path)), // Used for internal link lookup 34 | pageTree, 35 | basePath, 36 | publicUrl, 37 | logoSrc, 38 | useBrowserHistory 39 | } 40 | }; 41 | } 42 | 43 | render() { 44 | const { children } = this.props; 45 | return Children.only(children); 46 | } 47 | } 48 | 49 | CatalogContext.propTypes = { 50 | configuration: PropTypes.object.isRequired, 51 | children: PropTypes.element.isRequired 52 | }; 53 | 54 | CatalogContext.contextTypes = { 55 | // From react-router 56 | router: PropTypes.object.isRequired 57 | }; 58 | 59 | CatalogContext.childContextTypes = { 60 | catalog: catalogShape.isRequired 61 | }; 62 | 63 | export default function createCatalogContext(config) { 64 | const ConfiguredCatalogContext = ({ children }) => ( 65 | 66 | {children} 67 | 68 | ); 69 | 70 | ConfiguredCatalogContext.propTypes = { 71 | children: PropTypes.element.isRequired 72 | }; 73 | 74 | return ConfiguredCatalogContext; 75 | } 76 | -------------------------------------------------------------------------------- /packages/core/src/components/Content/Heading.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import HeadingLink from "../Link/HeadingLink"; 4 | import { catalogShape } from "../../CatalogPropTypes"; 5 | import { heading } from "../../styles/typography"; 6 | import { css } from "../../emotion"; 7 | 8 | const HeadingWithLink = ({ level, text, slug, catalog: { theme } }) => { 9 | const tag = "h" + level; 10 | 11 | const linkStyle = css({ display: "none" }); 12 | 13 | const headingStyle = css( 14 | { 15 | ...heading(theme, 5 - level), 16 | flexBasis: "100%", 17 | margin: `48px 0 0 0`, 18 | "blockquote + &, h1 + &, h2 + &, h3 + &, h4 + &, h5 + &, h6 + &": { 19 | margin: `16px 0 0 0` 20 | }, 21 | [`&:hover .${linkStyle}`]: { display: "inline" } 22 | }, 23 | { label: tag } 24 | ); 25 | 26 | return React.createElement( 27 | tag, 28 | { id: slug, className: headingStyle }, 29 | text, 30 | " ", 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | const PlainHeading = ({ level, text }) => { 38 | const tag = "h" + level; 39 | return React.createElement(tag, null, text); 40 | }; 41 | 42 | const Heading = ({ level, text, slug }, { catalog }) => 43 | slug ? ( 44 | 45 | ) : ( 46 | 47 | ); 48 | 49 | Heading.propTypes = HeadingWithLink.propTypes = PlainHeading.propTypes = { 50 | level: PropTypes.oneOf([1, 2, 3, 4, 5, 6]).isRequired, 51 | text: PropTypes.array.isRequired, 52 | slug: PropTypes.string 53 | }; 54 | 55 | Heading.contextTypes = { 56 | catalog: catalogShape.isRequired 57 | }; 58 | 59 | export default Heading; 60 | -------------------------------------------------------------------------------- /packages/core/src/components/Content/Markdown.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "../../styled"; 3 | import { text, getFontSize } from "../../styles/typography"; 4 | import { catalogShape } from "../../CatalogPropTypes"; 5 | import BaseLink from "../Link/Link"; 6 | import { css } from "../../emotion"; 7 | 8 | const baseListStyle = { 9 | width: "100%", 10 | marginLeft: 0, 11 | paddingLeft: "2rem" 12 | }; 13 | 14 | // Defined with `css`, so it can be used as a selector for nested elements 15 | // For example: `Paragraph` 16 | const blockquoteStyle = () => 17 | css({ 18 | quotes: "none", 19 | margin: "48px 0 32px 0", 20 | width: "100%", 21 | "&::before, &::after": { content: "none" }, 22 | "& > :first-child": { marginTop: 0 }, 23 | "& > :last-child": { marginBottom: 0 }, 24 | "& + &": { marginTop: 0 } 25 | }); 26 | 27 | export const Paragraph = styled("p", (props, { theme }) => ({ 28 | ...text(theme), 29 | flexBasis: "100%", 30 | [`.${blockquoteStyle()} &`]: { fontSize: getFontSize(theme, 1) }, 31 | margin: `16px 0 0 0` 32 | })); 33 | export const UnorderedList = styled("ul", { 34 | ...baseListStyle, 35 | listStyle: "disc", 36 | marginTop: "16px", 37 | marginBottom: 0, 38 | "& > li": { listStyle: "disc" } 39 | }); 40 | export const OrderedList = styled("ol", { 41 | ...baseListStyle, 42 | listStyle: "ordinal", 43 | marginTop: "16px", 44 | marginBottom: 0, 45 | "& > li": { listStyle: "ordinal" } 46 | }); 47 | export const ListItem = styled("li", (props, { theme }) => ({ 48 | ...text(theme), 49 | [`.${blockquoteStyle()} &`]: { fontSize: getFontSize(theme, 1) }, 50 | margin: 0, 51 | padding: 0, 52 | "& > :first-child, & > ul, & > ol": { marginTop: 0 }, 53 | "& > :last-child": { marginBottom: 0 } 54 | })); 55 | export const BlockQuote = props => ( 56 |
57 | ); 58 | export const Hr = styled("hr", { 59 | border: "none", 60 | flexBasis: "100%", 61 | margin: 0, 62 | height: 0 63 | }); 64 | export const Em = styled("em", { fontStyle: "italic" }); 65 | export const Strong = styled("strong", { 66 | fontWeight: 700 67 | }); 68 | export const CodeSpan = styled("code", (props, { theme }) => ({ 69 | background: theme.bgLight, 70 | border: `1px solid #eee`, 71 | borderRadius: 1, 72 | display: "inline-block", 73 | fontFamily: theme.fontMono, 74 | fontSize: `${Math.pow(theme.msRatio, -0.5)}em`, 75 | lineHeight: 1, 76 | padding: "0.12em 0.2em", 77 | textIndent: 0 78 | })); 79 | export const Del = styled("del", { 80 | textDecoration: "line-through" 81 | }); 82 | export const Image = styled("img", { 83 | maxWidth: "100%" 84 | }); 85 | 86 | export const Link = (props, { catalog: { theme } }) => { 87 | const baseLinkStyle = { 88 | color: theme.linkColor, 89 | transition: "none", 90 | border: "none", 91 | background: "none", 92 | textDecoration: "none" 93 | }; 94 | return ( 95 | 106 | ); 107 | }; 108 | 109 | Link.contextTypes = { 110 | catalog: catalogShape 111 | }; 112 | -------------------------------------------------------------------------------- /packages/core/src/components/Frame/Frame.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React, { Component } from "react"; 3 | import { catalogShape } from "../../CatalogPropTypes"; 4 | import FrameComponent from "./FrameComponent"; 5 | import { css } from "../../emotion"; 6 | 7 | const frameStyle = { 8 | width: "100%", 9 | height: "100%", 10 | lineHeight: 0, 11 | margin: 0, 12 | padding: 0, 13 | border: "none" 14 | }; 15 | 16 | const renderStyles = styles => { 17 | return styles.map((src, i) => ( 18 | 19 | )); 20 | }; 21 | 22 | export default class Frame extends Component { 23 | constructor() { 24 | super(); 25 | this.state = {}; 26 | } 27 | 28 | render() { 29 | const { children, width, parentWidth, scrolling, background } = this.props; 30 | const { catalog: { page: { styles } } } = this.context; 31 | const height = this.state.height || this.props.height; 32 | const autoHeight = !this.props.height; 33 | const scale = Math.min(1, parentWidth / width); 34 | const scaledHeight = autoHeight ? height : height * scale; 35 | 36 | return ( 37 |
44 |
53 | 61 | {"html,body{margin:0;padding:0;}"} 62 | , 63 | ...renderStyles(styles) 64 | ]} 65 | onRender={ 66 | autoHeight 67 | ? content => { 68 | const contentHeight = content.offsetHeight; 69 | if (contentHeight !== height) { 70 | this.setState({ height: contentHeight }); 71 | } 72 | } 73 | : () => null 74 | } 75 | > 76 | {children} 77 | 78 |
79 |
80 | ); 81 | } 82 | } 83 | 84 | Frame.propTypes = { 85 | children: PropTypes.element, 86 | width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 87 | parentWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 88 | height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 89 | scrolling: PropTypes.bool, 90 | background: PropTypes.string 91 | }; 92 | 93 | Frame.contextTypes = { 94 | catalog: catalogShape.isRequired 95 | }; 96 | -------------------------------------------------------------------------------- /packages/core/src/components/HighlightedCode/HighlightedCode.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React, { Component } from "react"; 3 | import Prism from "prismjs"; 4 | import "prismjs/components/prism-jsx"; 5 | import "prismjs/components/prism-markdown"; 6 | import { css } from "../../emotion"; 7 | import { text } from "../../styles/typography"; 8 | 9 | const getStyle = theme => { 10 | return { 11 | pre: { 12 | ...text(theme, -0.5), 13 | background: "#fff", 14 | border: "none", 15 | boxSizing: "border-box", 16 | color: theme.codeColor, 17 | display: "block", 18 | height: "auto", 19 | margin: 0, 20 | overflow: "auto", 21 | WebkitOverflowScrolling: "touch", 22 | padding: 20, 23 | whiteSpace: "pre", 24 | width: "100%" 25 | }, 26 | code: { 27 | fontFamily: theme.fontMono, 28 | fontWeight: 400 29 | } 30 | }; 31 | }; 32 | 33 | const isToken = t => t instanceof Prism.Token; 34 | 35 | const renderPrismTokens = (tokens, styles) => { 36 | return tokens.map((t, i) => { 37 | if (isToken(t)) { 38 | return ( 39 | 40 | {Array.isArray(t.content) 41 | ? renderPrismTokens(t.content, styles) 42 | : t.content} 43 | 44 | ); 45 | } 46 | 47 | if (typeof t === "string") { 48 | return t; 49 | } 50 | 51 | throw Error("wat"); 52 | }); 53 | }; 54 | 55 | export default class HighlightedCode extends Component { 56 | render() { 57 | const { language, theme, code } = this.props; 58 | const styles = getStyle(theme); 59 | const lang = Prism.languages.hasOwnProperty(language) ? language : null; 60 | 61 | return ( 62 |
63 |         
64 |           {lang
65 |             ? renderPrismTokens(
66 |                 Prism.tokenize(code, Prism.languages[lang], lang),
67 |                 theme.codeStyles
68 |               )
69 |             : code}
70 |         
71 |       
72 | ); 73 | } 74 | } 75 | 76 | HighlightedCode.propTypes = { 77 | language: PropTypes.string, 78 | theme: PropTypes.object.isRequired, 79 | code: PropTypes.string.isRequired 80 | }; 81 | -------------------------------------------------------------------------------- /packages/core/src/components/Link/HeadingLink.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | import Link from "./Link"; 4 | import { catalogShape } from "../../CatalogPropTypes"; 5 | import { css } from "../../emotion"; 6 | 7 | const style = theme => ({ 8 | headingLink: { 9 | color: theme.lightColor, 10 | textDecoration: "none", 11 | ":hover": { 12 | color: theme.linkColor 13 | } 14 | } 15 | }); 16 | 17 | const HeadingLink = ({ slug, ...rest }, { catalog }) => { 18 | return ( 19 | 26 | # 27 | 28 | ); 29 | }; 30 | 31 | HeadingLink.propTypes = { 32 | slug: PropTypes.string.isRequired 33 | }; 34 | 35 | HeadingLink.contextTypes = { 36 | catalog: catalogShape.isRequired 37 | }; 38 | 39 | export default HeadingLink; 40 | -------------------------------------------------------------------------------- /packages/core/src/components/Link/Link.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Link as RouterLink } from "react-router"; 4 | import { catalogShape } from "../../CatalogPropTypes"; 5 | import { parsePath, isInternalPath, getPublicPath } from "../../utils/path"; 6 | 7 | const Link = ({ to, ...rest }, { catalog }) => { 8 | const parsedTo = parsePath(to, catalog); 9 | return isInternalPath(parsedTo, catalog) ? ( 10 | 11 | ) : ( 12 | 13 | ); 14 | }; 15 | 16 | Link.propTypes = { 17 | to: PropTypes.string.isRequired 18 | }; 19 | 20 | Link.contextTypes = { 21 | catalog: catalogShape.isRequired 22 | }; 23 | 24 | export default Link; 25 | -------------------------------------------------------------------------------- /packages/core/src/components/Page/Loader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { css, keyframes } from "../../emotion"; 3 | 4 | const SHOW_AFTER_MS = 500; 5 | 6 | const loaderKeyframes = keyframes( 7 | { 8 | "0%": { transform: "rotate(0deg)" }, 9 | "50%": { transform: "rotate(180deg)" }, 10 | "100%": { transform: "rotate(360deg)" } 11 | }, 12 | "Loader" 13 | ); 14 | 15 | const styles = { 16 | spinner: { 17 | borderColor: "#EEEEEE #D3D3D3 #B6B6B6 #999999", 18 | borderRadius: "50px", 19 | borderStyle: "solid", 20 | borderWidth: "3px", 21 | height: "50px", 22 | margin: "calc(50% - 25px) auto 0 auto", 23 | width: "50px", 24 | animation: `${loaderKeyframes} 2s linear infinite` 25 | }, 26 | hidden: { 27 | display: "none" 28 | } 29 | }; 30 | 31 | class Loader extends React.Component { 32 | constructor() { 33 | super(); 34 | this.state = { 35 | visible: false 36 | }; 37 | } 38 | 39 | componentDidMount() { 40 | this.interval = setTimeout( 41 | () => this.setState({ visible: true }), 42 | SHOW_AFTER_MS 43 | ); 44 | } 45 | 46 | componentWillUnmount() { 47 | if (this.interval) { 48 | clearTimeout(this.interval); 49 | } 50 | } 51 | 52 | render() { 53 | const loader = this.state.visible ? styles.spinner : styles.hidden; 54 | 55 | return
; 56 | } 57 | } 58 | 59 | export default Loader; 60 | -------------------------------------------------------------------------------- /packages/core/src/components/Page/NotFound.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | import Page from "./Page"; 4 | 5 | const NotFound = ({ location }) => ( 6 | {`Sorry, no page exists at **${location.pathname}**.`} 7 | ); 8 | 9 | NotFound.propTypes = { 10 | location: PropTypes.object.isRequired 11 | }; 12 | 13 | export default NotFound; 14 | -------------------------------------------------------------------------------- /packages/core/src/components/Page/Page.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { catalogShape } from "../../CatalogPropTypes"; 3 | import PropTypes from "prop-types"; 4 | import renderMarkdown from "../../markdown/renderMarkdown"; 5 | import seqKey from "../../utils/seqKey"; 6 | import MarkdownSpecimen from "../Specimen/MarkdownSpecimen"; 7 | import { css } from "../../emotion"; 8 | 9 | const pageStyle = { 10 | boxSizing: "border-box", 11 | margin: `0 20px 0 20px`, 12 | maxWidth: "64em", 13 | display: "flex", 14 | flexFlow: "row wrap", 15 | padding: `48px 0`, 16 | "@media (min-width: 640px)": { 17 | margin: `0 10px 0 20px` 18 | }, 19 | "@media (min-width: 1000px)": { 20 | margin: `0 30px 0 40px` 21 | }, 22 | "& > :first-child": { 23 | marginTop: 0 24 | } 25 | }; 26 | 27 | class Page extends Component { 28 | render() { 29 | const { children } = this.props; 30 | const { catalog: { getSpecimen } } = this.context; 31 | 32 | const getSpecimenKey = seqKey("Specimen"); 33 | 34 | return ( 35 |
40 | {React.Children.map(children, child => { 41 | const md = 42 | typeof child === "string" 43 | ? renderMarkdown({ 44 | text: child, 45 | renderer: { 46 | code: (body, options) => { 47 | return ( 48 | 54 | ); 55 | } 56 | } 57 | }) 58 | : child; 59 | return md; 60 | })} 61 |
62 | ); 63 | } 64 | } 65 | 66 | Page.propTypes = { 67 | children: PropTypes.node 68 | }; 69 | 70 | Page.contextTypes = { 71 | catalog: catalogShape.isRequired 72 | }; 73 | 74 | export default Page; 75 | -------------------------------------------------------------------------------- /packages/core/src/components/Page/PageHeader.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { css } from "../../emotion"; 4 | import { heading } from "../../styles/typography"; 5 | 6 | class PageHeader extends Component { 7 | render() { 8 | const { theme, title, superTitle } = this.props; 9 | 10 | const styles = { 11 | outerHeader: { 12 | boxSizing: "border-box", 13 | position: "relative", 14 | height: theme.pageHeadingHeight, 15 | background: theme.pageHeadingBackground 16 | }, 17 | innerHeader: { 18 | position: "absolute", 19 | bottom: 21, 20 | left: 21, 21 | "@media (min-width: 1000px)": { 22 | left: 42 23 | } 24 | }, 25 | superTitle: { 26 | ...heading(theme, 1), 27 | color: theme.pageHeadingTextColor, 28 | opacity: 0.6, 29 | margin: 0 30 | }, 31 | title: { 32 | ...heading(theme, 4), 33 | color: theme.pageHeadingTextColor, 34 | margin: 0 35 | } 36 | }; 37 | 38 | return ( 39 |
40 |
41 |

{superTitle}

42 |

{title}

43 |
44 |
45 | ); 46 | } 47 | } 48 | 49 | PageHeader.propTypes = { 50 | theme: PropTypes.object.isRequired, 51 | title: PropTypes.string.isRequired, 52 | superTitle: PropTypes.string.isRequired 53 | }; 54 | 55 | export default PageHeader; 56 | -------------------------------------------------------------------------------- /packages/core/src/components/Page/PageRenderer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import "raf/polyfill"; 3 | 4 | import React, { PureComponent } from "react"; 5 | import { catalogShape } from "../../CatalogPropTypes"; 6 | import Page from "./Page"; 7 | import runscript from "../../utils/runscript"; 8 | 9 | const renderStyles = styles => { 10 | return styles.map((src, i) => ( 11 | 12 | )); 13 | }; 14 | 15 | const renderContent = Content => 16 | typeof Content === "string" ? {Content} : ; 17 | 18 | class PageRenderer extends PureComponent { 19 | constructor() { 20 | super(); 21 | this.jump = this.jump.bind(this); 22 | this.jumpTimeout = null; 23 | } 24 | 25 | componentDidMount() { 26 | this.context.catalog.page.scripts.forEach(runscript); 27 | this.jump(); 28 | } 29 | 30 | componentDidUpdate() { 31 | this.context.catalog.page.scripts.forEach(runscript); 32 | this.jump(); 33 | } 34 | 35 | componentWillUnmount() { 36 | if (this.jumpTimeout !== null) { 37 | cancelAnimationFrame(this.jumpTimeout); 38 | this.jumpTimeout = null; 39 | } 40 | } 41 | 42 | jump() { 43 | const { location: { query: { a }, hash } } = this.props; 44 | 45 | // Hash is always defined, but may be an empty string. But the query param 46 | // is indeed optional and may be undefined. We do not want to be jumping 47 | // to the '#undefined' selector. 48 | 49 | if (hash !== "") { 50 | this.jumpToSelector(hash); 51 | } else if (a !== undefined && a !== "") { 52 | this.jumpToSelector(`#${a}`); 53 | } 54 | } 55 | 56 | jumpToSelector(selector) { 57 | if (this.jumpTimeout !== null) { 58 | cancelAnimationFrame(this.jumpTimeout); 59 | this.jumpTimeout = null; 60 | } 61 | 62 | // Don't freak out when hash is not a valid selector (e.g. #/foo) 63 | try { 64 | const el = document.querySelector(selector); 65 | if (el) { 66 | // Defer scrolling by one tick (when the page has completely rendered) 67 | this.jumpTimeout = requestAnimationFrame(() => { 68 | this.jumpTimeout = null; 69 | el.scrollIntoView(); 70 | }); 71 | } 72 | } catch (e) { 73 | // eslint-disable-line no-empty 74 | } 75 | } 76 | 77 | render() { 78 | const { content } = this.props; 79 | const { catalog: { page: { styles } } } = this.context; 80 | return ( 81 |
82 | {renderStyles(styles)} 83 | {renderContent(content)} 84 |
85 | ); 86 | } 87 | } 88 | 89 | PageRenderer.propTypes = { 90 | content: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).isRequired, 91 | location: PropTypes.object.isRequired 92 | }; 93 | 94 | PageRenderer.contextTypes = { 95 | catalog: catalogShape.isRequired 96 | }; 97 | 98 | export default PageRenderer; 99 | -------------------------------------------------------------------------------- /packages/core/src/components/ResponsiveTabs/Preview.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | import { css } from "../../emotion"; 4 | 5 | /** 6 | * Generates a small preview showing the aspect ratio 7 | */ 8 | 9 | const Preview = ({ proportion }) => { 10 | if (!proportion) null; 11 | return ( 12 |
20 | 21 | 28 | 29 |
30 | ); 31 | }; 32 | 33 | Preview.propTypes = { 34 | proportion: PropTypes.number.isRequired 35 | }; 36 | 37 | export default Preview; 38 | -------------------------------------------------------------------------------- /packages/core/src/components/ResponsiveTabs/ResponsiveTabs.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | import Preview from "./Preview"; 4 | import { css } from "../../emotion"; 5 | import { text } from "../../styles/typography"; 6 | 7 | function getStyle(theme) { 8 | return { 9 | tabContainer: { 10 | background: "white", 11 | display: "flex", 12 | overflowX: "auto", 13 | width: "100%", 14 | flexShrink: 0 15 | }, 16 | tab: { 17 | ...text(theme), 18 | alignItems: "center", 19 | background: "#eee", 20 | boxSizing: "border-box", 21 | color: "#777", 22 | cursor: "pointer", 23 | display: "flex", 24 | lineHeight: theme.msRatio, 25 | flexBasis: "100%", 26 | flexDirection: "row", 27 | padding: "10px", 28 | transition: ".2s background-color, .4s color" 29 | }, 30 | tabActive: { 31 | background: "white", 32 | fontWeight: "bold", 33 | color: theme.brandColor, 34 | cursor: "auto" 35 | }, 36 | description: { 37 | paddingLeft: 5 38 | }, 39 | tabDimension: { 40 | color: "#777", 41 | display: "block", 42 | fontFamily: theme.fontMono, 43 | fontSize: "smaller", 44 | fontWeight: "normal", 45 | marginTop: 2, 46 | opacity: 0.6 47 | } 48 | }; 49 | } 50 | 51 | const ResponsiveTabs = ({ sizes, action, activeSize, theme, parentWidth }) => { 52 | const styles = getStyle(theme); 53 | return ( 54 |
55 | {sizes.map((val, i) => { 56 | const isTabActive = activeSize.name === val.name; 57 | const activeStyles = isTabActive && styles.tabActive; 58 | return ( 59 |
action(val)} 63 | > 64 | 65 |
66 | {val.name} 67 |
68 | {val.width}×{val.height} 69 |   70 | {parentWidth <= val.width && "(scaled)"} 71 |
72 |
73 |
74 | ); 75 | })} 76 |
77 | ); 78 | }; 79 | 80 | ResponsiveTabs.propTypes = { 81 | sizes: PropTypes.array, 82 | action: PropTypes.func, 83 | activeSize: PropTypes.object, 84 | theme: PropTypes.object, 85 | parentWidth: PropTypes.number 86 | }; 87 | 88 | export default ResponsiveTabs; 89 | -------------------------------------------------------------------------------- /packages/core/src/components/Specimen/MarkdownSpecimen.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React, { Component } from "react"; 3 | import Hint from "../../specimens/Hint"; 4 | import parseSpecimenType from "../../utils/parseSpecimenType"; 5 | 6 | // eslint-disable-next-line react/display-name 7 | const getUnknownSpecimen = specimenType => () => ( 8 | {`Unknown Specimen: **${specimenType}**`} 9 | ); 10 | 11 | export default class MarkdownSpecimen extends Component { 12 | render() { 13 | const { options, body, getSpecimen } = this.props; 14 | const specimenType = parseSpecimenType(options); 15 | const Specimen = 16 | getSpecimen(specimenType) || getUnknownSpecimen(specimenType); 17 | 18 | return ; 19 | } 20 | } 21 | 22 | MarkdownSpecimen.propTypes = { 23 | body: PropTypes.string.isRequired, 24 | options: PropTypes.string.isRequired, 25 | getSpecimen: PropTypes.func.isRequired 26 | }; 27 | -------------------------------------------------------------------------------- /packages/core/src/components/Specimen/Span.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { css } from "../../emotion"; 4 | 5 | const Span = ({ span = 6, children }) => { 6 | const style = { 7 | boxSizing: "border-box", 8 | display: "flex", 9 | flexBasis: "100%", 10 | // Bug fix for Firefox; width and flexBasis don't work on horizontally scrolling code blocks 11 | maxWidth: "100%", 12 | flexWrap: "wrap", 13 | margin: "24px 0 0 0", 14 | padding: 0, 15 | position: "relative", 16 | "@media (min-width: 640px)": { 17 | flexBasis: `calc(${(span / 6) * 100}% - 10px)`, 18 | // Bug fix for Firefox; width and flexBasis don't work on horizontally scrolling code blocks 19 | maxWidth: `calc(${(span / 6) * 100}% - 10px)`, 20 | margin: "24px 10px 0 0" 21 | } 22 | }; 23 | return
{children}
; 24 | }; 25 | 26 | Span.propTypes = { 27 | span: PropTypes.oneOf([1, 2, 3, 4, 5, 6]), 28 | children: PropTypes.node 29 | }; 30 | 31 | export default Span; 32 | -------------------------------------------------------------------------------- /packages/core/src/components/Specimen/Specimen.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | // Higher-order Specimen which provides theme 3 | 4 | import React from "react"; 5 | import { catalogShape } from "../../CatalogPropTypes"; 6 | import Span from "./Span"; 7 | import parseSpecimenOptions from "../../utils/parseSpecimenOptions"; 8 | import { 9 | parseSpecimenBody, 10 | parseSpecimenYamlBody 11 | } from "../../utils/parseSpecimenBody"; 12 | 13 | export default function Specimen( 14 | mapBodyToProps, 15 | mapOptionsToProps, 16 | options = {} 17 | ) { 18 | const parseOptions = parseSpecimenOptions(mapOptionsToProps); 19 | const parseBody = options.withChildren 20 | ? parseSpecimenBody(mapBodyToProps) 21 | : parseSpecimenYamlBody(mapBodyToProps); 22 | 23 | return WrappedSpecimen => { 24 | const SpecimenContainer = (props, { catalog }) => { 25 | const { rawOptions, rawBody } = props; 26 | const optionProps = parseOptions(rawOptions); 27 | const bodyProps = parseBody(rawBody, catalog.page.imports); 28 | const span = props.span || bodyProps.span || optionProps.span; 29 | 30 | return ( 31 | 32 | 38 | 39 | ); 40 | }; 41 | 42 | SpecimenContainer.propTypes = { 43 | span: PropTypes.number, 44 | rawBody: PropTypes.string, 45 | rawOptions: PropTypes.string 46 | }; 47 | 48 | SpecimenContainer.contextTypes = { 49 | catalog: catalogShape.isRequired 50 | }; 51 | 52 | return SpecimenContainer; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /packages/core/src/configure.test.js: -------------------------------------------------------------------------------- 1 | import configure from "./configure"; 2 | 3 | test("Configuration with default theme and specimens", () => { 4 | expect( 5 | configure({ 6 | title: "Catalog", 7 | pages: [ 8 | { 9 | path: "/", 10 | title: "Overview", 11 | src: "overview.md" 12 | } 13 | ] 14 | }) 15 | ).toMatchSnapshot(); 16 | }); 17 | 18 | test("Configuration with nested pages", () => { 19 | expect( 20 | configure({ 21 | title: "Catalog", 22 | pages: [ 23 | { 24 | title: "Overview", 25 | pages: [ 26 | { 27 | path: "foo", 28 | title: "Foo", 29 | src: "foo.md" 30 | }, 31 | { 32 | path: "bar", 33 | title: "Bar", 34 | src: "bar.md" 35 | } 36 | ] 37 | } 38 | ] 39 | }) 40 | ).toMatchSnapshot(); 41 | }); 42 | 43 | test("Configuration with component", () => { 44 | expect( 45 | configure({ 46 | title: "Catalog", 47 | pages: [ 48 | { 49 | path: "/", 50 | title: "Overview", 51 | component: () => null 52 | } 53 | ] 54 | }) 55 | ).toMatchSnapshot(); 56 | }); 57 | 58 | test("`content` is aliased to `component`", () => { 59 | const Content = () => null; 60 | const config = configure({ 61 | title: "Catalog", 62 | pages: [ 63 | { 64 | path: "/", 65 | title: "Overview", 66 | content: Content 67 | } 68 | ] 69 | }); 70 | 71 | expect(config.pages[0].component).toBe(Content); 72 | }); 73 | 74 | test("Imports are merged on pages", () => { 75 | const config = configure({ 76 | title: "Catalog", 77 | imports: { Foo: "Foo" }, 78 | pages: [ 79 | { 80 | path: "/", 81 | title: "Overview", 82 | imports: { Bar: "Bar" }, 83 | src: "overview.md" 84 | } 85 | ] 86 | }); 87 | 88 | expect(config.pages[0].imports).toEqual({ Foo: "Foo", Bar: "Bar" }); 89 | }); 90 | 91 | test("basePath support", () => { 92 | const config = configure({ 93 | title: "Catalog", 94 | basePath: "catalog", 95 | pages: [ 96 | { 97 | path: "/", 98 | title: "Overview", 99 | src: "overview.md" 100 | } 101 | ] 102 | }); 103 | 104 | expect(config.basePath).toBe("/catalog"); 105 | expect(config.pages[0].path).toBe("/catalog"); 106 | // Fallback page 107 | expect(config.pages[1].path).toBe("/catalog/*"); 108 | }); 109 | 110 | test("publicUrl support with hash history", () => { 111 | const config = configure({ 112 | title: "Catalog", 113 | publicUrl: "https://foo.bar/baz/", 114 | pages: [ 115 | { 116 | path: "/", 117 | title: "Overview", 118 | src: "overview.md" 119 | } 120 | ] 121 | }); 122 | 123 | expect(config.basePath).toBe("/"); 124 | expect(config.publicUrl).toBe("https://foo.bar/baz"); // no trailing slash 125 | expect(config.pages[0].path).toBe("/"); // It's hash history! 126 | // Fallback page 127 | expect(config.pages[1].path).toBe("/*"); 128 | }); 129 | 130 | test("publicUrl support with browser history", () => { 131 | const config = configure({ 132 | title: "Catalog", 133 | useBrowserHistory: true, 134 | publicUrl: "https://foo.bar/baz/", 135 | pages: [ 136 | { 137 | path: "/", 138 | title: "Overview", 139 | src: "overview.md" 140 | } 141 | ] 142 | }); 143 | 144 | expect(config.basePath).toBe("/baz"); 145 | expect(config.publicUrl).toBe("https://foo.bar/baz"); // no trailing slash 146 | expect(config.pages[0].path).toBe("/baz"); 147 | // Fallback page 148 | expect(config.pages[1].path).toBe("/baz/*"); 149 | }); 150 | -------------------------------------------------------------------------------- /packages/core/src/configureRoutes.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route } from "react-router"; 3 | import configure from "./configure"; 4 | import warning from "./utils/warning"; 5 | import requireModuleDefault from "./utils/requireModuleDefault"; 6 | import CatalogContext from "./components/CatalogContext"; 7 | import pageLoader from "./pageLoader"; 8 | 9 | const pageToRoute = ({ path, component, src }) => ({ 10 | component: component ? requireModuleDefault(component) : pageLoader(src), 11 | path 12 | }); 13 | 14 | // eslint-disable-next-line react/prop-types 15 | const pageToJSXRoute = ({ path, component, src }) => ( 16 | 21 | ); 22 | 23 | const autoConfigure = config => { 24 | warning( 25 | !config.__catalogConfig, 26 | "The `configure` function is deprecated; use `configureRoutes` or `configureJSXRoutes` directly." 27 | ); 28 | 29 | return config.__catalogConfig ? config : configure(config); 30 | }; 31 | 32 | export default config => { 33 | const finalConfig = autoConfigure(config); 34 | return { 35 | component: CatalogContext(finalConfig), 36 | childRoutes: finalConfig.pages.map(pageToRoute) 37 | }; 38 | }; 39 | 40 | export const configureJSXRoutes = config => { 41 | const finalConfig = autoConfigure(config); 42 | return ( 43 | 44 | {finalConfig.pages.map(pageToJSXRoute)} 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /packages/core/src/configureRoutes.test.js: -------------------------------------------------------------------------------- 1 | import configure from "./configure"; 2 | import configureRoutes from "./configureRoutes"; 3 | 4 | test("Pre-Configuration", () => { 5 | const routes = configureRoutes( 6 | configure({ 7 | title: "Catalog", 8 | pages: [ 9 | { 10 | path: "/", 11 | title: "Overview", 12 | pages: [ 13 | { 14 | path: "foo", 15 | title: "Foo", 16 | src: "foo.md" 17 | }, 18 | { 19 | path: "bar", 20 | title: "Bar", 21 | src: "bar.md" 22 | } 23 | ] 24 | } 25 | ] 26 | }) 27 | ); 28 | 29 | expect(routes.childRoutes.length).toBe(3); 30 | expect(routes.childRoutes[0].path).toBe("/foo"); 31 | }); 32 | 33 | test("Auto-Configuration", () => { 34 | const routes = configureRoutes({ 35 | title: "Catalog", 36 | pages: [ 37 | { 38 | path: "/", 39 | title: "Overview", 40 | pages: [ 41 | { 42 | path: "foo", 43 | title: "Foo", 44 | src: "foo.md" 45 | }, 46 | { 47 | path: "bar", 48 | title: "Bar", 49 | src: "bar.md" 50 | } 51 | ] 52 | } 53 | ] 54 | }); 55 | 56 | expect(routes.childRoutes.length).toBe(3); 57 | expect(routes.childRoutes[0].path).toBe("/foo"); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/core/src/emotion.js: -------------------------------------------------------------------------------- 1 | import createEmotion from "create-emotion"; 2 | 3 | const context = typeof global !== "undefined" ? global : {}; 4 | 5 | if (context.__CATALOG_EMOTION_INSTANCE__ === undefined) { 6 | context.__CATALOG_EMOTION_INSTANCE__ = {}; 7 | } 8 | 9 | export const { 10 | flush, 11 | hydrate, 12 | cx, 13 | merge, 14 | getRegisteredStyles, 15 | injectGlobal, 16 | keyframes, 17 | css, 18 | sheet, 19 | caches 20 | } = createEmotion(context.__CATALOG_EMOTION_INSTANCE__, { 21 | // The key option is required when there will be multiple instances in a single app 22 | key: "catalog" 23 | }); 24 | -------------------------------------------------------------------------------- /packages/core/src/index.js: -------------------------------------------------------------------------------- 1 | // Configuration 2 | export { default as render } from "./render"; 3 | export { default as configure } from "./configure"; 4 | export { default as configureRoutes } from "./configureRoutes"; 5 | export { configureJSXRoutes } from "./configureRoutes"; 6 | export { default as markdown } from "./markdownPage"; 7 | export { default as pageLoader } from "./pageLoader"; 8 | export { default as DefaultTheme } from "./DefaultTheme"; 9 | export { default as DefaultResponsiveSizes } from "./DefaultResponsiveSizes"; 10 | 11 | // Components 12 | export { default as Catalog } from "./components/Catalog"; 13 | export { default as Card } from "./components/Card/Card"; 14 | export { default as Page } from "./components/Page/Page"; 15 | export { default as Span } from "./components/Specimen/Span"; 16 | 17 | // Export for use in loader 18 | export { default as PageRenderer } from "./components/Page/PageRenderer"; 19 | 20 | // Higher-order component for creating specimens 21 | export { default as Specimen } from "./components/Specimen/Specimen"; 22 | export { default as mapSpecimenOption } from "./utils/mapSpecimenOption"; 23 | 24 | // Content blocks 25 | import Heading from "./components/Content/Heading"; 26 | import * as _Markdown from "./components/Content/Markdown"; 27 | 28 | export const Markdown = { ..._Markdown, Heading }; 29 | 30 | // Specimens 31 | export { default as AudioSpecimen } from "./specimens/Audio"; 32 | export { default as CodeSpecimen } from "./specimens/Code"; 33 | export { default as ColorSpecimen } from "./specimens/Color"; 34 | export { default as ColorPaletteSpecimen } from "./specimens/ColorPalette"; 35 | export { default as HtmlSpecimen } from "./specimens/Html"; 36 | export { default as HintSpecimen } from "./specimens/Hint"; 37 | export { default as ImageSpecimen } from "./specimens/Image"; 38 | export { default as TableSpecimen } from "./specimens/Table"; 39 | export { default as TypeSpecimen } from "./specimens/Type"; 40 | export { default as DownloadSpecimen } from "./specimens/Download"; 41 | export { 42 | default as ReactSpecimen 43 | } from "./specimens/ReactSpecimen/ReactSpecimen"; 44 | export { default as VideoSpecimen } from "./specimens/Video"; 45 | -------------------------------------------------------------------------------- /packages/core/src/markdown/ReactRenderer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Slugger from "github-slugger"; 3 | import Heading from "../components/Content/Heading"; 4 | import { 5 | Paragraph, 6 | UnorderedList, 7 | OrderedList, 8 | ListItem, 9 | BlockQuote, 10 | Hr, 11 | Em, 12 | Strong, 13 | CodeSpan, 14 | Del, 15 | Image, 16 | Link 17 | } from "../components/Content/Markdown"; 18 | 19 | export default class ReactRenderer { 20 | constructor() { 21 | this.slugger = new Slugger(); 22 | this.itemsRenderedCount = 0; 23 | } 24 | getKey() { 25 | return this.itemsRenderedCount++; 26 | } 27 | code(code, lang /* , escaped*/) { 28 | return ( 29 |
 30 |         {code}
 31 |       
32 | ); 33 | } 34 | blockquote(quote) { 35 | return
{quote}
; 36 | } 37 | heading(text, level, raw) { 38 | const slug = this.slugger.slug(raw); 39 | return ; 40 | } 41 | hr() { 42 | return
; 43 | } 44 | br() { 45 | return
; 46 | } 47 | list(body, ordered) { 48 | const key = this.getKey(); 49 | return ordered ? ( 50 | {body} 51 | ) : ( 52 | {body} 53 | ); 54 | } 55 | listitem(text) { 56 | return {text}; 57 | } 58 | paragraph(text) { 59 | return {text}; 60 | } 61 | table(header, body) { 62 | return ( 63 | 64 | {header} 65 | {body} 66 |
67 | ); 68 | } 69 | tablerow(content) { 70 | return {content}; 71 | } 72 | tablecell(content) { 73 | return {content}; 74 | } 75 | strong(content) { 76 | return {content}; 77 | } 78 | em(content) { 79 | return {content}; 80 | } 81 | codespan(content) { 82 | return {content}; 83 | } 84 | del(content) { 85 | return {content}; 86 | } 87 | link(href, title, text) { 88 | return ( 89 | 90 | {text} 91 | 92 | ); 93 | } 94 | image(href, title, alt) { 95 | return {alt}; 96 | } 97 | html(html) { 98 | return ( 99 |
104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /packages/core/src/markdown/__snapshots__/renderMarkdown.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Renders some Markdown 1`] = ` 4 | Array [ 5 | , 14 | 15 | World 16 | , 17 | "", 18 | 19 | 20 | One 21 | 22 | 23 | Two 24 | 25 | 26 | 27 | 28 | Three 29 | 30 | 31 | , 32 | 33 | A 34 | 38 | link 39 | 40 | and some 41 | 42 | bold 43 | 44 | and 45 | 46 | italic 47 | 48 | text. Blank links 49 | 53 | foo@bar.com 54 | 55 | 56 | 60 | https://foobar.com 61 | 62 | , 63 | "", 64 |
 65 |     
 66 |       a code block
 67 |     
 68 |   
, 69 | 70 | Inline 71 | 72 | code 73 | 74 | , 75 | "", 76 |
77 | 78 | Block quotes rock 79 | 80 | another em style 81 | 82 | 83 |
, 84 | ] 85 | `; 86 | 87 | exports[`Renders some Markdown with custom renderers 1`] = ` 88 | Array [ 89 | , 98 | "HELLO PARAGRAPH", 99 | "", 100 | 101 | HELLO LIST ITEM 102 | HELLO LIST ITEM 103 | , 104 | "HELLO PARAGRAPH", 105 | "", 106 |
107 |     
108 |       a code block
109 |     
110 |   
, 111 | "HELLO PARAGRAPH", 112 | "", 113 |
114 | HELLO PARAGRAPH 115 |
, 116 | ] 117 | `; 118 | -------------------------------------------------------------------------------- /packages/core/src/markdown/renderMarkdown.js: -------------------------------------------------------------------------------- 1 | import marked from "./marked-react"; 2 | import ReactRenderer from "./ReactRenderer"; 3 | 4 | let MARKDOWN_CONFIG = { 5 | gfm: true, 6 | breaks: true, 7 | sanitize: false, 8 | smartLists: true, 9 | smartypants: true 10 | }; 11 | 12 | export default ({ text, renderer }) => { 13 | return marked(text, { 14 | ...MARKDOWN_CONFIG, 15 | renderer: Object.assign(new ReactRenderer(), renderer) 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/core/src/markdown/renderMarkdown.test.js: -------------------------------------------------------------------------------- 1 | import renderMarkdown from "./renderMarkdown"; 2 | 3 | const md = ` 4 | # Hello 5 | 6 | World 7 | 8 | - One 9 | - Two 10 | - Three 11 | 12 | A [link](http://www.interactivethings.com/) and some **bold** and *italic* text. Blank links foo@bar.com https://foobar.com 13 | 14 | ~~~ 15 | a code block 16 | ~~~ 17 | 18 | Inline \`code\` 19 | 20 | > Block quotes rock _another em style_ 21 | `; 22 | 23 | test("Renders some Markdown", () => { 24 | expect( 25 | renderMarkdown({ 26 | text: md 27 | }) 28 | ).toMatchSnapshot(); 29 | }); 30 | 31 | test("Renders some Markdown with custom renderers", () => { 32 | expect( 33 | renderMarkdown({ 34 | text: md, 35 | renderer: { 36 | paragraph() { 37 | return "HELLO PARAGRAPH"; 38 | }, 39 | listitem() { 40 | return "HELLO LIST ITEM"; 41 | } 42 | } 43 | }) 44 | ).toMatchSnapshot(); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/core/src/markdownPage.js: -------------------------------------------------------------------------------- 1 | import { createElement, isValidElement } from "react"; 2 | import Page from "./components/Page/Page"; 3 | 4 | // This function simply intersperses the values between the strings, and 5 | // passes the result as children to the Page component. No further 6 | // transformation is done, the Page component itself processes strings 7 | // as markdown. 8 | // 9 | // The most we could do at this point is to parse strings into MDAST (or 10 | // a similar abstract form). We can't convert the markdown text into React 11 | // elements because that requires the Catalog context. 12 | // 13 | // Values SHOULD be React Elements, strings, numbers, or anything 14 | // stringifiable. The primary use case is to allow 15 | // developers to easily instantiate React components in plain JavaScript, 16 | // so that type checkers (flow, typescript) can verify that the correct 17 | // props are pased to the component. 18 | // 19 | // > import {HintSpecimen, markdown} from 'catalog'; 20 | // > export const catalogPage = markdown` 21 | // > # This is a page 22 | // > 23 | // > With a paragraph. And a number ${123} 24 | // > 25 | // > ${And a hint} 26 | // > 27 | // > ${} 28 | // > `; 29 | 30 | const replaceLast = (f, arr) => { 31 | arr[arr.length - 1] = f(arr[arr.length - 1]); 32 | return arr; 33 | }; 34 | 35 | const markdownPage = (strings, ...values) => 36 | createElement( 37 | Page, 38 | {}, 39 | ...values.reduce( 40 | (a, v, i) => { 41 | // If it's a valid React element or array, just concat to the end of the array 42 | if (isValidElement(v) || Array.isArray(v)) { 43 | return a.concat(v, strings[i + 1]); 44 | } 45 | 46 | // String-concat v to last and next string part 47 | if (typeof v === "string" || typeof v === "number") { 48 | return replaceLast(last => last + v + strings[i + 1], a); 49 | } 50 | 51 | // Finally, try to stringify v 52 | return replaceLast( 53 | last => last + JSON.stringify(v) + strings[i + 1], 54 | a 55 | ); 56 | }, 57 | [strings[0]] 58 | ) 59 | ); 60 | 61 | export default markdownPage; 62 | -------------------------------------------------------------------------------- /packages/core/src/markdownPage.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import markdownPage from "./markdownPage"; 3 | import Hint from "./specimens/Hint"; 4 | 5 | test("Catalog markdown page template literal", () => { 6 | const page = markdownPage` 7 | # This is a title 8 | 9 | A paragraph 10 | 11 | ${( 12 | 13 | {`# THIS IS A HINT 14 | 15 | Cool, eh?`} 16 | 17 | )} 18 | `; 19 | expect(page).toMatchSnapshot(); 20 | }); 21 | 22 | test("Catalog markdown page template literal with arbitrary values", () => { 23 | const page = markdownPage` 24 | # This is a title 25 | 26 | A paragraph ${123} 27 | 28 | ~~~ 29 | ${["foo", "bar",
hello
]} 30 | ~~~ 31 | `; 32 | expect(page).toMatchSnapshot(); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/core/src/pageLoader.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import Loader from "./components/Page/Loader"; 5 | import PageRenderer from "./components/Page/PageRenderer"; 6 | import Page from "./components/Page/Page"; 7 | import requireModuleDefault from "./utils/requireModuleDefault"; 8 | 9 | const fetchMarkdown = url => 10 | fetch(url, { 11 | credentials: "same-origin", 12 | headers: { 13 | Accept: "text/markdown, text/x-markdown, text/plain" 14 | } 15 | }).then(res => { 16 | if (res.status < 200 || res.status >= 300) { 17 | throw new Error(`Failed to load content from 18 | 19 | \`${url}\`. 20 | 21 | Reason: ${res.status} ${res.statusText}`); 22 | } 23 | return res.text(); 24 | }); 25 | 26 | // The contents of the page when loading the page fails. 'msg' is the error 27 | // string or message with additional details. 28 | const errorMarkdown = msg => ` 29 | \`\`\`hint|warning 30 | ${msg} 31 | \`\`\` 32 | `; 33 | 34 | class PageLoader extends PureComponent { 35 | constructor() { 36 | super(); 37 | this.state = { content: null }; 38 | } 39 | 40 | componentDidMount() { 41 | this.fetchPageContent(); 42 | } 43 | 44 | fetchPageContent() { 45 | const { urlOrComponentPromise } = this.props; 46 | 47 | const contentPromise = 48 | typeof urlOrComponentPromise === "string" 49 | ? fetchMarkdown(urlOrComponentPromise).then(text => () => ( 50 | {text} 51 | )) 52 | : urlOrComponentPromise().then(requireModuleDefault); 53 | 54 | contentPromise.then( 55 | content => { 56 | this.setState({ content }); 57 | }, 58 | err => { 59 | this.setState({ content: errorMarkdown(err.message) }); 60 | } 61 | ); 62 | } 63 | 64 | render() { 65 | const { location } = this.props; 66 | const Content = this.state.content || Loader; 67 | return Content.__catalog_loader__ === true ? ( 68 | 69 | ) : ( 70 | 71 | ); 72 | } 73 | } 74 | 75 | PageLoader.propTypes = { 76 | urlOrComponentPromise: PropTypes.any.isRequired, 77 | location: PropTypes.object.isRequired 78 | }; 79 | 80 | // eslint-disable-next-line react/display-name 81 | export default urlOrComponentPromise => ( 82 | { location } // eslint-disable-line react/prop-types 83 | ) => ( 84 | 88 | ); 89 | -------------------------------------------------------------------------------- /packages/core/src/render.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import Catalog from "./components/Catalog"; 4 | 5 | export default (configuration, element) => { 6 | ReactDOM.render(, element); 7 | }; 8 | -------------------------------------------------------------------------------- /packages/core/src/specimens.js: -------------------------------------------------------------------------------- 1 | import RawCode from "./specimens/RawCode"; 2 | import Audio from "./specimens/Audio"; 3 | import Code from "./specimens/Code"; 4 | import Color from "./specimens/Color"; 5 | import ColorPalette from "./specimens/ColorPalette"; 6 | import Html from "./specimens/Html"; 7 | import Hint from "./specimens/Hint"; 8 | import Image from "./specimens/Image"; 9 | import Table from "./specimens/Table"; 10 | import Type from "./specimens/Type"; 11 | import Download from "./specimens/Download"; 12 | import Video from "./specimens/Video"; 13 | import ReactSpecimen from "./specimens/ReactSpecimen/ReactSpecimen"; 14 | 15 | export default { 16 | "raw-code": RawCode, 17 | audio: Audio, 18 | code: Code, 19 | color: Color, 20 | "color-palette": ColorPalette, 21 | html: Html, 22 | hint: Hint, 23 | image: Image, 24 | table: Table, 25 | type: Type, 26 | download: Download, 27 | video: Video, 28 | react: ReactSpecimen 29 | }; 30 | -------------------------------------------------------------------------------- /packages/core/src/specimens/Audio.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { catalogShape } from "../CatalogPropTypes"; 3 | import PropTypes from "prop-types"; 4 | import { css } from "../emotion"; 5 | import Specimen from "../components/Specimen/Specimen"; 6 | import { getPublicPath } from "../utils/path"; 7 | 8 | import { heading } from "../styles/typography"; 9 | 10 | class Audio extends React.Component { 11 | render() { 12 | const { 13 | src, 14 | title, 15 | loop, 16 | autoplay, 17 | catalog, 18 | catalog: { theme } 19 | } = this.props; 20 | const parsedSrc = getPublicPath(src, catalog); 21 | 22 | const styles = { 23 | section: { 24 | display: "flex", 25 | flexFlow: "row wrap", 26 | minWidth: "calc(100% + 10px)" 27 | }, 28 | title: { 29 | ...heading(theme, 1), 30 | margin: 0 31 | }, 32 | container: { 33 | width: "100%", 34 | background: theme.background 35 | } 36 | }; 37 | 38 | const audioTitle = 39 | title !== undefined ? title : parsedSrc.split("/").slice(-1)[0]; 40 | 41 | return ( 42 |
43 |
{audioTitle}
44 |
52 | ); 53 | } 54 | } 55 | 56 | Audio.propTypes = { 57 | catalog: catalogShape.isRequired, 58 | src: PropTypes.string.isRequired, 59 | title: PropTypes.string, 60 | loop: PropTypes.bool, 61 | autoplay: PropTypes.bool 62 | }; 63 | 64 | Audio.defaultProps = { 65 | loop: false, 66 | autoplay: false 67 | }; 68 | 69 | export default Specimen()(Audio); 70 | -------------------------------------------------------------------------------- /packages/core/src/specimens/Code.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { catalogShape } from "../CatalogPropTypes"; 3 | import { text } from "../styles/typography"; 4 | import PropTypes from "prop-types"; 5 | import { css } from "../emotion"; 6 | import Specimen from "../components/Specimen/Specimen"; 7 | import mapSpecimenOption from "../utils/mapSpecimenOption"; 8 | import HighlightedCode from "../components/HighlightedCode/HighlightedCode"; 9 | 10 | function getStyle(theme) { 11 | return { 12 | container: { 13 | ...text(theme, -0.5), 14 | boxSizing: "border-box", 15 | display: "block", 16 | width: "100%", 17 | background: "#fff", 18 | border: "1px solid #eee", 19 | color: theme.textColor, 20 | fontFamily: theme.fontMono, 21 | fontWeight: 400 22 | }, 23 | toggle: { 24 | textDecoration: "underline", 25 | cursor: "pointer", 26 | marginBottom: 0, 27 | padding: 20, 28 | WebkitUserSelect: "none", 29 | userSelect: "none", 30 | background: "#eee" 31 | } 32 | }; 33 | } 34 | 35 | class Code extends React.Component { 36 | constructor(props) { 37 | super(props); 38 | this.state = { 39 | viewSource: props.collapsed ? false : true 40 | }; 41 | } 42 | 43 | render() { 44 | const { 45 | catalog: { theme }, 46 | children, 47 | rawBody, 48 | collapsed, 49 | lang, 50 | raw 51 | } = this.props; 52 | const { viewSource } = this.state; 53 | const styles = getStyle(theme); 54 | 55 | const toggle = collapsed ? ( 56 |
this.setState({ viewSource: !viewSource })} 59 | > 60 | {viewSource ? "close" : "show example code"} 61 |
62 | ) : null; 63 | 64 | const content = viewSource ? ( 65 | 70 | ) : null; 71 | 72 | return ( 73 |
74 | {toggle} 75 | {content} 76 |
77 | ); 78 | } 79 | } 80 | 81 | Code.propTypes = { 82 | children: PropTypes.string.isRequired, 83 | rawBody: PropTypes.string.isRequired, 84 | catalog: catalogShape.isRequired, 85 | collapsed: PropTypes.bool, 86 | lang: PropTypes.string, 87 | raw: PropTypes.bool 88 | }; 89 | 90 | const mapOptionsToProps = mapSpecimenOption(/^lang-(\w+)$/, lang => ({ lang })); 91 | 92 | const mapBodyToProps = (parsed, rawBody) => ({ ...parsed, rawBody }); 93 | 94 | export default Specimen(mapBodyToProps, mapOptionsToProps, { 95 | withChildren: true 96 | })(Code); 97 | -------------------------------------------------------------------------------- /packages/core/src/specimens/Color.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { catalogShape } from "../CatalogPropTypes"; 3 | import PropTypes from "prop-types"; 4 | import { css } from "../emotion"; 5 | import { text } from "../styles/typography"; 6 | import Specimen from "../components/Specimen/Specimen"; 7 | 8 | class Color extends React.Component { 9 | render() { 10 | const { 11 | catalog: { theme }, 12 | value, 13 | name 14 | } = this.props; 15 | const styles = { 16 | text: { 17 | ...text(theme), 18 | boxSizing: "border-box", 19 | padding: "8px 0", 20 | background: theme.background 21 | } 22 | }; 23 | 24 | return ( 25 |
26 |
27 |
28 | {name}{" "} 29 |
{value}
30 |
31 |
32 | ); 33 | } 34 | } 35 | 36 | Color.propTypes = { 37 | catalog: catalogShape.isRequired, 38 | value: PropTypes.string.isRequired, 39 | name: PropTypes.string 40 | }; 41 | 42 | export default Specimen()(Color); 43 | -------------------------------------------------------------------------------- /packages/core/src/specimens/ColorPalette.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { catalogShape } from "../CatalogPropTypes"; 3 | import PropTypes from "prop-types"; 4 | import { css } from "../emotion"; 5 | import { text } from "../styles/typography"; 6 | import Specimen from "../components/Specimen/Specimen"; 7 | import { hcl } from "d3-color"; 8 | 9 | const _ColorPaletteItem = ({ name, value, styles, width }) => { 10 | const contrastingValue = hcl(value).l < 55 ? "#fff" : "#000"; 11 | return ( 12 |
15 |
16 | {name}
{value}
17 |
18 |
19 | ); 20 | }; 21 | 22 | _ColorPaletteItem.propTypes = { 23 | name: PropTypes.string, 24 | value: PropTypes.string.isRequired, 25 | styles: PropTypes.object, 26 | width: PropTypes.string 27 | }; 28 | 29 | const ColorPaletteItem = _ColorPaletteItem; 30 | 31 | class ColorPalette extends React.Component { 32 | render() { 33 | const { 34 | catalog: { theme }, 35 | colors, 36 | horizontal 37 | } = this.props; 38 | const styles = { 39 | container: { 40 | width: "100%", 41 | overflow: "hidden" 42 | }, 43 | mono: { 44 | fontFamily: theme.fontMono 45 | }, 46 | paletteItem: { 47 | float: "left", 48 | boxSizing: "border-box", 49 | padding: "20px 10px", 50 | "@media (max-width: 640px)": { 51 | width: "100%", 52 | float: "none" 53 | } 54 | }, 55 | textPalette: { 56 | ...text(theme), 57 | fontFamily: theme.fontFamily, 58 | color: theme.textColor, 59 | textOverflow: "ellipsis", 60 | whiteSpace: "nowrap", 61 | overflow: "hidden", 62 | opacity: 0.55, 63 | ":hover": { 64 | opacity: 1 65 | } 66 | }, 67 | info: { 68 | alignSelf: "flex-start", 69 | flex: "1 1 auto", 70 | width: "7em" 71 | } 72 | }; 73 | 74 | const width = `${horizontal ? 100 / colors.length : 100}%`; 75 | const paletteItems = colors.map((color, i) => ( 76 | 77 | )); 78 | 79 | return
{paletteItems}
; 80 | } 81 | } 82 | 83 | ColorPalette.propTypes = { 84 | catalog: catalogShape.isRequired, 85 | colors: PropTypes.arrayOf( 86 | PropTypes.shape({ 87 | name: PropTypes.string, 88 | value: PropTypes.string.isRequired 89 | }) 90 | ).isRequired, 91 | horizontal: PropTypes.bool 92 | }; 93 | 94 | ColorPalette.defaultProps = { 95 | horizontal: false 96 | }; 97 | 98 | export default Specimen()(ColorPalette); 99 | -------------------------------------------------------------------------------- /packages/core/src/specimens/RawCode.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Code from "./Code"; 3 | 4 | const RawCode = props => ; 5 | 6 | export default RawCode; 7 | -------------------------------------------------------------------------------- /packages/core/src/specimens/ReactSpecimen/reactElementToString.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | /* 4 | 5 | Turns a React element into its JSX string representation. 6 | 7 | Probably not complete 8 | 9 | Features: 10 | 11 | - Uses self-closing tags when no children are set 12 | - Uses a single line for one/none prop 13 | - Uses multiple lines for multiple props 14 | - Sorts props alphabetically 15 | - Removes defaultProps from output 16 | 17 | Needs work: 18 | 19 | - Don't use JSON.stringify: Nested objects are rendered as JSON (e.g.
) 20 | 21 | */ 22 | 23 | const reactElementToString = (el, indent = "") => { 24 | if (el === void 0) { 25 | return ""; 26 | } 27 | 28 | if (typeof el === "string") { 29 | return `${indent}${el}`; 30 | } 31 | 32 | const { props, type } = el; 33 | let displayName = ""; 34 | let defaultProps = null; 35 | 36 | if (typeof type === "string") { 37 | displayName = type; 38 | } else { 39 | displayName = type.displayName || type.name; 40 | defaultProps = type.defaultProps; 41 | } 42 | 43 | const formatProp = (k, v) => { 44 | if (v === true) { 45 | return k; 46 | } 47 | if (typeof v === "string") { 48 | return `${k}='${v}'`; 49 | } 50 | if (React.isValidElement(v)) { 51 | return `${k}={${reactElementToString(v)}}`; 52 | } 53 | return `${k}={${JSON.stringify(v) || v.name || typeof v}}`; 54 | }; 55 | 56 | const propKeys = Object.keys(props) 57 | .sort() 58 | .filter(k => k !== "children") 59 | .filter(k => props[k] !== undefined) 60 | .filter(k => (defaultProps ? props[k] !== defaultProps[k] : true)); 61 | 62 | let propString = ""; 63 | try { 64 | propString = propKeys 65 | .map(k => formatProp(k, props[k])) 66 | .join(`\n${indent} `); 67 | } catch (e) { 68 | return `Couldn't stringify React Element. Try setting \`sourceText\` explicitly or use \`noSource\`. 69 | 70 | ${e}`; 71 | } 72 | 73 | const whitespaceBeforeProps = 74 | propKeys.length > 1 // eslint-disable-line no-nested-ternary 75 | ? `\n${indent} ` 76 | : propKeys.length === 1 ? " " : ""; 77 | const whitespaceAfterProps = propKeys.length > 1 ? `\n${indent}` : ""; 78 | 79 | return props.children 80 | ? `${indent}<${displayName}${whitespaceBeforeProps}${propString}${whitespaceAfterProps}> 81 | ${React.Children.map(props.children, c => 82 | reactElementToString(c, `${indent} `) 83 | ).join("\n")} 84 | ${indent}` 85 | : `${indent}<${displayName}${whitespaceBeforeProps}${propString}${whitespaceAfterProps} />`; 86 | }; 87 | 88 | export default reactElementToString; 89 | -------------------------------------------------------------------------------- /packages/core/src/specimens/ReactSpecimen/reactElementToString.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import reactElementToString from "./reactElementToString"; 3 | 4 | test("Children", () => { 5 | expect(reactElementToString(
foo
)).toBe("
\n foo\n
"); 6 | }); 7 | 8 | test("Nested Children", () => { 9 | expect( 10 | reactElementToString( 11 |
12 |
foo
13 |
14 | ) 15 | ).toBe("
\n
\n foo\n
\n
"); 16 | }); 17 | 18 | test("No Children", () => { 19 | expect(reactElementToString(
)).toBe("
"); 20 | }); 21 | 22 | test("Prop formatting", () => { 23 | expect(reactElementToString(
)).toBe("
"); 24 | expect(reactElementToString(
)).toBe("
"); 25 | expect(reactElementToString(
)).toBe( 26 | "" 27 | ); 28 | expect(reactElementToString(
)).toBe("
"); 29 | expect(reactElementToString(
)).toBe( 30 | "" 31 | ); 32 | }); 33 | 34 | test("Skip stringification on error", () => { 35 | let foo = {}; 36 | foo.foo = foo; 37 | expect(reactElementToString(
)).toEqual( 38 | expect.stringContaining(`Couldn't stringify React Element`) 39 | ); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/core/src/specimens/Table.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { catalogShape } from "../CatalogPropTypes"; 3 | import PropTypes from "prop-types"; 4 | import { css } from "../emotion"; 5 | import Specimen from "../components/Specimen/Specimen"; 6 | import { text } from "../styles/typography"; 7 | import renderMarkdown from "../markdown/renderMarkdown"; 8 | 9 | function getStyle(theme) { 10 | return { 11 | container: { 12 | flexBasis: "100%", 13 | overflow: "auto", 14 | paddingBottom: "10px" 15 | }, 16 | table: { 17 | borderCollapse: "collapse", 18 | lineHeight: "auto", 19 | width: "100%", 20 | borderBottom: `none` 21 | }, 22 | tableRow: { 23 | borderBottom: `1px solid ${theme.lightColor}` 24 | }, 25 | head: { 26 | fontWeight: "bold", 27 | borderBottom: `2px solid ${theme.lightColor}` 28 | }, 29 | cell: { 30 | ...text(theme), 31 | padding: "16px 16px 16px 0 ", 32 | textAlign: "left", 33 | verticalAlign: "top", 34 | ":last-child": { padding: "16px 0" }, 35 | "& > :first-child": { marginTop: 0 }, 36 | "& > :last-child": { marginBottom: 0 } 37 | } 38 | }; 39 | } 40 | 41 | const Cell = ({ value, style }) => { 42 | let content; 43 | if (typeof value === "string" || typeof value === "number") { 44 | content = renderMarkdown({ text: value.toString() }); 45 | } else if (value === void 0) { 46 | content = ; 47 | } else { 48 | content = value; 49 | } 50 | 51 | return {content}; 52 | }; 53 | 54 | Cell.propTypes = { 55 | value: PropTypes.node, 56 | style: PropTypes.object.isRequired 57 | }; 58 | 59 | const HeadingCell = ({ value, style }) => ( 60 | {value} 61 | ); 62 | 63 | HeadingCell.propTypes = Cell.propTypes; 64 | 65 | class Table extends React.Component { 66 | render() { 67 | const { 68 | columns, 69 | rows, 70 | catalog: { theme } 71 | } = this.props; 72 | const { cell, container, table, head, tableRow } = getStyle(theme); 73 | 74 | const tableKeys = columns 75 | ? columns 76 | : rows 77 | .reduce((index, row) => index.concat(Object.keys(row)), []) 78 | .filter((value, i, self) => self.indexOf(value) === i); 79 | 80 | return ( 81 |
82 | 83 | 84 | 85 | {tableKeys.map((key, k) => ( 86 | 87 | ))} 88 | 89 | 90 | 91 | {rows.map((row, i) => ( 92 | 93 | {tableKeys.map((key, k) => ( 94 | 95 | ))} 96 | 97 | ))} 98 | 99 |
100 |
101 | ); 102 | } 103 | } 104 | 105 | Table.propTypes = { 106 | catalog: catalogShape.isRequired, 107 | rows: PropTypes.arrayOf(PropTypes.object).isRequired, 108 | columns: PropTypes.arrayOf(PropTypes.string) 109 | }; 110 | 111 | Table.defaultProps = {}; 112 | export default Specimen(undefined, undefined, { withChildren: false })(Table); 113 | -------------------------------------------------------------------------------- /packages/core/src/specimens/Video.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { catalogShape } from "../CatalogPropTypes"; 3 | import PropTypes from "prop-types"; 4 | import { css } from "../emotion"; 5 | import Specimen from "../components/Specimen/Specimen"; 6 | import { getPublicPath } from "../utils/path"; 7 | 8 | import { heading } from "../styles/typography"; 9 | 10 | class Video extends React.Component { 11 | render() { 12 | const { 13 | src, 14 | title, 15 | muted, 16 | loop, 17 | autoplay, 18 | catalog, 19 | catalog: { theme } 20 | } = this.props; 21 | const parsedSrc = getPublicPath(src, catalog); 22 | let styles = { 23 | section: { 24 | display: "flex", 25 | flexFlow: "row wrap", 26 | width: "100%" 27 | }, 28 | container: { 29 | boxSizing: "border-box", 30 | margin: "0 10px 10px 0", 31 | padding: 0, 32 | position: "relative" 33 | }, 34 | title: { 35 | ...heading(theme, 1), 36 | margin: 0 37 | } 38 | }; 39 | 40 | return ( 41 |
42 | 56 | {title &&
{title}
} 57 |
58 | ); 59 | } 60 | } 61 | 62 | Video.propTypes = { 63 | catalog: catalogShape.isRequired, 64 | src: PropTypes.string.isRequired, 65 | title: PropTypes.string, 66 | muted: PropTypes.bool, 67 | loop: PropTypes.bool, 68 | autoplay: PropTypes.bool 69 | }; 70 | 71 | export default Specimen()(Video); 72 | -------------------------------------------------------------------------------- /packages/core/src/styled.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { css, cx } from "./emotion"; 4 | 5 | const styled = (tag, styles) => { 6 | // eslint-disable-next-line react/prop-types 7 | const Styled = ({ className, ...props }, { catalog }) => 8 | React.createElement(tag, { 9 | ...props, 10 | className: cx( 11 | css(typeof styles === "function" ? styles(props, catalog) : styles, { 12 | label: tag 13 | }), 14 | className 15 | ) 16 | }); 17 | 18 | Styled.displayName = `Styled.${tag}`; 19 | Styled.contextTypes = { catalog: PropTypes.object.isRequired }; 20 | 21 | return Styled; 22 | }; 23 | 24 | export default styled; 25 | -------------------------------------------------------------------------------- /packages/core/src/styles/typography.js: -------------------------------------------------------------------------------- 1 | // Base styles 2 | 3 | const baseTextStyle = { 4 | fontStyle: "normal", 5 | fontWeight: 400, 6 | textRendering: "optimizeLegibility", 7 | WebkitFontSmoothing: "antialiased", 8 | MozOsxFontSmoothing: "grayscale", 9 | letterSpacing: "normal" 10 | }; 11 | 12 | // Helpers 13 | 14 | // Modular scale font size helper; level can be negative (for smaller font sizes) and positive (for larger font sizes) integers; level 0 === baseFontSize 15 | export const getFontSize = ({ baseFontSize, msRatio }, level = 0) => 16 | `${baseFontSize / 16 * Math.pow(msRatio, level)}rem`; 17 | 18 | // Exports 19 | 20 | // Text font style 21 | export const text = (theme, level = 0) => ({ 22 | ...baseTextStyle, 23 | color: theme.textColor, 24 | fontFamily: theme.fontFamily, 25 | fontSize: getFontSize(theme, level), 26 | lineHeight: theme.msRatio * theme.msRatio 27 | }); 28 | 29 | // Heading font style 30 | export const heading = (theme, level = 0) => ({ 31 | ...baseTextStyle, 32 | color: theme.brandColor, 33 | fontFamily: theme.fontHeading, 34 | fontSize: getFontSize(theme, level), 35 | lineHeight: theme.msRatio, 36 | position: "relative" 37 | }); 38 | -------------------------------------------------------------------------------- /packages/core/src/utils/__snapshots__/transformJSX.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Returns any expression 1`] = `"\\"use strict\\";var h=React.createElement;return h('div',{},'hey');"`; 4 | 5 | exports[`Returns the last expression 1`] = `"\\"use strict\\";React.createElement('div',{},'hey');return React.createElement('div',{},'ho');"`; 6 | 7 | exports[`Transforms a component 1`] = `"\\"use strict\\";return React.createElement(Foo,{bar:true},\\"baz\\");"`; 8 | 9 | exports[`Transforms a component which is defined inline 1`] = `"\\"use strict\\";var Foo=function Foo(){return null;};return React.createElement(Foo,{bar:true},\\"baz\\");"`; 10 | 11 | exports[`Transforms a nested element 1`] = `"\\"use strict\\";return React.createElement(\\"div\\",null,React.createElement(\\"p\\",null,\\"foo\\"));"`; 12 | 13 | exports[`Transforms a simple element 1`] = `"\\"use strict\\";return React.createElement(\\"div\\",null,\\"foo\\");"`; 14 | 15 | exports[`Transforms an array of elements 1`] = `"\\"use strict\\";return[React.createElement(\\"div\\",{key:1},\\"hey\\"),React.createElement(\\"div\\",{key:2},\\"ho\\")];"`; 16 | -------------------------------------------------------------------------------- /packages/core/src/utils/mapSpecimenOption.js: -------------------------------------------------------------------------------- 1 | const mapSpecimenOption = (test, map) => option => { 2 | const match = test.exec(option); 3 | if (match) { 4 | const [, value] = match; 5 | return map(value); 6 | } 7 | return null; 8 | }; 9 | 10 | export default mapSpecimenOption; 11 | -------------------------------------------------------------------------------- /packages/core/src/utils/parseSpecimenBody.js: -------------------------------------------------------------------------------- 1 | import { safeLoad, CORE_SCHEMA, Type, Schema } from "js-yaml"; 2 | 3 | const defaultMapBodyToProps = (parsedBody, rawBody) => parsedBody || rawBody; 4 | 5 | const INITIAL_SEPARATOR = /[ \t]*---[ \t]*\n/; 6 | const SEPARATOR = /\n[ \t]*---[ \t]*\n/; 7 | const splitText = text => { 8 | let matched = text.match(INITIAL_SEPARATOR); 9 | if (matched && matched.index === 0) { 10 | return [void 0, text.slice(matched[0].length)]; 11 | } 12 | matched = text.match(SEPARATOR); 13 | return matched && matched.index > -1 14 | ? [ 15 | text.slice(0, matched.index), 16 | text.slice(matched.index + matched[0].length) 17 | ] 18 | : [void 0, text]; 19 | }; 20 | 21 | const parseYaml = (str, imports) => { 22 | let parsed; 23 | try { 24 | const ImportType = new Type("!import", { 25 | kind: "scalar", 26 | // TODO: Gracefully handle missing imports 27 | // resolve(key) { 28 | // return imports.hasOwnProperty(key); 29 | // }, 30 | construct(key) { 31 | return imports[key]; 32 | } 33 | }); 34 | 35 | const yamlOptions = { 36 | schema: Schema.create(CORE_SCHEMA, [ImportType]) 37 | }; 38 | 39 | parsed = safeLoad(str, yamlOptions); 40 | } catch (e) { 41 | parsed = void 0; 42 | } 43 | return typeof parsed === "string" ? void 0 : parsed; 44 | }; 45 | 46 | export const parseSpecimenYamlBody = _mapBodyToProps => ( 47 | body = "", 48 | imports = {} 49 | ) => { 50 | const mapBodyToProps = _mapBodyToProps || defaultMapBodyToProps; 51 | return mapBodyToProps(parseYaml(body, imports), body); 52 | }; 53 | 54 | export const parseSpecimenBody = _mapBodyToProps => ( 55 | body = "", 56 | imports = {} 57 | ) => { 58 | const mapBodyToProps = _mapBodyToProps || defaultMapBodyToProps; 59 | const splitBody = splitText(body); 60 | const [props, children] = splitBody; 61 | return mapBodyToProps({ ...parseYaml(props, imports), children }, body); 62 | }; 63 | -------------------------------------------------------------------------------- /packages/core/src/utils/parseSpecimenOptions.js: -------------------------------------------------------------------------------- 1 | import { filter, compose, split, complement, isEmpty, mergeAll } from "ramda"; 2 | import mapSpecimenOption from "./mapSpecimenOption"; 3 | 4 | const removeEmpty = filter(complement(isEmpty)); 5 | const splitType = compose(removeEmpty, split("|")); 6 | const splitOptions = compose(removeEmpty, split(",")); 7 | 8 | const camelize = str => str.replace(/-(\w)/g, (_, c) => c.toUpperCase()); 9 | 10 | const nothing = () => null; 11 | const mapSpanToProp = mapSpecimenOption(/^span-(\d)$/, v => ({ span: +v })); 12 | const camelizeOption = option => ({ [camelize(option)]: true }); 13 | 14 | const optionToKeyValue = mapOptionsToProps => option => { 15 | for (let mapper of [mapOptionsToProps, mapSpanToProp]) { 16 | if (typeof mapper === "function") { 17 | const prop = mapper(option); 18 | if (prop !== null) { 19 | return prop; 20 | } 21 | } 22 | } 23 | return camelizeOption(option); 24 | }; 25 | 26 | const parseSpecimenOptions = (mapOptionsToProps = nothing) => ( 27 | options = "" 28 | ) => { 29 | const [, restOptions = ""] = splitType(options); 30 | return mergeAll( 31 | splitOptions(restOptions).map(optionToKeyValue(mapOptionsToProps)) 32 | ); 33 | }; 34 | 35 | export default parseSpecimenOptions; 36 | -------------------------------------------------------------------------------- /packages/core/src/utils/parseSpecimenOptions.test.js: -------------------------------------------------------------------------------- 1 | import parseSpecimenOptions from "./parseSpecimenOptions"; 2 | import mapSpecimenOption from "./mapSpecimenOption"; 3 | 4 | test("Default specimen options", () => { 5 | expect(parseSpecimenOptions()()).toEqual({}); 6 | }); 7 | 8 | test("Boolean specimen options", () => { 9 | expect(parseSpecimenOptions()("html|foo")).toEqual({ foo: true }); 10 | expect(parseSpecimenOptions()("html|foo,bar")).toEqual({ 11 | foo: true, 12 | bar: true 13 | }); 14 | }); 15 | 16 | test("Specimen options are camelized", () => { 17 | expect(parseSpecimenOptions()("html|foo-bar")).toEqual({ fooBar: true }); 18 | }); 19 | 20 | test("Specimen span option is mapped by default", () => { 21 | expect(parseSpecimenOptions()("html|span-1")).toEqual({ span: 1 }); 22 | }); 23 | 24 | test("Mapped specimen option", () => { 25 | expect( 26 | parseSpecimenOptions(mapSpecimenOption(/^foo-(\d)$/, v => ({ foo: +v })))( 27 | "html|foo-1" 28 | ) 29 | ).toEqual({ foo: 1 }); 30 | }); 31 | 32 | test("Custom specimen option mapper", () => { 33 | expect( 34 | parseSpecimenOptions(option => ({ 35 | [option.split("-")[0]]: +option.split("-")[1] 36 | }))("html|foo-1,bar-3") 37 | ).toEqual({ foo: 1, bar: 3 }); 38 | }); 39 | 40 | test("Mixed specimen options", () => { 41 | expect( 42 | parseSpecimenOptions(mapSpecimenOption(/^lang-(\w+)$/, lang => ({ lang })))( 43 | "code|lang-javascript,collapsed," 44 | ) 45 | ).toEqual({ lang: "javascript", collapsed: true }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/core/src/utils/parseSpecimenType.js: -------------------------------------------------------------------------------- 1 | import { compose, toLower, head, split, or } from "ramda"; 2 | 3 | const getType = compose(toLower, head, split("|")); 4 | 5 | const parseSpecimenType = (options = "") => or(getType(options), "raw-code"); 6 | 7 | export default parseSpecimenType; 8 | -------------------------------------------------------------------------------- /packages/core/src/utils/parseSpecimenType.test.js: -------------------------------------------------------------------------------- 1 | import parseSpecimenType from "./parseSpecimenType"; 2 | 3 | test("Default specimen type is `raw-code`", () => { 4 | expect(parseSpecimenType()).toBe("raw-code"); 5 | }); 6 | 7 | test("String without options", () => { 8 | expect(parseSpecimenType("html")).toBe("html"); 9 | }); 10 | 11 | test("String before | is specimen type", () => { 12 | expect(parseSpecimenType("html|no-source")).toBe("html"); 13 | }); 14 | 15 | test("Specimen type is always lower-cased", () => { 16 | expect(parseSpecimenType("HtmL")).toBe("html"); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/core/src/utils/path.js: -------------------------------------------------------------------------------- 1 | const removeMultiSlashes = path => path.replace(/\/+/g, "/"); 2 | export const stripTrailingSlashes = path => path.replace(/\/+$/, ""); 3 | export const addLeadingSlash = path => 4 | path.charAt(0) === "/" ? path : "/" + path; 5 | const stripBasePath = (path, basePath) => 6 | basePath !== "/" && path.indexOf(basePath) === 0 7 | ? path.substr(basePath.length) 8 | : path; 9 | const absoluteUrlRe = /^[a-z][a-z0-9+.-]*:/; 10 | 11 | export const parsePath = (path, options) => { 12 | let pathname = path; 13 | let hash = ""; 14 | let anchor = null; 15 | 16 | if (!absoluteUrlRe.test(pathname)) { 17 | const hashIndex = pathname.indexOf("#"); 18 | if (hashIndex !== -1) { 19 | hash = pathname.substr(hashIndex); 20 | anchor = pathname.substr(hashIndex + 1); 21 | pathname = pathname.substr(0, hashIndex); 22 | } 23 | 24 | if (pathname === "" && options.page) { 25 | // fall back to current page path (already contains basePath) 26 | pathname = stripBasePath(options.page.path, options.basePath); 27 | } 28 | 29 | // join basePath 30 | pathname = addLeadingSlash( 31 | stripTrailingSlashes( 32 | removeMultiSlashes( 33 | options.basePath + "/" + stripBasePath(pathname, options.basePath) 34 | ) 35 | ) 36 | ); 37 | } 38 | 39 | return options.useBrowserHistory 40 | ? { pathname, hash: hash === "#" ? "" : hash } 41 | : { pathname, query: anchor ? { a: anchor } : {} }; 42 | }; 43 | 44 | export const getPublicPath = (path, options) => { 45 | return absoluteUrlRe.test(path) 46 | ? path 47 | : options.publicUrl + 48 | addLeadingSlash(stripBasePath(path, options.basePath)); 49 | }; 50 | 51 | export const isInternalPath = (parsedPath, options) => { 52 | return options.pagePaths.has(parsedPath.pathname); 53 | }; 54 | -------------------------------------------------------------------------------- /packages/core/src/utils/requireModuleDefault.js: -------------------------------------------------------------------------------- 1 | // A little helper to require babel-transformed modules with default export 2 | export default module => (module.__esModule ? module.default : module); 3 | -------------------------------------------------------------------------------- /packages/core/src/utils/runscript.js: -------------------------------------------------------------------------------- 1 | import { isEmpty, is } from "ramda"; 2 | 3 | // 4 | // Sequentially runs scripts as they are added 5 | // 6 | 7 | let current = null; 8 | let queue = []; 9 | let dequeue = handler => { 10 | current = handler(); 11 | current.then(() => { 12 | current = null; 13 | if (queue.length > 0) { 14 | return dequeue(queue.shift()); 15 | } 16 | return void 0; 17 | }); 18 | return current.catch(() => { 19 | throw new Error("Error loading script"); 20 | }); 21 | }; 22 | let enqueue = handler => { 23 | if (current !== null) { 24 | return queue.push(handler); 25 | } 26 | return dequeue(handler); 27 | }; 28 | let execScript = decorate => { 29 | let script = document.createElement("script"); 30 | script.setAttribute("type", "text/javascript"); 31 | decorate(script); 32 | let head = 33 | document.getElementsByTagName("head")[0] || document.documentElement; 34 | return head.appendChild(script); 35 | }; 36 | let execRemote = src => { 37 | return () => { 38 | return new Promise((resolve, reject) => { 39 | return execScript(script => { 40 | script.addEventListener("load", resolve, false); 41 | script.addEventListener("error", reject, false); 42 | return script.setAttribute("src", src); 43 | }); 44 | }); 45 | }; 46 | }; 47 | let execInline = src => { 48 | return () => { 49 | return new Promise(resolve => { 50 | return execScript(script => { 51 | script.appendChild(document.createTextNode(src)); 52 | return resolve(); 53 | }); 54 | }); 55 | }; 56 | }; 57 | 58 | export default srcOrEl => { 59 | if (is(String, srcOrEl) && !isEmpty(srcOrEl.trim())) { 60 | enqueue(execRemote(srcOrEl)); 61 | } 62 | if (srcOrEl.textContent && !isEmpty(srcOrEl.textContent.trim())) { 63 | return enqueue(execInline(srcOrEl.textContent)); 64 | } 65 | return void 0; 66 | }; 67 | -------------------------------------------------------------------------------- /packages/core/src/utils/seqKey.js: -------------------------------------------------------------------------------- 1 | export default namespace => { 2 | let counter; 3 | counter = 0; 4 | return () => `${namespace}-${counter++}`; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/core/src/utils/transformJSX.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { transform } from "@babel/standalone"; 3 | import requireModuleDefault from "./requireModuleDefault"; 4 | 5 | const presets = ["es2015", "react"]; 6 | 7 | // Babel plugin to return last top-level expression statement 8 | const returnLastExpressionPlugin = ({ types: t }) => ({ 9 | visitor: { 10 | // We only care about top-level expressions 11 | Program(path) { 12 | // Find the last expression statement 13 | let lastExpr; 14 | for (let i = path.node.body.length - 1; i >= 0; i--) { 15 | if (t.isExpressionStatement(path.node.body[i])) { 16 | lastExpr = path.get(`body.${i}`); 17 | break; 18 | } 19 | } 20 | 21 | if (lastExpr) { 22 | // ... and turn it into a return statement 23 | lastExpr.replaceWith(t.returnStatement(lastExpr.node.expression)); 24 | } 25 | } 26 | } 27 | }); 28 | 29 | let cached = {}; 30 | const cachedTransform = jsx => { 31 | if (cached[jsx]) { 32 | return cached[jsx]; 33 | } 34 | const transformed = transform(jsx, { 35 | compact: true, 36 | presets, 37 | plugins: [returnLastExpressionPlugin] 38 | }).code; 39 | cached[jsx] = transformed; 40 | return transformed; 41 | }; 42 | 43 | const missingTransformError = { 44 | error: 45 | "Please include [babel-standalone](https://github.com/babel/babel-standalone) before Catalog." 46 | }; 47 | 48 | export default (jsx, imports) => { 49 | // Check for transform to provide a better error message 50 | try { 51 | transform; 52 | } catch (error) { 53 | return missingTransformError; 54 | } 55 | 56 | try { 57 | const importKeys = Object.keys(imports).filter(k => imports[k]); 58 | const importModules = importKeys.map(k => requireModuleDefault(imports[k])); 59 | const code = cachedTransform(jsx); 60 | // eslint-disable-next-line no-new-func 61 | const element = new Function("React", ...importKeys, code)( 62 | React, 63 | ...importModules 64 | ); 65 | return { code, element }; 66 | } catch (error) { 67 | return { error }; 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /packages/core/src/utils/transformJSX.test.js: -------------------------------------------------------------------------------- 1 | import transformJSX from "./transformJSX"; 2 | 3 | test("Transforms a simple element", () => { 4 | expect(transformJSX("
foo
", {}).code).toMatchSnapshot(); 5 | }); 6 | 7 | test("Transforms a nested element", () => { 8 | expect(transformJSX("

foo

", {}).code).toMatchSnapshot(); 9 | }); 10 | 11 | test("Transforms a component", () => { 12 | expect( 13 | transformJSX("baz", { Foo: () => null }).code 14 | ).toMatchSnapshot(); 15 | }); 16 | 17 | test("Transforms a component which is defined inline", () => { 18 | expect( 19 | transformJSX( 20 | `const Foo = () => null; 21 | baz`, 22 | {} 23 | ).code 24 | ).toMatchSnapshot(); 25 | }); 26 | 27 | test("Transforms an array of elements", () => { 28 | expect( 29 | transformJSX( 30 | `[ 31 |
hey
, 32 |
ho
33 | ]`, 34 | {} 35 | ).code 36 | ).toMatchSnapshot(); 37 | }); 38 | 39 | test("Returns the last expression", () => { 40 | expect( 41 | transformJSX( 42 | ` 43 | React.createElement('div', {}, 'hey'); 44 | React.createElement('div', {}, 'ho'); 45 | `, 46 | {} 47 | ).code 48 | ).toMatchSnapshot(); 49 | }); 50 | 51 | test("Returns any expression", () => { 52 | expect( 53 | transformJSX( 54 | ` 55 | const h = React.createElement; 56 | h('div', {}, 'hey'); 57 | `, 58 | {} 59 | ).code 60 | ).toMatchSnapshot(); 61 | }); 62 | 63 | test("Returns error on syntax error", () => { 64 | expect(transformJSX("
", {}).error).toBeDefined(); 65 | }); 66 | 67 | test("Returns error when import is missing", () => { 68 | expect(transformJSX("", {}).error).toBeDefined(); 69 | }); 70 | -------------------------------------------------------------------------------- /packages/core/src/utils/validateSizes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if the delivered props are valid, returns false if not, otherwise a filtered Array 3 | */ 4 | 5 | const validateSizes = (input, catalogSizes) => { 6 | const isArray = Array.isArray(input); 7 | if (input === true) { 8 | return catalogSizes; 9 | } else if (typeof input === "string") { 10 | const foundInList = catalogSizes.find(val => input === val.name); 11 | return foundInList ? [].concat(foundInList) : false; 12 | } else if ( 13 | isArray && 14 | input.length === input.filter(item => typeof item === "string").length 15 | ) { 16 | const filtered = input 17 | .map(name => catalogSizes.find(size => size.name === name)) 18 | .filter(Boolean); 19 | return filtered.length === input.length ? filtered : false; 20 | } 21 | return false; 22 | }; 23 | 24 | export default validateSizes; 25 | -------------------------------------------------------------------------------- /packages/core/src/utils/validateSizes.test.js: -------------------------------------------------------------------------------- 1 | import validateSizes from "./validateSizes"; 2 | import DefaultResponsiveSizes from "./../DefaultResponsiveSizes"; 3 | 4 | test("Value is string that is not contained in the list", () => { 5 | expect(validateSizes("Palm Pre", DefaultResponsiveSizes)).toBeFalsy(); 6 | }); 7 | 8 | test("Value is boolean true, should return DefaultResponsiveSizes Array", () => { 9 | expect(validateSizes(true, DefaultResponsiveSizes)).toEqual( 10 | DefaultResponsiveSizes 11 | ); 12 | }); 13 | 14 | test("Value is Array, consisting of defined sizes", () => { 15 | const values = ["small", "medium", "large", "xlarge"]; 16 | expect(DefaultResponsiveSizes).toEqual( 17 | validateSizes(values, DefaultResponsiveSizes) 18 | ); 19 | }); 20 | 21 | test("Value is Array, allow re-ordering defined sizes", () => { 22 | const sizes = [ 23 | { name: "small", width: 360, height: 640 }, 24 | { name: "xlarge", width: 1920, height: 1080 } 25 | ]; 26 | const values = ["xlarge", "small"]; 27 | expect(validateSizes(values, sizes)).toEqual([sizes[1], sizes[0]]); 28 | }); 29 | 30 | test("Value is Array, not containing at least one of the defined sizes", () => { 31 | const values = ["camera obscura", "That weird giant sony rear projection TV"]; 32 | expect(validateSizes(values, DefaultResponsiveSizes)).toBeFalsy(); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/core/src/utils/warning.js: -------------------------------------------------------------------------------- 1 | let warning = () => {}; 2 | 3 | if (process.env.NODE_ENV !== "production") { 4 | // Logs an error if condition is _not_ met. 5 | warning = (condition, message, ...args) => { 6 | if (condition) { 7 | return; 8 | } 9 | 10 | if (typeof console !== "undefined") { 11 | console.error(`Catalog warning: ${message}`, ...args); // eslint-disable-line no-console 12 | } 13 | }; 14 | } 15 | 16 | export default warning; 17 | -------------------------------------------------------------------------------- /packages/core/types/catalog/test-page-2.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { markdown } from "catalog"; 3 | 4 | export default class Page extends React.Component { 5 | render() { 6 | return
Hello
; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/types/catalog/test-page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { markdown } from "catalog"; 3 | 4 | export default () => markdown` 5 | # Hi! 6 | `; 7 | -------------------------------------------------------------------------------- /packages/core/types/catalog/test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { 4 | Catalog, 5 | markdown, 6 | ReactSpecimen, 7 | Config, 8 | Page, 9 | pageLoader, 10 | HtmlSpecimen, 11 | TableSpecimen, 12 | ColorSpecimen, 13 | ColorPaletteSpecimen, 14 | CodeSpecimen 15 | } from "catalog"; 16 | 17 | const MyPage = () => markdown` 18 | 19 | # Hello 20 | 21 | ${( 22 | 23 |
Hello
24 |
25 | )} 26 | 27 | ${( 28 | 29 |
Hello
30 |
World
31 |
32 | )} 33 | 34 | ${( 35 | foo

}]} 38 | columns={["bar", "foo"]} 39 | /> 40 | )} 41 | 42 | ${} 43 | 44 | ${} 45 | 46 | ${( 47 | 53 | )} 54 | 55 | ${} 56 | 57 | ${{"hello"}} 58 | 59 | ${{"hello"}} 60 | 61 | ${( 62 | 63 | {"var x = 3;"} 64 | 65 | )} 66 | 67 | `; 68 | 69 | ReactDOM.render(, document.body); 70 | 71 | const config: Config = { 72 | title: "Test", 73 | theme: { 74 | background: "white" 75 | }, 76 | pages: [ 77 | { 78 | path: "/", 79 | title: "Introduction", 80 | styles: ["foo.css"], 81 | content: () => hello 82 | }, 83 | { 84 | path: "/wat", 85 | title: "Wat", 86 | content: () =>

I can also be any Element

87 | }, 88 | { 89 | path: "/foo", 90 | title: "Foo", 91 | content: MyPage 92 | }, 93 | { 94 | path: "/bar", 95 | title: "Bar", 96 | content: pageLoader("./bar.md") 97 | }, 98 | { 99 | title: "Materials", 100 | pages: [ 101 | { 102 | path: "/materials/typeface", 103 | title: "Typeface", 104 | content: pageLoader(() => import("./test-page")) 105 | }, 106 | { 107 | path: "/materials/typeface2", 108 | title: "Typeface2", 109 | content: pageLoader(() => import("./test-page-2")) 110 | } 111 | ] 112 | } 113 | ], 114 | useBrowserHistory: true, 115 | basePath: "/doc", 116 | responsiveSizes: [ 117 | { name: "large", width: 978, height: 1100 }, 118 | { name: "medium", width: 640, height: 900 }, 119 | { name: "small", width: 471, height: 700 } 120 | ] 121 | }; 122 | 123 | ReactDOM.render(, document.body); 124 | 125 | ReactDOM.render(, document.body); 126 | -------------------------------------------------------------------------------- /packages/core/types/catalog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "lib": ["es6", "dom"], 9 | "noEmit": true, 10 | "baseUrl": "../", 11 | "typeRoots": ["../"] 12 | }, 13 | "include": [ 14 | "./*.ts", 15 | "./*.tsx" 16 | ] 17 | } -------------------------------------------------------------------------------- /packages/markdown-loader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@catalog/markdown-loader", 3 | "version": "4.0.1-canary.2", 4 | "description": "Webpack loader for Catalog markdown files", 5 | "homepage": "https://www.catalog.style/", 6 | "bugs": "https://github.com/interactivethings/catalog/issues", 7 | "main": "dist/index.js", 8 | "files": [ 9 | "dist" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/interactivethings/catalog.git" 14 | }, 15 | "license": "BSD-3-Clause", 16 | "publishConfig": { 17 | "access": "public" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/markdown-loader/rollup.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WARNING: Don't use rollup's ES import/export here because otherwise file paths won't resolve correctly 3 | */ 4 | const babel = require("rollup-plugin-babel"); 5 | const resolve = require("rollup-plugin-node-resolve"); 6 | const path = require("path"); 7 | const pkg = require("./package.json"); 8 | 9 | const externals = [ 10 | ...Object.keys(pkg.dependencies || {}), 11 | ...Object.keys(pkg.peerDependencies || {}), 12 | "url", 13 | "fs", 14 | "path" 15 | ]; 16 | 17 | const extensions = [".js", ".jsx", ".ts", ".tsx"]; 18 | 19 | module.exports = { 20 | input: path.resolve(__dirname, "src/index.ts"), 21 | external: externals, 22 | plugins: [ 23 | resolve({ 24 | extensions 25 | }), 26 | babel({ 27 | extensions 28 | }) 29 | ], 30 | output: [ 31 | { 32 | dir: path.resolve(__dirname, "dist/"), 33 | entryFileNames: "[name].js", 34 | format: "cjs", 35 | sourcemap: true 36 | } 37 | ] 38 | }; 39 | -------------------------------------------------------------------------------- /packages/markdown-loader/src/index.ts: -------------------------------------------------------------------------------- 1 | export default function(source: string) { 2 | const content = JSON.stringify(source); 3 | 4 | const output = ` 5 | import React from 'react'; 6 | import {PageRenderer} from '@catalog/core'; 7 | 8 | function WrappedPageRenderer(props) { 9 | return React.createElement(PageRenderer, Object.assign({}, props, {content:${content}})); 10 | } 11 | WrappedPageRenderer.__catalog_loader__ = true; 12 | export default WrappedPageRenderer; 13 | `; 14 | 15 | return output; 16 | } 17 | -------------------------------------------------------------------------------- /packages/markdown-loader/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/standalone/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "loose": true, 7 | "modules": false, 8 | "targets": { "browsers": [">0.25%"] } 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/standalone/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /catalog.js 3 | /catalog.development.js 4 | /catalog.min.js 5 | -------------------------------------------------------------------------------- /packages/standalone/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | ../../node_modules/.bin/rollup --config=rollup.config.js 4 | -------------------------------------------------------------------------------- /packages/standalone/README.md: -------------------------------------------------------------------------------- 1 | # Example of a standalone catalog 2 | 3 | ```color 4 | span: 3 5 | name: "Light Blue" 6 | value: "#b0f6ff" 7 | ``` 8 | 9 | ```color 10 | span: 2 11 | name: "Dark Blue" 12 | value: "#2666a4" 13 | ``` 14 | 15 | ```color 16 | span: 1 17 | name: "Bright Red" 18 | value: "#ff5555" 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/standalone/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/standalone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@catalog/standalone", 3 | "version": "4.0.1-canary.2", 4 | "description": "Standalone version of Catalog", 5 | "homepage": "https://www.catalog.style/", 6 | "bugs": "https://github.com/interactivethings/catalog/issues", 7 | "main": "catalog.min.js", 8 | "unpkg": "catalog.min.js", 9 | "files": [ 10 | "catalog.development.js", 11 | "catalog.min.js" 12 | ], 13 | "scripts": { 14 | "watch": "rollup -c rollup.config.js --watch", 15 | "build": "rollup -c rollup.config.js" 16 | }, 17 | "devDependencies": { 18 | "@catalog/core": "^4.0.1-canary.2" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/interactivethings/catalog.git" 23 | }, 24 | "license": "BSD-3-Clause", 25 | "publishConfig": { 26 | "access": "public" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/standalone/rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from "rollup-plugin-babel"; 2 | import commonjs from "rollup-plugin-commonjs"; 3 | import nodeResolve from "rollup-plugin-node-resolve"; 4 | import { terser } from "rollup-plugin-terser"; 5 | import replace from "rollup-plugin-replace"; 6 | import * as path from "path"; 7 | 8 | let plugins = [ 9 | babel({ 10 | exclude: /node_modules/ 11 | }), 12 | nodeResolve({ 13 | preferBuiltins: false 14 | }), 15 | commonjs({ 16 | include: /node_modules/, 17 | namedExports: { 18 | // left-hand side can be an absolute path, a path 19 | // relative to the current directory, or the name 20 | // of a module in node_modules 21 | react: [ 22 | "PureComponent", 23 | "Component", 24 | "PropTypes", 25 | "Children", 26 | "createElement", 27 | "isValidElement" 28 | ], 29 | "react-dom": [ 30 | "unstable_renderSubtreeIntoContainer", 31 | "unmountComponentAtNode" 32 | ], 33 | "react-is": ["isValidElementType"], 34 | "js-yaml": ["safeLoad", "CORE_SCHEMA", "Type", "Schema"], 35 | "prop-types": [ 36 | "bool", 37 | "array", 38 | "func", 39 | "object", 40 | "arrayOf", 41 | "oneOfType", 42 | "element", 43 | "shape", 44 | "string", 45 | "elementType" 46 | ] 47 | } 48 | }) 49 | ]; 50 | 51 | export default [ 52 | { 53 | input: { catalog: path.resolve(__dirname, "src/index.js") }, 54 | external: ["@babel/standalone"], 55 | plugins: [ 56 | replace({ 57 | "process.env.NODE_ENV": JSON.stringify("development") 58 | }), 59 | ...plugins 60 | ], 61 | output: { 62 | dir: __dirname, 63 | globals: { 64 | "@babel/standalone": "Babel" 65 | }, 66 | entryFileNames: "[name].development.js", 67 | format: "umd", 68 | name: "Catalog" 69 | } 70 | }, 71 | { 72 | input: { catalog: path.resolve(__dirname, "src/index.js") }, 73 | external: ["@babel/standalone"], 74 | plugins: [ 75 | replace({ 76 | "process.env.NODE_ENV": JSON.stringify("production") 77 | }), 78 | ...plugins, 79 | terser() 80 | ], 81 | output: { 82 | dir: __dirname, 83 | globals: { 84 | "@babel/standalone": "Babel" 85 | }, 86 | entryFileNames: "[name].min.js", 87 | format: "umd", 88 | name: "Catalog" 89 | } 90 | } 91 | ]; 92 | -------------------------------------------------------------------------------- /packages/standalone/src/index.js: -------------------------------------------------------------------------------- 1 | import * as R from "ramda"; 2 | import * as React from "react"; 3 | 4 | export { R, React }; 5 | export * from "@catalog/core"; 6 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | require("./packages/babel-preset/rollup.config.js"), 3 | require("./packages/catalog/rollup.config.js"), 4 | require("./packages/cli/rollup.config.js"), 5 | require("./packages/core/rollup.config.js"), 6 | require("./packages/markdown-loader/rollup.config.js") 7 | ]; 8 | -------------------------------------------------------------------------------- /scripts/publish-canary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit the script on any command with non 0 return code 4 | # We assume that all the commands in the pipeline set their return code 5 | # properly and that we do not need to validate that the output is correct 6 | set -e 7 | 8 | yarn build 9 | 10 | # Get 2FA when not CI 11 | otp="" 12 | if [ -z $CI ]; then 13 | echo "Please enter npm two-factor auth code: " 14 | read otp 15 | fi 16 | 17 | NPM_CONFIG_OTP="$otp" yarn lerna publish from-git --npm-tag=canary 18 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import (fetchTarball https://nixos.org/channels/nixpkgs-unstable/nixexprs.tar.xz) { }; 3 | nodejs = pkgs.nodejs-12_x; 4 | 5 | in pkgs.mkShell { 6 | buildInputs = [ 7 | pkgs.darwin.apple_sdk.frameworks.CoreServices 8 | pkgs.yarn 9 | nodejs 10 | ]; 11 | } -------------------------------------------------------------------------------- /tsconfig.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "strict": true, 7 | "noEmit": true, 8 | 9 | "target": "es2018", 10 | "module": "esnext" 11 | }, 12 | "exclude": ["node_modules"] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "packages/babel-preset" }, 5 | { "path": "packages/cli" }, 6 | { "path": "packages/core/types/catalog" }, 7 | { "path": "packages/catalog" }, 8 | { "path": "packages/markdown-loader" } 9 | ] 10 | } 11 | --------------------------------------------------------------------------------