├── CNAME ├── .gitattributes ├── example ├── avengers.png ├── avengersbg.jpeg ├── .showroom │ ├── config.js │ ├── showroom.time-component.js │ ├── showroom.demo-user-card.js │ ├── hello.showroom.js │ ├── showroom.money-input.js │ ├── showroom.stencil.js │ └── showroom.demo-component-avengers.js ├── toggle.js ├── timer-component.js ├── money-input.js ├── demo-user-card.js └── demo-component-avengers.js ├── .npmignore ├── .gitignore ├── app ├── client │ ├── assets │ │ ├── FiraMono-Bold.ttf │ │ ├── Forum-Regular.ttf │ │ ├── showroom-bg.png │ │ ├── FiraMono-Regular.ttf │ │ ├── SourceCodePro-Bold.ttf │ │ ├── SourceSansPro-Bold.ttf │ │ ├── SourceCodePro-Regular.ttf │ │ ├── SourceSansPro-Regular.ttf │ │ ├── fonts.css │ │ ├── main.css │ │ ├── normalize.css │ │ └── topcoat-desktop-light.css │ ├── router.js │ ├── component-description.js │ ├── showroom-component-list.js │ ├── component-info.js │ ├── component-renderer.js │ ├── index.html │ ├── showroom-integration.js │ ├── showroom-json-editor.js │ ├── showroom-app.js │ ├── custom-control-form.js │ └── component-dashboard.js └── server │ ├── logger.js │ ├── build-component-index.js │ └── index.js ├── .github ├── workflows │ └── test.yml └── ISSUE_TEMPLATE │ ├── bug.md │ └── feature_request.md ├── test ├── loading-correctly.test.js ├── set-test-subject.test.js ├── launch-server.js ├── properties-attributes.js └── api.test.js ├── Dockerfile ├── package.json ├── README.md ├── CODE_OF_CONDUCT.md ├── puppeteer └── index.js ├── test-utils.js └── LICENSE /CNAME: -------------------------------------------------------------------------------- 1 | showroom.js.org 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | app/client/assets/* linguist-vendored 2 | -------------------------------------------------------------------------------- /example/avengers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/example/avengers.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example 2 | test 3 | docs 4 | .vscode 5 | .idea 6 | .nyc_output 7 | coverage 8 | .openode -------------------------------------------------------------------------------- /example/avengersbg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/example/avengersbg.jpeg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | .idea 4 | package-lock.json 5 | .openode 6 | .nyc_output 7 | coverage 8 | docs -------------------------------------------------------------------------------- /app/client/assets/FiraMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/app/client/assets/FiraMono-Bold.ttf -------------------------------------------------------------------------------- /app/client/assets/Forum-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/app/client/assets/Forum-Regular.ttf -------------------------------------------------------------------------------- /app/client/assets/showroom-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/app/client/assets/showroom-bg.png -------------------------------------------------------------------------------- /app/client/assets/FiraMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/app/client/assets/FiraMono-Regular.ttf -------------------------------------------------------------------------------- /app/client/assets/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/app/client/assets/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /app/client/assets/SourceSansPro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/app/client/assets/SourceSansPro-Bold.ttf -------------------------------------------------------------------------------- /app/client/assets/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/app/client/assets/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /app/client/assets/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavichay/showroom/HEAD/app/client/assets/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /example/.showroom/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | styles: [], 3 | scripts: [ 4 | 'https://unpkg.com/split-me/dist/split-me.js' 5 | ] 6 | } -------------------------------------------------------------------------------- /example/.showroom/showroom.time-component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | component: 'timer-component', 3 | alias: 'Simple Custom Element', 4 | section: 'Vanilla', 5 | path: '/timer-component.js', 6 | events: ['tick-tock'] 7 | } -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Install 13 | run: npm ci 14 | - name: Test 15 | run: npm test 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: 'What went wrong? ' 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | **Reproduce ** 13 | 14 | **Code snippets? Links? ** 15 | 16 | Add any other context about the problem here. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: 'What can be done? ' 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | **Describe the solution you'd like** 13 | 14 | **Additional context** 15 | -------------------------------------------------------------------------------- /test/loading-correctly.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | describe('Showroom site loading', async () => { 4 | 5 | it('Should display title', async () => { 6 | const app = await page.$('showroom-app'); 7 | assert(app, 'Expecting showroom app to be on page'); 8 | assert(app._remoteObject.type === 'object'); 9 | }); 10 | }); -------------------------------------------------------------------------------- /app/server/logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | const {silent, verbose} = Object.assign({silent: false, verbose: false}, global.showroom); 4 | const stream = console; 5 | 6 | module.exports = { 7 | info: (...args)=>!silent && verbose && stream.log(...args.map(arg=>chalk.green(arg))), 8 | warn: (...args)=>!silent && stream.log(...args.map(arg=>chalk.yellow(arg))), 9 | error: (...args)=>!silent && stream.error(...args.map(arg=>chalk.red(arg))), 10 | }; 11 | -------------------------------------------------------------------------------- /test/set-test-subject.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | describe('showroom::setTestSubject', () => { 4 | [ 5 | 'demo-component-avengers', 6 | 'split-me', 7 | 'timer-component', 8 | 'demo-user-card' 9 | ].forEach(componentName => { 10 | it('Component: ' + componentName, async () => { 11 | await showroom.setTestSubject(componentName); 12 | assert(await showroom.getProperty('localName') === componentName); 13 | }); 14 | }) 15 | }); -------------------------------------------------------------------------------- /app/client/router.js: -------------------------------------------------------------------------------- 1 | customElements.define('showroom-router', class extends HTMLElement { 2 | 3 | constructor () { 4 | super(); 5 | window.router = this; 6 | this.onHash = () => { 7 | const hash = window.location.hash.slice(1); 8 | this.component = hash; 9 | this.dispatchEvent(new CustomEvent('change', { 10 | detail: hash 11 | })); 12 | } 13 | window.addEventListener('hashchange', this.onHash); 14 | this.onHash(); 15 | } 16 | 17 | }); -------------------------------------------------------------------------------- /example/.showroom/showroom.demo-user-card.js: -------------------------------------------------------------------------------- 1 | import '../demo-user-card.js'; 2 | const DemoUserCard = customElements.get('demo-user-card'); 3 | 4 | export default { 5 | component: 'demo-user-card', 6 | alias: 'Lit: ', 7 | section: 'Lit-Element', 8 | path: '/demo-user-card.js', 9 | events: ['data-loaded'], 10 | autoAttributes: true, 11 | autoProperties: true, 12 | attributes: { 13 | 'user-id': Math.floor(Math.random() * 10000).toString(), 14 | 'accent-color': '#FFFFEE' 15 | } 16 | } -------------------------------------------------------------------------------- /example/.showroom/hello.showroom.js: -------------------------------------------------------------------------------- 1 | import '../toggle.js'; 2 | 3 | export default { 4 | section: 'Showroom', 5 | component: 'Welcome', 6 | descriptionURL: '/docs/index.html', 7 | innerHTML: ` 8 |
20 |
` 21 | }; -------------------------------------------------------------------------------- /example/toggle.js: -------------------------------------------------------------------------------- 1 | dashboard.setAttribute('collapsed', ''); 2 | 3 | (async function prepare () { 4 | await window.showroomReady; 5 | if (!window.location.hash) showroom.setTestSubject('Welcome'); 6 | setTimeout( () => { 7 | if (!window.location.hash) { 8 | window.location.hash = 'Welcome'; 9 | } 10 | }, 5) 11 | })(); 12 | 13 | 14 | setTimeout( async () => { 15 | await window.showroomReady; 16 | window.addEventListener('hashchange', () => { 17 | if (window.location.hash !== '#Welcome') { 18 | dashboard.removeAttribute('collapsed'); 19 | } else { 20 | dashboard.setAttribute('collapsed', ''); 21 | } 22 | }); 23 | }, 350); -------------------------------------------------------------------------------- /example/.showroom/showroom.money-input.js: -------------------------------------------------------------------------------- 1 | export default { 2 | component: 'money-input', 3 | alias: 'Extending Native Elements', 4 | section: 'Vanilla', 5 | path: '/money-input.js', 6 | events: ['change'], 7 | 8 | // customized element 9 | extends: 'input', 10 | attributes: { 11 | currency: 'USD' 12 | }, 13 | properties: { 14 | disabled: false 15 | }, 16 | functions: { 17 | clear: () => { 18 | showroom.component.clear() 19 | } 20 | }, 21 | outerHTML: ` 22 |
23 |

class MoneyInput extends HTMLInputElement

24 | 25 |
` 26 | } -------------------------------------------------------------------------------- /example/timer-component.js: -------------------------------------------------------------------------------- 1 | customElements.define('timer-component', class extends HTMLElement { 2 | constructor () { 3 | super(); 4 | this.attachShadow({mode: 'open'}); 5 | } 6 | 7 | connectedCallback () { 8 | clearInterval(this.intervalId); 9 | this.intervalId = setInterval(() => { 10 | this.update(); 11 | }, 1000); 12 | this.update(); 13 | } 14 | 15 | disconnectedCallback () { 16 | clearInterval(this.intervalId); 17 | } 18 | 19 | update () { 20 | const now = new Date(); 21 | this.shadowRoot.innerHTML = now.toTimeString(); 22 | this.dispatchEvent(new CustomEvent('tick-tock', { 23 | detail: now 24 | })); 25 | } 26 | }); -------------------------------------------------------------------------------- /test/launch-server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const puppeteer = require('puppeteer'); 3 | const showroomFactory = require('../puppeteer/index.js'); 4 | 5 | /** 6 | * @typedef {import('../puppeteer').Showroom} Showroom 7 | */ 8 | 9 | global.opts = { 10 | baseUrl: 'http://127.0.0.1:3001', 11 | headless: process.env.NODE_ENV !== 'development', 12 | slowMo: 15 13 | } 14 | 15 | global.puppeteer = puppeteer; 16 | global.noWatch = true; 17 | 18 | before(async () => { 19 | const showroom = showroomFactory({ 20 | port: 3001, 21 | silent: true, 22 | path: path.join(__dirname, '../example'), 23 | headless: global.opts.headless 24 | }); 25 | await showroom.start(); 26 | 27 | global.browser = showroom.browser; 28 | global.page = showroom.page; 29 | global.showroom = showroom.utils; 30 | global.showroomInstance = showroom; 31 | }) 32 | 33 | after(async () => { 34 | await showroomInstance.stop(); 35 | }) -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10-alpine 2 | 3 | WORKDIR /opt/app 4 | 5 | ENV PORT=80 6 | ENV FORCE_SSL=true 7 | 8 | RUN touch /usr/bin/start.sh # this is the script which will run on start 9 | 10 | # if you need a build script, uncomment the line below 11 | # RUN echo 'sh mybuild.sh' >> /usr/bin/start.sh 12 | 13 | # if you need redis, uncomment the lines below 14 | # RUN apk --update add redis 15 | # RUN echo 'redis-server &' >> /usr/bin/start.sh 16 | 17 | # daemon for cron jobs 18 | RUN echo 'echo will install crond...' >> /usr/bin/start.sh 19 | RUN echo 'crond' >> /usr/bin/start.sh 20 | 21 | # Basic npm start verification 22 | RUN echo 'nb=`cat package.json | grep start | wc -l` && if test "$nb" = "0" ; then echo "*** Boot issue: No start command found in your package.json in the scripts. See https://docs.npmjs.com/cli/start" ; exit 1 ; fi' >> /usr/bin/start.sh 23 | 24 | RUN echo 'npm install --production' >> /usr/bin/start.sh 25 | 26 | # npm start, make sure to have a start attribute in "scripts" in package.json 27 | RUN echo 'npm run serve' >> /usr/bin/start.sh 28 | -------------------------------------------------------------------------------- /example/.showroom/showroom.stencil.js: -------------------------------------------------------------------------------- 1 | export default { 2 | section: 'Stencil', 3 | component: 'split-me', 4 | alias: 'Stencil: ', 5 | outerHTML: ` 6 | 7 | 15 |
16 | 17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 |
27 |
`, 28 | attributes: { 29 | n: 3, 30 | sizes: "0.33, 0.33, 0.34" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/client/component-description.js: -------------------------------------------------------------------------------- 1 | customElements.define('component-description', class extends HTMLElement { 2 | 3 | constructor () { 4 | super(); 5 | this._ = this.attachShadow({mode:'open'}); 6 | this._.innerHTML = /*html*/` 7 | 8 | 16 | 17 | 18 | `; 19 | this.modal = this._.querySelector('dialog'); 20 | } 21 | 22 | close () { 23 | this.modal.close(); 24 | } 25 | 26 | open () { 27 | if (!this.modal.open) { 28 | this.modal.showModal(); 29 | } 30 | } 31 | 32 | replaceContent (markdown) { 33 | this.modal.innerHTML = ` 34 | 35 |
36 | ${marked(markdown)}
`; 37 | } 38 | 39 | setContent (markdown) { 40 | this.replaceContent(markdown); 41 | this.open(); 42 | this._.querySelector('#closeButton').onclick = () => { 43 | this.modal.close(); 44 | }; 45 | } 46 | 47 | }); -------------------------------------------------------------------------------- /example/money-input.js: -------------------------------------------------------------------------------- 1 | class MoneyInput extends HTMLInputElement { 2 | 3 | constructor () { 4 | super(); 5 | this.addEventListener('input', this.update.bind(this)); 6 | this.addEventListener('change', this.update.bind(this)); 7 | this.currency = 'USD'; 8 | } 9 | 10 | static get observedAttributes () { 11 | return ['currency', 'type']; 12 | } 13 | 14 | attributeChangedCallback (attr, oldValue, newValue) { 15 | switch (attr) { 16 | case 'currency': 17 | this.currency = newValue; 18 | this.update(); 19 | break; 20 | case 'type': 21 | if (newValue !== 'text') { 22 | this.setAttribute('type', 'text'); 23 | this.update(); 24 | } 25 | break; 26 | } 27 | } 28 | 29 | set disabled (v) { 30 | if (v) { 31 | this.setAttribute('disabled', 'disabled'); 32 | } else { 33 | this.removeAttribute('disabled'); 34 | } 35 | } 36 | 37 | get disabled () { 38 | return this.hasAttribute('disabled'); 39 | } 40 | 41 | clear () { 42 | this.value = this.currency + ' ' + 0; 43 | } 44 | 45 | update () { 46 | this.value = this.currency + ' ' + this.value.replace(/\D/g,''); 47 | } 48 | 49 | } 50 | 51 | customElements.define('money-input', MoneyInput, { 52 | extends: 'input' 53 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "showroom", 3 | "version": "0.9.3", 4 | "description": "Interactive visual development and testing of custom elements", 5 | "main": "./app/server/index.js", 6 | "scripts": { 7 | "start": "node ./app/server/index.js --path ./example --port 8080", 8 | "dev": "nodemon ./app/server/index.js --path ./example --port 3000", 9 | "test": "mocha -R progress --timeout 15000", 10 | "serve": "node ./app/server/index.js --path ./example --port 80", 11 | "docs": "jsdoc ./puppeteer/index.js test-utils.js README.md -d docs", 12 | "prepublish": "npm run docs" 13 | }, 14 | "repository": "https://github.com/eavichay/showroom", 15 | "bin": { 16 | "showroom": "./app/server/index.js" 17 | }, 18 | "author": "Avichay Eyal ", 19 | "license": "MIT", 20 | "dependencies": { 21 | "chalk": "^2.4.1", 22 | "html-validator": "^3.1.3", 23 | "jsdoc": "^3.5.5", 24 | "jsoneditor": "^5.24.7", 25 | "koa": "^2.5.3", 26 | "koa-mount": "^4.0.0", 27 | "koa-sslify": "^2.1.2", 28 | "koa-static": "^5.0.0", 29 | "marked": "^0.5.1", 30 | "milligram": "^1.3.0", 31 | "puppeteer": "^1.9.0", 32 | "slim-js": "^4.0.5", 33 | "yargs": "^12.0.2" 34 | }, 35 | "devDependencies": { 36 | "mocha": "^5.2.0", 37 | "nodemon": "^1.19.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/client/assets/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Fira Mono'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Fira Mono Regular'), local('FiraMono-Regular'), url('./FiraMono-Regular.ttf') format('woff2'); 6 | } 7 | 8 | @font-face { 9 | font-family: 'Fira Mono'; 10 | font-style: bold; 11 | font-weight: 700; 12 | src: local('Fira Mono Bold'), local('FiraMono-Bold'), url('./FiraMono-Bold.ttf') format('woff2'); 13 | } 14 | 15 | @font-face { 16 | font-family: 'Source Code Pro'; 17 | font-style: normal; 18 | font-weight: 400; 19 | src: local('Source Code Pro'), local('SourceCodePro-Regular'), url('./SourceCodePro-Regular.ttf') format('woff2'); 20 | } 21 | 22 | @font-face { 23 | font-family: 'Source Code Pro'; 24 | font-style: bold; 25 | font-weight: 700; 26 | src: local('Source Code Pro'), local('SourceCodePro-Bold'), url('./SourceCodePro-Bold.ttf') format('woff2'); 27 | } 28 | 29 | @font-face { 30 | font-family: 'Forum'; 31 | font-style: normal; 32 | font-weight: 400; 33 | src: local('Forum Regular'), local('Forum-Regular'), url('./Forum-Regular.ttf') format('woff2'); 34 | } 35 | 36 | @font-face { 37 | font-family: 'Source Sans'; 38 | font-style: normal; 39 | font-weight: 400; 40 | src: local('Source Sans'), local('SourceSansPro-Regular'), url('./SourceSansPro-Regular.ttf') format('woff2'); 41 | } 42 | 43 | @font-face { 44 | font-family: 'Source Sans'; 45 | font-style: bold; 46 | font-weight: 700; 47 | src: local('Source Sans Pro'), local('SourceSansPro-Bold'), url('./SourceSansPro-Bold.ttf') format('woff2'); 48 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![showroom](../app/client/assets/showroom-bg.png) 2 | 3 | # showroom: Universal development and automated test environment for web components 4 | 5 | [![Build Status](https://semaphoreci.com/api/v1/eavichay/showroom/branches/master/badge.svg)](https://semaphoreci.com/eavichay/showroom) 6 | 7 | ### Installation 8 | 9 | `npm install -g showroom` 10 | 11 | create .showroom folder in your project 12 | add descriptor files (see [example](https://github.com/eavichay/showroom/tree/master/example/.showroom)) for your web components. 13 | 14 | run showroom and see the magic. 15 | 16 | See [live demo here](https://showroomjs.com) 17 | 18 | Supports: 19 | - Any stack: Polymer, LitElement, Slim.js, Vanilla, Skate,... 20 | - Supports customized built-in elements (i.e. extends HTMLInputElement) 21 | - Supports innerHTML and wrapping HTML 22 | - Supports global/local scripting and styling 23 | - Supports CI/CD queries for shadow-roots 24 | - Smooth pupeteer integration 25 | 26 | ### Configuration 27 | 28 | The server following options: 29 | - `port (int)` - The port on which the server listens. Default is `3000`. 30 | - `path (string)` - The path, relative to the `process.cwd()`, used to search for project files. Default is `./`. 31 | - `silent (boolean)` - If true, completely disables logging. Default is `false`. 32 | - `verbose (boolean)` - If true, verbose messages will be logged. Otherwise only errors and warnings will be logged. Default it `false`. 33 | 34 | ### Build & Development 35 | `git clone git@github.com:eavichay/showroom.git` 36 | `npm install` 37 | `npm run dev` 38 | 39 | ### Online Documentation/Wiki 40 | [Here](https://github.com/eavichay/showroom/wiki) 41 | -------------------------------------------------------------------------------- /example/.showroom/showroom.demo-component-avengers.js: -------------------------------------------------------------------------------- 1 | export default { 2 | section: 'Slim.js', 3 | alias: 'slim.js component', 4 | descriptionURL: false, //'https://google.com', 5 | description: ` 6 | # Demo Component 7 | 8 | ### Usage 9 | \`\`\`html 10 | 11 | \`\`\` 12 | \`\`\`javascript 13 | component.data = { 14 | rank: 95, 15 | lastAssignment: 'Save captain Fury' 16 | } // will trigger *datachanged* event 17 | 18 | component.mission = 'Save the world' // will trigger *taskselected* event 19 | \`\`\` 20 | 21 | ### Attributes 22 | - **accent-color** Accent color of the component 23 | - **text-color** Color of text 24 | 25 | ### Properties 26 | - {object} data 27 | - {string} title 28 | - {Array} members 29 | - {string} mission 30 | 31 | ### Events 32 | - missionstarted 33 | - datachanged 34 | - taskselected 35 | `, 36 | 37 | component: 'demo-component-avengers', 38 | 39 | path: '/demo-component-avengers.js', 40 | 41 | events: ['missionstarted', 'datachanged', 'taskselected'], 42 | properties: { 43 | title: 'Avengers', 44 | members: [ 45 | 'Captain America', 'Ironman', 'Hawkeye' 46 | ], 47 | mission: new Set(['Protect New York', 'Save the world', 'Shut down Hydra']), 48 | data: { 49 | rank: 95, 50 | lastMission: 'Save captain Fury' 51 | } 52 | }, 53 | attributes: { 54 | 'accent-color': 'darkred', 55 | 'text-color': 'white', 56 | }, 57 | outerHTML: /*html*/` 58 | 59 |
60 | 61 | 62 | 63 |
64 | ` 65 | } -------------------------------------------------------------------------------- /app/server/build-component-index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { promisify } = require('util'); 3 | const readdir = promisify(fs.readdir); 4 | const lstat = promisify(fs.lstat); 5 | const path = require('path'); 6 | const logger = require('./logger'); 7 | 8 | let componentList = []; 9 | 10 | function debounce(func, wait, immediate) { 11 | var timeout; 12 | return function() { 13 | var context = this, args = arguments; 14 | var later = function() { 15 | timeout = null; 16 | if (!immediate) func.apply(context, args); 17 | }; 18 | var callNow = immediate && !timeout; 19 | clearTimeout(timeout); 20 | timeout = setTimeout(later, wait); 21 | if (callNow) func.apply(context, args); 22 | }; 23 | }; 24 | 25 | let watcher; 26 | 27 | const doSearch = async (rootPath) => { 28 | logger.info('Searching in', rootPath); 29 | const files = (await readdir(rootPath)).sort(); 30 | logger.info(`Found files: ${files}`); 31 | await Promise.all(files.map(async filename => { 32 | const filePath = path.join(rootPath, filename); 33 | const stats = await lstat(filePath); 34 | if (stats.isDirectory()) { 35 | if (filename !== 'node_modules') { 36 | await doSearch(filePath); 37 | } 38 | } else if (stats.isFile() && filename !== 'config.js') { 39 | logger.info(`\tAdding: ${filename}`); 40 | componentList.push(filename); 41 | } 42 | })); 43 | logger.warn(`${componentList.length} Total file(s) found in .showroom folder`); 44 | return componentList; 45 | }; 46 | 47 | module.exports = { 48 | getComponents: () => componentList, 49 | search: async function (root, silent = false) { 50 | if (!watcher) { 51 | const result = await doSearch(root); 52 | if (!global.noWatch) { 53 | watcher = fs.watch(root, {}, debounce((e, f) => { 54 | componentList = []; 55 | logger.warn(`Filesystem change detected... scanning .showroom folder`); 56 | doSearch(root, silent); 57 | }, 150)); 58 | } 59 | return result; 60 | } else { 61 | return componentList; 62 | } 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /app/client/showroom-component-list.js: -------------------------------------------------------------------------------- 1 | import { Slim } from '/.showroom-app/Slim.js'; 2 | import '/.showroom-app/directives/repeat.js'; 3 | 4 | Slim.tag('showroom-component-list', 5 | /*html*/` 6 | 32 |
33 | {{section.name}} 34 |
    35 |
  • 36 | {{getAlias(item)}} 37 | 38 |
  • 39 |
40 |
41 | `, class extends Slim { 42 | 43 | constructor () { 44 | super(); 45 | this.currentComponent = ''; 46 | window.router.addEventListener('change', ({detail}) => { 47 | this.currentComponent = detail; 48 | this.commit('currentComponent'); 49 | }); 50 | } 51 | 52 | onRender () { 53 | setTimeout( () => { 54 | this.currentComponent = window.router.component; 55 | this.commit('currentComponent'); 56 | }); 57 | } 58 | 59 | isSelected (current, item) { 60 | return current === item.component; 61 | } 62 | 63 | getAlias (module) { 64 | return module.alias || module.component; 65 | } 66 | 67 | getButtonStyle (item) { 68 | if (item.description || item.descriptionURL) { 69 | return '' 70 | } else { 71 | return 'display: none'; 72 | } 73 | } 74 | 75 | get useShadow () { return true; } 76 | 77 | onComponentClick (e) { 78 | const item = e.target.item; 79 | window.location.hash = item.component; 80 | } 81 | 82 | onDocsClick (e) { 83 | const item = e.target.item; 84 | if (item.descriptionURL) { 85 | window.open(item.descriptionURL, '_blank'); 86 | } else { 87 | this.callAttribute('on-docs', item.description); 88 | } 89 | } 90 | 91 | }) -------------------------------------------------------------------------------- /test/properties-attributes.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | describe('showroom::setProperty', () => { 4 | 5 | const component = 'demo-component-avengers'; 6 | let root; 7 | 8 | const findControlNodeValue = async (type, name) => { 9 | const node = await showroom.find(`// component-dashboard // #dashboard custom-control-form // [data-target-${type}="${name}"]`, root); 10 | return await showroom.getProperty('value', node); 11 | } 12 | 13 | beforeEach(async () => { 14 | await showroom.setTestSubject(component); 15 | root = await page.$('showroom-app'); 16 | await page.waitFor(300); 17 | }); 18 | 19 | it('Should set property via UI', async () => { 20 | const input = await showroom.find('// component-dashboard // #dashboard custom-control-form // input[data-target-property="title"]', root); 21 | await input.click({clickCount: 2}); 22 | await input.type('New Title'); 23 | await page.keyboard.press('Enter'); 24 | const titleElement = await showroom.find('// h3'); 25 | assert.equal(await showroom.getProperty('innerText', titleElement), 'New Title'); 26 | }); 27 | 28 | it('Should set attribute via UI', async () => { 29 | const input = await showroom.find('// component-dashboard // #dashboard custom-control-form // input[data-target-attribute="text-color"]', root); 30 | await input.click({clickCount: 2}); 31 | await input.type('blue'); 32 | await page.keyboard.press('Enter'); 33 | assert.equal(await showroom.getAttribute('text-color'), 'blue'); 34 | }); 35 | 36 | describe('UI Sync when values change via showroom-puppeteer integration', async () => { 37 | beforeEach(async () => { 38 | await showroom.setTestSubject(component); 39 | root = await page.$('showroom-app'); 40 | await page.waitFor(300); 41 | }); 42 | 43 | it('Should update text', async () => { 44 | await showroom.setProperty('title', 'New Title'); 45 | const text = await findControlNodeValue('property', 'title'); 46 | assert.equal(text, 'New Title'); 47 | }); 48 | 49 | it('Should update from array', async () => { 50 | await showroom.setProperty('mission', 'Save the world'); 51 | assert.equal(await findControlNodeValue('property', 'mission'), 'Save the world'); 52 | await showroom.setProperty('mission', 'Shut down Hydra'); 53 | assert.equal(await findControlNodeValue('property', 'mission'), 'Shut down Hydra'); 54 | }); 55 | 56 | it('Should sync attribute', async () => { 57 | await showroom.setAttribute('accent-color', 'grey'); 58 | assert.equal(await findControlNodeValue('attribute', 'accent-color'), 'grey'); 59 | }); 60 | }); 61 | 62 | }); -------------------------------------------------------------------------------- /app/client/component-info.js: -------------------------------------------------------------------------------- 1 | const template = /*html*/` 2 | 59 | 60 | 61 | 62 |

Attributes

63 |
64 | {{attr.name}}{{attr.value}} 65 |
66 |
67 | 68 |

Properties

69 |
70 | {{prop.name}}{{prop.value}} 71 |
72 |
73 | `; 74 | 75 | Slim.tag('component-info-panel', 76 | template, 77 | class extends Slim { 78 | 79 | constructor () { 80 | super(); 81 | this.cAttributes = []; 82 | this.cProps = []; 83 | window.addEventListener('click', this.update.bind(this)); 84 | } 85 | 86 | connectedCallback () { 87 | this.setAttribute('collapsed', ''); 88 | } 89 | 90 | get useShadow() { return true; } 91 | 92 | toggle() { 93 | this.toggleAttribute('collapsed'); 94 | } 95 | 96 | update () { 97 | try { 98 | const attrs = window.component.getAttributeNames(); 99 | this.cAttributes = attrs.map(attrName => { 100 | return { 101 | name: attrName, 102 | value: window.component.getAttribute(attrName) 103 | }; 104 | }); 105 | } 106 | catch (err) { 107 | this.cAttributes = []; 108 | } 109 | try { 110 | const properties = Object.getOwnPropertyNames(dashboard.componentModule); 111 | this.cProps = properties.map(prop => { 112 | return { 113 | name: prop, 114 | value: window.component[prop] 115 | } 116 | }); 117 | } catch (err) { 118 | this.cProps = []; 119 | } 120 | } 121 | }) -------------------------------------------------------------------------------- /app/client/component-renderer.js: -------------------------------------------------------------------------------- 1 | export default class ComponentRenderer extends HTMLElement { 2 | 3 | constructor ( 4 | outerHTML = '', 5 | attributes = {}, 6 | isExtending) { 7 | super(); 8 | this.extending = isExtending; 9 | this.targetAttributes = attributes; 10 | this.attachShadow({mode: 'open'}); 11 | this._ = this.shadowRoot; 12 | this._.innerHTML = /*html*/` 13 | 16 |
17 | ${outerHTML} 18 | `; 19 | this.fallbackContainer = this._.querySelector('#fallback'); 20 | } 21 | 22 | static get observedAttributes () { 23 | return ['url', 'name']; 24 | } 25 | 26 | attributeChangedCallback() { 27 | this.render(); 28 | } 29 | 30 | createElement (name, extending) { 31 | let result; 32 | if (extending) { 33 | result = document.createElement(extending, { 34 | is: name 35 | }); 36 | result.setAttribute('is', name); 37 | } else { 38 | result = document.createElement(name); 39 | } 40 | return result; 41 | } 42 | 43 | async render () { 44 | const {url, name } = this.attributes; 45 | let range; 46 | 47 | if (url) await import(url); 48 | 49 | const prePlacedComponent = this._.querySelector(name.nodeValue); 50 | if (!prePlacedComponent) { 51 | const mountpoint = this._.querySelector('showroom-mount-point') || this.fallbackContainer; 52 | if (mountpoint !== this.fallbackContainer) { 53 | this.fallbackContainer.remove(); 54 | } 55 | range = document.createRange(); 56 | range.selectNode(mountpoint); 57 | range.deleteContents(); 58 | this.component = this.createElement(name.nodeValue, this.extending); 59 | } else { 60 | this.component = prePlacedComponent; 61 | } 62 | Object.keys(this.targetAttributes).forEach(attr => { 63 | const value = this.targetAttributes[attr]; 64 | if (typeof value === 'boolean' && value) { 65 | this.component.setAttribute(attr, ''); 66 | } else if (typeof value !== 'boolean') { 67 | this.component.setAttribute(attr, this.targetAttributes[attr]); 68 | } 69 | }); 70 | // mountpoint.outerHTML = `<${name.nodeValue} ${attributesString}>`; 71 | if (!prePlacedComponent) { 72 | range.insertNode(this.component); 73 | } 74 | window.showroomGlobalStyles.forEach(styleNode => { 75 | this._.appendChild(styleNode.cloneNode(true)); 76 | }); 77 | } 78 | 79 | setComponentAttribute(attr, value) { 80 | const component = this.firstElementChild; 81 | if (component) { 82 | component.setAttribute(attr, value); 83 | } 84 | } 85 | 86 | setComponentProperty(prop, value) { 87 | const component = this._.firstElementChild; 88 | if (component) { 89 | component[prop] = value; 90 | } 91 | } 92 | 93 | } 94 | 95 | customElements.define('component-renderer', ComponentRenderer); -------------------------------------------------------------------------------- /app/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ShowRoom 17 | 18 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 | 72 | 73 | -------------------------------------------------------------------------------- /app/client/assets/main.css: -------------------------------------------------------------------------------- 1 | @import url("/assets/fonts.css"); 2 | @import url("/assets/topcoat-desktop-light.css"); 3 | 4 | :root { 5 | --accent-color: #3e86c5; 6 | --accent-color-transparent: #e386c500; 7 | --dark-text: #444; 8 | --darker-text: #222; 9 | --light-text: #777; 10 | --lighter-text: #eee; 11 | --default-bg: #dfe2e2; 12 | --double-border: 4px double var(--light-text); 13 | --awesome-border: linear-gradient(to right, var(--accent-color-transparent) 5%, var(--accent-color) 10%, var(--accent-color-transparent) 90%); 14 | --awesome-border-heading: linear-gradient(to right, var(--accent-color-transparent) 3%, var(--accent-color) 10%, var(--accent-color-transparent) 65%) 15 | } 16 | 17 | :host { 18 | all: initlal; 19 | font-family: Source Sans, sans-serif; 20 | font-weight: normal; 21 | font-size: 12pt; 22 | } 23 | 24 | .grow { 25 | flex-grow: 1; 26 | } 27 | 28 | .hbox { 29 | display: flex; 30 | flex-direction: row; 31 | } 32 | 33 | .vbox { 34 | display: flex; 35 | flex-direction: column; 36 | } 37 | 38 | *:focus, .topcoat-button:focus, .topcoat-button--large:focus { 39 | outline: none !important; 40 | box-shadow: unset; 41 | border: 1px solid #9daca9; 42 | } 43 | 44 | details { 45 | font-weight: bold; 46 | } 47 | 48 | summary::-webkit-details-marker { 49 | display: none; 50 | } 51 | 52 | summary:before { 53 | content: '>'; 54 | display: inline-block; 55 | width: 1rem; 56 | text-align: center; 57 | transform: rotateZ(0deg) scaleY(1.2); 58 | transform-origin: 50% 50%; 59 | color: var(--light-text); 60 | transition: 0.25s ease-in-out transform; 61 | font-weight: normal; 62 | } 63 | 64 | details[open] > summary:before { 65 | transform: rotateZ(90deg) scaleY(1.2); 66 | transform-origin: 50% 50%; 67 | } 68 | 69 | details ul { 70 | list-style-type: none; 71 | margin: 0; 72 | padding: 0; 73 | padding-left: 1rem; 74 | color: var(--dark-text); 75 | font-weight: normal; 76 | } 77 | 78 | details ul li { 79 | display: flex; 80 | justify-content: space-between; 81 | align-items: center; 82 | height: 2rem; 83 | } 84 | 85 | :host details[open] > ul > li > button { 86 | margin-left: 1rem; 87 | padding: 0px 10px 0px 10px; 88 | font-size: 0.7rem; 89 | line-height: 1rem; 90 | font-weight: normal; 91 | position: relative; 92 | top: 2px; 93 | } 94 | 95 | h1, h2, h3, h4, h5, h6 { 96 | font-weight: normal; 97 | letter-spacing: 0.05rem; 98 | text-shadow: 0px 1px 1px rgba(0,0,0,0.2); 99 | } 100 | 101 | @keyframes SHOW { 102 | from { 103 | opacity: 0; 104 | transform: translate3d(0, -30px, 0); 105 | } 106 | to { 107 | opacity: 1; 108 | transform: translate3d(0, 0, 0); 109 | } 110 | } 111 | 112 | dialog { 113 | box-shadow: 0px 0px 3px 2px rgba(0, 0, 0, 0.4); 114 | border: var(--double-border); 115 | background: rgba(255, 255, 255, 0.97); 116 | opacity: 0; 117 | position: relative; 118 | width: 70vw; 119 | height: 70vh; 120 | overflow: auto; 121 | } 122 | 123 | dialog[open] { 124 | opacity: 1; 125 | animation: SHOW 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); 126 | } -------------------------------------------------------------------------------- /test/api.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | /** 4 | * @typedef {import('../puppeteer').Showroom} Showroom 5 | */ 6 | 7 | describe('API', () => { 8 | 9 | /** 10 | * @type Showroom 11 | */ 12 | let showroom; 13 | 14 | beforeEach( () => { 15 | /** 16 | * @type Showroom 17 | */ 18 | showroom = showroomInstance; 19 | }); 20 | 21 | 22 | it('Should invoke methods', async () => { 23 | await showroom.test('money-input'); 24 | await showroom.trigger('clear'); 25 | const value = await showroom.getProperty('value'); 26 | assert(value === 'USD 0'); 27 | }); 28 | 29 | it('setProperty/getProperty', async () => { 30 | await showroom.test('demo-component-avengers'); 31 | await showroom.setProperty('title', 'Hello'); 32 | const actual = await showroom.getProperty('title'); 33 | assert.equal(actual, 'Hello'); 34 | }); 35 | 36 | it('setAttribute/getAttribute/hasAttribute', async () => { 37 | await showroom.test('money-input'); 38 | await showroom.setAttribute('abc', 'abc'); 39 | assert(await showroom.hasAttribute('abc') === true); 40 | assert(await showroom.getAttribute('abc') === 'abc'); 41 | }); 42 | 43 | it('find', async () => { 44 | await showroom.test('demo-component-avengers'); 45 | await showroom.setProperty('title', 'Hello'); 46 | const titleEl = await showroom.find('// h3'); 47 | const titleText = await showroom.getTextContent(titleEl); 48 | assert(titleText === 'Hello', `Expecting title text to be "Hello", instead got ${titleText}`); 49 | }); 50 | 51 | it('component selection', async function () { 52 | this.retries(3); 53 | await showroom.test('money-input'); 54 | await showroom.page.waitFor(350); 55 | const menuItem = await showroom.page.evaluateHandle(() => queryDeepSelector('showroom-app // #component-list showroom-component-list[section-name="Vanilla" // li[data-component-name="money-input"]')); 56 | await menuItem.click(); 57 | const selectedMenuItem = await showroom.page.evaluateHandle(() => queryDeepSelector('showroom-app // #component-list showroom-component-list[section-name="Vanilla" // li[is-selected="true"]')); 58 | const component = await showroom.getAttribute('data-component-name', selectedMenuItem); 59 | assert(component === 'money-input'); 60 | }); 61 | 62 | it('clearEventList/getEventList', async function () { 63 | this.retries(3); 64 | await showroom.test('demo-component-avengers'); 65 | await showroom.setProperty('data', {}); 66 | const eventsBeforeClear = await showroom.getEventList(); 67 | await showroom.clearEventList(); 68 | await showroom.page.waitFor(100); 69 | const eventsAfterClear = await showroom.getEventList(); 70 | assert(eventsBeforeClear.length === 1); 71 | assert(eventsAfterClear.length === 0); 72 | }); 73 | 74 | it('isVisible', async () => { 75 | const component = await showroom.test('money-input'); 76 | await showroom.utils.page.evaluate((target) => target.style.visibility = 'hidden', component); 77 | assert(await showroom.isVisible() === false); 78 | await showroom.utils.page.evaluate((target) => target.style.visibility = null, component); 79 | assert(await showroom.isVisible() === true); 80 | }); 81 | 82 | }); -------------------------------------------------------------------------------- /app/client/showroom-integration.js: -------------------------------------------------------------------------------- 1 | class Showroom { 2 | 3 | constructor () { 4 | window.showroomReady = window.showroomReady || new Promise((resolve) => { 5 | requestAnimationFrame( () => { 6 | window.showroomReady.resolve = resolve; 7 | }); 8 | }); 9 | this.inject(); 10 | this.ready = window.showroomReady; 11 | } 12 | 13 | find (path, component = this.component) { 14 | return window.queryDeepSelector(path, component); 15 | } 16 | 17 | inject () { 18 | if (!window.queryDeepSelector) { 19 | /** 20 | * @param {string} selectorStr 21 | * @param {any} container 22 | */ 23 | const queryDeepSelector = (selectorStr, container = document) => { 24 | const selectorArr = selectorStr.replace(new RegExp('//', 'g'), '%split%//%split%').split('%split%'); 25 | for (const index in selectorArr) { 26 | const selector = selectorArr[index].trim(); 27 | 28 | if (!selector) continue; 29 | 30 | if (selector === '//') { 31 | container = container.shadowRoot; 32 | } else if (selector === 'document') { 33 | container = document; 34 | } else { 35 | container = container.querySelector(selector); 36 | } 37 | if (!container) break; 38 | } 39 | return container; 40 | }; 41 | window['queryDeepSelector'] = queryDeepSelector; 42 | }; 43 | } 44 | 45 | async setTestSubject(component) { 46 | await window.showroomReady; 47 | await showroomApp.findAndLoadComponent(component); 48 | } 49 | 50 | setProperty(prop, value) { 51 | dashboard.targetComponent[prop] = value; 52 | window.dispatchEvent(new CustomEvent('property-changed', { 53 | detail: { 54 | prop, value 55 | } 56 | })) 57 | } 58 | 59 | getProperty(prop) { 60 | return dashboard.targetComponent[prop]; 61 | } 62 | 63 | setAttribute(attr, value) { 64 | dashboard.targetComponent.setAttribute(attr, value); 65 | window.dispatchEvent(new CustomEvent('attribute-changed', { 66 | detail: { 67 | attr, value 68 | } 69 | })) 70 | } 71 | 72 | toggleAttribute(attr) { 73 | dashboard.targetComponent.toggleAttribute(attr); 74 | window.dispatchEvent(new CustomEvent('attribute-toogled'),{ 75 | detail: { 76 | attr, 77 | value: dashboard.targetComponent.hasAttribute(attr) 78 | } 79 | }); 80 | } 81 | 82 | getAttribute(attr) { 83 | return dashboard.targetComponent.getAttribute(attr); 84 | } 85 | 86 | hasAttribute(attr) { 87 | return dashboard.targetComponent.hasAttribute(attr); 88 | } 89 | 90 | trigger(fnName) { 91 | this.triggers[fnName](); 92 | window.dispatchEvent(new CustomEvent('trigger', { 93 | detail: { 94 | fnName, 95 | trigger: this.triggers[fnName] 96 | } 97 | })); 98 | } 99 | 100 | clearEventList() { 101 | dashboard.clearEvents(); 102 | window.dispatchEvent(new CustomEvent('events-cleared')); 103 | } 104 | 105 | on (eventName, callback) { 106 | window.addEventListener(eventName, callback); 107 | return () => window.removeEventListener(eventName, callback); 108 | } 109 | 110 | 111 | } 112 | 113 | window.showroom = new Showroom(); -------------------------------------------------------------------------------- /app/client/showroom-json-editor.js: -------------------------------------------------------------------------------- 1 | customElements.define('showroom-json-editor', class extends HTMLElement { 2 | 3 | constructor () { 4 | super(); 5 | this.root = this.attachShadow({mode: 'open'}); 6 | this.root.innerHTML = /*html*/` 7 | 8 | 58 | 59 |
60 |
61 |
62 |
63 | 64 | 65 |
66 |
67 | `; 68 | this.dialog = this.root.querySelector('dialog'); 69 | JSONEditor.ace = window.ace; 70 | this.editor = new JSONEditor(this.root.querySelector('#editor'), {modes: ['tree', 'text']}); 71 | this.btnSubmit = this.root.querySelector('#submit'); 72 | this.btnCancel = this.root.querySelector('#cancel'); 73 | this.btnCancel.onclick = () => this.close(); 74 | this.btnSubmit.onclick = () => this.submitData(); 75 | this.btnSubmit.classList.add('topcoat-button--cta'); 76 | this.btnCancel.classList.add('topcoat-button--large--quiet'); 77 | const controls = this.root.querySelector('#controls'); 78 | controls.attachShadow({mode: 'open'}); 79 | controls.shadowRoot.innerHTML = ''; 80 | controls.shadowRoot.appendChild(this.root.querySelector('.spacer')); 81 | controls.shadowRoot.appendChild(this.btnSubmit); 82 | controls.shadowRoot.appendChild(this.btnCancel); 83 | document.addEventListener('open-json-editor', (e) => { 84 | const { data, callback } = e.detail; 85 | this.callback = callback; 86 | this.open(data); 87 | }); 88 | } 89 | 90 | submitData () { 91 | const json = this.editor.get(); 92 | const { callback } = this; 93 | if (callback) { 94 | callback(json); 95 | } 96 | this.close(); 97 | } 98 | 99 | open (json) { 100 | this.dialog.showModal(); 101 | this.editor.set(json); 102 | } 103 | 104 | close () { 105 | this.dialog.close(); 106 | } 107 | 108 | }); -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at eavichay@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /example/demo-user-card.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html} from 'https://unpkg.com/@polymer/lit-element@0.6.2/lit-element.js?module'; 2 | 3 | 4 | 5 | customElements.define('demo-user-card', class extends LitElement { 6 | 7 | static get observedAttributes () { 8 | return ['user-id', 'accent-color']; 9 | } 10 | 11 | constructor () { 12 | super(); 13 | this.accentColor = '#FFFFDD'; 14 | } 15 | 16 | render () { 17 | 18 | return html` 19 | 74 | 75 | ${this.user ? html` 76 |
77 |
78 |

${this.user.name.last}, ${this.user.name.first}(${this.user.login.username})

79 |
80 | ${this.getUserAddress()} 81 |
82 |
83 | ${this.user.email} 84 |
85 |
86 | ${this.user.cell} 87 |
88 |
` : ''}`; 89 | } 90 | 91 | attributeChangedCallback (attr, old, value) { 92 | if (value !== old) { 93 | switch (attr) { 94 | case 'user-id': 95 | this.loadUserById(value); 96 | break; 97 | case 'accent-color': 98 | this.accentColor = value; 99 | this.requestUpdate(); 100 | } 101 | } 102 | } 103 | 104 | async loadUserById (userId) { 105 | const url = `https://randomuser.me/api?seed=${userId}`; 106 | const { results } = await (await fetch(url)).json(); 107 | const user = results[0]; 108 | this.dispatchEvent(new CustomEvent('data-loaded', { 109 | detail: { 110 | user, 111 | userId 112 | } 113 | })); 114 | this.user = user; 115 | this.requestUpdate(); 116 | } 117 | 118 | getUserImage () { 119 | const { user } = this; 120 | if (user) { 121 | return this.user.picture.large; 122 | } 123 | } 124 | 125 | getUserAddress () { 126 | const { user } = this; 127 | if (user) { 128 | const { street, city, state, postcode } = user.location; 129 | return [ street, city, state, postcode ].join(', '); 130 | } 131 | return ''; 132 | } 133 | 134 | }); -------------------------------------------------------------------------------- /example/demo-component-avengers.js: -------------------------------------------------------------------------------- 1 | import { Slim } from '/.showroom-app/Slim.js'; 2 | import '/.showroom-app/directives/repeat.js'; 3 | 4 | const template = /*html*/` 5 | 73 | 74 |
75 |

{{title}}

76 |
Current members:
77 |
    78 |
  • {{member}}
  • 79 |
80 |
81 |
82 |
Next mission:
{{mission}}
83 |
84 |
Data 85 |
86 | Rank: {{data.rank}}
87 | Last Mission: {{data.lastMission}}
88 | 89 |
90 | ` 91 | customElements.define('demo-component-avengers', class extends Slim { 92 | 93 | get useShadow () { return true; } 94 | 95 | get template () { return template; } 96 | 97 | onBeforeCreated () { 98 | this.data = { 99 | rank: 95, 100 | lastMission: 'Save captain Fury' 101 | }; 102 | this.mission = 'Protect New York'; 103 | } 104 | 105 | onCreated () { 106 | Slim.bind(this, {}, 'data', () => { 107 | this.dispatchEvent(new CustomEvent('datachanged', { 108 | detail: this.data 109 | })); 110 | }); 111 | 112 | Slim.bind(this, {}, 'mission', () => { 113 | this.dispatchEvent(new CustomEvent('taskselected', { 114 | detail: { 115 | mission: this.mission, 116 | memeber: this.memeber 117 | } 118 | })); 119 | }); 120 | } 121 | 122 | startMission () { 123 | this.dispatchEvent(new CustomEvent('missionstarted', { 124 | detail: { 125 | mission: this.mission, 126 | memebers: this.memeber 127 | } 128 | })); 129 | } 130 | 131 | get autoBoundAttributes () { 132 | return this.constructor.observedAttributes; 133 | } 134 | 135 | attributeChangedCallback (attr, oldVal, newVal) { 136 | this[Slim.dashToCamel(attr)] = newVal; 137 | } 138 | 139 | static get observedAttributes () { 140 | return ['accent-color', 'text-color']; 141 | } 142 | 143 | }); -------------------------------------------------------------------------------- /puppeteer/index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const testUtils = require('../test-utils'); 4 | const showroomServer = require('../app/server'); 5 | const puppeteer = require('puppeteer'); 6 | const { assign } = Object; 7 | 8 | /** 9 | * @exports Showroom 10 | */ 11 | 12 | /** 13 | * @typedef ShowroomInitOptions 14 | * @property {boolean} [silent = true] 15 | * @property {boolean} [headless = true] 16 | * @property {string} [path = process.cwd()] Application's root path, defaults to process cwd 17 | */ 18 | 19 | /** 20 | * @exports {import('puppeteer').JSHandle} JSHandle 21 | * @exports {import('puppeteer').ElementHandle} ElementHandle 22 | * @exports {import('puppeteer').Page} Page 23 | */ 24 | 25 | 26 | /* @type ShowroomInitOptions */ 27 | const defaultOptions = { 28 | port: 3001, 29 | silent: true, 30 | headless: true, 31 | path: process.cwd() 32 | } 33 | 34 | const { TestUtils } = testUtils; 35 | 36 | /** 37 | * @class Showroom 38 | * @extends TestUtils 39 | * @method {function(path:string, container?:JSHandle):JSHandle} find 40 | */ 41 | class Showroom { 42 | 43 | /** 44 | * @param {ShowroomInitOptions} options 45 | */ 46 | // @ts-ignore 47 | constructor (options) { 48 | // @ts-ignore 49 | this.options = assign({}, defaultOptions, options); 50 | // @ts-ignore 51 | this.page = null; 52 | // @ts-ignore 53 | this.browser = null; 54 | } 55 | 56 | /** 57 | * Starts the showroom server and launches puppeteer browser 58 | * @returns Showroom 59 | */ 60 | async start () { 61 | const { port, path, silent, headless} = this.options; 62 | const baseUrl = `http://127.0.0.1:${port}`; 63 | showroomServer.bootstrap({ 64 | port, 65 | path, 66 | silent 67 | }); 68 | this.browser = await puppeteer.launch({ 69 | headless 70 | }); 71 | this.page = await this.browser.newPage(); 72 | 73 | /** 74 | * @type number|string 75 | */ 76 | let status = 0; 77 | let retries = 10; 78 | while (!status) { 79 | if (retries < 0) { 80 | throw new Error('Error connecting to showroom server'); 81 | } 82 | try { 83 | await this.page.goto(baseUrl, { 84 | waitUntil: 'networkidle0' 85 | }); 86 | status = 'OK'; 87 | } 88 | catch (err) { 89 | await this.page.waitFor(150); 90 | retries--; 91 | } 92 | } 93 | this.utils = await testUtils(this.page); 94 | 95 | // API borrowing 96 | this.getAttribute = this.utils.getAttribute.bind(this.utils); 97 | this.setTestSubject = this.utils.setTestSubject.bind(this.utils); 98 | this.test = this.utils.setTestSubject.bind(this.utils); // alias 99 | this.setAttribute = this.utils.setAttribute.bind(this.utils); 100 | this.hasAttribute = this.utils.hasAttribute.bind(this.utils); 101 | this.find = this.utils.find.bind(this.utils); 102 | this.clearEventList = this.utils.clearEventList.bind(this.utils); 103 | this.getEventList = this.utils.getEventList.bind(this.utils); 104 | this.getTextContent = this.utils.getTextContent.bind(this.utils); 105 | this.isVisible = this.utils.isVisible.bind(this.utils); 106 | this.getProperty = this.utils.getProperty.bind(this.utils); 107 | this.setProperty = this.utils.setProperty.bind(this.utils); 108 | this.trigger = this.utils.trigger.bind(this.utils); 109 | this.validateHTML = this.utils.validateHTML.bind(this.utils); 110 | 111 | return this; 112 | } 113 | 114 | /** 115 | * Stops the showroom server and closes the puppeteer browser. 116 | */ 117 | async stop () { 118 | await this.browser.close(); 119 | await showroomServer.server().close(); 120 | } 121 | } 122 | 123 | /** 124 | * @returns Showroom 125 | */ 126 | module.exports = options => new Showroom(options); 127 | module.exports.Showroom = Showroom; -------------------------------------------------------------------------------- /app/server/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const path = require('path'); 4 | const Koa = require('koa'); 5 | const mount = require('koa-mount'); 6 | const serve = require('koa-static'); 7 | const yargs = require('yargs'); 8 | const { search, getComponents } = require('./build-component-index'); 9 | const fs = require('fs'); 10 | const { promisify } = require('util'); 11 | const enforceHttps = require('koa-sslify'); 12 | const logger = require('./logger'); 13 | 14 | const lstat = promisify(fs.lstat); 15 | 16 | const backend = new Koa(); 17 | const frontend = new Koa(); 18 | const app = new Koa(); 19 | let koaServer; 20 | 21 | const serveStaticOptions = { 22 | hidden: true, 23 | cacheControl: false 24 | }; 25 | 26 | if (require.main === module) { 27 | yargs 28 | .usage('$0 [--path] [--port]', '', (yargs) => {}, (argv) => { 29 | global.showroom = { 30 | verbose: argv.verbose, 31 | silent: argv.silent, 32 | port: argv.port, 33 | path: argv.path 34 | }; 35 | bootstrap({ 36 | port: argv.port, 37 | path: argv.path 38 | }); 39 | }) 40 | .default('verbose', false) 41 | .default('silent', false) 42 | .default('port', '3000') 43 | .default('path', './') 44 | .help() 45 | .argv; 46 | } 47 | 48 | async function bootstrap ({port = 3000, path = './', silent = false}) { 49 | global.showroom = Object.assign(global.showroom || {}, 50 | { 51 | path, 52 | port, 53 | silent 54 | }); 55 | await preflight(); 56 | logger.info('Starting server'); 57 | await startServer(); 58 | koaServer = app.listen(process.env.PORT || port); 59 | } 60 | 61 | async function preflight () { 62 | const parentDir = path.resolve(process.cwd(), global.showroom.path); 63 | const dir = path.resolve(process.cwd(), global.showroom.path, '.showroom'); 64 | if (fs.existsSync(dir) && (await lstat(dir)).isDirectory()) { 65 | logger.info(`.showroom folder located`); 66 | } else { 67 | logger.error(`Could not locate .showroom folder in ${parentDir}`); 68 | } 69 | } 70 | 71 | async function startServer () { 72 | const dir = (module, ...rest) => path.join(path.dirname(require.resolve(module)), ...rest); 73 | const allowedPaths = [ 74 | path.join(__dirname, '/../client'), 75 | process.cwd(), 76 | dir('jsoneditor', 'dist'), 77 | dir('marked', '..'), 78 | dir('milligram'), 79 | dir('slim-js'), 80 | global.showroom.path, 81 | ]; 82 | 83 | logger.info('Expecting Showroom files to be at', global.showroom.path + '/.showroom') 84 | await search(path.resolve(process.cwd(), global.showroom.path, '.showroom')); 85 | 86 | if (process.env.FORCE_SSL) { 87 | logger.info('Enforcing SSL'); 88 | app.use(enforceHttps({ 89 | trustProtoHeader: true 90 | })); 91 | } 92 | 93 | allowedPaths.forEach(path => { 94 | frontend.use(serve(path, serveStaticOptions)); 95 | }); 96 | 97 | backend.use(async (ctx, next) => { 98 | if (ctx.path === '/.showroom-app/showroom-config') { 99 | const cfgPath = path.resolve(process.cwd(), global.showroom.path, '.showroom', 'config.js'); 100 | ctx.set('Content-Type', 'application/javascript; charset=utf-8'); 101 | if (fs.existsSync(cfgPath)) { 102 | try { 103 | const config = fs.readFileSync(cfgPath); 104 | ctx.body = config.toString('utf-8'); 105 | } 106 | catch (err) { 107 | ctx.body = 'export default {}'; 108 | await next(); 109 | } 110 | } else { 111 | ctx.body = 'export default {}'; 112 | await next(); 113 | } 114 | } else { 115 | await next(); 116 | } 117 | }); 118 | 119 | backend.use(async (ctx, next) => { 120 | if (ctx.path === '/index.html' || ctx.path === '/') { 121 | ctx.redirect('/.showroom-app/index.html'); 122 | } else { 123 | next(); 124 | } 125 | }); 126 | 127 | backend.use(async (ctx, next) => { 128 | if (ctx.path === '/.showroom-app/showroom-components') { 129 | ctx.body = getComponents(); 130 | } else { 131 | await next(); 132 | } 133 | }); 134 | 135 | // attempt serving static files by path priority 136 | app.use(mount('/.showroom-app', frontend)); 137 | app.use(mount('/', frontend)); 138 | 139 | // attempt serving backend files 140 | app.use(mount('/', backend)); 141 | 142 | return true; 143 | } 144 | 145 | module.exports = { 146 | bootstrap, 147 | server: () => koaServer 148 | }; 149 | -------------------------------------------------------------------------------- /app/client/showroom-app.js: -------------------------------------------------------------------------------- 1 | import { Slim } from '/.showroom-app/Slim.js'; 2 | 3 | import './component-description.js'; 4 | import './showroom-component-list.js'; 5 | import './component-dashboard.js'; 6 | import './router.js'; 7 | 8 | export const createError = (err, module, filename) => ` 9 | # _Oops_, Error. 10 | 11 | > The error can be either in the component descriptor file or the component runtime. 12 | > Perhaps the browser console can provide additional information. 13 | 14 | --- 15 | 16 | ### Type 17 | **${err.name}: ${err.message}** 18 | 19 | ${filename ? ` 20 | ### File 21 | ${filename} 22 | ` : ''} 23 | 24 | ### Stack 25 | ${err.stack} 26 | `; 27 | 28 | Slim.tag('showroom-app', class extends Slim { 29 | 30 | constructor () { 31 | super(); 32 | window['showroomApp'] = this; 33 | } 34 | 35 | loadComponentByHash () { 36 | this.findAndLoadComponent(window.location.hash.slice(1)); 37 | } 38 | 39 | get useShadow () { return true; } 40 | 41 | get template () { 42 | return /*html*/` 43 | 82 | 83 |

SHOWROOM

84 |
85 |
86 |

Components

87 | 92 |
93 | 94 |
95 | `; 96 | } 97 | 98 | async findAndLoadComponent (name) { 99 | if (!name) { 100 | return; 101 | } 102 | this.sections && this.sections.forEach(section => { 103 | section.forEach((module) => { 104 | if (module.component === name) { 105 | this.dashboard.setupComponent(module); 106 | this.component = dashboard.targetComponent; 107 | showroom.component = this.component; 108 | } 109 | }); 110 | }); 111 | // if (!found && window.location.hash) { 112 | // // window.location.hash = ''; 113 | // this.descriptionView.setContent('# Oops.\nInvaliad URL. This route does not resolve to a registered component. \n\nTry selecting a component from the list instead.'); 114 | // } 115 | } 116 | 117 | onComponentDocs (description) { 118 | this.descriptionView.setContent(description); 119 | } 120 | 121 | async loadComponents () { 122 | let module; 123 | try { 124 | const sections = {}; 125 | const components = await (await fetch('/.showroom-app/showroom-components')).json(); 126 | for (let filename of components) { 127 | try { 128 | this.descriptionView.setContent('# Loading...\n' + filename); 129 | module = (await import('/.showroom/' + filename)).default; 130 | const { path, section } = module; 131 | 132 | if (path) { 133 | await import(path); 134 | } 135 | 136 | const targetSection = section || 'general'; 137 | 138 | sections[targetSection] = sections[targetSection] || []; 139 | sections[targetSection].push(module); 140 | sections[targetSection].name = targetSection; 141 | } 142 | catch (err) { 143 | console.error('Could not load module ' + filename); 144 | console.warn('Got error: ', err); 145 | this.descriptionView.setContent(createError(err, module, filename)); 146 | return; 147 | } 148 | } 149 | 150 | this.sections = Object.keys(sections).map(section => { 151 | return sections[section]; 152 | }); 153 | 154 | this.descriptionView.close(); 155 | const { router } = window; 156 | router.addEventListener('change', ({detail}) => { 157 | this.findAndLoadComponent(detail); 158 | }); 159 | // this.loadComponentByHash(); 160 | } catch (err) { 161 | this.descriptionView.setContent(createError(err)); 162 | } finally { 163 | this.loadComponentByHash(); 164 | } 165 | } 166 | 167 | }); -------------------------------------------------------------------------------- /app/client/assets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Sections 4 | ========================================================================== */ 5 | 6 | /** 7 | * Correct the font size and margin on `h1` elements within `section` and 8 | * `article` contexts in Chrome, Firefox, and Safari. 9 | */ 10 | 11 | 12 | 13 | h1 { 14 | font-size: 2em; 15 | margin: 0.67em 0; 16 | } 17 | 18 | /* Grouping content 19 | ========================================================================== */ 20 | 21 | /** 22 | * 1. Add the correct box sizing in Firefox. 23 | * 2. Show the overflow in Edge and IE. 24 | */ 25 | 26 | hr { 27 | box-sizing: content-box; /* 1 */ 28 | height: 0; /* 1 */ 29 | overflow: visible; /* 2 */ 30 | } 31 | 32 | /** 33 | * 1. Correct the inheritance and scaling of font size in all browsers. 34 | * 2. Correct the odd `em` font sizing in all browsers. 35 | */ 36 | 37 | pre { 38 | font-family: monospace, monospace; /* 1 */ 39 | font-size: 1em; /* 2 */ 40 | } 41 | 42 | /* Text-level semantics 43 | ========================================================================== */ 44 | 45 | /** 46 | * Remove the gray background on active links in IE 10. 47 | */ 48 | 49 | a { 50 | background-color: transparent; 51 | } 52 | 53 | /** 54 | * 1. Remove the bottom border in Chrome 57- 55 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 56 | */ 57 | 58 | abbr[title] { 59 | border-bottom: none; /* 1 */ 60 | text-decoration: underline; /* 2 */ 61 | text-decoration: underline dotted; /* 2 */ 62 | } 63 | 64 | /** 65 | * Add the correct font weight in Chrome, Edge, and Safari. 66 | */ 67 | 68 | b, 69 | strong { 70 | font-weight: bolder; 71 | } 72 | 73 | /** 74 | * 1. Correct the inheritance and scaling of font size in all browsers. 75 | * 2. Correct the odd `em` font sizing in all browsers. 76 | */ 77 | 78 | code, 79 | kbd, 80 | samp { 81 | font-family: monospace, monospace; /* 1 */ 82 | font-size: 1em; /* 2 */ 83 | } 84 | 85 | /** 86 | * Add the correct font size in all browsers. 87 | */ 88 | 89 | small { 90 | font-size: 80%; 91 | } 92 | 93 | /** 94 | * Prevent `sub` and `sup` elements from affecting the line height in 95 | * all browsers. 96 | */ 97 | 98 | sub, 99 | sup { 100 | font-size: 75%; 101 | line-height: 0; 102 | position: relative; 103 | vertical-align: baseline; 104 | } 105 | 106 | sub { 107 | bottom: -0.25em; 108 | } 109 | 110 | sup { 111 | top: -0.5em; 112 | } 113 | 114 | /* Embedded content 115 | ========================================================================== */ 116 | 117 | /** 118 | * Remove the border on images inside links in IE 10. 119 | */ 120 | 121 | img { 122 | border-style: none; 123 | } 124 | 125 | /* Forms 126 | ========================================================================== */ 127 | 128 | /** 129 | * 1. Change the font styles in all browsers. 130 | * 2. Remove the margin in Firefox and Safari. 131 | */ 132 | 133 | :host { 134 | all: initial; 135 | } 136 | 137 | button, 138 | input, 139 | optgroup, 140 | select, 141 | textarea { 142 | font-family: inherit; /* 1 */ 143 | font-size: 100%; /* 1 */ 144 | line-height: 1.15; /* 1 */ 145 | margin: 0; /* 2 */ 146 | } 147 | 148 | /** 149 | * Show the overflow in IE. 150 | * 1. Show the overflow in Edge. 151 | */ 152 | 153 | button, 154 | input { /* 1 */ 155 | overflow: visible; 156 | } 157 | 158 | /** 159 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 160 | * 1. Remove the inheritance of text transform in Firefox. 161 | */ 162 | 163 | button, 164 | select { /* 1 */ 165 | text-transform: none; 166 | } 167 | 168 | /** 169 | * Correct the inability to style clickable types in iOS and Safari. 170 | */ 171 | 172 | button, 173 | [type="button"], 174 | [type="reset"], 175 | [type="submit"] { 176 | -webkit-appearance: button; 177 | } 178 | 179 | /** 180 | * Remove the inner border and padding in Firefox. 181 | */ 182 | 183 | button::-moz-focus-inner, 184 | [type="button"]::-moz-focus-inner, 185 | [type="reset"]::-moz-focus-inner, 186 | [type="submit"]::-moz-focus-inner { 187 | border-style: none; 188 | padding: 0; 189 | } 190 | 191 | /** 192 | * Restore the focus styles unset by the previous rule. 193 | */ 194 | 195 | button:-moz-focusring, 196 | [type="button"]:-moz-focusring, 197 | [type="reset"]:-moz-focusring, 198 | [type="submit"]:-moz-focusring { 199 | outline: 1px dotted ButtonText; 200 | } 201 | 202 | /** 203 | * Correct the padding in Firefox. 204 | */ 205 | 206 | fieldset { 207 | padding: 0.35em 0.75em 0.625em; 208 | } 209 | 210 | /** 211 | * 1. Correct the text wrapping in Edge and IE. 212 | * 2. Correct the color inheritance from `fieldset` elements in IE. 213 | * 3. Remove the padding so developers are not caught out when they zero out 214 | * `fieldset` elements in all browsers. 215 | */ 216 | 217 | legend { 218 | box-sizing: border-box; /* 1 */ 219 | color: inherit; /* 2 */ 220 | display: table; /* 1 */ 221 | max-width: 100%; /* 1 */ 222 | padding: 0; /* 3 */ 223 | white-space: normal; /* 1 */ 224 | } 225 | 226 | /** 227 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 228 | */ 229 | 230 | progress { 231 | vertical-align: baseline; 232 | } 233 | 234 | /** 235 | * Remove the default vertical scrollbar in IE 10+. 236 | */ 237 | 238 | textarea { 239 | overflow: auto; 240 | } 241 | 242 | /** 243 | * 1. Add the correct box sizing in IE 10. 244 | * 2. Remove the padding in IE 10. 245 | */ 246 | 247 | [type="checkbox"], 248 | [type="radio"] { 249 | box-sizing: border-box; /* 1 */ 250 | padding: 0; /* 2 */ 251 | } 252 | 253 | /** 254 | * Correct the cursor style of increment and decrement buttons in Chrome. 255 | */ 256 | 257 | [type="number"]::-webkit-inner-spin-button, 258 | [type="number"]::-webkit-outer-spin-button { 259 | height: auto; 260 | } 261 | 262 | /** 263 | * 1. Correct the odd appearance in Chrome and Safari. 264 | * 2. Correct the outline style in Safari. 265 | */ 266 | 267 | [type="search"] { 268 | -webkit-appearance: textfield; /* 1 */ 269 | outline-offset: -2px; /* 2 */ 270 | } 271 | 272 | /** 273 | * Remove the inner padding in Chrome and Safari on macOS. 274 | */ 275 | 276 | [type="search"]::-webkit-search-decoration { 277 | -webkit-appearance: none; 278 | } 279 | 280 | /** 281 | * 1. Correct the inability to style clickable types in iOS and Safari. 282 | * 2. Change font properties to `inherit` in Safari. 283 | */ 284 | 285 | ::-webkit-file-upload-button { 286 | -webkit-appearance: button; /* 1 */ 287 | font: inherit; /* 2 */ 288 | } 289 | 290 | /* Interactive 291 | ========================================================================== */ 292 | 293 | /* 294 | * Add the correct display in Edge, IE 10+, and Firefox. 295 | */ 296 | 297 | details { 298 | display: block; 299 | } 300 | 301 | /* 302 | * Add the correct display in all browsers. 303 | */ 304 | 305 | summary { 306 | display: list-item; 307 | } 308 | 309 | /* Misc 310 | ========================================================================== */ 311 | 312 | /** 313 | * Add the correct display in IE 10+. 314 | */ 315 | 316 | template { 317 | display: none; 318 | } 319 | 320 | /** 321 | * Add the correct display in IE 10. 322 | */ 323 | 324 | [hidden] { 325 | display: none; 326 | } 327 | -------------------------------------------------------------------------------- /app/client/custom-control-form.js: -------------------------------------------------------------------------------- 1 | export class CustomControlForm extends HTMLElement { 2 | 3 | constructor (targetComponent, formData) { 4 | super(); 5 | this.targetComponent = targetComponent; 6 | this.formData = formData || {}; 7 | this._ = this.attachShadow({mode: 'open'}); 8 | this.triggers = {}; 9 | this.watchers = []; 10 | this._.innerHTML = /*html*/` 11 | 40 | `; 41 | requestAnimationFrame(this.buildForm.bind(this)); 42 | } 43 | 44 | buildForm () { 45 | this.watchers.forEach( watcher => watcher() ); 46 | this.watchers = []; 47 | const { targetComponent, formData } = this; 48 | const { properties, attributes, functions } = formData; 49 | if (properties) { 50 | const h = document.createElement('h3'); 51 | h.innerText = 'Properties'; 52 | this._.appendChild(h); 53 | Object.keys(properties).forEach(prop => { 54 | const type = properties[prop]; 55 | const wrapper = document.createElement('div'); 56 | wrapper.classList.add('input-control'); 57 | let input = document.createElement('input'); 58 | switch (true) { 59 | case typeof type === 'number': 60 | input.setAttribute('type', 'number'); 61 | input.setAttribute('value', type); 62 | break; 63 | case typeof type === 'string': 64 | input.setAttribute('value', type); 65 | input.setAttribute('type', 'text'); 66 | break; 67 | case typeof type === 'boolean': 68 | input = document.createElement('input'); 69 | input.setAttribute('type', 'button'); 70 | input.classList.add('topcoat-button--large'); 71 | input.value = properties[prop]; 72 | input.onclick = () => { 73 | showroom.setProperty(prop, !showroom.getProperty(prop)); 74 | }; 75 | break; 76 | case type instanceof Set: 77 | input = document.createElement('select'); 78 | Array.from(type).forEach((item) => { 79 | const option = document.createElement('option'); 80 | option.label = item.label || item; 81 | option.value = item.value || item; 82 | input.appendChild(option); 83 | }); 84 | 85 | type.value = type.values().next().value; 86 | break; 87 | default: 88 | input = document.createElement('input'); 89 | input.setAttribute('type', 'button'); 90 | input.setAttribute('data-type', 'object'); 91 | input.classList.add('topcoat-button'); 92 | input.value = 'Edit ' + type.constructor.name; 93 | input.onclick = () => { 94 | this.dispatchEvent(new CustomEvent('open-json-editor', 95 | { 96 | bubbles: true, 97 | composed: true, 98 | detail: { 99 | data: targetComponent[prop], 100 | callback: (value) => { 101 | this.targetComponent[prop] = value; 102 | } 103 | } 104 | })); 105 | } 106 | } 107 | input.setAttribute('data-target-property', prop); 108 | const label = document.createElement('label'); 109 | if (input.getAttribute('type') !== 'button') { 110 | input.classList.add('topcoat-text-input'); 111 | } 112 | label.innerText = prop; 113 | wrapper.appendChild(label); 114 | wrapper.appendChild(input); 115 | this._.appendChild(wrapper); 116 | this.targetComponent[prop] = type.value || type; 117 | if (input.getAttribute('data-type') !== 'object') { 118 | this.watchers.push(showroom.on('property-changed', ({detail}) => { 119 | if (detail.prop === prop) { 120 | input.value = this.targetComponent[prop].toString(); 121 | } 122 | })); 123 | } 124 | input.addEventListener('change', (evt) => { 125 | const type = input.getAttribute('type'); 126 | if (type === 'number') { 127 | showroom.setProperty(prop, parseFloat(input.value)); 128 | } else { 129 | showroom.setProperty(prop, input.value); 130 | } 131 | }) 132 | }) 133 | } 134 | if (attributes) { 135 | const h = document.createElement('h3'); 136 | h.innerText = 'Attributes'; 137 | this._.appendChild(h); 138 | Object.keys(attributes).forEach((attr) => { 139 | const value = attributes[attr]; 140 | const input = document.createElement('input'); 141 | input.type = 'text'; 142 | input.setAttribute('data-target-attribute', attr); 143 | input.classList.add('topcoat-text-input'); 144 | input.value = value; 145 | const label = document.createElement('label'); 146 | label.innerText = attr; 147 | const wrapper = document.createElement('div'); 148 | wrapper.classList.add('input-control'); 149 | wrapper.appendChild(label); 150 | wrapper.appendChild(input); 151 | this._.appendChild(wrapper); 152 | if (value) { 153 | targetComponent.setAttribute(attr, value); 154 | } 155 | input.addEventListener('change', () => { 156 | if (input.value) { 157 | targetComponent.setAttribute(attr, input.value); 158 | } else { 159 | targetComponent.removeAttribute(attr); 160 | } 161 | }); 162 | this.watchers.push(showroom.on('attribute-changed', ({detail}) => { 163 | if (detail.attr === attr) { 164 | input.value = detail.value.toString(); 165 | } 166 | })); 167 | }); 168 | } 169 | if (functions) { 170 | const h = document.createElement('h3'); 171 | h.innerText = 'Functions'; 172 | this._.appendChild(h); 173 | Object.keys(functions).forEach(fnName => { 174 | const wrapper = document.createElement('div'); 175 | wrapper.classList.add('input-control'); 176 | const btn = document.createElement('button'); 177 | btn.classList.add('topcoat-button--large'); 178 | const label = document.createElement('label'); 179 | label.innerText = fnName; 180 | btn.innerText = 'Invoke'; 181 | wrapper.appendChild(label); 182 | wrapper.appendChild(btn); 183 | this._.appendChild(wrapper); 184 | this.triggers[fnName] = () => { 185 | functions[fnName](); 186 | }; 187 | window.showroom.triggers = this.triggers; 188 | btn.onclick = this.triggers[fnName]; 189 | }); 190 | } 191 | } 192 | 193 | } 194 | 195 | customElements.define('custom-control-form', CustomControlForm); -------------------------------------------------------------------------------- /test-utils.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const validator = require('html-validator'); 4 | 5 | /** 6 | * @typedef SerializedRemoteEvent 7 | * @property {string} type 8 | * @property {any} detail 9 | */ 10 | 11 | 12 | /** 13 | * @ignore @typedef {import('puppeteer').ElementHandle} ElementHandle 14 | */ 15 | 16 | /** 17 | * @ignore @typedef {import('puppeteer').JSHandle} JStHandle 18 | */ 19 | 20 | /** 21 | * @ignore @typedef {import('puppeteer').Page} Page 22 | */ 23 | 24 | 25 | /** 26 | * @private 27 | * @param {Page} page 28 | * @param {string} path 29 | * @param {ElementHandle} container 30 | */ 31 | const find = async (page, path, container) => { 32 | const result = await page.evaluateHandle((path, container) => { 33 | // @ts-ignore 34 | return window.queryDeepSelector(path, container); 35 | }, path, container); 36 | return result; 37 | }; 38 | 39 | /** 40 | * @class TestUtils 41 | * @property {ElementHandle} targetComponent 42 | * @property {Page} page 43 | */ 44 | class TestUtils { 45 | 46 | /** 47 | * @param {string} path deeq query selector formatted path 48 | * @param {ElementHandle} container 49 | */ 50 | async find (path, container = this.targetComponent) { 51 | return await find(this.page, path.trim(), container); 52 | } 53 | 54 | /** 55 | * Initializes the showroom test-utils with a puppeteer page instance 56 | * @param {Page} page 57 | */ 58 | async initialize (page) { 59 | this.page = page; 60 | await page.evaluate(() => { 61 | // @ts-ignore 62 | if (!window.queryDeepSelector) { 63 | /** 64 | * @param {string} selectorStr 65 | * @param {any} container 66 | */ 67 | const queryDeepSelector = (selectorStr, container = document) => { 68 | const selectorArr = selectorStr.replace(new RegExp('//', 'g'), '%split%//%split%').split('%split%'); 69 | for (const index in selectorArr) { 70 | const selector = selectorArr[index].trim(); 71 | 72 | if (!selector) continue; 73 | 74 | if (selector === '//') { 75 | container = container.shadowRoot; 76 | } else if (selector === 'document') { 77 | container = document; 78 | } 79 | else { 80 | container = container.querySelector(selector); 81 | } 82 | if (!container) break; 83 | } 84 | return container; 85 | }; 86 | window['queryDeepSelector'] = queryDeepSelector; 87 | } 88 | }); 89 | return page; 90 | } 91 | 92 | /** 93 | * clears the collected events in the browser 94 | */ 95 | async clearEventList () { 96 | await this.page.evaluate(() => { 97 | // @ts-ignore 98 | dashboard.clearEvents(); 99 | }); 100 | } 101 | 102 | /** 103 | * Selects a component to be tested 104 | * @param {string} componentName 105 | * @returns ElementHandle 106 | */ 107 | async setTestSubject (componentName) { 108 | this.testSubjectName = componentName; 109 | let lastTargetComponent = this.targetComponent; 110 | while (lastTargetComponent === this.targetComponent) { 111 | // @ts-ignore 112 | lastTargetComponent = await this.page.evaluate((async (componentName) => { 113 | await showroom.ready; 114 | await showroom.setTestSubject(componentName) 115 | }), componentName); 116 | this.targetComponent = await this.testSubject(); 117 | } 118 | return this.targetComponent; 119 | } 120 | 121 | /** 122 | * @return ElementHandle 123 | */ 124 | async testSubject () { 125 | /** 126 | * @type ElementHandle 127 | */ 128 | // @ts-ignore 129 | const handle = await this.page.evaluateHandle(() => { 130 | // @ts-ignore 131 | return dashboard.targetComponent; 132 | }); 133 | return handle; 134 | } 135 | 136 | /** 137 | * @returns Promise> 138 | */ 139 | async getEventList () { 140 | return await (await this.page.evaluate(async () => { 141 | // @ts-ignore 142 | await showroom.ready; 143 | return window.dashboard.events.map(({type, detail, bubbles}) => { 144 | return { 145 | type, 146 | detail, 147 | bubbles 148 | }; 149 | }); 150 | })); 151 | } 152 | 153 | /** 154 | * @returns SerializedRemoteEvent 155 | */ 156 | async getLastEvent () { 157 | return this.page.evaluate(() => { 158 | // @ts-ignore 159 | const { type, detail, bubbles } = window.dashboard.lastEvent; 160 | return { type, detail, bubbles }; 161 | }); 162 | } 163 | 164 | /** 165 | * Tests HTML validity of a remote element 166 | * @param {ElementHandle} target HTML as string 167 | * @throws {Error} When validation fails 168 | */ 169 | async validateHTML (target) { 170 | /** 171 | * @type ElementHandle 172 | */ 173 | const resolvedTarget = target || this.targetComponent; 174 | const html = await this.getProperty('innerHTML', resolvedTarget); 175 | await validator({ 176 | format: 'text', 177 | data: html 178 | }); 179 | } 180 | 181 | /** 182 | * Returns the value of a property on the remote target 183 | * @param {string} property 184 | * @param {ElementHandle} [target] defaults to current tested component 185 | * @returns any|JSHandle 186 | */ 187 | async getProperty (property, target) { 188 | const resolvedTarget = target || this.targetComponent; 189 | return await (await this.page.evaluate((target, prop) => { 190 | return target[prop]; 191 | }, resolvedTarget, property)); 192 | } 193 | 194 | /** 195 | * Sets a property on a remote target 196 | * @param {string} property 197 | * @param {any} value 198 | * @param {ElementHandle} [target] defaults to current tested component 199 | */ 200 | async setProperty (property, value, target) { 201 | const resolvedTarget = target || this.targetComponent; 202 | await this.page.evaluate((target, prop, value) => { 203 | // @ts-ignore 204 | showroom.setProperty(prop, value); 205 | }, resolvedTarget, property, value); 206 | } 207 | 208 | async setAttribute (name, value, target) { 209 | const resolvedTarget = target || this.targetComponent; 210 | await this.page.evaluate((target, name, value) => { 211 | // @ts-ignore 212 | showroom.setAttribute(name, value); 213 | }, resolvedTarget, name, value); 214 | return resolvedTarget; 215 | } 216 | 217 | /** 218 | * Returns value of a remote element's attribute 219 | * @param {string} name 220 | * @param {ElementHandle} target 221 | * @returns Promise 222 | */ 223 | async getAttribute (name, target) { 224 | const resolvedTarget = target || this.targetComponent; 225 | /** 226 | * @type string 227 | */ 228 | const result = await(await this.page.evaluate((target, name) => { 229 | return target.getAttribute(name); 230 | }, resolvedTarget, name)); 231 | return result; 232 | } 233 | 234 | /** 235 | * @param {string} name 236 | * @param {ElementHandle} target 237 | * @returns Promise 238 | */ 239 | async hasAttribute (name, target) { 240 | const resolvedTarget = target || this.targetComponent; 241 | /** 242 | * @type boolean 243 | */ 244 | const result = await(await this.page.evaluate((target, name) => { 245 | return target.hasAttribute(name); 246 | }, resolvedTarget, name)); 247 | return result; 248 | } 249 | 250 | /** 251 | * @param {string} name 252 | * @param {ElementHandle} target 253 | */ 254 | async removeAttribute (name, target) { 255 | const resolvedTarget = target || this.targetComponent; 256 | await this.page.evaluate((target, name) => { 257 | target.removeAttribute(name); 258 | }, resolvedTarget, name); 259 | return resolvedTarget; 260 | } 261 | 262 | /** 263 | * 264 | * @param {ElementHandle} target 265 | * @returns boolean; 266 | */ 267 | async isVisible (target) { 268 | const resolvedTarget = target || this.targetComponent; 269 | 270 | /** 271 | * @type boolean 272 | */ 273 | const isIntersectingViewport = await resolvedTarget.isIntersectingViewport(); 274 | /** 275 | * @type boolean 276 | */ 277 | const isNotHidden = await this.page.evaluate(async (target) => { 278 | const style = getComputedStyle(target); 279 | return style && style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; 280 | }, resolvedTarget); 281 | return isIntersectingViewport && isNotHidden; 282 | } 283 | 284 | /** 285 | * 286 | * @param {ElementHandle} target 287 | * @returns Promise 288 | */ 289 | async getTextContent(target) { 290 | const resolvedTarget = target || this.targetComponent; 291 | await this.page.evaluate(async (target) => target, resolvedTarget); 292 | 293 | const textContent = await resolvedTarget.getProperty('textContent'); 294 | /** 295 | * @type Promise 296 | */ 297 | const asText = textContent.jsonValue(); 298 | return asText; 299 | } 300 | 301 | /** 302 | * Executes predefined function on a showroom descriptor file for the current tested component 303 | * @param {string} fnName 304 | */ 305 | async trigger (fnName) { 306 | await this.page.evaluate((fnName) => { 307 | // @ts-ignore 308 | dashboard.trigger(fnName); 309 | }, fnName); 310 | } 311 | } 312 | 313 | /** 314 | * @param {Page} page 315 | * @returns TestUtils 316 | */ 317 | module.exports = async function createUtils(page) { 318 | if (!page) { 319 | throw new Error('Page object is mandatory'); 320 | } 321 | const testUtils = new TestUtils(); 322 | await testUtils.initialize(page); 323 | return testUtils; 324 | } 325 | 326 | module.exports.TestUtils = TestUtils; 327 | -------------------------------------------------------------------------------- /app/client/component-dashboard.js: -------------------------------------------------------------------------------- 1 | import ComponentRenderer from './component-renderer.js'; 2 | import './showroom-json-editor.js'; 3 | import { CustomControlForm } from './custom-control-form.js'; 4 | 5 | 6 | const stringify = (event) => { 7 | const cloned = Object.assign({ 8 | detail: event.detail, 9 | type: event.type 10 | }, event); 11 | delete cloned.isTrusted; 12 | return JSON.stringify(cloned, null, 2); 13 | }; 14 | 15 | export default class ComponentDashboard extends HTMLElement { 16 | 17 | constructor () { 18 | super(); 19 | this._ = this.attachShadow({mode: 'open'}); 20 | this.render(); 21 | window.dashboard = this; 22 | } 23 | 24 | trigger (fnName) { 25 | if (fnName && this.customControlForm) { 26 | this.customControlForm.triggers[fnName](); 27 | } 28 | } 29 | 30 | get lastEvent () { 31 | return this.events[this.events.length - 1]; 32 | } 33 | 34 | get events () { 35 | try { 36 | return this.eventLog.logged || [] 37 | } 38 | catch (err) { 39 | return []; 40 | } 41 | } 42 | 43 | render () { 44 | this._.innerHTML = /*html*/` 45 | 146 |
147 |
148 |
149 | 150 |
151 |
152 |

Events

153 | 154 |
155 |
156 |
157 |
158 | 159 | `; 160 | this.footer = this._.getElementById('footer'); 161 | this.rendererContainer = this._.getElementById('renderer-container'); 162 | this.dashboard = this._.getElementById('dashboard'); 163 | this.eventLog = this._.getElementById('eventLog'); 164 | this.toggle = this._.getElementById('toggle'); 165 | const clearButton = this._.getElementById('clear-events'); 166 | this.toggle.onclick = () => { 167 | if (this.hasAttribute('collapsed')) { 168 | this.removeAttribute('collapsed'); 169 | } else { 170 | this.setAttribute('collapsed', ''); 171 | } 172 | } 173 | this.dashboard.classList.add('container'); 174 | clearButton.onclick = () => this.clearEvents(); 175 | } 176 | 177 | get component () { 178 | return this.renderer.component; 179 | } 180 | 181 | addCustomForm (formData) { 182 | this.dashboard.innerHTML = null; 183 | const targetComponent = this.renderer.component; 184 | this.customControlForm = new CustomControlForm(targetComponent, formData) 185 | this.dashboard.appendChild(this.customControlForm); 186 | } 187 | 188 | attachEvents (target, eventList) { 189 | this.eventLog.logged = []; 190 | if (target.$SHOWROOM_META_ATTACHED) { 191 | return; 192 | } 193 | if (eventList) { 194 | eventList.forEach(eventName => { 195 | target.addEventListener(eventName, (event) => { 196 | console.info(event); 197 | const stringified = stringify(event); 198 | const banner = document.createElement('div'); 199 | banner.classList.add('banner'); 200 | banner.innerHTML = ` 201 |
202 | Event: ${eventName} 203 |
${stringified}
204 |
205 | ` 206 | this.eventLog.appendChild(banner); 207 | this.eventLog.logged.push(event); 208 | }); 209 | }); 210 | } 211 | target.$SHOWROOM_META_ATTACHED = true; 212 | } 213 | 214 | clearEvents () { 215 | this.eventLog.innerHTML = ''; 216 | this.eventLog.logged = []; 217 | } 218 | 219 | autoResizeTextArea (el) { 220 | el.onkeydown = () => { 221 | requestAnimationFrame( () => { 222 | el.style.cssText = 'min-height:' + el.scrollHeight + 'px'; 223 | el.scrollIntoView({block: 'end', behavior: 'smooth'}); 224 | }); 225 | }; 226 | el.style.cssText = 'min-height:' + el.scrollHeight + 'px'; 227 | } 228 | 229 | addInnerHTMLForm (innerHTML) { 230 | if (innerHTML) { 231 | const editor = document.createElement('textarea'); 232 | editor.classList.add('topcoat-textarea'); 233 | const label = document.createElement('h3') 234 | label.innerText = 'Inner HTML'; 235 | this.dashboard.appendChild(label); 236 | this.dashboard.appendChild(editor); 237 | editor.value = innerHTML.trim(); 238 | editor.onchange = () => { 239 | this.targetComponent.innerHTML = editor.value; 240 | }; 241 | this.targetComponent.innerHTML = editor.value; 242 | this.autoResizeTextArea(editor); 243 | } 244 | } 245 | 246 | addOuterHTMLForm (outerHTML) { 247 | if (outerHTML) { 248 | const editor = document.createElement('textarea'); 249 | editor.classList.add('topcoat-textarea'); 250 | const label = document.createElement('h3') 251 | label.innerText = 'Wrapping HTML'; 252 | const innerLabel = document.createElement('span'); 253 | innerLabel.innerHTML = '
(use the <showroom-mount-point> node to position the custom component)'; 254 | innerLabel.style.cssText = 'font-size: 0.8rem; font-weight: normal; padding-left: 1rem;'; 255 | label.appendChild(innerLabel); 256 | this.dashboard.appendChild(label); 257 | this.dashboard.appendChild(editor); 258 | editor.value = outerHTML.trim(); 259 | editor.onchange = () => { 260 | this.setupComponent(Object.assign({}, this.componentModule, { 261 | outerHTML: editor.value 262 | })); 263 | }; 264 | this.autoResizeTextArea(editor); 265 | } 266 | } 267 | 268 | setupComponent (module) { 269 | console.log('Loading component: ' + module.component); 270 | this.componentModule = module; 271 | const { functions, component, properties, attributes, events, innerHTML, outerHTML, centered, extends : isExtending } = module; 272 | if (centered) { 273 | this.setAttribute('center', ''); 274 | } else { 275 | this.removeAttribute('center'); 276 | } 277 | this.dashboard.innerHTML = ''; 278 | if (this.renderer) { 279 | this.renderer.remove(); 280 | } 281 | this.clearEvents(); 282 | this.renderer = new ComponentRenderer(outerHTML, attributes, isExtending); 283 | this.renderer.setAttribute('name', component); 284 | this.rendererContainer.appendChild(this.renderer); 285 | if (this.targetComponent === this.renderer.component) { 286 | throw new Error('Something bad happened'); 287 | } 288 | this.targetComponent = this.renderer.component; 289 | this.addCustomForm({properties, attributes, functions}); 290 | this.addInnerHTMLForm(innerHTML); 291 | this.addOuterHTMLForm(outerHTML); 292 | requestAnimationFrame( () => { 293 | this.attachEvents(this.targetComponent, events); 294 | }); 295 | 296 | } 297 | 298 | async loadComponent (modulePath) { 299 | const module = await import(modulePath); 300 | this.setupComponent(module.default); 301 | } 302 | 303 | static get observedAttributes () { 304 | return ['descriptor']; 305 | } 306 | 307 | attributeChangedCallback () { 308 | const path = this.getAttribute('descriptor'); 309 | this.loadComponent(path); 310 | } 311 | 312 | } 313 | 314 | customElements.define('component-dashboard', ComponentDashboard); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Avichay Eyal 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /app/client/assets/topcoat-desktop-light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2012 Adobe Systems Inc.; 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | .button-bar { 20 | display: table; 21 | table-layout: fixed; 22 | white-space: nowrap; 23 | margin: 0; 24 | padding: 0; 25 | } 26 | 27 | .button-bar__item { 28 | display: table-cell; 29 | width: auto; 30 | border-radius: 0; 31 | } 32 | 33 | .button-bar__item > input { 34 | position: absolute; 35 | overflow: hidden; 36 | padding: 0; 37 | border: 0; 38 | opacity: 0.001; 39 | z-index: 1; 40 | vertical-align: top; 41 | outline: none; 42 | } 43 | 44 | .button-bar__button { 45 | border-radius: inherit; 46 | } 47 | 48 | .button-bar__item:disabled { 49 | opacity: 0.3; 50 | cursor: default; 51 | pointer-events: none; 52 | } 53 | 54 | /** 55 | * 56 | * Copyright 2012 Adobe Systems Inc.; 57 | * 58 | * Licensed under the Apache License, Version 2.0 (the "License"); 59 | * you may not use this file except in compliance with the License. 60 | * You may obtain a copy of the License at 61 | * 62 | * http://www.apache.org/licenses/LICENSE-2.0 63 | * 64 | * Unless required by applicable law or agreed to in writing, software 65 | * distributed under the License is distributed on an "AS IS" BASIS, 66 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 67 | * See the License for the specific language governing permissions and 68 | * limitations under the License. 69 | * 70 | */ 71 | 72 | /** 73 | * 74 | * Copyright 2012 Adobe Systems Inc.; 75 | * 76 | * Licensed under the Apache License, Version 2.0 (the "License"); 77 | * you may not use this file except in compliance with the License. 78 | * You may obtain a copy of the License at 79 | * 80 | * http://www.apache.org/licenses/LICENSE-2.0 81 | * 82 | * Unless required by applicable law or agreed to in writing, software 83 | * distributed under the License is distributed on an "AS IS" BASIS, 84 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 85 | * See the License for the specific language governing permissions and 86 | * limitations under the License. 87 | * 88 | */ 89 | 90 | /** 91 | * 92 | * Copyright 2012 Adobe Systems Inc.; 93 | * 94 | * Licensed under the Apache License, Version 2.0 (the "License"); 95 | * you may not use this file except in compliance with the License. 96 | * You may obtain a copy of the License at 97 | * 98 | * http://www.apache.org/licenses/LICENSE-2.0 99 | * 100 | * Unless required by applicable law or agreed to in writing, software 101 | * distributed under the License is distributed on an "AS IS" BASIS, 102 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 103 | * See the License for the specific language governing permissions and 104 | * limitations under the License. 105 | * 106 | */ 107 | 108 | .button, 109 | .topcoat-button, 110 | .topcoat-button--quiet, 111 | .topcoat-button--large, 112 | .topcoat-button--large--quiet, 113 | .topcoat-button--cta, 114 | .topcoat-button--large--cta, 115 | .topcoat-button-bar__button, 116 | .topcoat-button-bar__button--large { 117 | position: relative; 118 | display: inline-block; 119 | vertical-align: top; 120 | -moz-box-sizing: border-box; 121 | box-sizing: border-box; 122 | background-clip: padding-box; 123 | padding: 0; 124 | margin: 0; 125 | font: inherit; 126 | color: inherit; 127 | background: transparent; 128 | border: none; 129 | cursor: default; 130 | -webkit-user-select: none; 131 | -moz-user-select: none; 132 | -ms-user-select: none; 133 | user-select: none; 134 | text-overflow: ellipsis; 135 | white-space: nowrap; 136 | overflow: hidden; 137 | text-decoration: none; 138 | } 139 | 140 | .button--quiet { 141 | background: transparent; 142 | border: 1px solid transparent; 143 | box-shadow: none; 144 | } 145 | 146 | .button--disabled, 147 | .topcoat-button:disabled, 148 | .topcoat-button--quiet:disabled, 149 | .topcoat-button--large:disabled, 150 | .topcoat-button--large--quiet:disabled, 151 | .topcoat-button--cta:disabled, 152 | .topcoat-button--large--cta:disabled, 153 | .topcoat-button-bar__button:disabled, 154 | .topcoat-button-bar__button--large:disabled { 155 | opacity: 0.3; 156 | cursor: default; 157 | pointer-events: none; 158 | } 159 | 160 | .topcoat-button, 161 | .topcoat-button--quiet, 162 | .topcoat-button--large, 163 | .topcoat-button--large--quiet, 164 | .topcoat-button--cta, 165 | .topcoat-button--large--cta, 166 | .topcoat-button-bar__button, 167 | .topcoat-button-bar__button--large { 168 | padding: 0 0.563rem; 169 | font-size: 12px; 170 | line-height: 1.313rem; 171 | letter-spacing: 0; 172 | color: #454545; 173 | text-shadow: 0 1px #fff; 174 | vertical-align: top; 175 | background-color: #e5e9e8; 176 | box-shadow: inset 0 1px #fff; 177 | border: 1px solid #9daca9; 178 | border-radius: 4px; 179 | } 180 | 181 | .topcoat-button:hover, 182 | .topcoat-button--quiet:hover, 183 | .topcoat-button--large:hover, 184 | .topcoat-button--large--quiet:hover, 185 | .topcoat-button-bar__button:hover, 186 | .topcoat-button-bar__button--large:hover { 187 | background-color: #eff1f1; 188 | } 189 | 190 | .topcoat-button:focus, 191 | .topcoat-button--quiet:focus, 192 | .topcoat-button--quiet:hover:focus, 193 | .topcoat-button--large:focus, 194 | .topcoat-button--large--quiet:focus, 195 | .topcoat-button--large--quiet:hover:focus, 196 | .topcoat-button--cta:focus, 197 | .topcoat-button--large--cta:focus, 198 | .topcoat-button-bar__button:focus, 199 | .topcoat-button-bar__button--large:focus { 200 | border: 1px solid #0036ff; 201 | box-shadow: inset 0 1px rgba(255,255,255,0.36), 0 0 0 2px #6fb5f1; 202 | outline: 0; 203 | } 204 | 205 | .topcoat-button:active, 206 | .topcoat-button--large:active, 207 | .topcoat-button-bar__button:active, 208 | .topcoat-button-bar__button--large:active, 209 | :checked + .topcoat-button-bar__button { 210 | border: 1px solid #9daca9; 211 | background-color: #d2d6d6; 212 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 213 | } 214 | 215 | .topcoat-button--quiet { 216 | background: transparent; 217 | border: 1px solid transparent; 218 | box-shadow: none; 219 | } 220 | 221 | .topcoat-button--quiet:hover, 222 | .topcoat-button--large--quiet:hover { 223 | text-shadow: 0 1px #fff; 224 | border: 1px solid #9daca9; 225 | box-shadow: inset 0 1px #fff; 226 | } 227 | 228 | .topcoat-button--quiet:active, 229 | .topcoat-button--quiet:focus:active, 230 | .topcoat-button--large--quiet:active, 231 | .topcoat-button--large--quiet:focus:active { 232 | color: #454545; 233 | text-shadow: 0 1px #fff; 234 | background-color: #d2d6d6; 235 | border: 1px solid #9daca9; 236 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 237 | } 238 | 239 | .topcoat-button--large, 240 | .topcoat-button--large--quiet, 241 | .topcoat-button-bar__button--large { 242 | font-size: 0.875rem; 243 | font-weight: 600; 244 | line-height: 1.688rem; 245 | padding: 0 0.875rem; 246 | } 247 | 248 | .topcoat-button--large--quiet { 249 | background: transparent; 250 | border: 1px solid transparent; 251 | box-shadow: none; 252 | } 253 | 254 | .topcoat-button--cta, 255 | .topcoat-button--large--cta { 256 | border: 1px solid #134f7f; 257 | background-color: #288edf; 258 | box-shadow: inset 0 1px rgba(255,255,255,0.36); 259 | color: #fff; 260 | font-weight: 500; 261 | text-shadow: 0 -1px rgba(0,0,0,0.36); 262 | } 263 | 264 | .topcoat-button--cta:hover, 265 | .topcoat-button--large--cta:hover { 266 | background-color: #4ca1e4; 267 | } 268 | 269 | .topcoat-button--cta:active, 270 | .topcoat-button--large--cta:active { 271 | background-color: #1e7dc8; 272 | box-shadow: inset 0 1px rgba(0,0,0,0.12); 273 | } 274 | 275 | .topcoat-button--large--cta { 276 | font-size: 0.875rem; 277 | font-weight: 600; 278 | line-height: 1.688rem; 279 | padding: 0 0.875rem; 280 | } 281 | 282 | .button-bar, 283 | .topcoat-button-bar { 284 | display: table; 285 | table-layout: fixed; 286 | white-space: nowrap; 287 | margin: 0; 288 | padding: 0; 289 | } 290 | 291 | .button-bar__item, 292 | .topcoat-button-bar__item { 293 | display: table-cell; 294 | width: auto; 295 | border-radius: 0; 296 | } 297 | 298 | .button-bar__item > input, 299 | .topcoat-button-bar__item > input { 300 | position: absolute; 301 | overflow: hidden; 302 | padding: 0; 303 | border: 0; 304 | opacity: 0.001; 305 | z-index: 1; 306 | vertical-align: top; 307 | outline: none; 308 | } 309 | 310 | .button-bar__button { 311 | border-radius: inherit; 312 | } 313 | 314 | .button-bar__item:disabled { 315 | opacity: 0.3; 316 | cursor: default; 317 | pointer-events: none; 318 | } 319 | 320 | /* topdoc 321 | name: Button Bar 322 | description: Component of grouped buttons 323 | modifiers: 324 | :disabled: Disabled state 325 | markup: 326 |
327 |
328 | 329 |
330 |
331 | 332 |
333 |
334 | 335 |
336 |
337 | examples: 338 | mobile button bar: http://codepen.io/Topcoat/pen/kdKyg 339 | tags: 340 | - desktop 341 | - light 342 | - dark 343 | - mobile 344 | - button 345 | - group 346 | - bar 347 | */ 348 | 349 | .topcoat-button-bar > .topcoat-button-bar__item:first-child { 350 | border-top-left-radius: 4px; 351 | border-bottom-left-radius: 4px; 352 | } 353 | 354 | .topcoat-button-bar > .topcoat-button-bar__item:last-child { 355 | border-top-right-radius: 4px; 356 | border-bottom-right-radius: 4px; 357 | } 358 | 359 | .topcoat-button-bar__item:first-child > .topcoat-button-bar__button, 360 | .topcoat-button-bar__item:first-child > .topcoat-button-bar__button--large { 361 | border-right: none; 362 | } 363 | 364 | .topcoat-button-bar__item:last-child > .topcoat-button-bar__button, 365 | .topcoat-button-bar__item:last-child > .topcoat-button-bar__button--large { 366 | border-left: none; 367 | } 368 | 369 | .topcoat-button-bar__button { 370 | border-radius: inherit; 371 | } 372 | 373 | .topcoat-button-bar__button:focus, 374 | .topcoat-button-bar__button--large:focus { 375 | z-index: 1; 376 | } 377 | 378 | /* topdoc 379 | name: Large Button Bar 380 | description: A button bar, only larger 381 | modifiers: 382 | :disabled: Disabled state 383 | markup: 384 |
385 |
386 | 387 |
388 |
389 | 390 |
391 |
392 | 393 |
394 |
395 | tags: 396 | - desktop 397 | - light 398 | - dark 399 | - mobile 400 | - button 401 | - group 402 | - bar 403 | - large 404 | */ 405 | 406 | .topcoat-button-bar__button--large { 407 | border-radius: inherit; 408 | } 409 | 410 | /** 411 | * 412 | * Copyright 2012 Adobe Systems Inc.; 413 | * 414 | * Licensed under the Apache License, Version 2.0 (the "License"); 415 | * you may not use this file except in compliance with the License. 416 | * You may obtain a copy of the License at 417 | * 418 | * http://www.apache.org/licenses/LICENSE-2.0 419 | * 420 | * Unless required by applicable law or agreed to in writing, software 421 | * distributed under the License is distributed on an "AS IS" BASIS, 422 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 423 | * See the License for the specific language governing permissions and 424 | * limitations under the License. 425 | * 426 | */ 427 | 428 | .button { 429 | position: relative; 430 | display: inline-block; 431 | vertical-align: top; 432 | -moz-box-sizing: border-box; 433 | box-sizing: border-box; 434 | background-clip: padding-box; 435 | padding: 0; 436 | margin: 0; 437 | font: inherit; 438 | color: inherit; 439 | background: transparent; 440 | border: none; 441 | cursor: default; 442 | -webkit-user-select: none; 443 | -moz-user-select: none; 444 | -ms-user-select: none; 445 | user-select: none; 446 | text-overflow: ellipsis; 447 | white-space: nowrap; 448 | overflow: hidden; 449 | text-decoration: none; 450 | } 451 | 452 | .button--quiet { 453 | background: transparent; 454 | border: 1px solid transparent; 455 | box-shadow: none; 456 | } 457 | 458 | .button--disabled { 459 | opacity: 0.3; 460 | cursor: default; 461 | pointer-events: none; 462 | } 463 | 464 | /** 465 | * 466 | * Copyright 2012 Adobe Systems Inc.; 467 | * 468 | * Licensed under the Apache License, Version 2.0 (the "License"); 469 | * you may not use this file except in compliance with the License. 470 | * You may obtain a copy of the License at 471 | * 472 | * http://www.apache.org/licenses/LICENSE-2.0 473 | * 474 | * Unless required by applicable law or agreed to in writing, software 475 | * distributed under the License is distributed on an "AS IS" BASIS, 476 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 477 | * See the License for the specific language governing permissions and 478 | * limitations under the License. 479 | * 480 | */ 481 | 482 | /** 483 | * 484 | * Copyright 2012 Adobe Systems Inc.; 485 | * 486 | * Licensed under the Apache License, Version 2.0 (the "License"); 487 | * you may not use this file except in compliance with the License. 488 | * You may obtain a copy of the License at 489 | * 490 | * http://www.apache.org/licenses/LICENSE-2.0 491 | * 492 | * Unless required by applicable law or agreed to in writing, software 493 | * distributed under the License is distributed on an "AS IS" BASIS, 494 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 495 | * See the License for the specific language governing permissions and 496 | * limitations under the License. 497 | * 498 | */ 499 | 500 | /** 501 | * 502 | * Copyright 2012 Adobe Systems Inc.; 503 | * 504 | * Licensed under the Apache License, Version 2.0 (the "License"); 505 | * you may not use this file except in compliance with the License. 506 | * You may obtain a copy of the License at 507 | * 508 | * http://www.apache.org/licenses/LICENSE-2.0 509 | * 510 | * Unless required by applicable law or agreed to in writing, software 511 | * distributed under the License is distributed on an "AS IS" BASIS, 512 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 513 | * See the License for the specific language governing permissions and 514 | * limitations under the License. 515 | * 516 | */ 517 | 518 | .button, 519 | .topcoat-button, 520 | .topcoat-button--quiet, 521 | .topcoat-button--large, 522 | .topcoat-button--large--quiet, 523 | .topcoat-button--cta, 524 | .topcoat-button--large--cta { 525 | position: relative; 526 | display: inline-block; 527 | vertical-align: top; 528 | -moz-box-sizing: border-box; 529 | box-sizing: border-box; 530 | background-clip: padding-box; 531 | padding: 0; 532 | margin: 0; 533 | font: inherit; 534 | color: inherit; 535 | background: transparent; 536 | border: none; 537 | cursor: default; 538 | -webkit-user-select: none; 539 | -moz-user-select: none; 540 | -ms-user-select: none; 541 | user-select: none; 542 | text-overflow: ellipsis; 543 | white-space: nowrap; 544 | overflow: hidden; 545 | text-decoration: none; 546 | } 547 | 548 | .button--quiet { 549 | background: transparent; 550 | border: 1px solid transparent; 551 | box-shadow: none; 552 | } 553 | 554 | .button--disabled, 555 | .topcoat-button:disabled, 556 | .topcoat-button--quiet:disabled, 557 | .topcoat-button--large:disabled, 558 | .topcoat-button--large--quiet:disabled, 559 | .topcoat-button--cta:disabled, 560 | .topcoat-button--large--cta:disabled { 561 | opacity: 0.3; 562 | cursor: default; 563 | pointer-events: none; 564 | } 565 | 566 | /* topdoc 567 | name: Button 568 | description: A simple button 569 | modifiers: 570 | :active: Active state 571 | :disabled: Disabled state 572 | :hover: Hover state 573 | :focus: Focused 574 | markup: 575 | 576 | 577 | examples: 578 | mobile button: http://codepen.io/Topcoat/pen/DpKtf 579 | tags: 580 | - desktop 581 | - light 582 | - mobile 583 | - button 584 | */ 585 | 586 | .topcoat-button, 587 | .topcoat-button--quiet, 588 | .topcoat-button--large, 589 | .topcoat-button--large--quiet, 590 | .topcoat-button--cta, 591 | .topcoat-button--large--cta { 592 | padding: 0 0.563rem; 593 | font-size: 12px; 594 | line-height: 1.313rem; 595 | letter-spacing: 0; 596 | color: #454545; 597 | text-shadow: 0 1px #fff; 598 | vertical-align: top; 599 | background-color: #e5e9e8; 600 | box-shadow: inset 0 1px #fff; 601 | border: 1px solid #9daca9; 602 | border-radius: 4px; 603 | } 604 | 605 | .topcoat-button:hover, 606 | .topcoat-button--quiet:hover, 607 | .topcoat-button--large:hover, 608 | .topcoat-button--large--quiet:hover { 609 | background-color: #eff1f1; 610 | } 611 | 612 | .topcoat-button:focus, 613 | .topcoat-button--quiet:focus, 614 | .topcoat-button--quiet:hover:focus, 615 | .topcoat-button--large:focus, 616 | .topcoat-button--large--quiet:focus, 617 | .topcoat-button--large--quiet:hover:focus, 618 | .topcoat-button--cta:focus, 619 | .topcoat-button--large--cta:focus { 620 | border: 1px solid #0036ff; 621 | box-shadow: inset 0 1px rgba(255,255,255,0.36), 0 0 0 2px #6fb5f1; 622 | outline: 0; 623 | } 624 | 625 | .topcoat-button:active, 626 | .topcoat-button--large:active { 627 | border: 1px solid #9daca9; 628 | background-color: #d2d6d6; 629 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 630 | } 631 | 632 | /* topdoc 633 | name: Quiet Button 634 | description: A simple, yet quiet button 635 | modifiers: 636 | :active: Quiet button active state 637 | :disabled: Disabled state 638 | :hover: Hover state 639 | :focus: Focused 640 | markup: 641 | 642 | 643 | tags: 644 | - desktop 645 | - light 646 | - mobile 647 | - button 648 | - quiet 649 | */ 650 | 651 | .topcoat-button--quiet { 652 | background: transparent; 653 | border: 1px solid transparent; 654 | box-shadow: none; 655 | } 656 | 657 | .topcoat-button--quiet:hover, 658 | .topcoat-button--large--quiet:hover { 659 | text-shadow: 0 1px #fff; 660 | border: 1px solid #9daca9; 661 | box-shadow: inset 0 1px #fff; 662 | } 663 | 664 | .topcoat-button--quiet:active, 665 | .topcoat-button--quiet:focus:active, 666 | .topcoat-button--large--quiet:active, 667 | .topcoat-button--large--quiet:focus:active { 668 | color: #454545; 669 | text-shadow: 0 1px #fff; 670 | background-color: #d2d6d6; 671 | border: 1px solid #9daca9; 672 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 673 | } 674 | 675 | /* topdoc 676 | name: Large Button 677 | description: A big ol button 678 | modifiers: 679 | :active: Active state 680 | :disabled: Disabled state 681 | :hover: Hover state 682 | :focus: Focused 683 | markup: 684 | 685 | 686 | tags: 687 | - desktop 688 | - light 689 | - mobile 690 | - button 691 | - large 692 | */ 693 | 694 | .topcoat-button--large, 695 | .topcoat-button--large--quiet { 696 | font-size: 0.875rem; 697 | font-weight: 600; 698 | line-height: 1.688rem; 699 | padding: 0 0.875rem; 700 | } 701 | 702 | /* topdoc 703 | name: Large Quiet Button 704 | description: A large, yet quiet button 705 | modifiers: 706 | :active: Active state 707 | :disabled: Disabled state 708 | :hover: Hover state 709 | :focus: Focused 710 | markup: 711 | 712 | 713 | tags: 714 | - desktop 715 | - light 716 | - mobile 717 | - button 718 | - large 719 | - quiet 720 | */ 721 | 722 | .topcoat-button--large--quiet { 723 | background: transparent; 724 | border: 1px solid transparent; 725 | box-shadow: none; 726 | } 727 | 728 | /* topdoc 729 | name: Call To Action Button 730 | description: A CALL TO ARMS, er, ACTION! 731 | modifiers: 732 | :active: Active state 733 | :disabled: Disabled state 734 | :hover: Hover state 735 | :focus: Focused 736 | markup: 737 | 738 | 739 | tags: 740 | - desktop 741 | - light 742 | - mobile 743 | - button 744 | - call to action 745 | */ 746 | 747 | .topcoat-button--cta, 748 | .topcoat-button--large--cta { 749 | border: 1px solid #134f7f; 750 | background-color: #288edf; 751 | box-shadow: inset 0 1px rgba(255,255,255,0.36); 752 | color: #fff; 753 | font-weight: 500; 754 | text-shadow: 0 -1px rgba(0,0,0,0.36); 755 | } 756 | 757 | .topcoat-button--cta:hover, 758 | .topcoat-button--large--cta:hover { 759 | background-color: #4ca1e4; 760 | } 761 | 762 | .topcoat-button--cta:active, 763 | .topcoat-button--large--cta:active { 764 | background-color: #1e7dc8; 765 | box-shadow: inset 0 1px rgba(0,0,0,0.12); 766 | } 767 | 768 | /* topdoc 769 | name: Large Call To Action Button 770 | description: Like call to action, but bigger 771 | modifiers: 772 | :active: Active state 773 | :disabled: Disabled state 774 | :hover: Hover state 775 | :focus: Focused 776 | markup: 777 | 778 | 779 | tags: 780 | - desktop 781 | - light 782 | - mobile 783 | - button 784 | - large 785 | - call to action 786 | */ 787 | 788 | .topcoat-button--large--cta { 789 | font-size: 0.875rem; 790 | font-weight: 600; 791 | line-height: 1.688rem; 792 | padding: 0 0.875rem; 793 | } 794 | 795 | /** 796 | * 797 | * Copyright 2012 Adobe Systems Inc.; 798 | * 799 | * Licensed under the Apache License, Version 2.0 (the "License"); 800 | * you may not use this file except in compliance with the License. 801 | * You may obtain a copy of the License at 802 | * 803 | * http://www.apache.org/licenses/LICENSE-2.0 804 | * 805 | * Unless required by applicable law or agreed to in writing, software 806 | * distributed under the License is distributed on an "AS IS" BASIS, 807 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 808 | * See the License for the specific language governing permissions and 809 | * limitations under the License. 810 | * 811 | */ 812 | 813 | input[type="checkbox"] { 814 | position: absolute; 815 | overflow: hidden; 816 | padding: 0; 817 | border: 0; 818 | opacity: 0.001; 819 | z-index: 1; 820 | vertical-align: top; 821 | outline: none; 822 | } 823 | 824 | .checkbox { 825 | -moz-box-sizing: border-box; 826 | box-sizing: border-box; 827 | background-clip: padding-box; 828 | position: relative; 829 | display: inline-block; 830 | vertical-align: top; 831 | cursor: default; 832 | -webkit-user-select: none; 833 | -moz-user-select: none; 834 | -ms-user-select: none; 835 | user-select: none; 836 | } 837 | 838 | .checkbox__label { 839 | position: relative; 840 | display: inline-block; 841 | vertical-align: top; 842 | cursor: default; 843 | -webkit-user-select: none; 844 | -moz-user-select: none; 845 | -ms-user-select: none; 846 | user-select: none; 847 | } 848 | 849 | .checkbox--disabled { 850 | opacity: 0.3; 851 | cursor: default; 852 | pointer-events: none; 853 | } 854 | 855 | .checkbox:before, 856 | .checkbox:after { 857 | content: ''; 858 | position: absolute; 859 | } 860 | 861 | .checkbox:before { 862 | -moz-box-sizing: border-box; 863 | box-sizing: border-box; 864 | background-clip: padding-box; 865 | } 866 | 867 | /** 868 | * 869 | * Copyright 2012 Adobe Systems Inc.; 870 | * 871 | * Licensed under the Apache License, Version 2.0 (the "License"); 872 | * you may not use this file except in compliance with the License. 873 | * You may obtain a copy of the License at 874 | * 875 | * http://www.apache.org/licenses/LICENSE-2.0 876 | * 877 | * Unless required by applicable law or agreed to in writing, software 878 | * distributed under the License is distributed on an "AS IS" BASIS, 879 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 880 | * See the License for the specific language governing permissions and 881 | * limitations under the License. 882 | * 883 | */ 884 | 885 | /** 886 | * 887 | * Copyright 2012 Adobe Systems Inc.; 888 | * 889 | * Licensed under the Apache License, Version 2.0 (the "License"); 890 | * you may not use this file except in compliance with the License. 891 | * You may obtain a copy of the License at 892 | * 893 | * http://www.apache.org/licenses/LICENSE-2.0 894 | * 895 | * Unless required by applicable law or agreed to in writing, software 896 | * distributed under the License is distributed on an "AS IS" BASIS, 897 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 898 | * See the License for the specific language governing permissions and 899 | * limitations under the License. 900 | * 901 | */ 902 | 903 | /** 904 | * 905 | * Copyright 2012 Adobe Systems Inc.; 906 | * 907 | * Licensed under the Apache License, Version 2.0 (the "License"); 908 | * you may not use this file except in compliance with the License. 909 | * You may obtain a copy of the License at 910 | * 911 | * http://www.apache.org/licenses/LICENSE-2.0 912 | * 913 | * Unless required by applicable law or agreed to in writing, software 914 | * distributed under the License is distributed on an "AS IS" BASIS, 915 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 916 | * See the License for the specific language governing permissions and 917 | * limitations under the License. 918 | * 919 | */ 920 | 921 | input[type="checkbox"] { 922 | position: absolute; 923 | overflow: hidden; 924 | padding: 0; 925 | border: 0; 926 | opacity: 0.001; 927 | z-index: 1; 928 | vertical-align: top; 929 | outline: none; 930 | } 931 | 932 | .checkbox, 933 | .topcoat-checkbox__checkmark { 934 | -moz-box-sizing: border-box; 935 | box-sizing: border-box; 936 | background-clip: padding-box; 937 | position: relative; 938 | display: inline-block; 939 | vertical-align: top; 940 | cursor: default; 941 | -webkit-user-select: none; 942 | -moz-user-select: none; 943 | -ms-user-select: none; 944 | user-select: none; 945 | } 946 | 947 | .checkbox__label, 948 | .topcoat-checkbox { 949 | position: relative; 950 | display: inline-block; 951 | vertical-align: top; 952 | cursor: default; 953 | -webkit-user-select: none; 954 | -moz-user-select: none; 955 | -ms-user-select: none; 956 | user-select: none; 957 | } 958 | 959 | .checkbox--disabled, 960 | input[type="checkbox"]:disabled + .topcoat-checkbox__checkmark { 961 | opacity: 0.3; 962 | cursor: default; 963 | pointer-events: none; 964 | } 965 | 966 | .checkbox:before, 967 | .checkbox:after, 968 | .topcoat-checkbox__checkmark:before, 969 | .topcoat-checkbox__checkmark:after { 970 | content: ''; 971 | position: absolute; 972 | } 973 | 974 | .checkbox:before, 975 | .topcoat-checkbox__checkmark:before { 976 | -moz-box-sizing: border-box; 977 | box-sizing: border-box; 978 | background-clip: padding-box; 979 | } 980 | 981 | /* topdoc 982 | name: Checkbox 983 | description: Default skin for Topcoat checkbox 984 | modifiers: 985 | :focus: Focus state 986 | :disabled: Disabled state 987 | markup: 988 | 993 |
994 |
995 | 1000 | examples: 1001 | mobile checkbox: http://codepen.io/Topcoat/pen/piHcs 1002 | tags: 1003 | - desktop 1004 | - light 1005 | - mobile 1006 | - checkbox 1007 | */ 1008 | 1009 | .topcoat-checkbox__checkmark { 1010 | height: 1rem; 1011 | } 1012 | 1013 | input[type="checkbox"] { 1014 | height: 1rem; 1015 | width: 1rem; 1016 | margin-top: 0; 1017 | margin-right: -1rem; 1018 | margin-bottom: -1rem; 1019 | margin-left: 0; 1020 | } 1021 | 1022 | input[type="checkbox"]:checked + .topcoat-checkbox__checkmark:after { 1023 | opacity: 1; 1024 | } 1025 | 1026 | .topcoat-checkbox { 1027 | line-height: 1rem; 1028 | } 1029 | 1030 | .topcoat-checkbox__checkmark:before { 1031 | width: 1rem; 1032 | height: 1rem; 1033 | background: #e5e9e8; 1034 | border: 1px solid #9daca9; 1035 | border-radius: 3px; 1036 | box-shadow: inset 0 1px #fff; 1037 | } 1038 | 1039 | .topcoat-checkbox__checkmark { 1040 | width: 1rem; 1041 | height: 1rem; 1042 | } 1043 | 1044 | .topcoat-checkbox__checkmark:after { 1045 | top: 2px; 1046 | left: 1px; 1047 | opacity: 0; 1048 | width: 14px; 1049 | height: 4px; 1050 | background: transparent; 1051 | border: 7px solid #454545; 1052 | border-width: 3px; 1053 | border-top: none; 1054 | border-right: none; 1055 | border-radius: 1px; 1056 | -webkit-transform: rotate(-50deg); 1057 | -ms-transform: rotate(-50deg); 1058 | transform: rotate(-50deg); 1059 | } 1060 | 1061 | input[type="checkbox"]:focus + .topcoat-checkbox__checkmark:before { 1062 | border: 1px solid #0036ff; 1063 | box-shadow: inset 0 1px rgba(255,255,255,0.36), 0 0 0 2px #6fb5f1; 1064 | } 1065 | 1066 | input[type="checkbox"]:active + .topcoat-checkbox__checkmark:before { 1067 | border: 1px solid #9daca9; 1068 | background-color: #d2d6d6; 1069 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 1070 | } 1071 | 1072 | input[type="checkbox"]:disabled:active + .topcoat-checkbox__checkmark:before { 1073 | border: 1px solid #9daca9; 1074 | background: #e5e9e8; 1075 | box-shadow: inset 0 1px #fff; 1076 | } 1077 | 1078 | /** 1079 | * 1080 | * Copyright 2012 Adobe Systems Inc.; 1081 | * 1082 | * Licensed under the Apache License, Version 2.0 (the "License"); 1083 | * you may not use this file except in compliance with the License. 1084 | * You may obtain a copy of the License at 1085 | * 1086 | * http://www.apache.org/licenses/LICENSE-2.0 1087 | * 1088 | * Unless required by applicable law or agreed to in writing, software 1089 | * distributed under the License is distributed on an "AS IS" BASIS, 1090 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1091 | * See the License for the specific language governing permissions and 1092 | * limitations under the License. 1093 | * 1094 | */ 1095 | 1096 | /** 1097 | * 1098 | * Copyright 2012 Adobe Systems Inc.; 1099 | * 1100 | * Licensed under the Apache License, Version 2.0 (the "License"); 1101 | * you may not use this file except in compliance with the License. 1102 | * You may obtain a copy of the License at 1103 | * 1104 | * http://www.apache.org/licenses/LICENSE-2.0 1105 | * 1106 | * Unless required by applicable law or agreed to in writing, software 1107 | * distributed under the License is distributed on an "AS IS" BASIS, 1108 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1109 | * See the License for the specific language governing permissions and 1110 | * limitations under the License. 1111 | * 1112 | */ 1113 | 1114 | .button, 1115 | .topcoat-icon-button, 1116 | .topcoat-icon-button--quiet, 1117 | .topcoat-icon-button--large, 1118 | .topcoat-icon-button--large--quiet { 1119 | position: relative; 1120 | display: inline-block; 1121 | vertical-align: top; 1122 | -moz-box-sizing: border-box; 1123 | box-sizing: border-box; 1124 | background-clip: padding-box; 1125 | padding: 0; 1126 | margin: 0; 1127 | font: inherit; 1128 | color: inherit; 1129 | background: transparent; 1130 | border: none; 1131 | cursor: default; 1132 | -webkit-user-select: none; 1133 | -moz-user-select: none; 1134 | -ms-user-select: none; 1135 | user-select: none; 1136 | text-overflow: ellipsis; 1137 | white-space: nowrap; 1138 | overflow: hidden; 1139 | text-decoration: none; 1140 | } 1141 | 1142 | .button--quiet { 1143 | background: transparent; 1144 | border: 1px solid transparent; 1145 | box-shadow: none; 1146 | } 1147 | 1148 | .button--disabled, 1149 | .topcoat-icon-button:disabled, 1150 | .topcoat-icon-button--quiet:disabled, 1151 | .topcoat-icon-button--large:disabled, 1152 | .topcoat-icon-button--large--quiet:disabled { 1153 | opacity: 0.3; 1154 | cursor: default; 1155 | pointer-events: none; 1156 | } 1157 | 1158 | /* topdoc 1159 | name: Icon Button 1160 | description: Like button, but it has an icon. 1161 | modifiers: 1162 | :active: Active state 1163 | :disabled: Disabled state 1164 | :hover: Hover state 1165 | :focus: Focused 1166 | markup: 1167 | 1170 | 1173 | tags: 1174 | - desktop 1175 | - light 1176 | - mobile 1177 | - button 1178 | - icon 1179 | */ 1180 | 1181 | .topcoat-icon-button, 1182 | .topcoat-icon-button--quiet, 1183 | .topcoat-icon-button--large, 1184 | .topcoat-icon-button--large--quiet { 1185 | padding: 0 0.25rem; 1186 | line-height: 1.313rem; 1187 | letter-spacing: 0; 1188 | color: #454545; 1189 | text-shadow: 0 1px #fff; 1190 | vertical-align: baseline; 1191 | background-color: #e5e9e8; 1192 | box-shadow: inset 0 1px #fff; 1193 | border: 1px solid #9daca9; 1194 | border-radius: 4px; 1195 | } 1196 | 1197 | .topcoat-icon-button:hover, 1198 | .topcoat-icon-button--quiet:hover, 1199 | .topcoat-icon-button--large:hover, 1200 | .topcoat-icon-button--large--quiet:hover { 1201 | background-color: #eff1f1; 1202 | } 1203 | 1204 | .topcoat-icon-button:focus, 1205 | .topcoat-icon-button--quiet:focus, 1206 | .topcoat-icon-button--quiet:hover:focus, 1207 | .topcoat-icon-button--large:focus, 1208 | .topcoat-icon-button--large--quiet:focus, 1209 | .topcoat-icon-button--large--quiet:hover:focus { 1210 | border: 1px solid #0036ff; 1211 | box-shadow: inset 0 1px rgba(255,255,255,0.36), 0 0 0 2px #6fb5f1; 1212 | outline: 0; 1213 | } 1214 | 1215 | .topcoat-icon-button:active, 1216 | .topcoat-icon-button--large:active { 1217 | border: 1px solid #9daca9; 1218 | background-color: #d2d6d6; 1219 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 1220 | } 1221 | 1222 | /* topdoc 1223 | name: Quiet Icon Button 1224 | description: Like quiet button, but it has an icon. 1225 | modifiers: 1226 | :active: Active state 1227 | :disabled: Disabled state 1228 | :hover: Hover state 1229 | :focus: Focused 1230 | markup: 1231 | 1234 | 1237 | tags: 1238 | - desktop 1239 | - light 1240 | - mobile 1241 | - button 1242 | - icon 1243 | - quiet 1244 | */ 1245 | 1246 | .topcoat-icon-button--quiet { 1247 | background: transparent; 1248 | border: 1px solid transparent; 1249 | box-shadow: none; 1250 | } 1251 | 1252 | .topcoat-icon-button--quiet:hover, 1253 | .topcoat-icon-button--large--quiet:hover { 1254 | text-shadow: 0 1px #fff; 1255 | border: 1px solid #9daca9; 1256 | box-shadow: inset 0 1px #fff; 1257 | } 1258 | 1259 | .topcoat-icon-button--quiet:active, 1260 | .topcoat-icon-button--quiet:focus:active, 1261 | .topcoat-icon-button--large--quiet:active, 1262 | .topcoat-icon-button--large--quiet:focus:active { 1263 | color: #454545; 1264 | text-shadow: 0 1px #fff; 1265 | background-color: #d2d6d6; 1266 | border: 1px solid #9daca9; 1267 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 1268 | } 1269 | 1270 | /* topdoc 1271 | name: Large Icon Button 1272 | description: Like large button, but it has an icon. 1273 | modifiers: 1274 | :active: Active state 1275 | :disabled: Disabled state 1276 | :hover: Hover state 1277 | :focus: Focused 1278 | markup: 1279 | 1282 | 1285 | tags: 1286 | - desktop 1287 | - light 1288 | - mobile 1289 | - button 1290 | - icon 1291 | - large 1292 | */ 1293 | 1294 | .topcoat-icon-button--large, 1295 | .topcoat-icon-button--large--quiet { 1296 | width: 1.688rem; 1297 | height: 1.688rem; 1298 | line-height: 1.688rem; 1299 | } 1300 | 1301 | /* topdoc 1302 | name: Large Quiet Icon Button 1303 | description: Like large button, but it has an icon and this one is quiet. 1304 | modifiers: 1305 | :active: Active state 1306 | :disabled: Disabled state 1307 | :hover: Hover state 1308 | markup: 1309 | 1312 | 1315 | tags: 1316 | - desktop 1317 | - light 1318 | - mobile 1319 | - button 1320 | - icon 1321 | - large 1322 | - quiet 1323 | */ 1324 | 1325 | .topcoat-icon-button--large--quiet { 1326 | background: transparent; 1327 | border: 1px solid transparent; 1328 | box-shadow: none; 1329 | } 1330 | 1331 | .topcoat-icon, 1332 | .topcoat-icon--large { 1333 | position: relative; 1334 | display: inline-block; 1335 | vertical-align: top; 1336 | overflow: hidden; 1337 | width: 0.81406rem; 1338 | height: 0.81406rem; 1339 | vertical-align: middle; 1340 | top: -1px; 1341 | } 1342 | 1343 | .topcoat-icon--large { 1344 | width: 1.06344rem; 1345 | height: 1.06344rem; 1346 | top: -2px; 1347 | } 1348 | 1349 | /** 1350 | * 1351 | * Copyright 2012 Adobe Systems Inc.; 1352 | * 1353 | * Licensed under the Apache License, Version 2.0 (the "License"); 1354 | * you may not use this file except in compliance with the License. 1355 | * You may obtain a copy of the License at 1356 | * 1357 | * http://www.apache.org/licenses/LICENSE-2.0 1358 | * 1359 | * Unless required by applicable law or agreed to in writing, software 1360 | * distributed under the License is distributed on an "AS IS" BASIS, 1361 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1362 | * See the License for the specific language governing permissions and 1363 | * limitations under the License. 1364 | * 1365 | */ 1366 | 1367 | /** 1368 | * 1369 | * Copyright 2012 Adobe Systems Inc.; 1370 | * 1371 | * Licensed under the Apache License, Version 2.0 (the "License"); 1372 | * you may not use this file except in compliance with the License. 1373 | * You may obtain a copy of the License at 1374 | * 1375 | * http://www.apache.org/licenses/LICENSE-2.0 1376 | * 1377 | * Unless required by applicable law or agreed to in writing, software 1378 | * distributed under the License is distributed on an "AS IS" BASIS, 1379 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1380 | * See the License for the specific language governing permissions and 1381 | * limitations under the License. 1382 | * 1383 | */ 1384 | 1385 | .input { 1386 | padding: 0; 1387 | margin: 0; 1388 | font: inherit; 1389 | color: inherit; 1390 | background: transparent; 1391 | border: none; 1392 | -moz-box-sizing: border-box; 1393 | box-sizing: border-box; 1394 | background-clip: padding-box; 1395 | vertical-align: top; 1396 | outline: none; 1397 | } 1398 | 1399 | .input:disabled { 1400 | opacity: 0.3; 1401 | cursor: default; 1402 | pointer-events: none; 1403 | } 1404 | 1405 | /** 1406 | * 1407 | * Copyright 2012 Adobe Systems Inc.; 1408 | * 1409 | * Licensed under the Apache License, Version 2.0 (the "License"); 1410 | * you may not use this file except in compliance with the License. 1411 | * You may obtain a copy of the License at 1412 | * 1413 | * http://www.apache.org/licenses/LICENSE-2.0 1414 | * 1415 | * Unless required by applicable law or agreed to in writing, software 1416 | * distributed under the License is distributed on an "AS IS" BASIS, 1417 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1418 | * See the License for the specific language governing permissions and 1419 | * limitations under the License. 1420 | * 1421 | */ 1422 | 1423 | /** 1424 | * 1425 | * Copyright 2012 Adobe Systems Inc.; 1426 | * 1427 | * Licensed under the Apache License, Version 2.0 (the "License"); 1428 | * you may not use this file except in compliance with the License. 1429 | * You may obtain a copy of the License at 1430 | * 1431 | * http://www.apache.org/licenses/LICENSE-2.0 1432 | * 1433 | * Unless required by applicable law or agreed to in writing, software 1434 | * distributed under the License is distributed on an "AS IS" BASIS, 1435 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1436 | * See the License for the specific language governing permissions and 1437 | * limitations under the License. 1438 | * 1439 | */ 1440 | 1441 | .list { 1442 | padding: 0; 1443 | margin: 0; 1444 | font: inherit; 1445 | color: inherit; 1446 | background: transparent; 1447 | border: none; 1448 | cursor: default; 1449 | -webkit-user-select: none; 1450 | -moz-user-select: none; 1451 | -ms-user-select: none; 1452 | user-select: none; 1453 | overflow: auto; 1454 | -webkit-overflow-scrolling: touch; 1455 | } 1456 | 1457 | .list__header { 1458 | margin: 0; 1459 | } 1460 | 1461 | .list__container { 1462 | padding: 0; 1463 | margin: 0; 1464 | list-style-type: none; 1465 | } 1466 | 1467 | .list__item { 1468 | margin: 0; 1469 | padding: 0; 1470 | } 1471 | 1472 | /** 1473 | * 1474 | * Copyright 2012 Adobe Systems Inc.; 1475 | * 1476 | * Licensed under the Apache License, Version 2.0 (the "License"); 1477 | * you may not use this file except in compliance with the License. 1478 | * You may obtain a copy of the License at 1479 | * 1480 | * http://www.apache.org/licenses/LICENSE-2.0 1481 | * 1482 | * Unless required by applicable law or agreed to in writing, software 1483 | * distributed under the License is distributed on an "AS IS" BASIS, 1484 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1485 | * See the License for the specific language governing permissions and 1486 | * limitations under the License. 1487 | * 1488 | */ 1489 | 1490 | /** 1491 | * 1492 | * Copyright 2012 Adobe Systems Inc.; 1493 | * 1494 | * Licensed under the Apache License, Version 2.0 (the "License"); 1495 | * you may not use this file except in compliance with the License. 1496 | * You may obtain a copy of the License at 1497 | * 1498 | * http://www.apache.org/licenses/LICENSE-2.0 1499 | * 1500 | * Unless required by applicable law or agreed to in writing, software 1501 | * distributed under the License is distributed on an "AS IS" BASIS, 1502 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1503 | * See the License for the specific language governing permissions and 1504 | * limitations under the License. 1505 | * 1506 | */ 1507 | 1508 | .navigation-bar { 1509 | -moz-box-sizing: border-box; 1510 | box-sizing: border-box; 1511 | background-clip: padding-box; 1512 | white-space: nowrap; 1513 | overflow: hidden; 1514 | word-spacing: 0; 1515 | padding: 0; 1516 | margin: 0; 1517 | font: inherit; 1518 | color: inherit; 1519 | background: transparent; 1520 | border: none; 1521 | cursor: default; 1522 | -webkit-user-select: none; 1523 | -moz-user-select: none; 1524 | -ms-user-select: none; 1525 | user-select: none; 1526 | } 1527 | 1528 | .navigation-bar__item { 1529 | -moz-box-sizing: border-box; 1530 | box-sizing: border-box; 1531 | background-clip: padding-box; 1532 | position: relative; 1533 | display: inline-block; 1534 | vertical-align: top; 1535 | padding: 0; 1536 | margin: 0; 1537 | font: inherit; 1538 | color: inherit; 1539 | background: transparent; 1540 | border: none; 1541 | } 1542 | 1543 | .navigation-bar__title { 1544 | padding: 0; 1545 | margin: 0; 1546 | font: inherit; 1547 | color: inherit; 1548 | background: transparent; 1549 | border: none; 1550 | text-overflow: ellipsis; 1551 | white-space: nowrap; 1552 | overflow: hidden; 1553 | } 1554 | 1555 | /* 1556 | Copyright 2012 Adobe Systems Inc.; 1557 | Licensed under the Apache License, Version 2.0 (the "License"); 1558 | you may not use this file except in compliance with the License. 1559 | You may obtain a copy of the License at 1560 | 1561 | http://www.apache.org/licenses/LICENSE-2.0 1562 | 1563 | Unless required by applicable law or agreed to in writing, software 1564 | distributed under the License is distributed on an "AS IS" BASIS, 1565 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1566 | See the License for the specific language governing permissions and 1567 | limitations under the License. 1568 | */ 1569 | 1570 | /** 1571 | * 1572 | * Copyright 2012 Adobe Systems Inc.; 1573 | * 1574 | * Licensed under the Apache License, Version 2.0 (the "License"); 1575 | * you may not use this file except in compliance with the License. 1576 | * You may obtain a copy of the License at 1577 | * 1578 | * http://www.apache.org/licenses/LICENSE-2.0 1579 | * 1580 | * Unless required by applicable law or agreed to in writing, software 1581 | * distributed under the License is distributed on an "AS IS" BASIS, 1582 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1583 | * See the License for the specific language governing permissions and 1584 | * limitations under the License. 1585 | * 1586 | */ 1587 | 1588 | .notification { 1589 | position: relative; 1590 | display: inline-block; 1591 | vertical-align: top; 1592 | -moz-box-sizing: border-box; 1593 | box-sizing: border-box; 1594 | background-clip: padding-box; 1595 | padding: 0; 1596 | margin: 0; 1597 | font: inherit; 1598 | color: inherit; 1599 | background: transparent; 1600 | border: none; 1601 | cursor: default; 1602 | -webkit-user-select: none; 1603 | -moz-user-select: none; 1604 | -ms-user-select: none; 1605 | user-select: none; 1606 | text-overflow: ellipsis; 1607 | white-space: nowrap; 1608 | overflow: hidden; 1609 | text-decoration: none; 1610 | } 1611 | 1612 | /* 1613 | Copyright 2012 Adobe Systems Inc.; 1614 | Licensed under the Apache License, Version 2.0 (the "License"); 1615 | you may not use this file except in compliance with the License. 1616 | You may obtain a copy of the License at 1617 | 1618 | http://www.apache.org/licenses/LICENSE-2.0 1619 | 1620 | Unless required by applicable law or agreed to in writing, software 1621 | distributed under the License is distributed on an "AS IS" BASIS, 1622 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1623 | See the License for the specific language governing permissions and 1624 | limitations under the License. 1625 | */ 1626 | 1627 | /** 1628 | * 1629 | * Copyright 2012 Adobe Systems Inc.; 1630 | * 1631 | * Licensed under the Apache License, Version 2.0 (the "License"); 1632 | * you may not use this file except in compliance with the License. 1633 | * You may obtain a copy of the License at 1634 | * 1635 | * http://www.apache.org/licenses/LICENSE-2.0 1636 | * 1637 | * Unless required by applicable law or agreed to in writing, software 1638 | * distributed under the License is distributed on an "AS IS" BASIS, 1639 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1640 | * See the License for the specific language governing permissions and 1641 | * limitations under the License. 1642 | * 1643 | */ 1644 | 1645 | .notification, 1646 | .topcoat-notification { 1647 | position: relative; 1648 | display: inline-block; 1649 | vertical-align: top; 1650 | -moz-box-sizing: border-box; 1651 | box-sizing: border-box; 1652 | background-clip: padding-box; 1653 | padding: 0; 1654 | margin: 0; 1655 | font: inherit; 1656 | color: inherit; 1657 | background: transparent; 1658 | border: none; 1659 | cursor: default; 1660 | -webkit-user-select: none; 1661 | -moz-user-select: none; 1662 | -ms-user-select: none; 1663 | user-select: none; 1664 | text-overflow: ellipsis; 1665 | white-space: nowrap; 1666 | overflow: hidden; 1667 | text-decoration: none; 1668 | } 1669 | 1670 | /* topdoc 1671 | name: Notification 1672 | description: Notification badge 1673 | markup: 1674 | 1 1675 | tags: 1676 | - desktop 1677 | - light 1678 | - mobile 1679 | - notification 1680 | */ 1681 | 1682 | .topcoat-notification { 1683 | padding: 0.15em 0.5em 0.2em; 1684 | border-radius: 2px; 1685 | background-color: #ec514e; 1686 | color: #fff; 1687 | } 1688 | 1689 | /** 1690 | * 1691 | * Copyright 2012 Adobe Systems Inc.; 1692 | * 1693 | * Licensed under the Apache License, Version 2.0 (the "License"); 1694 | * you may not use this file except in compliance with the License. 1695 | * You may obtain a copy of the License at 1696 | * 1697 | * http://www.apache.org/licenses/LICENSE-2.0 1698 | * 1699 | * Unless required by applicable law or agreed to in writing, software 1700 | * distributed under the License is distributed on an "AS IS" BASIS, 1701 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1702 | * See the License for the specific language governing permissions and 1703 | * limitations under the License. 1704 | * 1705 | */ 1706 | 1707 | /** 1708 | * 1709 | * Copyright 2012 Adobe Systems Inc.; 1710 | * 1711 | * Licensed under the Apache License, Version 2.0 (the "License"); 1712 | * you may not use this file except in compliance with the License. 1713 | * You may obtain a copy of the License at 1714 | * 1715 | * http://www.apache.org/licenses/LICENSE-2.0 1716 | * 1717 | * Unless required by applicable law or agreed to in writing, software 1718 | * distributed under the License is distributed on an "AS IS" BASIS, 1719 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1720 | * See the License for the specific language governing permissions and 1721 | * limitations under the License. 1722 | * 1723 | */ 1724 | 1725 | input[type="radio"] { 1726 | position: absolute; 1727 | overflow: hidden; 1728 | padding: 0; 1729 | border: 0; 1730 | opacity: 0.001; 1731 | z-index: 1; 1732 | vertical-align: top; 1733 | outline: none; 1734 | } 1735 | 1736 | .radio-button { 1737 | -moz-box-sizing: border-box; 1738 | box-sizing: border-box; 1739 | background-clip: padding-box; 1740 | position: relative; 1741 | display: inline-block; 1742 | vertical-align: top; 1743 | cursor: default; 1744 | -webkit-user-select: none; 1745 | -moz-user-select: none; 1746 | -ms-user-select: none; 1747 | user-select: none; 1748 | } 1749 | 1750 | .radio-button__label { 1751 | position: relative; 1752 | display: inline-block; 1753 | vertical-align: top; 1754 | cursor: default; 1755 | -webkit-user-select: none; 1756 | -moz-user-select: none; 1757 | -ms-user-select: none; 1758 | user-select: none; 1759 | } 1760 | 1761 | .radio-button:before, 1762 | .radio-button:after { 1763 | content: ''; 1764 | position: absolute; 1765 | border-radius: 100%; 1766 | } 1767 | 1768 | .radio-button:after { 1769 | top: 50%; 1770 | left: 50%; 1771 | -webkit-transform: translate(-50%, -50%); 1772 | -ms-transform: translate(-50%, -50%); 1773 | transform: translate(-50%, -50%); 1774 | } 1775 | 1776 | .radio-button:before { 1777 | -moz-box-sizing: border-box; 1778 | box-sizing: border-box; 1779 | background-clip: padding-box; 1780 | } 1781 | 1782 | .radio-button--disabled { 1783 | opacity: 0.3; 1784 | cursor: default; 1785 | pointer-events: none; 1786 | } 1787 | 1788 | /** 1789 | * 1790 | * Copyright 2012 Adobe Systems Inc.; 1791 | * 1792 | * Licensed under the Apache License, Version 2.0 (the "License"); 1793 | * you may not use this file except in compliance with the License. 1794 | * You may obtain a copy of the License at 1795 | * 1796 | * http://www.apache.org/licenses/LICENSE-2.0 1797 | * 1798 | * Unless required by applicable law or agreed to in writing, software 1799 | * distributed under the License is distributed on an "AS IS" BASIS, 1800 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1801 | * See the License for the specific language governing permissions and 1802 | * limitations under the License. 1803 | * 1804 | */ 1805 | 1806 | /** 1807 | * 1808 | * Copyright 2012 Adobe Systems Inc.; 1809 | * 1810 | * Licensed under the Apache License, Version 2.0 (the "License"); 1811 | * you may not use this file except in compliance with the License. 1812 | * You may obtain a copy of the License at 1813 | * 1814 | * http://www.apache.org/licenses/LICENSE-2.0 1815 | * 1816 | * Unless required by applicable law or agreed to in writing, software 1817 | * distributed under the License is distributed on an "AS IS" BASIS, 1818 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1819 | * See the License for the specific language governing permissions and 1820 | * limitations under the License. 1821 | * 1822 | */ 1823 | 1824 | input[type="radio"] { 1825 | position: absolute; 1826 | overflow: hidden; 1827 | padding: 0; 1828 | border: 0; 1829 | opacity: 0.001; 1830 | z-index: 1; 1831 | vertical-align: top; 1832 | outline: none; 1833 | } 1834 | 1835 | .radio-button, 1836 | .topcoat-radio-button__checkmark { 1837 | -moz-box-sizing: border-box; 1838 | box-sizing: border-box; 1839 | background-clip: padding-box; 1840 | position: relative; 1841 | display: inline-block; 1842 | vertical-align: top; 1843 | cursor: default; 1844 | -webkit-user-select: none; 1845 | -moz-user-select: none; 1846 | -ms-user-select: none; 1847 | user-select: none; 1848 | } 1849 | 1850 | .radio-button__label, 1851 | .topcoat-radio-button { 1852 | position: relative; 1853 | display: inline-block; 1854 | vertical-align: top; 1855 | cursor: default; 1856 | -webkit-user-select: none; 1857 | -moz-user-select: none; 1858 | -ms-user-select: none; 1859 | user-select: none; 1860 | } 1861 | 1862 | .radio-button:before, 1863 | .radio-button:after, 1864 | .topcoat-radio-button__checkmark:before, 1865 | .topcoat-radio-button__checkmark:after { 1866 | content: ''; 1867 | position: absolute; 1868 | border-radius: 100%; 1869 | } 1870 | 1871 | .radio-button:after, 1872 | .topcoat-radio-button__checkmark:after { 1873 | top: 50%; 1874 | left: 50%; 1875 | -webkit-transform: translate(-50%, -50%); 1876 | -ms-transform: translate(-50%, -50%); 1877 | transform: translate(-50%, -50%); 1878 | } 1879 | 1880 | .radio-button:before, 1881 | .topcoat-radio-button__checkmark:before { 1882 | -moz-box-sizing: border-box; 1883 | box-sizing: border-box; 1884 | background-clip: padding-box; 1885 | } 1886 | 1887 | .radio-button--disabled, 1888 | input[type="radio"]:disabled + .topcoat-radio-button__checkmark { 1889 | opacity: 0.3; 1890 | cursor: default; 1891 | pointer-events: none; 1892 | } 1893 | 1894 | /* topdoc 1895 | name: Radio Button 1896 | description: A button that can play music, but usually just plays ads. 1897 | modifiers: 1898 | markup: 1899 | 1900 | 1904 |
1905 |
1906 | 1907 | 1912 |
1913 |
1914 | 1915 | 1920 |
1921 |
1922 | 1923 | 1928 | examples: 1929 | Mobile Radio Button: http://codepen.io/Topcoat/pen/HDcJj 1930 | tags: 1931 | - desktop 1932 | - light 1933 | - mobile 1934 | - Radio 1935 | */ 1936 | 1937 | input[type="radio"] { 1938 | height: 1.063rem; 1939 | width: 1.063rem; 1940 | margin-top: 0; 1941 | margin-right: -1.063rem; 1942 | margin-bottom: -1.063rem; 1943 | margin-left: 0; 1944 | } 1945 | 1946 | input[type="radio"]:checked + .topcoat-radio-button__checkmark:after { 1947 | opacity: 1; 1948 | } 1949 | 1950 | .topcoat-radio-button { 1951 | color: #454545; 1952 | line-height: 1.063rem; 1953 | } 1954 | 1955 | .topcoat-radio-button__checkmark:before { 1956 | width: 1.063rem; 1957 | height: 1.063rem; 1958 | background: #e5e9e8; 1959 | border: 1px solid #9daca9; 1960 | box-shadow: inset 0 1px #fff; 1961 | } 1962 | 1963 | .topcoat-radio-button__checkmark { 1964 | position: relative; 1965 | width: 1.063rem; 1966 | height: 1.063rem; 1967 | } 1968 | 1969 | .topcoat-radio-button__checkmark:after { 1970 | opacity: 0; 1971 | width: 0.313rem; 1972 | height: 0.313rem; 1973 | background: #454545; 1974 | border: 1px solid rgba(0,0,0,0.1); 1975 | box-shadow: 0 1px rgba(255,255,255,0.5); 1976 | -webkit-transform: none; 1977 | -ms-transform: none; 1978 | transform: none; 1979 | top: 0.313rem; 1980 | left: 0.313rem; 1981 | } 1982 | 1983 | input[type="radio"]:focus + .topcoat-radio-button__checkmark:before { 1984 | border: 1px solid #0036ff; 1985 | box-shadow: inset 0 1px rgba(255,255,255,0.36), 0 0 0 2px #6fb5f1; 1986 | } 1987 | 1988 | input[type="radio"]:active + .topcoat-radio-button__checkmark:before { 1989 | border: 1px solid #9daca9; 1990 | background-color: #d2d6d6; 1991 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 1992 | } 1993 | 1994 | input[type="radio"]:disabled:active + .topcoat-radio-button__checkmark:before { 1995 | border: 1px solid #9daca9; 1996 | background: #e5e9e8; 1997 | box-shadow: inset 0 1px #fff; 1998 | } 1999 | 2000 | /* 2001 | Copyright 2012 Adobe Systems Inc.; 2002 | Licensed under the Apache License, Version 2.0 (the "License"); 2003 | you may not use this file except in compliance with the License. 2004 | You may obtain a copy of the License at 2005 | 2006 | http://www.apache.org/licenses/LICENSE-2.0 2007 | 2008 | Unless required by applicable law or agreed to in writing, software 2009 | distributed under the License is distributed on an "AS IS" BASIS, 2010 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2011 | See the License for the specific language governing permissions and 2012 | limitations under the License. 2013 | */ 2014 | 2015 | /* 2016 | Copyright 2012 Adobe Systems Inc.; 2017 | Licensed under the Apache License, Version 2.0 (the "License"); 2018 | you may not use this file except in compliance with the License. 2019 | You may obtain a copy of the License at 2020 | 2021 | http://www.apache.org/licenses/LICENSE-2.0 2022 | 2023 | Unless required by applicable law or agreed to in writing, software 2024 | distributed under the License is distributed on an "AS IS" BASIS, 2025 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2026 | See the License for the specific language governing permissions and 2027 | limitations under the License. 2028 | */ 2029 | 2030 | .range { 2031 | padding: 0; 2032 | margin: 0; 2033 | font: inherit; 2034 | color: inherit; 2035 | background: transparent; 2036 | border: none; 2037 | -moz-box-sizing: border-box; 2038 | box-sizing: border-box; 2039 | background-clip: padding-box; 2040 | vertical-align: top; 2041 | outline: none; 2042 | -webkit-appearance: none; 2043 | } 2044 | 2045 | .range__thumb { 2046 | cursor: pointer; 2047 | } 2048 | 2049 | .range__thumb--webkit { 2050 | cursor: pointer; 2051 | -webkit-appearance: none; 2052 | } 2053 | 2054 | .range:disabled { 2055 | opacity: 0.3; 2056 | cursor: default; 2057 | pointer-events: none; 2058 | } 2059 | 2060 | /* 2061 | Copyright 2012 Adobe Systems Inc.; 2062 | Licensed under the Apache License, Version 2.0 (the "License"); 2063 | you may not use this file except in compliance with the License. 2064 | You may obtain a copy of the License at 2065 | 2066 | http://www.apache.org/licenses/LICENSE-2.0 2067 | 2068 | Unless required by applicable law or agreed to in writing, software 2069 | distributed under the License is distributed on an "AS IS" BASIS, 2070 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2071 | See the License for the specific language governing permissions and 2072 | limitations under the License. 2073 | */ 2074 | 2075 | /* 2076 | Copyright 2012 Adobe Systems Inc.; 2077 | Licensed under the Apache License, Version 2.0 (the "License"); 2078 | you may not use this file except in compliance with the License. 2079 | You may obtain a copy of the License at 2080 | 2081 | http://www.apache.org/licenses/LICENSE-2.0 2082 | 2083 | Unless required by applicable law or agreed to in writing, software 2084 | distributed under the License is distributed on an "AS IS" BASIS, 2085 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2086 | See the License for the specific language governing permissions and 2087 | limitations under the License. 2088 | */ 2089 | 2090 | .range, 2091 | .topcoat-range { 2092 | padding: 0; 2093 | margin: 0; 2094 | font: inherit; 2095 | color: inherit; 2096 | background: transparent; 2097 | border: none; 2098 | -moz-box-sizing: border-box; 2099 | box-sizing: border-box; 2100 | background-clip: padding-box; 2101 | vertical-align: top; 2102 | outline: none; 2103 | -webkit-appearance: none; 2104 | } 2105 | 2106 | .range__thumb, 2107 | .topcoat-range::-moz-range-thumb { 2108 | cursor: pointer; 2109 | } 2110 | 2111 | .range__thumb--webkit, 2112 | .topcoat-range::-webkit-slider-thumb { 2113 | cursor: pointer; 2114 | -webkit-appearance: none; 2115 | } 2116 | 2117 | .range:disabled, 2118 | .topcoat-range:disabled { 2119 | opacity: 0.3; 2120 | cursor: default; 2121 | pointer-events: none; 2122 | } 2123 | 2124 | /* topdoc 2125 | name: Range 2126 | description: Range input 2127 | modifiers: 2128 | :active: Active state 2129 | :disabled: Disabled state 2130 | :hover: Hover state 2131 | :focus: Focused 2132 | markup: 2133 | 2134 | 2135 | examples: 2136 | mobile range: http://codepen.io/Topcoat/pen/BskEn 2137 | tags: 2138 | - desktop 2139 | - mobile 2140 | - range 2141 | */ 2142 | 2143 | .topcoat-range { 2144 | border-radius: 4px; 2145 | border: 1px solid #9daca9; 2146 | background-color: #d6dcdb; 2147 | height: 0.5rem; 2148 | border-radius: 15px; 2149 | } 2150 | 2151 | .topcoat-range::-moz-range-track { 2152 | border-radius: 4px; 2153 | border: 1px solid #9daca9; 2154 | background-color: #d6dcdb; 2155 | height: 0.5rem; 2156 | border-radius: 15px; 2157 | } 2158 | 2159 | .topcoat-range::-webkit-slider-thumb { 2160 | height: 1.313rem; 2161 | width: 0.75rem; 2162 | background-color: #e5e9e8; 2163 | border: 1px solid #9daca9; 2164 | border-radius: 4px; 2165 | box-shadow: inset 0 1px #fff; 2166 | } 2167 | 2168 | .topcoat-range::-moz-range-thumb { 2169 | height: 1.313rem; 2170 | width: 0.75rem; 2171 | background-color: #e5e9e8; 2172 | border: 1px solid #9daca9; 2173 | border-radius: 4px; 2174 | box-shadow: inset 0 1px #fff; 2175 | } 2176 | 2177 | .topcoat-range:focus::-webkit-slider-thumb { 2178 | border: 1px solid #0036ff; 2179 | box-shadow: inset 0 1px rgba(255,255,255,0.36), 0 0 0 2px #6fb5f1; 2180 | } 2181 | 2182 | .topcoat-range:focus::-moz-range-thumb { 2183 | border: 1px solid #0036ff; 2184 | box-shadow: inset 0 1px rgba(255,255,255,0.36), 0 0 0 2px #6fb5f1; 2185 | } 2186 | 2187 | .topcoat-range:active::-webkit-slider-thumb { 2188 | border: 1px solid #9daca9; 2189 | box-shadow: inset 0 1px #fff; 2190 | } 2191 | 2192 | .topcoat-range:active::-moz-range-thumb { 2193 | border: 1px solid #9daca9; 2194 | box-shadow: inset 0 1px #fff; 2195 | } 2196 | 2197 | /** 2198 | * 2199 | * Copyright 2012 Adobe Systems Inc.; 2200 | * 2201 | * Licensed under the Apache License, Version 2.0 (the "License"); 2202 | * you may not use this file except in compliance with the License. 2203 | * You may obtain a copy of the License at 2204 | * 2205 | * http://www.apache.org/licenses/LICENSE-2.0 2206 | * 2207 | * Unless required by applicable law or agreed to in writing, software 2208 | * distributed under the License is distributed on an "AS IS" BASIS, 2209 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2210 | * See the License for the specific language governing permissions and 2211 | * limitations under the License. 2212 | * 2213 | */ 2214 | 2215 | /** 2216 | * 2217 | * Copyright 2012 Adobe Systems Inc.; 2218 | * 2219 | * Licensed under the Apache License, Version 2.0 (the "License"); 2220 | * you may not use this file except in compliance with the License. 2221 | * You may obtain a copy of the License at 2222 | * 2223 | * http://www.apache.org/licenses/LICENSE-2.0 2224 | * 2225 | * Unless required by applicable law or agreed to in writing, software 2226 | * distributed under the License is distributed on an "AS IS" BASIS, 2227 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2228 | * See the License for the specific language governing permissions and 2229 | * limitations under the License. 2230 | * 2231 | */ 2232 | 2233 | .search-input { 2234 | padding: 0; 2235 | margin: 0; 2236 | font: inherit; 2237 | color: inherit; 2238 | background: transparent; 2239 | border: none; 2240 | -moz-box-sizing: border-box; 2241 | box-sizing: border-box; 2242 | background-clip: padding-box; 2243 | vertical-align: top; 2244 | outline: none; 2245 | -webkit-appearance: none; 2246 | } 2247 | 2248 | input[type="search"]::-webkit-search-cancel-button { 2249 | -webkit-appearance: none; 2250 | } 2251 | 2252 | .search-input:disabled { 2253 | opacity: 0.3; 2254 | cursor: default; 2255 | pointer-events: none; 2256 | } 2257 | 2258 | /** 2259 | * 2260 | * Copyright 2012 Adobe Systems Inc.; 2261 | * 2262 | * Licensed under the Apache License, Version 2.0 (the "License"); 2263 | * you may not use this file except in compliance with the License. 2264 | * You may obtain a copy of the License at 2265 | * 2266 | * http://www.apache.org/licenses/LICENSE-2.0 2267 | * 2268 | * Unless required by applicable law or agreed to in writing, software 2269 | * distributed under the License is distributed on an "AS IS" BASIS, 2270 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2271 | * See the License for the specific language governing permissions and 2272 | * limitations under the License. 2273 | * 2274 | */ 2275 | 2276 | /** 2277 | * 2278 | * Copyright 2012 Adobe Systems Inc.; 2279 | * 2280 | * Licensed under the Apache License, Version 2.0 (the "License"); 2281 | * you may not use this file except in compliance with the License. 2282 | * You may obtain a copy of the License at 2283 | * 2284 | * http://www.apache.org/licenses/LICENSE-2.0 2285 | * 2286 | * Unless required by applicable law or agreed to in writing, software 2287 | * distributed under the License is distributed on an "AS IS" BASIS, 2288 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2289 | * See the License for the specific language governing permissions and 2290 | * limitations under the License. 2291 | * 2292 | */ 2293 | 2294 | .search-input, 2295 | .topcoat-search-input, 2296 | .topcoat-search-input--large { 2297 | padding: 0; 2298 | margin: 0; 2299 | font: inherit; 2300 | color: inherit; 2301 | background: transparent; 2302 | border: none; 2303 | -moz-box-sizing: border-box; 2304 | box-sizing: border-box; 2305 | background-clip: padding-box; 2306 | vertical-align: top; 2307 | outline: none; 2308 | -webkit-appearance: none; 2309 | } 2310 | 2311 | input[type="search"]::-webkit-search-cancel-button { 2312 | -webkit-appearance: none; 2313 | } 2314 | 2315 | .search-input:disabled, 2316 | .topcoat-search-input:disabled, 2317 | .topcoat-search-input--large:disabled { 2318 | opacity: 0.3; 2319 | cursor: default; 2320 | pointer-events: none; 2321 | } 2322 | 2323 | /* topdoc 2324 | name: Search Input 2325 | description: A text input designed for searching. 2326 | modifiers: 2327 | :disabled: Disabled state 2328 | markup: 2329 | 2330 | 2331 | tags: 2332 | - desktop 2333 | - light 2334 | - mobile 2335 | - text 2336 | - input 2337 | - search 2338 | - form 2339 | */ 2340 | 2341 | .topcoat-search-input, 2342 | .topcoat-search-input--large { 2343 | line-height: 1.313rem; 2344 | height: 1.313rem; 2345 | font-size: 12px; 2346 | border: 1px solid #9daca9; 2347 | background-color: #fff; 2348 | box-shadow: inset 0 1px 0 rgba(0,0,0,0.23); 2349 | color: #454545; 2350 | padding: 0 0 0 1.3rem; 2351 | border-radius: 15px; 2352 | background-image: url("../img/search.svg"); 2353 | background-position: 1rem center; 2354 | background-repeat: no-repeat; 2355 | background-size: 12px; 2356 | } 2357 | 2358 | .topcoat-search-input:focus, 2359 | .topcoat-search-input--large:focus { 2360 | background-color: #fff; 2361 | color: #454545; 2362 | border: 1px solid #0036ff; 2363 | box-shadow: inset 0 1px 0 rgba(0,0,0,0.23), 0 0 0 2px #6fb5f1; 2364 | } 2365 | 2366 | .topcoat-search-input::-webkit-search-cancel-button, 2367 | .topcoat-search-input::-webkit-search-decoration, 2368 | .topcoat-search-input--large::-webkit-search-cancel-button, 2369 | .topcoat-search-input--large::-webkit-search-decoration { 2370 | margin-right: 5px; 2371 | } 2372 | 2373 | .topcoat-search-input:focus::-webkit-input-placeholder, 2374 | .topcoat-search-input:focus::-webkit-input-placeholder { 2375 | color: #c6c8c8; 2376 | } 2377 | 2378 | .topcoat-search-input:disabled::-webkit-input-placeholder { 2379 | color: #454545; 2380 | } 2381 | 2382 | .topcoat-search-input:disabled::-moz-placeholder { 2383 | color: #454545; 2384 | } 2385 | 2386 | .topcoat-search-input:disabled:-ms-input-placeholder { 2387 | color: #454545; 2388 | } 2389 | 2390 | /* topdoc 2391 | name: Large Search Input 2392 | description: A large text input designed for searching. 2393 | modifiers: 2394 | :disabled: Disabled state 2395 | markup: 2396 | 2397 | 2398 | tags: 2399 | - desktop 2400 | - light 2401 | - mobile 2402 | - text 2403 | - input 2404 | - search 2405 | - form 2406 | - large 2407 | */ 2408 | 2409 | .topcoat-search-input--large { 2410 | line-height: 1.688rem; 2411 | height: 1.688rem; 2412 | font-size: 0.875rem; 2413 | font-weight: 400; 2414 | padding: 0 0 0 1.8rem; 2415 | border-radius: 25px; 2416 | background-position: 1.2rem center; 2417 | background-size: 0.875rem; 2418 | } 2419 | 2420 | .topcoat-search-input--large:disabled { 2421 | color: #454545; 2422 | } 2423 | 2424 | .topcoat-search-input--large:disabled::-webkit-input-placeholder { 2425 | color: #454545; 2426 | } 2427 | 2428 | .topcoat-search-input--large:disabled::-moz-placeholder { 2429 | color: #454545; 2430 | } 2431 | 2432 | .topcoat-search-input--large:disabled:-ms-input-placeholder { 2433 | color: #454545; 2434 | } 2435 | 2436 | /** 2437 | * 2438 | * Copyright 2012 Adobe Systems Inc.; 2439 | * 2440 | * Licensed under the Apache License, Version 2.0 (the "License"); 2441 | * you may not use this file except in compliance with the License. 2442 | * You may obtain a copy of the License at 2443 | * 2444 | * http://www.apache.org/licenses/LICENSE-2.0 2445 | * 2446 | * Unless required by applicable law or agreed to in writing, software 2447 | * distributed under the License is distributed on an "AS IS" BASIS, 2448 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2449 | * See the License for the specific language governing permissions and 2450 | * limitations under the License. 2451 | * 2452 | */ 2453 | 2454 | /** 2455 | * 2456 | * Copyright 2012 Adobe Systems Inc.; 2457 | * 2458 | * Licensed under the Apache License, Version 2.0 (the "License"); 2459 | * you may not use this file except in compliance with the License. 2460 | * You may obtain a copy of the License at 2461 | * 2462 | * http://www.apache.org/licenses/LICENSE-2.0 2463 | * 2464 | * Unless required by applicable law or agreed to in writing, software 2465 | * distributed under the License is distributed on an "AS IS" BASIS, 2466 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2467 | * See the License for the specific language governing permissions and 2468 | * limitations under the License. 2469 | * 2470 | */ 2471 | 2472 | .switch { 2473 | position: relative; 2474 | display: inline-block; 2475 | vertical-align: top; 2476 | -moz-box-sizing: border-box; 2477 | box-sizing: border-box; 2478 | background-clip: padding-box; 2479 | } 2480 | 2481 | .switch__input { 2482 | position: absolute; 2483 | overflow: hidden; 2484 | padding: 0; 2485 | border: 0; 2486 | opacity: 0.001; 2487 | z-index: 1; 2488 | vertical-align: top; 2489 | outline: none; 2490 | } 2491 | 2492 | .switch__toggle { 2493 | position: relative; 2494 | display: inline-block; 2495 | vertical-align: top; 2496 | -moz-box-sizing: border-box; 2497 | box-sizing: border-box; 2498 | background-clip: padding-box; 2499 | padding: 0; 2500 | margin: 0; 2501 | font: inherit; 2502 | color: inherit; 2503 | background: transparent; 2504 | border: none; 2505 | cursor: default; 2506 | -webkit-user-select: none; 2507 | -moz-user-select: none; 2508 | -ms-user-select: none; 2509 | user-select: none; 2510 | } 2511 | 2512 | .switch__toggle:before, 2513 | .switch__toggle:after { 2514 | content: ''; 2515 | position: absolute; 2516 | z-index: -1; 2517 | -moz-box-sizing: border-box; 2518 | box-sizing: border-box; 2519 | background-clip: padding-box; 2520 | } 2521 | 2522 | .switch--disabled { 2523 | opacity: 0.3; 2524 | cursor: default; 2525 | pointer-events: none; 2526 | } 2527 | 2528 | /** 2529 | * 2530 | * Copyright 2012 Adobe Systems Inc.; 2531 | * 2532 | * Licensed under the Apache License, Version 2.0 (the "License"); 2533 | * you may not use this file except in compliance with the License. 2534 | * You may obtain a copy of the License at 2535 | * 2536 | * http://www.apache.org/licenses/LICENSE-2.0 2537 | * 2538 | * Unless required by applicable law or agreed to in writing, software 2539 | * distributed under the License is distributed on an "AS IS" BASIS, 2540 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2541 | * See the License for the specific language governing permissions and 2542 | * limitations under the License. 2543 | * 2544 | */ 2545 | 2546 | /** 2547 | * 2548 | * Copyright 2012 Adobe Systems Inc.; 2549 | * 2550 | * Licensed under the Apache License, Version 2.0 (the "License"); 2551 | * you may not use this file except in compliance with the License. 2552 | * You may obtain a copy of the License at 2553 | * 2554 | * http://www.apache.org/licenses/LICENSE-2.0 2555 | * 2556 | * Unless required by applicable law or agreed to in writing, software 2557 | * distributed under the License is distributed on an "AS IS" BASIS, 2558 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2559 | * See the License for the specific language governing permissions and 2560 | * limitations under the License. 2561 | * 2562 | */ 2563 | 2564 | .switch, 2565 | .topcoat-switch { 2566 | position: relative; 2567 | display: inline-block; 2568 | vertical-align: top; 2569 | -moz-box-sizing: border-box; 2570 | box-sizing: border-box; 2571 | background-clip: padding-box; 2572 | } 2573 | 2574 | .switch__input, 2575 | .topcoat-switch__input { 2576 | position: absolute; 2577 | overflow: hidden; 2578 | padding: 0; 2579 | border: 0; 2580 | opacity: 0.001; 2581 | z-index: 1; 2582 | vertical-align: top; 2583 | outline: none; 2584 | } 2585 | 2586 | .switch__toggle, 2587 | .topcoat-switch__toggle { 2588 | position: relative; 2589 | display: inline-block; 2590 | vertical-align: top; 2591 | -moz-box-sizing: border-box; 2592 | box-sizing: border-box; 2593 | background-clip: padding-box; 2594 | padding: 0; 2595 | margin: 0; 2596 | font: inherit; 2597 | color: inherit; 2598 | background: transparent; 2599 | border: none; 2600 | cursor: default; 2601 | -webkit-user-select: none; 2602 | -moz-user-select: none; 2603 | -ms-user-select: none; 2604 | user-select: none; 2605 | } 2606 | 2607 | .switch__toggle:before, 2608 | .switch__toggle:after, 2609 | .topcoat-switch__toggle:before, 2610 | .topcoat-switch__toggle:after { 2611 | content: ''; 2612 | position: absolute; 2613 | z-index: -1; 2614 | -moz-box-sizing: border-box; 2615 | box-sizing: border-box; 2616 | background-clip: padding-box; 2617 | } 2618 | 2619 | .switch--disabled, 2620 | .topcoat-switch__input:disabled + .topcoat-switch__toggle { 2621 | opacity: 0.3; 2622 | cursor: default; 2623 | pointer-events: none; 2624 | } 2625 | 2626 | /* topdoc 2627 | name: Switch 2628 | description: Default skin for Topcoat switch 2629 | modifiers: 2630 | :focus: Focus state 2631 | :disabled: Disabled state 2632 | markup: 2633 | 2637 |
2638 |
2639 | 2643 |
2644 |
2645 | 2649 | examples: 2650 | mobile switch: http://codepen.io/Topcoat/pen/upxds 2651 | tags: 2652 | - desktop 2653 | - light 2654 | - mobile 2655 | - switch 2656 | */ 2657 | 2658 | .topcoat-switch { 2659 | font-size: 12px; 2660 | padding: 0 0.563rem; 2661 | border-radius: 4px; 2662 | border: 1px solid #9daca9; 2663 | overflow: hidden; 2664 | width: 3.5rem; 2665 | } 2666 | 2667 | .topcoat-switch__toggle:before, 2668 | .topcoat-switch__toggle:after { 2669 | top: -1px; 2670 | width: 2.6rem; 2671 | } 2672 | 2673 | .topcoat-switch__toggle:before { 2674 | content: 'ON'; 2675 | color: #288edf; 2676 | background-color: #e5f1fb; 2677 | right: 0.8rem; 2678 | padding-left: 0.75rem; 2679 | } 2680 | 2681 | .topcoat-switch__toggle { 2682 | line-height: 1.313rem; 2683 | height: 1.313rem; 2684 | width: 1rem; 2685 | border-radius: 4px; 2686 | color: #454545; 2687 | text-shadow: 0 1px #fff; 2688 | background-color: #e5e9e8; 2689 | border: 1px solid #9daca9; 2690 | margin-left: -0.6rem; 2691 | margin-bottom: -1px; 2692 | margin-top: -1px; 2693 | box-shadow: inset 0 1px #fff; 2694 | -webkit-transition: margin-left 0.05s ease-in-out; 2695 | transition: margin-left 0.05s ease-in-out; 2696 | } 2697 | 2698 | .topcoat-switch__toggle:after { 2699 | content: 'OFF'; 2700 | background-color: #d2d6d6; 2701 | left: 0.8rem; 2702 | padding-left: 0.6rem; 2703 | } 2704 | 2705 | .topcoat-switch__input:checked + .topcoat-switch__toggle { 2706 | margin-left: 1.85rem; 2707 | } 2708 | 2709 | .topcoat-switch__input:active + .topcoat-switch__toggle { 2710 | border: 1px solid #9daca9; 2711 | box-shadow: inset 0 1px #fff; 2712 | } 2713 | 2714 | .topcoat-switch__input:focus + .topcoat-switch__toggle { 2715 | border: 1px solid #0036ff; 2716 | box-shadow: 0 0 0 2px #6fb5f1; 2717 | } 2718 | 2719 | .topcoat-switch__input:disabled + .topcoat-switch__toggle:after, 2720 | .topcoat-switch__input:disabled + .topcoat-switch__toggle:before { 2721 | background: transparent; 2722 | } 2723 | 2724 | /** 2725 | * 2726 | * Copyright 2012 Adobe Systems Inc.; 2727 | * 2728 | * Licensed under the Apache License, Version 2.0 (the "License"); 2729 | * you may not use this file except in compliance with the License. 2730 | * You may obtain a copy of the License at 2731 | * 2732 | * http://www.apache.org/licenses/LICENSE-2.0 2733 | * 2734 | * Unless required by applicable law or agreed to in writing, software 2735 | * distributed under the License is distributed on an "AS IS" BASIS, 2736 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2737 | * See the License for the specific language governing permissions and 2738 | * limitations under the License. 2739 | * 2740 | */ 2741 | 2742 | /** 2743 | * 2744 | * Copyright 2012 Adobe Systems Inc.; 2745 | * 2746 | * Licensed under the Apache License, Version 2.0 (the "License"); 2747 | * you may not use this file except in compliance with the License. 2748 | * You may obtain a copy of the License at 2749 | * 2750 | * http://www.apache.org/licenses/LICENSE-2.0 2751 | * 2752 | * Unless required by applicable law or agreed to in writing, software 2753 | * distributed under the License is distributed on an "AS IS" BASIS, 2754 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2755 | * See the License for the specific language governing permissions and 2756 | * limitations under the License. 2757 | * 2758 | */ 2759 | 2760 | .button, 2761 | .topcoat-tab-bar__button { 2762 | position: relative; 2763 | display: inline-block; 2764 | vertical-align: top; 2765 | -moz-box-sizing: border-box; 2766 | box-sizing: border-box; 2767 | background-clip: padding-box; 2768 | padding: 0; 2769 | margin: 0; 2770 | font: inherit; 2771 | color: inherit; 2772 | background: transparent; 2773 | border: none; 2774 | cursor: default; 2775 | -webkit-user-select: none; 2776 | -moz-user-select: none; 2777 | -ms-user-select: none; 2778 | user-select: none; 2779 | text-overflow: ellipsis; 2780 | white-space: nowrap; 2781 | overflow: hidden; 2782 | text-decoration: none; 2783 | } 2784 | 2785 | .button--quiet { 2786 | background: transparent; 2787 | border: 1px solid transparent; 2788 | box-shadow: none; 2789 | } 2790 | 2791 | .button--disabled, 2792 | .topcoat-tab-bar__button:disabled { 2793 | opacity: 0.3; 2794 | cursor: default; 2795 | pointer-events: none; 2796 | } 2797 | 2798 | .button-bar, 2799 | .topcoat-tab-bar { 2800 | display: table; 2801 | table-layout: fixed; 2802 | white-space: nowrap; 2803 | margin: 0; 2804 | padding: 0; 2805 | } 2806 | 2807 | .button-bar__item, 2808 | .topcoat-tab-bar__item { 2809 | display: table-cell; 2810 | width: auto; 2811 | border-radius: 0; 2812 | } 2813 | 2814 | .button-bar__item > input, 2815 | .topcoat-tab-bar__item > input { 2816 | position: absolute; 2817 | overflow: hidden; 2818 | padding: 0; 2819 | border: 0; 2820 | opacity: 0.001; 2821 | z-index: 1; 2822 | vertical-align: top; 2823 | outline: none; 2824 | } 2825 | 2826 | .button-bar__button { 2827 | border-radius: inherit; 2828 | } 2829 | 2830 | .button-bar__item:disabled { 2831 | opacity: 0.3; 2832 | cursor: default; 2833 | pointer-events: none; 2834 | } 2835 | 2836 | /* topdoc 2837 | name: Tab Bar 2838 | description: Component of tab buttons 2839 | modifiers: 2840 | :disabled: Disabled state 2841 | markup: 2842 |
2843 | 2847 | 2851 | 2855 |
2856 | examples: 2857 | mobile tab bar: http://codepen.io/Topcoat/pen/rJICF 2858 | tags: 2859 | - desktop 2860 | - light 2861 | - dark 2862 | - mobile 2863 | - tab 2864 | - group 2865 | - bar 2866 | */ 2867 | 2868 | .topcoat-tab-bar__button { 2869 | padding: 0 0.563rem; 2870 | height: 1.313rem; 2871 | line-height: 1.313rem; 2872 | letter-spacing: 0; 2873 | color: #454545; 2874 | text-shadow: 0 1px #fff; 2875 | vertical-align: top; 2876 | background-color: #e5e9e8; 2877 | box-shadow: inset 0 1px #fff; 2878 | border-top: 1px solid #9daca9; 2879 | } 2880 | 2881 | .topcoat-tab-bar__button:active, 2882 | .topcoat-tab-bar__button--large:active, 2883 | :checked + .topcoat-tab-bar__button { 2884 | color: #288edf; 2885 | background-color: #e5f1fb; 2886 | box-shadow: inset 0 0 1px rgba(0,0,0,0.1); 2887 | } 2888 | 2889 | .topcoat-tab-bar__button:focus, 2890 | .topcoat-tab-bar__button--large:focus { 2891 | z-index: 1; 2892 | box-shadow: inset 0 1px rgba(255,255,255,0.36), 0 0 0 2px #6fb5f1; 2893 | outline: 0; 2894 | } 2895 | 2896 | /** 2897 | * 2898 | * Copyright 2012 Adobe Systems Inc.; 2899 | * 2900 | * Licensed under the Apache License, Version 2.0 (the "License"); 2901 | * you may not use this file except in compliance with the License. 2902 | * You may obtain a copy of the License at 2903 | * 2904 | * http://www.apache.org/licenses/LICENSE-2.0 2905 | * 2906 | * Unless required by applicable law or agreed to in writing, software 2907 | * distributed under the License is distributed on an "AS IS" BASIS, 2908 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2909 | * See the License for the specific language governing permissions and 2910 | * limitations under the License. 2911 | * 2912 | */ 2913 | 2914 | /** 2915 | * 2916 | * Copyright 2012 Adobe Systems Inc.; 2917 | * 2918 | * Licensed under the Apache License, Version 2.0 (the "License"); 2919 | * you may not use this file except in compliance with the License. 2920 | * You may obtain a copy of the License at 2921 | * 2922 | * http://www.apache.org/licenses/LICENSE-2.0 2923 | * 2924 | * Unless required by applicable law or agreed to in writing, software 2925 | * distributed under the License is distributed on an "AS IS" BASIS, 2926 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2927 | * See the License for the specific language governing permissions and 2928 | * limitations under the License. 2929 | * 2930 | */ 2931 | 2932 | .input, 2933 | .topcoat-text-input, 2934 | .topcoat-text-input--large { 2935 | padding: 0; 2936 | margin: 0; 2937 | font: inherit; 2938 | color: inherit; 2939 | background: transparent; 2940 | border: none; 2941 | -moz-box-sizing: border-box; 2942 | box-sizing: border-box; 2943 | background-clip: padding-box; 2944 | vertical-align: top; 2945 | outline: none; 2946 | } 2947 | 2948 | .input:disabled, 2949 | .topcoat-text-input:disabled, 2950 | .topcoat-text-input--large:disabled { 2951 | opacity: 0.3; 2952 | cursor: default; 2953 | pointer-events: none; 2954 | } 2955 | 2956 | /* topdoc 2957 | name: Text input 2958 | description: Topdoc text input 2959 | modifiers: 2960 | :disabled: Disabled state 2961 | :focus: Focused 2962 | :invalid: Hover state 2963 | markup: 2964 | 2965 |
2966 |
2967 | 2968 |
2969 |
2970 | 2971 | tags: 2972 | - desktop 2973 | - mobile 2974 | - text 2975 | - input 2976 | */ 2977 | 2978 | .topcoat-text-input, 2979 | .topcoat-text-input--large { 2980 | line-height: 1.313rem; 2981 | font-size: 12px; 2982 | letter-spacing: 0; 2983 | padding: 0 0.563rem; 2984 | border: 1px solid #9daca9; 2985 | border-radius: 4px; 2986 | background-color: #fff; 2987 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 2988 | color: #454545; 2989 | vertical-align: top; 2990 | } 2991 | 2992 | .topcoat-text-input:focus, 2993 | .topcoat-text-input--large:focus { 2994 | background-color: #fff; 2995 | color: #454545; 2996 | border: 1px solid #0036ff; 2997 | box-shadow: 0 0 0 2px #6fb5f1; 2998 | } 2999 | 3000 | .topcoat-text-input:disabled::-webkit-input-placeholder { 3001 | color: #454545; 3002 | } 3003 | 3004 | .topcoat-text-input:disabled::-moz-placeholder { 3005 | color: #454545; 3006 | } 3007 | 3008 | .topcoat-text-input:disabled:-ms-input-placeholder { 3009 | color: #454545; 3010 | } 3011 | 3012 | .topcoat-text-input:invalid { 3013 | border: 1px solid #ec514e; 3014 | } 3015 | 3016 | /* topdoc 3017 | name: Large Text Input 3018 | description: A bigger input, still for text. 3019 | modifiers: 3020 | :disabled: Disabled state 3021 | :focus: Focused 3022 | :invalid: Hover state 3023 | markup: 3024 | 3025 |
3026 |
3027 | 3028 |
3029 |
3030 | 3031 | tags: 3032 | - desktop 3033 | - light 3034 | - mobile 3035 | - form 3036 | - input 3037 | - large 3038 | */ 3039 | 3040 | .topcoat-text-input--large { 3041 | line-height: 1.688rem; 3042 | font-size: 0.875rem; 3043 | } 3044 | 3045 | .topcoat-text-input--large:disabled { 3046 | color: #454545; 3047 | } 3048 | 3049 | .topcoat-text-input--large:disabled::-webkit-input-placeholder { 3050 | color: #454545; 3051 | } 3052 | 3053 | .topcoat-text-input--large:disabled::-moz-placeholder { 3054 | color: #454545; 3055 | } 3056 | 3057 | .topcoat-text-input--large:disabled:-ms-input-placeholder { 3058 | color: #454545; 3059 | } 3060 | 3061 | .topcoat-text-input--large:invalid { 3062 | border: 1px solid #ec514e; 3063 | } 3064 | 3065 | /** 3066 | * 3067 | * Copyright 2012 Adobe Systems Inc.; 3068 | * 3069 | * Licensed under the Apache License, Version 2.0 (the "License"); 3070 | * you may not use this file except in compliance with the License. 3071 | * You may obtain a copy of the License at 3072 | * 3073 | * http://www.apache.org/licenses/LICENSE-2.0 3074 | * 3075 | * Unless required by applicable law or agreed to in writing, software 3076 | * distributed under the License is distributed on an "AS IS" BASIS, 3077 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3078 | * See the License for the specific language governing permissions and 3079 | * limitations under the License. 3080 | * 3081 | */ 3082 | 3083 | /** 3084 | * 3085 | * Copyright 2012 Adobe Systems Inc.; 3086 | * 3087 | * Licensed under the Apache License, Version 2.0 (the "License"); 3088 | * you may not use this file except in compliance with the License. 3089 | * You may obtain a copy of the License at 3090 | * 3091 | * http://www.apache.org/licenses/LICENSE-2.0 3092 | * 3093 | * Unless required by applicable law or agreed to in writing, software 3094 | * distributed under the License is distributed on an "AS IS" BASIS, 3095 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3096 | * See the License for the specific language governing permissions and 3097 | * limitations under the License. 3098 | * 3099 | */ 3100 | 3101 | .textarea { 3102 | -moz-box-sizing: border-box; 3103 | box-sizing: border-box; 3104 | background-clip: padding-box; 3105 | padding: 0; 3106 | margin: 0; 3107 | font: inherit; 3108 | color: inherit; 3109 | background: transparent; 3110 | border: none; 3111 | vertical-align: top; 3112 | resize: none; 3113 | outline: none; 3114 | } 3115 | 3116 | .textarea:disabled { 3117 | opacity: 0.3; 3118 | cursor: default; 3119 | pointer-events: none; 3120 | } 3121 | 3122 | /** 3123 | * 3124 | * Copyright 2012 Adobe Systems Inc.; 3125 | * 3126 | * Licensed under the Apache License, Version 2.0 (the "License"); 3127 | * you may not use this file except in compliance with the License. 3128 | * You may obtain a copy of the License at 3129 | * 3130 | * http://www.apache.org/licenses/LICENSE-2.0 3131 | * 3132 | * Unless required by applicable law or agreed to in writing, software 3133 | * distributed under the License is distributed on an "AS IS" BASIS, 3134 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3135 | * See the License for the specific language governing permissions and 3136 | * limitations under the License. 3137 | * 3138 | */ 3139 | 3140 | /** 3141 | * 3142 | * Copyright 2012 Adobe Systems Inc.; 3143 | * 3144 | * Licensed under the Apache License, Version 2.0 (the "License"); 3145 | * you may not use this file except in compliance with the License. 3146 | * You may obtain a copy of the License at 3147 | * 3148 | * http://www.apache.org/licenses/LICENSE-2.0 3149 | * 3150 | * Unless required by applicable law or agreed to in writing, software 3151 | * distributed under the License is distributed on an "AS IS" BASIS, 3152 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3153 | * See the License for the specific language governing permissions and 3154 | * limitations under the License. 3155 | * 3156 | */ 3157 | 3158 | .textarea, 3159 | .topcoat-textarea, 3160 | .topcoat-textarea--large { 3161 | -moz-box-sizing: border-box; 3162 | box-sizing: border-box; 3163 | background-clip: padding-box; 3164 | padding: 0; 3165 | margin: 0; 3166 | font: inherit; 3167 | color: inherit; 3168 | background: transparent; 3169 | border: none; 3170 | vertical-align: top; 3171 | resize: none; 3172 | outline: none; 3173 | } 3174 | 3175 | .textarea:disabled, 3176 | .topcoat-textarea:disabled, 3177 | .topcoat-textarea--large:disabled { 3178 | opacity: 0.3; 3179 | cursor: default; 3180 | pointer-events: none; 3181 | } 3182 | 3183 | /* topdoc 3184 | name: Textarea 3185 | description: A whole area, just for text. 3186 | modifiers: 3187 | :disabled: Disabled state 3188 | markup: 3189 | 3190 |
3191 |
3192 | 3193 | tags: 3194 | - desktop 3195 | - light 3196 | - mobile 3197 | - form 3198 | - input 3199 | - textarea 3200 | */ 3201 | 3202 | .topcoat-textarea, 3203 | .topcoat-textarea--large { 3204 | padding: 1rem; 3205 | font-size: 1rem; 3206 | font-weight: 400; 3207 | border-radius: 4px; 3208 | line-height: 1.313rem; 3209 | border: 1px solid #9daca9; 3210 | background-color: #fff; 3211 | box-shadow: inset 0 1px rgba(0,0,0,0.1); 3212 | color: #454545; 3213 | letter-spacing: 0; 3214 | } 3215 | 3216 | .topcoat-textarea:focus, 3217 | .topcoat-textarea--large:focus { 3218 | background-color: #fff; 3219 | color: #454545; 3220 | border: 1px solid #0036ff; 3221 | box-shadow: 0 0 0 2px #6fb5f1; 3222 | } 3223 | 3224 | .topcoat-textarea:disabled::-webkit-input-placeholder { 3225 | color: #454545; 3226 | } 3227 | 3228 | .topcoat-textarea:disabled::-moz-placeholder { 3229 | color: #454545; 3230 | } 3231 | 3232 | .topcoat-textarea:disabled:-ms-input-placeholder { 3233 | color: #454545; 3234 | } 3235 | 3236 | /* topdoc 3237 | name: Large Textarea 3238 | description: A whole area, just for text; now available in large. 3239 | modifiers: 3240 | :disabled: Disabled state 3241 | markup: 3242 | 3243 |
3244 |
3245 | 3246 | tags: 3247 | - desktop 3248 | - light 3249 | - mobile 3250 | - form 3251 | - input 3252 | - textarea 3253 | */ 3254 | 3255 | .topcoat-textarea--large { 3256 | font-size: 1.3rem; 3257 | line-height: 1.688rem; 3258 | } 3259 | 3260 | .topcoat-textarea--large:disabled { 3261 | color: #454545; 3262 | } 3263 | 3264 | .topcoat-textarea--large:disabled::-webkit-input-placeholder { 3265 | color: #454545; 3266 | } 3267 | 3268 | .topcoat-textarea--large:disabled::-moz-placeholder { 3269 | color: #454545; 3270 | } 3271 | 3272 | .topcoat-textarea--large:disabled:-ms-input-placeholder { 3273 | color: #454545; 3274 | } 3275 | 3276 | body { 3277 | margin: 0; 3278 | padding: 0; 3279 | background: #dfe2e2; 3280 | color: #000; 3281 | font: 16px "Source Sans", helvetica, arial, sans-serif; 3282 | font-weight: 400; 3283 | } 3284 | 3285 | :focus { 3286 | outline-color: transparent; 3287 | outline-style: none; 3288 | } 3289 | 3290 | .topcoat-icon--menu-stack { 3291 | background: url("../img/hamburger_dark.svg") no-repeat; 3292 | background-size: cover; 3293 | } 3294 | 3295 | .quarter { 3296 | width: 25%; 3297 | } 3298 | 3299 | .half { 3300 | width: 50%; 3301 | } 3302 | 3303 | .three-quarters { 3304 | width: 75%; 3305 | } 3306 | 3307 | .third { 3308 | width: 33.333%; 3309 | } 3310 | 3311 | .two-thirds { 3312 | width: 66.666%; 3313 | } 3314 | 3315 | .full { 3316 | width: 100%; 3317 | } 3318 | 3319 | .left { 3320 | text-align: left; 3321 | } 3322 | 3323 | .center { 3324 | text-align: center; 3325 | } 3326 | 3327 | .right { 3328 | text-align: right; 3329 | } 3330 | 3331 | .reset-ui { 3332 | -moz-box-sizing: border-box; 3333 | box-sizing: border-box; 3334 | background-clip: padding-box; 3335 | position: relative; 3336 | display: inline-block; 3337 | vertical-align: top; 3338 | padding: 0; 3339 | margin: 0; 3340 | font: inherit; 3341 | color: inherit; 3342 | background: transparent; 3343 | border: none; 3344 | cursor: default; 3345 | -webkit-user-select: none; 3346 | -moz-user-select: none; 3347 | -ms-user-select: none; 3348 | user-select: none; 3349 | text-overflow: ellipsis; 3350 | white-space: nowrap; 3351 | overflow: hidden; 3352 | } 3353 | 3354 | /* This file should include color and image variables corresponding to the dark theme */ 3355 | 3356 | /* ---------- colors ---------- */ 3357 | 3358 | /* ---------- darken ---------- */ 3359 | 3360 | /* ---------- lighten ---------- */ 3361 | 3362 | /* ---------- alphas ---------- */ 3363 | 3364 | /* ---------- thickness ---------- */ 3365 | 3366 | /* ---------- shadows ---------- */ 3367 | 3368 | /* Icons */ 3369 | 3370 | /* Navigation Bar */ 3371 | 3372 | /* Text Input */ 3373 | 3374 | /* List */ 3375 | 3376 | /* Overlay */ 3377 | 3378 | /* Progress bar */ 3379 | 3380 | /* Checkbox */ 3381 | 3382 | /* Range input */ 3383 | 3384 | /* Radio Button */ 3385 | 3386 | /* Tab bar */ 3387 | 3388 | /* Switch */ 3389 | 3390 | /* Icon Button */ 3391 | 3392 | /* Navigation bar */ 3393 | 3394 | /* List */ 3395 | 3396 | /* Search Input */ 3397 | 3398 | /* Textarea */ 3399 | 3400 | /* Checkbox */ 3401 | 3402 | /* Radio */ 3403 | 3404 | /* Range input */ 3405 | 3406 | /* Search Input */ 3407 | 3408 | /* Switch */ 3409 | 3410 | /* This file should include color and image variables corresponding to the light theme */ 3411 | 3412 | /* ---------- colors ---------- */ 3413 | 3414 | /* ---------- darken ---------- */ 3415 | 3416 | /* ---------- lighten ---------- */ 3417 | 3418 | /* ---------- alphas ---------- */ 3419 | 3420 | /* ---------- thickness ---------- */ 3421 | 3422 | /* ---------- shadows ---------- */ 3423 | 3424 | /* Secondary colors (based on colors above) 3425 | 3426 | Everything below this line should be calculated using the variables above. This area is for people that want to totally customize everything. Have fun, bros! 3427 | 3428 | */ 3429 | 3430 | /* Icons */ 3431 | 3432 | /* Navigation Bar */ 3433 | 3434 | /* Text Input */ 3435 | 3436 | /* List */ 3437 | 3438 | /* Overlay */ 3439 | 3440 | /* Progress bar */ 3441 | 3442 | /* Checkbox */ 3443 | 3444 | /* Range input */ 3445 | 3446 | /* Radio Button */ 3447 | 3448 | /* Tab bar */ 3449 | 3450 | /* Switch */ 3451 | 3452 | /* Containers */ 3453 | 3454 | /* Icon Button */ 3455 | 3456 | /* Navigation bar */ 3457 | 3458 | /* List */ 3459 | 3460 | /* Search Input */ 3461 | 3462 | /* Text Area */ 3463 | 3464 | /* Checkbox */ 3465 | 3466 | /* Radio */ 3467 | 3468 | /* Range input */ 3469 | 3470 | /* Search Input */ 3471 | 3472 | /* Switch */ 3473 | 3474 | /* Call To Action */ 3475 | 3476 | /* Text Input */ 3477 | 3478 | /* Radio input */ 3479 | 3480 | /* Overlay */ 3481 | 3482 | /* Textarea */ 3483 | 3484 | /* Progress bar container */ 3485 | 3486 | /* Progress bar progress */ 3487 | 3488 | /* Switch */ 3489 | 3490 | /* Notification */ 3491 | 3492 | /* Search */ --------------------------------------------------------------------------------