feat(social-media): implement social media component and integrate into store footer
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
<a
|
||||
class="social-media"
|
||||
[href]="url()"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
[attr.aria-label]="name()"
|
||||
[attr.data-social-media]="code()"
|
||||
[title]="name()"
|
||||
>
|
||||
<i [class]="iconClass()" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{ name() }}</span>
|
||||
</a>
|
||||
@@ -0,0 +1,31 @@
|
||||
:host {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.social-media {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
color: inherit;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
color 0.15s ease-in-out,
|
||||
background-color 0.15s ease-in-out,
|
||||
transform 0.15s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
color: #ffffff;
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid #ffffff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SocialMediaComponent } from './social-media.component';
|
||||
|
||||
describe('SocialMediaComponent', () => {
|
||||
let fixture: ComponentFixture<SocialMediaComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SocialMediaComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SocialMediaComponent);
|
||||
fixture.componentRef.setInput('code', 'instagram');
|
||||
fixture.componentRef.setInput('icon', 'instagram');
|
||||
fixture.componentRef.setInput('name', 'Instagram');
|
||||
fixture.componentRef.setInput('url', 'https://instagram.com/acme');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('renders a safe external social media link', () => {
|
||||
const link = fixture.nativeElement.querySelector('a') as HTMLAnchorElement;
|
||||
const icon = fixture.nativeElement.querySelector('i') as HTMLElement;
|
||||
|
||||
expect(link.getAttribute('href')).toBe('https://instagram.com/acme');
|
||||
expect(link.getAttribute('target')).toBe('_blank');
|
||||
expect(link.getAttribute('rel')).toBe('noopener noreferrer');
|
||||
expect(link.getAttribute('aria-label')).toBe('Instagram');
|
||||
expect(link.dataset['socialMedia']).toBe('instagram');
|
||||
expect(icon.className).toBe('fa-brands fa-instagram');
|
||||
});
|
||||
|
||||
it('keeps a complete Font Awesome class', () => {
|
||||
fixture.componentRef.setInput('icon', 'fa-brands fa-linkedin-in');
|
||||
fixture.detectChanges();
|
||||
|
||||
const icon = fixture.nativeElement.querySelector('i') as HTMLElement;
|
||||
|
||||
expect(icon.className).toBe('fa-brands fa-linkedin-in');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-social-media',
|
||||
imports: [],
|
||||
templateUrl: './social-media.component.html',
|
||||
styleUrl: './social-media.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SocialMediaComponent {
|
||||
readonly code = input.required<string>();
|
||||
readonly icon = input.required<string>();
|
||||
readonly name = input.required<string>();
|
||||
readonly url = input.required<string>();
|
||||
|
||||
protected readonly iconClass = computed(() => {
|
||||
const icon = this.icon().trim();
|
||||
|
||||
return icon.includes('fa-') ? icon : `fa-brands fa-${icon}`;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user