From 63024a619d1d1709a1da6d7228db32d4e15c1e41 Mon Sep 17 00:00:00 2001 From: Allan Oliveira <138060158+allan-chagas-brisa@users.noreply.github.com> Date: Thu, 28 Sep 2023 17:09:43 -0300 Subject: [PATCH] 835 add left button to ion header (#840) * feat: modal header button * fix: changes on mock * fix: header spacing * feat: modal body content emition and test adjustments * refactor: readonly added to the subject * docs: service subscription * test: pr sugested adjustments * refactor: pr sugested adjustments --------- Co-authored-by: Iury Nogueira --- .../lib/modal/component/modal.component.html | 21 ++++++ .../lib/modal/component/modal.component.scss | 2 + .../modal/component/modal.component.spec.ts | 70 +++++++++++++++++++ .../lib/modal/component/modal.component.ts | 5 ++ .../ion/src/lib/modal/modal.service.spec.ts | 18 +++++ projects/ion/src/lib/modal/modal.service.ts | 12 ++++ .../src/lib/modal/models/modal.interface.ts | 10 ++- stories/Modal.stories.mdx | 28 ++++++++ 8 files changed, 165 insertions(+), 1 deletion(-) diff --git a/projects/ion/src/lib/modal/component/modal.component.html b/projects/ion/src/lib/modal/component/modal.component.html index 975a5be1c..c952b0427 100644 --- a/projects/ion/src/lib/modal/component/modal.component.html +++ b/projects/ion/src/lib/modal/component/modal.component.html @@ -16,6 +16,27 @@ aria-modal="true" >
+

{{ configuration.title }}

{ 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(); + }); + + 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(); + }); + }); }); diff --git a/projects/ion/src/lib/modal/component/modal.component.ts b/projects/ion/src/lib/modal/component/modal.component.ts index b41748dad..b83585328 100644 --- a/projects/ion/src/lib/modal/component/modal.component.ts +++ b/projects/ion/src/lib/modal/component/modal.component.ts @@ -34,6 +34,7 @@ export class IonModalComponent implements OnInit, OnDestroy { @Input() configuration: IonModalConfiguration = {}; @Output() + ionOnHeaderButtonAction = new EventEmitter(); ionOnClose = new EventEmitter(); public DEFAULT_WIDTH = 500; @@ -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); diff --git a/projects/ion/src/lib/modal/modal.service.spec.ts b/projects/ion/src/lib/modal/modal.service.spec.ts index b68b08a7d..c203ac137 100644 --- a/projects/ion/src/lib/modal/modal.service.spec.ts +++ b/projects/ion/src/lib/modal/modal.service.spec.ts @@ -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', + }); + }); }); diff --git a/projects/ion/src/lib/modal/modal.service.ts b/projects/ion/src/lib/modal/modal.service.ts index 5d2bbe73e..dcfb07943 100644 --- a/projects/ion/src/lib/modal/modal.service.ts +++ b/projects/ion/src/lib/modal/modal.service.ts @@ -21,6 +21,7 @@ import { providedIn: 'root', }) export class IonModalService { + public readonly ionOnHeaderButtonAction = new Subject(); private modalComponentRef!: ComponentRef; private componentSubscriber!: Subject; @@ -62,6 +63,13 @@ export class IonModalService { this.emitValueAndCloseModal(valueFromModal); } ); + + this.modalComponentRef.instance.ionOnHeaderButtonAction.subscribe( + (valueFromModal: IonModalResponse) => { + this.emitHeaderAction(valueFromModal); + } + ); + this.componentSubscriber = new Subject(); return this.componentSubscriber.asObservable(); } @@ -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); diff --git a/projects/ion/src/lib/modal/models/modal.interface.ts b/projects/ion/src/lib/modal/models/modal.interface.ts index a254decd4..e6fb34b31 100644 --- a/projects/ion/src/lib/modal/models/modal.interface.ts +++ b/projects/ion/src/lib/modal/models/modal.interface.ts @@ -1,4 +1,4 @@ -import { IonButtonProps } from '../../core/types'; +import { IconType, IonButtonProps } from '../../core/types'; import { SafeAny } from '../../utils/safe-any'; export interface IonModalConfiguration { @@ -8,6 +8,7 @@ export interface IonModalConfiguration { showOverlay?: boolean; overlayCanDismiss?: boolean; ionParams?: SafeAny; + headerButton?: IonModalHeaderButton; footer?: IonModalFooterConfiguration; } @@ -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; } diff --git a/stories/Modal.stories.mdx b/stories/Modal.stories.mdx index 92827c5ad..c9074a076 100644 --- a/stories/Modal.stories.mdx +++ b/stories/Modal.stories.mdx @@ -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({ @@ -64,6 +72,26 @@ Função responsável pela renderização do modal. Ela recebe como parâmetros:
+## 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: + + + ## EmitValueAndCloseModal Recebe como parâmetro um valor, seguindo a interface _IonModalResponse_, a ser emitido e faz o fechamento do modal.