Skip to content

Commit

Permalink
adjustment basic structure from google clone @lucasferreiralimax
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasferreiralimax committed Oct 19, 2024
1 parent cb86096 commit 9cb4798
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 417 deletions.
93 changes: 21 additions & 72 deletions projects/voicewave-angular/src/lib/voicewave-angular.component.html
Original file line number Diff line number Diff line change
@@ -1,73 +1,22 @@
<div
#modal
class="up-window-container"
[class.active]="isOpen()"
role="dialog"
aria-labelledby="dialog-title"
aria-describedby="dialog-description"
aria-modal="true"
[attr.aria-hidden]="!isOpen()"
>
<div
class="up-window"
[ngClass]="getClass()"
(click)="$event.stopPropagation()"
>
@if(!restrictMode || hiddenActions) {
<button
class="close-window"
aria-label="Close window"
(click)="closeWindow()"
<section class="voicewave" [class.active]="show">
<button class="exit" type="button" (click)="disableVoice()">
<i class="icon icon-exit">X</i>
</button>
<p>Fale agora</p>
<button type="button" class="btn-voice" [class.active]="animationButton">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
&times;
</button>
} @if(title || subtitle) {
<div class="up-window-header">
@if(title) {
<h3 id="dialog-title" class="up-window-title">{{ title }}</h3>
} @if(subtitle) {
<h4 id="dialog-description" class="up-window-subtitle">
{{ subtitle }}
</h4>
}
</div>
}

<div class="up-window-body">
<ng-content></ng-content>
</div>

@if(!hiddenActions) {
<div class="up-window-footer" [ngClass]="'align-' + buttonAlignment">
<ng-content select="[footer]"></ng-content>
@if(!hasFooterContent()) {
<button
class="btn btn-cancel"
[ngClass]="getButtonClass(cancelType)"
(click)="onCancel()"
[attr.aria-label]="cancelText"
>
{{ cancelText }}
</button>
<button
class="btn btn-confirm"
[ngClass]="getButtonClass(confirmType)"
(click)="onConfirm()"
[attr.aria-label]="confirmText"
>
{{ confirmText }}
</button>
}
</div>
}
</div>
<div
class="overlay"
(click)="closeWindow('overlay')"
[class.fade]="openingAnimation"
[class.fade-out]="closingAnimation"
[class.active]="isOpen()"
[class.blur]="blur"
[class.grayscale]="grayscale"
></div>
</div>
<path
d="M17 11.998c0 2.76-2.23 5-4.99 5l-.002.002a4.994 4.994 0 01-4.979-5h-2c0 3.52 2.59 6.433 5.98 6.92v3.078h.01V22h2v-3.08h-.01A6.982 6.982 0 0019 11.998z"
/>
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 15c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v7c0 1.66 1.34 3 3 3z"
/>
</svg>
</button>
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.voicewave {
align-items: center;
background-color: var(--main-bg-color-primary);
bottom: 0;
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
display: flex;
font-family: arial,sans-serif;
font-size: 14px;
height: auto;
justify-content: center;
left: 0;
opacity: 0;
padding: 35px;
pointer-events: none;
position: absolute;
right: 0;
top: 0;
transition: .5s all;
user-select: none;
z-index: 99;
&.active {
opacity: 1;
pointer-events: all;
p {
opacity: 1;
transform: translateX(0);
}
.btn-voice { transform: scale(1); }
}
p {
font-size: 22px;
margin: 0;
opacity: 0;
padding: 0;
transform: translateX(100px);
transition: 1s all;
}
.exit {
&:before {
content: '';
display: block;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
}

.btn-voice {
--mic-volume-size: 150px;
align-items: center;
background-color: rgba(var(--main-color-rgb), .05);
border-radius: 50%;
border: 0;
box-shadow: 0 0 0 1px rgba(#fff, .6), 0 10px 20px rgba(var(--main-color-rgb), .1);
cursor: pointer;
display: flex;
height: var(--mic-volume-size);
justify-content: center;
margin-left: 20px;
opacity: .5;
outline: none;
transition: .4s all;
transform: scale(0);
user-select: none;
width: var(--mic-volume-size);
min-width: var(--mic-volume-size);
&.active {
animation: 1s voiceAnimation infinite alternate;
opacity: 1;
svg { fill: #ea4335; }
}
&:hover {
transform: scale(1.1);
}
svg {
height: 50%;
width: 50%;
fill: var(--main-color)
}
}

@keyframes voiceAnimation {
0% { box-shadow: 0 0 0 1px rgba(#fff, .6), 0 10px 20px rgba(var(--main-color-rgb), .1), 0 0 10px rgba(var(--main-color-rgb), .1); }
100% { box-shadow: 0 0 0 1px rgba(#fff, .6), 0 10px 20px rgba(var(--main-color-rgb), .1), 0 0 0 30px rgba(var(--main-color-rgb), .1);}
}
177 changes: 11 additions & 166 deletions projects/voicewave-angular/src/lib/voicewave-angular.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,179 +1,24 @@
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing';
import { VoiceWaveAngularComponent } from '../voicewave-angular.component';
import { By } from '@angular/platform-browser';
import { ComponentFixture, TestBed } from '@angular/core/testing';

describe('VoiceWaveAngularComponent', () => {
let component: VoiceWaveAngularComponent;
let fixture: ComponentFixture<VoiceWaveAngularComponent>;
import { VoiceWave } from './voicewave-angular.component';

describe('VoiceWave', () => {
let component: VoiceWave;
let fixture: ComponentFixture<VoiceWave>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [VoiceWaveAngularComponent],
declarations: [VoiceWave],
}).compileComponents();
});

fixture = TestBed.createComponent(VoiceWaveAngularComponent);
beforeEach(() => {
fixture = TestBed.createComponent(VoiceWave);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create the component', () => {
it('should create', () => {
expect(component).toBeTruthy();
});

it('should render title and subtitle', () => {
component.title = 'Test Title';
component.subtitle = 'Test Subtitle';
fixture.detectChanges();

const titleElement = fixture.debugElement.query(
By.css('.up-window-title')
).nativeElement;
const subtitleElement = fixture.debugElement.query(
By.css('.up-window-subtitle')
).nativeElement;

expect(titleElement.textContent).toContain('Test Title');
expect(subtitleElement.textContent).toContain('Test Subtitle');
});

it('should append modal to body when isOpen is true', fakeAsync(() => {
component.isOpen.set(false);
fixture.detectChanges();
expect(document.body.querySelector('.up-window')).toBeNull();

component.isOpen.set(true);
fixture.detectChanges();
tick(100);
expect(document.body.querySelector('.up-window')).toBeTruthy();

component.isOpen.set(false);
fixture.detectChanges();
tick(400);
expect(document.body.querySelector('.up-window')).toBeNull();
}));

it('should not reappend modal if it is already in the body', fakeAsync(() => {
component.isOpen.set(true);
fixture.detectChanges();
tick(600);

spyOn(component, 'addModalToBody').and.callThrough();

component.addModalToBody();
fixture.detectChanges();

expect(component.addModalToBody).toHaveBeenCalledTimes(1);
expect(document.body.querySelectorAll('.up-window').length).toBe(1);
}));

it('should apply the correct animation class when opening and closing the window', fakeAsync(() => {
component.animation = 'slide';

component.isOpen.set(true);
component.openingAnimation = true;
component.closingAnimation = false;
fixture.detectChanges();

tick(100);

let windowElement = fixture.debugElement.query(By.css('.up-window'));

expect(windowElement.classes['slide']).toBeTrue();

component.closingAnimation = true;
component.openingAnimation = false;
component.isOpen.set(false);
fixture.detectChanges();

tick(600);

windowElement = fixture.debugElement.query(By.css('.up-window'));

expect(windowElement.classes['slide-out']).toBeTrue();
expect(component.isOpen()).toBeFalse();
}));

it('should trigger onConfirm and onCancel when buttons are clicked', async () => {
spyOn(component, 'onConfirm').and.callThrough();
spyOn(component, 'onCancel').and.callThrough();

component.isOpen.set(true);
fixture.detectChanges();

await fixture.whenStable();
fixture.detectChanges();

const confirmButton = fixture.debugElement.query(By.css('.btn-confirm'));
const cancelButton = fixture.debugElement.query(By.css('.btn-cancel'));

expect(confirmButton).toBeTruthy();
expect(cancelButton).toBeTruthy();

confirmButton.nativeElement.click();
await fixture.whenStable();
expect(component.onConfirm).toHaveBeenCalled();
expect(component.isOpen()).toBeFalse();

component.isOpen.set(true);
fixture.detectChanges();

await fixture.whenStable();
cancelButton.nativeElement.click();
await fixture.whenStable();
expect(component.onCancel).toHaveBeenCalled();
expect(component.isOpen()).toBeFalse();
});

it('should apply custom class from @Input', () => {
component.class = 'custom-class';
fixture.detectChanges();

const windowElement = fixture.debugElement.query(By.css('.up-window'));
expect(windowElement.classes['custom-class']).toBeTruthy();
});

it('should set isOpen to true when openWindow is called', () => {
component.isOpen.set(true);
expect(component.isOpen()).toBeTrue();
});

it('should set isOpen to false after closeWindow is called', fakeAsync(() => {
component.isOpen.set(true);
fixture.detectChanges();

component.isOpen.set(false);
fixture.detectChanges();

tick(600);
expect(component.isOpen()).toBeFalse();
}));

it('should not show confirm and cancel buttons if hiddenActions is true', () => {
component.hiddenActions = true;
component.isOpen.set(true);
fixture.detectChanges();

const confirmButton = fixture.debugElement.query(By.css('.btn-confirm'));
const cancelButton = fixture.debugElement.query(By.css('.btn-cancel'));

expect(confirmButton).toBeNull();
expect(cancelButton).toBeNull();
});

it('should show confirm and cancel buttons if hiddenActions is false', () => {
component.hiddenActions = false;
component.isOpen.set(true);
fixture.detectChanges();

const confirmButton = fixture.debugElement.query(By.css('.btn-confirm'));
const cancelButton = fixture.debugElement.query(By.css('.btn-cancel'));

expect(confirmButton).toBeTruthy();
expect(cancelButton).toBeTruthy();
});
});
Loading

0 comments on commit 9cb4798

Please sign in to comment.