feat(ionicons): svg based ionicons

This commit is contained in:
Adam Bradley
2017-07-17 15:06:55 -05:00
parent 673159ab2e
commit 550b58a158
4 changed files with 163 additions and 65 deletions

View File

@ -11,7 +11,7 @@
@each $color-name, $color-base, $color-contrast in get-colors($colors-ios) {
.icon-ios-#{$color-name} {
color: $color-base;
fill: $color-base;
}
}

View File

@ -11,7 +11,7 @@
@each $color-name, $color-base, $color-contrast in get-colors($colors-md) {
.icon-md-#{$color-name} {
color: $color-base;
fill: $color-base;
}
}

View File

@ -9,6 +9,11 @@ ion-icon {
display: inline-block;
font-size: 1.2em;
svg {
width: 1.2em;
height: 1.2em;
}
}
ion-icon[small] {
@ -16,3 +21,4 @@ ion-icon[small] {
font-size: 1.1em;
}

View File

@ -1,5 +1,7 @@
import { Component, h, Prop, State, VNodeData, Ionic } from '@stencil/core';
import { CssClassObject } from '../../utils/interfaces';
import { Component, h, Prop, State, VNodeData } from '@stencil/core';
export declare const publicPath: string;
@Component({
tag: 'ion-icon',
@ -15,18 +17,11 @@ import { CssClassObject } from '../../utils/interfaces';
export class Icon {
mode: string;
@Prop() color: string;
/**
* @input {string} Specifies the label to use for accessibility. Defaults to the icon name.
*/
@State() label: string = '';
/**
* @input {string} Specifies the mode to use for the icon.
*/
@State() iconMode: string = '';
/**
* @input {string} Specifies which icon to use. The appropriate icon will be used based on the mode.
* For more information, see [Ionicons](/docs/ionicons/).
@ -43,86 +38,183 @@ export class Icon {
*/
@Prop() md: string = '';
/**
* @input {boolean} If true, the icon is styled with an "active" appearance.
* An active icon is filled in, and an inactive icon is the outline of the icon.
* The `isActive` property is largely used by the tabbar. Only affects `ios` icons.
*/
@Prop() isActive: boolean = null;
/**
* @input {boolean} If true, the icon is hidden.
*/
@Prop() hidden: boolean = false;
getElementClass(): string {
@State() svgContent: string = null;
getSvgUrl() {
const iconName = this.iconName;
if (iconName !== null) {
return `${publicPath}svg/${iconName}.svg`;
}
return null;
}
get iconName() {
let iconName: string;
// If no name was passed set iconName to null
// if no name was passed set iconName to null
if (!this.name) {
iconName = null;
} else if (!(/^md-|^ios-|^logo-/.test(this.name))) {
return null;
}
if (!(/^md-|^ios-|^logo-/.test(this.name))) {
// this does not have one of the defaults
// so lets auto add in the mode prefix for them
iconName = this.iconMode + '-' + this.name;
iconName = this.mode + '-' + this.name;
} else if (this.name) {
// this icon already has a prefix
iconName = this.name;
}
// If an icon was passed in using the ios or md attributes
// if an icon was passed in using the ios or md attributes
// set the iconName to whatever was passed in
if (this.ios && this.iconMode === 'ios') {
if (this.ios && this.mode === 'ios') {
iconName = this.ios;
} else if (this.md && this.iconMode === 'md') {
} else if (this.md && this.mode === 'md') {
iconName = this.md;
}
if ((iconName === null) || (this.hidden === true)) {
console.warn('Icon is hidden.');
return 'hide';
}
let iconMode = iconName.split('-', 2)[0];
if (
iconMode === 'ios' &&
this.isActive === false &&
iconName.indexOf('logo-') < 0 &&
iconName.indexOf('-outline') < 0) {
iconName += '-outline';
}
let label = iconName
.replace('ios-', '')
.replace('md-', '')
.replace('-', ' ');
this.label = label;
return `ion-${iconName}`;
return iconName;
}
hostData(): VNodeData {
// TODO set the right iconMode based on the config
let iconMode = this.mode === 'md' ? 'md' : 'ios';
this.iconMode = iconMode || Ionic.config.get('iconMode');
const iconClasses: CssClassObject = []
.concat(
this.getElementClass(),
)
.reduce((prevValue, cssClass) => {
prevValue[cssClass] = true;
return prevValue;
}, {});
hostData(): VNodeData {
const attrs: {[attrName: string]: string} = {
'role': 'img'
};
if (this.hidden) {
// adds the hidden attribute
attrs['hidden'] = '';
}
if (this.label) {
// user provided label
attrs['aria-label'] = this.label;
} else {
// come up with the label based on the icon name
const iconName = this.iconName;
if (iconName) {
attrs['aria-label'] = iconName
.replace('ios-', '')
.replace('md-', '')
.replace('-', ' ');
}
}
return {
class: iconClasses,
attrs: {
'role': 'img'
}
attrs
};
}
render() {
return <slot></slot>;
static loadSvgContent(svgUrl: string, callback: {(loadedSvgContent: string): void}) {
// static since all IonIcons use this same function and pointing at global/shared data
// passed in callback will have instance info
// add to the list of callbacks to fiure when this url is finished loading
IonIcon.loadCallbacks[svgUrl] = IonIcon.loadCallbacks[svgUrl] || [];
IonIcon.loadCallbacks[svgUrl].push(callback);
if (IonIcon.activeRequests[svgUrl]) {
// already requesting this url, don't bother again kicking off another
return;
}
// add this url to our list of active requests
IonIcon.activeRequests[svgUrl] = true;
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
// awesome, we've finished loading the svg file
// remove this url from the active requests
delete IonIcon.activeRequests[svgUrl];
if (this.status >= 400) {
// ok, not awesome, something is up
console.error('Icon could not be loaded:', svgUrl);
return;
}
// this response is the content of the svg file we're looking for
// cache it in the global IonIcon constant
IonIcon.svgContents[svgUrl] = this.responseText;
// find any callbacks waiting on this url
const svgLoadCallbacks = IonIcon.loadCallbacks[svgUrl];
if (svgLoadCallbacks) {
// loop through all the callbacks that are waiting on the svg content
for (var i = 0; i < svgLoadCallbacks.length; i++) {
// fire off this callback which
svgLoadCallbacks[i](this.responseText);
}
delete IonIcon.loadCallbacks[svgUrl];
}
});
xhr.addEventListener('error', function () {
// umm, idk
console.error('Icon could not be loaded:', svgUrl);
});
// let's do this!
xhr.open('GET', svgUrl, true);
xhr.send();
}
render() {
const svgUrl = this.getSvgUrl();
if (!svgUrl) {
// we don't have good data
return(<div class="missing-svg"></div>);
}
const svgContent = IonIcon.svgContents[svgUrl];
if (svgContent === this.svgContent) {
// we've already loaded up this svg at one point
// and the svg content we've loaded and assigned checks out
// render this svg!!
return(
<div innerHTML={svgContent}></div>
);
}
// haven't loaded this svg yet
// start the request
Icon.loadSvgContent(svgUrl, loadedSvgContent => {
// we're finished loading the svg content!
// set to this.svgContent so we do another render
this.svgContent = loadedSvgContent;
});
// actively requesting the svg, so let's just render a div for now
return(<div class="loading-svg"></div>);
}
}
const IonIcon: GlobalIonIcon = {
activeRequests: {},
loadCallbacks: [] as any,
svgContents: {}
};
interface GlobalIonIcon {
activeRequests: {[url: string]: boolean};
loadCallbacks: {[url: string]: {(loadedSvgContent: string): void}[]};
svgContents: {[url: string]: string};
}