Skip to content

Instantly share code, notes, and snippets.

@gossi
Created June 23, 2018 14:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gossi/9d4090dba409c047c267cfba877f7065 to your computer and use it in GitHub Desktop.
Save gossi/9d4090dba409c047c267cfba877f7065 to your computer and use it in GitHub Desktop.
Ember Font Awesome High-Order Components

Ember Font Awesome High-Order Components

The idea behind was to have decorators on top of an icon. The idea is taken from eclipse, where it has a "background" icon and then a decorator for each edge (top left, top right, bottom left, bottom right). To stack up icons, such as user in the background and a plus as bottom right decorator.

Luckily this is possible with FA5, see here: https://jsfiddle.net/8oqL3b5p/276/

Taken this and combine it with ember, I was able to create this API:

<FaIcon @icon='user' as |icon|>
  <icon.decorator @icon='plus' @position='br' />
</FaIcon>

Which works fine. So the eclipse API offers the ability to put a decorator into each edge. While my API would allow this, the following doesn't work (yet?):

<FaIcon @icon='user' as |icon|>
  <icon.decorator @icon='plus' @position='br' />
  <icon.decorator @icon='star' @position'tr' />
</FaIcon>

I wasn't able to achieve this in my early experiments and due to my limited understanding of SVG, though should be possible. Since it is working with masks, I think FA5 didn't intended to use multiple masks onto an icon?

In general I think, this is possible:

<FaIcon @icon='user' as |icon|>
  <icon.decorator @icon='plus' @position='br' />
  <icon.decorator @icon='star' @position'tr' />
  <icon.decorator @icon='search' @transform='down-6 left-6' />
  <icon.mask @icon='rocket' @transform='up-6 left-6' />
</FaIcon>

While mask just cuts of the piece of the provided icon in the background icon, a decorator puts itself the same item onto the cut out area.

FWIW, Animation:

<FaIcon @icon='user' as |icon|>
  <icon.decorator @icon='spinner' @position='br' @spin=true />
</FaIcon>
import { tagName } from '@ember-decorators/component';
import Component from '@ember/component';
import { htmlSafe } from '@ember/string';
import { icon, parse, toHtml } from '@fortawesome/fontawesome-svg-core';
import FaIconDecorator from 'videoathlyzer-client/pods/components/fa-icon/decorator/component';
import { alias } from '@ember-decorators/object/computed';
@tagName('')
export default class FaIcon extends Component {
// arguments
icon!: string;
prefix: string = this.prefix || 'far';
fw: boolean = this.fw || false;
li: boolean = this.li || false;
spin: boolean = this.spin || false;
pulse: boolean = this.pulse || false;
border: boolean = this.border || false;
flip?: string;
transform: string = this.transform || '';
rotation?: string;
size?: string;
pull?: string;
mask?: string;
maskPrefix: string = this.maskPrefix || 'far';
// BC
@alias('fw') fixedWidth!: boolean;
@alias('li') listItem!: boolean;
// props
html: string = '';
style: string = htmlSafe('');
viewBox?: string;
classes?: string;
decorators: FaIconDecorator[] = [];
didInsertElement() {
super.didInsertElement();
const iconLookup = {
iconName: this.icon,
prefix: this.prefix
};
const mask = {
iconName: this.mask,
prefix: this.maskPrefix
};
const classes = this.getClasses(this.getWithDefault('class', '').split(' '));
const params = {
classes,
mask,
transform: parse.transform(this.transform),
};
let html: string = '';
let rendered = icon(iconLookup, params);
// THIS. DOES. NOT. WORK.
// due to my limited understanding of svg but should be possible in general I guess
// maybe getting the masks for each applied mask and then construct the
// final svg
for (let decorator of this.decorators) {
rendered = icon({
iconName: decorator.icon,
prefix: decorator.prefix
}, {
classes,
transform: parse.transform(decorator.transform!),
mask: { icon: rendered.icon }
});
}
if (rendered) {
const abstract = rendered.abstract[0];
abstract.attributes && Object.keys(abstract.attributes).forEach(attr => {
if (attr === 'style') {
this.set('style', htmlSafe(abstract.attributes[attr]));
} else if (attr === 'viewBox') {
this.set('viewBox', abstract.attributes[attr]);
} else if (attr === 'class') {
this.set('classes', abstract.attributes[attr]);
}
});
if (abstract.children) {
html = htmlSafe(abstract.children.reduce((acc, cur) => {
return `${acc}${toHtml(cur)}`
}, ''));
}
}
this.set('html', html);
}
getClasses(previousClasses: string[]) {
let classes = {
'fa-spin': this.spin,
'fa-pulse': this.pulse,
'fa-fw': this.fw,
'fa-border': this.border,
'fa-li': this.li,
'fa-flip-horizontal': this.flip === 'horizontal' || this.flip === 'both',
'fa-flip-vertical': this.flip === 'vertical' || this.flip === 'both',
[`fa-${this.size}`]: this.size,
[`fa-rotate-${this.rotation}`]: this.rotation,
[`fa-pull-${this.pull}`]: this.pull
};
return Object.keys(classes)
.map(key => classes[key] ? key : null)
.filter(key => key)
.concat(previousClasses.filter(c => !c.match(/^fa-/)))
}
registerDecorator(decorator: FaIconDecorator) {
this.decorators.pushObject(decorator);
}
unregisterDecorator(decorator: FaIconDecorator) {
this.decorators.removeObject(decorator);
}
}
import { tagName } from '@ember-decorators/component';
import Component from '@ember/component';
import FaIcon from 'videoathlyzer-client/pods/components/fa-icon/component';
@tagName('')
export default class FaIconDecorator extends Component {
// arguments
parent!: FaIcon;
position!: string;
icon!: string;
prefix: string = this.prefix || 'far';
transform?: string;
// properties
x: number = this.x || 6;
y: number = this.y || 6;
transformPosition?: string;
constructor() {
super(...arguments);
this.parent.registerDecorator(this);
if (this.position && !this.transform) {
this.handlePosition(this.position);
}
this.addObserver('position', () => {
this.handlePosition(this.position);
});
}
willDestroyElement() {
super.willDestroyElement();
this.parent.unregisterDecorator(this);
}
handlePosition(position: string) {
let transform = '';
switch (position) {
case 'tl':
transform = `up-${this.y} left-${this.x}`;
break;
case 'tr':
transform = `up-${this.y} right-${this.x}`;
break;
case 'bl':
transform = `down-${this.y} left-${this.x}`;
break;
case 'br':
transform = `down-${this.y} right-${this.x}`;
}
if (transform) {
this.set('transformPosition', transform);
this.set('transform', transform);
}
}
}
{{#if (has-block)}}
<span class="fa-layers {{if this.fw 'fa-fw'}}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox={{this.viewBox}} class={{this.classes}} role="img" aria-hidden="true" data-icon={{this.icon}} data-prefix={{this.prefix}} data-fa-transform={{this.transform}} style={{this.style}}>
{{html}}
</svg>
{{#each decorators as |decorator|}}
<FaIcon @icon={{decorator.icon}} @prefix={{decorator.prefix}} @transform="shrink-5 {{decorator.transformPosition}}" />
{{/each}}
</span>
{{yield (hash
decorator=(component 'fa-icon/decorator' parent=this)
)}}
{{else}}
<svg xmlns="http://www.w3.org/2000/svg" viewBox={{this.viewBox}} class={{this.classes}} role="img" aria-hidden="true" data-icon={{this.icon}} data-prefix={{this.prefix}} data-fa-transform={{this.transform}} style={{this.style}}>
{{html}}
</svg>
{{/if}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment