From 88d3bd94142c1cd824f6510fcb1ecbb122e5affe Mon Sep 17 00:00:00 2001 From: Allan Oliveira <138060158+allan-chagas-brisa@users.noreply.github.com> Date: Thu, 28 Sep 2023 08:26:58 -0300 Subject: [PATCH 1/2] fix: ion tooltip close on scroll (#848) --- .../ion/src/lib/tooltip/tooltip.directive.spec.ts | 12 ++++++++++++ projects/ion/src/lib/tooltip/tooltip.directive.ts | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/projects/ion/src/lib/tooltip/tooltip.directive.spec.ts b/projects/ion/src/lib/tooltip/tooltip.directive.spec.ts index 780825313..0ef69d7f4 100644 --- a/projects/ion/src/lib/tooltip/tooltip.directive.spec.ts +++ b/projects/ion/src/lib/tooltip/tooltip.directive.spec.ts @@ -7,6 +7,7 @@ import { TooltipTrigger, } from '../core/types'; import { IonTooltipModule } from './tooltip.module'; +import { IonTooltipDirective } from './tooltip.directive'; @Component({ template: ` @@ -130,6 +131,17 @@ describe('Directive: Tooltip', () => { ); }); + it('should close the tooltip when scrolling the page', async () => { + await sut(); + const directive = IonTooltipDirective.prototype; + jest.spyOn(directive, 'onScroll'); + + fireEvent.mouseEnter(screen.getByTestId('hostTooltip')); + fireEvent.scroll(window); + expect(directive.onScroll).toBeCalled(); + expect(screen.queryByTestId('ion-tooltip')).not.toBeInTheDocument(); + }); + describe('trigger: click', () => { afterEach(async () => { fireEvent.click(screen.getByTestId('hostTooltip')); diff --git a/projects/ion/src/lib/tooltip/tooltip.directive.ts b/projects/ion/src/lib/tooltip/tooltip.directive.ts index 8fcafee40..417a255d1 100644 --- a/projects/ion/src/lib/tooltip/tooltip.directive.ts +++ b/projects/ion/src/lib/tooltip/tooltip.directive.ts @@ -157,6 +157,10 @@ export class IonTooltipDirective implements OnDestroy, OnInit { } } + @HostListener('window:scroll') onScroll(): void { + this.destroyComponent(); + } + ngOnDestroy(): void { this.destroyComponent(); this.subscription$.unsubscribe(); From bdeb1c9f2dcd47e19d30af3bdf7e7f5a8691d5d4 Mon Sep 17 00:00:00 2001 From: pedro-martins-brisa <138057813+pedro-martins-brisa@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:36:01 -0300 Subject: [PATCH 2/2] feat: add param to not close popover when click in action #837 (#838) :sparkles: * feat: keep open action * fix: changing interface button props popover * refactor: change action close validator * refactor: change name validator action close * refactor: change private function --------- Co-authored-by: Iury Nogueira --- projects/ion/src/lib/core/types/popover.ts | 7 +++- .../popover/component/popover.component.scss | 1 + .../popover/component/popover.component.ts | 7 ++-- .../keep-open-popover-action.component.ts | 41 +++++++++++++++++++ .../src/lib/popover/popover.directive.spec.ts | 28 +++++++++++-- .../ion/src/lib/popover/popover.directive.ts | 39 +++++++++++------- stories/PopoverDirectiveKeepAction.stories.ts | 41 +++++++++++++++++++ 7 files changed, 142 insertions(+), 22 deletions(-) create mode 100644 projects/ion/src/lib/popover/mock/keep-open-popover-action.component.ts create mode 100644 stories/PopoverDirectiveKeepAction.stories.ts diff --git a/projects/ion/src/lib/core/types/popover.ts b/projects/ion/src/lib/core/types/popover.ts index 1d9860593..424b1306e 100644 --- a/projects/ion/src/lib/core/types/popover.ts +++ b/projects/ion/src/lib/core/types/popover.ts @@ -1,4 +1,5 @@ import { TemplateRef } from '@angular/core'; + import { IonButtonProps } from './button'; import { IconType } from './icon'; @@ -16,10 +17,14 @@ export enum PopoverPosition { DEFAULT = 'bottomRight', } +export interface PopoverButtonsProps extends IonButtonProps { + keepOpenAfterAction?: boolean; +} + export interface PopoverProps { ionPopoverTitle: string; ionPopoverBody: TemplateRef; - ionPopoverActions?: IonButtonProps[]; + ionPopoverActions?: PopoverButtonsProps[]; ionPopoverIcon?: IconType; ionPopoverIconColor?: string; ionPopoverIconClose?: boolean; diff --git a/projects/ion/src/lib/popover/component/popover.component.scss b/projects/ion/src/lib/popover/component/popover.component.scss index 2b5d01baa..cbf3dfcad 100644 --- a/projects/ion/src/lib/popover/component/popover.component.scss +++ b/projects/ion/src/lib/popover/component/popover.component.scss @@ -121,6 +121,7 @@ $arrow-size: 4.5px; display: flex; background-color: $neutral-2; justify-content: space-between; + border-radius: spacing(1) spacing(1) 0 0; align-items: center; padding: spacing(1.5) spacing(2); diff --git a/projects/ion/src/lib/popover/component/popover.component.ts b/projects/ion/src/lib/popover/component/popover.component.ts index 81f17ef21..0bae1b74e 100644 --- a/projects/ion/src/lib/popover/component/popover.component.ts +++ b/projects/ion/src/lib/popover/component/popover.component.ts @@ -1,7 +1,8 @@ import { Component, Input, TemplateRef } from '@angular/core'; import { Subject } from 'rxjs'; -import { PopoverPosition } from '../../core/types/popover'; -import { IconType, IonButtonProps } from '../../core/types'; + +import { IconType } from '../../core/types'; +import { PopoverButtonsProps, PopoverPosition } from '../../core/types/popover'; const PRIMARY_6 = '#0858ce'; @@ -15,7 +16,7 @@ export class IonPopoverComponent { @Input() ionPopoverTitle: string; @Input() ionPopoverKeep: boolean; @Input() ionPopoverBody: TemplateRef; - @Input() ionPopoverActions?: IonButtonProps[]; + @Input() ionPopoverActions?: PopoverButtonsProps[]; @Input() ionPopoverIcon?: IconType; @Input() ionPopoverIconColor? = PRIMARY_6; @Input() ionPopoverIconClose = false; diff --git a/projects/ion/src/lib/popover/mock/keep-open-popover-action.component.ts b/projects/ion/src/lib/popover/mock/keep-open-popover-action.component.ts new file mode 100644 index 000000000..b664a3f98 --- /dev/null +++ b/projects/ion/src/lib/popover/mock/keep-open-popover-action.component.ts @@ -0,0 +1,41 @@ +import { Component } from '@angular/core'; +import { PopoverPosition } from '../../core/types/popover'; + +@Component({ + template: ` + +
+ + + {{ args.ionPopoverBody }} +
+ `, +}) +export class KeepOpenPopoverActionComponent { + args = { + ionPopoverTitle: 'Desafio na Jornada', + ionPopoverBody: + 'Você pode escolher avançar corajosamente para a próxima etapa da jornada ou optar por explorar o caminho anterior.', + ionPopoverPosition: PopoverPosition.DEFAULT, + ionPopoverIconClose: true, + ionPopoverActions: [ + { label: 'voltar', keepOpenAfterAction: true }, + { label: 'continuar', keepOpenAfterAction: true }, + ], + }; +} diff --git a/projects/ion/src/lib/popover/popover.directive.spec.ts b/projects/ion/src/lib/popover/popover.directive.spec.ts index 5c6102820..44b1047c7 100644 --- a/projects/ion/src/lib/popover/popover.directive.spec.ts +++ b/projects/ion/src/lib/popover/popover.directive.spec.ts @@ -11,8 +11,9 @@ import { FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; import { fireEvent, render, screen } from '@testing-library/angular'; + import { IonButtonModule } from '../button/button.module'; -import { PopoverPosition } from '../core/types/popover'; +import { PopoverButtonsProps, PopoverPosition } from '../core/types/popover'; import { IonDividerModule } from '../divider/divider.module'; import { IonSharedModule } from '../shared.module'; import { IonPopoverComponent } from './component/popover.component'; @@ -63,7 +64,10 @@ class HostTestComponent { ionPopoverKeep = false; ionPopoverIconClose = true; ionPopoverIcon = 'condominium'; - ionPopoverActions = [{ label: 'action 1' }, { label: 'action 2' }]; + ionPopoverActions: PopoverButtonsProps[] = [ + { label: 'action 1' }, + { label: 'action 2' }, + ]; ionOnFirstAction = firstAction; ionOnSecondAction = secondAction; @@ -181,6 +185,24 @@ describe('Directive: popover', () => { expect(screen.queryByTestId(`popover-${type.dataTestId}`)).toBeNull(); }); + it.each([ + { dataTestId: 'action-1', label: 'voltar' }, + { dataTestId: 'action-2', label: 'continuar' }, + ])( + 'should not close pop when click in $label when to have keepOpenAfterAction', + async (type) => { + await sut({ + ionPopoverActions: [ + { label: 'voltar', keepOpenAfterAction: true }, + { label: 'continuar', keepOpenAfterAction: true }, + ], + }); + fireEvent.click(screen.getByText(textButton)); + fireEvent.click(screen.getByTestId(`btn-${type.label}`)); + expect(screen.getByTestId('ion-popover')).toBeInTheDocument(); + } + ); + it('should emit an event when click on action-1', async () => { await sut(); fireEvent.click(screen.getByText(textButton)); @@ -211,7 +233,7 @@ describe('Directive: popover', () => { await sut(); fireEvent.click(screen.getByText(textButton)); fireEvent.click(document); - expect(screen.queryByTestId('ion-popover')).toBeFalsy(); + expect(screen.queryByTestId('ion-popover')).not.toBeInTheDocument(); }); it('should render popover with custom class', async () => { diff --git a/projects/ion/src/lib/popover/popover.directive.ts b/projects/ion/src/lib/popover/popover.directive.ts index b22460b44..9c5d4006c 100644 --- a/projects/ion/src/lib/popover/popover.directive.ts +++ b/projects/ion/src/lib/popover/popover.directive.ts @@ -1,32 +1,33 @@ +import { DOCUMENT } from '@angular/common'; import { - Directive, - Input, - HostListener, + ApplicationRef, ComponentFactoryResolver, - Injector, - Inject, ComponentRef, - ApplicationRef, - Output, + Directive, EventEmitter, - ViewContainerRef, + HostListener, + Inject, + Injector, + Input, OnDestroy, + Output, TemplateRef, + ViewContainerRef, } from '@angular/core'; -import { DOCUMENT } from '@angular/common'; +import { pick } from 'lodash'; + +import { IconType } from '../core/types'; +import { PopoverButtonsProps, PopoverPosition } from '../core/types/popover'; import { SafeAny } from './../utils/safe-any'; import { IonPopoverComponent } from './component/popover.component'; -import { PopoverPosition } from '../core/types/popover'; import { getPositionsPopover } from './utilsPopover'; -import { IonButtonProps, IconType } from '../core/types'; -import { pick } from 'lodash'; @Directive({ selector: '[ionPopover]' }) export class IonPopoverDirective implements OnDestroy { @Input() ionPopoverTitle: string; @Input() ionPopoverKeep = false; @Input() ionPopoverBody: TemplateRef; - @Input() ionPopoverActions?: IonButtonProps[]; + @Input() ionPopoverActions?: PopoverButtonsProps[]; @Input() ionPopoverIcon?: IconType; @Input() ionPopoverIconColor?: string; @Input() ionPopoverIconClose? = false; @@ -89,14 +90,22 @@ export class IonPopoverDirective implements OnDestroy { ['ionOnSecondAction', this.ionOnSecondAction], ['ionOnClose', this.ionOnClose], ]; - eventSubscriptions.forEach(([event, emitter]) => { + eventSubscriptions.forEach(([event, emitter], index) => { popoverInstance[event].subscribe(() => { - this.closePopover(); + this.handlePopoverAction(index); + emitter.emit(); }); }); } + handlePopoverAction(index: number): void { + const action = this.ionPopoverActions && this.ionPopoverActions[index]; + if (!action || !action.keepOpenAfterAction) { + this.closePopover(); + } + } + setComponentPosition(hostElement: DOMRect): void { const hostPositions = pick(hostElement, ['left', 'right', 'top', 'bottom']); const positions = getPositionsPopover( diff --git a/stories/PopoverDirectiveKeepAction.stories.ts b/stories/PopoverDirectiveKeepAction.stories.ts new file mode 100644 index 000000000..bf1d9efcd --- /dev/null +++ b/stories/PopoverDirectiveKeepAction.stories.ts @@ -0,0 +1,41 @@ +import { CommonModule } from '@angular/common'; +import { Meta, Story } from '@storybook/angular/types-6-0'; + +import { PopoverPosition } from '../projects/ion/src/lib/core/types/popover'; +import { KeepOpenPopoverActionComponent } from '../projects/ion/src/lib/popover/mock/keep-open-popover-action.component'; +import { + IonPopoverModule, + IonSharedModule, +} from '../projects/ion/src/public-api'; + +const Template: Story = ( + args: KeepOpenPopoverActionComponent +) => ({ + component: KeepOpenPopoverActionComponent, + props: { + ...args, + }, + moduleMetadata: { + declarations: [KeepOpenPopoverActionComponent], + imports: [CommonModule, IonSharedModule, IonPopoverModule], + entryComponents: [KeepOpenPopoverActionComponent], + }, +}); + +export const DirectiveWithActionsKeepAction = Template.bind({}); +DirectiveWithActionsKeepAction.args = { + ionPopoverTitle: 'Desafio na Jornada', + ionPopoverBody: + 'Você pode escolher avançar corajosamente para a próxima etapa da jornada ou optar por explorar o caminho anterior.', + ionPopoverPosition: PopoverPosition.DEFAULT, + ionPopoverIconClose: true, + ionPopoverActions: [ + { label: 'voltar', keepOpenAfterAction: true }, + { label: 'continuar', keepOpenAfterAction: true }, + ], +}; + +export default { + title: 'Ion/Data Display/Popover', + component: KeepOpenPopoverActionComponent, +} as Meta;