Skip to content

Commit

Permalink
refactor: icon bind to host in decorator (#52)
Browse files Browse the repository at this point in the history
* refactor: icon bind to host in decorator

refactor to the preferred binding in the decorator.
This way also computed is working directly for the binding.

addition to issue #46

* fix(avatar): fix HostBinding classes for onPush Host

The classes of the Hostbinding where not set correctly for onPush Hosts.
Classes where not merged correctly.
Now the classes of the icon are set and detected by the host correctly.
fixed unit tests as they would have never failed cause of setTimeout.

close #36
  • Loading branch information
elite-benni authored Nov 16, 2023
1 parent 14c2e87 commit 1a00a01
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import { ClassValue } from 'clsx';
exportAs: 'avatarFallback',
})
export class BrnAvatarFallbackDirective {
private readonly element = inject(ElementRef).nativeElement;
readonly userCls = signal<ClassValue>('');
readonly useAutoColor = signal(false);
readonly textContent = inject(ElementRef).nativeElement.textContent;

getTextContent(): string {
return this.element.textContent;
}

@Input() set class(cls: ClassValue | string) {
this.userCls.set(cls);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Component, PLATFORM_ID } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
import { HlmAvatarFallbackDirective } from './hlm-avatar-fallback.directive';
import { hexColorFor, isBright } from '@spartan-ng/ui-avatar-brain';

@Component({
selector: 'hlm-mock',
standalone: true,
imports: [HlmAvatarFallbackDirective],
template: `<span hlmAvatarFallback [class]="userCls" [autoColor]="false">fallback2</span>`,
template: `<span hlmAvatarFallback [class]="userCls" [autoColor]="autoColor">fallback2</span>`,
})
class HlmMockComponent {
userCls = '';
Expand Down Expand Up @@ -36,12 +36,9 @@ describe('HlmAvatarFallbackDirective', () => {

it('should add any user defined classes', async () => {
component.userCls = 'test-class';
fixture.detectChanges();

// fallback uses Promise.resolve().then() so we need to wait for the next tick
setTimeout(() => {
expect(fixture.nativeElement.querySelector('img').className).toContain('test-class');
});
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('span').className).toContain('test-class');
});

describe('autoColor', () => {
Expand All @@ -50,19 +47,16 @@ describe('HlmAvatarFallbackDirective', () => {
fixture.detectChanges();
});

it('should remove the bg-muted class from the component', () => {
setTimeout(() => {
expect(fixture.nativeElement.querySelector('span').className).not.toContain('bg-muted');
});
});
it('should remove the bg-muted class from the component', fakeAsync(() => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('span').className).not.toContain('bg-muted');
}));

it('should remove add a text color class and hex backgroundColor style depending on its content', () => {
const hex = hexColorFor('fallback2');
const textCls = isBright(hex) ? 'text-black' : 'text-white';
setTimeout(() => {
expect(fixture.nativeElement.querySelector('span').className).toContain(textCls);
expect(fixture.nativeElement.querySelector('span').style.backgroundColor).toBe(hex);
});
expect(fixture.nativeElement.querySelector('span').className).toContain(textCls);
expect(fixture.nativeElement.querySelector('span').style.backgroundColor).toBe('rgb(144, 53, 149)');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { computed, Directive, effect, HostBinding, inject } from '@angular/core';
import { computed, Directive, inject } from '@angular/core';
import { BrnAvatarFallbackDirective, hexColorFor, isBright } from '@spartan-ng/ui-avatar-brain';
import { hlm } from '@spartan-ng/ui-core';
import { ClassValue } from 'clsx';
Expand All @@ -22,34 +22,19 @@ const generateAutoColorTextCls = (hex?: string) => {
inputs: ['class:class', 'autoColor:autoColor'],
},
],
host: {
'[class]': 'generatedClasses()',
'[style.backgroundColor]': "hex() || ''",
},
})
export class HlmAvatarFallbackDirective {
private readonly brn = inject(BrnAvatarFallbackDirective);
private readonly hex = computed(() => {
if (!this.brn.useAutoColor() || !this.brn.textContent) return;
return hexColorFor(this.brn.textContent);
if (!this.brn.useAutoColor() || !this.brn.getTextContent()) return;
return hexColorFor(this.brn.getTextContent());
});

private readonly autoColorTextCls = computed(() => generateAutoColorTextCls(this.hex()));

@HostBinding('class')
protected cls = generateClasses(this.brn?.userCls(), this.autoColorTextCls());

@HostBinding('style.backgroundColor')
protected backgroundColor = this.hex() || '';

constructor() {
effect(() => {
const cls = generateClasses(this.brn.userCls(), this.autoColorTextCls());
Promise.resolve().then(() => {
this.cls = cls;
});
});
effect(() => {
const hex = this.hex() || '';
Promise.resolve().then(() => {
this.backgroundColor = hex;
});
});
}
protected readonly generatedClasses = computed(() => generateClasses(this.brn?.userCls(), this.autoColorTextCls()));
}
34 changes: 10 additions & 24 deletions libs/ui/icon/helm/src/lib/hlm-icon.component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import {
ChangeDetectionStrategy,
Component,
computed,
HostBinding,
Input,
signal,
ViewEncapsulation,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, Input, signal, ViewEncapsulation } from '@angular/core';
import { IconName, NgIconComponent } from '@ng-icons/core';
import { hlm } from '@spartan-ng/ui-core';
import { cva } from 'class-variance-authority';
Expand Down Expand Up @@ -51,6 +43,9 @@ const isDefinedSize = (size: IconSize): size is DefinedSizes => {
[color]="_color()"
[strokeWidth]="_strokeWidth()"
/>`,
host: {
'[class]': 'generatedClasses()',
},
})
export class HlmIconComponent {
protected readonly _name = signal<IconName | string>('');
Expand All @@ -61,48 +56,39 @@ export class HlmIconComponent {
protected readonly ngIconSize = computed(() => (isDefinedSize(this._size()) ? '100%' : (this._size() as string)));
protected readonly ngIconCls = signal<ClassValue>('');

protected readonly generatedClasses = computed(() => {
const size: IconSize = this._size();
const variant = isDefinedSize(size) ? size : 'none';
return hlm(iconVariants({ variant }), this.userCls());
});

@Input({ required: true })
set name(value: IconName | string) {
this._name.set(value);
this.cls = this.generateClasses();
}

@Input()
set size(value: IconSize) {
this._size.set(value);
this.cls = this.generateClasses();
}

@Input()
set color(value: string | undefined) {
this._color.set(value);
this.cls = this.generateClasses();
}

@Input()
set strokeWidth(value: string | number | undefined) {
this._strokeWidth.set(value);
this.cls = this.generateClasses();
}

@Input()
set ngIconClass(cls: ClassValue) {
this.ngIconCls.set(cls);
this.cls = this.generateClasses();
}

@Input()
set class(cls: ClassValue) {
this.userCls.set(cls);
this.cls = this.generateClasses();
}

@HostBinding('class')
protected cls = this.generateClasses();

private generateClasses() {
const size: IconSize = this._size();
const variant = isDefinedSize(size) ? size : 'none';
return hlm(iconVariants({ variant }), this.userCls());
}
}

1 comment on commit 1a00a01

@vercel
Copy link

@vercel vercel bot commented on 1a00a01 Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

spartan – ./

spartan-git-main-goetzrobin.vercel.app
spartan.ng
spartan-goetzrobin.vercel.app
www.spartan.ng

Please sign in to comment.