mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-08 15:51:16 +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) {
|
@each $color-name, $color-base, $color-contrast in get-colors($colors-ios) {
|
||||||
|
|
||||||
.icon-ios-#{$color-name} {
|
.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) {
|
@each $color-name, $color-base, $color-contrast in get-colors($colors-md) {
|
||||||
|
|
||||||
.icon-md-#{$color-name} {
|
.icon-md-#{$color-name} {
|
||||||
color: $color-base;
|
fill: $color-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,11 @@ ion-icon {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-icon[small] {
|
ion-icon[small] {
|
||||||
@ -16,3 +21,4 @@ ion-icon[small] {
|
|||||||
|
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Component, h, Prop, State, VNodeData, Ionic } from '@stencil/core';
|
import { Component, h, Prop, State, VNodeData } from '@stencil/core';
|
||||||
import { CssClassObject } from '../../utils/interfaces';
|
|
||||||
|
export declare const publicPath: string;
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-icon',
|
tag: 'ion-icon',
|
||||||
@ -15,18 +17,11 @@ import { CssClassObject } from '../../utils/interfaces';
|
|||||||
export class Icon {
|
export class Icon {
|
||||||
mode: string;
|
mode: string;
|
||||||
|
|
||||||
@Prop() color: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {string} Specifies the label to use for accessibility. Defaults to the icon name.
|
* @input {string} Specifies the label to use for accessibility. Defaults to the icon name.
|
||||||
*/
|
*/
|
||||||
@State() label: string = '';
|
@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.
|
* @input {string} Specifies which icon to use. The appropriate icon will be used based on the mode.
|
||||||
* For more information, see [Ionicons](/docs/ionicons/).
|
* For more information, see [Ionicons](/docs/ionicons/).
|
||||||
@ -43,86 +38,183 @@ export class Icon {
|
|||||||
*/
|
*/
|
||||||
@Prop() md: string = '';
|
@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.
|
* @input {boolean} If true, the icon is hidden.
|
||||||
*/
|
*/
|
||||||
@Prop() hidden: boolean = false;
|
@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;
|
let iconName: string;
|
||||||
|
|
||||||
// If no name was passed set iconName to null
|
// if no name was passed set iconName to null
|
||||||
if (!this.name) {
|
if (!this.name) {
|
||||||
iconName = null;
|
return null;
|
||||||
} else if (!(/^md-|^ios-|^logo-/.test(this.name))) {
|
}
|
||||||
|
|
||||||
|
if (!(/^md-|^ios-|^logo-/.test(this.name))) {
|
||||||
// this does not have one of the defaults
|
// this does not have one of the defaults
|
||||||
// so lets auto add in the mode prefix for them
|
// so lets auto add in the mode prefix for them
|
||||||
iconName = this.iconMode + '-' + this.name;
|
iconName = this.mode + '-' + this.name;
|
||||||
|
|
||||||
} else if (this.name) {
|
} else if (this.name) {
|
||||||
|
// this icon already has a prefix
|
||||||
iconName = this.name;
|
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
|
// set the iconName to whatever was passed in
|
||||||
if (this.ios && this.iconMode === 'ios') {
|
if (this.ios && this.mode === 'ios') {
|
||||||
iconName = this.ios;
|
iconName = this.ios;
|
||||||
} else if (this.md && this.iconMode === 'md') {
|
|
||||||
|
} else if (this.md && this.mode === 'md') {
|
||||||
iconName = this.md;
|
iconName = this.md;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((iconName === null) || (this.hidden === true)) {
|
return iconName;
|
||||||
console.warn('Icon is hidden.');
|
|
||||||
return 'hide';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let iconMode = iconName.split('-', 2)[0];
|
|
||||||
if (
|
hostData(): VNodeData {
|
||||||
iconMode === 'ios' &&
|
const attrs: {[attrName: string]: string} = {
|
||||||
this.isActive === false &&
|
'role': 'img'
|
||||||
iconName.indexOf('logo-') < 0 &&
|
};
|
||||||
iconName.indexOf('-outline') < 0) {
|
|
||||||
iconName += '-outline';
|
if (this.hidden) {
|
||||||
|
// adds the hidden attribute
|
||||||
|
attrs['hidden'] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let label = iconName
|
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('ios-', '')
|
||||||
.replace('md-', '')
|
.replace('md-', '')
|
||||||
.replace('-', ' ');
|
.replace('-', ' ');
|
||||||
this.label = label;
|
|
||||||
|
|
||||||
return `ion-${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;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
class: iconClasses,
|
attrs
|
||||||
attrs: {
|
|
||||||
'role': 'img'
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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