├── .circleci └── config.yml ├── .eslintignore ├── .gitignore ├── .stylelintrc ├── LICENSE ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── pull_request_template.md ├── src ├── data │ ├── global.yaml │ └── spacing.yaml ├── js │ ├── .eslintrc │ ├── design-system-interface.js │ ├── design-system-interface │ │ ├── copy-snippet.js │ │ └── variant-selector.js │ ├── index.js │ ├── init.js │ ├── safe-focus.js │ ├── spec-helpers │ │ ├── main.js │ │ └── mock │ │ │ └── withDom.js │ ├── toggler.js │ └── vendor │ │ └── picturefill.js ├── markup │ ├── layout │ │ ├── collection.hbs │ │ ├── drizzle.hbs │ │ └── page.hbs │ ├── pages │ │ ├── demos │ │ │ └── common-elements.hbs │ │ ├── documentation │ │ │ ├── grid.hbs │ │ │ ├── page-width.hbs │ │ │ └── spacing.hbs │ │ └── index.hbs │ └── patterns │ │ ├── components │ │ ├── buttons │ │ │ ├── btn-with-icon.hbs │ │ │ ├── btn.hbs │ │ │ └── collection.yaml │ │ ├── defaults │ │ │ ├── collection.yaml │ │ │ ├── heading.hbs │ │ │ ├── link.hbs │ │ │ ├── list--ordered.hbs │ │ │ ├── list--unordered.hbs │ │ │ └── paragraph.hbs │ │ └── interactive │ │ │ ├── collection.yaml │ │ │ └── expandable-content.hbs │ │ └── drizzle │ │ └── partials │ │ ├── item.hbs │ │ ├── script-tags.hbs │ │ ├── sidebar.hbs │ │ └── stylesheet-link-tags.hbs ├── public │ └── .keep ├── scss │ ├── base.scss │ ├── components │ │ ├── _button.scss │ │ ├── _expandable-content.scss │ │ ├── _heading.scss │ │ ├── _icon.scss │ │ ├── _link.scss │ │ ├── _list.scss │ │ ├── _paragraph.scss │ │ └── _skip-to-content.scss │ ├── design-system-interface.scss │ ├── elements │ │ ├── _a.scss │ │ ├── _asterisk.scss │ │ ├── _h1-6.scss │ │ ├── _html-body.scss │ │ ├── _img.scss │ │ ├── _ol-ul.scss │ │ ├── _p.scss │ │ └── _root.scss │ ├── generic │ │ ├── _keyframes.scss │ │ ├── _normalize.scss │ │ └── _safe-focus.scss │ ├── object │ │ ├── _grid.scss │ │ └── _width-limiter.scss │ ├── settings │ │ ├── _colors.scss │ │ ├── _fonts.scss │ │ ├── _variables.scss │ │ └── _z-index.scss │ ├── tools │ │ ├── .gitkeep │ │ ├── _functions.scss │ │ └── _mixins.scss │ ├── utilities │ │ ├── _display.scss │ │ ├── _spacing.scss │ │ └── _text.scss │ └── vendors │ │ └── .gitkeep └── svg │ └── chevron.svg ├── tasks ├── copy.js ├── pa11y.js ├── patterns.js ├── server.js ├── start.js ├── svg-sprite.js └── watch.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:10.13.0-browsers 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | # when lock file changes, use increasingly general patterns to restore cache 26 | - node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} 27 | - node-v1-{{ .Branch }}- 28 | - node-v1- 29 | 30 | - run: npm ci 31 | 32 | - save_cache: 33 | paths: 34 | - ~/usr/local/lib/node_modules # location depends on npm version 35 | key: node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} 36 | 37 | # run all linting and testing 38 | - run: npm run lint 39 | - run: npm t 40 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/js/vendor -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by us 2 | npm-debug.log 3 | dist/* 4 | 5 | # We don't want to check in packages 6 | node_modules/* 7 | 8 | # Generated by OS / editors 9 | .DS_Store 10 | *.esproj 11 | *.sublime-* 12 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sparkbox/stylelint-config-sparkbox", 3 | "plugins": [ 4 | "stylelint-scss" 5 | ], 6 | "rules": { 7 | "max-nesting-depth": [4, { 8 | "ignore": ["blockless-at-rules"] 9 | }], 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **First-time setup instructions:** 2 | 3 | - [**Click this link and make a new repo.**](https://github.com/sparkbox/designsystemstarter/generate) This will start a repo using [GitHub's repository templates](https://github.blog/2019-06-06-generate-new-repositories-with-repository-templates/), which copies all the files from this one to the new repo. 4 | - **Add your company's name.** You'll need to change it from `YOURNAMEHERE` in: 5 | - [package.json](package.json) 6 | - [src/data/global.yaml](src/data/global.yaml) 7 | - [README.md](README.md) (That's this file!) 8 | - **Consider changing the license** The Design System Starter is licensed with a CC Attribution-ShareAlike license. You should consider changing [the license file](LICENSE) if this doesn't fit the project you're starting with it. 9 | - **Start styling.** You can: 10 | - **Build a color palette** in [src/scss/settings/_variables.scss](src/scss/settings/_variables.scss) 11 | - **Adjust typography and default `` styles** in [src/scss/tools/_mixins.scss](src/scss/tools/_mixins.scss) 12 | - Add `@font-face` blocks in [src/scss/settings/_fonts.scss](src/scss/settings/_fonts.scss) 13 | - Add ``s to stylesheets in [src/markup/patterns/drizzle/partials/stylesheet-link-tags.hbs](src/markup/patterns/drizzle/partials/stylesheet-link-tags.hbs) 14 | - Good starting places (must be running the app to view these pages): 15 | - [Common Elements page](http://localhost:3000/demos/common-elements.html) 16 | - [The "btn" component](http://localhost:3000/patterns/components/buttons.html) 17 | - **Delete this documentation.** This documentation is for first-time setup. Go into [README.md](README.md) and delete this list of steps. 18 | 19 | # YOURNAMEHERE Design System 20 | 21 | Setup 22 | ----- 23 | 1. Before running the project setup node/npm ([Installation instructions](https://nodejs.org/en/download/)). 24 | 25 | 2. Run `npm install`. 26 | 27 | 3. Run `npm start`. This will: 28 | 29 | - Clear any previously built project files 30 | - Build project files 31 | - Start the server (localhost:3000) 32 | - Run watch tasks 33 | 34 | Drizzle 35 | ------- 36 | 37 | The pattern library is powered by [Drizzle](https://github.com/cloudfour/drizzle) and will be organized by: 38 | - [Data](https://github.com/cloudfour/drizzle/tree/master/docs#data) 39 | - [Pages](https://github.com/cloudfour/drizzle/tree/master/docs#pages) 40 | - [Patterns](https://github.com/cloudfour/drizzle/tree/master/docs#patterns) 41 | - [Templates](https://github.com/cloudfour/drizzle/tree/master/docs#templates) 42 | 43 | Public 44 | ------ 45 | 46 | The contents of the `public/` directory will be copied directly into the root of the `dist/` directory. 47 | 48 | Sass 49 | ---- 50 | 51 | All CSS is compiled from [Sass](https://sass-lang.com/) and can be found in the `scss/` directory. Any files not prefixed with an underscore will compile to `dist/css/`. 52 | 53 | JavaScript 54 | ---------- 55 | All JavaScript is compiled with [Webpack](https://webpack.js.org/) and can be found in the `js/` directory. All JavaScript files should be imported into `index.js` which will then be compiled to `dist/js/scripts.js`. 56 | 57 | Testing 58 | ------- 59 | 60 | Running `npm test` will run the following tasks: 61 | 62 | - `pa11y`: Runs accessibility tests on all HTML files in the `dist/` directory 63 | - `stylelint`: Checks all CSS in the `dist/` for errors and enforces [Sparkbox's code conventions](https://www.npmjs.com/package/@sparkbox/stylelint-config-sparkbox) 64 | - `eslint`: Checks all JavaScript in the `dist/` for errors and enforces [Sparkbox's code conventions](https://www.npmjs.com/package/eslint-config-sparkbox) 65 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache.forever(); 3 | 4 | const presets = [ 5 | [ 6 | '@babel/preset-env', 7 | { 8 | // { modules: false } tells babel not to transpile import statements 9 | // gives control to webpack (or rollup) 10 | // to exclusivley handle parsing/resolving of imports 11 | modules: false, 12 | useBuiltIns: 'entry', 13 | corejs: '3.0.0' 14 | } 15 | ] 16 | ]; 17 | 18 | const plugins = ['@babel/plugin-syntax-dynamic-import']; 19 | 20 | return { presets, plugins }; 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YOURNAMEHERE-design-system", 3 | "version": "0.0.1", 4 | "repository": "git@github.com:sparkbox/YOURNAMEHERE-design-system.git", 5 | "author": "Kasey Bonifacio ", 6 | "license": "cc-by-sa-4.0", 7 | "scripts": { 8 | "prestart": "run-p clean node-version", 9 | "start": "node tasks/start.js", 10 | "build": "run-p copy webpack svg-sprite", 11 | "build:dev": "run-p copy webpack:dev svg-sprite", 12 | "lint": "run-p pa11y stylelint eslint", 13 | "clean": "rimraf dist/", 14 | "copy": "node tasks/copy.js", 15 | "server": "node tasks/server.js", 16 | "watch": "node tasks/watch.js", 17 | "patterns": "node tasks/patterns.js", 18 | "svg-sprite": "node tasks/svg-sprite.js", 19 | "webpack": "webpack --color --config=webpack.prod.js", 20 | "webpack:dev": "webpack -w --color --progress --config=webpack.dev.js", 21 | "pa11y": "node tasks/pa11y.js", 22 | "stylelint": "stylelint 'src/**/*.scss' --syntax scss", 23 | "eslint": "eslint src/js", 24 | "test": "NODE_ENV=test mocha --require @babel/register './src/js/spec-helpers/main.js' './src/**/*-spec.js'", 25 | "node-version": "check-node-version --package" 26 | }, 27 | "engines": { 28 | "node": "^10.13.0", 29 | "npm": "^6.4.1" 30 | }, 31 | "browserslist": [ 32 | "last 1 major version", 33 | ">= 1%", 34 | "Chrome >= 68", 35 | "IE >= 11", 36 | "Safari >= 11", 37 | "Edge >= 16" 38 | ], 39 | "dependencies": { 40 | "@babel/polyfill": "^7.4.0", 41 | "autoprefixer": "^9.4.2", 42 | "babel-loader": "^8.0.4", 43 | "core-js": "^3.1.3", 44 | "express": "^4.15.2", 45 | "express-basic-auth": "^1.2.0", 46 | "lodash": "^4.17.5", 47 | "prism-themes": "^1.1.0", 48 | "svg4everybody": "^2.1.9", 49 | "webpack-merge": "^4.2.2" 50 | }, 51 | "devDependencies": { 52 | "@babel/core": "^7.4.0", 53 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 54 | "@babel/preset-env": "^7.4.2", 55 | "@babel/register": "^7.6.0", 56 | "@sparkbox/downpour": "^0.0.11", 57 | "@sparkbox/stylelint-config-sparkbox": "^0.2.0", 58 | "babel-loader": "^8.0.4", 59 | "bluebird": "^3.5.3", 60 | "check-node-version": "^3.2.0", 61 | "chokidar": "^2.0.4", 62 | "css-loader": "^3.2.0", 63 | "eslint": "^5.16.0", 64 | "eslint-config-airbnb": "^17.1.0", 65 | "eslint-config-sparkbox": "^0.5.1", 66 | "eslint-plugin-import": "^2.7.0", 67 | "eslint-plugin-jsx-a11y": "^6.0.2", 68 | "eslint-plugin-react": "^7.4.0", 69 | "glob": "^7.1.3", 70 | "helper-markdown": "^1.0.0", 71 | "mini-css-extract-plugin": "^0.8.0", 72 | "node": "^10.13.0", 73 | "node-sass": "^4.5.3", 74 | "npm-run-all": "^4.1.5", 75 | "pa11y": "^5.1.0", 76 | "postcss-cli": "^6.0.1", 77 | "postcss-loader": "^3.0.0", 78 | "rimraf": "^2.5.4", 79 | "sass-loader": "^8.0.0", 80 | "shelljs": "^0.8.3", 81 | "stylelint": "^11.1.1", 82 | "stylelint-scss": "^3.11.1", 83 | "svg-sprite": "^1.5.0", 84 | "uglifyjs-webpack-plugin": "^2.1.2", 85 | "webpack": "^4.27.1", 86 | "webpack-cli": "^3.3.0", 87 | "webpack-stylish": "^0.1.8" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer'); 2 | 3 | module.exports = { 4 | plugins: [autoprefixer({grid: true})] 5 | }; 6 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Jira Card:** 2 | 3 | ## ✍️ Description 4 | 5 | _[add a description of work done here]_ 6 | 7 | ## The following steps outline the added experience by the pull request: 8 | 9 | * [ ] 10 | -------------------------------------------------------------------------------- /src/data/global.yaml: -------------------------------------------------------------------------------- 1 | companyName: YOURNAMEHERE 2 | -------------------------------------------------------------------------------- /src/data/spacing.yaml: -------------------------------------------------------------------------------- 1 | spacing-classes: 2 | - class: util-margin-bottom-none 3 | description: Removes margin from the bottom of an element. 4 | - class: util-margin-bottom-xs 5 | description: Adds an extra small amount of margin to the bottom of an element. 6 | - class: util-margin-bottom-sm 7 | description: Adds a small amount of margin to the bottom of an element. 8 | - class: util-margin-bottom-md 9 | description: Adds a medium amount of margin to the bottom of an element. 10 | - class: util-margin-bottom-lg 11 | description: Adds a large amount of margin to the bottom of an element. 12 | - class: util-margin-bottom-xl 13 | description: Adds an extra large amount of margin to the bottom of an element. 14 | - class: util-margin-bottom-xxl 15 | description: Adds an extra extra large amount of margin to the bottom of an element. 16 | - class: util-margin-top-none 17 | description: Removes margin from the top of an element. 18 | - class: util-margin-top-xs 19 | description: Adds an extra small amount of margin to the top of an element. 20 | - class: util-margin-top-sm 21 | description: Adds a small amount of margin to the top of an element. 22 | - class: util-margin-top-md 23 | description: Adds a medium amount of margin to the top of an element. 24 | - class: util-margin-top-lg 25 | description: Adds a large amount of margin to the top of an element. 26 | - class: util-margin-top-xl 27 | description: Adds an extra large amount of margin to the top of an element. 28 | - class: util-margin-top-xxl 29 | description: Adds an extra extra large amount of margin to the top of an element. 30 | 31 | padding-classes: 32 | - class: util-padding-bottom-none 33 | description: Removes padding from the bottom of an element. 34 | - class: util-padding-bottom-xs 35 | description: Adds an extra small amount of padding to the bottom of an element. 36 | - class: util-padding-bottom-sm 37 | description: Adds a small amount of padding to the bottom of an element. 38 | - class: util-padding-bottom-md 39 | description: Adds a medium amount of padding to the bottom of an element. 40 | - class: util-padding-bottom-lg 41 | description: Adds a large amount of padding to the bottom of an element. 42 | - class: util-padding-bottom-xl 43 | description: Adds an extra large amount of padding to the bottom of an element. 44 | - class: util-padding-bottom-xxl 45 | description: Adds an extra extra large amount of padding to the bottom of an element. 46 | - class: util-padding-top-none 47 | description: Removes padding from the top of an element. 48 | - class: util-padding-top-xs 49 | description: Adds an extra small amount of padding to the top of an element. 50 | - class: util-padding-top-sm 51 | description: Adds a small amount of padding to the top of an element. 52 | - class: util-padding-top-md 53 | description: Adds a medium amount of padding to the top of an element. 54 | - class: util-padding-top-lg 55 | description: Adds a large amount of padding to the top of an element. 56 | - class: util-padding-top-xl 57 | description: Adds an extra large amount of padding to the top of an element. 58 | - class: util-padding-top-xxl 59 | description: Adds an extra extra large amount of padding to the top of an element. 60 | -------------------------------------------------------------------------------- /src/js/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["sparkbox"], 3 | "rules": { 4 | "comma-dangle": ["error", { 5 | "arrays": "only-multiline", 6 | "objects": "only-multiline", 7 | "imports": "never", 8 | "exports": "never", 9 | "functions": "never" 10 | }] 11 | }, 12 | "globals": { 13 | "navigationDocument": "readonly", 14 | "App": "readonly", 15 | "Device": "readonly", 16 | "Player": "readonly", 17 | "MediaItem": "readonly", 18 | "Playlist": "readonly" 19 | }, 20 | "overrides": [ 21 | { 22 | "files": ["spec-helpers/**", "*-spec.js"], 23 | "globals": { 24 | "describe": "readonly", 25 | "it": "readonly", 26 | "xit": "readonly", 27 | "expect": "readonly", 28 | "beforeEach": "readonly", 29 | "afterEach": "readonly" 30 | }, 31 | "rules": { 32 | "no-unused-expressions": "off", 33 | "no-underscore-dangle": [2, { "allow": ["__Rewire__", "__ResetDependency__"] }], 34 | "import/named": "off" 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/js/design-system-interface.js: -------------------------------------------------------------------------------- 1 | import './design-system-interface/variant-selector'; 2 | import './design-system-interface/copy-snippet'; 3 | -------------------------------------------------------------------------------- /src/js/design-system-interface/copy-snippet.js: -------------------------------------------------------------------------------- 1 | const copyBtns = Array.from(document.querySelectorAll('.drizzle-pattern__copy-button')); 2 | 3 | function success(copyBtn) { 4 | copyBtn.classList.add('drizzle-pattern__copy-button--success'); 5 | setTimeout(() => { 6 | copyBtn.classList.remove('drizzle-pattern__copy-button--success'); 7 | }, 1000); 8 | } 9 | 10 | copyBtns.forEach((copyBtn) => { 11 | copyBtn.addEventListener('click', () => { 12 | const snippet = copyBtn.parentNode.parentNode.querySelector('.drizzle-pattern__demo-box--code'); 13 | if ('clipboard' in navigator) { 14 | // modern copy 15 | const text = snippet.innerText; 16 | navigator.clipboard.writeText(text).then(() => success(copyBtn)); 17 | } else { 18 | // legacy copy 19 | const range = document.createRange(); 20 | range.selectNode(snippet); 21 | window.getSelection().addRange(range); 22 | const copied = document.execCommand('copy'); 23 | if (copied) { 24 | success(copyBtn); 25 | } 26 | window.getSelection().removeAllRanges(); 27 | } 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/js/design-system-interface/variant-selector.js: -------------------------------------------------------------------------------- 1 | const variantsBoxes = [...document.querySelectorAll('.drizzle-pattern__variant-box')]; 2 | 3 | const variantsData = variantsBoxes.map((v) => { 4 | const root = v.closest('.drizzle-pattern'); 5 | const patternContainer = root.querySelector('.drizzle-pattern__demo-box--view'); 6 | const source = root.querySelector('.drizzle-pattern__source'); 7 | const originalPattern = patternContainer.children[0].cloneNode(true); 8 | const variantGroups = [...v.querySelectorAll('.drizzle-pattern__variant-group')]; 9 | 10 | return { 11 | dom: v, 12 | root, 13 | patternContainer, 14 | source, 15 | originalPattern, 16 | variantGroups, 17 | }; 18 | }); 19 | 20 | function checkFirstInputInEachSet() { 21 | variantsData.forEach((vb) => { 22 | vb.variantGroups.forEach((vs) => { 23 | const variantSet = vs; 24 | variantSet.querySelector('input').checked = true; 25 | }); 26 | }); 27 | } 28 | 29 | function getVariantData(vb) { 30 | return variantsData.find(v => v.dom === vb); 31 | } 32 | 33 | function refreshPattern(v) { 34 | v.patternContainer.children[0].remove(); 35 | v.patternContainer.appendChild(v.originalPattern.cloneNode(true)); 36 | } 37 | 38 | function escapeHtml(str) { 39 | const div = document.createElement('div'); 40 | div.appendChild(document.createTextNode(str)); 41 | return div.innerHTML; 42 | } 43 | 44 | function setSource(v, newHtml) { 45 | const variant = v; 46 | variant.source.innerHTML = escapeHtml(newHtml); 47 | if (window.Prism) { 48 | window.Prism.highlightElement(variant.source); 49 | } 50 | } 51 | 52 | function getSelectedVariants(v) { 53 | const selections = []; 54 | 55 | v.variantGroups.forEach((vs) => { 56 | const { value } = vs.querySelector(':checked'); 57 | if (value) { 58 | selections.push(value); 59 | } 60 | }); 61 | 62 | return selections; 63 | } 64 | 65 | function renderVariant(v) { 66 | refreshPattern(v); 67 | const selections = getSelectedVariants(v); 68 | const pattern = v.patternContainer.children[0]; 69 | pattern.classList.add(...selections); 70 | const newHtml = v.patternContainer.innerHTML.trim(); 71 | setSource(v, newHtml); 72 | } 73 | 74 | function onLoad() { 75 | checkFirstInputInEachSet(); 76 | variantsData.forEach((v) => { 77 | renderVariant(v); 78 | }); 79 | } 80 | 81 | function onChange(e) { 82 | const v = getVariantData(e.target.closest('.drizzle-pattern__variant-box')); 83 | renderVariant(v); 84 | } 85 | 86 | variantsBoxes.forEach(v => v.addEventListener('change', onChange)); 87 | 88 | onLoad(); 89 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | // CUSTOM 2 | import './init'; 3 | import './toggler'; 4 | import './safe-focus'; 5 | 6 | // VENDOR 7 | const svg4everybody = require('svg4everybody'); 8 | 9 | svg4everybody(); 10 | -------------------------------------------------------------------------------- /src/js/init.js: -------------------------------------------------------------------------------- 1 | // Removes the .no-js class on the HTML and replaces it with the .js class. 2 | // This allows us to apply specific styles for noJS solutions. 3 | const html = document.querySelector('html'); 4 | 5 | html.classList.remove('no-js'); 6 | html.classList.add('js'); 7 | -------------------------------------------------------------------------------- /src/js/safe-focus.js: -------------------------------------------------------------------------------- 1 | const safeFocusClass = 'safe-focus'; 2 | const cutsTheMustard = () => !!document.documentElement.classList; 3 | const htmlEl = document.documentElement; 4 | 5 | /** 6 | * Add class to key off of for showing focus outlines 7 | * @return {undefined} 8 | */ 9 | const activateSafeFocus = () => { 10 | htmlEl.classList.add(safeFocusClass); 11 | }; 12 | 13 | /** 14 | * Remove class to key off of for showing focus outlines 15 | * @return {undefined} 16 | */ 17 | const deactivateSafeFocus = () => { 18 | htmlEl.classList.remove(safeFocusClass); 19 | }; 20 | 21 | /** 22 | * Bind events for adding & removing class to key off of for showing focus outlines 23 | * @return {undefined} 24 | */ 25 | const initSafeFocus = () => { 26 | if (cutsTheMustard()) { 27 | htmlEl.classList.remove(safeFocusClass); 28 | 29 | document.addEventListener('mousedown', deactivateSafeFocus); 30 | document.addEventListener('keydown', activateSafeFocus); 31 | } 32 | }; 33 | 34 | initSafeFocus(); 35 | -------------------------------------------------------------------------------- /src/js/spec-helpers/main.js: -------------------------------------------------------------------------------- 1 | // Any global testing setup can go here 2 | -------------------------------------------------------------------------------- /src/js/spec-helpers/mock/withDom.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkbox/design-system-starter/dd04e64f13dc9c70d5bc881ec5fc354395a16f18/src/js/spec-helpers/mock/withDom.js -------------------------------------------------------------------------------- /src/js/toggler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ACCESSIBLE TOGGLER 3 | * ================== 4 | * Add the js-toggler class to any element to create a toggler. The ID attribute 5 | * on the toggler must match the aria-labelledby attribute of the item being toggled. 6 | * 7 | * HTML Example: 8 | * 9 | *
...
10 | */ 11 | const togglers = document.querySelectorAll('.js-toggler'); 12 | 13 | if (togglers) { 14 | for (let i = 0; i < togglers.length; i += 1) { 15 | const toggler = togglers[i]; 16 | const togglerID = toggler.getAttribute('id'); 17 | const togglerContent = document.querySelector(`[aria-labelledby="${togglerID}"]`); 18 | let hiddenContent = true; 19 | let expandedContent = false; 20 | 21 | // Hide the togglable content on page load 22 | togglerContent.setAttribute('aria-hidden', hiddenContent); 23 | toggler.setAttribute('aria-expanded', expandedContent); 24 | 25 | toggler.addEventListener('click', () => { 26 | // Toggle the aria-hidden and aria-expanded values on click 27 | if (hiddenContent) { 28 | hiddenContent = false; 29 | expandedContent = true; 30 | } else { 31 | hiddenContent = true; 32 | expandedContent = false; 33 | } 34 | 35 | // Udate the toggler button and content elements with toggled values 36 | togglerContent.setAttribute('aria-hidden', hiddenContent); 37 | toggler.setAttribute('aria-expanded', expandedContent); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/js/vendor/picturefill.js: -------------------------------------------------------------------------------- 1 | /*! picturefill - v3.0.2 - 2016-02-12 2 | * https://scottjehl.github.io/picturefill/ 3 | * Copyright (c) 2016 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT 4 | */ 5 | /*! Gecko-Picture - v1.0 6 | * https://github.com/scottjehl/picturefill/tree/3.0/src/plugins/gecko-picture 7 | * Firefox's early picture implementation (prior to FF41) is static and does 8 | * not react to viewport changes. This tiny module fixes this. 9 | */ 10 | (function (window) { 11 | /* jshint eqnull:true */ 12 | const ua = navigator.userAgent; 13 | 14 | if ( 15 | window.HTMLPictureElement 16 | && (/ecko/.test(ua) && ua.match(/rv\:(\d+)/) && RegExp.$1 < 45) 17 | ) { 18 | addEventListener( 19 | 'resize', 20 | (function () { 21 | let timer; 22 | 23 | const dummySrc = document.createElement('source'); 24 | 25 | const fixRespimg = function (img) { 26 | let source; let 27 | sizes; 28 | const picture = img.parentNode; 29 | 30 | if (picture.nodeName.toUpperCase() === 'PICTURE') { 31 | source = dummySrc.cloneNode(); 32 | 33 | picture.insertBefore(source, picture.firstElementChild); 34 | setTimeout(() => { 35 | picture.removeChild(source); 36 | }); 37 | } else if (!img._pfLastSize || img.offsetWidth > img._pfLastSize) { 38 | img._pfLastSize = img.offsetWidth; 39 | sizes = img.sizes; 40 | img.sizes += ',100vw'; 41 | setTimeout(() => { 42 | img.sizes = sizes; 43 | }); 44 | } 45 | }; 46 | 47 | const findPictureImgs = function () { 48 | let i; 49 | const imgs = document.querySelectorAll( 50 | 'picture > img, img[srcset][sizes]' 51 | ); 52 | for (i = 0; i < imgs.length; i++) { 53 | fixRespimg(imgs[i]); 54 | } 55 | }; 56 | const onResize = function () { 57 | clearTimeout(timer); 58 | timer = setTimeout(findPictureImgs, 99); 59 | }; 60 | const mq = window.matchMedia && matchMedia('(orientation: landscape)'); 61 | const init = function () { 62 | onResize(); 63 | 64 | if (mq && mq.addListener) { 65 | mq.addListener(onResize); 66 | } 67 | }; 68 | 69 | dummySrc.srcset = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; 70 | 71 | if (/^[c|i]|d$/.test(document.readyState || '')) { 72 | init(); 73 | } else { 74 | document.addEventListener('DOMContentLoaded', init); 75 | } 76 | 77 | return onResize; 78 | }()) 79 | ); 80 | } 81 | }(window)); 82 | 83 | /*! Picturefill - v3.0.2 84 | * http://scottjehl.github.io/picturefill 85 | * Copyright (c) 2015 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; 86 | * License: MIT 87 | */ 88 | 89 | (function (window, document, undefined) { 90 | // Enable strict mode 91 | 92 | 'use strict'; 93 | 94 | // HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround) 95 | document.createElement('picture'); 96 | 97 | let warn; let eminpx; let alwaysCheckWDescriptor; let 98 | evalId; 99 | // local object for method references and testing exposure 100 | const pf = {}; 101 | let isSupportTestReady = false; 102 | const noop = function () {}; 103 | const image = document.createElement('img'); 104 | const getImgAttr = image.getAttribute; 105 | const setImgAttr = image.setAttribute; 106 | const removeImgAttr = image.removeAttribute; 107 | const docElem = document.documentElement; 108 | const types = {}; 109 | const cfg = { 110 | // resource selection: 111 | algorithm: '', 112 | }; 113 | const srcAttr = 'data-pfsrc'; 114 | const srcsetAttr = `${srcAttr}set`; 115 | // ua sniffing is done for undetectable img loading features, 116 | // to do some non crucial perf optimizations 117 | const ua = navigator.userAgent; 118 | const supportAbort = /rident/.test(ua) 119 | || (/ecko/.test(ua) && ua.match(/rv\:(\d+)/) && RegExp.$1 > 35); 120 | let curSrcProp = 'currentSrc'; 121 | const regWDesc = /\s+\+?\d+(e\d+)?w/; 122 | const regSize = /(\([^)]+\))?\s*(.+)/; 123 | const setOptions = window.picturefillCFG; 124 | /** 125 | * Shortcut property for https://w3c.github.io/webappsec/specs/mixedcontent/#restricts-mixed-content ( for easy overriding in tests ) 126 | */ 127 | // baseStyle also used by getEmValue (i.e.: width: 1em is important) 128 | const baseStyle = 'position:absolute;left:0;visibility:hidden;display:block;padding:0;border:none;font-size:1em;width:1em;overflow:hidden;clip:rect(0px, 0px, 0px, 0px)'; 129 | const fsCss = 'font-size:100%!important;'; 130 | let isVwDirty = true; 131 | 132 | let cssCache = {}; 133 | let sizeLengthCache = {}; 134 | let DPR = window.devicePixelRatio; 135 | const units = { 136 | px: 1, 137 | in: 96, 138 | }; 139 | const anchor = document.createElement('a'); 140 | /** 141 | * alreadyRun flag used for setOptions. is it true setOptions will reevaluate 142 | * @type {boolean} 143 | */ 144 | let alreadyRun = false; 145 | 146 | // Reusable, non-"g" Regexes 147 | 148 | // (Don't use \s, to avoid matching non-breaking space.) 149 | const regexLeadingSpaces = /^[ \t\n\r\u000c]+/; 150 | const regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/; 151 | const regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/; 152 | const regexTrailingCommas = /[,]+$/; 153 | const regexNonNegativeInteger = /^\d+$/; 154 | // ( Positive or negative or unsigned integers or decimals, without or without exponents. 155 | // Must include at least one digit. 156 | // According to spec tests any decimal point must be followed by a digit. 157 | // No leading plus sign is allowed.) 158 | // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number 159 | const regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/; 160 | 161 | const on = function (obj, evt, fn, capture) { 162 | if (obj.addEventListener) { 163 | obj.addEventListener(evt, fn, capture || false); 164 | } else if (obj.attachEvent) { 165 | obj.attachEvent(`on${evt}`, fn); 166 | } 167 | }; 168 | 169 | /** 170 | * simple memoize function: 171 | */ 172 | 173 | const memoize = function (fn) { 174 | const cache = {}; 175 | return function (input) { 176 | if (!(input in cache)) { 177 | cache[input] = fn(input); 178 | } 179 | return cache[input]; 180 | }; 181 | }; 182 | 183 | // UTILITY FUNCTIONS 184 | 185 | // Manual is faster than RegEx 186 | // http://jsperf.com/whitespace-character/5 187 | function isSpace(c) { 188 | return ( 189 | c === '\u0020' // space 190 | || c === '\u0009' // horizontal tab 191 | || c === '\u000A' // new line 192 | || c === '\u000C' // form feed 193 | || c === '\u000D' 194 | ); // carriage return 195 | } 196 | 197 | /** 198 | * gets a mediaquery and returns a boolean or gets a css length and returns a number 199 | * @param css mediaqueries or css length 200 | * @returns {boolean|number} 201 | * 202 | * based on: https://gist.github.com/jonathantneal/db4f77009b155f083738 203 | */ 204 | const evalCSS = (function () { 205 | const regLength = /^([\d\.]+)(em|vw|px)$/; 206 | const replace = function () { 207 | const args = arguments; 208 | let index = 0; 209 | let string = args[0]; 210 | while (++index in args) { 211 | string = string.replace(args[index], args[++index]); 212 | } 213 | return string; 214 | }; 215 | 216 | const buildStr = memoize(css => ( 217 | `return ${ 218 | replace( 219 | (css || '').toLowerCase(), 220 | // interpret `and` 221 | /\band\b/g, 222 | '&&', 223 | 224 | // interpret `,` 225 | /,/g, 226 | '||', 227 | 228 | // interpret `min-` as >= 229 | /min-([a-z-\s]+):/g, 230 | 'e.$1>=', 231 | 232 | // interpret `max-` as <= 233 | /max-([a-z-\s]+):/g, 234 | 'e.$1<=', 235 | 236 | // calc value 237 | /calc([^)]+)/g, 238 | '($1)', 239 | 240 | // interpret css values 241 | /(\d+[\.]*[\d]*)([a-z]+)/g, 242 | '($1 * e.$2)', 243 | // make eval less evil 244 | /^(?!(e.[a-z]|[0-9\.&=|><\+\-\*\(\)\/])).*/gi, 245 | '' 246 | ) 247 | };` 248 | )); 249 | 250 | return function (css, length) { 251 | let parsedLength; 252 | if (!(css in cssCache)) { 253 | cssCache[css] = false; 254 | if (length && (parsedLength = css.match(regLength))) { 255 | cssCache[css] = parsedLength[1] * units[parsedLength[2]]; 256 | } else { 257 | /* jshint evil:true */ 258 | try { 259 | cssCache[css] = new Function('e', buildStr(css))(units); 260 | } catch (e) {} 261 | /* jshint evil:false */ 262 | } 263 | } 264 | return cssCache[css]; 265 | }; 266 | }()); 267 | 268 | const setResolution = function (candidate, sizesattr) { 269 | if (candidate.w) { 270 | // h = means height: || descriptor.type === 'h' do not handle yet... 271 | candidate.cWidth = pf.calcListLength(sizesattr || '100vw'); 272 | candidate.res = candidate.w / candidate.cWidth; 273 | } else { 274 | candidate.res = candidate.d; 275 | } 276 | return candidate; 277 | }; 278 | 279 | /** 280 | * 281 | * @param opt 282 | */ 283 | let picturefill = function (opt) { 284 | if (!isSupportTestReady) { 285 | return; 286 | } 287 | 288 | let elements; let i; let 289 | plen; 290 | 291 | const options = opt || {}; 292 | 293 | if (options.elements && options.elements.nodeType === 1) { 294 | if (options.elements.nodeName.toUpperCase() === 'IMG') { 295 | options.elements = [options.elements]; 296 | } else { 297 | options.context = options.elements; 298 | options.elements = null; 299 | } 300 | } 301 | 302 | elements = options.elements 303 | || pf.qsa( 304 | options.context || document, 305 | options.reevaluate || options.reselect ? pf.sel : pf.selShort 306 | ); 307 | 308 | if ((plen = elements.length)) { 309 | pf.setupRun(options); 310 | alreadyRun = true; 311 | 312 | // Loop through all elements 313 | for (i = 0; i < plen; i++) { 314 | pf.fillImg(elements[i], options); 315 | } 316 | 317 | pf.teardownRun(options); 318 | } 319 | }; 320 | 321 | /** 322 | * outputs a warning for the developer 323 | * @param {message} 324 | * @type {Function} 325 | */ 326 | warn = window.console && console.warn 327 | ? function (message) { 328 | console.warn(message); 329 | } 330 | : noop; 331 | 332 | if (!(curSrcProp in image)) { 333 | curSrcProp = 'src'; 334 | } 335 | 336 | // Add support for standard mime types. 337 | types['image/jpeg'] = true; 338 | types['image/gif'] = true; 339 | types['image/png'] = true; 340 | 341 | function detectTypeSupport(type, typeUri) { 342 | // based on Modernizr's lossless img-webp test 343 | // note: asynchronous 344 | const image = new window.Image(); 345 | image.onerror = function () { 346 | types[type] = false; 347 | picturefill(); 348 | }; 349 | image.onload = function () { 350 | types[type] = image.width === 1; 351 | picturefill(); 352 | }; 353 | image.src = typeUri; 354 | return 'pending'; 355 | } 356 | 357 | // test svg support 358 | types['image/svg+xml'] = document.implementation.hasFeature( 359 | 'http://www.w3.org/TR/SVG11/feature#Image', 360 | '1.1' 361 | ); 362 | 363 | /** 364 | * updates the internal vW property with the current viewport width in px 365 | */ 366 | function updateMetrics() { 367 | isVwDirty = false; 368 | DPR = window.devicePixelRatio; 369 | cssCache = {}; 370 | sizeLengthCache = {}; 371 | 372 | pf.DPR = DPR || 1; 373 | 374 | units.width = Math.max(window.innerWidth || 0, docElem.clientWidth); 375 | units.height = Math.max(window.innerHeight || 0, docElem.clientHeight); 376 | 377 | units.vw = units.width / 100; 378 | units.vh = units.height / 100; 379 | 380 | evalId = [units.height, units.width, DPR].join('-'); 381 | 382 | units.em = pf.getEmValue(); 383 | units.rem = units.em; 384 | } 385 | 386 | function chooseLowRes(lowerValue, higherValue, dprValue, isCached) { 387 | let bonusFactor; let tooMuch; let bonus; let 388 | meanDensity; 389 | 390 | // experimental 391 | if (cfg.algorithm === 'saveData') { 392 | if (lowerValue > 2.7) { 393 | meanDensity = dprValue + 1; 394 | } else { 395 | tooMuch = higherValue - dprValue; 396 | bonusFactor = Math.pow(lowerValue - 0.6, 1.5); 397 | 398 | bonus = tooMuch * bonusFactor; 399 | 400 | if (isCached) { 401 | bonus += 0.1 * bonusFactor; 402 | } 403 | 404 | meanDensity = lowerValue + bonus; 405 | } 406 | } else { 407 | meanDensity = dprValue > 1 ? Math.sqrt(lowerValue * higherValue) : lowerValue; 408 | } 409 | 410 | return meanDensity > dprValue; 411 | } 412 | 413 | function applyBestCandidate(img) { 414 | let srcSetCandidates; 415 | const matchingSet = pf.getSet(img); 416 | let evaluated = false; 417 | if (matchingSet !== 'pending') { 418 | evaluated = evalId; 419 | if (matchingSet) { 420 | srcSetCandidates = pf.setRes(matchingSet); 421 | pf.applySetCandidate(srcSetCandidates, img); 422 | } 423 | } 424 | img[pf.ns].evaled = evaluated; 425 | } 426 | 427 | function ascendingSort(a, b) { 428 | return a.res - b.res; 429 | } 430 | 431 | function setSrcToCur(img, src, set) { 432 | let candidate; 433 | if (!set && src) { 434 | set = img[pf.ns].sets; 435 | set = set && set[set.length - 1]; 436 | } 437 | 438 | candidate = getCandidateForSrc(src, set); 439 | 440 | if (candidate) { 441 | src = pf.makeUrl(src); 442 | img[pf.ns].curSrc = src; 443 | img[pf.ns].curCan = candidate; 444 | 445 | if (!candidate.res) { 446 | setResolution(candidate, candidate.set.sizes); 447 | } 448 | } 449 | return candidate; 450 | } 451 | 452 | function getCandidateForSrc(src, set) { 453 | let i; let candidate; let 454 | candidates; 455 | if (src && set) { 456 | candidates = pf.parseSet(set); 457 | src = pf.makeUrl(src); 458 | for (i = 0; i < candidates.length; i++) { 459 | if (src === pf.makeUrl(candidates[i].url)) { 460 | candidate = candidates[i]; 461 | break; 462 | } 463 | } 464 | } 465 | return candidate; 466 | } 467 | 468 | function getAllSourceElements(picture, candidates) { 469 | let i; let len; let source; let 470 | srcset; 471 | 472 | // SPEC mismatch intended for size and perf: 473 | // actually only source elements preceding the img should be used 474 | // also note: don't use qsa here, because IE8 sometimes doesn't like source as the key part in a selector 475 | const sources = picture.getElementsByTagName('source'); 476 | 477 | for (i = 0, len = sources.length; i < len; i++) { 478 | source = sources[i]; 479 | source[pf.ns] = true; 480 | srcset = source.getAttribute('srcset'); 481 | 482 | // if source does not have a srcset attribute, skip 483 | if (srcset) { 484 | candidates.push({ 485 | srcset, 486 | media: source.getAttribute('media'), 487 | type: source.getAttribute('type'), 488 | sizes: source.getAttribute('sizes'), 489 | }); 490 | } 491 | } 492 | } 493 | 494 | /** 495 | * Srcset Parser 496 | * By Alex Bell | MIT License 497 | * 498 | * @returns Array [{url: _, d: _, w: _, h:_, set:_(????)}, ...] 499 | * 500 | * Based super duper closely on the reference algorithm at: 501 | * https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-srcset-attribute 502 | */ 503 | 504 | // 1. Let input be the value passed to this algorithm. 505 | // (TO-DO : Explain what "set" argument is here. Maybe choose a more 506 | // descriptive & more searchable name. Since passing the "set" in really has 507 | // nothing to do with parsing proper, I would prefer this assignment eventually 508 | // go in an external fn.) 509 | function parseSrcset(input, set) { 510 | function collectCharacters(regEx) { 511 | let chars; 512 | const match = regEx.exec(input.substring(pos)); 513 | if (match) { 514 | chars = match[0]; 515 | pos += chars.length; 516 | return chars; 517 | } 518 | } 519 | 520 | const inputLength = input.length; 521 | let url; 522 | let descriptors; 523 | let currentDescriptor; 524 | let state; 525 | let c; 526 | // 2. Let position be a pointer into input, initially pointing at the start 527 | // of the string. 528 | var pos = 0; 529 | // 3. Let candidates be an initially empty source set. 530 | const candidates = []; 531 | 532 | /** 533 | * Adds descriptor properties to a candidate, pushes to the candidates array 534 | * @return undefined 535 | */ 536 | // (Declared outside of the while loop so that it's only created once. 537 | // (This fn is defined before it is used, in order to pass JSHINT. 538 | // Unfortunately this breaks the sequencing of the spec comments. :/ ) 539 | function parseDescriptors() { 540 | // 9. Descriptor parser: Let error be no. 541 | let pError = false; 542 | // 10. Let width be absent. 543 | // 11. Let density be absent. 544 | // 12. Let future-compat-h be absent. (We're implementing it now as h) 545 | let w; 546 | let d; 547 | let h; 548 | let i; 549 | const candidate = {}; 550 | let desc; 551 | let lastChar; 552 | let value; 553 | let intVal; 554 | let floatVal; 555 | 556 | // 13. For each descriptor in descriptors, run the appropriate set of steps 557 | // from the following list: 558 | for (i = 0; i < descriptors.length; i++) { 559 | desc = descriptors[i]; 560 | 561 | lastChar = desc[desc.length - 1]; 562 | value = desc.substring(0, desc.length - 1); 563 | intVal = parseInt(value, 10); 564 | floatVal = parseFloat(value); 565 | 566 | // If the descriptor consists of a valid non-negative integer followed by 567 | // a U+0077 LATIN SMALL LETTER W character 568 | if (regexNonNegativeInteger.test(value) && lastChar === 'w') { 569 | // If width and density are not both absent, then let error be yes. 570 | if (w || d) { 571 | pError = true; 572 | } 573 | 574 | // Apply the rules for parsing non-negative integers to the descriptor. 575 | // If the result is zero, let error be yes. 576 | // Otherwise, let width be the result. 577 | if (intVal === 0) { 578 | pError = true; 579 | } else { 580 | w = intVal; 581 | } 582 | 583 | // If the descriptor consists of a valid floating-point number followed by 584 | // a U+0078 LATIN SMALL LETTER X character 585 | } else if (regexFloatingPoint.test(value) && lastChar === 'x') { 586 | // If width, density and future-compat-h are not all absent, then let error 587 | // be yes. 588 | if (w || d || h) { 589 | pError = true; 590 | } 591 | 592 | // Apply the rules for parsing floating-point number values to the descriptor. 593 | // If the result is less than zero, let error be yes. Otherwise, let density 594 | // be the result. 595 | if (floatVal < 0) { 596 | pError = true; 597 | } else { 598 | d = floatVal; 599 | } 600 | 601 | // If the descriptor consists of a valid non-negative integer followed by 602 | // a U+0068 LATIN SMALL LETTER H character 603 | } else if (regexNonNegativeInteger.test(value) && lastChar === 'h') { 604 | // If height and density are not both absent, then let error be yes. 605 | if (h || d) { 606 | pError = true; 607 | } 608 | 609 | // Apply the rules for parsing non-negative integers to the descriptor. 610 | // If the result is zero, let error be yes. Otherwise, let future-compat-h 611 | // be the result. 612 | if (intVal === 0) { 613 | pError = true; 614 | } else { 615 | h = intVal; 616 | } 617 | 618 | // Anything else, Let error be yes. 619 | } else { 620 | pError = true; 621 | } 622 | } // (close step 13 for loop) 623 | 624 | // 15. If error is still no, then append a new image source to candidates whose 625 | // URL is url, associated with a width width if not absent and a pixel 626 | // density density if not absent. Otherwise, there is a parse error. 627 | if (!pError) { 628 | candidate.url = url; 629 | 630 | if (w) { 631 | candidate.w = w; 632 | } 633 | if (d) { 634 | candidate.d = d; 635 | } 636 | if (h) { 637 | candidate.h = h; 638 | } 639 | if (!h && !d && !w) { 640 | candidate.d = 1; 641 | } 642 | if (candidate.d === 1) { 643 | set.has1x = true; 644 | } 645 | candidate.set = set; 646 | 647 | candidates.push(candidate); 648 | } 649 | } // (close parseDescriptors fn) 650 | 651 | /** 652 | * Tokenizes descriptor properties prior to parsing 653 | * Returns undefined. 654 | * (Again, this fn is defined before it is used, in order to pass JSHINT. 655 | * Unfortunately this breaks the logical sequencing of the spec comments. :/ ) 656 | */ 657 | function tokenize() { 658 | // 8.1. Descriptor tokeniser: Skip whitespace 659 | collectCharacters(regexLeadingSpaces); 660 | 661 | // 8.2. Let current descriptor be the empty string. 662 | currentDescriptor = ''; 663 | 664 | // 8.3. Let state be in descriptor. 665 | state = 'in descriptor'; 666 | 667 | while (true) { 668 | // 8.4. Let c be the character at position. 669 | c = input.charAt(pos); 670 | 671 | // Do the following depending on the value of state. 672 | // For the purpose of this step, "EOF" is a special character representing 673 | // that position is past the end of input. 674 | 675 | // In descriptor 676 | if (state === 'in descriptor') { 677 | // Do the following, depending on the value of c: 678 | 679 | // Space character 680 | // If current descriptor is not empty, append current descriptor to 681 | // descriptors and let current descriptor be the empty string. 682 | // Set state to after descriptor. 683 | if (isSpace(c)) { 684 | if (currentDescriptor) { 685 | descriptors.push(currentDescriptor); 686 | currentDescriptor = ''; 687 | state = 'after descriptor'; 688 | } 689 | 690 | // U+002C COMMA (,) 691 | // Advance position to the next character in input. If current descriptor 692 | // is not empty, append current descriptor to descriptors. Jump to the step 693 | // labeled descriptor parser. 694 | } else if (c === ',') { 695 | pos += 1; 696 | if (currentDescriptor) { 697 | descriptors.push(currentDescriptor); 698 | } 699 | parseDescriptors(); 700 | return; 701 | 702 | // U+0028 LEFT PARENTHESIS (() 703 | // Append c to current descriptor. Set state to in parens. 704 | } else if (c === '\u0028') { 705 | currentDescriptor += c; 706 | state = 'in parens'; 707 | 708 | // EOF 709 | // If current descriptor is not empty, append current descriptor to 710 | // descriptors. Jump to the step labeled descriptor parser. 711 | } else if (c === '') { 712 | if (currentDescriptor) { 713 | descriptors.push(currentDescriptor); 714 | } 715 | parseDescriptors(); 716 | return; 717 | 718 | // Anything else 719 | // Append c to current descriptor. 720 | } else { 721 | currentDescriptor += c; 722 | } 723 | // (end "in descriptor" 724 | 725 | // In parens 726 | } else if (state === 'in parens') { 727 | // U+0029 RIGHT PARENTHESIS ()) 728 | // Append c to current descriptor. Set state to in descriptor. 729 | if (c === ')') { 730 | currentDescriptor += c; 731 | state = 'in descriptor'; 732 | 733 | // EOF 734 | // Append current descriptor to descriptors. Jump to the step labeled 735 | // descriptor parser. 736 | } else if (c === '') { 737 | descriptors.push(currentDescriptor); 738 | parseDescriptors(); 739 | return; 740 | 741 | // Anything else 742 | // Append c to current descriptor. 743 | } else { 744 | currentDescriptor += c; 745 | } 746 | 747 | // After descriptor 748 | } else if (state === 'after descriptor') { 749 | // Do the following, depending on the value of c: 750 | // Space character: Stay in this state. 751 | if (isSpace(c)) { 752 | // EOF: Jump to the step labeled descriptor parser. 753 | } else if (c === '') { 754 | parseDescriptors(); 755 | return; 756 | 757 | // Anything else 758 | // Set state to in descriptor. Set position to the previous character in input. 759 | } else { 760 | state = 'in descriptor'; 761 | pos -= 1; 762 | } 763 | } 764 | 765 | // Advance position to the next character in input. 766 | pos += 1; 767 | 768 | // Repeat this step. 769 | } // (close while true loop) 770 | } 771 | 772 | // 4. Splitting loop: Collect a sequence of characters that are space 773 | // characters or U+002C COMMA characters. If any U+002C COMMA characters 774 | // were collected, that is a parse error. 775 | while (true) { 776 | collectCharacters(regexLeadingCommasOrSpaces); 777 | 778 | // 5. If position is past the end of input, return candidates and abort these steps. 779 | if (pos >= inputLength) { 780 | return candidates; // (we're done, this is the sole return path) 781 | } 782 | 783 | // 6. Collect a sequence of characters that are not space characters, 784 | // and let that be url. 785 | url = collectCharacters(regexLeadingNotSpaces); 786 | 787 | // 7. Let descriptors be a new empty list. 788 | descriptors = []; 789 | 790 | // 8. If url ends with a U+002C COMMA character (,), follow these substeps: 791 | // (1). Remove all trailing U+002C COMMA characters from url. If this removed 792 | // more than one character, that is a parse error. 793 | if (url.slice(-1) === ',') { 794 | url = url.replace(regexTrailingCommas, ''); 795 | // (Jump ahead to step 9 to skip tokenization and just push the candidate). 796 | parseDescriptors(); 797 | 798 | // Otherwise, follow these substeps: 799 | } else { 800 | tokenize(); 801 | } // (close else of step 8) 802 | 803 | // 16. Return to the step labeled splitting loop. 804 | } // (Close of big while loop.) 805 | } 806 | 807 | /* 808 | * Sizes Parser 809 | * 810 | * By Alex Bell | MIT License 811 | * 812 | * Non-strict but accurate and lightweight JS Parser for the string value 813 | * 814 | * Reference algorithm at: 815 | * https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-sizes-attribute 816 | * 817 | * Most comments are copied in directly from the spec 818 | * (except for comments in parens). 819 | * 820 | * Grammar is: 821 | * = # [ , ]? | 822 | * = 823 | * = 824 | * http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-sizes 825 | * 826 | * E.g. "(max-width: 30em) 100vw, (max-width: 50em) 70vw, 100vw" 827 | * or "(min-width: 30em), calc(30vw - 15px)" or just "30vw" 828 | * 829 | * Returns the first valid with a media condition that evaluates to true, 830 | * or "100vw" if all valid media conditions evaluate to false. 831 | * 832 | */ 833 | 834 | function parseSizes(strValue) { 835 | // (Percentage CSS lengths are not allowed in this case, to avoid confusion: 836 | // https://html.spec.whatwg.org/multipage/embedded-content.html#valid-source-size-list 837 | // CSS allows a single optional plus or minus sign: 838 | // http://www.w3.org/TR/CSS2/syndata.html#numbers 839 | // CSS is ASCII case-insensitive: 840 | // http://www.w3.org/TR/CSS2/syndata.html#characters ) 841 | // Spec allows exponential notation for type: 842 | // http://dev.w3.org/csswg/css-values/#numbers 843 | const regexCssLengthWithUnits = /^(?:[+-]?[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?(?:ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmin|vmax|vw)$/i; 844 | 845 | // (This is a quick and lenient test. Because of optional unlimited-depth internal 846 | // grouping parens and strict spacing rules, this could get very complicated.) 847 | const regexCssCalc = /^calc\((?:[0-9a-z \.\+\-\*\/\(\)]+)\)$/i; 848 | 849 | let i; 850 | let unparsedSizesList; 851 | let unparsedSizesListLength; 852 | let unparsedSize; 853 | let lastComponentValue; 854 | let size; 855 | 856 | // UTILITY FUNCTIONS 857 | 858 | // (Toy CSS parser. The goals here are: 859 | // 1) expansive test coverage without the weight of a full CSS parser. 860 | // 2) Avoiding regex wherever convenient. 861 | // Quick tests: http://jsfiddle.net/gtntL4gr/3/ 862 | // Returns an array of arrays.) 863 | function parseComponentValues(str) { 864 | let chrctr; 865 | let component = ''; 866 | let componentArray = []; 867 | const listArray = []; 868 | let parenDepth = 0; 869 | let pos = 0; 870 | let inComment = false; 871 | 872 | function pushComponent() { 873 | if (component) { 874 | componentArray.push(component); 875 | component = ''; 876 | } 877 | } 878 | 879 | function pushComponentArray() { 880 | if (componentArray[0]) { 881 | listArray.push(componentArray); 882 | componentArray = []; 883 | } 884 | } 885 | 886 | // (Loop forwards from the beginning of the string.) 887 | while (true) { 888 | chrctr = str.charAt(pos); 889 | 890 | if (chrctr === '') { 891 | // ( End of string reached.) 892 | pushComponent(); 893 | pushComponentArray(); 894 | return listArray; 895 | } if (inComment) { 896 | if (chrctr === '*' && str[pos + 1] === '/') { 897 | // (At end of a comment.) 898 | inComment = false; 899 | pos += 2; 900 | pushComponent(); 901 | continue; 902 | } else { 903 | pos += 1; // (Skip all characters inside comments.) 904 | continue; 905 | } 906 | } else if (isSpace(chrctr)) { 907 | // (If previous character in loop was also a space, or if 908 | // at the beginning of the string, do not add space char to 909 | // component.) 910 | if ( 911 | (str.charAt(pos - 1) && isSpace(str.charAt(pos - 1))) 912 | || !component 913 | ) { 914 | pos += 1; 915 | continue; 916 | } else if (parenDepth === 0) { 917 | pushComponent(); 918 | pos += 1; 919 | continue; 920 | } else { 921 | // (Replace any space character with a plain space for legibility.) 922 | chrctr = ' '; 923 | } 924 | } else if (chrctr === '(') { 925 | parenDepth += 1; 926 | } else if (chrctr === ')') { 927 | parenDepth -= 1; 928 | } else if (chrctr === ',') { 929 | pushComponent(); 930 | pushComponentArray(); 931 | pos += 1; 932 | continue; 933 | } else if (chrctr === '/' && str.charAt(pos + 1) === '*') { 934 | inComment = true; 935 | pos += 2; 936 | continue; 937 | } 938 | 939 | component += chrctr; 940 | pos += 1; 941 | } 942 | } 943 | 944 | function isValidNonNegativeSourceSizeValue(s) { 945 | if (regexCssLengthWithUnits.test(s) && parseFloat(s) >= 0) { 946 | return true; 947 | } 948 | if (regexCssCalc.test(s)) { 949 | return true; 950 | } 951 | // ( http://www.w3.org/TR/CSS2/syndata.html#numbers says: 952 | // "-0 is equivalent to 0 and is not a negative number." which means that 953 | // unitless zero and unitless negative zero must be accepted as special cases.) 954 | if (s === '0' || s === '-0' || s === '+0') { 955 | return true; 956 | } 957 | return false; 958 | } 959 | 960 | // When asked to parse a sizes attribute from an element, parse a 961 | // comma-separated list of component values from the value of the element's 962 | // sizes attribute (or the empty string, if the attribute is absent), and let 963 | // unparsed sizes list be the result. 964 | // http://dev.w3.org/csswg/css-syntax/#parse-comma-separated-list-of-component-values 965 | 966 | unparsedSizesList = parseComponentValues(strValue); 967 | unparsedSizesListLength = unparsedSizesList.length; 968 | 969 | // For each unparsed size in unparsed sizes list: 970 | for (i = 0; i < unparsedSizesListLength; i++) { 971 | unparsedSize = unparsedSizesList[i]; 972 | 973 | // 1. Remove all consecutive s from the end of unparsed size. 974 | // ( parseComponentValues() already omits spaces outside of parens. ) 975 | 976 | // If unparsed size is now empty, that is a parse error; continue to the next 977 | // iteration of this algorithm. 978 | // ( parseComponentValues() won't push an empty array. ) 979 | 980 | // 2. If the last component value in unparsed size is a valid non-negative 981 | // , let size be its value and remove the component value 982 | // from unparsed size. Any CSS function other than the calc() function is 983 | // invalid. Otherwise, there is a parse error; continue to the next iteration 984 | // of this algorithm. 985 | // http://dev.w3.org/csswg/css-syntax/#parse-component-value 986 | lastComponentValue = unparsedSize[unparsedSize.length - 1]; 987 | 988 | if (isValidNonNegativeSourceSizeValue(lastComponentValue)) { 989 | size = lastComponentValue; 990 | unparsedSize.pop(); 991 | } else { 992 | continue; 993 | } 994 | 995 | // 3. Remove all consecutive s from the end of unparsed 996 | // size. If unparsed size is now empty, return size and exit this algorithm. 997 | // If this was not the last item in unparsed sizes list, that is a parse error. 998 | if (unparsedSize.length === 0) { 999 | return size; 1000 | } 1001 | 1002 | // 4. Parse the remaining component values in unparsed size as a 1003 | // . If it does not parse correctly, or it does parse 1004 | // correctly but the evaluates to false, continue to the 1005 | // next iteration of this algorithm. 1006 | // (Parsing all possible compound media conditions in JS is heavy, complicated, 1007 | // and the payoff is unclear. Is there ever an situation where the 1008 | // media condition parses incorrectly but still somehow evaluates to true? 1009 | // Can we just rely on the browser/polyfill to do it?) 1010 | unparsedSize = unparsedSize.join(' '); 1011 | if (!pf.matchesMedia(unparsedSize)) { 1012 | continue; 1013 | } 1014 | 1015 | // 5. Return size and exit this algorithm. 1016 | return size; 1017 | } 1018 | 1019 | // If the above algorithm exhausts unparsed sizes list without returning a 1020 | // size value, return 100vw. 1021 | return '100vw'; 1022 | } 1023 | 1024 | // namespace 1025 | pf.ns = (`pf${new Date().getTime()}`).substr(0, 9); 1026 | 1027 | // srcset support test 1028 | pf.supSrcset = 'srcset' in image; 1029 | pf.supSizes = 'sizes' in image; 1030 | pf.supPicture = !!window.HTMLPictureElement; 1031 | 1032 | // UC browser does claim to support srcset and picture, but not sizes, 1033 | // this extended test reveals the browser does support nothing 1034 | if (pf.supSrcset && pf.supPicture && !pf.supSizes) { 1035 | (function (image2) { 1036 | image.srcset = 'data:,a'; 1037 | image2.src = 'data:,a'; 1038 | pf.supSrcset = image.complete === image2.complete; 1039 | pf.supPicture = pf.supSrcset && pf.supPicture; 1040 | }(document.createElement('img'))); 1041 | } 1042 | 1043 | // Safari9 has basic support for sizes, but does't expose the `sizes` idl attribute 1044 | if (pf.supSrcset && !pf.supSizes) { 1045 | (function () { 1046 | const width2 = 'data:image/gif;base64,R0lGODlhAgABAPAAAP///wAAACH5BAAAAAAALAAAAAACAAEAAAICBAoAOw=='; 1047 | const width1 = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; 1048 | const img = document.createElement('img'); 1049 | const test = function () { 1050 | const { width } = img; 1051 | 1052 | if (width === 2) { 1053 | pf.supSizes = true; 1054 | } 1055 | 1056 | alwaysCheckWDescriptor = pf.supSrcset && !pf.supSizes; 1057 | 1058 | isSupportTestReady = true; 1059 | // force async 1060 | setTimeout(picturefill); 1061 | }; 1062 | 1063 | img.onload = test; 1064 | img.onerror = test; 1065 | img.setAttribute('sizes', '9px'); 1066 | 1067 | img.srcset = `${width1} 1w,${width2} 9w`; 1068 | img.src = width1; 1069 | }()); 1070 | } else { 1071 | isSupportTestReady = true; 1072 | } 1073 | 1074 | // using pf.qsa instead of dom traversing does scale much better, 1075 | // especially on sites mixing responsive and non-responsive images 1076 | pf.selShort = 'picture>img,img[srcset]'; 1077 | pf.sel = pf.selShort; 1078 | pf.cfg = cfg; 1079 | 1080 | /** 1081 | * Shortcut property for `devicePixelRatio` ( for easy overriding in tests ) 1082 | */ 1083 | pf.DPR = DPR || 1; 1084 | pf.u = units; 1085 | 1086 | // container of supported mime types that one might need to qualify before using 1087 | pf.types = types; 1088 | 1089 | pf.setSize = noop; 1090 | 1091 | /** 1092 | * Gets a string and returns the absolute URL 1093 | * @param src 1094 | * @returns {String} absolute URL 1095 | */ 1096 | 1097 | pf.makeUrl = memoize((src) => { 1098 | anchor.href = src; 1099 | return anchor.href; 1100 | }); 1101 | 1102 | /** 1103 | * Gets a DOM element or document and a selctor and returns the found matches 1104 | * Can be extended with jQuery/Sizzle for IE7 support 1105 | * @param context 1106 | * @param sel 1107 | * @returns {NodeList|Array} 1108 | */ 1109 | pf.qsa = function (context, sel) { 1110 | return 'querySelector' in context ? context.querySelectorAll(sel) : []; 1111 | }; 1112 | 1113 | /** 1114 | * Shortcut method for matchMedia ( for easy overriding in tests ) 1115 | * wether native or pf.mMQ is used will be decided lazy on first call 1116 | * @returns {boolean} 1117 | */ 1118 | pf.matchesMedia = function () { 1119 | if (window.matchMedia && (matchMedia('(min-width: 0.1em)') || {}).matches) { 1120 | pf.matchesMedia = function (media) { 1121 | return !media || matchMedia(media).matches; 1122 | }; 1123 | } else { 1124 | pf.matchesMedia = pf.mMQ; 1125 | } 1126 | 1127 | return pf.matchesMedia.apply(this, arguments); 1128 | }; 1129 | 1130 | /** 1131 | * A simplified matchMedia implementation for IE8 and IE9 1132 | * handles only min-width/max-width with px or em values 1133 | * @param media 1134 | * @returns {boolean} 1135 | */ 1136 | pf.mMQ = function (media) { 1137 | return media ? evalCSS(media) : true; 1138 | }; 1139 | 1140 | /** 1141 | * Returns the calculated length in css pixel from the given sourceSizeValue 1142 | * http://dev.w3.org/csswg/css-values-3/#length-value 1143 | * intended Spec mismatches: 1144 | * * Does not check for invalid use of CSS functions 1145 | * * Does handle a computed length of 0 the same as a negative and therefore invalid value 1146 | * @param sourceSizeValue 1147 | * @returns {Number} 1148 | */ 1149 | pf.calcLength = function (sourceSizeValue) { 1150 | let value = evalCSS(sourceSizeValue, true) || false; 1151 | if (value < 0) { 1152 | value = false; 1153 | } 1154 | 1155 | return value; 1156 | }; 1157 | 1158 | /** 1159 | * Takes a type string and checks if its supported 1160 | */ 1161 | 1162 | pf.supportsType = function (type) { 1163 | return type ? types[type] : true; 1164 | }; 1165 | 1166 | /** 1167 | * Parses a sourceSize into mediaCondition (media) and sourceSizeValue (length) 1168 | * @param sourceSizeStr 1169 | * @returns {*} 1170 | */ 1171 | pf.parseSize = memoize((sourceSizeStr) => { 1172 | const match = (sourceSizeStr || '').match(regSize); 1173 | return { 1174 | media: match && match[1], 1175 | length: match && match[2], 1176 | }; 1177 | }); 1178 | 1179 | pf.parseSet = function (set) { 1180 | if (!set.cands) { 1181 | set.cands = parseSrcset(set.srcset, set); 1182 | } 1183 | return set.cands; 1184 | }; 1185 | 1186 | /** 1187 | * returns 1em in css px for html/body default size 1188 | * function taken from respondjs 1189 | * @returns {*|number} 1190 | */ 1191 | pf.getEmValue = function () { 1192 | let body; 1193 | if (!eminpx && (body = document.body)) { 1194 | const div = document.createElement('div'); 1195 | const originalHTMLCSS = docElem.style.cssText; 1196 | const originalBodyCSS = body.style.cssText; 1197 | 1198 | div.style.cssText = baseStyle; 1199 | 1200 | // 1em in a media query is the value of the default font size of the browser 1201 | // reset docElem and body to ensure the correct value is returned 1202 | docElem.style.cssText = fsCss; 1203 | body.style.cssText = fsCss; 1204 | 1205 | body.appendChild(div); 1206 | eminpx = div.offsetWidth; 1207 | body.removeChild(div); 1208 | 1209 | // also update eminpx before returning 1210 | eminpx = parseFloat(eminpx, 10); 1211 | 1212 | // restore the original values 1213 | docElem.style.cssText = originalHTMLCSS; 1214 | body.style.cssText = originalBodyCSS; 1215 | } 1216 | return eminpx || 16; 1217 | }; 1218 | 1219 | /** 1220 | * Takes a string of sizes and returns the width in pixels as a number 1221 | */ 1222 | pf.calcListLength = function (sourceSizeListStr) { 1223 | // Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33% 1224 | // 1225 | // or (min-width:30em) calc(30% - 15px) 1226 | if (!(sourceSizeListStr in sizeLengthCache) || cfg.uT) { 1227 | const winningLength = pf.calcLength(parseSizes(sourceSizeListStr)); 1228 | 1229 | sizeLengthCache[sourceSizeListStr] = !winningLength 1230 | ? units.width 1231 | : winningLength; 1232 | } 1233 | 1234 | return sizeLengthCache[sourceSizeListStr]; 1235 | }; 1236 | 1237 | /** 1238 | * Takes a candidate object with a srcset property in the form of url/ 1239 | * ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or 1240 | * "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or 1241 | * "images/pic-small.png" 1242 | * Get an array of image candidates in the form of 1243 | * {url: "/foo/bar.png", resolution: 1} 1244 | * where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value 1245 | * If sizes is specified, res is calculated 1246 | */ 1247 | pf.setRes = function (set) { 1248 | let candidates; 1249 | if (set) { 1250 | candidates = pf.parseSet(set); 1251 | 1252 | for (let i = 0, len = candidates.length; i < len; i++) { 1253 | setResolution(candidates[i], set.sizes); 1254 | } 1255 | } 1256 | return candidates; 1257 | }; 1258 | 1259 | pf.setRes.res = setResolution; 1260 | 1261 | pf.applySetCandidate = function (candidates, img) { 1262 | if (!candidates.length) { 1263 | return; 1264 | } 1265 | let candidate; 1266 | let i; 1267 | let j; 1268 | let length; 1269 | let bestCandidate; 1270 | let curSrc; 1271 | let curCan; 1272 | let candidateSrc; 1273 | let abortCurSrc; 1274 | 1275 | const imageData = img[pf.ns]; 1276 | const dpr = pf.DPR; 1277 | 1278 | curSrc = imageData.curSrc || img[curSrcProp]; 1279 | 1280 | curCan = imageData.curCan || setSrcToCur(img, curSrc, candidates[0].set); 1281 | 1282 | // if we have a current source, we might either become lazy or give this source some advantage 1283 | if (curCan && curCan.set === candidates[0].set) { 1284 | // if browser can abort image request and the image has a higher pixel density than needed 1285 | // and this image isn't downloaded yet, we skip next part and try to save bandwidth 1286 | abortCurSrc = supportAbort && !img.complete && curCan.res - 0.1 > dpr; 1287 | 1288 | if (!abortCurSrc) { 1289 | curCan.cached = true; 1290 | 1291 | // if current candidate is "best", "better" or "okay", 1292 | // set it to bestCandidate 1293 | if (curCan.res >= dpr) { 1294 | bestCandidate = curCan; 1295 | } 1296 | } 1297 | } 1298 | 1299 | if (!bestCandidate) { 1300 | candidates.sort(ascendingSort); 1301 | 1302 | length = candidates.length; 1303 | bestCandidate = candidates[length - 1]; 1304 | 1305 | for (i = 0; i < length; i++) { 1306 | candidate = candidates[i]; 1307 | if (candidate.res >= dpr) { 1308 | j = i - 1; 1309 | 1310 | // we have found the perfect candidate, 1311 | // but let's improve this a little bit with some assumptions ;-) 1312 | if ( 1313 | candidates[j] 1314 | && (abortCurSrc || curSrc !== pf.makeUrl(candidate.url)) 1315 | && chooseLowRes( 1316 | candidates[j].res, 1317 | candidate.res, 1318 | dpr, 1319 | candidates[j].cached 1320 | ) 1321 | ) { 1322 | bestCandidate = candidates[j]; 1323 | } else { 1324 | bestCandidate = candidate; 1325 | } 1326 | break; 1327 | } 1328 | } 1329 | } 1330 | 1331 | if (bestCandidate) { 1332 | candidateSrc = pf.makeUrl(bestCandidate.url); 1333 | 1334 | imageData.curSrc = candidateSrc; 1335 | imageData.curCan = bestCandidate; 1336 | 1337 | if (candidateSrc !== curSrc) { 1338 | pf.setSrc(img, bestCandidate); 1339 | } 1340 | pf.setSize(img); 1341 | } 1342 | }; 1343 | 1344 | pf.setSrc = function (img, bestCandidate) { 1345 | let origWidth; 1346 | img.src = bestCandidate.url; 1347 | 1348 | // although this is a specific Safari issue, we don't want to take too much different code paths 1349 | if (bestCandidate.set.type === 'image/svg+xml') { 1350 | origWidth = img.style.width; 1351 | img.style.width = `${img.offsetWidth + 1}px`; 1352 | 1353 | // next line only should trigger a repaint 1354 | // if... is only done to trick dead code removal 1355 | if (img.offsetWidth + 1) { 1356 | img.style.width = origWidth; 1357 | } 1358 | } 1359 | }; 1360 | 1361 | pf.getSet = function (img) { 1362 | let i; let set; let 1363 | supportsType; 1364 | let match = false; 1365 | const { sets } = img[pf.ns]; 1366 | 1367 | for (i = 0; i < sets.length && !match; i++) { 1368 | set = sets[i]; 1369 | 1370 | if ( 1371 | !set.srcset 1372 | || !pf.matchesMedia(set.media) 1373 | || !(supportsType = pf.supportsType(set.type)) 1374 | ) { 1375 | continue; 1376 | } 1377 | 1378 | if (supportsType === 'pending') { 1379 | set = supportsType; 1380 | } 1381 | 1382 | match = set; 1383 | break; 1384 | } 1385 | 1386 | return match; 1387 | }; 1388 | 1389 | pf.parseSets = function (element, parent, options) { 1390 | let srcsetAttribute; let imageSet; let isWDescripor; let 1391 | srcsetParsed; 1392 | 1393 | const hasPicture = parent && parent.nodeName.toUpperCase() === 'PICTURE'; 1394 | const imageData = element[pf.ns]; 1395 | 1396 | if (imageData.src === undefined || options.src) { 1397 | imageData.src = getImgAttr.call(element, 'src'); 1398 | if (imageData.src) { 1399 | setImgAttr.call(element, srcAttr, imageData.src); 1400 | } else { 1401 | removeImgAttr.call(element, srcAttr); 1402 | } 1403 | } 1404 | 1405 | if ( 1406 | imageData.srcset === undefined 1407 | || options.srcset 1408 | || !pf.supSrcset 1409 | || element.srcset 1410 | ) { 1411 | srcsetAttribute = getImgAttr.call(element, 'srcset'); 1412 | imageData.srcset = srcsetAttribute; 1413 | srcsetParsed = true; 1414 | } 1415 | 1416 | imageData.sets = []; 1417 | 1418 | if (hasPicture) { 1419 | imageData.pic = true; 1420 | getAllSourceElements(parent, imageData.sets); 1421 | } 1422 | 1423 | if (imageData.srcset) { 1424 | imageSet = { 1425 | srcset: imageData.srcset, 1426 | sizes: getImgAttr.call(element, 'sizes'), 1427 | }; 1428 | 1429 | imageData.sets.push(imageSet); 1430 | 1431 | isWDescripor = (alwaysCheckWDescriptor || imageData.src) 1432 | && regWDesc.test(imageData.srcset || ''); 1433 | 1434 | // add normal src as candidate, if source has no w descriptor 1435 | if ( 1436 | !isWDescripor 1437 | && imageData.src 1438 | && !getCandidateForSrc(imageData.src, imageSet) 1439 | && !imageSet.has1x 1440 | ) { 1441 | imageSet.srcset += `, ${imageData.src}`; 1442 | imageSet.cands.push({ 1443 | url: imageData.src, 1444 | d: 1, 1445 | set: imageSet, 1446 | }); 1447 | } 1448 | } else if (imageData.src) { 1449 | imageData.sets.push({ 1450 | srcset: imageData.src, 1451 | sizes: null, 1452 | }); 1453 | } 1454 | 1455 | imageData.curCan = null; 1456 | imageData.curSrc = undefined; 1457 | 1458 | // if img has picture or the srcset was removed or has a srcset and does not support srcset at all 1459 | // or has a w descriptor (and does not support sizes) set support to false to evaluate 1460 | imageData.supported = !( 1461 | hasPicture 1462 | || (imageSet && !pf.supSrcset) 1463 | || (isWDescripor && !pf.supSizes) 1464 | ); 1465 | 1466 | if (srcsetParsed && pf.supSrcset && !imageData.supported) { 1467 | if (srcsetAttribute) { 1468 | setImgAttr.call(element, srcsetAttr, srcsetAttribute); 1469 | element.srcset = ''; 1470 | } else { 1471 | removeImgAttr.call(element, srcsetAttr); 1472 | } 1473 | } 1474 | 1475 | if ( 1476 | imageData.supported 1477 | && !imageData.srcset 1478 | && ((!imageData.src && element.src) 1479 | || element.src !== pf.makeUrl(imageData.src)) 1480 | ) { 1481 | if (imageData.src === null) { 1482 | element.removeAttribute('src'); 1483 | } else { 1484 | element.src = imageData.src; 1485 | } 1486 | } 1487 | 1488 | imageData.parsed = true; 1489 | }; 1490 | 1491 | pf.fillImg = function (element, options) { 1492 | let imageData; 1493 | const extreme = options.reselect || options.reevaluate; 1494 | 1495 | // expando for caching data on the img 1496 | if (!element[pf.ns]) { 1497 | element[pf.ns] = {}; 1498 | } 1499 | 1500 | imageData = element[pf.ns]; 1501 | 1502 | // if the element has already been evaluated, skip it 1503 | // unless `options.reevaluate` is set to true ( this, for example, 1504 | // is set to true when running `picturefill` on `resize` ). 1505 | if (!extreme && imageData.evaled === evalId) { 1506 | return; 1507 | } 1508 | 1509 | if (!imageData.parsed || options.reevaluate) { 1510 | pf.parseSets(element, element.parentNode, options); 1511 | } 1512 | 1513 | if (!imageData.supported) { 1514 | applyBestCandidate(element); 1515 | } else { 1516 | imageData.evaled = evalId; 1517 | } 1518 | }; 1519 | 1520 | pf.setupRun = function () { 1521 | if (!alreadyRun || isVwDirty || DPR !== window.devicePixelRatio) { 1522 | updateMetrics(); 1523 | } 1524 | }; 1525 | 1526 | // If picture is supported, well, that's awesome. 1527 | if (pf.supPicture) { 1528 | picturefill = noop; 1529 | pf.fillImg = noop; 1530 | } else { 1531 | // Set up picture polyfill by polling the document 1532 | (function () { 1533 | let isDomReady; 1534 | const regReady = window.attachEvent ? /d$|^c/ : /d$|^c|^i/; 1535 | 1536 | var run = function () { 1537 | const readyState = document.readyState || ''; 1538 | 1539 | timerId = setTimeout(run, readyState === 'loading' ? 200 : 999); 1540 | if (document.body) { 1541 | pf.fillImgs(); 1542 | isDomReady = isDomReady || regReady.test(readyState); 1543 | if (isDomReady) { 1544 | clearTimeout(timerId); 1545 | } 1546 | } 1547 | }; 1548 | 1549 | var timerId = setTimeout(run, document.body ? 9 : 99); 1550 | 1551 | // Also attach picturefill on resize and readystatechange 1552 | // http://modernjavascript.blogspot.com/2013/08/building-better-debounce.html 1553 | const debounce = function (func, wait) { 1554 | let timeout; let 1555 | timestamp; 1556 | var later = function () { 1557 | const last = new Date() - timestamp; 1558 | 1559 | if (last < wait) { 1560 | timeout = setTimeout(later, wait - last); 1561 | } else { 1562 | timeout = null; 1563 | func(); 1564 | } 1565 | }; 1566 | 1567 | return function () { 1568 | timestamp = new Date(); 1569 | 1570 | if (!timeout) { 1571 | timeout = setTimeout(later, wait); 1572 | } 1573 | }; 1574 | }; 1575 | let lastClientWidth = docElem.clientHeight; 1576 | const onResize = function () { 1577 | isVwDirty = Math.max(window.innerWidth || 0, docElem.clientWidth) 1578 | !== units.width || docElem.clientHeight !== lastClientWidth; 1579 | lastClientWidth = docElem.clientHeight; 1580 | if (isVwDirty) { 1581 | pf.fillImgs(); 1582 | } 1583 | }; 1584 | 1585 | on(window, 'resize', debounce(onResize, 99)); 1586 | on(document, 'readystatechange', run); 1587 | }()); 1588 | } 1589 | 1590 | pf.picturefill = picturefill; 1591 | // use this internally for easy monkey patching/performance testing 1592 | pf.fillImgs = picturefill; 1593 | pf.teardownRun = noop; 1594 | 1595 | /* expose methods for testing */ 1596 | picturefill._ = pf; 1597 | 1598 | window.picturefillCFG = { 1599 | pf, 1600 | push(args) { 1601 | const name = args.shift(); 1602 | if (typeof pf[name] === 'function') { 1603 | pf[name].apply(pf, args); 1604 | } else { 1605 | cfg[name] = args[0]; 1606 | if (alreadyRun) { 1607 | pf.fillImgs({ reselect: true }); 1608 | } 1609 | } 1610 | }, 1611 | }; 1612 | 1613 | while (setOptions && setOptions.length) { 1614 | window.picturefillCFG.push(setOptions.shift()); 1615 | } 1616 | 1617 | /* expose picturefill */ 1618 | window.picturefill = picturefill; 1619 | 1620 | /* expose picturefill */ 1621 | if (typeof module === 'object' && typeof module.exports === 'object') { 1622 | // CommonJS, just export 1623 | module.exports = picturefill; 1624 | } else if (typeof define === 'function' && define.amd) { 1625 | // AMD support 1626 | define('picturefill', () => picturefill); 1627 | } 1628 | 1629 | // IE8 evals this sync, so it must be the last thing we do 1630 | if (!pf.supPicture) { 1631 | types['image/webp'] = detectTypeSupport( 1632 | 'image/webp', 1633 | 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==' 1634 | ); 1635 | } 1636 | }(window, document)); 1637 | -------------------------------------------------------------------------------- /src/markup/layout/collection.hbs: -------------------------------------------------------------------------------- 1 | {{#extend "drizzle"}} 2 | {{#content "main"}} 3 |

4 | Components/ 5 | {{name}} 6 |

7 | 8 | {{#if notes}} 9 |
10 | {{{markdown notes}}} 11 |
12 | {{/if}} 13 | 14 | 15 | {{#each patterns}} 16 | {{> patterns.drizzle.partials.item}} 17 | {{/each}} 18 | {{/content}} 19 | {{/extend}} 20 | -------------------------------------------------------------------------------- /src/markup/layout/drizzle.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{title}}{{name}} | {{data "global.companyName"}} Design System 10 | 11 | {{> patterns.drizzle.partials.stylesheet-link-tags}} 12 | 13 | 14 | {{> patterns.drizzle.partials.sidebar}} 15 | 16 |
17 |
18 | {{#block "body"}} 19 | {{#block "main"}} 20 | 21 | {{/block}} 22 | {{/block}} 23 |
24 |
25 | 26 | {{> patterns.drizzle.partials.script-tags}} 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/markup/layout/page.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{ title }} 10 | 11 | {{> patterns.drizzle.partials.stylesheet-link-tags}} 12 | 13 | 14 |
Skip to main content 15 | 16 |
17 | {{#block "body"}} 18 | {{#block "main"}} 19 | 20 | {{/block}} 21 | {{/block}} 22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/markup/pages/demos/common-elements.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | title: Common Elements 3 | order: 20 4 | layout: page 5 | --- 6 | 7 |
8 | 9 |

{{ title }}

10 | 11 |

12 | This paragraph shows a few inline styles. 13 | Here's some important text. 14 | Here's some emphasized text. 15 | This is what a link looks like. 16 |

17 | 18 |

Heading Level Two

19 | 20 |

This paragraph is used to show space between the previous and following elements. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras a ex nisl. Nullam sit amet sodales ante. Duis aliquet lorem sit amet erat commodo rhoncus.

21 | 22 |

Heading Level Three

23 | 24 |

This paragraph is used to show space between the previous and following elements. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras a ex nisl. Nullam sit amet sodales ante. Duis aliquet lorem sit amet erat commodo rhoncus.

25 | 26 |

Heading Level Four

27 | 28 |

This paragraph is used to show space between the previous and following elements. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras a ex nisl. Nullam sit amet sodales ante. Duis aliquet lorem sit amet erat commodo rhoncus.

29 | 30 |
Heading Level Five
31 | 32 |

This paragraph is used to show space between the previous and following elements. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras a ex nisl. Nullam sit amet sodales ante. Duis aliquet lorem sit amet erat commodo rhoncus.

33 | 34 |
Heading Level Six
35 | 36 |

This paragraph is used to show space between the previous and following elements. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras a ex nisl. Nullam sit amet sodales ante. Duis aliquet lorem sit amet erat commodo rhoncus.

37 | 38 |
39 |
    40 |
  1. Lorem ipsum dolor sit amet
  2. 41 |
  3. Consectetur adipiscing elit
  4. 42 |
  5. Quisque viverra a ex at semper
  6. 43 |
  7. Cras sagittis elit vel porta vehicula
  8. 44 |
45 |
46 | 47 |

This paragraph is used to show space between the previous and following elements. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras a ex nisl. Nullam sit amet sodales ante. Duis aliquet lorem sit amet erat commodo rhoncus.

48 | 49 |
50 | 56 |
57 | 58 |

This paragraph is used to show space between the previous and following elements. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras a ex nisl. Nullam sit amet sodales ante. Duis aliquet lorem sit amet erat commodo rhoncus.

59 | 60 |
61 | -------------------------------------------------------------------------------- /src/markup/pages/documentation/grid.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | title: Grid 3 | layout: drizzle 4 | --- 5 | 6 |

{{title}}

7 | 8 |

The grid system we are using for page layouts is based on a 12 column grid with 20px gutters between each column. Grid classes can be nested if needed for more complex layouts.

9 | 10 |

Simple Grid

11 | 12 |

Most grid layouts are going to use either a two, three, or four column layout. The basic grid system includes all of the classes needed to create a simple layout with class names that are spelled out and easy to read.

13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 |
 37 |   
 38 |     <div class="obj-grid">
 39 |       <div class="obj-grid__full"></div>
 40 | 
 41 |       <div class="obj-grid__half"></div>
 42 |       <div class="obj-grid__half"></div>
 43 | 
 44 |       <div class="obj-grid__third"></div>
 45 |       <div class="obj-grid__third"></div>
 46 |       <div class="obj-grid__third"></div>
 47 | 
 48 |       <div class="obj-grid__quarter"></div>
 49 |       <div class="obj-grid__quarter"></div>
 50 |       <div class="obj-grid__quarter"></div>
 51 |       <div class="obj-grid__quarter"></div>
 52 | 
 53 |       <div class="obj-grid__third"></div>
 54 |       <div class="obj-grid__twobj-third"></div>
 55 | 
 56 |       <div class="obj-grid__quarter"></div>
 57 |       <div class="obj-grid__three-quarter"></div>
 58 |     </div>
 59 |   
 60 | 
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
ClassDescription
.obj-gridThis is the class on the container that wraps around the grid columns.
.obj-grid__fullFor columns that span the entire width of the grid (12 columns).
.obj-grid__three-quarterFor columns that three quarter width of the grid (9 columns).
.obj-grid__twobj-thirdFor columns that span two third width of the grid (8 columns).
.obj-grid__halfFor columns that span half of the width of the grid (6 columns).
.obj-grid__thirdFor columns that span a third of the width of the grid (4 columns).
.obj-grid__quarterFor columns that span a quarter of the width of the grid (3 columns).
96 | 97 |

Responsive Grid

98 | 99 |

To adjust a the width of a column at different breakpoints you can add @sm, @md, @lg, @xl after any of the grid classes:

100 | 101 |
102 |
103 |
104 |
105 | 106 |
107 |
108 |
109 |
110 | 111 |
112 |   
113 |     <div class="obj-grid">
114 |       <div class="obj-grid__half obj-grid__third@sm"></div>
115 |       <div class="obj-grid__half obj-grid__third@sm"></div>
116 |       <div class="obj-grid__full obj-grid__third@sm"></div>
117 | 
118 |       <div class="obj-grid__full obj-grid__quarter@md"></div>
119 |       <div class="obj-grid__full obj-grid__half@md"></div>
120 |       <div class="obj-grid__full obj-grid__quarter@md"></div>
121 |     </div>
122 |   
123 | 
124 | 125 |

Extended Grid

126 | 127 |

In addition to the simple grid system there is an extended grid system that allows for any combination of a 12 column grid system. The extended grid system can also be used responsively in the same way that as the gimple grid system.

128 | 129 | 130 |
131 |
132 |
133 | 134 |
135 |
136 | 137 |
138 |
139 | 140 |
141 |
142 | 143 |
144 |
145 | 146 |
147 |
148 |
149 | 150 |
151 |   
152 |     <div class="obj-grid">
153 |       <div class="obj-grid__1-12"></div>
154 |       <div class="obj-grid__11-12"></div>
155 | 
156 |       <div class="obj-grid__2-12"></div>
157 |       <div class="obj-grid__10-12"></div>
158 | 
159 |       <div class="obj-grid__3-12"></div>
160 |       <div class="obj-grid__9-12"></div>
161 | 
162 |       <div class="obj-grid__4-12"></div>
163 |       <div class="obj-grid__8-12"></div>
164 | 
165 |       <div class="obj-grid__5-12"></div>
166 |       <div class="obj-grid__7-12"></div>
167 | 
168 |       <div class="obj-grid__6-12"></div>
169 |       <div class="obj-grid__6-12"></div>
170 |     </div>
171 |   
172 | 
173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 |
ClassDescription
.obj-gridThis is the class on the container that wraps around the grid columns.
.obj-grid__12-12For columns that span the entire width of the grid (12 columns).
.obj-grid__11-12For columns that span 11/12ths of the grid (11 columns).
.obj-grid__10-12For columns that span 5/6ths width of the grid (10 columns).
.obj-grid__9-12For columns that three quarter width of the grid (9 columns).
.obj-grid__8-12For columns that span two third width of the grid (8 columns).
.obj-grid__7-12For columns that span 7/12ths width of the grid (7 columns).
.obj-grid__6-12For columns that span half of the width of the grid (6 columns).
.obj-grid__5-12For columns that span 5/12ths width of the grid (5 columns).
.obj-grid__4-12For columns that span a third of the width of the grid (4 columns).
.obj-grid__3-12For columns that span a quarter of the width of the grid (3 columns).
.obj-grid__2-12For columns that span one sixth width of the grid (2 columns).
.obj-grid__1-12For columns that span 1/12th width of the grid (1 columns).
232 | -------------------------------------------------------------------------------- /src/markup/pages/documentation/page-width.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | title: Page Width 3 | layout: page 4 | --- 5 | 6 |
7 |

{{title}}

8 |
9 | 10 |
11 |

By default, text will touch the edge of the browser, like this.

12 |
13 | 14 |
15 | 16 |

The obj-width-limiter object has two primary functions:

17 | 18 |
    19 |
  1. Apply horizontal padding to create a page margin. When the screen is small, this prevents text from touching the edge of the browser.
  2. 20 |
  3. Apply a max-width, and center the element when the max width is reached. When the screen is large, this element will be horizontally centered within its parent. This often means the element is centered within the whole page.
  4. 21 |
22 | 23 |
24 | 25 |
26 |

If you want to use a obj-width-limiter but don't want the included page margin, add an additional obj-width-limiter--no-page-margin modifier class.

27 |
28 | -------------------------------------------------------------------------------- /src/markup/pages/documentation/spacing.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spacing 3 | layout: drizzle 4 | --- 5 | 6 |

{{title}}

7 | 8 |

9 | Spacing classes can be used to stitch together components. This is useful 10 | because if you build the spacing before and after a component into the 11 | component itself, it's less likely to be reusable in a different context. 12 |

13 | 14 | {{#with (data 'spacing')}} 15 | 16 |

Margin

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {{#each spacing-classes}} 27 | 28 | 33 | 34 | 35 | {{/each}} 36 | 37 |
ClassDescription
29 |
30 | .{{class}} 31 |
32 |
{{description}}
38 | 39 |

Padding

40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {{#each padding-classes}} 49 | 50 | 55 | 56 | 57 | {{/each}} 58 | 59 |
ClassDescription
51 |
52 | .{{class}} 53 |
54 |
{{description}}
60 | {{/with}} 61 | 62 | 63 |

Responsive Spacing

64 |

To adjust margin and padding at different breakpoints you can add @sm, @md, @lg, @xl after any of the spacing classes:

65 | 66 |
67 |
68 |
69 |
70 |
71 | 72 |
73 |   
74 |     <div class="util-margin-bottom-sm util-margin-bottom-md@md util-margin-bottom-lg@lg"></div>
75 |     <div class="util-margin-bottom-sm util-margin-bottom-md@md util-margin-bottom-lg@lg"></div>
76 |     <div class="util-margin-bottom-sm util-margin-bottom-md@md util-margin-bottom-lg@lg"></div>
77 |   
78 | 
79 | -------------------------------------------------------------------------------- /src/markup/pages/index.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | layout: drizzle 4 | main-class: drizzle-white-bg 5 | --- 6 | 7 |

{{data "global.companyName"}} Design System

8 | 9 |

This UI toolkit is a highly-modular design system for rapid web page development. It contains different materials that can be assembled into more complex page layouts. It includes working examples, code snippets and documentation.

10 | 11 |

Demo Pages

12 |
13 | 20 |
21 | -------------------------------------------------------------------------------- /src/markup/patterns/components/buttons/btn-with-icon.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | name: Button (with Icon) 3 | order: 5 4 | notes: | 5 | Icons can be added to buttons by using the [SVG sprite sheet](/svg/symbol/sprite.symbol.html) 6 | 7 | variantGroups: 8 | - name: Color 9 | set: 10 | - cmp-button--primary 11 | - cmp-button--secondary 12 | - cmp-button--tertiary 13 | - name: Size 14 | set: 15 | - "" 16 | - cmp-button--lg 17 | - cmp-button--sm 18 | - name: Other 19 | set: 20 | - "" 21 | - cmp-button--full-width 22 | --- 23 | 24 | Click Me 25 | 26 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/markup/patterns/components/buttons/btn.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | name: Button 3 | order: 1 4 | 5 | variantGroups: 6 | - name: Color 7 | set: 8 | - cmp-button--primary 9 | - cmp-button--secondary 10 | - cmp-button--tertiary 11 | - name: Size 12 | set: 13 | - "" 14 | - cmp-button--lg 15 | - cmp-button--sm 16 | - name: Other 17 | set: 18 | - "" 19 | - cmp-button--full-width 20 | --- 21 | Click Me 22 | -------------------------------------------------------------------------------- /src/markup/patterns/components/buttons/collection.yaml: -------------------------------------------------------------------------------- 1 | notes: | 2 | Buttons are used to indicate that an element is clickable. These button styles can be applied to a, button, or input elements -------------------------------------------------------------------------------- /src/markup/patterns/components/defaults/collection.yaml: -------------------------------------------------------------------------------- 1 | notes: | 2 | These components apply default styling to elements. This is useful in cases where applying a class to an element removes its default styling. -------------------------------------------------------------------------------- /src/markup/patterns/components/defaults/heading.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | name: Headings 3 | notes: | 4 | Use these heading classes on any element to add heading styles. This lets you apply heading styles for alternate heading levels as well. For example, `.cmp-heading-1` can go on an `

`, but it can also go on an `

`, or any other element that should look like the styles defined in the `.cmp-heading-1` class. 5 | --- 6 | 7 |
Heading 1
8 |
Heading 2
9 |
Heading 3
10 |
Heading 4
11 |
Heading 5
12 |
Heading 6
13 | -------------------------------------------------------------------------------- /src/markup/patterns/components/defaults/link.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | notes: | 3 | Use this to create default-looking links. 4 | --- 5 | Click Me 6 | -------------------------------------------------------------------------------- /src/markup/patterns/components/defaults/list--ordered.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | name: List (ordered) 3 | notes: | 4 | Use this to create default-looking ordered lists. 5 | --- 6 |
    7 |
  1. Lorem ipsum dolor sit amet
  2. 8 |
  3. Consectetur adipiscing elit
  4. 9 |
  5. Quisque viverra a ex at semper
  6. 10 |
  7. Cras sagittis elit vel porta vehicula
  8. 11 |
12 | -------------------------------------------------------------------------------- /src/markup/patterns/components/defaults/list--unordered.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | name: List (unordered) 3 | notes: | 4 | Use this to create default-looking unordered lists. 5 | --- 6 | 12 | -------------------------------------------------------------------------------- /src/markup/patterns/components/defaults/paragraph.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | notes: | 3 | Use this to create default-looking paragraphs. 4 | --- 5 |

Sed velit dignissim sodales ut eu sem integer vitae justo eget magna fermentum iaculis eu non diam phasellus vestibulum lorem sed risus. Venenatis urna cursus eget nunc scelerisque viverra mauris?

6 | -------------------------------------------------------------------------------- /src/markup/patterns/components/interactive/collection.yaml: -------------------------------------------------------------------------------- 1 | notes: | 2 | These components use JS to enhance users' experiences. 3 | -------------------------------------------------------------------------------- /src/markup/patterns/components/interactive/expandable-content.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | name: Expandable Content 3 | order: 20 4 | notes: | 5 | This can be used to provide content that's hidden by default, and opened by a user interaction. Additional styles can be added to the `&__button` and `&__content` elements to customize the look and feel of this component, while keeping the hide/show functionality in place. 6 | --- 7 |
8 | 17 |
18 | Nulla facilisi morbi tempus iaculis urna, id volutpat lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis aenean et? Vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique! 19 |
20 | 21 |
22 | -------------------------------------------------------------------------------- /src/markup/patterns/drizzle/partials/item.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | # This "hidden" is needed, or else 3 | # the pattern builder script fails 4 | hidden: true 5 | --- 6 |
7 | {{#> drizzle.labelheader tag="h2" id="(toSlug name)-pattern" labels=data.labels}} 8 |
9 |

10 | {{name}} 11 |

12 | 13 | {{#if data.variantGroups}} 14 | 22 | {{/if}} 23 |
24 | {{/drizzle.labelheader}} 25 | 26 | 27 | {{#if data.variantGroups}} 28 |
29 | {{#each data.variantGroups}} 30 |
31 | {{this.name}} 32 | {{#each this.set}} 33 | 41 | {{/each}} 42 |
43 | {{/each}} 44 |
45 | {{/if}} 46 | 47 | {{!-- Notes --}} 48 | {{#if data.notes}} 49 |
50 | {{{data.notes}}} 51 |
52 | {{/if}} 53 | 54 | {{#if data.options}} 55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | {{#each data.options}} 65 | 66 | 67 | 68 | 69 | {{/each}} 70 | 71 |
AttributeDescription
{{{attribute}}}{{{description}}}
72 |
73 | {{/if}} 74 | 75 | {{#if data.contentBlocks}} 76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | {{#each data.contentBlocks}} 86 | 87 | 88 | 89 | 90 | {{/each}} 91 | 92 |
Block NameDescription
{{{name}}}{{{description}}}
93 |
94 | {{/if}} 95 | 96 | 97 | {{!-- Links --}} 98 | {{#if data.links}} 99 |
100 | 113 |
114 | {{/if}} 115 | {{!-- Preview --}} 116 |
117 |

View

118 |
119 | {{{pattern id @root}}} 120 |
121 | {{!-- Code sample --}} 122 | {{#unless data.sourceless}} 123 |
124 |

HTML

125 | 126 |
127 |
{{{patternSource id @root}}}
128 | {{/unless}} 129 |
130 |
131 | -------------------------------------------------------------------------------- /src/markup/patterns/drizzle/partials/script-tags.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/markup/patterns/drizzle/partials/sidebar.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | hidden: true 3 | --- 4 | {{#*inline "nav-item"}} 5 | {{label}} 6 | {{/inline}} 7 | 8 | 52 | -------------------------------------------------------------------------------- /src/markup/patterns/drizzle/partials/stylesheet-link-tags.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/public/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkbox/design-system-starter/dd04e64f13dc9c70d5bc881ec5fc354395a16f18/src/public/.keep -------------------------------------------------------------------------------- /src/scss/base.scss: -------------------------------------------------------------------------------- 1 | // Introduction 2 | // Welcome to the CSS file! This is subjectively the most exciting code 3 | // of the website. Here we bring together the visual style of the website. 4 | // 5 | // Our CSS is organized using Harry Roberts' (csswizardry.com) Inverted 6 | // Triangle CSS (ITCSS) organizational approach. This method is mixed with 7 | // Block Element Modifier (BEM) naming convention for class names throughout 8 | // the stylesheet. 9 | // 10 | // Table of Contents: 11 | // 1. Settings 12 | // 2. Tools 13 | // 3. Generic 14 | // 4. Elements 15 | // 5. Objects 16 | // 6. Components 17 | // 7. Vendors 18 | // 8. Utilities 19 | 20 | // ===================================================================== 21 | // 1. Settings 22 | // When using a preprocessor, settings are used to define variable. 23 | // This puts variables at the top of the cascade so they are accessible 24 | // to all partials further down. No CSS selectors should be generated 25 | // by the preprocessor from partials in this section. 26 | 27 | // When not using a preprocessor, the settings area is a good place 28 | // to write comments describing the colors and font stacks as a guide. 29 | // If using features such as custom properties, those that are global 30 | // custom properties should be described in this section. 31 | @import "settings/variables"; 32 | @import "settings/colors"; 33 | @import "settings/fonts"; 34 | @import "settings/z-index"; 35 | 36 | 37 | // ===================================================================== 38 | // 2. Tools 39 | // This section is specifically for preprocessor global mixins and 40 | // functions. No CSS should be generated by the preprocessor from 41 | // partials in this section. 42 | @import "tools/functions"; 43 | @import "tools/mixins"; 44 | 45 | 46 | // ===================================================================== 47 | // 3. Generic 48 | // Here we define any generic styles that are not specific to the styles of 49 | // the site. This section should include any Reset or Normalize libraries 50 | // and any preferencial base styles for elements. There shouldn’t be any 51 | // classes or ids used in this section. 52 | @import "generic/normalize"; 53 | @import "generic/keyframes"; 54 | @import "generic/safe-focus"; 55 | 56 | 57 | // ===================================================================== 58 | // 4. Elements 59 | // Styling for bare HTML elements (like H1, A, etc.). These come with 60 | // default styling from the browser so we can redefine them here. 61 | @import "elements/asterisk"; 62 | @import "elements/root"; 63 | @import "elements/html-body"; 64 | @import "elements/a"; 65 | @import "elements/p"; 66 | @import "elements/h1-6"; 67 | @import "elements/img"; 68 | @import "elements/ol-ul"; 69 | 70 | 71 | // ===================================================================== 72 | // 5. Objects 73 | // Class-based selectors which define undecorated design patterns, 74 | // for example media object known from OOCSS. 75 | @import "object/grid"; 76 | @import "object/width-limiter"; 77 | 78 | 79 | // ===================================================================== 80 | // 6. Components 81 | // Specific UI components. This is where majority of our work takes place 82 | // and our UI components are often composed of Objects and Components 83 | @import "components/heading"; 84 | @import "components/paragraph"; 85 | @import "components/link"; 86 | @import "components/list"; 87 | @import "components/button"; 88 | @import "components/skip-to-content"; 89 | @import "components/expandable-content"; 90 | @import "components/icon"; 91 | 92 | // ===================================================================== 93 | // 7. Vendors 94 | // Whenever third party styles are needed add them to this section, 95 | // below the Components, but above the Utilities. This allows them 96 | // to live alongside our styles but still be overrided if needed by 97 | // a utility class. 98 | 99 | 100 | // ===================================================================== 101 | // 8. Utilities 102 | // Utilities and helper classes with ability to override anything 103 | // which goes before in the triangle, eg. hide helper class 104 | @import "utilities/display"; 105 | @import "utilities/spacing"; 106 | @import "utilities/text"; 107 | -------------------------------------------------------------------------------- /src/scss/components/_button.scss: -------------------------------------------------------------------------------- 1 | .cmp-button { 2 | // Reset default