commit 54d963d53f3f59449c454923c133c5781030b98b Author: Thomas Allmer Date: Sun Sep 2 22:48:23 2018 +0200 feat: initial release diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..62ada42b --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +node_modules +npm-debug.log + +## lerna +lerna-debug.log + +## temp folders +/.tmp/ + +## we don't want lock files for now +yarn.lock +package-lock.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..60227747 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 open-wc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..ef8cc802 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Open Web Component Recommendations + +We want to provide a good set of default on how to vasilitate your web component. + +## Usage +```bash +mkdir my-element +cd my-element +# Minimum setup +npx -p yo -p generator-open-wc -c 'yo open-wc:vanilla-bare' +``` + +## Available Recommendations +```bash +# Demos using storybook +npx -p yo -p generator-open-wc -c 'yo open-wc:storybook' + +# Linting using eslint +npx -p yo -p generator-open-wc -c 'yo open-wc:eslint' +``` + + + +## Working on it + +```bash +npm run bootstrap +# does: lerna bootstrap --hoist + +# run demos +lerna run storybook --scope @open-wc/example-vanilla --stream + +# eslint +lerna run lint:eslint --scope @open-wc/example-vanilla --stream +``` diff --git a/lerna.json b/lerna.json new file mode 100644 index 00000000..e3369a68 --- /dev/null +++ b/lerna.json @@ -0,0 +1,11 @@ +{ + "packages": [ + "packages/*" + ], + "version": "independent", + "command": { + "publish": { + "conventionalCommits": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..59dbaa61 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "@open-wc/root", + "private": true, + "license": "MIT", + "scripts": { + "bootstrap": "lerna bootstrap --hoist" + }, + "devDependencies": { + "lerna": "^3.2.1" + } +} diff --git a/packages/eslint-config/LICENSE b/packages/eslint-config/LICENSE new file mode 100644 index 00000000..60227747 --- /dev/null +++ b/packages/eslint-config/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 open-wc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js new file mode 100644 index 00000000..bbd12e12 --- /dev/null +++ b/packages/eslint-config/index.js @@ -0,0 +1,29 @@ +module.exports = { + extends: [ + 'eslint-config-airbnb-base', + ].map(require.resolve), + env: { + browser: true, + }, + rules: { + 'import/extensions': 'off', + 'no-underscore-dangle': 'off', + 'import/prefer-default-export': 'off', + "import/no-extraneous-dependencies": ["error", { + "devDependencies": [ + "**/*.test.js", + "**/*.spec.js", + "**/*.stories.js", + "**/*.stories.options.js" + ] + }] + }, + settings: { + 'import/resolver': { + 'node': { + 'moduleDirectory': ['node_modules', 'bower_components'] + } + } + }, + plugins: ['html'] +}; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json new file mode 100644 index 00000000..f5a15d7e --- /dev/null +++ b/packages/eslint-config/package.json @@ -0,0 +1,22 @@ +{ + "name": "@open-wc/eslint-config", + "version": "0.0.0", + "description": "Eslint config following open-wc recommendations", + "author": "open-wc", + "homepage": "https://github.com/open-wc/open-wc/", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": "https://github.com/open-wc/open-wc/tree/master/packages/eslint-config", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "eslint": "^5.5.0", + "eslint-config-airbnb-base": "^13.1.0", + "eslint-plugin-html": "^4.0.5", + "eslint-plugin-import": "^2.14.0" + } +} diff --git a/packages/example-vanilla/.editorconfig b/packages/example-vanilla/.editorconfig new file mode 100644 index 00000000..0c1c8bc4 --- /dev/null +++ b/packages/example-vanilla/.editorconfig @@ -0,0 +1,29 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.json] +indent_size = 2 + +[*.{html,js}] +block_comment_start = /** +block_comment = * +block_comment_end = */ diff --git a/packages/example-vanilla/.eslintignore b/packages/example-vanilla/.eslintignore new file mode 100644 index 00000000..07e6e472 --- /dev/null +++ b/packages/example-vanilla/.eslintignore @@ -0,0 +1 @@ +/node_modules diff --git a/packages/example-vanilla/.eslintrc.js b/packages/example-vanilla/.eslintrc.js new file mode 100644 index 00000000..1ccd046b --- /dev/null +++ b/packages/example-vanilla/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + extends: [ + '@open-wc/eslint-config', + ].map(require.resolve), +}; diff --git a/packages/example-vanilla/.gitignore b/packages/example-vanilla/.gitignore new file mode 100644 index 00000000..f1206923 --- /dev/null +++ b/packages/example-vanilla/.gitignore @@ -0,0 +1,21 @@ +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## bower +/bower_components*/ +/bower-*.json + +## temp folders +/.tmp/ + +## we don't want lock files for now +yarn.lock +package-lock.json diff --git a/packages/example-vanilla/.storybook/addons.js b/packages/example-vanilla/.storybook/addons.js new file mode 100644 index 00000000..63116c29 --- /dev/null +++ b/packages/example-vanilla/.storybook/addons.js @@ -0,0 +1,8 @@ +import '@storybook/addon-storysource/register'; +import '@storybook/addon-actions/register'; +import '@storybook/addon-backgrounds/register'; +import '@storybook/addon-notes/register'; +import '@storybook/addon-knobs/register'; +import '@storybook/addon-links/register'; +import '@storybook/addon-viewport/register'; +import '@storybook/addon-options/register'; diff --git a/packages/example-vanilla/.storybook/config.js b/packages/example-vanilla/.storybook/config.js new file mode 100644 index 00000000..26d8b2bf --- /dev/null +++ b/packages/example-vanilla/.storybook/config.js @@ -0,0 +1,13 @@ +import { configure } from '@storybook/polymer'; +import { setOptions } from '@storybook/addon-options'; + +setOptions({ + hierarchyRootSeparator: /\|/, +}); + +const req = require.context('../stories', true, /\.stories\.js$/); +function loadStories() { + req.keys().forEach(filename => req(filename)); +} + +configure(loadStories, module); diff --git a/packages/example-vanilla/.storybook/webpack.config.js b/packages/example-vanilla/.storybook/webpack.config.js new file mode 100644 index 00000000..56dec291 --- /dev/null +++ b/packages/example-vanilla/.storybook/webpack.config.js @@ -0,0 +1,17 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = (storybookBaseConfig, configType, defaultConfig) => { + defaultConfig.resolve.modules.push('bower_components'); + + defaultConfig.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + enforce: 'pre', + }); + + // TEMP fix: https://github.com/plotly/plotly.js/issues/2466#issuecomment-372924684 + defaultConfig.plugins.push(new webpack.IgnorePlugin(/vertx/)); + + return defaultConfig; +}; diff --git a/packages/example-vanilla/ExampleVanilla.js b/packages/example-vanilla/ExampleVanilla.js new file mode 100644 index 00000000..d6898ce9 --- /dev/null +++ b/packages/example-vanilla/ExampleVanilla.js @@ -0,0 +1,53 @@ +import { html, render } from 'lit-html'; + +export default class ExampleVanilla extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.header = 'My Example'; + } + + connectedCallback() { + this.update(); + } + + update() { + render(this.renderShadowDom(), this.shadowRoot); + } + + renderShadowDom() { + return html` + +

${this.header}

+
+ +
+ `; + } +} diff --git a/packages/example-vanilla/README.md b/packages/example-vanilla/README.md new file mode 100644 index 00000000..05d5ce01 --- /dev/null +++ b/packages/example-vanilla/README.md @@ -0,0 +1,28 @@ +# \ + +This webcomponent follows the [open-wc](https://github.com/open-wc/open-wc) recommendation. + +## Installation +```bash +npm i example-vanilla +``` + +## Usage +```html + + + +``` + +## Demos using storybook (if applied by author) +```bash +npm run storybook +# visit provided url +``` + +## Linting using eslint (if applied by author) +```bash +npm run lint:eslint +``` diff --git a/packages/example-vanilla/example-vanilla.js b/packages/example-vanilla/example-vanilla.js new file mode 100644 index 00000000..7a21f3f3 --- /dev/null +++ b/packages/example-vanilla/example-vanilla.js @@ -0,0 +1,3 @@ +import ExampleVanilla from './ExampleVanilla.js'; + +window.customElements.define('example-vanilla', ExampleVanilla); diff --git a/packages/example-vanilla/package.json b/packages/example-vanilla/package.json new file mode 100644 index 00000000..68c7c5e0 --- /dev/null +++ b/packages/example-vanilla/package.json @@ -0,0 +1,26 @@ +{ + "name": "@open-wc/example-vanilla", + "version": "0.0.0", + "description": "Example Webcomponent following open-wc recommendations", + "author": "open-wc", + "homepage": "https://github.com/open-wc/open-wc/", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": "https://github.com/open-wc/open-wc/tree/master/packages/example-vanilla", + "private": true, + "dependencies": { + "lit-html": "^0.11.0" + }, + "scripts": { + "build-storybook": "build-storybook", + "start": "webpack-dev-server", + "storybook": "start-storybook -p 9001 -c .storybook", + "lint:eslint": "eslint --ext .js,.html ." + }, + "devDependencies": { + "@open-wc/storybook": "^0.0.0", + "@open-wc/eslint-config": "^0.0.0" + } +} diff --git a/packages/example-vanilla/stories/example.stories.js b/packages/example-vanilla/stories/example.stories.js new file mode 100644 index 00000000..adaad946 --- /dev/null +++ b/packages/example-vanilla/stories/example.stories.js @@ -0,0 +1,134 @@ +/* eslint-disable no-alert */ + +import { + storiesOf, + addParameters, + html, + action, + linkTo, + withBackgrounds, + withNotes, + document, + withKnobs, + text, + button, + number, + select, + date, + color, + array, + boolean, +} from '@open-wc/storybook'; + +storiesOf('Addon|Actions', module) + .add('Action only', () => html` + + `) + .add('Action and method', () => html` + + `); + + +storiesOf('Addon|Links', module) + .add('To Welcome', () => html` + + `); + + +storiesOf('Addon|Backgrounds', module) + .addDecorator( + withBackgrounds([ + { name: 'twitter', value: '#00aced', default: true }, + { name: 'facebook', value: '#3b5998' }, + ]), + ) + .add('Button with text', () => ` + +

See tab "Backgrounds" at the bottom

+ `); + + +storiesOf('Addon|Notes', module) + .addDecorator(withNotes) + .add('Simple note', () => ` +

See tab "notes" at the bottom.

+ `, { + notes: 'My notes on some bold text', + }); + + +storiesOf('Core|Methods for rendering', module) + .add('html string', () => '
Rendered with string
') + .add('document.createElement', () => { + const el = document.createElement('button'); + el.innerText = 'click me'; + el.foo = 'bar'; + el.addEventListener('click', e => alert(e.target.foo)); + return el; + }) + .add('lit-html', () => html` + + `); + + +const globalParameter = 'globalParameter'; +const chapterParameter = 'chapterParameter'; +const storyParameter = 'storyParameter'; + +addParameters({ globalParameter }); + +storiesOf('Core|Parameters', module) + .addParameters({ chapterParameter }) + .add('passed to story', ({ parameters: { fileName, ...parameters } }) => ` +
Parameters are ${JSON.stringify(parameters)}
+ `, { + storyParameter, + }); + + +storiesOf('Addon|Knobs', module) + .addDecorator(withKnobs) + .add('button label', () => html` + + `) + .add('complex', () => { + const name = text('Name', 'Jane'); + const stock = number('Stock', 20, { + range: true, min: 0, max: 30, step: 5, + }); + const fruits = { + Apple: 'apples', + Banana: 'bananas', + Cherry: 'cherries', + }; + const fruit = select('Fruit', fruits, 'apples'); + const price = number('Price', 2.25); + const colour = color('Border', 'deeppink'); + const today = date('Today', new Date('Jan 20 2017 GMT+0')); + const items = array('Items', ['Laptop', 'Book', 'Whiskey']); + const nice = boolean('Nice', true); + + const stockMessage = stock + ? html`I have a stock of ${stock} ${fruit}, costing $${price} each.` + : html`I'm out of ${fruit}${nice ? ', Sorry!' : '.'}`; + const dateOptions = { + year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC', + }; + + button('Arbitrary action', () => alert('You clicked it!')); + + return html` +
+

My name is ${name},

+

today is ${new Date(today).toLocaleDateString('en-US', dateOptions)}

+

${stockMessage}

+

Also, I have:

+
    + ${items.map(item => html` +
  • ${item}
  • + `)} +
+

${nice ? 'Nice to meet you!' : 'Leave me alone!'}

+
+ `; + }); diff --git a/packages/example-vanilla/stories/index.stories.js b/packages/example-vanilla/stories/index.stories.js new file mode 100644 index 00000000..9ba86e92 --- /dev/null +++ b/packages/example-vanilla/stories/index.stories.js @@ -0,0 +1,15 @@ +import { + storiesOf, + html, +} from '@open-wc/storybook'; + +import { opts } from './index.stories.options.js'; +import '../example-vanilla.js'; + +storiesOf(`${opts.header}`, module) + .add('default', () => html` + <${opts.tag}>user content tag: ${opts.tag} + `) + .add('right', () => html` + <${opts.tag} class="right">user content tag: ${opts.tag} + `); diff --git a/packages/example-vanilla/stories/index.stories.options.js b/packages/example-vanilla/stories/index.stories.options.js new file mode 100644 index 00000000..833264dd --- /dev/null +++ b/packages/example-vanilla/stories/index.stories.options.js @@ -0,0 +1,11 @@ +import { unsafeStatic } from '@open-wc/storybook'; + +// eslint-disable-next-line import/no-mutable-exports +export let opts = { + tag: unsafeStatic('example-vanilla'), + header: '', +}; + +export function setOptions(newOptions) { + opts = newOptions; +} diff --git a/packages/generator-open-wc/LICENSE b/packages/generator-open-wc/LICENSE new file mode 100644 index 00000000..60227747 --- /dev/null +++ b/packages/generator-open-wc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 open-wc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/generator-open-wc/generators/eslint/index.js b/packages/generator-open-wc/generators/eslint/index.js new file mode 100644 index 00000000..3ec2d0e5 --- /dev/null +++ b/packages/generator-open-wc/generators/eslint/index.js @@ -0,0 +1,53 @@ +var Generator = require('yeoman-generator'); +const glob = require('glob'); + +function getClassName(tagName) { + return tagName.split('-').reduce((previous, part) => { + return previous + part.charAt(0).toUpperCase() + part.slice(1); + }, ''); +} + +module.exports = class GeneratorOpenWc extends Generator { + prompting() { + const done = this.async(); + + return this.prompt(PROMPTS).then(answers => { + this.props = answers; + done(); + }); + } + + default() { + const { tagName, type } = this.props; + + // Build component class + this.props.className = getClassName(tagName); + } + + writing() { + const { tagName, className } = this.props; + + // extend package.json + this.fs.extendJSON( + this.destinationPath('package.json'), + this.fs.readJSON(this.templatePath('package.json')) + ); + + // Write everything else + this.fs.copyTpl( + glob.sync(this.templatePath('!(package.json)'), { dot: true }), + this.destinationPath(), + this + ); + } +}; + +const PROMPTS = [ + { + name: 'tagName', + type: 'input', + required: true, + message: 'Give it a tag name (min two words separated by dashes)', + validate: str => /^([a-z])(?!.*[<>])(?=.*-).+$/.test(str) + }, +]; diff --git a/packages/generator-open-wc/generators/eslint/templates/.eslintignore b/packages/generator-open-wc/generators/eslint/templates/.eslintignore new file mode 100644 index 00000000..07e6e472 --- /dev/null +++ b/packages/generator-open-wc/generators/eslint/templates/.eslintignore @@ -0,0 +1 @@ +/node_modules diff --git a/packages/generator-open-wc/generators/eslint/templates/.eslintrc.js b/packages/generator-open-wc/generators/eslint/templates/.eslintrc.js new file mode 100644 index 00000000..1ccd046b --- /dev/null +++ b/packages/generator-open-wc/generators/eslint/templates/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + extends: [ + '@open-wc/eslint-config', + ].map(require.resolve), +}; diff --git a/packages/generator-open-wc/generators/eslint/templates/package.json b/packages/generator-open-wc/generators/eslint/templates/package.json new file mode 100644 index 00000000..7555b37d --- /dev/null +++ b/packages/generator-open-wc/generators/eslint/templates/package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "lint:eslint": "eslint --ext .js,.html ." + }, + "devDependencies": { + "@open-wc/eslint-config": "^0.0.0" + } +} diff --git a/packages/generator-open-wc/generators/storybook/index.js b/packages/generator-open-wc/generators/storybook/index.js new file mode 100644 index 00000000..3ec2d0e5 --- /dev/null +++ b/packages/generator-open-wc/generators/storybook/index.js @@ -0,0 +1,53 @@ +var Generator = require('yeoman-generator'); +const glob = require('glob'); + +function getClassName(tagName) { + return tagName.split('-').reduce((previous, part) => { + return previous + part.charAt(0).toUpperCase() + part.slice(1); + }, ''); +} + +module.exports = class GeneratorOpenWc extends Generator { + prompting() { + const done = this.async(); + + return this.prompt(PROMPTS).then(answers => { + this.props = answers; + done(); + }); + } + + default() { + const { tagName, type } = this.props; + + // Build component class + this.props.className = getClassName(tagName); + } + + writing() { + const { tagName, className } = this.props; + + // extend package.json + this.fs.extendJSON( + this.destinationPath('package.json'), + this.fs.readJSON(this.templatePath('package.json')) + ); + + // Write everything else + this.fs.copyTpl( + glob.sync(this.templatePath('!(package.json)'), { dot: true }), + this.destinationPath(), + this + ); + } +}; + +const PROMPTS = [ + { + name: 'tagName', + type: 'input', + required: true, + message: 'Give it a tag name (min two words separated by dashes)', + validate: str => /^([a-z])(?!.*[<>])(?=.*-).+$/.test(str) + }, +]; diff --git a/packages/generator-open-wc/generators/storybook/templates/.storybook/addons.js b/packages/generator-open-wc/generators/storybook/templates/.storybook/addons.js new file mode 100644 index 00000000..63116c29 --- /dev/null +++ b/packages/generator-open-wc/generators/storybook/templates/.storybook/addons.js @@ -0,0 +1,8 @@ +import '@storybook/addon-storysource/register'; +import '@storybook/addon-actions/register'; +import '@storybook/addon-backgrounds/register'; +import '@storybook/addon-notes/register'; +import '@storybook/addon-knobs/register'; +import '@storybook/addon-links/register'; +import '@storybook/addon-viewport/register'; +import '@storybook/addon-options/register'; diff --git a/packages/generator-open-wc/generators/storybook/templates/.storybook/config.js b/packages/generator-open-wc/generators/storybook/templates/.storybook/config.js new file mode 100644 index 00000000..26d8b2bf --- /dev/null +++ b/packages/generator-open-wc/generators/storybook/templates/.storybook/config.js @@ -0,0 +1,13 @@ +import { configure } from '@storybook/polymer'; +import { setOptions } from '@storybook/addon-options'; + +setOptions({ + hierarchyRootSeparator: /\|/, +}); + +const req = require.context('../stories', true, /\.stories\.js$/); +function loadStories() { + req.keys().forEach(filename => req(filename)); +} + +configure(loadStories, module); diff --git a/packages/generator-open-wc/generators/storybook/templates/.storybook/webpack.config.js b/packages/generator-open-wc/generators/storybook/templates/.storybook/webpack.config.js new file mode 100644 index 00000000..56dec291 --- /dev/null +++ b/packages/generator-open-wc/generators/storybook/templates/.storybook/webpack.config.js @@ -0,0 +1,17 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = (storybookBaseConfig, configType, defaultConfig) => { + defaultConfig.resolve.modules.push('bower_components'); + + defaultConfig.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + enforce: 'pre', + }); + + // TEMP fix: https://github.com/plotly/plotly.js/issues/2466#issuecomment-372924684 + defaultConfig.plugins.push(new webpack.IgnorePlugin(/vertx/)); + + return defaultConfig; +}; diff --git a/packages/generator-open-wc/generators/storybook/templates/package.json b/packages/generator-open-wc/generators/storybook/templates/package.json new file mode 100644 index 00000000..c6d8d3e9 --- /dev/null +++ b/packages/generator-open-wc/generators/storybook/templates/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "build-storybook": "build-storybook", + "start": "webpack-dev-server", + "storybook": "start-storybook -p 9001 -c .storybook" + }, + "devDependencies": { + "@open-wc/storybook": "^0.0.0" + } +} diff --git a/packages/generator-open-wc/generators/storybook/templates/stories/example.stories.js b/packages/generator-open-wc/generators/storybook/templates/stories/example.stories.js new file mode 100644 index 00000000..adaad946 --- /dev/null +++ b/packages/generator-open-wc/generators/storybook/templates/stories/example.stories.js @@ -0,0 +1,134 @@ +/* eslint-disable no-alert */ + +import { + storiesOf, + addParameters, + html, + action, + linkTo, + withBackgrounds, + withNotes, + document, + withKnobs, + text, + button, + number, + select, + date, + color, + array, + boolean, +} from '@open-wc/storybook'; + +storiesOf('Addon|Actions', module) + .add('Action only', () => html` + + `) + .add('Action and method', () => html` + + `); + + +storiesOf('Addon|Links', module) + .add('To Welcome', () => html` + + `); + + +storiesOf('Addon|Backgrounds', module) + .addDecorator( + withBackgrounds([ + { name: 'twitter', value: '#00aced', default: true }, + { name: 'facebook', value: '#3b5998' }, + ]), + ) + .add('Button with text', () => ` + +

See tab "Backgrounds" at the bottom

+ `); + + +storiesOf('Addon|Notes', module) + .addDecorator(withNotes) + .add('Simple note', () => ` +

See tab "notes" at the bottom.

+ `, { + notes: 'My notes on some bold text', + }); + + +storiesOf('Core|Methods for rendering', module) + .add('html string', () => '
Rendered with string
') + .add('document.createElement', () => { + const el = document.createElement('button'); + el.innerText = 'click me'; + el.foo = 'bar'; + el.addEventListener('click', e => alert(e.target.foo)); + return el; + }) + .add('lit-html', () => html` + + `); + + +const globalParameter = 'globalParameter'; +const chapterParameter = 'chapterParameter'; +const storyParameter = 'storyParameter'; + +addParameters({ globalParameter }); + +storiesOf('Core|Parameters', module) + .addParameters({ chapterParameter }) + .add('passed to story', ({ parameters: { fileName, ...parameters } }) => ` +
Parameters are ${JSON.stringify(parameters)}
+ `, { + storyParameter, + }); + + +storiesOf('Addon|Knobs', module) + .addDecorator(withKnobs) + .add('button label', () => html` + + `) + .add('complex', () => { + const name = text('Name', 'Jane'); + const stock = number('Stock', 20, { + range: true, min: 0, max: 30, step: 5, + }); + const fruits = { + Apple: 'apples', + Banana: 'bananas', + Cherry: 'cherries', + }; + const fruit = select('Fruit', fruits, 'apples'); + const price = number('Price', 2.25); + const colour = color('Border', 'deeppink'); + const today = date('Today', new Date('Jan 20 2017 GMT+0')); + const items = array('Items', ['Laptop', 'Book', 'Whiskey']); + const nice = boolean('Nice', true); + + const stockMessage = stock + ? html`I have a stock of ${stock} ${fruit}, costing $${price} each.` + : html`I'm out of ${fruit}${nice ? ', Sorry!' : '.'}`; + const dateOptions = { + year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC', + }; + + button('Arbitrary action', () => alert('You clicked it!')); + + return html` +
+

My name is ${name},

+

today is ${new Date(today).toLocaleDateString('en-US', dateOptions)}

+

${stockMessage}

+

Also, I have:

+
    + ${items.map(item => html` +
  • ${item}
  • + `)} +
+

${nice ? 'Nice to meet you!' : 'Leave me alone!'}

+
+ `; + }); diff --git a/packages/generator-open-wc/generators/storybook/templates/stories/index.stories.js b/packages/generator-open-wc/generators/storybook/templates/stories/index.stories.js new file mode 100644 index 00000000..459ffbfa --- /dev/null +++ b/packages/generator-open-wc/generators/storybook/templates/stories/index.stories.js @@ -0,0 +1,15 @@ +import { + storiesOf, + html, +} from '@open-wc/storybook'; + +import { opts } from './index.stories.options.js'; +import '../<%= props.tagName %>.js'; + +storiesOf(`${opts.header}`, module) + .add('default', () => html` + <${opts.tag}>user content tag: ${opts.tag} + `) + .add('right', () => html` + <${opts.tag} class="right">user content tag: ${opts.tag} + `); diff --git a/packages/generator-open-wc/generators/storybook/templates/stories/index.stories.options.js b/packages/generator-open-wc/generators/storybook/templates/stories/index.stories.options.js new file mode 100644 index 00000000..f6e0ff7f --- /dev/null +++ b/packages/generator-open-wc/generators/storybook/templates/stories/index.stories.options.js @@ -0,0 +1,11 @@ +import { unsafeStatic } from '@open-wc/storybook'; + +// eslint-disable-next-line import/no-mutable-exports +export let opts = { + tag: unsafeStatic('<%= props.tagName %>'), + header: '<<%= props.tagName %>>', +}; + +export function setOptions(newOptions) { + opts = newOptions; +} diff --git a/packages/generator-open-wc/generators/vanilla-bare/index.js b/packages/generator-open-wc/generators/vanilla-bare/index.js new file mode 100644 index 00000000..62f96eec --- /dev/null +++ b/packages/generator-open-wc/generators/vanilla-bare/index.js @@ -0,0 +1,61 @@ +var Generator = require('yeoman-generator'); +const glob = require('glob'); + +function getClassName(tagName) { + return tagName.split('-').reduce((previous, part) => { + return previous + part.charAt(0).toUpperCase() + part.slice(1); + }, ''); +} + +module.exports = class GeneratorOpenWc extends Generator { + prompting() { + const done = this.async(); + + return this.prompt(PROMPTS).then(answers => { + this.props = answers; + done(); + }); + } + + default() { + const { tagName, type } = this.props; + + // Build component class + this.props.className = getClassName(tagName); + } + + writing() { + const { tagName, className } = this.props; + + // Write & rename element src + this.fs.copyTpl( + this.templatePath('ExampleElement.js'), + this.destinationPath(`${className}.js`), + this + ); + + // Write & rename element definition + this.fs.copyTpl( + this.templatePath('example-element.js'), + this.destinationPath(`${tagName}.js`), + this + ); + + // Write everything else + this.fs.copyTpl( + glob.sync(this.templatePath('!(ExampleElement.js|example-element.js)'), { dot: true }), + this.destinationPath(), + this + ); + } +}; + +const PROMPTS = [ + { + name: 'tagName', + type: 'input', + required: true, + message: 'Give it a tag name (min two words separated by dashes)', + validate: str => /^([a-z])(?!.*[<>])(?=.*-).+$/.test(str) + }, +]; diff --git a/packages/generator-open-wc/generators/vanilla-bare/templates/.editorconfig b/packages/generator-open-wc/generators/vanilla-bare/templates/.editorconfig new file mode 100644 index 00000000..0c1c8bc4 --- /dev/null +++ b/packages/generator-open-wc/generators/vanilla-bare/templates/.editorconfig @@ -0,0 +1,29 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.json] +indent_size = 2 + +[*.{html,js}] +block_comment_start = /** +block_comment = * +block_comment_end = */ diff --git a/packages/generator-open-wc/generators/vanilla-bare/templates/.gitignore b/packages/generator-open-wc/generators/vanilla-bare/templates/.gitignore new file mode 100644 index 00000000..f1206923 --- /dev/null +++ b/packages/generator-open-wc/generators/vanilla-bare/templates/.gitignore @@ -0,0 +1,21 @@ +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## bower +/bower_components*/ +/bower-*.json + +## temp folders +/.tmp/ + +## we don't want lock files for now +yarn.lock +package-lock.json diff --git a/packages/generator-open-wc/generators/vanilla-bare/templates/ExampleElement.js b/packages/generator-open-wc/generators/vanilla-bare/templates/ExampleElement.js new file mode 100644 index 00000000..7a7ccef1 --- /dev/null +++ b/packages/generator-open-wc/generators/vanilla-bare/templates/ExampleElement.js @@ -0,0 +1,53 @@ +import { html, render } from 'lit-html'; + +export default class <%= props.className %> extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.header = 'My Example'; + } + + connectedCallback() { + this.update(); + } + + update() { + render(this.renderShadowDom(), this.shadowRoot); + } + + renderShadowDom() { + return html` + +

${this.header}

+
+ +
+ `; + } +} diff --git a/packages/generator-open-wc/generators/vanilla-bare/templates/LICENSE b/packages/generator-open-wc/generators/vanilla-bare/templates/LICENSE new file mode 100644 index 00000000..bc7483b0 --- /dev/null +++ b/packages/generator-open-wc/generators/vanilla-bare/templates/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 <%= props.tagName %> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/generator-open-wc/generators/vanilla-bare/templates/README.md b/packages/generator-open-wc/generators/vanilla-bare/templates/README.md new file mode 100644 index 00000000..c66d6645 --- /dev/null +++ b/packages/generator-open-wc/generators/vanilla-bare/templates/README.md @@ -0,0 +1,28 @@ +# \<<%= props.tagName %>> + +This webcomponent follows the [open-wc](https://github.com/open-wc/open-wc) recommendation. + +## Installation +```bash +npm i <%= props.tagName %> +``` + +## Usage +```html + + +<<%= props.tagName %>>> +``` + +## Demos using storybook (if applied by author) +```bash +npm run storybook +# visit provided url +``` + +## Linting using eslint (if applied by author) +```bash +npm run lint:eslint +``` diff --git a/packages/generator-open-wc/generators/vanilla-bare/templates/example-element.js b/packages/generator-open-wc/generators/vanilla-bare/templates/example-element.js new file mode 100644 index 00000000..36944944 --- /dev/null +++ b/packages/generator-open-wc/generators/vanilla-bare/templates/example-element.js @@ -0,0 +1,3 @@ +import <%= props.className %> from './<%= props.className %>.js'; + +window.customElements.define('<%= props.tagName %>', <%= props.className %>); diff --git a/packages/generator-open-wc/generators/vanilla-bare/templates/package.json b/packages/generator-open-wc/generators/vanilla-bare/templates/package.json new file mode 100644 index 00000000..29ac30e8 --- /dev/null +++ b/packages/generator-open-wc/generators/vanilla-bare/templates/package.json @@ -0,0 +1,12 @@ +{ + "name": "<%= props.tagName %>", + "version": "0.0.0", + "description": "Webcomponent <%= props.tagName %> following open-wc recommendations", + "author": "<%= props.tagName %>", + "homepage": "https://github.com/open-wc/open-wc/", + "license": "MIT", + "repository": "https://github.com/open-wc/open-wc/", + "dependencies": { + "lit-html": "^0.11.0" + } +} diff --git a/packages/generator-open-wc/package.json b/packages/generator-open-wc/package.json new file mode 100644 index 00000000..38e19821 --- /dev/null +++ b/packages/generator-open-wc/package.json @@ -0,0 +1,16 @@ +{ + "name": "generator-open-wc", + "version": "0.0.0", + "description": "Scaffold web components following open-wc recommendations", + "author": "open-wc", + "homepage": "https://github.com/open-wc/open-wc/", + "license": "MIT", + "repository": "https://github.com/open-wc/open-wc/tree/master/packages/generator-open-wc", + "keywords": [ + "yeoman-generator", + "open-wc" + ], + "dependencies": { + "yeoman-generator": "^1.0.0" + } +} diff --git a/packages/storybook/LICENSE b/packages/storybook/LICENSE new file mode 100644 index 00000000..60227747 --- /dev/null +++ b/packages/storybook/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 open-wc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/storybook/index.js b/packages/storybook/index.js new file mode 100644 index 00000000..305efc53 --- /dev/null +++ b/packages/storybook/index.js @@ -0,0 +1,71 @@ +export { storiesOf, addParameters } from '@storybook/polymer'; +export { action } from '@storybook/addon-actions'; +export { linkTo } from '@storybook/addon-links'; +export { withBackgrounds } from '@storybook/addon-backgrounds'; +export { withNotes } from '@storybook/addon-notes'; +export { document } from 'global'; +export { + withKnobs, + text, + button, + number, + select, + date, + color, + array, + boolean, +} from '@storybook/addon-knobs'; + +import { html as litHtml } from 'lit-html'; + +/** + * This is a wrapper around lit-html that supports dynamic strings to be added as a preprocessing + * step, before a template is passed to lit's html function. + * A dynamic string will be evaluated one time (on init) and passed to lit-html as being + * part of the static string. + * + * WARNING: do not use in production!!! has a huge performance penalty as every string is + * different from lit-htmls perspective so a new tag is created every time. + * + * A valid use case for this would be to create dynamic tag names. + * + * @example: + * const tag = unsafeStatic('my-tag'); + * html`<${tag} prop="${prop}">` + * // will in turn calls lit-html html function as: + * html`` + */ +export function html(strings, ...values) { + const newVal = []; // result values to be passed on to lit-html + const newStr = []; // result strings to be passed on to lit-html + + const isDynamicProp = p => p && p.d && typeof p.d === 'string' && Object.keys(p).length === 1; + const addToCurrentString = (add) => { + newStr[newStr.length - 1] = newStr[newStr.length - 1] + add; + }; + + // Create the result arrays + values.forEach((string, index) => { + if (index === 0) { + newStr.push(strings[0]); // this is either ''(if tagged starts with value) or string + } + // Situation 1 : dynamic string + const p = values[index]; // potential dynamic string + if (isDynamicProp(p)) { + addToCurrentString(p.d); + addToCurrentString(strings[index + 1]); + } else { + // Situation 2: no dynamic string, just push string and value + newVal.push(p); // a 'real' value + newStr.push(strings[index + 1]); + } + }); + // Return lit template + return litHtml(newStr, ...newVal); +}; + +export function unsafeStatic(options) { + return { + d: options + }; +} diff --git a/packages/storybook/package.json b/packages/storybook/package.json new file mode 100644 index 00000000..c9a33186 --- /dev/null +++ b/packages/storybook/package.json @@ -0,0 +1,29 @@ +{ + "name": "@open-wc/storybook", + "version": "0.0.0", + "description": "Storybook configuration following open-wc recommendations", + "author": "open-wc", + "homepage": "https://github.com/open-wc/open-wc/", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": "https://github.com/open-wc/open-wc/tree/master/packages/storybook", + "dependencies": { + "@babel/core": "^7.0.0", + "@storybook/addon-actions": "^4.0.0-alpha.20", + "@storybook/addon-backgrounds": "^4.0.0-alpha.20", + "@storybook/addon-knobs": "^4.0.0-alpha.20", + "@storybook/addon-links": "^4.0.0-alpha.20", + "@storybook/addon-notes": "^4.0.0-alpha.20", + "@storybook/addon-options": "^4.0.0-alpha.20", + "@storybook/addon-storysource": "^4.0.0-alpha.20", + "@storybook/addon-viewport": "^4.0.0-alpha.20", + "@storybook/polymer": "^4.0.0-alpha.20", + "@webcomponents/webcomponentsjs": "^1.2.0", + "babel-loader": "^8.0.1", + "lit-html": "^0.11.0", + "moment": "^2.22.2", + "polymer-webpack-loader": "^2.0.3" + } +}