mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-07 23:16:52 +08:00
feat(ionicons): svg based ionicons
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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};
|
||||
}
|
||||
Reference in New Issue
Block a user