Merge pull request #7 from purtuga/master

box-icon Custom Element support (supports #6)
This commit is contained in:
Atisa 2018-07-01 03:56:30 +05:30 committed by GitHub
commit 8cb9a6628c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 3441 additions and 1902 deletions

View file

@ -30,6 +30,8 @@ import 'boxicons/css/boxicons.css';
```
## Usage
### Using via CSS
1. Include the stylesheet on your document's `<head>`
```html
@ -60,6 +62,51 @@ Instead of installing you may use the remote version
[Check out all the icons here!](https://boxicons.com)
### Using via Web Component
Boxicons includes a Custom Element that makes using icons easy and efficient. To use it, add the `box-icon-element.js` file to the page:
```html
<script src="https://unpkg.com/boxicons@latest/dist/box-icon-element.js"></script>
```
To use an icon, add the `<box-icon>` element to the location where the icon should be displayed:
```html
<box-icon name="hot"></box-icon>
```
The `<box-icon>` custom element supports the following attributes:
```html
<box-icon
name="adjust|alarms|etc...."
color="blue|red|etc..."
size="xs|sm|md|lg|cssSize"
rotate="90|180|270"
flip="horizontal|vertical"
shape="square|circle"
animation="spin|tada|etc..."
></box-icon>
```
- `name` : (REQUIRED) the name of the icon to be displayed
- `color`: A color for the icon.
- `size`: The size for the icon. It supports one of two types of values:
- One of the followign shortcuts: `xs`, `sm`, `md`, `lg`
- A css unit size (ex. `60px`)
- `rotate`: one of the following values: `90`, `180`, `270`
- `flip`: one of the following values: `horizontal`, `vertical`
- `shape`: one of the following values: `square`, `circle`
- `animation`: One of the following values: `spin`, `tada`, `flashing`, `burst`, `fade-left`, `fade-right`, `spin-hover`, `tada-hover`, `flashing-hover`, `burst-hover`, `fade-left-hover`, `fade-right-hover`
The Custom Element class (`BoxIconElemnet`) exposes the following static members:
- `tagName`: property that holds the HTML element tag name. Default: `box-icon`
- `defined([tagName])`: Defines the Element in the custom element registry using either the tagName provided on input or the (default) the one defined on the Class.
- `cdnUrl`: property that holds the URL that will be used to retrieve the images. URL should point to the folder that contains the images. example: `//unpkg.com/boxicons@1.1.1/svg` (no trailing forward slash)
- `getIconSvg(iconName)`: method used to retrieve the SVG image. Should return a Promise that resolves with the SVG source (String).
## License
[You can read the license here!](https://boxicons.com/get-started#license)

110
dev.box-icon-element.html Normal file

File diff suppressed because one or more lines are too long

2
dist/box-icon-element.js vendored Normal file

File diff suppressed because one or more lines are too long

4417
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
{
"name": "boxicons",
"version": "1.1.0",
"version": "1.1.1",
"private": true,
"description": "High Quality web friendly icons",
"scripts": {
"start": "webpack-dev-server --devtool eval-source-map --history-api-fallback --open",
"start": "webpack-dev-server --history-api-fallback --hot --open",
"build": "webpack -p",
"optimize-svg": "svgo --config=.svgo.yml -f static/img/svg"
},
@ -25,16 +25,20 @@
"babel-core": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.2",
"babel-plugin-transform-builtin-classes": "^0.6.1",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-1": "^6.24.1",
"css-loader": "^0.28.11",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^14.1.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.10.3",
"svgo": "^1.0.5",
"to-string-loader": "^1.1.5",
"webpack": "^3.6.0",
"webpack-dev-server": "^2.8.2"
"webpack-dev-server": "^2.8.2",
"wrapper-webpack-plugin": "^1.0.0"
}
}

233
src/box-icon-element.js Normal file
View file

@ -0,0 +1,233 @@
/* global BUILD */
import animationsCss from '../static/css/animations.css';
import transformationsCss from '../static/css/transformations.css';
//= ======================================================
const GLOBAL = window;
const CACHE = {}; // iconName: Promise()
const CSS_CLASS_PREFIX = 'bx-';
const CSS_CLASS_PREFIX_ROTATE = `${CSS_CLASS_PREFIX}rotate-`;
const CSS_CLASS_PREFIX_FLIP = `${CSS_CLASS_PREFIX}flip-`;
const TEMPLATE = document.createElement('template');
const usingShadyCss = () => !!GLOBAL.ShadyCSS;
TEMPLATE.innerHTML = `
<style>
:host {
display: inline-block;
font-size: initial;
box-sizing: border-box;
width: 24px;
height: 24px;
}
:host([size=xs]) {
width: 0.8rem;
height: 0.8rem;
}
:host([size=sm]) {
width: 1.55rem;
height: 1.55rem;
}
:host([size=md]) {
width: 2.25rem;
height: 2.25rem;
}
:host([size=lg]) {
width: 3.0rem;
height: 3.0rem;
}
:host([size]:not([size=""]):not([size=xs]):not([size=sm]):not([size=md]):not([size=lg])) {
width: auto;
height: auto;
}
:host([shape=square]) #icon {
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: .25em;
}
:host([shape=circle]) #icon {
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: 50%;
}
#icon,
svg {
width: 100%;
height: 100%;
}
#icon {
box-sizing: border-box;
}
${animationsCss}
${transformationsCss}
</style>
<div id="icon"></div>`;
/**
* A Custom Element for displaying icon
*/
export class BoxIconElement extends HTMLElement {
static get cdnUrl() {
// BUILD.DATA.VERSION is injected by webpack during a build.
// Value is same as package.json#version property.
return `//unpkg.com/boxicons@${BUILD.DATA.VERSION}/svg`;
}
/**
* The html tag name to be use
* @type {String}
*/
static get tagName() { return 'box-icon'; }
static get observedAttributes() {
return [
'name',
'color',
'size',
'rotate',
'flip',
'animation',
'shape',
];
}
/**
* Returns a promise that should resolve with a string - the svg source.
*
* @param {String} iconName
* The icon name (file name) to the icon that should be loaded.
*
* @return {Promise<String, Error>}
*/
static getIconSvg(iconName) {
const iconUrl = `${this.cdnUrl}/${iconName}.svg`;
if (iconUrl && CACHE[iconUrl]) {
return CACHE[iconUrl];
}
CACHE[iconUrl] = new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.addEventListener('load', function () {
if (this.status < 200 || this.status >= 300) {
reject(new Error(`${this.status} ${this.responseText}`));
return;
}
resolve(this.responseText);
});
request.onerror = reject;
request.onabort = reject;
request.open('GET', iconUrl);
request.send();
});
return CACHE[iconUrl];
}
/**
* Define (register) the custom element
*
* @param {String} [tagName=this.tagName]
*/
static define(tagName) {
tagName = tagName || this.tagName;
if (usingShadyCss()) {
GLOBAL.ShadyCSS.prepareTemplate(TEMPLATE, tagName);
}
customElements.define(tagName, this);
}
constructor() {
super();
this.$ui = this.attachShadow({ mode: 'open' });
this.$ui.appendChild(this.ownerDocument.importNode(TEMPLATE.content, true));
if (usingShadyCss()) {
GLOBAL.ShadyCSS.styleElement(this);
}
this._state = {
$iconHolder: this.$ui.getElementById('icon'),
};
}
attributeChangedCallback(attr, oldVal, newVal) {
const $iconHolder = this._state.$iconHolder;
switch (attr) {
case 'name':
handleNameChange(this, oldVal, newVal);
break;
case 'color':
$iconHolder.style.fill = newVal || '';
break;
case 'size':
handleSizeChange(this, oldVal, newVal);
break;
case 'rotate':
if (oldVal) {
$iconHolder.classList.remove(`${CSS_CLASS_PREFIX_ROTATE}${oldVal}`);
}
if (newVal) {
$iconHolder.classList.add(`${CSS_CLASS_PREFIX_ROTATE}${newVal}`);
}
break;
case 'flip':
if (oldVal) {
$iconHolder.classList.remove(`${CSS_CLASS_PREFIX_FLIP}${oldVal}`);
}
if (newVal) {
$iconHolder.classList.add(`${CSS_CLASS_PREFIX_FLIP}${newVal}`);
}
break;
case 'animation':
if (oldVal) {
$iconHolder.classList.remove(`${CSS_CLASS_PREFIX}${oldVal}`);
}
if (newVal) {
$iconHolder.classList.add(`${CSS_CLASS_PREFIX}${newVal}`);
}
break;
}
}
connectedCallback() {
if (usingShadyCss()) {
GLOBAL.ShadyCSS.styleElement(this);
}
}
}
function handleNameChange(inst, oldVal, newVal) {
const state = inst._state;
state.currentName = newVal;
state.$iconHolder.textContent = '';
if (newVal) {
inst.constructor.getIconSvg(newVal)
.then((iconData) => {
if (state.currentName === newVal) {
state.$iconHolder.innerHTML = iconData;
}
})
.catch((error) => {
console.error(`Failed to load icon: ${newVal + "\n"}${error}`); //eslint-disable-line
});
}
}
function handleSizeChange(inst, oldVal, newVal) {
const state = inst._state;
if (state.size) {
state.$iconHolder.style.width = state.$iconHolder.style.height = '';
state.size = state.sizeType = null;
}
// If the size is not one of the short-hand ones, then it must be a
// css size unit - add it directly to the icon holder.
if (newVal && !/^(xs|sm|md|lg)$/.test(state.size)) {
state.size = newVal.trim();
state.$iconHolder.style.width = state.$iconHolder.style.height = state.size;
}
}
export default BoxIconElement;

View file

@ -0,0 +1,6 @@
import { BoxIconElement } from './box-icon-element';
export { BoxIconElement };
export default BoxIconElement;
BoxIconElement.define();

300
static/css/animations.css Normal file
View file

@ -0,0 +1,300 @@
@-webkit-keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-webkit-keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@-webkit-keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@-webkit-keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@-webkit-keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@-webkit-keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: rotate3d(0, 0, 1, -10deg);
transform: rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
.bx-spin
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-spin-hover:hover
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-tada
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-tada-hover:hover
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-flashing
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-flashing-hover:hover
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-burst
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-burst-hover:hover
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-fade-left
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-left-hover:hover
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-right
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}
.bx-fade-right-hover:hover
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}

View file

@ -0,0 +1,32 @@
.bx-rotate-90
{
transform: rotate(90deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';
}
.bx-rotate-180
{
transform: rotate(180deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
}
.bx-rotate-270
{
transform: rotate(270deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
}
.bx-flip-horizontal
{
transform: scaleX(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)';
}
.bx-flip-vertical
{
transform: scaleY(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
}

View file

@ -1,7 +1,189 @@
const path = require('path');
const webpack = require('webpack');
const WrapperPlugin = require('wrapper-webpack-plugin');
const packageJson = require('./package.json');
module.exports = {
entry: `${__dirname}/src/index.js`,
output: {
path: path.resolve(__dirname, 'dist'),
library: 'BoxIconElement',
libraryTarget: 'umd',
filename: 'box-icon-element.js',
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
babelrc: false,
presets: [
['env', { modules: false, targets: { uglify: true } }],
],
plugins: [
['babel-plugin-transform-builtin-classes', {
globals: ['Array', 'Error', 'HTMLElement'],
}],
],
},
},
{
test: /\.css$/,
use: [
{ loader: 'to-string-loader' },
{
loader: 'css-loader',
options: {
camelCase: true,
},
},
],
},
],
},
plugins: [
new webpack.DefinePlugin({
'BUILD.DATA': {
VERSION: JSON.stringify(packageJson.version),
},
}),
new WrapperPlugin({
test: /box-icon-element\.js$/,
header: getWrapper('header'),
footer: getWrapper('footer'),
}),
],
};
function getWrapper(type) {
if (getWrapper.header) {
return getWrapper[type];
}
const templatePieces = `(function (
DEFAULT_CDN_PREFIX,
STRING_WEB_COMPONENTS_REQUESTED,
WINDOW,
DOCUMENT,
init
) {
/**
* A Custom Elements (Web Components) polyfill loader wrapper.
* Use it to wrap modules that use CEs and it will take care of
* only executing the module initialization function when the
* polyfill is loaded.
* This loader wrapper also loads the \`core-js\` library if it
* finds that he current environment does not support Promises
* (ex. IE)
*
* The loader contains default URLs for polyfills, however, these
* can be overwritten with the following globals:
*
* - \`window.WEB_COMPONENTS_POLYFILL\`
* - \`window.ES6_CORE_POLYFILL\`
*
* In addition, this loader will add the following global
* variable, which is used to store module initialization
* functions until the environment is patched:
*
* - \`window.AWAITING_WEB_COMPONENTS_POLYFILL\`: an Array
* with callbacks. To store a new one, use
* \`window.AWAITING_WEB_COMPONENTS_POLYFILL.then(callbackHere)\`
*/
if (!('customElements' in WINDOW)) {
// If in the mist of loading the polyfills, then just add init to when that is done
if (WINDOW[STRING_WEB_COMPONENTS_REQUESTED]) {
WINDOW[STRING_WEB_COMPONENTS_REQUESTED].then(init);
return;
}
var _WEB_COMPONENTS_REQUESTED = WINDOW[STRING_WEB_COMPONENTS_REQUESTED] = getCallbackQueue();
_WEB_COMPONENTS_REQUESTED.then(init);
var WEB_COMPONENTS_POLYFILL = WINDOW.WEB_COMPONENTS_POLYFILL || "/" + "/" + DEFAULT_CDN_PREFIX + "/webcomponentsjs/2.0.2/webcomponents-bundle.js";
var ES6_CORE_POLYFILL = WINDOW.ES6_CORE_POLYFILL || "/" + "/" + DEFAULT_CDN_PREFIX + "/core-js/2.5.3/core.min.js";
if (!("Promise" in WINDOW)) {
loadScript(ES6_CORE_POLYFILL)
.then(function () {
loadScript(WEB_COMPONENTS_POLYFILL).then(function () {
_WEB_COMPONENTS_REQUESTED.isDone = true;
_WEB_COMPONENTS_REQUESTED.exec();
});
});
} else {
loadScript(WEB_COMPONENTS_POLYFILL).then(function () {
_WEB_COMPONENTS_REQUESTED.isDone = true;
_WEB_COMPONENTS_REQUESTED.exec();
});
}
} else {
init();
}
function getCallbackQueue() {
var callbacks = [];
callbacks.isDone = false;
callbacks.exec = function () {
callbacks.splice(0).forEach(function (callback) {
callback();
});
};
callbacks.then = function(callback){
if (callbacks.isDone) {
callback();
} else {
callbacks.push(callback);
}
return callbacks;
}
return callbacks;
}
function loadScript (url) {
var callbacks = getCallbackQueue();
var script = DOCUMENT.createElement("script")
script.type = "text/javascript";
if (script.readyState){ // IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange = null;
callbacks.isDone = true;
callbacks.exec();
}
};
} else {
script.onload = function(){
callbacks.isDone = true;
callbacks.exec();
};
}
script.src = url;
DOCUMENT.getElementsByTagName("head")[0].appendChild(script);
script.then = callbacks.then;
return script;
}
})(
"cdnjs.cloudflare.com/ajax/libs",
"AWAITING_WEB_COMPONENTS_POLYFILL", // Global wait queue var name
window,
document,
function () {
____SPLIT_HERE____
}
);`.split('____SPLIT_HERE____');
getWrapper.header = templatePieces[0];
getWrapper.footer = templatePieces[1];
return getWrapper[type];
}