Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

835 add left button to ion header #840

Merged
merged 12 commits into from
Sep 28, 2023
Merged
21 changes: 21 additions & 0 deletions projects/ion/src/lib/modal/component/modal.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@
aria-modal="true"
>
<header tabindex="0">
<ion-button
*ngIf="
configuration.headerButton &&
!(configuration.headerButton.hidden
? configuration.headerButton.hidden()
: false)
"
[iconType]="configuration.headerButton.icon"
[label]="configuration.headerButton.label"
[disabled]="
configuration.headerButton.disabled
? configuration.headerButton.disabled()
: false
"
type="ghost"
[circularButton]="true"
data-testid="header-button"
(ionOnClick)="
emitHeaderButtonAction(this.getChildComponentPropertiesValue())
"
></ion-button>
<h4 data-testid="modalTitle">{{ configuration.title }}</h4>
<ion-button
label="Deletar"
Expand Down
2 changes: 2 additions & 0 deletions projects/ion/src/lib/modal/component/modal.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;

h4 {
margin: 0;
Expand All @@ -54,6 +55,7 @@
font-size: 20px;
line-height: 28px;
color: $neutral-color;
margin-right: auto;
}

.close-icon {
Expand Down
70 changes: 70 additions & 0 deletions projects/ion/src/lib/modal/component/modal.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,74 @@ describe('IonModalComponent', () => {
fixture.nativeElement.querySelector('.modal-container').style.width;
expect(modalElement).toBe(`${modalConfig.width}px`);
});

describe('IonModalComponent - Header left button', () => {
const configuration: IonModalConfiguration = {
id: '1',
title: 'Ion Test',

footer: {
showDivider: false,
primaryButton: {
label: 'Ion Cancel',
iconType: 'icon',
},
secondaryButton: {
label: 'Ion Confirm',
iconType: 'icon',
},
},

headerButton: {
icon: 'left',
label: 'voltar',
},
};

it('should not be rendered as default', () => {
expect(screen.queryByTestId('btn-voltar')).not.toBeInTheDocument();
allan-chagas-brisa marked this conversation as resolved.
Show resolved Hide resolved
});

it('should emit event when call emitHeaderButtonAction function', () => {
jest.spyOn(component.ionOnHeaderButtonAction, 'emit');
component.emitHeaderButtonAction(
component.getChildComponentPropertiesValue()
);
expect(component.ionOnHeaderButtonAction.emit).toHaveBeenCalled();
});

it('should be visible as default if the config is informed', () => {
component.setConfig(configuration);
fixture.detectChanges();
expect(screen.getByTestId('btn-voltar')).toBeVisible();
});

it('should be enabled as default', () => {
component.setConfig(configuration);
fixture.detectChanges();
expect(screen.getByTestId('btn-voltar')).toBeEnabled();
});

it('should be disabled when informed', () => {
configuration.headerButton.disabled = (): boolean => true;
component.setConfig(configuration);
fixture.detectChanges();
expect(screen.getByTestId('btn-voltar')).toBeDisabled();
});

it('should render the specified icon', () => {
component.setConfig(configuration);
fixture.detectChanges();
const icon = document.getElementById('ion-icon-left');
expect(icon).toBeVisible();
});

it('should be hidden when informed', () => {
configuration.headerButton.hidden = (): boolean => true;

component.setConfig(configuration);
fixture.detectChanges();
expect(screen.queryByTestId('btn-voltar')).not.toBeInTheDocument();
});
});
});
5 changes: 5 additions & 0 deletions projects/ion/src/lib/modal/component/modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class IonModalComponent implements OnInit, OnDestroy {
@Input() configuration: IonModalConfiguration = {};

@Output()
ionOnHeaderButtonAction = new EventEmitter<IonModalResponse | undefined>();
ionOnClose = new EventEmitter<IonModalResponse | undefined>();

public DEFAULT_WIDTH = 500;
Expand Down Expand Up @@ -88,6 +89,10 @@ export class IonModalComponent implements OnInit, OnDestroy {
Object.assign(instance, params);
}

emitHeaderButtonAction(valueToEmit: IonModalResponse | undefined): void {
this.ionOnHeaderButtonAction.emit(valueToEmit);
}

ngOnInit(): void {
this.setDefaultConfig();
const factory = this.resolver.resolveComponentFactory(this.componentToBody);
Expand Down
18 changes: 18 additions & 0 deletions projects/ion/src/lib/modal/modal.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,22 @@ describe('ModalService', () => {
state: 'ceara',
});
});

it('should call emitHeaderAction when ionOnHeaderButtonAction fires', () => {
jest.spyOn(modalService, 'emitHeaderAction');

modalService.open(SelectMockComponent, {
headerButton: {
icon: 'left',
label: 'voltar',
},
});

fireEvent.click(screen.getByTestId('btn-voltar'));
fixture.detectChanges();

expect(modalService.emitHeaderAction).toHaveBeenCalledWith({
state: 'ceara',
});
});
});
12 changes: 12 additions & 0 deletions projects/ion/src/lib/modal/modal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
providedIn: 'root',
})
export class IonModalService {
public readonly ionOnHeaderButtonAction = new Subject<SafeAny>();
allan-chagas-brisa marked this conversation as resolved.
Show resolved Hide resolved
private modalComponentRef!: ComponentRef<IonModalComponent>;
private componentSubscriber!: Subject<IonModalResponse | unknown>;

Expand Down Expand Up @@ -62,6 +63,13 @@ export class IonModalService {
this.emitValueAndCloseModal(valueFromModal);
}
);

this.modalComponentRef.instance.ionOnHeaderButtonAction.subscribe(
(valueFromModal: IonModalResponse) => {
this.emitHeaderAction(valueFromModal);
}
);

this.componentSubscriber = new Subject<IonModalResponse | unknown>();
return this.componentSubscriber.asObservable();
}
Expand All @@ -71,6 +79,10 @@ export class IonModalService {
this.closeModal();
}

emitHeaderAction(valueToEmit: IonModalResponse | unknown): void {
this.ionOnHeaderButtonAction.next(valueToEmit);
}

closeModal(): void {
if (this.modalComponentRef) {
this.appRef.detachView(this.modalComponentRef.hostView);
Expand Down
10 changes: 9 additions & 1 deletion projects/ion/src/lib/modal/models/modal.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IonButtonProps } from '../../core/types';
import { IconType, IonButtonProps } from '../../core/types';
import { SafeAny } from '../../utils/safe-any';

export interface IonModalConfiguration {
Expand All @@ -8,6 +8,7 @@ export interface IonModalConfiguration {
showOverlay?: boolean;
overlayCanDismiss?: boolean;
ionParams?: SafeAny;
headerButton?: IonModalHeaderButton;

footer?: IonModalFooterConfiguration;
}
Expand All @@ -20,6 +21,13 @@ export interface IonModalFooterConfiguration {
secondaryButton?: IonButtonProps;
}

interface IonModalHeaderButton {
icon: IconType;
label?: string;
disabled?: () => boolean;
hidden?: () => boolean;
}

export interface IonModalResponse {
[key: string]: unknown;
}
28 changes: 28 additions & 0 deletions stories/Modal.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ Função responsável pela renderização do modal. Ela recebe como parâmetros:
height="350px"
args={{
componentToBody: SelectMockComponent,
modalConfig: {
headerButton: {
icon: 'left',
label: 'test',
disabled: () => false,
hidden: () => false,
},
},
}}
decorators={[
moduleMetadata({
Expand All @@ -64,6 +72,26 @@ Função responsável pela renderização do modal. Ela recebe como parâmetros:

<br />

## ionOnHeaderButtonAction

Um subject que irá informar ao Body Component do clique no HeaderLeftButton. O usuário deve instanciar o ModalService no componente usado como body e se inscrever nesse subject.

> Ideal para casos onde precisamos que o bodyComponent altere seu estado a partir da action do headerButton. Veja o exemplo abaixo:

<Source
language="js"
dark
format={false}
code={dedent`
export class BodyComponent implements OnInit {
constructor(private modalService: IonModalService) {}
ngOnInit(): void {
this.modalService.ionOnHeaderButtonAction.subscribe();
}
}
`}
/>

## EmitValueAndCloseModal

Recebe como parâmetro um valor, seguindo a interface _IonModalResponse_, a ser emitido e faz o fechamento do modal.
Expand Down
Loading